forked from organicmaps/organicmaps
[android] Add modal search
Signed-off-by: savsch <119003089+savsch@users.noreply.github.com>
This commit is contained in:
parent
466b9365f6
commit
5f503c5c25
19 changed files with 691 additions and 87 deletions
|
@ -380,14 +380,6 @@
|
|||
android:parentActivityName="app.organicmaps.MwmActivity"
|
||||
android:windowSoftInputMode="adjustResize" />
|
||||
|
||||
<activity
|
||||
android:name="app.organicmaps.search.SearchActivity"
|
||||
android:configChanges="orientation|screenLayout|screenSize"
|
||||
android:screenOrientation="fullUser"
|
||||
android:label="@string/search_map"
|
||||
android:parentActivityName="app.organicmaps.MwmActivity"
|
||||
android:windowSoftInputMode="stateVisible|adjustResize" />
|
||||
|
||||
<activity
|
||||
android:name="app.organicmaps.settings.SettingsActivity"
|
||||
android:configChanges="orientation|screenLayout|screenSize"
|
||||
|
|
|
@ -88,7 +88,6 @@ import app.organicmaps.routing.RoutingOptions;
|
|||
import app.organicmaps.routing.RoutingPlanFragment;
|
||||
import app.organicmaps.routing.RoutingPlanInplaceController;
|
||||
import app.organicmaps.search.FloatingSearchToolbarController;
|
||||
import app.organicmaps.search.SearchActivity;
|
||||
import app.organicmaps.sdk.search.SearchEngine;
|
||||
import app.organicmaps.search.SearchFragment;
|
||||
import app.organicmaps.settings.DrivingOptionsActivity;
|
||||
|
@ -108,6 +107,8 @@ import app.organicmaps.util.bottomsheet.MenuBottomSheetItem;
|
|||
import app.organicmaps.util.log.Logger;
|
||||
import app.organicmaps.widget.StackedButtonsDialog;
|
||||
import app.organicmaps.widget.menu.MainMenu;
|
||||
import app.organicmaps.widget.modalsearch.ModalSearchController;
|
||||
import app.organicmaps.widget.modalsearch.ModalSearchViewModel;
|
||||
import app.organicmaps.widget.placepage.PlacePageController;
|
||||
import app.organicmaps.widget.placepage.PlacePageData;
|
||||
import app.organicmaps.widget.placepage.PlacePageViewModel;
|
||||
|
@ -188,6 +189,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
|||
@SuppressWarnings("NotNullFieldNotInitialized")
|
||||
@NonNull
|
||||
private FloatingSearchToolbarController mSearchController;
|
||||
private ModalSearchViewModel mSearchViewModel;
|
||||
|
||||
private boolean mRestoreRoutingPlanFragmentNeeded;
|
||||
@Nullable
|
||||
|
@ -423,12 +425,12 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
|||
if (mIsTabletLayout)
|
||||
{
|
||||
final Bundle args = new Bundle();
|
||||
args.putString(SearchActivity.EXTRA_QUERY, query);
|
||||
args.putString(SearchFragment.ARG_QUERY, query);
|
||||
replaceFragment(SearchFragment.class, args, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
SearchActivity.start(this, query);
|
||||
mSearchViewModel.setModalSearchActive(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -464,9 +466,9 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
|||
{
|
||||
final Bundle args = new Bundle();
|
||||
args.putBoolean(DownloaderActivity.EXTRA_OPEN_DOWNLOADED, openDownloaded);
|
||||
closeSearchToolbar(false, true);
|
||||
if (mIsTabletLayout)
|
||||
{
|
||||
closeSearchToolbar(false, true);
|
||||
replaceFragment(DownloaderFragment.class, args, null);
|
||||
}
|
||||
else
|
||||
|
@ -533,6 +535,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
|||
// 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);
|
||||
mSearchViewModel = new ViewModelProvider(MwmActivity.this).get(ModalSearchViewModel.class);
|
||||
|
||||
mSearchController = new FloatingSearchToolbarController(this, this);
|
||||
mSearchController.getToolbar()
|
||||
|
@ -692,7 +695,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
|||
showSearchToolbar();
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (mIsTabletLayout)
|
||||
{
|
||||
closeSearchToolbar(true, true);
|
||||
}
|
||||
|
@ -906,7 +909,16 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
|||
*/
|
||||
private boolean closeSearchToolbar(boolean clearText, boolean stopSearch)
|
||||
{
|
||||
if (UiUtils.isVisible(mSearchController.getToolbar()) || !TextUtils.isEmpty(SearchEngine.INSTANCE.getQuery()))
|
||||
if (!mIsTabletLayout && stopSearch && Boolean.TRUE.equals(mSearchViewModel.getModalSearchActive().getValue()))
|
||||
{
|
||||
mSearchViewModel.setModalSearchActive(false);
|
||||
return true;
|
||||
}
|
||||
else if (
|
||||
UiUtils.isVisible(mSearchController.getToolbar())
|
||||
|| !TextUtils.isEmpty(SearchEngine.INSTANCE.getQuery())
|
||||
|| Boolean.TRUE.equals(mSearchViewModel.getModalSearchActive().getValue())
|
||||
)
|
||||
{
|
||||
if (stopSearch)
|
||||
{
|
||||
|
@ -970,7 +982,10 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
|||
mMainMenu = new MainMenu(menuFrame, (visible) -> {
|
||||
this.updateBottomWidgetsOffset();
|
||||
if (visible)
|
||||
{
|
||||
mPlacePageViewModel.setPlacePageDistanceToTop(menuFrame.getTop());
|
||||
mSearchViewModel.setModalSearchDistanceToTop(menuFrame.getTop());
|
||||
}
|
||||
});
|
||||
|
||||
if (mIsTabletLayout)
|
||||
|
@ -1271,6 +1286,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
|||
{
|
||||
// This will open the place page
|
||||
mPlacePageViewModel.setMapObject((MapObject) data);
|
||||
mSearchViewModel.setModalSearchSuspended(true);
|
||||
}
|
||||
|
||||
// Called from JNI.
|
||||
|
@ -1279,6 +1295,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
|||
public void onPlacePageDeactivated()
|
||||
{
|
||||
closePlacePage();
|
||||
mSearchViewModel.setModalSearchSuspended(false);
|
||||
}
|
||||
|
||||
// Called from JNI.
|
||||
|
@ -2415,6 +2432,26 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
|||
mAlertDialog.show();
|
||||
}
|
||||
|
||||
public void handleSearchLink(String query, String locale, boolean isSearchOnMap)
|
||||
{
|
||||
final Bundle args = new Bundle();
|
||||
args.putString(SearchFragment.ARG_QUERY, query);
|
||||
args.putString(SearchFragment.ARG_LOCALE, locale);
|
||||
args.putBoolean(SearchFragment.ARG_SEARCH_ON_MAP, isSearchOnMap);
|
||||
if (mIsTabletLayout)
|
||||
{
|
||||
closeSearchToolbar(true, true);
|
||||
replaceFragment(SearchFragment.class, args, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
final ModalSearchController fragment = (ModalSearchController) getSupportFragmentManager().findFragmentById(R.id.modal_search_container_fragment);
|
||||
if (fragment == null)
|
||||
throw new IllegalStateException("Must be called with a valid R.id.modal_search_container_fragment fragment in the layout");
|
||||
fragment.restartSearch(args);
|
||||
}
|
||||
}
|
||||
|
||||
public void onShareLocationOptionSelected()
|
||||
{
|
||||
closeFloatingPanels();
|
||||
|
|
|
@ -20,7 +20,6 @@ import app.organicmaps.bookmarks.data.FeatureId;
|
|||
import app.organicmaps.bookmarks.data.MapObject;
|
||||
import app.organicmaps.editor.OsmLoginActivity;
|
||||
import app.organicmaps.routing.RoutingController;
|
||||
import app.organicmaps.search.SearchActivity;
|
||||
import app.organicmaps.sdk.search.SearchEngine;
|
||||
import app.organicmaps.util.StorageUtils;
|
||||
import app.organicmaps.util.concurrency.ThreadPool;
|
||||
|
@ -114,7 +113,7 @@ public class Factory
|
|||
if (!request.mIsSearchOnMap)
|
||||
Framework.nativeSetSearchViewport(latlon[0], latlon[1], SEARCH_IN_VIEWPORT_ZOOM);
|
||||
}
|
||||
SearchActivity.start(target, request.mQuery, request.mLocale, request.mIsSearchOnMap);
|
||||
target.handleSearchLink(request.mQuery, request.mLocale, request.mIsSearchOnMap);
|
||||
return true;
|
||||
}
|
||||
case RequestType.CROSSHAIR:
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
package app.organicmaps.search;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StyleRes;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import app.organicmaps.base.BaseMwmFragmentActivity;
|
||||
import app.organicmaps.util.ThemeUtils;
|
||||
|
||||
public class SearchActivity extends BaseMwmFragmentActivity
|
||||
{
|
||||
public static final String EXTRA_QUERY = "search_query";
|
||||
public static final String EXTRA_LOCALE = "locale";
|
||||
public static final String EXTRA_SEARCH_ON_MAP = "search_on_map";
|
||||
|
||||
public static void start(@NonNull Activity activity, @Nullable String query)
|
||||
{
|
||||
start(activity, query, null /* locale */, false /* isSearchOnMap */);
|
||||
}
|
||||
|
||||
public static void start(@NonNull Activity activity, @Nullable String query, @Nullable String locale,
|
||||
boolean isSearchOnMap)
|
||||
{
|
||||
final Intent i = new Intent(activity, SearchActivity.class);
|
||||
Bundle args = new Bundle();
|
||||
args.putString(EXTRA_QUERY, query);
|
||||
args.putString(EXTRA_LOCALE, locale);
|
||||
args.putBoolean(EXTRA_SEARCH_ON_MAP, isSearchOnMap);
|
||||
i.putExtras(args);
|
||||
activity.startActivity(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
@StyleRes
|
||||
public int getThemeResourceId(@NonNull String theme)
|
||||
{
|
||||
return ThemeUtils.getCardBgThemeResourceId(getApplicationContext(), theme);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<? extends Fragment> getFragmentClass()
|
||||
{
|
||||
return SearchFragment.class;
|
||||
}
|
||||
}
|
|
@ -20,6 +20,7 @@ import androidx.appcompat.widget.Toolbar;
|
|||
import androidx.core.view.ViewCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
|
@ -46,6 +47,8 @@ import app.organicmaps.util.Utils;
|
|||
import app.organicmaps.util.WindowInsetUtils;
|
||||
import app.organicmaps.widget.PlaceholderView;
|
||||
import app.organicmaps.widget.SearchToolbarController;
|
||||
import app.organicmaps.widget.modalsearch.ModalSearchViewModel;
|
||||
|
||||
import com.google.android.material.appbar.AppBarLayout;
|
||||
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
|
@ -58,6 +61,11 @@ public class SearchFragment extends BaseMwmFragment
|
|||
implements SearchListener,
|
||||
CategoriesAdapter.CategoriesUiListener
|
||||
{
|
||||
public static final String ARG_QUERY = "search_query";
|
||||
public static final String ARG_LOCALE = "locale";
|
||||
public static final String ARG_SEARCH_ON_MAP = "search_on_map";
|
||||
|
||||
private ModalSearchViewModel mViewModel;
|
||||
private long mLastQueryTimestamp;
|
||||
@NonNull
|
||||
private final List<HiddenCommand> mHiddenCommands = new ArrayList<>();
|
||||
|
@ -78,9 +86,18 @@ public class SearchFragment extends BaseMwmFragment
|
|||
|
||||
private class ToolbarController extends SearchToolbarController
|
||||
{
|
||||
public ToolbarController(View root)
|
||||
public ToolbarController(View root, boolean isSearchModal)
|
||||
{
|
||||
super(root, SearchFragment.this.requireActivity());
|
||||
if (isSearchModal) // remove toolbar padding in case the search is modal
|
||||
ViewCompat.setOnApplyWindowInsetsListener(getToolbar(), new WindowInsetUtils.PaddingInsetsListener(false, false, false, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v)
|
||||
{
|
||||
super.onClick(v);
|
||||
mViewModel.setModalSearchCollapsed(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -92,6 +109,7 @@ public class SearchFragment extends BaseMwmFragment
|
|||
if (TextUtils.isEmpty(query))
|
||||
{
|
||||
mSearchAdapter.clear();
|
||||
mViewModel.setIsQueryEmpty(true);
|
||||
stopSearch();
|
||||
return;
|
||||
}
|
||||
|
@ -104,6 +122,7 @@ public class SearchFragment extends BaseMwmFragment
|
|||
return;
|
||||
}
|
||||
|
||||
mViewModel.setIsQueryEmpty(false);
|
||||
runSearch();
|
||||
}
|
||||
|
||||
|
@ -170,6 +189,7 @@ public class SearchFragment extends BaseMwmFragment
|
|||
|
||||
private final LastPosition mLastPosition = new LastPosition();
|
||||
private boolean mSearchRunning;
|
||||
private boolean mIsModal;
|
||||
private String mInitialQuery;
|
||||
@Nullable
|
||||
private String mInitialLocale;
|
||||
|
@ -230,12 +250,12 @@ public class SearchFragment extends BaseMwmFragment
|
|||
final boolean hasQuery = mToolbarController.hasQuery();
|
||||
Toolbar toolbar = mToolbarController.getToolbar();
|
||||
AppBarLayout.LayoutParams lp = (AppBarLayout.LayoutParams) toolbar.getLayoutParams();
|
||||
lp.setScrollFlags(hasQuery ? AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS
|
||||
| AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL : 0);
|
||||
lp.setScrollFlags(!mIsModal && hasQuery ? AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS
|
||||
| AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL : 0);
|
||||
toolbar.setLayoutParams(lp);
|
||||
|
||||
UiUtils.showIf(hasQuery, mResultsFrame);
|
||||
UiUtils.showIf(hasQuery, mShowOnMapFab);
|
||||
UiUtils.showIf(!mIsModal && hasQuery, mShowOnMapFab);
|
||||
if (hasQuery)
|
||||
hideDownloadSuggest();
|
||||
else if (doShowDownloadSuggest())
|
||||
|
@ -256,6 +276,7 @@ public class SearchFragment extends BaseMwmFragment
|
|||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
|
||||
{
|
||||
mViewModel = new ViewModelProvider(requireActivity()).get(ModalSearchViewModel.class);
|
||||
return inflater.inflate(R.layout.fragment_search, container, false);
|
||||
}
|
||||
|
||||
|
@ -271,7 +292,10 @@ public class SearchFragment extends BaseMwmFragment
|
|||
View mTabFrame = root.findViewById(R.id.tab_frame);
|
||||
ViewPager pager = mTabFrame.findViewById(R.id.pages);
|
||||
|
||||
mToolbarController = new ToolbarController(view);
|
||||
final Fragment modalSearchFragment = requireActivity().getSupportFragmentManager().findFragmentById(R.id.modal_search_container_fragment);
|
||||
mIsModal = modalSearchFragment != null && modalSearchFragment.isAdded();
|
||||
|
||||
mToolbarController = new ToolbarController(view, mIsModal);
|
||||
TabLayout tabLayout = root.findViewById(R.id.tabs);
|
||||
|
||||
if (Config.isSearchHistoryEnabled())
|
||||
|
@ -320,6 +344,11 @@ public class SearchFragment extends BaseMwmFragment
|
|||
|
||||
if (mInitialSearchOnMap)
|
||||
showAllResultsOnMap();
|
||||
|
||||
view.findViewById(R.id.query).setOnFocusChangeListener((v, hasFocus) -> {
|
||||
if (hasFocus)
|
||||
mViewModel.setModalSearchCollapsed(false);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -374,9 +403,9 @@ public class SearchFragment extends BaseMwmFragment
|
|||
if (arguments == null)
|
||||
return;
|
||||
|
||||
mInitialQuery = arguments.getString(SearchActivity.EXTRA_QUERY);
|
||||
mInitialLocale = arguments.getString(SearchActivity.EXTRA_LOCALE);
|
||||
mInitialSearchOnMap = arguments.getBoolean(SearchActivity.EXTRA_SEARCH_ON_MAP);
|
||||
mInitialQuery = arguments.getString(ARG_QUERY);
|
||||
mInitialLocale = arguments.getString(ARG_LOCALE);
|
||||
mInitialSearchOnMap = arguments.getBoolean(ARG_SEARCH_ON_MAP);
|
||||
}
|
||||
|
||||
private boolean tryRecognizeHiddenCommand(@NonNull String query)
|
||||
|
@ -421,16 +450,12 @@ public class SearchFragment extends BaseMwmFragment
|
|||
final MapObject point = MapObject.createMapObject(FeatureId.EMPTY, MapObject.SEARCH,
|
||||
title, subtitle, result.lat, result.lon);
|
||||
RoutingController.get().onPoiSelected(point);
|
||||
mViewModel.setModalSearchActive(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
SearchEngine.INSTANCE.showResult(resultIndex);
|
||||
}
|
||||
|
||||
mToolbarController.deactivate();
|
||||
|
||||
if (requireActivity() instanceof SearchActivity)
|
||||
Utils.navigateToParent(requireActivity());
|
||||
}
|
||||
|
||||
void showAllResultsOnMap()
|
||||
|
@ -486,7 +511,7 @@ public class SearchFragment extends BaseMwmFragment
|
|||
SearchEngine.INSTANCE.cancel();
|
||||
|
||||
mLastQueryTimestamp = System.nanoTime();
|
||||
if (isTabletSearch())
|
||||
if (mIsModal || isTabletSearch())
|
||||
{
|
||||
SearchEngine.INSTANCE.searchInteractive(requireContext(), getQuery(), isCategory(),
|
||||
mLastQueryTimestamp, true /* isMapAndTable */);
|
||||
|
@ -548,19 +573,19 @@ public class SearchFragment extends BaseMwmFragment
|
|||
return true;
|
||||
}
|
||||
|
||||
boolean isSearchActivity = requireActivity() instanceof SearchActivity;
|
||||
final boolean isModalSearchActive = Boolean.TRUE.equals(mViewModel.getModalSearchActive().getValue());
|
||||
mToolbarController.deactivate();
|
||||
if (RoutingController.get().isWaitingPoiPick())
|
||||
{
|
||||
RoutingController.get().onPoiSelected(null);
|
||||
if (isSearchActivity)
|
||||
closeSearch();
|
||||
return !isSearchActivity;
|
||||
if (isModalSearchActive)
|
||||
mViewModel.setModalSearchActive(false);
|
||||
return !isModalSearchActive;
|
||||
}
|
||||
|
||||
if (isSearchActivity)
|
||||
closeSearch();
|
||||
return isSearchActivity;
|
||||
if (isModalSearchActive)
|
||||
mViewModel.setModalSearchActive(false);
|
||||
return isModalSearchActive;
|
||||
}
|
||||
|
||||
private void closeSearch()
|
||||
|
|
|
@ -0,0 +1,305 @@
|
|||
package app.organicmaps.widget.modalsearch;
|
||||
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
||||
import androidx.core.graphics.Insets;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.core.view.WindowInsetsCompat;
|
||||
import androidx.core.widget.NestedScrollView;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.lifecycle.Observer;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior;
|
||||
|
||||
import app.organicmaps.Framework;
|
||||
import app.organicmaps.R;
|
||||
import app.organicmaps.sdk.search.SearchEngine;
|
||||
import app.organicmaps.search.SearchFragment;
|
||||
import app.organicmaps.util.InputUtils;
|
||||
|
||||
public class ModalSearchController extends Fragment
|
||||
{
|
||||
private static final String SEARCH_FRAGMENT_TAG = SearchFragment.class.getSimpleName();
|
||||
private SearchBottomSheetBehavior<NestedScrollView> mSearchBehavior;
|
||||
private NestedScrollView mModalSearch;
|
||||
private ViewGroup mCoordinator;
|
||||
private int mViewportMinHeight;
|
||||
private ModalSearchViewModel mViewModel;
|
||||
private final Observer<Boolean> mModalSearchSuspendedObserver = suspended -> {
|
||||
if (Boolean.FALSE.equals(mViewModel.getModalSearchActive().getValue()))
|
||||
return;
|
||||
if (suspended)
|
||||
{
|
||||
mModalSearch.setVisibility(View.GONE);
|
||||
InputUtils.hideKeyboard(mModalSearch);
|
||||
}
|
||||
else
|
||||
mModalSearch.setVisibility(View.VISIBLE);
|
||||
};
|
||||
private ViewGroup mModalSearchFragmentContainer;
|
||||
private WindowInsetsCompat mCurrentWindowInsets;
|
||||
private int mDistanceToTop;
|
||||
private final BottomSheetBehavior.BottomSheetCallback mDefaultBottomSheetCallback = new BottomSheetBehavior.BottomSheetCallback()
|
||||
{
|
||||
@Override
|
||||
public void onStateChanged(@NonNull View bottomSheet, int newState)
|
||||
{
|
||||
if (ModalSearchUtils.isSettlingState(newState) || ModalSearchUtils.isDraggingState(newState))
|
||||
return;
|
||||
|
||||
ModalSearchUtils.updateMapViewport(mCoordinator, mDistanceToTop, mViewportMinHeight);
|
||||
|
||||
if (ModalSearchUtils.isHiddenState(newState))
|
||||
onHiddenInternal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSlide(@NonNull View bottomSheet, float slideOffset)
|
||||
{
|
||||
mDistanceToTop = bottomSheet.getTop();
|
||||
mViewModel.setModalSearchDistanceToTop(mDistanceToTop);
|
||||
}
|
||||
};
|
||||
private int mDisplayHeight;
|
||||
private CoordinatorLayout mSearchFragmentCoordinator;
|
||||
private final FragmentManager.FragmentLifecycleCallbacks mFragmentLifecycleCallbacks = new FragmentManager.FragmentLifecycleCallbacks()
|
||||
{
|
||||
@Override
|
||||
public void onFragmentStarted(@NonNull FragmentManager fm, @NonNull Fragment f)
|
||||
{
|
||||
mSearchFragmentCoordinator = mModalSearchFragmentContainer.findViewById(R.id.coordinator);
|
||||
super.onFragmentStarted(fm, f);
|
||||
}
|
||||
};
|
||||
private FrameLayout mDragIndicator;
|
||||
private final Observer<Integer> mModalSearchDistanceToTopObserver = new Observer<>()
|
||||
{
|
||||
private int mDragHandleHeight;
|
||||
|
||||
@Override
|
||||
public void onChanged(Integer distanceToTop)
|
||||
{
|
||||
if (mDragHandleHeight == 0)
|
||||
{
|
||||
mDragHandleHeight = mDragIndicator.getMeasuredHeight();
|
||||
}
|
||||
int topInset = 0, bottomInset = 0;
|
||||
if (mCurrentWindowInsets != null)
|
||||
{
|
||||
Insets insets = mCurrentWindowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
|
||||
topInset = insets.top;
|
||||
bottomInset = insets.bottom;
|
||||
}
|
||||
final int topInsetOverlap = Math.max(0, topInset - distanceToTop);
|
||||
mDragIndicator.setPadding(0, topInsetOverlap, 0, 0);
|
||||
if (mSearchFragmentCoordinator != null)
|
||||
{
|
||||
ViewGroup.LayoutParams params = mSearchFragmentCoordinator.getLayoutParams();
|
||||
params.height = mDisplayHeight - distanceToTop - topInsetOverlap - mDragHandleHeight - bottomInset;
|
||||
mSearchFragmentCoordinator.setLayoutParams(params);
|
||||
}
|
||||
}
|
||||
};
|
||||
private final Observer<Boolean> mModalSearchActiveObserver = active -> {
|
||||
if (active)
|
||||
startSearch(null);
|
||||
else
|
||||
closeSearch();
|
||||
};
|
||||
private final Observer<Boolean> mModalSearchCollapsedObserver = collapsed -> {
|
||||
if (collapsed)
|
||||
{
|
||||
setCollapsible(true);
|
||||
mSearchBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (mSearchBehavior.getState() == BottomSheetBehavior.STATE_COLLAPSED)
|
||||
mSearchBehavior.setState(BottomSheetBehavior.STATE_HALF_EXPANDED);
|
||||
if (Boolean.TRUE.equals(mViewModel.getIsQueryEmpty().getValue()))
|
||||
setCollapsible(false);
|
||||
}
|
||||
};
|
||||
private final Observer<Boolean> mIsQueryEmptyObserver = isQueryEmpty -> setCollapsible(!isQueryEmpty);
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)
|
||||
{
|
||||
View rootView = inflater.inflate(R.layout.modal_search_container_fragment, container, false);
|
||||
rootView.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
|
||||
mDisplayHeight = rootView.getHeight();
|
||||
if (mSearchBehavior != null)
|
||||
{
|
||||
Rect r = new Rect();
|
||||
rootView.getWindowVisibleDisplayFrame(r);
|
||||
final int availableHeight = (r.bottom - r.top);
|
||||
mSearchBehavior.updateUnavailableScreenRatio((float) (mDisplayHeight - availableHeight) / mDisplayHeight);
|
||||
}
|
||||
});
|
||||
return rootView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState)
|
||||
{
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
final FragmentActivity activity = requireActivity();
|
||||
|
||||
final Resources res = activity.getResources();
|
||||
mViewportMinHeight = res.getDimensionPixelSize(R.dimen.viewport_min_height);
|
||||
|
||||
mCoordinator = activity.findViewById(R.id.coordinator);
|
||||
mModalSearch = activity.findViewById(R.id.modal_search);
|
||||
mDragIndicator = mModalSearch.findViewById(R.id.drag_indicator);
|
||||
mModalSearch.setNestedScrollingEnabled(false);
|
||||
mModalSearchFragmentContainer = activity.findViewById(R.id.modal_search_fragment);
|
||||
mSearchBehavior = SearchBottomSheetBehavior.from(
|
||||
mModalSearch,
|
||||
getLifecycle(),
|
||||
getResources().getFraction(R.fraction.modal_search_half_expanded_ratio, 1, 1)
|
||||
);
|
||||
|
||||
mSearchBehavior.setHideable(true);
|
||||
mSearchBehavior.setPeekHeight(300);
|
||||
mSearchBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
|
||||
mSearchBehavior.setFitToContents(false);
|
||||
mSearchBehavior.setSkipCollapsed(true);
|
||||
|
||||
mViewModel = new ViewModelProvider(requireActivity()).get(ModalSearchViewModel.class);
|
||||
|
||||
ViewCompat.setOnApplyWindowInsetsListener(mModalSearch, (v, windowInsets) -> {
|
||||
mCurrentWindowInsets = windowInsets;
|
||||
return windowInsets;
|
||||
});
|
||||
|
||||
ViewCompat.requestApplyInsets(mModalSearch);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(@NonNull Configuration newConfig)
|
||||
{
|
||||
super.onConfigurationChanged(newConfig);
|
||||
}
|
||||
|
||||
private void onHiddenInternal()
|
||||
{
|
||||
Framework.nativeDeactivatePopup();
|
||||
ModalSearchUtils.updateMapViewport(mCoordinator, mDistanceToTop, mViewportMinHeight);
|
||||
removeModalSearchFragments();
|
||||
}
|
||||
|
||||
private void startSearch(Bundle searchArguments)
|
||||
{
|
||||
createModalSearchFragments(searchArguments);
|
||||
mDragIndicator.setVisibility(View.VISIBLE);
|
||||
mModalSearch.setVisibility(View.VISIBLE);
|
||||
mSearchBehavior.setState(BottomSheetBehavior.STATE_HALF_EXPANDED);
|
||||
}
|
||||
|
||||
private void closeSearch()
|
||||
{
|
||||
SearchEngine.INSTANCE.cancel();
|
||||
mSearchBehavior.setHideable(true);
|
||||
mSearchBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
|
||||
removeModalSearchFragments();
|
||||
mDragIndicator.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void setCollapsible(boolean collapsible)
|
||||
{
|
||||
if (collapsible)
|
||||
{
|
||||
mSearchBehavior.setSkipCollapsed(false);
|
||||
mSearchBehavior.setPeekHeight(calculateCollapsedHeight());
|
||||
}
|
||||
else
|
||||
mSearchBehavior.setSkipCollapsed(true);
|
||||
}
|
||||
|
||||
private int calculateCollapsedHeight()
|
||||
{
|
||||
try
|
||||
{
|
||||
return mDragIndicator.getMeasuredHeight() +
|
||||
mModalSearch.findViewById(R.id.app_bar).getMeasuredHeight(); // TODO(savsch) get feedback on whether to change this height
|
||||
} catch (NullPointerException npe)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void removeModalSearchFragments()
|
||||
{
|
||||
final FragmentManager fm = getChildFragmentManager();
|
||||
final Fragment modalSearchFragment = fm.findFragmentByTag(SEARCH_FRAGMENT_TAG);
|
||||
|
||||
if (modalSearchFragment != null)
|
||||
{
|
||||
fm.beginTransaction()
|
||||
.setReorderingAllowed(true)
|
||||
.remove(modalSearchFragment)
|
||||
.commit();
|
||||
}
|
||||
}
|
||||
|
||||
private void createModalSearchFragments(Bundle searchArguments)
|
||||
{
|
||||
final FragmentManager fm = getChildFragmentManager();
|
||||
if (fm.findFragmentByTag(SEARCH_FRAGMENT_TAG) == null)
|
||||
{
|
||||
fm.beginTransaction()
|
||||
.setReorderingAllowed(true)
|
||||
.add(R.id.modal_search_fragment, SearchFragment.class, searchArguments, SEARCH_FRAGMENT_TAG)
|
||||
.commit();
|
||||
}
|
||||
}
|
||||
|
||||
public void restartSearch(Bundle searchArguments)
|
||||
{
|
||||
closeSearch();
|
||||
getChildFragmentManager().executePendingTransactions();
|
||||
startSearch(searchArguments);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart()
|
||||
{
|
||||
super.onStart();
|
||||
mSearchBehavior.addBottomSheetCallback(mDefaultBottomSheetCallback);
|
||||
getChildFragmentManager().registerFragmentLifecycleCallbacks(mFragmentLifecycleCallbacks, false);
|
||||
FragmentActivity activity = requireActivity();
|
||||
mViewModel.getModalSearchDistanceToTop().observe(activity, mModalSearchDistanceToTopObserver);
|
||||
mViewModel.getModalSearchActive().observe(activity, mModalSearchActiveObserver);
|
||||
mViewModel.getModalSearchCollapsed().observe(activity, mModalSearchCollapsedObserver);
|
||||
mViewModel.getModalSearchSuspended().observe(activity, mModalSearchSuspendedObserver);
|
||||
mViewModel.getIsQueryEmpty().observe(activity, mIsQueryEmptyObserver);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop()
|
||||
{
|
||||
super.onStop();
|
||||
mSearchBehavior.removeBottomSheetCallback(mDefaultBottomSheetCallback);
|
||||
getChildFragmentManager().unregisterFragmentLifecycleCallbacks(mFragmentLifecycleCallbacks);
|
||||
mViewModel.getModalSearchDistanceToTop().removeObserver(mModalSearchDistanceToTopObserver);
|
||||
mViewModel.getModalSearchActive().removeObserver(mModalSearchActiveObserver);
|
||||
mViewModel.getModalSearchCollapsed().removeObserver(mModalSearchCollapsedObserver);
|
||||
mViewModel.getModalSearchSuspended().removeObserver(mModalSearchSuspendedObserver);
|
||||
mViewModel.getIsQueryEmpty().removeObserver(mIsQueryEmptyObserver);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package app.organicmaps.widget.modalsearch;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior;
|
||||
|
||||
import app.organicmaps.Framework;
|
||||
import app.organicmaps.display.DisplayManager;
|
||||
|
||||
public class ModalSearchUtils
|
||||
{
|
||||
|
||||
static void updateMapViewport(@NonNull View parent, int newSearchDistanceToTop, int viewportMinHeight)
|
||||
{
|
||||
parent.post(() -> {
|
||||
// Because of the post(), this lambda is called after the car.SurfaceRenderer.onStableAreaChanged() and breaks the visibleRect configuration
|
||||
if (DisplayManager.from(parent.getContext()).isCarDisplayUsed())
|
||||
return;
|
||||
final int screenWidth = parent.getWidth();
|
||||
if (newSearchDistanceToTop >= viewportMinHeight)
|
||||
Framework.nativeSetVisibleRect(0, 0, screenWidth, newSearchDistanceToTop);
|
||||
});
|
||||
}
|
||||
|
||||
static boolean isSettlingState(@BottomSheetBehavior.State int state)
|
||||
{
|
||||
return state == BottomSheetBehavior.STATE_SETTLING;
|
||||
}
|
||||
|
||||
static boolean isDraggingState(@BottomSheetBehavior.State int state)
|
||||
{
|
||||
return state == BottomSheetBehavior.STATE_DRAGGING;
|
||||
}
|
||||
|
||||
static boolean isHiddenState(@BottomSheetBehavior.State int state)
|
||||
{
|
||||
return state == BottomSheetBehavior.STATE_HIDDEN;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
package app.organicmaps.widget.modalsearch;
|
||||
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
public class ModalSearchViewModel extends ViewModel
|
||||
{
|
||||
private final MutableLiveData<Integer> mModalSearchDistanceToTop = new MutableLiveData<>();
|
||||
private final MutableLiveData<Boolean> mModalSearchActive = new MutableLiveData<>();
|
||||
private final MutableLiveData<Boolean> mModalSearchSuspended = new MutableLiveData<>();
|
||||
private final MutableLiveData<Boolean> mModalSearchCollapsed = new MutableLiveData<>();
|
||||
private final MutableLiveData<Boolean> mIsQueryEmpty = new MutableLiveData<>(true);
|
||||
|
||||
|
||||
public MutableLiveData<Integer> getModalSearchDistanceToTop()
|
||||
{
|
||||
return mModalSearchDistanceToTop;
|
||||
}
|
||||
|
||||
public void setModalSearchDistanceToTop(int top)
|
||||
{
|
||||
mModalSearchDistanceToTop.setValue(top);
|
||||
}
|
||||
|
||||
public MutableLiveData<Boolean> getModalSearchActive()
|
||||
{
|
||||
return mModalSearchActive;
|
||||
}
|
||||
|
||||
public void setModalSearchActive(Boolean active)
|
||||
{
|
||||
mModalSearchActive.setValue(active);
|
||||
}
|
||||
|
||||
public MutableLiveData<Boolean> getModalSearchSuspended()
|
||||
{
|
||||
return mModalSearchSuspended;
|
||||
}
|
||||
|
||||
public void setModalSearchSuspended(Boolean suspended)
|
||||
{
|
||||
mModalSearchSuspended.setValue(suspended);
|
||||
}
|
||||
|
||||
public MutableLiveData<Boolean> getModalSearchCollapsed()
|
||||
{
|
||||
return mModalSearchCollapsed;
|
||||
}
|
||||
|
||||
public void setModalSearchCollapsed(Boolean collapsed)
|
||||
{
|
||||
mModalSearchCollapsed.setValue(collapsed);
|
||||
}
|
||||
|
||||
public MutableLiveData<Boolean> getIsQueryEmpty()
|
||||
{
|
||||
return mIsQueryEmpty;
|
||||
}
|
||||
|
||||
public void setIsQueryEmpty(Boolean isQueryEmpty)
|
||||
{
|
||||
mIsQueryEmpty.setValue(isQueryEmpty);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
package app.organicmaps.widget.modalsearch;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.Lifecycle;
|
||||
import androidx.lifecycle.LifecycleEventObserver;
|
||||
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior;
|
||||
|
||||
public class SearchBottomSheetBehavior<V extends View> extends BottomSheetBehavior<V>
|
||||
{
|
||||
private float mHalfExpandedBaseRatio = 0.5f;
|
||||
private float mHalfExpandedRatio = mHalfExpandedBaseRatio;
|
||||
private final SheetCollapseHelper sheetSlideHelper = new SheetCollapseHelper()
|
||||
{
|
||||
private static final float COLLAPSE_VELOCITY_THRESHOLD = 0.2f; // offset units per second
|
||||
private static final long VELOCITY_TRACKING_TIMEOUT_MS = 150;
|
||||
private float mLastSlideOffset = 0f;
|
||||
private long mLastSlideTimeMillis = 0;
|
||||
|
||||
public void onSlide(float slideOffset)
|
||||
{
|
||||
if (getSkipCollapsed() || getState() != STATE_DRAGGING || slideOffset >= getHalfExpandedRatio())
|
||||
return;
|
||||
long currentTimeMillis = System.currentTimeMillis();
|
||||
if (currentTimeMillis - mLastSlideTimeMillis < VELOCITY_TRACKING_TIMEOUT_MS)
|
||||
{
|
||||
// Calculate velocity (negative means downward movement)
|
||||
float timeDelta = (currentTimeMillis - mLastSlideTimeMillis) / 1000f; // convert to seconds
|
||||
float offsetDelta = slideOffset - mLastSlideOffset;
|
||||
float velocity = offsetDelta / timeDelta;
|
||||
if (velocity < -COLLAPSE_VELOCITY_THRESHOLD)
|
||||
{
|
||||
// detected a fast downward swipe from half-expanded state, so collapse
|
||||
setState(BottomSheetBehavior.STATE_COLLAPSED);
|
||||
}
|
||||
}
|
||||
mLastSlideTimeMillis = currentTimeMillis;
|
||||
mLastSlideOffset = slideOffset;
|
||||
}
|
||||
};
|
||||
private final BottomSheetCallback easierCollapseCallback = new BottomSheetCallback()
|
||||
{
|
||||
@Override
|
||||
public void onStateChanged(@NonNull View bottomSheet, int newState)
|
||||
{
|
||||
if (newState == STATE_HALF_EXPANDED && mHalfExpandedRatio != getHalfExpandedRatio())
|
||||
setState(STATE_HALF_EXPANDED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSlide(@NonNull View bottomSheet, float slideOffset)
|
||||
{
|
||||
sheetSlideHelper.onSlide(slideOffset);
|
||||
}
|
||||
};
|
||||
|
||||
public SearchBottomSheetBehavior(@NonNull Context context, @Nullable AttributeSet attrs)
|
||||
{
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public static <V extends View> SearchBottomSheetBehavior<V> from(@NonNull V view, Lifecycle lifecycle, float baseHalfExpandedRatio)
|
||||
{
|
||||
SearchBottomSheetBehavior<V> result = (SearchBottomSheetBehavior<V>) SearchBottomSheetBehavior.from(view);
|
||||
lifecycle.addObserver((LifecycleEventObserver) (lifecycleOwner, event) -> {
|
||||
switch (event)
|
||||
{
|
||||
case ON_START -> result.onStart();
|
||||
case ON_STOP -> result.onStop();
|
||||
}
|
||||
});
|
||||
result.mHalfExpandedBaseRatio = Math.min(0.999f, baseHalfExpandedRatio); // Limit the ratio to the maximum allowed value of less than 1.0f
|
||||
result.mHalfExpandedRatio = result.mHalfExpandedBaseRatio;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setState(int state)
|
||||
{
|
||||
if (state == STATE_HALF_EXPANDED)
|
||||
mHalfExpandedRatio = getHalfExpandedRatio();
|
||||
super.setState(state);
|
||||
}
|
||||
|
||||
public void onStart()
|
||||
{
|
||||
addBottomSheetCallback(easierCollapseCallback);
|
||||
}
|
||||
|
||||
private void onStop()
|
||||
{
|
||||
removeBottomSheetCallback(easierCollapseCallback);
|
||||
}
|
||||
|
||||
public void updateUnavailableScreenRatio(float unavailableRatio)
|
||||
{
|
||||
if (unavailableRatio < 0)
|
||||
return;
|
||||
setHalfExpandedRatio(Math.min(0.999f, mHalfExpandedBaseRatio + unavailableRatio));
|
||||
if (getState() == STATE_HALF_EXPANDED)
|
||||
super.setState(BottomSheetBehavior.STATE_HALF_EXPANDED); // intended super call; calling self setState would defeat the purpose of mHalfExpandedRatio
|
||||
}
|
||||
|
||||
private interface SheetCollapseHelper
|
||||
{
|
||||
void onSlide(float slideOffset);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="?attr/colorPrimary" />
|
||||
<corners android:topLeftRadius="@dimen/margin_half" android:topRightRadius="@dimen/margin_half" />
|
||||
</shape>
|
|
@ -55,4 +55,9 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:name="app.organicmaps.widget.placepage.PlacePageController" />
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/modal_search_container_fragment"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:name="app.organicmaps.widget.modalsearch.ModalSearchController" />
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
|
|
@ -29,7 +29,8 @@
|
|||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
android:nestedScrollingEnabled="true">
|
||||
<!-- Tabs -->
|
||||
<LinearLayout
|
||||
android:id="@+id/tab_frame"
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
<?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.NestedScrollView
|
||||
android:id="@+id/modal_search"
|
||||
style="?attr/bottomSheetStyle"
|
||||
android:maxWidth="@dimen/max_search_sheet_width"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:elevation="0dp"
|
||||
android:fillViewport="true"
|
||||
app:layout_behavior="@string/search_sheet_behavior">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/drag_indicator"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_modal_search_header">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="4dp"
|
||||
android:layout_marginBottom="@dimen/margin_quarter_plus"
|
||||
android:layout_marginTop="@dimen/margin_quarter"
|
||||
app:srcCompat="@drawable/bottom_sheet_handle"
|
||||
app:tint="?colorControlHighlight" />
|
||||
</FrameLayout>
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/modal_search_fragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:tag="SearchFragment" />
|
||||
</LinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
@ -17,4 +17,7 @@
|
|||
|
||||
<!--About Fragment -->
|
||||
<dimen name="about_max_button_width">310dp</dimen>
|
||||
|
||||
<!-- Search Bottom Sheet-->
|
||||
<dimen name="max_search_sheet_width">@dimen/panel_width</dimen>
|
||||
</resources>
|
||||
|
|
5
android/app/src/main/res/values-land/fraction.xml
Normal file
5
android/app/src/main/res/values-land/fraction.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- ModalSearch Bottom Sheet-->
|
||||
<fraction name="modal_search_half_expanded_ratio">100%</fraction> <!-- Skips the half-expanded state in landscape mode -->
|
||||
</resources>
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- ModalSearch Bottom Sheet-->
|
||||
<fraction name="modal_search_half_expanded_ratio">50%</fraction>
|
||||
</resources>
|
|
@ -179,4 +179,7 @@
|
|||
|
||||
<!--About Fragment -->
|
||||
<dimen name="about_max_button_width">310dp</dimen>
|
||||
|
||||
<!-- Search Bottom Sheet-->
|
||||
<dimen name="max_search_sheet_width">-1.0px</dimen>
|
||||
</resources>
|
||||
|
|
|
@ -66,6 +66,7 @@
|
|||
<string name="auto_enum_value" translatable="false">AUTO</string>
|
||||
|
||||
<string name="placepage_behavior" translatable="false">com.google.android.material.bottomsheet.BottomSheetBehavior</string>
|
||||
<string name="search_sheet_behavior" translatable="false">app.organicmaps.widget.modalsearch.SearchBottomSheetBehavior</string>
|
||||
|
||||
<string name="car_notification_channel_name" translatable="false">Car</string>
|
||||
</resources>
|
||||
|
|
5
android/app/src/main/res/values/fraction.xml
Normal file
5
android/app/src/main/res/values/fraction.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- ModalSearch Bottom Sheet-->
|
||||
<fraction name="modal_search_half_expanded_ratio">50%</fraction>
|
||||
</resources>
|
Loading…
Add table
Reference in a new issue