[android] Implement main menu bottom sheet controller

This commit is contained in:
Александр Зацепин 2020-04-17 20:30:38 +03:00 committed by yoksnod
parent 870cdd7a63
commit e90471c167
13 changed files with 347 additions and 23 deletions

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main_menu_sheet"
android:layout_width="@dimen/main_menu_bottom_sheet_width"
android:layout_height="wrap_content"
android:background="?cardBackground"
android:clickable="true"
android:focusable="true"
android:fillViewport="true"
app:behavior_defaultState="hidden"
app:behavior_skipAnchored="true"
app:behavior_skipCollapsed="true"
app:behavior_hideable="true"
app:behavior_peekHeight="0dp"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<include layout="@layout/menu_content" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>

View file

@ -97,6 +97,7 @@
<include layout="@layout/divider_horizontal"/>
</FrameLayout>
<include layout="@layout/elevation_profile_bottom_sheet" />
<include layout="@layout/main_menu_bottom_sheet" />
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/app_bar"
app:layout_behavior="@string/placepage_toolbar_behavior"

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main_menu_sheet"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?cardBackground"
android:clickable="true"
android:focusable="true"
android:fillViewport="true"
app:behavior_defaultState="hidden"
app:behavior_hideable="true"
app:behavior_skipAnchored="true"
app:behavior_skipCollapsed="true"
app:behavior_peekHeight="0dp"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<include layout="@layout/menu_content" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>

View file

@ -111,6 +111,7 @@
<dimen name="menu_line_button_inset_top">-20dp</dimen>
<dimen name="menu_line_button_inset_edge">-16dp</dimen>
<dimen name="menu_list_item_height">48dp</dimen>
<dimen name="main_menu_bottom_sheet_width">375dp</dimen>
<!-- Nav menu -->
<dimen name="nav_elevation">6dp</dimen>

View file

@ -124,6 +124,8 @@ import com.mapswithme.maps.tips.TutorialAction;
import com.mapswithme.maps.widget.FadeView;
import com.mapswithme.maps.widget.menu.BaseMenu;
import com.mapswithme.maps.widget.menu.MainMenu;
import com.mapswithme.maps.widget.menu.MenuController;
import com.mapswithme.maps.widget.menu.MenuStateObserver;
import com.mapswithme.maps.widget.menu.MyPositionButton;
import com.mapswithme.maps.widget.placepage.PlacePageController;
import com.mapswithme.maps.widget.placepage.PlacePageData;
@ -271,6 +273,9 @@ public class MwmActivity extends BaseMwmFragmentActivity
@SuppressWarnings("NullableProblems")
@NonNull
private PlacePageController mPlacePageController;
@SuppressWarnings("NullableProblems")
@NonNull
private MenuController mMainMenuController;
@Nullable
private Tutorial mTutorial;
@Nullable
@ -528,6 +533,10 @@ public class MwmActivity extends BaseMwmFragmentActivity
mPlacePageController.initialize(this);
mPlacePageController.onActivityCreated(this, savedInstanceState);
// TODO: set state observer.
mMainMenuController = com.mapswithme.maps.widget.menu.Factory.createMainMenuController(new MainMenuStateObserver());
mMainMenuController.initialize(findViewById(R.id.coordinator));
boolean isLaunchByDeepLink = getIntent().getBooleanExtra(EXTRA_LAUNCH_BY_DEEP_LINK, false);
initViews(isLaunchByDeepLink);
@ -826,8 +835,11 @@ public class MwmActivity extends BaseMwmFragmentActivity
public void closeMenu(@Nullable Runnable procAfterClose)
{
mFadeView.fadeOut();
mMainMenu.close(true, procAfterClose);
mMainMenuController.close();
if (procAfterClose != null)
procAfterClose.run();
}
private boolean closePositionChooser()
{
if (UiUtils.isVisible(mPositionChooser))
@ -1355,18 +1367,12 @@ public class MwmActivity extends BaseMwmFragmentActivity
{
super.onResume();
mSearchController.refreshToolbar();
mMainMenu.onResume(new Runnable()
mMainMenu.onResume(null);
if (Framework.nativeIsInChoosePositionMode())
{
@Override
public void run()
{
if (Framework.nativeIsInChoosePositionMode())
{
UiUtils.show(mPositionChooser);
setFullscreen(true);
}
}
});
UiUtils.show(mPositionChooser);
setFullscreen(true);
}
if (mOnmapDownloader != null)
mOnmapDownloader.onResume();
@ -1460,6 +1466,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
mToggleMapLayerController.detachCore();
TrafficManager.INSTANCE.detachAll();
mPlacePageController.destroy();
mMainMenuController.destroy();
SearchEngine.INSTANCE.removeListener(this);
}
@ -1756,11 +1763,6 @@ public class MwmActivity extends BaseMwmFragmentActivity
return this;
}
public MainMenu getMainMenu()
{
return mMainMenu;
}
@Override
public void showSearch()
{
@ -1782,12 +1784,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
return;
}
if (mIsTabletLayout)
{
mMainMenu.setEnabled(MainMenu.Item.POINT_TO_POINT, !RoutingController.get().isPlanning());
mMainMenu.setEnabled(MainMenu.Item.SEARCH, !RoutingController.get().isWaitingPoiPick());
}
else if (RoutingController.get().isPlanning())
if (RoutingController.get().isPlanning())
{
mMainMenu.setState(MainMenu.State.ROUTE_PREPARE, false, mIsFullscreen);
return;
@ -2666,6 +2663,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
return;
Toast.makeText(getActivity(), "Open bottom sheet menu!", Toast.LENGTH_SHORT).show();
getActivity().mMainMenuController.open();
}
}
@ -2857,4 +2855,20 @@ public class MwmActivity extends BaseMwmFragmentActivity
: UiUtils.getStatusBarHeight(getApplicationContext()));
}
}
private class MainMenuStateObserver implements MenuStateObserver
{
@Override
public void onMenuOpen()
{
mFadeView.fadeIn();
}
@Override
public void onMenuClosed()
{
mFadeView.fadeOut();
}
}
}

View file

@ -0,0 +1,101 @@
package com.mapswithme.maps.widget.menu;
import android.annotation.SuppressLint;
import android.view.View;
import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.GestureDetectorCompat;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
import com.mapswithme.util.log.Logger;
import com.mapswithme.util.log.LoggerFactory;
import java.util.Objects;
public class BottomSheetMenuController implements MenuController
{
private static final Logger LOGGER = LoggerFactory.INSTANCE.getLogger(LoggerFactory.Type.MISC);
private static final String TAG = BottomSheetMenuController.class.getSimpleName();
@SuppressWarnings("NullableProblems")
@NonNull
private BottomSheetBehavior<View> mSheetBehavior;
@IdRes
private final int mSheetResId;
@NonNull
private final MenuRenderer mMenuRenderer;
@Nullable
private MenuStateObserver mStateObserver;
private final BottomSheetBehavior.BottomSheetCallback mSheetCallback
= new BottomSheetBehavior.BottomSheetCallback()
{
@Override
public void onStateChanged(@NonNull View view, int state)
{
LOGGER.d(TAG, "State change, new = " + BottomSheetMenuUtils.toString(state));
if (BottomSheetMenuUtils.isSettlingState(state) || BottomSheetMenuUtils.isDraggingState(state))
return;
if (mStateObserver == null)
return;
if (BottomSheetMenuUtils.isHiddenState(state))
{
mStateObserver.onMenuClosed();
return;
}
mStateObserver.onMenuOpen();
}
@Override
public void onSlide(@NonNull View view, float v)
{
// Do nothing by default.
}
};
BottomSheetMenuController(int sheetResId, @NonNull MenuRenderer menuRenderer,
@Nullable MenuStateObserver stateObserver)
{
mSheetResId = sheetResId;
mMenuRenderer = menuRenderer;
mStateObserver = stateObserver;
}
@Override
public void open()
{
mMenuRenderer.render();
mSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
}
@Override
public void close()
{
mSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
}
@SuppressLint("ClickableViewAccessibility")
@Override
public void initialize(@Nullable View view)
{
Objects.requireNonNull(view);
View sheet = view.findViewById(mSheetResId);
Objects.requireNonNull(sheet);
mSheetBehavior = BottomSheetBehavior.from(sheet);
// FIXME: bottom sheet should be hidden by default without this call.
mSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
mSheetBehavior.setBottomSheetCallback(mSheetCallback);
GestureDetectorCompat gestureDetector = new GestureDetectorCompat(
view.getContext(), new BottomSheetMenuGestureListener(mSheetBehavior));
sheet.setOnTouchListener((v, event) -> gestureDetector.onTouchEvent(event));
mMenuRenderer.initialize(sheet);
}
@Override
public void destroy()
{
mMenuRenderer.destroy();
}
}

View file

@ -0,0 +1,35 @@
package com.mapswithme.maps.widget.menu;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import androidx.annotation.NonNull;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
import com.trafi.anchorbottomsheetbehavior.AnchorBottomSheetBehavior;
public class BottomSheetMenuGestureListener extends GestureDetector.SimpleOnGestureListener
{
@NonNull
private final BottomSheetBehavior<View> mBottomSheetBehavior;
public BottomSheetMenuGestureListener(@NonNull BottomSheetBehavior<View> bottomSheetBehavior)
{
mBottomSheetBehavior = bottomSheetBehavior;
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e)
{
@AnchorBottomSheetBehavior.State
int state = mBottomSheetBehavior.getState();
if (!BottomSheetMenuUtils.isHiddenState(state) && !BottomSheetMenuUtils.isDraggingState(state)
&& !BottomSheetMenuUtils.isSettlingState(state))
{
mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
return true;
}
return false;
}
}

View file

@ -0,0 +1,52 @@
package com.mapswithme.maps.widget.menu;
import androidx.annotation.NonNull;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
class BottomSheetMenuUtils
{
static boolean isSettlingState(int state)
{
return state == BottomSheetBehavior.STATE_SETTLING;
}
static boolean isDraggingState(int state)
{
return state == BottomSheetBehavior.STATE_DRAGGING;
}
static boolean isCollapsedState(int state)
{
return state == BottomSheetBehavior.STATE_COLLAPSED;
}
static boolean isExpandedState(int state)
{
return state == BottomSheetBehavior.STATE_EXPANDED;
}
static boolean isHiddenState(int state)
{
return state == BottomSheetBehavior.STATE_HIDDEN;
}
@NonNull
static String toString(int state)
{
switch (state)
{
case BottomSheetBehavior.STATE_EXPANDED:
return "EXPANDED";
case BottomSheetBehavior.STATE_COLLAPSED:
return "COLLAPSED";
case BottomSheetBehavior.STATE_DRAGGING:
return "DRAGGING";
case BottomSheetBehavior.STATE_SETTLING:
return "SETTLING";
case BottomSheetBehavior.STATE_HIDDEN:
return "HIDDEN";
default:
throw new AssertionError("Unsupported state detected: " + state);
}
}
}

View file

@ -0,0 +1,18 @@
package com.mapswithme.maps.widget.menu;
import android.app.Activity;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.mapswithme.maps.R;
public class Factory
{
@NonNull
public static MenuController createMainMenuController(@Nullable MenuStateObserver stateObserver)
{
return new BottomSheetMenuController(R.id.main_menu_sheet, new MainMenuRenderer(),
stateObserver);
}
}

View file

@ -0,0 +1,26 @@
package com.mapswithme.maps.widget.menu;
import android.view.View;
import androidx.annotation.Nullable;
public class MainMenuRenderer implements MenuRenderer
{
@Override
public void render()
{
// TODO: Implement.
}
@Override
public void initialize(@Nullable View view)
{
// TODO: Implement.
}
@Override
public void destroy()
{
// TODO: Implement.
}
}

View file

@ -0,0 +1,11 @@
package com.mapswithme.maps.widget.menu;
import android.view.View;
import com.mapswithme.maps.base.Initializable;
public interface MenuController extends Initializable<View>
{
void open();
void close();
}

View file

@ -0,0 +1,10 @@
package com.mapswithme.maps.widget.menu;
import android.view.View;
import com.mapswithme.maps.base.Initializable;
public interface MenuRenderer extends Initializable<View>
{
void render();
}

View file

@ -0,0 +1,7 @@
package com.mapswithme.maps.widget.menu;
public interface MenuStateObserver
{
void onMenuOpen();
void onMenuClosed();
}