mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 16:05:18 +00:00
Merge pull request #422 from KRTirtho/immutable-fl-query-integration
Immutable fl query integration
This commit is contained in:
commit
bd48ca44ee
@ -3,6 +3,7 @@ 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/pages/home/home.dart';
|
import 'package:spotube/pages/home/home.dart';
|
||||||
|
import 'package:spotube/pages/search/search.dart';
|
||||||
import 'package:spotube/pages/settings/blacklist.dart';
|
import 'package:spotube/pages/settings/blacklist.dart';
|
||||||
import 'package:spotube/pages/settings/about.dart';
|
import 'package:spotube/pages/settings/about.dart';
|
||||||
import 'package:spotube/utils/platform.dart';
|
import 'package:spotube/utils/platform.dart';
|
||||||
@ -16,7 +17,7 @@ import 'package:spotube/pages/lyrics/lyrics.dart';
|
|||||||
import 'package:spotube/pages/player/player.dart';
|
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/pages/settings/settings.dart';
|
import 'package:spotube/pages/settings/settings.dart';
|
||||||
import 'package:spotube/pages/mobile_login/mobile_login.dart';
|
import 'package:spotube/pages/mobile_login/mobile_login.dart';
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import 'package:fl_query/fl_query.dart';
|
import 'package:fl_query_hooks/fl_query_hooks.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
@ -7,10 +7,8 @@ import 'package:spotube/components/shared/playbutton_card.dart';
|
|||||||
import 'package:spotube/hooks/use_breakpoint_value.dart';
|
import 'package:spotube/hooks/use_breakpoint_value.dart';
|
||||||
import 'package:spotube/provider/playlist_queue_provider.dart';
|
import 'package:spotube/provider/playlist_queue_provider.dart';
|
||||||
import 'package:spotube/provider/spotify_provider.dart';
|
import 'package:spotube/provider/spotify_provider.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:uuid/uuid.dart';
|
|
||||||
|
|
||||||
enum AlbumType {
|
enum AlbumType {
|
||||||
album,
|
album,
|
||||||
@ -48,15 +46,18 @@ class AlbumCard extends HookConsumerWidget {
|
|||||||
final playing = useStream(PlaylistQueueNotifier.playing).data ??
|
final playing = useStream(PlaylistQueueNotifier.playing).data ??
|
||||||
PlaylistQueueNotifier.isPlaying;
|
PlaylistQueueNotifier.isPlaying;
|
||||||
final playlistNotifier = ref.watch(PlaylistQueueNotifier.notifier);
|
final playlistNotifier = ref.watch(PlaylistQueueNotifier.notifier);
|
||||||
final queryBowl = QueryBowl.of(context);
|
final queryClient = useQueryClient();
|
||||||
final query = queryBowl.getQuery<List<TrackSimple>, SpotifyApi>(
|
final query = queryClient
|
||||||
Queries.album.tracksOf(album.id!).queryKey);
|
.getQuery<List<TrackSimple>, dynamic>("album-tracks/${album.id}");
|
||||||
final tracks = useState(query?.data ?? album.tracks ?? <Track>[]);
|
final tracks = useState(query?.data ?? album.tracks ?? <Track>[]);
|
||||||
bool isPlaylistPlaying = playlistNotifier.isPlayingPlaylist(tracks.value);
|
bool isPlaylistPlaying = playlistNotifier.isPlayingPlaylist(tracks.value);
|
||||||
final int marginH =
|
final int marginH =
|
||||||
useBreakpointValue(sm: 10, md: 15, lg: 20, xl: 20, xxl: 20);
|
useBreakpointValue(sm: 10, md: 15, lg: 20, xl: 20, xxl: 20);
|
||||||
|
|
||||||
final updating = useState(false);
|
final updating = useState(false);
|
||||||
|
final spotify = ref.watch(spotifyProvider);
|
||||||
|
|
||||||
|
final scaffold = ScaffoldMessenger.of(context);
|
||||||
|
|
||||||
return PlaybuttonCard(
|
return PlaybuttonCard(
|
||||||
imageUrl: TypeConversionUtils.image_X_UrlString(
|
imageUrl: TypeConversionUtils.image_X_UrlString(
|
||||||
@ -99,10 +100,14 @@ class AlbumCard extends HookConsumerWidget {
|
|||||||
updating.value = true;
|
updating.value = true;
|
||||||
try {
|
try {
|
||||||
final fetchedTracks =
|
final fetchedTracks =
|
||||||
await queryBowl.fetchQuery<List<TrackSimple>, SpotifyApi>(
|
await queryClient.fetchQuery<List<TrackSimple>, SpotifyApi>(
|
||||||
Queries.album.tracksOf(album.id!),
|
"album-tracks/${album.id}",
|
||||||
externalData: ref.read(spotifyProvider),
|
() {
|
||||||
key: ValueKey(const Uuid().v4()),
|
return spotify.albums
|
||||||
|
.getTracks(album.id!)
|
||||||
|
.all()
|
||||||
|
.then((value) => value.toList());
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (fetchedTracks == null || fetchedTracks.isEmpty) return;
|
if (fetchedTracks == null || fetchedTracks.isEmpty) return;
|
||||||
@ -113,7 +118,7 @@ class AlbumCard extends HookConsumerWidget {
|
|||||||
.toList(),
|
.toList(),
|
||||||
);
|
);
|
||||||
tracks.value = fetchedTracks;
|
tracks.value = fetchedTracks;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
scaffold.showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text("Added ${album.tracks?.length} tracks to queue"),
|
content: Text("Added ${album.tracks?.length} tracks to queue"),
|
||||||
),
|
),
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import 'package:fl_query_hooks/fl_query_hooks.dart';
|
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart' hide Page;
|
import 'package:flutter/material.dart' hide Page;
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
@ -8,7 +7,6 @@ import 'package:spotube/components/album/album_card.dart';
|
|||||||
import 'package:spotube/components/shared/shimmers/shimmer_playbutton_card.dart';
|
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/services/queries/queries.dart';
|
import 'package:spotube/services/queries/queries.dart';
|
||||||
|
|
||||||
class ArtistAlbumList extends HookConsumerWidget {
|
class ArtistAlbumList extends HookConsumerWidget {
|
||||||
@ -23,20 +21,17 @@ class ArtistAlbumList extends HookConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
final scrollController = useScrollController();
|
final scrollController = useScrollController();
|
||||||
final albumsQuery = useInfiniteQuery(
|
final albumsQuery = useQueries.artist.albumsOf(ref, artistId);
|
||||||
job: Queries.artist.albumsOf(artistId),
|
|
||||||
externalData: ref.watch(spotifyProvider),
|
|
||||||
);
|
|
||||||
|
|
||||||
final albums = useMemoized(() {
|
final albums = useMemoized(() {
|
||||||
return albumsQuery.pages
|
return albumsQuery.pages
|
||||||
.expand<Album>((page) => page?.items ?? const Iterable.empty())
|
.expand<Album>((page) => page.items ?? const Iterable.empty())
|
||||||
.toList();
|
.toList();
|
||||||
}, [albumsQuery.pages]);
|
}, [albumsQuery.pages]);
|
||||||
|
|
||||||
final hasNextPage = albumsQuery.pages.isEmpty
|
final hasNextPage = albumsQuery.pages.isEmpty
|
||||||
? false
|
? false
|
||||||
: (albumsQuery.pages.last?.items?.length ?? 0) == 5;
|
: (albumsQuery.pages.last.items?.length ?? 0) == 5;
|
||||||
|
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: 300,
|
height: 300,
|
||||||
@ -52,9 +47,7 @@ class ArtistAlbumList extends HookConsumerWidget {
|
|||||||
controller: scrollController,
|
controller: scrollController,
|
||||||
child: Waypoint(
|
child: Waypoint(
|
||||||
controller: scrollController,
|
controller: scrollController,
|
||||||
onTouchEdge: () {
|
onTouchEdge: albumsQuery.fetchNext,
|
||||||
albumsQuery.fetchNextPage();
|
|
||||||
},
|
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
itemCount: albums.length,
|
itemCount: albums.length,
|
||||||
controller: scrollController,
|
controller: scrollController,
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import 'package:fl_query_hooks/fl_query_hooks.dart';
|
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart' hide Page;
|
import 'package:flutter/material.dart' hide Page;
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
@ -9,7 +8,6 @@ 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/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/services/queries/queries.dart';
|
import 'package:spotube/services/queries/queries.dart';
|
||||||
|
|
||||||
class CategoryCard extends HookConsumerWidget {
|
class CategoryCard extends HookConsumerWidget {
|
||||||
@ -24,18 +22,14 @@ class CategoryCard extends HookConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
final scrollController = useScrollController();
|
final scrollController = useScrollController();
|
||||||
final spotify = ref.watch(spotifyProvider);
|
final playlistQuery = useQueries.category.playlistsOf(
|
||||||
final playlistQuery = useInfiniteQuery(
|
ref,
|
||||||
job: Queries.category.playlistsOf(category.id!),
|
category.id!,
|
||||||
externalData: spotify,
|
|
||||||
);
|
);
|
||||||
final hasNextPage = playlistQuery.pages.isEmpty
|
|
||||||
? false
|
|
||||||
: (playlistQuery.pages.last?.items?.length ?? 0) == 5;
|
|
||||||
|
|
||||||
final playlists = playlistQuery.pages
|
final playlists = playlistQuery.pages
|
||||||
.expand(
|
.expand(
|
||||||
(page) => page?.items ?? const Iterable.empty(),
|
(page) => page.items ?? const Iterable.empty(),
|
||||||
)
|
)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
@ -49,7 +43,7 @@ class CategoryCard extends HookConsumerWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
playlistQuery.hasError
|
playlistQuery.hasPageError && !playlistQuery.hasPageData
|
||||||
? PlatformText(
|
? PlatformText(
|
||||||
"Something Went Wrong\n${playlistQuery.errors.first}")
|
"Something Went Wrong\n${playlistQuery.errors.first}")
|
||||||
: SizedBox(
|
: SizedBox(
|
||||||
@ -67,7 +61,7 @@ class CategoryCard extends HookConsumerWidget {
|
|||||||
child: Waypoint(
|
child: Waypoint(
|
||||||
controller: scrollController,
|
controller: scrollController,
|
||||||
onTouchEdge: () {
|
onTouchEdge: () {
|
||||||
playlistQuery.fetchNextPage();
|
playlistQuery.fetchNext();
|
||||||
},
|
},
|
||||||
child: ListView(
|
child: ListView(
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
@ -76,7 +70,7 @@ class CategoryCard extends HookConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
...playlists
|
...playlists
|
||||||
.map((playlist) => PlaylistCard(playlist)),
|
.map((playlist) => PlaylistCard(playlist)),
|
||||||
if (hasNextPage)
|
if (playlistQuery.hasNextPage)
|
||||||
const ShimmerPlaybuttonCard(count: 1),
|
const ShimmerPlaybuttonCard(count: 1),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import 'package:fl_query_hooks/fl_query_hooks.dart';
|
|
||||||
import 'package:flutter/material.dart' hide Image;
|
import 'package:flutter/material.dart' hide Image;
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
@ -12,7 +11,6 @@ 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/hooks/use_breakpoint_value.dart';
|
import 'package:spotube/hooks/use_breakpoint_value.dart';
|
||||||
import 'package:spotube/provider/authentication_provider.dart';
|
import 'package:spotube/provider/authentication_provider.dart';
|
||||||
import 'package:spotube/provider/spotify_provider.dart';
|
|
||||||
import 'package:spotube/services/queries/queries.dart';
|
import 'package:spotube/services/queries/queries.dart';
|
||||||
|
|
||||||
import 'package:spotube/utils/type_conversion_utils.dart';
|
import 'package:spotube/utils/type_conversion_utils.dart';
|
||||||
@ -24,10 +22,7 @@ class UserAlbums extends HookConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
final auth = ref.watch(AuthenticationNotifier.provider);
|
final auth = ref.watch(AuthenticationNotifier.provider);
|
||||||
final albumsQuery = useQuery(
|
final albumsQuery = useQueries.album.ofMine(ref);
|
||||||
job: Queries.album.ofMine,
|
|
||||||
externalData: ref.watch(spotifyProvider),
|
|
||||||
);
|
|
||||||
|
|
||||||
final spacing = useBreakpointValue<double>(
|
final spacing = useBreakpointValue<double>(
|
||||||
sm: 0,
|
sm: 0,
|
||||||
@ -64,7 +59,7 @@ class UserAlbums extends HookConsumerWidget {
|
|||||||
|
|
||||||
return RefreshIndicator(
|
return RefreshIndicator(
|
||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
await albumsQuery.refetch();
|
await albumsQuery.refresh();
|
||||||
},
|
},
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
physics: const AlwaysScrollableScrollPhysics(),
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import 'package:fl_query_hooks/fl_query_hooks.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
@ -11,7 +10,6 @@ import 'package:spotube/components/shared/fallbacks/anonymous_fallback.dart';
|
|||||||
import 'package:spotube/components/shared/waypoint.dart';
|
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/authentication_provider.dart';
|
import 'package:spotube/provider/authentication_provider.dart';
|
||||||
import 'package:spotube/provider/spotify_provider.dart';
|
|
||||||
import 'package:spotube/services/queries/queries.dart';
|
import 'package:spotube/services/queries/queries.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
@ -22,20 +20,17 @@ class UserArtists extends HookConsumerWidget {
|
|||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
final auth = ref.watch(AuthenticationNotifier.provider);
|
final auth = ref.watch(AuthenticationNotifier.provider);
|
||||||
|
|
||||||
final artistQuery = useInfiniteQuery(
|
final artistQuery = useQueries.artist.followedByMe(ref);
|
||||||
job: Queries.artist.followedByMe,
|
|
||||||
externalData: ref.watch(spotifyProvider),
|
|
||||||
);
|
|
||||||
|
|
||||||
final hasNextPage = artistQuery.pages.isEmpty
|
final hasNextPage = artistQuery.pages.isEmpty
|
||||||
? false
|
? false
|
||||||
: (artistQuery.pages.last?.items?.length ?? 0) == 15;
|
: (artistQuery.pages.last.items?.length ?? 0) == 15;
|
||||||
|
|
||||||
final searchText = useState('');
|
final searchText = useState('');
|
||||||
|
|
||||||
final filteredArtists = useMemoized(() {
|
final filteredArtists = useMemoized(() {
|
||||||
final artists = artistQuery.pages
|
final artists = artistQuery.pages
|
||||||
.expand<Artist>((page) => page?.items ?? const Iterable.empty());
|
.expand<Artist>((page) => page.items ?? const Iterable.empty());
|
||||||
|
|
||||||
if (searchText.value.isEmpty) {
|
if (searchText.value.isEmpty) {
|
||||||
return artists.toList();
|
return artists.toList();
|
||||||
@ -85,7 +80,7 @@ class UserArtists extends HookConsumerWidget {
|
|||||||
)
|
)
|
||||||
: RefreshIndicator(
|
: RefreshIndicator(
|
||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
await artistQuery.refetchPages();
|
await artistQuery.refreshAll();
|
||||||
},
|
},
|
||||||
child: GridView.builder(
|
child: GridView.builder(
|
||||||
itemCount: filteredArtists.length,
|
itemCount: filteredArtists.length,
|
||||||
@ -104,7 +99,7 @@ class UserArtists extends HookConsumerWidget {
|
|||||||
controller: useScrollController(),
|
controller: useScrollController(),
|
||||||
isGrid: true,
|
isGrid: true,
|
||||||
onTouchEdge: () {
|
onTouchEdge: () {
|
||||||
artistQuery.fetchNextPage();
|
artistQuery.fetchNext();
|
||||||
},
|
},
|
||||||
child: ArtistCard(filteredArtists[index]),
|
child: ArtistCard(filteredArtists[index]),
|
||||||
);
|
);
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import 'package:fl_query_hooks/fl_query_hooks.dart';
|
|
||||||
import 'package:flutter/material.dart' hide Image;
|
import 'package:flutter/material.dart' hide Image;
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:fuzzywuzzy/fuzzywuzzy.dart';
|
import 'package:fuzzywuzzy/fuzzywuzzy.dart';
|
||||||
@ -15,7 +14,6 @@ import 'package:spotube/components/playlist/playlist_card.dart';
|
|||||||
import 'package:spotube/hooks/use_breakpoint_value.dart';
|
import 'package:spotube/hooks/use_breakpoint_value.dart';
|
||||||
import 'package:spotube/hooks/use_breakpoints.dart';
|
import 'package:spotube/hooks/use_breakpoints.dart';
|
||||||
import 'package:spotube/provider/authentication_provider.dart';
|
import 'package:spotube/provider/authentication_provider.dart';
|
||||||
import 'package:spotube/provider/spotify_provider.dart';
|
|
||||||
import 'package:spotube/services/queries/queries.dart';
|
import 'package:spotube/services/queries/queries.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
@ -35,22 +33,23 @@ class UserPlaylists extends HookConsumerWidget {
|
|||||||
: PlaybuttonCardViewType.square;
|
: PlaybuttonCardViewType.square;
|
||||||
final auth = ref.watch(AuthenticationNotifier.provider);
|
final auth = ref.watch(AuthenticationNotifier.provider);
|
||||||
|
|
||||||
final playlistsQuery = useQuery(
|
final playlistsQuery = useQueries.playlist.ofMine(ref);
|
||||||
job: Queries.playlist.ofMine,
|
|
||||||
externalData: ref.watch(spotifyProvider),
|
|
||||||
);
|
|
||||||
|
|
||||||
Image image = Image();
|
final likedTracksPlaylist = useMemoized(
|
||||||
image.height = 300;
|
() => PlaylistSimple()
|
||||||
image.width = 300;
|
..name = "Liked Tracks"
|
||||||
PlaylistSimple likedTracksPlaylist = PlaylistSimple();
|
..type = "playlist"
|
||||||
likedTracksPlaylist.name = "Liked Tracks";
|
..collaborative = false
|
||||||
likedTracksPlaylist.type = "playlist";
|
..public = false
|
||||||
likedTracksPlaylist.collaborative = false;
|
..id = "user-liked-tracks"
|
||||||
likedTracksPlaylist.public = false;
|
..images = [
|
||||||
likedTracksPlaylist.id = "user-liked-tracks";
|
Image()
|
||||||
image.url = "https://t.scdn.co/images/3099b3803ad9496896c43f22fe9be8c4.png";
|
..height = 300
|
||||||
likedTracksPlaylist.images = [image];
|
..width = 300
|
||||||
|
..url =
|
||||||
|
"https://t.scdn.co/images/3099b3803ad9496896c43f22fe9be8c4.png"
|
||||||
|
],
|
||||||
|
[]);
|
||||||
|
|
||||||
final playlists = useMemoized(
|
final playlists = useMemoized(
|
||||||
() {
|
() {
|
||||||
@ -90,7 +89,7 @@ class UserPlaylists extends HookConsumerWidget {
|
|||||||
.toList(),
|
.toList(),
|
||||||
];
|
];
|
||||||
return RefreshIndicator(
|
return RefreshIndicator(
|
||||||
onRefresh: () => playlistsQuery.refetch(),
|
onRefresh: playlistsQuery.refresh,
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
physics: const AlwaysScrollableScrollPhysics(),
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
child: Material(
|
child: Material(
|
||||||
|
@ -10,7 +10,6 @@ import 'package:spotube/provider/spotify_provider.dart';
|
|||||||
import 'package:spotube/services/queries/queries.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:uuid/uuid.dart';
|
|
||||||
|
|
||||||
class PlaylistCard extends HookConsumerWidget {
|
class PlaylistCard extends HookConsumerWidget {
|
||||||
final PlaylistSimple playlist;
|
final PlaylistSimple playlist;
|
||||||
@ -26,9 +25,9 @@ class PlaylistCard extends HookConsumerWidget {
|
|||||||
final playlistNotifier = ref.watch(PlaylistQueueNotifier.notifier);
|
final playlistNotifier = ref.watch(PlaylistQueueNotifier.notifier);
|
||||||
final playing = useStream(PlaylistQueueNotifier.playing).data ??
|
final playing = useStream(PlaylistQueueNotifier.playing).data ??
|
||||||
PlaylistQueueNotifier.isPlaying;
|
PlaylistQueueNotifier.isPlaying;
|
||||||
final queryBowl = QueryBowl.of(context);
|
final queryBowl = QueryClient.of(context);
|
||||||
final query = queryBowl.getQuery<List<Track>, SpotifyApi>(
|
final query = queryBowl.getQuery<List<Track>, dynamic>(
|
||||||
Queries.playlist.tracksOf(playlist.id!).queryKey,
|
"playlist-tracks/${playlist.id}",
|
||||||
);
|
);
|
||||||
final tracks = useState(query?.data ?? []);
|
final tracks = useState(query?.data ?? []);
|
||||||
bool isPlaylistPlaying = playlistNotifier.isPlayingPlaylist(tracks.value);
|
bool isPlaylistPlaying = playlistNotifier.isPlayingPlaylist(tracks.value);
|
||||||
@ -37,6 +36,8 @@ class PlaylistCard extends HookConsumerWidget {
|
|||||||
useBreakpointValue(sm: 10, md: 15, lg: 20, xl: 20, xxl: 20);
|
useBreakpointValue(sm: 10, md: 15, lg: 20, xl: 20, xxl: 20);
|
||||||
|
|
||||||
final updating = useState(false);
|
final updating = useState(false);
|
||||||
|
final spotify = ref.watch(spotifyProvider);
|
||||||
|
final scaffold = ScaffoldMessenger.of(context);
|
||||||
|
|
||||||
return PlaybuttonCard(
|
return PlaybuttonCard(
|
||||||
viewType: viewType,
|
viewType: viewType,
|
||||||
@ -66,9 +67,8 @@ class PlaylistCard extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<Track> fetchedTracks = await queryBowl.fetchQuery(
|
List<Track> fetchedTracks = await queryBowl.fetchQuery(
|
||||||
key: ValueKey(const Uuid().v4()),
|
"playlist-tracks/${playlist.id}",
|
||||||
Queries.playlist.tracksOf(playlist.id!),
|
() => useQueries.playlist.tracksOf(playlist.id!, spotify),
|
||||||
externalData: ref.read(spotifyProvider),
|
|
||||||
) ??
|
) ??
|
||||||
[];
|
[];
|
||||||
|
|
||||||
@ -85,9 +85,8 @@ class PlaylistCard extends HookConsumerWidget {
|
|||||||
try {
|
try {
|
||||||
if (isPlaylistPlaying) return;
|
if (isPlaylistPlaying) return;
|
||||||
List<Track> fetchedTracks = await queryBowl.fetchQuery(
|
List<Track> fetchedTracks = await queryBowl.fetchQuery(
|
||||||
key: ValueKey(const Uuid().v4()),
|
"playlist-tracks/${playlist.id}",
|
||||||
Queries.playlist.tracksOf(playlist.id!),
|
() => useQueries.playlist.tracksOf(playlist.id!, spotify),
|
||||||
externalData: ref.read(spotifyProvider),
|
|
||||||
) ??
|
) ??
|
||||||
[];
|
[];
|
||||||
|
|
||||||
@ -95,7 +94,7 @@ class PlaylistCard extends HookConsumerWidget {
|
|||||||
|
|
||||||
playlistNotifier.add(fetchedTracks);
|
playlistNotifier.add(fetchedTracks);
|
||||||
tracks.value = fetchedTracks;
|
tracks.value = fetchedTracks;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
scaffold.showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text("Added ${fetchedTracks.length} tracks to queue"),
|
content: Text("Added ${fetchedTracks.length} tracks to queue"),
|
||||||
),
|
),
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import 'package:fl_query/fl_query.dart';
|
import 'package:fl_query_hooks/fl_query_hooks.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
@ -7,7 +7,6 @@ import 'package:platform_ui/platform_ui.dart';
|
|||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.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/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);
|
||||||
@ -28,6 +27,8 @@ class PlaylistCreateDialog extends HookConsumerWidget {
|
|||||||
final description = useTextEditingController();
|
final description = useTextEditingController();
|
||||||
final public = useState(false);
|
final public = useState(false);
|
||||||
final collaborative = useState(false);
|
final collaborative = useState(false);
|
||||||
|
final client = useQueryClient();
|
||||||
|
final navigator = Navigator.of(context);
|
||||||
|
|
||||||
onCreate() async {
|
onCreate() async {
|
||||||
if (playlistName.text.isEmpty) return;
|
if (playlistName.text.isEmpty) return;
|
||||||
@ -39,12 +40,12 @@ class PlaylistCreateDialog extends HookConsumerWidget {
|
|||||||
public: public.value,
|
public: public.value,
|
||||||
description: description.text,
|
description: description.text,
|
||||||
);
|
);
|
||||||
await QueryBowl.of(context)
|
await client
|
||||||
.getQuery(
|
.getQuery(
|
||||||
Queries.playlist.ofMine.queryKey,
|
"current-user-playlists",
|
||||||
)
|
)
|
||||||
?.refetch();
|
?.refresh();
|
||||||
Navigator.pop(context);
|
navigator.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
return PlatformAlertDialog(
|
return PlatformAlertDialog(
|
||||||
|
@ -1,73 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
||||||
import 'package:platform_ui/platform_ui.dart';
|
|
||||||
import 'package:spotify/spotify.dart';
|
|
||||||
import 'package:spotube/components/shared/page_window_title_bar.dart';
|
|
||||||
import 'package:spotube/components/playlist/playlist_card.dart';
|
|
||||||
import 'package:spotube/provider/spotify_provider.dart';
|
|
||||||
|
|
||||||
class PlaylistGenreView extends ConsumerWidget {
|
|
||||||
final String genreId;
|
|
||||||
final String genreName;
|
|
||||||
final Iterable<PlaylistSimple>? playlists;
|
|
||||||
const PlaylistGenreView(
|
|
||||||
this.genreId,
|
|
||||||
this.genreName, {
|
|
||||||
this.playlists,
|
|
||||||
Key? key,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context, ref) {
|
|
||||||
return Scaffold(
|
|
||||||
appBar: PageWindowTitleBar(
|
|
||||||
leading: const PlatformBackButton(),
|
|
||||||
),
|
|
||||||
body: Column(
|
|
||||||
children: [
|
|
||||||
PlatformText.subheading(
|
|
||||||
genreName,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
Consumer(
|
|
||||||
builder: (context, ref, child) {
|
|
||||||
SpotifyApi spotifyApi = ref.watch(spotifyProvider);
|
|
||||||
return Expanded(
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
child: FutureBuilder<Iterable<PlaylistSimple>>(
|
|
||||||
future: playlists == null
|
|
||||||
? (genreId != "user-featured-playlists"
|
|
||||||
? spotifyApi.playlists
|
|
||||||
.getByCategoryId(genreId)
|
|
||||||
.all()
|
|
||||||
: spotifyApi.playlists.featured.all())
|
|
||||||
: Future.value(playlists),
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
if (snapshot.hasError) {
|
|
||||||
return const Center(child: Text("Error occurred"));
|
|
||||||
}
|
|
||||||
if (!snapshot.hasData) {
|
|
||||||
return const PlatformCircularProgressIndicator();
|
|
||||||
}
|
|
||||||
return Center(
|
|
||||||
child: Wrap(
|
|
||||||
children: snapshot.data!
|
|
||||||
.map(
|
|
||||||
(playlist) => Padding(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: PlaylistCard(playlist),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +1,4 @@
|
|||||||
import 'package:badges/badges.dart';
|
import 'package:badges/badges.dart';
|
||||||
import 'package:fl_query_hooks/fl_query_hooks.dart';
|
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
@ -12,7 +11,6 @@ import 'package:spotube/components/shared/image/universal_image.dart';
|
|||||||
import 'package:spotube/hooks/use_breakpoints.dart';
|
import 'package:spotube/hooks/use_breakpoints.dart';
|
||||||
import 'package:spotube/provider/authentication_provider.dart';
|
import 'package:spotube/provider/authentication_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/user_preferences_provider.dart';
|
import 'package:spotube/provider/user_preferences_provider.dart';
|
||||||
import 'package:spotube/services/queries/queries.dart';
|
import 'package:spotube/services/queries/queries.dart';
|
||||||
@ -195,11 +193,7 @@ class SidebarFooter extends HookConsumerWidget {
|
|||||||
width: 256,
|
width: 256,
|
||||||
child: HookBuilder(
|
child: HookBuilder(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
var spotify = ref.watch(spotifyProvider);
|
final me = useQueries.user.me(ref);
|
||||||
final me = useQuery(
|
|
||||||
job: Queries.user.me,
|
|
||||||
externalData: spotify,
|
|
||||||
);
|
|
||||||
final data = me.data;
|
final data = me.data;
|
||||||
|
|
||||||
final avatarImg = TypeConversionUtils.image_X_UrlString(
|
final avatarImg = TypeConversionUtils.image_X_UrlString(
|
||||||
@ -208,20 +202,8 @@ class SidebarFooter extends HookConsumerWidget {
|
|||||||
placeholder: ImagePlaceholder.artist,
|
placeholder: ImagePlaceholder.artist,
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO: Remove below code after fl-query ^0.4.0
|
|
||||||
/// Temporary fix before fl-query 0.4.0
|
|
||||||
final auth = ref.watch(AuthenticationNotifier.provider);
|
final auth = ref.watch(AuthenticationNotifier.provider);
|
||||||
|
|
||||||
useEffect(() {
|
|
||||||
if (auth != null && me.hasError) {
|
|
||||||
me.setExternalData(spotify);
|
|
||||||
me.refetch();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}, [auth, me.hasError]);
|
|
||||||
|
|
||||||
/// ===================================
|
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(16).copyWith(left: 0),
|
padding: const EdgeInsets.all(16).copyWith(left: 0),
|
||||||
child: Row(
|
child: Row(
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
import 'package:fl_query_hooks/fl_query_hooks.dart';
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.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:platform_ui/platform_ui.dart';
|
import 'package:platform_ui/platform_ui.dart';
|
||||||
@ -18,14 +16,8 @@ class PlaylistAddTrackDialog extends HookConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
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 = useQueries.playlist.ofMine(ref);
|
||||||
job: Queries.playlist.ofMine,
|
final me = useQueries.user.me(ref);
|
||||||
externalData: spotify,
|
|
||||||
);
|
|
||||||
final me = useQuery(
|
|
||||||
job: Queries.user.me,
|
|
||||||
externalData: spotify,
|
|
||||||
);
|
|
||||||
final filteredPlaylists = userPlaylists.data?.where(
|
final filteredPlaylists = userPlaylists.data?.where(
|
||||||
(playlist) =>
|
(playlist) =>
|
||||||
playlist.owner?.id != null && playlist.owner!.id == me.data?.id,
|
playlist.owner?.id != null && playlist.owner!.id == me.data?.id,
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import 'package:fl_query/fl_query.dart';
|
import 'package:fl_query/fl_query.dart';
|
||||||
import 'package:fl_query_hooks/fl_query_hooks.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
@ -8,7 +7,6 @@ import 'package:spotify/spotify.dart';
|
|||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/hooks/use_palette_color.dart';
|
import 'package:spotube/hooks/use_palette_color.dart';
|
||||||
import 'package:spotube/provider/authentication_provider.dart';
|
import 'package:spotube/provider/authentication_provider.dart';
|
||||||
import 'package:spotube/provider/spotify_provider.dart';
|
|
||||||
import 'package:spotube/services/mutations/mutations.dart';
|
import 'package:spotube/services/mutations/mutations.dart';
|
||||||
import 'package:spotube/services/queries/queries.dart';
|
import 'package:spotube/services/queries/queries.dart';
|
||||||
|
|
||||||
@ -47,59 +45,36 @@ class HeartButton extends ConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Tuple3<bool, Mutation<bool, Tuple2<SpotifyApi, bool>>, Query<User, SpotifyApi>>
|
Tuple3<bool, Mutation<bool, dynamic, bool>, Query<User, dynamic>>
|
||||||
useTrackToggleLike(Track track, WidgetRef ref) {
|
useTrackToggleLike(Track track, WidgetRef ref) {
|
||||||
final me =
|
final me = useQueries.user.me(ref);
|
||||||
useQuery(job: Queries.user.me, externalData: ref.watch(spotifyProvider));
|
|
||||||
|
|
||||||
final savedTracks = useQuery(
|
final savedTracks =
|
||||||
job: Queries.playlist.tracksOf("user-liked-tracks"),
|
useQueries.playlist.tracksOfQuery(ref, "user-liked-tracks");
|
||||||
externalData: ref.watch(spotifyProvider),
|
|
||||||
);
|
|
||||||
|
|
||||||
final isLiked =
|
final isLiked =
|
||||||
savedTracks.data?.map((track) => track.id).contains(track.id) ?? false;
|
savedTracks.data?.map((track) => track.id).contains(track.id) ?? false;
|
||||||
|
|
||||||
final mounted = useIsMounted();
|
final mounted = useIsMounted();
|
||||||
|
|
||||||
final toggleTrackLike = useMutation<bool, Tuple2<SpotifyApi, bool>>(
|
final toggleTrackLike = useMutations.track.toggleFavorite(
|
||||||
job: Mutations.track.toggleFavorite(track.id!),
|
ref,
|
||||||
onMutate: (variable) {
|
track.id!,
|
||||||
savedTracks.setQueryData(
|
onMutate: (variables) {
|
||||||
(oldData) {
|
return variables;
|
||||||
if (!variable.item2) {
|
},
|
||||||
return [...(oldData ?? []), track];
|
onError: (payload, isLiked) {
|
||||||
}
|
if (!mounted()) return;
|
||||||
|
|
||||||
return oldData
|
savedTracks.setData(
|
||||||
|
isLiked == true
|
||||||
|
? [...(savedTracks.data ?? []), track]
|
||||||
|
: savedTracks.data
|
||||||
?.where(
|
?.where(
|
||||||
(element) => element.id != track.id,
|
(element) => element.id != track.id,
|
||||||
)
|
)
|
||||||
.toList() ??
|
.toList() ??
|
||||||
[];
|
[],
|
||||||
},
|
|
||||||
);
|
|
||||||
return track;
|
|
||||||
},
|
|
||||||
onData: (payload, variables, _) {
|
|
||||||
if (!mounted()) return;
|
|
||||||
savedTracks.refetch();
|
|
||||||
},
|
|
||||||
onError: (payload, variables, queryContext) {
|
|
||||||
if (!mounted()) return;
|
|
||||||
savedTracks.setQueryData(
|
|
||||||
(oldData) {
|
|
||||||
if (variables.item2) {
|
|
||||||
return [...(oldData ?? []), track];
|
|
||||||
}
|
|
||||||
|
|
||||||
return oldData
|
|
||||||
?.where(
|
|
||||||
(element) => element.id != track.id,
|
|
||||||
)
|
|
||||||
.toList() ??
|
|
||||||
[];
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -116,10 +91,8 @@ class TrackHeartButton extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
final savedTracks = useQuery(
|
final savedTracks =
|
||||||
job: Queries.playlist.tracksOf("user-liked-tracks"),
|
useQueries.playlist.tracksOfQuery(ref, "user-liked-tracks");
|
||||||
externalData: ref.watch(spotifyProvider),
|
|
||||||
);
|
|
||||||
final toggler = useTrackToggleLike(track, ref);
|
final toggler = useTrackToggleLike(track, ref);
|
||||||
if (toggler.item3.isLoading || !toggler.item3.hasData) {
|
if (toggler.item3.isLoading || !toggler.item3.hasData) {
|
||||||
return const PlatformCircularProgressIndicator();
|
return const PlatformCircularProgressIndicator();
|
||||||
@ -130,9 +103,7 @@ class TrackHeartButton extends HookConsumerWidget {
|
|||||||
isLiked: toggler.item1,
|
isLiked: toggler.item1,
|
||||||
onPressed: savedTracks.hasData
|
onPressed: savedTracks.hasData
|
||||||
? () {
|
? () {
|
||||||
toggler.item2.mutate(
|
toggler.item2.mutate(toggler.item1);
|
||||||
Tuple2(ref.read(spotifyProvider), toggler.item1),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
);
|
);
|
||||||
@ -149,26 +120,21 @@ class PlaylistHeartButton extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
final me = useQuery(
|
final me = useQueries.user.me(ref);
|
||||||
job: Queries.user.me,
|
|
||||||
externalData: ref.watch(spotifyProvider),
|
final isLikedQuery = useQueries.playlist.doesUserFollow(
|
||||||
|
ref,
|
||||||
|
playlist.id!,
|
||||||
|
me.data?.id ?? '',
|
||||||
);
|
);
|
||||||
|
|
||||||
final job =
|
final togglePlaylistLike = useMutations.playlist.toggleFavorite(
|
||||||
Queries.playlist.doesUserFollow("${playlist.id}:${me.data?.id}");
|
ref,
|
||||||
final isLikedQuery = useQuery(
|
playlist.id!,
|
||||||
job: job,
|
refreshQueries: [
|
||||||
externalData: ref.watch(spotifyProvider),
|
isLikedQuery.key,
|
||||||
);
|
"current-user-playlists",
|
||||||
|
],
|
||||||
final togglePlaylistLike = useMutation<bool, Tuple2<SpotifyApi, bool>>(
|
|
||||||
job: Mutations.playlist.toggleFavorite(playlist.id!),
|
|
||||||
onData: (payload, variables, queryContext) async {
|
|
||||||
await isLikedQuery.refetch();
|
|
||||||
await QueryBowl.of(context)
|
|
||||||
.getQuery(Queries.playlist.ofMine.queryKey)
|
|
||||||
?.refetch();
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
final titleImage = useMemoized(
|
final titleImage = useMemoized(
|
||||||
@ -195,12 +161,7 @@ class PlaylistHeartButton extends HookConsumerWidget {
|
|||||||
color: color?.titleTextColor,
|
color: color?.titleTextColor,
|
||||||
onPressed: isLikedQuery.hasData
|
onPressed: isLikedQuery.hasData
|
||||||
? () {
|
? () {
|
||||||
togglePlaylistLike.mutate(
|
togglePlaylistLike.mutate(isLikedQuery.data!);
|
||||||
Tuple2(
|
|
||||||
ref.read(spotifyProvider),
|
|
||||||
isLikedQuery.data!,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
);
|
);
|
||||||
@ -217,26 +178,18 @@ class AlbumHeartButton extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
final spotify = ref.watch(spotifyProvider);
|
final me = useQueries.user.me(ref);
|
||||||
final me = useQuery(
|
|
||||||
job: Queries.user.me,
|
|
||||||
externalData: spotify,
|
|
||||||
);
|
|
||||||
|
|
||||||
final albumIsSaved = useQuery(
|
final albumIsSaved = useQueries.album.isSavedForMe(ref, album.id!);
|
||||||
job: Queries.album.isSavedForMe(album.id!),
|
|
||||||
externalData: spotify,
|
|
||||||
);
|
|
||||||
final isLiked = albumIsSaved.data ?? false;
|
final isLiked = albumIsSaved.data ?? false;
|
||||||
|
|
||||||
final toggleAlbumLike = useMutation<bool, Tuple2<SpotifyApi, bool>>(
|
final toggleAlbumLike = useMutations.album.toggleFavorite(
|
||||||
job: Mutations.album.toggleFavorite(album.id!),
|
ref,
|
||||||
onData: (payload, variables, queryContext) {
|
album.id!,
|
||||||
albumIsSaved.refetch();
|
refreshQueries: [
|
||||||
QueryBowl.of(context)
|
albumIsSaved.key,
|
||||||
.getQuery(Queries.album.ofMine.queryKey)
|
"current-user-albums",
|
||||||
?.refetch();
|
],
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (me.isLoading || !me.hasData) {
|
if (me.isLoading || !me.hasData) {
|
||||||
@ -248,8 +201,7 @@ class AlbumHeartButton extends HookConsumerWidget {
|
|||||||
tooltip: isLiked ? "Remove from Favorite" : "Add to Favorite",
|
tooltip: isLiked ? "Remove from Favorite" : "Add to Favorite",
|
||||||
onPressed: albumIsSaved.hasData
|
onPressed: albumIsSaved.hasData
|
||||||
? () {
|
? () {
|
||||||
toggleAlbumLike
|
toggleAlbumLike.mutate(isLiked);
|
||||||
.mutate(Tuple2(ref.read(spotifyProvider), isLiked));
|
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
);
|
);
|
||||||
|
@ -218,7 +218,7 @@ class TrackCollectionView<T> extends HookConsumerWidget {
|
|||||||
: null,
|
: null,
|
||||||
body: RefreshIndicator(
|
body: RefreshIndicator(
|
||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
await tracksSnapshot.refetch();
|
await tracksSnapshot.refresh();
|
||||||
},
|
},
|
||||||
child: CustomScrollView(
|
child: CustomScrollView(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
@ -333,8 +333,7 @@ class TrackCollectionView<T> extends HookConsumerWidget {
|
|||||||
builder: (context) {
|
builder: (context) {
|
||||||
if (tracksSnapshot.isLoading || !tracksSnapshot.hasData) {
|
if (tracksSnapshot.isLoading || !tracksSnapshot.hasData) {
|
||||||
return const ShimmerTrackTile();
|
return const ShimmerTrackTile();
|
||||||
} else if (tracksSnapshot.hasError &&
|
} else if (tracksSnapshot.hasError) {
|
||||||
tracksSnapshot.isError) {
|
|
||||||
return SliverToBoxAdapter(
|
return SliverToBoxAdapter(
|
||||||
child: PlatformText("Error ${tracksSnapshot.error}"));
|
child: PlatformText("Error ${tracksSnapshot.error}"));
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
import 'package:auto_size_text/auto_size_text.dart';
|
import 'package:auto_size_text/auto_size_text.dart';
|
||||||
import 'package:fl_query/fl_query.dart';
|
|
||||||
import 'package:fl_query_hooks/fl_query_hooks.dart';
|
|
||||||
import 'package:flutter/material.dart' hide Action;
|
import 'package:flutter/material.dart' hide Action;
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
@ -21,10 +19,8 @@ import 'package:spotube/provider/blacklist_provider.dart';
|
|||||||
import 'package:spotube/provider/spotify_provider.dart';
|
import 'package:spotube/provider/spotify_provider.dart';
|
||||||
import 'package:spotube/provider/playlist_queue_provider.dart';
|
import 'package:spotube/provider/playlist_queue_provider.dart';
|
||||||
import 'package:spotube/services/mutations/mutations.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';
|
|
||||||
|
|
||||||
class TrackTile extends HookConsumerWidget {
|
class TrackTile extends HookConsumerWidget {
|
||||||
final PlaylistQueue? playlist;
|
final PlaylistQueue? playlist;
|
||||||
@ -77,16 +73,9 @@ class TrackTile extends HookConsumerWidget {
|
|||||||
final playlistQueueNotifier = ref.watch(PlaylistQueueNotifier.notifier);
|
final playlistQueueNotifier = ref.watch(PlaylistQueueNotifier.notifier);
|
||||||
|
|
||||||
final removingTrack = useState<String?>(null);
|
final removingTrack = useState<String?>(null);
|
||||||
final removeTrack = useMutation<bool, Tuple2<SpotifyApi, String>>(
|
final removeTrack = useMutations.playlist.removeTrackOf(
|
||||||
job: Mutations.playlist.removeTrackOf(playlistId ?? ""),
|
ref,
|
||||||
onData: (payload, variables, ctx) {
|
playlistId ?? "",
|
||||||
if (playlistId == null || !payload) return;
|
|
||||||
QueryBowl.of(context)
|
|
||||||
.getQuery(
|
|
||||||
Queries.playlist.tracksOf(playlistId!).queryKey,
|
|
||||||
)
|
|
||||||
?.refetch();
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
void actionShare(Track track) {
|
void actionShare(Track track) {
|
||||||
@ -359,7 +348,7 @@ class TrackTile extends HookConsumerWidget {
|
|||||||
: const Icon(SpotubeIcons.heart),
|
: const Icon(SpotubeIcons.heart),
|
||||||
text: const PlatformText("Save as favorite"),
|
text: const PlatformText("Save as favorite"),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
toggler.item2.mutate(Tuple2(spotify, toggler.item1));
|
toggler.item2.mutate(toggler.item1);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (auth != null)
|
if (auth != null)
|
||||||
@ -370,7 +359,7 @@ class TrackTile extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
if (userPlaylist && auth != null)
|
if (userPlaylist && auth != null)
|
||||||
Action(
|
Action(
|
||||||
icon: (removeTrack.isLoading || !removeTrack.hasData) &&
|
icon: (removeTrack.isMutating || !removeTrack.hasData) &&
|
||||||
removingTrack.value == track.value.uri
|
removingTrack.value == track.value.uri
|
||||||
? const Center(
|
? const Center(
|
||||||
child: PlatformCircularProgressIndicator(),
|
child: PlatformCircularProgressIndicator(),
|
||||||
@ -379,7 +368,7 @@ class TrackTile extends HookConsumerWidget {
|
|||||||
text: const PlatformText("Remove from playlist"),
|
text: const PlatformText("Remove from playlist"),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
removingTrack.value = track.value.uri;
|
removingTrack.value = track.value.uri;
|
||||||
removeTrack.mutate(Tuple2(spotify, track.value.uri!));
|
removeTrack.mutate(track.value.uri!);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Action(
|
Action(
|
||||||
|
15
lib/extensions/map.dart
Normal file
15
lib/extensions/map.dart
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
extension CastDeepMaps on Map {
|
||||||
|
Map<K2, dynamic> castKeyDeep<K2>() {
|
||||||
|
return cast<K2, dynamic>().map((key, value) {
|
||||||
|
if (value is Map) {
|
||||||
|
return MapEntry(key, value.castKeyDeep<K2>());
|
||||||
|
} else if (value is List) {
|
||||||
|
return MapEntry(
|
||||||
|
key,
|
||||||
|
value.map((e) => e is Map ? e.castKeyDeep<K2>() : e).toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return MapEntry(key, value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
61
lib/extensions/page.dart
Normal file
61
lib/extensions/page.dart
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import 'package:spotify/spotify.dart';
|
||||||
|
|
||||||
|
extension CursorPageJson<T> on CursorPage<T> {
|
||||||
|
static CursorPage<T> fromJson<T>(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
T Function(dynamic json) itemFromJson,
|
||||||
|
) {
|
||||||
|
final metadata = Paging.fromJson(json["metadata"]);
|
||||||
|
final paging = CursorPaging<T>();
|
||||||
|
paging.cursors = Cursor.fromJson(json["metadata"])..after = json["after"];
|
||||||
|
paging.href = metadata.href;
|
||||||
|
paging.itemsNative = paging.itemsNative;
|
||||||
|
paging.limit = metadata.limit;
|
||||||
|
paging.next = metadata.next;
|
||||||
|
return CursorPage<T>(
|
||||||
|
paging,
|
||||||
|
itemFromJson,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
"after": after,
|
||||||
|
"metadata": metadata.toJson(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PagingToJson<T> on Paging<T> {
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
"items": itemsNative,
|
||||||
|
"total": total,
|
||||||
|
"next": next,
|
||||||
|
"previous": previous,
|
||||||
|
"limit": limit,
|
||||||
|
"offset": offset,
|
||||||
|
"href": href,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PageJson<T> on Page<T> {
|
||||||
|
static Page<T> fromJson<T>(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
T Function(dynamic json) itemFromJson,
|
||||||
|
) {
|
||||||
|
return Page<T>(
|
||||||
|
Paging<T>.fromJson(
|
||||||
|
Map.castFrom<dynamic, dynamic, String, dynamic>(json["metadata"]),
|
||||||
|
),
|
||||||
|
itemFromJson,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
"metadata": metadata.toJson(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
53
lib/hooks/use_spotify_infinite_query.dart
Normal file
53
lib/hooks/use_spotify_infinite_query.dart
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:fl_query/fl_query.dart';
|
||||||
|
import 'package:fl_query_hooks/fl_query_hooks.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:spotify/spotify.dart';
|
||||||
|
import 'package:spotube/provider/spotify_provider.dart';
|
||||||
|
|
||||||
|
InfiniteQuery<DataType, ErrorType, PageType>
|
||||||
|
useSpotifyInfiniteQuery<DataType, ErrorType, PageType>(
|
||||||
|
String queryKey,
|
||||||
|
FutureOr<DataType?> Function(PageType page, SpotifyApi spotify) queryFn, {
|
||||||
|
required WidgetRef ref,
|
||||||
|
required InfiniteQueryNextPage<DataType, PageType> nextPage,
|
||||||
|
required PageType initialPage,
|
||||||
|
RetryConfig retryConfig = DefaultConstants.retryConfig,
|
||||||
|
RefreshConfig refreshConfig = DefaultConstants.refreshConfig,
|
||||||
|
JsonConfig<DataType>? jsonConfig,
|
||||||
|
ValueChanged<PageEvent<DataType, PageType>>? onData,
|
||||||
|
ValueChanged<PageEvent<ErrorType, PageType>>? onError,
|
||||||
|
bool enabled = true,
|
||||||
|
List<Object?>? keys,
|
||||||
|
}) {
|
||||||
|
final spotify = ref.watch(spotifyProvider);
|
||||||
|
final query = useInfiniteQuery<DataType, ErrorType, PageType>(
|
||||||
|
queryKey,
|
||||||
|
(page) => queryFn(page, spotify),
|
||||||
|
nextPage: nextPage,
|
||||||
|
initialPage: initialPage,
|
||||||
|
retryConfig: retryConfig,
|
||||||
|
refreshConfig: refreshConfig,
|
||||||
|
jsonConfig: jsonConfig,
|
||||||
|
onData: onData,
|
||||||
|
onError: onError,
|
||||||
|
enabled: enabled,
|
||||||
|
keys: keys,
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() {
|
||||||
|
return ref.listenManual(
|
||||||
|
spotifyProvider,
|
||||||
|
(previous, next) {
|
||||||
|
if (previous != next) {
|
||||||
|
query.refreshAll();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
).close;
|
||||||
|
}, [query]);
|
||||||
|
|
||||||
|
return query;
|
||||||
|
}
|
36
lib/hooks/use_spotify_mutation.dart
Normal file
36
lib/hooks/use_spotify_mutation.dart
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import 'package:fl_query/fl_query.dart';
|
||||||
|
import 'package:fl_query_hooks/fl_query_hooks.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:spotify/spotify.dart';
|
||||||
|
import 'package:spotube/provider/spotify_provider.dart';
|
||||||
|
|
||||||
|
Mutation<DataType, ErrorType, VariablesType>
|
||||||
|
useSpotifyMutation<DataType, ErrorType, VariablesType, RecoveryType>(
|
||||||
|
String mutationKey,
|
||||||
|
Future<DataType> Function(VariablesType variables, SpotifyApi spotify)
|
||||||
|
mutationFn, {
|
||||||
|
required WidgetRef ref,
|
||||||
|
RetryConfig retryConfig = DefaultConstants.retryConfig,
|
||||||
|
MutationOnDataFn<DataType, RecoveryType>? onData,
|
||||||
|
MutationOnErrorFn<ErrorType, RecoveryType>? onError,
|
||||||
|
MutationOnMutationFn<VariablesType, RecoveryType>? onMutate,
|
||||||
|
List<String>? refreshQueries,
|
||||||
|
List<String>? refreshInfiniteQueries,
|
||||||
|
List<Object?>? keys,
|
||||||
|
}) {
|
||||||
|
final spotify = ref.watch(spotifyProvider);
|
||||||
|
final mutation =
|
||||||
|
useMutation<DataType, ErrorType, VariablesType, RecoveryType>(
|
||||||
|
mutationKey,
|
||||||
|
(variables) => mutationFn(variables, spotify),
|
||||||
|
retryConfig: retryConfig,
|
||||||
|
onData: onData,
|
||||||
|
onError: onError,
|
||||||
|
onMutate: onMutate,
|
||||||
|
refreshQueries: refreshQueries,
|
||||||
|
refreshInfiniteQueries: refreshInfiniteQueries,
|
||||||
|
keys: keys,
|
||||||
|
);
|
||||||
|
|
||||||
|
return mutation;
|
||||||
|
}
|
52
lib/hooks/use_spotify_query.dart
Normal file
52
lib/hooks/use_spotify_query.dart
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:fl_query/fl_query.dart';
|
||||||
|
import 'package:fl_query_hooks/fl_query_hooks.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:spotify/spotify.dart';
|
||||||
|
import 'package:spotube/provider/spotify_provider.dart';
|
||||||
|
|
||||||
|
typedef SpotifyQueryFn<DataType> = FutureOr<DataType?> Function(
|
||||||
|
SpotifyApi spotify);
|
||||||
|
|
||||||
|
Query<DataType, ErrorType> useSpotifyQuery<DataType, ErrorType>(
|
||||||
|
final String queryKey,
|
||||||
|
final SpotifyQueryFn<DataType> queryFn, {
|
||||||
|
required WidgetRef ref,
|
||||||
|
final DataType? initial,
|
||||||
|
final RetryConfig retryConfig = DefaultConstants.retryConfig,
|
||||||
|
final RefreshConfig refreshConfig = DefaultConstants.refreshConfig,
|
||||||
|
final JsonConfig<DataType>? jsonConfig,
|
||||||
|
final ValueChanged<DataType>? onData,
|
||||||
|
final ValueChanged<ErrorType>? onError,
|
||||||
|
final bool enabled = true,
|
||||||
|
}) {
|
||||||
|
final spotify = ref.watch(spotifyProvider);
|
||||||
|
|
||||||
|
final query = useQuery<DataType, ErrorType>(
|
||||||
|
queryKey,
|
||||||
|
() => queryFn(spotify),
|
||||||
|
initial: initial,
|
||||||
|
retryConfig: retryConfig,
|
||||||
|
refreshConfig: refreshConfig,
|
||||||
|
jsonConfig: jsonConfig,
|
||||||
|
onData: onData,
|
||||||
|
onError: onError,
|
||||||
|
enabled: enabled,
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() {
|
||||||
|
return ref.listenManual(
|
||||||
|
spotifyProvider,
|
||||||
|
(previous, next) {
|
||||||
|
if (previous != next) {
|
||||||
|
query.refresh();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
).close;
|
||||||
|
}, [query]);
|
||||||
|
|
||||||
|
return query;
|
||||||
|
}
|
@ -31,7 +31,6 @@ import 'package:spotube/utils/platform.dart';
|
|||||||
import 'package:window_manager/window_manager.dart';
|
import 'package:window_manager/window_manager.dart';
|
||||||
import 'package:window_size/window_size.dart';
|
import 'package:window_size/window_size.dart';
|
||||||
|
|
||||||
final bowl = QueryBowl();
|
|
||||||
void main(List<String> rawArgs) async {
|
void main(List<String> rawArgs) async {
|
||||||
final parser = ArgParser();
|
final parser = ArgParser();
|
||||||
|
|
||||||
@ -70,7 +69,7 @@ void main(List<String> rawArgs) async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await Hive.initFlutter();
|
await QueryClient.initialize(cachePrefix: "oss.krtirtho.spotube");
|
||||||
Hive.registerAdapter(CacheTrackAdapter());
|
Hive.registerAdapter(CacheTrackAdapter());
|
||||||
Hive.registerAdapter(CacheTrackEngagementAdapter());
|
Hive.registerAdapter(CacheTrackEngagementAdapter());
|
||||||
Hive.registerAdapter(CacheTrackSkipSegmentAdapter());
|
Hive.registerAdapter(CacheTrackSkipSegmentAdapter());
|
||||||
@ -173,8 +172,7 @@ void main(List<String> rawArgs) async {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
child: QueryBowlScope(
|
child: QueryClientProvider(
|
||||||
bowl: bowl,
|
|
||||||
child: const Spotube(),
|
child: const Spotube(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import 'package:fl_query_hooks/fl_query_hooks.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
@ -12,7 +11,6 @@ import 'package:spotube/provider/playlist_queue_provider.dart';
|
|||||||
import 'package:spotube/services/queries/queries.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/provider/spotify_provider.dart';
|
|
||||||
|
|
||||||
class AlbumPage extends HookConsumerWidget {
|
class AlbumPage extends HookConsumerWidget {
|
||||||
final AlbumSimple album;
|
final AlbumSimple album;
|
||||||
@ -47,12 +45,7 @@ class AlbumPage extends HookConsumerWidget {
|
|||||||
ref.watch(PlaylistQueueNotifier.provider);
|
ref.watch(PlaylistQueueNotifier.provider);
|
||||||
final playback = ref.watch(PlaylistQueueNotifier.notifier);
|
final playback = ref.watch(PlaylistQueueNotifier.notifier);
|
||||||
|
|
||||||
final SpotifyApi spotify = ref.watch(spotifyProvider);
|
final tracksSnapshot = useQueries.album.tracksOf(ref, album.id!);
|
||||||
|
|
||||||
final tracksSnapshot = useQuery(
|
|
||||||
job: Queries.album.tracksOf(album.id!),
|
|
||||||
externalData: spotify,
|
|
||||||
);
|
|
||||||
|
|
||||||
final albumArt = useMemoized(
|
final albumArt = useMemoized(
|
||||||
() => TypeConversionUtils.image_X_UrlString(
|
() => TypeConversionUtils.image_X_UrlString(
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import 'package:fl_query/fl_query.dart';
|
import 'package:fl_query/fl_query.dart';
|
||||||
import 'package:fl_query_hooks/fl_query_hooks.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
@ -65,10 +64,7 @@ class ArtistPage extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
body: HookBuilder(
|
body: HookBuilder(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
final artistsQuery = useQuery(
|
final artistsQuery = useQueries.artist.get(ref, artistId);
|
||||||
job: Queries.artist.get(artistId),
|
|
||||||
externalData: spotify,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (artistsQuery.isLoading || !artistsQuery.hasData) {
|
if (artistsQuery.isLoading || !artistsQuery.hasData) {
|
||||||
return const ShimmerArtistProfile();
|
return const ShimmerArtistProfile();
|
||||||
@ -166,10 +162,8 @@ class ArtistPage extends HookConsumerWidget {
|
|||||||
if (auth != null)
|
if (auth != null)
|
||||||
HookBuilder(
|
HookBuilder(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
final isFollowingQuery = useQuery(
|
final isFollowingQuery = useQueries.artist
|
||||||
job: Queries.artist.doIFollow(artistId),
|
.doIFollow(ref, artistId);
|
||||||
externalData: spotify,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (isFollowingQuery.isLoading ||
|
if (isFollowingQuery.isLoading ||
|
||||||
!isFollowingQuery.hasData) {
|
!isFollowingQuery.hasData) {
|
||||||
@ -181,7 +175,7 @@ class ArtistPage extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final queryBowl = QueryBowl.of(context);
|
final queryBowl = QueryClient.of(context);
|
||||||
|
|
||||||
return PlatformFilledButton(
|
return PlatformFilledButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
@ -195,21 +189,14 @@ class ArtistPage extends HookConsumerWidget {
|
|||||||
FollowingType.artist,
|
FollowingType.artist,
|
||||||
[artistId],
|
[artistId],
|
||||||
);
|
);
|
||||||
await isFollowingQuery.refetch();
|
await isFollowingQuery.refresh();
|
||||||
|
|
||||||
queryBowl
|
queryBowl
|
||||||
.getInfiniteQuery(
|
.refreshInfiniteQueryAllPages(
|
||||||
Queries.artist.followedByMe
|
"user-following-artists");
|
||||||
.queryKey,
|
|
||||||
)
|
|
||||||
?.refetch();
|
|
||||||
} finally {
|
} finally {
|
||||||
QueryBowl.of(context)
|
QueryClient.of(context).refreshQuery(
|
||||||
.refetchQueries([
|
"user-follows-artists-query/$artistId");
|
||||||
Queries.artist
|
|
||||||
.doIFollow(artistId)
|
|
||||||
.queryKey,
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: PlatformText(
|
child: PlatformText(
|
||||||
@ -281,9 +268,9 @@ class ArtistPage extends HookConsumerWidget {
|
|||||||
const SizedBox(height: 50),
|
const SizedBox(height: 50),
|
||||||
HookBuilder(
|
HookBuilder(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
final topTracksQuery = useQuery(
|
final topTracksQuery = useQueries.artist.topTracksOf(
|
||||||
job: Queries.artist.topTracksOf(artistId),
|
ref,
|
||||||
externalData: spotify,
|
artistId,
|
||||||
);
|
);
|
||||||
|
|
||||||
final isPlaylistPlaying =
|
final isPlaylistPlaying =
|
||||||
@ -391,9 +378,9 @@ class ArtistPage extends HookConsumerWidget {
|
|||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
HookBuilder(
|
HookBuilder(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
final relatedArtists = useQuery(
|
final relatedArtists = useQueries.artist.relatedArtistsOf(
|
||||||
job: Queries.artist.relatedArtistsOf(artistId),
|
ref,
|
||||||
externalData: spotify,
|
artistId,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (relatedArtists.isLoading || !relatedArtists.hasData) {
|
if (relatedArtists.isLoading || !relatedArtists.hasData) {
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import 'package:fl_query_hooks/fl_query_hooks.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
@ -9,8 +8,6 @@ import 'package:spotube/components/genre/category_card.dart';
|
|||||||
import 'package:spotube/components/shared/compact_search.dart';
|
import 'package:spotube/components/shared/compact_search.dart';
|
||||||
import 'package:spotube/components/shared/shimmers/shimmer_categories.dart';
|
import 'package:spotube/components/shared/shimmers/shimmer_categories.dart';
|
||||||
import 'package:spotube/components/shared/waypoint.dart';
|
import 'package:spotube/components/shared/waypoint.dart';
|
||||||
import 'package:spotube/provider/authentication_provider.dart';
|
|
||||||
import 'package:spotube/provider/spotify_provider.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/services/queries/queries.dart';
|
||||||
@ -22,43 +19,19 @@ class GenrePage extends HookConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
final scrollController = useScrollController();
|
final scrollController = useScrollController();
|
||||||
final spotify = ref.watch(spotifyProvider);
|
|
||||||
final recommendationMarket = ref.watch(
|
final recommendationMarket = ref.watch(
|
||||||
userPreferencesProvider.select((s) => s.recommendationMarket),
|
userPreferencesProvider.select((s) => s.recommendationMarket),
|
||||||
);
|
);
|
||||||
final categoriesQuery = useInfiniteQuery(
|
final categoriesQuery = useQueries.category.list(ref, recommendationMarket);
|
||||||
job: Queries.category.list,
|
|
||||||
externalData: {
|
|
||||||
"spotify": spotify,
|
|
||||||
"recommendationMarket": recommendationMarket,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
final isMounted = useIsMounted();
|
final isMounted = useIsMounted();
|
||||||
|
|
||||||
final auth = ref.watch(AuthenticationNotifier.provider);
|
|
||||||
|
|
||||||
/// Temporary fix before fl-query 0.4.0
|
|
||||||
useEffect(() {
|
|
||||||
if (auth != null && categoriesQuery.hasError) {
|
|
||||||
categoriesQuery.setExternalData({
|
|
||||||
"spotify": spotify,
|
|
||||||
"recommendationMarket": recommendationMarket,
|
|
||||||
});
|
|
||||||
categoriesQuery.refetchPages();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}, [auth, categoriesQuery.hasError]);
|
|
||||||
|
|
||||||
/// ===================================
|
|
||||||
|
|
||||||
return HookBuilder(builder: (context) {
|
|
||||||
final searchText = useState("");
|
final searchText = useState("");
|
||||||
final categories = useMemoized(
|
final categories = useMemoized(
|
||||||
() {
|
() {
|
||||||
final categories = categoriesQuery.pages
|
final categories = categoriesQuery.pages
|
||||||
.expand<Category>(
|
.expand<Category>(
|
||||||
(page) => page?.items ?? const Iterable.empty(),
|
(page) => page.items ?? const Iterable.empty(),
|
||||||
)
|
)
|
||||||
.toList();
|
.toList();
|
||||||
if (searchText.value.isEmpty) {
|
if (searchText.value.isEmpty) {
|
||||||
@ -86,12 +59,12 @@ class GenrePage extends HookConsumerWidget {
|
|||||||
|
|
||||||
final list = RefreshIndicator(
|
final list = RefreshIndicator(
|
||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
await categoriesQuery.refetchPages();
|
await categoriesQuery.refreshAll();
|
||||||
},
|
},
|
||||||
child: Waypoint(
|
child: Waypoint(
|
||||||
onTouchEdge: () async {
|
onTouchEdge: () async {
|
||||||
if (categoriesQuery.hasNextPage && isMounted()) {
|
if (categoriesQuery.hasNextPage && isMounted()) {
|
||||||
await categoriesQuery.fetchNextPage();
|
await categoriesQuery.fetchNext();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
controller: scrollController,
|
controller: scrollController,
|
||||||
@ -109,6 +82,7 @@ class GenrePage extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
return Stack(
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
Positioned.fill(child: list),
|
Positioned.fill(child: list),
|
||||||
@ -119,6 +93,5 @@ class GenrePage extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import 'package:fl_query_hooks/fl_query_hooks.dart';
|
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart' hide Page;
|
import 'package:flutter/material.dart' hide Page;
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
@ -10,7 +9,6 @@ import 'package:spotube/components/playlist/playlist_card.dart';
|
|||||||
import 'package:spotube/components/shared/shimmers/shimmer_playbutton_card.dart';
|
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/services/queries/queries.dart';
|
import 'package:spotube/services/queries/queries.dart';
|
||||||
import 'package:spotube/utils/type_conversion_utils.dart';
|
import 'package:spotube/utils/type_conversion_utils.dart';
|
||||||
|
|
||||||
@ -104,30 +102,9 @@ class PersonalizedPage extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
final spotify = ref.watch(spotifyProvider);
|
final featuredPlaylistsQuery = useQueries.playlist.featured(ref);
|
||||||
|
|
||||||
final featuredPlaylistsQuery = useInfiniteQuery(
|
final newReleases = useQueries.album.newReleases(ref);
|
||||||
job: Queries.playlist.featured,
|
|
||||||
externalData: spotify,
|
|
||||||
);
|
|
||||||
|
|
||||||
final newReleases = useInfiniteQuery(
|
|
||||||
job: Queries.album.newReleases,
|
|
||||||
externalData: spotify,
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() {
|
|
||||||
if (featuredPlaylistsQuery.hasError &&
|
|
||||||
featuredPlaylistsQuery.pages.first == null) {
|
|
||||||
featuredPlaylistsQuery.setExternalData(spotify);
|
|
||||||
featuredPlaylistsQuery.refetch();
|
|
||||||
}
|
|
||||||
if (newReleases.hasError && newReleases.pages.first == null) {
|
|
||||||
newReleases.setExternalData(spotify);
|
|
||||||
newReleases.refetch();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}, [spotify]);
|
|
||||||
|
|
||||||
return ListView(
|
return ListView(
|
||||||
children: [
|
children: [
|
||||||
@ -136,13 +113,13 @@ class PersonalizedPage extends HookConsumerWidget {
|
|||||||
featuredPlaylistsQuery.pages.whereType<Page<PlaylistSimple>>(),
|
featuredPlaylistsQuery.pages.whereType<Page<PlaylistSimple>>(),
|
||||||
title: 'Featured',
|
title: 'Featured',
|
||||||
hasNextPage: featuredPlaylistsQuery.hasNextPage,
|
hasNextPage: featuredPlaylistsQuery.hasNextPage,
|
||||||
onFetchMore: featuredPlaylistsQuery.fetchNextPage,
|
onFetchMore: featuredPlaylistsQuery.fetchNext,
|
||||||
),
|
),
|
||||||
PersonalizedItemCard(
|
PersonalizedItemCard(
|
||||||
albums: newReleases.pages.whereType<Page<AlbumSimple>>(),
|
albums: newReleases.pages.whereType<Page<AlbumSimple>>(),
|
||||||
title: 'New Releases',
|
title: 'New Releases',
|
||||||
hasNextPage: newReleases.hasNextPage,
|
hasNextPage: newReleases.hasNextPage,
|
||||||
onFetchMore: newReleases.fetchNextPage,
|
onFetchMore: newReleases.fetchNext,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import 'package:fl_query_hooks/fl_query_hooks.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:palette_generator/palette_generator.dart';
|
import 'package:palette_generator/palette_generator.dart';
|
||||||
@ -10,7 +9,6 @@ import 'package:spotube/provider/playlist_queue_provider.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/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';
|
|
||||||
|
|
||||||
class GeniusLyrics extends HookConsumerWidget {
|
class GeniusLyrics extends HookConsumerWidget {
|
||||||
final PaletteColor palette;
|
final PaletteColor palette;
|
||||||
@ -24,12 +22,9 @@ class GeniusLyrics extends HookConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
final playlist = ref.watch(PlaylistQueueNotifier.provider);
|
final playlist = ref.watch(PlaylistQueueNotifier.provider);
|
||||||
final geniusLyricsQuery = useQuery(
|
final geniusLyricsQuery = useQueries.lyrics.static(
|
||||||
job: Queries.lyrics.static(playlist?.activeTrack.id ?? ""),
|
|
||||||
externalData: Tuple2(
|
|
||||||
playlist?.activeTrack,
|
playlist?.activeTrack,
|
||||||
ref.watch(userPreferencesProvider).geniusAccessToken,
|
ref.watch(userPreferencesProvider).geniusAccessToken,
|
||||||
),
|
|
||||||
);
|
);
|
||||||
final breakpoint = useBreakpoints();
|
final breakpoint = useBreakpoints();
|
||||||
final textTheme = Theme.of(context).textTheme;
|
final textTheme = Theme.of(context).textTheme;
|
||||||
@ -42,8 +37,8 @@ class GeniusLyrics extends HookConsumerWidget {
|
|||||||
child: Text(
|
child: Text(
|
||||||
playlist?.activeTrack.name ?? "",
|
playlist?.activeTrack.name ?? "",
|
||||||
style: breakpoint >= Breakpoints.md
|
style: breakpoint >= Breakpoints.md
|
||||||
? textTheme.headline3
|
? textTheme.displaySmall
|
||||||
: textTheme.headline4?.copyWith(
|
: textTheme.headlineMedium?.copyWith(
|
||||||
fontSize: 25,
|
fontSize: 25,
|
||||||
color: palette.titleTextColor,
|
color: palette.titleTextColor,
|
||||||
),
|
),
|
||||||
@ -54,8 +49,8 @@ class GeniusLyrics extends HookConsumerWidget {
|
|||||||
TypeConversionUtils.artists_X_String<Artist>(
|
TypeConversionUtils.artists_X_String<Artist>(
|
||||||
playlist?.activeTrack.artists ?? []),
|
playlist?.activeTrack.artists ?? []),
|
||||||
style: (breakpoint >= Breakpoints.md
|
style: (breakpoint >= Breakpoints.md
|
||||||
? textTheme.headline5
|
? textTheme.headlineSmall
|
||||||
: textTheme.headline6)
|
: textTheme.titleLarge)
|
||||||
?.copyWith(color: palette.bodyTextColor),
|
?.copyWith(color: palette.bodyTextColor),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -68,12 +63,12 @@ class GeniusLyrics extends HookConsumerWidget {
|
|||||||
child: Builder(
|
child: Builder(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
if (geniusLyricsQuery.isLoading ||
|
if (geniusLyricsQuery.isLoading ||
|
||||||
geniusLyricsQuery.isRefetching) {
|
geniusLyricsQuery.isRefreshing) {
|
||||||
return const ShimmerLyrics();
|
return const ShimmerLyrics();
|
||||||
} else if (geniusLyricsQuery.hasError) {
|
} else if (geniusLyricsQuery.hasError) {
|
||||||
return Text(
|
return Text(
|
||||||
"Sorry, no Lyrics were found for `${playlist?.activeTrack.name}` :'(\n${geniusLyricsQuery.error.toString()}",
|
"Sorry, no Lyrics were found for `${playlist?.activeTrack.name}` :'(\n${geniusLyricsQuery.error.toString()}",
|
||||||
style: textTheme.bodyText1?.copyWith(
|
style: textTheme.bodyLarge?.copyWith(
|
||||||
color: palette.bodyTextColor,
|
color: palette.bodyTextColor,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -44,28 +44,7 @@ class SyncedLyrics extends HookConsumerWidget {
|
|||||||
final breakpoint = useBreakpoints();
|
final breakpoint = useBreakpoints();
|
||||||
final controller = useAutoScrollController();
|
final controller = useAutoScrollController();
|
||||||
|
|
||||||
final textTheme = Theme.of(context).textTheme;
|
final timedLyricsQuery = useQueries.lyrics.synced(playlist?.activeTrack);
|
||||||
|
|
||||||
useEffect(() {
|
|
||||||
controller.scrollToIndex(0);
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
||||||
ref.read(lyricDelayState.notifier).state = Duration.zero;
|
|
||||||
});
|
|
||||||
return null;
|
|
||||||
}, [playlist?.activeTrack]);
|
|
||||||
|
|
||||||
final headlineTextStyle = (breakpoint >= Breakpoints.md
|
|
||||||
? textTheme.headline3
|
|
||||||
: textTheme.headline4?.copyWith(fontSize: 25))
|
|
||||||
?.copyWith(color: palette.titleTextColor);
|
|
||||||
|
|
||||||
return QueryBuilder<SubtitleSimple, SpotubeTrack?>(
|
|
||||||
job: Queries.lyrics.synced(playlist?.activeTrack.id ?? ""),
|
|
||||||
externalData: playlist?.isLoading == true
|
|
||||||
? playlist?.activeTrack as SpotubeTrack
|
|
||||||
: null,
|
|
||||||
builder: (context, timedLyricsQuery) {
|
|
||||||
return HookBuilder(builder: (context) {
|
|
||||||
final lyricValue = timedLyricsQuery.data;
|
final lyricValue = timedLyricsQuery.data;
|
||||||
final lyricsMap = useMemoized(
|
final lyricsMap = useMemoized(
|
||||||
() =>
|
() =>
|
||||||
@ -77,6 +56,23 @@ class SyncedLyrics extends HookConsumerWidget {
|
|||||||
[lyricValue],
|
[lyricValue],
|
||||||
);
|
);
|
||||||
final currentTime = useSyncedLyrics(ref, lyricsMap, lyricDelay);
|
final currentTime = useSyncedLyrics(ref, lyricsMap, lyricDelay);
|
||||||
|
|
||||||
|
final textTheme = Theme.of(context).textTheme;
|
||||||
|
|
||||||
|
useEffect(() {
|
||||||
|
controller.scrollToIndex(0);
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
ref.read(lyricDelayState.notifier).state = Duration.zero;
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
}, [playlist?.activeTrack]);
|
||||||
|
|
||||||
|
final headlineTextStyle = (breakpoint >= Breakpoints.md
|
||||||
|
? textTheme.displaySmall
|
||||||
|
: textTheme.headlineMedium?.copyWith(fontSize: 25))
|
||||||
|
?.copyWith(color: palette.titleTextColor);
|
||||||
|
|
||||||
|
return HookBuilder(builder: (context) {
|
||||||
return Stack(
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
Column(
|
Column(
|
||||||
@ -95,8 +91,8 @@ class SyncedLyrics extends HookConsumerWidget {
|
|||||||
TypeConversionUtils.artists_X_String<Artist>(
|
TypeConversionUtils.artists_X_String<Artist>(
|
||||||
playlist?.activeTrack.artists ?? []),
|
playlist?.activeTrack.artists ?? []),
|
||||||
style: breakpoint >= Breakpoints.md
|
style: breakpoint >= Breakpoints.md
|
||||||
? textTheme.headline5
|
? textTheme.headlineSmall
|
||||||
: textTheme.headline6,
|
: textTheme.titleLarge,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (lyricValue != null && lyricValue.lyrics.isNotEmpty)
|
if (lyricValue != null && lyricValue.lyrics.isNotEmpty)
|
||||||
@ -106,8 +102,7 @@ class SyncedLyrics extends HookConsumerWidget {
|
|||||||
itemCount: lyricValue.lyrics.length,
|
itemCount: lyricValue.lyrics.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final lyricSlice = lyricValue.lyrics[index];
|
final lyricSlice = lyricValue.lyrics[index];
|
||||||
final isActive =
|
final isActive = lyricSlice.time.inSeconds == currentTime;
|
||||||
lyricSlice.time.inSeconds == currentTime;
|
|
||||||
|
|
||||||
if (isActive) {
|
if (isActive) {
|
||||||
controller.scrollToIndex(
|
controller.scrollToIndex(
|
||||||
@ -125,8 +120,7 @@ class SyncedLyrics extends HookConsumerWidget {
|
|||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: AnimatedDefaultTextStyle(
|
child: AnimatedDefaultTextStyle(
|
||||||
duration:
|
duration: const Duration(milliseconds: 250),
|
||||||
const Duration(milliseconds: 250),
|
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: isActive
|
color: isActive
|
||||||
? Colors.white
|
? Colors.white
|
||||||
@ -149,8 +143,7 @@ class SyncedLyrics extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (playlist?.activeTrack != null &&
|
if (playlist?.activeTrack != null &&
|
||||||
(lyricValue == null ||
|
(lyricValue == null || lyricValue.lyrics.isEmpty == true))
|
||||||
lyricValue.lyrics.isEmpty == true))
|
|
||||||
const Expanded(child: ShimmerLyrics()),
|
const Expanded(child: ShimmerLyrics()),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -179,6 +172,5 @@ class SyncedLyrics extends HookConsumerWidget {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import 'package:fl_query_hooks/fl_query_hooks.dart';
|
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.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';
|
||||||
@ -9,7 +8,6 @@ import 'package:spotube/hooks/use_breakpoints.dart';
|
|||||||
import 'package:spotube/models/logger.dart';
|
import 'package:spotube/models/logger.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/playlist_queue_provider.dart';
|
import 'package:spotube/provider/playlist_queue_provider.dart';
|
||||||
import 'package:spotube/services/queries/queries.dart';
|
import 'package:spotube/services/queries/queries.dart';
|
||||||
|
|
||||||
@ -46,15 +44,11 @@ class PlaylistView extends HookConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
final playlistNotifier = ref.watch(PlaylistQueueNotifier.notifier);
|
final playlistNotifier = ref.watch(PlaylistQueueNotifier.notifier);
|
||||||
SpotifyApi spotify = ref.watch(spotifyProvider);
|
|
||||||
|
|
||||||
final breakpoint = useBreakpoints();
|
final breakpoint = useBreakpoints();
|
||||||
|
|
||||||
final meSnapshot = useQuery(job: Queries.user.me, externalData: spotify);
|
final meSnapshot = useQueries.user.me(ref);
|
||||||
final tracksSnapshot = useQuery(
|
final tracksSnapshot = useQueries.playlist.tracksOfQuery(ref, playlist.id!);
|
||||||
job: Queries.playlist.tracksOf(playlist.id!),
|
|
||||||
externalData: spotify,
|
|
||||||
);
|
|
||||||
|
|
||||||
final isPlaylistPlaying =
|
final isPlaylistPlaying =
|
||||||
playlistNotifier.isPlayingPlaylist(tracksSnapshot.data ?? []);
|
playlistNotifier.isPlayingPlaylist(tracksSnapshot.data ?? []);
|
||||||
|
@ -4,13 +4,11 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
|||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:platform_ui/platform_ui.dart';
|
import 'package:platform_ui/platform_ui.dart';
|
||||||
import 'package:spotube/collections/side_bar_tiles.dart';
|
|
||||||
import 'package:spotube/components/shared/dialogs/replace_downloaded_dialog.dart';
|
import 'package:spotube/components/shared/dialogs/replace_downloaded_dialog.dart';
|
||||||
import 'package:spotube/components/root/bottom_player.dart';
|
import 'package:spotube/components/root/bottom_player.dart';
|
||||||
import 'package:spotube/components/root/sidebar.dart';
|
import 'package:spotube/components/root/sidebar.dart';
|
||||||
import 'package:spotube/components/root/spotube_navigation_bar.dart';
|
import 'package:spotube/components/root/spotube_navigation_bar.dart';
|
||||||
import 'package:spotube/components/shared/page_window_title_bar.dart';
|
import 'package:spotube/components/shared/page_window_title_bar.dart';
|
||||||
import 'package:spotube/hooks/use_breakpoints.dart';
|
|
||||||
import 'package:spotube/hooks/use_update_checker.dart';
|
import 'package:spotube/hooks/use_update_checker.dart';
|
||||||
import 'package:spotube/provider/downloader_provider.dart';
|
import 'package:spotube/provider/downloader_provider.dart';
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import 'package:fl_query_hooks/fl_query_hooks.dart';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart' hide Page;
|
import 'package:flutter/material.dart' hide Page;
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
@ -17,14 +18,12 @@ import 'package:spotube/components/artist/artist_card.dart';
|
|||||||
import 'package:spotube/components/playlist/playlist_card.dart';
|
import 'package:spotube/components/playlist/playlist_card.dart';
|
||||||
import 'package:spotube/hooks/use_breakpoints.dart';
|
import 'package:spotube/hooks/use_breakpoints.dart';
|
||||||
import 'package:spotube/provider/authentication_provider.dart';
|
import 'package:spotube/provider/authentication_provider.dart';
|
||||||
import 'package:spotube/provider/spotify_provider.dart';
|
|
||||||
import 'package:spotube/provider/playlist_queue_provider.dart';
|
import 'package:spotube/provider/playlist_queue_provider.dart';
|
||||||
import 'package:spotube/services/queries/queries.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';
|
||||||
import 'package:tuple/tuple.dart';
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
|
||||||
final searchTermStateProvider = StateProvider<String>((ref) => "");
|
final searchTermStateProvider = StateProvider<String>((ref) => "");
|
||||||
@ -37,49 +36,29 @@ class SearchPage extends HookConsumerWidget {
|
|||||||
ref.watch(AuthenticationNotifier.provider);
|
ref.watch(AuthenticationNotifier.provider);
|
||||||
final authenticationNotifier =
|
final authenticationNotifier =
|
||||||
ref.watch(AuthenticationNotifier.provider.notifier);
|
ref.watch(AuthenticationNotifier.provider.notifier);
|
||||||
final spotify = ref.watch(spotifyProvider);
|
|
||||||
final albumController = useScrollController();
|
final albumController = useScrollController();
|
||||||
final playlistController = useScrollController();
|
final playlistController = useScrollController();
|
||||||
final artistController = useScrollController();
|
final artistController = useScrollController();
|
||||||
final breakpoint = useBreakpoints();
|
final breakpoint = useBreakpoints();
|
||||||
|
|
||||||
final getVariables = useCallback(
|
final searchTerm = ref.watch(searchTermStateProvider);
|
||||||
() => Tuple2(
|
|
||||||
ref.read(searchTermStateProvider),
|
|
||||||
spotify,
|
|
||||||
),
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
final searchTrack = useInfiniteQuery(
|
final searchTrack =
|
||||||
job: Queries.search.get(SearchType.track.key),
|
useQueries.search.query(ref, searchTerm, SearchType.track);
|
||||||
externalData: Tuple2("", spotify),
|
final searchAlbum =
|
||||||
);
|
useQueries.search.query(ref, searchTerm, SearchType.album);
|
||||||
final searchAlbum = useInfiniteQuery(
|
final searchPlaylist =
|
||||||
job: Queries.search.get(SearchType.album.key),
|
useQueries.search.query(ref, searchTerm, SearchType.playlist);
|
||||||
externalData: Tuple2("", spotify),
|
final searchArtist =
|
||||||
);
|
useQueries.search.query(ref, searchTerm, SearchType.artist);
|
||||||
final searchPlaylist = useInfiniteQuery(
|
|
||||||
job: Queries.search.get(SearchType.playlist.key),
|
|
||||||
externalData: Tuple2("", spotify),
|
|
||||||
);
|
|
||||||
final searchArtist = useInfiniteQuery(
|
|
||||||
job: Queries.search.get(SearchType.artist.key),
|
|
||||||
externalData: Tuple2("", spotify),
|
|
||||||
);
|
|
||||||
|
|
||||||
void onSearch() {
|
Future<void> onSearch() async {
|
||||||
for (final query in [
|
await Future.wait([
|
||||||
searchTrack,
|
searchTrack.refreshAll(),
|
||||||
searchAlbum,
|
searchAlbum.refreshAll(),
|
||||||
searchPlaylist,
|
searchPlaylist.refreshAll(),
|
||||||
searchArtist,
|
searchArtist.refreshAll(),
|
||||||
]) {
|
]);
|
||||||
query.enabled = false;
|
|
||||||
query.fetched = false;
|
|
||||||
query.setExternalData(getVariables());
|
|
||||||
query.refetchPages();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return SafeArea(
|
return SafeArea(
|
||||||
@ -96,10 +75,6 @@ class SearchPage extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
color: PlatformTheme.of(context).scaffoldBackgroundColor,
|
color: PlatformTheme.of(context).scaffoldBackgroundColor,
|
||||||
child: PlatformTextField(
|
child: PlatformTextField(
|
||||||
onChanged: (value) {
|
|
||||||
ref.read(searchTermStateProvider.notifier).state =
|
|
||||||
value;
|
|
||||||
},
|
|
||||||
prefixIcon: SpotubeIcons.search,
|
prefixIcon: SpotubeIcons.search,
|
||||||
prefixIconColor: PlatformProperty.only(
|
prefixIconColor: PlatformProperty.only(
|
||||||
ios:
|
ios:
|
||||||
@ -107,8 +82,14 @@ class SearchPage extends HookConsumerWidget {
|
|||||||
other: null,
|
other: null,
|
||||||
).resolve(platform!),
|
).resolve(platform!),
|
||||||
placeholder: "Search...",
|
placeholder: "Search...",
|
||||||
onSubmitted: (value) {
|
onSubmitted: (value) async {
|
||||||
|
ref.read(searchTermStateProvider.notifier).state =
|
||||||
|
value;
|
||||||
|
// Fl-Query is too fast, so we need to delay the search
|
||||||
|
// to prevent spamming the API :)
|
||||||
|
Timer(const Duration(milliseconds: 50), () {
|
||||||
onSearch();
|
onSearch();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -127,7 +108,7 @@ class SearchPage extends HookConsumerWidget {
|
|||||||
...searchAlbum.pages,
|
...searchAlbum.pages,
|
||||||
...searchPlaylist.pages,
|
...searchPlaylist.pages,
|
||||||
...searchArtist.pages,
|
...searchArtist.pages,
|
||||||
].expand<Page>((page) => page ?? []).toList();
|
].expand<Page>((page) => page).toList();
|
||||||
for (MapEntry<int, Page> page in pages.asMap().entries) {
|
for (MapEntry<int, Page> page in pages.asMap().entries) {
|
||||||
for (var item in page.value.items ?? []) {
|
for (var item in page.value.items ?? []) {
|
||||||
if (item is AlbumSimple) {
|
if (item is AlbumSimple) {
|
||||||
@ -153,12 +134,10 @@ class SearchPage extends HookConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
if (tracks.isNotEmpty)
|
if (tracks.isNotEmpty)
|
||||||
PlatformText.headline("Songs"),
|
PlatformText.headline("Songs"),
|
||||||
if (searchTrack.isLoading &&
|
if (searchTrack.isLoadingPage)
|
||||||
!searchTrack.isFetchingNextPage)
|
|
||||||
const PlatformCircularProgressIndicator()
|
const PlatformCircularProgressIndicator()
|
||||||
else if (searchTrack.hasError)
|
else if (searchTrack.hasPageError)
|
||||||
PlatformText(searchTrack
|
PlatformText(searchTrack.errors.lastOrNull
|
||||||
.error?[searchTrack.pageParams.last]
|
|
||||||
?.toString() ??
|
?.toString() ??
|
||||||
"")
|
"")
|
||||||
else
|
else
|
||||||
@ -204,10 +183,10 @@ class SearchPage extends HookConsumerWidget {
|
|||||||
tracks.isNotEmpty)
|
tracks.isNotEmpty)
|
||||||
Center(
|
Center(
|
||||||
child: PlatformTextButton(
|
child: PlatformTextButton(
|
||||||
onPressed: searchTrack.isFetchingNextPage
|
onPressed: searchTrack.isRefreshingPage
|
||||||
? null
|
? null
|
||||||
: () => searchTrack.fetchNextPage(),
|
: () => searchTrack.fetchNext(),
|
||||||
child: searchTrack.isFetchingNextPage
|
child: searchTrack.isRefreshingPage
|
||||||
? const PlatformCircularProgressIndicator()
|
? const PlatformCircularProgressIndicator()
|
||||||
: const PlatformText("Load more"),
|
: const PlatformText("Load more"),
|
||||||
),
|
),
|
||||||
@ -231,7 +210,7 @@ class SearchPage extends HookConsumerWidget {
|
|||||||
controller: playlistController,
|
controller: playlistController,
|
||||||
child: Waypoint(
|
child: Waypoint(
|
||||||
onTouchEdge: () {
|
onTouchEdge: () {
|
||||||
searchPlaylist.fetchNextPage();
|
searchPlaylist.fetchNext();
|
||||||
},
|
},
|
||||||
controller: playlistController,
|
controller: playlistController,
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
@ -256,13 +235,11 @@ class SearchPage extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (searchPlaylist.isLoading &&
|
if (searchPlaylist.isLoadingPage)
|
||||||
!searchPlaylist.isFetchingNextPage)
|
|
||||||
const PlatformCircularProgressIndicator(),
|
const PlatformCircularProgressIndicator(),
|
||||||
if (searchPlaylist.hasError)
|
if (searchPlaylist.hasPageError)
|
||||||
PlatformText(
|
PlatformText(
|
||||||
searchPlaylist.error?[
|
searchPlaylist.errors.lastOrNull
|
||||||
searchPlaylist.pageParams.last]
|
|
||||||
?.toString() ??
|
?.toString() ??
|
||||||
"",
|
"",
|
||||||
),
|
),
|
||||||
@ -283,7 +260,7 @@ class SearchPage extends HookConsumerWidget {
|
|||||||
child: Waypoint(
|
child: Waypoint(
|
||||||
controller: artistController,
|
controller: artistController,
|
||||||
onTouchEdge: () {
|
onTouchEdge: () {
|
||||||
searchArtist.fetchNextPage();
|
searchArtist.fetchNext();
|
||||||
},
|
},
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
@ -311,13 +288,11 @@ class SearchPage extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (searchArtist.isLoading &&
|
if (searchArtist.isLoadingPage)
|
||||||
!searchArtist.isFetchingNextPage)
|
|
||||||
const PlatformCircularProgressIndicator(),
|
const PlatformCircularProgressIndicator(),
|
||||||
if (searchArtist.hasError)
|
if (searchArtist.hasPageError)
|
||||||
PlatformText(
|
PlatformText(
|
||||||
searchArtist.error?[
|
searchArtist.errors.lastOrNull
|
||||||
searchArtist.pageParams.last]
|
|
||||||
?.toString() ??
|
?.toString() ??
|
||||||
"",
|
"",
|
||||||
),
|
),
|
||||||
@ -338,7 +313,7 @@ class SearchPage extends HookConsumerWidget {
|
|||||||
child: Waypoint(
|
child: Waypoint(
|
||||||
controller: albumController,
|
controller: albumController,
|
||||||
onTouchEdge: () {
|
onTouchEdge: () {
|
||||||
searchAlbum.fetchNextPage();
|
searchAlbum.fetchNext();
|
||||||
},
|
},
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
@ -364,14 +339,11 @@ class SearchPage extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (searchAlbum.isLoading &&
|
if (searchAlbum.isLoadingPage)
|
||||||
!searchAlbum.isFetchingNextPage)
|
|
||||||
const PlatformCircularProgressIndicator(),
|
const PlatformCircularProgressIndicator(),
|
||||||
if (searchAlbum.hasError)
|
if (searchAlbum.hasPageError)
|
||||||
PlatformText(
|
PlatformText(
|
||||||
searchAlbum
|
searchAlbum.errors.lastOrNull?.toString() ??
|
||||||
.error?[searchAlbum.pageParams.last]
|
|
||||||
?.toString() ??
|
|
||||||
"",
|
"",
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -1,16 +1,18 @@
|
|||||||
import 'package:fl_query/fl_query.dart';
|
import 'package:fl_query/fl_query.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:spotube/hooks/use_spotify_mutation.dart';
|
||||||
|
|
||||||
class AlbumMutations {
|
class AlbumMutations {
|
||||||
final toggleFavorite =
|
const AlbumMutations();
|
||||||
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;
|
|
||||||
|
|
||||||
|
Mutation<bool, dynamic, bool> toggleFavorite(
|
||||||
|
WidgetRef ref,
|
||||||
|
String albumId, {
|
||||||
|
List<String>? refreshQueries,
|
||||||
|
}) {
|
||||||
|
return useSpotifyMutation<bool, dynamic, bool, dynamic>(
|
||||||
|
"toggle-album-like/$albumId",
|
||||||
|
(isLiked, spotify) async {
|
||||||
if (isLiked) {
|
if (isLiked) {
|
||||||
await spotify.me.removeAlbums([albumId]);
|
await spotify.me.removeAlbums([albumId]);
|
||||||
} else {
|
} else {
|
||||||
@ -18,5 +20,8 @@ class AlbumMutations {
|
|||||||
}
|
}
|
||||||
return !isLiked;
|
return !isLiked;
|
||||||
},
|
},
|
||||||
|
ref: ref,
|
||||||
|
refreshQueries: refreshQueries,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -2,8 +2,11 @@ import 'package:spotube/services/mutations/album.dart';
|
|||||||
import 'package:spotube/services/mutations/playlist.dart';
|
import 'package:spotube/services/mutations/playlist.dart';
|
||||||
import 'package:spotube/services/mutations/track.dart';
|
import 'package:spotube/services/mutations/track.dart';
|
||||||
|
|
||||||
abstract class Mutations {
|
class _UseMutations {
|
||||||
static final playlist = PlaylistMutations();
|
const _UseMutations._();
|
||||||
static final album = AlbumMutations();
|
final playlist = const PlaylistMutations();
|
||||||
static final track = TrackMutations();
|
final album = const AlbumMutations();
|
||||||
|
final track = const TrackMutations();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const useMutations = _UseMutations._();
|
||||||
|
@ -1,16 +1,18 @@
|
|||||||
import 'package:fl_query/fl_query.dart';
|
import 'package:fl_query/fl_query.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:spotube/hooks/use_spotify_mutation.dart';
|
||||||
|
|
||||||
class PlaylistMutations {
|
class PlaylistMutations {
|
||||||
final toggleFavorite =
|
const PlaylistMutations();
|
||||||
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;
|
|
||||||
|
|
||||||
|
Mutation<bool, dynamic, bool> toggleFavorite(
|
||||||
|
WidgetRef ref,
|
||||||
|
String playlistId, {
|
||||||
|
List<String>? refreshQueries,
|
||||||
|
}) {
|
||||||
|
return useSpotifyMutation<bool, dynamic, bool, dynamic>(
|
||||||
|
"toggle-playlist-like/$playlistId",
|
||||||
|
(isLiked, spotify) async {
|
||||||
if (isLiked) {
|
if (isLiked) {
|
||||||
await spotify.playlists.unfollowPlaylist(playlistId);
|
await spotify.playlists.unfollowPlaylist(playlistId);
|
||||||
} else {
|
} else {
|
||||||
@ -18,18 +20,23 @@ class PlaylistMutations {
|
|||||||
}
|
}
|
||||||
return !isLiked;
|
return !isLiked;
|
||||||
},
|
},
|
||||||
|
ref: ref,
|
||||||
|
refreshQueries: refreshQueries,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
final removeTrackOf =
|
Mutation<bool, dynamic, String> removeTrackOf(
|
||||||
MutationJob.withVariableKey<bool, Tuple2<SpotifyApi, String>>(
|
WidgetRef ref,
|
||||||
preMutationKey: "remove-track-from-playlist",
|
String playlistId,
|
||||||
task: (queryKey, externalData) async {
|
) {
|
||||||
final spotify = externalData.item1;
|
return useSpotifyMutation<bool, dynamic, String, dynamic>(
|
||||||
final playlistId = getVariable(queryKey);
|
"remove-track-from-playlist/$playlistId",
|
||||||
final trackId = externalData.item2;
|
(trackId, spotify) async {
|
||||||
|
|
||||||
await spotify.playlists.removeTracks([trackId], playlistId);
|
await spotify.playlists.removeTracks([trackId], playlistId);
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
ref: ref,
|
||||||
|
refreshQueries: ["playlist-tracks/$playlistId"],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -1,16 +1,20 @@
|
|||||||
import 'package:fl_query/fl_query.dart';
|
import 'package:fl_query/fl_query.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:spotube/hooks/use_spotify_mutation.dart';
|
||||||
|
|
||||||
class TrackMutations {
|
class TrackMutations {
|
||||||
final toggleFavorite =
|
const TrackMutations();
|
||||||
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;
|
|
||||||
|
|
||||||
|
Mutation<bool, dynamic, bool> toggleFavorite(
|
||||||
|
WidgetRef ref,
|
||||||
|
String trackId, {
|
||||||
|
MutationOnMutationFn<bool, bool>? onMutate,
|
||||||
|
MutationOnDataFn<bool, bool>? onData,
|
||||||
|
MutationOnErrorFn<dynamic, bool>? onError,
|
||||||
|
}) {
|
||||||
|
return useSpotifyMutation<bool, dynamic, bool, bool>(
|
||||||
|
'toggle-track-like/$trackId',
|
||||||
|
(isLiked, spotify) async {
|
||||||
if (isLiked) {
|
if (isLiked) {
|
||||||
await spotify.tracks.me.removeOne(trackId);
|
await spotify.tracks.me.removeOne(trackId);
|
||||||
} else {
|
} else {
|
||||||
@ -18,5 +22,11 @@ class TrackMutations {
|
|||||||
}
|
}
|
||||||
return !isLiked;
|
return !isLiked;
|
||||||
},
|
},
|
||||||
|
ref: ref,
|
||||||
|
onData: onData,
|
||||||
|
onMutate: onMutate,
|
||||||
|
refreshQueries: ["playlist-tracks/user-liked-tracks"],
|
||||||
|
onError: onError,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -1,37 +1,56 @@
|
|||||||
import 'package:catcher/catcher.dart';
|
import 'package:catcher/catcher.dart';
|
||||||
import 'package:fl_query/fl_query.dart';
|
import 'package:fl_query/fl_query.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
|
import 'package:spotube/hooks/use_spotify_infinite_query.dart';
|
||||||
|
import 'package:spotube/hooks/use_spotify_query.dart';
|
||||||
|
|
||||||
class AlbumQueries {
|
class AlbumQueries {
|
||||||
final ofMine = QueryJob<Iterable<AlbumSimple>, SpotifyApi>(
|
const AlbumQueries();
|
||||||
queryKey: "current-user-albums",
|
|
||||||
task: (_, spotify) {
|
Query<Iterable<AlbumSimple>, dynamic> ofMine(WidgetRef ref) {
|
||||||
|
return useSpotifyQuery<Iterable<AlbumSimple>, dynamic>(
|
||||||
|
"current-user-albums",
|
||||||
|
(spotify) {
|
||||||
return spotify.me.savedAlbums().all();
|
return spotify.me.savedAlbums().all();
|
||||||
},
|
},
|
||||||
|
ref: ref,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
final tracksOf = QueryJob.withVariableKey<List<TrackSimple>, SpotifyApi>(
|
Query<List<TrackSimple>, dynamic> tracksOf(
|
||||||
preQueryKey: "album-tracks",
|
WidgetRef ref,
|
||||||
task: (queryKey, spotify) {
|
String albumId,
|
||||||
final id = getVariable(queryKey);
|
) {
|
||||||
return spotify.albums.getTracks(id).all().then((value) => value.toList());
|
return useSpotifyQuery<List<TrackSimple>, dynamic>(
|
||||||
|
"album-tracks/$albumId",
|
||||||
|
(spotify) {
|
||||||
|
return spotify.albums
|
||||||
|
.getTracks(albumId)
|
||||||
|
.all()
|
||||||
|
.then((value) => value.toList());
|
||||||
},
|
},
|
||||||
|
ref: ref,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
final isSavedForMe =
|
Query<bool, dynamic> isSavedForMe(
|
||||||
QueryJob.withVariableKey<bool, SpotifyApi>(task: (queryKey, spotify) {
|
WidgetRef ref,
|
||||||
return spotify.me
|
String album,
|
||||||
.isSavedAlbums([getVariable(queryKey)]).then((value) => value.first);
|
) {
|
||||||
});
|
return useSpotifyQuery<bool, dynamic>(
|
||||||
|
"is-saved-for-me/$album",
|
||||||
|
(spotify) {
|
||||||
|
return spotify.me.isSavedAlbums([album]).then((value) => value.first);
|
||||||
|
},
|
||||||
|
ref: ref,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
final newReleases = InfiniteQueryJob<Page<AlbumSimple>, SpotifyApi, int>(
|
InfiniteQuery<Page<AlbumSimple>, dynamic, int> newReleases(WidgetRef ref) {
|
||||||
queryKey: "new-releases",
|
return useSpotifyInfiniteQuery<Page<AlbumSimple>, dynamic, int>(
|
||||||
initialParam: 0,
|
"new-releases",
|
||||||
getNextPageParam: (lastPage, lastParam) =>
|
(pageParam, spotify) async {
|
||||||
lastPage.items?.length == 5 ? lastPage.nextOffset : null,
|
|
||||||
getPreviousPageParam: (firstPage, firstParam) => firstPage.nextOffset - 6,
|
|
||||||
refetchOnExternalDataChange: true,
|
|
||||||
task: (_, pageParam, spotify) async {
|
|
||||||
try {
|
try {
|
||||||
final albums = await Pages(
|
final albums = await Pages(
|
||||||
spotify,
|
spotify,
|
||||||
@ -46,5 +65,14 @@ class AlbumQueries {
|
|||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
ref: ref,
|
||||||
|
initialPage: 0,
|
||||||
|
nextPage: (lastPage, lastPageData) {
|
||||||
|
if (lastPageData.isLast || (lastPageData.items ?? []).length < 5) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return lastPageData.nextOffset;
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -1,59 +1,103 @@
|
|||||||
import 'package:fl_query/fl_query.dart';
|
import 'package:fl_query/fl_query.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
|
import 'package:spotube/hooks/use_spotify_infinite_query.dart';
|
||||||
|
import 'package:spotube/hooks/use_spotify_query.dart';
|
||||||
|
|
||||||
class ArtistQueries {
|
class ArtistQueries {
|
||||||
final get = QueryJob.withVariableKey<Artist, SpotifyApi>(
|
const ArtistQueries();
|
||||||
preQueryKey: "artist-profile",
|
|
||||||
task: (queryKey, externalData) =>
|
|
||||||
externalData.artists.get(getVariable(queryKey)),
|
|
||||||
);
|
|
||||||
|
|
||||||
final followedByMe = InfiniteQueryJob<CursorPage<Artist>, SpotifyApi, String>(
|
Query<Artist, dynamic> get(
|
||||||
queryKey: "user-following-artists",
|
WidgetRef ref,
|
||||||
initialParam: "",
|
String artist,
|
||||||
getNextPageParam: (lastPage, lastParam) => lastPage.after,
|
) {
|
||||||
getPreviousPageParam: (lastPage, lastParam) =>
|
return useSpotifyQuery<Artist, dynamic>(
|
||||||
lastPage.metadata.previous ?? "",
|
"artist-profile/$artist",
|
||||||
task: (queryKey, pageKey, spotify) {
|
(spotify) => spotify.artists.get(artist),
|
||||||
return spotify.me.following(FollowingType.artist).getPage(15, pageKey);
|
ref: ref,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
InfiniteQuery<CursorPage<Artist>, dynamic, String> followedByMe(
|
||||||
|
WidgetRef ref) {
|
||||||
|
return useSpotifyInfiniteQuery<CursorPage<Artist>, dynamic, String>(
|
||||||
|
"user-following-artists",
|
||||||
|
(pageParam, spotify) async {
|
||||||
|
return spotify.me
|
||||||
|
.following(FollowingType.artist)
|
||||||
|
.getPage(15, pageParam);
|
||||||
},
|
},
|
||||||
|
initialPage: "",
|
||||||
|
nextPage: (lastPage, lastPageData) {
|
||||||
|
if (lastPageData.isLast || (lastPageData.items ?? []).length < 15) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return lastPageData.after;
|
||||||
|
},
|
||||||
|
ref: ref,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
final doIFollow = QueryJob.withVariableKey<bool, SpotifyApi>(
|
Query<bool, dynamic> doIFollow(
|
||||||
preQueryKey: "user-follows-artists-query",
|
WidgetRef ref,
|
||||||
task: (artistId, spotify) async {
|
String artist,
|
||||||
|
) {
|
||||||
|
return useSpotifyQuery<bool, dynamic>(
|
||||||
|
"user-follows-artists-query/$artist",
|
||||||
|
(spotify) async {
|
||||||
final result = await spotify.me.isFollowing(
|
final result = await spotify.me.isFollowing(
|
||||||
FollowingType.artist,
|
FollowingType.artist,
|
||||||
[getVariable(artistId)],
|
[artist],
|
||||||
);
|
);
|
||||||
return result.first;
|
return result.first;
|
||||||
},
|
},
|
||||||
);
|
ref: ref,
|
||||||
|
|
||||||
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));
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Query<Iterable<Track>, dynamic> topTracksOf(
|
||||||
|
WidgetRef ref,
|
||||||
|
String artist,
|
||||||
|
) {
|
||||||
|
return useSpotifyQuery<Iterable<Track>, dynamic>(
|
||||||
|
"artist-top-track-query/$artist",
|
||||||
|
(spotify) {
|
||||||
|
return spotify.artists.getTopTracks(artist, "US");
|
||||||
|
},
|
||||||
|
ref: ref,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
InfiniteQuery<Page<Album>, dynamic, int> albumsOf(
|
||||||
|
WidgetRef ref,
|
||||||
|
String artist,
|
||||||
|
) {
|
||||||
|
return useSpotifyInfiniteQuery<Page<Album>, dynamic, int>(
|
||||||
|
"artist-albums/$artist",
|
||||||
|
(pageParam, spotify) async {
|
||||||
|
return spotify.artists.albums(artist).getPage(5, pageParam);
|
||||||
|
},
|
||||||
|
initialPage: 0,
|
||||||
|
nextPage: (lastPage, lastPageData) {
|
||||||
|
if (lastPageData.isLast || (lastPageData.items ?? []).length < 5) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return lastPageData.nextOffset;
|
||||||
|
},
|
||||||
|
ref: ref,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Query<Iterable<Artist>, dynamic> relatedArtistsOf(
|
||||||
|
WidgetRef ref,
|
||||||
|
String artist,
|
||||||
|
) {
|
||||||
|
return useSpotifyQuery<Iterable<Artist>, dynamic>(
|
||||||
|
"artist-related-artist-query/$artist",
|
||||||
|
(spotify) {
|
||||||
|
return spotify.artists.getRelatedArtists(artist);
|
||||||
|
},
|
||||||
|
ref: ref,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,33 +1,65 @@
|
|||||||
import 'package:fl_query/fl_query.dart';
|
import 'package:fl_query/fl_query.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
|
import 'package:spotube/extensions/map.dart';
|
||||||
|
import 'package:spotube/extensions/page.dart';
|
||||||
|
import 'package:spotube/hooks/use_spotify_infinite_query.dart';
|
||||||
|
|
||||||
class CategoryQueries {
|
class CategoryQueries {
|
||||||
final list = InfiniteQueryJob<Page<Category>, Map<String, dynamic>, int>(
|
const CategoryQueries();
|
||||||
queryKey: "categories-query",
|
|
||||||
initialParam: 0,
|
InfiniteQuery<Page<Category>, dynamic, int> list(
|
||||||
getNextPageParam: (lastPage, lastParam) => lastPage.nextOffset,
|
WidgetRef ref, String recommendationMarket) {
|
||||||
getPreviousPageParam: (lastPage, lastParam) => lastPage.nextOffset - 16,
|
return useSpotifyInfiniteQuery<Page<Category>, dynamic, int>(
|
||||||
refetchOnExternalDataChange: true,
|
"category-playlists",
|
||||||
task: (queryKey, pageParam, data) async {
|
(pageParam, spotify) async {
|
||||||
final SpotifyApi spotify = data["spotify"] as SpotifyApi;
|
|
||||||
final String recommendationMarket = data["recommendationMarket"];
|
|
||||||
final categories = await spotify.categories
|
final categories = await spotify.categories
|
||||||
.list(country: recommendationMarket)
|
.list(country: recommendationMarket)
|
||||||
.getPage(15, pageParam);
|
.getPage(15, pageParam);
|
||||||
|
|
||||||
return categories;
|
return categories;
|
||||||
},
|
},
|
||||||
);
|
initialPage: 0,
|
||||||
|
nextPage: (lastPage, lastPageData) {
|
||||||
final playlistsOf =
|
if (lastPageData.isLast || (lastPageData.items ?? []).length < 15) {
|
||||||
InfiniteQueryJob.withVariableKey<Page<PlaylistSimple>, SpotifyApi, int>(
|
return null;
|
||||||
preQueryKey: "category-playlists",
|
}
|
||||||
initialParam: 0,
|
return lastPageData.nextOffset;
|
||||||
getNextPageParam: (lastPage, lastParam) => lastPage.nextOffset,
|
|
||||||
getPreviousPageParam: (lastPage, lastParam) => lastPage.nextOffset - 6,
|
|
||||||
task: (queryKey, pageKey, spotify) {
|
|
||||||
final id = getVariable(queryKey);
|
|
||||||
return spotify.playlists.getByCategoryId(id).getPage(5, pageKey);
|
|
||||||
},
|
},
|
||||||
|
jsonConfig: JsonConfig<Page<Category>>(
|
||||||
|
toJson: (page) => page.toJson(),
|
||||||
|
fromJson: (json) => PageJson.fromJson<Category>(
|
||||||
|
json,
|
||||||
|
(json) {
|
||||||
|
return Category.fromJson((json as Map).castKeyDeep<String>());
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ref: ref,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
InfiniteQuery<Page<PlaylistSimple>, dynamic, int> playlistsOf(
|
||||||
|
WidgetRef ref,
|
||||||
|
String category,
|
||||||
|
) {
|
||||||
|
return useSpotifyInfiniteQuery<Page<PlaylistSimple>, dynamic, int>(
|
||||||
|
"category-playlists/$category",
|
||||||
|
(pageParam, spotify) async {
|
||||||
|
final playlists = await spotify.playlists
|
||||||
|
.getByCategoryId(category)
|
||||||
|
.getPage(5, pageParam);
|
||||||
|
|
||||||
|
return playlists;
|
||||||
|
},
|
||||||
|
initialPage: 0,
|
||||||
|
nextPage: (lastPage, lastPageData) {
|
||||||
|
if (lastPageData.isLast || (lastPageData.items ?? []).length < 5) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return lastPageData.nextOffset;
|
||||||
|
},
|
||||||
|
ref: ref,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,24 +1,27 @@
|
|||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:fl_query/fl_query.dart';
|
import 'package:fl_query/fl_query.dart';
|
||||||
|
import 'package:fl_query_hooks/fl_query_hooks.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/models/lyrics.dart';
|
import 'package:spotube/models/lyrics.dart';
|
||||||
import 'package:spotube/models/spotube_track.dart';
|
import 'package:spotube/models/spotube_track.dart';
|
||||||
import 'package:spotube/utils/service_utils.dart';
|
import 'package:spotube/utils/service_utils.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
|
||||||
|
|
||||||
class LyricsQueries {
|
class LyricsQueries {
|
||||||
final static = QueryJob.withVariableKey<String, Tuple2<Track?, String>>(
|
const LyricsQueries();
|
||||||
preQueryKey: "genius-lyrics-query",
|
|
||||||
refetchOnExternalDataChange: true,
|
Query<String, dynamic> static(
|
||||||
task: (queryKey, externalData) async {
|
Track? track,
|
||||||
final currentTrack = externalData.item1;
|
String geniusAccessToken,
|
||||||
final geniusAccessToken = externalData.item2;
|
) {
|
||||||
if (currentTrack == null || getVariable(queryKey).isEmpty) {
|
return useQuery<String, dynamic>(
|
||||||
|
"genius-lyrics-query/${track?.id}",
|
||||||
|
() async {
|
||||||
|
if (track == null) {
|
||||||
return "“Give this player a track to play”\n- S'Challa";
|
return "“Give this player a track to play”\n- S'Challa";
|
||||||
}
|
}
|
||||||
final lyrics = await ServiceUtils.getLyrics(
|
final lyrics = await ServiceUtils.getLyrics(
|
||||||
currentTrack.name!,
|
track.name!,
|
||||||
currentTrack.artists?.map((s) => s.name).whereNotNull().toList() ?? [],
|
track.artists?.map((s) => s.name).whereNotNull().toList() ?? [],
|
||||||
apiKey: geniusAccessToken,
|
apiKey: geniusAccessToken,
|
||||||
optimizeQuery: true,
|
optimizeQuery: true,
|
||||||
);
|
);
|
||||||
@ -27,18 +30,22 @@ class LyricsQueries {
|
|||||||
return lyrics;
|
return lyrics;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
final synced = QueryJob.withVariableKey<SubtitleSimple, SpotubeTrack?>(
|
|
||||||
preQueryKey: "synced-lyrics",
|
|
||||||
task: (queryKey, currentTrack) async {
|
|
||||||
if (currentTrack == null || getVariable(queryKey).isEmpty) {
|
|
||||||
throw "No track currently";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final timedLyrics = await ServiceUtils.getTimedLyrics(currentTrack);
|
Query<SubtitleSimple, dynamic> synced(
|
||||||
|
Track? track,
|
||||||
|
) {
|
||||||
|
return useQuery<SubtitleSimple, dynamic>(
|
||||||
|
"synced-lyrics/${track?.id}}",
|
||||||
|
() async {
|
||||||
|
if (track == null || track is! SpotubeTrack) {
|
||||||
|
throw "No track currently";
|
||||||
|
}
|
||||||
|
final timedLyrics = await ServiceUtils.getTimedLyrics(track);
|
||||||
if (timedLyrics == null) throw Exception("Unable to find lyrics");
|
if (timedLyrics == null) throw Exception("Unable to find lyrics");
|
||||||
|
|
||||||
return timedLyrics;
|
return timedLyrics;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -1,48 +1,80 @@
|
|||||||
import 'package:catcher/catcher.dart';
|
import 'package:catcher/catcher.dart';
|
||||||
import 'package:fl_query/fl_query.dart';
|
import 'package:fl_query/fl_query.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
|
import 'package:spotube/extensions/map.dart';
|
||||||
|
import 'package:spotube/extensions/track.dart';
|
||||||
|
import 'package:spotube/hooks/use_spotify_infinite_query.dart';
|
||||||
|
import 'package:spotube/hooks/use_spotify_query.dart';
|
||||||
|
|
||||||
class PlaylistQueries {
|
class PlaylistQueries {
|
||||||
final doesUserFollow = QueryJob.withVariableKey<bool, SpotifyApi>(
|
const PlaylistQueries();
|
||||||
preQueryKey: "playlist-is-followed",
|
|
||||||
task: (queryKey, spotify) {
|
|
||||||
final idMap = getVariable(queryKey).split(":");
|
|
||||||
|
|
||||||
return spotify.playlists.followedBy(idMap.first, [idMap.last]).then(
|
Query<bool, dynamic> doesUserFollow(
|
||||||
(value) => value.first,
|
WidgetRef ref,
|
||||||
);
|
String playlistId,
|
||||||
|
String userId,
|
||||||
|
) {
|
||||||
|
return useSpotifyQuery<bool, dynamic>(
|
||||||
|
"playlist-is-followed/$playlistId/$userId",
|
||||||
|
(spotify) async {
|
||||||
|
final result = await spotify.playlists.followedBy(playlistId, [userId]);
|
||||||
|
return result.first;
|
||||||
},
|
},
|
||||||
|
ref: ref,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
final ofMine = QueryJob<Iterable<PlaylistSimple>, SpotifyApi>(
|
Query<Iterable<PlaylistSimple>, dynamic> ofMine(WidgetRef ref) {
|
||||||
queryKey: "current-user-playlists",
|
return useSpotifyQuery<Iterable<PlaylistSimple>, dynamic>(
|
||||||
task: (_, spotify) {
|
"current-user-playlists",
|
||||||
|
(spotify) {
|
||||||
return spotify.playlists.me.all();
|
return spotify.playlists.me.all();
|
||||||
},
|
},
|
||||||
|
ref: ref,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
final tracksOf = QueryJob.withVariableKey<List<Track>, SpotifyApi>(
|
Future<List<Track>> tracksOf(String playlistId, SpotifyApi spotify) {
|
||||||
preQueryKey: "playlist-tracks",
|
if (playlistId == "user-liked-tracks") {
|
||||||
task: (queryKey, spotify) {
|
return spotify.tracks.me.saved.all().then(
|
||||||
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(),
|
(tracks) => tracks.map((e) => e.track!).toList(),
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
|
return spotify.playlists.getTracksByPlaylistId(playlistId).all().then(
|
||||||
|
(value) => value.toList(),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
final featured = InfiniteQueryJob<Page<PlaylistSimple>, SpotifyApi, int>(
|
Query<List<Track>, dynamic> tracksOfQuery(
|
||||||
queryKey: "featured-playlists",
|
WidgetRef ref,
|
||||||
initialParam: 0,
|
String playlistId,
|
||||||
getNextPageParam: (lastPage, lastParam) =>
|
) {
|
||||||
lastPage.items?.length == 5 ? lastPage.nextOffset : null,
|
return useSpotifyQuery<List<Track>, dynamic>(
|
||||||
getPreviousPageParam: (firstPage, firstParam) => firstPage.nextOffset - 6,
|
"playlist-tracks/$playlistId",
|
||||||
refetchOnExternalDataChange: true,
|
(spotify) => tracksOf(playlistId, spotify),
|
||||||
task: (_, pageParam, spotify) async {
|
jsonConfig: playlistId == "user-liked-tracks"
|
||||||
|
? JsonConfig(
|
||||||
|
toJson: (tracks) => <String, dynamic>{
|
||||||
|
'tracks': tracks.map((e) => e.toJson()).toList()
|
||||||
|
},
|
||||||
|
fromJson: (json) => (json['tracks'] as List)
|
||||||
|
.map((e) => Track.fromJson(
|
||||||
|
(e as Map).castKeyDeep<String>(),
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
ref: ref,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
InfiniteQuery<Page<PlaylistSimple>, dynamic, int> featured(
|
||||||
|
WidgetRef ref,
|
||||||
|
) {
|
||||||
|
return useSpotifyInfiniteQuery<Page<PlaylistSimple>, dynamic, int>(
|
||||||
|
"featured-playlists",
|
||||||
|
(pageParam, spotify) async {
|
||||||
try {
|
try {
|
||||||
final playlists =
|
final playlists =
|
||||||
await spotify.playlists.featured.getPage(5, pageParam);
|
await spotify.playlists.featured.getPage(5, pageParam);
|
||||||
@ -52,5 +84,14 @@ class PlaylistQueries {
|
|||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
initialPage: 0,
|
||||||
|
nextPage: (lastPage, lastPageData) {
|
||||||
|
if (lastPageData.isLast || (lastPageData.items ?? []).length < 5) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return lastPageData.nextOffset;
|
||||||
|
},
|
||||||
|
ref: ref,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -6,12 +6,15 @@ import 'package:spotube/services/queries/playlist.dart';
|
|||||||
import 'package:spotube/services/queries/search.dart';
|
import 'package:spotube/services/queries/search.dart';
|
||||||
import 'package:spotube/services/queries/user.dart';
|
import 'package:spotube/services/queries/user.dart';
|
||||||
|
|
||||||
abstract class Queries {
|
class Queries {
|
||||||
static final album = AlbumQueries();
|
const Queries._();
|
||||||
static final artist = ArtistQueries();
|
final album = const AlbumQueries();
|
||||||
static final category = CategoryQueries();
|
final artist = const ArtistQueries();
|
||||||
static final lyrics = LyricsQueries();
|
final category = const CategoryQueries();
|
||||||
static final playlist = PlaylistQueries();
|
final lyrics = const LyricsQueries();
|
||||||
static final search = SearchQueries();
|
final playlist = const PlaylistQueries();
|
||||||
static final user = UserQueries();
|
final search = const SearchQueries();
|
||||||
|
final user = const UserQueries();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const useQueries = Queries._();
|
||||||
|
@ -1,28 +1,36 @@
|
|||||||
import 'package:fl_query/fl_query.dart';
|
import 'package:fl_query/fl_query.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:spotube/hooks/use_spotify_infinite_query.dart';
|
||||||
|
|
||||||
class SearchQueries {
|
class SearchQueries {
|
||||||
final get = InfiniteQueryJob.withVariableKey<List<Page>,
|
const SearchQueries();
|
||||||
Tuple2<String, SpotifyApi>, int>(
|
InfiniteQuery<List<Page>, dynamic, int> query(
|
||||||
preQueryKey: "search-query",
|
WidgetRef ref,
|
||||||
refetchOnExternalDataChange: true,
|
String query,
|
||||||
initialParam: 0,
|
SearchType searchType,
|
||||||
enabled: false,
|
) {
|
||||||
getNextPageParam: (lastPage, lastParam) =>
|
return useSpotifyInfiniteQuery<List<Page>, dynamic, int>(
|
||||||
lastPage.isNotEmpty && (lastPage.first.items?.length ?? 0) < 10
|
"search-query/${searchType.key}",
|
||||||
? null
|
(page, spotify) {
|
||||||
: lastParam + 10,
|
if (query.trim().isEmpty) return [];
|
||||||
getPreviousPageParam: (lastPage, lastParam) => lastParam - 10,
|
final queryString = query;
|
||||||
task: (queryKey, pageParam, variables) {
|
|
||||||
if (variables.item1.trim().isEmpty) return [];
|
|
||||||
final queryString = variables.item1;
|
|
||||||
final spotify = variables.item2;
|
|
||||||
final searchType = getVariable(queryKey);
|
|
||||||
return spotify.search.get(
|
return spotify.search.get(
|
||||||
queryString,
|
queryString,
|
||||||
types: [SearchType(searchType)],
|
types: [searchType],
|
||||||
).getPage(10, pageParam);
|
).getPage(10, page);
|
||||||
|
},
|
||||||
|
enabled: false,
|
||||||
|
ref: ref,
|
||||||
|
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;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
import 'package:fl_query/fl_query.dart';
|
import 'package:fl_query/fl_query.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
|
import 'package:spotube/hooks/use_spotify_query.dart';
|
||||||
import 'package:spotube/utils/type_conversion_utils.dart';
|
import 'package:spotube/utils/type_conversion_utils.dart';
|
||||||
|
|
||||||
class UserQueries {
|
class UserQueries {
|
||||||
final me = QueryJob<User, SpotifyApi>(
|
const UserQueries();
|
||||||
queryKey: "current-user",
|
Query<User, dynamic> me(WidgetRef ref) {
|
||||||
refetchOnExternalDataChange: true,
|
return useSpotifyQuery<User, dynamic>(
|
||||||
task: (_, spotify) async {
|
"current-user",
|
||||||
|
(spotify) async {
|
||||||
final me = await spotify.me.get();
|
final me = await spotify.me.get();
|
||||||
if (me.images == null || me.images?.isEmpty == true) {
|
if (me.images == null || me.images?.isEmpty == true) {
|
||||||
me.images = [
|
me.images = [
|
||||||
@ -21,5 +24,7 @@ class UserQueries {
|
|||||||
}
|
}
|
||||||
return me;
|
return me;
|
||||||
},
|
},
|
||||||
|
ref: ref,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -9,7 +9,6 @@ import audio_service
|
|||||||
import audio_session
|
import audio_session
|
||||||
import audioplayers_darwin
|
import audioplayers_darwin
|
||||||
import catcher
|
import catcher
|
||||||
import connectivity_plus_macos
|
|
||||||
import device_info_plus
|
import device_info_plus
|
||||||
import macos_ui
|
import macos_ui
|
||||||
import metadata_god
|
import metadata_god
|
||||||
@ -27,7 +26,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||||||
AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin"))
|
AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin"))
|
||||||
AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin"))
|
AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin"))
|
||||||
CatcherPlugin.register(with: registry.registrar(forPlugin: "CatcherPlugin"))
|
CatcherPlugin.register(with: registry.registrar(forPlugin: "CatcherPlugin"))
|
||||||
ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin"))
|
|
||||||
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
|
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
|
||||||
MacOSUiPlugin.register(with: registry.registrar(forPlugin: "MacOSUiPlugin"))
|
MacOSUiPlugin.register(with: registry.registrar(forPlugin: "MacOSUiPlugin"))
|
||||||
MetadataGodPlugin.register(with: registry.registrar(forPlugin: "MetadataGodPlugin"))
|
MetadataGodPlugin.register(with: registry.registrar(forPlugin: "MetadataGodPlugin"))
|
||||||
|
84
pubspec.lock
84
pubspec.lock
@ -394,54 +394,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.0"
|
version: "3.0.0"
|
||||||
connectivity_plus:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: connectivity_plus
|
|
||||||
sha256: "3f8fe4e504c2d33696dac671a54909743bc6a902a9bb0902306f7a2aed7e528e"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.3.9"
|
|
||||||
connectivity_plus_linux:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: connectivity_plus_linux
|
|
||||||
sha256: "3caf859d001f10407b8e48134c761483e4495ae38094ffcca97193f6c271f5e2"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.3.1"
|
|
||||||
connectivity_plus_macos:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: connectivity_plus_macos
|
|
||||||
sha256: "488d2de1e47e1224ad486e501b20b088686ba1f4ee9c4420ecbc3b9824f0b920"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.2.6"
|
|
||||||
connectivity_plus_platform_interface:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: connectivity_plus_platform_interface
|
|
||||||
sha256: cf1d1c28f4416f8c654d7dc3cd638ec586076255d407cef3ddbdaf178272a71a
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.2.4"
|
|
||||||
connectivity_plus_web:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: connectivity_plus_web
|
|
||||||
sha256: "81332be1b4baf8898fed17bb4fdef27abb7c6fd990bf98c54fd978478adf2f1a"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.2.5"
|
|
||||||
connectivity_plus_windows:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: connectivity_plus_windows
|
|
||||||
sha256: "535b0404b4d5605c4dd8453d67e5d6d2ea0dd36e3b477f50f31af51b0aeab9dd"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.2.2"
|
|
||||||
convert:
|
convert:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -581,18 +533,20 @@ packages:
|
|||||||
fl_query:
|
fl_query:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: fl_query
|
path: "packages/fl_query"
|
||||||
sha256: "9d55b025d672aaf27766923817a7b458b5fb78631c83e6ce958faaef8c9ac61d"
|
ref: new-architecture
|
||||||
url: "https://pub.dev"
|
resolved-ref: "0c819d4e11572d592b5334280b8b4f2657f21459"
|
||||||
source: hosted
|
url: "https://github.com/KRTirtho/fl-query.git"
|
||||||
|
source: git
|
||||||
version: "0.3.1"
|
version: "0.3.1"
|
||||||
fl_query_hooks:
|
fl_query_hooks:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: fl_query_hooks
|
path: "packages/fl_query_hooks"
|
||||||
sha256: "052b50587794ca6e0d0d4cb6591efcd91308c299a3779087632754a09282562e"
|
ref: new-architecture
|
||||||
url: "https://pub.dev"
|
resolved-ref: "0c819d4e11572d592b5334280b8b4f2657f21459"
|
||||||
source: hosted
|
url: "https://github.com/KRTirtho/fl-query.git"
|
||||||
|
source: git
|
||||||
version: "0.3.1"
|
version: "0.3.1"
|
||||||
fluent_ui:
|
fluent_ui:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
@ -691,10 +645,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_hooks
|
name: flutter_hooks
|
||||||
sha256: "2b202559a4ed3656bbb7aae9d8b335fb0037b23acc7ae3f377d1ba0b95c21aec"
|
sha256: "6a126f703b89499818d73305e4ce1e3de33b4ae1c5512e3b8eab4b986f46774c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.18.5+1"
|
version: "0.18.6"
|
||||||
flutter_inappwebview:
|
flutter_inappwebview:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -1062,14 +1016,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.4"
|
version: "1.0.4"
|
||||||
nm:
|
mutex:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: nm
|
name: mutex
|
||||||
sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254"
|
sha256: "03116a4e46282a671b46c12de649d72c0ed18188ffe12a8d0fc63e83f4ad88f4"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.5.0"
|
version: "3.0.1"
|
||||||
oauth2:
|
oauth2:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1776,10 +1730,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: window_manager
|
name: window_manager
|
||||||
sha256: "5a7bda81b8b8e5feb733d07f4bd174e64dfb412031aff5314a375118e42b498e"
|
sha256: "492806c69879f0d28e95472bbe5e8d5940ac8c6e99cc07052fe14946974555ba"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.8"
|
version: "0.3.1"
|
||||||
window_size:
|
window_size:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -1822,5 +1776,5 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "1.12.3"
|
version: "1.12.3"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=2.19.0 <4.0.0"
|
dart: ">=2.19.0 <3.0.0"
|
||||||
flutter: ">=3.7.0"
|
flutter: ">=3.7.0"
|
||||||
|
19
pubspec.yaml
19
pubspec.yaml
@ -27,8 +27,16 @@ dependencies:
|
|||||||
cupertino_icons: ^1.0.5
|
cupertino_icons: ^1.0.5
|
||||||
dbus: ^0.7.8
|
dbus: ^0.7.8
|
||||||
file_picker: ^5.2.2
|
file_picker: ^5.2.2
|
||||||
fl_query: ^0.3.1
|
fl_query:
|
||||||
fl_query_hooks: ^0.3.1
|
git:
|
||||||
|
url: https://github.com/KRTirtho/fl-query.git
|
||||||
|
path: packages/fl_query
|
||||||
|
ref: new-architecture
|
||||||
|
fl_query_hooks:
|
||||||
|
git:
|
||||||
|
url: https://github.com/KRTirtho/fl-query.git
|
||||||
|
path: packages/fl_query_hooks
|
||||||
|
ref: new-architecture
|
||||||
fluent_ui: ^4.3.0
|
fluent_ui: ^4.3.0
|
||||||
fluentui_system_icons: ^1.1.189
|
fluentui_system_icons: ^1.1.189
|
||||||
flutter:
|
flutter:
|
||||||
@ -77,7 +85,7 @@ dependencies:
|
|||||||
uuid: ^3.0.7
|
uuid: ^3.0.7
|
||||||
version: ^3.0.2
|
version: ^3.0.2
|
||||||
visibility_detector: ^0.3.3
|
visibility_detector: ^0.3.3
|
||||||
window_manager: 0.2.8
|
window_manager: ^0.3.1
|
||||||
window_size:
|
window_size:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/google/flutter-desktop-embedding.git
|
url: https://github.com/google/flutter-desktop-embedding.git
|
||||||
@ -97,6 +105,11 @@ dev_dependencies:
|
|||||||
|
|
||||||
dependency_overrides:
|
dependency_overrides:
|
||||||
package_info_plus: ^3.0.2
|
package_info_plus: ^3.0.2
|
||||||
|
fl_query:
|
||||||
|
git:
|
||||||
|
url: https://github.com/KRTirtho/fl-query.git
|
||||||
|
path: packages/fl_query
|
||||||
|
ref: new-architecture
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
|
|
||||||
#include <audioplayers_windows/audioplayers_windows_plugin.h>
|
#include <audioplayers_windows/audioplayers_windows_plugin.h>
|
||||||
#include <catcher/catcher_plugin.h>
|
#include <catcher/catcher_plugin.h>
|
||||||
#include <connectivity_plus_windows/connectivity_plus_windows_plugin.h>
|
|
||||||
#include <metadata_god/metadata_god_plugin_c_api.h>
|
#include <metadata_god/metadata_god_plugin_c_api.h>
|
||||||
#include <permission_handler_windows/permission_handler_windows_plugin.h>
|
#include <permission_handler_windows/permission_handler_windows_plugin.h>
|
||||||
#include <screen_retriever/screen_retriever_plugin.h>
|
#include <screen_retriever/screen_retriever_plugin.h>
|
||||||
@ -21,8 +20,6 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
|||||||
registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin"));
|
registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin"));
|
||||||
CatcherPluginRegisterWithRegistrar(
|
CatcherPluginRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("CatcherPlugin"));
|
registry->GetRegistrarForPlugin("CatcherPlugin"));
|
||||||
ConnectivityPlusWindowsPluginRegisterWithRegistrar(
|
|
||||||
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
|
|
||||||
MetadataGodPluginCApiRegisterWithRegistrar(
|
MetadataGodPluginCApiRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("MetadataGodPluginCApi"));
|
registry->GetRegistrarForPlugin("MetadataGodPluginCApi"));
|
||||||
PermissionHandlerWindowsPluginRegisterWithRegistrar(
|
PermissionHandlerWindowsPluginRegisterWithRegistrar(
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
audioplayers_windows
|
audioplayers_windows
|
||||||
catcher
|
catcher
|
||||||
connectivity_plus_windows
|
|
||||||
metadata_god
|
metadata_god
|
||||||
permission_handler_windows
|
permission_handler_windows
|
||||||
screen_retriever
|
screen_retriever
|
||||||
|
Loading…
Reference in New Issue
Block a user