Files
customer-im-client-dev/Doc/emoji_sticker_attachment_architecture.md
pp-bot 21b7201590 docs(chat): 表情/贴纸/附件面板架构文档(#51~#56)
StickerMessageBubble/AttachmentPanelSheet/EmojiPanel/SendVideoUseCase
完整架构说明:组件设计、CDN路径规则、DI装配、待完成事项。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 21:18:36 +09:00

206 lines
5.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 表情 / 贴纸 / 附件面板架构设计文档
> 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` |