calendar_preview_controller.dart 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. import 'dart:async';
  2. import 'dart:io';
  3. import 'package:clean/base/base_controller.dart';
  4. import 'package:clean/module/calendar/selected_preview/calendar_selected_preview_view.dart';
  5. import 'package:flutter/Material.dart';
  6. import 'package:flutter_card_swiper/flutter_card_swiper.dart';
  7. import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
  8. import 'package:get/get.dart';
  9. import 'package:url_launcher/url_launcher.dart';
  10. import 'package:wechat_assets_picker/wechat_assets_picker.dart';
  11. import '../../../data/repositories/user_repository.dart';
  12. import '../../../dialog/photo_delete_finish_dialog.dart';
  13. import '../../../dialog/photo_deleting_dialog.dart';
  14. import '../../../utils/file_size_calculator_util.dart';
  15. import '../../../utils/toast_util.dart';
  16. import '../../image_picker/image_picker_util.dart';
  17. import '../../people_photo/photo_group.dart';
  18. import '../../store/store_view.dart';
  19. class CalendarPreviewController extends BaseController
  20. with GetSingleTickerProviderStateMixin {
  21. final Rx<PhotoGroup> photoGroup =
  22. PhotoGroup(isSelected: false, images: []).obs;
  23. late String? currentImageId;
  24. final CardSwiperController cardSwiperController = CardSwiperController();
  25. final RxBool isSwiperEnd = false.obs;
  26. RxInt groupIndex = 0.obs;
  27. late AnimationController animationController;
  28. RxBool animationIsComplete = false.obs;
  29. void playVideo(AssetEntity asset) async {
  30. final mediaUrl = await asset.getMediaUrl();
  31. if (mediaUrl != null) {
  32. final Uri videoUri = Uri.parse(mediaUrl);
  33. if (await canLaunchUrl(videoUri)) {
  34. await launchUrl(videoUri, mode: LaunchMode.externalApplication);
  35. } else {
  36. debugPrint("无法打开视频:$mediaUrl");
  37. }
  38. }
  39. }
  40. // void playVideo(AssetEntity asset) async {
  41. // final mediaUrl = await asset.getMediaUrl();
  42. // if (mediaUrl != null) {
  43. // late final VideoPlayerController videoPlayerController;
  44. // videoPlayerController =
  45. // VideoPlayerController.networkUrl(Uri.parse(mediaUrl))
  46. // ..initialize().then((_) {
  47. // print('videoPlayerController.value.size ${videoPlayerController.value.reactive}');
  48. // videoPlayerController.play();
  49. // Get.dialog(AlertDialog(
  50. // content: AspectRatio(
  51. // aspectRatio: videoPlayerController.value.aspectRatio,
  52. // child: VideoPlayer(videoPlayerController),
  53. // ),
  54. // actions: [
  55. // TextButton(
  56. // onPressed: () {
  57. // videoPlayerController.pause();
  58. // videoPlayerController.dispose(); // 释放资源
  59. // Get.back();
  60. // },
  61. // child: Text('Close'),
  62. // )
  63. // ],
  64. // ));
  65. // });
  66. // }
  67. // }
  68. @override
  69. void onInit() {
  70. super.onInit();
  71. _getArgs(); // 获取传递的参数
  72. animationController = AnimationController(
  73. vsync: this,
  74. duration: const Duration(seconds: 3),
  75. );
  76. WidgetsBinding.instance.addPostFrameCallback((_) {
  77. if (currentImageId != null) {
  78. for (int i = 0; i < photoGroup.value.images.length; i++) {
  79. if (photoGroup.value.images[i].id == currentImageId) {
  80. debugPrint(
  81. 'photoGroups[i].id ${photoGroup.value.images[i].id},i $i');
  82. groupIndex.value = i;
  83. cardSwiperController.moveTo(i);
  84. break;
  85. }
  86. }
  87. }
  88. });
  89. animationController.addStatusListener((status) {
  90. if (status == AnimationStatus.forward) {
  91. // 延迟一秒
  92. Future.delayed(Duration(seconds: 1), () {
  93. animationIsComplete.value = true;
  94. });
  95. }
  96. if (status == AnimationStatus.completed) {
  97. Future.delayed(Duration(seconds: 1), () {
  98. CalendarSelectedPreviewPage.start(photoGroup.value);
  99. });
  100. }
  101. });
  102. }
  103. // 获取参数
  104. void _getArgs() {
  105. photoGroup.value = parameters?['photoGroup'];
  106. currentImageId = parameters?['currentImageId'];
  107. }
  108. void clickUnselect() {
  109. debugPrint("clickUnselect");
  110. cardSwiperController.swipe(CardSwiperDirection.right);
  111. }
  112. FutureOr<bool> onSwipe(
  113. int previousIndex,
  114. int? currentIndex,
  115. CardSwiperDirection direction,
  116. ) {
  117. debugPrint(
  118. 'The card $previousIndex was swiped to the ${direction.name}. Now the card $currentIndex is on top',
  119. );
  120. if (currentIndex != null) {
  121. groupIndex.value = currentIndex;
  122. }
  123. // 预加载
  124. // precacheImage(AssetEntityImageProvider(entity), context);
  125. // 如果direction是left,
  126. if (direction == CardSwiperDirection.left) {
  127. // 先看看图片id是不是在selectedPhotosIds里面,如果在,不处理,如果不在,添加到selectedPhotosIds里面
  128. if (!photoGroup.value.selectedPhotosIds
  129. .contains(photoGroup.value.images[previousIndex].id)) {
  130. debugPrint(
  131. 'add photoGroups[groupIndex.value].id ${photoGroup.value.images[previousIndex].id}');
  132. photoGroup.value.selectedPhotosIds
  133. .add(photoGroup.value.images[previousIndex].id);
  134. }
  135. } else if (direction == CardSwiperDirection.right) {
  136. // 先看看图片id是不是在selectedPhotosIds里面,如果在,在selectedPhotosIds移除,不处理,如果不在,不处理
  137. if (photoGroup.value.selectedPhotosIds
  138. .contains(photoGroup.value.images[previousIndex].id)) {
  139. debugPrint(
  140. 'remove photoGroups[groupIndex.value].id ${photoGroup.value.images[previousIndex].id}');
  141. photoGroup.value.selectedPhotosIds
  142. .remove(photoGroup.value.images[previousIndex].id);
  143. }
  144. }
  145. updateSelectedFilesSize();
  146. return true;
  147. }
  148. bool onSwiperUndo(
  149. int? previousIndex,
  150. int currentIndex,
  151. CardSwiperDirection direction,
  152. ) {
  153. debugPrint(
  154. 'The card $currentIndex was swiped back to the ${direction.name}. Now the card $previousIndex is on top');
  155. groupIndex.value = currentIndex;
  156. // 撤销之前左滑的操作
  157. if (direction == CardSwiperDirection.left) {
  158. debugPrint(
  159. 'photoGroups[groupIndex.value].id ${photoGroup.value.images[groupIndex.value].id}');
  160. if (photoGroup.value.selectedPhotosIds
  161. .contains(photoGroup.value.images[groupIndex.value].id)) {
  162. photoGroup.value.selectedPhotosIds
  163. .remove(photoGroup.value.images[groupIndex.value].id);
  164. }
  165. }
  166. updateSelectedFilesSize();
  167. return true;
  168. }
  169. onSwiperEnd() {
  170. isSwiperEnd.value = true;
  171. debugPrint('onSwiperEnd');
  172. // 延迟500ms
  173. Future.delayed(Duration(milliseconds: 200), () {
  174. animationController.forward(from: 0);
  175. });
  176. PhotoManager.clearFileCache();
  177. // PhotoSelectedPreviewPage.start(photosType, selectedPhotosIds);
  178. }
  179. void recoverSelectPhoto() {
  180. debugPrint('PhotoPreviewController recoverSelectPhoto');
  181. cardSwiperController.undo();
  182. }
  183. void clickSelect() {
  184. cardSwiperController.swipe(CardSwiperDirection.left);
  185. }
  186. void clickBack() {
  187. Get.back();
  188. }
  189. clickDelete() async {
  190. debugPrint('CalendarPreviewController clickDelete');
  191. if (userRepository.isVip()) {
  192. if (photoGroup.value.selectedPhotosIds.isNotEmpty) {
  193. photoDeletingDialog();
  194. // 获取要删除的资产
  195. final assetsToDelete = photoGroup.value.images
  196. .where(
  197. (asset) => photoGroup.value.selectedPhotosIds.contains(asset.id),
  198. )
  199. .toList();
  200. // 调用方法会返回被删除的资源,如果全部失败会返回空列表。
  201. final List<String> result = await PhotoManager.editor.deleteWithIds(
  202. assetsToDelete.map((e) => e.id).toList(),
  203. );
  204. // 比较result和selectedPhotosIds,如果result和selectedPhotosIds相等,说明删除成功,走下面的逻辑
  205. // 如果不相等,说明有删除失败的,走else逻辑
  206. if (result.length == photoGroup.value.selectedPhotosIds.length) {
  207. ImagePickerUtil.updatePhotoData(photoGroup.value.selectedPhotosIds);
  208. cleanSelections();
  209. ToastUtil.show('Delete success');
  210. Future.delayed(Duration(seconds: 2), () {
  211. SmartDialog.dismiss(tag: 'photoDeletingDialog');
  212. photoDeleteFinishDialog();
  213. });
  214. } else {
  215. SmartDialog.dismiss(tag: 'photoDeletingDialog');
  216. // 删除失败
  217. ToastUtil.show("Delete failed");
  218. }
  219. }
  220. } else {
  221. StorePage.start();
  222. }
  223. }
  224. //删除成功清除选中的图片
  225. void cleanSelections() async {
  226. photoGroup.value.images.removeWhere(
  227. (element) => photoGroup.value.selectedPhotosIds.contains(element.id));
  228. photoGroup.value.selectedPhotosIds.clear();
  229. photoGroup.refresh();
  230. if (photoGroup.value.images.isEmpty) {
  231. return;
  232. }
  233. isSwiperEnd.value = false;
  234. if (photoGroup.value.images.isNotEmpty) {
  235. WidgetsBinding.instance.addPostFrameCallback((_) {
  236. groupIndex.value = 0;
  237. cardSwiperController.moveTo(0);
  238. });
  239. }
  240. updateSelectedFilesSize();
  241. }
  242. // 将selectedFilesSize转成String类型,然后单位转换,如果超过1MB,则转成MB,超过1GB,则转成GB,否则KB
  243. String get selectedFilesSizeString {
  244. final double sizeInKB = photoGroup.value.selectedTotalSize.value;
  245. if (sizeInKB >= 1024 * 1024) {
  246. // 先检查最大单位(GB)
  247. return "${(sizeInKB / (1024 * 1024)).toStringAsFixed(2)}GB";
  248. } else if (sizeInKB >= 1024) {
  249. // 然后检查MB
  250. return "${(sizeInKB / 1024).toStringAsFixed(2)}MB";
  251. } else {
  252. // 最后是KB
  253. return "${sizeInKB.toStringAsFixed(2)}KB";
  254. }
  255. }
  256. Future<void> updateSelectedFilesSize() async {
  257. // 如果没有选中的文件,直接返回
  258. if (photoGroup.value.selectedCount == 0) {
  259. photoGroup.value.selectedTotalSize.value = 0;
  260. return;
  261. }
  262. FileSizeCalculatorUtil.calculateTotalSizeByIOS(
  263. assetIds: photoGroup.value.selectedPhotosIds,
  264. updateValue: (double totalSize) {
  265. photoGroup.value.selectedTotalSize.value = totalSize;
  266. },
  267. );
  268. }
  269. void restoreSelections() {
  270. if (photoGroup.value.images.isEmpty) {
  271. clickBack();
  272. return;
  273. }
  274. isSwiperEnd.value = false;
  275. if (photoGroup.value.images.isNotEmpty) {
  276. WidgetsBinding.instance.addPostFrameCallback((_) {
  277. groupIndex.value = 0;
  278. cardSwiperController.moveTo(0);
  279. });
  280. }
  281. updateSelectedFilesSize();
  282. }
  283. @override
  284. void onClose() {
  285. debugPrint('CalendarPreviewController onClose');
  286. animationController.dispose();
  287. super.onClose();
  288. isSwiperEnd.value = false;
  289. // 清理操作,释放资源
  290. cardSwiperController.dispose();
  291. // 清理缓存
  292. PhotoManager.clearFileCache();
  293. }
  294. }