feat(queue): long-press menu with move up/down for queued items

This commit is contained in:
zarzet
2026-07-27 01:24:14 +07:00
parent e8a3fc58c7
commit 9da3607932
15 changed files with 155 additions and 1 deletions
+12
View File
@@ -6306,6 +6306,18 @@ abstract class AppLocalizations {
/// **'Download next'**
String get queueDownloadNext;
/// Queue item menu action - move the queued item one position earlier
///
/// In en, this message translates to:
/// **'Move up'**
String get queueMoveUp;
/// Queue item menu action - move the queued item one position later
///
/// In en, this message translates to:
/// **'Move down'**
String get queueMoveDown;
/// Tag editor button that fills genre and album artist from MusicBrainz by ISRC
///
/// In en, this message translates to:
+6
View File
@@ -3841,6 +3841,12 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get queueDownloadNext => 'Download next';
@override
String get queueMoveUp => 'Move up';
@override
String get queueMoveDown => 'Move down';
@override
String get editMetadataMusicBrainzButton => 'Fetch from MusicBrainz';
+6
View File
@@ -3795,6 +3795,12 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get queueDownloadNext => 'Download next';
@override
String get queueMoveUp => 'Move up';
@override
String get queueMoveDown => 'Move down';
@override
String get editMetadataMusicBrainzButton => 'Fetch from MusicBrainz';
+6
View File
@@ -3789,6 +3789,12 @@ class AppLocalizationsEs extends AppLocalizations {
@override
String get queueDownloadNext => 'Download next';
@override
String get queueMoveUp => 'Move up';
@override
String get queueMoveDown => 'Move down';
@override
String get editMetadataMusicBrainzButton => 'Fetch from MusicBrainz';
+6
View File
@@ -3896,6 +3896,12 @@ class AppLocalizationsFr extends AppLocalizations {
@override
String get queueDownloadNext => 'Download next';
@override
String get queueMoveUp => 'Move up';
@override
String get queueMoveDown => 'Move down';
@override
String get editMetadataMusicBrainzButton => 'Fetch from MusicBrainz';
+6
View File
@@ -3798,6 +3798,12 @@ class AppLocalizationsId extends AppLocalizations {
@override
String get queueDownloadNext => 'Download next';
@override
String get queueMoveUp => 'Move up';
@override
String get queueMoveDown => 'Move down';
@override
String get editMetadataMusicBrainzButton => 'Fetch from MusicBrainz';
+6
View File
@@ -3784,6 +3784,12 @@ class AppLocalizationsJa extends AppLocalizations {
@override
String get queueDownloadNext => 'Download next';
@override
String get queueMoveUp => 'Move up';
@override
String get queueMoveDown => 'Move down';
@override
String get editMetadataMusicBrainzButton => 'Fetch from MusicBrainz';
+6
View File
@@ -3685,6 +3685,12 @@ class AppLocalizationsKo extends AppLocalizations {
@override
String get queueDownloadNext => 'Download next';
@override
String get queueMoveUp => 'Move up';
@override
String get queueMoveDown => 'Move down';
@override
String get editMetadataMusicBrainzButton => 'Fetch from MusicBrainz';
+6
View File
@@ -3789,6 +3789,12 @@ class AppLocalizationsPt extends AppLocalizations {
@override
String get queueDownloadNext => 'Download next';
@override
String get queueMoveUp => 'Move up';
@override
String get queueMoveDown => 'Move down';
@override
String get editMetadataMusicBrainzButton => 'Fetch from MusicBrainz';
+6
View File
@@ -3826,6 +3826,12 @@ class AppLocalizationsRu extends AppLocalizations {
@override
String get queueDownloadNext => 'Download next';
@override
String get queueMoveUp => 'Move up';
@override
String get queueMoveDown => 'Move down';
@override
String get editMetadataMusicBrainzButton => 'Fetch from MusicBrainz';
+6
View File
@@ -3825,6 +3825,12 @@ class AppLocalizationsTr extends AppLocalizations {
@override
String get queueDownloadNext => 'Download next';
@override
String get queueMoveUp => 'Move up';
@override
String get queueMoveDown => 'Move down';
@override
String get editMetadataMusicBrainzButton => 'Fetch from MusicBrainz';
+6
View File
@@ -3843,6 +3843,12 @@ class AppLocalizationsUk extends AppLocalizations {
@override
String get queueDownloadNext => 'Download next';
@override
String get queueMoveUp => 'Move up';
@override
String get queueMoveDown => 'Move down';
@override
String get editMetadataMusicBrainzButton => 'Fetch from MusicBrainz';
+8
View File
@@ -4930,6 +4930,14 @@
"@queueDownloadNext": {
"description": "Tooltip on a queued download row; moves the item to the front of the queue so the next free slot downloads it"
},
"queueMoveUp": "Move up",
"@queueMoveUp": {
"description": "Queue item menu action - move the queued item one position earlier"
},
"queueMoveDown": "Move down",
"@queueMoveDown": {
"description": "Queue item menu action - move the queued item one position later"
},
"editMetadataMusicBrainzButton": "Fetch from MusicBrainz",
"@editMetadataMusicBrainzButton": {
"description": "Tag editor button that fills genre and album artist from MusicBrainz by ISRC"
@@ -1014,6 +1014,31 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
_saveQueueToStorage();
}
/// Moves a queued item [offset] positions up (negative) or down (positive)
/// among the queued items, leaving non-queued rows in place. Same native
/// worker caveat as [downloadNext]: an in-flight batch keeps its order.
void moveQueuedItem(String id, int offset) {
if (offset == 0) return;
final items = state.items;
final index = items.indexWhere((item) => item.id == id);
if (index == -1) return;
final item = items[index];
if (item.status != DownloadStatus.queued) return;
final queuedIndices = <int>[
for (var i = 0; i < items.length; i++)
if (items[i].status == DownloadStatus.queued) i,
];
final position = queuedIndices.indexOf(index);
final targetPosition = position + offset;
if (targetPosition < 0 || targetPosition >= queuedIndices.length) return;
final reordered = List<DownloadItem>.from(items)..removeAt(index);
// Inserting at the target's pre-removal index lands the item directly
// before it when moving up and directly after it when moving down.
reordered.insert(queuedIndices[targetPosition], item);
state = state.copyWith(items: reordered);
_saveQueueToStorage();
}
void removeItem(String id) {
final removedItem = state.items.where((item) => item.id == id).firstOrNull;
_locallyCancelledItemIds.remove(id);
+44 -1
View File
@@ -194,9 +194,13 @@ extension _QueueTabItemWidgets on _QueueTabState {
child: InkWell(
onTap: isCompleted
? () => _navigateToMetadataScreen(item)
: item.status == DownloadStatus.failed
: item.status == DownloadStatus.failed ||
item.status == DownloadStatus.skipped
? () => _showDownloadErrorDialog(context, item)
: null,
onLongPress: item.status == DownloadStatus.queued
? () => _showQueuedItemMenu(context, item)
: null,
borderRadius: BorderRadius.circular(12),
child: Stack(
children: [
@@ -479,6 +483,45 @@ extension _QueueTabItemWidgets on _QueueTabState {
);
}
Future<void> _showQueuedItemMenu(BuildContext context, DownloadItem item) {
final notifier = ref.read(downloadQueueProvider.notifier);
return showModalBottomSheet<void>(
context: context,
showDragHandle: true,
builder: (ctx) => SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
leading: const Icon(Icons.skip_next),
title: Text(context.l10n.queueDownloadNext),
onTap: () {
Navigator.of(ctx).pop();
notifier.downloadNext(item.id);
},
),
ListTile(
leading: const Icon(Icons.arrow_upward),
title: Text(context.l10n.queueMoveUp),
onTap: () {
Navigator.of(ctx).pop();
notifier.moveQueuedItem(item.id, -1);
},
),
ListTile(
leading: const Icon(Icons.arrow_downward),
title: Text(context.l10n.queueMoveDown),
onTap: () {
Navigator.of(ctx).pop();
notifier.moveQueuedItem(item.id, 1);
},
),
],
),
),
);
}
Widget _buildActionButtons(
BuildContext context,
DownloadItem item,