From 6cbc58ba78caf311a706823a8784ef4453743024 Mon Sep 17 00:00:00 2001 From: ByteHamster Date: Tue, 10 Sep 2024 20:59:18 +0200 Subject: [PATCH] Bottom navigation (#7176) --- .../antennapod/ui/NavigationDrawerTest.java | 1 + .../antennapod/activity/MainActivity.java | 200 +++++++++++++++--- .../HorizontalItemViewHolder.java | 4 +- .../drawer/BottomNavigationMoreAdapter.java | 35 +++ .../drawer/DrawerPreferencesDialog.java | 4 +- .../ui/screen/drawer/NavListAdapter.java | 57 +---- .../ui/screen/drawer/NavigationNames.java | 106 ++++++++++ .../playback/audio/AudioPlayerFragment.java | 3 - .../ui/view/LiftOnScrollListener.java | 7 +- app/src/main/res/layout-sw720dp/main.xml | 87 +++++--- app/src/main/res/layout/addfeed.xml | 3 +- .../main/res/layout/audioplayer_fragment.xml | 5 +- .../bottom_navigation_more_listitem.xml | 33 +++ .../res/layout/horizontal_itemlist_item.xml | 7 +- app/src/main/res/layout/main.xml | 72 ++++--- app/src/main/res/values/ids.xml | 12 ++ build.gradle | 2 +- .../storage/preferences/UserPreferences.java | 9 + .../src/main/res/drawable/dots_vertical.xml | 13 ++ ui/common/src/main/res/drawable/ic_pencil.xml | 13 ++ ui/common/src/main/res/values-v23/styles.xml | 2 - ui/common/src/main/res/values-v27/styles.xml | 2 - ui/common/src/main/res/values/colors.xml | 3 +- ui/common/src/main/res/values/styles.xml | 7 +- ui/i18n/src/main/res/values/strings.xml | 6 +- .../res/xml/preferences_user_interface.xml | 10 +- 26 files changed, 526 insertions(+), 177 deletions(-) create mode 100644 app/src/main/java/de/danoeh/antennapod/ui/screen/drawer/BottomNavigationMoreAdapter.java create mode 100644 app/src/main/java/de/danoeh/antennapod/ui/screen/drawer/NavigationNames.java create mode 100644 app/src/main/res/layout/bottom_navigation_more_listitem.xml create mode 100644 ui/common/src/main/res/drawable/dots_vertical.xml create mode 100644 ui/common/src/main/res/drawable/ic_pencil.xml diff --git a/app/src/androidTest/java/de/test/antennapod/ui/NavigationDrawerTest.java b/app/src/androidTest/java/de/test/antennapod/ui/NavigationDrawerTest.java index 57385f62a..d9e1db18d 100644 --- a/app/src/androidTest/java/de/test/antennapod/ui/NavigationDrawerTest.java +++ b/app/src/androidTest/java/de/test/antennapod/ui/NavigationDrawerTest.java @@ -52,6 +52,7 @@ public class NavigationDrawerTest { EspressoTestUtils.clearPreferences(); EspressoTestUtils.clearDatabase(); + UserPreferences.setBottomNavigationEnabled(false); } @After diff --git a/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java index 15fa614a4..79ee013bf 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java @@ -10,14 +10,19 @@ import android.os.Build; import android.os.Bundle; import android.util.DisplayMetrics; import android.util.Log; +import android.view.Gravity; import android.view.KeyEvent; +import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.EditText; +import android.widget.RelativeLayout; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBarDrawerToggle; +import androidx.appcompat.view.menu.MenuBuilder; +import androidx.appcompat.widget.ListPopupWindow; import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; import androidx.core.view.WindowCompat; @@ -32,48 +37,62 @@ import androidx.work.WorkInfo; import androidx.work.WorkManager; import com.bumptech.glide.Glide; import com.google.android.material.appbar.MaterialToolbar; +import com.google.android.material.bottomnavigation.BottomNavigationView; import com.google.android.material.bottomsheet.BottomSheetBehavior; +import com.google.android.material.navigation.NavigationBarView; import com.google.android.material.snackbar.Snackbar; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.net.download.service.feed.FeedUpdateManagerImpl; -import de.danoeh.antennapod.net.download.serviceinterface.FeedUpdateManager; -import de.danoeh.antennapod.net.sync.serviceinterface.SynchronizationQueueSink; -import de.danoeh.antennapod.ui.appstartintent.MediaButtonStarter; -import de.danoeh.antennapod.ui.common.ThemeSwitcher; -import de.danoeh.antennapod.ui.screen.rating.RatingDialogManager; import de.danoeh.antennapod.event.EpisodeDownloadEvent; import de.danoeh.antennapod.event.FeedUpdateRunningEvent; import de.danoeh.antennapod.event.MessageEvent; -import de.danoeh.antennapod.ui.screen.AddFeedFragment; -import de.danoeh.antennapod.ui.screen.AllEpisodesFragment; -import de.danoeh.antennapod.ui.screen.playback.audio.AudioPlayerFragment; -import de.danoeh.antennapod.ui.screen.download.CompletedDownloadsFragment; -import de.danoeh.antennapod.ui.screen.download.DownloadLogFragment; -import de.danoeh.antennapod.ui.screen.feed.FeedItemlistFragment; -import de.danoeh.antennapod.ui.screen.InboxFragment; -import de.danoeh.antennapod.ui.screen.drawer.NavDrawerFragment; -import de.danoeh.antennapod.ui.screen.PlaybackHistoryFragment; -import de.danoeh.antennapod.ui.screen.queue.QueueFragment; -import de.danoeh.antennapod.ui.screen.SearchFragment; -import de.danoeh.antennapod.ui.screen.subscriptions.SubscriptionFragment; -import de.danoeh.antennapod.ui.TransitionEffect; import de.danoeh.antennapod.model.download.DownloadStatus; +import de.danoeh.antennapod.model.feed.FeedItemFilter; +import de.danoeh.antennapod.net.download.service.feed.FeedUpdateManagerImpl; import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterface; +import de.danoeh.antennapod.net.download.serviceinterface.FeedUpdateManager; +import de.danoeh.antennapod.net.sync.serviceinterface.SynchronizationQueueSink; import de.danoeh.antennapod.playback.cast.CastEnabledActivity; +import de.danoeh.antennapod.storage.database.DBReader; import de.danoeh.antennapod.storage.importexport.AutomaticDatabaseExportWorker; import de.danoeh.antennapod.storage.preferences.UserPreferences; +import de.danoeh.antennapod.ui.TransitionEffect; import de.danoeh.antennapod.ui.appstartintent.MainActivityStarter; +import de.danoeh.antennapod.ui.appstartintent.MediaButtonStarter; +import de.danoeh.antennapod.ui.common.ThemeSwitcher; import de.danoeh.antennapod.ui.common.ThemeUtils; import de.danoeh.antennapod.ui.discovery.DiscoveryFragment; +import de.danoeh.antennapod.ui.screen.AddFeedFragment; +import de.danoeh.antennapod.ui.screen.AllEpisodesFragment; +import de.danoeh.antennapod.ui.screen.InboxFragment; +import de.danoeh.antennapod.ui.screen.PlaybackHistoryFragment; +import de.danoeh.antennapod.ui.screen.SearchFragment; +import de.danoeh.antennapod.ui.screen.download.CompletedDownloadsFragment; +import de.danoeh.antennapod.ui.screen.download.DownloadLogFragment; +import de.danoeh.antennapod.ui.screen.drawer.BottomNavigationMoreAdapter; +import de.danoeh.antennapod.ui.screen.drawer.DrawerPreferencesDialog; +import de.danoeh.antennapod.ui.screen.drawer.NavDrawerFragment; +import de.danoeh.antennapod.ui.screen.drawer.NavListAdapter; +import de.danoeh.antennapod.ui.screen.drawer.NavigationNames; +import de.danoeh.antennapod.ui.screen.feed.FeedItemlistFragment; import de.danoeh.antennapod.ui.screen.home.HomeFragment; +import de.danoeh.antennapod.ui.screen.playback.audio.AudioPlayerFragment; +import de.danoeh.antennapod.ui.screen.preferences.PreferenceActivity; +import de.danoeh.antennapod.ui.screen.queue.QueueFragment; +import de.danoeh.antennapod.ui.screen.rating.RatingDialogManager; +import de.danoeh.antennapod.ui.screen.subscriptions.SubscriptionFragment; import de.danoeh.antennapod.ui.view.LockableBottomSheetBehavior; +import io.reactivex.Observable; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.schedulers.Schedulers; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.Validate; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; /** @@ -94,11 +113,12 @@ public class MainActivity extends CastEnabledActivity { private @Nullable DrawerLayout drawerLayout; private @Nullable ActionBarDrawerToggle drawerToggle; + private BottomNavigationView bottomNavigationView; private View navDrawer; private LockableBottomSheetBehavior sheetBehavior; private RecyclerView.RecycledViewPool recycledViewPool = new RecyclerView.RecycledViewPool(); private int lastTheme = 0; - private Insets navigationBarInsets = Insets.NONE; + private Insets systemBarInsets = Insets.NONE; @NonNull public static Intent getIntentToOpenFeed(@NonNull Context context, long feedId) { @@ -122,11 +142,24 @@ public class MainActivity extends CastEnabledActivity { drawerLayout = findViewById(R.id.drawer_layout); navDrawer = findViewById(R.id.navDrawerFragment); - setNavDrawerSize(); + bottomNavigationView = findViewById(R.id.bottomNavigationView); + if (UserPreferences.isBottomNavigationEnabled()) { + buildBottomNavigationMenu(); + if (drawerLayout == null) { // Tablet mode + navDrawer.setVisibility(View.GONE); + } else { + drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED); + } + drawerLayout = null; + } else { + bottomNavigationView.setVisibility(View.GONE); + bottomNavigationView = null; + setNavDrawerSize(); + } // Consume navigation bar insets - we apply them in setPlayerVisible() ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main_view), (v, insets) -> { - navigationBarInsets = insets.getInsets(WindowInsetsCompat.Type.navigationBars()); + systemBarInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()); updateInsets(); return new WindowInsetsCompat.Builder(insets) .setInsets(WindowInsetsCompat.Type.navigationBars(), Insets.NONE) @@ -320,28 +353,35 @@ public class MainActivity extends CastEnabledActivity { private void updateInsets() { setPlayerVisible(findViewById(R.id.audioplayerFragment).getVisibility() == View.VISIBLE); - int playerHeight = (int) getResources().getDimension(R.dimen.external_player_height); - sheetBehavior.setPeekHeight(playerHeight + navigationBarInsets.bottom); } public void setPlayerVisible(boolean visible) { getBottomSheet().setLocked(!visible); + findViewById(R.id.audioplayerFragment).setVisibility(visible ? View.VISIBLE : View.GONE); if (visible) { bottomSheetCallback.onStateChanged(null, getBottomSheet().getState()); // Update toolbar visibility } else { getBottomSheet().setState(BottomSheetBehavior.STATE_COLLAPSED); } - FragmentContainerView mainView = findViewById(R.id.main_view); - ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) mainView.getLayoutParams(); + View bottomPaddingView = findViewById(R.id.bottom_padding); + ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) bottomPaddingView.getLayoutParams(); + params.height = systemBarInsets.bottom; + bottomPaddingView.setLayoutParams(params); + int externalPlayerHeight = (int) getResources().getDimension(R.dimen.external_player_height); - params.setMargins(navigationBarInsets.left, 0, navigationBarInsets.right, - navigationBarInsets.bottom + (visible ? externalPlayerHeight : 0)); + FragmentContainerView mainView = findViewById(R.id.main_content_view); + params = (ViewGroup.MarginLayoutParams) mainView.getLayoutParams(); + params.setMargins(systemBarInsets.left, 0, systemBarInsets.right, (visible ? externalPlayerHeight : 0)); mainView.setLayoutParams(params); + sheetBehavior.setPeekHeight(externalPlayerHeight); + sheetBehavior.setGestureInsetBottomIgnored(true); + FragmentContainerView playerView = findViewById(R.id.playerFragment); ViewGroup.MarginLayoutParams playerParams = (ViewGroup.MarginLayoutParams) playerView.getLayoutParams(); - playerParams.setMargins(navigationBarInsets.left, 0, navigationBarInsets.right, 0); + playerParams.setMargins(systemBarInsets.left, 0, systemBarInsets.right, 0); playerView.setLayoutParams(playerParams); - findViewById(R.id.audioplayerFragment).setVisibility(visible ? View.VISIBLE : View.GONE); + RelativeLayout playerContent = findViewById(R.id.playerContent); + playerContent.setPadding(systemBarInsets.left, systemBarInsets.top, systemBarInsets.right, 0); } public RecyclerView.RecycledViewPool getRecycledViewPool() { @@ -393,6 +433,15 @@ public class MainActivity extends CastEnabledActivity { public void loadFragment(String tag, Bundle args) { NavDrawerFragment.saveLastNavFragment(this, tag); + if (bottomNavigationView != null) { + int bottomSelectedItem = NavigationNames.getBottomNavigationItemId(tag); + if (bottomNavigationView.getMenu().findItem(bottomSelectedItem) == null) { + bottomSelectedItem = R.id.bottom_navigation_more; + } + bottomNavigationView.setOnItemSelectedListener(null); + bottomNavigationView.setSelectedItemId(bottomSelectedItem); + bottomNavigationView.setOnItemSelectedListener(bottomItemSelectedListener); + } loadFragment(createFragmentInstance(tag, args)); } @@ -412,7 +461,7 @@ public class MainActivity extends CastEnabledActivity { fragmentManager.popBackStack(); } FragmentTransaction t = fragmentManager.beginTransaction(); - t.replace(R.id.main_view, fragment, MAIN_FRAGMENT_TAG); + t.replace(R.id.main_content_view, fragment, MAIN_FRAGMENT_TAG); fragmentManager.popBackStack(); // TODO: we have to allow state loss here // since this function can get called from an AsyncTask which @@ -443,7 +492,7 @@ public class MainActivity extends CastEnabledActivity { transaction .hide(getSupportFragmentManager().findFragmentByTag(MAIN_FRAGMENT_TAG)) - .add(R.id.main_view, fragment, MAIN_FRAGMENT_TAG) + .add(R.id.main_content_view, fragment, MAIN_FRAGMENT_TAG) .addToBackStack(null) .commit(); } @@ -452,6 +501,85 @@ public class MainActivity extends CastEnabledActivity { loadChildFragment(fragment, TransitionEffect.NONE); } + private void buildBottomNavigationMenu() { + List drawerItems = UserPreferences.getVisibleDrawerItemOrder(); + drawerItems.remove(NavListAdapter.SUBSCRIPTION_LIST_TAG); + + Menu menu = bottomNavigationView.getMenu(); + menu.clear(); + for (int i = 0; i < drawerItems.size() && i < bottomNavigationView.getMaxItemCount() - 1; i++) { + String tag = drawerItems.get(i); + MenuItem item = menu.add(0, NavigationNames.getBottomNavigationItemId(tag), + 0, getString(NavigationNames.getLabel(tag))); + item.setIcon(NavigationNames.getDrawable(tag)); + } + MenuItem moreItem = menu.add(0, R.id.bottom_navigation_more, 0, getString(R.string.searchpreference_more)); + moreItem.setIcon(R.drawable.dots_vertical); + bottomNavigationView.setOnItemSelectedListener(bottomItemSelectedListener); + + if (bottomNavigationView.getMenu().findItem(R.id.bottom_navigation_inbox) != null) { + Observable.fromCallable(() -> DBReader.getTotalEpisodeCount(new FeedItemFilter(FeedItemFilter.NEW))) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(result -> + bottomNavigationView.getOrCreateBadge(R.id.bottom_navigation_inbox).setNumber(result), + error -> Log.e(TAG, Log.getStackTraceString(error))); + } + } + + private final NavigationBarView.OnItemSelectedListener bottomItemSelectedListener = item -> { + sheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED); + if (item.getItemId() == R.id.bottom_navigation_more) { + showBottomNavigationMorePopup(); + return false; + } else { + loadFragment(NavigationNames.getBottomNavigationFragmentTag(item.getItemId()), null); + return true; + } + }; + + private void showBottomNavigationMorePopup() { + List drawerItems = UserPreferences.getVisibleDrawerItemOrder(); + drawerItems.remove(NavListAdapter.SUBSCRIPTION_LIST_TAG); + + final List popupMenuItems = new ArrayList<>(); + for (int i = bottomNavigationView.getMaxItemCount() - 1; i < drawerItems.size(); i++) { + String tag = drawerItems.get(i); + MenuItem item = new MenuBuilder(this).add(0, NavigationNames.getBottomNavigationItemId(tag), + 0, getString(NavigationNames.getLabel(tag))); + item.setIcon(NavigationNames.getDrawable(tag)); + popupMenuItems.add(item); + } + MenuItem customizeItem = new MenuBuilder(this).add(0, R.id.bottom_navigation_settings, + 0, getString(R.string.pref_nav_drawer_items_title)); + customizeItem.setIcon(R.drawable.ic_pencil); + popupMenuItems.add(customizeItem); + + MenuItem settingsItem = new MenuBuilder(this).add(0, R.id.bottom_navigation_settings, + 0, getString(R.string.settings_label)); + settingsItem.setIcon(R.drawable.ic_settings); + popupMenuItems.add(settingsItem); + + final ListPopupWindow listPopupWindow = new ListPopupWindow(this); + listPopupWindow.setWidth((int) (250 * getResources().getDisplayMetrics().density)); + listPopupWindow.setAnchorView(bottomNavigationView); + listPopupWindow.setAdapter(new BottomNavigationMoreAdapter(this, popupMenuItems)); + listPopupWindow.setOnItemClickListener((parent, view, position, id) -> { + if (position == popupMenuItems.size() - 1) { + startActivity(new Intent(this, PreferenceActivity.class)); + } else if (position == popupMenuItems.size() - 2) { + new DrawerPreferencesDialog(this, this::buildBottomNavigationMenu).show(); + } else { + loadFragment(NavigationNames.getBottomNavigationFragmentTag( + popupMenuItems.get(position).getItemId()), null); + } + listPopupWindow.dismiss(); + }); + listPopupWindow.setDropDownGravity(Gravity.END | Gravity.BOTTOM); + listPopupWindow.setModal(true); + listPopupWindow.show(); + } + @Override protected void onPostCreate(Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); @@ -507,7 +635,9 @@ public class MainActivity extends CastEnabledActivity { super.onResume(); handleNavIntent(); - if (lastTheme != ThemeSwitcher.getNoTitleTheme(this)) { + boolean hasBottomNavigation = bottomNavigationView != null; + if (lastTheme != ThemeSwitcher.getNoTitleTheme(this) + || hasBottomNavigation != UserPreferences.isBottomNavigationEnabled()) { finish(); startActivity(new Intent(this, MainActivity.class)); } @@ -566,7 +696,7 @@ public class MainActivity extends CastEnabledActivity { String toPage = UserPreferences.getDefaultPage(); if (NavDrawerFragment.getLastNavFragment(this).equals(toPage) || UserPreferences.DEFAULT_PAGE_REMEMBER.equals(toPage)) { - if (UserPreferences.backButtonOpensDrawer() && drawerLayout != null) { + if (UserPreferences.backButtonOpensDrawer() && drawerLayout != null && bottomNavigationView == null) { drawerLayout.openDrawer(navDrawer); } else { super.onBackPressed(); @@ -644,7 +774,7 @@ public class MainActivity extends CastEnabledActivity { public Snackbar showSnackbarAbovePlayer(CharSequence text, int duration) { Snackbar s; if (getBottomSheet().getState() == BottomSheetBehavior.STATE_COLLAPSED) { - s = Snackbar.make(findViewById(R.id.main_view), text, duration); + s = Snackbar.make(findViewById(R.id.main_content_view), text, duration); if (findViewById(R.id.audioplayerFragment).getVisibility() == View.VISIBLE) { s.setAnchorView(findViewById(R.id.audioplayerFragment)); } diff --git a/app/src/main/java/de/danoeh/antennapod/ui/episodeslist/HorizontalItemViewHolder.java b/app/src/main/java/de/danoeh/antennapod/ui/episodeslist/HorizontalItemViewHolder.java index c297725b0..d6942fdbd 100644 --- a/app/src/main/java/de/danoeh/antennapod/ui/episodeslist/HorizontalItemViewHolder.java +++ b/app/src/main/java/de/danoeh/antennapod/ui/episodeslist/HorizontalItemViewHolder.java @@ -8,7 +8,6 @@ import android.widget.ProgressBar; import android.widget.TextView; import androidx.cardview.widget.CardView; import androidx.recyclerview.widget.RecyclerView; -import com.google.android.material.elevation.SurfaceColors; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.ui.CoverLoader; @@ -56,8 +55,7 @@ public class HorizontalItemViewHolder extends RecyclerView.ViewHolder { this.item = item; card.setAlpha(1.0f); - float density = activity.getResources().getDisplayMetrics().density; - card.setCardBackgroundColor(SurfaceColors.getColorForElevation(activity, 1 * density)); + card.setCardBackgroundColor(ThemeUtils.getColorFromAttr(activity, R.attr.colorSurfaceContainer)); new CoverLoader() .withUri(ImageResourceUtils.getEpisodeListImageLocation(item)) .withFallbackUri(item.getFeed().getImageUrl()) diff --git a/app/src/main/java/de/danoeh/antennapod/ui/screen/drawer/BottomNavigationMoreAdapter.java b/app/src/main/java/de/danoeh/antennapod/ui/screen/drawer/BottomNavigationMoreAdapter.java new file mode 100644 index 000000000..f0553f0a9 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/ui/screen/drawer/BottomNavigationMoreAdapter.java @@ -0,0 +1,35 @@ +package de.danoeh.antennapod.ui.screen.drawer; + +import android.content.Context; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.TextView; +import de.danoeh.antennapod.R; + +import java.util.List; + +public class BottomNavigationMoreAdapter extends ArrayAdapter { + private final Context context; + private final List listItems; + + public BottomNavigationMoreAdapter(Context context, List listItems) { + super(context, R.layout.bottom_navigation_more_listitem, listItems); + this.context = context; + this.listItems = listItems; + } + + @Override + public View getView(int position, View view, ViewGroup parent) { + if (view == null) { + view = View.inflate(context, R.layout.bottom_navigation_more_listitem, null); + } + + MenuItem item = listItems.get(position); + ((ImageView) view.findViewById(R.id.coverImage)).setImageDrawable(item.getIcon()); + ((TextView) view.findViewById(R.id.titleLabel)).setText(item.getTitle()); + return view; + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/ui/screen/drawer/DrawerPreferencesDialog.java b/app/src/main/java/de/danoeh/antennapod/ui/screen/drawer/DrawerPreferencesDialog.java index 12bf673f6..d738d8c8c 100644 --- a/app/src/main/java/de/danoeh/antennapod/ui/screen/drawer/DrawerPreferencesDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/ui/screen/drawer/DrawerPreferencesDialog.java @@ -35,7 +35,7 @@ public class DrawerPreferencesDialog extends ReorderDialog { final List drawerItemOrder = UserPreferences.getVisibleDrawerItemOrder(); for (String tag : drawerItemOrder) { settingsDialogItems.add(new ReorderDialogItem(ReorderDialogItem.ViewType.Section, - tag, context.getString(NavListAdapter.getLabel(tag)))); + tag, context.getString(NavigationNames.getLabel(tag)))); } settingsDialogItems.add(new ReorderDialogItem(ReorderDialogItem.ViewType.Header, @@ -44,7 +44,7 @@ public class DrawerPreferencesDialog extends ReorderDialog { final List hiddenDrawerItems = UserPreferences.getHiddenDrawerItems(); for (String sectionTag : hiddenDrawerItems) { settingsDialogItems.add(new ReorderDialogItem(ReorderDialogItem.ViewType.Section, - sectionTag, context.getString(NavListAdapter.getLabel(sectionTag)))); + sectionTag, context.getString(NavigationNames.getLabel(sectionTag)))); } return settingsDialogItems; } diff --git a/app/src/main/java/de/danoeh/antennapod/ui/screen/drawer/NavListAdapter.java b/app/src/main/java/de/danoeh/antennapod/ui/screen/drawer/NavListAdapter.java index a4ffb7566..7eaebcdc5 100644 --- a/app/src/main/java/de/danoeh/antennapod/ui/screen/drawer/NavListAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/ui/screen/drawer/NavListAdapter.java @@ -14,7 +14,6 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.TextView; -import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; import androidx.annotation.StringRes; import androidx.preference.PreferenceManager; @@ -29,12 +28,8 @@ import de.danoeh.antennapod.model.feed.Feed; import de.danoeh.antennapod.storage.database.NavDrawerData; import de.danoeh.antennapod.storage.preferences.UserPreferences; import de.danoeh.antennapod.ui.common.ImagePlaceholder; -import de.danoeh.antennapod.ui.screen.AddFeedFragment; -import de.danoeh.antennapod.ui.screen.AllEpisodesFragment; import de.danoeh.antennapod.ui.screen.InboxFragment; -import de.danoeh.antennapod.ui.screen.PlaybackHistoryFragment; import de.danoeh.antennapod.ui.screen.download.CompletedDownloadsFragment; -import de.danoeh.antennapod.ui.screen.home.HomeFragment; import de.danoeh.antennapod.ui.screen.preferences.PreferenceActivity; import de.danoeh.antennapod.ui.screen.queue.QueueFragment; import de.danoeh.antennapod.ui.screen.subscriptions.SubscriptionFragment; @@ -100,54 +95,6 @@ public class NavListAdapter extends RecyclerView.Adapter notifyDataSetChanged(); } - public static @StringRes int getLabel(String tag) { - switch (tag) { - case HomeFragment.TAG: - return R.string.home_label; - case QueueFragment.TAG: - return R.string.queue_label; - case InboxFragment.TAG: - return R.string.inbox_label; - case AllEpisodesFragment.TAG: - return R.string.episodes_label; - case SubscriptionFragment.TAG: - return R.string.subscriptions_label; - case CompletedDownloadsFragment.TAG: - return R.string.downloads_label; - case PlaybackHistoryFragment.TAG: - return R.string.playback_history_label; - case AddFeedFragment.TAG: - return R.string.add_feed_label; - case NavListAdapter.SUBSCRIPTION_LIST_TAG: - return R.string.subscriptions_list_label; - default: - return 0; - } - } - - private static @DrawableRes int getDrawable(String tag) { - switch (tag) { - case HomeFragment.TAG: - return R.drawable.ic_home; - case QueueFragment.TAG: - return R.drawable.ic_playlist_play; - case InboxFragment.TAG: - return R.drawable.ic_inbox; - case AllEpisodesFragment.TAG: - return R.drawable.ic_feed; - case CompletedDownloadsFragment.TAG: - return R.drawable.ic_download; - case PlaybackHistoryFragment.TAG: - return R.drawable.ic_history; - case SubscriptionFragment.TAG: - return R.drawable.ic_subscriptions; - case AddFeedFragment.TAG: - return R.drawable.ic_add; - default: - return 0; - } - } - public List getFragmentTags() { return Collections.unmodifiableList(fragmentTags); } @@ -207,7 +154,7 @@ public class NavListAdapter extends RecyclerView.Adapter holder.itemView.setOnCreateContextMenuListener(null); if (viewType == VIEW_TYPE_NAV) { - bindNavView(getLabel(fragmentTags.get(position)), position, (NavHolder) holder); + bindNavView(NavigationNames.getLabel(fragmentTags.get(position)), position, (NavHolder) holder); } else if (viewType == VIEW_TYPE_SECTION_DIVIDER) { bindSectionDivider((DividerHolder) holder); } else { @@ -292,7 +239,7 @@ public class NavListAdapter extends RecyclerView.Adapter } } - holder.image.setImageResource(getDrawable(fragmentTags.get(position))); + holder.image.setImageResource(NavigationNames.getDrawable(fragmentTags.get(position))); } private void bindSectionDivider(DividerHolder holder) { diff --git a/app/src/main/java/de/danoeh/antennapod/ui/screen/drawer/NavigationNames.java b/app/src/main/java/de/danoeh/antennapod/ui/screen/drawer/NavigationNames.java new file mode 100644 index 000000000..18a2fbb6b --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/ui/screen/drawer/NavigationNames.java @@ -0,0 +1,106 @@ +package de.danoeh.antennapod.ui.screen.drawer; + +import androidx.annotation.DrawableRes; +import androidx.annotation.StringRes; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.ui.screen.AddFeedFragment; +import de.danoeh.antennapod.ui.screen.AllEpisodesFragment; +import de.danoeh.antennapod.ui.screen.InboxFragment; +import de.danoeh.antennapod.ui.screen.PlaybackHistoryFragment; +import de.danoeh.antennapod.ui.screen.download.CompletedDownloadsFragment; +import de.danoeh.antennapod.ui.screen.home.HomeFragment; +import de.danoeh.antennapod.ui.screen.queue.QueueFragment; +import de.danoeh.antennapod.ui.screen.subscriptions.SubscriptionFragment; + +public abstract class NavigationNames { + public static @DrawableRes int getDrawable(String tag) { + switch (tag) { + case HomeFragment.TAG: + return R.drawable.ic_home; + case QueueFragment.TAG: + return R.drawable.ic_playlist_play; + case InboxFragment.TAG: + return R.drawable.ic_inbox; + case AllEpisodesFragment.TAG: + return R.drawable.ic_feed; + case CompletedDownloadsFragment.TAG: + return R.drawable.ic_download; + case PlaybackHistoryFragment.TAG: + return R.drawable.ic_history; + case SubscriptionFragment.TAG: + return R.drawable.ic_subscriptions; + case AddFeedFragment.TAG: + return R.drawable.ic_add; + default: + return 0; + } + } + + public static @StringRes int getLabel(String tag) { + switch (tag) { + case HomeFragment.TAG: + return R.string.home_label; + case QueueFragment.TAG: + return R.string.queue_label; + case InboxFragment.TAG: + return R.string.inbox_label; + case AllEpisodesFragment.TAG: + return R.string.episodes_label; + case SubscriptionFragment.TAG: + return R.string.subscriptions_label; + case CompletedDownloadsFragment.TAG: + return R.string.downloads_label; + case PlaybackHistoryFragment.TAG: + return R.string.playback_history_label; + case AddFeedFragment.TAG: + return R.string.add_feed_label; + case NavListAdapter.SUBSCRIPTION_LIST_TAG: + return R.string.subscriptions_list_label; + default: + return 0; + } + } + + public static int getBottomNavigationItemId(String tag) { + switch (tag) { + case QueueFragment.TAG: + return R.id.bottom_navigation_queue; + case InboxFragment.TAG: + return R.id.bottom_navigation_inbox; + case AllEpisodesFragment.TAG: + return R.id.bottom_navigation_episodes; + case CompletedDownloadsFragment.TAG: + return R.id.bottom_navigation_downloads; + case PlaybackHistoryFragment.TAG: + return R.id.bottom_navigation_history; + case AddFeedFragment.TAG: + return R.id.bottom_navigation_addfeed; + case SubscriptionFragment.TAG: + return R.id.bottom_navigation_subscriptions; + case HomeFragment.TAG: // fall-through + default: + return R.id.bottom_navigation_home; + } + } + + public static String getBottomNavigationFragmentTag(int id) { + if (id == R.id.bottom_navigation_queue) { + return QueueFragment.TAG; + } else if (id == R.id.bottom_navigation_inbox) { + return InboxFragment.TAG; + } else if (id == R.id.bottom_navigation_episodes) { + return AllEpisodesFragment.TAG; + } else if (id == R.id.bottom_navigation_downloads) { + return CompletedDownloadsFragment.TAG; + } else if (id == R.id.bottom_navigation_history) { + return PlaybackHistoryFragment.TAG; + } else if (id == R.id.bottom_navigation_addfeed) { + return AddFeedFragment.TAG; + } else if (id == R.id.bottom_navigation_subscriptions) { + return SubscriptionFragment.TAG; + } else if (id == R.id.bottom_navigation_home) { + return HomeFragment.TAG; + } + return null; + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/ui/screen/playback/audio/AudioPlayerFragment.java b/app/src/main/java/de/danoeh/antennapod/ui/screen/playback/audio/AudioPlayerFragment.java index 634f1ecfe..5b561bd30 100644 --- a/app/src/main/java/de/danoeh/antennapod/ui/screen/playback/audio/AudioPlayerFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/ui/screen/playback/audio/AudioPlayerFragment.java @@ -23,7 +23,6 @@ import androidx.viewpager2.widget.ViewPager2; import com.google.android.material.appbar.MaterialToolbar; import com.google.android.material.bottomsheet.BottomSheetBehavior; -import com.google.android.material.elevation.SurfaceColors; import de.danoeh.antennapod.model.feed.Feed; import de.danoeh.antennapod.playback.service.PlaybackController; @@ -121,8 +120,6 @@ public class AudioPlayerFragment extends Fragment implements getChildFragmentManager().beginTransaction() .replace(R.id.playerFragment, externalPlayerFragment, ExternalPlayerFragment.TAG) .commit(); - root.findViewById(R.id.playerFragment).setBackgroundColor( - SurfaceColors.getColorForElevation(getContext(), 8 * getResources().getDisplayMetrics().density)); butPlaybackSpeed = root.findViewById(R.id.butPlaybackSpeed); txtvPlaybackSpeed = root.findViewById(R.id.txtvPlaybackSpeed); diff --git a/app/src/main/java/de/danoeh/antennapod/ui/view/LiftOnScrollListener.java b/app/src/main/java/de/danoeh/antennapod/ui/view/LiftOnScrollListener.java index e046dd43e..d328f77ed 100644 --- a/app/src/main/java/de/danoeh/antennapod/ui/view/LiftOnScrollListener.java +++ b/app/src/main/java/de/danoeh/antennapod/ui/view/LiftOnScrollListener.java @@ -6,6 +6,8 @@ import androidx.annotation.NonNull; import androidx.core.widget.NestedScrollView; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.ui.common.ThemeUtils; /** * Workaround for app:liftOnScroll flickering when in SwipeRefreshLayout @@ -16,8 +18,9 @@ public class LiftOnScrollListener extends RecyclerView.OnScrollListener private boolean animatingToScrolled = false; public LiftOnScrollListener(View appBar) { - animator = ValueAnimator.ofFloat(0, appBar.getContext().getResources().getDisplayMetrics().density * 8); - animator.addUpdateListener(animation -> appBar.setElevation((float) animation.getAnimatedValue())); + int colorLifted = ThemeUtils.getColorFromAttr(appBar.getContext(), R.attr.colorSurfaceContainer); + animator = ValueAnimator.ofArgb(colorLifted & 0x00ffffff, colorLifted); + animator.addUpdateListener(animation -> appBar.setBackgroundColor((int) animation.getAnimatedValue())); } @Override diff --git a/app/src/main/res/layout-sw720dp/main.xml b/app/src/main/res/layout-sw720dp/main.xml index e8edc260f..a9984916e 100644 --- a/app/src/main/res/layout-sw720dp/main.xml +++ b/app/src/main/res/layout-sw720dp/main.xml @@ -3,45 +3,66 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/main_view" android:layout_width="match_parent" android:layout_height="match_parent" - android:orientation="horizontal" + android:orientation="vertical" tools:viewBindingIgnore="true"> - + + + + + + + + + + + + + + + + + - - - - - - - - + android:layout_height="0dp" + android:background="?attr/colorSurfaceContainer" /> diff --git a/app/src/main/res/layout/addfeed.xml b/app/src/main/res/layout/addfeed.xml index bc42aba05..15e44f2f9 100644 --- a/app/src/main/res/layout/addfeed.xml +++ b/app/src/main/res/layout/addfeed.xml @@ -41,6 +41,7 @@ android:layout_width="match_parent" android:layout_height="56dp" android:layout_marginTop="8dp" + android:layout_marginBottom="8dp" app:cardCornerRadius="28dp" app:cardElevation="0dp"> @@ -49,7 +50,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" - android:background="?attr/colorPrimaryContainer"> + android:background="?attr/colorSurfaceContainer"> + android:layout_height="match_parent"> + + + + + + + diff --git a/app/src/main/res/layout/horizontal_itemlist_item.xml b/app/src/main/res/layout/horizontal_itemlist_item.xml index 338409647..edbf7b724 100644 --- a/app/src/main/res/layout/horizontal_itemlist_item.xml +++ b/app/src/main/res/layout/horizontal_itemlist_item.xml @@ -6,8 +6,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:clipToPadding="false" - android:padding="4dp"> + android:clipToPadding="false"> + app:cardElevation="1dp"> - - + android:layout_height="0dp" + android:layout_weight="1"> + + + + + + + + + android:layout_gravity="start" + android:orientation="vertical" /> - + - - - + android:layout_height="wrap_content" + app:labelVisibilityMode="labeled" /> - + + + diff --git a/app/src/main/res/values/ids.xml b/app/src/main/res/values/ids.xml index 545e79da7..bdcbd6c0b 100644 --- a/app/src/main/res/values/ids.xml +++ b/app/src/main/res/values/ids.xml @@ -17,4 +17,16 @@ + + + + + + + + + + + + diff --git a/build.gradle b/build.gradle index 1e9c6e946..69b9f8564 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,7 @@ project.ext { recyclerViewVersion = "1.2.1" viewPager2Version = "1.1.0-beta01" workManagerVersion = "2.7.1" - googleMaterialVersion = "1.7.0" + googleMaterialVersion = "1.12.0" // Third-party commonslangVersion = "3.6" diff --git a/storage/preferences/src/main/java/de/danoeh/antennapod/storage/preferences/UserPreferences.java b/storage/preferences/src/main/java/de/danoeh/antennapod/storage/preferences/UserPreferences.java index 771ad5632..608998c00 100644 --- a/storage/preferences/src/main/java/de/danoeh/antennapod/storage/preferences/UserPreferences.java +++ b/storage/preferences/src/main/java/de/danoeh/antennapod/storage/preferences/UserPreferences.java @@ -62,6 +62,7 @@ public abstract class UserPreferences { public static final String PREF_FILTER_FEED = "prefSubscriptionsFilter"; public static final String PREF_SUBSCRIPTION_TITLE = "prefSubscriptionTitle"; public static final String PREF_BACK_OPENS_DRAWER = "prefBackButtonOpensDrawer"; + public static final String PREF_BOTTOM_NAVIGATION = "prefBottomNavigation"; public static final String PREF_QUEUE_KEEP_SORTED = "prefQueueKeepSorted"; public static final String PREF_QUEUE_KEEP_SORTED_ORDER = "prefQueueKeepSortedOrder"; @@ -758,6 +759,14 @@ public abstract class UserPreferences { return prefs.getBoolean(PREF_BACK_OPENS_DRAWER, false); } + public static boolean isBottomNavigationEnabled() { + return prefs.getBoolean(PREF_BOTTOM_NAVIGATION, false); + } + + public static void setBottomNavigationEnabled(boolean enabled) { + prefs.edit().putBoolean(PREF_BOTTOM_NAVIGATION, enabled).apply(); + } + public static boolean timeRespectsSpeed() { return prefs.getBoolean(PREF_TIME_RESPECTS_SPEED, false); } diff --git a/ui/common/src/main/res/drawable/dots_vertical.xml b/ui/common/src/main/res/drawable/dots_vertical.xml new file mode 100644 index 000000000..4848dde4e --- /dev/null +++ b/ui/common/src/main/res/drawable/dots_vertical.xml @@ -0,0 +1,13 @@ + + + + + + diff --git a/ui/common/src/main/res/drawable/ic_pencil.xml b/ui/common/src/main/res/drawable/ic_pencil.xml new file mode 100644 index 000000000..5a4838da7 --- /dev/null +++ b/ui/common/src/main/res/drawable/ic_pencil.xml @@ -0,0 +1,13 @@ + + + + + + diff --git a/ui/common/src/main/res/values-v23/styles.xml b/ui/common/src/main/res/values-v23/styles.xml index 95740f648..9475596dc 100644 --- a/ui/common/src/main/res/values-v23/styles.xml +++ b/ui/common/src/main/res/values-v23/styles.xml @@ -3,7 +3,5 @@ diff --git a/ui/common/src/main/res/values-v27/styles.xml b/ui/common/src/main/res/values-v27/styles.xml index df4d786be..1266e901f 100644 --- a/ui/common/src/main/res/values-v27/styles.xml +++ b/ui/common/src/main/res/values-v27/styles.xml @@ -3,8 +3,6 @@ diff --git a/ui/common/src/main/res/values/colors.xml b/ui/common/src/main/res/values/colors.xml index 55ba650e7..e3a3e1451 100644 --- a/ui/common/src/main/res/values/colors.xml +++ b/ui/common/src/main/res/values/colors.xml @@ -11,14 +11,13 @@ #55333333 - #FFFFFF + #f9fcff #EFEEEE #21272b #2D3337 #22777777 #90000000 #905B5B5B - #1F000000 #0078C2 #3D8BFF diff --git a/ui/common/src/main/res/values/styles.xml b/ui/common/src/main/res/values/styles.xml index 2cf3b3997..f2c9a1e27 100644 --- a/ui/common/src/main/res/values/styles.xml +++ b/ui/common/src/main/res/values/styles.xml @@ -27,6 +27,7 @@ false false true + @android:color/transparent @style/Style.AntennaPod.Toolbar @style/AppPreferenceThemeOverlay @@ -44,6 +45,8 @@ @color/background_light @color/background_light #D3DCE0 + #EBEEF3 + #D3DCE0 @@ -88,6 +91,8 @@ @color/background_darktheme @color/background_darktheme #2F3B4F + #1C2024 + #2F3B4F