From 866811d211265b905bb317ad4eafe0473242f2a4 Mon Sep 17 00:00:00 2001 From: Dmitry Strekha Date: Thu, 19 Sep 2024 16:52:35 +0300 Subject: [PATCH] [android] Add support for edge-to-edge Closes #9162 by adding [edge-to-edge](https://developer.android.com/develop/ui/views/layout/edge-to-edge) support. - Enable edge-to-edge support for all versions, not just Android 15. - In "gesture" mode navigation and status bars are completely transparent now. In "3 buttons" mode the navigation bar has a system-defined scrim. - `BottomSheetDialogFragment` correctly draws under the navigation bar, ensuring that the map won't be visible under the navbar when a menu is opened. - All `Toolbar`s now handle insets correctly by adding top padding (note that height must be changed to `wrap_content` instead of `?actionBarSize`) - Screens with scrollable content, such as `RecyclerView`, now draw under the navbar. - Some screens required refactoring to support this: no more `wrap_content` for `RecyclerView` inside `NestedScrollView` (this improves performance); changed colors of list items to maintain the same appearance as before - Fixed issue with display cutouts (such as camera hole) overlap content Signed-off-by: Dzmitry Strekha --- .../DownloadResourcesLegacyActivity.java | 5 +- .../java/app/organicmaps/MwmActivity.java | 1 - .../base/BaseMwmFragmentActivity.java | 4 + .../base/BaseMwmRecyclerFragment.java | 8 +- .../organicmaps/base/BaseToolbarActivity.java | 5 +- .../bookmarks/BookmarksListFragment.java | 19 +- .../downloader/OnmapDownloader.java | 9 +- .../editor/EditorHostFragment.java | 10 +- .../editor/FeatureCategoryAdapter.java | 50 ++++- .../editor/FeatureCategoryFragment.java | 7 +- .../organicmaps/editor/OsmLoginFragment.java | 7 +- .../organicmaps/editor/ReportFragment.java | 6 +- .../app/organicmaps/help/HelpFragment.java | 6 +- .../maplayer/MapButtonsController.java | 9 +- .../routing/NavigationController.java | 11 +- .../routing/RoutingPlanController.java | 16 +- .../FloatingSearchToolbarController.java | 10 +- .../search/SearchCategoriesFragment.java | 7 +- .../organicmaps/search/SearchFragment.java | 18 +- .../settings/BaseXmlSettingsFragment.java | 8 + .../java/app/organicmaps/util/UiUtils.java | 28 +-- .../organicmaps/util/WindowInsetUtils.java | 182 ++++++++++++++++++ .../bottomsheet/MenuBottomSheetFragment.java | 28 ++- .../organicmaps/widget/ToolbarController.java | 19 +- .../placepage/EditBookmarkFragment.java | 9 +- .../placepage/PlaceDescriptionFragment.java | 4 +- .../widget/placepage/PlacePageButtons.java | 7 +- .../main/res/drawable/bg_clickable_card.xml | 15 ++ .../app/src/main/res/layout-land/about.xml | 1 + .../res/layout-land/fragment_osm_login.xml | 2 + android/app/src/main/res/layout/about.xml | 1 + .../main/res/layout/bookmarks_activity.xml | 2 +- .../layout/fragment_bookmark_categories.xml | 24 +-- .../fragment_bookmark_category_settings.xml | 2 +- .../res/layout/fragment_bookmark_list.xml | 9 +- .../main/res/layout/fragment_categories.xml | 49 +---- .../res/layout/fragment_edit_description.xml | 2 +- .../src/main/res/layout/fragment_editor.xml | 2 +- .../main/res/layout/fragment_editor_host.xml | 2 +- .../main/res/layout/fragment_osm_login.xml | 2 + .../main/res/layout/fragment_osm_profile.xml | 2 +- .../src/main/res/layout/fragment_phone.xml | 3 +- .../src/main/res/layout/fragment_recycler.xml | 4 +- .../src/main/res/layout/fragment_report.xml | 3 +- .../src/main/res/layout/fragment_search.xml | 9 +- .../main/res/layout/fragment_search_base.xml | 6 +- .../main/res/layout/fragment_timetable.xml | 3 +- .../app/src/main/res/layout/item_bookmark.xml | 2 +- .../main/res/layout/item_bookmark_button.xml | 2 +- .../res/layout/item_bookmark_category.xml | 2 +- .../item_bookmark_group_list_header.xml | 1 - .../res/layout/item_category_description.xml | 1 + .../main/res/layout/item_feature_category.xml | 2 +- .../layout/item_feature_category_footer.xml | 26 +++ .../app/src/main/res/layout/item_track.xml | 2 +- .../src/main/res/layout/recycler_with_fab.xml | 10 - .../src/main/res/layout/toolbar_default.xml | 2 +- .../src/main/res/layout/toolbar_extended.xml | 2 +- .../main/res/layout/toolbar_transparent.xml | 2 +- .../layout/toolbar_with_optional_search.xml | 2 +- android/app/src/main/res/values/styles.xml | 7 +- 61 files changed, 469 insertions(+), 230 deletions(-) create mode 100644 android/app/src/main/java/app/organicmaps/util/WindowInsetUtils.java create mode 100644 android/app/src/main/res/drawable/bg_clickable_card.xml create mode 100644 android/app/src/main/res/layout/item_feature_category_footer.xml delete mode 100644 android/app/src/main/res/layout/recycler_with_fab.xml diff --git a/android/app/src/main/java/app/organicmaps/DownloadResourcesLegacyActivity.java b/android/app/src/main/java/app/organicmaps/DownloadResourcesLegacyActivity.java index cf8e42848b..3be0c88c4c 100644 --- a/android/app/src/main/java/app/organicmaps/DownloadResourcesLegacyActivity.java +++ b/android/app/src/main/java/app/organicmaps/DownloadResourcesLegacyActivity.java @@ -20,7 +20,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.annotation.StyleRes; - +import androidx.core.view.ViewCompat; import app.organicmaps.base.BaseMwmFragmentActivity; import app.organicmaps.downloader.CountryItem; import app.organicmaps.downloader.MapManager; @@ -32,6 +32,7 @@ import app.organicmaps.util.ConnectionState; import app.organicmaps.util.StringUtils; import app.organicmaps.util.UiUtils; import app.organicmaps.util.Utils; +import app.organicmaps.util.WindowInsetUtils.PaddingInsetsListener; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.progressindicator.LinearProgressIndicator; @@ -192,6 +193,8 @@ public class DownloadResourcesLegacyActivity extends BaseMwmFragmentActivity super.onSafeCreate(savedInstanceState); UiUtils.setLightStatusBar(this, true); setContentView(R.layout.activity_download_resources); + final View view = getWindow().getDecorView().findViewById(android.R.id.content); + ViewCompat.setOnApplyWindowInsetsListener(view, PaddingInsetsListener.allSides()); initViewsAndListeners(); mApiRequest = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> { setResult(result.getResultCode(), result.getData()); diff --git a/android/app/src/main/java/app/organicmaps/MwmActivity.java b/android/app/src/main/java/app/organicmaps/MwmActivity.java index cfc349f4ad..e176e153f9 100644 --- a/android/app/src/main/java/app/organicmaps/MwmActivity.java +++ b/android/app/src/main/java/app/organicmaps/MwmActivity.java @@ -520,7 +520,6 @@ public class MwmActivity extends BaseMwmFragmentActivity getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); setContentView(R.layout.activity_map); - UiUtils.setupTransparentStatusBar(this); mPlacePageViewModel = new ViewModelProvider(this).get(PlacePageViewModel.class); mMapButtonsViewModel = new ViewModelProvider(this).get(MapButtonsViewModel.class); diff --git a/android/app/src/main/java/app/organicmaps/base/BaseMwmFragmentActivity.java b/android/app/src/main/java/app/organicmaps/base/BaseMwmFragmentActivity.java index 640b8a5d9e..ed61649142 100644 --- a/android/app/src/main/java/app/organicmaps/base/BaseMwmFragmentActivity.java +++ b/android/app/src/main/java/app/organicmaps/base/BaseMwmFragmentActivity.java @@ -3,10 +3,13 @@ package app.organicmaps.base; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.graphics.Color; import android.media.AudioManager; import android.os.Bundle; import android.view.MenuItem; +import androidx.activity.EdgeToEdge; +import androidx.activity.SystemBarStyle; import androidx.annotation.CallSuper; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -61,6 +64,7 @@ public abstract class BaseMwmFragmentActivity extends AppCompatActivity @Override protected final void onCreate(@Nullable Bundle savedInstanceState) { + EdgeToEdge.enable(this, SystemBarStyle.dark(Color.TRANSPARENT)); super.onCreate(savedInstanceState); mThemeName = Config.getCurrentUiTheme(getApplicationContext()); setTheme(getThemeResourceId(mThemeName)); diff --git a/android/app/src/main/java/app/organicmaps/base/BaseMwmRecyclerFragment.java b/android/app/src/main/java/app/organicmaps/base/BaseMwmRecyclerFragment.java index 56548ffe70..7053432b60 100644 --- a/android/app/src/main/java/app/organicmaps/base/BaseMwmRecyclerFragment.java +++ b/android/app/src/main/java/app/organicmaps/base/BaseMwmRecyclerFragment.java @@ -11,14 +11,15 @@ import androidx.annotation.LayoutRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.widget.Toolbar; +import androidx.core.view.ViewCompat; import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; - import app.organicmaps.R; -import app.organicmaps.widget.PlaceholderView; import app.organicmaps.util.UiUtils; import app.organicmaps.util.Utils; +import app.organicmaps.util.WindowInsetUtils.ScrollableContentInsetsListener; +import app.organicmaps.widget.PlaceholderView; public abstract class BaseMwmRecyclerFragment extends Fragment { @@ -84,9 +85,12 @@ public abstract class BaseMwmRecyclerFragment ex LinearLayoutManager manager = new LinearLayoutManager(view.getContext()); manager.setSmoothScrollbarEnabled(true); mRecycler.setLayoutManager(manager); + mRecycler.setClipToPadding(false); mAdapter = createAdapter(); mRecycler.setAdapter(mAdapter); + ViewCompat.setOnApplyWindowInsetsListener(mRecycler, new ScrollableContentInsetsListener(mRecycler)); + mPlaceholder = view.findViewById(R.id.placeholder); setupPlaceholder(mPlaceholder); } diff --git a/android/app/src/main/java/app/organicmaps/base/BaseToolbarActivity.java b/android/app/src/main/java/app/organicmaps/base/BaseToolbarActivity.java index 7bc8b18334..f8bf306ae2 100644 --- a/android/app/src/main/java/app/organicmaps/base/BaseToolbarActivity.java +++ b/android/app/src/main/java/app/organicmaps/base/BaseToolbarActivity.java @@ -7,12 +7,13 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.appcompat.widget.Toolbar; +import androidx.core.view.ViewCompat; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentFactory; import androidx.fragment.app.FragmentManager; - import app.organicmaps.R; import app.organicmaps.util.UiUtils; +import app.organicmaps.util.WindowInsetUtils.PaddingInsetsListener; public abstract class BaseToolbarActivity extends BaseMwmFragmentActivity { @@ -36,6 +37,8 @@ public abstract class BaseToolbarActivity extends BaseMwmFragmentActivity setupHomeButton(toolbar); displayToolbarAsActionBar(); + + ViewCompat.setOnApplyWindowInsetsListener(toolbar, PaddingInsetsListener.excludeBottom()); } } diff --git a/android/app/src/main/java/app/organicmaps/bookmarks/BookmarksListFragment.java b/android/app/src/main/java/app/organicmaps/bookmarks/BookmarksListFragment.java index 6a5eebc332..bce9cd17b9 100644 --- a/android/app/src/main/java/app/organicmaps/bookmarks/BookmarksListFragment.java +++ b/android/app/src/main/java/app/organicmaps/bookmarks/BookmarksListFragment.java @@ -17,11 +17,10 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AppCompatActivity; +import androidx.core.view.ViewCompat; import androidx.recyclerview.widget.ConcatAdapter; import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.SimpleItemAnimator; - -import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton; import app.organicmaps.MwmActivity; import app.organicmaps.R; import app.organicmaps.base.BaseMwmRecyclerFragment; @@ -36,14 +35,16 @@ import app.organicmaps.bookmarks.data.Track; import app.organicmaps.location.LocationHelper; import app.organicmaps.search.NativeBookmarkSearchListener; import app.organicmaps.search.SearchEngine; +import app.organicmaps.util.SharingUtils; +import app.organicmaps.util.UiUtils; import app.organicmaps.util.Utils; +import app.organicmaps.util.WindowInsetUtils; +import app.organicmaps.util.bottomsheet.MenuBottomSheetFragment; +import app.organicmaps.util.bottomsheet.MenuBottomSheetItem; import app.organicmaps.widget.SearchToolbarController; import app.organicmaps.widget.placepage.EditBookmarkFragment; import app.organicmaps.widget.recycler.DividerItemDecorationWithPadding; -import app.organicmaps.util.SharingUtils; -import app.organicmaps.util.UiUtils; -import app.organicmaps.util.bottomsheet.MenuBottomSheetFragment; -import app.organicmaps.util.bottomsheet.MenuBottomSheetItem; +import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton; import java.util.ArrayList; import java.util.List; @@ -181,6 +182,12 @@ public class BookmarksListFragment extends BaseMwmRecyclerFragment { - UiUtils.setViewInsetsPadding(view, windowInsets); - return windowInsets; - }); + ViewCompat.setOnApplyWindowInsetsListener(mFrame, PaddingInsetsListener.allSides()); } @Override diff --git a/android/app/src/main/java/app/organicmaps/editor/EditorHostFragment.java b/android/app/src/main/java/app/organicmaps/editor/EditorHostFragment.java index 69e3634347..680152967a 100644 --- a/android/app/src/main/java/app/organicmaps/editor/EditorHostFragment.java +++ b/android/app/src/main/java/app/organicmaps/editor/EditorHostFragment.java @@ -13,8 +13,8 @@ import androidx.annotation.CallSuper; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; +import androidx.core.view.ViewCompat; import androidx.fragment.app.Fragment; - import androidx.fragment.app.FragmentManager; import app.organicmaps.MwmApplication; import app.organicmaps.R; @@ -25,11 +25,12 @@ import app.organicmaps.editor.data.LocalizedName; import app.organicmaps.editor.data.LocalizedStreet; import app.organicmaps.editor.data.NamesDataSource; import app.organicmaps.editor.data.PhoneFragment; -import app.organicmaps.widget.SearchToolbarController; -import app.organicmaps.widget.ToolbarController; import app.organicmaps.util.ConnectionState; import app.organicmaps.util.UiUtils; import app.organicmaps.util.Utils; +import app.organicmaps.util.WindowInsetUtils.PaddingInsetsListener; +import app.organicmaps.widget.SearchToolbarController; +import app.organicmaps.widget.ToolbarController; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import java.util.ArrayList; @@ -138,6 +139,9 @@ public class EditorHostFragment extends BaseMwmToolbarFragment implements View.O getToolbarController().setTitle(getTitle()); fillNames(); + + View fragmentContainer = view.findViewById(R.id.fragment_container); + ViewCompat.setOnApplyWindowInsetsListener(fragmentContainer, PaddingInsetsListener.excludeTop()); } @StringRes diff --git a/android/app/src/main/java/app/organicmaps/editor/FeatureCategoryAdapter.java b/android/app/src/main/java/app/organicmaps/editor/FeatureCategoryAdapter.java index 755f90660c..575bfc09b3 100644 --- a/android/app/src/main/java/app/organicmaps/editor/FeatureCategoryAdapter.java +++ b/android/app/src/main/java/app/organicmaps/editor/FeatureCategoryAdapter.java @@ -1,5 +1,6 @@ package app.organicmaps.editor; +import android.text.method.LinkMovementMethod; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -13,8 +14,12 @@ import app.organicmaps.R; import app.organicmaps.editor.data.FeatureCategory; import app.organicmaps.util.UiUtils; -public class FeatureCategoryAdapter extends RecyclerView.Adapter +public class FeatureCategoryAdapter extends RecyclerView.Adapter { + + private static final int TYPE_CATEGORY = 0; + private static final int TYPE_FOOTER = 1; + private FeatureCategory[] mCategories; private final FeatureCategoryFragment mFragment; private final FeatureCategory mSelectedCategory; @@ -33,21 +38,43 @@ public class FeatureCategoryAdapter extends RecyclerView.Adapter { + return new FeatureViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_feature_category, parent, false)); + } + case TYPE_FOOTER -> { + return new FooterViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_feature_category_footer, parent, false)); + } + default -> { + throw new IllegalArgumentException("Unsupported"); + } + } + } + + @Override + public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) + { + if (holder instanceof FeatureViewHolder) + { + ((FeatureViewHolder) holder).bind(position); + } } @Override public int getItemCount() { - return mCategories.length; + return mCategories.length + 1; } protected class FeatureViewHolder extends RecyclerView.ViewHolder @@ -75,6 +102,17 @@ public class FeatureCategoryAdapter extends RecyclerView.Adapter { - UiUtils.setViewInsetsPadding(view, windowInsets); - return windowInsets; - }); + ViewCompat.setOnApplyWindowInsetsListener(mFrame, PaddingInsetsListener.allSides()); return mFrame; } diff --git a/android/app/src/main/java/app/organicmaps/routing/NavigationController.java b/android/app/src/main/java/app/organicmaps/routing/NavigationController.java index a789dd4c2f..9d8417e54a 100644 --- a/android/app/src/main/java/app/organicmaps/routing/NavigationController.java +++ b/android/app/src/main/java/app/organicmaps/routing/NavigationController.java @@ -8,6 +8,7 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; +import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; import androidx.recyclerview.widget.RecyclerView; @@ -16,6 +17,7 @@ import app.organicmaps.R; import app.organicmaps.maplayer.traffic.TrafficManager; import app.organicmaps.util.UiUtils; import app.organicmaps.util.Utils; +import app.organicmaps.util.WindowInsetUtils; import app.organicmaps.widget.menu.NavMenu; import com.google.android.material.bottomsheet.BottomSheetBehavior; @@ -93,9 +95,12 @@ public class NavigationController implements TrafficManager.TrafficCallback, final View nextTurnContainer = mFrame.findViewById(R.id.nav_next_turn_container); ViewCompat.setOnApplyWindowInsetsListener(mStreetFrame, (v, windowInsets) -> { UiUtils.setViewInsetsPaddingNoBottom(v, windowInsets); - nextTurnContainer.setPadding(windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()).left, nextTurnContainer.getPaddingTop(), - nextTurnContainer.getPaddingEnd(), nextTurnContainer.getPaddingBottom()); - navigationBarBackground.getLayoutParams().height = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()).bottom; + + final Insets safeDrawingInsets = windowInsets.getInsets(WindowInsetUtils.TYPE_SAFE_DRAWING); + nextTurnContainer.setPadding( + safeDrawingInsets.left, nextTurnContainer.getPaddingTop(), + nextTurnContainer.getPaddingEnd(), nextTurnContainer.getPaddingBottom()); + navigationBarBackground.getLayoutParams().height = safeDrawingInsets.bottom; // The gesture navigation bar stays at the bottom in landscape // We need to add a background only above the nav menu navigationBarBackground.getLayoutParams().width = mFrame.findViewById(R.id.nav_bottom_sheet).getWidth(); diff --git a/android/app/src/main/java/app/organicmaps/routing/RoutingPlanController.java b/android/app/src/main/java/app/organicmaps/routing/RoutingPlanController.java index 5ec8007c4b..deadbcab41 100644 --- a/android/app/src/main/java/app/organicmaps/routing/RoutingPlanController.java +++ b/android/app/src/main/java/app/organicmaps/routing/RoutingPlanController.java @@ -11,16 +11,16 @@ import androidx.annotation.DrawableRes; import androidx.annotation.IdRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; - import androidx.core.view.ViewCompat; import app.organicmaps.Framework; import app.organicmaps.MwmApplication; import app.organicmaps.R; import app.organicmaps.settings.DrivingOptionsActivity; +import app.organicmaps.util.UiUtils; +import app.organicmaps.util.WindowInsetUtils.PaddingInsetsListener; import app.organicmaps.widget.RoutingToolbarButton; import app.organicmaps.widget.ToolbarController; import app.organicmaps.widget.WheelProgressView; -import app.organicmaps.util.UiUtils; public class RoutingPlanController extends ToolbarController { @@ -107,10 +107,8 @@ public class RoutingPlanController extends ToolbarController mAnimToggle = MwmApplication.from(activity.getApplicationContext()) .getResources().getInteger(R.integer.anim_default); - ViewCompat.setOnApplyWindowInsetsListener(mFrame, (view, windowInsets) -> { - UiUtils.setViewInsetsPaddingNoTop(activity.findViewById(R.id.menu_frame), windowInsets); - return windowInsets; - }); + final View menuFrame = activity.findViewById(R.id.menu_frame); + ViewCompat.setOnApplyWindowInsetsListener(menuFrame, PaddingInsetsListener.excludeTop()); } @NonNull @@ -331,12 +329,6 @@ public class RoutingPlanController extends ToolbarController return frameHeight - extraOppositeOffset; } - @Override - protected boolean useExtendedToolbar() - { - return true; - } - private class SelfTerminatedDrivingOptionsLayoutListener implements View.OnLayoutChangeListener { @Override diff --git a/android/app/src/main/java/app/organicmaps/search/FloatingSearchToolbarController.java b/android/app/src/main/java/app/organicmaps/search/FloatingSearchToolbarController.java index 78828d0f6e..c5c3503d5f 100644 --- a/android/app/src/main/java/app/organicmaps/search/FloatingSearchToolbarController.java +++ b/android/app/src/main/java/app/organicmaps/search/FloatingSearchToolbarController.java @@ -6,6 +6,8 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.view.ViewCompat; +import app.organicmaps.util.WindowInsetUtils; +import app.organicmaps.util.WindowInsetUtils.PaddingInsetsListener; import app.organicmaps.widget.SearchToolbarController; import app.organicmaps.util.UiUtils; @@ -28,10 +30,10 @@ public class FloatingSearchToolbarController extends SearchToolbarController mListener = listener; // We only want to detect a click on the input and not allow editing. disableQueryEditing(); - ViewCompat.setOnApplyWindowInsetsListener(getToolbar(), (view, windowInsets) -> { - UiUtils.setViewInsetsPaddingNoTop(view, windowInsets); - return windowInsets; - }); + + ViewCompat.setOnApplyWindowInsetsListener( + getToolbar(), + PaddingInsetsListener.excludeTop()); } @Override diff --git a/android/app/src/main/java/app/organicmaps/search/SearchCategoriesFragment.java b/android/app/src/main/java/app/organicmaps/search/SearchCategoriesFragment.java index 5bf4439637..19769b41d4 100644 --- a/android/app/src/main/java/app/organicmaps/search/SearchCategoriesFragment.java +++ b/android/app/src/main/java/app/organicmaps/search/SearchCategoriesFragment.java @@ -17,6 +17,8 @@ public class SearchCategoriesFragment extends BaseMwmRecyclerFragmentThis class updates the bottom padding of the scrollable view, + * ensuring that the content is not obscured by system UI elements + * (both the navigation bar and display cutouts). + * + *

Additionally, this class provides a special constructor that accepts a FloatingActionButton. + * When the button is provided, the bottom padding will also consider the height of this button to ensure + * that it does not overlap the scrollable content. + */ + public static final class ScrollableContentInsetsListener implements OnApplyWindowInsetsListener + { + + @Nullable + private final View mFloatingActionButton; + @NonNull + private final View mScrollableView; + + public ScrollableContentInsetsListener(@NonNull View scrollableView) + { + this(scrollableView, null); + } + + public ScrollableContentInsetsListener(@NonNull View scrollableView, @Nullable View floatingActionButton) + { + mScrollableView = scrollableView; + mFloatingActionButton = floatingActionButton; + + if (floatingActionButton != null) + { + floatingActionButton.addOnLayoutChangeListener(new View.OnLayoutChangeListener() + { + @Override + public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) + { + int height = v.getMeasuredHeight(); + if (height > 0) + { + floatingActionButton.removeOnLayoutChangeListener(this); + scrollableView.requestApplyInsets(); + } + } + }); + } + } + + @NonNull + @Override + public WindowInsetsCompat onApplyWindowInsets(@NonNull View v, @NonNull WindowInsetsCompat windowInsets) + { + final Insets insets = windowInsets.getInsets(TYPE_SAFE_DRAWING); + + int scrollableViewPaddingBottom; + + if (mFloatingActionButton != null) + { + int spacing = UiUtils.dimen(v.getContext(), R.dimen.margin_base); + int buttonMarginBottom = insets.bottom + spacing; + + ViewGroup.MarginLayoutParams buttonLayoutParams = (ViewGroup.MarginLayoutParams) mFloatingActionButton.getLayoutParams(); + buttonLayoutParams.bottomMargin = buttonMarginBottom; + + scrollableViewPaddingBottom = buttonMarginBottom + + mFloatingActionButton.getMeasuredHeight() + + spacing; + } + else + { + scrollableViewPaddingBottom = insets.bottom; + } + + mScrollableView.setPadding( + v.getPaddingLeft(), + v.getPaddingTop(), + v.getPaddingRight(), + scrollableViewPaddingBottom); + + // update margins instead of paddings, because item decorators do not respect paddings + updateScrollableViewMargins(insets); + + return windowInsets; + } + + private void updateScrollableViewMargins(@NonNull Insets insets) + { + final ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) mScrollableView.getLayoutParams(); + if (layoutParams != null) + { + boolean dirty = false; + if (layoutParams.rightMargin != insets.right) + { + layoutParams.rightMargin = insets.right; + dirty = true; + } + if (layoutParams.leftMargin != insets.left) + { + layoutParams.leftMargin = insets.left; + dirty = true; + } + if (dirty) + { + mScrollableView.requestLayout(); + } + } + } + } + + public static final class PaddingInsetsListener implements OnApplyWindowInsetsListener + { + + private final boolean top; + private final boolean bottom; + private final boolean left; + private final boolean right; + + public PaddingInsetsListener(boolean top, boolean bottom, boolean left, boolean right) + { + this.top = top; + this.bottom = bottom; + this.left = left; + this.right = right; + } + + public static PaddingInsetsListener allSides() + { + return new PaddingInsetsListener(true, true, true, true); + } + + public static PaddingInsetsListener excludeTop() + { + return new PaddingInsetsListener(false, true, true, true); + } + + public static PaddingInsetsListener excludeBottom() + { + return new PaddingInsetsListener(true, false, true, true); + } + + @NonNull + @Override + public WindowInsetsCompat onApplyWindowInsets(@NonNull View v, @NonNull WindowInsetsCompat windowInsets) + { + final Insets insets = windowInsets.getInsets(TYPE_SAFE_DRAWING); + v.setPadding( + left ? insets.left : v.getPaddingLeft(), + top ? insets.top : v.getPaddingTop(), + right ? insets.right : v.getPaddingRight(), + bottom ? insets.bottom : v.getPaddingBottom()); + return windowInsets; + } + } +} diff --git a/android/app/src/main/java/app/organicmaps/util/bottomsheet/MenuBottomSheetFragment.java b/android/app/src/main/java/app/organicmaps/util/bottomsheet/MenuBottomSheetFragment.java index 52ad968af7..ebe79f4091 100644 --- a/android/app/src/main/java/app/organicmaps/util/bottomsheet/MenuBottomSheetFragment.java +++ b/android/app/src/main/java/app/organicmaps/util/bottomsheet/MenuBottomSheetFragment.java @@ -1,23 +1,31 @@ package app.organicmaps.util.bottomsheet; import android.app.Activity; +import android.app.Dialog; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.Window; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.core.view.WindowCompat; +import androidx.core.view.WindowInsetsControllerCompat; import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; + import com.google.android.material.bottomsheet.BottomSheetBehavior; +import com.google.android.material.bottomsheet.BottomSheetDialog; import com.google.android.material.bottomsheet.BottomSheetDialogFragment; -import app.organicmaps.R; -import app.organicmaps.util.UiUtils; import java.util.ArrayList; +import java.util.Objects; + +import app.organicmaps.R; +import app.organicmaps.util.UiUtils; public class MenuBottomSheetFragment extends BottomSheetDialogFragment { @@ -46,6 +54,22 @@ public class MenuBottomSheetFragment extends BottomSheetDialogFragment return f; } + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) + { + return new BottomSheetDialog(requireContext(), getTheme()) { + @Override + public void onAttachedToWindow() { + super.onAttachedToWindow(); + Window window = Objects.requireNonNull(getWindow()); + WindowInsetsControllerCompat insetsController = + WindowCompat.getInsetsController(window, window.getDecorView()); + insetsController.setAppearanceLightNavigationBars(false); + } + }; + } + @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) diff --git a/android/app/src/main/java/app/organicmaps/widget/ToolbarController.java b/android/app/src/main/java/app/organicmaps/widget/ToolbarController.java index ce19e94fde..3c7a13fe0b 100644 --- a/android/app/src/main/java/app/organicmaps/widget/ToolbarController.java +++ b/android/app/src/main/java/app/organicmaps/widget/ToolbarController.java @@ -10,11 +10,11 @@ import androidx.annotation.StringRes; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; - import androidx.core.view.ViewCompat; import app.organicmaps.R; import app.organicmaps.util.UiUtils; import app.organicmaps.util.Utils; +import app.organicmaps.util.WindowInsetUtils; public class ToolbarController { @@ -30,13 +30,11 @@ public class ToolbarController mActivity = activity; mToolbar = root.findViewById(getToolbarId()); - if (useExtendedToolbar()) - { - ViewCompat.setOnApplyWindowInsetsListener(getToolbar(), (view, windowInsets) -> { - UiUtils.setViewInsetsPaddingNoBottom(getToolbar(), windowInsets); - return windowInsets; - }); - } + + ViewCompat.setOnApplyWindowInsetsListener( + getToolbar(), + WindowInsetUtils.PaddingInsetsListener.excludeBottom()); + UiUtils.setupNavigationIcon(mToolbar, mNavigationClickListener); setSupportActionBar(activity, mToolbar); } @@ -47,11 +45,6 @@ public class ToolbarController appCompatActivity.setSupportActionBar(toolbar); } - protected boolean useExtendedToolbar() - { - return false; - } - @IdRes private int getToolbarId() { diff --git a/android/app/src/main/java/app/organicmaps/widget/placepage/EditBookmarkFragment.java b/android/app/src/main/java/app/organicmaps/widget/placepage/EditBookmarkFragment.java index afbbca39ef..82a5be0351 100644 --- a/android/app/src/main/java/app/organicmaps/widget/placepage/EditBookmarkFragment.java +++ b/android/app/src/main/java/app/organicmaps/widget/placepage/EditBookmarkFragment.java @@ -20,7 +20,6 @@ import androidx.core.view.WindowCompat; import androidx.core.view.WindowInsetsCompat; import androidx.fragment.app.FragmentFactory; import androidx.fragment.app.FragmentManager; - import app.organicmaps.R; import app.organicmaps.base.BaseMwmDialogFragment; import app.organicmaps.bookmarks.ChooseBookmarkCategoryFragment; @@ -32,6 +31,7 @@ import app.organicmaps.bookmarks.data.Icon; import app.organicmaps.util.Graphics; import app.organicmaps.util.InputUtils; import app.organicmaps.util.UiUtils; +import app.organicmaps.util.WindowInsetUtils.PaddingInsetsListener; import com.google.android.material.textfield.TextInputEditText; import com.google.android.material.textfield.TextInputLayout; @@ -146,10 +146,9 @@ public class EditBookmarkFragment extends BaseMwmDialogFragment implements View. private void initToolbar(View view) { Toolbar toolbar = view.findViewById(R.id.toolbar); - ViewCompat.setOnApplyWindowInsetsListener(toolbar, (v, windowInsets) -> { - UiUtils.setViewInsetsPaddingNoBottom(v, windowInsets); - return windowInsets; - }); + + ViewCompat.setOnApplyWindowInsetsListener(toolbar, PaddingInsetsListener.excludeBottom()); + final ImageView imageView = toolbar.findViewById(R.id.save); imageView.setOnClickListener(v -> saveBookmark()); UiUtils.showHomeUpButton(toolbar); diff --git a/android/app/src/main/java/app/organicmaps/widget/placepage/PlaceDescriptionFragment.java b/android/app/src/main/java/app/organicmaps/widget/placepage/PlaceDescriptionFragment.java index 8c1bbe278c..2b2ee32b97 100644 --- a/android/app/src/main/java/app/organicmaps/widget/placepage/PlaceDescriptionFragment.java +++ b/android/app/src/main/java/app/organicmaps/widget/placepage/PlaceDescriptionFragment.java @@ -8,10 +8,11 @@ import android.webkit.WebView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; - +import androidx.core.view.ViewCompat; import app.organicmaps.R; import app.organicmaps.base.BaseMwmFragment; import app.organicmaps.util.Utils; +import app.organicmaps.util.WindowInsetUtils; import java.util.Objects; @@ -41,6 +42,7 @@ public class PlaceDescriptionFragment extends BaseMwmFragment WebView webView = root.findViewById(R.id.webview); webView.loadData(mDescription + SOURCE_SUFFIX, Utils.TEXT_HTML, Utils.UTF_8); webView.setVerticalScrollBarEnabled(true); + ViewCompat.setOnApplyWindowInsetsListener(root, WindowInsetUtils.PaddingInsetsListener.excludeTop()); return root; } } diff --git a/android/app/src/main/java/app/organicmaps/widget/placepage/PlacePageButtons.java b/android/app/src/main/java/app/organicmaps/widget/placepage/PlacePageButtons.java index 50a5610e6e..d4fd663135 100644 --- a/android/app/src/main/java/app/organicmaps/widget/placepage/PlacePageButtons.java +++ b/android/app/src/main/java/app/organicmaps/widget/placepage/PlacePageButtons.java @@ -16,7 +16,7 @@ import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModelProvider; import app.organicmaps.R; import app.organicmaps.util.Graphics; -import app.organicmaps.util.UiUtils; +import app.organicmaps.util.WindowInsetUtils.PaddingInsetsListener; import app.organicmaps.util.bottomsheet.MenuBottomSheetFragment; import java.util.ArrayList; @@ -44,10 +44,7 @@ public final class PlacePageButtons extends Fragment implements Observer { - UiUtils.setViewInsetsPaddingNoTop(v, windowInsets); - return windowInsets; - }); + ViewCompat.setOnApplyWindowInsetsListener(view, PaddingInsetsListener.excludeTop()); mMaxButtons = getResources().getInteger(R.integer.pp_buttons_max); Fragment parentFragment = getParentFragment(); diff --git a/android/app/src/main/res/drawable/bg_clickable_card.xml b/android/app/src/main/res/drawable/bg_clickable_card.xml new file mode 100644 index 0000000000..d4a8a28fe5 --- /dev/null +++ b/android/app/src/main/res/drawable/bg_clickable_card.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/layout-land/about.xml b/android/app/src/main/res/layout-land/about.xml index 56017f1543..9f9132b1a4 100644 --- a/android/app/src/main/res/layout-land/about.xml +++ b/android/app/src/main/res/layout-land/about.xml @@ -6,6 +6,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:background="?cardBackground" + android:clipToPadding="false" android:fadeScrollbars="false" android:fillViewport="true"> diff --git a/android/app/src/main/res/layout-land/fragment_osm_login.xml b/android/app/src/main/res/layout-land/fragment_osm_login.xml index e5b177f708..f95650d7dc 100644 --- a/android/app/src/main/res/layout-land/fragment_osm_login.xml +++ b/android/app/src/main/res/layout-land/fragment_osm_login.xml @@ -10,11 +10,13 @@ android:id="@+id/toolbar" layout="@layout/toolbar_default" /> diff --git a/android/app/src/main/res/layout/bookmarks_activity.xml b/android/app/src/main/res/layout/bookmarks_activity.xml index cfebb85093..7efd93adf4 100644 --- a/android/app/src/main/res/layout/bookmarks_activity.xml +++ b/android/app/src/main/res/layout/bookmarks_activity.xml @@ -7,7 +7,7 @@ - - - - - - - + android:layout_height="match_parent" /> diff --git a/android/app/src/main/res/layout/fragment_bookmark_category_settings.xml b/android/app/src/main/res/layout/fragment_bookmark_category_settings.xml index 40c2be3f9c..51c9fb4515 100644 --- a/android/app/src/main/res/layout/fragment_bookmark_category_settings.xml +++ b/android/app/src/main/res/layout/fragment_bookmark_category_settings.xml @@ -10,7 +10,7 @@ android:id="@+id/toolbar" style="@style/MwmWidget.ToolbarStyle" android:layout_width="match_parent" - android:layout_height="?attr/actionBarSize" + android:layout_height="wrap_content" android:theme="@style/MwmWidget.ToolbarTheme"/> - - - - + android:clipToPadding="false" /> - - - + - - - - - - - - - - - - - - - - - + android:layout_height="match_parent" /> diff --git a/android/app/src/main/res/layout/fragment_edit_description.xml b/android/app/src/main/res/layout/fragment_edit_description.xml index 4dfb509fa5..9adc63bd09 100644 --- a/android/app/src/main/res/layout/fragment_edit_description.xml +++ b/android/app/src/main/res/layout/fragment_edit_description.xml @@ -8,7 +8,7 @@ android:id="@+id/toolbar" style="@style/MwmWidget.ToolbarStyle" android:layout_width="match_parent" - android:layout_height="?attr/actionBarSize" + android:layout_height="wrap_content" android:theme="@style/MwmWidget.ToolbarTheme" app:layout_constraintTop_toTopOf="parent"> + android:layout_height="match_parent"> + layout="@layout/recycler_default" + android:layout_width="match_parent" + android:layout_height="match_parent" /> \ No newline at end of file diff --git a/android/app/src/main/res/layout/fragment_report.xml b/android/app/src/main/res/layout/fragment_report.xml index 3b91f5b66f..4b62ed2e57 100644 --- a/android/app/src/main/res/layout/fragment_report.xml +++ b/android/app/src/main/res/layout/fragment_report.xml @@ -10,7 +10,7 @@ android:id="@+id/toolbar" style="@style/MwmWidget.ToolbarStyle" android:layout_width="match_parent" - android:layout_height="?attr/actionBarSize" + android:layout_height="wrap_content" android:gravity="end|center_vertical" android:theme="@style/MwmWidget.ToolbarTheme" tools:ignore="UnusedAttribute"> @@ -21,6 +21,7 @@ diff --git a/android/app/src/main/res/layout/fragment_search.xml b/android/app/src/main/res/layout/fragment_search.xml index ccab5265d3..531b2a9dea 100644 --- a/android/app/src/main/res/layout/fragment_search.xml +++ b/android/app/src/main/res/layout/fragment_search.xml @@ -64,7 +64,14 @@ android:clickable="true" android:focusable="true" android:background="?cardBackground"> - + + - + + + android:layout_height="match_parent"> diff --git a/android/app/src/main/res/layout/item_bookmark_category.xml b/android/app/src/main/res/layout/item_bookmark_category.xml index ba18f9fc78..ee8d743e65 100644 --- a/android/app/src/main/res/layout/item_bookmark_category.xml +++ b/android/app/src/main/res/layout/item_bookmark_category.xml @@ -5,7 +5,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="?clickableBackground"> + android:background="@drawable/bg_clickable_card"> diff --git a/android/app/src/main/res/layout/item_feature_category_footer.xml b/android/app/src/main/res/layout/item_feature_category_footer.xml new file mode 100644 index 0000000000..7d78defa31 --- /dev/null +++ b/android/app/src/main/res/layout/item_feature_category_footer.xml @@ -0,0 +1,26 @@ + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/layout/item_track.xml b/android/app/src/main/res/layout/item_track.xml index d4063f165c..0e6bfee4e6 100644 --- a/android/app/src/main/res/layout/item_track.xml +++ b/android/app/src/main/res/layout/item_track.xml @@ -4,7 +4,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="@dimen/height_item_multiline" - android:background="?clickableBackground"> + android:background="@drawable/bg_clickable_card"> - \ No newline at end of file diff --git a/android/app/src/main/res/layout/toolbar_default.xml b/android/app/src/main/res/layout/toolbar_default.xml index b2afe76bd5..7a7af1ca7f 100644 --- a/android/app/src/main/res/layout/toolbar_default.xml +++ b/android/app/src/main/res/layout/toolbar_default.xml @@ -4,5 +4,5 @@ android:id="@+id/toolbar" style="@style/MwmWidget.ToolbarStyle" android:layout_width="match_parent" - android:layout_height="?attr/actionBarSize" + android:layout_height="wrap_content" android:theme="@style/MwmWidget.ToolbarTheme"/> diff --git a/android/app/src/main/res/layout/toolbar_extended.xml b/android/app/src/main/res/layout/toolbar_extended.xml index 8c55531b96..98f5d6f7b3 100644 --- a/android/app/src/main/res/layout/toolbar_extended.xml +++ b/android/app/src/main/res/layout/toolbar_extended.xml @@ -8,7 +8,7 @@ android:id="@+id/toolbar" style="@style/MwmWidget.ToolbarStyle" android:layout_width="match_parent" - android:layout_height="?attr/actionBarSize" + android:layout_height="wrap_content" android:theme="@style/MwmWidget.ToolbarTheme.DownButton"/> diff --git a/android/app/src/main/res/layout/toolbar_with_optional_search.xml b/android/app/src/main/res/layout/toolbar_with_optional_search.xml index 08db7f26fa..529316add0 100644 --- a/android/app/src/main/res/layout/toolbar_with_optional_search.xml +++ b/android/app/src/main/res/layout/toolbar_with_optional_search.xml @@ -5,7 +5,7 @@ style="@style/MwmWidget.ToolbarStyle.NoElevation" android:theme="@style/MwmWidget.ToolbarTheme" android:layout_width="match_parent" - android:layout_height="?attr/actionBarSize"> + android:layout_height="wrap_content"> - +