import 'dart:io'; import 'dart:math'; import 'package:clean/base/base_page.dart'; import 'package:clean/data/bean/photos_type.dart'; import 'package:clean/module/similar_photo/similar_photo_controller.dart'; import 'package:clean/resource/assets.gen.dart'; import 'package:clean/router/app_pages.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; import 'package:wechat_assets_picker/wechat_assets_picker.dart'; class SimilarPhotoPage extends BasePage { const SimilarPhotoPage({super.key}); static void start() { Get.toNamed(RoutePath.similarPhoto); } @override bool statusBarDarkFont() => false; @override bool immersive() => true; @override Widget buildBody(BuildContext context) { return Stack(children: [ Container( child: SafeArea(child: Obx(() { if (controller.photoGroups.isEmpty) { return _noNoPicturesCard(); } return Column( children: [ _titleCard(), Flexible( child: Obx(() { return ListView( padding: EdgeInsets.symmetric(horizontal: 16.w), children: [ ...controller.photoGroups.map((group) => Column( children: [ _buildPhotoGroup( imagesList: group.images, imageCount: group.images.length, ), SizedBox(height: 15.h), ], )) ], ); }), ), _bottomBarCard(), ], ); })), ), IgnorePointer( child: Assets.images.bgHome.image( width: 360.w, ), ), ]); } Widget _titleCard() { return Container( alignment: Alignment.centerLeft, padding: EdgeInsets.only(left: 16.w, top: 14.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ GestureDetector( onTap: () => Get.back(), child: Assets.images.iconBackArrow.image( width: 28.w, height: 28.h, ), ), (controller.photoGroups.isEmpty) ? SizedBox() : Container( child: Column( children: [ SizedBox(height: 12.h), Text( 'Similar Photos', style: TextStyle( color: Colors.white, fontSize: 24.sp, fontWeight: FontWeight.w700, ), ), SizedBox(height: 20.h), ], ), ) ], ), ); } Widget _bottomBarCard() { return GestureDetector( onTap:controller.clickJumpSelect, child: Container( width: 360.w, height: 81.h, padding: EdgeInsets.symmetric(horizontal: 16.w), decoration: ShapeDecoration( color: Color(0xFF23232A), shape: RoundedRectangleBorder( side: BorderSide(width: 1.w, color: Colors.white.withOpacity(0.1)), borderRadius: BorderRadius.only( topLeft: Radius.circular(14.r), topRight: Radius.circular(14.r), ), ), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Obx(() { return Text( '${controller.selectedFileCount.value} files selected (${controller.selectedFilesSizeString})', textAlign: TextAlign.center, style: TextStyle( color: Colors.white.withValues(alpha: 0.9), fontSize: 13.sp, fontWeight: FontWeight.w500, ), ); }), GestureDetector( onTap: () => controller.clickDelete(), child: Container( width: 108.w, height: 38.h, decoration: ShapeDecoration( color: Color(0xFF0279FB), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10.r), ), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Text( 'Delete', textAlign: TextAlign.center, style: TextStyle( color: Colors.white, fontSize: 16.sp, fontWeight: FontWeight.w500, ), ), Assets.images.iconDelete.image( width: 18.w, height: 18.h, ), ], ), ), ) ], ), )); } Widget _buildPhotoGroup({ required List imagesList, required int imageCount, }) { return Container( padding: EdgeInsets.symmetric(horizontal: 12.w), margin: EdgeInsets.only(top: 14.h), width: 328.w, height: 258.h, decoration: ShapeDecoration( color: Colors.white.withValues(alpha: 0.12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(14.sp), ), ), child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ Text( "photos $imageCount", textAlign: TextAlign.center, style: TextStyle( color: Colors.white, fontSize: 14.sp, fontWeight: FontWeight.w500, ), ), ], ), GestureDetector( onTap: () => controller.toggleGroupSelection(imagesList), child: Obx(() => Text( controller.photoGroups .firstWhere((g) => g.images.toSet().containsAll(imagesList)) .isSelected .value ? 'Deselect All' : 'Select All', style: TextStyle( color: Colors.white.withValues(alpha: 0.7), fontSize: 14.sp, fontWeight: FontWeight.w400, ), )), ), ], ), SizedBox( height: 148.h, child: ListView( scrollDirection: Axis.horizontal, physics: BouncingScrollPhysics(), children: [ // 第一张大图 if (imageCount > 0) GestureDetector( onTap: () => controller.clickImage(imagesList, 0, PhotosType.similarPhotos), child: SizedBox( width: 148.w, height: 148.h, child: Obx(() { final group = controller.photoGroups .firstWhere((g) => g.images.toSet().containsAll(imagesList)); return Stack( children: [ Container( decoration: ShapeDecoration( color: Colors.white.withValues(alpha: 0.12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8.r), ), image: DecorationImage( image: AssetEntityImageProvider( group.images[0], thumbnailSize: const ThumbnailSize.square(300), isOriginal: false, ), fit: BoxFit.cover, ), ), ), Positioned( left: 8.w, top: 8.h, child: Container( width: 108.w, height: 26.h, padding: EdgeInsets.symmetric( horizontal: 8.w, vertical: 4.h, ), decoration: ShapeDecoration( color: Colors.black.withValues(alpha: 0.74), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(14.21.r), ), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Assets.images.iconSimilarBest.image( width: 11.37.w, height: 11.37.h, ), Text( 'Best result', textAlign: TextAlign.center, style: TextStyle( color: Colors.white, fontSize: 13.sp, fontWeight: FontWeight.w400, ), ), ], ), ), ), Positioned( right: 4.w, bottom: 4.h, child: GestureDetector( onTap: () => controller.toggleImageSelection( imagesList, 0), child: Container( child: group.selectedImages[0] ? Center( child: Assets.images.iconSelected.image( width: 20.w, height: 20.h, ), ) : Center( child: Assets.images.iconUnselected .image( width: 20.w, height: 20.h, ), ), ), ), ), ], ); }), ), ), // 其他图片2x2网格 if (imageCount > 1) ...List.generate(((imageCount - 1) / 4).ceil(), (gridIndex) { return Container( margin: EdgeInsets.only(left: 8.w), width: 142.w, child: GridView.count( physics: NeverScrollableScrollPhysics(), crossAxisCount: 2, mainAxisSpacing: 8.h, crossAxisSpacing: 8.w, children: List.generate( min(4, imageCount - 1 - gridIndex * 4), (index) { final realIndex = gridIndex * 4 + index + 1; return GestureDetector( onTap: () => controller.clickImage(imagesList, realIndex, PhotosType.similarPhotos), child: Obx(() { final group = controller.photoGroups .firstWhere((g) => g.images.toSet().containsAll(imagesList)); return Container( decoration: ShapeDecoration( color: Colors.white.withValues(alpha: 0.12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8.r), ), image: DecorationImage( image: AssetEntityImageProvider( group.images[realIndex], thumbnailSize: const ThumbnailSize.square(300), isOriginal: false, ), fit: BoxFit.cover, ), ), child: Stack( children: [ Positioned( right: 4.w, bottom: 4.h, child: Obx(() { final isSelected = group.selectedImages[realIndex]; return GestureDetector( onTap: () => controller.toggleImageSelection( imagesList, realIndex), child: Container( child: isSelected ? Center( child: Assets .images.iconSelected .image( width: 20.w, height: 20.h, ), ) : Center( child: Assets .images.iconUnselected .image( width: 20.w, height: 20.h, ), ), ), ); }), ), ], ), ); }), ); }, ), ), ); }), ], ), ), GestureDetector( onTap: () => controller.clickSingleGroupDelete(imagesList), child: Container( width: 162.w, height: 38.h, decoration: ShapeDecoration( color: Color(0xFF0279FB), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10.r), ), ), child: Center( child: Obx(() => Text( 'Move ${controller.photoGroups.firstWhere((g) => g.images.toSet().containsAll(imagesList)).selectedImages.where((element) => element).length} to trash', style: TextStyle( color: Colors.white, fontSize: 16.sp, fontWeight: FontWeight.w500, ), )), ), ), ) ], ), ); } Widget _noNoPicturesCard() { return Column( // mainAxisAlignment: MainAxisAlignment.start, children: [ _titleCard(), Spacer(flex: 1), Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ Container( width: 70.w, height: 70.h, clipBehavior: Clip.antiAlias, decoration: BoxDecoration(), child: Assets.images.iconNoPictures.image(), ), SizedBox(height: 22.h), Text( 'No pictures found', textAlign: TextAlign.center, style: TextStyle( color: Colors.white, fontSize: 20.sp, fontWeight: FontWeight.w700, ), ), SizedBox(height: 12.h), Text( 'No pictures available at the moment', textAlign: TextAlign.center, style: TextStyle( color: Colors.white.withValues(alpha: 0.6), fontSize: 14.sp, fontWeight: FontWeight.w400, ), ), ], ), Spacer( flex: 3, ), ], ); } }