mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-07-31 00:07:25 +02:00
feat: improve track matching
This commit is contained in:
@@ -1559,7 +1559,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
final isDownloading = itemProgress['is_downloading'] as bool? ?? false;
|
||||
final status = itemProgress['status'] as String? ?? 'downloading';
|
||||
|
||||
if (status == 'finalizing' && bytesTotal > 0) {
|
||||
if (status == 'finalizing') {
|
||||
progressUpdates[itemId] = const _ProgressUpdate(
|
||||
status: DownloadStatus.finalizing,
|
||||
progress: 1.0,
|
||||
@@ -4358,7 +4358,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
|
||||
if (!wasExisting && decryptionKey.isNotEmpty && filePath != null) {
|
||||
_log.i('Encrypted stream detected, decrypting via FFmpeg...');
|
||||
updateItemStatus(item.id, DownloadStatus.downloading, progress: 0.9);
|
||||
updateItemStatus(item.id, DownloadStatus.finalizing, progress: 0.9);
|
||||
|
||||
if (effectiveSafMode && isContentUri(filePath)) {
|
||||
final currentFilePath = filePath;
|
||||
@@ -4503,7 +4503,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
try {
|
||||
updateItemStatus(
|
||||
item.id,
|
||||
DownloadStatus.downloading,
|
||||
DownloadStatus.finalizing,
|
||||
progress: 0.95,
|
||||
);
|
||||
|
||||
@@ -4524,7 +4524,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
_log.i('Embedding metadata to $format...');
|
||||
updateItemStatus(
|
||||
item.id,
|
||||
DownloadStatus.downloading,
|
||||
DownloadStatus.finalizing,
|
||||
progress: 0.99,
|
||||
);
|
||||
|
||||
@@ -4608,7 +4608,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
} else {
|
||||
updateItemStatus(
|
||||
item.id,
|
||||
DownloadStatus.downloading,
|
||||
DownloadStatus.finalizing,
|
||||
progress: 0.95,
|
||||
);
|
||||
flacPath = await FFmpegService.convertM4aToFlac(tempPath);
|
||||
@@ -4684,7 +4684,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
try {
|
||||
updateItemStatus(
|
||||
item.id,
|
||||
DownloadStatus.downloading,
|
||||
DownloadStatus.finalizing,
|
||||
progress: 0.95,
|
||||
);
|
||||
|
||||
@@ -4711,7 +4711,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
_log.i('Embedding metadata to $format...');
|
||||
updateItemStatus(
|
||||
item.id,
|
||||
DownloadStatus.downloading,
|
||||
DownloadStatus.finalizing,
|
||||
progress: 0.99,
|
||||
);
|
||||
|
||||
@@ -4765,7 +4765,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
} else {
|
||||
updateItemStatus(
|
||||
item.id,
|
||||
DownloadStatus.downloading,
|
||||
DownloadStatus.finalizing,
|
||||
progress: 0.95,
|
||||
);
|
||||
final flacPath = await FFmpegService.convertM4aToFlac(
|
||||
@@ -4849,7 +4849,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
try {
|
||||
updateItemStatus(
|
||||
item.id,
|
||||
DownloadStatus.downloading,
|
||||
DownloadStatus.finalizing,
|
||||
progress: 0.99,
|
||||
);
|
||||
|
||||
@@ -4930,7 +4930,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
try {
|
||||
updateItemStatus(
|
||||
item.id,
|
||||
DownloadStatus.downloading,
|
||||
DownloadStatus.finalizing,
|
||||
progress: 0.99,
|
||||
);
|
||||
|
||||
|
||||
@@ -301,11 +301,11 @@ class _MainShellState extends ConsumerState<MainShell>
|
||||
}
|
||||
}
|
||||
|
||||
void _handleBackPress() {
|
||||
Future<void> _handleBackPress() async {
|
||||
final rootNavigator = Navigator.of(context, rootNavigator: true);
|
||||
if (rootNavigator.canPop()) {
|
||||
_log.i('Back: step 1 - root navigator pop');
|
||||
rootNavigator.pop();
|
||||
final handledByRootNavigator = await rootNavigator.maybePop();
|
||||
if (handledByRootNavigator) {
|
||||
_log.i('Back: step 1 - root navigator handled back');
|
||||
_lastBackPress = null;
|
||||
return;
|
||||
}
|
||||
@@ -314,9 +314,10 @@ class _MainShellState extends ConsumerState<MainShell>
|
||||
settingsProvider.select((s) => s.showExtensionStore),
|
||||
);
|
||||
final currentNavigator = _navigatorForTab(_currentIndex, showStore);
|
||||
if (currentNavigator != null && currentNavigator.canPop()) {
|
||||
_log.i('Back: step 2 - tab navigator pop (tab=$_currentIndex)');
|
||||
currentNavigator.pop();
|
||||
final handledByCurrentNavigator =
|
||||
await currentNavigator?.maybePop() ?? false;
|
||||
if (handledByCurrentNavigator) {
|
||||
_log.i('Back: step 2 - tab navigator handled back (tab=$_currentIndex)');
|
||||
_lastBackPress = null;
|
||||
return;
|
||||
}
|
||||
@@ -522,7 +523,7 @@ class _MainShellState extends ConsumerState<MainShell>
|
||||
|
||||
return BackButtonListener(
|
||||
onBackButtonPressed: () async {
|
||||
_handleBackPress();
|
||||
await _handleBackPress();
|
||||
return true;
|
||||
},
|
||||
child: Scaffold(
|
||||
|
||||
+80
-28
@@ -1220,16 +1220,21 @@ class _QueueTabState extends ConsumerState<QueueTab> {
|
||||
}
|
||||
|
||||
void _toggleSelection(String itemId) {
|
||||
var shouldHideOverlay = false;
|
||||
setState(() {
|
||||
if (_selectedIds.contains(itemId)) {
|
||||
_selectedIds.remove(itemId);
|
||||
if (_selectedIds.isEmpty) {
|
||||
_isSelectionMode = false;
|
||||
shouldHideOverlay = true;
|
||||
}
|
||||
} else {
|
||||
_selectedIds.add(itemId);
|
||||
}
|
||||
});
|
||||
if (shouldHideOverlay) {
|
||||
_hideSelectionOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
void _selectAll(List<UnifiedLibraryItem> items) {
|
||||
@@ -1269,13 +1274,15 @@ class _QueueTabState extends ConsumerState<QueueTab> {
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: _buildSelectionBottomBar(
|
||||
context,
|
||||
colorScheme,
|
||||
_selectionOverlayItems,
|
||||
_selectionOverlayBottomPadding,
|
||||
child: _AnimatedOverlayBottomBar(
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: _buildSelectionBottomBar(
|
||||
context,
|
||||
colorScheme,
|
||||
_selectionOverlayItems,
|
||||
_selectionOverlayBottomPadding,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -1315,13 +1322,15 @@ class _QueueTabState extends ConsumerState<QueueTab> {
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: _buildPlaylistSelectionBottomBar(
|
||||
context,
|
||||
colorScheme,
|
||||
_playlistSelectionOverlayItems,
|
||||
_playlistSelectionOverlayBottomPadding,
|
||||
child: _AnimatedOverlayBottomBar(
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: _buildPlaylistSelectionBottomBar(
|
||||
context,
|
||||
colorScheme,
|
||||
_playlistSelectionOverlayItems,
|
||||
_playlistSelectionOverlayBottomPadding,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -1350,16 +1359,21 @@ class _QueueTabState extends ConsumerState<QueueTab> {
|
||||
}
|
||||
|
||||
void _togglePlaylistSelection(String playlistId) {
|
||||
var shouldHideOverlay = false;
|
||||
setState(() {
|
||||
if (_selectedPlaylistIds.contains(playlistId)) {
|
||||
_selectedPlaylistIds.remove(playlistId);
|
||||
if (_selectedPlaylistIds.isEmpty) {
|
||||
_isPlaylistSelectionMode = false;
|
||||
shouldHideOverlay = true;
|
||||
}
|
||||
} else {
|
||||
_selectedPlaylistIds.add(playlistId);
|
||||
}
|
||||
});
|
||||
if (shouldHideOverlay) {
|
||||
_hidePlaylistSelectionOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
void _selectAllPlaylists(List<UserPlaylistCollection> playlists) {
|
||||
@@ -5538,20 +5552,8 @@ class _QueueTabState extends ConsumerState<QueueTab> {
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
item.bytesTotal > 0 && item.bytesReceived > 0
|
||||
? (() {
|
||||
final receivedMB =
|
||||
item.bytesReceived / (1024 * 1024);
|
||||
final totalMB =
|
||||
item.bytesTotal / (1024 * 1024);
|
||||
final progressLabel = item.progress > 0
|
||||
? '${(item.progress * 100).toStringAsFixed(0)}% • '
|
||||
: '';
|
||||
final speedLabel = item.speedMBps > 0
|
||||
? ' • ${item.speedMBps.toStringAsFixed(1)} MB/s'
|
||||
: '';
|
||||
return '$progressLabel${receivedMB.toStringAsFixed(1)} / ${totalMB.toStringAsFixed(1)} MB$speedLabel';
|
||||
})()
|
||||
item.bytesTotal > 0
|
||||
? '${(item.progress * 100).toStringAsFixed(0)}%'
|
||||
: (item.bytesReceived > 0
|
||||
? '${(item.bytesReceived / (1024 * 1024)).toStringAsFixed(1)} MB${item.speedMBps > 0 ? ' • ${item.speedMBps.toStringAsFixed(1)} MB/s' : ''}'
|
||||
: (item.progress > 0
|
||||
@@ -6466,3 +6468,53 @@ class _SelectionActionButton extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AnimatedOverlayBottomBar extends StatefulWidget {
|
||||
final Widget child;
|
||||
|
||||
const _AnimatedOverlayBottomBar({required this.child});
|
||||
|
||||
@override
|
||||
State<_AnimatedOverlayBottomBar> createState() =>
|
||||
_AnimatedOverlayBottomBarState();
|
||||
}
|
||||
|
||||
class _AnimatedOverlayBottomBarState extends State<_AnimatedOverlayBottomBar>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late final AnimationController _controller;
|
||||
late final Animation<Offset> _slideAnimation;
|
||||
late final Animation<double> _fadeAnimation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 240),
|
||||
);
|
||||
final curve = CurvedAnimation(
|
||||
parent: _controller,
|
||||
curve: Curves.easeOutCubic,
|
||||
);
|
||||
_slideAnimation = Tween<Offset>(
|
||||
begin: const Offset(0, 0.08),
|
||||
end: Offset.zero,
|
||||
).animate(curve);
|
||||
_fadeAnimation = Tween<double>(begin: 0, end: 1).animate(curve);
|
||||
_controller.forward();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FadeTransition(
|
||||
opacity: _fadeAnimation,
|
||||
child: SlideTransition(position: _slideAnimation, child: widget.child),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user