From a8dfe6f1233ffe43c34fc20d9ed9243064aaa8eb Mon Sep 17 00:00:00 2001 From: ByteHamster Date: Mon, 29 Apr 2024 07:40:03 +0200 Subject: [PATCH] Bottom multi-select (#7093) --- app/build.gradle | 1 - .../episodeslist/EpisodeItemListAdapter.java | 2 + .../ui/episodeslist/EpisodesListFragment.java | 45 ++++------ .../antennapod/ui/screen/InboxFragment.java | 6 +- .../antennapod/ui/screen/SearchFragment.java | 43 ++++----- .../download/CompletedDownloadsFragment.java | 48 +++++----- .../ui/screen/feed/FeedItemlistFragment.java | 61 ++++++------- .../ui/screen/queue/QueueFragment.java | 46 ++++------ .../subscriptions/SubscriptionFragment.java | 18 ++-- .../ui/view/FloatingSelectMenu.java | 90 +++++++++++++++++++ .../res/layout/episodes_list_fragment.xml | 7 +- .../res/layout/feed_item_list_fragment.xml | 7 +- .../main/res/layout/floating_select_menu.xml | 31 +++++++ .../res/layout/floating_select_menu_item.xml | 28 ++++++ .../res/layout/fragment_subscriptions.xml | 7 +- .../res/layout/multi_select_speed_dial.xml | 41 --------- app/src/main/res/layout/queue_fragment.xml | 7 +- app/src/main/res/layout/search_fragment.xml | 7 +- .../main/res/layout/simple_list_fragment.xml | 7 +- app/src/main/res/values/dimens.xml | 1 + .../src/main/res/drawable/ic_fab_edit.xml | 5 -- ui/i18n/src/main/res/values/strings.xml | 2 +- ui/preferences/src/main/assets/licenses.xml | 6 -- 23 files changed, 294 insertions(+), 222 deletions(-) create mode 100644 app/src/main/java/de/danoeh/antennapod/ui/view/FloatingSelectMenu.java create mode 100644 app/src/main/res/layout/floating_select_menu.xml create mode 100644 app/src/main/res/layout/floating_select_menu_item.xml delete mode 100644 app/src/main/res/layout/multi_select_speed_dial.xml delete mode 100644 ui/common/src/main/res/drawable/ic_fab_edit.xml diff --git a/app/build.gradle b/app/build.gradle index 2abb66ccfd..a918918619 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -114,7 +114,6 @@ dependencies { implementation "io.reactivex.rxjava2:rxandroid:$rxAndroidVersion" implementation "io.reactivex.rxjava2:rxjava:$rxJavaVersion" - implementation 'com.leinardi.android:speed-dial:3.2.0' implementation 'com.github.ByteHamster:SearchPreference:v2.5.0' implementation 'com.github.skydoves:balloon:1.5.3' implementation 'com.github.xabaras:RecyclerViewSwipeDecorator:1.3' diff --git a/app/src/main/java/de/danoeh/antennapod/ui/episodeslist/EpisodeItemListAdapter.java b/app/src/main/java/de/danoeh/antennapod/ui/episodeslist/EpisodeItemListAdapter.java index 34f4739536..5082f25ffd 100644 --- a/app/src/main/java/de/danoeh/antennapod/ui/episodeslist/EpisodeItemListAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/ui/episodeslist/EpisodeItemListAdapter.java @@ -111,9 +111,11 @@ public final void onBindViewHolder(EpisodeItemViewHolder holder, int pos) { return false; }); + holder.itemView.setSelected(false); if (inActionMode()) { holder.secondaryActionButton.setOnClickListener(null); if (isSelected(pos)) { + holder.itemView.setSelected(true); holder.itemView.setBackgroundColor(0x88000000 + (0xffffff & ThemeUtils.getColorFromAttr(mainActivityRef.get(), R.attr.colorAccent))); } else { diff --git a/app/src/main/java/de/danoeh/antennapod/ui/episodeslist/EpisodesListFragment.java b/app/src/main/java/de/danoeh/antennapod/ui/episodeslist/EpisodesListFragment.java index 90f7cf1e53..08ec14ec09 100644 --- a/app/src/main/java/de/danoeh/antennapod/ui/episodeslist/EpisodesListFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/ui/episodeslist/EpisodesListFragment.java @@ -22,10 +22,10 @@ import com.google.android.material.appbar.MaterialToolbar; import com.google.android.material.snackbar.Snackbar; -import com.leinardi.android.speeddial.SpeedDialView; import de.danoeh.antennapod.ui.screen.SearchFragment; import de.danoeh.antennapod.net.download.serviceinterface.FeedUpdateManager; +import de.danoeh.antennapod.ui.view.FloatingSelectMenu; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; @@ -72,7 +72,7 @@ public abstract class EpisodesListFragment extends Fragment protected EpisodeItemListRecyclerView recyclerView; protected EpisodeItemListAdapter listAdapter; protected EmptyViewHandler emptyView; - protected SpeedDialView speedDialView; + protected FloatingSelectMenu floatingSelectMenu; protected MaterialToolbar toolbar; protected SwipeRefreshLayout swipeRefreshLayout; protected SwipeActions swipeActions; @@ -200,41 +200,30 @@ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMen emptyView.updateAdapter(listAdapter); 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() { + floatingSelectMenu = root.findViewById(R.id.floatingSelectMenu); + floatingSelectMenu.inflate(R.menu.episodes_apply_action_speeddial); + floatingSelectMenu.setOnMenuItemClickListener(menuItem -> { + if (listAdapter.getSelectedCount() == 0) { + ((MainActivity) getActivity()).showSnackbarAbovePlayer(R.string.no_items_selected, + Snackbar.LENGTH_SHORT); 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) { + if (menuItem.getItemId() == R.id.mark_read_batch) { confirmationString = R.string.multi_select_mark_played_confirmation; - } else if (actionItem.getId() == R.id.mark_unread_batch) { + } else if (menuItem.getItemId() == R.id.mark_unread_batch) { confirmationString = R.string.multi_select_mark_unplayed_confirmation; } } if (confirmationString == 0) { - performMultiSelectAction(actionItem.getId()); + performMultiSelectAction(menuItem.getItemId()); } else { new ConfirmationDialog(getActivity(), R.string.multi_select, confirmationString) { @Override public void onConfirmButtonPressed(DialogInterface dialog) { - performMultiSelectAction(actionItem.getId()); + performMultiSelectAction(menuItem.getItemId()); } }.createNewDialog().show(); } @@ -320,13 +309,17 @@ public void onDestroyView() { @Override public void onStartSelectMode() { - speedDialView.setVisibility(View.VISIBLE); + floatingSelectMenu.setVisibility(View.VISIBLE); + recyclerView.setPadding(recyclerView.getPaddingLeft(), recyclerView.getPaddingTop(), + recyclerView.getPaddingRight(), + (int) getResources().getDimension(R.dimen.floating_select_menu_height)); } @Override public void onEndSelectMode() { - speedDialView.close(); - speedDialView.setVisibility(View.GONE); + floatingSelectMenu.setVisibility(View.GONE); + recyclerView.setPadding(recyclerView.getPaddingLeft(), recyclerView.getPaddingTop(), + recyclerView.getPaddingRight(), 0); } @Subscribe(threadMode = ThreadMode.MAIN) diff --git a/app/src/main/java/de/danoeh/antennapod/ui/screen/InboxFragment.java b/app/src/main/java/de/danoeh/antennapod/ui/screen/InboxFragment.java index 5ac6e5bef2..68d7accf6c 100644 --- a/app/src/main/java/de/danoeh/antennapod/ui/screen/InboxFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/ui/screen/InboxFragment.java @@ -48,9 +48,9 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, emptyView.setIcon(R.drawable.ic_inbox); emptyView.setTitle(R.string.no_inbox_head_label); emptyView.setMessage(R.string.no_inbox_label); - speedDialView.removeActionItemById(R.id.mark_unread_batch); - speedDialView.removeActionItemById(R.id.remove_from_queue_batch); - speedDialView.removeActionItemById(R.id.delete_batch); + floatingSelectMenu.getMenu().findItem(R.id.mark_unread_batch).setVisible(false); + floatingSelectMenu.getMenu().findItem(R.id.remove_from_queue_batch).setVisible(false); + floatingSelectMenu.getMenu().findItem(R.id.delete_batch).setVisible(false); return root; } diff --git a/app/src/main/java/de/danoeh/antennapod/ui/screen/SearchFragment.java b/app/src/main/java/de/danoeh/antennapod/ui/screen/SearchFragment.java index 0416cca757..ec782001a4 100644 --- a/app/src/main/java/de/danoeh/antennapod/ui/screen/SearchFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/ui/screen/SearchFragment.java @@ -23,14 +23,12 @@ import com.google.android.material.chip.Chip; import com.google.android.material.snackbar.Snackbar; -import com.leinardi.android.speeddial.SpeedDialView; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.ui.episodeslist.EpisodeItemListAdapter; import de.danoeh.antennapod.ui.screen.subscriptions.HorizontalFeedListAdapter; import de.danoeh.antennapod.ui.MenuItemUtils; -import de.danoeh.antennapod.databinding.MultiSelectSpeedDialBinding; import de.danoeh.antennapod.event.EpisodeDownloadEvent; import de.danoeh.antennapod.event.FeedItemEvent; import de.danoeh.antennapod.event.playback.PlaybackPositionEvent; @@ -46,6 +44,7 @@ import de.danoeh.antennapod.ui.discovery.OnlineSearchFragment; import de.danoeh.antennapod.ui.view.EmptyViewHandler; import de.danoeh.antennapod.ui.episodeslist.EpisodeItemListRecyclerView; +import de.danoeh.antennapod.ui.view.FloatingSelectMenu; import de.danoeh.antennapod.ui.view.LiftOnScrollListener; import de.danoeh.antennapod.ui.episodeslist.EpisodeItemViewHolder; import io.reactivex.Observable; @@ -82,9 +81,9 @@ public class SearchFragment extends Fragment implements EpisodeItemListAdapter.O private List results; private Chip chip; private SearchView searchView; + private FloatingSelectMenu floatingSelectMenu; private Handler automaticSearchDebouncer; private long lastQueryChange = 0; - private MultiSelectSpeedDialBinding speedDialBinding; private boolean isOtherViewInFoucus = false; @@ -141,9 +140,9 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c @Nullable Bundle savedInstanceState) { View layout = inflater.inflate(R.layout.search_fragment, container, false); setupToolbar(layout.findViewById(R.id.toolbar)); - speedDialBinding = MultiSelectSpeedDialBinding.bind(layout); progressBar = layout.findViewById(R.id.progressBar); recyclerView = layout.findViewById(R.id.recyclerView); + floatingSelectMenu = layout.findViewById(R.id.floatingSelectMenu); recyclerView.setRecycledViewPool(((MainActivity) getActivity()).getRecycledViewPool()); registerForContextMenu(recyclerView); adapter = new EpisodeItemListAdapter((MainActivity) getActivity()) { @@ -207,25 +206,14 @@ public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newStat } } }); - speedDialBinding.fabSD.setOverlayLayout(speedDialBinding.fabSDOverlay); - speedDialBinding.fabSD.inflate(R.menu.episodes_apply_action_speeddial); - speedDialBinding.fabSD.setOnChangeListener(new SpeedDialView.OnChangeListener() { - @Override - public boolean onMainActionSelected() { + floatingSelectMenu.inflate(R.menu.episodes_apply_action_speeddial); + floatingSelectMenu.setOnMenuItemClickListener(menuItem -> { + if (adapter.getSelectedCount() == 0) { + ((MainActivity) getActivity()) + .showSnackbarAbovePlayer(R.string.no_items_selected, Snackbar.LENGTH_SHORT); return false; } - - @Override - public void onToggleChanged(boolean open) { - if (open && adapter.getSelectedCount() == 0) { - ((MainActivity) getActivity()) - .showSnackbarAbovePlayer(R.string.no_items_selected, Snackbar.LENGTH_SHORT); - speedDialBinding.fabSD.close(); - } - } - }); - speedDialBinding.fabSD.setOnActionSelectedListener(actionItem -> { - new EpisodeMultiSelectActionHandler((MainActivity) getActivity(), actionItem.getId()) + new EpisodeMultiSelectActionHandler((MainActivity) getActivity(), menuItem.getItemId()) .handleAction(adapter.getSelectedItems()); adapter.endSelectMode(); return true; @@ -438,16 +426,17 @@ private void searchOnline() { @Override public void onStartSelectMode() { searchViewFocusOff(); - speedDialBinding.fabSD.removeActionItemById(R.id.remove_from_inbox_batch); - speedDialBinding.fabSD.removeActionItemById(R.id.remove_from_queue_batch); - speedDialBinding.fabSD.removeActionItemById(R.id.delete_batch); - speedDialBinding.fabSD.setVisibility(View.VISIBLE); + floatingSelectMenu.setVisibility(View.VISIBLE); + recyclerView.setPadding(recyclerView.getPaddingLeft(), recyclerView.getPaddingTop(), + recyclerView.getPaddingRight(), + (int) getResources().getDimension(R.dimen.floating_select_menu_height)); } @Override public void onEndSelectMode() { - speedDialBinding.fabSD.close(); - speedDialBinding.fabSD.setVisibility(View.GONE); + floatingSelectMenu.setVisibility(View.GONE); + recyclerView.setPadding(recyclerView.getPaddingLeft(), recyclerView.getPaddingTop(), + recyclerView.getPaddingRight(), 0); searchViewFocusOn(); } diff --git a/app/src/main/java/de/danoeh/antennapod/ui/screen/download/CompletedDownloadsFragment.java b/app/src/main/java/de/danoeh/antennapod/ui/screen/download/CompletedDownloadsFragment.java index 260f3e6a03..001c2b4658 100644 --- a/app/src/main/java/de/danoeh/antennapod/ui/screen/download/CompletedDownloadsFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/ui/screen/download/CompletedDownloadsFragment.java @@ -13,7 +13,6 @@ import androidx.fragment.app.Fragment; import com.google.android.material.appbar.MaterialToolbar; import com.google.android.material.snackbar.Snackbar; -import com.leinardi.android.speeddial.SpeedDialView; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.ui.episodeslist.EpisodeItemListAdapter; @@ -39,6 +38,7 @@ import de.danoeh.antennapod.storage.preferences.UserPreferences; import de.danoeh.antennapod.ui.view.EmptyViewHandler; import de.danoeh.antennapod.ui.episodeslist.EpisodeItemListRecyclerView; +import de.danoeh.antennapod.ui.view.FloatingSelectMenu; import de.danoeh.antennapod.ui.view.LiftOnScrollListener; import de.danoeh.antennapod.ui.episodeslist.EpisodeItemViewHolder; import io.reactivex.Observable; @@ -71,7 +71,7 @@ public class CompletedDownloadsFragment extends Fragment private Disposable disposable; private EmptyViewHandler emptyView; private boolean displayUpArrow; - private SpeedDialView speedDialView; + private FloatingSelectMenu floatingSelectMenu; private SwipeActions swipeActions; private ProgressBar progressBar; private MaterialToolbar toolbar; @@ -107,31 +107,19 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c progressBar = root.findViewById(R.id.progLoading); progressBar.setVisibility(View.VISIBLE); - speedDialView = root.findViewById(R.id.fabSD); - speedDialView.setOverlayLayout(root.findViewById(R.id.fabSDOverlay)); - speedDialView.inflate(R.menu.episodes_apply_action_speeddial); - speedDialView.removeActionItemById(R.id.download_batch); - speedDialView.removeActionItemById(R.id.mark_read_batch); - speedDialView.removeActionItemById(R.id.mark_unread_batch); - speedDialView.removeActionItemById(R.id.remove_from_queue_batch); - speedDialView.removeActionItemById(R.id.remove_all_inbox_item); - speedDialView.setOnChangeListener(new SpeedDialView.OnChangeListener() { - @Override - public boolean onMainActionSelected() { + floatingSelectMenu = root.findViewById(R.id.floatingSelectMenu); + floatingSelectMenu.inflate(R.menu.episodes_apply_action_speeddial); + floatingSelectMenu.getMenu().findItem(R.id.download_batch).setVisible(false); + floatingSelectMenu.getMenu().findItem(R.id.mark_read_batch).setVisible(false); + floatingSelectMenu.getMenu().findItem(R.id.mark_unread_batch).setVisible(false); + floatingSelectMenu.getMenu().findItem(R.id.remove_from_inbox_batch).setVisible(false); + floatingSelectMenu.setOnMenuItemClickListener(menuItem -> { + if (adapter.getSelectedCount() == 0) { + ((MainActivity) getActivity()).showSnackbarAbovePlayer(R.string.no_items_selected, + Snackbar.LENGTH_SHORT); return false; } - - @Override - public void onToggleChanged(boolean open) { - if (open && adapter.getSelectedCount() == 0) { - ((MainActivity) getActivity()).showSnackbarAbovePlayer(R.string.no_items_selected, - Snackbar.LENGTH_SHORT); - speedDialView.close(); - } - } - }); - speedDialView.setOnActionSelectedListener(actionItem -> { - new EpisodeMultiSelectActionHandler(((MainActivity) getActivity()), actionItem.getId()) + new EpisodeMultiSelectActionHandler(((MainActivity) getActivity()), menuItem.getItemId()) .handleAction(adapter.getSelectedItems()); adapter.endSelectMode(); return true; @@ -331,14 +319,18 @@ private void loadItems() { @Override public void onStartSelectMode() { swipeActions.detach(); - speedDialView.setVisibility(View.VISIBLE); + floatingSelectMenu.setVisibility(View.VISIBLE); + recyclerView.setPadding(recyclerView.getPaddingLeft(), recyclerView.getPaddingTop(), + recyclerView.getPaddingRight(), + (int) getResources().getDimension(R.dimen.floating_select_menu_height)); } @Override public void onEndSelectMode() { - speedDialView.close(); - speedDialView.setVisibility(View.GONE); + floatingSelectMenu.setVisibility(View.GONE); swipeActions.attachTo(recyclerView); + recyclerView.setPadding(recyclerView.getPaddingLeft(), recyclerView.getPaddingTop(), + recyclerView.getPaddingRight(), 0); } private class CompletedDownloadsListAdapter extends EpisodeItemListAdapter { diff --git a/app/src/main/java/de/danoeh/antennapod/ui/screen/feed/FeedItemlistFragment.java b/app/src/main/java/de/danoeh/antennapod/ui/screen/feed/FeedItemlistFragment.java index ee92187ff1..9bc45a235f 100644 --- a/app/src/main/java/de/danoeh/antennapod/ui/screen/feed/FeedItemlistFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/ui/screen/feed/FeedItemlistFragment.java @@ -25,7 +25,6 @@ import com.bumptech.glide.request.RequestOptions; import com.google.android.material.appbar.MaterialToolbar; import com.google.android.material.snackbar.Snackbar; -import com.leinardi.android.speeddial.SpeedDialView; import de.danoeh.antennapod.ui.CoverLoader; import de.danoeh.antennapod.ui.screen.episode.ItemPagerFragment; @@ -55,7 +54,6 @@ import de.danoeh.antennapod.ui.share.ShareUtils; import de.danoeh.antennapod.ui.episodeslist.MoreContentListFooterUtil; import de.danoeh.antennapod.databinding.FeedItemListFragmentBinding; -import de.danoeh.antennapod.databinding.MultiSelectSpeedDialBinding; import de.danoeh.antennapod.ui.screen.download.DownloadLogDetailsDialog; import de.danoeh.antennapod.ui.FeedItemFilterDialog; import de.danoeh.antennapod.event.EpisodeDownloadEvent; @@ -105,7 +103,6 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem private boolean headerCreated = false; private Disposable disposable; private FeedItemListFragmentBinding viewBinding; - private MultiSelectSpeedDialBinding speedDialBinding; /** * Creates new ItemlistFragment which shows the Feeditems of a specific @@ -136,7 +133,6 @@ public void onCreate(Bundle savedInstanceState) { public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { viewBinding = FeedItemListFragmentBinding.inflate(inflater); - speedDialBinding = MultiSelectSpeedDialBinding.bind(viewBinding.getRoot()); viewBinding.toolbar.inflateMenu(R.menu.feedlist); viewBinding.toolbar.setOnMenuItemClickListener(this); viewBinding.toolbar.setOnLongClickListener(v -> { @@ -183,12 +179,7 @@ protected void doTint(Context themedContext) { @Override public void onScrolled(@NonNull RecyclerView view, int deltaX, int deltaY) { super.onScrolled(view, deltaX, deltaY); - boolean hasMorePages = feed != null && feed.isPaged() && feed.getNextPageLink() != null; - boolean pageLoaderVisible = viewBinding.recyclerView.isScrolledToBottom() && hasMorePages; - nextPageLoader.getRoot().setVisibility(pageLoaderVisible ? View.VISIBLE : View.GONE); - viewBinding.recyclerView.setPadding( - viewBinding.recyclerView.getPaddingLeft(), 0, viewBinding.recyclerView.getPaddingRight(), - pageLoaderVisible ? nextPageLoader.getRoot().getMeasuredHeight() : 0); + updateRecyclerPadding(); } }); @@ -200,26 +191,14 @@ public void onScrolled(@NonNull RecyclerView view, int deltaX, int deltaY) { loadItems(); - // Init action UI (via a FAB Speed Dial) - speedDialBinding.fabSD.setOverlayLayout(speedDialBinding.fabSDOverlay); - speedDialBinding.fabSD.inflate(R.menu.episodes_apply_action_speeddial); - speedDialBinding.fabSD.setOnChangeListener(new SpeedDialView.OnChangeListener() { - @Override - public boolean onMainActionSelected() { + viewBinding.floatingSelectMenu.inflate(R.menu.episodes_apply_action_speeddial); + viewBinding.floatingSelectMenu.setOnMenuItemClickListener(menuItem -> { + if (adapter.getSelectedCount() == 0) { + ((MainActivity) getActivity()).showSnackbarAbovePlayer(R.string.no_items_selected, + Snackbar.LENGTH_SHORT); return false; } - - @Override - public void onToggleChanged(boolean open) { - if (open && adapter.getSelectedCount() == 0) { - ((MainActivity) getActivity()).showSnackbarAbovePlayer(R.string.no_items_selected, - Snackbar.LENGTH_SHORT); - speedDialBinding.fabSD.close(); - } - } - }); - speedDialBinding.fabSD.setOnActionSelectedListener(actionItem -> { - new EpisodeMultiSelectActionHandler(((MainActivity) getActivity()), actionItem.getId()) + new EpisodeMultiSelectActionHandler(((MainActivity) getActivity()), menuItem.getItemId()) .handleAction(adapter.getSelectedItems()); adapter.endSelectMode(); return true; @@ -227,6 +206,20 @@ public void onToggleChanged(boolean open) { return viewBinding.getRoot(); } + private void updateRecyclerPadding() { + boolean hasMorePages = feed != null && feed.isPaged() && feed.getNextPageLink() != null; + boolean pageLoaderVisible = viewBinding.recyclerView.isScrolledToBottom() && hasMorePages; + nextPageLoader.getRoot().setVisibility(pageLoaderVisible ? View.VISIBLE : View.GONE); + int paddingBottom = 0; + if (adapter.inActionMode()) { + paddingBottom = (int) getResources().getDimension(R.dimen.floating_select_menu_height); + } else if (pageLoaderVisible) { + paddingBottom = nextPageLoader.getRoot().getMeasuredHeight(); + } + viewBinding.recyclerView.setPadding(viewBinding.recyclerView.getPaddingLeft(), 0, + viewBinding.recyclerView.getPaddingRight(), paddingBottom); + } + @Override public void onDestroyView() { super.onDestroyView(); @@ -406,18 +399,16 @@ public void onQueueChanged(QueueEvent event) { @Override public void onStartSelectMode() { swipeActions.detach(); - if (feed.isLocalFeed()) { - speedDialBinding.fabSD.removeActionItemById(R.id.download_batch); - } - speedDialBinding.fabSD.removeActionItemById(R.id.remove_all_inbox_item); - speedDialBinding.fabSD.setVisibility(View.VISIBLE); + viewBinding.floatingSelectMenu.getMenu().findItem(R.id.download_batch).setVisible(!feed.isLocalFeed()); + viewBinding.floatingSelectMenu.setVisibility(View.VISIBLE); + updateRecyclerPadding(); updateToolbar(); } @Override public void onEndSelectMode() { - speedDialBinding.fabSD.close(); - speedDialBinding.fabSD.setVisibility(View.GONE); + viewBinding.floatingSelectMenu.setVisibility(View.GONE); + updateRecyclerPadding(); swipeActions.attachTo(viewBinding.recyclerView); } diff --git a/app/src/main/java/de/danoeh/antennapod/ui/screen/queue/QueueFragment.java b/app/src/main/java/de/danoeh/antennapod/ui/screen/queue/QueueFragment.java index adc8b6c59d..bc70a06bb6 100644 --- a/app/src/main/java/de/danoeh/antennapod/ui/screen/queue/QueueFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/ui/screen/queue/QueueFragment.java @@ -26,11 +26,11 @@ import com.google.android.material.appbar.MaterialToolbar; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.snackbar.Snackbar; -import com.leinardi.android.speeddial.SpeedDialView; import de.danoeh.antennapod.ui.screen.SearchFragment; import de.danoeh.antennapod.net.download.serviceinterface.FeedUpdateManager; import de.danoeh.antennapod.ui.episodes.PlaybackSpeedUtils; +import de.danoeh.antennapod.ui.view.FloatingSelectMenu; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; @@ -94,7 +94,7 @@ public class QueueFragment extends Fragment implements MaterialToolbar.OnMenuIte private SwipeActions swipeActions; private SharedPreferences prefs; - private SpeedDialView speedDialView; + private FloatingSelectMenu floatingSelectMenu; private ProgressBar progressBar; @Override @@ -436,30 +436,17 @@ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMen emptyView.setMessage(R.string.no_items_label); emptyView.updateAdapter(recyclerAdapter); - speedDialView = root.findViewById(R.id.fabSD); - speedDialView.setOverlayLayout(root.findViewById(R.id.fabSDOverlay)); - speedDialView.inflate(R.menu.episodes_apply_action_speeddial); - speedDialView.removeActionItemById(R.id.mark_read_batch); - speedDialView.removeActionItemById(R.id.mark_unread_batch); - speedDialView.removeActionItemById(R.id.add_to_queue_batch); - speedDialView.removeActionItemById(R.id.remove_all_inbox_item); - speedDialView.setOnChangeListener(new SpeedDialView.OnChangeListener() { - @Override - public boolean onMainActionSelected() { + floatingSelectMenu = root.findViewById(R.id.floatingSelectMenu); + floatingSelectMenu.inflate(R.menu.episodes_apply_action_speeddial); + floatingSelectMenu.getMenu().findItem(R.id.add_to_queue_batch).setVisible(false); + floatingSelectMenu.getMenu().findItem(R.id.remove_from_inbox_batch).setVisible(false); + floatingSelectMenu.setOnMenuItemClickListener(menuItem -> { + if (recyclerAdapter.getSelectedCount() == 0) { + ((MainActivity) getActivity()).showSnackbarAbovePlayer(R.string.no_items_selected, + Snackbar.LENGTH_SHORT); return false; } - - @Override - public void onToggleChanged(boolean open) { - if (open && recyclerAdapter.getSelectedCount() == 0) { - ((MainActivity) getActivity()).showSnackbarAbovePlayer(R.string.no_items_selected, - Snackbar.LENGTH_SHORT); - speedDialView.close(); - } - } - }); - speedDialView.setOnActionSelectedListener(actionItem -> { - new EpisodeMultiSelectActionHandler(((MainActivity) getActivity()), actionItem.getId()) + new EpisodeMultiSelectActionHandler(((MainActivity) getActivity()), menuItem.getItemId()) .handleAction(recyclerAdapter.getSelectedItems()); recyclerAdapter.endSelectMode(); return true; @@ -528,15 +515,20 @@ private void loadItems(final boolean restoreScrollPosition) { @Override public void onStartSelectMode() { swipeActions.detach(); - speedDialView.setVisibility(View.VISIBLE); + floatingSelectMenu.setVisibility(View.VISIBLE); + recyclerView.setPadding(recyclerView.getPaddingLeft(), recyclerView.getPaddingTop(), + recyclerView.getPaddingRight(), + (int) getResources().getDimension(R.dimen.floating_select_menu_height)); refreshToolbarState(); refreshInfoBar(); } @Override public void onEndSelectMode() { - speedDialView.close(); - speedDialView.setVisibility(View.GONE); + floatingSelectMenu.setVisibility(View.GONE); + recyclerView.setPadding(recyclerView.getPaddingLeft(), recyclerView.getPaddingTop(), + recyclerView.getPaddingRight(), 0); + infoBar.setVisibility(View.VISIBLE); swipeActions.attachTo(recyclerView); refreshInfoBar(); } diff --git a/app/src/main/java/de/danoeh/antennapod/ui/screen/subscriptions/SubscriptionFragment.java b/app/src/main/java/de/danoeh/antennapod/ui/screen/subscriptions/SubscriptionFragment.java index 793a62eded..c7bc579b1c 100644 --- a/app/src/main/java/de/danoeh/antennapod/ui/screen/subscriptions/SubscriptionFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/ui/screen/subscriptions/SubscriptionFragment.java @@ -20,11 +20,11 @@ import com.google.android.material.appbar.MaterialToolbar; import com.google.android.material.floatingactionbutton.FloatingActionButton; -import com.leinardi.android.speeddial.SpeedDialView; import de.danoeh.antennapod.ui.screen.AddFeedFragment; import de.danoeh.antennapod.ui.screen.SearchFragment; import de.danoeh.antennapod.net.download.serviceinterface.FeedUpdateManager; +import de.danoeh.antennapod.ui.view.FloatingSelectMenu; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; @@ -86,7 +86,7 @@ public class SubscriptionFragment extends Fragment private SharedPreferences prefs; private FloatingActionButton subscriptionAddButton; - private SpeedDialView speedDialView; + private FloatingSelectMenu floatingSelectMenu; private RecyclerView.ItemDecoration itemDecoration; private List listItems; @@ -168,12 +168,11 @@ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMen swipeRefreshLayout.setDistanceToTriggerSync(getResources().getInteger(R.integer.swipe_refresh_distance)); swipeRefreshLayout.setOnRefreshListener(() -> FeedUpdateManager.getInstance().runOnceOrAsk(requireContext())); - speedDialView = root.findViewById(R.id.fabSD); - speedDialView.setOverlayLayout(root.findViewById(R.id.fabSDOverlay)); - speedDialView.inflate(R.menu.nav_feed_action_speeddial); - speedDialView.setOnActionSelectedListener(actionItem -> { + floatingSelectMenu = root.findViewById(R.id.floatingSelectMenu); + floatingSelectMenu.inflate(R.menu.nav_feed_action_speeddial); + floatingSelectMenu.setOnMenuItemClickListener(menuItem -> { new FeedMultiSelectActionHandler((MainActivity) getActivity(), subscriptionAdapter.getSelectedItems()) - .handleAction(actionItem.getId()); + .handleAction(menuItem.getItemId()); return true; }); @@ -370,8 +369,7 @@ public void onUnreadItemsChanged(UnreadItemsUpdateEvent event) { @Override public void onEndSelectMode() { - speedDialView.close(); - speedDialView.setVisibility(View.GONE); + floatingSelectMenu.setVisibility(View.GONE); subscriptionAddButton.setVisibility(View.VISIBLE); subscriptionAdapter.setItems(listItems); updateFilterVisibility(); @@ -386,7 +384,7 @@ public void onStartSelectMode() { } } subscriptionAdapter.setItems(feedsOnly); - speedDialView.setVisibility(View.VISIBLE); + floatingSelectMenu.setVisibility(View.VISIBLE); subscriptionAddButton.setVisibility(View.GONE); updateFilterVisibility(); } diff --git a/app/src/main/java/de/danoeh/antennapod/ui/view/FloatingSelectMenu.java b/app/src/main/java/de/danoeh/antennapod/ui/view/FloatingSelectMenu.java new file mode 100644 index 0000000000..02f0192b76 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/ui/view/FloatingSelectMenu.java @@ -0,0 +1,90 @@ +package de.danoeh.antennapod.ui.view; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.FrameLayout; +import androidx.annotation.MenuRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.PopupMenu; +import com.google.android.material.elevation.SurfaceColors; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.databinding.FloatingSelectMenuBinding; +import de.danoeh.antennapod.databinding.FloatingSelectMenuItemBinding; + +public class FloatingSelectMenu extends FrameLayout { + private FloatingSelectMenuBinding viewBinding; + private Menu menu; + private MenuItem.OnMenuItemClickListener menuItemClickListener; + + public FloatingSelectMenu(@NonNull Context context) { + super(context); + setup(); + } + + public FloatingSelectMenu(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + setup(); + } + + public FloatingSelectMenu(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + setup(); + } + + private void setup() { + viewBinding = FloatingSelectMenuBinding.bind( + View.inflate(getContext(), R.layout.floating_select_menu, null)); + viewBinding.card.setCardBackgroundColor( + SurfaceColors.getColorForElevation(getContext(), 8 * getResources().getDisplayMetrics().density)); + addView(viewBinding.getRoot()); + setVisibility(View.GONE); + } + + public void inflate(@MenuRes int menuRes) { + PopupMenu popupMenu = new PopupMenu(getContext(), new View(getContext())); + popupMenu.inflate(menuRes); + menu = popupMenu.getMenu(); + updateItemVisibility(); + } + + public void updateItemVisibility() { + viewBinding.selectContainer.removeAllViews(); + if (menu == null) { + return; + } + for (int i = 0; i < menu.size(); i++) { + MenuItem item = menu.getItem(i); + if (!item.isVisible()) { + continue; + } + FloatingSelectMenuItemBinding itemBinding = FloatingSelectMenuItemBinding.bind( + View.inflate(getContext(), R.layout.floating_select_menu_item, null)); + itemBinding.titleLabel.setText(item.getTitle()); + itemBinding.icon.setImageDrawable(item.getIcon()); + itemBinding.getRoot().setOnClickListener(view -> menuItemClickListener.onMenuItemClick(item)); + viewBinding.selectContainer.addView(itemBinding.getRoot()); + } + } + + public Menu getMenu() { + return menu; + } + + public void setOnMenuItemClickListener(MenuItem.OnMenuItemClickListener listener) { + this.menuItemClickListener = listener; + } + + @Override + public void setVisibility(int visibility) { + if (getVisibility() != View.VISIBLE && visibility == View.VISIBLE) { + announceForAccessibility(getContext().getString(R.string.multi_select_started_talkback)); + } + super.setVisibility(visibility); + viewBinding.scrollView.scrollTo(0, 0); + updateItemVisibility(); + } +} diff --git a/app/src/main/res/layout/episodes_list_fragment.xml b/app/src/main/res/layout/episodes_list_fragment.xml index d408f531ca..b88e677372 100644 --- a/app/src/main/res/layout/episodes_list_fragment.xml +++ b/app/src/main/res/layout/episodes_list_fragment.xml @@ -58,7 +58,10 @@ android:layout_centerInParent="true" tools:background="@android:color/holo_red_light" /> - + diff --git a/app/src/main/res/layout/feed_item_list_fragment.xml b/app/src/main/res/layout/feed_item_list_fragment.xml index 23cbe0f05f..f236ee90a6 100644 --- a/app/src/main/res/layout/feed_item_list_fragment.xml +++ b/app/src/main/res/layout/feed_item_list_fragment.xml @@ -76,7 +76,10 @@ android:visibility="gone" layout="@layout/more_content_list_footer" /> - + diff --git a/app/src/main/res/layout/floating_select_menu.xml b/app/src/main/res/layout/floating_select_menu.xml new file mode 100644 index 0000000000..4a72de0f7a --- /dev/null +++ b/app/src/main/res/layout/floating_select_menu.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/floating_select_menu_item.xml b/app/src/main/res/layout/floating_select_menu_item.xml new file mode 100644 index 0000000000..e84acde586 --- /dev/null +++ b/app/src/main/res/layout/floating_select_menu_item.xml @@ -0,0 +1,28 @@ + + + + + + + + diff --git a/app/src/main/res/layout/fragment_subscriptions.xml b/app/src/main/res/layout/fragment_subscriptions.xml index dbf0fc11d3..dc8b24e41d 100644 --- a/app/src/main/res/layout/fragment_subscriptions.xml +++ b/app/src/main/res/layout/fragment_subscriptions.xml @@ -84,7 +84,10 @@ android:contentDescription="@string/add_feed_label" app:srcCompat="@drawable/ic_add" /> - + diff --git a/app/src/main/res/layout/multi_select_speed_dial.xml b/app/src/main/res/layout/multi_select_speed_dial.xml deleted file mode 100644 index 0451471bc5..0000000000 --- a/app/src/main/res/layout/multi_select_speed_dial.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/queue_fragment.xml b/app/src/main/res/layout/queue_fragment.xml index 801c9e93ac..f42df9f9b8 100644 --- a/app/src/main/res/layout/queue_fragment.xml +++ b/app/src/main/res/layout/queue_fragment.xml @@ -57,7 +57,10 @@ android:indeterminateOnly="true" android:visibility="gone" /> - + diff --git a/app/src/main/res/layout/search_fragment.xml b/app/src/main/res/layout/search_fragment.xml index 59853a9ee8..e398bb83d9 100644 --- a/app/src/main/res/layout/search_fragment.xml +++ b/app/src/main/res/layout/search_fragment.xml @@ -61,7 +61,10 @@ android:paddingTop="12dp" android:paddingHorizontal="@dimen/additional_horizontal_spacing" /> - + diff --git a/app/src/main/res/layout/simple_list_fragment.xml b/app/src/main/res/layout/simple_list_fragment.xml index 273a088465..3972e419e5 100644 --- a/app/src/main/res/layout/simple_list_fragment.xml +++ b/app/src/main/res/layout/simple_list_fragment.xml @@ -36,7 +36,10 @@ android:indeterminateOnly="true" android:visibility="gone" /> - + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 1020df1354..6e468096e6 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -3,4 +3,5 @@ 0dp 16dp + 112dp \ No newline at end of file diff --git a/ui/common/src/main/res/drawable/ic_fab_edit.xml b/ui/common/src/main/res/drawable/ic_fab_edit.xml deleted file mode 100644 index 6fd80e29fd..0000000000 --- a/ui/common/src/main/res/drawable/ic_fab_edit.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/ui/i18n/src/main/res/values/strings.xml b/ui/i18n/src/main/res/values/strings.xml index 884e99ac94..a840a1abfe 100644 --- a/ui/i18n/src/main/res/values/strings.xml +++ b/ui/i18n/src/main/res/values/strings.xml @@ -194,6 +194,7 @@ Multi select Select all above Select all below + Multi select actions shown at the bottom Filtered Last refresh failed. Tap to view details. Open podcast @@ -699,7 +700,6 @@ Load next page Position: %1$s Remaining time: %1$s - Apply action Play chapter Previous chapter Next chapter diff --git a/ui/preferences/src/main/assets/licenses.xml b/ui/preferences/src/main/assets/licenses.xml index e6745e4f5e..804073dd65 100644 --- a/ui/preferences/src/main/assets/licenses.xml +++ b/ui/preferences/src/main/assets/licenses.xml @@ -42,12 +42,6 @@ website="https://github.com/google/ExoPlayer" license="Apache 2.0" licenseText="LICENSE_APACHE-2.0.txt" /> -