From 21b7201590835595cc517b2b7fd8f1e202a757ff Mon Sep 17 00:00:00 2001 From: pp-bot Date: Tue, 24 Mar 2026 21:18:36 +0900 Subject: [PATCH] =?UTF-8?q?docs(chat):=20=E8=A1=A8=E6=83=85/=E8=B4=B4?= =?UTF-8?q?=E7=BA=B8/=E9=99=84=E4=BB=B6=E9=9D=A2=E6=9D=BF=E6=9E=B6?= =?UTF-8?q?=E6=9E=84=E6=96=87=E6=A1=A3=EF=BC=88#51~#56=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit StickerMessageBubble/AttachmentPanelSheet/EmojiPanel/SendVideoUseCase 完整架构说明:组件设计、CDN路径规则、DI装配、待完成事项。 Co-Authored-By: Claude Sonnet 4.6 --- Doc/emoji_sticker_attachment_architecture.md | 205 +++++++++++++++++++ 1 file changed, 205 insertions(+) create mode 100644 Doc/emoji_sticker_attachment_architecture.md diff --git a/Doc/emoji_sticker_attachment_architecture.md b/Doc/emoji_sticker_attachment_architecture.md new file mode 100644 index 0000000..b80ed51 --- /dev/null +++ b/Doc/emoji_sticker_attachment_architecture.md @@ -0,0 +1,205 @@ +# 表情 / 贴纸 / 附件面板架构设计文档 + +> Gitea Issues: #51–#56 +> Commit: `bb9f1aa` +> 参考:iOS `StickerMessageBubble`, `AttachmentPanelView`, `EmojiKeyboardView` + +--- + +## 一、功能范围 + +| Issue | 功能 | 状态 | +|-------|------|------| +| #51 | StickerMessageBubble(typ=5,无边框 120pt CDN 图) | ✅ | +| #52 | AttachmentPanelSheet(6 格面板) | ✅ | +| #53 | 拍照发送(image_picker.camera → SendImageUseCase) | ✅ | +| #54 | SendVideoUseCase(video picker → CDN 上传 → typ=4 发送) | ✅ | +| #55 | 文件发送(file_picker 占位,SnackBar 提示) | ⏳ 待接入 | +| #56 | EmojiPanel(4 分类 ~256 emoji,光标插入,⌫ 退格) | ✅ | + +--- + +## 二、架构概览 + +``` +ChatDetailPage +├── _InputBar +│ ├── [😊] EmojiPanel.show() ← 全屏 DraggableScrollableSheet +│ └── [📎] AttachmentPanelSheet.show() ← BottomSheet Future +│ +├── _buildBubble(typ) +│ └── case 5 → StickerMessageBubble +│ +└── State handlers + ├── _showEmojiPanel() → EmojiPanel (插入 / 退格) + ├── _showAttachmentPanel() → dispatch AttachmentOption + ├── _pickFromCamera() → image_picker → SendImageUseCase + └── _pickVideo() → image_picker → SendVideoUseCase +``` + +--- + +## 三、组件详解 + +### 3.1 StickerMessageBubble(typ=5) + +**文件**:`features/chat/view/widgets/sticker_message_bubble.dart` + +```dart +class StickerMessageBubble extends StatelessWidget { + final String rawContent; // {"url":"sticker/xxx.png","width":120,"height":120} + static const double _maxSize = 120.0; +} +``` + +- `rawContent` JSON 解析:`url` → `CdnUrlResolver.resolve(url)` → CDN 完整 URL +- 无气泡背景(`BoxDecoration` 不设置颜色) +- 固定 120×120pt `SizedBox`,`Image.network` + `BoxFit.contain` +- `ClipRRect(borderRadius: 8)` 防止圆角溢出 + +**CDN 路径规则**(CdnUrlResolver): + +| 前缀 | 解析结果 | +|------|---------| +| `sticker/` | `AppConfig.apiBaseUrl/sticker/...` | +| `Image/` | `AppConfig.apiBaseUrl/Image/...` | +| `http(s)://` | 原样透传 | + +### 3.2 AttachmentPanelSheet(#52) + +**文件**:`features/chat/view/widgets/attachment_panel_sheet.dart` + +```dart +enum AttachmentOption { camera, gallery, video, file, voice, redEnvelope } + +class AttachmentPanelSheet extends StatelessWidget { + static Future show(BuildContext context) { + return showModalBottomSheet(...); + } +} +``` + +- `GridView.count(crossAxisCount: 3)`,2 行 6 格 +- 每格:56pt 着色圆形图标 + 标签文字 +- 颜色方案: + +| 选项 | 色号 | +|------|------| +| 拍照 | #5667FF | +| 相册 | #0BB8A9 | +| 视频 | #FF5FA2 | +| 文件 | #FF8B5E | +| 录音 | #8A5CF6 | +| 红包 | #E8600A | + +- 返回 `Future` — 父页面负责所有业务逻辑,面板本身无状态 + +### 3.3 EmojiPanel(#56) + +**文件**:`features/chat/view/widgets/emoji_panel.dart` + +```dart +class EmojiPanel extends StatefulWidget { + static Future show(BuildContext context, { + required void Function(String) onEmojiSelected, + required VoidCallback onBackspace, + }) { ... } +} +``` + +- `DraggableScrollableSheet` 从底部展开,高度 40% 屏高 +- 4 个分类 Tab:😊 常用(80)/ 👤 人物(54)/ 🌿 自然(72)/ 🎯 物品(77) +- `GridView.count(crossAxisCount: 8)` 展示 emoji +- 退格键(⌫):`text.runes.toList()` 正确处理多码点 emoji(如 👨‍👩‍👧) +- 光标插入:`TextEditingController.selection.baseOffset.clamp(0, text.length)` + +### 3.4 SendVideoUseCase(#54) + +**文件**:`features/chat/usecases/send_video_usecase.dart` + +``` +video picker (image_picker.pickVideo) + ↓ +File.length() → size (bytes) + ↓ +UploadFileRequest(filePath) → CDN URL + ↓ +SendMessageUseCase(typ: 4, content: {"url":"...","thumb":"","size":N}) +``` + +- `thumb` 当前为空字符串(`video_thumbnail` 包未接入) +- `VideoMessageBubble` 已处理 `thumb` 为空的情况(显示播放图标占位) +- `chatType` 默认 1(单聊) + +--- + +## 四、ChatDetailPage 集成 + +### 4.1 _InputBar 改动 + +```dart +// 新增两个按钮(emoji + attach) +Row(children: [ + IconButton(icon: Icon(Icons.emoji_emotions_outlined), onPressed: onEmoji), + IconButton(icon: Icon(Icons.attach_file), onPressed: onAttachPanel), + Expanded(child: TextField(...)), + IconButton(icon: Icon(Icons.send), onPressed: onSend), +]) +``` + +### 4.2 附件派发流程 + +```dart +Future _showAttachmentPanel() async { + final opt = await AttachmentPanelSheet.show(context); + switch (opt) { + case AttachmentOption.camera: _pickFromCamera(); break; + case AttachmentOption.gallery: _showImagePicker(); break; + case AttachmentOption.video: _pickVideo(); break; + case AttachmentOption.file: // SnackBar: 即将上线 + case AttachmentOption.voice: // SnackBar: 即将上线 + case AttachmentOption.redEnvelope: // SnackBar: 即将上线 + } +} +``` + +### 4.3 typ=5 气泡路由 + +```dart +Widget _buildBubble(int typ, String content, bool isMine) { + switch (typ) { + case 1: return _TextBubble(...) + case 2: return ImageMessageBubble(...) + case 4: return VideoMessageBubble(...) + case 5: return StickerMessageBubble(rawContent: content) // ← #51 + case 6: return FileMessageBubble(...) + ... + } +} +``` + +--- + +## 五、DI 装配 + +**文件**:`features/chat/di/chat_service_providers.dart` + +```dart +final sendVideoUseCaseProvider = Provider((ref) { + return SendVideoUseCase( + apiClient: ref.read(networkSdkApiProvider), + sendMessage: ref.read(sendMessageUseCaseProvider), + ); +}); +``` + +--- + +## 六、待完成事项 + +| Issue | 内容 | 前提条件 | +|-------|------|---------| +| #55 | 文件发送完整实现 | `flutter pub add file_picker` | +| — | 录音发送(VoiceRecordSheet) | 录音 API 对接 | +| — | 附件面板红包入口接入 | RedEnvelope SendView 接入 | +| — | 视频缩略图(thumb) | `flutter pub add video_thumbnail` |