Some checks failed
CI / Lint (push) Has been cancelled
## 新增 - 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>
106 lines
4.4 KiB
Dart
106 lines
4.4 KiB
Dart
/// 应用路由枚举
|
||
///
|
||
/// 每个枚举值对应一个注册路由及其绝对路径。
|
||
///
|
||
/// ## 为什么用枚举而不是常量类
|
||
///
|
||
/// `auth_guard.dart` 对路由做 switch 分析,Dart 的枚举 switch 是穷举的:
|
||
/// 新增路由时若没在 switch 里补 case,编译器直接报错,而不是等运行时漏掉。
|
||
///
|
||
/// ## 使用方式
|
||
///
|
||
/// ```dart
|
||
/// // 无参数导航
|
||
/// context.push(AppRouteName.settingsTheme.path);
|
||
/// context.go(AppRouteName.chat.path);
|
||
///
|
||
/// // 带参数导航(extra 传对象,适合列表点入详情等已有数据的场景)
|
||
/// context.push(
|
||
/// AppRouteName.chatDetail.path,
|
||
/// extra: (conversationId: '42', title: '技术支持', chatType: 1),
|
||
/// );
|
||
///
|
||
/// // 带路径参数导航(路径中内嵌 id,适合需要直接链接或分享的场景)
|
||
/// context.push(AppRouteName.chatDetailByIdPath('99'));
|
||
///
|
||
/// // 路由表定义
|
||
/// GoRoute(path: AppRouteName.chat.path, ...)
|
||
/// ```
|
||
///
|
||
/// ## 注意:子路由 path 是相对路径片段
|
||
///
|
||
/// go_router 在子路由中使用相对路径片段(不含父路径前缀),
|
||
/// 这是框架规定,不是硬编码字符串:
|
||
/// ```dart
|
||
/// GoRoute(
|
||
/// path: AppRouteName.settings.path, // '/settings'
|
||
/// routes: [
|
||
/// GoRoute(path: AppRouteName.settingsTheme.segment, ...), // 'theme'
|
||
/// ],
|
||
/// )
|
||
/// ```
|
||
/// 导航时仍用 `AppRouteName.settingsTheme.path`,与枚举保持一致。
|
||
///
|
||
/// 子路由声明使用 [segment](相对路径片段),避免在路由表中硬编码字符串:
|
||
/// ```dart
|
||
/// GoRoute(path: AppRouteName.settingsTheme.segment, ...) // 'theme'
|
||
/// ```
|
||
///
|
||
/// ## 注意:含路径参数的路由
|
||
///
|
||
/// [chatDetailById] 的 [path] 包含占位符 `:id`,不能直接用于导航。
|
||
/// 导航时使用 [chatDetailByIdPath] 传入实际 id:
|
||
/// ```dart
|
||
/// context.push(AppRouteName.chatDetailByIdPath('99'));
|
||
/// ```
|
||
enum AppRouteName {
|
||
// ── Tab 根路由 ────────────────────────────────────────────────────────────
|
||
chat('/chat'),
|
||
contact('/contact'),
|
||
settings('/settings'),
|
||
|
||
// ── Chat 子路由 ──────────────────────────────────────────────────────────
|
||
// extra: ({String conversationId, String title, int chatType})
|
||
chatDetail('/chat/detail'),
|
||
// 路径参数形式:导航用 AppRouteName.chatDetailByIdPath(id),不直接用 .path
|
||
chatDetailById('/chat/:id'),
|
||
chatDBTest('/chat/dbTest'),
|
||
|
||
// ── Settings 子路由 ───────────────────────────────────────────────────────
|
||
settingsTheme('/settings/theme'),
|
||
settingsEditProfile('/settings/edit-profile'),
|
||
settingsBlocklist('/settings/blocklist'),
|
||
settingsLanguage('/settings/language'),
|
||
settingsNetworkDiagnostics('/settings/network-diagnostics'),
|
||
settingsAbout('/settings/about'),
|
||
settingsFavorites('/settings/favorites'),
|
||
settingsRecentCalls('/settings/recent-calls'),
|
||
|
||
// ── 全屏页面(无底部导航栏)──────────────────────────────────────────────
|
||
login('/login');
|
||
|
||
const AppRouteName(this.path);
|
||
|
||
/// 绝对路径,用于 [context.push] / [context.go] 导航及顶层路由表声明
|
||
///
|
||
/// 注意:[chatDetailById] 的 path 含占位符 `:id`,导航时用 [chatDetailByIdPath]。
|
||
final String path;
|
||
|
||
/// 相对路径片段(path 的最后一段),用于 go_router 子路由的 [GoRoute.path] 声明
|
||
///
|
||
/// 例:`AppRouteName.settingsTheme.segment` → `'theme'`
|
||
String get segment => path.split('/').last;
|
||
|
||
/// 从绝对路径查找枚举值,路径未注册时返回 null
|
||
///
|
||
/// 注意:含路径参数的路由(如 `/chat/99`)无法匹配,返回 null,
|
||
/// auth_guard 会按受保护路由处理(未登录重定向到登录页)。
|
||
static AppRouteName? fromPath(String path) =>
|
||
AppRouteName.values.where((r) => r.path == path).firstOrNull;
|
||
|
||
/// 生成 [chatDetailById] 的实际导航路径,将 `:id` 替换为真实 id
|
||
///
|
||
/// 例:`AppRouteName.chatDetailByIdPath('99')` → `'/chat/99'`
|
||
static String chatDetailByIdPath(String id) => '/chat/$id';
|
||
}
|