mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-05-17 22:04:47 +02:00
fix: skip tracks already in FLAC from queue-as-FLAC selection and fix local album track list widget identity
This commit is contained in:
@@ -626,11 +626,13 @@ class _LocalAlbumScreenState extends ConsumerState<LocalAlbumScreen> {
|
||||
|
||||
slivers.add(
|
||||
SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) =>
|
||||
_buildTrackItem(context, colorScheme, discTracks[index]),
|
||||
childCount: discTracks.length,
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate((context, index) {
|
||||
final track = discTracks[index];
|
||||
return KeyedSubtree(
|
||||
key: ValueKey(track.id),
|
||||
child: _buildTrackItem(context, colorScheme, track),
|
||||
);
|
||||
}, childCount: discTracks.length),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -900,16 +902,19 @@ class _LocalAlbumScreenState extends ConsumerState<LocalAlbumScreen> {
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<void> _queueSelectedAsFlac(List<LocalLibraryItem> allTracks) async {
|
||||
List<LocalLibraryItem> _selectedFlacEligibleItems(
|
||||
List<LocalLibraryItem> allTracks,
|
||||
) {
|
||||
final tracksById = {for (final t in allTracks) t.id: t};
|
||||
final selected = <LocalLibraryItem>[];
|
||||
return _selectedIds
|
||||
.map((id) => tracksById[id])
|
||||
.whereType<LocalLibraryItem>()
|
||||
.where(LocalTrackRedownloadService.isFlacUpgradeEligible)
|
||||
.toList(growable: false);
|
||||
}
|
||||
|
||||
for (final id in _selectedIds) {
|
||||
final item = tracksById[id];
|
||||
if (item != null) {
|
||||
selected.add(item);
|
||||
}
|
||||
}
|
||||
Future<void> _queueSelectedAsFlac(List<LocalLibraryItem> allTracks) async {
|
||||
final selected = _selectedFlacEligibleItems(allTracks);
|
||||
|
||||
if (selected.isEmpty) {
|
||||
return;
|
||||
@@ -962,9 +967,7 @@ class _LocalAlbumScreenState extends ConsumerState<LocalAlbumScreen> {
|
||||
ScaffoldMessenger.of(context).clearSnackBars();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
context.l10n.queueFlacFindingProgress(i + 1, total),
|
||||
),
|
||||
content: Text(context.l10n.queueFlacFindingProgress(i + 1, total)),
|
||||
duration: const Duration(seconds: 30),
|
||||
),
|
||||
);
|
||||
@@ -1177,8 +1180,9 @@ class _LocalAlbumScreenState extends ConsumerState<LocalAlbumScreen> {
|
||||
String selectedFormat = formats.first;
|
||||
bool isLosslessTarget =
|
||||
selectedFormat == 'ALAC' || selectedFormat == 'FLAC';
|
||||
String selectedBitrate =
|
||||
isLosslessTarget ? '320k' : (selectedFormat == 'Opus' ? '128k' : '320k');
|
||||
String selectedBitrate = isLosslessTarget
|
||||
? '320k'
|
||||
: (selectedFormat == 'Opus' ? '128k' : '320k');
|
||||
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
@@ -1240,8 +1244,9 @@ class _LocalAlbumScreenState extends ConsumerState<LocalAlbumScreen> {
|
||||
isLosslessTarget =
|
||||
format == 'ALAC' || format == 'FLAC';
|
||||
if (!isLosslessTarget) {
|
||||
selectedBitrate =
|
||||
format == 'Opus' ? '128k' : '320k';
|
||||
selectedBitrate = format == 'Opus'
|
||||
? '128k'
|
||||
: '320k';
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1286,11 +1291,8 @@ class _LocalAlbumScreenState extends ConsumerState<LocalAlbumScreen> {
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
context.l10n.trackConvertLosslessHint,
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.bodySmall?.copyWith(
|
||||
color: colorScheme.primary,
|
||||
),
|
||||
style: Theme.of(context).textTheme.bodySmall
|
||||
?.copyWith(color: colorScheme.primary),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -1371,7 +1373,8 @@ class _LocalAlbumScreenState extends ConsumerState<LocalAlbumScreen> {
|
||||
if (currentFormat == null || currentFormat == targetFormat) continue;
|
||||
// Skip lossy sources when target is lossless (pointless re-encoding)
|
||||
final isLosslessTarget = targetFormat == 'ALAC' || targetFormat == 'FLAC';
|
||||
final isLosslessSource = currentFormat == 'FLAC' || currentFormat == 'M4A';
|
||||
final isLosslessSource =
|
||||
currentFormat == 'FLAC' || currentFormat == 'M4A';
|
||||
if (isLosslessTarget && !isLosslessSource) continue;
|
||||
selected.add(item);
|
||||
}
|
||||
@@ -1656,6 +1659,7 @@ class _LocalAlbumScreenState extends ConsumerState<LocalAlbumScreen> {
|
||||
double bottomPadding,
|
||||
) {
|
||||
final selectedCount = _selectedIds.length;
|
||||
final flacEligibleCount = _selectedFlacEligibleItems(tracks).length;
|
||||
final allSelected = selectedCount == tracks.length && tracks.isNotEmpty;
|
||||
|
||||
return Container(
|
||||
@@ -1750,8 +1754,9 @@ class _LocalAlbumScreenState extends ConsumerState<LocalAlbumScreen> {
|
||||
Expanded(
|
||||
child: _LocalAlbumSelectionActionButton(
|
||||
icon: Icons.download_for_offline_outlined,
|
||||
label: '${context.l10n.queueFlacAction} ($selectedCount)',
|
||||
onPressed: selectedCount > 0
|
||||
label:
|
||||
'${context.l10n.queueFlacAction} ($flacEligibleCount)',
|
||||
onPressed: flacEligibleCount > 0
|
||||
? () => _queueSelectedAsFlac(tracks)
|
||||
: null,
|
||||
colorScheme: colorScheme,
|
||||
|
||||
+28
-20
@@ -4484,14 +4484,21 @@ class _QueueTabState extends ConsumerState<QueueTab> {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<LocalLibraryItem> _selectedFlacEligibleLocalItems(
|
||||
List<UnifiedLibraryItem> allItems,
|
||||
) {
|
||||
final selectedItems = _selectedItemsFromAll(allItems);
|
||||
return selectedItems
|
||||
.map((item) => item.localItem)
|
||||
.whereType<LocalLibraryItem>()
|
||||
.where(LocalTrackRedownloadService.isFlacUpgradeEligible)
|
||||
.toList(growable: false);
|
||||
}
|
||||
|
||||
Future<void> _queueSelectedLocalAsFlac(
|
||||
List<UnifiedLibraryItem> allItems,
|
||||
) async {
|
||||
final selectedItems = _selectedItemsFromAll(allItems);
|
||||
final selectedLocalItems = selectedItems
|
||||
.map((item) => item.localItem)
|
||||
.whereType<LocalLibraryItem>()
|
||||
.toList(growable: false);
|
||||
final selectedLocalItems = _selectedFlacEligibleLocalItems(allItems);
|
||||
|
||||
if (selectedLocalItems.isEmpty) {
|
||||
return;
|
||||
@@ -4546,9 +4553,7 @@ class _QueueTabState extends ConsumerState<QueueTab> {
|
||||
ScaffoldMessenger.of(context).clearSnackBars();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
context.l10n.queueFlacFindingProgress(i + 1, total),
|
||||
),
|
||||
content: Text(context.l10n.queueFlacFindingProgress(i + 1, total)),
|
||||
duration: const Duration(seconds: 30),
|
||||
),
|
||||
);
|
||||
@@ -4797,8 +4802,9 @@ class _QueueTabState extends ConsumerState<QueueTab> {
|
||||
String selectedFormat = formats.first;
|
||||
bool isLosslessTarget =
|
||||
selectedFormat == 'ALAC' || selectedFormat == 'FLAC';
|
||||
String selectedBitrate =
|
||||
isLosslessTarget ? '320k' : (selectedFormat == 'Opus' ? '128k' : '320k');
|
||||
String selectedBitrate = isLosslessTarget
|
||||
? '320k'
|
||||
: (selectedFormat == 'Opus' ? '128k' : '320k');
|
||||
var didStartConversion = false;
|
||||
|
||||
_hideSelectionOverlay();
|
||||
@@ -4864,8 +4870,9 @@ class _QueueTabState extends ConsumerState<QueueTab> {
|
||||
isLosslessTarget =
|
||||
format == 'ALAC' || format == 'FLAC';
|
||||
if (!isLosslessTarget) {
|
||||
selectedBitrate =
|
||||
format == 'Opus' ? '128k' : '320k';
|
||||
selectedBitrate = format == 'Opus'
|
||||
? '128k'
|
||||
: '320k';
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -4910,11 +4917,8 @@ class _QueueTabState extends ConsumerState<QueueTab> {
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
context.l10n.trackConvertLosslessHint,
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.bodySmall?.copyWith(
|
||||
color: colorScheme.primary,
|
||||
),
|
||||
style: Theme.of(context).textTheme.bodySmall
|
||||
?.copyWith(color: colorScheme.primary),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -5054,7 +5058,8 @@ class _QueueTabState extends ConsumerState<QueueTab> {
|
||||
int successCount = 0;
|
||||
final total = selectedItems.length;
|
||||
final historyDb = HistoryDatabase.instance;
|
||||
final newQuality = (targetFormat.toUpperCase() == 'ALAC' ||
|
||||
final newQuality =
|
||||
(targetFormat.toUpperCase() == 'ALAC' ||
|
||||
targetFormat.toUpperCase() == 'FLAC')
|
||||
? '${targetFormat.toUpperCase()} Lossless'
|
||||
: '${targetFormat.toUpperCase()} ${bitrate.trim().toLowerCase()}';
|
||||
@@ -5375,6 +5380,9 @@ class _QueueTabState extends ConsumerState<QueueTab> {
|
||||
final allSelected =
|
||||
selectedCount == unifiedItems.length && unifiedItems.isNotEmpty;
|
||||
final localOnlySelection = _isLocalOnlySelection(unifiedItems);
|
||||
final flacEligibleCount = _selectedFlacEligibleLocalItems(
|
||||
unifiedItems,
|
||||
).length;
|
||||
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
@@ -5469,8 +5477,8 @@ class _QueueTabState extends ConsumerState<QueueTab> {
|
||||
child: _SelectionActionButton(
|
||||
icon: Icons.download_for_offline_outlined,
|
||||
label:
|
||||
'${context.l10n.queueFlacAction} ($selectedCount)',
|
||||
onPressed: selectedCount > 0
|
||||
'${context.l10n.queueFlacAction} ($flacEligibleCount)',
|
||||
onPressed: flacEligibleCount > 0
|
||||
? () => _queueSelectedLocalAsFlac(unifiedItems)
|
||||
: null,
|
||||
colorScheme: colorScheme,
|
||||
|
||||
@@ -23,6 +23,15 @@ class LocalTrackRedownloadService {
|
||||
static const int _minimumConfidenceScore = 85;
|
||||
static const int _ambiguousScoreGap = 8;
|
||||
|
||||
static bool isFlacUpgradeEligible(LocalLibraryItem item) {
|
||||
final format = item.format?.trim().toLowerCase();
|
||||
if (format == 'flac') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !item.filePath.toLowerCase().endsWith('.flac');
|
||||
}
|
||||
|
||||
static Future<LocalTrackRedownloadResolution> resolveBestMatch(
|
||||
LocalLibraryItem item, {
|
||||
required bool includeExtensions,
|
||||
|
||||
Reference in New Issue
Block a user