import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotify/spotify.dart'; import 'package:spotube/components/Album/AlbumCard.dart'; import 'package:spotube/components/Artist/ArtistCard.dart'; import 'package:spotube/components/Shared/PageWindowTitleBar.dart'; import 'package:spotube/components/Shared/TracksTableView.dart'; import 'package:spotube/helpers/image-to-url-string.dart'; import 'package:spotube/helpers/readable-number.dart'; import 'package:spotube/helpers/zero-pad-num-str.dart'; import 'package:spotube/hooks/useBreakpointValue.dart'; import 'package:spotube/hooks/useBreakpoints.dart'; import 'package:spotube/hooks/useForceUpdate.dart'; import 'package:spotube/models/Logger.dart'; import 'package:spotube/provider/Playback.dart'; import 'package:spotube/provider/SpotifyDI.dart'; class ArtistProfile extends HookConsumerWidget { final String artistId; final logger = getLogger(ArtistProfile); ArtistProfile(this.artistId, {Key? key}) : super(key: key); @override Widget build(BuildContext context, ref) { SpotifyApi spotify = ref.watch(spotifyProvider); final scrollController = useScrollController(); final parentScrollController = useScrollController(); final textTheme = Theme.of(context).textTheme; final chipTextVariant = useBreakpointValue( sm: textTheme.bodySmall, md: textTheme.bodyMedium, lg: textTheme.headline6, xl: textTheme.headline6, xxl: textTheme.headline6, ); final avatarWidth = useBreakpointValue( sm: MediaQuery.of(context).size.width * 0.50, md: MediaQuery.of(context).size.width * 0.40, lg: MediaQuery.of(context).size.width * 0.18, xl: MediaQuery.of(context).size.width * 0.18, xxl: MediaQuery.of(context).size.width * 0.18, ); final breakpoint = useBreakpoints(); final update = useForceUpdate(); return SafeArea( child: Scaffold( appBar: const PageWindowTitleBar( leading: BackButton(), ), body: FutureBuilder( future: spotify.artists.get(artistId), builder: (context, snapshot) { if (!snapshot.hasData) { return const Center(child: CircularProgressIndicator.adaptive()); } return SingleChildScrollView( controller: parentScrollController, padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Wrap( crossAxisAlignment: WrapCrossAlignment.center, runAlignment: WrapAlignment.center, children: [ const SizedBox(width: 50), CircleAvatar( radius: avatarWidth, backgroundImage: CachedNetworkImageProvider( imageToUrlString(snapshot.data!.images), ), ), Padding( padding: const EdgeInsets.all(20), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 5), decoration: BoxDecoration( color: Colors.blue, borderRadius: BorderRadius.circular(50)), child: Text(snapshot.data!.type!.toUpperCase(), style: chipTextVariant?.copyWith( color: Colors.white)), ), Text( snapshot.data!.name!, style: breakpoint.isSm ? textTheme.headline4 : textTheme.headline2, ), Text( "${toReadableNumber(snapshot.data!.followers!.total!.toDouble())} followers", style: breakpoint.isSm ? textTheme.bodyText1 : textTheme.headline5, ), const SizedBox(height: 20), Row( mainAxisSize: MainAxisSize.min, children: [ FutureBuilder>( future: spotify.me.isFollowing( FollowingType.artist, [artistId], ), builder: (context, snapshot) { final isFollowing = snapshot.data?.first == true; return OutlinedButton( onPressed: () async { try { isFollowing ? await spotify.me.unfollow( FollowingType.artist, [artistId], ) : await spotify.me.follow( FollowingType.artist, [artistId], ); } catch (e, stack) { logger.e( "FollowButton.onPressed", e, stack, ); } finally { update(); } }, child: snapshot.hasData ? Text(isFollowing ? "Following" : "Follow") : const CircularProgressIndicator .adaptive(), ); }), IconButton( icon: const Icon(Icons.share_rounded), onPressed: () { Clipboard.setData( ClipboardData( text: snapshot .data?.externalUrls?.spotify), ).then((val) { ScaffoldMessenger.of(context) .showSnackBar( const SnackBar( width: 300, behavior: SnackBarBehavior.floating, content: Text( "Artist URL copied to clipboard", textAlign: TextAlign.center, ), ), ); }); }, ) ], ) ], ), ), ], ), const SizedBox(height: 50), FutureBuilder>( future: spotify.artists.getTopTracks(snapshot.data!.id!, "US"), builder: (context, trackSnapshot) { if (!trackSnapshot.hasData) { return const Center( child: CircularProgressIndicator.adaptive()); } Playback playback = ref.watch(playbackProvider); var isPlaylistPlaying = playback.currentPlaylist?.id == snapshot.data?.id; playPlaylist(List tracks, {Track? currentTrack}) async { currentTrack ??= tracks.first; if (!isPlaylistPlaying) { playback.setCurrentPlaylist = CurrentPlaylist( tracks: tracks, id: snapshot.data!.id!, name: "${snapshot.data!.name!} To Tracks", thumbnail: imageToUrlString(snapshot.data?.images), ); playback.setCurrentTrack = currentTrack; } else if (isPlaylistPlaying && currentTrack.id != null && currentTrack.id != playback.currentTrack?.id) { playback.setCurrentTrack = currentTrack; } await playback.startPlaying(); } return Column(children: [ Row( children: [ Text( "Top Tracks", style: Theme.of(context).textTheme.headline4, ), Container( margin: const EdgeInsets.symmetric(horizontal: 5), decoration: BoxDecoration( color: Theme.of(context).primaryColor, borderRadius: BorderRadius.circular(50), ), child: IconButton( icon: Icon(isPlaylistPlaying ? Icons.stop_rounded : Icons.play_arrow_rounded), color: Colors.white, onPressed: trackSnapshot.hasData ? () => playPlaylist( trackSnapshot.data!.toList()) : null, ), ) ], ), ...trackSnapshot.data ?.toList() .asMap() .entries .map((track) { String duration = "${track.value.duration?.inMinutes.remainder(60)}:${zeroPadNumStr(track.value.duration?.inSeconds.remainder(60) ?? 0)}"; String? thumbnailUrl = imageToUrlString( track.value.album?.images, index: (track.value.album?.images?.length ?? 1) - 1); return TrackTile( playback, duration: duration, track: track, thumbnailUrl: thumbnailUrl, onTrackPlayButtonPressed: (currentTrack) => playPlaylist( trackSnapshot.data!.toList(), currentTrack: track.value, ), ); }) ?? [], ]); }, ), const SizedBox(height: 50), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "Albums", style: Theme.of(context).textTheme.headline4, ), TextButton( child: const Text("See All"), onPressed: () { GoRouter.of(context).push( "/artist-album/$artistId", extra: snapshot.data?.name ?? "KRTX", ); }, ) ], ), const SizedBox(height: 10), FutureBuilder>( future: spotify.artists .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( controller: scrollController, child: SingleChildScrollView( controller: scrollController, scrollDirection: Axis.horizontal, child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: snapshot.data ?.map((album) => AlbumCard(album)) .toList() ?? [], ), ), ); }, ), const SizedBox(height: 20), Text( "Fans also likes", style: Theme.of(context).textTheme.headline4, ), const SizedBox(height: 10), FutureBuilder>( future: spotify.artists.getRelatedArtists(artistId), builder: (context, snapshot) { if (!snapshot.hasData) { return const Center( child: CircularProgressIndicator.adaptive()); } return Center( child: Wrap( spacing: 20, runSpacing: 20, children: snapshot.data ?.map((artist) => ArtistCard(artist)) .toList() ?? [], ), ); }, ) ], ), ); }, ), ), ); } }