Compare commits

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

3 commits

Author SHA1 Message Date
f0deaa11c9 [android] Add dark mode support to widget config activity
Signed-off-by: coderang-gk <coderang.gk@gmail.com>
2025-03-09 16:58:53 +05:30
28545429ac [android] Optimize bookmark search with native implementation
Replace inefficient Java bookmark search methods with a C++ implementation that leverages the core place page infrastructure. The previous approach had significant computational overhead due to redundant iterations over all bookmark categories.

Signed-off-by: coderang-gk <coderang.gk@gmail.com>
2025-03-07 15:59:08 +05:30
2504282e67 [android] Add home screen widget for favorite bookmarks
This adds a new home screen widget feature that allows users to placebookmarks directly on their device's home screen for quick access.

Used coordinates and name-based approach for storing and retrieving bookmarks

Signed-off-by: Gaurang Khatavkar <coderang.gk@gmail.com>
2025-03-06 10:34:54 +00:00
15 changed files with 787 additions and 3 deletions

View file

@ -83,6 +83,27 @@
android:manageSpaceActivity="${applicationId}.ManageSpaceActivity"
tools:targetApi="33">
<receiver
android:name=".bookmarks.FavoriteBookmarkWidget"
android:label="@string/widget_name"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/favorite_bookmark_widget_info" />
</receiver>
<activity
android:name=".bookmarks.FavoriteBookmarkWidgetConfigActivity"
android:exported="true"
android:label="@string/widget_name">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
</intent-filter>
</activity>
<!-- Allows for config and orientation change without killing/restarting main activity -->
<activity
android:name="app.organicmaps.SplashActivity"
@ -365,7 +386,13 @@
android:launchMode="singleTask"
android:configChanges="uiMode"
android:screenOrientation="fullUser"
android:windowSoftInputMode="stateAlwaysHidden|adjustPan"/>
android:exported="true"
android:windowSoftInputMode="stateAlwaysHidden|adjustPan">
<intent-filter>
<action android:name="app.organicmaps.action.SHOW_BOOKMARK" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity-alias
android:name="${applicationId}.ManageSpaceActivity"

View file

@ -971,6 +971,48 @@ Java_app_organicmaps_Framework_nativeRemovePlacePageActivationListener(JNIEnv *e
g_placePageActivationListener = nullptr;
}
JNIEXPORT jlong JNICALL
Java_app_organicmaps_bookmarks_data_BookmarkManager_nativeFindBookmarkAtPoint(JNIEnv * env, jobject thiz, jdouble lat, jdouble lon)
{
LOG(LINFO, ("Native: Searching for bookmark at", lat, lon));
auto * framework = g_framework->NativeFramework();
auto const mercatorPoint = mercator::FromLatLon(lat, lon);
place_page::BuildInfo buildInfo;
buildInfo.m_mercator = mercatorPoint;
framework->BuildAndSetPlacePageInfo(buildInfo);
if (framework->HasPlacePageInfo())
{
auto const & placePageInfo = framework->GetCurrentPlacePageInfo();
if (placePageInfo.IsBookmark())
{
auto bookmarkId = placePageInfo.GetBookmarkId();
return static_cast<jlong>(bookmarkId);
}
}
double const searchRadiusM = 2.0;
auto const & bmManager = framework->GetBookmarkManager();
auto const rect = mercator::RectByCenterXYAndSizeInMeters(mercatorPoint, searchRadiusM);
m2::AnyRectD searchRect(rect);
auto const * userMark = bmManager.FindNearestUserMark(searchRect);
if (userMark && userMark->GetMarkType() == UserMark::Type::BOOKMARK)
{
auto const bookmarkId = userMark->GetId();
return static_cast<jlong>(bookmarkId);
}
return -1;
}
JNIEXPORT jstring JNICALL
Java_app_organicmaps_Framework_nativeGetGe0Url(JNIEnv * env, jclass, jdouble lat, jdouble lon, jdouble zoomLevel, jstring name)
{

View file

@ -45,6 +45,7 @@ import app.organicmaps.api.Const;
import app.organicmaps.base.BaseMwmFragmentActivity;
import app.organicmaps.base.OnBackPressListener;
import app.organicmaps.bookmarks.BookmarkCategoriesActivity;
import app.organicmaps.bookmarks.data.BookmarkInfo;
import app.organicmaps.bookmarks.data.BookmarkManager;
import app.organicmaps.bookmarks.data.MapObject;
import app.organicmaps.display.DisplayChangedListener;
@ -114,6 +115,7 @@ import app.organicmaps.widget.placepage.PlacePageViewModel;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
@ -327,6 +329,29 @@ public class MwmActivity extends BaseMwmFragmentActivity
return;
}
if (intent != null && "app.organicmaps.action.SHOW_BOOKMARK".equals(intent.getAction()))
{
String bookmarkName = intent.getStringExtra("BOOKMARK_NAME");
double lat = intent.getDoubleExtra("BOOKMARK_LAT", Double.NaN);
double lon = intent.getDoubleExtra("BOOKMARK_LON", Double.NaN);
String categoryName = intent.getStringExtra("BOOKMARK_CATEGORY");
if (!Double.isNaN(lat) && !Double.isNaN(lon))
{
BookmarkInfo nearestBookmark = BookmarkManager.INSTANCE.findBookmarkByCoordinates(
lat, lon, bookmarkName, categoryName
);
if (nearestBookmark != null)
{
BookmarkManager.INSTANCE.showBookmarkOnMap(nearestBookmark.getBookmarkId());
return;
}
Framework.nativeZoomToPoint(lat, lon, 16, true);
}
}
final IntentProcessor[] mIntentProcessors = {
new Factory.UrlProcessor(),
new Factory.KmzKmlProcessor(),

View file

@ -0,0 +1,77 @@
package app.organicmaps.bookmarks;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import app.organicmaps.R;
import app.organicmaps.bookmarks.data.BookmarkCategory;
import app.organicmaps.adapter.OnItemClickListener;
import java.util.List;
public class BookmarkWidgetCategoriesAdapter extends RecyclerView.Adapter<BookmarkWidgetCategoriesAdapter.ViewHolder>
{
private final Context context;
private final List<BookmarkCategory> categories;
private OnItemClickListener<BookmarkCategory> clickListener;
public BookmarkWidgetCategoriesAdapter(Context context, List<BookmarkCategory> categories)
{
this.context = context;
this.categories = categories;
}
public void setOnClickListener(OnItemClickListener<BookmarkCategory> listener)
{
this.clickListener = listener;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType)
{
View view = LayoutInflater.from(context).inflate(R.layout.item_widget_list, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position)
{
BookmarkCategory category = categories.get(position);
holder.categoryName.setText(category.getName());
holder.bookmarkCount.setText(String.valueOf(category.getBookmarksCount()));
String bookmarkText = context.getResources().getQuantityString(R.plurals.bookmarks_places, category.getBookmarksCount(), category.getBookmarksCount());
holder.bookmarkCount.setText(bookmarkText);
holder.itemView.setOnClickListener(v -> {
if (clickListener != null)
{
clickListener.onItemClick(v, category);
}
});
}
@Override
public int getItemCount()
{
return categories.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder
{
TextView categoryName;
TextView bookmarkCount;
public ViewHolder(View itemView)
{
super(itemView);
categoryName = itemView.findViewById(R.id.name);
bookmarkCount = itemView.findViewById(R.id.size);
}
}
}

View file

@ -0,0 +1,148 @@
package app.organicmaps.bookmarks;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.widget.RemoteViews;
import app.organicmaps.MwmActivity;
import app.organicmaps.R;
import app.organicmaps.bookmarks.data.BookmarkCategory;
import app.organicmaps.bookmarks.data.BookmarkInfo;
import app.organicmaps.bookmarks.data.BookmarkManager;
public class FavoriteBookmarkWidget extends AppWidgetProvider
{
private static final String TAG = "FavoriteBookmarkWidget";
private static final String PREFS_NAME = "FavoriteBookmarkWidget";
private static final String PREF_PREFIX_KEY = "favorite_bookmark_";
private static final String SUFFIX_LAT = "_latitude";
private static final String SUFFIX_LON = "_longitude";
private static final String SUFFIX_NAME = "_name";
private static final String SUFFIX_CATEGORY_NAME = "_category_name";
private static final String SUFFIX_COLOR = "_color";
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)
{
for (int appWidgetId : appWidgetIds)
{
updateWidget(context, appWidgetManager, appWidgetId);
}
}
public static void updateWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId)
{
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, 0);
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_favorite_bookmark);
double lat = prefs.getFloat(PREF_PREFIX_KEY + appWidgetId + SUFFIX_LAT, 0);
double lon = prefs.getFloat(PREF_PREFIX_KEY + appWidgetId + SUFFIX_LON, 0);
String categoryName = prefs.getString(PREF_PREFIX_KEY + appWidgetId + "_category_name", "");
String bookmarkName = prefs.getString(PREF_PREFIX_KEY + appWidgetId + SUFFIX_NAME, "");
int iconColor = prefs.getInt(PREF_PREFIX_KEY + appWidgetId + SUFFIX_COLOR, 0);
boolean validCoordinates = (lat != 0 || lon != 0);
BookmarkInfo bookmarkInfo = null;
if (validCoordinates)
{
bookmarkInfo = BookmarkManager.INSTANCE.findBookmarkByCoordinates(lat, lon, bookmarkName, categoryName);
}
String displayName = bookmarkInfo != null ? bookmarkInfo.getName() :
(bookmarkName != null && !bookmarkName.isEmpty() ? bookmarkName :
context.getString(R.string.select_bookmark));
views.setTextViewText(R.id.tv__bookmark_name, displayName);
if (bookmarkInfo != null)
{
int iconResId = BookmarkManager.INSTANCE.getBookmarkIcon(bookmarkInfo.getBookmarkId());
if (iconResId == 0)
{
iconResId = R.drawable.ic_bookmarks;
}
views.setImageViewResource(R.id.iv__bookmark_color, iconResId);
Intent intent = new Intent(context, MwmActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_CLEAR_TOP |
Intent.FLAG_ACTIVITY_SINGLE_TOP);
intent.setAction("app.organicmaps.action.SHOW_BOOKMARK");
intent.putExtra("FROM_WIDGET", true);
intent.putExtra("BOOKMARK_ID", bookmarkInfo.getBookmarkId());
intent.putExtra("BOOKMARK_NAME", bookmarkInfo.getName());
intent.putExtra("BOOKMARK_LAT", bookmarkInfo.getLat());
intent.putExtra("BOOKMARK_LON", bookmarkInfo.getLon());
intent.putExtra("BOOKMARK_CATEGORY",
BookmarkManager.INSTANCE.getCategoryById(bookmarkInfo.getCategoryId()).getName());
PendingIntent pendingIntent = PendingIntent.getActivity(
context,
appWidgetId,
intent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
views.setOnClickPendingIntent(R.id.widget_container, pendingIntent);
}
else
{
views.setImageViewResource(R.id.iv__bookmark_color, R.drawable.ic_bookmarks);
Intent intent = new Intent(context, FavoriteBookmarkWidgetConfigActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(
context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
views.setOnClickPendingIntent(R.id.widget_container, pendingIntent);
}
appWidgetManager.updateAppWidget(appWidgetId, views);
}
public static void saveBookmarkPref(Context context, int appWidgetId,
BookmarkInfo bookmarkInfo, BookmarkCategory category)
{
SharedPreferences.Editor prefs = context.getSharedPreferences(PREFS_NAME, 0).edit();
prefs.putFloat(PREF_PREFIX_KEY + appWidgetId + SUFFIX_LAT, (float) bookmarkInfo.getLat());
prefs.putFloat(PREF_PREFIX_KEY + appWidgetId + SUFFIX_LON, (float) bookmarkInfo.getLon());
prefs.putString(PREF_PREFIX_KEY + appWidgetId + SUFFIX_NAME, bookmarkInfo.getName());
prefs.putString(PREF_PREFIX_KEY + appWidgetId + SUFFIX_CATEGORY_NAME, category.getName());
prefs.putLong(PREF_PREFIX_KEY + appWidgetId + "_bookmark_id", bookmarkInfo.getBookmarkId());
prefs.putLong(PREF_PREFIX_KEY + appWidgetId + "_category_id", category.getId());
prefs.putString(PREF_PREFIX_KEY + appWidgetId + "_name_hash",
String.valueOf(bookmarkInfo.getName().hashCode()));
prefs.putString(PREF_PREFIX_KEY + appWidgetId + "_lat_lon_hash",
String.valueOf((bookmarkInfo.getLat() + "," + bookmarkInfo.getLon()).hashCode()));
prefs.apply();
}
@Override
public void onDeleted(Context context, int[] appWidgetIds)
{
SharedPreferences.Editor prefs = context.getSharedPreferences(PREFS_NAME, 0).edit();
for (int appWidgetId : appWidgetIds)
{
prefs.remove(PREF_PREFIX_KEY + appWidgetId + SUFFIX_LAT);
prefs.remove(PREF_PREFIX_KEY + appWidgetId + SUFFIX_LON);
prefs.remove(PREF_PREFIX_KEY + appWidgetId + SUFFIX_NAME);
prefs.remove(PREF_PREFIX_KEY + appWidgetId + SUFFIX_CATEGORY_NAME);
prefs.remove(PREF_PREFIX_KEY + appWidgetId + SUFFIX_COLOR);
}
prefs.apply();
}
}

View file

@ -0,0 +1,199 @@
package app.organicmaps.bookmarks;
import android.appwidget.AppWidgetManager;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import app.organicmaps.R;
import app.organicmaps.adapter.OnItemClickListener;
import app.organicmaps.bookmarks.data.BookmarkCategory;
import app.organicmaps.bookmarks.data.BookmarkInfo;
import app.organicmaps.bookmarks.data.BookmarkManager;
import app.organicmaps.content.DataSource;
import app.organicmaps.MwmApplication;
import app.organicmaps.util.Config;
import app.organicmaps.util.ThemeUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class FavoriteBookmarkWidgetConfigActivity extends AppCompatActivity
{
private static final String TAG = "BookmarkWidgetConfig";
private int mAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;
private TextView mSelectTextView;
private RecyclerView mCategoriesRecyclerView;
private RecyclerView mBookmarksRecyclerView;
private View mBackButton;
private List<BookmarkCategory> mCategories = new ArrayList<>();
private BookmarkCategory mCurrentCategory;
private boolean mShowingCategories = true;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState)
{
applyTheme();
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_widget_config);
mSelectTextView = findViewById(R.id.select_text);
mCategoriesRecyclerView = findViewById(R.id.categories_recycler);
mBookmarksRecyclerView = findViewById(R.id.bookmarks_recycler);
mBackButton = findViewById(R.id.back_button);
mCategoriesRecyclerView.setLayoutManager(new LinearLayoutManager(this));
mBookmarksRecyclerView.setLayoutManager(new LinearLayoutManager(this));
mBackButton.setOnClickListener(v -> onBackPressed());
setResult(RESULT_CANCELED);
Intent intent = getIntent();
Bundle extras = intent.getExtras();
if (extras != null)
{
mAppWidgetId = extras.getInt(
AppWidgetManager.EXTRA_APPWIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID);
}
if (mAppWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID)
{
finish();
return;
}
try
{
MwmApplication app = MwmApplication.from(this);
if (!app.arePlatformAndCoreInitialized())
{
app.init(() -> {
runOnUiThread(this::loadCategories);
});
}
else
{
loadCategories();
}
} catch (IOException e)
{
Log.e(TAG, "Failed to initialize app", e);
finish();
}
}
private void applyTheme()
{
String currentTheme = Config.getCurrentUiTheme(this);
setTheme(ThemeUtils.getCardBgThemeResourceId(this, currentTheme));
}
private void loadCategories()
{
mShowingCategories = true;
mBookmarksRecyclerView.setVisibility(View.GONE);
mCategoriesRecyclerView.setVisibility(View.VISIBLE);
mSelectTextView.setText(R.string.select_list);
mCategories = BookmarkManager.INSTANCE.getCategories();
List<BookmarkCategory> nonEmptyCategories = new ArrayList<>();
for (BookmarkCategory category : mCategories)
{
if (category.getBookmarksCount() > 0)
{
nonEmptyCategories.add(category);
}
}
mCategories = nonEmptyCategories;
BookmarkWidgetCategoriesAdapter adapter = new BookmarkWidgetCategoriesAdapter(this, mCategories);
adapter.setOnClickListener(new OnItemClickListener<BookmarkCategory>()
{
@Override
public void onItemClick(View view, BookmarkCategory category)
{
mCurrentCategory = category;
showBookmarksForCategory(category);
}
});
mCategoriesRecyclerView.setAdapter(adapter);
}
private void showBookmarksForCategory(BookmarkCategory category)
{
mShowingCategories = false;
mCategoriesRecyclerView.setVisibility(View.GONE);
mBookmarksRecyclerView.setVisibility(View.VISIBLE);
mSelectTextView.setText(R.string.select_bookmark);
DataSource<BookmarkCategory> dataSource = new DataSource<BookmarkCategory>()
{
@Override
public BookmarkCategory getData()
{
return category;
}
@Override
public void invalidate()
{
}
};
BookmarkListAdapter adapter = new BookmarkListAdapter(dataSource);
adapter.setOnClickListener((view, position) -> {
Object item = adapter.getItem(position);
if (item instanceof BookmarkInfo)
{
BookmarkInfo bookmarkInfo = (BookmarkInfo) item;
FavoriteBookmarkWidget.saveBookmarkPref(
this,
mAppWidgetId,
bookmarkInfo,
mCurrentCategory);
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this);
FavoriteBookmarkWidget.updateWidget(this, appWidgetManager, mAppWidgetId);
Intent resultValue = new Intent();
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
setResult(RESULT_OK, resultValue);
finish();
}
});
mBookmarksRecyclerView.setAdapter(adapter);
}
@Override
public void onBackPressed()
{
if (!mShowingCategories)
{
loadCategories();
}
else
{
setResult(RESULT_CANCELED);
super.onBackPressed();
}
}
}

View file

@ -147,6 +147,27 @@ public enum BookmarkManager
mOnElevationActivePointChangedListener = listener;
}
@Nullable
public BookmarkInfo findBookmarkByCoordinates(double lat, double lon, @Nullable String name, @Nullable String categoryName)
{
long bookmarkId = nativeFindBookmarkAtPoint(lat, lon);
if (bookmarkId == -1)
return null;
BookmarkInfo info = getBookmarkInfo(bookmarkId);
if (info != null &&
(name == null || name.isEmpty() || name.equals(info.getName())) &&
(categoryName == null || categoryName.isEmpty() ||
categoryName.equals(getCategoryById(info.getCategoryId()).getName())))
{
return info;
}
return null;
}
// Called from JNI.
@Keep
@SuppressWarnings("unused")
@ -714,7 +735,7 @@ public enum BookmarkManager
!description.equals(getBookmarkDescription(bookmark.getBookmarkId())))
{
setBookmarkParams(bookmark.getBookmarkId(), name,
icon != null ? icon.getColor() : getLastEditedColor(), description);
icon != null ? icon.getColor() : getLastEditedColor(), description);
}
}
@ -730,7 +751,7 @@ public enum BookmarkManager
public double getElevationCurPositionDistance(long trackId)
{
return nativeGetElevationCurPositionDistance(trackId);
return nativeGetElevationCurPositionDistance(trackId);
}
public void setElevationActivePoint(long trackId, double distance)
@ -743,6 +764,8 @@ public enum BookmarkManager
return nativeGetElevationActivePointDistance(trackId);
}
private native long nativeFindBookmarkAtPoint(double lat, double lon);
@Nullable
private native Bookmark nativeUpdateBookmarkPlacePage(long bmkId);

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="8dp" />
<solid android:color="#2E3443" />
<stroke
android:width="1dp"
android:color="#3F4756" />
</shape>

View file

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="110dp"
android:height="40dp"
android:viewportWidth="110"
android:viewportHeight="40">
<path
android:pathData="M10,0L100,0A10,10 0,0 1,110 10L110,30A10,10 0,0 1,100 40L10,40A10,10 0,0 1,0 30L0,10A10,10 0,0 1,10 0z"
android:fillColor="@color/bg_primary"/>
<path
android:pathData="M10,0.5L100,0.5A9.5,9.5 0,0 1,109.5 10L109.5,30A9.5,9.5 0,0 1,100 39.5L10,39.5A9.5,9.5 0,0 1,0.5 30L0.5,10A9.5,9.5 0,0 1,10 0.5z"
android:strokeWidth="1"
android:strokeColor="#3F4756"
android:fillColor="#00000000"/>
<group
android:scaleX="0.2"
android:scaleY="0.2"
android:translateX="10"
android:translateY="6">
<path
android:pathData="M128.27,22.42C131.43,31.02 114.54,51.94 98.44,57.31 69.31,46.97 68.36,64.34 44.52,80.84 72.7,100.81 102.23,85.26 101.67,65.6c-22.18,13.03 -36.07,14.45 -44.05,14.05 26.84,-5.45 55.81,-22.34 61.88,-29.45 0.01,0.32 0.01,0.63 0.01,0.95 0,33.95 -51.15,84.31 -51.15,84.31 0,0 -26.78,-26.28 -41.58,-53.73 -2.07,-0.03 -14.53,4.61 -19.12,-1.61C2.53,73.18 21.55,49.89 38.05,42.94 67.57,56.68 83.75,21.08 92.99,19.1 65.44,2.37 38.21,9.24 35.13,34.1 50.44,25.18 69.23,20.13 77.91,20.53 53.52,25.63 25.58,44.13 17.21,51.16c0,-28.25 22.9,-51.16 51.15,-51.16 16.71,0 31.54,8.01 40.88,20.4 0,-0 16.67,-5.56 19.04,2.02zM122.43,24.95c-2.19,-3.06 -9.83,0.51 -9.83,0.51 1.07,1.84 2.03,3.75 2.87,5.72 0.85,2 1.57,4.07 2.17,6.19 0,0 7.8,-8.24 4.8,-12.42zM13.33,77.24c2.37,3.31 10.63,-0.56 10.63,-0.56 -1.16,-1.99 -2.19,-4.05 -3.1,-6.18 -0.92,-2.16 -1.7,-4.4 -2.34,-6.69 0,0 -8.43,8.91 -5.19,13.43z"
android:strokeWidth="0.43"
android:fillColor="@color/white_primary"/>
</group>
<path
android:pathData="M45,16L45,16A1,1 0,0 1,46 15L73,15A1,1 0,0 1,74 16L74,16A1,1 0,0 1,73 17L46,17A1,1 0,0 1,45 16z"
android:fillColor="#CCCCCC"
android:fillAlpha="0.7"/>
<path
android:pathData="M45,24L45,24A1,1 0,0 1,46 23L93,23A1,1 0,0 1,94 24L94,24A1,1 0,0 1,93 25L46,25A1,1 0,0 1,45 24z"
android:fillColor="#FFFFFF"/>
</vector>

View file

@ -0,0 +1,95 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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="match_parent"
android:background="?attr/colorBackgroundFloating"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/ThemeOverlay.AppCompat.DayNight" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="48dp"
android:background="?attr/colorPrimary"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="8dp">
<ImageButton
android:id="@+id/back_button"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/back"
android:scaleType="centerInside"
android:src="?attr/homeAsUpIndicator"
app:tint="@android:color/white" />
<TextView
android:id="@+id/widget_name"
style="@style/MwmTextAppearance.Toolbar.Title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:text="@string/widget_name"
android:textColor="@android:color/white" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:attr/windowBackground"
android:minWidth="@dimen/bookmark_purchase_img_width"
android:orientation="vertical">
<TextView
android:id="@+id/select_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_base_plus"
android:layout_marginTop="@dimen/margin_base_plus_quarter"
android:layout_marginEnd="@dimen/margin_base_plus"
android:fontFamily="@string/robotoMedium"
android:lineSpacingExtra="@dimen/text_size_banner"
android:text="@string/select_list"
android:textAppearance="?android:textAppearanceLarge"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/text_size_toolbar"
android:textStyle="normal"
tools:text="Select list" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/categories_recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_base"
android:clipToPadding="false"
android:paddingBottom="@dimen/margin_base"
android:scrollbars="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/bookmarks_recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="@dimen/margin_base"
android:clipToPadding="false"
android:paddingBottom="@dimen/margin_base"
android:scrollbars="vertical"
android:visibility="gone"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
</FrameLayout>
</LinearLayout>
</LinearLayout>

View file

@ -57,3 +57,4 @@
app:srcCompat="@drawable/ic_more"
app:tint="?secondary" />
</RelativeLayout>

View file

@ -0,0 +1,52 @@
<?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="wrap_content"
android:background="?android:attr/windowBackground">
<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_base"
android:ellipsize="middle"
android:singleLine="true"
android:textColor="?android:attr/textColorPrimary"
android:layout_marginStart="16dp"
android:textAppearance="?fontBody1"
tools:text="Bookmark name looooooooooooooooooongasdasdasd" />
<LinearLayout
android:id="@+id/bottom_line_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/name"
android:layout_marginBottom="@dimen/margin_half_plus"
android:orientation="horizontal">
<TextView
android:id="@+id/size"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_gravity="center_vertical"
android:ellipsize="end"
android:singleLine="true"
android:textColor="?android:attr/textColorPrimary"
android:textAppearance="@style/MwmTextAppearance.Body3"
tools:text="42000000" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="@dimen/divider_height"
android:layout_below="@id/bottom_line_container"
android:layout_marginTop="8dp"
android:layout_marginStart="16dp"
android:background="?dividerHorizontal" />
</RelativeLayout>

View file

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/widget_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/bg_primary"
android:padding="8dp">
<ImageView
android:id="@+id/app_icon"
android:layout_width="46dp"
android:layout_height="48dp"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:layout_marginStart="4dp"
android:contentDescription="@string/app_name"
android:src="@drawable/splash" />
<TextView
android:id="@+id/tv__bookmark_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true"
android:layout_toEndOf="@id/app_icon"
android:ellipsize="end"
android:maxLines="1"
android:textColor="#FFFFFF"
android:textSize="16sp"
android:textStyle="bold"
android:gravity="center" />
</RelativeLayout>

View file

@ -933,4 +933,7 @@
<string name="uri_open_location_failed">No app installed that can open the location</string>
<!-- preference string for using auto theme only in navigation mode -->
<string name="nav_auto">Auto in navigation</string>
<string name="widget_name">Favorite Place</string>
<string name="select_bookmark">Select bookmark</string>
<string name="widget_description">Quick access to your favorite places</string>
</resources>

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/widget_favorite_bookmark"
android:minWidth="110dp"
android:minHeight="40dp"
android:previewImage="@drawable/widget_preview"
android:resizeMode="horizontal"
android:updatePeriodMillis="86400000"
android:widgetCategory="home_screen"
android:description="@string/widget_description"
android:configure="app.organicmaps.bookmarks.FavoriteBookmarkWidgetConfigActivity" />