Displaying Category image and Description (#4531)

* API call done

* API call

* Image implementation done

* Gradle

* Java docs and code convention

* Description added

* Refactoring Category

* Refactoring Category

* Refactoring Category

* Description and thumbnail issue fixed

* Description and thumbnail issue fixed

* Minor issue fixed

* Minor issue fixed

* Server changed

* Logo changed

* Change in structure

* Fixed failed tests

* Fixed Test failed

* Optimized imports

* Dialog can't be dismissed

* Dialog can't be dismissed

* Resolved Conflicts

* UI fixed

* Added description and thumbnail in local DB

* Added description and thumbnail in local DB

* Test fixed

* Added

* Updated with latest master

* Test Updated with latest master

* Issue fixed

* Revert gradle changes

* Revert gradle changes

* Update gradle-wrapper.properties

* Require Api removed
This commit is contained in:
Ayan Sarkar 2022-01-20 11:49:57 +05:30 committed by GitHub
parent 0914eeea53
commit 92957f4204
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 373 additions and 151 deletions

View file

@ -5,6 +5,7 @@ import android.content.ContentValues;
import android.database.Cursor; import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.os.RemoteException; import android.os.RemoteException;
import fr.free.nrw.commons.category.CategoryItem;
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem; import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -141,9 +142,17 @@ public class BookmarkItemsDao {
final String instanceListString final String instanceListString
= cursor.getString(cursor.getColumnIndex(Table.COLUMN_INSTANCE_LIST)); = cursor.getString(cursor.getColumnIndex(Table.COLUMN_INSTANCE_LIST));
final List<String> instanceList = StringToArray(instanceListString); final List<String> instanceList = StringToArray(instanceListString);
final String categoryListString = cursor.getString(cursor final String categoryNameListString = cursor.getString(cursor
.getColumnIndex(Table.COLUMN_CATEGORIES_LIST)); .getColumnIndex(Table.COLUMN_CATEGORIES_NAME_LIST));
final List<String> categoryList = StringToArray(categoryListString); 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 final boolean isSelected
= Boolean.parseBoolean(cursor.getString(cursor = Boolean.parseBoolean(cursor.getString(cursor
.getColumnIndex(Table.COLUMN_IS_SELECTED))); .getColumnIndex(Table.COLUMN_IS_SELECTED)));
@ -160,6 +169,17 @@ public class BookmarkItemsDao {
); );
} }
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));
}
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 of list items
@ -188,12 +208,35 @@ public class BookmarkItemsDao {
* @return ContentValues * @return ContentValues
*/ */
private ContentValues toContentValues(final DepictedItem depictedItem) { private ContentValues toContentValues(final DepictedItem depictedItem) {
final List<String> namesOfCommonsCategories = new ArrayList<>();
for (final CategoryItem category :
depictedItem.getCommonsCategories()) {
namesOfCommonsCategories.add(category.getName());
}
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(); final ContentValues cv = new ContentValues();
cv.put(Table.COLUMN_NAME, depictedItem.getName()); cv.put(Table.COLUMN_NAME, depictedItem.getName());
cv.put(Table.COLUMN_DESCRIPTION, depictedItem.getDescription()); cv.put(Table.COLUMN_DESCRIPTION, depictedItem.getDescription());
cv.put(Table.COLUMN_IMAGE, depictedItem.getImageUrl()); cv.put(Table.COLUMN_IMAGE, depictedItem.getImageUrl());
cv.put(Table.COLUMN_INSTANCE_LIST, ArrayToString(depictedItem.getInstanceOfs())); cv.put(Table.COLUMN_INSTANCE_LIST, ArrayToString(depictedItem.getInstanceOfs()));
cv.put(Table.COLUMN_CATEGORIES_LIST, ArrayToString(depictedItem.getCommonsCategories())); 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_IS_SELECTED, depictedItem.isSelected());
cv.put(Table.COLUMN_ID, depictedItem.getId()); cv.put(Table.COLUMN_ID, depictedItem.getId());
return cv; return cv;
@ -208,7 +251,9 @@ public class BookmarkItemsDao {
public static final String COLUMN_DESCRIPTION = "item_description"; public static final String COLUMN_DESCRIPTION = "item_description";
public static final String COLUMN_IMAGE = "item_image_url"; public static final String COLUMN_IMAGE = "item_image_url";
public static final String COLUMN_INSTANCE_LIST = "item_instance_of"; public static final String COLUMN_INSTANCE_LIST = "item_instance_of";
public static final String COLUMN_CATEGORIES_LIST = "item_categories"; 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_IS_SELECTED = "item_is_selected";
public static final String COLUMN_ID = "item_id"; public static final String COLUMN_ID = "item_id";
@ -217,7 +262,9 @@ public class BookmarkItemsDao {
COLUMN_DESCRIPTION, COLUMN_DESCRIPTION,
COLUMN_IMAGE, COLUMN_IMAGE,
COLUMN_INSTANCE_LIST, COLUMN_INSTANCE_LIST,
COLUMN_CATEGORIES_LIST, COLUMN_CATEGORIES_NAME_LIST,
COLUMN_CATEGORIES_DESCRIPTION_LIST,
COLUMN_CATEGORIES_THUMBNAIL_LIST,
COLUMN_IS_SELECTED, COLUMN_IS_SELECTED,
COLUMN_ID COLUMN_ID
}; };
@ -228,7 +275,9 @@ public class BookmarkItemsDao {
+ COLUMN_DESCRIPTION + " STRING," + COLUMN_DESCRIPTION + " STRING,"
+ COLUMN_IMAGE + " STRING," + COLUMN_IMAGE + " STRING,"
+ COLUMN_INSTANCE_LIST + " STRING," + COLUMN_INSTANCE_LIST + " STRING,"
+ COLUMN_CATEGORIES_LIST + " STRING," + COLUMN_CATEGORIES_NAME_LIST + " STRING,"
+ COLUMN_CATEGORIES_DESCRIPTION_LIST + " STRING,"
+ COLUMN_CATEGORIES_THUMBNAIL_LIST + " STRING,"
+ COLUMN_IS_SELECTED + " STRING," + COLUMN_IS_SELECTED + " STRING,"
+ COLUMN_ID + " STRING PRIMARY KEY" + COLUMN_ID + " STRING PRIMARY KEY"
+ ");"; + ");";

View file

@ -56,7 +56,7 @@ class CategoriesModel @Inject constructor(
// Newly used category... // Newly used category...
if (category == null) { if (category == null) {
category = Category(null, item.name, Date(), 0) category = Category(null, item.name, item.description, item.thumbnail, Date(), 0)
} }
category.incTimesUsed() category.incTimesUsed()
categoryDao.save(category) categoryDao.save(category)
@ -74,14 +74,14 @@ class CategoriesModel @Inject constructor(
selectedDepictions: List<DepictedItem> selectedDepictions: List<DepictedItem>
): Observable<List<CategoryItem>> { ): Observable<List<CategoryItem>> {
return suggestionsOrSearch(term, imageTitleList, selectedDepictions) return suggestionsOrSearch(term, imageTitleList, selectedDepictions)
.map { it.map { CategoryItem(it, false) } } .map { it.map { CategoryItem(it.name, it.description, it.thumbnail, false) } }
} }
private fun suggestionsOrSearch( private fun suggestionsOrSearch(
term: String, term: String,
imageTitleList: List<String>, imageTitleList: List<String>,
selectedDepictions: List<DepictedItem> selectedDepictions: List<DepictedItem>
): Observable<List<String>> { ): Observable<List<CategoryItem>> {
return if (TextUtils.isEmpty(term)) return if (TextUtils.isEmpty(term))
Observable.combineLatest( Observable.combineLatest(
categoriesFromDepiction(selectedDepictions), categoriesFromDepiction(selectedDepictions),
@ -100,10 +100,10 @@ class CategoriesModel @Inject constructor(
Observable.just(selectedDepictions.map { it.commonsCategories }.flatten()) Observable.just(selectedDepictions.map { it.commonsCategories }.flatten())
private fun combine( private fun combine(
depictionCategories: List<String>, depictionCategories: List<CategoryItem>,
locationCategories: List<String>, locationCategories: List<CategoryItem>,
titles: List<String>, titles: List<CategoryItem>,
recents: List<String> recents: List<CategoryItem>
) = depictionCategories + locationCategories + titles + recents ) = depictionCategories + locationCategories + titles + recents
@ -115,7 +115,7 @@ class CategoriesModel @Inject constructor(
private fun titleCategories(titleList: List<String>) = private fun titleCategories(titleList: List<String>) =
if (titleList.isNotEmpty()) if (titleList.isNotEmpty())
Observable.combineLatest(titleList.map { getTitleCategories(it) }) { searchResults -> Observable.combineLatest(titleList.map { getTitleCategories(it) }) { searchResults ->
searchResults.map { it as List<String> }.flatten() searchResults.map { it as List<CategoryItem> }.flatten()
} }
else else
Observable.just(emptyList()) Observable.just(emptyList())
@ -125,7 +125,7 @@ class CategoriesModel @Inject constructor(
* @param title * @param title
* @return * @return
*/ */
private fun getTitleCategories(title: String): Observable<List<String>> { private fun getTitleCategories(title: String): Observable<List<CategoryItem>> {
return categoryClient.searchCategories(title, SEARCH_CATS_LIMIT).toObservable() return categoryClient.searchCategories(title, SEARCH_CATS_LIMIT).toObservable()
} }

View file

@ -10,15 +10,19 @@ import java.util.Date;
public class Category { public class Category {
private Uri contentUri; private Uri contentUri;
private String name; private String name;
private String description;
private String thumbnail;
private Date lastUsed; private Date lastUsed;
private int timesUsed; private int timesUsed;
public Category() { public Category() {
} }
public Category(Uri contentUri, String name, Date lastUsed, int timesUsed) { public Category(Uri contentUri, String name, String description, String thumbnail, Date lastUsed, int timesUsed) {
this.contentUri = contentUri; this.contentUri = contentUri;
this.name = name; this.name = name;
this.description = description;
this.thumbnail = thumbnail;
this.lastUsed = lastUsed; this.lastUsed = lastUsed;
this.timesUsed = timesUsed; this.timesUsed = timesUsed;
} }
@ -93,4 +97,19 @@ public class Category {
this.contentUri = contentUri; this.contentUri = contentUri;
} }
public String getDescription() {
return description;
}
public String getThumbnail() {
return thumbnail;
}
public void setDescription(final String description) {
this.description = description;
}
public void setThumbnail(final String thumbnail) {
this.thumbnail = thumbnail;
}
} }

View file

@ -16,7 +16,7 @@ const val CATEGORY_NEEDING_CATEGORIES = "needing categories"
*/ */
@Singleton @Singleton
class CategoryClient @Inject constructor(private val categoryInterface: CategoryInterface) : class CategoryClient @Inject constructor(private val categoryInterface: CategoryInterface) :
ContinuationClient<MwQueryResponse, String>() { ContinuationClient<MwQueryResponse, CategoryItem>() {
/** /**
* Searches for categories containing the specified string. * Searches for categories containing the specified string.
@ -28,7 +28,7 @@ class CategoryClient @Inject constructor(private val categoryInterface: Category
*/ */
@JvmOverloads @JvmOverloads
fun searchCategories(filter: String?, itemLimit: Int, offset: Int = 0): fun searchCategories(filter: String?, itemLimit: Int, offset: Int = 0):
Single<List<String>> { Single<List<CategoryItem>> {
return responseMapper(categoryInterface.searchCategories(filter, itemLimit, offset)) return responseMapper(categoryInterface.searchCategories(filter, itemLimit, offset))
} }
@ -42,7 +42,7 @@ class CategoryClient @Inject constructor(private val categoryInterface: Category
*/ */
@JvmOverloads @JvmOverloads
fun searchCategoriesForPrefix(prefix: String?, itemLimit: Int, offset: Int = 0): fun searchCategoriesForPrefix(prefix: String?, itemLimit: Int, offset: Int = 0):
Single<List<String>> { Single<List<CategoryItem>> {
return responseMapper( return responseMapper(
categoryInterface.searchCategoriesForPrefix(prefix, itemLimit, offset) categoryInterface.searchCategoriesForPrefix(prefix, itemLimit, offset)
) )
@ -55,7 +55,7 @@ class CategoryClient @Inject constructor(private val categoryInterface: Category
* @param categoryName Category name as defined on commons * @param categoryName Category name as defined on commons
* @return Observable emitting the categories returned. If our search yielded "Category:Test", "Test" is emitted. * @return Observable emitting the categories returned. If our search yielded "Category:Test", "Test" is emitted.
*/ */
fun getSubCategoryList(categoryName: String): Single<List<String>> { fun getSubCategoryList(categoryName: String): Single<List<CategoryItem>> {
return continuationRequest(SUB_CATEGORY_CONTINUATION_PREFIX, categoryName) { return continuationRequest(SUB_CATEGORY_CONTINUATION_PREFIX, categoryName) {
categoryInterface.getSubCategoryList( categoryInterface.getSubCategoryList(
categoryName, it categoryName, it
@ -70,7 +70,7 @@ class CategoryClient @Inject constructor(private val categoryInterface: Category
* @param categoryName Category name as defined on commons * @param categoryName Category name as defined on commons
* @return * @return
*/ */
fun getParentCategoryList(categoryName: String): Single<List<String>> { fun getParentCategoryList(categoryName: String): Single<List<CategoryItem>> {
return continuationRequest(PARENT_CATEGORY_CONTINUATION_PREFIX, categoryName) { return continuationRequest(PARENT_CATEGORY_CONTINUATION_PREFIX, categoryName) {
categoryInterface.getParentCategoryList(categoryName, it) categoryInterface.getParentCategoryList(categoryName, it)
} }
@ -87,7 +87,7 @@ class CategoryClient @Inject constructor(private val categoryInterface: Category
override fun responseMapper( override fun responseMapper(
networkResult: Single<MwQueryResponse>, networkResult: Single<MwQueryResponse>,
key: String? key: String?
): Single<List<String>> { ): Single<List<CategoryItem>> {
return networkResult return networkResult
.map { .map {
handleContinuationResponse(it.continuation(), key) handleContinuationResponse(it.continuation(), key)
@ -96,7 +96,10 @@ class CategoryClient @Inject constructor(private val categoryInterface: Category
.map { .map {
it.filter { it.filter {
page -> page.categoryInfo() == null || !page.categoryInfo().isHidden page -> page.categoryInfo() == null || !page.categoryInfo().isHidden
}.map { page -> page.title().replace(CATEGORY_PREFIX, "") } }.map {
CategoryItem(it.title().replace(CATEGORY_PREFIX, ""),
it.description().toString(), it.thumbUrl().toString(), false)
}
} }
} }
} }

View file

@ -79,8 +79,8 @@ public class CategoryDao {
* @return a list containing recent categories * @return a list containing recent categories
*/ */
@NonNull @NonNull
List<String> recentCategories(int limit) { List<CategoryItem> recentCategories(int limit) {
List<String> items = new ArrayList<>(); List<CategoryItem> items = new ArrayList<>();
Cursor cursor = null; Cursor cursor = null;
ContentProviderClient db = clientProvider.get(); ContentProviderClient db = clientProvider.get();
try { try {
@ -93,7 +93,9 @@ public class CategoryDao {
// fixme add a limit on the original query instead of falling out of the loop? // fixme add a limit on the original query instead of falling out of the loop?
while (cursor != null && cursor.moveToNext() while (cursor != null && cursor.moveToNext()
&& cursor.getPosition() < limit) { && cursor.getPosition() < limit) {
items.add(fromCursor(cursor).getName()); items.add(new CategoryItem(fromCursor(cursor).getName(),
fromCursor(cursor).getDescription(), fromCursor(cursor).getThumbnail(),
false));
} }
} catch (RemoteException e) { } catch (RemoteException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
@ -112,6 +114,8 @@ public class CategoryDao {
return new Category( return new Category(
CategoryContentProvider.uriForId(cursor.getInt(cursor.getColumnIndex(Table.COLUMN_ID))), CategoryContentProvider.uriForId(cursor.getInt(cursor.getColumnIndex(Table.COLUMN_ID))),
cursor.getString(cursor.getColumnIndex(Table.COLUMN_NAME)), cursor.getString(cursor.getColumnIndex(Table.COLUMN_NAME)),
cursor.getString(cursor.getColumnIndex(Table.COLUMN_DESCRIPTION)),
cursor.getString(cursor.getColumnIndex(Table.COLUMN_THUMBNAIL)),
new Date(cursor.getLong(cursor.getColumnIndex(Table.COLUMN_LAST_USED))), new Date(cursor.getLong(cursor.getColumnIndex(Table.COLUMN_LAST_USED))),
cursor.getInt(cursor.getColumnIndex(Table.COLUMN_TIMES_USED)) cursor.getInt(cursor.getColumnIndex(Table.COLUMN_TIMES_USED))
); );
@ -120,6 +124,8 @@ public class CategoryDao {
private ContentValues toContentValues(Category category) { private ContentValues toContentValues(Category category) {
ContentValues cv = new ContentValues(); ContentValues cv = new ContentValues();
cv.put(CategoryDao.Table.COLUMN_NAME, category.getName()); cv.put(CategoryDao.Table.COLUMN_NAME, category.getName());
cv.put(Table.COLUMN_DESCRIPTION, category.getDescription());
cv.put(Table.COLUMN_THUMBNAIL, category.getThumbnail());
cv.put(CategoryDao.Table.COLUMN_LAST_USED, category.getLastUsed().getTime()); cv.put(CategoryDao.Table.COLUMN_LAST_USED, category.getLastUsed().getTime());
cv.put(CategoryDao.Table.COLUMN_TIMES_USED, category.getTimesUsed()); cv.put(CategoryDao.Table.COLUMN_TIMES_USED, category.getTimesUsed());
return cv; return cv;
@ -130,6 +136,8 @@ public class CategoryDao {
public static final String COLUMN_ID = "_id"; public static final String COLUMN_ID = "_id";
static final String COLUMN_NAME = "name"; static final String COLUMN_NAME = "name";
static final String COLUMN_DESCRIPTION = "description";
static final String COLUMN_THUMBNAIL = "thumbnail";
static final String COLUMN_LAST_USED = "last_used"; static final String COLUMN_LAST_USED = "last_used";
static final String COLUMN_TIMES_USED = "times_used"; static final String COLUMN_TIMES_USED = "times_used";
@ -137,6 +145,8 @@ public class CategoryDao {
public static final String[] ALL_FIELDS = { public static final String[] ALL_FIELDS = {
COLUMN_ID, COLUMN_ID,
COLUMN_NAME, COLUMN_NAME,
COLUMN_DESCRIPTION,
COLUMN_THUMBNAIL,
COLUMN_LAST_USED, COLUMN_LAST_USED,
COLUMN_TIMES_USED COLUMN_TIMES_USED
}; };
@ -146,6 +156,8 @@ public class CategoryDao {
static final String CREATE_TABLE_STATEMENT = "CREATE TABLE " + TABLE_NAME + " (" static final String CREATE_TABLE_STATEMENT = "CREATE TABLE " + TABLE_NAME + " ("
+ COLUMN_ID + " INTEGER PRIMARY KEY," + COLUMN_ID + " INTEGER PRIMARY KEY,"
+ COLUMN_NAME + " STRING," + COLUMN_NAME + " STRING,"
+ COLUMN_DESCRIPTION + " STRING,"
+ COLUMN_THUMBNAIL + " STRING,"
+ COLUMN_LAST_USED + " INTEGER," + COLUMN_LAST_USED + " INTEGER,"
+ COLUMN_TIMES_USED + " INTEGER" + COLUMN_TIMES_USED + " INTEGER"
+ ");"; + ");";

View file

@ -94,8 +94,14 @@ public class CategoryEditSearchRecyclerViewAdapter
@Override @Override
protected FilterResults performFiltering(CharSequence constraint) { protected FilterResults performFiltering(CharSequence constraint) {
FilterResults results = new FilterResults(); FilterResults results = new FilterResults();
List<String> resultCategories = categoryClient.searchCategories(constraint.toString(), 10).blockingGet(); List<CategoryItem> resultCategories = categoryClient
results.values = resultCategories; .searchCategories(constraint.toString(), 10).blockingGet();
final List<String> namesOfCommonsCategories = new ArrayList<>();
for (final CategoryItem category :
resultCategories) {
namesOfCommonsCategories.add(category.getName());
}
results.values = namesOfCommonsCategories;
results.count = resultCategories.size(); results.count = resultCategories.size();
return results; return results;
} }

View file

@ -20,9 +20,11 @@ public interface CategoryInterface {
* @return * @return
*/ */
@GET("w/api.php?action=query&format=json&formatversion=2" @GET("w/api.php?action=query&format=json&formatversion=2"
+ "&generator=search&gsrnamespace=14") + "&generator=search&prop=description|pageimages&piprop=thumbnail&pithumbsize=70"
+ "&gsrnamespace=14")
Single<MwQueryResponse> searchCategories(@Query("gsrsearch") String filter, Single<MwQueryResponse> searchCategories(@Query("gsrsearch") String filter,
@Query("gsrlimit") int itemLimit, @Query("gsroffset") int offset); @Query("gsrlimit") int itemLimit,
@Query("gsroffset") int offset);
/** /**
* Searches for categories starting with the specified prefix. * Searches for categories starting with the specified prefix.
@ -32,9 +34,11 @@ public interface CategoryInterface {
* @return * @return
*/ */
@GET("w/api.php?action=query&format=json&formatversion=2" @GET("w/api.php?action=query&format=json&formatversion=2"
+ "&generator=allcategories&prop=categoryinfo") + "&generator=allcategories&prop=categoryinfo|description|pageimages&piprop=thumbnail"
+ "&pithumbsize=70")
Single<MwQueryResponse> searchCategoriesForPrefix(@Query("gacprefix") String prefix, Single<MwQueryResponse> searchCategoriesForPrefix(@Query("gacprefix") String prefix,
@Query("gaclimit") int itemLimit, @Query("gacoffset") int offset); @Query("gaclimit") int itemLimit,
@Query("gacoffset") int offset);
@GET("w/api.php?action=query&format=json&formatversion=2" @GET("w/api.php?action=query&format=json&formatversion=2"
+ "&generator=categorymembers&gcmtype=subcat" + "&generator=categorymembers&gcmtype=subcat"

View file

@ -4,7 +4,8 @@ import android.os.Parcelable
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
@Parcelize @Parcelize
data class CategoryItem(val name: String, var isSelected: Boolean) : Parcelable { data class CategoryItem(val name: String, val description: String,
val thumbnail: String, var isSelected: Boolean) : Parcelable {
override fun toString(): String { override fun toString(): String {
return "CategoryItem: '$name'" return "CategoryItem: '$name'"

View file

@ -250,8 +250,10 @@ public class NetworkingModule {
@Provides @Provides
@Singleton @Singleton
public CategoryInterface provideCategoryInterface(@Named(NAMED_COMMONS_WIKI_SITE) WikiSite commonsWikiSite) { public CategoryInterface provideCategoryInterface(
return ServiceFactory.get(commonsWikiSite, BuildConfig.COMMONS_URL, CategoryInterface.class); @Named(NAMED_COMMONS_WIKI_SITE) WikiSite commonsWikiSite) {
return ServiceFactory
.get(commonsWikiSite, BuildConfig.COMMONS_URL, CategoryInterface.class);
} }
@Provides @Provides

View file

@ -14,6 +14,6 @@ class PageableParentCategoriesDataSource @Inject constructor(
if (startPosition == 0) { if (startPosition == 0) {
categoryClient.resetParentCategoryContinuation(query) categoryClient.resetParentCategoryContinuation(query)
} }
categoryClient.getParentCategoryList(query).blockingGet() categoryClient.getParentCategoryList(query).blockingGet().map { it.name }
} }
} }

View file

@ -12,5 +12,6 @@ class PageableSearchCategoriesDataSource @Inject constructor(
override val loadFunction = { loadSize: Int, startPosition: Int -> override val loadFunction = { loadSize: Int, startPosition: Int ->
categoryClient.searchCategories(query, loadSize, startPosition).blockingGet() categoryClient.searchCategories(query, loadSize, startPosition).blockingGet()
.map { it.name }
} }
} }

View file

@ -14,6 +14,6 @@ class PageableSubCategoriesDataSource @Inject constructor(
if (startPosition == 0) { if (startPosition == 0) {
categoryClient.resetSubCategoryContinuation(query) categoryClient.resetSubCategoryContinuation(query)
} }
categoryClient.getSubCategoryList(query).blockingGet() categoryClient.getSubCategoryList(query).blockingGet().map { it.name }
} }
} }

View file

@ -3,6 +3,7 @@ package fr.free.nrw.commons.mwapi;
import static fr.free.nrw.commons.category.CategoryClientKt.CATEGORY_PREFIX; import static fr.free.nrw.commons.category.CategoryClientKt.CATEGORY_PREFIX;
import com.google.gson.Gson; import com.google.gson.Gson;
import fr.free.nrw.commons.category.CategoryItem;
import io.reactivex.Single; import io.reactivex.Single;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@ -40,7 +41,7 @@ public class CategoryApi {
this.gson = gson; this.gson = gson;
} }
public Single<List<String>> request(String coords) { public Single<List<CategoryItem>> request(String coords) {
return Single.fromCallable(() -> { return Single.fromCallable(() -> {
HttpUrl apiUrl = buildUrl(coords); HttpUrl apiUrl = buildUrl(coords);
Timber.d("URL: %s", apiUrl.toString()); Timber.d("URL: %s", apiUrl.toString());
@ -53,12 +54,12 @@ public class CategoryApi {
} }
MwQueryResponse apiResponse = gson.fromJson(body.charStream(), MwQueryResponse.class); MwQueryResponse apiResponse = gson.fromJson(body.charStream(), MwQueryResponse.class);
Set<String> categories = new LinkedHashSet<>(); Set<CategoryItem> categories = new LinkedHashSet<>();
if (apiResponse != null && apiResponse.query() != null && apiResponse.query().pages() != null) { if (apiResponse != null && apiResponse.query() != null && apiResponse.query().pages() != null) {
for (MwQueryPage page : apiResponse.query().pages()) { for (MwQueryPage page : apiResponse.query().pages()) {
if (page.categories() != null) { if (page.categories() != null) {
for (MwQueryPage.Category category : page.categories()) { for (MwQueryPage.Category category : page.categories()) {
categories.add(category.title().replace(CATEGORY_PREFIX, "")); categories.add(new CategoryItem(category.title().replace(CATEGORY_PREFIX, ""), "", "", false));
} }
} }
} }

View file

@ -1,18 +1,19 @@
package fr.free.nrw.commons.upload package fr.free.nrw.commons.upload
import fr.free.nrw.commons.category.CategoryItem
import io.reactivex.subjects.BehaviorSubject import io.reactivex.subjects.BehaviorSubject
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@Singleton @Singleton
class GpsCategoryModel @Inject constructor() { class GpsCategoryModel @Inject constructor() {
val categoriesFromLocation = BehaviorSubject.createDefault(emptyList<String>()) val categoriesFromLocation = BehaviorSubject.createDefault(emptyList<CategoryItem>())
fun clear() { fun clear() {
categoriesFromLocation.onNext(emptyList()) categoriesFromLocation.onNext(emptyList())
} }
fun setCategoriesFromLocation(categoryList: List<String>) { fun setCategoriesFromLocation(categoryList: List<CategoryItem>) {
categoriesFromLocation.onNext(categoryList) categoriesFromLocation.onNext(categoryList)
} }
} }

View file

@ -1,8 +1,10 @@
package fr.free.nrw.commons.upload.categories package fr.free.nrw.commons.upload.categories
import fr.free.nrw.commons.category.CategoryItem import fr.free.nrw.commons.category.CategoryItem
import org.jetbrains.annotations.NotNull
class UploadCategoryAdapter(onCategoryClicked: (CategoryItem) -> Unit) : class UploadCategoryAdapter(
onCategoryClicked: @NotNull() (CategoryItem) -> Unit) :
BaseDelegateAdapter<CategoryItem>( BaseDelegateAdapter<CategoryItem>(
uploadCategoryDelegate(onCategoryClicked), uploadCategoryDelegate(onCategoryClicked),
areItemsTheSame = { oldItem, newItem -> oldItem.name == newItem.name }, areItemsTheSame = { oldItem, newItem -> oldItem.name == newItem.name },

View file

@ -1,20 +1,38 @@
package fr.free.nrw.commons.upload.categories package fr.free.nrw.commons.upload.categories
import android.view.View
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import fr.free.nrw.commons.R
import fr.free.nrw.commons.category.CategoryItem import fr.free.nrw.commons.category.CategoryItem
import fr.free.nrw.commons.databinding.LayoutUploadCategoriesItemBinding import fr.free.nrw.commons.databinding.LayoutUploadCategoriesItemBinding
fun uploadCategoryDelegate(onCategoryClicked: (CategoryItem) -> Unit) = fun uploadCategoryDelegate(onCategoryClicked: (CategoryItem) -> Unit) =
adapterDelegateViewBinding<CategoryItem, CategoryItem, LayoutUploadCategoriesItemBinding>({ layoutInflater, root -> adapterDelegateViewBinding<CategoryItem, CategoryItem,
LayoutUploadCategoriesItemBinding>({ layoutInflater, root ->
LayoutUploadCategoriesItemBinding.inflate(layoutInflater, root, false) LayoutUploadCategoriesItemBinding.inflate(layoutInflater, root, false)
}) { }) {
binding.root.setOnClickListener { val onClickListener = { _: View? ->
item.isSelected = !item.isSelected item.isSelected = !item.isSelected
binding.uploadCategoryCheckbox.isChecked = item.isSelected binding.uploadCategoryCheckbox.isChecked = item.isSelected
onCategoryClicked(item) onCategoryClicked(item)
} }
binding.root.setOnClickListener(onClickListener)
binding.uploadCategoryCheckbox.setOnClickListener(onClickListener)
bind { bind {
binding.uploadCategoryCheckbox.isChecked = item.isSelected binding.uploadCategoryCheckbox.isChecked = item.isSelected
binding.uploadCategoryCheckbox.text = item.name binding.categoryLabel.text = item.name
if(item.thumbnail != "null") {
binding.categoryImage.setImageURI(item.thumbnail)
} else {
binding.categoryImage.setActualImageResource(R.drawable.commons)
}
if(item.description != "null") {
binding.categoryDescription.text = item.description
} else {
binding.categoryDescription.text = ""
}
} }
} }

View file

@ -3,6 +3,7 @@ package fr.free.nrw.commons.upload.structure.depictions
import android.os.Parcelable import android.os.Parcelable
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import fr.free.nrw.commons.category.CategoryItem
import fr.free.nrw.commons.nearby.Place import fr.free.nrw.commons.nearby.Place
import fr.free.nrw.commons.upload.WikidataItem import fr.free.nrw.commons.upload.WikidataItem
import fr.free.nrw.commons.wikidata.WikidataProperties import fr.free.nrw.commons.wikidata.WikidataProperties
@ -28,7 +29,7 @@ data class DepictedItem constructor(
val description: String?, val description: String?,
val imageUrl: String?, val imageUrl: String?,
val instanceOfs: List<String>, val instanceOfs: List<String>,
val commonsCategories: List<String>, val commonsCategories: List<CategoryItem>,
var isSelected: Boolean, var isSelected: Boolean,
@PrimaryKey override val id: String @PrimaryKey override val id: String
) : WikidataItem, Parcelable { ) : WikidataItem, Parcelable {
@ -52,7 +53,8 @@ data class DepictedItem constructor(
getImageUrl(it.value, THUMB_IMAGE_SIZE) getImageUrl(it.value, THUMB_IMAGE_SIZE)
}, },
entity[INSTANCE_OF].toIds(), entity[INSTANCE_OF].toIds(),
entity[COMMONS_CATEGORY]?.map { (it.mainSnak.dataValue as DataValue.ValueString).value } entity[COMMONS_CATEGORY]?.map { CategoryItem((it.mainSnak.dataValue as DataValue.ValueString).value,
"", "", false) }
?: emptyList(), ?: emptyList(),
false, false,
entity.id() entity.id()

View file

@ -1,5 +1,6 @@
package fr.free.nrw.commons.utils; package fr.free.nrw.commons.utils;
import fr.free.nrw.commons.category.CategoryItem;
import java.util.Comparator; import java.util.Comparator;
public class StringSortingUtils { public class StringSortingUtils {
@ -16,10 +17,10 @@ public class StringSortingUtils {
* @param filter String to compare similarity with * @param filter String to compare similarity with
* @return Comparator with string similarity * @return Comparator with string similarity
*/ */
public static Comparator<String> sortBySimilarity(final String filter) { public static Comparator<CategoryItem> sortBySimilarity(final String filter) {
return (firstItem, secondItem) -> { return (firstItem, secondItem) -> {
double firstItemSimilarity = calculateSimilarity(firstItem, filter); double firstItemSimilarity = calculateSimilarity(firstItem.getName(), filter);
double secondItemSimilarity = calculateSimilarity(secondItem, filter); double secondItemSimilarity = calculateSimilarity(secondItem.getName(), filter);
return (int) Math.signum(secondItemSimilarity - firstItemSimilarity); return (int) Math.signum(secondItemSimilarity - firstItemSimilarity);
}; };
} }

View file

@ -1,9 +1,55 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<CheckBox xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/uploadCategoryCheckbox" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/category_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content">
<CheckBox
android:id="@+id/upload_category_checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:checkMark="?android:attr/textCheckMark" android:checkMark="?android:attr/textCheckMark"
android:checked="false" android:checked="false"
android:gravity="center_vertical" android:gravity="center_vertical"
android:padding="@dimen/tiny_gap"/> android:padding="@dimen/tiny_gap"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/category_image"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/category_image"
android:layout_width="50dp"
android:layout_height="50dp"
android:paddingEnd="@dimen/tiny_gap"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toRightOf="@+id/upload_category_checkbox"
app:layout_constraintTop_toTopOf="parent"
app:placeholderImage="@drawable/commons" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/category_image"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/category_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:text="@string/label"
android:textStyle="bold" />
<TextView
android:id="@+id/category_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:text="@string/description" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -652,6 +652,8 @@ Upload your first media by tapping on the add button.</string>
The shadow of the image view of the location picker</string> The shadow of the image view of the location picker</string>
<string name="image_location">Image Location</string> <string name="image_location">Image Location</string>
<string name="check_whether_location_is_correct">Check whether location is correct</string> <string name="check_whether_location_is_correct">Check whether location is correct</string>
<string name="label">Label</string>
<string name="description">Description</string>
<string name="title_page_bookmarks_items">Items</string> <string name="title_page_bookmarks_items">Items</string>
<string name="custom_selector_title">Custom Selector</string> <string name="custom_selector_title">Custom Selector</string>
<string name="custom_selector_empty_text">No Images</string> <string name="custom_selector_empty_text">No Images</string>

View file

@ -16,7 +16,7 @@ fun depictedItem(
description: String = "desc", description: String = "desc",
imageUrl: String = "", imageUrl: String = "",
instanceOfs: List<String> = listOf(), instanceOfs: List<String> = listOf(),
commonsCategories: List<String> = listOf(), commonsCategories: List<CategoryItem> = listOf(),
isSelected: Boolean = false, isSelected: Boolean = false,
id: String = "entityId" id: String = "entityId"
) = DepictedItem( ) = DepictedItem(
@ -29,8 +29,9 @@ fun depictedItem(
id = id id = id
) )
fun categoryItem(name: String = "name", selected: Boolean = false) = fun categoryItem(name: String = "name", description: String = "desc",
CategoryItem(name, selected) thumbUrl: String = "thumbUrl", selected: Boolean = false) =
CategoryItem(name, description, thumbUrl, selected)
fun media( fun media(
thumbUrl: String? = "thumbUrl", thumbUrl: String? = "thumbUrl",

View file

@ -1,6 +1,7 @@
package fr.free.nrw.commons.bookmarks.items package fr.free.nrw.commons.bookmarks.items
import com.nhaarman.mockitokotlin2.whenever import com.nhaarman.mockitokotlin2.whenever
import fr.free.nrw.commons.category.CategoryItem
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
import org.junit.Assert import org.junit.Assert
import org.junit.Before import org.junit.Before
@ -33,7 +34,8 @@ class BookmarkItemsControllerTest {
list.add( list.add(
DepictedItem( DepictedItem(
"name", "description", "image url", listOf("instance"), "name", "description", "image url", listOf("instance"),
listOf("categories"), true, "id") listOf(CategoryItem("category name", "category description",
"category thumbnail", false)), true, "id")
) )
return list return list
} }

View file

@ -10,6 +10,7 @@ import android.os.RemoteException
import com.nhaarman.mockitokotlin2.* import com.nhaarman.mockitokotlin2.*
import fr.free.nrw.commons.TestCommonsApplication import fr.free.nrw.commons.TestCommonsApplication
import fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao.Table.* import fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao.Table.*
import fr.free.nrw.commons.category.CategoryItem
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
import org.junit.Assert import org.junit.Assert
import org.junit.Before import org.junit.Before
@ -26,7 +27,9 @@ class BookmarkItemsDaoTest {
COLUMN_DESCRIPTION, COLUMN_DESCRIPTION,
COLUMN_IMAGE, COLUMN_IMAGE,
COLUMN_INSTANCE_LIST, COLUMN_INSTANCE_LIST,
COLUMN_CATEGORIES_LIST, COLUMN_CATEGORIES_NAME_LIST,
COLUMN_CATEGORIES_DESCRIPTION_LIST,
COLUMN_CATEGORIES_THUMBNAIL_LIST,
COLUMN_IS_SELECTED, COLUMN_IS_SELECTED,
COLUMN_ID, COLUMN_ID,
) )
@ -43,7 +46,10 @@ class BookmarkItemsDaoTest {
@Before @Before
fun setUp() { fun setUp() {
exampleItemBookmark = DepictedItem("itemName", "itemDescription", exampleItemBookmark = DepictedItem("itemName", "itemDescription",
"itemImageUrl", listOf("instance"), listOf("categories"), false, "itemImageUrl", listOf("instance"), listOf(
CategoryItem("category name", "category description",
"category thumbnail", false)
), false,
"itemID") "itemID")
testObject = BookmarkItemsDao { client } testObject = BookmarkItemsDao { client }
} }
@ -72,7 +78,9 @@ class BookmarkItemsDaoTest {
Assert.assertEquals("itemDescription", it.description) Assert.assertEquals("itemDescription", it.description)
Assert.assertEquals("itemImageUrl", it.imageUrl) Assert.assertEquals("itemImageUrl", it.imageUrl)
Assert.assertEquals(listOf("instance"), it.instanceOfs) Assert.assertEquals(listOf("instance"), it.instanceOfs)
Assert.assertEquals(listOf("categories"), it.commonsCategories) Assert.assertEquals(listOf(CategoryItem("category name",
"category description",
"category thumbnail", false)), it.commonsCategories)
Assert.assertEquals(false, it.isSelected) Assert.assertEquals(false, it.isSelected)
Assert.assertEquals("itemID", it.id) Assert.assertEquals("itemID", it.id)
} }
@ -131,7 +139,7 @@ class BookmarkItemsDaoTest {
Assert.assertTrue(testObject.updateBookmarkItem(exampleItemBookmark)) Assert.assertTrue(testObject.updateBookmarkItem(exampleItemBookmark))
verify(client).insert(eq(BookmarkItemsContentProvider.BASE_URI), captor.capture()) verify(client).insert(eq(BookmarkItemsContentProvider.BASE_URI), captor.capture())
captor.firstValue.let { cv -> captor.firstValue.let { cv ->
Assert.assertEquals(7, cv.size()) Assert.assertEquals(9, cv.size())
Assert.assertEquals( Assert.assertEquals(
exampleItemBookmark.name, exampleItemBookmark.name,
cv.getAsString(COLUMN_NAME) cv.getAsString(COLUMN_NAME)
@ -149,8 +157,16 @@ class BookmarkItemsDaoTest {
cv.getAsString(COLUMN_INSTANCE_LIST) cv.getAsString(COLUMN_INSTANCE_LIST)
) )
Assert.assertEquals( Assert.assertEquals(
exampleItemBookmark.commonsCategories[0], exampleItemBookmark.commonsCategories[0].name,
cv.getAsString(COLUMN_CATEGORIES_LIST) cv.getAsString(COLUMN_CATEGORIES_NAME_LIST)
)
Assert.assertEquals(
exampleItemBookmark.commonsCategories[0].description,
cv.getAsString(COLUMN_CATEGORIES_DESCRIPTION_LIST)
)
Assert.assertEquals(
exampleItemBookmark.commonsCategories[0].thumbnail,
cv.getAsString(COLUMN_CATEGORIES_THUMBNAIL_LIST)
) )
Assert.assertEquals( Assert.assertEquals(
exampleItemBookmark.isSelected, exampleItemBookmark.isSelected,
@ -263,8 +279,8 @@ class BookmarkItemsDaoTest {
for (i in 0 until rowCount) { for (i in 0 until rowCount) {
addRow(listOf("itemName", "itemDescription", addRow(listOf("itemName", "itemDescription",
"itemImageUrl", "instance", "categories", false, "itemImageUrl", "instance", "category name", "category description",
"itemID")) "category thumbnail", false, "itemID"))
} }
} }
} }

View file

@ -14,6 +14,7 @@ import com.nhaarman.mockitokotlin2.whenever
import fr.free.nrw.commons.R import fr.free.nrw.commons.R
import fr.free.nrw.commons.TestAppAdapter import fr.free.nrw.commons.TestAppAdapter
import fr.free.nrw.commons.TestCommonsApplication import fr.free.nrw.commons.TestCommonsApplication
import fr.free.nrw.commons.category.CategoryItem
import fr.free.nrw.commons.profile.ProfileActivity import fr.free.nrw.commons.profile.ProfileActivity
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
import org.junit.Assert import org.junit.Assert
@ -62,7 +63,10 @@ class BookmarkItemsFragmentUnitTest {
list.add( list.add(
DepictedItem( DepictedItem(
"name", "description", "image url", listOf("instance"), "name", "description", "image url", listOf("instance"),
listOf("categories"), true, "id") listOf(
CategoryItem("category name", "category description",
"category thumbnail", false)
), true, "id")
) )
return list return list
} }

View file

@ -34,7 +34,8 @@ class CategoriesModelTest {
fun searchAllFoundCaseTest() { fun searchAllFoundCaseTest() {
val categoriesModel = CategoriesModel(categoryClient, mock(), mock()) val categoriesModel = CategoriesModel(categoryClient, mock(), mock())
val expectedList = listOf("Test") val expectedList = listOf(CategoryItem(
"Test", "", "", false))
whenever( whenever(
categoryClient.searchCategoriesForPrefix( categoryClient.searchCategoriesForPrefix(
ArgumentMatchers.anyString(), ArgumentMatchers.anyString(),
@ -45,7 +46,8 @@ class CategoriesModelTest {
.thenReturn(Single.just(expectedList)) .thenReturn(Single.just(expectedList))
// Checking if both return "Test" // Checking if both return "Test"
val expectedItems = expectedList.map { CategoryItem(it, false) } val expectedItems = expectedList.map { CategoryItem(
it.name, it.description, it.thumbnail, false) }
var categoryTerm = "Test" var categoryTerm = "Test"
categoriesModel.searchAll(categoryTerm, emptyList(), emptyList()) categoriesModel.searchAll(categoryTerm, emptyList(), emptyList())
.test() .test()
@ -65,10 +67,12 @@ class CategoriesModelTest {
@Test @Test
fun `searchAll with empty search terms creates results from gps, title search & recents`() { fun `searchAll with empty search terms creates results from gps, title search & recents`() {
val gpsCategoryModel: GpsCategoryModel = mock() val gpsCategoryModel: GpsCategoryModel = mock()
val depictedItem = depictedItem(commonsCategories = listOf("depictionCategory")) val depictedItem = depictedItem(commonsCategories = listOf(CategoryItem(
"depictionCategory", "", "", false)))
whenever(gpsCategoryModel.categoriesFromLocation) whenever(gpsCategoryModel.categoriesFromLocation)
.thenReturn(BehaviorSubject.createDefault(listOf("gpsCategory"))) .thenReturn(BehaviorSubject.createDefault(listOf(CategoryItem(
"gpsCategory", "", "", false))))
whenever( whenever(
categoryClient.searchCategories( categoryClient.searchCategories(
ArgumentMatchers.anyString(), ArgumentMatchers.anyString(),
@ -76,8 +80,10 @@ class CategoriesModelTest {
ArgumentMatchers.anyInt() ArgumentMatchers.anyInt()
) )
) )
.thenReturn(Single.just(listOf("titleSearch"))) .thenReturn(Single.just(listOf(CategoryItem(
whenever(categoryDao.recentCategories(25)).thenReturn(listOf("recentCategories")) "titleSearch", "", "", false))))
whenever(categoryDao.recentCategories(25)).thenReturn(listOf(CategoryItem(
"recentCategories","","", false)))
val imageTitleList = listOf("Test") val imageTitleList = listOf("Test")
CategoriesModel(categoryClient, categoryDao, gpsCategoryModel) CategoriesModel(categoryClient, categoryDao, gpsCategoryModel)
.searchAll("", imageTitleList, listOf(depictedItem)) .searchAll("", imageTitleList, listOf(depictedItem))

View file

@ -34,10 +34,10 @@ class CategoryClientTest {
.thenReturn(Single.just(mockResponse)) .thenReturn(Single.just(mockResponse))
categoryClient.searchCategories("tes", 10) categoryClient.searchCategories("tes", 10)
.test() .test()
.assertValues(listOf("Test")) .assertValues(listOf(CategoryItem("Test", "", "", false)))
categoryClient.searchCategories("tes", 10, 10) categoryClient.searchCategories("tes", 10, 10)
.test() .test()
.assertValues(listOf("Test")) .assertValues(listOf(CategoryItem("Test", "", "", false)))
} }
@Test @Test
@ -59,10 +59,10 @@ class CategoryClientTest {
.thenReturn(Single.just(mockResponse)) .thenReturn(Single.just(mockResponse))
categoryClient.searchCategoriesForPrefix("tes", 10) categoryClient.searchCategoriesForPrefix("tes", 10)
.test() .test()
.assertValues(listOf("Test")) .assertValues(listOf(CategoryItem("Test", "", "", false)))
categoryClient.searchCategoriesForPrefix("tes", 10, 10) categoryClient.searchCategoriesForPrefix("tes", 10, 10)
.test() .test()
.assertValues(listOf("Test")) .assertValues(listOf(CategoryItem("Test", "", "", false)))
} }
@Test @Test
@ -84,7 +84,7 @@ class CategoryClientTest {
.thenReturn(Single.just(mockResponse)) .thenReturn(Single.just(mockResponse))
categoryClient.getParentCategoryList("tes") categoryClient.getParentCategoryList("tes")
.test() .test()
.assertValues(listOf("Test")) .assertValues(listOf(CategoryItem("Test", "", "", false)))
} }
@Test @Test
@ -104,7 +104,7 @@ class CategoryClientTest {
.thenReturn(Single.just(mockResponse)) .thenReturn(Single.just(mockResponse))
categoryClient.getSubCategoryList("tes") categoryClient.getSubCategoryList("tes")
.test() .test()
.assertValues(listOf("Test")) .assertValues(listOf(CategoryItem("Test", "", "", false)))
} }
@Test @Test

View file

@ -24,7 +24,8 @@ import java.util.*
@Config(sdk = [21], application = TestCommonsApplication::class) @Config(sdk = [21], application = TestCommonsApplication::class)
class CategoryDaoTest { class CategoryDaoTest {
private val columns = arrayOf(COLUMN_ID, COLUMN_NAME, COLUMN_LAST_USED, COLUMN_TIMES_USED) private val columns = arrayOf(COLUMN_ID, COLUMN_NAME, COLUMN_DESCRIPTION,
COLUMN_THUMBNAIL, COLUMN_LAST_USED, COLUMN_TIMES_USED)
private val client: ContentProviderClient = mock() private val client: ContentProviderClient = mock()
private val database: SQLiteDatabase = mock() private val database: SQLiteDatabase = mock()
private val captor = argumentCaptor<ContentValues>() private val captor = argumentCaptor<ContentValues>()
@ -122,8 +123,10 @@ class CategoryDaoTest {
verify(client).update(eq(category.contentUri), captor.capture(), isNull(), isNull()) verify(client).update(eq(category.contentUri), captor.capture(), isNull(), isNull())
captor.firstValue.let { cv -> captor.firstValue.let { cv ->
assertEquals(3, cv.size()) assertEquals(5, cv.size())
assertEquals(category.name, cv.getAsString(COLUMN_NAME)) assertEquals(category.name, cv.getAsString(COLUMN_NAME))
assertEquals(category.description, cv.getAsString(COLUMN_DESCRIPTION))
assertEquals(category.thumbnail, cv.getAsString(COLUMN_THUMBNAIL))
assertEquals(category.lastUsed.time, cv.getAsLong(COLUMN_LAST_USED)) assertEquals(category.lastUsed.time, cv.getAsLong(COLUMN_LAST_USED))
assertEquals(category.timesUsed, cv.getAsInteger(COLUMN_TIMES_USED)) assertEquals(category.timesUsed, cv.getAsInteger(COLUMN_TIMES_USED))
} }
@ -134,14 +137,17 @@ class CategoryDaoTest {
fun saveNewCategory() { fun saveNewCategory() {
val contentUri = CategoryContentProvider.uriForId(111) val contentUri = CategoryContentProvider.uriForId(111)
whenever(client.insert(isA(), isA())).thenReturn(contentUri) whenever(client.insert(isA(), isA())).thenReturn(contentUri)
val category = Category(null, "showImageWithItem", Date(234L), 1) val category = Category(null, "showImageWithItem", "description",
"image", Date(234L), 1)
testObject.save(category) testObject.save(category)
verify(client).insert(eq(BASE_URI), captor.capture()) verify(client).insert(eq(BASE_URI), captor.capture())
captor.firstValue.let { cv -> captor.firstValue.let { cv ->
assertEquals(3, cv.size()) assertEquals(5, cv.size())
assertEquals(category.name, cv.getAsString(COLUMN_NAME)) assertEquals(category.name, cv.getAsString(COLUMN_NAME))
assertEquals(category.description, cv.getAsString(COLUMN_DESCRIPTION))
assertEquals(category.thumbnail, cv.getAsString(COLUMN_THUMBNAIL))
assertEquals(category.lastUsed.time, cv.getAsLong(COLUMN_LAST_USED)) assertEquals(category.lastUsed.time, cv.getAsLong(COLUMN_LAST_USED))
assertEquals(category.timesUsed, cv.getAsInteger(COLUMN_TIMES_USED)) assertEquals(category.timesUsed, cv.getAsInteger(COLUMN_TIMES_USED))
assertEquals(contentUri, category.contentUri) assertEquals(contentUri, category.contentUri)
@ -186,6 +192,8 @@ class CategoryDaoTest {
assertEquals(uriForId(1), category?.contentUri) assertEquals(uriForId(1), category?.contentUri)
assertEquals("showImageWithItem", category?.name) assertEquals("showImageWithItem", category?.name)
assertEquals("description", category?.description)
assertEquals("image", category?.thumbnail)
assertEquals(123L, category?.lastUsed?.time) assertEquals(123L, category?.lastUsed?.time)
assertEquals(2, category?.timesUsed) assertEquals(2, category?.timesUsed)
@ -241,7 +249,7 @@ class CategoryDaoTest {
val result = testObject.recentCategories(10) val result = testObject.recentCategories(10)
assertEquals(1, result.size) assertEquals(1, result.size)
assertEquals("showImageWithItem", result[0]) assertEquals("showImageWithItem", result[0].name)
verify(client).query( verify(client).query(
eq(BASE_URI), eq(BASE_URI),
@ -264,7 +272,7 @@ class CategoryDaoTest {
private fun createCursor(rowCount: Int) = MatrixCursor(columns, rowCount).apply { private fun createCursor(rowCount: Int) = MatrixCursor(columns, rowCount).apply {
for (i in 0 until rowCount) { for (i in 0 until rowCount) {
addRow(listOf("1", "showImageWithItem", "123", "2")) addRow(listOf("1", "showImageWithItem", "description", "image", "123", "2"))
} }
} }

View file

@ -67,13 +67,15 @@ class CategoriesPresenterTest {
) )
whenever(repository.containsYear("selected")).thenReturn(false) whenever(repository.containsYear("selected")).thenReturn(false)
whenever(repository.containsYear("doesContainYear")).thenReturn(true) whenever(repository.containsYear("doesContainYear")).thenReturn(true)
whenever(repository.selectedCategories).thenReturn(listOf(categoryItem("selected", true))) whenever(repository.selectedCategories).thenReturn(listOf(
categoryItem("selected", "", "",true)))
categoriesPresenter.searchForCategories("test") categoriesPresenter.searchForCategories("test")
testScheduler.triggerActions() testScheduler.triggerActions()
verify(view).showProgress(true) verify(view).showProgress(true)
verify(view).showError(null) verify(view).showError(null)
verify(view).setCategories(null) verify(view).setCategories(null)
verify(view).setCategories(listOf(categoryItem("selected", true))) verify(view).setCategories(listOf(
categoryItem("selected", "", "", true)))
verify(view).showProgress(false) verify(view).showProgress(false)
verifyNoMoreInteractions(view) verifyNoMoreInteractions(view)
} }

View file

@ -1,5 +1,6 @@
package fr.free.nrw.commons.upload package fr.free.nrw.commons.upload
import fr.free.nrw.commons.category.CategoryItem
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
@ -18,7 +19,8 @@ class GpsCategoryModelTest {
@Test @Test
fun `setCategoriesFromLocation emits the new value`() { fun `setCategoriesFromLocation emits the new value`() {
val expectedList = listOf("category") val expectedList = listOf(
CategoryItem("category", "", "", false))
gpsCategoryModel.categoriesFromLocation.test() gpsCategoryModel.categoriesFromLocation.test()
.also { gpsCategoryModel.setCategoriesFromLocation(expectedList) } .also { gpsCategoryModel.setCategoriesFromLocation(expectedList) }
.assertValues(emptyList(), expectedList) .assertValues(emptyList(), expectedList)

View file

@ -110,7 +110,7 @@ class DepictedItemTest {
) )
) )
) )
).commonsCategories, ).commonsCategories.map { it.name },
listOf("1", "2")) listOf("1", "2"))
} }

View file

@ -1,5 +1,6 @@
package fr.free.nrw.commons.utils package fr.free.nrw.commons.utils
import fr.free.nrw.commons.category.CategoryItem
import fr.free.nrw.commons.utils.StringSortingUtils.sortBySimilarity import fr.free.nrw.commons.utils.StringSortingUtils.sortBySimilarity
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Test import org.junit.Test
@ -9,8 +10,18 @@ class StringSortingUtilsTest {
@Test @Test
fun testSortingNumbersBySimilarity() { fun testSortingNumbersBySimilarity() {
val actualList = listOf("1234567", "4567", "12345", "123", "1234") val actualList = listOf(
val expectedList = listOf("1234", "12345", "123", "1234567", "4567") CategoryItem("1234567", "", "", false),
CategoryItem("4567", "", "", false),
CategoryItem("12345", "", "", false),
CategoryItem("123", "", "", false),
CategoryItem("1234", "", "", false))
val expectedList = listOf(
CategoryItem("1234", "", "", false),
CategoryItem("12345", "", "", false),
CategoryItem("123", "", "", false),
CategoryItem("1234567", "", "", false),
CategoryItem("4567", "", "", false))
sort(actualList, sortBySimilarity("1234")) sort(actualList, sortBySimilarity("1234"))
@ -20,22 +31,22 @@ class StringSortingUtilsTest {
@Test @Test
fun testSortingTextBySimilarity() { fun testSortingTextBySimilarity() {
val actualList = listOf( val actualList = listOf(
"The quick brown fox", CategoryItem("The quick brown fox", "", "", false),
"quick brown fox", CategoryItem("quick brown fox", "", "", false),
"The", CategoryItem("The", "", "", false),
"The quick ", CategoryItem("The quick ", "", "", false),
"The fox", CategoryItem("The fox", "", "", false),
"brown fox", CategoryItem("brown fox", "", "", false),
"fox" CategoryItem("fox", "", "", false)
) )
val expectedList = listOf( val expectedList = listOf(
"The", CategoryItem("The", "", "", false),
"The fox", CategoryItem("The fox", "", "", false),
"The quick ", CategoryItem("The quick ", "", "", false),
"The quick brown fox", CategoryItem("The quick brown fox", "", "", false),
"quick brown fox", CategoryItem("quick brown fox", "", "", false),
"brown fox", CategoryItem("brown fox", "", "", false),
"fox" CategoryItem("fox", "", "", false)
) )
sort(actualList, sortBySimilarity("The")) sort(actualList, sortBySimilarity("The"))
@ -46,18 +57,18 @@ class StringSortingUtilsTest {
@Test @Test
fun testSortingSymbolsBySimilarity() { fun testSortingSymbolsBySimilarity() {
val actualList = listOf( val actualList = listOf(
"$$$$$", CategoryItem("$$$$$", "", "", false),
"****", CategoryItem("****", "", "", false),
"**$*", CategoryItem("**$*", "", "", false),
"*$*$", CategoryItem("*$*$", "", "", false),
".*$" CategoryItem(".*$", "", "", false)
) )
val expectedList = listOf( val expectedList = listOf(
"**$*", CategoryItem("**$*", "", "", false),
"*$*$", CategoryItem("*$*$", "", "", false),
".*$", CategoryItem(".*$", "", "", false),
"****", CategoryItem("****", "", "", false),
"$$$$$" CategoryItem("$$$$$", "", "", false)
) )
sort(actualList, sortBySimilarity("**$")) sort(actualList, sortBySimilarity("**$"))
@ -69,25 +80,25 @@ class StringSortingUtilsTest {
fun testSortingMixedStringsBySimilarity() { fun testSortingMixedStringsBySimilarity() {
// Sample from Category:2018 Android phones // Sample from Category:2018 Android phones
val actualList = listOf( val actualList = listOf(
"ASUS ZenFone 5 (2018)", CategoryItem("ASUS ZenFone 5 (2018)", "", "", false),
"Google Pixel 3", CategoryItem("Google Pixel 3", "", "", false),
"HTC U12", CategoryItem("HTC U12", "", "", false),
"Huawei P20", CategoryItem("Huawei P20", "", "", false),
"LG G7 ThinQ", CategoryItem("LG G7 ThinQ", "", "", false),
"Samsung Galaxy A8 (2018)", CategoryItem("Samsung Galaxy A8 (2018)", "", "", false),
"Samsung Galaxy S9", CategoryItem("Samsung Galaxy S9", "", "", false),
// One with more complicated symbols // One with more complicated symbols
"MadeUpPhone 2018.$£#你好" CategoryItem("MadeUpPhone 2018.$£#你好", "", "", false)
) )
val expectedList = listOf( val expectedList = listOf(
"Samsung Galaxy S9", CategoryItem("Samsung Galaxy S9", "", "", false),
"ASUS ZenFone 5 (2018)", CategoryItem("ASUS ZenFone 5 (2018)", "", "", false),
"Samsung Galaxy A8 (2018)", CategoryItem("Samsung Galaxy A8 (2018)", "", "", false),
"Google Pixel 3", CategoryItem("Google Pixel 3", "", "", false),
"HTC U12", CategoryItem("HTC U12", "", "", false),
"Huawei P20", CategoryItem("Huawei P20", "", "", false),
"LG G7 ThinQ", CategoryItem("LG G7 ThinQ", "", "", false),
"MadeUpPhone 2018.$£#你好" CategoryItem("MadeUpPhone 2018.$£#你好", "", "", false)
) )
sort(actualList, sortBySimilarity("S9")) sort(actualList, sortBySimilarity("S9"))
@ -98,26 +109,26 @@ class StringSortingUtilsTest {
@Test @Test
fun testSortingWithEmptyStrings() { fun testSortingWithEmptyStrings() {
val actualList = listOf( val actualList = listOf(
"brown fox", CategoryItem("brown fox", "", "", false),
"", CategoryItem("", "", "", false),
"quick brown fox", CategoryItem("quick brown fox", "", "", false),
"the", CategoryItem("the", "", "", false),
"", CategoryItem("", "", "", false),
"the fox", CategoryItem("the fox", "", "", false),
"fox", CategoryItem("fox", "", "", false),
"", CategoryItem("", "", "", false),
"" CategoryItem("", "", "", false)
) )
val expectedList = listOf( val expectedList = listOf(
"the fox", CategoryItem("the fox", "", "", false),
"brown fox", CategoryItem("brown fox", "", "", false),
"the", CategoryItem("the", "", "", false),
"fox", CategoryItem("fox", "", "", false),
"quick brown fox", CategoryItem("quick brown fox", "", "", false),
"", CategoryItem("", "", "", false),
"", CategoryItem("", "", "", false),
"", CategoryItem("", "", "", false),
"" CategoryItem("", "", "", false)
) )
sort(actualList, sortBySimilarity("the fox")) sort(actualList, sortBySimilarity("the fox"))