Skip to content

Commit

Permalink
feat: paginated user albums
Browse files Browse the repository at this point in the history
  • Loading branch information
KRTirtho committed Aug 25, 2023
1 parent 6ced0a0 commit d239d64
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 64 deletions.
9 changes: 7 additions & 2 deletions lib/components/album/album_card.dart
Expand Up @@ -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<int>(
xs: 10,
sm: 10,
md: 15,
others: 20,
);

final updating = useState(false);
final spotify = ref.watch(spotifyProvider);
Expand Down
117 changes: 64 additions & 53 deletions lib/components/library/user_albums.dart
Expand Up @@ -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';

Expand All @@ -23,76 +24,86 @@ class UserAlbums extends HookConsumerWidget {
final auth = ref.watch(AuthenticationNotifier.provider);
final albumsQuery = useQueries.album.ofMine(ref);

final spacing = useBreakpointValue<double>(
xs: 0,
sm: 0,
others: 20,
);
final controller = useScrollController();

final searchText = useState('');

final allAlbums = useMemoized(
() => albumsQuery.pages
.expand((element) => element.items ?? <AlbumSimple>[]),
[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),
)
],
),
),
),
),
Expand Down
10 changes: 6 additions & 4 deletions 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';
Expand Down Expand Up @@ -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!);
Expand All @@ -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) {
Expand Down
8 changes: 7 additions & 1 deletion lib/provider/proxy_playlist/proxy_playlist_provider.dart
Expand Up @@ -532,7 +532,13 @@ class ProxyPlaylistNotifier extends PersistedStateNotifier<ProxyPlaylist>
final cached = await SkipSegment.box.get(id);
if (cached != null && cached.isNotEmpty) {
return List.castFrom<dynamic, SkipSegment>(
(cached as List).map((json) => SkipSegment.fromJson(json)).toList(),
(cached as List)
.map(
(json) => SkipSegment.fromJson(
Map.castFrom<dynamic, dynamic, String, dynamic>(json),
),
)
.toList(),
);
}

Expand Down
4 changes: 4 additions & 0 deletions lib/services/mutations/album.dart
Expand Up @@ -9,6 +9,8 @@ class AlbumMutations {
WidgetRef ref,
String albumId, {
List<String>? refreshQueries,
List<String>? refreshInfiniteQueries,
MutationOnDataFn<bool, dynamic>? onData,
}) {
return useSpotifyMutation<bool, dynamic, bool, dynamic>(
"toggle-album-like/$albumId",
Expand All @@ -22,6 +24,8 @@ class AlbumMutations {
},
ref: ref,
refreshQueries: refreshQueries,
refreshInfiniteQueries: refreshInfiniteQueries,
onData: onData,
);
}
}
16 changes: 12 additions & 4 deletions lib/services/queries/album.dart
Expand Up @@ -9,12 +9,20 @@ import 'package:spotube/provider/user_preferences_provider.dart';
class AlbumQueries {
const AlbumQueries();

Query<Iterable<AlbumSimple>, dynamic> ofMine(WidgetRef ref) {
return useSpotifyQuery<Iterable<AlbumSimple>, dynamic>(
InfiniteQuery<Page<AlbumSimple>, dynamic, int> ofMine(WidgetRef ref) {
return useSpotifyInfiniteQuery<Page<AlbumSimple>, 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,
);
}
Expand Down

0 comments on commit d239d64

Please sign in to comment.