From 2504282e6716ca0153772d6e01c3debb646beaae Mon Sep 17 00:00:00 2001 From: Gaurang Khatavkar Date: Thu, 6 Mar 2025 10:34:54 +0000 Subject: [PATCH 1/3] [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 --- android/app/src/main/AndroidManifest.xml | 29 ++- .../java/app/organicmaps/MwmActivity.java | 34 ++++ .../BookmarkWidgetCategoriesAdapter.java | 77 +++++++ .../bookmarks/FavoriteBookmarkWidget.java | 151 ++++++++++++++ .../FavoriteBookmarkWidgetConfigActivity.java | 190 ++++++++++++++++++ .../bookmarks/data/BookmarkManager.java | 188 ++++++++++++++++- .../main/res/drawable/widget_background.xml | 9 + .../src/main/res/drawable/widget_preview.xml | 37 ++++ .../res/layout/activity_widget_config.xml | 94 +++++++++ .../res/layout/item_bookmark_category.xml | 1 + .../src/main/res/layout/item_widget_list.xml | 50 +++++ .../res/layout/widget_favorite_bookmark.xml | 33 +++ android/app/src/main/res/values/strings.xml | 3 + .../res/xml/favorite_bookmark_widget_info.xml | 12 ++ 14 files changed, 905 insertions(+), 3 deletions(-) create mode 100644 android/app/src/main/java/app/organicmaps/bookmarks/BookmarkWidgetCategoriesAdapter.java create mode 100644 android/app/src/main/java/app/organicmaps/bookmarks/FavoriteBookmarkWidget.java create mode 100644 android/app/src/main/java/app/organicmaps/bookmarks/FavoriteBookmarkWidgetConfigActivity.java create mode 100644 android/app/src/main/res/drawable/widget_background.xml create mode 100644 android/app/src/main/res/drawable/widget_preview.xml create mode 100644 android/app/src/main/res/layout/activity_widget_config.xml create mode 100644 android/app/src/main/res/layout/item_widget_list.xml create mode 100644 android/app/src/main/res/layout/widget_favorite_bookmark.xml create mode 100644 android/app/src/main/res/xml/favorite_bookmark_widget_info.xml diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 86bdadc494..c1951169dd 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -83,6 +83,27 @@ android:manageSpaceActivity="${applicationId}.ManageSpaceActivity" tools:targetApi="33"> + + + + + + + + + + + + + + android:exported="true" + android:windowSoftInputMode="stateAlwaysHidden|adjustPan"> + + + + + +{ + private final Context context; + private final List categories; + private OnItemClickListener clickListener; + + public BookmarkWidgetCategoriesAdapter(Context context, List categories) + { + this.context = context; + this.categories = categories; + } + + public void setOnClickListener(OnItemClickListener 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); + } + } +} diff --git a/android/app/src/main/java/app/organicmaps/bookmarks/FavoriteBookmarkWidget.java b/android/app/src/main/java/app/organicmaps/bookmarks/FavoriteBookmarkWidget.java new file mode 100644 index 0000000000..d68b3d19e9 --- /dev/null +++ b/android/app/src/main/java/app/organicmaps/bookmarks/FavoriteBookmarkWidget.java @@ -0,0 +1,151 @@ +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; +import app.organicmaps.bookmarks.data.Icon; + +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); + } + + // Always set the bookmark name, using stored name as fallback + 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 color = iconColor != 0 ? iconColor : bookmarkInfo.getIcon().getColor(); + + 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 + "_category_name", category.getName()); + + 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())); + + boolean success = prefs.commit(); + } + + @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(); + } +} diff --git a/android/app/src/main/java/app/organicmaps/bookmarks/FavoriteBookmarkWidgetConfigActivity.java b/android/app/src/main/java/app/organicmaps/bookmarks/FavoriteBookmarkWidgetConfigActivity.java new file mode 100644 index 0000000000..d4408c01d8 --- /dev/null +++ b/android/app/src/main/java/app/organicmaps/bookmarks/FavoriteBookmarkWidgetConfigActivity.java @@ -0,0 +1,190 @@ +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 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 mCategories = new ArrayList<>(); + private BookmarkCategory mCurrentCategory; + private boolean mShowingCategories = true; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) + { + 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 loadCategories() + { + mShowingCategories = true; + mBookmarksRecyclerView.setVisibility(View.GONE); + mCategoriesRecyclerView.setVisibility(View.VISIBLE); + mSelectTextView.setText(R.string.select_list); + + mCategories = BookmarkManager.INSTANCE.getCategories(); + List 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() + { + @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 dataSource = new DataSource() + { + @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(); + } + } +} \ No newline at end of file diff --git a/android/app/src/main/java/app/organicmaps/bookmarks/data/BookmarkManager.java b/android/app/src/main/java/app/organicmaps/bookmarks/data/BookmarkManager.java index a7015715da..9feafafd8c 100644 --- a/android/app/src/main/java/app/organicmaps/bookmarks/data/BookmarkManager.java +++ b/android/app/src/main/java/app/organicmaps/bookmarks/data/BookmarkManager.java @@ -147,6 +147,190 @@ public enum BookmarkManager mOnElevationActivePointChangedListener = listener; } + @Nullable + public BookmarkInfo findBookmarkByCoordinates(double latitude, double longitude, @Nullable String name, @Nullable String categoryName) + { + final double MAX_DISTANCE_TOLERANCE = 50.0; + + List categories = getCategories(); + + BookmarkInfo closestMatch = null; + double closestDistance = Double.MAX_VALUE; + + for (BookmarkCategory category : categories) + { + if (categoryName != null && !categoryName.isEmpty() && + !category.getName().equals(categoryName)) + { + continue; + } + + for (int i = 0; i < category.getBookmarksCount(); i++) + { + long bookmarkId = getBookmarkIdByPosition(category.getId(), i); + BookmarkInfo bookmarkInfo = getBookmarkInfo(bookmarkId); + + if (bookmarkInfo == null) + continue; + + boolean nameMatches = name == null || name.isEmpty() || + bookmarkInfo.getName().equals(name); + + double distance = calculateDistance(latitude, longitude, + bookmarkInfo.getLat(), + bookmarkInfo.getLon()); + + if (nameMatches && distance <= MAX_DISTANCE_TOLERANCE) + { + if (distance < closestDistance) + { + closestMatch = bookmarkInfo; + closestDistance = distance; + } + } + } + } + + return closestMatch; + } + + @Nullable + private BookmarkInfo findExactBookmarkByCoordinates(double lat, double lon, @Nullable String name, @Nullable String categoryName) + { + final double COORD_TOLERANCE = 0.0001; + + List categories = getCategories(); + + if (categoryName != null && !categoryName.isEmpty()) + { + for (BookmarkCategory category : categories) + { + if (categoryName.equals(category.getName())) + { + for (int i = 0; i < category.getBookmarksCount(); i++) + { + long bookmarkId = getBookmarkIdByPosition(category.getId(), i); + BookmarkInfo bookmark = getBookmarkInfo(bookmarkId); + + if (bookmark != null && + (name == null || name.isEmpty() || name.equals(bookmark.getName())) && + Math.abs(bookmark.getLat() - lat) < COORD_TOLERANCE && + Math.abs(bookmark.getLon() - lon) < COORD_TOLERANCE) + { + return bookmark; + } + } + } + } + } + + for (BookmarkCategory category : categories) + { + for (int i = 0; i < category.getBookmarksCount(); i++) + { + long bookmarkId = getBookmarkIdByPosition(category.getId(), i); + BookmarkInfo bookmark = getBookmarkInfo(bookmarkId); + + if (bookmark != null && + (name == null || name.isEmpty() || name.equals(bookmark.getName())) && + Math.abs(bookmark.getLat() - lat) < COORD_TOLERANCE && + Math.abs(bookmark.getLon() - lon) < COORD_TOLERANCE) + { + return bookmark; + } + } + } + + return null; + } + + @Nullable + private BookmarkInfo findNearestBookmark(double lat, double lon, double maxDistanceMeters, + @Nullable String name, @Nullable String categoryName) + { + List categories = getCategories(); + BookmarkInfo closestBookmark = null; + double closestDistance = Double.MAX_VALUE; + + if (categoryName != null && !categoryName.isEmpty()) + { + for (BookmarkCategory category : categories) + { + if (categoryName.equals(category.getName())) + { + BookmarkInfo categoryResult = findNearestInCategory(category, lat, lon, + maxDistanceMeters, name, closestDistance); + if (categoryResult != null) + { + return categoryResult; + } + break; + } + } + } + + for (BookmarkCategory category : categories) + { + if (categoryName != null && !categoryName.isEmpty() && categoryName.equals(category.getName())) + { + continue; + } + + BookmarkInfo categoryResult = findNearestInCategory(category, lat, lon, + maxDistanceMeters, name, closestDistance); + if (categoryResult != null) + { + closestBookmark = categoryResult; + closestDistance = calculateDistance(lat, lon, closestBookmark.getLat(), closestBookmark.getLon()); + } + } + + return closestBookmark; + } + + @Nullable + private BookmarkInfo findNearestInCategory(BookmarkCategory category, double lat, double lon, + double maxDistanceMeters, @Nullable String name, double currentClosestDistance) + { + BookmarkInfo closestBookmark = null; + double closestDistance = currentClosestDistance; + + for (int i = 0; i < category.getBookmarksCount(); i++) + { + long bookmarkId = getBookmarkIdByPosition(category.getId(), i); + BookmarkInfo bookmark = getBookmarkInfo(bookmarkId); + + if (bookmark != null && (name == null || name.isEmpty() || name.equals(bookmark.getName()))) + { + double distance = calculateDistance(lat, lon, bookmark.getLat(), bookmark.getLon()); + + if (distance < closestDistance && distance <= maxDistanceMeters) + { + closestBookmark = bookmark; + closestDistance = distance; + } + } + } + + return closestBookmark; + } + + private double calculateDistance(double lat1, double lon1, double lat2, double lon2) + { + final double earthRadius = 6371000; + + double dLat = Math.toRadians(lat2 - lat1); + double dLon = Math.toRadians(lon2 - lon1); + + double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) * + Math.sin(dLon / 2) * Math.sin(dLon / 2); + + double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + + return earthRadius * c; + } + // Called from JNI. @Keep @SuppressWarnings("unused") @@ -714,7 +898,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 +914,7 @@ public enum BookmarkManager public double getElevationCurPositionDistance(long trackId) { - return nativeGetElevationCurPositionDistance(trackId); + return nativeGetElevationCurPositionDistance(trackId); } public void setElevationActivePoint(long trackId, double distance) diff --git a/android/app/src/main/res/drawable/widget_background.xml b/android/app/src/main/res/drawable/widget_background.xml new file mode 100644 index 0000000000..e5b941258b --- /dev/null +++ b/android/app/src/main/res/drawable/widget_background.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/android/app/src/main/res/drawable/widget_preview.xml b/android/app/src/main/res/drawable/widget_preview.xml new file mode 100644 index 0000000000..4b956bd2fe --- /dev/null +++ b/android/app/src/main/res/drawable/widget_preview.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + diff --git a/android/app/src/main/res/layout/activity_widget_config.xml b/android/app/src/main/res/layout/activity_widget_config.xml new file mode 100644 index 0000000000..2a915c802a --- /dev/null +++ b/android/app/src/main/res/layout/activity_widget_config.xml @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/res/layout/item_bookmark_category.xml b/android/app/src/main/res/layout/item_bookmark_category.xml index ee8d743e65..d2e9da64fe 100644 --- a/android/app/src/main/res/layout/item_bookmark_category.xml +++ b/android/app/src/main/res/layout/item_bookmark_category.xml @@ -57,3 +57,4 @@ app:srcCompat="@drawable/ic_more" app:tint="?secondary" /> + diff --git a/android/app/src/main/res/layout/item_widget_list.xml b/android/app/src/main/res/layout/item_widget_list.xml new file mode 100644 index 0000000000..cf9e5363c9 --- /dev/null +++ b/android/app/src/main/res/layout/item_widget_list.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + diff --git a/android/app/src/main/res/layout/widget_favorite_bookmark.xml b/android/app/src/main/res/layout/widget_favorite_bookmark.xml new file mode 100644 index 0000000000..a417af4afe --- /dev/null +++ b/android/app/src/main/res/layout/widget_favorite_bookmark.xml @@ -0,0 +1,33 @@ + + + + + + + + diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 36eb211979..9047530175 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -933,4 +933,7 @@ No app installed that can open the location Auto in navigation + Favorite Place + Select bookmark + Quick access to your favorite places diff --git a/android/app/src/main/res/xml/favorite_bookmark_widget_info.xml b/android/app/src/main/res/xml/favorite_bookmark_widget_info.xml new file mode 100644 index 0000000000..3f5fbf70ed --- /dev/null +++ b/android/app/src/main/res/xml/favorite_bookmark_widget_info.xml @@ -0,0 +1,12 @@ + + + \ No newline at end of file -- 2.45.3 From 28545429ac86c442ecebf42109b20803cb4bbf6e Mon Sep 17 00:00:00 2001 From: coderang-gk Date: Fri, 7 Mar 2025 15:59:08 +0530 Subject: [PATCH 2/3] [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 --- .../main/cpp/app/organicmaps/Framework.cpp | 42 ++++ .../java/app/organicmaps/MwmActivity.java | 23 +-- .../bookmarks/FavoriteBookmarkWidget.java | 11 +- .../bookmarks/data/BookmarkManager.java | 185 ++---------------- .../res/xml/favorite_bookmark_widget_info.xml | 1 + 5 files changed, 66 insertions(+), 196 deletions(-) diff --git a/android/app/src/main/cpp/app/organicmaps/Framework.cpp b/android/app/src/main/cpp/app/organicmaps/Framework.cpp index 7b47eef574..a943f83ac8 100644 --- a/android/app/src/main/cpp/app/organicmaps/Framework.cpp +++ b/android/app/src/main/cpp/app/organicmaps/Framework.cpp @@ -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(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(bookmarkId); +} + +return -1; +} + JNIEXPORT jstring JNICALL Java_app_organicmaps_Framework_nativeGetGe0Url(JNIEnv * env, jclass, jdouble lat, jdouble lon, jdouble zoomLevel, jstring name) { diff --git a/android/app/src/main/java/app/organicmaps/MwmActivity.java b/android/app/src/main/java/app/organicmaps/MwmActivity.java index 128e367295..376f41553e 100644 --- a/android/app/src/main/java/app/organicmaps/MwmActivity.java +++ b/android/app/src/main/java/app/organicmaps/MwmActivity.java @@ -331,7 +331,6 @@ public class MwmActivity extends BaseMwmFragmentActivity if (intent != null && "app.organicmaps.action.SHOW_BOOKMARK".equals(intent.getAction())) { - boolean fromWidget = intent.getBooleanExtra("FROM_WIDGET", false); String bookmarkName = intent.getStringExtra("BOOKMARK_NAME"); double lat = intent.getDoubleExtra("BOOKMARK_LAT", Double.NaN); @@ -340,23 +339,15 @@ public class MwmActivity extends BaseMwmFragmentActivity if (!Double.isNaN(lat) && !Double.isNaN(lon)) { - try + BookmarkInfo nearestBookmark = BookmarkManager.INSTANCE.findBookmarkByCoordinates( + lat, lon, bookmarkName, categoryName + ); + + if (nearestBookmark != null) { - BookmarkInfo nearestBookmark = BookmarkManager.INSTANCE.findBookmarkByCoordinates( - lat, lon, bookmarkName, categoryName - ); - - if (nearestBookmark != null) - { - - BookmarkManager.INSTANCE.showBookmarkOnMap(nearestBookmark.getBookmarkId()); - return; - } - } catch (Exception e) - { - + BookmarkManager.INSTANCE.showBookmarkOnMap(nearestBookmark.getBookmarkId()); + return; } - Framework.nativeZoomToPoint(lat, lon, 16, true); } } diff --git a/android/app/src/main/java/app/organicmaps/bookmarks/FavoriteBookmarkWidget.java b/android/app/src/main/java/app/organicmaps/bookmarks/FavoriteBookmarkWidget.java index d68b3d19e9..5093d3ae29 100644 --- a/android/app/src/main/java/app/organicmaps/bookmarks/FavoriteBookmarkWidget.java +++ b/android/app/src/main/java/app/organicmaps/bookmarks/FavoriteBookmarkWidget.java @@ -13,7 +13,6 @@ 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; public class FavoriteBookmarkWidget extends AppWidgetProvider { @@ -55,7 +54,6 @@ public class FavoriteBookmarkWidget extends AppWidgetProvider bookmarkInfo = BookmarkManager.INSTANCE.findBookmarkByCoordinates(lat, lon, bookmarkName, categoryName); } - // Always set the bookmark name, using stored name as fallback String displayName = bookmarkInfo != null ? bookmarkInfo.getName() : (bookmarkName != null && !bookmarkName.isEmpty() ? bookmarkName : context.getString(R.string.select_bookmark)); @@ -64,7 +62,6 @@ public class FavoriteBookmarkWidget extends AppWidgetProvider if (bookmarkInfo != null) { - int color = iconColor != 0 ? iconColor : bookmarkInfo.getIcon().getColor(); int iconResId = BookmarkManager.INSTANCE.getBookmarkIcon(bookmarkInfo.getBookmarkId()); if (iconResId == 0) @@ -119,19 +116,19 @@ public class FavoriteBookmarkWidget extends AppWidgetProvider { 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 + "_category_name", category.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())); - boolean success = prefs.commit(); + prefs.apply(); } @Override diff --git a/android/app/src/main/java/app/organicmaps/bookmarks/data/BookmarkManager.java b/android/app/src/main/java/app/organicmaps/bookmarks/data/BookmarkManager.java index 9feafafd8c..0eaece47f2 100644 --- a/android/app/src/main/java/app/organicmaps/bookmarks/data/BookmarkManager.java +++ b/android/app/src/main/java/app/organicmaps/bookmarks/data/BookmarkManager.java @@ -148,189 +148,26 @@ public enum BookmarkManager } @Nullable - public BookmarkInfo findBookmarkByCoordinates(double latitude, double longitude, @Nullable String name, @Nullable String categoryName) + public BookmarkInfo findBookmarkByCoordinates(double lat, double lon, @Nullable String name, @Nullable String categoryName) { - final double MAX_DISTANCE_TOLERANCE = 50.0; + long bookmarkId = nativeFindBookmarkAtPoint(lat, lon); - List categories = getCategories(); + if (bookmarkId == -1) + return null; - BookmarkInfo closestMatch = null; - double closestDistance = Double.MAX_VALUE; + BookmarkInfo info = getBookmarkInfo(bookmarkId); - for (BookmarkCategory category : categories) + if (info != null && + (name == null || name.isEmpty() || name.equals(info.getName())) && + (categoryName == null || categoryName.isEmpty() || + categoryName.equals(getCategoryById(info.getCategoryId()).getName()))) { - if (categoryName != null && !categoryName.isEmpty() && - !category.getName().equals(categoryName)) - { - continue; - } - - for (int i = 0; i < category.getBookmarksCount(); i++) - { - long bookmarkId = getBookmarkIdByPosition(category.getId(), i); - BookmarkInfo bookmarkInfo = getBookmarkInfo(bookmarkId); - - if (bookmarkInfo == null) - continue; - - boolean nameMatches = name == null || name.isEmpty() || - bookmarkInfo.getName().equals(name); - - double distance = calculateDistance(latitude, longitude, - bookmarkInfo.getLat(), - bookmarkInfo.getLon()); - - if (nameMatches && distance <= MAX_DISTANCE_TOLERANCE) - { - if (distance < closestDistance) - { - closestMatch = bookmarkInfo; - closestDistance = distance; - } - } - } - } - - return closestMatch; - } - - @Nullable - private BookmarkInfo findExactBookmarkByCoordinates(double lat, double lon, @Nullable String name, @Nullable String categoryName) - { - final double COORD_TOLERANCE = 0.0001; - - List categories = getCategories(); - - if (categoryName != null && !categoryName.isEmpty()) - { - for (BookmarkCategory category : categories) - { - if (categoryName.equals(category.getName())) - { - for (int i = 0; i < category.getBookmarksCount(); i++) - { - long bookmarkId = getBookmarkIdByPosition(category.getId(), i); - BookmarkInfo bookmark = getBookmarkInfo(bookmarkId); - - if (bookmark != null && - (name == null || name.isEmpty() || name.equals(bookmark.getName())) && - Math.abs(bookmark.getLat() - lat) < COORD_TOLERANCE && - Math.abs(bookmark.getLon() - lon) < COORD_TOLERANCE) - { - return bookmark; - } - } - } - } - } - - for (BookmarkCategory category : categories) - { - for (int i = 0; i < category.getBookmarksCount(); i++) - { - long bookmarkId = getBookmarkIdByPosition(category.getId(), i); - BookmarkInfo bookmark = getBookmarkInfo(bookmarkId); - - if (bookmark != null && - (name == null || name.isEmpty() || name.equals(bookmark.getName())) && - Math.abs(bookmark.getLat() - lat) < COORD_TOLERANCE && - Math.abs(bookmark.getLon() - lon) < COORD_TOLERANCE) - { - return bookmark; - } - } + return info; } return null; } - @Nullable - private BookmarkInfo findNearestBookmark(double lat, double lon, double maxDistanceMeters, - @Nullable String name, @Nullable String categoryName) - { - List categories = getCategories(); - BookmarkInfo closestBookmark = null; - double closestDistance = Double.MAX_VALUE; - - if (categoryName != null && !categoryName.isEmpty()) - { - for (BookmarkCategory category : categories) - { - if (categoryName.equals(category.getName())) - { - BookmarkInfo categoryResult = findNearestInCategory(category, lat, lon, - maxDistanceMeters, name, closestDistance); - if (categoryResult != null) - { - return categoryResult; - } - break; - } - } - } - - for (BookmarkCategory category : categories) - { - if (categoryName != null && !categoryName.isEmpty() && categoryName.equals(category.getName())) - { - continue; - } - - BookmarkInfo categoryResult = findNearestInCategory(category, lat, lon, - maxDistanceMeters, name, closestDistance); - if (categoryResult != null) - { - closestBookmark = categoryResult; - closestDistance = calculateDistance(lat, lon, closestBookmark.getLat(), closestBookmark.getLon()); - } - } - - return closestBookmark; - } - - @Nullable - private BookmarkInfo findNearestInCategory(BookmarkCategory category, double lat, double lon, - double maxDistanceMeters, @Nullable String name, double currentClosestDistance) - { - BookmarkInfo closestBookmark = null; - double closestDistance = currentClosestDistance; - - for (int i = 0; i < category.getBookmarksCount(); i++) - { - long bookmarkId = getBookmarkIdByPosition(category.getId(), i); - BookmarkInfo bookmark = getBookmarkInfo(bookmarkId); - - if (bookmark != null && (name == null || name.isEmpty() || name.equals(bookmark.getName()))) - { - double distance = calculateDistance(lat, lon, bookmark.getLat(), bookmark.getLon()); - - if (distance < closestDistance && distance <= maxDistanceMeters) - { - closestBookmark = bookmark; - closestDistance = distance; - } - } - } - - return closestBookmark; - } - - private double calculateDistance(double lat1, double lon1, double lat2, double lon2) - { - final double earthRadius = 6371000; - - double dLat = Math.toRadians(lat2 - lat1); - double dLon = Math.toRadians(lon2 - lon1); - - double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + - Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) * - Math.sin(dLon / 2) * Math.sin(dLon / 2); - - double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); - - return earthRadius * c; - } - // Called from JNI. @Keep @SuppressWarnings("unused") @@ -927,6 +764,8 @@ public enum BookmarkManager return nativeGetElevationActivePointDistance(trackId); } + private native long nativeFindBookmarkAtPoint(double lat, double lon); + @Nullable private native Bookmark nativeUpdateBookmarkPlacePage(long bmkId); diff --git a/android/app/src/main/res/xml/favorite_bookmark_widget_info.xml b/android/app/src/main/res/xml/favorite_bookmark_widget_info.xml index 3f5fbf70ed..e3b645f908 100644 --- a/android/app/src/main/res/xml/favorite_bookmark_widget_info.xml +++ b/android/app/src/main/res/xml/favorite_bookmark_widget_info.xml @@ -9,4 +9,5 @@ android:widgetCategory="home_screen" android:description="@string/widget_description" android:configure="app.organicmaps.bookmarks.FavoriteBookmarkWidgetConfigActivity" /> + \ No newline at end of file -- 2.45.3 From f0deaa11c923c97e9fc8cbc79190739468aa9563 Mon Sep 17 00:00:00 2001 From: coderang-gk Date: Sun, 9 Mar 2025 16:58:53 +0530 Subject: [PATCH 3/3] [android] Add dark mode support to widget config activity Signed-off-by: coderang-gk --- .../FavoriteBookmarkWidgetConfigActivity.java | 13 +++++++++++-- .../src/main/res/layout/activity_widget_config.xml | 5 +++-- .../app/src/main/res/layout/item_widget_list.xml | 4 +++- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/android/app/src/main/java/app/organicmaps/bookmarks/FavoriteBookmarkWidgetConfigActivity.java b/android/app/src/main/java/app/organicmaps/bookmarks/FavoriteBookmarkWidgetConfigActivity.java index d4408c01d8..6914cc30ff 100644 --- a/android/app/src/main/java/app/organicmaps/bookmarks/FavoriteBookmarkWidgetConfigActivity.java +++ b/android/app/src/main/java/app/organicmaps/bookmarks/FavoriteBookmarkWidgetConfigActivity.java @@ -19,6 +19,8 @@ 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; @@ -26,7 +28,6 @@ import java.util.List; public class FavoriteBookmarkWidgetConfigActivity extends AppCompatActivity { - private static final String TAG = "BookmarkWidgetConfig"; private int mAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID; @@ -42,6 +43,8 @@ public class FavoriteBookmarkWidgetConfigActivity extends AppCompatActivity @Override protected void onCreate(@Nullable Bundle savedInstanceState) { + applyTheme(); + super.onCreate(savedInstanceState); setContentView(R.layout.activity_widget_config); @@ -92,6 +95,12 @@ public class FavoriteBookmarkWidgetConfigActivity extends AppCompatActivity } } + private void applyTheme() + { + String currentTheme = Config.getCurrentUiTheme(this); + setTheme(ThemeUtils.getCardBgThemeResourceId(this, currentTheme)); + } + private void loadCategories() { mShowingCategories = true; @@ -187,4 +196,4 @@ public class FavoriteBookmarkWidgetConfigActivity extends AppCompatActivity super.onBackPressed(); } } -} \ No newline at end of file +} diff --git a/android/app/src/main/res/layout/activity_widget_config.xml b/android/app/src/main/res/layout/activity_widget_config.xml index 2a915c802a..394443dd48 100644 --- a/android/app/src/main/res/layout/activity_widget_config.xml +++ b/android/app/src/main/res/layout/activity_widget_config.xml @@ -12,7 +12,7 @@ android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" - app:popupTheme="@style/ThemeOverlay.AppCompat.Light" /> + app:popupTheme="@style/ThemeOverlay.AppCompat.DayNight" /> @@ -60,6 +60,7 @@ 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" /> diff --git a/android/app/src/main/res/layout/item_widget_list.xml b/android/app/src/main/res/layout/item_widget_list.xml index cf9e5363c9..570446c008 100644 --- a/android/app/src/main/res/layout/item_widget_list.xml +++ b/android/app/src/main/res/layout/item_widget_list.xml @@ -6,7 +6,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="@color/white_primary"> + android:background="?android:attr/windowBackground"> @@ -35,6 +36,7 @@ android:layout_gravity="center_vertical" android:ellipsize="end" android:singleLine="true" + android:textColor="?android:attr/textColorPrimary" android:textAppearance="@style/MwmTextAppearance.Body3" tools:text="42000000" /> -- 2.45.3