refactor: query and mutation jobs as separate abstract class

This commit is contained in:
Kingkor Roy Tirtho 2022-12-08 12:32:27 +06:00
parent 7c25e1cc8a
commit 42d284f8d8
33 changed files with 437 additions and 363 deletions

View File

@ -1,6 +1,8 @@
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:spotify/spotify.dart' hide Search; import 'package:spotify/spotify.dart' hide Search;
import 'package:spotube/utils/platform.dart';
import 'package:spotube/components/shared/spotube_page_route.dart';
import 'package:spotube/pages/album/album.dart'; import 'package:spotube/pages/album/album.dart';
import 'package:spotube/pages/artist/artist.dart'; import 'package:spotube/pages/artist/artist.dart';
import 'package:spotube/pages/genre/genres.dart'; import 'package:spotube/pages/genre/genres.dart';
@ -12,9 +14,7 @@ import 'package:spotube/pages/player/player.dart';
import 'package:spotube/pages/playlist/playlist.dart'; import 'package:spotube/pages/playlist/playlist.dart';
import 'package:spotube/pages/root/root_app.dart'; import 'package:spotube/pages/root/root_app.dart';
import 'package:spotube/pages/search/search.dart'; import 'package:spotube/pages/search/search.dart';
import 'package:spotube/components/shared/spotube_page_route.dart';
import 'package:spotube/pages/settings/settings.dart'; import 'package:spotube/pages/settings/settings.dart';
import 'package:spotube/utils/platform.dart';
import 'package:spotube/pages/mobile_login/mobile_login.dart'; import 'package:spotube/pages/mobile_login/mobile_login.dart';
final rootNavigatorKey = GlobalKey<NavigatorState>(); final rootNavigatorKey = GlobalKey<NavigatorState>();

View File

@ -9,7 +9,7 @@ import 'package:spotube/components/shared/shimmers/shimmer_playbutton_card.dart'
import 'package:spotube/components/shared/waypoint.dart'; import 'package:spotube/components/shared/waypoint.dart';
import 'package:spotube/models/logger.dart'; import 'package:spotube/models/logger.dart';
import 'package:spotube/provider/spotify_provider.dart'; import 'package:spotube/provider/spotify_provider.dart';
import 'package:spotube/provider/SpotifyRequests.dart'; import 'package:spotube/services/queries/queries.dart';
class ArtistAlbumList extends HookConsumerWidget { class ArtistAlbumList extends HookConsumerWidget {
final String artistId; final String artistId;
@ -24,7 +24,7 @@ class ArtistAlbumList extends HookConsumerWidget {
Widget build(BuildContext context, ref) { Widget build(BuildContext context, ref) {
final scrollController = useScrollController(); final scrollController = useScrollController();
final albumsQuery = useInfiniteQuery( final albumsQuery = useInfiniteQuery(
job: artistAlbumsQueryJob(artistId), job: Queries.artist.albumsOf(artistId),
externalData: ref.watch(spotifyProvider), externalData: ref.watch(spotifyProvider),
); );

View File

@ -10,7 +10,7 @@ import 'package:spotube/components/shared/waypoint.dart';
import 'package:spotube/components/playlist/playlist_card.dart'; import 'package:spotube/components/playlist/playlist_card.dart';
import 'package:spotube/models/logger.dart'; import 'package:spotube/models/logger.dart';
import 'package:spotube/provider/spotify_provider.dart'; import 'package:spotube/provider/spotify_provider.dart';
import 'package:spotube/provider/SpotifyRequests.dart'; import 'package:spotube/services/queries/queries.dart';
class CategoryCard extends HookConsumerWidget { class CategoryCard extends HookConsumerWidget {
final Category category; final Category category;
@ -28,7 +28,7 @@ class CategoryCard extends HookConsumerWidget {
final scrollController = useScrollController(); final scrollController = useScrollController();
final spotify = ref.watch(spotifyProvider); final spotify = ref.watch(spotifyProvider);
final playlistQuery = useInfiniteQuery( final playlistQuery = useInfiniteQuery(
job: categoryPlaylistsQueryJob(category.id!), job: Queries.category.playlistsOf(category.id!),
externalData: spotify, externalData: spotify,
); );
final hasNextPage = playlistQuery.pages.isEmpty final hasNextPage = playlistQuery.pages.isEmpty

View File

@ -7,7 +7,8 @@ import 'package:spotube/components/shared/shimmers/shimmer_playbutton_card.dart'
import 'package:spotube/components/shared/fallbacks/anonymous_fallback.dart'; import 'package:spotube/components/shared/fallbacks/anonymous_fallback.dart';
import 'package:spotube/provider/auth_provider.dart'; import 'package:spotube/provider/auth_provider.dart';
import 'package:spotube/provider/spotify_provider.dart'; import 'package:spotube/provider/spotify_provider.dart';
import 'package:spotube/provider/SpotifyRequests.dart'; import 'package:spotube/services/queries/queries.dart';
import 'package:spotube/utils/type_conversion_utils.dart'; import 'package:spotube/utils/type_conversion_utils.dart';
class UserAlbums extends HookConsumerWidget { class UserAlbums extends HookConsumerWidget {
@ -20,7 +21,7 @@ class UserAlbums extends HookConsumerWidget {
return const AnonymousFallback(); return const AnonymousFallback();
} }
final albumsQuery = useQuery( final albumsQuery = useQuery(
job: currentUserAlbumsQueryJob, job: Queries.album.ofMine,
externalData: ref.watch(spotifyProvider), externalData: ref.watch(spotifyProvider),
); );

View File

@ -9,7 +9,7 @@ import 'package:spotube/components/shared/waypoint.dart';
import 'package:spotube/components/artist/artist_card.dart'; import 'package:spotube/components/artist/artist_card.dart';
import 'package:spotube/provider/auth_provider.dart'; import 'package:spotube/provider/auth_provider.dart';
import 'package:spotube/provider/spotify_provider.dart'; import 'package:spotube/provider/spotify_provider.dart';
import 'package:spotube/provider/SpotifyRequests.dart'; import 'package:spotube/services/queries/queries.dart';
class UserArtists extends HookConsumerWidget { class UserArtists extends HookConsumerWidget {
const UserArtists({Key? key}) : super(key: key); const UserArtists({Key? key}) : super(key: key);
@ -21,7 +21,7 @@ class UserArtists extends HookConsumerWidget {
return const AnonymousFallback(); return const AnonymousFallback();
} }
final artistQuery = useInfiniteQuery( final artistQuery = useInfiniteQuery(
job: currentUserFollowingArtistsQueryJob, job: Queries.artist.followedByMe,
externalData: ref.watch(spotifyProvider), externalData: ref.watch(spotifyProvider),
); );

View File

@ -9,7 +9,7 @@ import 'package:spotube/components/playlist/playlist_card.dart';
import 'package:spotube/components/playlist/playlist_create_dialog.dart'; import 'package:spotube/components/playlist/playlist_create_dialog.dart';
import 'package:spotube/provider/auth_provider.dart'; import 'package:spotube/provider/auth_provider.dart';
import 'package:spotube/provider/spotify_provider.dart'; import 'package:spotube/provider/spotify_provider.dart';
import 'package:spotube/provider/SpotifyRequests.dart'; import 'package:spotube/services/queries/queries.dart';
class UserPlaylists extends HookConsumerWidget { class UserPlaylists extends HookConsumerWidget {
const UserPlaylists({Key? key}) : super(key: key); const UserPlaylists({Key? key}) : super(key: key);
@ -22,7 +22,7 @@ class UserPlaylists extends HookConsumerWidget {
} }
final playlistsQuery = useQuery( final playlistsQuery = useQuery(
job: currentUserPlaylistsQueryJob, job: Queries.playlist.ofMine,
externalData: ref.watch(spotifyProvider), externalData: ref.watch(spotifyProvider),
); );
Image image = Image(); Image image = Image();

View File

@ -5,7 +5,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:platform_ui/platform_ui.dart'; import 'package:platform_ui/platform_ui.dart';
import 'package:spotube/components/root/sidebar.dart'; import 'package:spotube/components/root/sidebar.dart';
import 'package:spotube/provider/spotify_provider.dart'; import 'package:spotube/provider/spotify_provider.dart';
import 'package:spotube/provider/SpotifyRequests.dart'; import 'package:spotube/services/queries/queries.dart';
class PlaylistCreateDialog extends HookConsumerWidget { class PlaylistCreateDialog extends HookConsumerWidget {
const PlaylistCreateDialog({Key? key}) : super(key: key); const PlaylistCreateDialog({Key? key}) : super(key: key);
@ -44,7 +44,7 @@ class PlaylistCreateDialog extends HookConsumerWidget {
) )
.then((_) { .then((_) {
QueryBowl.of(context).refetchQueries([ QueryBowl.of(context).refetchQueries([
currentUserPlaylistsQueryJob.queryKey, Queries.playlist.ofMine.queryKey,
]); ]);
Navigator.pop(context); Navigator.pop(context);
}); });

View File

@ -3,7 +3,7 @@ import 'package:flutter/gestures.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:macos_ui/macos_ui.dart'; import 'package:macos_ui/macos_ui.dart';
import 'package:fluent_ui/fluent_ui.dart' as FluentUI; import 'package:fluent_ui/fluent_ui.dart' as fluent_ui;
import 'package:platform_ui/platform_ui.dart'; import 'package:platform_ui/platform_ui.dart';
import 'package:spotube/components/player/player_actions.dart'; import 'package:spotube/components/player/player_actions.dart';
import 'package:spotube/components/player/player_overlay.dart'; import 'package:spotube/components/player/player_overlay.dart';
@ -17,10 +17,10 @@ import 'package:flutter/material.dart';
import 'package:spotube/provider/user_preferences_provider.dart'; import 'package:spotube/provider/user_preferences_provider.dart';
import 'package:spotube/utils/type_conversion_utils.dart'; import 'package:spotube/utils/type_conversion_utils.dart';
class Player extends HookConsumerWidget { class BottomPlayer extends HookConsumerWidget {
Player({Key? key}) : super(key: key); BottomPlayer({Key? key}) : super(key: key);
final logger = getLogger(Player); final logger = getLogger(BottomPlayer);
@override @override
Widget build(BuildContext context, ref) { Widget build(BuildContext context, ref) {
Playback playback = ref.watch(playbackProvider); Playback playback = ref.watch(playbackProvider);
@ -59,7 +59,7 @@ class Player extends HookConsumerWidget {
? Colors.grey[800] ? Colors.grey[800]
: Colors.blueGrey[50], : Colors.blueGrey[50],
linux: Theme.of(context).backgroundColor, linux: Theme.of(context).backgroundColor,
windows: FluentUI.FluentTheme.maybeOf(context)?.micaBackgroundColor, windows: fluent_ui.FluentTheme.maybeOf(context)?.micaBackgroundColor,
), ),
); );

View File

@ -12,11 +12,12 @@ import 'package:spotube/hooks/use_breakpoints.dart';
import 'package:spotube/provider/auth_provider.dart'; import 'package:spotube/provider/auth_provider.dart';
import 'package:spotube/provider/downloader_provider.dart'; import 'package:spotube/provider/downloader_provider.dart';
import 'package:spotube/provider/spotify_provider.dart'; import 'package:spotube/provider/spotify_provider.dart';
import 'package:spotube/provider/SpotifyRequests.dart';
import 'package:spotube/provider/user_preferences_provider.dart'; import 'package:spotube/provider/user_preferences_provider.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';
import 'package:fluent_ui/fluent_ui.dart' as FluentUI; import 'package:fluent_ui/fluent_ui.dart' as fluent_ui;
final sidebarExtendedStateProvider = StateProvider<bool?>((ref) => null); final sidebarExtendedStateProvider = StateProvider<bool?>((ref) => null);
@ -153,8 +154,8 @@ class Sidebar extends HookConsumerWidget {
], ],
), ),
windowsFooterItems: [ windowsFooterItems: [
FluentUI.PaneItemAction( fluent_ui.PaneItemAction(
icon: const FluentUI.Icon(FluentUI.FluentIcons.settings), icon: const fluent_ui.Icon(fluent_ui.FluentIcons.settings),
onTap: () => goToSettings(context), onTap: () => goToSettings(context),
), ),
], ],
@ -180,7 +181,7 @@ class SidebarFooter extends HookConsumerWidget {
child: HookBuilder( child: HookBuilder(
builder: (context) { builder: (context) {
final me = useQuery( final me = useQuery(
job: currentUserQueryJob, job: Queries.user.me,
externalData: ref.watch(spotifyProvider), externalData: ref.watch(spotifyProvider),
); );
final data = me.data; final data = me.data;

View File

@ -5,7 +5,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:platform_ui/platform_ui.dart'; import 'package:platform_ui/platform_ui.dart';
import 'package:spotify/spotify.dart'; import 'package:spotify/spotify.dart';
import 'package:spotube/provider/spotify_provider.dart'; import 'package:spotube/provider/spotify_provider.dart';
import 'package:spotube/provider/SpotifyRequests.dart'; import 'package:spotube/services/queries/queries.dart';
class PlaylistAddTrackDialog extends HookConsumerWidget { class PlaylistAddTrackDialog extends HookConsumerWidget {
final List<Track> tracks; final List<Track> tracks;
@ -18,11 +18,11 @@ class PlaylistAddTrackDialog extends HookConsumerWidget {
Widget build(BuildContext context, ref) { Widget build(BuildContext context, ref) {
final spotify = ref.watch(spotifyProvider); final spotify = ref.watch(spotifyProvider);
final userPlaylists = useQuery( final userPlaylists = useQuery(
job: currentUserPlaylistsQueryJob, job: Queries.playlist.ofMine,
externalData: spotify, externalData: spotify,
); );
final me = useQuery( final me = useQuery(
job: currentUserQueryJob, job: Queries.user.me,
externalData: spotify, externalData: spotify,
); );
final filteredPlaylists = userPlaylists.data?.where( final filteredPlaylists = userPlaylists.data?.where(

View File

@ -8,7 +8,9 @@ import 'package:spotify/spotify.dart';
import 'package:spotube/hooks/use_palette_color.dart'; import 'package:spotube/hooks/use_palette_color.dart';
import 'package:spotube/provider/auth_provider.dart'; import 'package:spotube/provider/auth_provider.dart';
import 'package:spotube/provider/spotify_provider.dart'; import 'package:spotube/provider/spotify_provider.dart';
import 'package:spotube/provider/SpotifyRequests.dart'; import 'package:spotube/services/mutations/mutations.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:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
@ -49,11 +51,11 @@ class HeartButton extends ConsumerWidget {
Tuple3<bool, Mutation<bool, Tuple2<SpotifyApi, bool>>, Query<User, SpotifyApi>> Tuple3<bool, Mutation<bool, Tuple2<SpotifyApi, bool>>, Query<User, SpotifyApi>>
useTrackToggleLike(Track track, WidgetRef ref) { useTrackToggleLike(Track track, WidgetRef ref) {
final me = useQuery( final me =
job: currentUserQueryJob, externalData: ref.watch(spotifyProvider)); useQuery(job: Queries.user.me, externalData: ref.watch(spotifyProvider));
final savedTracks = useQuery( final savedTracks = useQuery(
job: playlistTracksQueryJob("user-liked-tracks"), job: Queries.playlist.tracksOf("user-liked-tracks"),
externalData: ref.watch(spotifyProvider), externalData: ref.watch(spotifyProvider),
); );
@ -63,7 +65,7 @@ Tuple3<bool, Mutation<bool, Tuple2<SpotifyApi, bool>>, Query<User, SpotifyApi>>
final mounted = useIsMounted(); final mounted = useIsMounted();
final toggleTrackLike = useMutation<bool, Tuple2<SpotifyApi, bool>>( final toggleTrackLike = useMutation<bool, Tuple2<SpotifyApi, bool>>(
job: toggleFavoriteTrackMutationJob(track.id!), job: Mutations.track.toggleFavorite(track.id!),
onMutate: (variable) { onMutate: (variable) {
savedTracks.setQueryData( savedTracks.setQueryData(
(oldData) { (oldData) {
@ -117,7 +119,7 @@ class TrackHeartButton extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, ref) { Widget build(BuildContext context, ref) {
final savedTracks = useQuery( final savedTracks = useQuery(
job: playlistTracksQueryJob("user-liked-tracks"), job: Queries.playlist.tracksOf("user-liked-tracks"),
externalData: ref.watch(spotifyProvider), externalData: ref.watch(spotifyProvider),
); );
final toggler = useTrackToggleLike(track, ref); final toggler = useTrackToggleLike(track, ref);
@ -150,22 +152,23 @@ class PlaylistHeartButton extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, ref) { Widget build(BuildContext context, ref) {
final me = useQuery( final me = useQuery(
job: currentUserQueryJob, job: Queries.user.me,
externalData: ref.watch(spotifyProvider), externalData: ref.watch(spotifyProvider),
); );
final job = playlistIsFollowedQueryJob("${playlist.id}:${me.data?.id}"); final job =
Queries.playlist.doesUserFollow("${playlist.id}:${me.data?.id}");
final isLikedQuery = useQuery( final isLikedQuery = useQuery(
job: job, job: job,
externalData: ref.watch(spotifyProvider), externalData: ref.watch(spotifyProvider),
); );
final togglePlaylistLike = useMutation<bool, Tuple2<SpotifyApi, bool>>( final togglePlaylistLike = useMutation<bool, Tuple2<SpotifyApi, bool>>(
job: toggleFavoritePlaylistMutationJob(playlist.id!), job: Mutations.playlist.toggleFavorite(playlist.id!),
onData: (payload, variables, queryContext) { onData: (payload, variables, queryContext) {
isLikedQuery.refetch(); isLikedQuery.refetch();
QueryBowl.of(context) QueryBowl.of(context)
.getQuery(currentUserPlaylistsQueryJob.queryKey) .getQuery(Queries.playlist.ofMine.queryKey)
?.refetch(); ?.refetch();
}, },
); );
@ -182,8 +185,9 @@ class PlaylistHeartButton extends HookConsumerWidget {
titleImage, titleImage,
).dominantColor; ).dominantColor;
if (me.isLoading || !me.hasData) if (me.isLoading || !me.hasData) {
return const PlatformCircularProgressIndicator(); return const PlatformCircularProgressIndicator();
}
return HeartButton( return HeartButton(
isLiked: isLikedQuery.data ?? false, isLiked: isLikedQuery.data ?? false,
@ -217,28 +221,29 @@ class AlbumHeartButton extends HookConsumerWidget {
Widget build(BuildContext context, ref) { Widget build(BuildContext context, ref) {
final spotify = ref.watch(spotifyProvider); final spotify = ref.watch(spotifyProvider);
final me = useQuery( final me = useQuery(
job: currentUserQueryJob, job: Queries.user.me,
externalData: spotify, externalData: spotify,
); );
final albumIsSaved = useQuery( final albumIsSaved = useQuery(
job: albumIsSavedForCurrentUserQueryJob(album.id!), job: Queries.album.isSavedForMe(album.id!),
externalData: spotify, externalData: spotify,
); );
final isLiked = albumIsSaved.data ?? false; final isLiked = albumIsSaved.data ?? false;
final toggleAlbumLike = useMutation<bool, Tuple2<SpotifyApi, bool>>( final toggleAlbumLike = useMutation<bool, Tuple2<SpotifyApi, bool>>(
job: toggleFavoriteAlbumMutationJob(album.id!), job: Mutations.album.toggleFavorite(album.id!),
onData: (payload, variables, queryContext) { onData: (payload, variables, queryContext) {
albumIsSaved.refetch(); albumIsSaved.refetch();
QueryBowl.of(context) QueryBowl.of(context)
.getQuery(currentUserAlbumsQueryJob.queryKey) .getQuery(Queries.album.ofMine.queryKey)
?.refetch(); ?.refetch();
}, },
); );
if (me.isLoading || !me.hasData) if (me.isLoading || !me.hasData) {
return const PlatformCircularProgressIndicator(); return const PlatformCircularProgressIndicator();
}
return HeartButton( return HeartButton(
isLiked: isLiked, isLiked: isLiked,

View File

@ -16,7 +16,9 @@ import 'package:spotube/models/logger.dart';
import 'package:spotube/provider/auth_provider.dart'; import 'package:spotube/provider/auth_provider.dart';
import 'package:spotube/provider/playback_provider.dart'; import 'package:spotube/provider/playback_provider.dart';
import 'package:spotube/provider/spotify_provider.dart'; import 'package:spotube/provider/spotify_provider.dart';
import 'package:spotube/provider/SpotifyRequests.dart'; import 'package:spotube/services/mutations/mutations.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:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
@ -63,12 +65,12 @@ class TrackTile extends HookConsumerWidget {
final spotify = ref.watch(spotifyProvider); final spotify = ref.watch(spotifyProvider);
final removingTrack = useState<String?>(null); final removingTrack = useState<String?>(null);
final removeTrack = useMutation<bool, Tuple2<SpotifyApi, String>>( final removeTrack = useMutation<bool, Tuple2<SpotifyApi, String>>(
job: removeTrackFromPlaylistMutationJob(playlistId ?? ""), job: Mutations.playlist.removeTrackOf(playlistId ?? ""),
onData: (payload, variables, ctx) { onData: (payload, variables, ctx) {
if (playlistId == null || !payload) return; if (playlistId == null || !payload) return;
QueryBowl.of(context) QueryBowl.of(context)
.getQuery( .getQuery(
playlistTracksQueryJob(playlistId!).queryKey, Queries.playlist.tracksOf(playlistId!).queryKey,
) )
?.refetch(); ?.refetch();
}, },

View File

@ -8,12 +8,12 @@ import 'package:spotube/components/shared/heart_button.dart';
import 'package:spotube/components/shared/track_table/track_collection_view.dart'; import 'package:spotube/components/shared/track_table/track_collection_view.dart';
import 'package:spotube/components/shared/track_table/tracks_table_view.dart'; import 'package:spotube/components/shared/track_table/tracks_table_view.dart';
import 'package:spotube/hooks/use_breakpoints.dart'; import 'package:spotube/hooks/use_breakpoints.dart';
import 'package:spotube/services/queries/queries.dart';
import 'package:spotube/utils/service_utils.dart'; import 'package:spotube/utils/service_utils.dart';
import 'package:spotube/utils/type_conversion_utils.dart'; import 'package:spotube/utils/type_conversion_utils.dart';
import 'package:spotube/models/current_playlist.dart'; import 'package:spotube/models/current_playlist.dart';
import 'package:spotube/provider/playback_provider.dart'; import 'package:spotube/provider/playback_provider.dart';
import 'package:spotube/provider/spotify_provider.dart'; import 'package:spotube/provider/spotify_provider.dart';
import 'package:spotube/provider/SpotifyRequests.dart';
class AlbumPage extends HookConsumerWidget { class AlbumPage extends HookConsumerWidget {
final AlbumSimple album; final AlbumSimple album;
@ -56,7 +56,7 @@ class AlbumPage extends HookConsumerWidget {
final SpotifyApi spotify = ref.watch(spotifyProvider); final SpotifyApi spotify = ref.watch(spotifyProvider);
final tracksSnapshot = useQuery( final tracksSnapshot = useQuery(
job: albumTracksQueryJob(album.id!), job: Queries.album.tracksOf(album.id!),
externalData: spotify, externalData: spotify,
); );

View File

@ -18,7 +18,8 @@ import 'package:spotube/models/current_playlist.dart';
import 'package:spotube/models/logger.dart'; import 'package:spotube/models/logger.dart';
import 'package:spotube/provider/playback_provider.dart'; import 'package:spotube/provider/playback_provider.dart';
import 'package:spotube/provider/spotify_provider.dart'; import 'package:spotube/provider/spotify_provider.dart';
import 'package:spotube/provider/SpotifyRequests.dart'; import 'package:spotube/services/queries/queries.dart';
import 'package:spotube/utils/primitive_utils.dart'; import 'package:spotube/utils/primitive_utils.dart';
import 'package:spotube/utils/type_conversion_utils.dart'; import 'package:spotube/utils/type_conversion_utils.dart';
@ -60,7 +61,7 @@ class ArtistPage extends HookConsumerWidget {
body: HookBuilder( body: HookBuilder(
builder: (context) { builder: (context) {
final artistsQuery = useQuery( final artistsQuery = useQuery(
job: artistProfileQueryJob(artistId), job: Queries.artist.get(artistId),
externalData: spotify, externalData: spotify,
); );
@ -130,8 +131,7 @@ class ArtistPage extends HookConsumerWidget {
HookBuilder( HookBuilder(
builder: (context) { builder: (context) {
final isFollowingQuery = useQuery( final isFollowingQuery = useQuery(
job: currentUserFollowsArtistQueryJob( job: Queries.artist.doIFollow(artistId),
artistId),
externalData: spotify, externalData: spotify,
); );
@ -165,8 +165,8 @@ class ArtistPage extends HookConsumerWidget {
); );
} finally { } finally {
QueryBowl.of(context).refetchQueries([ QueryBowl.of(context).refetchQueries([
currentUserFollowsArtistQueryJob( Queries.artist
artistId) .doIFollow(artistId)
.queryKey, .queryKey,
]); ]);
} }
@ -211,7 +211,7 @@ class ArtistPage extends HookConsumerWidget {
HookBuilder( HookBuilder(
builder: (context) { builder: (context) {
final topTracksQuery = useQuery( final topTracksQuery = useQuery(
job: artistTopTracksQueryJob(artistId), job: Queries.artist.topTracksOf(artistId),
externalData: spotify, externalData: spotify,
); );
@ -311,7 +311,7 @@ class ArtistPage extends HookConsumerWidget {
HookBuilder( HookBuilder(
builder: (context) { builder: (context) {
final relatedArtists = useQuery( final relatedArtists = useQuery(
job: artistRelatedArtistsQueryJob(artistId), job: Queries.artist.relatedArtistsOf(artistId),
externalData: spotify, externalData: spotify,
); );

View File

@ -9,8 +9,9 @@ import 'package:spotube/components/shared/shimmers/shimmer_categories.dart';
import 'package:spotube/components/shared/page_window_title_bar.dart'; import 'package:spotube/components/shared/page_window_title_bar.dart';
import 'package:spotube/components/shared/waypoint.dart'; import 'package:spotube/components/shared/waypoint.dart';
import 'package:spotube/provider/spotify_provider.dart'; import 'package:spotube/provider/spotify_provider.dart';
import 'package:spotube/provider/SpotifyRequests.dart';
import 'package:spotube/provider/user_preferences_provider.dart'; import 'package:spotube/provider/user_preferences_provider.dart';
import 'package:spotube/services/queries/queries.dart';
import 'package:spotube/utils/platform.dart'; import 'package:spotube/utils/platform.dart';
class GenrePage extends HookConsumerWidget { class GenrePage extends HookConsumerWidget {
@ -24,7 +25,7 @@ class GenrePage extends HookConsumerWidget {
userPreferencesProvider.select((s) => s.recommendationMarket), userPreferencesProvider.select((s) => s.recommendationMarket),
); );
final categoriesQuery = useInfiniteQuery( final categoriesQuery = useInfiniteQuery(
job: categoriesQueryJob, job: Queries.category.list,
externalData: { externalData: {
"spotify": spotify, "spotify": spotify,
"recommendationMarket": recommendationMarket, "recommendationMarket": recommendationMarket,

View File

@ -7,8 +7,9 @@ import 'package:spotify/spotify.dart';
import 'package:spotube/components/shared/shimmers/shimmer_lyrics.dart'; import 'package:spotube/components/shared/shimmers/shimmer_lyrics.dart';
import 'package:spotube/hooks/use_breakpoints.dart'; import 'package:spotube/hooks/use_breakpoints.dart';
import 'package:spotube/provider/playback_provider.dart'; import 'package:spotube/provider/playback_provider.dart';
import 'package:spotube/provider/SpotifyRequests.dart';
import 'package:spotube/provider/user_preferences_provider.dart'; import 'package:spotube/provider/user_preferences_provider.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:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
@ -23,7 +24,7 @@ class GeniusLyrics extends HookConsumerWidget {
Widget build(BuildContext context, ref) { Widget build(BuildContext context, ref) {
Playback playback = ref.watch(playbackProvider); Playback playback = ref.watch(playbackProvider);
final geniusLyricsQuery = useQuery( final geniusLyricsQuery = useQuery(
job: geniusLyricsQueryJob, job: Queries.lyrics.static,
externalData: Tuple2( externalData: Tuple2(
playback.track, playback.track,
ref.watch(userPreferencesProvider).geniusAccessToken, ref.watch(userPreferencesProvider).geniusAccessToken,

View File

@ -13,7 +13,8 @@ import 'package:spotube/hooks/use_breakpoints.dart';
import 'package:spotube/hooks/use_synced_lyrics.dart'; import 'package:spotube/hooks/use_synced_lyrics.dart';
import 'package:spotube/provider/playback_provider.dart'; import 'package:spotube/provider/playback_provider.dart';
import 'package:scroll_to_index/scroll_to_index.dart'; import 'package:scroll_to_index/scroll_to_index.dart';
import 'package:spotube/provider/SpotifyRequests.dart'; import 'package:spotube/services/queries/queries.dart';
import 'package:spotube/utils/type_conversion_utils.dart'; import 'package:spotube/utils/type_conversion_utils.dart';
final lyricDelayState = StateProvider<Duration>( final lyricDelayState = StateProvider<Duration>(
@ -33,7 +34,7 @@ class SyncedLyrics extends HookConsumerWidget {
Widget build(BuildContext context, ref) { Widget build(BuildContext context, ref) {
Playback playback = ref.watch(playbackProvider); Playback playback = ref.watch(playbackProvider);
final timedLyricsQuery = useQuery( final timedLyricsQuery = useQuery(
job: rentanadviserLyricsQueryJob, job: Queries.lyrics.synced,
externalData: playback.track, externalData: playback.track,
); );
final lyricDelay = ref.watch(lyricDelayState); final lyricDelay = ref.watch(lyricDelayState);

View File

@ -12,7 +12,8 @@ import 'package:spotube/provider/playback_provider.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:spotify/spotify.dart'; import 'package:spotify/spotify.dart';
import 'package:spotube/provider/spotify_provider.dart'; import 'package:spotube/provider/spotify_provider.dart';
import 'package:spotube/provider/SpotifyRequests.dart'; import 'package:spotube/services/queries/queries.dart';
import 'package:spotube/utils/service_utils.dart'; import 'package:spotube/utils/service_utils.dart';
import 'package:spotube/utils/type_conversion_utils.dart'; import 'package:spotube/utils/type_conversion_utils.dart';
@ -61,10 +62,9 @@ class PlaylistView extends HookConsumerWidget {
final breakpoint = useBreakpoints(); final breakpoint = useBreakpoints();
final meSnapshot = final meSnapshot = useQuery(job: Queries.user.me, externalData: spotify);
useQuery(job: currentUserQueryJob, externalData: spotify);
final tracksSnapshot = useQuery( final tracksSnapshot = useQuery(
job: playlistTracksQueryJob(playlist.id!), job: Queries.playlist.tracksOf(playlist.id!),
externalData: spotify, externalData: spotify,
); );

View File

@ -75,7 +75,7 @@ class RootApp extends HookConsumerWidget {
bottomNavigationBar: Column( bottomNavigationBar: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Player(), BottomPlayer(),
SpotubeNavigationBar( SpotubeNavigationBar(
selectedIndex: index.value, selectedIndex: index.value,
onSelectedIndexChanged: (selectedIndex) { onSelectedIndexChanged: (selectedIndex) {

View File

@ -18,7 +18,8 @@ import 'package:spotube/models/current_playlist.dart';
import 'package:spotube/provider/auth_provider.dart'; import 'package:spotube/provider/auth_provider.dart';
import 'package:spotube/provider/playback_provider.dart'; import 'package:spotube/provider/playback_provider.dart';
import 'package:spotube/provider/spotify_provider.dart'; import 'package:spotube/provider/spotify_provider.dart';
import 'package:spotube/provider/SpotifyRequests.dart'; import 'package:spotube/services/queries/queries.dart';
import 'package:spotube/utils/platform.dart'; import 'package:spotube/utils/platform.dart';
import 'package:spotube/utils/primitive_utils.dart'; import 'package:spotube/utils/primitive_utils.dart';
import 'package:spotube/utils/type_conversion_utils.dart'; import 'package:spotube/utils/type_conversion_utils.dart';
@ -47,16 +48,16 @@ class SearchPage extends HookConsumerWidget {
); );
final searchTrack = useInfiniteQuery( final searchTrack = useInfiniteQuery(
job: searchQueryJob(SearchType.track.key), job: Queries.search.get(SearchType.track.key),
externalData: getVariables()); externalData: getVariables());
final searchAlbum = useInfiniteQuery( final searchAlbum = useInfiniteQuery(
job: searchQueryJob(SearchType.album.key), job: Queries.search.get(SearchType.album.key),
externalData: getVariables()); externalData: getVariables());
final searchPlaylist = useInfiniteQuery( final searchPlaylist = useInfiniteQuery(
job: searchQueryJob(SearchType.playlist.key), job: Queries.search.get(SearchType.playlist.key),
externalData: getVariables()); externalData: getVariables());
final searchArtist = useInfiniteQuery( final searchArtist = useInfiniteQuery(
job: searchQueryJob(SearchType.artist.key), job: Queries.search.get(SearchType.artist.key),
externalData: getVariables()); externalData: getVariables());
void onSearch() { void onSearch() {

View File

@ -1,291 +0,0 @@
import 'package:fl_query/fl_query.dart';
import 'package:spotube/models/lyrics.dart';
import 'package:spotube/models/spotube_track.dart';
import 'package:spotify/spotify.dart';
import 'package:collection/collection.dart';
import 'package:spotube/utils/service_utils.dart';
import 'package:spotube/utils/type_conversion_utils.dart';
import 'package:tuple/tuple.dart';
final categoriesQueryJob =
InfiniteQueryJob<Page<Category>, Map<String, dynamic>, int>(
queryKey: "categories-query",
initialParam: 0,
getNextPageParam: (lastPage, lastParam) => lastPage.nextOffset,
getPreviousPageParam: (lastPage, lastParam) => lastPage.nextOffset - 16,
refetchOnExternalDataChange: true,
task: (queryKey, pageParam, data) async {
final SpotifyApi spotify = data["spotify"] as SpotifyApi;
final String recommendationMarket = data["recommendationMarket"];
final categories = await spotify.categories
.list(country: recommendationMarket)
.getPage(15, pageParam);
return categories;
},
);
final categoryPlaylistsQueryJob =
InfiniteQueryJob.withVariableKey<Page<PlaylistSimple>, SpotifyApi, int>(
preQueryKey: "category-playlists",
initialParam: 0,
getNextPageParam: (lastPage, lastParam) => lastPage.nextOffset,
getPreviousPageParam: (lastPage, lastParam) => lastPage.nextOffset - 6,
task: (queryKey, pageKey, spotify) {
final id = getVariable(queryKey);
return (id != "user-featured-playlists"
? spotify.playlists.getByCategoryId(id)
: spotify.playlists.featured)
.getPage(5, pageKey);
},
);
final currentUserPlaylistsQueryJob =
QueryJob<Iterable<PlaylistSimple>, SpotifyApi>(
queryKey: "current-user-playlists",
task: (_, spotify) {
return spotify.playlists.me.all();
},
);
final currentUserAlbumsQueryJob = QueryJob<Iterable<AlbumSimple>, SpotifyApi>(
queryKey: "current-user-albums",
task: (_, spotify) {
return spotify.me.savedAlbums().all();
},
);
final currentUserFollowingArtistsQueryJob =
InfiniteQueryJob<CursorPage<Artist>, SpotifyApi, String>(
queryKey: "user-following-artists",
initialParam: "",
getNextPageParam: (lastPage, lastParam) => lastPage.after,
getPreviousPageParam: (lastPage, lastParam) =>
lastPage.metadata.previous ?? "",
task: (queryKey, pageKey, spotify) {
return spotify.me.following(FollowingType.artist).getPage(15, pageKey);
},
);
final artistProfileQueryJob = QueryJob.withVariableKey<Artist, SpotifyApi>(
preQueryKey: "artist-profile",
task: (queryKey, externalData) =>
externalData.artists.get(getVariable(queryKey)),
);
final artistTopTracksQueryJob =
QueryJob.withVariableKey<Iterable<Track>, SpotifyApi>(
preQueryKey: "artist-top-track-query",
task: (queryKey, spotify) {
return spotify.artists.getTopTracks(getVariable(queryKey), "US");
},
);
final artistAlbumsQueryJob =
InfiniteQueryJob.withVariableKey<Page<Album>, SpotifyApi, int>(
preQueryKey: "artist-albums",
initialParam: 0,
getNextPageParam: (lastPage, lastParam) => lastPage.nextOffset,
getPreviousPageParam: (lastPage, lastParam) => lastPage.nextOffset - 6,
task: (queryKey, pageKey, spotify) {
final id = getVariable(queryKey);
return spotify.artists.albums(id).getPage(5, pageKey);
},
);
final artistRelatedArtistsQueryJob =
QueryJob.withVariableKey<Iterable<Artist>, SpotifyApi>(
preQueryKey: "artist-related-artist-query",
task: (queryKey, spotify) {
return spotify.artists.getRelatedArtists(getVariable(queryKey));
},
);
final currentUserFollowsArtistQueryJob =
QueryJob.withVariableKey<bool, SpotifyApi>(
preQueryKey: "user-follows-artists-query",
task: (artistId, spotify) async {
final result = await spotify.me.isFollowing(
FollowingType.artist,
[getVariable(artistId)],
);
return result.first;
},
);
final playlistTracksQueryJob =
QueryJob.withVariableKey<List<Track>, SpotifyApi>(
preQueryKey: "playlist-tracks",
task: (queryKey, spotify) {
final id = getVariable(queryKey);
return id != "user-liked-tracks"
? spotify.playlists.getTracksByPlaylistId(id).all().then(
(value) => value.toList(),
)
: spotify.tracks.me.saved.all().then(
(tracks) => tracks.map((e) => e.track!).toList(),
);
},
);
final albumTracksQueryJob =
QueryJob.withVariableKey<List<TrackSimple>, SpotifyApi>(
preQueryKey: "album-tracks",
task: (queryKey, spotify) {
final id = getVariable(queryKey);
return spotify.albums.getTracks(id).all().then((value) => value.toList());
},
);
final currentUserQueryJob = QueryJob<User, SpotifyApi>(
queryKey: "current-user",
refetchOnExternalDataChange: true,
task: (_, spotify) async {
final me = await spotify.me.get();
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;
},
);
final playlistIsFollowedQueryJob = QueryJob.withVariableKey<bool, SpotifyApi>(
preQueryKey: "playlist-is-followed",
task: (queryKey, spotify) {
final idMap = getVariable(queryKey).split(":");
return spotify.playlists.followedBy(idMap.first, [idMap.last]).then(
(value) => value.first,
);
},
);
final albumIsSavedForCurrentUserQueryJob =
QueryJob.withVariableKey<bool, SpotifyApi>(task: (queryKey, spotify) {
return spotify.me
.isSavedAlbums([getVariable(queryKey)]).then((value) => value.first);
});
final searchQueryJob = InfiniteQueryJob.withVariableKey<List<Page>,
Tuple2<String, SpotifyApi>, int>(
preQueryKey: "search-query",
initialParam: 0,
enabled: false,
getNextPageParam: (lastPage, lastParam) =>
(lastPage.first.items?.length ?? 0) < 10 ? null : lastParam + 10,
getPreviousPageParam: (lastPage, lastParam) => lastParam - 10,
task: (queryKey, pageParam, variables) {
final queryString = variables.item1;
final spotify = variables.item2;
if (queryString.isEmpty) return [];
final searchType = getVariable(queryKey);
return spotify.search.get(
queryString,
types: [SearchType(searchType)],
).getPage(10, pageParam);
},
);
final geniusLyricsQueryJob = QueryJob<String, Tuple2<Track?, String>>(
queryKey: "genius-lyrics-query",
task: (_, externalData) async {
final currentTrack = externalData.item1;
final geniusAccessToken = externalData.item2;
if (currentTrack == null) {
return "“Give this player a track to play”\n- S'Challa";
}
final lyrics = await ServiceUtils.getLyrics(
currentTrack.name!,
currentTrack.artists?.map((s) => s.name).whereNotNull().toList() ?? [],
apiKey: geniusAccessToken,
optimizeQuery: true,
);
if (lyrics == null) throw Exception("Unable find lyrics");
return lyrics;
},
);
final rentanadviserLyricsQueryJob = QueryJob<SubtitleSimple, SpotubeTrack?>(
queryKey: "synced-lyrics",
task: (_, currentTrack) async {
if (currentTrack == null) throw "No track currently";
final timedLyrics = await ServiceUtils.getTimedLyrics(currentTrack);
if (timedLyrics == null) throw Exception("Unable to find lyrics");
return timedLyrics;
},
);
final toggleFavoriteTrackMutationJob =
MutationJob.withVariableKey<bool, Tuple2<SpotifyApi, bool>>(
preMutationKey: "toggle-track-like",
task: (queryKey, externalData) async {
final trackId = getVariable(queryKey);
final spotify = externalData.item1;
final isLiked = externalData.item2;
if (isLiked) {
await spotify.tracks.me.removeOne(trackId);
} else {
await spotify.tracks.me.saveOne(trackId);
}
return !isLiked;
},
);
final toggleFavoritePlaylistMutationJob =
MutationJob.withVariableKey<bool, Tuple2<SpotifyApi, bool>>(
preMutationKey: "toggle-playlist-like",
task: (queryKey, externalData) async {
final playlistId = getVariable(queryKey);
final spotify = externalData.item1;
final isLiked = externalData.item2;
if (isLiked) {
await spotify.playlists.unfollowPlaylist(playlistId);
} else {
await spotify.playlists.followPlaylist(playlistId);
}
return !isLiked;
},
);
final toggleFavoriteAlbumMutationJob =
MutationJob.withVariableKey<bool, Tuple2<SpotifyApi, bool>>(
preMutationKey: "toggle-album-like",
task: (queryKey, externalData) async {
final albumId = getVariable(queryKey);
final spotify = externalData.item1;
final isLiked = externalData.item2;
if (isLiked) {
await spotify.me.removeAlbums([albumId]);
} else {
await spotify.me.saveAlbums([albumId]);
}
return !isLiked;
},
);
final removeTrackFromPlaylistMutationJob =
MutationJob.withVariableKey<bool, Tuple2<SpotifyApi, String>>(
preMutationKey: "remove-track-from-playlist",
task: (queryKey, externalData) async {
final spotify = externalData.item1;
final playlistId = getVariable(queryKey);
final trackId = externalData.item2;
await spotify.playlists.removeTracks([trackId], playlistId);
return true;
},
);

View File

@ -0,0 +1,22 @@
import 'package:fl_query/fl_query.dart';
import 'package:spotify/spotify.dart';
import 'package:tuple/tuple.dart';
class AlbumMutations {
final toggleFavorite =
MutationJob.withVariableKey<bool, Tuple2<SpotifyApi, bool>>(
preMutationKey: "toggle-album-like",
task: (queryKey, externalData) async {
final albumId = getVariable(queryKey);
final spotify = externalData.item1;
final isLiked = externalData.item2;
if (isLiked) {
await spotify.me.removeAlbums([albumId]);
} else {
await spotify.me.saveAlbums([albumId]);
}
return !isLiked;
},
);
}

View File

@ -0,0 +1,9 @@
import 'package:spotube/services/mutations/album.dart';
import 'package:spotube/services/mutations/playlist.dart';
import 'package:spotube/services/mutations/track.dart';
abstract class Mutations {
static final playlist = PlaylistMutations();
static final album = AlbumMutations();
static final track = TrackMutations();
}

View File

@ -0,0 +1,35 @@
import 'package:fl_query/fl_query.dart';
import 'package:spotify/spotify.dart';
import 'package:tuple/tuple.dart';
class PlaylistMutations {
final toggleFavorite =
MutationJob.withVariableKey<bool, Tuple2<SpotifyApi, bool>>(
preMutationKey: "toggle-playlist-like",
task: (queryKey, externalData) async {
final playlistId = getVariable(queryKey);
final spotify = externalData.item1;
final isLiked = externalData.item2;
if (isLiked) {
await spotify.playlists.unfollowPlaylist(playlistId);
} else {
await spotify.playlists.followPlaylist(playlistId);
}
return !isLiked;
},
);
final removeTrackOf =
MutationJob.withVariableKey<bool, Tuple2<SpotifyApi, String>>(
preMutationKey: "remove-track-from-playlist",
task: (queryKey, externalData) async {
final spotify = externalData.item1;
final playlistId = getVariable(queryKey);
final trackId = externalData.item2;
await spotify.playlists.removeTracks([trackId], playlistId);
return true;
},
);
}

View File

@ -0,0 +1,22 @@
import 'package:fl_query/fl_query.dart';
import 'package:spotify/spotify.dart';
import 'package:tuple/tuple.dart';
class TrackMutations {
final toggleFavorite =
MutationJob.withVariableKey<bool, Tuple2<SpotifyApi, bool>>(
preMutationKey: "toggle-track-like",
task: (queryKey, externalData) async {
final trackId = getVariable(queryKey);
final spotify = externalData.item1;
final isLiked = externalData.item2;
if (isLiked) {
await spotify.tracks.me.removeOne(trackId);
} else {
await spotify.tracks.me.saveOne(trackId);
}
return !isLiked;
},
);
}

View File

@ -0,0 +1,25 @@
import 'package:fl_query/fl_query.dart';
import 'package:spotify/spotify.dart';
class AlbumQueries {
final ofMine = QueryJob<Iterable<AlbumSimple>, SpotifyApi>(
queryKey: "current-user-albums",
task: (_, spotify) {
return spotify.me.savedAlbums().all();
},
);
final tracksOf = QueryJob.withVariableKey<List<TrackSimple>, SpotifyApi>(
preQueryKey: "album-tracks",
task: (queryKey, spotify) {
final id = getVariable(queryKey);
return spotify.albums.getTracks(id).all().then((value) => value.toList());
},
);
final isSavedForMe =
QueryJob.withVariableKey<bool, SpotifyApi>(task: (queryKey, spotify) {
return spotify.me
.isSavedAlbums([getVariable(queryKey)]).then((value) => value.first);
});
}

View File

@ -0,0 +1,59 @@
import 'package:fl_query/fl_query.dart';
import 'package:spotify/spotify.dart';
class ArtistQueries {
final get = QueryJob.withVariableKey<Artist, SpotifyApi>(
preQueryKey: "artist-profile",
task: (queryKey, externalData) =>
externalData.artists.get(getVariable(queryKey)),
);
final followedByMe = InfiniteQueryJob<CursorPage<Artist>, SpotifyApi, String>(
queryKey: "user-following-artists",
initialParam: "",
getNextPageParam: (lastPage, lastParam) => lastPage.after,
getPreviousPageParam: (lastPage, lastParam) =>
lastPage.metadata.previous ?? "",
task: (queryKey, pageKey, spotify) {
return spotify.me.following(FollowingType.artist).getPage(15, pageKey);
},
);
final doIFollow = QueryJob.withVariableKey<bool, SpotifyApi>(
preQueryKey: "user-follows-artists-query",
task: (artistId, spotify) async {
final result = await spotify.me.isFollowing(
FollowingType.artist,
[getVariable(artistId)],
);
return result.first;
},
);
final topTracksOf = QueryJob.withVariableKey<Iterable<Track>, SpotifyApi>(
preQueryKey: "artist-top-track-query",
task: (queryKey, spotify) {
return spotify.artists.getTopTracks(getVariable(queryKey), "US");
},
);
final albumsOf =
InfiniteQueryJob.withVariableKey<Page<Album>, SpotifyApi, int>(
preQueryKey: "artist-albums",
initialParam: 0,
getNextPageParam: (lastPage, lastParam) => lastPage.nextOffset,
getPreviousPageParam: (lastPage, lastParam) => lastPage.nextOffset - 6,
task: (queryKey, pageKey, spotify) {
final id = getVariable(queryKey);
return spotify.artists.albums(id).getPage(5, pageKey);
},
);
final relatedArtistsOf =
QueryJob.withVariableKey<Iterable<Artist>, SpotifyApi>(
preQueryKey: "artist-related-artist-query",
task: (queryKey, spotify) {
return spotify.artists.getRelatedArtists(getVariable(queryKey));
},
);
}

View File

@ -0,0 +1,36 @@
import 'package:fl_query/fl_query.dart';
import 'package:spotify/spotify.dart';
class CategoryQueries {
final list = InfiniteQueryJob<Page<Category>, Map<String, dynamic>, int>(
queryKey: "categories-query",
initialParam: 0,
getNextPageParam: (lastPage, lastParam) => lastPage.nextOffset,
getPreviousPageParam: (lastPage, lastParam) => lastPage.nextOffset - 16,
refetchOnExternalDataChange: true,
task: (queryKey, pageParam, data) async {
final SpotifyApi spotify = data["spotify"] as SpotifyApi;
final String recommendationMarket = data["recommendationMarket"];
final categories = await spotify.categories
.list(country: recommendationMarket)
.getPage(15, pageParam);
return categories;
},
);
final playlistsOf =
InfiniteQueryJob.withVariableKey<Page<PlaylistSimple>, SpotifyApi, int>(
preQueryKey: "category-playlists",
initialParam: 0,
getNextPageParam: (lastPage, lastParam) => lastPage.nextOffset,
getPreviousPageParam: (lastPage, lastParam) => lastPage.nextOffset - 6,
task: (queryKey, pageKey, spotify) {
final id = getVariable(queryKey);
return (id != "user-featured-playlists"
? spotify.playlists.getByCategoryId(id)
: spotify.playlists.featured)
.getPage(5, pageKey);
},
);
}

View File

@ -0,0 +1,41 @@
import 'package:collection/collection.dart';
import 'package:fl_query/fl_query.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/models/lyrics.dart';
import 'package:spotube/models/spotube_track.dart';
import 'package:spotube/utils/service_utils.dart';
import 'package:tuple/tuple.dart';
class LyricsQueries {
final static = QueryJob<String, Tuple2<Track?, String>>(
queryKey: "genius-lyrics-query",
task: (_, externalData) async {
final currentTrack = externalData.item1;
final geniusAccessToken = externalData.item2;
if (currentTrack == null) {
return "“Give this player a track to play”\n- S'Challa";
}
final lyrics = await ServiceUtils.getLyrics(
currentTrack.name!,
currentTrack.artists?.map((s) => s.name).whereNotNull().toList() ?? [],
apiKey: geniusAccessToken,
optimizeQuery: true,
);
if (lyrics == null) throw Exception("Unable find lyrics");
return lyrics;
},
);
final synced = QueryJob<SubtitleSimple, SpotubeTrack?>(
queryKey: "synced-lyrics",
task: (_, currentTrack) async {
if (currentTrack == null) throw "No track currently";
final timedLyrics = await ServiceUtils.getTimedLyrics(currentTrack);
if (timedLyrics == null) throw Exception("Unable to find lyrics");
return timedLyrics;
},
);
}

View File

@ -0,0 +1,36 @@
import 'package:fl_query/fl_query.dart';
import 'package:spotify/spotify.dart';
class PlaylistQueries {
final doesUserFollow = QueryJob.withVariableKey<bool, SpotifyApi>(
preQueryKey: "playlist-is-followed",
task: (queryKey, spotify) {
final idMap = getVariable(queryKey).split(":");
return spotify.playlists.followedBy(idMap.first, [idMap.last]).then(
(value) => value.first,
);
},
);
final ofMine = QueryJob<Iterable<PlaylistSimple>, SpotifyApi>(
queryKey: "current-user-playlists",
task: (_, spotify) {
return spotify.playlists.me.all();
},
);
final tracksOf = QueryJob.withVariableKey<List<Track>, SpotifyApi>(
preQueryKey: "playlist-tracks",
task: (queryKey, spotify) {
final id = getVariable(queryKey);
return id != "user-liked-tracks"
? spotify.playlists.getTracksByPlaylistId(id).all().then(
(value) => value.toList(),
)
: spotify.tracks.me.saved.all().then(
(tracks) => tracks.map((e) => e.track!).toList(),
);
},
);
}

View File

@ -0,0 +1,17 @@
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/user.dart';
abstract class Queries {
static final album = AlbumQueries();
static final artist = ArtistQueries();
static final category = CategoryQueries();
static final lyrics = LyricsQueries();
static final playlist = PlaylistQueries();
static final search = SearchQueries();
static final user = UserQueries();
}

View File

@ -0,0 +1,25 @@
import 'package:fl_query/fl_query.dart';
import 'package:spotify/spotify.dart';
import 'package:tuple/tuple.dart';
class SearchQueries {
final get = InfiniteQueryJob.withVariableKey<List<Page>,
Tuple2<String, SpotifyApi>, int>(
preQueryKey: "search-query",
initialParam: 0,
enabled: false,
getNextPageParam: (lastPage, lastParam) =>
(lastPage.first.items?.length ?? 0) < 10 ? null : lastParam + 10,
getPreviousPageParam: (lastPage, lastParam) => lastParam - 10,
task: (queryKey, pageParam, variables) {
final queryString = variables.item1;
final spotify = variables.item2;
if (queryString.isEmpty) return [];
final searchType = getVariable(queryKey);
return spotify.search.get(
queryString,
types: [SearchType(searchType)],
).getPage(10, pageParam);
},
);
}

View File

@ -0,0 +1,25 @@
import 'package:fl_query/fl_query.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/utils/type_conversion_utils.dart';
class UserQueries {
final me = QueryJob<User, SpotifyApi>(
queryKey: "current-user",
refetchOnExternalDataChange: true,
task: (_, spotify) async {
final me = await spotify.me.get();
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;
},
);
}