nav_bar_config.dart 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. import 'package:flutter/material.dart';
  2. import 'package:flutter_riverpod/flutter_riverpod.dart';
  3. /// NavBar 配置,由各页面在 build 中设置,AppShell 统一渲染
  4. class NavBarConfig {
  5. final String title;
  6. final bool showBack;
  7. final bool showRight;
  8. final Widget? rightWidget;
  9. final VoidCallback? onBack;
  10. final IconData? leadingIcon;
  11. final bool hasFilter;
  12. final int filterVersion;
  13. const NavBarConfig({
  14. required this.title,
  15. this.showBack = false,
  16. this.showRight = false,
  17. this.rightWidget,
  18. this.onBack,
  19. this.leadingIcon,
  20. this.hasFilter = false,
  21. this.filterVersion = 0,
  22. });
  23. /// 根页面默认配置(无返回按钮)
  24. static const home = NavBarConfig(title: 'TBOSS 工作台', showBack: false);
  25. static const messages = NavBarConfig(title: '消息', showBack: false);
  26. static const profile = NavBarConfig(title: '我的', showBack: false);
  27. /// 带返回按钮的页面快捷构造
  28. factory NavBarConfig.withBack(String title, {VoidCallback? onBack}) {
  29. return NavBarConfig(title: title, showBack: true, onBack: onBack);
  30. }
  31. /// 带返回按钮 + 右侧按钮的页面快捷构造
  32. factory NavBarConfig.withRight(
  33. String title, {
  34. required Widget rightWidget,
  35. VoidCallback? onBack,
  36. }) {
  37. return NavBarConfig(
  38. title: title,
  39. showBack: true,
  40. showRight: true,
  41. rightWidget: rightWidget,
  42. onBack: onBack,
  43. );
  44. }
  45. @override
  46. bool operator ==(Object other) =>
  47. other is NavBarConfig &&
  48. other.title == title &&
  49. other.showBack == showBack &&
  50. other.showRight == showRight &&
  51. other.hasFilter == hasFilter &&
  52. other.filterVersion == filterVersion;
  53. // 故意不比较 onBack / rightWidget / leadingIcon,避免每次 build 新闭包触发无限重建
  54. @override
  55. int get hashCode =>
  56. Object.hash(title, showBack, showRight, hasFilter, filterVersion);
  57. }
  58. /// NavBar 配置变更器,内部做相等判断避免无限重建
  59. ///
  60. /// 通过 [Future.microtask] 将状态更新推迟到事件循环下一个 tick,
  61. /// 避免子页面在 build 中更新 provider 时与 AppShell watch 冲突。
  62. class NavBarConfigNotifier extends StateNotifier<NavBarConfig> {
  63. NavBarConfigNotifier() : super(NavBarConfig.home);
  64. void update(NavBarConfig config) {
  65. if (state != config) {
  66. Future.microtask(() {
  67. if (mounted) state = config;
  68. });
  69. }
  70. }
  71. }
  72. /// NavBar 配置的 Provider —— 各页面在 build 中更新,AppShell 统一消费
  73. final navBarConfigProvider =
  74. StateNotifierProvider<NavBarConfigNotifier, NavBarConfig>(
  75. (ref) => NavBarConfigNotifier(),
  76. );
  77. /// 仅在当前路由可见时更新导航栏标题,避免被覆盖的页面因 GoRouter
  78. /// rebuild 级联效应竞争标题(如 showDatePicker 导致的路由栈全量重建)。
  79. ///
  80. /// [context] 为当前页面 build 的 context,
  81. /// [config] 为要设置的 NavBarConfig。
  82. void setNavBarTitle(BuildContext context, WidgetRef ref, NavBarConfig config) {
  83. final route = ModalRoute.of(context);
  84. // null = 首次 build 路由尚未完全插入,仍需更新
  85. // isCurrent = 当前可见路由
  86. // false = 已被覆盖的旧页面,跳过
  87. if (route == null || route.isCurrent) {
  88. ref.read(navBarConfigProvider.notifier).update(config);
  89. }
  90. }