mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 20:33:53 +01:00
Merge remote-tracking branch 'origin/master' into macgills/3756-depictions-pagination
# Conflicts: # app/src/main/java/fr/free/nrw/commons/category/CategoryClient.java # app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.java # app/src/main/java/fr/free/nrw/commons/upload/categories/UploadCategoriesFragment.java # app/src/main/res/values/strings.xml # gradle.properties
This commit is contained in:
commit
8d13122e0e
32 changed files with 651 additions and 907 deletions
|
|
@ -127,6 +127,8 @@ dependencies {
|
||||||
implementation "androidx.preference:preference:$PREFERENCE_VERSION"
|
implementation "androidx.preference:preference:$PREFERENCE_VERSION"
|
||||||
// Kotlin
|
// Kotlin
|
||||||
implementation "androidx.preference:preference-ktx:$PREFERENCE_VERSION"
|
implementation "androidx.preference:preference-ktx:$PREFERENCE_VERSION"
|
||||||
|
|
||||||
|
implementation "androidx.multidex:multidex:$MULTIDEX_VERSION"
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
|
@ -144,6 +146,8 @@ android {
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
testInstrumentationRunnerArguments clearPackageData: 'true'
|
testInstrumentationRunnerArguments clearPackageData: 'true'
|
||||||
|
|
||||||
|
multiDexEnabled true
|
||||||
|
|
||||||
testOptions {
|
testOptions {
|
||||||
execution 'ANDROIDX_TEST_ORCHESTRATOR'
|
execution 'ANDROIDX_TEST_ORCHESTRATOR'
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,242 +0,0 @@
|
||||||
package fr.free.nrw.commons.category;
|
|
||||||
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
|
||||||
import fr.free.nrw.commons.upload.GpsCategoryModel;
|
|
||||||
import fr.free.nrw.commons.utils.StringSortingUtils;
|
|
||||||
import io.reactivex.Observable;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Calendar;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Named;
|
|
||||||
import timber.log.Timber;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The model class for categories in upload
|
|
||||||
*/
|
|
||||||
public class CategoriesModel{
|
|
||||||
private static final int SEARCH_CATS_LIMIT = 25;
|
|
||||||
|
|
||||||
private final CategoryClient categoryClient;
|
|
||||||
private final CategoryDao categoryDao;
|
|
||||||
private final JsonKvStore directKvStore;
|
|
||||||
private final GpsCategoryModel gpsCategoryModel;
|
|
||||||
|
|
||||||
private List<CategoryItem> selectedCategories;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public CategoriesModel(CategoryClient categoryClient,
|
|
||||||
CategoryDao categoryDao,
|
|
||||||
@Named("default_preferences") JsonKvStore directKvStore,
|
|
||||||
final GpsCategoryModel gpsCategoryModel) {
|
|
||||||
this.categoryClient = categoryClient;
|
|
||||||
this.categoryDao = categoryDao;
|
|
||||||
this.directKvStore = directKvStore;
|
|
||||||
this.gpsCategoryModel = gpsCategoryModel;
|
|
||||||
this.selectedCategories = new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sorts CategoryItem by similarity
|
|
||||||
* @param filter
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public Comparator<CategoryItem> sortBySimilarity(final String filter) {
|
|
||||||
Comparator<String> stringSimilarityComparator = StringSortingUtils.sortBySimilarity(filter);
|
|
||||||
return (firstItem, secondItem) -> stringSimilarityComparator
|
|
||||||
.compare(firstItem.getName(), secondItem.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns if the item contains an year
|
|
||||||
* @param item
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public boolean containsYear(String item) {
|
|
||||||
//Check for current and previous year to exclude these categories from removal
|
|
||||||
Calendar now = Calendar.getInstance();
|
|
||||||
int year = now.get(Calendar.YEAR);
|
|
||||||
String yearInString = String.valueOf(year);
|
|
||||||
|
|
||||||
int prevYear = year - 1;
|
|
||||||
String prevYearInString = String.valueOf(prevYear);
|
|
||||||
Timber.d("Previous year: %s", prevYearInString);
|
|
||||||
|
|
||||||
//Check if item contains a 4-digit word anywhere within the string (.* is wildcard)
|
|
||||||
//And that item does not equal the current year or previous year
|
|
||||||
//And if it is an irrelevant category such as Media_needing_categories_as_of_16_June_2017(Issue #750)
|
|
||||||
//Check if the year in the form of XX(X)0s is relevant, i.e. in the 2000s or 2010s as stated in Issue #1029
|
|
||||||
return ((item.matches(".*(19|20)\\d{2}.*") && !item.contains(yearInString) && !item.contains(prevYearInString))
|
|
||||||
|| item.matches("(.*)needing(.*)") || item.matches("(.*)taken on(.*)")
|
|
||||||
|| (item.matches(".*0s.*") && !item.matches(".*(200|201)0s.*")));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates category count in category dao
|
|
||||||
* @param item
|
|
||||||
*/
|
|
||||||
public void updateCategoryCount(CategoryItem item) {
|
|
||||||
Category category = categoryDao.find(item.getName());
|
|
||||||
|
|
||||||
// Newly used category...
|
|
||||||
if (category == null) {
|
|
||||||
category = new Category(null, item.getName(), new Date(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
category.incTimesUsed();
|
|
||||||
categoryDao.save(category);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Regional category search
|
|
||||||
* @param term
|
|
||||||
* @param imageTitleList
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public Observable<CategoryItem> searchAll(String term, List<String> imageTitleList) {
|
|
||||||
//If query text is empty, show him category based on gps and title and recent searches
|
|
||||||
if (TextUtils.isEmpty(term)) {
|
|
||||||
Observable<CategoryItem> categoryItemObservable =
|
|
||||||
Observable.concat(gpsCategories(), titleCategories(imageTitleList));
|
|
||||||
if (hasDirectCategories()) {
|
|
||||||
return Observable.concat(
|
|
||||||
categoryItemObservable,
|
|
||||||
directCategories(),
|
|
||||||
recentCategories()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return categoryItemObservable;
|
|
||||||
}
|
|
||||||
|
|
||||||
//otherwise, search API for matching categories
|
|
||||||
//term passed as lower case to make search case-insensitive(taking only lower case for everything)
|
|
||||||
return categoryClient
|
|
||||||
.searchCategoriesForPrefix(term.toLowerCase(), SEARCH_CATS_LIMIT)
|
|
||||||
.map(name -> new CategoryItem(name, false));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns if we have a category in DirectKV Store
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private boolean hasDirectCategories() {
|
|
||||||
return !directKvStore.getString("Category", "").equals("");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns categories in DirectKVStore
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private Observable<CategoryItem> directCategories() {
|
|
||||||
String directCategory = directKvStore.getString("Category", "");
|
|
||||||
List<String> categoryList = new ArrayList<>();
|
|
||||||
Timber.d("Direct category found: " + directCategory);
|
|
||||||
|
|
||||||
if (!directCategory.equals("")) {
|
|
||||||
categoryList.add(directCategory);
|
|
||||||
Timber.d("DirectCat does not equal emptyString. Direct Cat list has " + categoryList);
|
|
||||||
}
|
|
||||||
return Observable.fromIterable(categoryList).map(name -> new CategoryItem(name, false));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns GPS categories
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
Observable<CategoryItem> gpsCategories() {
|
|
||||||
return Observable.fromIterable(gpsCategoryModel.getCategoryList())
|
|
||||||
.map(name -> new CategoryItem(name, false));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns title based categories
|
|
||||||
* @param titleList
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private Observable<CategoryItem> titleCategories(List<String> titleList) {
|
|
||||||
return Observable.fromIterable(titleList)
|
|
||||||
.concatMap(this::getTitleCategories);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return category for single title
|
|
||||||
* title is converted to lower case to make search case-insensitive
|
|
||||||
* @param title
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private Observable<CategoryItem> getTitleCategories(String title) {
|
|
||||||
return categoryClient.searchCategories(title.toLowerCase(), SEARCH_CATS_LIMIT)
|
|
||||||
.map(name -> new CategoryItem(name, false));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns recent categories
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private Observable<CategoryItem> recentCategories() {
|
|
||||||
return Observable.fromIterable(categoryDao.recentCategories(SEARCH_CATS_LIMIT))
|
|
||||||
.map(s -> new CategoryItem(s, false));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles category item selection
|
|
||||||
* @param item
|
|
||||||
*/
|
|
||||||
public void onCategoryItemClicked(CategoryItem item) {
|
|
||||||
if (item.isSelected()) {
|
|
||||||
selectCategory(item);
|
|
||||||
updateCategoryCount(item);
|
|
||||||
} else {
|
|
||||||
unselectCategory(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Select's category
|
|
||||||
* @param item
|
|
||||||
*/
|
|
||||||
public void selectCategory(CategoryItem item) {
|
|
||||||
selectedCategories.add(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unselect Category
|
|
||||||
* @param item
|
|
||||||
*/
|
|
||||||
public void unselectCategory(CategoryItem item) {
|
|
||||||
selectedCategories.remove(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get Selected Categories
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public List<CategoryItem> getSelectedCategories() {
|
|
||||||
return selectedCategories;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get Categories String List
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public List<String> getCategoryStringList() {
|
|
||||||
List<String> output = new ArrayList<>();
|
|
||||||
for (CategoryItem item : selectedCategories) {
|
|
||||||
output.add(item.getName());
|
|
||||||
}
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cleanup the existing in memory cache's
|
|
||||||
*/
|
|
||||||
public void cleanUp() {
|
|
||||||
this.selectedCategories.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,152 @@
|
||||||
|
package fr.free.nrw.commons.category
|
||||||
|
|
||||||
|
import android.text.TextUtils
|
||||||
|
import fr.free.nrw.commons.upload.GpsCategoryModel
|
||||||
|
import fr.free.nrw.commons.utils.StringSortingUtils
|
||||||
|
import io.reactivex.Observable
|
||||||
|
import io.reactivex.functions.Function3
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.util.*
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The model class for categories in upload
|
||||||
|
*/
|
||||||
|
class CategoriesModel @Inject constructor(
|
||||||
|
private val categoryClient: CategoryClient,
|
||||||
|
private val categoryDao: CategoryDao,
|
||||||
|
private val gpsCategoryModel: GpsCategoryModel
|
||||||
|
) {
|
||||||
|
private val selectedCategories: MutableList<CategoryItem> = mutableListOf()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns if the item contains an year
|
||||||
|
* @param item
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
fun containsYear(item: String): Boolean {
|
||||||
|
//Check for current and previous year to exclude these categories from removal
|
||||||
|
val now = Calendar.getInstance()
|
||||||
|
val year = now[Calendar.YEAR]
|
||||||
|
val yearInString = year.toString()
|
||||||
|
val prevYear = year - 1
|
||||||
|
val prevYearInString = prevYear.toString()
|
||||||
|
Timber.d("Previous year: %s", prevYearInString)
|
||||||
|
|
||||||
|
//Check if item contains a 4-digit word anywhere within the string (.* is wildcard)
|
||||||
|
//And that item does not equal the current year or previous year
|
||||||
|
//And if it is an irrelevant category such as Media_needing_categories_as_of_16_June_2017(Issue #750)
|
||||||
|
//Check if the year in the form of XX(X)0s is relevant, i.e. in the 2000s or 2010s as stated in Issue #1029
|
||||||
|
return item.matches(".*(19|20)\\d{2}.*".toRegex())
|
||||||
|
&& !item.contains(yearInString)
|
||||||
|
&& !item.contains(prevYearInString)
|
||||||
|
|| item.matches("(.*)needing(.*)".toRegex())
|
||||||
|
|| item.matches("(.*)taken on(.*)".toRegex())
|
||||||
|
|| item.matches(".*0s.*".toRegex())
|
||||||
|
&& !item.matches(".*(200|201)0s.*".toRegex())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates category count in category dao
|
||||||
|
* @param item
|
||||||
|
*/
|
||||||
|
fun updateCategoryCount(item: CategoryItem) {
|
||||||
|
var category = categoryDao.find(item.name)
|
||||||
|
|
||||||
|
// Newly used category...
|
||||||
|
if (category == null) {
|
||||||
|
category = Category(null, item.name, Date(), 0)
|
||||||
|
}
|
||||||
|
category.incTimesUsed()
|
||||||
|
categoryDao.save(category)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Regional category search
|
||||||
|
* @param term
|
||||||
|
* @param imageTitleList
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
fun searchAll(term: String, imageTitleList: List<String>): Observable<List<CategoryItem>> {
|
||||||
|
return suggestionsOrSearch(term, imageTitleList)
|
||||||
|
.map { it.map { CategoryItem(it, false) } }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun suggestionsOrSearch(term: String, imageTitleList: List<String>):
|
||||||
|
Observable<List<String>> {
|
||||||
|
return if (TextUtils.isEmpty(term))
|
||||||
|
Observable.combineLatest(
|
||||||
|
gpsCategoryModel.categoriesFromLocation,
|
||||||
|
titleCategories(imageTitleList),
|
||||||
|
Observable.just(categoryDao.recentCategories(SEARCH_CATS_LIMIT)),
|
||||||
|
Function3(::combine)
|
||||||
|
)
|
||||||
|
else
|
||||||
|
categoryClient.searchCategoriesForPrefix(term.toLowerCase(), SEARCH_CATS_LIMIT)
|
||||||
|
.map { it.sortedWith(StringSortingUtils.sortBySimilarity(term)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun combine(
|
||||||
|
locationCategories: List<String>,
|
||||||
|
titles: List<String>,
|
||||||
|
recents: List<String>
|
||||||
|
) = locationCategories + titles + recents
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns title based categories
|
||||||
|
* @param titleList
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private fun titleCategories(titleList: List<String>): Observable<List<String>> {
|
||||||
|
return if (titleList.isNotEmpty())
|
||||||
|
Observable.combineLatest(titleList.map { getTitleCategories(it) }) { searchResults ->
|
||||||
|
searchResults.map { it as List<String> }.flatten()
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Observable.just(emptyList())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return category for single title
|
||||||
|
* title is converted to lower case to make search case-insensitive
|
||||||
|
* @param title
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private fun getTitleCategories(title: String): Observable<List<String>> {
|
||||||
|
return categoryClient.searchCategories(title.toLowerCase(), SEARCH_CATS_LIMIT)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles category item selection
|
||||||
|
* @param item
|
||||||
|
*/
|
||||||
|
fun onCategoryItemClicked(item: CategoryItem) {
|
||||||
|
if (item.isSelected) {
|
||||||
|
selectedCategories.add(item)
|
||||||
|
updateCategoryCount(item)
|
||||||
|
} else {
|
||||||
|
selectedCategories.remove(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Selected Categories
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
fun getSelectedCategories(): List<CategoryItem> {
|
||||||
|
return selectedCategories
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleanup the existing in memory cache's
|
||||||
|
*/
|
||||||
|
fun cleanUp() {
|
||||||
|
selectedCategories.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val SEARCH_CATS_LIMIT = 25
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,126 +0,0 @@
|
||||||
package fr.free.nrw.commons.category;
|
|
||||||
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import org.wikipedia.dataclient.mwapi.MwQueryPage;
|
|
||||||
import org.wikipedia.dataclient.mwapi.MwQueryResponse;
|
|
||||||
import org.wikipedia.dataclient.mwapi.MwQueryResult;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
|
|
||||||
import io.reactivex.Observable;
|
|
||||||
import timber.log.Timber;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Category Client to handle custom calls to Commons MediaWiki APIs
|
|
||||||
*/
|
|
||||||
@Singleton
|
|
||||||
public class CategoryClient {
|
|
||||||
|
|
||||||
public static final String CATEGORY_PREFIX = "Category:";
|
|
||||||
private final CategoryInterface CategoryInterface;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public CategoryClient(CategoryInterface CategoryInterface) {
|
|
||||||
this.CategoryInterface = CategoryInterface;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Searches for categories containing the specified string.
|
|
||||||
*
|
|
||||||
* @param filter The string to be searched
|
|
||||||
* @param itemLimit How many results are returned
|
|
||||||
* @param offset Starts returning items from the nth result. If offset is 9, the response starts with the 9th item of the search result
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public Observable<String> searchCategories(String filter, int itemLimit, int offset) {
|
|
||||||
return responseToCategoryName(CategoryInterface.searchCategories(filter, itemLimit, offset));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Searches for categories containing the specified string.
|
|
||||||
*
|
|
||||||
* @param filter The string to be searched
|
|
||||||
* @param itemLimit How many results are returned
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public Observable<String> searchCategories(String filter, int itemLimit) {
|
|
||||||
return searchCategories(filter, itemLimit, 0);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Searches for categories starting with the specified string.
|
|
||||||
*
|
|
||||||
* @param prefix The prefix to be searched
|
|
||||||
* @param itemLimit How many results are returned
|
|
||||||
* @param offset Starts returning items from the nth result. If offset is 9, the response starts with the 9th item of the search result
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public Observable<String> searchCategoriesForPrefix(String prefix, int itemLimit, int offset) {
|
|
||||||
return responseToCategoryName(CategoryInterface.searchCategoriesForPrefix(prefix, itemLimit, offset));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Searches for categories starting with the specified string.
|
|
||||||
*
|
|
||||||
* @param prefix The prefix to be searched
|
|
||||||
* @param itemLimit How many results are returned
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public Observable<String> searchCategoriesForPrefix(String prefix, int itemLimit) {
|
|
||||||
return searchCategoriesForPrefix(prefix, itemLimit, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The method takes categoryName as input and returns a List of Subcategories
|
|
||||||
* It uses the generator query API to get the subcategories in a category, 500 at a time.
|
|
||||||
*
|
|
||||||
* @param categoryName Category name as defined on commons
|
|
||||||
* @return Observable emitting the categories returned. If our search yielded "Category:Test", "Test" is emitted.
|
|
||||||
*/
|
|
||||||
public Observable<String> getSubCategoryList(String categoryName) {
|
|
||||||
return responseToCategoryName(CategoryInterface.getSubCategoryList(categoryName));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The method takes categoryName as input and returns a List of parent categories
|
|
||||||
* It uses the generator query API to get the parent categories of a category, 500 at a time.
|
|
||||||
*
|
|
||||||
* @param categoryName Category name as defined on commons
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@NonNull
|
|
||||||
public Observable<String> getParentCategoryList(String categoryName) {
|
|
||||||
return responseToCategoryName(CategoryInterface.getParentCategoryList(categoryName));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal function to reduce code reuse. Extracts the categories returned from MwQueryResponse.
|
|
||||||
*
|
|
||||||
* @param responseObservable The query response observable
|
|
||||||
* @return Observable emitting the categories returned. If our search yielded "Category:Test", "Test" is emitted.
|
|
||||||
*/
|
|
||||||
private Observable<String> responseToCategoryName(Observable<MwQueryResponse> responseObservable) {
|
|
||||||
return responseObservable
|
|
||||||
.flatMap(mwQueryResponse -> {
|
|
||||||
MwQueryResult query;
|
|
||||||
List<MwQueryPage> pages;
|
|
||||||
if ((query = mwQueryResponse.query()) == null ||
|
|
||||||
(pages = query.pages()) == null) {
|
|
||||||
Timber.d("No categories returned.");
|
|
||||||
return Observable.empty();
|
|
||||||
} else
|
|
||||||
return Observable.fromIterable(pages);
|
|
||||||
})
|
|
||||||
.map(MwQueryPage::title)
|
|
||||||
.doOnEach(s -> Timber.d("Category returned: %s", s))
|
|
||||||
.map(cat -> cat.replace(CATEGORY_PREFIX, ""));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
package fr.free.nrw.commons.category
|
||||||
|
|
||||||
|
import io.reactivex.Observable
|
||||||
|
import org.wikipedia.dataclient.mwapi.MwQueryResponse
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
const val CATEGORY_PREFIX = "Category:"
|
||||||
|
/**
|
||||||
|
* Category Client to handle custom calls to Commons MediaWiki APIs
|
||||||
|
*/
|
||||||
|
@Singleton
|
||||||
|
class CategoryClient @Inject constructor(private val categoryInterface: CategoryInterface) {
|
||||||
|
/**
|
||||||
|
* Searches for categories containing the specified string.
|
||||||
|
*
|
||||||
|
* @param filter The string to be searched
|
||||||
|
* @param itemLimit How many results are returned
|
||||||
|
* @param offset Starts returning items from the nth result. If offset is 9, the response starts with the 9th item of the search result
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@JvmOverloads
|
||||||
|
fun searchCategories(filter: String?, itemLimit: Int, offset: Int = 0):
|
||||||
|
Observable<List<String>> {
|
||||||
|
return responseToCategoryName(categoryInterface.searchCategories(filter, itemLimit, offset))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches for categories starting with the specified string.
|
||||||
|
*
|
||||||
|
* @param prefix The prefix to be searched
|
||||||
|
* @param itemLimit How many results are returned
|
||||||
|
* @param offset Starts returning items from the nth result. If offset is 9, the response starts with the 9th item of the search result
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@JvmOverloads
|
||||||
|
fun searchCategoriesForPrefix(prefix: String?, itemLimit: Int, offset: Int = 0):
|
||||||
|
Observable<List<String>> {
|
||||||
|
return responseToCategoryName(
|
||||||
|
categoryInterface.searchCategoriesForPrefix(prefix, itemLimit, offset)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The method takes categoryName as input and returns a List of Subcategories
|
||||||
|
* It uses the generator query API to get the subcategories in a category, 500 at a time.
|
||||||
|
*
|
||||||
|
* @param categoryName Category name as defined on commons
|
||||||
|
* @return Observable emitting the categories returned. If our search yielded "Category:Test", "Test" is emitted.
|
||||||
|
*/
|
||||||
|
fun getSubCategoryList(categoryName: String?): Observable<List<String>> {
|
||||||
|
return responseToCategoryName(categoryInterface.getSubCategoryList(categoryName))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The method takes categoryName as input and returns a List of parent categories
|
||||||
|
* It uses the generator query API to get the parent categories of a category, 500 at a time.
|
||||||
|
*
|
||||||
|
* @param categoryName Category name as defined on commons
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
fun getParentCategoryList(categoryName: String?): Observable<List<String>> {
|
||||||
|
return responseToCategoryName(categoryInterface.getParentCategoryList(categoryName))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal function to reduce code reuse. Extracts the categories returned from MwQueryResponse.
|
||||||
|
*
|
||||||
|
* @param responseObservable The query response observable
|
||||||
|
* @return Observable emitting the categories returned. If our search yielded "Category:Test", "Test" is emitted.
|
||||||
|
*/
|
||||||
|
private fun responseToCategoryName(responseObservable: Observable<MwQueryResponse>): Observable<List<String>> {
|
||||||
|
return responseObservable
|
||||||
|
.map { it.query()?.pages() ?: emptyList() }
|
||||||
|
.map {
|
||||||
|
it.map { page -> page.title().replace(CATEGORY_PREFIX, "") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,79 +0,0 @@
|
||||||
package fr.free.nrw.commons.category;
|
|
||||||
|
|
||||||
import android.os.Parcel;
|
|
||||||
import android.os.Parcelable;
|
|
||||||
|
|
||||||
public class CategoryItem implements Parcelable {
|
|
||||||
private final String name;
|
|
||||||
private boolean selected;
|
|
||||||
|
|
||||||
public static Creator<CategoryItem> CREATOR = new Creator<CategoryItem>() {
|
|
||||||
@Override
|
|
||||||
public CategoryItem createFromParcel(Parcel parcel) {
|
|
||||||
return new CategoryItem(parcel);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CategoryItem[] newArray(int i) {
|
|
||||||
return new CategoryItem[0];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public CategoryItem(String name, boolean selected) {
|
|
||||||
this.name = name;
|
|
||||||
this.selected = selected;
|
|
||||||
}
|
|
||||||
|
|
||||||
private CategoryItem(Parcel in) {
|
|
||||||
name = in.readString();
|
|
||||||
selected = in.readInt() == 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSelected() {
|
|
||||||
return selected;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSelected(boolean selected) {
|
|
||||||
this.selected = selected;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int describeContents() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeToParcel(Parcel parcel, int flags) {
|
|
||||||
parcel.writeString(name);
|
|
||||||
parcel.writeInt(selected ? 1 : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (o == null || getClass() != o.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
CategoryItem that = (CategoryItem) o;
|
|
||||||
|
|
||||||
return name.equals(that.name);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return name.hashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "CategoryItem: '" + name + '\'';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
package fr.free.nrw.commons.category
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class CategoryItem(val name: String, var isSelected: Boolean) : Parcelable {
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "CategoryItem: '$name'"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
|
||||||
|
other as CategoryItem
|
||||||
|
|
||||||
|
if (name != other.name) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return name.hashCode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -89,14 +89,12 @@ public class SubCategoryListFragment extends CommonsDaggerSupportFragment {
|
||||||
CategoryClient.CATEGORY_PREFIX +categoryName)
|
CategoryClient.CATEGORY_PREFIX +categoryName)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.collect(ArrayList<String>::new, ArrayList::add)
|
|
||||||
.subscribe(this::handleSuccess, this::handleError));
|
.subscribe(this::handleSuccess, this::handleError));
|
||||||
} else {
|
} else {
|
||||||
compositeDisposable.add(categoryClient.getSubCategoryList(
|
compositeDisposable.add(categoryClient.getSubCategoryList(
|
||||||
CategoryClient.CATEGORY_PREFIX +categoryName)
|
CategoryClient.CATEGORY_PREFIX +categoryName)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.collect(ArrayList<String>::new, ArrayList::add)
|
|
||||||
.subscribe(this::handleSuccess, this::handleError));
|
.subscribe(this::handleSuccess, this::handleError));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -129,7 +129,6 @@ public class SearchCategoryFragment extends CommonsDaggerSupportFragment {
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.doOnSubscribe(disposable -> saveQuery(query))
|
.doOnSubscribe(disposable -> saveQuery(query))
|
||||||
.collect(ArrayList<String>::new, ArrayList::add)
|
|
||||||
.subscribe(this::handleSuccess, this::handleError));
|
.subscribe(this::handleSuccess, this::handleError));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -148,7 +147,6 @@ public class SearchCategoryFragment extends CommonsDaggerSupportFragment {
|
||||||
compositeDisposable.add(categoryClient.searchCategories(query,25, queryList.size())
|
compositeDisposable.add(categoryClient.searchCategories(query,25, queryList.size())
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.collect(ArrayList<String>::new, ArrayList::add)
|
|
||||||
.subscribe(this::handlePaginationSuccess, this::handleError));
|
.subscribe(this::handlePaginationSuccess, this::handleError));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -151,6 +151,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
||||||
MapView mapView;
|
MapView mapView;
|
||||||
@BindView(R.id.rv_nearby_list)
|
@BindView(R.id.rv_nearby_list)
|
||||||
RecyclerView rvNearbyList;
|
RecyclerView rvNearbyList;
|
||||||
|
@BindView(R.id.no_results_message) TextView noResultsView;
|
||||||
|
|
||||||
@Inject LocationServiceManager locationManager;
|
@Inject LocationServiceManager locationManager;
|
||||||
@Inject NearbyController nearbyController;
|
@Inject NearbyController nearbyController;
|
||||||
|
|
@ -633,13 +634,13 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
||||||
@Override
|
@Override
|
||||||
public void updateListFragment(final List<Place> placeList) {
|
public void updateListFragment(final List<Place> placeList) {
|
||||||
adapter.setItems(placeList);
|
adapter.setItems(placeList);
|
||||||
|
noResultsView.setVisibility(placeList.isEmpty() ? View.VISIBLE : View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clearNearbyList() {
|
public void clearNearbyList() {
|
||||||
adapter.clear();
|
adapter.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void addPlaceToNearbyList(final Place place) {
|
public void addPlaceToNearbyList(final Place place) {
|
||||||
adapter.add(place);
|
adapter.add(place);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ import io.reactivex.Flowable;
|
||||||
import io.reactivex.Observable;
|
import io.reactivex.Observable;
|
||||||
import io.reactivex.Single;
|
import io.reactivex.Single;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
@ -111,20 +110,10 @@ public class UploadRepository {
|
||||||
* @param imageTitleList
|
* @param imageTitleList
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public Observable<CategoryItem> searchAll(String query, List<String> imageTitleList) {
|
public Observable<List<CategoryItem>> searchAll(String query, List<String> imageTitleList) {
|
||||||
return categoriesModel.searchAll(query, imageTitleList);
|
return categoriesModel.searchAll(query, imageTitleList);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* returns the string list of categories
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
|
|
||||||
public List<String> getCategoryStringList() {
|
|
||||||
return categoriesModel.getCategoryStringList();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* sets the list of selected categories for the current upload
|
* sets the list of selected categories for the current upload
|
||||||
*
|
*
|
||||||
|
|
@ -143,16 +132,6 @@ public class UploadRepository {
|
||||||
categoriesModel.onCategoryItemClicked(categoryItem);
|
categoriesModel.onCategoryItemClicked(categoryItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* returns category sorted based on similarity with query
|
|
||||||
*
|
|
||||||
* @param query
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public Comparator<? super CategoryItem> sortBySimilarity(String query) {
|
|
||||||
return categoriesModel.sortBySimilarity(query);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* prunes the category list for irrelevant categories see #750
|
* prunes the category list for irrelevant categories see #750
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -180,7 +180,7 @@ class FileProcessor @Inject constructor(
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(Schedulers.io())
|
.observeOn(Schedulers.io())
|
||||||
.subscribe(
|
.subscribe(
|
||||||
{ gpsCategoryModel.categoryList = it },
|
gpsCategoryModel::setCategoriesFromLocation,
|
||||||
{
|
{
|
||||||
Timber.e(it)
|
Timber.e(it)
|
||||||
gpsCategoryModel.clear()
|
gpsCategoryModel.clear()
|
||||||
|
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
package fr.free.nrw.commons.upload;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
public class GpsCategoryModel {
|
|
||||||
private Set<String> categorySet;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public GpsCategoryModel() {
|
|
||||||
clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clear() {
|
|
||||||
categorySet = new HashSet<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean getGpsCatExists() {
|
|
||||||
return !categorySet.isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<String> getCategoryList() {
|
|
||||||
return new ArrayList<>(categorySet);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCategoryList(List<String> categoryList) {
|
|
||||||
clear();
|
|
||||||
categorySet.addAll(categoryList != null ? categoryList : new ArrayList<>());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
package fr.free.nrw.commons.upload
|
||||||
|
|
||||||
|
import io.reactivex.subjects.BehaviorSubject
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class GpsCategoryModel @Inject constructor() {
|
||||||
|
val categoriesFromLocation = BehaviorSubject.createDefault(emptyList<String>())
|
||||||
|
|
||||||
|
fun clear() {
|
||||||
|
categoriesFromLocation.onNext(emptyList())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setCategoriesFromLocation(categoryList: List<String>) {
|
||||||
|
categoriesFromLocation.onNext(categoryList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -30,7 +30,6 @@ import fr.free.nrw.commons.CommonsApplication;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.auth.LoginActivity;
|
import fr.free.nrw.commons.auth.LoginActivity;
|
||||||
import fr.free.nrw.commons.auth.SessionManager;
|
import fr.free.nrw.commons.auth.SessionManager;
|
||||||
import fr.free.nrw.commons.category.CategoriesModel;
|
|
||||||
import fr.free.nrw.commons.contributions.ContributionController;
|
import fr.free.nrw.commons.contributions.ContributionController;
|
||||||
import fr.free.nrw.commons.filepicker.UploadableFile;
|
import fr.free.nrw.commons.filepicker.UploadableFile;
|
||||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||||
|
|
@ -63,8 +62,6 @@ public class UploadActivity extends BaseActivity implements UploadContract.View,
|
||||||
@Inject
|
@Inject
|
||||||
UploadContract.UserActionListener presenter;
|
UploadContract.UserActionListener presenter;
|
||||||
@Inject
|
@Inject
|
||||||
CategoriesModel categoriesModel;
|
|
||||||
@Inject
|
|
||||||
SessionManager sessionManager;
|
SessionManager sessionManager;
|
||||||
@Inject
|
@Inject
|
||||||
UserClient userClient;
|
UserClient userClient;
|
||||||
|
|
@ -96,7 +93,7 @@ public class UploadActivity extends BaseActivity implements UploadContract.View,
|
||||||
private CompositeDisposable compositeDisposable;
|
private CompositeDisposable compositeDisposable;
|
||||||
private ProgressDialog progressDialog;
|
private ProgressDialog progressDialog;
|
||||||
private UploadImageAdapter uploadImagesAdapter;
|
private UploadImageAdapter uploadImagesAdapter;
|
||||||
private List<Fragment> fragments;
|
private List<UploadBaseFragment> fragments;
|
||||||
private UploadCategoriesFragment uploadCategoriesFragment;
|
private UploadCategoriesFragment uploadCategoriesFragment;
|
||||||
private DepictsFragment depictsFragment;
|
private DepictsFragment depictsFragment;
|
||||||
private MediaLicenseFragment mediaLicenseFragment;
|
private MediaLicenseFragment mediaLicenseFragment;
|
||||||
|
|
@ -416,6 +413,7 @@ public class UploadActivity extends BaseActivity implements UploadContract.View,
|
||||||
public void onNextButtonClicked(int index) {
|
public void onNextButtonClicked(int index) {
|
||||||
if (index < fragments.size() - 1) {
|
if (index < fragments.size() - 1) {
|
||||||
vpUpload.setCurrentItem(index + 1, false);
|
vpUpload.setCurrentItem(index + 1, false);
|
||||||
|
fragments.get(index + 1).onBecameVisible();
|
||||||
} else {
|
} else {
|
||||||
presenter.handleSubmit();
|
presenter.handleSubmit();
|
||||||
}
|
}
|
||||||
|
|
@ -425,6 +423,7 @@ public class UploadActivity extends BaseActivity implements UploadContract.View,
|
||||||
public void onPreviousButtonClicked(int index) {
|
public void onPreviousButtonClicked(int index) {
|
||||||
if (index != 0) {
|
if (index != 0) {
|
||||||
vpUpload.setCurrentItem(index - 1, true);
|
vpUpload.setCurrentItem(index - 1, true);
|
||||||
|
fragments.get(index - 1).onBecameVisible();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -433,14 +432,14 @@ public class UploadActivity extends BaseActivity implements UploadContract.View,
|
||||||
*/
|
*/
|
||||||
|
|
||||||
private class UploadImageAdapter extends FragmentStatePagerAdapter {
|
private class UploadImageAdapter extends FragmentStatePagerAdapter {
|
||||||
List<Fragment> fragments;
|
List<UploadBaseFragment> fragments;
|
||||||
|
|
||||||
public UploadImageAdapter(FragmentManager fragmentManager) {
|
public UploadImageAdapter(FragmentManager fragmentManager) {
|
||||||
super(fragmentManager);
|
super(fragmentManager);
|
||||||
this.fragments = new ArrayList<>();
|
this.fragments = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFragments(List<Fragment> fragments) {
|
public void setFragments(List<UploadBaseFragment> fragments) {
|
||||||
this.fragments = fragments;
|
this.fragments = fragments;
|
||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,9 @@ public class UploadBaseFragment extends CommonsDaggerSupportFragment {
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void onBecameVisible() {
|
||||||
|
}
|
||||||
|
|
||||||
public interface Callback {
|
public interface Callback {
|
||||||
|
|
||||||
void onNextButtonClicked(int index);
|
void onNextButtonClicked(int index);
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,8 @@ public abstract class UploadModule {
|
||||||
presenter);
|
presenter);
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
public abstract CategoriesContract.UserActionListener bindsCategoriesPresenter(CategoriesPresenter
|
public abstract CategoriesContract.UserActionListener bindsCategoriesPresenter(
|
||||||
presenter);
|
CategoriesPresenter presenter);
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
public abstract MediaLicenseContract.UserActionListener bindsMediaLicensePresenter(
|
public abstract MediaLicenseContract.UserActionListener bindsMediaLicensePresenter(
|
||||||
|
|
|
||||||
|
|
@ -1,146 +0,0 @@
|
||||||
package fr.free.nrw.commons.upload.categories;
|
|
||||||
|
|
||||||
import static fr.free.nrw.commons.di.CommonsApplicationModule.IO_THREAD;
|
|
||||||
import static fr.free.nrw.commons.di.CommonsApplicationModule.MAIN_THREAD;
|
|
||||||
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import fr.free.nrw.commons.R;
|
|
||||||
import fr.free.nrw.commons.category.CategoryItem;
|
|
||||||
import fr.free.nrw.commons.repository.UploadRepository;
|
|
||||||
import fr.free.nrw.commons.upload.UploadItem;
|
|
||||||
import io.reactivex.Observable;
|
|
||||||
import io.reactivex.Scheduler;
|
|
||||||
import io.reactivex.disposables.CompositeDisposable;
|
|
||||||
import io.reactivex.disposables.Disposable;
|
|
||||||
import java.lang.reflect.Proxy;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Named;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
import timber.log.Timber;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The presenter class for UploadCategoriesFragment
|
|
||||||
*/
|
|
||||||
@Singleton
|
|
||||||
public class CategoriesPresenter implements CategoriesContract.UserActionListener {
|
|
||||||
|
|
||||||
private static final CategoriesContract.View DUMMY = (CategoriesContract.View) Proxy
|
|
||||||
.newProxyInstance(
|
|
||||||
CategoriesContract.View.class.getClassLoader(),
|
|
||||||
new Class[]{CategoriesContract.View.class},
|
|
||||||
(proxy, method, methodArgs) -> null);
|
|
||||||
private final Scheduler ioScheduler;
|
|
||||||
private final Scheduler mainThreadScheduler;
|
|
||||||
|
|
||||||
CategoriesContract.View view = DUMMY;
|
|
||||||
private UploadRepository repository;
|
|
||||||
|
|
||||||
private CompositeDisposable compositeDisposable;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public CategoriesPresenter(UploadRepository repository, @Named(IO_THREAD) Scheduler ioScheduler,
|
|
||||||
@Named(MAIN_THREAD) Scheduler mainThreadScheduler) {
|
|
||||||
this.repository = repository;
|
|
||||||
this.ioScheduler = ioScheduler;
|
|
||||||
this.mainThreadScheduler = mainThreadScheduler;
|
|
||||||
compositeDisposable = new CompositeDisposable();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAttachView(CategoriesContract.View view) {
|
|
||||||
this.view = view;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDetachView() {
|
|
||||||
this.view = DUMMY;
|
|
||||||
compositeDisposable.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* asks the repository to fetch categories for the query
|
|
||||||
* @param query
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void searchForCategories(String query) {
|
|
||||||
List<CategoryItem> categoryItems = new ArrayList<>();
|
|
||||||
List<String> imageTitleList = getImageTitleList();
|
|
||||||
Observable<CategoryItem> distinctCategoriesObservable = Observable
|
|
||||||
.fromIterable(repository.getSelectedCategories())
|
|
||||||
.subscribeOn(ioScheduler)
|
|
||||||
.observeOn(mainThreadScheduler)
|
|
||||||
.doOnSubscribe(disposable -> {
|
|
||||||
view.showProgress(true);
|
|
||||||
view.showError(null);
|
|
||||||
view.setCategories(null);
|
|
||||||
})
|
|
||||||
.observeOn(ioScheduler)
|
|
||||||
.concatWith(
|
|
||||||
repository.searchAll(query, imageTitleList)
|
|
||||||
)
|
|
||||||
.filter(categoryItem -> !repository.containsYear(categoryItem.getName()))
|
|
||||||
.distinct();
|
|
||||||
|
|
||||||
if(!TextUtils.isEmpty(query)) {
|
|
||||||
distinctCategoriesObservable=distinctCategoriesObservable.sorted(repository.sortBySimilarity(query));
|
|
||||||
}
|
|
||||||
Disposable searchCategoriesDisposable = distinctCategoriesObservable
|
|
||||||
.observeOn(mainThreadScheduler)
|
|
||||||
.subscribe(
|
|
||||||
s -> categoryItems.add(s),
|
|
||||||
Timber::e,
|
|
||||||
() -> {
|
|
||||||
view.setCategories(categoryItems);
|
|
||||||
view.showProgress(false);
|
|
||||||
|
|
||||||
if (categoryItems.isEmpty()) {
|
|
||||||
view.showError(R.string.no_categories_found);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
compositeDisposable.add(searchCategoriesDisposable);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns image title list from UploadItem
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private List<String> getImageTitleList() {
|
|
||||||
List<String> titleList = new ArrayList<>();
|
|
||||||
for (UploadItem item : repository.getUploads()) {
|
|
||||||
final String captionText = item.getUploadMediaDetails().get(0).getCaptionText();
|
|
||||||
if (!TextUtils.isEmpty(captionText)) {
|
|
||||||
titleList.add(captionText);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return titleList;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verifies the number of categories selected, prompts the user if none selected
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void verifyCategories() {
|
|
||||||
List<CategoryItem> selectedCategories = repository.getSelectedCategories();
|
|
||||||
if (selectedCategories != null && !selectedCategories.isEmpty()) {
|
|
||||||
repository.setSelectedCategories(repository.getCategoryStringList());
|
|
||||||
view.goToNextScreen();
|
|
||||||
} else {
|
|
||||||
view.showNoCategorySelected();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ask repository to handle category clicked
|
|
||||||
*
|
|
||||||
* @param categoryItem
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onCategoryItemClicked(CategoryItem categoryItem) {
|
|
||||||
repository.onCategoryClicked(categoryItem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,111 @@
|
||||||
|
package fr.free.nrw.commons.upload.categories
|
||||||
|
|
||||||
|
import android.text.TextUtils
|
||||||
|
import fr.free.nrw.commons.R
|
||||||
|
import fr.free.nrw.commons.category.CategoryItem
|
||||||
|
import fr.free.nrw.commons.di.CommonsApplicationModule
|
||||||
|
import fr.free.nrw.commons.repository.UploadRepository
|
||||||
|
import fr.free.nrw.commons.upload.depicts.proxy
|
||||||
|
import io.reactivex.Scheduler
|
||||||
|
import io.reactivex.disposables.CompositeDisposable
|
||||||
|
import io.reactivex.subjects.PublishSubject
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Named
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The presenter class for UploadCategoriesFragment
|
||||||
|
*/
|
||||||
|
@Singleton
|
||||||
|
class CategoriesPresenter @Inject constructor(
|
||||||
|
private val repository: UploadRepository,
|
||||||
|
@param:Named(CommonsApplicationModule.IO_THREAD) private val ioScheduler: Scheduler,
|
||||||
|
@param:Named(CommonsApplicationModule.MAIN_THREAD) private val mainThreadScheduler: Scheduler
|
||||||
|
) : CategoriesContract.UserActionListener {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val DUMMY: CategoriesContract.View = proxy()
|
||||||
|
}
|
||||||
|
|
||||||
|
var view = DUMMY
|
||||||
|
private val compositeDisposable = CompositeDisposable()
|
||||||
|
private val searchTerms = PublishSubject.create<String>()
|
||||||
|
|
||||||
|
override fun onAttachView(view: CategoriesContract.View) {
|
||||||
|
this.view = view
|
||||||
|
compositeDisposable.add(
|
||||||
|
searchTerms
|
||||||
|
.observeOn(mainThreadScheduler)
|
||||||
|
.doOnNext {
|
||||||
|
view.showProgress(true)
|
||||||
|
view.showError(null)
|
||||||
|
view.setCategories(null)
|
||||||
|
}
|
||||||
|
.switchMap(::searchResults)
|
||||||
|
.map { repository.selectedCategories + it }
|
||||||
|
.map { it.distinctBy { categoryItem -> categoryItem.name } }
|
||||||
|
.observeOn(mainThreadScheduler)
|
||||||
|
.subscribe(
|
||||||
|
{
|
||||||
|
view.setCategories(it)
|
||||||
|
view.showProgress(false)
|
||||||
|
if (it.isEmpty()) {
|
||||||
|
view.showError(R.string.no_categories_found)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Timber::e
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun searchResults(term: String) =
|
||||||
|
repository.searchAll(term, getImageTitleList())
|
||||||
|
.subscribeOn(ioScheduler)
|
||||||
|
.map { it.filterNot { categoryItem -> repository.containsYear(categoryItem.name) } }
|
||||||
|
|
||||||
|
override fun onDetachView() {
|
||||||
|
view = DUMMY
|
||||||
|
compositeDisposable.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* asks the repository to fetch categories for the query
|
||||||
|
* @param query
|
||||||
|
*/
|
||||||
|
override fun searchForCategories(query: String) {
|
||||||
|
searchTerms.onNext(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns image title list from UploadItem
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private fun getImageTitleList(): List<String> {
|
||||||
|
return repository.uploads
|
||||||
|
.map { it.uploadMediaDetails[0].captionText }
|
||||||
|
.filterNot { TextUtils.isEmpty(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies the number of categories selected, prompts the user if none selected
|
||||||
|
*/
|
||||||
|
override fun verifyCategories() {
|
||||||
|
val selectedCategories = repository.selectedCategories
|
||||||
|
if (selectedCategories.isNotEmpty()) {
|
||||||
|
repository.setSelectedCategories(selectedCategories.map { it.name })
|
||||||
|
view.goToNextScreen()
|
||||||
|
} else {
|
||||||
|
view.showNoCategorySelected()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ask repository to handle category clicked
|
||||||
|
*
|
||||||
|
* @param categoryItem
|
||||||
|
*/
|
||||||
|
override fun onCategoryItemClicked(categoryItem: CategoryItem) {
|
||||||
|
repository.onCategoryClicked(categoryItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package fr.free.nrw.commons.upload.categories;
|
package fr.free.nrw.commons.upload.categories;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.text.Editable;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
@ -46,8 +47,6 @@ public class UploadCategoriesFragment extends UploadBaseFragment implements Cate
|
||||||
CategoriesContract.UserActionListener presenter;
|
CategoriesContract.UserActionListener presenter;
|
||||||
private UploadCategoryAdapter adapter;
|
private UploadCategoryAdapter adapter;
|
||||||
private Disposable subscribe;
|
private Disposable subscribe;
|
||||||
private List<CategoryItem> categories;
|
|
||||||
private boolean isVisible;
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -69,15 +68,6 @@ public class UploadCategoriesFragment extends UploadBaseFragment implements Cate
|
||||||
presenter.onAttachView(this);
|
presenter.onAttachView(this);
|
||||||
initRecyclerView();
|
initRecyclerView();
|
||||||
addTextChangeListenerToEtSearch();
|
addTextChangeListenerToEtSearch();
|
||||||
//get default categories for empty query
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
if (presenter != null && isVisible && (categories == null || categories.isEmpty())) {
|
|
||||||
presenter.searchForCategories(null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addTextChangeListenerToEtSearch() {
|
private void addTextChangeListenerToEtSearch() {
|
||||||
|
|
@ -130,7 +120,6 @@ public class UploadCategoriesFragment extends UploadBaseFragment implements Cate
|
||||||
adapter.clear();
|
adapter.clear();
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
this.categories = categories;
|
|
||||||
adapter.setItems(categories);
|
adapter.setItems(categories);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -163,12 +152,11 @@ public class UploadCategoriesFragment extends UploadBaseFragment implements Cate
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setUserVisibleHint(boolean isVisibleToUser) {
|
protected void onBecameVisible() {
|
||||||
super.setUserVisibleHint(isVisibleToUser);
|
super.onBecameVisible();
|
||||||
isVisible = isVisibleToUser;
|
final Editable text = etSearch.getText();
|
||||||
|
if (text != null) {
|
||||||
if (presenter != null && isResumed() && (categories == null || categories.isEmpty())) {
|
presenter.searchForCategories(text.toString());
|
||||||
presenter.searchForCategories(null);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,29 @@
|
||||||
android:id="@+id/bottom_sheet"
|
android:id="@+id/bottom_sheet"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical"
|
android:gravity="bottom"
|
||||||
app:behavior_hideable="true"
|
app:behavior_hideable="true"
|
||||||
android:visibility="visible"
|
android:visibility="visible"
|
||||||
app:layout_behavior="@string/bottom_sheet_behavior"
|
app:layout_behavior="@string/bottom_sheet_behavior"
|
||||||
android:background="@android:color/white">
|
android:background="@android:color/transparent">
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@android:color/white">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/no_results_message"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="50dp"
|
||||||
|
android:layout_centerInParent="true"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:text="@string/nearby_no_results"/>
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/rv_nearby_list"
|
android:id="@+id/rv_nearby_list"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent" />
|
android:layout_height="wrap_content"/>
|
||||||
|
</RelativeLayout>
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@
|
||||||
<string name="provider_contributions">Моите качвания</string>
|
<string name="provider_contributions">Моите качвания</string>
|
||||||
<string name="menu_share">Споделяне</string>
|
<string name="menu_share">Споделяне</string>
|
||||||
<string name="menu_open_in_browser">Преглед в браузъра</string>
|
<string name="menu_open_in_browser">Преглед в браузъра</string>
|
||||||
<string name="share_title_hint" fuzzy="true">Заглавие (задълж.)</string>
|
<string name="share_title_hint">Описание (задълж.)</string>
|
||||||
<string name="share_description_hint">Описание</string>
|
<string name="share_description_hint">Описание</string>
|
||||||
<string name="login_failed_network">Неуспешно влизане – проблем в мрежата</string>
|
<string name="login_failed_network">Неуспешно влизане – проблем в мрежата</string>
|
||||||
<string name="login_failed_wrong_credentials">Неуспешно влизане. Проверете потребителското Ви име и паролата.</string>
|
<string name="login_failed_wrong_credentials">Неуспешно влизане. Проверете потребителското Ви име и паролата.</string>
|
||||||
|
|
@ -96,7 +96,7 @@
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="menu_download">Изтегляне</string>
|
<string name="menu_download">Изтегляне</string>
|
||||||
<string name="preference_license">Лиценз по подразбиране</string>
|
<string name="preference_license">Лиценз по подразбиране</string>
|
||||||
<string name="preference_theme" fuzzy="true">Нощен режим</string>
|
<string name="preference_theme">Облик</string>
|
||||||
<string name="license_name_cc_by_sa_four">Признание-Споделяне на Споделеното 4.0</string>
|
<string name="license_name_cc_by_sa_four">Признание-Споделяне на Споделеното 4.0</string>
|
||||||
<string name="license_name_cc_by_sa">Признание-Споделяне на Споделеното 3.0</string>
|
<string name="license_name_cc_by_sa">Признание-Споделяне на Споделеното 3.0</string>
|
||||||
<string name="license_name_cc0">CC0</string>
|
<string name="license_name_cc0">CC0</string>
|
||||||
|
|
@ -188,7 +188,7 @@
|
||||||
<string name="use_external_storage_summary">Съхраняване на направените картини в приложението с камерата на устройството Ви</string>
|
<string name="use_external_storage_summary">Съхраняване на направените картини в приложението с камерата на устройството Ви</string>
|
||||||
<string name="nominate_deletion">Номиниране за изтриване</string>
|
<string name="nominate_deletion">Номиниране за изтриване</string>
|
||||||
<string name="nominated_for_deletion">Изображението е предложено за изтриване.</string>
|
<string name="nominated_for_deletion">Изображението е предложено за изтриване.</string>
|
||||||
<string name="nominated_see_more" fuzzy="true"><u>По-подробно ще намерите на уеб страницата</u></string>
|
<string name="nominated_see_more">По-подробно ще намерите на уеб страницата</string>
|
||||||
<string name="skip_login">Пропускане</string>
|
<string name="skip_login">Пропускане</string>
|
||||||
<string name="navigation_item_login">Влизане</string>
|
<string name="navigation_item_login">Влизане</string>
|
||||||
<string name="skip_login_message">Ще трябва да влезете, за да качвате картини в бъдеще.</string>
|
<string name="skip_login_message">Ще трябва да влезете, за да качвате картини в бъдеще.</string>
|
||||||
|
|
@ -196,7 +196,7 @@
|
||||||
<string name="copy_wikicode">Копиране на уикитекста в кеша</string>
|
<string name="copy_wikicode">Копиране на уикитекста в кеша</string>
|
||||||
<string name="wikicode_copied">Уикитекстът е копиран в кеша</string>
|
<string name="wikicode_copied">Уикитекстът е копиран в кеша</string>
|
||||||
<string name="nearby_location_has_not_changed">Местоположението не е променено.</string>
|
<string name="nearby_location_has_not_changed">Местоположението не е променено.</string>
|
||||||
<string name="nearby_location_not_available" fuzzy="true">Местоположението не е налично.</string>
|
<string name="nearby_location_not_available">„Близки места“ може да не работи правилно, тъй като местоположението не е налично.</string>
|
||||||
<string name="toggle_view_button">Превключване на изгледа</string>
|
<string name="toggle_view_button">Превключване на изгледа</string>
|
||||||
<string name="nearby_wikidata">Уикиданни</string>
|
<string name="nearby_wikidata">Уикиданни</string>
|
||||||
<string name="nearby_wikipedia">Уикипедия</string>
|
<string name="nearby_wikipedia">Уикипедия</string>
|
||||||
|
|
@ -213,7 +213,7 @@
|
||||||
<string name="preference_author_name_toggle">Използване на персонализирано авторско име</string>
|
<string name="preference_author_name_toggle">Използване на персонализирано авторско име</string>
|
||||||
<string name="preference_author_name_toggle_summary">При качването използвайте персонализирано авторско име вместо потребителското си име</string>
|
<string name="preference_author_name_toggle_summary">При качването използвайте персонализирано авторско име вместо потребителското си име</string>
|
||||||
<string name="preference_author_name">Персонализирано авторско име</string>
|
<string name="preference_author_name">Персонализирано авторско име</string>
|
||||||
<string name="read_notifications" fuzzy="true">Известия (архивирани)</string>
|
<string name="read_notifications">Известия (прочетени)</string>
|
||||||
<string name="list_sheet">Списък</string>
|
<string name="list_sheet">Списък</string>
|
||||||
<string name="next">Следваща</string>
|
<string name="next">Следваща</string>
|
||||||
<string name="submit">Изпращане</string>
|
<string name="submit">Изпращане</string>
|
||||||
|
|
@ -243,9 +243,9 @@
|
||||||
<string name="no_image_reverted">Няма върнати изображения</string>
|
<string name="no_image_reverted">Няма върнати изображения</string>
|
||||||
<string name="no_image_uploaded">Няма качени изображения</string>
|
<string name="no_image_uploaded">Няма качени изображения</string>
|
||||||
<string name="no_notification">Нямате непрочетени известия</string>
|
<string name="no_notification">Нямате непрочетени известия</string>
|
||||||
<string name="no_read_notification" fuzzy="true">Нямате архивирани известия</string>
|
<string name="no_read_notification">Нямате прочетени известия</string>
|
||||||
<string name="share_logs_using">Споделяне на дневници, използвайки</string>
|
<string name="share_logs_using">Споделяне на дневници, използвайки</string>
|
||||||
<string name="menu_option_read" fuzzy="true">Преглеждане на архивирани</string>
|
<string name="menu_option_read">Преглеждане на прочетени</string>
|
||||||
<string name="menu_option_unread">Преглеждане на непрочетени</string>
|
<string name="menu_option_unread">Преглеждане на непрочетени</string>
|
||||||
<string name="error_occurred_in_picking_images">Възникна грешка при избирането на изображенията</string>
|
<string name="error_occurred_in_picking_images">Възникна грешка при избирането на изображенията</string>
|
||||||
<string name="image_chooser_title">Изберете изображения за качване</string>
|
<string name="image_chooser_title">Изберете изображения за качване</string>
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
* Nuevo Paso
|
* Nuevo Paso
|
||||||
* Revi
|
* Revi
|
||||||
* Ykhwong
|
* Ykhwong
|
||||||
|
* 그냥기여자
|
||||||
* 아라
|
* 아라
|
||||||
-->
|
-->
|
||||||
<resources>
|
<resources>
|
||||||
|
|
@ -62,6 +63,7 @@
|
||||||
<string name="menu_share">공유</string>
|
<string name="menu_share">공유</string>
|
||||||
<string name="menu_open_in_browser">브라우저로 보기</string>
|
<string name="menu_open_in_browser">브라우저로 보기</string>
|
||||||
<string name="share_title_hint">캡션 (필수)</string>
|
<string name="share_title_hint">캡션 (필수)</string>
|
||||||
|
<string name="add_caption_toast">이 파일의 설명을 작성해 주십시오</string>
|
||||||
<string name="share_description_hint">설명</string>
|
<string name="share_description_hint">설명</string>
|
||||||
<string name="share_caption_hint">캡션 (255자 제한)</string>
|
<string name="share_caption_hint">캡션 (255자 제한)</string>
|
||||||
<string name="login_failed_network">로그인할 수 없습니다 - 네트워크 오류입니다</string>
|
<string name="login_failed_network">로그인할 수 없습니다 - 네트워크 오류입니다</string>
|
||||||
|
|
@ -95,6 +97,7 @@
|
||||||
<item quantity="one">%1$d개 업로드</item>
|
<item quantity="one">%1$d개 업로드</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="categories_not_found">%1$s와(과) 일치하는 분류를 찾을 수 없습니다</string>
|
<string name="categories_not_found">%1$s와(과) 일치하는 분류를 찾을 수 없습니다</string>
|
||||||
|
<string name="depictions_not_found">%1$s에 대한 위키데이터 검색 결과가 없습니다</string>
|
||||||
<string name="categories_skip_explanation">위키미디어 공용에서 그림을 더 찾기 쉽게 만들기 위해 분류를 추가합니다.\n분류를 추가하려면 입력을 시작하세요.</string>
|
<string name="categories_skip_explanation">위키미디어 공용에서 그림을 더 찾기 쉽게 만들기 위해 분류를 추가합니다.\n분류를 추가하려면 입력을 시작하세요.</string>
|
||||||
<string name="categories_activity_title">분류</string>
|
<string name="categories_activity_title">분류</string>
|
||||||
<string name="title_activity_settings">설정</string>
|
<string name="title_activity_settings">설정</string>
|
||||||
|
|
@ -169,6 +172,7 @@
|
||||||
<string name="detail_panel_cats_label">분류</string>
|
<string name="detail_panel_cats_label">분류</string>
|
||||||
<string name="detail_panel_cats_loading">불러오는 중…</string>
|
<string name="detail_panel_cats_loading">불러오는 중…</string>
|
||||||
<string name="detail_panel_cats_none">선택하지 않음</string>
|
<string name="detail_panel_cats_none">선택하지 않음</string>
|
||||||
|
<string name="detail_caption_empty">설명 없음</string>
|
||||||
<string name="detail_description_empty">설명 없음</string>
|
<string name="detail_description_empty">설명 없음</string>
|
||||||
<string name="detail_discussion_empty">토론 없음</string>
|
<string name="detail_discussion_empty">토론 없음</string>
|
||||||
<string name="detail_license_empty">알 수 없는 라이선스</string>
|
<string name="detail_license_empty">알 수 없는 라이선스</string>
|
||||||
|
|
@ -187,6 +191,7 @@
|
||||||
<string name="upload">업로드</string>
|
<string name="upload">업로드</string>
|
||||||
<string name="yes">예</string>
|
<string name="yes">예</string>
|
||||||
<string name="no">아니오</string>
|
<string name="no">아니오</string>
|
||||||
|
<string name="media_detail_caption">설명</string>
|
||||||
<string name="media_detail_title">제목</string>
|
<string name="media_detail_title">제목</string>
|
||||||
<string name="media_detail_description">설명</string>
|
<string name="media_detail_description">설명</string>
|
||||||
<string name="media_detail_discussion">토론</string>
|
<string name="media_detail_discussion">토론</string>
|
||||||
|
|
@ -310,6 +315,7 @@
|
||||||
<string name="no_images_found">그림이 없습니다!</string>
|
<string name="no_images_found">그림이 없습니다!</string>
|
||||||
<string name="error_loading_images">그림을 불러오는 동안 오류가 발생했습니다.</string>
|
<string name="error_loading_images">그림을 불러오는 동안 오류가 발생했습니다.</string>
|
||||||
<string name="image_uploaded_by">올린이: %1$s</string>
|
<string name="image_uploaded_by">올린이: %1$s</string>
|
||||||
|
<string name="block_notification_title">차단됨</string>
|
||||||
<string name="block_notification">공용 편집이 차단되어 있습니다</string>
|
<string name="block_notification">공용 편집이 차단되어 있습니다</string>
|
||||||
<string name="appwidget_img">오늘의 이미지</string>
|
<string name="appwidget_img">오늘의 이미지</string>
|
||||||
<string name="app_widget_heading">오늘의 이미지</string>
|
<string name="app_widget_heading">오늘의 이미지</string>
|
||||||
|
|
@ -361,6 +367,7 @@
|
||||||
<string name="delete">삭제</string>
|
<string name="delete">삭제</string>
|
||||||
<string name="Achievements">성과</string>
|
<string name="Achievements">성과</string>
|
||||||
<string name="statistics">통계</string>
|
<string name="statistics">통계</string>
|
||||||
|
<string name="statistics_thanks">받은 감사</string>
|
||||||
<string name="statistics_featured">알찬 그림</string>
|
<string name="statistics_featured">알찬 그림</string>
|
||||||
<string name="statistics_wikidata_edits">\"주변 장소\" 경유 이미지</string>
|
<string name="statistics_wikidata_edits">\"주변 장소\" 경유 이미지</string>
|
||||||
<string name="level">레벨</string>
|
<string name="level">레벨</string>
|
||||||
|
|
@ -426,6 +433,8 @@
|
||||||
<string name="unable_to_display_nearest_place">위치 권한 없이 사진이 필요한 주변 장소를 표시할 수 없습니다</string>
|
<string name="unable_to_display_nearest_place">위치 권한 없이 사진이 필요한 주변 장소를 표시할 수 없습니다</string>
|
||||||
<string name="never_ask_again">다시는 묻지 않음</string>
|
<string name="never_ask_again">다시는 묻지 않음</string>
|
||||||
<string name="display_location_permission_title">위치 권한 표시</string>
|
<string name="display_location_permission_title">위치 권한 표시</string>
|
||||||
|
<string name="display_campaigns">캠페인 표시</string>
|
||||||
|
<string name="display_campaigns_explanation">진행되고 있는 캠페인 보기</string>
|
||||||
<string name="this_function_needs_network_connection">이 기능에는 네트워크 연결이 필요합니다. 연결 설정을 확인해 주십시오.</string>
|
<string name="this_function_needs_network_connection">이 기능에는 네트워크 연결이 필요합니다. 연결 설정을 확인해 주십시오.</string>
|
||||||
<string name="bad_token_error_proposed_solution">편집 토큰에 문제가 있어서 업로드를 실패했습니다. 로그아웃한 다음 다시 로그인해 보십시오.</string>
|
<string name="bad_token_error_proposed_solution">편집 토큰에 문제가 있어서 업로드를 실패했습니다. 로그아웃한 다음 다시 로그인해 보십시오.</string>
|
||||||
<string name="error_processing_image">이미지를 처리하는 동안 오류가 발생했습니다. 다시 시도해 주십시오!</string>
|
<string name="error_processing_image">이미지를 처리하는 동안 오류가 발생했습니다. 다시 시도해 주십시오!</string>
|
||||||
|
|
@ -473,10 +482,12 @@
|
||||||
<string name="delete_helper_show_deletion_title_success">성공</string>
|
<string name="delete_helper_show_deletion_title_success">성공</string>
|
||||||
<string name="delete_helper_show_deletion_title_failed">실패</string>
|
<string name="delete_helper_show_deletion_title_failed">실패</string>
|
||||||
<string name="delete_helper_ask_spam_selfie">셀카</string>
|
<string name="delete_helper_ask_spam_selfie">셀카</string>
|
||||||
|
<string name="delete_helper_ask_spam_blurry">흐림</string>
|
||||||
<string name="delete_helper_ask_spam_other">기타</string>
|
<string name="delete_helper_ask_spam_other">기타</string>
|
||||||
<string name="delete_helper_ask_reason_copyright_internet_photo">인터넷의 임의 사진</string>
|
<string name="delete_helper_ask_reason_copyright_internet_photo">인터넷의 임의 사진</string>
|
||||||
<string name="delete_helper_ask_reason_copyright_logo">로고</string>
|
<string name="delete_helper_ask_reason_copyright_logo">로고</string>
|
||||||
<string name="delete_helper_ask_reason_copyright_other">기타</string>
|
<string name="delete_helper_ask_reason_copyright_other">기타</string>
|
||||||
|
<string name="delete_helper_ask_alert_set_positive_button_reason">왜냐하면</string>
|
||||||
<string name="share_image_via">다음을 통해 이미지 공유</string>
|
<string name="share_image_via">다음을 통해 이미지 공유</string>
|
||||||
<string name="no_achievements_yet">아직 기여가 없습니다</string>
|
<string name="no_achievements_yet">아직 기여가 없습니다</string>
|
||||||
<string name="account_created">계정을 만들었습니다!</string>
|
<string name="account_created">계정을 만들었습니다!</string>
|
||||||
|
|
@ -490,10 +501,14 @@
|
||||||
<string name="nearby_search_hint">다리, 박물관, 호텔 등.</string>
|
<string name="nearby_search_hint">다리, 박물관, 호텔 등.</string>
|
||||||
<string name="title_for_media">미디어</string>
|
<string name="title_for_media">미디어</string>
|
||||||
<string name="title_app_shortcut_explore">찾아보기</string>
|
<string name="title_app_shortcut_explore">찾아보기</string>
|
||||||
|
<string name="title_app_shortcut_bookmark">북마크</string>
|
||||||
<string name="title_app_shortcut_setting">설정</string>
|
<string name="title_app_shortcut_setting">설정</string>
|
||||||
|
<string name="remove_bookmark">북마크에서 제거됨</string>
|
||||||
|
<string name="add_bookmark">북마크에 추가됨</string>
|
||||||
<string name="wallpaper_set_unsuccessfully">무언가 잘못되었습니다. 배경화면을 설정하지 못했습니다</string>
|
<string name="wallpaper_set_unsuccessfully">무언가 잘못되었습니다. 배경화면을 설정하지 못했습니다</string>
|
||||||
<string name="setting_wallpaper_dialog_title">배경화면으로 설정</string>
|
<string name="setting_wallpaper_dialog_title">배경화면으로 설정</string>
|
||||||
<string name="setting_wallpaper_dialog_message">배경화면을 설정 중입니다. 기다려 주십시오...</string>
|
<string name="setting_wallpaper_dialog_message">배경화면을 설정 중입니다. 기다려 주십시오...</string>
|
||||||
<string name="theme_dark_name">어두움</string>
|
<string name="theme_dark_name">어두움</string>
|
||||||
<string name="theme_light_name">밝음</string>
|
<string name="theme_light_name">밝음</string>
|
||||||
|
<string name="ask_to_turn_location_on">위치를 켭니까?</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@
|
||||||
<string name="provider_contributions">Carregamentos</string>
|
<string name="provider_contributions">Carregamentos</string>
|
||||||
<string name="menu_share">Partilhar</string>
|
<string name="menu_share">Partilhar</string>
|
||||||
<string name="menu_open_in_browser">Ver no navegador</string>
|
<string name="menu_open_in_browser">Ver no navegador</string>
|
||||||
<string name="share_title_hint">Título (Obrigatório)</string>
|
<string name="share_title_hint">Legenda (obrigatória)</string>
|
||||||
<string name="share_description_hint">Descrição</string>
|
<string name="share_description_hint">Descrição</string>
|
||||||
<string name="login_failed_network">Não é possível iniciar uma sessão - falha de rede</string>
|
<string name="login_failed_network">Não é possível iniciar uma sessão - falha de rede</string>
|
||||||
<string name="login_failed_wrong_credentials">Não é possível iniciar uma sessão - verifique o seu nome de utilizador e a palavra-passe, por favor</string>
|
<string name="login_failed_wrong_credentials">Não é possível iniciar uma sessão - verifique o seu nome de utilizador e a palavra-passe, por favor</string>
|
||||||
|
|
@ -334,7 +334,7 @@
|
||||||
<string name="error_loading_subcategories">Ocorreu um erro ao carregar subcategorias.</string>
|
<string name="error_loading_subcategories">Ocorreu um erro ao carregar subcategorias.</string>
|
||||||
<string name="search_tab_title_media">Multimédia</string>
|
<string name="search_tab_title_media">Multimédia</string>
|
||||||
<string name="search_tab_title_categories">Categorias</string>
|
<string name="search_tab_title_categories">Categorias</string>
|
||||||
<string name="search_tab_title_depictions">Itens</string>
|
<string name="search_tab_title_depictions">Elementos</string>
|
||||||
<string name="explore_tab_title_featured">Destacadas</string>
|
<string name="explore_tab_title_featured">Destacadas</string>
|
||||||
<string name="explore_tab_title_mobile">Carregada via telemóvel</string>
|
<string name="explore_tab_title_mobile">Carregada via telemóvel</string>
|
||||||
<string name="successful_wikidata_edit">Imagem adicionada a %1$s na wiki Wikidata!</string>
|
<string name="successful_wikidata_edit">Imagem adicionada a %1$s na wiki Wikidata!</string>
|
||||||
|
|
@ -437,7 +437,7 @@
|
||||||
<string name="desc_language_Asia">Ásia</string>
|
<string name="desc_language_Asia">Ásia</string>
|
||||||
<string name="desc_language_Pacific">Pacífico</string>
|
<string name="desc_language_Pacific">Pacífico</string>
|
||||||
<string name="no_categories_selected">Não foi selecionada nenhuma categoria</string>
|
<string name="no_categories_selected">Não foi selecionada nenhuma categoria</string>
|
||||||
<string name="no_categories_selected_warning_desc">As imagens sem categorias são utilizáveis raramente. Tem a certeza que deseja continuar sem selecionar categorias?</string>
|
<string name="no_categories_selected_warning_desc">As imagens sem categorias só raramente são utilizáveis. Tem a certeza de que deseja continuar sem selecionar categorias?</string>
|
||||||
<string name="upload_flow_all_images_in_set">(Para todas as imagens do conjunto)</string>
|
<string name="upload_flow_all_images_in_set">(Para todas as imagens do conjunto)</string>
|
||||||
<string name="search_this_area">Pesquisar nesta área</string>
|
<string name="search_this_area">Pesquisar nesta área</string>
|
||||||
<string name="nearby_card_permission_title">Pedido de permissões</string>
|
<string name="nearby_card_permission_title">Pedido de permissões</string>
|
||||||
|
|
|
||||||
|
|
@ -587,6 +587,9 @@
|
||||||
<string name="place_type">Тип места:</string>
|
<string name="place_type">Тип места:</string>
|
||||||
<string name="nearby_search_hint">Мост, музей, гостиница и т. д.</string>
|
<string name="nearby_search_hint">Мост, музей, гостиница и т. д.</string>
|
||||||
<string name="you_must_reset_your_passsword">Что-то пошло не так со входом, вы должны сбросить пароль!</string>
|
<string name="you_must_reset_your_passsword">Что-то пошло не так со входом, вы должны сбросить пароль!</string>
|
||||||
|
<string name="title_for_media">МЕДИА</string>
|
||||||
|
<string name="title_for_child_classes">ДЕТСКИЕ КЛАССЫ</string>
|
||||||
|
<string name="title_for_parent_classes">РОДИТЕЛЬСКИЕ КЛАССЫ</string>
|
||||||
<string name="upload_nearby_place_found_title">Место поблизости найдено</string>
|
<string name="upload_nearby_place_found_title">Место поблизости найдено</string>
|
||||||
<string name="upload_nearby_place_found_description">Является ли это фото местом %1$s?</string>
|
<string name="upload_nearby_place_found_description">Является ли это фото местом %1$s?</string>
|
||||||
<string name="title_app_shortcut_explore">Обзор</string>
|
<string name="title_app_shortcut_explore">Обзор</string>
|
||||||
|
|
|
||||||
|
|
@ -189,7 +189,7 @@
|
||||||
<string name="cannot_be_zero">El limite de cargamento no\'l połe esare 0</string>
|
<string name="cannot_be_zero">El limite de cargamento no\'l połe esare 0</string>
|
||||||
<string name="set_limit">El me limite di cargamento ultimo</string>
|
<string name="set_limit">El me limite di cargamento ultimo</string>
|
||||||
<string name="login_failed_2fa_not_supported">L\'autenticasion a do fatori no ła xé pa\'l momento suportada</string>
|
<string name="login_failed_2fa_not_supported">L\'autenticasion a do fatori no ła xé pa\'l momento suportada</string>
|
||||||
<string name="logout_verification">Vuto realmente ndar fora?</string>
|
<string name="logout_verification">Vuto seriamente sevitar a ndaxer fora?</string>
|
||||||
<string name="commons_logo">Logo de Commons</string>
|
<string name="commons_logo">Logo de Commons</string>
|
||||||
<string name="commons_website">Sito web de Commons</string>
|
<string name="commons_website">Sito web de Commons</string>
|
||||||
<string name="commons_facebook">Pajina Facebook de Commons</string>
|
<string name="commons_facebook">Pajina Facebook de Commons</string>
|
||||||
|
|
@ -226,7 +226,7 @@
|
||||||
<string name="nearby_needs_permissions">I posti cuà rente no i pole mia esar mostrai sensa ver autorixà l\'uxo de la poxision</string>
|
<string name="nearby_needs_permissions">I posti cuà rente no i pole mia esar mostrai sensa ver autorixà l\'uxo de la poxision</string>
|
||||||
<string name="no_description_found">Nisuna descrision catada</string>
|
<string name="no_description_found">Nisuna descrision catada</string>
|
||||||
<string name="nearby_info_menu_commons_article">Pajina de Commons del file</string>
|
<string name="nearby_info_menu_commons_article">Pajina de Commons del file</string>
|
||||||
<string name="nearby_info_menu_wikidata_article">Elemento Wikidata</string>
|
<string name="nearby_info_menu_wikidata_article">Ełemento Wikidata</string>
|
||||||
<string name="nearby_info_menu_wikipedia_article">Articulo so Wikipedia</string>
|
<string name="nearby_info_menu_wikipedia_article">Articulo so Wikipedia</string>
|
||||||
<string name="error_while_cache">Eror co se jera drio memorixare le imajini n\'te la cache</string>
|
<string name="error_while_cache">Eror co se jera drio memorixare le imajini n\'te la cache</string>
|
||||||
<string name="title_info">On unego titoło descrivitivo par el file, che el vegnarà doparà come so no,e. Te połi doparar on lenguajo senpliçe co spasi. No sta scrivere l\'estension del file</string>
|
<string name="title_info">On unego titoło descrivitivo par el file, che el vegnarà doparà come so no,e. Te połi doparar on lenguajo senpliçe co spasi. No sta scrivere l\'estension del file</string>
|
||||||
|
|
@ -272,7 +272,7 @@
|
||||||
<string name="notifications_talk_page_message">%1$s el te ga dasà on mesajo n\'te ła to pajina de discusion</string>
|
<string name="notifications_talk_page_message">%1$s el te ga dasà on mesajo n\'te ła to pajina de discusion</string>
|
||||||
<string name="notifications_thank_you_edit">Grasie par ver fato na modifega!</string>
|
<string name="notifications_thank_you_edit">Grasie par ver fato na modifega!</string>
|
||||||
<string name="notifications_mention">%1$s el te ga mensionà so %2$s.</string>
|
<string name="notifications_mention">%1$s el te ga mensionà so %2$s.</string>
|
||||||
<string name="toggle_view_button">Ativa/Dixativa vixuałixasion</string>
|
<string name="toggle_view_button">Ativa/Dexativa vixuałixasion</string>
|
||||||
<string name="nearby_directions">Indicasion</string>
|
<string name="nearby_directions">Indicasion</string>
|
||||||
<string name="nearby_wikidata">Wikidata</string>
|
<string name="nearby_wikidata">Wikidata</string>
|
||||||
<string name="nearby_wikipedia">Wikipedia</string>
|
<string name="nearby_wikipedia">Wikipedia</string>
|
||||||
|
|
|
||||||
|
|
@ -629,5 +629,5 @@ Upload your first media by tapping on the add button.</string>
|
||||||
<string name="nearby_needs_location">Nearby needs location enabled to work properly</string>
|
<string name="nearby_needs_location">Nearby needs location enabled to work properly</string>
|
||||||
<string name="use_location_from_similar_image">Did you shoot these two pictures at the same place? Do you want to use the latitude/longitude of the picture on the right?</string>
|
<string name="use_location_from_similar_image">Did you shoot these two pictures at the same place? Do you want to use the latitude/longitude of the picture on the right?</string>
|
||||||
<string name="load_more">Load More</string>
|
<string name="load_more">Load More</string>
|
||||||
|
<string name="nearby_no_results">No places found, try changing your search criteria.</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,11 @@ package fr.free.nrw.commons.category
|
||||||
|
|
||||||
import com.nhaarman.mockitokotlin2.mock
|
import com.nhaarman.mockitokotlin2.mock
|
||||||
import com.nhaarman.mockitokotlin2.whenever
|
import com.nhaarman.mockitokotlin2.whenever
|
||||||
import fr.free.nrw.commons.kvstore.JsonKvStore
|
|
||||||
import fr.free.nrw.commons.upload.GpsCategoryModel
|
import fr.free.nrw.commons.upload.GpsCategoryModel
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import junit.framework.Assert.assertEquals
|
import io.reactivex.subjects.BehaviorSubject
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.mockito.ArgumentMatchers.anyString
|
|
||||||
import org.mockito.ArgumentMatchers.eq
|
|
||||||
import org.mockito.Mock
|
import org.mockito.Mock
|
||||||
import org.mockito.MockitoAnnotations
|
import org.mockito.MockitoAnnotations
|
||||||
|
|
||||||
|
|
@ -31,48 +28,41 @@ class CategoriesModelTest {
|
||||||
// Test Case for verifying that Categories search (MW api calls) are case-insensitive
|
// Test Case for verifying that Categories search (MW api calls) are case-insensitive
|
||||||
@Test
|
@Test
|
||||||
fun searchAllFoundCaseTest() {
|
fun searchAllFoundCaseTest() {
|
||||||
val categoriesModel = CategoriesModel(categoryClient, null, null, mock())
|
val categoriesModel = CategoriesModel(categoryClient, mock(), mock())
|
||||||
|
|
||||||
whenever(categoryClient.searchCategoriesForPrefix(anyString(), eq(25)))
|
val expectedList = listOf("Test")
|
||||||
.thenReturn(Observable.just("Test"))
|
whenever(categoryClient.searchCategoriesForPrefix("tes", 25))
|
||||||
|
.thenReturn(Observable.just(expectedList))
|
||||||
|
|
||||||
// Checking if both return "Test"
|
// Checking if both return "Test"
|
||||||
val actualCategoryName = categoriesModel.searchAll("tes", null).blockingFirst()
|
val expectedItems = expectedList.map { CategoryItem(it, false) }
|
||||||
assertEquals("Test", actualCategoryName.name)
|
categoriesModel.searchAll("tes", emptyList())
|
||||||
|
.test()
|
||||||
|
.assertValues(expectedItems)
|
||||||
|
|
||||||
val actualCategoryNameCaps = categoriesModel.searchAll("Tes", null).blockingFirst()
|
categoriesModel.searchAll("Tes", emptyList())
|
||||||
assertEquals("Test", actualCategoryNameCaps.name)
|
.test()
|
||||||
|
.assertValues(expectedItems)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* For testing the substring search algorithm for Categories search
|
|
||||||
* To be more precise it tests the In Between substring( ex: searching `atte`
|
|
||||||
* will give search suggestions: `Latte`, `Iced latte` e.t.c) which has been described
|
|
||||||
* on github repo wiki:
|
|
||||||
* https://github.com/commons-app/apps-android-commons/wiki/Category-suggestions-(readme)#user-content-3-category-search-when-typing-in-the-search-field-has-been-made-more-flexible
|
|
||||||
*/
|
|
||||||
@Test
|
@Test
|
||||||
fun searchAllFoundCaseTestForSubstringSearch() {
|
fun `searchAll with empty search terms creates results from gps, title search & recents`() {
|
||||||
val gpsCategoryModel: GpsCategoryModel = mock()
|
val gpsCategoryModel: GpsCategoryModel = mock()
|
||||||
val kvStore: JsonKvStore = mock()
|
|
||||||
|
|
||||||
whenever(gpsCategoryModel.categoryList).thenReturn(listOf("gpsCategory"))
|
whenever(gpsCategoryModel.categoriesFromLocation)
|
||||||
|
.thenReturn(BehaviorSubject.createDefault(listOf("gpsCategory")))
|
||||||
whenever(categoryClient.searchCategories("tes", 25))
|
whenever(categoryClient.searchCategories("tes", 25))
|
||||||
.thenReturn(Observable.just("tes"))
|
.thenReturn(Observable.just(listOf("titleSearch")))
|
||||||
whenever(kvStore.getString("Category", "")).thenReturn("Random Value")
|
|
||||||
whenever(categoryDao.recentCategories(25)).thenReturn(listOf("recentCategories"))
|
whenever(categoryDao.recentCategories(25)).thenReturn(listOf("recentCategories"))
|
||||||
CategoriesModel(
|
CategoriesModel(categoryClient, categoryDao, gpsCategoryModel)
|
||||||
categoryClient,
|
.searchAll("", listOf("tes"))
|
||||||
categoryDao,
|
|
||||||
kvStore,
|
|
||||||
gpsCategoryModel
|
|
||||||
).searchAll(null, listOf("tes"))
|
|
||||||
.test()
|
.test()
|
||||||
.assertValues(
|
.assertValue(
|
||||||
CategoryItem("gpsCategory", false),
|
listOf(
|
||||||
CategoryItem("tes", false),
|
CategoryItem("gpsCategory", false),
|
||||||
CategoryItem("Random Value", false),
|
CategoryItem("titleSearch", false),
|
||||||
CategoryItem("recentCategories", false)
|
CategoryItem("recentCategories", false)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,26 @@
|
||||||
package fr.free.nrw.commons.category
|
package fr.free.nrw.commons.category
|
||||||
|
|
||||||
|
import com.nhaarman.mockitokotlin2.mock
|
||||||
|
import com.nhaarman.mockitokotlin2.whenever
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import junit.framework.Assert.*
|
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.mockito.*
|
import org.mockito.ArgumentMatchers.anyInt
|
||||||
|
import org.mockito.ArgumentMatchers.anyString
|
||||||
|
import org.mockito.InjectMocks
|
||||||
|
import org.mockito.Mock
|
||||||
|
import org.mockito.Mockito.mock
|
||||||
|
import org.mockito.MockitoAnnotations
|
||||||
import org.wikipedia.dataclient.mwapi.MwQueryPage
|
import org.wikipedia.dataclient.mwapi.MwQueryPage
|
||||||
import org.wikipedia.dataclient.mwapi.MwQueryResponse
|
import org.wikipedia.dataclient.mwapi.MwQueryResponse
|
||||||
import org.wikipedia.dataclient.mwapi.MwQueryResult
|
import org.wikipedia.dataclient.mwapi.MwQueryResult
|
||||||
|
|
||||||
class CategoryClientTest {
|
class CategoryClientTest {
|
||||||
@Mock
|
@Mock
|
||||||
internal var categoryInterface: CategoryInterface? = null
|
internal lateinit var categoryInterface: CategoryInterface
|
||||||
|
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
var categoryClient: CategoryClient? = null
|
lateinit var categoryClient: CategoryClient
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
|
|
@ -24,132 +30,111 @@ class CategoryClientTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun searchCategoriesFound() {
|
fun searchCategoriesFound() {
|
||||||
val mwQueryPage = Mockito.mock(MwQueryPage::class.java)
|
val mockResponse = withMockResponse("Category:Test")
|
||||||
Mockito.`when`(mwQueryPage.title()).thenReturn("Category:Test")
|
whenever(categoryInterface.searchCategories(anyString(), anyInt(), anyInt()))
|
||||||
val mwQueryResult = Mockito.mock(MwQueryResult::class.java)
|
.thenReturn(Observable.just(mockResponse))
|
||||||
Mockito.`when`(mwQueryResult.pages()).thenReturn(listOf(mwQueryPage))
|
categoryClient.searchCategories("tes", 10)
|
||||||
val mockResponse = Mockito.mock(MwQueryResponse::class.java)
|
.test()
|
||||||
Mockito.`when`(mockResponse.query()).thenReturn(mwQueryResult)
|
.assertValues(listOf("Test"))
|
||||||
|
categoryClient.searchCategories("tes", 10, 10)
|
||||||
Mockito.`when`(categoryInterface!!.searchCategories(ArgumentMatchers.anyString(), ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt()))
|
.test()
|
||||||
.thenReturn(Observable.just(mockResponse))
|
.assertValues(listOf("Test"))
|
||||||
|
|
||||||
val actualCategoryName = categoryClient!!.searchCategories("tes", 10).blockingFirst()
|
|
||||||
assertEquals("Test", actualCategoryName)
|
|
||||||
|
|
||||||
val actualCategoryName2 = categoryClient!!.searchCategories("tes", 10, 10).blockingFirst()
|
|
||||||
assertEquals("Test", actualCategoryName2)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun searchCategoriesNull() {
|
fun searchCategoriesNull() {
|
||||||
val mwQueryResult = Mockito.mock(MwQueryResult::class.java)
|
val mockResponse = withNullPages()
|
||||||
Mockito.`when`(mwQueryResult.pages()).thenReturn(null)
|
whenever(categoryInterface.searchCategories(anyString(), anyInt(), anyInt()))
|
||||||
val mockResponse = Mockito.mock(MwQueryResponse::class.java)
|
.thenReturn(Observable.just(mockResponse))
|
||||||
Mockito.`when`(mockResponse.query()).thenReturn(mwQueryResult)
|
categoryClient.searchCategories("tes", 10)
|
||||||
|
.test()
|
||||||
Mockito.`when`(categoryInterface!!.searchCategories(ArgumentMatchers.anyString(), ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt()))
|
.assertValues(emptyList())
|
||||||
.thenReturn(Observable.just(mockResponse))
|
categoryClient.searchCategories("tes", 10, 10)
|
||||||
|
.test()
|
||||||
categoryClient!!.searchCategories("tes", 10).subscribe(
|
.assertValues(emptyList())
|
||||||
{ fail("SearchCategories returned element when it shouldn't have.") },
|
|
||||||
{ s -> throw s })
|
|
||||||
categoryClient!!.searchCategories("tes", 10, 10).subscribe(
|
|
||||||
{ fail("SearchCategories returned element when it shouldn't have.") },
|
|
||||||
{ s -> throw s })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun searchCategoriesForPrefixFound() {
|
fun searchCategoriesForPrefixFound() {
|
||||||
val mwQueryPage = Mockito.mock(MwQueryPage::class.java)
|
val mockResponse = withMockResponse("Category:Test")
|
||||||
Mockito.`when`(mwQueryPage.title()).thenReturn("Category:Test")
|
whenever(categoryInterface.searchCategoriesForPrefix(anyString(), anyInt(), anyInt()))
|
||||||
val mwQueryResult = Mockito.mock(MwQueryResult::class.java)
|
.thenReturn(Observable.just(mockResponse))
|
||||||
Mockito.`when`(mwQueryResult.pages()).thenReturn(listOf(mwQueryPage))
|
categoryClient.searchCategoriesForPrefix("tes", 10)
|
||||||
val mockResponse = Mockito.mock(MwQueryResponse::class.java)
|
.test()
|
||||||
Mockito.`when`(mockResponse.query()).thenReturn(mwQueryResult)
|
.assertValues(listOf("Test"))
|
||||||
|
categoryClient.searchCategoriesForPrefix("tes", 10, 10)
|
||||||
Mockito.`when`(categoryInterface!!.searchCategoriesForPrefix(ArgumentMatchers.anyString(), ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt()))
|
.test()
|
||||||
.thenReturn(Observable.just(mockResponse))
|
.assertValues(listOf("Test"))
|
||||||
|
|
||||||
val actualCategoryName = categoryClient!!.searchCategoriesForPrefix("tes", 10).blockingFirst()
|
|
||||||
assertEquals("Test", actualCategoryName)
|
|
||||||
val actualCategoryName2 = categoryClient!!.searchCategoriesForPrefix("tes", 10, 10).blockingFirst()
|
|
||||||
assertEquals("Test", actualCategoryName2)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun searchCategoriesForPrefixNull() {
|
fun searchCategoriesForPrefixNull() {
|
||||||
val mwQueryResult = Mockito.mock(MwQueryResult::class.java)
|
val mockResponse = withNullPages()
|
||||||
Mockito.`when`(mwQueryResult.pages()).thenReturn(null)
|
whenever(categoryInterface.searchCategoriesForPrefix(anyString(), anyInt(), anyInt()))
|
||||||
val mockResponse = Mockito.mock(MwQueryResponse::class.java)
|
.thenReturn(Observable.just(mockResponse))
|
||||||
Mockito.`when`(mockResponse.query()).thenReturn(mwQueryResult)
|
categoryClient.searchCategoriesForPrefix("tes", 10)
|
||||||
|
.test()
|
||||||
Mockito.`when`(categoryInterface!!.searchCategoriesForPrefix(ArgumentMatchers.anyString(), ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt()))
|
.assertValues(emptyList())
|
||||||
.thenReturn(Observable.just(mockResponse))
|
categoryClient.searchCategoriesForPrefix("tes", 10, 10)
|
||||||
categoryClient!!.searchCategoriesForPrefix("tes", 10).subscribe(
|
.test()
|
||||||
{ fail("SearchCategories returned element when it shouldn't have.") },
|
.assertValues(emptyList())
|
||||||
{ s -> throw s })
|
|
||||||
categoryClient!!.searchCategoriesForPrefix("tes", 10, 10).subscribe(
|
|
||||||
{ fail("SearchCategories returned element when it shouldn't have.") },
|
|
||||||
{ s -> throw s })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun getParentCategoryListFound() {
|
fun getParentCategoryListFound() {
|
||||||
val mwQueryPage = Mockito.mock(MwQueryPage::class.java)
|
val mockResponse = withMockResponse("Category:Test")
|
||||||
Mockito.`when`(mwQueryPage.title()).thenReturn("Category:Test")
|
whenever(categoryInterface.getParentCategoryList(anyString()))
|
||||||
val mwQueryResult = Mockito.mock(MwQueryResult::class.java)
|
.thenReturn(Observable.just(mockResponse))
|
||||||
Mockito.`when`(mwQueryResult.pages()).thenReturn(listOf(mwQueryPage))
|
categoryClient.getParentCategoryList("tes")
|
||||||
val mockResponse = Mockito.mock(MwQueryResponse::class.java)
|
.test()
|
||||||
Mockito.`when`(mockResponse.query()).thenReturn(mwQueryResult)
|
.assertValues(listOf("Test"))
|
||||||
|
|
||||||
Mockito.`when`(categoryInterface!!.getParentCategoryList(ArgumentMatchers.anyString()))
|
|
||||||
.thenReturn(Observable.just(mockResponse))
|
|
||||||
|
|
||||||
val actualCategoryName = categoryClient!!.getParentCategoryList("tes").blockingFirst()
|
|
||||||
assertEquals("Test", actualCategoryName)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun getParentCategoryListNull() {
|
fun getParentCategoryListNull() {
|
||||||
val mwQueryResult = Mockito.mock(MwQueryResult::class.java)
|
val mockResponse = withNullPages()
|
||||||
Mockito.`when`(mwQueryResult.pages()).thenReturn(null)
|
whenever(categoryInterface.getParentCategoryList(anyString()))
|
||||||
val mockResponse = Mockito.mock(MwQueryResponse::class.java)
|
.thenReturn(Observable.just(mockResponse))
|
||||||
Mockito.`when`(mockResponse.query()).thenReturn(mwQueryResult)
|
categoryClient.getParentCategoryList("tes")
|
||||||
|
.test()
|
||||||
Mockito.`when`(categoryInterface!!.getParentCategoryList(ArgumentMatchers.anyString()))
|
.assertValues(emptyList())
|
||||||
.thenReturn(Observable.just(mockResponse))
|
|
||||||
categoryClient!!.getParentCategoryList("tes").subscribe(
|
|
||||||
{ fail("SearchCategories returned element when it shouldn't have.") },
|
|
||||||
{ s -> throw s })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun getSubCategoryListFound() {
|
fun getSubCategoryListFound() {
|
||||||
val mwQueryPage = Mockito.mock(MwQueryPage::class.java)
|
val mockResponse = withMockResponse("Category:Test")
|
||||||
Mockito.`when`(mwQueryPage.title()).thenReturn("Category:Test")
|
whenever(categoryInterface.getSubCategoryList("tes"))
|
||||||
val mwQueryResult = Mockito.mock(MwQueryResult::class.java)
|
.thenReturn(Observable.just(mockResponse))
|
||||||
Mockito.`when`(mwQueryResult.pages()).thenReturn(listOf(mwQueryPage))
|
categoryClient.getSubCategoryList("tes")
|
||||||
val mockResponse = Mockito.mock(MwQueryResponse::class.java)
|
.test()
|
||||||
Mockito.`when`(mockResponse.query()).thenReturn(mwQueryResult)
|
.assertValues(listOf("Test"))
|
||||||
|
|
||||||
Mockito.`when`(categoryInterface!!.getSubCategoryList(ArgumentMatchers.anyString()))
|
|
||||||
.thenReturn(Observable.just(mockResponse))
|
|
||||||
|
|
||||||
val actualCategoryName = categoryClient!!.getSubCategoryList("tes").blockingFirst()
|
|
||||||
assertEquals("Test", actualCategoryName)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun getSubCategoryListNull() {
|
fun getSubCategoryListNull() {
|
||||||
val mwQueryResult = Mockito.mock(MwQueryResult::class.java)
|
val mockResponse = withNullPages()
|
||||||
Mockito.`when`(mwQueryResult.pages()).thenReturn(null)
|
whenever(categoryInterface.getSubCategoryList(anyString()))
|
||||||
val mockResponse = Mockito.mock(MwQueryResponse::class.java)
|
.thenReturn(Observable.just(mockResponse))
|
||||||
Mockito.`when`(mockResponse.query()).thenReturn(mwQueryResult)
|
categoryClient.getSubCategoryList("tes")
|
||||||
|
.test()
|
||||||
Mockito.`when`(categoryInterface!!.getSubCategoryList(ArgumentMatchers.anyString()))
|
.assertValues(emptyList())
|
||||||
.thenReturn(Observable.just(mockResponse))
|
|
||||||
categoryClient!!.getSubCategoryList("tes").subscribe(
|
|
||||||
{ fail("SearchCategories returned element when it shouldn't have.") },
|
|
||||||
{ s -> throw s })
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
private fun withMockResponse(title: String): MwQueryResponse? {
|
||||||
|
val mwQueryPage: MwQueryPage = mock()
|
||||||
|
whenever(mwQueryPage.title()).thenReturn(title)
|
||||||
|
val mwQueryResult: MwQueryResult = mock()
|
||||||
|
whenever(mwQueryResult.pages()).thenReturn(listOf(mwQueryPage))
|
||||||
|
val mockResponse = mock(MwQueryResponse::class.java)
|
||||||
|
whenever(mockResponse.query()).thenReturn(mwQueryResult)
|
||||||
|
return mockResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun withNullPages(): MwQueryResponse? {
|
||||||
|
val mwQueryResult = mock(MwQueryResult::class.java)
|
||||||
|
whenever(mwQueryResult.pages()).thenReturn(null)
|
||||||
|
val mockResponse = mock(MwQueryResponse::class.java)
|
||||||
|
whenever(mockResponse.query()).thenReturn(mwQueryResult)
|
||||||
|
return mockResponse
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package fr.free.nrw.commons.upload
|
package fr.free.nrw.commons.upload
|
||||||
|
|
||||||
import com.nhaarman.mockitokotlin2.verify
|
import com.nhaarman.mockitokotlin2.*
|
||||||
import com.nhaarman.mockitokotlin2.whenever
|
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.repository.UploadRepository
|
import fr.free.nrw.commons.repository.UploadRepository
|
||||||
import fr.free.nrw.commons.upload.categories.CategoriesContract
|
import fr.free.nrw.commons.upload.categories.CategoriesContract
|
||||||
|
|
@ -10,7 +10,6 @@ import io.reactivex.Observable
|
||||||
import io.reactivex.schedulers.TestScheduler
|
import io.reactivex.schedulers.TestScheduler
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.mockito.ArgumentMatchers
|
|
||||||
import org.mockito.Mock
|
import org.mockito.Mock
|
||||||
import org.mockito.MockitoAnnotations
|
import org.mockito.MockitoAnnotations
|
||||||
|
|
||||||
|
|
@ -20,6 +19,7 @@ import org.mockito.MockitoAnnotations
|
||||||
class CategoriesPresenterTest {
|
class CategoriesPresenterTest {
|
||||||
@Mock
|
@Mock
|
||||||
internal lateinit var repository: UploadRepository
|
internal lateinit var repository: UploadRepository
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
internal lateinit var view: CategoriesContract.View
|
internal lateinit var view: CategoriesContract.View
|
||||||
|
|
||||||
|
|
@ -49,30 +49,76 @@ class CategoriesPresenterTest {
|
||||||
* unit test case for method CategoriesPresenter.searchForCategories
|
* unit test case for method CategoriesPresenter.searchForCategories
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
fun searchForCategoriesTest() {
|
fun `searchForCategories combines selection and search results without years distinctly`() {
|
||||||
whenever(repository.sortBySimilarity(ArgumentMatchers.anyString())).thenReturn(Comparator<CategoryItem> { _, _ -> 1 })
|
val nonEmptyCaptionUploadItem = mock<UploadItem>()
|
||||||
whenever(repository.selectedCategories).thenReturn(categoryItems)
|
whenever(nonEmptyCaptionUploadItem.uploadMediaDetails)
|
||||||
whenever(repository.searchAll(ArgumentMatchers.anyString(), ArgumentMatchers.anyList())).thenReturn(Observable.empty())
|
.thenReturn(listOf(UploadMediaDetail(captionText = "nonEmpty")))
|
||||||
|
val emptyCaptionUploadItem = mock<UploadItem>()
|
||||||
|
whenever(emptyCaptionUploadItem.uploadMediaDetails)
|
||||||
|
.thenReturn(listOf(UploadMediaDetail(captionText = "")))
|
||||||
|
whenever(repository.uploads).thenReturn(
|
||||||
|
listOf(
|
||||||
|
nonEmptyCaptionUploadItem,
|
||||||
|
emptyCaptionUploadItem
|
||||||
|
)
|
||||||
|
)
|
||||||
|
whenever(repository.searchAll("test", listOf("nonEmpty")))
|
||||||
|
.thenReturn(
|
||||||
|
Observable.just(
|
||||||
|
listOf(
|
||||||
|
categoryItem("selected"),
|
||||||
|
categoryItem("doesContainYear")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
whenever(repository.containsYear("selected")).thenReturn(false)
|
||||||
|
whenever(repository.containsYear("doesContainYear")).thenReturn(true)
|
||||||
|
whenever(repository.selectedCategories).thenReturn(listOf(categoryItem("selected", true)))
|
||||||
categoriesPresenter.searchForCategories("test")
|
categoriesPresenter.searchForCategories("test")
|
||||||
|
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)
|
||||||
testScheduler.triggerActions()
|
verify(view).setCategories(listOf(categoryItem("selected", true)))
|
||||||
verify(view).setCategories(categoryItems)
|
|
||||||
verify(view).showProgress(false)
|
verify(view).showProgress(false)
|
||||||
|
verifyNoMoreInteractions(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `searchForCategoriesTest sets Error when list is empty`() {
|
||||||
|
whenever(repository.uploads).thenReturn(listOf())
|
||||||
|
whenever(repository.searchAll(any(), any())).thenReturn(Observable.just(listOf()))
|
||||||
|
whenever(repository.selectedCategories).thenReturn(listOf())
|
||||||
|
categoriesPresenter.searchForCategories("test")
|
||||||
|
testScheduler.triggerActions()
|
||||||
|
verify(view).showProgress(true)
|
||||||
|
verify(view).showError(null)
|
||||||
|
verify(view).setCategories(null)
|
||||||
|
verify(view).setCategories(listOf())
|
||||||
|
verify(view).showProgress(false)
|
||||||
|
verify(view).showError(R.string.no_categories_found)
|
||||||
|
verifyNoMoreInteractions(view)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* unit test for method CategoriesPresenter.verifyCategories
|
* unit test for method CategoriesPresenter.verifyCategories
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
fun verifyCategoriesTest() {
|
fun `verifyCategories with non empty selection goes to next screen`() {
|
||||||
whenever(repository.selectedCategories).thenReturn(categoryItems)
|
val item = categoryItem()
|
||||||
|
whenever(repository.selectedCategories).thenReturn(listOf(item))
|
||||||
categoriesPresenter.verifyCategories()
|
categoriesPresenter.verifyCategories()
|
||||||
verify(repository).setSelectedCategories(ArgumentMatchers.anyList())
|
verify(repository).setSelectedCategories(listOf(item.name))
|
||||||
verify(view).goToNextScreen()
|
verify(view).goToNextScreen()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `verifyCategories with empty selection show no category selected`() {
|
||||||
|
whenever(repository.selectedCategories).thenReturn(listOf())
|
||||||
|
categoriesPresenter.verifyCategories()
|
||||||
|
verify(view).showNoCategorySelected()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test onCategory Item clicked
|
* Test onCategory Item clicked
|
||||||
*/
|
*/
|
||||||
|
|
@ -81,4 +127,7 @@ class CategoriesPresenterTest {
|
||||||
categoriesPresenter.onCategoryItemClicked(categoryItem)
|
categoriesPresenter.onCategoryItemClicked(categoryItem)
|
||||||
verify(repository).onCategoryClicked(categoryItem)
|
verify(repository).onCategoryClicked(categoryItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun categoryItem(name: String = "name", selected: Boolean = false) =
|
||||||
|
CategoryItem(name, selected)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,76 +1,33 @@
|
||||||
package fr.free.nrw.commons.upload
|
package fr.free.nrw.commons.upload
|
||||||
|
|
||||||
import org.junit.Assert.*
|
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class GpsCategoryModelTest {
|
class GpsCategoryModelTest {
|
||||||
|
lateinit var gpsCategoryModel: GpsCategoryModel
|
||||||
private lateinit var testObject: GpsCategoryModel
|
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
testObject = GpsCategoryModel()
|
gpsCategoryModel = GpsCategoryModel()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun initiallyTheModelIsEmpty() {
|
fun `intial value is empty`() {
|
||||||
assertFalse(testObject.gpsCatExists)
|
gpsCategoryModel.categoriesFromLocation.test().assertValues(emptyList())
|
||||||
assertTrue(testObject.categoryList.isEmpty())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun addingCategoriesToTheModel() {
|
fun `setCategoriesFromLocation emits the new value`() {
|
||||||
testObject.categoryList = listOf("one")
|
val expectedList = listOf("category")
|
||||||
assertTrue(testObject.gpsCatExists)
|
gpsCategoryModel.categoriesFromLocation.test()
|
||||||
assertFalse(testObject.categoryList.isEmpty())
|
.also { gpsCategoryModel.setCategoriesFromLocation(expectedList) }
|
||||||
assertEquals(listOf("one"), testObject.categoryList)
|
.assertValues(emptyList(), expectedList)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun duplicatesAreIgnored() {
|
fun `clear emits an empty value`() {
|
||||||
testObject.categoryList = listOf("one", "one")
|
gpsCategoryModel.categoriesFromLocation.test()
|
||||||
assertEquals(listOf("one"), testObject.categoryList)
|
.also { gpsCategoryModel.clear() }
|
||||||
}
|
.assertValues(emptyList(), emptyList())
|
||||||
|
|
||||||
@Test
|
|
||||||
fun modelProtectsAgainstExternalModification() {
|
|
||||||
testObject.categoryList = listOf("one")
|
|
||||||
|
|
||||||
val list = testObject.categoryList
|
|
||||||
list.add("two")
|
|
||||||
|
|
||||||
assertEquals(listOf("one"), testObject.categoryList)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun clearingTheModel() {
|
|
||||||
testObject.categoryList = listOf("one")
|
|
||||||
|
|
||||||
testObject.clear()
|
|
||||||
assertFalse(testObject.gpsCatExists)
|
|
||||||
assertTrue(testObject.categoryList.isEmpty())
|
|
||||||
|
|
||||||
testObject.categoryList = listOf("two")
|
|
||||||
assertEquals(listOf("two"), testObject.categoryList)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun settingTheListHandlesNull() {
|
|
||||||
testObject.categoryList = listOf("one")
|
|
||||||
|
|
||||||
testObject.categoryList = null
|
|
||||||
|
|
||||||
assertFalse(testObject.gpsCatExists)
|
|
||||||
assertTrue(testObject.categoryList.isEmpty())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun settingTheListOverwritesExistingValues() {
|
|
||||||
testObject.categoryList = listOf("one")
|
|
||||||
|
|
||||||
testObject.categoryList = listOf("two")
|
|
||||||
|
|
||||||
assertEquals(listOf("two"), testObject.categoryList)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ PREFERENCE_VERSION=1.1.0
|
||||||
CORE_KTX_VERSION=1.2.0
|
CORE_KTX_VERSION=1.2.0
|
||||||
ADAPTER_DELEGATES_VERSION=4.3.0
|
ADAPTER_DELEGATES_VERSION=4.3.0
|
||||||
PAGING_VERSION=2.1.2
|
PAGING_VERSION=2.1.2
|
||||||
|
MULTIDEX_VERSION=2.0.1
|
||||||
|
|
||||||
systemProp.http.proxyPort=0
|
systemProp.http.proxyPort=0
|
||||||
systemProp.http.proxyHost=
|
systemProp.http.proxyHost=
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue