Kaynağa Gözat

feat(media): 添加图片选择和管理工具类- 实现图片选择功能,支持多选和单选
- 添加图片复制到应用私有目录的逻辑
- 实现图片删除和存在性检查方法
- 添加未使用封面图片的清理功能
- 提供获取歌单封面目录的工具方法- 集成日志记录和错误处理机制

chendeben 9 ay önce
ebeveyn
işleme
e239f17814
1 değiştirilmiş dosya ile 222 ekleme ve 0 silme
  1. 222 0
      entry/src/main/ets/common/util/ImagePickerUtil.ets

+ 222 - 0
entry/src/main/ets/common/util/ImagePickerUtil.ets

@@ -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;