fix(navigation): open download queue from snackbar actions

This commit is contained in:
zarzet
2026-07-24 22:19:03 +07:00
parent 01c08322c1
commit 9bea2a3e54
6 changed files with 150 additions and 12 deletions
+2 -4
View File
@@ -30,6 +30,7 @@ import 'package:spotiflac_android/utils/clickable_metadata.dart';
import 'package:spotiflac_android/widgets/cached_cover_image.dart';
import 'package:spotiflac_android/widgets/motion_header_banner.dart';
import 'package:spotiflac_android/widgets/cross_extension_share_sheet.dart';
import 'package:spotiflac_android/widgets/view_queue_snackbar_action.dart';
class _ArtistCache {
static final _cache = TtlCache<_CacheEntry>(const Duration(minutes: 10));
@@ -1055,10 +1056,7 @@ class _ArtistScreenState extends ConsumerState<ArtistScreen>
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
action: SnackBarAction(
label: context.l10n.snackbarViewQueue,
onPressed: () {},
),
action: buildViewQueueSnackBarAction(context),
),
);
}
+3 -8
View File
@@ -37,6 +37,7 @@ import 'package:spotiflac_android/widgets/error_card.dart';
import 'package:spotiflac_android/widgets/in_library_badge.dart';
import 'package:spotiflac_android/widgets/preview_button.dart';
import 'package:spotiflac_android/widgets/settings_group.dart';
import 'package:spotiflac_android/widgets/view_queue_snackbar_action.dart';
part 'home_tab_helpers.dart';
part 'home_tab_widgets.dart';
@@ -1077,10 +1078,7 @@ class _HomeTabState extends ConsumerState<HomeTab>
ScaffoldMessenger.of(this.context).showSnackBar(
SnackBar(
content: Text(queueSnackbarMessage),
action: SnackBarAction(
label: l10n.snackbarViewQueue,
onPressed: () {},
),
action: buildViewQueueSnackBarAction(this.context),
),
);
}
@@ -1109,10 +1107,7 @@ class _HomeTabState extends ConsumerState<HomeTab>
ScaffoldMessenger.of(this.context).showSnackBar(
SnackBar(
content: Text(queueSnackbarMessage),
action: SnackBarAction(
label: l10n.snackbarViewQueue,
onPressed: () {},
),
action: buildViewQueueSnackBarAction(this.context),
),
);
}
+18
View File
@@ -105,6 +105,10 @@ class _MainShellState extends ConsumerState<MainShell>
duration: const Duration(milliseconds: 180),
value: 1,
);
ShellNavigationService.registerTabSelectionHandler(
owner: this,
handler: _onShellTabRequested,
);
ShellNavigationService.syncState(
currentTabIndex: _currentIndex,
showRepoTab: false,
@@ -433,6 +437,7 @@ class _MainShellState extends ConsumerState<MainShell>
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
ShellNavigationService.unregisterTabSelectionHandler(this);
_shareSubscription?.cancel();
_pageController.dispose();
_tabJumpTransitionController.dispose();
@@ -452,6 +457,19 @@ class _MainShellState extends ConsumerState<MainShell>
ref.read(trackProvider.notifier).clear();
}
void _onShellTabRequested(ShellTab tab) {
final showStore = ref.read(
settingsProvider.select((s) => s.showExtensionStore),
);
final index = switch (tab) {
ShellTab.home => 0,
ShellTab.library => 1,
ShellTab.repository => showStore ? 2 : null,
ShellTab.settings => showStore ? 3 : 2,
};
if (index != null) _onNavTap(index);
}
void _onNavTap(int index) {
if (index == 0 && _currentIndex == 0) {
_resetHomeToMain();
@@ -1,5 +1,7 @@
import 'package:flutter/widgets.dart';
enum ShellTab { home, library, repository, settings }
class ShellNavigationService {
static final GlobalKey<NavigatorState> homeTabNavigatorKey =
GlobalKey<NavigatorState>();
@@ -10,6 +12,29 @@ class ShellNavigationService {
static int _currentTabIndex = 0;
static bool _showRepoTab = false;
static Object? _tabSelectionOwner;
static ValueChanged<ShellTab>? _tabSelectionHandler;
static void registerTabSelectionHandler({
required Object owner,
required ValueChanged<ShellTab> handler,
}) {
_tabSelectionOwner = owner;
_tabSelectionHandler = handler;
}
static void unregisterTabSelectionHandler(Object owner) {
if (!identical(_tabSelectionOwner, owner)) return;
_tabSelectionOwner = null;
_tabSelectionHandler = null;
}
static bool requestTab(ShellTab tab) {
final handler = _tabSelectionHandler;
if (handler == null) return false;
handler(tab);
return true;
}
static void syncState({
required int currentTabIndex,
@@ -0,0 +1,12 @@
import 'package:flutter/material.dart';
import 'package:spotiflac_android/l10n/l10n.dart';
import 'package:spotiflac_android/services/shell_navigation_service.dart';
SnackBarAction buildViewQueueSnackBarAction(BuildContext context) {
return SnackBarAction(
label: context.l10n.snackbarViewQueue,
onPressed: () {
ShellNavigationService.requestTab(ShellTab.library);
},
);
}
+90
View File
@@ -0,0 +1,90 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:spotiflac_android/l10n/app_localizations.dart';
import 'package:spotiflac_android/services/shell_navigation_service.dart';
import 'package:spotiflac_android/widgets/view_queue_snackbar_action.dart';
void main() {
group('ShellNavigationService tab requests', () {
test('forwards a named tab request to the registered shell', () {
final owner = Object();
ShellTab? requestedTab;
addTearDown(
() => ShellNavigationService.unregisterTabSelectionHandler(owner),
);
ShellNavigationService.registerTabSelectionHandler(
owner: owner,
handler: (tab) => requestedTab = tab,
);
expect(ShellNavigationService.requestTab(ShellTab.library), isTrue);
expect(requestedTab, ShellTab.library);
});
test('does not remove a newer shell handler', () {
final oldOwner = Object();
final currentOwner = Object();
ShellTab? requestedTab;
addTearDown(
() =>
ShellNavigationService.unregisterTabSelectionHandler(currentOwner),
);
ShellNavigationService.registerTabSelectionHandler(
owner: oldOwner,
handler: (_) {},
);
ShellNavigationService.registerTabSelectionHandler(
owner: currentOwner,
handler: (tab) => requestedTab = tab,
);
ShellNavigationService.unregisterTabSelectionHandler(oldOwner);
expect(ShellNavigationService.requestTab(ShellTab.settings), isTrue);
expect(requestedTab, ShellTab.settings);
});
test('reports when no shell can handle the request', () {
final owner = Object();
ShellNavigationService.registerTabSelectionHandler(
owner: owner,
handler: (_) {},
);
ShellNavigationService.unregisterTabSelectionHandler(owner);
expect(ShellNavigationService.requestTab(ShellTab.library), isFalse);
});
testWidgets('View Queue snackbar action requests the Library tab', (
tester,
) async {
final owner = Object();
ShellTab? requestedTab;
addTearDown(
() => ShellNavigationService.unregisterTabSelectionHandler(owner),
);
ShellNavigationService.registerTabSelectionHandler(
owner: owner,
handler: (tab) => requestedTab = tab,
);
await tester.pumpWidget(
MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: Scaffold(
body: Builder(
builder: (context) => buildViewQueueSnackBarAction(context),
),
),
),
);
await tester.tap(find.text('View Queue'));
expect(requestedTab, ShellTab.library);
});
});
}