Преглед изворни кода

[feat]亲密度分析,增加图片预览页

hezihao пре 7 месеци
родитељ
комит
30bde84cf9

BIN
assets/images/icon_image_viewer_close.webp


+ 2 - 0
assets/string/base/string.xml

@@ -288,6 +288,8 @@
     <string name="intimacy_analyse_get_reply">获取回复</string>
     <string name="intimacy_analyse_reply_tone">回复语气:</string>
 
+    <string name="preview">预览</string>
+
     <string name="retry">再试试</string>
     <string name="next_step">下一步</string>
     <string name="recently">最近</string>

+ 24 - 0
lib/data/bean/image_viewer_item.dart

@@ -0,0 +1,24 @@
+/// 图片类型的枚举
+enum ImageType {
+  // 资源图片
+  asset,
+  // 网络图片
+  network,
+  // 本地文件
+  file,
+}
+
+/// 图片预览的模型
+class ImageViewerItem {
+  /// 类型
+  final ImageType type;
+
+  /// 路径
+  final String path;
+
+  ImageViewerItem.asset(this.path) : type = ImageType.asset;
+
+  ImageViewerItem.network(this.path) : type = ImageType.network;
+
+  ImageViewerItem.file(this.path) : type = ImageType.file;
+}

+ 5 - 0
lib/di/get_it.config.dart

@@ -45,6 +45,8 @@ import '../module/character_custom/list/character_custom_list_controller.dart'
 import '../module/feedback/feedback_controller.dart' as _i876;
 import '../module/intimacy_analyse/analyse_report/intimacy_analyse_report_view_controller.dart'
     as _i987;
+import '../module/intimacy_analyse/image_viewer/image_viewer_controller.dart'
+    as _i1071;
 import '../module/intimacy_analyse/intimacy_analyse_controller.dart' as _i977;
 import '../module/intimacy_analyse/intimacy_analyse_upload/intimacy_analyse_upload_controller.dart'
     as _i666;
@@ -115,6 +117,9 @@ extension GetItInjectableX on _i174.GetIt {
     gh.factory<_i415.KeyboardMethodHandler>(
       () => _i415.KeyboardMethodHandler(),
     );
+    gh.factory<_i1071.ImageViewerController>(
+      () => _i1071.ImageViewerController(),
+    );
     gh.singleton<_i361.Dio>(
       () => networkModule.createStreamDio(),
       instanceName: 'streamDio',

+ 99 - 0
lib/module/intimacy_analyse/image_viewer/image_viewer_controller.dart

@@ -0,0 +1,99 @@
+import 'dart:io';
+
+import 'package:cached_network_image/cached_network_image.dart';
+import 'package:flutter/cupertino.dart';
+import 'package:get/get.dart';
+import 'package:injectable/injectable.dart';
+import 'package:keyboard/base/base_controller.dart';
+import 'package:photo_view/photo_view.dart';
+
+import '../../../data/bean/image_viewer_item.dart';
+import '../../../router/app_page_arguments.dart';
+import '../../../utils/atmob_log.dart';
+
+/// 图片预览控制器
+@injectable
+class ImageViewerController extends BaseController {
+  final String _tag = "ImageViewerController";
+
+  /// 要预览的图片列表
+  RxList<ImageViewerItem> imageViewerItemList = <ImageViewerItem>[].obs;
+
+  /// 当前的查看的图片的索引
+  Rx<int> currentIndex = 0.obs;
+
+  late PageController pageController;
+
+  /// 缓存每一页的PhotoViewController
+  final Map<int, PhotoViewController> photoViewControllers = {};
+
+  @override
+  void onInit() {
+    super.onInit();
+    _initArgs();
+  }
+
+  @override
+  void onClose() {
+    // 销毁每个PhotoViewController
+    pageController.dispose();
+    for (var controller in photoViewControllers.values) {
+      controller.dispose();
+    }
+    super.onClose();
+  }
+
+  /// 初始化参数
+  void _initArgs() {
+    final arguments = Get.arguments as Map<String, dynamic>?;
+
+    // 图片预览列表
+    if (arguments?[AppPageArguments.imageViewerItemList] == null) {
+      AtmobLog.i(_tag, '没有传递 imageViewerItemList 参数');
+    } else {
+      final List<ImageViewerItem>? argumentList =
+          arguments?[AppPageArguments.imageViewerItemList]
+              as List<ImageViewerItem>?;
+      if (argumentList != null) {
+        imageViewerItemList.assignAll(argumentList);
+        AtmobLog.i(_tag, "imageViewerItemList: $imageViewerItemList");
+      }
+    }
+
+    // 当前索引
+    if (arguments?[AppPageArguments.index] == null) {
+      AtmobLog.i(_tag, '没有传递 index 参数');
+    } else {
+      final int? argumentIndex = arguments?[AppPageArguments.index] as int?;
+      if (argumentIndex != null) {
+        currentIndex.value = argumentIndex;
+        AtmobLog.i(_tag, "index: $currentIndex");
+      }
+    }
+
+    // 创建PageController,并设置当前页的索引
+    pageController = PageController(initialPage: currentIndex.value);
+  }
+
+  /// 返回
+  void clickBack() {
+    Get.back();
+  }
+
+  /// 更新当前索引
+  void updateCurrentIndex(int newIndex) {
+    currentIndex.value = newIndex;
+  }
+
+  /// 获取对应类型的图片ImageProvider
+  ImageProvider getImageProvider(ImageViewerItem item) {
+    switch (item.type) {
+      case ImageType.asset:
+        return AssetImage(item.path);
+      case ImageType.network:
+        return CachedNetworkImageProvider(item.path);
+      case ImageType.file:
+        return FileImage(File(item.path));
+    }
+  }
+}

+ 185 - 0
lib/module/intimacy_analyse/image_viewer/image_viewer_page.dart

@@ -0,0 +1,185 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:get/get.dart';
+import 'package:keyboard/base/base_page.dart';
+import 'package:keyboard/data/bean/image_viewer_item.dart';
+import 'package:keyboard/resource/colors.gen.dart';
+import 'package:keyboard/resource/string.gen.dart';
+import 'package:keyboard/router/app_page_arguments.dart';
+import 'package:keyboard/router/app_pages.dart';
+import 'package:photo_view/photo_view.dart';
+
+import '../../../resource/assets.gen.dart';
+import 'image_viewer_controller.dart';
+
+/// 图片预览页
+class ImageViewerPage extends BasePage<ImageViewerController> {
+  const ImageViewerPage({super.key});
+
+  /// 跳转
+  /// [imageViewerItemList] 图片预览数据列表
+  /// [index] 当前预览的索引,默认为0
+  static void start(
+    List<ImageViewerItem> imageViewerItemList, {
+    int index = 0,
+  }) {
+    Map<String, dynamic> args = {
+      AppPageArguments.imageViewerItemList: imageViewerItemList,
+      AppPageArguments.index: index,
+    };
+    Get.toNamed(RoutePath.imageViewer, arguments: args);
+  }
+
+  @override
+  backgroundColor() => ColorName.black;
+
+  @override
+  immersive() {
+    // 沉浸式页面
+    return true;
+  }
+
+  @override
+  bool statusBarDarkFont() {
+    // 状态栏,白色字体
+    return false;
+  }
+
+  @override
+  Widget buildBody(BuildContext context) {
+    return Scaffold(
+      body: Stack(
+        children: [
+          // 预览图
+          Obx(() {
+            return PageView.builder(
+              controller: controller.pageController,
+              itemCount: controller.imageViewerItemList.length,
+              onPageChanged: (index) {
+                // 更新当前索引
+                controller.updateCurrentIndex(index);
+              },
+              itemBuilder: (context, index) {
+                // 构建每一个PhotoView
+                return _buildPhotoView(
+                  controller.imageViewerItemList[index],
+                  index,
+                );
+              },
+            );
+          }),
+          // 指示器
+          Visibility(
+            visible: false,
+            child: Positioned(
+              left: 0,
+              right: 0,
+              bottom: 40.h,
+              child: Obx(() {
+                return _buildIndicator();
+              }),
+            ),
+          ),
+          // 标题栏
+          Positioned(
+            left: 0,
+            top: 0,
+            right: 0,
+            child: Column(children: [_buildStatusBar(), _buildTitleBar()]),
+          ),
+        ],
+      ),
+    );
+  }
+
+  /// 指示器
+  Widget _buildIndicator() {
+    return Center(
+      child: Row(
+        mainAxisSize: MainAxisSize.min,
+        children: [
+          Text(
+            '${controller.currentIndex.value + 1}/${controller.imageViewerItemList.length}',
+            style: TextStyle(
+              color: Colors.white,
+              fontSize: 18.sp,
+              fontWeight: FontWeight.w500,
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+
+  /// 状态栏占位
+  Widget _buildStatusBar() {
+    double statusBarHeight = MediaQuery.of(Get.context!).padding.top;
+    return Container(
+      // 导航栏高度
+      height: statusBarHeight,
+      color: backgroundColor(),
+    );
+  }
+
+  /// 标题栏
+  Widget _buildTitleBar() {
+    return Container(
+      height: kToolbarHeight,
+      // 宽度,匹配父组件
+      width: double.infinity,
+      color: ColorName.black,
+      padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 14.0),
+      child: Stack(
+        children: [
+          // 返回按钮
+          GestureDetector(
+            onTap: controller.clickBack,
+            child: Assets.images.iconImageViewerClose.image(
+              width: 24.w,
+              height: 24.h,
+            ),
+          ),
+          // 标题
+          Positioned(
+            left: 0,
+            top: 0,
+            right: 0,
+            child: Container(
+              alignment: Alignment.center,
+              child: Text(
+                StringName.preview,
+                style: TextStyle(
+                  fontSize: 16.sp,
+                  fontWeight: FontWeight.w500,
+                  color: ColorName.white,
+                ),
+              ),
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+
+  /// 每一页的PhotoView
+  Widget _buildPhotoView(ImageViewerItem item, int index) {
+    final photoViewController = controller.photoViewControllers.putIfAbsent(
+      index,
+      () => PhotoViewController(),
+    );
+
+    return PhotoView(
+      // 设置图片ImageProvider
+      imageProvider: controller.getImageProvider(item),
+      controller: photoViewController,
+      minScale: PhotoViewComputedScale.contained,
+      maxScale: PhotoViewComputedScale.covered * 2,
+      // 禁止旋转
+      enableRotation: false,
+      // 背景色
+      backgroundDecoration: BoxDecoration(color: ColorName.black),
+      // 加载中
+      loadingBuilder: (_, __) => Center(child: CircularProgressIndicator()),
+    );
+  }
+}

+ 2 - 1
lib/module/intimacy_analyse/intimacy_analyse_upload/intimacy_analyse_upload_page.dart

@@ -14,6 +14,7 @@ import 'package:wechat_assets_picker/wechat_assets_picker.dart';
 import '../../../resource/assets.gen.dart';
 import '../../../router/app_page_arguments.dart';
 import '../../../router/app_pages.dart';
+import '../../../utils/fake_image_util.dart';
 import '../../../utils/string_format_util.dart';
 import '../../../widget/actionbtn/action_btn.dart';
 import '../../../widget/gradient_text.dart';
@@ -225,7 +226,7 @@ class IntimacyAnalyseUploadPage
             ),
             child: UploadNineGrid(
               mode: Mode.preview,
-              imageSrcList: ["", "", "", "", "", "", ""],
+              imageSrcList: [...FakeImageUtil.getImageUrlList()],
               maxCount: 9,
               spacing: 8.0,
             ),

+ 21 - 13
lib/module/intimacy_analyse/intimacy_analyse_upload/widget/upload_item_widget.dart

@@ -8,6 +8,7 @@ import 'package:keyboard/resource/assets.gen.dart';
 
 import '../../../../resource/colors.gen.dart';
 import '../../../../resource/string.gen.dart';
+import '../../../../utils/prefix_util.dart';
 import '../../../../widget/rotate_image.dart';
 
 /// 上传状态
@@ -63,12 +64,16 @@ class UploadItemWidget extends StatelessWidget {
     return GestureDetector(
       onTap: onClickItemCallback,
       child: Container(
-        decoration: BoxDecoration(borderRadius: BorderRadius.circular(12.5.r)),
+        // decoration: BoxDecoration(borderRadius: BorderRadius.circular(12.5.r)),
         child: Stack(
           alignment: Alignment.center,
           children: [
             // 图片
-            _buildImage(),
+            ClipRRect(
+              // 图片裁切成圆角
+              borderRadius: BorderRadius.circular(12.5.r),
+              child: _buildImage(),
+            ),
             // 上传状态的遮罩层
             _buildMaskByUploadStatus(uploadState),
             // 删除按钮
@@ -82,22 +87,25 @@ class UploadItemWidget extends StatelessWidget {
     );
   }
 
-  /// 判断是否是远程图片
-  bool _isRemoteImage(String imageSrc) {
-    if (imageSrc.isEmpty) {
-      return false;
-    }
-    return imageSrc.startsWith('http') || imageSrc.startsWith('https');
-  }
-
   /// 图片
   Widget _buildImage() {
     // 网络图片
-    if (_isRemoteImage(imageSrc)) {
-      return CachedNetworkImage(imageUrl: imageSrc);
+    if (PrefixUtil.isRemoteImage(imageSrc)) {
+      return CachedNetworkImage(
+        imageUrl: imageSrc,
+        // 宽高铺满父组件
+        height: double.infinity,
+        width: double.infinity,
+        fit: BoxFit.cover,
+      );
     } else {
       // 本地图片
-      return Image.file(File(imageSrc), fit: BoxFit.cover);
+      return Image.file(
+        File(imageSrc),
+        height: double.infinity,
+        width: double.infinity,
+        fit: BoxFit.cover,
+      );
     }
   }
 

+ 16 - 3
lib/module/intimacy_analyse/intimacy_analyse_upload/widget/upload_nine_grid.dart

@@ -1,6 +1,10 @@
 import 'package:flutter/cupertino.dart';
+import 'package:keyboard/data/bean/image_viewer_item.dart';
 import 'package:keyboard/module/intimacy_analyse/intimacy_analyse_upload/widget/upload_add_widget.dart';
 import 'package:keyboard/module/intimacy_analyse/intimacy_analyse_upload/widget/upload_item_widget.dart';
+import 'package:keyboard/utils/prefix_util.dart';
+
+import '../../image_viewer/image_viewer_page.dart';
 
 /// 模式
 enum Mode {
@@ -67,7 +71,7 @@ class UploadNineGrid extends StatelessWidget {
         } else {
           // 上传项
           String src = imageSrcList[index];
-          return _buildUploadItem(src);
+          return _buildUploadItem(src, index);
         }
       },
     );
@@ -96,16 +100,25 @@ class UploadNineGrid extends StatelessWidget {
   }
 
   /// 上传项
-  Widget _buildUploadItem(String imageSrc) {
+  Widget _buildUploadItem(String imageSrc, int index) {
     return UploadItemWidget(
       imageSrc: imageSrc,
       hasDeleteBtn: true,
-      uploadState: UploadState.fail,
+      uploadState: UploadState.success,
       onClickDeleteCallback: () {
         // 删除图片
       },
       onClickItemCallback: () {
         // 预览图片
+        ImageViewerPage.start(
+          imageSrcList.map((src) {
+            if (PrefixUtil.isRemoteImage(src)) {
+              return ImageViewerItem.network(src);
+            }
+            return ImageViewerItem.file(src);
+          }).toList(),
+          index: index,
+        );
       },
     );
   }

+ 2 - 1
lib/module/intimacy_analyse/screenshot_reply/conversation_analysis/conversation_analysis_view.dart

@@ -11,6 +11,7 @@ import '../../../../data/bean/option_select_item.dart';
 import '../../../../resource/assets.gen.dart';
 import '../../../../resource/colors.gen.dart';
 import '../../../../resource/string.gen.dart';
+import '../../../../utils/fake_image_util.dart';
 import '../../intimacy_analyse_upload/widget/upload_nine_grid.dart';
 import '../../widget/option_select_widget.dart';
 import '../../widget/step/upload_step_card.dart';
@@ -200,7 +201,7 @@ class ConversationAnalysisView
             ),
             child: UploadNineGrid(
               mode: Mode.preview,
-              imageSrcList: ["", "", "", "", "", "", ""],
+              imageSrcList: [...FakeImageUtil.getImageUrlList()],
               maxCount: 9,
               spacing: 8.0,
             ),

+ 2 - 1
lib/module/intimacy_analyse/widget/step/upload_step_card.dart

@@ -3,6 +3,7 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
 
 import '../../../../resource/assets.gen.dart';
 import '../../../../resource/colors.gen.dart';
+import '../../../../utils/fake_image_util.dart';
 import '../../intimacy_analyse_upload/widget/upload_add_widget.dart';
 import '../../intimacy_analyse_upload/widget/upload_nine_grid.dart';
 import '../step_card.dart';
@@ -56,7 +57,7 @@ class UploadStepCard extends StatelessWidget {
             // 图片九宫格
             child: UploadNineGrid(
               mode: Mode.edit,
-              imageSrcList: ["", "", "", "", "", "", ""],
+              imageSrcList: [...FakeImageUtil.getImageUrlList()],
               maxCount: 9,
               spacing: 8.0,
               onClickAddCallback: onClickAddCallback,

+ 5 - 0
lib/resource/assets.gen.dart

@@ -500,6 +500,10 @@ class $AssetsImagesGen {
   AssetGenImage get iconGoodsInfoTitle =>
       const AssetGenImage('assets/images/icon_goods_info_title.webp');
 
+  /// File path: assets/images/icon_image_viewer_close.webp
+  AssetGenImage get iconImageViewerClose =>
+      const AssetGenImage('assets/images/icon_image_viewer_close.webp');
+
   /// File path: assets/images/icon_intimacy_analyse_arrow.webp
   AssetGenImage get iconIntimacyAnalyseArrow =>
       const AssetGenImage('assets/images/icon_intimacy_analyse_arrow.webp');
@@ -1030,6 +1034,7 @@ class $AssetsImagesGen {
     iconEmojiPercent,
     iconGemini,
     iconGoodsInfoTitle,
+    iconImageViewerClose,
     iconIntimacyAnalyseArrow,
     iconIntimacyAnalyseLove,
     iconIntimacyAnalyseReportOverviewLove,

+ 2 - 0
lib/resource/string.gen.dart

@@ -203,6 +203,7 @@ class StringName {
   static final String intimacyAnalyseReUpload = 'intimacy_analyse_re_upload'.tr; // 重新上传
   static final String intimacyAnalyseGetReply = 'intimacy_analyse_get_reply'.tr; // 获取回复
   static final String intimacyAnalyseReplyTone = 'intimacy_analyse_reply_tone'.tr; // 回复语气:
+  static final String preview = 'preview'.tr; // 预览
   static final String retry = 'retry'.tr; // 再试试
   static final String nextStep = 'next_step'.tr; // 下一步
   static final String recently = 'recently'.tr; // 最近
@@ -414,6 +415,7 @@ class StringMultiSource {
       'intimacy_analyse_re_upload': '重新上传',
       'intimacy_analyse_get_reply': '获取回复',
       'intimacy_analyse_reply_tone': '回复语气:',
+      'preview': '预览',
       'retry': '再试试',
       'next_step': '下一步',
       'recently': '最近',

+ 6 - 0
lib/router/app_page_arguments.dart

@@ -2,4 +2,10 @@
 abstract class AppPageArguments {
   /// 选择的图片资源列表
   static const selectedAssetList = "selectedAssetList";
+
+  /// 图片预览列表(支持本地图片和网络图片)
+  static const imageViewerItemList = "imageViewerItemList";
+
+  /// 索引
+  static const index = "index";
 }

+ 14 - 4
lib/router/app_pages.dart

@@ -37,6 +37,8 @@ import '../module/character_custom/detail/character_custom_detail_controller.dar
 import '../module/character_custom/list/character_custom_list_page.dart';
 import '../module/feedback/feedback_page.dart';
 import '../module/intimacy_analyse/analyse_report/intimacy_analyse_report_view_controller.dart';
+import '../module/intimacy_analyse/image_viewer/image_viewer_controller.dart';
+import '../module/intimacy_analyse/image_viewer/image_viewer_page.dart';
 import '../module/intimacy_analyse/intimacy_analyse_upload/intimacy_analyse_upload_controller.dart';
 import '../module/intimacy_analyse/intimacy_analyse_upload/intimacy_analyse_upload_page.dart';
 import '../module/intimacy_analyse/screenshot_reply/conversation_analysis/conversation_analysis_controller.dart';
@@ -84,9 +86,11 @@ abstract class RoutePath {
   static const changeNickname = '/changeNickname';
   static const changeGender = '/changeGender';
   static const changeBirthday = '/changeBirthday';
-  static const changeHobbies ='/changeHobbies';
+  static const changeHobbies = '/changeHobbies';
   static const changeCharacters = '/changeCharacters';
 
+  // 图片预览页
+  static const imageViewer = '/imageViewerPage';
 }
 
 class AppBinding extends Bindings {
@@ -120,9 +124,9 @@ class AppBinding extends Bindings {
     lazyPut(() => getIt.get<ChangeBirthdayController>());
     lazyPut(() => getIt.get<ConversationAnalysisController>());
     lazyPut(() => getIt.get<ScanImageReplyController>());
-    lazyPut(()=>getIt.get<ChangeHobbiesController>());
+    lazyPut(() => getIt.get<ChangeHobbiesController>());
     lazyPut(() => getIt.get<ChangeCharacterController>());
-
+    lazyPut(() => getIt.get<ImageViewerController>());
   }
 
   void lazyPut<S>(InstanceBuilderCallback<S> builder) {
@@ -154,11 +158,17 @@ final generalPages = [
   // 亲密度报告页
   GetPage(name: RoutePath.intimacyAnalyse, page: () => IntimacyAnalysePage()),
   // 亲密度分析上传页
-  GetPage(name: RoutePath.intimacyAnalyseUpload, page: () => IntimacyAnalyseUploadPage()),
+  GetPage(
+    name: RoutePath.intimacyAnalyseUpload,
+    page: () => IntimacyAnalyseUploadPage(),
+  ),
 
   GetPage(name: RoutePath.changeNickname, page: () => ChangeNicknamePage()),
   GetPage(name: RoutePath.changeGender, page: () => ChangeGenderPage()),
   GetPage(name: RoutePath.changeBirthday, page: () => ChangeBirthdayPage()),
   GetPage(name: RoutePath.changeHobbies, page: () => ChangeHobbiesPage()),
   GetPage(name: RoutePath.changeCharacters, page: () => ChangeCharacterPage()),
+
+  // 图片预览页
+  GetPage(name: RoutePath.imageViewer, page: () => ImageViewerPage()),
 ];

+ 12 - 0
lib/utils/fake_image_util.dart

@@ -0,0 +1,12 @@
+class FakeImageUtil {
+  /// 测试图片列表
+  static List<String> getImageUrlList() {
+    return [
+      "https://img0.baidu.com/it/u=4227008132,2843866888&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=1084",
+      "https://wx3.sinaimg.cn/mw690/88e90961ly1hwvqdknjo4j20u0140tav.jpg",
+      "https://img0.baidu.com/it/u=600722015,3838115472&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=750",
+      "https://pic.rmb.bdstatic.com/bjh/news/6792ab1e35c6a2a6cd10a5990bd033d0.png",
+      "http://t15.baidu.com/it/u=3292075640,1695839085&fm=224&app=112&f=JPEG?w=333&h=500"
+    ];
+  }
+}

+ 10 - 0
lib/utils/prefix_util.dart

@@ -0,0 +1,10 @@
+/// 前缀判断的工具类
+class PrefixUtil {
+  /// 判断是否是远程图片
+  static bool isRemoteImage(String imageSrc) {
+    if (imageSrc.isEmpty) {
+      return false;
+    }
+    return imageSrc.startsWith('http') || imageSrc.startsWith('https');
+  }
+}

+ 8 - 0
pubspec.lock

@@ -1083,6 +1083,14 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "2.2.0"
+  photo_view:
+    dependency: "direct main"
+    description:
+      name: photo_view
+      sha256: "1fc3d970a91295fbd1364296575f854c9863f225505c28c46e0a03e48960c75e"
+      url: "https://pub.dev"
+    source: hosted
+    version: "0.15.0"
   platform:
     dependency: transitive
     description:

+ 3 - 0
pubspec.yaml

@@ -62,6 +62,9 @@ dependencies:
   # 图片选择器
   wechat_assets_picker: ^9.5.0
 
+  # 图片预览
+  photo_view: ^0.15.0
+
   collection: ^1.19.1
 
   #虚线