[android] Add UI elements for user route management

For users to interact with this feature the following was added:

- Button in the map screen to enter a bottom sheet menu

- Fragment for the bottom sheet menu.
  Contains a button to save the selected route, the button is
  disabled if there are not enough route points;
  a recycle view with an item for each user route.

- A route item contains a button with the name of the route,
  used to load the route points; a button to rename the route;
  and a button to delete the route. Checks are in place to
  confirm before deleting a route or overwriting an existing one.

Notes:

- The map screen button has a placeholder icon and a
  placeholder location when not in navigation mode.

- All the text is still hard coded in English and
  is to be implemented correctly shortly.

- It might make sense to move this from a bottom sheet next to
  the bookmarks, as to keep every user saved element together.

Signed-off-by: Fábio Gomes <gabriel.gomes@tecnico.ulisboa.pt>
This commit is contained in:
Fábio Gomes 2024-06-21 10:12:34 +01:00
parent 7ab0c25a13
commit a1cb87bca6
13 changed files with 671 additions and 24 deletions

View file

@ -72,6 +72,7 @@ import app.organicmaps.location.SensorHelper;
import app.organicmaps.location.SensorListener;
import app.organicmaps.maplayer.MapButtonsController;
import app.organicmaps.maplayer.MapButtonsViewModel;
import app.organicmaps.maplayer.MyRoutesFragment;
import app.organicmaps.maplayer.ToggleMapLayerFragment;
import app.organicmaps.maplayer.isolines.IsolinesManager;
import app.organicmaps.maplayer.isolines.IsolinesState;
@ -156,6 +157,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
private static final String MAIN_MENU_ID = "MAIN_MENU_BOTTOM_SHEET";
private static final String LAYERS_MENU_ID = "LAYERS_MENU_BOTTOM_SHEET";
private static final String MYROUTES_MENU_ID = "MYROUTES_MENU_BOTTOM_SHEET";
@Nullable
private MapFragment mMapFragment;
@ -774,6 +776,11 @@ public class MwmActivity extends BaseMwmFragmentActivity
showBottomSheet(MAIN_MENU_ID);
}
case help -> showHelp();
case myRoutes ->
{
closeFloatingPanels();
showBottomSheet(MYROUTES_MENU_ID);
}
}
}
@ -892,6 +899,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
{
closeBottomSheet(LAYERS_MENU_ID);
closeBottomSheet(MAIN_MENU_ID);
closeBottomSheet(MYROUTES_MENU_ID);
closePlacePage();
}
@ -1147,7 +1155,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
public void onBackPressed()
{
final RoutingController routingController = RoutingController.get();
if (!closeBottomSheet(MAIN_MENU_ID) && !closeBottomSheet(LAYERS_MENU_ID) &&
if (!closeBottomSheet(MAIN_MENU_ID) && !closeBottomSheet(LAYERS_MENU_ID) && !closeBottomSheet(MYROUTES_MENU_ID) &&
!collapseNavMenu() && !closePlacePage() && !closeSearchToolbar(true, true) &&
!closeSidePanel() && !closePositionChooser() &&
!routingController.resetToPlanningStateIfNavigating() && !routingController.cancel())
@ -2164,6 +2172,8 @@ public class MwmActivity extends BaseMwmFragmentActivity
{
if (id.equals(LAYERS_MENU_ID))
return new ToggleMapLayerFragment();
else if (id.equals(MYROUTES_MENU_ID))
return new MyRoutesFragment();
return null;
}

View file

@ -44,6 +44,8 @@ public class MapButtonsController extends Fragment
private View mBottomButtonsFrame;
@Nullable
private FloatingActionButton mToggleMapLayerButton;
@Nullable
private FloatingActionButton mMyRoutesButton;
@Nullable
private MyPositionButton mNavMyPosition;
@ -104,6 +106,11 @@ public class MapButtonsController extends Fragment
final View myPosition = mFrame.findViewById(R.id.my_position);
mNavMyPosition = new MyPositionButton(myPosition, (v) -> mMapButtonClickListener.onMapButtonClick(MapButtons.myPosition));
mMyRoutesButton = mFrame.findViewById(R.id.btn_myroutes);
if (mMyRoutesButton != null)
{
mMyRoutesButton.setOnClickListener(view -> mMapButtonClickListener.onMapButtonClick(MapButtons.myRoutes));
}
// Some buttons do not exist in navigation mode
mToggleMapLayerButton = mFrame.findViewById(R.id.layers_button);
if (mToggleMapLayerButton != null)
@ -143,6 +150,8 @@ public class MapButtonsController extends Fragment
mButtonsMap.put(MapButtons.bookmarks, bookmarksButton);
mButtonsMap.put(MapButtons.search, searchButton);
if (mMyRoutesButton != null)
mButtonsMap.put(MapButtons.myRoutes, mMyRoutesButton);
if (mToggleMapLayerButton != null)
mButtonsMap.put(MapButtons.toggleMapLayer, mToggleMapLayerButton);
if (menuButton != null)
@ -181,6 +190,11 @@ public class MapButtonsController extends Fragment
case bookmarks:
case menu:
UiUtils.showIf(show, buttonView);
break;
case myRoutes:
UiUtils.showIf(show, mMyRoutesButton);
break;
}
}
@ -350,7 +364,8 @@ public class MapButtonsController extends Fragment
search,
bookmarks,
menu,
help
help,
myRoutes
}
public interface MapButtonClickListener

View file

@ -0,0 +1,265 @@
package app.organicmaps.maplayer;
import android.app.AlertDialog;
import android.os.Bundle;
import android.os.SystemClock;
import android.text.InputFilter;
import android.text.InputType;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.EditText;
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 com.google.android.material.button.MaterialButton;
import java.util.ArrayList;
import java.util.List;
import app.organicmaps.Framework;
import app.organicmaps.R;
import app.organicmaps.util.bottomsheet.MenuBottomSheetFragment;
public class MyRoutesFragment extends Fragment
{
private static final String MYROUTES_MENU_ID = "MYROUTES_MENU_BOTTOM_SHEET";
@Nullable
private RoutesAdapter mAdapter;
private MapButtonsViewModel mMapButtonsViewModel;
private String mEditText = "";
private String mDialogCaller = "";
private RouteBottomSheetItem mCurrentItem = null;
private static final String SAVE_ID = "SAVE_ID";
private static final String RENAME_ID = "RENAME_ID";
private static final String DELETE_ID = "DELETE_ID";
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)
{
View mRoot = inflater.inflate(R.layout.fragment_myroutes, container, false);
mMapButtonsViewModel = new ViewModelProvider(requireActivity()).get(MapButtonsViewModel.class);
MaterialButton mCloseButton = mRoot.findViewById(R.id.close_button);
mCloseButton.setOnClickListener(view -> closeMyRoutesBottomSheet());
Button mSaveButton = mRoot.findViewById(R.id.save_button);
if (Framework.nativeGetRoutePoints().length >= 2)
mSaveButton.setOnClickListener(view -> onSaveButtonClick());
else
mSaveButton.setEnabled(false);
initRecycler(mRoot);
return mRoot;
}
private void initRecycler(@NonNull View root)
{
RecyclerView recycler = root.findViewById(R.id.recycler);
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(requireContext(),
LinearLayoutManager.VERTICAL,
false);
recycler.setLayoutManager(layoutManager);
mAdapter = new RoutesAdapter(getRouteItems());
recycler.setAdapter(mAdapter);
recycler.setNestedScrollingEnabled(false);
}
private List<RouteBottomSheetItem> getRouteItems()
{
String[] savedRouteNames = Framework.nativeGetUserRouteNames();
List<RouteBottomSheetItem> items = new ArrayList<>();
for (String routeName : savedRouteNames)
items.add(createItem(routeName));
return items;
}
private RouteBottomSheetItem createItem(String routeName)
{
return RouteBottomSheetItem.create(routeName, this::onItemTitleClick, this::onItemRenameClick, this::onItemDeleteClick);
}
private void onSaveButtonClick()
{
mEditText = "";
mDialogCaller = SAVE_ID;
showTextInputDialog("");
}
private void checkSave()
{
String newRouteName = mEditText;
if (newRouteName.isEmpty())
return;
if (Framework.nativeHasSavedUserRoute(newRouteName))
{
showConfirmationDialog(newRouteName + " already exists",
"Overwrite existing " + newRouteName + "?",
"Overwrite");
return;
}
save(false);
}
private void save(boolean isOverwrite)
{
String newRouteName = mEditText;
if (isOverwrite)
Framework.nativeDeleteUserRoute(newRouteName);
Framework.nativeSaveUserRoutePoints(newRouteName);
if (!isOverwrite)
mAdapter.addRoute(createItem(newRouteName));
}
private void onItemTitleClick(@NonNull View v, @NonNull RouteBottomSheetItem item)
{
Framework.nativeLoadUserRoutePoints(item.getRouteName());
}
private void onItemRenameClick(@NonNull View v, @NonNull RouteBottomSheetItem item)
{
mEditText = "";
mDialogCaller = RENAME_ID;
mCurrentItem = item;
showTextInputDialog(item.getRouteName());
}
private void checkRename()
{
String newRouteName = mEditText;
if (newRouteName.isEmpty())
return;
if (newRouteName.equals(mCurrentItem.getRouteName()))
return;
if (Framework.nativeHasSavedUserRoute(newRouteName))
{
showConfirmationDialog(newRouteName + " already exists",
"Overwrite existing " + newRouteName + "?",
"Overwrite");
return;
}
rename(false);
}
private void rename(boolean isOverwrite)
{
String newRouteName = mEditText;
if (isOverwrite)
{
Framework.nativeDeleteUserRoute(newRouteName);
// Sometimes delete takes too long and renaming cannot happen; thus, sleep
SystemClock.sleep(250); // TODO(Fábio Gomes) find a better solution
}
Framework.nativeRenameUserRoute(mCurrentItem.getRouteName(), newRouteName);
mAdapter.removeRoute(mCurrentItem);
if (!isOverwrite)
mAdapter.addRoute(createItem(newRouteName));
}
private void onItemDeleteClick(@NonNull View v, @NonNull RouteBottomSheetItem item)
{
mDialogCaller = DELETE_ID;
mCurrentItem = item;
showConfirmationDialog("Delete " + item.getRouteName() + "?",
"This action cannot be undone.",
"Delete");
}
private void delete()
{
Framework.nativeDeleteUserRoute(mCurrentItem.getRouteName());
mAdapter.removeRoute(mCurrentItem);
}
private void closeMyRoutesBottomSheet()
{
MenuBottomSheetFragment bottomSheet =
(MenuBottomSheetFragment) requireActivity().getSupportFragmentManager().findFragmentByTag(MYROUTES_MENU_ID);
if (bottomSheet != null)
bottomSheet.dismiss();
}
private void showConfirmationDialog(String title, String message, String buttonText)
{
AlertDialog dialog = new AlertDialog.Builder(this.getContext(), R.style.MwmTheme_AlertDialog)
.setTitle(title)
.setMessage(message)
.setPositiveButton(buttonText, (dialogInterface, i) -> {
switch (mDialogCaller) {
case SAVE_ID -> save(true);
case RENAME_ID -> rename(true);
case DELETE_ID -> delete();
}
})
.setNegativeButton("Cancel", (dialogInterface, i) -> {
switch (mDialogCaller) {
case SAVE_ID, RENAME_ID -> retryInput();
}
})
.create();
dialog.show();
}
private void showTextInputDialog(String defaultText)
{
EditText input = new EditText(this.getContext());
input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES);
input.setFilters(getInputFilters());
input.setText(defaultText);
AlertDialog dialog = new AlertDialog.Builder(this.getContext(), R.style.MwmTheme_AlertDialog)
.setTitle("Route Name")
.setView(input)
.setPositiveButton("OK", (dialogInterface, i) -> {
mEditText = input.getText().toString();
switch (mDialogCaller) {
case SAVE_ID -> checkSave();
case RENAME_ID -> checkRename();
}
})
.setNegativeButton("Cancel", null)
.create();
dialog.show();
}
private void retryInput()
{
showTextInputDialog(mEditText);
}
private InputFilter[] getInputFilters()
{
InputFilter filter = (source, start, end, dest, dstart, dend) -> {
for (int i = start; i < end; i++)
{
char current = source.charAt(i);
if (!Character.isSpaceChar(current) && !Character.isLetterOrDigit(current))
{
return "";
}
}
return null;
};
return new InputFilter[] {filter, new InputFilter.LengthFilter(32)};
}
}

View file

@ -0,0 +1,58 @@
package app.organicmaps.maplayer;
import android.view.View;
import androidx.annotation.NonNull;
import app.organicmaps.adapter.OnItemClickListener;
public class RouteBottomSheetItem
{
@NonNull
private final String mRouteName;
@NonNull
private final OnItemClickListener<RouteBottomSheetItem> mItemTitleClickListener;
@NonNull
private final OnItemClickListener<RouteBottomSheetItem> mItemRenameClickListener;
@NonNull
private final OnItemClickListener<RouteBottomSheetItem> mItemDeleteClickListener;
RouteBottomSheetItem(@NonNull String routeName,
@NonNull OnItemClickListener<RouteBottomSheetItem> itemTitleClickListener,
@NonNull OnItemClickListener<RouteBottomSheetItem> itemRenameClickListener,
@NonNull OnItemClickListener<RouteBottomSheetItem> itemDeleteClickListener)
{
mRouteName = routeName;
mItemTitleClickListener = itemTitleClickListener;
mItemRenameClickListener = itemRenameClickListener;
mItemDeleteClickListener = itemDeleteClickListener;
}
public static RouteBottomSheetItem create(@NonNull String routeName,
@NonNull OnItemClickListener<RouteBottomSheetItem> routeItemTitleClickListener,
@NonNull OnItemClickListener<RouteBottomSheetItem> routeItemRenameClickListener,
@NonNull OnItemClickListener<RouteBottomSheetItem> routeItemDeleteClickListener)
{
return new RouteBottomSheetItem(routeName, routeItemTitleClickListener, routeItemRenameClickListener, routeItemDeleteClickListener);
}
public String getRouteName()
{
return mRouteName;
}
public void onTitleClick(@NonNull View v, @NonNull RouteBottomSheetItem item)
{
mItemTitleClickListener.onItemClick(v, item);
}
public void onRenameClick(@NonNull View v, @NonNull RouteBottomSheetItem item)
{
mItemRenameClickListener.onItemClick(v, item);
}
public void onDeleteClick(@NonNull View v, @NonNull RouteBottomSheetItem item)
{
mItemDeleteClickListener.onItemClick(v, item);
}
}

View file

@ -0,0 +1,59 @@
package app.organicmaps.maplayer;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import app.organicmaps.R;
import app.organicmaps.adapter.OnItemClickListener;
class RouteHolder extends RecyclerView.ViewHolder
{
@NonNull
final TextView mTitle;
@NonNull
final ImageView mRenameButton;
@NonNull
final ImageView mDeleteButton;
@Nullable
RouteBottomSheetItem mItem;
@Nullable
OnItemClickListener<RouteBottomSheetItem> mTitleListener;
@Nullable
OnItemClickListener<RouteBottomSheetItem> mRenameListener;
@Nullable
OnItemClickListener<RouteBottomSheetItem> mDeleteListener;
RouteHolder(@NonNull View root)
{
super(root);
mTitle = root.findViewById(R.id.name);
mTitle.setOnClickListener(this::onItemTitleClicked);
mRenameButton = root.findViewById(R.id.rename);
mRenameButton.setOnClickListener(this::onItemRenameClicked);
mDeleteButton = root.findViewById(R.id.delete);
mDeleteButton.setOnClickListener(this::onItemDeleteClicked);
}
public void onItemTitleClicked(@NonNull View v)
{
if (mTitleListener != null && mItem != null)
mTitleListener.onItemClick(v, mItem);
}
public void onItemRenameClicked(@NonNull View v)
{
if (mRenameListener != null && mItem != null)
mRenameListener.onItemClick(v, mItem);
}
public void onItemDeleteClicked(@NonNull View v)
{
if (mDeleteListener != null && mItem != null)
mDeleteListener.onItemClick(v, mItem);
}
}

View file

@ -0,0 +1,83 @@
package app.organicmaps.maplayer;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
import app.organicmaps.R;
import app.organicmaps.util.SharedPropertiesUtils;
import app.organicmaps.util.UiUtils;
import app.organicmaps.util.log.Logger;
public class RoutesAdapter extends RecyclerView.Adapter<RouteHolder>
{
@NonNull
private final List<RouteBottomSheetItem> mItems;
public RoutesAdapter(@NonNull List<RouteBottomSheetItem> items)
{
mItems = items;
}
@NonNull
@Override
public RouteHolder onCreateViewHolder(ViewGroup parent, int viewType)
{
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View root = inflater.inflate(R.layout.item_myroute_button, parent, false);
return new RouteHolder(root);
}
@Override
public void onBindViewHolder(RouteHolder holder, int position)
{
RouteBottomSheetItem item = mItems.get(position);
holder.mItem = item;
holder.mTitle.setSelected(true);
holder.mTitle.setText(item.getRouteName());
holder.mTitleListener = item::onTitleClick;
holder.mRenameListener = item::onRenameClick;
holder.mDeleteListener = item::onDeleteClick;
}
@Override
public int getItemCount()
{
return mItems.size();
}
public void addRoute(@NonNull RouteBottomSheetItem item)
{
// Compare strings toUpperCase to ignore case
String routeName = item.getRouteName().toUpperCase();
String iName;
// Find index to add ordered
int pos = mItems.size();
for (int i = 0; i < mItems.size(); i++)
{
iName = mItems.get(i).getRouteName().toUpperCase();
if(routeName.compareTo(iName) < 0)
{
pos = i;
break;
}
}
mItems.add(pos, item);
notifyItemInserted(pos);
}
public void removeRoute(@NonNull RouteBottomSheetItem item)
{
int pos = mItems.indexOf(item);
mItems.remove(item);
notifyItemRemoved(pos);
}
}

View file

@ -33,6 +33,14 @@
android:layout_marginBottom="@dimen/margin_half"
app:layout_constraintBottom_toTopOf="@+id/btn_bookmarks"
app:layout_constraintStart_toStartOf="parent" />
<include
layout="@layout/map_buttons_myroutes"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginBottom="@dimen/margin_half"
app:layout_constraintBottom_toTopOf="@+id/btn_search"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/map_buttons_inner_right"

View file

@ -57,4 +57,12 @@
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentEnd="true" />
<include
layout="@layout/map_buttons_myroutes"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginBottom="@dimen/margin_half"
android:layout_alignParentStart="true"
android:layout_marginStart="@dimen/margin_half" />
</RelativeLayout>

View file

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/layers_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_base"
android:layout_marginTop="@dimen/margin_base"
android:text="My Routes"
android:textAppearance="?fontHeadline6"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/close_button"
style="@style/Widget.MaterialComponents.Button.UnelevatedButton"
android:layout_width="@dimen/place_page_top_button"
android:layout_height="@dimen/place_page_top_button"
android:layout_marginEnd="@dimen/margin_half_plus"
android:layout_marginTop="@dimen/margin_half_plus"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/close"
app:icon="@drawable/ic_close"
app:iconGravity="textStart"
app:iconPadding="0dp"
app:iconSize="24dp"
app:iconTint="?iconTint"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Button.Round" />
</androidx.constraintlayout.widget.ConstraintLayout>
<Button
android:id="@+id/save_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Save Current Route"
android:layout_gravity="center_horizontal"
android:padding="16dp"/>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?cardBackground"
android:clipToPadding="false"
android:paddingBottom="@dimen/action_bar_extended_height"
android:scrollbars="none"/>
</androidx.core.widget.NestedScrollView>
<View
android:id="@+id/divider_bottom"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?dividerHorizontal"/>
</LinearLayout>

View file

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
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:layout_width="match_parent"
android:layout_height="@dimen/height_item_edit_bookmark"
android:background="?clickableBackground">
<Button
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="?selectableItemBackgroundBorderless"
android:layout_marginTop="@dimen/margin_base"
android:layout_marginStart="8dp"
android:paddingHorizontal="8dp"
android:layout_toStartOf="@+id/rename"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:ellipsize="middle"
android:singleLine="true"
android:textAppearance="?fontBody1"
tools:text="Route name looooooooooooooooooongasdasdasd"
android:gravity="center"
android:clickable="true"
android:focusable="true"/>
<ImageView
android:id="@+id/rename"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="?selectableItemBackgroundBorderless"
android:layout_toStartOf="@+id/delete"
android:paddingHorizontal="8dp"
app:srcCompat="@drawable/ic_edit"
app:tint="?secondary"
android:importantForAccessibility="no" />
<ImageView
android:id="@+id/delete"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentEnd="true"
android:background="?selectableItemBackgroundBorderless"
android:paddingHorizontal="@dimen/margin_half"
app:srcCompat="@drawable/ic_delete"
app:tint="?secondary"
android:importantForAccessibility="no" />
</RelativeLayout>

View file

@ -8,32 +8,43 @@
android:clipChildren="false"
android:clipToPadding="false"
tools:background="@color/bg_primary">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/map_buttons_inner_left"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginTop="@dimen/map_buttons_bottom_margin"
android:layout_marginBottom="@dimen/map_buttons_bottom_margin"
android:clipChildren="false"
android:clipToPadding="false"
android:padding="@dimen/nav_frame_padding"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent">
<include
layout="@layout/map_buttons_bookmarks"
android:id="@+id/map_buttons_inner_left"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_height="match_parent"
android:layout_marginBottom="@dimen/map_buttons_bottom_margin"
android:clipChildren="false"
android:clipToPadding="false"
android:padding="@dimen/nav_frame_padding"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
app:layout_constraintStart_toStartOf="parent">
<include
layout="@layout/map_buttons_search"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginBottom="@dimen/margin_half"
app:layout_constraintBottom_toTopOf="@+id/btn_bookmarks"
app:layout_constraintStart_toStartOf="parent" />
layout="@layout/map_buttons_bookmarks"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<include
layout="@layout/map_buttons_search"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginBottom="@dimen/margin_half"
app:layout_constraintBottom_toTopOf="@+id/btn_bookmarks"
app:layout_constraintStart_toStartOf="parent" />
<include
layout="@layout/map_buttons_myroutes"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginBottom="@dimen/margin_half"
app:layout_constraintBottom_toTopOf="@+id/btn_search"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/map_buttons_inner_right"

View file

@ -57,4 +57,11 @@
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentEnd="true" />
<include
layout="@layout/map_buttons_myroutes"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginBottom="@dimen/margin_half"
android:layout_toStartOf="@+id/map_buttons_bottom" />
</RelativeLayout>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.floatingactionbutton.FloatingActionButton
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/btn_myroutes"
style="@style/MwmWidget.MapButton"
android:contentDescription="@string/tracks_title"
app:srcCompat="@drawable/ic_bookmark_start" />