mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-27 12:53:55 +01:00
* WikidataEditService: stop automatically adding WikidataPlace as a depiction When the user initiates the upload process from Nearby and also manually adds the place as a depiction, the depiction is added twice. Since this behavior is invisible to the user, it is being removed in preparation for auto-selecting the place as a depiction on the DepictsFragment screen. * DepictsFragment: auto-select place as a depiction Pass the Place reference from UploadActivity to DepictsFragment and select the corresponding DepictedItem. Using the place id, retrieve the corresponding Entity to create and select a DepictedItem. * UploadRepository: use Place from UploadItem to obtain a DepictedItem Instead of passing a Place object from UploadActivity to DepictsFragment and then passing the Place object up the chain to obtain and select a DepictedItem, retrieve the Place object directly within UploadRepository * DepictsFragment: select Place depiction when fragment becomes visible * UploadDepictsAdapter: make adapter aware of selection state Update selection state when recycled list items are automatically selected, preventing automatically selected items from appearing as unselected until they are forced to re-bind (i.e. after scrolling) * DepictsFragment: pre-select place depictions for all UploadItems If several images are selected and set to different places, pre-select all place depictions to reinforce the intended upload workflow philosophy (i.e. all images in a set are intended to be from/of the same place). See discussion in commons-app/apps-android-commons#3694 * DepictsFragment: scroll to the top every time list is updated
This commit is contained in:
parent
10ed6678b3
commit
2be828c50e
7 changed files with 85 additions and 15 deletions
|
|
@ -19,8 +19,11 @@ 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.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Set;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
|
@ -254,6 +257,23 @@ public class UploadRepository {
|
||||||
return depictModel.searchAllEntities(query);
|
return depictModel.searchAllEntities(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the depiction for each unique {@link Place} associated with an {@link UploadItem}
|
||||||
|
* from {@link #getUploads()}
|
||||||
|
*
|
||||||
|
* @return a single that provides the depictions
|
||||||
|
*/
|
||||||
|
public Single<List<DepictedItem>> getPlaceDepictions() {
|
||||||
|
final Set<Place> places = new HashSet<>();
|
||||||
|
for (final UploadItem item : getUploads()) {
|
||||||
|
final Place place = item.getPlace();
|
||||||
|
if (place != null) {
|
||||||
|
places.add(place);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return depictModel.getPlaceDepictions(new ArrayList<>(places));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns nearest place matching the passed latitude and longitude
|
* Returns nearest place matching the passed latitude and longitude
|
||||||
* @param decLatitude
|
* @param decLatitude
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,11 @@ public interface DepictsContract {
|
||||||
*/
|
*/
|
||||||
void searchForDepictions(String query);
|
void searchForDepictions(String query);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selects all associated places (if any) as depictions
|
||||||
|
*/
|
||||||
|
void selectPlaceDepictions();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if depictions were selected
|
* Check if depictions were selected
|
||||||
* from the depiction list
|
* from the depiction list
|
||||||
|
|
|
||||||
|
|
@ -100,6 +100,14 @@ public class DepictsFragment extends UploadBaseFragment implements DepictsContra
|
||||||
depictsRecyclerView.setAdapter(adapter);
|
depictsRecyclerView.setAdapter(adapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onBecameVisible() {
|
||||||
|
super.onBecameVisible();
|
||||||
|
// Select Place depiction as the fragment becomes visible to ensure that the most up to date
|
||||||
|
// Place is used (i.e. if the user accepts a nearby place dialog)
|
||||||
|
presenter.selectPlaceDepictions();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void goToNextScreen() {
|
public void goToNextScreen() {
|
||||||
callback.onNextButtonClicked(callback.getIndexInViewFlipper(this));
|
callback.onNextButtonClicked(callback.getIndexInViewFlipper(this));
|
||||||
|
|
@ -146,6 +154,7 @@ public class DepictsFragment extends UploadBaseFragment implements DepictsContra
|
||||||
@Override
|
@Override
|
||||||
public void setDepictsList(List<DepictedItem> depictedItemList) {
|
public void setDepictsList(List<DepictedItem> depictedItemList) {
|
||||||
adapter.setItems(depictedItemList);
|
adapter.setItems(depictedItemList);
|
||||||
|
depictsRecyclerView.smoothScrollToPosition(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnClick(R.id.depicts_next)
|
@OnClick(R.id.depicts_next)
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,35 @@ class DepictsPresenter @Inject constructor(
|
||||||
compositeDisposable.clear()
|
compositeDisposable.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selects the place depictions retrieved by the repository
|
||||||
|
*/
|
||||||
|
override fun selectPlaceDepictions() {
|
||||||
|
compositeDisposable.add(repository.placeDepictions
|
||||||
|
.subscribeOn(ioScheduler)
|
||||||
|
.observeOn(mainThreadScheduler)
|
||||||
|
.subscribe(::selectNewDepictions)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selects each [DepictedItem] in a given list as if they were clicked by the user by calling
|
||||||
|
* [onDepictItemClicked] for each depiction and adding the depictions to [depictedItems]
|
||||||
|
*/
|
||||||
|
private fun selectNewDepictions(toSelect: List<DepictedItem>) {
|
||||||
|
toSelect.forEach {
|
||||||
|
it.isSelected = true
|
||||||
|
repository.onDepictItemClicked(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the new selections to the list of depicted items so that the selections appear
|
||||||
|
// immediately (i.e. without any search term queries)
|
||||||
|
depictedItems.value?.toMutableList()
|
||||||
|
?.let { toSelect + it }
|
||||||
|
?.distinctBy(DepictedItem::id)
|
||||||
|
?.let { depictedItems.value = it }
|
||||||
|
}
|
||||||
|
|
||||||
override fun onPreviousButtonClicked() {
|
override fun onPreviousButtonClicked() {
|
||||||
view.goToPreviousScreen()
|
view.goToPreviousScreen()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,5 +6,6 @@ import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||||
class UploadDepictsAdapter(onDepictsClicked: (DepictedItem) -> Unit) :
|
class UploadDepictsAdapter(onDepictsClicked: (DepictedItem) -> Unit) :
|
||||||
BaseDelegateAdapter<DepictedItem>(
|
BaseDelegateAdapter<DepictedItem>(
|
||||||
uploadDepictsDelegate(onDepictsClicked),
|
uploadDepictsDelegate(onDepictsClicked),
|
||||||
areItemsTheSame = { oldItem, newItem -> oldItem.id == newItem.id }
|
areItemsTheSame = { oldItem, newItem -> oldItem.id == newItem.id },
|
||||||
|
areContentsTheSame = { itemA, itemB -> itemA.isSelected == itemB.isSelected}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package fr.free.nrw.commons.upload.structure.depictions
|
||||||
import fr.free.nrw.commons.explore.depictions.DepictsClient
|
import fr.free.nrw.commons.explore.depictions.DepictsClient
|
||||||
import fr.free.nrw.commons.nearby.Place
|
import fr.free.nrw.commons.nearby.Place
|
||||||
import io.reactivex.Flowable
|
import io.reactivex.Flowable
|
||||||
|
import io.reactivex.Observable
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
import io.reactivex.processors.BehaviorProcessor
|
import io.reactivex.processors.BehaviorProcessor
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
@ -27,19 +28,29 @@ class DepictModel @Inject constructor(private val depictsClient: DepictsClient)
|
||||||
fun searchAllEntities(query: String): Flowable<List<DepictedItem>> {
|
fun searchAllEntities(query: String): Flowable<List<DepictedItem>> {
|
||||||
return if (query.isBlank())
|
return if (query.isBlank())
|
||||||
nearbyPlaces.switchMap { places: List<Place> ->
|
nearbyPlaces.switchMap { places: List<Place> ->
|
||||||
depictsClient.getEntities(places.toIds())
|
getPlaceDepictions(places).toFlowable()
|
||||||
.map {
|
|
||||||
it.entities()
|
|
||||||
.values
|
|
||||||
.mapIndexed { index, entity -> DepictedItem(entity, places[index]) }
|
|
||||||
}
|
|
||||||
.onErrorResumeWithEmptyList()
|
|
||||||
.toFlowable()
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
networkItems(query)
|
networkItems(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides [DepictedItem] instances via a [Single] for a given list of [Place], providing an
|
||||||
|
* empty list if no places are provided or if there is an error
|
||||||
|
*/
|
||||||
|
fun getPlaceDepictions(places: List<Place>): Single<List<DepictedItem>> =
|
||||||
|
places.toIds().let { ids ->
|
||||||
|
if (ids.isNotEmpty())
|
||||||
|
depictsClient.getEntities(ids)
|
||||||
|
.map{
|
||||||
|
it.entities()
|
||||||
|
.values
|
||||||
|
.mapIndexed { index, entity -> DepictedItem(entity, places[index])}
|
||||||
|
}
|
||||||
|
.onErrorResumeWithEmptyList()
|
||||||
|
else Single.just(emptyList())
|
||||||
|
}
|
||||||
|
|
||||||
private fun networkItems(query: String): Flowable<List<DepictedItem>> {
|
private fun networkItems(query: String): Flowable<List<DepictedItem>> {
|
||||||
return depictsClient.searchForDepictions(query, SEARCH_DEPICTS_LIMIT, 0)
|
return depictsClient.searchForDepictions(query, SEARCH_DEPICTS_LIMIT, 0)
|
||||||
.onErrorResumeWithEmptyList()
|
.onErrorResumeWithEmptyList()
|
||||||
|
|
|
||||||
|
|
@ -206,12 +206,7 @@ public class WikidataEditService {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Observable<Boolean> depictionEdits(Contribution contribution, Long fileEntityId) {
|
private Observable<Boolean> depictionEdits(Contribution contribution, Long fileEntityId) {
|
||||||
final ArrayList<WikidataItem> depictedItems = new ArrayList<>(contribution.getDepictedItems());
|
return Observable.fromIterable(contribution.getDepictedItems())
|
||||||
final WikidataPlace wikidataPlace = contribution.getWikidataPlace();
|
|
||||||
if (wikidataPlace != null) {
|
|
||||||
depictedItems.add(wikidataPlace);
|
|
||||||
}
|
|
||||||
return Observable.fromIterable(depictedItems)
|
|
||||||
.concatMap(wikidataItem -> addDepictsProperty(fileEntityId.toString(), wikidataItem));
|
.concatMap(wikidataItem -> addDepictsProperty(fileEntityId.toString(), wikidataItem));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue