Files
customer-im-client-dev/apps/im_app/lib/app/router/app_router.dart
2026-03-06 15:05:53 +08:00

155 lines
5.7 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../features/app_tab/view/app_tab.dart';
import '../../features/chat/view/chat_detail_page.dart';
import '../../features/chat/view/chat_page.dart';
import '../../features/contact/view/contact_page.dart';
import '../../features/login/view/login_page.dart';
import '../../features/settings/view/settings_page.dart';
import '../../features/settings/view/theme_view.dart';
import '../di/app_providers.dart';
import 'app_route_name.dart';
import 'guards/auth_guard.dart';
/// 应用路由 Provider
///
/// 路由结构:
/// ```
/// StatefulShellRoute底部导航栏持久容器
/// ├── /chat ChatPage
/// ├── /contact ContactPage
/// └── /settings SettingsPage
///
/// ── 全屏页面无底部导航栏parentNavigatorKey = _rootKey──
/// /chat/detail ChatDetailPageextra 传参)
/// /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 NavigatorShell 不会被盖住TabBar 仍然可见。
///
/// 设置 `parentNavigatorKey: _rootKey` 后,路由强制放到 Root Navigator
/// 盖住整个 ShellTabBar 消失,表现为真正的全屏页面。
///
/// ## 登录守卫
///
/// [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 确保路由覆盖 ShellTabBar 消失
//
// extra 传参:接收 ({String conversationId, String title})
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.login.path,
builder: (context, state) => const LoginPage(),
),
],
);
});