mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-09-02 16:20:57 +02:00
fix(library): show play action for completed batch items
This commit is contained in:
@@ -25,3 +25,17 @@ bool shouldRetainQueueLibraryPageSnapshot({
|
||||
required bool cachedHasContent,
|
||||
required bool activeDownloadFallbackAvailable,
|
||||
}) => currentIsEmpty && cachedHasContent && activeDownloadFallbackAvailable;
|
||||
|
||||
/// Resolves the playable path for a just-completed download while its pinned
|
||||
/// completion-bridge card is still visible. The finalized history path wins
|
||||
/// because conversion or SAF publication may change the original queue path.
|
||||
String? resolveCompletionBridgePlayablePath({
|
||||
String? historyFilePath,
|
||||
String? completedItemFilePath,
|
||||
}) {
|
||||
final historyPath = historyFilePath?.trim();
|
||||
if (historyPath != null && historyPath.isNotEmpty) return historyPath;
|
||||
|
||||
final completedPath = completedItemFilePath?.trim();
|
||||
return completedPath == null || completedPath.isEmpty ? null : completedPath;
|
||||
}
|
||||
|
||||
@@ -122,6 +122,10 @@ extension _QueueTabCollectionItemWidgets on _QueueTabState {
|
||||
final quality =
|
||||
unifiedItem?.qualityForMode(_libraryQualityLabelMode) ??
|
||||
track.audioQuality;
|
||||
final playablePath = resolveCompletionBridgePlayablePath(
|
||||
historyFilePath: historyItem?.filePath,
|
||||
completedItemFilePath: item.filePath,
|
||||
);
|
||||
final cover = unifiedItem != null
|
||||
? _buildUnifiedCoverImage(unifiedItem, colorScheme)
|
||||
: track.coverUrl != null
|
||||
@@ -132,6 +136,8 @@ extension _QueueTabCollectionItemWidgets on _QueueTabState {
|
||||
: 'cover_${item.id}';
|
||||
final trackName = historyItem?.trackName ?? track.name;
|
||||
final artistName = historyItem?.artistName ?? track.artistName;
|
||||
final albumName = historyItem?.albumName ?? track.albumName;
|
||||
final coverUrl = historyItem?.coverUrl ?? track.coverUrl ?? '';
|
||||
return TrackGridCard(
|
||||
semanticLabel: context.l10n.a11yTrackByArtist(trackName, artistName),
|
||||
onTap: () => historyItem != null
|
||||
@@ -143,16 +149,72 @@ extension _QueueTabCollectionItemWidgets on _QueueTabState {
|
||||
: _navigateToMetadataScreen(item),
|
||||
cover: Hero(tag: heroTag, child: cover),
|
||||
overlays: [
|
||||
Positioned(
|
||||
right: 4,
|
||||
top: 4,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.primaryContainer,
|
||||
borderRadius: context.tokens.borderRadiusBadge,
|
||||
),
|
||||
child: Icon(
|
||||
Icons.download_done,
|
||||
size: 12,
|
||||
color: colorScheme.onPrimaryContainer,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (quality != null && quality.isNotEmpty)
|
||||
Positioned(
|
||||
left: 4,
|
||||
right: 4,
|
||||
right: 28,
|
||||
top: 4,
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: _buildLibraryQualityBadge(context, colorScheme, quality),
|
||||
),
|
||||
),
|
||||
if (playablePath != null)
|
||||
Positioned(
|
||||
right: 4,
|
||||
bottom: 4,
|
||||
child: ValueListenableBuilder<bool>(
|
||||
valueListenable: _fileExistsListenable(playablePath),
|
||||
builder: (context, fileExists, child) {
|
||||
if (fileExists) {
|
||||
return TrackGridPlayButton(
|
||||
tooltip: context.l10n.a11yPlayTrackByArtist(
|
||||
trackName,
|
||||
artistName,
|
||||
),
|
||||
onPressed: () => _openFile(
|
||||
playablePath,
|
||||
title: trackName,
|
||||
artist: artistName,
|
||||
album: albumName,
|
||||
coverUrl: coverUrl,
|
||||
),
|
||||
);
|
||||
}
|
||||
return Tooltip(
|
||||
message: context.l10n.queueDownloadedFileMissing,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.errorContainer,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
Icons.error_outline,
|
||||
color: colorScheme.error,
|
||||
size: 14,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
title: trackName,
|
||||
subtitle: Text(artistName),
|
||||
@@ -176,6 +238,10 @@ extension _QueueTabCollectionItemWidgets on _QueueTabState {
|
||||
final quality =
|
||||
unifiedItem?.qualityForMode(_libraryQualityLabelMode) ??
|
||||
track.audioQuality;
|
||||
final playablePath = resolveCompletionBridgePlayablePath(
|
||||
historyFilePath: historyItem?.filePath,
|
||||
completedItemFilePath: item.filePath,
|
||||
);
|
||||
final cover = unifiedItem != null
|
||||
? _buildUnifiedCoverImage(unifiedItem, colorScheme, coverSize)
|
||||
: track.coverUrl != null
|
||||
@@ -199,6 +265,8 @@ extension _QueueTabCollectionItemWidgets on _QueueTabState {
|
||||
: 'cover_${item.id}';
|
||||
final trackName = historyItem?.trackName ?? track.name;
|
||||
final artistName = historyItem?.artistName ?? track.artistName;
|
||||
final albumName = historyItem?.albumName ?? track.albumName;
|
||||
final coverUrl = historyItem?.coverUrl ?? track.coverUrl ?? '';
|
||||
return TrackCard(
|
||||
onTap: () => historyItem != null
|
||||
? _navigateToHistoryMetadataScreen(
|
||||
@@ -235,6 +303,40 @@ extension _QueueTabCollectionItemWidgets on _QueueTabState {
|
||||
],
|
||||
],
|
||||
),
|
||||
trailing: playablePath == null
|
||||
? null
|
||||
: ValueListenableBuilder<bool>(
|
||||
valueListenable: _fileExistsListenable(playablePath),
|
||||
builder: (context, fileExists, child) {
|
||||
if (fileExists) {
|
||||
return IconButton(
|
||||
onPressed: () => _openFile(
|
||||
playablePath,
|
||||
title: trackName,
|
||||
artist: artistName,
|
||||
album: albumName,
|
||||
coverUrl: coverUrl,
|
||||
),
|
||||
icon: Icon(Icons.play_arrow, color: colorScheme.primary),
|
||||
tooltip: context.l10n.tooltipPlay,
|
||||
style: IconButton.styleFrom(
|
||||
minimumSize: Size.square(context.tokens.minTouchTarget),
|
||||
backgroundColor: colorScheme.primaryContainer.withValues(
|
||||
alpha: 0.3,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return Tooltip(
|
||||
message: context.l10n.queueDownloadedFileMissing,
|
||||
child: Icon(
|
||||
Icons.error_outline,
|
||||
color: colorScheme.error,
|
||||
size: 20,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:spotiflac_android/screens/queue_library_refresh_policy.dart';
|
||||
import 'package:spotiflac_android/services/library_database.dart';
|
||||
|
||||
@@ -73,4 +76,51 @@ void main() {
|
||||
isFalse,
|
||||
);
|
||||
});
|
||||
|
||||
test('completion bridge prefers the finalized history path', () {
|
||||
expect(
|
||||
resolveCompletionBridgePlayablePath(
|
||||
historyFilePath: ' /music/final.flac ',
|
||||
completedItemFilePath: '/music/staging.flac',
|
||||
),
|
||||
'/music/final.flac',
|
||||
);
|
||||
expect(
|
||||
resolveCompletionBridgePlayablePath(
|
||||
historyFilePath: ' ',
|
||||
completedItemFilePath: ' /music/completed.flac ',
|
||||
),
|
||||
'/music/completed.flac',
|
||||
);
|
||||
expect(
|
||||
resolveCompletionBridgePlayablePath(
|
||||
historyFilePath: null,
|
||||
completedItemFilePath: '',
|
||||
),
|
||||
isNull,
|
||||
);
|
||||
});
|
||||
|
||||
test('completion bridge cards retain Play actions during a batch', () {
|
||||
final source = File(
|
||||
'lib/screens/queue_tab_collection_items.dart',
|
||||
).readAsStringSync();
|
||||
final gridStart = source.indexOf('Widget _buildBridgeGridItem(');
|
||||
final listStart = source.indexOf('Widget _buildBridgeListItem(');
|
||||
final badgeStart = source.indexOf('Widget _buildLibraryQualityBadge(');
|
||||
|
||||
expect(gridStart, greaterThanOrEqualTo(0));
|
||||
expect(listStart, greaterThan(gridStart));
|
||||
expect(badgeStart, greaterThan(listStart));
|
||||
|
||||
final gridSource = source.substring(gridStart, listStart);
|
||||
final listSource = source.substring(listStart, badgeStart);
|
||||
|
||||
expect(gridSource, contains('resolveCompletionBridgePlayablePath('));
|
||||
expect(gridSource, contains('_fileExistsListenable(playablePath)'));
|
||||
expect(gridSource, contains('TrackGridPlayButton('));
|
||||
expect(listSource, contains('resolveCompletionBridgePlayablePath('));
|
||||
expect(listSource, contains('_fileExistsListenable(playablePath)'));
|
||||
expect(listSource, contains('Icons.play_arrow'));
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user