From 5ff1fc9f829f6950632b5c90229a24cc20f04c38 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Fri, 25 Apr 2025 16:27:07 +0600 Subject: [PATCH] fix: artist images are not loading up --- lib/modules/stats/top/artists.dart | 8 +- lib/pages/search/search.dart | 11 ++- .../audio_player/audio_player_streams.dart | 21 +++++- lib/provider/history/history.dart | 10 +++ lib/provider/history/top/tracks.dart | 74 ++++++++++++++++++- pubspec.lock | 20 ++++- pubspec.yaml | 2 +- 7 files changed, 136 insertions(+), 10 deletions(-) diff --git a/lib/modules/stats/top/artists.dart b/lib/modules/stats/top/artists.dart index c53c34fd..cb2a152f 100644 --- a/lib/modules/stats/top/artists.dart +++ b/lib/modules/stats/top/artists.dart @@ -25,7 +25,13 @@ class TopArtists extends HookConsumerWidget { ref.watch(historyTopTracksProvider(historyDuration).notifier); final artistsData = useMemoized( - () => topTracks.asData?.value.artists ?? [], [topTracks.asData?.value]); + () => topTracks.asData?.value.artists ?? [], + [topTracks.asData?.value], + ); + + for (final artist in artistsData) { + print("${artist.artist.name} has ${artist.artist.images?.length} images"); + } return Skeletonizer.sliver( enabled: topTracks.isLoading && !topTracks.isLoadingNextPage, diff --git a/lib/pages/search/search.dart b/lib/pages/search/search.dart index eeedfb9c..c0a244a5 100644 --- a/lib/pages/search/search.dart +++ b/lib/pages/search/search.dart @@ -121,7 +121,16 @@ class SearchPage extends HookConsumerWidget { } }, child: AutoComplete( - suggestions: suggestions, + suggestions: suggestions.length <= 2 + ? [ + ...suggestions, + "Twenty One Pilots", + "Linkin Park", + "d4vd" + ] + : suggestions, + completer: (suggestion) => suggestion, + mode: AutoCompleteMode.replaceAll, child: TextField( autofocus: true, controller: controller, diff --git a/lib/provider/audio_player/audio_player_streams.dart b/lib/provider/audio_player/audio_player_streams.dart index 54c6d7cd..fbcf74e4 100644 --- a/lib/provider/audio_player/audio_player_streams.dart +++ b/lib/provider/audio_player/audio_player_streams.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:math'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:spotify/spotify.dart'; import 'package:spotube/models/local_track.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/audio_player/state.dart'; @@ -10,6 +11,7 @@ import 'package:spotube/provider/history/history.dart'; import 'package:spotube/provider/skip_segments/skip_segments.dart'; import 'package:spotube/provider/scrobbler/scrobbler.dart'; import 'package:spotube/provider/server/sourced_track.dart'; +import 'package:spotube/provider/spotify/spotify.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; import 'package:spotube/services/audio_services/audio_services.dart'; @@ -80,7 +82,7 @@ class AudioPlayerStreamListeners { StreamSubscription subscribeToScrobbleChanged() { String? lastScrobbled; - return audioPlayer.positionStream.listen((position) { + return audioPlayer.positionStream.listen((position) async { try { final uid = audioPlayerState.activeTrack is LocalTrack ? (audioPlayerState.activeTrack as LocalTrack).path @@ -93,8 +95,23 @@ class AudioPlayerStreamListeners { } scrobbler.scrobble(audioPlayerState.activeTrack!); - history.addTrack(audioPlayerState.activeTrack!); lastScrobbled = uid; + + /// The [Track] from Playlist.getTracks doesn't contain artist images + /// so we need to fetch them from the API + final activeTrack = + Track.fromJson(audioPlayerState.activeTrack!.toJson()); + if (audioPlayerState.activeTrack!.artists + ?.any((a) => a.images == null) ?? + false) { + activeTrack.artists = + await ref.read(spotifyProvider).api.artists.list([ + for (final artist in audioPlayerState.activeTrack!.artists!) + artist.id!, + ]).then((value) => value.toList()); + } + + await history.addTrack(activeTrack); } catch (e, stack) { AppLogger.reportError(e, stack); } diff --git a/lib/provider/history/history.dart b/lib/provider/history/history.dart index 0c20a9e5..25b722ff 100644 --- a/lib/provider/history/history.dart +++ b/lib/provider/history/history.dart @@ -39,6 +39,11 @@ class PlaybackHistoryActions { } Future addTracks(List tracks) async { + assert( + tracks.every((t) => t.artists?.every((a) => a.images != null) ?? false), + 'Track artists must have images', + ); + await _batchInsertHistoryEntries([ for (final track in tracks) HistoryTableCompanion.insert( @@ -50,6 +55,11 @@ class PlaybackHistoryActions { } Future addTrack(Track track) async { + assert( + track.artists?.every((a) => a.images != null) ?? false, + 'Track artists must have images', + ); + await _db.into(_db.historyTable).insert( HistoryTableCompanion.insert( type: HistoryEntryType.track, diff --git a/lib/provider/history/top/tracks.dart b/lib/provider/history/top/tracks.dart index b737d148..3c057e56 100644 --- a/lib/provider/history/top/tracks.dart +++ b/lib/provider/history/top/tracks.dart @@ -28,7 +28,15 @@ class HistoryTopTracksState extends PaginatedState { return groupBy(artists, (artist) => artist.id!) .entries .map((entry) { - return (count: entry.value.length, artist: entry.value.first); + return ( + count: entry.value.length, + + /// Previously, due to a bug, artist images were not being saved. + /// Now it's fixed, but we need to handle the case where images are null. + /// So we take the first artist with images if available, otherwise the first one. + artist: entry.value.firstWhereOrNull((a) => a.images != null) ?? + entry.value.first, + ); }) .sorted((a, b) => b.count.compareTo(a.count)) .toList(); @@ -85,11 +93,58 @@ class HistoryTopTracksNotifier extends FamilyPaginatedAsyncNotifier< ); } + Future fixImageNotLoadingForArtistIssue( + List entries, + ) async { + final nonImageArtistTracks = + entries.where((e) => e.track!.artists!.any((a) => a.images == null)); + + if (nonImageArtistTracks.isEmpty) return; + + final artistIds = nonImageArtistTracks + .map((e) => e.track!.artists!.map((a) => a.id!)) + .expand((e) => e) + .toSet() + .toList(); + + if (artistIds.isEmpty) return; + + final artists = await ref.read(spotifyProvider).api.artists.list(artistIds); + + final imagedArtistTracks = nonImageArtistTracks.map((e) { + final track = e.track!; + final includedArtists = track.artists! + .map((a) => artists.firstWhereOrNull((artist) => artist.id == a.id)) + .nonNulls + .toList(); + + track.artists = includedArtists; + + return e.copyWith(data: track.toJson()); + }); + + assert( + imagedArtistTracks + .every((e) => e.track!.artists!.every((a) => a.images != null)), + 'Tracks artists should have images', + ); + + final database = ref.read(databaseProvider); + await database.batch((batch) { + batch.insertAllOnConflictUpdate( + database.historyTable, + imagedArtistTracks, + ); + }); + } + @override fetch(arg, offset, limit) async { final tracksQuery = createTracksQuery()..limit(limit, offset: offset); - final items = getTracksWithCount(await tracksQuery.get()); + final entries = await tracksQuery.get(); + + final items = getTracksWithCount(entries); return ( items: items, @@ -123,13 +178,26 @@ class HistoryTopTracksNotifier extends FamilyPaginatedAsyncNotifier< } List getTracksWithCount(List tracks) { + fixImageNotLoadingForArtistIssue(tracks); + return groupBy( tracks, (track) => track.track!.id!, ) .entries .map((entry) { - return (count: entry.value.length, track: entry.value.first.track!); + return ( + count: entry.value.length, + + /// Previously, due to a bug, artist images were not being saved. + /// Now it's fixed, but we need to handle the case where images are null. + /// So we take the first artist with images if available, otherwise the first one. + track: entry.value + .firstWhereOrNull( + (t) => t.track!.artists!.every((a) => a.images != null)) + ?.track! ?? + entry.value.first.track!, + ); }) .sorted((a, b) => b.count.compareTo(a.count)) .toList(); diff --git a/pubspec.lock b/pubspec.lock index 1ddc1705..4744467d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -642,6 +642,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.7" + expressions: + dependency: transitive + description: + name: expressions + sha256: "308a621b602923dd8a0cf3072793b24850d06453eb49c6b698cbda41a282e904" + url: "https://pub.dev" + source: hosted + version: "0.2.5+2" fake_async: dependency: transitive description: @@ -1919,6 +1927,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" + quiver: + dependency: transitive + description: + name: quiver + sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2 + url: "https://pub.dev" + source: hosted + version: "3.2.2" recase: dependency: transitive description: @@ -2012,10 +2028,10 @@ packages: dependency: "direct main" description: name: shadcn_flutter - sha256: "2b6faf9a93628469c29a534e653295e26781f2799efe5dc971b91e91062ebf52" + sha256: "8635e8e0cd2e0fba0a3093a53fbe3bdd37d90d038c4c66d761728d7cfcf23ce3" url: "https://pub.dev" source: hosted - version: "0.0.32" + version: "0.0.34" shared_preferences: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index dbd97b91..9e464205 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -102,7 +102,7 @@ dependencies: ref: dart-3-support url: https://github.com/KRTirtho/scrobblenaut.git scroll_to_index: ^3.0.1 - shadcn_flutter: ^0.0.32 + shadcn_flutter: ^0.0.34 shared_preferences: ^2.2.3 shelf: ^1.4.1 shelf_router: ^1.1.4