Directly enter multi-select when long-pressing subscriptions (#8189)

This commit is contained in:
Hans-Peter Lehmann
2026-01-01 13:28:31 +01:00
committed by GitHub
parent 9333f07a69
commit adaefae3be
10 changed files with 50 additions and 114 deletions

View File

@ -299,7 +299,7 @@ public class SearchFragment extends Fragment implements EpisodeItemListAdapter.O
public boolean onContextItemSelected(@NonNull MenuItem item) { public boolean onContextItemSelected(@NonNull MenuItem item) {
Feed selectedFeedItem = adapterFeeds.getLongPressedItem(); Feed selectedFeedItem = adapterFeeds.getLongPressedItem();
if (selectedFeedItem != null if (selectedFeedItem != null
&& FeedMenuHandler.onMenuItemClicked(this, item.getItemId(), selectedFeedItem, () -> { })) { && FeedMenuHandler.onMenuItemClicked(this, item.getItemId(), selectedFeedItem)) {
return true; return true;
} }
FeedItem selectedItem = adapter.getLongPressedItem(); FeedItem selectedItem = adapter.getLongPressedItem();

View File

@ -198,7 +198,7 @@ public class NavDrawerFragment extends Fragment implements SharedPreferences.OnS
new RemoveFeedDialogClose(Collections.singletonList(feed)).show(getParentFragmentManager(), null); new RemoveFeedDialogClose(Collections.singletonList(feed)).show(getParentFragmentManager(), null);
return true; return true;
} }
if (FeedMenuHandler.onMenuItemClicked(this, itemId, feed, null)) { if (FeedMenuHandler.onMenuItemClicked(this, itemId, feed)) {
return true; return true;
} }
return super.onContextItemSelected(item); return super.onContextItemSelected(item);

View File

@ -335,9 +335,7 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
return true; return true;
} }
Runnable showRemovedAllSnackbar = () -> EventBus.getDefault().post( return FeedMenuHandler.onMenuItemClicked(this, item.getItemId(), feed);
new MessageEvent(getString(R.string.removed_all_inbox_msg)));
return FeedMenuHandler.onMenuItemClicked(this, item.getItemId(), feed, showRemovedAllSnackbar);
} }
public static class RemoveFeedDialogClose extends RemoveFeedDialog { public static class RemoveFeedDialogClose extends RemoveFeedDialog {

View File

@ -57,7 +57,7 @@ public abstract class HomeSection extends Fragment implements View.OnCreateConte
HorizontalFeedListAdapter adapter = (HorizontalFeedListAdapter) viewBinding.recyclerView.getAdapter(); HorizontalFeedListAdapter adapter = (HorizontalFeedListAdapter) viewBinding.recyclerView.getAdapter();
Feed selectedFeed = adapter.getLongPressedItem(); Feed selectedFeed = adapter.getLongPressedItem();
return selectedFeed != null return selectedFeed != null
&& FeedMenuHandler.onMenuItemClicked(this, item.getItemId(), selectedFeed, () -> { }); && FeedMenuHandler.onMenuItemClicked(this, item.getItemId(), selectedFeed);
} }
FeedItem longPressedItem; FeedItem longPressedItem;
if (viewBinding.recyclerView.getAdapter() instanceof EpisodeItemListAdapter) { if (viewBinding.recyclerView.getAdapter() instanceof EpisodeItemListAdapter) {

View File

@ -1,37 +1,24 @@
package de.danoeh.antennapod.ui.screen.subscriptions; package de.danoeh.antennapod.ui.screen.subscriptions;
import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface;
import android.util.Log;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import de.danoeh.antennapod.R; import de.danoeh.antennapod.R;
import de.danoeh.antennapod.ui.common.ConfirmationDialog; import de.danoeh.antennapod.model.feed.Feed;
import de.danoeh.antennapod.storage.database.DBWriter;
import de.danoeh.antennapod.ui.screen.feed.RemoveFeedDialog; import de.danoeh.antennapod.ui.screen.feed.RemoveFeedDialog;
import de.danoeh.antennapod.ui.screen.feed.RenameFeedDialog; import de.danoeh.antennapod.ui.screen.feed.RenameFeedDialog;
import de.danoeh.antennapod.ui.screen.feed.preferences.TagSettingsDialog; import de.danoeh.antennapod.ui.screen.feed.preferences.TagSettingsDialog;
import de.danoeh.antennapod.model.feed.Feed;
import de.danoeh.antennapod.ui.share.ShareUtils; import de.danoeh.antennapod.ui.share.ShareUtils;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.schedulers.Schedulers;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
/** /**
* Handles interactions with the FeedItemMenu. * Handles interactions with the FeedItemMenu.
*/ */
public abstract class FeedMenuHandler { public abstract class FeedMenuHandler {
private static final String TAG = "FeedMenuHandler";
public static boolean onPrepareMenu(Menu menu, List<Feed> selectedItems) { public static boolean onPrepareMenu(Menu menu, List<Feed> selectedItems) {
if (menu == null || selectedItems == null || selectedItems.isEmpty() || selectedItems.get(0) == null) { if (menu == null || selectedItems == null || selectedItems.isEmpty() || selectedItems.get(0) == null) {
return false; return false;
@ -62,29 +49,13 @@ public abstract class FeedMenuHandler {
} }
public static boolean onMenuItemClicked(@NonNull Fragment fragment, int menuItemId, public static boolean onMenuItemClicked(@NonNull Fragment fragment, int menuItemId,
@NonNull Feed selectedFeed, @Nullable Runnable removeFromInboxCallback) { @NonNull Feed selectedFeed) {
@NonNull Context context = fragment.requireContext(); @NonNull Context context = fragment.requireContext();
if (menuItemId == R.id.rename_folder_item) { if (menuItemId == R.id.rename_folder_item) {
new RenameFeedDialog(fragment.getActivity(), selectedFeed).show(); new RenameFeedDialog(fragment.getActivity(), selectedFeed).show();
} else if (menuItemId == R.id.remove_all_inbox_item) { } else if (menuItemId == R.id.remove_all_inbox_item) {
ConfirmationDialog dialog = new ConfirmationDialog(fragment.getActivity(), new FeedMultiSelectActionHandler(fragment.getActivity(), Collections.singletonList(selectedFeed))
R.string.remove_all_inbox_label, R.string.remove_all_inbox_confirmation_msg) { .handleAction(R.id.remove_all_inbox_item);
@Override
@SuppressLint("CheckResult")
public void onConfirmButtonPressed(DialogInterface clickedDialog) {
clickedDialog.dismiss();
Observable.fromCallable((Callable<Future>) () -> DBWriter.removeFeedNewFlag(selectedFeed.getId()))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(result -> {
if (removeFromInboxCallback != null) {
removeFromInboxCallback.run();
}
}, error -> Log.e(TAG, Log.getStackTraceString(error)));
}
};
dialog.createNewDialog().show();
} else if (menuItemId == R.id.edit_tags) { } else if (menuItemId == R.id.edit_tags) {
TagSettingsDialog.newInstance(Collections.singletonList(selectedFeed.getPreferences())) TagSettingsDialog.newInstance(Collections.singletonList(selectedFeed.getPreferences()))
.show(fragment.getChildFragmentManager(), TagSettingsDialog.TAG); .show(fragment.getChildFragmentManager(), TagSettingsDialog.TAG);

View File

@ -1,5 +1,6 @@
package de.danoeh.antennapod.ui.screen.subscriptions; package de.danoeh.antennapod.ui.screen.subscriptions;
import android.content.DialogInterface;
import android.util.Log; import android.util.Log;
import androidx.annotation.PluralsRes; import androidx.annotation.PluralsRes;
@ -15,12 +16,16 @@ import de.danoeh.antennapod.R;
import de.danoeh.antennapod.event.MessageEvent; import de.danoeh.antennapod.event.MessageEvent;
import de.danoeh.antennapod.storage.database.DBWriter; import de.danoeh.antennapod.storage.database.DBWriter;
import de.danoeh.antennapod.databinding.PlaybackSpeedFeedSettingDialogBinding; import de.danoeh.antennapod.databinding.PlaybackSpeedFeedSettingDialogBinding;
import de.danoeh.antennapod.ui.common.ConfirmationDialog;
import de.danoeh.antennapod.ui.screen.feed.RemoveFeedDialog; import de.danoeh.antennapod.ui.screen.feed.RemoveFeedDialog;
import de.danoeh.antennapod.ui.screen.feed.preferences.TagSettingsDialog; import de.danoeh.antennapod.ui.screen.feed.preferences.TagSettingsDialog;
import de.danoeh.antennapod.model.feed.Feed; import de.danoeh.antennapod.model.feed.Feed;
import de.danoeh.antennapod.model.feed.FeedPreferences; import de.danoeh.antennapod.model.feed.FeedPreferences;
import de.danoeh.antennapod.ui.screen.preferences.PreferenceListDialog; import de.danoeh.antennapod.ui.screen.preferences.PreferenceListDialog;
import de.danoeh.antennapod.ui.screen.preferences.PreferenceSwitchDialog; import de.danoeh.antennapod.ui.screen.preferences.PreferenceSwitchDialog;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.schedulers.Schedulers;
import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.EventBus;
import de.danoeh.antennapod.ui.share.ShareUtils; import de.danoeh.antennapod.ui.share.ShareUtils;
@ -53,6 +58,8 @@ public class FeedMultiSelectActionHandler {
playbackSpeedPrefHandler(); playbackSpeedPrefHandler();
} else if (id == R.id.edit_tags) { } else if (id == R.id.edit_tags) {
editFeedPrefTags(); editFeedPrefTags();
} else if (id == R.id.remove_all_inbox_item) {
removeAllFromInbox();
} else if (id == R.id.share_feed) { } else if (id == R.id.share_feed) {
if (!selectedItems.get(0).isLocalFeed()) { if (!selectedItems.get(0).isLocalFeed()) {
ShareUtils.shareFeedLink(activity, selectedItems.get(0)); ShareUtils.shareFeedLink(activity, selectedItems.get(0));
@ -150,4 +157,21 @@ public class FeedMultiSelectActionHandler {
TagSettingsDialog.newInstance(preferencesList).show(activity.getSupportFragmentManager(), TagSettingsDialog.newInstance(preferencesList).show(activity.getSupportFragmentManager(),
TagSettingsDialog.TAG); TagSettingsDialog.TAG);
} }
private void removeAllFromInbox() {
new ConfirmationDialog(activity, R.string.remove_all_inbox_label, R.string.remove_all_inbox_confirmation_msg) {
@Override
public void onConfirmButtonPressed(DialogInterface clickedDialog) {
clickedDialog.dismiss();
Observable.fromAction(() -> {
for (Feed selectedFeed : selectedItems) {
DBWriter.removeFeedNewFlag(selectedFeed.getId());
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(result -> { }, error -> Log.e(TAG, Log.getStackTraceString(error)));
}
}.createNewDialog().show();
}
} }

View File

@ -144,13 +144,6 @@ public class SubscriptionFragment extends Fragment
subscriptionRecycler.addOnScrollListener(new LiftOnScrollListener(root.findViewById(R.id.appbar))); subscriptionRecycler.addOnScrollListener(new LiftOnScrollListener(root.findViewById(R.id.appbar)));
subscriptionRecycler.addOnScrollListener(new LiftOnScrollListener(collapsingContainer)); subscriptionRecycler.addOnScrollListener(new LiftOnScrollListener(collapsingContainer));
subscriptionAdapter = new SubscriptionsRecyclerAdapter((MainActivity) getActivity()) { subscriptionAdapter = new SubscriptionsRecyclerAdapter((MainActivity) getActivity()) {
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
MenuItemUtils.setOnClickListeners(menu, SubscriptionFragment.this::onContextItemSelected);
FeedMenuHandler.onPrepareMenu(menu, Collections.singletonList(getLongPressedItem()));
}
@Override @Override
protected void onSelectedItemsUpdated() { protected void onSelectedItemsUpdated() {
super.onSelectedItemsUpdated(); super.onSelectedItemsUpdated();
@ -201,8 +194,12 @@ public class SubscriptionFragment extends Fragment
subscriptionAddButton.setVisibility(View.GONE); subscriptionAddButton.setVisibility(View.GONE);
} }
floatingSelectMenu.setOnMenuItemClickListener(menuItem -> { floatingSelectMenu.setOnMenuItemClickListener(menuItem -> {
new FeedMultiSelectActionHandler(getActivity(), subscriptionAdapter.getSelectedItems()) List<Feed> selection = subscriptionAdapter.getSelectedItems();
new FeedMultiSelectActionHandler(getActivity(), selection)
.handleAction(menuItem.getItemId()); .handleAction(menuItem.getItemId());
if (selection.size() <= 1) {
subscriptionAdapter.endSelectMode();
}
return true; return true;
}); });
@ -473,18 +470,6 @@ public class SubscriptionFragment extends Fragment
return TagMenuHandler.onMenuItemClicked(this, selectedTag, item, tagAdapter); return TagMenuHandler.onMenuItemClicked(this, selectedTag, item, tagAdapter);
} }
@Override
public boolean onContextItemSelected(@NonNull MenuItem item) {
Feed selectedFeed = subscriptionAdapter.getSelectedItem();
if (selectedFeed == null) {
return false;
}
if (item.getItemId() == R.id.multi_select) {
return subscriptionAdapter.onContextItemSelected(item);
}
return FeedMenuHandler.onMenuItemClicked(this, item.getItemId(), selectedFeed, this::loadSubscriptionsAndTags);
}
@Subscribe(threadMode = ThreadMode.MAIN) @Subscribe(threadMode = ThreadMode.MAIN)
public void onFeedListChanged(FeedListUpdateEvent event) { public void onFeedListChanged(FeedListUpdateEvent event) {
loadSubscriptionsAndTags(); loadSubscriptionsAndTags();

View File

@ -3,13 +3,7 @@ package de.danoeh.antennapod.ui.screen.subscriptions;
import android.content.Context; import android.content.Context;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Rect; import android.graphics.Rect;
import android.os.Build;
import android.view.ContextMenu;
import android.view.InputDevice;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.FrameLayout; import android.widget.FrameLayout;
@ -32,13 +26,10 @@ import java.util.Map;
/** /**
* Adapter for subscriptions * Adapter for subscriptions
*/ */
public class SubscriptionsRecyclerAdapter extends SelectableAdapter<SubscriptionViewHolder> public class SubscriptionsRecyclerAdapter extends SelectableAdapter<SubscriptionViewHolder> {
implements View.OnCreateContextMenuListener {
private final WeakReference<MainActivity> mainActivityRef; private final WeakReference<MainActivity> mainActivityRef;
private List<Feed> listItems; private List<Feed> listItems;
private Map<Long, Integer> feedCounters; private Map<Long, Integer> feedCounters;
private Feed selectedItem = null;
int longPressedPosition = 0; // used to init actionMode
private int columnCount = 3; private int columnCount = 3;
public SubscriptionsRecyclerAdapter(MainActivity mainActivity) { public SubscriptionsRecyclerAdapter(MainActivity mainActivity) {
@ -57,10 +48,6 @@ public class SubscriptionsRecyclerAdapter extends SelectableAdapter<Subscription
return listItems.get(position); return listItems.get(position);
} }
public Feed getSelectedItem() {
return selectedItem;
}
@NonNull @NonNull
@Override @Override
public SubscriptionViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { public SubscriptionViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
@ -80,18 +67,19 @@ public class SubscriptionsRecyclerAdapter extends SelectableAdapter<Subscription
public void onBindViewHolder(@NonNull SubscriptionViewHolder holder, int position) { public void onBindViewHolder(@NonNull SubscriptionViewHolder holder, int position) {
Feed feed = listItems.get(position); Feed feed = listItems.get(position);
holder.bind(feed, columnCount, feedCounters.containsKey(feed.getId()) ? feedCounters.get(feed.getId()) : 0); holder.bind(feed, columnCount, feedCounters.containsKey(feed.getId()) ? feedCounters.get(feed.getId()) : 0);
holder.itemView.setOnCreateContextMenuListener(this);
int cardMargin = 0; int cardMargin = 0;
if (inActionMode()) { if (inActionMode()) {
if (holder.selectIcon != null) { if (holder.selectIcon != null) {
holder.selectIcon.setVisibility(View.VISIBLE); holder.selectIcon.setVisibility(View.VISIBLE);
holder.gradient.setVisibility(View.VISIBLE); holder.gradient.setVisibility(View.VISIBLE);
holder.itemView.setSelected(isSelected(position));
holder.selectIcon.setImageResource(isSelected(position) holder.selectIcon.setImageResource(isSelected(position)
? R.drawable.circle_checked : R.drawable.circle_unchecked); ? R.drawable.circle_checked : R.drawable.circle_unchecked);
cardMargin = isSelected(position) ? (int) convertDpToPixel( cardMargin = isSelected(position) ? (int) convertDpToPixel(
holder.itemView.getContext(), 12f) : 0; holder.itemView.getContext(), 12f) : 0;
holder.count.setVisibility(View.GONE); holder.count.setVisibility(View.GONE);
} else { } else {
holder.itemView.setSelected(isSelected(position));
holder.itemView.setBackgroundResource(android.R.color.transparent); holder.itemView.setBackgroundResource(android.R.color.transparent);
if (isSelected(position)) { if (isSelected(position)) {
holder.itemView.setBackgroundColor(0x88000000 holder.itemView.setBackgroundColor(0x88000000
@ -99,6 +87,7 @@ public class SubscriptionsRecyclerAdapter extends SelectableAdapter<Subscription
} }
} }
} else { } else {
holder.itemView.setSelected(false);
holder.itemView.setBackgroundResource(android.R.color.transparent); holder.itemView.setBackgroundResource(android.R.color.transparent);
if (holder.selectIcon != null) { if (holder.selectIcon != null) {
holder.selectIcon.setVisibility(View.GONE); holder.selectIcon.setVisibility(View.GONE);
@ -116,21 +105,8 @@ public class SubscriptionsRecyclerAdapter extends SelectableAdapter<Subscription
holder.itemView.setOnLongClickListener(v -> { holder.itemView.setOnLongClickListener(v -> {
if (!inActionMode()) { if (!inActionMode()) {
longPressedPosition = holder.getBindingAdapterPosition(); startSelectMode(holder.getBindingAdapterPosition());
selectedItem = feed; return true;
}
return false;
});
holder.itemView.setOnTouchListener((v, e) -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (e.isFromSource(InputDevice.SOURCE_MOUSE)
&& e.getButtonState() == MotionEvent.BUTTON_SECONDARY) {
if (!inActionMode()) {
longPressedPosition = holder.getBindingAdapterPosition();
selectedItem = feed;
}
}
} }
return false; return false;
}); });
@ -143,7 +119,6 @@ public class SubscriptionsRecyclerAdapter extends SelectableAdapter<Subscription
mainActivityRef.get().loadChildFragment(fragment); mainActivityRef.get().loadChildFragment(fragment);
} }
}); });
} }
@Override @Override
@ -159,29 +134,6 @@ public class SubscriptionsRecyclerAdapter extends SelectableAdapter<Subscription
return listItems.get(position).getId(); return listItems.get(position).getId();
} }
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
if (inActionMode() || selectedItem == null) {
return;
}
MenuInflater inflater = mainActivityRef.get().getMenuInflater();
inflater.inflate(R.menu.nav_feed_context, menu);
menu.findItem(R.id.multi_select).setVisible(true);
menu.setHeaderTitle(selectedItem.getTitle());
}
public boolean onContextItemSelected(MenuItem item) {
if (item.getItemId() == R.id.multi_select) {
startSelectMode(longPressedPosition);
return true;
}
return false;
}
public Feed getLongPressedItem() {
return listItems.get(longPressedPosition);
}
public List<Feed> getSelectedItems() { public List<Feed> getSelectedItems() {
List<Feed> items = new ArrayList<>(); List<Feed> items = new ArrayList<>();
for (int i = 0; i < getItemCount(); i++) { for (int i = 0; i < getItemCount(); i++) {

View File

@ -69,7 +69,7 @@
android:layout_height="48dp" android:layout_height="48dp"
android:layout_gravity="top" android:layout_gravity="top"
android:rotation="180" android:rotation="180"
android:alpha="0.6" android:alpha="0.4"
android:visibility="gone" android:visibility="gone"
tools:visibility="visible" tools:visibility="visible"
app:srcCompat="@drawable/bg_gradient" app:srcCompat="@drawable/bg_gradient"

View File

@ -12,6 +12,12 @@
android:title="@string/share_label" android:title="@string/share_label"
android:icon="@drawable/ic_share"/> android:icon="@drawable/ic_share"/>
<item
android:id="@+id/remove_all_inbox_item"
android:menuCategory="container"
android:title="@string/remove_all_inbox_label"
android:icon="@drawable/ic_check"/>
<item <item
android:id="@+id/remove_archive_feed" android:id="@+id/remove_archive_feed"
android:menuCategory="container" android:menuCategory="container"