Explorar o código

添加图片二级查看页

云天逵 hai 1 ano
pai
achega
536e24a4b5

BIN=BIN
assets/images/icon_no_pictures.webp


BIN=BIN
assets/images/icon_preview_no_select.webp


BIN=BIN
assets/images/icon_preview_recover.webp


BIN=BIN
assets/images/icon_preview_select.webp


+ 7 - 0
lib/data/bean/photos_type.dart

@@ -0,0 +1,7 @@
+enum PhotosType {
+  peoplePhotos,
+  screenshots,
+  similarPhotos,
+  locationPhotos,
+  blurryPhotos
+}

+ 30 - 1
lib/module/home/home_controller.dart

@@ -5,6 +5,7 @@ import 'package:clean/base/base_controller.dart';
 import 'package:clean/module/image_picker/image_picker_util.dart';
 import 'package:clean/module/locations_photo/locations_photo_view.dart';
 import 'package:clean/module/people_photo/people_photo_view.dart';
+import 'package:clean/module/screenshots_blurry/screenshots_view.dart';
 import 'package:clean/module/similar_photo/similar_photo_view.dart';
 import 'package:clean/utils/toast_util.dart';
 import 'package:disk_space/disk_space.dart';
@@ -14,6 +15,7 @@ import 'package:permission_handler/permission_handler.dart';
 import 'dart:typed_data';
 
 import 'package:wechat_assets_picker/wechat_assets_picker.dart';
+
 class HomeController extends BaseController {
 
   Rx<double> totalSpace = 500.0.obs;
@@ -50,16 +52,41 @@ class HomeController extends BaseController {
   Future<void> onInit() async {
     // TODO: implement onInit
     super.onInit();
-
+    // loadPhotosFromDirectory();
     if (await Permission.photos.request().isGranted) {
       PhotoManager.clearFileCache();
       getStorageInfo();
+
       handlePhotos();
     } else {
       ToastUtil.show("请先开启相册权限");
     }
   }
+  Future<void> loadPhotosFromDirectory() async {
+
+
+    if(ImagePickerUtil.peoplePhotos.isEmpty||ImagePickerUtil.similarPhotos.isEmpty||ImagePickerUtil.locationPhotos.isEmpty||ImagePickerUtil.screenshotPhotos.isEmpty){
+      try {
+
+        final List<AssetEntity>? result = await AssetPicker.pickAssets(
+          Get.context!,
+        );
+        ImagePickerUtil.peoplePhotos.value = result ?? [];
+
+        ImagePickerUtil.locationPhotos['location'] = result ?? [];
 
+        ImagePickerUtil.screenshotPhotos.value = result ?? [];
+
+        ImagePickerUtil.similarPhotos.add(result ?? []);
+
+        ImagePickerUtil.blurryPhotos.value = result ?? [];
+
+      } catch (e) {
+        print('Error loading photos: $e');
+      }
+    }
+
+  }
   Future<void> getStorageInfo() async {
 
     final classifyPhoto = ClassifyPhoto();
@@ -167,10 +194,12 @@ class HomeController extends BaseController {
   }
 
   screenshotCleanClick() {
+    ScreenshotsPage.start("Screenshots");
     print('screenshotCleanClick');
   }
 
   blurryCleanClick() {
+    ScreenshotsPage.start("Blurry");
     print('blurCleanClick');
   }
 }

+ 16 - 0
lib/module/image_picker/image_picker_util.dart

@@ -2,6 +2,7 @@ import 'dart:io';
 import 'dart:math';
 
 import 'package:clean/model/asset_group.dart';
+import 'package:clean/module/people_photo/photo_group.dart';
 import 'package:get/get.dart';
 import 'package:photo_manager/photo_manager.dart';
 
@@ -27,9 +28,22 @@ class ImagePickerUtil {
   static final RxMap<String, List<AssetEntity>> locationPhotos =
       <String, List<AssetEntity>>{}.obs;
 
+
   // 人物图片
   static final RxList<AssetEntity> peoplePhotos = <AssetEntity>[].obs;
 
+
+
+
+
+  static final  RxSet<String>  selectedScreenshotPhotosIds = <String>{}.obs;
+  static final  RxSet<String>  selectedSimilarPhotosIds = <String>{}.obs;
+  static final  RxSet<String>  selectedLocationPhotosIds = <String>{}.obs;
+  static final  RxSet<String>  selectedPeoplePhotosIds = <String>{}.obs;
+  static final  RxSet<String>  selectedBlurryPhotosIds = <String>{}.obs;
+
+
+
   // 添加大小信息的变量
   static final Rx<int> screenshotsSize = 0.obs;
   static final Rx<int> blurrySize = 0.obs;
@@ -183,4 +197,6 @@ class ImagePickerUtil {
       return '${(sizeInGB / 1024).toStringAsFixed(1)} ';
     }
   }
+
+
 }

+ 0 - 21
lib/module/locations_photo/locations_photo_controller.dart

@@ -12,31 +12,10 @@ class LocationsPhotoController extends GetxController {
   onInit() {
     super.onInit();
     loadLocationPhoto();
-    // loadPhotosFromDirectory();
-  }
-
-  Future<void> loadPhotosFromDirectory() async {
-    try {
-      final List<AssetEntity>? result = await AssetPicker.pickAssets(
-        Get.context!,
-      );
-      if (result != null && result.isNotEmpty) {
-        photoGroups.add(PhotoGroup(
-          title: 'photo: ${result.length}',
-          imageCount: result.length,
-          isSelected: false,
-          images: result,
-          location: "测试".obs,
-        ));
-      }
-    } catch (e) {
-      print('Error loading photos: $e');
-    }
   }
 
   void loadLocationPhoto() {
     photoGroups.clear();
-
     RxMap<String, List<AssetEntity>> locationPhotos = ImagePickerUtil.locationPhotos;
 
     if (locationPhotos.isNotEmpty) {

+ 2 - 3
lib/module/locations_photo/locations_photo_view.dart

@@ -36,8 +36,8 @@ class LocationsPhotoPage extends BasePage<LocationsPhotoController> {
                       ...controller.photoGroups.map((group) => Column(
                             children: [
                               _buildPhotoGroup(
-                                title: group.title,
-                                imageCount: group.imageCount,
+                                title: "photo: ${group.images.length}",
+                                imageCount: group.images.length,
                               ),
                               SizedBox(height: 15.h),
                             ],
@@ -128,7 +128,6 @@ class LocationsPhotoPage extends BasePage<LocationsPhotoController> {
                     controller.photoGroups.firstWhere((g) => g.title == title);
                 final imagePath = group.images[0];
                 final location = group.location?.value?? '';
-
                 return Stack(
                   children: [
                     Container(

+ 92 - 74
lib/module/people_photo/people_photo_controller.dart

@@ -1,112 +1,130 @@
-import 'dart:io';
-
+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_view.dart';
 import 'package:get/get.dart';
-import 'package:path_provider/path_provider.dart';
-import 'package:path/path.dart' as p;
 import 'package:wechat_assets_picker/wechat_assets_picker.dart';
 
-class PeoplePhotoController extends GetxController {
+class PeoplePhotoController extends BaseController {
   final RxList<PhotoGroup> photoGroups = <PhotoGroup>[].obs;
+  final RxDouble selectedFilesSize = 0.0.obs;
+  final  RxInt selectedFileCount = 0.obs;
 
-  static final Map<String, List<bool>> _savedSelections = {};
-  static bool _hasInitializedSelections = false;
+  @override
+  void onInit() {
+    super.onInit();
+    loadPeoplePhoto();
+    restoreSelections();
+  }
 
-  int get selectedFileCount =>
-      photoGroups.fold(0, (sum, group) => sum + group.selectedCount);
+  @override
+  void onReady() {
+    super.onReady();
+  }
 
-  Future<double> getSelectedFilesSize() async {
+  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();
-          }
+    // 通过selectedPhotosIds获取选中的图片,然后计算大小
+    for (var id in ImagePickerUtil.selectedPeoplePhotosIds) {
+      final entity = await AssetEntity.fromId(id);
+      if (entity != null) {
+        final file = await entity.file;
+        if (file != null) {
+          totalSize += await file.length();
         }
       }
     }
-    return totalSize / 1024; // Convert to KB
+
+    selectedFilesSize.value = totalSize / 1024; // Convert to KB
   }
 
-  void toggleImageSelection(String groupTitle, int imageIndex) {
-    final group = photoGroups.firstWhere((g) => g.title == groupTitle);
-    group.selectedImages[imageIndex] = !group.selectedImages[imageIndex];
+  void toggleImageSelection(List<AssetEntity> groupTitle, int imageIndex) async {
+    final group = getGroupByImages(groupTitle);
+    final image = group.images[imageIndex];
+    final selected = !group.selectedImages[imageIndex];
+
+    group.selectedImages[imageIndex] = selected;
+    _updateSelectedPeoplePhotosIds(image.id, selected);
+
     group.isSelected.value = group.selectedImages.every((selected) => selected);
-    _saveSelections(); // 保存选择状态
+
+    selectedFileCount.value = ImagePickerUtil.selectedPeoplePhotosIds.length;
+    await updateSelectedFilesSize();
   }
 
-  void toggleGroupSelection(String groupTitle) {
-    final group = photoGroups.firstWhere((g) => g.title == groupTitle);
+  void clickImage(List<AssetEntity> images, int imageIndex) {
+    final group = getGroupByImages(images);
+    final image = group.images[imageIndex];
+
+    PhotoPreviewPage.start(PhotosType.peoplePhotos, image.id);
+  }
+
+  void toggleGroupSelection(List<AssetEntity> images) async {
+    final group = getGroupByImages(images);
     final newValue = !group.isSelected.value;
     group.toggleSelectAll(newValue);
-    _saveSelections(); // 保存选择状态
-  }
 
-  @override
-  void onInit() {
-    super.onInit();
-    loadPeoplePhoto();
-      if (_hasInitializedSelections) {
-        _restoreSelections();
-      }
-    // loadPhotosFromDirectory().then((_) {
-    //   // 恢复保存的选择状态
-    //   if (_hasInitializedSelections) {
-    //     _restoreSelections();
-    //   }
-    // });
+    for (var image in group.images) {
+      _updateSelectedPeoplePhotosIds(image.id, newValue);
+    }
+    selectedFileCount.value = ImagePickerUtil.selectedPeoplePhotosIds.length;
+     updateSelectedFilesSize();
   }
 
-  void _saveSelections() {
-    for (var group in photoGroups) {
-      _savedSelections[group.title] = group.selectedImages.toList();
-    }
-    _hasInitializedSelections = true;
+  PhotoGroup _getGroupByTitle(String groupTitle) {
+    return photoGroups.firstWhere((g) => g.title == groupTitle);
   }
 
+  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 _updateSelectedPeoplePhotosIds(String photoId, bool isSelected) {
+    if (isSelected) {
+      // 检查这个id有没有在这个list里面
+      if (!ImagePickerUtil.selectedPeoplePhotosIds.contains(photoId)) {
+        ImagePickerUtil.selectedPeoplePhotosIds.add(photoId);
+      }
+    } else {
+      ImagePickerUtil.selectedPeoplePhotosIds.remove(photoId);
+    }
+  }
 
+  void restoreSelections() async {
+    print("PeoplePhotoController  restoreSelections");
 
-  void _restoreSelections() {
     for (var group in photoGroups) {
-      // 这里假设每次页面加载时,已选择的状态会保存在 `RxList<bool>` 中。
-      group.isSelected.value = group.selectedImages.every((selected) => selected);
-    }
-  }
-  Future<void> loadPhotosFromDirectory() async {
-    try {
-      final List<AssetEntity>? result = await AssetPicker.pickAssets(
-        Get.context!,
-      );
-      if (result != null && result.isNotEmpty) {
-        photoGroups.add(PhotoGroup(
-          title: 'photo: ${result.length}',
-          imageCount: result.length,
-          isSelected: false,
-          images: result,
-        ));
+      for (int i = 0; i < group.images.length; i++) {
+        if (ImagePickerUtil.selectedPeoplePhotosIds
+            .contains(group.images[i].id)) {
+          group.selectedImages[i] = true;
+        } else {
+          group.selectedImages[i] = false;
+        }
       }
-    } catch (e) {
-      print('Error loading photos: $e');
+      group.isSelected.value =
+          group.selectedImages.every((selected) => selected);
     }
+     updateSelectedFilesSize();
+    selectedFileCount.value = ImagePickerUtil.selectedPeoplePhotosIds.length;
   }
 
   void loadPeoplePhoto() {
-
+    print("loadPeoplePhoto");
     photoGroups.clear();
 
-      final photoGroup = ImagePickerUtil.peoplePhotos;
-      if (photoGroup.isNotEmpty) {
-        photoGroups.add(PhotoGroup(
-          title: 'photo : ${photoGroup.length}',
-          imageCount: photoGroup.length,
-          isSelected: false,
-          images: photoGroup,
-        ));
-      }
-
+    final photoGroup = ImagePickerUtil.peoplePhotos;
+    if (photoGroup.isNotEmpty) {
+      photoGroups.add(PhotoGroup(
+        title: 'photo : ${photoGroup.length}',
+        imageCount: photoGroup.length,
+        isSelected: false,
+        images: photoGroup,
+      ));
+    }
   }
 }

+ 93 - 89
lib/module/people_photo/people_photo_view.dart

@@ -1,6 +1,7 @@
 import 'dart:io';
 
 import 'package:clean/base/base_page.dart';
+import 'package:clean/module/image_picker/image_picker_util.dart';
 import 'package:clean/module/people_photo/people_photo_controller.dart';
 import 'package:clean/resource/assets.gen.dart';
 import 'package:clean/router/app_pages.dart';
@@ -14,7 +15,6 @@ class PeoplePhotoPage extends BasePage<PeoplePhotoController> {
   const PeoplePhotoPage({super.key});
 
   static start() {
-    Get.put((PeoplePhotoController()));
     Get.toNamed(RoutePath.peoplePhoto, arguments: {});
   }
 
@@ -45,14 +45,15 @@ class PeoplePhotoPage extends BasePage<PeoplePhotoController> {
                     padding: EdgeInsets.symmetric(horizontal: 16.w),
                     children: [
                       ...controller.photoGroups.map((group) => Column(
-                        children: [
-                          _buildPhotoGroup(
-                            title: group.title,
-                            imageCount: group.imageCount,
-                          ),
-                          SizedBox(height: 15.h),
-                        ],
-                      ))
+                            children: [
+                              _buildPhotoGroup(
+                                images: group.images,
+                                title: group.title,
+                                imageCount: group.imageCount,
+                              ),
+                              SizedBox(height: 15.h),
+                            ],
+                          ))
                     ],
                   );
                 }),
@@ -119,27 +120,17 @@ class PeoplePhotoPage extends BasePage<PeoplePhotoController> {
       child: Row(
         mainAxisAlignment: MainAxisAlignment.spaceBetween,
         children: [
-          Obx(() => FutureBuilder<double>(
-            future: controller.getSelectedFilesSize(),
-            builder: (context, snapshot) {
-              if (snapshot.connectionState == ConnectionState.waiting) {
-                return CircularProgressIndicator();
-              } else if (snapshot.hasError) {
-                return Text('Error: ${snapshot.error}');
-              } else {
-                return Text(
-                  '${controller.selectedFileCount} files selected (${snapshot.data?.toStringAsFixed(1)} KB)',
-                  textAlign: TextAlign.center,
-                  style: TextStyle(
-                    color: Colors.white.withValues(alpha: 0.9),
-                    fontSize: 13.sp,
-                    fontWeight: FontWeight.w500,
-                  ),
-                );
-              }
-            },
-          )
-          ),
+          Obx(() {
+            return Text(
+              '${controller.selectedFileCount.value} files selected (${controller.selectedFilesSize.value.toStringAsFixed(1)} KB)',
+              textAlign: TextAlign.center,
+              style: TextStyle(
+                color: Colors.white.withOpacity(0.9),
+                fontSize: 13.sp,
+                fontWeight: FontWeight.w500,
+              ),
+            );
+          }),
           Container(
             width: 108.w,
             height: 38.h,
@@ -174,6 +165,7 @@ class PeoplePhotoPage extends BasePage<PeoplePhotoController> {
   }
 
   Widget _buildPhotoGroup({
+    required List<AssetEntity> images,
     required String title,
     required int imageCount,
   }) {
@@ -181,7 +173,7 @@ class PeoplePhotoPage extends BasePage<PeoplePhotoController> {
       padding: EdgeInsets.symmetric(horizontal: 12.w),
       margin: EdgeInsets.only(top: 14.h),
       width: 328.w,
-      height: 230.h,
+      height: imageCount < 5 ? 160.h : 230.h,
       decoration: ShapeDecoration(
         color: Colors.white.withValues(alpha: 0.12),
         shape: RoundedRectangleBorder(
@@ -204,63 +196,68 @@ class PeoplePhotoPage extends BasePage<PeoplePhotoController> {
                 ),
               ),
               GestureDetector(
-                onTap: () => controller.toggleGroupSelection(title),
+                onTap: () => controller.toggleGroupSelection(images),
                 child: Obx(() => Text(
-                  controller.photoGroups
-                      .firstWhere((g) => g.title == title)
-                      .isSelected
-                      .value
-                      ? 'Deselect All'
-                      : 'Select All',
-                  style: TextStyle(
-                    color: Colors.white.withValues(alpha: 0.7),
-                    fontSize: 14.sp,
-                    fontWeight: FontWeight.w400,
-                  ),
-                )),
+                      controller.getGroupByImages(images).isSelected.value
+                          ? 'Deselect All'
+                          : 'Select All',
+                      style: TextStyle(
+                        color: Colors.white.withValues(alpha: 0.7),
+                        fontSize: 14.sp,
+                        fontWeight: FontWeight.w400,
+                      ),
+                    )),
               ),
             ],
           ),
           SizedBox(
-            height: imageCount <= 8 ? null : 148.w,
+            height: imageCount < 3
+                ? 70.h
+                : imageCount <= 8
+                    ? null
+                    : 148.h,
             child: imageCount <= 8
                 ? GridView.builder(
-              shrinkWrap: true,
-              physics: NeverScrollableScrollPhysics(),
-              gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
-                crossAxisCount: 4,
-                mainAxisSpacing: 8.w,
-                crossAxisSpacing: 8.h,
-              ),
-              itemCount: imageCount,
-              itemBuilder: _buildPhotoItem(title),
-            )
+                    shrinkWrap: true,
+                    physics: NeverScrollableScrollPhysics(),
+                    gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
+                      crossAxisCount: 4,
+                      mainAxisSpacing: 8.w,
+                      crossAxisSpacing: 8.h,
+                    ),
+                    itemCount: imageCount,
+                    itemBuilder: _buildPhotoItem(images),
+                  )
                 : GridView.builder(
-              scrollDirection: Axis.horizontal,
-              physics: BouncingScrollPhysics(),
-              gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
-                crossAxisCount: 2,
-                mainAxisSpacing: 8.w,
-                crossAxisSpacing: 8.h,
-                childAspectRatio: 1,
-              ),
-              itemCount: imageCount,
-              itemBuilder: _buildPhotoItem(title),
-            ),
+                    scrollDirection: Axis.horizontal,
+                    physics: BouncingScrollPhysics(),
+                    gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
+                      crossAxisCount: 2,
+                      mainAxisSpacing: 8.w,
+                      crossAxisSpacing: 8.h,
+                      childAspectRatio: 1,
+                    ),
+                    itemCount: imageCount,
+                    itemBuilder: _buildPhotoItem(images),
+                  ),
           ),
         ],
       ),
     );
   }
 
-  Widget Function(BuildContext, int) _buildPhotoItem(String title) =>
-          (context, index) {
-        final group =
-        controller.photoGroups.firstWhere((group) => group.title == title);
+  Widget Function(BuildContext, int) _buildPhotoItem(
+          List<AssetEntity> images) =>
+      (context, index) {
+        final group = controller.getGroupByImages(images);
+
         final assetEntity = group.images[index];
 
         return GestureDetector(
-          onTap: () => controller.toggleImageSelection(title, index),
+          onTap: () {
+            controller.clickImage(images, index);
+            print("点击图片");
+          },
           child: Obx(() {
             final isSelected = group.selectedImages[index];
             return Stack(
@@ -269,34 +266,41 @@ class PeoplePhotoPage extends BasePage<PeoplePhotoController> {
                   width: 70.w,
                   height: 70.w,
                   decoration: ShapeDecoration(
-                    color: Colors.white.withOpacity(0.12),
+                    color: Colors.white.withValues(alpha: 0.12),
                     shape: RoundedRectangleBorder(
                       borderRadius: BorderRadius.circular(9.27.sp),
                     ),
                     image: DecorationImage(
-                      image: AssetEntityImageProvider(assetEntity),
+                      image: AssetEntityImageProvider(
+                        assetEntity,
+                        isOriginal: false,
+                      ),
                       fit: BoxFit.cover,
                     ),
                   ),
                 ),
                 Positioned(
-                  right: 6.w,
-                  bottom: 6.h,
-                  child: Container(
-                    child: isSelected
-                        ? Center(
-                      child: Assets.images.iconSelected.image(
-                        width: 20.w,
-                        height: 20.h,
+                    right: 6.w,
+                    bottom: 6.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,
+                              )),
                       ),
-                    )
-                        : Center(
-                        child: Assets.images.iconUnselected.image(
-                          width: 20.w,
-                          height: 20.h,
-                        )),
-                  ),
-                ),
+                    )),
               ],
             );
           }),

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

@@ -2,33 +2,33 @@ import 'package:get/get.dart';
 import 'package:wechat_assets_picker/wechat_assets_picker.dart';
 
 class PhotoGroup {
+  // 照片组的标题
   final String title;
 
-  //照片组的总数。
+  // 照片组中的图片数量
   final int imageCount;
-
-  // 照片组是否被全选中
+  // 照片组的选择状态
   final RxBool isSelected;
 
-  // 照片组的列表
+  // 照片组图片列表
   final List<AssetEntity> images;
 
-  //单个照片是否被选中
+  // 每张图片的选择状态
   final RxList<bool> selectedImages;
 
+  // 照片组的位置
   final RxString? location;
 
-  // 选中的照片数量
   int get selectedCount => selectedImages.where((selected) => selected).length;
 
   PhotoGroup({
-    required this.title,
     required this.imageCount,
+    required this.title,
     required bool isSelected,
     required this.images,
     this.location,
   })  : isSelected = isSelected.obs,
-        selectedImages = List.generate(imageCount, (_) => isSelected).obs;
+        selectedImages = List.generate(images.length, (_) => isSelected).obs;
 
   void toggleSelectAll(bool value) {
     isSelected.value = value;

+ 223 - 0
lib/module/photo_preview/photo_preview_controller.dart

@@ -0,0 +1,223 @@
+import 'dart:async';
+
+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/people_photo_controller.dart';
+import 'package:clean/module/people_photo/photo_group.dart';
+import 'package:clean/module/screenshots_blurry/screenshots_controller.dart';
+import 'package:clean/module/similar_photo/similar_photo_controller.dart';
+import 'package:flutter/Material.dart';
+import 'package:flutter_card_swiper/flutter_card_swiper.dart';
+import 'package:get/get.dart';
+import 'package:wechat_assets_picker/wechat_assets_picker.dart';
+
+
+class PhotoPreviewController extends BaseController {
+  Rx<CardSwiperController> cardSwiperController = CardSwiperController().obs;
+  final RxList<AssetEntity> photoGroups = <AssetEntity>[].obs;
+  final RxSet<String> selectedPhotosIds = <String>{}.obs;
+
+  RxInt groupIndex = 0.obs;
+
+  late PhotosType photosType;
+  late String? currentImageId;
+  RxDouble selectedFilesSize = 0.0.obs;
+  RxInt selectedFileCount = 0.obs;
+
+  @override
+  void onInit() {
+    super.onInit();
+
+    print('PhotoPreviewController onInit');
+    _getArgs(); // 获取传递的参数
+    _initData(); // 初始化数据
+
+    WidgetsBinding.instance.addPostFrameCallback((_) {
+      if (currentImageId != null) {
+        for (int i = 0; i < photoGroups.length; i++) {
+          if (photoGroups[i].id == currentImageId) {
+            print('photoGroups[i].id ${photoGroups[i].id},i $i');
+            groupIndex.value = i;
+            cardSwiperController.value.moveTo(i);
+            break;
+          }
+        }
+      }
+    });
+  }
+
+  // 获取参数
+  void _getArgs() {
+    photosType = parameters?['photosType'];
+    currentImageId = parameters?['currentImageId'];
+  }
+
+  void _initData() {
+    photoGroups.clear();
+    selectedPhotosIds.clear();
+
+    switch(photosType) {
+      case PhotosType.peoplePhotos:
+        photoGroups.assignAll(ImagePickerUtil.peoplePhotos);
+        selectedPhotosIds.assignAll(ImagePickerUtil.selectedPeoplePhotosIds);
+        selectedFileCount.value = ImagePickerUtil.selectedPeoplePhotosIds.length;
+        break;
+      case PhotosType.screenshots:
+        photoGroups.assignAll(ImagePickerUtil.screenshotPhotos);
+        selectedPhotosIds.assignAll(ImagePickerUtil.selectedScreenshotPhotosIds);
+        selectedFileCount.value = ImagePickerUtil.selectedScreenshotPhotosIds.length;
+        break;
+      case PhotosType.similarPhotos:
+        for (var group in ImagePickerUtil.similarPhotos) {
+          photoGroups.addAll(group);
+        }
+        selectedPhotosIds.assignAll(ImagePickerUtil.selectedSimilarPhotosIds);
+        selectedFileCount.value = ImagePickerUtil.selectedSimilarPhotosIds.length;
+        break;
+      case PhotosType.locationPhotos:
+        for (var group in ImagePickerUtil.locationPhotos.values) {
+          photoGroups.addAll(group);
+        }
+        selectedPhotosIds.assignAll(ImagePickerUtil.selectedLocationPhotosIds);
+        selectedFileCount.value = ImagePickerUtil.selectedLocationPhotosIds.length;
+        break;
+        case PhotosType.blurryPhotos:
+        photoGroups.assignAll(ImagePickerUtil.blurryPhotos);
+        selectedPhotosIds.assignAll(ImagePickerUtil.selectedBlurryPhotosIds);
+        selectedFileCount.value = ImagePickerUtil.selectedBlurryPhotosIds.length;
+        break;
+    }
+    updateSelectedFilesSize();
+
+  }
+
+  Future<void> updateSelectedFilesSize() async {
+    double totalSize = 0;
+    // 通过selectedPhotosIds获取选中的图片,然后计算大小
+    for (var id in selectedPhotosIds) {
+      final entity = await AssetEntity.fromId(id);
+      if (entity != null) {
+        final file = await entity.file;
+        if (file != null) {
+          totalSize += await file.length();
+        }
+      }
+    }
+
+    selectedFilesSize.value = totalSize / 1024; // Convert to KB
+  }
+
+  void recoverSelectPhoto() {
+    cardSwiperController.value.undo();
+  }
+
+  void clickSelect() {
+
+
+    cardSwiperController.value.swipe(CardSwiperDirection.left);
+  }
+
+  @override
+  void onClose() {
+    super.onClose();
+
+    // 清理操作,释放资源
+    cardSwiperController.value.dispose();
+
+  }
+
+  void clickBack() {
+
+    _saveSelectedPhotos(photosType);
+    Get.back();
+  }
+
+  // // 保存选择的图片ID列表
+  void _saveSelectedPhotos(PhotosType type) {
+    switch (type) {
+      case PhotosType.peoplePhotos:
+        ImagePickerUtil.selectedPeoplePhotosIds.assignAll(selectedPhotosIds);
+        PeoplePhotoController controller = Get.find<PeoplePhotoController>();
+        controller.restoreSelections();
+        break;
+      case PhotosType.screenshots:
+        ImagePickerUtil.selectedScreenshotPhotosIds.assignAll(selectedPhotosIds);
+        ScreenShotsController controller = Get.find<ScreenShotsController>();
+        controller.restoreSelections();
+        break;
+      case PhotosType.similarPhotos:
+        ImagePickerUtil.selectedSimilarPhotosIds.assignAll(selectedPhotosIds);
+        SimilarPhotoController controller = Get.find<SimilarPhotoController>();
+        controller.restoreSelections();
+        break;
+      case PhotosType.locationPhotos:
+        ImagePickerUtil.selectedLocationPhotosIds.assignAll(selectedPhotosIds);
+        break;
+      case PhotosType.blurryPhotos:
+        ImagePickerUtil.selectedBlurryPhotosIds.assignAll(selectedPhotosIds);
+        ScreenShotsController controller = Get.find<ScreenShotsController>();
+        controller.restoreSelections();
+        break;
+      }
+  }
+  void clickUnselect() {
+    print("clickUnselect");
+    cardSwiperController.value.swipe(CardSwiperDirection.right);
+  }
+
+  FutureOr<bool> onSwipe(
+    int previousIndex,
+    int? currentIndex,
+    CardSwiperDirection direction,
+  ) {
+    print(
+      'The card $previousIndex was swiped to the ${direction.name}. Now the card $currentIndex is on top',
+    );
+    if (currentIndex != null) {
+      groupIndex.value = currentIndex;
+    }
+    // 如果direction是left,
+    if (direction == CardSwiperDirection.left) {
+      //   先看看图片id是不是在selectedPhotosIds里面,如果在,不处理,如果不在,添加到selectedPhotosIds里面
+      if (!selectedPhotosIds.contains(photoGroups[previousIndex].id)) {
+        print(
+            'add photoGroups[groupIndex.value].id ${photoGroups[previousIndex].id}');
+        selectedPhotosIds.add(photoGroups[previousIndex].id);
+      }
+    } else if (direction == CardSwiperDirection.right) {
+      //   先看看图片id是不是在selectedPhotosIds里面,如果在,在selectedPhotosIds移除,不处理,如果不在,不处理
+      if (selectedPhotosIds.contains(photoGroups[previousIndex].id)) {
+        print(
+            'remove photoGroups[groupIndex.value].id ${photoGroups[previousIndex].id}');
+        selectedPhotosIds.remove(photoGroups[previousIndex].id);
+      }
+    }
+    selectedFileCount.value = selectedPhotosIds.length;
+    updateSelectedFilesSize();
+    return true;
+  }
+
+  bool onSwiperUndo(
+    int? previousIndex,
+    int currentIndex,
+    CardSwiperDirection direction,
+  ) {
+    print(
+        'The card $currentIndex was swiped back to the ${direction.name}. Now the card $previousIndex is on top');
+
+    groupIndex.value = currentIndex;
+    //   撤销之前左滑的操作
+    if (direction == CardSwiperDirection.left) {
+      print(
+          'photoGroups[groupIndex.value].id ${photoGroups[groupIndex.value].id}');
+      if (selectedPhotosIds.contains(photoGroups[groupIndex.value].id)) {
+        selectedPhotosIds.remove(photoGroups[groupIndex.value].id);
+      }
+    }
+    selectedFileCount.value = selectedPhotosIds.length;
+    updateSelectedFilesSize();
+    return true;
+  }
+}
+

+ 242 - 0
lib/module/photo_preview/photo_preview_view.dart

@@ -0,0 +1,242 @@
+import 'package:clean/base/base_page.dart';
+import 'package:clean/data/bean/photos_type.dart';
+import 'package:clean/module/photo_preview/photo_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/src/widgets/framework.dart';
+import 'package:flutter_card_swiper/flutter_card_swiper.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:get/get.dart';
+import 'package:wechat_assets_picker/wechat_assets_picker.dart';
+
+
+class PhotoPreviewPage extends BasePage<PhotoPreviewController> {
+  PhotoPreviewPage({Key? key}) : super(key: key);
+
+  static void start(PhotosType photosType, String currentImageId) {
+    print(' PhotoPreviewPage start $photosType $currentImageId');
+    Get.toNamed(RoutePath.photoPreview, arguments: {
+      "photosType": photosType,
+      "currentImageId": currentImageId,
+    });
+  }
+
+  @override
+  bool immersive() {
+    // TODO: implement immersive
+    return true;
+  }
+
+  @override
+  bool statusBarDarkFont() {
+    // TODO: implement statusBarDarkFont
+    return false;
+  }
+
+  @override
+  Widget buildBody(BuildContext context) {
+    return Stack(children: [
+      Container(
+        child: SafeArea(
+          child: Column(
+            children: [
+              _titleCard(),
+              Spacer(),
+              (controller.photoGroups.length == 1)
+                  ? 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,
+                        cardBuilder: (context,
+                            index,
+                            horizontalOffsetPercentage,
+                            verticalOffsetPercentage) {
+                          final assetEntity = controller.photoGroups[index];
+                          return AssetEntityImage(
+                            assetEntity,
+                            width: 314.w,
+                            height: 392.h,
+                          );
+                        },
+                      ),
+                    ),
+              Spacer(),
+              bottomButtonCard(),
+              _bottomBarCard(),
+            ],
+          ),
+        ),
+      ),
+      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),
+      child: Column(
+        crossAxisAlignment: CrossAxisAlignment.start,
+        children: [
+          Row(
+            mainAxisAlignment: MainAxisAlignment.spaceBetween,
+            children: [
+              GestureDetector(
+                onTap: controller.clickBack,
+                child: Assets.images.iconBackArrow.image(
+                  width: 28.w,
+                  height: 28.h,
+                ),
+              ),
+              Obx(() => Row(
+                    mainAxisAlignment: MainAxisAlignment.center,
+                    children: [
+                      Text(
+                        '${controller.groupIndex.value + 1}',
+                        textAlign: TextAlign.center,
+                        style: TextStyle(
+                          color: Colors.white,
+                          fontSize: 16.sp,
+                          fontWeight: FontWeight.w700,
+                        ),
+                      ),
+                      Text(
+                        ' / ${controller.photoGroups.length}',
+                        textAlign: TextAlign.center,
+                        style: TextStyle(
+                          color: Colors.white.withValues(alpha: 0.6),
+                          fontSize: 16.sp,
+                          fontWeight: FontWeight.w500,
+                        ),
+                      ),
+                    ],
+                  )),
+              GestureDetector(
+                onTap: controller.recoverSelectPhoto,
+                child: Assets.images.iconPreviewRecover.image(
+                  width: 30.w,
+                  height: 30.h,
+                ),
+              ),
+            ],
+          ),
+          SizedBox(height: 12.h),
+        ],
+      ),
+    );
+  }
+
+  Widget _bottomBarCard() {
+    return Container(
+      width: 360.w,
+      height: 81.h,
+      padding: EdgeInsets.symmetric(horizontal: 16.w),
+      decoration: ShapeDecoration(
+        color: Color(0xFF23232A),
+        shape: RoundedRectangleBorder(
+          side: BorderSide(
+              width: 1.w, color: Colors.white.withValues(alpha: 0.1)),
+          borderRadius: BorderRadius.only(
+            topLeft: Radius.circular(14.r),
+            topRight: Radius.circular(14.r),
+          ),
+        ),
+      ),
+      child: Row(
+        mainAxisAlignment: MainAxisAlignment.spaceBetween,
+        children: [
+          Obx(() {
+            return Text(
+              '${controller.selectedFileCount.value} files selected (${controller.selectedFilesSize.value.toStringAsFixed(1)} KB)',
+              textAlign: TextAlign.center,
+              style: TextStyle(
+                color: Colors.white.withValues(alpha: 0.9),
+                fontSize: 13.sp,
+                fontWeight: FontWeight.w500,
+              ),
+            );
+          }),
+          Container(
+            width: 108.w,
+            height: 38.h,
+            decoration: ShapeDecoration(
+              color: Color(0xFF0279FB),
+              shape: RoundedRectangleBorder(
+                borderRadius: BorderRadius.circular(10.r),
+              ),
+            ),
+            child: Row(
+              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+              children: [
+                Text(
+                  'Delete',
+                  textAlign: TextAlign.center,
+                  style: TextStyle(
+                    color: Colors.white,
+                    fontSize: 16.sp,
+                    fontWeight: FontWeight.w500,
+                  ),
+                ),
+                Assets.images.iconDelete.image(
+                  width: 18.w,
+                  height: 18.h,
+                ),
+              ],
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+
+  Widget bottomButtonCard() {
+    return Container(
+      margin: EdgeInsets.only(bottom: 54.h),
+      child: Row(
+        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+        children: [
+          GestureDetector(
+            onTap: () => controller.clickSelect(),
+            child: Assets.images.iconPreviewSelect.image(
+              width: 62.w,
+              height: 62.h,
+            ),
+          ),
+          GestureDetector(
+            onTap: () => controller.clickUnselect(),
+            child: Assets.images.iconPreviewNoSelect.image(
+              width: 62.w,
+              height: 62.h,
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+}

+ 165 - 3
lib/module/screenshots_blurry/screenshots_controller.dart

@@ -1,6 +1,168 @@
-import 'package:get/get_state_manager/src/simple/get_controllers.dart';
+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_view.dart';
+import 'package:get/get.dart';
 
-class ScreenShotsController extends GetxController {
 
+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;
 
-}
+  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 toggleImageSelection(String groupTitle, int imageIndex) {
+    final group = _getGroupByTitle(groupTitle);
+    final image = group.images[imageIndex];
+    final selected = !group.selectedImages[imageIndex];
+
+    group.selectedImages[imageIndex] = selected;
+    _updateSelectedScreenshotsPhotosIds(image.id, selected);
+
+    group.isSelected.value = group.selectedImages.every((selected) => selected);
+
+    updateSelectedFilesSize();
+    selectedFileCount.value = selectedPhotosIds.length;
+  }
+
+  void clickImage(String groupTitle, int imageIndex) {
+    final group = _getGroupByTitle(groupTitle);
+    final image = group.images[imageIndex];
+    print('clickImage $groupTitle $imageIndex');
+
+    if (titleName == "Screenshots") {
+      PhotoPreviewPage.start(PhotosType.screenshots, image.id);
+    } else if (titleName == "Blurry") {
+      PhotoPreviewPage.start(PhotosType.blurryPhotos, image.id);
+    }
+
+  }
+
+
+  void toggleGroupSelection(String groupTitle) {
+    final group = _getGroupByTitle(groupTitle);
+    final newValue = !group.isSelected.value;
+    group.toggleSelectAll(newValue);
+
+    for (var image in group.images) {
+      _updateSelectedScreenshotsPhotosIds(image.id, newValue);
+    }
+
+    updateSelectedFilesSize();
+    selectedFileCount.value = selectedPhotosIds.length;
+  }
+
+  // 通过标题获取照片组
+  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);
+  // }
+
+  void _updateSelectedScreenshotsPhotosIds(String photoId, bool isSelected) {
+    if (isSelected) {
+      selectedPhotosIds.add(photoId);
+    } else {
+      selectedPhotosIds.remove(photoId);
+    }
+  }
+
+  void restoreSelections() {
+    if (titleName == "Screenshots") {
+      selectedPhotosIds.assignAll(ImagePickerUtil.selectedScreenshotPhotosIds);
+    } else if (titleName == "Blurry") {
+      selectedPhotosIds.assignAll(ImagePickerUtil.selectedBlurryPhotosIds);
+    }
+
+    // print('screenshots restoreSelections ${selectedPhotosIds.length}');
+    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;
+  }
+
+  @override
+  void onInit() async {
+    // TODO: implement onInit
+    super.onInit();
+    _getArgs();
+    loadScreenshots();
+    restoreSelections();
+  }
+
+  void _getArgs() {
+    titleName = parameters?['titleName'];
+  }
+
+  void loadScreenshots() {
+    // 清空现有数据
+    photoGroups.clear();
+
+    // 将 ImagePickerUtil 中的相似照片转换为 PhotoGroup
+
+    if (titleName == "Screenshots") {
+      final photoGroup = ImagePickerUtil.screenshotPhotos;
+      if (photoGroup.isNotEmpty) {
+        photoGroups.add(PhotoGroup(
+          title: 'photo : ${photoGroup.length}',
+          imageCount: photoGroup.length,
+          isSelected: false,
+          images: photoGroup,
+        ));
+      }
+      selectedPhotosIds.assignAll(ImagePickerUtil.selectedScreenshotPhotosIds);
+    } else if (titleName == "Blurry") {
+
+      final photoGroup = ImagePickerUtil.blurryPhotos;
+      if (photoGroup.isNotEmpty) {
+        photoGroups.add(PhotoGroup(
+          title: 'photo : ${photoGroup.length}',
+          imageCount: photoGroup.length,
+          isSelected: false,
+          images: photoGroup,
+        ));
+      }
+      selectedPhotosIds.assignAll(ImagePickerUtil.selectedBlurryPhotosIds);
+    }
+  }
+
+  @override
+  void onClose() {
+    // TODO: implement onClose
+    super.onClose();
+    if (titleName == "Screenshots") {
+      ImagePickerUtil.selectedScreenshotPhotosIds.assignAll(selectedPhotosIds);
+    } else if (titleName == "Blurry") {
+      ImagePickerUtil.selectedBlurryPhotosIds.assignAll(selectedPhotosIds);
+    }
+  }
+}

+ 234 - 6
lib/module/screenshots_blurry/screenshots_view.dart

@@ -1,14 +1,19 @@
 import 'package:clean/base/base_page.dart';
 import 'package:clean/module/screenshots_blurry/screenshots_controller.dart';
+import 'package:clean/resource/assets.gen.dart';
 import 'package:clean/router/app_pages.dart';
 import 'package:flutter/Material.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
 import 'package:get/get.dart';
-class ScreenshotsPage extends BasePage<ScreenShotsController> {
+import 'package:wechat_assets_picker/wechat_assets_picker.dart';
 
+class ScreenshotsPage extends BasePage<ScreenShotsController> {
+  const ScreenshotsPage({super.key});
 
-  static start() {
-    Get.put((ScreenShotsController()));
-    Get.toNamed(RoutePath.screenshots, arguments: {});
+  static void start(String titleName) {
+    Get.toNamed(RoutePath.screenshots, arguments: {
+      "titleName": titleName,
+    });
   }
 
   @override
@@ -23,7 +28,230 @@ class ScreenshotsPage extends BasePage<ScreenShotsController> {
 
   @override
   Widget buildBody(BuildContext context) {
-    return Container();
+    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, // 每行显示 2 个元素
+                          mainAxisSpacing: 8.w, // 垂直间距
+                          crossAxisSpacing: 8.h, // 水平间距
+                        ),
+                        itemCount: controller.photoGroups[0].imageCount,
+                        itemBuilder:
+                            _buildPhotoItem(controller.photoGroups[0].title)),
+                  );
+                }),
+              ),
+
+              _bottomBarCard(),
+              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),
+      child: Column(
+        crossAxisAlignment: CrossAxisAlignment.start,
+        children: [
+          Row(
+            mainAxisAlignment: MainAxisAlignment.spaceBetween,
+            children: [
+              GestureDetector(
+                onTap: () => Get.back(),
+                child: Assets.images.iconBackArrow.image(
+                  width: 28.w,
+                  height: 28.h,
+                ),
+              ),
+              GestureDetector(
+                onTap: () => controller
+                    .toggleGroupSelection(controller.photoGroups[0].title),
+                child: Obx(() => Text(
+                      controller.photoGroups
+                              .firstWhere((g) =>
+                                  g.title == controller.photoGroups[0].title)
+                              .isSelected
+                              .value
+                          ? 'Deselect All'
+                          : 'Select All',
+                      style: TextStyle(
+                        color: Colors.white.withValues(alpha: 0.7),
+                        fontSize: 14.sp,
+                        fontWeight: FontWeight.w400,
+                      ),
+                    )),
+              ),
+            ],
+          ),
+          SizedBox(height: 12.h),
+          Text(
+            controller.titleName,
+            style: TextStyle(
+              color: Colors.white,
+              fontSize: 24.sp,
+              fontWeight: FontWeight.w700,
+            ),
+          ),
+          SizedBox(height: 20.h),
+        ],
+      ),
+    );
   }
 
-}
+  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,
+          ),
+          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) =>
+      (context, index) {
+        final group =
+            controller.photoGroups.firstWhere((group) => group.title == title);
+        final assetEntity = group.images[index];
+
+        return GestureDetector(
+          onTap: () => controller.clickImage(title, index),
+          child: Obx(() {
+            final 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(assetEntity),
+                      fit: BoxFit.cover,
+                    ),
+                  ),
+                ),
+                Positioned(
+                  right: 8.w,
+                  bottom: 8.h,
+                  child: GestureDetector(
+                      onTap: () =>
+                          controller.toggleImageSelection(title, 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(
+      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: 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,
+                  ),
+                ),
+              ],
+            ),
+          ),
+        ),
+      ],
+    );
+  }
+}

+ 67 - 54
lib/module/similar_photo/similar_photo_controller.dart

@@ -1,21 +1,21 @@
 import 'dart:io';
 
+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_view.dart';
 import 'package:get/get.dart';
 
 import 'package:wechat_assets_picker/wechat_assets_picker.dart';
 
-class SimilarPhotoController extends GetxController {
-  final RxList<PhotoGroup> photoGroups = <PhotoGroup>[].obs;
-
-  static final Map<String, List<bool>> _savedSelections = {};
-  static bool _hasInitializedSelections = false;
+class SimilarPhotoController extends BaseController {
 
-  int get selectedFileCount =>
-      photoGroups.fold(0, (sum, group) => sum + group.selectedCount);
+  final RxList<PhotoGroup> photoGroups = <PhotoGroup>[].obs;
+  final RxDouble selectedFilesSize = 0.0.obs;
+  RxInt selectedFileCount = 0.obs;
 
-  Future<double> getSelectedFilesSize() async {
+  Future<void> getSelectedFilesSize() async {
     double totalSize = 0;
     for (var group in photoGroups) {
       for (int i = 0; i < group.images.length; i++) {
@@ -27,85 +27,98 @@ class SimilarPhotoController extends GetxController {
         }
       }
     }
-    return totalSize / 1024; // Convert to KB
+    selectedFilesSize.value = totalSize / 1024; // Convert to KB
   }
 
 
-  void toggleImageSelection(String groupTitle, int imageIndex) {
-    final group = photoGroups.firstWhere((g) => g.title == groupTitle);
-    group.selectedImages[imageIndex] = !group.selectedImages[imageIndex];
+  void toggleImageSelection(List<AssetEntity> groupTitle, int imageIndex) {
+    final group = _getGroupByImages(groupTitle);
+    final image = group.images[imageIndex];
+    final selected = !group.selectedImages[imageIndex];
+
+    group.selectedImages[imageIndex] = selected;
+    _updateSelectedSimilarPhotosIds(image.id, selected);
+
     group.isSelected.value = group.selectedImages.every((selected) => selected);
-    _saveSelections();
+
+    selectedFileCount.value = ImagePickerUtil.selectedSimilarPhotosIds.length;
+    getSelectedFilesSize();
+  }
+
+  void clickImage(List<AssetEntity> images , int imageIndex) {
+    final group = _getGroupByImages(images);
+    final image = group.images[imageIndex];
+    PhotoPreviewPage.start(PhotosType.similarPhotos,image.id);
+
   }
 
-  void toggleGroupSelection(String groupTitle) {
-    final group = photoGroups.firstWhere((g) => g.title == groupTitle);
+  void toggleGroupSelection(List<AssetEntity> imagesList) {
+    final group = _getGroupByImages(imagesList);
     final newValue = !group.isSelected.value;
     group.toggleSelectAll(newValue);
-    _saveSelections();
+
+    for (var image in group.images) {
+      _updateSelectedSimilarPhotosIds(image.id, newValue);
+    }
+    selectedFileCount.value = ImagePickerUtil.selectedSimilarPhotosIds.length;
+    getSelectedFilesSize();
   }
 
-  void _saveSelections() {
-    for (var group in photoGroups) {
-      _savedSelections[group.title] = group.selectedImages.toList();
+
+  PhotoGroup _getGroupByImages(List<AssetEntity> images) {
+    return photoGroups.firstWhere((g) => g.images == images);
+  }
+
+
+
+  void _updateSelectedSimilarPhotosIds(String photoId, bool isSelected) {
+    if (isSelected) {
+      ImagePickerUtil.selectedSimilarPhotosIds.add(photoId);
+    } else {
+      ImagePickerUtil.selectedSimilarPhotosIds.remove(photoId);
     }
-    _hasInitializedSelections = true;
   }
 
-  void _restoreSelections() {
+  void restoreSelections() async {
+    final selectedIds = ImagePickerUtil.selectedSimilarPhotosIds.toSet();
     for (var group in photoGroups) {
-      // 这里假设每次页面加载时,已选择的状态会保存在 `RxList<bool>` 中。
-      group.isSelected.value =
-          group.selectedImages.every((selected) => selected);
+      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);
     }
+    await getSelectedFilesSize();
+    selectedFileCount.value = selectedIds.length;
   }
 
   @override
   void onInit() {
     super.onInit();
     loadSimilarPhotos();
-    if (_hasInitializedSelections) {
-          _restoreSelections();
-        }
-    // loadPhotosFromDirectory().then((_) {
-    //
-    // });
-  }
 
-  Future<void> loadPhotosFromDirectory() async {
-    try {
-      final List<AssetEntity>? result = await AssetPicker.pickAssets(
-        Get.context!,
-      );
-      if (result != null && result.isNotEmpty) {
-        photoGroups.add(PhotoGroup(
-          title: 'photo: ${result.length}',
-          imageCount: result.length,
-          isSelected: false,
-          images: result,
-        ));
-      }
-    } catch (e) {
-      print('Error loading photos: $e');
-    }
+    restoreSelections();
+
+
   }
 
   void loadSimilarPhotos() {
     // 清空现有数据
     photoGroups.clear();
 
-    // 将 ImagePickerUtil 中的相似照片转换为 PhotoGroup
-    for (int i = 0; i < ImagePickerUtil.similarPhotos.length; i++) {
-      final photoGroup = ImagePickerUtil.similarPhotos[i];
-      if (photoGroup.isNotEmpty) {
+    // 加载相似图片
+    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 : ${photoGroup.length}',
-          imageCount: photoGroup.length,
+          title: 'photo : ${group.length}',
+          imageCount: group.length,
           isSelected: false,
-          images: photoGroup,
+          images: group,
         ));
       }
     }
+
+
   }
 
   // 修改视图构建方法,使用 AssetEntity 而不是文件路径

+ 88 - 89
lib/module/similar_photo/similar_photo_view.dart

@@ -13,7 +13,6 @@ class SimilarPhotoPage extends BasePage<SimilarPhotoController> {
   const SimilarPhotoPage({super.key});
 
   static void start() {
-    Get.put((SimilarPhotoController()));
     Get.toNamed(RoutePath.similarPhoto);
   }
 
@@ -37,14 +36,15 @@ class SimilarPhotoPage extends BasePage<SimilarPhotoController> {
                     padding: EdgeInsets.symmetric(horizontal: 16.w),
                     children: [
                       ...controller.photoGroups.map((group) => Column(
-                        children: [
-                          _buildPhotoGroup(
-                            title: group.title,
-                            imageCount: group.imageCount,
-                          ),
-                          SizedBox(height: 15.h),
-                        ],
-                      ))
+                            children: [
+                              _buildPhotoGroup(
+                                imagesList: group.images,
+                                title: group.title,
+                                imageCount: group.imageCount,
+                              ),
+                              SizedBox(height: 15.h),
+                            ],
+                          ))
                     ],
                   );
                 }),
@@ -111,25 +111,14 @@ class SimilarPhotoPage extends BasePage<SimilarPhotoController> {
         mainAxisAlignment: MainAxisAlignment.spaceBetween,
         children: [
           Obx(() {
-            return FutureBuilder<double>(
-              future: controller.getSelectedFilesSize(),
-              builder: (context, snapshot) {
-                if (snapshot.connectionState == ConnectionState.waiting) {
-                  return CircularProgressIndicator();
-                } else if (snapshot.hasError) {
-                  return Text('Error: ${snapshot.error}');
-                } else {
-                  return Text(
-                    '${controller.selectedFileCount} files selected (${snapshot.data?.toStringAsFixed(1)} KB)',
-                    textAlign: TextAlign.center,
-                    style: TextStyle(
-                      color: Colors.white.withValues(alpha: 0.9),
-                      fontSize: 13.sp,
-                      fontWeight: FontWeight.w500,
-                    ),
-                  );
-                }
-              },
+            return Text(
+              '${controller.selectedFileCount.value} files selected (${controller.selectedFilesSize.value.toStringAsFixed(1)} KB)',
+              textAlign: TextAlign.center,
+              style: TextStyle(
+                color: Colors.white.withValues(alpha: 0.9),
+                fontSize: 13.sp,
+                fontWeight: FontWeight.w500,
+              ),
             );
           }),
           Container(
@@ -166,6 +155,7 @@ class SimilarPhotoPage extends BasePage<SimilarPhotoController> {
   }
 
   Widget _buildPhotoGroup({
+    required List<AssetEntity> imagesList,
     required String title,
     required int imageCount,
   }) {
@@ -200,20 +190,20 @@ class SimilarPhotoPage extends BasePage<SimilarPhotoController> {
                 ],
               ),
               GestureDetector(
-                onTap: () => controller.toggleGroupSelection(title),
+                onTap: () => controller.toggleGroupSelection(imagesList),
                 child: Obx(() => Text(
-                  controller.photoGroups
-                      .firstWhere((g) => g.title == title)
-                      .isSelected
-                      .value
-                      ? 'Deselect All'
-                      : 'Select All',
-                  style: TextStyle(
-                    color: Colors.white.withValues(alpha: 0.7),
-                    fontSize: 14.sp,
-                    fontWeight: FontWeight.w400,
-                  ),
-                )),
+                      controller.photoGroups
+                              .firstWhere((g) => g.images == imagesList)
+                              .isSelected
+                              .value
+                          ? 'Deselect All'
+                          : 'Select All',
+                      style: TextStyle(
+                        color: Colors.white.withValues(alpha: 0.7),
+                        fontSize: 14.sp,
+                        fontWeight: FontWeight.w400,
+                      ),
+                    )),
               ),
             ],
           ),
@@ -226,13 +216,13 @@ class SimilarPhotoPage extends BasePage<SimilarPhotoController> {
                 // 第一张大图
                 if (imageCount > 0)
                   GestureDetector(
-                    onTap: () => controller.toggleImageSelection(title, 0),
+                    onTap: () => controller.clickImage(imagesList, 0),
                     child: SizedBox(
                       width: 148.w,
                       height: 148.h,
                       child: Obx(() {
                         final group = controller.photoGroups
-                            .firstWhere((g) => g.title == title);
+                            .firstWhere((g) => g.images == imagesList);
                         return Stack(
                           children: [
                             Container(
@@ -243,7 +233,7 @@ class SimilarPhotoPage extends BasePage<SimilarPhotoController> {
                                 ),
                                 image: DecorationImage(
                                   image:
-                                  AssetEntityImageProvider(group.images[0]),
+                                      AssetEntityImageProvider(group.images[0]),
                                   fit: BoxFit.cover,
                                 ),
                               ),
@@ -262,12 +252,12 @@ class SimilarPhotoPage extends BasePage<SimilarPhotoController> {
                                   color: Colors.black.withValues(alpha: 0.74),
                                   shape: RoundedRectangleBorder(
                                     borderRadius:
-                                    BorderRadius.circular(14.21.r),
+                                        BorderRadius.circular(14.21.r),
                                   ),
                                 ),
                                 child: Row(
                                   mainAxisAlignment:
-                                  MainAxisAlignment.spaceAround,
+                                      MainAxisAlignment.spaceAround,
                                   children: [
                                     Assets.images.iconSimilarBest.image(
                                       width: 11.37.w,
@@ -289,20 +279,25 @@ class SimilarPhotoPage extends BasePage<SimilarPhotoController> {
                             Positioned(
                               right: 4.w,
                               bottom: 4.h,
-                              child: Container(
-                                child: group.selectedImages[0]
-                                    ? Center(
-                                  child: Assets.images.iconSelected.image(
-                                    width: 16.w,
-                                    height: 16.h,
-                                  ),
-                                )
-                                    : Center(
-                                  child:
-                                  Assets.images.iconUnselected.image(
-                                    width: 16.w,
-                                    height: 16.h,
-                                  ),
+                              child: GestureDetector(
+                                onTap: () =>
+                                    controller.toggleImageSelection(imagesList, 0),
+                                child: Container(
+                                  child: group.selectedImages[0]
+                                      ? Center(
+                                          child:
+                                              Assets.images.iconSelected.image(
+                                            width: 16.w,
+                                            height: 16.h,
+                                          ),
+                                        )
+                                      : Center(
+                                          child: Assets.images.iconUnselected
+                                              .image(
+                                            width: 16.w,
+                                            height: 16.h,
+                                          ),
+                                        ),
                                 ),
                               ),
                             ),
@@ -324,14 +319,14 @@ class SimilarPhotoPage extends BasePage<SimilarPhotoController> {
                         crossAxisSpacing: 8.w,
                         children: List.generate(
                           min(4, imageCount - 1 - gridIndex * 4),
-                              (index) {
+                          (index) {
                             final realIndex = gridIndex * 4 + index + 1;
                             return GestureDetector(
-                              onTap: () => controller.toggleImageSelection(
-                                  title, realIndex),
+                              onTap: () => controller.clickImage(
+                                  imagesList, realIndex),
                               child: Obx(() {
                                 final group = controller.photoGroups
-                                    .firstWhere((g) => g.title == title);
+                                    .firstWhere((g) => g.images == imagesList);
                                 return Container(
                                   decoration: ShapeDecoration(
                                     color: Colors.white.withOpacity(0.12),
@@ -351,24 +346,28 @@ class SimilarPhotoPage extends BasePage<SimilarPhotoController> {
                                         bottom: 4.h,
                                         child: Obx(() {
                                           final isSelected =
-                                          group.selectedImages[realIndex];
-                                          return Container(
-                                            child: isSelected
-                                                ? Center(
-                                              child: Assets
-                                                  .images.iconSelected
-                                                  .image(
-                                                width: 16.w,
-                                                height: 16.h,
-                                              ),
-                                            )
-                                                : Center(
-                                              child: Assets
-                                                  .images.iconUnselected
-                                                  .image(
-                                                width: 16.w,
-                                                height: 16.h,
-                                              ),
+                                              group.selectedImages[realIndex];
+                                          return GestureDetector(
+                                            onTap: () => controller.toggleImageSelection(
+                                                imagesList, realIndex),
+                                            child: Container(
+                                              child: isSelected
+                                                  ? Center(
+                                                      child: Assets
+                                                          .images.iconSelected
+                                                          .image(
+                                                        width: 16.w,
+                                                        height: 16.h,
+                                                      ),
+                                                    )
+                                                  : Center(
+                                                      child: Assets
+                                                          .images.iconUnselected
+                                                          .image(
+                                                        width: 16.w,
+                                                        height: 16.h,
+                                                      ),
+                                                    ),
                                             ),
                                           );
                                         }),
@@ -397,13 +396,13 @@ class SimilarPhotoPage extends BasePage<SimilarPhotoController> {
             ),
             child: Center(
               child: Obx(() => Text(
-                'Move ${controller.photoGroups.firstWhere((g) => g.title == title).selectedCount} to trash',
-                style: TextStyle(
-                  color: Colors.white,
-                  fontSize: 16.sp,
-                  fontWeight: FontWeight.w500,
-                ),
-              )),
+                    'Move ${controller.photoGroups.firstWhere((g) => g.images == imagesList).selectedCount} to trash',
+                    style: TextStyle(
+                      color: Colors.white,
+                      fontSize: 16.sp,
+                      fontWeight: FontWeight.w500,
+                    ),
+                  )),
             ),
           ),
         ],

+ 11 - 0
lib/router/app_pages.dart

@@ -2,10 +2,14 @@ import 'package:clean/module/home/home_controller.dart';
 import 'package:clean/module/locations_photo/locations_photo_controller.dart';
 import 'package:clean/module/locations_photo/locations_photo_view.dart';
 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/privacy/privacy_view.dart';
 import 'package:clean/module/people_photo/people_photo_controller.dart';
 import 'package:clean/module/people_photo/people_photo_view.dart';
+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:get/get.dart';
@@ -30,6 +34,7 @@ abstract class RoutePath {
   static const locationsPhoto = '/locationsPhoto';
   static const screenshots = '/screenshots';
   static const photoInfo = '/photoInfo';
+  static const photoPreview = '/photoPreview';
 }
 
 class AppBinding extends Bindings {
@@ -41,6 +46,9 @@ class AppBinding extends Bindings {
     lazyPut(() => PeoplePhotoController());
     lazyPut(() => SimilarPhotoController());
     lazyPut(() => LocationsPhotoController());
+    lazyPut(() => ScreenShotsController());
+    lazyPut(() => PhotoPreviewController());
+
     lazyPut(() => LocationsPhotoPage());
     lazyPut(() => PhotoInfoController());
   }
@@ -48,6 +56,7 @@ class AppBinding extends Bindings {
   void lazyPut<S>(InstanceBuilderCallback<S> builder) {
     Get.lazyPut(builder, fenix: true);
   }
+
 }
 
 final generalPages = [
@@ -56,5 +65,7 @@ final generalPages = [
   GetPage(name: RoutePath.peoplePhoto, page: () => PeoplePhotoPage()),
   GetPage(name: RoutePath.similarPhoto, page: () => SimilarPhotoPage()),
   GetPage(name: RoutePath.locationsPhoto, page: () => LocationsPhotoPage()),
+  GetPage(name: RoutePath.screenshots, page: () => ScreenshotsPage()),
+  GetPage(name: RoutePath.photoPreview, page: () => PhotoPreviewPage()),
   GetPage(name: RoutePath.photoInfo, page: () => PhotoInfoPage()),
 ];

+ 2 - 0
pubspec.yaml

@@ -53,6 +53,8 @@ dependencies:
 
   # 圆形进度条
   syncfusion_flutter_charts: ^28.1.38
+  # 卡片滑动
+  flutter_card_swiper: ^7.0.2
   #权限申请
   permission_handler: ^11.3.1