diff --git a/android/app/src/main/java/app/organicmaps/MwmActivity.java b/android/app/src/main/java/app/organicmaps/MwmActivity.java index 37e97b821f..416f528e24 100644 --- a/android/app/src/main/java/app/organicmaps/MwmActivity.java +++ b/android/app/src/main/java/app/organicmaps/MwmActivity.java @@ -593,14 +593,14 @@ public class MwmActivity extends BaseMwmFragmentActivity ViewCompat.setOnApplyWindowInsetsListener(mPointChooser, (view, windowInsets) -> { UiUtils.setViewInsetsPaddingBottom(mPointChooser, windowInsets); UiUtils.setViewInsetsPaddingNoBottom(mPointChooserToolbar, windowInsets); - + final int trackRecorderOffset = TrackRecorder.nativeIsTrackRecordingEnabled() ? UiUtils.dimen(this, R.dimen.map_button_size) : 0; mNavBarHeight = isFullscreen() ? 0 : windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()).bottom; // For the first loading, set compass top margin to status bar size // The top inset will be then be updated by the routing controller if (mCurrentWindowInsets == null) - updateCompassOffset(windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()).top, windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()).right); - else - updateCompassOffset(-1, windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()).right); + { + updateCompassOffset(trackRecorderOffset + windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()).top, windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()).right); + } refreshLightStatusBar(); updateBottomWidgetsOffset(windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()).left); mCurrentWindowInsets = windowInsets; @@ -817,6 +817,7 @@ public class MwmActivity extends BaseMwmFragmentActivity showBottomSheet(MAIN_MENU_ID); } case help -> showHelp(); + case trackRecordingStatus -> showTrackSaveDialog(); } } @@ -1503,14 +1504,30 @@ public class MwmActivity extends BaseMwmFragmentActivity if (mCurrentWindowInsets == null) { return; } - int offset = mCurrentWindowInsets.getInsets(WindowInsetsCompat.Type.systemBars()).top; + int offsetY = mCurrentWindowInsets.getInsets(WindowInsetsCompat.Type.systemBars()).top; + int offsetX = mCurrentWindowInsets.getInsets(WindowInsetsCompat.Type.systemBars()).right; if (show && mRoutingPlanInplaceController != null) { final int height = mRoutingPlanInplaceController.calcHeight(); if (height != 0) - offset = height; + offsetY = height; } - updateCompassOffset(offset); + final int orientation = getResources().getConfiguration().orientation; + final boolean isTrackRecordingEnabled = TrackRecorder.nativeIsTrackRecordingEnabled(); + if (isTrackRecordingEnabled && (orientation != Configuration.ORIENTATION_LANDSCAPE)) + offsetY += UiUtils.dimen(this, R.dimen.map_button_size); + if (orientation == Configuration.ORIENTATION_LANDSCAPE) + { + if (show) + { + final boolean isSmallScreen = UiUtils.getDisplayTotalHeight(this) < UiUtils.dimen(this, R.dimen.dp_400); + if (!isSmallScreen || TrackRecorder.nativeIsTrackRecordingEnabled()) + offsetX += UiUtils.dimen(this, R.dimen.map_button_size); + } + else if (isTrackRecordingEnabled) + offsetY += UiUtils.dimen(this, R.dimen.map_button_size); + } + updateCompassOffset(offsetY, offsetX); } @Override @@ -2323,6 +2340,11 @@ public class MwmActivity extends BaseMwmFragmentActivity requestPostNotificationsPermission(); + if (mCurrentWindowInsets != null) + { + final int offset = mCurrentWindowInsets.getInsets(WindowInsetsCompat.Type.systemBars()).top; + updateCompassOffset(offset + UiUtils.dimen(this, R.dimen.map_button_size)); + } Toast.makeText(this, R.string.track_recording, Toast.LENGTH_SHORT).show(); TrackRecordingService.startForegroundService(getApplicationContext()); mMapButtonsViewModel.setTrackRecorderState(true); @@ -2331,6 +2353,18 @@ public class MwmActivity extends BaseMwmFragmentActivity private void stopTrackRecording() { + if (mCurrentWindowInsets != null) + { + int offsetY = mCurrentWindowInsets.getInsets(WindowInsetsCompat.Type.systemBars()).top; + final int offsetX = mCurrentWindowInsets.getInsets(WindowInsetsCompat.Type.systemBars()).right; + if (RoutingController.get().isPlanning() && mRoutingPlanInplaceController != null) + { + final int height = mRoutingPlanInplaceController.calcHeight(); + if (height != 0) + offsetY = height; + } + updateCompassOffset(offsetY, offsetX); + } TrackRecordingService.stopService(getApplicationContext()); mMapButtonsViewModel.setTrackRecorderState(false); } diff --git a/android/app/src/main/java/app/organicmaps/maplayer/MapButtonsController.java b/android/app/src/main/java/app/organicmaps/maplayer/MapButtonsController.java index 8f53e4387d..e1d4c125b7 100644 --- a/android/app/src/main/java/app/organicmaps/maplayer/MapButtonsController.java +++ b/android/app/src/main/java/app/organicmaps/maplayer/MapButtonsController.java @@ -1,6 +1,9 @@ package app.organicmaps.maplayer; +import android.animation.ArgbEvaluator; +import android.animation.ObjectAnimator; import android.content.Context; +import android.graphics.drawable.Drawable; import android.os.Bundle; import android.text.TextUtils; import android.util.TypedValue; @@ -52,6 +55,8 @@ public class MapButtonsController extends Fragment private View mBottomButtonsFrame; @Nullable private LayersButton mToggleMapLayerButton; + @Nullable + FloatingActionButton mTrackRecordingStatusButton; @Nullable private MyPositionButton mNavMyPosition; @@ -68,7 +73,11 @@ public class MapButtonsController extends Fragment private final Observer mButtonHiddenObserver = this::setButtonsHidden; private final Observer mMyPositionModeObserver = this::updateNavMyPositionButton; private final Observer mSearchOptionObserver = this::onSearchOptionChange; - private final Observer mTrackRecorderObserver = this::updateMenuBadge; + private final Observer mTrackRecorderObserver = (enable) -> { + updateMenuBadge(enable); + showButton(enable, MapButtons.trackRecordingStatus); + }; + private final Observer mTopButtonMarginObserver = this::updateTopButtonsMargin; @Nullable @Override @@ -119,6 +128,10 @@ public class MapButtonsController extends Fragment mToggleMapLayerButton.setOnClickListener(view -> mMapButtonClickListener.onMapButtonClick(MapButtons.toggleMapLayer)); mToggleMapLayerButton.setVisibility(View.VISIBLE); } + mMapButtonsViewModel.setTopButtonsMarginTop(-1); + mTrackRecordingStatusButton = mFrame.findViewById(R.id.track_recording_status); + if (mTrackRecordingStatusButton != null) + mTrackRecordingStatusButton.setOnClickListener(view -> mMapButtonClickListener.onMapButtonClick(MapButtons.trackRecordingStatus)); final View menuButton = mFrame.findViewById(R.id.menu_button); if (menuButton != null) { @@ -157,6 +170,9 @@ public class MapButtonsController extends Fragment mButtonsMap.put(MapButtons.menu, menuButton); if (helpButton != null) mButtonsMap.put(MapButtons.help, helpButton); + if (mTrackRecordingStatusButton != null) + mButtonsMap.put(MapButtons.trackRecordingStatus, mTrackRecordingStatusButton); + showButton(false, MapButtons.trackRecordingStatus); return mFrame; } @@ -184,6 +200,28 @@ public class MapButtonsController extends Fragment case bookmarks: case menu: UiUtils.showIf(show, buttonView); + break; + case trackRecordingStatus: + UiUtils.showIf(show, buttonView); + animateIconBlinking(show, (FloatingActionButton) buttonView); + } + } + + void animateIconBlinking(boolean show, @NonNull FloatingActionButton button) + { + if (show) + { + Drawable drawable = button.getDrawable(); + ObjectAnimator colorAnimator = ObjectAnimator.ofArgb( + drawable, + "tint", + 0xFF757575, + 0xFFFF0000); + colorAnimator.setDuration(2500); + colorAnimator.setEvaluator(new ArgbEvaluator()); + colorAnimator.setRepeatCount(ObjectAnimator.INFINITE); + colorAnimator.setRepeatMode(ObjectAnimator.REVERSE); + colorAnimator.start(); } } @@ -192,6 +230,15 @@ public class MapButtonsController extends Fragment return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, context.getResources().getDisplayMetrics()); } + private void updateTopButtonsMargin(int margin) + { + if (margin == -1 || mTrackRecordingStatusButton == null) + return; + ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) mTrackRecordingStatusButton.getLayoutParams(); + params.topMargin = margin; + mTrackRecordingStatusButton.setLayoutParams(params); + } + @OptIn(markerClass = ExperimentalBadgeUtils.class) private void updateMenuBadge(Boolean enable) { @@ -352,6 +399,7 @@ public class MapButtonsController extends Fragment mMapButtonsViewModel.getMyPositionMode().observe(activity, mMyPositionModeObserver); mMapButtonsViewModel.getSearchOption().observe(activity, mSearchOptionObserver); mMapButtonsViewModel.getTrackRecorderState().observe(activity, mTrackRecorderObserver); + mMapButtonsViewModel.getTopButtonsMarginTop().observe(activity, mTopButtonMarginObserver); } public void onResume() @@ -378,6 +426,7 @@ public class MapButtonsController extends Fragment public void onStop() { super.onStop(); + mMapButtonsViewModel.getTopButtonsMarginTop().removeObserver(mTopButtonMarginObserver); mPlacePageViewModel.getPlacePageDistanceToTop().removeObserver(mPlacePageDistanceToTopObserver); mMapButtonsViewModel.getButtonsHidden().removeObserver(mButtonHiddenObserver); mMapButtonsViewModel.getMyPositionMode().removeObserver(mMyPositionModeObserver); @@ -407,7 +456,8 @@ public class MapButtonsController extends Fragment search, bookmarks, menu, - help + help, + trackRecordingStatus } public interface MapButtonClickListener diff --git a/android/app/src/main/java/app/organicmaps/maplayer/MapButtonsViewModel.java b/android/app/src/main/java/app/organicmaps/maplayer/MapButtonsViewModel.java index d4acfcb045..ab3bf73dee 100644 --- a/android/app/src/main/java/app/organicmaps/maplayer/MapButtonsViewModel.java +++ b/android/app/src/main/java/app/organicmaps/maplayer/MapButtonsViewModel.java @@ -9,6 +9,7 @@ public class MapButtonsViewModel extends ViewModel { private final MutableLiveData mButtonsHidden = new MutableLiveData<>(false); private final MutableLiveData mBottomButtonsHeight = new MutableLiveData<>(0f); + private final MutableLiveData mTopButtonsMarginTop = new MutableLiveData<>(-1); private final MutableLiveData mLayoutMode = new MutableLiveData<>(MapButtonsController.LayoutMode.regular); private final MutableLiveData mMyPositionMode = new MutableLiveData<>(); private final MutableLiveData mSearchOption = new MutableLiveData<>(); @@ -34,6 +35,16 @@ public class MapButtonsViewModel extends ViewModel mBottomButtonsHeight.setValue(height); } + public MutableLiveData getTopButtonsMarginTop() + { + return mTopButtonsMarginTop; + } + + public void setTopButtonsMarginTop(int margin) + { + mTopButtonsMarginTop.setValue(margin); + } + public MutableLiveData getLayoutMode() { return mLayoutMode; 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 f5bfa85e5c..fe78c86a10 100644 --- a/android/app/src/main/java/app/organicmaps/routing/NavigationController.java +++ b/android/app/src/main/java/app/organicmaps/routing/NavigationController.java @@ -12,16 +12,18 @@ import androidx.appcompat.app.AppCompatActivity; import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; +import androidx.lifecycle.ViewModelProvider; import app.organicmaps.Framework; import app.organicmaps.R; import app.organicmaps.location.LocationHelper; +import app.organicmaps.maplayer.MapButtonsViewModel; import app.organicmaps.maplayer.traffic.TrafficManager; import app.organicmaps.util.StringUtils; import app.organicmaps.util.UiUtils; import app.organicmaps.util.Utils; +import app.organicmaps.util.WindowInsetUtils; import app.organicmaps.widget.LanesView; import app.organicmaps.widget.SpeedLimitView; -import app.organicmaps.util.WindowInsetUtils; import app.organicmaps.widget.menu.NavMenu; import com.google.android.material.bottomsheet.BottomSheetBehavior; @@ -45,6 +47,8 @@ public class NavigationController implements TrafficManager.TrafficCallback, @NonNull private final SpeedLimitView mSpeedLimit; + private final MapButtonsViewModel mMapButtonsViewModel; + private final NavMenu mNavMenu; View.OnClickListener mOnSettingsClickListener; @@ -60,6 +64,8 @@ public class NavigationController implements TrafficManager.TrafficCallback, public NavigationController(AppCompatActivity activity, View.OnClickListener onSettingsClickListener, NavMenu.OnMenuSizeChangedListener onMenuSizeChangedListener) { + mMapButtonsViewModel = new ViewModelProvider(activity).get(MapButtonsViewModel.class); + mFrame = activity.findViewById(R.id.navigation_frame); mNavMenu = new NavMenu(activity, this, onMenuSizeChangedListener); mOnSettingsClickListener = onSettingsClickListener; @@ -157,6 +163,10 @@ public class NavigationController implements TrafficManager.TrafficCallback, UiUtils.visibleIf(hasStreet, mStreetFrame); if (!TextUtils.isEmpty(info.nextStreet)) mNextStreet.setText(info.nextStreet); + int margin = UiUtils.dimen(mFrame.getContext(), R.dimen.nav_frame_padding); + if (hasStreet) + margin += mStreetFrame.getHeight(); + mMapButtonsViewModel.setTopButtonsMarginTop(margin); } public void show(boolean show) diff --git a/android/app/src/main/java/app/organicmaps/util/UiUtils.java b/android/app/src/main/java/app/organicmaps/util/UiUtils.java index bfb1eba977..aa97078569 100644 --- a/android/app/src/main/java/app/organicmaps/util/UiUtils.java +++ b/android/app/src/main/java/app/organicmaps/util/UiUtils.java @@ -9,6 +9,7 @@ import android.graphics.Color; import android.graphics.Rect; import android.os.Build; import android.text.TextUtils; +import android.util.DisplayMetrics; import android.view.TouchDelegate; import android.view.View; import android.view.ViewGroup; @@ -208,6 +209,15 @@ public final class UiUtils return context.getResources().getDimensionPixelSize(id); } + // this method returns the total height of the display (in pixels) including notch and other touchable areas + public static int getDisplayTotalHeight(Context context) + { + DisplayMetrics metrics = new DisplayMetrics(); + WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + windowManager.getDefaultDisplay().getRealMetrics(metrics); + return metrics.heightPixels; + } + public static void updateRedButton(Button button) { button.setTextColor(ThemeUtils.getColor(button.getContext(), button.isEnabled() ? R.attr.redButtonTextColor diff --git a/android/app/src/main/res/drawable/ic_track_recording_status.xml b/android/app/src/main/res/drawable/ic_track_recording_status.xml new file mode 100644 index 0000000000..9ca2e31d6f --- /dev/null +++ b/android/app/src/main/res/drawable/ic_track_recording_status.xml @@ -0,0 +1,14 @@ + + + + diff --git a/android/app/src/main/res/layout-h400dp-land/map_buttons_layout_navigation.xml b/android/app/src/main/res/layout-h400dp-land/map_buttons_layout_navigation.xml new file mode 100644 index 0000000000..4ed89632e4 --- /dev/null +++ b/android/app/src/main/res/layout-h400dp-land/map_buttons_layout_navigation.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/layout-h400dp-land/map_buttons_layout_planning.xml b/android/app/src/main/res/layout-h400dp-land/map_buttons_layout_planning.xml new file mode 100644 index 0000000000..b80f476643 --- /dev/null +++ b/android/app/src/main/res/layout-h400dp-land/map_buttons_layout_planning.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/layout-h400dp-land/map_buttons_layout_regular.xml b/android/app/src/main/res/layout-h400dp-land/map_buttons_layout_regular.xml new file mode 100644 index 0000000000..1f8f5a0a03 --- /dev/null +++ b/android/app/src/main/res/layout-h400dp-land/map_buttons_layout_regular.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/layout-h400dp/map_buttons_layout_navigation.xml b/android/app/src/main/res/layout-h400dp/map_buttons_layout_navigation.xml index f20833f98b..bfd6f149b8 100644 --- a/android/app/src/main/res/layout-h400dp/map_buttons_layout_navigation.xml +++ b/android/app/src/main/res/layout-h400dp/map_buttons_layout_navigation.xml @@ -49,6 +49,13 @@ app:layout_constraintBottom_toTopOf="@+id/btn_bookmarks" app:layout_constraintStart_toStartOf="parent" /> + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/layout-land/map_buttons_layout_regular.xml b/android/app/src/main/res/layout-land/map_buttons_layout_regular.xml new file mode 100644 index 0000000000..095491e5d1 --- /dev/null +++ b/android/app/src/main/res/layout-land/map_buttons_layout_regular.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/layout/map_buttons_layout_navigation.xml b/android/app/src/main/res/layout/map_buttons_layout_navigation.xml index ad3fac1b19..cee0adaf6c 100644 --- a/android/app/src/main/res/layout/map_buttons_layout_navigation.xml +++ b/android/app/src/main/res/layout/map_buttons_layout_navigation.xml @@ -49,6 +49,13 @@ app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" /> + + + + \ No newline at end of file diff --git a/android/app/src/main/res/values-land/dimens.xml b/android/app/src/main/res/values-land/dimens.xml index 6c8c201ba5..a016eca294 100644 --- a/android/app/src/main/res/values-land/dimens.xml +++ b/android/app/src/main/res/values-land/dimens.xml @@ -3,10 +3,12 @@ 48dp 100dp + 20dp 48dp 24dp + 48dp 334dp diff --git a/android/app/src/main/res/values/dimens.xml b/android/app/src/main/res/values/dimens.xml index ab6250e119..8ee79ed8dd 100644 --- a/android/app/src/main/res/values/dimens.xml +++ b/android/app/src/main/res/values/dimens.xml @@ -4,6 +4,7 @@ 40dp 24dp 24dp + 400dp 2dp @@ -66,7 +67,7 @@ 40dp - 32dp + 24dp 26dp 10dp 2dp @@ -90,6 +91,7 @@ 4dp 20dp 104dp + 64dp 48dp