Compare commits

...
Sign in to create a new pull request.

6 commits

Author SHA1 Message Date
42b9f7bb5f Merge remote-tracking branch 'origin/vng-data' 2024-11-26 13:57:50 +00:00
4ab9869bf5 Merge branch 'sl-rb' 2024-11-26 13:57:21 +00:00
d2af16d213 Merge branch 'android-auto/sort-bookmarks' 2024-11-26 13:56:48 +00:00
Viktor Govako
a15eb602fb [planet] New data from 241122.
Signed-off-by: Viktor Govako <viktor.govako@gmail.com>
2024-11-25 12:46:03 -03:00
TobiPeterG
48aee896c8 [android] SpeedLimitView
Rebase + improvements, original by AndrewShkrob

Signed-off-by: TobiPeterG <tobi.goergens@gmail.com>
2024-11-24 23:41:29 +01:00
Andrew Shkrob
10c9f39839 [android-auto] Add bookmarks sorting
Signed-off-by: Andrew Shkrob <andrew.shkrob.social@yandex.by>
2024-08-31 13:09:10 +02:00
14 changed files with 2903 additions and 2352 deletions

View file

@ -916,7 +916,7 @@ public enum BookmarkManager
public interface BookmarksSortingListener
{
void onBookmarksSortingCompleted(@NonNull SortedBlock[] sortedBlocks, long timestamp);
void onBookmarksSortingCancelled(long timestamp);
default void onBookmarksSortingCancelled(long timestamp) {};
}
public interface BookmarksSharingListener

View file

@ -21,6 +21,7 @@ import app.organicmaps.bookmarks.data.BookmarkCategory;
import app.organicmaps.bookmarks.data.BookmarkInfo;
import app.organicmaps.bookmarks.data.BookmarkManager;
import app.organicmaps.bookmarks.data.Icon;
import app.organicmaps.bookmarks.data.SortedBlock;
import app.organicmaps.car.util.Colors;
import app.organicmaps.car.util.RoutingHelpers;
import app.organicmaps.location.LocationHelper;
@ -29,11 +30,14 @@ import app.organicmaps.util.Graphics;
import app.organicmaps.util.concurrency.ThreadPool;
import app.organicmaps.util.concurrency.UiThread;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Future;
public class BookmarksLoader
class BookmarksLoader implements BookmarkManager.BookmarksSortingListener
{
public interface OnBookmarksLoaded
{
@ -47,32 +51,102 @@ public class BookmarksLoader
// each row contains a unique icon, resulting in serialization of each icon.
private static final int MAX_BOOKMARKS_SIZE = 50;
public static void load(@NonNull CarContext carContext, @NonNull BookmarkCategory bookmarkCategory, @NonNull OnBookmarksLoaded onBookmarksLoaded)
@Nullable
private Future<?> mBookmarkLoaderTask = null;
@NonNull
private final CarContext mCarContext;
@NonNull
private final OnBookmarksLoaded mOnBookmarksLoaded;
private final long mBookmarkCategoryId;
private final int mBookmarksListSize;
public BookmarksLoader(@NonNull CarContext carContext, @NonNull BookmarkCategory bookmarkCategory, @NonNull OnBookmarksLoaded onBookmarksLoaded)
{
UiThread.run(() -> {
final ConstraintManager constraintManager = carContext.getCarService(ConstraintManager.class);
final int maxCategoriesSize = constraintManager.getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_LIST);
final long bookmarkCategoryId = bookmarkCategory.getId();
final int bookmarkCategoriesSize = Math.min(bookmarkCategory.getBookmarksCount(), Math.min(maxCategoriesSize, MAX_BOOKMARKS_SIZE));
final ConstraintManager constraintManager = carContext.getCarService(ConstraintManager.class);
final int maxCategoriesSize = constraintManager.getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_LIST);
final BookmarkInfo[] bookmarks = new BookmarkInfo[bookmarkCategoriesSize];
for (int i = 0; i < bookmarkCategoriesSize; ++i)
{
final long id = BookmarkManager.INSTANCE.getBookmarkIdByPosition(bookmarkCategoryId, i);
bookmarks[i] = new BookmarkInfo(bookmarkCategoryId, id);
}
mCarContext = carContext;
mOnBookmarksLoaded = onBookmarksLoaded;
mBookmarkCategoryId = bookmarkCategory.getId();
mBookmarksListSize = Math.min(bookmarkCategory.getBookmarksCount(), Math.min(maxCategoriesSize, MAX_BOOKMARKS_SIZE));
}
ThreadPool.getWorker().submit(() -> {
final ItemList bookmarksList = createBookmarksList(carContext, bookmarks);
UiThread.run(() -> onBookmarksLoaded.onBookmarksLoaded(bookmarksList));
public void load()
{
UiThread.runLater(() -> {
BookmarkManager.INSTANCE.addSortingListener(this);
if (sortBookmarks())
return;
final List<Long> bookmarkIds = new ArrayList<>();
for (int i = 0; i < mBookmarksListSize; ++i)
bookmarkIds.add(BookmarkManager.INSTANCE.getBookmarkIdByPosition(mBookmarkCategoryId, i));
loadBookmarks(bookmarkIds);
});
}
public void cancel()
{
BookmarkManager.INSTANCE.removeSortingListener(this);
if (mBookmarkLoaderTask != null)
{
mBookmarkLoaderTask.cancel(true);
mBookmarkLoaderTask = null;
}
}
/**
* Calls BookmarkManager to sort bookmarks.
*
* @return false if the sorting not needed or can't be done.
*/
private boolean sortBookmarks()
{
if (!BookmarkManager.INSTANCE.hasLastSortingType(mBookmarkCategoryId))
return false;
final int sortingType = BookmarkManager.INSTANCE.getLastSortingType(mBookmarkCategoryId);
if (sortingType < 0)
return false;
final Location loc = LocationHelper.from(mCarContext).getSavedLocation();
final boolean hasMyPosition = loc != null;
if (!hasMyPosition && sortingType == BookmarkManager.SORT_BY_DISTANCE)
return false;
final double lat = hasMyPosition ? loc.getLatitude() : 0;
final double lon = hasMyPosition ? loc.getLongitude() : 0;
BookmarkManager.INSTANCE.getSortedCategory(mBookmarkCategoryId, sortingType, hasMyPosition, lat, lon, 0);
return true;
}
private void loadBookmarks(@NonNull List<Long> bookmarksIds)
{
final BookmarkInfo[] bookmarks = new BookmarkInfo[mBookmarksListSize];
for (int i = 0; i < mBookmarksListSize && i < bookmarksIds.size(); ++i)
{
final long id = bookmarksIds.get(i);
bookmarks[i] = new BookmarkInfo(mBookmarkCategoryId, id);
}
mBookmarkLoaderTask = ThreadPool.getWorker().submit(() -> {
final ItemList bookmarksList = createBookmarksList(bookmarks);
UiThread.run(() -> {
cancel();
mOnBookmarksLoaded.onBookmarksLoaded(bookmarksList);
});
});
}
@NonNull
private static ItemList createBookmarksList(@NonNull CarContext carContext, @NonNull BookmarkInfo[] bookmarks)
private ItemList createBookmarksList(@NonNull BookmarkInfo[] bookmarks)
{
final Location location = LocationHelper.from(carContext).getSavedLocation();
final Location location = LocationHelper.from(mCarContext).getSavedLocation();
final ItemList.Builder builder = new ItemList.Builder();
final Map<Icon, CarIcon> iconsCache = new HashMap<>();
for (final BookmarkInfo bookmarkInfo : bookmarks)
@ -91,7 +165,7 @@ public class BookmarksLoader
R.dimen.track_circle_size,
icon.getResId(),
R.dimen.bookmark_icon_size,
carContext);
mCarContext);
final CarIcon carIcon = new CarIcon.Builder(IconCompat.createWithBitmap(Graphics.drawableToBitmap(drawable))).build();
iconsCache.put(icon, carIcon);
}
@ -105,20 +179,31 @@ public class BookmarksLoader
@NonNull
private static CharSequence getDescription(@NonNull BookmarkInfo bookmark, @Nullable Location location)
{
final SpannableStringBuilder result = new SpannableStringBuilder(" ");
final SpannableStringBuilder result = new SpannableStringBuilder("");
if (location != null)
{
result.append(" ");
final Distance distance = bookmark.getDistance(location.getLatitude(), location.getLongitude(), 0.0);
result.setSpan(DistanceSpan.create(RoutingHelpers.createDistance(distance)), 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
result.setSpan(ForegroundCarColorSpan.create(Colors.DISTANCE), 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
if (!bookmark.getFeatureType().isEmpty())
{
if (!bookmark.getFeatureType().isEmpty())
{
if (result.length() > 0)
result.append("");
result.append(bookmark.getFeatureType());
}
result.append(bookmark.getFeatureType());
}
return result;
}
@Override
public void onBookmarksSortingCompleted(@NonNull SortedBlock[] sortedBlocks, long timestamp)
{
final List<Long> bookmarkIds = new ArrayList<>();
for (final SortedBlock block : sortedBlocks)
bookmarkIds.addAll(block.getBookmarkIds());
loadBookmarks(bookmarkIds);
}
}

View file

@ -4,12 +4,16 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.car.app.CarContext;
import androidx.car.app.model.Action;
import androidx.car.app.model.CarIcon;
import androidx.car.app.model.Header;
import androidx.car.app.model.ItemList;
import androidx.car.app.model.ListTemplate;
import androidx.car.app.model.Template;
import androidx.car.app.navigation.model.MapWithContentTemplate;
import androidx.core.graphics.drawable.IconCompat;
import androidx.lifecycle.LifecycleOwner;
import app.organicmaps.R;
import app.organicmaps.bookmarks.data.BookmarkCategory;
import app.organicmaps.car.SurfaceRenderer;
import app.organicmaps.car.screens.base.BaseMapScreen;
@ -20,13 +24,19 @@ public class BookmarksScreen extends BaseMapScreen
@NonNull
private final BookmarkCategory mBookmarkCategory;
@NonNull
private final BookmarksLoader mBookmarksLoader;
@Nullable
private ItemList mBookmarksList = null;
private boolean mIsOnSortingScreen = false;
public BookmarksScreen(@NonNull CarContext carContext, @NonNull SurfaceRenderer surfaceRenderer, @NonNull BookmarkCategory bookmarkCategory)
{
super(carContext, surfaceRenderer);
mBookmarkCategory = bookmarkCategory;
mBookmarksLoader = new BookmarksLoader(carContext, mBookmarkCategory, this::onBookmarksLoaded);
}
@NonNull
@ -39,12 +49,20 @@ public class BookmarksScreen extends BaseMapScreen
return builder.build();
}
@Override
public void onStop(@NonNull LifecycleOwner owner)
{
if (!mIsOnSortingScreen)
mBookmarksLoader.cancel();
}
@NonNull
private Header createHeader()
{
final Header.Builder builder = new Header.Builder();
builder.setStartHeaderAction(Action.BACK);
builder.setTitle(mBookmarkCategory.getName());
builder.addEndHeaderAction(createSortingAction());
return builder.build();
}
@ -57,14 +75,39 @@ public class BookmarksScreen extends BaseMapScreen
if (mBookmarksList == null)
{
builder.setLoading(true);
BookmarksLoader.load(getCarContext(), mBookmarkCategory, (bookmarksList) -> {
mBookmarksList = bookmarksList;
invalidate();
});
mBookmarksLoader.load();
}
else
builder.setSingleList(mBookmarksList);
return builder.build();
}
@NonNull
private Action createSortingAction()
{
final Action.Builder builder = new Action.Builder();
builder.setIcon(new CarIcon.Builder(IconCompat.createWithResource(getCarContext(), R.drawable.ic_sort)).build());
builder.setOnClickListener(() -> {
mIsOnSortingScreen = true;
getScreenManager().pushForResult(new SortingScreen(getCarContext(), getSurfaceRenderer(), mBookmarkCategory), this::onSortingResult);
});
return builder.build();
}
private void onBookmarksLoaded(@NonNull ItemList bookmarksList)
{
mBookmarksList = bookmarksList;
invalidate();
}
private void onSortingResult(final Object result)
{
mIsOnSortingScreen = false;
if (Boolean.TRUE.equals(result))
{
mBookmarksList = null;
invalidate();
}
}
}

View file

@ -0,0 +1,159 @@
package app.organicmaps.car.screens.bookmarks;
import android.location.Location;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import androidx.car.app.CarContext;
import androidx.car.app.model.Action;
import androidx.car.app.model.CarIcon;
import androidx.car.app.model.Header;
import androidx.car.app.model.ItemList;
import androidx.car.app.model.ListTemplate;
import androidx.car.app.model.Row;
import androidx.car.app.model.Template;
import androidx.car.app.navigation.model.MapWithContentTemplate;
import androidx.core.graphics.drawable.IconCompat;
import androidx.lifecycle.LifecycleOwner;
import app.organicmaps.R;
import app.organicmaps.bookmarks.data.BookmarkCategory;
import app.organicmaps.bookmarks.data.BookmarkManager;
import app.organicmaps.car.SurfaceRenderer;
import app.organicmaps.car.screens.base.BaseMapScreen;
import app.organicmaps.car.util.UiHelpers;
import app.organicmaps.location.LocationHelper;
import java.util.Arrays;
import java.util.stream.IntStream;
class SortingScreen extends BaseMapScreen
{
private static final int DEFAULT_SORTING_TYPE = -1;
@NonNull
private final CarIcon mRadioButtonIcon;
@NonNull
private final CarIcon mRadioButtonSelectedIcon;
private final long mBookmarkCategoryId;
private final @BookmarkManager.SortingType int mLastSortingType;
private @BookmarkManager.SortingType int mNewSortingType;
public SortingScreen(@NonNull CarContext carContext, @NonNull SurfaceRenderer surfaceRenderer, @NonNull BookmarkCategory bookmarkCategory)
{
super(carContext, surfaceRenderer);
mRadioButtonIcon = new CarIcon.Builder(IconCompat.createWithResource(carContext, R.drawable.ic_radio_button_unchecked)).build();
mRadioButtonSelectedIcon = new CarIcon.Builder(IconCompat.createWithResource(carContext, R.drawable.ic_radio_button_checked)).build();
mBookmarkCategoryId = bookmarkCategory.getId();
mLastSortingType = mNewSortingType = getLastSortingType();
}
@NonNull
@Override
public Template onGetTemplate()
{
final MapWithContentTemplate.Builder builder = new MapWithContentTemplate.Builder();
builder.setMapController(UiHelpers.createMapController(getCarContext(), getSurfaceRenderer()));
builder.setContentTemplate(createSortingTypesListTemplate());
return builder.build();
}
@Override
public void onStop(@NonNull LifecycleOwner owner)
{
super.onStop(owner);
final boolean sortingTypeChanged = mNewSortingType != mLastSortingType;
setResult(sortingTypeChanged);
}
@NonNull
private Header createHeader()
{
final Header.Builder builder = new Header.Builder();
builder.setStartHeaderAction(Action.BACK);
builder.setTitle(getCarContext().getString(R.string.sort_bookmarks));
return builder.build();
}
@NonNull
private ListTemplate createSortingTypesListTemplate()
{
final ListTemplate.Builder builder = new ListTemplate.Builder();
builder.setHeader(createHeader());
builder.setSingleList(createSortingTypesList(getAvailableSortingTypes(), getLastAvailableSortingType()));
return builder.build();
}
@NonNull
private ItemList createSortingTypesList(@NonNull final @BookmarkManager.SortingType int[] availableSortingTypes, final int lastSortingType)
{
final ItemList.Builder builder = new ItemList.Builder();
for (int type : IntStream.concat(IntStream.of(DEFAULT_SORTING_TYPE), Arrays.stream(availableSortingTypes)).toArray())
{
final Row.Builder rowBuilder = new Row.Builder();
rowBuilder.setTitle(getCarContext().getString(sortingTypeToStringRes(type)));
if (type == lastSortingType)
rowBuilder.setImage(mRadioButtonSelectedIcon);
else
{
rowBuilder.setImage(mRadioButtonIcon);
rowBuilder.setOnClickListener(() -> {
if (type == DEFAULT_SORTING_TYPE)
BookmarkManager.INSTANCE.resetLastSortingType(mBookmarkCategoryId);
else
BookmarkManager.INSTANCE.setLastSortingType(mBookmarkCategoryId, type);
mNewSortingType = type;
invalidate();
});
}
builder.addItem(rowBuilder.build());
}
return builder.build();
}
@StringRes
private int sortingTypeToStringRes(@BookmarkManager.SortingType int sortingType)
{
return switch (sortingType)
{
case BookmarkManager.SORT_BY_TYPE -> R.string.by_type;
case BookmarkManager.SORT_BY_DISTANCE -> R.string.by_distance;
case BookmarkManager.SORT_BY_TIME -> R.string.by_date;
case BookmarkManager.SORT_BY_NAME -> R.string.by_name;
default -> R.string.by_default;
};
}
@NonNull
@BookmarkManager.SortingType
private int[] getAvailableSortingTypes()
{
final Location loc = LocationHelper.from(getCarContext()).getSavedLocation();
final boolean hasMyPosition = loc != null;
return BookmarkManager.INSTANCE.getAvailableSortingTypes(mBookmarkCategoryId, hasMyPosition);
}
private int getLastSortingType()
{
if (BookmarkManager.INSTANCE.hasLastSortingType(mBookmarkCategoryId))
return BookmarkManager.INSTANCE.getLastSortingType(mBookmarkCategoryId);
return DEFAULT_SORTING_TYPE;
}
private int getLastAvailableSortingType()
{
int currentType = getLastSortingType();
@BookmarkManager.SortingType int[] types = getAvailableSortingTypes();
for (@BookmarkManager.SortingType int type : types)
{
if (type == currentType)
return currentType;
}
return DEFAULT_SORTING_TYPE;
}
}

View file

@ -1,5 +1,6 @@
package app.organicmaps.routing;
import android.location.Location;
import android.text.TextUtils;
import android.view.View;
import android.widget.ImageView;
@ -13,11 +14,13 @@ import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import app.organicmaps.Framework;
import app.organicmaps.R;
import app.organicmaps.location.LocationHelper;
import app.organicmaps.maplayer.traffic.TrafficManager;
import app.organicmaps.util.UiUtils;
import app.organicmaps.util.Utils;
import app.organicmaps.util.WindowInsetUtils;
import app.organicmaps.widget.LanesView;
import app.organicmaps.widget.SpeedLimitView;
import app.organicmaps.util.WindowInsetUtils;
import app.organicmaps.widget.menu.NavMenu;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
@ -38,6 +41,8 @@ public class NavigationController implements TrafficManager.TrafficCallback,
@NonNull
private final LanesView mLanesView;
@NonNull
private final SpeedLimitView mSpeedLimit;
private final NavMenu mNavMenu;
View.OnClickListener mOnSettingsClickListener;
@ -75,6 +80,8 @@ public class NavigationController implements TrafficManager.TrafficCallback,
mLanesView = topFrame.findViewById(R.id.lanes);
mSpeedLimit = topFrame.findViewById(R.id.nav_speed_limit);
// Show a blank view below the navbar to hide the menu content
final View navigationBarBackground = mFrame.findViewById(R.id.nav_bottom_sheet_nav_bar);
final View nextTurnContainer = mFrame.findViewById(R.id.nav_next_turn_container);
@ -108,6 +115,8 @@ public class NavigationController implements TrafficManager.TrafficCallback,
info.nextCarDirection.setNextTurnDrawable(mNextNextTurnImage);
mLanesView.setLanes(info.lanes);
updateSpeedLimit(info);
}
private void updatePedestrian(@NonNull RoutingInfo info)
@ -236,4 +245,14 @@ public class NavigationController implements TrafficManager.TrafficCallback,
RoutingController.get().cancel();
}
private void updateSpeedLimit(@NonNull final RoutingInfo info)
{
final Location location = LocationHelper.from(mFrame.getContext()).getSavedLocation();
if (location == null) {
mSpeedLimit.setSpeedLimitMps(0);
return;
}
mSpeedLimit.setCurrentSpeed(location.getSpeed());
mSpeedLimit.setSpeedLimitMps(info.speedLimitMps);
}
}

View file

@ -140,7 +140,7 @@ public final class UiUtils
public static void showIf(boolean condition, View view)
{
view.setVisibility(condition ? View.VISIBLE : View.GONE);
view.setVisibility(condition ? View.VISIBLE : View.INVISIBLE);
}
public static void hideIf(boolean condition, View... views)

View file

@ -0,0 +1,221 @@
package app.organicmaps.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.util.Pair;
import android.view.View;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import app.organicmaps.R;
import app.organicmaps.util.StringUtils;
public class SpeedLimitView extends View
{
private interface DefaultValues
{
@ColorInt
int BACKGROUND_COLOR = Color.WHITE;
@ColorInt
int TEXT_COLOR = Color.BLACK;
@ColorInt
int TEXT_ALERT_COLOR = Color.WHITE;
float BORDER_WIDTH_RATIO = 0.1f;
}
@ColorInt
private final int mBackgroundColor;
@ColorInt
private final int mBorderColor;
@ColorInt
private final int mAlertColor;
@ColorInt
private final int mTextColor;
@ColorInt
private final int mTextAlertColor;
@NonNull
private final Paint mSignBackgroundPaint;
@NonNull
private final Paint mSignBorderPaint;
@NonNull
private final Paint mTextPaint;
private float mWidth;
private float mHeight;
private float mBackgroundRadius;
private float mBorderRadius;
private float mBorderWidth;
private double mSpeedLimitMps;
@Nullable
private String mSpeedLimitStr;
private double mCurrentSpeed;
public SpeedLimitView(Context context, @Nullable AttributeSet attrs)
{
super(context, attrs);
try (TypedArray data = context.getTheme()
.obtainStyledAttributes(attrs, R.styleable.SpeedLimitView, 0, 0))
{
mBackgroundColor = data.getColor(R.styleable.SpeedLimitView_BackgroundColor, DefaultValues.BACKGROUND_COLOR);
mBorderColor = data.getColor(R.styleable.SpeedLimitView_borderColor, ContextCompat.getColor(context, R.color.base_red));
mAlertColor = data.getColor(R.styleable.SpeedLimitView_alertColor, ContextCompat.getColor(context, R.color.base_red));
mTextColor = data.getColor(R.styleable.SpeedLimitView_textColor, DefaultValues.TEXT_COLOR);
mTextAlertColor = data.getColor(R.styleable.SpeedLimitView_textAlertColor, DefaultValues.TEXT_ALERT_COLOR);
if (isInEditMode())
{
mSpeedLimitMps = data.getInt(R.styleable.SpeedLimitView_editModeSpeedLimit, -1);
mSpeedLimitStr = mSpeedLimitMps > 0 ? String.valueOf(((int) mSpeedLimitMps)) : null;
mCurrentSpeed = data.getInt(R.styleable.SpeedLimitView_editModeCurrentSpeed, -1);
}
}
mSignBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mSignBackgroundPaint.setColor(mBackgroundColor);
mSignBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mSignBorderPaint.setColor(mBorderColor);
mSignBorderPaint.setStrokeWidth(mBorderWidth);
mSignBorderPaint.setStyle(Paint.Style.STROKE);
mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mTextPaint.setColor(mTextColor);
mTextPaint.setTextAlign(Paint.Align.CENTER);
mTextPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD));
}
public void setSpeedLimitMps(final double speedLimitMps)
{
if (mSpeedLimitMps == speedLimitMps)
return;
mSpeedLimitMps = speedLimitMps;
if (mSpeedLimitMps <= 0)
{
mSpeedLimitStr = null;
setVisibility(GONE);
return;
}
final Pair<String, String> speedLimitAndUnits = StringUtils.nativeFormatSpeedAndUnits(mSpeedLimitMps);
setVisibility(VISIBLE);
mSpeedLimitStr = speedLimitAndUnits.first;
configureTextSize();
invalidate();
}
public void setCurrentSpeed(final double currentSpeed)
{
mCurrentSpeed = currentSpeed;
invalidate();
}
@Override
protected void onDraw(@NonNull Canvas canvas)
{
super.onDraw(canvas);
final boolean alert = mCurrentSpeed > mSpeedLimitMps && mSpeedLimitMps > 0;
final float cx = mWidth / 2;
final float cy = mHeight / 2;
drawSign(canvas, cx, cy, alert);
drawText(canvas, cx, cy, alert);
}
private void drawSign(@NonNull Canvas canvas, float cx, float cy, boolean alert)
{
if (alert)
mSignBackgroundPaint.setColor(mAlertColor);
else
mSignBackgroundPaint.setColor(mBackgroundColor);
canvas.drawCircle(cx, cy, mBackgroundRadius, mSignBackgroundPaint);
if (!alert)
{
mSignBorderPaint.setStrokeWidth(mBorderWidth);
canvas.drawCircle(cx, cy, mBorderRadius, mSignBorderPaint);
}
}
private void drawText(@NonNull Canvas canvas, float cx, float cy, boolean alert)
{
if (mSpeedLimitStr == null)
return;
if (alert)
mTextPaint.setColor(mTextAlertColor);
else
mTextPaint.setColor(mTextColor);
final Rect textBounds = new Rect();
mTextPaint.getTextBounds(mSpeedLimitStr, 0, mSpeedLimitStr.length(), textBounds);
final float textY = cy - textBounds.exactCenterY();
canvas.drawText(mSpeedLimitStr, cx, textY, mTextPaint);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
{
super.onSizeChanged(w, h, oldw, oldh);
final float paddingX = (float) (getPaddingLeft() + getPaddingRight());
final float paddingY = (float) (getPaddingTop() + getPaddingBottom());
mWidth = (float) w - paddingX;
mHeight = (float) h - paddingY;
mBackgroundRadius = Math.min(mWidth, mHeight) / 2;
mBorderWidth = mBackgroundRadius * 2 * DefaultValues.BORDER_WIDTH_RATIO;
mBorderRadius = mBackgroundRadius - mBorderWidth / 2;
configureTextSize();
}
// Apply binary search to determine the optimal text size that fits within the circular boundary.
private void configureTextSize()
{
if (mSpeedLimitStr == null)
return;
final String text = mSpeedLimitStr;
final float textRadius = mBorderRadius - mBorderWidth;
final float textMaxSize = 2 * textRadius;
final float textMaxSizeSquared = (float) Math.pow(textMaxSize, 2);
float lowerBound = 0;
float upperBound = textMaxSize;
float textSize = textMaxSize;
final Rect textBounds = new Rect();
while (lowerBound <= upperBound)
{
textSize = (lowerBound + upperBound) / 2;
mTextPaint.setTextSize(textSize);
mTextPaint.getTextBounds(text, 0, text.length(), textBounds);
if (Math.pow(textBounds.width(), 2) + Math.pow(textBounds.height(), 2) <= textMaxSizeSquared)
lowerBound = textSize + 1;
else
upperBound = textSize - 1;
}
mTextPaint.setTextSize(Math.max(1, textSize));
}
}

View file

@ -213,14 +213,7 @@ public class NavMenu
return;
Pair<String, String> speedAndUnits = StringUtils.nativeFormatSpeedAndUnits(last.getSpeed());
if (info.speedLimitMps > 0.0)
{
Pair<String, String> speedLimitAndUnits = StringUtils.nativeFormatSpeedAndUnits(info.speedLimitMps);
mSpeedValue.setText(speedAndUnits.first + "\u202F/\u202F" + speedLimitAndUnits.first);
}
else
mSpeedValue.setText(speedAndUnits.first);
mSpeedValue.setText(speedAndUnits.first);
if (info.speedLimitMps > 0.0 && last.getSpeed() > info.speedLimitMps)
{

View file

@ -110,12 +110,11 @@
android:id="@+id/lanes"
android:layout_width="0dp"
android:layout_height="68dp"
android:layout_marginEnd="@dimen/margin_half"
android:layout_marginTop="@dimen/margin_half"
android:layout_margin="@dimen/margin_half"
android:padding="@dimen/margin_half"
android:visibility="gone"
app:layout_constraintStart_toEndOf="@+id/nav_speed_limit"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/nav_next_turn_container"
app:layout_constraintTop_toBottomOf="@id/street_frame"
app:activeLaneTintColor="?navLaneArrowActiveColor"
app:inactiveLaneTintColor="?navLaneArrowInactiveColor"
@ -123,4 +122,14 @@
app:cornerRadius="@dimen/margin_quarter"
app:editModeLanesCount="10"
tools:visibility="visible" />
<app.organicmaps.widget.SpeedLimitView
android:id="@+id/nav_speed_limit"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_margin="@dimen/margin_half"
app:editModeCurrentSpeed="90"
app:editModeSpeedLimit="120"
app:layout_constraintStart_toEndOf="@id/nav_next_turn_container"
app:layout_constraintTop_toBottomOf="@id/street_frame" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -94,7 +94,7 @@
android:layout_alignEnd="@id/nav_next_turn_frame"
android:background="?navNextNextTurnFrame"
android:elevation="@dimen/nav_elevation"
android:visibility="gone"
android:visibility="invisible"
tools:visibility="visible">
<ImageView
android:id="@id/turn"
@ -123,4 +123,15 @@
app:cornerRadius="@dimen/margin_quarter"
app:editModeLanesCount="5"
tools:visibility="visible" />
<app.organicmaps.widget.SpeedLimitView
android:id="@+id/nav_speed_limit"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_margin="@dimen/margin_half"
app:editModeCurrentSpeed="90"
app:editModeSpeedLimit="120"
app:layout_constraintEnd_toEndOf="@id/nav_next_turn_container"
app:layout_constraintStart_toStartOf="@id/nav_next_turn_container"
app:layout_constraintTop_toBottomOf="@id/nav_next_turn_container" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,5 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="SpeedLimitView">
<attr name="BackgroundColor" format="color" />
<attr name="borderColor" format="color" />
<attr name="alertColor" format="color" />
<attr name="textColor" format="color" />
<attr name="textAlertColor" format="color" />
<!-- These values are used only in edit mode -->
<attr name="editModeSpeedLimit" format="integer" />
<attr name="editModeCurrentSpeed" format="integer" />
</declare-styleable>
<declare-styleable name="LanesView">
<attr name="activeLaneTintColor" format="reference" />
<attr name="inactiveLaneTintColor" format="color" />

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load diff