diff --git a/.github/workflows/android-ci-comment.yml b/.github/workflows/android-ci-comment.yml
index 64422e284..b200c9a70 100644
--- a/.github/workflows/android-ci-comment.yml
+++ b/.github/workflows/android-ci-comment.yml
@@ -1,6 +1,10 @@
name: Android CI Comment
-on: [pull_request_target]
+on:
+ workflow_run:
+ workflows: ["Android CI"]
+ types: [completed]
+ branches: [main]
permissions:
issues: write
@@ -9,16 +13,17 @@ jobs:
comment:
name: Comment on PR with APK links
runs-on: ubuntu-latest
+ if: github.event.workflow_run.conclusion == 'success'
steps:
- name: Checkout base branch
uses: actions/checkout@v3
with:
- ref: ${{ github.base_ref }}
-
+ ref: ${{ github.event.workflow_run.head_branch }}
- name: Download Run ID Artifact
uses: actions/download-artifact@v4
with:
name: run-id
+ run-id: ${{ github.event.workflow_run.id }}
- name: Read Run ID
id: read-run-id
@@ -26,7 +31,7 @@ jobs:
- name: Comment on PR with APK download links
env:
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
uses: actions/github-script@v6
with:
script: |
@@ -41,6 +46,13 @@ jobs:
throw new Error('Run ID not found.');
}
+ // Get the PR number from the workflow_run event
+ const prNumber = ${{ github.event.workflow_run.pull_requests[0].number }};
+ if (!prNumber) {
+ console.log('No PR number found in workflow_run event.');
+ return;
+ }
+
const { data: { artifacts } } = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
@@ -71,7 +83,7 @@ jobs:
`;
await github.rest.issues.createComment({
- issue_number: context.issue.number,
+ issue_number: prNumber,
owner: context.repo.owner,
repo: context.repo.repo,
body: commentBody
diff --git a/README.md b/README.md
index d7e9d334b..0b31ff5be 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# Wikimedia Commons Android app

-[](https://github.com/commons-app/apps-android-commons/actions?query=branch%3Amaster)
+[](https://github.com/commons-app/apps-android-commons/actions?query=branch%3Amain)
[](https://appetize.io/app/8ywtpe9f8tb8h6bey11c92vkcw)
[](https://codecov.io/gh/commons-app/apps-android-commons)
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