Makes depicted place and category items unselectable for nearby place (#5325)

* Makes depicted place and category items unselectable for nearby place

* UploadCategoriesFragmentUnitTests.kt fixes and javadoc addition

* comment fix

* Fixes tests and hidden category appearing and dissapearing issue
This commit is contained in:
Srishti Rohatgi 2023-10-08 14:56:27 +05:30 committed by GitHub
parent 05fbfce865
commit 733c8709fc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 229 additions and 66 deletions

View file

@ -136,7 +136,11 @@ class CategoriesModel @Inject constructor(
return Observable.fromIterable(categoryNames) return Observable.fromIterable(categoryNames)
.map { categoryName -> .map { categoryName ->
buildCategories(categoryName) buildCategories(categoryName)
}.toList().toObservable() }
.filter { categoryItem ->
categoryItem.name != "Hidden"
}
.toList().toObservable()
} }
/** /**

View file

@ -293,6 +293,23 @@ public class UploadRepository {
return depictModel.getPlaceDepictions(new ArrayList<>(qids)); return depictModel.getPlaceDepictions(new ArrayList<>(qids));
} }
/**
* Gets the category for each unique {@link Place} associated with an {@link UploadItem}
* from {@link #getUploads()}
*
* @return a single that provides the categories
*/
public Single<List<CategoryItem>> getPlaceCategories() {
final Set<String> qids = new HashSet<>();
for (final UploadItem item : getUploads()) {
final Place place = item.getPlace();
if (place != null) {
qids.add(place.getCategory());
}
}
return Single.fromObservable(categoriesModel.getCategoriesByName(new ArrayList<>(qids)));
}
/** /**
* Takes depict IDs as a parameter, converts into a slash separated String and Gets DepictItem * Takes depict IDs as a parameter, converts into a slash separated String and Gets DepictItem
* from the server * from the server

View file

@ -3,6 +3,8 @@ package fr.free.nrw.commons.upload;
import static fr.free.nrw.commons.contributions.ContributionController.ACTION_INTERNAL_UPLOADS; import static fr.free.nrw.commons.contributions.ContributionController.ACTION_INTERNAL_UPLOADS;
import static fr.free.nrw.commons.utils.PermissionUtils.PERMISSIONS_STORAGE; import static fr.free.nrw.commons.utils.PermissionUtils.PERMISSIONS_STORAGE;
import static fr.free.nrw.commons.wikidata.WikidataConstants.PLACE_OBJECT; import static fr.free.nrw.commons.wikidata.WikidataConstants.PLACE_OBJECT;
import static fr.free.nrw.commons.wikidata.WikidataConstants.SELECTED_NEARBY_PLACE;
import static fr.free.nrw.commons.wikidata.WikidataConstants.SELECTED_NEARBY_PLACE_CATEGORY;
import android.Manifest; import android.Manifest;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
@ -483,9 +485,17 @@ public class UploadActivity extends BaseActivity implements UploadContract.View,
} }
uploadCategoriesFragment = new UploadCategoriesFragment(); uploadCategoriesFragment = new UploadCategoriesFragment();
if (place != null) {
Bundle categoryBundle = new Bundle();
categoryBundle.putString(SELECTED_NEARBY_PLACE_CATEGORY, place.getCategory());
uploadCategoriesFragment.setArguments(categoryBundle);
}
uploadCategoriesFragment.setCallback(this); uploadCategoriesFragment.setCallback(this);
depictsFragment = new DepictsFragment(); depictsFragment = new DepictsFragment();
Bundle placeBundle = new Bundle();
placeBundle.putParcelable(SELECTED_NEARBY_PLACE, place);
depictsFragment.setArguments(placeBundle);
depictsFragment.setCallback(this); depictsFragment.setCallback(this);
mediaLicenseFragment = new MediaLicenseFragment(); mediaLicenseFragment = new MediaLicenseFragment();

View file

@ -2,6 +2,7 @@ package fr.free.nrw.commons.upload.categories;
import android.content.Context; import android.content.Context;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData;
import fr.free.nrw.commons.BasePresenter; import fr.free.nrw.commons.BasePresenter;
import fr.free.nrw.commons.Media; import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.category.CategoryItem; import fr.free.nrw.commons.category.CategoryItem;
@ -80,6 +81,9 @@ public interface CategoriesContract {
*/ */
void updateCategories(Media media, String wikiText); void updateCategories(Media media, String wikiText);
LiveData<List<CategoryItem>> getCategories();
void selectCategories();
} }

View file

@ -1,6 +1,8 @@
package fr.free.nrw.commons.upload.categories package fr.free.nrw.commons.upload.categories
import android.text.TextUtils import android.text.TextUtils
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import fr.free.nrw.commons.Media import fr.free.nrw.commons.Media
import fr.free.nrw.commons.R import fr.free.nrw.commons.R
import fr.free.nrw.commons.category.CategoryEditHelper import fr.free.nrw.commons.category.CategoryEditHelper
@ -36,6 +38,7 @@ class CategoriesPresenter @Inject constructor(
var view = DUMMY var view = DUMMY
private val compositeDisposable = CompositeDisposable() private val compositeDisposable = CompositeDisposable()
private val searchTerms = PublishSubject.create<String>() private val searchTerms = PublishSubject.create<String>()
private var categoryList: MutableLiveData<List<CategoryItem>> = MutableLiveData()
/** /**
* Current media * Current media
*/ */
@ -54,8 +57,6 @@ class CategoriesPresenter @Inject constructor(
.observeOn(mainThreadScheduler) .observeOn(mainThreadScheduler)
.doOnNext { .doOnNext {
view.showProgress(true) view.showProgress(true)
view.showError(null)
view.setCategories(null)
} }
.switchMap(::searchResults) .switchMap(::searchResults)
.map { repository.selectedCategories + it } .map { repository.selectedCategories + it }
@ -63,13 +64,17 @@ class CategoriesPresenter @Inject constructor(
.observeOn(mainThreadScheduler) .observeOn(mainThreadScheduler)
.subscribe( .subscribe(
{ {
view.setCategories(it) setCategoryListValue(it)
view.showProgress(false) view.showProgress(false)
if (it.isEmpty()) { if (it.isEmpty()) {
view.showError(R.string.no_categories_found) view.showError(R.string.no_categories_found)
} }
}, },
Timber::e { t: Throwable? ->
view.showProgress(false)
view.showError(R.string.no_categories_found)
Timber.e(t)
}
) )
) )
} }
@ -92,11 +97,10 @@ class CategoriesPresenter @Inject constructor(
CategoryItem(it.name, it.description, it.thumbnail, true) CategoryItem(it.name, it.description, it.thumbnail, true)
} }
}, },
repository.searchAll(term, getImageTitleList(), repository.selectedDepictions), repository.searchAll(term, getImageTitleList(), repository.selectedDepictions)
{ it1, it2 -> ) { it1, it2 ->
it1 + it2 it1 + it2
} }
)
.subscribeOn(ioScheduler) .subscribeOn(ioScheduler)
.map { it.filter { categoryItem -> !repository.containsYear(categoryItem.name) .map { it.filter { categoryItem -> !repository.containsYear(categoryItem.name)
|| categoryItem.name==term } } || categoryItem.name==term } }
@ -161,8 +165,6 @@ class CategoriesPresenter @Inject constructor(
.observeOn(mainThreadScheduler) .observeOn(mainThreadScheduler)
.doOnNext { .doOnNext {
view.showProgress(true) view.showProgress(true)
view.showError(null)
view.setCategories(null)
} }
.switchMap(::searchResults) .switchMap(::searchResults)
.map { repository.selectedCategories + it } .map { repository.selectedCategories + it }
@ -170,13 +172,17 @@ class CategoriesPresenter @Inject constructor(
.observeOn(mainThreadScheduler) .observeOn(mainThreadScheduler)
.subscribe( .subscribe(
{ {
view.setCategories(it) setCategoryListValue(it)
view.showProgress(false) view.showProgress(false)
if (it.isEmpty()) { if (it.isEmpty()) {
view.showError(R.string.no_categories_found) view.showError(R.string.no_categories_found)
} }
}, },
Timber::e { t: Throwable? ->
view.showProgress(false)
view.showError(R.string.no_categories_found)
Timber.e(t)
}
) )
) )
} }
@ -230,4 +236,53 @@ class CategoriesPresenter @Inject constructor(
view.showNoCategorySelected() view.showNoCategorySelected()
} }
} }
/**
* Selects each [CategoryItem] in a given list as if they were clicked by the user by calling
* [onCategoryItemClicked] for each category and adding the category to [categoryList]
*/
private fun selectNewCategories(toSelect: List<CategoryItem>) {
toSelect.forEach{
it.isSelected = true
repository.onCategoryClicked(it, media)
}
// Add the new selections to the list of category items so that the selections appear
// immediately (i.e. without any search term queries)
categoryList.value?.toMutableList()
?.let { toSelect + it }
?.distinctBy(CategoryItem::name)
?.let { setCategoryListValue(it) }
}
/**
* Livedata being used to observe category list inside
* @see UploadCategoriesFragment
* Any changes to category list reflect immediately to the adapter list
*/
override fun getCategories(): LiveData<List<CategoryItem>> {
return categoryList
}
/**
* needed for tests
*/
fun setCategoryList(categoryList: MutableLiveData<List<CategoryItem>>) {
this.categoryList = categoryList
}
/**
* needed for tests
*/
fun setCategoryListValue(categoryItems: List<CategoryItem>) {
categoryList.postValue(categoryItems)
}
override fun selectCategories() {
compositeDisposable.add(repository.placeCategories
.subscribeOn(ioScheduler)
.observeOn(mainThreadScheduler)
.subscribe(::selectNewCategories)
)
}
} }

View file

@ -1,5 +1,7 @@
package fr.free.nrw.commons.upload.categories; package fr.free.nrw.commons.upload.categories;
import static fr.free.nrw.commons.wikidata.WikidataConstants.SELECTED_NEARBY_PLACE_CATEGORY;
import android.app.Activity; import android.app.Activity;
import android.app.ProgressDialog; import android.app.ProgressDialog;
import android.content.Context; import android.content.Context;
@ -81,6 +83,7 @@ public class UploadCategoriesFragment extends UploadBaseFragment implements Cate
* WikiText from the server * WikiText from the server
*/ */
private String wikiText; private String wikiText;
private String nearbyPlaceCategory;
@Nullable @Nullable
@Override @Override
@ -97,9 +100,10 @@ public class UploadCategoriesFragment extends UploadBaseFragment implements Cate
if (bundle != null) { if (bundle != null) {
media = bundle.getParcelable("Existing_Categories"); media = bundle.getParcelable("Existing_Categories");
wikiText = bundle.getString("WikiText"); wikiText = bundle.getString("WikiText");
nearbyPlaceCategory = bundle.getString(SELECTED_NEARBY_PLACE_CATEGORY);
} }
init(); init();
presenter.getCategories().observe(getViewLifecycleOwner(), this::setCategories);
} }
private void init() { private void init() {
@ -160,7 +164,7 @@ public class UploadCategoriesFragment extends UploadBaseFragment implements Cate
adapter = new UploadCategoryAdapter(categoryItem -> { adapter = new UploadCategoryAdapter(categoryItem -> {
presenter.onCategoryItemClicked(categoryItem); presenter.onCategoryItemClicked(categoryItem);
return Unit.INSTANCE; return Unit.INSTANCE;
}); }, nearbyPlaceCategory);
rvCategories.setLayoutManager(new LinearLayoutManager(getContext())); rvCategories.setLayoutManager(new LinearLayoutManager(getContext()));
rvCategories.setAdapter(adapter); rvCategories.setAdapter(adapter);
} }
@ -189,10 +193,9 @@ public class UploadCategoriesFragment extends UploadBaseFragment implements Cate
@Override @Override
public void setCategories(List<CategoryItem> categories) { public void setCategories(List<CategoryItem> categories) {
if(categories==null) { if (categories == null) {
adapter.clear(); adapter.clear();
} } else {
else{
adapter.setItems(categories); adapter.setItems(categories);
} }
} }
@ -299,6 +302,7 @@ public class UploadCategoriesFragment extends UploadBaseFragment implements Cate
@Override @Override
protected void onBecameVisible() { protected void onBecameVisible() {
super.onBecameVisible(); super.onBecameVisible();
presenter.selectCategories();
final Editable text = etSearch.getText(); final Editable text = etSearch.getText();
if (text != null) { if (text != null) {
presenter.searchForCategories(text.toString()); presenter.searchForCategories(text.toString());

View file

@ -4,9 +4,9 @@ import fr.free.nrw.commons.category.CategoryItem
import org.jetbrains.annotations.NotNull import org.jetbrains.annotations.NotNull
class UploadCategoryAdapter( class UploadCategoryAdapter(
onCategoryClicked: @NotNull() (CategoryItem) -> Unit) : onCategoryClicked: @NotNull() (CategoryItem) -> Unit, nearbyPlaceCategory: String?) :
BaseDelegateAdapter<CategoryItem>( BaseDelegateAdapter<CategoryItem>(
uploadCategoryDelegate(onCategoryClicked), uploadCategoryDelegate(onCategoryClicked, nearbyPlaceCategory),
areItemsTheSame = { oldItem, newItem -> oldItem.name == newItem.name }, areItemsTheSame = { oldItem, newItem -> oldItem.name == newItem.name },
areContentsTheSame = { oldItem, newItem -> areContentsTheSame = { oldItem, newItem ->
oldItem.name == newItem.name && oldItem.isSelected == newItem.isSelected oldItem.name == newItem.name && oldItem.isSelected == newItem.isSelected

View file

@ -6,30 +6,39 @@ import fr.free.nrw.commons.R
import fr.free.nrw.commons.category.CategoryItem import fr.free.nrw.commons.category.CategoryItem
import fr.free.nrw.commons.databinding.LayoutUploadCategoriesItemBinding import fr.free.nrw.commons.databinding.LayoutUploadCategoriesItemBinding
fun uploadCategoryDelegate(onCategoryClicked: (CategoryItem) -> Unit) = fun uploadCategoryDelegate(onCategoryClicked: (CategoryItem) -> Unit, nearbyPlaceCategory: String?) =
adapterDelegateViewBinding<CategoryItem, CategoryItem, adapterDelegateViewBinding<CategoryItem, CategoryItem,
LayoutUploadCategoriesItemBinding>({ layoutInflater, root -> LayoutUploadCategoriesItemBinding>({ layoutInflater, root ->
LayoutUploadCategoriesItemBinding.inflate(layoutInflater, root, false) LayoutUploadCategoriesItemBinding.inflate(layoutInflater, root, false)
}) { }) {
val onClickListener = { _: View? -> val onClickListener = { _: View? ->
item.isSelected = !item.isSelected if (item.name != nearbyPlaceCategory) {
binding.uploadCategoryCheckbox.isChecked = item.isSelected item.isSelected = !item.isSelected
onCategoryClicked(item) binding.uploadCategoryCheckbox.isChecked = item.isSelected
onCategoryClicked(item)
}
} }
binding.root.setOnClickListener(onClickListener) binding.root.setOnClickListener(onClickListener)
binding.uploadCategoryCheckbox.setOnClickListener(onClickListener) binding.uploadCategoryCheckbox.setOnClickListener(onClickListener)
bind { bind {
binding.uploadCategoryCheckbox.isChecked = item.isSelected if (item.name == nearbyPlaceCategory) {
item.isSelected = true
binding.uploadCategoryCheckbox.isChecked = true
binding.uploadCategoryCheckbox.isEnabled = false
} else {
binding.uploadCategoryCheckbox.isEnabled = true
binding.uploadCategoryCheckbox.isChecked = item.isSelected
}
binding.categoryLabel.text = item.name binding.categoryLabel.text = item.name
if(item.thumbnail != "null") { if (item.thumbnail != "null") {
binding.categoryImage.setImageURI(item.thumbnail) binding.categoryImage.setImageURI(item.thumbnail)
} else { } else {
binding.categoryImage.setActualImageResource(R.drawable.commons) binding.categoryImage.setActualImageResource(R.drawable.commons)
} }
if(item.description != "null") { if (item.description != "null") {
binding.categoryDescription.text = item.description binding.categoryDescription.text = item.description
} else { } else {
binding.categoryDescription.text = "" binding.categoryDescription.text = ""

View file

@ -1,5 +1,7 @@
package fr.free.nrw.commons.upload.depicts; package fr.free.nrw.commons.upload.depicts;
import static fr.free.nrw.commons.wikidata.WikidataConstants.SELECTED_NEARBY_PLACE;
import android.app.Activity; import android.app.Activity;
import android.app.ProgressDialog; import android.app.ProgressDialog;
import android.content.Context; import android.content.Context;
@ -29,6 +31,7 @@ import fr.free.nrw.commons.R;
import fr.free.nrw.commons.contributions.ContributionsFragment; import fr.free.nrw.commons.contributions.ContributionsFragment;
import fr.free.nrw.commons.kvstore.JsonKvStore; import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.media.MediaDetailFragment; import fr.free.nrw.commons.media.MediaDetailFragment;
import fr.free.nrw.commons.nearby.Place;
import fr.free.nrw.commons.ui.PasteSensitiveTextInputEditText; import fr.free.nrw.commons.ui.PasteSensitiveTextInputEditText;
import fr.free.nrw.commons.upload.UploadActivity; import fr.free.nrw.commons.upload.UploadActivity;
import fr.free.nrw.commons.upload.UploadBaseFragment; import fr.free.nrw.commons.upload.UploadBaseFragment;
@ -83,6 +86,7 @@ public class DepictsFragment extends UploadBaseFragment implements DepictsContra
* Determines each encounter of edit depicts * Determines each encounter of edit depicts
*/ */
private int count; private int count;
private Place nearbyPlace;
@Nullable @Nullable
@Override @Override
@ -99,6 +103,7 @@ public class DepictsFragment extends UploadBaseFragment implements DepictsContra
Bundle bundle = getArguments(); Bundle bundle = getArguments();
if (bundle != null) { if (bundle != null) {
media = bundle.getParcelable("Existing_Depicts"); media = bundle.getParcelable("Existing_Depicts");
nearbyPlace = bundle.getParcelable(SELECTED_NEARBY_PLACE);
} }
init(); init();
@ -111,8 +116,7 @@ public class DepictsFragment extends UploadBaseFragment implements DepictsContra
private void init() { private void init() {
if (media == null) { if (media == null) {
depictsTitle depictsTitle.setText(String.format(getString(R.string.step_count), callback.getIndexInViewFlipper(this) + 1,
.setText(getString(R.string.step_count, callback.getIndexInViewFlipper(this) + 1,
callback.getTotalNumberOfSteps(), getString(R.string.depicts_step_title))); callback.getTotalNumberOfSteps(), getString(R.string.depicts_step_title)));
} else { } else {
depictsTitle.setText(R.string.edit_depictions); depictsTitle.setText(R.string.edit_depictions);
@ -156,12 +160,12 @@ public class DepictsFragment extends UploadBaseFragment implements DepictsContra
adapter = new UploadDepictsAdapter(categoryItem -> { adapter = new UploadDepictsAdapter(categoryItem -> {
presenter.onDepictItemClicked(categoryItem); presenter.onDepictItemClicked(categoryItem);
return Unit.INSTANCE; return Unit.INSTANCE;
}); }, nearbyPlace);
} else { } else {
adapter = new UploadDepictsAdapter(item -> { adapter = new UploadDepictsAdapter(item -> {
presenter.onDepictItemClicked(item); presenter.onDepictItemClicked(item);
return Unit.INSTANCE; return Unit.INSTANCE;
}); }, nearbyPlace);
} }
depictsRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); depictsRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
depictsRecyclerView.setAdapter(adapter); depictsRecyclerView.setAdapter(adapter);

View file

@ -101,11 +101,10 @@ class DepictsPresenter @Inject constructor(
it.commonsCategories, true, it.id) it.commonsCategories, true, it.id)
} }
}, },
repository.searchAllEntities(querystring), repository.searchAllEntities(querystring)
{ it1, it2 -> ) { it1, it2 ->
it1 + it2 it1 + it2
} }
)
.subscribeOn(ioScheduler) .subscribeOn(ioScheduler)
.map { repository.selectedDepictions + it + recentDepictedItemList + controller.loadFavoritesItems() } .map { repository.selectedDepictions + it + recentDepictedItemList + controller.loadFavoritesItems() }
.map { it.filterNot { item -> WikidataDisambiguationItems.isDisambiguationItem(item.instanceOfs) } } .map { it.filterNot { item -> WikidataDisambiguationItems.isDisambiguationItem(item.instanceOfs) } }

View file

@ -1,11 +1,12 @@
package fr.free.nrw.commons.upload.depicts package fr.free.nrw.commons.upload.depicts
import fr.free.nrw.commons.nearby.Place
import fr.free.nrw.commons.upload.categories.BaseDelegateAdapter import fr.free.nrw.commons.upload.categories.BaseDelegateAdapter
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
class UploadDepictsAdapter(onDepictsClicked: (DepictedItem) -> Unit) : class UploadDepictsAdapter(onDepictsClicked: (DepictedItem) -> Unit, nearbyPlace: Place?) :
BaseDelegateAdapter<DepictedItem>( BaseDelegateAdapter<DepictedItem>(
uploadDepictsDelegate(onDepictsClicked), uploadDepictsDelegate(onDepictsClicked, nearbyPlace),
areItemsTheSame = { oldItem, newItem -> oldItem.id == newItem.id }, areItemsTheSame = { oldItem, newItem -> oldItem.id == newItem.id },
areContentsTheSame = { itemA, itemB -> itemA.isSelected == itemB.isSelected} areContentsTheSame = { itemA, itemB -> itemA.isSelected == itemB.isSelected}
) )

View file

@ -6,29 +6,39 @@ import android.view.View
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import fr.free.nrw.commons.R import fr.free.nrw.commons.R
import fr.free.nrw.commons.databinding.LayoutUploadDepictsItemBinding import fr.free.nrw.commons.databinding.LayoutUploadDepictsItemBinding
import fr.free.nrw.commons.nearby.Place
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
fun uploadDepictsDelegate(onDepictClicked: (DepictedItem) -> Unit) = fun uploadDepictsDelegate(onDepictClicked: (DepictedItem) -> Unit, nearbyPlace: Place?) =
adapterDelegateViewBinding<DepictedItem, DepictedItem, LayoutUploadDepictsItemBinding>({ layoutInflater, parent -> adapterDelegateViewBinding<DepictedItem, DepictedItem, LayoutUploadDepictsItemBinding>({ layoutInflater, parent ->
LayoutUploadDepictsItemBinding.inflate(layoutInflater, parent, false) LayoutUploadDepictsItemBinding.inflate(layoutInflater, parent, false)
}) { }) {
val onClickListener = { _: View? -> val onClickListener = { _: View? ->
item.isSelected = !item.isSelected if (item.name != nearbyPlace?.name) {
binding.depictCheckbox.isChecked = item.isSelected item.isSelected = !item.isSelected
onDepictClicked(item) binding.depictCheckbox.isChecked = item.isSelected
onDepictClicked(item)
}
} }
binding.root.setOnClickListener(onClickListener) binding.root.setOnClickListener(onClickListener)
binding.depictCheckbox.setOnClickListener(onClickListener) binding.depictCheckbox.setOnClickListener(onClickListener)
bind { bind {
binding.depictCheckbox.isChecked = item.isSelected if (item.name == nearbyPlace?.name) {
binding.depictsLabel.text = item.name item.isSelected = true
binding.description.text = item.description binding.depictCheckbox.isChecked = true
val imageUrl = item.imageUrl binding.depictCheckbox.isEnabled = false
if (TextUtils.isEmpty(imageUrl)) {
binding.depictedImage.setActualImageResource(R.drawable.ic_wikidata_logo_24dp)
} else { } else {
binding.depictedImage.setImageURI(Uri.parse(imageUrl)) binding.depictCheckbox.isEnabled = true
binding.depictCheckbox.isChecked = item.isSelected
} }
binding.depictsLabel.text = item.name
binding.description.text = item.description
val imageUrl = item.imageUrl
if (TextUtils.isEmpty(imageUrl)) {
binding.depictedImage.setActualImageResource(R.drawable.ic_wikidata_logo_24dp)
} else {
binding.depictedImage.setImageURI(Uri.parse(imageUrl))
}
} }
} }

View file

@ -3,4 +3,6 @@ package fr.free.nrw.commons.wikidata;
public class WikidataConstants { public class WikidataConstants {
public static final String PLACE_OBJECT = "place"; public static final String PLACE_OBJECT = "place";
public static final String BOOKMARKS_ITEMS = "bookmarks.items"; public static final String BOOKMARKS_ITEMS = "bookmarks.items";
public static final String SELECTED_NEARBY_PLACE = "selected.nearby.place";
public static final String SELECTED_NEARBY_PLACE_CATEGORY = "selected.nearby.place.category";
} }

View file

@ -1,8 +1,10 @@
package fr.free.nrw.commons.upload package fr.free.nrw.commons.upload
import androidx.lifecycle.MutableLiveData
import categoryItem import categoryItem
import com.nhaarman.mockitokotlin2.* import com.nhaarman.mockitokotlin2.*
import fr.free.nrw.commons.R 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.repository.UploadRepository
import fr.free.nrw.commons.upload.categories.CategoriesContract import fr.free.nrw.commons.upload.categories.CategoriesContract
import fr.free.nrw.commons.upload.categories.CategoriesPresenter import fr.free.nrw.commons.upload.categories.CategoriesPresenter
@ -13,9 +15,9 @@ import org.junit.Before
import org.junit.Test import org.junit.Test
import org.mockito.Mock import org.mockito.Mock
import org.mockito.MockitoAnnotations import org.mockito.MockitoAnnotations
import org.powermock.reflect.Whitebox
import java.lang.reflect.Method import java.lang.reflect.Method
/** /**
* The class contains unit test cases for CategoriesPresenter * The class contains unit test cases for CategoriesPresenter
*/ */
@ -39,7 +41,7 @@ class CategoriesPresenterTest {
MockitoAnnotations.initMocks(this) MockitoAnnotations.initMocks(this)
testScheduler = TestScheduler() testScheduler = TestScheduler()
categoriesPresenter = CategoriesPresenter(repository, testScheduler, testScheduler) categoriesPresenter = CategoriesPresenter(repository, testScheduler, testScheduler)
categoriesPresenter.onAttachView(view)
} }
@Test @Test
@ -51,7 +53,7 @@ class CategoriesPresenterTest {
@Test @Test
@Throws(Exception::class) @Throws(Exception::class)
fun `Test onAttachViewWithMedia when media is not null`() { fun `Test onAttachViewWithMedia when media is not null`() {
Whitebox.setInternalState(categoriesPresenter, "media", media()) categoriesPresenter.onAttachViewWithMedia(view, media())
whenever(repository.getCategories(repository.selectedExistingCategories)) whenever(repository.getCategories(repository.selectedExistingCategories))
.thenReturn(Observable.just(mutableListOf(categoryItem()))) .thenReturn(Observable.just(mutableListOf(categoryItem())))
whenever(repository.searchAll("mock", emptyList(), repository.selectedDepictions)) whenever(repository.searchAll("mock", emptyList(), repository.selectedDepictions))
@ -68,6 +70,11 @@ class CategoriesPresenterTest {
*/ */
@Test @Test
fun `searchForCategories combines selection and search results without years distinctly`() { fun `searchForCategories combines selection and search results without years distinctly`() {
categoriesPresenter.onAttachView(view)
val liveData = MutableLiveData<List<CategoryItem>>()
categoriesPresenter.setCategoryList(liveData)
categoriesPresenter.setCategoryListValue(listOf(
categoryItem("selected", "", "", true)))
val nonEmptyCaptionUploadItem = mock<UploadItem>() val nonEmptyCaptionUploadItem = mock<UploadItem>()
whenever(nonEmptyCaptionUploadItem.uploadMediaDetails) whenever(nonEmptyCaptionUploadItem.uploadMediaDetails)
.thenReturn(listOf(UploadMediaDetail(captionText = "nonEmpty"))) .thenReturn(listOf(UploadMediaDetail(captionText = "nonEmpty")))
@ -80,7 +87,7 @@ class CategoriesPresenterTest {
emptyCaptionUploadItem emptyCaptionUploadItem
) )
) )
whenever(repository.searchAll("test", listOf("nonEmpty"), repository.selectedDepictions)) whenever(repository.searchAll(any(), any(), any()))
.thenReturn( .thenReturn(
Observable.just( Observable.just(
listOf( listOf(
@ -96,25 +103,34 @@ class CategoriesPresenterTest {
categoriesPresenter.searchForCategories("test") categoriesPresenter.searchForCategories("test")
testScheduler.triggerActions() testScheduler.triggerActions()
verify(view).showProgress(true) verify(view).showProgress(true)
verify(view).showError(null)
verify(view).setCategories(null)
verify(view).setCategories(listOf(
categoryItem("selected", "", "", true)))
verify(view).showProgress(false) verify(view).showProgress(false)
verifyNoMoreInteractions(view) verifyNoMoreInteractions(view)
} }
@Test @Test
fun `searchForCategoriesTest sets Error when list is empty`() { fun `searchForCategoriesTest sets Error when list is empty`() {
whenever(repository.uploads).thenReturn(listOf()) categoriesPresenter.onAttachView(view)
whenever(repository.searchAll(any(), any(), any())).thenReturn(Observable.just(listOf())) val liveData = MutableLiveData<List<CategoryItem>>()
categoriesPresenter.setCategoryList(liveData)
// Arrange
val query = "testQuery"
val emptyCategories = ArrayList<CategoryItem>()
liveData.postValue(emptyCategories)
whenever(repository.searchAll(any(), any(), any()))
.thenReturn(Observable.just(emptyCategories))
whenever(repository.selectedCategories).thenReturn(listOf()) whenever(repository.selectedCategories).thenReturn(listOf())
categoriesPresenter.searchForCategories("test") categoriesPresenter.searchForCategories(query)
testScheduler.triggerActions() testScheduler.triggerActions()
val method: Method = CategoriesPresenter::class.java.getDeclaredMethod(
"searchResults",
String::class.java
)
method.isAccessible = true
method.invoke(categoriesPresenter, query)
verify(view).showProgress(true) verify(view).showProgress(true)
verify(view).showError(null)
verify(view).setCategories(null)
verify(view).setCategories(listOf())
verify(view).showProgress(false) verify(view).showProgress(false)
verify(view).showError(R.string.no_categories_found) verify(view).showError(R.string.no_categories_found)
verifyNoMoreInteractions(view) verifyNoMoreInteractions(view)
@ -125,6 +141,7 @@ class CategoriesPresenterTest {
*/ */
@Test @Test
fun `verifyCategories with non empty selection goes to next screen`() { fun `verifyCategories with non empty selection goes to next screen`() {
categoriesPresenter.onAttachView(view)
val item = categoryItem() val item = categoryItem()
whenever(repository.selectedCategories).thenReturn(listOf(item)) whenever(repository.selectedCategories).thenReturn(listOf(item))
categoriesPresenter.verifyCategories() categoriesPresenter.verifyCategories()
@ -134,6 +151,7 @@ class CategoriesPresenterTest {
@Test @Test
fun `verifyCategories with empty selection show no category selected`() { fun `verifyCategories with empty selection show no category selected`() {
categoriesPresenter.onAttachView(view)
whenever(repository.selectedCategories).thenReturn(listOf()) whenever(repository.selectedCategories).thenReturn(listOf())
categoriesPresenter.verifyCategories() categoriesPresenter.verifyCategories()
verify(view).showNoCategorySelected() verify(view).showNoCategorySelected()
@ -156,6 +174,7 @@ class CategoriesPresenterTest {
@Test @Test
fun testUpdateCategories() { fun testUpdateCategories() {
categoriesPresenter.onAttachView(view)
categoriesPresenter.updateCategories(media(), "[[Category:Test]]") categoriesPresenter.updateCategories(media(), "[[Category:Test]]")
} }
} }

View file

@ -145,9 +145,34 @@ class UploadCategoriesFragmentUnitTests {
@Test @Test
@Throws(Exception::class) @Throws(Exception::class)
fun testOnViewCreated() { fun testInitMethod() {
val method: Method = UploadCategoriesFragment::class.java.getDeclaredMethod(
"init"
)
Shadows.shadowOf(Looper.getMainLooper()).idle() Shadows.shadowOf(Looper.getMainLooper()).idle()
fragment.onViewCreated(view, null) method.isAccessible = true
method.invoke(fragment)
}
@Test
@Throws(Exception::class)
fun `Test init when media is non null`() {
Whitebox.setInternalState(fragment, "media", media)
val method: Method = UploadCategoriesFragment::class.java.getDeclaredMethod(
"init"
)
method.isAccessible = true
method.invoke(fragment)
}
@Test
@Throws(Exception::class)
fun testFragmentOnBecameVisible() {
val method: Method = UploadCategoriesFragment::class.java.getDeclaredMethod(
"onBecameVisible"
)
method.isAccessible = true
method.invoke(fragment)
} }
@Test @Test