From d239d641ff8f1b3edd64243994fd4a58cf71a5d3 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Fri, 25 Aug 2023 18:54:54 +0600 Subject: [PATCH] feat: paginated user albums --- lib/components/album/album_card.dart | 9 +- lib/components/library/user_albums.dart | 117 ++++++++++-------- lib/components/shared/heart_button.dart | 10 +- .../proxy_playlist_provider.dart | 8 +- lib/services/mutations/album.dart | 4 + lib/services/queries/album.dart | 16 ++- 6 files changed, 100 insertions(+), 64 deletions(-) diff --git a/lib/components/album/album_card.dart b/lib/components/album/album_card.dart index afb637a04..b946209a6 100644 --- a/lib/components/album/album_card.dart +++ b/lib/components/album/album_card.dart @@ -50,8 +50,13 @@ class AlbumCard extends HookConsumerWidget { () => playlist.containsCollection(album.id!), [playlist, album.id], ); - final int marginH = - useBreakpointValue(xs: 10, sm: 10, md: 15, lg: 20, xl: 20, xxl: 20); + + final marginH = useBreakpointValue( + xs: 10, + sm: 10, + md: 15, + others: 20, + ); final updating = useState(false); final spotify = ref.watch(spotifyProvider); diff --git a/lib/components/library/user_albums.dart b/lib/components/library/user_albums.dart index 014a84f62..8df343466 100644 --- a/lib/components/library/user_albums.dart +++ b/lib/components/library/user_albums.dart @@ -3,13 +3,14 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:collection/collection.dart'; import 'package:fuzzywuzzy/fuzzywuzzy.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:spotify/spotify.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/album/album_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/waypoint.dart'; import 'package:spotube/extensions/context.dart'; -import 'package:spotube/hooks/use_breakpoint_value.dart'; import 'package:spotube/provider/authentication_provider.dart'; import 'package:spotube/services/queries/queries.dart'; @@ -23,76 +24,86 @@ class UserAlbums extends HookConsumerWidget { final auth = ref.watch(AuthenticationNotifier.provider); final albumsQuery = useQueries.album.ofMine(ref); - final spacing = useBreakpointValue( - xs: 0, - sm: 0, - others: 20, - ); + final controller = useScrollController(); final searchText = useState(''); + final allAlbums = useMemoized( + () => albumsQuery.pages + .expand((element) => element.items ?? []), + [albumsQuery.pages], + ); + final albums = useMemoized(() { if (searchText.value.isEmpty) { - return albumsQuery.data?.toList() ?? []; + return allAlbums; } - return albumsQuery.data - ?.map((e) => ( - weightedRatio(e.name!, searchText.value), - e, - )) - .sorted((a, b) => b.$1.compareTo(a.$1)) - .where((e) => e.$1 > 50) - .map((e) => e.$2) - .toList() ?? - []; - }, [albumsQuery.data, searchText.value]); + return allAlbums + .map((e) => ( + weightedRatio(e.name!, searchText.value), + e, + )) + .sorted((a, b) => b.$1.compareTo(a.$1)) + .where((e) => e.$1 > 50) + .map((e) => e.$2) + .toList(); + }, [allAlbums, searchText.value]); if (auth == null) { return const AnonymousFallback(); } + final theme = Theme.of(context); + return RefreshIndicator( onRefresh: () async { await albumsQuery.refresh(); }, - child: SingleChildScrollView( - physics: const AlwaysScrollableScrollPhysics(), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: SafeArea( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SearchBar( + child: SafeArea( + child: Scaffold( + appBar: PreferredSize( + preferredSize: const Size.fromHeight(50), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: ColoredBox( + color: theme.scaffoldBackgroundColor, + child: SearchBar( onChanged: (value) => searchText.value = value, leading: const Icon(SpotubeIcons.filter), - hintText: context.l10n.filter_albums, + hintText: context.l10n.filter_artist, ), - const SizedBox(height: 20), - AnimatedCrossFade( - duration: const Duration(milliseconds: 300), - firstChild: Container( - alignment: Alignment.topLeft, - padding: const EdgeInsets.all(16.0), - child: const ShimmerPlaybuttonCard(count: 7), - ), - secondChild: Wrap( - spacing: spacing, // gap between adjacent chips - runSpacing: 20, // gap between lines - alignment: WrapAlignment.center, - children: albums - .map((album) => AlbumCard( - TypeConversionUtils.simpleAlbum_X_Album(album), - )) - .toList(), - ), - crossFadeState: albumsQuery.isLoading || - !albumsQuery.hasData || - searchText.value.isNotEmpty - ? CrossFadeState.showFirst - : CrossFadeState.showSecond, - ), - ], + ), + ), + ), + body: SizedBox.expand( + child: SingleChildScrollView( + padding: const EdgeInsets.all(8.0), + controller: controller, + child: Wrap( + runSpacing: 20, + alignment: WrapAlignment.center, + runAlignment: WrapAlignment.center, + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + if (albums.isEmpty) + Container( + alignment: Alignment.topLeft, + padding: const EdgeInsets.all(16.0), + child: const ShimmerPlaybuttonCard(count: 4), + ), + for (final album in albums) + AlbumCard( + TypeConversionUtils.simpleAlbum_X_Album(album), + ), + if (albumsQuery.hasNextPage) + Waypoint( + controller: controller, + isGrid: true, + onTouchEdge: albumsQuery.fetchNext, + child: const ShimmerPlaybuttonCard(count: 1), + ) + ], + ), ), ), ), diff --git a/lib/components/shared/heart_button.dart b/lib/components/shared/heart_button.dart index cf7909182..2b877ecf5 100644 --- a/lib/components/shared/heart_button.dart +++ b/lib/components/shared/heart_button.dart @@ -1,4 +1,5 @@ import 'package:fl_query/fl_query.dart'; +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'; @@ -188,6 +189,7 @@ class AlbumHeartButton extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { + final client = useQueryClient(); final me = useQueries.user.me(ref); final albumIsSaved = useQueries.album.isSavedForMe(ref, album.id!); @@ -196,10 +198,10 @@ class AlbumHeartButton extends HookConsumerWidget { final toggleAlbumLike = useMutations.album.toggleFavorite( ref, album.id!, - refreshQueries: [ - albumIsSaved.key, - "current-user-albums", - ], + refreshQueries: [albumIsSaved.key], + onData: (_, __) async { + await client.refreshInfiniteQueryAllPages("current-user-albums"); + }, ); if (me.isLoading || !me.hasData) { diff --git a/lib/provider/proxy_playlist/proxy_playlist_provider.dart b/lib/provider/proxy_playlist/proxy_playlist_provider.dart index 4879867b8..fdb056735 100644 --- a/lib/provider/proxy_playlist/proxy_playlist_provider.dart +++ b/lib/provider/proxy_playlist/proxy_playlist_provider.dart @@ -532,7 +532,13 @@ class ProxyPlaylistNotifier extends PersistedStateNotifier final cached = await SkipSegment.box.get(id); if (cached != null && cached.isNotEmpty) { return List.castFrom( - (cached as List).map((json) => SkipSegment.fromJson(json)).toList(), + (cached as List) + .map( + (json) => SkipSegment.fromJson( + Map.castFrom(json), + ), + ) + .toList(), ); } diff --git a/lib/services/mutations/album.dart b/lib/services/mutations/album.dart index 920e11c24..dfc72fccb 100644 --- a/lib/services/mutations/album.dart +++ b/lib/services/mutations/album.dart @@ -9,6 +9,8 @@ class AlbumMutations { WidgetRef ref, String albumId, { List? refreshQueries, + List? refreshInfiniteQueries, + MutationOnDataFn? onData, }) { return useSpotifyMutation( "toggle-album-like/$albumId", @@ -22,6 +24,8 @@ class AlbumMutations { }, ref: ref, refreshQueries: refreshQueries, + refreshInfiniteQueries: refreshInfiniteQueries, + onData: onData, ); } } diff --git a/lib/services/queries/album.dart b/lib/services/queries/album.dart index a0c968eb7..76a7937e9 100644 --- a/lib/services/queries/album.dart +++ b/lib/services/queries/album.dart @@ -9,12 +9,20 @@ import 'package:spotube/provider/user_preferences_provider.dart'; class AlbumQueries { const AlbumQueries(); - Query, dynamic> ofMine(WidgetRef ref) { - return useSpotifyQuery, dynamic>( + InfiniteQuery, dynamic, int> ofMine(WidgetRef ref) { + return useSpotifyInfiniteQuery, dynamic, int>( "current-user-albums", - (spotify) { - return spotify.me.savedAlbums().all(); + (page, spotify) { + return spotify.me.savedAlbums().getPage( + 20, + page * 20, + ); }, + initialPage: 0, + nextPage: (lastPage, lastPageData) => + (lastPageData.items?.length ?? 0) < 20 || lastPageData.isLast + ? null + : lastPage + 1, ref: ref, ); }