feat: improve track matching

This commit is contained in:
zarzet
2026-04-13 23:32:11 +07:00
parent e2a489ec92
commit 2b2c2bc90a
9 changed files with 227 additions and 86 deletions
+10 -10
View File
@@ -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,
);
+9 -8
View File
@@ -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
View File
@@ -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),
);
}
}