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/related_artists.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 {
final String artistId;
@ -24,7 +24,7 @@ class ArtistPage extends HookConsumerWidget {
final scrollController = useScrollController();
final theme = Theme.of(context);
final artistQuery = useQueries.artist.get(ref, artistId);
final artistQuery = ref.watch(artistProvider(artistId));
return SafeArea(
bottom: false,
@ -35,11 +35,11 @@ class ArtistPage extends HookConsumerWidget {
),
extendBodyBehindAppBar: true,
body: Builder(builder: (context) {
if (artistQuery.hasError && artistQuery.data == null) {
if (artistQuery.hasError && artistQuery.value == null) {
return Center(child: Text(artistQuery.error.toString()));
}
return Skeletonizer(
enabled: artistQuery.isLoading,
enabled: artistQuery.isLoadingAndEmpty,
child: CustomScrollView(
controller: scrollController,
slivers: [
@ -66,11 +66,11 @@ class ArtistPage extends HookConsumerWidget {
SliverSafeArea(
sliver: ArtistPageRelatedArtists(artistId: artistId),
),
if (artistQuery.data != null)
if (artistQuery.value != null)
SliverSafeArea(
top: false,
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/components/shared/image/universal_image.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:url_launcher/url_launcher_string.dart';
class ArtistPageFooter extends HookConsumerWidget {
class ArtistPageFooter extends ConsumerWidget {
final Artist artist;
const ArtistPageFooter({super.key, required this.artist});
@ -22,8 +22,9 @@ class ArtistPageFooter extends HookConsumerWidget {
artist.images,
placeholder: ImagePlaceholder.artist,
);
final summary = useQueries.artist.wikipediaSummary(artist);
if (summary.hasError || !summary.hasData) return const SizedBox.shrink();
final summary = ref.watch(artistWikipediaSummaryProvider(artist));
if (summary.value == null) return const SizedBox.shrink();
return Container(
margin: const EdgeInsets.all(16),
padding: mediaQuery.smAndDown
@ -38,9 +39,9 @@ class ArtistPageFooter extends HookConsumerWidget {
BlendMode.darken,
),
image: UniversalImage.imageProvider(
summary.data!.thumbnail?.source_ ?? artistImage,
height: summary.data!.thumbnail?.height.toDouble(),
width: summary.data!.thumbnail?.width.toDouble(),
summary.value!.thumbnail?.source_ ?? artistImage,
height: summary.value!.thumbnail?.height.toDouble(),
width: summary.value!.thumbnail?.width.toDouble(),
),
fit: BoxFit.cover,
alignment: Alignment.center,
@ -69,7 +70,7 @@ class ArtistPageFooter extends HookConsumerWidget {
),
const TextSpan(text: '\n\n'),
TextSpan(
text: summary.data!.extract,
text: summary.value!.extract,
),
TextSpan(
text: '\n...read more at wikipedia',
@ -81,7 +82,7 @@ class ArtistPageFooter extends HookConsumerWidget {
recognizer: TapGestureRecognizer()
..onTap = () async {
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/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:skeletonizer/skeletonizer.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/collections/fake.dart';
import 'package:spotube/collections/spotube_icons.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/provider/authentication_provider.dart';
import 'package:spotube/provider/blacklist_provider.dart';
import 'package:spotube/provider/spotify_provider.dart';
import 'package:spotube/services/queries/queries.dart';
import 'package:spotube/provider/spotify/spotify.dart';
import 'package:spotube/utils/primitive_utils.dart';
import 'package:spotube/utils/type_conversion_utils.dart';
@ -25,9 +21,8 @@ class ArtistPageHeader extends HookConsumerWidget {
@override
Widget build(BuildContext context, ref) {
final queryClient = useQueryClient();
final artistQuery = useQueries.artist.get(ref, artistId);
final artist = artistQuery.data ?? FakeData.artist;
final artistQuery = ref.watch(artistProvider(artistId));
final artist = artistQuery.value ?? FakeData.artist;
final scaffoldMessenger = ScaffoldMessenger.of(context);
final mediaQuery = MediaQuery.of(context);
@ -43,7 +38,6 @@ class ArtistPageHeader extends HookConsumerWidget {
xxl: textTheme.titleMedium,
);
final spotify = ref.read(spotifyProvider);
final auth = ref.watch(AuthenticationNotifier.provider);
final blacklist = ref.watch(BlackListNotifier.provider);
final isBlackListed = blacklist.contains(
@ -143,53 +137,41 @@ class ArtistPageHeader extends HookConsumerWidget {
mainAxisSize: MainAxisSize.min,
children: [
if (auth != null)
HookBuilder(
builder: (context) {
final isFollowingQuery =
useQueries.artist.doIFollow(ref, artistId);
Consumer(
builder: (context, ref, _) {
final isFollowingQuery = ref
.watch(artistIsFollowingProvider(artist.id!));
final followingArtistNotifier =
ref.watch(followedArtistsProvider.notifier);
final followUnfollow = useCallback(() async {
try {
isFollowingQuery.data!
? await spotify.me.unfollow(
FollowingType.artist,
[artistId],
)
: await spotify.me.follow(
FollowingType.artist,
[artistId],
return switch (isFollowingQuery) {
AsyncData(value: final following) => Builder(
builder: (context) {
if (following) {
return OutlinedButton(
onPressed: () async {
await followingArtistNotifier
.removeArtists([artist.id!]);
},
child: Text(context.l10n.following),
);
await isFollowingQuery.refresh();
}
queryClient.refreshInfiniteQueryAllPages(
"user-following-artists");
} finally {
queryClient.refreshQuery(
"user-follows-artists-query/$artistId",
);
}
}, [isFollowingQuery]);
if (isFollowingQuery.isLoading ||
!isFollowingQuery.hasData) {
return const SizedBox(
height: 20,
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),
);
return FilledButton(
onPressed: () async {
await followingArtistNotifier
.saveArtists([artist.id!]);
},
child: Text(context.l10n.follow),
);
},
),
AsyncError() => const SizedBox(),
_ => const SizedBox.square(
dimension: 20,
child: CircularProgressIndicator(),
)
};
},
),
const SizedBox(width: 5),

View File

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

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/extensions/context.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 {
final String artistId;
@ -20,13 +20,10 @@ class ArtistPageTopTracks extends HookConsumerWidget {
final playlist = ref.watch(ProxyPlaylistNotifier.provider);
final playlistNotifier = ref.watch(ProxyPlaylistNotifier.notifier);
final topTracksQuery = useQueries.artist.topTracksOf(
ref,
artistId,
);
final topTracksQuery = ref.watch(artistTopTracksProvider(artistId));
final isPlaylistPlaying = playlist.containsTracks(
topTracksQuery.data ?? <Track>[],
topTracksQuery.value ?? <Track>[],
);
if (topTracksQuery.hasError) {
@ -38,7 +35,7 @@ class ArtistPageTopTracks extends HookConsumerWidget {
}
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 {
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';
final artistTopTracksProvider = FutureProviderFamily<List<TrackSimple>, String>(
final artistTopTracksProvider = FutureProviderFamily<List<Track>, String>(
(ref, artistId) async {
final spotify = ref.watch(spotifyProvider);
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/spotify_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:http/http.dart' as http;
import 'package:spotube/utils/type_conversion_utils.dart';
import 'package:wikipedia_api/wikipedia_api.dart';
part 'album/favorite.dart';
part 'album/tracks.dart';
@ -32,6 +34,8 @@ part 'artist/is_following.dart';
part 'artist/following.dart';
part 'artist/top_tracks.dart';
part 'artist/albums.dart';
part 'artist/wikipedia.dart';
part 'artist/related.dart';
part 'category/genres.dart';
part 'category/categories.dart';