Initial project

This commit is contained in:
Cody
2026-03-06 14:56:17 +08:00
parent 977b627b15
commit bf9e099747
1180 changed files with 50973 additions and 0 deletions

View File

@@ -0,0 +1,154 @@
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(),
),
],
);
});