From 2488da2279b9fa5d65c101989e5a45e96b06188a Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Sat, 21 Dec 2024 14:38:54 +0600 Subject: [PATCH] refactor: bottom player border, player queue using shadcn drawer --- .vscode/settings.json | 1 + lib/collections/spotube_icons.dart | 1 + lib/main.dart | 4 +- lib/modules/player/player.dart | 95 +++---- lib/modules/player/player_actions.dart | 30 ++- lib/modules/player/player_queue.dart | 350 +++++++++++-------------- lib/modules/player/volume_slider.dart | 15 +- lib/modules/root/bottom_player.dart | 148 +++++------ lib/modules/root/sidebar.dart | 51 ++-- lib/pages/root/root_app.dart | 159 +++++------ 10 files changed, 397 insertions(+), 457 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 1f47bada..57413df4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -13,6 +13,7 @@ "RGBO", "riverpod", "Scrobblenaut", + "shadcn", "skeletonizer", "songlink", "speechiness", diff --git a/lib/collections/spotube_icons.dart b/lib/collections/spotube_icons.dart index 5c4df85f..a1c6d69f 100644 --- a/lib/collections/spotube_icons.dart +++ b/lib/collections/spotube_icons.dart @@ -127,4 +127,5 @@ abstract class SpotubeIcons { static const cache = FeatherIcons.hardDrive; static const export = Icons.file_open_outlined; static const delete = FeatherIcons.trash2; + static const open = FeatherIcons.externalLink; } diff --git a/lib/main.dart b/lib/main.dart index cd9acec2..d1275577 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -220,14 +220,14 @@ class Spotube extends HookConsumerWidget { radius: .5, iconTheme: const IconThemeProperties(), colorScheme: ColorSchemes.lightBlue(), - surfaceOpacity: .9, + surfaceOpacity: .8, surfaceBlur: 10, ), darkTheme: ThemeData( radius: .5, iconTheme: const IconThemeProperties(), colorScheme: ColorSchemes.darkNeutral(), - surfaceOpacity: .9, + surfaceOpacity: .8, surfaceBlur: 10, ), themeMode: themeMode, diff --git a/lib/modules/player/player.dart b/lib/modules/player/player.dart index 925afadc..a79934d9 100644 --- a/lib/modules/player/player.dart +++ b/lib/modules/player/player.dart @@ -3,6 +3,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:shadcn_flutter/shadcn_flutter.dart' + show openDrawer, OverlayPosition; +import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; import 'package:spotube/collections/assets.gen.dart'; import 'package:spotube/collections/spotube_icons.dart'; @@ -289,53 +292,53 @@ class PlayerView extends HookConsumerWidget { const SizedBox(width: 10), Expanded( child: OutlinedButton.icon( - icon: const Icon(SpotubeIcons.queue), - label: Text(context.l10n.queue), - style: OutlinedButton.styleFrom( - foregroundColor: bodyTextColor, - side: BorderSide( - color: bodyTextColor ?? Colors.white, - ), + icon: const Icon(SpotubeIcons.queue), + label: Text(context.l10n.queue), + style: OutlinedButton.styleFrom( + foregroundColor: bodyTextColor, + side: BorderSide( + color: bodyTextColor ?? Colors.white, ), - onPressed: currentTrack != null - ? () { - showModalBottomSheet( - context: context, - isDismissible: true, - enableDrag: true, - isScrollControlled: true, - backgroundColor: Colors.black12, - barrierColor: Colors.black12, - shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(10), - ), - constraints: BoxConstraints( - maxHeight: - MediaQuery.of(context) - .size - .height * - .7, - ), - builder: (context) => Consumer( - builder: (context, ref, _) { - final playlist = ref.watch( - audioPlayerProvider, - ); - final playlistNotifier = ref - .read(audioPlayerProvider - .notifier); - return PlayerQueue - .fromAudioPlayerNotifier( - floating: false, - playlist: playlist, - notifier: playlistNotifier, - ); - }, - ), - ); - } - : null), + ), + // enabled: currentTrack != null, + onPressed: () { + openDrawer( + context: context, + barrierDismissible: true, + draggable: true, + barrierColor: Colors.black12, + 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, + ), + ); + }, + ), + ); + }, + ), ), if (auth.asData?.value != null) const SizedBox(width: 10), diff --git a/lib/modules/player/player_actions.dart b/lib/modules/player/player_actions.dart index 7db65c23..dbdfa11b 100644 --- a/lib/modules/player/player_actions.dart +++ b/lib/modules/player/player_actions.dart @@ -2,8 +2,10 @@ import 'package:flutter/foundation.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; +import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; import 'package:spotube/collections/spotube_icons.dart'; +import 'package:spotube/modules/player/player_queue.dart'; import 'package:spotube/modules/player/sibling_tracks_sheet.dart'; import 'package:spotube/components/adaptive/adaptive_pop_sheet_list.dart'; import 'package:spotube/components/heart_button/heart_button.dart'; @@ -82,7 +84,32 @@ class PlayerActions extends HookConsumerWidget { icon: const Icon(SpotubeIcons.queue), enabled: playlist.activeTrack != null, onPressed: () { - // Scaffold.of(context).openEndDrawer(); + openDrawer( + context: context, + position: OverlayPosition.right, + transformBackdrop: false, + draggable: false, + surfaceBlur: context.theme.surfaceBlur, + surfaceOpacity: 0.7, + builder: (context) { + return Container( + constraints: const BoxConstraints(maxWidth: 800), + child: Consumer( + builder: (context, ref, _) { + final playlist = ref.watch(audioPlayerProvider); + final playlistNotifier = + ref.read(audioPlayerProvider.notifier); + + return PlayerQueue.fromAudioPlayerNotifier( + floating: true, + playlist: playlist, + notifier: playlistNotifier, + ); + }, + ), + ); + }, + ); }, ), ), @@ -100,6 +127,7 @@ class PlayerActions extends HookConsumerWidget { draggable: true, barrierColor: Colors.black.withValues(alpha: .2), borderRadius: BorderRadius.circular(10), + transformBackdrop: false, builder: (context) { return SiblingTracksSheet(floating: floatingQueue); }, diff --git a/lib/modules/player/player_queue.dart b/lib/modules/player/player_queue.dart index 369b95d2..49279d5c 100644 --- a/lib/modules/player/player_queue.dart +++ b/lib/modules/player/player_queue.dart @@ -1,14 +1,12 @@ -import 'dart:ui'; - +import 'package:auto_size_text/auto_size_text.dart'; import 'package:collection/collection.dart'; -import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:fuzzywuzzy/fuzzywuzzy.dart'; -import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:scroll_to_index/scroll_to_index.dart'; +import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:spotify/spotify.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/fallbacks/not_found.dart'; @@ -60,16 +58,6 @@ class PlayerQueue extends HookConsumerWidget { final isSearching = useState(false); final tracks = playlist.tracks; - final borderRadius = floating - ? const BorderRadius.only( - topLeft: Radius.circular(10), - ) - : const BorderRadius.only( - topLeft: Radius.circular(10), - topRight: Radius.circular(10), - ); - final theme = Theme.of(context); - final headlineColor = theme.textTheme.headlineSmall?.color; final filteredTracks = useMemoized( () { @@ -92,217 +80,173 @@ class PlayerQueue extends HookConsumerWidget { [tracks, searchText.value], ); - useEffect(() { - if (playlist.activeTrack == null) return null; - - controller.scrollToIndex( - playlist.playlist.index, - preferPosition: AutoScrollPosition.middle, - ); - return null; - }, []); - if (tracks.isEmpty) { return const NotFound(vertical: true); } - return LayoutBuilder( - builder: (context, constrains) { - return ClipRRect( - borderRadius: borderRadius, - clipBehavior: Clip.hardEdge, - child: BackdropFilter( - filter: ImageFilter.blur( - sigmaX: 15, - sigmaY: 15, - ), - child: Container( - padding: const EdgeInsets.only( - top: 5.0, + return Stack( + children: [ + LayoutBuilder( + builder: (context, constrains) { + final searchBar = ConstrainedBox( + constraints: BoxConstraints( + maxHeight: 40, + maxWidth: + mediaQuery.smAndDown ? mediaQuery.size.width - 40 : 300, ), - decoration: BoxDecoration( - color: - theme.colorScheme.surfaceContainerHighest.withOpacity(0.5), - borderRadius: borderRadius, - ), - child: CallbackShortcuts( - bindings: { - LogicalKeySet(LogicalKeyboardKey.escape): () { - if (!isSearching.value) { - Navigator.of(context).pop(); - } - isSearching.value = false; - searchText.value = ''; - } + child: TextField( + onChanged: (value) { + searchText.value = value; }, - child: InterScrollbar( - controller: controller, - child: CustomScrollView( - controller: controller, - slivers: [ - if (!floating) - SliverToBoxAdapter( - child: Center( - child: Container( - height: 5, - width: 100, - margin: const EdgeInsets.only(bottom: 5, top: 2), - decoration: BoxDecoration( - color: headlineColor, - borderRadius: BorderRadius.circular(20), - ), + placeholder: Text(context.l10n.search), + ), + ); + return CallbackShortcuts( + bindings: { + LogicalKeySet(LogicalKeyboardKey.escape): () { + if (!isSearching.value) { + Navigator.of(context).pop(); + } + isSearching.value = false; + searchText.value = ''; + } + }, + child: Column( + children: [ + if (isSearching.value && mediaQuery.smAndDown) + AppBar( + backgroundColor: Colors.transparent, + leading: [ + if (mediaQuery.smAndDown) + IconButton.ghost( + icon: const Icon( + Icons.arrow_back_ios_new_outlined, ), - ), - ), - SliverAppBar( - floating: true, - pinned: false, - snap: false, - backgroundColor: Colors.transparent, - elevation: 0, - automaticallyImplyLeading: false, - title: BackdropFilter( - filter: ImageFilter.blur( - sigmaX: 10, - sigmaY: 10, - ), - child: SizedBox( - height: kToolbarHeight, - child: mediaQuery.mdAndUp || !isSearching.value - ? Align( - alignment: Alignment.centerLeft, - child: Text( - context.l10n - .tracks_in_queue(tracks.length), - style: TextStyle( - color: headlineColor, - fontWeight: FontWeight.bold, - fontSize: 18, - ), - ), - ) - : null, - ), - ), - actions: [ - if (mediaQuery.mdAndUp || isSearching.value) - TextField( - onChanged: (value) { - searchText.value = value; - }, - decoration: InputDecoration( - hintText: context.l10n.search, - isDense: true, - prefixIcon: mediaQuery.smAndDown - ? IconButton( - icon: const Icon( - Icons.arrow_back_ios_new_outlined, - ), - onPressed: () { - isSearching.value = false; - searchText.value = ''; - }, - style: IconButton.styleFrom( - padding: EdgeInsets.zero, - minimumSize: const Size.square(20), - ), - ) - : const Icon(SpotubeIcons.filter), - constraints: BoxConstraints( - maxHeight: 40, - maxWidth: mediaQuery.smAndDown - ? mediaQuery.size.width - 40 - : 300, - ), + onPressed: () { + isSearching.value = false; + searchText.value = ''; + }, + ) + ], + surfaceBlur: 0, + surfaceOpacity: 0, + child: searchBar, + ) + else + AppBar( + trailingGap: 0, + backgroundColor: Colors.transparent, + surfaceBlur: 0, + surfaceOpacity: 0, + title: mediaQuery.mdAndUp || !isSearching.value + ? SizedBox( + height: 30, + child: AutoSizeText( + context.l10n.tracks_in_queue(tracks.length), + maxLines: 1, ), ) - else - IconButton.filledTonal( - icon: const Icon(SpotubeIcons.filter), - onPressed: () { - isSearching.value = !isSearching.value; - }, - ), - if (mediaQuery.mdAndUp || !isSearching.value) ...[ - const SizedBox(width: 10), - FilledButton( - style: FilledButton.styleFrom( - backgroundColor: theme.scaffoldBackgroundColor - .withOpacity(0.5), - foregroundColor: - theme.textTheme.headlineSmall?.color, - ), - child: Row( - children: [ - const Icon(SpotubeIcons.playlistRemove), - const SizedBox(width: 5), - Text(context.l10n.clear_all), - ], - ), + : null, + trailing: [ + if (mediaQuery.mdAndUp) + searchBar + else + IconButton.ghost( + icon: const Icon(SpotubeIcons.filter), + onPressed: () { + isSearching.value = !isSearching.value; + }, + ), + if (mediaQuery.mdAndUp || !isSearching.value) ...[ + const SizedBox(width: 10), + Tooltip( + tooltip: Text(context.l10n.clear_all), + child: IconButton.outline( + icon: const Icon(SpotubeIcons.playlistRemove), onPressed: () { onStop(); Navigator.of(context).pop(); }, ), - const SizedBox(width: 10), - ], + ), ], - ), - const SliverGap(10), - SliverReorderableList( - onReorder: onReorder, - itemCount: filteredTracks.length, - onReorderStart: (index) { - HapticFeedback.selectionClick(); - }, - onReorderEnd: (index) { - HapticFeedback.selectionClick(); - }, - itemBuilder: (context, i) { - final track = filteredTracks.elementAt(i); - return AutoScrollTag( - key: ValueKey(i), - controller: controller, - index: i, - child: Material( - color: Colors.transparent, - child: TrackTile( - playlist: playlist, + ], + ), + const Divider(), + Expanded( + child: InterScrollbar( + controller: controller, + child: CustomScrollView( + controller: controller, + slivers: [ + const SliverGap(10), + SliverReorderableList( + onReorder: onReorder, + itemCount: filteredTracks.length, + onReorderStart: (index) { + HapticFeedback.selectionClick(); + }, + onReorderEnd: (index) { + HapticFeedback.selectionClick(); + }, + itemBuilder: (context, i) { + final track = filteredTracks.elementAt(i); + return AutoScrollTag( + key: ValueKey(i), + controller: controller, index: i, - track: track, - onTap: () async { - if (playlist.activeTrack?.id == track.id) { - return; - } - await onJump(track); - }, - leadingActions: [ - if (!isSearching.value && - searchText.value.isEmpty) - Padding( - padding: const EdgeInsets.only(left: 8.0), - child: ReorderableDragStartListener( - index: i, - child: const Icon( - SpotubeIcons.dragHandle, + child: TrackTile( + playlist: playlist, + index: i, + track: track, + onTap: () async { + if (playlist.activeTrack?.id == track.id) { + return; + } + await onJump(track); + }, + leadingActions: [ + if (!isSearching.value && + searchText.value.isEmpty) + Padding( + padding: + const EdgeInsets.only(left: 8.0), + child: ReorderableDragStartListener( + index: i, + child: const Icon( + SpotubeIcons.dragHandle, + ), ), ), - ), - ], - ), - ), - ); - }, + ], + ), + ); + }, + ), + const SliverGap(100), + ], ), - const SliverGap(100), - ], + ), ), - ), + ], ), - ), + ); + }, + ), + Positioned( + right: 20, + bottom: 20, + child: IconButton.secondary( + icon: const Icon(SpotubeIcons.open), + onPressed: () { + controller.scrollToIndex( + playlist.playlist.index, + preferPosition: AutoScrollPosition.middle, + ); + }, ), - ); - }, + ) + ], ); } } diff --git a/lib/modules/player/volume_slider.dart b/lib/modules/player/volume_slider.dart index 515f1fbc..ee4ac9c5 100644 --- a/lib/modules/player/volume_slider.dart +++ b/lib/modules/player/volume_slider.dart @@ -31,13 +31,18 @@ class VolumeSlider extends HookConsumerWidget { } } }, - child: Slider( - min: 0, - max: 1, - value: SliderValue.single(value), - onChanged: (v) => onChanged(v.value), + child: SizedBox( + height: 20, + width: 100, + child: Slider( + min: 0, + max: 1, + value: SliderValue.single(value), + onChanged: (v) => onChanged(v.value), + ), ), ); + return Row( mainAxisAlignment: !fullWidth ? MainAxisAlignment.center : MainAxisAlignment.start, diff --git a/lib/modules/root/bottom_player.dart b/lib/modules/root/bottom_player.dart index f435eefb..8a22cc7a 100644 --- a/lib/modules/root/bottom_player.dart +++ b/lib/modules/root/bottom_player.dart @@ -1,9 +1,8 @@ -import 'dart:ui'; - 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/spotube_icons.dart'; @@ -16,7 +15,6 @@ import 'package:spotube/modules/player/volume_slider.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/image.dart'; -import 'package:spotube/hooks/utils/use_brightness_value.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; @@ -45,14 +43,6 @@ class BottomPlayer extends HookConsumerWidget { [playlist.activeTrack?.album?.images], ); - final theme = Theme.of(context); - final bg = theme.colorScheme.background; - - final bgColor = useBrightnessValue( - Color.lerp(bg, Colors.white, 0.7), - Color.lerp(bg, Colors.black, 0.45)!, - ); - // returning an empty non spacious Container as the overlay will take // place in the global overlay stack aka [_entries] if (layoutMode == LayoutMode.compact || @@ -60,85 +50,81 @@ class BottomPlayer extends HookConsumerWidget { return PlayerOverlay(albumArt: albumArt); } - return ClipRect( - child: BackdropFilter( - filter: ImageFilter.blur(sigmaX: 15, sigmaY: 15), - child: DecoratedBox( - decoration: BoxDecoration(color: bgColor?.withValues(alpha: .8)), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + return SurfaceCard( + borderRadius: BorderRadius.zero, + surfaceBlur: context.theme.surfaceBlur, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: PlayerTrackDetails(track: playlist.activeTrack), + ), + // controls + const Flexible( + flex: 3, + child: Padding( + padding: EdgeInsets.only(top: 5), + child: PlayerControls(), + ), + ), + // add to saved tracks + Column( + mainAxisSize: MainAxisSize.min, children: [ - Expanded( - child: PlayerTrackDetails(track: playlist.activeTrack), - ), - // controls - const Flexible( - flex: 3, - child: Padding( - padding: EdgeInsets.only(top: 5), - child: PlayerControls(), - ), - ), - // add to saved tracks - Column( - children: [ - PlayerActions( - extraActions: [ - Tooltip( - tooltip: Text(context.l10n.mini_player), - child: IconButton( - variance: ButtonVariance.ghost, - icon: const Icon(SpotubeIcons.miniPlayer), - onPressed: () async { - if (!kIsDesktop) return; + PlayerActions( + extraActions: [ + Tooltip( + tooltip: Text(context.l10n.mini_player), + child: IconButton( + variance: ButtonVariance.ghost, + icon: const Icon(SpotubeIcons.miniPlayer), + onPressed: () async { + if (!kIsDesktop) return; - final prevSize = await windowManager.getSize(); - await windowManager.setMinimumSize( - const Size(300, 300), - ); - await windowManager.setAlwaysOnTop(true); - if (!kIsLinux) { - await windowManager.setHasShadow(false); + final prevSize = await windowManager.getSize(); + await windowManager.setMinimumSize( + const Size(300, 300), + ); + await windowManager.setAlwaysOnTop(true); + if (!kIsLinux) { + await windowManager.setHasShadow(false); + } + await windowManager.setAlignment(Alignment.topRight); + await windowManager.setSize(const Size(400, 500)); + await Future.delayed( + const Duration(milliseconds: 100), + () async { + if (context.mounted) { + context.go( + '/mini-player', + extra: prevSize, + ); } - await windowManager - .setAlignment(Alignment.topRight); - await windowManager.setSize(const Size(400, 500)); - await Future.delayed( - const Duration(milliseconds: 100), - () async { - if (context.mounted) { - context.go( - '/mini-player', - extra: prevSize, - ); - } - }, - ); }, - ), - ), - ], + ); + }, + ), ), - Container( - height: 40, - constraints: const BoxConstraints(maxWidth: 250), - padding: const EdgeInsets.only(right: 10), - child: Consumer(builder: (context, ref, _) { - final volume = ref.watch(volumeProvider); - return VolumeSlider( - fullWidth: true, - value: volume, - onChanged: (value) { - ref.read(volumeProvider.notifier).setVolume(value); - }, - ); - }), - ) ], ), + Container( + height: 40, + constraints: const BoxConstraints(maxWidth: 250), + padding: const EdgeInsets.only(right: 10), + child: Consumer(builder: (context, ref, _) { + final volume = ref.watch(volumeProvider); + return VolumeSlider( + fullWidth: true, + value: volume, + onChanged: (value) { + ref.read(volumeProvider.notifier).setVolume(value); + }, + ); + }), + ) ], ), - ), + ], ), ); } diff --git a/lib/modules/root/sidebar.dart b/lib/modules/root/sidebar.dart index 79e8d6d4..f045c23d 100644 --- a/lib/modules/root/sidebar.dart +++ b/lib/modules/root/sidebar.dart @@ -96,32 +96,31 @@ class Sidebar extends HookConsumerWidget { return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - SafeArea( - child: Column( - children: [ - Expanded( - child: mediaQuery.lgAndUp - ? NavigationSidebar( - index: selectedIndex, - onSelected: (index) { - final tile = sidebarTileList[index]; - ServiceUtils.pushNamed(context, tile.name); - }, - children: navigationButtons, - ) - : NavigationRail( - alignment: NavigationRailAlignment.start, - index: selectedIndex, - onSelected: (index) { - final tile = sidebarTileList[index]; - ServiceUtils.pushNamed(context, tile.name); - }, - children: navigationButtons, - ), - ), - const SidebarFooter(), - ], - ), + Column( + children: [ + Expanded( + child: mediaQuery.lgAndUp + ? NavigationSidebar( + index: selectedIndex, + onSelected: (index) { + final tile = sidebarTileList[index]; + ServiceUtils.pushNamed(context, tile.name); + }, + children: navigationButtons, + ) + : NavigationRail( + alignment: NavigationRailAlignment.start, + index: selectedIndex, + onSelected: (index) { + final tile = sidebarTileList[index]; + ServiceUtils.pushNamed(context, tile.name); + }, + children: navigationButtons, + ), + ), + const SidebarFooter(), + const Gap(130) + ], ), const VerticalDivider(), Expanded(child: child), diff --git a/lib/pages/root/root_app.dart b/lib/pages/root/root_app.dart index 2a6c36f0..84c40a2f 100644 --- a/lib/pages/root/root_app.dart +++ b/lib/pages/root/root_app.dart @@ -1,14 +1,13 @@ import 'dart:async'; -import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:spotube/collections/side_bar_tiles.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/framework/app_pop_scope.dart'; -import 'package:spotube/modules/player/player_queue.dart'; import 'package:spotube/components/dialogs/replace_downloaded_dialog.dart'; import 'package:spotube/modules/root/bottom_player.dart'; import 'package:spotube/modules/root/sidebar.dart'; @@ -17,7 +16,6 @@ import 'package:spotube/extensions/context.dart'; import 'package:spotube/hooks/configurators/use_endless_playback.dart'; import 'package:spotube/pages/home/home.dart'; import 'package:spotube/provider/download_manager_provider.dart'; -import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/glance/glance.dart'; import 'package:spotube/provider/server/routes/connect.dart'; import 'package:spotube/services/connectivity_adapter.dart'; @@ -37,7 +35,7 @@ class RootApp extends HookConsumerWidget { final showingDialogCompleter = useRef(Completer()..complete()); final downloader = ref.watch(downloadManagerProvider); - final scaffoldMessenger = ScaffoldMessenger.of(context); + final connectRoutes = ref.watch(serverConnectRoutesProvider); ref.listen(glanceProvider, (_, __) {}); @@ -50,64 +48,70 @@ class RootApp extends HookConsumerWidget { final subscriptions = [ ConnectionCheckerService.instance.onConnectivityChanged .listen((status) { + if (!context.mounted) return; if (status) { - scaffoldMessenger.showSnackBar( - SnackBar( - content: Row( - children: [ - Icon( - SpotubeIcons.wifi, - color: theme.colorScheme.onPrimary, - ), - const SizedBox(width: 10), - Text(context.l10n.connection_restored), - ], - ), - backgroundColor: theme.colorScheme.primary, - showCloseIcon: true, - width: 350, - ), + showToast( + context: context, + builder: (context, overlay) { + return SurfaceCard( + fillColor: theme.colorScheme.primary, + child: Row( + children: [ + Icon( + SpotubeIcons.wifi, + color: theme.colorScheme.primaryForeground, + ), + const SizedBox(width: 10), + Text(context.l10n.connection_restored), + ], + ), + ); + }, ); } else { - scaffoldMessenger.showSnackBar( - SnackBar( - content: Row( - children: [ - Icon( - SpotubeIcons.noWifi, - color: theme.colorScheme.onError, - ), - const SizedBox(width: 10), - Text(context.l10n.you_are_offline), - ], - ), - backgroundColor: theme.colorScheme.error, - showCloseIcon: true, - width: 300, - ), + showToast( + context: context, + builder: (context, overlay) { + return SurfaceCard( + fillColor: theme.colorScheme.destructive, + child: Row( + children: [ + Icon( + SpotubeIcons.noWifi, + color: theme.colorScheme.destructiveForeground, + ), + const SizedBox(width: 10), + Text(context.l10n.you_are_offline), + ], + ), + ); + }, ); } }), connectRoutes.connectClientStream.listen((clientOrigin) { - scaffoldMessenger.showSnackBar( - SnackBar( - backgroundColor: Colors.yellow[600], - behavior: SnackBarBehavior.floating, - content: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon( - SpotubeIcons.error, - color: Colors.black, - ), - const SizedBox(width: 10), - Text( - context.l10n.connect_client_alert(clientOrigin), - style: const TextStyle(color: Colors.black), - ), - ], - ), - ), + if (!context.mounted) return; + showToast( + context: context, + builder: (context, overlay) { + return SurfaceCard( + fillColor: Colors.yellow[600], + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon( + SpotubeIcons.error, + color: Colors.black, + ), + const SizedBox(width: 10), + Text( + context.l10n.connect_client_alert(clientOrigin), + style: const TextStyle(color: Colors.black), + ), + ], + ), + ); + }, ); }) ]; @@ -156,7 +160,7 @@ class RootApp extends HookConsumerWidget { useEndlessPlayback(ref); - final backgroundColor = Theme.of(context).scaffoldBackgroundColor; + final backgroundColor = Theme.of(context).colorScheme.background; useEffect(() { SystemChrome.setSystemUIOverlayStyle( @@ -175,43 +179,12 @@ class RootApp extends HookConsumerWidget { }, []); final scaffold = Scaffold( - body: Sidebar(child: child), - extendBody: true, - drawerScrimColor: Colors.transparent, - endDrawer: kIsDesktop - ? Container( - constraints: const BoxConstraints(maxWidth: 800), - decoration: BoxDecoration( - boxShadow: theme.brightness == Brightness.light - ? null - : kElevationToShadow[8], - ), - margin: const EdgeInsets.only( - top: 40, - bottom: 100, - ), - child: Consumer( - builder: (context, ref, _) { - final playlist = ref.watch(audioPlayerProvider); - final playlistNotifier = - ref.read(audioPlayerProvider.notifier); - - return PlayerQueue.fromAudioPlayerNotifier( - floating: true, - playlist: playlist, - notifier: playlistNotifier, - ); - }, - ), - ) - : null, - bottomNavigationBar: const Column( - mainAxisSize: MainAxisSize.min, - children: [ - BottomPlayer(), - SpotubeNavigationBar(), - ], - ), + footers: const [ + BottomPlayer(), + SpotubeNavigationBar(), + ], + floatingFooter: true, + child: Sidebar(child: child), ); if (!kIsAndroid) {