refactor: use route names

This commit is contained in:
Kingkor Roy Tirtho 2024-04-29 12:02:28 +06:00
parent ddadb0edc5
commit 5f442a1ff7
48 changed files with 592 additions and 254 deletions

View File

@ -7,6 +7,10 @@ import 'package:go_router/go_router.dart';
import 'package:spotube/collections/routes.dart'; import 'package:spotube/collections/routes.dart';
import 'package:spotube/components/player/player_controls.dart'; import 'package:spotube/components/player/player_controls.dart';
import 'package:spotube/models/logger.dart'; import 'package:spotube/models/logger.dart';
import 'package:spotube/pages/home/home.dart';
import 'package:spotube/pages/library/library.dart';
import 'package:spotube/pages/lyrics/lyrics.dart';
import 'package:spotube/pages/search/search.dart';
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
import 'package:spotube/services/audio_player/audio_player.dart'; import 'package:spotube/services/audio_player/audio_player.dart';
import 'package:spotube/utils/platform.dart'; import 'package:spotube/utils/platform.dart';
@ -67,16 +71,16 @@ class HomeTabAction extends Action<HomeTabIntent> {
final router = intent.ref.read(routerProvider); final router = intent.ref.read(routerProvider);
switch (intent.tab) { switch (intent.tab) {
case HomeTabs.browse: case HomeTabs.browse:
router.go("/"); router.goNamed(HomePage.name);
break; break;
case HomeTabs.search: case HomeTabs.search:
router.go("/search"); router.goNamed(SearchPage.name);
break; break;
case HomeTabs.library: case HomeTabs.library:
router.go("/library"); router.goNamed(LibraryPage.name);
break; break;
case HomeTabs.lyrics: case HomeTabs.lyrics:
router.go("/lyrics"); router.goNamed(LyricsPage.name);
break; break;
} }
return null; return null;

View File

@ -24,6 +24,7 @@ import 'package:spotube/pages/search/search.dart';
import 'package:spotube/pages/settings/blacklist.dart'; import 'package:spotube/pages/settings/blacklist.dart';
import 'package:spotube/pages/settings/about.dart'; import 'package:spotube/pages/settings/about.dart';
import 'package:spotube/pages/settings/logs.dart'; import 'package:spotube/pages/settings/logs.dart';
import 'package:spotube/pages/stats/stats.dart';
import 'package:spotube/pages/track/track.dart'; import 'package:spotube/pages/track/track.dart';
import 'package:spotube/provider/authentication_provider.dart'; import 'package:spotube/provider/authentication_provider.dart';
import 'package:spotube/services/kv_store/kv_store.dart'; import 'package:spotube/services/kv_store/kv_store.dart';
@ -50,6 +51,7 @@ final routerProvider = Provider((ref) {
routes: [ routes: [
GoRoute( GoRoute(
path: "/", path: "/",
name: HomePage.name,
redirect: (context, state) async { redirect: (context, state) async {
final authNotifier = ref.read(authenticationProvider.notifier); final authNotifier = ref.read(authenticationProvider.notifier);
final json = await authNotifier.box.get(authNotifier.cacheKey); final json = await authNotifier.box.get(authNotifier.cacheKey);
@ -66,11 +68,13 @@ final routerProvider = Provider((ref) {
routes: [ routes: [
GoRoute( GoRoute(
path: "genres", path: "genres",
name: GenrePage.name,
pageBuilder: (context, state) => pageBuilder: (context, state) =>
const SpotubePage(child: GenrePage()), const SpotubePage(child: GenrePage()),
), ),
GoRoute( GoRoute(
path: "genre/:categoryId", path: "genre/:categoryId",
name: GenrePlaylistsPage.name,
pageBuilder: (context, state) => SpotubePage( pageBuilder: (context, state) => SpotubePage(
child: GenrePlaylistsPage( child: GenrePlaylistsPage(
category: state.extra as Category, category: state.extra as Category,
@ -79,6 +83,7 @@ final routerProvider = Provider((ref) {
), ),
GoRoute( GoRoute(
path: "feeds/:feedId", path: "feeds/:feedId",
name: HomeFeedSectionPage.name,
pageBuilder: (context, state) => SpotubePage( pageBuilder: (context, state) => SpotubePage(
child: HomeFeedSectionPage( child: HomeFeedSectionPage(
sectionUri: state.pathParameters["feedId"] as String, sectionUri: state.pathParameters["feedId"] as String,
@ -89,45 +94,50 @@ final routerProvider = Provider((ref) {
), ),
GoRoute( GoRoute(
path: "/search", path: "/search",
name: "Search", name: SearchPage.name,
pageBuilder: (context, state) => pageBuilder: (context, state) =>
const SpotubePage(child: SearchPage()), const SpotubePage(child: SearchPage()),
), ),
GoRoute( GoRoute(
path: "/library", path: "/library",
name: "Library", name: LibraryPage.name,
pageBuilder: (context, state) => pageBuilder: (context, state) =>
const SpotubePage(child: LibraryPage()), const SpotubePage(child: LibraryPage()),
routes: [ routes: [
GoRoute( GoRoute(
path: "generate", path: "generate",
pageBuilder: (context, state) => name: PlaylistGeneratorPage.name,
const SpotubePage(child: PlaylistGeneratorPage()), pageBuilder: (context, state) =>
routes: [ const SpotubePage(child: PlaylistGeneratorPage()),
GoRoute( routes: [
path: "result", GoRoute(
pageBuilder: (context, state) => SpotubePage( path: "result",
child: PlaylistGenerateResultPage( name: PlaylistGenerateResultPage.name,
state: state.extra as GeneratePlaylistProviderInput, pageBuilder: (context, state) => SpotubePage(
), child: PlaylistGenerateResultPage(
state: state.extra as GeneratePlaylistProviderInput,
), ),
), ),
]), ),
]), ]),
],
),
GoRoute( GoRoute(
path: "/lyrics", path: "/lyrics",
name: "Lyrics", name: LyricsPage.name,
pageBuilder: (context, state) => pageBuilder: (context, state) =>
const SpotubePage(child: LyricsPage()), const SpotubePage(child: LyricsPage()),
), ),
GoRoute( GoRoute(
path: "/settings", path: "/settings",
name: SettingsPage.name,
pageBuilder: (context, state) => const SpotubePage( pageBuilder: (context, state) => const SpotubePage(
child: SettingsPage(), child: SettingsPage(),
), ),
routes: [ routes: [
GoRoute( GoRoute(
path: "blacklist", path: "blacklist",
name: BlackListPage.name,
pageBuilder: (context, state) => SpotubeSlidePage( pageBuilder: (context, state) => SpotubeSlidePage(
child: const BlackListPage(), child: const BlackListPage(),
), ),
@ -135,12 +145,14 @@ final routerProvider = Provider((ref) {
if (!kIsWeb) if (!kIsWeb)
GoRoute( GoRoute(
path: "logs", path: "logs",
name: LogsPage.name,
pageBuilder: (context, state) => SpotubeSlidePage( pageBuilder: (context, state) => SpotubeSlidePage(
child: const LogsPage(), child: const LogsPage(),
), ),
), ),
GoRoute( GoRoute(
path: "about", path: "about",
name: AboutSpotube.name,
pageBuilder: (context, state) => SpotubeSlidePage( pageBuilder: (context, state) => SpotubeSlidePage(
child: const AboutSpotube(), child: const AboutSpotube(),
), ),
@ -149,6 +161,7 @@ final routerProvider = Provider((ref) {
), ),
GoRoute( GoRoute(
path: "/album/:id", path: "/album/:id",
name: AlbumPage.name,
pageBuilder: (context, state) { pageBuilder: (context, state) {
assert(state.extra is AlbumSimple); assert(state.extra is AlbumSimple);
return SpotubePage( return SpotubePage(
@ -158,6 +171,7 @@ final routerProvider = Provider((ref) {
), ),
GoRoute( GoRoute(
path: "/artist/:id", path: "/artist/:id",
name: ArtistPage.name,
pageBuilder: (context, state) { pageBuilder: (context, state) {
assert(state.pathParameters["id"] != null); assert(state.pathParameters["id"] != null);
return SpotubePage( return SpotubePage(
@ -166,6 +180,7 @@ final routerProvider = Provider((ref) {
), ),
GoRoute( GoRoute(
path: "/playlist/:id", path: "/playlist/:id",
name: PlaylistPage.name,
pageBuilder: (context, state) { pageBuilder: (context, state) {
assert(state.extra is PlaylistSimple); assert(state.extra is PlaylistSimple);
return SpotubePage( return SpotubePage(
@ -177,6 +192,7 @@ final routerProvider = Provider((ref) {
), ),
GoRoute( GoRoute(
path: "/track/:id", path: "/track/:id",
name: TrackPage.name,
pageBuilder: (context, state) { pageBuilder: (context, state) {
final id = state.pathParameters["id"]!; final id = state.pathParameters["id"]!;
return SpotubePage( return SpotubePage(
@ -186,12 +202,14 @@ final routerProvider = Provider((ref) {
), ),
GoRoute( GoRoute(
path: "/connect", path: "/connect",
name: ConnectPage.name,
pageBuilder: (context, state) => const SpotubePage( pageBuilder: (context, state) => const SpotubePage(
child: ConnectPage(), child: ConnectPage(),
), ),
routes: [ routes: [
GoRoute( GoRoute(
path: "control", path: "control",
name: ConnectControlPage.name,
pageBuilder: (context, state) { pageBuilder: (context, state) {
return const SpotubePage( return const SpotubePage(
child: ConnectControlPage(), child: ConnectControlPage(),
@ -202,13 +220,22 @@ final routerProvider = Provider((ref) {
), ),
GoRoute( GoRoute(
path: "/profile", path: "/profile",
name: ProfilePage.name,
pageBuilder: (context, state) => pageBuilder: (context, state) =>
const SpotubePage(child: ProfilePage()), const SpotubePage(child: ProfilePage()),
),
GoRoute(
path: "/stats",
name: StatsPage.name,
pageBuilder: (context, state) => const SpotubePage(
child: StatsPage(),
),
) )
], ],
), ),
GoRoute( GoRoute(
path: "/mini-player", path: "/mini-player",
name: MiniLyricsPage.name,
parentNavigatorKey: rootNavigatorKey, parentNavigatorKey: rootNavigatorKey,
pageBuilder: (context, state) => SpotubePage( pageBuilder: (context, state) => SpotubePage(
child: MiniLyricsPage(prevSize: state.extra as Size), child: MiniLyricsPage(prevSize: state.extra as Size),
@ -216,6 +243,7 @@ final routerProvider = Provider((ref) {
), ),
GoRoute( GoRoute(
path: "/getting-started", path: "/getting-started",
name: GettingStarting.name,
parentNavigatorKey: rootNavigatorKey, parentNavigatorKey: rootNavigatorKey,
pageBuilder: (context, state) => const SpotubePage( pageBuilder: (context, state) => const SpotubePage(
child: GettingStarting(), child: GettingStarting(),
@ -223,6 +251,7 @@ final routerProvider = Provider((ref) {
), ),
GoRoute( GoRoute(
path: "/login", path: "/login",
name: WebViewLogin.name,
parentNavigatorKey: rootNavigatorKey, parentNavigatorKey: rootNavigatorKey,
pageBuilder: (context, state) => SpotubePage( pageBuilder: (context, state) => SpotubePage(
child: kIsMobile ? const WebViewLogin() : const DesktopLoginPage(), child: kIsMobile ? const WebViewLogin() : const DesktopLoginPage(),
@ -230,6 +259,7 @@ final routerProvider = Provider((ref) {
), ),
GoRoute( GoRoute(
path: "/login-tutorial", path: "/login-tutorial",
name: LoginTutorial.name,
parentNavigatorKey: rootNavigatorKey, parentNavigatorKey: rootNavigatorKey,
pageBuilder: (context, state) => const SpotubePage( pageBuilder: (context, state) => const SpotubePage(
child: LoginTutorial(), child: LoginTutorial(),
@ -237,6 +267,7 @@ final routerProvider = Provider((ref) {
), ),
GoRoute( GoRoute(
path: "/lastfm-login", path: "/lastfm-login",
name: LastFMLoginPage.name,
parentNavigatorKey: rootNavigatorKey, parentNavigatorKey: rootNavigatorKey,
pageBuilder: (context, state) => pageBuilder: (context, state) =>
const SpotubePage(child: LastFMLoginPage()), const SpotubePage(child: LastFMLoginPage()),

View File

@ -1,32 +1,82 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/collections/spotube_icons.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:spotube/pages/home/home.dart';
import 'package:spotube/pages/library/library.dart';
import 'package:spotube/pages/lyrics/lyrics.dart';
import 'package:spotube/pages/search/search.dart';
import 'package:spotube/pages/settings/settings.dart';
import 'package:spotube/pages/stats/stats.dart';
class SideBarTiles { class SideBarTiles {
final IconData icon; final IconData icon;
final String title; final String title;
final String id; final String id;
SideBarTiles({required this.icon, required this.title, required this.id}); final String name;
SideBarTiles({
required this.icon,
required this.title,
required this.id,
required this.name,
});
} }
List<SideBarTiles> getSidebarTileList(AppLocalizations l10n) => [ List<SideBarTiles> getSidebarTileList(AppLocalizations l10n) => [
SideBarTiles(id: "browse", icon: SpotubeIcons.home, title: l10n.browse),
SideBarTiles(id: "search", icon: SpotubeIcons.search, title: l10n.search),
SideBarTiles( SideBarTiles(
id: "library", icon: SpotubeIcons.library, title: l10n.library), id: "browse",
SideBarTiles(id: "lyrics", icon: SpotubeIcons.music, title: l10n.lyrics), name: HomePage.name,
]; icon: SpotubeIcons.home,
title: l10n.browse,
List<SideBarTiles> getNavbarTileList(AppLocalizations l10n) => [ ),
SideBarTiles(id: "browse", icon: SpotubeIcons.home, title: l10n.browse), SideBarTiles(
SideBarTiles(id: "search", icon: SpotubeIcons.search, title: l10n.search), id: "search",
name: SearchPage.name,
icon: SpotubeIcons.search,
title: l10n.search,
),
SideBarTiles( SideBarTiles(
id: "library", id: "library",
name: LibraryPage.name,
icon: SpotubeIcons.library, icon: SpotubeIcons.library,
title: l10n.library, title: l10n.library,
), ),
SideBarTiles(
id: "lyrics",
name: LyricsPage.name,
icon: SpotubeIcons.music,
title: l10n.lyrics,
),
SideBarTiles(
id: "stats",
name: StatsPage.name,
icon: SpotubeIcons.chart,
title: l10n.stats,
),
];
List<SideBarTiles> getNavbarTileList(AppLocalizations l10n) => [
SideBarTiles(
id: "browse",
name: HomePage.name,
icon: SpotubeIcons.home,
title: l10n.browse,
),
SideBarTiles(
id: "library",
name: LibraryPage.name,
icon: SpotubeIcons.library,
title: l10n.library,
),
SideBarTiles(
id: "stats",
name: StatsPage.name,
icon: SpotubeIcons.chart,
title: l10n.stats,
),
SideBarTiles( SideBarTiles(
id: "settings", id: "settings",
name: SettingsPage.name,
icon: SpotubeIcons.settings, icon: SpotubeIcons.settings,
title: l10n.settings, title: l10n.settings,
) )

View File

@ -121,4 +121,5 @@ abstract class SpotubeIcons {
static const monitor = FeatherIcons.monitor; static const monitor = FeatherIcons.monitor;
static const power = FeatherIcons.power; static const power = FeatherIcons.power;
static const bluetooth = FeatherIcons.bluetooth; static const bluetooth = FeatherIcons.bluetooth;
static const chart = FeatherIcons.barChart2;
} }

View File

@ -9,6 +9,7 @@ import 'package:spotube/extensions/context.dart';
import 'package:spotube/extensions/image.dart'; import 'package:spotube/extensions/image.dart';
import 'package:spotube/extensions/track.dart'; import 'package:spotube/extensions/track.dart';
import 'package:spotube/models/connect/connect.dart'; import 'package:spotube/models/connect/connect.dart';
import 'package:spotube/pages/album/album.dart';
import 'package:spotube/provider/connect/connect.dart'; import 'package:spotube/provider/connect/connect.dart';
import 'package:spotube/provider/history/history.dart'; import 'package:spotube/provider/history/history.dart';
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
@ -64,7 +65,14 @@ class AlbumCard extends HookConsumerWidget {
description: description:
"${album.albumType?.formatted}${album.artists?.asString() ?? ""}", "${album.albumType?.formatted}${album.artists?.asString() ?? ""}",
onTap: () { onTap: () {
ServiceUtils.push(context, "/album/${album.id}", extra: album); ServiceUtils.pushNamed(
context,
AlbumPage.name,
pathParameters: {
"id": album.id!,
},
extra: album,
);
}, },
onPlaybuttonPressed: () async { onPlaybuttonPressed: () async {
updating.value = true; updating.value = true;

View File

@ -9,6 +9,7 @@ import 'package:spotube/extensions/context.dart';
import 'package:spotube/extensions/image.dart'; import 'package:spotube/extensions/image.dart';
import 'package:spotube/hooks/utils/use_breakpoint_value.dart'; import 'package:spotube/hooks/utils/use_breakpoint_value.dart';
import 'package:spotube/hooks/utils/use_brightness_value.dart'; import 'package:spotube/hooks/utils/use_brightness_value.dart';
import 'package:spotube/pages/artist/artist.dart';
import 'package:spotube/provider/blacklist_provider.dart'; import 'package:spotube/provider/blacklist_provider.dart';
import 'package:spotube/utils/service_utils.dart'; import 'package:spotube/utils/service_utils.dart';
@ -63,7 +64,13 @@ class ArtistCard extends HookConsumerWidget {
), ),
child: InkWell( child: InkWell(
onTap: () { onTap: () {
ServiceUtils.push(context, "/artist/${artist.id}"); ServiceUtils.pushNamed(
context,
ArtistPage.name,
pathParameters: {
"id": artist.id!,
},
);
}, },
borderRadius: radius, borderRadius: radius,
child: Padding( child: Padding(

View File

@ -3,6 +3,7 @@ import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/context.dart';
import 'package:spotube/pages/connect/connect.dart';
import 'package:spotube/provider/connect/clients.dart'; import 'package:spotube/provider/connect/clients.dart';
import 'package:spotube/utils/service_utils.dart'; import 'package:spotube/utils/service_utils.dart';
@ -22,7 +23,7 @@ class ConnectDeviceButton extends HookConsumerWidget {
width: double.infinity, width: double.infinity,
child: TextButton( child: TextButton(
onPressed: () { onPressed: () {
ServiceUtils.push(context, "/connect"); ServiceUtils.pushNamed(context, ConnectPage.name);
}, },
style: FilledButton.styleFrom( style: FilledButton.styleFrom(
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
@ -59,7 +60,7 @@ class ConnectDeviceButton extends HookConsumerWidget {
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
child: InkWell( child: InkWell(
onTap: () { onTap: () {
ServiceUtils.push(context, "/connect"); ServiceUtils.pushNamed(context, ConnectPage.name);
}, },
borderRadius: BorderRadius.circular(50), borderRadius: BorderRadius.circular(50),
child: Ink( child: Ink(
@ -111,7 +112,7 @@ class ConnectDeviceButton extends HookConsumerWidget {
foregroundColor: colorScheme.onPrimary, foregroundColor: colorScheme.onPrimary,
), ),
onPressed: () { onPressed: () {
ServiceUtils.push(context, "/connect"); ServiceUtils.pushNamed(context, ConnectPage.name);
}, },
), ),
), ),

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/components/shared/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart'; import 'package:spotube/components/shared/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart';
import 'package:spotube/pages/home/feed/feed_section.dart';
import 'package:spotube/provider/spotify/views/home.dart'; import 'package:spotube/provider/spotify/views/home.dart';
import 'package:spotube/utils/service_utils.dart'; import 'package:spotube/utils/service_utils.dart';
@ -41,8 +42,13 @@ class HomePageFeedSection extends HookConsumerWidget {
child: TextButton.icon( child: TextButton.icon(
label: const Text("Browse More"), label: const Text("Browse More"),
icon: const Icon(SpotubeIcons.angleRight), icon: const Icon(SpotubeIcons.angleRight),
onPressed: () => onPressed: () => ServiceUtils.pushNamed(
ServiceUtils.push(context, "/feeds/${section.uri}"), context,
HomeFeedSectionPage.name,
pathParameters: {
"feedId": section.uri,
},
),
), ),
), ),
); );

View File

@ -6,6 +6,9 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/components/shared/image/universal_image.dart'; import 'package:spotube/components/shared/image/universal_image.dart';
import 'package:spotube/models/spotify_friends.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'; import 'package:spotube/provider/spotify_provider.dart';
class FriendItem extends HookConsumerWidget { class FriendItem extends HookConsumerWidget {
@ -57,7 +60,9 @@ class FriendItem extends HookConsumerWidget {
text: friend.track.name, text: friend.track.name,
recognizer: TapGestureRecognizer() recognizer: TapGestureRecognizer()
..onTap = () { ..onTap = () {
context.push("/track/${friend.track.id}"); context.pushNamed(TrackPage.name, pathParameters: {
"id": friend.track.id,
});
}, },
), ),
const TextSpan(text: ""), const TextSpan(text: ""),
@ -71,8 +76,12 @@ class FriendItem extends HookConsumerWidget {
text: " ${friend.track.artist.name}", text: " ${friend.track.artist.name}",
recognizer: TapGestureRecognizer() recognizer: TapGestureRecognizer()
..onTap = () { ..onTap = () {
context.push( context.pushNamed(
"/artist/${friend.track.artist.id}", ArtistPage.name,
pathParameters: {
"id": friend.track.artist.id,
},
extra: friend.track.artist,
); );
}, },
), ),
@ -105,8 +114,11 @@ class FriendItem extends HookConsumerWidget {
final album = final album =
await spotify.albums.get(friend.track.album.id); await spotify.albums.get(friend.track.album.id);
if (context.mounted) { if (context.mounted) {
context.push( context.pushNamed(
"/album/${friend.track.album.id}", AlbumPage.name,
pathParameters: {
"id": friend.track.album.id,
},
extra: album, extra: album,
); );
} }

View File

@ -13,6 +13,8 @@ import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/components/shared/image/universal_image.dart'; import 'package:spotube/components/shared/image/universal_image.dart';
import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/constrains.dart';
import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/context.dart';
import 'package:spotube/pages/home/genres/genre_playlists.dart';
import 'package:spotube/pages/home/genres/genres.dart';
import 'package:spotube/provider/spotify/spotify.dart'; import 'package:spotube/provider/spotify/spotify.dart';
class HomeGenresSection extends HookConsumerWidget { class HomeGenresSection extends HookConsumerWidget {
@ -50,7 +52,7 @@ class HomeGenresSection extends HookConsumerWidget {
textDirection: TextDirection.rtl, textDirection: TextDirection.rtl,
child: TextButton.icon( child: TextButton.icon(
onPressed: () { onPressed: () {
context.push('/genres'); context.pushNamed(GenrePage.name);
}, },
icon: const Icon(SpotubeIcons.angleRight), icon: const Icon(SpotubeIcons.angleRight),
label: Text( label: Text(
@ -110,7 +112,13 @@ class HomeGenresSection extends HookConsumerWidget {
return InkWell( return InkWell(
onTap: () { onTap: () {
context.push('/genre/${category.id}', extra: category); context.pushNamed(
GenrePlaylistsPage.name,
pathParameters: {
"categoryId": category.id!,
},
extra: category,
);
}, },
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
child: Ink( child: Ink(

View File

@ -6,6 +6,7 @@ import 'package:spotube/components/shared/dialogs/select_device_dialog.dart';
import 'package:spotube/components/shared/playbutton_card.dart'; import 'package:spotube/components/shared/playbutton_card.dart';
import 'package:spotube/extensions/image.dart'; import 'package:spotube/extensions/image.dart';
import 'package:spotube/models/connect/connect.dart'; import 'package:spotube/models/connect/connect.dart';
import 'package:spotube/pages/playlist/playlist.dart';
import 'package:spotube/provider/connect/connect.dart'; import 'package:spotube/provider/connect/connect.dart';
import 'package:spotube/provider/history/history.dart'; import 'package:spotube/provider/history/history.dart';
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
@ -58,9 +59,12 @@ class PlaylistCard extends HookConsumerWidget {
isOwner: playlist.owner?.id == me.asData?.value.id && isOwner: playlist.owner?.id == me.asData?.value.id &&
me.asData?.value.id != null, me.asData?.value.id != null,
onTap: () { onTap: () {
ServiceUtils.push( ServiceUtils.pushNamed(
context, context,
"/playlist/${playlist.id}", PlaylistPage.name,
pathParameters: {
"id": playlist.id!,
},
extra: playlist, extra: playlist,
); );
}, },

View File

@ -16,6 +16,8 @@ import 'package:spotube/extensions/context.dart';
import 'package:spotube/extensions/image.dart'; import 'package:spotube/extensions/image.dart';
import 'package:spotube/hooks/utils/use_brightness_value.dart'; import 'package:spotube/hooks/utils/use_brightness_value.dart';
import 'package:spotube/hooks/controllers/use_sidebarx_controller.dart'; import 'package:spotube/hooks/controllers/use_sidebarx_controller.dart';
import 'package:spotube/pages/profile/profile.dart';
import 'package:spotube/pages/settings/settings.dart';
import 'package:spotube/provider/download_manager_provider.dart'; import 'package:spotube/provider/download_manager_provider.dart';
import 'package:spotube/provider/authentication_provider.dart'; import 'package:spotube/provider/authentication_provider.dart';
import 'package:spotube/provider/spotify/spotify.dart'; import 'package:spotube/provider/spotify/spotify.dart';
@ -26,13 +28,9 @@ import 'package:spotube/utils/platform.dart';
import 'package:spotube/utils/service_utils.dart'; import 'package:spotube/utils/service_utils.dart';
class Sidebar extends HookConsumerWidget { class Sidebar extends HookConsumerWidget {
final int? selectedIndex;
final void Function(int) onSelectedIndexChanged;
final Widget child; final Widget child;
const Sidebar({ const Sidebar({
required this.selectedIndex,
required this.onSelectedIndexChanged,
required this.child, required this.child,
super.key, super.key,
}); });
@ -47,12 +45,9 @@ class Sidebar extends HookConsumerWidget {
); );
} }
static void goToSettings(BuildContext context) {
GoRouter.of(context).go("/settings");
}
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final routerState = GoRouterState.of(context);
final mediaQuery = MediaQuery.of(context); final mediaQuery = MediaQuery.of(context);
final downloadCount = ref.watch(downloadManagerProvider).$downloadCount; final downloadCount = ref.watch(downloadManagerProvider).$downloadCount;
@ -60,8 +55,17 @@ class Sidebar extends HookConsumerWidget {
final layoutMode = final layoutMode =
ref.watch(userPreferencesProvider.select((s) => s.layoutMode)); ref.watch(userPreferencesProvider.select((s) => s.layoutMode));
final sidebarTileList = useMemoized(
() => getSidebarTileList(context.l10n),
[context.l10n],
);
final selectedIndex = sidebarTileList.indexWhere(
(e) => routerState.namedLocation(e.name) == routerState.matchedLocation,
);
final controller = useSidebarXController( final controller = useSidebarXController(
selectedIndex: selectedIndex ?? 0, selectedIndex: selectedIndex,
extended: mediaQuery.lgAndUp, extended: mediaQuery.lgAndUp,
); );
@ -73,29 +77,6 @@ class Sidebar extends HookConsumerWidget {
Color.lerp(bg, Colors.black, 0.45)!, Color.lerp(bg, Colors.black, 0.45)!,
); );
final sidebarTileList = useMemoized(
() => getSidebarTileList(context.l10n),
[context.l10n],
);
useEffect(() {
if (controller.selectedIndex != selectedIndex && selectedIndex != null) {
controller.selectIndex(selectedIndex!);
}
return null;
}, [selectedIndex]);
useEffect(() {
void listener() {
onSelectedIndexChanged(controller.selectedIndex);
}
controller.addListener(listener);
return () {
controller.removeListener(listener);
};
}, [controller]);
useEffect(() { useEffect(() {
if (!context.mounted) return; if (!context.mounted) return;
if (mediaQuery.lgAndUp && !controller.extended) { if (mediaQuery.lgAndUp && !controller.extended) {
@ -106,6 +87,13 @@ class Sidebar extends HookConsumerWidget {
return null; return null;
}, [mediaQuery, controller]); }, [mediaQuery, controller]);
useEffect(() {
if (controller.selectedIndex != selectedIndex) {
controller.selectIndex(selectedIndex);
}
return null;
}, [selectedIndex]);
if (layoutMode == LayoutMode.compact || if (layoutMode == LayoutMode.compact ||
(mediaQuery.smAndDown && layoutMode == LayoutMode.adaptive)) { (mediaQuery.smAndDown && layoutMode == LayoutMode.adaptive)) {
return Scaffold(body: child); return Scaffold(body: child);
@ -119,23 +107,28 @@ class Sidebar extends HookConsumerWidget {
items: sidebarTileList.mapIndexed( items: sidebarTileList.mapIndexed(
(index, e) { (index, e) {
return SidebarXItem( return SidebarXItem(
iconWidget: Badge( onTap: () {
backgroundColor: theme.colorScheme.primary, context.goNamed(e.name);
isLabelVisible: e.title == "Library" && downloadCount > 0, },
label: Text( iconBuilder: (selected, hovered) {
downloadCount.toString(), return Badge(
style: const TextStyle( backgroundColor: theme.colorScheme.primary,
color: Colors.white, isLabelVisible: e.title == "Library" && downloadCount > 0,
fontSize: 10, label: Text(
downloadCount.toString(),
style: const TextStyle(
color: Colors.white,
fontSize: 10,
),
), ),
), child: Icon(
child: Icon( e.icon,
e.icon, color: selected || hovered
color: selectedIndex == index ? theme.colorScheme.primary
? theme.colorScheme.primary : null,
: null, ),
), );
), },
label: e.title, label: e.title,
); );
}, },
@ -257,7 +250,7 @@ class SidebarFooter extends HookConsumerWidget {
if (mediaQuery.mdAndDown) { if (mediaQuery.mdAndDown) {
return IconButton( return IconButton(
icon: const Icon(SpotubeIcons.settings), icon: const Icon(SpotubeIcons.settings),
onPressed: () => Sidebar.goToSettings(context), onPressed: () => ServiceUtils.navigateNamed(context, SettingsPage.name),
); );
} }
@ -278,7 +271,7 @@ class SidebarFooter extends HookConsumerWidget {
Flexible( Flexible(
child: InkWell( child: InkWell(
onTap: () { onTap: () {
ServiceUtils.push(context, "/profile"); ServiceUtils.pushNamed(context, ProfilePage.name);
}, },
borderRadius: BorderRadius.circular(30), borderRadius: BorderRadius.circular(30),
child: Row( child: Row(
@ -310,7 +303,7 @@ class SidebarFooter extends HookConsumerWidget {
IconButton( IconButton(
icon: const Icon(SpotubeIcons.settings), icon: const Icon(SpotubeIcons.settings),
onPressed: () { onPressed: () {
Sidebar.goToSettings(context); ServiceUtils.navigateNamed(context, SettingsPage.name);
}, },
), ),
], ],

View File

@ -3,39 +3,35 @@ import 'dart:ui';
import 'package:curved_navigation_bar/curved_navigation_bar.dart'; import 'package:curved_navigation_bar/curved_navigation_bar.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotube/collections/side_bar_tiles.dart'; import 'package:spotube/collections/side_bar_tiles.dart';
import 'package:spotube/components/root/sidebar.dart';
import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/constrains.dart';
import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/context.dart';
import 'package:spotube/hooks/utils/use_brightness_value.dart'; import 'package:spotube/hooks/utils/use_brightness_value.dart';
import 'package:spotube/provider/download_manager_provider.dart'; import 'package:spotube/provider/download_manager_provider.dart';
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
import 'package:spotube/provider/user_preferences/user_preferences_state.dart'; import 'package:spotube/provider/user_preferences/user_preferences_state.dart';
import 'package:spotube/utils/service_utils.dart';
final navigationPanelHeight = StateProvider<double>((ref) => 50); final navigationPanelHeight = StateProvider<double>((ref) => 50);
class SpotubeNavigationBar extends HookConsumerWidget { class SpotubeNavigationBar extends HookConsumerWidget {
final int? selectedIndex;
final void Function(int) onSelectedIndexChanged;
const SpotubeNavigationBar({ const SpotubeNavigationBar({
required this.selectedIndex,
required this.onSelectedIndexChanged,
super.key, super.key,
}); });
@override @override
Widget build(BuildContext context, ref) { Widget build(BuildContext context, ref) {
final theme = Theme.of(context); final theme = Theme.of(context);
final routerState = GoRouterState.of(context);
final downloadCount = ref.watch(downloadManagerProvider).$downloadCount; final downloadCount = ref.watch(downloadManagerProvider).$downloadCount;
final mediaQuery = MediaQuery.of(context); final mediaQuery = MediaQuery.of(context);
final layoutMode = final layoutMode =
ref.watch(userPreferencesProvider.select((s) => s.layoutMode)); ref.watch(userPreferencesProvider.select((s) => s.layoutMode));
final insideSelectedIndex = useState<int>(selectedIndex ?? 0);
final buttonColor = useBrightnessValue( final buttonColor = useBrightnessValue(
theme.colorScheme.inversePrimary, theme.colorScheme.inversePrimary,
theme.colorScheme.primary.withOpacity(0.2), theme.colorScheme.primary.withOpacity(0.2),
@ -46,12 +42,9 @@ class SpotubeNavigationBar extends HookConsumerWidget {
final panelHeight = ref.watch(navigationPanelHeight); final panelHeight = ref.watch(navigationPanelHeight);
useEffect(() { final selectedIndex = navbarTileList.indexWhere(
if (selectedIndex != null) { (e) => routerState.namedLocation(e.name) == routerState.matchedLocation,
insideSelectedIndex.value = selectedIndex!; );
}
return null;
}, [selectedIndex]);
if (layoutMode == LayoutMode.extended || if (layoutMode == LayoutMode.extended ||
(mediaQuery.mdAndUp && layoutMode == LayoutMode.adaptive) || (mediaQuery.mdAndUp && layoutMode == LayoutMode.adaptive) ||
@ -91,14 +84,9 @@ class SpotubeNavigationBar extends HookConsumerWidget {
}); });
}, },
).toList(), ).toList(),
index: insideSelectedIndex.value, index: selectedIndex,
onTap: (i) { onTap: (i) {
insideSelectedIndex.value = i; ServiceUtils.navigateNamed(context, navbarTileList[i].name);
if (navbarTileList[i].id == "settings") {
Sidebar.goToSettings(context);
return;
}
onSelectedIndexChanged(i);
}, },
), ),
), ),

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/context.dart';
import 'package:spotube/pages/settings/settings.dart';
import 'package:spotube/provider/authentication_provider.dart'; import 'package:spotube/provider/authentication_provider.dart';
import 'package:spotube/utils/service_utils.dart'; import 'package:spotube/utils/service_utils.dart';
@ -25,7 +26,7 @@ class AnonymousFallback extends ConsumerWidget {
const SizedBox(height: 10), const SizedBox(height: 10),
FilledButton( FilledButton(
child: Text(context.l10n.login_with_spotify), child: Text(context.l10n.login_with_spotify),
onPressed: () => ServiceUtils.push(context, "/settings"), onPressed: () => ServiceUtils.pushNamed(context, SettingsPage.name),
) )
], ],
), ),

View File

@ -1,6 +1,7 @@
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:spotify/spotify.dart'; import 'package:spotify/spotify.dart';
import 'package:spotube/components/shared/links/anchor_button.dart'; import 'package:spotube/components/shared/links/anchor_button.dart';
import 'package:spotube/pages/artist/artist.dart';
import 'package:spotube/utils/service_utils.dart'; import 'package:spotube/utils/service_utils.dart';
class ArtistLink extends StatelessWidget { class ArtistLink extends StatelessWidget {
@ -40,9 +41,12 @@ class ArtistLink extends StatelessWidget {
if (onRouteChange != null) { if (onRouteChange != null) {
onRouteChange?.call("/artist/${artist.value.id}"); onRouteChange?.call("/artist/${artist.value.id}");
} else { } else {
ServiceUtils.push( ServiceUtils.pushNamed(
context, context,
"/artist/${artist.value.id}", ArtistPage.name,
pathParameters: {
"id": artist.value.id!,
},
); );
} }
}, },

View File

@ -320,5 +320,6 @@
"select": "Select", "select": "Select",
"connect_client_alert": "You're being controlled by {client}", "connect_client_alert": "You're being controlled by {client}",
"this_device": "This Device", "this_device": "This Device",
"remote": "Remote" "remote": "Remote",
"stats": "Stats"
} }

View File

@ -8,6 +8,8 @@ import 'package:spotube/extensions/image.dart';
import 'package:spotube/provider/spotify/spotify.dart'; import 'package:spotube/provider/spotify/spotify.dart';
class AlbumPage extends HookConsumerWidget { class AlbumPage extends HookConsumerWidget {
static const name = "album";
final AlbumSimple album; final AlbumSimple album;
const AlbumPage({ const AlbumPage({
super.key, super.key,

View File

@ -15,6 +15,8 @@ import 'package:spotube/pages/artist/section/top_tracks.dart';
import 'package:spotube/provider/spotify/spotify.dart'; import 'package:spotube/provider/spotify/spotify.dart';
class ArtistPage extends HookConsumerWidget { class ArtistPage extends HookConsumerWidget {
static const name = "artist";
final String artistId; final String artistId;
final logger = getLogger(ArtistPage); final logger = getLogger(ArtistPage);
ArtistPage(this.artistId, {super.key}); ArtistPage(this.artistId, {super.key});

View File

@ -5,10 +5,13 @@ import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/components/connect/local_devices.dart'; import 'package:spotube/components/connect/local_devices.dart';
import 'package:spotube/components/shared/page_window_title_bar.dart'; import 'package:spotube/components/shared/page_window_title_bar.dart';
import 'package:spotube/extensions/context.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/provider/connect/clients.dart';
import 'package:spotube/utils/service_utils.dart'; import 'package:spotube/utils/service_utils.dart';
class ConnectPage extends HookConsumerWidget { class ConnectPage extends HookConsumerWidget {
static const name = "connect";
const ConnectPage({super.key}); const ConnectPage({super.key});
@override @override
@ -65,9 +68,9 @@ class ConnectPage extends HookConsumerWidget {
selected: selected, selected: selected,
onTap: () { onTap: () {
if (selected) { if (selected) {
ServiceUtils.push( ServiceUtils.pushNamed(
context, context,
"/connect/control", ConnectControlPage.name,
); );
} else { } else {
connectClientsNotifier.resolveService(device); connectClientsNotifier.resolveService(device);

View File

@ -13,6 +13,7 @@ import 'package:spotube/extensions/constrains.dart';
import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/context.dart';
import 'package:spotube/extensions/duration.dart'; import 'package:spotube/extensions/duration.dart';
import 'package:spotube/extensions/image.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/clients.dart';
import 'package:spotube/provider/connect/connect.dart'; import 'package:spotube/provider/connect/connect.dart';
import 'package:spotube/services/audio_player/loop_mode.dart'; import 'package:spotube/services/audio_player/loop_mode.dart';
@ -46,6 +47,8 @@ class RemotePlayerQueue extends ConsumerWidget {
} }
class ConnectControlPage extends HookConsumerWidget { class ConnectControlPage extends HookConsumerWidget {
static const name = "connect_control";
const ConnectControlPage({super.key}); const ConnectControlPage({super.key});
@override @override
@ -125,9 +128,13 @@ class ConnectControlPage extends HookConsumerWidget {
playlist.activeTrack?.name ?? "", playlist.activeTrack?.name ?? "",
style: textTheme.titleLarge!, style: textTheme.titleLarge!,
onTap: () { onTap: () {
ServiceUtils.push( if (playlist.activeTrack == null) return;
ServiceUtils.pushNamed(
context, context,
"/track/${playlist.activeTrack?.id}", TrackPage.name,
pathParameters: {
"id": playlist.activeTrack!.id!,
},
); );
}, },
), ),

View File

@ -7,8 +7,10 @@ import 'package:spotube/components/desktop_login/login_form.dart';
import 'package:spotube/components/shared/page_window_title_bar.dart'; import 'package:spotube/components/shared/page_window_title_bar.dart';
import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/constrains.dart';
import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/context.dart';
import 'package:spotube/pages/mobile_login/mobile_login.dart';
class DesktopLoginPage extends HookConsumerWidget { class DesktopLoginPage extends HookConsumerWidget {
static const name = WebViewLogin.name;
const DesktopLoginPage({super.key}); const DesktopLoginPage({super.key});
@override @override

View File

@ -8,10 +8,12 @@ import 'package:spotube/components/desktop_login/login_form.dart';
import 'package:spotube/components/shared/links/hyper_link.dart'; import 'package:spotube/components/shared/links/hyper_link.dart';
import 'package:spotube/components/shared/page_window_title_bar.dart'; import 'package:spotube/components/shared/page_window_title_bar.dart';
import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/context.dart';
import 'package:spotube/pages/home/home.dart';
import 'package:spotube/provider/authentication_provider.dart'; import 'package:spotube/provider/authentication_provider.dart';
import 'package:spotube/utils/service_utils.dart'; import 'package:spotube/utils/service_utils.dart';
class LoginTutorial extends ConsumerWidget { class LoginTutorial extends ConsumerWidget {
static const name = "login_tutorial";
const LoginTutorial({super.key}); const LoginTutorial({super.key});
@override @override
@ -53,7 +55,7 @@ class LoginTutorial extends ConsumerWidget {
overrideDone: FilledButton( overrideDone: FilledButton(
onPressed: authenticationNotifier.isLoggedIn onPressed: authenticationNotifier.isLoggedIn
? () { ? () {
ServiceUtils.push(context, "/"); ServiceUtils.pushNamed(context, HomePage.name);
} }
: null, : null,
child: Center(child: Text(context.l10n.done)), child: Center(child: Text(context.l10n.done)),

View File

@ -12,6 +12,8 @@ import 'package:spotube/provider/user_preferences/user_preferences_provider.dart
import 'package:spotube/themes/theme.dart'; import 'package:spotube/themes/theme.dart';
class GettingStarting extends HookConsumerWidget { class GettingStarting extends HookConsumerWidget {
static const name = "getting_started";
const GettingStarting({super.key}); const GettingStarting({super.key});
@override @override

View File

@ -5,6 +5,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/components/getting_started/blur_card.dart'; import 'package:spotube/components/getting_started/blur_card.dart';
import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/context.dart';
import 'package:spotube/pages/mobile_login/mobile_login.dart';
import 'package:spotube/services/kv_store/kv_store.dart'; import 'package:spotube/services/kv_store/kv_store.dart';
import 'package:url_launcher/url_launcher_string.dart'; import 'package:url_launcher/url_launcher_string.dart';
@ -120,7 +121,7 @@ class GettingStartedScreenSupportSection extends HookConsumerWidget {
onPressed: () async { onPressed: () async {
await KVStoreService.setDoneGettingStarted(true); await KVStoreService.setDoneGettingStarted(true);
if (context.mounted) { if (context.mounted) {
context.push("/login"); context.pushNamed(WebViewLogin.name);
} }
}, },
), ),

View File

@ -10,6 +10,8 @@ import 'package:spotube/extensions/constrains.dart';
import 'package:spotube/provider/spotify/views/home_section.dart'; import 'package:spotube/provider/spotify/views/home_section.dart';
class HomeFeedSectionPage extends HookConsumerWidget { class HomeFeedSectionPage extends HookConsumerWidget {
static const name = "home_feed_section";
final String sectionUri; final String sectionUri;
const HomeFeedSectionPage({super.key, required this.sectionUri}); const HomeFeedSectionPage({super.key, required this.sectionUri});

View File

@ -15,6 +15,8 @@ import 'package:collection/collection.dart';
import 'package:spotube/utils/platform.dart'; import 'package:spotube/utils/platform.dart';
class GenrePlaylistsPage extends HookConsumerWidget { class GenrePlaylistsPage extends HookConsumerWidget {
static const name = "genre_playlists";
final Category category; final Category category;
const GenrePlaylistsPage({super.key, required this.category}); const GenrePlaylistsPage({super.key, required this.category});

View File

@ -9,9 +9,11 @@ import 'package:spotube/collections/gradients.dart';
import 'package:spotube/components/shared/page_window_title_bar.dart'; import 'package:spotube/components/shared/page_window_title_bar.dart';
import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/constrains.dart';
import 'package:spotube/extensions/context.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:spotube/provider/spotify/spotify.dart';
class GenrePage extends HookConsumerWidget { class GenrePage extends HookConsumerWidget {
static const name = "genre";
const GenrePage({super.key}); const GenrePage({super.key});
@override @override
@ -47,7 +49,13 @@ class GenrePage extends HookConsumerWidget {
return InkWell( return InkWell(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
onTap: () { onTap: () {
context.push("/genre/${category.id}", extra: category); context.pushNamed(
GenrePlaylistsPage.name,
pathParameters: {
"categoryId": category.id!,
},
extra: category,
);
}, },
child: Ink( child: Ink(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),

View File

@ -3,6 +3,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:gap/gap.dart'; import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotube/collections/assets.gen.dart'; import 'package:spotube/collections/assets.gen.dart';
import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/components/connect/connect_device.dart'; import 'package:spotube/components/connect/connect_device.dart';
import 'package:spotube/components/home/sections/featured.dart'; import 'package:spotube/components/home/sections/featured.dart';
import 'package:spotube/components/home/sections/feed.dart'; import 'package:spotube/components/home/sections/feed.dart';
@ -15,12 +16,15 @@ import 'package:spotube/components/shared/image/universal_image.dart';
import 'package:spotube/components/shared/page_window_title_bar.dart'; import 'package:spotube/components/shared/page_window_title_bar.dart';
import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/constrains.dart';
import 'package:spotube/extensions/image.dart'; import 'package:spotube/extensions/image.dart';
import 'package:spotube/pages/profile/profile.dart';
import 'package:spotube/pages/search/search.dart';
import 'package:spotube/provider/authentication_provider.dart'; import 'package:spotube/provider/authentication_provider.dart';
import 'package:spotube/provider/spotify/spotify.dart'; import 'package:spotube/provider/spotify/spotify.dart';
import 'package:spotube/utils/platform.dart'; import 'package:spotube/utils/platform.dart';
import 'package:spotube/utils/service_utils.dart'; import 'package:spotube/utils/service_utils.dart';
class HomePage extends HookConsumerWidget { class HomePage extends HookConsumerWidget {
static const name = "home";
const HomePage({super.key}); const HomePage({super.key});
@override @override
@ -63,11 +67,18 @@ class HomePage extends HookConsumerWidget {
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
), ),
onPressed: () { onPressed: () {
ServiceUtils.push(context, "/profile"); ServiceUtils.pushNamed(context, ProfilePage.name);
}, },
); );
}), }),
const Gap(10), const Gap(10),
IconButton(
icon: const Icon(SpotubeIcons.search),
onPressed: () {
ServiceUtils.pushNamed(context, SearchPage.name);
},
),
const Gap(10),
], ],
) )
else if (kIsMacOS) else if (kIsMacOS)

View File

@ -10,6 +10,7 @@ import 'package:spotube/extensions/context.dart';
import 'package:spotube/provider/scrobbler_provider.dart'; import 'package:spotube/provider/scrobbler_provider.dart';
class LastFMLoginPage extends HookConsumerWidget { class LastFMLoginPage extends HookConsumerWidget {
static const name = "lastfm_login";
const LastFMLoginPage({super.key}); const LastFMLoginPage({super.key});
@override @override

View File

@ -12,6 +12,8 @@ import 'package:spotube/extensions/context.dart';
import 'package:spotube/provider/download_manager_provider.dart'; import 'package:spotube/provider/download_manager_provider.dart';
class LibraryPage extends HookConsumerWidget { class LibraryPage extends HookConsumerWidget {
static const name = "library";
const LibraryPage({super.key}); const LibraryPage({super.key});
@override @override
Widget build(BuildContext context, ref) { Widget build(BuildContext context, ref) {

View File

@ -24,6 +24,8 @@ import 'package:spotube/provider/user_preferences/user_preferences_provider.dart
const RecommendationAttribute zeroValues = (min: 0, target: 0, max: 0); const RecommendationAttribute zeroValues = (min: 0, target: 0, max: 0);
class PlaylistGeneratorPage extends HookConsumerWidget { class PlaylistGeneratorPage extends HookConsumerWidget {
static const name = "playlist_generator";
const PlaylistGeneratorPage({super.key}); const PlaylistGeneratorPage({super.key});
@override @override

View File

@ -10,10 +10,13 @@ import 'package:spotube/components/shared/dialogs/playlist_add_track_dialog.dart
import 'package:spotube/components/shared/page_window_title_bar.dart'; import 'package:spotube/components/shared/page_window_title_bar.dart';
import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/context.dart';
import 'package:spotube/models/spotify/recommendation_seeds.dart'; import 'package:spotube/models/spotify/recommendation_seeds.dart';
import 'package:spotube/pages/playlist/playlist.dart';
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
import 'package:spotube/provider/spotify/spotify.dart'; import 'package:spotube/provider/spotify/spotify.dart';
class PlaylistGenerateResultPage extends HookConsumerWidget { class PlaylistGenerateResultPage extends HookConsumerWidget {
static const name = "playlist_generate_result";
final GeneratePlaylistProviderInput state; final GeneratePlaylistProviderInput state;
const PlaylistGenerateResultPage({ const PlaylistGenerateResultPage({
@ -123,8 +126,11 @@ class PlaylistGenerateResultPage extends HookConsumerWidget {
); );
if (playlist != null) { if (playlist != null) {
router.go( router.goNamed(
'/playlist/${playlist.id}', PlaylistPage.name,
pathParameters: {
"id": playlist.id!,
},
extra: playlist, extra: playlist,
); );
} }

View File

@ -23,6 +23,8 @@ import 'package:spotube/utils/platform.dart';
import 'package:spotube/provider/spotify/spotify.dart'; import 'package:spotube/provider/spotify/spotify.dart';
class LyricsPage extends HookConsumerWidget { class LyricsPage extends HookConsumerWidget {
static const name = "lyrics";
final bool isModal; final bool isModal;
const LyricsPage({super.key, this.isModal = false}); const LyricsPage({super.key, this.isModal = false});

View File

@ -20,6 +20,8 @@ import 'package:spotube/utils/platform.dart';
import 'package:window_manager/window_manager.dart'; import 'package:window_manager/window_manager.dart';
class MiniLyricsPage extends HookConsumerWidget { class MiniLyricsPage extends HookConsumerWidget {
static const name = "mini_lyrics";
final Size prevSize; final Size prevSize;
const MiniLyricsPage({super.key, required this.prevSize}); const MiniLyricsPage({super.key, required this.prevSize});

View File

@ -7,6 +7,7 @@ import 'package:spotube/provider/authentication_provider.dart';
import 'package:spotube/utils/platform.dart'; import 'package:spotube/utils/platform.dart';
class WebViewLogin extends HookConsumerWidget { class WebViewLogin extends HookConsumerWidget {
static const name = "login";
const WebViewLogin({super.key}); const WebViewLogin({super.key});
@override @override

View File

@ -3,9 +3,12 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotify/spotify.dart'; import 'package:spotify/spotify.dart';
import 'package:spotube/components/shared/tracks_view/track_view.dart'; import 'package:spotube/components/shared/tracks_view/track_view.dart';
import 'package:spotube/components/shared/tracks_view/track_view_props.dart'; import 'package:spotube/components/shared/tracks_view/track_view_props.dart';
import 'package:spotube/pages/playlist/playlist.dart';
import 'package:spotube/provider/spotify/spotify.dart'; import 'package:spotube/provider/spotify/spotify.dart';
class LikedPlaylistPage extends HookConsumerWidget { class LikedPlaylistPage extends HookConsumerWidget {
static const name = PlaylistPage.name;
final PlaylistSimple playlist; final PlaylistSimple playlist;
const LikedPlaylistPage({ const LikedPlaylistPage({
super.key, super.key,

View File

@ -10,6 +10,8 @@ import 'package:spotube/extensions/image.dart';
import 'package:spotube/provider/spotify/spotify.dart'; import 'package:spotube/provider/spotify/spotify.dart';
class PlaylistPage extends HookConsumerWidget { class PlaylistPage extends HookConsumerWidget {
static const name = "playlist";
final PlaylistSimple playlist; final PlaylistSimple playlist;
const PlaylistPage({ const PlaylistPage({
super.key, super.key,

View File

@ -14,6 +14,8 @@ import 'package:spotube/provider/spotify/spotify.dart';
import 'package:url_launcher/url_launcher_string.dart'; import 'package:url_launcher/url_launcher_string.dart';
class ProfilePage extends HookConsumerWidget { class ProfilePage extends HookConsumerWidget {
static const name = "profile";
const ProfilePage({super.key}); const ProfilePage({super.key});
@override @override

View File

@ -22,13 +22,6 @@ import 'package:spotube/services/connectivity_adapter.dart';
import 'package:spotube/utils/persisted_state_notifier.dart'; import 'package:spotube/utils/persisted_state_notifier.dart';
import 'package:spotube/utils/platform.dart'; import 'package:spotube/utils/platform.dart';
const rootPaths = {
"/": 0,
"/search": 1,
"/library": 2,
"/lyrics": 3,
};
class RootApp extends HookConsumerWidget { class RootApp extends HookConsumerWidget {
final Widget child; final Widget child;
const RootApp({ const RootApp({
@ -42,7 +35,6 @@ class RootApp extends HookConsumerWidget {
final downloader = ref.watch(downloadManagerProvider); final downloader = ref.watch(downloadManagerProvider);
final scaffoldMessenger = ScaffoldMessenger.of(context); final scaffoldMessenger = ScaffoldMessenger.of(context);
final theme = Theme.of(context); final theme = Theme.of(context);
final location = GoRouterState.of(context).matchedLocation;
useEffect(() { useEffect(() {
WidgetsBinding.instance.addPostFrameCallback((_) async { WidgetsBinding.instance.addPostFrameCallback((_) async {
@ -178,32 +170,17 @@ class RootApp extends HookConsumerWidget {
return null; return null;
}, [backgroundColor]); }, [backgroundColor]);
void onSelectIndexChanged(int d) {
final invertedRouteMap =
rootPaths.map((key, value) => MapEntry(value, key));
if (context.mounted) {
WidgetsBinding.instance.addPostFrameCallback((_) {
GoRouter.of(context).go(invertedRouteMap[d]!);
});
}
}
// ignore: deprecated_member_use // ignore: deprecated_member_use
return WillPopScope( return WillPopScope(
onWillPop: () async { onWillPop: () async {
if (rootPaths[location] != 0) { // if (rootPaths[location] != 0) {
onSelectIndexChanged(0); // onSelectIndexChanged(0);
return false; // return false;
} // }
return true; return true;
}, },
child: Scaffold( child: Scaffold(
body: Sidebar( body: Sidebar(child: child),
selectedIndex: rootPaths[location],
onSelectedIndexChanged: onSelectIndexChanged,
child: child,
),
extendBody: true, extendBody: true,
drawerScrimColor: Colors.transparent, drawerScrimColor: Colors.transparent,
endDrawer: kIsDesktop endDrawer: kIsDesktop
@ -237,10 +214,7 @@ class RootApp extends HookConsumerWidget {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
BottomPlayer(), BottomPlayer(),
SpotubeNavigationBar( const SpotubeNavigationBar(),
selectedIndex: rootPaths[location],
onSelectedIndexChanged: onSelectIndexChanged,
),
], ],
), ),
), ),

View File

@ -5,6 +5,7 @@ import 'package:flutter/material.dart' hide Page;
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:fuzzywuzzy/fuzzywuzzy.dart'; import 'package:fuzzywuzzy/fuzzywuzzy.dart';
import 'package:gap/gap.dart'; import 'package:gap/gap.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotify/spotify.dart'; import 'package:spotify/spotify.dart';
@ -26,6 +27,8 @@ import 'package:spotube/services/kv_store/kv_store.dart';
import 'package:spotube/utils/platform.dart'; import 'package:spotube/utils/platform.dart';
class SearchPage extends HookConsumerWidget { class SearchPage extends HookConsumerWidget {
static const name = "search";
const SearchPage({super.key}); const SearchPage({super.key});
@override @override
@ -85,99 +88,117 @@ class SearchPage extends HookConsumerWidget {
return SafeArea( return SafeArea(
bottom: false, bottom: false,
child: Scaffold( child: Scaffold(
appBar: kIsDesktop && !kIsMacOS ? const PageWindowTitleBar() : null, appBar: kIsDesktop && !kIsMacOS
? const PageWindowTitleBar(automaticallyImplyLeading: true)
: null,
body: !authenticationNotifier.isLoggedIn body: !authenticationNotifier.isLoggedIn
? const AnonymousFallback() ? const AnonymousFallback()
: Column( : Column(
children: [ children: [
Container( Row(
padding: const EdgeInsets.symmetric( crossAxisAlignment: CrossAxisAlignment.center,
horizontal: 20, children: [
vertical: 10, if ((kIsMobile || kIsMacOS) && context.canPop())
), const BackButton()
color: theme.scaffoldBackgroundColor, else
child: SearchAnchor( const Gap(20),
searchController: controller, Expanded(
viewBuilder: (_) => HookBuilder(builder: (context) { child: Padding(
final searchController = useListenable(controller); padding: const EdgeInsets.only(
final update = useForceUpdate(); right: 20,
final suggestions = searchController.text.isEmpty top: 20,
? KVStoreService.recentSearches bottom: 20,
: KVStoreService.recentSearches ),
.where( child: SearchAnchor(
(s) => searchController: controller,
weightedRatio( viewBuilder: (_) => HookBuilder(builder: (context) {
s.toLowerCase(), final searchController =
searchController.text.toLowerCase(), useListenable(controller);
) > final update = useForceUpdate();
50, final suggestions = searchController.text.isEmpty
) ? KVStoreService.recentSearches
.toList(); : KVStoreService.recentSearches
.where(
(s) =>
weightedRatio(
s.toLowerCase(),
searchController.text
.toLowerCase(),
) >
50,
)
.toList();
return ListView.builder( return ListView.builder(
itemCount: suggestions.length, itemCount: suggestions.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final suggestion = suggestions[index]; final suggestion = suggestions[index];
return ListTile( return ListTile(
leading: const Icon(SpotubeIcons.history), leading: const Icon(SpotubeIcons.history),
title: Text(suggestion), title: Text(suggestion),
trailing: IconButton( trailing: IconButton(
icon: const Icon(SpotubeIcons.trash), icon: const Icon(SpotubeIcons.trash),
onPressed: () { onPressed: () {
KVStoreService.setRecentSearches( KVStoreService.setRecentSearches(
KVStoreService.recentSearches KVStoreService.recentSearches
.where((s) => s != suggestion) .where((s) => s != suggestion)
.toList(), .toList(),
);
update();
},
),
onTap: () {
controller.closeView(suggestion);
ref
.read(
searchTermStateProvider.notifier)
.state = suggestion;
},
); );
update();
}, },
), );
onTap: () { }),
controller.closeView(suggestion); suggestionsBuilder: (context, controller) {
ref return [];
.read(searchTermStateProvider.notifier) },
.state = suggestion; viewOnSubmitted: (value) async {
}, controller.closeView(value);
); Timer(
}, const Duration(milliseconds: 50),
); () {
}), ref
suggestionsBuilder: (context, controller) { .read(searchTermStateProvider.notifier)
return []; .state = value;
}, if (value.trim().isEmpty) {
viewOnSubmitted: (value) async { return;
controller.closeView(value); }
Timer( KVStoreService.setRecentSearches(
const Duration(milliseconds: 50), {
() { value,
ref.read(searchTermStateProvider.notifier).state = ...KVStoreService.recentSearches,
value; }.toList(),
if (value.trim().isEmpty) { );
return; },
} );
KVStoreService.setRecentSearches( },
{ builder: (context, controller) {
value, return SearchBar(
...KVStoreService.recentSearches, autoFocus: queries.none((s) =>
}.toList(), s.asData?.value != null &&
); !s.hasError) &&
}, !kIsMobile,
); controller: controller,
}, leading: const Icon(SpotubeIcons.search),
builder: (context, controller) { hintText: "${context.l10n.search}...",
return SearchBar( onTap: controller.openView,
autoFocus: queries.none((s) => onChanged: (_) => controller.openView(),
s.asData?.value != null && !s.hasError) && );
!kIsMobile, },
controller: controller, ),
leading: const Icon(SpotubeIcons.search), ),
hintText: "${context.l10n.search}...", ),
onTap: controller.openView, ],
onChanged: (_) => controller.openView(),
);
},
),
), ),
Expanded( Expanded(
child: AnimatedSwitcher( child: AnimatedSwitcher(

View File

@ -16,6 +16,8 @@ final _licenseProvider = FutureProvider<String>((ref) async {
}); });
class AboutSpotube extends HookConsumerWidget { class AboutSpotube extends HookConsumerWidget {
static const name = "about";
const AboutSpotube({super.key}); const AboutSpotube({super.key});
@override @override

View File

@ -11,6 +11,8 @@ import 'package:spotube/extensions/context.dart';
import 'package:spotube/provider/blacklist_provider.dart'; import 'package:spotube/provider/blacklist_provider.dart';
class BlackListPage extends HookConsumerWidget { class BlackListPage extends HookConsumerWidget {
static const name = "blacklist";
const BlackListPage({super.key}); const BlackListPage({super.key});
@override @override

View File

@ -11,6 +11,8 @@ import 'package:spotube/extensions/context.dart';
import 'package:spotube/models/logger.dart'; import 'package:spotube/models/logger.dart';
class LogsPage extends HookWidget { class LogsPage extends HookWidget {
static const name = "logs";
const LogsPage({super.key}); const LogsPage({super.key});
List<({DateTime? date, String body})> parseLogs(String raw) { List<({DateTime? date, String body})> parseLogs(String raw) {

View File

@ -16,6 +16,8 @@ import 'package:spotube/provider/user_preferences/user_preferences_provider.dart
import 'package:spotube/utils/platform.dart'; import 'package:spotube/utils/platform.dart';
class SettingsPage extends HookConsumerWidget { class SettingsPage extends HookConsumerWidget {
static const name = "settings";
const SettingsPage({super.key}); const SettingsPage({super.key});
@override @override

View File

@ -0,0 +1,13 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
class StatsPage extends HookConsumerWidget {
static const name = "stats";
const StatsPage({super.key});
@override
Widget build(BuildContext context, ref) {
return Container();
}
}

View File

@ -21,6 +21,8 @@ import 'package:spotube/services/audio_player/audio_player.dart';
import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/constrains.dart';
class TrackPage extends HookConsumerWidget { class TrackPage extends HookConsumerWidget {
static const name = "track";
final String trackId; final String trackId;
const TrackPage({ const TrackPage({
super.key, super.key,

View File

@ -262,6 +262,22 @@ abstract class ServiceUtils {
GoRouter.of(context).go(location, extra: extra); GoRouter.of(context).go(location, extra: extra);
} }
static void navigateNamed(
BuildContext context,
String name, {
Object? extra,
Map<String, String>? pathParameters,
Map<String, dynamic>? 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}) { static void push(BuildContext context, String location, {Object? extra}) {
final router = GoRouter.of(context); final router = GoRouter.of(context);
final routerState = GoRouterState.of(context); final routerState = GoRouterState.of(context);
@ -273,6 +289,36 @@ abstract class ServiceUtils {
router.push(location, extra: extra); router.push(location, extra: extra);
} }
static void pushNamed(
BuildContext context,
String name, {
Object? extra,
Map<String, String> pathParameters = const {},
Map<String, String> 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) { static DateTime parseSpotifyAlbumDate(AlbumSimple? album) {
if (album == null || album.releaseDate == null) { if (album == null || album.releaseDate == null) {
return DateTime.parse("1975-01-01"); return DateTime.parse("1975-01-01");

View File

@ -1 +1,89 @@
{} {
"ar": [
"stats"
],
"bn": [
"stats"
],
"ca": [
"stats"
],
"cs": [
"stats"
],
"de": [
"stats"
],
"es": [
"stats"
],
"fa": [
"stats"
],
"fr": [
"stats"
],
"hi": [
"stats"
],
"it": [
"stats"
],
"ja": [
"stats"
],
"ko": [
"stats"
],
"ne": [
"stats"
],
"nl": [
"stats"
],
"pl": [
"stats"
],
"pt": [
"stats"
],
"ru": [
"stats"
],
"th": [
"stats"
],
"tr": [
"stats"
],
"uk": [
"stats"
],
"vi": [
"stats"
],
"zh": [
"stats"
]
}