feat: paginated user playlists

This commit is contained in:
Kingkor Roy Tirtho 2023-08-07 17:34:56 +06:00
parent 38dc4beb44
commit e7c6813ccb
6 changed files with 110 additions and 55 deletions

View File

@ -11,6 +11,7 @@ import 'package:spotube/components/playlist/playlist_create_dialog.dart';
import 'package:spotube/components/shared/shimmers/shimmer_playbutton_card.dart'; import 'package:spotube/components/shared/shimmers/shimmer_playbutton_card.dart';
import 'package:spotube/components/shared/fallbacks/anonymous_fallback.dart'; import 'package:spotube/components/shared/fallbacks/anonymous_fallback.dart';
import 'package:spotube/components/playlist/playlist_card.dart'; import 'package:spotube/components/playlist/playlist_card.dart';
import 'package:spotube/components/shared/waypoint.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/services/queries/queries.dart'; import 'package:spotube/services/queries/queries.dart';
@ -26,6 +27,12 @@ class UserPlaylists extends HookConsumerWidget {
final playlistsQuery = useQueries.playlist.ofMine(ref); final playlistsQuery = useQueries.playlist.ofMine(ref);
final pagePlaylists = useMemoized(
() => playlistsQuery.pages
.expand((page) => page.items?.toList() ?? <PlaylistSimple>[]),
[playlistsQuery.pages],
);
final likedTracksPlaylist = useMemoized( final likedTracksPlaylist = useMemoized(
() => PlaylistSimple() () => PlaylistSimple()
..name = context.l10n.liked_tracks ..name = context.l10n.liked_tracks
@ -48,12 +55,12 @@ class UserPlaylists extends HookConsumerWidget {
if (searchText.value.isEmpty) { if (searchText.value.isEmpty) {
return [ return [
likedTracksPlaylist, likedTracksPlaylist,
...?playlistsQuery.data, ...pagePlaylists,
]; ];
} }
return [ return [
likedTracksPlaylist, likedTracksPlaylist,
...?playlistsQuery.data, ...pagePlaylists,
] ]
.map((e) => (weightedRatio(e.name!, searchText.value), e)) .map((e) => (weightedRatio(e.name!, searchText.value), e))
.sorted((a, b) => b.$1.compareTo(a.$1)) .sorted((a, b) => b.$1.compareTo(a.$1))
@ -61,9 +68,11 @@ class UserPlaylists extends HookConsumerWidget {
.map((e) => e.$2) .map((e) => e.$2)
.toList(); .toList();
}, },
[playlistsQuery.data, searchText.value], [pagePlaylists, searchText.value],
); );
final controller = useScrollController();
if (auth == null) { if (auth == null) {
return const AnonymousFallback(); return const AnonymousFallback();
} }
@ -71,7 +80,15 @@ class UserPlaylists extends HookConsumerWidget {
return RefreshIndicator( return RefreshIndicator(
onRefresh: playlistsQuery.refresh, onRefresh: playlistsQuery.refresh,
child: SingleChildScrollView( child: SingleChildScrollView(
controller: controller,
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),
child: Waypoint(
controller: controller,
onTouchEdge: () {
if (playlistsQuery.hasNextPage) {
playlistsQuery.fetchNext();
}
},
child: SafeArea( child: SafeArea(
child: Column( child: Column(
children: [ children: [
@ -85,8 +102,8 @@ class UserPlaylists extends HookConsumerWidget {
), ),
AnimatedCrossFade( AnimatedCrossFade(
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
crossFadeState: crossFadeState: playlistsQuery.isLoadingPage ||
playlistsQuery.isLoading || !playlistsQuery.hasData !playlistsQuery.hasPageData
? CrossFadeState.showFirst ? CrossFadeState.showFirst
: CrossFadeState.showSecond, : CrossFadeState.showSecond,
firstChild: firstChild:
@ -118,6 +135,7 @@ class UserPlaylists extends HookConsumerWidget {
), ),
), ),
), ),
),
); );
} }
} }

View File

@ -1,3 +1,4 @@
import 'package:async/async.dart';
import 'package:fl_query_hooks/fl_query_hooks.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';
@ -21,11 +22,33 @@ class PlaylistAddTrackDialog extends HookConsumerWidget {
Widget build(BuildContext context, ref) { Widget build(BuildContext context, ref) {
final spotify = ref.watch(spotifyProvider); final spotify = ref.watch(spotifyProvider);
final userPlaylists = useQueries.playlist.ofMine(ref); final userPlaylists = useQueries.playlist.ofMine(ref);
useEffect(() {
final op = CancelableOperation.fromFuture(
() async {
while (userPlaylists.hasNextPage) {
await userPlaylists.fetchNext();
}
}(),
);
return () {
op.cancel();
};
}, [userPlaylists.hasNextPage]);
final me = useQueries.user.me(ref); final me = useQueries.user.me(ref);
final filteredPlaylists = userPlaylists.data?.where(
final filteredPlaylists = useMemoized(
() => userPlaylists.pages
.expand((page) => page.items?.toList() ?? <PlaylistSimple>[])
.where(
(playlist) => (playlist) =>
playlist.owner?.id != null && playlist.owner!.id == me.data?.id, playlist.owner?.id != null && playlist.owner!.id == me.data?.id,
),
[userPlaylists.pages, me.data?.id],
); );
final playlistsCheck = useState(<String, bool>{}); final playlistsCheck = useState(<String, bool>{});
final queryClient = useQueryClient(); final queryClient = useQueryClient();
@ -70,11 +93,11 @@ class PlaylistAddTrackDialog extends HookConsumerWidget {
content: SizedBox( content: SizedBox(
height: 300, height: 300,
width: 300, width: 300,
child: !userPlaylists.hasData child: userPlaylists.hasNextPage
? const Center(child: CircularProgressIndicator()) ? const Center(child: CircularProgressIndicator())
: ListView.builder( : ListView.builder(
shrinkWrap: true, shrinkWrap: true,
itemCount: filteredPlaylists!.length, itemCount: filteredPlaylists.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final playlist = filteredPlaylists.elementAt(index); final playlist = filteredPlaylists.elementAt(index);
return CheckboxListTile( return CheckboxListTile(

View File

@ -156,7 +156,6 @@ class PlaylistHeartButton extends HookConsumerWidget {
playlist.id!, playlist.id!,
refreshQueries: [ refreshQueries: [
isLikedQuery.key, isLikedQuery.key,
"current-user-playlists",
], ],
); );

View File

@ -1,4 +1,5 @@
import 'package:fl_query/fl_query.dart'; import 'package:fl_query/fl_query.dart';
import 'package:fl_query_hooks/fl_query_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotube/hooks/use_spotify_mutation.dart'; import 'package:spotube/hooks/use_spotify_mutation.dart';
@ -9,7 +10,9 @@ class PlaylistMutations {
WidgetRef ref, WidgetRef ref,
String playlistId, { String playlistId, {
List<String>? refreshQueries, List<String>? refreshQueries,
List<String>? refreshInfiniteQueries,
}) { }) {
final queryClient = useQueryClient();
return useSpotifyMutation<bool, dynamic, bool, dynamic>( return useSpotifyMutation<bool, dynamic, bool, dynamic>(
"toggle-playlist-like/$playlistId", "toggle-playlist-like/$playlistId",
(isLiked, spotify) async { (isLiked, spotify) async {
@ -22,6 +25,11 @@ class PlaylistMutations {
}, },
ref: ref, ref: ref,
refreshQueries: refreshQueries, refreshQueries: refreshQueries,
refreshInfiniteQueries: refreshInfiniteQueries,
onData: (data, recoveryData) async {
await queryClient
.refreshInfiniteQueryAllPages("current-user-playlists");
},
); );
} }

View File

@ -42,7 +42,8 @@ class AlbumQueries {
return useSpotifyQuery<bool, dynamic>( return useSpotifyQuery<bool, dynamic>(
"is-saved-for-me/$album", "is-saved-for-me/$album",
(spotify) { (spotify) {
return spotify.me.isSavedAlbums([album]).then((value) => value.first); return spotify.me
.containsSavedAlbums([album]).then((value) => value[album]);
}, },
ref: ref, ref: ref,
); );

View File

@ -126,12 +126,18 @@ class PlaylistQueries {
); );
} }
Query<Iterable<PlaylistSimple>, dynamic> ofMine(WidgetRef ref) { InfiniteQuery<Page<PlaylistSimple>, dynamic, int> ofMine(WidgetRef ref) {
return useSpotifyQuery<Iterable<PlaylistSimple>, dynamic>( return useSpotifyInfiniteQuery<Page<PlaylistSimple>, dynamic, int>(
"current-user-playlists", "current-user-playlists",
(spotify) { (page, spotify) async {
return spotify.playlists.me.all(); final playlists = await spotify.playlists.me.getPage(10, page * 10);
return playlists;
}, },
initialPage: 0,
nextPage: (lastPage, lastPageData) =>
(lastPageData.items?.length ?? 0) < 10 || lastPageData.isLast
? null
: lastPage + 1,
ref: ref, ref: ref,
); );
} }