feat: show loading indicator on play track

This commit is contained in:
Kingkor Roy Tirtho 2023-09-11 11:05:42 +06:00
parent 1540999f50
commit d12ea48b97
7 changed files with 57 additions and 47 deletions

View File

@ -131,7 +131,7 @@ final localTracksProvider = FutureProvider<List<LocalTrack>>((ref) async {
class UserLocalTracks extends HookConsumerWidget { class UserLocalTracks extends HookConsumerWidget {
const UserLocalTracks({Key? key}) : super(key: key); const UserLocalTracks({Key? key}) : super(key: key);
void playLocalTracks( Future<void> playLocalTracks(
WidgetRef ref, WidgetRef ref,
List<LocalTrack> tracks, { List<LocalTrack> tracks, {
LocalTrack? currentTrack, LocalTrack? currentTrack,
@ -203,10 +203,10 @@ class UserLocalTracks extends HookConsumerWidget {
const SizedBox(width: 10), const SizedBox(width: 10),
FilledButton( FilledButton(
onPressed: trackSnapshot.value != null onPressed: trackSnapshot.value != null
? () { ? () async {
if (trackSnapshot.value?.isNotEmpty == true) { if (trackSnapshot.value?.isNotEmpty == true) {
if (!isPlaylistPlaying) { if (!isPlaylistPlaying) {
playLocalTracks( await playLocalTracks(
ref, ref,
trackSnapshot.value!, trackSnapshot.value!,
); );
@ -295,8 +295,8 @@ class UserLocalTracks extends HookConsumerWidget {
index: index, index: index,
track: track, track: track,
userPlaylist: false, userPlaylist: false,
onTap: () { onTap: () async {
playLocalTracks( await playLocalTracks(
ref, ref,
sortedTracks, sortedTracks,
currentTrack: track, currentTrack: track,

View File

@ -1,3 +1,5 @@
import 'dart:async';
import 'package:fl_query/fl_query.dart'; import 'package:fl_query/fl_query.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
@ -27,7 +29,7 @@ class TrackCollectionView<T> extends HookConsumerWidget {
final Query<List<TrackSimple>, T> tracksSnapshot; final Query<List<TrackSimple>, T> tracksSnapshot;
final String titleImage; final String titleImage;
final PlayButtonState playingState; final PlayButtonState playingState;
final void Function([Track? currentTrack]) onPlay; final Future<void> Function([Track? currentTrack]) onPlay;
final void Function([Track? currentTrack]) onShuffledPlay; final void Function([Track? currentTrack]) onShuffledPlay;
final void Function() onAddToQueue; final void Function() onAddToQueue;
final void Function() onShare; final void Function() onShare;

View File

@ -1,3 +1,5 @@
import 'dart:async';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
@ -21,7 +23,7 @@ class TrackTile extends HookConsumerWidget {
final Track track; final Track track;
final bool selected; final bool selected;
final ValueChanged<bool?>? onChanged; final ValueChanged<bool?>? onChanged;
final VoidCallback? onTap; final Future<void> Function()? onTap;
final VoidCallback? onLongPress; final VoidCallback? onLongPress;
final bool userPlaylist; final bool userPlaylist;
final String? playlistId; final String? playlistId;
@ -62,6 +64,10 @@ class TrackTile extends HookConsumerWidget {
final isPlaying = track.id == playlist.activeTrack?.id; final isPlaying = track.id == playlist.activeTrack?.id;
final isLoading = useState(false);
final isSelected = isPlaying || isLoading.value;
return LayoutBuilder(builder: (context, constrains) { return LayoutBuilder(builder: (context, constrains) {
return Listener( return Listener(
onPointerDown: (event) { onPointerDown: (event) {
@ -76,11 +82,18 @@ class TrackTile extends HookConsumerWidget {
); );
}, },
child: HoverBuilder( child: HoverBuilder(
permanentState: isPlaying || constrains.smAndDown ? true : null, permanentState: isSelected || constrains.smAndDown ? true : null,
builder: (context, isHovering) { builder: (context, isHovering) {
return ListTile( return ListTile(
selected: isPlaying, selected: isSelected,
onTap: onTap, onTap: () async {
try {
isLoading.value = true;
await onTap?.call();
} finally {
isLoading.value = false;
}
},
onLongPress: onLongPress, onLongPress: onLongPress,
enabled: !isBlackListed, enabled: !isBlackListed,
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
@ -145,22 +158,23 @@ class TrackTile extends HookConsumerWidget {
.copyWith(size: 26, color: Colors.white), .copyWith(size: 26, color: Colors.white),
child: AnimatedSwitcher( child: AnimatedSwitcher(
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
child: !isHovering child: (isPlaying && playlist.isFetching) ||
? const SizedBox.shrink() isLoading.value
: isPlaying && playlist.isFetching ? const SizedBox(
? const SizedBox( width: 26,
width: 26, height: 26,
height: 26, child: CircularProgressIndicator(
child: CircularProgressIndicator( strokeWidth: 1.5,
strokeWidth: 1.5, color: Colors.white,
color: Colors.white, ),
), )
: isPlaying
? Icon(
SpotubeIcons.pause,
color: theme.colorScheme.primary,
) )
: isPlaying : !isHovering
? Icon( ? const SizedBox.shrink()
SpotubeIcons.pause,
color: theme.colorScheme.primary,
)
: const Icon(SpotubeIcons.play), : const Icon(SpotubeIcons.play),
), ),
), ),

View File

@ -1,3 +1,5 @@
import 'dart:async';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
@ -27,7 +29,7 @@ final trackCollectionSortState =
StateProvider.family<SortBy, String>((ref, _) => SortBy.none); StateProvider.family<SortBy, String>((ref, _) => SortBy.none);
class TracksTableView extends HookConsumerWidget { class TracksTableView extends HookConsumerWidget {
final void Function(Track currentTrack)? onTrackPlayButtonPressed; final Future<void> Function(Track currentTrack)? onTrackPlayButtonPressed;
final List<Track> tracks; final List<Track> tracks;
final bool userPlaylist; final bool userPlaylist;
final String? playlistId; final String? playlistId;
@ -58,8 +60,7 @@ class TracksTableView extends HookConsumerWidget {
final downloader = ref.watch(downloadManagerProvider.notifier); final downloader = ref.watch(downloadManagerProvider.notifier);
final apiType = final apiType =
ref.watch(userPreferencesProvider.select((s) => s.youtubeApiType)); ref.watch(userPreferencesProvider.select((s) => s.youtubeApiType));
final tableHeadStyle = const tableHeadStyle = TextStyle(fontWeight: FontWeight.bold, fontSize: 16);
const TextStyle(fontWeight: FontWeight.bold, fontSize: 16);
final selected = useState<List<String>>([]); final selected = useState<List<String>>([]);
final showCheck = useState<bool>(false); final showCheck = useState<bool>(false);
@ -297,7 +298,7 @@ class TracksTableView extends HookConsumerWidget {
selected: selected.value.contains(track.id), selected: selected.value.contains(track.id),
userPlaylist: userPlaylist, userPlaylist: userPlaylist,
playlistId: playlistId, playlistId: playlistId,
onTap: () { onTap: () async {
if (showCheck.value) { if (showCheck.value) {
final alreadyChecked = selected.value.contains(track.id); final alreadyChecked = selected.value.contains(track.id);
if (alreadyChecked) { if (alreadyChecked) {
@ -314,9 +315,8 @@ class TracksTableView extends HookConsumerWidget {
), ),
), ),
); );
if (!isBlackListed) { if (isBlackListed) return;
onTrackPlayButtonPressed?.call(track); await onTrackPlayButtonPressed?.call(track);
}
} }
}, },
onLongPress: () { onLongPress: () {

View File

@ -85,10 +85,10 @@ class AlbumPage extends HookConsumerWidget {
album: album, album: album,
routePath: "/album/${album.id}", routePath: "/album/${album.id}",
bottomSpace: mediaQuery.mdAndDown, bottomSpace: mediaQuery.mdAndDown,
onPlay: ([track]) { onPlay: ([track]) async {
if (tracksSnapshot.hasData) { if (tracksSnapshot.hasData) {
if (!isAlbumPlaying) { if (!isAlbumPlaying) {
playPlaylist( await playPlaylist(
tracksSnapshot.data! tracksSnapshot.data!
.map((track) => .map((track) =>
TypeConversionUtils.simpleTrack_X_Track(track, album)) TypeConversionUtils.simpleTrack_X_Track(track, album))
@ -96,7 +96,7 @@ class AlbumPage extends HookConsumerWidget {
ref, ref,
); );
} else if (isAlbumPlaying && track != null) { } else if (isAlbumPlaying && track != null) {
playPlaylist( await playPlaylist(
tracksSnapshot.data! tracksSnapshot.data!
.map((track) => .map((track) =>
TypeConversionUtils.simpleTrack_X_Track(track, album)) TypeConversionUtils.simpleTrack_X_Track(track, album))
@ -105,7 +105,7 @@ class AlbumPage extends HookConsumerWidget {
ref, ref,
); );
} else { } else {
playback await playback
.removeTracks(tracksSnapshot.data!.map((track) => track.id!)); .removeTracks(tracksSnapshot.data!.map((track) => track.id!));
} }
} }

View File

@ -390,7 +390,7 @@ class ArtistPage extends HookConsumerWidget {
return TrackTile( return TrackTile(
index: i, index: i,
track: track, track: track,
onTap: () { onTap: () async {
playPlaylist( playPlaylist(
topTracks.toList(), topTracks.toList(),
currentTrack: track, currentTrack: track,

View File

@ -104,22 +104,16 @@ class PlaylistView extends HookConsumerWidget {
tracksSnapshot: tracksSnapshot, tracksSnapshot: tracksSnapshot,
description: playlist.description, description: playlist.description,
isOwned: ownPlaylist, isOwned: ownPlaylist,
onPlay: ([track]) { onPlay: ([track]) async {
if (tracksSnapshot.hasData) { if (tracksSnapshot.hasData) {
if (!isPlaylistPlaying) { if (!isPlaylistPlaying || (isPlaylistPlaying && track != null)) {
playPlaylist( await playPlaylist(
tracksSnapshot.data!,
ref,
currentTrack: track,
);
} else if (isPlaylistPlaying && track != null) {
playPlaylist(
tracksSnapshot.data!, tracksSnapshot.data!,
ref, ref,
currentTrack: track, currentTrack: track,
); );
} else { } else {
playlistNotifier await playlistNotifier
.removeTracks(tracksSnapshot.data!.map((e) => e.id!)); .removeTracks(tracksSnapshot.data!.map((e) => e.id!));
} }
} }