refactor: use expanded sidebar tiles for library

This commit is contained in:
Kingkor Roy Tirtho 2025-01-23 20:34:04 +06:00
parent 2411f46877
commit 6e357230ac
21 changed files with 403 additions and 240 deletions

11
.vscode/launch.json vendored
View File

@ -30,6 +30,17 @@
"request": "launch",
"program": "lib/main.dart",
"flutterMode": "release"
},
{
"name": "spotube (mobile) (release)",
"type": "dart",
"request": "launch",
"program": "lib/main.dart",
"flutterMode": "release",
"args": [
"--flavor",
"dev"
]
}
],
"compounds": []

View File

@ -7,7 +7,11 @@ import 'package:go_router/go_router.dart';
import 'package:spotube/collections/routes.dart';
import 'package:spotube/modules/player/player_controls.dart';
import 'package:spotube/pages/home/home.dart';
import 'package:spotube/pages/library/library.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.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';
@ -52,8 +56,13 @@ class NavigationAction extends Action<NavigationIntent> {
enum HomeTabs {
browse,
search,
library,
lyrics,
userPlaylists,
userArtists,
userAlbums,
userLocalLibrary,
userDownloads,
}
class HomeTabIntent extends Intent {
@ -73,12 +82,24 @@ class HomeTabAction extends Action<HomeTabIntent> {
case HomeTabs.search:
router.goNamed(SearchPage.name);
break;
case HomeTabs.library:
router.goNamed(LibraryPage.name);
break;
case HomeTabs.lyrics:
router.goNamed(LyricsPage.name);
break;
case HomeTabs.userPlaylists:
router.goNamed(UserPlaylistsPage.name);
break;
case HomeTabs.userArtists:
router.goNamed(UserArtistsPage.name);
break;
case HomeTabs.userAlbums:
router.goNamed(UserAlbumsPage.name);
break;
case HomeTabs.userLocalLibrary:
router.goNamed(UserLocalLibraryPage.name);
break;
case HomeTabs.userDownloads:
router.goNamed(UserDownloadsPage.name);
break;
}
return null;
}

View File

@ -16,6 +16,11 @@ import 'package:spotube/pages/lastfm_login/lastfm_login.dart';
import 'package:spotube/pages/library/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.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';
@ -99,45 +104,73 @@ final routerProvider = Provider((ref) {
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()),
),
GoRoute(
path: "/library/downloads",
name: UserDownloadsPage.name,
pageBuilder: (context, state) =>
const SpotubePage(child: UserDownloadsPage()),
),
],
),
GoRoute(
path: "/library",
name: LibraryPage.name,
pageBuilder: (context, state) =>
const SpotubePage(child: LibraryPage()),
routes: [
GoRoute(
path: "generate",
name: PlaylistGeneratorPage.name,
pageBuilder: (context, state) =>
const SpotubePage(child: PlaylistGeneratorPage()),
routes: [
GoRoute(
path: "result",
name: PlaylistGenerateResultPage.name,
pageBuilder: (context, state) => SpotubePage(
child: PlaylistGenerateResultPage(
state: state.extra as GeneratePlaylistProviderInput,
),
),
)
],
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: "local",
name: LocalLibraryPage.name,
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/local",
name: LocalLibraryPage.name,
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: "/lyrics",
name: LyricsPage.name,

View File

@ -2,7 +2,10 @@ import 'package:flutter/material.dart';
import 'package:spotube/collections/spotube_icons.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:spotube/pages/home/home.dart';
import 'package:spotube/pages/library/library.dart';
import 'package:spotube/pages/library/user_albums.dart';
import 'package:spotube/pages/library/user_artists.dart';
import 'package:spotube/pages/library/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';
@ -34,12 +37,6 @@ List<SideBarTiles> getSidebarTileList(AppLocalizations l10n) => [
icon: SpotubeIcons.search,
title: l10n.search,
),
SideBarTiles(
id: "library",
name: LibraryPage.name,
icon: SpotubeIcons.library,
title: l10n.library,
),
SideBarTiles(
id: "lyrics",
name: LyricsPage.name,
@ -54,6 +51,33 @@ List<SideBarTiles> getSidebarTileList(AppLocalizations l10n) => [
),
];
List<SideBarTiles> getSidebarLibraryTileList(AppLocalizations l10n) => [
SideBarTiles(
id: "playlists",
title: l10n.playlists,
name: UserPlaylistsPage.name,
icon: SpotubeIcons.playlist,
),
SideBarTiles(
id: "artists",
title: l10n.artists,
name: UserArtistsPage.name,
icon: SpotubeIcons.artist,
),
SideBarTiles(
id: "albums",
title: l10n.albums,
name: UserAlbumsPage.name,
icon: SpotubeIcons.album,
),
SideBarTiles(
id: "local_library",
title: l10n.local_library,
name: UserLocalLibraryPage.name,
icon: SpotubeIcons.device,
),
];
List<SideBarTiles> getNavbarTileList(AppLocalizations l10n) => [
SideBarTiles(
id: "browse",
@ -69,7 +93,7 @@ List<SideBarTiles> getNavbarTileList(AppLocalizations l10n) => [
),
SideBarTiles(
id: "library",
name: LibraryPage.name,
name: UserPlaylistsPage.name,
icon: SpotubeIcons.library,
title: l10n.library,
),

View File

@ -37,6 +37,7 @@ abstract class SpotubeIcons {
static const share = FeatherIcons.share2;
static const playlistAdd = Icons.playlist_add_rounded;
static const playlistRemove = Icons.playlist_remove_rounded;
static const playlist = Icons.playlist_play_rounded;
static const trash = FeatherIcons.trash2;
static const clock = FeatherIcons.clock;
static const lyrics = Icons.lyrics_rounded;
@ -132,4 +133,5 @@ abstract class SpotubeIcons {
static const radioUnchecked = Icons.radio_button_off_rounded;
static const grid = FeatherIcons.grid;
static const list = FeatherIcons.list;
static const device = FeatherIcons.smartphone;
}

View File

@ -2,7 +2,7 @@ import 'package:collection/collection.dart';
import 'package:fuzzywuzzy/fuzzywuzzy.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/modules/library/user_local_tracks.dart';
import 'package:spotube/pages/library/user_local_tracks.dart';
import 'package:spotube/provider/spotify/spotify.dart';
import 'package:spotube/utils/service_utils.dart';

View File

@ -1,6 +1,6 @@
import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/modules/library/user_local_tracks.dart';
import 'package:spotube/pages/library/user_local_tracks.dart';
import 'package:spotube/components/adaptive/adaptive_pop_sheet_list.dart';
import 'package:spotube/extensions/context.dart';

View File

@ -264,12 +264,32 @@ class Spotube extends HookConsumerWidget {
LogicalKeyboardKey.digit3,
LogicalKeyboardKey.control,
LogicalKeyboardKey.shift,
): HomeTabIntent(ref, tab: HomeTabs.library),
): HomeTabIntent(ref, tab: HomeTabs.lyrics),
LogicalKeySet(
LogicalKeyboardKey.digit4,
LogicalKeyboardKey.control,
LogicalKeyboardKey.shift,
): HomeTabIntent(ref, tab: HomeTabs.lyrics),
): HomeTabIntent(ref, tab: HomeTabs.userPlaylists),
LogicalKeySet(
LogicalKeyboardKey.digit5,
LogicalKeyboardKey.control,
LogicalKeyboardKey.shift,
): HomeTabIntent(ref, tab: HomeTabs.userArtists),
LogicalKeySet(
LogicalKeyboardKey.digit6,
LogicalKeyboardKey.control,
LogicalKeyboardKey.shift,
): HomeTabIntent(ref, tab: HomeTabs.userAlbums),
LogicalKeySet(
LogicalKeyboardKey.digit7,
LogicalKeyboardKey.control,
LogicalKeyboardKey.shift,
): HomeTabIntent(ref, tab: HomeTabs.userLocalLibrary),
LogicalKeySet(
LogicalKeyboardKey.digit8,
LogicalKeyboardKey.control,
LogicalKeyboardKey.shift,
): HomeTabIntent(ref, tab: HomeTabs.userDownloads),
LogicalKeySet(
LogicalKeyboardKey.keyW,
LogicalKeyboardKey.control,

View File

@ -1,6 +1,7 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shadcn_flutter/shadcn_flutter.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';
@ -19,6 +20,17 @@ class ConnectDeviceButton extends HookConsumerWidget {
connectClients.asData?.value.services.isNotEmpty == true;
if (_sidebar) {
final mediaQuery = MediaQuery.sizeOf(context);
if (mediaQuery.mdAndDown) {
return IconButton.ghost(
icon: const Icon(SpotubeIcons.speaker),
onPressed: () {
ServiceUtils.pushNamed(context, ConnectPage.name);
},
);
}
return SizedBox(
width: double.infinity,
child: Button.primary(

View File

@ -3,7 +3,6 @@ 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/side_bar_tiles.dart';
@ -14,6 +13,7 @@ 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';
@ -47,8 +47,6 @@ class Sidebar extends HookConsumerWidget {
final routerState = GoRouterState.of(context);
final mediaQuery = MediaQuery.of(context);
final downloadCount = ref.watch(downloadManagerProvider).$downloadCount;
final layoutMode =
ref.watch(userPreferencesProvider.select((s) => s.layoutMode));
@ -57,7 +55,14 @@ class Sidebar extends HookConsumerWidget {
[context.l10n],
);
final selectedIndex = sidebarTileList.indexWhere(
final sidebarLibraryTileList = useMemoized(
() => getSidebarLibraryTileList(context.l10n),
[context.l10n],
);
final tileList = [...sidebarTileList, ...sidebarLibraryTileList];
final selectedIndex = tileList.indexWhere(
(e) => routerState.namedLocation(e.name) == routerState.matchedLocation,
);
@ -73,16 +78,8 @@ class Sidebar extends HookConsumerWidget {
for (final tile in sidebarTileList)
NavigationButton(
label: mediaQuery.lgAndUp ? Text(tile.title) : null,
child: Badge(
backgroundColor: context.theme.colorScheme.primary,
isLabelVisible: tile.title == "Library" && downloadCount > 0,
label: Text(
downloadCount.toString(),
style: TextStyle(
color: context.theme.colorScheme.primaryForeground,
fontSize: 10,
),
),
child: Tooltip(
tooltip: TooltipContainer(child: Text(tile.title)),
child: Icon(tile.icon),
),
onChanged: (value) {
@ -91,6 +88,22 @@ class Sidebar extends HookConsumerWidget {
}
},
),
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(
@ -103,7 +116,7 @@ class Sidebar extends HookConsumerWidget {
? NavigationSidebar(
index: selectedIndex,
onSelected: (index) {
final tile = sidebarTileList[index];
final tile = tileList[index];
context.goNamed(tile.name);
},
children: navigationButtons,
@ -112,7 +125,7 @@ class Sidebar extends HookConsumerWidget {
alignment: NavigationRailAlignment.start,
index: selectedIndex,
onSelected: (index) {
final tile = sidebarTileList[index];
final tile = tileList[index];
context.goNamed(tile.name);
},
children: navigationButtons,
@ -138,8 +151,10 @@ class SidebarFooter extends HookConsumerWidget implements NavigationBarItem {
Widget build(BuildContext context, ref) {
final theme = Theme.of(context);
final mediaQuery = MediaQuery.of(context);
final me = ref.watch(meProvider);
final data = me.asData?.value;
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,
@ -149,10 +164,30 @@ class SidebarFooter extends HookConsumerWidget implements NavigationBarItem {
final auth = ref.watch(authenticationProvider);
if (mediaQuery.mdAndDown) {
return IconButton(
variance: ButtonVariance.ghost,
icon: const Icon(SpotubeIcons.settings),
onPressed: () => ServiceUtils.navigateNamed(context, SettingsPage.name),
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),
),
],
);
}
@ -161,9 +196,27 @@ class SidebarFooter extends HookConsumerWidget implements NavigationBarItem {
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(),
const Gap(10),
Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceBetween,

View File

@ -34,32 +34,35 @@ class GettingStarting extends HookConsumerWidget {
return Scaffold(
headers: [
TitleBar(
backgroundColor: Colors.transparent,
surfaceBlur: 0,
trailing: [
ListenableBuilder(
listenable: pageController,
builder: (context, _) {
return AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: pageController.hasClients &&
(pageController.page == 0 || pageController.page == 3)
? const SizedBox()
: Button.secondary(
onPressed: () {
pageController.animateToPage(
3,
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
},
child: Text(context.l10n.skip_this_nonsense),
),
);
},
),
],
SafeArea(
child: TitleBar(
backgroundColor: Colors.transparent,
surfaceBlur: 0,
trailing: [
ListenableBuilder(
listenable: pageController,
builder: (context, _) {
return AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: pageController.hasClients &&
(pageController.page == 0 ||
pageController.page == 3)
? const SizedBox()
: Button.secondary(
onPressed: () {
pageController.animateToPage(
3,
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
},
child: Text(context.l10n.skip_this_nonsense),
),
);
},
),
],
),
),
],
floatingHeader: true,

View File

@ -1,71 +1,63 @@
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/modules/library/user_local_tracks.dart';
import 'package:spotube/collections/side_bar_tiles.dart';
import 'package:spotube/components/titlebar/titlebar.dart';
import 'package:spotube/modules/library/user_albums.dart';
import 'package:spotube/modules/library/user_artists.dart';
import 'package:spotube/modules/library/user_downloads.dart';
import 'package:spotube/modules/library/user_playlists.dart';
import 'package:spotube/extensions/constrains.dart';
import 'package:spotube/extensions/context.dart';
import 'package:spotube/provider/download_manager_provider.dart';
class LibraryPage extends HookConsumerWidget {
static const name = "library";
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 index = useState(0);
final children = [
Text(context.l10n.playlists),
Text(context.l10n.local_tab),
Badge(
isLabelVisible: downloadingCount > 0,
label: Text(downloadingCount.toString()),
child: Text(context.l10n.downloads),
),
Text(context.l10n.artists),
Text(context.l10n.albums),
];
final routerState = GoRouterState.of(context);
final sidebarLibraryTileList = useMemoized(
() => getSidebarLibraryTileList(context.l10n),
[context.l10n],
);
final index = sidebarLibraryTileList.indexWhere(
(e) => routerState.namedLocation(e.name) == routerState.matchedLocation,
);
return SafeArea(
bottom: false,
child: Scaffold(
headers: [
TitleBar(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: TabList(
index: index.value,
children: [
for (final child in children)
TabButton(
child: child,
onPressed: () {
index.value = children.indexOf(child);
},
),
],
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),
),
onPressed: () {
context.goNamed(tile.name);
},
),
],
),
),
),
),
),
const Gap(10),
],
child: IndexedStack(
index: index.value,
children: const [
UserPlaylists(),
UserLocalTracks(),
UserDownloads(),
UserArtists(),
UserAlbums(),
const Gap(10),
],
),
),
child: child,
);
}),
);
}
}

View File

@ -16,9 +16,8 @@ import 'package:spotube/components/button/back_button.dart';
import 'package:spotube/extensions/constrains.dart';
import 'package:spotube/extensions/string.dart';
import 'package:spotube/modules/library/local_folder/cache_export_dialog.dart';
import 'package:spotube/modules/library/user_local_tracks.dart';
import 'package:spotube/pages/library/user_local_tracks.dart';
import 'package:spotube/components/expandable_search/expandable_search.dart';
import 'package:spotube/components/fallbacks/not_found.dart';
import 'package:spotube/components/inter_scrollbar/inter_scrollbar.dart';
import 'package:spotube/components/titlebar/titlebar.dart';
import 'package:spotube/components/track_presentation/sort_tracks_dropdown.dart';

View File

@ -16,8 +16,9 @@ import 'package:spotube/extensions/context.dart';
import 'package:spotube/provider/authentication/authentication.dart';
import 'package:spotube/provider/spotify/spotify.dart';
class UserAlbums extends HookConsumerWidget {
const UserAlbums({super.key});
class UserAlbumsPage extends HookConsumerWidget {
static const name = 'user_albums';
const UserAlbumsPage({super.key});
@override
Widget build(BuildContext context, ref) {

View File

@ -19,8 +19,9 @@ import 'package:spotube/extensions/context.dart';
import 'package:spotube/provider/authentication/authentication.dart';
import 'package:spotube/provider/spotify/spotify.dart';
class UserArtists extends HookConsumerWidget {
const UserArtists({super.key});
class UserArtistsPage extends HookConsumerWidget {
static const name = 'user_artists';
const UserArtistsPage({super.key});
@override
Widget build(BuildContext context, ref) {

View File

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

View File

@ -24,8 +24,9 @@ enum SortBy {
album,
}
class UserLocalTracks extends HookConsumerWidget {
const UserLocalTracks({super.key});
class UserLocalLibraryPage extends HookConsumerWidget {
static const name = 'user_local_library';
const UserLocalLibraryPage({super.key});
@override
Widget build(BuildContext context, ref) {

View File

@ -20,8 +20,9 @@ import 'package:spotube/provider/spotify/spotify.dart';
import 'package:spotube/utils/platform.dart';
import 'package:spotube/utils/service_utils.dart';
class UserPlaylists extends HookConsumerWidget {
const UserPlaylists({super.key});
class UserPlaylistsPage extends HookConsumerWidget {
static const name = 'user_playlists';
const UserPlaylistsPage({super.key});
@override
Widget build(BuildContext context, ref) {

View File

@ -8,7 +8,6 @@ import 'package:spotube/components/titlebar/titlebar.dart';
import 'package:spotube/components/image/universal_image.dart';
import 'package:spotube/extensions/context.dart';
import 'package:spotube/extensions/image.dart';
import 'package:spotube/hooks/utils/use_custom_status_bar_color.dart';
import 'package:spotube/hooks/utils/use_palette_color.dart';
import 'package:spotube/pages/lyrics/plain_lyrics.dart';
import 'package:spotube/pages/lyrics/synced_lyrics.dart';
@ -33,15 +32,8 @@ class LyricsPage extends HookConsumerWidget {
[playlist.activeTrack?.album?.images],
);
final palette = usePaletteColor(albumArt, ref);
final route = ModalRoute.of(context);
final selectedIndex = useState(0);
final resetStatusBar = useCustomStatusBarColor(
palette.color,
route?.isCurrent ?? false,
noSetBGColor: true,
);
Widget tabbar = Padding(
padding: const EdgeInsets.all(10),
child: isModal
@ -93,51 +85,47 @@ class LyricsPage extends HookConsumerWidget {
);
if (isModal) {
return PopScope(
canPop: true,
onPopInvokedWithResult: (_, __) => resetStatusBar(),
child: SafeArea(
bottom: false,
child: SurfaceCard(
surfaceBlur: context.theme.surfaceBlur,
surfaceOpacity: context.theme.surfaceOpacity,
padding: EdgeInsets.zero,
borderRadius: BorderRadius.zero,
borderWidth: 0,
child: Column(
children: [
const SizedBox(height: 5),
Container(
height: 7,
width: 150,
decoration: BoxDecoration(
color: palette.titleTextColor,
borderRadius: BorderRadius.circular(10),
),
return SafeArea(
bottom: false,
child: SurfaceCard(
surfaceBlur: context.theme.surfaceBlur,
surfaceOpacity: context.theme.surfaceOpacity,
padding: EdgeInsets.zero,
borderRadius: BorderRadius.zero,
borderWidth: 0,
child: Column(
children: [
const SizedBox(height: 20),
Container(
height: 7,
width: 150,
decoration: BoxDecoration(
color: palette.titleTextColor,
borderRadius: BorderRadius.circular(10),
),
Row(
),
Row(
children: [
Expanded(
child: tabbar,
),
IconButton.ghost(
icon: const Icon(SpotubeIcons.minimize),
onPressed: () => Navigator.of(context).pop(),
),
const SizedBox(width: 5),
],
),
Expanded(
child: IndexedStack(
index: selectedIndex.value,
children: [
Expanded(
child: tabbar,
),
IconButton.ghost(
icon: const Icon(SpotubeIcons.minimize),
onPressed: () => Navigator.of(context).pop(),
),
const SizedBox(width: 5),
SyncedLyrics(palette: palette, isModal: isModal),
PlainLyrics(palette: palette, isModal: isModal),
],
),
Expanded(
child: IndexedStack(
index: selectedIndex.value,
children: [
SyncedLyrics(palette: palette, isModal: isModal),
PlainLyrics(palette: palette, isModal: isModal),
],
),
),
],
),
),
],
),
),
);

View File

@ -6,7 +6,7 @@ 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';
import 'package:spotube/modules/library/user_local_tracks.dart';
import 'package:spotube/pages/library/user_local_tracks.dart';
import 'package:spotube/modules/root/update_dialog.dart';
import 'package:spotube/models/lyrics.dart';

View File

@ -168,35 +168,35 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/window_manager/macos
SPEC CHECKSUMS:
app_links: 10e0a0ab602ffaf34d142cd4862f29d34b303b2a
audio_service: b88ff778e0e3915efd4cd1a5ad6f0beef0c950a9
audio_session: dea1f41890dbf1718f04a56f1d6150fd50039b72
bonsoir_darwin: e3b8526c42ca46a885142df84229131dfabea842
connectivity_plus: 18382e7311ba19efcaee94442b23b32507b20695
desktop_webview_window: 89bb3d691f4c80314a10be312f4cd35db93a9d5a
device_info_plus: 1b14eed9bf95428983aed283a8d51cce3d8c4215
file_selector_macos: cc3858c981fe6889f364731200d6232dac1d812d
flutter_discord_rpc: 67a7c10ea24d9d3bf35d01af643f48fbcfa7c24f
flutter_inappwebview_macos: bdf207b8f4ebd58e86ae06cd96b147de99a67c9b
flutter_secure_storage_macos: 59459653abe1adb92abbc8ea747d79f8d19866c9
app_links: 9028728e32c83a0831d9db8cf91c526d16cc5468
audio_service: 0d9e4e25347bb3efb768f3b9f005911a81e587a7
audio_session: 48ab6500f7a5e7c64363e206565a5dfe5a0c1441
bonsoir_darwin: 29c7ccf356646118844721f36e1de4b61f6cbd0e
connectivity_plus: 2256d3e20624a7749ed21653aafe291a46446fee
desktop_webview_window: 2f0cdefecc06e21208a51589bd3d1580a87a703c
device_info_plus: 4fb280989f669696856f8b129e4a5e3cd6c48f76
file_selector_macos: 6280b52b459ae6c590af5d78fc35c7267a3c4b31
flutter_discord_rpc: 90614fcca26f3cebfd33263557ea7875936d184b
flutter_inappwebview_macos: c2d68649f9f8f1831bfcd98d73fd6256366d9d1d
flutter_secure_storage_macos: b2d62a774c23b060f0b99d0173b0b36abb4a8632
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
local_notifier: e9506bc66fc70311e8bc7291fb70f743c081e4ff
media_kit_libs_macos_audio: 3871782a4f3f84c77f04d7666c87800a781c24da
media_kit_native_event_loop: 7321675377cb9ae8596a29bddf3a3d2b5e8792c5
metadata_god: 829f61208b44ac1173e7cd32ab740d8776be5435
open_file_mac: 0e554648e2a87ce59e9438e3e5ca3e552e90d89a
local_notifier: ebf072651e35ae5e47280ad52e2707375cb2ae4e
media_kit_libs_macos_audio: 06f3cf88d6d89c7c3c87eae57689d1c6adb335b2
media_kit_native_event_loop: a5833d1e4d5bedb6f691e9909fa57f15f436f2c8
metadata_god: 8029e6ff4b1400ae4f13c38d2c478e8633f0e58b
open_file_mac: 01874b6d6a2c1485ac9b126d7105b99102dea2cf
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
package_info_plus: fa739dd842b393193c5ca93c26798dff6e3d0e0c
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
screen_retriever_macos: 776e0fa5d42c6163d2bf772d22478df4b302b161
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
package_info_plus: a8a591e70e87ce97ce5d21b2594f69cea9e0312f
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
screen_retriever_macos: 452e51764a9e1cdb74b3c541238795849f21557f
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
sqlite3: 7559e33dae4c78538df563795af3a86fc887ee71
sqlite3_flutter_libs: 1b4e98da20ebd4e9b1240269b78cdcf492dbe9f3
system_theme: c7b9f6659a5caa26c9bc2284da096781e9a6fcbc
tray_manager: 9064e219c56d75c476e46b9a21182087930baf90
url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404
window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8
sqlite3_flutter_libs: f0b59f6bb2a18597d0796558725007e5a7428397
system_theme: ed74293ad07d3a05e3e2d0059ff342360346f1a0
tray_manager: a104b5c81b578d83f3c3d0f40a997c8b10810166
url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673
window_manager: 1d01fa7ac65a6e6f83b965471b1a7fdd3f06166c
PODFILE CHECKSUM: 0d3963a09fc94f580682bd88480486da345dc3f0