Migrated items and locations in bookmark to kotlin

This commit is contained in:
Saifuddin 2024-12-13 23:33:22 +05:30
parent 5a2d5f2aab
commit 2b8dbf090f
8 changed files with 725 additions and 762 deletions

View file

@ -1,129 +1,137 @@
package fr.free.nrw.commons.bookmarks.items; package fr.free.nrw.commons.bookmarks.items
import static fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao.Table.COLUMN_ID; import android.content.ContentValues
import static fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao.Table.TABLE_NAME; 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.items.BookmarkItemsDao.Table.COLUMN_ID
import fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao.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
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.data.DBOpenHelper;
import fr.free.nrw.commons.di.CommonsDaggerContentProvider;
import javax.inject.Inject;
import timber.log.Timber;
/** /**
* Handles private storage for bookmarked items * Handles private storage for bookmarked items
*/ */
public class BookmarkItemsContentProvider extends CommonsDaggerContentProvider { class BookmarkItemsContentProvider : CommonsDaggerContentProvider() {
private static final String BASE_PATH = "bookmarksItems";
public static final Uri BASE_URI =
Uri.parse("content://" + BuildConfig.BOOKMARK_ITEMS_AUTHORITY + "/" + BASE_PATH);
companion object {
private const val BASE_PATH = "bookmarksItems"
val BASE_URI: Uri = Uri
.parse("content://${BuildConfig.BOOKMARK_ITEMS_AUTHORITY}/$BASE_PATH")
/** /**
* Append bookmark items ID to the base uri * Append bookmark items ID to the base URI
*/ */
public static Uri uriForName(final String id) { fun uriForName(id: String): Uri {
return Uri.parse(BASE_URI + "/" + id); return Uri.parse("$BASE_URI/$id")
}
} }
@Inject @Inject
DBOpenHelper dbOpenHelper; lateinit var dbOpenHelper: DBOpenHelper
@Override override fun getType(uri: Uri): String? {
public String getType(@NonNull final Uri uri) { return null
return null;
} }
/** /**
* Queries the SQLite database for the bookmark items * Queries the SQLite database for the bookmark items
* @param uri : contains the uri for bookmark items * @param uri : contains the URI for bookmark items
* @param projection : contains the all fields of the table * @param projection : contains the all fields of the table
* @param selection : handles Where * @param selection : handles Where
* @param selectionArgs : the condition of Where clause * @param selectionArgs : the condition of Where clause
* @param sortOrder : ascending or descending * @param sortOrder : ascending or descending
*/ */
@SuppressWarnings("ConstantConditions") override fun query(
@Override uri: Uri,
public Cursor query(@NonNull final Uri uri, final String[] projection, final String selection, projection: Array<out String>?,
final String[] selectionArgs, final String sortOrder) { selection: String?,
final SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); selectionArgs: Array<out String>?,
queryBuilder.setTables(TABLE_NAME); sortOrder: String?
final SQLiteDatabase db = dbOpenHelper.getReadableDatabase(); ): Cursor {
final Cursor cursor = queryBuilder.query(db, projection, selection, val queryBuilder = SQLiteQueryBuilder().apply {
selectionArgs, null, null, sortOrder); tables = TABLE_NAME
cursor.setNotificationUri(getContext().getContentResolver(), uri); }
return cursor; 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 * Handles the update query of local SQLite Database
* @param uri : contains the uri for bookmark items * @param uri : contains the URI for bookmark items
* @param contentValues : new values to be entered to db * @param contentValues : new values to be entered to DB
* @param selection : handles Where * @param selection : handles Where
* @param selectionArgs : the condition of Where clause * @param selectionArgs : the condition of Where clause
*/ */
@SuppressWarnings("ConstantConditions") override fun update(
@Override uri: Uri,
public int update(@NonNull final Uri uri, final ContentValues contentValues, contentValues: ContentValues?,
final String selection, final String[] selectionArgs) { selection: String?,
final SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase(); selectionArgs: Array<out String>?
final int rowsUpdated; ): Int {
if (TextUtils.isEmpty(selection)) { val sqlDB = dbOpenHelper.writableDatabase
final int id = Integer.parseInt(uri.getLastPathSegment()); val rowsUpdated: Int
rowsUpdated = sqlDB.update(TABLE_NAME,
if (selection.isNullOrEmpty()) {
val id = uri.lastPathSegment?.toIntOrNull()
?: throw IllegalArgumentException("Invalid ID in URI: $uri")
rowsUpdated = sqlDB.update(
TABLE_NAME,
contentValues, contentValues,
COLUMN_ID + " = ?", "$COLUMN_ID = ?",
new String[]{String.valueOf(id)}); arrayOf(id.toString())
)
} else { } else {
throw new IllegalArgumentException( throw IllegalArgumentException(
"Parameter `selection` should be empty when updating an ID"); "Parameter `selection` should be empty when updating an ID"
)
} }
getContext().getContentResolver().notifyChange(uri, null); context?.contentResolver?.notifyChange(uri, null)
return rowsUpdated; return rowsUpdated
} }
/** /**
* Handles the insertion of new bookmark items record to local SQLite Database * Handles the insertion of new bookmark items record to local SQLite Database
* @param uri * @param uri : contains the URI for bookmark items
* @param contentValues * @param contentValues : values to be inserted
* @return
*/ */
@SuppressWarnings("ConstantConditions") override fun insert(uri: Uri, contentValues: ContentValues?): Uri? {
@Override val sqlDB = dbOpenHelper.writableDatabase
public Uri insert(@NonNull final Uri uri, final ContentValues contentValues) { val id = sqlDB.insert(TABLE_NAME, null, contentValues)
final SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase(); context?.contentResolver?.notifyChange(uri, null)
final long id = sqlDB.insert(TABLE_NAME, null, contentValues); return Uri.parse("$BASE_URI/$id")
getContext().getContentResolver().notifyChange(uri, null);
return Uri.parse(BASE_URI + "/" + id);
} }
/** /**
* Handles the deletion of new bookmark items record to local SQLite Database * Handles the deletion of bookmark items record in the local SQLite Database
* @param uri * @param uri : contains the URI for bookmark items
* @param s * @param selection : unused parameter
* @param strings * @param selectionArgs : unused parameter
* @return
*/ */
@SuppressWarnings("ConstantConditions") override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {
@Override val db = dbOpenHelper.readableDatabase
public int delete(@NonNull final Uri uri, final String s, final String[] strings) { Timber.d("Deleting bookmark name %s", uri.lastPathSegment)
final int rows; val rows = db.delete(
final SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
Timber.d("Deleting bookmark name %s", uri.getLastPathSegment());
rows = db.delete(
TABLE_NAME, TABLE_NAME,
"item_id = ?", "$COLUMN_ID = ?",
new String[]{uri.getLastPathSegment()} arrayOf(uri.lastPathSegment)
); )
getContext().getContentResolver().notifyChange(uri, null); context?.contentResolver?.notifyChange(uri, null)
return rows; return rows
} }
} }

View file

@ -1,27 +1,22 @@
package fr.free.nrw.commons.bookmarks.items; package fr.free.nrw.commons.bookmarks.items
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem; import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
import java.util.List; import javax.inject.Inject
import javax.inject.Inject; import javax.inject.Singleton
import javax.inject.Singleton;
/** /**
* Handles loading bookmarked items from Database * Handles loading bookmarked items from Database
*/ */
@Singleton @Singleton
public class BookmarkItemsController { class BookmarkItemsController @Inject constructor(
private val bookmarkItemsDao: BookmarkItemsDao
@Inject ) {
BookmarkItemsDao bookmarkItemsDao;
@Inject
public BookmarkItemsController() {}
/** /**
* Load from DB the bookmarked items * Load from DB the bookmarked items
* @return a list of DepictedItem objects. * @return a list of DepictedItem objects.
*/ */
public List<DepictedItem> loadFavoritesItems() { fun loadFavoritesItems(): List<DepictedItem> {
return bookmarkItemsDao.getAllBookmarksItems(); return bookmarkItemsDao.getAllBookmarksItems()
} }
} }

View file

@ -1,89 +1,81 @@
package fr.free.nrw.commons.bookmarks.items; package fr.free.nrw.commons.bookmarks.items
import android.annotation.SuppressLint
import android.content.ContentProviderClient
import android.content.ContentValues
import android.database.Cursor
import android.database.sqlite.SQLiteDatabase
import android.os.RemoteException
import fr.free.nrw.commons.category.CategoryItem
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
import javax.inject.Inject
import javax.inject.Named
import javax.inject.Provider
import javax.inject.Singleton
import android.annotation.SuppressLint;
import android.content.ContentProviderClient;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.RemoteException;
import fr.free.nrw.commons.category.CategoryItem;
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Provider;
import javax.inject.Singleton;
import org.apache.commons.lang3.StringUtils;
/** /**
* Handles database operations for bookmarked items * Handles database operations for bookmarked items
*/ */
@Singleton @Singleton
public class BookmarkItemsDao { class BookmarkItemsDao @Inject constructor(
@Named("bookmarksItem") private val clientProvider: Provider<ContentProviderClient>
private final Provider<ContentProviderClient> clientProvider; ) {
@Inject
public BookmarkItemsDao(
@Named("bookmarksItem") final Provider<ContentProviderClient> clientProvider) {
this.clientProvider = clientProvider;
}
/** /**
* Find all persisted items bookmarks on database * Find all persisted items bookmarks on database
* @return list of bookmarks * @return list of bookmarks
*/ */
public List<DepictedItem> getAllBookmarksItems() { fun getAllBookmarksItems(): List<DepictedItem> {
final List<DepictedItem> items = new ArrayList<>(); val items = mutableListOf<DepictedItem>()
final ContentProviderClient db = clientProvider.get(); val db = clientProvider.get()
try (final Cursor cursor = db.query( try {
db.query(
BookmarkItemsContentProvider.BASE_URI, BookmarkItemsContentProvider.BASE_URI,
Table.ALL_FIELDS, Table.ALL_FIELDS,
null, null,
new String[]{}, emptyArray(),
null)) { null
while (cursor != null && cursor.moveToNext()) { )?.use { cursor ->
items.add(fromCursor(cursor)); while (cursor.moveToNext()) {
items.add(fromCursor(cursor))
} }
} catch (final RemoteException e) { }
throw new RuntimeException(e); } catch (e: RemoteException) {
throw RuntimeException(e)
} finally { } finally {
db.release(); db.close()
} }
return items; return items
} }
/** /**
* Look for a bookmark in database and in order to insert or delete it * Look for a bookmark in database and in order to insert or delete it
* @param depictedItem : Bookmark object * @param depictedItem : Bookmark object
* @return boolean : is bookmark now favorite ? * @return boolean : is bookmark now favorite?
*/ */
public boolean updateBookmarkItem(final DepictedItem depictedItem) { fun updateBookmarkItem(depictedItem: DepictedItem): Boolean {
final boolean bookmarkExists = findBookmarkItem(depictedItem.getId()); val bookmarkExists = findBookmarkItem(depictedItem.id)
if (bookmarkExists) { if (bookmarkExists) {
deleteBookmarkItem(depictedItem); deleteBookmarkItem(depictedItem)
} else { } else {
addBookmarkItem(depictedItem); addBookmarkItem(depictedItem)
} }
return !bookmarkExists; return !bookmarkExists
} }
/** /**
* Add a Bookmark to database * Add a Bookmark to database
* @param depictedItem : Bookmark to add * @param depictedItem : Bookmark to add
*/ */
private void addBookmarkItem(final DepictedItem depictedItem) { private fun addBookmarkItem(depictedItem: DepictedItem) {
final ContentProviderClient db = clientProvider.get(); val db = clientProvider.get()
try { try {
db.insert(BookmarkItemsContentProvider.BASE_URI, toContentValues(depictedItem)); db.insert(BookmarkItemsContentProvider.BASE_URI, toContentValues(depictedItem))
} catch (final RemoteException e) { } catch (e: RemoteException) {
throw new RuntimeException(e); throw RuntimeException(e)
} finally { } finally {
db.release(); db.close()
} }
} }
@ -91,76 +83,75 @@ public class BookmarkItemsDao {
* Delete a bookmark from database * Delete a bookmark from database
* @param depictedItem : Bookmark to delete * @param depictedItem : Bookmark to delete
*/ */
private void deleteBookmarkItem(final DepictedItem depictedItem) { private fun deleteBookmarkItem(depictedItem: DepictedItem) {
final ContentProviderClient db = clientProvider.get(); val db = clientProvider.get()
try { try {
db.delete(BookmarkItemsContentProvider.uriForName(depictedItem.getId()), null, null); db.delete(
} catch (final RemoteException e) { BookmarkItemsContentProvider.uriForName(depictedItem.id),
throw new RuntimeException(e); null,
null
)
} catch (e: RemoteException) {
throw RuntimeException(e)
} finally { } finally {
db.release(); db.close()
} }
} }
/** /**
* Find a bookmark from database based on its name * Find a bookmark from database based on its name
* @param depictedItemID : Bookmark to find * @param depictedItemID : Bookmark to find
* @return boolean : is bookmark in database ? * @return boolean : is bookmark in database?
*/ */
public boolean findBookmarkItem(final String depictedItemID) { fun findBookmarkItem(depictedItemID: String?): Boolean {
if (depictedItemID == null) { //Avoiding NPE's if (depictedItemID == null) return false // Avoiding NPEs
return false; val db = clientProvider.get()
} try {
final ContentProviderClient db = clientProvider.get(); db.query(
try (final Cursor cursor = db.query(
BookmarkItemsContentProvider.BASE_URI, BookmarkItemsContentProvider.BASE_URI,
Table.ALL_FIELDS, Table.ALL_FIELDS,
Table.COLUMN_ID + "=?", "${Table.COLUMN_ID}=?",
new String[]{depictedItemID}, arrayOf(depictedItemID),
null null
)) { )?.use { cursor ->
if (cursor != null && cursor.moveToFirst()) { if (cursor.moveToFirst()) return true
return true;
} }
} catch (final RemoteException e) { } catch (e: RemoteException) {
throw new RuntimeException(e); throw RuntimeException(e)
} finally { } finally {
db.release(); db.close()
} }
return false; return false
} }
/** /**
* Recives real data from cursor * Receives real data from cursor
* @param cursor : Object for storing database data * @param cursor : Object for storing database data
* @return DepictedItem * @return DepictedItem
*/ */
@SuppressLint("Range") @SuppressLint("Range")
DepictedItem fromCursor(final Cursor cursor) { fun fromCursor(cursor: Cursor): DepictedItem {
final String fileName = cursor.getString(cursor.getColumnIndex(Table.COLUMN_NAME)); val fileName = cursor.getString(cursor.getColumnIndex(Table.COLUMN_NAME))
final String description val description = cursor.getString(cursor.getColumnIndex(Table.COLUMN_DESCRIPTION))
= cursor.getString(cursor.getColumnIndex(Table.COLUMN_DESCRIPTION)); val imageUrl = cursor.getString(cursor.getColumnIndex(Table.COLUMN_IMAGE))
final String imageUrl = cursor.getString(cursor.getColumnIndex(Table.COLUMN_IMAGE)); val instanceListString = cursor.getString(cursor.getColumnIndex(Table.COLUMN_INSTANCE_LIST))
final String instanceListString val instanceList = stringToArray(instanceListString)
= cursor.getString(cursor.getColumnIndex(Table.COLUMN_INSTANCE_LIST)); val categoryNameListString =
final List<String> instanceList = StringToArray(instanceListString); cursor.getString(cursor.getColumnIndex(Table.COLUMN_CATEGORIES_NAME_LIST))
final String categoryNameListString = cursor.getString(cursor val categoryNameList = stringToArray(categoryNameListString)
.getColumnIndex(Table.COLUMN_CATEGORIES_NAME_LIST)); val categoryDescriptionListString =
final List<String> categoryNameList = StringToArray(categoryNameListString); cursor.getString(cursor.getColumnIndex(Table.COLUMN_CATEGORIES_DESCRIPTION_LIST))
final String categoryDescriptionListString = cursor.getString(cursor val categoryDescriptionList = stringToArray(categoryDescriptionListString)
.getColumnIndex(Table.COLUMN_CATEGORIES_DESCRIPTION_LIST)); val categoryThumbnailListString =
final List<String> categoryDescriptionList = StringToArray(categoryDescriptionListString); cursor.getString(cursor.getColumnIndex(Table.COLUMN_CATEGORIES_THUMBNAIL_LIST))
final String categoryThumbnailListString = cursor.getString(cursor val categoryThumbnailList = stringToArray(categoryThumbnailListString)
.getColumnIndex(Table.COLUMN_CATEGORIES_THUMBNAIL_LIST)); val categoryList = convertToCategoryItems(
final List<String> categoryThumbnailList = StringToArray(categoryThumbnailListString); categoryNameList, categoryDescriptionList, categoryThumbnailList
final List<CategoryItem> categoryList = convertToCategoryItems(categoryNameList, )
categoryDescriptionList, categoryThumbnailList); val isSelected = cursor.getString(cursor.getColumnIndex(Table.COLUMN_IS_SELECTED)).toBoolean()
final boolean isSelected val id = cursor.getString(cursor.getColumnIndex(Table.COLUMN_ID))
= Boolean.parseBoolean(cursor.getString(cursor
.getColumnIndex(Table.COLUMN_IS_SELECTED)));
final String id = cursor.getString(cursor.getColumnIndex(Table.COLUMN_ID));
return new DepictedItem( return DepictedItem(
fileName, fileName,
description, description,
imageUrl, imageUrl,
@ -168,40 +159,40 @@ public class BookmarkItemsDao {
categoryList, categoryList,
isSelected, isSelected,
id id
); )
} }
private List<CategoryItem> convertToCategoryItems(List<String> categoryNameList, private fun convertToCategoryItems(
List<String> categoryDescriptionList, List<String> categoryThumbnailList) { categoryNameList: List<String>,
List<CategoryItem> categoryItems = new ArrayList<>(); categoryDescriptionList: List<String>,
for(int i=0; i<categoryNameList.size(); i++){ categoryThumbnailList: List<String>
categoryItems.add(new CategoryItem(categoryNameList.get(i), ): List<CategoryItem> {
categoryDescriptionList.get(i), return categoryNameList.mapIndexed { index, name ->
categoryThumbnailList.get(i), false)); CategoryItem(
name,
categoryDescriptionList.getOrNull(index),
categoryThumbnailList.getOrNull(index),
false
)
} }
return categoryItems;
} }
/** /**
* Converts string to List * Converts string to List
* @param listString comma separated single string from of list items * @param listString comma separated single string from list items
* @return List of string * @return List of string
*/ */
private List<String> StringToArray(final String listString) { private fun stringToArray(listString: String): List<String> {
final String[] elements = listString.split(","); return listString.split(",")
return Arrays.asList(elements);
} }
/** /**
* Converts string to List * Converts list to string
* @param list list of items * @param list list of items
* @return string comma separated single string of items * @return string comma separated single string of items
*/ */
private String ArrayToString(final List<String> list) { private fun arrayToString(list: List<String?>): String {
if (list != null) { return list.joinToString(",")
return StringUtils.join(list, ',');
}
return null;
} }
/** /**
@ -209,57 +200,46 @@ public class BookmarkItemsDao {
* @param depictedItem depicted item * @param depictedItem depicted item
* @return ContentValues * @return ContentValues
*/ */
private ContentValues toContentValues(final DepictedItem depictedItem) { private fun toContentValues(depictedItem: DepictedItem): ContentValues {
val namesOfCommonsCategories = depictedItem.commonsCategories.map { it.name }
val descriptionsOfCommonsCategories = depictedItem.commonsCategories.map { it.description }
val thumbnailsOfCommonsCategories = depictedItem.commonsCategories.map { it.thumbnail }
final List<String> namesOfCommonsCategories = new ArrayList<>(); return ContentValues().apply {
for (final CategoryItem category : put(Table.COLUMN_NAME, depictedItem.name)
depictedItem.getCommonsCategories()) { put(Table.COLUMN_DESCRIPTION, depictedItem.description)
namesOfCommonsCategories.add(category.getName()); put(Table.COLUMN_IMAGE, depictedItem.imageUrl)
put(Table.COLUMN_INSTANCE_LIST, arrayToString(depictedItem.instanceOfs))
put(Table.COLUMN_CATEGORIES_NAME_LIST, arrayToString(namesOfCommonsCategories))
put(
Table.COLUMN_CATEGORIES_DESCRIPTION_LIST,
arrayToString(descriptionsOfCommonsCategories)
)
put(
Table.COLUMN_CATEGORIES_THUMBNAIL_LIST,
arrayToString(thumbnailsOfCommonsCategories)
)
put(Table.COLUMN_IS_SELECTED, depictedItem.isSelected.toString())
put(Table.COLUMN_ID, depictedItem.id)
} }
final List<String> descriptionsOfCommonsCategories = new ArrayList<>();
for (final CategoryItem category :
depictedItem.getCommonsCategories()) {
descriptionsOfCommonsCategories.add(category.getDescription());
}
final List<String> thumbnailsOfCommonsCategories = new ArrayList<>();
for (final CategoryItem category :
depictedItem.getCommonsCategories()) {
thumbnailsOfCommonsCategories.add(category.getThumbnail());
}
final ContentValues cv = new ContentValues();
cv.put(Table.COLUMN_NAME, depictedItem.getName());
cv.put(Table.COLUMN_DESCRIPTION, depictedItem.getDescription());
cv.put(Table.COLUMN_IMAGE, depictedItem.getImageUrl());
cv.put(Table.COLUMN_INSTANCE_LIST, ArrayToString(depictedItem.getInstanceOfs()));
cv.put(Table.COLUMN_CATEGORIES_NAME_LIST, ArrayToString(namesOfCommonsCategories));
cv.put(Table.COLUMN_CATEGORIES_DESCRIPTION_LIST,
ArrayToString(descriptionsOfCommonsCategories));
cv.put(Table.COLUMN_CATEGORIES_THUMBNAIL_LIST,
ArrayToString(thumbnailsOfCommonsCategories));
cv.put(Table.COLUMN_IS_SELECTED, depictedItem.isSelected());
cv.put(Table.COLUMN_ID, depictedItem.getId());
return cv;
} }
/** /**
* Table of bookmarksItems data * Table of bookmarksItems data
*/ */
public static final class Table { object Table {
public static final String TABLE_NAME = "bookmarksItems"; const val TABLE_NAME = "bookmarksItems"
public static final String COLUMN_NAME = "item_name"; const val COLUMN_NAME = "item_name"
public static final String COLUMN_DESCRIPTION = "item_description"; const val COLUMN_DESCRIPTION = "item_description"
public static final String COLUMN_IMAGE = "item_image_url"; const val COLUMN_IMAGE = "item_image_url"
public static final String COLUMN_INSTANCE_LIST = "item_instance_of"; const val COLUMN_INSTANCE_LIST = "item_instance_of"
public static final String COLUMN_CATEGORIES_NAME_LIST = "item_name_categories"; const val COLUMN_CATEGORIES_NAME_LIST = "item_name_categories"
public static final String COLUMN_CATEGORIES_DESCRIPTION_LIST = "item_description_categories"; const val COLUMN_CATEGORIES_DESCRIPTION_LIST = "item_description_categories"
public static final String COLUMN_CATEGORIES_THUMBNAIL_LIST = "item_thumbnail_categories"; const val COLUMN_CATEGORIES_THUMBNAIL_LIST = "item_thumbnail_categories"
public static final String COLUMN_IS_SELECTED = "item_is_selected"; const val COLUMN_IS_SELECTED = "item_is_selected"
public static final String COLUMN_ID = "item_id"; const val COLUMN_ID = "item_id"
public static final String[] ALL_FIELDS = { val ALL_FIELDS = arrayOf(
COLUMN_NAME, COLUMN_NAME,
COLUMN_DESCRIPTION, COLUMN_DESCRIPTION,
COLUMN_IMAGE, COLUMN_IMAGE,
@ -269,60 +249,55 @@ public class BookmarkItemsDao {
COLUMN_CATEGORIES_THUMBNAIL_LIST, COLUMN_CATEGORIES_THUMBNAIL_LIST,
COLUMN_IS_SELECTED, COLUMN_IS_SELECTED,
COLUMN_ID COLUMN_ID
}; )
static final String DROP_TABLE_STATEMENT = "DROP TABLE IF EXISTS " + TABLE_NAME; private const val DROP_TABLE_STATEMENT = "DROP TABLE IF EXISTS $TABLE_NAME"
static final String CREATE_TABLE_STATEMENT = "CREATE TABLE " + TABLE_NAME + " (" private const val CREATE_TABLE_STATEMENT = """
+ COLUMN_NAME + " STRING," CREATE TABLE $TABLE_NAME (
+ COLUMN_DESCRIPTION + " STRING," $COLUMN_NAME STRING,
+ COLUMN_IMAGE + " STRING," $COLUMN_DESCRIPTION STRING,
+ COLUMN_INSTANCE_LIST + " STRING," $COLUMN_IMAGE STRING,
+ COLUMN_CATEGORIES_NAME_LIST + " STRING," $COLUMN_INSTANCE_LIST STRING,
+ COLUMN_CATEGORIES_DESCRIPTION_LIST + " STRING," $COLUMN_CATEGORIES_NAME_LIST STRING,
+ COLUMN_CATEGORIES_THUMBNAIL_LIST + " STRING," $COLUMN_CATEGORIES_DESCRIPTION_LIST STRING,
+ COLUMN_IS_SELECTED + " STRING," $COLUMN_CATEGORIES_THUMBNAIL_LIST STRING,
+ COLUMN_ID + " STRING PRIMARY KEY" $COLUMN_IS_SELECTED STRING,
+ ");"; $COLUMN_ID STRING PRIMARY KEY
);
"""
/** /**
* Creates table * Creates table
* @param db SQLiteDatabase * @param db SQLiteDatabase
*/ */
public static void onCreate(final SQLiteDatabase db) { fun onCreate(db: SQLiteDatabase) {
db.execSQL(CREATE_TABLE_STATEMENT); db.execSQL(CREATE_TABLE_STATEMENT)
} }
/** /**
* Deletes database * Deletes database
* @param db SQLiteDatabase * @param db SQLiteDatabase
*/ */
public static void onDelete(final SQLiteDatabase db) { fun onDelete(db: SQLiteDatabase) {
db.execSQL(DROP_TABLE_STATEMENT); db.execSQL(DROP_TABLE_STATEMENT)
onCreate(db); onCreate(db)
} }
/** /**
* Updates database * Updates database
* @param db SQLiteDatabase * @param db SQLiteDatabase
* @param from starting * @param from starting version
* @param to end * @param to end version
*/ */
public static void onUpdate(final SQLiteDatabase db, int from, final int to) { fun onUpdate(db: SQLiteDatabase, from: Int, to: Int) {
if (from == to) { if (from == to) return
return;
}
if (from < 18) { if (from < 18) {
// doesn't exist yet onUpdate(db, from + 1, to)
from++; return
onUpdate(db, from, to);
return;
} }
if (from == 18) { if (from == 18) {
// table added in version 19 onCreate(db)
onCreate(db); onUpdate(db, from + 1, to)
from++;
onUpdate(db, from, to);
} }
} }
} }

View file

@ -1,81 +1,72 @@
package fr.free.nrw.commons.bookmarks.items; package fr.free.nrw.commons.bookmarks.items
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import dagger.android.support.DaggerFragment
import fr.free.nrw.commons.R
import fr.free.nrw.commons.databinding.FragmentBookmarksItemsBinding
import javax.inject.Inject
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import dagger.android.support.DaggerFragment;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.databinding.FragmentBookmarksItemsBinding;
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
import java.util.List;
import javax.inject.Inject;
import org.jetbrains.annotations.NotNull;
/** /**
* Tab fragment to show list of bookmarked Wikidata Items * Tab fragment to show list of bookmarked Wikidata Items
*/ */
public class BookmarkItemsFragment extends DaggerFragment { class BookmarkItemsFragment : DaggerFragment() {
private FragmentBookmarksItemsBinding binding; private var binding: FragmentBookmarksItemsBinding? = null
@Inject @Inject
BookmarkItemsController controller; lateinit var controller: BookmarkItemsController
public static BookmarkItemsFragment newInstance() { companion object {
return new BookmarkItemsFragment(); fun newInstance(): BookmarkItemsFragment {
return BookmarkItemsFragment()
}
} }
@Override override fun onCreateView(
public View onCreateView( inflater: LayoutInflater,
@NonNull final LayoutInflater inflater, container: ViewGroup?,
final ViewGroup container, savedInstanceState: Bundle?
final Bundle savedInstanceState ): View? {
) { binding = FragmentBookmarksItemsBinding.inflate(inflater, container, false)
binding = FragmentBookmarksItemsBinding.inflate(inflater, container, false); return binding?.root
return binding.getRoot();
} }
@Override override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
public void onViewCreated(final @NotNull View view, @Nullable final Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState)
super.onViewCreated(view, savedInstanceState); initList(requireContext())
initList(requireContext());
} }
@Override override fun onResume() {
public void onResume() { super.onResume()
super.onResume(); initList(requireContext())
initList(requireContext());
} }
/** /**
* Get list of DepictedItem and sets to the adapter * Get list of DepictedItem and sets to the adapter
* @param context context * @param context context
*/ */
private void initList(final Context context) { private fun initList(context: Context) {
final List<DepictedItem> depictItems = controller.loadFavoritesItems(); val depictItems = controller.loadFavoritesItems()
final BookmarkItemsAdapter adapter = new BookmarkItemsAdapter(depictItems, context); val adapter = BookmarkItemsAdapter(depictItems, context)
binding.listView.setAdapter(adapter); binding?.apply {
binding.loadingImagesProgressBar.setVisibility(View.GONE); listView.adapter = adapter
loadingImagesProgressBar.visibility = View.GONE
if (depictItems.isEmpty()) { if (depictItems.isEmpty()) {
binding.statusMessage.setText(R.string.bookmark_empty); statusMessage.setText(R.string.bookmark_empty)
binding.statusMessage.setVisibility(View.VISIBLE); statusMessage.visibility = View.VISIBLE
} else { } else {
binding.statusMessage.setVisibility(View.GONE); statusMessage.visibility = View.GONE
}
} }
} }
@Override override fun onDestroy() {
public void onDestroy() { super.onDestroy()
super.onDestroy(); binding = null
binding = null;
} }
} }

View file

@ -1,119 +1,128 @@
package fr.free.nrw.commons.bookmarks.locations; package fr.free.nrw.commons.bookmarks.locations
import android.content.ContentValues
import android.database.Cursor
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteQueryBuilder // We can get uri using java.Net.Uri, but andoid implimentation is faster (but it's forgiving with handling exceptions though)
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
// We can get uri using java.Net.Uri, but andoid implimentation is faster (but it's forgiving with handling exceptions though) // We can get uri using java.Net.Uri, but andoid implimentation is faster (but it's forgiving with handling exceptions though)
import android.net.Uri; import android.net.Uri
import android.text.TextUtils; import android.text.TextUtils
import androidx.annotation.NonNull; import androidx.annotation.NonNull
import javax.inject.Inject; import javax.inject.Inject
import fr.free.nrw.commons.BuildConfig; import fr.free.nrw.commons.BuildConfig
import fr.free.nrw.commons.data.DBOpenHelper; import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao.Table.COLUMN_NAME
import fr.free.nrw.commons.di.CommonsDaggerContentProvider; import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao.Table.TABLE_NAME
import timber.log.Timber; import fr.free.nrw.commons.data.DBOpenHelper
import fr.free.nrw.commons.di.CommonsDaggerContentProvider
import timber.log.Timber
import static fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao.Table.COLUMN_NAME;
import static fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao.Table.TABLE_NAME;
/** /**
* Handles private storage for Bookmark locations * Handles private storage for Bookmark locations
*/ */
public class BookmarkLocationsContentProvider extends CommonsDaggerContentProvider { class BookmarkLocationsContentProvider : CommonsDaggerContentProvider() {
private static final String BASE_PATH = "bookmarksLocations"; companion object {
public static final Uri BASE_URI = Uri.parse("content://" + BuildConfig.BOOKMARK_LOCATIONS_AUTHORITY + "/" + BASE_PATH); 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 * Append bookmark locations name to the base URI
*/ */
public static Uri uriForName(String name) { fun uriForName(name: String): Uri {
return Uri.parse(BASE_URI.toString() + "/" + name); return Uri.parse("$BASE_URI/$name")
}
} }
@Inject DBOpenHelper dbOpenHelper; @Inject
lateinit var dbOpenHelper: DBOpenHelper
@Override override fun getType(uri: Uri): String? {
public String getType(@NonNull Uri uri) { return null
return null;
} }
/** /**
* Queries the SQLite database for the bookmark locations * Queries the SQLite database for the bookmark locations
* @param uri : contains the uri for bookmark locations * @param uri : contains the URI for bookmark locations
* @param projection * @param projection
* @param selection : handles Where * @param selection : handles Where
* @param selectionArgs : the condition of Where clause * @param selectionArgs : the condition of Where clause
* @param sortOrder : ascending or descending * @param sortOrder : ascending or descending
*/ */
@SuppressWarnings("ConstantConditions") override fun query(
@Override uri: Uri,
public Cursor query(@NonNull Uri uri, String[] projection, String selection, projection: Array<String>?,
String[] selectionArgs, String sortOrder) { selection: String?,
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); selectionArgs: Array<String>?,
queryBuilder.setTables(TABLE_NAME); sortOrder: String?
): Cursor? {
val queryBuilder = SQLiteQueryBuilder().apply {
tables = TABLE_NAME
}
SQLiteDatabase db = dbOpenHelper.getReadableDatabase(); val db = dbOpenHelper.readableDatabase
Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder); val cursor = queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder)
cursor.setNotificationUri(getContext().getContentResolver(), uri); cursor.setNotificationUri(context?.contentResolver, uri)
return cursor; return cursor
} }
/** /**
* Handles the update query of local SQLite Database * Handles the update query of local SQLite Database
* @param uri : contains the uri for bookmark locations * @param uri : contains the URI for bookmark locations
* @param contentValues : new values to be entered to db * @param contentValues : new values to be entered to DB
* @param selection : handles Where * @param selection : handles Where
* @param selectionArgs : the condition of Where clause * @param selectionArgs : the condition of Where clause
*/ */
@SuppressWarnings("ConstantConditions") override fun update(
@Override uri: Uri,
public int update(@NonNull Uri uri, ContentValues contentValues, String selection, contentValues: ContentValues?,
String[] selectionArgs) { selection: String?,
SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase(); selectionArgs: Array<String>?
int rowsUpdated; ): Int {
if (TextUtils.isEmpty(selection)) { val sqlDB = dbOpenHelper.writableDatabase
int id = Integer.valueOf(uri.getLastPathSegment()); val rowsUpdated: Int
rowsUpdated = sqlDB.update(TABLE_NAME, if (selection.isNullOrEmpty()) {
val id = uri.lastPathSegment?.toInt() ?: throw IllegalArgumentException("Invalid ID in URI")
rowsUpdated = sqlDB.update(
TABLE_NAME,
contentValues, contentValues,
COLUMN_NAME + " = ?", "$COLUMN_NAME = ?",
new String[]{String.valueOf(id)}); arrayOf(id.toString())
)
} else { } else {
throw new IllegalArgumentException( throw IllegalArgumentException("Parameter `selection` should be empty when updating an ID")
"Parameter `selection` should be empty when updating an ID");
} }
getContext().getContentResolver().notifyChange(uri, null); context?.contentResolver?.notifyChange(uri, null)
return rowsUpdated; return rowsUpdated
} }
/** /**
* Handles the insertion of new bookmark locations record to local SQLite Database * Handles the insertion of new bookmark locations record to local SQLite Database
*/ */
@SuppressWarnings("ConstantConditions") override fun insert(uri: Uri, contentValues: ContentValues?): Uri {
@Override val sqlDB = dbOpenHelper.writableDatabase
public Uri insert(@NonNull Uri uri, ContentValues contentValues) { val id = sqlDB.insert(BookmarkLocationsDao.Table.TABLE_NAME, null, contentValues)
SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase(); context?.contentResolver?.notifyChange(uri, null)
long id = sqlDB.insert(BookmarkLocationsDao.Table.TABLE_NAME, null, contentValues); return Uri.parse("$BASE_URI/$id")
getContext().getContentResolver().notifyChange(uri, null);
return Uri.parse(BASE_URI + "/" + id);
} }
@SuppressWarnings("ConstantConditions") /**
@Override * Handles the deletion of bookmark locations record from local SQLite Database
public int delete(@NonNull Uri uri, String s, String[] strings) { */
int rows; override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
SQLiteDatabase db = dbOpenHelper.getReadableDatabase(); val db = dbOpenHelper.readableDatabase
Timber.d("Deleting bookmark name %s", uri.getLastPathSegment()); Timber.d("Deleting bookmark name %s", uri.lastPathSegment)
rows = db.delete(TABLE_NAME, val rows = db.delete(
TABLE_NAME,
"location_name = ?", "location_name = ?",
new String[]{uri.getLastPathSegment()} arrayOf(uri.lastPathSegment)
); )
getContext().getContentResolver().notifyChange(uri, null); context?.contentResolver?.notifyChange(uri, null)
return rows; return rows
} }
} }

View file

@ -1,26 +1,20 @@
package fr.free.nrw.commons.bookmarks.locations; package fr.free.nrw.commons.bookmarks.locations
import java.util.List; import javax.inject.Inject
import javax.inject.Singleton
import javax.inject.Inject; import fr.free.nrw.commons.nearby.Place
import javax.inject.Singleton;
import fr.free.nrw.commons.nearby.Place;
@Singleton @Singleton
public class BookmarkLocationsController { class BookmarkLocationsController @Inject constructor(
private val bookmarkLocationDao: BookmarkLocationsDao
@Inject ) {
BookmarkLocationsDao bookmarkLocationDao;
@Inject
public BookmarkLocationsController() {}
/** /**
* Load from DB the bookmarked locations * Load from DB the bookmarked locations
* @return a list of Place objects. * @return a list of Place objects.
*/ */
public List<Place> loadFavoritesLocations() { fun loadFavoritesLocations(): List<Place> {
return bookmarkLocationDao.getAllBookmarksLocations(); return bookmarkLocationDao.getAllBookmarksLocations()
} }
} }

View file

@ -1,214 +1,198 @@
package fr.free.nrw.commons.bookmarks.locations; 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 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 fr.free.nrw.commons.location.LatLng
import fr.free.nrw.commons.nearby.Label
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 fr.free.nrw.commons.nearby.NearbyController;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject; class BookmarkLocationsDao @Inject constructor(
import javax.inject.Named; @Named("bookmarksLocation") private val clientProvider: Provider<ContentProviderClient>
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<ContentProviderClient> clientProvider;
@Inject
public BookmarkLocationsDao(@Named("bookmarksLocation") Provider<ContentProviderClient> clientProvider) {
this.clientProvider = clientProvider;
}
/** /**
* Find all persisted locations bookmarks on database * Find all persisted location bookmarks in the database
*
* @return list of Place * @return list of Place
*/ */
@NonNull fun getAllBookmarksLocations(): List<Place> {
public List<Place> getAllBookmarksLocations() { val items = mutableListOf<Place>()
List<Place> items = new ArrayList<>(); var cursor: Cursor? = null
Cursor cursor = null; val db = clientProvider.get()
ContentProviderClient db = clientProvider.get();
try { try {
cursor = db.query( cursor = db.query(
BookmarkLocationsContentProvider.BASE_URI, BookmarkLocationsContentProvider.BASE_URI,
Table.ALL_FIELDS, Table.ALL_FIELDS,
null, null,
new String[]{}, arrayOf(),
null); null
while (cursor != null && cursor.moveToNext()) { )
items.add(fromCursor(cursor)); while (cursor?.moveToNext() == true) {
items.add(fromCursor(cursor))
} }
} catch (RemoteException e) { } catch (e: RemoteException) {
throw new RuntimeException(e); throw RuntimeException(e)
} finally { } finally {
if (cursor != null) { cursor?.close()
cursor.close(); db.close()
} }
db.release(); return items
}
return items;
} }
/** /**
* Look for a place in bookmarks table in order to insert or delete it * Look for a place in bookmarks table to insert or delete it
* * @param bookmarkLocation: Place object
* @param bookmarkLocation : Place object * @return is Place now a favorite?
* @return is Place now fav ?
*/ */
public boolean updateBookmarkLocation(Place bookmarkLocation) { fun updateBookmarkLocation(bookmarkLocation: Place): Boolean {
boolean bookmarkExists = findBookmarkLocation(bookmarkLocation); val bookmarkExists = findBookmarkLocation(bookmarkLocation)
if (bookmarkExists) { if (bookmarkExists) {
deleteBookmarkLocation(bookmarkLocation); deleteBookmarkLocation(bookmarkLocation)
NearbyController.updateMarkerLabelListBookmark(bookmarkLocation, false); NearbyController.updateMarkerLabelListBookmark(bookmarkLocation, false)
} else { } else {
addBookmarkLocation(bookmarkLocation); addBookmarkLocation(bookmarkLocation)
NearbyController.updateMarkerLabelListBookmark(bookmarkLocation, true); NearbyController.updateMarkerLabelListBookmark(bookmarkLocation, true)
} }
return !bookmarkExists; return !bookmarkExists
} }
/** /**
* Add a Place to bookmarks table * Add a Place to bookmarks table
* * @param bookmarkLocation: Place to add
* @param bookmarkLocation : Place to add
*/ */
private void addBookmarkLocation(Place bookmarkLocation) { private fun addBookmarkLocation(bookmarkLocation: Place) {
ContentProviderClient db = clientProvider.get(); val db = clientProvider.get()
try { try {
db.insert(BASE_URI, toContentValues(bookmarkLocation)); db.insert(BookmarkLocationsContentProvider.BASE_URI, toContentValues(bookmarkLocation))
} catch (RemoteException e) { } catch (e: RemoteException) {
throw new RuntimeException(e); throw RuntimeException(e)
} finally { } finally {
db.release(); db.close()
} }
} }
/** /**
* Delete a Place from bookmarks table * Delete a Place from bookmarks table
* * @param bookmarkLocation: Place to delete
* @param bookmarkLocation : Place to delete
*/ */
private void deleteBookmarkLocation(Place bookmarkLocation) { private fun deleteBookmarkLocation(bookmarkLocation: Place) {
ContentProviderClient db = clientProvider.get(); val db = clientProvider.get()
try { try {
db.delete(BookmarkLocationsContentProvider.uriForName(bookmarkLocation.name), null, null); db.delete(
} catch (RemoteException e) { BookmarkLocationsContentProvider.uriForName(bookmarkLocation.name),
throw new RuntimeException(e); null,
null
)
} catch (e: RemoteException) {
throw RuntimeException(e)
} finally { } finally {
db.release(); db.close()
} }
} }
/** /**
* Find a Place from database based on its name * Find a Place from database based on its name
* * @param bookmarkLocation: Place to find
* @param bookmarkLocation : Place to find * @return is Place in the database?
* @return boolean : is Place in database ?
*/ */
public boolean findBookmarkLocation(Place bookmarkLocation) { fun findBookmarkLocation(bookmarkLocation: Place): Boolean {
Cursor cursor = null; var cursor: Cursor? = null
ContentProviderClient db = clientProvider.get(); val db = clientProvider.get()
try { try {
cursor = db.query( cursor = db.query(
BookmarkLocationsContentProvider.BASE_URI, BookmarkLocationsContentProvider.BASE_URI,
Table.ALL_FIELDS, Table.ALL_FIELDS,
Table.COLUMN_NAME + "=?", "${Table.COLUMN_NAME}=?",
new String[]{bookmarkLocation.name}, arrayOf(bookmarkLocation.name),
null); null
if (cursor != null && cursor.moveToFirst()) { )
return true; if (cursor?.moveToFirst() == true) {
return true
} }
} catch (RemoteException e) { } catch (e: RemoteException) {
// This feels lazy, but to hell with checked exceptions. :) throw RuntimeException(e)
throw new RuntimeException(e);
} finally { } finally {
if (cursor != null) { cursor?.close()
cursor.close(); db.close()
} }
db.release(); return false
}
return false;
} }
@SuppressLint("Range") @SuppressLint("Range")
@NonNull private fun fromCursor(cursor: Cursor): Place {
Place fromCursor(final Cursor cursor) { val location = LatLng(
final LatLng location = new LatLng(cursor.getDouble(cursor.getColumnIndex(Table.COLUMN_LAT)), cursor.getDouble(cursor.getColumnIndex(Table.COLUMN_LAT)),
cursor.getDouble(cursor.getColumnIndex(Table.COLUMN_LONG)), 1F); cursor.getDouble(cursor.getColumnIndex(Table.COLUMN_LONG)),
1f
)
final Sitelinks.Builder builder = new Sitelinks.Builder(); val builder = Sitelinks.Builder().apply {
builder.setWikipediaLink(cursor.getString(cursor.getColumnIndex(Table.COLUMN_WIKIPEDIA_LINK))); setWikipediaLink(cursor.getString(cursor.getColumnIndex(Table.COLUMN_WIKIPEDIA_LINK)))
builder.setWikidataLink(cursor.getString(cursor.getColumnIndex(Table.COLUMN_WIKIDATA_LINK))); setWikidataLink(cursor.getString(cursor.getColumnIndex(Table.COLUMN_WIKIDATA_LINK)))
builder.setCommonsLink(cursor.getString(cursor.getColumnIndex(Table.COLUMN_COMMONS_LINK))); setCommonsLink(cursor.getString(cursor.getColumnIndex(Table.COLUMN_COMMONS_LINK)))
}
return new Place( return Place(
cursor.getString(cursor.getColumnIndex(Table.COLUMN_LANGUAGE)), cursor.getString(cursor.getColumnIndex(Table.COLUMN_LANGUAGE)),
cursor.getString(cursor.getColumnIndex(Table.COLUMN_NAME)), cursor.getString(cursor.getColumnIndex(Table.COLUMN_NAME)),
Label.fromText((cursor.getString(cursor.getColumnIndex(Table.COLUMN_LABEL_TEXT)))), Label.fromText(cursor.getString(cursor.getColumnIndex(Table.COLUMN_LABEL_TEXT))),
cursor.getString(cursor.getColumnIndex(Table.COLUMN_DESCRIPTION)), cursor.getString(cursor.getColumnIndex(Table.COLUMN_DESCRIPTION)),
location, location,
cursor.getString(cursor.getColumnIndex(Table.COLUMN_CATEGORY)), cursor.getString(cursor.getColumnIndex(Table.COLUMN_CATEGORY)),
builder.build(), builder.build(),
cursor.getString(cursor.getColumnIndex(Table.COLUMN_PIC)), cursor.getString(cursor.getColumnIndex(Table.COLUMN_PIC)),
Boolean.parseBoolean(cursor.getString(cursor.getColumnIndex(Table.COLUMN_EXISTS))) cursor.getString(cursor.getColumnIndex(Table.COLUMN_EXISTS)).toBoolean()
); )
} }
private ContentValues toContentValues(Place bookmarkLocation) { private fun toContentValues(bookmarkLocation: Place): ContentValues {
ContentValues cv = new ContentValues(); return ContentValues().apply {
cv.put(BookmarkLocationsDao.Table.COLUMN_NAME, bookmarkLocation.getName()); put(Table.COLUMN_NAME, bookmarkLocation.name)
cv.put(BookmarkLocationsDao.Table.COLUMN_LANGUAGE, bookmarkLocation.getLanguage()); put(Table.COLUMN_LANGUAGE, bookmarkLocation.language)
cv.put(BookmarkLocationsDao.Table.COLUMN_DESCRIPTION, bookmarkLocation.getLongDescription()); put(Table.COLUMN_DESCRIPTION, bookmarkLocation.longDescription)
cv.put(BookmarkLocationsDao.Table.COLUMN_CATEGORY, bookmarkLocation.getCategory()); put(Table.COLUMN_CATEGORY, bookmarkLocation.category)
cv.put(BookmarkLocationsDao.Table.COLUMN_LABEL_TEXT, bookmarkLocation.getLabel()!=null ? bookmarkLocation.getLabel().getText() : ""); put(Table.COLUMN_LABEL_TEXT, bookmarkLocation.label?.text ?: "")
cv.put(BookmarkLocationsDao.Table.COLUMN_LABEL_ICON, bookmarkLocation.getLabel()!=null ? bookmarkLocation.getLabel().getIcon() : null); put(Table.COLUMN_LABEL_ICON, bookmarkLocation.label?.icon)
cv.put(BookmarkLocationsDao.Table.COLUMN_WIKIPEDIA_LINK, bookmarkLocation.siteLinks.getWikipediaLink().toString()); put(Table.COLUMN_WIKIPEDIA_LINK, bookmarkLocation.siteLinks.wikipediaLink.toString())
cv.put(BookmarkLocationsDao.Table.COLUMN_WIKIDATA_LINK, bookmarkLocation.siteLinks.getWikidataLink().toString()); put(Table.COLUMN_WIKIDATA_LINK, bookmarkLocation.siteLinks.wikidataLink.toString())
cv.put(BookmarkLocationsDao.Table.COLUMN_COMMONS_LINK, bookmarkLocation.siteLinks.getCommonsLink().toString()); put(Table.COLUMN_COMMONS_LINK, bookmarkLocation.siteLinks.commonsLink.toString())
cv.put(BookmarkLocationsDao.Table.COLUMN_LAT, bookmarkLocation.location.getLatitude()); put(Table.COLUMN_LAT, bookmarkLocation.location.latitude)
cv.put(BookmarkLocationsDao.Table.COLUMN_LONG, bookmarkLocation.location.getLongitude()); put(Table.COLUMN_LONG, bookmarkLocation.location.longitude)
cv.put(BookmarkLocationsDao.Table.COLUMN_PIC, bookmarkLocation.pic); put(Table.COLUMN_PIC, bookmarkLocation.pic)
cv.put(BookmarkLocationsDao.Table.COLUMN_EXISTS, bookmarkLocation.exists.toString()); put(Table.COLUMN_EXISTS, bookmarkLocation.exists.toString())
return cv; }
} }
public static class Table { object Table {
public static final String TABLE_NAME = "bookmarksLocations"; 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"
static final String COLUMN_NAME = "location_name"; val ALL_FIELDS = arrayOf(
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_NAME,
COLUMN_LANGUAGE, COLUMN_LANGUAGE,
COLUMN_DESCRIPTION, COLUMN_DESCRIPTION,
@ -222,90 +206,95 @@ public class BookmarkLocationsDao {
COLUMN_WIKIDATA_LINK, COLUMN_WIKIDATA_LINK,
COLUMN_COMMONS_LINK, COLUMN_COMMONS_LINK,
COLUMN_PIC, COLUMN_PIC,
COLUMN_EXISTS, COLUMN_EXISTS
}; )
static final String DROP_TABLE_STATEMENT = "DROP TABLE IF EXISTS " + TABLE_NAME; private const val DROP_TABLE_STATEMENT = "DROP TABLE IF EXISTS $TABLE_NAME"
static final String CREATE_TABLE_STATEMENT = "CREATE TABLE " + TABLE_NAME + " (" private const val CREATE_TABLE_STATEMENT = """
+ COLUMN_NAME + " STRING PRIMARY KEY," CREATE TABLE $TABLE_NAME (
+ COLUMN_LANGUAGE + " STRING," $COLUMN_NAME STRING PRIMARY KEY,
+ COLUMN_DESCRIPTION + " STRING," $COLUMN_LANGUAGE STRING,
+ COLUMN_CATEGORY + " STRING," $COLUMN_DESCRIPTION STRING,
+ COLUMN_LABEL_TEXT + " STRING," $COLUMN_CATEGORY STRING,
+ COLUMN_LABEL_ICON + " INTEGER," $COLUMN_LABEL_TEXT STRING,
+ COLUMN_LAT + " DOUBLE," $COLUMN_LABEL_ICON INTEGER,
+ COLUMN_LONG + " DOUBLE," $COLUMN_LAT DOUBLE,
+ COLUMN_IMAGE_URL + " STRING," $COLUMN_LONG DOUBLE,
+ COLUMN_WIKIPEDIA_LINK + " STRING," $COLUMN_IMAGE_URL STRING,
+ COLUMN_WIKIDATA_LINK + " STRING," $COLUMN_WIKIPEDIA_LINK STRING,
+ COLUMN_COMMONS_LINK + " STRING," $COLUMN_WIKIDATA_LINK STRING,
+ COLUMN_PIC + " STRING," $COLUMN_COMMONS_LINK STRING,
+ COLUMN_EXISTS + " STRING" $COLUMN_PIC STRING,
+ ");"; $COLUMN_EXISTS STRING
)
"""
public static void onCreate(SQLiteDatabase db) { fun onCreate(db: SQLiteDatabase) {
db.execSQL(CREATE_TABLE_STATEMENT); db.execSQL(CREATE_TABLE_STATEMENT)
} }
public static void onDelete(SQLiteDatabase db) { fun onDelete(db: SQLiteDatabase) {
db.execSQL(DROP_TABLE_STATEMENT); db.execSQL(DROP_TABLE_STATEMENT)
onCreate(db); onCreate(db)
} }
public static void onUpdate(final SQLiteDatabase db, int from, final int to) { @SuppressLint("SQLiteString")
Timber.d("bookmarksLocations db is updated from:"+from+", to:"+to); fun onUpdate(db: SQLiteDatabase, from: Int, to: Int) {
if (from == to) { Timber.d("bookmarksLocations db is updated from: $from, to: $to")
return; if (from == to) return
}
if (from < 7) { if (from < 7) {
// doesn't exist yet // doesn't exist yet
from++; onUpdate(db, from + 1, to)
onUpdate(db, from, to); return
return;
} }
if (from == 7) { if (from == 7) {
// table added in version 8 // table added in version 8
onCreate(db); onCreate(db)
from++; onUpdate(db, from + 1, to)
onUpdate(db, from, to); return
return;
} }
if (from < 10) { if (from < 10) {
from++; onUpdate(db, from + 1, to)
onUpdate(db, from, to); return
return;
} }
if (from == 10) { if (from == 10) {
//This is safe, and can be called clean, as we/I do not remember the appropriate version for this // Adding column `location_pic`
//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 { try {
db.execSQL( db.execSQL(
"ALTER TABLE bookmarksLocations ADD COLUMN location_destroyed STRING;"); "ALTER TABLE $TABLE_NAME ADD COLUMN location_pic STRING;"
} catch (SQLiteException exception) { )
Timber.e(exception); } catch (exception: SQLiteException) {
Timber.e(exception)
} }
return
} }
if (from >= 13){
if (from >= 12) {
try { try {
db.execSQL("ALTER TABLE bookmarksLocations ADD COLUMN location_language STRING;"); db.execSQL("ALTER TABLE $TABLE_NAME ADD COLUMN location_destroyed STRING;")
} catch (SQLiteException exception){ } catch (exception: SQLiteException) {
Timber.e(exception); Timber.e(exception)
} }
} }
if (from >= 14){
if (from >= 13) {
try { try {
db.execSQL("ALTER TABLE bookmarksLocations ADD COLUMN location_exists STRING;"); db.execSQL("ALTER TABLE $TABLE_NAME ADD COLUMN location_language STRING;")
} catch (SQLiteException exception){ } catch (exception: SQLiteException) {
Timber.e(exception); 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

@ -1,137 +1,139 @@
package fr.free.nrw.commons.bookmarks.locations; package fr.free.nrw.commons.bookmarks.locations
import android.Manifest.permission; import android.Manifest.permission
import android.content.Intent; import android.os.Bundle
import android.os.Bundle; import android.view.LayoutInflater
import android.view.LayoutInflater; import android.view.View
import android.view.View; import android.view.ViewGroup
import android.view.ViewGroup; import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.ActivityResultCallback; import androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions
import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
import androidx.activity.result.contract.ActivityResultContracts; import androidx.recyclerview.widget.LinearLayoutManager
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult; import dagger.android.support.DaggerFragment
import androidx.annotation.NonNull; import fr.free.nrw.commons.R
import androidx.annotation.Nullable; import fr.free.nrw.commons.contributions.ContributionController
import androidx.recyclerview.widget.LinearLayoutManager; import fr.free.nrw.commons.databinding.FragmentBookmarksLocationsBinding
import dagger.android.support.DaggerFragment; import fr.free.nrw.commons.filepicker.FilePicker
import fr.free.nrw.commons.R; import fr.free.nrw.commons.nearby.fragments.CommonPlaceClickActions
import fr.free.nrw.commons.contributions.ContributionController; import fr.free.nrw.commons.nearby.fragments.PlaceAdapter
import fr.free.nrw.commons.databinding.FragmentBookmarksLocationsBinding; import javax.inject.Inject
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; class BookmarkLocationsFragment : DaggerFragment() {
@Inject BookmarkLocationsController controller; private var _binding: FragmentBookmarksLocationsBinding? = null
@Inject ContributionController contributionController; private val binding get() = _binding!!
@Inject BookmarkLocationsDao bookmarkLocationDao;
@Inject CommonPlaceClickActions commonPlaceClickActions;
private PlaceAdapter adapter;
private final ActivityResultLauncher<Intent> cameraPickLauncherForResult = @Inject lateinit var controller: BookmarkLocationsController
registerForActivityResult(new StartActivityForResult(), @Inject lateinit var contributionController: ContributionController
result -> { @Inject lateinit var bookmarkLocationDao: BookmarkLocationsDao
contributionController.handleActivityResultWithCallback(requireActivity(), callbacks -> { @Inject lateinit var commonPlaceClickActions: CommonPlaceClickActions
contributionController.onPictureReturnedFromCamera(result, requireActivity(), callbacks);
});
});
private final ActivityResultLauncher<Intent> galleryPickLauncherForResult = private lateinit var adapter: PlaceAdapter
registerForActivityResult(new StartActivityForResult(), private lateinit var inAppCameraLocationPermissionLauncher
result -> { : ActivityResultLauncher<Array<String>>
contributionController.handleActivityResultWithCallback(requireActivity(), callbacks -> {
contributionController.onPictureReturnedFromGallery(result, requireActivity(), callbacks);
});
});
private ActivityResultLauncher<String[]> inAppCameraLocationPermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), new ActivityResultCallback<Map<String, Boolean>>() { private val cameraPickLauncherForResult =
@Override registerForActivityResult(StartActivityForResult()) { result ->
public void onActivityResult(Map<String, Boolean> result) { contributionController.handleActivityResultWithCallback(
boolean areAllGranted = true; requireActivity(),
for(final boolean b : result.values()) { object: FilePicker.HandleActivityResult {
areAllGranted = areAllGranted && b; override fun onHandleActivityResult(callbacks: FilePicker.Callbacks) {
contributionController.onPictureReturnedFromCamera(
result,
requireActivity(),
callbacks
)
} }
})
}
private val galleryPickLauncherForResult =
registerForActivityResult(StartActivityForResult()) { result ->
contributionController.handleActivityResultWithCallback(
requireActivity(),
object: FilePicker.HandleActivityResult {
override fun onHandleActivityResult(callbacks: FilePicker.Callbacks) {
contributionController.onPictureReturnedFromGallery(
result,
requireActivity(),
callbacks
)
}
})
}
companion object {
fun newInstance(): BookmarkLocationsFragment = BookmarkLocationsFragment()
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentBookmarksLocationsBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.loadingImagesProgressBar.visibility = View.VISIBLE
binding.listView.layoutManager = LinearLayoutManager(context)
inAppCameraLocationPermissionLauncher =
registerForActivityResult(RequestMultiplePermissions()) { result ->
val areAllGranted = result.values.all { it }
if (areAllGranted) { if (areAllGranted) {
contributionController.locationPermissionCallback.onLocationPermissionGranted(); contributionController.locationPermissionCallback.onLocationPermissionGranted()
} else { } else {
if (shouldShowRequestPermissionRationale(permission.ACCESS_FINE_LOCATION)) { if (shouldShowRequestPermissionRationale(permission.ACCESS_FINE_LOCATION)) {
contributionController.handleShowRationaleFlowCameraLocation(getActivity(), inAppCameraLocationPermissionLauncher, cameraPickLauncherForResult); contributionController.handleShowRationaleFlowCameraLocation(
} else { activity,
contributionController.locationPermissionCallback.onLocationPermissionDenied(getActivity().getString(R.string.in_app_camera_location_permission_denied));
}
}
}
});
/**
* Create an instance of the fragment with the right bundle parameters
* @return an instance of the fragment
*/
public static BookmarkLocationsFragment newInstance() {
return new BookmarkLocationsFragment();
}
@Override
public View onCreateView(
@NonNull LayoutInflater inflater,
ViewGroup container,
Bundle savedInstanceState
) {
binding = FragmentBookmarksLocationsBinding.inflate(inflater, container, false);
return binding.getRoot();
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
binding.loadingImagesProgressBar.setVisibility(View.VISIBLE);
binding.listView.setLayoutManager(new LinearLayoutManager(getContext()));
adapter = new PlaceAdapter(bookmarkLocationDao,
place -> Unit.INSTANCE,
(place, isBookmarked) -> {
adapter.remove(place);
return Unit.INSTANCE;
},
commonPlaceClickActions,
inAppCameraLocationPermissionLauncher, inAppCameraLocationPermissionLauncher,
galleryPickLauncherForResult,
cameraPickLauncherForResult cameraPickLauncherForResult
); )
binding.listView.setAdapter(adapter);
}
@Override
public void onResume() {
super.onResume();
initList();
}
/**
* Initialize the recycler view with bookmarked locations
*/
private void initList() {
List<Place> places = controller.loadFavoritesLocations();
adapter.setItems(places);
binding.loadingImagesProgressBar.setVisibility(View.GONE);
if (places.size() <= 0) {
binding.statusMessage.setText(R.string.bookmark_empty);
binding.statusMessage.setVisibility(View.VISIBLE);
} else { } else {
binding.statusMessage.setVisibility(View.GONE); contributionController.locationPermissionCallback.onLocationPermissionDenied(
getString(R.string.in_app_camera_location_permission_denied)
)
}
} }
} }
@Override adapter = PlaceAdapter(
public void onDestroy() { bookmarkLocationsDao = bookmarkLocationDao,
super.onDestroy(); onBookmarkClicked = { place, _ ->
binding = null; adapter.remove(place)
},
commonPlaceClickActions = commonPlaceClickActions,
inAppCameraLocationPermissionLauncher = inAppCameraLocationPermissionLauncher,
galleryPickLauncherForResult = galleryPickLauncherForResult,
cameraPickLauncherForResult = cameraPickLauncherForResult
)
binding.listView.adapter = adapter
}
override fun onResume() {
super.onResume()
initList()
}
private fun initList() {
val places = controller.loadFavoritesLocations()
adapter.items = places
binding.loadingImagesProgressBar.visibility = View.GONE
if (places.isEmpty()) {
binding.statusMessage.setText(R.string.bookmark_empty)
binding.statusMessage.visibility = View.VISIBLE
} else {
binding.statusMessage.visibility = View.GONE
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
} }
} }