diff --git a/lib/components/artist/artist_album_list.dart b/lib/components/artist/artist_album_list.dart index 0d736471..a91327ce 100644 --- a/lib/components/artist/artist_album_list.dart +++ b/lib/components/artist/artist_album_list.dart @@ -1,11 +1,10 @@ import 'package:flutter/material.dart' hide Page; -import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotify/spotify.dart'; import 'package:spotube/components/shared/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart'; import 'package:spotube/extensions/context.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 { final String artistId; @@ -18,21 +17,19 @@ class ArtistAlbumList extends HookConsumerWidget { @override 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(() { - return albumsQuery.pages - .expand((page) => page.items ?? const Iterable.empty()) - .toList(); - }, [albumsQuery.pages]); + final albums = albumsQuery.asData?.value.items ?? []; final theme = Theme.of(context); return HorizontalPlaybuttonCardView( isLoadingNextPage: albumsQuery.isLoadingNextPage, - hasNextPage: albumsQuery.hasNextPage, + hasNextPage: albumsQuery.asData?.value.hasMore ?? false, items: albums, - onFetchMore: albumsQuery.fetchNext, + onFetchMore: albumsQueryNotifier.fetchMore, title: Text( context.l10n.albums, style: theme.textTheme.headlineSmall, diff --git a/lib/components/home/sections/friends/friend_item.dart b/lib/components/home/sections/friends/friend_item.dart index 0bb2502a..b883e2cc 100644 --- a/lib/components/home/sections/friends/friend_item.dart +++ b/lib/components/home/sections/friends/friend_item.dart @@ -1,10 +1,8 @@ -import 'package:fl_query_hooks/fl_query_hooks.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/shared/image/universal_image.dart'; import 'package:spotube/models/spotify_friends.dart'; @@ -24,7 +22,6 @@ class FriendItem extends HookConsumerWidget { colorScheme: colorScheme, ) = Theme.of(context); - final queryClient = useQueryClient(); final spotify = ref.watch(spotifyProvider); return Container( @@ -86,15 +83,11 @@ class FriendItem extends HookConsumerWidget { ..onTap = () async { context.push( "/${friend.track.context.path}", - extra: !friend.track.context.path - .startsWith("album") - ? null - : await queryClient.fetchQuery( - "album/${friend.track.album.id}", - () => spotify.albums.get( - friend.track.album.id, - ), - ), + extra: + !friend.track.context.path.startsWith("album") + ? null + : await spotify.albums + .get(friend.track.context.id), ); }, ), @@ -110,12 +103,7 @@ class FriendItem extends HookConsumerWidget { recognizer: TapGestureRecognizer() ..onTap = () async { final album = - await queryClient.fetchQuery( - "album/${friend.track.album.id}", - () => spotify.albums.get( - friend.track.album.id, - ), - ); + await spotify.albums.get(friend.track.album.id); if (context.mounted) { context.push( "/album/${friend.track.album.id}", diff --git a/lib/components/playlist/playlist_create_dialog.dart b/lib/components/playlist/playlist_create_dialog.dart index 09dbb113..669dce51 100644 --- a/lib/components/playlist/playlist_create_dialog.dart +++ b/lib/components/playlist/playlist_create_dialog.dart @@ -16,7 +16,6 @@ import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/provider/spotify/spotify.dart'; import 'package:spotube/provider/spotify_provider.dart'; -import 'package:spotube/services/mutations/playlist.dart'; import 'package:spotube/utils/type_conversion_utils.dart'; class PlaylistCreateDialog extends HookConsumerWidget { @@ -90,7 +89,7 @@ class PlaylistCreateDialog extends HookConsumerWidget { Future onCreate() async { if (!formKey.currentState!.validate()) return; - final PlaylistCRUDVariables payload = ( + final PlaylistInput payload = ( playlistName: playlistName.text, collaborative: collaborative.value, public: public.value, diff --git a/lib/components/root/sidebar.dart b/lib/components/root/sidebar.dart index fdcb3eb2..21259a94 100644 --- a/lib/components/root/sidebar.dart +++ b/lib/components/root/sidebar.dart @@ -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/provider/download_manager_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_state.dart'; -import 'package:spotube/services/queries/queries.dart'; import 'package:spotube/utils/platform.dart'; import 'package:spotube/utils/type_conversion_utils.dart'; @@ -241,8 +241,8 @@ class SidebarFooter extends HookConsumerWidget { Widget build(BuildContext context, ref) { final theme = Theme.of(context); final mediaQuery = MediaQuery.of(context); - final me = useQueries.user.me(ref); - final data = me.data; + final me = ref.watch(meProvider); + final data = me.asData?.value; final avatarImg = TypeConversionUtils.image_X_UrlString( data?.images, diff --git a/lib/components/shared/track_tile/track_options.dart b/lib/components/shared/track_tile/track_options.dart index b2d0e5cd..8522738d 100644 --- a/lib/components/shared/track_tile/track_options.dart +++ b/lib/components/shared/track_tile/track_options.dart @@ -1,6 +1,5 @@ import 'dart:io'; -import 'package:fl_query/fl_query.dart'; import 'package:flutter/material.dart' hide Page; import 'package:flutter/services.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/spotify/spotify.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:url_launcher/url_launcher_string.dart'; @@ -100,21 +97,10 @@ class TrackOptions extends HookConsumerWidget { final playlist = ref.read(ProxyPlaylistNotifier.provider); final spotify = ref.read(spotifyProvider); final query = "${track.name} Radio"; - final pages = await QueryClient.of(context) - .fetchInfiniteQueryJob, dynamic, int, SearchParams>( - job: SearchQueries.queryJob(query), - args: ( - spotify: spotify, - searchType: SearchType.playlist, - query: query, - ), - ) ?? - []; + final pages = + await spotify.search.get(query, types: [SearchType.playlist]).first(); - final radios = pages - .expand((e) => e.items?.toList() ?? []) - .toList() - .cast(); + final radios = pages.map((e) => e.items).toList().cast(); final artists = track.artists!.map((e) => e.name); @@ -192,10 +178,8 @@ class TrackOptions extends HookConsumerWidget { ); final removingTrack = useState(null); - final removeTrack = useMutations.playlist.removeTrackOf( - ref, - playlistId ?? "", - ); + final favoritePlaylistsNotifier = + ref.watch(favoritePlaylistsProvider.notifier); final isInQueue = useMemoized(() { if (playlist.activeTrack == null) return false; @@ -266,7 +250,8 @@ class TrackOptions extends HookConsumerWidget { break; case TrackOptionValue.removeFromPlaylist: removingTrack.value = track.uri; - removeTrack.mutate(track.uri!); + favoritePlaylistsNotifier + .removeTracks(playlistId ?? "", [track.id!]); break; case TrackOptionValue.blacklist: if (isBlackListed) { @@ -330,7 +315,7 @@ class TrackOptions extends HookConsumerWidget { ), ], children: switch (track.runtimeType) { - LocalTrack => [ + LocalTrack() => [ PopSheetEntry( value: TrackOptionValue.delete, leading: const Icon(SpotubeIcons.trash), @@ -393,10 +378,7 @@ class TrackOptions extends HookConsumerWidget { if (userPlaylist && auth != null) PopSheetEntry( value: TrackOptionValue.removeFromPlaylist, - leading: (removeTrack.isMutating || !removeTrack.hasData) && - removingTrack.value == track.uri - ? const CircularProgressIndicator() - : const Icon(SpotubeIcons.removeFilled), + leading: const Icon(SpotubeIcons.removeFilled), title: Text(context.l10n.remove_from_playlist), ), PopSheetEntry( diff --git a/lib/components/shared/tracks_view/track_view_props.dart b/lib/components/shared/tracks_view/track_view_props.dart index 21bbaec7..a1a07f84 100644 --- a/lib/components/shared/tracks_view/track_view_props.dart +++ b/lib/components/shared/tracks_view/track_view_props.dart @@ -1,6 +1,5 @@ import 'dart:async'; -import 'package:fl_query/fl_query.dart'; import 'package:flutter/material.dart' hide Page; import 'package:spotify/spotify.dart'; @@ -19,19 +18,6 @@ class PaginationProps { required this.onRefresh, }); - factory PaginationProps.fromQuery( - InfiniteQuery, dynamic, int> query, { - required Future> Function() onFetchAll, - }) { - return PaginationProps( - hasNextPage: query.hasNextPage, - isLoading: query.isLoadingNextPage, - onFetchMore: query.fetchNext, - onFetchAll: onFetchAll, - onRefresh: query.refreshAll, - ); - } - @override operator ==(Object other) { return other is PaginationProps && diff --git a/lib/extensions/infinite_query.dart b/lib/extensions/infinite_query.dart deleted file mode 100644 index 2181ab3c..00000000 --- a/lib/extensions/infinite_query.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'package:fl_query/fl_query.dart'; -import 'package:spotify/spotify.dart'; - -extension FetchAllTracks on InfiniteQuery, dynamic, int> { - Future> fetchAllTracks({ - required Future> 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> 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(); - } -} diff --git a/lib/hooks/configurators/use_deep_linking.dart b/lib/hooks/configurators/use_deep_linking.dart index f11a1cff..2650b05c 100644 --- a/lib/hooks/configurators/use_deep_linking.dart +++ b/lib/hooks/configurators/use_deep_linking.dart @@ -1,10 +1,8 @@ import 'dart:async'; 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:hooks_riverpod/hooks_riverpod.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/collections/routes.dart'; import 'package:spotube/provider/spotify_provider.dart'; import 'package:flutter_sharing_intent/flutter_sharing_intent.dart'; @@ -17,8 +15,6 @@ final linkStream = appLinks.allStringLinkStream.asBroadcastStream(); void useDeepLinking(WidgetRef ref) { // single instance no worries final spotify = ref.watch(spotifyProvider); - final queryClient = useQueryClient(); - final router = ref.watch(routerProvider); useEffect(() { @@ -32,10 +28,7 @@ void useDeepLinking(WidgetRef ref) { case "album": router.push( "/album/${url.pathSegments.last}", - extra: await queryClient.fetchQuery( - "album/${url.pathSegments.last}", - () => spotify.albums.get(url.pathSegments.last), - ), + extra: await spotify.albums.get(url.pathSegments.last), ); break; case "artist": @@ -44,10 +37,7 @@ void useDeepLinking(WidgetRef ref) { case "playlist": router.push( "/playlist/${url.pathSegments.last}", - extra: await queryClient.fetchQuery( - "playlist/${url.pathSegments.last}", - () => spotify.playlists.get(url.pathSegments.last), - ), + extra: await spotify.playlists.get(url.pathSegments.last), ); break; case "track": @@ -78,10 +68,7 @@ void useDeepLinking(WidgetRef ref) { case "spotify:album": await router.push( "/album/$endSegment", - extra: await queryClient.fetchQuery( - "album/$endSegment", - () => spotify.albums.get(endSegment), - ), + extra: await spotify.albums.get(endSegment), ); break; case "spotify:artist": @@ -93,10 +80,7 @@ void useDeepLinking(WidgetRef ref) { case "spotify:playlist": await router.push( "/playlist/$endSegment", - extra: await queryClient.fetchQuery( - "playlist/$endSegment", - () => spotify.playlists.get(endSegment), - ), + extra: await spotify.playlists.get(endSegment), ); break; default: @@ -108,5 +92,5 @@ void useDeepLinking(WidgetRef ref) { mediaStream?.cancel(); subscription.cancel(); }; - }, [spotify, queryClient]); + }, [spotify]); } diff --git a/lib/hooks/configurators/use_endless_playback.dart b/lib/hooks/configurators/use_endless_playback.dart index f5d11829..3cd55e40 100644 --- a/lib/hooks/configurators/use_endless_playback.dart +++ b/lib/hooks/configurators/use_endless_playback.dart @@ -1,5 +1,4 @@ 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_riverpod/flutter_riverpod.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/user_preferences/user_preferences_provider.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; -import 'package:spotube/services/queries/search.dart'; void useEndlessPlayback(WidgetRef ref) { final auth = ref.watch(AuthenticationNotifier.provider); @@ -18,7 +16,6 @@ void useEndlessPlayback(WidgetRef ref) { final endlessPlayback = ref.watch(userPreferencesProvider.select((s) => s.endlessPlayback)); - final queryClient = useQueryClient(); useEffect( () { @@ -32,16 +29,8 @@ void useEndlessPlayback(WidgetRef ref) { final track = playlist.tracks.last; final query = "${track.name} Radio"; - final pages = await queryClient.fetchInfiniteQueryJob, - dynamic, int, SearchParams>( - job: SearchQueries.queryJob(query), - args: ( - spotify: spotify, - searchType: SearchType.playlist, - query: query - ), - ) ?? - []; + final pages = await spotify.search + .get(query, types: [SearchType.playlist]).first(); final radios = pages .expand((e) => e.items?.toList() ?? []) @@ -94,7 +83,6 @@ void useEndlessPlayback(WidgetRef ref) { [ spotify, playback, - queryClient, playlist.tracks, endlessPlayback, auth, diff --git a/lib/hooks/spotify/use_spotify_infinite_query.dart b/lib/hooks/spotify/use_spotify_infinite_query.dart deleted file mode 100644 index 2063b083..00000000 --- a/lib/hooks/spotify/use_spotify_infinite_query.dart +++ /dev/null @@ -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 - useSpotifyInfiniteQuery( - String queryKey, - FutureOr Function(PageType page, SpotifyApi spotify) queryFn, { - required WidgetRef ref, - required InfiniteQueryNextPage nextPage, - required PageType initialPage, - RetryConfig? retryConfig, - RefreshConfig? refreshConfig, - JsonConfig? jsonConfig, - ValueChanged>? onData, - ValueChanged>? onError, - bool enabled = true, - List? keys, -}) { - final spotify = ref.watch(spotifyProvider); - final query = useInfiniteQuery( - 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; -} diff --git a/lib/hooks/spotify/use_spotify_mutation.dart b/lib/hooks/spotify/use_spotify_mutation.dart deleted file mode 100644 index 637f778f..00000000 --- a/lib/hooks/spotify/use_spotify_mutation.dart +++ /dev/null @@ -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 - useSpotifyMutation( - String mutationKey, - Future Function(VariablesType variables, SpotifyApi spotify) - mutationFn, { - required WidgetRef ref, - RetryConfig? retryConfig, - MutationOnDataFn? onData, - MutationOnErrorFn? onError, - MutationOnMutationFn? onMutate, - List? refreshQueries, - List? refreshInfiniteQueries, - List? keys, -}) { - final spotify = ref.watch(spotifyProvider); - final mutation = - useMutation( - mutationKey, - (variables) => mutationFn(variables, spotify), - retryConfig: retryConfig, - onData: onData, - onError: onError, - onMutate: onMutate, - refreshQueries: refreshQueries, - refreshInfiniteQueries: refreshInfiniteQueries, - keys: keys, - ); - - return mutation; -} diff --git a/lib/hooks/spotify/use_spotify_query.dart b/lib/hooks/spotify/use_spotify_query.dart deleted file mode 100644 index 0c79de91..00000000 --- a/lib/hooks/spotify/use_spotify_query.dart +++ /dev/null @@ -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 = FutureOr Function( - SpotifyApi spotify); - -Query useSpotifyQuery( - final String queryKey, - final SpotifyQueryFn queryFn, { - required WidgetRef ref, - final DataType? initial, - final RetryConfig? retryConfig, - final RefreshConfig? refreshConfig, - final JsonConfig? jsonConfig, - final ValueChanged? onData, - final ValueChanged? onError, - final bool enabled = true, -}) { - final spotify = ref.watch(spotifyProvider); - - final query = useQuery( - 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; -} diff --git a/lib/main.dart b/lib/main.dart index 61fa9044..5c100fd3 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,7 +1,6 @@ import 'package:catcher_2/catcher_2.dart'; import 'package:dart_discord_rpc/dart_discord_rpc.dart'; import 'package:device_preview/device_preview.dart'; -import 'package:fl_query/fl_query.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.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/services/audio_player/audio_player.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/themes/theme.dart'; import 'package:spotube/utils/persisted_state_notifier.dart'; @@ -74,11 +72,7 @@ Future main(List rawArgs) async { final hiveCacheDir = kIsWeb ? null : (await getApplicationSupportDirectory()).path; - await QueryClient.initialize( - cachePrefix: "oss.krtirtho.spotube", - cacheDir: hiveCacheDir, - connectivity: FlQueryInternetConnectionCheckerAdapter(), - ); + Hive.init(hiveCacheDir); Hive.registerAdapter(SkipSegmentAdapter()); @@ -144,10 +138,7 @@ Future main(List rawArgs) async { orientation: Orientation.portrait, ), builder: (context) { - return QueryClientProvider( - staleDuration: const Duration(minutes: 30), - child: const Spotube(), - ); + return const Spotube(); }, ), ), diff --git a/lib/pages/root/root_app.dart b/lib/pages/root/root_app.dart index ec6f899e..b562adab 100644 --- a/lib/pages/root/root_app.dart +++ b/lib/pages/root/root_app.dart @@ -1,6 +1,5 @@ import 'dart:async'; -import 'package:fl_query/fl_query.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.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_update_checker.dart'; import 'package:spotube/provider/download_manager_provider.dart'; +import 'package:spotube/services/connectivity_adapter.dart'; import 'package:spotube/utils/persisted_state_notifier.dart'; const rootPaths = { @@ -53,8 +53,9 @@ class RootApp extends HookConsumerWidget { } }); - final subscription = - QueryClient.connectivity.onConnectivityChanged.listen((status) { + final subscription = ConnectionCheckerService + .instance.onConnectivityChanged + .listen((status) { if (status) { scaffoldMessenger.showSnackBar( SnackBar( diff --git a/lib/pages/track/track.dart b/lib/pages/track/track.dart index 5a82c589..ca5dbf95 100644 --- a/lib/pages/track/track.dart +++ b/lib/pages/track/track.dart @@ -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/extensions/context.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/queries/queries.dart'; import 'package:spotube/utils/type_conversion_utils.dart'; import 'package:spotube/extensions/constrains.dart'; @@ -35,9 +35,9 @@ class TrackPage extends HookConsumerWidget { 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 { if (isActive) { diff --git a/lib/provider/authentication_provider.dart b/lib/provider/authentication_provider.dart index cd77e7bb..f1cf58ec 100644 --- a/lib/provider/authentication_provider.dart +++ b/lib/provider/authentication_provider.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'dart:convert'; -import 'package:fl_query/fl_query.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:http/http.dart'; @@ -52,8 +51,7 @@ class AuthenticationCredentials { ), ); } catch (e) { - if (rootNavigatorKey?.currentContext != null && - await QueryClient.connectivity.isConnected) { + if (rootNavigatorKey?.currentContext != null) { showPromptDialog( context: rootNavigatorKey!.currentContext!, title: rootNavigatorKey!.currentContext!.l10n diff --git a/lib/provider/spotify/artist/albums.dart b/lib/provider/spotify/artist/albums.dart index c50d0a9b..0e7ea84b 100644 --- a/lib/provider/spotify/artist/albums.dart +++ b/lib/provider/spotify/artist/albums.dart @@ -1,6 +1,6 @@ part of '../spotify.dart'; -class ArtistAlbumsState extends PaginatedState { +class ArtistAlbumsState extends PaginatedState { ArtistAlbumsState({ required super.items, required super.offset, @@ -10,7 +10,7 @@ class ArtistAlbumsState extends PaginatedState { @override ArtistAlbumsState copyWith({ - List? items, + List? items, int? offset, int? limit, bool? hasMore, @@ -24,8 +24,8 @@ class ArtistAlbumsState extends PaginatedState { } } -class ArtistAlbumsNotifier extends FamilyPaginatedAsyncNotifier { +class ArtistAlbumsNotifier + extends FamilyPaginatedAsyncNotifier { ArtistAlbumsNotifier() : super(); @override diff --git a/lib/provider/spotify/playlist/favorite.dart b/lib/provider/spotify/playlist/favorite.dart index afa764ac..a0e051aa 100644 --- a/lib/provider/spotify/playlist/favorite.dart +++ b/lib/provider/spotify/playlist/favorite.dart @@ -85,6 +85,19 @@ class FavoritePlaylistsNotifier ref.invalidate(playlistTracksProvider(playlistId)); } + + Future removeTracks(String playlistId, List 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 = diff --git a/lib/services/connectivity_adapter.dart b/lib/services/connectivity_adapter.dart index c628f2f7..1a3835ee 100644 --- a/lib/services/connectivity_adapter.dart +++ b/lib/services/connectivity_adapter.dart @@ -2,17 +2,17 @@ import 'dart:async'; import 'dart:io'; import 'package:dio/dio.dart'; -import 'package:fl_query/fl_query.dart'; import 'package:flutter/widgets.dart'; -class FlQueryInternetConnectionCheckerAdapter extends ConnectivityAdapter - with WidgetsBindingObserver { +class ConnectionCheckerService with WidgetsBindingObserver { final _connectionStreamController = StreamController.broadcast(); final Dio dio; - FlQueryInternetConnectionCheckerAdapter() - : dio = Dio(), - super() { + static final _instance = ConnectionCheckerService._(); + + static ConnectionCheckerService get instance => _instance; + + ConnectionCheckerService._() : dio = Dio() { Timer? timer; onConnectivityChanged.listen((connected) { @@ -100,15 +100,16 @@ class FlQueryInternetConnectionCheckerAdapter extends ConnectivityAdapter await isVpnActive(); // when VPN is active that means we are connected } - @override + bool isConnectedSync = false; + Future get isConnected async { final connected = await _isConnected(); + isConnectedSync = connected; if (connected != isConnectedSync /*previous value*/) { _connectionStreamController.add(connected); } return connected; } - @override Stream get onConnectivityChanged => _connectionStreamController.stream; } diff --git a/lib/services/mutations/album.dart b/lib/services/mutations/album.dart deleted file mode 100644 index 144b6a8f..00000000 --- a/lib/services/mutations/album.dart +++ /dev/null @@ -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 toggleFavorite( - WidgetRef ref, - String albumId, { - List? refreshQueries, - List? refreshInfiniteQueries, - MutationOnDataFn? onData, - }) { - return useSpotifyMutation( - "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, - ); - } -} diff --git a/lib/services/mutations/mutations.dart b/lib/services/mutations/mutations.dart deleted file mode 100644 index 28670486..00000000 --- a/lib/services/mutations/mutations.dart +++ /dev/null @@ -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._(); diff --git a/lib/services/mutations/playlist.dart b/lib/services/mutations/playlist.dart deleted file mode 100644 index f480c565..00000000 --- a/lib/services/mutations/playlist.dart +++ /dev/null @@ -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 toggleFavorite( - WidgetRef ref, - String playlistId, { - List? refreshQueries, - List? refreshInfiniteQueries, - ValueChanged? onData, - }) { - return useSpotifyMutation( - "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 removeTrackOf( - WidgetRef ref, - String playlistId, - ) { - return useSpotifyMutation( - "remove-track-from-playlist/$playlistId", - (trackId, spotify) async { - await spotify.playlists.removeTracks([trackId], playlistId); - return true; - }, - ref: ref, - refreshQueries: ["playlist-tracks/$playlistId"], - ); - } - - Mutation create( - WidgetRef ref, { - List? trackIds, - ValueChanged? onError, - ValueChanged? onData, - }) { - final me = useQueries.user.me(ref); - return useSpotifyMutation( - "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 update( - WidgetRef ref, { - String? playlistId, - ValueChanged? onError, - ValueChanged? onData, - }) { - return useSpotifyMutation( - "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); - }, - ); - } -} diff --git a/lib/services/mutations/track.dart b/lib/services/mutations/track.dart deleted file mode 100644 index f8208b5e..00000000 --- a/lib/services/mutations/track.dart +++ /dev/null @@ -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 toggleFavorite( - WidgetRef ref, - String trackId, { - MutationOnMutationFn? onMutate, - MutationOnDataFn? onData, - MutationOnErrorFn? onError, - }) { - return useSpotifyMutation( - '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, - ); - } -} diff --git a/lib/services/queries/album.dart b/lib/services/queries/album.dart deleted file mode 100644 index 0cc10256..00000000 --- a/lib/services/queries/album.dart +++ /dev/null @@ -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, dynamic, int> ofMine(WidgetRef ref) { - return useSpotifyInfiniteQuery, 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, - 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() ?? - []; - }, - nextPage: (lastPage, lastPageData) { - if (lastPageData.length < 20) { - return null; - } - return lastPage + 1; - }, - ); - - InfiniteQuery, dynamic, int> tracksOf( - WidgetRef ref, - AlbumSimple album, - ) { - final spotify = ref.watch(spotifyProvider); - - return useInfiniteQueryJob( - job: tracksOfJob(album.id!), - args: (spotify: spotify, album: album), - ); - } - - Query isSavedForMe( - WidgetRef ref, - String album, - ) { - return useSpotifyQuery( - "is-saved-for-me/$album", - (spotify) { - return spotify.me - .containsSavedAlbums([album]).then((value) => value[album]); - }, - ref: ref, - ); - } - - InfiniteQuery, dynamic, int> newReleases(WidgetRef ref) { - final market = ref - .watch(userPreferencesProvider.select((s) => s.recommendationMarket)); - - return useSpotifyInfiniteQuery, 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; - }, - ); - } -} diff --git a/lib/services/queries/artist.dart b/lib/services/queries/artist.dart deleted file mode 100644 index 1b939c82..00000000 --- a/lib/services/queries/artist.dart +++ /dev/null @@ -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 get( - WidgetRef ref, - String artist, - ) { - return useSpotifyQuery( - "artist-profile/$artist", - (spotify) => spotify.artists.get(artist), - ref: ref, - ); - } - - InfiniteQuery, dynamic, String> followedByMe( - WidgetRef ref) { - return useSpotifyInfiniteQuery, 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, dynamic> followedByMeAll(WidgetRef ref) { - return useSpotifyQuery( - "user-following-artists-all", - (spotify) async { - CursorPage? page = - await spotify.me.following(FollowingType.artist).getPage(50); - - final following = []; - - 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 doIFollow( - WidgetRef ref, - String artist, - ) { - return useSpotifyQuery( - "user-follows-artists-query/$artist", - (spotify) async { - final result = await spotify.me.checkFollowing( - FollowingType.artist, - [artist], - ); - return result[artist]; - }, - ref: ref, - ); - } - - Query, dynamic> topTracksOf( - WidgetRef ref, - String artist, - ) { - final preferences = ref.watch(userPreferencesProvider); - return useSpotifyQuery, dynamic>( - "artist-top-track-query/$artist", - (spotify) { - return spotify.artists - .topTracks(artist, preferences.recommendationMarket); - }, - ref: ref, - ); - } - - InfiniteQuery, dynamic, int> albumsOf( - WidgetRef ref, - String artist, - ) { - return useSpotifyInfiniteQuery, 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, dynamic> relatedArtistsOf( - WidgetRef ref, - String artist, - ) { - return useSpotifyQuery, dynamic>( - "artist-related-artist-query/$artist", - (spotify) { - return spotify.artists.relatedArtists(artist); - }, - ref: ref, - ); - } - - Query wikipediaSummary(ArtistSimple artist) { - return useQuery( - "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; - }, - ); - } -} diff --git a/lib/services/queries/category.dart b/lib/services/queries/category.dart deleted file mode 100644 index d520b909..00000000 --- a/lib/services/queries/category.dart +++ /dev/null @@ -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, dynamic> listAll( - WidgetRef ref, - Market recommendationMarket, - ) { - ref.watch(userPreferencesProvider.select((s) => s.locale)); - final locale = useContext().l10n.localeName; - final query = useSpotifyQuery, 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, dynamic, int> list( - WidgetRef ref, - Market recommendationMarket, - ) { - ref.watch(userPreferencesProvider.select((s) => s.locale)); - final locale = useContext().l10n.localeName; - return useSpotifyInfiniteQuery, 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, 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, dynamic, int>( - "category-playlists/$category", - (pageParam, spotify) async { - final playlists = await Pages( - 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, dynamic> genreSeeds(WidgetRef ref) { - final customSpotify = ref.watch(customSpotifyEndpointProvider); - final query = useQuery, dynamic>( - "genre-seeds", - customSpotify.listGenreSeeds, - ); - - useEffect(() { - return ref.listenManual( - customSpotifyEndpointProvider, - (previous, next) { - if (previous != next) { - query.refresh(); - } - }, - ).close; - }, [query]); - - return query; - } -} diff --git a/lib/services/queries/lyrics.dart b/lib/services/queries/lyrics.dart deleted file mode 100644 index 618f960f..00000000 --- a/lib/services/queries/lyrics.dart +++ /dev/null @@ -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 static( - Track? track, - String geniusAccessToken, - ) { - return useQuery( - "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 synced( - Track? track, - ) { - return useQuery( - "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 spotifySynced(WidgetRef ref, Track? track) { - return useSpotifyQuery( - "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( - 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()), - toJson: (data) => data.toJson(), - ), - ref: ref, - ); - } -} diff --git a/lib/services/queries/playlist.dart b/lib/services/queries/playlist.dart deleted file mode 100644 index 59daf7f3..00000000 --- a/lib/services/queries/playlist.dart +++ /dev/null @@ -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 recommendationAttributeToMap(RecommendationAttribute attr) => { - "min": attr.min, - "target": attr.target, - "max": attr.max, - }; - -({Map min, Map target, Map max}) - recommendationParametersToMap(RecommendationParameters params) { - final maxMap = { - 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 = { - 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 = { - 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 doesUserFollow( - WidgetRef ref, - String playlistId, - String userId, - ) { - return useSpotifyQuery( - "playlist-is-followed/$playlistId/$userId", - (spotify) async { - final result = - await spotify.playlists.followedByUsers(playlistId, [userId]); - return result[userId] ?? false; - }, - ref: ref, - ); - } - - InfiniteQuery, dynamic, int> ofMine(WidgetRef ref) { - return useSpotifyInfiniteQuery, 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, dynamic> ofMineAll(WidgetRef ref) { - return useSpotifyQuery, dynamic>( - "current-user-all-playlists", - (spotify) async { - var page = await spotify.playlists.me.getPage(50); - final playlists = []; - - 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> likedTracks(SpotifyApi spotify) async { - final tracks = await spotify.tracks.me.saved.all(); - - return tracks.map((e) => e.track!).toList(); - } - - Query, dynamic> likedTracksQuery(WidgetRef ref) { - final query = useCallback((spotify) => likedTracks(spotify), []); - final context = useContext(); - - return useSpotifyQuery, dynamic>( - "user-liked-tracks", - query, - jsonConfig: JsonConfig( - toJson: (tracks) => { - 'tracks': tracks.map((e) => e.toJson()).toList(), - }, - fromJson: (json) => (json['tracks'] as List) - .map( - (e) => Track.fromJson((e as Map).castKeyDeep()), - ) - .toList(), - ), - refreshConfig: RefreshConfig.withDefaults( - context, - // will never make it stale - staleDuration: const Duration(days: 60), - ), - ref: ref, - ); - } - - Query byId(WidgetRef ref, String id) { - return useSpotifyQuery( - "playlist/$id", - (spotify) async { - return await spotify.playlists.get(id); - }, - ref: ref, - ); - } - - Future> tracksOf( - int pageParam, - SpotifyApi spotify, - String playlistId, - ) async { - try { - final playlists = await spotify.playlists - .getTracksByPlaylistId(playlistId) - .getPage(20, pageParam * 20); - return playlists.items?.toList() ?? []; - } catch (e, stack) { - Catcher2.reportCheckedError(e, stack); - rethrow; - } - } - - int? tracksOfQueryNextPage(int lastPage, List lastPageData) { - if (lastPageData.length < 20) { - return null; - } - return lastPage + 1; - } - - InfiniteQuery, dynamic, int> tracksOfQuery( - WidgetRef ref, - String playlistId, - ) { - return useSpotifyInfiniteQuery, dynamic, int>( - "playlist-tracks/$playlistId", - (page, spotify) => tracksOf(page, spotify, playlistId), - initialPage: 0, - nextPage: tracksOfQueryNextPage, - ref: ref, - ); - } - - InfiniteQuery, dynamic, int> featured( - WidgetRef ref, - ) { - return useSpotifyInfiniteQuery, 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, dynamic> generate( - WidgetRef ref, { - ({List tracks, List artists, List 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, 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; - } -} diff --git a/lib/services/queries/queries.dart b/lib/services/queries/queries.dart deleted file mode 100644 index 30c23268..00000000 --- a/lib/services/queries/queries.dart +++ /dev/null @@ -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._(); diff --git a/lib/services/queries/search.dart b/lib/services/queries/search.dart deleted file mode 100644 index 3c6ee064..00000000 --- a/lib/services/queries/search.dart +++ /dev/null @@ -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, 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, dynamic, int> query( - WidgetRef ref, - String queryStr, - SearchType searchType, - ) { - final spotify = ref.watch(spotifyProvider); - final query = useInfiniteQueryJob, 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; - } -} diff --git a/lib/services/queries/tracks.dart b/lib/services/queries/tracks.dart deleted file mode 100644 index 52bab984..00000000 --- a/lib/services/queries/tracks.dart +++ /dev/null @@ -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(WidgetRef ref, String id) { - return useSpotifyQuery( - "track/$id", - (spotify) => spotify.tracks.get(id), - ref: ref, - ); - } -} diff --git a/lib/services/queries/user.dart b/lib/services/queries/user.dart deleted file mode 100644 index 82af600f..00000000 --- a/lib/services/queries/user.dart +++ /dev/null @@ -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 me(WidgetRef ref) { - final context = useContext(); - - return useSpotifyQuery( - "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 friendActivity(WidgetRef ref) { - final customSpotify = ref.read(customSpotifyEndpointProvider); - return useSpotifyQuery( - "friend-activity", - (spotify) { - return customSpotify.getFriendActivity(); - }, - ref: ref, - ); - } -} diff --git a/lib/services/queries/views.dart b/lib/services/queries/views.dart deleted file mode 100644 index 4864ffe1..00000000 --- a/lib/services/queries/views.dart +++ /dev/null @@ -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?, 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?, 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; - } -} diff --git a/pubspec.lock b/pubspec.lock index 4485b118..bbf4faeb 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -675,30 +675,6 @@ packages: url: "https://pub.dev" source: hosted 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: dependency: "direct main" description: @@ -1319,14 +1295,6 @@ packages: url: "https://pub.dev" source: hosted 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: dependency: transitive description: @@ -1495,14 +1463,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.4" - mutex: - dependency: transitive - description: - name: mutex - sha256: "03116a4e46282a671b46c12de649d72c0ed18188ffe12a8d0fc63e83f4ad88f4" - url: "https://pub.dev" - source: hosted - version: "3.0.1" nested: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index e055c9d7..ef8401bc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -32,9 +32,6 @@ dependencies: duration: ^3.0.12 envied: ^0.3.0 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 flutter: sdk: flutter