Added bookmark Wikidata items option (#4515)

* classes

* Almost done

* All done but build fail

* All done but build fail

* Modifications

* Modifications

* Fixed

* string resource

* minor change

* Maintained code conventions

* Moving

* Exception handled

* id name changed

* Test fail fixed

* Bookmark is available from other activities

* Test fail fixed

* Documentation error fixed

* Test added

* Revert Project_Default.xml

* Minor Change

* Revert changes
This commit is contained in:
Ayan Sarkar 2021-12-28 16:45:09 +05:30 committed by GitHub
parent 3c29f45f15
commit 38ed364423
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 1222 additions and 4 deletions

View file

@ -16,6 +16,7 @@ import butterknife.BindView;
import butterknife.ButterKnife;
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.bookmarks.items.BookmarkItemsFragment;
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsFragment;
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesFragment;
import fr.free.nrw.commons.category.CategoryImagesCallback;
@ -48,10 +49,14 @@ public class BookmarkListRootFragment extends CommonsDaggerSupportFragment imple
public BookmarkListRootFragment(Bundle bundle, BookmarksPagerAdapter bookmarksPagerAdapter) {
String title = bundle.getString("categoryName");
int order = bundle.getInt("order");
final int orderItem = bundle.getInt("orderItem");
if (order == 0) {
listFragment = new BookmarkPicturesFragment();
} else {
listFragment = new BookmarkLocationsFragment();
if(orderItem == 2) {
listFragment = new BookmarkItemsFragment();
}
}
Bundle featuredArguments = new Bundle();
featuredArguments.putString("categoryName", title);

View file

@ -44,6 +44,11 @@ public class BookmarksPagerAdapter extends FragmentPagerAdapter {
pages.add(new BookmarkPages(
new BookmarkListRootFragment(locationBundle, this),
context.getString(R.string.title_page_bookmarks_locations)));
locationBundle.putInt("orderItem", 2);
pages.add(new BookmarkPages(
new BookmarkListRootFragment(locationBundle, this),
context.getString(R.string.title_page_bookmarks_items)));
}
notifyDataSetChanged();
}

View file

@ -0,0 +1,54 @@
package fr.free.nrw.commons.bookmarks.items
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.RecyclerView
import com.facebook.drawee.view.SimpleDraweeView
import fr.free.nrw.commons.R
import fr.free.nrw.commons.explore.depictions.WikidataItemDetailsActivity
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
/**
* Helps to inflate Wikidata Items into Items tab
*/
class BookmarkItemsAdapter (val list: List<DepictedItem>, val context: Context) :
RecyclerView.Adapter<BookmarkItemsAdapter.BookmarkItemViewHolder>() {
class BookmarkItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var depictsLabel: TextView = itemView.findViewById(R.id.depicts_label)
var description: TextView = itemView.findViewById(R.id.description)
var depictsImage: SimpleDraweeView = itemView.findViewById(R.id.depicts_image)
var layout : ConstraintLayout = itemView.findViewById(R.id.layout_item)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BookmarkItemViewHolder {
val v: View = LayoutInflater.from(context)
.inflate(R.layout.item_depictions, parent, false)
return BookmarkItemViewHolder(v)
}
override fun onBindViewHolder(holder: BookmarkItemViewHolder, position: Int) {
val depictedItem = list[position]
holder.depictsLabel.text = depictedItem.name
holder.description.text = depictedItem.description
if (depictedItem.imageUrl?.isNotBlank() == true) {
holder.depictsImage.setImageURI(depictedItem.imageUrl)
} else {
holder.depictsImage.setActualImageResource(R.drawable.ic_wikidata_logo_24dp)
}
holder.layout.setOnClickListener {
WikidataItemDetailsActivity.startYourself(context, depictedItem)
}
}
override fun getItemCount(): Int {
return list.size
}
}

View file

@ -0,0 +1,129 @@
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.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 {
private static final String BASE_PATH = "bookmarksItems";
public static final Uri BASE_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);
}
@Inject
DBOpenHelper dbOpenHelper;
@Override
public String getType(@NonNull final Uri uri) {
return null;
}
/**
* Queries the SQLite database for the 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;
}
/**
* 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 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,
contentValues,
COLUMN_ID + " = ?",
new String[]{String.valueOf(id)});
} else {
throw new IllegalArgumentException(
"Parameter `selection` should be empty when updating an ID");
}
getContext().getContentResolver().notifyChange(uri, null);
return rowsUpdated;
}
/**
* Handles the insertion of new bookmark items record to local SQLite Database
* @param uri
* @param contentValues
* @return
*/
@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);
}
/**
* Handles the deletion of new bookmark items record to local SQLite Database
* @param uri
* @param s
* @param strings
* @return
*/
@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(
TABLE_NAME,
"item_id = ?",
new String[]{uri.getLastPathSegment()}
);
getContext().getContentResolver().notifyChange(uri, null);
return rows;
}
}

View file

@ -0,0 +1,27 @@
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;
/**
* Handles loading bookmarked items from Database
*/
@Singleton
public class BookmarkItemsController {
@Inject
BookmarkItemsDao bookmarkItemsDao;
@Inject
public BookmarkItemsController() {}
/**
* Load from DB the bookmarked items
* @return a list of DepictedItem objects.
*/
public List<DepictedItem> loadFavoritesItems() {
return bookmarkItemsDao.getAllBookmarksItems();
}
}

View file

@ -0,0 +1,282 @@
package fr.free.nrw.commons.bookmarks.items;
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.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;
}
/**
* 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));
}
} catch (final RemoteException e) {
throw new RuntimeException(e);
} finally {
db.release();
}
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 ?
*/
public boolean updateBookmarkItem(final DepictedItem depictedItem) {
final boolean bookmarkExists = findBookmarkItem(depictedItem.getId());
if (bookmarkExists) {
deleteBookmarkItem(depictedItem);
} else {
addBookmarkItem(depictedItem);
}
return !bookmarkExists;
}
/**
* Add a Bookmark to database
* @param depictedItem : Bookmark to add
*/
private void addBookmarkItem(final DepictedItem depictedItem) {
final ContentProviderClient db = clientProvider.get();
try {
db.insert(BookmarkItemsContentProvider.BASE_URI, toContentValues(depictedItem));
} catch (final RemoteException e) {
throw new RuntimeException(e);
} finally {
db.release();
}
}
/**
* Delete a bookmark from database
* @param depictedItem : Bookmark to delete
*/
private void deleteBookmarkItem(final DepictedItem depictedItem) {
final ContentProviderClient db = clientProvider.get();
try {
db.delete(BookmarkItemsContentProvider.uriForName(depictedItem.getId()), null, null);
} catch (final RemoteException e) {
throw new RuntimeException(e);
} finally {
db.release();
}
}
/**
* Find a bookmark from database based on its name
* @param depictedItemID : Bookmark to find
* @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;
}
} catch (final RemoteException e) {
throw new RuntimeException(e);
} finally {
db.release();
}
return false;
}
/**
* Recives real data from cursor
* @param cursor : Object for storing database data
* @return DepictedItem
*/
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 categoryListString = cursor.getString(cursor
.getColumnIndex(Table.COLUMN_CATEGORIES_LIST));
final List<String> categoryList = StringToArray(categoryListString);
final boolean isSelected
= Boolean.parseBoolean(cursor.getString(cursor
.getColumnIndex(Table.COLUMN_IS_SELECTED)));
final String id = cursor.getString(cursor.getColumnIndex(Table.COLUMN_ID));
return new DepictedItem(
fileName,
description,
imageUrl,
instanceList,
categoryList,
isSelected,
id
);
}
/**
* Converts string to List
* @param listString comma separated single string from of list items
* @return List of string
*/
private List<String> StringToArray(final String listString) {
final String[] elements = listString.split(",");
return Arrays.asList(elements);
}
/**
* Converts string to List
* @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;
}
/**
* Takes data from DepictedItem and create a content value object
* @param depictedItem depicted item
* @return ContentValues
*/
private ContentValues toContentValues(final DepictedItem depictedItem) {
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_LIST, ArrayToString(depictedItem.getCommonsCategories()));
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_LIST = "item_categories";
public static final String COLUMN_IS_SELECTED = "item_is_selected";
public static final String COLUMN_ID = "item_id";
public static final String[] ALL_FIELDS = {
COLUMN_NAME,
COLUMN_DESCRIPTION,
COLUMN_IMAGE,
COLUMN_INSTANCE_LIST,
COLUMN_CATEGORIES_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_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);
}
/**
* Deletes database
* @param db SQLiteDatabase
*/
public static void onDelete(final SQLiteDatabase db) {
db.execSQL(DROP_TABLE_STATEMENT);
onCreate(db);
}
/**
* Updates database
* @param db SQLiteDatabase
* @param from starting
* @param to end
*/
public static void onUpdate(final SQLiteDatabase db, int from, final int to) {
if (from == to) {
return;
}
if (from < 7) {
from++;
onUpdate(db, from, to);
return;
}
if (from == 7) {
onCreate(db);
from++;
onUpdate(db, from, to);
return;
}
if (from == 8) {
from++;
onUpdate(db, from, to);
}
}
}
}

View file

@ -0,0 +1,89 @@
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 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 butterknife.BindView;
import butterknife.ButterKnife;
import dagger.android.support.DaggerFragment;
import fr.free.nrw.commons.R;
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 {
@BindView(R.id.status_message)
TextView statusTextView;
@BindView(R.id.loading_images_progress_bar)
ProgressBar progressBar;
@BindView(R.id.list_view)
RecyclerView recyclerView;
@BindView(R.id.parent_layout)
RelativeLayout parentLayout;
@Inject
BookmarkItemsController controller;
public static BookmarkItemsFragment newInstance() {
return new BookmarkItemsFragment();
}
@Override
public View onCreateView(
@NonNull final LayoutInflater inflater,
final ViewGroup container,
final Bundle savedInstanceState
) {
final View v = inflater.inflate(R.layout.fragment_bookmarks_items, container, false);
ButterKnife.bind(this, v);
return v;
}
@Override
public void onViewCreated(final @NotNull View view, @Nullable final Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
progressBar.setVisibility(View.VISIBLE);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
initList(requireContext());
}
@Override
public void 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);
recyclerView.setAdapter(adapter);
progressBar.setVisibility(View.GONE);
if (depictItems.isEmpty()) {
statusTextView.setText(R.string.bookmark_empty);
statusTextView.setVisibility(View.VISIBLE);
} else {
statusTextView.setVisibility(View.GONE);
}
}
}