feat: use provider for playlist and album card and heart button

This commit is contained in:
Kingkor Roy Tirtho 2024-03-17 12:33:11 +06:00
parent 4ae530d29d
commit 25badae7ad
6 changed files with 33 additions and 213 deletions

View File

@ -1,15 +1,12 @@
import 'package:fl_query_hooks/fl_query_hooks.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.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:spotify/spotify.dart'; import 'package:spotify/spotify.dart';
import 'package:spotube/components/shared/playbutton_card.dart'; import 'package:spotube/components/shared/playbutton_card.dart';
import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/context.dart';
import 'package:spotube/extensions/infinite_query.dart';
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
import 'package:spotube/provider/spotify_provider.dart'; import 'package:spotube/provider/spotify/spotify.dart';
import 'package:spotube/services/audio_player/audio_player.dart'; import 'package:spotube/services/audio_player/audio_player.dart';
import 'package:spotube/services/queries/album.dart';
import 'package:spotube/utils/service_utils.dart'; import 'package:spotube/utils/service_utils.dart';
import 'package:spotube/utils/type_conversion_utils.dart'; import 'package:spotube/utils/type_conversion_utils.dart';
@ -31,15 +28,12 @@ class AlbumCard extends HookConsumerWidget {
useStream(audioPlayer.playingStream).data ?? audioPlayer.isPlaying; useStream(audioPlayer.playingStream).data ?? audioPlayer.isPlaying;
final playlistNotifier = ref.watch(ProxyPlaylistNotifier.notifier); final playlistNotifier = ref.watch(ProxyPlaylistNotifier.notifier);
final queryClient = useQueryClient();
bool isPlaylistPlaying = useMemoized( bool isPlaylistPlaying = useMemoized(
() => playlist.containsCollection(album.id!), () => playlist.containsCollection(album.id!),
[playlist, album.id], [playlist, album.id],
); );
final updating = useState(false); final updating = useState(false);
final spotify = ref.watch(spotifyProvider);
final scaffoldMessenger = ScaffoldMessenger.maybeOf(context); final scaffoldMessenger = ScaffoldMessenger.maybeOf(context);
@ -50,23 +44,8 @@ class AlbumCard extends HookConsumerWidget {
TypeConversionUtils.simpleTrack_X_Track(track, album)) TypeConversionUtils.simpleTrack_X_Track(track, album))
.toList(); .toList();
} }
final job = AlbumQueries.tracksOfJob(album.id!); await ref.read(albumTracksProvider(album).future);
return ref.read(albumTracksProvider(album).notifier).fetchAll();
final query = queryClient.createInfiniteQuery(
job.queryKey,
(page) => job.task(page, (spotify: spotify, album: album)),
initialPage: 0,
nextPage: job.nextPage,
);
return await query.fetchAllTracks(
getAllTracks: () async {
final res = await spotify.albums.tracks(album.id!).all();
return res
.map((e) => TypeConversionUtils.simpleTrack_X_Track(e, album))
.toList();
},
);
} }
return PlaybuttonCard( return PlaybuttonCard(

View File

@ -34,7 +34,6 @@ class PlayerActions extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, ref) { Widget build(BuildContext context, ref) {
final mediaQuery = MediaQuery.of(context);
final playlist = ref.watch(ProxyPlaylistNotifier.provider); final playlist = ref.watch(ProxyPlaylistNotifier.provider);
final isLocalTrack = playlist.activeTrack is LocalTrack; final isLocalTrack = playlist.activeTrack is LocalTrack;
ref.watch(downloadManagerProvider); ref.watch(downloadManagerProvider);

View File

@ -1,14 +1,11 @@
import 'package:fl_query/fl_query.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.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:spotify/spotify.dart'; import 'package:spotify/spotify.dart';
import 'package:spotube/components/shared/playbutton_card.dart'; import 'package:spotube/components/shared/playbutton_card.dart';
import 'package:spotube/extensions/infinite_query.dart';
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
import 'package:spotube/provider/spotify_provider.dart'; import 'package:spotube/provider/spotify/spotify.dart';
import 'package:spotube/services/audio_player/audio_player.dart'; import 'package:spotube/services/audio_player/audio_player.dart';
import 'package:spotube/services/queries/queries.dart';
import 'package:spotube/utils/service_utils.dart'; import 'package:spotube/utils/service_utils.dart';
import 'package:spotube/utils/type_conversion_utils.dart'; import 'package:spotube/utils/type_conversion_utils.dart';
@ -24,7 +21,6 @@ class PlaylistCard extends HookConsumerWidget {
final playlistNotifier = ref.watch(ProxyPlaylistNotifier.notifier); final playlistNotifier = ref.watch(ProxyPlaylistNotifier.notifier);
final playing = final playing =
useStream(audioPlayer.playingStream).data ?? audioPlayer.isPlaying; useStream(audioPlayer.playingStream).data ?? audioPlayer.isPlaying;
final queryClient = QueryClient.of(context);
final tracks = useState<List<TrackSimple>?>(null); final tracks = useState<List<TrackSimple>?>(null);
bool isPlaylistPlaying = useMemoized( bool isPlaylistPlaying = useMemoized(
() => playlistQueue.containsCollection(playlist.id!), () => playlistQueue.containsCollection(playlist.id!),
@ -32,32 +28,16 @@ class PlaylistCard extends HookConsumerWidget {
); );
final updating = useState(false); final updating = useState(false);
final spotify = ref.watch(spotifyProvider); final me = ref.watch(meProvider);
final me = useQueries.user.me(ref);
Future<List<Track>> fetchAllTracks() async { Future<List<Track>> fetchAllTracks() async {
if (playlist.id == 'user-liked-tracks') { if (playlist.id == 'user-liked-tracks') {
return await queryClient.fetchQuery( return await ref.read(likedTracksProvider.future);
"user-liked-tracks",
() => useQueries.playlist.likedTracks(spotify),
) ??
[];
} }
final query = queryClient.createInfiniteQuery<List<Track>, dynamic, int>( await ref.read(playlistTracksProvider(playlist.id!).future);
"playlist-tracks/${playlist.id}",
(page) => useQueries.playlist.tracksOf(page, spotify, playlist.id!),
initialPage: 0,
nextPage: useQueries.playlist.tracksOfQueryNextPage,
);
return await query.fetchAllTracks( return ref.read(playlistTracksProvider(playlist.id!).notifier).fetchAll();
getAllTracks: () async {
final res =
await spotify.playlists.getTracksByPlaylistId(playlist.id!).all();
return res.toList();
},
);
} }
return PlaybuttonCard( return PlaybuttonCard(
@ -71,7 +51,7 @@ class PlaylistCard extends HookConsumerWidget {
isPlaying: isPlaylistPlaying, isPlaying: isPlaylistPlaying,
isLoading: isLoading:
(isPlaylistPlaying && playlistQueue.isFetching) || updating.value, (isPlaylistPlaying && playlistQueue.isFetching) || updating.value,
isOwner: playlist.owner?.id == me.data?.id && me.data?.id != null, isOwner: playlist.owner?.id == me.value?.id && me.value?.id != null,
onTap: () { onTap: () {
ServiceUtils.push( ServiceUtils.push(
context, context,

View File

@ -1,5 +1,3 @@
import 'package:fl_query/fl_query.dart';
import 'package:fl_query_hooks/fl_query_hooks.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.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';
@ -8,8 +6,7 @@ import 'package:spotify/spotify.dart';
import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/context.dart';
import 'package:spotube/provider/authentication_provider.dart'; import 'package:spotube/provider/authentication_provider.dart';
import 'package:spotube/provider/scrobbler_provider.dart'; import 'package:spotube/provider/scrobbler_provider.dart';
import 'package:spotube/services/mutations/mutations.dart'; import 'package:spotube/provider/spotify/spotify.dart';
import 'package:spotube/services/queries/queries.dart';
class HeartButton extends HookConsumerWidget { class HeartButton extends HookConsumerWidget {
final bool isLiked; final bool isLiked;
@ -60,75 +57,32 @@ class HeartButton extends HookConsumerWidget {
typedef UseTrackToggleLike = ({ typedef UseTrackToggleLike = ({
bool isLiked, bool isLiked,
Mutation<bool, dynamic, bool> toggleTrackLike, Future<void> Function(Track track) toggleTrackLike,
Query<User?, dynamic> me,
}); });
UseTrackToggleLike useTrackToggleLike(Track track, WidgetRef ref) { UseTrackToggleLike useTrackToggleLike(Track track, WidgetRef ref) {
final me = useQueries.user.me(ref); final savedTracks = ref.watch(likedTracksProvider);
final savedTracksNotifier = ref.watch(likedTracksProvider.notifier);
final savedTracks = useQueries.playlist.likedTracksQuery(ref);
final isLiked = useMemoized( final isLiked = useMemoized(
() => savedTracks.data?.any((element) => element.id == track.id) ?? false, () => savedTracks.value?.any((element) => element.id == track.id) ?? false,
[savedTracks.data, track.id], [savedTracks.value, track.id],
); );
final mounted = useIsMounted();
final scrobblerNotifier = ref.read(scrobblerProvider.notifier); final scrobblerNotifier = ref.read(scrobblerProvider.notifier);
final toggleTrackLike = useMutations.track.toggleFavorite( return (
ref, isLiked: isLiked,
track.id!, toggleTrackLike: (track) async {
onMutate: (isLiked) { await savedTracksNotifier.toggleFavorite(track);
if (isLiked) {
savedTracks.setData( if (!isLiked) {
savedTracks.data
?.where((element) => element.id != track.id)
.toList() ??
[],
);
} else {
savedTracks.setData(
[
...?savedTracks.data,
track,
],
);
}
return isLiked;
},
onData: (isLiked, recoveryData) async {
await savedTracks.refresh();
if (isLiked) {
await scrobblerNotifier.love(track); await scrobblerNotifier.love(track);
} else { } else {
await scrobblerNotifier.unlove(track); await scrobblerNotifier.unlove(track);
} }
}, },
onError: (payload, isLiked) {
if (!mounted()) return;
if (isLiked != true) {
savedTracks.setData(
savedTracks.data
?.where((element) => element.id != track.id)
.toList() ??
[],
);
} else {
savedTracks.setData(
[
...?savedTracks.data,
track,
],
);
}
},
); );
return (isLiked: isLiked, toggleTrackLike: toggleTrackLike, me: me);
} }
class TrackHeartButton extends HookConsumerWidget { class TrackHeartButton extends HookConsumerWidget {
@ -140,10 +94,11 @@ class TrackHeartButton extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, ref) { Widget build(BuildContext context, ref) {
final savedTracks = useQueries.playlist.likedTracksQuery(ref); final savedTracks = ref.watch(likedTracksProvider);
final (:me, :isLiked, :toggleTrackLike) = useTrackToggleLike(track, ref); final me = ref.watch(meProvider);
final (:isLiked, :toggleTrackLike) = useTrackToggleLike(track, ref);
if (me.isLoading || !me.hasData) { if (me.isLoading) {
return const CircularProgressIndicator(); return const CircularProgressIndicator();
} }
@ -152,104 +107,9 @@ class TrackHeartButton extends HookConsumerWidget {
? context.l10n.remove_from_favorites ? context.l10n.remove_from_favorites
: context.l10n.save_as_favorite, : context.l10n.save_as_favorite,
isLiked: isLiked, isLiked: isLiked,
onPressed: savedTracks.hasData onPressed: savedTracks.value != null
? () { ? () {
toggleTrackLike.mutate(isLiked); toggleTrackLike(track);
}
: null,
);
}
}
class PlaylistHeartButton extends HookConsumerWidget {
final PlaylistSimple playlist;
final IconData? icon;
final ValueChanged<bool>? onData;
const PlaylistHeartButton({
required this.playlist,
super.key,
this.icon,
this.onData,
});
@override
Widget build(BuildContext context, ref) {
final me = useQueries.user.me(ref);
final isLikedQuery = useQueries.playlist.doesUserFollow(
ref,
playlist.id!,
me.data?.id ?? '',
);
final togglePlaylistLike = useMutations.playlist.toggleFavorite(
ref,
playlist.id!,
refreshQueries: [
isLikedQuery.key,
],
onData: onData,
);
if (me.isLoading || !me.hasData) {
return const CircularProgressIndicator();
}
return HeartButton(
isLiked: isLikedQuery.data ?? false,
tooltip: isLikedQuery.data ?? false
? context.l10n.remove_from_favorites
: context.l10n.save_as_favorite,
color: Colors.white,
icon: icon,
onPressed: isLikedQuery.hasData
? () {
togglePlaylistLike.mutate(isLikedQuery.data!);
}
: null,
);
}
}
class AlbumHeartButton extends HookConsumerWidget {
final AlbumSimple album;
const AlbumHeartButton({
required this.album,
super.key,
});
@override
Widget build(BuildContext context, ref) {
final client = useQueryClient();
final me = useQueries.user.me(ref);
final albumIsSaved = useQueries.album.isSavedForMe(ref, album.id!);
final isLiked = albumIsSaved.data ?? false;
final toggleAlbumLike = useMutations.album.toggleFavorite(
ref,
album.id!,
refreshQueries: [albumIsSaved.key],
onData: (_, __) async {
await client.refreshInfiniteQueryAllPages("current-user-albums");
},
);
if (me.isLoading || !me.hasData) {
return const CircularProgressIndicator();
}
return HeartButton(
isLiked: isLiked,
tooltip: isLiked
? context.l10n.remove_from_favorites
: context.l10n.save_as_favorite,
color: Colors.white,
onPressed: albumIsSaved.hasData
? () {
toggleAlbumLike.mutate(isLiked);
} }
: null, : null,
); );

View File

@ -23,6 +23,7 @@ import 'package:spotube/provider/authentication_provider.dart';
import 'package:spotube/provider/blacklist_provider.dart'; import 'package:spotube/provider/blacklist_provider.dart';
import 'package:spotube/provider/download_manager_provider.dart'; import 'package:spotube/provider/download_manager_provider.dart';
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
import 'package:spotube/provider/spotify/spotify.dart';
import 'package:spotube/provider/spotify_provider.dart'; import 'package:spotube/provider/spotify_provider.dart';
import 'package:spotube/services/mutations/mutations.dart'; import 'package:spotube/services/mutations/mutations.dart';
import 'package:spotube/services/queries/search.dart'; import 'package:spotube/services/queries/search.dart';
@ -176,6 +177,7 @@ class TrackOptions extends HookConsumerWidget {
ref.watch(downloadManagerProvider); ref.watch(downloadManagerProvider);
final downloadManager = ref.watch(downloadManagerProvider.notifier); final downloadManager = ref.watch(downloadManagerProvider.notifier);
final blacklist = ref.watch(BlackListNotifier.provider); final blacklist = ref.watch(BlackListNotifier.provider);
final me = ref.watch(meProvider);
final favorites = useTrackToggleLike(track, ref); final favorites = useTrackToggleLike(track, ref);
@ -220,7 +222,7 @@ class TrackOptions extends HookConsumerWidget {
break; break;
case TrackOptionValue.delete: case TrackOptionValue.delete:
await File((track as LocalTrack).path).delete(); await File((track as LocalTrack).path).delete();
ref.refresh(localTracksProvider); ref.invalidate(localTracksProvider);
break; break;
case TrackOptionValue.addToQueue: case TrackOptionValue.addToQueue:
await playback.addTrack(track); await playback.addTrack(track);
@ -257,7 +259,7 @@ class TrackOptions extends HookConsumerWidget {
); );
break; break;
case TrackOptionValue.favorite: case TrackOptionValue.favorite:
favorites.toggleTrackLike.mutate(favorites.isLiked); favorites.toggleTrackLike(track);
break; break;
case TrackOptionValue.addToPlaylist: case TrackOptionValue.addToPlaylist:
actionAddToPlaylist(context, track); actionAddToPlaylist(context, track);
@ -361,7 +363,7 @@ class TrackOptions extends HookConsumerWidget {
leading: const Icon(SpotubeIcons.queueRemove), leading: const Icon(SpotubeIcons.queueRemove),
title: Text(context.l10n.remove_from_queue), title: Text(context.l10n.remove_from_queue),
), ),
if (favorites.me.hasData) if (me.value != null)
PopSheetEntry( PopSheetEntry(
value: TrackOptionValue.favorite, value: TrackOptionValue.favorite,
leading: favorites.isLiked leading: favorites.isLiked

View File

@ -25,7 +25,7 @@ class LikedTracksNotifier extends AsyncNotifier<List<Track>> with Persistence {
return tracks.where((e) => e.id != track.id).toList(); return tracks.where((e) => e.id != track.id).toList();
} else { } else {
await spotify.tracks.me.saveOne(track.id!); await spotify.tracks.me.saveOne(track.id!);
return [...tracks, track]; return [track, ...tracks];
} }
}); });
} }