| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130 |
- import 'package:flutter/material.dart';
- import 'package:flutter_riverpod/flutter_riverpod.dart';
- import 'package:go_router/go_router.dart';
- /// 按 BuildContext 缓存 token:同 State 多次 build 复用同一 token,
- /// 新 State(context 对象不同)自动分配新 token,确保 onBack 闭包不被旧 State 覆盖
- final Expando<int> _contextToken = Expando<int>();
- int _globalNavToken = 0;
- int _tokenForContext(BuildContext context) =>
- _contextToken[context] ??= ++_globalNavToken;
- /// NavBar 配置,由各页面在 build 中设置,AppShell 统一渲染
- class NavBarConfig {
- final String title;
- final bool showBack;
- final bool showRight;
- final Widget? rightWidget;
- final VoidCallback? onBack;
- final IconData? leadingIcon;
- final bool hasFilter;
- final int filterVersion;
- /// 页面实例标识:每个 State 实例生成唯一 token,确保新 State 的 onBack 闭包不被
- /// 旧 State 的 config(值相等但闭包指向已销毁 State)覆盖
- final int? token;
- const NavBarConfig({
- required this.title,
- this.showBack = false,
- this.showRight = false,
- this.rightWidget,
- this.onBack,
- this.leadingIcon,
- this.hasFilter = false,
- this.filterVersion = 0,
- this.token,
- });
- /// 根页面默认配置(无返回按钮)
- static const home = NavBarConfig(title: 'TBOSS 工作台', showBack: false);
- static const messages = NavBarConfig(title: '消息', showBack: false);
- static const profile = NavBarConfig(title: '我的', showBack: false);
- /// 带返回按钮的页面快捷构造
- factory NavBarConfig.withBack(String title, {VoidCallback? onBack}) {
- return NavBarConfig(title: title, showBack: true, onBack: onBack);
- }
- /// 带返回按钮 + 右侧按钮的页面快捷构造
- factory NavBarConfig.withRight(
- String title, {
- required Widget rightWidget,
- VoidCallback? onBack,
- }) {
- return NavBarConfig(
- title: title,
- showBack: true,
- showRight: true,
- rightWidget: rightWidget,
- onBack: onBack,
- );
- }
- @override
- bool operator ==(Object other) =>
- other is NavBarConfig &&
- other.title == title &&
- other.showBack == showBack &&
- other.showRight == showRight &&
- other.hasFilter == hasFilter &&
- other.filterVersion == filterVersion &&
- other.token == token;
- // token 确保不同 State 实例的 config 不相等,onBack/rightWidget 通过 token 间接区分
- @override
- int get hashCode =>
- Object.hash(title, showBack, showRight, hasFilter, filterVersion, token);
- }
- /// NavBar 配置变更器,内部做相等判断避免无限重建
- ///
- /// 通过 [Future.microtask] 将状态更新推迟到事件循环下一个 tick,
- /// 避免子页面在 build 中更新 provider 时与 AppShell watch 冲突。
- class NavBarConfigNotifier extends StateNotifier<NavBarConfig> {
- NavBarConfigNotifier() : super(NavBarConfig.home);
- void update(NavBarConfig config) {
- if (state != config) {
- Future.microtask(() {
- if (mounted) state = config;
- });
- }
- }
- }
- /// NavBar 配置的 Provider —— 各页面在 build 中更新,AppShell 统一消费
- final navBarConfigProvider =
- StateNotifierProvider<NavBarConfigNotifier, NavBarConfig>(
- (ref) => NavBarConfigNotifier(),
- );
- /// 更新导航栏配置。使用 GoRouter 判断当前页面是否为可见路由:
- /// - 页面自身的 GoRouterState.uri == GoRouter 注册的当前 uri → 允许更新
- /// - 不匹配 → 被覆盖的旧页面(GoRouter 级联重建导致),跳过
- ///
- /// 此方法替代了 ModalRoute.isCurrent,因为缓存引擎下旧路由残留会导致
- /// ModalRoute 误判;GoRouter 的状态不受 Widget 树生命周期影响,结果更可靠。
- void setNavBarTitle(BuildContext context, WidgetRef ref, NavBarConfig config) {
- try {
- final pageUri = GoRouterState.of(context).uri.toString();
- final currentUri = GoRouter.of(context).state.uri.toString();
- if (pageUri != currentUri) return;
- } catch (_) {
- // GoRouter 不可用时直接放行
- }
- // 注入 context 绑定的 token:同 State 多次 build 共享,换 State 自动换新 token
- final token = _tokenForContext(context);
- final effective = config.token != null ? config : NavBarConfig(
- title: config.title,
- showBack: config.showBack,
- showRight: config.showRight,
- rightWidget: config.rightWidget,
- onBack: config.onBack,
- leadingIcon: config.leadingIcon,
- hasFilter: config.hasFilter,
- filterVersion: config.filterVersion,
- token: token,
- );
- ref.read(navBarConfigProvider.notifier).update(effective);
- }
|