## 新增 - TypingIndicatorManager: 内存态管理器,精准Timer替代iOS 1s轮询 - TypingInputSender: per-chatId 节流(3s)/防抖(2s),修复iOS跨chat竞态 - WS chat_input/chat_typing 帧处理(mode2 + ctl 双路径) ## UI - ChatDetailPage AppBar 绿色副标题显示"正在输入…" - ChatPage 列表 snippet 绿色输入状态优先于 lastMsg - 群聊不发送 typing 事件(对齐 iOS gate) ## 改进 (vs iOS) - Timer 仅在有 entry 时启动,空时 null(零空转) - per-chatId 隔离节流/防抖(iOS 全局共享有竞态 bug) - msgIdx 守卫防止乱序帧覆盖 lastMsg Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:im_app/app/di/app_providers.dart';
|
||||
import 'package:im_app/app/di/network_provider.dart';
|
||||
import 'package:im_app/core/services/typing_indicator_manager.dart';
|
||||
import 'package:im_app/core/services/typing_input_sender.dart';
|
||||
import 'package:im_app/core/services/ws_message_service.dart';
|
||||
import 'package:im_app/features/chat/di/chat_provider.dart';
|
||||
import 'package:im_app/features/chat/di/message_provider.dart';
|
||||
@@ -20,6 +22,40 @@ import 'package:im_app/features/chat/usecases/send_video_usecase.dart';
|
||||
/// WS 消息服务在 Provider 创建时自动调用 `start()`,
|
||||
/// Provider dispose 时调用 `stop()`,生命周期与 Riverpod 容器绑定。
|
||||
|
||||
// ── TypingIndicatorManager ────────────────────────────────────────────────────
|
||||
|
||||
/// 输入状态管理器 Provider — 全局单例
|
||||
///
|
||||
/// 维护 [chatId → [userId → TypingEntry]] 的内存 Map。
|
||||
/// WS 帧由 [WsMessageService] 转发到此。
|
||||
/// UI 通过 [typingTextProvider] 订阅指定 chat 的输入状态。
|
||||
final typingIndicatorManagerProvider = Provider<TypingIndicatorManager>((ref) {
|
||||
final manager = TypingIndicatorManager();
|
||||
ref.onDispose(manager.dispose);
|
||||
return manager;
|
||||
});
|
||||
|
||||
/// 输入状态全局变化流 — 聊天列表监听此 Provider 触发重建
|
||||
///
|
||||
/// 每次任何 chat 的输入状态变化时 emit,ChatPage 通过 ref.watch 自动重建列表。
|
||||
/// 值无意义(只用作触发器),轻量级且不产生 GC 压力。
|
||||
final typingChangeProvider = StreamProvider<void>((ref) {
|
||||
return ref.watch(typingIndicatorManagerProvider).globalChangeStream;
|
||||
});
|
||||
|
||||
// ── TypingInputSender ────────────────────────────────────────────────────────
|
||||
|
||||
/// 发送侧输入状态 Provider — 3s 节流 + 2s 防抖
|
||||
///
|
||||
/// 由 ChatDetailPage 的 TextField.onChanged 调用。
|
||||
final typingInputSenderProvider = Provider<TypingInputSender>((ref) {
|
||||
final sender = TypingInputSender(
|
||||
socketManager: ref.read(socketManagerProvider),
|
||||
);
|
||||
ref.onDispose(sender.dispose);
|
||||
return sender;
|
||||
});
|
||||
|
||||
// ── WsMessageService ──────────────────────────────────────────────────────────
|
||||
|
||||
/// WS 消息服务 Provider
|
||||
@@ -32,6 +68,7 @@ final wsMessageServiceProvider = Provider<WsMessageService>((ref) {
|
||||
apiClient: ref.read(networkSdkApiProvider),
|
||||
messageRepo: ref.read(messageRepositoryProvider),
|
||||
chatRepo: ref.read(chatRepositoryProvider),
|
||||
typingManager: ref.read(typingIndicatorManagerProvider),
|
||||
);
|
||||
|
||||
service.start();
|
||||
|
||||
Reference in New Issue
Block a user