docs(chat): 表情/贴纸/附件面板架构文档(#51~#56)

StickerMessageBubble/AttachmentPanelSheet/EmojiPanel/SendVideoUseCase
完整架构说明:组件设计、CDN路径规则、DI装配、待完成事项。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
pp-bot
2026-03-24 21:18:36 +09:00
parent bb9f1aa956
commit 21b7201590

View File

@@ -0,0 +1,205 @@
# 表情 / 贴纸 / 附件面板架构设计文档
> Gitea Issues: #51#56
> Commit: `bb9f1aa`
> 参考iOS `StickerMessageBubble`, `AttachmentPanelView`, `EmojiKeyboardView`
---
## 一、功能范围
| Issue | 功能 | 状态 |
|-------|------|------|
| #51 | StickerMessageBubbletyp=5无边框 120pt CDN 图) | ✅ |
| #52 | AttachmentPanelSheet6 格面板) | ✅ |
| #53 | 拍照发送image_picker.camera → SendImageUseCase | ✅ |
| #54 | SendVideoUseCasevideo picker → CDN 上传 → typ=4 发送) | ✅ |
| #55 | 文件发送file_picker 占位SnackBar 提示) | ⏳ 待接入 |
| #56 | EmojiPanel4 分类 ~256 emoji光标插入⌫ 退格) | ✅ |
---
## 二、架构概览
```
ChatDetailPage
├── _InputBar
│ ├── [😊] EmojiPanel.show() ← 全屏 DraggableScrollableSheet
│ └── [📎] AttachmentPanelSheet.show() ← BottomSheet Future<AttachmentOption?>
├── _buildBubble(typ)
│ └── case 5 → StickerMessageBubble
└── State handlers
├── _showEmojiPanel() → EmojiPanel (插入 / 退格)
├── _showAttachmentPanel() → dispatch AttachmentOption
├── _pickFromCamera() → image_picker → SendImageUseCase
└── _pickVideo() → image_picker → SendVideoUseCase
```
---
## 三、组件详解
### 3.1 StickerMessageBubbletyp=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<AttachmentOption?> show(BuildContext context) {
return showModalBottomSheet<AttachmentOption>(...);
}
}
```
- `GridView.count(crossAxisCount: 3)`2 行 6 格
- 每格56pt 着色圆形图标 + 标签文字
- 颜色方案:
| 选项 | 色号 |
|------|------|
| 拍照 | #5667FF |
| 相册 | #0BB8A9 |
| 视频 | #FF5FA2 |
| 文件 | #FF8B5E |
| 录音 | #8A5CF6 |
| 红包 | #E8600A |
- 返回 `Future<AttachmentOption?>` — 父页面负责所有业务逻辑,面板本身无状态
### 3.3 EmojiPanel#56
**文件**`features/chat/view/widgets/emoji_panel.dart`
```dart
class EmojiPanel extends StatefulWidget {
static Future<void> 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<void> _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<SendVideoUseCase>((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` |