|
@@ -0,0 +1,222 @@
|
|
|
|
|
+import { photoAccessHelper } from '@kit.MediaLibraryKit';
|
|
|
|
|
+import { fileIo } from '@kit.CoreFileKit';
|
|
|
|
|
+import { FileUtil } from '@pura/harmony-utils';
|
|
|
|
|
+import Logger from './Logger';
|
|
|
|
|
+import { Context } from '@kit.AbilityKit';
|
|
|
|
|
+import { fileUri } from '@kit.CoreFileKit';
|
|
|
|
|
+
|
|
|
|
|
+const TAG = 'ImagePickerUtil';
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 图片选择工具类
|
|
|
|
|
+ */
|
|
|
|
|
+export class ImagePickerUtil {
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 选择图片并返回复制到应用私有目录的路径
|
|
|
|
|
+ * @param context 应用上下文
|
|
|
|
|
+ * @param maxSelectCount 最大选择数量,默认1张
|
|
|
|
|
+ * @returns 返回选择的图片路径数组
|
|
|
|
|
+ */
|
|
|
|
|
+ static async selectImages(context: Context, maxSelectCount: number = 1): Promise<string[]> {
|
|
|
|
|
+ try {
|
|
|
|
|
+ Logger.info(TAG, '开始选择图片');
|
|
|
|
|
+
|
|
|
|
|
+ const photoSelectOptions: photoAccessHelper.PhotoSelectOptions = {
|
|
|
|
|
+ MIMEType: photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE,
|
|
|
|
|
+ maxSelectNumber: maxSelectCount
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const photoViewPicker = new photoAccessHelper.PhotoViewPicker();
|
|
|
|
|
+ const photoSelectResult = await photoViewPicker.select(photoSelectOptions);
|
|
|
|
|
+
|
|
|
|
|
+ Logger.info(TAG, `选择了 ${photoSelectResult.photoUris.length} 张图片`);
|
|
|
|
|
+
|
|
|
|
|
+ if (photoSelectResult.photoUris.length === 0) {
|
|
|
|
|
+ return [];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 将选中的图片复制到应用私有目录
|
|
|
|
|
+ const copiedPaths: string[] = [];
|
|
|
|
|
+ for (const uri of photoSelectResult.photoUris) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const copiedPath = await ImagePickerUtil.copyImageToAppDir(context, uri);
|
|
|
|
|
+ if (copiedPath) {
|
|
|
|
|
+ copiedPaths.push(copiedPath);
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ Logger.error(TAG, `复制图片失败: ${(error as Error).message}`);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return copiedPaths;
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ Logger.error(TAG, `选择图片失败: ${(error as Error).message}`);
|
|
|
|
|
+ return [];
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 选择单张图片
|
|
|
|
|
+ * @param context 应用上下文
|
|
|
|
|
+ * @returns 返回选择的图片路径,如果取消选择返回null
|
|
|
|
|
+ */
|
|
|
|
|
+ static async selectSingleImage(context: Context): Promise<string | null> {
|
|
|
|
|
+ const paths = await ImagePickerUtil.selectImages(context, 1);
|
|
|
|
|
+ return paths.length > 0 ? paths[0] : null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 将图片复制到应用私有目录
|
|
|
|
|
+ * @param context 应用上下文
|
|
|
|
|
+ * @param imageUri 图片URI
|
|
|
|
|
+ * @returns 返回复制后的图片路径
|
|
|
|
|
+ */
|
|
|
|
|
+ private static async copyImageToAppDir(context: Context, imageUri: string): Promise<string> {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 生成唯一文件名
|
|
|
|
|
+ const timestamp = Date.now();
|
|
|
|
|
+ const randomSuffix = Math.random().toString(36).substring(2, 8);
|
|
|
|
|
+ const fileName = `playlist_cover_${timestamp}_${randomSuffix}.jpg`;
|
|
|
|
|
+
|
|
|
|
|
+ // 目标路径
|
|
|
|
|
+ const targetDir = context.filesDir + FileUtil.separator + 'playlist_covers';
|
|
|
|
|
+ const targetPath = targetDir + FileUtil.separator + fileName;
|
|
|
|
|
+
|
|
|
|
|
+ // 确保目录存在
|
|
|
|
|
+ try {
|
|
|
|
|
+ fileIo.mkdirSync(targetDir);
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ // 目录可能已存在,忽略错误
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 打开源文件和目标文件
|
|
|
|
|
+ const sourceFile = fileIo.openSync(imageUri, fileIo.OpenMode.READ_ONLY);
|
|
|
|
|
+ const targetFile = fileIo.openSync(targetPath, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);
|
|
|
|
|
+
|
|
|
|
|
+ // 复制文件内容
|
|
|
|
|
+ const buffer = new ArrayBuffer(4096);
|
|
|
|
|
+ let readSize = 0;
|
|
|
|
|
+
|
|
|
|
|
+ do {
|
|
|
|
|
+ readSize = fileIo.readSync(sourceFile.fd, buffer);
|
|
|
|
|
+ if (readSize > 0) {
|
|
|
|
|
+ fileIo.writeSync(targetFile.fd, buffer, { length: readSize });
|
|
|
|
|
+ }
|
|
|
|
|
+ } while (readSize > 0);
|
|
|
|
|
+
|
|
|
|
|
+ // 关闭文件
|
|
|
|
|
+ fileIo.closeSync(sourceFile.fd);
|
|
|
|
|
+ fileIo.closeSync(targetFile.fd);
|
|
|
|
|
+
|
|
|
|
|
+ // 转换为URI格式(关键步骤!)
|
|
|
|
|
+ const uriPath = fileUri.getUriFromPath(targetPath);
|
|
|
|
|
+ Logger.info(TAG, `图片复制成功: ${uriPath}`);
|
|
|
|
|
+ return uriPath;
|
|
|
|
|
+
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ Logger.error(TAG, `复制图片失败: ${(error as Error).message}`);
|
|
|
|
|
+ throw new Error(`复制图片失败: ${(error as Error).message}`);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 删除歌单封面图片
|
|
|
|
|
+ * @param imagePath 图片路径
|
|
|
|
|
+ */
|
|
|
|
|
+ static deleteImage(imagePath: string): void {
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (imagePath && ImagePickerUtil.checkFileExists(imagePath)) {
|
|
|
|
|
+ fileIo.unlinkSync(imagePath);
|
|
|
|
|
+ Logger.info(TAG, `删除图片成功: ${imagePath}`);
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ Logger.error(TAG, `删除图片失败: ${(error as Error).message}`);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 检查文件是否存在
|
|
|
|
|
+ * @param filePath 文件路径
|
|
|
|
|
+ * @returns 是否存在
|
|
|
|
|
+ */
|
|
|
|
|
+ private static checkFileExists(filePath: string): boolean {
|
|
|
|
|
+ try {
|
|
|
|
|
+ fileIo.accessSync(filePath);
|
|
|
|
|
+ return true;
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 检查图片文件是否存在
|
|
|
|
|
+ * @param imagePath 图片路径
|
|
|
|
|
+ * @returns 是否存在
|
|
|
|
|
+ */
|
|
|
|
|
+ static imageExists(imagePath: string): boolean {
|
|
|
|
|
+ if (!imagePath) {
|
|
|
|
|
+ Logger.info(TAG, '图片路径为空');
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ const exists = ImagePickerUtil.checkFileExists(imagePath);
|
|
|
|
|
+ Logger.info(TAG, `检查图片是否存在: ${imagePath}, 结果: ${exists}`);
|
|
|
|
|
+ return exists;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 获取歌单封面目录
|
|
|
|
|
+ * @param context 应用上下文
|
|
|
|
|
+ * @returns 歌单封面目录路径
|
|
|
|
|
+ */
|
|
|
|
|
+ static getPlaylistCoverDir(context: Context): string {
|
|
|
|
|
+ return context.filesDir + FileUtil.separator + 'playlist_covers';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 清理未使用的封面图片
|
|
|
|
|
+ * @param context 应用上下文
|
|
|
|
|
+ * @param usedPaths 正在使用的图片路径列表
|
|
|
|
|
+ */
|
|
|
|
|
+ static async cleanupUnusedCovers(context: Context, usedPaths: string[]): Promise<void> {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const coverDir = ImagePickerUtil.getPlaylistCoverDir(context);
|
|
|
|
|
+
|
|
|
|
|
+ // 检查目录是否存在
|
|
|
|
|
+ if (!ImagePickerUtil.checkFileExists(coverDir)) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 读取目录中的所有文件
|
|
|
|
|
+ const files = fileIo.listFileSync(coverDir);
|
|
|
|
|
+ const usedFileNames = new Set<string>();
|
|
|
|
|
+
|
|
|
|
|
+ // 将使用中的路径转换为文件名
|
|
|
|
|
+ for (const path of usedPaths) {
|
|
|
|
|
+ if (path && path.includes('playlist_covers' + FileUtil.separator)) {
|
|
|
|
|
+ const fileName = path.substring(path.lastIndexOf(FileUtil.separator) + 1);
|
|
|
|
|
+ usedFileNames.add(fileName);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 删除未使用的文件
|
|
|
|
|
+ for (const fileName of files) {
|
|
|
|
|
+ if (!usedFileNames.has(fileName)) {
|
|
|
|
|
+ const filePath = coverDir + FileUtil.separator + fileName;
|
|
|
|
|
+ try {
|
|
|
|
|
+ fileIo.unlinkSync(filePath);
|
|
|
|
|
+ Logger.info(TAG, `清理未使用的封面: ${filePath}`);
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ Logger.error(TAG, `清理封面失败: ${filePath}, 错误: ${(error as Error).message}`);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Logger.info(TAG, '封面清理完成');
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ Logger.error(TAG, `清理封面失败: ${(error as Error).message}`);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export default ImagePickerUtil;
|