Prechádzať zdrojové kódy

完成预览的删除功能

云天逵 1 rok pred
rodič
commit
3bc5023b5a

+ 7 - 2
lib/module/home/home_controller.dart

@@ -52,7 +52,7 @@ class HomeController extends BaseController {
   Future<void> onInit() async {
     // TODO: implement onInit
     super.onInit();
-    loadPhotosFromDirectory();
+    // loadPhotosFromDirectory();
     if (await Permission.photos.request().isGranted) {
       PhotoManager.clearFileCache();
       getStorageInfo();
@@ -73,7 +73,12 @@ class HomeController extends BaseController {
         );
         ImagePickerUtil.peoplePhotos.value = result ?? [];
 
-        ImagePickerUtil.locationPhotos['location'] = result ?? [];
+        if (result == null) {
+
+        }else {
+          ImagePickerUtil.locationPhotos['location'] = result ?? [];
+        }
+
 
         ImagePickerUtil.screenshotPhotos.value = result ?? [];
 

+ 58 - 50
lib/module/image_picker/image_picker_util.dart

@@ -3,9 +3,11 @@ import 'dart:math';
 
 import 'package:clean/data/bean/photos_type.dart';
 import 'package:clean/model/asset_group.dart';
+import 'package:clean/module/locations_photo/locations_photo_controller.dart';
 import 'package:clean/module/locations_photo/locations_single_photo_controller.dart';
 import 'package:clean/module/people_photo/people_photo_controller.dart';
 import 'package:clean/module/people_photo/photo_group.dart';
+import 'package:clean/module/photo_preview/photo_preview_controller.dart';
 import 'package:clean/module/screenshots_blurry/screenshots_controller.dart';
 import 'package:clean/module/similar_photo/similar_photo_controller.dart';
 import 'package:get/get.dart';
@@ -51,76 +53,82 @@ class ImagePickerUtil {
 
   // 清除所有照片数据
   static void clearAllPhotos() {
-    similarPhotoCount.value = 0;
     screenshotPhotos.clear();
     blurryPhotos.clear();
     similarPhotos.clear();
     locationPhotos.clear();
     peoplePhotos.clear();
   }
+  static Future<void> updatePhotoGroupDate(PhotosType photosType,Set<String> selectedPhotosIds, ) async {
+    // 1. 统一移除相关的照片列表
+    screenshotPhotos.removeWhere((element) => selectedPhotosIds.contains(element.id));
+    for (var group in similarPhotos) {
+      group.removeWhere((element) => selectedPhotosIds.contains(element.id));
+    }
+    locationPhotos.forEach((key, group) => group.removeWhere((element) => selectedPhotosIds.contains(element.id)));
+    peoplePhotos.removeWhere((element) => selectedPhotosIds.contains(element.id));
+    blurryPhotos.removeWhere((element) => selectedPhotosIds.contains(element.id));
+    // 2. 移除空的集合
+    similarPhotos.removeWhere((element) => element.isEmpty);
+    locationPhotos.removeWhere((key, value) => value.isEmpty);
+
 
-  // 更新删除后的照片数据
-  static Future<void> updatePhotoGroupDate(PhotosType photosType) async {
+
+    // 3. 根据photosType来处理不同的控制器和photoGroups
     switch (photosType) {
       case PhotosType.screenshots:
-        // 去除包含在selectedScreenshotPhotosIds中的screenshotPhotos
-        screenshotPhotos.removeWhere(
-            (element) => selectedScreenshotPhotosIds.contains(element.id));
-
-        ScreenShotsController controller = Get.find<ScreenShotsController>();
-        // 同时移除控制器photoGroups中images对应的数据
-        controller.photoGroups.removeWhere((element) => element.images.any((element) => selectedScreenshotPhotosIds.contains(element.id)));
-        selectedScreenshotPhotosIds.clear();
-        controller.restoreSelections();
-        print(screenshotPhotos);
+        ScreenShotsController screenshotController = Get.find<ScreenShotsController>();
+        screenshotController.photoGroups.removeWhere((element) => element.images.any((image) => selectedPhotosIds.contains(image.id)));
+        screenshotController.photoGroups.removeWhere((element) => element.images.isEmpty);
+        selectedScreenshotPhotosIds.removeWhere((element) => selectedPhotosIds.contains(element));
 
+        screenshotController.restoreSelections();
         break;
+
       case PhotosType.similarPhotos:
-        // 去除包含在selectedSimilarPhotosIds中的similarPhotos
-        for (var group in similarPhotos) {
-          group.removeWhere(
-              (element) => selectedSimilarPhotosIds.contains(element.id));
-        }
-        SimilarPhotoController controller = Get.find<SimilarPhotoController>();
-        // 同时移除控制器photoGroups中images对应的数据
-        controller.photoGroups.removeWhere((element) => element.images.any((element) => selectedSimilarPhotosIds.contains(element.id)));
-        selectedSimilarPhotosIds.clear();
-        controller.restoreSelections();
+        SimilarPhotoController similarController = Get.find<SimilarPhotoController>();
+        similarController.photoGroups.removeWhere((element) => element.images.any((image) => selectedPhotosIds.contains(image.id)));
+        similarController.photoGroups.removeWhere((element) => element.images.isEmpty);
+        selectedSimilarPhotosIds.removeWhere((element) => selectedPhotosIds.contains(element));
+        similarController.restoreSelections();
         break;
-      case PhotosType.locationPhotos:
-        // 去除包含在selectedLocationPhotosIds中的locationPhotos
-        for (var group in locationPhotos.values) {
-          group.removeWhere(
-              (element) => selectedLocationPhotosIds.contains(element.id));
-        }
-        LocationsSinglePhotoController controller = Get.find<LocationsSinglePhotoController>();
-        // 同时移除控制器photoGroups中images对应的数据
-        controller.photoGroups.removeWhere((element) => element.images.any((element) => selectedLocationPhotosIds.contains(element.id)));
-        selectedLocationPhotosIds.clear();
-        controller.restoreSelections();
 
+      case PhotosType.locationPhotos:
+        LocationsSinglePhotoController locationsSingleController = Get.find<LocationsSinglePhotoController>();
+        locationsSingleController.photoGroups.removeWhere((element) => element.images.any((image) => selectedPhotosIds.contains(image.id)));
+        locationsSingleController.photoGroups.removeWhere((element) => element.images.isEmpty);
+        LocationsPhotoController locationsPhotoController = Get.find<LocationsPhotoController>();
+        locationsPhotoController.photoGroups.removeWhere((element) => element.images.any((image) => selectedPhotosIds.contains(image.id)));
+        locationsPhotoController.photoGroups.removeWhere((element) => element.images.isEmpty);
+        selectedLocationPhotosIds.removeWhere((element) => selectedPhotosIds.contains(element));
+        locationsSingleController.restoreSelections();
+        locationsPhotoController.restoreSelections();
         break;
+
       case PhotosType.peoplePhotos:
-        // 去除包含在selectedPeoplePhotosIds中的peoplePhotos
-        peoplePhotos.removeWhere(
-            (element) => selectedPeoplePhotosIds.contains(element.id));
-        PeoplePhotoController controller = Get.find<PeoplePhotoController>();
-        // 同时移除控制器photoGroups中images对应的数据
-        controller.photoGroups.removeWhere((element) => element.images.any((element) => selectedPeoplePhotosIds.contains(element.id)));
-        selectedPeoplePhotosIds.clear();
-        controller.restoreSelections();
+        PeoplePhotoController peoplePhotoController = Get.find<PeoplePhotoController>();
+        peoplePhotoController.photoGroups.removeWhere((element) => element.images.any((image) => selectedPhotosIds.contains(image.id)));
+        peoplePhotoController.photoGroups.removeWhere((element) => element.images.isEmpty);
+        selectedPeoplePhotosIds.removeWhere((element) => selectedPhotosIds.contains(element));
+        peoplePhotoController.restoreSelections();
         break;
+
       case PhotosType.blurryPhotos:
-        // 去除包含在selectedBlurryPhotosIds中的blurryPhotos
-        blurryPhotos.removeWhere(
-            (element) => selectedBlurryPhotosIds.contains(element.id));
-        ScreenShotsController controller = Get.find<ScreenShotsController>();
-        // 同时移除控制器photoGroups中images对应的数据
-        controller.photoGroups.removeWhere((element) => element.images.any((element) => selectedBlurryPhotosIds.contains(element.id)));
-        selectedBlurryPhotosIds.clear();
-        controller.restoreSelections();
+        ScreenShotsController blurryController = Get.find<ScreenShotsController>();
+        blurryController.photoGroups.removeWhere((element) => element.images.any((image) => selectedPhotosIds.contains(image.id)));
+        blurryController.photoGroups.removeWhere((element) => element.images.isEmpty);
+        selectedBlurryPhotosIds.removeWhere((element) => selectedPhotosIds.contains(element));
+        blurryController.restoreSelections();
         break;
+
     }
+
+
+
+
+    // 4. 清空已选择的照片ID集合
+    selectedPhotosIds.clear();
+
   }
 
   // 更新照片数据

+ 10 - 7
lib/module/locations_photo/locations_photo_controller.dart

@@ -9,7 +9,7 @@ import 'package:path/path.dart' as p;
 import 'package:wechat_assets_picker/wechat_assets_picker.dart';
 
 class LocationsPhotoController extends GetxController {
-  final RxList<PhotoGroup> photoGroups = <PhotoGroup>[].obs;
+   final RxList<PhotoGroup> photoGroups = <PhotoGroup>[].obs;
 
   @override
   onInit() {
@@ -19,14 +19,15 @@ class LocationsPhotoController extends GetxController {
 
   void loadLocationPhoto() {
     photoGroups.clear();
-    RxMap<String, List<AssetEntity>> locationPhotos =
-        ImagePickerUtil.locationPhotos;
 
-    if (locationPhotos.isNotEmpty) {
-      for (var entry in locationPhotos.entries) {
+    if (ImagePickerUtil.locationPhotos.isEmpty) {
+      print('locationPhotos.isEmpty');
+      return;
+    }
+    if (ImagePickerUtil.locationPhotos.values.isNotEmpty) {
+      print('locationPhotos.isNotEmpty ${ImagePickerUtil.locationPhotos.values}');
+      for (var entry in ImagePickerUtil.locationPhotos.entries) {
         photoGroups.add(PhotoGroup(
-          title: 'photo: ${entry.value.length}',
-          imageCount: entry.value.length,
           isSelected: false,
           images: entry.value,
           location: entry.key,
@@ -44,6 +45,8 @@ class LocationsPhotoController extends GetxController {
     return photoGroups.firstWhere((group) => group.location == location);
   }
 
+  void restoreSelections() {
+  }
 // PhotoGroup getGroupByImages(List<AssetEntity> images) {
 //   final imageIds = images.map((img) => img.id).toSet();
 //   return photoGroups.firstWhere((group) =>

+ 136 - 69
lib/module/locations_photo/locations_photo_view.dart

@@ -25,30 +25,13 @@ class LocationsPhotoPage extends BasePage<LocationsPhotoController> {
     return Stack(children: [
       Container(
         child: SafeArea(
-          child: Column(
-            children: [
-              _titleCard(),
-              Expanded(
-                child: Obx(() {
-                  return ListView(
-                    padding: EdgeInsets.symmetric(horizontal: 16.w),
-                    children: [
-                      ...controller.photoGroups.map((group) => Column(
-                            children: [
-                              _buildPhotoGroup(
-                                title: "photo: ${group.images.length}",
-                                location: group.location?? '',
-                                imageCount: group.images.length,
-                              ),
-                              SizedBox(height: 15.h),
-                            ],
-                          ))
-                    ],
-                  );
-                }),
-              ),
-            ],
-          ),
+          child: Obx(() {
+            if (controller.photoGroups.isEmpty) {
+              return _noNoPicturesCard();
+            } else {
+              return photoDateCard();
+            }
+          }),
         ),
       ),
       IgnorePointer(
@@ -60,6 +43,33 @@ class LocationsPhotoPage extends BasePage<LocationsPhotoController> {
     ]);
   }
 
+  Widget photoDateCard() {
+    return Column(
+      children: [
+        _titleCard(),
+        Expanded(
+          child: Obx(() {
+            return ListView(
+              padding: EdgeInsets.symmetric(horizontal: 16.w),
+              children: [
+                ...controller.photoGroups.map((group) => Column(
+                      children: [
+                        _buildPhotoGroup(
+                          title: "photo: ${group.images.length}",
+                          location: group.location ?? '',
+                          imageCount: group.images.length,
+                        ),
+                        SizedBox(height: 15.h),
+                      ],
+                    ))
+              ],
+            );
+          }),
+        ),
+      ],
+    );
+  }
+
   Widget _titleCard() {
     return Container(
       alignment: Alignment.centerLeft,
@@ -74,16 +84,22 @@ class LocationsPhotoPage extends BasePage<LocationsPhotoController> {
               height: 28.h,
             ),
           ),
-          SizedBox(height: 12.h),
-          Text(
-            'Places Photos',
-            style: TextStyle(
-              color: Colors.white,
-              fontSize: 24.sp,
-              fontWeight: FontWeight.w700,
-            ),
-          ),
-          SizedBox(height: 20.h),
+          (controller.photoGroups.isEmpty)
+              ? const SizedBox()
+              : Column(
+                  children: [
+                    SizedBox(height: 12.h),
+                    Text(
+                      'Places Photos',
+                      style: TextStyle(
+                        color: Colors.white,
+                        fontSize: 24.sp,
+                        fontWeight: FontWeight.w700,
+                      ),
+                    ),
+                    SizedBox(height: 20.h),
+                  ],
+                )
         ],
       ),
     );
@@ -124,43 +140,50 @@ class LocationsPhotoPage extends BasePage<LocationsPhotoController> {
           ),
           SizedBox(
             child: GestureDetector(
-              onTap: () => controller.clickPhotoGroup(controller.getGroupByLocation(location)),
+              onTap: () => controller
+                  .clickPhotoGroup(controller.getGroupByLocation(location)),
               child: Obx(() {
-                final group =
-                    controller.getGroupByLocation(location);
-                final imagePath = group.images.first;
-                return Stack(
-                  children: [
-                    Container(
-                      width: 304.w,
-                      height: 171.h,
-                      decoration: ShapeDecoration(
-                        color: Colors.white.withValues(alpha: 0.12),
-                        shape: RoundedRectangleBorder(
-                          borderRadius: BorderRadius.circular(8.r),
-                        ),
-                        image: DecorationImage(
-                          image: AssetEntityImageProvider(imagePath,thumbnailSize: const ThumbnailSize.square(300),
-                            isOriginal: false,),
-                          fit: BoxFit.cover,
-                        ),
-                      ),
-                    ),
-                    Positioned(
-                        left: 1.w,
-                        right: 1.w,
-                        bottom: 16.sp,
-                        child: Text(
-                          location,
-                          textAlign: TextAlign.center,
-                          style: TextStyle(
-                            color: Colors.white,
-                            fontSize: 20.sp,
-                            fontWeight: FontWeight.w500,
+                final group = controller.getGroupByLocation(location);
+                final imagePath =
+                    group.images.isNotEmpty ? group.images.first : null;
+                return imagePath != null
+                    ? Stack(
+                        children: [
+                          Container(
+                            width: 304.w,
+                            height: 171.h,
+                            decoration: ShapeDecoration(
+                              color: Colors.white.withValues(alpha: 0.12),
+                              shape: RoundedRectangleBorder(
+                                borderRadius: BorderRadius.circular(8.r),
+                              ),
+                              image: DecorationImage(
+                                image: AssetEntityImageProvider(
+                                  imagePath,
+                                  thumbnailSize:
+                                      const ThumbnailSize.square(300),
+                                  isOriginal: false,
+                                ),
+                                fit: BoxFit.cover,
+                              ),
+                            ),
                           ),
-                        ))
-                  ],
-                );
+                          Positioned(
+                              left: 1.w,
+                              right: 1.w,
+                              bottom: 16.sp,
+                              child: Text(
+                                location,
+                                textAlign: TextAlign.center,
+                                style: TextStyle(
+                                  color: Colors.white,
+                                  fontSize: 20.sp,
+                                  fontWeight: FontWeight.w500,
+                                ),
+                              ))
+                        ],
+                      )
+                    : Container();
               }),
             ),
           ),
@@ -168,4 +191,48 @@ class LocationsPhotoPage extends BasePage<LocationsPhotoController> {
       ),
     );
   }
+
+  Widget _noNoPicturesCard() {
+    return Column(
+      // mainAxisAlignment: MainAxisAlignment.start,
+      children: [
+        _titleCard(),
+        SizedBox(
+          height: 170.h,
+        ),
+        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,
+              ),
+            ),
+          ],
+        ),
+      ],
+    );
+  }
 }

+ 37 - 5
lib/module/locations_photo/locations_single_photo_controller.dart

@@ -5,6 +5,7 @@ import 'package:clean/module/image_picker/image_picker_util.dart';
 import 'package:clean/module/locations_photo/locations_photo_controller.dart';
 import 'package:clean/module/people_photo/photo_group.dart';
 import 'package:clean/module/photo_preview/photo_preview_view.dart';
+import 'package:clean/utils/toast_util.dart';
 import 'package:get/get.dart';
 import 'package:wechat_assets_picker/wechat_assets_picker.dart';
 class LocationsSinglePhotoController extends BaseController {
@@ -72,16 +73,21 @@ class LocationsSinglePhotoController extends BaseController {
 
   // 获取参数
   void _getArgs() {
-    photoGroup = parameters?['PhotoGroup'];
+    photoGroup = parameters?['PhotoGroup'] as PhotoGroup;
 
   }
   void loadLocationsSinglePhoto() {
     photoGroups.clear();
+    if (ImagePickerUtil.locationPhotos.isEmpty) {
+      print('locationPhotos.isEmpty');
+      return;
+    }
     photoGroups.add(photoGroup);
   }
 
   void restoreSelections() async {
     final selectedIds = ImagePickerUtil.selectedLocationPhotosIds.toSet();
+
     for (var group in photoGroups) {
       for (int i = 0; i < group.images.length; i++) {
         group.selectedImages[i] = selectedIds.contains(group.images[i].id);
@@ -105,14 +111,40 @@ class LocationsSinglePhotoController extends BaseController {
     selectedFileCount.value =  ImagePickerUtil.selectedLocationPhotosIds.length;
   }
 
-  // 通过标题获取照片组
-  PhotoGroup _getGroupByTitle(String groupTitle) {
-    return photoGroups.firstWhere((g) => g.title == groupTitle);
-  }
 
   // 通过位置获取照片组
   PhotoGroup getGroupByLocation(String? location) {
     return photoGroups.firstWhere((group) => group.location == location);
   }
 
+
+  clickDelete() async {
+    print('clickDelete');
+
+    if (ImagePickerUtil.selectedLocationPhotosIds.isNotEmpty) {
+      final assetsToDelete = photoGroups.expand((group) =>
+          group.images.where((asset) => ImagePickerUtil.selectedLocationPhotosIds.contains(asset.id))
+      ).toList();
+
+      final List<String> result = await PhotoManager.editor.deleteWithIds(
+        assetsToDelete.map((e) => e.id).toList(),
+      );
+      print('PhotoPreviewController result $result');
+
+      if (result.length == ImagePickerUtil.selectedLocationPhotosIds.length) {
+        if (photoGroup.images.length == ImagePickerUtil.selectedLocationPhotosIds.length) {
+          ImagePickerUtil.locationPhotos.remove(photoGroup.location);
+        } else {
+          photoGroup.images.removeWhere((element) => ImagePickerUtil.selectedLocationPhotosIds.contains(element.id));
+        }
+        ImagePickerUtil.updatePhotoGroupDate(PhotosType.locationPhotos, ImagePickerUtil.selectedLocationPhotosIds);
+        ToastUtil.show("Delete success");
+      } else {
+        // 删除失败
+        ToastUtil.show("Delete failed");
+      }
+    }
+  }
+
+
 }

+ 137 - 116
lib/module/locations_photo/locations_single_photo_view.dart

@@ -33,34 +33,44 @@ class LocationsSinglePhotoPage
     return Stack(children: [
       Container(
         child: SafeArea(
-          child: Column(
-            children: [
-              _titleCard(),
-              // Photo groups
-              Expanded(
-                child: Obx(() {
-                  return SizedBox(
-                    child: GridView.builder(
-                        padding: EdgeInsets.symmetric(horizontal: 16.w),
-                        scrollDirection: Axis.vertical,
-                        // 设置为垂直方向滚动
-                        physics: BouncingScrollPhysics(),
-                        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
-                          crossAxisCount: 3, // 每行显示 3 个元素
-                          mainAxisSpacing: 8.w, // 垂直间距
-                          crossAxisSpacing: 8.h, // 水平间距
-                        ),
-                        itemCount: controller.photoGroups.first.imageCount,
-                        itemBuilder: _buildPhotoItem(
-                            controller.photoGroups.first.location ?? "")),
-                  );
+          child: Obx(() {
+            if (controller.photoGroups.isEmpty) {
+              return _noNoPicturesCard();
+            }
+            return Column(
+              children: [
+                _titleCard(),
+                Expanded(
+                  child: Obx(() {
+                    return SizedBox(
+                      child: GridView.builder(
+                          padding: EdgeInsets.symmetric(horizontal: 16.w),
+                          scrollDirection: Axis.vertical,
+                          // 设置为垂直方向滚动
+                          physics: BouncingScrollPhysics(),
+                          gridDelegate:
+                              SliverGridDelegateWithFixedCrossAxisCount(
+                            crossAxisCount: 3, // 每行显示 3 个元素
+                            mainAxisSpacing: 8.w, // 垂直间距
+                            crossAxisSpacing: 8.h, // 水平间距
+                          ),
+                          itemCount: controller.photoGroups.first.images.length,
+                          itemBuilder: _buildPhotoItem(
+                              controller.photoGroups.first.location ?? "")),
+                    );
+                  }),
+                ),
+                Obx(() {
+                  if (controller.selectedFileCount.value == 0) {
+                    return SizedBox();
+                  } else {
+                    return _bottomBarCard();
+                  }
                 }),
-              ),
-
-              _bottomBarCard(),
-              SizedBox(height: 8.h),
-            ],
-          ),
+                SizedBox(height: 8.h),
+              ],
+            );
+          }),
         ),
       ),
       IgnorePointer(
@@ -79,7 +89,6 @@ class LocationsSinglePhotoPage
       child: Column(
         crossAxisAlignment: CrossAxisAlignment.start,
         children: [
-
           Row(
             mainAxisAlignment: MainAxisAlignment.spaceBetween,
             children: [
@@ -90,29 +99,35 @@ class LocationsSinglePhotoPage
                   height: 28.h,
                 ),
               ),
-             Text(
-                controller.photoGroups.first.location ?? "",
-                textAlign: TextAlign.center,
-                style: TextStyle(
-                  color: Colors.white,
-                  fontSize: 17.sp,
-                  fontWeight: FontWeight.w500,
-                ),
-              ),
-              GestureDetector(
-                onTap: () => controller
-                    .toggleGroupSelection(controller.photoGroups.first.location?? ""),
-                child: Obx(() => Text(
-                      controller.photoGroups.first.isSelected.value
-                          ? 'Deselect All'
-                          : 'Select All',
-                      style: TextStyle(
-                        color: Colors.white.withValues(alpha: 0.7),
-                        fontSize: 14.sp,
-                        fontWeight: FontWeight.w400,
-                      ),
-                    )),
-              ),
+              Obx(() {
+                if (controller.photoGroups.isEmpty) return SizedBox();
+                return Text(
+                  controller.photoGroups.first.location ?? "",
+                  textAlign: TextAlign.center,
+                  style: TextStyle(
+                    color: Colors.white,
+                    fontSize: 17.sp,
+                    fontWeight: FontWeight.w500,
+                  ),
+                );
+              }),
+              Obx(() {
+                if (controller.photoGroups.isEmpty) return SizedBox();
+                return GestureDetector(
+                  onTap: () => controller.toggleGroupSelection(
+                      controller.photoGroups.first.location ?? ""),
+                  child: Obx(() => Text(
+                        controller.photoGroups.first.isSelected.value
+                            ? 'Deselect All'
+                            : 'Select All',
+                        style: TextStyle(
+                          color: Colors.white.withValues(alpha: 0.7),
+                          fontSize: 14.sp,
+                          fontWeight: FontWeight.w400,
+                        ),
+                      )),
+                );
+              }),
             ],
           ),
           SizedBox(height: 30.h),
@@ -122,36 +137,39 @@ class LocationsSinglePhotoPage
   }
 
   Widget _bottomBarCard() {
-    return Container(
-      width: 328.w,
-      height: 48.h,
-      decoration: ShapeDecoration(
-        color: Color(0xFF0279FB),
-        shape: RoundedRectangleBorder(
-          borderRadius: BorderRadius.circular(10.r),
-        ),
-      ),
-      padding: EdgeInsets.symmetric(horizontal: 16.w),
-      child: Row(
-        mainAxisAlignment: MainAxisAlignment.center,
-        children: [
-          Assets.images.iconDelete.image(
-            width: 18.w,
-            height: 18.h,
+    return GestureDetector(
+      onTap: () => controller.clickDelete(),
+      child: Container(
+        width: 328.w,
+        height: 48.h,
+        decoration: ShapeDecoration(
+          color: Color(0xFF0279FB),
+          shape: RoundedRectangleBorder(
+            borderRadius: BorderRadius.circular(10.r),
           ),
-          SizedBox(width: 5.w),
-          Obx(() {
-            return Text(
-              '${controller.selectedFileCount.value} files selected (${controller.selectedFilesSize.value.toStringAsFixed(1)} KB)',
-              textAlign: TextAlign.center,
-              style: TextStyle(
-                color: Colors.white,
-                fontSize: 16.sp,
-                fontWeight: FontWeight.w500,
-              ),
-            );
-          }),
-        ],
+        ),
+        padding: EdgeInsets.symmetric(horizontal: 16.w),
+        child: Row(
+          mainAxisAlignment: MainAxisAlignment.center,
+          children: [
+            Assets.images.iconDelete.image(
+              width: 18.w,
+              height: 18.h,
+            ),
+            SizedBox(width: 5.w),
+            Obx(() {
+              return Text(
+                '${controller.selectedFileCount.value} files selected (${controller.selectedFilesSize.value.toStringAsFixed(1)} KB)',
+                textAlign: TextAlign.center,
+                style: TextStyle(
+                  color: Colors.white,
+                  fontSize: 16.sp,
+                  fontWeight: FontWeight.w500,
+                ),
+              );
+            }),
+          ],
+        ),
       ),
     );
   }
@@ -159,7 +177,6 @@ class LocationsSinglePhotoPage
   Widget Function(BuildContext, int) _buildPhotoItem(String location) =>
       (context, index) {
         final group = controller.getGroupByLocation(location);
-        final assetEntity = group.images[index];
 
         return GestureDetector(
           onTap: () => controller.clickImage(location, index),
@@ -176,8 +193,11 @@ class LocationsSinglePhotoPage
                       borderRadius: BorderRadius.circular(9.27.sp),
                     ),
                     image: DecorationImage(
-                      image: AssetEntityImageProvider(assetEntity,thumbnailSize: const ThumbnailSize.square(300),
-                        isOriginal: false,),
+                      image: AssetEntityImageProvider(
+                        group.images[index],
+                        thumbnailSize: const ThumbnailSize.square(300),
+                        isOriginal: false,
+                      ),
                       fit: BoxFit.cover,
                     ),
                   ),
@@ -213,41 +233,42 @@ class LocationsSinglePhotoPage
     return Column(
       children: [
         _titleCard(),
-        Expanded(
-          child: Center(
-            child: Column(
-              mainAxisAlignment: MainAxisAlignment.center,
-              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: 170.h,
+        ),
+        Container(
+          child: Column(
+            mainAxisAlignment: MainAxisAlignment.center,
+            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,
-                  ),
+              ),
+              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,
                 ),
-              ],
-            ),
+              ),
+            ],
           ),
         ),
       ],

+ 37 - 13
lib/module/people_photo/people_photo_controller.dart

@@ -3,13 +3,14 @@ import 'package:clean/data/bean/photos_type.dart';
 import 'package:clean/module/image_picker/image_picker_util.dart';
 import 'package:clean/module/people_photo/photo_group.dart';
 import 'package:clean/module/photo_preview/photo_preview_view.dart';
+import 'package:clean/utils/toast_util.dart';
 import 'package:get/get.dart';
 import 'package:wechat_assets_picker/wechat_assets_picker.dart';
 
 class PeoplePhotoController extends BaseController {
   final RxList<PhotoGroup> photoGroups = <PhotoGroup>[].obs;
   final RxDouble selectedFilesSize = 0.0.obs;
-  final  RxInt selectedFileCount = 0.obs;
+  final RxInt selectedFileCount = 0.obs;
 
   @override
   void onInit() {
@@ -39,7 +40,8 @@ class PeoplePhotoController extends BaseController {
     selectedFilesSize.value = totalSize / 1024; // Convert to KB
   }
 
-  void toggleImageSelection(List<AssetEntity> groupTitle, int imageIndex) async {
+  void toggleImageSelection(
+      List<AssetEntity> groupTitle, int imageIndex) async {
     final group = getGroupByImages(groupTitle);
     final image = group.images[imageIndex];
     final selected = !group.selectedImages[imageIndex];
@@ -69,18 +71,13 @@ class PeoplePhotoController extends BaseController {
       _updateSelectedPeoplePhotosIds(image.id, newValue);
     }
     selectedFileCount.value = ImagePickerUtil.selectedPeoplePhotosIds.length;
-     updateSelectedFilesSize();
-  }
-
-  PhotoGroup _getGroupByTitle(String groupTitle) {
-    return photoGroups.firstWhere((g) => g.title == groupTitle);
+    updateSelectedFilesSize();
   }
 
   PhotoGroup getGroupByImages(List<AssetEntity> images) {
     final imageIds = images.map((img) => img.id).toSet();
-    return photoGroups.firstWhere((group) =>
-        group.images.every((image) => imageIds.contains(image.id))
-    );
+    return photoGroups.firstWhere(
+        (group) => group.images.every((image) => imageIds.contains(image.id)));
   }
 
   void _updateSelectedPeoplePhotosIds(String photoId, bool isSelected) {
@@ -109,7 +106,7 @@ class PeoplePhotoController extends BaseController {
       group.isSelected.value =
           group.selectedImages.every((selected) => selected);
     }
-     updateSelectedFilesSize();
+    updateSelectedFilesSize();
     selectedFileCount.value = ImagePickerUtil.selectedPeoplePhotosIds.length;
   }
 
@@ -120,11 +117,38 @@ class PeoplePhotoController extends BaseController {
     final photoGroup = ImagePickerUtil.peoplePhotos;
     if (photoGroup.isNotEmpty) {
       photoGroups.add(PhotoGroup(
-        title: 'photo : ${photoGroup.length}',
-        imageCount: photoGroup.length,
         isSelected: false,
         images: photoGroup,
       ));
     }
   }
+
+  clickDelete() async {
+    print('clickDelete');
+
+    if (ImagePickerUtil.selectedPeoplePhotosIds.isNotEmpty) {
+      // 获取要删除的资产
+      final assetsToDelete = ImagePickerUtil.peoplePhotos
+          .where(
+            (asset) =>
+                ImagePickerUtil.selectedPeoplePhotosIds.contains(asset.id),
+          )
+          .toList();
+
+      // 调用方法会返回被删除的资源,如果全部失败会返回空列表。
+      final List<String> result = await PhotoManager.editor.deleteWithIds(
+        assetsToDelete.map((e) => e.id).toList(),
+      );
+      print('PhotoPreviewController result $result');
+
+      if (result.length == ImagePickerUtil.selectedPeoplePhotosIds.length) {
+        // 删除成功
+        ToastUtil.show("Delete success");
+        ImagePickerUtil.updatePhotoGroupDate(PhotosType.peoplePhotos,ImagePickerUtil.selectedPeoplePhotosIds);
+      } else {
+        // 删除失败
+        ToastUtil.show("Delete failed");
+      }
+    }
+  }
 }

+ 100 - 36
lib/module/people_photo/people_photo_view.dart

@@ -35,32 +35,38 @@ class PeoplePhotoPage extends BasePage<PeoplePhotoController> {
     return Stack(children: [
       Container(
         child: SafeArea(
-          child: Column(
-            children: [
-              _titleCard(),
-              // Photo groups
-              Expanded(
-                child: Obx(() {
-                  return ListView(
-                    padding: EdgeInsets.symmetric(horizontal: 16.w),
-                    children: [
-                      ...controller.photoGroups.map((group) => Column(
-                            children: [
-                              _buildPhotoGroup(
-                                images: group.images,
-                                title: group.title,
-                                imageCount: group.images.length,
-                              ),
-                              SizedBox(height: 15.h),
-                            ],
-                          ))
-                    ],
-                  );
-                }),
-              ),
-              _bottomBarCard(),
-            ],
-          ),
+          child: Obx(() {
+            if (controller.photoGroups.isEmpty ||
+                controller.photoGroups[0].images.isEmpty) {
+              return _noNoPicturesCard();
+            }
+            return Column(
+              children: [
+                _titleCard(),
+                // Photo groups
+                Flexible(
+                  child: Obx(() {
+                    return ListView(
+                      padding: EdgeInsets.symmetric(horizontal: 16.w),
+                      children: [
+                        ...controller.photoGroups.map((group) => Column(
+                              children: [
+                                _buildPhotoGroup(
+                                  images: group.images,
+                                  title: "photo: ${group.images.length}",
+                                  imageCount: group.images.length,
+                                ),
+                                SizedBox(height: 15.h),
+                              ],
+                            ))
+                      ],
+                    );
+                  }),
+                ),
+                _bottomBarCard(),
+              ],
+            );
+          }),
         ),
       ),
       IgnorePointer(
@@ -86,16 +92,23 @@ class PeoplePhotoPage extends BasePage<PeoplePhotoController> {
               height: 28.h,
             ),
           ),
-          SizedBox(height: 12.h),
-          Text(
-            'People Photos',
-            style: TextStyle(
-              color: Colors.white,
-              fontSize: 24.sp,
-              fontWeight: FontWeight.w700,
-            ),
-          ),
-          SizedBox(height: 20.h),
+          (controller.photoGroups.isEmpty ||
+                  controller.photoGroups[0].images.isEmpty)
+              ? SizedBox()
+              : Column(
+                  children: [
+                    SizedBox(height: 12.h),
+                    Text(
+                      'People Photos',
+                      style: TextStyle(
+                        color: Colors.white,
+                        fontSize: 24.sp,
+                        fontWeight: FontWeight.w700,
+                      ),
+                    ),
+                    SizedBox(height: 20.h),
+                  ],
+                )
         ],
       ),
     );
@@ -131,6 +144,11 @@ class PeoplePhotoPage extends BasePage<PeoplePhotoController> {
               ),
             );
           }),
+          GestureDetector(
+            onTap: () {
+              controller.clickDelete();
+            },
+            child:
           Container(
             width: 108.w,
             height: 38.h,
@@ -159,6 +177,7 @@ class PeoplePhotoPage extends BasePage<PeoplePhotoController> {
               ],
             ),
           ),
+          ),
         ],
       ),
     );
@@ -307,4 +326,49 @@ class PeoplePhotoPage extends BasePage<PeoplePhotoController> {
           }),
         );
       };
+
+  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,
+        ),
+      ],
+    );
+  }
 }

+ 5 - 5
lib/module/people_photo/photo_group.dart

@@ -3,10 +3,10 @@ import 'package:wechat_assets_picker/wechat_assets_picker.dart';
 
 class PhotoGroup {
   // 照片组的标题
-  final String title;
+  // final String title;
 
-  // 照片组中的图片数量
-  final int imageCount;
+  // // 照片组中的图片数量
+  // final int imageCount;
 
   // 照片组的选择状态
   final RxBool isSelected;
@@ -25,8 +25,8 @@ class PhotoGroup {
 
   // 构造函数
   PhotoGroup({
-    required this.imageCount,
-    required this.title,
+    // required this.imageCount,
+    // required this.title,
     required bool isSelected,
     required this.images,
     this.location,

+ 56 - 28
lib/module/photo_preview/photo_preview_controller.dart

@@ -6,9 +6,11 @@ import 'package:clean/module/image_picker/image_picker_util.dart';
 import 'package:clean/module/locations_photo/locations_single_photo_controller.dart';
 import 'package:clean/module/people_photo/people_photo_controller.dart';
 import 'package:clean/module/people_photo/photo_group.dart';
+import 'package:clean/module/photo_preview/phtoto_selected_preview_view.dart';
 import 'package:clean/module/screenshots_blurry/screenshots_controller.dart';
 import 'package:clean/module/similar_photo/similar_photo_controller.dart';
 import 'package:clean/utils/file_utils.dart';
+import 'package:clean/utils/toast_util.dart';
 import 'package:flutter/Material.dart';
 import 'package:flutter_card_swiper/flutter_card_swiper.dart';
 import 'package:get/get.dart';
@@ -98,6 +100,21 @@ class PhotoPreviewController extends BaseController {
     updateSelectedFilesSize();
   }
 
+  void restoreSelections() async {
+
+    photoGroups.removeWhere((element) => selectedPhotosIds.contains(element.id));
+    selectedPhotosIds.clear();
+    if (photoGroups.isNotEmpty) {
+      WidgetsBinding.instance.addPostFrameCallback((_) {
+        groupIndex.value = 0;
+        cardSwiperController.value.moveTo(0);
+      });
+
+    }
+    updateSelectedFilesSize();
+    selectedFileCount.value = selectedPhotosIds.length;
+  }
+
   Future<void> updateSelectedFilesSize() async {
     double totalSize = 0;
     // 通过selectedPhotosIds获取选中的图片,然后计算大小
@@ -115,7 +132,6 @@ class PhotoPreviewController extends BaseController {
   }
 
   void recoverSelectPhoto() {
-
     cardSwiperController.value.undo();
   }
 
@@ -148,7 +164,6 @@ class PhotoPreviewController extends BaseController {
         ImagePickerUtil.selectedScreenshotPhotosIds
             .assignAll(selectedPhotosIds);
         ScreenShotsController controller = Get.find<ScreenShotsController>();
-
         controller.restoreSelections();
         break;
       case PhotosType.similarPhotos:
@@ -235,16 +250,19 @@ class PhotoPreviewController extends BaseController {
   onSwiperEnd() {
     isSwiperEnd.value = true;
     print('onSwiperEnd');
+
+    PhotoSelectedPreviewPage.start(photosType, selectedPhotosIds);
   }
 
-  clickDelete() async{
+  clickDelete() async {
     print('clickDelete');
-    switch(photosType){
+    switch (photosType) {
       case PhotosType.peoplePhotos:
         ImagePickerUtil.selectedPeoplePhotosIds.assignAll(selectedPhotosIds);
         break;
       case PhotosType.screenshots:
-        ImagePickerUtil.selectedScreenshotPhotosIds.assignAll(selectedPhotosIds);
+        ImagePickerUtil.selectedScreenshotPhotosIds
+            .assignAll(selectedPhotosIds);
         break;
       case PhotosType.similarPhotos:
         ImagePickerUtil.selectedSimilarPhotosIds.assignAll(selectedPhotosIds);
@@ -258,41 +276,51 @@ class PhotoPreviewController extends BaseController {
     }
     if (selectedPhotosIds.isNotEmpty) {
       // 获取要删除的资产
-      final assetsToDelete = photoGroups.where(
-        (asset) => selectedPhotosIds.contains(asset.id),
-      ).toList();
+      final assetsToDelete = photoGroups
+          .where(
+            (asset) => selectedPhotosIds.contains(asset.id),
+          )
+          .toList();
 
       // 调用方法会返回被删除的资源,如果全部失败会返回空列表。
       final List<String> result = await PhotoManager.editor.deleteWithIds(
         assetsToDelete.map((e) => e.id).toList(),
       );
-      print ('PhotoPreviewController result $result');
-    }
+      print('PhotoPreviewController result $result');
 
-    switch(photosType){
-      case PhotosType.peoplePhotos:
-        ImagePickerUtil.updatePhotoGroupDate(PhotosType.peoplePhotos);
+      //   比较result和selectedPhotosIds,如果result和selectedPhotosIds相等,说明删除成功,走下面的逻辑
+      // 如果不相等,说明有删除失败的,走else逻辑
+      if (result.length == selectedPhotosIds.length) {
+        switch (photosType) {
+          case PhotosType.peoplePhotos:
+            ImagePickerUtil.updatePhotoGroupDate(photosType,ImagePickerUtil.selectedPeoplePhotosIds);
 
-        break;
-      case PhotosType.screenshots:
-        ImagePickerUtil.updatePhotoGroupDate(PhotosType.screenshots);
+            break;
+          case PhotosType.screenshots:
+            ImagePickerUtil.updatePhotoGroupDate(photosType,ImagePickerUtil.selectedScreenshotPhotosIds);
 
-        break;
-      case PhotosType.similarPhotos:
-        ImagePickerUtil.updatePhotoGroupDate(PhotosType.similarPhotos);
+            break;
+          case PhotosType.similarPhotos:
+            ImagePickerUtil.updatePhotoGroupDate(photosType,ImagePickerUtil.selectedSimilarPhotosIds);
 
-        break;
-      case PhotosType.locationPhotos:
-        ImagePickerUtil.updatePhotoGroupDate(PhotosType.locationPhotos);
+            break;
+          case PhotosType.locationPhotos:
+            ImagePickerUtil.updatePhotoGroupDate(photosType,ImagePickerUtil.selectedLocationPhotosIds);
 
-        break;
-      case PhotosType.blurryPhotos:
-        ImagePickerUtil.updatePhotoGroupDate(PhotosType.blurryPhotos);
+            break;
+          case PhotosType.blurryPhotos:
+            ImagePickerUtil.updatePhotoGroupDate(photosType,ImagePickerUtil.selectedBlurryPhotosIds);
 
-        break;
-    }
-    _initData();
+            break;
+        }
 
+        restoreSelections();
+      } else {
+        // 删除失败
+        ToastUtil.show("Delete failed");
+      }
+    }
   }
 
+
 }

+ 45 - 52
lib/module/photo_preview/photo_preview_view.dart

@@ -48,10 +48,50 @@ class PhotoPreviewPage extends BasePage<PhotoPreviewController> {
         child: SafeArea(
           child: Container(
             child: Obx(() {
-              if (controller.isSwiperEnd.value) {
+              if (controller.isSwiperEnd.value||controller.photoGroups.isEmpty) {
                 return onSwiperEndCard();
               } else {
-                return _photoDataCard();
+                return Column(
+                  children: [
+                    _titleCard(),
+                    Spacer(),
+                    Container(
+                      width: 314.w,
+                      height: 392.h,
+                      child: CardSwiper(
+                        scale: 0.8,
+                        allowedSwipeDirection: AllowedSwipeDirection.only(
+                          right: true,
+                          left: true,
+                        ),
+                        isLoop: false,
+                        backCardOffset: Offset(0.w, -20.h),
+                        controller: controller.cardSwiperController.value,
+                        cardsCount: controller.photoGroups.length,
+                        onSwipe: controller.onSwipe,
+                        onUndo: controller.onSwiperUndo,
+                        numberOfCardsDisplayed: (controller.photoGroups.length == 1)
+                            ? 1
+                            : (controller.photoGroups.length == 2)
+                            ? 2
+                            : 3,
+                        onEnd: controller.onSwiperEnd,
+                        cardBuilder: (context, index, horizontalOffsetPercentage,
+                            verticalOffsetPercentage) {
+                          final assetEntity = controller.photoGroups[index];
+                          return AssetEntityImage(
+                            assetEntity,
+                            width: 314.w,
+                            height: 392.h,
+                          );
+                        },
+                      ),
+                    ),
+                    Spacer(),
+                    bottomButtonCard(),
+                    _bottomBarCard(),
+                  ],
+                );
               }
             }),
           ),
@@ -66,55 +106,6 @@ class PhotoPreviewPage extends BasePage<PhotoPreviewController> {
     ]);
   }
 
-  Widget _photoDataCard() {
-    return Column(
-      children: [
-        _titleCard(),
-        Spacer(),
-        (controller.photoGroups.length == 0)
-            ? Container(
-                width: 314.w,
-                height: 392.h,
-                child: AssetEntityImage(
-                  controller.photoGroups[0],
-                  width: 314.w,
-                  height: 392.h,
-                ),
-              )
-            : Container(
-                width: 314.w,
-                height: 392.h,
-                child: CardSwiper(
-                  scale: 0.8,
-                  allowedSwipeDirection: AllowedSwipeDirection.only(
-                    right: true,
-                    left: true,
-                  ),
-                  isLoop: false,
-                  backCardOffset: Offset(0.w, -20.h),
-                  controller: controller.cardSwiperController.value,
-                  cardsCount: controller.photoGroups.length,
-                  onSwipe: controller.onSwipe,
-                  onUndo: controller.onSwiperUndo,
-                  numberOfCardsDisplayed: 1,
-                  onEnd: controller.onSwiperEnd,
-                  cardBuilder: (context, index, horizontalOffsetPercentage,
-                      verticalOffsetPercentage) {
-                    final assetEntity = controller.photoGroups[index];
-                    return AssetEntityImage(
-                      assetEntity,
-                      width: 314.w,
-                      height: 392.h,
-                    );
-                  },
-                ),
-              ),
-        Spacer(),
-        bottomButtonCard(),
-        _bottomBarCard(),
-      ],
-    );
-  }
 
   Widget _titleCard() {
     return Container(
@@ -319,8 +310,10 @@ class PhotoPreviewPage extends BasePage<PhotoPreviewController> {
           ),
         ),
         Spacer(),
-        _bottomBarCard(),
+        controller.photoGroups.isEmpty? SizedBox() : _bottomBarCard(),
       ],
     );
   }
+
+
 }

+ 204 - 0
lib/module/photo_preview/photo_selected_preview_controller.dart

@@ -0,0 +1,204 @@
+import 'package:clean/base/base_controller.dart';
+import 'package:clean/data/bean/photos_type.dart';
+import 'package:clean/module/image_picker/image_picker_util.dart';
+import 'package:clean/module/people_photo/photo_group.dart';
+import 'package:clean/module/photo_preview/photo_preview_controller.dart';
+import 'package:clean/utils/toast_util.dart';
+import 'package:get/get.dart';
+import 'package:wechat_assets_picker/wechat_assets_picker.dart';
+
+class PhotoSelectedPreviewController extends BaseController {
+  late PhotosType photosType;
+
+  final RxList<PhotoGroup> photoGroups = <PhotoGroup>[].obs;
+  final RxDouble selectedFilesSize = 0.0.obs;
+  RxInt selectedFileCount = 0.obs;
+  RxList<AssetEntity> selectedPhotos = <AssetEntity>[].obs;
+  late final RxSet<String> selectedPhotosIds;
+
+  PhotoPreviewController photoPreviewController =
+      Get.find<PhotoPreviewController>();
+
+  @override
+  onInit() {
+    super.onInit();
+    _getArgs();
+
+    loadPhotoSelectedPreview();
+  }
+
+  _getArgs() {
+    photosType = parameters?['photosType'];
+    selectedPhotosIds = parameters?['selectedIds'];
+  }
+
+  loadPhotoSelectedPreview() {
+    photoGroups.clear();
+    for (var assetsEntity in photoPreviewController.photoGroups) {
+      if (selectedPhotosIds.contains(assetsEntity.id)) {
+        selectedPhotos.add(assetsEntity);
+      }
+    }
+    if (selectedPhotos.isNotEmpty) {
+      photoGroups.add(PhotoGroup(
+        isSelected: true,
+        images: selectedPhotos,
+      ));
+    }
+
+    updateSelectedFilesSize();
+
+    selectedFileCount.value = selectedPhotosIds.length;
+  }
+
+  void restoreSelections() async {
+    for (var group in photoGroups) {
+      for (int i = 0; i < group.images.length; i++) {
+        if (selectedPhotosIds.contains(group.images[i].id)) {
+          group.selectedImages[i] = true;
+        } else {
+          group.selectedImages[i] = false;
+        }
+      }
+      group.isSelected.value =
+          group.selectedImages.every((selected) => selected);
+    }
+    updateSelectedFilesSize();
+    selectedFileCount.value = selectedPhotosIds.length;
+  }
+
+  clickDelete() async {
+    print('clickDelete');
+    switch (photosType) {
+      case PhotosType.peoplePhotos:
+        ImagePickerUtil.selectedPeoplePhotosIds.assignAll(selectedPhotosIds);
+        break;
+      case PhotosType.screenshots:
+        ImagePickerUtil.selectedScreenshotPhotosIds
+            .assignAll(selectedPhotosIds);
+        break;
+      case PhotosType.similarPhotos:
+        ImagePickerUtil.selectedSimilarPhotosIds.assignAll(selectedPhotosIds);
+        break;
+      case PhotosType.locationPhotos:
+        ImagePickerUtil.selectedLocationPhotosIds.assignAll(selectedPhotosIds);
+        break;
+      case PhotosType.blurryPhotos:
+        ImagePickerUtil.selectedBlurryPhotosIds.assignAll(selectedPhotosIds);
+        break;
+    }
+    if (selectedPhotosIds.isNotEmpty) {
+      final assetsToDelete = photoGroups
+          .expand((group) => group.images
+              .where((asset) => selectedPhotosIds.contains(asset.id)))
+          .toList();
+
+      final List<String> result = await PhotoManager.editor.deleteWithIds(
+        assetsToDelete.map((e) => e.id).toList(),
+      );
+
+      //   比较result和selectedPhotosIds,如果result和selectedPhotosIds相等,说明删除成功,走下面的逻辑
+      // 如果不相等,说明有删除失败的,走else逻辑
+      if (result.length == selectedPhotosIds.length) {
+        switch (photosType) {
+          case PhotosType.peoplePhotos:
+            ImagePickerUtil.updatePhotoGroupDate(
+                photosType, ImagePickerUtil.selectedPeoplePhotosIds);
+
+            break;
+          case PhotosType.screenshots:
+            ImagePickerUtil.updatePhotoGroupDate(
+                photosType, ImagePickerUtil.selectedScreenshotPhotosIds);
+
+            break;
+          case PhotosType.similarPhotos:
+            ImagePickerUtil.updatePhotoGroupDate(
+                photosType, ImagePickerUtil.selectedSimilarPhotosIds);
+
+            break;
+          case PhotosType.locationPhotos:
+            ImagePickerUtil.updatePhotoGroupDate(
+                photosType, ImagePickerUtil.selectedLocationPhotosIds);
+
+            break;
+          case PhotosType.blurryPhotos:
+            ImagePickerUtil.updatePhotoGroupDate(
+                photosType, ImagePickerUtil.selectedBlurryPhotosIds);
+
+            break;
+        }
+
+        photoGroups.removeWhere((element) => element.images
+            .any((image) => selectedPhotosIds.contains(image.id)));
+        photoGroups.removeWhere((element) => element.images.isEmpty);
+        selectedPhotosIds.clear();
+
+        restoreSelections();
+      } else {
+        // 删除失败
+        ToastUtil.show("Delete failed");
+      }
+    }
+  }
+
+  void toggleGroupSelection(List<AssetEntity> images) {
+    final group = getGroupByImages(images);
+    final newValue = !group.isSelected.value;
+    group.toggleSelectAll(newValue);
+
+    for (var image in group.images) {
+      _updateSelectedPhotosIds(image.id, newValue);
+    }
+
+    updateSelectedFilesSize();
+    selectedFileCount.value = selectedPhotosIds.length;
+  }
+
+  Future<void> updateSelectedFilesSize() async {
+    double totalSize = 0;
+    for (var group in photoGroups) {
+      for (int i = 0; i < group.images.length; i++) {
+        if (group.selectedImages[i]) {
+          final file = await group.images[i].file;
+          if (file != null) {
+            totalSize += file.lengthSync();
+          }
+        }
+      }
+    }
+    selectedFilesSize.value = totalSize / 1024; // Convert to KB
+  }
+
+  void clickImage(List<AssetEntity> images, int imageIndex) {
+    final group = getGroupByImages(images);
+    final image = group.images[imageIndex];
+  }
+
+  void toggleImageSelection(List<AssetEntity> images, int imageIndex) {
+    final group = getGroupByImages(images);
+    final image = group.images[imageIndex];
+    final selected = !group.selectedImages[imageIndex];
+
+    group.selectedImages[imageIndex] = selected;
+    _updateSelectedPhotosIds(image.id, selected);
+
+    group.isSelected.value = group.selectedImages.every((selected) => selected);
+
+    updateSelectedFilesSize();
+    selectedFileCount.value = selectedPhotosIds.length;
+  }
+
+  PhotoGroup getGroupByImages(List<AssetEntity> images) {
+    final imageIds = images.map((img) => img.id).toSet();
+    return photoGroups.firstWhere(
+        (group) => group.images.every((image) => imageIds.contains(image.id)));
+  }
+
+  void _updateSelectedPhotosIds(String photoId, bool isSelected) {
+    if (isSelected) {
+      selectedPhotosIds.add(photoId);
+    } else {
+      selectedPhotosIds.remove(photoId);
+    }
+  }
+}

+ 274 - 0
lib/module/photo_preview/phtoto_selected_preview_view.dart

@@ -0,0 +1,274 @@
+import 'package:clean/base/base_page.dart';
+import 'package:clean/data/bean/photos_type.dart';
+import 'package:clean/module/photo_preview/photo_selected_preview_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_core/src/get_main.dart';
+import 'package:wechat_assets_picker/wechat_assets_picker.dart';
+import 'package:get/get.dart';
+
+class PhotoSelectedPreviewPage
+    extends BasePage<PhotoSelectedPreviewController> {
+  const PhotoSelectedPreviewPage({super.key});
+
+  static void start(PhotosType photosType, Set<String> selectedIds) {
+    Get.toNamed(RoutePath.photoSelectedPreview, arguments: {
+      "photosType": photosType,
+      "selectedIds": selectedIds,
+    });
+  }
+
+  @override
+  bool statusBarDarkFont() {
+    return false;
+  }
+
+  @override
+  bool immersive() {
+    return true;
+  }
+
+  @override
+  Widget buildBody(BuildContext context) {
+    return Stack(children: [
+      Container(
+        child: SafeArea(
+          child: Obx(() {
+            if (controller.photoGroups.isEmpty ||
+                controller.photoGroups[0].images.isEmpty) {
+              return _noNoPicturesCard();
+            }
+            return Column(
+              children: [
+                _titleCard(),
+                Flexible(
+                  child: Obx(() {
+                    return SizedBox(
+                      child: GridView.builder(
+                          padding: EdgeInsets.symmetric(horizontal: 16.w),
+                          scrollDirection: Axis.vertical,
+                          // 设置为垂直方向滚动
+                          physics: BouncingScrollPhysics(),
+                          gridDelegate:
+                              SliverGridDelegateWithFixedCrossAxisCount(
+                            crossAxisCount: 3, // 每行显示 2 个元素
+                            mainAxisSpacing: 8.w, // 垂直间距
+                            crossAxisSpacing: 8.h, // 水平间距
+                          ),
+                          itemCount: controller.photoGroups[0].images.length,
+                          itemBuilder: _buildPhotoItem(
+                              controller.photoGroups[0].images)),
+                    );
+                  }),
+                ),
+                Obx(() {
+                  if (controller.selectedPhotosIds.isNotEmpty) {
+                    return _bottomBarCard();
+                  } else {
+                    return SizedBox();
+                  }
+                }),
+                SizedBox(height: 8.h),
+              ],
+            );
+          }),
+        ),
+      ),
+      IgnorePointer(
+        child: Assets.images.bgHome.image(
+          width: 360.w,
+          height: 234.h,
+        ),
+      ),
+    ]);
+  }
+
+  Widget _titleCard() {
+    return Container(
+      alignment: Alignment.centerLeft,
+      padding: EdgeInsets.only(left: 16.w, top: 14.h, right: 16.w,bottom: 20.h),
+      child: Column(
+        crossAxisAlignment: CrossAxisAlignment.start,
+        children: [
+          Obx(() {
+            return Row(
+              mainAxisAlignment: MainAxisAlignment.spaceBetween,
+              children: [
+                GestureDetector(
+                  onTap: () => Get.back(),
+                  child: Assets.images.iconBackArrow.image(
+                    width: 28.w,
+                    height: 28.h,
+                  ),
+                ),
+                // 如果photoGroup数据为空,不显示全选按钮
+                controller.photoGroups.isEmpty ||
+                        controller.photoGroups[0].images.isEmpty
+                    ? Container()
+                    : GestureDetector(
+                        onTap: () => controller.toggleGroupSelection(
+                            controller.photoGroups[0].images),
+                        child: Obx(() => Text(
+                              controller
+                                      .getGroupByImages(
+                                          controller.photoGroups[0].images)
+                                      .isSelected
+                                      .value
+                                  ? 'Deselect All'
+                                  : 'Select All',
+                              style: TextStyle(
+                                color: Colors.white.withValues(alpha: 0.7),
+                                fontSize: 14.sp,
+                                fontWeight: FontWeight.w400,
+                              ),
+                            )),
+                      ),
+              ],
+            );
+          }),
+        ],
+      ),
+    );
+  }
+
+  Widget _bottomBarCard() {
+    return GestureDetector(
+        onTap: () {
+          controller.clickDelete();
+        },
+        child: Container(
+          width: 328.w,
+          height: 48.h,
+          decoration: ShapeDecoration(
+            color: Color(0xFF0279FB),
+            shape: RoundedRectangleBorder(
+              borderRadius: BorderRadius.circular(10.r),
+            ),
+          ),
+          padding: EdgeInsets.symmetric(horizontal: 16.w),
+          child: Row(
+            mainAxisAlignment: MainAxisAlignment.center,
+            children: [
+              Assets.images.iconDelete.image(
+                width: 18.w,
+                height: 18.h,
+              ),
+              SizedBox(width: 5.w),
+              Obx(() {
+                return Text(
+                  'delete ${controller.selectedFilesSize.value.toStringAsFixed(1)} KB',
+                  textAlign: TextAlign.center,
+                  style: TextStyle(
+                    color: Colors.white,
+                    fontSize: 16.sp,
+                    fontWeight: FontWeight.w500,
+                  ),
+                );
+              }),
+            ],
+          ),
+        ));
+  }
+
+  Widget Function(BuildContext, int) _buildPhotoItem(
+          List<AssetEntity> images) =>
+      (context, index) {
+        final group =
+            controller.getGroupByImages(controller.photoGroups[0].images);
+
+        return GestureDetector(
+          onTap: () => controller.clickImage(images, index),
+          child: Obx(() {
+            var isSelected = group.selectedImages[index];
+            return Stack(
+              children: [
+                Container(
+                  width: 104.w,
+                  height: 104.w,
+                  decoration: ShapeDecoration(
+                    color: Colors.white.withValues(alpha: 0.12),
+                    shape: RoundedRectangleBorder(
+                      borderRadius: BorderRadius.circular(9.27.sp),
+                    ),
+                    image: DecorationImage(
+                      image: AssetEntityImageProvider(
+                        group.images[index],
+                        thumbnailSize: const ThumbnailSize.square(300),
+                        isOriginal: false,
+                      ),
+                      fit: BoxFit.cover,
+                    ),
+                  ),
+                ),
+                Positioned(
+                  right: 8.w,
+                  bottom: 8.h,
+                  child: GestureDetector(
+                      onTap: () =>
+                          controller.toggleImageSelection(images, index),
+                      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,
+                              )),
+                      )),
+                ),
+              ],
+            );
+          }),
+        );
+      };
+
+  Widget _noNoPicturesCard() {
+    return Column(
+      mainAxisAlignment: MainAxisAlignment.start,
+      children: [
+        _titleCard(),
+        SizedBox(height: 170.h),
+        Column(
+          mainAxisAlignment: MainAxisAlignment.center,
+          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,
+              ),
+            ),
+          ],
+        ),
+      ],
+    );
+  }
+}

+ 53 - 25
lib/module/screenshots_blurry/screenshots_controller.dart

@@ -3,15 +3,16 @@ import 'package:clean/data/bean/photos_type.dart';
 import 'package:clean/module/image_picker/image_picker_util.dart';
 import 'package:clean/module/people_photo/photo_group.dart';
 import 'package:clean/module/photo_preview/photo_preview_view.dart';
+import 'package:clean/utils/toast_util.dart';
 import 'package:get/get.dart';
-
+import 'package:wechat_assets_picker/wechat_assets_picker.dart';
 
 class ScreenShotsController extends BaseController {
   late String titleName;
   final RxList<PhotoGroup> photoGroups = <PhotoGroup>[].obs;
   final RxDouble selectedFilesSize = 0.0.obs;
   RxInt selectedFileCount = 0.obs;
-  final RxList<String> selectedPhotosIds = <String>[].obs;
+  final RxSet<String> selectedPhotosIds = <String>{}.obs;
 
   Future<void> updateSelectedFilesSize() async {
     double totalSize = 0;
@@ -29,8 +30,8 @@ class ScreenShotsController extends BaseController {
   }
 
 
-  void toggleImageSelection(String groupTitle, int imageIndex) {
-    final group = _getGroupByTitle(groupTitle);
+  void toggleImageSelection(List<AssetEntity> images, int imageIndex) {
+    final group = getGroupByImages(images);
     final image = group.images[imageIndex];
     final selected = !group.selectedImages[imageIndex];
 
@@ -43,10 +44,10 @@ class ScreenShotsController extends BaseController {
     selectedFileCount.value = selectedPhotosIds.length;
   }
 
-  void clickImage(String groupTitle, int imageIndex) {
-    final group = _getGroupByTitle(groupTitle);
+  void clickImage(List<AssetEntity> images, int imageIndex) {
+    final group = getGroupByImages(images);
     final image = group.images[imageIndex];
-    print('clickImage $groupTitle $imageIndex');
+
 
     if (titleName == "Screenshots") {
       ImagePickerUtil.selectedScreenshotPhotosIds.assignAll(selectedPhotosIds);
@@ -59,8 +60,8 @@ class ScreenShotsController extends BaseController {
   }
 
 
-  void toggleGroupSelection(String groupTitle) {
-    final group = _getGroupByTitle(groupTitle);
+  void toggleGroupSelection(List<AssetEntity> images) {
+    final group = getGroupByImages(images);
     final newValue = !group.isSelected.value;
     group.toggleSelectAll(newValue);
 
@@ -71,17 +72,20 @@ class ScreenShotsController extends BaseController {
     updateSelectedFilesSize();
     selectedFileCount.value = selectedPhotosIds.length;
   }
-
-  // 通过标题获取照片组
-  PhotoGroup _getGroupByTitle(String groupTitle) {
-    return photoGroups.firstWhere((g) => g.title == groupTitle);
-  }
+  //
+  // // 通过标题获取照片组
+  // PhotoGroup _getGroupByTitle(String groupTitle) {
+  //   return photoGroups.firstWhere((g) => g.title == groupTitle);
+  // }
 
   // // 通过photoGroup的images来获取图片组
-  // PhotoGroup _getGroupByImages(List<AssetEntity> images) {
-  //   return photoGroups.firstWhere((g) => g.images == images);
-  // }
 
+  PhotoGroup getGroupByImages(List<AssetEntity> images) {
+    final imageIds = images.map((img) => img.id).toSet();
+    return photoGroups.firstWhere((group) =>
+        group.images.every((image) => imageIds.contains(image.id))
+    );
+  }
   void _updateSelectedScreenshotsPhotosIds(String photoId, bool isSelected) {
     if (isSelected) {
       selectedPhotosIds.add(photoId);
@@ -93,14 +97,11 @@ class ScreenShotsController extends BaseController {
   void restoreSelections() {
     if (titleName == "Screenshots") {
       selectedPhotosIds.assignAll(ImagePickerUtil.selectedScreenshotPhotosIds);
+
     } else if (titleName == "Blurry") {
       selectedPhotosIds.assignAll(ImagePickerUtil.selectedBlurryPhotosIds);
     }
 
-    // print('screenshots restoreSelections ${selectedPhotosIds.length}');
-    if (selectedPhotosIds.isEmpty) {
-      return;
-    }
     for (var group in photoGroups) {
       for (int i = 0; i < group.images.length; i++) {
         if (selectedPhotosIds.contains(group.images[i].id)) {
@@ -139,8 +140,6 @@ class ScreenShotsController extends BaseController {
       final photoGroup = ImagePickerUtil.screenshotPhotos;
       if (photoGroup.isNotEmpty) {
         photoGroups.add(PhotoGroup(
-          title: 'photo : ${photoGroup.length}',
-          imageCount: photoGroup.length,
           isSelected: false,
           images: photoGroup,
         ));
@@ -151,8 +150,6 @@ class ScreenShotsController extends BaseController {
       final photoGroup = ImagePickerUtil.blurryPhotos;
       if (photoGroup.isNotEmpty) {
         photoGroups.add(PhotoGroup(
-          title: 'photo : ${photoGroup.length}',
-          imageCount: photoGroup.length,
           isSelected: false,
           images: photoGroup,
         ));
@@ -171,4 +168,35 @@ class ScreenShotsController extends BaseController {
       ImagePickerUtil.selectedBlurryPhotosIds.assignAll(selectedPhotosIds);
     }
   }
+
+  clickDelete() async {
+    print('clickDelete');
+
+
+    if (selectedPhotosIds.isNotEmpty) {
+
+      final assetsToDelete = photoGroups.expand((group) =>
+          group.images.where((asset) => selectedPhotosIds.contains(asset.id))
+      ).toList();
+
+      final List<String> result = await PhotoManager.editor.deleteWithIds(
+        assetsToDelete.map((e) => e.id).toList(),
+      );
+      print('PhotoPreviewController result $result');
+
+      if (result.length == selectedPhotosIds.length) {
+
+
+        if (titleName == "Screenshots") {
+          ImagePickerUtil.updatePhotoGroupDate(PhotosType.screenshots, selectedPhotosIds);
+        } else if (titleName == "Blurry") {
+          ImagePickerUtil.updatePhotoGroupDate(PhotosType.blurryPhotos, selectedPhotosIds);
+        }
+        ToastUtil.show("Delete success");
+      } else {
+        // 删除失败
+        ToastUtil.show("Delete failed");
+      }
+    }
+  }
 }

+ 120 - 105
lib/module/screenshots_blurry/screenshots_view.dart

@@ -31,37 +31,45 @@ class ScreenshotsPage extends BasePage<ScreenShotsController> {
     return Stack(children: [
       Container(
         child: SafeArea(
-          child: (controller.photoGroups.isEmpty ||
-                  controller.photoGroups[0].images.isEmpty)
-              ? _noNoPicturesCard()
-              : Column(
-                  children: [
-                    _titleCard(),
-                    Flexible(
-                      child: Obx(() {
-                        return SizedBox(
-                          child: GridView.builder(
-                              padding: EdgeInsets.symmetric(horizontal: 16.w),
-                              scrollDirection: Axis.vertical,
-                              // 设置为垂直方向滚动
-                              physics: BouncingScrollPhysics(),
-                              gridDelegate:
-                                  SliverGridDelegateWithFixedCrossAxisCount(
-                                crossAxisCount: 3, // 每行显示 2 个元素
-                                mainAxisSpacing: 8.w, // 垂直间距
-                                crossAxisSpacing: 8.h, // 水平间距
-                              ),
-                              itemCount:
-                                  controller.photoGroups[0].images.length,
-                              itemBuilder: _buildPhotoItem(
-                                  controller.photoGroups[0].title)),
-                        );
-                      }),
-                    ),
-                    _bottomBarCard(),
-                    SizedBox(height: 8.h),
-                  ],
+          child: Obx(() {
+            if (controller.photoGroups.isEmpty ||
+                controller.photoGroups[0].images.isEmpty) {
+              return _noNoPicturesCard();
+            }
+            return Column(
+              children: [
+                _titleCard(),
+                Flexible(
+                  child: Obx(() {
+                    return SizedBox(
+                      child: GridView.builder(
+                          padding: EdgeInsets.symmetric(horizontal: 16.w),
+                          scrollDirection: Axis.vertical,
+                          // 设置为垂直方向滚动
+                          physics: BouncingScrollPhysics(),
+                          gridDelegate:
+                              SliverGridDelegateWithFixedCrossAxisCount(
+                            crossAxisCount: 3, // 每行显示 2 个元素
+                            mainAxisSpacing: 8.w, // 垂直间距
+                            crossAxisSpacing: 8.h, // 水平间距
+                          ),
+                          itemCount: controller.photoGroups[0].images.length,
+                          itemBuilder: _buildPhotoItem(
+                              controller.photoGroups[0].images)),
+                    );
+                  }),
                 ),
+                Obx(() {
+                  if (controller.selectedPhotosIds.isNotEmpty) {
+                    return _bottomBarCard();
+                  } else {
+                    return SizedBox();
+                  }
+                }),
+                SizedBox(height: 8.h),
+              ],
+            );
+          }),
         ),
       ),
       IgnorePointer(
@@ -97,12 +105,11 @@ class ScreenshotsPage extends BasePage<ScreenShotsController> {
                     ? Container()
                     : GestureDetector(
                         onTap: () => controller.toggleGroupSelection(
-                            controller.photoGroups[0].title),
+                            controller.photoGroups[0].images),
                         child: Obx(() => Text(
-                              controller.photoGroups
-                                      .firstWhere((g) =>
-                                          g.title ==
-                                          controller.photoGroups[0].title)
+                              controller
+                                      .getGroupByImages(
+                                          controller.photoGroups[0].images)
                                       .isSelected
                                       .value
                                   ? 'Deselect All'
@@ -117,7 +124,8 @@ class ScreenshotsPage extends BasePage<ScreenShotsController> {
               ],
             );
           }),
-          controller.photoGroups.isEmpty
+          controller.photoGroups.isEmpty ||
+                  controller.photoGroups[0].images.isEmpty
               ? Container()
               : Column(
                   children: [
@@ -139,47 +147,52 @@ class ScreenshotsPage extends BasePage<ScreenShotsController> {
   }
 
   Widget _bottomBarCard() {
-    return Container(
-      width: 328.w,
-      height: 48.h,
-      decoration: ShapeDecoration(
-        color: Color(0xFF0279FB),
-        shape: RoundedRectangleBorder(
-          borderRadius: BorderRadius.circular(10.r),
-        ),
-      ),
-      padding: EdgeInsets.symmetric(horizontal: 16.w),
-      child: Row(
-        mainAxisAlignment: MainAxisAlignment.center,
-        children: [
-          Assets.images.iconDelete.image(
-            width: 18.w,
-            height: 18.h,
+    return GestureDetector(
+        onTap: () {
+          controller.clickDelete();
+        },
+        child: Container(
+          width: 328.w,
+          height: 48.h,
+          decoration: ShapeDecoration(
+            color: Color(0xFF0279FB),
+            shape: RoundedRectangleBorder(
+              borderRadius: BorderRadius.circular(10.r),
+            ),
           ),
-          SizedBox(width: 5.w),
-          Obx(() {
-            return Text(
-              '${controller.selectedFileCount.value} files selected (${controller.selectedFilesSize.value.toStringAsFixed(1)} KB)',
-              textAlign: TextAlign.center,
-              style: TextStyle(
-                color: Colors.white,
-                fontSize: 16.sp,
-                fontWeight: FontWeight.w500,
+          padding: EdgeInsets.symmetric(horizontal: 16.w),
+          child: Row(
+            mainAxisAlignment: MainAxisAlignment.center,
+            children: [
+              Assets.images.iconDelete.image(
+                width: 18.w,
+                height: 18.h,
               ),
-            );
-          }),
-        ],
-      ),
-    );
+              SizedBox(width: 5.w),
+              Obx(() {
+                return Text(
+                  '${controller.selectedFileCount.value} files selected (${controller.selectedFilesSize.value.toStringAsFixed(1)} KB)',
+                  textAlign: TextAlign.center,
+                  style: TextStyle(
+                    color: Colors.white,
+                    fontSize: 16.sp,
+                    fontWeight: FontWeight.w500,
+                  ),
+                );
+              }),
+            ],
+          ),
+        ));
   }
 
-  Widget Function(BuildContext, int) _buildPhotoItem(String title) =>
+  Widget Function(BuildContext, int) _buildPhotoItem(
+          List<AssetEntity> images) =>
       (context, index) {
         final group =
-            controller.photoGroups.firstWhere((group) => group.title == title);
+            controller.getGroupByImages(controller.photoGroups[0].images);
 
         return GestureDetector(
-          onTap: () => controller.clickImage(title, index),
+          onTap: () => controller.clickImage(images, index),
           child: Obx(() {
             var isSelected = group.selectedImages[index];
             return Stack(
@@ -193,8 +206,11 @@ class ScreenshotsPage extends BasePage<ScreenShotsController> {
                       borderRadius: BorderRadius.circular(9.27.sp),
                     ),
                     image: DecorationImage(
-                      image: AssetEntityImageProvider(group.images[index],thumbnailSize: const ThumbnailSize.square(300),
-                        isOriginal: false,),
+                      image: AssetEntityImageProvider(
+                        group.images[index],
+                        thumbnailSize: const ThumbnailSize.square(300),
+                        isOriginal: false,
+                      ),
                       fit: BoxFit.cover,
                     ),
                   ),
@@ -204,7 +220,7 @@ class ScreenshotsPage extends BasePage<ScreenShotsController> {
                   bottom: 8.h,
                   child: GestureDetector(
                       onTap: () =>
-                          controller.toggleImageSelection(title, index),
+                          controller.toggleImageSelection(images, index),
                       child: Container(
                         child: isSelected
                             ? Center(
@@ -231,41 +247,40 @@ class ScreenshotsPage extends BasePage<ScreenShotsController> {
       mainAxisAlignment: MainAxisAlignment.start,
       children: [
         _titleCard(),
-        Expanded(child: Center(
-          child: Column(
-            mainAxisAlignment: MainAxisAlignment.center,
-            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: 170.h),
+        Column(
+          mainAxisAlignment: MainAxisAlignment.center,
+          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,
-                ),
+            ),
+            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,
               ),
-            ],
-          ),
-        )),
+            ),
+          ],
+        ),
       ],
     );
   }

+ 51 - 22
lib/module/similar_photo/similar_photo_controller.dart

@@ -5,12 +5,12 @@ import 'package:clean/data/bean/photos_type.dart';
 import 'package:clean/module/image_picker/image_picker_util.dart';
 import 'package:clean/module/people_photo/photo_group.dart';
 import 'package:clean/module/photo_preview/photo_preview_view.dart';
+import 'package:clean/utils/toast_util.dart';
 import 'package:get/get.dart';
 
 import 'package:wechat_assets_picker/wechat_assets_picker.dart';
 
 class SimilarPhotoController extends BaseController {
-
   final RxList<PhotoGroup> photoGroups = <PhotoGroup>[].obs;
   final RxDouble selectedFilesSize = 0.0.obs;
   RxInt selectedFileCount = 0.obs;
@@ -30,7 +30,6 @@ class SimilarPhotoController extends BaseController {
     selectedFilesSize.value = totalSize / 1024; // Convert to KB
   }
 
-
   void toggleImageSelection(List<AssetEntity> groupTitle, int imageIndex) {
     final group = _getGroupByImages(groupTitle);
     final image = group.images[imageIndex];
@@ -45,11 +44,10 @@ class SimilarPhotoController extends BaseController {
     getSelectedFilesSize();
   }
 
-  void clickImage(List<AssetEntity> images , int imageIndex) {
+  void clickImage(List<AssetEntity> images, int imageIndex) {
     final group = _getGroupByImages(images);
     final image = group.images[imageIndex];
-    PhotoPreviewPage.start(PhotosType.similarPhotos,image.id);
-
+    PhotoPreviewPage.start(PhotosType.similarPhotos, image.id);
   }
 
   void toggleGroupSelection(List<AssetEntity> imagesList) {
@@ -64,13 +62,10 @@ class SimilarPhotoController extends BaseController {
     getSelectedFilesSize();
   }
 
-
   PhotoGroup _getGroupByImages(List<AssetEntity> images) {
     return photoGroups.firstWhere((g) => g.images == images);
   }
 
-
-
   void _updateSelectedSimilarPhotosIds(String photoId, bool isSelected) {
     if (isSelected) {
       ImagePickerUtil.selectedSimilarPhotosIds.add(photoId);
@@ -85,7 +80,8 @@ class SimilarPhotoController extends BaseController {
       for (int i = 0; i < group.images.length; i++) {
         group.selectedImages[i] = selectedIds.contains(group.images[i].id);
       }
-      group.isSelected.value = group.selectedImages.every((selected) => selected);
+      group.isSelected.value =
+          group.selectedImages.every((selected) => selected);
     }
     await getSelectedFilesSize();
     selectedFileCount.value = selectedIds.length;
@@ -95,30 +91,63 @@ class SimilarPhotoController extends BaseController {
   void onInit() {
     super.onInit();
     loadSimilarPhotos();
-
     restoreSelections();
-
-
   }
 
   void loadSimilarPhotos() {
     // 清空现有数据
     photoGroups.clear();
 
-    // 加载相似图片
-    for (var group in ImagePickerUtil.similarPhotos) {
-      print('group.length ${group.length}, group[0].id ${group[0].id}');
-      if (group.isNotEmpty) {
-        photoGroups.add(PhotoGroup(
-          title: 'photo : ${group.length}',
-          imageCount: group.length,
-          isSelected: false,
-          images: group,
-        ));
+    photoGroups.addAll(
+      ImagePickerUtil.similarPhotos.where((group) => group.isNotEmpty).map(
+        (group) {
+          return PhotoGroup(
+            isSelected: true,
+            images: group,
+          );
+        },
+      ),
+    );
+    // 每组第一张图片不选中,其余选中
+    for (var group in photoGroups) {
+      for (int i = 0; i < group.images.length; i++) {
+        group.selectedImages[i] = i == 0 ? false : true;
+
+        _updateSelectedSimilarPhotosIds(group.images[i].id, i != 0);
       }
     }
 
+    selectedFileCount.value = ImagePickerUtil.selectedSimilarPhotosIds.length;
+    getSelectedFilesSize();
+  }
+
+  clickDelete() async {
+    print('clickDelete');
 
+    if (ImagePickerUtil.selectedSimilarPhotosIds.isNotEmpty) {
+      final assetsToDelete = photoGroups
+          .expand((group) => group.images.where((asset) =>
+              ImagePickerUtil.selectedSimilarPhotosIds.contains(asset.id)))
+          .toList();
+
+      final List<String> result = await PhotoManager.editor.deleteWithIds(
+        assetsToDelete.map((e) => e.id).toList(),
+      );
+      print('PhotoPreviewController result $result');
+
+      if (result.length == ImagePickerUtil.selectedSimilarPhotosIds.length) {
+        for (var group in photoGroups) {
+          group.images.removeWhere((element) =>
+              ImagePickerUtil.selectedSimilarPhotosIds.contains(element.id));
+        }
+        ImagePickerUtil.updatePhotoGroupDate(
+            PhotosType.similarPhotos, ImagePickerUtil.selectedSimilarPhotosIds);
+        ToastUtil.show("Delete success");
+      } else {
+        // 删除失败
+        ToastUtil.show("Delete failed");
+      }
+    }
   }
 
   // 修改视图构建方法,使用 AssetEntity 而不是文件路径

+ 141 - 74
lib/module/similar_photo/similar_photo_view.dart

@@ -26,11 +26,14 @@ class SimilarPhotoPage extends BasePage<SimilarPhotoController> {
   Widget buildBody(BuildContext context) {
     return Stack(children: [
       Container(
-        child: SafeArea(
-          child: Column(
+        child: SafeArea(child: Obx(() {
+          if (controller.photoGroups.isEmpty) {
+            return _noNoPicturesCard();
+          }
+          return Column(
             children: [
               _titleCard(),
-              Expanded(
+              Flexible(
                 child: Obx(() {
                   return ListView(
                     padding: EdgeInsets.symmetric(horizontal: 16.w),
@@ -39,8 +42,7 @@ class SimilarPhotoPage extends BasePage<SimilarPhotoController> {
                             children: [
                               _buildPhotoGroup(
                                 imagesList: group.images,
-                                title: group.title,
-                                imageCount: group.imageCount,
+                                imageCount: group.images.length,
                               ),
                               SizedBox(height: 15.h),
                             ],
@@ -51,8 +53,8 @@ class SimilarPhotoPage extends BasePage<SimilarPhotoController> {
               ),
               _bottomBarCard(),
             ],
-          ),
-        ),
+          );
+        })),
       ),
       IgnorePointer(
         child: Assets.images.bgHome.image(
@@ -77,16 +79,24 @@ class SimilarPhotoPage extends BasePage<SimilarPhotoController> {
               height: 28.h,
             ),
           ),
-          SizedBox(height: 12.h),
-          Text(
-            'Similar Photos',
-            style: TextStyle(
-              color: Colors.white,
-              fontSize: 24.sp,
-              fontWeight: FontWeight.w700,
-            ),
-          ),
-          SizedBox(height: 20.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),
+                    ],
+                  ),
+                )
         ],
       ),
     );
@@ -121,34 +131,37 @@ class SimilarPhotoPage extends BasePage<SimilarPhotoController> {
               ),
             );
           }),
-          Container(
-            width: 108.w,
-            height: 38.h,
-            decoration: ShapeDecoration(
-              color: Color(0xFF0279FB),
-              shape: RoundedRectangleBorder(
-                borderRadius: BorderRadius.circular(10.r),
+          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,
+              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,
-                ),
-              ],
+                  Assets.images.iconDelete.image(
+                    width: 18.w,
+                    height: 18.h,
+                  ),
+                ],
+              ),
             ),
-          ),
+          )
         ],
       ),
     );
@@ -156,7 +169,6 @@ class SimilarPhotoPage extends BasePage<SimilarPhotoController> {
 
   Widget _buildPhotoGroup({
     required List<AssetEntity> imagesList,
-    required String title,
     required int imageCount,
   }) {
     return Container(
@@ -179,7 +191,7 @@ class SimilarPhotoPage extends BasePage<SimilarPhotoController> {
               Row(
                 children: [
                   Text(
-                    title,
+                    "photos $imageCount",
                     textAlign: TextAlign.center,
                     style: TextStyle(
                       color: Colors.white,
@@ -227,14 +239,17 @@ class SimilarPhotoPage extends BasePage<SimilarPhotoController> {
                           children: [
                             Container(
                               decoration: ShapeDecoration(
-                                color: Colors.white.withOpacity(0.12),
+                                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,),
+                                  image: AssetEntityImageProvider(
+                                    group.images[0],
+                                    thumbnailSize:
+                                        const ThumbnailSize.square(300),
+                                    isOriginal: false,
+                                  ),
                                   fit: BoxFit.cover,
                                 ),
                               ),
@@ -281,8 +296,8 @@ class SimilarPhotoPage extends BasePage<SimilarPhotoController> {
                               right: 4.w,
                               bottom: 4.h,
                               child: GestureDetector(
-                                onTap: () =>
-                                    controller.toggleImageSelection(imagesList, 0),
+                                onTap: () => controller.toggleImageSelection(
+                                    imagesList, 0),
                                 child: Container(
                                   child: group.selectedImages[0]
                                       ? Center(
@@ -323,8 +338,8 @@ class SimilarPhotoPage extends BasePage<SimilarPhotoController> {
                           (index) {
                             final realIndex = gridIndex * 4 + index + 1;
                             return GestureDetector(
-                              onTap: () => controller.clickImage(
-                                  imagesList, realIndex),
+                              onTap: () =>
+                                  controller.clickImage(imagesList, realIndex),
                               child: Obx(() {
                                 final group = controller.photoGroups
                                     .firstWhere((g) => g.images == imagesList);
@@ -336,8 +351,11 @@ class SimilarPhotoPage extends BasePage<SimilarPhotoController> {
                                     ),
                                     image: DecorationImage(
                                       image: AssetEntityImageProvider(
-                                          group.images[realIndex],thumbnailSize: const ThumbnailSize.square(300),
-                                        isOriginal: false,),
+                                        group.images[realIndex],
+                                        thumbnailSize:
+                                            const ThumbnailSize.square(300),
+                                        isOriginal: false,
+                                      ),
                                       fit: BoxFit.cover,
                                     ),
                                   ),
@@ -350,8 +368,9 @@ class SimilarPhotoPage extends BasePage<SimilarPhotoController> {
                                           final isSelected =
                                               group.selectedImages[realIndex];
                                           return GestureDetector(
-                                            onTap: () => controller.toggleImageSelection(
-                                                imagesList, realIndex),
+                                            onTap: () =>
+                                                controller.toggleImageSelection(
+                                                    imagesList, realIndex),
                                             child: Container(
                                               child: isSelected
                                                   ? Center(
@@ -387,28 +406,76 @@ class SimilarPhotoPage extends BasePage<SimilarPhotoController> {
               ],
             ),
           ),
-          Container(
-            width: 162.w,
-            height: 38.h,
-            decoration: ShapeDecoration(
-              color: Color(0xFF0279FB),
-              shape: RoundedRectangleBorder(
-                borderRadius: BorderRadius.circular(10.r),
+          GestureDetector(
+            onTap: () => controller.clickDelete(),
+            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 == imagesList).selectedCount} to trash',
+                      style: TextStyle(
+                        color: Colors.white,
+                        fontSize: 16.sp,
+                        fontWeight: FontWeight.w500,
+                      ),
+                    )),
               ),
             ),
-            child: Center(
-              child: Obx(() => Text(
-                    'Move ${controller.photoGroups.firstWhere((g) => g.images == imagesList).selectedCount} 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,
+        ),
+      ],
+    );
+  }
 }

+ 6 - 0
lib/router/app_pages.dart

@@ -7,6 +7,8 @@ import 'package:clean/module/main/main_view.dart';
 import 'package:clean/module/photo_preview/photo_preview_controller.dart';
 import 'package:clean/module/photo_preview/photo_preview_view.dart';
 import 'package:clean/module/photo_info/photo_info_controller.dart';
+import 'package:clean/module/photo_preview/photo_selected_preview_controller.dart';
+import 'package:clean/module/photo_preview/phtoto_selected_preview_view.dart';
 import 'package:clean/module/privacy/privacy_view.dart';
 import 'package:clean/module/people_photo/people_photo_controller.dart';
 import 'package:clean/module/people_photo/people_photo_view.dart';
@@ -14,6 +16,7 @@ import 'package:clean/module/screenshots_blurry/screenshots_controller.dart';
 import 'package:clean/module/screenshots_blurry/screenshots_view.dart';
 import 'package:clean/module/similar_photo/similar_photo_controller.dart';
 import 'package:clean/module/similar_photo/similar_photo_view.dart';
+import 'package:flutter/cupertino.dart';
 import 'package:get/get.dart';
 import 'package:get/get_core/src/get_main.dart';
 import 'package:get/get_instance/src/bindings_interface.dart';
@@ -38,6 +41,7 @@ abstract class RoutePath {
   static const photoInfo = '/photoInfo';
   static const photoPreview = '/photoPreview';
   static const locationsSinglePhoto = '/locationsSinglePhoto';
+  static const photoSelectedPreview = '/photoSelectedPreview';
 }
 
 class AppBinding extends Bindings {
@@ -54,6 +58,7 @@ class AppBinding extends Bindings {
 
     lazyPut(() => PhotoInfoController());
     lazyPut(() => LocationsSinglePhotoController());
+    lazyPut(() => PhotoSelectedPreviewController());
   }
 
   void lazyPut<S>(InstanceBuilderCallback<S> builder) {
@@ -72,4 +77,5 @@ final generalPages = [
   GetPage(name: RoutePath.photoPreview, page: () => PhotoPreviewPage()),
   GetPage(name: RoutePath.photoInfo, page: () => PhotoInfoPage()),
   GetPage(name: RoutePath.locationsSinglePhoto, page: () => LocationsSinglePhotoPage()),
+  GetPage(name: RoutePath.photoSelectedPreview, page: () => PhotoSelectedPreviewPage()),
 ];