Browse Source

[feat]完成图片查看页和隐私空间开发

Destiny 1 year ago
parent
commit
6db5bbbf70

+ 2 - 0
lib/model/asset_info.dart

@@ -18,6 +18,7 @@ class AssetInfo {
   final String? localPath;
   final String? localPath;
   final String? filePath;
   final String? filePath;
   final String? thumbFilePath;
   final String? thumbFilePath;
+  String? dateTitle;
   int? size;
   int? size;
 
 
   AssetInfo({
   AssetInfo({
@@ -36,6 +37,7 @@ class AssetInfo {
     this.filePath,
     this.filePath,
     this.thumbFilePath,
     this.thumbFilePath,
     this.size,
     this.size,
+    this.dateTitle,
   });
   });
 
 
   // 从 AssetEntity 创建
   // 从 AssetEntity 创建

+ 137 - 0
lib/module/photo_info/photo_info_controller.dart

@@ -0,0 +1,137 @@
+import 'dart:math';
+
+import 'package:clean/module/privacy/privacy_state.dart';
+import 'package:clean/utils/expand.dart';
+import 'package:clean/utils/file_utils.dart';
+import 'package:flutter/cupertino.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:get/get.dart';
+import 'package:wechat_assets_picker/wechat_assets_picker.dart';
+
+import '../../base/base_controller.dart';
+import '../../model/asset_info.dart';
+
+class PhotoInfoController extends BaseController {
+  RxBool isAnalysis = false.obs;
+
+  // 存储所有图片,按月份分组
+  RxList<AssetInfo> imageList = <AssetInfo>[].obs;
+
+  // 当前显示的图片索引
+  final RxInt currentImageIndex = 0.obs;
+
+  @override
+  void onInit() {
+    // TODO: implement onInit
+    super.onInit();
+
+    if (Get.arguments != null) {
+      final args = Get.arguments as Map<String, dynamic>;
+      imageList.value = Get.arguments["list"] as List<AssetInfo>;
+      var type = Get.arguments["type"] as String;
+      ;
+      if (type == "privacy") {
+        isAnalysis.value = false;
+      } else {
+        isAnalysis.value = true;
+      }
+
+      // 设置初始索引
+      if (args.containsKey('index')) {
+        currentImageIndex.value = args['index'] as int;
+      }
+    }
+  }
+
+  // 切换到下一张图片
+  void nextImage() {
+    if (currentImageIndex.value < imageList.length - 1) {
+      currentImageIndex.value++;
+    }
+  }
+
+  // 切换到上一张图片
+  void previousImage() {
+    if (currentImageIndex.value > 0) {
+      currentImageIndex.value--;
+    }
+  }
+
+  void deleteBtnClick(AssetInfo asset, int index) {
+    showCupertinoModalPopup(
+      context: Get.context!,
+      builder: (context) {
+        return CupertinoActionSheet(
+          title: Text(
+            "The file will be deleted from the privacy space",
+            style: TextStyle(
+              color: "#6E6E6E".color,
+              fontSize: 12.sp,
+              fontWeight: FontWeight.w300,
+            ),
+          ),
+          actions: <Widget>[
+            //操作按钮集合
+            CupertinoActionSheetAction(
+              onPressed: () {
+                Navigator.pop(context);
+                FileUtils.deleteAsset(asset.id.substring(0, 36));
+
+                if (!isAnalysis.value) {
+                  PrivacyState.removeAsset(asset.id);
+                  // 从列表中移除
+                  imageList = PrivacyState.imageList;
+                }
+
+                // 如果没有图片了,返回上一页
+                if (imageList.isEmpty) {
+                  Get.back();
+                } else {
+                  if (currentImageIndex.value != 0) {
+                    currentImageIndex.value = imageList.length - 1;
+                  }
+                }
+              },
+              child: Text(
+                'Delete',
+                style: TextStyle(
+                  color: "#FC4C4F".color,
+                  fontWeight: FontWeight.w500,
+                  fontSize: 16.sp,
+                ),
+              ),
+            ),
+          ],
+          cancelButton: CupertinoActionSheetAction(
+            //取消按钮
+            onPressed: () {
+              Navigator.pop(context);
+            },
+            child: Text(
+              'Cancel',
+              style: TextStyle(
+                color: "#007AFF".color,
+                fontWeight: FontWeight.w500,
+                fontSize: 16.sp,
+              ),
+            ),
+          ),
+        );
+      },
+    );
+  }
+
+  // 格式化文件大小显示
+  String formatFileSize(int bytes) {
+    if (bytes <= 0) return "Delete";
+
+    final units = ['B', 'KB', 'MB', 'GB'];
+    int digitGroups = (log(bytes) / log(1024)).floor();
+
+    if (bytes == 0) {
+      return "Delete";
+    } else {
+      return "Delete(${(bytes / pow(1024, digitGroups)).toStringAsFixed(1)} ${units[digitGroups]})";
+    }
+  }
+}

+ 191 - 0
lib/module/photo_info/photo_info_view.dart

@@ -0,0 +1,191 @@
+import 'dart:io';
+
+import 'package:clean/base/base_page.dart';
+import 'package:clean/module/image_picker/image_picker_util.dart';
+import 'package:clean/module/photo_info/photo_info_controller.dart';
+import 'package:clean/utils/expand.dart';
+import 'package:flutter/Material.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:get/get.dart';
+import 'package:wechat_assets_picker/wechat_assets_picker.dart';
+import '../../resource/assets.gen.dart';
+import '../../utils/image_util.dart';
+import 'dart:typed_data';
+
+class PhotoInfoPage extends BasePage<PhotoInfoController> {
+  const PhotoInfoPage({super.key});
+
+  @override
+  bool statusBarDarkFont() => false;
+
+  @override
+  bool immersive() => true;
+
+  @override
+  Widget buildBody(BuildContext context) {
+    return Stack(
+      children: [
+        SafeArea(
+          child: Column(
+            // mainAxisAlignment: MainAxisAlignment.center,
+            crossAxisAlignment: CrossAxisAlignment.start,
+            children: [
+              Container(
+                margin: EdgeInsets.only(left: 16.w, top: 14.h),
+                child: GestureDetector(
+                  onTap: () {
+                    Get.back();
+                  },
+                  child: Assets.images.iconCommonBack
+                      .image(width: 28.w, height: 28.w),
+                ),
+              ),
+              Container(
+                margin: EdgeInsets.only(left: 16.w, top: 12.h),
+                child: Text(
+                  controller.imageList[controller.currentImageIndex.value]
+                      .dateTitle ?? "",
+                  style: TextStyle(
+                    color: Colors.white,
+                    fontWeight: FontWeight.w500,
+                    fontSize: 24.sp,
+                  ),
+                ),
+              ),
+              SizedBox(height: 20.h,),
+              _buildImageCarousel(),
+              Spacer(),
+              Center(
+                child: GestureDetector(
+                  onTap: () {
+                    controller.deleteBtnClick(controller.imageList[controller
+                        .currentImageIndex.value], controller.currentImageIndex.value);
+                  },
+                  child: Container(
+                    width: 328.w,
+                    height: 48.h,
+                    decoration: BoxDecoration(
+                      color: "#0279FB".color,
+                      borderRadius: BorderRadius.all(
+                        Radius.circular(10.r),
+                      ),
+                    ),
+                    child: Center(
+                      child: Row(
+                        mainAxisAlignment: MainAxisAlignment.center,
+                        children: [
+                          Assets.images.iconPrivacyPhotoDelete
+                              .image(width: 18.w, height: 18.h),
+                          SizedBox(
+                            width: 5.w,
+                          ),
+                          Obx(() {
+                            if (controller.imageList.isEmpty) return SizedBox.shrink();
+                            return Text(
+                              controller.formatFileSize(
+                                  controller.imageList[controller
+                                      .currentImageIndex.value].size ?? 0),
+                              style: TextStyle(
+                                color: Colors.white,
+                                fontSize: 16.sp,
+                                fontWeight: FontWeight.w500,
+                              ),
+                            );
+                          }),
+                        ],
+                      ),
+                    ),
+                  ),
+                ),
+              ),
+            ],
+          ),
+        ),
+        IgnorePointer(
+          child: Assets.images.bgHome.image(
+            width: 360.w,
+          ),
+        ),
+      ],
+    );
+  }
+
+  // 轮播图组件
+  Widget _buildImageCarousel() {
+    return Obx(() {
+      if (controller.imageList.isEmpty) {
+        return SizedBox.shrink();
+      }
+
+      return SizedBox(
+        height: 492.h,
+        width: ScreenUtil().screenWidth,
+        child: PageView.builder(
+          controller: PageController(
+            viewportFraction: 1, // 显示部分下一张图片
+            initialPage: controller.currentImageIndex.value,
+          ),
+          onPageChanged: (index) {
+            controller.currentImageIndex.value = index;
+            PhotoManager.clearFileCache();
+          },
+          itemCount: controller.imageList.length,
+          itemBuilder: (context, index) {
+            final asset = controller.imageList[index];
+            return
+              AnimatedPadding(
+                duration: Duration(milliseconds: 300),
+                padding: EdgeInsets.symmetric(
+                  horizontal: 0.w,
+                  vertical:
+                  controller.currentImageIndex.value == index ? 0 : 20.h,
+                ),
+                child:
+                GestureDetector(
+                  // onTap: () => _showImageDetail(asset),
+                  child: Container(
+                    decoration: BoxDecoration(
+                      borderRadius: BorderRadius.circular(12.r),
+                      boxShadow: [
+                        BoxShadow(
+                          color: Colors.black.withOpacity(0.2),
+                          blurRadius: 8,
+                          offset: Offset(0, 4),
+                        ),
+                      ],
+                    ),
+                    child: ClipRRect(
+                      child: FutureBuilder<File?>(
+                        key: ValueKey(asset.id),
+                        future: ImageUtil.getImageFile(asset),
+                        builder: (context, snapshot) {
+                          if (snapshot.hasData && snapshot.data != null) {
+                            return InteractiveViewer(
+                              minScale: 0.5,
+                              maxScale: 3.0,
+                              child: Image.file(
+                                snapshot.data!,
+                                fit: BoxFit.fill,
+                                width: double.infinity,
+                                height: double.infinity,
+                                filterQuality: FilterQuality.high,
+                              ),
+                            );
+                          }
+
+                          return Container(
+                            color: Colors.grey[800],
+                            child: Icon(Icons.error, color: Colors.white60),
+                          );
+                        },
+                      ),
+                    ),
+                  ),
+                ),
+              );
+          },
+        ),
+      );
+    });
+  }
+}

+ 14 - 6
lib/module/privacy/privacy_controller.dart

@@ -3,6 +3,7 @@ import 'dart:math';
 
 
 import 'package:clean/base/base_controller.dart';
 import 'package:clean/base/base_controller.dart';
 import 'package:clean/model/asset_info.dart';
 import 'package:clean/model/asset_info.dart';
+import 'package:clean/module/privacy/privacy_state.dart';
 import 'package:clean/utils/expand.dart';
 import 'package:clean/utils/expand.dart';
 import 'package:clean/utils/file_utils.dart';
 import 'package:clean/utils/file_utils.dart';
 import 'package:clean/utils/image_util.dart';
 import 'package:clean/utils/image_util.dart';
@@ -53,10 +54,10 @@ class PrivacyController extends BaseController {
   // 是否为解锁状态
   // 是否为解锁状态
   late var isUnlock = false.obs;
   late var isUnlock = false.obs;
 
 
-  RxList<AssetInfo> imageList = <AssetInfo>[].obs;
+  // 使用共享状态
+  RxList<AssetInfo>imageList = PrivacyState.imageList;
 
 
-  // 存储所有图片,按月份分组
-  final assetsByMonth = <String, List<AssetInfo>>{}.obs;
+  RxMap<String, List<AssetInfo>> assetsByMonth = PrivacyState.assetsByMonth;
 
 
   // 获取月份数量
   // 获取月份数量
   int get monthCount => assetsByMonth.length;
   int get monthCount => assetsByMonth.length;
@@ -95,8 +96,9 @@ class PrivacyController extends BaseController {
 
 
   // 加载并分组图片
   // 加载并分组图片
   Future<void> loadAssets() async {
   Future<void> loadAssets() async {
-    imageList.value = await FileUtils.getAllAssets();
-    if (imageList.isEmpty) {
+    var newImageList = <AssetInfo>[];
+    newImageList = await FileUtils.getAllAssets();
+    if (newImageList.isEmpty) {
       isEdit.value = false;
       isEdit.value = false;
       return;
       return;
     }
     }
@@ -105,14 +107,20 @@ class PrivacyController extends BaseController {
     assetsByMonth.clear();
     assetsByMonth.clear();
 
 
     // 按月份分组
     // 按月份分组
-    for (var asset in imageList) {
+    for (var asset in newImageList) {
       final monthKey = ImageUtil.getMonthKey(asset.createDateTime);
       final monthKey = ImageUtil.getMonthKey(asset.createDateTime);
       if (!assetsByMonth.containsKey(monthKey)) {
       if (!assetsByMonth.containsKey(monthKey)) {
         assetsByMonth[monthKey] = [];
         assetsByMonth[monthKey] = [];
       }
       }
       assetsByMonth[monthKey]!.add(asset);
       assetsByMonth[monthKey]!.add(asset);
+      asset.dateTitle = ImageUtil.formatMonthKey(monthKey);
     }
     }
 
 
+    newImageList.sort((a, b) => b.createDateTime.compareTo(a.createDateTime));
+
+    PrivacyState.imageList.value = newImageList;
+    PrivacyState.updateMonthlyAssets();
+
     // 对每个月份内的图片按时间排序(新的在前)
     // 对每个月份内的图片按时间排序(新的在前)
     assetsByMonth.forEach((key, list) {
     assetsByMonth.forEach((key, list) {
       list.sort((a, b) => b.createDateTime.compareTo(a.createDateTime));
       list.sort((a, b) => b.createDateTime.compareTo(a.createDateTime));

+ 28 - 0
lib/module/privacy/privacy_state.dart

@@ -0,0 +1,28 @@
+// 创建一个共享状态类
+import 'package:get/get_rx/src/rx_types/rx_types.dart';
+import 'package:intl/intl.dart';
+
+import '../../model/asset_info.dart';
+
+class PrivacyState {
+  static RxList<AssetInfo> imageList = <AssetInfo>[].obs;
+  static RxMap<String, List<AssetInfo>> assetsByMonth = <String, List<AssetInfo>>{}.obs;
+
+  // 删除图片
+  static void removeAsset(String assetId) {
+    imageList.removeWhere((asset) => asset.id == assetId);
+    updateMonthlyAssets();
+  }
+
+  // 更新月份分组
+  static void updateMonthlyAssets() {
+    assetsByMonth.clear();
+    for (var asset in imageList) {
+      final month = DateFormat('yyyy-MM').format(asset.createDateTime);
+      if (!assetsByMonth.containsKey(month)) {
+        assetsByMonth[month] = [];
+      }
+      assetsByMonth[month]!.add(asset);
+    }
+  }
+}

+ 21 - 4
lib/module/privacy/privacy_view.dart

@@ -2,6 +2,8 @@ import 'dart:io';
 
 
 import 'package:clean/model/asset_info.dart';
 import 'package:clean/model/asset_info.dart';
 import 'package:clean/module/privacy/privacy_controller.dart';
 import 'package:clean/module/privacy/privacy_controller.dart';
+import 'package:clean/module/privacy/privacy_state.dart';
+import 'package:clean/router/app_pages.dart';
 import 'package:clean/utils/expand.dart';
 import 'package:clean/utils/expand.dart';
 import 'package:clean/utils/file_utils.dart';
 import 'package:clean/utils/file_utils.dart';
 import 'package:clean/utils/image_util.dart';
 import 'package:clean/utils/image_util.dart';
@@ -267,7 +269,7 @@ class PrivacyPage extends BaseView<PrivacyController> {
                         ),
                         ),
                         Obx(() {
                         Obx(() {
                           return Visibility(
                           return Visibility(
-                            visible: controller.imageList.isNotEmpty,
+                            visible: PrivacyState.imageList.isNotEmpty,
                             child: GestureDetector(
                             child: GestureDetector(
                               onTap: () {
                               onTap: () {
                                 controller.isEdit.value = true;
                                 controller.isEdit.value = true;
@@ -325,7 +327,7 @@ class PrivacyPage extends BaseView<PrivacyController> {
                         ),
                         ),
                         Obx(() {
                         Obx(() {
                           return Visibility(
                           return Visibility(
-                            visible: controller.imageList.isNotEmpty,
+                            visible: PrivacyState.imageList.isNotEmpty,
                             child: GestureDetector(
                             child: GestureDetector(
                               onTap: () {
                               onTap: () {
                                 controller.toggleSelectAll();
                                 controller.toggleSelectAll();
@@ -383,7 +385,7 @@ class PrivacyPage extends BaseView<PrivacyController> {
                   ),
                   ),
                 ],
                 ],
               ),
               ),
-              controller.imageList.isEmpty
+              PrivacyState.imageList.isEmpty
                   ? _buildEmptyPhotoView(context)
                   ? _buildEmptyPhotoView(context)
                   : _buildPhotoView(),
                   : _buildPhotoView(),
             ],
             ],
@@ -568,7 +570,22 @@ class PrivacyPage extends BaseView<PrivacyController> {
   // 构建图片项
   // 构建图片项
   Widget _buildAssetItem(AssetInfo asset) {
   Widget _buildAssetItem(AssetInfo asset) {
     return GestureDetector(
     return GestureDetector(
-      onTap: () => _showImageDetail(asset),
+      onTap: () async {
+        // 获取当前资产在列表中的索引
+        final index = PrivacyState.imageList.indexWhere((item) => item.id == asset.id);
+        if (index != -1) { // 确保找到了索引
+          final result = await Get.toNamed(RoutePath.photoInfo, arguments: {
+            "type": "privacy",
+            "list": PrivacyState.imageList,
+            "index": index,
+          });
+
+          // 检查返回结果并刷新
+          if (result != null && result['deleted'] == true) {
+            controller.loadAssets();
+          }
+        }
+      },
       child: Stack(
       child: Stack(
         children: [
         children: [
           ClipRRect(
           ClipRRect(

+ 5 - 1
lib/router/app_pages.dart

@@ -2,6 +2,7 @@ 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_controller.dart';
 import 'package:clean/module/locations_photo/locations_photo_view.dart';
 import 'package:clean/module/locations_photo/locations_photo_view.dart';
 import 'package:clean/module/main/main_view.dart';
 import 'package:clean/module/main/main_view.dart';
+import 'package:clean/module/photo_info/photo_info_controller.dart';
 import 'package:clean/module/privacy/privacy_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_controller.dart';
 import 'package:clean/module/people_photo/people_photo_view.dart';
 import 'package:clean/module/people_photo/people_photo_view.dart';
@@ -12,6 +13,7 @@ import 'package:get/get_core/src/get_main.dart';
 import 'package:get/get_instance/src/bindings_interface.dart';
 import 'package:get/get_instance/src/bindings_interface.dart';
 
 
 import '../module/main/main_controller.dart';
 import '../module/main/main_controller.dart';
+import '../module/photo_info/photo_info_view.dart';
 import '../module/privacy/privacy_controller.dart';
 import '../module/privacy/privacy_controller.dart';
 
 
 abstract class AppPage {
 abstract class AppPage {
@@ -27,6 +29,7 @@ abstract class RoutePath {
   static const similarPhoto = '/similarPhoto';
   static const similarPhoto = '/similarPhoto';
   static const locationsPhoto = '/locationsPhoto';
   static const locationsPhoto = '/locationsPhoto';
   static const screenshots = '/screenshots';
   static const screenshots = '/screenshots';
+  static const photoInfo = '/photoInfo';
 }
 }
 
 
 class AppBinding extends Bindings {
 class AppBinding extends Bindings {
@@ -39,7 +42,7 @@ class AppBinding extends Bindings {
     lazyPut(() => SimilarPhotoController());
     lazyPut(() => SimilarPhotoController());
     lazyPut(() => LocationsPhotoController());
     lazyPut(() => LocationsPhotoController());
     lazyPut(() => LocationsPhotoPage());
     lazyPut(() => LocationsPhotoPage());
-
+    lazyPut(() => PhotoInfoController());
   }
   }
 
 
   void lazyPut<S>(InstanceBuilderCallback<S> builder) {
   void lazyPut<S>(InstanceBuilderCallback<S> builder) {
@@ -53,4 +56,5 @@ final generalPages = [
   GetPage(name: RoutePath.peoplePhoto, page: () => PeoplePhotoPage()),
   GetPage(name: RoutePath.peoplePhoto, page: () => PeoplePhotoPage()),
   GetPage(name: RoutePath.similarPhoto, page: () => SimilarPhotoPage()),
   GetPage(name: RoutePath.similarPhoto, page: () => SimilarPhotoPage()),
   GetPage(name: RoutePath.locationsPhoto, page: () => LocationsPhotoPage()),
   GetPage(name: RoutePath.locationsPhoto, page: () => LocationsPhotoPage()),
+  GetPage(name: RoutePath.photoInfo, page: () => PhotoInfoPage()),
 ];
 ];

+ 11 - 0
lib/utils/file_utils.dart

@@ -139,11 +139,22 @@ class FileUtils {
   /// 删除 AssetEntity 文件
   /// 删除 AssetEntity 文件
   static Future<bool> deleteAsset(String fileName) async {
   static Future<bool> deleteAsset(String fileName) async {
     try {
     try {
+
       final assetPath = await getAssetPath();
       final assetPath = await getAssetPath();
       final assetFile = File('$assetPath/$fileName.json');
       final assetFile = File('$assetPath/$fileName.json');
       if (await assetFile.exists()) {
       if (await assetFile.exists()) {
         await assetFile.delete();
         await assetFile.delete();
       }
       }
+
+      final assetFileImage = File('$assetPath/$fileName.jpg');
+      if (await assetFileImage.exists()) {
+        await assetFileImage.delete();
+      }
+
+      final assetFileIThumb = File('$assetPath/${fileName}thumb.jpg');
+      if (await assetFileIThumb.exists()) {
+        await assetFileIThumb.delete();
+      }
       return true;
       return true;
     } catch (e) {
     } catch (e) {
       print('删除 AssetEntity 失败: $e');
       print('删除 AssetEntity 失败: $e');