expanded Cached netwrok request support

This commit is contained in:
Kingkor Roy Tirtho 2022-05-29 23:32:47 +06:00
parent 20ada95312
commit 9224b4c316
6 changed files with 319 additions and 293 deletions

View File

@ -18,6 +18,7 @@ import 'package:spotube/hooks/useForceUpdate.dart';
import 'package:spotube/models/Logger.dart'; import 'package:spotube/models/Logger.dart';
import 'package:spotube/provider/Playback.dart'; import 'package:spotube/provider/Playback.dart';
import 'package:spotube/provider/SpotifyDI.dart'; import 'package:spotube/provider/SpotifyDI.dart';
import 'package:spotube/provider/SpotifyRequests.dart';
class ArtistProfile extends HookConsumerWidget { class ArtistProfile extends HookConsumerWidget {
final String artistId; final String artistId;
@ -49,18 +50,22 @@ class ArtistProfile extends HookConsumerWidget {
final breakpoint = useBreakpoints(); final breakpoint = useBreakpoints();
final update = useForceUpdate(); final update = useForceUpdate();
final Playback playback = ref.watch(playbackProvider);
final artistsSnapshot = ref.watch(artistProfileQuery(artistId));
final isFollowingSnapshot =
ref.watch(currentUserFollowsArtistQuery(artistId));
final topTracksSnapshot = ref.watch(artistTopTracksQuery(artistId));
final albums = ref.watch(artistAlbumsQuery(artistId));
final relatedArtists = ref.watch(artistRelatedArtistsQuery(artistId));
return SafeArea( return SafeArea(
child: Scaffold( child: Scaffold(
appBar: const PageWindowTitleBar( appBar: const PageWindowTitleBar(
leading: BackButton(), leading: BackButton(),
), ),
body: FutureBuilder<Artist>( body: artistsSnapshot.when<Widget>(
future: spotify.artists.get(artistId), data: (data) {
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(child: CircularProgressIndicator.adaptive());
}
return SingleChildScrollView( return SingleChildScrollView(
controller: parentScrollController, controller: parentScrollController,
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
@ -75,7 +80,7 @@ class ArtistProfile extends HookConsumerWidget {
CircleAvatar( CircleAvatar(
radius: avatarWidth, radius: avatarWidth,
backgroundImage: CachedNetworkImageProvider( backgroundImage: CachedNetworkImageProvider(
imageToUrlString(snapshot.data!.images), imageToUrlString(data.images),
), ),
), ),
Padding( Padding(
@ -90,18 +95,18 @@ class ArtistProfile extends HookConsumerWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.blue, color: Colors.blue,
borderRadius: BorderRadius.circular(50)), borderRadius: BorderRadius.circular(50)),
child: Text(snapshot.data!.type!.toUpperCase(), child: Text(data.type!.toUpperCase(),
style: chipTextVariant?.copyWith( style: chipTextVariant?.copyWith(
color: Colors.white)), color: Colors.white)),
), ),
Text( Text(
snapshot.data!.name!, data.name!,
style: breakpoint.isSm style: breakpoint.isSm
? textTheme.headline4 ? textTheme.headline4
: textTheme.headline2, : textTheme.headline2,
), ),
Text( Text(
"${toReadableNumber(snapshot.data!.followers!.total!.toDouble())} followers", "${toReadableNumber(data.followers!.total!.toDouble())} followers",
style: breakpoint.isSm style: breakpoint.isSm
? textTheme.bodyText1 ? textTheme.bodyText1
: textTheme.headline5, : textTheme.headline5,
@ -110,14 +115,8 @@ class ArtistProfile extends HookConsumerWidget {
Row( Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
FutureBuilder<List<bool>>( isFollowingSnapshot.when(
future: spotify.me.isFollowing( data: (isFollowing) {
FollowingType.artist,
[artistId],
),
builder: (context, snapshot) {
final isFollowing =
snapshot.data?.first == true;
return OutlinedButton( return OutlinedButton(
onPressed: () async { onPressed: () async {
try { try {
@ -137,24 +136,29 @@ class ArtistProfile extends HookConsumerWidget {
stack, stack,
); );
} finally { } finally {
update(); ref.refresh(
currentUserFollowsArtistQuery(
artistId),
);
} }
}, },
child: snapshot.hasData child: Text(
? Text(isFollowing isFollowing
? "Following" ? "Following"
: "Follow") : "Follow",
: const CircularProgressIndicator ),
.adaptive(),
); );
}), },
error: (error, stackTrace) => Container(),
loading: () =>
const CircularProgressIndicator
.adaptive()),
IconButton( IconButton(
icon: const Icon(Icons.share_rounded), icon: const Icon(Icons.share_rounded),
onPressed: () { onPressed: () {
Clipboard.setData( Clipboard.setData(
ClipboardData( ClipboardData(
text: snapshot text: data.externalUrls?.spotify),
.data?.externalUrls?.spotify),
).then((val) { ).then((val) {
ScaffoldMessenger.of(context) ScaffoldMessenger.of(context)
.showSnackBar( .showSnackBar(
@ -178,26 +182,19 @@ class ArtistProfile extends HookConsumerWidget {
], ],
), ),
const SizedBox(height: 50), const SizedBox(height: 50),
FutureBuilder<Iterable<Track>>( topTracksSnapshot.when(
future: data: (topTracks) {
spotify.artists.getTopTracks(snapshot.data!.id!, "US"), final isPlaylistPlaying =
builder: (context, trackSnapshot) { playback.currentPlaylist?.id == data.id;
if (!trackSnapshot.hasData) {
return const Center(
child: CircularProgressIndicator.adaptive());
}
Playback playback = ref.watch(playbackProvider);
var isPlaylistPlaying =
playback.currentPlaylist?.id == snapshot.data?.id;
playPlaylist(List<Track> tracks, playPlaylist(List<Track> tracks,
{Track? currentTrack}) async { {Track? currentTrack}) async {
currentTrack ??= tracks.first; currentTrack ??= tracks.first;
if (!isPlaylistPlaying) { if (!isPlaylistPlaying) {
playback.setCurrentPlaylist = CurrentPlaylist( playback.setCurrentPlaylist = CurrentPlaylist(
tracks: tracks, tracks: tracks,
id: snapshot.data!.id!, id: data.id!,
name: "${snapshot.data!.name!} To Tracks", name: "${data.name!} To Tracks",
thumbnail: imageToUrlString(snapshot.data?.images), thumbnail: imageToUrlString(data.images),
); );
playback.setCurrentTrack = currentTrack; playback.setCurrentTrack = currentTrack;
} else if (isPlaylistPlaying && } else if (isPlaylistPlaying &&
@ -216,7 +213,8 @@ class ArtistProfile extends HookConsumerWidget {
style: Theme.of(context).textTheme.headline4, style: Theme.of(context).textTheme.headline4,
), ),
Container( Container(
margin: const EdgeInsets.symmetric(horizontal: 5), margin:
const EdgeInsets.symmetric(horizontal: 5),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).primaryColor, color: Theme.of(context).primaryColor,
borderRadius: BorderRadius.circular(50), borderRadius: BorderRadius.circular(50),
@ -226,19 +224,13 @@ class ArtistProfile extends HookConsumerWidget {
? Icons.stop_rounded ? Icons.stop_rounded
: Icons.play_arrow_rounded), : Icons.play_arrow_rounded),
color: Colors.white, color: Colors.white,
onPressed: trackSnapshot.hasData onPressed: () =>
? () => playPlaylist( playPlaylist(topTracks.toList()),
trackSnapshot.data!.toList())
: null,
), ),
) )
], ],
), ),
...trackSnapshot.data ...topTracks.toList().asMap().entries.map((track) {
?.toList()
.asMap()
.entries
.map((track) {
String duration = String duration =
"${track.value.duration?.inMinutes.remainder(60)}:${zeroPadNumStr(track.value.duration?.inSeconds.remainder(60) ?? 0)}"; "${track.value.duration?.inMinutes.remainder(60)}:${zeroPadNumStr(track.value.duration?.inSeconds.remainder(60) ?? 0)}";
String? thumbnailUrl = imageToUrlString( String? thumbnailUrl = imageToUrlString(
@ -253,14 +245,17 @@ class ArtistProfile extends HookConsumerWidget {
thumbnailUrl: thumbnailUrl, thumbnailUrl: thumbnailUrl,
onTrackPlayButtonPressed: (currentTrack) => onTrackPlayButtonPressed: (currentTrack) =>
playPlaylist( playPlaylist(
trackSnapshot.data!.toList(), topTracks.toList(),
currentTrack: track.value, currentTrack: track.value,
), ),
); );
}) ?? }),
[],
]); ]);
}, },
error: (error, stack) =>
Text("Failed to find top tracks $error"),
loading: () => const Center(
child: CircularProgressIndicator.adaptive()),
), ),
const SizedBox(height: 50), const SizedBox(height: 50),
Row( Row(
@ -275,23 +270,15 @@ class ArtistProfile extends HookConsumerWidget {
onPressed: () { onPressed: () {
GoRouter.of(context).push( GoRouter.of(context).push(
"/artist-album/$artistId", "/artist-album/$artistId",
extra: snapshot.data?.name ?? "KRTX", extra: data.name ?? "KRTX",
); );
}, },
) )
], ],
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
FutureBuilder<List<Album>>( albums.when(
future: spotify.artists data: (albums) {
.albums(snapshot.data!.id!)
.getPage(5, 0)
.then((al) => al.items?.toList() ?? []),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(
child: CircularProgressIndicator.adaptive());
}
return Scrollbar( return Scrollbar(
controller: scrollController, controller: scrollController,
child: SingleChildScrollView( child: SingleChildScrollView(
@ -299,7 +286,7 @@ class ArtistProfile extends HookConsumerWidget {
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,
children: snapshot.data children: albums.items
?.map((album) => AlbumCard(album)) ?.map((album) => AlbumCard(album))
.toList() ?? .toList() ??
[], [],
@ -307,6 +294,9 @@ class ArtistProfile extends HookConsumerWidget {
), ),
); );
}, },
error: (error, stackTrack) =>
Text("Failed to get Artist albums $error"),
loading: () => const CircularProgressIndicator.adaptive(),
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
Text( Text(
@ -314,31 +304,29 @@ class ArtistProfile extends HookConsumerWidget {
style: Theme.of(context).textTheme.headline4, style: Theme.of(context).textTheme.headline4,
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
FutureBuilder<Iterable<Artist>>( relatedArtists.when(
future: spotify.artists.getRelatedArtists(artistId), data: (artists) {
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(
child: CircularProgressIndicator.adaptive());
}
return Center( return Center(
child: Wrap( child: Wrap(
spacing: 20, spacing: 20,
runSpacing: 20, runSpacing: 20,
children: snapshot.data children: artists
?.map((artist) => ArtistCard(artist)) .map((artist) => ArtistCard(artist))
.toList() ?? .toList(),
[],
), ),
); );
}, },
) error: (error, stackTrack) =>
Text("Failed to get Artist albums $error"),
loading: () => const CircularProgressIndicator.adaptive(),
),
], ],
), ),
); );
}, },
), error: (_, __) => const Text("Life's miserable"),
loading: () =>
const Center(child: CircularProgressIndicator.adaptive())),
), ),
); );
} }

View File

@ -34,11 +34,10 @@ class CategoryCard extends HookConsumerWidget {
), ),
ref: ref, ref: ref,
firstPageKey: 0, firstPageKey: 0,
onData: (data, pagingController, pageKey) { onData: (page, pagingController, pageKey) {
if (playlists != null && playlists?.isNotEmpty == true && mounted()) { if (playlists != null && playlists?.isNotEmpty == true && mounted()) {
return pagingController.appendLastPage(playlists!.toList()); return pagingController.appendLastPage(playlists!.toList());
} }
final page = data.value;
if (page.isLast && page.items != null) { if (page.isLast && page.items != null) {
pagingController.appendLastPage(page.items!.toList()); pagingController.appendLastPage(page.items!.toList());
} else if (page.items != null) { } else if (page.items != null) {

View File

@ -114,8 +114,7 @@ class Home extends HookConsumerWidget {
(pageKey) => categoriesQuery(pageKey), (pageKey) => categoriesQuery(pageKey),
ref: ref, ref: ref,
firstPageKey: 0, firstPageKey: 0,
onData: (data, pagingController, pageKey) { onData: (categories, pagingController, pageKey) {
final categories = data.value;
final items = categories.items?.toList(); final items = categories.items?.toList();
if (pageKey == 0) { if (pageKey == 0) {
Category category = Category(); Category category = Category();

View File

@ -18,8 +18,7 @@ class UserArtists extends HookConsumerWidget {
(pageKey) => currentUserFollowingArtistsQuery(pageKey), (pageKey) => currentUserFollowingArtistsQuery(pageKey),
ref: ref, ref: ref,
firstPageKey: "", firstPageKey: "",
onData: (data, pagingController, pageKey) { onData: (artists, pagingController, pageKey) {
final artists = data.value;
final items = artists.items!.toList(); final items = artists.items!.toList();
if (artists.items != null && items.length < 15) { if (artists.items != null && items.length < 15) {

View File

@ -8,13 +8,13 @@ PagingController<P, ItemType> usePaginatedFutureProvider<T, P, ItemType>(
required P firstPageKey, required P firstPageKey,
required WidgetRef ref, required WidgetRef ref,
void Function( void Function(
AsyncData<T>, T,
PagingController<P, ItemType> pagingController, PagingController<P, ItemType> pagingController,
P pageKey, P pageKey,
)? )?
onData, onData,
void Function(AsyncError<T>)? onError, void Function(Object)? onError,
void Function(AsyncLoading<T>)? onLoading, void Function()? onLoading,
}) { }) {
final currentPageKey = useState(firstPageKey); final currentPageKey = useState(firstPageKey);
final snapshot = ref.watch(createSnapshot(currentPageKey.value)); final snapshot = ref.watch(createSnapshot(currentPageKey.value));
@ -32,10 +32,10 @@ PagingController<P, ItemType> usePaginatedFutureProvider<T, P, ItemType>(
}, [snapshot, currentPageKey]); }, [snapshot, currentPageKey]);
useEffect(() { useEffect(() {
snapshot.mapOrNull( snapshot.whenOrNull(
data: (data) => data: (data) =>
onData?.call(data, pagingController, currentPageKey.value), onData?.call(data, pagingController, currentPageKey.value),
error: (error) { error: (error, _) {
pagingController.error = error; pagingController.error = error;
return onError?.call(error); return onError?.call(error);
}, },

View File

@ -50,3 +50,44 @@ final currentUserFollowingArtistsQuery =
return spotify.me.following(FollowingType.artist).getPage(15, pageKey); return spotify.me.following(FollowingType.artist).getPage(15, pageKey);
}, },
); );
final artistProfileQuery = FutureProvider.autoDispose.family<Artist, String>(
(ref, id) {
final spotify = ref.watch(spotifyProvider);
return spotify.artists.get(id);
},
);
final currentUserFollowsArtistQuery =
FutureProvider.autoDispose.family<bool, String>(
(ref, artistId) async {
final spotify = ref.watch(spotifyProvider);
final result = await spotify.me.isFollowing(
FollowingType.artist,
[artistId],
);
return result.first;
},
);
final artistTopTracksQuery =
FutureProvider.autoDispose.family<Iterable<Track>, String>((ref, id) {
final spotify = ref.watch(spotifyProvider);
return spotify.artists.getTopTracks(id, "US");
});
final artistAlbumsQuery =
FutureProvider.autoDispose.family<Page<Album>, String>(
(ref, id) {
final spotify = ref.watch(spotifyProvider);
return spotify.artists.albums(id).getPage(5, 0);
},
);
final artistRelatedArtistsQuery =
FutureProvider.autoDispose.family<Iterable<Artist>, String>(
(ref, id) {
final spotify = ref.watch(spotifyProvider);
return spotify.artists.getRelatedArtists(id);
},
);