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 _contextToken = Expando(); 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 { NavBarConfigNotifier() : super(NavBarConfig.home); void update(NavBarConfig config) { if (state != config) { Future.microtask(() { if (mounted) state = config; }); } } } /// NavBar 配置的 Provider —— 各页面在 build 中更新,AppShell 统一消费 final navBarConfigProvider = StateNotifierProvider( (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); }