StickerMessageBubble/AttachmentPanelSheet/EmojiPanel/SendVideoUseCase 完整架构说明:组件设计、CDN路径规则、DI装配、待完成事项。 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5.9 KiB
5.9 KiB
表情 / 贴纸 / 附件面板架构设计文档
Gitea Issues: #51–#56 Commit:
bb9f1aa参考:iOSStickerMessageBubble,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<AttachmentOption?>
│
├── _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
class StickerMessageBubble extends StatelessWidget {
final String rawContent; // {"url":"sticker/xxx.png","width":120,"height":120}
static const double _maxSize = 120.0;
}
rawContentJSON 解析: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
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
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 改动
// 新增两个按钮(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 附件派发流程
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 气泡路由
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
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 |