Fixes 4620 : Editing categories of an existing picture: Reuse categories selection UI from the Upload Wizard (#4928)

* Entry to new UI

* Getting existing categories

* Hidden categories managed

* Category edit updated

* Category Edition implemented

* Java docs added

* Java docs added

* Java docs added

* Previous UI discarded

* Test added

* More test added

* More test added

* More test added

* More test added

* More java docs added

* Minor changes
This commit is contained in:
Ayan Sarkar 2022-04-11 16:00:21 +05:30 committed by GitHub
parent 48343035d3
commit 11292ab514
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 977 additions and 913 deletions

View file

@ -20,7 +20,6 @@ import android.graphics.drawable.Animatable;
import android.net.Uri;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@ -38,15 +37,12 @@ import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.ScrollView;
import android.widget.SearchView;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
@ -57,8 +53,6 @@ import com.facebook.drawee.interfaces.DraweeController;
import com.facebook.drawee.view.SimpleDraweeView;
import com.facebook.imagepipeline.image.ImageInfo;
import com.facebook.imagepipeline.request.ImageRequest;
import com.jakewharton.rxbinding2.view.RxView;
import com.jakewharton.rxbinding2.widget.RxSearchView;
import com.mapbox.mapboxsdk.camera.CameraPosition;
import com.mapbox.mapboxsdk.geometry.LatLng;
import fr.free.nrw.commons.LocationPicker.LocationPicker;
@ -71,8 +65,6 @@ import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.category.CategoryClient;
import fr.free.nrw.commons.category.CategoryDetailsActivity;
import fr.free.nrw.commons.category.CategoryEditHelper;
import fr.free.nrw.commons.category.CategoryEditSearchRecyclerViewAdapter;
import fr.free.nrw.commons.category.CategoryEditSearchRecyclerViewAdapter.Callback;
import fr.free.nrw.commons.contributions.ContributionsFragment;
import fr.free.nrw.commons.coordinates.CoordinateEditHelper;
import fr.free.nrw.commons.delete.DeleteHelper;
@ -83,9 +75,9 @@ import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
import fr.free.nrw.commons.explore.depictions.WikidataItemDetailsActivity;
import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.location.LocationServiceManager;
import fr.free.nrw.commons.nearby.Label;
import fr.free.nrw.commons.profile.ProfileActivity;
import fr.free.nrw.commons.ui.widget.HtmlTextView;
import fr.free.nrw.commons.upload.categories.UploadCategoriesFragment;
import fr.free.nrw.commons.upload.depicts.DepictsFragment;
import fr.free.nrw.commons.upload.UploadMediaDetail;
import fr.free.nrw.commons.utils.ViewUtilWrapper;
@ -99,7 +91,6 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Named;
import org.apache.commons.lang3.StringUtils;
@ -107,7 +98,7 @@ import org.wikipedia.language.AppLanguageLookUpTable;
import org.wikipedia.util.DateUtil;
import timber.log.Timber;
public class MediaDetailFragment extends CommonsDaggerSupportFragment implements Callback,
public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
CategoryEditHelper.Callback {
private static final int REQUEST_CODE = 1001 ;
@ -212,24 +203,8 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
LinearLayout toDoLayout;
@BindView(R.id.toDoReason)
TextView toDoReason;
@BindView(R.id.category_edit_layout)
LinearLayout categoryEditLayout;
@BindView(R.id.et_search)
SearchView categorySearchView;
@BindView(R.id.rv_categories)
RecyclerView categoryRecyclerView;
@BindView(R.id.update_categories_button)
Button updateCategoriesButton;
@BindView(R.id.coordinate_edit)
Button coordinateEditButton;
@BindView(R.id.dummy_category_edit_container)
LinearLayout dummyCategoryEditContainer;
@BindView(R.id.pb_categories)
ProgressBar progressbarCategories;
@BindView(R.id.existing_categories)
TextView existingCategories;
@BindView(R.id.no_results_found)
TextView noResultsFound;
@BindView(R.id.dummy_caption_description_container)
LinearLayout showCaptionAndDescriptionContainer;
@BindView(R.id.show_caption_description_textview)
@ -247,6 +222,8 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
ProgressBar progressBarDeletion;
@BindView(R.id.progressBarEdit)
ProgressBar progressBarEditDescription;
@BindView(R.id.progressBarEditCategory)
ProgressBar progressBarEditCategory;
@BindView(R.id.description_edit)
Button editDescription;
@ -263,7 +240,6 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
private int newWidthOfImageView;
private boolean heightVerifyingBoolean = true; // helps in maintaining aspect ratio
private ViewTreeObserver.OnGlobalLayoutListener layoutListener; // for layout stuff, only used once!
private CategoryEditSearchRecyclerViewAdapter categoryEditSearchRecyclerViewAdapter;
//Had to make this class variable, to implement various onClicks, which access the media, also I fell why make separate variables when one can serve the purpose
private Media media;
@ -396,11 +372,6 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
.setVisibility(View.GONE);
}
}
categoryEditSearchRecyclerViewAdapter =
new CategoryEditSearchRecyclerViewAdapter(getContext(), new ArrayList<>(
Label.valuesAsList()), categoryRecyclerView, categoryClient, this);
categoryRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
categoryRecyclerView.setAdapter(categoryEditSearchRecyclerViewAdapter);
// detail provider is null when fragment is shown in review activity
if (detailProvider != null) {
media = detailProvider.getMediaAtPosition(index);
@ -473,6 +444,11 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::onMediaRefreshed, Timber::e),
mediaDataExtractor.getCurrentWikiText(
Objects.requireNonNull(media.getFilename()))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::updateCategoryList, Timber::e),
mediaDataExtractor.checkDeletionRequestExists(media)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
@ -485,6 +461,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
}
private void onMediaRefreshed(Media media) {
media.setCategories(this.media.getCategories());
this.media = media;
setTextFields(media);
compositeDisposable.addAll(
@ -494,7 +471,6 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
.subscribe(this::onDepictionsLoaded, Timber::e)
);
// compositeDisposable.add(disposable);
setupToDo();
}
private void onDiscussionLoaded(String discussion) {
@ -602,30 +578,6 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
image.setController(controller);
}
/**
* Displays layout about missing actions to inform user
* - Images that they uploaded with no categories/descriptions, so that they can add them
* - Images that can be added to associated Wikipedia articles that have no pictures
*/
private void setupToDo() {
updateToDoWarning();
compositeDisposable.add(RxSearchView.queryTextChanges(categorySearchView)
.takeUntil(RxView.detaches(categorySearchView))
.debounce(500, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(query -> {
this.categorySearchQuery = query.toString();
//update image list
if (!TextUtils.isEmpty(query)) {
if (categoryEditLayout.getVisibility() == VISIBLE) {
((CategoryEditSearchRecyclerViewAdapter) categoryRecyclerView.getAdapter()).
getFilter().filter(query.toString());
}
}
}, Timber::e
));
}
private void updateToDoWarning() {
String toDoMessage = "";
boolean toDoNeeded = false;
@ -685,11 +637,6 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
categoryNames.clear();
categoryNames.addAll(media.getCategories());
categoryEditSearchRecyclerViewAdapter.addToCategories(media.getCategories());
updateSelectedCategoriesTextView(categoryEditSearchRecyclerViewAdapter.getCategories());
categoryRecyclerView.setVisibility(GONE);
updateCategoryList();
if (media.getAuthor() == null || media.getAuthor().equals("")) {
authorLayout.setVisibility(GONE);
@ -698,20 +645,35 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
}
}
private void updateCategoryList() {
List<String> allCategories = new ArrayList<String>( media.getCategories());
if (media.getAddedCategories() != null) {
// TODO this added categories logic should be removed.
// It is just a short term hack. Categories should be fetch everytime they are updated.
// if media.getCategories contains addedCategory, then do not re-add them
for (String addedCategory : media.getAddedCategories()) {
if (allCategories.contains(addedCategory)) {
media.setAddedCategories(null);
break;
}
}
allCategories.addAll(media.getAddedCategories());
/**
* Gets new categories from the WikiText and updates it on the UI
*
* @param s WikiText
*/
private void updateCategoryList(final String s) {
final List<String> allCategories = new ArrayList<String>();
int i = s.indexOf("[[Category:");
while(i != -1){
final String category = s.substring(i+11, s.indexOf("]]", i));
allCategories.add(category);
i = s.indexOf("]]", i);
i = s.indexOf("[[Category:", i);
}
media.setCategories(allCategories);
if (allCategories.isEmpty()) {
// Stick in a filler element.
allCategories.add(getString(R.string.detail_panel_cats_none));
}
categoryEditButton.setVisibility(VISIBLE);
rebuildCatList(allCategories);
}
/**
* Updates the categories
*/
public void updateCategories() {
List<String> allCategories = new ArrayList<String>(media.getAddedCategories());
media.setCategories(allCategories);
if (allCategories.isEmpty()) {
// Stick in a filler element.
allCategories.add(getString(R.string.detail_panel_cats_none));
@ -720,35 +682,6 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
rebuildCatList(allCategories);
}
@Override
public void updateSelectedCategoriesTextView(List<String> selectedCategories) {
if (selectedCategories == null || selectedCategories.size() == 0) {
updateCategoriesButton.setClickable(false);
updateCategoriesButton.setAlpha(.5f);
} else {
existingCategories.setText(StringUtils.join(selectedCategories,", "));
if (selectedCategories.equals(media.getCategories())) {
updateCategoriesButton.setClickable(false);
updateCategoriesButton.setAlpha(.5f);
} else {
updateCategoriesButton.setClickable(true);
updateCategoriesButton.setAlpha(1f);
}
}
}
@Override
public void noResultsFound() {
categoryRecyclerView.setVisibility(GONE);
noResultsFound.setVisibility(VISIBLE);
}
@Override
public void someResultsFound() {
categoryRecyclerView.setVisibility(VISIBLE);
noResultsFound.setVisibility(GONE);
}
/**
* Populates media details fragment with depiction list
* @param idAndCaptions
@ -802,41 +735,41 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
Toast.makeText(getContext(), getString(R.string.wikicode_copied), Toast.LENGTH_SHORT).show();
}
@OnClick(R.id.dummy_category_edit_container)
public void onOutsideOfCategoryEditClicked() {
if (dummyCategoryEditContainer.getVisibility() == VISIBLE) {
dummyCategoryEditContainer.setVisibility(GONE);
}
}
@OnClick(R.id.categoryEditButton)
public void onCategoryEditButtonClicked(){
displayHideCategorySearch();
progressBarEditCategory.setVisibility(VISIBLE);
categoryEditButton.setVisibility(GONE);
getWikiText();
}
/**
* Hides the categoryEditContainer.
* returns true after closing the categoryEditContainer if open, implying that event was handled.
* else returns false
* @return
* Gets WikiText from the server and send it to catgory editor
*/
public boolean hideCategoryEditContainerIfOpen(){
if (dummyCategoryEditContainer.getVisibility() == VISIBLE) {
// editCategory is open, close it and return true as the event was handled.
dummyCategoryEditContainer.setVisibility(GONE);
return true;
}
// Event was not handled.
return false;
private void getWikiText() {
compositeDisposable.add(mediaDataExtractor.getCurrentWikiText(
Objects.requireNonNull(media.getFilename()))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::gotoCategoryEditor, Timber::e));
}
public void displayHideCategorySearch() {
showCaptionAndDescriptionContainer.setVisibility(GONE);
if (dummyCategoryEditContainer.getVisibility() != VISIBLE) {
dummyCategoryEditContainer.setVisibility(VISIBLE);
} else {
dummyCategoryEditContainer.setVisibility(GONE);
}
/**
* Opens the category editor
*
* @param s WikiText
*/
private void gotoCategoryEditor(final String s) {
categoryEditButton.setVisibility(VISIBLE);
progressBarEditCategory.setVisibility(GONE);
final Fragment categoriesFragment = new UploadCategoriesFragment();
final Bundle bundle = new Bundle();
bundle.putParcelable("Existing_Categories", media);
bundle.putString("WikiText", s);
categoriesFragment.setArguments(bundle);
final FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
transaction.replace(R.id.mediaDetailFrameLayout, categoriesFragment);
transaction.addToBackStack(null);
transaction.commit();
}
@OnClick(R.id.coordinate_edit)
@ -1113,29 +1046,6 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
media.setCaptions(updatedCaptions);
}
@OnClick(R.id.update_categories_button)
public void onUpdateCategoriesClicked() {
updateCategories(categoryEditSearchRecyclerViewAdapter.getNewCategories());
displayHideCategorySearch();
}
@OnClick(R.id.cancel_categories_button)
public void onCancelCategoriesClicked() {
displayHideCategorySearch();
}
public void updateCategories(List<String> selectedCategories) {
compositeDisposable.add(categoryEditHelper.makeCategoryEdit(getContext(), media, selectedCategories, this)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(s -> {
Timber.d("Categories are added.");
onOutsideOfCategoryEditClicked();
media.setAddedCategories(selectedCategories);
updateCategoryList();
}));
}
/**
* Fetched coordinates are replaced with existing coordinates by a POST API call.
* @param Latitude to be added
@ -1417,7 +1327,6 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
@OnClick(R.id.show_caption_description_textview)
void showCaptionAndDescription() {
dummyCategoryEditContainer.setVisibility(GONE);
if (showCaptionAndDescriptionContainer.getVisibility() == GONE) {
showCaptionAndDescriptionContainer.setVisibility(VISIBLE);
setUpCaptionAndDescriptionLayout();

View file

@ -404,16 +404,6 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
public void nominatingForDeletion(int index) {
provider.refreshNominatedMedia(index);
}
/**
* backButtonClicked is called on a back event in the media details pager.
* returns true after closing the categoryEditContainer if open, implying that event was handled.
* else returns false
* @return
*/
public boolean backButtonClicked(){
return ((MediaDetailFragment)(adapter.getCurrentFragment())).hideCategoryEditContainerIfOpen();
}
public interface MediaDetailProvider {
Media getMediaAtPosition(int i);