chore: fix artist top tracks play button not showing loading indicator

This commit is contained in:
Kingkor Roy Tirtho 2025-09-05 17:53:06 +06:00
parent 2e48ac380b
commit 69d50eec35
4 changed files with 359 additions and 336 deletions

View File

@ -101,7 +101,9 @@ extension ToMetadataSpotubeFullTrackObject on SpotubeFullTrackObject {
albumArtist: artists.map((a) => a.name).join(", "),
year: album.releaseDate == null
? 1970
: DateTime.parse(album.releaseDate!).year,
: DateTime.tryParse(album.releaseDate!)?.year ??
int.tryParse(album.releaseDate!) ??
1970,
durationMs: durationMs.toDouble(),
fileSize: BigInt.from(fileLength),
picture: imageBytes != null

View File

@ -1,5 +1,7 @@
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:shadcn_flutter/shadcn_flutter_extension.dart';
import 'package:skeletonizer/skeletonizer.dart';
import 'package:spotube/collections/fake.dart';
import 'package:spotube/collections/spotube_icons.dart';
@ -19,6 +21,7 @@ class ArtistPageTopTracks extends HookConsumerWidget {
@override
Widget build(BuildContext context, ref) {
final theme = Theme.of(context);
final isLoading = useState(false);
final playlist = ref.watch(audioPlayerProvider);
final playlistNotifier = ref.watch(audioPlayerProvider.notifier);
@ -40,10 +43,14 @@ class ArtistPageTopTracks extends HookConsumerWidget {
final topTracks = topTracksQuery.asData?.value.items ??
List.generate(10, (index) => FakeData.track);
void playPlaylist(List<SpotubeFullTrackObject> tracks,
{SpotubeTrackObject? currentTrack}) async {
currentTrack ??= tracks.first;
void playPlaylist(
List<SpotubeFullTrackObject> tracks, {
SpotubeTrackObject? currentTrack,
}) async {
isLoading.value = true;
currentTrack ??= tracks.first;
try {
final isRemoteDevice = await showSelectDeviceDialog(context, ref);
if (isRemoteDevice == null) return;
@ -59,7 +66,8 @@ class ArtistPageTopTracks extends HookConsumerWidget {
WebSocketLoadEventData.playlist(
tracks: tracks,
collection: null,
initialIndex: tracks.indexWhere((s) => s.id == currentTrack?.id),
initialIndex:
tracks.indexWhere((s) => s.id == currentTrack?.id),
),
);
} else if (isPlaylistPlaying &&
@ -81,6 +89,9 @@ class ArtistPageTopTracks extends HookConsumerWidget {
await playlistNotifier.jumpToTrack(currentTrack);
}
}
} finally {
isLoading.value = false;
}
}
return SliverMainAxisGroup(
@ -120,10 +131,17 @@ class ArtistPageTopTracks extends HookConsumerWidget {
const SizedBox(width: 5),
IconButton.primary(
shape: ButtonShape.circle,
enabled: !isPlaylistPlaying,
icon: Skeleton.keep(
enabled: !isPlaylistPlaying && !isLoading.value,
icon: isLoading.value
? CircularProgressIndicator(
size: 20 * context.theme.scaling,
color: theme.colorScheme.primaryForeground,
)
: Skeleton.keep(
child: Icon(
isPlaylistPlaying ? SpotubeIcons.pause : SpotubeIcons.play,
isPlaylistPlaying
? SpotubeIcons.pause
: SpotubeIcons.play,
),
),
onPressed: () => playPlaylist(topTracks.toList()),

View File

@ -225,15 +225,12 @@ class LocalLibraryPage extends HookConsumerWidget {
Button.primary(
onPressed: trackSnapshot.asData?.value != null
? () async {
if (trackSnapshot
.asData?.value.isNotEmpty ==
if (trackSnapshot.asData?.value.isNotEmpty ==
true) {
if (!isPlaylistPlaying) {
await playLocalTracks(
ref,
trackSnapshot
.asData!.value[location] ??
[],
trackSnapshot.asData!.value[location] ?? [],
);
}
}
@ -294,8 +291,7 @@ class LocalLibraryPage extends HookConsumerWidget {
data: (tracks) {
final sortedTracks = useMemoized(() {
return ServiceUtils.sortTracks(
tracks[location] ??
<SpotubeLocalTrackObject>[],
tracks[location] ?? <SpotubeLocalTrackObject>[],
sortBy.value);
}, [sortBy.value, tracks]);
@ -321,8 +317,7 @@ class LocalLibraryPage extends HookConsumerWidget {
.toList();
}, [searchController.text, sortedTracks]);
if (!trackSnapshot.isLoading &&
filteredTracks.isEmpty) {
if (!trackSnapshot.isLoading && filteredTracks.isEmpty) {
return Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
@ -353,8 +348,7 @@ class LocalLibraryPage extends HookConsumerWidget {
enabled: trackSnapshot.isLoading,
child: ListView.builder(
controller: controller,
physics:
const AlwaysScrollableScrollPhysics(),
physics: const AlwaysScrollableScrollPhysics(),
itemCount: trackSnapshot.isLoading
? 5
: filteredTracks.length,
@ -406,7 +400,9 @@ class LocalLibraryPage extends HookConsumerWidget {
);
})
],
))),
),
),
),
);
}
}

View File

@ -393,6 +393,7 @@ class YoutubeSourcedTrack extends SourcedTrack {
Future<SourcedTrack> refreshStream() async {
List<TrackSource> validStreams = [];
final stringBuffer = StringBuffer();
for (final source in sources) {
final res = await globalDio.head(
source.url,
@ -400,11 +401,17 @@ class YoutubeSourcedTrack extends SourcedTrack {
Options(validateStatus: (status) => status != null && status < 500),
);
stringBuffer.writeln(
"[${query.id}] ${res.statusCode} ${source.quality} ${source.codec} ${source.bitrate}",
);
if (res.statusCode! < 400) {
validStreams.add(source);
}
}
AppLogger.log.d(stringBuffer.toString());
if (validStreams.isEmpty) {
final manifest =
await ref.read(youtubeEngineProvider).getStreamManifest(info.id);