[android] Map fragments improvements #4808

Merged
arnaudvergnet merged 4 commits from fragments-improvements into master 2023-05-08 14:43:15 +00:00
23 changed files with 643 additions and 650 deletions

View file

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
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/coordinator"
android:layout_width="match_parent"
@ -51,26 +50,9 @@
android:paddingBottom="@dimen/margin_base"
android:visibility="invisible" />
</RelativeLayout>
<androidx.core.widget.NestedScrollViewClickFixed
android:id="@+id/placepage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?attr/bottomSheetStyle"
android:fillViewport="true"
app:layout_behavior="@string/placepage_behavior" >
<RelativeLayout
android:id="@+id/placepage_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?panel" >
<androidx.fragment.app.FragmentContainerView
android:id="@+id/placepage_fragment"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</RelativeLayout>
</androidx.core.widget.NestedScrollViewClickFixed>
<androidx.fragment.app.FragmentContainerView
android:id="@+id/pp_buttons_fragment"
android:id="@+id/place_page_container_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
android:layout_height="match_parent"
android:name="app.organicmaps.widget.placepage.PlacePageController" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.core.widget.NestedScrollViewClickFixed
android:id="@+id/placepage"
style="?attr/bottomSheetStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fillViewport="true"
app:layout_behavior="@string/placepage_behavior">
<RelativeLayout
android:id="@+id/placepage_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?panel">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/placepage_fragment"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</RelativeLayout>
</androidx.core.widget.NestedScrollViewClickFixed>
<androidx.fragment.app.FragmentContainerView
android:id="@+id/pp_buttons_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -27,6 +27,8 @@ import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentFactory;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModelProvider;
import app.organicmaps.Framework.PlacePageActivationListener;
import app.organicmaps.api.Const;
import app.organicmaps.background.Notifier;
@ -56,6 +58,7 @@ import app.organicmaps.location.LocationHelper;
import app.organicmaps.location.LocationListener;
import app.organicmaps.location.LocationState;
import app.organicmaps.maplayer.MapButtonsController;
import app.organicmaps.maplayer.MapButtonsViewModel;
import app.organicmaps.maplayer.Mode;
import app.organicmaps.maplayer.ToggleMapLayerFragment;
import app.organicmaps.maplayer.isolines.IsolinesManager;
@ -89,18 +92,15 @@ import app.organicmaps.util.bottomsheet.MenuBottomSheetFragment;
import app.organicmaps.util.bottomsheet.MenuBottomSheetItem;
import app.organicmaps.util.log.Logger;
import app.organicmaps.widget.menu.MainMenu;
import app.organicmaps.widget.placepage.PlacePageButtons;
import app.organicmaps.widget.placepage.PlacePageController;
import app.organicmaps.widget.placepage.PlacePageData;
import app.organicmaps.widget.placepage.PlacePageView;
import app.organicmaps.widget.placepage.PlacePageViewModel;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.util.ArrayList;
import java.util.Objects;
import java.util.Stack;
import static app.organicmaps.widget.placepage.PlacePageButtons.PLACEPAGE_MORE_MENU_ID;
public class MwmActivity extends BaseMwmFragmentActivity
implements PlacePageActivationListener,
View.OnTouchListener,
@ -113,12 +113,10 @@ public class MwmActivity extends BaseMwmFragmentActivity
RoutingBottomMenuListener,
BookmarkManager.BookmarksLoadingListener,
FloatingSearchToolbarController.SearchToolbarListener,
PlacePageController.SlideListener,
NoConnectionListener,
MenuBottomSheetFragment.MenuBottomSheetInterfaceWithHeader,
ToggleMapLayerFragment.LayerItemClickListener,
PlacePageButtons.PlacePageButtonClickListener,
PlacePageView.PlacePageViewListener
PlacePageController.PlacePageRouteSettingsListener,
MapButtonsController.MapButtonClickListener
{
private static final String TAG = MwmActivity.class.getSimpleName();
@ -133,8 +131,6 @@ public class MwmActivity extends BaseMwmFragmentActivity
EditorHostFragment.class.getName(),
ReportFragment.class.getName() };
private static final String EXTRA_CURRENT_LAYOUT_MODE = "CURRENT_LAYOUT_MODE";
private static final String EXTRA_IS_FULLSCREEN = "IS_FULLSCREEN";
public static final int REQ_CODE_ERROR_DRIVING_OPTIONS_DIALOG = 5;
public static final int REQ_CODE_DRIVING_OPTIONS = 6;
private static final int REQ_CODE_ISOLINES_ERROR = 8;
@ -172,12 +168,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
private PanelAnimator mPanelAnimator;
@Nullable
private OnmapDownloader mOnmapDownloader;
@Nullable
private MapButtonsController mMapButtonsController;
private boolean mIsTabletLayout;
private boolean mIsFullscreen;
@SuppressWarnings("NotNullFieldNotInitialized")
@NonNull
private FloatingSearchToolbarController mSearchController;
@ -185,15 +176,15 @@ public class MwmActivity extends BaseMwmFragmentActivity
private boolean mRestoreRoutingPlanFragmentNeeded;
@Nullable
private Bundle mSavedForTabletState;
@SuppressWarnings("NotNullFieldNotInitialized")
@NonNull
private PlacePageController mPlacePageController;
private MapButtonsController.LayoutMode mCurrentLayoutMode;
private String mDonatesUrl;
private int mNavBarHeight;
private PlacePageViewModel mPlacePageViewModel;
private MapButtonsViewModel mMapButtonsViewModel;
private MapButtonsController.LayoutMode mPreviousMapLayoutMode;
private Mode mPreviousLayerMode;
@Nullable
private WindowInsetsCompat mCurrentWindowInsets;
@ -369,15 +360,6 @@ public class MwmActivity extends BaseMwmFragmentActivity
protected void onSafeCreate(@Nullable Bundle savedInstanceState)
{
super.onSafeCreate(savedInstanceState);
if (savedInstanceState != null)
{
mCurrentLayoutMode = MapButtonsController.LayoutMode.values()[savedInstanceState.getInt(EXTRA_CURRENT_LAYOUT_MODE)];
mIsFullscreen = savedInstanceState.getBoolean(EXTRA_IS_FULLSCREEN);
}
else
{
mCurrentLayoutMode = MapButtonsController.LayoutMode.regular;
}
mIsTabletLayout = getResources().getBoolean(R.bool.tabletLayout);
if (!mIsTabletLayout)
@ -386,8 +368,13 @@ public class MwmActivity extends BaseMwmFragmentActivity
setContentView(R.layout.activity_map);
UiUtils.setupTransparentStatusBar(this);
mPlacePageController = new PlacePageController(this, this);
mPlacePageController.initialize(this);
mPlacePageViewModel = new ViewModelProvider(this).get(PlacePageViewModel.class);
mMapButtonsViewModel = new ViewModelProvider(this).get(MapButtonsViewModel.class);
// We don't need to manually handle removing the observers it follows the activity lifecycle
mMapButtonsViewModel.getBottomButtonsHeight().observe(this, this::onMapBottomButtonsHeightChange);
mMapButtonsViewModel.getLayoutMode().observe(this, this::initNavigationButtons);
mPreviousLayerMode = mMapButtonsViewModel.getMapLayerMode().getValue();
mMapButtonsViewModel.getMapLayerMode().observe(this, this::onLayerChange);
mSearchController = new FloatingSearchToolbarController(this, this);
mSearchController.getToolbar()
@ -424,7 +411,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
UiUtils.setViewInsetsPaddingBottom(mPointChooser, windowInsets);
UiUtils.setViewInsetsPaddingNoBottom(mPointChooserToolbar, windowInsets);
mNavBarHeight = mIsFullscreen ? 0 : windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()).bottom;
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)
@ -465,7 +452,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
removeCurrentFragment(false);
}
mNavigationController = new NavigationController(this, mMapButtonsController, v -> onSettingsOptionSelected(), this::updateBottomWidgetsOffset);
mNavigationController = new NavigationController(this, v -> onSettingsOptionSelected(), this::updateBottomWidgetsOffset);
//TrafficManager.INSTANCE.attach(mNavigationController);
initMainMenu();
@ -565,7 +552,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
mPointChooserMode = mode;
closeFloatingToolbarsAndPanels(false);
UiUtils.show(mPointChooser);
mMapButtonsController.showMapButtons(false);
mMapButtonsViewModel.setButtonsHidden(true);
Framework.nativeTurnOnChoosePositionMode(isBusiness, applyPosition);
refreshLightStatusBar();
}
@ -574,7 +561,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
{
UiUtils.hide(mPointChooser);
Framework.nativeTurnOffChoosePositionMode();
mMapButtonsController.showMapButtons(true);
mMapButtonsViewModel.setButtonsHidden(false);
if (mPointChooserMode == PointChooserMode.API)
finish();
mPointChooserMode = PointChooserMode.NONE;
@ -613,32 +600,29 @@ public class MwmActivity extends BaseMwmFragmentActivity
private void initNavigationButtons()
{
initNavigationButtons(mCurrentLayoutMode);
initNavigationButtons(mMapButtonsViewModel.getLayoutMode().getValue());
}
private void initNavigationButtons(MapButtonsController.LayoutMode layoutMode)
{
if (mMapButtonsController == null || mMapButtonsController.getLayoutMode() != layoutMode)
// Recreate the navigation buttons with the correct layout when it changes
if (mPreviousMapLayoutMode != layoutMode)
{
mCurrentLayoutMode = layoutMode;
mMapButtonsController = new MapButtonsController();
mMapButtonsController.init(
layoutMode,
LocationState.nativeGetMode(),
this::onMapButtonClick,
(v) -> closeSearchToolbar(true, true),
mPlacePageController,
this::updateBottomWidgetsOffset);
FragmentTransaction transaction = getSupportFragmentManager()
.beginTransaction().replace(R.id.map_buttons, mMapButtonsController);
.beginTransaction().replace(R.id.map_buttons, new MapButtonsController());
transaction.commit();
mPreviousMapLayoutMode = layoutMode;
}
}
void onMapButtonClick(MapButtonsController.MapButtons button)
@Override
public void onSearchCanceled()
{
closeSearchToolbar(true, true);
}
@Override
public void onMapButtonClick(MapButtonsController.MapButtons button)
{
switch (button)
{
@ -699,10 +683,10 @@ public class MwmActivity extends BaseMwmFragmentActivity
*/
public boolean closePlacePage()
{
if (mPlacePageController.isClosed())
if (mPlacePageViewModel.getMapObject().getValue() == null)
return false;
mPlacePageController.close(true);
mPlacePageViewModel.setMapObject(null);
return true;
}
@ -762,7 +746,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
if (stopSearch)
{
mSearchController.cancelSearchApiAndHide(clearText);
mMapButtonsController.resetSearch();
mMapButtonsViewModel.setSearchOption(null);
}
else
{
@ -829,7 +813,6 @@ public class MwmActivity extends BaseMwmFragmentActivity
@Override
protected void onSaveInstanceState(@NonNull Bundle outState)
{
mPlacePageController.onSave(outState);
if (!mIsTabletLayout && RoutingController.get().isPlanning())
mRoutingPlanInplaceController.onSaveState(outState);
@ -843,8 +826,6 @@ public class MwmActivity extends BaseMwmFragmentActivity
mNavigationController.onActivitySaveInstanceState(this, outState);
RoutingController.get().onSaveState();
outState.putInt(EXTRA_CURRENT_LAYOUT_MODE, mCurrentLayoutMode.ordinal());
outState.putBoolean(EXTRA_IS_FULLSCREEN, mIsFullscreen);
if (!isChangingConfigurations())
RoutingController.get().saveRoute();
@ -860,7 +841,6 @@ public class MwmActivity extends BaseMwmFragmentActivity
protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState)
{
super.onRestoreInstanceState(savedInstanceState);
mPlacePageController.onRestore(savedInstanceState);
if (mIsTabletLayout)
{
RoutingPlanFragment fragment = (RoutingPlanFragment) getFragment(RoutingPlanFragment.class);
@ -991,11 +971,11 @@ public class MwmActivity extends BaseMwmFragmentActivity
{
super.onResume();
refreshSearchToolbar();
setFullscreen(mIsFullscreen);
setFullscreen(isFullscreen());
if (Framework.nativeIsInChoosePositionMode())
{
UiUtils.show(mPointChooser);
mMapButtonsController.showMapButtons(false);
mMapButtonsViewModel.setButtonsHidden(true);
}
if (mOnmapDownloader != null)
mOnmapDownloader.onResume();
@ -1070,8 +1050,6 @@ public class MwmActivity extends BaseMwmFragmentActivity
{
super.onSafeDestroy();
mNavigationController.destroy();
//TrafficManager.INSTANCE.detachAll();
mPlacePageController.destroy();
}
@Override
@ -1150,8 +1128,8 @@ public class MwmActivity extends BaseMwmFragmentActivity
public void onPlacePageActivated(@NonNull PlacePageData data)
{
setFullscreen(false);
mPlacePageController.openFor(data);
// This will open the place page
mPlacePageViewModel.setMapObject((MapObject) data);
}
// Called from JNI.
@ -1164,7 +1142,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
UiUtils.isVisible(mSearchController.getToolbar()))
return;
setFullscreen(!mIsFullscreen);
setFullscreen(!isFullscreen());
}
else
{
@ -1179,15 +1157,14 @@ public class MwmActivity extends BaseMwmFragmentActivity
|| RoutingController.get().isPlanning())
return;
mIsFullscreen = isFullscreen;
mMapButtonsController.showMapButtons(!isFullscreen);
mMapButtonsViewModel.setButtonsHidden(isFullscreen);
UiUtils.setFullscreen(this, isFullscreen);
}
@Override
public void onPlacePageSlide(int top)
private boolean isFullscreen()
{
mMapButtonsController.move(top);
// Buttons are hidden in position chooser mode but we are not in fullscreen
return Boolean.TRUE.equals(mMapButtonsViewModel.getButtonsHidden().getValue()) && !Framework.nativeIsInChoosePositionMode();
}
@Override
@ -1222,6 +1199,10 @@ public class MwmActivity extends BaseMwmFragmentActivity
Map.onCompassUpdated(north, true);
}
public void onMapBottomButtonsHeightChange(float height) {
updateBottomWidgetsOffset();
}
public void updateBottomWidgetsOffset()
{
updateBottomWidgetsOffset(-1);
@ -1233,8 +1214,9 @@ public class MwmActivity extends BaseMwmFragmentActivity
return;
int offsetY = mNavBarHeight;
if (mMapButtonsController != null)
offsetY = Math.max(offsetY, (int) mMapButtonsController.getBottomButtonsHeight() + mNavBarHeight);
final Float bottomButtonHeight = mMapButtonsViewModel.getBottomButtonsHeight().getValue();
if (bottomButtonHeight != null)
offsetY = Math.max(offsetY, bottomButtonHeight.intValue() + mNavBarHeight);
if (mMainMenu != null)
offsetY = Math.max(offsetY, mMainMenu.getMenuHeight());
@ -1271,17 +1253,17 @@ public class MwmActivity extends BaseMwmFragmentActivity
{
mNavigationController.show(true);
closeSearchToolbar(false, false);
mMainMenu.setState(MainMenu.State.NAVIGATION, mIsFullscreen);
mMainMenu.setState(MainMenu.State.NAVIGATION, isFullscreen());
return;
}
if (RoutingController.get().isPlanning())
{
mMainMenu.setState(MainMenu.State.ROUTE_PREPARE, mIsFullscreen);
mMainMenu.setState(MainMenu.State.ROUTE_PREPARE, isFullscreen());
return;
}
mMainMenu.setState(MainMenu.State.MENU, mIsFullscreen);
mMainMenu.setState(MainMenu.State.MENU, isFullscreen());
}
private boolean adjustMenuLineFrameVisibility()
@ -1508,7 +1490,8 @@ public class MwmActivity extends BaseMwmFragmentActivity
mRoutingPlanInplaceController.hideDrivingOptionsView();
mNavigationController.stop(this);
initNavigationButtons(MapButtonsController.LayoutMode.regular);
mMapButtonsViewModel.setSearchOption(null);
mMapButtonsViewModel.setLayoutMode(MapButtonsController.LayoutMode.regular);
refreshLightStatusBar();
}
@ -1518,7 +1501,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
closeFloatingToolbarsAndPanels(true);
ThemeSwitcher.INSTANCE.restart(isMapRendererActive());
mNavigationController.start(this);
initNavigationButtons(MapButtonsController.LayoutMode.navigation);
mMapButtonsViewModel.setLayoutMode(MapButtonsController.LayoutMode.navigation);
refreshLightStatusBar();
}
@ -1526,7 +1509,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
public void onPlanningCancelled()
{
closeFloatingToolbarsAndPanels(true);
initNavigationButtons(MapButtonsController.LayoutMode.regular);
mMapButtonsViewModel.setLayoutMode(MapButtonsController.LayoutMode.regular);
refreshLightStatusBar();
}
@ -1534,7 +1517,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
public void onPlanningStarted()
{
closeFloatingToolbarsAndPanels(true);
initNavigationButtons(MapButtonsController.LayoutMode.planning);
mMapButtonsViewModel.setLayoutMode(MapButtonsController.LayoutMode.planning);
refreshLightStatusBar();
}
@ -1544,7 +1527,8 @@ public class MwmActivity extends BaseMwmFragmentActivity
closeFloatingToolbarsAndPanels(true);
ThemeSwitcher.INSTANCE.restart(isMapRendererActive());
mNavigationController.stop(this);
initNavigationButtons(MapButtonsController.LayoutMode.planning);
mMapButtonsViewModel.setSearchOption(null);
mMapButtonsViewModel.setLayoutMode(MapButtonsController.LayoutMode.planning);
refreshLightStatusBar();
}
@ -1607,7 +1591,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
public void onMyPositionModeChanged(int newMode)
{
Logger.d(TAG, "location newMode = " + newMode);
mMapButtonsController.updateNavMyPositionButton(newMode);
mMapButtonsViewModel.setMyPositionMode(newMode);
RoutingController controller = RoutingController.get();
if (controller.isPlanning())
showAddStartOrFinishFrame(controller, true);
@ -1783,12 +1767,11 @@ public class MwmActivity extends BaseMwmFragmentActivity
shareMyLocation();
}
@Override
public void onLayerItemClick(@NonNull Mode mode)
public void onLayerChange(Mode mode)
{
closeFloatingPanels();
if (mMapButtonsController != null)
mMapButtonsController.toggleMapLayer(mode);
if (mPreviousLayerMode != mode)
closeFloatingPanels();
mPreviousLayerMode = mode;
}
@Override
@ -1812,8 +1795,6 @@ public class MwmActivity extends BaseMwmFragmentActivity
items.add(new MenuBottomSheetItem(R.string.share_my_location, R.drawable.ic_share, this::onShareLocationOptionSelected));
return items;
}
else if (id.equals(PLACEPAGE_MORE_MENU_ID))
return mPlacePageController.getMenuBottomSheetItems(id);
return null;
}
@ -1826,30 +1807,6 @@ public class MwmActivity extends BaseMwmFragmentActivity
return null;
}
@Override
public void onPlacePageButtonClick(PlacePageButtons.ButtonType item)
{
mPlacePageController.onPlacePageButtonClick(item);
}
@Override
public void onPlacePageContentChanged(int previewHeight, int frameHeight)
{
mPlacePageController.onPlacePageContentChanged(previewHeight, frameHeight);
}
@Override
public void onPlacePageRequestClose()
{
mPlacePageController.onPlacePageRequestClose();
}
@Override
public void onPlacePageRequestToggleState()
{
mPlacePageController.onPlacePageRequestToggleState();
}
@Override
public void onPlacePageRequestToggleRouteSettings(@NonNull RoadType roadType)
{

View file

@ -13,6 +13,10 @@ import androidx.annotation.Nullable;
import androidx.annotation.OptIn;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import app.organicmaps.MwmActivity;
import app.organicmaps.R;
import app.organicmaps.downloader.MapManager;
import app.organicmaps.downloader.UpdateInfo;
@ -21,7 +25,7 @@ import app.organicmaps.util.Config;
import app.organicmaps.util.ThemeUtils;
import app.organicmaps.util.UiUtils;
import app.organicmaps.widget.menu.MyPositionButton;
import app.organicmaps.widget.placepage.PlacePageController;
import app.organicmaps.widget.placepage.PlacePageViewModel;
import com.google.android.material.badge.BadgeDrawable;
import com.google.android.material.badge.BadgeUtils;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
@ -48,20 +52,28 @@ public class MapButtonsController extends Fragment
private float mContentWidth;
private MapButtonClickListener mMapButtonClickListener;
private View.OnClickListener mOnSearchCanceledListener;
private PlacePageController mPlacePageController;
private OnBottomButtonsHeightChangedListener mOnBottomButtonsHeightChangedListener;
private PlacePageViewModel mPlacePageViewModel;
private MapButtonsViewModel mMapButtonsViewModel;
private final Observer<Integer> mPlacePageDistanceToTopObserver = this::move;
private final Observer<Boolean> mButtonHiddenObserver = this::setButtonsHidden;
private final Observer<Integer> mMyPositionModeObserver = this::updateNavMyPositionButton;
private final Observer<Mode> mMapLayerModeObserver = this::toggleMapLayer;
private final Observer<SearchWheel.SearchOption> mSearchOptionObserver = this::onSearchOptionChange;
private LayoutMode mLayoutMode;
private int mMyPositionMode;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)
{
if (mLayoutMode == LayoutMode.navigation)
final FragmentActivity activity = requireActivity();
mMapButtonClickListener = (MwmActivity) activity;
mPlacePageViewModel = new ViewModelProvider(activity).get(PlacePageViewModel.class);
mMapButtonsViewModel = new ViewModelProvider(activity).get(MapButtonsViewModel.class);
final LayoutMode layoutMode = mMapButtonsViewModel.getLayoutMode().getValue();
if (layoutMode == LayoutMode.navigation)
mFrame = inflater.inflate(R.layout.map_buttons_layout_navigation, container, false);
else if (mLayoutMode == LayoutMode.planning)
else if (layoutMode == LayoutMode.planning)
mFrame = inflater.inflate(R.layout.map_buttons_layout_planning, container, false);
else
mFrame = inflater.inflate(R.layout.map_buttons_layout_regular, container, false);
@ -75,44 +87,51 @@ public class MapButtonsController extends Fragment
{
helpButton.setImageResource(R.drawable.logo);
// Keep this button colorful in normal theme.
if (!ThemeUtils.isNightTheme(getContext()))
if (!ThemeUtils.isNightTheme(requireContext()))
helpButton.getDrawable().setTintList(null);
}
final View zoomFrame = mFrame.findViewById(R.id.zoom_buttons_container);
mFrame.findViewById(R.id.nav_zoom_in)
.setOnClickListener((v) -> mMapButtonClickListener.onClick(MapButtons.zoomIn));
.setOnClickListener((v) -> mMapButtonClickListener.onMapButtonClick(MapButtons.zoomIn));
mFrame.findViewById(R.id.nav_zoom_out)
.setOnClickListener((v) -> mMapButtonClickListener.onClick(MapButtons.zoomOut));
.setOnClickListener((v) -> mMapButtonClickListener.onMapButtonClick(MapButtons.zoomOut));
final View bookmarksButton = mFrame.findViewById(R.id.btn_bookmarks);
bookmarksButton.setOnClickListener((v) -> mMapButtonClickListener.onClick(MapButtons.bookmarks));
bookmarksButton.setOnClickListener((v) -> mMapButtonClickListener.onMapButtonClick(MapButtons.bookmarks));
final View myPosition = mFrame.findViewById(R.id.my_position);
mNavMyPosition = new MyPositionButton(myPosition, mMyPositionMode, (v) -> mMapButtonClickListener.onClick(MapButtons.myPosition));
mNavMyPosition = new MyPositionButton(myPosition, (v) -> mMapButtonClickListener.onMapButtonClick(MapButtons.myPosition));
// Some buttons do not exist in navigation mode
final FloatingActionButton layersButton = mFrame.findViewById(R.id.layers_button);
if (layersButton != null)
{
mToggleMapLayerController = new MapLayersController(layersButton,
() -> mMapButtonClickListener.onClick(MapButtons.toggleMapLayer), requireActivity());
mToggleMapLayerController = new MapLayersController(
layersButton,
() -> mMapButtonClickListener.onMapButtonClick(MapButtons.toggleMapLayer),
requireActivity(),
mMapButtonsViewModel);
}
final View menuButton = mFrame.findViewById(R.id.menu_button);
if (menuButton != null)
{
menuButton.setOnClickListener((v) -> mMapButtonClickListener.onClick(MapButtons.menu));
menuButton.setOnClickListener((v) -> mMapButtonClickListener.onMapButtonClick(MapButtons.menu));
// This hack is needed to show the badge on the initial startup. For some reason, updateMenuBadge does not work from onResume() there.
menuButton.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
public void onGlobalLayout()
{
updateMenuBadge();
menuButton.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
}
if (helpButton != null)
helpButton.setOnClickListener((v) -> mMapButtonClickListener.onClick(MapButtons.help));
helpButton.setOnClickListener((v) -> mMapButtonClickListener.onMapButtonClick(MapButtons.help));
mSearchWheel = new SearchWheel(mFrame, (v) -> mMapButtonClickListener.onClick(MapButtons.search), mOnSearchCanceledListener);
mSearchWheel = new SearchWheel(mFrame,
(v) -> mMapButtonClickListener.onMapButtonClick(MapButtons.search),
(v) -> mMapButtonClickListener.onSearchCanceled(),
mMapButtonsViewModel);
final View searchButton = mFrame.findViewById(R.id.btn_search);
// Used to get the maximum height the buttons will evolve in
@ -135,25 +154,9 @@ public class MapButtonsController extends Fragment
UiUtils.setViewInsetsPadding(view, windowInsets);
return windowInsets;
});
return mFrame;
}
public LayoutMode getLayoutMode()
{
return mLayoutMode;
}
public void init(LayoutMode layoutMode, int myPositionMode, MapButtonClickListener mapButtonClickListener, @NonNull View.OnClickListener onSearchCanceledListener, PlacePageController placePageController, OnBottomButtonsHeightChangedListener onBottomButtonsHeightChangedListener)
{
mLayoutMode = layoutMode;
mMyPositionMode = myPositionMode;
mMapButtonClickListener = mapButtonClickListener;
mOnSearchCanceledListener = onSearchCanceledListener;
mPlacePageController = placePageController;
mOnBottomButtonsHeightChangedListener = onBottomButtonsHeightChangedListener;
}
public void showButton(boolean show, MapButtonsController.MapButtons button)
{
// TODO(AB): Why do we need this check? Isn't it better to crash and fix the wrong logic ASAP?
@ -209,7 +212,12 @@ public class MapButtonsController extends Fragment
private boolean isBehindPlacePage(View v)
{
return !(mContentWidth / 2 > (mPlacePageController.getPlacePageWidth() / 2.0) + v.getWidth());
if (mPlacePageViewModel == null)
return false;
final Integer placePageWidth = mPlacePageViewModel.getPlacePageWidth().getValue();
if (placePageWidth != null)
return !(mContentWidth / 2 > (placePageWidth.floatValue() / 2.0) + v.getWidth());
return true;
}
private boolean isMoving(View v)
@ -259,7 +267,7 @@ public class MapButtonsController extends Fragment
}
}
public float getBottomButtonsHeight()
private float getBottomButtonsHeight()
{
if (mBottomButtonsFrame != null && mFrame != null && UiUtils.isVisible(mFrame))
return mBottomButtonsFrame.getMeasuredHeight();
@ -267,16 +275,12 @@ public class MapButtonsController extends Fragment
return 0;
}
public void showMapButtons(boolean show)
public void setButtonsHidden(boolean buttonHidden)
{
if (show)
{
UiUtils.show(mFrame);
UiUtils.showIf(!buttonHidden, mFrame);
if (!buttonHidden)
updateButtonsVisibility();
}
else
UiUtils.hide(mFrame);
mOnBottomButtonsHeightChangedListener.OnBottomButtonsHeightChanged();
mMapButtonsViewModel.setBottomButtonsHeight(getBottomButtonsHeight());
}
private boolean isInNavigationMode()
@ -284,10 +288,15 @@ public class MapButtonsController extends Fragment
return RoutingController.get().isPlanning() || RoutingController.get().isNavigating();
}
public void toggleMapLayer(@NonNull Mode mode)
public void toggleMapLayer(@Nullable Mode mode)
{
if (mToggleMapLayerController != null)
mToggleMapLayerController.toggleMode(mode);
{
if (mode == null)
mToggleMapLayerController.disableModes();
else
mToggleMapLayerController.enableMode(mode);
}
}
public void updateNavMyPositionButton(int newMode)
@ -301,6 +310,18 @@ public class MapButtonsController extends Fragment
return (int) (translation + v.getTop());
}
@Override
public void onStart()
{
super.onStart();
final FragmentActivity activity = requireActivity();
mPlacePageViewModel.getPlacePageDistanceToTop().observe(activity, mPlacePageDistanceToTopObserver);
mMapButtonsViewModel.getButtonsHidden().observe(activity, mButtonHiddenObserver);
mMapButtonsViewModel.getMyPositionMode().observe(activity, mMyPositionModeObserver);
mMapButtonsViewModel.getMapLayerMode().observe(activity, mMapLayerModeObserver);
mMapButtonsViewModel.getSearchOption().observe(activity, mSearchOptionObserver);
}
public void onResume()
{
super.onResume();
@ -308,19 +329,21 @@ public class MapButtonsController extends Fragment
updateMenuBadge();
}
public void resetSearch()
@Override
public void onStop()
{
mSearchWheel.reset();
super.onStop();
mPlacePageViewModel.getPlacePageDistanceToTop().removeObserver(mPlacePageDistanceToTopObserver);
mMapButtonsViewModel.getButtonsHidden().removeObserver(mButtonHiddenObserver);
mMapButtonsViewModel.getMyPositionMode().removeObserver(mMyPositionModeObserver);
mMapButtonsViewModel.getMapLayerMode().removeObserver(mMapLayerModeObserver);
mMapButtonsViewModel.getSearchOption().removeObserver(mSearchOptionObserver);
}
public void saveNavSearchState(@NonNull Bundle outState)
public void onSearchOptionChange(@Nullable SearchWheel.SearchOption searchOption)
{
mSearchWheel.saveState(outState);
}
public void restoreNavSearchState(@NonNull Bundle savedInstanceState)
{
mSearchWheel.restoreState(savedInstanceState);
if (searchOption == null)
mSearchWheel.reset();
}
public enum LayoutMode
@ -345,12 +368,9 @@ public class MapButtonsController extends Fragment
public interface MapButtonClickListener
{
void onClick(MapButtons button);
}
void onMapButtonClick(MapButtons button);
public interface OnBottomButtonsHeightChangedListener
{
void OnBottomButtonsHeightChanged();
void onSearchCanceled();
}
private class ContentViewLayoutChangeListener implements View.OnLayoutChangeListener
@ -369,7 +389,7 @@ public class MapButtonsController extends Fragment
{
mContentHeight = bottom - top;
mContentWidth = right - left;
mOnBottomButtonsHeightChangedListener.OnBottomButtonsHeightChanged();
mMapButtonsViewModel.setBottomButtonsHeight(getBottomButtonsHeight());
mContentView.removeOnLayoutChangeListener(this);
}
}

View file

@ -0,0 +1,75 @@
package app.organicmaps.maplayer;
import androidx.annotation.Nullable;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
public class MapButtonsViewModel extends ViewModel
{
private final MutableLiveData<Boolean> mButtonsHidden = new MutableLiveData<>(false);
private final MutableLiveData<Float> mBottomButtonsHeight = new MutableLiveData<>(0f);
private final MutableLiveData<MapButtonsController.LayoutMode> mLayoutMode = new MutableLiveData<>(MapButtonsController.LayoutMode.regular);
private final MutableLiveData<Integer> mMyPositionMode = new MutableLiveData<>();
private final MutableLiveData<Mode> mMapLayerMode = new MutableLiveData<>();
private final MutableLiveData<SearchWheel.SearchOption> mSearchOption = new MutableLiveData<>();
vng commented 2023-04-27 16:06:20 +00:00 (Migrated from github.com)
Review

Just curious, what is a MutableLiveData and why it is needed here?

Just curious, what is a MutableLiveData and why it is needed here?
arnaudvergnet commented 2023-04-29 13:28:29 +00:00 (Migrated from github.com)
Review

Here is the documentation: https://developer.android.com/topic/libraries/architecture/livedata#java.

This is a lifecycle aware observable value. When you create the view model, you tie it to an activity. Then the live data inside that view model follows the activity life cycle, so it does not trigger callbacks when the activity is stopped and automatically removes listeners when it is destroyed.

In our case, when we use the viewmodels in fragments we still have to remove the listeners because they have a shorter life cycle than the parent activity (to prevent the listeners from trying to update a destroyed UI).

Tying the view model to the parent activity allows us the reuse the value across different fragments. So the viewmodels store the UI state, and fragments can simply observe the live data to update their UI. No more onSavedInstanceState, no more dependency injection all over the place and we are sure all the fragments use the same state.

This makes debugging a lot easier and makes the codebase more robust. But then again I am no Android expert.

Here is the documentation: https://developer.android.com/topic/libraries/architecture/livedata#java. **This is a lifecycle aware observable value**. When you create the view model, you tie it to an activity. Then the live data inside that view model follows the activity life cycle, so it does not trigger callbacks when the activity is stopped and automatically removes listeners when it is destroyed. In our case, when we use the viewmodels in fragments we still have to remove the listeners because they have a shorter life cycle than the parent activity (to prevent the listeners from trying to update a destroyed UI). Tying the view model to the parent activity allows us the reuse the value across different fragments. So the viewmodels store the UI state, and fragments can simply observe the live data to update their UI. No more `onSavedInstanceState`, no more dependency injection all over the place and we are sure all the fragments use the same state. This makes debugging a lot easier and makes the codebase more robust. But then again I am no Android expert.
public MutableLiveData<Boolean> getButtonsHidden()
{
return mButtonsHidden;
}
public void setButtonsHidden(boolean buttonsHidden)
{
mButtonsHidden.setValue(buttonsHidden);
}
public MutableLiveData<Float> getBottomButtonsHeight()
{
return mBottomButtonsHeight;
}
public void setBottomButtonsHeight(float height)
{
mBottomButtonsHeight.setValue(height);
}
public MutableLiveData<MapButtonsController.LayoutMode> getLayoutMode()
{
return mLayoutMode;
}
public void setLayoutMode(MapButtonsController.LayoutMode layoutMode)
{
mLayoutMode.setValue(layoutMode);
}
public MutableLiveData<Integer> getMyPositionMode()
{
return mMyPositionMode;
}
public void setMyPositionMode(int mode)
{
mMyPositionMode.setValue(mode);
}
public MutableLiveData<Mode> getMapLayerMode()
{
return mMapLayerMode;
}
public void setMapLayerMode(Mode mode)
{
mMapLayerMode.setValue(mode);
}
public MutableLiveData<SearchWheel.SearchOption> getSearchOption()
{
return mSearchOption;
}
public void setSearchOption(@Nullable SearchWheel.SearchOption searchOption)
{
mSearchOption.setValue(searchOption);
}
}

View file

@ -23,21 +23,19 @@ public class MapLayersController
OnShowMenuListener mOnShowMenuListener;
@NonNull
private Mode mCurrentLayer;
private final MapButtonsViewModel mMapButtonsViewModel;
public MapLayersController(@NonNull ImageButton layersButton, @NonNull OnShowMenuListener onShowMenuListener, @NonNull Activity activity)
public MapLayersController(@NonNull ImageButton layersButton, @NonNull OnShowMenuListener onShowMenuListener, @NonNull Activity activity, MapButtonsViewModel mapButtonsViewModel)
{
mActivity = activity;
mMapButtonsViewModel = mapButtonsViewModel;
mLayersButton = layersButton;
mLayersButton.setOnClickListener(view -> onLayersButtonClick());
mOnShowMenuListener = onShowMenuListener;
mLayers = LayersUtils.getAvailableLayers();
mCurrentLayer = getCurrentLayer();
initMode();
}
private void initMode()
{
setEnabled(mCurrentLayer.isEnabled(mActivity));
// View model only expects a layer if it is active
mMapButtonsViewModel.setMapLayerMode(mCurrentLayer.isEnabled(activity) ? mCurrentLayer : null);
showButton(true);
}
@ -76,16 +74,21 @@ public class MapLayersController
private void onLayersButtonClick()
{
if (mCurrentLayer.isEnabled(mActivity))
setEnabled(false);
mMapButtonsViewModel.setMapLayerMode(null);
else
mOnShowMenuListener.onShow();
}
public void toggleMode(@NonNull Mode mode)
public void disableModes()
{
setEnabled(false);
}
public void enableMode(@NonNull Mode mode)
{
setCurrentLayer(mode);
showButton(true);
setEnabled(!mode.isEnabled(mActivity));
setEnabled(true);
}
public void showButton(boolean show)

View file

@ -26,7 +26,6 @@ import app.organicmaps.util.concurrency.UiThread;
public class SearchWheel implements View.OnClickListener
{
private static final String EXTRA_CURRENT_OPTION = "extra_current_option";
private final View mFrame;
private View mSearchLayout;
@ -35,12 +34,11 @@ public class SearchWheel implements View.OnClickListener
private final View mTouchInterceptor;
private boolean mIsExpanded;
@Nullable
private SearchOption mCurrentOption;
@NonNull
private final View.OnClickListener mOnSearchPressedListener;
@NonNull
private final View.OnClickListener mOnSearchCanceledListener;
private MapButtonsViewModel mMapButtonsViewModel;
private static final long CLOSE_DELAY_MILLIS = 5000L;
private final Runnable mCloseRunnable = new Runnable() {
@ -55,7 +53,7 @@ public class SearchWheel implements View.OnClickListener
}
};
private enum SearchOption
public enum SearchOption
{
FUEL(R.id.search_fuel, R.drawable.ic_routing_fuel_off, R.string.fuel),
PARKING(R.id.search_parking, R.drawable.ic_routing_parking_off, R.string.parking),
@ -103,9 +101,11 @@ public class SearchWheel implements View.OnClickListener
}
}
public SearchWheel(View frame, @NonNull View.OnClickListener onSearchPressedListener, @NonNull View.OnClickListener onSearchCanceledListener)
public SearchWheel(View frame, @NonNull View.OnClickListener onSearchPressedListener,
@NonNull View.OnClickListener onSearchCanceledListener, MapButtonsViewModel mapButtonsViewModel)
{
mFrame = frame;
mMapButtonsViewModel = mapButtonsViewModel;
mOnSearchPressedListener = onSearchPressedListener;
mOnSearchCanceledListener = onSearchCanceledListener;
mTouchInterceptor = mFrame.findViewById(R.id.touch_interceptor);
@ -151,26 +151,15 @@ public class SearchWheel implements View.OnClickListener
UiUtils.showIf(show && mIsExpanded, mSearchLayout);
}
public void saveState(@NonNull Bundle outState)
{
outState.putSerializable(EXTRA_CURRENT_OPTION, mCurrentOption);
}
public void restoreState(@NonNull Bundle savedState)
{
mCurrentOption = Utils.getSerializable(savedState, EXTRA_CURRENT_OPTION, SearchOption.class);
}
public void reset()
{
mIsExpanded = false;
mCurrentOption = null;
resetSearchButtonImage();
}
public void onResume()
{
if (mCurrentOption != null)
if (mMapButtonsViewModel.getSearchOption().getValue() != null)
{
refreshSearchButtonImage();
return;
@ -183,7 +172,6 @@ public class SearchWheel implements View.OnClickListener
return;
}
mCurrentOption = SearchOption.fromSearchQuery(query, mFrame.getContext());
refreshSearchButtonImage();
}
@ -244,10 +232,11 @@ public class SearchWheel implements View.OnClickListener
private void refreshSearchButtonImage()
{
final SearchOption searchOption = mMapButtonsViewModel.getSearchOption().getValue();
mSearchButton.setImageDrawable(Graphics.tint(mSearchButton.getContext(),
mCurrentOption == null ?
searchOption == null ?
R.drawable.ic_routing_search_off :
mCurrentOption.mDrawableOff,
searchOption.mDrawableOff,
R.attr.colorAccent));
}
@ -278,7 +267,7 @@ public class SearchWheel implements View.OnClickListener
return;
}
if (mCurrentOption != null || !TextUtils.isEmpty(SearchEngine.INSTANCE.getQuery()))
if (mMapButtonsViewModel.getSearchOption().getValue() != null || !TextUtils.isEmpty(SearchEngine.INSTANCE.getQuery()))
{
mOnSearchCanceledListener.onClick(v);
refreshSearchVisibility();
@ -303,7 +292,7 @@ public class SearchWheel implements View.OnClickListener
private void startSearch(SearchOption searchOption)
{
mCurrentOption = searchOption;
mMapButtonsViewModel.setSearchOption(searchOption);
final String query = mFrame.getContext().getString(searchOption.mQueryId);
// Category request from navigation search wheel.
SearchEngine.INSTANCE.searchInteractive(mFrame.getContext(), query, true, System.nanoTime(), false);

View file

@ -9,24 +9,23 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import app.organicmaps.R;
import app.organicmaps.maplayer.isolines.IsolinesManager;
import app.organicmaps.widget.recycler.SpanningLinearLayoutManager;
import app.organicmaps.util.SharedPropertiesUtils;
import app.organicmaps.util.Utils;
import app.organicmaps.widget.recycler.SpanningLinearLayoutManager;
import java.util.ArrayList;
import java.util.List;
public class ToggleMapLayerFragment extends Fragment
{
@Nullable
private LayerItemClickListener mLayerItemClickListener;
@Nullable
private LayersAdapter mAdapter;
private MapButtonsViewModel mMapButtonsViewModel;
@Nullable
@Override
@ -34,8 +33,7 @@ public class ToggleMapLayerFragment extends Fragment
{
View mRoot = inflater.inflate(R.layout.fragment_toggle_map_layer, container, false);
if (requireActivity() instanceof LayerItemClickListener)
mLayerItemClickListener = ((LayerItemClickListener) requireActivity());
mMapButtonsViewModel = new ViewModelProvider(requireActivity()).get(MapButtonsViewModel.class);
initRecycler(mRoot);
return mRoot;
@ -72,12 +70,6 @@ public class ToggleMapLayerFragment extends Fragment
mAdapter.notifyDataSetChanged();
if (IsolinesManager.from(context).shouldShowNotification())
Utils.showSnackbar(context, v.getRootView(), R.string.isolines_toast_zooms_1_10);
if (mLayerItemClickListener != null)
mLayerItemClickListener.onLayerItemClick(mode);
}
public interface LayerItemClickListener
{
void onLayerItemClick(@NonNull Mode mode);
mMapButtonsViewModel.setMapLayerMode(mode);
}
}

View file

@ -21,17 +21,16 @@ import androidx.appcompat.app.AppCompatActivity;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
import app.organicmaps.Framework;
import app.organicmaps.MwmActivity;
import app.organicmaps.R;
import app.organicmaps.base.MediaPlayerWrapper;
import app.organicmaps.maplayer.MapButtonsController;
import app.organicmaps.maplayer.traffic.TrafficManager;
import app.organicmaps.sound.TtsPlayer;
import app.organicmaps.widget.menu.NavMenu;
import app.organicmaps.util.UiUtils;
import app.organicmaps.util.Utils;
import app.organicmaps.widget.menu.NavMenu;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
import java.util.Arrays;
@ -59,10 +58,7 @@ public class NavigationController implements Application.ActivityLifecycleCallba
private final RecyclerView mLanes;
@NonNull
private final LanesAdapter mLanesAdapter;
@NonNull
private final MapButtonsController mMapButtonsController;
@NonNull
private final MediaPlayer.OnCompletionListener mSpeedCamSignalCompletionListener;
@ -106,13 +102,12 @@ public class NavigationController implements Application.ActivityLifecycleCallba
mLanes.setNestedScrollingEnabled(false);
}
public NavigationController(AppCompatActivity activity, @NonNull MapButtonsController mapButtonsController,
View.OnClickListener onSettingsClickListener, NavMenu.OnMenuSizeChangedListener onMenuSizeChangedListener)
public NavigationController(AppCompatActivity activity, View.OnClickListener onSettingsClickListener,
NavMenu.OnMenuSizeChangedListener onMenuSizeChangedListener)
{
mFrame = activity.findViewById(R.id.navigation_frame);
mNavMenu = new NavMenu(activity, this, onMenuSizeChangedListener);
mOnSettingsClickListener = onSettingsClickListener;
mMapButtonsController = mapButtonsController;
// Top frame
View topFrame = mFrame.findViewById(R.id.nav_top_frame);
@ -154,8 +149,6 @@ public class NavigationController implements Application.ActivityLifecycleCallba
public void stop(MwmActivity parent)
{
mMapButtonsController.resetSearch();
if (mBound)
{
parent.unbindService(mServiceConnection);
@ -330,7 +323,6 @@ public class NavigationController implements Application.ActivityLifecycleCallba
public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState)
{
outState.putBoolean(STATE_BOUND, mBound);
mMapButtonsController.saveNavSearchState(outState);
}
public void onRestoreState(@NonNull Bundle savedInstanceState, @NonNull MwmActivity parent)
@ -338,7 +330,6 @@ public class NavigationController implements Application.ActivityLifecycleCallba
mBound = savedInstanceState.getBoolean(STATE_BOUND);
if (mBound)
start(parent);
mMapButtonsController.restoreNavSearchState(savedInstanceState);
}
@Override

View file

@ -30,13 +30,13 @@ public class MyPositionButton
private final int mFollowPaddingShift;
public MyPositionButton(@NonNull View button, int myPositionMode, @NonNull View.OnClickListener listener)
public MyPositionButton(@NonNull View button, @NonNull View.OnClickListener listener)
{
mButton = (FloatingActionButton) button;
mButton.setOnClickListener(listener);
mIcons.clear();
mFollowPaddingShift = (int) (FOLLOW_SHIFT * button.getResources().getDisplayMetrics().density);
update(myPositionMode);
update(LocationState.nativeGetMode());
}
public void update(int mode)

View file

@ -1,6 +1,5 @@
package app.organicmaps.widget.placepage;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
@ -50,28 +49,24 @@ public final class PlacePageButtons extends Fragment implements Observer<List<Pl
return windowInsets;
});
mMaxButtons = getResources().getInteger(R.integer.pp_buttons_max);
mViewModel.getCurrentButtons().observe(requireActivity(), this);
Fragment parentFragment = getParentFragment();
mItemListener = (PlacePageButtonClickListener) parentFragment;
createButtons(mViewModel.getCurrentButtons().getValue());
}
@Override
public void onAttach(@NonNull Context context)
public void onStart()
{
super.onAttach(context);
try
{
mItemListener = (PlacePageButtonClickListener) context;
}
catch (ClassCastException e)
{
throw new ClassCastException(context + " must implement PlacePageButtonClickListener");
}
super.onStart();
mViewModel.getCurrentButtons().observe(requireActivity(), this);
}
@Override
public void onDestroyView()
public void onStop()
{
super.onDestroyView();
super.onStop();
mViewModel.getCurrentButtons().removeObserver(this);
}
@ -94,7 +89,7 @@ public final class PlacePageButtons extends Fragment implements Observer<List<Pl
private void showMoreBottomSheet()
{
MenuBottomSheetFragment.newInstance(PLACEPAGE_MORE_MENU_ID)
.show(requireActivity().getSupportFragmentManager(), PLACEPAGE_MORE_MENU_ID);
.show(getParentFragmentManager(), PLACEPAGE_MORE_MENU_ID);
}
private void createButtons(@Nullable List<ButtonType> buttons)

View file

@ -1,101 +1,87 @@
package app.organicmaps.widget.placepage;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.core.widget.NestedScrollViewClickFixed;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import app.organicmaps.Framework;
import app.organicmaps.MwmActivity;
import app.organicmaps.R;
import app.organicmaps.api.Const;
import app.organicmaps.api.ParsedMwmRequest;
import app.organicmaps.base.Initializable;
import app.organicmaps.base.Savable;
import app.organicmaps.bookmarks.data.BookmarkManager;
import app.organicmaps.bookmarks.data.MapObject;
import app.organicmaps.bookmarks.data.RoadWarningMarkType;
import app.organicmaps.routing.RoutingController;
import app.organicmaps.settings.RoadType;
import app.organicmaps.util.SharingUtils;
import app.organicmaps.util.UiUtils;
import app.organicmaps.util.bottomsheet.MenuBottomSheetFragment;
import app.organicmaps.util.bottomsheet.MenuBottomSheetItem;
import app.organicmaps.util.log.Logger;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
public class PlacePageController implements Initializable<Activity>,
Savable<Bundle>,
PlacePageView.PlacePageViewListener,
PlacePageButtons.PlacePageButtonClickListener,
MenuBottomSheetFragment.MenuBottomSheetInterface,
Observer<MapObject>
public class PlacePageController extends Fragment implements
PlacePageView.PlacePageViewListener,
PlacePageButtons.PlacePageButtonClickListener,
MenuBottomSheetFragment.MenuBottomSheetInterface,
Observer<MapObject>
{
private static final String TAG = PlacePageController.class.getSimpleName();
private static final String PLACE_PAGE_BUTTONS_FRAGMENT_TAG = "PLACE_PAGE_BUTTONS";
private static final String PLACE_PAGE_FRAGMENT_TAG = "PLACE_PAGE";
private static final float PREVIEW_PLUS_RATIO = 0.45f;
@NonNull
private final SlideListener mSlideListener;
@NonNull
private final BottomSheetBehavior<View> mPlacePageBehavior;
@NonNull
private final NestedScrollViewClickFixed mPlacePage;
@NonNull
private final ViewGroup mPlacePageContainer;
private final ViewGroup mCoordinator;
private final int mViewportMinHeight;
private final AppCompatActivity mMwmActivity;
private final int mButtonsHeight;
private final int mMaxButtons;
private final PlacePageViewModel mViewModel;
private BottomSheetBehavior<View> mPlacePageBehavior;
private NestedScrollViewClickFixed mPlacePage;
private ViewGroup mPlacePageContainer;
private ViewGroup mCoordinator;
private int mViewportMinHeight;
private int mButtonsHeight;
private int mMaxButtons;
private PlacePageViewModel mViewModel;
private int mPreviewHeight;
private int mFrameHeight;
private boolean mDeactivateMapSelection = true;
@Nullable
private MapObject mMapObject;
@Nullable
private MapObject mPreviousMapObject;
private WindowInsetsCompat mCurrentWindowInsets;
private boolean mShouldCollapse;
private int mDistanceToTop;
private ValueAnimator mCustomPeekHeightAnimator;
class DefaultBottomSheetCallback extends BottomSheetBehavior.BottomSheetCallback
private PlacePageRouteSettingsListener mPlacePageRouteSettingsListener;
private final BottomSheetBehavior.BottomSheetCallback mDefaultBottomSheetCallback = new BottomSheetBehavior.BottomSheetCallback()
{
@Override
public void onStateChanged(@NonNull View bottomSheet, int newState)
{
final Lifecycle.State state = mMwmActivity.getLifecycle().getCurrentState();
if (!state.isAtLeast(Lifecycle.State.RESUMED))
{
Logger.e(TAG, "Called in the wrong activity state=" + state);
return;
}
Logger.d(TAG, "State change, new = " + PlacePageUtils.toString(newState));
if (PlacePageUtils.isSettlingState(newState) || PlacePageUtils.isDraggingState(newState))
return;
PlacePageUtils.moveViewportUp(mPlacePage, mViewportMinHeight);
PlacePageUtils.updateMapViewport(mCoordinator, mDistanceToTop, mViewportMinHeight);
if (PlacePageUtils.isHiddenState(newState))
onHiddenInternal();
@ -104,51 +90,48 @@ public class PlacePageController implements Initializable<Activity>,
@Override
public void onSlide(@NonNull View bottomSheet, float slideOffset)
{
final Lifecycle.State state = mMwmActivity.getLifecycle().getCurrentState();
if (!state.isAtLeast(Lifecycle.State.RESUMED))
{
Logger.e(TAG, "Called in the wrong activity state=" + state);
return;
}
stopCustomPeekHeightAnimation();
mDistanceToTop = bottomSheet.getTop();
mSlideListener.onPlacePageSlide(mDistanceToTop);
mViewModel.setPlacePageDistanceToTop(mDistanceToTop);
}
};
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)
{
return inflater.inflate(R.layout.place_page_container_fragment, container, false);
}
@SuppressLint("ClickableViewAccessibility")
public PlacePageController(@Nullable Activity activity,
@NonNull SlideListener listener)
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState)
{
mSlideListener = listener;
super.onViewCreated(view, savedInstanceState);
final FragmentActivity activity = requireActivity();
mPlacePageRouteSettingsListener = (MwmActivity) activity;
Objects.requireNonNull(activity);
mMwmActivity = (AppCompatActivity) activity;
mCoordinator = mMwmActivity.findViewById(R.id.coordinator);
Resources res = activity.getResources();
final Resources res = activity.getResources();
mViewportMinHeight = res.getDimensionPixelSize(R.dimen.viewport_min_height);
mPlacePage = activity.findViewById(R.id.placepage);
mPlacePageContainer = activity.findViewById(R.id.placepage_container);
mButtonsHeight = (int) res.getDimension(R.dimen.place_page_buttons_height);
mMaxButtons = res.getInteger(R.integer.pp_buttons_max);
mCoordinator = activity.findViewById(R.id.coordinator);
mPlacePage = view.findViewById(R.id.placepage);
mPlacePageContainer = view.findViewById(R.id.placepage_container);
mPlacePageBehavior = BottomSheetBehavior.from(mPlacePage);
mShouldCollapse = true;
mPlacePageBehavior.addBottomSheetCallback(new DefaultBottomSheetCallback());
mPlacePageBehavior.setHideable(true);
mPlacePageBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
mPlacePageBehavior.setFitToContents(true);
mPlacePageBehavior.setSkipCollapsed(true);
UiUtils.bringViewToFrontOf(activity.findViewById(R.id.pp_buttons_fragment), mPlacePage);
UiUtils.bringViewToFrontOf(view.findViewById(R.id.pp_buttons_fragment), mPlacePage);
mPlacePage.requestApplyInsets();
mViewModel = new ViewModelProvider(requireActivity()).get(PlacePageViewModel.class);
mButtonsHeight = (int) mMwmActivity.getResources()
.getDimension(R.dimen.place_page_buttons_height);
mMaxButtons = mMwmActivity.getResources().getInteger(R.integer.pp_buttons_max);
mViewModel = new ViewModelProvider(mMwmActivity).get(PlacePageViewModel.class);
ViewCompat.setOnApplyWindowInsetsListener(mPlacePage, (view, windowInsets) -> {
ViewCompat.setOnApplyWindowInsetsListener(mPlacePage, (v, windowInsets) -> {
mCurrentWindowInsets = windowInsets;
return windowInsets;
});
@ -181,19 +164,12 @@ public class PlacePageController implements Initializable<Activity>,
private void onHiddenInternal()
{
if (mDeactivateMapSelection)
Framework.nativeDeactivatePopup();
mDeactivateMapSelection = true;
PlacePageUtils.moveViewportUp(mPlacePage, mViewportMinHeight);
Framework.nativeDeactivatePopup();
PlacePageUtils.updateMapViewport(mCoordinator, mDistanceToTop, mViewportMinHeight);
resetPlacePageHeightBounds();
removePlacePageFragments();
}
public int getPlacePageWidth()
{
return mPlacePage.getWidth();
}
@Nullable
public ArrayList<MenuBottomSheetItem> getMenuBottomSheetItems(String id)
{
@ -203,24 +179,27 @@ public class PlacePageController implements Initializable<Activity>,
ArrayList<MenuBottomSheetItem> items = new ArrayList<>();
for (int i = mMaxButtons - 1; i < currentItems.size(); i++)
{
final PlacePageButton bsItem = PlacePageButtonFactory.createButton(currentItems.get(i), mMwmActivity);
final PlacePageButton bsItem = PlacePageButtonFactory.createButton(currentItems.get(i), requireActivity());
items.add(new MenuBottomSheetItem(
bsItem.getTitle(),
bsItem.getIcon(),
() -> ((PlacePageButtons.PlacePageButtonClickListener) mMwmActivity).onPlacePageButtonClick(bsItem.getType())
() -> onPlacePageButtonClick(bsItem.getType())
));
}
return items;
}
public void openFor(@NonNull PlacePageData data)
private void open()
{
mDeactivateMapSelection = true;
MapObject mapObject = (MapObject) data;
final MapObject previousMapObject = mViewModel.getMapObject().getValue();
// Only collapse the place page if the data is different from the one already available
mShouldCollapse = PlacePageUtils.isHiddenState(mPlacePageBehavior.getState()) || !MapObject.same(previousMapObject, mapObject);
mViewModel.setMapObject(mapObject);
mShouldCollapse = PlacePageUtils.isHiddenState(mPlacePageBehavior.getState()) || !MapObject.same(mPreviousMapObject, mMapObject);
mPreviousMapObject = mMapObject;
// Place page will automatically open when the bottom sheet content is loaded so we can compute the peek height
}
private void close()
{
mPlacePageBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
}
private void resetPlacePageHeightBounds()
@ -299,10 +278,10 @@ public class PlacePageController implements Initializable<Activity>,
mPlacePageBehavior.setPeekHeight(value);
// The place page is not firing the slide callbacks when using this animation, so we must call them manually
mDistanceToTop = parentHeight - value - bottomInsets;
mSlideListener.onPlacePageSlide(mDistanceToTop);
mViewModel.setPlacePageDistanceToTop(mDistanceToTop);
if (value == peekHeight)
{
PlacePageUtils.moveViewportUp(mPlacePage, mViewportMinHeight);
PlacePageUtils.updateMapViewport(mCoordinator, mDistanceToTop, mViewportMinHeight);
setPlacePageHeightBounds();
}
});
@ -317,37 +296,12 @@ public class PlacePageController implements Initializable<Activity>,
return mPreviewHeight + mButtonsHeight;
}
public void close(boolean deactivateMapSelection)
{
mDeactivateMapSelection = deactivateMapSelection;
mPlacePageBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
}
public boolean isClosed()
{
return PlacePageUtils.isHiddenState(mPlacePageBehavior.getState());
}
@Override
public void onSave(@NonNull Bundle outState)
{
// no op
}
@Override
public void onRestore(@NonNull Bundle inState)
{
if (mPlacePageBehavior.getState() == BottomSheetBehavior.STATE_HIDDEN)
return;
if (!Framework.nativeHasPlacePageInfo())
close(false);
}
@Override
public void onPlacePageContentChanged(int previewHeight, int frameHeight)
{
mPreviewHeight = previewHeight;
mFrameHeight = frameHeight;
mViewModel.setPlacePageWidth(mPlacePage.getWidth());
// Make sure to update the peek height on the UI thread to prevent weird animation jumps
mPlacePage.post(() -> {
setPeekHeight();
@ -357,12 +311,6 @@ public class PlacePageController implements Initializable<Activity>,
});
}
@Override
public void onPlacePageRequestClose()
{
close(true);
}
@Override
public void onPlacePageRequestToggleState()
{
@ -375,24 +323,150 @@ public class PlacePageController implements Initializable<Activity>,
mPlacePageBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
}
@Override
public void onPlacePageRequestToggleRouteSettings(@NonNull RoadType roadType)
{
// no op
}
@Override
public void onPlacePageButtonClick(PlacePageButtons.ButtonType item)
{
@Nullable final PlacePageView placePageFragment = (PlacePageView) mMwmActivity.getSupportFragmentManager()
.findFragmentByTag(PLACE_PAGE_FRAGMENT_TAG);
if (placePageFragment != null)
placePageFragment.onPlacePageButtonClick(item);
switch (item)
{
case BOOKMARK_SAVE:
case BOOKMARK_DELETE:
onBookmarkBtnClicked();
break;
case SHARE:
onShareBtnClicked();
break;
case BACK:
onBackBtnClicked();
break;
case ROUTE_FROM:
onRouteFromBtnClicked();
break;
case ROUTE_TO:
onRouteToBtnClicked();
break;
case ROUTE_ADD:
onRouteAddBtnClicked();
break;
case ROUTE_REMOVE:
onRouteRemoveBtnClicked();
break;
case ROUTE_AVOID_TOLL:
onAvoidTollBtnClicked();
break;
case ROUTE_AVOID_UNPAVED:
onAvoidUnpavedBtnClicked();
break;
case ROUTE_AVOID_FERRY:
onAvoidFerryBtnClicked();
break;
}
}
private void onBookmarkBtnClicked()
{
// No need to call setMapObject here as the native methods will reopen the place page
if (MapObject.isOfType(MapObject.BOOKMARK, mMapObject))
Framework.nativeDeleteBookmarkFromMapObject();
else
BookmarkManager.INSTANCE.addNewBookmark(mMapObject.getLat(), mMapObject.getLon());
}
private void onShareBtnClicked()
{
if (mMapObject != null)
SharingUtils.shareMapObject(requireContext(), mMapObject);
}
private void onBackBtnClicked()
{
if (mMapObject == null)
return;
final ParsedMwmRequest request = ParsedMwmRequest.getCurrentRequest();
if (request != null && request.isPickPointMode())
{
final Intent result = new Intent();
result.putExtra(Const.EXTRA_POINT_LAT, mMapObject.getLat())
.putExtra(Const.EXTRA_POINT_LON, mMapObject.getLon())
.putExtra(Const.EXTRA_POINT_NAME, mMapObject.getTitle())
.putExtra(Const.EXTRA_POINT_ID, mMapObject.getApiId())
.putExtra(Const.EXTRA_ZOOM_LEVEL, Framework.nativeGetDrawScale());
requireActivity().setResult(Activity.RESULT_OK, result);
ParsedMwmRequest.setCurrentRequest(null);
}
requireActivity().finish();
}
private void onRouteFromBtnClicked()
{
if (mMapObject == null)
return;
RoutingController controller = RoutingController.get();
if (!controller.isPlanning())
{
controller.prepare(mMapObject, null);
close();
}
else if (controller.setStartPoint(mMapObject))
close();
}
private void onRouteToBtnClicked()
{
if (mMapObject == null)
return;
if (RoutingController.get().isPlanning())
{
RoutingController.get().setEndPoint(mMapObject);
close();
}
else
((MwmActivity) requireActivity()).startLocationToPoint(mMapObject);
}
private void onRouteAddBtnClicked()
{
if (mMapObject != null)
RoutingController.get().addStop(mMapObject);
}
private void onRouteRemoveBtnClicked()
{
if (mMapObject != null)
RoutingController.get().removeStop(mMapObject);
}
private void onAvoidUnpavedBtnClicked()
{
onAvoidBtnClicked(RoadType.Dirty);
}
private void onAvoidFerryBtnClicked()
{
onAvoidBtnClicked(RoadType.Ferry);
}
private void onAvoidTollBtnClicked()
{
onAvoidBtnClicked(RoadType.Toll);
}
private void onAvoidBtnClicked(@NonNull RoadType roadType)
{
mPlacePageRouteSettingsListener.onPlacePageRequestToggleRouteSettings(roadType);
}
private void removePlacePageFragments()
{
final FragmentManager fm = mMwmActivity.getSupportFragmentManager();
final FragmentManager fm = getChildFragmentManager();
final Fragment placePageButtonsFragment = fm.findFragmentByTag(PLACE_PAGE_BUTTONS_FRAGMENT_TAG);
final Fragment placePageFragment = fm.findFragmentByTag(PLACE_PAGE_FRAGMENT_TAG);
@ -401,23 +475,21 @@ public class PlacePageController implements Initializable<Activity>,
fm.beginTransaction()
.setReorderingAllowed(true)
.remove(placePageButtonsFragment)
.commitNow();
.commit();
}
if (placePageFragment != null)
{
// Make sure to synchronously remove the fragment so setting the map object to null
// won't impact the fragment
fm.beginTransaction()
.setReorderingAllowed(true)
.remove(placePageFragment)
.commitNow();
.commit();
}
mViewModel.setMapObject(null);
}
private void createPlacePageFragments()
{
final FragmentManager fm = mMwmActivity.getSupportFragmentManager();
final FragmentManager fm = getChildFragmentManager();
if (fm.findFragmentByTag(PLACE_PAGE_FRAGMENT_TAG) == null)
{
fm.beginTransaction()
@ -486,29 +558,42 @@ public class PlacePageController implements Initializable<Activity>,
mMapObject = mapObject;
if (mapObject != null)
{
open();
createPlacePageFragments();
updateButtons(
mapObject,
MapObject.isOfType(MapObject.API_POINT, mMapObject),
!MapObject.isOfType(MapObject.MY_POSITION, mMapObject));
}
} else
close();
}
@Override
public void initialize(@Nullable Activity activity)
public void onStart()
{
Objects.requireNonNull(activity);
mViewModel.getMapObject().observe((MwmActivity) activity, this);
super.onStart();
mPlacePageBehavior.addBottomSheetCallback(mDefaultBottomSheetCallback);
mViewModel.getMapObject().observe(requireActivity(), this);
}
@Override
public void destroy()
public void onResume()
{
super.onResume();
if (mPlacePageBehavior.getState() != BottomSheetBehavior.STATE_HIDDEN && !Framework.nativeHasPlacePageInfo())
mViewModel.setMapObject(null);
}
@Override
public void onStop()
{
super.onStop();
mPlacePageBehavior.removeBottomSheetCallback(mDefaultBottomSheetCallback);
mViewModel.getMapObject().removeObserver(this);
}
public interface SlideListener
public interface PlacePageRouteSettingsListener
{
void onPlacePageSlide(int top);
void onPlacePageRequestToggleRouteSettings(@NonNull RoadType roadType);
}
}

View file

@ -1,10 +1,8 @@
package app.organicmaps.widget.placepage;
import android.content.Context;
import android.graphics.Rect;
import android.view.Menu;
import android.view.View;
import android.view.ViewGroup;
import android.widget.PopupMenu;
import androidx.annotation.NonNull;
@ -15,20 +13,14 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior;
import java.util.List;
class PlacePageUtils
public class PlacePageUtils
{
static void moveViewportUp(@NonNull View placePageView, int viewportMinHeight)
static void updateMapViewport(@NonNull View parent, int placePageDistanceToTop, int viewportMinHeight)
{
placePageView.post(() -> {
final View coordinatorLayout = (ViewGroup) placePageView.getParent();
final int viewPortWidth = coordinatorLayout.getWidth();
int viewPortHeight = coordinatorLayout.getHeight();
Rect sheetRect = new Rect();
placePageView.getGlobalVisibleRect(sheetRect);
viewPortHeight -= sheetRect.height();
if (viewPortHeight >= viewportMinHeight)
Framework.nativeSetVisibleRect(0, 0, viewPortWidth, viewPortHeight);
parent.post(() -> {
final int screenWidth = parent.getWidth();
if (placePageDistanceToTop >= viewportMinHeight)
Framework.nativeSetVisibleRect(0, 0, screenWidth, placePageDistanceToTop);
});
}
@ -84,7 +76,7 @@ class PlacePageUtils
}
}
static void copyToClipboard(Context context, View frame, String text)
public static void copyToClipboard(Context context, View frame, String text)
{
Utils.copyTextToClipboard(context, text);
Utils.showSnackbarAbove(frame,
@ -92,7 +84,7 @@ class PlacePageUtils
context.getString(R.string.copied_to_clipboard, text));
}
static void showCopyPopup(Context context, View popupAnchor, View frame, List<String> items)
public static void showCopyPopup(Context context, View popupAnchor, View frame, List<String> items)
{
final PopupMenu popup = new PopupMenu(context, popupAnchor);
final Menu menu = popup.getMenu();

View file

@ -1,8 +1,6 @@
package app.organicmaps.widget.placepage;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.location.Location;
import android.os.Bundle;
import android.text.SpannableStringBuilder;
@ -29,9 +27,6 @@ import app.organicmaps.Framework;
import app.organicmaps.MwmActivity;
import app.organicmaps.MwmApplication;
import app.organicmaps.R;
import app.organicmaps.api.Const;
import app.organicmaps.api.ParsedMwmRequest;
import app.organicmaps.bookmarks.data.BookmarkManager;
import app.organicmaps.bookmarks.data.DistanceAndAzimut;
import app.organicmaps.bookmarks.data.MapObject;
import app.organicmaps.bookmarks.data.Metadata;
@ -42,12 +37,15 @@ import app.organicmaps.editor.Editor;
import app.organicmaps.location.LocationHelper;
import app.organicmaps.location.LocationListener;
import app.organicmaps.routing.RoutingController;
import app.organicmaps.settings.RoadType;
import app.organicmaps.util.SharingUtils;
import app.organicmaps.util.StringUtils;
import app.organicmaps.util.UiUtils;
import app.organicmaps.util.concurrency.UiThread;
import app.organicmaps.widget.ArrowView;
import app.organicmaps.widget.placepage.sections.PlacePageBookmarkFragment;
import app.organicmaps.widget.placepage.sections.PlacePageLinksFragment;
import app.organicmaps.widget.placepage.sections.PlacePageOpeningHoursFragment;
import app.organicmaps.widget.placepage.sections.PlacePagePhoneFragment;
import app.organicmaps.widget.placepage.sections.PlacePageWikipediaFragment;
import java.util.ArrayList;
import java.util.Arrays;
@ -58,7 +56,6 @@ import static android.view.View.VISIBLE;
public class PlacePageView extends Fragment implements View.OnClickListener,
View.OnLongClickListener,
PlacePageButtons.PlacePageButtonClickListener,
LocationListener,
Observer<MapObject>
@ -176,6 +173,9 @@ public class PlacePageView extends Fragment implements View.OnClickListener,
MwmApplication.prefs(requireContext()).getInt(
PREF_COORDINATES_FORMAT, CoordinatesFormat.LatLonDecimal.getId()));
Fragment parentFragment = getParentFragment();
mPlacePageViewListener = (PlacePageViewListener) parentFragment;
mFrame = view;
mFrame.setOnClickListener((v) -> mPlacePageViewListener.onPlacePageRequestToggleState());
@ -238,186 +238,30 @@ public class PlacePageView extends Fragment implements View.OnClickListener,
mDownloaderIcon = new DownloaderStatusIcon(mPreview.findViewById(R.id.downloader_status_frame));
mDownloaderInfo = mPreview.findViewById(R.id.tv__downloader_details);
mMapObject = mViewModel.getMapObject().getValue();
}
@Override
public void onAttach(@NonNull Context context)
public void onStart()
{
super.onAttach(context);
mPlacePageViewListener = (MwmActivity) context;
}
@Override
public void onResume()
{
super.onResume();
super.onStart();
mViewModel.getMapObject().observe(requireActivity(), this);
LocationHelper.INSTANCE.addListener(this);
}
@Override
public void onPause()
{
super.onPause();
// Unsubscribe from events as soon as the fragment becomes inactive
// to prevent unwanted side effects
mViewModel.getMapObject().removeObserver(this);
LocationHelper.INSTANCE.removeListener(this);
setCurrentCountry();
}
@Override
public void onStop()
{
super.onStop();
// Safely detach the country once the fragment is hidden from the user
// It is safer to call this here than in onPause as the app could go from onPause to
// onResume without killing the fragment.
// In this case we would not want to detach the country.
mViewModel.getMapObject().removeObserver(this);
LocationHelper.INSTANCE.removeListener(this);
detachCountry();
}
@Override
public void onPlacePageButtonClick(PlacePageButtons.ButtonType item)
{
switch (item)
{
case BOOKMARK_SAVE:
case BOOKMARK_DELETE:
onBookmarkBtnClicked();
break;
case SHARE:
onShareBtnClicked();
break;
case BACK:
onBackBtnClicked();
break;
case ROUTE_FROM:
onRouteFromBtnClicked();
break;
case ROUTE_TO:
onRouteToBtnClicked();
break;
case ROUTE_ADD:
onRouteAddBtnClicked();
break;
case ROUTE_REMOVE:
onRouteRemoveBtnClicked();
break;
case ROUTE_AVOID_TOLL:
onAvoidTollBtnClicked();
break;
case ROUTE_AVOID_UNPAVED:
onAvoidUnpavedBtnClicked();
break;
case ROUTE_AVOID_FERRY:
onAvoidFerryBtnClicked();
break;
}
}
private void onBookmarkBtnClicked()
{
// No need to call setMapObject here as the native methods will reopen the place page
if (MapObject.isOfType(MapObject.BOOKMARK, mMapObject))
Framework.nativeDeleteBookmarkFromMapObject();
else
BookmarkManager.INSTANCE.addNewBookmark(mMapObject.getLat(), mMapObject.getLon());
}
private void onShareBtnClicked()
{
SharingUtils.shareMapObject(requireContext(), mMapObject);
}
private void onBackBtnClicked()
{
final ParsedMwmRequest request = ParsedMwmRequest.getCurrentRequest();
if (request != null && request.isPickPointMode())
{
final Intent result = new Intent();
result.putExtra(Const.EXTRA_POINT_LAT, mMapObject.getLat())
.putExtra(Const.EXTRA_POINT_LON, mMapObject.getLon())
.putExtra(Const.EXTRA_POINT_NAME, mMapObject.getTitle())
.putExtra(Const.EXTRA_POINT_ID, mMapObject.getApiId())
.putExtra(Const.EXTRA_ZOOM_LEVEL, Framework.nativeGetDrawScale());
requireActivity().setResult(Activity.RESULT_OK, result);
ParsedMwmRequest.setCurrentRequest(null);
}
requireActivity().finish();
}
private void onRouteFromBtnClicked()
{
RoutingController controller = RoutingController.get();
if (!controller.isPlanning())
{
controller.prepare(mMapObject, null);
mPlacePageViewListener.onPlacePageRequestClose();
}
else if (controller.setStartPoint(mMapObject))
{
mPlacePageViewListener.onPlacePageRequestClose();
}
}
private void onRouteToBtnClicked()
{
if (RoutingController.get().isPlanning())
{
RoutingController.get().setEndPoint(mMapObject);
mPlacePageViewListener.onPlacePageRequestClose();
}
else
{
((MwmActivity) requireActivity()).startLocationToPoint(mMapObject);
}
}
private void onRouteAddBtnClicked()
{
RoutingController.get().addStop(mMapObject);
}
private void onRouteRemoveBtnClicked()
{
RoutingController.get().removeStop(mMapObject);
}
private void onAvoidUnpavedBtnClicked()
{
onAvoidBtnClicked(RoadType.Dirty);
}
private void onAvoidFerryBtnClicked()
{
onAvoidBtnClicked(RoadType.Ferry);
}
private void onAvoidTollBtnClicked()
{
onAvoidBtnClicked(RoadType.Toll);
}
private void onAvoidBtnClicked(@NonNull RoadType roadType)
{
mPlacePageViewListener.onPlacePageRequestToggleRouteSettings(roadType);
}
private void setCurrentCountry()
{
if (mCurrentCountry != null)
throw new AssertionError("country should be detached before!");
if (mCurrentCountry != null || mMapObject == null)
return;
String country = MapManager.nativeGetSelectedCountry();
if (country != null && !RoutingController.get().isNavigating())
attachCountry(country);
@ -450,7 +294,7 @@ public class PlacePageView extends Fragment implements View.OnClickListener,
fm.beginTransaction()
.setReorderingAllowed(true)
.remove(fragment)
.commitNow();
.commit();
}
}
@ -499,7 +343,7 @@ public class PlacePageView extends Fragment implements View.OnClickListener,
int end = text.lastIndexOf("") + 1;
if (start > -1)
{
sb.setSpan(new ForegroundColorSpan(ContextCompat.getColor(getContext(), R.color.base_yellow)),
sb.setSpan(new ForegroundColorSpan(ContextCompat.getColor(requireContext(), R.color.base_yellow)),
start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
}
mTvSubtitle.setText(sb);
@ -753,8 +597,10 @@ public class PlacePageView extends Fragment implements View.OnClickListener,
}
@Override
public void onChanged(MapObject mapObject)
public void onChanged(@Nullable MapObject mapObject)
{
if (mapObject == null)
return;
if (!mapObject.sameAs(mMapObject))
{
detachCountry();
@ -773,6 +619,8 @@ public class PlacePageView extends Fragment implements View.OnClickListener,
@Override
public void onLocationUpdated(@NonNull Location location)
{
if (mMapObject == null)
return;
if (MapObject.isOfType(MapObject.MY_POSITION, mMapObject))
refreshMyPosition(location);
else
@ -782,7 +630,7 @@ public class PlacePageView extends Fragment implements View.OnClickListener,
@Override
public void onCompassUpdated(double north)
{
if (MapObject.isOfType(MapObject.MY_POSITION, mMapObject))
if (mMapObject == null || MapObject.isOfType(MapObject.MY_POSITION, mMapObject))
return;
final Location location = LocationHelper.INSTANCE.getSavedLocation();
@ -810,10 +658,6 @@ public class PlacePageView extends Fragment implements View.OnClickListener,
// Called when the content has actually changed and we are ready to compute the peek height
void onPlacePageContentChanged(int previewHeight, int frameHeight);
void onPlacePageRequestClose();
void onPlacePageRequestToggleState();
void onPlacePageRequestToggleRouteSettings(@NonNull RoadType roadType);
}
}

View file

@ -11,6 +11,8 @@ public class PlacePageViewModel extends ViewModel
{
private final MutableLiveData<List<PlacePageButtons.ButtonType>> mCurrentButtons = new MutableLiveData<>();
private final MutableLiveData<MapObject> mMapObject = new MutableLiveData<>();
private final MutableLiveData<Integer> mPlacePageWidth = new MutableLiveData<>();
private final MutableLiveData<Integer> mPlacePageDistanceToTop = new MutableLiveData<>();
vng commented 2023-04-27 16:16:03 +00:00 (Migrated from github.com)
Review

Same here, why MutableLiveData ?

Same here, why MutableLiveData ?
public LiveData<List<PlacePageButtons.ButtonType>> getCurrentButtons()
{
@ -31,4 +33,24 @@ public class PlacePageViewModel extends ViewModel
{
mMapObject.setValue(mapObject);
}
public MutableLiveData<Integer> getPlacePageWidth()
{
return mPlacePageWidth;
}
public void setPlacePageWidth(int width)
{
mPlacePageWidth.setValue(width);
}
public MutableLiveData<Integer> getPlacePageDistanceToTop()
{
return mPlacePageDistanceToTop;
}
public void setPlacePageDistanceToTop(int top)
{
mPlacePageDistanceToTop.setValue(top);
}
}

View file

@ -1,4 +1,4 @@
package app.organicmaps.widget.placepage;
package app.organicmaps.widget.placepage.sections;
import android.view.LayoutInflater;
import android.view.View;

View file

@ -1,4 +1,4 @@
package app.organicmaps.widget.placepage;
package app.organicmaps.widget.placepage.sections;
import android.content.Context;
import android.os.Bundle;
@ -26,6 +26,8 @@ import app.organicmaps.bookmarks.data.MapObject;
import app.organicmaps.util.StringUtils;
import app.organicmaps.util.UiUtils;
import app.organicmaps.util.Utils;
import app.organicmaps.widget.placepage.EditBookmarkFragment;
import app.organicmaps.widget.placepage.PlacePageViewModel;
public class PlacePageBookmarkFragment extends Fragment implements View.OnClickListener,
View.OnLongClickListener,
@ -73,16 +75,16 @@ public class PlacePageBookmarkFragment extends Fragment implements View.OnClickL
}
@Override
public void onResume()
public void onStart()
{
super.onResume();
super.onStart();
mViewModel.getMapObject().observe(requireActivity(), this);
}
@Override
public void onPause()
public void onStop()
{
super.onPause();
super.onStop();
mViewModel.getMapObject().removeObserver(this);
}
@ -141,12 +143,12 @@ public class PlacePageBookmarkFragment extends Fragment implements View.OnClickL
}
@Override
public void onChanged(MapObject mapObject)
public void onChanged(@Nullable MapObject mapObject)
{
// MapObject could be something else than a bookmark if the user already has the place page
// opened and clicks on a non-bookmarked POI.
// This callback would be called before the fragment had time to be destroyed
if (mapObject.getMapObjectType() == MapObject.BOOKMARK)
if (mapObject != null && mapObject.getMapObjectType() == MapObject.BOOKMARK)
{
currentBookmark = (Bookmark) mapObject;
updateBookmarkDetails();

View file

@ -1,4 +1,4 @@
package app.organicmaps.widget.placepage;
package app.organicmaps.widget.placepage.sections;
import android.os.Bundle;
import android.text.TextUtils;
@ -17,6 +17,8 @@ import app.organicmaps.R;
import app.organicmaps.bookmarks.data.MapObject;
import app.organicmaps.bookmarks.data.Metadata;
import app.organicmaps.util.Utils;
import app.organicmaps.widget.placepage.PlacePageUtils;
import app.organicmaps.widget.placepage.PlacePageViewModel;
import java.util.ArrayList;
import java.util.Arrays;
@ -216,23 +218,26 @@ public class PlacePageLinksFragment extends Fragment implements Observer<MapObje
}
@Override
public void onResume()
public void onStart()
{
super.onResume();
super.onStart();
mViewModel.getMapObject().observe(requireActivity(), this);
}
@Override
public void onPause()
public void onStop()
{
super.onPause();
super.onStop();
mViewModel.getMapObject().removeObserver(this);
}
@Override
public void onChanged(MapObject mapObject)
public void onChanged(@Nullable MapObject mapObject)
{
mMapObject = mapObject;
refreshLinks();
if (mapObject != null)
{
mMapObject = mapObject;
refreshLinks();
}
}
}

View file

@ -1,4 +1,4 @@
package app.organicmaps.widget.placepage;
package app.organicmaps.widget.placepage.sections;
import android.content.res.Resources;
import android.os.Bundle;
@ -25,6 +25,8 @@ import app.organicmaps.editor.data.Timetable;
import app.organicmaps.util.ThemeUtils;
import app.organicmaps.util.UiUtils;
import app.organicmaps.util.Utils;
import app.organicmaps.widget.placepage.PlacePageUtils;
import app.organicmaps.widget.placepage.PlacePageViewModel;
import java.util.Calendar;
import java.util.Locale;
@ -88,11 +90,9 @@ public class PlacePageOpeningHoursFragment extends Fragment implements Observer<
mTodayOpenTime.setTextColor(color);
}
private void refreshOpeningHours()
private void refreshOpeningHours(MapObject mapObject)
{
final String ohStr = mViewModel.getMapObject()
.getValue()
.getMetadata(Metadata.MetadataType.FMD_OPEN_HOURS);
final String ohStr = mapObject.getMetadata(Metadata.MetadataType.FMD_OPEN_HOURS);
final Timetable[] timetables = OpeningHours.nativeTimetablesFromString(ohStr);
mFrame.setOnLongClickListener((v) -> {
PlacePageUtils.copyToClipboard(requireContext(), mFrame, TimeFormatUtils.formatTimetables(getResources(), ohStr, timetables));
@ -170,7 +170,7 @@ public class PlacePageOpeningHoursFragment extends Fragment implements Observer<
// Show that place is closed today.
if (!containsCurrentWeekday)
{
refreshTodayOpeningHours(resources.getString(R.string.day_off_today), ContextCompat.getColor(getContext(), R.color.base_red));
refreshTodayOpeningHours(resources.getString(R.string.day_off_today), ContextCompat.getColor(requireContext(), R.color.base_red));
UiUtils.hide(mTodayNonBusinessTime);
}
}
@ -178,22 +178,23 @@ public class PlacePageOpeningHoursFragment extends Fragment implements Observer<
}
@Override
public void onResume()
public void onStart()
{
super.onResume();
super.onStart();
mViewModel.getMapObject().observe(requireActivity(), this);
}
@Override
public void onPause()
public void onStop()
{
super.onPause();
super.onStop();
mViewModel.getMapObject().removeObserver(this);
biodranik commented 2023-04-09 21:48:18 +00:00 (Migrated from github.com)
Review
    if (mapObject != null)
      refreshOpeningHours(mapObject);
```suggestion if (mapObject != null) refreshOpeningHours(mapObject); ```
}
@Override
public void onChanged(MapObject mapObject)
public void onChanged(@Nullable MapObject mapObject)
{
refreshOpeningHours();
if (mapObject != null)
refreshOpeningHours(mapObject);
}
}

View file

@ -1,4 +1,4 @@
package app.organicmaps.widget.placepage;
package app.organicmaps.widget.placepage.sections;
import android.os.Bundle;
import android.view.LayoutInflater;
@ -14,6 +14,7 @@ import androidx.recyclerview.widget.RecyclerView;
import app.organicmaps.R;
import app.organicmaps.bookmarks.data.MapObject;
import app.organicmaps.bookmarks.data.Metadata;
import app.organicmaps.widget.placepage.PlacePageViewModel;
public class PlacePagePhoneFragment extends Fragment implements Observer<MapObject>
{
@ -39,22 +40,23 @@ public class PlacePagePhoneFragment extends Fragment implements Observer<MapObje
}
@Override
public void onResume()
public void onStart()
{
super.onResume();
super.onStart();
mViewModel.getMapObject().observe(requireActivity(), this);
}
@Override
public void onPause()
public void onStop()
{
super.onPause();
super.onStop();
mViewModel.getMapObject().removeObserver(this);
biodranik commented 2023-04-09 21:49:00 +00:00 (Migrated from github.com)
Review
    if (mapObject != null)
      mPhoneAdapter.refreshPhones(mapObject.getMetadata(Metadata.MetadataType.FMD_PHONE_NUMBER));
```suggestion if (mapObject != null) mPhoneAdapter.refreshPhones(mapObject.getMetadata(Metadata.MetadataType.FMD_PHONE_NUMBER)); ```
}
@Override
public void onChanged(MapObject mapObject)
public void onChanged(@Nullable MapObject mapObject)
{
mPhoneAdapter.refreshPhones(mapObject.getMetadata(Metadata.MetadataType.FMD_PHONE_NUMBER));
if (mapObject != null)
mPhoneAdapter.refreshPhones(mapObject.getMetadata(Metadata.MetadataType.FMD_PHONE_NUMBER));
}
}

View file

@ -1,4 +1,4 @@
package app.organicmaps.widget.placepage;
package app.organicmaps.widget.placepage.sections;
import android.os.Bundle;
import android.text.SpannableStringBuilder;
@ -19,6 +19,9 @@ import app.organicmaps.bookmarks.data.MapObject;
import app.organicmaps.bookmarks.data.Metadata;
import app.organicmaps.util.Utils;
import app.organicmaps.util.UiUtils;
import app.organicmaps.widget.placepage.PlaceDescriptionActivity;
import app.organicmaps.widget.placepage.PlacePageUtils;
import app.organicmaps.widget.placepage.PlacePageViewModel;
public class PlacePageWikipediaFragment extends Fragment implements Observer<MapObject>
{
@ -109,23 +112,26 @@ public class PlacePageWikipediaFragment extends Fragment implements Observer<Map
}
@Override
public void onResume()
public void onStart()
{
super.onResume();
super.onStart();
mViewModel.getMapObject().observe(requireActivity(), this);
}
@Override
public void onPause()
public void onStop()
{
super.onPause();
super.onStop();
mViewModel.getMapObject().removeObserver(this);
}
@Override
public void onChanged(MapObject mapObject)
public void onChanged(@Nullable MapObject mapObject)
{
mMapObject = mapObject;
updateViews();
if (mapObject != null)
{
mMapObject = mapObject;
updateViews();
}
}
}

View file

@ -1,4 +1,4 @@
package app.organicmaps.widget.placepage;
package app.organicmaps.widget.placepage.sections;
import android.content.Context;
import android.text.TextUtils;

View file

@ -1,9 +1,10 @@
package app.organicmaps.widget.placepage;
package app.organicmaps.widget.placepage.sections;
import app.organicmaps.editor.data.HoursMinutes;
import app.organicmaps.editor.data.Timespan;
import app.organicmaps.editor.data.Timetable;
import app.organicmaps.widget.placepage.PlaceOpeningHoursAdapter.WeekScheduleData;
import app.organicmaps.widget.placepage.sections.PlaceOpeningHoursAdapter;
import app.organicmaps.widget.placepage.sections.PlaceOpeningHoursAdapter.WeekScheduleData;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;