refactored internal documentation and simplified comments across chat BLoCs, file viewer, and navigation components
This commit is contained in:
+16
-43
@@ -41,9 +41,6 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
||||
StreamSubscription<RemoteMessage>? _onMessageSub;
|
||||
StreamSubscription<RemoteMessage>? _onMessageOpenedAppSub;
|
||||
StreamSubscription<String>? _fcmTokenRefreshSub;
|
||||
// Tracked via the bottom-nav controller's listener so it always reflects the
|
||||
// user's actual position, even between rapid setting emits where the
|
||||
// controller hasn't caught up to a scheduled jump yet.
|
||||
int _knownTotalTabs = 1;
|
||||
bool _userOnLastTab = false;
|
||||
|
||||
@@ -84,12 +81,9 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
||||
Future<void> _handlePendingWidgetNavigation() async {
|
||||
final pending = await WidgetNavigation.consumePendingTimetableTap();
|
||||
if (!pending || !mounted) return;
|
||||
// Routes pushed with `withNavBar: false` (chat views, file viewers, …)
|
||||
// sit on the root navigator above the bottom-nav, so a bare jumpToTab
|
||||
// would swap the tab behind them and leave the user staring at the
|
||||
// previous screen. Reset to the tab root first — but stop at any open
|
||||
// popup so a confirmation dialog or bottom sheet that the user hasn't
|
||||
// dismissed yet doesn't get silently torn down.
|
||||
// `withNavBar: false` routes sit on the root navigator above the
|
||||
// bottom-nav; pop them so jumpToTab is actually visible. Stop at
|
||||
// popups so open dialogs/sheets stay alive.
|
||||
final navigator = Navigator.of(context);
|
||||
if (navigator.canPop()) {
|
||||
navigator.popUntil((route) => route.isFirst || route is PopupRoute);
|
||||
@@ -101,10 +95,8 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
||||
if (!mounted) return;
|
||||
final share = ShareIntentListener.pending.value;
|
||||
if (share == null) return;
|
||||
// A second share arriving while a previous share-flow page is still on
|
||||
// the stack would otherwise leave the old page sitting on top with stale
|
||||
// (already-cleared) file paths. Reset to the tab root before pushing —
|
||||
// but stop at any open popup so dialogs/bottom-sheets remain intact.
|
||||
// A second share would otherwise leave the previous share-flow page
|
||||
// on top with stale (already-cleared) file paths.
|
||||
final navigator = Navigator.of(context);
|
||||
if (navigator.canPop()) {
|
||||
navigator.popUntil((route) => route.isFirst || route is PopupRoute);
|
||||
@@ -123,15 +115,11 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
||||
if (!mounted) return;
|
||||
context.read<BreakerBloc>().refresh();
|
||||
context.read<ChatListBloc>().refresh();
|
||||
// App is freshly mounted on every login (BlocConsumer in main.dart
|
||||
// swaps it in for Login), so this also covers the post-logout case
|
||||
// where the bloc was reset to an empty state and needs a fresh fetch.
|
||||
// Re-mounts on every login, so this also covers post-logout state reset.
|
||||
final timetable = context.read<TimetableBloc>();
|
||||
timetable.refresh();
|
||||
// Push the freshest timetable state into the home-screen widget any
|
||||
// time the BLoC reports new data — without waiting for the periodic
|
||||
// background refresh. This is the "user just opened the app" path:
|
||||
// the widget gets the same data the user is looking at on screen.
|
||||
// Mirror BLoC updates into the home-screen widget without waiting
|
||||
// for the periodic background refresh.
|
||||
final settingsCubit = context.read<SettingsCubit>();
|
||||
_timetableWidgetSync?.cancel();
|
||||
_timetableWidgetSync = timetable.stream.listen((state) {
|
||||
@@ -145,8 +133,7 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
||||
);
|
||||
}
|
||||
});
|
||||
// Also publish the current state once, in case data is already loaded
|
||||
// from hydrated storage before the listener attaches.
|
||||
// Initial publish in case hydrated storage already has data.
|
||||
final initialData = timetable.state.data;
|
||||
if (initialData is TimetableState) {
|
||||
unawaited(
|
||||
@@ -222,17 +209,9 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
||||
final totalTabs = bottomBarModules.length + 1;
|
||||
final currentIndex = Main.bottomNavigator.index;
|
||||
|
||||
// The bottom-bar layout is identified by the ordered list of module
|
||||
// names plus the trailing 'more' slot. Whenever this layout changes
|
||||
// — slot count, reordering, or hiding a module — we recreate the
|
||||
// entire PersistentTabView via the [layoutKey] below. The package
|
||||
// caches per-tab navigator state by index in `_navigatorKeys`, and
|
||||
// its internal `alignLength` only ever appends or trims at the end.
|
||||
// So when the module sitting at e.g. index 3 changes, the navigator
|
||||
// at that index still serves the old screen's route stack and the
|
||||
// user sees stale content. Re-mounting clears those stacks; the
|
||||
// trade-off (losing in-tab pushed routes on a settings change) is
|
||||
// acceptable since the user explicitly re-shaped the bar.
|
||||
// PersistentTabView caches per-tab navigators by index and only
|
||||
// appends/trims at the end, so reordering/hiding leaves stale
|
||||
// route stacks under the wrong tabs. Re-key on layout to remount.
|
||||
final layoutKey = ValueKey(
|
||||
'${bottomBarModules.map((m) => m.module.name).join('|')}|more',
|
||||
);
|
||||
@@ -244,12 +223,8 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
||||
} else if (currentIndex >= totalTabs) {
|
||||
targetIndex = totalTabs - 1;
|
||||
}
|
||||
// Re-mounting PTV with a new key constructs fresh internals from
|
||||
// its controller's current index. If the controller still points
|
||||
// past the new tab list, Style6BottomNavBar (and others) crash on
|
||||
// out-of-range access during initState. Replace the controller
|
||||
// atomically with one initialised at the safe target index so the
|
||||
// new PTV mounts cleanly.
|
||||
// Replace the controller atomically: a stale index past the new
|
||||
// tab list crashes Style6BottomNavBar's initState.
|
||||
if (targetIndex != currentIndex) {
|
||||
Main.bottomNavigator.removeListener(_onTabControllerChanged);
|
||||
Main.bottomNavigator = PersistentTabController(
|
||||
@@ -285,10 +260,8 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
||||
),
|
||||
],
|
||||
navBarBuilder: (config) => Style6BottomNavBar(
|
||||
// Style6BottomNavBar builds its internal animation controller list
|
||||
// in initState and never grows it on didUpdateWidget. Keying by the
|
||||
// item count forces a fresh State whenever the slot count changes,
|
||||
// which avoids a RangeError when more tabs slide in.
|
||||
// Animation controllers are built once in initState and never
|
||||
// grown — re-key on item count to avoid RangeError on growth.
|
||||
key: ValueKey(config.items.length),
|
||||
navBarConfig: config,
|
||||
navBarDecoration: NavBarDecoration(
|
||||
|
||||
Reference in New Issue
Block a user