Replace old episode multi-select with new multi-select. (#5253)

This commit is contained in:
peakvalleytech 2021-07-10 00:11:41 -07:00 committed by GitHub
parent 323f1f6142
commit 00bf2db0a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 177 additions and 694 deletions

View File

@ -106,5 +106,5 @@ workflows:
branchBaseCommit=`git merge-base origin/develop HEAD`
echo "Comparing to $branchBaseCommit"
curl -s -L https://github.com/ByteHamster/android-xml-formatter/releases/download/1.1.0/android-xml-formatter.jar > android-xml-formatter.jar
git diff --name-only $branchBaseCommit | { grep "res/layout/" || true; } | xargs java -jar android-xml-formatter.jar
git diff --name-only $branchBaseCommit --diff-filter=AM | { grep "res/layout/" || true; } | xargs java -jar android-xml-formatter.jar
test $(git diff | wc -l) -eq 0 || (echo -e "\n\n===== Found XML code style violations! See output below how to fix them. =====\n\n" && git --no-pager diff --color=always && false)

View File

@ -44,7 +44,7 @@ public class QueueRecyclerAdapter extends EpisodeItemListAdapter {
return false;
};
if (!dragDropEnabled) {
if (!dragDropEnabled || inActionMode()) {
holder.dragHandle.setVisibility(View.GONE);
holder.dragHandle.setOnTouchListener(null);
holder.coverHolder.setOnTouchListener(null);
@ -63,11 +63,17 @@ public class QueueRecyclerAdapter extends EpisodeItemListAdapter {
inflater.inflate(R.menu.queue_context, menu);
super.onCreateContextMenu(menu, v, menuInfo);
final boolean keepSorted = UserPreferences.isQueueKeepSorted();
if (getItem(0).getId() == getLongPressedItem().getId() || keepSorted) {
if (!inActionMode()) {
menu.findItem(R.id.multi_select).setVisible(true);
final boolean keepSorted = UserPreferences.isQueueKeepSorted();
if (getItem(0).getId() == getLongPressedItem().getId() || keepSorted) {
menu.findItem(R.id.move_to_top_item).setVisible(false);
}
if (getItem(getItemCount() - 1).getId() == getLongPressedItem().getId() || keepSorted) {
menu.findItem(R.id.move_to_bottom_item).setVisible(false);
}
} else {
menu.findItem(R.id.move_to_top_item).setVisible(false);
}
if (getItem(getItemCount() - 1).getId() == getLongPressedItem().getId() || keepSorted) {
menu.findItem(R.id.move_to_bottom_item).setVisible(false);
}
}

View File

@ -1,466 +0,0 @@
package de.danoeh.antennapod.dialog;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.PluralsRes;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.Toolbar;
import androidx.collection.ArrayMap;
import androidx.fragment.app.Fragment;
import com.google.android.material.snackbar.Snackbar;
import com.leinardi.android.speeddial.SpeedDialView;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.core.dialog.DownloadRequestErrorDialogCreator;
import de.danoeh.antennapod.model.feed.FeedItem;
import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.storage.DownloadRequestException;
import de.danoeh.antennapod.core.storage.DownloadRequester;
import de.danoeh.antennapod.core.util.FeedItemPermutors;
import de.danoeh.antennapod.core.util.LongList;
import de.danoeh.antennapod.model.feed.SortOrder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class EpisodesApplyActionFragment extends Fragment implements Toolbar.OnMenuItemClickListener {
public static final String TAG = "EpisodeActionFragment";
public static final int ACTION_ADD_TO_QUEUE = 1;
public static final int ACTION_REMOVE_FROM_QUEUE = 2;
private static final int ACTION_MARK_PLAYED = 4;
private static final int ACTION_MARK_UNPLAYED = 8;
public static final int ACTION_DOWNLOAD = 16;
public static final int ACTION_DELETE = 32;
public static final int ACTION_ALL = ACTION_ADD_TO_QUEUE | ACTION_REMOVE_FROM_QUEUE
| ACTION_MARK_PLAYED | ACTION_MARK_UNPLAYED | ACTION_DOWNLOAD | ACTION_DELETE;
/**
* Specify an action (defined by #flag) 's UI bindings.
*
* Includes: the menu / action item and the actual logic
*/
private static class ActionBinding {
int flag;
@IdRes
final int actionItemId;
@NonNull
final Runnable action;
ActionBinding(int flag, @IdRes int actionItemId, @NonNull Runnable action) {
this.flag = flag;
this.actionItemId = actionItemId;
this.action = action;
}
}
private final List<? extends ActionBinding> actionBindings;
private final Map<Long, FeedItem> idMap = new ArrayMap<>();
private final List<FeedItem> episodes = new ArrayList<>();
private int actions;
private final List<String> titles = new ArrayList<>();
private final LongList checkedIds = new LongList();
private ListView mListView;
private ArrayAdapter<String> mAdapter;
private SpeedDialView mSpeedDialView;
private Toolbar toolbar;
public EpisodesApplyActionFragment() {
actionBindings = Arrays.asList(
new ActionBinding(ACTION_ADD_TO_QUEUE,
R.id.add_to_queue_batch, this::queueChecked),
new ActionBinding(ACTION_REMOVE_FROM_QUEUE,
R.id.remove_from_queue_batch, this::removeFromQueueChecked),
new ActionBinding(ACTION_MARK_PLAYED,
R.id.mark_read_batch, this::markedCheckedPlayed),
new ActionBinding(ACTION_MARK_UNPLAYED,
R.id.mark_unread_batch, this::markedCheckedUnplayed),
new ActionBinding(ACTION_DOWNLOAD,
R.id.download_batch, this::downloadChecked),
new ActionBinding(ACTION_DELETE,
R.id.delete_batch, this::deleteChecked)
);
}
public static EpisodesApplyActionFragment newInstance(List<FeedItem> items, int actions) {
EpisodesApplyActionFragment f = new EpisodesApplyActionFragment();
f.episodes.addAll(items);
for (FeedItem episode : items) {
f.idMap.put(episode.getId(), episode);
}
f.actions = actions;
return f;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.episodes_apply_action_fragment, container, false);
toolbar = view.findViewById(R.id.toolbar);
toolbar.inflateMenu(R.menu.episodes_apply_action_options);
toolbar.setNavigationOnClickListener(v -> getParentFragmentManager().popBackStack());
toolbar.setOnMenuItemClickListener(this);
refreshToolbarState();
mListView = view.findViewById(android.R.id.list);
mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
mListView.setOnItemClickListener((listView, view1, position, rowId) -> {
long id = episodes.get(position).getId();
if (checkedIds.contains(id)) {
checkedIds.remove(id);
} else {
checkedIds.add(id);
}
refreshCheckboxes();
});
mListView.setOnItemLongClickListener((adapterView, view12, position, id) -> {
new AlertDialog.Builder(getActivity())
.setItems(R.array.batch_long_press_options, (dialogInterface, item) -> {
int direction;
if (item == 0) {
direction = -1;
} else {
direction = 1;
}
int currentPosition = position + direction;
while (currentPosition >= 0 && currentPosition < episodes.size()) {
long id1 = episodes.get(currentPosition).getId();
if (!checkedIds.contains(id1)) {
checkedIds.add(id1);
}
currentPosition += direction;
}
refreshCheckboxes();
}).show();
return true;
});
titles.clear();
for (FeedItem episode : episodes) {
titles.add(episode.getTitle());
}
mAdapter = new ArrayAdapter<>(getActivity(),
R.layout.simple_list_item_multiple_choice_on_start, titles);
mListView.setAdapter(mAdapter);
// Init action UI (via a FAB Speed Dial)
mSpeedDialView = view.findViewById(R.id.fabSD);
mSpeedDialView.inflate(R.menu.episodes_apply_action_speeddial);
// show only specified actions, and bind speed dial UIs to the actual logic
for (ActionBinding binding : actionBindings) {
if ((actions & binding.flag) == 0) {
mSpeedDialView.removeActionItemById(binding.actionItemId);
}
}
mSpeedDialView.setOnChangeListener(new SpeedDialView.OnChangeListener() {
@Override
public boolean onMainActionSelected() {
return false;
}
@Override
public void onToggleChanged(boolean open) {
if (open && checkedIds.size() == 0) {
((MainActivity) getActivity()).showSnackbarAbovePlayer(R.string.no_items_selected,
Snackbar.LENGTH_SHORT);
mSpeedDialView.close();
}
}
});
mSpeedDialView.setOnActionSelectedListener(actionItem -> {
ActionBinding selectedBinding = null;
for (ActionBinding binding : actionBindings) {
if (actionItem.getId() == binding.actionItemId) {
selectedBinding = binding;
break;
}
}
if (selectedBinding != null) {
selectedBinding.action.run();
} else {
Log.e(TAG, "Unrecognized speed dial action item. Do nothing. id=" + actionItem.getId());
}
return true;
});
refreshCheckboxes();
return view;
}
public void refreshToolbarState() {
MenuItem selectAllItem = toolbar.getMenu().findItem(R.id.select_toggle);
if (checkedIds.size() == episodes.size()) {
selectAllItem.setIcon(R.drawable.ic_select_none);
selectAllItem.setTitle(R.string.deselect_all_label);
} else {
selectAllItem.setIcon(R.drawable.ic_select_all);
selectAllItem.setTitle(R.string.select_all_label);
}
}
private static final Map<Integer, SortOrder> menuItemIdToSortOrder;
static {
Map<Integer, SortOrder> map = new ArrayMap<>();
map.put(R.id.sort_title_a_z, SortOrder.EPISODE_TITLE_A_Z);
map.put(R.id.sort_title_z_a, SortOrder.EPISODE_TITLE_Z_A);
map.put(R.id.sort_date_new_old, SortOrder.DATE_NEW_OLD);
map.put(R.id.sort_date_old_new, SortOrder.DATE_OLD_NEW);
map.put(R.id.sort_duration_long_short, SortOrder.DURATION_LONG_SHORT);
map.put(R.id.sort_duration_short_long, SortOrder.DURATION_SHORT_LONG);
menuItemIdToSortOrder = Collections.unmodifiableMap(map);
}
@Override
public boolean onMenuItemClick(MenuItem item) {
@StringRes int resId = 0;
switch (item.getItemId()) {
case R.id.select_options:
return true;
case R.id.select_toggle:
if (checkedIds.size() == episodes.size()) {
checkNone();
} else {
checkAll();
}
return true;
case R.id.check_all:
checkAll();
resId = R.string.selected_all_label;
break;
case R.id.check_none:
checkNone();
resId = R.string.deselected_all_label;
break;
case R.id.check_played:
checkPlayed(true);
resId = R.string.selected_played_label;
break;
case R.id.check_unplayed:
checkPlayed(false);
resId = R.string.selected_unplayed_label;
break;
case R.id.check_downloaded:
checkDownloaded(true);
resId = R.string.selected_downloaded_label;
break;
case R.id.check_not_downloaded:
checkDownloaded(false);
resId = R.string.selected_not_downloaded_label;
break;
case R.id.check_queued:
checkQueued(true);
resId = R.string.selected_queued_label;
break;
case R.id.check_not_queued:
checkQueued(false);
resId = R.string.selected_not_queued_label;
break;
case R.id.check_has_media:
checkWithMedia();
resId = R.string.selected_has_media_label;
break;
default: // handle various sort options
SortOrder sortOrder = menuItemIdToSortOrder.get(item.getItemId());
if (sortOrder != null) {
sort(sortOrder);
return true;
}
}
if (resId != 0) {
((MainActivity) getActivity()).showSnackbarAbovePlayer(resId, Snackbar.LENGTH_SHORT);
return true;
} else {
return false;
}
}
private void sort(@NonNull SortOrder sortOrder) {
FeedItemPermutors.getPermutor(sortOrder)
.reorder(episodes);
refreshTitles();
refreshCheckboxes();
}
private void checkAll() {
for (FeedItem episode : episodes) {
if (!checkedIds.contains(episode.getId())) {
checkedIds.add(episode.getId());
}
}
refreshCheckboxes();
}
private void checkNone() {
checkedIds.clear();
refreshCheckboxes();
}
private void checkPlayed(boolean isPlayed) {
for (FeedItem episode : episodes) {
if (episode.isPlayed() == isPlayed) {
if (!checkedIds.contains(episode.getId())) {
checkedIds.add(episode.getId());
}
} else {
if (checkedIds.contains(episode.getId())) {
checkedIds.remove(episode.getId());
}
}
}
refreshCheckboxes();
}
private void checkDownloaded(boolean isDownloaded) {
for (FeedItem episode : episodes) {
if (episode.hasMedia() && episode.getMedia().isDownloaded() == isDownloaded) {
if (!checkedIds.contains(episode.getId())) {
checkedIds.add(episode.getId());
}
} else {
if (checkedIds.contains(episode.getId())) {
checkedIds.remove(episode.getId());
}
}
}
refreshCheckboxes();
}
private void checkQueued(boolean isQueued) {
for (FeedItem episode : episodes) {
if (episode.isTagged(FeedItem.TAG_QUEUE) == isQueued) {
checkedIds.add(episode.getId());
} else {
checkedIds.remove(episode.getId());
}
}
refreshCheckboxes();
}
private void checkWithMedia() {
for (FeedItem episode : episodes) {
if (episode.hasMedia()) {
checkedIds.add(episode.getId());
} else {
checkedIds.remove(episode.getId());
}
}
refreshCheckboxes();
}
private void refreshTitles() {
titles.clear();
for (FeedItem episode : episodes) {
titles.add(episode.getTitle());
}
mAdapter.notifyDataSetChanged();
}
private void refreshCheckboxes() {
for (int i = 0; i < episodes.size(); i++) {
FeedItem episode = episodes.get(i);
boolean checked = checkedIds.contains(episode.getId());
mListView.setItemChecked(i, checked);
}
refreshToolbarState();
toolbar.setTitle(getResources().getQuantityString(R.plurals.num_selected_label,
checkedIds.size(), checkedIds.size()));
}
private void queueChecked() {
// Check if an episode actually contains any media files before adding it to queue
LongList toQueue = new LongList(checkedIds.size());
for (FeedItem episode : episodes) {
if (checkedIds.contains(episode.getId()) && episode.hasMedia()) {
toQueue.add(episode.getId());
}
}
DBWriter.addQueueItem(getActivity(), true, toQueue.toArray());
close(R.plurals.added_to_queue_batch_label, toQueue.size());
}
private void removeFromQueueChecked() {
DBWriter.removeQueueItem(getActivity(), true, checkedIds.toArray());
close(R.plurals.removed_from_queue_batch_label, checkedIds.size());
}
private void markedCheckedPlayed() {
DBWriter.markItemPlayed(FeedItem.PLAYED, checkedIds.toArray());
close(R.plurals.marked_read_batch_label, checkedIds.size());
}
private void markedCheckedUnplayed() {
DBWriter.markItemPlayed(FeedItem.UNPLAYED, checkedIds.toArray());
close(R.plurals.marked_unread_batch_label, checkedIds.size());
}
private void downloadChecked() {
// download the check episodes in the same order as they are currently displayed
List<FeedItem> toDownload = new ArrayList<>(checkedIds.size());
for (FeedItem episode : episodes) {
if (checkedIds.contains(episode.getId()) && episode.hasMedia() && !episode.getFeed().isLocalFeed()) {
toDownload.add(episode);
}
}
try {
DownloadRequester.getInstance().downloadMedia(getActivity(), true, toDownload.toArray(new FeedItem[0]));
} catch (DownloadRequestException e) {
e.printStackTrace();
DownloadRequestErrorDialogCreator.newRequestErrorDialog(getActivity(), e.getMessage());
}
close(R.plurals.downloading_batch_label, toDownload.size());
}
private void deleteChecked() {
int countHasMedia = 0;
int countNoMedia = 0;
for (long id : checkedIds.toArray()) {
FeedItem episode = idMap.get(id);
if (episode.hasMedia() && episode.getMedia().isDownloaded()) {
countHasMedia++;
DBWriter.deleteFeedMediaOfItem(getActivity(), episode.getMedia().getId());
} else {
countNoMedia++;
}
}
closeMore(R.plurals.deleted_multi_episode_batch_label, countNoMedia, countHasMedia);
}
private void close(@PluralsRes int msgId, int numItems) {
((MainActivity) getActivity()).showSnackbarAbovePlayer(
getResources().getQuantityString(msgId, numItems, numItems), Snackbar.LENGTH_LONG);
getActivity().getSupportFragmentManager().popBackStack();
}
private void closeMore(@PluralsRes int msgId, int countNoMedia, int countHasMedia) {
((MainActivity) getActivity()).showSnackbarAbovePlayer(
getResources().getQuantityString(msgId,
(countHasMedia + countNoMedia),
(countHasMedia + countNoMedia), countHasMedia),
Snackbar.LENGTH_LONG);
getActivity().getSupportFragmentManager().popBackStack();
}
}

View File

@ -2,6 +2,7 @@ package de.danoeh.antennapod.fragment;
import android.os.Bundle;
import android.util.Log;
import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
@ -12,6 +13,10 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.Fragment;
import com.google.android.material.snackbar.Snackbar;
import com.leinardi.android.speeddial.SpeedDialView;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.adapter.EpisodeItemListAdapter;
@ -22,13 +27,13 @@ import de.danoeh.antennapod.core.event.FeedItemEvent;
import de.danoeh.antennapod.core.event.PlaybackPositionEvent;
import de.danoeh.antennapod.core.event.PlayerStatusEvent;
import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent;
import de.danoeh.antennapod.fragment.actions.EpisodeMultiSelectActionHandler;
import de.danoeh.antennapod.model.feed.FeedItem;
import de.danoeh.antennapod.core.service.download.DownloadService;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.DownloadRequester;
import de.danoeh.antennapod.core.util.FeedItemUtil;
import de.danoeh.antennapod.core.util.download.AutoUpdateManager;
import de.danoeh.antennapod.dialog.EpisodesApplyActionFragment;
import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler;
import de.danoeh.antennapod.menuhandler.MenuItemUtils;
import de.danoeh.antennapod.view.EmptyViewHandler;
@ -45,13 +50,11 @@ import org.greenrobot.eventbus.ThreadMode;
import java.util.ArrayList;
import java.util.List;
import static de.danoeh.antennapod.dialog.EpisodesApplyActionFragment.ACTION_ADD_TO_QUEUE;
import static de.danoeh.antennapod.dialog.EpisodesApplyActionFragment.ACTION_DELETE;
/**
* Displays all completed downloads and provides a button to delete them.
*/
public class CompletedDownloadsFragment extends Fragment {
public class CompletedDownloadsFragment extends Fragment implements
EpisodeItemListAdapter.OnEndSelectModeListener {
private static final String TAG = CompletedDownloadsFragment.class.getSimpleName();
@ -64,6 +67,8 @@ public class CompletedDownloadsFragment extends Fragment {
private boolean isUpdatingFeeds = false;
private SpeedDialView speedDialView;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
@ -74,9 +79,39 @@ public class CompletedDownloadsFragment extends Fragment {
recyclerView = root.findViewById(R.id.recyclerView);
recyclerView.setRecycledViewPool(((MainActivity) getActivity()).getRecycledViewPool());
adapter = new CompletedDownloadsListAdapter((MainActivity) getActivity());
adapter.setOnEndSelectModeListener(this);
recyclerView.setAdapter(adapter);
progressBar = root.findViewById(R.id.progLoading);
speedDialView = root.findViewById(R.id.fabSD);
speedDialView.inflate(R.menu.episodes_apply_action_speeddial);
speedDialView.removeActionItemById(R.id.download_batch);
speedDialView.removeActionItemById(R.id.mark_read_batch);
speedDialView.removeActionItemById(R.id.mark_unread_batch);
speedDialView.removeActionItemById(R.id.remove_from_queue_batch);
speedDialView.setOnChangeListener(new SpeedDialView.OnChangeListener() {
@Override
public boolean onMainActionSelected() {
return false;
}
@Override
public void onToggleChanged(boolean open) {
if (open && adapter.getSelectedCount() == 0) {
((MainActivity) getActivity()).showSnackbarAbovePlayer(R.string.no_items_selected,
Snackbar.LENGTH_SHORT);
speedDialView.close();
}
}
});
speedDialView.setOnActionSelectedListener(actionItem -> {
new EpisodeMultiSelectActionHandler(((MainActivity) getActivity()), adapter.getSelectedItems())
.handleAction(actionItem.getId());
onEndSelectMode();
adapter.endSelectMode();
return true;
});
addEmptyView();
EventBus.getDefault().register(this);
return root;
@ -105,17 +140,12 @@ public class CompletedDownloadsFragment extends Fragment {
@Override
public void onPrepareOptionsMenu(@NonNull Menu menu) {
menu.findItem(R.id.clear_logs_item).setVisible(false);
menu.findItem(R.id.episode_actions).setVisible(items.size() > 0);
isUpdatingFeeds = MenuItemUtils.updateRefreshMenuItem(menu, R.id.refresh_item, updateRefreshMenuItemChecker);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.episode_actions) {
((MainActivity) requireActivity())
.loadChildFragment(EpisodesApplyActionFragment.newInstance(items, ACTION_DELETE | ACTION_ADD_TO_QUEUE));
return true;
} else if (item.getItemId() == R.id.refresh_item) {
if (item.getItemId() == R.id.refresh_item) {
AutoUpdateManager.runImmediate(requireContext());
return true;
}
@ -140,6 +170,13 @@ public class CompletedDownloadsFragment extends Fragment {
Log.i(TAG, "Selected item at current position was null, ignoring selection");
return super.onContextItemSelected(item);
}
if (item.getItemId() == R.id.multi_select) {
speedDialView.setVisibility(View.VISIBLE);
}
if (adapter.onContextItemSelected(item)) {
return true;
}
return FeedItemMenuHandler.onMenuItemClicked(this, item.getItemId(), selectedItem);
}
@ -151,7 +188,6 @@ public class CompletedDownloadsFragment extends Fragment {
emptyView.attachToRecyclerView(recyclerView);
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEventMainThread(FeedItemEvent event) {
Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]");
@ -221,6 +257,12 @@ public class CompletedDownloadsFragment extends Fragment {
}, error -> Log.e(TAG, Log.getStackTraceString(error)));
}
@Override
public void onEndSelectMode() {
speedDialView.close();
speedDialView.setVisibility(View.GONE);
}
private static class CompletedDownloadsListAdapter extends EpisodeItemListAdapter {
public CompletedDownloadsListAdapter(MainActivity mainActivity) {
@ -232,5 +274,13 @@ public class CompletedDownloadsFragment extends Fragment {
DeleteActionButton actionButton = new DeleteActionButton(getItem(pos));
actionButton.configure(holder.secondaryActionButton, holder.secondaryActionIcon, getActivity());
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
if (!inActionMode()) {
menu.findItem(R.id.multi_select).setVisible(true);
}
}
}
}

View File

@ -164,7 +164,6 @@ public class DownloadLogFragment extends ListFragment {
@Override
public void onPrepareOptionsMenu(@NonNull Menu menu) {
menu.findItem(R.id.episode_actions).setVisible(false);
menu.findItem(R.id.clear_logs_item).setVisible(!downloadLog.isEmpty());
isUpdatingFeeds = MenuItemUtils.updateRefreshMenuItem(menu, R.id.refresh_item, updateRefreshMenuItemChecker);
}

View File

@ -24,8 +24,11 @@ import androidx.recyclerview.widget.SimpleItemAnimator;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.google.android.material.snackbar.Snackbar;
import com.leinardi.android.speeddial.SpeedDialView;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.adapter.EpisodeItemListAdapter;
import de.danoeh.antennapod.adapter.QueueRecyclerAdapter;
import de.danoeh.antennapod.core.dialog.ConfirmationDialog;
import de.danoeh.antennapod.core.event.DownloadEvent;
@ -35,6 +38,7 @@ import de.danoeh.antennapod.core.event.PlaybackPositionEvent;
import de.danoeh.antennapod.core.event.PlayerStatusEvent;
import de.danoeh.antennapod.core.event.QueueEvent;
import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent;
import de.danoeh.antennapod.fragment.actions.EpisodeMultiSelectActionHandler;
import de.danoeh.antennapod.model.feed.FeedItem;
import de.danoeh.antennapod.core.feed.util.PlaybackSpeedUtils;
import de.danoeh.antennapod.core.preferences.UserPreferences;
@ -46,7 +50,6 @@ import de.danoeh.antennapod.core.util.Converter;
import de.danoeh.antennapod.core.util.FeedItemUtil;
import de.danoeh.antennapod.model.feed.SortOrder;
import de.danoeh.antennapod.core.util.download.AutoUpdateManager;
import de.danoeh.antennapod.dialog.EpisodesApplyActionFragment;
import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler;
import de.danoeh.antennapod.menuhandler.MenuItemUtils;
import de.danoeh.antennapod.view.EmptyViewHandler;
@ -63,14 +66,11 @@ import org.greenrobot.eventbus.ThreadMode;
import java.util.List;
import java.util.Locale;
import static de.danoeh.antennapod.dialog.EpisodesApplyActionFragment.ACTION_DELETE;
import static de.danoeh.antennapod.dialog.EpisodesApplyActionFragment.ACTION_DOWNLOAD;
import static de.danoeh.antennapod.dialog.EpisodesApplyActionFragment.ACTION_REMOVE_FROM_QUEUE;
/**
* Shows all items in the queue.
*/
public class QueueFragment extends Fragment implements Toolbar.OnMenuItemClickListener {
public class QueueFragment extends Fragment implements Toolbar.OnMenuItemClickListener,
EpisodeItemListAdapter.OnEndSelectModeListener {
public static final String TAG = "QueueFragment";
private static final String KEY_UP_ARROW = "up_arrow";
@ -93,6 +93,8 @@ public class QueueFragment extends Fragment implements Toolbar.OnMenuItemClickLi
private ItemTouchHelper itemTouchHelper;
private SharedPreferences prefs;
private SpeedDialView speedDialView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -277,11 +279,6 @@ public class QueueFragment extends Fragment implements Toolbar.OnMenuItemClickLi
};
conDialog.createNewDialog().show();
return true;
case R.id.episode_actions:
((MainActivity) requireActivity()).loadChildFragment(
EpisodesApplyActionFragment.newInstance(queue,
ACTION_DELETE | ACTION_REMOVE_FROM_QUEUE | ACTION_DOWNLOAD));
return true;
case R.id.queue_sort_episode_title_asc:
setSortOrder(SortOrder.EPISODE_TITLE_A_Z);
return true;
@ -402,6 +399,15 @@ public class QueueFragment extends Fragment implements Toolbar.OnMenuItemClickLi
Log.i(TAG, "Selected item no longer exist, ignoring selection");
return super.onContextItemSelected(item);
}
if (item.getItemId() == R.id.multi_select) {
speedDialView.setVisibility(View.VISIBLE);
refreshToolbarState();
infoBar.setVisibility(View.GONE);
// Do not return: Let adapter handle its actions, too.
}
if (recyclerAdapter.onContextItemSelected(item)) {
return true;
}
switch(item.getItemId()) {
case R.id.move_to_top_item:
@ -419,7 +425,6 @@ public class QueueFragment extends Fragment implements Toolbar.OnMenuItemClickLi
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
@ -538,6 +543,33 @@ public class QueueFragment extends Fragment implements Toolbar.OnMenuItemClickLi
progLoading = root.findViewById(R.id.progLoading);
progLoading.setVisibility(View.VISIBLE);
speedDialView = root.findViewById(R.id.fabSD);
speedDialView.inflate(R.menu.episodes_apply_action_speeddial);
speedDialView.removeActionItemById(R.id.mark_read_batch);
speedDialView.removeActionItemById(R.id.mark_unread_batch);
speedDialView.removeActionItemById(R.id.add_to_queue_batch);
speedDialView.setOnChangeListener(new SpeedDialView.OnChangeListener() {
@Override
public boolean onMainActionSelected() {
return false;
}
@Override
public void onToggleChanged(boolean open) {
if (open && recyclerAdapter.getSelectedCount() == 0) {
((MainActivity) getActivity()).showSnackbarAbovePlayer(R.string.no_items_selected,
Snackbar.LENGTH_SHORT);
speedDialView.close();
}
}
});
speedDialView.setOnActionSelectedListener(actionItem -> {
new EpisodeMultiSelectActionHandler(((MainActivity) getActivity()), recyclerAdapter.getSelectedItems())
.handleAction(actionItem.getId());
onEndSelectMode();
recyclerAdapter.endSelectMode();
return true;
});
return root;
}
@ -552,6 +584,7 @@ public class QueueFragment extends Fragment implements Toolbar.OnMenuItemClickLi
if (recyclerAdapter == null) {
MainActivity activity = (MainActivity) getActivity();
recyclerAdapter = new QueueRecyclerAdapter(activity, itemTouchHelper);
recyclerAdapter.setOnEndSelectModeListener(this);
recyclerView.setAdapter(recyclerAdapter);
emptyView.updateAdapter(recyclerAdapter);
}
@ -615,4 +648,11 @@ public class QueueFragment extends Fragment implements Toolbar.OnMenuItemClickLi
}
}, error -> Log.e(TAG, Log.getStackTraceString(error)));
}
@Override
public void onEndSelectMode() {
speedDialView.close();
speedDialView.setVisibility(View.GONE);
infoBar.setVisibility(View.VISIBLE);
}
}

View File

@ -1,59 +0,0 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?attr/actionBarSize"
android:theme="?attr/actionBarTheme"
android:layout_alignParentTop="true"
app:navigationIcon="?homeAsUpIndicator"
android:id="@+id/toolbar"/>
<ListView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/toolbar"
android:layout_marginTop="0dp" />
<com.leinardi.android.speeddial.SpeedDialOverlayLayout
android:id="@+id/fabSDOverlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:importantForAccessibility="no"
android:layout_below="@id/toolbar" />
<!-- The FAB SpeedDial
1. MUST be placed at the bottom of the layout xml to ensure it is at the front,
clickable on Pre-Lollipop devices (that do not support elevation).
See: https://stackoverflow.com/a/2614402
2. ScrollView is needed to ensure the vertical list of speed dials are
accessible when screen height is small, eg., landscape mode on most phones.
-->
<ScrollView
android:id="@+id/fabSDScrollCtr"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:elevation="@dimen/sd_open_elevation"
tools:ignore="UnusedAttribute" >
<com.leinardi.android.speeddial.SpeedDialView
android:id="@+id/fabSD"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:sdMainFabClosedSrc="@drawable/ic_fab_edit"
app:sdOverlayLayout="@id/fabSDOverlay"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:layout_marginBottom="16dp"
android:accessibilityTraversalBefore="@android:id/list"
android:contentDescription="@string/apply_action" />
</ScrollView>
</RelativeLayout>

View File

@ -21,6 +21,9 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:layout_alignParentBottom="true"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:elevation="@dimen/sd_open_elevation">
<com.leinardi.android.speeddial.SpeedDialView

View File

@ -1,42 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?attr/actionBarSize"
android:theme="?attr/actionBarTheme"
android:layout_alignParentTop="true"
app:title="@string/queue_label"
android:id="@+id/toolbar"/>
app:title="@string/queue_label" />
<TextView
android:layout_below="@id/toolbar"
android:id="@+id/info_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/toolbar"
android:textSize="12sp"
android:layout_marginTop="-8dp"
android:layout_marginLeft="72dp"
android:layout_marginStart="72dp"
android:layout_marginBottom="4dp"
tools:text="12 Episodes - Time remaining: 12 hours"/>
tools:text="12 Episodes - Time remaining: 12 hours" />
<View
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_below="@id/info_bar"
android:background="?android:attr/listDivider"/>
android:background="?android:attr/listDivider" />
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefresh"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/divider">
android:id="@+id/swipeRefresh"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/divider">
<de.danoeh.antennapod.view.EpisodeItemListRecyclerView
android:id="@+id/recyclerView"
@ -54,4 +56,7 @@
android:indeterminateOnly="true"
android:visibility="gone" />
<include
layout="@layout/multi_select_speed_dial" />
</RelativeLayout>

View File

@ -1,29 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?attr/actionBarSize"
android:theme="?attr/actionBarTheme"
android:layout_alignParentTop="true"
android:id="@+id/toolbar"/>
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?attr/actionBarSize"
android:theme="?attr/actionBarTheme"
android:layout_alignParentTop="true" />
<de.danoeh.antennapod.view.EpisodeItemListRecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingHorizontal="@dimen/additional_horizontal_spacing"
android:layout_below="@id/toolbar"
android:id="@+id/recyclerView"/>
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingHorizontal="@dimen/additional_horizontal_spacing"
android:layout_below="@id/toolbar" />
<ProgressBar
android:id="@+id/progLoading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:indeterminateOnly="true"
android:visibility="gone"/>
android:id="@+id/progLoading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:indeterminateOnly="true"
android:visibility="gone" />
<include
layout="@layout/multi_select_speed_dial" />
</RelativeLayout>

View File

@ -1,13 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/episode_actions"
android:menuCategory="container"
android:title="@string/multi_select"
android:icon="@drawable/ic_check_multiple"
android:visible="false"
app:showAsAction="ifRoom" />
<item
android:id="@+id/clear_logs_item"
android:menuCategory="container"

View File

@ -1,58 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/sort"
android:icon="@drawable/ic_sort"
android:title="@string/sort"
app:showAsAction="always">
<menu>
<item android:id="@+id/sort_title_a_z"
android:title="@string/sort_title_a_z"/>
<item android:id="@+id/sort_title_z_a"
android:title="@string/sort_title_z_a"/>
<item android:id="@+id/sort_date_new_old"
android:title="@string/sort_date_new_old"/>
<item android:id="@+id/sort_date_old_new"
android:title="@string/sort_date_old_new"/>
<item android:id="@+id/sort_duration_short_long"
android:title="@string/sort_duration_short_long"/>
<item android:id="@+id/sort_duration_long_short"
android:title="@string/sort_duration_long_short"/>
</menu>
</item>
<item
android:id="@+id/select_options"
android:icon="@drawable/ic_filter"
android:title="@string/filter"
app:showAsAction="always">
<menu>
<item android:id="@+id/check_all"
android:title="@string/all_label"/>
<item android:id="@+id/check_none"
android:title="@string/select_none_label"/>
<item android:id="@+id/check_played"
android:title="@string/played_label"/>
<item android:id="@+id/check_unplayed"
android:title="@string/unplayed_label"/>
<item android:id="@+id/check_downloaded"
android:title="@string/downloaded_label"/>
<item android:id="@+id/check_not_downloaded"
android:title="@string/not_downloaded_label"/>
<item android:id="@+id/check_queued"
android:title="@string/queued_label"/>
<item android:id="@+id/check_not_queued"
android:title="@string/not_queued_label"/>
<item android:id="@+id/check_has_media"
android:title="@string/has_media"/>
</menu>
</item>
<item
android:id="@+id/select_toggle"
android:title="@string/select_all_label"
app:showAsAction="always"/>
</menu>

View File

@ -1,11 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/multi_select"
android:menuCategory="container"
android:title="@string/multi_select"
android:visible="false"/>
<item
android:id="@id/skip_episode_item"
android:menuCategory="container"
@ -75,4 +69,10 @@
android:id="@+id/share_item"
android:menuCategory="container"
android:title="@string/share_label" />
<item
android:id="@+id/multi_select"
android:menuCategory="container"
android:title="@string/multi_select"
android:visible="false" />
</menu>

View File

@ -115,10 +115,4 @@
android:title="@string/clear_queue_label"
custom:showAsAction="collapseActionView"
android:icon="@drawable/ic_check"/>
<item
android:id="@+id/episode_actions"
custom:showAsAction="collapseActionView"
android:title="@string/multi_select" />
</menu>

View File

@ -1,5 +0,0 @@
<vector android:height="24dp"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="?attr/action_icon_color" android:pathData="M20,16V10H22V16A2,2 0 0,1 20,18H8C6.89,18 6,17.1 6,16V4C6,2.89 6.89,2 8,2H16V4H8V16H20M10.91,7.08L14,10.17L20.59,3.58L22,5L14,13L9.5,8.5L10.91,7.08M16,20V22H4A2,2 0 0,1 2,20V7H4V20H16Z"/>
</vector>

View File

@ -314,11 +314,6 @@
<item>continue</item>
</string-array>
<string-array name="batch_long_press_options">
<item>@string/select_all_above</item>
<item>@string/select_all_below</item>
</string-array>
<string-array name="back_button_behavior_options">
<item>@string/back_button_default</item>
<item>@string/back_button_go_to_page</item>

View File

@ -727,24 +727,6 @@
<string name="unable_to_start_system_file_manager">Unable to start system file manager</string>
<string name="filter">Filter</string>
<!-- Episodes apply actions -->
<string name="all_label">All</string>
<string name="selected_all_label">Selected all Episodes</string>
<string name="select_none_label">None</string>
<string name="deselected_all_label">Deselected all Episodes</string>
<string name="played_label">Played</string>
<string name="selected_played_label">Selected played Episodes</string>
<string name="unplayed_label">Unplayed</string>
<string name="selected_unplayed_label">Selected unplayed Episodes</string>
<string name="downloaded_label">Downloaded</string>
<string name="selected_downloaded_label">Selected downloaded Episodes</string>
<string name="not_downloaded_label">Not downloaded</string>
<string name="selected_not_downloaded_label">Selected not downloaded Episodes</string>
<string name="selected_queued_label">Selected queued Episodes</string>
<string name="selected_not_queued_label">Selected not queued Episodes</string>
<string name="selected_has_media_label">Selected episodes with media</string>
<string name="hide_is_favorite_label">Is favorite</string>
<string name="not_favorite">Not favorite</string>
<string name="hide_downloaded_episodes_label">Downloaded</string>