diff options
author | peakvalleytech <65185819+peakvalleytech@users.noreply.github.com> | 2021-07-10 00:11:41 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-07-10 09:11:41 +0200 |
commit | 00bf2db0a454e5c0d6bbe62d468f97a6f8056127 (patch) | |
tree | 6ec497cb1cc660ab5c2e57f0739c8ad79d8eafab /app/src | |
parent | 323f1f61424c39f8cde6076a4d30501bc75fc109 (diff) | |
download | AntennaPod-00bf2db0a454e5c0d6bbe62d468f97a6f8056127.zip |
Replace old episode multi-select with new multi-select. (#5253)
Diffstat (limited to 'app/src')
13 files changed, 176 insertions, 665 deletions
diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/QueueRecyclerAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/QueueRecyclerAdapter.java index 117ba3258..0e238eae2 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/QueueRecyclerAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/QueueRecyclerAdapter.java @@ -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); } } diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/EpisodesApplyActionFragment.java b/app/src/main/java/de/danoeh/antennapod/dialog/EpisodesApplyActionFragment.java deleted file mode 100644 index 508ce74f4..000000000 --- a/app/src/main/java/de/danoeh/antennapod/dialog/EpisodesApplyActionFragment.java +++ /dev/null @@ -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(); - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java index 9e3402972..f6a59f158 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java @@ -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); + } + } } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/DownloadLogFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/DownloadLogFragment.java index 1f6067125..80ab2b719 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/DownloadLogFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/DownloadLogFragment.java @@ -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); } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java index d309ac253..3db53595d 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java @@ -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); + } } diff --git a/app/src/main/res/layout/episodes_apply_action_fragment.xml b/app/src/main/res/layout/episodes_apply_action_fragment.xml deleted file mode 100644 index 78827a12a..000000000 --- a/app/src/main/res/layout/episodes_apply_action_fragment.xml +++ /dev/null @@ -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> diff --git a/app/src/main/res/layout/multi_select_speed_dial.xml b/app/src/main/res/layout/multi_select_speed_dial.xml index 159b9a955..0451471bc 100644 --- a/app/src/main/res/layout/multi_select_speed_dial.xml +++ b/app/src/main/res/layout/multi_select_speed_dial.xml @@ -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 diff --git a/app/src/main/res/layout/queue_fragment.xml b/app/src/main/res/layout/queue_fragment.xml index 3bcd4819f..292b1bb45 100644 --- a/app/src/main/res/layout/queue_fragment.xml +++ b/app/src/main/res/layout/queue_fragment.xml @@ -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> diff --git a/app/src/main/res/layout/simple_list_fragment.xml b/app/src/main/res/layout/simple_list_fragment.xml index 989566499..6ea3ab54b 100644 --- a/app/src/main/res/layout/simple_list_fragment.xml +++ b/app/src/main/res/layout/simple_list_fragment.xml @@ -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> diff --git a/app/src/main/res/menu/downloads.xml b/app/src/main/res/menu/downloads.xml index 142f251fc..54469a101 100644 --- a/app/src/main/res/menu/downloads.xml +++ b/app/src/main/res/menu/downloads.xml @@ -2,13 +2,6 @@ <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" android:title="@string/clear_history_label" diff --git a/app/src/main/res/menu/episodes_apply_action_options.xml b/app/src/main/res/menu/episodes_apply_action_options.xml deleted file mode 100644 index 221ec4d59..000000000 --- a/app/src/main/res/menu/episodes_apply_action_options.xml +++ /dev/null @@ -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> diff --git a/app/src/main/res/menu/feeditemlist_context.xml b/app/src/main/res/menu/feeditemlist_context.xml index f55afd3f8..27f1af6ca 100644 --- a/app/src/main/res/menu/feeditemlist_context.xml +++ b/app/src/main/res/menu/feeditemlist_context.xml @@ -1,12 +1,6 @@ <?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" android:title="@string/skip_episode_label" /> @@ -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>
\ No newline at end of file diff --git a/app/src/main/res/menu/queue.xml b/app/src/main/res/menu/queue.xml index adf44b8b1..34d8f32c0 100644 --- a/app/src/main/res/menu/queue.xml +++ b/app/src/main/res/menu/queue.xml @@ -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> |