Todo for user (#3851)

* Add a simple message

* Categories are edited

* Display categories

* read whole page

* Revert wrong changes

* Add newly added category field

* Doesnt displaey alreasy added categories

* add strings for notifications

* clean code

* Readd accidentally removed imports

* Fix edit layout style

* Fix category update messages

* Pass isWikipediaButtonDisplayed information to fragment

* Remove unused class

* Fix strings

* Fix string

* Add exeption for uncategorised images too

* Revert project.xml changes

* fix update buttonvisibility issue

* Make sure it works for auto added categories too

* make the button visible

* Make the button appear when categories are entered

* Include cancel button

* Make view updated too

* Make category view edited

* Update categories in an hacky way

* Fix unnecessary method call

* Add notes for short term fix to display added category

* Fix tests

* Fix strings

* Fix click issue
This commit is contained in:
neslihanturan 2020-08-17 14:30:46 +03:00 committed by GitHub
parent f5e28834fc
commit 1856196851
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 799 additions and 36 deletions

View file

@ -121,4 +121,14 @@ class Media constructor(
get() = captions[Locale.getDefault().language] get() = captions[Locale.getDefault().language]
?: captions.values.firstOrNull() ?: captions.values.firstOrNull()
?: displayTitle ?: displayTitle
/**
* Gets the categories the file falls under.
* @return file categories as an ArrayList of Strings
*/
var addedCategories: List<String>? = null
// TODO added categories should be removed. It is added for a short fix. On category update,
// categories should be re-fetched instead
get() = field // getter
set(value) { field = value } // setter
} }

View file

@ -1,5 +1,7 @@
package fr.free.nrw.commons.actions; package fr.free.nrw.commons.actions;
import io.reactivex.Single;
import io.reactivex.SingleOnSubscribe;
import org.wikipedia.csrf.CsrfTokenClient; import org.wikipedia.csrf.CsrfTokenClient;
import org.wikipedia.dataclient.Service; import org.wikipedia.dataclient.Service;
@ -48,12 +50,16 @@ public class PageEditClient {
* @param summary Edit summary * @param summary Edit summary
*/ */
public Observable<Boolean> appendEdit(String pageTitle, String appendText, String summary) { public Observable<Boolean> appendEdit(String pageTitle, String appendText, String summary) {
return Single.create((SingleOnSubscribe<String>) emitter -> {
try { try {
return pageEditInterface.postAppendEdit(pageTitle, summary, appendText, csrfTokenClient.getTokenBlocking()) emitter.onSuccess(csrfTokenClient.getTokenBlocking());
.map(editResponse -> editResponse.edit().editSucceeded());
} catch (Throwable throwable) { } catch (Throwable throwable) {
return Observable.just(false); emitter.onError(throwable);
throwable.printStackTrace();
} }
}).flatMapObservable(token -> pageEditInterface.postAppendEdit(pageTitle, summary, appendText, token)
.map(editResponse -> editResponse.edit().editSucceeded()));
} }
/** /**

View file

@ -8,6 +8,8 @@ import javax.inject.Singleton
const val CATEGORY_PREFIX = "Category:" const val CATEGORY_PREFIX = "Category:"
const val SUB_CATEGORY_CONTINUATION_PREFIX = "sub_category_" const val SUB_CATEGORY_CONTINUATION_PREFIX = "sub_category_"
const val PARENT_CATEGORY_CONTINUATION_PREFIX = "parent_category_" const val PARENT_CATEGORY_CONTINUATION_PREFIX = "parent_category_"
const val CATEGORY_UNCATEGORISED = "uncategorised"
const val CATEGORY_NEEDING_CATEGORIES = "needing categories"
/** /**
* Category Client to handle custom calls to Commons MediaWiki APIs * Category Client to handle custom calls to Commons MediaWiki APIs

View file

@ -0,0 +1,109 @@
package fr.free.nrw.commons.category;
import static fr.free.nrw.commons.notification.NotificationHelper.NOTIFICATION_EDIT_CATEGORY;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.util.Log;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.actions.PageEditClient;
import fr.free.nrw.commons.notification.NotificationHelper;
import fr.free.nrw.commons.utils.ViewUtilWrapper;
import io.reactivex.Observable;
import io.reactivex.Single;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
import timber.log.Timber;
public class CategoryEditHelper {
private final NotificationHelper notificationHelper;
public final PageEditClient pageEditClient;
private final ViewUtilWrapper viewUtil;
private final String username;
private Callback callback;
@Inject
public CategoryEditHelper(NotificationHelper notificationHelper,
@Named("commons-page-edit") PageEditClient pageEditClient,
ViewUtilWrapper viewUtil,
@Named("username") String username) {
this.notificationHelper = notificationHelper;
this.pageEditClient = pageEditClient;
this.viewUtil = viewUtil;
this.username = username;
}
/**
* Public interface to edit categories
* @param context
* @param media
* @param categories
* @return
*/
public Single<Boolean> makeCategoryEdit(Context context, Media media, List<String> categories, Callback callback) {
viewUtil.showShortToast(context, context.getString(R.string.category_edit_helper_make_edit_toast));
return addCategory(media, categories)
.flatMapSingle(result -> Single.just(showCategoryEditNotification(context, media, result)))
.firstOrError();
}
/**
* Appends new categories
* @param media
* @param categories to be added
* @return
*/
private Observable<Boolean> addCategory(Media media, List<String> categories) {
Timber.d("thread is category adding %s", Thread.currentThread().getName());
String summary = "Adding categories";
StringBuilder buffer = new StringBuilder();
if (categories != null && categories.size() != 0) {
for (int i = 0; i < categories.size(); i++) {
buffer.append("\n[[Category:").append(categories.get(i)).append("]]");
}
} else {
buffer.append("{{subst:unc}}");
}
String appendText = buffer.toString();
return pageEditClient.appendEdit(media.getFilename(), appendText + "\n", summary);
}
private boolean showCategoryEditNotification(Context context, Media media, boolean result) {
String message;
String title = context.getString(R.string.category_edit_helper_show_edit_title);
if (result) {
title += ": " + context.getString(R.string.category_edit_helper_show_edit_title_success);
StringBuilder categoriesInMessage = new StringBuilder();
List<String> mediaCategoryList = media.getCategories();
for (String category : mediaCategoryList) {
categoriesInMessage.append(category);
if (category.equals(mediaCategoryList.get(mediaCategoryList.size()-1))) {
continue;
}
categoriesInMessage.append(",");
}
message = context.getResources().getQuantityString(R.plurals.category_edit_helper_show_edit_message_if, mediaCategoryList.size(), categoriesInMessage.toString());
} else {
title += ": " + context.getString(R.string.category_edit_helper_show_edit_title);
message = context.getString(R.string.category_edit_helper_edit_message_else) ;
}
String urlForFile = BuildConfig.COMMONS_URL + "/wiki/" + media.getFilename();
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(urlForFile));
notificationHelper.showNotification(context, title, message, NOTIFICATION_EDIT_CATEGORY, browserIntent);
return result;
}
public interface Callback {
boolean updateCategoryDisplay(List<String> categories);
}
}

View file

@ -0,0 +1,176 @@
package fr.free.nrw.commons.category;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.category.CategoryEditSearchRecyclerViewAdapter.RecyclerViewHolder;
import fr.free.nrw.commons.nearby.Label;
import java.util.ArrayList;
import java.util.List;
public class CategoryEditSearchRecyclerViewAdapter
extends RecyclerView.Adapter<RecyclerViewHolder>
implements Filterable {
private List<String> displayedCategories;
private List<String> categories = new ArrayList<>();
private List<String> newCategories = new ArrayList<>();
private final LayoutInflater inflater;
private CategoryClient categoryClient;
private Context context;
private Callback callback;
public CategoryEditSearchRecyclerViewAdapter(Context context, ArrayList<Label> labels,
RecyclerView categoryRecyclerView, CategoryClient categoryClient, Callback callback) {
this.context = context;
inflater = LayoutInflater.from(context);
this.categoryClient = categoryClient;
this.callback = callback;
}
public void addToCategories(List<String> categories) {
for(String category : categories) {
if (!this.categories.contains(category)) {
this.categories.add(category);
}
}
}
public void addToCategories(String categoryToBeAdded) {
if (!categories.contains(categoryToBeAdded)) {
categories.add(categoryToBeAdded);
}
}
public void removeFromCategories(String categoryToBeRemoved) {
if (categories.contains(categoryToBeRemoved)) {
categories.remove(categoryToBeRemoved);
}
}
public void removeFromNewCategories(String categoryToBeRemoved) {
if (newCategories.contains(categoryToBeRemoved)) {
newCategories.remove(categoryToBeRemoved);
}
}
public void addToNewCategories(List<String> newCategories) {
for(String category : newCategories) {
if (!this.newCategories.contains(category)) {
this.newCategories.add(category);
}
}
}
public void addToNewCategories(String addedCategory) {
if (!newCategories.contains(addedCategory)) {
newCategories.add(addedCategory);
}
}
public List<String> getCategories() {
return categories;
}
public List<String> getNewCategories() {
return newCategories;
}
@Override
public Filter getFilter() {
return new Filter() {
@Override
protected FilterResults performFiltering(CharSequence constraint) {
FilterResults results = new FilterResults();
List<String> resultCategories = categoryClient.searchCategories(constraint.toString(), 10).blockingGet();
results.values = resultCategories;
results.count = resultCategories.size();
return results;
}
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
List<String> resultList = (List<String>)results.values;
// Do not re-add already added categories
for (String category : categories) {
if (resultList.contains(category)) {
resultList.remove(category);
}
}
displayedCategories = resultList;
notifyDataSetChanged();
if (displayedCategories.size()==0) {
callback.noResultsFound();
} else {
callback.someResultsFound();
}
}
};
}
public class RecyclerViewHolder extends RecyclerView.ViewHolder {
public CheckBox categoryCheckBox;
public TextView categoryTextView;
public RecyclerViewHolder(View view) {
super(view);
categoryCheckBox = view.findViewById(R.id.category_checkbox);
categoryTextView = view.findViewById(R.id.category_text);
categoryCheckBox.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
addToNewCategories(categoryTextView.getText().toString());
} else {
removeFromNewCategories(categoryTextView.getText().toString());
}
List<String> allCategories = new ArrayList<>();
allCategories.addAll(categories);
allCategories.addAll(newCategories);
callback.updateSelectedCategoriesTextView(allCategories);
}
});
}
}
@NonNull
@Override
public RecyclerViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View itemView = inflater.inflate(R.layout.layout_edit_category_item , parent, false);
return new RecyclerViewHolder(itemView);
}
@Override
public void onBindViewHolder(@NonNull RecyclerViewHolder holder, int position) {
holder.categoryTextView.setText(displayedCategories.get(position));
}
@Override
public long getItemId(int position) {
return displayedCategories.get(position).hashCode();
}
@Override
public int getItemCount() {
return (displayedCategories == null) ? 0 : displayedCategories.size();
}
public interface Callback {
void updateSelectedCategoriesTextView(List<String> selectedCategories);
void noResultsFound();
void someResultsFound();
}
}

View file

@ -51,6 +51,7 @@ public class ContributionViewHolder extends RecyclerView.ViewHolder {
private Contribution contribution; private Contribution contribution;
private final CompositeDisposable compositeDisposable = new CompositeDisposable(); private final CompositeDisposable compositeDisposable = new CompositeDisposable();
private final MediaClient mediaClient; private final MediaClient mediaClient;
private boolean isWikipediaButtonDisplayed;
ContributionViewHolder(final View parent, final Callback callback, ContributionViewHolder(final View parent, final Callback callback,
final MediaClient mediaClient) { final MediaClient mediaClient) {
@ -160,6 +161,7 @@ public class ContributionViewHolder extends RecyclerView.ViewHolder {
private void displayWikipediaButton(Boolean mediaExists) { private void displayWikipediaButton(Boolean mediaExists) {
if (!mediaExists) { if (!mediaExists) {
addToWikipediaButton.setVisibility(View.VISIBLE); addToWikipediaButton.setVisibility(View.VISIBLE);
isWikipediaButtonDisplayed = true;
cancelButton.setVisibility(View.GONE); cancelButton.setVisibility(View.GONE);
retryButton.setVisibility(View.GONE); retryButton.setVisibility(View.GONE);
imageOptions.setVisibility(View.VISIBLE); imageOptions.setVisibility(View.VISIBLE);
@ -199,7 +201,7 @@ public class ContributionViewHolder extends RecyclerView.ViewHolder {
@OnClick(R.id.contributionImage) @OnClick(R.id.contributionImage)
public void imageClicked() { public void imageClicked() {
callback.openMediaDetail(position); callback.openMediaDetail(position, isWikipediaButtonDisplayed);
} }
@OnClick(R.id.wikipediaButton) @OnClick(R.id.wikipediaButton)

View file

@ -482,12 +482,12 @@ public class ContributionsFragment
* contribution. * contribution.
*/ */
@Override @Override
public void showDetail(int position) { public void showDetail(int position, boolean isWikipediaButtonDisplayed) {
if (mediaDetailPagerFragment == null || !mediaDetailPagerFragment.isVisible()) { if (mediaDetailPagerFragment == null || !mediaDetailPagerFragment.isVisible()) {
mediaDetailPagerFragment = new MediaDetailPagerFragment(); mediaDetailPagerFragment = new MediaDetailPagerFragment();
showMediaDetailPagerFragment(); showMediaDetailPagerFragment();
} }
mediaDetailPagerFragment.showImage(position); mediaDetailPagerFragment.showImage(position, isWikipediaButtonDisplayed);
} }
@Override @Override

View file

@ -7,7 +7,6 @@ import androidx.paging.PagedListAdapter;
import androidx.recyclerview.widget.DiffUtil; import androidx.recyclerview.widget.DiffUtil;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.media.MediaClient; import fr.free.nrw.commons.media.MediaClient;
import org.wikipedia.dataclient.WikiSite;
/** /**
* Represents The View Adapter for the List of Contributions * Represents The View Adapter for the List of Contributions
@ -75,7 +74,7 @@ public class ContributionsListAdapter extends
void deleteUpload(Contribution contribution); void deleteUpload(Contribution contribution);
void openMediaDetail(int contribution); void openMediaDetail(int contribution, boolean isWikipediaPageExists);
void addImageToWikipedia(Contribution contribution); void addImageToWikipedia(Contribution contribution);

View file

@ -29,9 +29,8 @@ import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.Utils; import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment; import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
import fr.free.nrw.commons.media.MediaClient;
import fr.free.nrw.commons.utils.DialogUtil; import fr.free.nrw.commons.utils.DialogUtil;
import fr.free.nrw.commons.wikidata.WikidataEditService; import fr.free.nrw.commons.media.MediaClient;
import java.util.Locale; import java.util.Locale;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
@ -244,9 +243,9 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl
} }
@Override @Override
public void openMediaDetail(final int position) { public void openMediaDetail(final int position, boolean isWikipediaButtonDisplayed) {
if (null != callback) {//Just being safe, ideally they won't be called when detached if (null != callback) {//Just being safe, ideally they won't be called when detached
callback.showDetail(position); callback.showDetail(position, isWikipediaButtonDisplayed);
} }
} }
@ -334,8 +333,8 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl
void retryUpload(Contribution contribution); void retryUpload(Contribution contribution);
void pauseUpload(Contribution contribution); void showDetail(int position, boolean isWikipediaButtonDisplayed);
void showDetail(int position); void pauseUpload(Contribution contribution);
} }
} }

View file

@ -2,7 +2,9 @@ package fr.free.nrw.commons.media;
import static android.view.View.GONE; import static android.view.View.GONE;
import static android.view.View.VISIBLE; import static android.view.View.VISIBLE;
import static fr.free.nrw.commons.category.CategoryClientKt.CATEGORY_NEEDING_CATEGORIES;
import static fr.free.nrw.commons.category.CategoryClientKt.CATEGORY_PREFIX; import static fr.free.nrw.commons.category.CategoryClientKt.CATEGORY_PREFIX;
import static fr.free.nrw.commons.category.CategoryClientKt.CATEGORY_UNCATEGORISED;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.AlertDialog; import android.app.AlertDialog;
@ -13,7 +15,9 @@ import android.graphics.drawable.Animatable;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.text.Editable; import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher; import android.text.TextWatcher;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -23,11 +27,15 @@ import android.widget.ArrayAdapter;
import android.widget.Button; import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.ScrollView; import android.widget.ScrollView;
import android.widget.SearchView;
import android.widget.Spinner; import android.widget.Spinner;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import butterknife.BindView; import butterknife.BindView;
import butterknife.ButterKnife; import butterknife.ButterKnife;
import butterknife.OnClick; import butterknife.OnClick;
@ -38,6 +46,8 @@ import com.facebook.drawee.interfaces.DraweeController;
import com.facebook.drawee.view.SimpleDraweeView; import com.facebook.drawee.view.SimpleDraweeView;
import com.facebook.imagepipeline.image.ImageInfo; import com.facebook.imagepipeline.image.ImageInfo;
import com.facebook.imagepipeline.request.ImageRequest; import com.facebook.imagepipeline.request.ImageRequest;
import com.jakewharton.rxbinding2.view.RxView;
import com.jakewharton.rxbinding2.widget.RxSearchView;
import fr.free.nrw.commons.Media; import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.MediaDataExtractor; import fr.free.nrw.commons.MediaDataExtractor;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
@ -45,13 +55,18 @@ import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.auth.AccountUtil; import fr.free.nrw.commons.auth.AccountUtil;
import fr.free.nrw.commons.category.CategoryClient; import fr.free.nrw.commons.category.CategoryClient;
import fr.free.nrw.commons.category.CategoryDetailsActivity; 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.contributions.ContributionsFragment;
import fr.free.nrw.commons.delete.DeleteHelper; import fr.free.nrw.commons.delete.DeleteHelper;
import fr.free.nrw.commons.delete.ReasonBuilder; import fr.free.nrw.commons.delete.ReasonBuilder;
import fr.free.nrw.commons.explore.depictions.WikidataItemDetailsActivity; import fr.free.nrw.commons.explore.depictions.WikidataItemDetailsActivity;
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment; import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
import fr.free.nrw.commons.nearby.Label;
import fr.free.nrw.commons.ui.widget.HtmlTextView; import fr.free.nrw.commons.ui.widget.HtmlTextView;
import fr.free.nrw.commons.utils.ViewUtilWrapper; import fr.free.nrw.commons.utils.ViewUtilWrapper;
import io.reactivex.Observable;
import io.reactivex.Single; import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers; import io.reactivex.schedulers.Schedulers;
@ -59,22 +74,25 @@ import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.concurrent.TimeUnit;
import java.util.Map; import java.util.Map;
import javax.inject.Inject; import javax.inject.Inject;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.wikipedia.util.DateUtil; import org.wikipedia.util.DateUtil;
import timber.log.Timber; import timber.log.Timber;
public class MediaDetailFragment extends CommonsDaggerSupportFragment { public class MediaDetailFragment extends CommonsDaggerSupportFragment implements Callback,
CategoryEditHelper.Callback {
private boolean editable; private boolean editable;
private boolean isCategoryImage; private boolean isCategoryImage;
private MediaDetailPagerFragment.MediaDetailProvider detailProvider; private MediaDetailPagerFragment.MediaDetailProvider detailProvider;
private int index; private int index;
private boolean isDeleted = false; private boolean isDeleted = false;
private boolean isWikipediaButtonDisplayed;
public static MediaDetailFragment forMedia(int index, boolean editable, boolean isCategoryImage) { public static MediaDetailFragment forMedia(int index, boolean editable, boolean isCategoryImage, boolean isWikipediaButtonDisplayed) {
MediaDetailFragment mf = new MediaDetailFragment(); MediaDetailFragment mf = new MediaDetailFragment();
Bundle state = new Bundle(); Bundle state = new Bundle();
@ -83,6 +101,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
state.putInt("index", index); state.putInt("index", index);
state.putInt("listIndex", 0); state.putInt("listIndex", 0);
state.putInt("listTop", 0); state.putInt("listTop", 0);
state.putBoolean("isWikipediaButtonDisplayed", isWikipediaButtonDisplayed);
mf.setArguments(state); mf.setArguments(state);
@ -96,7 +115,11 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
@Inject @Inject
DeleteHelper deleteHelper; DeleteHelper deleteHelper;
@Inject @Inject
CategoryEditHelper categoryEditHelper;
@Inject
ViewUtilWrapper viewUtil; ViewUtilWrapper viewUtil;
@Inject
CategoryClient categoryClient;
private int initialListTop = 0; private int initialListTop = 0;
@ -132,6 +155,8 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
LinearLayout nominatedForDeletion; LinearLayout nominatedForDeletion;
@BindView(R.id.mediaDetailCategoryContainer) @BindView(R.id.mediaDetailCategoryContainer)
LinearLayout categoryContainer; LinearLayout categoryContainer;
@BindView(R.id.categoryEditButton)
Button categoryEditButton;
@BindView(R.id.media_detail_depiction_container) @BindView(R.id.media_detail_depiction_container)
LinearLayout depictionContainer; LinearLayout depictionContainer;
@BindView(R.id.authorLinearLayout) @BindView(R.id.authorLinearLayout)
@ -140,6 +165,29 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
Button delete; Button delete;
@BindView(R.id.mediaDetailScrollView) @BindView(R.id.mediaDetailScrollView)
ScrollView scrollView; ScrollView scrollView;
@BindView(R.id.toDoLayout)
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.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;
private ArrayList<String> categoryNames = new ArrayList<>();
private String categorySearchQuery;
/** /**
* Depicts is a feature part of Structured data. Multiple Depictions can be added for an image just like categories. * Depicts is a feature part of Structured data. Multiple Depictions can be added for an image just like categories.
@ -151,6 +199,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
private int newWidthOfImageView; private int newWidthOfImageView;
private boolean heightVerifyingBoolean = true; // helps in maintaining aspect ratio private boolean heightVerifyingBoolean = true; // helps in maintaining aspect ratio
private ViewTreeObserver.OnGlobalLayoutListener layoutListener; // for layout stuff, only used once! 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 //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; private Media media;
@ -163,6 +212,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
outState.putInt("index", index); outState.putInt("index", index);
outState.putBoolean("editable", editable); outState.putBoolean("editable", editable);
outState.putBoolean("isCategoryImage", isCategoryImage); outState.putBoolean("isCategoryImage", isCategoryImage);
outState.putBoolean("isWikipediaButtonDisplayed", isWikipediaButtonDisplayed);
getScrollPosition(); getScrollPosition();
outState.putInt("listTop", initialListTop); outState.putInt("listTop", initialListTop);
@ -182,11 +232,13 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
if (savedInstanceState != null) { if (savedInstanceState != null) {
editable = savedInstanceState.getBoolean("editable"); editable = savedInstanceState.getBoolean("editable");
isCategoryImage = savedInstanceState.getBoolean("isCategoryImage"); isCategoryImage = savedInstanceState.getBoolean("isCategoryImage");
isWikipediaButtonDisplayed = savedInstanceState.getBoolean("isWikipediaButtonDisplayed");
index = savedInstanceState.getInt("index"); index = savedInstanceState.getInt("index");
initialListTop = savedInstanceState.getInt("listTop"); initialListTop = savedInstanceState.getInt("listTop");
} else { } else {
editable = getArguments().getBoolean("editable"); editable = getArguments().getBoolean("editable");
isCategoryImage = getArguments().getBoolean("isCategoryImage"); isCategoryImage = getArguments().getBoolean("isCategoryImage");
isWikipediaButtonDisplayed = getArguments().getBoolean("isWikipediaButtonDisplayed");
index = getArguments().getInt("index"); index = getArguments().getInt("index");
initialListTop = 0; initialListTop = 0;
} }
@ -232,6 +284,12 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
.getParentFragment())).nearbyNotificationCardView .getParentFragment())).nearbyNotificationCardView
.setVisibility(View.GONE); .setVisibility(View.GONE);
} }
categoryEditSearchRecyclerViewAdapter =
new CategoryEditSearchRecyclerViewAdapter(getContext(), new ArrayList<>(
Label.valuesAsList()), categoryRecyclerView, categoryClient, this);
categoryRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
categoryRecyclerView.setAdapter(categoryEditSearchRecyclerViewAdapter);
media = detailProvider.getMediaAtPosition(index); media = detailProvider.getMediaAtPosition(index);
scrollView.getViewTreeObserver().addOnGlobalLayoutListener( scrollView.getViewTreeObserver().addOnGlobalLayoutListener(
new OnGlobalLayoutListener() { new OnGlobalLayoutListener() {
@ -311,6 +369,8 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(this::onDepictionsLoaded, Timber::e) .subscribe(this::onDepictionsLoaded, Timber::e)
); );
// compositeDisposable.add(disposable);
setupToDo();
} }
private void onDiscussionLoaded(String discussion) { private void onDiscussionLoaded(String discussion) {
@ -385,6 +445,63 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
imageLandscape.setController(controllerLandscape); imageLandscape.setController(controllerLandscape);
} }
/**
* 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;
boolean categoriesPresent = media.getCategories() == null ? false : (media.getCategories().size() == 0 ? false : true);
// Check if the presented category is about need of category
if (categoriesPresent) {
for (String category : media.getCategories()) {
if (category.toLowerCase().contains(CATEGORY_NEEDING_CATEGORIES) ||
category.toLowerCase().contains(CATEGORY_UNCATEGORISED)) {
categoriesPresent = false;
}
break;
}
}
if (!categoriesPresent) {
toDoNeeded = true;
toDoMessage += getString(R.string.missing_category);
}
if (isWikipediaButtonDisplayed) {
toDoNeeded = true;
toDoMessage += (toDoMessage.isEmpty()) ? "" : "\n" + getString(R.string.missing_article);
}
if (toDoNeeded) {
toDoMessage = getString(R.string.todo_improve) + "\n" + toDoMessage;
toDoLayout.setVisibility(VISIBLE);
toDoReason.setText(toDoMessage);
} else {
toDoLayout.setVisibility(GONE);
}
}
@Override @Override
public void onDestroyView() { public void onDestroyView() {
if (layoutListener != null && getView() != null) { if (layoutListener != null && getView() != null) {
@ -409,15 +526,12 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
mediaCaption.setText(prettyCaption(media)); mediaCaption.setText(prettyCaption(media));
} }
final List<String> categories = media.getCategories(); categoryNames.clear();
if (categories.isEmpty()) { categoryNames.addAll(media.getCategories());
// Stick in a filler element. categoryEditSearchRecyclerViewAdapter.addToCategories(media.getCategories());
categories.add(getString(R.string.detail_panel_cats_none)); updateSelectedCategoriesTextView(categoryEditSearchRecyclerViewAdapter.getCategories());
}
rebuildCatList(categories);
updateCategoryList();
if (media.getCreator() == null || media.getCreator().equals("")) { if (media.getCreator() == null || media.getCreator().equals("")) {
authorLayout.setVisibility(GONE); authorLayout.setVisibility(GONE);
@ -426,6 +540,49 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
} }
} }
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());
}
if (allCategories.isEmpty()) {
// Stick in a filler element.
allCategories.add(getString(R.string.detail_panel_cats_none));
}
rebuildCatList(allCategories);
}
@Override
public void updateSelectedCategoriesTextView(List<String> selectedCategories) {
if (selectedCategories == null || selectedCategories.size() == 0) {
updateCategoriesButton.setClickable(false);
}
if (selectedCategories != null) {
existingCategories.setText(StringUtils.join(selectedCategories,", "));
updateCategoriesButton.setClickable(true);
}
}
@Override
public void noResultsFound() {
noResultsFound.setVisibility(VISIBLE);
}
@Override
public void someResultsFound() {
noResultsFound.setVisibility(GONE);
}
/** /**
* Populates media details fragment with depiction list * Populates media details fragment with depiction list
* @param idAndCaptions * @param idAndCaptions
@ -467,6 +624,50 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
Toast.makeText(getContext(), getString(R.string.wikicode_copied), Toast.LENGTH_SHORT).show(); 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();
}
public void displayHideCategorySearch() {
if (dummyCategoryEditContainer.getVisibility() != VISIBLE) {
dummyCategoryEditContainer.setVisibility(VISIBLE);
} else {
dummyCategoryEditContainer.setVisibility(GONE);
}
}
@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();
}));
}
@SuppressLint("StringFormatInvalid")
@OnClick(R.id.nominateDeletion) @OnClick(R.id.nominateDeletion)
public void onDeleteButtonClicked(){ public void onDeleteButtonClicked(){
if (AccountUtil.getUserName(getContext()) != null && AccountUtil.getUserName(getContext()).equals(media.getCreator())) { if (AccountUtil.getUserName(getContext()) != null && AccountUtil.getUserName(getContext()).equals(media.getCreator())) {
@ -537,7 +738,6 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
} }
} }
@SuppressLint("CheckResult") @SuppressLint("CheckResult")
private void onDeleteClicked(Spinner spinner) { private void onDeleteClicked(Spinner spinner) {
String reason = spinner.getSelectedItem().toString(); String reason = spinner.getSelectedItem().toString();
@ -588,6 +788,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
} }
private void rebuildCatList(List<String> categories) { private void rebuildCatList(List<String> categories) {
Log.d("deneme","rebuild cat list size:"+categories.size());
categoryContainer.removeAllViews(); categoryContainer.removeAllViews();
for (String category : categories) { for (String category : categories) {
categoryContainer.addView(buildCatLabel(sanitise(category), categoryContainer)); categoryContainer.addView(buildCatLabel(sanitise(category), categoryContainer));
@ -706,4 +907,13 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
return media.getCoordinates().getPrettyCoordinateString(); return media.getCoordinates().getPrettyCoordinateString();
} }
@Override
public boolean updateCategoryDisplay(List<String> categories) {
if (categories == null) {
return false;
} else {
rebuildCatList(categories);
return true;
}
}
} }

View file

@ -41,6 +41,7 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
@BindView(R.id.mediaDetailsPager) ViewPager pager; @BindView(R.id.mediaDetailsPager) ViewPager pager;
private Boolean editable; private Boolean editable;
private boolean isFeaturedImage; private boolean isFeaturedImage;
private boolean isWikipediaButtonDisplayed;
MediaDetailAdapter adapter; MediaDetailAdapter adapter;
private Bookmark bookmark; private Bookmark bookmark;
private MediaDetailProvider provider; private MediaDetailProvider provider;
@ -249,6 +250,12 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
item.setIcon(icon); item.setIcon(icon);
} }
public void showImage(int i, boolean isWikipediaButtonDisplayed) {
this.isWikipediaButtonDisplayed = isWikipediaButtonDisplayed;
Handler handler = new Handler();
handler.postDelayed(() -> pager.setCurrentItem(i), 5);
}
public void showImage(int i) { public void showImage(int i) {
Handler handler = new Handler(); Handler handler = new Handler();
handler.postDelayed(() -> pager.setCurrentItem(i), 5); handler.postDelayed(() -> pager.setCurrentItem(i), 5);
@ -310,7 +317,7 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
} }
pager.postDelayed(() -> getActivity().invalidateOptionsMenu(), 5); pager.postDelayed(() -> getActivity().invalidateOptionsMenu(), 5);
} }
return MediaDetailFragment.forMedia(i, editable, isFeaturedImage); return MediaDetailFragment.forMedia(i, editable, isFeaturedImage, isWikipediaButtonDisplayed);
} }
@Override @Override

View file

@ -24,6 +24,7 @@ import static androidx.core.app.NotificationCompat.PRIORITY_HIGH;
public class NotificationHelper { public class NotificationHelper {
public static final int NOTIFICATION_DELETE = 1; public static final int NOTIFICATION_DELETE = 1;
public static final int NOTIFICATION_EDIT_CATEGORY = 2;
private NotificationManager notificationManager; private NotificationManager notificationManager;
private NotificationCompat.Builder notificationBuilder; private NotificationCompat.Builder notificationBuilder;

View file

@ -23,12 +23,12 @@ import com.facebook.imagepipeline.image.CloseableImage;
import com.facebook.imagepipeline.request.ImageRequest; import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder; import com.facebook.imagepipeline.request.ImageRequestBuilder;
import fr.free.nrw.commons.media.MediaClient;
import javax.inject.Inject; import javax.inject.Inject;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.contributions.MainActivity; import fr.free.nrw.commons.contributions.MainActivity;
import fr.free.nrw.commons.di.ApplicationlessInjection; import fr.free.nrw.commons.di.ApplicationlessInjection;
import fr.free.nrw.commons.media.MediaClient;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable; import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers; import io.reactivex.schedulers.Schedulers;
@ -43,7 +43,8 @@ public class PicOfDayAppWidget extends AppWidgetProvider {
private CompositeDisposable compositeDisposable = new CompositeDisposable(); private CompositeDisposable compositeDisposable = new CompositeDisposable();
@Inject MediaClient mediaClient; @Inject
MediaClient mediaClient;
void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) { void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.pic_of_day_app_widget); RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.pic_of_day_app_widget);

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
</vector>

View file

@ -8,6 +8,20 @@
android:background="?attr/mainBackground" android:background="?attr/mainBackground"
> >
<LinearLayout
android:id="@+id/dummy_category_edit_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:elevation="35dp"
android:layout_gravity="bottom"
android:visibility="gone"
android:orientation="vertical"
android:weightSum="10">
<include
layout="@layout/layout_edit_categories" />
</LinearLayout>
<ImageView <ImageView
android:id="@+id/mediaDetailImageFailed" android:id="@+id/mediaDetailImageFailed"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -53,9 +67,12 @@
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/dimen_250" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:id="@+id/mediaDetailImageViewSpacer" /> android:id="@+id/mediaDetailImageViewSpacer"
>
</LinearLayout>
</FrameLayout> </FrameLayout>
@ -88,6 +105,30 @@
tools:text="Title of the media" /> tools:text="Title of the media" />
</LinearLayout> </LinearLayout>
<LinearLayout
android:id="@+id/toDoLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone"
android:background="@color/layout_light_grey"
>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/MediaDetailTextLabel"
android:text="@string/warning" />
<TextView
android:id="@+id/toDoReason"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/activity_margin_vertical"
style="@style/MediaDetailTextBody" />
</LinearLayout>
<LinearLayout <LinearLayout
style="@style/MediaDetailContainer" style="@style/MediaDetailContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -255,9 +296,19 @@
android:layout_width="@dimen/widget_margin" android:layout_width="@dimen/widget_margin"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_weight="70" android:layout_weight="70"
android:orientation="vertical" /> android:orientation="vertical">
</LinearLayout> </LinearLayout>
</LinearLayout>
<Button
android:id="@+id/categoryEditButton"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_margin="@dimen/standard_gap"
android:layout_gravity="end"
android:background="@drawable/ic_baseline_edit_24" />
<LinearLayout <LinearLayout
style="@style/MediaDetailContainer" style="@style/MediaDetailContainer"
android:layout_width="match_parent" android:layout_width="match_parent"

View file

@ -0,0 +1,114 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
android:id="@+id/category_edit_layout"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:orientation="vertical"
android:background="@color/white"
android:elevation="30dp">
<TextView
android:id="@+id/tv_subtitle"
android:layout_width="wrap_content"
android:layout_height="@dimen/half_standard_height"
android:layout_margin="@dimen/quarter_standard_height"
android:gravity="center_vertical"
android:text="Type categories"
android:textSize="@dimen/subtitle_text"
android:visibility="visible" />
<FrameLayout
android:id="@+id/category_search_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/standard_gap"
>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/til_container_search"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<SearchView
android:id="@+id/et_search"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/categories_search_text_hint"
android:imeOptions="actionSearch"
android:inputType="text"
android:maxLines="1"/>
</com.google.android.material.textfield.TextInputLayout>
<ProgressBar
android:id="@+id/pb_categories"
style="?android:progressBarStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/tiny_gap"
android:layout_marginRight="@dimen/tiny_gap"
android:layout_gravity="center_vertical|end"
android:indeterminate="true"
android:indeterminateOnly="true"
android:visibility="gone" />
</FrameLayout>
<TextView
android:id="@+id/existing_categories"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/quarter_standard_height"
android:gravity="center_vertical"
android:textSize="@dimen/subtitle_text"
android:visibility="visible" />
<TextView
android:id="@+id/no_results_found"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/quarter_standard_height"
android:gravity="center_vertical"
android:text="No results found"
android:textSize="@dimen/description_text_size"
android:visibility="gone" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:weightSum="6"
>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_categories"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="4"
android:background="@color/white"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="2">
<Button
android:id="@+id/cancel_categories_button"
android:layout_width="wrap_content"
android:layout_margin="@dimen/quarter_standard_height"
android:layout_height="wrap_content"
android:text="@string/cancel"
android:padding="@dimen/small_gap"
android:textColor="@color/white"
android:background="@color/opak_middle_grey"
/>
<Button
android:id="@+id/update_categories_button"
android:layout_width="wrap_content"
android:layout_margin="@dimen/quarter_standard_height"
android:layout_height="wrap_content"
android:text="@string/category_edit_button_text"
android:padding="@dimen/small_gap"
android:textColor="@color/white"
android:background="@color/button_blue"
/>
</LinearLayout>
</LinearLayout>
</LinearLayout>

View file

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/category_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<CheckBox
android:id="@+id/category_checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checkMark="?android:attr/textCheckMark"
android:checked="false"
android:gravity="center_vertical"
android:padding="@dimen/tiny_gap"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/category_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Category"
app:layout_constraintLeft_toRightOf="@+id/category_checkbox"
app:layout_constraintTop_toTopOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -598,6 +598,17 @@ Upload your first media by tapping on the add button.</string>
<string name="delete_helper_ask_reason_copyright_logo">Logo</string> <string name="delete_helper_ask_reason_copyright_logo">Logo</string>
<string name="delete_helper_ask_reason_copyright_other">Other</string> <string name="delete_helper_ask_reason_copyright_other">Other</string>
<string name="delete_helper_ask_alert_set_positive_button_reason">Because it is</string> <string name="delete_helper_ask_alert_set_positive_button_reason">Because it is</string>
<string name="category_edit_helper_make_edit_toast">Trying to update categories.</string>
<string name="category_edit_helper_show_edit_title">Category update</string>
<string name="category_edit_helper_show_edit_title_success">Success</string>
<plurals name="category_edit_helper_show_edit_message_if">
<item quantity="one">Category %1$s is added.</item>
<item quantity="other">Categories %1$s are added.</item>
</plurals>
<string name="category_edit_helper_edit_message_else">Could not add categories.</string>
<string name="category_edit_button_text">Update categories</string>
<string name="share_image_via">Share image via</string> <string name="share_image_via">Share image via</string>
<string name="no_achievements_yet">You haven\'t made any contributions yet</string> <string name="no_achievements_yet">You haven\'t made any contributions yet</string>
<string name="account_created">Account created!</string> <string name="account_created">Account created!</string>
@ -636,6 +647,10 @@ Upload your first media by tapping on the add button.</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> <string name="nearby_no_results">No places found, try changing your search criteria.</string>
<string name="todo_improve">Suggested improvements:</string>
<string name="missing_category">- Add categories to this image to improve usability.</string>
<string name="missing_article">- Add this image to the associated Wikipedia article that has no images.</string>
<string name="edit_category">Edit categories</string>
<string name="add_picture_to_wikipedia_article_title">Add image to Wikipedia</string> <string name="add_picture_to_wikipedia_article_title">Add image to Wikipedia</string>
<string name="add_picture_to_wikipedia_article_desc">Do you want to add this picture to the %1$s language Wikipedia article?</string> <string name="add_picture_to_wikipedia_article_desc">Do you want to add this picture to the %1$s language Wikipedia article?</string>

View file

@ -2,13 +2,16 @@ package fr.free.nrw.commons.actions
import com.nhaarman.mockitokotlin2.eq import com.nhaarman.mockitokotlin2.eq
import com.nhaarman.mockitokotlin2.verify import com.nhaarman.mockitokotlin2.verify
import io.reactivex.Observable
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.Mockito import org.mockito.Mockito
import org.mockito.MockitoAnnotations import org.mockito.MockitoAnnotations
import org.wikipedia.csrf.CsrfTokenClient import org.wikipedia.csrf.CsrfTokenClient
import org.wikipedia.dataclient.Service import org.wikipedia.dataclient.Service
import org.wikipedia.edit.Edit
class PageEditClientTest { class PageEditClientTest {
@Mock @Mock
@ -20,6 +23,12 @@ class PageEditClientTest {
private lateinit var pageEditClient: PageEditClient private lateinit var pageEditClient: PageEditClient
@Mock
lateinit var edit: Edit
@Mock
lateinit var editResult: Edit.Result
/** /**
* initial setup, test environment * initial setup, test environment
*/ */
@ -46,8 +55,23 @@ class PageEditClientTest {
@Test @Test
fun testAppendEdit() { fun testAppendEdit() {
Mockito.`when`(csrfTokenClient.tokenBlocking).thenReturn("test") Mockito.`when`(csrfTokenClient.tokenBlocking).thenReturn("test")
pageEditClient.appendEdit("test", "test", "test") Mockito.`when`(
pageEditInterface.postAppendEdit(
ArgumentMatchers.anyString(),
ArgumentMatchers.anyString(),
ArgumentMatchers.anyString(),
ArgumentMatchers.anyString()
)
).thenReturn(
Observable.just(edit)
)
Mockito.`when`(edit.edit()).thenReturn(editResult)
Mockito.`when`(editResult.editSucceeded()).thenReturn(true)
pageEditClient.appendEdit("test", "test", "test").test()
verify(csrfTokenClient).tokenBlocking
verify(pageEditInterface).postAppendEdit(eq("test"), eq("test"), eq("test"), eq("test")) verify(pageEditInterface).postAppendEdit(eq("test"), eq("test"), eq("test"), eq("test"))
verify(edit).edit()
verify(editResult).editSucceeded()
} }
/** /**