mirror of
https://github.com/AntennaPod/AntennaPod.git
synced 2025-10-29 11:49:33 +00:00
Add move to top/bottom multi select action in queue (#7696)
This commit is contained in:
parent
84907b114f
commit
5459368e51
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
7
ui/common/src/main/res/drawable/ic_arrow_full_down.xml
Normal file
7
ui/common/src/main/res/drawable/ic_arrow_full_down.xml
Normal 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>
|
||||
7
ui/common/src/main/res/drawable/ic_arrow_full_up.xml
Normal file
7
ui/common/src/main/res/drawable/ic_arrow_full_up.xml
Normal 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>
|
||||
@ -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>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user