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 static fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao.Table.TABLE_NAME;
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.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
*/
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
*/
public static Uri uriForName(final String id) {
return Uri.parse(BASE_URI + "/" + id);
/**
* Append bookmark items ID to the base URI
*/
fun uriForName(id: String): Uri {
return Uri.parse("$BASE_URI/$id")
}
}
@Inject
DBOpenHelper dbOpenHelper;
lateinit var dbOpenHelper: DBOpenHelper
@Override
public String getType(@NonNull final Uri uri) {
return null;
override fun getType(uri: Uri): String? {
return null
}
/**
* 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 selection : handles Where
* @param selectionArgs : the condition of Where clause
* @param sortOrder : ascending or descending
*/
@SuppressWarnings("ConstantConditions")
@Override
public Cursor query(@NonNull final Uri uri, final String[] projection, final String selection,
final String[] selectionArgs, final String sortOrder) {
final SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
queryBuilder.setTables(TABLE_NAME);
final SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
final Cursor cursor = queryBuilder.query(db, projection, selection,
selectionArgs, null, null, sortOrder);
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
override fun query(
uri: Uri,
projection: Array<out String>?,
selection: String?,
selectionArgs: Array<out 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
* @param uri : contains the uri for bookmark items
* @param contentValues : new values to be entered to db
* @param uri : contains the URI for bookmark items
* @param contentValues : new values to be entered to DB
* @param selection : handles Where
* @param selectionArgs : the condition of Where clause
*/
@SuppressWarnings("ConstantConditions")
@Override
public int update(@NonNull final Uri uri, final ContentValues contentValues,
final String selection, final String[] selectionArgs) {
final SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
final int rowsUpdated;
if (TextUtils.isEmpty(selection)) {
final int id = Integer.parseInt(uri.getLastPathSegment());
rowsUpdated = sqlDB.update(TABLE_NAME,
override fun update(
uri: Uri,
contentValues: ContentValues?,
selection: String?,
selectionArgs: Array<out String>?
): Int {
val sqlDB = dbOpenHelper.writableDatabase
val rowsUpdated: Int
if (selection.isNullOrEmpty()) {
val id = uri.lastPathSegment?.toIntOrNull()
?: throw IllegalArgumentException("Invalid ID in URI: $uri")
rowsUpdated = sqlDB.update(
TABLE_NAME,
contentValues,
COLUMN_ID + " = ?",
new String[]{String.valueOf(id)});
"$COLUMN_ID = ?",
arrayOf(id.toString())
)
} else {
throw new IllegalArgumentException(
"Parameter `selection` should be empty when updating an ID");
throw IllegalArgumentException(
"Parameter `selection` should be empty when updating an ID"
)
}
getContext().getContentResolver().notifyChange(uri, null);
return rowsUpdated;
context?.contentResolver?.notifyChange(uri, null)
return rowsUpdated
}
/**
* Handles the insertion of new bookmark items record to local SQLite Database
* @param uri
* @param contentValues
* @return
* @param uri : contains the URI for bookmark items
* @param contentValues : values to be inserted
*/
@SuppressWarnings("ConstantConditions")
@Override
public Uri insert(@NonNull final Uri uri, final ContentValues contentValues) {
final SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
final long id = sqlDB.insert(TABLE_NAME, null, contentValues);
getContext().getContentResolver().notifyChange(uri, null);
return Uri.parse(BASE_URI + "/" + id);
override fun insert(uri: Uri, contentValues: ContentValues?): Uri? {
val sqlDB = dbOpenHelper.writableDatabase
val id = sqlDB.insert(TABLE_NAME, null, contentValues)
context?.contentResolver?.notifyChange(uri, null)
return Uri.parse("$BASE_URI/$id")
}
/**
* Handles the deletion of new bookmark items record to local SQLite Database
* @param uri
* @param s
* @param strings
* @return
* Handles the deletion of bookmark items record in the local SQLite Database
* @param uri : contains the URI for bookmark items
* @param selection : unused parameter
* @param selectionArgs : unused parameter
*/
@SuppressWarnings("ConstantConditions")
@Override
public int delete(@NonNull final Uri uri, final String s, final String[] strings) {
final int rows;
final SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
Timber.d("Deleting bookmark name %s", uri.getLastPathSegment());
rows = db.delete(
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {
val db = dbOpenHelper.readableDatabase
Timber.d("Deleting bookmark name %s", uri.lastPathSegment)
val rows = db.delete(
TABLE_NAME,
"item_id = ?",
new String[]{uri.getLastPathSegment()}
);
getContext().getContentResolver().notifyChange(uri, null);
return rows;
"$COLUMN_ID = ?",
arrayOf(uri.lastPathSegment)
)
context?.contentResolver?.notifyChange(uri, null)
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 java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
import javax.inject.Inject
import javax.inject.Singleton
/**
* Handles loading bookmarked items from Database
*/
@Singleton
public class BookmarkItemsController {
@Inject
BookmarkItemsDao bookmarkItemsDao;
@Inject
public BookmarkItemsController() {}
class BookmarkItemsController @Inject constructor(
private val bookmarkItemsDao: BookmarkItemsDao
) {
/**
* Load from DB the bookmarked items
* @return a list of DepictedItem objects.
*/
public List<DepictedItem> loadFavoritesItems() {
return bookmarkItemsDao.getAllBookmarksItems();
fun loadFavoritesItems(): List<DepictedItem> {
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
*/
@Singleton
public class BookmarkItemsDao {
private final Provider<ContentProviderClient> clientProvider;
@Inject
public BookmarkItemsDao(
@Named("bookmarksItem") final Provider<ContentProviderClient> clientProvider) {
this.clientProvider = clientProvider;
}
class BookmarkItemsDao @Inject constructor(
@Named("bookmarksItem") private val clientProvider: Provider<ContentProviderClient>
) {
/**
* Find all persisted items bookmarks on database
* @return list of bookmarks
*/
public List<DepictedItem> getAllBookmarksItems() {
final List<DepictedItem> items = new ArrayList<>();
final ContentProviderClient db = clientProvider.get();
try (final Cursor cursor = db.query(
BookmarkItemsContentProvider.BASE_URI,
Table.ALL_FIELDS,
null,
new String[]{},
null)) {
while (cursor != null && cursor.moveToNext()) {
items.add(fromCursor(cursor));
fun getAllBookmarksItems(): List<DepictedItem> {
val items = mutableListOf<DepictedItem>()
val db = clientProvider.get()
try {
db.query(
BookmarkItemsContentProvider.BASE_URI,
Table.ALL_FIELDS,
null,
emptyArray(),
null
)?.use { cursor ->
while (cursor.moveToNext()) {
items.add(fromCursor(cursor))
}
}
} catch (final RemoteException e) {
throw new RuntimeException(e);
} catch (e: RemoteException) {
throw RuntimeException(e)
} finally {
db.release();
db.close()
}
return items;
return items
}
/**
* Look for a bookmark in database and in order to insert or delete it
* @param depictedItem : Bookmark object
* @return boolean : is bookmark now favorite ?
* @return boolean : is bookmark now favorite?
*/
public boolean updateBookmarkItem(final DepictedItem depictedItem) {
final boolean bookmarkExists = findBookmarkItem(depictedItem.getId());
fun updateBookmarkItem(depictedItem: DepictedItem): Boolean {
val bookmarkExists = findBookmarkItem(depictedItem.id)
if (bookmarkExists) {
deleteBookmarkItem(depictedItem);
deleteBookmarkItem(depictedItem)
} else {
addBookmarkItem(depictedItem);
addBookmarkItem(depictedItem)
}
return !bookmarkExists;
return !bookmarkExists
}
/**
* Add a Bookmark to database
* @param depictedItem : Bookmark to add
*/
private void addBookmarkItem(final DepictedItem depictedItem) {
final ContentProviderClient db = clientProvider.get();
private fun addBookmarkItem(depictedItem: DepictedItem) {
val db = clientProvider.get()
try {
db.insert(BookmarkItemsContentProvider.BASE_URI, toContentValues(depictedItem));
} catch (final RemoteException e) {
throw new RuntimeException(e);
db.insert(BookmarkItemsContentProvider.BASE_URI, toContentValues(depictedItem))
} catch (e: RemoteException) {
throw RuntimeException(e)
} finally {
db.release();
db.close()
}
}
@ -91,76 +83,75 @@ public class BookmarkItemsDao {
* Delete a bookmark from database
* @param depictedItem : Bookmark to delete
*/
private void deleteBookmarkItem(final DepictedItem depictedItem) {
final ContentProviderClient db = clientProvider.get();
private fun deleteBookmarkItem(depictedItem: DepictedItem) {
val db = clientProvider.get()
try {
db.delete(BookmarkItemsContentProvider.uriForName(depictedItem.getId()), null, null);
} catch (final RemoteException e) {
throw new RuntimeException(e);
db.delete(
BookmarkItemsContentProvider.uriForName(depictedItem.id),
null,
null
)
} catch (e: RemoteException) {
throw RuntimeException(e)
} finally {
db.release();
db.close()
}
}
/**
* Find a bookmark from database based on its name
* @param depictedItemID : Bookmark to find
* @return boolean : is bookmark in database ?
* @return boolean : is bookmark in database?
*/
public boolean findBookmarkItem(final String depictedItemID) {
if (depictedItemID == null) { //Avoiding NPE's
return false;
}
final ContentProviderClient db = clientProvider.get();
try (final Cursor cursor = db.query(
BookmarkItemsContentProvider.BASE_URI,
Table.ALL_FIELDS,
Table.COLUMN_ID + "=?",
new String[]{depictedItemID},
null
)) {
if (cursor != null && cursor.moveToFirst()) {
return true;
fun findBookmarkItem(depictedItemID: String?): Boolean {
if (depictedItemID == null) return false // Avoiding NPEs
val db = clientProvider.get()
try {
db.query(
BookmarkItemsContentProvider.BASE_URI,
Table.ALL_FIELDS,
"${Table.COLUMN_ID}=?",
arrayOf(depictedItemID),
null
)?.use { cursor ->
if (cursor.moveToFirst()) return true
}
} catch (final RemoteException e) {
throw new RuntimeException(e);
} catch (e: RemoteException) {
throw RuntimeException(e)
} 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
* @return DepictedItem
*/
@SuppressLint("Range")
DepictedItem fromCursor(final Cursor cursor) {
final String fileName = cursor.getString(cursor.getColumnIndex(Table.COLUMN_NAME));
final String description
= cursor.getString(cursor.getColumnIndex(Table.COLUMN_DESCRIPTION));
final String imageUrl = cursor.getString(cursor.getColumnIndex(Table.COLUMN_IMAGE));
final String instanceListString
= cursor.getString(cursor.getColumnIndex(Table.COLUMN_INSTANCE_LIST));
final List<String> instanceList = StringToArray(instanceListString);
final String categoryNameListString = cursor.getString(cursor
.getColumnIndex(Table.COLUMN_CATEGORIES_NAME_LIST));
final List<String> categoryNameList = StringToArray(categoryNameListString);
final String categoryDescriptionListString = cursor.getString(cursor
.getColumnIndex(Table.COLUMN_CATEGORIES_DESCRIPTION_LIST));
final List<String> categoryDescriptionList = StringToArray(categoryDescriptionListString);
final String categoryThumbnailListString = cursor.getString(cursor
.getColumnIndex(Table.COLUMN_CATEGORIES_THUMBNAIL_LIST));
final List<String> categoryThumbnailList = StringToArray(categoryThumbnailListString);
final List<CategoryItem> categoryList = convertToCategoryItems(categoryNameList,
categoryDescriptionList, categoryThumbnailList);
final boolean isSelected
= Boolean.parseBoolean(cursor.getString(cursor
.getColumnIndex(Table.COLUMN_IS_SELECTED)));
final String id = cursor.getString(cursor.getColumnIndex(Table.COLUMN_ID));
fun fromCursor(cursor: Cursor): DepictedItem {
val fileName = cursor.getString(cursor.getColumnIndex(Table.COLUMN_NAME))
val description = cursor.getString(cursor.getColumnIndex(Table.COLUMN_DESCRIPTION))
val imageUrl = cursor.getString(cursor.getColumnIndex(Table.COLUMN_IMAGE))
val instanceListString = cursor.getString(cursor.getColumnIndex(Table.COLUMN_INSTANCE_LIST))
val instanceList = stringToArray(instanceListString)
val categoryNameListString =
cursor.getString(cursor.getColumnIndex(Table.COLUMN_CATEGORIES_NAME_LIST))
val categoryNameList = stringToArray(categoryNameListString)
val categoryDescriptionListString =
cursor.getString(cursor.getColumnIndex(Table.COLUMN_CATEGORIES_DESCRIPTION_LIST))
val categoryDescriptionList = stringToArray(categoryDescriptionListString)
val categoryThumbnailListString =
cursor.getString(cursor.getColumnIndex(Table.COLUMN_CATEGORIES_THUMBNAIL_LIST))
val categoryThumbnailList = stringToArray(categoryThumbnailListString)
val categoryList = convertToCategoryItems(
categoryNameList, categoryDescriptionList, categoryThumbnailList
)
val isSelected = cursor.getString(cursor.getColumnIndex(Table.COLUMN_IS_SELECTED)).toBoolean()
val id = cursor.getString(cursor.getColumnIndex(Table.COLUMN_ID))
return new DepictedItem(
return DepictedItem(
fileName,
description,
imageUrl,
@ -168,40 +159,40 @@ public class BookmarkItemsDao {
categoryList,
isSelected,
id
);
)
}
private List<CategoryItem> convertToCategoryItems(List<String> categoryNameList,
List<String> categoryDescriptionList, List<String> categoryThumbnailList) {
List<CategoryItem> categoryItems = new ArrayList<>();
for(int i=0; i<categoryNameList.size(); i++){
categoryItems.add(new CategoryItem(categoryNameList.get(i),
categoryDescriptionList.get(i),
categoryThumbnailList.get(i), false));
private fun convertToCategoryItems(
categoryNameList: List<String>,
categoryDescriptionList: List<String>,
categoryThumbnailList: List<String>
): List<CategoryItem> {
return categoryNameList.mapIndexed { index, name ->
CategoryItem(
name,
categoryDescriptionList.getOrNull(index),
categoryThumbnailList.getOrNull(index),
false
)
}
return categoryItems;
}
/**
* 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
*/
private List<String> StringToArray(final String listString) {
final String[] elements = listString.split(",");
return Arrays.asList(elements);
private fun stringToArray(listString: String): List<String> {
return listString.split(",")
}
/**
* Converts string to List
* Converts list to string
* @param list list of items
* @return string comma separated single string of items
*/
private String ArrayToString(final List<String> list) {
if (list != null) {
return StringUtils.join(list, ',');
}
return null;
private fun arrayToString(list: List<String?>): String {
return list.joinToString(",")
}
/**
@ -209,57 +200,46 @@ public class BookmarkItemsDao {
* @param depictedItem depicted item
* @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<>();
for (final CategoryItem category :
depictedItem.getCommonsCategories()) {
namesOfCommonsCategories.add(category.getName());
return ContentValues().apply {
put(Table.COLUMN_NAME, depictedItem.name)
put(Table.COLUMN_DESCRIPTION, depictedItem.description)
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
*/
public static final class Table {
public static final String TABLE_NAME = "bookmarksItems";
public static final String COLUMN_NAME = "item_name";
public static final String COLUMN_DESCRIPTION = "item_description";
public static final String COLUMN_IMAGE = "item_image_url";
public static final String COLUMN_INSTANCE_LIST = "item_instance_of";
public static final String COLUMN_CATEGORIES_NAME_LIST = "item_name_categories";
public static final String COLUMN_CATEGORIES_DESCRIPTION_LIST = "item_description_categories";
public static final String COLUMN_CATEGORIES_THUMBNAIL_LIST = "item_thumbnail_categories";
public static final String COLUMN_IS_SELECTED = "item_is_selected";
public static final String COLUMN_ID = "item_id";
object Table {
const val TABLE_NAME = "bookmarksItems"
const val COLUMN_NAME = "item_name"
const val COLUMN_DESCRIPTION = "item_description"
const val COLUMN_IMAGE = "item_image_url"
const val COLUMN_INSTANCE_LIST = "item_instance_of"
const val COLUMN_CATEGORIES_NAME_LIST = "item_name_categories"
const val COLUMN_CATEGORIES_DESCRIPTION_LIST = "item_description_categories"
const val COLUMN_CATEGORIES_THUMBNAIL_LIST = "item_thumbnail_categories"
const val COLUMN_IS_SELECTED = "item_is_selected"
const val COLUMN_ID = "item_id"
public static final String[] ALL_FIELDS = {
val ALL_FIELDS = arrayOf(
COLUMN_NAME,
COLUMN_DESCRIPTION,
COLUMN_IMAGE,
@ -269,60 +249,55 @@ public class BookmarkItemsDao {
COLUMN_CATEGORIES_THUMBNAIL_LIST,
COLUMN_IS_SELECTED,
COLUMN_ID
};
)
static final String DROP_TABLE_STATEMENT = "DROP TABLE IF EXISTS " + TABLE_NAME;
static final String CREATE_TABLE_STATEMENT = "CREATE TABLE " + TABLE_NAME + " ("
+ COLUMN_NAME + " STRING,"
+ COLUMN_DESCRIPTION + " STRING,"
+ COLUMN_IMAGE + " STRING,"
+ COLUMN_INSTANCE_LIST + " STRING,"
+ COLUMN_CATEGORIES_NAME_LIST + " STRING,"
+ COLUMN_CATEGORIES_DESCRIPTION_LIST + " STRING,"
+ COLUMN_CATEGORIES_THUMBNAIL_LIST + " STRING,"
+ COLUMN_IS_SELECTED + " STRING,"
+ COLUMN_ID + " STRING PRIMARY KEY"
+ ");";
private const val DROP_TABLE_STATEMENT = "DROP TABLE IF EXISTS $TABLE_NAME"
private const val CREATE_TABLE_STATEMENT = """
CREATE TABLE $TABLE_NAME (
$COLUMN_NAME STRING,
$COLUMN_DESCRIPTION STRING,
$COLUMN_IMAGE STRING,
$COLUMN_INSTANCE_LIST STRING,
$COLUMN_CATEGORIES_NAME_LIST STRING,
$COLUMN_CATEGORIES_DESCRIPTION_LIST STRING,
$COLUMN_CATEGORIES_THUMBNAIL_LIST STRING,
$COLUMN_IS_SELECTED STRING,
$COLUMN_ID STRING PRIMARY KEY
);
"""
/**
* Creates table
* @param db SQLiteDatabase
*/
public static void onCreate(final SQLiteDatabase db) {
db.execSQL(CREATE_TABLE_STATEMENT);
fun onCreate(db: SQLiteDatabase) {
db.execSQL(CREATE_TABLE_STATEMENT)
}
/**
* Deletes database
* @param db SQLiteDatabase
*/
public static void onDelete(final SQLiteDatabase db) {
db.execSQL(DROP_TABLE_STATEMENT);
onCreate(db);
fun onDelete(db: SQLiteDatabase) {
db.execSQL(DROP_TABLE_STATEMENT)
onCreate(db)
}
/**
* Updates database
* @param db SQLiteDatabase
* @param from starting
* @param to end
* @param from starting version
* @param to end version
*/
public static void onUpdate(final SQLiteDatabase db, int from, final int to) {
if (from == to) {
return;
}
fun onUpdate(db: SQLiteDatabase, from: Int, to: Int) {
if (from == to) return
if (from < 18) {
// doesn't exist yet
from++;
onUpdate(db, from, to);
return;
onUpdate(db, from + 1, to)
return
}
if (from == 18) {
// table added in version 19
onCreate(db);
from++;
onUpdate(db, from, to);
onCreate(db)
onUpdate(db, from + 1, 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
*/
public class BookmarkItemsFragment extends DaggerFragment {
class BookmarkItemsFragment : DaggerFragment() {
private FragmentBookmarksItemsBinding binding;
private var binding: FragmentBookmarksItemsBinding? = null
@Inject
BookmarkItemsController controller;
lateinit var controller: BookmarkItemsController
public static BookmarkItemsFragment newInstance() {
return new BookmarkItemsFragment();
companion object {
fun newInstance(): BookmarkItemsFragment {
return BookmarkItemsFragment()
}
}
@Override
public View onCreateView(
@NonNull final LayoutInflater inflater,
final ViewGroup container,
final Bundle savedInstanceState
) {
binding = FragmentBookmarksItemsBinding.inflate(inflater, container, false);
return binding.getRoot();
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentBookmarksItemsBinding.inflate(inflater, container, false)
return binding?.root
}
@Override
public void onViewCreated(final @NotNull View view, @Nullable final Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
initList(requireContext());
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initList(requireContext())
}
@Override
public void onResume() {
super.onResume();
initList(requireContext());
override fun onResume() {
super.onResume()
initList(requireContext())
}
/**
* Get list of DepictedItem and sets to the adapter
* @param context context
*/
private void initList(final Context context) {
final List<DepictedItem> depictItems = controller.loadFavoritesItems();
final BookmarkItemsAdapter adapter = new BookmarkItemsAdapter(depictItems, context);
binding.listView.setAdapter(adapter);
binding.loadingImagesProgressBar.setVisibility(View.GONE);
if (depictItems.isEmpty()) {
binding.statusMessage.setText(R.string.bookmark_empty);
binding.statusMessage.setVisibility(View.VISIBLE);
} else {
binding.statusMessage.setVisibility(View.GONE);
private fun initList(context: Context) {
val depictItems = controller.loadFavoritesItems()
val adapter = BookmarkItemsAdapter(depictItems, context)
binding?.apply {
listView.adapter = adapter
loadingImagesProgressBar.visibility = View.GONE
if (depictItems.isEmpty()) {
statusMessage.setText(R.string.bookmark_empty)
statusMessage.visibility = View.VISIBLE
} else {
statusMessage.visibility = View.GONE
}
}
}
@Override
public void onDestroy() {
super.onDestroy();
binding = null;
override fun onDestroy() {
super.onDestroy()
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)
import android.net.Uri;
import android.text.TextUtils;
import android.net.Uri
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.data.DBOpenHelper;
import fr.free.nrw.commons.di.CommonsDaggerContentProvider;
import timber.log.Timber;
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 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
*/
public class BookmarkLocationsContentProvider extends CommonsDaggerContentProvider {
class BookmarkLocationsContentProvider : CommonsDaggerContentProvider() {
private static final String BASE_PATH = "bookmarksLocations";
public static final Uri BASE_URI = Uri.parse("content://" + BuildConfig.BOOKMARK_LOCATIONS_AUTHORITY + "/" + BASE_PATH);
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
*/
public static Uri uriForName(String name) {
return Uri.parse(BASE_URI.toString() + "/" + name);
/**
* Append bookmark locations name to the base URI
*/
fun uriForName(name: String): Uri {
return Uri.parse("$BASE_URI/$name")
}
}
@Inject DBOpenHelper dbOpenHelper;
@Inject
lateinit var dbOpenHelper: DBOpenHelper
@Override
public String getType(@NonNull Uri uri) {
return null;
override fun getType(uri: Uri): String? {
return null
}
/**
* 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 selection : handles Where
* @param selectionArgs : the condition of Where clause
* @param sortOrder : ascending or descending
*/
@SuppressWarnings("ConstantConditions")
@Override
public Cursor query(@NonNull Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
queryBuilder.setTables(TABLE_NAME);
override fun query(
uri: Uri,
projection: Array<String>?,
selection: String?,
selectionArgs: Array<String>?,
sortOrder: String?
): Cursor? {
val queryBuilder = SQLiteQueryBuilder().apply {
tables = TABLE_NAME
}
SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder);
cursor.setNotificationUri(getContext().getContentResolver(), uri);
val db = dbOpenHelper.readableDatabase
val cursor = queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder)
cursor.setNotificationUri(context?.contentResolver, uri)
return cursor;
return cursor
}
/**
* Handles the update query of local SQLite Database
* @param uri : contains the uri for bookmark locations
* @param contentValues : new values to be entered to db
* Handles the update query of local SQLite Database
* @param uri : contains the URI for bookmark locations
* @param contentValues : new values to be entered to DB
* @param selection : handles Where
* @param selectionArgs : the condition of Where clause
*/
@SuppressWarnings("ConstantConditions")
@Override
public int update(@NonNull Uri uri, ContentValues contentValues, String selection,
String[] selectionArgs) {
SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
int rowsUpdated;
if (TextUtils.isEmpty(selection)) {
int id = Integer.valueOf(uri.getLastPathSegment());
rowsUpdated = sqlDB.update(TABLE_NAME,
contentValues,
COLUMN_NAME + " = ?",
new String[]{String.valueOf(id)});
override fun update(
uri: Uri,
contentValues: ContentValues?,
selection: String?,
selectionArgs: Array<String>?
): Int {
val sqlDB = dbOpenHelper.writableDatabase
val rowsUpdated: Int
if (selection.isNullOrEmpty()) {
val id = uri.lastPathSegment?.toInt() ?: throw IllegalArgumentException("Invalid ID in URI")
rowsUpdated = sqlDB.update(
TABLE_NAME,
contentValues,
"$COLUMN_NAME = ?",
arrayOf(id.toString())
)
} else {
throw new IllegalArgumentException(
"Parameter `selection` should be empty when updating an ID");
throw IllegalArgumentException("Parameter `selection` should be empty when updating an ID")
}
getContext().getContentResolver().notifyChange(uri, null);
return rowsUpdated;
context?.contentResolver?.notifyChange(uri, null)
return rowsUpdated
}
/**
* Handles the insertion of new bookmark locations record to local SQLite Database
*/
@SuppressWarnings("ConstantConditions")
@Override
public Uri insert(@NonNull Uri uri, ContentValues contentValues) {
SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
long id = sqlDB.insert(BookmarkLocationsDao.Table.TABLE_NAME, null, contentValues);
getContext().getContentResolver().notifyChange(uri, null);
return Uri.parse(BASE_URI + "/" + id);
override fun insert(uri: Uri, contentValues: ContentValues?): Uri {
val sqlDB = dbOpenHelper.writableDatabase
val id = sqlDB.insert(BookmarkLocationsDao.Table.TABLE_NAME, null, contentValues)
context?.contentResolver?.notifyChange(uri, null)
return Uri.parse("$BASE_URI/$id")
}
@SuppressWarnings("ConstantConditions")
@Override
public int delete(@NonNull Uri uri, String s, String[] strings) {
int rows;
SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
Timber.d("Deleting bookmark name %s", uri.getLastPathSegment());
rows = db.delete(TABLE_NAME,
"location_name = ?",
new String[]{uri.getLastPathSegment()}
);
getContext().getContentResolver().notifyChange(uri, null);
return rows;
/**
* Handles the deletion of bookmark locations record from 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

@ -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 javax.inject.Singleton;
import fr.free.nrw.commons.nearby.Place;
import fr.free.nrw.commons.nearby.Place
@Singleton
public class BookmarkLocationsController {
@Inject
BookmarkLocationsDao bookmarkLocationDao;
@Inject
public BookmarkLocationsController() {}
class BookmarkLocationsController @Inject constructor(
private val bookmarkLocationDao: BookmarkLocationsDao
) {
/**
* Load from DB the bookmarked locations
* @return a list of Place objects.
*/
public List<Place> loadFavoritesLocations() {
return bookmarkLocationDao.getAllBookmarksLocations();
fun loadFavoritesLocations(): List<Place> {
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;
import javax.inject.Named;
import javax.inject.Provider;
import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.nearby.Label;
import fr.free.nrw.commons.nearby.Place;
import fr.free.nrw.commons.nearby.Sitelinks;
import timber.log.Timber;
import static fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsContentProvider.BASE_URI;
public class BookmarkLocationsDao {
private final Provider<ContentProviderClient> clientProvider;
@Inject
public BookmarkLocationsDao(@Named("bookmarksLocation") Provider<ContentProviderClient> clientProvider) {
this.clientProvider = clientProvider;
}
class BookmarkLocationsDao @Inject constructor(
@Named("bookmarksLocation") private val clientProvider: Provider<ContentProviderClient>
) {
/**
* Find all persisted locations bookmarks on database
*
* Find all persisted location bookmarks in the database
* @return list of Place
*/
@NonNull
public List<Place> getAllBookmarksLocations() {
List<Place> items = new ArrayList<>();
Cursor cursor = null;
ContentProviderClient db = clientProvider.get();
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,
new String[]{},
null);
while (cursor != null && cursor.moveToNext()) {
items.add(fromCursor(cursor));
arrayOf(),
null
)
while (cursor?.moveToNext() == true) {
items.add(fromCursor(cursor))
}
} catch (RemoteException e) {
throw new RuntimeException(e);
} catch (e: RemoteException) {
throw RuntimeException(e)
} finally {
if (cursor != null) {
cursor.close();
}
db.release();
cursor?.close()
db.close()
}
return items;
return items
}
/**
* Look for a place in bookmarks table in order to insert or delete it
*
* @param bookmarkLocation : Place object
* @return is Place now fav ?
* Look for a place in bookmarks table to insert or delete it
* @param bookmarkLocation: Place object
* @return is Place now a favorite?
*/
public boolean updateBookmarkLocation(Place bookmarkLocation) {
boolean bookmarkExists = findBookmarkLocation(bookmarkLocation);
fun updateBookmarkLocation(bookmarkLocation: Place): Boolean {
val bookmarkExists = findBookmarkLocation(bookmarkLocation)
if (bookmarkExists) {
deleteBookmarkLocation(bookmarkLocation);
NearbyController.updateMarkerLabelListBookmark(bookmarkLocation, false);
deleteBookmarkLocation(bookmarkLocation)
NearbyController.updateMarkerLabelListBookmark(bookmarkLocation, false)
} else {
addBookmarkLocation(bookmarkLocation);
NearbyController.updateMarkerLabelListBookmark(bookmarkLocation, true);
addBookmarkLocation(bookmarkLocation)
NearbyController.updateMarkerLabelListBookmark(bookmarkLocation, true)
}
return !bookmarkExists;
return !bookmarkExists
}
/**
* Add a Place to bookmarks table
*
* @param bookmarkLocation : Place to add
* @param bookmarkLocation: Place to add
*/
private void addBookmarkLocation(Place bookmarkLocation) {
ContentProviderClient db = clientProvider.get();
private fun addBookmarkLocation(bookmarkLocation: Place) {
val db = clientProvider.get()
try {
db.insert(BASE_URI, toContentValues(bookmarkLocation));
} catch (RemoteException e) {
throw new RuntimeException(e);
db.insert(BookmarkLocationsContentProvider.BASE_URI, toContentValues(bookmarkLocation))
} catch (e: RemoteException) {
throw RuntimeException(e)
} finally {
db.release();
db.close()
}
}
/**
* Delete a Place from bookmarks table
*
* @param bookmarkLocation : Place to delete
* @param bookmarkLocation: Place to delete
*/
private void deleteBookmarkLocation(Place bookmarkLocation) {
ContentProviderClient db = clientProvider.get();
private fun deleteBookmarkLocation(bookmarkLocation: Place) {
val db = clientProvider.get()
try {
db.delete(BookmarkLocationsContentProvider.uriForName(bookmarkLocation.name), null, null);
} catch (RemoteException e) {
throw new RuntimeException(e);
db.delete(
BookmarkLocationsContentProvider.uriForName(bookmarkLocation.name),
null,
null
)
} catch (e: RemoteException) {
throw RuntimeException(e)
} finally {
db.release();
db.close()
}
}
/**
* Find a Place from database based on its name
*
* @param bookmarkLocation : Place to find
* @return boolean : is Place in database ?
* @param bookmarkLocation: Place to find
* @return is Place in the database?
*/
public boolean findBookmarkLocation(Place bookmarkLocation) {
Cursor cursor = null;
ContentProviderClient db = clientProvider.get();
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 + "=?",
new String[]{bookmarkLocation.name},
null);
if (cursor != null && cursor.moveToFirst()) {
return true;
"${Table.COLUMN_NAME}=?",
arrayOf(bookmarkLocation.name),
null
)
if (cursor?.moveToFirst() == true) {
return true
}
} catch (RemoteException e) {
// This feels lazy, but to hell with checked exceptions. :)
throw new RuntimeException(e);
} catch (e: RemoteException) {
throw RuntimeException(e)
} finally {
if (cursor != null) {
cursor.close();
}
db.release();
cursor?.close()
db.close()
}
return false;
return false
}
@SuppressLint("Range")
@NonNull
Place fromCursor(final Cursor cursor) {
final LatLng location = new LatLng(cursor.getDouble(cursor.getColumnIndex(Table.COLUMN_LAT)),
cursor.getDouble(cursor.getColumnIndex(Table.COLUMN_LONG)), 1F);
private fun fromCursor(cursor: Cursor): Place {
val location = LatLng(
cursor.getDouble(cursor.getColumnIndex(Table.COLUMN_LAT)),
cursor.getDouble(cursor.getColumnIndex(Table.COLUMN_LONG)),
1f
)
final Sitelinks.Builder builder = new Sitelinks.Builder();
builder.setWikipediaLink(cursor.getString(cursor.getColumnIndex(Table.COLUMN_WIKIPEDIA_LINK)));
builder.setWikidataLink(cursor.getString(cursor.getColumnIndex(Table.COLUMN_WIKIDATA_LINK)));
builder.setCommonsLink(cursor.getString(cursor.getColumnIndex(Table.COLUMN_COMMONS_LINK)));
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 new Place(
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)))),
Label.fromText(cursor.getString(cursor.getColumnIndex(Table.COLUMN_LABEL_TEXT))),
cursor.getString(cursor.getColumnIndex(Table.COLUMN_DESCRIPTION)),
location,
cursor.getString(cursor.getColumnIndex(Table.COLUMN_CATEGORY)),
builder.build(),
cursor.getString(cursor.getColumnIndex(Table.COLUMN_PIC)),
Boolean.parseBoolean(cursor.getString(cursor.getColumnIndex(Table.COLUMN_EXISTS)))
);
cursor.getString(cursor.getColumnIndex(Table.COLUMN_EXISTS)).toBoolean()
)
}
private ContentValues toContentValues(Place bookmarkLocation) {
ContentValues cv = new ContentValues();
cv.put(BookmarkLocationsDao.Table.COLUMN_NAME, bookmarkLocation.getName());
cv.put(BookmarkLocationsDao.Table.COLUMN_LANGUAGE, bookmarkLocation.getLanguage());
cv.put(BookmarkLocationsDao.Table.COLUMN_DESCRIPTION, bookmarkLocation.getLongDescription());
cv.put(BookmarkLocationsDao.Table.COLUMN_CATEGORY, bookmarkLocation.getCategory());
cv.put(BookmarkLocationsDao.Table.COLUMN_LABEL_TEXT, bookmarkLocation.getLabel()!=null ? bookmarkLocation.getLabel().getText() : "");
cv.put(BookmarkLocationsDao.Table.COLUMN_LABEL_ICON, bookmarkLocation.getLabel()!=null ? bookmarkLocation.getLabel().getIcon() : null);
cv.put(BookmarkLocationsDao.Table.COLUMN_WIKIPEDIA_LINK, bookmarkLocation.siteLinks.getWikipediaLink().toString());
cv.put(BookmarkLocationsDao.Table.COLUMN_WIKIDATA_LINK, bookmarkLocation.siteLinks.getWikidataLink().toString());
cv.put(BookmarkLocationsDao.Table.COLUMN_COMMONS_LINK, bookmarkLocation.siteLinks.getCommonsLink().toString());
cv.put(BookmarkLocationsDao.Table.COLUMN_LAT, bookmarkLocation.location.getLatitude());
cv.put(BookmarkLocationsDao.Table.COLUMN_LONG, bookmarkLocation.location.getLongitude());
cv.put(BookmarkLocationsDao.Table.COLUMN_PIC, bookmarkLocation.pic);
cv.put(BookmarkLocationsDao.Table.COLUMN_EXISTS, bookmarkLocation.exists.toString());
return cv;
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())
}
}
public static class Table {
public static final String TABLE_NAME = "bookmarksLocations";
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"
static final String COLUMN_NAME = "location_name";
static final String COLUMN_LANGUAGE = "location_language";
static final String COLUMN_DESCRIPTION = "location_description";
static final String COLUMN_LAT = "location_lat";
static final String COLUMN_LONG = "location_long";
static final String COLUMN_CATEGORY = "location_category";
static final String COLUMN_LABEL_TEXT = "location_label_text";
static final String COLUMN_LABEL_ICON = "location_label_icon";
static final String COLUMN_IMAGE_URL = "location_image_url";
static final String COLUMN_WIKIPEDIA_LINK = "location_wikipedia_link";
static final String COLUMN_WIKIDATA_LINK = "location_wikidata_link";
static final String COLUMN_COMMONS_LINK = "location_commons_link";
static final String COLUMN_PIC = "location_pic";
static final String COLUMN_EXISTS = "location_exists";
// NOTE! KEEP IN SAME ORDER AS THEY ARE DEFINED UP THERE. HELPS HARD CODE COLUMN INDICES.
public static final String[] ALL_FIELDS = {
val ALL_FIELDS = arrayOf(
COLUMN_NAME,
COLUMN_LANGUAGE,
COLUMN_DESCRIPTION,
@ -222,90 +206,95 @@ public class BookmarkLocationsDao {
COLUMN_WIKIDATA_LINK,
COLUMN_COMMONS_LINK,
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 + " ("
+ 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"
+ ");";
private 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
)
"""
public static void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_TABLE_STATEMENT);
fun onCreate(db: SQLiteDatabase) {
db.execSQL(CREATE_TABLE_STATEMENT)
}
public static void onDelete(SQLiteDatabase db) {
db.execSQL(DROP_TABLE_STATEMENT);
onCreate(db);
fun onDelete(db: SQLiteDatabase) {
db.execSQL(DROP_TABLE_STATEMENT)
onCreate(db)
}
public static void onUpdate(final SQLiteDatabase db, int from, final int to) {
Timber.d("bookmarksLocations db is updated from:"+from+", to:"+to);
if (from == to) {
return;
}
@SuppressLint("SQLiteString")
fun onUpdate(db: SQLiteDatabase, from: Int, to: Int) {
Timber.d("bookmarksLocations db is updated from: $from, to: $to")
if (from == to) return
if (from < 7) {
// doesn't exist yet
from++;
onUpdate(db, from, to);
return;
onUpdate(db, from + 1, to)
return
}
if (from == 7) {
// table added in version 8
onCreate(db);
from++;
onUpdate(db, from, to);
return;
onCreate(db)
onUpdate(db, from + 1, to)
return
}
if (from < 10) {
from++;
onUpdate(db, from, to);
return;
onUpdate(db, from + 1, to)
return
}
if (from == 10) {
//This is safe, and can be called clean, as we/I do not remember the appropriate version for this
//We are anyways switching to room, these things won't be necessary then
try {
db.execSQL("ALTER TABLE bookmarksLocations ADD COLUMN location_pic STRING;");
}catch (SQLiteException exception){
Timber.e(exception);//
}
return;
}
if (from >= 12) {
// Adding column `location_pic`
try {
db.execSQL(
"ALTER TABLE bookmarksLocations ADD COLUMN location_destroyed STRING;");
} catch (SQLiteException exception) {
Timber.e(exception);
"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){
if (from >= 13) {
try {
db.execSQL("ALTER TABLE bookmarksLocations ADD COLUMN location_language STRING;");
} catch (SQLiteException exception){
Timber.e(exception);
db.execSQL("ALTER TABLE $TABLE_NAME ADD COLUMN location_language STRING;")
} catch (exception: SQLiteException) {
Timber.e(exception)
}
}
if (from >= 14){
if (from >= 14) {
try {
db.execSQL("ALTER TABLE bookmarksLocations ADD COLUMN location_exists STRING;");
} catch (SQLiteException exception){
Timber.e(exception);
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.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.LinearLayoutManager;
import dagger.android.support.DaggerFragment;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.contributions.ContributionController;
import fr.free.nrw.commons.databinding.FragmentBookmarksLocationsBinding;
import fr.free.nrw.commons.nearby.Place;
import fr.free.nrw.commons.nearby.fragments.CommonPlaceClickActions;
import fr.free.nrw.commons.nearby.fragments.PlaceAdapter;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import kotlin.Unit;
import android.Manifest.permission
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
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.recyclerview.widget.LinearLayoutManager
import dagger.android.support.DaggerFragment
import fr.free.nrw.commons.R
import fr.free.nrw.commons.contributions.ContributionController
import fr.free.nrw.commons.databinding.FragmentBookmarksLocationsBinding
import fr.free.nrw.commons.filepicker.FilePicker
import fr.free.nrw.commons.nearby.fragments.CommonPlaceClickActions
import fr.free.nrw.commons.nearby.fragments.PlaceAdapter
import javax.inject.Inject
public class BookmarkLocationsFragment extends DaggerFragment {
public FragmentBookmarksLocationsBinding binding;
class BookmarkLocationsFragment : DaggerFragment() {
@Inject BookmarkLocationsController controller;
@Inject ContributionController contributionController;
@Inject BookmarkLocationsDao bookmarkLocationDao;
@Inject CommonPlaceClickActions commonPlaceClickActions;
private PlaceAdapter adapter;
private var _binding: FragmentBookmarksLocationsBinding? = null
private val binding get() = _binding!!
private final ActivityResultLauncher<Intent> cameraPickLauncherForResult =
registerForActivityResult(new StartActivityForResult(),
result -> {
contributionController.handleActivityResultWithCallback(requireActivity(), callbacks -> {
contributionController.onPictureReturnedFromCamera(result, requireActivity(), callbacks);
});
});
@Inject lateinit var controller: BookmarkLocationsController
@Inject lateinit var contributionController: ContributionController
@Inject lateinit var bookmarkLocationDao: BookmarkLocationsDao
@Inject lateinit var commonPlaceClickActions: CommonPlaceClickActions
private final ActivityResultLauncher<Intent> galleryPickLauncherForResult =
registerForActivityResult(new StartActivityForResult(),
result -> {
contributionController.handleActivityResultWithCallback(requireActivity(), callbacks -> {
contributionController.onPictureReturnedFromGallery(result, requireActivity(), callbacks);
});
});
private lateinit var adapter: PlaceAdapter
private lateinit var inAppCameraLocationPermissionLauncher
: ActivityResultLauncher<Array<String>>
private ActivityResultLauncher<String[]> inAppCameraLocationPermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), new ActivityResultCallback<Map<String, Boolean>>() {
@Override
public void onActivityResult(Map<String, Boolean> result) {
boolean areAllGranted = true;
for(final boolean b : result.values()) {
areAllGranted = areAllGranted && b;
}
private val cameraPickLauncherForResult =
registerForActivityResult(StartActivityForResult()) { result ->
contributionController.handleActivityResultWithCallback(
requireActivity(),
object: FilePicker.HandleActivityResult {
override fun onHandleActivityResult(callbacks: FilePicker.Callbacks) {
contributionController.onPictureReturnedFromCamera(
result,
requireActivity(),
callbacks
)
}
})
}
if (areAllGranted) {
contributionController.locationPermissionCallback.onLocationPermissionGranted();
} else {
if (shouldShowRequestPermissionRationale(permission.ACCESS_FINE_LOCATION)) {
contributionController.handleShowRationaleFlowCameraLocation(getActivity(), inAppCameraLocationPermissionLauncher, cameraPickLauncherForResult);
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) {
contributionController.locationPermissionCallback.onLocationPermissionGranted()
} else {
contributionController.locationPermissionCallback.onLocationPermissionDenied(getActivity().getString(R.string.in_app_camera_location_permission_denied));
if (shouldShowRequestPermissionRationale(permission.ACCESS_FINE_LOCATION)) {
contributionController.handleShowRationaleFlowCameraLocation(
activity,
inAppCameraLocationPermissionLauncher,
cameraPickLauncherForResult
)
} else {
contributionController.locationPermissionCallback.onLocationPermissionDenied(
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;
adapter = PlaceAdapter(
bookmarkLocationsDao = bookmarkLocationDao,
onBookmarkClicked = { place, _ ->
adapter.remove(place)
},
commonPlaceClickActions,
inAppCameraLocationPermissionLauncher,
galleryPickLauncherForResult,
cameraPickLauncherForResult
);
binding.listView.setAdapter(adapter);
commonPlaceClickActions = commonPlaceClickActions,
inAppCameraLocationPermissionLauncher = inAppCameraLocationPermissionLauncher,
galleryPickLauncherForResult = galleryPickLauncherForResult,
cameraPickLauncherForResult = cameraPickLauncherForResult
)
binding.listView.adapter = adapter
}
@Override
public void onResume() {
super.onResume();
initList();
override fun 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);
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.setVisibility(View.GONE);
binding.statusMessage.visibility = View.GONE
}
}
@Override
public void onDestroy() {
super.onDestroy();
binding = null;
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}