summaryrefslogtreecommitdiff
path: root/app/src/main/java/de/danoeh
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/java/de/danoeh')
-rw-r--r--app/src/main/java/de/danoeh/antennapod/adapter/EpisodeItemListAdapter.java87
-rw-r--r--app/src/main/java/de/danoeh/antennapod/adapter/QueueRecyclerAdapter.java4
-rw-r--r--app/src/main/java/de/danoeh/antennapod/adapter/SelectableAdapter.java168
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java2
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java4
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java97
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java2
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java2
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java2
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/actions/EpisodeMultiSelectActionHandler.java130
-rw-r--r--app/src/main/java/de/danoeh/antennapod/view/viewholder/EpisodeItemViewHolder.java7
11 files changed, 455 insertions, 50 deletions
diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/EpisodeItemListAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/EpisodeItemListAdapter.java
index 774b97454..674b5f86e 100644
--- a/app/src/main/java/de/danoeh/antennapod/adapter/EpisodeItemListAdapter.java
+++ b/app/src/main/java/de/danoeh/antennapod/adapter/EpisodeItemListAdapter.java
@@ -3,11 +3,20 @@ package de.danoeh.antennapod.adapter;
import android.app.Activity;
import android.view.ContextMenu;
import android.view.MenuInflater;
+import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
+
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
+
+import org.apache.commons.lang3.ArrayUtils;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.model.feed.FeedItem;
@@ -15,24 +24,20 @@ import de.danoeh.antennapod.core.util.FeedItemUtil;
import de.danoeh.antennapod.fragment.ItemPagerFragment;
import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler;
import de.danoeh.antennapod.view.viewholder.EpisodeItemViewHolder;
-import org.apache.commons.lang3.ArrayUtils;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.List;
/**
* List adapter for the list of new episodes.
*/
-public class EpisodeItemListAdapter extends RecyclerView.Adapter<EpisodeItemViewHolder>
+public class EpisodeItemListAdapter extends SelectableAdapter<EpisodeItemViewHolder>
implements View.OnCreateContextMenuListener {
private final WeakReference<MainActivity> mainActivityRef;
private List<FeedItem> episodes = new ArrayList<>();
- private FeedItem selectedItem;
+ private FeedItem longPressedItem;
+ int longPressedPosition = 0; // used to init actionMode
public EpisodeItemListAdapter(MainActivity mainActivity) {
- super();
+ super(mainActivity);
this.mainActivityRef = new WeakReference<>(mainActivity);
setHasStableIds(true);
}
@@ -63,19 +68,35 @@ public class EpisodeItemListAdapter extends RecyclerView.Adapter<EpisodeItemView
FeedItem item = episodes.get(pos);
holder.bind(item);
- holder.itemView.setOnLongClickListener(v -> {
- selectedItem = item;
- return false;
- });
+
holder.itemView.setOnClickListener(v -> {
MainActivity activity = mainActivityRef.get();
- if (activity != null) {
+ if (activity != null && !inActionMode()) {
long[] ids = FeedItemUtil.getIds(episodes);
int position = ArrayUtils.indexOf(ids, item.getId());
activity.loadChildFragment(ItemPagerFragment.newInstance(ids, position));
+ } else {
+ toggleSelection(pos);
}
});
holder.itemView.setOnCreateContextMenuListener(this);
+ holder.itemView.setOnLongClickListener(v -> {
+ longPressedItem = item;
+ longPressedPosition = pos;
+ return false;
+ });
+
+ if (inActionMode()) {
+ holder.secondaryActionButton.setVisibility(View.GONE);
+ holder.selectCheckBox.setOnClickListener(v -> {
+ toggleSelection(pos);
+ });
+ holder.selectCheckBox.setChecked(isSelected(pos));
+ holder.selectCheckBox.setVisibility(View.VISIBLE);
+ } else {
+ holder.selectCheckBox.setVisibility(View.GONE);
+ }
+
afterBindViewHolder(holder, pos);
holder.hideSeparatorIfNecessary();
}
@@ -106,6 +127,7 @@ public class EpisodeItemListAdapter extends RecyclerView.Adapter<EpisodeItemView
* Instead, we tell the adapter to use partial binding by calling {@link #notifyItemChanged(int, Object)}.
* We actually ignore the payload and always do a full bind but calling the partial bind method ensures
* that ViewHolders are always re-used.
+ *
* @param position Position of the item that has changed
*/
public void notifyItemChangedCompat(int position) {
@@ -113,8 +135,8 @@ public class EpisodeItemListAdapter extends RecyclerView.Adapter<EpisodeItemView
}
@Nullable
- public FeedItem getSelectedItem() {
- return selectedItem;
+ public FeedItem getLongPressedItem() {
+ return longPressedItem;
}
@Override
@@ -139,8 +161,37 @@ public class EpisodeItemListAdapter extends RecyclerView.Adapter<EpisodeItemView
@Override
public void onCreateContextMenu(final ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
MenuInflater inflater = mainActivityRef.get().getMenuInflater();
- inflater.inflate(R.menu.feeditemlist_context, menu);
- menu.setHeaderTitle(selectedItem.getTitle());
- FeedItemMenuHandler.onPrepareMenu(menu, selectedItem, R.id.skip_episode_item);
+ if (inActionMode()) {
+ inflater.inflate(R.menu.multi_select_context_popup, menu);
+ } else {
+ inflater.inflate(R.menu.feeditemlist_context, menu);
+ menu.setHeaderTitle(longPressedItem.getTitle());
+ FeedItemMenuHandler.onPrepareMenu(menu, longPressedItem, R.id.skip_episode_item);
+ }
+ }
+
+ public boolean onContextItemSelected(MenuItem item) {
+ if (item.getItemId() == R.id.multi_select) {
+ startSelectMode(longPressedPosition);
+ return true;
+ } else if (item.getItemId() == R.id.select_all_above) {
+ setSelected(0, longPressedPosition, true);
+ return true;
+ } else if (item.getItemId() == R.id.select_all_below) {
+ setSelected(longPressedPosition + 1, getItemCount(), true);
+ return true;
+ }
+ return false;
}
+
+ public List<FeedItem> getSelectedItems() {
+ List<FeedItem> items = new ArrayList<>();
+ for (int i = 0; i < getItemCount(); i++) {
+ if (isSelected(i)) {
+ items.add(getItem(i));
+ }
+ }
+ return items;
+ }
+
}
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 01712ea29..117ba3258 100644
--- a/app/src/main/java/de/danoeh/antennapod/adapter/QueueRecyclerAdapter.java
+++ b/app/src/main/java/de/danoeh/antennapod/adapter/QueueRecyclerAdapter.java
@@ -64,10 +64,10 @@ public class QueueRecyclerAdapter extends EpisodeItemListAdapter {
super.onCreateContextMenu(menu, v, menuInfo);
final boolean keepSorted = UserPreferences.isQueueKeepSorted();
- if (getItem(0).getId() == getSelectedItem().getId() || keepSorted) {
+ if (getItem(0).getId() == getLongPressedItem().getId() || keepSorted) {
menu.findItem(R.id.move_to_top_item).setVisible(false);
}
- if (getItem(getItemCount() - 1).getId() == getSelectedItem().getId() || keepSorted) {
+ 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/adapter/SelectableAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/SelectableAdapter.java
new file mode 100644
index 000000000..670357d16
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/adapter/SelectableAdapter.java
@@ -0,0 +1,168 @@
+package de.danoeh.antennapod.adapter;
+
+import android.app.Activity;
+import android.view.ActionMode;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+import de.danoeh.antennapod.R;
+
+import java.util.HashSet;
+
+/**
+ * Used by Recyclerviews that need to provide ability to select items.
+ */
+abstract class SelectableAdapter<T extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<T> {
+ private ActionMode actionMode;
+ private final HashSet<Long> selectedIds = new HashSet<>();
+ private final Activity activity;
+ private OnEndSelectModeListener onEndSelectModeListener;
+
+ public SelectableAdapter(Activity activity) {
+ this.activity = activity;
+ }
+
+ public void startSelectMode(int pos) {
+ if (inActionMode()) {
+ endSelectMode();
+ }
+
+ selectedIds.clear();
+ selectedIds.add(getItemId(pos));
+ notifyDataSetChanged();
+
+ actionMode = activity.startActionMode(new ActionMode.Callback() {
+ @Override
+ public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ MenuInflater inflater = mode.getMenuInflater();
+ inflater.inflate(R.menu.multi_select_options, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+ updateTitle();
+ toggleSelectAllIcon(menu.findItem(R.id.select_toggle), false);
+ return false;
+ }
+
+ @Override
+ public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+ if (item.getItemId() == R.id.select_toggle) {
+ boolean allSelected = selectedIds.size() == getItemCount();
+ setSelected(0, getItemCount(), !allSelected);
+ toggleSelectAllIcon(item, !allSelected);
+ updateTitle();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void onDestroyActionMode(ActionMode mode) {
+ callOnEndSelectMode();
+ actionMode = null;
+ selectedIds.clear();
+ notifyDataSetChanged();
+ }
+ });
+ updateTitle();
+ }
+
+ /**
+ * End action mode if currently in select mode, otherwise do nothing
+ */
+ public void endSelectMode() {
+ if (inActionMode()) {
+ callOnEndSelectMode();
+ actionMode.finish();
+ }
+ }
+
+ public boolean isSelected(int pos) {
+ return selectedIds.contains(getItemId(pos));
+ }
+
+ /**
+ * Set the selected state of item at given position
+ *
+ * @param pos the position to select
+ * @param selected true for selected state and false for unselected
+ */
+ public void setSelected(int pos, boolean selected) {
+ if (selected) {
+ selectedIds.add(getItemId(pos));
+ } else {
+ selectedIds.remove(getItemId(pos));
+ }
+ updateTitle();
+ }
+
+ /**
+ * Set the selected state of item for a given range
+ *
+ * @param startPos start position of range, inclusive
+ * @param endPos end position of range, inclusive
+ * @param selected indicates the selection state
+ * @throws IllegalArgumentException if start and end positions are not valid
+ */
+ public void setSelected(int startPos, int endPos, boolean selected) throws IllegalArgumentException {
+ for (int i = startPos; i < endPos && i < getItemCount(); i++) {
+ setSelected(i, selected);
+ }
+ notifyItemRangeChanged(startPos, (endPos - startPos));
+ }
+
+ protected void toggleSelection(int pos) {
+ setSelected(pos, !isSelected(pos));
+ notifyItemChanged(pos);
+
+ if (selectedIds.size() == 0) {
+ endSelectMode();
+ }
+ }
+
+ public boolean inActionMode() {
+ return actionMode != null;
+ }
+
+ public int getSelectedCount() {
+ return selectedIds.size();
+ }
+
+ private void toggleSelectAllIcon(MenuItem selectAllItem, boolean allSelected) {
+ if (allSelected) {
+ 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 void updateTitle() {
+ if (actionMode == null) {
+ return;
+ }
+ actionMode.setTitle(activity.getResources()
+ .getQuantityString(R.plurals.num_selected_label, selectedIds.size(),
+ selectedIds.size(), getItemCount()));
+ }
+
+ public void setOnEndSelectModeListener(OnEndSelectModeListener onEndSelectModeListener) {
+ this.onEndSelectModeListener = onEndSelectModeListener;
+ }
+
+ private void callOnEndSelectMode() {
+ if (onEndSelectModeListener != null) {
+ onEndSelectModeListener.onEndSelectMode();
+ }
+ }
+
+ public interface OnEndSelectModeListener {
+ void onEndSelectMode();
+ }
+}
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 66f85d84d..9e3402972 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java
@@ -135,7 +135,7 @@ public class CompletedDownloadsFragment extends Fragment {
@Override
public boolean onContextItemSelected(@NonNull MenuItem item) {
- FeedItem selectedItem = adapter.getSelectedItem();
+ FeedItem selectedItem = adapter.getLongPressedItem();
if (selectedItem == null) {
Log.i(TAG, "Selected item at current position was null, ignoring selection");
return super.onContextItemSelected(item);
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java
index e88ef432c..9e5600d76 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java
@@ -177,11 +177,11 @@ public abstract class EpisodesListFragment extends Fragment {
return true; // avoids that the position is reset when we need it in the submenu
}
- if (listAdapter.getSelectedItem() == null) {
+ if (listAdapter.getLongPressedItem() == null) {
Log.i(TAG, "Selected item or listAdapter was null, ignoring selection");
return super.onContextItemSelected(item);
}
- FeedItem selectedItem = listAdapter.getSelectedItem();
+ FeedItem selectedItem = listAdapter.getLongPressedItem();
return FeedItemMenuHandler.onMenuItemClicked(this, item.getItemId(), selectedItem);
}
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java
index de27ff0af..11f9950f9 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java
@@ -1,13 +1,14 @@
package de.danoeh.antennapod.fragment;
import android.content.Context;
-import android.content.res.Configuration;
import android.content.Intent;
+import android.content.res.Configuration;
import android.graphics.LightingColorFilter;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
+import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
@@ -18,6 +19,7 @@ import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
+
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatDrawableManager;
@@ -30,8 +32,19 @@ import com.bumptech.glide.Glide;
import com.bumptech.glide.request.RequestOptions;
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.appbar.CollapsingToolbarLayout;
+import com.google.android.material.snackbar.Snackbar;
import com.joanzapata.iconify.Iconify;
import com.joanzapata.iconify.widget.IconTextView;
+import com.leinardi.android.speeddial.SpeedDialView;
+
+import org.apache.commons.lang3.Validate;
+import org.greenrobot.eventbus.EventBus;
+import org.greenrobot.eventbus.Subscribe;
+import org.greenrobot.eventbus.ThreadMode;
+
+import java.util.List;
+import java.util.Set;
+
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.adapter.EpisodeItemListAdapter;
@@ -58,10 +71,10 @@ import de.danoeh.antennapod.core.storage.DownloadRequester;
import de.danoeh.antennapod.core.util.FeedItemPermutors;
import de.danoeh.antennapod.core.util.FeedItemUtil;
import de.danoeh.antennapod.core.util.gui.MoreContentListFooterUtil;
-import de.danoeh.antennapod.dialog.EpisodesApplyActionFragment;
import de.danoeh.antennapod.dialog.FilterDialog;
import de.danoeh.antennapod.dialog.RemoveFeedDialog;
import de.danoeh.antennapod.dialog.RenameFeedDialog;
+import de.danoeh.antennapod.fragment.actions.EpisodeMultiSelectActionHandler;
import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler;
import de.danoeh.antennapod.menuhandler.FeedMenuHandler;
import de.danoeh.antennapod.menuhandler.MenuItemUtils;
@@ -72,19 +85,12 @@ import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
-import org.apache.commons.lang3.Validate;
-import org.greenrobot.eventbus.EventBus;
-import org.greenrobot.eventbus.Subscribe;
-import org.greenrobot.eventbus.ThreadMode;
-
-import java.util.List;
-import java.util.Set;
/**
* Displays a list of FeedItems.
*/
public class FeedItemlistFragment extends Fragment implements AdapterView.OnItemClickListener,
- Toolbar.OnMenuItemClickListener {
+ Toolbar.OnMenuItemClickListener, EpisodeItemListAdapter.OnEndSelectModeListener {
private static final String TAG = "ItemlistFragment";
private static final String ARGUMENT_FEED_ID = "argument.de.danoeh.antennapod.feed_id";
private static final String KEY_UP_ARROW = "up_arrow";
@@ -106,6 +112,8 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
private View header;
private Toolbar toolbar;
private ToolbarIconTintManager iconTintManager;
+ private SpeedDialView speedDialView;
+
private boolean displayUpArrow;
private long feedID;
@@ -222,6 +230,32 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
});
loadItems();
+
+ // Init action UI (via a FAB Speed Dial)
+ speedDialView = root.findViewById(R.id.fabSD);
+ speedDialView.inflate(R.menu.episodes_apply_action_speeddial);
+ 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;
+ });
return root;
}
@@ -292,17 +326,6 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
return true;
}
switch (item.getItemId()) {
- case R.id.episode_actions:
- int actions = EpisodesApplyActionFragment.ACTION_ALL;
- if (feed.isLocalFeed()) {
- // turn off download and delete actions for local feed
- actions ^= EpisodesApplyActionFragment.ACTION_DOWNLOAD;
- actions ^= EpisodesApplyActionFragment.ACTION_DELETE;
- }
- EpisodesApplyActionFragment fragment = EpisodesApplyActionFragment
- .newInstance(feed.getItems(), actions);
- ((MainActivity) getActivity()).loadChildFragment(fragment);
- return true;
case R.id.rename_item:
new RenameFeedDialog(getActivity(), feed).show();
return true;
@@ -317,11 +340,24 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
@Override
public boolean onContextItemSelected(@NonNull MenuItem item) {
- FeedItem selectedItem = adapter.getSelectedItem();
+ FeedItem selectedItem = adapter.getLongPressedItem();
if (selectedItem == null) {
Log.i(TAG, "Selected item at current position was null, ignoring selection");
return super.onContextItemSelected(item);
}
+
+ if (item.getItemId() == R.id.multi_select) {
+ if (feed.isLocalFeed()) {
+ speedDialView.removeActionItemById(R.id.download_batch);
+ speedDialView.removeActionItemById(R.id.delete_batch);
+ }
+ speedDialView.setVisibility(View.VISIBLE);
+ refreshToolbarState();
+ // Do not return: Let adapter handle its actions, too.
+ }
+ if (adapter.onContextItemSelected(item)) {
+ return true;
+ }
return FeedItemMenuHandler.onMenuItemClicked(this, item.getItemId(), selectedItem);
}
@@ -393,6 +429,12 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
}
}
+ @Override
+ public void onEndSelectMode() {
+ speedDialView.close();
+ speedDialView.setVisibility(View.GONE);
+ }
+
private void updateUi() {
loadItems();
updateSyncProgressBarVisibility();
@@ -433,6 +475,7 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
if (adapter == null) {
recyclerView.setAdapter(null);
adapter = new FeedItemListAdapter((MainActivity) getActivity());
+ adapter.setOnEndSelectModeListener(this);
recyclerView.setAdapter(adapter);
}
progressBar.setVisibility(View.GONE);
@@ -592,5 +635,13 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
protected void beforeBindViewHolder(EpisodeItemViewHolder holder, int pos) {
holder.coverHolder.setVisibility(View.GONE);
}
+
+ @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);
+ }
+ }
}
-} \ No newline at end of file
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java
index 84b0e97c1..5e3d36c03 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java
@@ -172,7 +172,7 @@ public class PlaybackHistoryFragment extends Fragment implements Toolbar.OnMenuI
@Override
public boolean onContextItemSelected(@NonNull MenuItem item) {
- FeedItem selectedItem = adapter.getSelectedItem();
+ FeedItem selectedItem = adapter.getLongPressedItem();
if (selectedItem == null) {
Log.i(TAG, "Selected item at current position was null, ignoring selection");
return super.onContextItemSelected(item);
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 80f666fa2..d309ac253 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java
@@ -391,7 +391,7 @@ public class QueueFragment extends Fragment implements Toolbar.OnMenuItemClickLi
if (!isVisible() || recyclerAdapter == null) {
return false;
}
- FeedItem selectedItem = recyclerAdapter.getSelectedItem();
+ FeedItem selectedItem = recyclerAdapter.getLongPressedItem();
if (selectedItem == null) {
Log.i(TAG, "Selected item was null, ignoring selection");
return super.onContextItemSelected(item);
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java
index 0394b5987..855078d03 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java
@@ -191,7 +191,7 @@ public class SearchFragment extends Fragment {
@Override
public boolean onContextItemSelected(@NonNull MenuItem item) {
- FeedItem selectedItem = adapter.getSelectedItem();
+ FeedItem selectedItem = adapter.getLongPressedItem();
if (selectedItem == null) {
Log.i(TAG, "Selected item at current position was null, ignoring selection");
return super.onContextItemSelected(item);
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/actions/EpisodeMultiSelectActionHandler.java b/app/src/main/java/de/danoeh/antennapod/fragment/actions/EpisodeMultiSelectActionHandler.java
new file mode 100644
index 000000000..028d2fff4
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/actions/EpisodeMultiSelectActionHandler.java
@@ -0,0 +1,130 @@
+package de.danoeh.antennapod.fragment.actions;
+
+import android.util.Log;
+
+import androidx.annotation.PluralsRes;
+
+import com.google.android.material.snackbar.Snackbar;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.activity.MainActivity;
+import de.danoeh.antennapod.core.dialog.DownloadRequestErrorDialogCreator;
+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.LongList;
+import de.danoeh.antennapod.model.feed.FeedItem;
+
+public class EpisodeMultiSelectActionHandler {
+ private static final String TAG = "EpisodeSelectHandler";
+ private final MainActivity activity;
+ private final List<FeedItem> selectedItems;
+
+ public EpisodeMultiSelectActionHandler(MainActivity activity, List<FeedItem> selectedItems) {
+ this.activity = activity;
+ this.selectedItems = selectedItems;
+ }
+
+ public void handleAction(int id) {
+ if (id == R.id.add_to_queue_batch) {
+ queueChecked();
+ } else if (id == R.id.remove_from_queue_batch) {
+ removeFromQueueChecked();
+ } else if (id == R.id.mark_read_batch) {
+ markedCheckedPlayed();
+ } else if (id == R.id.mark_unread_batch) {
+ markedCheckedUnplayed();
+ } else if (id == R.id.download_batch) {
+ downloadChecked();
+ } else if (id == R.id.delete_batch) {
+ deleteChecked();
+ } else {
+ Log.e(TAG, "Unrecognized speed dial action item. Do nothing. id=" + id);
+ }
+ }
+
+ private void queueChecked() {
+ // Check if an episode actually contains any media files before adding it to queue
+ LongList toQueue = new LongList(selectedItems.size());
+ for (FeedItem episode : selectedItems) {
+ if (episode.hasMedia()) {
+ toQueue.add(episode.getId());
+ }
+ }
+ DBWriter.addQueueItem(activity, true, toQueue.toArray());
+ showMessage(R.plurals.added_to_queue_batch_label, toQueue.size());
+ }
+
+ private void removeFromQueueChecked() {
+ long[] checkedIds = getSelectedIds();
+ DBWriter.removeQueueItem(activity, true, checkedIds);
+ showMessage(R.plurals.removed_from_queue_batch_label, checkedIds.length);
+ }
+
+ private void markedCheckedPlayed() {
+ long[] checkedIds = getSelectedIds();
+ DBWriter.markItemPlayed(FeedItem.PLAYED, checkedIds);
+ showMessage(R.plurals.marked_read_batch_label, checkedIds.length);
+ }
+
+ private void markedCheckedUnplayed() {
+ long[] checkedIds = getSelectedIds();
+ DBWriter.markItemPlayed(FeedItem.UNPLAYED, checkedIds);
+ showMessage(R.plurals.marked_unread_batch_label, checkedIds.length);
+ }
+
+ private void downloadChecked() {
+ // download the check episodes in the same order as they are currently displayed
+ List<FeedItem> toDownload = new ArrayList<>(selectedItems.size());
+ for (FeedItem episode : selectedItems) {
+ if (episode.hasMedia() && !episode.getFeed().isLocalFeed()) {
+ toDownload.add(episode);
+ }
+ }
+ try {
+ DownloadRequester.getInstance().downloadMedia(activity, true, toDownload.toArray(new FeedItem[0]));
+ } catch (DownloadRequestException e) {
+ e.printStackTrace();
+ DownloadRequestErrorDialogCreator.newRequestErrorDialog(activity, e.getMessage());
+ }
+ showMessage(R.plurals.downloading_batch_label, toDownload.size());
+ }
+
+ private void deleteChecked() {
+ int countHasMedia = 0;
+ int countNoMedia = 0;
+ for (FeedItem feedItem : selectedItems) {
+ if (feedItem.hasMedia() && feedItem.getMedia().isDownloaded()) {
+ countHasMedia++;
+ DBWriter.deleteFeedMediaOfItem(activity, feedItem.getMedia().getId());
+ } else {
+ countNoMedia++;
+ }
+ }
+ showMessageMore(R.plurals.deleted_multi_episode_batch_label, countNoMedia, countHasMedia);
+ }
+
+ private void showMessage(@PluralsRes int msgId, int numItems) {
+ activity.showSnackbarAbovePlayer(activity.getResources()
+ .getQuantityString(msgId, numItems, numItems), Snackbar.LENGTH_LONG);
+ }
+
+ private void showMessageMore(@PluralsRes int msgId, int countNoMedia, int countHasMedia) {
+ activity.showSnackbarAbovePlayer(activity.getResources()
+ .getQuantityString(msgId,
+ (countHasMedia + countNoMedia),
+ (countHasMedia + countNoMedia), countHasMedia),
+ Snackbar.LENGTH_LONG);
+ }
+
+ private long[] getSelectedIds() {
+ long[] checkedIds = new long[selectedItems.size()];
+ for (int i = 0; i < selectedItems.size(); ++i) {
+ checkedIds[i] = selectedItems.get(i).getId();
+ }
+ return checkedIds;
+ }
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/view/viewholder/EpisodeItemViewHolder.java b/app/src/main/java/de/danoeh/antennapod/view/viewholder/EpisodeItemViewHolder.java
index 428ce9e1e..02d45b2a0 100644
--- a/app/src/main/java/de/danoeh/antennapod/view/viewholder/EpisodeItemViewHolder.java
+++ b/app/src/main/java/de/danoeh/antennapod/view/viewholder/EpisodeItemViewHolder.java
@@ -7,13 +7,16 @@ import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.cardview.widget.CardView;
import androidx.recyclerview.widget.RecyclerView;
+
import com.joanzapata.iconify.Iconify;
+
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.adapter.CoverLoader;
@@ -31,8 +34,8 @@ import de.danoeh.antennapod.core.util.Converter;
import de.danoeh.antennapod.core.util.DateUtils;
import de.danoeh.antennapod.core.util.FeedItemUtil;
import de.danoeh.antennapod.core.util.NetworkUtils;
-import de.danoeh.antennapod.ui.common.ThemeUtils;
import de.danoeh.antennapod.ui.common.CircularProgressBar;
+import de.danoeh.antennapod.ui.common.ThemeUtils;
/**
* Holds the view which shows FeedItems.
@@ -60,6 +63,7 @@ public class EpisodeItemViewHolder extends RecyclerView.ViewHolder {
private final TextView separatorIcons;
private final View leftPadding;
public final CardView coverHolder;
+ public final CheckBox selectCheckBox;
private final MainActivity activity;
private FeedItem item;
@@ -91,6 +95,7 @@ public class EpisodeItemViewHolder extends RecyclerView.ViewHolder {
coverHolder = itemView.findViewById(R.id.coverHolder);
leftPadding = itemView.findViewById(R.id.left_padding);
itemView.setTag(this);
+ selectCheckBox = itemView.findViewById(R.id.selectCheckBox);
}
public void bind(FeedItem item) {