瀏覽代碼

fix: 修复权限问题导致无法获取图片

Destiny 9 月之前
父節點
當前提交
aa62e00f4e

+ 588 - 141
lib/handler/photo_scan_handler.dart

@@ -1,6 +1,10 @@
+import 'dart:async';
 import 'dart:io';
 
 import 'package:classify_photo/classify_photo.dart';
+import 'package:clean/widget/multi_segment_circle_indicator.dart';
+import 'package:disk_space/disk_space.dart';
+import 'package:flutter/Material.dart';
 import 'package:get/get.dart';
 import 'package:permission_handler/permission_handler.dart';
 import 'package:wechat_assets_picker/wechat_assets_picker.dart';
@@ -31,186 +35,629 @@ class PhotoScanHandler {
   // 相似照片
   static RxList<AssetEntity> similarPhotos = <AssetEntity>[].obs;
 
-  /// 执行所有的照片处理操作
-  static Future<void> handleAllPhotos() async {
-    var request = Platform.isIOS
-        ? await Permission.photos.request()
-        : await Permission.storage.request();
-
-    if (request.isGranted) {
-      PhotoManager.clearFileCache();
-      await handleScreenPhotos();
-      await handleBlurryPhotos();
-      await handlePeoplePhotos();
-      await handleSimilarPhotos();
-      if (Platform.isAndroid) {
-        await handleAndroidPhotos();
-      }
-    } else if (request.isPermanentlyDenied) {
-      ToastUtil.show("Please enable the album permission");
-      openAppSettings();
-    } else {
-      ToastUtil.show("Please enable the album permission");
-
-      isSimilarScanned.value = false;
-      isPeopleScanned.value = false;
-      isBlurryScanned.value = false;
-      isScreenShotScanned.value = false;
+  // 添加一个标志来跟踪当前扫描状态
+  bool _isScanningCancelled = false;
+
+  List<StreamSubscription> _scanningTasks = [];
+
+  static Rx<double> totalSpace = 0.0.obs;
+  static Rx<double> usedSpace = 0.0.obs;
+  static Rx<double> photoSpace = 0.0.obs;
+  static Rx<double> freeSpace = 0.0.obs;
+
+  static Rx<String> totalSpaceStr = "0.0 GB".obs;
+  static Rx<String> usedSpaceStr = "0.0 GB".obs;
+  static Rx<String> photoSpaceStr = "0.0 GB".obs;
+  static Rx<String> freeSpaceStr = "0.0 GB".obs;
+
+  // 计算已用存储百分比
+  static double get usedSpacePercentage => (usedSpace.value / totalSpace.value) * 100;
+
+  // 计算照片占用存储百分比
+  static double get photoSpacePercentage =>
+      (photoSpace.value / totalSpace.value) * 100;
+
+  // 计算可用存储百分比
+  static double get freeSpacePercentage => (freeSpace.value / totalSpace.value) * 100;
+
+  static List<PieData> get pieDataList => [
+    PieData("PhotoSpace", photoSpacePercentage, Colors.blue),
+    PieData("OtherUsed", usedSpacePercentage - photoSpacePercentage,
+        Colors.red),
+    PieData("totalSpace", totalSpace.value, Colors.grey.withOpacity(0.1)),
+  ];
+
+  // 存储是否扫描完成
+  static RxBool isStorageScanned = false.obs;
+
+  // 监听照片库变化
+  void _listenToPhotoLibraryChanges() {
+    PhotoManager.addChangeCallback((change) {
+      print("照片库发生变化: $change");
+
+      // 取消当前正在进行的扫描
+      _cancelCurrentScan();
+
+      // 延迟一小段时间后重新开始扫描
+      Future.delayed(Duration(milliseconds: 500), () {
+        _restartScan();
+      });
+    });
+
+    // 开始监听
+    PhotoManager.startChangeNotify();
+  }
+
+  // 取消当前扫描
+  void _cancelCurrentScan() {
+    _isScanningCancelled = true;
+
+    // 取消所有正在进行的异步任务
+    for (var subscription in _scanningTasks) {
+      subscription.cancel();
     }
+    _scanningTasks.clear();
+
+    print("已取消当前扫描任务");
   }
 
-  /// 截图照片
-  static Future<bool> handleScreenPhotos() async {
+// 重新开始扫描
+  void _restartScan() {
+    _isScanningCancelled = false;
+
+    isSimilarScanned.value = false;
+    isPeopleScanned.value = false;
+    isBlurryScanned.value = false;
+    isScreenShotScanned.value = false;
+
+    // 清除之前的结果
+    similarPhotos.clear();
+    peoplePhotos.clear();
+    locationPhoto.value = null;
+    screenshotPhoto.value = null;
+    blurryPhoto.value = null;
+
+
+    print("开始重新扫描照片");
+
+    // 重新开始扫描流程
+    startScanningProcess();
+  }
+
+  // 开始扫描过程
+  Future<void> startScanningProcess() async {
+    // 确保在开始新扫描前取消标志被重置
+    _isScanningCancelled = false;
+
+    // 清空之前的任务
+    _scanningTasks.clear();
+
     try {
-      final photos = await _photoClassify.getScreenshots();
-      print('获取截图照片完成: ${photos?.length ?? 0} 组照片');
-      isScreenShotScanned.value = true;
-      if (photos != null) {
-        await ImagePickerUtil.updatePhotos(photos);
-        if (ImagePickerUtil.screenshotPhotos.isNotEmpty) {
-          var asset = ImagePickerUtil.screenshotPhotos.first;
-          screenshotPhoto.value = asset;
-        }
+      // 按顺序执行扫描任务
+      print('开始顺序扫描照片');
+
+      // 1. 先扫描截图照片
+      if (!_isScanningCancelled) {
+        var screenTask = _handleScreenPhotosWithCancellation();
+        _scanningTasks.add(screenTask);
+        // 等待截图扫描完成
+        await Future.delayed(Duration(milliseconds: 500));
       }
-    } catch (e, stackTrace) {
-      print('获取截图失败: $e\n$stackTrace');
+
+      // 2. 再扫描模糊照片
+      if (!_isScanningCancelled) {
+        var blurryTask = _handleBlurryPhotosWithCancellation();
+        _scanningTasks.add(blurryTask);
+        // 等待模糊照片扫描完成
+        await Future.delayed(Duration(milliseconds: 500));
+      }
+
+      // 3. 然后扫描人物照片
+      if (!_isScanningCancelled) {
+        var peopleTask = _handlePeoplePhotosWithCancellation();
+        _scanningTasks.add(peopleTask);
+        // 等待人物照片扫描完成
+        await Future.delayed(Duration(milliseconds: 500));
+      }
+
+      // 4. 最后扫描相似照片
+      if (!_isScanningCancelled) {
+        var similarTask = _handleSimilarPhotosWithCancellation();
+        _scanningTasks.add(similarTask);
+      }
+
+      print('所有扫描任务已启动');
+    } catch (e) {
+      print('扫描过程中出错: $e');
     }
-    isScreenShotScanned.value = true;
-    return true;
   }
 
-  /// 模糊照片
-  static Future<bool> handleBlurryPhotos() async {
+  Future<void> getStorageInfo() async {
+
+    if (Platform.isAndroid) {
+      // 延迟3秒
+      Future.delayed(const Duration(seconds: 3), () {
+        isStorageScanned.value = true;
+        totalSpace.value = 200.0;
+        usedSpace.value = 50.0;
+        photoSpace.value = 30.0;
+        freeSpace.value = 150.0;
+      });
+    }
+
+    final totalSpaceGB = (await DiskSpace.getTotalDiskSpace ?? 0) / 1000;
+    final freeSpaceGB = (await DiskSpace.getFreeDiskSpace ?? 0) / 1024;
+    final usedSpaceGB = ((await DiskSpace.getTotalDiskSpace ?? 0) - (await DiskSpace.getFreeDiskSpace ?? 0)) / 1024;
+
+    totalSpaceStr.value = ImagePickerUtil.formatFileSize(
+        (await DiskSpace.getTotalDiskSpace ?? 0).round(),
+        decimals: 1);
+    freeSpaceStr.value = ImagePickerUtil.formatFileSize(
+        (await DiskSpace.getFreeDiskSpace ?? 0).round(),
+        decimals: 1);
+    usedSpaceStr.value = ImagePickerUtil.formatFileSize(
+        ((await DiskSpace.getTotalDiskSpace ?? 0) - (await DiskSpace.getFreeDiskSpace ?? 0)).round(),
+        decimals: 1);
+
+    totalSpace.value = totalSpaceGB.round().toDouble();
+    freeSpace.value = freeSpaceGB;
+    usedSpace.value = usedSpaceGB;
+
+    final classifyPhoto = ClassifyPhoto();
+
     try {
-      final photos = await _photoClassify.getBlurryPhotos();
-      print('获取模糊照片完成: ${photos?.length ?? 0} 组照片');
-      isBlurryScanned.value = true;
-      if (photos != null) {
-        await ImagePickerUtil.updatePhotos(photos);
-        if (ImagePickerUtil.blurryPhotos.isNotEmpty) {
-          var asset = ImagePickerUtil.blurryPhotos.first;
-          blurryPhoto.value = asset;
+      final storageInfo = await classifyPhoto.getStorageInfo();
+
+      // 转换为 GB
+      final totalSpaceGB = storageInfo['totalSpace']! / (1000 * 1000 * 1000);
+      final freeSpaceGB = storageInfo['freeSpace']! / (1024 * 1024 * 1024);
+      final usedSpaceGB = storageInfo['usedSpace']! / (1024 * 1024 * 1024);
+      final photoSpaceGB = storageInfo['photoSpace']! / (1024 * 1024 * 1024);
+
+      totalSpaceStr.value = ImagePickerUtil.formatFileSize(
+          storageInfo['totalSpace']!,
+          decimals: 1);
+      freeSpaceStr.value = ImagePickerUtil.formatFileSize(
+          storageInfo['freeSpace']!,
+          decimals: 1);
+      usedSpaceStr.value = ImagePickerUtil.formatFileSize(
+          storageInfo['usedSpace']!,
+          decimals: 1);
+      photoSpaceStr.value = ImagePickerUtil.formatFileSize(
+          storageInfo['photoSpace']!,
+          decimals: 1);
+
+      totalSpace.value = totalSpaceGB.round().toDouble();
+      freeSpace.value = freeSpaceGB;
+      usedSpace.value = usedSpaceGB;
+      photoSpace.value = photoSpaceGB;
+
+      print('总容量: $totalSpaceStr');
+      print('可用空间: $freeSpaceStr');
+      print('已用空间: $usedSpaceStr');
+      print('照片占用: $photoSpaceStr');
+      isStorageScanned.value = true;
+    } catch (e) {
+      print('获取存储信息失败: $e');
+    }
+  }
+
+  /// 执行所有的照片处理操作
+  Future<void> handleAllPhotos() async {
+    // var request = Platform.isIOS
+    //     ? await Permission.photos.request()
+    //     : await Permission.storage.request();
+    //
+    // if (request.isGranted) {
+    //   PhotoManager.clearFileCache();
+    //   await handleScreenPhotos();
+    //   await handleBlurryPhotos();
+    //   await handlePeoplePhotos();
+    //   await handleSimilarPhotos();
+    //   if (Platform.isAndroid) {
+    //     await handleAndroidPhotos();
+    //   }
+    // } else if (request.isPermanentlyDenied) {
+    //   ToastUtil.show("Please enable the album permission");
+    //   openAppSettings();
+    // } else {
+    //   ToastUtil.show("Please enable the album permission");
+    //
+    //   isSimilarScanned.value = false;
+    //   isPeopleScanned.value = false;
+    //   isBlurryScanned.value = false;
+    //   isScreenShotScanned.value = false;
+  // }
+
+
+      var currentStatus = await Permission.photos.status;
+
+      if (currentStatus.isGranted) {
+        // 已有完全权限,直接扫描
+        PhotoManager.clearFileCache();
+        // _listenToPhotoLibraryChanges(); // 添加监听
+        await handleScreenPhotos();
+        await handleBlurryPhotos();
+        await handlePeoplePhotos();
+        await handleSimilarPhotos();
+      } else if (currentStatus.isLimited) {
+        // 已有有限权限,显示自定义弹窗
+        PhotoManager.clearFileCache();
+        await handleScreenPhotos();
+        await handleBlurryPhotos();
+        await handlePeoplePhotos();
+        await handleSimilarPhotos();
+      } else {
+        // 未授权,请求权限
+        var result = await Permission.photos.request();
+        if (result.isGranted || result.isLimited) {
+          PhotoManager.clearFileCache();
+          getStorageInfo();
+          await handleScreenPhotos();
+          await handleBlurryPhotos();
+          await handlePeoplePhotos();
+          await handleSimilarPhotos();
+        } else {
+          isSimilarScanned.value = true;
+          isPeopleScanned.value = true;
+          isBlurryScanned.value = true;
+          isScreenShotScanned.value = true;
         }
-        return true;
       }
-    } catch (e, stackTrace) {
-      print('获取模糊失败: $e\n$stackTrace');
-    }
-    isBlurryScanned.value = true;
-    return true;
   }
 
-  /// 人物照片
-  static Future<bool> handlePeoplePhotos() async {
-    try {
-      final photos = await _photoClassify.getPeoplePhotos();
-      print('获取人物照片完成: ${photos?.length ?? 0} 组照片');
-      isPeopleScanned.value = true;
-      if (photos != null) {
-        await ImagePickerUtil.updatePhotos(photos);
-        // 处理人物照片
-        peoplePhotos.clear();
-        if (ImagePickerUtil.peoplePhotos.isNotEmpty) {
-          for (var personPhotos in ImagePickerUtil.peoplePhotos) {
-            peoplePhotos.add(personPhotos);
-            if (peoplePhotos.length == 2) {
-              break;
+    // 可取消的截图照片扫描
+    StreamSubscription _handleScreenPhotosWithCancellation() {
+      var controller = StreamController<bool>();
+
+      Future<void> task() async {
+        if (_isScanningCancelled) {
+          controller.add(false);
+          return;
+        }
+
+        try {
+          print('开始获取截图照片');
+          final photoClassify = ClassifyPhoto();
+          final photos = await photoClassify.getScreenshots();
+
+          if (_isScanningCancelled) {
+            controller.add(false);
+            return;
+          }
+
+          print('获取截图照片完成: ${photos?.length ?? 0} 组照片');
+          isScreenShotScanned.value = true;
+
+          if (photos != null) {
+            await ImagePickerUtil.updatePhotos(photos);
+            if (ImagePickerUtil.screenshotPhotos.isNotEmpty) {
+              var asset = ImagePickerUtil.screenshotPhotos.first;
+              screenshotPhoto.value = asset;
             }
           }
+
+          controller.add(true);
+        } catch (e) {
+          print('获取截图照片失败: $e');
+          controller.add(false);
         }
-        return true;
       }
-    } catch (e, stackTrace) {
-      print('获取人物失败: $e\n$stackTrace');
+
+      task();
+
+      return controller.stream.listen((success) {
+        controller.close();
+      });
     }
-    isPeopleScanned.value = true;
-    return true;
-  }
 
-  /// 相似照片
-  static Future<bool> handleSimilarPhotos() async {
-    try {
-      print('开始获取相似照片');
-      final photos = await _photoClassify.getSimilarPhotos();
-      print('获取相似照片完成: ${photos?.length ?? 0} 组照片');
-      isSimilarScanned.value = true;
-      if (photos != null) {
-        await ImagePickerUtil.updatePhotos(photos);
-        similarPhotos.clear();
-        if (ImagePickerUtil.similarPhotos.isNotEmpty) {
-          for (var group in ImagePickerUtil.similarPhotos) {
-            for (var asset in group) {
-              similarPhotos.add(asset);
-              if (similarPhotos.length == 4) {
-                break;
-              }
+// 可取消的模糊照片扫描
+    StreamSubscription _handleBlurryPhotosWithCancellation() {
+      var controller = StreamController<bool>();
+
+      Future<void> task() async {
+        if (_isScanningCancelled) {
+          controller.add(false);
+          return;
+        }
+
+        try {
+          print('开始获取模糊照片');
+          final photoClassify = ClassifyPhoto();
+          final photos = await photoClassify.getBlurryPhotos();
+
+          if (_isScanningCancelled) {
+            controller.add(false);
+            return;
+          }
+
+          print('获取模糊照片完成: ${photos?.length ?? 0} 组照片');
+          isBlurryScanned.value = true;
+
+          if (photos != null) {
+            await ImagePickerUtil.updatePhotos(photos);
+            if (ImagePickerUtil.blurryPhotos.isNotEmpty) {
+              var asset = ImagePickerUtil.blurryPhotos.first;
+              blurryPhoto.value = asset;
             }
           }
+
+          controller.add(true);
+        } catch (e) {
+          print('获取模糊照片失败: $e');
+          controller.add(false);
         }
-        return true;
       }
-    } catch (e, stackTrace) {
-      print('获取相似失败: $e\n$stackTrace');
+
+      task();
+
+      return controller.stream.listen((success) {
+        controller.close();
+      });
     }
-    isSimilarScanned.value = true;
-    return true;
-  }
 
-  /// Android平台处理方式
-  static Future<void> handleAndroidPhotos() async {
-    final List<AssetEntity> result = await ImagePickerUtil.loadAssets();
-    ImagePickerUtil.peoplePhotos.value = result ?? [];
-    ImagePickerUtil.locationPhotos['location'] = result ?? [];
-    ImagePickerUtil.screenshotPhotos.value = result ?? [];
-    const int batchSize = 10;
-    for (int i = 0; i < result.length; i += batchSize) {
-      int end = (i + batchSize < result.length) ? i + batchSize : result.length;
-      List<AssetEntity> batch = result.sublist(i, end);
-      ImagePickerUtil.similarPhotos.add(batch);
+// 可取消的人物照片扫描
+    StreamSubscription _handlePeoplePhotosWithCancellation() {
+      var controller = StreamController<bool>();
+
+      Future<void> task() async {
+        if (_isScanningCancelled) {
+          controller.add(false);
+          return;
+        }
+
+        try {
+          print('开始获取人物照片');
+          final photoClassify = ClassifyPhoto();
+          final photos = await photoClassify.getPeoplePhotos();
+
+          if (_isScanningCancelled) {
+            controller.add(false);
+            return;
+          }
+
+          print('获取人物照片完成: ${photos?.length ?? 0} 组照片');
+          isPeopleScanned.value = true;
+
+          if (photos != null) {
+            await ImagePickerUtil.updatePhotos(photos);
+
+            // 处理人物照片
+            peoplePhotos.clear();
+            if (ImagePickerUtil.peoplePhotos.isNotEmpty) {
+              for (var personPhotos in ImagePickerUtil.peoplePhotos) {
+                if (_isScanningCancelled) break;
+
+                peoplePhotos.add(personPhotos);
+                if (peoplePhotos.length == 2) {
+                  break;
+                }
+              }
+            }
+          }
+
+          controller.add(true);
+        } catch (e) {
+          print('获取人物照片失败: $e');
+          controller.add(false);
+        }
+      }
+
+      task();
+
+      return controller.stream.listen((success) {
+        controller.close();
+      });
     }
-    ImagePickerUtil.blurryPhotos.value = result ?? [];
 
-    print("handleAndroidPhotos $result");
-    print(
-        "ImagePickerUtil.peoplePhotos.value ${ImagePickerUtil.peoplePhotos.length}");
+// 可取消的相似照片扫描
+    StreamSubscription _handleSimilarPhotosWithCancellation() {
+      var controller = StreamController<bool>();
 
-    isSimilarScanned.value = true;
-    isPeopleScanned.value = true;
-    isBlurryScanned.value = true;
-    isScreenShotScanned.value = true;
+      Future<void> task() async {
+        if (_isScanningCancelled) {
+          controller.add(false);
+          return;
+        }
 
-    similarPhotos.clear();
-    if (ImagePickerUtil.similarPhotos.isNotEmpty) {
-      for (var group in ImagePickerUtil.similarPhotos) {
-        print(
-            " ImagePickerUtil.similarPhotos ${ImagePickerUtil.similarPhotos.length}");
-        for (var asset in group) {
-          similarPhotos.add(asset);
-          if (similarPhotos.length == 4) {
-            break;
+        try {
+          print('开始获取相似照片');
+          final photoClassify = ClassifyPhoto();
+          final photos = await photoClassify.getSimilarPhotos();
+
+          if (_isScanningCancelled) {
+            controller.add(false);
+            return;
+          }
+
+          print('获取相似照片完成: ${photos?.length ?? 0} 组照片');
+          isSimilarScanned.value = true;
+
+          if (photos != null) {
+            await ImagePickerUtil.updatePhotos(photos);
+
+            similarPhotos.clear();
+            if (ImagePickerUtil.similarPhotos.isNotEmpty) {
+              for (var group in ImagePickerUtil.similarPhotos) {
+                if (_isScanningCancelled) break;
+
+                for (var asset in group) {
+                  similarPhotos.add(asset);
+                  if (similarPhotos.length == 4) {
+                    break;
+                  }
+                }
+
+                if (similarPhotos.length == 4) {
+                  break;
+                }
+              }
+            }
           }
+
+          controller.add(true);
+        } catch (e) {
+          print('获取相似照片失败: $e');
+          controller.add(false);
         }
       }
+
+      task();
+
+      return controller.stream.listen((success) {
+        controller.close();
+      });
     }
 
-    peoplePhotos.clear();
-    if (ImagePickerUtil.peoplePhotos.isNotEmpty) {
-      for (var personPhotos in ImagePickerUtil.peoplePhotos) {
-        peoplePhotos.add(personPhotos);
-        if (peoplePhotos.length == 2) {
-          break;
+    /// 截图照片
+    Future<bool> handleScreenPhotos() async {
+      try {
+        final photos = await _photoClassify.getScreenshots();
+        print('获取截图照片完成: ${photos?.length ?? 0} 组照片');
+        isScreenShotScanned.value = true;
+        if (photos != null) {
+          await ImagePickerUtil.updatePhotos(photos);
+          if (ImagePickerUtil.screenshotPhotos.isNotEmpty) {
+            var asset = ImagePickerUtil.screenshotPhotos.first;
+            screenshotPhoto.value = asset;
+          }
+        }
+      } catch (e, stackTrace) {
+        print('获取截图失败: $e\n$stackTrace');
+      }
+      isScreenShotScanned.value = true;
+      return true;
+    }
+
+    /// 模糊照片
+    Future<bool> handleBlurryPhotos() async {
+      try {
+        final photos = await _photoClassify.getBlurryPhotos();
+        print('获取模糊照片完成: ${photos?.length ?? 0} 组照片');
+        isBlurryScanned.value = true;
+        if (photos != null) {
+          await ImagePickerUtil.updatePhotos(photos);
+          if (ImagePickerUtil.blurryPhotos.isNotEmpty) {
+            var asset = ImagePickerUtil.blurryPhotos.first;
+            blurryPhoto.value = asset;
+          }
+          return true;
         }
+      } catch (e, stackTrace) {
+        print('获取模糊失败: $e\n$stackTrace');
       }
+      isBlurryScanned.value = true;
+      return true;
     }
 
-    if (ImagePickerUtil.blurryPhotos.isNotEmpty) {
-      var asset = ImagePickerUtil.blurryPhotos.first;
-      blurryPhoto.value = asset;
+    /// 人物照片
+    Future<bool> handlePeoplePhotos() async {
+      try {
+        final photos = await _photoClassify.getPeoplePhotos();
+        print('获取人物照片完成: ${photos?.length ?? 0} 组照片');
+        isPeopleScanned.value = true;
+        if (photos != null) {
+          await ImagePickerUtil.updatePhotos(photos);
+          // 处理人物照片
+          peoplePhotos.clear();
+          if (ImagePickerUtil.peoplePhotos.isNotEmpty) {
+            for (var personPhotos in ImagePickerUtil.peoplePhotos) {
+              peoplePhotos.add(personPhotos);
+              if (peoplePhotos.length == 2) {
+                break;
+              }
+            }
+          }
+          return true;
+        }
+      } catch (e, stackTrace) {
+        print('获取人物失败: $e\n$stackTrace');
+      }
+      isPeopleScanned.value = true;
+      return true;
+    }
+
+    /// 相似照片
+    Future<bool> handleSimilarPhotos() async {
+      try {
+        print('开始获取相似照片');
+        final photos = await _photoClassify.getSimilarPhotos();
+        print('获取相似照片完成: ${photos?.length ?? 0} 组照片');
+        isSimilarScanned.value = true;
+        if (photos != null) {
+          await ImagePickerUtil.updatePhotos(photos);
+          similarPhotos.clear();
+          if (ImagePickerUtil.similarPhotos.isNotEmpty) {
+            for (var group in ImagePickerUtil.similarPhotos) {
+              for (var asset in group) {
+                similarPhotos.add(asset);
+                if (similarPhotos.length == 4) {
+                  break;
+                }
+              }
+            }
+          }
+          return true;
+        }
+      } catch (e, stackTrace) {
+        print('获取相似失败: $e\n$stackTrace');
+      }
+      isSimilarScanned.value = true;
+      return true;
     }
 
-    if (ImagePickerUtil.screenshotPhotos.isNotEmpty) {
-      var asset = ImagePickerUtil.screenshotPhotos.first;
-      screenshotPhoto.value = asset;
+    /// Android平台处理方式
+    Future<void> handleAndroidPhotos() async {
+      final List<AssetEntity> result = await ImagePickerUtil.loadAssets();
+      ImagePickerUtil.peoplePhotos.value = result ?? [];
+      ImagePickerUtil.locationPhotos['location'] = result ?? [];
+      ImagePickerUtil.screenshotPhotos.value = result ?? [];
+      ImagePickerUtil.similarPhotos.add(result ?? []);
+      ImagePickerUtil.blurryPhotos.value = result ?? [];
+
+      print("handleAndroidPhotos $result");
+      print(
+          "ImagePickerUtil.peoplePhotos.value ${ImagePickerUtil.peoplePhotos
+              .length}");
+
+      isSimilarScanned.value = true;
+      isPeopleScanned.value = true;
+      isBlurryScanned.value = true;
+      isScreenShotScanned.value = true;
+
+      similarPhotos.clear();
+      if (ImagePickerUtil.similarPhotos.isNotEmpty) {
+        for (var group in ImagePickerUtil.similarPhotos) {
+          print(
+              " ImagePickerUtil.similarPhotos ${ImagePickerUtil.similarPhotos
+                  .length}");
+          for (var asset in group) {
+            similarPhotos.add(asset);
+            if (similarPhotos.length == 4) {
+              break;
+            }
+          }
+        }
+      }
+
+      peoplePhotos.clear();
+      if (ImagePickerUtil.peoplePhotos.isNotEmpty) {
+        for (var personPhotos in ImagePickerUtil.peoplePhotos) {
+          peoplePhotos.add(personPhotos);
+          if (peoplePhotos.length == 2) {
+            break;
+          }
+        }
+      }
+
+      if (ImagePickerUtil.blurryPhotos.isNotEmpty) {
+        var asset = ImagePickerUtil.blurryPhotos.first;
+        blurryPhoto.value = asset;
+      }
+
+      if (ImagePickerUtil.screenshotPhotos.isNotEmpty) {
+        var asset = ImagePickerUtil.screenshotPhotos.first;
+        screenshotPhoto.value = asset;
+      }
     }
   }
-}

+ 74 - 82
lib/module/home/home_controller.dart

@@ -14,7 +14,9 @@ import 'package:clean/module/screenshots_blurry/screenshots_view.dart';
 import 'package:clean/module/similar_photo/similar_photo_view.dart';
 import 'package:clean/router/app_pages.dart';
 import 'package:clean/utils/toast_util.dart';
+import 'package:disk_space/disk_space.dart';
 import 'package:flutter/Material.dart';
+import 'package:flutter/cupertino.dart';
 import 'package:get/get.dart';
 import 'package:permission_handler/permission_handler.dart';
 import 'package:wechat_assets_picker/wechat_assets_picker.dart';
@@ -27,25 +29,6 @@ import '../../handler/photo_scan_handler.dart';
 import '../../widget/multi_segment_circle_indicator.dart';
 
 class HomeController extends BaseController {
-  Rx<double> totalSpace = 0.0.obs;
-  Rx<double> usedSpace = 0.0.obs;
-  Rx<double> photoSpace = 0.0.obs;
-  Rx<double> freeSpace = 0.0.obs;
-
-  Rx<String> totalSpaceStr = "".obs;
-  Rx<String> usedSpaceStr = "".obs;
-  Rx<String> photoSpaceStr = "".obs;
-  Rx<String> freeSpaceStr = "".obs;
-
-  // 计算已用存储百分比
-  double get usedSpacePercentage => (usedSpace.value / totalSpace.value) * 100;
-
-  // 计算照片占用存储百分比
-  double get photoSpacePercentage =>
-      (photoSpace.value / totalSpace.value) * 100;
-
-  // 计算可用存储百分比
-  double get freeSpacePercentage => (freeSpace.value / totalSpace.value) * 100;
 
   RxList<String> similarImages =
       List.generate(4, (index) => 'iconHomeNoPhoto').obs;
@@ -78,34 +61,43 @@ class HomeController extends BaseController {
   // 是否扫描完成
   RxBool isBlurryScanned = PhotoScanHandler.isBlurryScanned;
 
-  // 存储是否扫描完成
-  RxBool isStorageScanned = false.obs;
+  Rx<double> totalSpace = PhotoScanHandler.totalSpace;
+  Rx<double> usedSpace = PhotoScanHandler.usedSpace;
+  Rx<double> photoSpace = PhotoScanHandler.photoSpace;
+  Rx<double> freeSpace = PhotoScanHandler.freeSpace;
 
-  UserInfoResponse? get userInfo => userRepository.userInfo.value;
+  Rx<String> totalSpaceStr = PhotoScanHandler.totalSpaceStr;
+  Rx<String> usedSpaceStr = PhotoScanHandler.usedSpaceStr;
+  Rx<String> photoSpaceStr = PhotoScanHandler.photoSpaceStr;
+  Rx<String> freeSpaceStr = PhotoScanHandler.freeSpaceStr;
+
+  // 计算已用存储百分比
+  double get usedSpacePercentage => PhotoScanHandler.usedSpacePercentage;
+
+  // 计算照片占用存储百分比
+  double get photoSpacePercentage =>
+      (photoSpace.value / totalSpace.value) * 100;
+
+  // 计算可用存储百分比
+  double get freeSpacePercentage => (freeSpace.value / totalSpace.value) * 100;
 
   List<PieData> get pieDataList => [
-        PieData("PhotoSpace", photoSpacePercentage, Colors.blue),
-        PieData("OtherUsed", usedSpacePercentage - photoSpacePercentage,
-            Colors.red),
-        PieData("totalSpace", totalSpace.value, Colors.grey.withOpacity(0.1)),
-      ];
+    PieData("PhotoSpace", photoSpacePercentage, Colors.blue),
+    PieData("OtherUsed", usedSpacePercentage - photoSpacePercentage,
+        Colors.red),
+    PieData("totalSpace", totalSpace.value, Colors.grey.withOpacity(0.1)),
+  ];
+
+  // 存储是否扫描完成
+  RxBool isStorageScanned =  PhotoScanHandler.isStorageScanned;
+
+  UserInfoResponse? get userInfo => userRepository.userInfo.value;
 
   @override
   Future<void> onInit() async {
     // TODO: implement onInit
     super.onInit();
 
-    if (Platform.isAndroid) {
-      // 延迟3秒
-      Future.delayed(const Duration(seconds: 3), () {
-        isStorageScanned.value = true;
-        totalSpace.value = 200.0;
-        usedSpace.value = 50.0;
-        photoSpace.value = 30.0;
-        freeSpace.value = 150.0;
-      });
-    }
-
     configRepository.refreshConfig();
     await userRepository.getUserInfo();
 
@@ -131,63 +123,63 @@ class HomeController extends BaseController {
     setFirstIntoApp(false);
 
     var status = await Permission.photos.status;
-    if (status.isGranted) {
-
-      getStorageInfo();
+    if (status.isGranted || status.isLimited) {
+      if (status.isLimited) {
+        _showLimitedAccessPrompt();
+      }
     } else if (status.isPermanentlyDenied) {
       ToastUtil.show("Please enable the album permission");
-      openAppSettings();
+      _showLimitedAccessPrompt();
+      isSimilarScanned.value = true;
+      isPeopleScanned.value = true;
+      isBlurryScanned.value = true;
+      isScreenShotScanned.value = true;
     } else {
       ToastUtil.show("Please enable the album permission");
+      _showLimitedAccessPrompt();
+      isSimilarScanned.value = true;
+      isPeopleScanned.value = true;
+      isBlurryScanned.value = true;
+      isScreenShotScanned.value = true;
     }
   }
 
+  void _showLimitedAccessPrompt() {
+    showDialog<bool>(
+      context: Get.context!,
+      builder: (BuildContext context) {
+        return CupertinoAlertDialog(
+          title: Text("Full access permission required"),
+          content: Text("We need full access permission to scan your entire library and find more duplicate content"),
+          actions: <Widget>[
+            CupertinoDialogAction(
+              isDefaultAction: false,
+              isDestructiveAction: true,
+              onPressed: () {
+                Navigator.of(context).pop(false);
+              },
+              child: Text("Cancel"),
+            ),
+            CupertinoDialogAction(
+              isDefaultAction: true,
+              onPressed: () {
+                openAppSettings();
+                Navigator.of(context).pop(false);
+              },
+              child: Text("Open Setting"),
+            ),
+          ],
+        );
+      },
+    );
+  }
+
   @override
   void onReady() {
     super.onReady();
     // EventHandler.report(EventId.event_03000);
   }
 
-  Future<void> getStorageInfo() async {
-    final classifyPhoto = ClassifyPhoto();
-
-    try {
-      final storageInfo = await classifyPhoto.getStorageInfo();
-
-      // 转换为 GB
-      final totalSpaceGB = storageInfo['totalSpace']! / (1000 * 1000 * 1000);
-      final freeSpaceGB = storageInfo['freeSpace']! / (1024 * 1024 * 1024);
-      final usedSpaceGB = storageInfo['usedSpace']! / (1024 * 1024 * 1024);
-      final photoSpaceGB = storageInfo['photoSpace']! / (1024 * 1024 * 1024);
-
-      totalSpaceStr.value = ImagePickerUtil.formatFileSize(
-          storageInfo['totalSpace']!,
-          decimals: 1);
-      freeSpaceStr.value = ImagePickerUtil.formatFileSize(
-          storageInfo['freeSpace']!,
-          decimals: 1);
-      usedSpaceStr.value = ImagePickerUtil.formatFileSize(
-          storageInfo['usedSpace']!,
-          decimals: 1);
-      photoSpaceStr.value = ImagePickerUtil.formatFileSize(
-          storageInfo['photoSpace']!,
-          decimals: 1);
-
-      totalSpace.value = totalSpaceGB.round().toDouble();
-      freeSpace.value = freeSpaceGB;
-      usedSpace.value = usedSpaceGB;
-      photoSpace.value = photoSpaceGB;
-
-      print('总容量: $totalSpaceStr');
-      print('可用空间: $freeSpaceStr');
-      print('已用空间: $usedSpaceStr');
-      print('照片占用: $photoSpaceStr');
-      isStorageScanned.value = true;
-    } catch (e) {
-      print('获取存储信息失败: $e');
-    }
-  }
-
   // Future<void> handleScreenPhotos() async {
   //   final photoClassify = ClassifyPhoto();
   //   try {

+ 2 - 4
lib/module/splash/splash_controller.dart

@@ -58,10 +58,8 @@ class SplashController extends BaseController {
     await initAfterGrant();
     initStoreIndexData();
 
-
-
-
-    PhotoScanHandler.handleAllPhotos();
+    PhotoScanHandler().getStorageInfo();
+    PhotoScanHandler().handleAllPhotos();
 
     Future.delayed(const Duration(seconds: 2), () {
       if (isFirstIntro()) {

+ 4 - 1
lib/module/store/discount/discount_controller.dart

@@ -43,7 +43,7 @@ class DiscountController extends BaseController
 
   void initStoreIndexData() {
     LoadingDialog.showLoading();
-    storeRepository.storeIndex().then((indexData) async {
+    storeRepository.storeIndex().timeout(Duration(seconds: 60)).then((indexData) async {
       storeItems.clear();
       storeItems.addAll(indexData.items);
       currentSelectedStoreItem.value =
@@ -62,6 +62,9 @@ class DiscountController extends BaseController
       }
       // isFree.value = await ApplePay().check(freeAppleId);
       LoadingDialog.hide();
+    }, onError: (_) {
+      LoadingDialog.hide();
+      ToastUtil.show("Connect Error");
     });
   }
 

+ 4 - 1
lib/module/store/store_controller.dart

@@ -64,7 +64,7 @@ class StoreController extends BaseController implements PaymentStatusCallback {
 
   void initStoreIndexData() {
     LoadingDialog.showLoading();
-    storeRepository.storeIndex().then((indexData) async {
+    storeRepository.storeIndex().timeout(Duration(seconds: 60)).then((indexData) async {
       storeItems.clear();
       storeItems.addAll(indexData.items);
       currentSelectedStoreItem.value =
@@ -84,6 +84,9 @@ class StoreController extends BaseController implements PaymentStatusCallback {
 
       // isFree.value = await ApplePay().check(freeAppleId);
       LoadingDialog.hide();
+    }, onError: (_) {
+      LoadingDialog.hide();
+      ToastUtil.show("Connect Error");
     });
   }
 

+ 0 - 16
pubspec.lock

@@ -1269,22 +1269,6 @@ packages:
       url: "https://pub.flutter-io.cn"
     source: hosted
     version: "1.3.0"
-  syncfusion_flutter_charts:
-    dependency: "direct main"
-    description:
-      name: syncfusion_flutter_charts
-      sha256: "9745a1afb3d14f2f8973a4422767d0124217d6727eacbbf4b2ed0dd66bc58887"
-      url: "https://pub.flutter-io.cn"
-    source: hosted
-    version: "28.1.38"
-  syncfusion_flutter_core:
-    dependency: transitive
-    description:
-      name: syncfusion_flutter_core
-      sha256: "12735505d616320aebe39a6fc90b6608a09116378d66aee9636b0eddf7b75971"
-      url: "https://pub.flutter-io.cn"
-    source: hosted
-    version: "28.1.38"
   synchronized:
     dependency: "direct main"
     description:

+ 1 - 1
pubspec.yaml

@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
 # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
 # In Windows, build-name is used as the major, minor, and patch parts
 # of the product and file versions while build-number is used as the build suffix.
-version: 1.3.0+30
+version: 1.4.0+33
 
 environment:
   sdk: ^3.6.0