Add move to top/bottom multi select action in queue (#7696)

This commit is contained in:
dominikfill 2025-04-09 20:19:35 +02:00 committed by GitHub
parent 84907b114f
commit 5459368e51
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 161 additions and 70 deletions

View File

@ -43,6 +43,10 @@ public class EpisodeMultiSelectActionHandler {
downloadChecked(items);
} else if (actionId == R.id.remove_item) {
LocalDeleteModal.showLocalFeedDeleteWarningIfNecessary(activity, items, () -> deleteChecked(items));
} else if (actionId == R.id.move_to_top_item) {
moveToTopChecked(items);
} else if (actionId == R.id.move_to_bottom_item) {
moveToBottomChecked(items);
} else {
Log.e(TAG, "Unrecognized speed dial action item. Do nothing. id=" + actionId);
}
@ -113,6 +117,16 @@ public class EpisodeMultiSelectActionHandler {
showMessage(R.plurals.deleted_multi_episode_batch_label, countHasMedia);
}
private void moveToTopChecked(List<FeedItem> items) {
DBWriter.moveQueueItemsToTop(items);
showMessage(R.plurals.move_to_top_message, items.size());
}
private void moveToBottomChecked(List<FeedItem> items) {
DBWriter.moveQueueItemsToBottom(items);
showMessage(R.plurals.move_to_bottom_message, items.size());
}
private void showMessage(@PluralsRes int msgId, int numItems) {
if (numItems == 1) {
return;

View File

@ -8,6 +8,7 @@ import android.util.Log;
import android.view.ContextMenu;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
@ -38,6 +39,7 @@ import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import java.util.Collections;
import java.util.List;
import de.danoeh.antennapod.R;
@ -140,6 +142,7 @@ public class QueueFragment extends Fragment implements MaterialToolbar.OnMenuIte
loadItems(true);
return;
}
int position;
switch (event.action) {
case ADDED:
queue.add(event.position, event.item);
@ -152,7 +155,7 @@ public class QueueFragment extends Fragment implements MaterialToolbar.OnMenuIte
break;
case REMOVED:
case IRREVERSIBLE_REMOVED:
int position = FeedItemEvent.indexOfItemWithId(queue, event.item.getId());
position = FeedItemEvent.indexOfItemWithId(queue, event.item.getId());
queue.remove(position);
recyclerAdapter.notifyItemRemoved(position);
break;
@ -160,6 +163,11 @@ public class QueueFragment extends Fragment implements MaterialToolbar.OnMenuIte
queue.clear();
recyclerAdapter.updateItems(queue);
break;
case MOVED:
position = FeedItemEvent.indexOfItemWithId(queue, event.item.getId());
queue.add(event.position, queue.remove(position));
recyclerAdapter.notifyItemMoved(position, event.position);
break;
default:
return;
}
@ -374,16 +382,18 @@ public class QueueFragment extends Fragment implements MaterialToolbar.OnMenuIte
}
final int itemId = item.getItemId();
if (itemId == R.id.move_to_top_item) {
queue.add(0, queue.remove(position));
recyclerAdapter.notifyItemMoved(position, 0);
DBWriter.moveQueueItemToTop(selectedItem.getId(), true);
return true;
} else if (itemId == R.id.move_to_bottom_item) {
queue.add(queue.size() - 1, queue.remove(position));
recyclerAdapter.notifyItemMoved(position, queue.size() - 1);
DBWriter.moveQueueItemToBottom(selectedItem.getId(), true);
return true;
if (!recyclerAdapter.inActionMode()) {
if (itemId == R.id.move_to_top_item) {
queue.add(0, queue.remove(position));
recyclerAdapter.notifyItemMoved(position, 0);
DBWriter.moveQueueItemsToTop(Collections.singletonList(selectedItem));
return true;
} else if (itemId == R.id.move_to_bottom_item) {
queue.add(queue.remove(position));
recyclerAdapter.notifyItemMoved(position, queue.size() - 1);
DBWriter.moveQueueItemsToBottom(Collections.singletonList(selectedItem));
return true;
}
}
return FeedItemMenuHandler.onMenuItemClicked(this, item.getItemId(), selectedItem);
}
@ -437,8 +447,15 @@ public class QueueFragment extends Fragment implements MaterialToolbar.OnMenuIte
@Override
protected void onSelectedItemsUpdated() {
super.onSelectedItemsUpdated();
Menu menu = floatingSelectMenu.getMenu();
List<FeedItem> selectedItems = getSelectedItems();
FeedItemMenuHandler.onPrepareMenu(floatingSelectMenu.getMenu(), getSelectedItems(),
R.id.add_to_queue_item, R.id.remove_inbox_item);
Pair<Boolean, Boolean> canMove = canMove(queue, selectedItems);
menu.findItem(R.id.move_to_top_item).setVisible(canMove.first);
menu.findItem(R.id.move_to_bottom_item).setVisible(canMove.second);
floatingSelectMenu.updateItemVisibility();
}
};
@ -608,6 +625,40 @@ public class QueueFragment extends Fragment implements MaterialToolbar.OnMenuIte
}
}
/**
* This method checks if the selected items are allowed to be moved to the top, bottom or both of the Queue.
* @param queue The FeedItems currently in the Queue.
* @param selectedItems The FeedItems for which the check is performed.
* @return A Pair of booleans where
* [0] is true if moving to the top is allowed, false otherwise.
* [1] is true if moving to the bottom is allowed, false otherwise.
* */
public static Pair<Boolean, Boolean> canMove(List<FeedItem> queue, List<FeedItem> selectedItems) {
int queueSize = queue.size();
int selectedSize = selectedItems.size();
// No manual reordering allowed or reordering would be a no-op.
if (selectedItems.isEmpty() || queue.isEmpty() || UserPreferences.isQueueLocked()
|| UserPreferences.isQueueKeepSorted() || selectedSize == queueSize) {
return new Pair<>(false, false);
}
boolean isFirstItemSelected = selectedItems.get(0).getId() == queue.get(0).getId();
boolean isLastItemSelected = selectedItems.get(selectedSize - 1).getId() == queue.get(queueSize - 1).getId();
// If only one item is selected and its already at the top of the list, disable option to move item to the top.
// If the item is already at the bottom of the list, disable the option to move it to the bottom.
if (selectedSize == 1) {
return new Pair<>(!isFirstItemSelected, !isLastItemSelected);
}
// If contiguous from the top, moving items to the top is disabled, as they are already there.
if (isFirstItemSelected && selectedItems.equals(queue.subList(0, selectedSize))) {
return new Pair<>(false, true);
}
// If contiguous from the bottom, moving items to the bottom is disabled, as they are already there.
if (isLastItemSelected && selectedItems.equals(queue.subList(queueSize - selectedSize, queueSize))) {
return new Pair<>(true, false);
}
return new Pair<>(true, true);
}
private class QueueSwipeActions extends SwipeActions {
// Position tracking whilst dragging

View File

@ -42,4 +42,14 @@
android:icon="@drawable/ic_check"
android:title="@string/remove_inbox_label" />
<item
android:id="@+id/move_to_top_item"
android:icon="@drawable/ic_arrow_full_up"
android:title="@string/move_to_top_label" />
<item
android:id="@+id/move_to_bottom_item"
android:icon="@drawable/ic_arrow_full_down"
android:title="@string/move_to_bottom_label" />
</menu>

View File

@ -24,6 +24,7 @@ import org.greenrobot.eventbus.EventBus;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
@ -551,44 +552,6 @@ public class DBWriter {
});
}
/**
* Moves the specified item to the top of the queue.
*
* @param itemId The item to move to the top of the queue
* @param broadcastUpdate true if this operation should trigger a QueueUpdateBroadcast. This option should be set to
*/
public static Future<?> moveQueueItemToTop(final long itemId, final boolean broadcastUpdate) {
return runOnDbThread(() -> {
LongList queueIdList = DBReader.getQueueIDList();
int index = queueIdList.indexOf(itemId);
if (index >= 0) {
moveQueueItemHelper(index, 0, broadcastUpdate);
} else {
Log.e(TAG, "moveQueueItemToTop: item not found");
}
});
}
/**
* Moves the specified item to the bottom of the queue.
*
* @param itemId The item to move to the bottom of the queue
* @param broadcastUpdate true if this operation should trigger a QueueUpdateBroadcast. This option should be set to
*/
public static Future<?> moveQueueItemToBottom(final long itemId,
final boolean broadcastUpdate) {
return runOnDbThread(() -> {
LongList queueIdList = DBReader.getQueueIDList();
int index = queueIdList.indexOf(itemId);
if (index >= 0) {
moveQueueItemHelper(index, queueIdList.size() - 1,
broadcastUpdate);
} else {
Log.e(TAG, "moveQueueItemToBottom: item not found");
}
});
}
/**
* Changes the position of a FeedItem in the queue.
*
@ -598,36 +561,67 @@ public class DBWriter {
* false if the caller wants to avoid unexpected updates of the GUI.
* @throws IndexOutOfBoundsException if (to < 0 || to >= queue.size()) || (from < 0 || from >= queue.size())
*/
public static Future<?> moveQueueItem(final int from,
final int to, final boolean broadcastUpdate) {
return runOnDbThread(() -> moveQueueItemHelper(from, to, broadcastUpdate));
public static Future<?> moveQueueItem(final int from, final int to, final boolean broadcastUpdate) {
return runOnDbThread(() -> {
final PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
final List<FeedItem> queue = DBReader.getQueue();
if (from >= 0 && from < queue.size() && to >= 0 && to < queue.size()) {
final FeedItem item = queue.remove(from);
queue.add(to, item);
adapter.setQueue(queue);
if (broadcastUpdate) {
EventBus.getDefault().post(QueueEvent.moved(item, to));
}
}
adapter.close();
});
}
/**
* Changes the position of a FeedItem in the queue.
* <p/>
* This function must be run using the ExecutorService (dbExec).
*
* @param from Source index. Must be in range 0..queue.size()-1.
* @param to Destination index. Must be in range 0..queue.size()-1.
* @param broadcastUpdate true if this operation should trigger a QueueUpdateBroadcast. This option should be set to
* false if the caller wants to avoid unexpected updates of the GUI.
* @throws IndexOutOfBoundsException if (to < 0 || to >= queue.size()) || (from < 0 || from >= queue.size())
*/
private static void moveQueueItemHelper(final int from,
final int to, final boolean broadcastUpdate) {
public static Future<?> moveQueueItemsToTop(final List<FeedItem> items) {
return runOnDbThread(() -> moveQueueItemsSynchronous(true, items));
}
public static Future<?> moveQueueItemsToBottom(final List<FeedItem> items) {
return runOnDbThread(() -> moveQueueItemsSynchronous(false, items));
}
private static void moveQueueItemsSynchronous(final boolean moveToTop, final List<FeedItem> items) {
if (items.isEmpty()) {
return;
}
final PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
final List<FeedItem> queue = DBReader.getQueue();
if (from >= 0 && from < queue.size() && to >= 0 && to < queue.size()) {
final FeedItem item = queue.remove(from);
queue.add(to, item);
List<FeedItem> selectedItems = moveToTop ? new ArrayList<>(items) : items;
if (moveToTop) {
Collections.reverse(selectedItems);
}
boolean queueModified = false;
List<QueueEvent> events = new ArrayList<>();
queue.removeAll(selectedItems);
events.add(QueueEvent.setQueue(queue));
for (FeedItem item : selectedItems) {
int newIndex = moveToTop ? 0 : queue.size();
queue.add(newIndex, item);
events.add(QueueEvent.moved(item, newIndex));
queueModified = true;
}
if (queueModified) {
adapter.setQueue(queue);
if (broadcastUpdate) {
EventBus.getDefault().post(QueueEvent.moved(item, to));
for (QueueEvent event : events) {
EventBus.getDefault().post(event);
}
} else {
Log.w(TAG, "moveToTop: " + moveToTop + " - Queue was not modified.");
}
adapter.close();
}

View File

@ -0,0 +1,7 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path android:fillColor="?attr/action_icon_color" android:pathData="M 12.9 4.1 L 10.9 4.1 L 10.9 16.1 L 5.4 10.6 L 4 12 L 11.9 19.9 L 19.8 12 L 18.4 10.6 L 12.9 16.1 L 12.9 4.1 Z"/>
</vector>

View File

@ -0,0 +1,7 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path android:fillColor="?attr/action_icon_color" android:pathData="M 10.9 19.9 L 12.9 19.9 L 12.9 7.9 L 18.4 13.4 L 19.8 12 L 11.9 4.1 L 4 12 L 5.4 13.4 L 10.9 7.9 L 10.9 19.9 Z" />
</vector>

View File

@ -364,7 +364,15 @@
<string name="clear_queue_label">Clear queue</string>
<string name="undo">Undo</string>
<string name="move_to_top_label">Move to top</string>
<plurals name="move_to_top_message">
<item quantity="one">%d episode moved to top.</item>
<item quantity="other">%d episodes moved to top.</item>
</plurals>
<string name="move_to_bottom_label">Move to bottom</string>
<plurals name="move_to_bottom_message">
<item quantity="one">%d episode moved to bottom.</item>
<item quantity="other">%d episodes moved to bottom.</item>
</plurals>
<string name="sort">Sort</string>
<string name="keep_sorted">Keep sorted</string>
<string name="date">Date</string>