mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-12 23:45:18 +00:00
chore: fix saving/removing tracks, albums, artists and playlists from library not working
This commit is contained in:
parent
f870e12011
commit
83172f198c
@ -38,6 +38,7 @@ class HeartButton extends HookConsumerWidget {
|
||||
child: IconButton(
|
||||
variance: variance,
|
||||
size: size,
|
||||
enabled: onPressed != null,
|
||||
icon: AnimatedSwitcher(
|
||||
switchInCurve: Curves.fastOutSlowIn,
|
||||
switchOutCurve: Curves.fastOutSlowIn,
|
||||
@ -74,7 +75,8 @@ class TrackHeartButton extends HookConsumerWidget {
|
||||
Widget build(BuildContext context, ref) {
|
||||
final savedTracks = ref.watch(metadataPluginSavedTracksProvider);
|
||||
final me = ref.watch(metadataPluginUserProvider);
|
||||
final (:isLiked, :toggleTrackLike) = useTrackToggleLike(track, ref);
|
||||
final (:isLiked, :isLoading, :toggleTrackLike) =
|
||||
useTrackToggleLike(track, ref);
|
||||
|
||||
if (me.isLoading) {
|
||||
return const CircularProgressIndicator();
|
||||
@ -85,11 +87,11 @@ class TrackHeartButton extends HookConsumerWidget {
|
||||
? context.l10n.remove_from_favorites
|
||||
: context.l10n.save_as_favorite,
|
||||
isLiked: isLiked,
|
||||
onPressed: savedTracks.asData?.value != null
|
||||
? () {
|
||||
onPressed: savedTracks.asData?.value == null || isLoading
|
||||
? null
|
||||
: () {
|
||||
toggleTrackLike(track);
|
||||
}
|
||||
: null,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import 'package:spotube/provider/metadata_plugin/library/tracks.dart';
|
||||
|
||||
typedef UseTrackToggleLike = ({
|
||||
bool isLiked,
|
||||
bool isLoading,
|
||||
Future<void> Function(SpotubeTrackObject track) toggleTrackLike,
|
||||
});
|
||||
|
||||
@ -11,12 +12,11 @@ UseTrackToggleLike useTrackToggleLike(SpotubeTrackObject track, WidgetRef ref) {
|
||||
final savedTracksNotifier =
|
||||
ref.watch(metadataPluginSavedTracksProvider.notifier);
|
||||
|
||||
final isSavedTrack = ref.watch(
|
||||
metadataPluginIsSavedTrackProvider(track.id),
|
||||
);
|
||||
final isSavedTrack = ref.watch(metadataPluginIsSavedTrackProvider(track.id));
|
||||
|
||||
return (
|
||||
isLiked: isSavedTrack.asData?.value ?? false,
|
||||
isLoading: isSavedTrack.isLoading,
|
||||
toggleTrackLike: (track) async {
|
||||
final isLikedTrack = await ref.read(
|
||||
metadataPluginIsSavedTrackProvider(track.id).future,
|
||||
|
@ -14,6 +14,7 @@ import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
||||
class HorizontalPlaybuttonCardView<T> extends HookWidget {
|
||||
final Widget title;
|
||||
final List<T> items;
|
||||
final Widget? error;
|
||||
final VoidCallback onFetchMore;
|
||||
final bool isLoadingNextPage;
|
||||
final bool hasNextPage;
|
||||
@ -26,6 +27,7 @@ class HorizontalPlaybuttonCardView<T> extends HookWidget {
|
||||
required this.onFetchMore,
|
||||
required this.isLoadingNextPage,
|
||||
this.titleTrailing,
|
||||
this.error,
|
||||
super.key,
|
||||
}) : assert(
|
||||
items.every(
|
||||
@ -64,6 +66,9 @@ class HorizontalPlaybuttonCardView<T> extends HookWidget {
|
||||
if (titleTrailing != null) titleTrailing!,
|
||||
],
|
||||
),
|
||||
if (error != null)
|
||||
error!
|
||||
else
|
||||
SizedBox(
|
||||
height: isArtist ? 250 : 225,
|
||||
child: NotificationListener(
|
||||
@ -100,8 +105,8 @@ class HorizontalPlaybuttonCardView<T> extends HookWidget {
|
||||
final item = items[index];
|
||||
|
||||
return switch (item) {
|
||||
SpotubeSimplePlaylistObject() =>
|
||||
PlaylistCard(item as SpotubeSimplePlaylistObject),
|
||||
SpotubeSimplePlaylistObject() => PlaylistCard(
|
||||
item as SpotubeSimplePlaylistObject),
|
||||
SpotubeSimpleAlbumObject() =>
|
||||
AlbumCard(item as SpotubeSimpleAlbumObject),
|
||||
SpotubeFullArtistObject() =>
|
||||
|
@ -6,6 +6,7 @@ import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter_extension.dart';
|
||||
import 'package:skeletonizer/skeletonizer.dart';
|
||||
import 'package:spotube/collections/fake.dart';
|
||||
import 'package:spotube/components/fallbacks/error_box.dart';
|
||||
import 'package:spotube/components/track_presentation/presentation_props.dart';
|
||||
import 'package:spotube/components/track_presentation/presentation_state.dart';
|
||||
import 'package:spotube/components/track_presentation/use_track_tile_play_callback.dart';
|
||||
@ -30,6 +31,19 @@ class PresentationListSection extends HookConsumerWidget {
|
||||
final onTileTap = useTrackTilePlayCallback(ref);
|
||||
|
||||
if (state.presentationTracks.isEmpty && !options.pagination.isLoading) {
|
||||
if (options.error != null) {
|
||||
return SliverToBoxAdapter(
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: ErrorBox(
|
||||
error: options.error!,
|
||||
onRetry: options.pagination.onRefresh,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
|
@ -50,6 +50,7 @@ class TrackPresentationOptions {
|
||||
final PaginationProps pagination;
|
||||
final bool isLiked;
|
||||
final String? shareUrl;
|
||||
final Object? error;
|
||||
|
||||
// events
|
||||
final FutureOr<bool?> Function()? onHeart; // if null heart button will hidden
|
||||
@ -67,6 +68,7 @@ class TrackPresentationOptions {
|
||||
this.shareUrl,
|
||||
this.isLiked = false,
|
||||
this.onHeart,
|
||||
this.error,
|
||||
}) : assert(collection is SpotubeSimpleAlbumObject ||
|
||||
collection is SpotubeSimplePlaylistObject);
|
||||
|
||||
@ -90,7 +92,8 @@ class TrackPresentationOptions {
|
||||
other.pagination == pagination &&
|
||||
other.isLiked == isLiked &&
|
||||
other.shareUrl == shareUrl &&
|
||||
other.onHeart == onHeart;
|
||||
other.onHeart == onHeart &&
|
||||
other.error == error;
|
||||
}
|
||||
|
||||
@override
|
||||
@ -105,5 +108,6 @@ class TrackPresentationOptions {
|
||||
pagination.hashCode ^
|
||||
isLiked.hashCode ^
|
||||
shareUrl.hashCode ^
|
||||
onHeart.hashCode;
|
||||
onHeart.hashCode ^
|
||||
error.hashCode;
|
||||
}
|
||||
|
@ -1,11 +1,13 @@
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
import 'package:spotube/components/fallbacks/error_box.dart';
|
||||
import 'package:spotube/components/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart';
|
||||
import 'package:spotube/extensions/context.dart';
|
||||
import 'package:spotube/models/metadata/metadata.dart';
|
||||
import 'package:spotube/provider/metadata_plugin/album/releases.dart';
|
||||
import 'package:spotube/provider/metadata_plugin/core/auth.dart';
|
||||
import 'package:spotube/provider/metadata_plugin/utils/common.dart';
|
||||
import 'package:spotube/services/metadata/errors/exceptions.dart';
|
||||
|
||||
class HomeNewReleasesSection extends HookConsumerWidget {
|
||||
const HomeNewReleasesSection({super.key});
|
||||
@ -24,12 +26,30 @@ class HomeNewReleasesSection extends HookConsumerWidget {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
if (newReleases.error
|
||||
case MetadataPluginException(
|
||||
errorCode: MetadataPluginErrorCode.noDefaultPlugin,
|
||||
message: _,
|
||||
)) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return HorizontalPlaybuttonCardView<SpotubeSimpleAlbumObject>(
|
||||
items: newReleases.asData?.value.items ?? [],
|
||||
title: Text(context.l10n.new_releases),
|
||||
isLoadingNextPage: newReleases.isLoadingNextPage,
|
||||
hasNextPage: newReleases.asData?.value.hasMore ?? false,
|
||||
onFetchMore: newReleasesNotifier.fetchMore,
|
||||
error: newReleases.hasError
|
||||
? Center(
|
||||
child: ErrorBox(
|
||||
error: newReleases.error!,
|
||||
onRetry: () {
|
||||
ref.invalidate(metadataPluginAlbumReleasesProvider);
|
||||
},
|
||||
),
|
||||
)
|
||||
: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -48,6 +48,7 @@ class AlbumPage extends HookConsumerWidget {
|
||||
description:
|
||||
"${context.l10n.released} • ${album.releaseDate} • ${album.artists.first.name}",
|
||||
tracks: tracks.asData?.value.items ?? [],
|
||||
error: tracks.error,
|
||||
pagination: PaginationProps(
|
||||
hasNextPage: tracks.asData?.value.hasMore ?? false,
|
||||
isLoading: tracks.isLoading || tracks.isLoadingNextPage,
|
||||
|
@ -51,6 +51,7 @@ class LikedPlaylistPage extends HookConsumerWidget {
|
||||
title: playlist.name,
|
||||
description: playlist.description,
|
||||
tracks: tracks,
|
||||
error: likedTracks.error,
|
||||
routePath: '/playlist/${playlist.id}',
|
||||
isLiked: false,
|
||||
shareUrl: null,
|
||||
|
@ -79,6 +79,7 @@ class PlaylistPage extends HookConsumerWidget {
|
||||
owner: playlist.owner.name,
|
||||
ownerImage: playlist.owner.images.lastOrNull?.url,
|
||||
tracks: tracks.asData?.value.items ?? [],
|
||||
error: tracks.error,
|
||||
routePath: '/playlist/${playlist.id}',
|
||||
isLiked: isFavoritePlaylist.asData?.value ?? false,
|
||||
shareUrl: playlist.externalUri,
|
||||
|
@ -1,6 +1,6 @@
|
||||
import 'package:riverpod/riverpod.dart';
|
||||
import 'package:spotube/models/metadata/metadata.dart';
|
||||
import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart';
|
||||
import 'package:spotube/provider/metadata_plugin/core/auth.dart';
|
||||
import 'package:spotube/provider/metadata_plugin/utils/paginated.dart';
|
||||
|
||||
class MetadataPluginSavedAlbumNotifier
|
||||
@ -18,38 +18,50 @@ class MetadataPluginSavedAlbumNotifier
|
||||
|
||||
@override
|
||||
build() async {
|
||||
ref.watch(metadataPluginProvider);
|
||||
await ref.watch(metadataPluginAuthenticatedProvider.future);
|
||||
return await fetch(0, 20);
|
||||
}
|
||||
|
||||
Future<void> addFavorite(List<SpotubeSimpleAlbumObject> albums) async {
|
||||
await update((state) async {
|
||||
(await metadataPlugin).album.save(albums.map((e) => e.id).toList());
|
||||
return state.copyWith(
|
||||
items: [...state.items, ...albums],
|
||||
);
|
||||
});
|
||||
if (albums.isEmpty || state.value == null) return;
|
||||
final oldState = state.value;
|
||||
|
||||
for (final album in albums) {
|
||||
ref.invalidate(metadataPluginIsSavedAlbumProvider(album.id));
|
||||
state = AsyncData(
|
||||
state.value!.copyWith(
|
||||
items: [
|
||||
...albums,
|
||||
...state.value!.items,
|
||||
],
|
||||
),
|
||||
);
|
||||
try {
|
||||
await (await metadataPlugin).album.save(albums.map((e) => e.id).toList());
|
||||
} catch (e) {
|
||||
state = AsyncData(oldState!);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> removeFavorite(List<SpotubeSimpleAlbumObject> albums) async {
|
||||
await update((state) async {
|
||||
if (albums.isEmpty || state.value == null) return;
|
||||
|
||||
final oldState = state.value;
|
||||
|
||||
final albumIds = albums.map((e) => e.id).toList();
|
||||
(await metadataPlugin).album.unsave(albumIds);
|
||||
return state.copyWith(
|
||||
items: state.items
|
||||
state = AsyncData(
|
||||
state.value!.copyWith(
|
||||
items: state.value!.items
|
||||
.where(
|
||||
(e) => albumIds.contains((e).id) == false,
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
for (final album in albums) {
|
||||
ref.invalidate(metadataPluginIsSavedAlbumProvider(album.id));
|
||||
try {
|
||||
await (await metadataPlugin).album.unsave(albumIds);
|
||||
} catch (e) {
|
||||
state = AsyncData(oldState!);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -63,9 +75,14 @@ final metadataPluginSavedAlbumsProvider = AsyncNotifierProvider<
|
||||
final metadataPluginIsSavedAlbumProvider =
|
||||
FutureProvider.autoDispose.family<bool, String>(
|
||||
(ref, albumId) async {
|
||||
final metadataPlugin = await ref.watch(metadataPluginProvider.future);
|
||||
final savedAlbums =
|
||||
await ref.watch(metadataPluginSavedAlbumsProvider.future);
|
||||
final savedAlbumsNotifier =
|
||||
ref.read(metadataPluginSavedAlbumsProvider.notifier);
|
||||
final allSavedAlbums = savedAlbums.hasMore
|
||||
? await savedAlbumsNotifier.fetchAll()
|
||||
: savedAlbums.items;
|
||||
|
||||
return metadataPlugin!.user
|
||||
.isSavedAlbums([albumId]).then((value) => value.first);
|
||||
return allSavedAlbums.any((element) => element.id == albumId);
|
||||
},
|
||||
);
|
||||
|
@ -1,6 +1,6 @@
|
||||
import 'package:riverpod/riverpod.dart';
|
||||
import 'package:spotube/models/metadata/metadata.dart';
|
||||
import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart';
|
||||
import 'package:spotube/provider/metadata_plugin/core/auth.dart';
|
||||
import 'package:spotube/provider/metadata_plugin/utils/paginated.dart';
|
||||
|
||||
class MetadataPluginSavedArtistNotifier
|
||||
@ -20,38 +20,53 @@ class MetadataPluginSavedArtistNotifier
|
||||
|
||||
@override
|
||||
build() async {
|
||||
ref.watch(metadataPluginProvider);
|
||||
await ref.watch(metadataPluginAuthenticatedProvider.future);
|
||||
return await fetch(0, 20);
|
||||
}
|
||||
|
||||
Future<void> addFavorite(List<SpotubeFullArtistObject> artists) async {
|
||||
await update((state) async {
|
||||
(await metadataPlugin).artist.save(artists.map((e) => e.id).toList());
|
||||
return state.copyWith(
|
||||
items: [...state.items, ...artists],
|
||||
);
|
||||
});
|
||||
if (artists.isEmpty || state.value == null) return;
|
||||
final oldState = state.value;
|
||||
|
||||
for (final artist in artists) {
|
||||
ref.invalidate(metadataPluginIsSavedArtistProvider(artist.id));
|
||||
state = AsyncData(
|
||||
state.value!.copyWith(
|
||||
items: [
|
||||
...artists,
|
||||
...state.value!.items,
|
||||
],
|
||||
),
|
||||
);
|
||||
try {
|
||||
await (await metadataPlugin)
|
||||
.artist
|
||||
.save(artists.map((e) => e.id).toList());
|
||||
} catch (e) {
|
||||
state = AsyncData(oldState!);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> removeFavorite(List<SpotubeFullArtistObject> artists) async {
|
||||
await update((state) async {
|
||||
if (artists.isEmpty || state.value == null) return;
|
||||
|
||||
final oldState = state.value;
|
||||
|
||||
final artistIds = artists.map((e) => e.id).toList();
|
||||
(await metadataPlugin).artist.unsave(artistIds);
|
||||
return state.copyWith(
|
||||
items: state.items
|
||||
state = AsyncData(
|
||||
state.value!.copyWith(
|
||||
items: state.value!.items
|
||||
.where(
|
||||
(e) => artistIds.contains((e).id) == false,
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
for (final artist in artists) {
|
||||
ref.invalidate(metadataPluginIsSavedArtistProvider(artist.id));
|
||||
try {
|
||||
await (await metadataPlugin).artist.unsave(artistIds);
|
||||
} catch (e) {
|
||||
state = AsyncData(oldState!);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -65,9 +80,15 @@ final metadataPluginSavedArtistsProvider = AsyncNotifierProvider<
|
||||
final metadataPluginIsSavedArtistProvider =
|
||||
FutureProvider.autoDispose.family<bool, String>(
|
||||
(ref, artistId) async {
|
||||
final metadataPlugin = await ref.watch(metadataPluginProvider.future);
|
||||
final savedArtists =
|
||||
await ref.watch(metadataPluginSavedArtistsProvider.future);
|
||||
final savedArtistsNotifier =
|
||||
ref.read(metadataPluginSavedArtistsProvider.notifier);
|
||||
|
||||
return metadataPlugin!.user
|
||||
.isSavedArtists([artistId]).then((value) => value.first);
|
||||
final allSavedArtists = savedArtists.hasMore
|
||||
? await savedArtistsNotifier.fetchAll()
|
||||
: savedArtists.items;
|
||||
|
||||
return allSavedArtists.any((element) => element.id == artistId);
|
||||
},
|
||||
);
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:spotube/models/metadata/metadata.dart';
|
||||
import 'package:spotube/provider/metadata_plugin/core/auth.dart';
|
||||
import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart';
|
||||
import 'package:spotube/provider/metadata_plugin/tracks/playlist.dart';
|
||||
import 'package:spotube/provider/metadata_plugin/utils/paginated.dart';
|
||||
@ -21,7 +22,8 @@ class MetadataPluginSavedPlaylistsNotifier
|
||||
|
||||
@override
|
||||
build() async {
|
||||
ref.watch(metadataPluginProvider);
|
||||
await ref.watch(metadataPluginAuthenticatedProvider.future);
|
||||
|
||||
final playlists = await fetch(0, 20);
|
||||
|
||||
return playlists;
|
||||
@ -42,25 +44,43 @@ class MetadataPluginSavedPlaylistsNotifier
|
||||
}
|
||||
|
||||
Future<void> addFavorite(SpotubeSimplePlaylistObject playlist) async {
|
||||
await update((state) async {
|
||||
(await metadataPlugin).playlist.save(playlist.id);
|
||||
return state.copyWith(
|
||||
items: [...state.items, playlist],
|
||||
);
|
||||
});
|
||||
if (state.value == null) return;
|
||||
|
||||
ref.invalidate(metadataPluginIsSavedPlaylistProvider(playlist.id));
|
||||
final oldState = state.value;
|
||||
|
||||
state = AsyncData(
|
||||
state.value!.copyWith(
|
||||
items: [
|
||||
playlist,
|
||||
...state.value!.items,
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
try {
|
||||
await (await metadataPlugin).playlist.save(playlist.id);
|
||||
} catch (e) {
|
||||
state = AsyncData(oldState!);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> removeFavorite(SpotubeSimplePlaylistObject playlist) async {
|
||||
await update((state) async {
|
||||
(await metadataPlugin).playlist.unsave(playlist.id);
|
||||
return state.copyWith(
|
||||
items: state.items.where((e) => (e).id != playlist.id).toList(),
|
||||
);
|
||||
});
|
||||
if (state.value == null) return;
|
||||
|
||||
ref.invalidate(metadataPluginIsSavedPlaylistProvider(playlist.id));
|
||||
final oldState = state.value;
|
||||
state = AsyncData(
|
||||
state.value!.copyWith(
|
||||
items: state.value!.items.where((e) => (e).id != playlist.id).toList(),
|
||||
),
|
||||
);
|
||||
|
||||
try {
|
||||
await (await metadataPlugin).playlist.unsave(playlist.id);
|
||||
} catch (e) {
|
||||
state = AsyncData(oldState!);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> delete(String playlistId) async {
|
||||
@ -114,8 +134,16 @@ final metadataPluginIsSavedPlaylistProvider =
|
||||
throw MetadataPluginException.noDefaultPlugin();
|
||||
}
|
||||
|
||||
final follows = await plugin.user.isSavedPlaylist(id);
|
||||
final savedPlaylists =
|
||||
await ref.watch(metadataPluginSavedPlaylistsProvider.future);
|
||||
|
||||
return follows;
|
||||
final savedPlaylistsNotifier =
|
||||
ref.read(metadataPluginSavedPlaylistsProvider.notifier);
|
||||
|
||||
final allSavedPlaylists = savedPlaylists.hasMore
|
||||
? await savedPlaylistsNotifier.fetchAll()
|
||||
: savedPlaylists.items;
|
||||
|
||||
return allSavedPlaylists.any((element) => element.id == id);
|
||||
},
|
||||
);
|
||||
|
@ -1,6 +1,6 @@
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:spotube/models/metadata/metadata.dart';
|
||||
import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart';
|
||||
import 'package:spotube/provider/metadata_plugin/core/auth.dart';
|
||||
import 'package:spotube/provider/metadata_plugin/utils/common.dart';
|
||||
import 'package:spotube/provider/metadata_plugin/utils/paginated.dart';
|
||||
|
||||
@ -22,20 +22,57 @@ class MetadataPluginSavedTracksNotifier
|
||||
build() async {
|
||||
ref.cacheFor();
|
||||
|
||||
ref.watch(metadataPluginProvider);
|
||||
await ref.watch(metadataPluginAuthenticatedProvider.future);
|
||||
return await fetch(0, 20);
|
||||
}
|
||||
|
||||
Future<void> addFavorite(List<SpotubeTrackObject> tracks) async {
|
||||
await (await metadataPlugin).track.save(tracks.map((e) => e.id).toList());
|
||||
if (state.value == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ref.invalidateSelf();
|
||||
final oldState = state.value;
|
||||
state = AsyncData(
|
||||
state.value!.copyWith(
|
||||
items: [
|
||||
...tracks.whereType<SpotubeFullTrackObject>(),
|
||||
...state.value!.items
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
try {
|
||||
await (await metadataPlugin).track.save(tracks.map((e) => e.id).toList());
|
||||
} catch (e) {
|
||||
state = AsyncData(oldState!);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> removeFavorite(List<SpotubeTrackObject> tracks) async {
|
||||
await (await metadataPlugin).track.unsave(tracks.map((e) => e.id).toList());
|
||||
if (state.value == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ref.invalidateSelf();
|
||||
final oldState = state.value;
|
||||
state = AsyncData(
|
||||
state.value!.copyWith(
|
||||
items: state.value!.items
|
||||
.where(
|
||||
(savedTrack) => !tracks.any((track) => track.id == savedTrack.id),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
|
||||
try {
|
||||
await (await metadataPlugin)
|
||||
.track
|
||||
.unsave(tracks.map((e) => e.id).toList());
|
||||
} catch (e) {
|
||||
state = AsyncData(oldState!);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -48,9 +85,11 @@ final metadataPluginSavedTracksProvider = AutoDisposeAsyncNotifierProvider<
|
||||
final metadataPluginIsSavedTrackProvider =
|
||||
FutureProvider.autoDispose.family<bool, String>(
|
||||
(ref, trackId) async {
|
||||
final savedTracks =
|
||||
await ref.watch(metadataPluginSavedTracksProvider.future);
|
||||
final allSavedTracks =
|
||||
await ref.read(metadataPluginSavedTracksProvider.notifier).fetchAll();
|
||||
final allSavedTracks = savedTracks.hasMore
|
||||
? await ref.read(metadataPluginSavedTracksProvider.notifier).fetchAll()
|
||||
: savedTracks.items;
|
||||
|
||||
return allSavedTracks.any((track) => track.id == trackId);
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user