# 图片预览与编辑架构文档 对应 Gitea issues #31–#35 --- ## 1. 背景与目标 iOS 老项目 (`ImageMessageBubble.swift`, `ImageGridBubble.swift`, `ImageProcessor.swift`) 已实现图片收发、全屏查看和编辑。Flutter 新项目目前 `_MessageBubble` 对所有消息类型均渲染为纯文本,需补齐以下能力: | 功能 | Issue | iOS 对应 | |------|-------|---------| | typ=2 图片气泡展示 | #31 | `ImageMessageBubble.swift` | | 全屏查看 + 保存 + 分享 | #32 | `ImageFullscreenView.swift` | | 相册/相机选图 + CDN 上传 | #33 | `ChatView.swift photosPicker` | | 图片编辑(裁剪/旋转) | #34 | `ImageProcessor.swift` + `TOCropViewController` | | 消息气泡 BubbleKind 路由 | #35 | `ChatMessageBubble.swift switch` | --- ## 2. 消息格式 ### typ=2 单张图片 ```json {"url":"Image/xxx.jpg","width":1024,"height":768} ``` - `url`:CDN 相对路径,经 `CdnUrlResolver.resolve()` 转为完整 URL - `width` / `height`:原始像素尺寸,用于计算显示比例 --- ## 3. 架构层次 ``` ┌─────────────────────────────────────────────────────────────────┐ │ UI Layer │ │ ImagePickerSheet → (image_picker + image_cropper) │ │ ImageMessageBubble → ImageViewerPage (photo_view) │ │ _MessageBubble routing (typ switch → MediaBubble) │ ├─────────────────────────────────────────────────────────────────┤ │ UseCase Layer │ │ SendImageUseCase │ │ ├─ readAsBytes() → dart:ui ImageDescriptor (width/height) │ │ ├─ UploadFileRequest (POST /app/api/upload/file) │ │ └─ SendMessageUseCase.execute(typ=2, content=JSON) │ ├─────────────────────────────────────────────────────────────────┤ │ Data Layer │ │ UploadFileRequest → UploadResult.url (CDN path) │ │ CdnUrlResolver.resolve(path) → full URL │ └─────────────────────────────────────────────────────────────────┘ ``` --- ## 4. 关键设计决策 ### 4.1 图片尺寸计算(iOS 对齐) | 参数 | 值 | |------|---| | 最长边上限 | 220 pt | | 最短边下限 | 80 pt | | 宽高比 | 保持原比例 | | 圆角 | 12 pt | ```dart // 伪码 final longest = max(w, h); if (longest > 220) { w *= 220/longest; h *= 220/longest; } final shortest = min(w, h); if (shortest < 80) { w *= 80/shortest; h *= 80/shortest; } ``` ### 4.2 图片压缩策略(iOS ImageProcessor 对齐) | 参数 | 值 | |------|---| | 最大边长 | 1920 px | | JPEG 质量 | 85 | | 方式 | `image_picker` 内置 `maxWidth/maxHeight/imageQuality` | 不引入 `flutter_image_compress`——`image_picker` 的内置参数已满足 iOS 等价逻辑。 ### 4.3 尺寸解析 上传前使用 `dart:ui.ImageDescriptor.encoded(ImmutableBuffer)` 解析压缩后尺寸,零外部依赖。 ### 4.4 CDN 上传(单步 FormData) 沿用现有 `UploadFileRequest`(FormData → `/app/api/upload/file`),返回 `UploadResult.url`(CDN 相对路径)。 > 注意:iOS 使用三步 presign→S3 PUT→finish,Flutter 项目因后端已提供单步上传端点,使用更简单的 FormData 模式。 ### 4.5 全屏查看器(iOS ImageFullscreenView 对齐) | iOS | Flutter | |-----|---------| | `UIScrollView` pinch-to-zoom | `photo_view` PhotoView | | 双击 2.5x | `PhotoView.enableDoubleTapZoom` | | 底部保存/分享 | `image_gallery_saver_plus` + `share_plus` | | 多图滑动 | `PhotoViewGallery` | ### 4.6 图片编辑(iOS TOCropViewController 对齐) `image_cropper` 在 iOS 上也调用 `TOCropViewController`(通过 Plugin Bridge),UI 效果一致。 Flutter 侧无需自建裁剪页,直接调用: ```dart await ImageCropper().cropImage(sourcePath: ..., compressQuality: 85, ...) ``` --- ## 5. 数据流:发送图片 ``` 用户点击附件图标 └─ ImagePickerSheet.show() ├─ 拍照: ImagePicker.pickImage(camera, maxW=1920, q=85) └─ 相册: ImagePicker.pickMultiImage(maxW=1920, q=85, limit=9) ├─ (可选) _PreviewTile 点击 → ImageCropper.cropImage() └─ "发送" → SendImageUseCase.execute(imageFile, chatId) ├─ readAsBytes() → Uint8List ├─ dart:ui.ImageDescriptor → (width, height) ├─ 写临时文件 → UploadFileRequest → UploadResult.url ├─ jsonEncode({"url":url,"width":w,"height":h}) └─ SendMessageUseCase(chatId, content, typ=2) ├─ 乐观写入 MessageRepository (DB Stream → UI) └─ HTTP POST /app/api/chat/send-message ``` ## 6. 数据流:接收/查看图片 ``` WS / HTTP 历史 → DB → MessageRepository Stream └─ messagesByChatIdProvider(chatId) └─ ListView → _MessageBubble(message) └─ message.typ == 2 └─ ImageMessageBubble(rawContent) ├─ parse JSON → url, width, height ├─ CdnUrlResolver.resolve(url) → fullUrl ├─ Image.network(fullUrl, fit: cover) └─ GestureTap → ImageViewerPage(urls: [fullUrl]) └─ PhotoView pinch-to-zoom ├─ 保存 → ImageGallerySaverPlus.saveNetworkImage(url) └─ 分享 → Share.share(url) ``` --- ## 7. 新增文件清单 | 文件 | 说明 | |------|------| | `lib/features/chat/view/widgets/image_message_bubble.dart` | typ=2 气泡(#31) | | `lib/features/chat/view/image_viewer_page.dart` | 全屏查看(#32) | | `lib/features/chat/view/widgets/image_picker_sheet.dart` | 选图底部弹窗(#33) | | `lib/features/chat/usecases/send_image_usecase.dart` | 上传+发送(#33) | ## 8. 修改文件清单 | 文件 | 修改内容 | |------|---------| | `pubspec.yaml` | 新增 photo_view / image_picker / image_cropper / image_gallery_saver_plus / share_plus | | `lib/features/chat/di/chat_service_providers.dart` | 新增 sendImageUseCaseProvider | | `lib/features/chat/view/chat_detail_page.dart` | 输入栏附件按钮 + _MessageBubble typ 路由(#35) | --- ## 9. 待完成 - 多图网格气泡 `ImageGridBubble`(微信 2-9 图 layout,待 typ 确认) - 图片 sticker (typ=5) 展示 - GIF 消息 (typ=25) 支持 - 端到端加密图片(`MediaCrypto` decrypt,待 cipher_guard_sdk 接入)