fix: artist images are not loading up

This commit is contained in:
Kingkor Roy Tirtho 2025-04-25 16:27:07 +06:00
parent d3edf07ac9
commit 5ff1fc9f82
7 changed files with 136 additions and 10 deletions

View File

@ -25,7 +25,13 @@ class TopArtists extends HookConsumerWidget {
ref.watch(historyTopTracksProvider(historyDuration).notifier); ref.watch(historyTopTracksProvider(historyDuration).notifier);
final artistsData = useMemoized( 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( return Skeletonizer.sliver(
enabled: topTracks.isLoading && !topTracks.isLoadingNextPage, enabled: topTracks.isLoading && !topTracks.isLoadingNextPage,

View File

@ -121,7 +121,16 @@ class SearchPage extends HookConsumerWidget {
} }
}, },
child: AutoComplete( child: AutoComplete(
suggestions: suggestions, suggestions: suggestions.length <= 2
? [
...suggestions,
"Twenty One Pilots",
"Linkin Park",
"d4vd"
]
: suggestions,
completer: (suggestion) => suggestion,
mode: AutoCompleteMode.replaceAll,
child: TextField( child: TextField(
autofocus: true, autofocus: true,
controller: controller, controller: controller,

View File

@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:math'; import 'dart:math';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/models/local_track.dart'; import 'package:spotube/models/local_track.dart';
import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/audio_player/audio_player.dart';
import 'package:spotube/provider/audio_player/state.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/skip_segments/skip_segments.dart';
import 'package:spotube/provider/scrobbler/scrobbler.dart'; import 'package:spotube/provider/scrobbler/scrobbler.dart';
import 'package:spotube/provider/server/sourced_track.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/provider/user_preferences/user_preferences_provider.dart';
import 'package:spotube/services/audio_player/audio_player.dart'; import 'package:spotube/services/audio_player/audio_player.dart';
import 'package:spotube/services/audio_services/audio_services.dart'; import 'package:spotube/services/audio_services/audio_services.dart';
@ -80,7 +82,7 @@ class AudioPlayerStreamListeners {
StreamSubscription subscribeToScrobbleChanged() { StreamSubscription subscribeToScrobbleChanged() {
String? lastScrobbled; String? lastScrobbled;
return audioPlayer.positionStream.listen((position) { return audioPlayer.positionStream.listen((position) async {
try { try {
final uid = audioPlayerState.activeTrack is LocalTrack final uid = audioPlayerState.activeTrack is LocalTrack
? (audioPlayerState.activeTrack as LocalTrack).path ? (audioPlayerState.activeTrack as LocalTrack).path
@ -93,8 +95,23 @@ class AudioPlayerStreamListeners {
} }
scrobbler.scrobble(audioPlayerState.activeTrack!); scrobbler.scrobble(audioPlayerState.activeTrack!);
history.addTrack(audioPlayerState.activeTrack!);
lastScrobbled = uid; 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) { } catch (e, stack) {
AppLogger.reportError(e, stack); AppLogger.reportError(e, stack);
} }

View File

@ -39,6 +39,11 @@ class PlaybackHistoryActions {
} }
Future<void> addTracks(List<Track> tracks) async { Future<void> addTracks(List<Track> tracks) async {
assert(
tracks.every((t) => t.artists?.every((a) => a.images != null) ?? false),
'Track artists must have images',
);
await _batchInsertHistoryEntries([ await _batchInsertHistoryEntries([
for (final track in tracks) for (final track in tracks)
HistoryTableCompanion.insert( HistoryTableCompanion.insert(
@ -50,6 +55,11 @@ class PlaybackHistoryActions {
} }
Future<void> addTrack(Track track) async { Future<void> addTrack(Track track) async {
assert(
track.artists?.every((a) => a.images != null) ?? false,
'Track artists must have images',
);
await _db.into(_db.historyTable).insert( await _db.into(_db.historyTable).insert(
HistoryTableCompanion.insert( HistoryTableCompanion.insert(
type: HistoryEntryType.track, type: HistoryEntryType.track,

View File

@ -28,7 +28,15 @@ class HistoryTopTracksState extends PaginatedState<PlaybackHistoryTrack> {
return groupBy(artists, (artist) => artist.id!) return groupBy(artists, (artist) => artist.id!)
.entries .entries
.map((entry) { .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)) .sorted((a, b) => b.count.compareTo(a.count))
.toList(); .toList();
@ -85,11 +93,58 @@ class HistoryTopTracksNotifier extends FamilyPaginatedAsyncNotifier<
); );
} }
Future<void> fixImageNotLoadingForArtistIssue(
List<HistoryTableData> 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 @override
fetch(arg, offset, limit) async { fetch(arg, offset, limit) async {
final tracksQuery = createTracksQuery()..limit(limit, offset: offset); final tracksQuery = createTracksQuery()..limit(limit, offset: offset);
final items = getTracksWithCount(await tracksQuery.get()); final entries = await tracksQuery.get();
final items = getTracksWithCount(entries);
return ( return (
items: items, items: items,
@ -123,13 +178,26 @@ class HistoryTopTracksNotifier extends FamilyPaginatedAsyncNotifier<
} }
List<PlaybackHistoryTrack> getTracksWithCount(List<HistoryTableData> tracks) { List<PlaybackHistoryTrack> getTracksWithCount(List<HistoryTableData> tracks) {
fixImageNotLoadingForArtistIssue(tracks);
return groupBy( return groupBy(
tracks, tracks,
(track) => track.track!.id!, (track) => track.track!.id!,
) )
.entries .entries
.map((entry) { .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)) .sorted((a, b) => b.count.compareTo(a.count))
.toList(); .toList();

View File

@ -642,6 +642,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.7" 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: fake_async:
dependency: transitive dependency: transitive
description: description:
@ -1919,6 +1927,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.0" version: "1.3.0"
quiver:
dependency: transitive
description:
name: quiver
sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2
url: "https://pub.dev"
source: hosted
version: "3.2.2"
recase: recase:
dependency: transitive dependency: transitive
description: description:
@ -2012,10 +2028,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: shadcn_flutter name: shadcn_flutter
sha256: "2b6faf9a93628469c29a534e653295e26781f2799efe5dc971b91e91062ebf52" sha256: "8635e8e0cd2e0fba0a3093a53fbe3bdd37d90d038c4c66d761728d7cfcf23ce3"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.0.32" version: "0.0.34"
shared_preferences: shared_preferences:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@ -102,7 +102,7 @@ dependencies:
ref: dart-3-support ref: dart-3-support
url: https://github.com/KRTirtho/scrobblenaut.git url: https://github.com/KRTirtho/scrobblenaut.git
scroll_to_index: ^3.0.1 scroll_to_index: ^3.0.1
shadcn_flutter: ^0.0.32 shadcn_flutter: ^0.0.34
shared_preferences: ^2.2.3 shared_preferences: ^2.2.3
shelf: ^1.4.1 shelf: ^1.4.1
shelf_router: ^1.1.4 shelf_router: ^1.1.4