feat: use provider in add track dialog

This commit is contained in:
Kingkor Roy Tirtho 2024-03-17 15:21:57 +06:00
parent 82802fef2c
commit d2a9ff6652
4 changed files with 79 additions and 45 deletions

View File

@ -1,4 +1,3 @@
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';
import 'package:gap/gap.dart'; import 'package:gap/gap.dart';
@ -8,8 +7,7 @@ import 'package:spotify/spotify.dart';
import 'package:spotube/components/playlist/playlist_create_dialog.dart'; import 'package:spotube/components/playlist/playlist_create_dialog.dart';
import 'package:spotube/components/shared/image/universal_image.dart'; import 'package:spotube/components/shared/image/universal_image.dart';
import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/context.dart';
import 'package:spotube/provider/spotify_provider.dart'; import 'package:spotube/provider/spotify/spotify.dart';
import 'package:spotube/services/queries/queries.dart';
import 'package:spotube/utils/type_conversion_utils.dart'; import 'package:spotube/utils/type_conversion_utils.dart';
class PlaylistAddTrackDialog extends HookConsumerWidget { class PlaylistAddTrackDialog extends HookConsumerWidget {
@ -25,27 +23,34 @@ class PlaylistAddTrackDialog extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, ref) { Widget build(BuildContext context, ref) {
final ThemeData(:textTheme) = Theme.of(context); final ThemeData(:textTheme) = Theme.of(context);
final spotify = ref.watch(spotifyProvider); final userPlaylists = ref.watch(favoritePlaylistsProvider);
final userPlaylists = useQueries.playlist.ofMineAll(ref); final favoritePlaylistsNotifier =
ref.watch(favoritePlaylistsProvider.notifier);
final me = useQueries.user.me(ref); final me = ref.watch(meProvider);
final filteredPlaylists = useMemoized( final filteredPlaylists = useMemoized(
() => () =>
userPlaylists.data userPlaylists.asData?.value.items
?.where( .where(
(playlist) => (playlist) =>
playlist.owner?.id != null && playlist.owner?.id != null &&
playlist.owner!.id == me.data?.id && playlist.owner!.id == me.asData?.value.id &&
playlist.id != openFromPlaylist, playlist.id != openFromPlaylist,
) )
.toList() ?? .toList() ??
[], [],
[userPlaylists.data, me.data?.id, openFromPlaylist], [userPlaylists.asData?.value, me.asData?.value.id, openFromPlaylist],
); );
final playlistsCheck = useState(<String, bool>{}); final playlistsCheck = useState(<String, bool>{});
final queryClient = useQueryClient();
useEffect(() {
if (userPlaylists.asData?.value != null) {
favoritePlaylistsNotifier.fetchAll();
}
return null;
}, [userPlaylists.asData?.value]);
Future<void> onAdd() async { Future<void> onAdd() async {
final selectedPlaylists = playlistsCheck.value.entries final selectedPlaylists = playlistsCheck.value.entries
@ -54,21 +59,12 @@ class PlaylistAddTrackDialog extends HookConsumerWidget {
await Future.wait( await Future.wait(
selectedPlaylists.map( selectedPlaylists.map(
(playlistId) => spotify.playlists.addTracks( (playlistId) => favoritePlaylistsNotifier.addTracks(
tracks playlistId,
.map( tracks.map((e) => e.id!).toList(),
(track) => track.uri!, ),
)
.toList(),
playlistId),
), ),
).then((_) => Navigator.pop(context, true)); ).then((_) => Navigator.pop(context, true));
await queryClient.refreshQueries(
selectedPlaylists
.map((playlistId) => "playlist-tracks/$playlistId")
.toList(),
);
} }
return AlertDialog( return AlertDialog(

View File

@ -72,6 +72,19 @@ class FavoritePlaylistsNotifier
ref.invalidate(isFavoritePlaylistProvider(playlist.id!)); ref.invalidate(isFavoritePlaylistProvider(playlist.id!));
} }
Future<void> addTracks(String playlistId, List<String> trackIds) async {
if (state.value == null) return;
final spotify = ref.read(spotifyProvider);
await spotify.playlists.addTracks(
trackIds.map((id) => 'spotify:track:$id').toList(),
playlistId,
);
ref.invalidate(playlistTracksProvider(playlistId));
}
} }
final favoritePlaylistsProvider = final favoritePlaylistsProvider =
@ -79,13 +92,6 @@ final favoritePlaylistsProvider =
() => FavoritePlaylistsNotifier(), () => FavoritePlaylistsNotifier(),
); );
final allFavoritePlaylistsProvider = FutureProvider<List<PlaylistSimple>>(
(ref) async {
final spotify = ref.watch(spotifyProvider);
return (await spotify.playlists.me.all()).toList();
},
);
final isFavoritePlaylistProvider = FutureProvider.family<bool, String>( final isFavoritePlaylistProvider = FutureProvider.family<bool, String>(
(ref, id) async { (ref, id) async {
final spotify = ref.watch(spotifyProvider); final spotify = ref.watch(spotifyProvider);

View File

@ -16,7 +16,7 @@ class PlaylistNotifier extends FamilyAsyncNotifier<Playlist, String> {
} }
Future<void> create(PlaylistInput input, [ValueChanged? onError]) async { Future<void> create(PlaylistInput input, [ValueChanged? onError]) async {
if (state is AsyncData || state is AsyncLoading) return; if (state is AsyncLoading) return;
state = const AsyncLoading(); state = const AsyncLoading();
final spotify = ref.read(spotifyProvider); final spotify = ref.read(spotifyProvider);
@ -51,19 +51,6 @@ class PlaylistNotifier extends FamilyAsyncNotifier<Playlist, String> {
ref.invalidate(favoritePlaylistsProvider); ref.invalidate(favoritePlaylistsProvider);
} }
Future<void> addTracks(List<String> trackIds) async {
if (state.value == null) return;
final spotify = ref.read(spotifyProvider);
await spotify.playlists.addTracks(
trackIds.map((id) => 'spotify:track:$id').toList(),
state.value!.id!,
);
ref.invalidate(playlistTracksProvider(state.value!.id!));
}
Future<void> modify(PlaylistInput input, [ValueChanged? onError]) async { Future<void> modify(PlaylistInput input, [ValueChanged? onError]) async {
if (state.value == null) return; if (state.value == null) return;

View File

@ -23,6 +23,30 @@ abstract class PaginatedAsyncNotifier<K, T extends BasePaginatedState<K, int>>
}, },
); );
} }
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(
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 CursorPaginatedAsyncNotifier<K, abstract class CursorPaginatedAsyncNotifier<K,
@ -49,6 +73,27 @@ abstract class CursorPaginatedAsyncNotifier<K,
}, },
); );
} }
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(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;
}
} }
abstract class FamilyPaginatedAsyncNotifier< abstract class FamilyPaginatedAsyncNotifier<