从 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>
193 lines
7.3 KiB
Dart
193 lines
7.3 KiB
Dart
import 'package:flutter/material.dart';
|
||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||
import 'package:go_router/go_router.dart';
|
||
import 'package:im_app/features/chat/view/chat_db_test_page.dart';
|
||
|
||
import 'package:im_app/features/app_tab/view/app_tab.dart';
|
||
import 'package:im_app/features/chat/view/chat_detail_page.dart';
|
||
import 'package:im_app/features/chat/view/chat_page.dart';
|
||
import 'package:im_app/features/contact/view/contact_page.dart';
|
||
import 'package:im_app/features/login/view/login_page.dart';
|
||
import 'package:im_app/features/settings/view/settings_page.dart';
|
||
import 'package:im_app/features/settings/view/theme_view.dart';
|
||
import 'package:im_app/features/settings/view/edit_profile_page.dart';
|
||
import 'package:im_app/features/settings/view/blocklist_page.dart';
|
||
import 'package:im_app/features/settings/view/language_page.dart';
|
||
import 'package:im_app/features/settings/view/network_diagnostics_page.dart';
|
||
import 'package:im_app/features/settings/view/about_page.dart';
|
||
import 'package:im_app/app/di/app_providers.dart';
|
||
import 'package:im_app/app/router/app_route_name.dart';
|
||
import 'package:im_app/app/router/guards/auth_guard.dart';
|
||
|
||
/// 应用路由 Provider
|
||
///
|
||
/// 路由结构:
|
||
/// ```
|
||
/// StatefulShellRoute(底部导航栏持久容器)
|
||
/// ├── /chat ChatPage
|
||
/// ├── /contact ContactPage
|
||
/// └── /settings SettingsPage
|
||
///
|
||
/// ── 全屏页面(无底部导航栏,parentNavigatorKey = _rootKey)──
|
||
/// /chat/detail ChatDetailPage(extra 传参)
|
||
/// /chat/:id ChatDetailPage(路径参数)
|
||
/// /settings/theme ThemeView
|
||
/// /login LoginPage
|
||
/// ```
|
||
///
|
||
/// ## Shell 内 vs Shell 外
|
||
///
|
||
/// Shell 内路由(Tab 根路由)始终显示底部导航栏。
|
||
/// Shell 外路由(详情页 / 子功能页)无底部导航栏,push 进入后有返回按钮。
|
||
/// 这与 iOS / Android 主流 IM App 的交互一致(会话详情、设置子页均全屏)。
|
||
///
|
||
/// ## parentNavigatorKey 的作用
|
||
///
|
||
/// go_router push 时,路由默认放到"最近的 Navigator 祖先"上。
|
||
/// 在 StatefulShellBranch 内 push,最近的 Navigator 是 Branch Navigator,
|
||
/// 而不是 Root Navigator,Shell 不会被盖住,TabBar 仍然可见。
|
||
///
|
||
/// 设置 `parentNavigatorKey: _rootKey` 后,路由强制放到 Root Navigator,
|
||
/// 盖住整个 Shell,TabBar 消失,表现为真正的全屏页面。
|
||
///
|
||
/// ## 登录守卫
|
||
///
|
||
/// [authGuard] 检查 [AuthNotifier.isLoggedIn],未登录时重定向到 /login。
|
||
/// 登录 / 退出后 [AuthNotifier.notifyListeners] 触发 [refreshListenable],
|
||
/// go_router 自动重新执行 redirect,无需手动跳转。
|
||
///
|
||
/// ## Tab 状态保持
|
||
///
|
||
/// [StatefulShellRoute.indexedStack] 为每个 Tab 维护独立的 Navigator 栈,
|
||
/// 切换 Tab 时页面状态(滚动位置、输入内容等)不丢失。
|
||
|
||
// Root Navigator Key:供全屏路由声明 parentNavigatorKey,确保覆盖整个 Shell
|
||
final _rootKey = GlobalKey<NavigatorState>();
|
||
|
||
final routerProvider = Provider<GoRouter>((ref) {
|
||
final authNotifier = ref.read(authNotifierProvider);
|
||
|
||
return GoRouter(
|
||
// Root Navigator 的 Key,供全屏路由声明 parentNavigatorKey 使用,
|
||
// 确保 push 时覆盖整个 Shell(包括 TabBar)
|
||
navigatorKey: _rootKey,
|
||
|
||
// 冷启动默认落地页;authGuard 会在进入前检查登录状态并按需重定向
|
||
initialLocation: AppRouteName.chat.path,
|
||
|
||
// 在控制台打印每次路由变化,方便开发期间调试;上线前设为 false
|
||
debugLogDiagnostics: true,
|
||
|
||
// 监听 authNotifier 变化:登录 / 退出时自动触发 redirect 重新执行,
|
||
// 无需在业务代码里手动 context.go,守卫统一负责跳转
|
||
refreshListenable: authNotifier,
|
||
|
||
redirect: (context, state) => authGuard(authNotifier, state),
|
||
routes: [
|
||
// ── Shell 内:底部导航栏始终可见 ─────────────────────────────────────
|
||
StatefulShellRoute.indexedStack(
|
||
builder: (context, state, navigationShell) {
|
||
return AppTab(navigationShell: navigationShell);
|
||
},
|
||
branches: [
|
||
StatefulShellBranch(
|
||
routes: [
|
||
GoRoute(
|
||
path: AppRouteName.chat.path,
|
||
builder: (context, state) => const ChatPage(),
|
||
),
|
||
],
|
||
),
|
||
StatefulShellBranch(
|
||
routes: [
|
||
GoRoute(
|
||
path: AppRouteName.contact.path,
|
||
builder: (context, state) => const ContactPage(),
|
||
),
|
||
],
|
||
),
|
||
StatefulShellBranch(
|
||
routes: [
|
||
GoRoute(
|
||
path: AppRouteName.settings.path,
|
||
builder: (context, state) => const SettingsPage(),
|
||
),
|
||
],
|
||
),
|
||
],
|
||
),
|
||
|
||
// ── Shell 外:全屏页面,无底部导航栏 ─────────────────────────────────
|
||
// parentNavigatorKey: _rootKey 确保路由覆盖 Shell,TabBar 消失
|
||
//
|
||
// extra 传参:接收 ({String conversationId, String title})
|
||
GoRoute(
|
||
parentNavigatorKey: _rootKey,
|
||
path: AppRouteName.chatDBTest.path,
|
||
builder: (context, state) {
|
||
return const ChatDbTestPage();
|
||
},
|
||
),
|
||
GoRoute(
|
||
parentNavigatorKey: _rootKey,
|
||
path: AppRouteName.chatDetail.path,
|
||
builder: (context, state) {
|
||
final extra =
|
||
state.extra as ({String conversationId, String title});
|
||
return ChatDetailPage(
|
||
conversationId: extra.conversationId,
|
||
title: extra.title,
|
||
);
|
||
},
|
||
),
|
||
// 路径参数:id 内嵌在 URL 中(/chat/:id)
|
||
GoRoute(
|
||
parentNavigatorKey: _rootKey,
|
||
path: AppRouteName.chatDetailById.path,
|
||
builder: (context, state) {
|
||
final id = state.pathParameters['id']!;
|
||
return ChatDetailPage(
|
||
conversationId: id,
|
||
title: '路径参数详情',
|
||
);
|
||
},
|
||
),
|
||
GoRoute(
|
||
parentNavigatorKey: _rootKey,
|
||
path: AppRouteName.settingsTheme.path,
|
||
builder: (context, state) => const ThemeView(),
|
||
),
|
||
GoRoute(
|
||
parentNavigatorKey: _rootKey,
|
||
path: AppRouteName.settingsEditProfile.path,
|
||
builder: (context, state) => const EditProfilePage(),
|
||
),
|
||
GoRoute(
|
||
parentNavigatorKey: _rootKey,
|
||
path: AppRouteName.settingsBlocklist.path,
|
||
builder: (context, state) => const BlocklistPage(),
|
||
),
|
||
GoRoute(
|
||
parentNavigatorKey: _rootKey,
|
||
path: AppRouteName.settingsLanguage.path,
|
||
builder: (context, state) => const LanguagePage(),
|
||
),
|
||
GoRoute(
|
||
parentNavigatorKey: _rootKey,
|
||
path: AppRouteName.settingsNetworkDiagnostics.path,
|
||
builder: (context, state) => const NetworkDiagnosticsPage(),
|
||
),
|
||
GoRoute(
|
||
parentNavigatorKey: _rootKey,
|
||
path: AppRouteName.settingsAbout.path,
|
||
builder: (context, state) => const AboutPage(),
|
||
),
|
||
GoRoute(
|
||
parentNavigatorKey: _rootKey,
|
||
path: AppRouteName.login.path,
|
||
builder: (context, state) => const LoginPage(),
|
||
),
|
||
],
|
||
);
|
||
});
|