diff --git a/lib/components/Album/AlbumView.dart b/lib/components/Album/AlbumView.dart index 4c34d7356..cd0dff888 100644 --- a/lib/components/Album/AlbumView.dart +++ b/lib/components/Album/AlbumView.dart @@ -5,6 +5,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotify/spotify.dart'; import 'package:spotube/components/Shared/HeartButton.dart'; import 'package:spotube/components/Shared/TrackCollectionView.dart'; +import 'package:spotube/hooks/useBreakpoints.dart'; import 'package:spotube/utils/type_conversion_utils.dart'; import 'package:spotube/models/CurrentPlaylist.dart'; import 'package:spotube/provider/Auth.dart'; @@ -52,6 +53,8 @@ class AlbumView extends HookConsumerWidget { () => TypeConversionUtils.image_X_UrlString(album.images), [album.images]); + final breakpoint = useBreakpoints(); + return TrackCollectionView( id: album.id!, isPlaying: @@ -61,6 +64,7 @@ class AlbumView extends HookConsumerWidget { tracksSnapshot: tracksSnapshot, album: album, routePath: "/album/${album.id}", + bottomSpace: breakpoint.isLessThanOrEqualTo(Breakpoints.md), onPlay: ([track]) { if (tracksSnapshot.asData?.value != null) { playPlaylist( diff --git a/lib/components/Player/PlayerControls.dart b/lib/components/Player/PlayerControls.dart index 327895e85..fcd556ed9 100644 --- a/lib/components/Player/PlayerControls.dart +++ b/lib/components/Player/PlayerControls.dart @@ -20,11 +20,9 @@ class PlayerControls extends HookConsumerWidget { Widget build(BuildContext context, ref) { final Playback playback = ref.watch(playbackProvider); - final onNext = useNextTrack(playback); - - final onPrevious = usePreviousTrack(playback); - - final _playOrPause = useTogglePlayPause(playback); + final onNext = useNextTrack(ref); + final onPrevious = usePreviousTrack(ref); + final _playOrPause = useTogglePlayPause(ref); final duration = playback.currentDuration; diff --git a/lib/components/Player/PlayerOverlay.dart b/lib/components/Player/PlayerOverlay.dart index 958874c96..b5dd5cca7 100644 --- a/lib/components/Player/PlayerOverlay.dart +++ b/lib/components/Player/PlayerOverlay.dart @@ -6,7 +6,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotube/components/Player/PlayerTrackDetails.dart'; import 'package:spotube/hooks/playback.dart'; import 'package:spotube/hooks/useBreakpoints.dart'; -import 'package:spotube/hooks/useIsCurrentRoute.dart'; import 'package:spotube/hooks/usePaletteColor.dart'; import 'package:spotube/provider/Playback.dart'; @@ -21,24 +20,24 @@ class PlayerOverlay extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { final breakpoint = useBreakpoints(); - final isCurrentRoute = useIsCurrentRoute("/"); final paletteColor = usePaletteColor(albumArt, ref); - final playback = ref.watch(playbackProvider); - if (isCurrentRoute == false) { - return Container(); - } - - final onNext = useNextTrack(playback); + var isHome = GoRouter.of(context).location == "/"; + final isAllowedPage = ["/playlist/", "/album/"].any( + (el) => GoRouter.of(context).location.startsWith(el), + ); - final onPrevious = usePreviousTrack(playback); + final onNext = useNextTrack(ref); + final onPrevious = usePreviousTrack(ref); + final _playOrPause = useTogglePlayPause(ref); - final _playOrPause = useTogglePlayPause(playback); + if (!isHome && !isAllowedPage) return Container(); - return Positioned( - right: (breakpoint.isMd ? 10 : 5), - left: (breakpoint.isSm ? 5 : 80), - bottom: (breakpoint.isSm ? 63 : 10), + return AnimatedPositioned( + duration: const Duration(milliseconds: 2500), + right: (breakpoint.isMd && !isAllowedPage ? 10 : 5), + left: (breakpoint.isSm || isAllowedPage ? 5 : 90), + bottom: (breakpoint.isSm && !isAllowedPage ? 63 : 10), child: GestureDetector( onVerticalDragEnd: (details) { int sensitivity = 8; @@ -88,14 +87,18 @@ class PlayerOverlay extends HookConsumerWidget { onPressed: () { onPrevious(); }), - IconButton( - icon: Icon( - playback.isPlaying - ? Icons.pause_rounded - : Icons.play_arrow_rounded, - ), - color: paletteColor.bodyTextColor, - onPressed: _playOrPause, + Consumer( + builder: (context, ref, _) { + return IconButton( + icon: Icon( + ref.read(playbackProvider).isPlaying + ? Icons.pause_rounded + : Icons.play_arrow_rounded, + ), + color: paletteColor.bodyTextColor, + onPressed: _playOrPause, + ); + }, ), IconButton( icon: const Icon(Icons.skip_next_rounded), diff --git a/lib/components/Playlist/PlaylistView.dart b/lib/components/Playlist/PlaylistView.dart index 83ab459cf..126dfce39 100644 --- a/lib/components/Playlist/PlaylistView.dart +++ b/lib/components/Playlist/PlaylistView.dart @@ -5,6 +5,7 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotube/components/Shared/HeartButton.dart'; import 'package:spotube/components/Shared/TrackCollectionView.dart'; +import 'package:spotube/hooks/useBreakpoints.dart'; import 'package:spotube/hooks/usePaletteColor.dart'; import 'package:spotube/models/CurrentPlaylist.dart'; import 'package:spotube/models/Logger.dart'; @@ -51,6 +52,8 @@ class PlaylistView extends HookConsumerWidget { final isPlaylistPlaying = playback.playlist?.id != null && playback.playlist?.id == playlist.id; + final breakpoint = useBreakpoints(); + final meSnapshot = ref.watch(currentUserQuery); final tracksSnapshot = ref.watch(playlistTracksQuery(playlist.id!)); @@ -81,6 +84,7 @@ class PlaylistView extends HookConsumerWidget { ); } }, + bottomSpace: breakpoint.isLessThanOrEqualTo(Breakpoints.md), showShare: playlist.id != "user-liked-tracks", routePath: "/playlist/${playlist.id}", onShare: () { diff --git a/lib/components/Shared/TrackCollectionView.dart b/lib/components/Shared/TrackCollectionView.dart index 55c67efe5..1e3b1a910 100644 --- a/lib/components/Shared/TrackCollectionView.dart +++ b/lib/components/Shared/TrackCollectionView.dart @@ -28,6 +28,7 @@ class TrackCollectionView extends HookConsumerWidget { final bool showShare; final bool isOwned; + final bool bottomSpace; final String routePath; TrackCollectionView({ @@ -44,6 +45,7 @@ class TrackCollectionView extends HookConsumerWidget { this.description, this.showShare = true, this.isOwned = false, + this.bottomSpace = false, Key? key, }) : super(key: key); @@ -233,6 +235,7 @@ class TrackCollectionView extends HookConsumerWidget { onTrackPlayButtonPressed: onPlay, playlistId: id, userPlaylist: isOwned, + bottomSpace: bottomSpace, ); }, error: (error, _) => diff --git a/lib/components/Shared/TracksTableView.dart b/lib/components/Shared/TracksTableView.dart index c88266ba1..e010a91fb 100644 --- a/lib/components/Shared/TracksTableView.dart +++ b/lib/components/Shared/TracksTableView.dart @@ -16,6 +16,7 @@ class TracksTableView extends HookConsumerWidget { final List tracks; final bool userPlaylist; final String? playlistId; + final bool bottomSpace; final Widget? heading; const TracksTableView( @@ -25,6 +26,7 @@ class TracksTableView extends HookConsumerWidget { this.userPlaylist = false, this.playlistId, this.heading, + this.bottomSpace = false, }) : super(key: key); @override @@ -47,156 +49,159 @@ class TracksTableView extends HookConsumerWidget { ); return SliverList( - delegate: SliverChildListDelegate([ - if (heading != null) heading!, - Row( - children: [ - Checkbox( - value: selected.value.length == tracks.length, - onChanged: (checked) { - if (!showCheck.value) showCheck.value = true; - if (checked == true) { - selected.value = tracks.map((s) => s.id!).toList(); - } else { - selected.value = []; - showCheck.value = false; - } - }, - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - "#", - textAlign: TextAlign.center, - style: tableHeadStyle, + delegate: SliverChildListDelegate( + [ + if (heading != null) heading!, + Row( + children: [ + Checkbox( + value: selected.value.length == tracks.length, + onChanged: (checked) { + if (!showCheck.value) showCheck.value = true; + if (checked == true) { + selected.value = tracks.map((s) => s.id!).toList(); + } else { + selected.value = []; + showCheck.value = false; + } + }, ), - ), - Expanded( - child: Row( - children: [ - Text( - "Title", - style: tableHeadStyle, - overflow: TextOverflow.ellipsis, - ), - ], + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + "#", + textAlign: TextAlign.center, + style: tableHeadStyle, + ), ), - ), - // used alignment of this table-head - if (breakpoint.isMoreThan(Breakpoints.md)) ...[ - const SizedBox(width: 100), Expanded( child: Row( children: [ Text( - "Album", - overflow: TextOverflow.ellipsis, + "Title", style: tableHeadStyle, + overflow: TextOverflow.ellipsis, ), ], ), - ) - ], - if (!breakpoint.isSm) ...[ - const SizedBox(width: 10), - Text("Time", style: tableHeadStyle), - const SizedBox(width: 10), - ], - PopupMenuButton( - itemBuilder: (context) { - return [ - PopupMenuItem( - enabled: selected.value.isNotEmpty, - child: Row( - children: [ - const Icon(Icons.file_download_outlined), - Text( - "Download ${selectedTracks.isNotEmpty ? "(${selectedTracks.length})" : ""}", - ), - ], - ), - value: "download", + ), + // used alignment of this table-head + if (breakpoint.isMoreThan(Breakpoints.md)) ...[ + const SizedBox(width: 100), + Expanded( + child: Row( + children: [ + Text( + "Album", + overflow: TextOverflow.ellipsis, + style: tableHeadStyle, + ), + ], ), - ]; - }, - onSelected: (action) async { - switch (action) { - case "download": - { - final isConfirmed = await showDialog( - context: context, - builder: (context) { - return const DownloadConfirmationDialog(); + ) + ], + if (!breakpoint.isSm) ...[ + const SizedBox(width: 10), + Text("Time", style: tableHeadStyle), + const SizedBox(width: 10), + ], + PopupMenuButton( + itemBuilder: (context) { + return [ + PopupMenuItem( + enabled: selected.value.isNotEmpty, + child: Row( + children: [ + const Icon(Icons.file_download_outlined), + Text( + "Download ${selectedTracks.isNotEmpty ? "(${selectedTracks.length})" : ""}", + ), + ], + ), + value: "download", + ), + ]; + }, + onSelected: (action) async { + switch (action) { + case "download": + { + final isConfirmed = await showDialog( + context: context, + builder: (context) { + return const DownloadConfirmationDialog(); + }); + if (isConfirmed != true) return; + final queue = Queue( + delay: const Duration(seconds: 5), + ); + for (final selectedTrack in selectedTracks) { + queue.add(() async { + downloader.addToQueue( + await playback.toSpotubeTrack( + selectedTrack, + noSponsorBlock: true, + ), + ); }); - if (isConfirmed != true) return; - final queue = Queue( - delay: const Duration(seconds: 5), - ); - for (final selectedTrack in selectedTracks) { - queue.add(() async { - downloader.addToQueue( - await playback.toSpotubeTrack( - selectedTrack, - noSponsorBlock: true, - ), - ); - }); - } + } - selected.value = []; - showCheck.value = false; - await queue.onComplete; - break; - } - default: - } - }, - ), - ], - ), - ...tracks.asMap().entries.map((track) { - String? thumbnailUrl = TypeConversionUtils.image_X_UrlString( - track.value.album?.images, - index: (track.value.album?.images?.length ?? 1) - 1, - ); - String duration = - "${track.value.duration?.inMinutes.remainder(60)}:${PrimitiveUtils.zeroPadNumStr(track.value.duration?.inSeconds.remainder(60) ?? 0)}"; - return InkWell( - onLongPress: () { - showCheck.value = true; - selected.value = [...selected.value, track.value.id!]; - }, - onTap: () { - if (showCheck.value) { + selected.value = []; + showCheck.value = false; + await queue.onComplete; + break; + } + default: + } + }, + ), + ], + ), + ...tracks.asMap().entries.map((track) { + String? thumbnailUrl = TypeConversionUtils.image_X_UrlString( + track.value.album?.images, + index: (track.value.album?.images?.length ?? 1) - 1, + ); + String duration = + "${track.value.duration?.inMinutes.remainder(60)}:${PrimitiveUtils.zeroPadNumStr(track.value.duration?.inSeconds.remainder(60) ?? 0)}"; + return InkWell( + onLongPress: () { + showCheck.value = true; selected.value = [...selected.value, track.value.id!]; - } else { - onTrackPlayButtonPressed?.call(track.value); - } - }, - child: TrackTile( - playback, - playlistId: playlistId, - track: track, - duration: duration, - thumbnailUrl: thumbnailUrl, - userPlaylist: userPlaylist, - isActive: playback.track?.id == track.value.id, - onTrackPlayButtonPressed: onTrackPlayButtonPressed, - isChecked: selected.value.contains(track.value.id), - showCheck: showCheck.value, - onCheckChange: (checked) { - if (checked == true) { + }, + onTap: () { + if (showCheck.value) { selected.value = [...selected.value, track.value.id!]; } else { - selected.value = selected.value - .where((id) => id != track.value.id) - .toList(); + onTrackPlayButtonPressed?.call(track.value); } }, - ), - ); - }).toList() - ]), + child: TrackTile( + playback, + playlistId: playlistId, + track: track, + duration: duration, + thumbnailUrl: thumbnailUrl, + userPlaylist: userPlaylist, + isActive: playback.track?.id == track.value.id, + onTrackPlayButtonPressed: onTrackPlayButtonPressed, + isChecked: selected.value.contains(track.value.id), + showCheck: showCheck.value, + onCheckChange: (checked) { + if (checked == true) { + selected.value = [...selected.value, track.value.id!]; + } else { + selected.value = selected.value + .where((id) => id != track.value.id) + .toList(); + } + }, + ), + ); + }).toList(), + if (bottomSpace) const SizedBox(height: 70), + ], + ), ); } } diff --git a/lib/hooks/playback.dart b/lib/hooks/playback.dart index 34ae47ddc..69a12cbfc 100644 --- a/lib/hooks/playback.dart +++ b/lib/hooks/playback.dart @@ -1,12 +1,14 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:spotify/spotify.dart'; import 'package:spotube/models/Logger.dart'; import 'package:spotube/provider/Playback.dart'; final logger = getLogger("PlaybackHook"); -Future Function() useNextTrack(Playback playback) { +Future Function() useNextTrack(WidgetRef ref) { return () async { try { + final playback = ref.read(playbackProvider); await playback.player.pause(); await playback.player.seek(Duration.zero); playback.seekForward(); @@ -16,9 +18,10 @@ Future Function() useNextTrack(Playback playback) { }; } -Future Function() usePreviousTrack(Playback playback) { +Future Function() usePreviousTrack(WidgetRef ref) { return () async { try { + final playback = ref.read(playbackProvider); await playback.player.pause(); await playback.player.seek(Duration.zero); playback.seekBackward(); @@ -28,9 +31,10 @@ Future Function() usePreviousTrack(Playback playback) { }; } -Future Function([dynamic]) useTogglePlayPause(Playback playback) { +Future Function([dynamic]) useTogglePlayPause(WidgetRef ref) { return ([key]) async { try { + final playback = ref.read(playbackProvider); if (playback.track == null) { return; } else if (playback.track != null &&