photo_preview_controller.dart 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. import 'dart:async';
  2. import 'package:clean/base/base_controller.dart';
  3. import 'package:clean/base/base_photo_controller.dart';
  4. import 'package:clean/data/bean/photos_type.dart';
  5. import 'package:clean/data/consts/constants.dart';
  6. import 'package:clean/data/repositories/user_repository.dart';
  7. import 'package:clean/dialog/photo_delete_finish_dialog.dart';
  8. import 'package:clean/dialog/photo_deleting_dialog.dart';
  9. import 'package:clean/dialog/photo_preview_tip_dialog.dart';
  10. import 'package:clean/module/image_picker/image_picker_util.dart';
  11. import 'package:clean/module/locations_photo/locations_single_photo_controller.dart';
  12. import 'package:clean/module/people_photo/people_photo_controller.dart';
  13. import 'package:clean/module/people_photo/photo_group.dart';
  14. import 'package:clean/module/photo_preview/phtoto_selected_preview_view.dart';
  15. import 'package:clean/module/screenshots_blurry/screenshots_controller.dart';
  16. import 'package:clean/module/similar_photo/similar_photo_controller.dart';
  17. import 'package:clean/module/store/store_view.dart';
  18. import 'package:clean/utils/file_utils.dart';
  19. import 'package:clean/utils/toast_util.dart';
  20. import 'package:flutter/Material.dart';
  21. import 'package:flutter_card_swiper/flutter_card_swiper.dart';
  22. import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
  23. import 'package:get/get.dart';
  24. import 'package:lottie/lottie.dart';
  25. import 'package:wechat_assets_picker/wechat_assets_picker.dart';
  26. import '../../base/base_photos_controller.dart';
  27. import '../../data/consts/event_report_id.dart';
  28. import '../../dialog/play_video_dialog.dart';
  29. import '../../handler/event_handler.dart';
  30. import '../../utils/file_size_calculator_util.dart';
  31. class PhotoPreviewController extends BaseController
  32. with GetSingleTickerProviderStateMixin {
  33. Rx<CardSwiperController> cardSwiperController = CardSwiperController().obs;
  34. final RxList<AssetEntity> listAssetEntity = <AssetEntity>[].obs;
  35. final RxSet<String> selectedPhotosIds = <String>{}.obs;
  36. final RxBool isSwiperEnd = false.obs;
  37. RxInt groupIndex = 0.obs;
  38. late PhotosType photosType;
  39. late String? currentImageId;
  40. RxDouble selectedFilesSize = 0.0.obs;
  41. RxInt selectedFileCount = 0.obs;
  42. late AnimationController animationController;
  43. RxBool animationIsComplete = false.obs;
  44. @override
  45. void onInit() {
  46. super.onInit();
  47. animationController = AnimationController(
  48. vsync: this,
  49. duration: const Duration(seconds: 3),
  50. );
  51. print('PhotoPreviewController onInit');
  52. _getArgs(); // 获取传递的参数
  53. _initData(); // 初始化数据
  54. WidgetsBinding.instance.addPostFrameCallback((_) {
  55. if (currentImageId != null) {
  56. for (int i = 0; i < listAssetEntity.length; i++) {
  57. if (listAssetEntity[i].id == currentImageId) {
  58. print('photoGroups[i].id ${listAssetEntity[i].id},i $i');
  59. groupIndex.value = i;
  60. cardSwiperController.value.moveTo(i);
  61. break;
  62. }
  63. }
  64. }
  65. });
  66. animationController.addStatusListener((status) {
  67. if (status == AnimationStatus.forward) {
  68. // 延迟一秒
  69. Future.delayed(Duration(seconds: 1), () {
  70. animationIsComplete.value = true;
  71. EventHandler.report(EventId.event_04008);
  72. });
  73. }
  74. if (status == AnimationStatus.completed) {
  75. Future.delayed(Duration(seconds: 1), () {
  76. PhotoSelectedPreviewPage.start(photosType, selectedPhotosIds);
  77. });
  78. }
  79. });
  80. }
  81. @override
  82. void onReady() {
  83. // TODO: implement onReady
  84. super.onReady();
  85. EventHandler.report(EventId.event_04002);
  86. print('PhotoPreviewController onReady');
  87. if (isFirstOpenPhotoPreview()) {
  88. photoPreviewTipDialog(clickCallback: () {
  89. setFirstOpenPhotoPreview(false);
  90. EventHandler.report(EventId.event_04001);
  91. });
  92. }
  93. }
  94. // 获取参数
  95. void _getArgs() {
  96. photosType = parameters?['photosType'];
  97. currentImageId = parameters?['currentImageId'];
  98. }
  99. void _assignPhotosAndSelections(BasePhotoController controller) {
  100. listAssetEntity.assignAll(controller.getAllPhotos());
  101. selectedPhotosIds.assignAll(controller.selectedPhotosIds);
  102. selectedFileCount.value = selectedPhotosIds.length;
  103. }
  104. void _assignPhotosAndSelections1(BasePhotosController controller) {
  105. listAssetEntity.assignAll(controller.photoGroup.value.images);
  106. selectedPhotosIds.assignAll(controller.photoGroup.value.selectedPhotosIds);
  107. selectedFileCount.value = selectedPhotosIds.length;
  108. }
  109. void _initData() {
  110. listAssetEntity.clear();
  111. selectedPhotosIds.clear();
  112. switch (photosType) {
  113. case PhotosType.peoplePhotos:
  114. final PeoplePhotoController controller =
  115. Get.find<PeoplePhotoController>();
  116. _assignPhotosAndSelections1(controller);
  117. break;
  118. case PhotosType.screenshots:
  119. // final ScreenShotsController controller =
  120. // Get.find<ScreenShotsController>();
  121. // _assignPhotosAndSelections(controller);
  122. break;
  123. case PhotosType.similarPhotos:
  124. final SimilarPhotoController controller =
  125. Get.find<SimilarPhotoController>();
  126. _assignPhotosAndSelections(controller);
  127. break;
  128. case PhotosType.locationPhotos:
  129. // final LocationsSinglePhotoController controller =
  130. // Get.find<LocationsSinglePhotoController>();
  131. // _assignPhotosAndSelections(controller);
  132. break;
  133. case PhotosType.blurryPhotos:
  134. // final ScreenShotsController controller =
  135. // Get.find<ScreenShotsController>();
  136. // _assignPhotosAndSelections(controller);
  137. break;
  138. }
  139. updateSelectedFilesSize();
  140. }
  141. //删除成功清除选中的图片
  142. void cleanSelections() async {
  143. debugPrint('PhotoPreviewController cleanSelections');
  144. listAssetEntity
  145. .removeWhere((element) => selectedPhotosIds.contains(element.id));
  146. selectedPhotosIds.clear();
  147. if (listAssetEntity.isEmpty) {
  148. return;
  149. }
  150. isSwiperEnd.value = false;
  151. if (listAssetEntity.isNotEmpty) {
  152. WidgetsBinding.instance.addPostFrameCallback((_) {
  153. groupIndex.value = 0;
  154. cardSwiperController.value.moveTo(0);
  155. });
  156. }
  157. updateSelectedFilesSize();
  158. selectedFileCount.value = selectedPhotosIds.length;
  159. }
  160. // 恢复状态
  161. void restoreSelections() {
  162. if (listAssetEntity.isEmpty) {
  163. clickBack();
  164. return;
  165. }
  166. isSwiperEnd.value = false;
  167. if (listAssetEntity.isNotEmpty) {
  168. WidgetsBinding.instance.addPostFrameCallback((_) {
  169. groupIndex.value = 0;
  170. cardSwiperController.value.moveTo(0);
  171. });
  172. }
  173. updateSelectedFilesSize();
  174. selectedFileCount.value = selectedPhotosIds.length;
  175. }
  176. Future<void> updateSelectedFilesSize() async {
  177. if (selectedPhotosIds.isEmpty) {
  178. selectedFilesSize.value = 0;
  179. return;
  180. }
  181. FileSizeCalculatorUtil.calculateTotalSize(
  182. assetIds: selectedPhotosIds,
  183. updateValue: (double totalSize) {
  184. selectedFilesSize.value = totalSize;
  185. },
  186. );
  187. PhotoManager.clearFileCache();
  188. }
  189. void updateSelections(Set<String> selectedIds) {
  190. print(
  191. 'PhotoPreviewController updateSelections photosType $photosType selectedIds $selectedIds');
  192. selectedPhotosIds.assignAll(selectedIds);
  193. restoreSelections();
  194. }
  195. void recoverSelectPhoto() {
  196. print('PhotoPreviewController recoverSelectPhoto');
  197. cardSwiperController.value.undo();
  198. }
  199. void clickSelect() {
  200. cardSwiperController.value.swipe(CardSwiperDirection.left);
  201. }
  202. @override
  203. void onClose() {
  204. print('PhotoPreviewController onClose');
  205. animationController.dispose();
  206. super.onClose();
  207. isSwiperEnd.value = false;
  208. // 清理操作,释放资源
  209. cardSwiperController.value.dispose();
  210. // 清理缓存
  211. PhotoManager.clearFileCache();
  212. }
  213. void clickBack() {
  214. EventHandler.report(EventId.event_04003);
  215. _saveSelectedPhotos(photosType);
  216. Get.back();
  217. }
  218. void clickJumpSelected() {
  219. debugPrint('PhotoPreviewController clickJumpSelected');
  220. EventHandler.report(EventId.event_04005);
  221. PhotoSelectedPreviewPage.start(photosType, selectedPhotosIds);
  222. }
  223. // // 保存选择的图片ID列表
  224. void _saveSelectedPhotos(PhotosType type) {
  225. switch (type) {
  226. case PhotosType.peoplePhotos:
  227. // PeoplePhotoController controller = Get.find<PeoplePhotoController>();
  228. // controller.updateSelections(selectedPhotosIds);
  229. // controller.restoreSelections();
  230. break;
  231. case PhotosType.screenshots:
  232. // ScreenShotsController controller = Get.find<ScreenShotsController>();
  233. // controller.updateSelections(selectedPhotosIds);
  234. // controller.restoreSelections();
  235. break;
  236. case PhotosType.similarPhotos:
  237. SimilarPhotoController controller = Get.find<SimilarPhotoController>();
  238. controller.updateSelections(selectedPhotosIds);
  239. controller.restoreSelections();
  240. break;
  241. case PhotosType.locationPhotos:
  242. // LocationsSinglePhotoController controller =
  243. // Get.find<LocationsSinglePhotoController>();
  244. // controller.updateSelections(selectedPhotosIds);
  245. // controller.restoreSelections();
  246. break;
  247. case PhotosType.blurryPhotos:
  248. // ScreenShotsController controller = Get.find<ScreenShotsController>();
  249. // controller.updateSelections(selectedPhotosIds);
  250. // controller.restoreSelections();
  251. break;
  252. }
  253. }
  254. void clickUnselect() {
  255. print("clickUnselect");
  256. cardSwiperController.value.swipe(CardSwiperDirection.right);
  257. }
  258. FutureOr<bool> onSwipe(
  259. int previousIndex,
  260. int? currentIndex,
  261. CardSwiperDirection direction,
  262. ) {
  263. print(
  264. 'onSwipe The card $previousIndex was swiped to the ${direction.name}. Now the card $currentIndex is on top',
  265. );
  266. if (currentIndex != null) {
  267. groupIndex.value = currentIndex;
  268. }
  269. // 如果direction是left,
  270. if (direction == CardSwiperDirection.left) {
  271. // 先看看图片id是不是在selectedPhotosIds里面,如果在,不处理,如果不在,添加到selectedPhotosIds里面
  272. if (!selectedPhotosIds.contains(listAssetEntity[previousIndex].id)) {
  273. print(
  274. 'add photoGroups[groupIndex.value].id ${listAssetEntity[previousIndex].id}');
  275. selectedPhotosIds.add(listAssetEntity[previousIndex].id);
  276. }
  277. } else if (direction == CardSwiperDirection.right) {
  278. // 先看看图片id是不是在selectedPhotosIds里面,如果在,在selectedPhotosIds移除,不处理,如果不在,不处理
  279. if (selectedPhotosIds.contains(listAssetEntity[previousIndex].id)) {
  280. print(
  281. 'remove photoGroups[groupIndex.value].id ${listAssetEntity[previousIndex].id}');
  282. selectedPhotosIds.remove(listAssetEntity[previousIndex].id);
  283. }
  284. }
  285. selectedFileCount.value = selectedPhotosIds.length;
  286. updateSelectedFilesSize();
  287. return true;
  288. }
  289. bool onSwiperUndo(
  290. int? previousIndex,
  291. int currentIndex,
  292. CardSwiperDirection direction,
  293. ) {
  294. print(
  295. 'onSwiperUndo The card $currentIndex was swiped back to the ${direction.name}. Now the card $previousIndex is on top');
  296. groupIndex.value = currentIndex;
  297. // 撤销之前左滑的操作
  298. if (direction == CardSwiperDirection.left) {
  299. print(
  300. 'photoGroups[groupIndex.value].id ${listAssetEntity[groupIndex.value].id}');
  301. if (selectedPhotosIds.contains(listAssetEntity[groupIndex.value].id)) {
  302. selectedPhotosIds.remove(listAssetEntity[groupIndex.value].id);
  303. }
  304. }
  305. selectedFileCount.value = selectedPhotosIds.length;
  306. updateSelectedFilesSize();
  307. return true;
  308. }
  309. onSwiperEnd() {
  310. isSwiperEnd.value = true;
  311. print('onSwiperEnd');
  312. // 延迟500ms
  313. Future.delayed(Duration(milliseconds: 200), () {
  314. animationController.forward(from: 0);
  315. });
  316. PhotoManager.clearFileCache();
  317. // PhotoSelectedPreviewPage.start(photosType, selectedPhotosIds);
  318. }
  319. clickDelete() async {
  320. print('clickDelete');
  321. if (selectedPhotosIds.isEmpty) {
  322. ToastUtil.show('Please select the picture');
  323. return;
  324. }
  325. EventHandler.report(EventId.event_04004);
  326. if (userRepository.isVip()) {
  327. switch (photosType) {
  328. case PhotosType.peoplePhotos:
  329. ImagePickerUtil.selectedPeoplePhotosIds.assignAll(selectedPhotosIds);
  330. break;
  331. case PhotosType.screenshots:
  332. ImagePickerUtil.selectedScreenshotPhotosIds
  333. .assignAll(selectedPhotosIds);
  334. break;
  335. case PhotosType.similarPhotos:
  336. ImagePickerUtil.selectedSimilarPhotosIds.assignAll(selectedPhotosIds);
  337. break;
  338. case PhotosType.locationPhotos:
  339. ImagePickerUtil.selectedLocationPhotosIds
  340. .assignAll(selectedPhotosIds);
  341. break;
  342. case PhotosType.blurryPhotos:
  343. ImagePickerUtil.selectedBlurryPhotosIds.assignAll(selectedPhotosIds);
  344. break;
  345. }
  346. if (selectedPhotosIds.isNotEmpty) {
  347. photoDeletingDialog();
  348. // 获取要删除的资产
  349. final assetsToDelete = listAssetEntity
  350. .where(
  351. (asset) => selectedPhotosIds.contains(asset.id),
  352. )
  353. .toList();
  354. // 调用方法会返回被删除的资源,如果全部失败会返回空列表。
  355. final List<String> result = await PhotoManager.editor.deleteWithIds(
  356. assetsToDelete.map((e) => e.id).toList(),
  357. );
  358. print('PhotoPreviewController result $result');
  359. // 比较result和selectedPhotosIds,如果result和selectedPhotosIds相等,说明删除成功,走下面的逻辑
  360. // 如果不相等,说明有删除失败的,走else逻辑
  361. if (result.length == selectedPhotosIds.length) {
  362. switch (photosType) {
  363. case PhotosType.peoplePhotos:
  364. ImagePickerUtil.updatePhotoGroupDate(
  365. photosType, ImagePickerUtil.selectedPeoplePhotosIds);
  366. break;
  367. case PhotosType.screenshots:
  368. ImagePickerUtil.updatePhotoGroupDate(
  369. photosType, ImagePickerUtil.selectedScreenshotPhotosIds);
  370. break;
  371. case PhotosType.similarPhotos:
  372. ImagePickerUtil.updatePhotoGroupDate(
  373. photosType, ImagePickerUtil.selectedSimilarPhotosIds);
  374. break;
  375. case PhotosType.locationPhotos:
  376. ImagePickerUtil.updatePhotoGroupDate(
  377. photosType, ImagePickerUtil.selectedLocationPhotosIds);
  378. break;
  379. case PhotosType.blurryPhotos:
  380. ImagePickerUtil.updatePhotoGroupDate(
  381. photosType, ImagePickerUtil.selectedBlurryPhotosIds);
  382. break;
  383. }
  384. cleanSelections();
  385. ToastUtil.show('Delete success');
  386. Future.delayed(Duration(seconds: 2), () {
  387. SmartDialog.dismiss(tag: 'photoDeletingDialog');
  388. photoDeleteFinishDialog();
  389. });
  390. } else {
  391. SmartDialog.dismiss(tag: 'photoDeletingDialog');
  392. // 删除失败
  393. ToastUtil.show("Delete failed");
  394. }
  395. }
  396. } else {
  397. StorePage.start();
  398. }
  399. // 清理缓存
  400. PhotoManager.clearFileCache();
  401. }
  402. void playVideo(AssetEntity asset) async {
  403. playVideoDialog(asset);
  404. }
  405. // 将selectedFilesSize转成String类型,然后单位转换,如果超过1MB,则转成MB,超过1GB,则转成GB,否则KB
  406. String get selectedFilesSizeString {
  407. final double sizeInKB = selectedFilesSize.value;
  408. if (sizeInKB >= 1024 * 1024) {
  409. // 先检查最大单位(GB)
  410. return "${(sizeInKB / (1024 * 1024)).toStringAsFixed(2)}GB";
  411. } else if (sizeInKB >= 1024) {
  412. // 然后检查MB
  413. return "${(sizeInKB / 1024).toStringAsFixed(2)}MB";
  414. } else {
  415. // 最后是KB
  416. return "${sizeInKB.toStringAsFixed(2)}KB";
  417. }
  418. }
  419. }