fix(library): show play actions during batch downloads

This commit is contained in:
zarzet
2026-08-27 13:56:48 +07:00
parent 47deb9f34e
commit 4609e59806
7 changed files with 115 additions and 35 deletions
@@ -1135,6 +1135,61 @@ final downloadHistoryBatchExistsProvider = FutureProvider.autoDispose
};
});
bool historyLookupExistsInMemory(
DownloadHistoryState state,
HistoryLookupRequest request,
) {
for (final candidate in HistoryDatabase.spotifyLookupCandidates(
request.spotifyId,
)) {
if (state.getBySpotifyId(candidate) != null) return true;
}
final isrc = request.isrc?.trim();
if (isrc != null && isrc.isNotEmpty && state.getByIsrc(isrc) != null) {
return true;
}
return state.findByTrackAndArtist(request.trackName, request.artistName) !=
null;
}
Set<String> mergeVisibleHistoryLookupKeys({
required DownloadHistoryState state,
required HistoryBatchLookupRequest request,
required Set<String> persistedKeys,
}) {
final keys = <String>{...persistedKeys};
for (final track in request.tracks) {
if (historyLookupExistsInMemory(state, track)) {
keys.add(track.lookupKey);
}
}
return keys;
}
/// UI-facing history lookup that keeps the last database result visible while
/// a batch query reloads and immediately merges downloads persisted in memory.
/// This prevents completed tracks from losing their Play action until the
/// remainder of a large download batch finishes.
final downloadHistoryVisibleBatchExistsProvider = Provider.autoDispose
.family<Set<String>, HistoryBatchLookupRequest>((ref, request) {
final state = ref.watch(downloadHistoryProvider);
final persistedKeys = ref
.watch(downloadHistoryBatchExistsProvider(request))
.maybeWhen(
skipLoadingOnReload: true,
skipLoadingOnRefresh: true,
data: (keys) => keys,
orElse: () => const <String>{},
);
return mergeVisibleHistoryLookupKeys(
state: state,
request: request,
persistedKeys: persistedKeys,
);
});
class DownloadedAlbumTracksRequest {
final String albumName;
final String artistName;
+5 -7
View File
@@ -552,13 +552,11 @@ class _AlbumScreenState extends ConsumerState<AlbumScreen>
final historyLookups = tracks
.map(historyLookupForTrack)
.toList(growable: false);
final existingHistoryKeys = ref
.watch(
downloadHistoryBatchExistsProvider(
HistoryBatchLookupRequest(historyLookups),
),
)
.maybeWhen(data: (keys) => keys, orElse: () => const <String>{});
final existingHistoryKeys = ref.watch(
downloadHistoryVisibleBatchExistsProvider(
HistoryBatchLookupRequest(historyLookups),
),
);
return SliverPadding(
padding: EdgeInsets.symmetric(horizontal: wideListInset(context)),
sliver: SliverList(
+5 -7
View File
@@ -311,13 +311,11 @@ extension _ArtistScreenSections on _ArtistScreenState {
final historyLookups = tracks
.map(historyLookupForTrack)
.toList(growable: false);
final existingHistoryKeys = ref
.watch(
downloadHistoryBatchExistsProvider(
HistoryBatchLookupRequest(historyLookups),
),
)
.maybeWhen(data: (keys) => keys, orElse: () => const <String>{});
final existingHistoryKeys = ref.watch(
downloadHistoryVisibleBatchExistsProvider(
HistoryBatchLookupRequest(historyLookups),
),
);
const tracksPerPage = 5;
final pageCount = (tracks.length / tracksPerPage).ceil();
+5 -7
View File
@@ -406,13 +406,11 @@ extension _HomeTabSearchResultsUI on _HomeTabState {
final historyLookups = sortedTracks
.map(historyLookupForTrack)
.toList(growable: false);
final existingHistoryKeys = ref
.watch(
downloadHistoryBatchExistsProvider(
HistoryBatchLookupRequest(historyLookups),
),
)
.maybeWhen(data: (keys) => keys, orElse: () => const <String>{});
final existingHistoryKeys = ref.watch(
downloadHistoryVisibleBatchExistsProvider(
HistoryBatchLookupRequest(historyLookups),
),
);
slivers.addAll(
_buildVirtualizedResultSection(
title: context.l10n.searchSongs,
@@ -302,13 +302,11 @@ class _LibraryTracksFolderScreenState
final historyLookups = folderTracks
.map(historyLookupForTrack)
.toList(growable: false);
final existingHistoryKeys = ref
.watch(
downloadHistoryBatchExistsProvider(
HistoryBatchLookupRequest(historyLookups),
),
)
.maybeWhen(data: (keys) => keys, orElse: () => const <String>{});
final existingHistoryKeys = ref.watch(
downloadHistoryVisibleBatchExistsProvider(
HistoryBatchLookupRequest(historyLookups),
),
);
final bottomPadding = MediaQuery.paddingOf(context).bottom;
final bottomInset = context.navBarBottomInset;
+5 -7
View File
@@ -399,13 +399,11 @@ class _PlaylistScreenState extends ConsumerState<PlaylistScreen>
final historyLookups = _tracks
.map(historyLookupForTrack)
.toList(growable: false);
final existingHistoryKeys = ref
.watch(
downloadHistoryBatchExistsProvider(
HistoryBatchLookupRequest(historyLookups),
),
)
.maybeWhen(data: (keys) => keys, orElse: () => const <String>{});
final existingHistoryKeys = ref.watch(
downloadHistoryVisibleBatchExistsProvider(
HistoryBatchLookupRequest(historyLookups),
),
);
return SliverPadding(
padding: EdgeInsets.symmetric(horizontal: wideListInset(context)),
sliver: SliverList(
+35
View File
@@ -93,6 +93,41 @@ void main() {
expect(state.items, hasLength(2));
});
test('recent in-memory completion is visible during batch reload', () {
final completed = _historyItem(
id: 'completed',
filePath: '/music/Album/Same Song.flac',
downloadedAt: DateTime.utc(2026, 8, 27),
);
final state = DownloadHistoryState(
items: [completed],
lookupItems: [completed],
totalCount: 1,
);
const completedLookup = HistoryLookupRequest(
spotifyId: 'same',
isrc: 'SAME-ISRC',
trackName: 'Same Song',
artistName: 'Same Artist',
);
const olderLookup = HistoryLookupRequest(
spotifyId: 'older',
trackName: 'Older Song',
artistName: 'Same Artist',
);
final visible = mergeVisibleHistoryLookupKeys(
state: state,
request: const HistoryBatchLookupRequest([
completedLookup,
olderLookup,
]),
persistedKeys: {olderLookup.lookupKey},
);
expect(visible, {completedLookup.lookupKey, olderLookup.lookupKey});
});
test('actual audio quality survives history serialization', () {
final item =
_historyItem(