[android-auto] Fix bookmarks screen

Signed-off-by: Andrew Shkrob <andrew.shkrob.social@yandex.by>
This commit is contained in:
Andrew Shkrob 2024-03-15 22:09:26 +01:00 committed by Viktor Govako
parent 87fb28e9a9
commit 398588f892
6 changed files with 285 additions and 176 deletions

View file

@ -7,6 +7,8 @@ import androidx.annotation.DrawableRes;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import com.google.common.base.Objects;
import app.organicmaps.R;
import java.lang.annotation.Retention;
@ -232,17 +234,17 @@ public class Icon implements Parcelable
@Override
public boolean equals(Object o)
{
if (o == null || !(o instanceof Icon))
return false;
final Icon comparedIcon = (Icon) o;
return mColor == comparedIcon.getColor();
if (this == o)
return true;
if (o instanceof Icon comparedIcon)
return mColor == comparedIcon.mColor && mType == comparedIcon.mType;
return false;
}
@Override
@PredefinedColor
public int hashCode()
{
return mColor;
return Objects.hashCode(mColor, mType);
}
public static final Parcelable.Creator<Icon> CREATOR = new Parcelable.Creator<>()

View file

@ -1,169 +0,0 @@
package app.organicmaps.car.screens;
import static java.util.Objects.requireNonNull;
import android.graphics.drawable.Drawable;
import android.location.Location;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.car.app.CarContext;
import androidx.car.app.constraints.ConstraintManager;
import androidx.car.app.model.Action;
import androidx.car.app.model.CarIcon;
import androidx.car.app.model.DistanceSpan;
import androidx.car.app.model.ForegroundCarColorSpan;
import androidx.car.app.model.Header;
import androidx.car.app.model.ItemList;
import androidx.car.app.model.Row;
import androidx.car.app.model.Template;
import androidx.car.app.navigation.model.MapTemplate;
import androidx.core.graphics.drawable.IconCompat;
import app.organicmaps.R;
import app.organicmaps.bookmarks.data.BookmarkCategory;
import app.organicmaps.bookmarks.data.BookmarkInfo;
import app.organicmaps.bookmarks.data.BookmarkManager;
import app.organicmaps.car.SurfaceRenderer;
import app.organicmaps.car.screens.base.BaseMapScreen;
import app.organicmaps.car.util.Colors;
import app.organicmaps.car.util.RoutingHelpers;
import app.organicmaps.car.util.UiHelpers;
import app.organicmaps.location.LocationHelper;
import app.organicmaps.util.Distance;
import app.organicmaps.util.Graphics;
import java.util.ArrayList;
import java.util.List;
public class BookmarksScreen extends BaseMapScreen
{
private final int MAX_CATEGORIES_SIZE;
@Nullable
private BookmarkCategory mBookmarkCategory;
public BookmarksScreen(@NonNull CarContext carContext, @NonNull SurfaceRenderer surfaceRenderer)
{
super(carContext, surfaceRenderer);
final ConstraintManager constraintManager = getCarContext().getCarService(ConstraintManager.class);
MAX_CATEGORIES_SIZE = constraintManager.getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_LIST);
}
private BookmarksScreen(@NonNull CarContext carContext, @NonNull SurfaceRenderer surfaceRenderer, @NonNull BookmarkCategory bookmarkCategory)
{
this(carContext, surfaceRenderer);
mBookmarkCategory = bookmarkCategory;
}
@NonNull
@Override
public Template onGetTemplate()
{
final MapTemplate.Builder builder = new MapTemplate.Builder();
builder.setHeader(createHeader());
builder.setMapController(UiHelpers.createMapController(getCarContext(), getSurfaceRenderer()));
builder.setItemList(mBookmarkCategory == null ? createBookmarkCategoriesList() : createBookmarksList());
return builder.build();
}
@NonNull
private Header createHeader()
{
final Header.Builder builder = new Header.Builder();
builder.setStartHeaderAction(Action.BACK);
builder.setTitle(mBookmarkCategory == null ? getCarContext().getString(R.string.bookmarks) : mBookmarkCategory.getName());
return builder.build();
}
@NonNull
private ItemList createBookmarkCategoriesList()
{
final List<BookmarkCategory> bookmarkCategories = getBookmarks();
final int categoriesSize = Math.min(bookmarkCategories.size(), MAX_CATEGORIES_SIZE);
ItemList.Builder builder = new ItemList.Builder();
for (int i = 0; i < categoriesSize; ++i)
{
final BookmarkCategory bookmarkCategory = bookmarkCategories.get(i);
Row.Builder itemBuilder = new Row.Builder();
itemBuilder.setTitle(bookmarkCategory.getName());
itemBuilder.addText(bookmarkCategory.getDescription());
itemBuilder.setOnClickListener(() -> getScreenManager().push(new BookmarksScreen(getCarContext(), getSurfaceRenderer(), bookmarkCategory)));
itemBuilder.setBrowsable(true);
builder.addItem(itemBuilder.build());
}
return builder.build();
}
@NonNull
private ItemList createBookmarksList()
{
final long bookmarkCategoryId = requireNonNull(mBookmarkCategory).getId();
final int bookmarkCategoriesSize = Math.min(mBookmarkCategory.getBookmarksCount(), MAX_CATEGORIES_SIZE);
ItemList.Builder builder = new ItemList.Builder();
for (int i = 0; i < bookmarkCategoriesSize; ++i)
{
final long bookmarkId = BookmarkManager.INSTANCE.getBookmarkIdByPosition(bookmarkCategoryId, i);
final BookmarkInfo bookmarkInfo = new BookmarkInfo(bookmarkCategoryId, bookmarkId);
final Row.Builder itemBuilder = new Row.Builder();
itemBuilder.setTitle(bookmarkInfo.getName());
if (!bookmarkInfo.getAddress().isEmpty())
itemBuilder.addText(bookmarkInfo.getAddress());
final CharSequence description = getDescription(bookmarkInfo);
if (description.length() != 0)
itemBuilder.addText(description);
final Drawable icon = Graphics.drawCircleAndImage(bookmarkInfo.getIcon().argb(),
R.dimen.track_circle_size,
bookmarkInfo.getIcon().getResId(),
R.dimen.bookmark_icon_size,
getCarContext());
itemBuilder.setImage(new CarIcon.Builder(IconCompat.createWithBitmap(Graphics.drawableToBitmap(icon))).build());
itemBuilder.setOnClickListener(() -> BookmarkManager.INSTANCE.showBookmarkOnMap(bookmarkId));
builder.addItem(itemBuilder.build());
}
return builder.build();
}
@NonNull
private CharSequence getDescription(final BookmarkInfo bookmark)
{
final SpannableStringBuilder result = new SpannableStringBuilder(" ");
final Location loc = LocationHelper.from(getCarContext()).getSavedLocation();
if (loc != null)
{
final Distance distance = bookmark.getDistance(loc.getLatitude(), loc.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 (loc != null && !bookmark.getFeatureType().isEmpty())
{
result.append("");
result.append(bookmark.getFeatureType());
}
return result;
}
@NonNull
private static List<BookmarkCategory> getBookmarks()
{
final List<BookmarkCategory> bookmarkCategories = new ArrayList<>(BookmarkManager.INSTANCE.getCategories());
final List<BookmarkCategory> toRemove = new ArrayList<>();
for (final BookmarkCategory bookmarkCategory : bookmarkCategories)
{
if (bookmarkCategory.getBookmarksCount() == 0 || !bookmarkCategory.isVisible())
toRemove.add(bookmarkCategory);
}
bookmarkCategories.removeAll(toRemove);
return bookmarkCategories;
}
}

View file

@ -16,6 +16,7 @@ import androidx.core.graphics.drawable.IconCompat;
import app.organicmaps.R;
import app.organicmaps.car.SurfaceRenderer;
import app.organicmaps.car.screens.base.BaseMapScreen;
import app.organicmaps.car.screens.bookmarks.BookmarkCategoriesScreen;
import app.organicmaps.car.screens.search.SearchScreen;
import app.organicmaps.car.util.SuggestionsHelpers;
import app.organicmaps.car.util.UiHelpers;
@ -127,6 +128,6 @@ public class MapScreen extends BaseMapScreen
// Details in UiHelpers.createSettingsAction()
if (getScreenManager().getTop() != this)
return;
getScreenManager().push(new BookmarksScreen(getCarContext(), getSurfaceRenderer()));
getScreenManager().push(new BookmarkCategoriesScreen(getCarContext(), getSurfaceRenderer()));
}
}

View file

@ -0,0 +1,90 @@
package app.organicmaps.car.screens.bookmarks;
import androidx.annotation.NonNull;
import androidx.car.app.CarContext;
import androidx.car.app.constraints.ConstraintManager;
import androidx.car.app.model.Action;
import androidx.car.app.model.Header;
import androidx.car.app.model.ItemList;
import androidx.car.app.model.Row;
import androidx.car.app.model.Template;
import androidx.car.app.navigation.model.MapTemplate;
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 java.util.ArrayList;
import java.util.List;
public class BookmarkCategoriesScreen extends BaseMapScreen
{
private final int MAX_CATEGORIES_SIZE;
public BookmarkCategoriesScreen(@NonNull CarContext carContext, @NonNull SurfaceRenderer surfaceRenderer)
{
super(carContext, surfaceRenderer);
final ConstraintManager constraintManager = getCarContext().getCarService(ConstraintManager.class);
MAX_CATEGORIES_SIZE = constraintManager.getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_LIST);
}
@NonNull
@Override
public Template onGetTemplate()
{
final MapTemplate.Builder builder = new MapTemplate.Builder();
builder.setHeader(createHeader());
builder.setMapController(UiHelpers.createMapController(getCarContext(), getSurfaceRenderer()));
builder.setItemList(createBookmarkCategoriesList());
return builder.build();
}
@NonNull
private Header createHeader()
{
final Header.Builder builder = new Header.Builder();
builder.setStartHeaderAction(Action.BACK);
builder.setTitle(getCarContext().getString(R.string.bookmarks));
return builder.build();
}
@NonNull
private ItemList createBookmarkCategoriesList()
{
final List<BookmarkCategory> bookmarkCategories = getBookmarks();
final int categoriesSize = Math.min(bookmarkCategories.size(), MAX_CATEGORIES_SIZE);
final ItemList.Builder builder = new ItemList.Builder();
for (int i = 0; i < categoriesSize; ++i)
{
final BookmarkCategory bookmarkCategory = bookmarkCategories.get(i);
Row.Builder itemBuilder = new Row.Builder();
itemBuilder.setTitle(bookmarkCategory.getName());
itemBuilder.addText(bookmarkCategory.getDescription());
itemBuilder.setOnClickListener(() -> getScreenManager().push(new BookmarksScreen(getCarContext(), getSurfaceRenderer(), bookmarkCategory)));
itemBuilder.setBrowsable(true);
builder.addItem(itemBuilder.build());
}
return builder.build();
}
@NonNull
private static List<BookmarkCategory> getBookmarks()
{
final List<BookmarkCategory> bookmarkCategories = new ArrayList<>(BookmarkManager.INSTANCE.getCategories());
final List<BookmarkCategory> toRemove = new ArrayList<>();
for (final BookmarkCategory bookmarkCategory : bookmarkCategories)
{
if (bookmarkCategory.getBookmarksCount() == 0 || !bookmarkCategory.isVisible())
toRemove.add(bookmarkCategory);
}
bookmarkCategories.removeAll(toRemove);
return bookmarkCategories;
}
}

View file

@ -0,0 +1,124 @@
package app.organicmaps.car.screens.bookmarks;
import android.graphics.drawable.Drawable;
import android.location.Location;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.car.app.CarContext;
import androidx.car.app.constraints.ConstraintManager;
import androidx.car.app.model.CarIcon;
import androidx.car.app.model.DistanceSpan;
import androidx.car.app.model.ForegroundCarColorSpan;
import androidx.car.app.model.ItemList;
import androidx.car.app.model.Row;
import androidx.core.graphics.drawable.IconCompat;
import app.organicmaps.R;
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.car.util.Colors;
import app.organicmaps.car.util.RoutingHelpers;
import app.organicmaps.location.LocationHelper;
import app.organicmaps.util.Distance;
import app.organicmaps.util.Graphics;
import app.organicmaps.util.concurrency.ThreadPool;
import app.organicmaps.util.concurrency.UiThread;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
public class BookmarksLoader
{
public interface OnBookmarksLoaded
{
void onBookmarksLoaded(@NonNull ItemList bookmarks);
}
// The maximum size should be equal to ConstraintManager.CONTENT_LIMIT_TYPE_LIST.
// However, having more than 50 items results in android.os.TransactionTooLargeException.
// This exception occurs because the data parcel size is too large to be transferred between services.
// The primary cause of this issue is the icons. Even though we have the maximum Icon.TYPE_ICONS.length icons,
// 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)
{
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 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);
}
ThreadPool.getWorker().submit(() -> {
final ItemList bookmarksList = createBookmarksList(carContext, bookmarks);
UiThread.run(() -> onBookmarksLoaded.onBookmarksLoaded(bookmarksList));
});
});
}
@NonNull
private static ItemList createBookmarksList(@NonNull CarContext carContext, @NonNull BookmarkInfo[] bookmarks)
{
final Location location = LocationHelper.from(carContext).getSavedLocation();
final ItemList.Builder builder = new ItemList.Builder();
final Map<Icon, CarIcon> iconsCache = new HashMap<>();
for (final BookmarkInfo bookmarkInfo : bookmarks)
{
final Row.Builder itemBuilder = new Row.Builder();
itemBuilder.setTitle(bookmarkInfo.getName());
if (!bookmarkInfo.getAddress().isEmpty())
itemBuilder.addText(bookmarkInfo.getAddress());
final CharSequence description = getDescription(bookmarkInfo, location);
if (description.length() != 0)
itemBuilder.addText(description);
final Icon icon = bookmarkInfo.getIcon();
if (!iconsCache.containsKey(icon))
{
final Drawable drawable = Graphics.drawCircleAndImage(icon.argb(),
R.dimen.track_circle_size,
icon.getResId(),
R.dimen.bookmark_icon_size,
carContext);
final CarIcon carIcon = new CarIcon.Builder(IconCompat.createWithBitmap(Graphics.drawableToBitmap(drawable))).build();
iconsCache.put(icon, carIcon);
}
itemBuilder.setImage(Objects.requireNonNull(iconsCache.get(icon)));
itemBuilder.setOnClickListener(() -> BookmarkManager.INSTANCE.showBookmarkOnMap(bookmarkInfo.getBookmarkId()));
builder.addItem(itemBuilder.build());
}
return builder.build();
}
@NonNull
private static CharSequence getDescription(@NonNull BookmarkInfo bookmark, @Nullable Location location)
{
final SpannableStringBuilder result = new SpannableStringBuilder(" ");
if (location != null)
{
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())
{
result.append("");
result.append(bookmark.getFeatureType());
}
}
return result;
}
}

View file

@ -0,0 +1,61 @@
package app.organicmaps.car.screens.bookmarks;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.car.app.CarContext;
import androidx.car.app.model.Action;
import androidx.car.app.model.Header;
import androidx.car.app.model.ItemList;
import androidx.car.app.model.Pane;
import androidx.car.app.model.Template;
import androidx.car.app.navigation.model.MapTemplate;
import app.organicmaps.bookmarks.data.BookmarkCategory;
import app.organicmaps.car.SurfaceRenderer;
import app.organicmaps.car.screens.base.BaseMapScreen;
import app.organicmaps.car.util.UiHelpers;
public class BookmarksScreen extends BaseMapScreen
{
@NonNull
private final BookmarkCategory mBookmarkCategory;
@Nullable
private ItemList mBookmarksList = null;
public BookmarksScreen(@NonNull CarContext carContext, @NonNull SurfaceRenderer surfaceRenderer, @NonNull BookmarkCategory bookmarkCategory)
{
super(carContext, surfaceRenderer);
mBookmarkCategory = bookmarkCategory;
}
@NonNull
@Override
public Template onGetTemplate()
{
final MapTemplate.Builder builder = new MapTemplate.Builder();
builder.setHeader(createHeader());
builder.setMapController(UiHelpers.createMapController(getCarContext(), getSurfaceRenderer()));
if (mBookmarksList == null)
{
builder.setPane(new Pane.Builder().setLoading(true).build());
BookmarksLoader.load(getCarContext(), mBookmarkCategory, (bookmarksList) -> {
mBookmarksList = bookmarksList;
invalidate();
});
}
else
builder.setItemList(mBookmarksList);
return builder.build();
}
@NonNull
private Header createHeader()
{
final Header.Builder builder = new Header.Builder();
builder.setStartHeaderAction(Action.BACK);
builder.setTitle(mBookmarkCategory.getName());
return builder.build();
}
}