fix(download): preserve finalization metadata and library labels

This commit is contained in:
zarzet
2026-08-29 00:31:25 +07:00
parent c3b2214e2a
commit 5dac33dfa4
11 changed files with 264 additions and 26 deletions
@@ -1,6 +1,8 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:spotiflac_android/utils/audio_format_utils.dart';
import 'package:spotiflac_android/utils/audio_quality_badge_policy.dart';
import 'package:spotiflac_android/services/downloaded_embedded_cover_resolver.dart';
import 'package:spotiflac_android/services/library_database.dart';
import 'package:spotiflac_android/utils/file_access.dart';
@@ -31,6 +33,37 @@ bool shouldRetainQueueLibraryPageSnapshot({
required bool activeDownloadFallbackAvailable,
}) => currentIsEmpty && cachedHasContent && activeDownloadFallbackAvailable;
/// Keeps a completed queue card visible until the matching Library row has
/// actually landed. A completed item with an in-memory History row must not
/// expire merely because the rest of its album batch was cancelled: the
/// paged Library query can still be one refresh behind that persisted row.
bool shouldRetainCompletionBridge({
required bool isRequeued,
required bool hasActiveDownloads,
required bool libraryRowLanded,
required bool hasHistoryItem,
required bool expired,
}) {
if (isRequeued || libraryRowLanded) return false;
if (hasActiveDownloads || hasHistoryItem) return true;
return !expired;
}
/// Builds a mode-aware label while a just-completed item is waiting for its
/// History row. This avoids pinning the card to the track's old quality text
/// when the user changes the Library label setting during an album download.
String? buildCompletionBridgeFallbackQualityLabel({
required String mode,
required String? completedItemFilePath,
required String? storedQuality,
}) {
return buildLibraryAudioQualityLabel(
mode: mode,
format: audioFormatForPath(completedItemFilePath),
storedQuality: storedQuality,
);
}
/// Returns distinct final-path candidates for a just-completed download.
/// History is authoritative after conversion/SAF publication, while the
/// completed queue item remains a safe fallback if the matched history row is
+10 -2
View File
@@ -121,7 +121,11 @@ extension _QueueTabCollectionItemWidgets on _QueueTabState {
: UnifiedLibraryItem.fromDownloadHistory(historyItem);
final quality =
unifiedItem?.qualityForMode(_libraryQualityLabelMode) ??
track.audioQuality;
buildCompletionBridgeFallbackQualityLabel(
mode: _libraryQualityLabelMode,
completedItemFilePath: item.filePath,
storedQuality: track.audioQuality,
);
final playablePath = resolveCompletionBridgePlayablePath(
historyFilePath: historyItem?.filePath,
completedItemFilePath: item.filePath,
@@ -258,7 +262,11 @@ extension _QueueTabCollectionItemWidgets on _QueueTabState {
: UnifiedLibraryItem.fromDownloadHistory(historyItem);
final quality =
unifiedItem?.qualityForMode(_libraryQualityLabelMode) ??
track.audioQuality;
buildCompletionBridgeFallbackQualityLabel(
mode: _libraryQualityLabelMode,
completedItemFilePath: item.filePath,
storedQuality: track.audioQuality,
);
final playablePath = resolveCompletionBridgePlayablePath(
historyFilePath: historyItem?.filePath,
completedItemFilePath: item.filePath,
+14 -11
View File
@@ -81,23 +81,26 @@ extension _QueueTabFilterWidgets on _QueueTabState {
final pending = <String>[];
final hasActiveDownloads = activeDownloadIds.isNotEmpty;
_completionBridge.forEach((id, _) {
final historyId = bridgeHistoryById[id]?.id ?? id;
final historyItem = bridgeHistoryById[id];
final historyId = historyItem?.id ?? id;
final landed = libIdSet.contains('dl_$historyId');
final addedAt = _completionBridgeAt[id];
final expired =
addedAt == null || now.difference(addedAt).inSeconds >= 6;
if (activeDownloadIds.contains(id)) {
// Re-queued (retry): the live row takes over from the bridge.
stale.add(id);
} else if (hasActiveDownloads) {
// Keep just-completed tracks pinned in the lead zone while the
// rest of the batch is still downloading, so they don't jump
// below the remaining queue the moment they finish.
if (shouldRetainCompletionBridge(
isRequeued: activeDownloadIds.contains(id),
hasActiveDownloads: hasActiveDownloads,
libraryRowLanded: landed,
hasHistoryItem: historyItem != null,
expired: expired,
)) {
// Keep completed tracks pinned while their batch is active or their
// persisted History row is still waiting for the paged Library.
pending.add(id);
} else if (landed || expired) {
stale.add(id);
} else {
pending.add(id);
// Re-queued items are represented by the live row; landed items by
// the normal Library row; unpersisted bridges retain a short grace.
stale.add(id);
}
});
bridgeIds = pending;