calendar_preview_view.dart 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  1. import 'package:clean/base/base_page.dart';
  2. import 'package:clean/module/calendar/preview/calendar_preview_controller.dart';
  3. import 'package:flutter/Material.dart';
  4. import 'package:flutter/src/widgets/framework.dart';
  5. import 'package:flutter_card_swiper/flutter_card_swiper.dart';
  6. import 'package:flutter_screenutil/flutter_screenutil.dart';
  7. import 'package:get/get.dart';
  8. import 'package:lottie/lottie.dart';
  9. import 'package:wechat_assets_picker/wechat_assets_picker.dart';
  10. import '../../../resource/assets.gen.dart';
  11. import '../../../router/app_pages.dart';
  12. import '../../../utils/styles.dart';
  13. import '../../people_photo/photo_group.dart';
  14. import 'package:video_player/video_player.dart';
  15. class CalendarPreviewPage extends BasePage<CalendarPreviewController> {
  16. const CalendarPreviewPage({Key? key}) : super(key: key);
  17. static void start(
  18. {required PhotoGroup photoGroup, required String currentImageId}) {
  19. Get.toNamed(RoutePath.calendarPreview, arguments: {
  20. "photoGroup": photoGroup,
  21. "currentImageId": currentImageId,
  22. });
  23. }
  24. @override
  25. bool statusBarDarkFont() {
  26. return false;
  27. }
  28. @override
  29. bool immersive() {
  30. return true;
  31. }
  32. @override
  33. Widget buildBody(BuildContext context) {
  34. return Stack(children: [
  35. PopScope(
  36. canPop: false,
  37. onPopInvokedWithResult: (didPop, result) {
  38. if (didPop) {
  39. return;
  40. }
  41. controller.clickBack();
  42. },
  43. child: SafeArea(
  44. child: Obx(() {
  45. if (controller.isSwiperEnd.value ||
  46. controller.photoGroup.value.images.isEmpty) {
  47. return onSwiperEndCard();
  48. } else {
  49. return Column(
  50. children: [
  51. _titleCard(),
  52. Spacer(),
  53. SizedBox(
  54. width: 314.w,
  55. height: 392.h,
  56. child: CardSwiper(
  57. scale: 0.8,
  58. allowedSwipeDirection: AllowedSwipeDirection.only(
  59. right: true,
  60. left: true,
  61. ),
  62. isLoop: false,
  63. backCardOffset: Offset(0.w, -20.h),
  64. controller: controller.cardSwiperController,
  65. cardsCount: controller.photoGroup.value.images.length,
  66. onSwipe: controller.onSwipe,
  67. onUndo: controller.onSwiperUndo,
  68. numberOfCardsDisplayed:
  69. (controller.photoGroup.value.images.length == 1)
  70. ? 1
  71. : 1,
  72. onEnd: controller.onSwiperEnd,
  73. cardBuilder: (context, index, horizontalOffsetPercentage,
  74. verticalOffsetPercentage) {
  75. final assetEntity =
  76. controller.photoGroup.value.images[index];
  77. return Stack(
  78. children: [
  79. ClipRRect(
  80. borderRadius: BorderRadius.circular(20.r),
  81. child:
  82. // 判断这个是不是视频
  83. assetEntity.type == AssetType.video
  84. ? Stack(
  85. alignment: Alignment.center,
  86. children: [
  87. AssetEntityImage(
  88. assetEntity,
  89. width: 314.w,
  90. height: 392.h,
  91. fit: BoxFit.cover,
  92. ),
  93. GestureDetector(
  94. // 点击播放视频
  95. onTap: () => controller
  96. .playVideo(assetEntity),
  97. child: Icon(
  98. Icons.play_circle_filled,
  99. size: 50.w,
  100. color: Colors.white
  101. .withValues(alpha: 0.8),
  102. ),
  103. )
  104. ],
  105. )
  106. : AssetEntityImage(
  107. assetEntity,
  108. width: 314.w,
  109. height: 392.h,
  110. fit: BoxFit.cover,
  111. isOriginal: true,
  112. loadingBuilder: (context,child,loadingProgress){
  113. }
  114. // errorBuilder:
  115. // (context, error, stackTrace) {
  116. // debugPrint(
  117. // 'AssetEntityImage error $error');
  118. // return Container();
  119. // },
  120. // frameBuilder: Styles.animFrameBuilder(),
  121. )),
  122. if (horizontalOffsetPercentage != 0)
  123. Positioned(
  124. top: 0,
  125. right: horizontalOffsetPercentage < -10.w
  126. ? 0
  127. : null,
  128. left: horizontalOffsetPercentage > 10.w
  129. ? 0
  130. : null,
  131. child: Container(
  132. child: Column(
  133. children: [
  134. (horizontalOffsetPercentage < -10.w)
  135. ? Assets.images.iconPhotoPreviewDelete
  136. .image(
  137. width: 60.w,
  138. height: 60.w,
  139. )
  140. : (horizontalOffsetPercentage > 10.w)
  141. ? Assets.images.iconPhotoPreviewKeep
  142. .image(
  143. width: 60.w,
  144. height: 60.w,
  145. )
  146. : SizedBox(),
  147. (horizontalOffsetPercentage < -10.w)
  148. ? Text(
  149. 'Delete ',
  150. style: TextStyle(
  151. color: Colors.white,
  152. fontSize: 24.sp,
  153. fontWeight: FontWeight.w500,
  154. ),
  155. )
  156. : (horizontalOffsetPercentage > 10.w)
  157. ? Text(
  158. 'Keep',
  159. style: TextStyle(
  160. color: Colors.white,
  161. fontSize: 24.sp,
  162. fontWeight: FontWeight.w500,
  163. ),
  164. )
  165. : SizedBox(),
  166. ],
  167. )),
  168. ),
  169. ],
  170. );
  171. },
  172. ),
  173. ),
  174. Spacer(),
  175. bottomButtonCard(),
  176. _bottomBarCard(),
  177. ],
  178. );
  179. }
  180. }),
  181. ),
  182. ),
  183. Obx(() {
  184. if (controller.isSwiperEnd.value ||
  185. controller.photoGroup.value.images.isEmpty) {
  186. return IgnorePointer(
  187. child: Assets.images.bgPhotoSelectedPreviewFinish.image(
  188. width: 360.w,
  189. ),
  190. );
  191. } else {
  192. return IgnorePointer(
  193. child: Assets.images.bgHome.image(
  194. width: 360.w,
  195. ),
  196. );
  197. }
  198. }),
  199. ]);
  200. }
  201. Widget _titleCard() {
  202. return Container(
  203. alignment: Alignment.centerLeft,
  204. padding: EdgeInsets.only(left: 16.w, top: 14.h, right: 16.w),
  205. child: Column(
  206. crossAxisAlignment: CrossAxisAlignment.start,
  207. children: [
  208. Row(
  209. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  210. children: [
  211. GestureDetector(
  212. onTap: controller.clickBack,
  213. child: Assets.images.iconBackArrow.image(
  214. width: 28.w,
  215. height: 28.h,
  216. ),
  217. ),
  218. Obx(() => Row(
  219. mainAxisAlignment: MainAxisAlignment.center,
  220. children: [
  221. Text(
  222. '${controller.groupIndex.value + 1}',
  223. textAlign: TextAlign.center,
  224. style: TextStyle(
  225. color: Colors.white,
  226. fontSize: 16.sp,
  227. fontWeight: FontWeight.w700,
  228. ),
  229. ),
  230. Text(
  231. ' / ${controller.photoGroup.value.images.length}',
  232. textAlign: TextAlign.center,
  233. style: TextStyle(
  234. color: Colors.white.withValues(alpha: 0.6),
  235. fontSize: 16.sp,
  236. fontWeight: FontWeight.w500,
  237. ),
  238. ),
  239. ],
  240. )),
  241. GestureDetector(
  242. onTap: controller.recoverSelectPhoto,
  243. child: Assets.images.iconPreviewRecover.image(
  244. width: 30.w,
  245. height: 30.h,
  246. ),
  247. ),
  248. ],
  249. ),
  250. SizedBox(height: 12.h),
  251. ],
  252. ),
  253. );
  254. }
  255. Widget _bottomBarCard() {
  256. return GestureDetector(
  257. onTap: controller.clickJumpSelected,
  258. child: Container(
  259. width: 360.w,
  260. height: 81.h,
  261. padding: EdgeInsets.symmetric(horizontal: 16.w),
  262. decoration: ShapeDecoration(
  263. color: Color(0xFF23232A),
  264. shape: RoundedRectangleBorder(
  265. side: BorderSide(
  266. width: 1.w, color: Colors.white.withValues(alpha: 0.1)),
  267. borderRadius: BorderRadius.only(
  268. topLeft: Radius.circular(14.r),
  269. topRight: Radius.circular(14.r),
  270. ),
  271. ),
  272. ),
  273. child: Row(
  274. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  275. children: [
  276. Obx(() {
  277. return Text(
  278. '${controller.photoGroup.value.selectedPhotosIds.length} files selected (${controller.selectedFilesSizeString} )',
  279. textAlign: TextAlign.center,
  280. style: TextStyle(
  281. color: Colors.white.withValues(alpha: 0.9),
  282. fontSize: 13.sp,
  283. fontWeight: FontWeight.w500,
  284. ),
  285. );
  286. }),
  287. GestureDetector(
  288. onTap: controller.clickDelete,
  289. child: Container(
  290. width: 108.w,
  291. height: 38.h,
  292. decoration: ShapeDecoration(
  293. color: Color(0xFF0279FB),
  294. shape: RoundedRectangleBorder(
  295. borderRadius: BorderRadius.circular(10.r),
  296. ),
  297. ),
  298. child: Row(
  299. mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  300. children: [
  301. Text(
  302. 'Delete',
  303. textAlign: TextAlign.center,
  304. style: TextStyle(
  305. color: Colors.white,
  306. fontSize: 16.sp,
  307. fontWeight: FontWeight.w500,
  308. ),
  309. ),
  310. Assets.images.iconDelete.image(
  311. width: 18.w,
  312. height: 18.h,
  313. ),
  314. ],
  315. ),
  316. )),
  317. ],
  318. ),
  319. ));
  320. }
  321. Widget bottomButtonCard() {
  322. return Container(
  323. margin: EdgeInsets.only(bottom: 54.h),
  324. child: Row(
  325. mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  326. children: [
  327. GestureDetector(
  328. onTap: () => controller.clickSelect(),
  329. child: Assets.images.iconPreviewSelect.image(
  330. width: 62.w,
  331. height: 62.h,
  332. ),
  333. ),
  334. GestureDetector(
  335. onTap: () => controller.clickUnselect(),
  336. child: Assets.images.iconPreviewNoSelect.image(
  337. width: 62.w,
  338. height: 62.h,
  339. ),
  340. ),
  341. ],
  342. ),
  343. );
  344. }
  345. Widget onSwiperEndCard() {
  346. return Column(
  347. children: [
  348. Container(
  349. child: Lottie.asset(
  350. Assets.anim.animFireworks,
  351. controller: controller.animationController,
  352. height: 351.h,
  353. repeat: false,
  354. ),
  355. ),
  356. Visibility(
  357. visible: controller.animationIsComplete.value,
  358. child: Center(
  359. child: Column(
  360. crossAxisAlignment: CrossAxisAlignment.center,
  361. mainAxisAlignment: MainAxisAlignment.center,
  362. children: [
  363. Text(
  364. 'Perfect!',
  365. textAlign: TextAlign.center,
  366. style: TextStyle(
  367. color: Colors.white,
  368. fontSize: 32.sp,
  369. fontWeight: FontWeight.w700,
  370. ),
  371. ),
  372. SizedBox(height: 16.h),
  373. SizedBox(
  374. child: Row(
  375. mainAxisAlignment: MainAxisAlignment.center,
  376. children: [
  377. Container(
  378. clipBehavior: Clip.antiAlias,
  379. decoration: BoxDecoration(),
  380. child:
  381. Assets.images.iconPreviewSwiperEndFirework.image(
  382. width: 40.w,
  383. height: 40.w,
  384. ),
  385. ),
  386. SizedBox(width: 4.w),
  387. Text(
  388. 'All Similar and Redundant\nPhotos Cleared',
  389. textAlign: TextAlign.center,
  390. style: TextStyle(
  391. color: Colors.white.withValues(alpha: 0.9),
  392. fontSize: 16.sp,
  393. fontWeight: FontWeight.w400,
  394. ),
  395. ),
  396. SizedBox(width: 4.w),
  397. Container(
  398. clipBehavior: Clip.antiAlias,
  399. decoration: BoxDecoration(),
  400. child:
  401. Assets.images.iconPreviewSwiperEndFirework.image(
  402. width: 40.w,
  403. height: 40.w,
  404. ),
  405. ),
  406. ],
  407. ),
  408. ),
  409. ],
  410. ),
  411. )),
  412. Spacer(
  413. flex: 5,
  414. ),
  415. controller.photoGroup.value.images.isEmpty
  416. ? SizedBox()
  417. : _bottomBarCard(),
  418. ],
  419. );
  420. }
  421. }