从 im-client-ios-swift-demo 搬运 Settings 逻辑,对齐 Gitea issue #5–#13 ## 基础设施 - AuthNotifier 新增 currentUid 字段,login() 接受 uid 参数 (#5) - LoginViewModel 登录成功后传入 user.uid - ApiPaths 补充 account/block/store 系列路径 - Tab 重命名"设置"→"我的",icon 改为 person_outline (#5) - AppRouteName 新增5条子路由 (edit-profile/blocklist/language/network-diagnostics/about) - app_router + auth_guard 同步注册新路由 ## Settings Feature - SettingsViewModel 重写为 NotifierProvider(去除 @riverpod 依赖) - build() 自动触发 loadProfile() - logout() 完整流程:API → WS 断开 → DB 关闭 → AuthNotifier - 6 个 navigateTo* 方法 - SettingsPage 完整 UI:资料卡 / 偏好设置 / 工具 / 关于 / 退出登录按钮 (#5 #7) - FetchProfileUseCase: GET /app/api/user/profile (#5) - LogoutUseCase: logout + disconnect + closeDatabase (#7) - UpdateProfileUseCase + UpdateProfileRequest: POST /app/api/user/update-profile (#6) - EditProfilePage + EditProfileViewModel: 昵称/bio 编辑 (#6) - LanguagePage: 语言选择 UI 框架,l10n_sdk 待接入 (#9) - BlocklistPage: 黑名单框架,API 待实现 (#10) - NetworkDiagnosticsPage + ViewModel: 四步诊断(连通/TCP/DNS/HTTPS)(#12) - AboutPage: 版本号 + 服务条款/隐私政策入口 (#13) - settings_providers.dart: 扩展 DI 装配 ## 文档 - Doc/mine_tab_architecture.md: 架构说明、数据流、路由、待完成事项 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
104 lines
4.3 KiB
Dart
104 lines
4.3 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: '技术支持'),
|
||
/// );
|
||
///
|
||
/// // 带路径参数导航(路径中内嵌 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})
|
||
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'),
|
||
|
||
// ── 全屏页面(无底部导航栏)──────────────────────────────────────────────
|
||
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';
|
||
}
|