From 6a4054d75e2e3631e649824a74616a7fcadecd33 Mon Sep 17 00:00:00 2001 From: zarzet <42882290+zarzet@users.noreply.github.com> Date: Sat, 3 Oct 2026 17:57:26 +0700 Subject: [PATCH] fix(library): preserve album view when returning from detail screens --- lib/screens/main_shell.dart | 12 +++++-- lib/screens/queue_tab.dart | 6 +++- test/album_completeness_query_test.dart | 43 +++++++++++++++++++++++-- 3 files changed, 56 insertions(+), 5 deletions(-) diff --git a/lib/screens/main_shell.dart b/lib/screens/main_shell.dart index 6d649e58..b1fb3c02 100644 --- a/lib/screens/main_shell.dart +++ b/lib/screens/main_shell.dart @@ -860,7 +860,10 @@ class _MainShellState extends ConsumerState navigatorKey: _libraryTabNavigatorKey, observers: [_libraryPreviewStopObserver], heroAnimationsEnabled: heroAnimationsEnabled, - child: _LibraryTabRoot(parentPageController: _pageController), + child: _LibraryTabRoot( + parentPageController: _pageController, + isTabActive: _currentIndex == 1, + ), ), if (showStore) _TabNavigator( @@ -1232,8 +1235,12 @@ class _PreviewStopNavigatorObserver extends NavigatorObserver { class _LibraryTabRoot extends ConsumerWidget { final PageController parentPageController; + final bool isTabActive; - const _LibraryTabRoot({required this.parentPageController}); + const _LibraryTabRoot({ + required this.parentPageController, + required this.isTabActive, + }); @override Widget build(BuildContext context, WidgetRef ref) { @@ -1241,6 +1248,7 @@ class _LibraryTabRoot extends ConsumerWidget { settingsProvider.select((s) => s.showExtensionStore), ); return QueueTab( + isTabActive: isTabActive, parentPageController: parentPageController, parentPageIndex: 1, nextPageIndex: showStore ? 2 : 3, diff --git a/lib/screens/queue_tab.dart b/lib/screens/queue_tab.dart index 0f3a6762..7348245d 100644 --- a/lib/screens/queue_tab.dart +++ b/lib/screens/queue_tab.dart @@ -211,6 +211,7 @@ DownloadHistoryItem? _historyItemForCompletionBridge( class QueueTab extends ConsumerStatefulWidget { final String? librarySection; + final bool isTabActive; final PageController? parentPageController; final int parentPageIndex; final int? nextPageIndex; @@ -218,6 +219,7 @@ class QueueTab extends ConsumerStatefulWidget { const QueueTab({ super.key, this.librarySection, + this.isTabActive = true, this.parentPageController, this.parentPageIndex = 1, this.nextPageIndex, @@ -364,7 +366,9 @@ class _QueueTabState extends ConsumerState { /// configured, jump the filter pager to it. void _applyDefaultLibraryViewOnTabVisible() { if (widget.librarySection != null) return; - final isVisible = TickerMode.valuesOf(context).enabled; + // Route transitions also disable TickerMode. Only a shell tab change + // should reset the user's current album/single view. + final isVisible = widget.isTabActive; final becameVisible = isVisible && !_wasTabVisible; _wasTabVisible = isVisible; if (!becameVisible) return; diff --git a/test/album_completeness_query_test.dart b/test/album_completeness_query_test.dart index 76e30e21..0d330226 100644 --- a/test/album_completeness_query_test.dart +++ b/test/album_completeness_query_test.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:convert'; import 'dart:io'; @@ -92,7 +93,8 @@ print(json.dumps([dict(row) for row in db.execute(data['sql'], data['args'])])) class _Settings extends SettingsNotifier { @override - AppSettings build() => const AppSettings(localLibraryEnabled: true); + AppSettings build() => + const AppSettings(localLibraryEnabled: true, defaultLibraryView: 'all'); @override void setHistoryFilterMode(String mode) { @@ -287,6 +289,8 @@ void main() { tester.view.physicalSize = const Size(800, 1400); tester.view.devicePixelRatio = 1; addTearDown(tester.view.reset); + final tabActive = ValueNotifier(true); + addTearDown(tabActive.dispose); await tester.pumpWidget( ProviderScope( overrides: [ @@ -302,7 +306,15 @@ void main() { theme: AppTheme.light(), localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, - home: const Scaffold(body: QueueTab()), + home: ValueListenableBuilder( + valueListenable: tabActive, + builder: (_, active, _) => Scaffold( + body: TickerMode( + enabled: active, + child: QueueTab(isTabActive: active), + ), + ), + ), ), ), ); @@ -327,6 +339,25 @@ void main() { ); expect(container.read(settingsProvider).historyFilterMode, 'albums'); + // Pushing an album/detail route disables the underlying route's ticker. + // Popping it must retain Albums rather than reapply the All default. + final navigator = Navigator.of(tester.element(find.byType(QueueTab))); + unawaited( + navigator.push( + MaterialPageRoute( + builder: (_) => const Scaffold(body: Text('Album details')), + ), + ), + ); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 400)); + expect(find.text('Album details'), findsOneWidget); + navigator.pop(); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 400)); + expect(container.read(settingsProvider).historyFilterMode, 'albums'); + await _waitFor(tester, find.text('1/12')); + await tester.enterText(find.byType(TextField), 'one-track'); await tester.pump(const Duration(milliseconds: 400)); await _waitFor(tester, find.text('1 album')); @@ -350,6 +381,14 @@ void main() { await tester.pump(const Duration(milliseconds: 400)); await _waitFor(tester, find.text('complete')); expect(find.text('1/12'), findsNothing); + expect(container.read(settingsProvider).historyFilterMode, 'albums'); + // An actual shell-tab switch still applies the configured All default. + tabActive.value = false; + await tester.pump(); + tabActive.value = true; + await tester.pump(); + await tester.pump(const Duration(milliseconds: 400)); + expect(container.read(settingsProvider).historyFilterMode, 'all'); expect(tester.takeException(), isNull); await tester.pumpWidget(const SizedBox()); // Let any already-dispatched SQLite queries finish after provider disposal.