fix: always showing play in playlist/album views

This commit is contained in:
Kingkor Roy Tirtho 2023-08-15 21:57:26 +06:00
parent 4adf6951d9
commit 8521cc5c88
6 changed files with 97 additions and 28 deletions

View File

@ -13,6 +13,12 @@ import 'package:spotube/components/shared/playbutton_card.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';
enum PlayButtonState {
playing,
notPlaying,
loading,
}
class TrackCollectionHeading<T> extends HookConsumerWidget { class TrackCollectionHeading<T> extends HookConsumerWidget {
final String title; final String title;
final String? description; final String? description;
@ -20,7 +26,7 @@ class TrackCollectionHeading<T> extends HookConsumerWidget {
final List<Widget> buttons; final List<Widget> buttons;
final AlbumSimple? album; final AlbumSimple? album;
final Query<List<TrackSimple>, T> tracksSnapshot; final Query<List<TrackSimple>, T> tracksSnapshot;
final bool isPlaying; final PlayButtonState playingState;
final void Function([Track? currentTrack]) onPlay; final void Function([Track? currentTrack]) onPlay;
final void Function([Track? currentTrack]) onShuffledPlay; final void Function([Track? currentTrack]) onShuffledPlay;
final PaletteColor? color; final PaletteColor? color;
@ -31,7 +37,7 @@ class TrackCollectionHeading<T> extends HookConsumerWidget {
required this.titleImage, required this.titleImage,
required this.buttons, required this.buttons,
required this.tracksSnapshot, required this.tracksSnapshot,
required this.isPlaying, required this.playingState,
required this.onPlay, required this.onPlay,
required this.onShuffledPlay, required this.onShuffledPlay,
required this.color, required this.color,
@ -155,7 +161,8 @@ class TrackCollectionHeading<T> extends HookConsumerWidget {
label: Text(context.l10n.shuffle), label: Text(context.l10n.shuffle),
icon: const Icon(SpotubeIcons.shuffle), icon: const Icon(SpotubeIcons.shuffle),
onPressed: tracksSnapshot.data == null || onPressed: tracksSnapshot.data == null ||
isPlaying playingState ==
PlayButtonState.playing
? null ? null
: onShuffledPlay, : onShuffledPlay,
), ),
@ -167,16 +174,27 @@ class TrackCollectionHeading<T> extends HookConsumerWidget {
backgroundColor: color?.color, backgroundColor: color?.color,
foregroundColor: color?.bodyTextColor, foregroundColor: color?.bodyTextColor,
), ),
onPressed: tracksSnapshot.data != null onPressed: tracksSnapshot.data != null ||
playingState ==
PlayButtonState.loading
? onPlay ? onPlay
: null, : null,
icon: Icon( icon: switch (playingState) {
isPlaying PlayButtonState.playing =>
? SpotubeIcons.stop const Icon(SpotubeIcons.pause),
: SpotubeIcons.play, PlayButtonState.notPlaying =>
const Icon(SpotubeIcons.play),
PlayButtonState.loading =>
const SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
strokeWidth: .7,
), ),
),
},
label: Text( label: Text(
isPlaying playingState == PlayButtonState.playing
? context.l10n.stop ? context.l10n.stop
: context.l10n.play, : context.l10n.play,
), ),

View File

@ -25,7 +25,7 @@ class TrackCollectionView<T> extends HookConsumerWidget {
final String? description; final String? description;
final Query<List<TrackSimple>, T> tracksSnapshot; final Query<List<TrackSimple>, T> tracksSnapshot;
final String titleImage; final String titleImage;
final bool isPlaying; final PlayButtonState playingState;
final void Function([Track? currentTrack]) onPlay; final void Function([Track? currentTrack]) onPlay;
final void Function([Track? currentTrack]) onShuffledPlay; final void Function([Track? currentTrack]) onShuffledPlay;
final void Function() onAddToQueue; final void Function() onAddToQueue;
@ -43,7 +43,7 @@ class TrackCollectionView<T> extends HookConsumerWidget {
required this.id, required this.id,
required this.tracksSnapshot, required this.tracksSnapshot,
required this.titleImage, required this.titleImage,
required this.isPlaying, required this.playingState,
required this.onPlay, required this.onPlay,
required this.onShuffledPlay, required this.onShuffledPlay,
required this.onAddToQueue, required this.onAddToQueue,
@ -62,6 +62,7 @@ class TrackCollectionView<T> extends HookConsumerWidget {
Widget build(BuildContext context, ref) { Widget build(BuildContext context, ref) {
final theme = Theme.of(context); final theme = Theme.of(context);
final auth = ref.watch(AuthenticationNotifier.provider); final auth = ref.watch(AuthenticationNotifier.provider);
final color = usePaletteGenerator(titleImage).dominantColor; final color = usePaletteGenerator(titleImage).dominantColor;
final List<Widget> buttons = [ final List<Widget> buttons = [
@ -72,7 +73,7 @@ class TrackCollectionView<T> extends HookConsumerWidget {
), ),
if (heartBtn != null && auth != null) heartBtn!, if (heartBtn != null && auth != null) heartBtn!,
IconButton( IconButton(
onPressed: isPlaying onPressed: playingState == PlayButtonState.playing
? null ? null
: tracksSnapshot.data != null : tracksSnapshot.data != null
? onAddToQueue ? onAddToQueue
@ -143,7 +144,9 @@ class TrackCollectionView<T> extends HookConsumerWidget {
child: IconButton( child: IconButton(
tooltip: context.l10n.shuffle, tooltip: context.l10n.shuffle,
icon: const Icon(SpotubeIcons.shuffle), icon: const Icon(SpotubeIcons.shuffle),
onPressed: isPlaying ? null : onShuffledPlay, onPressed: playingState == PlayButtonState.playing
? null
: onShuffledPlay,
), ),
), ),
AnimatedScale( AnimatedScale(
@ -155,8 +158,19 @@ class TrackCollectionView<T> extends HookConsumerWidget {
backgroundColor: theme.colorScheme.inversePrimary, backgroundColor: theme.colorScheme.inversePrimary,
), ),
onPressed: tracksSnapshot.data != null ? onPlay : null, onPressed: tracksSnapshot.data != null ? onPlay : null,
child: Icon( child: switch (playingState) {
isPlaying ? SpotubeIcons.stop : SpotubeIcons.play), PlayButtonState.playing =>
const Icon(SpotubeIcons.pause),
PlayButtonState.notPlaying =>
const Icon(SpotubeIcons.play),
PlayButtonState.loading => const SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
strokeWidth: .7,
),
),
},
), ),
), ),
], ],
@ -185,7 +199,7 @@ class TrackCollectionView<T> extends HookConsumerWidget {
title: title, title: title,
description: description, description: description,
titleImage: titleImage, titleImage: titleImage,
isPlaying: isPlaying, playingState: playingState,
onPlay: onPlay, onPlay: onPlay,
onShuffledPlay: onShuffledPlay, onShuffledPlay: onShuffledPlay,
tracksSnapshot: tracksSnapshot, tracksSnapshot: tracksSnapshot,

View File

@ -66,7 +66,7 @@ class SpotubeTrack extends Track {
await client.search("$title - ${artists.join(", ")}").then( await client.search("$title - ${artists.join(", ")}").then(
(res) { (res) {
final siblings = res final siblings = res
.sorted((a, b) => a.views.compareTo(b.views)) .sorted((a, b) => b.views.compareTo(a.views))
.where((item) { .where((item) {
return artists.any( return artists.any(
(artist) => (artist) =>

View File

@ -4,9 +4,11 @@ import 'package:flutter_hooks/flutter_hooks.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';
import 'package:spotube/components/shared/heart_button.dart'; import 'package:spotube/components/shared/heart_button.dart';
import 'package:spotube/components/shared/track_table/track_collection_view/track_collection_heading.dart';
import 'package:spotube/components/shared/track_table/track_collection_view/track_collection_view.dart'; import 'package:spotube/components/shared/track_table/track_collection_view/track_collection_view.dart';
import 'package:spotube/components/shared/track_table/tracks_table_view.dart'; import 'package:spotube/components/shared/track_table/tracks_table_view.dart';
import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/constrains.dart';
import 'package:spotube/models/spotube_track.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/queries/queries.dart'; import 'package:spotube/services/queries/queries.dart';
import 'package:spotube/utils/service_utils.dart'; import 'package:spotube/utils/service_utils.dart';
@ -26,14 +28,15 @@ class AlbumPage extends HookConsumerWidget {
final sortBy = ref.read(trackCollectionSortState(album.id!)); final sortBy = ref.read(trackCollectionSortState(album.id!));
final sortedTracks = ServiceUtils.sortTracks(tracks, sortBy); final sortedTracks = ServiceUtils.sortTracks(tracks, sortBy);
currentTrack ??= sortedTracks.first; currentTrack ??= sortedTracks.first;
final isPlaylistPlaying = playlist.containsTracks(tracks); final isAlbumPlaying = playlist.containsTracks(tracks);
if (!isPlaylistPlaying) { if (!isAlbumPlaying) {
playback.addCollection(album.id!); // for enabling loading indicator
await playback.load( await playback.load(
sortedTracks, sortedTracks,
initialIndex: sortedTracks.indexWhere((s) => s.id == currentTrack?.id), initialIndex: sortedTracks.indexWhere((s) => s.id == currentTrack?.id),
); );
playback.addCollection(album.id!); playback.addCollection(album.id!);
} else if (isPlaylistPlaying && } else if (isAlbumPlaying &&
currentTrack.id != null && currentTrack.id != null &&
currentTrack.id != playlist.activeTrack?.id) { currentTrack.id != playlist.activeTrack?.id) {
await playback.jumpToTrack(currentTrack); await playback.jumpToTrack(currentTrack);
@ -57,12 +60,25 @@ class AlbumPage extends HookConsumerWidget {
final mediaQuery = MediaQuery.of(context); final mediaQuery = MediaQuery.of(context);
final isAlbumPlaying = useMemoized( final isAlbumPlaying = useMemoized(
() => playlist.containsTracks(tracksSnapshot.data ?? []), () => playlist.collections.contains(album.id!),
[playback, tracksSnapshot.data], [playlist, album],
); );
final albumTrackPlaying = useMemoized(
() =>
tracksSnapshot.data?.any((s) => s.id! == playlist.activeTrack?.id!) ==
true &&
playlist.activeTrack is SpotubeTrack,
[playlist.activeTrack, tracksSnapshot.data],
);
return TrackCollectionView( return TrackCollectionView(
id: album.id!, id: album.id!,
isPlaying: isAlbumPlaying, playingState: isAlbumPlaying && albumTrackPlaying
? PlayButtonState.playing
: isAlbumPlaying && !albumTrackPlaying
? PlayButtonState.loading
: PlayButtonState.notPlaying,
title: album.name!, title: album.name!,
titleImage: albumArt, titleImage: albumArt,
tracksSnapshot: tracksSnapshot, tracksSnapshot: tracksSnapshot,

View File

@ -2,12 +2,14 @@ import 'package:flutter/services.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:spotube/components/shared/heart_button.dart'; import 'package:spotube/components/shared/heart_button.dart';
import 'package:spotube/components/shared/track_table/track_collection_view/track_collection_heading.dart';
import 'package:spotube/components/shared/track_table/track_collection_view/track_collection_view.dart'; import 'package:spotube/components/shared/track_table/track_collection_view/track_collection_view.dart';
import 'package:spotube/components/shared/track_table/tracks_table_view.dart'; import 'package:spotube/components/shared/track_table/tracks_table_view.dart';
import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/constrains.dart';
import 'package:spotube/models/logger.dart'; import 'package:spotube/models/logger.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:spotify/spotify.dart'; import 'package:spotify/spotify.dart';
import 'package:spotube/models/spotube_track.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/queries/queries.dart'; import 'package:spotube/services/queries/queries.dart';
@ -31,6 +33,7 @@ class PlaylistView extends HookConsumerWidget {
currentTrack ??= sortedTracks.first; currentTrack ??= sortedTracks.first;
final isPlaylistPlaying = proxyPlaylist.containsTracks(tracks); final isPlaylistPlaying = proxyPlaylist.containsTracks(tracks);
if (!isPlaylistPlaying) { if (!isPlaylistPlaying) {
playback.addCollection(playlist.id!); // for enabling loading indicator
await playback.load( await playback.load(
sortedTracks, sortedTracks,
initialIndex: sortedTracks.indexWhere((s) => s.id == currentTrack?.id), initialIndex: sortedTracks.indexWhere((s) => s.id == currentTrack?.id),
@ -55,8 +58,8 @@ class PlaylistView extends HookConsumerWidget {
final tracksSnapshot = useQueries.playlist.tracksOfQuery(ref, playlist.id!); final tracksSnapshot = useQueries.playlist.tracksOfQuery(ref, playlist.id!);
final isPlaylistPlaying = useMemoized( final isPlaylistPlaying = useMemoized(
() => proxyPlaylist.containsTracks(tracksSnapshot.data ?? []), () => proxyPlaylist.collections.contains(playlist.id!),
[playlistNotifier, tracksSnapshot.data], [proxyPlaylist, playlist],
); );
final titleImage = useMemoized( final titleImage = useMemoized(
@ -66,9 +69,22 @@ class PlaylistView extends HookConsumerWidget {
), ),
[playlist.images]); [playlist.images]);
final playlistTrackPlaying = useMemoized(
() =>
tracksSnapshot.data
?.any((s) => s.id! == proxyPlaylist.activeTrack?.id!) ==
true &&
proxyPlaylist.activeTrack is SpotubeTrack,
[proxyPlaylist.activeTrack, tracksSnapshot.data],
);
return TrackCollectionView( return TrackCollectionView(
id: playlist.id!, id: playlist.id!,
isPlaying: isPlaylistPlaying, playingState: isPlaylistPlaying && playlistTrackPlaying
? PlayButtonState.playing
: isPlaylistPlaying && !playlistTrackPlaying
? PlayButtonState.loading
: PlayButtonState.notPlaying,
title: playlist.name!, title: playlist.name!,
titleImage: titleImage, titleImage: titleImage,
tracksSnapshot: tracksSnapshot, tracksSnapshot: tracksSnapshot,

View File

@ -290,7 +290,12 @@ class ProxyPlaylistNotifier extends PersistedStateNotifier<ProxyPlaylist>
final addableTrack = await SpotubeTrack.fetchFromTrack( final addableTrack = await SpotubeTrack.fetchFromTrack(
tracks.elementAtOrNull(initialIndex) ?? tracks.first, tracks.elementAtOrNull(initialIndex) ?? tracks.first,
youtube, youtube,
).catchError((e, stackTrace) {
return SpotubeTrack.fetchFromTrack(
tracks.elementAtOrNull(initialIndex + 1) ?? tracks.first,
youtube,
); );
});
state = state.copyWith( state = state.copyWith(
tracks: mergeTracks([addableTrack], tracks), tracks: mergeTracks([addableTrack], tracks),