From 75173e5096209104763059f812961982bd3755e6 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Mon, 24 Jun 2024 21:01:09 +0600 Subject: [PATCH] refactor: use provider based is track loading implementation --- lib/collections/intents.dart | 6 +- lib/components/track_tile/track_tile.dart | 347 +++++++++--------- lib/modules/album/album_card.dart | 6 +- lib/modules/player/player_controls.dart | 22 +- lib/modules/player/player_overlay.dart | 9 +- lib/modules/player/sibling_tracks_sheet.dart | 11 +- lib/modules/playlist/playlist_card.dart | 5 +- lib/provider/audio_player/audio_player.dart | 5 - .../audio_player/querying_track_info.dart | 12 + 9 files changed, 216 insertions(+), 207 deletions(-) create mode 100644 lib/provider/audio_player/querying_track_info.dart diff --git a/lib/collections/intents.dart b/lib/collections/intents.dart index 1a44a846..ac0451ac 100644 --- a/lib/collections/intents.dart +++ b/lib/collections/intents.dart @@ -11,7 +11,7 @@ import 'package:spotube/pages/home/home.dart'; import 'package:spotube/pages/library/library.dart'; import 'package:spotube/pages/lyrics/lyrics.dart'; import 'package:spotube/pages/search/search.dart'; -import 'package:spotube/provider/audio_player/audio_player.dart'; +import 'package:spotube/provider/audio_player/querying_track_info.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; import 'package:spotube/utils/platform.dart'; @@ -96,8 +96,8 @@ class SeekIntent extends Intent { class SeekAction extends Action { @override invoke(intent) async { - final playlist = intent.ref.read(audioPlayerProvider.notifier); - if (playlist.isFetching()) { + final isFetchingActiveTrack = intent.ref.read(queryingTrackInfoProvider); + if (isFetchingActiveTrack) { DirectionalFocusAction().invoke( DirectionalFocusIntent( intent.forward ? TraversalDirection.right : TraversalDirection.left, diff --git a/lib/components/track_tile/track_tile.dart b/lib/components/track_tile/track_tile.dart index cdc18d9b..0e8d2cd0 100644 --- a/lib/components/track_tile/track_tile.dart +++ b/lib/components/track_tile/track_tile.dart @@ -17,7 +17,7 @@ import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/duration.dart'; import 'package:spotube/extensions/image.dart'; import 'package:spotube/models/local_track.dart'; -import 'package:spotube/provider/audio_player/audio_player.dart'; +import 'package:spotube/provider/audio_player/querying_track_info.dart'; import 'package:spotube/provider/audio_player/state.dart'; import 'package:spotube/provider/blacklist_provider.dart'; @@ -84,191 +84,190 @@ class TrackTile extends HookConsumerWidget { }, child: HoverBuilder( permanentState: isSelected || constrains.smAndDown ? true : null, - builder: (context, isHovering) { - return ListTile( - selected: isSelected, - onTap: () async { - try { - isLoading.value = true; - await onTap?.call(); - } finally { - if (context.mounted) { - isLoading.value = false; - } + builder: (context, isHovering) => ListTile( + selected: isSelected, + onTap: () async { + try { + isLoading.value = true; + await onTap?.call(); + } finally { + if (context.mounted) { + isLoading.value = false; } - }, - onLongPress: onLongPress, - enabled: !isBlackListed, - contentPadding: EdgeInsets.zero, - tileColor: - isBlackListed ? theme.colorScheme.errorContainer : null, - horizontalTitleGap: 12, - leadingAndTrailingTextStyle: theme.textTheme.bodyMedium, - leading: Row( - mainAxisSize: MainAxisSize.min, - children: [ - ...?leadingActions, - if (index != null && onChanged == null && constrains.mdAndUp) - SizedBox( - width: 50, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 6), - child: Text( - '${(index ?? 0) + 1}', - maxLines: 1, - style: theme.textTheme.bodySmall, - textAlign: TextAlign.center, - ), + } + }, + onLongPress: onLongPress, + enabled: !isBlackListed, + contentPadding: EdgeInsets.zero, + tileColor: isBlackListed ? theme.colorScheme.errorContainer : null, + horizontalTitleGap: 12, + leadingAndTrailingTextStyle: theme.textTheme.bodyMedium, + leading: Row( + mainAxisSize: MainAxisSize.min, + children: [ + ...?leadingActions, + if (index != null && onChanged == null && constrains.mdAndUp) + SizedBox( + width: 50, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 6), + child: Text( + '${(index ?? 0) + 1}', + maxLines: 1, + style: theme.textTheme.bodySmall, + textAlign: TextAlign.center, ), - ) - else if (constrains.smAndDown) - const SizedBox(width: 16), - if (onChanged != null) - Checkbox( - value: selected, - onChanged: onChanged, ), - Stack( - children: [ - ClipRRect( - borderRadius: BorderRadius.circular(4), - child: AspectRatio( - aspectRatio: 1, - child: UniversalImage( - path: (track.album?.images).asUrlString( - placeholder: ImagePlaceholder.albumArt, - ), - fit: BoxFit.cover, - ), - ), - ), - Positioned.fill( - child: AnimatedContainer( - duration: const Duration(milliseconds: 300), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(4), - color: isHovering - ? Colors.black.withOpacity(0.4) - : Colors.transparent, - ), - ), - ), - Positioned.fill( - child: Center( - child: IconTheme( - data: theme.iconTheme - .copyWith(size: 26, color: Colors.white), - child: Skeleton.ignore( - child: AnimatedSwitcher( - duration: const Duration(milliseconds: 300), - child: (isPlaying && - ref - .watch(audioPlayerProvider - .notifier) - .isFetching()) || - isLoading.value - ? const SizedBox( - width: 26, - height: 26, - child: CircularProgressIndicator( - strokeWidth: 1.5, - color: Colors.white, - ), - ) - : isPlaying - ? Icon( - SpotubeIcons.pause, - color: theme.colorScheme.primary, - ) - : !isHovering - ? const SizedBox.shrink() - : const Icon(SpotubeIcons.play), - ), - ), - ), - ), - ), - ], + ) + else if (constrains.smAndDown) + const SizedBox(width: 16), + if (onChanged != null) + Checkbox( + value: selected, + onChanged: onChanged, ), - ], - ), - title: Row( - children: [ - Expanded( - flex: 6, - child: switch (track) { - LocalTrack() => Text( - track.name!, - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - _ => LinkText( - track.name!, - "/track/${track.id}", - push: true, - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - }, - ), - if (constrains.mdAndUp) ...[ - const SizedBox(width: 8), - Expanded( - flex: 4, - child: switch (track) { - LocalTrack() => Text( - track.album!.name!, - maxLines: 1, - overflow: TextOverflow.ellipsis, + Stack( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(4), + child: AspectRatio( + aspectRatio: 1, + child: UniversalImage( + path: (track.album?.images).asUrlString( + placeholder: ImagePlaceholder.albumArt, ), - _ => Align( - alignment: Alignment.centerLeft, - child: LinkText( - track.album!.name!, - "/album/${track.album?.id}", - extra: track.album, - push: true, - overflow: TextOverflow.ellipsis, + fit: BoxFit.cover, + ), + ), + ), + Positioned.fill( + child: AnimatedContainer( + duration: const Duration(milliseconds: 300), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4), + color: isHovering + ? Colors.black.withOpacity(0.4) + : Colors.transparent, + ), + ), + ), + Positioned.fill( + child: Center( + child: IconTheme( + data: theme.iconTheme + .copyWith(size: 26, color: Colors.white), + child: Skeleton.ignore( + child: Consumer( + builder: (context, ref, _) { + final isFetchingActiveTrack = + ref.watch(queryingTrackInfoProvider); + return AnimatedSwitcher( + duration: const Duration(milliseconds: 300), + child: (isPlaying && isFetchingActiveTrack) || + isLoading.value + ? const SizedBox( + width: 26, + height: 26, + child: CircularProgressIndicator( + strokeWidth: 1.5, + color: Colors.white, + ), + ) + : isPlaying + ? Icon( + SpotubeIcons.pause, + color: theme.colorScheme.primary, + ) + : !isHovering + ? const SizedBox.shrink() + : const Icon(SpotubeIcons.play), + ); + }, ), - ) - }, + ), + ), + ), ), ], - ], - ), - subtitle: Align( - alignment: Alignment.centerLeft, - child: track is LocalTrack - ? Text( - track.artists?.asString() ?? '', - ) - : ClipRect( - child: ConstrainedBox( - constraints: const BoxConstraints(maxHeight: 40), - child: ArtistLink(artists: track.artists ?? []), - ), + ), + ], + ), + title: Row( + children: [ + Expanded( + flex: 6, + child: switch (track) { + LocalTrack() => Text( + track.name!, + maxLines: 1, + overflow: TextOverflow.ellipsis, ), - ), - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ + _ => LinkText( + track.name!, + "/track/${track.id}", + push: true, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + }, + ), + if (constrains.mdAndUp) ...[ const SizedBox(width: 8), - Text( - Duration(milliseconds: track.durationMs ?? 0) - .toHumanReadableString(padZero: false), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - TrackOptions( - track: track, - playlistId: playlistId, - userPlaylist: userPlaylist, - showMenuCbRef: showOptionCbRef, + Expanded( + flex: 4, + child: switch (track) { + LocalTrack() => Text( + track.album!.name!, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + _ => Align( + alignment: Alignment.centerLeft, + child: LinkText( + track.album!.name!, + "/album/${track.album?.id}", + extra: track.album, + push: true, + overflow: TextOverflow.ellipsis, + ), + ) + }, ), ], - ), - ); - }, + ], + ), + subtitle: Align( + alignment: Alignment.centerLeft, + child: track is LocalTrack + ? Text( + track.artists?.asString() ?? '', + ) + : ClipRect( + child: ConstrainedBox( + constraints: const BoxConstraints(maxHeight: 40), + child: ArtistLink(artists: track.artists ?? []), + ), + ), + ), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox(width: 8), + Text( + Duration(milliseconds: track.durationMs ?? 0) + .toHumanReadableString(padZero: false), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + TrackOptions( + track: track, + playlistId: playlistId, + userPlaylist: userPlaylist, + showMenuCbRef: showOptionCbRef, + ), + ], + ), + ), ), ); }); diff --git a/lib/modules/album/album_card.dart b/lib/modules/album/album_card.dart index f9f70c66..de7aa5f8 100644 --- a/lib/modules/album/album_card.dart +++ b/lib/modules/album/album_card.dart @@ -10,6 +10,7 @@ import 'package:spotube/extensions/image.dart'; import 'package:spotube/extensions/track.dart'; import 'package:spotube/models/connect/connect.dart'; import 'package:spotube/pages/album/album.dart'; +import 'package:spotube/provider/audio_player/querying_track_info.dart'; import 'package:spotube/provider/connect/connect.dart'; import 'package:spotube/provider/history/history.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; @@ -35,6 +36,7 @@ class AlbumCard extends HookConsumerWidget { useStream(audioPlayer.playingStream).data ?? audioPlayer.isPlaying; final playlistNotifier = ref.watch(audioPlayerProvider.notifier); final historyNotifier = ref.read(playbackHistoryProvider.notifier); + final isFetchingActiveTrack = ref.watch(queryingTrackInfoProvider); bool isPlaylistPlaying = useMemoized( () => playlist.containsCollection(album.id!), @@ -59,8 +61,8 @@ class AlbumCard extends HookConsumerWidget { ), margin: const EdgeInsets.symmetric(horizontal: 10), isPlaying: isPlaylistPlaying, - isLoading: (isPlaylistPlaying && playlistNotifier.isFetching()) || - updating.value, + isLoading: + (isPlaylistPlaying && isFetchingActiveTrack) || updating.value, title: album.name!, description: "${album.albumType?.formatted} • ${album.artists?.asString() ?? ""}", diff --git a/lib/modules/player/player_controls.dart b/lib/modules/player/player_controls.dart index c5ef82d6..25080b66 100644 --- a/lib/modules/player/player_controls.dart +++ b/lib/modules/player/player_controls.dart @@ -11,7 +11,7 @@ import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/duration.dart'; import 'package:spotube/modules/player/use_progress.dart'; import 'package:spotube/models/logger.dart'; -import 'package:spotube/provider/audio_player/audio_player.dart'; +import 'package:spotube/provider/audio_player/querying_track_info.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; class PlayerControls extends HookConsumerWidget { @@ -43,8 +43,7 @@ class PlayerControls extends HookConsumerWidget { SeekIntent: SeekAction(), }, []); - ref.watch(audioPlayerProvider); - final playlistNotifier = ref.watch(audioPlayerProvider.notifier); + final isFetchingActiveTrack = ref.watch(queryingTrackInfoProvider); final playing = useStream(audioPlayer.playingStream).data ?? audioPlayer.isPlaying; @@ -132,7 +131,7 @@ class PlayerControls extends HookConsumerWidget { // than total duration. Keeping it resolved value: progress.value.toDouble(), secondaryTrackValue: bufferProgress, - onChanged: playlistNotifier.isFetching() + onChanged: isFetchingActiveTrack ? null : (v) { progress.value = v; @@ -183,7 +182,7 @@ class PlayerControls extends HookConsumerWidget { : context.l10n.shuffle_playlist, icon: const Icon(SpotubeIcons.shuffle), style: shuffled ? activeButtonStyle : buttonStyle, - onPressed: playlistNotifier.isFetching() + onPressed: isFetchingActiveTrack ? null : () { if (shuffled) { @@ -198,7 +197,7 @@ class PlayerControls extends HookConsumerWidget { tooltip: context.l10n.previous_track, icon: const Icon(SpotubeIcons.skipBack), style: buttonStyle, - onPressed: playlistNotifier.isFetching() + onPressed: isFetchingActiveTrack ? null : audioPlayer.skipToPrevious, ), @@ -206,7 +205,7 @@ class PlayerControls extends HookConsumerWidget { tooltip: playing ? context.l10n.pause_playback : context.l10n.resume_playback, - icon: playlistNotifier.isFetching() + icon: isFetchingActiveTrack ? SizedBox( height: 20, width: 20, @@ -219,7 +218,7 @@ class PlayerControls extends HookConsumerWidget { playing ? SpotubeIcons.pause : SpotubeIcons.play, ), style: resumePauseStyle, - onPressed: playlistNotifier.isFetching() + onPressed: isFetchingActiveTrack ? null : Actions.handler( context, @@ -230,9 +229,8 @@ class PlayerControls extends HookConsumerWidget { tooltip: context.l10n.next_track, icon: const Icon(SpotubeIcons.skipForward), style: buttonStyle, - onPressed: playlistNotifier.isFetching() - ? null - : audioPlayer.skipToNext, + onPressed: + isFetchingActiveTrack ? null : audioPlayer.skipToNext, ), StreamBuilder( stream: audioPlayer.loopModeStream, @@ -253,7 +251,7 @@ class PlayerControls extends HookConsumerWidget { loopMode == PlaylistMode.loop ? activeButtonStyle : buttonStyle, - onPressed: playlistNotifier.isFetching() + onPressed: isFetchingActiveTrack ? null : () async { await audioPlayer.setLoopMode(loopMode); diff --git a/lib/modules/player/player_overlay.dart b/lib/modules/player/player_overlay.dart index c1b285ee..2322bcba 100644 --- a/lib/modules/player/player_overlay.dart +++ b/lib/modules/player/player_overlay.dart @@ -12,6 +12,7 @@ import 'package:spotube/collections/intents.dart'; import 'package:spotube/modules/player/use_progress.dart'; import 'package:spotube/modules/player/player.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; +import 'package:spotube/provider/audio_player/querying_track_info.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; class PlayerOverlay extends HookConsumerWidget { @@ -24,7 +25,7 @@ class PlayerOverlay extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { - final playlistNotifier = ref.watch(audioPlayerProvider.notifier); + final isFetchingActiveTrack = ref.watch(queryingTrackInfoProvider); final playlist = ref.watch(audioPlayerProvider); final canShow = playlist.activeTrack != null; @@ -127,14 +128,14 @@ class PlayerOverlay extends HookConsumerWidget { SpotubeIcons.skipBack, color: textColor, ), - onPressed: playlistNotifier.isFetching() + onPressed: isFetchingActiveTrack ? null : audioPlayer.skipToPrevious, ), Consumer( builder: (context, ref, _) { return IconButton( - icon: playlistNotifier.isFetching() + icon: isFetchingActiveTrack ? const SizedBox( height: 20, width: 20, @@ -158,7 +159,7 @@ class PlayerOverlay extends HookConsumerWidget { SpotubeIcons.skipForward, color: textColor, ), - onPressed: playlistNotifier.isFetching() + onPressed: isFetchingActiveTrack ? null : audioPlayer.skipToNext, ), diff --git a/lib/modules/player/sibling_tracks_sheet.dart b/lib/modules/player/sibling_tracks_sheet.dart index 8592f1e3..ddc77b15 100644 --- a/lib/modules/player/sibling_tracks_sheet.dart +++ b/lib/modules/player/sibling_tracks_sheet.dart @@ -16,6 +16,7 @@ import 'package:spotube/extensions/duration.dart'; import 'package:spotube/hooks/utils/use_debounce.dart'; import 'package:spotube/models/database/database.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; +import 'package:spotube/provider/audio_player/querying_track_info.dart'; import 'package:spotube/provider/server/active_sourced_track.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; @@ -54,7 +55,7 @@ class SiblingTracksSheet extends HookConsumerWidget { Widget build(BuildContext context, ref) { final theme = Theme.of(context); final playlist = ref.watch(audioPlayerProvider); - final playlistNotifier = ref.watch(audioPlayerProvider.notifier); + final isFetchingActiveTrack = ref.watch(queryingTrackInfoProvider); final preferences = ref.watch(userPreferencesProvider); final isSearching = useState(false); @@ -130,7 +131,7 @@ class SiblingTracksSheet extends HookConsumerWidget { ]); final siblings = useMemoized( - () => playlistNotifier.isFetching() + () => isFetchingActiveTrack ? [ (activeTrack as SourcedTrack).sourceInfo, ...activeTrack.siblings, @@ -176,12 +177,12 @@ class SiblingTracksSheet extends HookConsumerWidget { Text(" • ${sourceInfo.artist}"), ], ), - enabled: !playlistNotifier.isFetching(), - selected: !playlistNotifier.isFetching() && + enabled: !isFetchingActiveTrack, + selected: !isFetchingActiveTrack && sourceInfo.id == (activeTrack as SourcedTrack).sourceInfo.id, selectedTileColor: theme.popupMenuTheme.color, onTap: () { - if (!playlistNotifier.isFetching() && + if (!isFetchingActiveTrack && sourceInfo.id != (activeTrack as SourcedTrack).sourceInfo.id) { activeTrackNotifier.swapSibling(sourceInfo); Navigator.of(context).pop(); diff --git a/lib/modules/playlist/playlist_card.dart b/lib/modules/playlist/playlist_card.dart index c4164701..4e81a254 100644 --- a/lib/modules/playlist/playlist_card.dart +++ b/lib/modules/playlist/playlist_card.dart @@ -7,6 +7,7 @@ import 'package:spotube/components/playbutton_card.dart'; import 'package:spotube/extensions/image.dart'; import 'package:spotube/models/connect/connect.dart'; import 'package:spotube/pages/playlist/playlist.dart'; +import 'package:spotube/provider/audio_player/querying_track_info.dart'; import 'package:spotube/provider/connect/connect.dart'; import 'package:spotube/provider/history/history.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; @@ -24,6 +25,7 @@ class PlaylistCard extends HookConsumerWidget { Widget build(BuildContext context, ref) { final playlistQueue = ref.watch(audioPlayerProvider); final playlistNotifier = ref.watch(audioPlayerProvider.notifier); + final isFetchingActiveTrack = ref.watch(queryingTrackInfoProvider); final historyNotifier = ref.read(playbackHistoryProvider.notifier); final playing = @@ -65,8 +67,7 @@ class PlaylistCard extends HookConsumerWidget { placeholder: ImagePlaceholder.collection, ), isPlaying: isPlaylistPlaying, - isLoading: (isPlaylistPlaying && playlistNotifier.isFetching()) || - updating.value, + isLoading: (isPlaylistPlaying && isFetchingActiveTrack) || updating.value, isOwner: playlist.owner?.id == me.asData?.value.id && me.asData?.value.id != null, onTap: () { diff --git a/lib/provider/audio_player/audio_player.dart b/lib/provider/audio_player/audio_player.dart index 258e15d8..06081b19 100644 --- a/lib/provider/audio_player/audio_player.dart +++ b/lib/provider/audio_player/audio_player.dart @@ -299,11 +299,6 @@ class AudioPlayerNotifier extends Notifier { await audioPlayer.moveTrack(oldIndex, newIndex); } - bool isFetching() { - if (state.activeTrack == null) return false; - return ref.read(sourcedTrackProvider(state.activeTrack!)).isLoading; - } - Future stop() async { await audioPlayer.stop(); ref.read(discordProvider.notifier).clear(); diff --git a/lib/provider/audio_player/querying_track_info.dart b/lib/provider/audio_player/querying_track_info.dart new file mode 100644 index 00000000..4069523b --- /dev/null +++ b/lib/provider/audio_player/querying_track_info.dart @@ -0,0 +1,12 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:spotube/provider/audio_player/audio_player.dart'; +import 'package:spotube/provider/server/sourced_track.dart'; + +final queryingTrackInfoProvider = Provider((ref) { + final activeTrack = + ref.watch(audioPlayerProvider.select((s) => s.activeTrack)); + + if (activeTrack == null) return false; + + return ref.read(sourcedTrackProvider(activeTrack)).isLoading; +});