feat: use providers in artist page

This commit is contained in:
Kingkor Roy Tirtho 2024-03-17 11:09:26 +06:00
parent 84cb8d7988
commit 786342912b
10 changed files with 132 additions and 108 deletions

View File

@ -12,7 +12,7 @@ import 'package:spotube/pages/artist/section/footer.dart';
import 'package:spotube/pages/artist/section/header.dart'; import 'package:spotube/pages/artist/section/header.dart';
import 'package:spotube/pages/artist/section/related_artists.dart'; import 'package:spotube/pages/artist/section/related_artists.dart';
import 'package:spotube/pages/artist/section/top_tracks.dart'; import 'package:spotube/pages/artist/section/top_tracks.dart';
import 'package:spotube/services/queries/queries.dart'; import 'package:spotube/provider/spotify/spotify.dart';
class ArtistPage extends HookConsumerWidget { class ArtistPage extends HookConsumerWidget {
final String artistId; final String artistId;
@ -24,7 +24,7 @@ class ArtistPage extends HookConsumerWidget {
final scrollController = useScrollController(); final scrollController = useScrollController();
final theme = Theme.of(context); final theme = Theme.of(context);
final artistQuery = useQueries.artist.get(ref, artistId); final artistQuery = ref.watch(artistProvider(artistId));
return SafeArea( return SafeArea(
bottom: false, bottom: false,
@ -35,11 +35,11 @@ class ArtistPage extends HookConsumerWidget {
), ),
extendBodyBehindAppBar: true, extendBodyBehindAppBar: true,
body: Builder(builder: (context) { body: Builder(builder: (context) {
if (artistQuery.hasError && artistQuery.data == null) { if (artistQuery.hasError && artistQuery.value == null) {
return Center(child: Text(artistQuery.error.toString())); return Center(child: Text(artistQuery.error.toString()));
} }
return Skeletonizer( return Skeletonizer(
enabled: artistQuery.isLoading, enabled: artistQuery.isLoadingAndEmpty,
child: CustomScrollView( child: CustomScrollView(
controller: scrollController, controller: scrollController,
slivers: [ slivers: [
@ -66,11 +66,11 @@ class ArtistPage extends HookConsumerWidget {
SliverSafeArea( SliverSafeArea(
sliver: ArtistPageRelatedArtists(artistId: artistId), sliver: ArtistPageRelatedArtists(artistId: artistId),
), ),
if (artistQuery.data != null) if (artistQuery.value != null)
SliverSafeArea( SliverSafeArea(
top: false, top: false,
sliver: SliverToBoxAdapter( sliver: SliverToBoxAdapter(
child: ArtistPageFooter(artist: artistQuery.data!), child: ArtistPageFooter(artist: artistQuery.value!),
), ),
), ),
], ],

View File

@ -5,11 +5,11 @@ import 'package:spotify/spotify.dart';
import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/components/shared/image/universal_image.dart'; import 'package:spotube/components/shared/image/universal_image.dart';
import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/constrains.dart';
import 'package:spotube/services/queries/queries.dart'; import 'package:spotube/provider/spotify/spotify.dart';
import 'package:spotube/utils/type_conversion_utils.dart'; import 'package:spotube/utils/type_conversion_utils.dart';
import 'package:url_launcher/url_launcher_string.dart'; import 'package:url_launcher/url_launcher_string.dart';
class ArtistPageFooter extends HookConsumerWidget { class ArtistPageFooter extends ConsumerWidget {
final Artist artist; final Artist artist;
const ArtistPageFooter({super.key, required this.artist}); const ArtistPageFooter({super.key, required this.artist});
@ -22,8 +22,9 @@ class ArtistPageFooter extends HookConsumerWidget {
artist.images, artist.images,
placeholder: ImagePlaceholder.artist, placeholder: ImagePlaceholder.artist,
); );
final summary = useQueries.artist.wikipediaSummary(artist); final summary = ref.watch(artistWikipediaSummaryProvider(artist));
if (summary.hasError || !summary.hasData) return const SizedBox.shrink(); if (summary.value == null) return const SizedBox.shrink();
return Container( return Container(
margin: const EdgeInsets.all(16), margin: const EdgeInsets.all(16),
padding: mediaQuery.smAndDown padding: mediaQuery.smAndDown
@ -38,9 +39,9 @@ class ArtistPageFooter extends HookConsumerWidget {
BlendMode.darken, BlendMode.darken,
), ),
image: UniversalImage.imageProvider( image: UniversalImage.imageProvider(
summary.data!.thumbnail?.source_ ?? artistImage, summary.value!.thumbnail?.source_ ?? artistImage,
height: summary.data!.thumbnail?.height.toDouble(), height: summary.value!.thumbnail?.height.toDouble(),
width: summary.data!.thumbnail?.width.toDouble(), width: summary.value!.thumbnail?.width.toDouble(),
), ),
fit: BoxFit.cover, fit: BoxFit.cover,
alignment: Alignment.center, alignment: Alignment.center,
@ -69,7 +70,7 @@ class ArtistPageFooter extends HookConsumerWidget {
), ),
const TextSpan(text: '\n\n'), const TextSpan(text: '\n\n'),
TextSpan( TextSpan(
text: summary.data!.extract, text: summary.value!.extract,
), ),
TextSpan( TextSpan(
text: '\n...read more at wikipedia', text: '\n...read more at wikipedia',
@ -81,7 +82,7 @@ class ArtistPageFooter extends HookConsumerWidget {
recognizer: TapGestureRecognizer() recognizer: TapGestureRecognizer()
..onTap = () async { ..onTap = () async {
await launchUrlString( await launchUrlString(
"http://en.wikipedia.org/wiki?curid=${summary.data?.pageid}", "http://en.wikipedia.org/wiki?curid=${summary.value?.pageid}",
); );
}, },
), ),

View File

@ -1,11 +1,8 @@
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:gap/gap.dart'; import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:skeletonizer/skeletonizer.dart'; import 'package:skeletonizer/skeletonizer.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/collections/fake.dart'; import 'package:spotube/collections/fake.dart';
import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/components/shared/image/universal_image.dart'; import 'package:spotube/components/shared/image/universal_image.dart';
@ -14,8 +11,7 @@ import 'package:spotube/extensions/context.dart';
import 'package:spotube/hooks/utils/use_breakpoint_value.dart'; import 'package:spotube/hooks/utils/use_breakpoint_value.dart';
import 'package:spotube/provider/authentication_provider.dart'; import 'package:spotube/provider/authentication_provider.dart';
import 'package:spotube/provider/blacklist_provider.dart'; import 'package:spotube/provider/blacklist_provider.dart';
import 'package:spotube/provider/spotify_provider.dart'; import 'package:spotube/provider/spotify/spotify.dart';
import 'package:spotube/services/queries/queries.dart';
import 'package:spotube/utils/primitive_utils.dart'; import 'package:spotube/utils/primitive_utils.dart';
import 'package:spotube/utils/type_conversion_utils.dart'; import 'package:spotube/utils/type_conversion_utils.dart';
@ -25,9 +21,8 @@ class ArtistPageHeader extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, ref) { Widget build(BuildContext context, ref) {
final queryClient = useQueryClient(); final artistQuery = ref.watch(artistProvider(artistId));
final artistQuery = useQueries.artist.get(ref, artistId); final artist = artistQuery.value ?? FakeData.artist;
final artist = artistQuery.data ?? FakeData.artist;
final scaffoldMessenger = ScaffoldMessenger.of(context); final scaffoldMessenger = ScaffoldMessenger.of(context);
final mediaQuery = MediaQuery.of(context); final mediaQuery = MediaQuery.of(context);
@ -43,7 +38,6 @@ class ArtistPageHeader extends HookConsumerWidget {
xxl: textTheme.titleMedium, xxl: textTheme.titleMedium,
); );
final spotify = ref.read(spotifyProvider);
final auth = ref.watch(AuthenticationNotifier.provider); final auth = ref.watch(AuthenticationNotifier.provider);
final blacklist = ref.watch(BlackListNotifier.provider); final blacklist = ref.watch(BlackListNotifier.provider);
final isBlackListed = blacklist.contains( final isBlackListed = blacklist.contains(
@ -143,53 +137,41 @@ class ArtistPageHeader extends HookConsumerWidget {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
if (auth != null) if (auth != null)
HookBuilder( Consumer(
builder: (context) { builder: (context, ref, _) {
final isFollowingQuery = final isFollowingQuery = ref
useQueries.artist.doIFollow(ref, artistId); .watch(artistIsFollowingProvider(artist.id!));
final followingArtistNotifier =
ref.watch(followedArtistsProvider.notifier);
final followUnfollow = useCallback(() async { return switch (isFollowingQuery) {
try { AsyncData(value: final following) => Builder(
isFollowingQuery.data! builder: (context) {
? await spotify.me.unfollow( if (following) {
FollowingType.artist, return OutlinedButton(
[artistId], onPressed: () async {
) await followingArtistNotifier
: await spotify.me.follow( .removeArtists([artist.id!]);
FollowingType.artist, },
[artistId], child: Text(context.l10n.following),
); );
await isFollowingQuery.refresh(); }
queryClient.refreshInfiniteQueryAllPages( return FilledButton(
"user-following-artists"); onPressed: () async {
} finally { await followingArtistNotifier
queryClient.refreshQuery( .saveArtists([artist.id!]);
"user-follows-artists-query/$artistId", },
); child: Text(context.l10n.follow),
} );
}, [isFollowingQuery]); },
),
if (isFollowingQuery.isLoading || AsyncError() => const SizedBox(),
!isFollowingQuery.hasData) { _ => const SizedBox.square(
return const SizedBox( dimension: 20,
height: 20, child: CircularProgressIndicator(),
width: 20, )
child: CircularProgressIndicator(), };
);
}
if (isFollowingQuery.data!) {
return OutlinedButton(
onPressed: followUnfollow,
child: Text(context.l10n.following),
);
}
return FilledButton(
onPressed: followUnfollow,
child: Text(context.l10n.follow),
);
}, },
), ),
const SizedBox(width: 5), const SizedBox(width: 5),

View File

@ -1,9 +1,9 @@
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:spotube/components/artist/artist_card.dart'; import 'package:spotube/components/artist/artist_card.dart';
import 'package:spotube/services/queries/queries.dart'; import 'package:spotube/provider/spotify/spotify.dart';
class ArtistPageRelatedArtists extends HookConsumerWidget { class ArtistPageRelatedArtists extends ConsumerWidget {
final String artistId; final String artistId;
const ArtistPageRelatedArtists({ const ArtistPageRelatedArtists({
super.key, super.key,
@ -12,38 +12,34 @@ class ArtistPageRelatedArtists extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, ref) { Widget build(BuildContext context, ref) {
final relatedArtists = useQueries.artist.relatedArtistsOf( final relatedArtists = ref.watch(relatedArtistsProvider(artistId));
ref,
artistId,
);
if (relatedArtists.isLoading || !relatedArtists.hasData) { return switch (relatedArtists) {
return const SliverToBoxAdapter( AsyncData(value: final artists) => SliverPadding(
child: Center(child: CircularProgressIndicator())); padding: const EdgeInsets.symmetric(horizontal: 8.0),
} else if (relatedArtists.hasError) { sliver: SliverGrid.builder(
return SliverToBoxAdapter( itemCount: artists.length,
child: Center( gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
child: Text(relatedArtists.error.toString()), maxCrossAxisExtent: 200,
mainAxisExtent: 250,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
childAspectRatio: 0.8,
),
itemBuilder: (context, index) {
final artist = artists.elementAt(index);
return ArtistCard(artist);
},
),
), ),
); AsyncError(:final error) => SliverToBoxAdapter(
} child: Center(
child: Text(error.toString()),
return SliverPadding( ),
padding: const EdgeInsets.symmetric(horizontal: 8.0),
sliver: SliverGrid.builder(
itemCount: relatedArtists.data!.length,
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 200,
mainAxisExtent: 250,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
childAspectRatio: 0.8,
), ),
itemBuilder: (context, index) { _ => const SliverToBoxAdapter(
final artist = relatedArtists.data!.elementAt(index); child: Center(child: CircularProgressIndicator()),
return ArtistCard(artist); ),
}, };
),
);
} }
} }

View File

@ -7,7 +7,7 @@ import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/components/shared/track_tile/track_tile.dart'; import 'package:spotube/components/shared/track_tile/track_tile.dart';
import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/context.dart';
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
import 'package:spotube/services/queries/queries.dart'; import 'package:spotube/provider/spotify/spotify.dart';
class ArtistPageTopTracks extends HookConsumerWidget { class ArtistPageTopTracks extends HookConsumerWidget {
final String artistId; final String artistId;
@ -20,13 +20,10 @@ class ArtistPageTopTracks extends HookConsumerWidget {
final playlist = ref.watch(ProxyPlaylistNotifier.provider); final playlist = ref.watch(ProxyPlaylistNotifier.provider);
final playlistNotifier = ref.watch(ProxyPlaylistNotifier.notifier); final playlistNotifier = ref.watch(ProxyPlaylistNotifier.notifier);
final topTracksQuery = useQueries.artist.topTracksOf( final topTracksQuery = ref.watch(artistTopTracksProvider(artistId));
ref,
artistId,
);
final isPlaylistPlaying = playlist.containsTracks( final isPlaylistPlaying = playlist.containsTracks(
topTracksQuery.data ?? <Track>[], topTracksQuery.value ?? <Track>[],
); );
if (topTracksQuery.hasError) { if (topTracksQuery.hasError) {
@ -38,7 +35,7 @@ class ArtistPageTopTracks extends HookConsumerWidget {
} }
final topTracks = final topTracks =
topTracksQuery.data ?? List.generate(10, (index) => FakeData.track); topTracksQuery.value ?? List.generate(10, (index) => FakeData.track);
void playPlaylist(List<Track> tracks, {Track? currentTrack}) async { void playPlaylist(List<Track> tracks, {Track? currentTrack}) async {
currentTrack ??= tracks.first; currentTrack ??= tracks.first;

View File

@ -64,6 +64,29 @@ class FollowedArtistsNotifier
], ],
); );
}); });
for (final id in artistIds) {
ref.invalidate(artistIsFollowingProvider(id));
}
}
Future<void> removeArtists(List<String> artistIds) async {
if (state.value == null) return;
await spotify.me.unfollow(FollowingType.artist, artistIds);
state = await AsyncValue.guard(() async {
final artists = state.value!.items.where((artist) {
return !artistIds.contains(artist.id);
}).toList();
return state.value!.copyWith(
items: artists,
);
});
for (final id in artistIds) {
ref.invalidate(artistIsFollowingProvider(id));
}
} }
} }

View File

@ -0,0 +1,9 @@
part of '../spotify.dart';
final relatedArtistsProvider =
FutureProvider.family<List<Artist>, String>((ref, artistId) async {
final spotify = ref.watch(spotifyProvider);
final artists = await spotify.artists.relatedArtists(artistId);
return artists.toList();
});

View File

@ -1,6 +1,6 @@
part of '../spotify.dart'; part of '../spotify.dart';
final artistTopTracksProvider = FutureProviderFamily<List<TrackSimple>, String>( final artistTopTracksProvider = FutureProviderFamily<List<Track>, String>(
(ref, artistId) async { (ref, artistId) async {
final spotify = ref.watch(spotifyProvider); final spotify = ref.watch(spotifyProvider);
final market = ref final market = ref

View File

@ -0,0 +1,12 @@
part of '../spotify.dart';
final artistWikipediaSummaryProvider = FutureProvider.autoDispose
.family<Summary?, ArtistSimple>((ref, artist) async {
final query = artist.name!.replaceAll(" ", "_");
final res = await wikipedia.pageContent.pageSummaryTitleGet(query);
if (res?.type != "standard") {
return await wikipedia.pageContent.pageSummaryTitleGet("${query}_(singer)");
}
return res;
});

View File

@ -18,9 +18,11 @@ import 'package:spotube/models/spotify_friends.dart';
import 'package:spotube/provider/custom_spotify_endpoint_provider.dart'; import 'package:spotube/provider/custom_spotify_endpoint_provider.dart';
import 'package:spotube/provider/spotify_provider.dart'; import 'package:spotube/provider/spotify_provider.dart';
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
import 'package:spotube/services/wikipedia/wikipedia.dart';
import 'package:spotube/utils/persisted_state_notifier.dart'; import 'package:spotube/utils/persisted_state_notifier.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:spotube/utils/type_conversion_utils.dart'; import 'package:spotube/utils/type_conversion_utils.dart';
import 'package:wikipedia_api/wikipedia_api.dart';
part 'album/favorite.dart'; part 'album/favorite.dart';
part 'album/tracks.dart'; part 'album/tracks.dart';
@ -32,6 +34,8 @@ part 'artist/is_following.dart';
part 'artist/following.dart'; part 'artist/following.dart';
part 'artist/top_tracks.dart'; part 'artist/top_tracks.dart';
part 'artist/albums.dart'; part 'artist/albums.dart';
part 'artist/wikipedia.dart';
part 'artist/related.dart';
part 'category/genres.dart'; part 'category/genres.dart';
part 'category/categories.dart'; part 'category/categories.dart';