mirror of
https://github.com/KRTirtho/spotube.git
synced 2026-05-08 16:24:36 +00:00
feat: use new providers for playlist and albums screen
This commit is contained in:
parent
dc999b81f0
commit
84cb8d7988
@ -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],
|
||||
);
|
||||
}
|
||||
|
||||
@ -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(),
|
||||
);
|
||||
}
|
||||
|
||||
@ -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 ?? <Track>[];
|
||||
final likedTracks = ref.watch(likedTracksProvider);
|
||||
final tracks = likedTracks.value ?? <Track>[];
|
||||
|
||||
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!,
|
||||
|
||||
@ -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(),
|
||||
);
|
||||
}
|
||||
|
||||
@ -55,6 +55,10 @@ class FavoriteAlbumNotifier
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
for (final id in ids) {
|
||||
ref.invalidate(albumsIsSavedProvider(id));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> removeFavorites(List<String> ids) async {
|
||||
@ -69,6 +73,10 @@ class FavoriteAlbumNotifier
|
||||
.toList(),
|
||||
);
|
||||
});
|
||||
|
||||
for (final id in ids) {
|
||||
ref.invalidate(albumsIsSavedProvider(id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
part of '../spotify.dart';
|
||||
|
||||
class AlbumTracksState extends PaginatedState<TrackSimple> {
|
||||
class AlbumTracksState extends PaginatedState<Track> {
|
||||
AlbumTracksState({
|
||||
required super.items,
|
||||
required super.offset,
|
||||
@ -10,7 +10,7 @@ class AlbumTracksState extends PaginatedState<TrackSimple> {
|
||||
|
||||
@override
|
||||
AlbumTracksState copyWith({
|
||||
List<TrackSimple>? items,
|
||||
List<Track>? items,
|
||||
int? offset,
|
||||
int? limit,
|
||||
bool? hasMore,
|
||||
@ -24,14 +24,17 @@ class AlbumTracksState extends PaginatedState<TrackSimple> {
|
||||
}
|
||||
}
|
||||
|
||||
class AlbumTracksNotifier extends FamilyPaginatedAsyncNotifier<TrackSimple,
|
||||
AlbumTracksState, String> {
|
||||
class AlbumTracksNotifier
|
||||
extends FamilyPaginatedAsyncNotifier<Track, AlbumTracksState, AlbumSimple> {
|
||||
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<TrackSimple,
|
||||
}
|
||||
}
|
||||
|
||||
final albumTracksProvider =
|
||||
AsyncNotifierProviderFamily<AlbumTracksNotifier, AlbumTracksState, String>(
|
||||
final albumTracksProvider = AsyncNotifierProviderFamily<AlbumTracksNotifier,
|
||||
AlbumTracksState, AlbumSimple>(
|
||||
() => AlbumTracksNotifier(),
|
||||
);
|
||||
|
||||
@ -52,21 +52,25 @@ class FavoritePlaylistsNotifier
|
||||
}
|
||||
|
||||
Future<void> 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<void> 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!));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -54,7 +54,7 @@ class PlaylistNotifier extends FamilyAsyncNotifier<Playlist, String> {
|
||||
state.value!.id!,
|
||||
);
|
||||
|
||||
ref.refresh(playlistTracksProvider(state.value!.id!));
|
||||
ref.invalidate(playlistTracksProvider(state.value!.id!));
|
||||
}
|
||||
|
||||
Future<void> modify(PlaylistInput input) async {
|
||||
|
||||
@ -80,6 +80,31 @@ abstract class FamilyPaginatedAsyncNotifier<
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<List<K>> 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<List<K>> 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;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user