chore: use pages instead of drawer for queue, lyrics, sources in mobile

This commit is contained in:
Kingkor Roy Tirtho 2025-03-04 18:26:59 +06:00
parent 7eb0e69dd7
commit bb71fc0eea
10 changed files with 428 additions and 401 deletions

View File

@ -190,6 +190,18 @@ class AppRouter extends RootStackRouter {
), ),
], ],
), ),
AutoRoute(
path: "/player/queue",
page: PlayerQueueRoute.page,
),
AutoRoute(
path: "/player/sources",
page: PlayerTrackSourcesRoute.page,
),
AutoRoute(
path: "/player/lyrics",
page: PlayerLyricsRoute.page,
),
AutoRoute( AutoRoute(
path: "/mini-player", path: "/mini-player",
page: MiniLyricsRoute.page, page: MiniLyricsRoute.page,

File diff suppressed because it is too large Load Diff

View File

@ -3,18 +3,18 @@ import 'package:spotube/collections/spotube_icons.dart';
class BackButton extends StatelessWidget { class BackButton extends StatelessWidget {
final Color? color; final Color? color;
final IconData icon;
const BackButton({ const BackButton({
super.key, super.key,
this.color, this.color,
this.icon = SpotubeIcons.angleLeft,
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return IconButton.ghost( return IconButton.ghost(
size: const ButtonSize(.9), size: const ButtonSize(.9),
icon: color != null icon: Icon(icon, color: color),
? Icon(SpotubeIcons.angleLeft, color: color)
: const Icon(SpotubeIcons.angleLeft),
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
); );
} }

View File

@ -1,10 +1,8 @@
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:auto_size_text/auto_size_text.dart'; import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart' show showModalBottomSheet;
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:shadcn_flutter/shadcn_flutter_extension.dart';
import 'package:sliding_up_panel/sliding_up_panel.dart'; import 'package:sliding_up_panel/sliding_up_panel.dart';
import 'package:spotube/collections/assets.gen.dart'; import 'package:spotube/collections/assets.gen.dart';
@ -13,7 +11,6 @@ import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/components/framework/app_pop_scope.dart'; import 'package:spotube/components/framework/app_pop_scope.dart';
import 'package:spotube/modules/player/player_actions.dart'; import 'package:spotube/modules/player/player_actions.dart';
import 'package:spotube/modules/player/player_controls.dart'; import 'package:spotube/modules/player/player_controls.dart';
import 'package:spotube/modules/player/player_queue.dart';
import 'package:spotube/modules/player/volume_slider.dart'; import 'package:spotube/modules/player/volume_slider.dart';
import 'package:spotube/components/dialogs/track_details_dialog.dart'; import 'package:spotube/components/dialogs/track_details_dialog.dart';
import 'package:spotube/components/links/artist_link.dart'; import 'package:spotube/components/links/artist_link.dart';
@ -25,7 +22,6 @@ import 'package:spotube/extensions/context.dart';
import 'package:spotube/extensions/image.dart'; import 'package:spotube/extensions/image.dart';
import 'package:spotube/models/local_track.dart'; import 'package:spotube/models/local_track.dart';
import 'package:spotube/modules/root/spotube_navigation_bar.dart'; import 'package:spotube/modules/root/spotube_navigation_bar.dart';
import 'package:spotube/pages/lyrics/lyrics.dart';
import 'package:spotube/provider/authentication/authentication.dart'; import 'package:spotube/provider/authentication/authentication.dart';
import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/audio_player/audio_player.dart';
import 'package:spotube/provider/server/active_sourced_track.dart'; import 'package:spotube/provider/server/active_sourced_track.dart';
@ -235,39 +231,7 @@ class PlayerView extends HookConsumerWidget {
leading: const Icon(SpotubeIcons.queue), leading: const Icon(SpotubeIcons.queue),
child: Text(context.l10n.queue), child: Text(context.l10n.queue),
onPressed: () { onPressed: () {
openDrawer( context.pushRoute(const PlayerQueueRoute());
context: context,
barrierDismissible: true,
draggable: true,
barrierColor: Colors.black.withAlpha(100),
borderRadius: BorderRadius.circular(10),
transformBackdrop: false,
position: OverlayPosition.bottom,
surfaceBlur: context.theme.surfaceBlur,
surfaceOpacity: 0.7,
expands: true,
builder: (context) => Consumer(
builder: (context, ref, _) {
final playlist = ref.watch(
audioPlayerProvider,
);
final playlistNotifier =
ref.read(audioPlayerProvider.notifier);
return ConstrainedBox(
constraints: BoxConstraints(
maxHeight:
MediaQuery.of(context).size.height *
0.8,
),
child: PlayerQueue.fromAudioPlayerNotifier(
floating: false,
playlist: playlist,
notifier: playlistNotifier,
),
);
},
),
);
}, },
), ),
), ),
@ -278,22 +242,7 @@ class PlayerView extends HookConsumerWidget {
leading: const Icon(SpotubeIcons.music), leading: const Icon(SpotubeIcons.music),
child: Text(context.l10n.lyrics), child: Text(context.l10n.lyrics),
onPressed: () { onPressed: () {
showModalBottomSheet( context.pushRoute(const PlayerLyricsRoute());
context: context,
isDismissible: true,
enableDrag: true,
isScrollControlled: true,
backgroundColor: Colors.black.withAlpha(100),
barrierColor: Colors.black.withAlpha(100),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(20),
),
),
builder: (context) =>
const LyricsPage(isModal: true),
);
}, },
), ),
), ),

View File

@ -1,8 +1,10 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; import 'package:shadcn_flutter/shadcn_flutter_extension.dart';
import 'package:spotube/collections/routes.gr.dart';
import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/constrains.dart';
@ -141,30 +143,7 @@ class PlayerActions extends HookConsumerWidget {
}, },
); );
} else { } else {
openDrawer( context.pushRoute(const PlayerTrackSourcesRoute());
context: context,
position: OverlayPosition.bottom,
barrierDismissible: true,
draggable: true,
barrierColor: Colors.black.withValues(alpha: .2),
borderRadius: BorderRadius.circular(10),
transformBackdrop: false,
surfaceBlur: context.theme.surfaceBlur,
surfaceOpacity: context.theme.surfaceOpacity,
builder: (context) {
return Card(
borderWidth: 0,
borderColor: Colors.transparent,
padding: EdgeInsets.zero,
child: ConstrainedBox(
constraints: BoxConstraints(
maxHeight: screenSize.height * .8,
),
child: SiblingTracksSheet(floating: floatingQueue),
),
);
},
);
} }
}, },
), ),

View File

@ -6,6 +6,7 @@ import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; import 'package:shadcn_flutter/shadcn_flutter_extension.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/collections/spotube_icons.dart';
import 'package:spotube/components/button/back_button.dart';
import 'package:spotube/components/image/universal_image.dart'; import 'package:spotube/components/image/universal_image.dart';
import 'package:spotube/components/inter_scrollbar/inter_scrollbar.dart'; import 'package:spotube/components/inter_scrollbar/inter_scrollbar.dart';
import 'package:spotube/components/ui/button_tile.dart'; import 'package:spotube/components/ui/button_tile.dart';
@ -244,14 +245,15 @@ class SiblingTracksSheet extends HookConsumerWidget {
), ),
), ),
const Spacer(), const Spacer(),
if (!isSearching.value) if (!isSearching.value) ...[
IconButton.outline( IconButton.outline(
icon: const Icon(SpotubeIcons.search, size: 18), icon: const Icon(SpotubeIcons.search, size: 18),
onPressed: () { onPressed: () {
isSearching.value = true; isSearching.value = true;
}, },
) ),
else ...[ if (!floating) const BackButton(icon: SpotubeIcons.close)
] else ...[
if (preferences.audioSource == AudioSource.piped) if (preferences.audioSource == AudioSource.piped)
IconButton.outline( IconButton.outline(
icon: const Icon(SpotubeIcons.filter, size: 18), icon: const Icon(SpotubeIcons.filter, size: 18),

View File

@ -3,7 +3,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; import 'package:shadcn_flutter/shadcn_flutter_extension.dart';
import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/components/titlebar/titlebar.dart'; import 'package:spotube/components/titlebar/titlebar.dart';
import 'package:spotube/components/image/universal_image.dart'; import 'package:spotube/components/image/universal_image.dart';
import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/context.dart';
@ -20,8 +19,7 @@ import 'package:auto_route/auto_route.dart';
class LyricsPage extends HookConsumerWidget { class LyricsPage extends HookConsumerWidget {
static const name = "lyrics"; static const name = "lyrics";
final bool isModal; const LyricsPage({super.key});
const LyricsPage({super.key, this.isModal = false});
@override @override
Widget build(BuildContext context, ref) { Widget build(BuildContext context, ref) {
@ -38,27 +36,14 @@ class LyricsPage extends HookConsumerWidget {
Widget tabbar = Padding( Widget tabbar = Padding(
padding: const EdgeInsets.all(10), padding: const EdgeInsets.all(10),
child: isModal child: Tabs(
? TabList( index: selectedIndex.value,
index: selectedIndex.value, onChanged: (index) => selectedIndex.value = index,
onChanged: (index) => selectedIndex.value = index, children: [
children: [ TabItem(child: Text(context.l10n.synced)),
TabItem( TabItem(child: Text(context.l10n.plain)),
child: Text(context.l10n.synced), ],
), ),
TabItem(
child: Text(context.l10n.plain),
),
],
)
: Tabs(
index: selectedIndex.value,
onChanged: (index) => selectedIndex.value = index,
children: [
TabItem(child: Text(context.l10n.synced)),
TabItem(child: Text(context.l10n.plain)),
],
),
); );
tabbar = Row( tabbar = Row(
@ -85,52 +70,6 @@ class LyricsPage extends HookConsumerWidget {
], ],
); );
if (isModal) {
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(
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: [
SyncedLyrics(palette: palette, isModal: isModal),
PlainLyrics(palette: palette, isModal: isModal),
],
),
),
],
),
),
);
}
return SafeArea( return SafeArea(
bottom: false, bottom: false,
child: Scaffold( child: Scaffold(
@ -166,8 +105,8 @@ class LyricsPage extends HookConsumerWidget {
child: IndexedStack( child: IndexedStack(
index: selectedIndex.value, index: selectedIndex.value,
children: [ children: [
SyncedLyrics(palette: palette, isModal: isModal), SyncedLyrics(palette: palette, isModal: false),
PlainLyrics(palette: palette, isModal: isModal), PlainLyrics(palette: palette, isModal: false),
], ],
), ),
), ),

View File

@ -0,0 +1,64 @@
import 'package:auto_route/annotations.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/components/button/back_button.dart';
import 'package:spotube/extensions/context.dart';
import 'package:spotube/extensions/image.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';
import 'package:spotube/provider/audio_player/audio_player.dart';
@RoutePage()
class PlayerLyricsPage extends HookConsumerWidget {
const PlayerLyricsPage({super.key});
@override
Widget build(BuildContext context, ref) {
final playlist = ref.watch(audioPlayerProvider);
String albumArt = useMemoized(
() => (playlist.activeTrack?.album?.images).asUrlString(
index: (playlist.activeTrack?.album?.images?.length ?? 1) - 1,
placeholder: ImagePlaceholder.albumArt,
),
[playlist.activeTrack?.album?.images],
);
final selectedIndex = useState(0);
final palette = usePaletteColor(albumArt, ref);
final tabbar = Padding(
padding: const EdgeInsets.all(10),
child: TabList(
index: selectedIndex.value,
onChanged: (index) => selectedIndex.value = index,
children: [
TabItem(
child: Text(context.l10n.synced),
),
TabItem(
child: Text(context.l10n.plain),
),
],
));
return Scaffold(
headers: [
AppBar(
leading: [tabbar],
trailing: const [
BackButton(icon: SpotubeIcons.close),
],
),
],
child: IndexedStack(
index: selectedIndex.value,
children: [
SyncedLyrics(palette: palette, isModal: false),
PlainLyrics(palette: palette, isModal: false),
],
),
);
}
}

View File

@ -0,0 +1,36 @@
import 'package:auto_route/annotations.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/components/button/back_button.dart';
import 'package:spotube/extensions/context.dart';
import 'package:spotube/modules/player/player_queue.dart';
import 'package:spotube/provider/audio_player/audio_player.dart';
@RoutePage()
class PlayerQueuePage extends HookConsumerWidget {
const PlayerQueuePage({super.key});
@override
Widget build(BuildContext context, ref) {
final playlist = ref.watch(
audioPlayerProvider,
);
final playlistNotifier = ref.read(audioPlayerProvider.notifier);
return Scaffold(
headers: [
AppBar(
title: Text(context.l10n.queue),
trailing: const [
BackButton(icon: SpotubeIcons.close),
],
),
],
child: PlayerQueue.fromAudioPlayerNotifier(
floating: false,
playlist: playlist,
notifier: playlistNotifier,
),
);
}
}

View File

@ -0,0 +1,15 @@
import 'package:auto_route/auto_route.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:spotube/modules/player/sibling_tracks_sheet.dart';
@RoutePage()
class PlayerTrackSourcesPage extends StatelessWidget {
const PlayerTrackSourcesPage({super.key});
@override
Widget build(BuildContext context) {
return const Scaffold(
child: SiblingTracksSheet(floating: false),
);
}
}