summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/src/main/java/de/danoeh/antennapod/adapter/EpisodeItemListAdapter.java2
-rw-r--r--app/src/main/java/de/danoeh/antennapod/adapter/SelectableAdapter.java38
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java8
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java4
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java140
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/FavoriteEpisodesFragment.java9
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java4
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/InboxFragment.java10
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java4
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/actions/EpisodeMultiSelectActionHandler.java82
-rw-r--r--app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java15
-rw-r--r--app/src/main/res/layout/all_episodes_fragment.xml33
-rw-r--r--app/src/main/res/menu/episodes.xml8
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java13
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java30
-rw-r--r--core/src/test/java/de/danoeh/antennapod/core/storage/DbWriterTest.java56
-rw-r--r--storage/database/src/main/java/de/danoeh/antennapod/storage/database/PodDBAdapter.java8
-rw-r--r--storage/database/src/main/java/de/danoeh/antennapod/storage/database/mapper/FeedItemFilterQuery.java2
-rw-r--r--ui/i18n/src/main/res/values/strings.xml6
19 files changed, 264 insertions, 208 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 53223896f..088caf70a 100644
--- a/app/src/main/java/de/danoeh/antennapod/adapter/EpisodeItemListAdapter.java
+++ b/app/src/main/java/de/danoeh/antennapod/adapter/EpisodeItemListAdapter.java
@@ -48,6 +48,7 @@ public class EpisodeItemListAdapter extends SelectableAdapter<EpisodeItemViewHol
public void updateItems(List<FeedItem> items) {
episodes = items;
notifyDataSetChanged();
+ updateTitle();
}
@Override
@@ -194,6 +195,7 @@ public class EpisodeItemListAdapter extends SelectableAdapter<EpisodeItemViewHol
setSelected(0, longPressedPosition, true);
return true;
} else if (item.getItemId() == R.id.select_all_below) {
+ shouldSelectLazyLoadedItems = true;
setSelected(longPressedPosition + 1, getItemCount(), true);
return true;
}
diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/SelectableAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/SelectableAdapter.java
index 43f749ff3..70d00cbff 100644
--- a/app/src/main/java/de/danoeh/antennapod/adapter/SelectableAdapter.java
+++ b/app/src/main/java/de/danoeh/antennapod/adapter/SelectableAdapter.java
@@ -15,11 +15,14 @@ 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> {
+public abstract class SelectableAdapter<T extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<T> {
+ public static final int COUNT_AUTOMATICALLY = -1;
private ActionMode actionMode;
private final HashSet<Long> selectedIds = new HashSet<>();
private final Activity activity;
private OnSelectModeListener onSelectModeListener;
+ boolean shouldSelectLazyLoadedItems = false;
+ private int totalNumberOfItems = COUNT_AUTOMATICALLY;
public SelectableAdapter(Activity activity) {
this.activity = activity;
@@ -34,6 +37,7 @@ abstract class SelectableAdapter<T extends RecyclerView.ViewHolder> extends Recy
onSelectModeListener.onStartSelectMode();
}
+ shouldSelectLazyLoadedItems = false;
selectedIds.clear();
selectedIds.add(getItemId(pos));
notifyDataSetChanged();
@@ -56,9 +60,10 @@ abstract class SelectableAdapter<T extends RecyclerView.ViewHolder> extends Recy
@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);
+ boolean selectAll = selectedIds.size() != getItemCount();
+ shouldSelectLazyLoadedItems = selectAll;
+ setSelected(0, getItemCount(), selectAll);
+ toggleSelectAllIcon(item, selectAll);
updateTitle();
return true;
}
@@ -69,6 +74,7 @@ abstract class SelectableAdapter<T extends RecyclerView.ViewHolder> extends Recy
public void onDestroyActionMode(ActionMode mode) {
callOnEndSelectMode();
actionMode = null;
+ shouldSelectLazyLoadedItems = false;
selectedIds.clear();
notifyDataSetChanged();
}
@@ -147,13 +153,21 @@ abstract class SelectableAdapter<T extends RecyclerView.ViewHolder> extends Recy
}
}
- private void updateTitle() {
+ void updateTitle() {
if (actionMode == null) {
return;
}
+ int totalCount = getItemCount();
+ int selectedCount = selectedIds.size();
+ if (totalNumberOfItems != COUNT_AUTOMATICALLY) {
+ totalCount = totalNumberOfItems;
+ if (shouldSelectLazyLoadedItems) {
+ selectedCount += (totalNumberOfItems - getItemCount());
+ }
+ }
actionMode.setTitle(activity.getResources()
.getQuantityString(R.plurals.num_selected_label, selectedIds.size(),
- selectedIds.size(), getItemCount()));
+ selectedCount, totalCount));
}
public void setOnSelectModeListener(OnSelectModeListener onSelectModeListener) {
@@ -166,6 +180,18 @@ abstract class SelectableAdapter<T extends RecyclerView.ViewHolder> extends Recy
}
}
+ public boolean shouldSelectLazyLoadedItems() {
+ return shouldSelectLazyLoadedItems;
+ }
+
+ /**
+ * Sets the total number of items that could be lazy-loaded.
+ * Can also be set to {@link #COUNT_AUTOMATICALLY} to simply use {@link #getItemCount}
+ */
+ public void setTotalNumberOfItems(int totalNumberOfItems) {
+ this.totalNumberOfItems = totalNumberOfItems;
+ }
+
public interface OnSelectModeListener {
void onStartSelectMode();
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java
index 853e7d6f7..46a648e57 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java
@@ -66,7 +66,6 @@ public class AllEpisodesFragment extends EpisodesListFragment {
public void onPrepareOptionsMenu(@NonNull Menu menu) {
super.onPrepareOptionsMenu(menu);
menu.findItem(R.id.filter_items).setVisible(true);
- menu.findItem(R.id.mark_all_read_item).setVisible(true);
}
@Override
@@ -102,7 +101,12 @@ public class AllEpisodesFragment extends EpisodesListFragment {
@NonNull
@Override
- protected List<FeedItem> loadMoreData() {
+ protected List<FeedItem> loadMoreData(int page) {
return DBReader.getRecentlyPublishedEpisodes((page - 1) * EPISODES_PER_PAGE, EPISODES_PER_PAGE, feedItemFilter);
}
+
+ @Override
+ protected int loadTotalItemCount() {
+ return DBReader.getTotalEpisodeCount(feedItemFilter);
+ }
}
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 18c5d8514..d82b3ff0c 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java
@@ -120,8 +120,8 @@ public class CompletedDownloadsFragment extends Fragment
}
});
speedDialView.setOnActionSelectedListener(actionItem -> {
- new EpisodeMultiSelectActionHandler(((MainActivity) getActivity()), adapter.getSelectedItems())
- .handleAction(actionItem.getId());
+ new EpisodeMultiSelectActionHandler(((MainActivity) getActivity()), actionItem.getId())
+ .handleAction(adapter.getSelectedItems());
adapter.endSelectMode();
return true;
});
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 b658e5f08..03dbc6ae4 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java
@@ -5,6 +5,7 @@ import android.os.Bundle;
import android.view.ContextMenu;
import android.view.KeyEvent;
import androidx.annotation.NonNull;
+import androidx.core.util.Pair;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.SimpleItemAnimator;
@@ -22,15 +23,20 @@ import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
+import com.google.android.material.snackbar.Snackbar;
+import com.leinardi.android.speeddial.SpeedDialView;
import de.danoeh.antennapod.adapter.EpisodeItemListAdapter;
+import de.danoeh.antennapod.adapter.SelectableAdapter;
import de.danoeh.antennapod.event.FeedListUpdateEvent;
import de.danoeh.antennapod.event.playback.PlaybackPositionEvent;
import de.danoeh.antennapod.event.PlayerStatusEvent;
import de.danoeh.antennapod.event.UnreadItemsUpdateEvent;
import de.danoeh.antennapod.core.menuhandler.MenuItemUtils;
+import de.danoeh.antennapod.fragment.actions.EpisodeMultiSelectActionHandler;
import de.danoeh.antennapod.ui.common.PagedToolbarFragment;
import de.danoeh.antennapod.view.EpisodeItemListRecyclerView;
import de.danoeh.antennapod.view.viewholder.EpisodeItemViewHolder;
+import io.reactivex.Completable;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
@@ -59,7 +65,7 @@ import io.reactivex.schedulers.Schedulers;
/**
* Shows unread or recently published episodes
*/
-public abstract class EpisodesListFragment extends Fragment {
+public abstract class EpisodesListFragment extends Fragment implements EpisodeItemListAdapter.OnSelectModeListener {
public static final String TAG = "EpisodesListFragment";
protected static final int EPISODES_PER_PAGE = 150;
@@ -72,6 +78,7 @@ public abstract class EpisodesListFragment extends Fragment {
ProgressBar progLoading;
View loadingMoreView;
EmptyViewHandler emptyView;
+ SpeedDialView speedDialView;
@NonNull
List<FeedItem> episodes = new ArrayList<>();
@@ -130,21 +137,6 @@ public abstract class EpisodesListFragment extends Fragment {
if (itemId == R.id.refresh_item) {
AutoUpdateManager.runImmediate(requireContext());
return true;
- } else if (itemId == R.id.mark_all_read_item) {
- ConfirmationDialog markAllReadConfirmationDialog = new ConfirmationDialog(getActivity(),
- R.string.mark_all_read_label,
- R.string.mark_all_read_confirmation_msg) {
-
- @Override
- public void onConfirmButtonPressed(DialogInterface dialog) {
- dialog.dismiss();
- DBWriter.markAllItemsRead();
- ((MainActivity) getActivity()).showSnackbarAbovePlayer(
- R.string.mark_all_read_msg, Toast.LENGTH_SHORT);
- }
- };
- markAllReadConfirmationDialog.createNewDialog().show();
- return true;
} else if (itemId == R.id.remove_all_inbox_item) {
ConfirmationDialog removeAllNewFlagsConfirmationDialog = new ConfirmationDialog(getActivity(),
R.string.remove_all_inbox_label,
@@ -174,17 +166,13 @@ public abstract class EpisodesListFragment extends Fragment {
// The method is called on all fragments in a ViewPager, so this needs to be ignored in invisible ones.
// Apparently, none of the visibility check method works reliably on its own, so we just use all.
return false;
- }
- if (item.getItemId() == R.id.share_item) {
- return true; // avoids that the position is reset when we need it in the submenu
- }
-
- if (listAdapter.getLongPressedItem() == null) {
+ } else if (listAdapter.getLongPressedItem() == null) {
Log.i(TAG, "Selected item or listAdapter was null, ignoring selection");
return super.onContextItemSelected(item);
+ } else if (listAdapter.onContextItemSelected(item)) {
+ return true;
}
FeedItem selectedItem = listAdapter.getLongPressedItem();
-
return FeedItemMenuHandler.onMenuItemClicked(this, item.getItemId(), selectedItem);
}
@@ -225,9 +213,72 @@ public abstract class EpisodesListFragment extends Fragment {
createRecycleAdapter(recyclerView, emptyView);
emptyView.hide();
+ speedDialView = root.findViewById(R.id.fabSD);
+ speedDialView.setOverlayLayout(root.findViewById(R.id.fabSDOverlay));
+ 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 && listAdapter.getSelectedCount() == 0) {
+ ((MainActivity) getActivity()).showSnackbarAbovePlayer(R.string.no_items_selected,
+ Snackbar.LENGTH_SHORT);
+ speedDialView.close();
+ }
+ }
+ });
+ speedDialView.setOnActionSelectedListener(actionItem -> {
+ int confirmationString = 0;
+ if (listAdapter.getSelectedItems().size() >= 25 || listAdapter.shouldSelectLazyLoadedItems()) {
+ // Should ask for confirmation
+ if (actionItem.getId() == R.id.mark_read_batch) {
+ confirmationString = R.string.multi_select_mark_played_confirmation;
+ } else if (actionItem.getId() == R.id.mark_unread_batch) {
+ confirmationString = R.string.multi_select_mark_unplayed_confirmation;
+ }
+ }
+ if (confirmationString == 0) {
+ performMultiSelectAction(actionItem.getId());
+ } else {
+ new ConfirmationDialog(getActivity(), R.string.multi_select, confirmationString) {
+ @Override
+ public void onConfirmButtonPressed(DialogInterface dialog) {
+ performMultiSelectAction(actionItem.getId());
+ }
+ }.createNewDialog().show();
+ }
+ return true;
+ });
+
return root;
}
+ private void performMultiSelectAction(int actionItemId) {
+ EpisodeMultiSelectActionHandler handler =
+ new EpisodeMultiSelectActionHandler(((MainActivity) getActivity()), actionItemId);
+ Completable.fromAction(
+ () -> {
+ handler.handleAction(listAdapter.getSelectedItems());
+ if (listAdapter.shouldSelectLazyLoadedItems()) {
+ int applyPage = page + 1;
+ List<FeedItem> nextPage;
+ do {
+ nextPage = loadMoreData(applyPage);
+ handler.handleAction(nextPage);
+ applyPage++;
+ } while (nextPage.size() == EPISODES_PER_PAGE);
+ }
+ })
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(() -> listAdapter.endSelectMode(),
+ error -> Log.e(TAG, Log.getStackTraceString(error)));
+ }
+
private void setupLoadMoreScrollListener() {
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
@@ -249,7 +300,7 @@ public abstract class EpisodesListFragment extends Fragment {
}
isLoadingMore = true;
loadingMoreView.setVisibility(View.VISIBLE);
- disposable = Observable.fromCallable(this::loadMoreData)
+ disposable = Observable.fromCallable(() -> loadMoreData(page))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(data -> {
@@ -258,6 +309,9 @@ public abstract class EpisodesListFragment extends Fragment {
}
episodes.addAll(data);
onFragmentLoaded(episodes);
+ if (listAdapter.shouldSelectLazyLoadedItems()) {
+ listAdapter.setSelected(episodes.size() - data.size(), episodes.size(), true);
+ }
}, error -> Log.e(TAG, Log.getStackTraceString(error)),
() -> {
recyclerView.post(() -> isLoadingMore = false); // Make sure to not always load 2 pages at once
@@ -292,14 +346,38 @@ public abstract class EpisodesListFragment extends Fragment {
@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);
+ }
MenuItemUtils.setOnClickListeners(menu, EpisodesListFragment.this::onContextItemSelected);
}
};
+ listAdapter.setOnSelectModeListener(this);
listAdapter.updateItems(episodes);
recyclerView.setAdapter(listAdapter);
emptyViewHandler.updateAdapter(listAdapter);
}
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ if (listAdapter != null) {
+ listAdapter.endSelectMode();
+ }
+ listAdapter = null;
+ }
+
+ @Override
+ public void onStartSelectMode() {
+ speedDialView.setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ public void onEndSelectMode() {
+ speedDialView.close();
+ speedDialView.setVisibility(View.GONE);
+ }
+
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEventMainThread(FeedItemEvent event) {
Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]");
@@ -395,14 +473,15 @@ public abstract class EpisodesListFragment extends Fragment {
if (disposable != null) {
disposable.dispose();
}
- disposable = Observable.fromCallable(this::loadData)
+ disposable = Observable.fromCallable(() -> new Pair<>(loadData(), loadTotalItemCount()))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(data -> {
progLoading.setVisibility(View.GONE);
loadingMoreView.setVisibility(View.GONE);
hasMoreItems = true;
- episodes = data;
+ episodes = data.first;
+ listAdapter.setTotalNumberOfItems(data.second);
onFragmentLoaded(episodes);
if (getParentFragment() instanceof PagedToolbarFragment) {
((PagedToolbarFragment) getParentFragment()).invalidateOptionsMenuIfActive(this);
@@ -422,5 +501,12 @@ public abstract class EpisodesListFragment extends Fragment {
* @return The items from the next page of data
*/
@NonNull
- protected abstract List<FeedItem> loadMoreData();
+ protected abstract List<FeedItem> loadMoreData(int page);
+
+ /**
+ * Returns the total number of items that would be returned if {@link #loadMoreData} was called often enough.
+ */
+ protected int loadTotalItemCount() {
+ return SelectableAdapter.COUNT_AUTOMATICALLY;
+ }
}
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/FavoriteEpisodesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/FavoriteEpisodesFragment.java
index 33aba3b54..30eec8780 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/FavoriteEpisodesFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/FavoriteEpisodesFragment.java
@@ -12,6 +12,7 @@ import android.view.View;
import android.view.ViewGroup;
import de.danoeh.antennapod.activity.MainActivity;
+import de.danoeh.antennapod.model.feed.FeedItemFilter;
import de.danoeh.antennapod.view.viewholder.EpisodeItemViewHolder;
import org.greenrobot.eventbus.Subscribe;
@@ -47,7 +48,6 @@ public class FavoriteEpisodesFragment extends EpisodesListFragment {
public void onPrepareOptionsMenu(@NonNull Menu menu) {
super.onPrepareOptionsMenu(menu);
menu.findItem(R.id.filter_items).setVisible(false);
- menu.findItem(R.id.mark_all_read_item).setVisible(false);
}
@NonNull
@@ -98,7 +98,12 @@ public class FavoriteEpisodesFragment extends EpisodesListFragment {
@NonNull
@Override
- protected List<FeedItem> loadMoreData() {
+ protected List<FeedItem> loadMoreData(int page) {
return DBReader.getFavoriteItemsList((page - 1) * EPISODES_PER_PAGE, EPISODES_PER_PAGE);
}
+
+ @Override
+ protected int loadTotalItemCount() {
+ return DBReader.getTotalEpisodeCount(new FeedItemFilter(FeedItemFilter.IS_FAVORITE));
+ }
}
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 a42abd724..80a65e518 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java
@@ -197,8 +197,8 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
}
});
speedDialBinding.fabSD.setOnActionSelectedListener(actionItem -> {
- new EpisodeMultiSelectActionHandler(((MainActivity) getActivity()), adapter.getSelectedItems())
- .handleAction(actionItem.getId());
+ new EpisodeMultiSelectActionHandler(((MainActivity) getActivity()), actionItem.getId())
+ .handleAction(adapter.getSelectedItems());
adapter.endSelectMode();
return true;
});
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/InboxFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/InboxFragment.java
index abb04b2f3..067e7466c 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/InboxFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/InboxFragment.java
@@ -67,6 +67,9 @@ public class InboxFragment extends EpisodesListFragment implements Toolbar.OnMen
SwipeActions swipeActions = new SwipeActions(this, TAG).attachTo(recyclerView);
swipeActions.setFilter(new FeedItemFilter(FeedItemFilter.NEW));
+ speedDialView.removeActionItemById(R.id.mark_unread_batch);
+ speedDialView.removeActionItemById(R.id.remove_from_queue_batch);
+ speedDialView.removeActionItemById(R.id.delete_batch);
return inboxContainer;
}
@@ -113,7 +116,12 @@ public class InboxFragment extends EpisodesListFragment implements Toolbar.OnMen
@NonNull
@Override
- protected List<FeedItem> loadMoreData() {
+ protected List<FeedItem> loadMoreData(int page) {
return DBReader.getNewItemsList((page - 1) * EPISODES_PER_PAGE, EPISODES_PER_PAGE);
}
+
+ @Override
+ protected int loadTotalItemCount() {
+ return DBReader.getTotalEpisodeCount(new FeedItemFilter(FeedItemFilter.NEW));
+ }
}
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 2a89519a5..61f45f0f9 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java
@@ -505,8 +505,8 @@ public class QueueFragment extends Fragment implements Toolbar.OnMenuItemClickLi
}
});
speedDialView.setOnActionSelectedListener(actionItem -> {
- new EpisodeMultiSelectActionHandler(((MainActivity) getActivity()), recyclerAdapter.getSelectedItems())
- .handleAction(actionItem.getId());
+ new EpisodeMultiSelectActionHandler(((MainActivity) getActivity()), actionItem.getId())
+ .handleAction(recyclerAdapter.getSelectedItems());
recyclerAdapter.endSelectMode();
return true;
});
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
index 17642874e..0dc416e0e 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/actions/EpisodeMultiSelectActionHandler.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/actions/EpisodeMultiSelectActionHandler.java
@@ -21,35 +21,37 @@ 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;
+ private final int actionId;
+ private int totalNumItems = 0;
+ private Snackbar snackbar = null;
- public EpisodeMultiSelectActionHandler(MainActivity activity, List<FeedItem> selectedItems) {
+ public EpisodeMultiSelectActionHandler(MainActivity activity, int actionId) {
this.activity = activity;
- this.selectedItems = selectedItems;
+ this.actionId = actionId;
}
- 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();
+ public void handleAction(List<FeedItem> items) {
+ if (actionId == R.id.add_to_queue_batch) {
+ queueChecked(items);
+ } else if (actionId == R.id.remove_from_queue_batch) {
+ removeFromQueueChecked(items);
+ } else if (actionId == R.id.mark_read_batch) {
+ markedCheckedPlayed(items);
+ } else if (actionId == R.id.mark_unread_batch) {
+ markedCheckedUnplayed(items);
+ } else if (actionId == R.id.download_batch) {
+ downloadChecked(items);
+ } else if (actionId == R.id.delete_batch) {
+ deleteChecked(items);
} else {
- Log.e(TAG, "Unrecognized speed dial action item. Do nothing. id=" + id);
+ Log.e(TAG, "Unrecognized speed dial action item. Do nothing. id=" + actionId);
}
}
- private void queueChecked() {
+ private void queueChecked(List<FeedItem> items) {
// Check if an episode actually contains any media files before adding it to queue
- LongList toQueue = new LongList(selectedItems.size());
- for (FeedItem episode : selectedItems) {
+ LongList toQueue = new LongList(items.size());
+ for (FeedItem episode : items) {
if (episode.hasMedia()) {
toQueue.add(episode.getId());
}
@@ -58,28 +60,28 @@ public class EpisodeMultiSelectActionHandler {
showMessage(R.plurals.added_to_queue_batch_label, toQueue.size());
}
- private void removeFromQueueChecked() {
- long[] checkedIds = getSelectedIds();
+ private void removeFromQueueChecked(List<FeedItem> items) {
+ long[] checkedIds = getSelectedIds(items);
DBWriter.removeQueueItem(activity, true, checkedIds);
showMessage(R.plurals.removed_from_queue_batch_label, checkedIds.length);
}
- private void markedCheckedPlayed() {
- long[] checkedIds = getSelectedIds();
+ private void markedCheckedPlayed(List<FeedItem> items) {
+ long[] checkedIds = getSelectedIds(items);
DBWriter.markItemPlayed(FeedItem.PLAYED, checkedIds);
showMessage(R.plurals.marked_read_batch_label, checkedIds.length);
}
- private void markedCheckedUnplayed() {
- long[] checkedIds = getSelectedIds();
+ private void markedCheckedUnplayed(List<FeedItem> items) {
+ long[] checkedIds = getSelectedIds(items);
DBWriter.markItemPlayed(FeedItem.UNPLAYED, checkedIds);
showMessage(R.plurals.marked_unread_batch_label, checkedIds.length);
}
- private void downloadChecked() {
+ private void downloadChecked(List<FeedItem> items) {
// download the check episodes in the same order as they are currently displayed
List<DownloadRequest> requests = new ArrayList<>();
- for (FeedItem episode : selectedItems) {
+ for (FeedItem episode : items) {
if (episode.hasMedia() && !episode.getFeed().isLocalFeed()) {
requests.add(DownloadRequestCreator.create(episode.getMedia()).build());
}
@@ -88,9 +90,9 @@ public class EpisodeMultiSelectActionHandler {
showMessage(R.plurals.downloading_batch_label, requests.size());
}
- private void deleteChecked() {
+ private void deleteChecked(List<FeedItem> items) {
int countHasMedia = 0;
- for (FeedItem feedItem : selectedItems) {
+ for (FeedItem feedItem : items) {
if (feedItem.hasMedia() && feedItem.getMedia().isDownloaded()) {
countHasMedia++;
DBWriter.deleteFeedMediaOfItem(activity, feedItem.getMedia().getId());
@@ -100,14 +102,22 @@ public class EpisodeMultiSelectActionHandler {
}
private void showMessage(@PluralsRes int msgId, int numItems) {
- activity.showSnackbarAbovePlayer(activity.getResources()
- .getQuantityString(msgId, numItems, numItems), Snackbar.LENGTH_LONG);
+ totalNumItems += numItems;
+ activity.runOnUiThread(() -> {
+ String text = activity.getResources().getQuantityString(msgId, totalNumItems, totalNumItems);
+ if (snackbar != null) {
+ snackbar.setText(text);
+ snackbar.show(); // Resets the timeout
+ } else {
+ snackbar = activity.showSnackbarAbovePlayer(text, 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();
+ private long[] getSelectedIds(List<FeedItem> items) {
+ long[] checkedIds = new long[items.size()];
+ for (int i = 0; i < items.size(); ++i) {
+ checkedIds[i] = items.get(i).getId();
}
return checkedIds;
}
diff --git a/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java b/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java
index bfe269caa..de6056063 100644
--- a/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java
+++ b/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java
@@ -1,13 +1,11 @@
package de.danoeh.antennapod.menuhandler;
import android.content.Context;
-import android.content.DialogInterface;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import androidx.annotation.NonNull;
import de.danoeh.antennapod.R;
-import de.danoeh.antennapod.core.dialog.ConfirmationDialog;
import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.util.IntentUtils;
@@ -56,19 +54,6 @@ public class FeedMenuHandler {
DBTasks.forceRefreshCompleteFeed(context, selectedFeed);
} else if (itemId == R.id.sort_items) {
showSortDialog(context, selectedFeed);
- } else if (itemId == R.id.mark_all_read_item) {
- ConfirmationDialog conDialog = new ConfirmationDialog(context,
- R.string.mark_all_read_label,
- R.string.mark_all_read_feed_confirmation_msg) {
-
- @Override
- public void onConfirmButtonPressed(
- DialogInterface dialog) {
- dialog.dismiss();
- DBWriter.markFeedRead(selectedFeed.getId());
- }
- };
- conDialog.createNewDialog().show();
} else if (itemId == R.id.visit_website_item) {
IntentUtils.openInBrowser(context, selectedFeed.getLink());
} else if (itemId == R.id.share_item) {
diff --git a/app/src/main/res/layout/all_episodes_fragment.xml b/app/src/main/res/layout/all_episodes_fragment.xml
index 3b560967c..c1e7e6434 100644
--- a/app/src/main/res/layout/all_episodes_fragment.xml
+++ b/app/src/main/res/layout/all_episodes_fragment.xml
@@ -2,9 +2,9 @@
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
- android:orientation="vertical"
android:layout_width="match_parent"
- android:layout_height="match_parent">
+ android:layout_height="match_parent"
+ android:orientation="vertical">
<TextView
android:id="@+id/txtvInformation"
@@ -23,17 +23,17 @@
android:layout_below="@+id/txtvInformation"
android:layout_above="@id/loadingMore">
- <de.danoeh.antennapod.view.EpisodeItemListRecyclerView
- android:id="@android:id/list"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_marginTop="0dp"
- android:layout_marginBottom="0dp"
- android:paddingTop="@dimen/list_vertical_padding"
- android:paddingBottom="@dimen/list_vertical_padding"
- android:paddingHorizontal="@dimen/additional_horizontal_spacing"
- tools:itemCount="13"
- tools:listitem="@layout/feeditemlist_item" />
+ <de.danoeh.antennapod.view.EpisodeItemListRecyclerView
+ android:id="@android:id/list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginTop="0dp"
+ android:layout_marginBottom="0dp"
+ android:paddingTop="@dimen/list_vertical_padding"
+ android:paddingBottom="@dimen/list_vertical_padding"
+ android:paddingHorizontal="@dimen/additional_horizontal_spacing"
+ tools:itemCount="13"
+ tools:listitem="@layout/feeditemlist_item" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
@@ -45,7 +45,7 @@
android:indeterminateOnly="true"
android:visibility="gone"
android:layout_centerInParent="true"
- tools:background="@android:color/holo_red_light"/>
+ tools:background="@android:color/holo_red_light" />
<LinearLayout
android:id="@+id/loadingMore"
@@ -76,4 +76,7 @@
</LinearLayout>
-</RelativeLayout> \ No newline at end of file
+ <include
+ layout="@layout/multi_select_speed_dial" />
+
+</RelativeLayout>
diff --git a/app/src/main/res/menu/episodes.xml b/app/src/main/res/menu/episodes.xml
index 75f7df861..4e6da923b 100644
--- a/app/src/main/res/menu/episodes.xml
+++ b/app/src/main/res/menu/episodes.xml
@@ -24,12 +24,4 @@
android:visible="false"
custom:showAsAction="ifRoom"/>
- <item
- android:id="@+id/mark_all_read_item"
- android:title="@string/mark_all_read_label"
- android:menuCategory="container"
- custom:showAsAction="collapseActionView"
- android:visible="false"
- android:icon="@drawable/ic_check"/>
-
</menu>
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java
index a55aed9df..cffcbf32f 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java
@@ -402,6 +402,19 @@ public final class DBReader {
}
}
+ public static int getTotalEpisodeCount(FeedItemFilter filter) {
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
+ adapter.open();
+ try (Cursor cursor = adapter.getTotalEpisodeCountCursor(filter)) {
+ if (cursor.moveToFirst()) {
+ return cursor.getInt(0);
+ }
+ return -1;
+ } finally {
+ adapter.close();
+ }
+ }
+
/**
* Loads the playback history from the database. A FeedItem is in the playback history if playback of the correpsonding episode
* has been completed at least once.
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java
index df4094590..ff8c990db 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java
@@ -732,36 +732,6 @@ public class DBWriter {
}
/**
- * Sets the 'read'-attribute of all FeedItems of a specific Feed to PLAYED.
- *
- * @param feedId ID of the Feed.
- */
- public static Future<?> markFeedRead(final long feedId) {
- return dbExec.submit(() -> {
- final PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setFeedItems(FeedItem.PLAYED, feedId);
- adapter.close();
-
- EventBus.getDefault().post(new UnreadItemsUpdateEvent());
- });
- }
-
- /**
- * Sets the 'read'-attribute of all FeedItems to PLAYED.
- */
- public static Future<?> markAllItemsRead() {
- return dbExec.submit(() -> {
- final PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setFeedItems(FeedItem.PLAYED);
- adapter.close();
-
- EventBus.getDefault().post(new UnreadItemsUpdateEvent());
- });
- }
-
- /**
* Sets the 'read'-attribute of all NEW FeedItems to UNPLAYED.
*/
public static Future<?> removeAllNewFlags() {
diff --git a/core/src/test/java/de/danoeh/antennapod/core/storage/DbWriterTest.java b/core/src/test/java/de/danoeh/antennapod/core/storage/DbWriterTest.java
index de1e78408..533b517d5 100644
--- a/core/src/test/java/de/danoeh/antennapod/core/storage/DbWriterTest.java
+++ b/core/src/test/java/de/danoeh/antennapod/core/storage/DbWriterTest.java
@@ -752,34 +752,6 @@ public class DbWriterTest {
}
@Test
- public void testMarkFeedRead() throws Exception {
- final int numItems = 10;
- Feed feed = new Feed("url", null, "title");
- feed.setItems(new ArrayList<>());
- for (int i = 0; i < numItems; i++) {
- FeedItem item = new FeedItem(0, "title " + i, "id " + i, "link " + i,
- new Date(), FeedItem.UNPLAYED, feed);
- feed.getItems().add(item);
- }
-
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setCompleteFeed(feed);
- adapter.close();
-
- assertTrue(feed.getId() != 0);
- for (FeedItem item : feed.getItems()) {
- assertTrue(item.getId() != 0);
- }
-
- DBWriter.markFeedRead(feed.getId()).get(TIMEOUT, TimeUnit.SECONDS);
- List<FeedItem> loadedItems = DBReader.getFeedItemList(feed);
- for (FeedItem item : loadedItems) {
- assertTrue(item.isPlayed());
- }
- }
-
- @Test
public void testRemoveAllNewFlags() throws Exception {
final int numItems = 10;
Feed feed = new Feed("url", null, "title");
@@ -807,34 +779,6 @@ public class DbWriterTest {
}
}
- @Test
- public void testMarkAllItemsReadSameFeed() throws Exception {
- final int numItems = 10;
- Feed feed = new Feed("url", null, "title");
- feed.setItems(new ArrayList<>());
- for (int i = 0; i < numItems; i++) {
- FeedItem item = new FeedItem(0, "title " + i, "id " + i, "link " + i,
- new Date(), FeedItem.UNPLAYED, feed);
- feed.getItems().add(item);
- }
-
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setCompleteFeed(feed);
- adapter.close();
-
- assertTrue(feed.getId() != 0);
- for (FeedItem item : feed.getItems()) {
- assertTrue(item.getId() != 0);
- }
-
- DBWriter.markAllItemsRead().get(TIMEOUT, TimeUnit.SECONDS);
- List<FeedItem> loadedItems = DBReader.getFeedItemList(feed);
- for (FeedItem item : loadedItems) {
- assertTrue(item.isPlayed());
- }
- }
-
private static Feed createTestFeed(int numItems) {
Feed feed = new Feed("url", null, "title");
feed.setItems(new ArrayList<>());
diff --git a/storage/database/src/main/java/de/danoeh/antennapod/storage/database/PodDBAdapter.java b/storage/database/src/main/java/de/danoeh/antennapod/storage/database/PodDBAdapter.java
index 3f361eb89..41c8e051a 100644
--- a/storage/database/src/main/java/de/danoeh/antennapod/storage/database/PodDBAdapter.java
+++ b/storage/database/src/main/java/de/danoeh/antennapod/storage/database/PodDBAdapter.java
@@ -1052,6 +1052,14 @@ public class PodDBAdapter {
return db.rawQuery(query, null);
}
+ public final Cursor getTotalEpisodeCountCursor(FeedItemFilter filter) {
+ String filterQuery = FeedItemFilterQuery.generateFrom(filter);
+ String whereClause = "".equals(filterQuery) ? "" : " WHERE " + filterQuery;
+ final String query = "SELECT count(" + TABLE_NAME_FEED_ITEMS + "." + KEY_ID + ") FROM " + TABLE_NAME_FEED_ITEMS
+ + JOIN_FEED_ITEM_AND_MEDIA + whereClause;
+ return db.rawQuery(query, null);
+ }
+
public Cursor getDownloadedItemsCursor() {
final String query = SELECT_FEED_ITEMS_AND_MEDIA
+ "WHERE " + TABLE_NAME_FEED_MEDIA + "." + KEY_DOWNLOADED + " > 0";
diff --git a/storage/database/src/main/java/de/danoeh/antennapod/storage/database/mapper/FeedItemFilterQuery.java b/storage/database/src/main/java/de/danoeh/antennapod/storage/database/mapper/FeedItemFilterQuery.java
index 1728a905f..6c78af50c 100644
--- a/storage/database/src/main/java/de/danoeh/antennapod/storage/database/mapper/FeedItemFilterQuery.java
+++ b/storage/database/src/main/java/de/danoeh/antennapod/storage/database/mapper/FeedItemFilterQuery.java
@@ -34,6 +34,8 @@ public class FeedItemFilterQuery {
statements.add(keyRead + " = 1 ");
} else if (filter.showUnplayed) {
statements.add(" NOT " + keyRead + " = 1 "); // Match "New" items (read = -1) as well
+ } else if (filter.showNew) {
+ statements.add(keyRead + " = -1 ");
}
if (filter.showPaused) {
statements.add(" (" + keyPosition + " NOT NULL AND " + keyPosition + " > 0 " + ") ");
diff --git a/ui/i18n/src/main/res/values/strings.xml b/ui/i18n/src/main/res/values/strings.xml
index f42188982..8c8dd1b2f 100644
--- a/ui/i18n/src/main/res/values/strings.xml
+++ b/ui/i18n/src/main/res/values/strings.xml
@@ -152,13 +152,11 @@
<string name="new_episode_notification_group_text">Your subscriptions have new episodes.</string>
<!-- Actions on feeds -->
- <string name="mark_all_read_label">Mark all as played</string>
- <string name="mark_all_read_msg">Marked all Episodes as played</string>
- <string name="mark_all_read_confirmation_msg">Please confirm that you want to mark all episodes as being played.</string>
- <string name="mark_all_read_feed_confirmation_msg">Please confirm that you want to mark all episodes in this podcast as being played.</string>
<string name="remove_all_inbox_label">Remove all from inbox</string>
<string name="removed_all_inbox_msg">Removed all from inbox</string>
<string name="remove_all_inbox_confirmation_msg">Please confirm that you want to remove all from the inbox.</string>
+ <string name="multi_select_mark_played_confirmation">Please confirm that you want to mark all selected items as played.</string>
+ <string name="multi_select_mark_unplayed_confirmation">Please confirm that you want to mark all selected items as unplayed.</string>
<string name="show_info_label">Show information</string>
<string name="show_feed_settings_label">Show podcast settings</string>
<string name="feed_settings_label">Podcast settings</string>