From 5f442a1ff7bfd551255f91d07e33ea65dd1682f1 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Mon, 29 Apr 2024 12:02:28 +0600 Subject: [PATCH] refactor: use route names --- lib/collections/intents.dart | 12 +- lib/collections/routes.dart | 71 +++++-- lib/collections/side_bar_tiles.dart | 70 ++++++- lib/collections/spotube_icons.dart | 1 + lib/components/album/album_card.dart | 10 +- lib/components/artist/artist_card.dart | 9 +- lib/components/connect/connect_device.dart | 7 +- lib/components/home/sections/feed.dart | 10 +- .../home/sections/friends/friend_item.dart | 22 +- lib/components/home/sections/genres.dart | 12 +- lib/components/playlist/playlist_card.dart | 8 +- lib/components/root/sidebar.dart | 95 ++++----- .../root/spotube_navigation_bar.dart | 30 +-- .../shared/fallbacks/anonymous_fallback.dart | 3 +- lib/components/shared/links/artist_link.dart | 8 +- lib/l10n/app_en.arb | 3 +- lib/pages/album/album.dart | 2 + lib/pages/artist/artist.dart | 2 + lib/pages/connect/connect.dart | 7 +- lib/pages/connect/control/control.dart | 11 +- lib/pages/desktop_login/desktop_login.dart | 2 + lib/pages/desktop_login/login_tutorial.dart | 4 +- .../getting_started/getting_started.dart | 2 + .../getting_started/sections/support.dart | 3 +- lib/pages/home/feed/feed_section.dart | 2 + lib/pages/home/genres/genre_playlists.dart | 2 + lib/pages/home/genres/genres.dart | 10 +- lib/pages/home/home.dart | 13 +- lib/pages/lastfm_login/lastfm_login.dart | 1 + lib/pages/library/library.dart | 2 + .../playlist_generate/playlist_generate.dart | 2 + .../playlist_generate_result.dart | 10 +- lib/pages/lyrics/lyrics.dart | 2 + lib/pages/lyrics/mini_lyrics.dart | 2 + lib/pages/mobile_login/mobile_login.dart | 1 + lib/pages/playlist/liked_playlist.dart | 3 + lib/pages/playlist/playlist.dart | 2 + lib/pages/profile/profile.dart | 2 + lib/pages/root/root_app.dart | 38 +--- lib/pages/search/search.dart | 191 ++++++++++-------- lib/pages/settings/about.dart | 2 + lib/pages/settings/blacklist.dart | 2 + lib/pages/settings/logs.dart | 2 + lib/pages/settings/settings.dart | 2 + lib/pages/stats/stats.dart | 13 ++ lib/pages/track/track.dart | 2 + lib/utils/service_utils.dart | 46 +++++ untranslated_messages.json | 90 ++++++++- 48 files changed, 592 insertions(+), 254 deletions(-) create mode 100644 lib/pages/stats/stats.dart diff --git a/lib/collections/intents.dart b/lib/collections/intents.dart index 5f60959e..579aff18 100644 --- a/lib/collections/intents.dart +++ b/lib/collections/intents.dart @@ -7,6 +7,10 @@ import 'package:go_router/go_router.dart'; import 'package:spotube/collections/routes.dart'; import 'package:spotube/components/player/player_controls.dart'; import 'package:spotube/models/logger.dart'; +import 'package:spotube/pages/home/home.dart'; +import 'package:spotube/pages/library/library.dart'; +import 'package:spotube/pages/lyrics/lyrics.dart'; +import 'package:spotube/pages/search/search.dart'; import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; import 'package:spotube/utils/platform.dart'; @@ -67,16 +71,16 @@ class HomeTabAction extends Action { final router = intent.ref.read(routerProvider); switch (intent.tab) { case HomeTabs.browse: - router.go("/"); + router.goNamed(HomePage.name); break; case HomeTabs.search: - router.go("/search"); + router.goNamed(SearchPage.name); break; case HomeTabs.library: - router.go("/library"); + router.goNamed(LibraryPage.name); break; case HomeTabs.lyrics: - router.go("/lyrics"); + router.goNamed(LyricsPage.name); break; } return null; diff --git a/lib/collections/routes.dart b/lib/collections/routes.dart index 080cbd8a..2ce29b40 100644 --- a/lib/collections/routes.dart +++ b/lib/collections/routes.dart @@ -24,6 +24,7 @@ import 'package:spotube/pages/search/search.dart'; import 'package:spotube/pages/settings/blacklist.dart'; import 'package:spotube/pages/settings/about.dart'; import 'package:spotube/pages/settings/logs.dart'; +import 'package:spotube/pages/stats/stats.dart'; import 'package:spotube/pages/track/track.dart'; import 'package:spotube/provider/authentication_provider.dart'; import 'package:spotube/services/kv_store/kv_store.dart'; @@ -50,6 +51,7 @@ final routerProvider = Provider((ref) { routes: [ GoRoute( path: "/", + name: HomePage.name, redirect: (context, state) async { final authNotifier = ref.read(authenticationProvider.notifier); final json = await authNotifier.box.get(authNotifier.cacheKey); @@ -66,11 +68,13 @@ final routerProvider = Provider((ref) { routes: [ GoRoute( path: "genres", + name: GenrePage.name, pageBuilder: (context, state) => const SpotubePage(child: GenrePage()), ), GoRoute( path: "genre/:categoryId", + name: GenrePlaylistsPage.name, pageBuilder: (context, state) => SpotubePage( child: GenrePlaylistsPage( category: state.extra as Category, @@ -79,6 +83,7 @@ final routerProvider = Provider((ref) { ), GoRoute( path: "feeds/:feedId", + name: HomeFeedSectionPage.name, pageBuilder: (context, state) => SpotubePage( child: HomeFeedSectionPage( sectionUri: state.pathParameters["feedId"] as String, @@ -89,45 +94,50 @@ final routerProvider = Provider((ref) { ), GoRoute( path: "/search", - name: "Search", + name: SearchPage.name, pageBuilder: (context, state) => const SpotubePage(child: SearchPage()), ), GoRoute( - path: "/library", - name: "Library", - pageBuilder: (context, state) => - const SpotubePage(child: LibraryPage()), - routes: [ - GoRoute( - path: "generate", - pageBuilder: (context, state) => - const SpotubePage(child: PlaylistGeneratorPage()), - routes: [ - GoRoute( - path: "result", - pageBuilder: (context, state) => SpotubePage( - child: PlaylistGenerateResultPage( - state: state.extra as GeneratePlaylistProviderInput, - ), + path: "/library", + name: LibraryPage.name, + pageBuilder: (context, state) => + const SpotubePage(child: LibraryPage()), + routes: [ + GoRoute( + path: "generate", + name: PlaylistGeneratorPage.name, + pageBuilder: (context, state) => + const SpotubePage(child: PlaylistGeneratorPage()), + routes: [ + GoRoute( + path: "result", + name: PlaylistGenerateResultPage.name, + pageBuilder: (context, state) => SpotubePage( + child: PlaylistGenerateResultPage( + state: state.extra as GeneratePlaylistProviderInput, ), ), - ]), - ]), + ), + ]), + ], + ), GoRoute( path: "/lyrics", - name: "Lyrics", + name: LyricsPage.name, pageBuilder: (context, state) => const SpotubePage(child: LyricsPage()), ), GoRoute( path: "/settings", + name: SettingsPage.name, pageBuilder: (context, state) => const SpotubePage( child: SettingsPage(), ), routes: [ GoRoute( path: "blacklist", + name: BlackListPage.name, pageBuilder: (context, state) => SpotubeSlidePage( child: const BlackListPage(), ), @@ -135,12 +145,14 @@ final routerProvider = Provider((ref) { if (!kIsWeb) GoRoute( path: "logs", + name: LogsPage.name, pageBuilder: (context, state) => SpotubeSlidePage( child: const LogsPage(), ), ), GoRoute( path: "about", + name: AboutSpotube.name, pageBuilder: (context, state) => SpotubeSlidePage( child: const AboutSpotube(), ), @@ -149,6 +161,7 @@ final routerProvider = Provider((ref) { ), GoRoute( path: "/album/:id", + name: AlbumPage.name, pageBuilder: (context, state) { assert(state.extra is AlbumSimple); return SpotubePage( @@ -158,6 +171,7 @@ final routerProvider = Provider((ref) { ), GoRoute( path: "/artist/:id", + name: ArtistPage.name, pageBuilder: (context, state) { assert(state.pathParameters["id"] != null); return SpotubePage( @@ -166,6 +180,7 @@ final routerProvider = Provider((ref) { ), GoRoute( path: "/playlist/:id", + name: PlaylistPage.name, pageBuilder: (context, state) { assert(state.extra is PlaylistSimple); return SpotubePage( @@ -177,6 +192,7 @@ final routerProvider = Provider((ref) { ), GoRoute( path: "/track/:id", + name: TrackPage.name, pageBuilder: (context, state) { final id = state.pathParameters["id"]!; return SpotubePage( @@ -186,12 +202,14 @@ final routerProvider = Provider((ref) { ), GoRoute( path: "/connect", + name: ConnectPage.name, pageBuilder: (context, state) => const SpotubePage( child: ConnectPage(), ), routes: [ GoRoute( path: "control", + name: ConnectControlPage.name, pageBuilder: (context, state) { return const SpotubePage( child: ConnectControlPage(), @@ -202,13 +220,22 @@ final routerProvider = Provider((ref) { ), GoRoute( path: "/profile", + name: ProfilePage.name, pageBuilder: (context, state) => const SpotubePage(child: ProfilePage()), + ), + GoRoute( + path: "/stats", + name: StatsPage.name, + pageBuilder: (context, state) => const SpotubePage( + child: StatsPage(), + ), ) ], ), GoRoute( path: "/mini-player", + name: MiniLyricsPage.name, parentNavigatorKey: rootNavigatorKey, pageBuilder: (context, state) => SpotubePage( child: MiniLyricsPage(prevSize: state.extra as Size), @@ -216,6 +243,7 @@ final routerProvider = Provider((ref) { ), GoRoute( path: "/getting-started", + name: GettingStarting.name, parentNavigatorKey: rootNavigatorKey, pageBuilder: (context, state) => const SpotubePage( child: GettingStarting(), @@ -223,6 +251,7 @@ final routerProvider = Provider((ref) { ), GoRoute( path: "/login", + name: WebViewLogin.name, parentNavigatorKey: rootNavigatorKey, pageBuilder: (context, state) => SpotubePage( child: kIsMobile ? const WebViewLogin() : const DesktopLoginPage(), @@ -230,6 +259,7 @@ final routerProvider = Provider((ref) { ), GoRoute( path: "/login-tutorial", + name: LoginTutorial.name, parentNavigatorKey: rootNavigatorKey, pageBuilder: (context, state) => const SpotubePage( child: LoginTutorial(), @@ -237,6 +267,7 @@ final routerProvider = Provider((ref) { ), GoRoute( path: "/lastfm-login", + name: LastFMLoginPage.name, parentNavigatorKey: rootNavigatorKey, pageBuilder: (context, state) => const SpotubePage(child: LastFMLoginPage()), diff --git a/lib/collections/side_bar_tiles.dart b/lib/collections/side_bar_tiles.dart index 551d70d7..b52227e8 100644 --- a/lib/collections/side_bar_tiles.dart +++ b/lib/collections/side_bar_tiles.dart @@ -1,32 +1,82 @@ import 'package:flutter/material.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:spotube/pages/home/home.dart'; +import 'package:spotube/pages/library/library.dart'; +import 'package:spotube/pages/lyrics/lyrics.dart'; +import 'package:spotube/pages/search/search.dart'; +import 'package:spotube/pages/settings/settings.dart'; +import 'package:spotube/pages/stats/stats.dart'; class SideBarTiles { final IconData icon; final String title; final String id; - SideBarTiles({required this.icon, required this.title, required this.id}); + final String name; + + SideBarTiles({ + required this.icon, + required this.title, + required this.id, + required this.name, + }); } List getSidebarTileList(AppLocalizations l10n) => [ - SideBarTiles(id: "browse", icon: SpotubeIcons.home, title: l10n.browse), - SideBarTiles(id: "search", icon: SpotubeIcons.search, title: l10n.search), SideBarTiles( - id: "library", icon: SpotubeIcons.library, title: l10n.library), - SideBarTiles(id: "lyrics", icon: SpotubeIcons.music, title: l10n.lyrics), - ]; - -List getNavbarTileList(AppLocalizations l10n) => [ - SideBarTiles(id: "browse", icon: SpotubeIcons.home, title: l10n.browse), - SideBarTiles(id: "search", icon: SpotubeIcons.search, title: l10n.search), + id: "browse", + name: HomePage.name, + icon: SpotubeIcons.home, + title: l10n.browse, + ), + SideBarTiles( + id: "search", + name: SearchPage.name, + icon: SpotubeIcons.search, + title: l10n.search, + ), SideBarTiles( id: "library", + name: LibraryPage.name, icon: SpotubeIcons.library, title: l10n.library, ), + SideBarTiles( + id: "lyrics", + name: LyricsPage.name, + icon: SpotubeIcons.music, + title: l10n.lyrics, + ), + SideBarTiles( + id: "stats", + name: StatsPage.name, + icon: SpotubeIcons.chart, + title: l10n.stats, + ), + ]; + +List getNavbarTileList(AppLocalizations l10n) => [ + SideBarTiles( + id: "browse", + name: HomePage.name, + icon: SpotubeIcons.home, + title: l10n.browse, + ), + SideBarTiles( + id: "library", + name: LibraryPage.name, + icon: SpotubeIcons.library, + title: l10n.library, + ), + SideBarTiles( + id: "stats", + name: StatsPage.name, + icon: SpotubeIcons.chart, + title: l10n.stats, + ), SideBarTiles( id: "settings", + name: SettingsPage.name, icon: SpotubeIcons.settings, title: l10n.settings, ) diff --git a/lib/collections/spotube_icons.dart b/lib/collections/spotube_icons.dart index 6de21284..096a1a5f 100644 --- a/lib/collections/spotube_icons.dart +++ b/lib/collections/spotube_icons.dart @@ -121,4 +121,5 @@ abstract class SpotubeIcons { static const monitor = FeatherIcons.monitor; static const power = FeatherIcons.power; static const bluetooth = FeatherIcons.bluetooth; + static const chart = FeatherIcons.barChart2; } diff --git a/lib/components/album/album_card.dart b/lib/components/album/album_card.dart index b7093b60..7212a574 100644 --- a/lib/components/album/album_card.dart +++ b/lib/components/album/album_card.dart @@ -9,6 +9,7 @@ import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/image.dart'; import 'package:spotube/extensions/track.dart'; import 'package:spotube/models/connect/connect.dart'; +import 'package:spotube/pages/album/album.dart'; import 'package:spotube/provider/connect/connect.dart'; import 'package:spotube/provider/history/history.dart'; import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; @@ -64,7 +65,14 @@ class AlbumCard extends HookConsumerWidget { description: "${album.albumType?.formatted} • ${album.artists?.asString() ?? ""}", onTap: () { - ServiceUtils.push(context, "/album/${album.id}", extra: album); + ServiceUtils.pushNamed( + context, + AlbumPage.name, + pathParameters: { + "id": album.id!, + }, + extra: album, + ); }, onPlaybuttonPressed: () async { updating.value = true; diff --git a/lib/components/artist/artist_card.dart b/lib/components/artist/artist_card.dart index cc8485d5..57971ada 100644 --- a/lib/components/artist/artist_card.dart +++ b/lib/components/artist/artist_card.dart @@ -9,6 +9,7 @@ import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/image.dart'; import 'package:spotube/hooks/utils/use_breakpoint_value.dart'; import 'package:spotube/hooks/utils/use_brightness_value.dart'; +import 'package:spotube/pages/artist/artist.dart'; import 'package:spotube/provider/blacklist_provider.dart'; import 'package:spotube/utils/service_utils.dart'; @@ -63,7 +64,13 @@ class ArtistCard extends HookConsumerWidget { ), child: InkWell( onTap: () { - ServiceUtils.push(context, "/artist/${artist.id}"); + ServiceUtils.pushNamed( + context, + ArtistPage.name, + pathParameters: { + "id": artist.id!, + }, + ); }, borderRadius: radius, child: Padding( diff --git a/lib/components/connect/connect_device.dart b/lib/components/connect/connect_device.dart index 3ac585df..f4888534 100644 --- a/lib/components/connect/connect_device.dart +++ b/lib/components/connect/connect_device.dart @@ -3,6 +3,7 @@ import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/extensions/context.dart'; +import 'package:spotube/pages/connect/connect.dart'; import 'package:spotube/provider/connect/clients.dart'; import 'package:spotube/utils/service_utils.dart'; @@ -22,7 +23,7 @@ class ConnectDeviceButton extends HookConsumerWidget { width: double.infinity, child: TextButton( onPressed: () { - ServiceUtils.push(context, "/connect"); + ServiceUtils.pushNamed(context, ConnectPage.name); }, style: FilledButton.styleFrom( shape: RoundedRectangleBorder( @@ -59,7 +60,7 @@ class ConnectDeviceButton extends HookConsumerWidget { clipBehavior: Clip.hardEdge, child: InkWell( onTap: () { - ServiceUtils.push(context, "/connect"); + ServiceUtils.pushNamed(context, ConnectPage.name); }, borderRadius: BorderRadius.circular(50), child: Ink( @@ -111,7 +112,7 @@ class ConnectDeviceButton extends HookConsumerWidget { foregroundColor: colorScheme.onPrimary, ), onPressed: () { - ServiceUtils.push(context, "/connect"); + ServiceUtils.pushNamed(context, ConnectPage.name); }, ), ), diff --git a/lib/components/home/sections/feed.dart b/lib/components/home/sections/feed.dart index 793cd2c3..f3f632ce 100644 --- a/lib/components/home/sections/feed.dart +++ b/lib/components/home/sections/feed.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/shared/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart'; +import 'package:spotube/pages/home/feed/feed_section.dart'; import 'package:spotube/provider/spotify/views/home.dart'; import 'package:spotube/utils/service_utils.dart'; @@ -41,8 +42,13 @@ class HomePageFeedSection extends HookConsumerWidget { child: TextButton.icon( label: const Text("Browse More"), icon: const Icon(SpotubeIcons.angleRight), - onPressed: () => - ServiceUtils.push(context, "/feeds/${section.uri}"), + onPressed: () => ServiceUtils.pushNamed( + context, + HomeFeedSectionPage.name, + pathParameters: { + "feedId": section.uri, + }, + ), ), ), ); diff --git a/lib/components/home/sections/friends/friend_item.dart b/lib/components/home/sections/friends/friend_item.dart index b883e2cc..2b575756 100644 --- a/lib/components/home/sections/friends/friend_item.dart +++ b/lib/components/home/sections/friends/friend_item.dart @@ -6,6 +6,9 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/shared/image/universal_image.dart'; import 'package:spotube/models/spotify_friends.dart'; +import 'package:spotube/pages/album/album.dart'; +import 'package:spotube/pages/artist/artist.dart'; +import 'package:spotube/pages/track/track.dart'; import 'package:spotube/provider/spotify_provider.dart'; class FriendItem extends HookConsumerWidget { @@ -57,7 +60,9 @@ class FriendItem extends HookConsumerWidget { text: friend.track.name, recognizer: TapGestureRecognizer() ..onTap = () { - context.push("/track/${friend.track.id}"); + context.pushNamed(TrackPage.name, pathParameters: { + "id": friend.track.id, + }); }, ), const TextSpan(text: " • "), @@ -71,8 +76,12 @@ class FriendItem extends HookConsumerWidget { text: " ${friend.track.artist.name}", recognizer: TapGestureRecognizer() ..onTap = () { - context.push( - "/artist/${friend.track.artist.id}", + context.pushNamed( + ArtistPage.name, + pathParameters: { + "id": friend.track.artist.id, + }, + extra: friend.track.artist, ); }, ), @@ -105,8 +114,11 @@ class FriendItem extends HookConsumerWidget { final album = await spotify.albums.get(friend.track.album.id); if (context.mounted) { - context.push( - "/album/${friend.track.album.id}", + context.pushNamed( + AlbumPage.name, + pathParameters: { + "id": friend.track.album.id, + }, extra: album, ); } diff --git a/lib/components/home/sections/genres.dart b/lib/components/home/sections/genres.dart index ac2644f0..edab6db2 100644 --- a/lib/components/home/sections/genres.dart +++ b/lib/components/home/sections/genres.dart @@ -13,6 +13,8 @@ import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/shared/image/universal_image.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; +import 'package:spotube/pages/home/genres/genre_playlists.dart'; +import 'package:spotube/pages/home/genres/genres.dart'; import 'package:spotube/provider/spotify/spotify.dart'; class HomeGenresSection extends HookConsumerWidget { @@ -50,7 +52,7 @@ class HomeGenresSection extends HookConsumerWidget { textDirection: TextDirection.rtl, child: TextButton.icon( onPressed: () { - context.push('/genres'); + context.pushNamed(GenrePage.name); }, icon: const Icon(SpotubeIcons.angleRight), label: Text( @@ -110,7 +112,13 @@ class HomeGenresSection extends HookConsumerWidget { return InkWell( onTap: () { - context.push('/genre/${category.id}', extra: category); + context.pushNamed( + GenrePlaylistsPage.name, + pathParameters: { + "categoryId": category.id!, + }, + extra: category, + ); }, borderRadius: BorderRadius.circular(8), child: Ink( diff --git a/lib/components/playlist/playlist_card.dart b/lib/components/playlist/playlist_card.dart index 8aaf4b61..72e13b26 100644 --- a/lib/components/playlist/playlist_card.dart +++ b/lib/components/playlist/playlist_card.dart @@ -6,6 +6,7 @@ import 'package:spotube/components/shared/dialogs/select_device_dialog.dart'; import 'package:spotube/components/shared/playbutton_card.dart'; import 'package:spotube/extensions/image.dart'; import 'package:spotube/models/connect/connect.dart'; +import 'package:spotube/pages/playlist/playlist.dart'; import 'package:spotube/provider/connect/connect.dart'; import 'package:spotube/provider/history/history.dart'; import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; @@ -58,9 +59,12 @@ class PlaylistCard extends HookConsumerWidget { isOwner: playlist.owner?.id == me.asData?.value.id && me.asData?.value.id != null, onTap: () { - ServiceUtils.push( + ServiceUtils.pushNamed( context, - "/playlist/${playlist.id}", + PlaylistPage.name, + pathParameters: { + "id": playlist.id!, + }, extra: playlist, ); }, diff --git a/lib/components/root/sidebar.dart b/lib/components/root/sidebar.dart index a100ca8e..00f45dae 100644 --- a/lib/components/root/sidebar.dart +++ b/lib/components/root/sidebar.dart @@ -16,6 +16,8 @@ import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/image.dart'; import 'package:spotube/hooks/utils/use_brightness_value.dart'; import 'package:spotube/hooks/controllers/use_sidebarx_controller.dart'; +import 'package:spotube/pages/profile/profile.dart'; +import 'package:spotube/pages/settings/settings.dart'; import 'package:spotube/provider/download_manager_provider.dart'; import 'package:spotube/provider/authentication_provider.dart'; import 'package:spotube/provider/spotify/spotify.dart'; @@ -26,13 +28,9 @@ import 'package:spotube/utils/platform.dart'; import 'package:spotube/utils/service_utils.dart'; class Sidebar extends HookConsumerWidget { - final int? selectedIndex; - final void Function(int) onSelectedIndexChanged; final Widget child; const Sidebar({ - required this.selectedIndex, - required this.onSelectedIndexChanged, required this.child, super.key, }); @@ -47,12 +45,9 @@ class Sidebar extends HookConsumerWidget { ); } - static void goToSettings(BuildContext context) { - GoRouter.of(context).go("/settings"); - } - @override Widget build(BuildContext context, WidgetRef ref) { + final routerState = GoRouterState.of(context); final mediaQuery = MediaQuery.of(context); final downloadCount = ref.watch(downloadManagerProvider).$downloadCount; @@ -60,8 +55,17 @@ class Sidebar extends HookConsumerWidget { final layoutMode = ref.watch(userPreferencesProvider.select((s) => s.layoutMode)); + final sidebarTileList = useMemoized( + () => getSidebarTileList(context.l10n), + [context.l10n], + ); + + final selectedIndex = sidebarTileList.indexWhere( + (e) => routerState.namedLocation(e.name) == routerState.matchedLocation, + ); + final controller = useSidebarXController( - selectedIndex: selectedIndex ?? 0, + selectedIndex: selectedIndex, extended: mediaQuery.lgAndUp, ); @@ -73,29 +77,6 @@ class Sidebar extends HookConsumerWidget { Color.lerp(bg, Colors.black, 0.45)!, ); - final sidebarTileList = useMemoized( - () => getSidebarTileList(context.l10n), - [context.l10n], - ); - - useEffect(() { - if (controller.selectedIndex != selectedIndex && selectedIndex != null) { - controller.selectIndex(selectedIndex!); - } - return null; - }, [selectedIndex]); - - useEffect(() { - void listener() { - onSelectedIndexChanged(controller.selectedIndex); - } - - controller.addListener(listener); - return () { - controller.removeListener(listener); - }; - }, [controller]); - useEffect(() { if (!context.mounted) return; if (mediaQuery.lgAndUp && !controller.extended) { @@ -106,6 +87,13 @@ class Sidebar extends HookConsumerWidget { return null; }, [mediaQuery, controller]); + useEffect(() { + if (controller.selectedIndex != selectedIndex) { + controller.selectIndex(selectedIndex); + } + return null; + }, [selectedIndex]); + if (layoutMode == LayoutMode.compact || (mediaQuery.smAndDown && layoutMode == LayoutMode.adaptive)) { return Scaffold(body: child); @@ -119,23 +107,28 @@ class Sidebar extends HookConsumerWidget { items: sidebarTileList.mapIndexed( (index, e) { return SidebarXItem( - iconWidget: Badge( - backgroundColor: theme.colorScheme.primary, - isLabelVisible: e.title == "Library" && downloadCount > 0, - label: Text( - downloadCount.toString(), - style: const TextStyle( - color: Colors.white, - fontSize: 10, + onTap: () { + context.goNamed(e.name); + }, + iconBuilder: (selected, hovered) { + return Badge( + backgroundColor: theme.colorScheme.primary, + isLabelVisible: e.title == "Library" && downloadCount > 0, + label: Text( + downloadCount.toString(), + style: const TextStyle( + color: Colors.white, + fontSize: 10, + ), ), - ), - child: Icon( - e.icon, - color: selectedIndex == index - ? theme.colorScheme.primary - : null, - ), - ), + child: Icon( + e.icon, + color: selected || hovered + ? theme.colorScheme.primary + : null, + ), + ); + }, label: e.title, ); }, @@ -257,7 +250,7 @@ class SidebarFooter extends HookConsumerWidget { if (mediaQuery.mdAndDown) { return IconButton( icon: const Icon(SpotubeIcons.settings), - onPressed: () => Sidebar.goToSettings(context), + onPressed: () => ServiceUtils.navigateNamed(context, SettingsPage.name), ); } @@ -278,7 +271,7 @@ class SidebarFooter extends HookConsumerWidget { Flexible( child: InkWell( onTap: () { - ServiceUtils.push(context, "/profile"); + ServiceUtils.pushNamed(context, ProfilePage.name); }, borderRadius: BorderRadius.circular(30), child: Row( @@ -310,7 +303,7 @@ class SidebarFooter extends HookConsumerWidget { IconButton( icon: const Icon(SpotubeIcons.settings), onPressed: () { - Sidebar.goToSettings(context); + ServiceUtils.navigateNamed(context, SettingsPage.name); }, ), ], diff --git a/lib/components/root/spotube_navigation_bar.dart b/lib/components/root/spotube_navigation_bar.dart index 489399e5..0601a37a 100644 --- a/lib/components/root/spotube_navigation_bar.dart +++ b/lib/components/root/spotube_navigation_bar.dart @@ -3,39 +3,35 @@ import 'dart:ui'; import 'package:curved_navigation_bar/curved_navigation_bar.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotube/collections/side_bar_tiles.dart'; -import 'package:spotube/components/root/sidebar.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/hooks/utils/use_brightness_value.dart'; import 'package:spotube/provider/download_manager_provider.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; import 'package:spotube/provider/user_preferences/user_preferences_state.dart'; +import 'package:spotube/utils/service_utils.dart'; final navigationPanelHeight = StateProvider((ref) => 50); class SpotubeNavigationBar extends HookConsumerWidget { - final int? selectedIndex; - final void Function(int) onSelectedIndexChanged; - const SpotubeNavigationBar({ - required this.selectedIndex, - required this.onSelectedIndexChanged, super.key, }); @override Widget build(BuildContext context, ref) { final theme = Theme.of(context); + final routerState = GoRouterState.of(context); + final downloadCount = ref.watch(downloadManagerProvider).$downloadCount; final mediaQuery = MediaQuery.of(context); final layoutMode = ref.watch(userPreferencesProvider.select((s) => s.layoutMode)); - final insideSelectedIndex = useState(selectedIndex ?? 0); - final buttonColor = useBrightnessValue( theme.colorScheme.inversePrimary, theme.colorScheme.primary.withOpacity(0.2), @@ -46,12 +42,9 @@ class SpotubeNavigationBar extends HookConsumerWidget { final panelHeight = ref.watch(navigationPanelHeight); - useEffect(() { - if (selectedIndex != null) { - insideSelectedIndex.value = selectedIndex!; - } - return null; - }, [selectedIndex]); + final selectedIndex = navbarTileList.indexWhere( + (e) => routerState.namedLocation(e.name) == routerState.matchedLocation, + ); if (layoutMode == LayoutMode.extended || (mediaQuery.mdAndUp && layoutMode == LayoutMode.adaptive) || @@ -91,14 +84,9 @@ class SpotubeNavigationBar extends HookConsumerWidget { }); }, ).toList(), - index: insideSelectedIndex.value, + index: selectedIndex, onTap: (i) { - insideSelectedIndex.value = i; - if (navbarTileList[i].id == "settings") { - Sidebar.goToSettings(context); - return; - } - onSelectedIndexChanged(i); + ServiceUtils.navigateNamed(context, navbarTileList[i].name); }, ), ), diff --git a/lib/components/shared/fallbacks/anonymous_fallback.dart b/lib/components/shared/fallbacks/anonymous_fallback.dart index 2f06b0b6..5ced6bb6 100644 --- a/lib/components/shared/fallbacks/anonymous_fallback.dart +++ b/lib/components/shared/fallbacks/anonymous_fallback.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:spotube/extensions/context.dart'; +import 'package:spotube/pages/settings/settings.dart'; import 'package:spotube/provider/authentication_provider.dart'; import 'package:spotube/utils/service_utils.dart'; @@ -25,7 +26,7 @@ class AnonymousFallback extends ConsumerWidget { const SizedBox(height: 10), FilledButton( child: Text(context.l10n.login_with_spotify), - onPressed: () => ServiceUtils.push(context, "/settings"), + onPressed: () => ServiceUtils.pushNamed(context, SettingsPage.name), ) ], ), diff --git a/lib/components/shared/links/artist_link.dart b/lib/components/shared/links/artist_link.dart index af8b186a..5236a061 100644 --- a/lib/components/shared/links/artist_link.dart +++ b/lib/components/shared/links/artist_link.dart @@ -1,6 +1,7 @@ import 'package:flutter/widgets.dart'; import 'package:spotify/spotify.dart'; import 'package:spotube/components/shared/links/anchor_button.dart'; +import 'package:spotube/pages/artist/artist.dart'; import 'package:spotube/utils/service_utils.dart'; class ArtistLink extends StatelessWidget { @@ -40,9 +41,12 @@ class ArtistLink extends StatelessWidget { if (onRouteChange != null) { onRouteChange?.call("/artist/${artist.value.id}"); } else { - ServiceUtils.push( + ServiceUtils.pushNamed( context, - "/artist/${artist.value.id}", + ArtistPage.name, + pathParameters: { + "id": artist.value.id!, + }, ); } }, diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 832862c0..01539ece 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -320,5 +320,6 @@ "select": "Select", "connect_client_alert": "You're being controlled by {client}", "this_device": "This Device", - "remote": "Remote" + "remote": "Remote", + "stats": "Stats" } \ No newline at end of file diff --git a/lib/pages/album/album.dart b/lib/pages/album/album.dart index 8461b1f1..aea890a0 100644 --- a/lib/pages/album/album.dart +++ b/lib/pages/album/album.dart @@ -8,6 +8,8 @@ import 'package:spotube/extensions/image.dart'; import 'package:spotube/provider/spotify/spotify.dart'; class AlbumPage extends HookConsumerWidget { + static const name = "album"; + final AlbumSimple album; const AlbumPage({ super.key, diff --git a/lib/pages/artist/artist.dart b/lib/pages/artist/artist.dart index c3b04691..49890949 100644 --- a/lib/pages/artist/artist.dart +++ b/lib/pages/artist/artist.dart @@ -15,6 +15,8 @@ import 'package:spotube/pages/artist/section/top_tracks.dart'; import 'package:spotube/provider/spotify/spotify.dart'; class ArtistPage extends HookConsumerWidget { + static const name = "artist"; + final String artistId; final logger = getLogger(ArtistPage); ArtistPage(this.artistId, {super.key}); diff --git a/lib/pages/connect/connect.dart b/lib/pages/connect/connect.dart index cbdb446e..c7cb493a 100644 --- a/lib/pages/connect/connect.dart +++ b/lib/pages/connect/connect.dart @@ -5,10 +5,13 @@ import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/connect/local_devices.dart'; import 'package:spotube/components/shared/page_window_title_bar.dart'; import 'package:spotube/extensions/context.dart'; +import 'package:spotube/pages/connect/control/control.dart'; import 'package:spotube/provider/connect/clients.dart'; import 'package:spotube/utils/service_utils.dart'; class ConnectPage extends HookConsumerWidget { + static const name = "connect"; + const ConnectPage({super.key}); @override @@ -65,9 +68,9 @@ class ConnectPage extends HookConsumerWidget { selected: selected, onTap: () { if (selected) { - ServiceUtils.push( + ServiceUtils.pushNamed( context, - "/connect/control", + ConnectControlPage.name, ); } else { connectClientsNotifier.resolveService(device); diff --git a/lib/pages/connect/control/control.dart b/lib/pages/connect/control/control.dart index b78f0ed3..639a9dd9 100644 --- a/lib/pages/connect/control/control.dart +++ b/lib/pages/connect/control/control.dart @@ -13,6 +13,7 @@ import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/duration.dart'; import 'package:spotube/extensions/image.dart'; +import 'package:spotube/pages/track/track.dart'; import 'package:spotube/provider/connect/clients.dart'; import 'package:spotube/provider/connect/connect.dart'; import 'package:spotube/services/audio_player/loop_mode.dart'; @@ -46,6 +47,8 @@ class RemotePlayerQueue extends ConsumerWidget { } class ConnectControlPage extends HookConsumerWidget { + static const name = "connect_control"; + const ConnectControlPage({super.key}); @override @@ -125,9 +128,13 @@ class ConnectControlPage extends HookConsumerWidget { playlist.activeTrack?.name ?? "", style: textTheme.titleLarge!, onTap: () { - ServiceUtils.push( + if (playlist.activeTrack == null) return; + ServiceUtils.pushNamed( context, - "/track/${playlist.activeTrack?.id}", + TrackPage.name, + pathParameters: { + "id": playlist.activeTrack!.id!, + }, ); }, ), diff --git a/lib/pages/desktop_login/desktop_login.dart b/lib/pages/desktop_login/desktop_login.dart index 9c061091..9c9bdddb 100644 --- a/lib/pages/desktop_login/desktop_login.dart +++ b/lib/pages/desktop_login/desktop_login.dart @@ -7,8 +7,10 @@ import 'package:spotube/components/desktop_login/login_form.dart'; import 'package:spotube/components/shared/page_window_title_bar.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; +import 'package:spotube/pages/mobile_login/mobile_login.dart'; class DesktopLoginPage extends HookConsumerWidget { + static const name = WebViewLogin.name; const DesktopLoginPage({super.key}); @override diff --git a/lib/pages/desktop_login/login_tutorial.dart b/lib/pages/desktop_login/login_tutorial.dart index 83b04af1..dbec28dc 100644 --- a/lib/pages/desktop_login/login_tutorial.dart +++ b/lib/pages/desktop_login/login_tutorial.dart @@ -8,10 +8,12 @@ import 'package:spotube/components/desktop_login/login_form.dart'; import 'package:spotube/components/shared/links/hyper_link.dart'; import 'package:spotube/components/shared/page_window_title_bar.dart'; import 'package:spotube/extensions/context.dart'; +import 'package:spotube/pages/home/home.dart'; import 'package:spotube/provider/authentication_provider.dart'; import 'package:spotube/utils/service_utils.dart'; class LoginTutorial extends ConsumerWidget { + static const name = "login_tutorial"; const LoginTutorial({super.key}); @override @@ -53,7 +55,7 @@ class LoginTutorial extends ConsumerWidget { overrideDone: FilledButton( onPressed: authenticationNotifier.isLoggedIn ? () { - ServiceUtils.push(context, "/"); + ServiceUtils.pushNamed(context, HomePage.name); } : null, child: Center(child: Text(context.l10n.done)), diff --git a/lib/pages/getting_started/getting_started.dart b/lib/pages/getting_started/getting_started.dart index cbab03b9..fa205403 100644 --- a/lib/pages/getting_started/getting_started.dart +++ b/lib/pages/getting_started/getting_started.dart @@ -12,6 +12,8 @@ import 'package:spotube/provider/user_preferences/user_preferences_provider.dart import 'package:spotube/themes/theme.dart'; class GettingStarting extends HookConsumerWidget { + static const name = "getting_started"; + const GettingStarting({super.key}); @override diff --git a/lib/pages/getting_started/sections/support.dart b/lib/pages/getting_started/sections/support.dart index 46823425..ec29de24 100644 --- a/lib/pages/getting_started/sections/support.dart +++ b/lib/pages/getting_started/sections/support.dart @@ -5,6 +5,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/getting_started/blur_card.dart'; import 'package:spotube/extensions/context.dart'; +import 'package:spotube/pages/mobile_login/mobile_login.dart'; import 'package:spotube/services/kv_store/kv_store.dart'; import 'package:url_launcher/url_launcher_string.dart'; @@ -120,7 +121,7 @@ class GettingStartedScreenSupportSection extends HookConsumerWidget { onPressed: () async { await KVStoreService.setDoneGettingStarted(true); if (context.mounted) { - context.push("/login"); + context.pushNamed(WebViewLogin.name); } }, ), diff --git a/lib/pages/home/feed/feed_section.dart b/lib/pages/home/feed/feed_section.dart index c945251c..d31b8256 100644 --- a/lib/pages/home/feed/feed_section.dart +++ b/lib/pages/home/feed/feed_section.dart @@ -10,6 +10,8 @@ import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/provider/spotify/views/home_section.dart'; class HomeFeedSectionPage extends HookConsumerWidget { + static const name = "home_feed_section"; + final String sectionUri; const HomeFeedSectionPage({super.key, required this.sectionUri}); diff --git a/lib/pages/home/genres/genre_playlists.dart b/lib/pages/home/genres/genre_playlists.dart index ca4e7238..531ea889 100644 --- a/lib/pages/home/genres/genre_playlists.dart +++ b/lib/pages/home/genres/genre_playlists.dart @@ -15,6 +15,8 @@ import 'package:collection/collection.dart'; import 'package:spotube/utils/platform.dart'; class GenrePlaylistsPage extends HookConsumerWidget { + static const name = "genre_playlists"; + final Category category; const GenrePlaylistsPage({super.key, required this.category}); diff --git a/lib/pages/home/genres/genres.dart b/lib/pages/home/genres/genres.dart index 291ce737..bb84fc16 100644 --- a/lib/pages/home/genres/genres.dart +++ b/lib/pages/home/genres/genres.dart @@ -9,9 +9,11 @@ import 'package:spotube/collections/gradients.dart'; import 'package:spotube/components/shared/page_window_title_bar.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; +import 'package:spotube/pages/home/genres/genre_playlists.dart'; import 'package:spotube/provider/spotify/spotify.dart'; class GenrePage extends HookConsumerWidget { + static const name = "genre"; const GenrePage({super.key}); @override @@ -47,7 +49,13 @@ class GenrePage extends HookConsumerWidget { return InkWell( borderRadius: BorderRadius.circular(8), onTap: () { - context.push("/genre/${category.id}", extra: category); + context.pushNamed( + GenrePlaylistsPage.name, + pathParameters: { + "categoryId": category.id!, + }, + extra: category, + ); }, child: Ink( padding: const EdgeInsets.all(8), diff --git a/lib/pages/home/home.dart b/lib/pages/home/home.dart index 1872f030..dd4c6f73 100644 --- a/lib/pages/home/home.dart +++ b/lib/pages/home/home.dart @@ -3,6 +3,7 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotube/collections/assets.gen.dart'; +import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/connect/connect_device.dart'; import 'package:spotube/components/home/sections/featured.dart'; import 'package:spotube/components/home/sections/feed.dart'; @@ -15,12 +16,15 @@ import 'package:spotube/components/shared/image/universal_image.dart'; import 'package:spotube/components/shared/page_window_title_bar.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/image.dart'; +import 'package:spotube/pages/profile/profile.dart'; +import 'package:spotube/pages/search/search.dart'; import 'package:spotube/provider/authentication_provider.dart'; import 'package:spotube/provider/spotify/spotify.dart'; import 'package:spotube/utils/platform.dart'; import 'package:spotube/utils/service_utils.dart'; class HomePage extends HookConsumerWidget { + static const name = "home"; const HomePage({super.key}); @override @@ -63,11 +67,18 @@ class HomePage extends HookConsumerWidget { padding: EdgeInsets.zero, ), onPressed: () { - ServiceUtils.push(context, "/profile"); + ServiceUtils.pushNamed(context, ProfilePage.name); }, ); }), const Gap(10), + IconButton( + icon: const Icon(SpotubeIcons.search), + onPressed: () { + ServiceUtils.pushNamed(context, SearchPage.name); + }, + ), + const Gap(10), ], ) else if (kIsMacOS) diff --git a/lib/pages/lastfm_login/lastfm_login.dart b/lib/pages/lastfm_login/lastfm_login.dart index b6aeef2e..2baeaad9 100644 --- a/lib/pages/lastfm_login/lastfm_login.dart +++ b/lib/pages/lastfm_login/lastfm_login.dart @@ -10,6 +10,7 @@ import 'package:spotube/extensions/context.dart'; import 'package:spotube/provider/scrobbler_provider.dart'; class LastFMLoginPage extends HookConsumerWidget { + static const name = "lastfm_login"; const LastFMLoginPage({super.key}); @override diff --git a/lib/pages/library/library.dart b/lib/pages/library/library.dart index ccdb6a35..89b22341 100644 --- a/lib/pages/library/library.dart +++ b/lib/pages/library/library.dart @@ -12,6 +12,8 @@ import 'package:spotube/extensions/context.dart'; import 'package:spotube/provider/download_manager_provider.dart'; class LibraryPage extends HookConsumerWidget { + static const name = "library"; + const LibraryPage({super.key}); @override Widget build(BuildContext context, ref) { diff --git a/lib/pages/library/playlist_generate/playlist_generate.dart b/lib/pages/library/playlist_generate/playlist_generate.dart index 5044090d..648e8528 100644 --- a/lib/pages/library/playlist_generate/playlist_generate.dart +++ b/lib/pages/library/playlist_generate/playlist_generate.dart @@ -24,6 +24,8 @@ import 'package:spotube/provider/user_preferences/user_preferences_provider.dart const RecommendationAttribute zeroValues = (min: 0, target: 0, max: 0); class PlaylistGeneratorPage extends HookConsumerWidget { + static const name = "playlist_generator"; + const PlaylistGeneratorPage({super.key}); @override diff --git a/lib/pages/library/playlist_generate/playlist_generate_result.dart b/lib/pages/library/playlist_generate/playlist_generate_result.dart index 01b73267..5ee7ab36 100644 --- a/lib/pages/library/playlist_generate/playlist_generate_result.dart +++ b/lib/pages/library/playlist_generate/playlist_generate_result.dart @@ -10,10 +10,13 @@ import 'package:spotube/components/shared/dialogs/playlist_add_track_dialog.dart import 'package:spotube/components/shared/page_window_title_bar.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/models/spotify/recommendation_seeds.dart'; +import 'package:spotube/pages/playlist/playlist.dart'; import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; import 'package:spotube/provider/spotify/spotify.dart'; class PlaylistGenerateResultPage extends HookConsumerWidget { + static const name = "playlist_generate_result"; + final GeneratePlaylistProviderInput state; const PlaylistGenerateResultPage({ @@ -123,8 +126,11 @@ class PlaylistGenerateResultPage extends HookConsumerWidget { ); if (playlist != null) { - router.go( - '/playlist/${playlist.id}', + router.goNamed( + PlaylistPage.name, + pathParameters: { + "id": playlist.id!, + }, extra: playlist, ); } diff --git a/lib/pages/lyrics/lyrics.dart b/lib/pages/lyrics/lyrics.dart index ca13864a..850eccfa 100644 --- a/lib/pages/lyrics/lyrics.dart +++ b/lib/pages/lyrics/lyrics.dart @@ -23,6 +23,8 @@ import 'package:spotube/utils/platform.dart'; import 'package:spotube/provider/spotify/spotify.dart'; class LyricsPage extends HookConsumerWidget { + static const name = "lyrics"; + final bool isModal; const LyricsPage({super.key, this.isModal = false}); diff --git a/lib/pages/lyrics/mini_lyrics.dart b/lib/pages/lyrics/mini_lyrics.dart index 6d6f75a9..996e190d 100644 --- a/lib/pages/lyrics/mini_lyrics.dart +++ b/lib/pages/lyrics/mini_lyrics.dart @@ -20,6 +20,8 @@ import 'package:spotube/utils/platform.dart'; import 'package:window_manager/window_manager.dart'; class MiniLyricsPage extends HookConsumerWidget { + static const name = "mini_lyrics"; + final Size prevSize; const MiniLyricsPage({super.key, required this.prevSize}); diff --git a/lib/pages/mobile_login/mobile_login.dart b/lib/pages/mobile_login/mobile_login.dart index 0a1ff8b3..1f2df95a 100644 --- a/lib/pages/mobile_login/mobile_login.dart +++ b/lib/pages/mobile_login/mobile_login.dart @@ -7,6 +7,7 @@ import 'package:spotube/provider/authentication_provider.dart'; import 'package:spotube/utils/platform.dart'; class WebViewLogin extends HookConsumerWidget { + static const name = "login"; const WebViewLogin({super.key}); @override diff --git a/lib/pages/playlist/liked_playlist.dart b/lib/pages/playlist/liked_playlist.dart index 8477a215..44e99aea 100644 --- a/lib/pages/playlist/liked_playlist.dart +++ b/lib/pages/playlist/liked_playlist.dart @@ -3,9 +3,12 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotify/spotify.dart'; import 'package:spotube/components/shared/tracks_view/track_view.dart'; import 'package:spotube/components/shared/tracks_view/track_view_props.dart'; +import 'package:spotube/pages/playlist/playlist.dart'; import 'package:spotube/provider/spotify/spotify.dart'; class LikedPlaylistPage extends HookConsumerWidget { + static const name = PlaylistPage.name; + final PlaylistSimple playlist; const LikedPlaylistPage({ super.key, diff --git a/lib/pages/playlist/playlist.dart b/lib/pages/playlist/playlist.dart index 87909061..8fb22458 100644 --- a/lib/pages/playlist/playlist.dart +++ b/lib/pages/playlist/playlist.dart @@ -10,6 +10,8 @@ import 'package:spotube/extensions/image.dart'; import 'package:spotube/provider/spotify/spotify.dart'; class PlaylistPage extends HookConsumerWidget { + static const name = "playlist"; + final PlaylistSimple playlist; const PlaylistPage({ super.key, diff --git a/lib/pages/profile/profile.dart b/lib/pages/profile/profile.dart index 52b69835..d77ae98d 100644 --- a/lib/pages/profile/profile.dart +++ b/lib/pages/profile/profile.dart @@ -14,6 +14,8 @@ import 'package:spotube/provider/spotify/spotify.dart'; import 'package:url_launcher/url_launcher_string.dart'; class ProfilePage extends HookConsumerWidget { + static const name = "profile"; + const ProfilePage({super.key}); @override diff --git a/lib/pages/root/root_app.dart b/lib/pages/root/root_app.dart index f3ed6571..423312a9 100644 --- a/lib/pages/root/root_app.dart +++ b/lib/pages/root/root_app.dart @@ -22,13 +22,6 @@ import 'package:spotube/services/connectivity_adapter.dart'; import 'package:spotube/utils/persisted_state_notifier.dart'; import 'package:spotube/utils/platform.dart'; -const rootPaths = { - "/": 0, - "/search": 1, - "/library": 2, - "/lyrics": 3, -}; - class RootApp extends HookConsumerWidget { final Widget child; const RootApp({ @@ -42,7 +35,6 @@ class RootApp extends HookConsumerWidget { final downloader = ref.watch(downloadManagerProvider); final scaffoldMessenger = ScaffoldMessenger.of(context); final theme = Theme.of(context); - final location = GoRouterState.of(context).matchedLocation; useEffect(() { WidgetsBinding.instance.addPostFrameCallback((_) async { @@ -178,32 +170,17 @@ class RootApp extends HookConsumerWidget { return null; }, [backgroundColor]); - void onSelectIndexChanged(int d) { - final invertedRouteMap = - rootPaths.map((key, value) => MapEntry(value, key)); - - if (context.mounted) { - WidgetsBinding.instance.addPostFrameCallback((_) { - GoRouter.of(context).go(invertedRouteMap[d]!); - }); - } - } - // ignore: deprecated_member_use return WillPopScope( onWillPop: () async { - if (rootPaths[location] != 0) { - onSelectIndexChanged(0); - return false; - } + // if (rootPaths[location] != 0) { + // onSelectIndexChanged(0); + // return false; + // } return true; }, child: Scaffold( - body: Sidebar( - selectedIndex: rootPaths[location], - onSelectedIndexChanged: onSelectIndexChanged, - child: child, - ), + body: Sidebar(child: child), extendBody: true, drawerScrimColor: Colors.transparent, endDrawer: kIsDesktop @@ -237,10 +214,7 @@ class RootApp extends HookConsumerWidget { mainAxisSize: MainAxisSize.min, children: [ BottomPlayer(), - SpotubeNavigationBar( - selectedIndex: rootPaths[location], - onSelectedIndexChanged: onSelectIndexChanged, - ), + const SpotubeNavigationBar(), ], ), ), diff --git a/lib/pages/search/search.dart b/lib/pages/search/search.dart index e9ada236..d5374786 100644 --- a/lib/pages/search/search.dart +++ b/lib/pages/search/search.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart' hide Page; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:fuzzywuzzy/fuzzywuzzy.dart'; import 'package:gap/gap.dart'; +import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotify/spotify.dart'; @@ -26,6 +27,8 @@ import 'package:spotube/services/kv_store/kv_store.dart'; import 'package:spotube/utils/platform.dart'; class SearchPage extends HookConsumerWidget { + static const name = "search"; + const SearchPage({super.key}); @override @@ -85,99 +88,117 @@ class SearchPage extends HookConsumerWidget { return SafeArea( bottom: false, child: Scaffold( - appBar: kIsDesktop && !kIsMacOS ? const PageWindowTitleBar() : null, + appBar: kIsDesktop && !kIsMacOS + ? const PageWindowTitleBar(automaticallyImplyLeading: true) + : null, body: !authenticationNotifier.isLoggedIn ? const AnonymousFallback() : Column( children: [ - Container( - padding: const EdgeInsets.symmetric( - horizontal: 20, - vertical: 10, - ), - color: theme.scaffoldBackgroundColor, - child: SearchAnchor( - searchController: controller, - viewBuilder: (_) => HookBuilder(builder: (context) { - final searchController = useListenable(controller); - final update = useForceUpdate(); - final suggestions = searchController.text.isEmpty - ? KVStoreService.recentSearches - : KVStoreService.recentSearches - .where( - (s) => - weightedRatio( - s.toLowerCase(), - searchController.text.toLowerCase(), - ) > - 50, - ) - .toList(); + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + if ((kIsMobile || kIsMacOS) && context.canPop()) + const BackButton() + else + const Gap(20), + Expanded( + child: Padding( + padding: const EdgeInsets.only( + right: 20, + top: 20, + bottom: 20, + ), + child: SearchAnchor( + searchController: controller, + viewBuilder: (_) => HookBuilder(builder: (context) { + final searchController = + useListenable(controller); + final update = useForceUpdate(); + final suggestions = searchController.text.isEmpty + ? KVStoreService.recentSearches + : KVStoreService.recentSearches + .where( + (s) => + weightedRatio( + s.toLowerCase(), + searchController.text + .toLowerCase(), + ) > + 50, + ) + .toList(); - return ListView.builder( - itemCount: suggestions.length, - itemBuilder: (context, index) { - final suggestion = suggestions[index]; + return ListView.builder( + itemCount: suggestions.length, + itemBuilder: (context, index) { + final suggestion = suggestions[index]; - return ListTile( - leading: const Icon(SpotubeIcons.history), - title: Text(suggestion), - trailing: IconButton( - icon: const Icon(SpotubeIcons.trash), - onPressed: () { - KVStoreService.setRecentSearches( - KVStoreService.recentSearches - .where((s) => s != suggestion) - .toList(), + return ListTile( + leading: const Icon(SpotubeIcons.history), + title: Text(suggestion), + trailing: IconButton( + icon: const Icon(SpotubeIcons.trash), + onPressed: () { + KVStoreService.setRecentSearches( + KVStoreService.recentSearches + .where((s) => s != suggestion) + .toList(), + ); + update(); + }, + ), + onTap: () { + controller.closeView(suggestion); + ref + .read( + searchTermStateProvider.notifier) + .state = suggestion; + }, ); - update(); }, - ), - onTap: () { - controller.closeView(suggestion); - ref - .read(searchTermStateProvider.notifier) - .state = suggestion; - }, - ); - }, - ); - }), - suggestionsBuilder: (context, controller) { - return []; - }, - viewOnSubmitted: (value) async { - controller.closeView(value); - Timer( - const Duration(milliseconds: 50), - () { - ref.read(searchTermStateProvider.notifier).state = - value; - if (value.trim().isEmpty) { - return; - } - KVStoreService.setRecentSearches( - { - value, - ...KVStoreService.recentSearches, - }.toList(), - ); - }, - ); - }, - builder: (context, controller) { - return SearchBar( - autoFocus: queries.none((s) => - s.asData?.value != null && !s.hasError) && - !kIsMobile, - controller: controller, - leading: const Icon(SpotubeIcons.search), - hintText: "${context.l10n.search}...", - onTap: controller.openView, - onChanged: (_) => controller.openView(), - ); - }, - ), + ); + }), + suggestionsBuilder: (context, controller) { + return []; + }, + viewOnSubmitted: (value) async { + controller.closeView(value); + Timer( + const Duration(milliseconds: 50), + () { + ref + .read(searchTermStateProvider.notifier) + .state = value; + if (value.trim().isEmpty) { + return; + } + KVStoreService.setRecentSearches( + { + value, + ...KVStoreService.recentSearches, + }.toList(), + ); + }, + ); + }, + builder: (context, controller) { + return SearchBar( + autoFocus: queries.none((s) => + s.asData?.value != null && + !s.hasError) && + !kIsMobile, + controller: controller, + leading: const Icon(SpotubeIcons.search), + hintText: "${context.l10n.search}...", + onTap: controller.openView, + onChanged: (_) => controller.openView(), + ); + }, + ), + ), + ), + ], ), Expanded( child: AnimatedSwitcher( diff --git a/lib/pages/settings/about.dart b/lib/pages/settings/about.dart index 21b8117b..da8eaaa0 100644 --- a/lib/pages/settings/about.dart +++ b/lib/pages/settings/about.dart @@ -16,6 +16,8 @@ final _licenseProvider = FutureProvider((ref) async { }); class AboutSpotube extends HookConsumerWidget { + static const name = "about"; + const AboutSpotube({super.key}); @override diff --git a/lib/pages/settings/blacklist.dart b/lib/pages/settings/blacklist.dart index 9dd85c50..6eccab07 100644 --- a/lib/pages/settings/blacklist.dart +++ b/lib/pages/settings/blacklist.dart @@ -11,6 +11,8 @@ import 'package:spotube/extensions/context.dart'; import 'package:spotube/provider/blacklist_provider.dart'; class BlackListPage extends HookConsumerWidget { + static const name = "blacklist"; + const BlackListPage({super.key}); @override diff --git a/lib/pages/settings/logs.dart b/lib/pages/settings/logs.dart index b07ebbb1..8b6f7312 100644 --- a/lib/pages/settings/logs.dart +++ b/lib/pages/settings/logs.dart @@ -11,6 +11,8 @@ import 'package:spotube/extensions/context.dart'; import 'package:spotube/models/logger.dart'; class LogsPage extends HookWidget { + static const name = "logs"; + const LogsPage({super.key}); List<({DateTime? date, String body})> parseLogs(String raw) { diff --git a/lib/pages/settings/settings.dart b/lib/pages/settings/settings.dart index d293518d..2949a0d3 100644 --- a/lib/pages/settings/settings.dart +++ b/lib/pages/settings/settings.dart @@ -16,6 +16,8 @@ import 'package:spotube/provider/user_preferences/user_preferences_provider.dart import 'package:spotube/utils/platform.dart'; class SettingsPage extends HookConsumerWidget { + static const name = "settings"; + const SettingsPage({super.key}); @override diff --git a/lib/pages/stats/stats.dart b/lib/pages/stats/stats.dart new file mode 100644 index 00000000..13fe5771 --- /dev/null +++ b/lib/pages/stats/stats.dart @@ -0,0 +1,13 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +class StatsPage extends HookConsumerWidget { + static const name = "stats"; + + const StatsPage({super.key}); + + @override + Widget build(BuildContext context, ref) { + return Container(); + } +} diff --git a/lib/pages/track/track.dart b/lib/pages/track/track.dart index fc90d19a..2109fe6e 100644 --- a/lib/pages/track/track.dart +++ b/lib/pages/track/track.dart @@ -21,6 +21,8 @@ import 'package:spotube/services/audio_player/audio_player.dart'; import 'package:spotube/extensions/constrains.dart'; class TrackPage extends HookConsumerWidget { + static const name = "track"; + final String trackId; const TrackPage({ super.key, diff --git a/lib/utils/service_utils.dart b/lib/utils/service_utils.dart index 88c52896..2d29d93e 100644 --- a/lib/utils/service_utils.dart +++ b/lib/utils/service_utils.dart @@ -262,6 +262,22 @@ abstract class ServiceUtils { GoRouter.of(context).go(location, extra: extra); } + static void navigateNamed( + BuildContext context, + String name, { + Object? extra, + Map? pathParameters, + Map? queryParameters, + }) { + if (GoRouterState.of(context).matchedLocation == name) return; + GoRouter.of(context).goNamed( + name, + pathParameters: pathParameters ?? const {}, + queryParameters: queryParameters ?? const {}, + extra: extra, + ); + } + static void push(BuildContext context, String location, {Object? extra}) { final router = GoRouter.of(context); final routerState = GoRouterState.of(context); @@ -273,6 +289,36 @@ abstract class ServiceUtils { router.push(location, extra: extra); } + static void pushNamed( + BuildContext context, + String name, { + Object? extra, + Map pathParameters = const {}, + Map queryParameters = const {}, + }) { + final router = GoRouter.of(context); + final routerState = GoRouterState.of(context); + final routerStack = router.routerDelegate.currentConfiguration.matches + .map((e) => e.matchedLocation); + + final nameLocation = routerState.namedLocation( + name, + pathParameters: pathParameters, + queryParameters: queryParameters, + ); + + if (routerState.matchedLocation == nameLocation || + routerStack.contains(nameLocation)) { + return; + } + router.pushNamed( + name, + pathParameters: pathParameters, + queryParameters: queryParameters, + extra: extra, + ); + } + static DateTime parseSpotifyAlbumDate(AlbumSimple? album) { if (album == null || album.releaseDate == null) { return DateTime.parse("1975-01-01"); diff --git a/untranslated_messages.json b/untranslated_messages.json index 9e26dfee..eedce4f1 100644 --- a/untranslated_messages.json +++ b/untranslated_messages.json @@ -1 +1,89 @@ -{} \ No newline at end of file +{ + "ar": [ + "stats" + ], + + "bn": [ + "stats" + ], + + "ca": [ + "stats" + ], + + "cs": [ + "stats" + ], + + "de": [ + "stats" + ], + + "es": [ + "stats" + ], + + "fa": [ + "stats" + ], + + "fr": [ + "stats" + ], + + "hi": [ + "stats" + ], + + "it": [ + "stats" + ], + + "ja": [ + "stats" + ], + + "ko": [ + "stats" + ], + + "ne": [ + "stats" + ], + + "nl": [ + "stats" + ], + + "pl": [ + "stats" + ], + + "pt": [ + "stats" + ], + + "ru": [ + "stats" + ], + + "th": [ + "stats" + ], + + "tr": [ + "stats" + ], + + "uk": [ + "stats" + ], + + "vi": [ + "stats" + ], + + "zh": [ + "stats" + ] +}