From 84cb8d7988d9a57bfce5f4548207cd29c2bfb928 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Sun, 17 Mar 2024 10:49:17 +0600 Subject: [PATCH] feat: use new providers for playlist and albums screen --- .../sections/body/use_is_user_playlist.dart | 14 +-- lib/pages/album/album.dart | 71 +++++--------- lib/pages/playlist/liked_playlist.dart | 8 +- lib/pages/playlist/playlist.dart | 98 +++++++------------ lib/provider/spotify/album/favorite.dart | 8 ++ lib/provider/spotify/album/tracks.dart | 19 ++-- lib/provider/spotify/playlist/favorite.dart | 8 +- lib/provider/spotify/playlist/playlist.dart | 2 +- lib/provider/spotify/utils/provider.dart | 50 ++++++++++ 9 files changed, 152 insertions(+), 126 deletions(-) diff --git a/lib/components/shared/tracks_view/sections/body/use_is_user_playlist.dart b/lib/components/shared/tracks_view/sections/body/use_is_user_playlist.dart index ca3c6706..6c77c766 100644 --- a/lib/components/shared/tracks_view/sections/body/use_is_user_playlist.dart +++ b/lib/components/shared/tracks_view/sections/body/use_is_user_playlist.dart @@ -1,18 +1,18 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:spotube/services/queries/queries.dart'; +import 'package:spotube/provider/spotify/spotify.dart'; bool useIsUserPlaylist(WidgetRef ref, String playlistId) { - final userPlaylistsQuery = useQueries.playlist.ofMineAll(ref); - final me = useQueries.user.me(ref); + final userPlaylistsQuery = ref.watch(favoritePlaylistsProvider); + final me = ref.watch(meProvider); return useMemoized( () => - userPlaylistsQuery.data?.any((e) => + userPlaylistsQuery.value?.items.any((e) => e.id == playlistId && - me.data != null && - e.owner?.id == me.data?.id) ?? + me.value != null && + e.owner?.id == me.value?.id) ?? false, - [userPlaylistsQuery.data, playlistId, me.data], + [userPlaylistsQuery.value, playlistId, me.value], ); } diff --git a/lib/pages/album/album.dart b/lib/pages/album/album.dart index 4578aea2..5c5d98b5 100644 --- a/lib/pages/album/album.dart +++ b/lib/pages/album/album.dart @@ -1,15 +1,10 @@ -import 'package:fl_query_hooks/fl_query_hooks.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotify/spotify.dart'; import 'package:spotube/components/shared/tracks_view/track_view.dart'; import 'package:spotube/components/shared/tracks_view/track_view_props.dart'; import 'package:spotube/extensions/context.dart'; -import 'package:spotube/extensions/infinite_query.dart'; -import 'package:spotube/provider/spotify_provider.dart'; -import 'package:spotube/services/mutations/mutations.dart'; -import 'package:spotube/services/queries/queries.dart'; +import 'package:spotube/provider/spotify/spotify.dart'; import 'package:spotube/utils/type_conversion_utils.dart'; class AlbumPage extends HookConsumerWidget { @@ -21,26 +16,10 @@ class AlbumPage extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { - final spotify = ref.watch(spotifyProvider); - final tracksQuery = useQueries.album.tracksOf(ref, album); - - final tracks = useMemoized(() { - return tracksQuery.pages.expand((element) => element).toList(); - }, [tracksQuery.pages]); - - final client = useQueryClient(); - - 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"); - }, - ); + final tracks = ref.watch(albumTracksProvider(album)); + final tracksNotifier = ref.watch(albumTracksProvider(album).notifier); + final favoriteAlbumsNotifier = ref.watch(favoriteAlbumsProvider.notifier); + final isSavedAlbum = ref.watch(albumsIsSavedProvider(album.id!)); return InheritedTrackView( collectionId: album.id!, @@ -51,29 +30,33 @@ class AlbumPage extends HookConsumerWidget { title: album.name!, description: "${context.l10n.released} • ${album.releaseDate} • ${album.artists!.first.name}", - tracks: tracks, - pagination: PaginationProps.fromQuery( - tracksQuery, - onFetchAll: () { - return tracksQuery.fetchAllTracks(getAllTracks: () async { - final res = await spotify.albums.tracks(album.id!).all(); - - return res - .map((track) => - TypeConversionUtils.simpleTrack_X_Track(track, album)) - .toList(); - }); + tracks: tracks.value?.items ?? [], + pagination: PaginationProps( + hasNextPage: tracks.value?.hasMore ?? false, + isLoading: tracks.isLoading, + onFetchMore: () async { + await tracksNotifier.fetchMore(); + }, + onFetchAll: () async { + return tracksNotifier.fetchAll(); + }, + onRefresh: () async { + ref.invalidate(albumTracksProvider(album)); }, ), routePath: "/album/${album.id}", shareUrl: album.externalUrls!.spotify!, - isLiked: isLiked, - onHeart: albumIsSaved.hasData - ? () async { - await toggleAlbumLike.mutate(isLiked); + isLiked: isSavedAlbum.value ?? false, + onHeart: isSavedAlbum.value == null + ? null + : () async { + if (isSavedAlbum.value!) { + await favoriteAlbumsNotifier.removeFavorites([album.id!]); + } else { + await favoriteAlbumsNotifier.addFavorites([album.id!]); + } return null; - } - : null, + }, child: const TrackView(), ); } diff --git a/lib/pages/playlist/liked_playlist.dart b/lib/pages/playlist/liked_playlist.dart index 0915411c..eeea8cb1 100644 --- a/lib/pages/playlist/liked_playlist.dart +++ b/lib/pages/playlist/liked_playlist.dart @@ -3,7 +3,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotify/spotify.dart'; import 'package:spotube/components/shared/tracks_view/track_view.dart'; import 'package:spotube/components/shared/tracks_view/track_view_props.dart'; -import 'package:spotube/services/queries/queries.dart'; +import 'package:spotube/provider/spotify/spotify.dart'; class LikedPlaylistPage extends HookConsumerWidget { final PlaylistSimple playlist; @@ -14,8 +14,8 @@ class LikedPlaylistPage extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { - final likedTracks = useQueries.playlist.likedTracksQuery(ref); - final tracks = likedTracks.data ?? []; + final likedTracks = ref.watch(likedTracksProvider); + final tracks = likedTracks.value ?? []; return InheritedTrackView( collectionId: playlist.id!, @@ -28,7 +28,7 @@ class LikedPlaylistPage extends HookConsumerWidget { return tracks.toList(); }, onRefresh: () async { - await likedTracks.refresh(); + ref.invalidate(likedTracksProvider); }, ), title: playlist.name!, diff --git a/lib/pages/playlist/playlist.dart b/lib/pages/playlist/playlist.dart index 79f27bbf..b7f366a1 100644 --- a/lib/pages/playlist/playlist.dart +++ b/lib/pages/playlist/playlist.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart' hide Page; -import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotify/spotify.dart'; import 'package:spotube/components/shared/dialogs/prompt_dialog.dart'; @@ -7,10 +6,7 @@ import 'package:spotube/components/shared/tracks_view/sections/body/use_is_user_ import 'package:spotube/components/shared/tracks_view/track_view.dart'; import 'package:spotube/components/shared/tracks_view/track_view_props.dart'; import 'package:spotube/extensions/context.dart'; -import 'package:spotube/extensions/infinite_query.dart'; -import 'package:spotube/provider/spotify_provider.dart'; -import 'package:spotube/services/mutations/mutations.dart'; -import 'package:spotube/services/queries/queries.dart'; +import 'package:spotube/provider/spotify/spotify.dart'; import 'package:spotube/utils/type_conversion_utils.dart'; class PlaylistPage extends HookConsumerWidget { @@ -22,31 +18,13 @@ class PlaylistPage extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { - final spotify = ref.watch(spotifyProvider); - final tracksQuery = useQueries.playlist.tracksOfQuery(ref, playlist.id!); - - final tracks = useMemoized( - () { - return tracksQuery.pages.expand((page) => page).toList(); - }, - [tracksQuery.pages], - ); - - 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, - ], - ); + final tracks = ref.watch(playlistTracksProvider(playlist.id!)); + final tracksNotifier = + ref.watch(playlistTracksProvider(playlist.id!).notifier); + final isFavoritePlaylist = + ref.watch(isFavoritePlaylistProvider(playlist.id!)); + final favoritePlaylistsNotifier = + ref.watch(favoritePlaylistsProvider.notifier); final isUserPlaylist = useIsUserPlaylist(ref, playlist.id!); @@ -56,42 +34,42 @@ class PlaylistPage extends HookConsumerWidget { playlist.images, placeholder: ImagePlaceholder.collection, ), - pagination: PaginationProps.fromQuery( - tracksQuery, - onFetchAll: () { - return tracksQuery.fetchAllTracks( - getAllTracks: () async { - final res = await spotify.playlists - .getTracksByPlaylistId(playlist.id!) - .all(); - return res.toList(); - }, - ); + pagination: PaginationProps( + hasNextPage: tracks.value?.hasMore ?? false, + isLoading: tracks.isLoading, + onFetchMore: tracksNotifier.fetchMore, + onRefresh: () async { + ref.invalidate(playlistTracksProvider(playlist.id!)); + }, + onFetchAll: () async { + return await tracksNotifier.fetchAll(); }, ), title: playlist.name!, description: playlist.description, - tracks: tracks, + tracks: tracks.value?.items ?? [], routePath: '/playlist/${playlist.id}', - isLiked: isLikedQuery.data ?? false, + isLiked: isFavoritePlaylist.value ?? false, shareUrl: playlist.externalUrls?.spotify ?? "", - onHeart: () async { - if (!isLikedQuery.hasData || togglePlaylistLike.isMutating) { - return false; - } - final confirmed = isUserPlaylist - ? await showPromptDialog( - context: context, - title: context.l10n.delete_playlist, - message: context.l10n.delete_playlist_confirmation, - ) - : true; - if (confirmed) { - await togglePlaylistLike.mutate(isLikedQuery.data!); - return isUserPlaylist; - } - return null; - }, + onHeart: isFavoritePlaylist.value == null + ? null + : () async { + final confirmed = isUserPlaylist + ? await showPromptDialog( + context: context, + title: context.l10n.delete_playlist, + message: context.l10n.delete_playlist_confirmation, + ) + : true; + if (!confirmed) return null; + + if (isFavoritePlaylist.value!) { + await favoritePlaylistsNotifier.removeFavorite(playlist); + } else { + await favoritePlaylistsNotifier.addFavorite(playlist); + } + return isUserPlaylist; + }, child: const TrackView(), ); } diff --git a/lib/provider/spotify/album/favorite.dart b/lib/provider/spotify/album/favorite.dart index e93176e2..cf444d49 100644 --- a/lib/provider/spotify/album/favorite.dart +++ b/lib/provider/spotify/album/favorite.dart @@ -55,6 +55,10 @@ class FavoriteAlbumNotifier ], ); }); + + for (final id in ids) { + ref.invalidate(albumsIsSavedProvider(id)); + } } Future removeFavorites(List ids) async { @@ -69,6 +73,10 @@ class FavoriteAlbumNotifier .toList(), ); }); + + for (final id in ids) { + ref.invalidate(albumsIsSavedProvider(id)); + } } } diff --git a/lib/provider/spotify/album/tracks.dart b/lib/provider/spotify/album/tracks.dart index 4cc56331..b9bc9173 100644 --- a/lib/provider/spotify/album/tracks.dart +++ b/lib/provider/spotify/album/tracks.dart @@ -1,6 +1,6 @@ part of '../spotify.dart'; -class AlbumTracksState extends PaginatedState { +class AlbumTracksState extends PaginatedState { AlbumTracksState({ required super.items, required super.offset, @@ -10,7 +10,7 @@ class AlbumTracksState extends PaginatedState { @override AlbumTracksState copyWith({ - List? items, + List? items, int? offset, int? limit, bool? hasMore, @@ -24,14 +24,17 @@ class AlbumTracksState extends PaginatedState { } } -class AlbumTracksNotifier extends FamilyPaginatedAsyncNotifier { +class AlbumTracksNotifier + extends FamilyPaginatedAsyncNotifier { AlbumTracksNotifier() : super(); @override fetch(arg, offset, limit) async { - final tracks = await spotify.albums.tracks(arg).getPage(limit, offset); - return tracks.items?.toList() ?? []; + final tracks = await spotify.albums.tracks(arg.id!).getPage(limit, offset); + return tracks.items + ?.map((e) => TypeConversionUtils.simpleTrack_X_Track(e, arg)) + .toList() ?? + []; } @override @@ -47,7 +50,7 @@ class AlbumTracksNotifier extends FamilyPaginatedAsyncNotifier( +final albumTracksProvider = AsyncNotifierProviderFamily( () => AlbumTracksNotifier(), ); diff --git a/lib/provider/spotify/playlist/favorite.dart b/lib/provider/spotify/playlist/favorite.dart index 9296bc51..d8fbfb9c 100644 --- a/lib/provider/spotify/playlist/favorite.dart +++ b/lib/provider/spotify/playlist/favorite.dart @@ -52,21 +52,25 @@ class FavoritePlaylistsNotifier } Future addFavorite(PlaylistSimple playlist) async { - update((state) async { + await update((state) async { await spotify.playlists.followPlaylist(playlist.id!); return state.copyWith( items: [...state.items, playlist], ); }); + + ref.invalidate(isFavoritePlaylistProvider(playlist.id!)); } Future removeFavorite(PlaylistSimple playlist) async { - update((state) async { + await update((state) async { await spotify.playlists.unfollowPlaylist(playlist.id!); return state.copyWith( items: state.items.where((e) => e.id != playlist.id).toList(), ); }); + + ref.invalidate(isFavoritePlaylistProvider(playlist.id!)); } } diff --git a/lib/provider/spotify/playlist/playlist.dart b/lib/provider/spotify/playlist/playlist.dart index 59226865..3ead2b16 100644 --- a/lib/provider/spotify/playlist/playlist.dart +++ b/lib/provider/spotify/playlist/playlist.dart @@ -54,7 +54,7 @@ class PlaylistNotifier extends FamilyAsyncNotifier { state.value!.id!, ); - ref.refresh(playlistTracksProvider(state.value!.id!)); + ref.invalidate(playlistTracksProvider(state.value!.id!)); } Future modify(PlaylistInput input) async { diff --git a/lib/provider/spotify/utils/provider.dart b/lib/provider/spotify/utils/provider.dart index 6e6a1aee..c952f9e8 100644 --- a/lib/provider/spotify/utils/provider.dart +++ b/lib/provider/spotify/utils/provider.dart @@ -80,6 +80,31 @@ abstract class FamilyPaginatedAsyncNotifier< }, ); } + + Future> fetchAll() async { + if (state.value == null) return []; + if (!state.value!.hasMore) return state.value!.items; + + bool hasMore = true; + while (hasMore) { + await update((state) async { + final items = await fetch( + arg, + state.offset + state.limit, + state.limit, + ); + + hasMore = items.length == state.limit; + return state.copyWith( + items: [...state.items, ...items], + offset: state.offset + state.limit, + hasMore: hasMore, + ) as T; + }); + } + + return state.value!.items; + } } abstract class FamilyCursorPaginatedAsyncNotifier< @@ -111,4 +136,29 @@ abstract class FamilyCursorPaginatedAsyncNotifier< }, ); } + + Future> fetchAll() async { + if (state.value == null) return []; + if (!state.value!.hasMore) return state.value!.items; + + bool hasMore = true; + while (hasMore) { + await update((state) async { + final items = await fetch( + arg, + state.offset, + state.limit, + ); + + hasMore = items.$1.length == state.limit; + return state.copyWith( + items: [...state.items, ...items.$1], + offset: items.$2, + hasMore: hasMore, + ) as T; + }); + } + + return state.value!.items; + } }