diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index fb776920e..d56a874b5 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -232,12 +232,6 @@
android:exported="false"
android:label="@string/provider_bookmarks"
android:syncable="false" />
-
loadFavoritesLocations() {
- return bookmarkLocationDao.getAllBookmarksLocations();
- }
-}
diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/locations/BookmarkLocationsController.kt b/app/src/main/java/fr/free/nrw/commons/bookmarks/locations/BookmarkLocationsController.kt
new file mode 100644
index 000000000..81ec80214
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/bookmarks/locations/BookmarkLocationsController.kt
@@ -0,0 +1,20 @@
+package fr.free.nrw.commons.bookmarks.locations
+
+import fr.free.nrw.commons.nearby.Place
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flow
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class BookmarkLocationsController @Inject constructor(
+ private val bookmarkLocationDao: BookmarkLocationsDao
+) {
+
+ /**
+ * Load bookmarked locations from the database.
+ * @return a list of Place objects.
+ */
+ suspend fun loadFavoritesLocations(): List =
+ bookmarkLocationDao.getAllBookmarksLocationsPlace()
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/locations/BookmarkLocationsDao.java b/app/src/main/java/fr/free/nrw/commons/bookmarks/locations/BookmarkLocationsDao.java
deleted file mode 100644
index fe4f603f4..000000000
--- a/app/src/main/java/fr/free/nrw/commons/bookmarks/locations/BookmarkLocationsDao.java
+++ /dev/null
@@ -1,313 +0,0 @@
-package fr.free.nrw.commons.bookmarks.locations;
-
-import android.annotation.SuppressLint;
-import android.content.ContentProviderClient;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteException;
-import android.os.RemoteException;
-
-import androidx.annotation.NonNull;
-
-import fr.free.nrw.commons.nearby.NearbyController;
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-import javax.inject.Provider;
-
-import fr.free.nrw.commons.location.LatLng;
-import fr.free.nrw.commons.nearby.Label;
-import fr.free.nrw.commons.nearby.Place;
-import fr.free.nrw.commons.nearby.Sitelinks;
-import timber.log.Timber;
-
-import static fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsContentProvider.BASE_URI;
-
-public class BookmarkLocationsDao {
-
- private final Provider clientProvider;
-
- @Inject
- public BookmarkLocationsDao(@Named("bookmarksLocation") Provider clientProvider) {
- this.clientProvider = clientProvider;
- }
-
- /**
- * Find all persisted locations bookmarks on database
- *
- * @return list of Place
- */
- @NonNull
- public List getAllBookmarksLocations() {
- List items = new ArrayList<>();
- Cursor cursor = null;
- ContentProviderClient db = clientProvider.get();
- try {
- cursor = db.query(
- BookmarkLocationsContentProvider.BASE_URI,
- Table.ALL_FIELDS,
- null,
- new String[]{},
- null);
- while (cursor != null && cursor.moveToNext()) {
- items.add(fromCursor(cursor));
- }
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- db.release();
- }
- return items;
- }
-
- /**
- * Look for a place in bookmarks table in order to insert or delete it
- *
- * @param bookmarkLocation : Place object
- * @return is Place now fav ?
- */
- public boolean updateBookmarkLocation(Place bookmarkLocation) {
- boolean bookmarkExists = findBookmarkLocation(bookmarkLocation);
- if (bookmarkExists) {
- deleteBookmarkLocation(bookmarkLocation);
- NearbyController.updateMarkerLabelListBookmark(bookmarkLocation, false);
- } else {
- addBookmarkLocation(bookmarkLocation);
- NearbyController.updateMarkerLabelListBookmark(bookmarkLocation, true);
- }
- return !bookmarkExists;
- }
-
- /**
- * Add a Place to bookmarks table
- *
- * @param bookmarkLocation : Place to add
- */
- private void addBookmarkLocation(Place bookmarkLocation) {
- ContentProviderClient db = clientProvider.get();
- try {
- db.insert(BASE_URI, toContentValues(bookmarkLocation));
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- } finally {
- db.release();
- }
- }
-
- /**
- * Delete a Place from bookmarks table
- *
- * @param bookmarkLocation : Place to delete
- */
- private void deleteBookmarkLocation(Place bookmarkLocation) {
- ContentProviderClient db = clientProvider.get();
- try {
- db.delete(BookmarkLocationsContentProvider.uriForName(bookmarkLocation.name), null, null);
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- } finally {
- db.release();
- }
- }
-
- /**
- * Find a Place from database based on its name
- *
- * @param bookmarkLocation : Place to find
- * @return boolean : is Place in database ?
- */
- public boolean findBookmarkLocation(Place bookmarkLocation) {
- Cursor cursor = null;
- ContentProviderClient db = clientProvider.get();
- try {
- cursor = db.query(
- BookmarkLocationsContentProvider.BASE_URI,
- Table.ALL_FIELDS,
- Table.COLUMN_NAME + "=?",
- new String[]{bookmarkLocation.name},
- null);
- if (cursor != null && cursor.moveToFirst()) {
- return true;
- }
- } catch (RemoteException e) {
- // This feels lazy, but to hell with checked exceptions. :)
- throw new RuntimeException(e);
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- db.release();
- }
- return false;
- }
-
- @SuppressLint("Range")
- @NonNull
- Place fromCursor(final Cursor cursor) {
- final LatLng location = new LatLng(cursor.getDouble(cursor.getColumnIndex(Table.COLUMN_LAT)),
- cursor.getDouble(cursor.getColumnIndex(Table.COLUMN_LONG)), 1F);
-
- final Sitelinks.Builder builder = new Sitelinks.Builder();
- builder.setWikipediaLink(cursor.getString(cursor.getColumnIndex(Table.COLUMN_WIKIPEDIA_LINK)));
- builder.setWikidataLink(cursor.getString(cursor.getColumnIndex(Table.COLUMN_WIKIDATA_LINK)));
- builder.setCommonsLink(cursor.getString(cursor.getColumnIndex(Table.COLUMN_COMMONS_LINK)));
-
- return new Place(
- cursor.getString(cursor.getColumnIndex(Table.COLUMN_LANGUAGE)),
- cursor.getString(cursor.getColumnIndex(Table.COLUMN_NAME)),
- Label.fromText((cursor.getString(cursor.getColumnIndex(Table.COLUMN_LABEL_TEXT)))),
- cursor.getString(cursor.getColumnIndex(Table.COLUMN_DESCRIPTION)),
- location,
- cursor.getString(cursor.getColumnIndex(Table.COLUMN_CATEGORY)),
- builder.build(),
- cursor.getString(cursor.getColumnIndex(Table.COLUMN_PIC)),
- Boolean.parseBoolean(cursor.getString(cursor.getColumnIndex(Table.COLUMN_EXISTS)))
- );
- }
-
- private ContentValues toContentValues(Place bookmarkLocation) {
- ContentValues cv = new ContentValues();
- cv.put(BookmarkLocationsDao.Table.COLUMN_NAME, bookmarkLocation.getName());
- cv.put(BookmarkLocationsDao.Table.COLUMN_LANGUAGE, bookmarkLocation.getLanguage());
- cv.put(BookmarkLocationsDao.Table.COLUMN_DESCRIPTION, bookmarkLocation.getLongDescription());
- cv.put(BookmarkLocationsDao.Table.COLUMN_CATEGORY, bookmarkLocation.getCategory());
- cv.put(BookmarkLocationsDao.Table.COLUMN_LABEL_TEXT, bookmarkLocation.getLabel()!=null ? bookmarkLocation.getLabel().getText() : "");
- cv.put(BookmarkLocationsDao.Table.COLUMN_LABEL_ICON, bookmarkLocation.getLabel()!=null ? bookmarkLocation.getLabel().getIcon() : null);
- cv.put(BookmarkLocationsDao.Table.COLUMN_WIKIPEDIA_LINK, bookmarkLocation.siteLinks.getWikipediaLink().toString());
- cv.put(BookmarkLocationsDao.Table.COLUMN_WIKIDATA_LINK, bookmarkLocation.siteLinks.getWikidataLink().toString());
- cv.put(BookmarkLocationsDao.Table.COLUMN_COMMONS_LINK, bookmarkLocation.siteLinks.getCommonsLink().toString());
- cv.put(BookmarkLocationsDao.Table.COLUMN_LAT, bookmarkLocation.location.getLatitude());
- cv.put(BookmarkLocationsDao.Table.COLUMN_LONG, bookmarkLocation.location.getLongitude());
- cv.put(BookmarkLocationsDao.Table.COLUMN_PIC, bookmarkLocation.pic);
- cv.put(BookmarkLocationsDao.Table.COLUMN_EXISTS, bookmarkLocation.exists.toString());
- return cv;
- }
-
- public static class Table {
- public static final String TABLE_NAME = "bookmarksLocations";
-
- static final String COLUMN_NAME = "location_name";
- static final String COLUMN_LANGUAGE = "location_language";
- static final String COLUMN_DESCRIPTION = "location_description";
- static final String COLUMN_LAT = "location_lat";
- static final String COLUMN_LONG = "location_long";
- static final String COLUMN_CATEGORY = "location_category";
- static final String COLUMN_LABEL_TEXT = "location_label_text";
- static final String COLUMN_LABEL_ICON = "location_label_icon";
- static final String COLUMN_IMAGE_URL = "location_image_url";
- static final String COLUMN_WIKIPEDIA_LINK = "location_wikipedia_link";
- static final String COLUMN_WIKIDATA_LINK = "location_wikidata_link";
- static final String COLUMN_COMMONS_LINK = "location_commons_link";
- static final String COLUMN_PIC = "location_pic";
- static final String COLUMN_EXISTS = "location_exists";
-
- // NOTE! KEEP IN SAME ORDER AS THEY ARE DEFINED UP THERE. HELPS HARD CODE COLUMN INDICES.
- public static final String[] ALL_FIELDS = {
- COLUMN_NAME,
- COLUMN_LANGUAGE,
- COLUMN_DESCRIPTION,
- COLUMN_CATEGORY,
- COLUMN_LABEL_TEXT,
- COLUMN_LABEL_ICON,
- COLUMN_LAT,
- COLUMN_LONG,
- COLUMN_IMAGE_URL,
- COLUMN_WIKIPEDIA_LINK,
- COLUMN_WIKIDATA_LINK,
- COLUMN_COMMONS_LINK,
- COLUMN_PIC,
- COLUMN_EXISTS,
- };
-
- static final String DROP_TABLE_STATEMENT = "DROP TABLE IF EXISTS " + TABLE_NAME;
-
- static final String CREATE_TABLE_STATEMENT = "CREATE TABLE " + TABLE_NAME + " ("
- + COLUMN_NAME + " STRING PRIMARY KEY,"
- + COLUMN_LANGUAGE + " STRING,"
- + COLUMN_DESCRIPTION + " STRING,"
- + COLUMN_CATEGORY + " STRING,"
- + COLUMN_LABEL_TEXT + " STRING,"
- + COLUMN_LABEL_ICON + " INTEGER,"
- + COLUMN_LAT + " DOUBLE,"
- + COLUMN_LONG + " DOUBLE,"
- + COLUMN_IMAGE_URL + " STRING,"
- + COLUMN_WIKIPEDIA_LINK + " STRING,"
- + COLUMN_WIKIDATA_LINK + " STRING,"
- + COLUMN_COMMONS_LINK + " STRING,"
- + COLUMN_PIC + " STRING,"
- + COLUMN_EXISTS + " STRING"
- + ");";
-
- public static void onCreate(SQLiteDatabase db) {
- db.execSQL(CREATE_TABLE_STATEMENT);
- }
-
- public static void onDelete(SQLiteDatabase db) {
- db.execSQL(DROP_TABLE_STATEMENT);
- onCreate(db);
- }
-
- public static void onUpdate(final SQLiteDatabase db, int from, final int to) {
- Timber.d("bookmarksLocations db is updated from:"+from+", to:"+to);
- if (from == to) {
- return;
- }
- if (from < 7) {
- // doesn't exist yet
- from++;
- onUpdate(db, from, to);
- return;
- }
- if (from == 7) {
- // table added in version 8
- onCreate(db);
- from++;
- onUpdate(db, from, to);
- return;
- }
- if (from < 10) {
- from++;
- onUpdate(db, from, to);
- return;
- }
- if (from == 10) {
- //This is safe, and can be called clean, as we/I do not remember the appropriate version for this
- //We are anyways switching to room, these things won't be necessary then
- try {
- db.execSQL("ALTER TABLE bookmarksLocations ADD COLUMN location_pic STRING;");
- }catch (SQLiteException exception){
- Timber.e(exception);//
- }
- return;
- }
- if (from >= 12) {
- try {
- db.execSQL(
- "ALTER TABLE bookmarksLocations ADD COLUMN location_destroyed STRING;");
- } catch (SQLiteException exception) {
- Timber.e(exception);
- }
- }
- if (from >= 13){
- try {
- db.execSQL("ALTER TABLE bookmarksLocations ADD COLUMN location_language STRING;");
- } catch (SQLiteException exception){
- Timber.e(exception);
- }
- }
- if (from >= 14){
- try {
- db.execSQL("ALTER TABLE bookmarksLocations ADD COLUMN location_exists STRING;");
- } catch (SQLiteException exception){
- Timber.e(exception);
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/locations/BookmarkLocationsDao.kt b/app/src/main/java/fr/free/nrw/commons/bookmarks/locations/BookmarkLocationsDao.kt
new file mode 100644
index 000000000..2fa65b2d9
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/bookmarks/locations/BookmarkLocationsDao.kt
@@ -0,0 +1,65 @@
+package fr.free.nrw.commons.bookmarks.locations
+
+import androidx.room.Dao
+import androidx.room.Delete
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
+import fr.free.nrw.commons.nearby.NearbyController
+import fr.free.nrw.commons.nearby.Place
+
+/**
+ * DAO for managing bookmark locations in the database.
+ */
+@Dao
+abstract class BookmarkLocationsDao {
+
+ /**
+ * Adds or updates a bookmark location in the database.
+ */
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ abstract suspend fun addBookmarkLocation(bookmarkLocation: BookmarksLocations)
+
+ /**
+ * Fetches all bookmark locations from the database.
+ */
+ @Query("SELECT * FROM bookmarks_locations")
+ abstract suspend fun getAllBookmarksLocations(): List
+
+ /**
+ * Checks if a bookmark location exists by name.
+ */
+ @Query("SELECT EXISTS (SELECT 1 FROM bookmarks_locations WHERE location_name = :name)")
+ abstract suspend fun findBookmarkLocation(name: String): Boolean
+
+ /**
+ * Deletes a bookmark location from the database.
+ */
+ @Delete
+ abstract suspend fun deleteBookmarkLocation(bookmarkLocation: BookmarksLocations)
+
+ /**
+ * Adds or removes a bookmark location and updates markers.
+ * @return `true` if added, `false` if removed.
+ */
+ suspend fun updateBookmarkLocation(bookmarkLocation: Place): Boolean {
+ val exists = findBookmarkLocation(bookmarkLocation.name)
+
+ if (exists) {
+ deleteBookmarkLocation(bookmarkLocation.toBookmarksLocations())
+ NearbyController.updateMarkerLabelListBookmark(bookmarkLocation, false)
+ } else {
+ addBookmarkLocation(bookmarkLocation.toBookmarksLocations())
+ NearbyController.updateMarkerLabelListBookmark(bookmarkLocation, true)
+ }
+
+ return !exists
+ }
+
+ /**
+ * Fetches all bookmark locations as `Place` objects.
+ */
+ suspend fun getAllBookmarksLocationsPlace(): List {
+ return getAllBookmarksLocations().map { it.toPlace() }
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/locations/BookmarkLocationsFragment.java b/app/src/main/java/fr/free/nrw/commons/bookmarks/locations/BookmarkLocationsFragment.java
deleted file mode 100644
index f5ce556c4..000000000
--- a/app/src/main/java/fr/free/nrw/commons/bookmarks/locations/BookmarkLocationsFragment.java
+++ /dev/null
@@ -1,137 +0,0 @@
-package fr.free.nrw.commons.bookmarks.locations;
-
-import android.Manifest.permission;
-import android.content.Intent;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import androidx.activity.result.ActivityResultCallback;
-import androidx.activity.result.ActivityResultLauncher;
-import androidx.activity.result.contract.ActivityResultContracts;
-import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import dagger.android.support.DaggerFragment;
-import fr.free.nrw.commons.R;
-import fr.free.nrw.commons.contributions.ContributionController;
-import fr.free.nrw.commons.databinding.FragmentBookmarksLocationsBinding;
-import fr.free.nrw.commons.nearby.Place;
-import fr.free.nrw.commons.nearby.fragments.CommonPlaceClickActions;
-import fr.free.nrw.commons.nearby.fragments.PlaceAdapter;
-import java.util.List;
-import java.util.Map;
-import javax.inject.Inject;
-import kotlin.Unit;
-
-public class BookmarkLocationsFragment extends DaggerFragment {
-
- public FragmentBookmarksLocationsBinding binding;
-
- @Inject BookmarkLocationsController controller;
- @Inject ContributionController contributionController;
- @Inject BookmarkLocationsDao bookmarkLocationDao;
- @Inject CommonPlaceClickActions commonPlaceClickActions;
- private PlaceAdapter adapter;
-
- private final ActivityResultLauncher cameraPickLauncherForResult =
- registerForActivityResult(new StartActivityForResult(),
- result -> {
- contributionController.handleActivityResultWithCallback(requireActivity(), callbacks -> {
- contributionController.onPictureReturnedFromCamera(result, requireActivity(), callbacks);
- });
- });
-
- private final ActivityResultLauncher galleryPickLauncherForResult =
- registerForActivityResult(new StartActivityForResult(),
- result -> {
- contributionController.handleActivityResultWithCallback(requireActivity(), callbacks -> {
- contributionController.onPictureReturnedFromGallery(result, requireActivity(), callbacks);
- });
- });
-
- private ActivityResultLauncher inAppCameraLocationPermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), new ActivityResultCallback