feat(chat): 发收消息全量实现 (#25~#28)
- 移除 @riverpod/@freezed 注解依赖,全部改为手写 Provider(无需 build_runner) · LoginState 改为纯 Dart,LoginViewModel/ThemeViewModel/ChatViewModel 改为 Notifier · UserNotifier 改为 FamilyAsyncNotifier<User?,int>,mini_app_provider 改为手写 Provider · 15 个 StreamProvider/StreamProvider.family 从 @riverpod 迁移至手写 - 发送消息(#25) · SendMessageRequest/SendMessageResponse DTO · SendMessageUseCase:乐观写入 DB → HTTP POST → 更新 Chat 摘要 - 接收消息 WS(#26) · WsMessageService:监听 mode2 WS 帧 → HTTP 补拉 → DB 写入 → Chat 更新 · FetchHistoryRequest/FetchHistoryResponse DTO(GET /app/api/chat/history) · FetchHistoryUseCase:拉取 → insertOrReplaceAll - DI 装配(chat_service_providers.dart) · wsMessageServiceProvider、sendMessageUseCaseProvider、fetchHistoryUseCaseProvider - 聊天列表页(#27) · ChatListViewModel(Notifier<void>)+ chat_page.dart 真实会话列表 UI · ListTile:头像首字母、最新消息摘要、未读角标、时间格式化 - 聊天详情页(#28) · ChatDetailViewModel(FamilyNotifier<ChatDetailState,int>)+ chat_detail_page.dart · 消息气泡(自己/他人分左右)、底部输入框、发送状态与错误提示 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
import 'package:networks_sdk/networks_sdk.dart';
|
||||
|
||||
import 'package:im_app/data/remote/fetch_history_request.dart';
|
||||
import 'package:im_app/domain/repositories/message_repository.dart';
|
||||
|
||||
/// 拉取消息历史用例
|
||||
///
|
||||
/// 调用 HTTP GET `/app/api/chat/history`,将消息写入本地 DB。
|
||||
/// DB Stream → StreamProvider → UI 自动重建。
|
||||
///
|
||||
/// ## 分页
|
||||
///
|
||||
/// [chatIdx] 为锚点消息 index:
|
||||
/// - 0 → 拉最新 [limit] 条
|
||||
/// - N → 拉 idx < N 的 [limit] 条(向上翻页)
|
||||
///
|
||||
/// 返回拉取到的消息数,0 表示已到头或网络失败。
|
||||
class FetchHistoryUseCase {
|
||||
final NetworksSdkApi _apiClient;
|
||||
final MessageRepository _messageRepo;
|
||||
|
||||
FetchHistoryUseCase({
|
||||
required NetworksSdkApi apiClient,
|
||||
required MessageRepository messageRepo,
|
||||
}) : _apiClient = apiClient,
|
||||
_messageRepo = messageRepo;
|
||||
|
||||
Future<int> execute({
|
||||
required int chatId,
|
||||
int chatIdx = 0,
|
||||
int limit = 20,
|
||||
}) async {
|
||||
final response = await _apiClient.executeRequest(
|
||||
FetchHistoryRequest(chatId: chatId, chatIdx: chatIdx, limit: limit),
|
||||
);
|
||||
if (response == null || response.messages.isEmpty) return 0;
|
||||
|
||||
final entities = response.messages.map((m) => m.toEntity()).toList();
|
||||
await _messageRepo.insertOrReplaceAll(entities);
|
||||
return entities.length;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:networks_sdk/networks_sdk.dart';
|
||||
|
||||
import 'package:im_app/data/remote/send_message_request.dart';
|
||||
import 'package:im_app/domain/entities/message.dart';
|
||||
import 'package:im_app/domain/repositories/chat_repository.dart';
|
||||
import 'package:im_app/domain/repositories/message_repository.dart';
|
||||
|
||||
/// 发送文本消息用例
|
||||
///
|
||||
/// ## 执行流程
|
||||
/// 1. 乐观写入本地 DB(临时消息,id=0)→ UI 立即刷新
|
||||
/// 2. HTTP POST `/app/api/chat/send-message` → 获取服务端 messageId / chatIdx
|
||||
/// 3. 更新 ChatRepository 的 lastMsg / lastTyp / lastTime
|
||||
///
|
||||
/// DB Stream → StreamProvider → UI 自动重建,无需额外通知。
|
||||
class SendMessageUseCase {
|
||||
final NetworksSdkApi _apiClient;
|
||||
final MessageRepository _messageRepo;
|
||||
final ChatRepository _chatRepo;
|
||||
final int currentUid;
|
||||
|
||||
SendMessageUseCase({
|
||||
required NetworksSdkApi apiClient,
|
||||
required MessageRepository messageRepo,
|
||||
required ChatRepository chatRepo,
|
||||
required this.currentUid,
|
||||
}) : _apiClient = apiClient,
|
||||
_messageRepo = messageRepo,
|
||||
_chatRepo = chatRepo;
|
||||
|
||||
Future<void> execute({
|
||||
required int chatId,
|
||||
required String content,
|
||||
int typ = 1,
|
||||
}) async {
|
||||
final sendTime = DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
||||
|
||||
// 1. 乐观本地写入
|
||||
await _messageRepo.insertOrReplace(
|
||||
Message(
|
||||
id: 0,
|
||||
chatId: chatId,
|
||||
sendId: currentUid,
|
||||
content: content,
|
||||
typ: typ,
|
||||
sendTime: sendTime,
|
||||
),
|
||||
);
|
||||
|
||||
// 2. HTTP 发送
|
||||
SendMessageResponse? resp;
|
||||
try {
|
||||
resp = await _apiClient.executeRequest(
|
||||
SendMessageRequest(
|
||||
chatId: chatId,
|
||||
content: content,
|
||||
typ: typ,
|
||||
sendTime: sendTime,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('[SendMessageUseCase] HTTP error: $e');
|
||||
}
|
||||
|
||||
// 3. 更新 Chat 摘要
|
||||
try {
|
||||
final chat = await _chatRepo.getChat(chatId);
|
||||
if (chat != null) {
|
||||
await _chatRepo.updateChat(
|
||||
chat.copyWith(
|
||||
lastMsg: content,
|
||||
lastTyp: typ,
|
||||
lastTime: sendTime,
|
||||
msgIdx: resp?.chatIdx ?? chat.msgIdx,
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('[SendMessageUseCase] updateChat error: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user