diff options
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> |