diff --git a/build.yaml b/build.yaml index 17d5bc50..76771f22 100644 --- a/build.yaml +++ b/build.yaml @@ -4,6 +4,16 @@ targets: exclude: - bin/*.dart builders: + auto_route_generator:auto_route_generator: # this for @RoutePage + options: + enable_cached_builds: true + generate_for: + - lib/pages/**/*.dart + auto_route_generator:auto_router_generator: # this for @AutoRouterConfig + options: + enable_cached_builds: true + generate_for: + - lib/collections/routes.dart json_serializable: options: any_map: true diff --git a/lib/collections/intents.dart b/lib/collections/intents.dart index d0a0c8b6..e4e3fa07 100644 --- a/lib/collections/intents.dart +++ b/lib/collections/intents.dart @@ -3,17 +3,9 @@ import 'dart:io'; import 'package:flutter/cupertino.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:go_router/go_router.dart'; import 'package:spotube/collections/routes.dart'; +import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/modules/player/player_controls.dart'; -import 'package:spotube/pages/home/home.dart'; -import 'package:spotube/pages/library/user_albums.dart'; -import 'package:spotube/pages/library/user_artists.dart'; -import 'package:spotube/pages/library/user_downloads.dart'; -import 'package:spotube/pages/library/user_local_tracks/user_local_tracks.dart'; -import 'package:spotube/pages/library/user_playlists.dart'; -import 'package:spotube/pages/lyrics/lyrics.dart'; -import 'package:spotube/pages/search/search.dart'; import 'package:spotube/provider/audio_player/querying_track_info.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; import 'package:spotube/utils/platform.dart'; @@ -40,7 +32,7 @@ class PlayPauseAction extends Action { } class NavigationIntent extends Intent { - final GoRouter router; + final AppRouter router; final String path; const NavigationIntent(this.router, this.path); } @@ -48,7 +40,7 @@ class NavigationIntent extends Intent { class NavigationAction extends Action { @override invoke(intent) { - intent.router.go(intent.path); + intent.router.navigateNamed(intent.path); return null; } } @@ -66,39 +58,39 @@ enum HomeTabs { } class HomeTabIntent extends Intent { - final WidgetRef ref; + final AppRouter router; final HomeTabs tab; - const HomeTabIntent(this.ref, {required this.tab}); + const HomeTabIntent(this.router, {required this.tab}); } class HomeTabAction extends Action { @override invoke(intent) { - final router = intent.ref.read(routerProvider); + final router = intent.router; switch (intent.tab) { case HomeTabs.browse: - router.goNamed(HomePage.name); + router.navigate(const HomeRoute()); break; case HomeTabs.search: - router.goNamed(SearchPage.name); + router.navigate(const SearchRoute()); break; case HomeTabs.lyrics: - router.goNamed(LyricsPage.name); + router.navigate(LyricsRoute()); break; case HomeTabs.userPlaylists: - router.goNamed(UserPlaylistsPage.name); + router.navigate(const UserPlaylistsRoute()); break; case HomeTabs.userArtists: - router.goNamed(UserArtistsPage.name); + router.navigate(const UserArtistsRoute()); break; case HomeTabs.userAlbums: - router.goNamed(UserAlbumsPage.name); + router.navigate(const UserAlbumsRoute()); break; case HomeTabs.userLocalLibrary: - router.goNamed(UserLocalLibraryPage.name); + router.navigate(const UserLocalLibraryRoute()); break; case HomeTabs.userDownloads: - router.goNamed(UserDownloadsPage.name); + router.navigate(const UserDownloadsRoute()); break; } return null; diff --git a/lib/collections/routes.dart b/lib/collections/routes.dart index 4cd869cd..10b2dc0d 100644 --- a/lib/collections/routes.dart +++ b/lib/collections/routes.dart @@ -1,365 +1,216 @@ -import 'package:flutter/foundation.dart' hide Category; -import 'package:flutter/widgets.dart'; -import 'package:go_router/go_router.dart'; +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:spotify/spotify.dart' hide Search; -import 'package:spotube/models/spotify/recommendation_seeds.dart'; -import 'package:spotube/pages/album/album.dart'; -import 'package:spotube/pages/connect/connect.dart'; -import 'package:spotube/pages/connect/control/control.dart'; -import 'package:spotube/pages/getting_started/getting_started.dart'; -import 'package:spotube/pages/home/feed/feed_section.dart'; -import 'package:spotube/pages/home/genres/genre_playlists.dart'; -import 'package:spotube/pages/home/genres/genres.dart'; -import 'package:spotube/pages/home/home.dart'; -import 'package:spotube/pages/lastfm_login/lastfm_login.dart'; -import 'package:spotube/pages/library/user_local_tracks/local_folder.dart'; -import 'package:spotube/pages/library/playlist_generate/playlist_generate.dart'; -import 'package:spotube/pages/library/playlist_generate/playlist_generate_result.dart'; -import 'package:spotube/pages/library/user_albums.dart'; -import 'package:spotube/pages/library/user_artists.dart'; -import 'package:spotube/pages/library/user_downloads.dart'; -import 'package:spotube/pages/library/user_local_tracks/user_local_tracks.dart'; -import 'package:spotube/pages/library/user_playlists.dart'; -import 'package:spotube/pages/lyrics/mini_lyrics.dart'; -import 'package:spotube/pages/playlist/liked_playlist.dart'; -import 'package:spotube/pages/playlist/playlist.dart'; -import 'package:spotube/pages/profile/profile.dart'; -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/albums/albums.dart'; -import 'package:spotube/pages/stats/artists/artists.dart'; -import 'package:spotube/pages/stats/fees/fees.dart'; -import 'package:spotube/pages/stats/minutes/minutes.dart'; -import 'package:spotube/pages/stats/playlists/playlists.dart'; -import 'package:spotube/pages/stats/stats.dart'; -import 'package:spotube/pages/stats/streams/streams.dart'; -import 'package:spotube/pages/track/track.dart'; +import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/provider/authentication/authentication.dart'; import 'package:spotube/services/kv_store/kv_store.dart'; -import 'package:spotube/components/spotube_page_route.dart'; -import 'package:spotube/pages/artist/artist.dart'; -import 'package:spotube/pages/library/library.dart'; -import 'package:spotube/pages/lyrics/lyrics.dart'; -import 'package:spotube/pages/root/root_app.dart'; -import 'package:spotube/pages/settings/settings.dart'; -import 'package:spotube/pages/mobile_login/mobile_login.dart'; final rootNavigatorKey = GlobalKey(); -final shellRouteNavigatorKey = GlobalKey(); -final routerProvider = Provider((ref) { - return GoRouter( - navigatorKey: rootNavigatorKey, - routes: [ - ShellRoute( - navigatorKey: shellRouteNavigatorKey, - builder: (context, state, child) => RootApp(child: child), - routes: [ - GoRoute( - path: "/", - name: HomePage.name, - redirect: (context, state) async { - final auth = await ref.read(authenticationProvider.future); - if (auth == null && !KVStoreService.doneGettingStarted) { - return "/getting-started"; - } +@AutoRouterConfig(replaceInRouteName: 'Screen|Page,Route') +class AppRouter extends RootStackRouter { + final WidgetRef ref; - return null; - }, - pageBuilder: (context, state) => - const SpotubePage(child: HomePage()), - 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, - ), - ), - ), - GoRoute( - path: "feeds/:feedId", - name: HomeFeedSectionPage.name, - pageBuilder: (context, state) => SpotubePage( - child: HomeFeedSectionPage( - sectionUri: state.pathParameters["feedId"] as String, - ), - ), - ) - ], - ), - GoRoute( - path: "/search", - name: SearchPage.name, - pageBuilder: (context, state) => - const SpotubePage(child: SearchPage()), - ), - ShellRoute( - pageBuilder: (context, state, child) => - SpotubePage(child: LibraryPage(child: child)), - routes: [ - GoRoute( - path: "/library/playlists", - name: UserPlaylistsPage.name, - pageBuilder: (context, state) => - const SpotubePage(child: UserPlaylistsPage()), - ), - GoRoute( - path: "/library/artists", - name: UserArtistsPage.name, - pageBuilder: (context, state) => - const SpotubePage(child: UserArtistsPage()), - ), - GoRoute( - path: "/library/album", - name: UserAlbumsPage.name, - pageBuilder: (context, state) => - const SpotubePage(child: UserAlbumsPage()), - ), - GoRoute( - path: "/library/local", - name: UserLocalLibraryPage.name, - pageBuilder: (context, state) => - const SpotubePage(child: UserLocalLibraryPage()), - routes: [ - GoRoute( - path: "folder", - name: LocalLibraryPage.name, - parentNavigatorKey: shellRouteNavigatorKey, - pageBuilder: (context, state) { - assert(state.extra is String); - return SpotubePage( - child: LocalLibraryPage( - state.extra as String, - isDownloads: - state.uri.queryParameters["downloads"] != null, - isCache: state.uri.queryParameters["cache"] != null, - ), - ); - }, - ), - ]), - GoRoute( - path: "/library/downloads", - name: UserDownloadsPage.name, - pageBuilder: (context, state) => - const SpotubePage(child: UserDownloadsPage()), - ), - ], - ), - GoRoute( - path: "/library/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: 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(), - ), - ), - 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(), - ), - ), - ], - ), - GoRoute( - path: "/album/:id", - name: AlbumPage.name, - pageBuilder: (context, state) { - assert(state.extra is AlbumSimple); - return SpotubePage( - child: AlbumPage(album: state.extra as AlbumSimple), - ); - }, - ), - GoRoute( - path: "/artist/:id", - name: ArtistPage.name, - pageBuilder: (context, state) { - assert(state.pathParameters["id"] != null); - return SpotubePage( - child: ArtistPage(state.pathParameters["id"]!)); - }, - ), - GoRoute( - path: "/playlist/:id", - name: PlaylistPage.name, - pageBuilder: (context, state) { - assert(state.extra is PlaylistSimple); - return SpotubePage( - child: state.pathParameters["id"] == "user-liked-tracks" - ? LikedPlaylistPage(playlist: state.extra as PlaylistSimple) - : PlaylistPage(playlist: state.extra as PlaylistSimple), - ); - }, - ), - GoRoute( - path: "/track/:id", - name: TrackPage.name, - pageBuilder: (context, state) { - final id = state.pathParameters["id"]!; - return SpotubePage( - child: TrackPage(trackId: id), - ); - }, - ), - 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(), - ); - }, - ) - ], - ), - 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(), - ), - routes: [ - GoRoute( - path: "minutes", - name: StatsMinutesPage.name, - pageBuilder: (context, state) => const SpotubePage( - child: StatsMinutesPage(), - ), - ), - GoRoute( - path: "streams", - name: StatsStreamsPage.name, - pageBuilder: (context, state) => const SpotubePage( - child: StatsStreamsPage(), - ), - ), - GoRoute( - path: "fees", - name: StatsStreamFeesPage.name, - pageBuilder: (context, state) => const SpotubePage( - child: StatsStreamFeesPage(), - ), - ), - GoRoute( - path: "artists", - name: StatsArtistsPage.name, - pageBuilder: (context, state) => const SpotubePage( - child: StatsArtistsPage(), - ), - ), - GoRoute( - path: "albums", - name: StatsAlbumsPage.name, - pageBuilder: (context, state) => const SpotubePage( - child: StatsAlbumsPage(), - ), - ), - GoRoute( - path: "playlists", - name: StatsPlaylistsPage.name, - pageBuilder: (context, state) => const SpotubePage( - child: StatsPlaylistsPage(), - ), - ), - ], - ) - ], - ), - GoRoute( - path: "/mini-player", - name: MiniLyricsPage.name, - parentNavigatorKey: rootNavigatorKey, - pageBuilder: (context, state) => SpotubePage( - child: MiniLyricsPage(prevSize: state.extra as Size), + AppRouter(this.ref) : super(navigatorKey: rootNavigatorKey); + + @override + List get guards => [ + AutoRouteGuardCallback( + (resolver, router) async { + final auth = await ref.read(authenticationProvider.future); + + if (auth == null && !KVStoreService.doneGettingStarted) { + resolver.redirect(const GettingStartedRoute()); + } else { + resolver.next(true); + } + }, ), - ), - GoRoute( - path: "/getting-started", - name: GettingStarting.name, - parentNavigatorKey: rootNavigatorKey, - pageBuilder: (context, state) => const SpotubePage( - child: GettingStarting(), + ]; + + @override + List get routes => [ + AutoRoute( + page: RootAppRoute.page, + path: "/", + initial: true, + children: [ + AutoRoute( + path: "home", + page: HomeRoute.page, + initial: true, + ), + AutoRoute( + path: "home/genres", + page: GenreRoute.page, + ), + AutoRoute( + path: "home/genre/:categoryId", + page: GenrePlaylistsRoute.page, + ), + AutoRoute( + path: "home/feeds/:feedId", + page: HomeFeedSectionRoute.page, + ), + AutoRoute( + path: "search", + page: SearchRoute.page, + ), + AutoRoute( + path: "library", + page: LibraryRoute.page, + children: [ + AutoRoute( + path: "playlists", + page: UserPlaylistsRoute.page, + ), + AutoRoute( + path: "artists", + page: UserArtistsRoute.page, + ), + AutoRoute( + path: "albums", + page: UserAlbumsRoute.page, + ), + AutoRoute( + path: "local", + page: UserLocalLibraryRoute.page, + ), + AutoRoute( + path: "local/folder", + page: LocalLibraryRoute.page, + // parentNavigatorKey: shellRouteNavigatorKey, + ), + AutoRoute( + path: "downloads", + page: UserDownloadsRoute.page, + ), + ], + ), + AutoRoute( + path: "library/generate", + page: PlaylistGeneratorRoute.page, + ), + AutoRoute( + path: "library/generate/result", + page: PlaylistGenerateResultRoute.page, + ), + AutoRoute( + path: "lyrics", + page: LyricsRoute.page, + ), + AutoRoute( + path: "settings", + page: SettingsRoute.page, + ), + AutoRoute( + path: "settings/blacklist", + page: BlackListRoute.page, + ), + if (!kIsWeb) + AutoRoute( + path: "settings/logs", + page: LogsRoute.page, + ), + AutoRoute( + path: "settings/about", + page: AboutSpotubeRoute.page, + ), + AutoRoute( + path: "album/:id", + page: AlbumRoute.page, + ), + AutoRoute( + path: "artist/:id", + page: ArtistRoute.page, + ), + AutoRoute( + path: "liked-tracks", + page: LikedPlaylistRoute.page, + ), + AutoRoute( + path: "playlist/:id", + page: PlaylistRoute.page, + guards: [ + AutoRouteGuard.redirect( + (resolver) { + final PlaylistRouteArgs(:id, :playlist) = + resolver.route.args as PlaylistRouteArgs; + if (id == "user-liked-tracks") { + return LikedPlaylistRoute(playlist: playlist); + } + + return null; + }, + ), + ], + ), + AutoRoute( + path: "track/:id", + page: TrackRoute.page, + ), + AutoRoute( + path: "connect", + page: ConnectRoute.page, + ), + AutoRoute( + path: "connect/control", + page: ConnectControlRoute.page, + ), + AutoRoute( + path: "profile", + page: ProfileRoute.page, + ), + AutoRoute( + path: "stats", + page: StatsRoute.page, + ), + AutoRoute( + path: "stats/minutes", + page: StatsMinutesRoute.page, + ), + AutoRoute( + path: "stats/streams", + page: StatsStreamsRoute.page, + ), + AutoRoute( + path: "stats/fees", + page: StatsStreamFeesRoute.page, + ), + AutoRoute( + path: "stats/artists", + page: StatsArtistsRoute.page, + ), + AutoRoute( + path: "stats/albums", + page: StatsAlbumsRoute.page, + ), + AutoRoute( + path: "stats/playlists", + page: StatsPlaylistsRoute.page, + ), + ], ), - ), - GoRoute( - path: "/login", - name: WebViewLogin.name, - parentNavigatorKey: rootNavigatorKey, - pageBuilder: (context, state) => const SpotubePage( - child: WebViewLogin(), + AutoRoute( + path: "/mini-player", + page: MiniLyricsRoute.page, + // parentNavigatorKey: rootNavigatorKey, ), - ), - GoRoute( - path: "/lastfm-login", - name: LastFMLoginPage.name, - parentNavigatorKey: rootNavigatorKey, - pageBuilder: (context, state) => - const SpotubePage(child: LastFMLoginPage()), - ), - ], - ); -}); + AutoRoute( + path: "/getting-started", + page: GettingStartedRoute.page, + // parentNavigatorKey: rootNavigatorKey, + ), + AutoRoute( + path: "/login", + page: WebViewLoginRoute.page, + // parentNavigatorKey: rootNavigatorKey, + ), + AutoRoute( + path: "/lastfm-login", + page: LastFMLoginRoute.page, + // parentNavigatorKey: rootNavigatorKey, + ), + ]; +} diff --git a/lib/collections/routes.gr.dart b/lib/collections/routes.gr.dart new file mode 100644 index 00000000..4572a23d --- /dev/null +++ b/lib/collections/routes.gr.dart @@ -0,0 +1,1143 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ************************************************************************** +// AutoRouterGenerator +// ************************************************************************** + +// ignore_for_file: type=lint +// coverage:ignore-file + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'package:auto_route/auto_route.dart' as _i40; +import 'package:flutter/material.dart' as _i43; +import 'package:flutter/widgets.dart' as _i44; +import 'package:shadcn_flutter/shadcn_flutter.dart' as _i41; +import 'package:spotify/spotify.dart' as _i42; +import 'package:spotube/models/spotify/recommendation_seeds.dart' as _i45; +import 'package:spotube/pages/album/album.dart' as _i2; +import 'package:spotube/pages/artist/artist.dart' as _i3; +import 'package:spotube/pages/connect/connect.dart' as _i6; +import 'package:spotube/pages/connect/control/control.dart' as _i5; +import 'package:spotube/pages/getting_started/getting_started.dart' as _i9; +import 'package:spotube/pages/home/feed/feed_section.dart' as _i10; +import 'package:spotube/pages/home/genres/genre_playlists.dart' as _i8; +import 'package:spotube/pages/home/genres/genres.dart' as _i7; +import 'package:spotube/pages/home/home.dart' as _i11; +import 'package:spotube/pages/lastfm_login/lastfm_login.dart' as _i12; +import 'package:spotube/pages/library/library.dart' as _i13; +import 'package:spotube/pages/library/playlist_generate/playlist_generate.dart' + as _i20; +import 'package:spotube/pages/library/playlist_generate/playlist_generate_result.dart' + as _i19; +import 'package:spotube/pages/library/user_albums.dart' as _i34; +import 'package:spotube/pages/library/user_artists.dart' as _i35; +import 'package:spotube/pages/library/user_downloads.dart' as _i36; +import 'package:spotube/pages/library/user_local_tracks/local_folder.dart' + as _i15; +import 'package:spotube/pages/library/user_local_tracks/user_local_tracks.dart' + as _i37; +import 'package:spotube/pages/library/user_playlists.dart' as _i38; +import 'package:spotube/pages/lyrics/lyrics.dart' as _i17; +import 'package:spotube/pages/lyrics/mini_lyrics.dart' as _i18; +import 'package:spotube/pages/mobile_login/mobile_login.dart' as _i39; +import 'package:spotube/pages/playlist/liked_playlist.dart' as _i14; +import 'package:spotube/pages/playlist/playlist.dart' as _i21; +import 'package:spotube/pages/profile/profile.dart' as _i22; +import 'package:spotube/pages/root/root_app.dart' as _i23; +import 'package:spotube/pages/search/search.dart' as _i24; +import 'package:spotube/pages/settings/about.dart' as _i1; +import 'package:spotube/pages/settings/blacklist.dart' as _i4; +import 'package:spotube/pages/settings/logs.dart' as _i16; +import 'package:spotube/pages/settings/settings.dart' as _i25; +import 'package:spotube/pages/stats/albums/albums.dart' as _i26; +import 'package:spotube/pages/stats/artists/artists.dart' as _i27; +import 'package:spotube/pages/stats/fees/fees.dart' as _i31; +import 'package:spotube/pages/stats/minutes/minutes.dart' as _i28; +import 'package:spotube/pages/stats/playlists/playlists.dart' as _i30; +import 'package:spotube/pages/stats/stats.dart' as _i29; +import 'package:spotube/pages/stats/streams/streams.dart' as _i32; +import 'package:spotube/pages/track/track.dart' as _i33; + +/// generated route for +/// [_i1.AboutSpotubePage] +class AboutSpotubeRoute extends _i40.PageRouteInfo { + const AboutSpotubeRoute({List<_i40.PageRouteInfo>? children}) + : super( + AboutSpotubeRoute.name, + initialChildren: children, + ); + + static const String name = 'AboutSpotubeRoute'; + + static _i40.PageInfo page = _i40.PageInfo( + name, + builder: (data) { + return const _i1.AboutSpotubePage(); + }, + ); +} + +/// generated route for +/// [_i2.AlbumPage] +class AlbumRoute extends _i40.PageRouteInfo { + AlbumRoute({ + _i41.Key? key, + required String id, + required _i42.AlbumSimple album, + List<_i40.PageRouteInfo>? children, + }) : super( + AlbumRoute.name, + args: AlbumRouteArgs( + key: key, + id: id, + album: album, + ), + rawPathParams: {'id': id}, + initialChildren: children, + ); + + static const String name = 'AlbumRoute'; + + static _i40.PageInfo page = _i40.PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return _i2.AlbumPage( + key: args.key, + id: args.id, + album: args.album, + ); + }, + ); +} + +class AlbumRouteArgs { + const AlbumRouteArgs({ + this.key, + required this.id, + required this.album, + }); + + final _i41.Key? key; + + final String id; + + final _i42.AlbumSimple album; + + @override + String toString() { + return 'AlbumRouteArgs{key: $key, id: $id, album: $album}'; + } +} + +/// generated route for +/// [_i3.ArtistPage] +class ArtistRoute extends _i40.PageRouteInfo { + ArtistRoute({ + required String artistId, + _i41.Key? key, + List<_i40.PageRouteInfo>? children, + }) : super( + ArtistRoute.name, + args: ArtistRouteArgs( + artistId: artistId, + key: key, + ), + rawPathParams: {'id': artistId}, + initialChildren: children, + ); + + static const String name = 'ArtistRoute'; + + static _i40.PageInfo page = _i40.PageInfo( + name, + builder: (data) { + final pathParams = data.inheritedPathParams; + final args = data.argsAs( + orElse: () => ArtistRouteArgs(artistId: pathParams.getString('id'))); + return _i3.ArtistPage( + args.artistId, + key: args.key, + ); + }, + ); +} + +class ArtistRouteArgs { + const ArtistRouteArgs({ + required this.artistId, + this.key, + }); + + final String artistId; + + final _i41.Key? key; + + @override + String toString() { + return 'ArtistRouteArgs{artistId: $artistId, key: $key}'; + } +} + +/// generated route for +/// [_i4.BlackListPage] +class BlackListRoute extends _i40.PageRouteInfo { + const BlackListRoute({List<_i40.PageRouteInfo>? children}) + : super( + BlackListRoute.name, + initialChildren: children, + ); + + static const String name = 'BlackListRoute'; + + static _i40.PageInfo page = _i40.PageInfo( + name, + builder: (data) { + return const _i4.BlackListPage(); + }, + ); +} + +/// generated route for +/// [_i5.ConnectControlPage] +class ConnectControlRoute extends _i40.PageRouteInfo { + const ConnectControlRoute({List<_i40.PageRouteInfo>? children}) + : super( + ConnectControlRoute.name, + initialChildren: children, + ); + + static const String name = 'ConnectControlRoute'; + + static _i40.PageInfo page = _i40.PageInfo( + name, + builder: (data) { + return const _i5.ConnectControlPage(); + }, + ); +} + +/// generated route for +/// [_i6.ConnectPage] +class ConnectRoute extends _i40.PageRouteInfo { + const ConnectRoute({List<_i40.PageRouteInfo>? children}) + : super( + ConnectRoute.name, + initialChildren: children, + ); + + static const String name = 'ConnectRoute'; + + static _i40.PageInfo page = _i40.PageInfo( + name, + builder: (data) { + return const _i6.ConnectPage(); + }, + ); +} + +/// generated route for +/// [_i7.GenrePage] +class GenreRoute extends _i40.PageRouteInfo { + const GenreRoute({List<_i40.PageRouteInfo>? children}) + : super( + GenreRoute.name, + initialChildren: children, + ); + + static const String name = 'GenreRoute'; + + static _i40.PageInfo page = _i40.PageInfo( + name, + builder: (data) { + return const _i7.GenrePage(); + }, + ); +} + +/// generated route for +/// [_i8.GenrePlaylistsPage] +class GenrePlaylistsRoute extends _i40.PageRouteInfo { + GenrePlaylistsRoute({ + _i43.Key? key, + required String id, + required _i42.Category category, + List<_i40.PageRouteInfo>? children, + }) : super( + GenrePlaylistsRoute.name, + args: GenrePlaylistsRouteArgs( + key: key, + id: id, + category: category, + ), + rawPathParams: {'categoryId': id}, + initialChildren: children, + ); + + static const String name = 'GenrePlaylistsRoute'; + + static _i40.PageInfo page = _i40.PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return _i8.GenrePlaylistsPage( + key: args.key, + id: args.id, + category: args.category, + ); + }, + ); +} + +class GenrePlaylistsRouteArgs { + const GenrePlaylistsRouteArgs({ + this.key, + required this.id, + required this.category, + }); + + final _i43.Key? key; + + final String id; + + final _i42.Category category; + + @override + String toString() { + return 'GenrePlaylistsRouteArgs{key: $key, id: $id, category: $category}'; + } +} + +/// generated route for +/// [_i9.GettingStartedPage] +class GettingStartedRoute extends _i40.PageRouteInfo { + const GettingStartedRoute({List<_i40.PageRouteInfo>? children}) + : super( + GettingStartedRoute.name, + initialChildren: children, + ); + + static const String name = 'GettingStartedRoute'; + + static _i40.PageInfo page = _i40.PageInfo( + name, + builder: (data) { + return const _i9.GettingStartedPage(); + }, + ); +} + +/// generated route for +/// [_i10.HomeFeedSectionPage] +class HomeFeedSectionRoute + extends _i40.PageRouteInfo { + HomeFeedSectionRoute({ + _i41.Key? key, + required String sectionUri, + List<_i40.PageRouteInfo>? children, + }) : super( + HomeFeedSectionRoute.name, + args: HomeFeedSectionRouteArgs( + key: key, + sectionUri: sectionUri, + ), + rawPathParams: {'feedId': sectionUri}, + initialChildren: children, + ); + + static const String name = 'HomeFeedSectionRoute'; + + static _i40.PageInfo page = _i40.PageInfo( + name, + builder: (data) { + final pathParams = data.inheritedPathParams; + final args = data.argsAs( + orElse: () => HomeFeedSectionRouteArgs( + sectionUri: pathParams.getString('feedId'))); + return _i10.HomeFeedSectionPage( + key: args.key, + sectionUri: args.sectionUri, + ); + }, + ); +} + +class HomeFeedSectionRouteArgs { + const HomeFeedSectionRouteArgs({ + this.key, + required this.sectionUri, + }); + + final _i41.Key? key; + + final String sectionUri; + + @override + String toString() { + return 'HomeFeedSectionRouteArgs{key: $key, sectionUri: $sectionUri}'; + } +} + +/// generated route for +/// [_i11.HomePage] +class HomeRoute extends _i40.PageRouteInfo { + const HomeRoute({List<_i40.PageRouteInfo>? children}) + : super( + HomeRoute.name, + initialChildren: children, + ); + + static const String name = 'HomeRoute'; + + static _i40.PageInfo page = _i40.PageInfo( + name, + builder: (data) { + return const _i11.HomePage(); + }, + ); +} + +/// generated route for +/// [_i12.LastFMLoginPage] +class LastFMLoginRoute extends _i40.PageRouteInfo { + const LastFMLoginRoute({List<_i40.PageRouteInfo>? children}) + : super( + LastFMLoginRoute.name, + initialChildren: children, + ); + + static const String name = 'LastFMLoginRoute'; + + static _i40.PageInfo page = _i40.PageInfo( + name, + builder: (data) { + return const _i12.LastFMLoginPage(); + }, + ); +} + +/// generated route for +/// [_i13.LibraryPage] +class LibraryRoute extends _i40.PageRouteInfo { + const LibraryRoute({List<_i40.PageRouteInfo>? children}) + : super( + LibraryRoute.name, + initialChildren: children, + ); + + static const String name = 'LibraryRoute'; + + static _i40.PageInfo page = _i40.PageInfo( + name, + builder: (data) { + return const _i13.LibraryPage(); + }, + ); +} + +/// generated route for +/// [_i14.LikedPlaylistPage] +class LikedPlaylistRoute extends _i40.PageRouteInfo { + LikedPlaylistRoute({ + _i44.Key? key, + required _i42.PlaylistSimple playlist, + List<_i40.PageRouteInfo>? children, + }) : super( + LikedPlaylistRoute.name, + args: LikedPlaylistRouteArgs( + key: key, + playlist: playlist, + ), + initialChildren: children, + ); + + static const String name = 'LikedPlaylistRoute'; + + static _i40.PageInfo page = _i40.PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return _i14.LikedPlaylistPage( + key: args.key, + playlist: args.playlist, + ); + }, + ); +} + +class LikedPlaylistRouteArgs { + const LikedPlaylistRouteArgs({ + this.key, + required this.playlist, + }); + + final _i44.Key? key; + + final _i42.PlaylistSimple playlist; + + @override + String toString() { + return 'LikedPlaylistRouteArgs{key: $key, playlist: $playlist}'; + } +} + +/// generated route for +/// [_i15.LocalLibraryPage] +class LocalLibraryRoute extends _i40.PageRouteInfo { + LocalLibraryRoute({ + required String location, + _i41.Key? key, + bool isDownloads = false, + bool isCache = false, + List<_i40.PageRouteInfo>? children, + }) : super( + LocalLibraryRoute.name, + args: LocalLibraryRouteArgs( + location: location, + key: key, + isDownloads: isDownloads, + isCache: isCache, + ), + initialChildren: children, + ); + + static const String name = 'LocalLibraryRoute'; + + static _i40.PageInfo page = _i40.PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return _i15.LocalLibraryPage( + args.location, + key: args.key, + isDownloads: args.isDownloads, + isCache: args.isCache, + ); + }, + ); +} + +class LocalLibraryRouteArgs { + const LocalLibraryRouteArgs({ + required this.location, + this.key, + this.isDownloads = false, + this.isCache = false, + }); + + final String location; + + final _i41.Key? key; + + final bool isDownloads; + + final bool isCache; + + @override + String toString() { + return 'LocalLibraryRouteArgs{location: $location, key: $key, isDownloads: $isDownloads, isCache: $isCache}'; + } +} + +/// generated route for +/// [_i16.LogsPage] +class LogsRoute extends _i40.PageRouteInfo { + const LogsRoute({List<_i40.PageRouteInfo>? children}) + : super( + LogsRoute.name, + initialChildren: children, + ); + + static const String name = 'LogsRoute'; + + static _i40.PageInfo page = _i40.PageInfo( + name, + builder: (data) { + return const _i16.LogsPage(); + }, + ); +} + +/// generated route for +/// [_i17.LyricsPage] +class LyricsRoute extends _i40.PageRouteInfo { + LyricsRoute({ + _i41.Key? key, + bool isModal = false, + List<_i40.PageRouteInfo>? children, + }) : super( + LyricsRoute.name, + args: LyricsRouteArgs( + key: key, + isModal: isModal, + ), + initialChildren: children, + ); + + static const String name = 'LyricsRoute'; + + static _i40.PageInfo page = _i40.PageInfo( + name, + builder: (data) { + final args = + data.argsAs(orElse: () => const LyricsRouteArgs()); + return _i17.LyricsPage( + key: args.key, + isModal: args.isModal, + ); + }, + ); +} + +class LyricsRouteArgs { + const LyricsRouteArgs({ + this.key, + this.isModal = false, + }); + + final _i41.Key? key; + + final bool isModal; + + @override + String toString() { + return 'LyricsRouteArgs{key: $key, isModal: $isModal}'; + } +} + +/// generated route for +/// [_i18.MiniLyricsPage] +class MiniLyricsRoute extends _i40.PageRouteInfo { + MiniLyricsRoute({ + _i41.Key? key, + required _i41.Size prevSize, + List<_i40.PageRouteInfo>? children, + }) : super( + MiniLyricsRoute.name, + args: MiniLyricsRouteArgs( + key: key, + prevSize: prevSize, + ), + initialChildren: children, + ); + + static const String name = 'MiniLyricsRoute'; + + static _i40.PageInfo page = _i40.PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return _i18.MiniLyricsPage( + key: args.key, + prevSize: args.prevSize, + ); + }, + ); +} + +class MiniLyricsRouteArgs { + const MiniLyricsRouteArgs({ + this.key, + required this.prevSize, + }); + + final _i41.Key? key; + + final _i41.Size prevSize; + + @override + String toString() { + return 'MiniLyricsRouteArgs{key: $key, prevSize: $prevSize}'; + } +} + +/// generated route for +/// [_i19.PlaylistGenerateResultPage] +class PlaylistGenerateResultRoute + extends _i40.PageRouteInfo { + PlaylistGenerateResultRoute({ + _i41.Key? key, + required _i45.GeneratePlaylistProviderInput state, + List<_i40.PageRouteInfo>? children, + }) : super( + PlaylistGenerateResultRoute.name, + args: PlaylistGenerateResultRouteArgs( + key: key, + state: state, + ), + initialChildren: children, + ); + + static const String name = 'PlaylistGenerateResultRoute'; + + static _i40.PageInfo page = _i40.PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return _i19.PlaylistGenerateResultPage( + key: args.key, + state: args.state, + ); + }, + ); +} + +class PlaylistGenerateResultRouteArgs { + const PlaylistGenerateResultRouteArgs({ + this.key, + required this.state, + }); + + final _i41.Key? key; + + final _i45.GeneratePlaylistProviderInput state; + + @override + String toString() { + return 'PlaylistGenerateResultRouteArgs{key: $key, state: $state}'; + } +} + +/// generated route for +/// [_i20.PlaylistGeneratorPage] +class PlaylistGeneratorRoute extends _i40.PageRouteInfo { + const PlaylistGeneratorRoute({List<_i40.PageRouteInfo>? children}) + : super( + PlaylistGeneratorRoute.name, + initialChildren: children, + ); + + static const String name = 'PlaylistGeneratorRoute'; + + static _i40.PageInfo page = _i40.PageInfo( + name, + builder: (data) { + return const _i20.PlaylistGeneratorPage(); + }, + ); +} + +/// generated route for +/// [_i21.PlaylistPage] +class PlaylistRoute extends _i40.PageRouteInfo { + PlaylistRoute({ + _i43.Key? key, + required String id, + required _i42.PlaylistSimple playlist, + List<_i40.PageRouteInfo>? children, + }) : super( + PlaylistRoute.name, + args: PlaylistRouteArgs( + key: key, + id: id, + playlist: playlist, + ), + rawPathParams: {'id': id}, + initialChildren: children, + ); + + static const String name = 'PlaylistRoute'; + + static _i40.PageInfo page = _i40.PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return _i21.PlaylistPage( + key: args.key, + id: args.id, + playlist: args.playlist, + ); + }, + ); +} + +class PlaylistRouteArgs { + const PlaylistRouteArgs({ + this.key, + required this.id, + required this.playlist, + }); + + final _i43.Key? key; + + final String id; + + final _i42.PlaylistSimple playlist; + + @override + String toString() { + return 'PlaylistRouteArgs{key: $key, id: $id, playlist: $playlist}'; + } +} + +/// generated route for +/// [_i22.ProfilePage] +class ProfileRoute extends _i40.PageRouteInfo { + const ProfileRoute({List<_i40.PageRouteInfo>? children}) + : super( + ProfileRoute.name, + initialChildren: children, + ); + + static const String name = 'ProfileRoute'; + + static _i40.PageInfo page = _i40.PageInfo( + name, + builder: (data) { + return const _i22.ProfilePage(); + }, + ); +} + +/// generated route for +/// [_i23.RootAppPage] +class RootAppRoute extends _i40.PageRouteInfo { + const RootAppRoute({List<_i40.PageRouteInfo>? children}) + : super( + RootAppRoute.name, + initialChildren: children, + ); + + static const String name = 'RootAppRoute'; + + static _i40.PageInfo page = _i40.PageInfo( + name, + builder: (data) { + return const _i23.RootAppPage(); + }, + ); +} + +/// generated route for +/// [_i24.SearchPage] +class SearchRoute extends _i40.PageRouteInfo { + const SearchRoute({List<_i40.PageRouteInfo>? children}) + : super( + SearchRoute.name, + initialChildren: children, + ); + + static const String name = 'SearchRoute'; + + static _i40.PageInfo page = _i40.PageInfo( + name, + builder: (data) { + return const _i24.SearchPage(); + }, + ); +} + +/// generated route for +/// [_i25.SettingsPage] +class SettingsRoute extends _i40.PageRouteInfo { + const SettingsRoute({List<_i40.PageRouteInfo>? children}) + : super( + SettingsRoute.name, + initialChildren: children, + ); + + static const String name = 'SettingsRoute'; + + static _i40.PageInfo page = _i40.PageInfo( + name, + builder: (data) { + return const _i25.SettingsPage(); + }, + ); +} + +/// generated route for +/// [_i26.StatsAlbumsPage] +class StatsAlbumsRoute extends _i40.PageRouteInfo { + const StatsAlbumsRoute({List<_i40.PageRouteInfo>? children}) + : super( + StatsAlbumsRoute.name, + initialChildren: children, + ); + + static const String name = 'StatsAlbumsRoute'; + + static _i40.PageInfo page = _i40.PageInfo( + name, + builder: (data) { + return const _i26.StatsAlbumsPage(); + }, + ); +} + +/// generated route for +/// [_i27.StatsArtistsPage] +class StatsArtistsRoute extends _i40.PageRouteInfo { + const StatsArtistsRoute({List<_i40.PageRouteInfo>? children}) + : super( + StatsArtistsRoute.name, + initialChildren: children, + ); + + static const String name = 'StatsArtistsRoute'; + + static _i40.PageInfo page = _i40.PageInfo( + name, + builder: (data) { + return const _i27.StatsArtistsPage(); + }, + ); +} + +/// generated route for +/// [_i28.StatsMinutesPage] +class StatsMinutesRoute extends _i40.PageRouteInfo { + const StatsMinutesRoute({List<_i40.PageRouteInfo>? children}) + : super( + StatsMinutesRoute.name, + initialChildren: children, + ); + + static const String name = 'StatsMinutesRoute'; + + static _i40.PageInfo page = _i40.PageInfo( + name, + builder: (data) { + return const _i28.StatsMinutesPage(); + }, + ); +} + +/// generated route for +/// [_i29.StatsPage] +class StatsRoute extends _i40.PageRouteInfo { + const StatsRoute({List<_i40.PageRouteInfo>? children}) + : super( + StatsRoute.name, + initialChildren: children, + ); + + static const String name = 'StatsRoute'; + + static _i40.PageInfo page = _i40.PageInfo( + name, + builder: (data) { + return const _i29.StatsPage(); + }, + ); +} + +/// generated route for +/// [_i30.StatsPlaylistsPage] +class StatsPlaylistsRoute extends _i40.PageRouteInfo { + const StatsPlaylistsRoute({List<_i40.PageRouteInfo>? children}) + : super( + StatsPlaylistsRoute.name, + initialChildren: children, + ); + + static const String name = 'StatsPlaylistsRoute'; + + static _i40.PageInfo page = _i40.PageInfo( + name, + builder: (data) { + return const _i30.StatsPlaylistsPage(); + }, + ); +} + +/// generated route for +/// [_i31.StatsStreamFeesPage] +class StatsStreamFeesRoute extends _i40.PageRouteInfo { + const StatsStreamFeesRoute({List<_i40.PageRouteInfo>? children}) + : super( + StatsStreamFeesRoute.name, + initialChildren: children, + ); + + static const String name = 'StatsStreamFeesRoute'; + + static _i40.PageInfo page = _i40.PageInfo( + name, + builder: (data) { + return const _i31.StatsStreamFeesPage(); + }, + ); +} + +/// generated route for +/// [_i32.StatsStreamsPage] +class StatsStreamsRoute extends _i40.PageRouteInfo { + const StatsStreamsRoute({List<_i40.PageRouteInfo>? children}) + : super( + StatsStreamsRoute.name, + initialChildren: children, + ); + + static const String name = 'StatsStreamsRoute'; + + static _i40.PageInfo page = _i40.PageInfo( + name, + builder: (data) { + return const _i32.StatsStreamsPage(); + }, + ); +} + +/// generated route for +/// [_i33.TrackPage] +class TrackRoute extends _i40.PageRouteInfo { + TrackRoute({ + _i41.Key? key, + required String trackId, + List<_i40.PageRouteInfo>? children, + }) : super( + TrackRoute.name, + args: TrackRouteArgs( + key: key, + trackId: trackId, + ), + rawPathParams: {'id': trackId}, + initialChildren: children, + ); + + static const String name = 'TrackRoute'; + + static _i40.PageInfo page = _i40.PageInfo( + name, + builder: (data) { + final pathParams = data.inheritedPathParams; + final args = data.argsAs( + orElse: () => TrackRouteArgs(trackId: pathParams.getString('id'))); + return _i33.TrackPage( + key: args.key, + trackId: args.trackId, + ); + }, + ); +} + +class TrackRouteArgs { + const TrackRouteArgs({ + this.key, + required this.trackId, + }); + + final _i41.Key? key; + + final String trackId; + + @override + String toString() { + return 'TrackRouteArgs{key: $key, trackId: $trackId}'; + } +} + +/// generated route for +/// [_i34.UserAlbumsPage] +class UserAlbumsRoute extends _i40.PageRouteInfo { + const UserAlbumsRoute({List<_i40.PageRouteInfo>? children}) + : super( + UserAlbumsRoute.name, + initialChildren: children, + ); + + static const String name = 'UserAlbumsRoute'; + + static _i40.PageInfo page = _i40.PageInfo( + name, + builder: (data) { + return const _i34.UserAlbumsPage(); + }, + ); +} + +/// generated route for +/// [_i35.UserArtistsPage] +class UserArtistsRoute extends _i40.PageRouteInfo { + const UserArtistsRoute({List<_i40.PageRouteInfo>? children}) + : super( + UserArtistsRoute.name, + initialChildren: children, + ); + + static const String name = 'UserArtistsRoute'; + + static _i40.PageInfo page = _i40.PageInfo( + name, + builder: (data) { + return const _i35.UserArtistsPage(); + }, + ); +} + +/// generated route for +/// [_i36.UserDownloadsPage] +class UserDownloadsRoute extends _i40.PageRouteInfo { + const UserDownloadsRoute({List<_i40.PageRouteInfo>? children}) + : super( + UserDownloadsRoute.name, + initialChildren: children, + ); + + static const String name = 'UserDownloadsRoute'; + + static _i40.PageInfo page = _i40.PageInfo( + name, + builder: (data) { + return const _i36.UserDownloadsPage(); + }, + ); +} + +/// generated route for +/// [_i37.UserLocalLibraryPage] +class UserLocalLibraryRoute extends _i40.PageRouteInfo { + const UserLocalLibraryRoute({List<_i40.PageRouteInfo>? children}) + : super( + UserLocalLibraryRoute.name, + initialChildren: children, + ); + + static const String name = 'UserLocalLibraryRoute'; + + static _i40.PageInfo page = _i40.PageInfo( + name, + builder: (data) { + return const _i37.UserLocalLibraryPage(); + }, + ); +} + +/// generated route for +/// [_i38.UserPlaylistsPage] +class UserPlaylistsRoute extends _i40.PageRouteInfo { + const UserPlaylistsRoute({List<_i40.PageRouteInfo>? children}) + : super( + UserPlaylistsRoute.name, + initialChildren: children, + ); + + static const String name = 'UserPlaylistsRoute'; + + static _i40.PageInfo page = _i40.PageInfo( + name, + builder: (data) { + return const _i38.UserPlaylistsPage(); + }, + ); +} + +/// generated route for +/// [_i39.WebViewLoginPage] +class WebViewLoginRoute extends _i40.PageRouteInfo { + const WebViewLoginRoute({List<_i40.PageRouteInfo>? children}) + : super( + WebViewLoginRoute.name, + initialChildren: children, + ); + + static const String name = 'WebViewLoginRoute'; + + static _i40.PageInfo page = _i40.PageInfo( + name, + builder: (data) { + return const _i39.WebViewLoginPage(); + }, + ); +} diff --git a/lib/collections/side_bar_tiles.dart b/lib/collections/side_bar_tiles.dart index f12517bb..44c8b308 100644 --- a/lib/collections/side_bar_tiles.dart +++ b/lib/collections/side_bar_tiles.dart @@ -1,51 +1,51 @@ +import 'package:auto_route/auto_route.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; +import 'package:spotube/collections/routes.gr.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/user_albums.dart'; -import 'package:spotube/pages/library/user_artists.dart'; -import 'package:spotube/pages/library/user_local_tracks/user_local_tracks.dart'; -import 'package:spotube/pages/library/user_playlists.dart'; -import 'package:spotube/pages/lyrics/lyrics.dart'; -import 'package:spotube/pages/search/search.dart'; -import 'package:spotube/pages/stats/stats.dart'; class SideBarTiles { final IconData icon; final String title; final String id; - final String name; + final String pathPrefix; + final PageRouteInfo route; SideBarTiles({ required this.icon, required this.title, required this.id, - required this.name, + required this.route, + required this.pathPrefix, }); } List getSidebarTileList(AppLocalizations l10n) => [ SideBarTiles( - id: "browse", - name: HomePage.name, + id: "home", + pathPrefix: "/home", + route: const HomeRoute(), icon: SpotubeIcons.home, title: l10n.browse, ), SideBarTiles( id: "search", - name: SearchPage.name, + pathPrefix: "/search", + route: const SearchRoute(), icon: SpotubeIcons.search, title: l10n.search, ), SideBarTiles( id: "lyrics", - name: LyricsPage.name, + pathPrefix: "/lyrics", + route: LyricsRoute(), icon: SpotubeIcons.music, title: l10n.lyrics, ), SideBarTiles( id: "stats", - name: StatsPage.name, + pathPrefix: "/stats", + route: const StatsRoute(), icon: SpotubeIcons.chart, title: l10n.stats, ), @@ -54,52 +54,60 @@ List getSidebarTileList(AppLocalizations l10n) => [ List getSidebarLibraryTileList(AppLocalizations l10n) => [ SideBarTiles( id: "playlists", + pathPrefix: "/library/playlists", title: l10n.playlists, - name: UserPlaylistsPage.name, + route: const UserPlaylistsRoute(), icon: SpotubeIcons.playlist, ), SideBarTiles( id: "artists", + pathPrefix: "/library/artists", title: l10n.artists, - name: UserArtistsPage.name, + route: const UserArtistsRoute(), icon: SpotubeIcons.artist, ), SideBarTiles( id: "albums", + pathPrefix: "/library/albums", title: l10n.albums, - name: UserAlbumsPage.name, + route: const UserAlbumsRoute(), icon: SpotubeIcons.album, ), SideBarTiles( id: "local_library", + pathPrefix: "/library/local", title: l10n.local_library, - name: UserLocalLibraryPage.name, + route: const UserLocalLibraryRoute(), icon: SpotubeIcons.device, ), ]; List getNavbarTileList(AppLocalizations l10n) => [ SideBarTiles( - id: "browse", - name: HomePage.name, + id: "home", + pathPrefix: "/home", + route: const HomeRoute(), icon: SpotubeIcons.home, title: l10n.browse, ), SideBarTiles( id: "search", - name: SearchPage.name, + pathPrefix: "/search", + route: const SearchRoute(), icon: SpotubeIcons.search, title: l10n.search, ), SideBarTiles( id: "library", - name: UserPlaylistsPage.name, + pathPrefix: "/library", + route: const UserPlaylistsRoute(), icon: SpotubeIcons.library, title: l10n.library, ), SideBarTiles( id: "stats", - name: StatsPage.name, + pathPrefix: "/stats", + route: const StatsRoute(), icon: SpotubeIcons.chart, title: l10n.stats, ), diff --git a/lib/components/dialogs/track_details_dialog.dart b/lib/components/dialogs/track_details_dialog.dart index 1296ae0e..bfb4a318 100644 --- a/lib/components/dialogs/track_details_dialog.dart +++ b/lib/components/dialogs/track_details_dialog.dart @@ -1,6 +1,7 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:spotify/spotify.dart'; +import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/links/artist_link.dart'; import 'package:spotube/components/links/hyper_link.dart'; @@ -32,8 +33,7 @@ class TrackDetailsDialog extends HookWidget { ), context.l10n.album: LinkText( track.album!.name!, - "/album/${track.album?.id}", - extra: track.album, + AlbumRoute(album: track.album!, id: track.album!.id!), overflow: TextOverflow.ellipsis, style: const TextStyle(color: Colors.blue), ), diff --git a/lib/components/fallbacks/anonymous_fallback.dart b/lib/components/fallbacks/anonymous_fallback.dart index 373e0454..293df932 100644 --- a/lib/components/fallbacks/anonymous_fallback.dart +++ b/lib/components/fallbacks/anonymous_fallback.dart @@ -1,13 +1,13 @@ +import 'package:auto_route/auto_route.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_undraw/flutter_undraw.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; +import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/extensions/context.dart'; -import 'package:spotube/pages/settings/settings.dart'; import 'package:spotube/provider/authentication/authentication.dart'; import 'package:spotube/utils/platform.dart'; -import 'package:spotube/utils/service_utils.dart'; class AnonymousFallback extends ConsumerWidget { final Widget? child; @@ -40,7 +40,7 @@ class AnonymousFallback extends ConsumerWidget { Text(context.l10n.not_logged_in), Button.primary( child: Text(context.l10n.login_with_spotify), - onPressed: () => ServiceUtils.pushNamed(context, SettingsPage.name), + onPressed: () => context.navigateTo(const SettingsRoute()), ) ], ), diff --git a/lib/components/links/artist_link.dart b/lib/components/links/artist_link.dart index c6ea5c14..9467cb38 100644 --- a/lib/components/links/artist_link.dart +++ b/lib/components/links/artist_link.dart @@ -1,9 +1,9 @@ +import 'package:auto_route/auto_route.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:spotify/spotify.dart'; +import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/components/links/anchor_button.dart'; import 'package:spotube/extensions/context.dart'; -import 'package:spotube/pages/artist/artist.dart'; -import 'package:spotube/utils/service_utils.dart'; class ArtistLink extends StatelessWidget { final List artists; @@ -49,13 +49,8 @@ class ArtistLink extends StatelessWidget { if (onRouteChange != null) { onRouteChange?.call("/artist/${artist.value.id}"); } else { - ServiceUtils.pushNamed( - context, - ArtistPage.name, - pathParameters: { - "id": artist.value.id!, - }, - ); + context + .navigateTo(ArtistRoute(artistId: artist.value.id!)); } }, overflow: TextOverflow.ellipsis, diff --git a/lib/components/links/link_text.dart b/lib/components/links/link_text.dart index a54c8b9f..c64ae93d 100644 --- a/lib/components/links/link_text.dart +++ b/lib/components/links/link_text.dart @@ -1,15 +1,14 @@ +import 'package:auto_route/auto_route.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:spotube/components/links/anchor_button.dart'; -import 'package:spotube/utils/service_utils.dart'; class LinkText extends StatelessWidget { final String text; final TextStyle style; final TextAlign? textAlign; final TextOverflow? overflow; - final String route; + final PageRouteInfo route; final int? maxLines; - final T? extra; final bool push; const LinkText( @@ -17,7 +16,6 @@ class LinkText extends StatelessWidget { this.route, { super.key, this.textAlign, - this.extra, this.overflow, this.style = const TextStyle(), this.maxLines, @@ -30,9 +28,9 @@ class LinkText extends StatelessWidget { text, onTap: () { if (push) { - ServiceUtils.push(context, route, extra: extra); + context.navigateTo(route); } else { - ServiceUtils.navigate(context, route, extra: extra); + context.navigateTo(route); } }, key: key, diff --git a/lib/components/spotube_page_route.dart b/lib/components/spotube_page_route.dart index 6d152dd5..cff32975 100644 --- a/lib/components/spotube_page_route.dart +++ b/lib/components/spotube_page_route.dart @@ -1,25 +1,24 @@ import 'package:shadcn_flutter/shadcn_flutter.dart'; -import 'package:go_router/go_router.dart'; class SpotubePage extends MaterialPage { const SpotubePage({required super.child}); } -class SpotubeSlidePage extends CustomTransitionPage { - SpotubeSlidePage({ - required super.child, - super.key, - }) : super( - reverseTransitionDuration: const Duration(milliseconds: 150), - transitionDuration: const Duration(milliseconds: 150), - transitionsBuilder: (context, animation, secondaryAnimation, child) { - return SlideTransition( - position: Tween( - begin: const Offset(1, 0), - end: Offset.zero, - ).animate(animation), - child: child, - ); - }, - ); -} +// class SpotubeSlidePage extends CustomTransitionPage { +// SpotubeSlidePage({ +// required super.child, +// super.key, +// }) : super( +// reverseTransitionDuration: const Duration(milliseconds: 150), +// transitionDuration: const Duration(milliseconds: 150), +// transitionsBuilder: (context, animation, secondaryAnimation, child) { +// return SlideTransition( +// position: Tween( +// begin: const Offset(1, 0), +// end: Offset.zero, +// ).animate(animation), +// child: child, +// ); +// }, +// ); +// } diff --git a/lib/components/titlebar/titlebar.dart b/lib/components/titlebar/titlebar.dart index 5b86f6ad..778f0b09 100644 --- a/lib/components/titlebar/titlebar.dart +++ b/lib/components/titlebar/titlebar.dart @@ -1,3 +1,4 @@ +import 'package:auto_route/auto_route.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; @@ -73,6 +74,10 @@ class TitleBar extends HookConsumerWidget implements PreferredSizeWidget { final hasFullscreen = MediaQuery.sizeOf(context).width == constraints.maxWidth; + final canPop = leading.isEmpty && + automaticallyImplyLeading && + (Navigator.canPop(context) || context.watchRouter.canPop()); + return GestureDetector( onHorizontalDragStart: (_) => onDrag(ref), onVerticalDragStart: (_) => onDrag(ref), @@ -94,13 +99,7 @@ class TitleBar extends HookConsumerWidget implements PreferredSizeWidget { } }, child: AppBar( - leading: leading.isEmpty && - automaticallyImplyLeading && - Navigator.canPop(context) - ? [ - const BackButton(), - ] - : leading, + leading: canPop ? [const BackButton()] : leading, trailing: [ ...trailing, Align( diff --git a/lib/components/track_tile/track_options.dart b/lib/components/track_tile/track_options.dart index 14514cde..b1105c7b 100644 --- a/lib/components/track_tile/track_options.dart +++ b/lib/components/track_tile/track_options.dart @@ -1,14 +1,16 @@ import 'dart:io'; +import 'package:auto_route/auto_route.dart'; import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:go_router/go_router.dart'; + import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; import 'package:spotify/spotify.dart' hide Offset; import 'package:spotube/collections/assets.gen.dart'; +import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/adaptive/adaptive_pop_sheet_list.dart'; import 'package:spotube/components/dialogs/playlist_add_track_dialog.dart'; @@ -22,7 +24,6 @@ import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/image.dart'; import 'package:spotube/models/database/database.dart'; import 'package:spotube/models/local_track.dart'; -import 'package:spotube/pages/track/track.dart'; import 'package:spotube/provider/authentication/authentication.dart'; import 'package:spotube/provider/blacklist_provider.dart'; import 'package:spotube/provider/download_manager_provider.dart'; @@ -30,7 +31,6 @@ import 'package:spotube/provider/local_tracks/local_tracks_provider.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/spotify/spotify.dart'; import 'package:spotube/provider/spotify_provider.dart'; -import 'package:spotube/utils/service_utils.dart'; import 'package:url_launcher/url_launcher_string.dart'; @@ -166,7 +166,6 @@ class TrackOptions extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { final mediaQuery = MediaQuery.of(context); - final router = GoRouter.of(context); final ThemeData(:colorScheme) = Theme.of(context); final playlist = ref.watch(audioPlayerProvider); @@ -211,9 +210,8 @@ class TrackOptions extends HookConsumerWidget { onSelected: (value) async { switch (value) { case TrackOptionValue.album: - await router.push( - '/album/${track.album!.id}', - extra: track.album!, + await context.navigateTo( + AlbumRoute(id: track.album!.id!, album: track.album!), ); break; case TrackOptionValue.delete: @@ -347,12 +345,8 @@ class TrackOptions extends HookConsumerWidget { alignment: Alignment.centerLeft, child: ArtistLink( artists: track.artists!, - onOverflowArtistClick: () => ServiceUtils.pushNamed( - context, - TrackPage.name, - pathParameters: { - "id": track.id!, - }, + onOverflowArtistClick: () => context.navigateTo( + TrackRoute(trackId: track.id!), ), ), ), diff --git a/lib/components/track_tile/track_tile.dart b/lib/components/track_tile/track_tile.dart index 0ca14979..9bb300f4 100644 --- a/lib/components/track_tile/track_tile.dart +++ b/lib/components/track_tile/track_tile.dart @@ -1,13 +1,14 @@ import 'dart:async'; +import 'package:auto_route/auto_route.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:skeletonizer/skeletonizer.dart'; import 'package:spotify/spotify.dart'; +import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/hover_builder.dart'; import 'package:spotube/components/image/universal_image.dart'; @@ -21,12 +22,10 @@ import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/duration.dart'; import 'package:spotube/extensions/image.dart'; import 'package:spotube/models/local_track.dart'; -import 'package:spotube/pages/track/track.dart'; import 'package:spotube/provider/audio_player/querying_track_info.dart'; import 'package:spotube/provider/audio_player/state.dart'; import 'package:spotube/provider/blacklist_provider.dart'; import 'package:spotube/utils/platform.dart'; -import 'package:spotube/utils/service_utils.dart'; class TrackTile extends HookConsumerWidget { /// [index] will not be shown if null @@ -234,12 +233,8 @@ class TrackTile extends HookConsumerWidget { padding: (context, states) => EdgeInsets.zero, ), onPressed: () { - context.pushNamed( - TrackPage.name, - pathParameters: { - "id": track.id!, - }, - ); + context + .navigateTo(TrackRoute(trackId: track.id!)); }, child: Text( track.name!, @@ -266,8 +261,8 @@ class TrackTile extends HookConsumerWidget { alignment: Alignment.centerLeft, child: LinkText( track.album!.name!, - "/album/${track.album?.id}", - extra: track.album, + AlbumRoute( + album: track.album!, id: track.album!.id!), push: true, overflow: TextOverflow.ellipsis, ), @@ -288,13 +283,11 @@ class TrackTile extends HookConsumerWidget { constraints: const BoxConstraints(maxHeight: 40), child: ArtistLink( artists: track.artists ?? [], - onOverflowArtistClick: () => ServiceUtils.pushNamed( - context, - TrackPage.name, - pathParameters: { - "id": track.id!, - }, - ), + onOverflowArtistClick: () { + context.navigateTo( + TrackRoute(trackId: track.id!), + ); + }, ), ), ), diff --git a/lib/hooks/configurators/use_deep_linking.dart b/lib/hooks/configurators/use_deep_linking.dart index ec6d8516..67000d49 100644 --- a/lib/hooks/configurators/use_deep_linking.dart +++ b/lib/hooks/configurators/use_deep_linking.dart @@ -4,6 +4,7 @@ import 'package:app_links/app_links.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotube/collections/routes.dart'; +import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/provider/spotify_provider.dart'; import 'package:flutter_sharing_intent/flutter_sharing_intent.dart'; import 'package:flutter_sharing_intent/model/sharing_file.dart'; @@ -13,10 +14,9 @@ import 'package:spotube/utils/platform.dart'; final appLinks = AppLinks(); final linkStream = appLinks.stringLinkStream.asBroadcastStream(); -void useDeepLinking(WidgetRef ref) { +void useDeepLinking(WidgetRef ref, AppRouter router) { // single instance no worries final spotify = ref.watch(spotifyProvider); - final router = ref.watch(routerProvider); useEffect(() { void uriListener(List files) async { @@ -27,24 +27,21 @@ void useDeepLinking(WidgetRef ref) { switch (url.pathSegments.first) { case "album": - router.push( - "/album/${url.pathSegments.last}", - extra: await spotify.albums.get(url.pathSegments.last), + final album = await spotify.albums.get(url.pathSegments.last); + router.navigate( + AlbumRoute(id: album.id!, album: album), ); break; case "artist": - router.push("/artist/${url.pathSegments.last}"); + router.navigate(ArtistRoute(artistId: url.pathSegments.last)); break; case "playlist": - router.push( - "/playlist/${url.pathSegments.last}", - extra: await spotify.playlists.get(url.pathSegments.last), - ); + final playlist = await spotify.playlists.get(url.pathSegments.last); + router + .navigate(PlaylistRoute(id: playlist.id!, playlist: playlist)); break; case "track": - router.push( - "/track/${url.pathSegments.last}", - ); + router.navigate(TrackRoute(trackId: url.pathSegments.last)); break; default: break; @@ -68,21 +65,21 @@ void useDeepLinking(WidgetRef ref) { switch (startSegment) { case "spotify:album": - await router.push( - "/album/$endSegment", - extra: await spotify.albums.get(endSegment), + final album = await spotify.albums.get(endSegment); + await router.navigate( + AlbumRoute(id: album.id!, album: album), ); break; case "spotify:artist": - await router.push("/artist/$endSegment"); + await router.navigate(ArtistRoute(artistId: endSegment)); break; case "spotify:track": - await router.push("/track/$endSegment"); + await router.navigate(TrackRoute(trackId: endSegment)); break; case "spotify:playlist": - await router.push( - "/playlist/$endSegment", - extra: await spotify.playlists.get(endSegment), + final playlist = await spotify.playlists.get(endSegment); + await router.navigate( + PlaylistRoute(id: playlist.id!, playlist: playlist), ); break; default: diff --git a/lib/main.dart b/lib/main.dart index 3994fd50..016c0fea 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -17,8 +17,8 @@ import 'package:metadata_god/metadata_god.dart'; import 'package:smtc_windows/smtc_windows.dart'; import 'package:spotube/collections/env.dart'; import 'package:spotube/collections/initializers.dart'; -import 'package:spotube/collections/routes.dart'; import 'package:spotube/collections/intents.dart'; +import 'package:spotube/collections/routes.dart'; import 'package:spotube/hooks/configurators/use_close_behavior.dart'; import 'package:spotube/hooks/configurators/use_deep_linking.dart'; import 'package:spotube/hooks/configurators/use_disable_battery_optimizations.dart'; @@ -133,7 +133,7 @@ class Spotube extends HookConsumerWidget { final locale = ref.watch(userPreferencesProvider.select((s) => s.locale)); final accentMaterialColor = ref.watch(userPreferencesProvider.select((s) => s.accentColorScheme)); - final router = ref.watch(routerProvider); + final router = useMemoized(() => AppRouter(ref), []); final hasTouchSupport = useHasTouch(); ref.listen(audioPlayerStreamListenersProvider, (_, __) {}); @@ -144,7 +144,7 @@ class Spotube extends HookConsumerWidget { useFixWindowStretching(); useDisableBatteryOptimizations(); - useDeepLinking(ref); + useDeepLinking(ref, router); useCloseBehavior(ref); useGetStoragePermissions(ref); @@ -171,7 +171,7 @@ class Spotube extends HookConsumerWidget { GlobalWidgetsLocalizations.delegate, GlobalCupertinoLocalizations.delegate, ], - routerConfig: router, + routerConfig: router.config(), debugShowCheckedModeBanner: false, title: 'Spotube', builder: (context, child) { @@ -240,42 +240,42 @@ class Spotube extends HookConsumerWidget { LogicalKeyboardKey.digit1, LogicalKeyboardKey.control, LogicalKeyboardKey.shift, - ): HomeTabIntent(ref, tab: HomeTabs.browse), + ): HomeTabIntent(router, tab: HomeTabs.browse), LogicalKeySet( LogicalKeyboardKey.digit2, LogicalKeyboardKey.control, LogicalKeyboardKey.shift, - ): HomeTabIntent(ref, tab: HomeTabs.search), + ): HomeTabIntent(router, tab: HomeTabs.search), LogicalKeySet( LogicalKeyboardKey.digit3, LogicalKeyboardKey.control, LogicalKeyboardKey.shift, - ): HomeTabIntent(ref, tab: HomeTabs.lyrics), + ): HomeTabIntent(router, tab: HomeTabs.lyrics), LogicalKeySet( LogicalKeyboardKey.digit4, LogicalKeyboardKey.control, LogicalKeyboardKey.shift, - ): HomeTabIntent(ref, tab: HomeTabs.userPlaylists), + ): HomeTabIntent(router, tab: HomeTabs.userPlaylists), LogicalKeySet( LogicalKeyboardKey.digit5, LogicalKeyboardKey.control, LogicalKeyboardKey.shift, - ): HomeTabIntent(ref, tab: HomeTabs.userArtists), + ): HomeTabIntent(router, tab: HomeTabs.userArtists), LogicalKeySet( LogicalKeyboardKey.digit6, LogicalKeyboardKey.control, LogicalKeyboardKey.shift, - ): HomeTabIntent(ref, tab: HomeTabs.userAlbums), + ): HomeTabIntent(router, tab: HomeTabs.userAlbums), LogicalKeySet( LogicalKeyboardKey.digit7, LogicalKeyboardKey.control, LogicalKeyboardKey.shift, - ): HomeTabIntent(ref, tab: HomeTabs.userLocalLibrary), + ): HomeTabIntent(router, tab: HomeTabs.userLocalLibrary), LogicalKeySet( LogicalKeyboardKey.digit8, LogicalKeyboardKey.control, LogicalKeyboardKey.shift, - ): HomeTabIntent(ref, tab: HomeTabs.userDownloads), + ): HomeTabIntent(router, tab: HomeTabs.userDownloads), LogicalKeySet( LogicalKeyboardKey.keyW, LogicalKeyboardKey.control, diff --git a/lib/modules/album/album_card.dart b/lib/modules/album/album_card.dart index 2efacbfd..84106594 100644 --- a/lib/modules/album/album_card.dart +++ b/lib/modules/album/album_card.dart @@ -1,7 +1,9 @@ +import 'package:auto_route/auto_route.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:spotify/spotify.dart'; +import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/components/dialogs/select_device_dialog.dart'; import 'package:spotube/components/playbutton_view/playbutton_card.dart'; import 'package:spotube/components/playbutton_view/playbutton_tile.dart'; @@ -10,14 +12,12 @@ 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/audio_player/querying_track_info.dart'; import 'package:spotube/provider/connect/connect.dart'; import 'package:spotube/provider/history/history.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/spotify/spotify.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; -import 'package:spotube/utils/service_utils.dart'; extension FormattedAlbumType on AlbumType { String get formatted => name.replaceFirst(name[0], name[0].toUpperCase()); @@ -69,14 +69,7 @@ class AlbumCard extends HookConsumerWidget { "${album.albumType?.formatted} • ${album.artists?.asString() ?? ""}"; void onTap() { - ServiceUtils.pushNamed( - context, - AlbumPage.name, - pathParameters: { - "id": album.id!, - }, - extra: album, - ); + context.navigateTo(AlbumRoute(id: album.id!, album: album)); } void onPlaybuttonPressed() async { diff --git a/lib/modules/artist/artist_card.dart b/lib/modules/artist/artist_card.dart index 57c955c7..e53070ef 100644 --- a/lib/modules/artist/artist_card.dart +++ b/lib/modules/artist/artist_card.dart @@ -1,16 +1,16 @@ +import 'package:auto_route/auto_route.dart'; import 'package:auto_size_text/auto_size_text.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:spotify/spotify.dart'; +import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/components/image/universal_image.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/image.dart'; -import 'package:spotube/pages/artist/artist.dart'; import 'package:spotube/provider/blacklist_provider.dart'; -import 'package:spotube/utils/service_utils.dart'; class ArtistCard extends HookConsumerWidget { final Artist artist; @@ -36,13 +36,7 @@ class ArtistCard extends HookConsumerWidget { width: 180, child: Button.card( onPressed: () { - ServiceUtils.pushNamed( - context, - ArtistPage.name, - pathParameters: { - "id": artist.id!, - }, - ); + context.navigateTo(ArtistRoute(artistId: artist.id!)); }, child: Column( children: [ diff --git a/lib/modules/connect/connect_device.dart b/lib/modules/connect/connect_device.dart index a285284c..2c8d612b 100644 --- a/lib/modules/connect/connect_device.dart +++ b/lib/modules/connect/connect_device.dart @@ -1,11 +1,11 @@ +import 'package:auto_route/auto_route.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; +import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/extensions/constrains.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'; class ConnectDeviceButton extends HookConsumerWidget { final bool _sidebar; @@ -26,7 +26,7 @@ class ConnectDeviceButton extends HookConsumerWidget { return IconButton.ghost( icon: const Icon(SpotubeIcons.speaker), onPressed: () { - ServiceUtils.pushNamed(context, ConnectPage.name); + context.navigateTo(const ConnectRoute()); }, ); } @@ -35,7 +35,7 @@ class ConnectDeviceButton extends HookConsumerWidget { width: double.infinity, child: Button.primary( onPressed: () { - ServiceUtils.pushNamed(context, ConnectPage.name); + context.navigateTo(const ConnectRoute()); }, trailing: const Icon(SpotubeIcons.speaker), child: Text( @@ -50,7 +50,7 @@ class ConnectDeviceButton extends HookConsumerWidget { children: [ SecondaryBadge( onPressed: () { - ServiceUtils.pushNamed(context, ConnectPage.name); + context.navigateTo(const ConnectRoute()); }, style: const ButtonStyle.secondary(size: ButtonSize(.8)), leading: connectClients.asData?.value.resolvedService != null @@ -70,7 +70,7 @@ class ConnectDeviceButton extends HookConsumerWidget { IconButton.primary( icon: const Icon(SpotubeIcons.speaker), onPressed: () { - ServiceUtils.pushNamed(context, ConnectPage.name); + context.navigateTo(const ConnectRoute()); }, ) ], diff --git a/lib/modules/home/sections/feed.dart b/lib/modules/home/sections/feed.dart index 34a9ee4b..d3e363cc 100644 --- a/lib/modules/home/sections/feed.dart +++ b/lib/modules/home/sections/feed.dart @@ -1,10 +1,10 @@ +import 'package:auto_route/auto_route.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; +import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/components/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart'; import 'package:spotube/extensions/context.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'; class HomePageFeedSection extends HookConsumerWidget { const HomePageFeedSection({super.key}); @@ -39,13 +39,9 @@ class HomePageFeedSection extends HookConsumerWidget { onFetchMore: () {}, titleTrailing: Button.text( child: Text(context.l10n.browse_all), - onPressed: () => ServiceUtils.pushNamed( - context, - HomeFeedSectionPage.name, - pathParameters: { - "feedId": section.uri, - }, - ), + onPressed: () { + context.navigateTo(HomeFeedSectionRoute(sectionUri: section.uri)); + }, ), ); }, diff --git a/lib/modules/home/sections/friends/friend_item.dart b/lib/modules/home/sections/friends/friend_item.dart index 94feb5cd..8e91ab66 100644 --- a/lib/modules/home/sections/friends/friend_item.dart +++ b/lib/modules/home/sections/friends/friend_item.dart @@ -1,14 +1,13 @@ +import 'package:auto_route/auto_route.dart'; import 'package:flutter/gestures.dart'; -import 'package:go_router/go_router.dart'; + import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; +import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/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 { @@ -50,9 +49,8 @@ class FriendItem extends HookConsumerWidget { text: friend.track.name, recognizer: TapGestureRecognizer() ..onTap = () { - context.pushNamed(TrackPage.name, pathParameters: { - "id": friend.track.id, - }); + context + .navigateTo(TrackRoute(trackId: friend.track.id)); }, ), const TextSpan(text: " • "), @@ -66,12 +64,8 @@ class FriendItem extends HookConsumerWidget { text: " ${friend.track.artist.name}", recognizer: TapGestureRecognizer() ..onTap = () { - context.pushNamed( - ArtistPage.name, - pathParameters: { - "id": friend.track.artist.id, - }, - extra: friend.track.artist, + context.navigateTo( + ArtistRoute(artistId: friend.track.artist.id), ); }, ), @@ -80,13 +74,13 @@ class FriendItem extends HookConsumerWidget { text: friend.track.context.name, recognizer: TapGestureRecognizer() ..onTap = () async { - context.push( + context.router.navigateNamed( "/${friend.track.context.path}", - extra: - !friend.track.context.path.startsWith("album") - ? null - : await spotify.albums - .get(friend.track.context.id), + // extra: + // !friend.track.context.path.startsWith("album") + // ? null + // : await spotify.albums + // .get(friend.track.context.id), ); }, ), @@ -104,12 +98,8 @@ class FriendItem extends HookConsumerWidget { final album = await spotify.albums.get(friend.track.album.id); if (context.mounted) { - context.pushNamed( - AlbumPage.name, - pathParameters: { - "id": friend.track.album.id, - }, - extra: album, + context.navigateTo( + AlbumRoute(id: album.id!, album: album), ); } }, diff --git a/lib/modules/home/sections/genres/genre_card.dart b/lib/modules/home/sections/genres/genre_card.dart index 617d7392..8133f0db 100644 --- a/lib/modules/home/sections/genres/genre_card.dart +++ b/lib/modules/home/sections/genres/genre_card.dart @@ -1,17 +1,17 @@ import 'dart:math'; import 'dart:ui'; -import 'package:go_router/go_router.dart'; +import 'package:auto_route/auto_route.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:skeletonizer/skeletonizer.dart'; import 'package:spotify/spotify.dart' hide Offset; import 'package:spotube/collections/fake.dart'; import 'package:spotube/collections/gradients.dart'; +import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/components/image/universal_image.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/modules/home/sections/genres/genre_card_playlist_card.dart'; -import 'package:spotube/pages/home/genres/genre_playlists.dart'; import 'package:spotube/provider/spotify/spotify.dart'; final random = Random(); @@ -76,10 +76,11 @@ class GenreSectionCard extends HookConsumerWidget { ).h3(), Button.link( onPressed: () { - context.pushNamed( - GenrePlaylistsPage.name, - pathParameters: {'categoryId': category.id!}, - extra: category, + context.navigateTo( + GenrePlaylistsRoute( + id: category.id!, + category: category, + ), ); }, child: Text( diff --git a/lib/modules/home/sections/genres/genre_card_playlist_card.dart b/lib/modules/home/sections/genres/genre_card_playlist_card.dart index 0e2284b3..1e1b3b76 100644 --- a/lib/modules/home/sections/genres/genre_card_playlist_card.dart +++ b/lib/modules/home/sections/genres/genre_card_playlist_card.dart @@ -1,12 +1,12 @@ -import 'package:go_router/go_router.dart'; +import 'package:auto_route/auto_route.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:spotify/spotify.dart' hide Image; import 'package:spotube/collections/env.dart'; +import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/components/image/universal_image.dart'; import 'package:spotube/extensions/image.dart'; import 'package:spotube/extensions/string.dart'; -import 'package:spotube/pages/playlist/playlist.dart'; import 'package:spotube/provider/spotify/spotify.dart'; import 'package:stroke_text/stroke_text.dart'; @@ -47,12 +47,8 @@ class GenreSectionCardPlaylistCard extends HookConsumerWidget { }, ), onPressed: () { - context.pushNamed( - PlaylistPage.name, - pathParameters: { - "id": playlist.id!, - }, - extra: playlist, + context.navigateTo( + PlaylistRoute(id: playlist.id!, playlist: playlist), ); }, child: Column( diff --git a/lib/modules/home/sections/genres/genres.dart b/lib/modules/home/sections/genres/genres.dart index 64a6be1c..dd5512c8 100644 --- a/lib/modules/home/sections/genres/genres.dart +++ b/lib/modules/home/sections/genres/genres.dart @@ -1,15 +1,16 @@ +import 'package:auto_route/auto_route.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:go_router/go_router.dart'; + import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; import 'package:skeletonizer/skeletonizer.dart'; import 'package:spotube/collections/fake.dart'; +import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/modules/home/sections/genres/genre_card.dart'; -import 'package:spotube/pages/home/genres/genres.dart'; import 'package:spotube/provider/spotify/spotify.dart'; class HomeGenresSection extends HookConsumerWidget { @@ -47,7 +48,7 @@ class HomeGenresSection extends HookConsumerWidget { ), Button.link( onPressed: () { - context.pushNamed(GenrePage.name); + context.navigateTo(const GenreRoute()); }, child: Text( context.l10n.browse_all, diff --git a/lib/modules/library/local_folder/local_folder_item.dart b/lib/modules/library/local_folder/local_folder_item.dart index 149657cc..78f1aa14 100644 --- a/lib/modules/library/local_folder/local_folder_item.dart +++ b/lib/modules/library/local_folder/local_folder_item.dart @@ -1,18 +1,18 @@ import 'dart:math'; +import 'package:auto_route/auto_route.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:path/path.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; +import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/image/universal_image.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/image.dart'; import 'package:spotube/extensions/string.dart'; -import 'package:spotube/pages/library/user_local_tracks/local_folder.dart'; import 'package:spotube/provider/local_tracks/local_tracks_provider.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; @@ -59,13 +59,12 @@ class LocalFolderItem extends HookConsumerWidget { return Button( onPressed: () { - context.pushNamed( - LocalLibraryPage.name, - queryParameters: { - if (isDownloadFolder) "downloads": "true", - if (isCacheFolder) "cache": "true", - }, - extra: folder, + context.navigateTo( + LocalLibraryRoute( + location: folder, + isCache: isCacheFolder, + isDownloads: isDownloadFolder, + ), ); }, style: ButtonVariance.card.copyWith( diff --git a/lib/modules/library/user_downloads/download_item.dart b/lib/modules/library/user_downloads/download_item.dart index 4b104ed1..2c0a96a5 100644 --- a/lib/modules/library/user_downloads/download_item.dart +++ b/lib/modules/library/user_downloads/download_item.dart @@ -1,18 +1,18 @@ +import 'package:auto_route/auto_route.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:spotify/spotify.dart'; +import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/image/universal_image.dart'; import 'package:spotube/components/links/artist_link.dart'; import 'package:spotube/components/ui/button_tile.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/image.dart'; -import 'package:spotube/pages/track/track.dart'; import 'package:spotube/provider/download_manager_provider.dart'; import 'package:spotube/services/download_manager/download_status.dart'; import 'package:spotube/services/sourced_track/sourced_track.dart'; -import 'package:spotube/utils/service_utils.dart'; class DownloadItem extends HookConsumerWidget { final Track track; @@ -66,13 +66,9 @@ class DownloadItem extends HookConsumerWidget { subtitle: ArtistLink( artists: track.artists ?? [], mainAxisAlignment: WrapAlignment.start, - onOverflowArtistClick: () => ServiceUtils.pushNamed( - context, - TrackPage.name, - pathParameters: { - "id": track.id!, - }, - ), + onOverflowArtistClick: () { + context.navigateTo(TrackRoute(trackId: track.id!)); + }, ), trailing: isQueryingSourceInfo ? Text(context.l10n.querying_info).small() diff --git a/lib/modules/player/player.dart b/lib/modules/player/player.dart index 16ee6c72..4335e298 100644 --- a/lib/modules/player/player.dart +++ b/lib/modules/player/player.dart @@ -1,13 +1,14 @@ +import 'package:auto_route/auto_route.dart'; import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter/material.dart' show showModalBottomSheet; import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; import 'package:sliding_up_panel/sliding_up_panel.dart'; import 'package:spotube/collections/assets.gen.dart'; +import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/framework/app_pop_scope.dart'; import 'package:spotube/modules/player/player_actions.dart'; @@ -25,13 +26,11 @@ import 'package:spotube/extensions/image.dart'; import 'package:spotube/models/local_track.dart'; import 'package:spotube/modules/root/spotube_navigation_bar.dart'; import 'package:spotube/pages/lyrics/lyrics.dart'; -import 'package:spotube/pages/track/track.dart'; import 'package:spotube/provider/authentication/authentication.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/server/active_sourced_track.dart'; import 'package:spotube/provider/volume_provider.dart'; import 'package:spotube/services/sourced_track/sources/youtube.dart'; -import 'package:spotube/utils/service_utils.dart'; import 'package:url_launcher/url_launcher_string.dart'; @@ -94,7 +93,7 @@ class PlayerView extends HookConsumerWidget { }, [panelController.isAttached && panelController.isPanelOpen]); return AppPopScope( - canPop: context.canPop(), + canPop: false, onPopInvoked: (didPop) async { await panelController.close(); }, @@ -210,14 +209,10 @@ class PlayerView extends HookConsumerWidget { .copyWith(fontWeight: FontWeight.bold), onRouteChange: (route) { panelController.close(); - GoRouter.of(context).push(route); + context.router.navigateNamed(route); }, - onOverflowArtistClick: () => ServiceUtils.pushNamed( - context, - TrackPage.name, - pathParameters: { - "id": currentTrack!.id!, - }, + onOverflowArtistClick: () => context.navigateTo( + TrackRoute(trackId: currentTrack!.id!), ), ), ], diff --git a/lib/modules/player/player_overlay.dart b/lib/modules/player/player_overlay.dart index a37ac5bc..3c3ff373 100644 --- a/lib/modules/player/player_overlay.dart +++ b/lib/modules/player/player_overlay.dart @@ -1,4 +1,3 @@ -import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:sliding_up_panel/sliding_up_panel.dart'; @@ -8,6 +7,10 @@ import 'package:spotube/modules/root/spotube_navigation_bar.dart'; import 'package:spotube/modules/player/player.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; +final playerOverlayControllerProvider = StateProvider((ref) { + return PanelController(); +}); + class PlayerOverlay extends HookConsumerWidget { final String albumArt; @@ -23,7 +26,7 @@ class PlayerOverlay extends HookConsumerWidget { final screenSize = MediaQuery.sizeOf(context); - final panelController = useMemoized(() => PanelController(), []); + final panelController = ref.watch(playerOverlayControllerProvider); return SlidingUpPanel( maxHeight: screenSize.height, diff --git a/lib/modules/player/player_track_details.dart b/lib/modules/player/player_track_details.dart index 5c13f3e8..2e38bf37 100644 --- a/lib/modules/player/player_track_details.dart +++ b/lib/modules/player/player_track_details.dart @@ -1,17 +1,18 @@ +import 'package:auto_route/auto_route.dart'; + import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotify/spotify.dart'; import 'package:spotube/collections/assets.gen.dart'; +import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/components/image/universal_image.dart'; import 'package:spotube/components/links/artist_link.dart'; import 'package:spotube/components/links/link_text.dart'; import 'package:spotube/extensions/artist_simple.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/image.dart'; -import 'package:spotube/pages/track/track.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; -import 'package:spotube/utils/service_utils.dart'; class PlayerTrackDetails extends HookConsumerWidget { final Color? color; @@ -50,7 +51,7 @@ class PlayerTrackDetails extends HookConsumerWidget { const SizedBox(height: 4), LinkText( playback.activeTrack?.name ?? "", - "/track/${playback.activeTrack?.id}", + TrackRoute(trackId: playback.activeTrack?.id ?? ""), push: true, overflow: TextOverflow.ellipsis, style: theme.typography.normal.copyWith( @@ -72,7 +73,7 @@ class PlayerTrackDetails extends HookConsumerWidget { children: [ LinkText( playback.activeTrack?.name ?? "", - "/track/${playback.activeTrack?.id}", + TrackRoute(trackId: playback.activeTrack?.id ?? ""), push: true, overflow: TextOverflow.ellipsis, style: TextStyle(fontWeight: FontWeight.bold, color: color), @@ -80,15 +81,10 @@ class PlayerTrackDetails extends HookConsumerWidget { ArtistLink( artists: playback.activeTrack?.artists ?? [], onRouteChange: (route) { - ServiceUtils.push(context, route); + context.router.navigateNamed(route); }, - onOverflowArtistClick: () => ServiceUtils.pushNamed( - context, - TrackPage.name, - pathParameters: { - "id": track!.id!, - }, - ), + onOverflowArtistClick: () => + context.navigateTo(TrackRoute(trackId: track!.id!)), ) ], ), diff --git a/lib/modules/playlist/playlist_card.dart b/lib/modules/playlist/playlist_card.dart index c24eb24b..8f0b5097 100644 --- a/lib/modules/playlist/playlist_card.dart +++ b/lib/modules/playlist/playlist_card.dart @@ -1,8 +1,10 @@ +import 'package:auto_route/auto_route.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:spotify/spotify.dart' hide Offset, Image; import 'package:spotube/collections/env.dart'; +import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/components/dialogs/select_device_dialog.dart'; import 'package:spotube/components/image/universal_image.dart'; import 'package:spotube/components/playbutton_view/playbutton_card.dart'; @@ -10,14 +12,12 @@ import 'package:spotube/components/playbutton_view/playbutton_tile.dart'; import 'package:spotube/extensions/context.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/audio_player/querying_track_info.dart'; import 'package:spotube/provider/connect/connect.dart'; import 'package:spotube/provider/history/history.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/spotify/spotify.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; -import 'package:spotube/utils/service_utils.dart'; import 'package:stroke_text/stroke_text.dart'; class PlaylistCard extends HookConsumerWidget { @@ -73,14 +73,7 @@ class PlaylistCard extends HookConsumerWidget { } void onTap() { - ServiceUtils.pushNamed( - context, - PlaylistPage.name, - pathParameters: { - "id": playlist.id!, - }, - extra: playlist, - ); + context.navigateTo(PlaylistRoute(id: playlist.id!, playlist: playlist)); } void onPlaybuttonPressed() async { diff --git a/lib/modules/playlist/playlist_create_dialog.dart b/lib/modules/playlist/playlist_create_dialog.dart index 55e7ce77..9619b2ee 100644 --- a/lib/modules/playlist/playlist_create_dialog.dart +++ b/lib/modules/playlist/playlist_create_dialog.dart @@ -1,11 +1,11 @@ import 'dart:convert'; import 'dart:io'; +import 'package:auto_route/auto_route.dart'; import 'package:collection/collection.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:form_builder_validators/form_builder_validators.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:image_picker/image_picker.dart'; import 'package:path/path.dart'; @@ -105,7 +105,7 @@ class PlaylistCreateDialog extends HookConsumerWidget { isSubmitting.value = false; if (context.mounted && !ref.read(playlistProvider(playlistId ?? "")).hasError) { - context.pop(); + context.router.maybePop(); } } } diff --git a/lib/modules/root/bottom_player.dart b/lib/modules/root/bottom_player.dart index fc581377..18b4c221 100644 --- a/lib/modules/root/bottom_player.dart +++ b/lib/modules/root/bottom_player.dart @@ -1,10 +1,11 @@ +import 'package:auto_route/auto_route.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; import 'package:spotube/collections/assets.gen.dart'; +import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/models/database/database.dart'; import 'package:spotube/modules/player/player_actions.dart'; @@ -96,9 +97,8 @@ class BottomPlayer extends HookConsumerWidget { const Duration(milliseconds: 100), () async { if (context.mounted) { - context.go( - '/mini-player', - extra: prevSize, + context.navigateTo( + MiniLyricsRoute(prevSize: prevSize), ); } }, diff --git a/lib/modules/root/sidebar.dart b/lib/modules/root/sidebar.dart deleted file mode 100644 index 1d5d9da0..00000000 --- a/lib/modules/root/sidebar.dart +++ /dev/null @@ -1,270 +0,0 @@ -import 'package:flutter/material.dart' show Badge; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:go_router/go_router.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:shadcn_flutter/shadcn_flutter.dart'; - -import 'package:spotube/collections/assets.gen.dart'; -import 'package:spotube/collections/side_bar_tiles.dart'; -import 'package:spotube/collections/spotube_icons.dart'; -import 'package:spotube/components/image/universal_image.dart'; -import 'package:spotube/extensions/image.dart'; -import 'package:spotube/models/database/database.dart'; -import 'package:spotube/extensions/constrains.dart'; -import 'package:spotube/extensions/context.dart'; -import 'package:spotube/modules/connect/connect_device.dart'; -import 'package:spotube/pages/library/user_downloads.dart'; -import 'package:spotube/pages/profile/profile.dart'; -import 'package:spotube/pages/settings/settings.dart'; -import 'package:spotube/provider/authentication/authentication.dart'; -import 'package:spotube/provider/download_manager_provider.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; - -import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; - -import 'package:spotube/utils/service_utils.dart'; - -class Sidebar extends HookConsumerWidget { - final Widget child; - - const Sidebar({ - required this.child, - super.key, - }); - - static Widget brandLogo() { - return Container( - decoration: BoxDecoration( - color: Colors.black, - borderRadius: BorderRadius.circular(50), - ), - child: Assets.spotubeLogoPng.image(height: 50), - ); - } - - @override - Widget build(BuildContext context, WidgetRef ref) { - final routerState = GoRouterState.of(context); - final mediaQuery = MediaQuery.of(context); - - final layoutMode = - ref.watch(userPreferencesProvider.select((s) => s.layoutMode)); - - final sidebarTileList = useMemoized( - () => getSidebarTileList(context.l10n), - [context.l10n], - ); - - final sidebarLibraryTileList = useMemoized( - () => getSidebarLibraryTileList(context.l10n), - [context.l10n], - ); - - final tileList = [...sidebarTileList, ...sidebarLibraryTileList]; - - final selectedIndex = tileList.indexWhere( - (e) => routerState.namedLocation(e.name) == routerState.matchedLocation, - ); - - if (layoutMode == LayoutMode.compact || - (mediaQuery.smAndDown && layoutMode == LayoutMode.adaptive)) { - return Scaffold(child: child); - } - - final navigationButtons = [ - NavigationLabel( - child: mediaQuery.lgAndUp ? const Text("Spotube") : const Text(""), - ), - for (final tile in sidebarTileList) - NavigationButton( - label: mediaQuery.lgAndUp ? Text(tile.title) : null, - child: Tooltip( - tooltip: TooltipContainer(child: Text(tile.title)), - child: Icon(tile.icon), - ), - onChanged: (value) { - if (value) { - context.goNamed(tile.name); - } - }, - ), - const NavigationDivider(), - if (mediaQuery.lgAndUp) - NavigationLabel(child: Text(context.l10n.library)), - for (final tile in sidebarLibraryTileList) - NavigationButton( - label: mediaQuery.lgAndUp ? Text(tile.title) : null, - onChanged: (value) { - if (value) { - context.goNamed(tile.name); - } - }, - child: Tooltip( - tooltip: TooltipContainer(child: Text(tile.title)), - child: Icon(tile.icon), - ), - ), - ]; - - return Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Column( - children: [ - Expanded( - child: mediaQuery.lgAndUp - ? NavigationSidebar( - index: selectedIndex, - onSelected: (index) { - final tile = tileList[index]; - context.goNamed(tile.name); - }, - children: navigationButtons, - ) - : NavigationRail( - alignment: NavigationRailAlignment.start, - index: selectedIndex, - onSelected: (index) { - final tile = tileList[index]; - context.goNamed(tile.name); - }, - children: navigationButtons, - ), - ), - const SidebarFooter(), - if (mediaQuery.lgAndUp) const Gap(130) else const Gap(65), - ], - ), - const VerticalDivider(), - Expanded(child: child), - ], - ); - } -} - -class SidebarFooter extends HookConsumerWidget implements NavigationBarItem { - const SidebarFooter({ - super.key, - }); - - @override - Widget build(BuildContext context, ref) { - final theme = Theme.of(context); - final mediaQuery = MediaQuery.of(context); - final routerState = GoRouterState.of(context); - final downloadCount = ref.watch(downloadManagerProvider).$downloadCount; - final userSnapshot = ref.watch(meProvider); - final data = userSnapshot.asData?.value; - - final avatarImg = (data?.images).asUrlString( - index: (data?.images?.length ?? 1) - 1, - placeholder: ImagePlaceholder.artist, - ); - - final auth = ref.watch(authenticationProvider); - - if (mediaQuery.mdAndDown) { - return Column( - mainAxisSize: MainAxisSize.min, - spacing: 10, - children: [ - Badge( - isLabelVisible: downloadCount > 0, - label: Text(downloadCount.toString()), - child: IconButton( - variance: routerState.topRoute?.name == UserDownloadsPage.name - ? ButtonVariance.secondary - : ButtonVariance.ghost, - icon: const Icon(SpotubeIcons.download), - onPressed: () => - ServiceUtils.navigateNamed(context, UserDownloadsPage.name), - ), - ), - const ConnectDeviceButton.sidebar(), - IconButton( - variance: ButtonVariance.ghost, - icon: const Icon(SpotubeIcons.settings), - onPressed: () => - ServiceUtils.navigateNamed(context, SettingsPage.name), - ), - ], - ); - } - - return Container( - padding: const EdgeInsets.only(left: 12), - width: 180, - child: Column( - mainAxisSize: MainAxisSize.min, - spacing: 10, - children: [ - SizedBox( - width: double.infinity, - child: Button( - style: routerState.topRoute?.name == UserDownloadsPage.name - ? ButtonVariance.secondary - : ButtonVariance.outline, - onPressed: () { - ServiceUtils.navigateNamed(context, UserDownloadsPage.name); - }, - leading: const Icon(SpotubeIcons.download), - trailing: downloadCount > 0 - ? PrimaryBadge( - child: Text(downloadCount.toString()), - ) - : null, - child: Text(context.l10n.downloads), - ), - ), - const ConnectDeviceButton.sidebar(), - Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - if (auth.asData?.value != null && data == null) - const CircularProgressIndicator() - else if (data != null) - Flexible( - child: GestureDetector( - onTap: () { - ServiceUtils.pushNamed(context, ProfilePage.name); - }, - child: Row( - children: [ - Avatar( - initials: - Avatar.getInitials(data.displayName ?? "User"), - provider: UniversalImage.imageProvider(avatarImg), - ), - const SizedBox(width: 10), - Flexible( - child: Text( - data.displayName ?? context.l10n.guest, - maxLines: 1, - softWrap: false, - overflow: TextOverflow.fade, - style: theme.typography.normal - .copyWith(fontWeight: FontWeight.bold), - ), - ), - ], - ), - ), - ), - IconButton( - variance: ButtonVariance.ghost, - icon: const Icon(SpotubeIcons.settings), - onPressed: () { - ServiceUtils.pushNamed(context, SettingsPage.name); - }, - ), - ], - ), - ], - ), - ); - } - - @override - bool get selectable => false; -} diff --git a/lib/modules/root/sidebar/sidebar.dart b/lib/modules/root/sidebar/sidebar.dart new file mode 100644 index 00000000..1149bd00 --- /dev/null +++ b/lib/modules/root/sidebar/sidebar.dart @@ -0,0 +1,132 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:shadcn_flutter/shadcn_flutter.dart'; + +import 'package:spotube/collections/assets.gen.dart'; +import 'package:spotube/collections/side_bar_tiles.dart'; +import 'package:spotube/models/database/database.dart'; +import 'package:spotube/extensions/constrains.dart'; +import 'package:spotube/extensions/context.dart'; +import 'package:spotube/modules/root/sidebar/sidebar_footer.dart'; + +import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; + +class Sidebar extends HookConsumerWidget { + final Widget child; + + const Sidebar({ + required this.child, + super.key, + }); + + static Widget brandLogo() { + return Container( + decoration: BoxDecoration( + color: Colors.black, + borderRadius: BorderRadius.circular(50), + ), + child: Assets.spotubeLogoPng.image(height: 50), + ); + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + final mediaQuery = MediaQuery.of(context); + + final layoutMode = + ref.watch(userPreferencesProvider.select((s) => s.layoutMode)); + + final sidebarTileList = useMemoized( + () => getSidebarTileList(context.l10n), + [context.l10n], + ); + + final sidebarLibraryTileList = useMemoized( + () => getSidebarLibraryTileList(context.l10n), + [context.l10n], + ); + + final tileList = [...sidebarTileList, ...sidebarLibraryTileList]; + + final router = context.watchRouter; + + final selectedIndex = tileList.indexWhere( + (e) => router.currentPath.startsWith(e.pathPrefix), + ); + + if (layoutMode == LayoutMode.compact || + (mediaQuery.smAndDown && layoutMode == LayoutMode.adaptive)) { + return child; + } + + final navigationButtons = [ + NavigationLabel( + child: mediaQuery.lgAndUp ? const Text("Spotube") : const Text(""), + ), + for (final tile in sidebarTileList) + NavigationButton( + label: mediaQuery.lgAndUp ? Text(tile.title) : null, + child: Tooltip( + tooltip: TooltipContainer(child: Text(tile.title)), + child: Icon(tile.icon), + ), + onChanged: (value) { + if (value) { + context.navigateTo(tile.route); + } + }, + ), + const NavigationDivider(), + if (mediaQuery.lgAndUp) + NavigationLabel(child: Text(context.l10n.library)), + for (final tile in sidebarLibraryTileList) + NavigationButton( + label: mediaQuery.lgAndUp ? Text(tile.title) : null, + onChanged: (value) { + if (value) { + context.navigateTo(tile.route); + } + }, + child: Tooltip( + tooltip: TooltipContainer(child: Text(tile.title)), + child: Icon(tile.icon), + ), + ), + ]; + + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + children: [ + Expanded( + child: mediaQuery.lgAndUp + ? NavigationSidebar( + index: selectedIndex, + onSelected: (index) { + final tile = tileList[index]; + context.navigateTo(tile.route); + }, + children: navigationButtons, + ) + : NavigationRail( + alignment: NavigationRailAlignment.start, + index: selectedIndex, + onSelected: (index) { + final tile = tileList[index]; + context.navigateTo(tile.route); + }, + children: navigationButtons, + ), + ), + const SidebarFooter(), + if (mediaQuery.lgAndUp) const Gap(130) else const Gap(65), + ], + ), + const VerticalDivider(), + Expanded(child: child), + ], + ); + } +} diff --git a/lib/modules/root/sidebar/sidebar_footer.dart b/lib/modules/root/sidebar/sidebar_footer.dart new file mode 100644 index 00000000..fb3edddd --- /dev/null +++ b/lib/modules/root/sidebar/sidebar_footer.dart @@ -0,0 +1,140 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart' show Badge; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:shadcn_flutter/shadcn_flutter.dart'; + +import 'package:spotube/collections/routes.gr.dart'; +import 'package:spotube/collections/spotube_icons.dart'; +import 'package:spotube/components/image/universal_image.dart'; +import 'package:spotube/extensions/image.dart'; +import 'package:spotube/extensions/constrains.dart'; +import 'package:spotube/extensions/context.dart'; +import 'package:spotube/modules/connect/connect_device.dart'; +import 'package:spotube/provider/authentication/authentication.dart'; +import 'package:spotube/provider/download_manager_provider.dart'; +import 'package:spotube/provider/spotify/spotify.dart'; + +class SidebarFooter extends HookConsumerWidget implements NavigationBarItem { + const SidebarFooter({ + super.key, + }); + + @override + Widget build(BuildContext context, ref) { + final theme = Theme.of(context); + final router = AutoRouter.of(context, watch: true); + final mediaQuery = MediaQuery.of(context); + final downloadCount = ref.watch(downloadManagerProvider).$downloadCount; + final userSnapshot = ref.watch(meProvider); + final data = userSnapshot.asData?.value; + + final avatarImg = (data?.images).asUrlString( + index: (data?.images?.length ?? 1) - 1, + placeholder: ImagePlaceholder.artist, + ); + + final auth = ref.watch(authenticationProvider); + + if (mediaQuery.mdAndDown) { + return Column( + mainAxisSize: MainAxisSize.min, + spacing: 10, + children: [ + Badge( + isLabelVisible: downloadCount > 0, + label: Text(downloadCount.toString()), + child: IconButton( + variance: router.topRoute.name == UserDownloadsRoute.name + ? ButtonVariance.secondary + : ButtonVariance.ghost, + icon: const Icon(SpotubeIcons.download), + onPressed: () => context.navigateTo(const UserDownloadsRoute()), + ), + ), + const ConnectDeviceButton.sidebar(), + IconButton( + variance: ButtonVariance.ghost, + icon: const Icon(SpotubeIcons.settings), + onPressed: () => context.navigateTo(const SettingsRoute()), + ), + ], + ); + } + + return Container( + padding: const EdgeInsets.only(left: 12), + width: 180, + child: Column( + mainAxisSize: MainAxisSize.min, + spacing: 10, + children: [ + SizedBox( + width: double.infinity, + child: Button( + style: router.topRoute.name == UserDownloadsRoute.name + ? ButtonVariance.secondary + : ButtonVariance.outline, + onPressed: () { + context.navigateTo(const UserDownloadsRoute()); + }, + leading: const Icon(SpotubeIcons.download), + trailing: downloadCount > 0 + ? PrimaryBadge( + child: Text(downloadCount.toString()), + ) + : null, + child: Text(context.l10n.downloads), + ), + ), + const ConnectDeviceButton.sidebar(), + Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if (auth.asData?.value != null && data == null) + const CircularProgressIndicator() + else if (data != null) + Flexible( + child: GestureDetector( + onTap: () { + context.navigateTo(const ProfileRoute()); + }, + child: Row( + children: [ + Avatar( + initials: + Avatar.getInitials(data.displayName ?? "User"), + provider: UniversalImage.imageProvider(avatarImg), + ), + const SizedBox(width: 10), + Flexible( + child: Text( + data.displayName ?? context.l10n.guest, + maxLines: 1, + softWrap: false, + overflow: TextOverflow.fade, + style: theme.typography.normal + .copyWith(fontWeight: FontWeight.bold), + ), + ), + ], + ), + ), + ), + IconButton( + variance: ButtonVariance.ghost, + icon: const Icon(SpotubeIcons.settings), + onPressed: () { + context.navigateTo(const SettingsRoute()); + }, + ), + ], + ), + ], + ), + ); + } + + @override + bool get selectable => false; +} diff --git a/lib/modules/root/spotube_navigation_bar.dart b/lib/modules/root/spotube_navigation_bar.dart index c19b3a40..9dc02378 100644 --- a/lib/modules/root/spotube_navigation_bar.dart +++ b/lib/modules/root/spotube_navigation_bar.dart @@ -1,6 +1,8 @@ +import 'dart:math'; + +import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart' show Badge; import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; @@ -12,8 +14,6 @@ import 'package:spotube/models/database/database.dart'; import 'package:spotube/provider/download_manager_provider.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; -import 'package:spotube/utils/service_utils.dart'; - final navigationPanelHeight = StateProvider((ref) => 50); class SpotubeNavigationBar extends HookConsumerWidget { @@ -23,10 +23,9 @@ class SpotubeNavigationBar extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { - final routerState = GoRouterState.of(context); + final mediaQuery = MediaQuery.of(context); final downloadCount = ref.watch(downloadManagerProvider).$downloadCount; - final mediaQuery = MediaQuery.of(context); final layoutMode = ref.watch(userPreferencesProvider.select((s) => s.layoutMode)); @@ -37,13 +36,13 @@ class SpotubeNavigationBar extends HookConsumerWidget { final panelHeight = ref.watch(navigationPanelHeight); - final selectedIndex = useMemoized(() { - final index = navbarTileList.indexWhere( - (e) => routerState.namedLocation(e.name) == routerState.matchedLocation, - ); - - return index == -1 ? 0 : index; - }, [navbarTileList, routerState.matchedLocation]); + final router = context.watchRouter; + final selectedIndex = max( + 0, + navbarTileList.indexWhere( + (e) => router.currentPath.startsWith(e.pathPrefix), + ), + ); if (layoutMode == LayoutMode.extended || (mediaQuery.mdAndUp && layoutMode == LayoutMode.adaptive) || @@ -63,7 +62,7 @@ class SpotubeNavigationBar extends HookConsumerWidget { surfaceBlur: context.theme.surfaceBlur, surfaceOpacity: context.theme.surfaceOpacity, onSelected: (i) { - ServiceUtils.navigateNamed(context, navbarTileList[i].name); + context.navigateTo(navbarTileList[i].route); }, children: [ for (final tile in navbarTileList) diff --git a/lib/modules/stats/common/album_item.dart b/lib/modules/stats/common/album_item.dart index 0920baae..cd0a6caf 100644 --- a/lib/modules/stats/common/album_item.dart +++ b/lib/modules/stats/common/album_item.dart @@ -1,12 +1,12 @@ +import 'package:auto_route/auto_route.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:spotify/spotify.dart'; +import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/components/ui/button_tile.dart'; import 'package:spotube/modules/album/album_card.dart'; import 'package:spotube/components/image/universal_image.dart'; import 'package:spotube/components/links/artist_link.dart'; import 'package:spotube/extensions/image.dart'; -import 'package:spotube/pages/album/album.dart'; -import 'package:spotube/utils/service_utils.dart'; class StatsAlbumItem extends StatelessWidget { final AlbumSimple album; @@ -36,25 +36,15 @@ class StatsAlbumItem extends StatelessWidget { child: ArtistLink( artists: album.artists ?? [], mainAxisAlignment: WrapAlignment.start, - onOverflowArtistClick: () => ServiceUtils.pushNamed( - context, - AlbumPage.name, - pathParameters: { - "id": album.id!, - }, - ), + onOverflowArtistClick: () => + context.navigateTo(AlbumRoute(id: album.id!, album: album)), ), ), ], ), trailing: info, onPressed: () { - ServiceUtils.pushNamed( - context, - AlbumPage.name, - pathParameters: {"id": album.id!}, - extra: album, - ); + context.navigateTo(AlbumRoute(id: album.id!, album: album)); }, ); } diff --git a/lib/modules/stats/common/artist_item.dart b/lib/modules/stats/common/artist_item.dart index 26691ba4..5eff9a9d 100644 --- a/lib/modules/stats/common/artist_item.dart +++ b/lib/modules/stats/common/artist_item.dart @@ -1,10 +1,10 @@ +import 'package:auto_route/auto_route.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:spotify/spotify.dart'; +import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/components/image/universal_image.dart'; import 'package:spotube/components/ui/button_tile.dart'; import 'package:spotube/extensions/image.dart'; -import 'package:spotube/pages/artist/artist.dart'; -import 'package:spotube/utils/service_utils.dart'; class StatsArtistItem extends StatelessWidget { final Artist artist; @@ -30,11 +30,7 @@ class StatsArtistItem extends StatelessWidget { ), trailing: info, onPressed: () { - ServiceUtils.pushNamed( - context, - ArtistPage.name, - pathParameters: {"id": artist.id!}, - ); + context.navigateTo(ArtistRoute(artistId: artist.id!)); }, ); } diff --git a/lib/modules/stats/common/playlist_item.dart b/lib/modules/stats/common/playlist_item.dart index 3859db6b..58610af1 100644 --- a/lib/modules/stats/common/playlist_item.dart +++ b/lib/modules/stats/common/playlist_item.dart @@ -1,11 +1,11 @@ +import 'package:auto_route/auto_route.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:spotify/spotify.dart'; +import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/components/image/universal_image.dart'; import 'package:spotube/components/ui/button_tile.dart'; import 'package:spotube/extensions/image.dart'; import 'package:spotube/extensions/string.dart'; -import 'package:spotube/pages/playlist/playlist.dart'; -import 'package:spotube/utils/service_utils.dart'; class StatsPlaylistItem extends StatelessWidget { final PlaylistSimple playlist; @@ -35,12 +35,7 @@ class StatsPlaylistItem extends StatelessWidget { ), trailing: info, onPressed: () { - ServiceUtils.pushNamed( - context, - PlaylistPage.name, - pathParameters: {"id": playlist.id!}, - extra: playlist, - ); + context.navigateTo(PlaylistRoute(id: playlist.id!, playlist: playlist)); }, ); } diff --git a/lib/modules/stats/common/track_item.dart b/lib/modules/stats/common/track_item.dart index 8f0f5b8d..ae2e22c6 100644 --- a/lib/modules/stats/common/track_item.dart +++ b/lib/modules/stats/common/track_item.dart @@ -1,11 +1,11 @@ +import 'package:auto_route/auto_route.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:spotify/spotify.dart'; +import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/components/image/universal_image.dart'; import 'package:spotube/components/links/artist_link.dart'; import 'package:spotube/components/ui/button_tile.dart'; import 'package:spotube/extensions/image.dart'; -import 'package:spotube/pages/track/track.dart'; -import 'package:spotube/utils/service_utils.dart'; class StatsTrackItem extends StatelessWidget { final Track track; @@ -34,23 +34,13 @@ class StatsTrackItem extends StatelessWidget { subtitle: ArtistLink( artists: track.artists!, mainAxisAlignment: WrapAlignment.start, - onOverflowArtistClick: () => ServiceUtils.pushNamed( - context, - TrackPage.name, - pathParameters: { - "id": track.id!, - }, - ), + onOverflowArtistClick: () { + context.navigateTo(TrackRoute(trackId: track.id!)); + }, ), trailing: info, onPressed: () { - ServiceUtils.pushNamed( - context, - TrackPage.name, - pathParameters: { - "id": track.id!, - }, - ); + context.navigateTo(TrackRoute(trackId: track.id!)); }, ); } diff --git a/lib/modules/stats/summary/summary.dart b/lib/modules/stats/summary/summary.dart index 351b0264..352d9ed3 100644 --- a/lib/modules/stats/summary/summary.dart +++ b/lib/modules/stats/summary/summary.dart @@ -1,19 +1,14 @@ +import 'package:auto_route/auto_route.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:skeletonizer/skeletonizer.dart'; import 'package:spotube/collections/fake.dart'; import 'package:spotube/collections/formatters.dart'; +import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/modules/stats/summary/summary_card.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; -import 'package:spotube/pages/stats/albums/albums.dart'; -import 'package:spotube/pages/stats/artists/artists.dart'; -import 'package:spotube/pages/stats/fees/fees.dart'; -import 'package:spotube/pages/stats/minutes/minutes.dart'; -import 'package:spotube/pages/stats/playlists/playlists.dart'; -import 'package:spotube/pages/stats/streams/streams.dart'; import 'package:spotube/provider/history/summary.dart'; -import 'package:spotube/utils/service_utils.dart'; class StatsPageSummarySection extends HookConsumerWidget { const StatsPageSummarySection({super.key}); @@ -50,7 +45,7 @@ class StatsPageSummarySection extends HookConsumerWidget { description: context.l10n.summary_listened_to_music, color: Colors.indigo, onTap: () { - ServiceUtils.pushNamed(context, StatsMinutesPage.name); + context.navigateTo(const StatsMinutesRoute()); }, ), SummaryCard( @@ -59,7 +54,7 @@ class StatsPageSummarySection extends HookConsumerWidget { description: context.l10n.summary_streamed_overall, color: Colors.blue, onTap: () { - ServiceUtils.pushNamed(context, StatsStreamsPage.name); + context.navigateTo(const StatsStreamsRoute()); }, ), SummaryCard.unformatted( @@ -68,7 +63,7 @@ class StatsPageSummarySection extends HookConsumerWidget { description: context.l10n.summary_owed_to_artists, color: Colors.green, onTap: () { - ServiceUtils.pushNamed(context, StatsStreamFeesPage.name); + context.navigateTo(const StatsStreamsRoute()); }, ), SummaryCard( @@ -77,7 +72,7 @@ class StatsPageSummarySection extends HookConsumerWidget { description: context.l10n.summary_music_reached_you, color: Colors.yellow, onTap: () { - ServiceUtils.pushNamed(context, StatsArtistsPage.name); + context.navigateTo(const StatsArtistsRoute()); }, ), SummaryCard( @@ -86,7 +81,7 @@ class StatsPageSummarySection extends HookConsumerWidget { description: context.l10n.summary_got_your_love, color: Colors.pink, onTap: () { - ServiceUtils.pushNamed(context, StatsAlbumsPage.name); + context.navigateTo(const StatsAlbumsRoute()); }, ), SummaryCard( @@ -95,7 +90,7 @@ class StatsPageSummarySection extends HookConsumerWidget { description: context.l10n.summary_were_on_repeat, color: Colors.teal, onTap: () { - ServiceUtils.pushNamed(context, StatsPlaylistsPage.name); + context.navigateTo(const StatsPlaylistsRoute()); }, ), ]), diff --git a/lib/modules/stats/top/top.dart b/lib/modules/stats/top/top.dart index 8e9134c7..1df2b7e9 100644 --- a/lib/modules/stats/top/top.dart +++ b/lib/modules/stats/top/top.dart @@ -56,6 +56,7 @@ class StatsPageTopSection extends HookConsumerWidget { floating: true, elevation: 0, backgroundColor: context.theme.colorScheme.background, + automaticallyImplyLeading: false, flexibleSpace: Padding( padding: const EdgeInsets.all(8.0), child: Row( diff --git a/lib/pages/album/album.dart b/lib/pages/album/album.dart index bc013574..5d19f5ed 100644 --- a/lib/pages/album/album.dart +++ b/lib/pages/album/album.dart @@ -1,3 +1,4 @@ +import 'package:auto_route/auto_route.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:spotify/spotify.dart'; @@ -7,12 +8,15 @@ import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/image.dart'; import 'package:spotube/provider/spotify/spotify.dart'; +@RoutePage() class AlbumPage extends HookConsumerWidget { static const name = "album"; final AlbumSimple album; + final String id; const AlbumPage({ super.key, + @PathParam("id") required this.id, required this.album, }); diff --git a/lib/pages/artist/artist.dart b/lib/pages/artist/artist.dart index 5565d897..67db398d 100644 --- a/lib/pages/artist/artist.dart +++ b/lib/pages/artist/artist.dart @@ -13,12 +13,17 @@ import 'package:spotube/pages/artist/section/header.dart'; import 'package:spotube/pages/artist/section/related_artists.dart'; import 'package:spotube/pages/artist/section/top_tracks.dart'; import 'package:spotube/provider/spotify/spotify.dart'; +import 'package:auto_route/auto_route.dart'; +@RoutePage() class ArtistPage extends HookConsumerWidget { static const name = "artist"; final String artistId; - const ArtistPage(this.artistId, {super.key}); + const ArtistPage( + @PathParam("id") this.artistId, { + super.key, + }); @override Widget build(BuildContext context, ref) { diff --git a/lib/pages/connect/connect.dart b/lib/pages/connect/connect.dart index 55c72026..bb8bbfae 100644 --- a/lib/pages/connect/connect.dart +++ b/lib/pages/connect/connect.dart @@ -1,14 +1,15 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; +import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/ui/button_tile.dart'; import 'package:spotube/modules/connect/local_devices.dart'; import 'package:spotube/components/titlebar/titlebar.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'; +import 'package:auto_route/auto_route.dart'; +@RoutePage() class ConnectPage extends HookConsumerWidget { static const name = "connect"; @@ -22,68 +23,65 @@ class ConnectPage extends HookConsumerWidget { final connectClientsNotifier = ref.read(connectClientsProvider.notifier); final discoveredDevices = connectClients.asData?.value.services; - return Scaffold( - headers: [ - TitleBar( - automaticallyImplyLeading: true, - title: Text(context.l10n.devices), - ) - ], - child: Padding( - padding: const EdgeInsets.all(10.0), - child: CustomScrollView( - slivers: [ - SliverPadding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - sliver: SliverToBoxAdapter( - child: Text( - context.l10n.remote, - style: typography.bold, + return SafeArea( + bottom: false, + child: Scaffold( + headers: [ + TitleBar(title: Text(context.l10n.devices)), + ], + child: Padding( + padding: const EdgeInsets.all(10.0), + child: CustomScrollView( + slivers: [ + SliverPadding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + sliver: SliverToBoxAdapter( + child: Text( + context.l10n.remote, + style: typography.bold, + ), ), ), - ), - const SliverGap(10), - SliverList.separated( - itemCount: discoveredDevices?.length ?? 0, - separatorBuilder: (context, index) => const Gap(10), - itemBuilder: (context, index) { - final device = discoveredDevices![index]; - final selected = - connectClients.asData?.value.resolvedService?.name == - device.name; - return ButtonTile( - selected: selected, - leading: const Icon(SpotubeIcons.monitor), - title: Text(device.name), - subtitle: selected - ? Text( - "${connectClients.asData?.value.resolvedService?.host}" - ":${connectClients.asData?.value.resolvedService?.port}", - ) - : null, - trailing: selected - ? IconButton.outline( - icon: const Icon(SpotubeIcons.power), - size: ButtonSize.small, - onPressed: () => - connectClientsNotifier.clearResolvedService(), - ) - : null, - onPressed: () { - if (selected) { - ServiceUtils.pushNamed( - context, - ConnectControlPage.name, - ); - } else { - connectClientsNotifier.resolveService(device); - } - }, - ); - }, - ), - const ConnectPageLocalDevices(), - ], + const SliverGap(10), + SliverList.separated( + itemCount: discoveredDevices?.length ?? 0, + separatorBuilder: (context, index) => const Gap(10), + itemBuilder: (context, index) { + final device = discoveredDevices![index]; + final selected = + connectClients.asData?.value.resolvedService?.name == + device.name; + return ButtonTile( + selected: selected, + leading: const Icon(SpotubeIcons.monitor), + title: Text(device.name), + subtitle: selected + ? Text( + "${connectClients.asData?.value.resolvedService?.host}" + ":${connectClients.asData?.value.resolvedService?.port}", + ) + : null, + trailing: selected + ? IconButton.outline( + icon: const Icon(SpotubeIcons.power), + size: ButtonSize.small, + onPressed: () => + connectClientsNotifier.clearResolvedService(), + ) + : null, + onPressed: () { + if (selected) { + context.navigateTo(const ConnectControlRoute()); + } else { + connectClientsNotifier.resolveService(device); + } + }, + ); + }, + ), + const ConnectPageLocalDevices(), + ], + ), ), ), ); diff --git a/lib/pages/connect/control/control.dart b/lib/pages/connect/control/control.dart index afe68b16..2511809c 100644 --- a/lib/pages/connect/control/control.dart +++ b/lib/pages/connect/control/control.dart @@ -1,7 +1,8 @@ -import 'package:go_router/go_router.dart'; +import 'package:auto_route/auto_route.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; +import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/modules/player/player_queue.dart'; import 'package:spotube/modules/player/volume_slider.dart'; @@ -13,11 +14,9 @@ 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:media_kit/media_kit.dart' hide Track; -import 'package:spotube/utils/service_utils.dart'; class RemotePlayerQueue extends ConsumerWidget { const RemotePlayerQueue({super.key}); @@ -46,6 +45,7 @@ class RemotePlayerQueue extends ConsumerWidget { } } +@RoutePage() class ConnectControlPage extends HookConsumerWidget { static const name = "connect_control"; @@ -65,7 +65,7 @@ class ConnectControlPage extends HookConsumerWidget { ref.listen(connectClientsProvider, (prev, next) { if (next.asData?.value.resolvedService == null) { - context.pop(); + context.back(); } }); @@ -75,7 +75,6 @@ class ConnectControlPage extends HookConsumerWidget { headers: [ TitleBar( title: Text(resolvedService!.name), - automaticallyImplyLeading: true, ) ], child: LayoutBuilder(builder: (context, constrains) { @@ -115,12 +114,9 @@ class ConnectControlPage extends HookConsumerWidget { style: typography.h4, onTap: () { if (playlist.activeTrack == null) return; - ServiceUtils.pushNamed( - context, - TrackPage.name, - pathParameters: { - "id": playlist.activeTrack!.id!, - }, + context.navigateTo( + TrackRoute( + trackId: playlist.activeTrack!.id!), ); }, ), @@ -130,13 +126,8 @@ class ConnectControlPage extends HookConsumerWidget { artists: playlist.activeTrack?.artists ?? [], textStyle: typography.normal, mainAxisAlignment: WrapAlignment.start, - onOverflowArtistClick: () => - ServiceUtils.pushNamed( - context, - TrackPage.name, - pathParameters: { - "id": playlist.activeTrack!.id!, - }, + onOverflowArtistClick: () => context.navigateTo( + TrackRoute(trackId: playlist.activeTrack!.id!), ), ), ), diff --git a/lib/pages/getting_started/getting_started.dart b/lib/pages/getting_started/getting_started.dart index f710bd8f..a576ed09 100644 --- a/lib/pages/getting_started/getting_started.dart +++ b/lib/pages/getting_started/getting_started.dart @@ -8,11 +8,13 @@ import 'package:spotube/pages/getting_started/sections/greeting.dart'; import 'package:spotube/pages/getting_started/sections/playback.dart'; import 'package:spotube/pages/getting_started/sections/region.dart'; import 'package:spotube/pages/getting_started/sections/support.dart'; +import 'package:auto_route/auto_route.dart'; -class GettingStarting extends HookConsumerWidget { +@RoutePage() +class GettingStartedPage extends HookConsumerWidget { static const name = "getting_started"; - const GettingStarting({super.key}); + const GettingStartedPage({super.key}); @override Widget build(BuildContext context, ref) { diff --git a/lib/pages/getting_started/sections/support.dart b/lib/pages/getting_started/sections/support.dart index 640b0b38..9559d28d 100644 --- a/lib/pages/getting_started/sections/support.dart +++ b/lib/pages/getting_started/sections/support.dart @@ -1,11 +1,11 @@ -import 'package:go_router/go_router.dart'; +import 'package:auto_route/auto_route.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:spotube/collections/env.dart'; +import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/modules/getting_started/blur_card.dart'; import 'package:spotube/extensions/context.dart'; -import 'package:spotube/pages/home/home.dart'; import 'package:spotube/pages/mobile_login/hooks/login_callback.dart'; import 'package:spotube/services/kv_store/kv_store.dart'; import 'package:url_launcher/url_launcher_string.dart'; @@ -112,7 +112,7 @@ class GettingStartedScreenSupportSection extends HookConsumerWidget { onPressed: () async { await KVStoreService.setDoneGettingStarted(true); if (context.mounted) { - context.goNamed(HomePage.name); + context.navigateTo(const HomeRoute()); } }, child: Text(context.l10n.browse_anonymously), diff --git a/lib/pages/home/feed/feed_section.dart b/lib/pages/home/feed/feed_section.dart index 38d0887c..2b38d0ed 100644 --- a/lib/pages/home/feed/feed_section.dart +++ b/lib/pages/home/feed/feed_section.dart @@ -1,3 +1,4 @@ +import 'package:auto_route/auto_route.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; @@ -10,11 +11,15 @@ import 'package:spotube/modules/playlist/playlist_card.dart'; import 'package:spotube/components/titlebar/titlebar.dart'; import 'package:spotube/provider/spotify/views/home_section.dart'; +@RoutePage() class HomeFeedSectionPage extends HookConsumerWidget { static const name = "home_feed_section"; final String sectionUri; - const HomeFeedSectionPage({super.key, required this.sectionUri}); + const HomeFeedSectionPage({ + super.key, + @PathParam("feedId") required this.sectionUri, + }); @override Widget build(BuildContext context, ref) { @@ -23,68 +28,71 @@ class HomeFeedSectionPage extends HookConsumerWidget { final controller = useScrollController(); final isArtist = section.items.every((item) => item.artist != null); - return Skeletonizer( - enabled: homeFeedSection.isLoading, - child: Scaffold( - headers: [ - TitleBar( - title: Text(section.title ?? ""), - automaticallyImplyLeading: true, - ) - ], - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: CustomScrollView( - controller: controller, - slivers: [ - if (isArtist) - SliverGrid.builder( - gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( - maxCrossAxisExtent: 200, - mainAxisExtent: 250, - crossAxisSpacing: 8, - mainAxisSpacing: 8, + return SafeArea( + bottom: false, + child: Skeletonizer( + enabled: homeFeedSection.isLoading, + child: Scaffold( + headers: [ + TitleBar( + title: Text(section.title ?? ""), + ) + ], + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: CustomScrollView( + controller: controller, + slivers: [ + if (isArtist) + SliverGrid.builder( + gridDelegate: + const SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: 200, + mainAxisExtent: 250, + crossAxisSpacing: 8, + mainAxisSpacing: 8, + ), + itemCount: section.items.length, + itemBuilder: (context, index) { + final item = section.items[index]; + return ArtistCard(item.artist!.asArtist); + }, + ) + else + PlaybuttonView( + controller: controller, + itemCount: section.items.length, + hasMore: false, + isLoading: false, + onRequestMore: () => {}, + listItemBuilder: (context, index) { + final item = section.items[index]; + if (item.album != null) { + return AlbumCard.tile(item.album!.asAlbum); + } + if (item.playlist != null) { + return PlaylistCard.tile(item.playlist!.asPlaylist); + } + return const SizedBox.shrink(); + }, + gridItemBuilder: (context, index) { + final item = section.items[index]; + if (item.album != null) { + return AlbumCard(item.album!.asAlbum); + } + if (item.playlist != null) { + return PlaylistCard(item.playlist!.asPlaylist); + } + return const SizedBox.shrink(); + }, + ), + const SliverToBoxAdapter( + child: SafeArea( + child: SizedBox(), ), - itemCount: section.items.length, - itemBuilder: (context, index) { - final item = section.items[index]; - return ArtistCard(item.artist!.asArtist); - }, - ) - else - PlaybuttonView( - controller: controller, - itemCount: section.items.length, - hasMore: false, - isLoading: false, - onRequestMore: () => {}, - listItemBuilder: (context, index) { - final item = section.items[index]; - if (item.album != null) { - return AlbumCard.tile(item.album!.asAlbum); - } - if (item.playlist != null) { - return PlaylistCard.tile(item.playlist!.asPlaylist); - } - return const SizedBox.shrink(); - }, - gridItemBuilder: (context, index) { - final item = section.items[index]; - if (item.album != null) { - return AlbumCard(item.album!.asAlbum); - } - if (item.playlist != null) { - return PlaylistCard(item.playlist!.asPlaylist); - } - return const SizedBox.shrink(); - }, ), - const SliverToBoxAdapter( - child: SafeArea( - child: SizedBox(), - ), - ), - ], + ], + ), ), ), ), diff --git a/lib/pages/home/genres/genre_playlists.dart b/lib/pages/home/genres/genre_playlists.dart index ebfc4450..ea421cb4 100644 --- a/lib/pages/home/genres/genre_playlists.dart +++ b/lib/pages/home/genres/genre_playlists.dart @@ -1,12 +1,12 @@ import 'package:flutter/material.dart' show CollapseMode, FlexibleSpaceBar; import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; import 'package:spotify/spotify.dart' hide Offset; +import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/components/button/back_button.dart'; import 'package:spotube/components/playbutton_view/playbutton_view.dart'; import 'package:spotube/hooks/utils/use_custom_status_bar_color.dart'; @@ -16,12 +16,19 @@ import 'package:spotube/components/titlebar/titlebar.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/provider/spotify/spotify.dart'; import 'package:spotube/utils/platform.dart'; +import 'package:auto_route/auto_route.dart'; +@RoutePage() class GenrePlaylistsPage extends HookConsumerWidget { static const name = "genre_playlists"; final Category category; - const GenrePlaylistsPage({super.key, required this.category}); + final String id; + const GenrePlaylistsPage({ + super.key, + @PathParam("categoryId") required this.id, + required this.category, + }); @override Widget build(BuildContext context, ref) { @@ -30,102 +37,106 @@ class GenrePlaylistsPage extends HookConsumerWidget { final playlistsNotifier = ref.read(categoryPlaylistsProvider(category.id!).notifier); final scrollController = useScrollController(); - final routeName = GoRouterState.of(context).name; useCustomStatusBarColor( Colors.black, - routeName == GenrePlaylistsPage.name, + context.watchRouter.topRoute.name == GenrePlaylistsRoute.name, noSetBGColor: true, automaticSystemUiAdjustment: false, ); - return Scaffold( - headers: [ - if (kIsDesktop) - const TitleBar( - leading: [ - BackButton(), - ], - backgroundColor: Colors.transparent, - surfaceOpacity: 0, - surfaceBlur: 0, - ) - ], - floatingHeader: true, - child: DecoratedBox( - decoration: BoxDecoration( - image: DecorationImage( - image: UniversalImage.imageProvider(category.icons!.first.url!), - alignment: Alignment.topCenter, - fit: BoxFit.cover, - repeat: ImageRepeat.noRepeat, - matchTextDirection: true, + return SafeArea( + child: Scaffold( + headers: [ + if (kIsDesktop) + const TitleBar( + leading: [ + BackButton(), + ], + backgroundColor: Colors.transparent, + surfaceOpacity: 0, + surfaceBlur: 0, + ) + ], + floatingHeader: true, + child: DecoratedBox( + decoration: BoxDecoration( + image: DecorationImage( + image: UniversalImage.imageProvider(category.icons!.first.url!), + alignment: Alignment.topCenter, + fit: BoxFit.cover, + repeat: ImageRepeat.noRepeat, + matchTextDirection: true, + ), ), - ), - child: SurfaceCard( - borderRadius: BorderRadius.zero, - padding: EdgeInsets.zero, - child: CustomScrollView( - controller: scrollController, - slivers: [ - SliverAppBar( - automaticallyImplyLeading: false, - leading: kIsMobile ? const BackButton() : null, - expandedHeight: mediaQuery.mdAndDown ? 200 : 150, - title: const Text(""), - backgroundColor: Colors.transparent, - flexibleSpace: FlexibleSpaceBar( - centerTitle: kIsDesktop, - title: Text( - category.name!, - style: context.theme.typography.h3.copyWith( - color: Colors.white, - letterSpacing: 3, - shadows: [ - Shadow( - offset: const Offset(-1.5, -1.5), - color: Colors.black.withAlpha(138), + child: SurfaceCard( + borderRadius: BorderRadius.zero, + padding: EdgeInsets.zero, + child: CustomScrollView( + controller: scrollController, + slivers: [ + SliverSafeArea( + bottom: false, + sliver: SliverAppBar( + automaticallyImplyLeading: false, + leading: kIsMobile ? const BackButton() : null, + expandedHeight: mediaQuery.mdAndDown ? 200 : 150, + title: const Text(""), + backgroundColor: Colors.transparent, + flexibleSpace: FlexibleSpaceBar( + centerTitle: kIsDesktop, + title: Text( + category.name!, + style: context.theme.typography.h3.copyWith( + color: Colors.white, + letterSpacing: 3, + shadows: [ + Shadow( + offset: const Offset(-1.5, -1.5), + color: Colors.black.withAlpha(138), + ), + Shadow( + offset: const Offset(1.5, -1.5), + color: Colors.black.withAlpha(138), + ), + Shadow( + offset: const Offset(1.5, 1.5), + color: Colors.black.withAlpha(138), + ), + Shadow( + offset: const Offset(-1.5, 1.5), + color: Colors.black.withAlpha(138), + ), + ], ), - Shadow( - offset: const Offset(1.5, -1.5), - color: Colors.black.withAlpha(138), - ), - Shadow( - offset: const Offset(1.5, 1.5), - color: Colors.black.withAlpha(138), - ), - Shadow( - offset: const Offset(-1.5, 1.5), - color: Colors.black.withAlpha(138), - ), - ], + ), + collapseMode: CollapseMode.parallax, ), ), - collapseMode: CollapseMode.parallax, ), - ), - const SliverGap(20), - SliverSafeArea( - top: false, - sliver: SliverPadding( - padding: EdgeInsets.symmetric( - horizontal: mediaQuery.mdAndDown ? 12 : 24, - ), - sliver: PlaybuttonView( - controller: scrollController, - itemCount: playlists.asData?.value.items.length ?? 0, - isLoading: playlists.isLoading, - hasMore: playlists.asData?.value.hasMore == true, - onRequestMore: playlistsNotifier.fetchMore, - listItemBuilder: (context, index) => - PlaylistCard.tile(playlists.asData!.value.items[index]), - gridItemBuilder: (context, index) => - PlaylistCard(playlists.asData!.value.items[index]), + const SliverGap(20), + SliverSafeArea( + top: false, + sliver: SliverPadding( + padding: EdgeInsets.symmetric( + horizontal: mediaQuery.mdAndDown ? 12 : 24, + ), + sliver: PlaybuttonView( + controller: scrollController, + itemCount: playlists.asData?.value.items.length ?? 0, + isLoading: playlists.isLoading, + hasMore: playlists.asData?.value.hasMore == true, + onRequestMore: playlistsNotifier.fetchMore, + listItemBuilder: (context, index) => PlaylistCard.tile( + playlists.asData!.value.items[index]), + gridItemBuilder: (context, index) => + PlaylistCard(playlists.asData!.value.items[index]), + ), ), ), - ), - const SliverGap(20), - ], + const SliverGap(20), + ], + ), ), ), ), diff --git a/lib/pages/home/genres/genres.dart b/lib/pages/home/genres/genres.dart index 062852e8..38d110db 100644 --- a/lib/pages/home/genres/genres.dart +++ b/lib/pages/home/genres/genres.dart @@ -3,17 +3,19 @@ import 'dart:math'; import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:go_router/go_router.dart'; + import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; import 'package:spotube/collections/gradients.dart'; +import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/components/titlebar/titlebar.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'; +import 'package:auto_route/auto_route.dart'; +@RoutePage() class GenrePage extends HookConsumerWidget { static const name = "genre"; const GenrePage({super.key}); @@ -30,7 +32,6 @@ class GenrePage extends HookConsumerWidget { headers: [ TitleBar( title: Text(context.l10n.explore_genres), - automaticallyImplyLeading: true, ) ], child: GridView.builder( @@ -49,12 +50,11 @@ class GenrePage extends HookConsumerWidget { final gradient = gradients[Random().nextInt(gradients.length)]; return CardImage( onPressed: () { - context.pushNamed( - GenrePlaylistsPage.name, - pathParameters: { - "categoryId": category.id!, - }, - extra: category, + context.navigateTo( + GenrePlaylistsRoute( + id: category.id!, + category: category, + ), ); }, image: Stack( diff --git a/lib/pages/home/home.dart b/lib/pages/home/home.dart index 1638393b..cd3ed273 100644 --- a/lib/pages/home/home.dart +++ b/lib/pages/home/home.dart @@ -1,9 +1,11 @@ +import 'package:auto_route/auto_route.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; import 'package:spotube/collections/assets.gen.dart'; +import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/models/database/database.dart'; import 'package:spotube/modules/connect/connect_device.dart'; @@ -16,11 +18,10 @@ import 'package:spotube/modules/home/sections/new_releases.dart'; import 'package:spotube/modules/home/sections/recent.dart'; import 'package:spotube/components/titlebar/titlebar.dart'; import 'package:spotube/extensions/constrains.dart'; -import 'package:spotube/pages/settings/settings.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; import 'package:spotube/utils/platform.dart'; -import 'package:spotube/utils/service_utils.dart'; +@RoutePage() class HomePage extends HookConsumerWidget { static const name = "home"; const HomePage({super.key}); @@ -53,7 +54,7 @@ class HomePage extends HookConsumerWidget { IconButton.ghost( icon: const Icon(SpotubeIcons.settings, size: 20), onPressed: () { - ServiceUtils.pushNamed(context, SettingsPage.name); + context.navigateTo(const SettingsRoute()); }, ), const Gap(10), diff --git a/lib/pages/lastfm_login/lastfm_login.dart b/lib/pages/lastfm_login/lastfm_login.dart index 89b8270a..41042a1b 100644 --- a/lib/pages/lastfm_login/lastfm_login.dart +++ b/lib/pages/lastfm_login/lastfm_login.dart @@ -1,6 +1,5 @@ import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:spotube/collections/spotube_icons.dart'; @@ -9,14 +8,15 @@ import 'package:spotube/components/dialogs/prompt_dialog.dart'; import 'package:spotube/components/titlebar/titlebar.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/provider/scrobbler/scrobbler.dart'; +import 'package:auto_route/auto_route.dart'; +@RoutePage() class LastFMLoginPage extends HookConsumerWidget { static const name = "lastfm_login"; const LastFMLoginPage({super.key}); @override Widget build(BuildContext context, ref) { - final router = GoRouter.of(context); final scrobblerNotifier = ref.read(scrobblerProvider.notifier); final usernameKey = @@ -31,6 +31,7 @@ class LastFMLoginPage extends HookConsumerWidget { return Scaffold( headers: const [ SafeArea( + bottom: false, child: TitleBar( leading: [BackButton()], ), @@ -39,100 +40,104 @@ class LastFMLoginPage extends HookConsumerWidget { child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - Container( - constraints: const BoxConstraints(maxWidth: 400), - alignment: Alignment.center, - padding: const EdgeInsets.all(16), - child: Card( - padding: const EdgeInsets.all(16.0), - child: Form( - onSubmit: (context, values) async { - try { - isLoading.value = true; - await scrobblerNotifier.login( - values[usernameKey].trim(), - values[passwordKey], - ); - router.pop(); - } catch (e) { - if (context.mounted) { - showPromptDialog( - context: context, - title: context.l10n.error("Authentication failed"), - message: e.toString(), - cancelText: null, + Flexible( + child: Container( + constraints: const BoxConstraints(maxWidth: 400), + alignment: Alignment.center, + padding: const EdgeInsets.all(16), + child: Card( + padding: const EdgeInsets.all(16.0), + child: Form( + onSubmit: (context, values) async { + try { + isLoading.value = true; + await scrobblerNotifier.login( + values[usernameKey].trim(), + values[passwordKey], ); + if (context.mounted) { + context.back(); + } + } catch (e) { + if (context.mounted) { + showPromptDialog( + context: context, + title: context.l10n.error("Authentication failed"), + message: e.toString(), + cancelText: null, + ); + } + } finally { + isLoading.value = false; } - } finally { - isLoading.value = false; - } - }, - child: Column( - mainAxisSize: MainAxisSize.min, - spacing: 10, - children: [ - Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(30), - color: const Color.fromARGB(255, 186, 0, 0), + }, + child: Column( + mainAxisSize: MainAxisSize.min, + spacing: 10, + children: [ + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(30), + color: const Color.fromARGB(255, 186, 0, 0), + ), + padding: const EdgeInsets.all(12), + child: const Icon( + SpotubeIcons.lastFm, + color: Colors.white, + size: 60, + ), ), - padding: const EdgeInsets.all(12), - child: const Icon( - SpotubeIcons.lastFm, - color: Colors.white, - size: 60, - ), - ), - const Text("last.fm").h3(), - Text(context.l10n.login_with_your_lastfm), - AutofillGroup( - child: Column( - spacing: 10, - children: [ - FormField( - label: Text(context.l10n.username), - key: usernameKey, - validator: const NotEmptyValidator(), - child: TextField( - autofillHints: const [ - AutofillHints.username, - AutofillHints.email, - ], - placeholder: Text(context.l10n.username), - ), - ), - FormField( - key: passwordKey, - validator: const NotEmptyValidator(), - label: Text(context.l10n.password), - child: TextField( - autofillHints: const [ - AutofillHints.password, - ], - obscureText: !passwordVisible.value, - placeholder: Text(context.l10n.password), - trailing: IconButton.ghost( - icon: Icon( - passwordVisible.value - ? SpotubeIcons.eye - : SpotubeIcons.noEye, - ), - onPressed: () => passwordVisible.value = - !passwordVisible.value, + const Text("last.fm").h3(), + Text(context.l10n.login_with_your_lastfm), + AutofillGroup( + child: Column( + spacing: 10, + children: [ + FormField( + label: Text(context.l10n.username), + key: usernameKey, + validator: const NotEmptyValidator(), + child: TextField( + autofillHints: const [ + AutofillHints.username, + AutofillHints.email, + ], + placeholder: Text(context.l10n.username), ), ), - ), - ], + FormField( + key: passwordKey, + validator: const NotEmptyValidator(), + label: Text(context.l10n.password), + child: TextField( + autofillHints: const [ + AutofillHints.password, + ], + obscureText: !passwordVisible.value, + placeholder: Text(context.l10n.password), + trailing: IconButton.ghost( + icon: Icon( + passwordVisible.value + ? SpotubeIcons.eye + : SpotubeIcons.noEye, + ), + onPressed: () => passwordVisible.value = + !passwordVisible.value, + ), + ), + ), + ], + ), ), - ), - FormErrorBuilder(builder: (context, errors, child) { - return Button.primary( - onPressed: () => context.submitForm(), - enabled: errors.isEmpty && !isLoading.value, - child: Text(context.l10n.login), - ); - }), - ], + FormErrorBuilder(builder: (context, errors, child) { + return Button.primary( + onPressed: () => context.submitForm(), + enabled: errors.isEmpty && !isLoading.value, + child: Text(context.l10n.login), + ); + }), + ], + ), ), ), ), diff --git a/lib/pages/library/library.dart b/lib/pages/library/library.dart index 6677a3b6..359cc609 100644 --- a/lib/pages/library/library.dart +++ b/lib/pages/library/library.dart @@ -1,73 +1,81 @@ +import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart' show Badge; import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; +import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/collections/side_bar_tiles.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/titlebar/titlebar.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; -import 'package:spotube/pages/library/user_downloads.dart'; import 'package:spotube/provider/download_manager_provider.dart'; +@RoutePage() class LibraryPage extends HookConsumerWidget { - final Widget child; - const LibraryPage({super.key, required this.child}); + const LibraryPage({super.key}); @override Widget build(BuildContext context, ref) { final downloadingCount = ref.watch(downloadManagerProvider).$downloadCount; - final routerState = GoRouterState.of(context); + final router = context.watchRouter; final sidebarLibraryTileList = useMemoized( () => [ ...getSidebarLibraryTileList(context.l10n), SideBarTiles( id: "downloads", + pathPrefix: "library/downloads", title: context.l10n.downloads, - name: UserDownloadsPage.name, + route: const UserDownloadsRoute(), icon: SpotubeIcons.download, ), ], [context.l10n], ); final index = sidebarLibraryTileList.indexWhere( - (e) => routerState.namedLocation(e.name) == routerState.matchedLocation, + (e) => router.currentPath.startsWith(e.pathPrefix), ); - return SafeArea( - bottom: false, - child: LayoutBuilder(builder: (context, constraints) { - return Scaffold( - headers: [ - if (constraints.smAndDown) - TitleBar( - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: TabList( - index: index, - children: [ - for (final tile in sidebarLibraryTileList) - TabButton( - child: Badge( - isLabelVisible: - tile.id == 'downloads' && downloadingCount > 0, - label: Text(downloadingCount.toString()), - child: Text(tile.title), + return PopScope( + canPop: false, + onPopInvokedWithResult: (didPop, result) { + context.navigateTo(const HomeRoute()); + }, + child: SafeArea( + bottom: false, + child: LayoutBuilder(builder: (context, constraints) { + return Scaffold( + headers: [ + if (constraints.smAndDown) + TitleBar( + automaticallyImplyLeading: false, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: TabList( + index: index, + children: [ + for (final tile in sidebarLibraryTileList) + TabButton( + child: Badge( + isLabelVisible: tile.id == 'downloads' && + downloadingCount > 0, + label: Text(downloadingCount.toString()), + child: Text(tile.title), + ), + onPressed: () { + context.navigateTo(tile.route); + }, ), - onPressed: () { - context.goNamed(tile.name); - }, - ), - ], + ], + ), ), ), - ), - const Gap(10), - ], - child: child, - ); - }), + const Gap(10), + ], + child: const AutoRouter(), + ); + }), + ), ); } } diff --git a/lib/pages/library/playlist_generate/playlist_generate.dart b/lib/pages/library/playlist_generate/playlist_generate.dart index 2b1e7512..573d502c 100644 --- a/lib/pages/library/playlist_generate/playlist_generate.dart +++ b/lib/pages/library/playlist_generate/playlist_generate.dart @@ -1,10 +1,11 @@ import 'package:collection/collection.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:go_router/go_router.dart'; + import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:spotify/spotify.dart'; +import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/collections/spotify_markets.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/button/back_button.dart'; @@ -23,9 +24,11 @@ import 'package:spotube/models/spotify/recommendation_seeds.dart'; import 'package:spotube/provider/spotify/spotify.dart'; import 'package:spotube/provider/spotify_provider.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; +import 'package:auto_route/auto_route.dart'; const RecommendationAttribute zeroValues = (min: 0, target: 0, max: 0); +@RoutePage() class PlaylistGeneratorPage extends HookConsumerWidget { static const name = "playlist_generator"; @@ -253,425 +256,430 @@ class PlaylistGeneratorPage extends HookConsumerWidget { final controller = useScrollController(); - return Scaffold( - headers: [ - TitleBar( - leading: const [BackButton()], - title: Text(context.l10n.generate), - ) - ], - child: Scrollbar( - controller: controller, - child: Center( - child: ConstrainedBox( - constraints: BoxConstraints(maxWidth: Breakpoints.lg), - child: SafeArea( - child: LayoutBuilder(builder: (context, constrains) { - return ScrollConfiguration( - behavior: ScrollConfiguration.of(context) - .copyWith(scrollbars: false), - child: ListView( - controller: controller, - padding: const EdgeInsets.all(16), - children: [ - ValueListenableBuilder( - valueListenable: limit, - builder: (context, value, child) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - context.l10n.number_of_tracks_generate, - style: typography.semiBold, - ), - Row( - spacing: 5, - children: [ - Container( - width: 40, - height: 40, - alignment: Alignment.center, - decoration: BoxDecoration( - color: theme.colorScheme.primary - .withAlpha(25), - shape: BoxShape.circle, - ), - child: Text( - value.round().toString(), - style: typography.large.copyWith( - color: theme.colorScheme.primary, + return SafeArea( + bottom: false, + child: Scaffold( + headers: [ + TitleBar( + leading: const [BackButton()], + title: Text(context.l10n.generate), + ) + ], + child: Scrollbar( + controller: controller, + child: Center( + child: ConstrainedBox( + constraints: BoxConstraints(maxWidth: Breakpoints.lg), + child: SafeArea( + child: LayoutBuilder(builder: (context, constrains) { + return ScrollConfiguration( + behavior: ScrollConfiguration.of(context) + .copyWith(scrollbars: false), + child: ListView( + controller: controller, + padding: const EdgeInsets.all(16), + children: [ + ValueListenableBuilder( + valueListenable: limit, + builder: (context, value, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + context.l10n.number_of_tracks_generate, + style: typography.semiBold, + ), + Row( + spacing: 5, + children: [ + Container( + width: 40, + height: 40, + alignment: Alignment.center, + decoration: BoxDecoration( + color: theme.colorScheme.primary + .withAlpha(25), + shape: BoxShape.circle, + ), + child: Text( + value.round().toString(), + style: typography.large.copyWith( + color: theme.colorScheme.primary, + ), ), ), - ), - Expanded( - child: Slider( - value: - SliderValue.single(value.toDouble()), - min: 10, - max: 100, - divisions: 9, - onChanged: (value) { - limit.value = value.value.round(); - }, - ), - ) - ], - ) + Expanded( + child: Slider( + value: SliderValue.single( + value.toDouble()), + min: 10, + max: 100, + divisions: 9, + onChanged: (value) { + limit.value = value.value.round(); + }, + ), + ) + ], + ) + ], + ); + }, + ), + const SizedBox(height: 16), + if (constrains.mdAndUp) + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: countrySelector, + ), + const SizedBox(width: 16), + Expanded( + child: genreSelector, + ), ], - ); - }, - ), - const SizedBox(height: 16), - if (constrains.mdAndUp) - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: countrySelector, - ), - const SizedBox(width: 16), - Expanded( - child: genreSelector, - ), - ], - ) - else ...[ - countrySelector, + ) + else ...[ + countrySelector, + const SizedBox(height: 16), + genreSelector, + ], const SizedBox(height: 16), - genreSelector, - ], - const SizedBox(height: 16), - if (constrains.mdAndUp) - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: artistAutoComplete, - ), - const SizedBox(width: 16), - Expanded( - child: tracksAutocomplete, - ), - ], - ) - else ...[ - artistAutoComplete, + if (constrains.mdAndUp) + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: artistAutoComplete, + ), + const SizedBox(width: 16), + Expanded( + child: tracksAutocomplete, + ), + ], + ) + else ...[ + artistAutoComplete, + const SizedBox(height: 16), + tracksAutocomplete, + ], const SizedBox(height: 16), - tracksAutocomplete, - ], - const SizedBox(height: 16), - RecommendationAttributeDials( - title: Text(context.l10n.acousticness), - values: ( - target: target.value.acousticness?.toDouble() ?? 0, - min: min.value.acousticness?.toDouble() ?? 0, - max: max.value.acousticness?.toDouble() ?? 0, - ), - onChanged: (value) { - target.value = target.value.copyWith( - acousticness: value.target, - ); - min.value = min.value.copyWith( - acousticness: value.min, - ); - max.value = max.value.copyWith( - acousticness: value.max, - ); - }, - ), - RecommendationAttributeDials( - title: Text(context.l10n.danceability), - values: ( - target: target.value.danceability?.toDouble() ?? 0, - min: min.value.danceability?.toDouble() ?? 0, - max: max.value.danceability?.toDouble() ?? 0, - ), - onChanged: (value) { - target.value = target.value.copyWith( - danceability: value.target, - ); - min.value = min.value.copyWith( - danceability: value.min, - ); - max.value = max.value.copyWith( - danceability: value.max, - ); - }, - ), - RecommendationAttributeDials( - title: Text(context.l10n.energy), - values: ( - target: target.value.energy?.toDouble() ?? 0, - min: min.value.energy?.toDouble() ?? 0, - max: max.value.energy?.toDouble() ?? 0, - ), - onChanged: (value) { - target.value = target.value.copyWith( - energy: value.target, - ); - min.value = min.value.copyWith( - energy: value.min, - ); - max.value = max.value.copyWith( - energy: value.max, - ); - }, - ), - RecommendationAttributeDials( - title: Text(context.l10n.instrumentalness), - values: ( - target: - target.value.instrumentalness?.toDouble() ?? 0, - min: min.value.instrumentalness?.toDouble() ?? 0, - max: max.value.instrumentalness?.toDouble() ?? 0, - ), - onChanged: (value) { - target.value = target.value.copyWith( - instrumentalness: value.target, - ); - min.value = min.value.copyWith( - instrumentalness: value.min, - ); - max.value = max.value.copyWith( - instrumentalness: value.max, - ); - }, - ), - RecommendationAttributeDials( - title: Text(context.l10n.liveness), - values: ( - target: target.value.liveness?.toDouble() ?? 0, - min: min.value.liveness?.toDouble() ?? 0, - max: max.value.liveness?.toDouble() ?? 0, - ), - onChanged: (value) { - target.value = target.value.copyWith( - liveness: value.target, - ); - min.value = min.value.copyWith( - liveness: value.min, - ); - max.value = max.value.copyWith( - liveness: value.max, - ); - }, - ), - RecommendationAttributeDials( - title: Text(context.l10n.loudness), - values: ( - target: target.value.loudness?.toDouble() ?? 0, - min: min.value.loudness?.toDouble() ?? 0, - max: max.value.loudness?.toDouble() ?? 0, - ), - onChanged: (value) { - target.value = target.value.copyWith( - loudness: value.target, - ); - min.value = min.value.copyWith( - loudness: value.min, - ); - max.value = max.value.copyWith( - loudness: value.max, - ); - }, - ), - RecommendationAttributeDials( - title: Text(context.l10n.speechiness), - values: ( - target: target.value.speechiness?.toDouble() ?? 0, - min: min.value.speechiness?.toDouble() ?? 0, - max: max.value.speechiness?.toDouble() ?? 0, - ), - onChanged: (value) { - target.value = target.value.copyWith( - speechiness: value.target, - ); - min.value = min.value.copyWith( - speechiness: value.min, - ); - max.value = max.value.copyWith( - speechiness: value.max, - ); - }, - ), - RecommendationAttributeDials( - title: Text(context.l10n.valence), - values: ( - target: target.value.valence?.toDouble() ?? 0, - min: min.value.valence?.toDouble() ?? 0, - max: max.value.valence?.toDouble() ?? 0, - ), - onChanged: (value) { - target.value = target.value.copyWith( - valence: value.target, - ); - min.value = min.value.copyWith( - valence: value.min, - ); - max.value = max.value.copyWith( - valence: value.max, - ); - }, - ), - RecommendationAttributeDials( - title: Text(context.l10n.popularity), - base: 100, - values: ( - target: target.value.popularity?.toDouble() ?? 0, - min: min.value.popularity?.toDouble() ?? 0, - max: max.value.popularity?.toDouble() ?? 0, - ), - onChanged: (value) { - target.value = target.value.copyWith( - popularity: value.target, - ); - min.value = min.value.copyWith( - popularity: value.min, - ); - max.value = max.value.copyWith( - popularity: value.max, - ); - }, - ), - RecommendationAttributeDials( - title: Text(context.l10n.key), - base: 11, - values: ( - target: target.value.key?.toDouble() ?? 0, - min: min.value.key?.toDouble() ?? 0, - max: max.value.key?.toDouble() ?? 0, - ), - onChanged: (value) { - target.value = target.value.copyWith( - key: value.target, - ); - min.value = min.value.copyWith( - key: value.min, - ); - max.value = max.value.copyWith( - key: value.max, - ); - }, - ), - RecommendationAttributeFields( - title: Text(context.l10n.duration), - values: ( - max: (max.value.durationMs ?? 0) / 1000, - target: (target.value.durationMs ?? 0) / 1000, - min: (min.value.durationMs ?? 0) / 1000, - ), - onChanged: (value) { - target.value = target.value.copyWith( - durationMs: (value.target * 1000).toInt(), - ); - min.value = min.value.copyWith( - durationMs: (value.min * 1000).toInt(), - ); - max.value = max.value.copyWith( - durationMs: (value.max * 1000).toInt(), - ); - }, - presets: { - context.l10n.short: (min: 50, target: 90, max: 120), - context.l10n.medium: ( - min: 120, - target: 180, - max: 200 + RecommendationAttributeDials( + title: Text(context.l10n.acousticness), + values: ( + target: target.value.acousticness?.toDouble() ?? 0, + min: min.value.acousticness?.toDouble() ?? 0, + max: max.value.acousticness?.toDouble() ?? 0, ), - context.l10n.long: (min: 480, target: 560, max: 640) - }, - ), - RecommendationAttributeFields( - title: Text(context.l10n.tempo), - values: ( - max: max.value.tempo?.toDouble() ?? 0, - target: target.value.tempo?.toDouble() ?? 0, - min: min.value.tempo?.toDouble() ?? 0, + onChanged: (value) { + target.value = target.value.copyWith( + acousticness: value.target, + ); + min.value = min.value.copyWith( + acousticness: value.min, + ); + max.value = max.value.copyWith( + acousticness: value.max, + ); + }, ), - onChanged: (value) { - target.value = target.value.copyWith( - tempo: value.target, - ); - min.value = min.value.copyWith( - tempo: value.min, - ); - max.value = max.value.copyWith( - tempo: value.max, - ); - }, - ), - RecommendationAttributeFields( - title: Text(context.l10n.mode), - values: ( - max: max.value.mode?.toDouble() ?? 0, - target: target.value.mode?.toDouble() ?? 0, - min: min.value.mode?.toDouble() ?? 0, + RecommendationAttributeDials( + title: Text(context.l10n.danceability), + values: ( + target: target.value.danceability?.toDouble() ?? 0, + min: min.value.danceability?.toDouble() ?? 0, + max: max.value.danceability?.toDouble() ?? 0, + ), + onChanged: (value) { + target.value = target.value.copyWith( + danceability: value.target, + ); + min.value = min.value.copyWith( + danceability: value.min, + ); + max.value = max.value.copyWith( + danceability: value.max, + ); + }, ), - onChanged: (value) { - target.value = target.value.copyWith( - mode: value.target, - ); - min.value = min.value.copyWith( - mode: value.min, - ); - max.value = max.value.copyWith( - mode: value.max, - ); - }, - ), - RecommendationAttributeFields( - title: Text(context.l10n.time_signature), - values: ( - max: max.value.timeSignature?.toDouble() ?? 0, - target: target.value.timeSignature?.toDouble() ?? 0, - min: min.value.timeSignature?.toDouble() ?? 0, + RecommendationAttributeDials( + title: Text(context.l10n.energy), + values: ( + target: target.value.energy?.toDouble() ?? 0, + min: min.value.energy?.toDouble() ?? 0, + max: max.value.energy?.toDouble() ?? 0, + ), + onChanged: (value) { + target.value = target.value.copyWith( + energy: value.target, + ); + min.value = min.value.copyWith( + energy: value.min, + ); + max.value = max.value.copyWith( + energy: value.max, + ); + }, ), - onChanged: (value) { - target.value = target.value.copyWith( - timeSignature: value.target, - ); - min.value = min.value.copyWith( - timeSignature: value.min, - ); - max.value = max.value.copyWith( - timeSignature: value.max, - ); - }, - ), - const Gap(20), - Center( - child: Button.primary( - leading: const Icon(SpotubeIcons.magic), - onPressed: artists.value.isEmpty && - tracks.value.isEmpty && - genres.value.isEmpty - ? null - : () { - final routeState = - GeneratePlaylistProviderInput( - seedArtists: artists.value - .map((a) => a.id!) - .toList(), - seedTracks: - tracks.value.map((t) => t.id!).toList(), - seedGenres: genres.value, - limit: limit.value, - max: max.value, - min: min.value, - target: target.value, - ); - GoRouter.of(context).push( - "/library/generate/result", - extra: routeState, - ); - }, - child: Text(context.l10n.generate), + RecommendationAttributeDials( + title: Text(context.l10n.instrumentalness), + values: ( + target: + target.value.instrumentalness?.toDouble() ?? 0, + min: min.value.instrumentalness?.toDouble() ?? 0, + max: max.value.instrumentalness?.toDouble() ?? 0, + ), + onChanged: (value) { + target.value = target.value.copyWith( + instrumentalness: value.target, + ); + min.value = min.value.copyWith( + instrumentalness: value.min, + ); + max.value = max.value.copyWith( + instrumentalness: value.max, + ); + }, ), - ), - ], - ), - ); - }), + RecommendationAttributeDials( + title: Text(context.l10n.liveness), + values: ( + target: target.value.liveness?.toDouble() ?? 0, + min: min.value.liveness?.toDouble() ?? 0, + max: max.value.liveness?.toDouble() ?? 0, + ), + onChanged: (value) { + target.value = target.value.copyWith( + liveness: value.target, + ); + min.value = min.value.copyWith( + liveness: value.min, + ); + max.value = max.value.copyWith( + liveness: value.max, + ); + }, + ), + RecommendationAttributeDials( + title: Text(context.l10n.loudness), + values: ( + target: target.value.loudness?.toDouble() ?? 0, + min: min.value.loudness?.toDouble() ?? 0, + max: max.value.loudness?.toDouble() ?? 0, + ), + onChanged: (value) { + target.value = target.value.copyWith( + loudness: value.target, + ); + min.value = min.value.copyWith( + loudness: value.min, + ); + max.value = max.value.copyWith( + loudness: value.max, + ); + }, + ), + RecommendationAttributeDials( + title: Text(context.l10n.speechiness), + values: ( + target: target.value.speechiness?.toDouble() ?? 0, + min: min.value.speechiness?.toDouble() ?? 0, + max: max.value.speechiness?.toDouble() ?? 0, + ), + onChanged: (value) { + target.value = target.value.copyWith( + speechiness: value.target, + ); + min.value = min.value.copyWith( + speechiness: value.min, + ); + max.value = max.value.copyWith( + speechiness: value.max, + ); + }, + ), + RecommendationAttributeDials( + title: Text(context.l10n.valence), + values: ( + target: target.value.valence?.toDouble() ?? 0, + min: min.value.valence?.toDouble() ?? 0, + max: max.value.valence?.toDouble() ?? 0, + ), + onChanged: (value) { + target.value = target.value.copyWith( + valence: value.target, + ); + min.value = min.value.copyWith( + valence: value.min, + ); + max.value = max.value.copyWith( + valence: value.max, + ); + }, + ), + RecommendationAttributeDials( + title: Text(context.l10n.popularity), + base: 100, + values: ( + target: target.value.popularity?.toDouble() ?? 0, + min: min.value.popularity?.toDouble() ?? 0, + max: max.value.popularity?.toDouble() ?? 0, + ), + onChanged: (value) { + target.value = target.value.copyWith( + popularity: value.target, + ); + min.value = min.value.copyWith( + popularity: value.min, + ); + max.value = max.value.copyWith( + popularity: value.max, + ); + }, + ), + RecommendationAttributeDials( + title: Text(context.l10n.key), + base: 11, + values: ( + target: target.value.key?.toDouble() ?? 0, + min: min.value.key?.toDouble() ?? 0, + max: max.value.key?.toDouble() ?? 0, + ), + onChanged: (value) { + target.value = target.value.copyWith( + key: value.target, + ); + min.value = min.value.copyWith( + key: value.min, + ); + max.value = max.value.copyWith( + key: value.max, + ); + }, + ), + RecommendationAttributeFields( + title: Text(context.l10n.duration), + values: ( + max: (max.value.durationMs ?? 0) / 1000, + target: (target.value.durationMs ?? 0) / 1000, + min: (min.value.durationMs ?? 0) / 1000, + ), + onChanged: (value) { + target.value = target.value.copyWith( + durationMs: (value.target * 1000).toInt(), + ); + min.value = min.value.copyWith( + durationMs: (value.min * 1000).toInt(), + ); + max.value = max.value.copyWith( + durationMs: (value.max * 1000).toInt(), + ); + }, + presets: { + context.l10n.short: (min: 50, target: 90, max: 120), + context.l10n.medium: ( + min: 120, + target: 180, + max: 200 + ), + context.l10n.long: (min: 480, target: 560, max: 640) + }, + ), + RecommendationAttributeFields( + title: Text(context.l10n.tempo), + values: ( + max: max.value.tempo?.toDouble() ?? 0, + target: target.value.tempo?.toDouble() ?? 0, + min: min.value.tempo?.toDouble() ?? 0, + ), + onChanged: (value) { + target.value = target.value.copyWith( + tempo: value.target, + ); + min.value = min.value.copyWith( + tempo: value.min, + ); + max.value = max.value.copyWith( + tempo: value.max, + ); + }, + ), + RecommendationAttributeFields( + title: Text(context.l10n.mode), + values: ( + max: max.value.mode?.toDouble() ?? 0, + target: target.value.mode?.toDouble() ?? 0, + min: min.value.mode?.toDouble() ?? 0, + ), + onChanged: (value) { + target.value = target.value.copyWith( + mode: value.target, + ); + min.value = min.value.copyWith( + mode: value.min, + ); + max.value = max.value.copyWith( + mode: value.max, + ); + }, + ), + RecommendationAttributeFields( + title: Text(context.l10n.time_signature), + values: ( + max: max.value.timeSignature?.toDouble() ?? 0, + target: target.value.timeSignature?.toDouble() ?? 0, + min: min.value.timeSignature?.toDouble() ?? 0, + ), + onChanged: (value) { + target.value = target.value.copyWith( + timeSignature: value.target, + ); + min.value = min.value.copyWith( + timeSignature: value.min, + ); + max.value = max.value.copyWith( + timeSignature: value.max, + ); + }, + ), + const Gap(20), + Center( + child: Button.primary( + leading: const Icon(SpotubeIcons.magic), + onPressed: artists.value.isEmpty && + tracks.value.isEmpty && + genres.value.isEmpty + ? null + : () { + final routeState = + GeneratePlaylistProviderInput( + seedArtists: artists.value + .map((a) => a.id!) + .toList(), + seedTracks: tracks.value + .map((t) => t.id!) + .toList(), + seedGenres: genres.value, + limit: limit.value, + max: max.value, + min: min.value, + target: target.value, + ); + context.navigateTo( + PlaylistGenerateResultRoute( + state: routeState, + ), + ); + }, + child: Text(context.l10n.generate), + ), + ), + ], + ), + ); + }), + ), ), ), ), diff --git a/lib/pages/library/playlist_generate/playlist_generate_result.dart b/lib/pages/library/playlist_generate/playlist_generate_result.dart index 87d6fdc9..9e6f2987 100644 --- a/lib/pages/library/playlist_generate/playlist_generate_result.dart +++ b/lib/pages/library/playlist_generate/playlist_generate_result.dart @@ -1,8 +1,10 @@ +import 'package:auto_route/auto_route.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:go_router/go_router.dart'; + import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:spotify/spotify.dart'; +import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/button/back_button.dart'; import 'package:spotube/modules/library/playlist_generate/simple_track_tile.dart'; @@ -11,10 +13,10 @@ import 'package:spotube/components/dialogs/playlist_add_track_dialog.dart'; import 'package:spotube/components/titlebar/titlebar.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/audio_player/audio_player.dart'; import 'package:spotube/provider/spotify/spotify.dart'; +@RoutePage() class PlaylistGenerateResultPage extends HookConsumerWidget { static const name = "playlist_generate_result"; @@ -27,8 +29,6 @@ class PlaylistGenerateResultPage extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { - final router = GoRouter.of(context); - final playlistNotifier = ref.watch(audioPlayerProvider.notifier); final generatedPlaylist = ref.watch(generatePlaylistProvider(state)); @@ -48,219 +48,225 @@ class PlaylistGenerateResultPage extends HookConsumerWidget { final isAllTrackSelected = selectedTracks.value.length == (generatedPlaylist.asData?.value.length ?? 0); - return Scaffold( - headers: const [ - TitleBar(leading: [BackButton()]) - ], - child: generatedPlaylist.isLoading - ? Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const CircularProgressIndicator(), - Text(context.l10n.generating_playlist), - ], - ), - ) - : Padding( - padding: const EdgeInsets.all(8.0), - child: ListView( - children: [ - GridView( - gridDelegate: - const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 2, - crossAxisSpacing: 8, - mainAxisSpacing: 8, - mainAxisExtent: 32, - ), - shrinkWrap: true, - children: [ - Button.primary( - leading: const Icon(SpotubeIcons.play), - onPressed: selectedTracks.value.isEmpty - ? null - : () async { - await playlistNotifier.load( - generatedPlaylist.asData!.value - .where( - (e) => selectedTracks.value - .contains(e.id!), - ) - .toList(), - autoPlay: true, - ); - }, - child: Text(context.l10n.play), + return SafeArea( + bottom: false, + child: Scaffold( + headers: const [ + TitleBar(leading: [BackButton()]) + ], + child: generatedPlaylist.isLoading + ? Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const CircularProgressIndicator(), + Text(context.l10n.generating_playlist), + ], + ), + ) + : Padding( + padding: const EdgeInsets.all(8.0), + child: ListView( + children: [ + GridView( + gridDelegate: + const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + crossAxisSpacing: 8, + mainAxisSpacing: 8, + mainAxisExtent: 32, ), - Button.primary( - leading: const Icon(SpotubeIcons.queueAdd), - onPressed: selectedTracks.value.isEmpty - ? null - : () async { - await playlistNotifier.addTracks( - generatedPlaylist.asData!.value.where( - (e) => selectedTracks.value.contains(e.id!), - ), - ); - if (context.mounted) { - showToast( - context: context, - location: ToastLocation.topRight, - builder: (context, overlay) { - return SurfaceCard( - child: Text( - context.l10n.add_count_to_queue( - selectedTracks.value.length, - ), - ), - ); - }, - ); - } - }, - child: Text(context.l10n.add_to_queue), - ), - Button.primary( - leading: const Icon(SpotubeIcons.addFilled), - onPressed: selectedTracks.value.isEmpty - ? null - : () async { - final playlist = await showDialog( - context: context, - builder: (context) => PlaylistCreateDialog( - trackIds: selectedTracks.value, - ), - ); - - if (playlist != null) { - router.goNamed( - PlaylistPage.name, - pathParameters: { - "id": playlist.id!, - }, - extra: playlist, - ); - } - }, - child: Text(context.l10n.create_a_playlist), - ), - Button.primary( - leading: const Icon(SpotubeIcons.playlistAdd), - onPressed: selectedTracks.value.isEmpty - ? null - : () async { - final hasAdded = await showDialog( - context: context, - builder: (context) => PlaylistAddTrackDialog( - openFromPlaylist: null, - tracks: selectedTracks.value - .map( - (e) => generatedPlaylist.asData!.value - .firstWhere( - (element) => element.id == e, - ), + shrinkWrap: true, + children: [ + Button.primary( + leading: const Icon(SpotubeIcons.play), + onPressed: selectedTracks.value.isEmpty + ? null + : () async { + await playlistNotifier.load( + generatedPlaylist.asData!.value + .where( + (e) => selectedTracks.value + .contains(e.id!), ) .toList(), - ), - ); - - if (context.mounted && hasAdded == true) { - showToast( - context: context, - location: ToastLocation.topRight, - builder: (context, overlay) { - return SurfaceCard( - child: Text( - context.l10n.add_count_to_playlist( - selectedTracks.value.length, - ), - ), - ); - }, + autoPlay: true, ); - } - }, - child: Text(context.l10n.add_to_playlist), - ) - ], - ), - const SizedBox(height: 16), - if (generatedPlaylist.asData?.value != null) - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - context.l10n.selected_count_tracks( - selectedTracks.value.length, - ), + }, + child: Text(context.l10n.play), ), - Button.secondary( - onPressed: () { - if (isAllTrackSelected) { - selectedTracks.value = []; - } else { - selectedTracks.value = generatedPlaylist - .asData?.value - .map((e) => e.id!) - .toList() ?? - []; - } - }, - leading: const Icon(SpotubeIcons.selectionCheck), - child: Text( - isAllTrackSelected - ? context.l10n.deselect_all - : context.l10n.select_all, - ), + Button.primary( + leading: const Icon(SpotubeIcons.queueAdd), + onPressed: selectedTracks.value.isEmpty + ? null + : () async { + await playlistNotifier.addTracks( + generatedPlaylist.asData!.value.where( + (e) => + selectedTracks.value.contains(e.id!), + ), + ); + if (context.mounted) { + showToast( + context: context, + location: ToastLocation.topRight, + builder: (context, overlay) { + return SurfaceCard( + child: Text( + context.l10n.add_count_to_queue( + selectedTracks.value.length, + ), + ), + ); + }, + ); + } + }, + child: Text(context.l10n.add_to_queue), ), + Button.primary( + leading: const Icon(SpotubeIcons.addFilled), + onPressed: selectedTracks.value.isEmpty + ? null + : () async { + final playlist = await showDialog( + context: context, + builder: (context) => PlaylistCreateDialog( + trackIds: selectedTracks.value, + ), + ); + + if (playlist != null && context.mounted) { + context.navigateTo( + PlaylistRoute( + id: playlist.id!, + playlist: playlist, + ), + ); + } + }, + child: Text(context.l10n.create_a_playlist), + ), + Button.primary( + leading: const Icon(SpotubeIcons.playlistAdd), + onPressed: selectedTracks.value.isEmpty + ? null + : () async { + final hasAdded = await showDialog( + context: context, + builder: (context) => + PlaylistAddTrackDialog( + openFromPlaylist: null, + tracks: selectedTracks.value + .map( + (e) => generatedPlaylist + .asData!.value + .firstWhere( + (element) => element.id == e, + ), + ) + .toList(), + ), + ); + + if (context.mounted && hasAdded == true) { + showToast( + context: context, + location: ToastLocation.topRight, + builder: (context, overlay) { + return SurfaceCard( + child: Text( + context.l10n.add_count_to_playlist( + selectedTracks.value.length, + ), + ), + ); + }, + ); + } + }, + child: Text(context.l10n.add_to_playlist), + ) ], ), - const SizedBox(height: 8), - SafeArea( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - for (final track - in generatedPlaylist.asData?.value ?? []) - Row( - spacing: 5, - children: [ - Checkbox( - state: selectedTracks.value.contains(track.id) - ? CheckboxState.checked - : CheckboxState.unchecked, - onChanged: (value) { - if (value == CheckboxState.checked) { - selectedTracks.value.add(track.id!); - } else { - selectedTracks.value.remove(track.id); - } - selectedTracks.value = - selectedTracks.value.toList(); - }, - ), - Expanded( - child: GestureDetector( - onTap: () { - selectedTracks.value.contains(track.id) - ? selectedTracks.value.remove(track.id) - : selectedTracks.value.add(track.id!); + const SizedBox(height: 16), + if (generatedPlaylist.asData?.value != null) + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + context.l10n.selected_count_tracks( + selectedTracks.value.length, + ), + ), + Button.secondary( + onPressed: () { + if (isAllTrackSelected) { + selectedTracks.value = []; + } else { + selectedTracks.value = generatedPlaylist + .asData?.value + .map((e) => e.id!) + .toList() ?? + []; + } + }, + leading: const Icon(SpotubeIcons.selectionCheck), + child: Text( + isAllTrackSelected + ? context.l10n.deselect_all + : context.l10n.select_all, + ), + ), + ], + ), + const SizedBox(height: 8), + SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + for (final track + in generatedPlaylist.asData?.value ?? []) + Row( + spacing: 5, + children: [ + Checkbox( + state: selectedTracks.value.contains(track.id) + ? CheckboxState.checked + : CheckboxState.unchecked, + onChanged: (value) { + if (value == CheckboxState.checked) { + selectedTracks.value.add(track.id!); + } else { + selectedTracks.value.remove(track.id); + } selectedTracks.value = selectedTracks.value.toList(); }, - child: SimpleTrackTile(track: track), ), - ), - ], - ) - ], + Expanded( + child: GestureDetector( + onTap: () { + selectedTracks.value.contains(track.id) + ? selectedTracks.value + .remove(track.id) + : selectedTracks.value.add(track.id!); + selectedTracks.value = + selectedTracks.value.toList(); + }, + child: SimpleTrackTile(track: track), + ), + ), + ], + ) + ], + ), ), - ), - ], + ], + ), ), - ), + ), ); } } diff --git a/lib/pages/library/user_albums.dart b/lib/pages/library/user_albums.dart index 861d1705..99cee135 100644 --- a/lib/pages/library/user_albums.dart +++ b/lib/pages/library/user_albums.dart @@ -15,7 +15,9 @@ import 'package:spotube/components/fallbacks/anonymous_fallback.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/provider/authentication/authentication.dart'; import 'package:spotube/provider/spotify/spotify.dart'; +import 'package:auto_route/auto_route.dart'; +@RoutePage() class UserAlbumsPage extends HookConsumerWidget { static const name = 'user_albums'; const UserAlbumsPage({super.key}); @@ -63,6 +65,7 @@ class UserAlbumsPage extends HookConsumerWidget { controller: controller, slivers: [ SliverAppBar( + automaticallyImplyLeading: false, backgroundColor: Theme.of(context).colorScheme.background, floating: true, flexibleSpace: Padding( diff --git a/lib/pages/library/user_artists.dart b/lib/pages/library/user_artists.dart index 6ce715ad..c8c1dda8 100644 --- a/lib/pages/library/user_artists.dart +++ b/lib/pages/library/user_artists.dart @@ -18,7 +18,9 @@ import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/provider/authentication/authentication.dart'; import 'package:spotube/provider/spotify/spotify.dart'; +import 'package:auto_route/auto_route.dart'; +@RoutePage() class UserArtistsPage extends HookConsumerWidget { static const name = 'user_artists'; const UserArtistsPage({super.key}); @@ -70,6 +72,7 @@ class UserArtistsPage extends HookConsumerWidget { controller: controller, slivers: [ SliverAppBar( + automaticallyImplyLeading: false, backgroundColor: Theme.of(context).colorScheme.background, floating: true, flexibleSpace: SizedBox( diff --git a/lib/pages/library/user_downloads.dart b/lib/pages/library/user_downloads.dart index 871e21ab..1d8f560a 100644 --- a/lib/pages/library/user_downloads.dart +++ b/lib/pages/library/user_downloads.dart @@ -5,7 +5,9 @@ import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:spotube/modules/library/user_downloads/download_item.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/provider/download_manager_provider.dart'; +import 'package:auto_route/auto_route.dart'; +@RoutePage() class UserDownloadsPage extends HookConsumerWidget { static const name = 'user_downloads'; const UserDownloadsPage({super.key}); diff --git a/lib/pages/library/user_local_tracks/local_folder.dart b/lib/pages/library/user_local_tracks/local_folder.dart index 5282894a..028d4e69 100644 --- a/lib/pages/library/user_local_tracks/local_folder.dart +++ b/lib/pages/library/user_local_tracks/local_folder.dart @@ -29,7 +29,9 @@ import 'package:spotube/provider/local_tracks/local_tracks_provider.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; import 'package:spotube/utils/service_utils.dart'; +import 'package:auto_route/auto_route.dart'; +@RoutePage() class LocalLibraryPage extends HookConsumerWidget { static const name = "local_library_page"; diff --git a/lib/pages/library/user_local_tracks/user_local_tracks.dart b/lib/pages/library/user_local_tracks/user_local_tracks.dart index 66c011e5..67e02b0b 100644 --- a/lib/pages/library/user_local_tracks/user_local_tracks.dart +++ b/lib/pages/library/user_local_tracks/user_local_tracks.dart @@ -1,3 +1,4 @@ +import 'package:auto_route/auto_route.dart'; import 'package:file_picker/file_picker.dart'; import 'package:file_selector/file_selector.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; @@ -24,6 +25,7 @@ enum SortBy { album, } +@RoutePage() class UserLocalLibraryPage extends HookConsumerWidget { static const name = 'user_local_library'; const UserLocalLibraryPage({super.key}); diff --git a/lib/pages/library/user_playlists.dart b/lib/pages/library/user_playlists.dart index a4711e1b..6b92f8ea 100644 --- a/lib/pages/library/user_playlists.dart +++ b/lib/pages/library/user_playlists.dart @@ -7,6 +7,7 @@ import 'package:shadcn_flutter/shadcn_flutter.dart' hide Image; import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; import 'package:spotify/spotify.dart'; +import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/playbutton_view/playbutton_view.dart'; import 'package:spotube/modules/playlist/playlist_create_dialog.dart'; @@ -14,12 +15,12 @@ import 'package:spotube/components/inter_scrollbar/inter_scrollbar.dart'; import 'package:spotube/components/fallbacks/anonymous_fallback.dart'; import 'package:spotube/modules/playlist/playlist_card.dart'; import 'package:spotube/extensions/context.dart'; -import 'package:spotube/pages/library/playlist_generate/playlist_generate.dart'; import 'package:spotube/provider/authentication/authentication.dart'; import 'package:spotube/provider/spotify/spotify.dart'; import 'package:spotube/utils/platform.dart'; -import 'package:spotube/utils/service_utils.dart'; +import 'package:auto_route/auto_route.dart'; +@RoutePage() class UserPlaylistsPage extends HookConsumerWidget { static const name = 'user_playlists'; const UserPlaylistsPage({super.key}); @@ -90,6 +91,7 @@ class UserPlaylistsPage extends HookConsumerWidget { controller: controller, slivers: [ SliverAppBar( + automaticallyImplyLeading: false, floating: true, backgroundColor: context.theme.colorScheme.background, flexibleSpace: Container( @@ -113,10 +115,7 @@ class UserPlaylistsPage extends HookConsumerWidget { leading: const Icon(SpotubeIcons.magic), child: Text(context.l10n.generate), onPressed: () { - ServiceUtils.pushNamed( - context, - PlaylistGeneratorPage.name, - ); + context.navigateTo(const PlaylistGeneratorRoute()); }, ), const Gap(10), diff --git a/lib/pages/lyrics/lyrics.dart b/lib/pages/lyrics/lyrics.dart index 85798eda..ab8782a8 100644 --- a/lib/pages/lyrics/lyrics.dart +++ b/lib/pages/lyrics/lyrics.dart @@ -14,7 +14,9 @@ import 'package:spotube/pages/lyrics/synced_lyrics.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/utils/platform.dart'; import 'package:spotube/provider/spotify/spotify.dart'; +import 'package:auto_route/auto_route.dart'; +@RoutePage() class LyricsPage extends HookConsumerWidget { static const name = "lyrics"; diff --git a/lib/pages/lyrics/mini_lyrics.dart b/lib/pages/lyrics/mini_lyrics.dart index 9fd54ad6..bb879888 100644 --- a/lib/pages/lyrics/mini_lyrics.dart +++ b/lib/pages/lyrics/mini_lyrics.dart @@ -1,9 +1,10 @@ import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:go_router/go_router.dart'; + import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:palette_generator/palette_generator.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; +import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/modules/player/player_controls.dart'; import 'package:spotube/modules/player/player_queue.dart'; @@ -14,7 +15,9 @@ import 'package:spotube/pages/lyrics/synced_lyrics.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/utils/platform.dart'; import 'package:window_manager/window_manager.dart'; +import 'package:auto_route/auto_route.dart'; +@RoutePage() class MiniLyricsPage extends HookConsumerWidget { static const name = "mini_lyrics"; @@ -265,7 +268,7 @@ class MiniLyricsPage extends HookConsumerWidget { const Duration(milliseconds: 200)); } finally { if (context.mounted) { - GoRouter.of(context).go('/lyrics'); + context.navigateTo(LyricsRoute()); } } }, diff --git a/lib/pages/mobile_login/hooks/login_callback.dart b/lib/pages/mobile_login/hooks/login_callback.dart index 9979f4a5..986b7f4a 100644 --- a/lib/pages/mobile_login/hooks/login_callback.dart +++ b/lib/pages/mobile_login/hooks/login_callback.dart @@ -1,14 +1,15 @@ import 'dart:io'; +import 'package:auto_route/auto_route.dart'; import 'package:desktop_webview_window/desktop_webview_window.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart' hide join; import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:go_router/go_router.dart'; + import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; -import 'package:spotube/pages/mobile_login/mobile_login.dart'; +import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/pages/mobile_login/no_webview_runtime_dialog.dart'; import 'package:spotube/provider/authentication/authentication.dart'; import 'package:spotube/utils/platform.dart'; @@ -20,7 +21,7 @@ Future Function() useLoginCallback(WidgetRef ref) { return useCallback(() async { if (kIsMobile || kIsMacOS) { - context.pushNamed(WebViewLogin.name); + context.navigateTo(const WebViewLoginRoute()); return; } @@ -57,7 +58,7 @@ Future Function() useLoginCallback(WidgetRef ref) { webview.close(); if (context.mounted) { - context.go("/"); + context.navigateTo(const HomeRoute()); } }); } @@ -76,5 +77,5 @@ Future Function() useLoginCallback(WidgetRef ref) { }); } } - }, [authNotifier, theme, context.go, context.pushNamed]); + }, [authNotifier, theme, context.navigateTo]); } diff --git a/lib/pages/mobile_login/mobile_login.dart b/lib/pages/mobile_login/mobile_login.dart index e2191586..eb50316f 100644 --- a/lib/pages/mobile_login/mobile_login.dart +++ b/lib/pages/mobile_login/mobile_login.dart @@ -1,16 +1,19 @@ import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; -import 'package:go_router/go_router.dart'; + import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/components/button/back_button.dart'; import 'package:spotube/components/titlebar/titlebar.dart'; import 'package:spotube/provider/authentication/authentication.dart'; import 'package:spotube/utils/platform.dart'; +import 'package:auto_route/auto_route.dart'; -class WebViewLogin extends HookConsumerWidget { +@RoutePage() +class WebViewLoginPage extends HookConsumerWidget { static const name = "login"; - const WebViewLogin({super.key}); + const WebViewLoginPage({super.key}); @override Widget build(BuildContext context, ref) { @@ -24,50 +27,53 @@ class WebViewLogin extends HookConsumerWidget { ); } - return Scaffold( - headers: const [ - TitleBar( - leading: [BackButton(color: Colors.white)], - backgroundColor: Colors.transparent, - ), - ], - floatingHeader: true, - child: InAppWebView( - initialSettings: InAppWebViewSettings( - userAgent: - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 safari/537.36", - ), - initialUrlRequest: URLRequest( - url: WebUri("https://accounts.spotify.com/"), - ), - onPermissionRequest: (controller, permissionRequest) async { - return PermissionResponse( - resources: permissionRequest.resources, - action: PermissionResponseAction.GRANT, - ); - }, - onLoadStop: (controller, action) async { - if (action == null) return; - String url = action.toString(); - if (url.endsWith("/")) { - url = url.substring(0, url.length - 1); - } - - final exp = RegExp(r"https:\/\/accounts.spotify.com\/.+\/status"); - - if (exp.hasMatch(url)) { - final cookies = - await CookieManager.instance().getCookies(url: action); - final cookieHeader = - "sp_dc=${cookies.firstWhere((element) => element.name == "sp_dc").value}"; - - await authenticationNotifier.login(cookieHeader); - if (context.mounted) { - // ignore: use_build_context_synchronously - GoRouter.of(context).go("/"); + return SafeArea( + bottom: false, + child: Scaffold( + headers: const [ + TitleBar( + leading: [BackButton(color: Colors.white)], + backgroundColor: Colors.transparent, + ), + ], + floatingHeader: true, + child: InAppWebView( + initialSettings: InAppWebViewSettings( + userAgent: + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 safari/537.36", + ), + initialUrlRequest: URLRequest( + url: WebUri("https://accounts.spotify.com/"), + ), + onPermissionRequest: (controller, permissionRequest) async { + return PermissionResponse( + resources: permissionRequest.resources, + action: PermissionResponseAction.GRANT, + ); + }, + onLoadStop: (controller, action) async { + if (action == null) return; + String url = action.toString(); + if (url.endsWith("/")) { + url = url.substring(0, url.length - 1); } - } - }, + + final exp = RegExp(r"https:\/\/accounts.spotify.com\/.+\/status"); + + if (exp.hasMatch(url)) { + final cookies = + await CookieManager.instance().getCookies(url: action); + final cookieHeader = + "sp_dc=${cookies.firstWhere((element) => element.name == "sp_dc").value}"; + + await authenticationNotifier.login(cookieHeader); + if (context.mounted) { + // ignore: use_build_context_synchronously + context.navigateTo(const HomeRoute()); + } + } + }, + ), ), ); } diff --git a/lib/pages/playlist/liked_playlist.dart b/lib/pages/playlist/liked_playlist.dart index 95107a8c..54df2c88 100644 --- a/lib/pages/playlist/liked_playlist.dart +++ b/lib/pages/playlist/liked_playlist.dart @@ -5,7 +5,9 @@ import 'package:spotube/components/track_presentation/presentation_props.dart'; import 'package:spotube/components/track_presentation/track_presentation.dart'; import 'package:spotube/pages/playlist/playlist.dart'; import 'package:spotube/provider/spotify/spotify.dart'; +import 'package:auto_route/auto_route.dart'; +@RoutePage() class LikedPlaylistPage extends HookConsumerWidget { static const name = PlaylistPage.name; diff --git a/lib/pages/playlist/playlist.dart b/lib/pages/playlist/playlist.dart index b610b1d4..e3992db8 100644 --- a/lib/pages/playlist/playlist.dart +++ b/lib/pages/playlist/playlist.dart @@ -9,13 +9,17 @@ import 'package:spotube/components/track_presentation/use_is_user_playlist.dart' import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/image.dart'; import 'package:spotube/provider/spotify/spotify.dart'; +import 'package:auto_route/auto_route.dart'; +@RoutePage() class PlaylistPage extends HookConsumerWidget { static const name = "playlist"; final PlaylistSimple _playlist; + final String id; const PlaylistPage({ super.key, + @PathParam("id") required this.id, required PlaylistSimple playlist, }) : _playlist = playlist; diff --git a/lib/pages/profile/profile.dart b/lib/pages/profile/profile.dart index 004fbd1a..b6c4a2cd 100644 --- a/lib/pages/profile/profile.dart +++ b/lib/pages/profile/profile.dart @@ -12,7 +12,9 @@ import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/image.dart'; import 'package:spotube/provider/spotify/spotify.dart'; import 'package:url_launcher/url_launcher_string.dart'; +import 'package:auto_route/auto_route.dart'; +@RoutePage() class ProfilePage extends HookConsumerWidget { static const name = "profile"; @@ -42,7 +44,6 @@ class ProfilePage extends HookConsumerWidget { headers: [ TitleBar( title: Text(context.l10n.profile), - automaticallyImplyLeading: true, ) ], child: Skeletonizer( diff --git a/lib/pages/root/root_app.dart b/lib/pages/root/root_app.dart index 2a1bf088..43e8fdcb 100644 --- a/lib/pages/root/root_app.dart +++ b/lib/pages/root/root_app.dart @@ -1,27 +1,19 @@ +import 'package:auto_route/auto_route.dart'; import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; -import 'package:spotube/collections/side_bar_tiles.dart'; -import 'package:spotube/components/framework/app_pop_scope.dart'; import 'package:spotube/modules/root/bottom_player.dart'; -import 'package:spotube/modules/root/sidebar.dart'; +import 'package:spotube/modules/root/sidebar/sidebar.dart'; import 'package:spotube/modules/root/spotube_navigation_bar.dart'; -import 'package:spotube/extensions/context.dart'; import 'package:spotube/hooks/configurators/use_endless_playback.dart'; import 'package:spotube/modules/root/use_downloader_dialogs.dart'; import 'package:spotube/modules/root/use_global_subscriptions.dart'; -import 'package:spotube/pages/home/home.dart'; import 'package:spotube/provider/glance/glance.dart'; -import 'package:spotube/utils/platform.dart'; -class RootApp extends HookConsumerWidget { - final Widget child; - const RootApp({ - required this.child, - super.key, - }); +@RoutePage() +class RootAppPage extends HookConsumerWidget { + const RootAppPage({super.key}); @override Widget build(BuildContext context, ref) { @@ -45,42 +37,19 @@ class RootApp extends HookConsumerWidget { return null; }, [backgroundColor, brightness]); - final navTileNames = useMemoized(() { - return getSidebarTileList(context.l10n).map((s) => s.name).toList(); - }, []); - final scaffold = MediaQuery.removeViewInsets( context: context, removeBottom: true, - child: Scaffold( - footers: const [ + child: const Scaffold( + footers: [ BottomPlayer(), SpotubeNavigationBar(), ], floatingFooter: true, - child: Sidebar(child: child), + child: Sidebar(child: AutoRouter()), ), ); - if (!kIsAndroid) { - return scaffold; - } - - final topRoute = GoRouterState.of(context).topRoute; - final canPop = topRoute != null && !navTileNames.contains(topRoute.name); - - return AppPopScope( - canPop: canPop, - onPopInvoked: (didPop) { - if (didPop) return; - - if (topRoute?.name == HomePage.name) { - SystemNavigator.pop(); - } else { - context.goNamed(HomePage.name); - } - }, - child: scaffold, - ); + return scaffold; } } diff --git a/lib/pages/search/search.dart b/lib/pages/search/search.dart index 701c3c5c..1555c062 100644 --- a/lib/pages/search/search.dart +++ b/lib/pages/search/search.dart @@ -6,6 +6,7 @@ import 'package:spotify/spotify.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:fuzzywuzzy/fuzzywuzzy.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/inter_scrollbar/inter_scrollbar.dart'; @@ -20,7 +21,9 @@ import 'package:spotube/pages/search/sections/tracks.dart'; import 'package:spotube/provider/authentication/authentication.dart'; import 'package:spotube/provider/spotify/spotify.dart'; import 'package:spotube/services/kv_store/kv_store.dart'; +import 'package:auto_route/auto_route.dart'; +@RoutePage() class SearchPage extends HookConsumerWidget { static const name = "search"; @@ -66,165 +69,174 @@ class SearchPage extends HookConsumerWidget { ); } - return SafeArea( - bottom: false, - child: Scaffold( - headers: [ - if (kTitlebarVisible) - const TitleBar(automaticallyImplyLeading: true, height: 30) - ], - child: auth.asData?.value == null - ? const AnonymousFallback() - : Column( - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Expanded( - child: Padding( - padding: const EdgeInsets.all(20), - child: ListenableBuilder( - listenable: controller, - builder: (context, _) { - final suggestions = controller.text.isEmpty - ? KVStoreService.recentSearches - : KVStoreService.recentSearches - .where( - (s) => - weightedRatio( - s.toLowerCase(), - controller.text.toLowerCase(), - ) > - 50, - ) - .toList(); + return PopScope( + canPop: false, + onPopInvokedWithResult: (didPop, result) { + context.navigateTo(const HomeRoute()); + }, + child: SafeArea( + bottom: false, + child: Scaffold( + headers: [ + if (kTitlebarVisible) + const TitleBar(automaticallyImplyLeading: false, height: 30) + ], + child: auth.asData?.value == null + ? const AnonymousFallback() + : Column( + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.all(20), + child: ListenableBuilder( + listenable: controller, + builder: (context, _) { + final suggestions = controller.text.isEmpty + ? KVStoreService.recentSearches + : KVStoreService.recentSearches + .where( + (s) => + weightedRatio( + s.toLowerCase(), + controller.text.toLowerCase(), + ) > + 50, + ) + .toList(); - return KeyboardListener( - focusNode: focusNode, - autofocus: true, - onKeyEvent: (value) { - final isEnter = value.logicalKey == - LogicalKeyboardKey.enter; - - if (isEnter) { - onSubmitted(controller.text); - focusNode.unfocus(); - } - }, - child: AutoComplete( + return KeyboardListener( + focusNode: focusNode, autofocus: true, - controller: controller, - suggestions: suggestions, - leading: const Icon(SpotubeIcons.search), - textInputAction: TextInputAction.search, - placeholder: Text(context.l10n.search), - trailing: AnimatedCrossFade( - duration: - const Duration(milliseconds: 300), - crossFadeState: controller.text.isNotEmpty - ? CrossFadeState.showFirst - : CrossFadeState.showSecond, - firstChild: IconButton.ghost( - size: ButtonSize.small, - icon: const Icon(SpotubeIcons.close), - onPressed: () { - controller.clear(); - }, - ), - secondChild: - const SizedBox.square(dimension: 28), - ), - onAcceptSuggestion: (index) { - controller.text = - KVStoreService.recentSearches[index]; - ref - .read(searchTermStateProvider - .notifier) - .state = - KVStoreService.recentSearches[index]; + onKeyEvent: (value) { + final isEnter = value.logicalKey == + LogicalKeyboardKey.enter; + + if (isEnter) { + onSubmitted(controller.text); + focusNode.unfocus(); + } }, - onChanged: (value) {}, - onSubmitted: onSubmitted, - ), - ); - }), - ), - ), - ], - ), - Expanded( - child: AnimatedSwitcher( - duration: const Duration(milliseconds: 300), - child: switch ((searchTerm.isEmpty, isFetching)) { - (true, false) => Column( - children: [ - SizedBox( - height: mediaQuery.height * 0.2, - ), - Undraw( - illustration: UndrawIllustration.explore, - color: theme.colorScheme.primary, - height: 200 * theme.scaling, - ), - const SizedBox(height: 20), - Text(context.l10n.search_to_get_results).large(), - ], + child: AutoComplete( + autofocus: true, + controller: controller, + suggestions: suggestions, + leading: const Icon(SpotubeIcons.search), + textInputAction: TextInputAction.search, + placeholder: Text(context.l10n.search), + trailing: AnimatedCrossFade( + duration: + const Duration(milliseconds: 300), + crossFadeState: + controller.text.isNotEmpty + ? CrossFadeState.showFirst + : CrossFadeState.showSecond, + firstChild: IconButton.ghost( + size: ButtonSize.small, + icon: const Icon(SpotubeIcons.close), + onPressed: () { + controller.clear(); + }, + ), + secondChild: const SizedBox.square( + dimension: 28), + ), + onAcceptSuggestion: (index) { + controller.text = KVStoreService + .recentSearches[index]; + ref + .read(searchTermStateProvider + .notifier) + .state = + KVStoreService + .recentSearches[index]; + }, + onChanged: (value) {}, + onSubmitted: onSubmitted, + ), + ); + }), ), - (false, true) => Container( - constraints: BoxConstraints( - maxWidth: mediaQuery.lgAndUp - ? mediaQuery.width * 0.5 - : mediaQuery.width, - ), - padding: const EdgeInsets.symmetric( - horizontal: 20, - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, + ), + ], + ), + Expanded( + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 300), + child: switch ((searchTerm.isEmpty, isFetching)) { + (true, false) => Column( children: [ - Text( - context.l10n.crunching_results, - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.w900, - color: theme.colorScheme.foreground - .withOpacity(0.7), - ), + SizedBox( + height: mediaQuery.height * 0.2, + ), + Undraw( + illustration: UndrawIllustration.explore, + color: theme.colorScheme.primary, + height: 200 * theme.scaling, ), const SizedBox(height: 20), - const LinearProgressIndicator(), + Text(context.l10n.search_to_get_results) + .large(), ], ), - ), - _ => InterScrollbar( - controller: scrollController, - child: SingleChildScrollView( + (false, true) => Container( + constraints: BoxConstraints( + maxWidth: mediaQuery.lgAndUp + ? mediaQuery.width * 0.5 + : mediaQuery.width, + ), + padding: const EdgeInsets.symmetric( + horizontal: 20, + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + context.l10n.crunching_results, + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w900, + color: theme.colorScheme.foreground + .withOpacity(0.7), + ), + ), + const SizedBox(height: 20), + const LinearProgressIndicator(), + ], + ), + ), + _ => InterScrollbar( controller: scrollController, - child: const Padding( - padding: EdgeInsets.symmetric(vertical: 8), - child: SafeArea( - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - SearchTracksSection(), - SearchPlaylistsSection(), - Gap(20), - SearchArtistsSection(), - Gap(20), - SearchAlbumsSection(), - ], + child: SingleChildScrollView( + controller: scrollController, + child: const Padding( + padding: EdgeInsets.symmetric(vertical: 8), + child: SafeArea( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + SearchTracksSection(), + SearchPlaylistsSection(), + Gap(20), + SearchArtistsSection(), + Gap(20), + SearchAlbumsSection(), + ], + ), ), ), ), ), - ), - }, + }, + ), ), - ), - ], - ), + ], + ), + ), ), ); } diff --git a/lib/pages/settings/about.dart b/lib/pages/settings/about.dart index 79c6692b..1837bbec 100644 --- a/lib/pages/settings/about.dart +++ b/lib/pages/settings/about.dart @@ -11,15 +11,17 @@ import 'package:spotube/hooks/controllers/use_package_info.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:url_launcher/url_launcher.dart'; +import 'package:auto_route/auto_route.dart'; final _licenseProvider = FutureProvider((ref) async { return await rootBundle.loadString("LICENSE"); }); -class AboutSpotube extends HookConsumerWidget { +@RoutePage() +class AboutSpotubePage extends HookConsumerWidget { static const name = "about"; - const AboutSpotube({super.key}); + const AboutSpotubePage({super.key}); @override Widget build(BuildContext context, ref) { @@ -29,163 +31,166 @@ class AboutSpotube extends HookConsumerWidget { const colon = TableCell(child: Text(":")); - return Scaffold( - headers: [ - TitleBar( - leading: const [BackButton()], - title: Text(context.l10n.about_spotube), - ) - ], - child: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: Column( - children: [ - Assets.spotubeLogoPng.image( - height: 200, - width: 200, - ), - Center( - child: Column( - children: [ - Text(context.l10n.spotube_description).semiBold().large(), - const SizedBox(height: 20), - Table( - columnWidths: const { - 0: FixedTableSize(95), - 1: FixedTableSize(10), - 2: IntrinsicTableSize(), - }, - defaultRowHeight: const FixedTableSize(40), - rows: [ - TableRow( - cells: [ - TableCell(child: Text(context.l10n.founder)), - colon, - TableCell( - child: Hyperlink( - context.l10n.kingkor_roy_tirtho, - "https://github.com/KRTirtho", + return SafeArea( + bottom: false, + child: Scaffold( + headers: [ + TitleBar( + leading: const [BackButton()], + title: Text(context.l10n.about_spotube), + ) + ], + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Column( + children: [ + Assets.spotubeLogoPng.image( + height: 200, + width: 200, + ), + Center( + child: Column( + children: [ + Text(context.l10n.spotube_description).semiBold().large(), + const SizedBox(height: 20), + Table( + columnWidths: const { + 0: FixedTableSize(95), + 1: FixedTableSize(10), + 2: IntrinsicTableSize(), + }, + defaultRowHeight: const FixedTableSize(40), + rows: [ + TableRow( + cells: [ + TableCell(child: Text(context.l10n.founder)), + colon, + TableCell( + child: Hyperlink( + context.l10n.kingkor_roy_tirtho, + "https://github.com/KRTirtho", + ), + ) + ], + ), + TableRow( + cells: [ + TableCell(child: Text(context.l10n.version)), + colon, + TableCell(child: Text("v${packageInfo.version}")) + ], + ), + TableRow( + cells: [ + TableCell(child: Text(context.l10n.channel)), + colon, + TableCell(child: Text(Env.releaseChannel.name)) + ], + ), + TableRow( + cells: [ + TableCell(child: Text(context.l10n.build_number)), + colon, + TableCell( + child: Text(packageInfo.buildNumber + .replaceAll(".", " ")), + ) + ], + ), + TableRow( + cells: [ + TableCell(child: Text(context.l10n.repository)), + colon, + const TableCell( + child: Hyperlink( + "github.com/KRTirtho/spotube", + "https://github.com/KRTirtho/spotube", + ), ), - ) - ], - ), - TableRow( - cells: [ - TableCell(child: Text(context.l10n.version)), - colon, - TableCell(child: Text("v${packageInfo.version}")) - ], - ), - TableRow( - cells: [ - TableCell(child: Text(context.l10n.channel)), - colon, - TableCell(child: Text(Env.releaseChannel.name)) - ], - ), - TableRow( - cells: [ - TableCell(child: Text(context.l10n.build_number)), - colon, - TableCell( - child: Text( - packageInfo.buildNumber.replaceAll(".", " ")), - ) - ], - ), - TableRow( - cells: [ - TableCell(child: Text(context.l10n.repository)), - colon, - const TableCell( - child: Hyperlink( - "github.com/KRTirtho/spotube", - "https://github.com/KRTirtho/spotube", + ], + ), + TableRow( + cells: [ + TableCell(child: Text(context.l10n.license)), + colon, + const TableCell( + child: Hyperlink( + "BSD-4-Clause", + "https://raw.githubusercontent.com/KRTirtho/spotube/master/LICENSE", + ), ), - ), - ], - ), - TableRow( - cells: [ - TableCell(child: Text(context.l10n.license)), - colon, - const TableCell( - child: Hyperlink( - "BSD-4-Clause", - "https://raw.githubusercontent.com/KRTirtho/spotube/master/LICENSE", + ], + ), + TableRow( + cells: [ + TableCell(child: Text(context.l10n.bug_issues)), + colon, + const TableCell( + child: Hyperlink( + "github.com/KRTirtho/spotube/issues", + "https://github.com/KRTirtho/spotube/issues", + ), ), - ), - ], - ), - TableRow( - cells: [ - TableCell(child: Text(context.l10n.bug_issues)), - colon, - const TableCell( - child: Hyperlink( - "github.com/KRTirtho/spotube/issues", - "https://github.com/KRTirtho/spotube/issues", - ), - ), - ], - ), - ], + ], + ), + ], + ), + ], + ), + ), + const SizedBox(height: 20), + MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: () => launchUrl( + Uri.parse("https://discord.gg/uJ94vxB6vg"), + mode: LaunchMode.externalApplication, + ), + child: const UniversalImage( + path: + "https://discord.com/api/guilds/1012234096237350943/widget.png?style=banner2", ), - ], - ), - ), - const SizedBox(height: 20), - MouseRegion( - cursor: SystemMouseCursors.click, - child: GestureDetector( - onTap: () => launchUrl( - Uri.parse("https://discord.gg/uJ94vxB6vg"), - mode: LaunchMode.externalApplication, - ), - child: const UniversalImage( - path: - "https://discord.com/api/guilds/1012234096237350943/widget.png?style=banner2", ), ), - ), - const SizedBox(height: 20), - Text( - context.l10n.made_with, - textAlign: TextAlign.center, - style: theme.typography.small, - ), - Text( - context.l10n.copyright(DateTime.now().year), - textAlign: TextAlign.center, - style: theme.typography.small, - ), - const SizedBox(height: 20), - ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 750), - child: SafeArea( - child: license.when( - data: (data) { - return Text( - data, - style: theme.typography.small, - ); - }, - loading: () { - return const Center( - child: CircularProgressIndicator(), - ); - }, - error: (e, s) { - return Text( - e.toString(), - style: theme.typography.small, - ); - }, + const SizedBox(height: 20), + Text( + context.l10n.made_with, + textAlign: TextAlign.center, + style: theme.typography.small, + ), + Text( + context.l10n.copyright(DateTime.now().year), + textAlign: TextAlign.center, + style: theme.typography.small, + ), + const SizedBox(height: 20), + ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 750), + child: SafeArea( + child: license.when( + data: (data) { + return Text( + data, + style: theme.typography.small, + ); + }, + loading: () { + return const Center( + child: CircularProgressIndicator(), + ); + }, + error: (e, s) { + return Text( + e.toString(), + style: theme.typography.small, + ); + }, + ), ), ), - ), - ], + ], + ), ), ), ), diff --git a/lib/pages/settings/blacklist.dart b/lib/pages/settings/blacklist.dart index b525b1b5..8ac2c1b9 100644 --- a/lib/pages/settings/blacklist.dart +++ b/lib/pages/settings/blacklist.dart @@ -11,7 +11,9 @@ import 'package:spotube/components/titlebar/titlebar.dart'; import 'package:spotube/components/ui/button_tile.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/provider/blacklist_provider.dart'; +import 'package:auto_route/auto_route.dart'; +@RoutePage() class BlackListPage extends HookConsumerWidget { static const name = "blacklist"; @@ -45,50 +47,52 @@ class BlackListPage extends HookConsumerWidget { [blacklist, searchText.value], ); - return Scaffold( - headers: [ - TitleBar( - title: Text(context.l10n.blacklist), - leading: const [BackButton()], - ) - ], - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: TextField( - onChanged: (value) => searchText.value = value, - placeholder: Text(context.l10n.search), - leading: const Icon(SpotubeIcons.search), - ), - ), - InterScrollbar( - controller: controller, - child: ListView.builder( - controller: controller, - shrinkWrap: true, - itemCount: filteredBlacklist.length, - itemBuilder: (context, index) { - final item = filteredBlacklist.elementAt(index); - return ButtonTile( - style: ButtonVariance.ghost, - leading: Text("${index + 1}."), - title: Text("${item.name} (${item.elementType.name})"), - subtitle: Text(item.elementId), - trailing: IconButton.ghost( - icon: Icon(SpotubeIcons.trash, color: Colors.red[400]), - onPressed: () { - ref - .read(blacklistProvider.notifier) - .remove(filteredBlacklist.elementAt(index).elementId); - }, - ), - ); - }, - ), - ), + return SafeArea( + bottom: false, + child: Scaffold( + headers: [ + TitleBar( + title: Text(context.l10n.blacklist), + leading: const [BackButton()], + ) ], + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: TextField( + onChanged: (value) => searchText.value = value, + placeholder: Text(context.l10n.search), + leading: const Icon(SpotubeIcons.search), + ), + ), + InterScrollbar( + controller: controller, + child: ListView.builder( + controller: controller, + shrinkWrap: true, + itemCount: filteredBlacklist.length, + itemBuilder: (context, index) { + final item = filteredBlacklist.elementAt(index); + return ButtonTile( + style: ButtonVariance.ghost, + leading: Text("${index + 1}."), + title: Text("${item.name} (${item.elementType.name})"), + subtitle: Text(item.elementId), + trailing: IconButton.ghost( + icon: Icon(SpotubeIcons.trash, color: Colors.red[400]), + onPressed: () { + ref.read(blacklistProvider.notifier).remove( + filteredBlacklist.elementAt(index).elementId); + }, + ), + ); + }, + ), + ), + ], + ), ), ); } diff --git a/lib/pages/settings/logs.dart b/lib/pages/settings/logs.dart index 3a4f7715..4985b57a 100644 --- a/lib/pages/settings/logs.dart +++ b/lib/pages/settings/logs.dart @@ -11,7 +11,9 @@ import 'package:spotube/components/titlebar/titlebar.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/provider/logs/logs_provider.dart'; import 'package:spotube/services/logger/logger.dart'; +import 'package:auto_route/auto_route.dart'; +@RoutePage() class LogsPage extends HookConsumerWidget { static const name = "logs"; diff --git a/lib/pages/settings/sections/about.dart b/lib/pages/settings/sections/about.dart index 7f5d3977..82c98e90 100644 --- a/lib/pages/settings/sections/about.dart +++ b/lib/pages/settings/sections/about.dart @@ -1,9 +1,11 @@ +import 'package:auto_route/auto_route.dart'; import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter/material.dart' show ListTile; -import 'package:go_router/go_router.dart'; + import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart' hide ButtonStyle; import 'package:spotube/collections/env.dart'; +import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/modules/settings/section_card_with_heading.dart'; import 'package:spotube/components/adaptive/adaptive_list_tile.dart'; @@ -88,7 +90,7 @@ class SettingsAboutSection extends HookConsumerWidget { title: Text(context.l10n.about_spotube), trailing: const Icon(SpotubeIcons.angleRight), onTap: () { - GoRouter.of(context).push("/settings/about"); + context.navigateTo(const AboutSpotubeRoute()); }, ) ], diff --git a/lib/pages/settings/sections/accounts.dart b/lib/pages/settings/sections/accounts.dart index 6132776c..5e40b9ec 100644 --- a/lib/pages/settings/sections/accounts.dart +++ b/lib/pages/settings/sections/accounts.dart @@ -1,20 +1,20 @@ +import 'package:auto_route/auto_route.dart'; import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter/material.dart' show ListTile; -import 'package:go_router/go_router.dart'; + import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; +import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/modules/settings/section_card_with_heading.dart'; import 'package:spotube/components/image/universal_image.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/image.dart'; -import 'package:spotube/pages/profile/profile.dart'; import 'package:spotube/pages/mobile_login/hooks/login_callback.dart'; import 'package:spotube/provider/authentication/authentication.dart'; import 'package:spotube/provider/scrobbler/scrobbler.dart'; import 'package:spotube/provider/spotify/spotify.dart'; -import 'package:spotube/utils/service_utils.dart'; class SettingsAccountSection extends HookConsumerWidget { const SettingsAccountSection({super.key}); @@ -22,7 +22,6 @@ class SettingsAccountSection extends HookConsumerWidget { @override Widget build(context, ref) { final theme = Theme.of(context); - final router = GoRouter.of(context); final auth = ref.watch(authenticationProvider); final scrobbler = ref.watch(scrobblerProvider); @@ -50,7 +49,7 @@ class SettingsAccountSection extends HookConsumerWidget { ), ), onTap: () { - ServiceUtils.pushNamed(context, ProfilePage.name); + context.navigateTo(ProfileRoute()); }, ), if (auth.asData?.value == null) @@ -99,7 +98,7 @@ class SettingsAccountSection extends HookConsumerWidget { trailing: Button.destructive( onPressed: () async { ref.read(authenticationProvider.notifier).logout(); - GoRouter.of(context).pop(); + context.maybePop(); }, child: Text(context.l10n.logout), ), @@ -113,7 +112,7 @@ class SettingsAccountSection extends HookConsumerWidget { trailing: Button.secondary( leading: const Icon(SpotubeIcons.lastFm), onPressed: () { - router.push("/lastfm-login"); + context.navigateTo(const LastFMLoginRoute()); }, child: Text(context.l10n.connect), ), diff --git a/lib/pages/settings/sections/developers.dart b/lib/pages/settings/sections/developers.dart index 4d8b8ba1..0862e023 100644 --- a/lib/pages/settings/sections/developers.dart +++ b/lib/pages/settings/sections/developers.dart @@ -1,7 +1,9 @@ +import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart' show ListTile; import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:go_router/go_router.dart'; + import 'package:shadcn_flutter/shadcn_flutter.dart'; +import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/modules/settings/section_card_with_heading.dart'; import 'package:spotube/extensions/context.dart'; @@ -19,7 +21,7 @@ class SettingsDevelopersSection extends HookWidget { title: Text(context.l10n.logs), trailing: const Icon(SpotubeIcons.angleRight), onTap: () { - GoRouter.of(context).push("/settings/logs"); + context.navigateTo(const LogsRoute()); }, ) ], diff --git a/lib/pages/settings/sections/playback.dart b/lib/pages/settings/sections/playback.dart index be3fc15b..537156d0 100644 --- a/lib/pages/settings/sections/playback.dart +++ b/lib/pages/settings/sections/playback.dart @@ -1,12 +1,13 @@ +import 'package:auto_route/auto_route.dart'; import 'package:collection/collection.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart' show ListTile; -import 'package:go_router/go_router.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:piped_client/piped_client.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; +import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/models/database/database.dart'; import 'package:spotube/modules/settings/section_card_with_heading.dart'; @@ -267,7 +268,7 @@ class SettingsPlaybackSection extends HookConsumerWidget { title: Text(context.l10n.blacklist), subtitle: Text(context.l10n.blacklist_description), onTap: () { - GoRouter.of(context).push("/settings/blacklist"); + context.navigateTo(const BlackListRoute()); }, trailing: const Icon(SpotubeIcons.angleRight), ), diff --git a/lib/pages/settings/settings.dart b/lib/pages/settings/settings.dart index 54c377eb..0948bdeb 100644 --- a/lib/pages/settings/settings.dart +++ b/lib/pages/settings/settings.dart @@ -15,7 +15,9 @@ import 'package:spotube/pages/settings/sections/language_region.dart'; import 'package:spotube/pages/settings/sections/playback.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; import 'package:spotube/utils/platform.dart'; +import 'package:auto_route/auto_route.dart'; +@RoutePage() class SettingsPage extends HookConsumerWidget { static const name = "settings"; @@ -32,7 +34,6 @@ class SettingsPage extends HookConsumerWidget { headers: [ TitleBar( title: Text(context.l10n.settings), - automaticallyImplyLeading: true, ) ], child: Scrollbar( diff --git a/lib/pages/stats/albums/albums.dart b/lib/pages/stats/albums/albums.dart index eee6694b..834837af 100644 --- a/lib/pages/stats/albums/albums.dart +++ b/lib/pages/stats/albums/albums.dart @@ -10,7 +10,9 @@ import 'package:spotube/provider/history/top.dart'; import 'package:spotube/provider/history/top/albums.dart'; import 'package:spotube/provider/spotify/spotify.dart'; import 'package:very_good_infinite_list/very_good_infinite_list.dart'; +import 'package:auto_route/auto_route.dart'; +@RoutePage() class StatsAlbumsPage extends HookConsumerWidget { static const name = "stats_albums"; const StatsAlbumsPage({super.key}); @@ -24,31 +26,33 @@ class StatsAlbumsPage extends HookConsumerWidget { final albumsData = topAlbums.asData?.value.items ?? []; - return Scaffold( - headers: [ - TitleBar( - automaticallyImplyLeading: true, - title: Text(context.l10n.albums), - ) - ], - child: Skeletonizer( - enabled: topAlbums.isLoading && !topAlbums.isLoadingNextPage, - child: InfiniteList( - onFetchData: () async { - await topAlbumsNotifier.fetchMore(); - }, - hasError: topAlbums.hasError, - isLoading: topAlbums.isLoading && !topAlbums.isLoadingNextPage, - hasReachedMax: topAlbums.asData?.value.hasMore ?? true, - itemCount: albumsData.length, - itemBuilder: (context, index) { - final album = albumsData[index]; - return StatsAlbumItem( - album: album.album, - info: Text(context.l10n - .count_plays(compactNumberFormatter.format(album.count))), - ); - }, + return SafeArea( + bottom: false, + child: Scaffold( + headers: [ + TitleBar( + title: Text(context.l10n.albums), + ) + ], + child: Skeletonizer( + enabled: topAlbums.isLoading && !topAlbums.isLoadingNextPage, + child: InfiniteList( + onFetchData: () async { + await topAlbumsNotifier.fetchMore(); + }, + hasError: topAlbums.hasError, + isLoading: topAlbums.isLoading && !topAlbums.isLoadingNextPage, + hasReachedMax: topAlbums.asData?.value.hasMore ?? true, + itemCount: albumsData.length, + itemBuilder: (context, index) { + final album = albumsData[index]; + return StatsAlbumItem( + album: album.album, + info: Text(context.l10n + .count_plays(compactNumberFormatter.format(album.count))), + ); + }, + ), ), ), ); diff --git a/lib/pages/stats/artists/artists.dart b/lib/pages/stats/artists/artists.dart index 3a719725..f3d2f0dd 100644 --- a/lib/pages/stats/artists/artists.dart +++ b/lib/pages/stats/artists/artists.dart @@ -11,7 +11,9 @@ import 'package:spotube/provider/history/top.dart'; import 'package:spotube/provider/history/top/tracks.dart'; import 'package:spotube/provider/spotify/spotify.dart'; import 'package:very_good_infinite_list/very_good_infinite_list.dart'; +import 'package:auto_route/auto_route.dart'; +@RoutePage() class StatsArtistsPage extends HookConsumerWidget { static const name = "stats_artists"; const StatsArtistsPage({super.key}); @@ -27,31 +29,33 @@ class StatsArtistsPage extends HookConsumerWidget { final artistsData = useMemoized( () => topTracks.asData?.value.artists ?? [], [topTracks.asData?.value]); - return Scaffold( - headers: [ - TitleBar( - automaticallyImplyLeading: true, - title: Text(context.l10n.artists), - ) - ], - child: Skeletonizer( - enabled: topTracks.isLoading && !topTracks.isLoadingNextPage, - child: InfiniteList( - onFetchData: () async { - await topTracksNotifier.fetchMore(); - }, - hasError: topTracks.hasError, - isLoading: topTracks.isLoading && !topTracks.isLoadingNextPage, - hasReachedMax: topTracks.asData?.value.hasMore ?? true, - itemCount: artistsData.length, - itemBuilder: (context, index) { - final artist = artistsData[index]; - return StatsArtistItem( - artist: artist.artist, - info: Text(context.l10n - .count_plays(compactNumberFormatter.format(artist.count))), - ); - }, + return SafeArea( + bottom: false, + child: Scaffold( + headers: [ + TitleBar( + title: Text(context.l10n.artists), + ) + ], + child: Skeletonizer( + enabled: topTracks.isLoading && !topTracks.isLoadingNextPage, + child: InfiniteList( + onFetchData: () async { + await topTracksNotifier.fetchMore(); + }, + hasError: topTracks.hasError, + isLoading: topTracks.isLoading && !topTracks.isLoadingNextPage, + hasReachedMax: topTracks.asData?.value.hasMore ?? true, + itemCount: artistsData.length, + itemBuilder: (context, index) { + final artist = artistsData[index]; + return StatsArtistItem( + artist: artist.artist, + info: Text(context.l10n + .count_plays(compactNumberFormatter.format(artist.count))), + ); + }, + ), ), ), ); diff --git a/lib/pages/stats/fees/fees.dart b/lib/pages/stats/fees/fees.dart index 14f432d6..6df911ce 100644 --- a/lib/pages/stats/fees/fees.dart +++ b/lib/pages/stats/fees/fees.dart @@ -12,7 +12,9 @@ import 'package:spotube/provider/history/top.dart'; import 'package:spotube/provider/history/top/tracks.dart'; import 'package:spotube/provider/spotify/spotify.dart'; import 'package:very_good_infinite_list/very_good_infinite_list.dart'; +import 'package:auto_route/auto_route.dart'; +@RoutePage() class StatsStreamFeesPage extends HookConsumerWidget { static const name = "stats_stream_fees"; @@ -48,79 +50,83 @@ class StatsStreamFeesPage extends HookConsumerWidget { HistoryDuration.allTime: context.l10n.all_time, }; - return Scaffold( - headers: [ - TitleBar( - automaticallyImplyLeading: true, - title: Text(context.l10n.streaming_fees_hypothetical), - ) - ], - child: CustomScrollView( - slivers: [ - SliverCrossAxisConstrained( - maxCrossAxisExtent: 600, - alignment: -1, - child: SliverPadding( - padding: const EdgeInsets.all(16.0), - sliver: SliverToBoxAdapter( - child: Text( - context.l10n.spotify_hipotetical_calculation, - ).small().muted(), - ), - ), - ), - SliverToBoxAdapter( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - context.l10n.total_money(usdFormatter.format(total)), - ).semiBold().large(), - Select( - value: duration.value, - onChanged: (value) { - if (value == null) return; - duration.value = value; - }, - itemBuilder: (context, value) => Text(translations[value]!), - constraints: const BoxConstraints(maxWidth: 150), - popupWidthConstraint: PopoverConstraint.anchorMaxSize, - children: [ - for (final entry in translations.entries) - SelectItemButton( - value: entry.key, - child: Text(entry.value), - ), - ], - ), - ], - ), - ), - ), - SliverSafeArea( - sliver: Skeletonizer.sliver( - enabled: topTracks.isLoading && !topTracks.isLoadingNextPage, - child: SliverInfiniteList( - onFetchData: () async { - await topTracksNotifier.fetchMore(); - }, - hasError: topTracks.hasError, - isLoading: topTracks.isLoading && !topTracks.isLoadingNextPage, - hasReachedMax: topTracks.asData?.value.hasMore ?? true, - itemCount: artistsData.length, - itemBuilder: (context, index) { - final artist = artistsData[index]; - return StatsArtistItem( - artist: artist.artist, - info: Text(usdFormatter.format(artist.count * 0.005)), - ); - }, - ), - ), - ), + return SafeArea( + bottom: false, + child: Scaffold( + headers: [ + TitleBar( + title: Text(context.l10n.streaming_fees_hypothetical), + ) ], + child: CustomScrollView( + slivers: [ + SliverCrossAxisConstrained( + maxCrossAxisExtent: 600, + alignment: -1, + child: SliverPadding( + padding: const EdgeInsets.all(16.0), + sliver: SliverToBoxAdapter( + child: Text( + context.l10n.spotify_hipotetical_calculation, + ).small().muted(), + ), + ), + ), + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + context.l10n.total_money(usdFormatter.format(total)), + ).semiBold().large(), + Select( + value: duration.value, + onChanged: (value) { + if (value == null) return; + duration.value = value; + }, + itemBuilder: (context, value) => + Text(translations[value]!), + constraints: const BoxConstraints(maxWidth: 150), + popupWidthConstraint: PopoverConstraint.anchorMaxSize, + children: [ + for (final entry in translations.entries) + SelectItemButton( + value: entry.key, + child: Text(entry.value), + ), + ], + ), + ], + ), + ), + ), + SliverSafeArea( + sliver: Skeletonizer.sliver( + enabled: topTracks.isLoading && !topTracks.isLoadingNextPage, + child: SliverInfiniteList( + onFetchData: () async { + await topTracksNotifier.fetchMore(); + }, + hasError: topTracks.hasError, + isLoading: + topTracks.isLoading && !topTracks.isLoadingNextPage, + hasReachedMax: topTracks.asData?.value.hasMore ?? true, + itemCount: artistsData.length, + itemBuilder: (context, index) { + final artist = artistsData[index]; + return StatsArtistItem( + artist: artist.artist, + info: Text(usdFormatter.format(artist.count * 0.005)), + ); + }, + ), + ), + ), + ], + ), ), ); } diff --git a/lib/pages/stats/minutes/minutes.dart b/lib/pages/stats/minutes/minutes.dart index 39438b47..2ee4c8d7 100644 --- a/lib/pages/stats/minutes/minutes.dart +++ b/lib/pages/stats/minutes/minutes.dart @@ -10,7 +10,9 @@ import 'package:spotube/provider/history/top.dart'; import 'package:spotube/provider/history/top/tracks.dart'; import 'package:spotube/provider/spotify/spotify.dart'; import 'package:very_good_infinite_list/very_good_infinite_list.dart'; +import 'package:auto_route/auto_route.dart'; +@RoutePage() class StatsMinutesPage extends HookConsumerWidget { static const name = "stats_minutes"; @@ -26,34 +28,36 @@ class StatsMinutesPage extends HookConsumerWidget { final tracksData = topTracks.asData?.value.items ?? []; - return Scaffold( - headers: [ - TitleBar( - title: Text(context.l10n.minutes_listened), - automaticallyImplyLeading: true, - ) - ], - child: Skeletonizer( - enabled: topTracks.isLoading && !topTracks.isLoadingNextPage, - child: InfiniteList( - separatorBuilder: (context, index) => const Gap(8), - onFetchData: () async { - await topTracksNotifier.fetchMore(); - }, - hasError: topTracks.hasError, - isLoading: topTracks.isLoading && !topTracks.isLoadingNextPage, - hasReachedMax: topTracks.asData?.value.hasMore ?? true, - itemCount: tracksData.length, - itemBuilder: (context, index) { - final track = tracksData[index]; - return StatsTrackItem( - track: track.track, - info: Text( - context.l10n.count_mins(compactNumberFormatter - .format(track.count * track.track.duration!.inMinutes)), - ), - ); - }, + return SafeArea( + bottom: false, + child: Scaffold( + headers: [ + TitleBar( + title: Text(context.l10n.minutes_listened), + ) + ], + child: Skeletonizer( + enabled: topTracks.isLoading && !topTracks.isLoadingNextPage, + child: InfiniteList( + separatorBuilder: (context, index) => const Gap(8), + onFetchData: () async { + await topTracksNotifier.fetchMore(); + }, + hasError: topTracks.hasError, + isLoading: topTracks.isLoading && !topTracks.isLoadingNextPage, + hasReachedMax: topTracks.asData?.value.hasMore ?? true, + itemCount: tracksData.length, + itemBuilder: (context, index) { + final track = tracksData[index]; + return StatsTrackItem( + track: track.track, + info: Text( + context.l10n.count_mins(compactNumberFormatter + .format(track.count * track.track.duration!.inMinutes)), + ), + ); + }, + ), ), ), ); diff --git a/lib/pages/stats/playlists/playlists.dart b/lib/pages/stats/playlists/playlists.dart index f5d7a285..03ea5126 100644 --- a/lib/pages/stats/playlists/playlists.dart +++ b/lib/pages/stats/playlists/playlists.dart @@ -10,7 +10,9 @@ import 'package:spotube/provider/history/top.dart'; import 'package:spotube/provider/history/top/playlists.dart'; import 'package:spotube/provider/spotify/spotify.dart'; import 'package:very_good_infinite_list/very_good_infinite_list.dart'; +import 'package:auto_route/auto_route.dart'; +@RoutePage() class StatsPlaylistsPage extends HookConsumerWidget { static const name = "stats_playlists"; const StatsPlaylistsPage({super.key}); @@ -25,33 +27,36 @@ class StatsPlaylistsPage extends HookConsumerWidget { final playlistsData = topPlaylists.asData?.value.items ?? []; - return Scaffold( - headers: [ - TitleBar( - automaticallyImplyLeading: true, - title: Text(context.l10n.playlists), - ) - ], - child: Skeletonizer( - enabled: topPlaylists.isLoading && !topPlaylists.isLoadingNextPage, - child: InfiniteList( - onFetchData: () async { - await topPlaylistsNotifier.fetchMore(); - }, - hasError: topPlaylists.hasError, - isLoading: topPlaylists.isLoading && !topPlaylists.isLoadingNextPage, - hasReachedMax: topPlaylists.asData?.value.hasMore ?? true, - itemCount: playlistsData.length, - itemBuilder: (context, index) { - final playlist = playlistsData[index]; - return StatsPlaylistItem( - playlist: playlist.playlist, - info: Text( - context.l10n - .count_plays(compactNumberFormatter.format(playlist.count)), - ), - ); - }, + return SafeArea( + bottom: false, + child: Scaffold( + headers: [ + TitleBar( + title: Text(context.l10n.playlists), + ) + ], + child: Skeletonizer( + enabled: topPlaylists.isLoading && !topPlaylists.isLoadingNextPage, + child: InfiniteList( + onFetchData: () async { + await topPlaylistsNotifier.fetchMore(); + }, + hasError: topPlaylists.hasError, + isLoading: + topPlaylists.isLoading && !topPlaylists.isLoadingNextPage, + hasReachedMax: topPlaylists.asData?.value.hasMore ?? true, + itemCount: playlistsData.length, + itemBuilder: (context, index) { + final playlist = playlistsData[index]; + return StatsPlaylistItem( + playlist: playlist.playlist, + info: Text( + context.l10n.count_plays( + compactNumberFormatter.format(playlist.count)), + ), + ); + }, + ), ), ), ); diff --git a/lib/pages/stats/stats.dart b/lib/pages/stats/stats.dart index e543900c..da7c64f3 100644 --- a/lib/pages/stats/stats.dart +++ b/lib/pages/stats/stats.dart @@ -1,10 +1,13 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; +import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/components/titlebar/titlebar.dart'; import 'package:spotube/modules/stats/summary/summary.dart'; import 'package:spotube/modules/stats/top/top.dart'; import 'package:spotube/utils/platform.dart'; +import 'package:auto_route/auto_route.dart'; +@RoutePage() class StatsPage extends HookConsumerWidget { static const name = "stats"; @@ -12,23 +15,30 @@ class StatsPage extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { - return SafeArea( - bottom: false, - child: Scaffold( - headers: [ - if (kTitlebarVisible) const TitleBar(), - ], - child: CustomScrollView( - slivers: [ - if (kIsMacOS) const SliverGap(20), - const StatsPageSummarySection(), - const StatsPageTopSection(), - const SliverToBoxAdapter( - child: SafeArea( - child: SizedBox(), - ), - ) + return PopScope( + canPop: false, + onPopInvokedWithResult: (didPop, result) { + context.navigateTo(const HomeRoute()); + }, + child: SafeArea( + bottom: false, + child: Scaffold( + headers: [ + if (kTitlebarVisible) + const TitleBar(automaticallyImplyLeading: false), ], + child: CustomScrollView( + slivers: [ + if (kIsMacOS) const SliverGap(20), + const StatsPageSummarySection(), + const StatsPageTopSection(), + const SliverToBoxAdapter( + child: SafeArea( + child: SizedBox(), + ), + ) + ], + ), ), ), ); diff --git a/lib/pages/stats/streams/streams.dart b/lib/pages/stats/streams/streams.dart index 2c2e0c9b..0d919a44 100644 --- a/lib/pages/stats/streams/streams.dart +++ b/lib/pages/stats/streams/streams.dart @@ -10,7 +10,9 @@ import 'package:spotube/provider/history/top.dart'; import 'package:spotube/provider/history/top/tracks.dart'; import 'package:spotube/provider/spotify/spotify.dart'; import 'package:very_good_infinite_list/very_good_infinite_list.dart'; +import 'package:auto_route/auto_route.dart'; +@RoutePage() class StatsStreamsPage extends HookConsumerWidget { static const name = "stats_streams"; @@ -26,34 +28,36 @@ class StatsStreamsPage extends HookConsumerWidget { final tracksData = topTracks.asData?.value.items ?? []; - return Scaffold( - headers: [ - TitleBar( - title: Text(context.l10n.streamed_songs), - automaticallyImplyLeading: true, - ) - ], - child: Skeletonizer( - enabled: topTracks.isLoading && !topTracks.isLoadingNextPage, - child: InfiniteList( - separatorBuilder: (context, index) => const Gap(8), - onFetchData: () async { - await topTracksNotifier.fetchMore(); - }, - hasError: topTracks.hasError, - isLoading: topTracks.isLoading && !topTracks.isLoadingNextPage, - hasReachedMax: topTracks.asData?.value.hasMore ?? true, - itemCount: tracksData.length, - itemBuilder: (context, index) { - final track = tracksData[index]; - return StatsTrackItem( - track: track.track, - info: Text( - context.l10n - .count_plays(compactNumberFormatter.format(track.count)), - ), - ); - }, + return SafeArea( + bottom: false, + child: Scaffold( + headers: [ + TitleBar( + title: Text(context.l10n.streamed_songs), + ) + ], + child: Skeletonizer( + enabled: topTracks.isLoading && !topTracks.isLoadingNextPage, + child: InfiniteList( + separatorBuilder: (context, index) => const Gap(8), + onFetchData: () async { + await topTracksNotifier.fetchMore(); + }, + hasError: topTracks.hasError, + isLoading: topTracks.isLoading && !topTracks.isLoadingNextPage, + hasReachedMax: topTracks.asData?.value.hasMore ?? true, + itemCount: tracksData.length, + itemBuilder: (context, index) { + final track = tracksData[index]; + return StatsTrackItem( + track: track.track, + info: Text( + context.l10n + .count_plays(compactNumberFormatter.format(track.count)), + ), + ); + }, + ), ), ), ); diff --git a/lib/pages/track/track.dart b/lib/pages/track/track.dart index 54563bfe..0f8dae5d 100644 --- a/lib/pages/track/track.dart +++ b/lib/pages/track/track.dart @@ -1,10 +1,10 @@ import 'dart:ui'; -import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:skeletonizer/skeletonizer.dart'; import 'package:spotube/collections/fake.dart'; +import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/heart_button/heart_button.dart'; import 'package:spotube/components/image/universal_image.dart'; @@ -20,14 +20,16 @@ import 'package:spotube/provider/spotify/spotify.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; import 'package:spotube/extensions/constrains.dart'; +import 'package:auto_route/auto_route.dart'; +@RoutePage() class TrackPage extends HookConsumerWidget { static const name = "track"; final String trackId; const TrackPage({ super.key, - required this.trackId, + @PathParam("id") required this.trackId, }); @override @@ -52,195 +54,200 @@ class TrackPage extends HookConsumerWidget { } } - return Scaffold( - headers: const [ - TitleBar( - automaticallyImplyLeading: true, - backgroundColor: Colors.transparent, - surfaceBlur: 0, - ) - ], - floatingHeader: true, - child: Stack( - children: [ - Positioned.fill( - child: Container( - decoration: BoxDecoration( - image: DecorationImage( - image: UniversalImage.imageProvider( - track.album!.images.asUrlString( - placeholder: ImagePlaceholder.albumArt, + return SafeArea( + bottom: false, + child: Scaffold( + headers: const [ + TitleBar( + backgroundColor: Colors.transparent, + surfaceBlur: 0, + ) + ], + floatingHeader: true, + child: Stack( + children: [ + Positioned.fill( + child: Container( + decoration: BoxDecoration( + image: DecorationImage( + image: UniversalImage.imageProvider( + track.album!.images.asUrlString( + placeholder: ImagePlaceholder.albumArt, + ), ), + fit: BoxFit.cover, + colorFilter: ColorFilter.mode( + colorScheme.background.withOpacity(0.5), + BlendMode.srcOver, + ), + alignment: Alignment.topCenter, ), - fit: BoxFit.cover, - colorFilter: ColorFilter.mode( - colorScheme.background.withOpacity(0.5), - BlendMode.srcOver, - ), - alignment: Alignment.topCenter, ), ), ), - ), - Positioned.fill( - child: BackdropFilter( - filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), - child: Skeletonizer( - enabled: trackQuery.isLoading, - child: Container( - alignment: Alignment.topCenter, - decoration: BoxDecoration( - gradient: LinearGradient( - colors: [ - colorScheme.background, - Colors.transparent, - ], - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - stops: const [0.2, 1], + Positioned.fill( + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), + child: Skeletonizer( + enabled: trackQuery.isLoading, + child: Container( + alignment: Alignment.topCenter, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + colorScheme.background, + Colors.transparent, + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + stops: const [0.2, 1], + ), ), - ), - child: SafeArea( - child: Wrap( - spacing: 20, - runSpacing: 20, - alignment: WrapAlignment.center, - crossAxisAlignment: WrapCrossAlignment.center, - runAlignment: WrapAlignment.center, - children: [ - ClipRRect( - borderRadius: BorderRadius.circular(10), - child: UniversalImage( - path: track.album!.images.asUrlString( - placeholder: ImagePlaceholder.albumArt, + child: SafeArea( + child: Wrap( + spacing: 20, + runSpacing: 20, + alignment: WrapAlignment.center, + crossAxisAlignment: WrapCrossAlignment.center, + runAlignment: WrapAlignment.center, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(10), + child: UniversalImage( + path: track.album!.images.asUrlString( + placeholder: ImagePlaceholder.albumArt, + ), + height: 200, + width: 200, ), - height: 200, - width: 200, ), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: Column( - crossAxisAlignment: mediaQuery.smAndDown - ? CrossAxisAlignment.center - : CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - track.name!, - ).large().semiBold(), - const Gap(10), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(SpotubeIcons.album), - const Gap(5), - Flexible( - child: LinkText( - track.album!.name!, - '/album/${track.album!.id}', - push: true, - extra: track.album, - ), - ), - ], - ), - const Gap(10), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(SpotubeIcons.artist), - const Gap(5), - Flexible( - child: ArtistLink( - artists: track.artists!, - hideOverflowArtist: false, - ), - ), - ], - ), - const Gap(10), - ConstrainedBox( - constraints: - const BoxConstraints(maxWidth: 350), - child: Row( - mainAxisSize: mediaQuery.smAndDown - ? MainAxisSize.max - : MainAxisSize.min, + Padding( + padding: + const EdgeInsets.symmetric(horizontal: 16.0), + child: Column( + crossAxisAlignment: mediaQuery.smAndDown + ? CrossAxisAlignment.center + : CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + track.name!, + ).large().semiBold(), + const Gap(10), + Row( + mainAxisSize: MainAxisSize.min, children: [ + const Icon(SpotubeIcons.album), const Gap(5), - if (!isActive && - !playlist.tracks - .containsBy(track, (t) => t.id)) - Button.outline( - leading: - const Icon(SpotubeIcons.queueAdd), - child: Text(context.l10n.queue), - onPressed: () { - playlistNotifier.addTrack(track); - }, - ), - const Gap(5), - if (!isActive && - !playlist.tracks - .containsBy(track, (t) => t.id)) - Tooltip( - tooltip: TooltipContainer( - child: Text(context.l10n.play_next), + Flexible( + child: LinkText( + track.album!.name!, + AlbumRoute( + id: track.album!.id!, + album: track.album!, ), - child: IconButton.outline( - icon: const Icon( - SpotubeIcons.lightning), - onPressed: () { - playlistNotifier - .addTracksAtFirst([track]); - }, - ), - ), - const Gap(5), - Tooltip( - tooltip: TooltipContainer( - child: Text( - isActive - ? context.l10n.pause_playback - : context.l10n.play, - ), - ), - child: IconButton.primary( - shape: ButtonShape.circle, - icon: Icon( - isActive - ? SpotubeIcons.pause - : SpotubeIcons.play, - ), - onPressed: onPlay, + push: true, ), ), - const Gap(5), - if (mediaQuery.smAndDown) - const Spacer() - else - const Gap(20), - TrackHeartButton(track: track), - TrackOptions( - track: track, - userPlaylist: false, - ), - const Gap(5), ], ), - ), - ], + const Gap(10), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(SpotubeIcons.artist), + const Gap(5), + Flexible( + child: ArtistLink( + artists: track.artists!, + hideOverflowArtist: false, + ), + ), + ], + ), + const Gap(10), + ConstrainedBox( + constraints: + const BoxConstraints(maxWidth: 350), + child: Row( + mainAxisSize: mediaQuery.smAndDown + ? MainAxisSize.max + : MainAxisSize.min, + children: [ + const Gap(5), + if (!isActive && + !playlist.tracks + .containsBy(track, (t) => t.id)) + Button.outline( + leading: + const Icon(SpotubeIcons.queueAdd), + child: Text(context.l10n.queue), + onPressed: () { + playlistNotifier.addTrack(track); + }, + ), + const Gap(5), + if (!isActive && + !playlist.tracks + .containsBy(track, (t) => t.id)) + Tooltip( + tooltip: TooltipContainer( + child: Text(context.l10n.play_next), + ), + child: IconButton.outline( + icon: const Icon( + SpotubeIcons.lightning), + onPressed: () { + playlistNotifier + .addTracksAtFirst([track]); + }, + ), + ), + const Gap(5), + Tooltip( + tooltip: TooltipContainer( + child: Text( + isActive + ? context.l10n.pause_playback + : context.l10n.play, + ), + ), + child: IconButton.primary( + shape: ButtonShape.circle, + icon: Icon( + isActive + ? SpotubeIcons.pause + : SpotubeIcons.play, + ), + onPressed: onPlay, + ), + ), + const Gap(5), + if (mediaQuery.smAndDown) + const Spacer() + else + const Gap(20), + TrackHeartButton(track: track), + TrackOptions( + track: track, + userPlaylist: false, + ), + const Gap(5), + ], + ), + ), + ], + ), ), - ), - ], + ], + ), ), ), ), ), ), - ), - ], + ], + ), ), ); } diff --git a/lib/utils/service_utils.dart b/lib/utils/service_utils.dart index c89866b4..b55af9e9 100644 --- a/lib/utils/service_utils.dart +++ b/lib/utils/service_utils.dart @@ -1,8 +1,9 @@ import 'dart:typed_data'; +import 'package:auto_route/auto_route.dart'; import 'package:dio/dio.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; -import 'package:go_router/go_router.dart'; + import 'package:html/dom.dart' hide Text; import 'package:shadcn_flutter/shadcn_flutter.dart' hide Element; import 'package:spotify/spotify.dart'; @@ -276,70 +277,6 @@ abstract class ServiceUtils { return subtitle; } - static void navigate(BuildContext context, String location, {Object? extra}) { - if (GoRouterState.of(context).matchedLocation == location) return; - 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); - final routerStack = router.routerDelegate.currentConfiguration.matches - .map((e) => e.matchedLocation); - - if (routerState.matchedLocation == location || - routerStack.contains(location)) { - return; - } - 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/pubspec.lock b/pubspec.lock index 1e9d9265..8c1bb00d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -142,6 +142,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.1.21" + auto_route: + dependency: "direct main" + description: + name: auto_route + sha256: "1d1bd908a1fec327719326d5d0791edd37f16caff6493c01003689fb03315ad7" + url: "https://pub.dev" + source: hosted + version: "9.3.0+1" + auto_route_generator: + dependency: "direct dev" + description: + name: auto_route_generator + sha256: c9086eb07271e51b44071ad5cff34e889f3156710b964a308c2ab590769e79e6 + url: "https://pub.dev" + source: hosted + version: "9.0.0" auto_size_text: dependency: "direct main" description: @@ -1073,14 +1089,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" - go_router: - dependency: "direct main" - description: - name: go_router - sha256: "2fd11229f59e23e967b0775df8d5948a519cd7e1e8b6e849729e010587b46539" - url: "https://pub.dev" - source: hosted - version: "14.6.2" google_fonts: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index ed0d7ce5..8b3c3e7b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -19,6 +19,7 @@ dependencies: audio_service: ^0.18.13 audio_service_mpris: ^0.2.0 audio_session: ^0.1.19 + auto_route: ^9.3.0+1 auto_size_text: ^3.0.0 bonsoir: ^5.1.10 cached_network_image: ^3.3.1 @@ -70,7 +71,6 @@ dependencies: freezed_annotation: ^2.4.1 fuzzywuzzy: ^1.1.6 gap: ^3.0.1 - go_router: ^14.2.7 google_fonts: ^6.2.1 hive: ^2.2.3 hive_flutter: ^1.1.0 @@ -161,6 +161,7 @@ dev_dependencies: xml: ^6.5.0 io: ^1.0.4 drift_dev: ^2.21.0 + auto_route_generator: ^9.0.0 dependency_overrides: bonsoir_android: