mirror of
https://github.com/KRTirtho/spotube.git
synced 2026-05-08 16:24:36 +00:00
feat: use providers in remaining pages and remove fl_query
This commit is contained in:
parent
d2a9ff6652
commit
51710d5aee
@ -1,11 +1,10 @@
|
|||||||
import 'package:flutter/material.dart' hide Page;
|
import 'package:flutter/material.dart' hide Page;
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/components/shared/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart';
|
import 'package:spotube/components/shared/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/models/logger.dart';
|
import 'package:spotube/models/logger.dart';
|
||||||
import 'package:spotube/services/queries/queries.dart';
|
import 'package:spotube/provider/spotify/spotify.dart';
|
||||||
|
|
||||||
class ArtistAlbumList extends HookConsumerWidget {
|
class ArtistAlbumList extends HookConsumerWidget {
|
||||||
final String artistId;
|
final String artistId;
|
||||||
@ -18,21 +17,19 @@ class ArtistAlbumList extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
final albumsQuery = useQueries.artist.albumsOf(ref, artistId);
|
final albumsQuery = ref.watch(artistAlbumsProvider(artistId));
|
||||||
|
final albumsQueryNotifier =
|
||||||
|
ref.watch(artistAlbumsProvider(artistId).notifier);
|
||||||
|
|
||||||
final albums = useMemoized(() {
|
final albums = albumsQuery.asData?.value.items ?? [];
|
||||||
return albumsQuery.pages
|
|
||||||
.expand<Album>((page) => page.items ?? const Iterable.empty())
|
|
||||||
.toList();
|
|
||||||
}, [albumsQuery.pages]);
|
|
||||||
|
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
return HorizontalPlaybuttonCardView<Album>(
|
return HorizontalPlaybuttonCardView<Album>(
|
||||||
isLoadingNextPage: albumsQuery.isLoadingNextPage,
|
isLoadingNextPage: albumsQuery.isLoadingNextPage,
|
||||||
hasNextPage: albumsQuery.hasNextPage,
|
hasNextPage: albumsQuery.asData?.value.hasMore ?? false,
|
||||||
items: albums,
|
items: albums,
|
||||||
onFetchMore: albumsQuery.fetchNext,
|
onFetchMore: albumsQueryNotifier.fetchMore,
|
||||||
title: Text(
|
title: Text(
|
||||||
context.l10n.albums,
|
context.l10n.albums,
|
||||||
style: theme.textTheme.headlineSmall,
|
style: theme.textTheme.headlineSmall,
|
||||||
|
|||||||
@ -1,10 +1,8 @@
|
|||||||
import 'package:fl_query_hooks/fl_query_hooks.dart';
|
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/components/shared/image/universal_image.dart';
|
import 'package:spotube/components/shared/image/universal_image.dart';
|
||||||
import 'package:spotube/models/spotify_friends.dart';
|
import 'package:spotube/models/spotify_friends.dart';
|
||||||
@ -24,7 +22,6 @@ class FriendItem extends HookConsumerWidget {
|
|||||||
colorScheme: colorScheme,
|
colorScheme: colorScheme,
|
||||||
) = Theme.of(context);
|
) = Theme.of(context);
|
||||||
|
|
||||||
final queryClient = useQueryClient();
|
|
||||||
final spotify = ref.watch(spotifyProvider);
|
final spotify = ref.watch(spotifyProvider);
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
@ -86,15 +83,11 @@ class FriendItem extends HookConsumerWidget {
|
|||||||
..onTap = () async {
|
..onTap = () async {
|
||||||
context.push(
|
context.push(
|
||||||
"/${friend.track.context.path}",
|
"/${friend.track.context.path}",
|
||||||
extra: !friend.track.context.path
|
extra:
|
||||||
.startsWith("album")
|
!friend.track.context.path.startsWith("album")
|
||||||
? null
|
? null
|
||||||
: await queryClient.fetchQuery<Album, dynamic>(
|
: await spotify.albums
|
||||||
"album/${friend.track.album.id}",
|
.get(friend.track.context.id),
|
||||||
() => spotify.albums.get(
|
|
||||||
friend.track.album.id,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -110,12 +103,7 @@ class FriendItem extends HookConsumerWidget {
|
|||||||
recognizer: TapGestureRecognizer()
|
recognizer: TapGestureRecognizer()
|
||||||
..onTap = () async {
|
..onTap = () async {
|
||||||
final album =
|
final album =
|
||||||
await queryClient.fetchQuery<Album, dynamic>(
|
await spotify.albums.get(friend.track.album.id);
|
||||||
"album/${friend.track.album.id}",
|
|
||||||
() => spotify.albums.get(
|
|
||||||
friend.track.album.id,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
context.push(
|
context.push(
|
||||||
"/album/${friend.track.album.id}",
|
"/album/${friend.track.album.id}",
|
||||||
|
|||||||
@ -16,7 +16,6 @@ import 'package:spotube/extensions/constrains.dart';
|
|||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/provider/spotify/spotify.dart';
|
import 'package:spotube/provider/spotify/spotify.dart';
|
||||||
import 'package:spotube/provider/spotify_provider.dart';
|
import 'package:spotube/provider/spotify_provider.dart';
|
||||||
import 'package:spotube/services/mutations/playlist.dart';
|
|
||||||
import 'package:spotube/utils/type_conversion_utils.dart';
|
import 'package:spotube/utils/type_conversion_utils.dart';
|
||||||
|
|
||||||
class PlaylistCreateDialog extends HookConsumerWidget {
|
class PlaylistCreateDialog extends HookConsumerWidget {
|
||||||
@ -90,7 +89,7 @@ class PlaylistCreateDialog extends HookConsumerWidget {
|
|||||||
Future<void> onCreate() async {
|
Future<void> onCreate() async {
|
||||||
if (!formKey.currentState!.validate()) return;
|
if (!formKey.currentState!.validate()) return;
|
||||||
|
|
||||||
final PlaylistCRUDVariables payload = (
|
final PlaylistInput payload = (
|
||||||
playlistName: playlistName.text,
|
playlistName: playlistName.text,
|
||||||
collaborative: collaborative.value,
|
collaborative: collaborative.value,
|
||||||
public: public.value,
|
public: public.value,
|
||||||
|
|||||||
@ -15,10 +15,10 @@ import 'package:spotube/hooks/utils/use_brightness_value.dart';
|
|||||||
import 'package:spotube/hooks/controllers/use_sidebarx_controller.dart';
|
import 'package:spotube/hooks/controllers/use_sidebarx_controller.dart';
|
||||||
import 'package:spotube/provider/download_manager_provider.dart';
|
import 'package:spotube/provider/download_manager_provider.dart';
|
||||||
import 'package:spotube/provider/authentication_provider.dart';
|
import 'package:spotube/provider/authentication_provider.dart';
|
||||||
|
import 'package:spotube/provider/spotify/spotify.dart';
|
||||||
|
|
||||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||||
import 'package:spotube/provider/user_preferences/user_preferences_state.dart';
|
import 'package:spotube/provider/user_preferences/user_preferences_state.dart';
|
||||||
import 'package:spotube/services/queries/queries.dart';
|
|
||||||
import 'package:spotube/utils/platform.dart';
|
import 'package:spotube/utils/platform.dart';
|
||||||
import 'package:spotube/utils/type_conversion_utils.dart';
|
import 'package:spotube/utils/type_conversion_utils.dart';
|
||||||
|
|
||||||
@ -241,8 +241,8 @@ class SidebarFooter extends HookConsumerWidget {
|
|||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final mediaQuery = MediaQuery.of(context);
|
final mediaQuery = MediaQuery.of(context);
|
||||||
final me = useQueries.user.me(ref);
|
final me = ref.watch(meProvider);
|
||||||
final data = me.data;
|
final data = me.asData?.value;
|
||||||
|
|
||||||
final avatarImg = TypeConversionUtils.image_X_UrlString(
|
final avatarImg = TypeConversionUtils.image_X_UrlString(
|
||||||
data?.images,
|
data?.images,
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:fl_query/fl_query.dart';
|
|
||||||
import 'package:flutter/material.dart' hide Page;
|
import 'package:flutter/material.dart' hide Page;
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
@ -25,8 +24,6 @@ import 'package:spotube/provider/download_manager_provider.dart';
|
|||||||
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
||||||
import 'package:spotube/provider/spotify/spotify.dart';
|
import 'package:spotube/provider/spotify/spotify.dart';
|
||||||
import 'package:spotube/provider/spotify_provider.dart';
|
import 'package:spotube/provider/spotify_provider.dart';
|
||||||
import 'package:spotube/services/mutations/mutations.dart';
|
|
||||||
import 'package:spotube/services/queries/search.dart';
|
|
||||||
import 'package:spotube/utils/type_conversion_utils.dart';
|
import 'package:spotube/utils/type_conversion_utils.dart';
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
|
|
||||||
@ -100,21 +97,10 @@ class TrackOptions extends HookConsumerWidget {
|
|||||||
final playlist = ref.read(ProxyPlaylistNotifier.provider);
|
final playlist = ref.read(ProxyPlaylistNotifier.provider);
|
||||||
final spotify = ref.read(spotifyProvider);
|
final spotify = ref.read(spotifyProvider);
|
||||||
final query = "${track.name} Radio";
|
final query = "${track.name} Radio";
|
||||||
final pages = await QueryClient.of(context)
|
final pages =
|
||||||
.fetchInfiniteQueryJob<List<Page>, dynamic, int, SearchParams>(
|
await spotify.search.get(query, types: [SearchType.playlist]).first();
|
||||||
job: SearchQueries.queryJob(query),
|
|
||||||
args: (
|
|
||||||
spotify: spotify,
|
|
||||||
searchType: SearchType.playlist,
|
|
||||||
query: query,
|
|
||||||
),
|
|
||||||
) ??
|
|
||||||
[];
|
|
||||||
|
|
||||||
final radios = pages
|
final radios = pages.map((e) => e.items).toList().cast<PlaylistSimple>();
|
||||||
.expand((e) => e.items?.toList() ?? <PlaylistSimple>[])
|
|
||||||
.toList()
|
|
||||||
.cast<PlaylistSimple>();
|
|
||||||
|
|
||||||
final artists = track.artists!.map((e) => e.name);
|
final artists = track.artists!.map((e) => e.name);
|
||||||
|
|
||||||
@ -192,10 +178,8 @@ class TrackOptions extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
|
|
||||||
final removingTrack = useState<String?>(null);
|
final removingTrack = useState<String?>(null);
|
||||||
final removeTrack = useMutations.playlist.removeTrackOf(
|
final favoritePlaylistsNotifier =
|
||||||
ref,
|
ref.watch(favoritePlaylistsProvider.notifier);
|
||||||
playlistId ?? "",
|
|
||||||
);
|
|
||||||
|
|
||||||
final isInQueue = useMemoized(() {
|
final isInQueue = useMemoized(() {
|
||||||
if (playlist.activeTrack == null) return false;
|
if (playlist.activeTrack == null) return false;
|
||||||
@ -266,7 +250,8 @@ class TrackOptions extends HookConsumerWidget {
|
|||||||
break;
|
break;
|
||||||
case TrackOptionValue.removeFromPlaylist:
|
case TrackOptionValue.removeFromPlaylist:
|
||||||
removingTrack.value = track.uri;
|
removingTrack.value = track.uri;
|
||||||
removeTrack.mutate(track.uri!);
|
favoritePlaylistsNotifier
|
||||||
|
.removeTracks(playlistId ?? "", [track.id!]);
|
||||||
break;
|
break;
|
||||||
case TrackOptionValue.blacklist:
|
case TrackOptionValue.blacklist:
|
||||||
if (isBlackListed) {
|
if (isBlackListed) {
|
||||||
@ -330,7 +315,7 @@ class TrackOptions extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
children: switch (track.runtimeType) {
|
children: switch (track.runtimeType) {
|
||||||
LocalTrack => [
|
LocalTrack() => [
|
||||||
PopSheetEntry(
|
PopSheetEntry(
|
||||||
value: TrackOptionValue.delete,
|
value: TrackOptionValue.delete,
|
||||||
leading: const Icon(SpotubeIcons.trash),
|
leading: const Icon(SpotubeIcons.trash),
|
||||||
@ -393,10 +378,7 @@ class TrackOptions extends HookConsumerWidget {
|
|||||||
if (userPlaylist && auth != null)
|
if (userPlaylist && auth != null)
|
||||||
PopSheetEntry(
|
PopSheetEntry(
|
||||||
value: TrackOptionValue.removeFromPlaylist,
|
value: TrackOptionValue.removeFromPlaylist,
|
||||||
leading: (removeTrack.isMutating || !removeTrack.hasData) &&
|
leading: const Icon(SpotubeIcons.removeFilled),
|
||||||
removingTrack.value == track.uri
|
|
||||||
? const CircularProgressIndicator()
|
|
||||||
: const Icon(SpotubeIcons.removeFilled),
|
|
||||||
title: Text(context.l10n.remove_from_playlist),
|
title: Text(context.l10n.remove_from_playlist),
|
||||||
),
|
),
|
||||||
PopSheetEntry(
|
PopSheetEntry(
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:fl_query/fl_query.dart';
|
|
||||||
import 'package:flutter/material.dart' hide Page;
|
import 'package:flutter/material.dart' hide Page;
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
|
|
||||||
@ -19,19 +18,6 @@ class PaginationProps {
|
|||||||
required this.onRefresh,
|
required this.onRefresh,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory PaginationProps.fromQuery(
|
|
||||||
InfiniteQuery<List<Track>, dynamic, int> query, {
|
|
||||||
required Future<List<Track>> Function() onFetchAll,
|
|
||||||
}) {
|
|
||||||
return PaginationProps(
|
|
||||||
hasNextPage: query.hasNextPage,
|
|
||||||
isLoading: query.isLoadingNextPage,
|
|
||||||
onFetchMore: query.fetchNext,
|
|
||||||
onFetchAll: onFetchAll,
|
|
||||||
onRefresh: query.refreshAll,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
operator ==(Object other) {
|
operator ==(Object other) {
|
||||||
return other is PaginationProps &&
|
return other is PaginationProps &&
|
||||||
|
|||||||
@ -1,34 +0,0 @@
|
|||||||
import 'package:fl_query/fl_query.dart';
|
|
||||||
import 'package:spotify/spotify.dart';
|
|
||||||
|
|
||||||
extension FetchAllTracks on InfiniteQuery<List<Track>, dynamic, int> {
|
|
||||||
Future<List<Track>> fetchAllTracks({
|
|
||||||
required Future<List<Track>> Function() getAllTracks,
|
|
||||||
}) async {
|
|
||||||
if (pages.isNotEmpty && !hasNextPage) {
|
|
||||||
return pages.expand((page) => page).toList();
|
|
||||||
}
|
|
||||||
final tracks = await getAllTracks();
|
|
||||||
|
|
||||||
final numOfPages = (tracks.length / 20).round();
|
|
||||||
|
|
||||||
final Map<int, List<Track>> pagedTracks = {};
|
|
||||||
|
|
||||||
for (var i = 0; i < numOfPages; i++) {
|
|
||||||
if (i == numOfPages - 1) {
|
|
||||||
final pageTracks = tracks.sublist(i * 20);
|
|
||||||
pagedTracks[i] = pageTracks;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
final pageTracks = tracks.sublist(i * 20, (i + 1) * 20);
|
|
||||||
pagedTracks[i] = pageTracks;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (final group in pagedTracks.entries) {
|
|
||||||
setPageData(group.key, group.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return tracks.toList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,10 +1,8 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:app_links/app_links.dart';
|
import 'package:app_links/app_links.dart';
|
||||||
import 'package:fl_query_hooks/fl_query_hooks.dart';
|
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
|
||||||
import 'package:spotube/collections/routes.dart';
|
import 'package:spotube/collections/routes.dart';
|
||||||
import 'package:spotube/provider/spotify_provider.dart';
|
import 'package:spotube/provider/spotify_provider.dart';
|
||||||
import 'package:flutter_sharing_intent/flutter_sharing_intent.dart';
|
import 'package:flutter_sharing_intent/flutter_sharing_intent.dart';
|
||||||
@ -17,8 +15,6 @@ final linkStream = appLinks.allStringLinkStream.asBroadcastStream();
|
|||||||
void useDeepLinking(WidgetRef ref) {
|
void useDeepLinking(WidgetRef ref) {
|
||||||
// single instance no worries
|
// single instance no worries
|
||||||
final spotify = ref.watch(spotifyProvider);
|
final spotify = ref.watch(spotifyProvider);
|
||||||
final queryClient = useQueryClient();
|
|
||||||
|
|
||||||
final router = ref.watch(routerProvider);
|
final router = ref.watch(routerProvider);
|
||||||
|
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
@ -32,10 +28,7 @@ void useDeepLinking(WidgetRef ref) {
|
|||||||
case "album":
|
case "album":
|
||||||
router.push(
|
router.push(
|
||||||
"/album/${url.pathSegments.last}",
|
"/album/${url.pathSegments.last}",
|
||||||
extra: await queryClient.fetchQuery<Album, dynamic>(
|
extra: await spotify.albums.get(url.pathSegments.last),
|
||||||
"album/${url.pathSegments.last}",
|
|
||||||
() => spotify.albums.get(url.pathSegments.last),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case "artist":
|
case "artist":
|
||||||
@ -44,10 +37,7 @@ void useDeepLinking(WidgetRef ref) {
|
|||||||
case "playlist":
|
case "playlist":
|
||||||
router.push(
|
router.push(
|
||||||
"/playlist/${url.pathSegments.last}",
|
"/playlist/${url.pathSegments.last}",
|
||||||
extra: await queryClient.fetchQuery<Playlist, dynamic>(
|
extra: await spotify.playlists.get(url.pathSegments.last),
|
||||||
"playlist/${url.pathSegments.last}",
|
|
||||||
() => spotify.playlists.get(url.pathSegments.last),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case "track":
|
case "track":
|
||||||
@ -78,10 +68,7 @@ void useDeepLinking(WidgetRef ref) {
|
|||||||
case "spotify:album":
|
case "spotify:album":
|
||||||
await router.push(
|
await router.push(
|
||||||
"/album/$endSegment",
|
"/album/$endSegment",
|
||||||
extra: await queryClient.fetchQuery<Album, dynamic>(
|
extra: await spotify.albums.get(endSegment),
|
||||||
"album/$endSegment",
|
|
||||||
() => spotify.albums.get(endSegment),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case "spotify:artist":
|
case "spotify:artist":
|
||||||
@ -93,10 +80,7 @@ void useDeepLinking(WidgetRef ref) {
|
|||||||
case "spotify:playlist":
|
case "spotify:playlist":
|
||||||
await router.push(
|
await router.push(
|
||||||
"/playlist/$endSegment",
|
"/playlist/$endSegment",
|
||||||
extra: await queryClient.fetchQuery<Playlist, dynamic>(
|
extra: await spotify.playlists.get(endSegment),
|
||||||
"playlist/$endSegment",
|
|
||||||
() => spotify.playlists.get(endSegment),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -108,5 +92,5 @@ void useDeepLinking(WidgetRef ref) {
|
|||||||
mediaStream?.cancel();
|
mediaStream?.cancel();
|
||||||
subscription.cancel();
|
subscription.cancel();
|
||||||
};
|
};
|
||||||
}, [spotify, queryClient]);
|
}, [spotify]);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import 'package:catcher_2/catcher_2.dart';
|
import 'package:catcher_2/catcher_2.dart';
|
||||||
import 'package:fl_query_hooks/fl_query_hooks.dart';
|
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
@ -8,7 +7,6 @@ import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
|||||||
import 'package:spotube/provider/spotify_provider.dart';
|
import 'package:spotube/provider/spotify_provider.dart';
|
||||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||||
import 'package:spotube/services/audio_player/audio_player.dart';
|
import 'package:spotube/services/audio_player/audio_player.dart';
|
||||||
import 'package:spotube/services/queries/search.dart';
|
|
||||||
|
|
||||||
void useEndlessPlayback(WidgetRef ref) {
|
void useEndlessPlayback(WidgetRef ref) {
|
||||||
final auth = ref.watch(AuthenticationNotifier.provider);
|
final auth = ref.watch(AuthenticationNotifier.provider);
|
||||||
@ -18,7 +16,6 @@ void useEndlessPlayback(WidgetRef ref) {
|
|||||||
final endlessPlayback =
|
final endlessPlayback =
|
||||||
ref.watch(userPreferencesProvider.select((s) => s.endlessPlayback));
|
ref.watch(userPreferencesProvider.select((s) => s.endlessPlayback));
|
||||||
|
|
||||||
final queryClient = useQueryClient();
|
|
||||||
|
|
||||||
useEffect(
|
useEffect(
|
||||||
() {
|
() {
|
||||||
@ -32,16 +29,8 @@ void useEndlessPlayback(WidgetRef ref) {
|
|||||||
final track = playlist.tracks.last;
|
final track = playlist.tracks.last;
|
||||||
|
|
||||||
final query = "${track.name} Radio";
|
final query = "${track.name} Radio";
|
||||||
final pages = await queryClient.fetchInfiniteQueryJob<List<Page>,
|
final pages = await spotify.search
|
||||||
dynamic, int, SearchParams>(
|
.get(query, types: [SearchType.playlist]).first();
|
||||||
job: SearchQueries.queryJob(query),
|
|
||||||
args: (
|
|
||||||
spotify: spotify,
|
|
||||||
searchType: SearchType.playlist,
|
|
||||||
query: query
|
|
||||||
),
|
|
||||||
) ??
|
|
||||||
[];
|
|
||||||
|
|
||||||
final radios = pages
|
final radios = pages
|
||||||
.expand((e) => e.items?.toList() ?? <PlaylistSimple>[])
|
.expand((e) => e.items?.toList() ?? <PlaylistSimple>[])
|
||||||
@ -94,7 +83,6 @@ void useEndlessPlayback(WidgetRef ref) {
|
|||||||
[
|
[
|
||||||
spotify,
|
spotify,
|
||||||
playback,
|
playback,
|
||||||
queryClient,
|
|
||||||
playlist.tracks,
|
playlist.tracks,
|
||||||
endlessPlayback,
|
endlessPlayback,
|
||||||
auth,
|
auth,
|
||||||
|
|||||||
@ -1,53 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
|
|
||||||
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';
|
|
||||||
import 'package:spotify/spotify.dart';
|
|
||||||
import 'package:spotube/provider/spotify_provider.dart';
|
|
||||||
|
|
||||||
InfiniteQuery<DataType, ErrorType, PageType>
|
|
||||||
useSpotifyInfiniteQuery<DataType, ErrorType, PageType>(
|
|
||||||
String queryKey,
|
|
||||||
FutureOr<DataType?> Function(PageType page, SpotifyApi spotify) queryFn, {
|
|
||||||
required WidgetRef ref,
|
|
||||||
required InfiniteQueryNextPage<DataType, PageType> nextPage,
|
|
||||||
required PageType initialPage,
|
|
||||||
RetryConfig? retryConfig,
|
|
||||||
RefreshConfig? refreshConfig,
|
|
||||||
JsonConfig<DataType>? jsonConfig,
|
|
||||||
ValueChanged<PageEvent<DataType, PageType>>? onData,
|
|
||||||
ValueChanged<PageEvent<ErrorType, PageType>>? onError,
|
|
||||||
bool enabled = true,
|
|
||||||
List<Object?>? keys,
|
|
||||||
}) {
|
|
||||||
final spotify = ref.watch(spotifyProvider);
|
|
||||||
final query = useInfiniteQuery<DataType, ErrorType, PageType>(
|
|
||||||
queryKey,
|
|
||||||
(page) => queryFn(page, spotify),
|
|
||||||
nextPage: nextPage,
|
|
||||||
initialPage: initialPage,
|
|
||||||
retryConfig: retryConfig,
|
|
||||||
refreshConfig: refreshConfig,
|
|
||||||
jsonConfig: jsonConfig,
|
|
||||||
onData: onData,
|
|
||||||
onError: onError,
|
|
||||||
enabled: enabled,
|
|
||||||
keys: keys,
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() {
|
|
||||||
return ref.listenManual(
|
|
||||||
spotifyProvider,
|
|
||||||
(previous, next) {
|
|
||||||
if (previous != next) {
|
|
||||||
query.refreshAll();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
).close;
|
|
||||||
}, [query]);
|
|
||||||
|
|
||||||
return query;
|
|
||||||
}
|
|
||||||
@ -1,36 +0,0 @@
|
|||||||
import 'package:fl_query/fl_query.dart';
|
|
||||||
import 'package:fl_query_hooks/fl_query_hooks.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
||||||
import 'package:spotify/spotify.dart';
|
|
||||||
import 'package:spotube/provider/spotify_provider.dart';
|
|
||||||
|
|
||||||
Mutation<DataType, ErrorType, VariablesType>
|
|
||||||
useSpotifyMutation<DataType, ErrorType, VariablesType, RecoveryType>(
|
|
||||||
String mutationKey,
|
|
||||||
Future<DataType> Function(VariablesType variables, SpotifyApi spotify)
|
|
||||||
mutationFn, {
|
|
||||||
required WidgetRef ref,
|
|
||||||
RetryConfig? retryConfig,
|
|
||||||
MutationOnDataFn<DataType, RecoveryType>? onData,
|
|
||||||
MutationOnErrorFn<ErrorType, RecoveryType>? onError,
|
|
||||||
MutationOnMutationFn<VariablesType, RecoveryType>? onMutate,
|
|
||||||
List<String>? refreshQueries,
|
|
||||||
List<String>? refreshInfiniteQueries,
|
|
||||||
List<Object?>? keys,
|
|
||||||
}) {
|
|
||||||
final spotify = ref.watch(spotifyProvider);
|
|
||||||
final mutation =
|
|
||||||
useMutation<DataType, ErrorType, VariablesType, RecoveryType>(
|
|
||||||
mutationKey,
|
|
||||||
(variables) => mutationFn(variables, spotify),
|
|
||||||
retryConfig: retryConfig,
|
|
||||||
onData: onData,
|
|
||||||
onError: onError,
|
|
||||||
onMutate: onMutate,
|
|
||||||
refreshQueries: refreshQueries,
|
|
||||||
refreshInfiniteQueries: refreshInfiniteQueries,
|
|
||||||
keys: keys,
|
|
||||||
);
|
|
||||||
|
|
||||||
return mutation;
|
|
||||||
}
|
|
||||||
@ -1,52 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
|
|
||||||
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';
|
|
||||||
import 'package:spotify/spotify.dart';
|
|
||||||
import 'package:spotube/provider/spotify_provider.dart';
|
|
||||||
|
|
||||||
typedef SpotifyQueryFn<DataType> = FutureOr<DataType?> Function(
|
|
||||||
SpotifyApi spotify);
|
|
||||||
|
|
||||||
Query<DataType, ErrorType> useSpotifyQuery<DataType, ErrorType>(
|
|
||||||
final String queryKey,
|
|
||||||
final SpotifyQueryFn<DataType> queryFn, {
|
|
||||||
required WidgetRef ref,
|
|
||||||
final DataType? initial,
|
|
||||||
final RetryConfig? retryConfig,
|
|
||||||
final RefreshConfig? refreshConfig,
|
|
||||||
final JsonConfig<DataType>? jsonConfig,
|
|
||||||
final ValueChanged<DataType>? onData,
|
|
||||||
final ValueChanged<ErrorType>? onError,
|
|
||||||
final bool enabled = true,
|
|
||||||
}) {
|
|
||||||
final spotify = ref.watch(spotifyProvider);
|
|
||||||
|
|
||||||
final query = useQuery<DataType, ErrorType>(
|
|
||||||
queryKey,
|
|
||||||
() => queryFn(spotify),
|
|
||||||
initial: initial,
|
|
||||||
retryConfig: retryConfig,
|
|
||||||
refreshConfig: refreshConfig,
|
|
||||||
jsonConfig: jsonConfig,
|
|
||||||
onData: onData,
|
|
||||||
onError: onError,
|
|
||||||
enabled: enabled,
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() {
|
|
||||||
return ref.listenManual(
|
|
||||||
spotifyProvider,
|
|
||||||
(previous, next) {
|
|
||||||
if (previous != next) {
|
|
||||||
query.refresh();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
).close;
|
|
||||||
}, [query]);
|
|
||||||
|
|
||||||
return query;
|
|
||||||
}
|
|
||||||
@ -1,7 +1,6 @@
|
|||||||
import 'package:catcher_2/catcher_2.dart';
|
import 'package:catcher_2/catcher_2.dart';
|
||||||
import 'package:dart_discord_rpc/dart_discord_rpc.dart';
|
import 'package:dart_discord_rpc/dart_discord_rpc.dart';
|
||||||
import 'package:device_preview/device_preview.dart';
|
import 'package:device_preview/device_preview.dart';
|
||||||
import 'package:fl_query/fl_query.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
@ -28,7 +27,6 @@ import 'package:spotube/provider/palette_provider.dart';
|
|||||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||||
import 'package:spotube/services/audio_player/audio_player.dart';
|
import 'package:spotube/services/audio_player/audio_player.dart';
|
||||||
import 'package:spotube/services/cli/cli.dart';
|
import 'package:spotube/services/cli/cli.dart';
|
||||||
import 'package:spotube/services/connectivity_adapter.dart';
|
|
||||||
import 'package:spotube/services/kv_store/kv_store.dart';
|
import 'package:spotube/services/kv_store/kv_store.dart';
|
||||||
import 'package:spotube/themes/theme.dart';
|
import 'package:spotube/themes/theme.dart';
|
||||||
import 'package:spotube/utils/persisted_state_notifier.dart';
|
import 'package:spotube/utils/persisted_state_notifier.dart';
|
||||||
@ -74,11 +72,7 @@ Future<void> main(List<String> rawArgs) async {
|
|||||||
final hiveCacheDir =
|
final hiveCacheDir =
|
||||||
kIsWeb ? null : (await getApplicationSupportDirectory()).path;
|
kIsWeb ? null : (await getApplicationSupportDirectory()).path;
|
||||||
|
|
||||||
await QueryClient.initialize(
|
Hive.init(hiveCacheDir);
|
||||||
cachePrefix: "oss.krtirtho.spotube",
|
|
||||||
cacheDir: hiveCacheDir,
|
|
||||||
connectivity: FlQueryInternetConnectionCheckerAdapter(),
|
|
||||||
);
|
|
||||||
|
|
||||||
Hive.registerAdapter(SkipSegmentAdapter());
|
Hive.registerAdapter(SkipSegmentAdapter());
|
||||||
|
|
||||||
@ -144,10 +138,7 @@ Future<void> main(List<String> rawArgs) async {
|
|||||||
orientation: Orientation.portrait,
|
orientation: Orientation.portrait,
|
||||||
),
|
),
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return QueryClientProvider(
|
return const Spotube();
|
||||||
staleDuration: const Duration(minutes: 30),
|
|
||||||
child: const Spotube(),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:fl_query/fl_query.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
|
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
|
||||||
@ -18,6 +17,7 @@ import 'package:spotube/extensions/context.dart';
|
|||||||
import 'package:spotube/hooks/configurators/use_endless_playback.dart';
|
import 'package:spotube/hooks/configurators/use_endless_playback.dart';
|
||||||
import 'package:spotube/hooks/configurators/use_update_checker.dart';
|
import 'package:spotube/hooks/configurators/use_update_checker.dart';
|
||||||
import 'package:spotube/provider/download_manager_provider.dart';
|
import 'package:spotube/provider/download_manager_provider.dart';
|
||||||
|
import 'package:spotube/services/connectivity_adapter.dart';
|
||||||
import 'package:spotube/utils/persisted_state_notifier.dart';
|
import 'package:spotube/utils/persisted_state_notifier.dart';
|
||||||
|
|
||||||
const rootPaths = {
|
const rootPaths = {
|
||||||
@ -53,8 +53,9 @@ class RootApp extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
final subscription =
|
final subscription = ConnectionCheckerService
|
||||||
QueryClient.connectivity.onConnectivityChanged.listen((status) {
|
.instance.onConnectivityChanged
|
||||||
|
.listen((status) {
|
||||||
if (status) {
|
if (status) {
|
||||||
scaffoldMessenger.showSnackBar(
|
scaffoldMessenger.showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
|
|||||||
@ -13,8 +13,8 @@ import 'package:spotube/components/shared/page_window_title_bar.dart';
|
|||||||
import 'package:spotube/components/shared/track_tile/track_options.dart';
|
import 'package:spotube/components/shared/track_tile/track_options.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
||||||
|
import 'package:spotube/provider/spotify/spotify.dart';
|
||||||
import 'package:spotube/services/audio_player/audio_player.dart';
|
import 'package:spotube/services/audio_player/audio_player.dart';
|
||||||
import 'package:spotube/services/queries/queries.dart';
|
|
||||||
import 'package:spotube/utils/type_conversion_utils.dart';
|
import 'package:spotube/utils/type_conversion_utils.dart';
|
||||||
import 'package:spotube/extensions/constrains.dart';
|
import 'package:spotube/extensions/constrains.dart';
|
||||||
|
|
||||||
@ -35,9 +35,9 @@ class TrackPage extends HookConsumerWidget {
|
|||||||
|
|
||||||
final isActive = playlist.activeTrack?.id == trackId;
|
final isActive = playlist.activeTrack?.id == trackId;
|
||||||
|
|
||||||
final trackQuery = useQueries.tracks.track(ref, trackId);
|
final trackQuery = ref.watch(trackProvider(trackId));
|
||||||
|
|
||||||
final track = trackQuery.data ?? FakeData.track;
|
final track = trackQuery.asData?.value ?? FakeData.track;
|
||||||
|
|
||||||
void onPlay() async {
|
void onPlay() async {
|
||||||
if (isActive) {
|
if (isActive) {
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:fl_query/fl_query.dart';
|
|
||||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:http/http.dart';
|
import 'package:http/http.dart';
|
||||||
@ -52,8 +51,7 @@ class AuthenticationCredentials {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (rootNavigatorKey?.currentContext != null &&
|
if (rootNavigatorKey?.currentContext != null) {
|
||||||
await QueryClient.connectivity.isConnected) {
|
|
||||||
showPromptDialog(
|
showPromptDialog(
|
||||||
context: rootNavigatorKey!.currentContext!,
|
context: rootNavigatorKey!.currentContext!,
|
||||||
title: rootNavigatorKey!.currentContext!.l10n
|
title: rootNavigatorKey!.currentContext!.l10n
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
part of '../spotify.dart';
|
part of '../spotify.dart';
|
||||||
|
|
||||||
class ArtistAlbumsState extends PaginatedState<AlbumSimple> {
|
class ArtistAlbumsState extends PaginatedState<Album> {
|
||||||
ArtistAlbumsState({
|
ArtistAlbumsState({
|
||||||
required super.items,
|
required super.items,
|
||||||
required super.offset,
|
required super.offset,
|
||||||
@ -10,7 +10,7 @@ class ArtistAlbumsState extends PaginatedState<AlbumSimple> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
ArtistAlbumsState copyWith({
|
ArtistAlbumsState copyWith({
|
||||||
List<AlbumSimple>? items,
|
List<Album>? items,
|
||||||
int? offset,
|
int? offset,
|
||||||
int? limit,
|
int? limit,
|
||||||
bool? hasMore,
|
bool? hasMore,
|
||||||
@ -24,8 +24,8 @@ class ArtistAlbumsState extends PaginatedState<AlbumSimple> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ArtistAlbumsNotifier extends FamilyPaginatedAsyncNotifier<AlbumSimple,
|
class ArtistAlbumsNotifier
|
||||||
ArtistAlbumsState, String> {
|
extends FamilyPaginatedAsyncNotifier<Album, ArtistAlbumsState, String> {
|
||||||
ArtistAlbumsNotifier() : super();
|
ArtistAlbumsNotifier() : super();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@ -85,6 +85,19 @@ class FavoritePlaylistsNotifier
|
|||||||
|
|
||||||
ref.invalidate(playlistTracksProvider(playlistId));
|
ref.invalidate(playlistTracksProvider(playlistId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> removeTracks(String playlistId, List<String> trackIds) async {
|
||||||
|
if (state.value == null) return;
|
||||||
|
|
||||||
|
final spotify = ref.read(spotifyProvider);
|
||||||
|
|
||||||
|
await spotify.playlists.removeTracks(
|
||||||
|
trackIds.map((id) => 'spotify:track:$id').toList(),
|
||||||
|
playlistId,
|
||||||
|
);
|
||||||
|
|
||||||
|
ref.invalidate(playlistTracksProvider(playlistId));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final favoritePlaylistsProvider =
|
final favoritePlaylistsProvider =
|
||||||
|
|||||||
@ -2,17 +2,17 @@ import 'dart:async';
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:fl_query/fl_query.dart';
|
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
class FlQueryInternetConnectionCheckerAdapter extends ConnectivityAdapter
|
class ConnectionCheckerService with WidgetsBindingObserver {
|
||||||
with WidgetsBindingObserver {
|
|
||||||
final _connectionStreamController = StreamController<bool>.broadcast();
|
final _connectionStreamController = StreamController<bool>.broadcast();
|
||||||
final Dio dio;
|
final Dio dio;
|
||||||
|
|
||||||
FlQueryInternetConnectionCheckerAdapter()
|
static final _instance = ConnectionCheckerService._();
|
||||||
: dio = Dio(),
|
|
||||||
super() {
|
static ConnectionCheckerService get instance => _instance;
|
||||||
|
|
||||||
|
ConnectionCheckerService._() : dio = Dio() {
|
||||||
Timer? timer;
|
Timer? timer;
|
||||||
|
|
||||||
onConnectivityChanged.listen((connected) {
|
onConnectivityChanged.listen((connected) {
|
||||||
@ -100,15 +100,16 @@ class FlQueryInternetConnectionCheckerAdapter extends ConnectivityAdapter
|
|||||||
await isVpnActive(); // when VPN is active that means we are connected
|
await isVpnActive(); // when VPN is active that means we are connected
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
bool isConnectedSync = false;
|
||||||
|
|
||||||
Future<bool> get isConnected async {
|
Future<bool> get isConnected async {
|
||||||
final connected = await _isConnected();
|
final connected = await _isConnected();
|
||||||
|
isConnectedSync = connected;
|
||||||
if (connected != isConnectedSync /*previous value*/) {
|
if (connected != isConnectedSync /*previous value*/) {
|
||||||
_connectionStreamController.add(connected);
|
_connectionStreamController.add(connected);
|
||||||
}
|
}
|
||||||
return connected;
|
return connected;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Stream<bool> get onConnectivityChanged => _connectionStreamController.stream;
|
Stream<bool> get onConnectivityChanged => _connectionStreamController.stream;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,31 +0,0 @@
|
|||||||
import 'package:fl_query/fl_query.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
||||||
import 'package:spotube/hooks/spotify/use_spotify_mutation.dart';
|
|
||||||
|
|
||||||
class AlbumMutations {
|
|
||||||
const AlbumMutations();
|
|
||||||
|
|
||||||
Mutation<bool, dynamic, bool> toggleFavorite(
|
|
||||||
WidgetRef ref,
|
|
||||||
String albumId, {
|
|
||||||
List<String>? refreshQueries,
|
|
||||||
List<String>? refreshInfiniteQueries,
|
|
||||||
MutationOnDataFn<bool, dynamic>? onData,
|
|
||||||
}) {
|
|
||||||
return useSpotifyMutation<bool, dynamic, bool, dynamic>(
|
|
||||||
"toggle-album-like/$albumId",
|
|
||||||
(isLiked, spotify) async {
|
|
||||||
if (isLiked) {
|
|
||||||
await spotify.me.removeAlbums([albumId]);
|
|
||||||
} else {
|
|
||||||
await spotify.me.saveAlbums([albumId]);
|
|
||||||
}
|
|
||||||
return !isLiked;
|
|
||||||
},
|
|
||||||
ref: ref,
|
|
||||||
refreshQueries: refreshQueries,
|
|
||||||
refreshInfiniteQueries: refreshInfiniteQueries,
|
|
||||||
onData: onData,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
import 'package:spotube/services/mutations/album.dart';
|
|
||||||
import 'package:spotube/services/mutations/playlist.dart';
|
|
||||||
import 'package:spotube/services/mutations/track.dart';
|
|
||||||
|
|
||||||
class _UseMutations {
|
|
||||||
const _UseMutations._();
|
|
||||||
final playlist = const PlaylistMutations();
|
|
||||||
final album = const AlbumMutations();
|
|
||||||
final track = const TrackMutations();
|
|
||||||
}
|
|
||||||
|
|
||||||
const useMutations = _UseMutations._();
|
|
||||||
@ -1,147 +0,0 @@
|
|||||||
import 'package:fl_query/fl_query.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
||||||
import 'package:spotify/spotify.dart';
|
|
||||||
import 'package:spotube/hooks/spotify/use_spotify_mutation.dart';
|
|
||||||
import 'package:spotube/services/queries/queries.dart';
|
|
||||||
|
|
||||||
typedef PlaylistCRUDVariables = ({
|
|
||||||
String playlistName,
|
|
||||||
bool? public,
|
|
||||||
bool? collaborative,
|
|
||||||
String? description,
|
|
||||||
String? base64Image,
|
|
||||||
});
|
|
||||||
|
|
||||||
class PlaylistMutations {
|
|
||||||
const PlaylistMutations();
|
|
||||||
|
|
||||||
Mutation<bool, dynamic, bool> toggleFavorite(
|
|
||||||
WidgetRef ref,
|
|
||||||
String playlistId, {
|
|
||||||
List<String>? refreshQueries,
|
|
||||||
List<String>? refreshInfiniteQueries,
|
|
||||||
ValueChanged<bool>? onData,
|
|
||||||
}) {
|
|
||||||
return useSpotifyMutation<bool, dynamic, bool, dynamic>(
|
|
||||||
"toggle-playlist-like/$playlistId",
|
|
||||||
(isLiked, spotify) async {
|
|
||||||
if (isLiked) {
|
|
||||||
await spotify.playlists.unfollowPlaylist(playlistId);
|
|
||||||
} else {
|
|
||||||
await spotify.playlists.followPlaylist(playlistId);
|
|
||||||
}
|
|
||||||
return !isLiked;
|
|
||||||
},
|
|
||||||
ref: ref,
|
|
||||||
refreshQueries: refreshQueries,
|
|
||||||
refreshInfiniteQueries: [
|
|
||||||
...?refreshInfiniteQueries,
|
|
||||||
"current-user-playlists",
|
|
||||||
],
|
|
||||||
onData: (data, recoveryData) {
|
|
||||||
onData?.call(data);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Mutation<bool, dynamic, String> removeTrackOf(
|
|
||||||
WidgetRef ref,
|
|
||||||
String playlistId,
|
|
||||||
) {
|
|
||||||
return useSpotifyMutation<bool, dynamic, String, dynamic>(
|
|
||||||
"remove-track-from-playlist/$playlistId",
|
|
||||||
(trackId, spotify) async {
|
|
||||||
await spotify.playlists.removeTracks([trackId], playlistId);
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
ref: ref,
|
|
||||||
refreshQueries: ["playlist-tracks/$playlistId"],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Mutation<Playlist, dynamic, PlaylistCRUDVariables> create(
|
|
||||||
WidgetRef ref, {
|
|
||||||
List<String>? trackIds,
|
|
||||||
ValueChanged<dynamic>? onError,
|
|
||||||
ValueChanged<Playlist>? onData,
|
|
||||||
}) {
|
|
||||||
final me = useQueries.user.me(ref);
|
|
||||||
return useSpotifyMutation<Playlist, dynamic, PlaylistCRUDVariables, void>(
|
|
||||||
"create-playlist",
|
|
||||||
(variable, spotify) async {
|
|
||||||
final playlist = await spotify.playlists.createPlaylist(
|
|
||||||
me.data!.id!,
|
|
||||||
variable.playlistName,
|
|
||||||
collaborative: variable.collaborative,
|
|
||||||
description: variable.description,
|
|
||||||
public: variable.public,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (variable.base64Image != null) {
|
|
||||||
await spotify.playlists.updatePlaylistImage(
|
|
||||||
playlist.id!,
|
|
||||||
variable.base64Image!,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (trackIds != null && trackIds.isNotEmpty) {
|
|
||||||
await spotify.playlists.addTracks(
|
|
||||||
trackIds.map((id) => "spotify:track:$id").toList(),
|
|
||||||
playlist.id!,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return playlist;
|
|
||||||
},
|
|
||||||
refreshInfiniteQueries: ["current-user-playlists"],
|
|
||||||
refreshQueries: ["current-user-all-playlists"],
|
|
||||||
ref: ref,
|
|
||||||
onError: (error, recoveryData) {
|
|
||||||
onError?.call(error);
|
|
||||||
},
|
|
||||||
onData: (data, recoveryData) {
|
|
||||||
onData?.call(data);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Mutation<void, dynamic, PlaylistCRUDVariables> update(
|
|
||||||
WidgetRef ref, {
|
|
||||||
String? playlistId,
|
|
||||||
ValueChanged<dynamic>? onError,
|
|
||||||
ValueChanged<void>? onData,
|
|
||||||
}) {
|
|
||||||
return useSpotifyMutation<void, dynamic, PlaylistCRUDVariables, void>(
|
|
||||||
"update-playlist/$playlistId",
|
|
||||||
(variable, spotify) async {
|
|
||||||
if (playlistId == null) return;
|
|
||||||
await spotify.playlists.updatePlaylist(
|
|
||||||
playlistId,
|
|
||||||
variable.playlistName,
|
|
||||||
collaborative: variable.collaborative,
|
|
||||||
description: variable.description,
|
|
||||||
public: variable.public,
|
|
||||||
);
|
|
||||||
if (variable.base64Image != null) {
|
|
||||||
await spotify.playlists.updatePlaylistImage(
|
|
||||||
playlistId,
|
|
||||||
variable.base64Image!,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
refreshInfiniteQueries: [
|
|
||||||
"playlist/$playlistId",
|
|
||||||
"current-user-playlists",
|
|
||||||
],
|
|
||||||
refreshQueries: ["current-user-all-playlists"],
|
|
||||||
ref: ref,
|
|
||||||
onError: (error, recoveryData) {
|
|
||||||
onError?.call(error);
|
|
||||||
},
|
|
||||||
onData: (data, recoveryData) {
|
|
||||||
onData?.call(data);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
import 'package:fl_query/fl_query.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
||||||
import 'package:spotube/hooks/spotify/use_spotify_mutation.dart';
|
|
||||||
|
|
||||||
class TrackMutations {
|
|
||||||
const TrackMutations();
|
|
||||||
|
|
||||||
Mutation<bool, dynamic, bool> toggleFavorite(
|
|
||||||
WidgetRef ref,
|
|
||||||
String trackId, {
|
|
||||||
MutationOnMutationFn<bool, bool>? onMutate,
|
|
||||||
MutationOnDataFn<bool, bool>? onData,
|
|
||||||
MutationOnErrorFn<dynamic, bool>? onError,
|
|
||||||
}) {
|
|
||||||
return useSpotifyMutation<bool, dynamic, bool, bool>(
|
|
||||||
'toggle-track-like/$trackId',
|
|
||||||
(isLiked, spotify) async {
|
|
||||||
if (isLiked) {
|
|
||||||
await spotify.tracks.me.removeOne(trackId);
|
|
||||||
} else {
|
|
||||||
await spotify.tracks.me.saveOne(trackId);
|
|
||||||
}
|
|
||||||
return !isLiked;
|
|
||||||
},
|
|
||||||
ref: ref,
|
|
||||||
onData: onData,
|
|
||||||
onMutate: onMutate,
|
|
||||||
refreshQueries: ["playlist-tracks/user-liked-tracks"],
|
|
||||||
onError: onError,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,114 +0,0 @@
|
|||||||
import 'package:catcher_2/catcher_2.dart';
|
|
||||||
import 'package:fl_query/fl_query.dart';
|
|
||||||
import 'package:fl_query_hooks/fl_query_hooks.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
||||||
import 'package:spotify/spotify.dart';
|
|
||||||
import 'package:spotube/hooks/spotify/use_spotify_infinite_query.dart';
|
|
||||||
import 'package:spotube/hooks/spotify/use_spotify_query.dart';
|
|
||||||
import 'package:spotube/provider/spotify_provider.dart';
|
|
||||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
|
||||||
import 'package:spotube/utils/type_conversion_utils.dart';
|
|
||||||
|
|
||||||
class AlbumQueries {
|
|
||||||
const AlbumQueries();
|
|
||||||
|
|
||||||
InfiniteQuery<Page<AlbumSimple>, dynamic, int> ofMine(WidgetRef ref) {
|
|
||||||
return useSpotifyInfiniteQuery<Page<AlbumSimple>, dynamic, int>(
|
|
||||||
"current-user-albums",
|
|
||||||
(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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static final tracksOfJob = InfiniteQueryJob.withVariableKey<
|
|
||||||
List<Track>,
|
|
||||||
dynamic,
|
|
||||||
int,
|
|
||||||
({
|
|
||||||
SpotifyApi spotify,
|
|
||||||
AlbumSimple album,
|
|
||||||
})>(
|
|
||||||
baseQueryKey: "album-tracks",
|
|
||||||
initialPage: 0,
|
|
||||||
task: (albumId, page, args) async {
|
|
||||||
final res =
|
|
||||||
await args!.spotify.albums.tracks(albumId).getPage(20, page * 20);
|
|
||||||
return res.items
|
|
||||||
?.map((track) =>
|
|
||||||
TypeConversionUtils.simpleTrack_X_Track(track, args.album))
|
|
||||||
.toList() ??
|
|
||||||
<Track>[];
|
|
||||||
},
|
|
||||||
nextPage: (lastPage, lastPageData) {
|
|
||||||
if (lastPageData.length < 20) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return lastPage + 1;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
InfiniteQuery<List<Track>, dynamic, int> tracksOf(
|
|
||||||
WidgetRef ref,
|
|
||||||
AlbumSimple album,
|
|
||||||
) {
|
|
||||||
final spotify = ref.watch(spotifyProvider);
|
|
||||||
|
|
||||||
return useInfiniteQueryJob(
|
|
||||||
job: tracksOfJob(album.id!),
|
|
||||||
args: (spotify: spotify, album: album),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Query<bool, dynamic> isSavedForMe(
|
|
||||||
WidgetRef ref,
|
|
||||||
String album,
|
|
||||||
) {
|
|
||||||
return useSpotifyQuery<bool, dynamic>(
|
|
||||||
"is-saved-for-me/$album",
|
|
||||||
(spotify) {
|
|
||||||
return spotify.me
|
|
||||||
.containsSavedAlbums([album]).then((value) => value[album]);
|
|
||||||
},
|
|
||||||
ref: ref,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
InfiniteQuery<Page<AlbumSimple>, dynamic, int> newReleases(WidgetRef ref) {
|
|
||||||
final market = ref
|
|
||||||
.watch(userPreferencesProvider.select((s) => s.recommendationMarket));
|
|
||||||
|
|
||||||
return useSpotifyInfiniteQuery<Page<AlbumSimple>, dynamic, int>(
|
|
||||||
"new-releases",
|
|
||||||
(pageParam, spotify) async {
|
|
||||||
try {
|
|
||||||
final albums = await spotify.browse
|
|
||||||
.newReleases(country: market)
|
|
||||||
.getPage(50, pageParam);
|
|
||||||
|
|
||||||
return albums;
|
|
||||||
} catch (e, stack) {
|
|
||||||
Catcher2.reportCheckedError(e, stack);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ref: ref,
|
|
||||||
initialPage: 0,
|
|
||||||
nextPage: (lastPage, lastPageData) {
|
|
||||||
if (lastPageData.isLast) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return lastPageData.nextOffset;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,151 +0,0 @@
|
|||||||
import 'package:fl_query/fl_query.dart';
|
|
||||||
import 'package:fl_query_hooks/fl_query_hooks.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
||||||
import 'package:spotify/spotify.dart';
|
|
||||||
import 'package:spotube/hooks/spotify/use_spotify_infinite_query.dart';
|
|
||||||
import 'package:spotube/hooks/spotify/use_spotify_query.dart';
|
|
||||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
|
||||||
import 'package:spotube/services/wikipedia/wikipedia.dart';
|
|
||||||
import 'package:wikipedia_api/wikipedia_api.dart';
|
|
||||||
|
|
||||||
class ArtistQueries {
|
|
||||||
const ArtistQueries();
|
|
||||||
|
|
||||||
Query<Artist, dynamic> get(
|
|
||||||
WidgetRef ref,
|
|
||||||
String artist,
|
|
||||||
) {
|
|
||||||
return useSpotifyQuery<Artist, dynamic>(
|
|
||||||
"artist-profile/$artist",
|
|
||||||
(spotify) => spotify.artists.get(artist),
|
|
||||||
ref: ref,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
InfiniteQuery<CursorPage<Artist>, dynamic, String> followedByMe(
|
|
||||||
WidgetRef ref) {
|
|
||||||
return useSpotifyInfiniteQuery<CursorPage<Artist>, dynamic, String>(
|
|
||||||
"user-following-artists",
|
|
||||||
(pageParam, spotify) async {
|
|
||||||
return spotify.me
|
|
||||||
.following(FollowingType.artist)
|
|
||||||
.getPage(15, pageParam);
|
|
||||||
},
|
|
||||||
initialPage: "",
|
|
||||||
nextPage: (lastPage, lastPageData) {
|
|
||||||
if (lastPageData.isLast || (lastPageData.items ?? []).length < 15) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return lastPageData.after;
|
|
||||||
},
|
|
||||||
ref: ref,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Query<List<Artist>, dynamic> followedByMeAll(WidgetRef ref) {
|
|
||||||
return useSpotifyQuery(
|
|
||||||
"user-following-artists-all",
|
|
||||||
(spotify) async {
|
|
||||||
CursorPage<Artist>? page =
|
|
||||||
await spotify.me.following(FollowingType.artist).getPage(50);
|
|
||||||
|
|
||||||
final following = <Artist>[];
|
|
||||||
|
|
||||||
if (page.isLast == true) {
|
|
||||||
return page.items?.toList() ?? [];
|
|
||||||
}
|
|
||||||
|
|
||||||
following.addAll(page.items ?? []);
|
|
||||||
while (page?.isLast != true) {
|
|
||||||
page = await spotify.me
|
|
||||||
.following(FollowingType.artist)
|
|
||||||
.getPage(50, page?.after ?? '');
|
|
||||||
following.addAll(page.items ?? []);
|
|
||||||
}
|
|
||||||
|
|
||||||
return following;
|
|
||||||
},
|
|
||||||
ref: ref,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Query<bool, dynamic> doIFollow(
|
|
||||||
WidgetRef ref,
|
|
||||||
String artist,
|
|
||||||
) {
|
|
||||||
return useSpotifyQuery<bool, dynamic>(
|
|
||||||
"user-follows-artists-query/$artist",
|
|
||||||
(spotify) async {
|
|
||||||
final result = await spotify.me.checkFollowing(
|
|
||||||
FollowingType.artist,
|
|
||||||
[artist],
|
|
||||||
);
|
|
||||||
return result[artist];
|
|
||||||
},
|
|
||||||
ref: ref,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Query<Iterable<Track>, dynamic> topTracksOf(
|
|
||||||
WidgetRef ref,
|
|
||||||
String artist,
|
|
||||||
) {
|
|
||||||
final preferences = ref.watch(userPreferencesProvider);
|
|
||||||
return useSpotifyQuery<Iterable<Track>, dynamic>(
|
|
||||||
"artist-top-track-query/$artist",
|
|
||||||
(spotify) {
|
|
||||||
return spotify.artists
|
|
||||||
.topTracks(artist, preferences.recommendationMarket);
|
|
||||||
},
|
|
||||||
ref: ref,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
InfiniteQuery<Page<Album>, dynamic, int> albumsOf(
|
|
||||||
WidgetRef ref,
|
|
||||||
String artist,
|
|
||||||
) {
|
|
||||||
return useSpotifyInfiniteQuery<Page<Album>, dynamic, int>(
|
|
||||||
"artist-albums/$artist",
|
|
||||||
(pageParam, spotify) async {
|
|
||||||
return spotify.artists.albums(artist).getPage(5, pageParam);
|
|
||||||
},
|
|
||||||
initialPage: 0,
|
|
||||||
nextPage: (lastPage, lastPageData) {
|
|
||||||
if (lastPageData.isLast || (lastPageData.items ?? []).length < 5) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return lastPageData.nextOffset;
|
|
||||||
},
|
|
||||||
ref: ref,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Query<Iterable<Artist>, dynamic> relatedArtistsOf(
|
|
||||||
WidgetRef ref,
|
|
||||||
String artist,
|
|
||||||
) {
|
|
||||||
return useSpotifyQuery<Iterable<Artist>, dynamic>(
|
|
||||||
"artist-related-artist-query/$artist",
|
|
||||||
(spotify) {
|
|
||||||
return spotify.artists.relatedArtists(artist);
|
|
||||||
},
|
|
||||||
ref: ref,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Query<Summary?, dynamic> wikipediaSummary(ArtistSimple artist) {
|
|
||||||
return useQuery<Summary?, dynamic>(
|
|
||||||
"artist-wikipedia-query/${artist.id}",
|
|
||||||
() async {
|
|
||||||
final query = artist.name!.replaceAll(" ", "_");
|
|
||||||
final res = await wikipedia.pageContent.pageSummaryTitleGet(query);
|
|
||||||
if (res?.type != "standard") {
|
|
||||||
return await wikipedia.pageContent
|
|
||||||
.pageSummaryTitleGet("${query}_(singer)");
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,120 +0,0 @@
|
|||||||
import 'package:fl_query/fl_query.dart';
|
|
||||||
import 'package:fl_query_hooks/fl_query_hooks.dart';
|
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
||||||
import 'package:spotify/spotify.dart';
|
|
||||||
import 'package:spotube/extensions/context.dart';
|
|
||||||
import 'package:spotube/hooks/spotify/use_spotify_infinite_query.dart';
|
|
||||||
import 'package:spotube/hooks/spotify/use_spotify_query.dart';
|
|
||||||
import 'package:spotube/provider/custom_spotify_endpoint_provider.dart';
|
|
||||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
|
||||||
|
|
||||||
class CategoryQueries {
|
|
||||||
const CategoryQueries();
|
|
||||||
|
|
||||||
Query<List<Category>, dynamic> listAll(
|
|
||||||
WidgetRef ref,
|
|
||||||
Market recommendationMarket,
|
|
||||||
) {
|
|
||||||
ref.watch(userPreferencesProvider.select((s) => s.locale));
|
|
||||||
final locale = useContext().l10n.localeName;
|
|
||||||
final query = useSpotifyQuery<List<Category>, dynamic>(
|
|
||||||
"category-playlists",
|
|
||||||
(spotify) async {
|
|
||||||
final categories = await spotify.categories
|
|
||||||
.list(
|
|
||||||
country: recommendationMarket,
|
|
||||||
locale: locale,
|
|
||||||
)
|
|
||||||
.all();
|
|
||||||
|
|
||||||
return categories.toList()..shuffle();
|
|
||||||
},
|
|
||||||
ref: ref,
|
|
||||||
);
|
|
||||||
|
|
||||||
return query;
|
|
||||||
}
|
|
||||||
|
|
||||||
InfiniteQuery<Page<Category>, dynamic, int> list(
|
|
||||||
WidgetRef ref,
|
|
||||||
Market recommendationMarket,
|
|
||||||
) {
|
|
||||||
ref.watch(userPreferencesProvider.select((s) => s.locale));
|
|
||||||
final locale = useContext().l10n.localeName;
|
|
||||||
return useSpotifyInfiniteQuery<Page<Category>, dynamic, int>(
|
|
||||||
"category-playlists",
|
|
||||||
(pageParam, spotify) async {
|
|
||||||
final categories = await spotify.categories
|
|
||||||
.list(
|
|
||||||
country: recommendationMarket,
|
|
||||||
locale: locale,
|
|
||||||
)
|
|
||||||
.getPage(8, pageParam);
|
|
||||||
|
|
||||||
return categories;
|
|
||||||
},
|
|
||||||
initialPage: 0,
|
|
||||||
nextPage: (lastPage, lastPageData) {
|
|
||||||
if (lastPageData.isLast || (lastPageData.items ?? []).length < 8) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return lastPageData.nextOffset;
|
|
||||||
},
|
|
||||||
ref: ref,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
InfiniteQuery<Page<PlaylistSimple?>, dynamic, int> playlistsOf(
|
|
||||||
WidgetRef ref,
|
|
||||||
String category,
|
|
||||||
) {
|
|
||||||
ref.watch(userPreferencesProvider.select((s) => s.locale));
|
|
||||||
final market = ref
|
|
||||||
.watch(userPreferencesProvider.select((s) => s.recommendationMarket));
|
|
||||||
final locale = useContext().l10n.localeName;
|
|
||||||
return useSpotifyInfiniteQuery<Page<PlaylistSimple?>, dynamic, int>(
|
|
||||||
"category-playlists/$category",
|
|
||||||
(pageParam, spotify) async {
|
|
||||||
final playlists = await Pages<PlaylistSimple?>(
|
|
||||||
spotify,
|
|
||||||
"v1/browse/categories/$category/playlists?country=${market.name}&locale=$locale",
|
|
||||||
(json) => json == null ? null : PlaylistSimple.fromJson(json),
|
|
||||||
'playlists',
|
|
||||||
(json) => PlaylistsFeatured.fromJson(json),
|
|
||||||
).getPage(5, pageParam);
|
|
||||||
|
|
||||||
return playlists;
|
|
||||||
},
|
|
||||||
initialPage: 0,
|
|
||||||
nextPage: (lastPage, lastPageData) {
|
|
||||||
if (lastPageData.isLast || (lastPageData.items ?? []).length < 5) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return lastPageData.nextOffset;
|
|
||||||
},
|
|
||||||
ref: ref,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Query<List<String>, dynamic> genreSeeds(WidgetRef ref) {
|
|
||||||
final customSpotify = ref.watch(customSpotifyEndpointProvider);
|
|
||||||
final query = useQuery<List<String>, dynamic>(
|
|
||||||
"genre-seeds",
|
|
||||||
customSpotify.listGenreSeeds,
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() {
|
|
||||||
return ref.listenManual(
|
|
||||||
customSpotifyEndpointProvider,
|
|
||||||
(previous, next) {
|
|
||||||
if (previous != next) {
|
|
||||||
query.refresh();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
).close;
|
|
||||||
}, [query]);
|
|
||||||
|
|
||||||
return query;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,114 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:fl_query/fl_query.dart';
|
|
||||||
import 'package:fl_query_hooks/fl_query_hooks.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:spotify/spotify.dart';
|
|
||||||
import 'package:spotube/extensions/map.dart';
|
|
||||||
import 'package:spotube/hooks/spotify/use_spotify_query.dart';
|
|
||||||
import 'package:spotube/models/lyrics.dart';
|
|
||||||
import 'package:spotube/services/sourced_track/sourced_track.dart';
|
|
||||||
import 'package:spotube/utils/service_utils.dart';
|
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
|
|
||||||
class LyricsQueries {
|
|
||||||
const LyricsQueries();
|
|
||||||
|
|
||||||
Query<String, dynamic> static(
|
|
||||||
Track? track,
|
|
||||||
String geniusAccessToken,
|
|
||||||
) {
|
|
||||||
return useQuery<String, dynamic>(
|
|
||||||
"genius-lyrics-query/${track?.id}",
|
|
||||||
() async {
|
|
||||||
if (track == null) {
|
|
||||||
return "“Give this player a track to play”\n- S'Challa";
|
|
||||||
}
|
|
||||||
final lyrics = await ServiceUtils.getLyrics(
|
|
||||||
track.name!,
|
|
||||||
track.artists?.map((s) => s.name).whereNotNull().toList() ?? [],
|
|
||||||
apiKey: geniusAccessToken,
|
|
||||||
optimizeQuery: true,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (lyrics == null) throw Exception("Unable find lyrics");
|
|
||||||
return lyrics;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Query<SubtitleSimple, dynamic> synced(
|
|
||||||
Track? track,
|
|
||||||
) {
|
|
||||||
return useQuery<SubtitleSimple, dynamic>(
|
|
||||||
"synced-lyrics/${track?.id}}",
|
|
||||||
() async {
|
|
||||||
if (track == null || track is! SourcedTrack) {
|
|
||||||
throw "No track currently";
|
|
||||||
}
|
|
||||||
final timedLyrics = await ServiceUtils.getTimedLyrics(track);
|
|
||||||
if (timedLyrics == null) throw Exception("Unable to find lyrics");
|
|
||||||
|
|
||||||
return timedLyrics;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The Concept behind this method was shamelessly stolen from
|
|
||||||
/// https://github.com/akashrchandran/spotify-lyrics-api
|
|
||||||
///
|
|
||||||
/// Thanks to [akashrchandran](https://github.com/akashrchandran) for the idea
|
|
||||||
///
|
|
||||||
/// Special thanks to [raptag](https://github.com/raptag) for discovering this
|
|
||||||
/// jem
|
|
||||||
|
|
||||||
Query<SubtitleSimple, Exception> spotifySynced(WidgetRef ref, Track? track) {
|
|
||||||
return useSpotifyQuery<SubtitleSimple, Exception>(
|
|
||||||
"spotify-synced-lyrics/${track?.id}}",
|
|
||||||
(spotify) async {
|
|
||||||
if (track == null) {
|
|
||||||
throw "No track currently";
|
|
||||||
}
|
|
||||||
final token = await spotify.getCredentials();
|
|
||||||
final res = await http.get(
|
|
||||||
Uri.parse(
|
|
||||||
"https://spclient.wg.spotify.com/color-lyrics/v2/track/${track.id}?format=json&market=from_token",
|
|
||||||
),
|
|
||||||
headers: {
|
|
||||||
"User-Agent":
|
|
||||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.0.0 Safari/537.36",
|
|
||||||
"App-platform": "WebPlayer",
|
|
||||||
"authorization": "Bearer ${token.accessToken}"
|
|
||||||
});
|
|
||||||
|
|
||||||
if (res.statusCode != 200) {
|
|
||||||
throw Exception("Unable to find lyrics");
|
|
||||||
}
|
|
||||||
final linesRaw = Map.castFrom<dynamic, dynamic, String, dynamic>(
|
|
||||||
jsonDecode(res.body),
|
|
||||||
)["lyrics"]?["lines"] as List?;
|
|
||||||
|
|
||||||
final lines = linesRaw?.map((line) {
|
|
||||||
return LyricSlice(
|
|
||||||
time: Duration(milliseconds: int.parse(line["startTimeMs"])),
|
|
||||||
text: line["words"] as String,
|
|
||||||
);
|
|
||||||
}).toList() ??
|
|
||||||
[];
|
|
||||||
|
|
||||||
return SubtitleSimple(
|
|
||||||
lyrics: lines,
|
|
||||||
name: track.name!,
|
|
||||||
uri: res.request!.url,
|
|
||||||
rating: 100,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
jsonConfig: JsonConfig(
|
|
||||||
fromJson: (json) => SubtitleSimple.fromJson(json.castKeyDeep<String>()),
|
|
||||||
toJson: (data) => data.toJson(),
|
|
||||||
),
|
|
||||||
ref: ref,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,320 +0,0 @@
|
|||||||
import 'package:catcher_2/catcher_2.dart';
|
|
||||||
import 'package:fl_query/fl_query.dart';
|
|
||||||
import 'package:fl_query_hooks/fl_query_hooks.dart';
|
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
||||||
import 'package:spotify/spotify.dart';
|
|
||||||
import 'package:spotube/components/library/playlist_generate/recommendation_attribute_dials.dart';
|
|
||||||
import 'package:spotube/extensions/map.dart';
|
|
||||||
import 'package:spotube/extensions/track.dart';
|
|
||||||
import 'package:spotube/hooks/spotify/use_spotify_infinite_query.dart';
|
|
||||||
import 'package:spotube/hooks/spotify/use_spotify_query.dart';
|
|
||||||
import 'package:spotube/pages/library/playlist_generate/playlist_generate.dart';
|
|
||||||
import 'package:spotube/provider/custom_spotify_endpoint_provider.dart';
|
|
||||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
|
||||||
|
|
||||||
typedef RecommendationParameters = ({
|
|
||||||
RecommendationAttribute acousticness,
|
|
||||||
RecommendationAttribute danceability,
|
|
||||||
// ignore: non_constant_identifier_names
|
|
||||||
RecommendationAttribute duration_ms,
|
|
||||||
RecommendationAttribute energy,
|
|
||||||
RecommendationAttribute instrumentalness,
|
|
||||||
RecommendationAttribute key,
|
|
||||||
RecommendationAttribute liveness,
|
|
||||||
RecommendationAttribute loudness,
|
|
||||||
RecommendationAttribute mode,
|
|
||||||
RecommendationAttribute popularity,
|
|
||||||
RecommendationAttribute speechiness,
|
|
||||||
RecommendationAttribute tempo,
|
|
||||||
// ignore: non_constant_identifier_names
|
|
||||||
RecommendationAttribute time_signature,
|
|
||||||
RecommendationAttribute valence,
|
|
||||||
});
|
|
||||||
|
|
||||||
Map<String, num> recommendationAttributeToMap(RecommendationAttribute attr) => {
|
|
||||||
"min": attr.min,
|
|
||||||
"target": attr.target,
|
|
||||||
"max": attr.max,
|
|
||||||
};
|
|
||||||
|
|
||||||
({Map<String, num> min, Map<String, num> target, Map<String, num> max})
|
|
||||||
recommendationParametersToMap(RecommendationParameters params) {
|
|
||||||
final maxMap = <String, num>{
|
|
||||||
if (params.acousticness != zeroValues)
|
|
||||||
"acousticness": params.acousticness.max,
|
|
||||||
if (params.danceability != zeroValues)
|
|
||||||
"danceability": params.danceability.max,
|
|
||||||
if (params.duration_ms != zeroValues) "duration_ms": params.duration_ms.max,
|
|
||||||
if (params.energy != zeroValues) "energy": params.energy.max,
|
|
||||||
if (params.instrumentalness != zeroValues)
|
|
||||||
"instrumentalness": params.instrumentalness.max,
|
|
||||||
if (params.key != zeroValues) "key": params.key.max,
|
|
||||||
if (params.liveness != zeroValues) "liveness": params.liveness.max,
|
|
||||||
if (params.loudness != zeroValues) "loudness": params.loudness.max,
|
|
||||||
if (params.mode != zeroValues) "mode": params.mode.max,
|
|
||||||
if (params.popularity != zeroValues) "popularity": params.popularity.max,
|
|
||||||
if (params.speechiness != zeroValues) "speechiness": params.speechiness.max,
|
|
||||||
if (params.tempo != zeroValues) "tempo": params.tempo.max,
|
|
||||||
if (params.time_signature != zeroValues)
|
|
||||||
"time_signature": params.time_signature.max,
|
|
||||||
if (params.valence != zeroValues) "valence": params.valence.max,
|
|
||||||
};
|
|
||||||
final minMap = <String, num>{
|
|
||||||
if (params.acousticness != zeroValues)
|
|
||||||
"acousticness": params.acousticness.min,
|
|
||||||
if (params.danceability != zeroValues)
|
|
||||||
"danceability": params.danceability.min,
|
|
||||||
if (params.duration_ms != zeroValues) "duration_ms": params.duration_ms.min,
|
|
||||||
if (params.energy != zeroValues) "energy": params.energy.min,
|
|
||||||
if (params.instrumentalness != zeroValues)
|
|
||||||
"instrumentalness": params.instrumentalness.min,
|
|
||||||
if (params.key != zeroValues) "key": params.key.min,
|
|
||||||
if (params.liveness != zeroValues) "liveness": params.liveness.min,
|
|
||||||
if (params.loudness != zeroValues) "loudness": params.loudness.min,
|
|
||||||
if (params.mode != zeroValues) "mode": params.mode.min,
|
|
||||||
if (params.popularity != zeroValues) "popularity": params.popularity.min,
|
|
||||||
if (params.speechiness != zeroValues) "speechiness": params.speechiness.min,
|
|
||||||
if (params.tempo != zeroValues) "tempo": params.tempo.min,
|
|
||||||
if (params.time_signature != zeroValues)
|
|
||||||
"time_signature": params.time_signature.min,
|
|
||||||
if (params.valence != zeroValues) "valence": params.valence.min,
|
|
||||||
};
|
|
||||||
final targetMap = <String, num>{
|
|
||||||
if (params.acousticness != zeroValues)
|
|
||||||
"acousticness": params.acousticness.target,
|
|
||||||
if (params.danceability != zeroValues)
|
|
||||||
"danceability": params.danceability.target,
|
|
||||||
if (params.duration_ms != zeroValues)
|
|
||||||
"duration_ms": params.duration_ms.target,
|
|
||||||
if (params.energy != zeroValues) "energy": params.energy.target,
|
|
||||||
if (params.instrumentalness != zeroValues)
|
|
||||||
"instrumentalness": params.instrumentalness.target,
|
|
||||||
if (params.key != zeroValues) "key": params.key.target,
|
|
||||||
if (params.liveness != zeroValues) "liveness": params.liveness.target,
|
|
||||||
if (params.loudness != zeroValues) "loudness": params.loudness.target,
|
|
||||||
if (params.mode != zeroValues) "mode": params.mode.target,
|
|
||||||
if (params.popularity != zeroValues) "popularity": params.popularity.target,
|
|
||||||
if (params.speechiness != zeroValues)
|
|
||||||
"speechiness": params.speechiness.target,
|
|
||||||
if (params.tempo != zeroValues) "tempo": params.tempo.target,
|
|
||||||
if (params.time_signature != zeroValues)
|
|
||||||
"time_signature": params.time_signature.target,
|
|
||||||
if (params.valence != zeroValues) "valence": params.valence.target,
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
max: maxMap,
|
|
||||||
min: minMap,
|
|
||||||
target: targetMap,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class PlaylistQueries {
|
|
||||||
const PlaylistQueries();
|
|
||||||
|
|
||||||
Query<bool, dynamic> doesUserFollow(
|
|
||||||
WidgetRef ref,
|
|
||||||
String playlistId,
|
|
||||||
String userId,
|
|
||||||
) {
|
|
||||||
return useSpotifyQuery<bool, dynamic>(
|
|
||||||
"playlist-is-followed/$playlistId/$userId",
|
|
||||||
(spotify) async {
|
|
||||||
final result =
|
|
||||||
await spotify.playlists.followedByUsers(playlistId, [userId]);
|
|
||||||
return result[userId] ?? false;
|
|
||||||
},
|
|
||||||
ref: ref,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
InfiniteQuery<Page<PlaylistSimple>, dynamic, int> ofMine(WidgetRef ref) {
|
|
||||||
return useSpotifyInfiniteQuery<Page<PlaylistSimple>, dynamic, int>(
|
|
||||||
"current-user-playlists",
|
|
||||||
(page, spotify) async {
|
|
||||||
final playlists = await spotify.playlists.me.getPage(10, page * 10);
|
|
||||||
return playlists;
|
|
||||||
},
|
|
||||||
initialPage: 0,
|
|
||||||
nextPage: (lastPage, lastPageData) =>
|
|
||||||
(lastPageData.items?.length ?? 0) < 10 || lastPageData.isLast
|
|
||||||
? null
|
|
||||||
: lastPage + 1,
|
|
||||||
ref: ref,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Query<List<PlaylistSimple>, dynamic> ofMineAll(WidgetRef ref) {
|
|
||||||
return useSpotifyQuery<List<PlaylistSimple>, dynamic>(
|
|
||||||
"current-user-all-playlists",
|
|
||||||
(spotify) async {
|
|
||||||
var page = await spotify.playlists.me.getPage(50);
|
|
||||||
final playlists = <PlaylistSimple>[];
|
|
||||||
|
|
||||||
if (page.isLast == true) {
|
|
||||||
return page.items?.toList() ?? [];
|
|
||||||
}
|
|
||||||
|
|
||||||
playlists.addAll(page.items ?? []);
|
|
||||||
while (!page.isLast) {
|
|
||||||
page = await spotify.playlists.me.getPage(50, page.nextOffset);
|
|
||||||
playlists.addAll(page.items ?? []);
|
|
||||||
}
|
|
||||||
|
|
||||||
return playlists;
|
|
||||||
},
|
|
||||||
ref: ref,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<Track>> likedTracks(SpotifyApi spotify) async {
|
|
||||||
final tracks = await spotify.tracks.me.saved.all();
|
|
||||||
|
|
||||||
return tracks.map((e) => e.track!).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
Query<List<Track>, dynamic> likedTracksQuery(WidgetRef ref) {
|
|
||||||
final query = useCallback((spotify) => likedTracks(spotify), []);
|
|
||||||
final context = useContext();
|
|
||||||
|
|
||||||
return useSpotifyQuery<List<Track>, dynamic>(
|
|
||||||
"user-liked-tracks",
|
|
||||||
query,
|
|
||||||
jsonConfig: JsonConfig(
|
|
||||||
toJson: (tracks) => <String, dynamic>{
|
|
||||||
'tracks': tracks.map((e) => e.toJson()).toList(),
|
|
||||||
},
|
|
||||||
fromJson: (json) => (json['tracks'] as List)
|
|
||||||
.map(
|
|
||||||
(e) => Track.fromJson((e as Map).castKeyDeep<String>()),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
),
|
|
||||||
refreshConfig: RefreshConfig.withDefaults(
|
|
||||||
context,
|
|
||||||
// will never make it stale
|
|
||||||
staleDuration: const Duration(days: 60),
|
|
||||||
),
|
|
||||||
ref: ref,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Query<Playlist, dynamic> byId(WidgetRef ref, String id) {
|
|
||||||
return useSpotifyQuery<Playlist, dynamic>(
|
|
||||||
"playlist/$id",
|
|
||||||
(spotify) async {
|
|
||||||
return await spotify.playlists.get(id);
|
|
||||||
},
|
|
||||||
ref: ref,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<Track>> tracksOf(
|
|
||||||
int pageParam,
|
|
||||||
SpotifyApi spotify,
|
|
||||||
String playlistId,
|
|
||||||
) async {
|
|
||||||
try {
|
|
||||||
final playlists = await spotify.playlists
|
|
||||||
.getTracksByPlaylistId(playlistId)
|
|
||||||
.getPage(20, pageParam * 20);
|
|
||||||
return playlists.items?.toList() ?? <Track>[];
|
|
||||||
} catch (e, stack) {
|
|
||||||
Catcher2.reportCheckedError(e, stack);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int? tracksOfQueryNextPage(int lastPage, List<Track> lastPageData) {
|
|
||||||
if (lastPageData.length < 20) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return lastPage + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
InfiniteQuery<List<Track>, dynamic, int> tracksOfQuery(
|
|
||||||
WidgetRef ref,
|
|
||||||
String playlistId,
|
|
||||||
) {
|
|
||||||
return useSpotifyInfiniteQuery<List<Track>, dynamic, int>(
|
|
||||||
"playlist-tracks/$playlistId",
|
|
||||||
(page, spotify) => tracksOf(page, spotify, playlistId),
|
|
||||||
initialPage: 0,
|
|
||||||
nextPage: tracksOfQueryNextPage,
|
|
||||||
ref: ref,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
InfiniteQuery<Page<PlaylistSimple>, dynamic, int> featured(
|
|
||||||
WidgetRef ref,
|
|
||||||
) {
|
|
||||||
return useSpotifyInfiniteQuery<Page<PlaylistSimple>, dynamic, int>(
|
|
||||||
"featured-playlists",
|
|
||||||
(pageParam, spotify) async {
|
|
||||||
try {
|
|
||||||
final playlists =
|
|
||||||
await spotify.playlists.featured.getPage(5, pageParam);
|
|
||||||
return playlists;
|
|
||||||
} catch (e, stack) {
|
|
||||||
Catcher2.reportCheckedError(e, stack);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
initialPage: 0,
|
|
||||||
nextPage: (lastPage, lastPageData) {
|
|
||||||
if (lastPageData.isLast || (lastPageData.items ?? []).length < 5) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return lastPageData.nextOffset;
|
|
||||||
},
|
|
||||||
ref: ref,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Query<List<Track>, dynamic> generate(
|
|
||||||
WidgetRef ref, {
|
|
||||||
({List<String> tracks, List<String> artists, List<String> genres})? seeds,
|
|
||||||
RecommendationParameters? parameters,
|
|
||||||
int limit = 20,
|
|
||||||
Market? market,
|
|
||||||
}) {
|
|
||||||
final marketOfPreference = ref.watch(
|
|
||||||
userPreferencesProvider.select((s) => s.recommendationMarket),
|
|
||||||
);
|
|
||||||
final customSpotify = ref.watch(customSpotifyEndpointProvider);
|
|
||||||
|
|
||||||
final parametersMap =
|
|
||||||
parameters == null ? null : recommendationParametersToMap(parameters);
|
|
||||||
|
|
||||||
final query = useQuery<List<Track>, dynamic>(
|
|
||||||
"generate-playlist",
|
|
||||||
() async {
|
|
||||||
final tracks = await customSpotify.getRecommendations(
|
|
||||||
limit: limit,
|
|
||||||
market: market ?? marketOfPreference,
|
|
||||||
max: parametersMap?.max,
|
|
||||||
min: parametersMap?.min,
|
|
||||||
target: parametersMap?.target,
|
|
||||||
seedArtists: seeds?.artists,
|
|
||||||
seedGenres: seeds?.genres,
|
|
||||||
seedTracks: seeds?.tracks,
|
|
||||||
);
|
|
||||||
return tracks;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() {
|
|
||||||
return ref.listenManual(
|
|
||||||
customSpotifyEndpointProvider,
|
|
||||||
(previous, next) {
|
|
||||||
if (previous != next) {
|
|
||||||
query.refresh();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
).close;
|
|
||||||
}, [query]);
|
|
||||||
|
|
||||||
return query;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
import 'package:spotube/services/queries/album.dart';
|
|
||||||
import 'package:spotube/services/queries/artist.dart';
|
|
||||||
import 'package:spotube/services/queries/category.dart';
|
|
||||||
import 'package:spotube/services/queries/lyrics.dart';
|
|
||||||
import 'package:spotube/services/queries/playlist.dart';
|
|
||||||
import 'package:spotube/services/queries/search.dart';
|
|
||||||
import 'package:spotube/services/queries/tracks.dart';
|
|
||||||
import 'package:spotube/services/queries/user.dart';
|
|
||||||
import 'package:spotube/services/queries/views.dart';
|
|
||||||
|
|
||||||
class Queries {
|
|
||||||
const Queries._();
|
|
||||||
final album = const AlbumQueries();
|
|
||||||
final artist = const ArtistQueries();
|
|
||||||
final category = const CategoryQueries();
|
|
||||||
final lyrics = const LyricsQueries();
|
|
||||||
final playlist = const PlaylistQueries();
|
|
||||||
final search = const SearchQueries();
|
|
||||||
final user = const UserQueries();
|
|
||||||
final views = const ViewsQueries();
|
|
||||||
final tracks = const TracksQueries();
|
|
||||||
}
|
|
||||||
|
|
||||||
const useQueries = Queries._();
|
|
||||||
@ -1,60 +0,0 @@
|
|||||||
import 'package:fl_query/fl_query.dart';
|
|
||||||
import 'package:fl_query_hooks/fl_query_hooks.dart';
|
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
||||||
import 'package:spotify/spotify.dart';
|
|
||||||
import 'package:spotube/provider/spotify_provider.dart';
|
|
||||||
|
|
||||||
typedef SearchParams = ({
|
|
||||||
SpotifyApi spotify,
|
|
||||||
SearchType searchType,
|
|
||||||
String query
|
|
||||||
});
|
|
||||||
|
|
||||||
class SearchQueries {
|
|
||||||
const SearchQueries();
|
|
||||||
|
|
||||||
static final queryJob =
|
|
||||||
InfiniteQueryJob.withVariableKey<List<Page>, dynamic, int, SearchParams>(
|
|
||||||
baseQueryKey: "search-query",
|
|
||||||
task: (variableKey, page, args) => args!.spotify.search.get(
|
|
||||||
args.query,
|
|
||||||
types: [args.searchType],
|
|
||||||
).getPage(10, page),
|
|
||||||
initialPage: 0,
|
|
||||||
nextPage: (lastPage, lastPageData) {
|
|
||||||
if (lastPageData.isEmpty) return null;
|
|
||||||
if ((lastPageData.first.isLast ||
|
|
||||||
(lastPageData.first.items ?? []).length < 10)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return lastPageData.first.nextOffset;
|
|
||||||
},
|
|
||||||
enabled: false,
|
|
||||||
);
|
|
||||||
|
|
||||||
InfiniteQuery<List<Page>, dynamic, int> query(
|
|
||||||
WidgetRef ref,
|
|
||||||
String queryStr,
|
|
||||||
SearchType searchType,
|
|
||||||
) {
|
|
||||||
final spotify = ref.watch(spotifyProvider);
|
|
||||||
final query = useInfiniteQueryJob<List<Page>, dynamic, int, SearchParams>(
|
|
||||||
job: queryJob(searchType.name),
|
|
||||||
args: (spotify: spotify, searchType: searchType, query: queryStr),
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() {
|
|
||||||
return ref.listenManual(
|
|
||||||
spotifyProvider,
|
|
||||||
(previous, next) {
|
|
||||||
if (previous != next) {
|
|
||||||
query.refreshAll();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
).close;
|
|
||||||
}, [query]);
|
|
||||||
|
|
||||||
return query;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
import 'package:fl_query/fl_query.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
||||||
import 'package:spotify/spotify.dart';
|
|
||||||
import 'package:spotube/hooks/spotify/use_spotify_query.dart';
|
|
||||||
|
|
||||||
class TracksQueries {
|
|
||||||
const TracksQueries();
|
|
||||||
|
|
||||||
Query<Track, dynamic> track(WidgetRef ref, String id) {
|
|
||||||
return useSpotifyQuery(
|
|
||||||
"track/$id",
|
|
||||||
(spotify) => spotify.tracks.get(id),
|
|
||||||
ref: ref,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,53 +0,0 @@
|
|||||||
import 'package:fl_query/fl_query.dart';
|
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
||||||
import 'package:spotify/spotify.dart';
|
|
||||||
import 'package:spotube/hooks/spotify/use_spotify_query.dart';
|
|
||||||
import 'package:spotube/models/spotify_friends.dart';
|
|
||||||
import 'package:spotube/provider/authentication_provider.dart';
|
|
||||||
import 'package:spotube/provider/custom_spotify_endpoint_provider.dart';
|
|
||||||
import 'package:spotube/utils/type_conversion_utils.dart';
|
|
||||||
|
|
||||||
class UserQueries {
|
|
||||||
const UserQueries();
|
|
||||||
Query<User?, dynamic> me(WidgetRef ref) {
|
|
||||||
final context = useContext();
|
|
||||||
|
|
||||||
return useSpotifyQuery<User, dynamic>(
|
|
||||||
"current-user",
|
|
||||||
(spotify) async {
|
|
||||||
final me = await spotify.me.get();
|
|
||||||
if (ref.read(AuthenticationNotifier.provider) == null) return null;
|
|
||||||
if (me.images == null || me.images?.isEmpty == true) {
|
|
||||||
me.images = [
|
|
||||||
Image()
|
|
||||||
..height = 50
|
|
||||||
..width = 50
|
|
||||||
..url = TypeConversionUtils.image_X_UrlString(
|
|
||||||
me.images,
|
|
||||||
placeholder: ImagePlaceholder.artist,
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
return me;
|
|
||||||
},
|
|
||||||
refreshConfig: RefreshConfig.withDefaults(
|
|
||||||
context,
|
|
||||||
// will never make it stale
|
|
||||||
staleDuration: const Duration(days: 60),
|
|
||||||
),
|
|
||||||
ref: ref,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Query<SpotifyFriends, dynamic> friendActivity(WidgetRef ref) {
|
|
||||||
final customSpotify = ref.read(customSpotifyEndpointProvider);
|
|
||||||
return useSpotifyQuery<SpotifyFriends, dynamic>(
|
|
||||||
"friend-activity",
|
|
||||||
(spotify) {
|
|
||||||
return customSpotify.getFriendActivity();
|
|
||||||
},
|
|
||||||
ref: ref,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,47 +0,0 @@
|
|||||||
import 'package:fl_query/fl_query.dart';
|
|
||||||
import 'package:fl_query_hooks/fl_query_hooks.dart';
|
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
||||||
import 'package:spotube/extensions/context.dart';
|
|
||||||
import 'package:spotube/provider/authentication_provider.dart';
|
|
||||||
import 'package:spotube/provider/custom_spotify_endpoint_provider.dart';
|
|
||||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
|
||||||
|
|
||||||
class ViewsQueries {
|
|
||||||
const ViewsQueries();
|
|
||||||
|
|
||||||
Query<Map<String, dynamic>?, dynamic> get(
|
|
||||||
WidgetRef ref,
|
|
||||||
String view,
|
|
||||||
) {
|
|
||||||
final customSpotify = ref.watch(customSpotifyEndpointProvider);
|
|
||||||
final auth = ref.watch(AuthenticationNotifier.provider);
|
|
||||||
final market = ref
|
|
||||||
.watch(userPreferencesProvider.select((s) => s.recommendationMarket));
|
|
||||||
|
|
||||||
final locale = useContext().l10n.localeName;
|
|
||||||
|
|
||||||
final query = useQuery<Map<String, dynamic>?, dynamic>("views/$view", () {
|
|
||||||
if (auth == null) return null;
|
|
||||||
return customSpotify.getView(
|
|
||||||
view,
|
|
||||||
market: market,
|
|
||||||
country: market,
|
|
||||||
locale: locale,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() {
|
|
||||||
return ref.listenManual(
|
|
||||||
customSpotifyEndpointProvider,
|
|
||||||
(previous, next) {
|
|
||||||
if (previous != next) {
|
|
||||||
query.refresh();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
).close;
|
|
||||||
}, [query]);
|
|
||||||
|
|
||||||
return query;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
40
pubspec.lock
40
pubspec.lock
@ -675,30 +675,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.1.0"
|
||||||
fl_query:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: fl_query
|
|
||||||
sha256: daee5ab0ed8899baa201b89b5813107df5258144a9e2bcf192dbcf922c57d985
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.0.0"
|
|
||||||
fl_query_devtools:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: fl_query_devtools
|
|
||||||
sha256: "2ae8905fd4a95f1d245a1b54057c31c8d27fc961223bcb7ce13088bcf6595059"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.1.0"
|
|
||||||
fl_query_hooks:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: fl_query_hooks
|
|
||||||
sha256: "6c88b3bfbdc3e1330931b927903929d7351f86fc63266ac93b3acb9f133a09a9"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.0.0"
|
|
||||||
fluentui_system_icons:
|
fluentui_system_icons:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -1319,14 +1295,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.7.1"
|
version: "6.7.1"
|
||||||
json_view:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: json_view
|
|
||||||
sha256: "905c69f9e69d1eab5406b87ab6c10c3706c04c70c6a4959621bd2b43c2d27374"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.4.2"
|
|
||||||
leak_tracker:
|
leak_tracker:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1495,14 +1463,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.4"
|
version: "1.0.4"
|
||||||
mutex:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: mutex
|
|
||||||
sha256: "03116a4e46282a671b46c12de649d72c0ed18188ffe12a8d0fc63e83f4ad88f4"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "3.0.1"
|
|
||||||
nested:
|
nested:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@ -32,9 +32,6 @@ dependencies:
|
|||||||
duration: ^3.0.12
|
duration: ^3.0.12
|
||||||
envied: ^0.3.0
|
envied: ^0.3.0
|
||||||
file_selector: ^1.0.1
|
file_selector: ^1.0.1
|
||||||
fl_query: ^1.0.0
|
|
||||||
fl_query_hooks: ^1.0.0
|
|
||||||
fl_query_devtools: ^0.1.0
|
|
||||||
fluentui_system_icons: ^1.1.189
|
fluentui_system_icons: ^1.1.189
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user