fix(library): retain Opus badges when bitrate is missing

This commit is contained in:
zarzet
2026-09-06 23:59:23 +07:00
parent ed51b5d6ea
commit 7556d64596
5 changed files with 134 additions and 4 deletions
+3
View File
@@ -110,6 +110,8 @@ class UnifiedLibraryItem {
return buildLibraryAudioQualityLabel(
mode: mode,
format: history.format,
filePath: history.filePath,
fileName: history.safFileName,
bitrateKbps: history.bitrate,
bitDepth: history.bitDepth,
sampleRate: history.sampleRate,
@@ -122,6 +124,7 @@ class UnifiedLibraryItem {
return buildLibraryAudioQualityLabel(
mode: mode,
format: local.format,
filePath: local.filePath,
bitrateKbps: local.bitrate,
bitDepth: local.bitDepth,
sampleRate: local.sampleRate,
+2
View File
@@ -495,6 +495,8 @@ class _DownloadedAlbumScreenState extends ConsumerState<DownloadedAlbumScreen>
String? label(DownloadHistoryItem track) => buildLibraryAudioQualityLabel(
mode: mode,
format: track.format,
filePath: track.filePath,
fileName: track.safFileName,
bitrateKbps: track.bitrate,
bitDepth: track.bitDepth,
sampleRate: track.sampleRate,
+1
View File
@@ -323,6 +323,7 @@ class _LocalAlbumScreenState extends ConsumerState<LocalAlbumScreen>
String? label(LocalLibraryItem track) => buildLibraryAudioQualityLabel(
mode: mode,
format: track.format,
filePath: track.filePath,
bitrateKbps: track.bitrate,
bitDepth: track.bitDepth,
sampleRate: track.sampleRate,
+15 -4
View File
@@ -49,11 +49,16 @@ String? _libraryFileFormatLabel(String? format) {
String? buildLibraryAudioQualityLabel({
required String mode,
String? format,
String? filePath,
String? fileName,
int? bitrateKbps,
int? bitDepth,
int? sampleRate,
String? storedQuality,
}) {
final effectiveFormat =
normalizeOptionalString(format) ??
audioFormatForPath(filePath, fileName: fileName);
final stored = normalizeOptionalString(storedQuality);
final storedBitrate = _bitrateFromStoredQuality(stored);
final storedBitDepth = _bitDepthFromStoredQuality(stored);
@@ -64,7 +69,10 @@ String? buildLibraryAudioQualityLabel({
? bitDepth
: storedBitDepth;
final bitrateLabel = effectiveBitrate != null
? buildDisplayAudioQuality(bitrateKbps: effectiveBitrate, format: format)
? buildDisplayAudioQuality(
bitrateKbps: effectiveBitrate,
format: effectiveFormat,
)
: null;
final bitDepthLabel =
bitDepth != null && bitDepth > 0 && sampleRate != null && sampleRate > 0
@@ -76,11 +84,14 @@ String? buildLibraryAudioQualityLabel({
final normalizedMode = normalizeLibraryQualityLabelMode(mode);
if (normalizedMode == AppSettings.libraryQualityLabelFileFormat) {
return _libraryFileFormatLabel(format);
return _libraryFileFormatLabel(effectiveFormat);
}
if (isLossyAudioFormat(format)) {
return bitrateLabel;
if (isLossyAudioFormat(effectiveFormat)) {
// Older rows may know the codec before their bitrate has been backfilled.
// Keep a useful badge without showing stale lossless specs or inventing a
// bitrate from the user's current download/conversion setting.
return bitrateLabel ?? _libraryFileFormatLabel(effectiveFormat);
}
return switch (normalizedMode) {
+113
View File
@@ -1,8 +1,50 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:spotiflac_android/models/settings.dart';
import 'package:spotiflac_android/models/unified_library_item.dart';
import 'package:spotiflac_android/providers/download_history_provider.dart';
import 'package:spotiflac_android/services/library_database.dart';
import 'package:spotiflac_android/utils/audio_quality_badge_policy.dart';
void main() {
test(
'Library history and local cards retain labels for legacy Opus rows',
() {
final history = UnifiedLibraryItem.fromDownloadHistory(
DownloadHistoryItem(
id: 'history-song',
trackName: 'Song',
artistName: 'Artist',
albumName: 'Album',
filePath: 'content://music/document/42',
safFileName: 'Song.opus',
quality: '16-bit/44.1kHz',
service: 'example',
downloadedAt: DateTime(2026),
),
);
final local = UnifiedLibraryItem.fromLocalLibrary(
LocalLibraryItem(
id: 'local-song',
trackName: 'Song',
artistName: 'Artist',
albumName: 'Album',
filePath: '/music/Song.opus',
scannedAt: DateTime(2026),
),
);
for (final item in [history, local]) {
expect(
item.qualityForMode(AppSettings.libraryQualityLabelBitDepth),
'OPUS',
);
expect(
item.qualityForMode(AppSettings.libraryQualityLabelBitrate),
'OPUS',
);
}
},
);
group('Library audio quality badge color', () {
test('keeps legacy 24-bit labels highlighted', () {
expect(shouldHighlightAudioQualityBadge('24-bit/96kHz'), isTrue);
@@ -25,6 +67,77 @@ void main() {
});
group('Library audio quality label mode', () {
test('keeps an Opus badge when bitrate has not been backfilled', () {
for (final mode in [
AppSettings.libraryQualityLabelBitrate,
AppSettings.libraryQualityLabelBitDepth,
AppSettings.libraryQualityLabelBitDepthOnly,
AppSettings.libraryQualityLabelBitDepthBitrate,
AppSettings.libraryQualityLabelFileFormat,
]) {
expect(
buildLibraryAudioQualityLabel(
mode: mode,
format: 'opus',
bitDepth: 16,
sampleRate: 44100,
storedQuality: '16-bit/44.1kHz',
),
'OPUS',
reason: mode,
);
}
});
test('uses stored Opus bitrate until measured bitrate is available', () {
expect(
buildLibraryAudioQualityLabel(
mode: AppSettings.libraryQualityLabelBitDepth,
format: 'opus',
storedQuality: 'OPUS 320kbps',
),
'OPUS 320kbps',
);
expect(
buildLibraryAudioQualityLabel(
mode: AppSettings.libraryQualityLabelBitDepth,
format: 'opus',
bitrateKbps: 256,
storedQuality: 'OPUS 320kbps',
),
'OPUS 256kbps',
);
expect(formatLibraryGridAudioQualityLabel('OPUS 320kbps'), '320k');
expect(formatLibraryGridAudioQualityLabel('OPUS'), 'OPUS');
});
test('uses the path or SAF name when a legacy row has no format', () {
expect(
buildLibraryAudioQualityLabel(
mode: AppSettings.libraryQualityLabelBitDepth,
filePath: '/music/Song.opus',
storedQuality: '16-bit/44.1kHz',
),
'OPUS',
);
expect(
buildLibraryAudioQualityLabel(
mode: AppSettings.libraryQualityLabelBitrate,
filePath: 'content://music/document/42',
fileName: 'Song.OPUS',
),
'OPUS',
);
expect(
buildLibraryAudioQualityLabel(
mode: AppSettings.libraryQualityLabelFileFormat,
format: 'alac',
filePath: '/music/Song.m4a',
),
'ALAC',
);
});
test('uses measured bitrate by default', () {
expect(
buildLibraryAudioQualityLabel(