feat(chat): 表情/贴纸/附件面板全量实现(#51~#56)
## 贴纸(#51)
- `StickerMessageBubble`:typ=5,CDN 图片,120×120pt max,无气泡背景,圆角 8pt
- `_buildBubble()` switch 新增 `case 5:`,isMedia 添加 typ=5
## 附件面板(#52~#55)
- `AttachmentPanelSheet`:6 格 BottomSheet(拍照/相册/视频/文件/录音/红包)
- 返回 `AttachmentOption` enum,由 `_ChatDetailPageState` 分发后续行为
- 拍照(#53):`image_picker.pickImage(camera)` → `SendImageUseCase`
- 视频(#54):`image_picker.pickVideo()` → `SendVideoUseCase`(新建)
- 文件/录音/红包:SnackBar 占位「暂未支持」
- `SendVideoUseCase`:上传 + `jsonEncode({url,thumb,size})` → typ=4
## 表情面板(#56)
- `EmojiPanel`:4 分类(常用/人物/自然/物件),每类 ~64 个 Unicode emoji
- 点击插入到 `TextEditingController` 当前光标位置(支持多码点 emoji)
- ⌫ 退格按钮按 rune 删除(正确处理多码点 emoji)
- `_InputBar` 新增表情按钮(😊)和附件按钮,原 `onAttach` 拆分为 `onAttachPanel` + `onEmoji`
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:networks_sdk/networks_sdk.dart';
|
||||
|
||||
import 'package:im_app/data/remote/upload_file_request.dart';
|
||||
import 'package:im_app/features/chat/usecases/send_message_use_case.dart';
|
||||
|
||||
/// 视频上传并发送消息用例(typ=4,Gitea issue #54)
|
||||
///
|
||||
/// 对应 iOS PhotosPicker 单视频选取 + UploadService + sendMessage(typ=4)
|
||||
///
|
||||
/// ## 流程
|
||||
/// 1. 读取文件大小
|
||||
/// 2. [UploadFileRequest] 上传视频文件到 CDN
|
||||
/// 3. `jsonEncode({"url":url,"thumb":"","size":N})` → [SendMessageUseCase] typ=4
|
||||
///
|
||||
/// ## 说明
|
||||
/// - 缩略图(thumb)当前为空字符串:生成视频首帧需要 `video_thumbnail` 等额外包
|
||||
/// - [VideoMessageBubble] 已有,无需改动
|
||||
class SendVideoUseCase {
|
||||
final NetworksSdkApi _apiClient;
|
||||
final SendMessageUseCase _sendMessage;
|
||||
|
||||
SendVideoUseCase({
|
||||
required NetworksSdkApi apiClient,
|
||||
required SendMessageUseCase sendMessage,
|
||||
}) : _apiClient = apiClient,
|
||||
_sendMessage = sendMessage;
|
||||
|
||||
/// 上传并发送视频消息
|
||||
///
|
||||
/// [filePath]:本地视频文件路径(由 `image_picker.pickVideo()` 返回的 XFile.path)
|
||||
/// [chatId]:目标会话 ID
|
||||
Future<void> execute({
|
||||
required String filePath,
|
||||
required int chatId,
|
||||
int chatType = 1,
|
||||
}) async {
|
||||
// 1. 文件大小
|
||||
final file = File(filePath);
|
||||
int size = 0;
|
||||
try {
|
||||
size = await file.length();
|
||||
} catch (e) {
|
||||
debugPrint('[SendVideoUseCase] 获取文件大小失败: $e');
|
||||
}
|
||||
|
||||
// 2. 上传
|
||||
String uploadedUrl = '';
|
||||
try {
|
||||
final result = await _apiClient.executeRequest(
|
||||
UploadFileRequest(filePath: filePath),
|
||||
);
|
||||
uploadedUrl = result?.url ?? '';
|
||||
} catch (e) {
|
||||
debugPrint('[SendVideoUseCase] upload error: $e');
|
||||
rethrow;
|
||||
}
|
||||
|
||||
if (uploadedUrl.isEmpty) {
|
||||
throw Exception('[SendVideoUseCase] upload returned empty url');
|
||||
}
|
||||
|
||||
// 3. 发送 typ=4 消息
|
||||
final content = jsonEncode({
|
||||
'url': uploadedUrl,
|
||||
'thumb': '', // 视频缩略图(待后续接入 video_thumbnail)
|
||||
'size': size,
|
||||
});
|
||||
|
||||
await _sendMessage.execute(
|
||||
chatId: chatId,
|
||||
content: content,
|
||||
typ: 4,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user