Refactor: Migrate to Room Database for Bookmark Locations

This commit migrates the bookmark locations functionality from a custom content provider to Room database.

Key changes:

*   **Removal of `BookmarkLocationsContentProvider`:** This class, which previously handled data storage, has been removed.
*   **Introduction of `BookmarksLocations`:** This data class now represents a bookmarked location, serving as the Room entity.
*   **Creation of `BookmarkLocationsDao`:** This Room DAO handles database interactions for bookmark locations, including:
    *   Adding, deleting, and querying bookmarked locations.
    *   Checking if a location is already bookmarked.
    *   Updating the bookmark status of a location.
    *   Retrieving all bookmarked locations as `Place` objects.
*   **`BookmarkLocationsViewModel`:** Added to manage the data layer for bookmark locations
*   **`NearbyUtil`:** Created a Util class for Nearby to manage the bookmark locations.
*   **Updates in `PlaceAdapter` and `PlaceAdapterDelegate`:** These classes have been modified to work with the new Room-based data layer.
*   **Updates in `AppDatabase`:** The database now includes `BookmarksLocations` as an entity and exposes the `bookmarkLocationsDao`.
*   **Updates in `FragmentBuilderModule` and `CommonsApplicationModule`**: for DI
*   **Removal of `DBOpenHelper` upgrade for locations**: as it is no longer needed
* **Updates in `NearbyParentFragmentPresenter`**: refactored the logic to use the dao functions
* **Updates in `NearbyParentFragment`**: refactored the logic to use the util and dao functions
* **Update in `BookmarkLocationsController`**: removed as its no longer needed
* **Add `toPlace` and `toBookmarksLocations`**: extension functions to map between data class and entities
* **Update in `CommonsApplication`**: to remove old db table.
This commit is contained in:
Saifuddin 2025-01-16 23:11:10 +05:30
parent 313f61538f
commit 54d0f65294
18 changed files with 210 additions and 429 deletions

View file

@ -253,7 +253,6 @@ class CommonsApplication : MultiDexApplication() {
Timber.e(e)
}
BookmarkPicturesDao.Table.onDelete(db)
BookmarkLocationsDao.Table.onDelete(db)
BookmarkItemsDao.Table.onDelete(db)
}

View file

@ -1,126 +0,0 @@
package fr.free.nrw.commons.bookmarks.locations
// We can get uri using java.Net.Uri, but android implementation is faster
// (but it's forgiving with handling exceptions though)
import android.content.ContentValues
import android.database.Cursor
import android.database.sqlite.SQLiteQueryBuilder
import android.net.Uri
import fr.free.nrw.commons.BuildConfig
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao.Table.COLUMN_NAME
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao.Table.TABLE_NAME
import fr.free.nrw.commons.data.DBOpenHelper
import fr.free.nrw.commons.di.CommonsDaggerContentProvider
import timber.log.Timber
import javax.inject.Inject
/**
* Handles private storage for Bookmark locations
*/
class BookmarkLocationsContentProvider : CommonsDaggerContentProvider() {
companion object {
private const val BASE_PATH = "bookmarksLocations"
val BASE_URI: Uri =
Uri.parse("content://${BuildConfig.BOOKMARK_LOCATIONS_AUTHORITY}/$BASE_PATH")
/**
* Append bookmark locations name to the base URI.
*/
fun uriForName(name: String): Uri {
return Uri.parse("$BASE_URI/$name")
}
}
@Inject
lateinit var dbOpenHelper: DBOpenHelper
override fun getType(uri: Uri): String? = null
/**
* Queries the SQLite database for the bookmark locations.
*/
override fun query(
uri: Uri,
projection: Array<String>?,
selection: String?,
selectionArgs: Array<String>?,
sortOrder: String?
): Cursor {
val queryBuilder = SQLiteQueryBuilder().apply {
tables = TABLE_NAME
}
val db = dbOpenHelper.readableDatabase
val cursor = queryBuilder.query(
db,
projection,
selection,
selectionArgs,
null,
null,
sortOrder
)
cursor.setNotificationUri(context?.contentResolver, uri)
return cursor
}
/**
* Handles the update query of local SQLite database.
*/
override fun update(
uri: Uri,
contentValues: ContentValues?,
selection: String?,
selectionArgs: Array<String>?
): Int {
val db = dbOpenHelper.writableDatabase
val rowsUpdated: Int
if (selection.isNullOrEmpty()) {
val id = uri.lastPathSegment?.toIntOrNull()
?: throw IllegalArgumentException("Invalid ID in URI")
rowsUpdated = db.update(
TABLE_NAME,
contentValues,
"$COLUMN_NAME = ?",
arrayOf(id.toString())
)
} else {
throw IllegalArgumentException(
"Parameter `selection` should be empty when updating an ID"
)
}
context?.contentResolver?.notifyChange(uri, null)
return rowsUpdated
}
/**
* Handles the insertion of a new bookmark locations record to the local SQLite database.
*/
override fun insert(uri: Uri, contentValues: ContentValues?): Uri {
val db = dbOpenHelper.writableDatabase
val id = db.insert(TABLE_NAME, null, contentValues)
context?.contentResolver?.notifyChange(uri, null)
return Uri.parse("$BASE_URI/$id")
}
/**
* Handles the deletion of bookmark locations from the local SQLite database.
*/
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
val db = dbOpenHelper.readableDatabase
Timber.d("Deleting bookmark name %s", uri.lastPathSegment)
val rows = db.delete(
TABLE_NAME,
"location_name = ?",
arrayOf(uri.lastPathSegment)
)
context?.contentResolver?.notifyChange(uri, null)
return rows
}
}

View file

@ -13,5 +13,5 @@ class BookmarkLocationsController @Inject constructor(
* Load bookmarked locations from the database.
* @return a list of Place objects.
*/
fun loadFavoritesLocations(): List<Place> = bookmarkLocationDao.getAllBookmarksLocations()
fun loadFavoritesLocations(): List<Place> = listOf()
}

View file

@ -1,286 +1,49 @@
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.location.LatLng
import fr.free.nrw.commons.nearby.Label
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
import fr.free.nrw.commons.nearby.Sitelinks
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Named
import javax.inject.Provider
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
@Dao
abstract class BookmarkLocationsDao {
class BookmarkLocationsDao @Inject constructor(
@Named("bookmarksLocation") private val clientProvider: Provider<ContentProviderClient>
) {
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract suspend fun addBookmarkLocation(bookmarkLocation: BookmarksLocations)
/**
* Find all persisted location bookmarks in the database
* @return list of Place
*/
fun getAllBookmarksLocations(): List<Place> {
val items = mutableListOf<Place>()
var cursor: Cursor? = null
val db = clientProvider.get()
try {
cursor = db.query(
BookmarkLocationsContentProvider.BASE_URI,
Table.ALL_FIELDS,
null,
emptyArray(),
null
@Query("SELECT * FROM bookmarks_locations")
abstract suspend fun getAllBookmarksLocations(): List<BookmarksLocations>
@Query("SELECT EXISTS (SELECT 1 FROM bookmarks_locations WHERE location_name = :name)")
abstract suspend fun findBookmarkLocation(name: String): Boolean
@Delete
abstract suspend fun deleteBookmarkLocation(bookmarkLocation: BookmarksLocations)
suspend fun updateBookmarkLocation(bookmarkLocation: Place): Boolean {
val bookmarkLocationExists = findBookmarkLocation(bookmarkLocation.name)
if(bookmarkLocationExists) {
deleteBookmarkLocation(
bookmarkLocation.toBookmarksLocations()
)
cursor?.let {
while (it.moveToNext()) {
items.add(fromCursor(it))
}
}
} catch (e: RemoteException) {
throw RuntimeException(e)
} finally {
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 boolean : is Place now fav ?
*/
fun updateBookmarkLocation(bookmarkLocation: Place): Boolean {
val bookmarkExists = findBookmarkLocation(bookmarkLocation)
if (bookmarkExists) {
deleteBookmarkLocation(bookmarkLocation)
NearbyController.updateMarkerLabelListBookmark(bookmarkLocation, false)
} else {
addBookmarkLocation(bookmarkLocation)
addBookmarkLocation(
bookmarkLocation.toBookmarksLocations()
)
NearbyController.updateMarkerLabelListBookmark(bookmarkLocation, true)
}
return !bookmarkExists
return !bookmarkLocationExists
}
/**
* Add a Place to bookmarks table
* @param bookmarkLocation : Place to add
*/
private fun addBookmarkLocation(bookmarkLocation: Place) {
val db = clientProvider.get()
try {
db.insert(BookmarkLocationsContentProvider.BASE_URI, toContentValues(bookmarkLocation))
} catch (e: RemoteException) {
throw RuntimeException(e)
} finally {
db.release()
}
fun getAllBookmarksLocationsPlace(): Flow<List<Place>> {
return flow { getAllBookmarksLocations().map { it.toPlace() } }
}
/**
* Delete a Place from bookmarks table
* @param bookmarkLocation : Place to delete
*/
private fun deleteBookmarkLocation(bookmarkLocation: Place) {
val db = clientProvider.get()
try {
db.delete(
BookmarkLocationsContentProvider.uriForName(bookmarkLocation.name),
null,
null
)
} catch (e: RemoteException) {
throw 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 ?
*/
fun findBookmarkLocation(bookmarkLocation: Place): Boolean {
var cursor: Cursor? = null
val db = clientProvider.get()
try {
cursor = db.query(
BookmarkLocationsContentProvider.BASE_URI,
Table.ALL_FIELDS,
"${Table.COLUMN_NAME}=?",
arrayOf(bookmarkLocation.name),
null
)
return cursor?.moveToFirst() == true
} catch (e: RemoteException) {
throw RuntimeException(e)
} finally {
cursor?.close()
db.release()
}
}
@SuppressLint("Range")
@NonNull
fun fromCursor(cursor: Cursor): Place {
val location = LatLng(
cursor.getDouble(cursor.getColumnIndex(Table.COLUMN_LAT)),
cursor.getDouble(cursor.getColumnIndex(Table.COLUMN_LONG)),
1F
)
val builder = Sitelinks.Builder().apply {
setWikipediaLink(cursor.getString(cursor.getColumnIndex(Table.COLUMN_WIKIPEDIA_LINK)))
setWikidataLink(cursor.getString(cursor.getColumnIndex(Table.COLUMN_WIKIDATA_LINK)))
setCommonsLink(cursor.getString(cursor.getColumnIndex(Table.COLUMN_COMMONS_LINK)))
}
return 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)),
cursor.getString(cursor.getColumnIndex(Table.COLUMN_EXISTS))?.toBoolean() ?: false
)
}
private fun toContentValues(bookmarkLocation: Place): ContentValues {
return ContentValues().apply {
put(Table.COLUMN_NAME, bookmarkLocation.name)
put(Table.COLUMN_LANGUAGE, bookmarkLocation.language)
put(Table.COLUMN_DESCRIPTION, bookmarkLocation.longDescription)
put(Table.COLUMN_CATEGORY, bookmarkLocation.category)
put(Table.COLUMN_LABEL_TEXT, bookmarkLocation.label?.text ?: "")
put(Table.COLUMN_LABEL_ICON, bookmarkLocation.label?.icon)
put(Table.COLUMN_WIKIPEDIA_LINK, bookmarkLocation.siteLinks.wikipediaLink.toString())
put(Table.COLUMN_WIKIDATA_LINK, bookmarkLocation.siteLinks.wikidataLink.toString())
put(Table.COLUMN_COMMONS_LINK, bookmarkLocation.siteLinks.commonsLink.toString())
put(Table.COLUMN_LAT, bookmarkLocation.location.latitude)
put(Table.COLUMN_LONG, bookmarkLocation.location.longitude)
put(Table.COLUMN_PIC, bookmarkLocation.pic)
put(Table.COLUMN_EXISTS, bookmarkLocation.exists.toString())
}
}
object Table {
const val TABLE_NAME = "bookmarksLocations"
const val COLUMN_NAME = "location_name"
const val COLUMN_LANGUAGE = "location_language"
const val COLUMN_DESCRIPTION = "location_description"
const val COLUMN_LAT = "location_lat"
const val COLUMN_LONG = "location_long"
const val COLUMN_CATEGORY = "location_category"
const val COLUMN_LABEL_TEXT = "location_label_text"
const val COLUMN_LABEL_ICON = "location_label_icon"
const val COLUMN_IMAGE_URL = "location_image_url"
const val COLUMN_WIKIPEDIA_LINK = "location_wikipedia_link"
const val COLUMN_WIKIDATA_LINK = "location_wikidata_link"
const val COLUMN_COMMONS_LINK = "location_commons_link"
const val COLUMN_PIC = "location_pic"
const val COLUMN_EXISTS = "location_exists"
// NOTE! KEEP IN SAME ORDER AS THEY ARE DEFINED UP THERE. HELPS HARD CODE COLUMN INDICES.
val ALL_FIELDS = arrayOf(
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
)
const val DROP_TABLE_STATEMENT = "DROP TABLE IF EXISTS $TABLE_NAME"
const val 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
);
"""
fun onCreate(db: SQLiteDatabase) {
db.execSQL(CREATE_TABLE_STATEMENT)
}
fun onDelete(db: SQLiteDatabase) {
db.execSQL(DROP_TABLE_STATEMENT)
onCreate(db)
}
@SuppressLint("SQLiteString")
fun onUpdate(db: SQLiteDatabase, from: Int, to: Int) {
Timber.d("bookmarksLocations db is updated from:$from, to:$to")
var currFrom = from
if (from == to) {
return
}
if (from < 7) {
onUpdate(db, ++currFrom, to)
return
}
if (from == 7) {
onCreate(db)
onUpdate(db, ++currFrom, to)
return
}
if (from < 10) {
onUpdate(db, ++currFrom, to)
return
}
if (from == 10) {
try {
db.execSQL("ALTER TABLE $TABLE_NAME ADD COLUMN location_pic STRING;")
} catch (exception: SQLiteException) {
Timber.e(exception)
}
return
}
if (from >= 12) {
try {
db.execSQL("ALTER TABLE $TABLE_NAME ADD COLUMN location_destroyed STRING;")
} catch (exception: SQLiteException) {
Timber.e(exception)
}
}
if (from >= 13) {
try {
db.execSQL("ALTER TABLE $TABLE_NAME ADD COLUMN location_language STRING;")
} catch (exception: SQLiteException) {
Timber.e(exception)
}
}
if (from >= 14) {
try {
db.execSQL("ALTER TABLE $TABLE_NAME ADD COLUMN location_exists STRING;")
} catch (exception: SQLiteException) {
Timber.e(exception)
}
}
}
}
}
}

View file

@ -8,6 +8,7 @@ import android.view.ViewGroup
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import dagger.android.support.DaggerFragment
import fr.free.nrw.commons.R
@ -108,6 +109,7 @@ class BookmarkLocationsFragment : DaggerFragment() {
adapter = PlaceAdapter(
bookmarkLocationDao,
scope = lifecycleScope,
{ },
{ place, _ ->
adapter.remove(place)

View file

@ -0,0 +1,15 @@
package fr.free.nrw.commons.bookmarks.locations
import androidx.lifecycle.ViewModel
import fr.free.nrw.commons.nearby.Place
import kotlinx.coroutines.flow.Flow
class BookmarkLocationsViewModel(
private val bookmarkLocationsDao: BookmarkLocationsDao
): ViewModel() {
fun getAllBookmarkLocations(): Flow<List<Place>> {
return bookmarkLocationsDao.getAllBookmarksLocationsPlace()
}
}

View file

@ -0,0 +1,72 @@
package fr.free.nrw.commons.bookmarks.locations
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
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
@Entity(tableName = "bookmarks_locations")
data class BookmarksLocations(
@PrimaryKey @ColumnInfo(name = "location_name") val locationName: String,
@ColumnInfo(name = "location_language") val locationLanguage: String,
@ColumnInfo(name = "location_description") val locationDescription: String,
@ColumnInfo(name = "location_lat") val locationLat: Double,
@ColumnInfo(name = "location_long") val locationLong: Double,
@ColumnInfo(name = "location_category") val locationCategory: String,
@ColumnInfo(name = "location_label_text") val locationLabelText: String,
@ColumnInfo(name = "location_label_icon") val locationLabelIcon: Int?,
@ColumnInfo(name = "location_image_url") val locationImageUrl: String,
@ColumnInfo(name = "location_wikipedia_link") val locationWikipediaLink: String,
@ColumnInfo(name = "location_wikidata_link") val locationWikidataLink: String,
@ColumnInfo(name = "location_commons_link") val locationCommonsLink: String,
@ColumnInfo(name = "location_pic") val locationPic: String,
@ColumnInfo(name = "location_exists") val locationExists: Boolean
)
fun BookmarksLocations.toPlace(): Place {
val location = LatLng(
locationLat,
locationLong,
1F
)
val builder = Sitelinks.Builder().apply {
setWikipediaLink(locationWikipediaLink)
setWikidataLink(locationWikidataLink)
setCommonsLink(locationCommonsLink)
}
return Place(
locationLanguage,
locationName,
Label.fromText(locationLabelText),
locationDescription,
location,
locationCategory,
builder.build(),
locationPic,
locationExists
)
}
fun Place.toBookmarksLocations(): BookmarksLocations {
return BookmarksLocations(
locationName = name,
locationLanguage = language,
locationDescription = longDescription,
locationCategory = category,
locationLat = location.latitude,
locationLong = location.longitude,
locationLabelText = label?.text ?: "",
locationLabelIcon = label?.icon,
locationImageUrl = pic,
locationWikipediaLink = siteLinks.wikipediaLink.toString(),
locationWikidataLink = siteLinks.wikidataLink.toString(),
locationCommonsLink = siteLinks.commonsLink.toString(),
locationPic = pic,
locationExists = exists
)
}

View file

@ -30,7 +30,6 @@ class DBOpenHelper(
override fun onCreate(db: SQLiteDatabase) {
CategoryDao.Table.onCreate(db)
BookmarkPicturesDao.Table.onCreate(db)
BookmarkLocationsDao.Table.onCreate(db)
BookmarkItemsDao.Table.onCreate(db)
RecentSearchesDao.Table.onCreate(db)
RecentLanguagesDao.Table.onCreate(db)
@ -39,7 +38,6 @@ class DBOpenHelper(
override fun onUpgrade(db: SQLiteDatabase, from: Int, to: Int) {
CategoryDao.Table.onUpdate(db, from, to)
BookmarkPicturesDao.Table.onUpdate(db, from, to)
BookmarkLocationsDao.Table.onUpdate(db, from, to)
BookmarkItemsDao.Table.onUpdate(db, from, to)
RecentSearchesDao.Table.onUpdate(db, from, to)
RecentLanguagesDao.Table.onUpdate(db, from, to)

View file

@ -5,6 +5,8 @@ import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import fr.free.nrw.commons.bookmarks.category.BookmarkCategoriesDao
import fr.free.nrw.commons.bookmarks.category.BookmarksCategoryModal
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao
import fr.free.nrw.commons.bookmarks.locations.BookmarksLocations
import fr.free.nrw.commons.contributions.Contribution
import fr.free.nrw.commons.contributions.ContributionDao
import fr.free.nrw.commons.customselector.database.NotForUploadStatus
@ -23,7 +25,7 @@ import fr.free.nrw.commons.upload.depicts.DepictsDao
*
*/
@Database(
entities = [Contribution::class, Depicts::class, UploadedStatus::class, NotForUploadStatus::class, ReviewEntity::class, Place::class, BookmarksCategoryModal::class],
entities = [Contribution::class, Depicts::class, UploadedStatus::class, NotForUploadStatus::class, ReviewEntity::class, Place::class, BookmarksCategoryModal::class, BookmarksLocations::class],
version = 19,
exportSchema = false,
)
@ -42,4 +44,6 @@ abstract class AppDatabase : RoomDatabase() {
abstract fun ReviewDao(): ReviewDao
abstract fun bookmarkCategoriesDao(): BookmarkCategoriesDao
abstract fun bookmarkLocationsDao(): BookmarkLocationsDao
}

View file

@ -16,6 +16,7 @@ import fr.free.nrw.commons.BuildConfig
import fr.free.nrw.commons.R
import fr.free.nrw.commons.auth.SessionManager
import fr.free.nrw.commons.bookmarks.category.BookmarkCategoriesDao
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao
import fr.free.nrw.commons.contributions.ContributionDao
import fr.free.nrw.commons.customselector.database.NotForUploadStatusDao
import fr.free.nrw.commons.customselector.database.UploadedStatusDao
@ -206,6 +207,10 @@ open class CommonsApplicationModule(private val applicationContext: Context) {
fun providesPlaceDao(appDatabase: AppDatabase): PlaceDao =
appDatabase.PlaceDao()
@Provides
fun providesBookmarkLocationsDao(appDatabase: AppDatabase): BookmarkLocationsDao =
appDatabase.bookmarkLocationsDao()
@Provides
fun providesDepictDao(appDatabase: AppDatabase): DepictsDao =
appDatabase.DepictsDao()

View file

@ -3,7 +3,6 @@ package fr.free.nrw.commons.di
import dagger.Module
import dagger.android.ContributesAndroidInjector
import fr.free.nrw.commons.bookmarks.items.BookmarkItemsContentProvider
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsContentProvider
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesContentProvider
import fr.free.nrw.commons.category.CategoryContentProvider
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesContentProvider
@ -26,9 +25,6 @@ abstract class ContentProviderBuilderModule {
@ContributesAndroidInjector
abstract fun bindBookmarkContentProvider(): BookmarkPicturesContentProvider
@ContributesAndroidInjector
abstract fun bindBookmarkLocationContentProvider(): BookmarkLocationsContentProvider
@ContributesAndroidInjector
abstract fun bindBookmarkItemContentProvider(): BookmarkItemsContentProvider

View file

@ -101,6 +101,9 @@ abstract class FragmentBuilderModule {
@ContributesAndroidInjector
abstract fun bindBookmarkCategoriesListFragment(): BookmarkCategoriesFragment
@ContributesAndroidInjector
abstract fun bindBookmarkLocationsListFragment(): BookmarkLocationsFragment
@ContributesAndroidInjector
abstract fun bindReviewOutOfContextFragment(): ReviewImageFragment

View file

@ -0,0 +1,22 @@
package fr.free.nrw.commons.nearby
import androidx.lifecycle.LifecycleCoroutineScope
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao
import fr.free.nrw.commons.bookmarks.locations.BookmarksLocations
import kotlinx.coroutines.launch
object NearbyUtil {
fun getBookmarkLocationExists(
bookmarksLocationsDao: BookmarkLocationsDao,
name: String,
scope: LifecycleCoroutineScope?
): Boolean {
var isBookmarked = false
scope?.launch {
isBookmarked = bookmarksLocationsDao.findBookmarkLocation(name)
}
return isBookmarked
}
}

View file

@ -7,6 +7,7 @@ import android.view.View.INVISIBLE
import android.view.View.VISIBLE
import android.widget.RelativeLayout
import androidx.activity.result.ActivityResultLauncher
import androidx.lifecycle.LifecycleCoroutineScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.transition.TransitionManager
@ -16,9 +17,11 @@ import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import fr.free.nrw.commons.R
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao
import fr.free.nrw.commons.databinding.ItemPlaceBinding
import kotlinx.coroutines.launch
fun placeAdapterDelegate(
bookmarkLocationDao: BookmarkLocationsDao,
scope: LifecycleCoroutineScope?,
onItemClick: ((Place) -> Unit)? = null,
onCameraClicked: (Place, ActivityResultLauncher<Array<String>>, ActivityResultLauncher<Intent>) -> Unit,
onCameraLongPressed: () -> Boolean,
@ -61,7 +64,10 @@ fun placeAdapterDelegate(
nearbyButtonLayout.galleryButton.setOnClickListener { onGalleryClicked(item, galleryPickLauncherForResult) }
nearbyButtonLayout.galleryButton.setOnLongClickListener { onGalleryLongPressed() }
bookmarkButtonImage.setOnClickListener {
val isBookmarked = bookmarkLocationDao.updateBookmarkLocation(item)
var isBookmarked = false
scope?.launch {
isBookmarked = bookmarkLocationDao.updateBookmarkLocation(item)
}
bookmarkButtonImage.setImageResource(
if (isBookmarked) R.drawable.ic_round_star_filled_24px else R.drawable.ic_round_star_border_24px,
)
@ -93,13 +99,15 @@ fun placeAdapterDelegate(
GONE
}
bookmarkButtonImage.setImageResource(
if (bookmarkLocationDao.findBookmarkLocation(item)) {
R.drawable.ic_round_star_filled_24px
} else {
R.drawable.ic_round_star_border_24px
},
)
scope?.launch {
bookmarkButtonImage.setImageResource(
if (bookmarkLocationDao.findBookmarkLocation(item.name)) {
R.drawable.ic_round_star_filled_24px
} else {
R.drawable.ic_round_star_border_24px
},
)
}
}
nearbyButtonLayout.directionsButton.setOnLongClickListener { onDirectionsLongPressed() }
}

View file

@ -134,7 +134,7 @@ public interface NearbyParentFragmentContract {
void setAdvancedQuery(String query);
void toggleBookmarkedStatus(Place place);
void toggleBookmarkedStatus(Place place, LifecycleCoroutineScope scope);
void handleMapScrolled(LifecycleCoroutineScope scope, boolean isNetworkAvailable);
}

View file

@ -90,6 +90,7 @@ import fr.free.nrw.commons.nearby.MarkerPlaceGroup;
import fr.free.nrw.commons.nearby.NearbyController;
import fr.free.nrw.commons.nearby.NearbyFilterSearchRecyclerViewAdapter;
import fr.free.nrw.commons.nearby.NearbyFilterState;
import fr.free.nrw.commons.nearby.NearbyUtil;
import fr.free.nrw.commons.nearby.Place;
import fr.free.nrw.commons.nearby.PlacesRepository;
import fr.free.nrw.commons.nearby.Sitelinks;
@ -126,6 +127,7 @@ import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Named;
import kotlin.Unit;
import kotlinx.coroutines.BuildersKt;
import org.jetbrains.annotations.NotNull;
import org.osmdroid.api.IGeoPoint;
import org.osmdroid.events.MapEventsReceiver;
@ -577,13 +579,14 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
binding.bottomSheetNearby.rvNearbyList.setLayoutManager(
new LinearLayoutManager(getContext()));
adapter = new PlaceAdapter(bookmarkLocationDao,
scope,
place -> {
moveCameraToPosition(
new GeoPoint(place.location.getLatitude(), place.location.getLongitude()));
return Unit.INSTANCE;
},
(place, isBookmarked) -> {
presenter.toggleBookmarkedStatus(place);
presenter.toggleBookmarkedStatus(place, scope);
return Unit.INSTANCE;
},
commonPlaceClickActions,
@ -2135,7 +2138,11 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
private void updateBookmarkButtonImage(final Place place) {
final int bookmarkIcon;
if (bookmarkLocationDao.findBookmarkLocation(place)) {
if (NearbyUtil.INSTANCE.getBookmarkLocationExists(
bookmarkLocationDao,
place.getName(),
scope
)) {
bookmarkIcon = R.drawable.ic_round_star_filled_24px;
} else {
bookmarkIcon = R.drawable.ic_round_star_border_24px;
@ -2301,11 +2308,11 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
BottomSheetItem item = dataList.get(position);
switch (item.getImageResourceId()) {
case R.drawable.ic_round_star_border_24px:
presenter.toggleBookmarkedStatus(selectedPlace);
presenter.toggleBookmarkedStatus(selectedPlace, scope);
updateBookmarkButtonImage(selectedPlace);
break;
case R.drawable.ic_round_star_filled_24px:
presenter.toggleBookmarkedStatus(selectedPlace);
presenter.toggleBookmarkedStatus(selectedPlace, scope);
updateBookmarkButtonImage(selectedPlace);
break;
case R.drawable.ic_directions_black_24dp:

View file

@ -2,6 +2,7 @@ package fr.free.nrw.commons.nearby.fragments
import android.content.Intent
import androidx.activity.result.ActivityResultLauncher
import androidx.lifecycle.LifecycleCoroutineScope
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao
import fr.free.nrw.commons.nearby.Place
import fr.free.nrw.commons.nearby.placeAdapterDelegate
@ -9,6 +10,7 @@ import fr.free.nrw.commons.upload.categories.BaseDelegateAdapter
class PlaceAdapter(
bookmarkLocationsDao: BookmarkLocationsDao,
scope: LifecycleCoroutineScope? = null,
onPlaceClicked: ((Place) -> Unit)? = null,
onBookmarkClicked: (Place, Boolean) -> Unit,
commonPlaceClickActions: CommonPlaceClickActions,
@ -18,6 +20,7 @@ class PlaceAdapter(
) : BaseDelegateAdapter<Place>(
placeAdapterDelegate(
bookmarkLocationsDao,
scope,
onPlaceClicked,
commonPlaceClickActions.onCameraClicked(),
commonPlaceClickActions.onCameraLongPressed(),

View file

@ -26,6 +26,7 @@ import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.delay
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.lang.reflect.InvocationHandler
@ -133,16 +134,25 @@ class NearbyParentFragmentPresenter
* @param place The place whose bookmarked status is to be toggled. If the place is `null`,
* the operation is skipped.
*/
override fun toggleBookmarkedStatus(place: Place?) {
override fun toggleBookmarkedStatus(
place: Place?,
scope: LifecycleCoroutineScope?
) {
if (place == null) return
val nowBookmarked = bookmarkLocationDao.updateBookmarkLocation(place)
var nowBookmarked: Boolean? = null
scope?.launch {
nowBookmarked = bookmarkLocationDao.updateBookmarkLocation(place)
}
bookmarkChangedPlaces.add(place)
val placeIndex =
NearbyController.markerLabelList.indexOfFirst { it.place.location == place.location }
NearbyController.markerLabelList[placeIndex] = MarkerPlaceGroup(
nowBookmarked,
NearbyController.markerLabelList[placeIndex].place
)
NearbyController.markerLabelList[placeIndex] = nowBookmarked?.let {
MarkerPlaceGroup(
it,
NearbyController.markerLabelList[placeIndex].place
)
}
nearbyParentFragmentView.setFilterState()
}
@ -334,7 +344,7 @@ class NearbyParentFragmentPresenter
for (i in 0..updatedGroups.lastIndex) {
val repoPlace = placesRepository.fetchPlace(updatedGroups[i].place.entityID)
if (repoPlace != null && repoPlace.name != null && repoPlace.name != ""){
updatedGroups[i].isBookmarked = bookmarkLocationDao.findBookmarkLocation(repoPlace)
updatedGroups[i].isBookmarked = bookmarkLocationDao.findBookmarkLocation(repoPlace.name)
updatedGroups[i].place.apply {
name = repoPlace.name
isMonument = repoPlace.isMonument
@ -372,7 +382,7 @@ class NearbyParentFragmentPresenter
collectResults.send(
fetchedPlaces.mapIndexed { index, place ->
Pair(indices[index], MarkerPlaceGroup(
bookmarkLocationDao.findBookmarkLocation(place),
bookmarkLocationDao.findBookmarkLocation(place.name),
place
))
}
@ -435,7 +445,7 @@ class NearbyParentFragmentPresenter
if (bookmarkChangedPlacesBacklog.containsKey(group.place.location)) {
updatedGroups[index] = MarkerPlaceGroup(
bookmarkLocationDao
.findBookmarkLocation(updatedGroups[index].place),
.findBookmarkLocation(updatedGroups[index].place.name),
updatedGroups[index].place
)
}
@ -545,7 +555,7 @@ class NearbyParentFragmentPresenter
).sortedBy { it.getDistanceInDouble(mapFocus) }.take(NearbyController.MAX_RESULTS)
.map {
MarkerPlaceGroup(
bookmarkLocationDao.findBookmarkLocation(it), it
bookmarkLocationDao.findBookmarkLocation(it.name), it
)
}
ensureActive()