photo_scan_handler.dart 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663
  1. import 'dart:async';
  2. import 'dart:io';
  3. import 'package:classify_photo/classify_photo.dart';
  4. import 'package:clean/widget/multi_segment_circle_indicator.dart';
  5. import 'package:disk_space/disk_space.dart';
  6. import 'package:flutter/Material.dart';
  7. import 'package:get/get.dart';
  8. import 'package:permission_handler/permission_handler.dart';
  9. import 'package:wechat_assets_picker/wechat_assets_picker.dart';
  10. import '../../module/image_picker/image_picker_util.dart';
  11. import '../utils/toast_util.dart';
  12. class PhotoScanHandler {
  13. static final _photoClassify = ClassifyPhoto();
  14. static RxBool isSimilarScanned = false.obs;
  15. static RxBool isPeopleScanned = false.obs;
  16. static RxBool isScreenShotScanned = false.obs;
  17. static RxBool isBlurryScanned = false.obs;
  18. // 人物图片
  19. static RxList<AssetEntity> peoplePhotos = <AssetEntity>[].obs;
  20. // 地点图片
  21. static Rx<AssetEntity?> locationPhoto = Rx<AssetEntity?>(null);
  22. // 截图照片
  23. static Rx<AssetEntity?> screenshotPhoto = Rx<AssetEntity?>(null);
  24. // 模糊照片
  25. static Rx<AssetEntity?> blurryPhoto = Rx<AssetEntity?>(null);
  26. // 相似照片
  27. static RxList<AssetEntity> similarPhotos = <AssetEntity>[].obs;
  28. // 添加一个标志来跟踪当前扫描状态
  29. bool _isScanningCancelled = false;
  30. List<StreamSubscription> _scanningTasks = [];
  31. static Rx<double> totalSpace = 0.0.obs;
  32. static Rx<double> usedSpace = 0.0.obs;
  33. static Rx<double> photoSpace = 0.0.obs;
  34. static Rx<double> freeSpace = 0.0.obs;
  35. static Rx<String> totalSpaceStr = "0.0 GB".obs;
  36. static Rx<String> usedSpaceStr = "0.0 GB".obs;
  37. static Rx<String> photoSpaceStr = "0.0 GB".obs;
  38. static Rx<String> freeSpaceStr = "0.0 GB".obs;
  39. // 计算已用存储百分比
  40. static double get usedSpacePercentage => (usedSpace.value / totalSpace.value) * 100;
  41. // 计算照片占用存储百分比
  42. static double get photoSpacePercentage =>
  43. (photoSpace.value / totalSpace.value) * 100;
  44. // 计算可用存储百分比
  45. static double get freeSpacePercentage => (freeSpace.value / totalSpace.value) * 100;
  46. static List<PieData> get pieDataList => [
  47. PieData("PhotoSpace", photoSpacePercentage, Colors.blue),
  48. PieData("OtherUsed", usedSpacePercentage - photoSpacePercentage,
  49. Colors.red),
  50. PieData("totalSpace", totalSpace.value, Colors.grey.withOpacity(0.1)),
  51. ];
  52. // 存储是否扫描完成
  53. static RxBool isStorageScanned = false.obs;
  54. // 监听照片库变化
  55. void _listenToPhotoLibraryChanges() {
  56. PhotoManager.addChangeCallback((change) {
  57. print("照片库发生变化: $change");
  58. // 取消当前正在进行的扫描
  59. _cancelCurrentScan();
  60. // 延迟一小段时间后重新开始扫描
  61. Future.delayed(Duration(milliseconds: 500), () {
  62. _restartScan();
  63. });
  64. });
  65. // 开始监听
  66. PhotoManager.startChangeNotify();
  67. }
  68. // 取消当前扫描
  69. void _cancelCurrentScan() {
  70. _isScanningCancelled = true;
  71. // 取消所有正在进行的异步任务
  72. for (var subscription in _scanningTasks) {
  73. subscription.cancel();
  74. }
  75. _scanningTasks.clear();
  76. print("已取消当前扫描任务");
  77. }
  78. // 重新开始扫描
  79. void _restartScan() {
  80. _isScanningCancelled = false;
  81. isSimilarScanned.value = false;
  82. isPeopleScanned.value = false;
  83. isBlurryScanned.value = false;
  84. isScreenShotScanned.value = false;
  85. // 清除之前的结果
  86. similarPhotos.clear();
  87. peoplePhotos.clear();
  88. locationPhoto.value = null;
  89. screenshotPhoto.value = null;
  90. blurryPhoto.value = null;
  91. print("开始重新扫描照片");
  92. // 重新开始扫描流程
  93. startScanningProcess();
  94. }
  95. // 开始扫描过程
  96. Future<void> startScanningProcess() async {
  97. // 确保在开始新扫描前取消标志被重置
  98. _isScanningCancelled = false;
  99. // 清空之前的任务
  100. _scanningTasks.clear();
  101. try {
  102. // 按顺序执行扫描任务
  103. print('开始顺序扫描照片');
  104. // 1. 先扫描截图照片
  105. if (!_isScanningCancelled) {
  106. var screenTask = _handleScreenPhotosWithCancellation();
  107. _scanningTasks.add(screenTask);
  108. // 等待截图扫描完成
  109. await Future.delayed(Duration(milliseconds: 500));
  110. }
  111. // 2. 再扫描模糊照片
  112. if (!_isScanningCancelled) {
  113. var blurryTask = _handleBlurryPhotosWithCancellation();
  114. _scanningTasks.add(blurryTask);
  115. // 等待模糊照片扫描完成
  116. await Future.delayed(Duration(milliseconds: 500));
  117. }
  118. // 3. 然后扫描人物照片
  119. if (!_isScanningCancelled) {
  120. var peopleTask = _handlePeoplePhotosWithCancellation();
  121. _scanningTasks.add(peopleTask);
  122. // 等待人物照片扫描完成
  123. await Future.delayed(Duration(milliseconds: 500));
  124. }
  125. // 4. 最后扫描相似照片
  126. if (!_isScanningCancelled) {
  127. var similarTask = _handleSimilarPhotosWithCancellation();
  128. _scanningTasks.add(similarTask);
  129. }
  130. print('所有扫描任务已启动');
  131. } catch (e) {
  132. print('扫描过程中出错: $e');
  133. }
  134. }
  135. Future<void> getStorageInfo() async {
  136. if (Platform.isAndroid) {
  137. // 延迟3秒
  138. Future.delayed(const Duration(seconds: 3), () {
  139. isStorageScanned.value = true;
  140. totalSpace.value = 200.0;
  141. usedSpace.value = 50.0;
  142. photoSpace.value = 30.0;
  143. freeSpace.value = 150.0;
  144. });
  145. }
  146. final totalSpaceGB = (await DiskSpace.getTotalDiskSpace ?? 0) / 1000;
  147. final freeSpaceGB = (await DiskSpace.getFreeDiskSpace ?? 0) / 1024;
  148. final usedSpaceGB = ((await DiskSpace.getTotalDiskSpace ?? 0) - (await DiskSpace.getFreeDiskSpace ?? 0)) / 1024;
  149. totalSpaceStr.value = ImagePickerUtil.formatFileSize(
  150. (await DiskSpace.getTotalDiskSpace ?? 0).round(),
  151. decimals: 1);
  152. freeSpaceStr.value = ImagePickerUtil.formatFileSize(
  153. (await DiskSpace.getFreeDiskSpace ?? 0).round(),
  154. decimals: 1);
  155. usedSpaceStr.value = ImagePickerUtil.formatFileSize(
  156. ((await DiskSpace.getTotalDiskSpace ?? 0) - (await DiskSpace.getFreeDiskSpace ?? 0)).round(),
  157. decimals: 1);
  158. totalSpace.value = totalSpaceGB.round().toDouble();
  159. freeSpace.value = freeSpaceGB;
  160. usedSpace.value = usedSpaceGB;
  161. final classifyPhoto = ClassifyPhoto();
  162. try {
  163. final storageInfo = await classifyPhoto.getStorageInfo();
  164. // 转换为 GB
  165. final totalSpaceGB = storageInfo['totalSpace']! / (1000 * 1000 * 1000);
  166. final freeSpaceGB = storageInfo['freeSpace']! / (1024 * 1024 * 1024);
  167. final usedSpaceGB = storageInfo['usedSpace']! / (1024 * 1024 * 1024);
  168. final photoSpaceGB = storageInfo['photoSpace']! / (1024 * 1024 * 1024);
  169. totalSpaceStr.value = ImagePickerUtil.formatFileSize(
  170. storageInfo['totalSpace']!,
  171. decimals: 1);
  172. freeSpaceStr.value = ImagePickerUtil.formatFileSize(
  173. storageInfo['freeSpace']!,
  174. decimals: 1);
  175. usedSpaceStr.value = ImagePickerUtil.formatFileSize(
  176. storageInfo['usedSpace']!,
  177. decimals: 1);
  178. photoSpaceStr.value = ImagePickerUtil.formatFileSize(
  179. storageInfo['photoSpace']!,
  180. decimals: 1);
  181. totalSpace.value = totalSpaceGB.round().toDouble();
  182. freeSpace.value = freeSpaceGB;
  183. usedSpace.value = usedSpaceGB;
  184. photoSpace.value = photoSpaceGB;
  185. print('总容量: $totalSpaceStr');
  186. print('可用空间: $freeSpaceStr');
  187. print('已用空间: $usedSpaceStr');
  188. print('照片占用: $photoSpaceStr');
  189. isStorageScanned.value = true;
  190. } catch (e) {
  191. print('获取存储信息失败: $e');
  192. }
  193. }
  194. /// 执行所有的照片处理操作
  195. Future<void> handleAllPhotos() async {
  196. // var request = Platform.isIOS
  197. // ? await Permission.photos.request()
  198. // : await Permission.storage.request();
  199. //
  200. // if (request.isGranted) {
  201. // PhotoManager.clearFileCache();
  202. // await handleScreenPhotos();
  203. // await handleBlurryPhotos();
  204. // await handlePeoplePhotos();
  205. // await handleSimilarPhotos();
  206. // if (Platform.isAndroid) {
  207. // await handleAndroidPhotos();
  208. // }
  209. // } else if (request.isPermanentlyDenied) {
  210. // ToastUtil.show("Please enable the album permission");
  211. // openAppSettings();
  212. // } else {
  213. // ToastUtil.show("Please enable the album permission");
  214. //
  215. // isSimilarScanned.value = false;
  216. // isPeopleScanned.value = false;
  217. // isBlurryScanned.value = false;
  218. // isScreenShotScanned.value = false;
  219. // }
  220. var currentStatus = await Permission.photos.status;
  221. if (currentStatus.isGranted) {
  222. // 已有完全权限,直接扫描
  223. PhotoManager.clearFileCache();
  224. // _listenToPhotoLibraryChanges(); // 添加监听
  225. await handleScreenPhotos();
  226. await handleBlurryPhotos();
  227. await handlePeoplePhotos();
  228. await handleSimilarPhotos();
  229. } else if (currentStatus.isLimited) {
  230. // 已有有限权限,显示自定义弹窗
  231. PhotoManager.clearFileCache();
  232. await handleScreenPhotos();
  233. await handleBlurryPhotos();
  234. await handlePeoplePhotos();
  235. await handleSimilarPhotos();
  236. } else {
  237. // 未授权,请求权限
  238. var result = await Permission.photos.request();
  239. if (result.isGranted || result.isLimited) {
  240. PhotoManager.clearFileCache();
  241. getStorageInfo();
  242. await handleScreenPhotos();
  243. await handleBlurryPhotos();
  244. await handlePeoplePhotos();
  245. await handleSimilarPhotos();
  246. } else {
  247. isSimilarScanned.value = true;
  248. isPeopleScanned.value = true;
  249. isBlurryScanned.value = true;
  250. isScreenShotScanned.value = true;
  251. }
  252. }
  253. }
  254. // 可取消的截图照片扫描
  255. StreamSubscription _handleScreenPhotosWithCancellation() {
  256. var controller = StreamController<bool>();
  257. Future<void> task() async {
  258. if (_isScanningCancelled) {
  259. controller.add(false);
  260. return;
  261. }
  262. try {
  263. print('开始获取截图照片');
  264. final photoClassify = ClassifyPhoto();
  265. final photos = await photoClassify.getScreenshots();
  266. if (_isScanningCancelled) {
  267. controller.add(false);
  268. return;
  269. }
  270. print('获取截图照片完成: ${photos?.length ?? 0} 组照片');
  271. isScreenShotScanned.value = true;
  272. if (photos != null) {
  273. await ImagePickerUtil.updatePhotos(photos);
  274. if (ImagePickerUtil.screenshotPhotos.isNotEmpty) {
  275. var asset = ImagePickerUtil.screenshotPhotos.first;
  276. screenshotPhoto.value = asset;
  277. }
  278. }
  279. controller.add(true);
  280. } catch (e) {
  281. print('获取截图照片失败: $e');
  282. controller.add(false);
  283. }
  284. }
  285. task();
  286. return controller.stream.listen((success) {
  287. controller.close();
  288. });
  289. }
  290. // 可取消的模糊照片扫描
  291. StreamSubscription _handleBlurryPhotosWithCancellation() {
  292. var controller = StreamController<bool>();
  293. Future<void> task() async {
  294. if (_isScanningCancelled) {
  295. controller.add(false);
  296. return;
  297. }
  298. try {
  299. print('开始获取模糊照片');
  300. final photoClassify = ClassifyPhoto();
  301. final photos = await photoClassify.getBlurryPhotos();
  302. if (_isScanningCancelled) {
  303. controller.add(false);
  304. return;
  305. }
  306. print('获取模糊照片完成: ${photos?.length ?? 0} 组照片');
  307. isBlurryScanned.value = true;
  308. if (photos != null) {
  309. await ImagePickerUtil.updatePhotos(photos);
  310. if (ImagePickerUtil.blurryPhotos.isNotEmpty) {
  311. var asset = ImagePickerUtil.blurryPhotos.first;
  312. blurryPhoto.value = asset;
  313. }
  314. }
  315. controller.add(true);
  316. } catch (e) {
  317. print('获取模糊照片失败: $e');
  318. controller.add(false);
  319. }
  320. }
  321. task();
  322. return controller.stream.listen((success) {
  323. controller.close();
  324. });
  325. }
  326. // 可取消的人物照片扫描
  327. StreamSubscription _handlePeoplePhotosWithCancellation() {
  328. var controller = StreamController<bool>();
  329. Future<void> task() async {
  330. if (_isScanningCancelled) {
  331. controller.add(false);
  332. return;
  333. }
  334. try {
  335. print('开始获取人物照片');
  336. final photoClassify = ClassifyPhoto();
  337. final photos = await photoClassify.getPeoplePhotos();
  338. if (_isScanningCancelled) {
  339. controller.add(false);
  340. return;
  341. }
  342. print('获取人物照片完成: ${photos?.length ?? 0} 组照片');
  343. isPeopleScanned.value = true;
  344. if (photos != null) {
  345. await ImagePickerUtil.updatePhotos(photos);
  346. // 处理人物照片
  347. peoplePhotos.clear();
  348. if (ImagePickerUtil.peoplePhotos.isNotEmpty) {
  349. for (var personPhotos in ImagePickerUtil.peoplePhotos) {
  350. if (_isScanningCancelled) break;
  351. peoplePhotos.add(personPhotos);
  352. if (peoplePhotos.length == 2) {
  353. break;
  354. }
  355. }
  356. }
  357. }
  358. controller.add(true);
  359. } catch (e) {
  360. print('获取人物照片失败: $e');
  361. controller.add(false);
  362. }
  363. }
  364. task();
  365. return controller.stream.listen((success) {
  366. controller.close();
  367. });
  368. }
  369. // 可取消的相似照片扫描
  370. StreamSubscription _handleSimilarPhotosWithCancellation() {
  371. var controller = StreamController<bool>();
  372. Future<void> task() async {
  373. if (_isScanningCancelled) {
  374. controller.add(false);
  375. return;
  376. }
  377. try {
  378. print('开始获取相似照片');
  379. final photoClassify = ClassifyPhoto();
  380. final photos = await photoClassify.getSimilarPhotos();
  381. if (_isScanningCancelled) {
  382. controller.add(false);
  383. return;
  384. }
  385. print('获取相似照片完成: ${photos?.length ?? 0} 组照片');
  386. isSimilarScanned.value = true;
  387. if (photos != null) {
  388. await ImagePickerUtil.updatePhotos(photos);
  389. similarPhotos.clear();
  390. if (ImagePickerUtil.similarPhotos.isNotEmpty) {
  391. for (var group in ImagePickerUtil.similarPhotos) {
  392. if (_isScanningCancelled) break;
  393. for (var asset in group) {
  394. similarPhotos.add(asset);
  395. if (similarPhotos.length == 4) {
  396. break;
  397. }
  398. }
  399. if (similarPhotos.length == 4) {
  400. break;
  401. }
  402. }
  403. }
  404. }
  405. controller.add(true);
  406. } catch (e) {
  407. print('获取相似照片失败: $e');
  408. controller.add(false);
  409. }
  410. }
  411. task();
  412. return controller.stream.listen((success) {
  413. controller.close();
  414. });
  415. }
  416. /// 截图照片
  417. Future<bool> handleScreenPhotos() async {
  418. try {
  419. final photos = await _photoClassify.getScreenshots();
  420. print('获取截图照片完成: ${photos?.length ?? 0} 组照片');
  421. isScreenShotScanned.value = true;
  422. if (photos != null) {
  423. await ImagePickerUtil.updatePhotos(photos);
  424. if (ImagePickerUtil.screenshotPhotos.isNotEmpty) {
  425. var asset = ImagePickerUtil.screenshotPhotos.first;
  426. screenshotPhoto.value = asset;
  427. }
  428. }
  429. } catch (e, stackTrace) {
  430. print('获取截图失败: $e\n$stackTrace');
  431. }
  432. isScreenShotScanned.value = true;
  433. return true;
  434. }
  435. /// 模糊照片
  436. Future<bool> handleBlurryPhotos() async {
  437. try {
  438. final photos = await _photoClassify.getBlurryPhotos();
  439. print('获取模糊照片完成: ${photos?.length ?? 0} 组照片');
  440. isBlurryScanned.value = true;
  441. if (photos != null) {
  442. await ImagePickerUtil.updatePhotos(photos);
  443. if (ImagePickerUtil.blurryPhotos.isNotEmpty) {
  444. var asset = ImagePickerUtil.blurryPhotos.first;
  445. blurryPhoto.value = asset;
  446. }
  447. return true;
  448. }
  449. } catch (e, stackTrace) {
  450. print('获取模糊失败: $e\n$stackTrace');
  451. }
  452. isBlurryScanned.value = true;
  453. return true;
  454. }
  455. /// 人物照片
  456. Future<bool> handlePeoplePhotos() async {
  457. try {
  458. final photos = await _photoClassify.getPeoplePhotos();
  459. print('获取人物照片完成: ${photos?.length ?? 0} 组照片');
  460. isPeopleScanned.value = true;
  461. if (photos != null) {
  462. await ImagePickerUtil.updatePhotos(photos);
  463. // 处理人物照片
  464. peoplePhotos.clear();
  465. if (ImagePickerUtil.peoplePhotos.isNotEmpty) {
  466. for (var personPhotos in ImagePickerUtil.peoplePhotos) {
  467. peoplePhotos.add(personPhotos);
  468. if (peoplePhotos.length == 2) {
  469. break;
  470. }
  471. }
  472. }
  473. return true;
  474. }
  475. } catch (e, stackTrace) {
  476. print('获取人物失败: $e\n$stackTrace');
  477. }
  478. isPeopleScanned.value = true;
  479. return true;
  480. }
  481. /// 相似照片
  482. Future<bool> handleSimilarPhotos() async {
  483. try {
  484. print('开始获取相似照片');
  485. final photos = await _photoClassify.getSimilarPhotos();
  486. print('获取相似照片完成: ${photos?.length ?? 0} 组照片');
  487. isSimilarScanned.value = true;
  488. if (photos != null) {
  489. await ImagePickerUtil.updatePhotos(photos);
  490. similarPhotos.clear();
  491. if (ImagePickerUtil.similarPhotos.isNotEmpty) {
  492. for (var group in ImagePickerUtil.similarPhotos) {
  493. for (var asset in group) {
  494. similarPhotos.add(asset);
  495. if (similarPhotos.length == 4) {
  496. break;
  497. }
  498. }
  499. }
  500. }
  501. return true;
  502. }
  503. } catch (e, stackTrace) {
  504. print('获取相似失败: $e\n$stackTrace');
  505. }
  506. isSimilarScanned.value = true;
  507. return true;
  508. }
  509. /// Android平台处理方式
  510. Future<void> handleAndroidPhotos() async {
  511. final List<AssetEntity> result = await ImagePickerUtil.loadAssets();
  512. ImagePickerUtil.peoplePhotos.value = result ?? [];
  513. ImagePickerUtil.locationPhotos['location'] = result ?? [];
  514. ImagePickerUtil.screenshotPhotos.value = result ?? [];
  515. ImagePickerUtil.similarPhotos.add(result ?? []);
  516. ImagePickerUtil.blurryPhotos.value = result ?? [];
  517. print("handleAndroidPhotos $result");
  518. print(
  519. "ImagePickerUtil.peoplePhotos.value ${ImagePickerUtil.peoplePhotos
  520. .length}");
  521. isSimilarScanned.value = true;
  522. isPeopleScanned.value = true;
  523. isBlurryScanned.value = true;
  524. isScreenShotScanned.value = true;
  525. similarPhotos.clear();
  526. if (ImagePickerUtil.similarPhotos.isNotEmpty) {
  527. for (var group in ImagePickerUtil.similarPhotos) {
  528. print(
  529. " ImagePickerUtil.similarPhotos ${ImagePickerUtil.similarPhotos
  530. .length}");
  531. for (var asset in group) {
  532. similarPhotos.add(asset);
  533. if (similarPhotos.length == 4) {
  534. break;
  535. }
  536. }
  537. }
  538. }
  539. peoplePhotos.clear();
  540. if (ImagePickerUtil.peoplePhotos.isNotEmpty) {
  541. for (var personPhotos in ImagePickerUtil.peoplePhotos) {
  542. peoplePhotos.add(personPhotos);
  543. if (peoplePhotos.length == 2) {
  544. break;
  545. }
  546. }
  547. }
  548. if (ImagePickerUtil.blurryPhotos.isNotEmpty) {
  549. var asset = ImagePickerUtil.blurryPhotos.first;
  550. blurryPhoto.value = asset;
  551. }
  552. if (ImagePickerUtil.screenshotPhotos.isNotEmpty) {
  553. var asset = ImagePickerUtil.screenshotPhotos.first;
  554. screenshotPhoto.value = asset;
  555. }
  556. }
  557. }