Fixed 4616 : Option for editing depictions (#4725)

* Dialog can't be dismissed

* Dialog can't be dismissed

* Option for editing depiction

* Java docs added

* Minor issues fixed

* Lining done

* "Depictions not updating instantly" issue resolved

* Existing Depicts on the top

* Existing Depicts on the top

* Back press handled

* Previous depictions unchecked

* Whole Screen issue fixed

* Nearby banner removed

* Test fixed

* Upload Wizard issue fixed

* Upload Wizard issue fixed

* Previous depicts issue fixed

* Previous depicts issue fixed

* All issues fixed

* Fixed late loading of updated depicts

* Depiction is removable

* Test fixed

* Back button press handled after losing focus for edittext

* RequiresApi removed

* RequiresApi removed

* Test fixed

* Requested changes

* Test added

* Test added

* UploadModelUnitTest added

* DepictEditHelperUnitTest added

* DepictEditHelperUnitTest added

* Test added

* More test added

* Indentation Reversed

* Indentation reversed

* Update MediaDetailFragment.java

* Indentation reversed

* Update MediaDetailFragment.java

* Indentation reversed

* Indentation reversed

* Indentation reversed

* Indentation reversed

* More test added

* More test added

* Minor fixes

* Minor fixes

* Minor fixes
This commit is contained in:
Ayan Sarkar 2022-03-22 11:03:43 +05:30 committed by GitHub
parent e58322ed63
commit bd9531b969
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 1261 additions and 75 deletions

View file

@ -125,6 +125,16 @@ public class MainActivity extends BaseActivity
toolbar.setNavigationOnClickListener(view -> { toolbar.setNavigationOnClickListener(view -> {
onSupportNavigateUp(); onSupportNavigateUp();
}); });
/*
"first_edit_depict" is a key for getting information about opening the depiction editor
screen for the first time after opening the app.
Getting true by the key means the depiction editor screen is opened for the first time
after opening the app.
Getting false by the key means the depiction editor screen is not opened for the first time
after opening the app.
*/
applicationKvStore.putBoolean("first_edit_depict", true);
if (applicationKvStore.getBoolean("login_skipped") == true) { if (applicationKvStore.getBoolean("login_skipped") == true) {
setTitle(getString(R.string.navigation_item_explore)); setTitle(getString(R.string.navigation_item_explore));
setUpLoggedOutPager(); setUpLoggedOutPager();

View file

@ -10,10 +10,8 @@ import static fr.free.nrw.commons.description.EditDescriptionConstants.LIST_OF_D
import static fr.free.nrw.commons.description.EditDescriptionConstants.UPDATED_WIKITEXT; import static fr.free.nrw.commons.description.EditDescriptionConstants.UPDATED_WIKITEXT;
import static fr.free.nrw.commons.description.EditDescriptionConstants.WIKITEXT; import static fr.free.nrw.commons.description.EditDescriptionConstants.WIKITEXT;
import static fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.LAST_LOCATION; import static fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.LAST_LOCATION;
import android.content.res.Resources;
import static fr.free.nrw.commons.utils.LangCodeUtils.getLocalizedResources; import static fr.free.nrw.commons.utils.LangCodeUtils.getLocalizedResources;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import java.lang.reflect.Field;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@ -45,6 +43,8 @@ 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.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import butterknife.BindView; import butterknife.BindView;
@ -86,6 +86,7 @@ import fr.free.nrw.commons.location.LocationServiceManager;
import fr.free.nrw.commons.nearby.Label; import fr.free.nrw.commons.nearby.Label;
import fr.free.nrw.commons.profile.ProfileActivity; import fr.free.nrw.commons.profile.ProfileActivity;
import fr.free.nrw.commons.ui.widget.HtmlTextView; import fr.free.nrw.commons.ui.widget.HtmlTextView;
import fr.free.nrw.commons.upload.depicts.DepictsFragment;
import fr.free.nrw.commons.upload.UploadMediaDetail; import fr.free.nrw.commons.upload.UploadMediaDetail;
import fr.free.nrw.commons.utils.ViewUtilWrapper; import fr.free.nrw.commons.utils.ViewUtilWrapper;
import io.reactivex.Single; import io.reactivex.Single;
@ -93,7 +94,6 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers; import io.reactivex.schedulers.Schedulers;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@ -176,6 +176,8 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
LinearLayout captionLayout; LinearLayout captionLayout;
@BindView(R.id.depicts_layout) @BindView(R.id.depicts_layout)
LinearLayout depictsLayout; LinearLayout depictsLayout;
@BindView(R.id.depictionsEditButton)
Button depictEditButton;
@BindView(R.id.media_detail_caption) @BindView(R.id.media_detail_caption)
TextView mediaCaption; TextView mediaCaption;
@BindView(R.id.mediaDetailDesc) @BindView(R.id.mediaDetailDesc)
@ -239,7 +241,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
@BindView(R.id.description_label) @BindView(R.id.description_label)
TextView descriptionLabel; TextView descriptionLabel;
@BindView(R.id.pb_circular) @BindView(R.id.pb_circular)
ProgressBar progressBar; ProgressBar progressBar;
String descriptionHtmlCode; String descriptionHtmlCode;
@BindView(R.id.progressBarDeletion) @BindView(R.id.progressBarDeletion)
ProgressBar progressBarDeletion; ProgressBar progressBarDeletion;
@ -467,10 +469,10 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
private void displayMediaDetails() { private void displayMediaDetails() {
setTextFields(media); setTextFields(media);
compositeDisposable.addAll( compositeDisposable.addAll(
mediaDataExtractor.fetchDepictionIdsAndLabels(media) mediaDataExtractor.refresh(media)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(this::onDepictionsLoaded, Timber::e), .subscribe(this::onMediaRefreshed, Timber::e),
mediaDataExtractor.checkDeletionRequestExists(media) mediaDataExtractor.checkDeletionRequestExists(media)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
@ -478,15 +480,12 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
mediaDataExtractor.fetchDiscussion(media) mediaDataExtractor.fetchDiscussion(media)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(this::onDiscussionLoaded, Timber::e), .subscribe(this::onDiscussionLoaded, Timber::e)
mediaDataExtractor.refresh(media)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::onMediaRefreshed, Timber::e)
); );
} }
private void onMediaRefreshed(Media media) { private void onMediaRefreshed(Media media) {
this.media = media;
setTextFields(media); setTextFields(media);
compositeDisposable.addAll( compositeDisposable.addAll(
mediaDataExtractor.fetchDepictionIdsAndLabels(media) mediaDataExtractor.fetchDepictionIdsAndLabels(media)
@ -517,8 +516,26 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
} }
private void onDepictionsLoaded(List<IdAndCaptions> idAndCaptions){ private void onDepictionsLoaded(List<IdAndCaptions> idAndCaptions){
depictsLayout.setVisibility(idAndCaptions.isEmpty() ? GONE : VISIBLE); depictsLayout.setVisibility(idAndCaptions.isEmpty() ? GONE : VISIBLE);
buildDepictionList(idAndCaptions); depictEditButton.setVisibility(idAndCaptions.isEmpty() ? GONE : VISIBLE);
buildDepictionList(idAndCaptions);
}
/**
* By clicking on the edit depictions button, it will send user to depict fragment
*/
@OnClick(R.id.depictionsEditButton)
public void onDepictionsEditButtonClicked() {
depictionContainer.removeAllViews();
depictEditButton.setVisibility(GONE);
final Fragment depictsFragment = new DepictsFragment();
final Bundle bundle = new Bundle();
bundle.putParcelable("Existing_Depicts", media);
depictsFragment.setArguments(bundle);
final FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
transaction.replace(R.id.mediaDetailFrameLayout, depictsFragment);
transaction.addToBackStack(null);
transaction.commit();
} }
/** /**
* The imageSpacer is Basically a transparent overlay for the SimpleDraweeView * The imageSpacer is Basically a transparent overlay for the SimpleDraweeView

View file

@ -27,6 +27,7 @@ public class NotificationHelper {
public static final int NOTIFICATION_EDIT_CATEGORY = 2; public static final int NOTIFICATION_EDIT_CATEGORY = 2;
public static final int NOTIFICATION_EDIT_COORDINATES = 3; public static final int NOTIFICATION_EDIT_COORDINATES = 3;
public static final int NOTIFICATION_EDIT_DESCRIPTION = 4; public static final int NOTIFICATION_EDIT_DESCRIPTION = 4;
public static final int NOTIFICATION_EDIT_DEPICTIONS = 5;
private NotificationManager notificationManager; private NotificationManager notificationManager;
private NotificationCompat.Builder notificationBuilder; private NotificationCompat.Builder notificationBuilder;

View file

@ -1,6 +1,7 @@
package fr.free.nrw.commons.repository; package fr.free.nrw.commons.repository;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.category.CategoriesModel; import fr.free.nrw.commons.category.CategoriesModel;
import fr.free.nrw.commons.category.CategoryItem; import fr.free.nrw.commons.category.CategoryItem;
import fr.free.nrw.commons.contributions.Contribution; import fr.free.nrw.commons.contributions.Contribution;
@ -233,8 +234,8 @@ public class UploadRepository {
uploadModel.setSelectedLicense(licenseName); uploadModel.setSelectedLicense(licenseName);
} }
public void onDepictItemClicked(DepictedItem depictedItem) { public void onDepictItemClicked(DepictedItem depictedItem, final Media media) {
uploadModel.onDepictItemClicked(depictedItem); uploadModel.onDepictItemClicked(depictedItem, media);
} }
/** /**
@ -247,6 +248,23 @@ public class UploadRepository {
return uploadModel.getSelectedDepictions(); return uploadModel.getSelectedDepictions();
} }
/**
* Provides selected existing depicts
*
* @return selected existing depicts
*/
public List<String> getSelectedExistingDepictions() {
return uploadModel.getSelectedExistingDepictions();
}
/**
* Initialize existing depicts
*
* @param selectedExistingDepictions existing depicts
*/
public void setSelectedExistingDepictions(final List<String> selectedExistingDepictions) {
uploadModel.setSelectedExistingDepictions(selectedExistingDepictions);
}
/** /**
* Search all depictions from * Search all depictions from
* *
@ -275,6 +293,39 @@ public class UploadRepository {
return depictModel.getPlaceDepictions(new ArrayList<>(qids)); return depictModel.getPlaceDepictions(new ArrayList<>(qids));
} }
/**
* Takes depict IDs as a parameter, converts into a slash separated String and Gets DepictItem
* from the server
*
* @param depictionsQIDs IDs of Depiction
* @return Flowable<List<DepictedItem>>
*/
public Flowable<List<DepictedItem>> getDepictions(final List<String> depictionsQIDs){
final String ids = joinQIDs(depictionsQIDs);
return depictModel.getDepictions(ids).toFlowable();
}
/**
* Builds a string by joining all IDs divided by "|"
*
* @param depictionsQIDs IDs of depiction ex. ["Q11023","Q1356"]
* @return string ex. "Q11023|Q1356"
*/
private String joinQIDs(final List<String> depictionsQIDs) {
if (depictionsQIDs != null && !depictionsQIDs.isEmpty()) {
final StringBuilder buffer = new StringBuilder(depictionsQIDs.get(0));
if (depictionsQIDs.size() > 1) {
for (int i = 1; i < depictionsQIDs.size(); i++) {
buffer.append("|");
buffer.append(depictionsQIDs.get(i));
}
}
return buffer.toString();
}
return null;
}
/** /**
* Returns nearest place matching the passed latitude and longitude * Returns nearest place matching the passed latitude and longitude
* @param decLatitude * @param decLatitude

View file

@ -2,6 +2,7 @@ package fr.free.nrw.commons.upload;
import android.content.Context; import android.content.Context;
import android.net.Uri; import android.net.Uri;
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.auth.SessionManager; import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.contributions.Contribution; import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.filepicker.UploadableFile; import fr.free.nrw.commons.filepicker.UploadableFile;
@ -40,6 +41,10 @@ public class UploadModel {
private final ImageProcessingService imageProcessingService; private final ImageProcessingService imageProcessingService;
private final List<String> selectedCategories = new ArrayList<>(); private final List<String> selectedCategories = new ArrayList<>();
private final List<DepictedItem> selectedDepictions = new ArrayList<>(); private final List<DepictedItem> selectedDepictions = new ArrayList<>();
/**
* Existing depicts which are selected
*/
private List<String> selectedExistingDepictions = new ArrayList<>();
@Inject @Inject
UploadModel(@Named("licenses") final List<String> licenses, UploadModel(@Named("licenses") final List<String> licenses,
@ -68,6 +73,7 @@ public class UploadModel {
items.clear(); items.clear();
selectedCategories.clear(); selectedCategories.clear();
selectedDepictions.clear(); selectedDepictions.clear();
selectedExistingDepictions.clear();
} }
public void setSelectedCategories(List<String> selectedCategories) { public void setSelectedCategories(List<String> selectedCategories) {
@ -185,11 +191,33 @@ public class UploadModel {
return items; return items;
} }
public void onDepictItemClicked(DepictedItem depictedItem) { public void onDepictItemClicked(DepictedItem depictedItem, Media media) {
if (depictedItem.isSelected()) { if (media == null) {
selectedDepictions.add(depictedItem); if (depictedItem.isSelected()) {
selectedDepictions.add(depictedItem);
} else {
selectedDepictions.remove(depictedItem);
}
} else { } else {
selectedDepictions.remove(depictedItem); if (depictedItem.isSelected()) {
if (media.getDepictionIds().contains(depictedItem.getId())) {
selectedExistingDepictions.add(depictedItem.getId());
} else {
selectedDepictions.add(depictedItem);
}
} else {
if (media.getDepictionIds().contains(depictedItem.getId())) {
selectedExistingDepictions.remove(depictedItem.getId());
if (!media.getDepictionIds().contains(depictedItem.getId())) {
final List<String> depictsList = new ArrayList<>();
depictsList.add(depictedItem.getId());
depictsList.addAll(media.getDepictionIds());
media.setDepictionIds(depictsList);
}
} else {
selectedDepictions.remove(depictedItem);
}
}
} }
} }
@ -207,4 +235,21 @@ public class UploadModel {
return selectedDepictions; return selectedDepictions;
} }
/**
* Provides selected existing depicts
*
* @return selected existing depicts
*/
public List<String> getSelectedExistingDepictions() {
return selectedExistingDepictions;
}
/**
* Initialize existing depicts
*
* @param selectedExistingDepictions existing depicts
*/
public void setSelectedExistingDepictions(final List<String> selectedExistingDepictions) {
this.selectedExistingDepictions = selectedExistingDepictions;
}
} }

View file

@ -26,6 +26,21 @@ public interface WikiBaseInterface {
@NonNull @Field("token") String editToken, @NonNull @Field("token") String editToken,
@NonNull @Field("data") String data); @NonNull @Field("data") String data);
/**
* Uploads depicts for a file in the server
*
* @param filename name of the file
* @param editToken editToken for the file
* @param data data of the depicts to be uploaded
* @return Observable<MwPostResponse>
*/
@Headers("Cache-Control: no-cache")
@FormUrlEncoded
@POST(MW_API_PREFIX + "action=wbeditentity&site=commonswiki&clear=1")
Observable<MwPostResponse> postEditEntityByFilename(@NonNull @Field("title") String filename,
@NonNull @Field("token") String editToken,
@NonNull @Field("data") String data);
@GET(MW_API_PREFIX + "action=query&prop=info") @GET(MW_API_PREFIX + "action=query&prop=info")
Observable<MwQueryResponse> getFileEntityId(@Query("titles") String fileName); Observable<MwQueryResponse> getFileEntityId(@Query("titles") String fileName);

View file

@ -0,0 +1,121 @@
package fr.free.nrw.commons.upload.depicts
import android.content.Context
import android.content.Intent
import android.net.Uri
import fr.free.nrw.commons.BuildConfig
import fr.free.nrw.commons.Media
import fr.free.nrw.commons.R
import fr.free.nrw.commons.notification.NotificationHelper
import fr.free.nrw.commons.utils.ViewUtilWrapper
import fr.free.nrw.commons.wikidata.WikidataEditService
import io.reactivex.Observable
import timber.log.Timber
import javax.inject.Inject
class DepictEditHelper @Inject constructor (notificationHelper: NotificationHelper,
wikidataEditService: WikidataEditService,
viewUtilWrapper: ViewUtilWrapper) {
/**
* Class for making post operations
*/
@Inject
lateinit var wikidataEditService: WikidataEditService
/**
* Class for creating notification
*/
@Inject
lateinit var notificationHelper: NotificationHelper
/**
* Class for showing toast
*/
@Inject
lateinit var viewUtilWrapper: ViewUtilWrapper
/**
* Public interface to edit depictions
*
* @param context context
* @param media media
* @param depictions selected depictions to be added ex: ["Q12", "Q234"]
* @return Single<Boolean>
*/
fun makeDepictionEdit(
context: Context,
media: Media,
depictions: List<String>
): Observable<Boolean> {
viewUtilWrapper.showShortToast(
context,
context.getString(R.string.depictions_edit_helper_make_edit_toast)
)
return addDepiction(media, depictions)
.flatMap { result: Boolean ->
Observable.just(
showDepictionEditNotification(context, media, result)
)
}
}
/**
* Appends new depictions
*
* @param media media
* @param depictions to be added
* @return Observable<Boolean>
*/
private fun addDepiction(media: Media, depictions: List<String>): Observable<Boolean> {
Timber.d("thread is adding depiction %s", Thread.currentThread().name)
return wikidataEditService.updateDepictsProperty(media.filename, depictions)
}
/**
* Helps to create notification about condition of editing depictions
*
* @param context context
* @param media media
* @param result response of result
* @return Single<Boolean>
*/
private fun showDepictionEditNotification(
context: Context,
media: Media,
result: Boolean
): Boolean {
val message: String
var title = context.getString(R.string.depictions_edit_helper_show_edit_title)
if (result) {
title += ": " + context.getString(R.string.category_edit_helper_show_edit_title_success)
val depictsInMessage = StringBuilder()
val depictIdList = media.depictionIds
for (depiction in depictIdList) {
depictsInMessage.append(depiction)
if (depiction == depictIdList[depictIdList.size - 1]) {
continue
}
depictsInMessage.append(",")
}
message = context.resources.getQuantityString(
R.plurals.depictions_edit_helper_show_edit_message_if,
depictIdList.size,
depictsInMessage.toString()
)
} else {
title += ": " + context.getString(R.string.depictions_edit_helper_show_edit_title)
message = context.getString(R.string.depictions_edit_helper_edit_message_else)
}
val urlForFile = BuildConfig.COMMONS_URL + "/wiki/" + media.filename
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(urlForFile))
notificationHelper.showNotification(
context,
title,
message,
NotificationHelper.NOTIFICATION_EDIT_DEPICTIONS,
browserIntent
)
return result
}
}

View file

@ -1,7 +1,10 @@
package fr.free.nrw.commons.upload.depicts; package fr.free.nrw.commons.upload.depicts;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData; 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.upload.structure.depictions.DepictedItem; import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
import java.util.List; import java.util.List;
@ -40,6 +43,36 @@ public interface DepictsContract {
* add depictions to list * add depictions to list
*/ */
void setDepictsList(List<DepictedItem> depictedItemList); void setDepictsList(List<DepictedItem> depictedItemList);
/**
* Returns required context
*/
Context getFragmentContext();
/**
* Returns to previous fragment
*/
void goBackToPreviousScreen();
/**
* Gets existing depictions IDs from media
*/
List<String> getExistingDepictions();
/**
* Shows the progress dialog
*/
void showProgressDialog();
/**
* Hides the progress dialog
*/
void dismissProgressDialog();
/**
* Update the depictions
*/
void updateDepicts();
} }
interface UserActionListener extends BasePresenter<View> { interface UserActionListener extends BasePresenter<View> {
@ -71,6 +104,21 @@ public interface DepictsContract {
*/ */
void verifyDepictions(); void verifyDepictions();
/**
* Clears previous selections
*/
void clearPreviousSelection();
LiveData<List<DepictedItem>> getDepictedItems(); LiveData<List<DepictedItem>> getDepictedItems();
/**
* Update the depictions
*/
void updateDepictions(Media media);
/**
* Attaches view and media
*/
void onAttachViewWithMedia(@NonNull View view, Media media);
} }
} }

View file

@ -1,15 +1,21 @@
package fr.free.nrw.commons.upload.depicts; package fr.free.nrw.commons.upload.depicts;
import android.app.Activity; import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.view.KeyEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import butterknife.BindView; import butterknife.BindView;
@ -18,7 +24,11 @@ import butterknife.OnClick;
import com.google.android.material.textfield.TextInputLayout; import com.google.android.material.textfield.TextInputLayout;
import com.jakewharton.rxbinding2.view.RxView; import com.jakewharton.rxbinding2.view.RxView;
import com.jakewharton.rxbinding2.widget.RxTextView; import com.jakewharton.rxbinding2.widget.RxTextView;
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.contributions.ContributionsFragment;
import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.media.MediaDetailFragment;
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;
@ -27,8 +37,10 @@ import fr.free.nrw.commons.utils.DialogUtil;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable; import io.reactivex.disposables.Disposable;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named;
import kotlin.Unit; import kotlin.Unit;
import timber.log.Timber; import timber.log.Timber;
@ -52,11 +64,26 @@ public class DepictsFragment extends UploadBaseFragment implements DepictsContra
RecyclerView depictsRecyclerView; RecyclerView depictsRecyclerView;
@BindView(R.id.tooltip) @BindView(R.id.tooltip)
ImageView tooltip; ImageView tooltip;
@BindView(R.id.depicts_next)
Button btnNext;
@BindView(R.id.depicts_previous)
Button btnPrevious;
@Inject
@Named("default_preferences")
public
JsonKvStore applicationKvStore;
@Inject @Inject
DepictsContract.UserActionListener presenter; DepictsContract.UserActionListener presenter;
private UploadDepictsAdapter adapter; private UploadDepictsAdapter adapter;
private Disposable subscribe; private Disposable subscribe;
private Media media;
private ProgressDialog progressDialog;
/**
* Determines each encounter of edit depicts
*/
private int count;
@Nullable @Nullable
@Override @Override
public android.view.View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, public android.view.View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@ -67,7 +94,13 @@ public class DepictsFragment extends UploadBaseFragment implements DepictsContra
@Override @Override
public void onViewCreated(@NonNull android.view.View view, @Nullable Bundle savedInstanceState) { public void onViewCreated(@NonNull android.view.View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
ButterKnife.bind(this, view); ButterKnife.bind(this, view);
Bundle bundle = getArguments();
if (bundle != null) {
media = bundle.getParcelable("Existing_Depicts");
}
init(); init();
presenter.getDepictedItems().observe(getViewLifecycleOwner(), this::setDepictsList); presenter.getDepictedItems().observe(getViewLifecycleOwner(), this::setDepictsList);
} }
@ -76,13 +109,27 @@ public class DepictsFragment extends UploadBaseFragment implements DepictsContra
* Initialize presenter and views * Initialize presenter and views
*/ */
private void init() { private void init() {
depictsTitle.setText(getString(R.string.step_count, callback.getIndexInViewFlipper(this) + 1,
callback.getTotalNumberOfSteps(), getString(R.string.depicts_step_title))); if (media == null) {
depictsTitle
.setText(getString(R.string.step_count, callback.getIndexInViewFlipper(this) + 1,
callback.getTotalNumberOfSteps(), getString(R.string.depicts_step_title)));
} else {
depictsTitle.setText(R.string.edit_depictions);
depictsSubTitle.setVisibility(View.GONE);
btnNext.setText(R.string.menu_save_categories);
btnPrevious.setText(R.string.menu_cancel_upload);
}
setDepictsSubTitle(); setDepictsSubTitle();
tooltip.setOnClickListener(v -> DialogUtil tooltip.setOnClickListener(v -> DialogUtil
.showAlertDialog(getActivity(), getString(R.string.depicts_step_title), .showAlertDialog(getActivity(), getString(R.string.depicts_step_title),
getString(R.string.depicts_tooltip), getString(android.R.string.ok), null, true)); getString(R.string.depicts_tooltip), getString(android.R.string.ok), null, true));
presenter.onAttachView(this); if (media == null) {
presenter.onAttachView(this);
} else {
presenter.onAttachViewWithMedia(this, media);
}
initRecyclerView(); initRecyclerView();
addTextChangeListenerToSearchBox(); addTextChangeListenerToSearchBox();
} }
@ -105,10 +152,17 @@ public class DepictsFragment extends UploadBaseFragment implements DepictsContra
* Initialise recyclerView and set adapter * Initialise recyclerView and set adapter
*/ */
private void initRecyclerView() { private void initRecyclerView() {
adapter = new UploadDepictsAdapter(item -> { if (media == null) {
presenter.onDepictItemClicked(item); adapter = new UploadDepictsAdapter(categoryItem -> {
return Unit.INSTANCE; presenter.onDepictItemClicked(categoryItem);
}); return Unit.INSTANCE;
});
} else {
adapter = new UploadDepictsAdapter(item -> {
presenter.onDepictItemClicked(item);
return Unit.INSTANCE;
});
}
depictsRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); depictsRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
depictsRecyclerView.setAdapter(adapter); depictsRecyclerView.setAdapter(adapter);
} }
@ -133,19 +187,28 @@ public class DepictsFragment extends UploadBaseFragment implements DepictsContra
@Override @Override
public void noDepictionSelected() { public void noDepictionSelected() {
DialogUtil.showAlertDialog(getActivity(), if (media == null) {
getString(R.string.no_depictions_selected), DialogUtil.showAlertDialog(getActivity(),
getString(R.string.no_depictions_selected_warning_desc), getString(R.string.no_depictions_selected),
getString(R.string.continue_message), getString(R.string.no_depictions_selected_warning_desc),
getString(R.string.cancel), getString(R.string.continue_message),
this::goToNextScreen, getString(R.string.cancel),
null this::goToNextScreen,
); null
);
} else {
Toast.makeText(requireContext(), getString(R.string.no_depictions_selected),
Toast.LENGTH_SHORT).show();
presenter.clearPreviousSelection();
updateDepicts();
goBackToPreviousScreen();
}
} }
@Override @Override
public void onDestroyView() { public void onDestroyView() {
super.onDestroyView(); super.onDestroyView();
media = null;
presenter.onDetachView(); presenter.onDetachView();
subscribe.dispose(); subscribe.dispose();
} }
@ -166,18 +229,98 @@ public class DepictsFragment extends UploadBaseFragment implements DepictsContra
@Override @Override
public void setDepictsList(List<DepictedItem> depictedItemList) { public void setDepictsList(List<DepictedItem> depictedItemList) {
adapter.setItems(depictedItemList);
if (applicationKvStore.getBoolean("first_edit_depict")) {
count = 1;
applicationKvStore.putBoolean("first_edit_depict", false);
adapter.setItems(depictedItemList);
} else {
if ((count == 0) && (!depictedItemList.isEmpty())) {
adapter.setItems(null);
count = 1;
} else {
adapter.setItems(depictedItemList);
}
}
depictsRecyclerView.smoothScrollToPosition(0); depictsRecyclerView.smoothScrollToPosition(0);
} }
@OnClick(R.id.depicts_next) /**
public void onNextButtonClicked() { * Returns required context
presenter.verifyDepictions(); */
@Override
public Context getFragmentContext(){
return requireContext();
} }
/**
* Returns to previous fragment
*/
@Override
public void goBackToPreviousScreen() {
getFragmentManager().popBackStack();
}
/**
* Gets existing depictions IDs from media
*/
@Override
public List<String> getExistingDepictions(){
return (media == null) ? null : media.getDepictionIds();
}
/**
* Shows the progress dialog
*/
@Override
public void showProgressDialog() {
progressDialog = new ProgressDialog(requireContext());
progressDialog.setMessage(getString(R.string.please_wait));
progressDialog.show();
}
/**
* Hides the progress dialog
*/
@Override
public void dismissProgressDialog() {
progressDialog.dismiss();
}
/**
* Update the depicts
*/
@Override
public void updateDepicts() {
final MediaDetailFragment mediaDetailFragment = (MediaDetailFragment) getParentFragment();
assert mediaDetailFragment != null;
mediaDetailFragment.onResume();
}
/**
* Determines the calling fragment by media nullability and act accordingly
*/
@OnClick(R.id.depicts_next)
public void onNextButtonClicked() {
if(media != null){
presenter.updateDepictions(media);
} else {
presenter.verifyDepictions();
}
}
/**
* Determines the calling fragment by media nullability and act accordingly
*/
@OnClick(R.id.depicts_previous) @OnClick(R.id.depicts_previous)
public void onPreviousButtonClicked() { public void onPreviousButtonClicked() {
callback.onPreviousButtonClicked(callback.getIndexInViewFlipper(this)); if(media != null){
presenter.clearPreviousSelection();
updateDepicts();
goBackToPreviousScreen();
} else {
callback.onPreviousButtonClicked(callback.getIndexInViewFlipper(this));
}
} }
/** /**
@ -200,4 +343,63 @@ public class DepictsFragment extends UploadBaseFragment implements DepictsContra
private void searchForDepictions(final String query) { private void searchForDepictions(final String query) {
presenter.searchForDepictions(query); presenter.searchForDepictions(query);
} }
/**
* Hides the action bar while opening editing fragment
*/
@Override
public void onResume() {
super.onResume();
if (media != null) {
depictsSearch.setOnKeyListener((v, keyCode, event) -> {
if (keyCode == KeyEvent.KEYCODE_BACK) {
depictsSearch.clearFocus();
presenter.clearPreviousSelection();
updateDepicts();
goBackToPreviousScreen();
return true;
}
return false;
});
Objects.requireNonNull(getView()).setFocusableInTouchMode(true);
getView().requestFocus();
getView().setOnKeyListener((v, keyCode, event) -> {
if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
presenter.clearPreviousSelection();
updateDepicts();
goBackToPreviousScreen();
return true;
}
return false;
});
Objects.requireNonNull(
((AppCompatActivity) Objects.requireNonNull(getActivity())).getSupportActionBar())
.hide();
if (getParentFragment().getParentFragment().getParentFragment()
instanceof ContributionsFragment) {
((ContributionsFragment) (getParentFragment()
.getParentFragment().getParentFragment())).nearbyNotificationCardView
.setVisibility(View.GONE);
}
}
}
/**
* Shows the action bar while closing editing fragment
*/
@Override
public void onStop() {
super.onStop();
if (media != null) {
Objects.requireNonNull(
((AppCompatActivity) Objects.requireNonNull(getActivity())).getSupportActionBar())
.show();
}
}
} }

View file

@ -1,15 +1,19 @@
package fr.free.nrw.commons.upload.depicts package fr.free.nrw.commons.upload.depicts
import android.annotation.SuppressLint
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import fr.free.nrw.commons.Media
import fr.free.nrw.commons.di.CommonsApplicationModule import fr.free.nrw.commons.di.CommonsApplicationModule
import fr.free.nrw.commons.repository.UploadRepository import fr.free.nrw.commons.repository.UploadRepository
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
import fr.free.nrw.commons.wikidata.WikidataDisambiguationItems import fr.free.nrw.commons.wikidata.WikidataDisambiguationItems
import io.reactivex.Flowable import io.reactivex.Flowable
import io.reactivex.Scheduler import io.reactivex.Scheduler
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.CompositeDisposable
import io.reactivex.processors.PublishProcessor import io.reactivex.processors.PublishProcessor
import io.reactivex.schedulers.Schedulers
import timber.log.Timber import timber.log.Timber
import java.lang.reflect.Proxy import java.lang.reflect.Proxy
import java.util.* import java.util.*
@ -35,8 +39,11 @@ class DepictsPresenter @Inject constructor(
private val compositeDisposable: CompositeDisposable = CompositeDisposable() private val compositeDisposable: CompositeDisposable = CompositeDisposable()
private val searchTerm: PublishProcessor<String> = PublishProcessor.create() private val searchTerm: PublishProcessor<String> = PublishProcessor.create()
private val depictedItems: MutableLiveData<List<DepictedItem>> = MutableLiveData() private val depictedItems: MutableLiveData<List<DepictedItem>> = MutableLiveData()
private var media: Media? = null
@Inject @Inject
lateinit var depictsDao: DepictsDao; lateinit var depictsDao: DepictsDao
@Inject
lateinit var depictsHelper: DepictEditHelper
override fun onAttachView(view: DepictsContract.View) { override fun onAttachView(view: DepictsContract.View) {
this.view = view this.view = view
@ -71,16 +78,37 @@ class DepictsPresenter @Inject constructor(
if (querystring.isEmpty()) { if (querystring.isEmpty()) {
recentDepictedItemList = getRecentDepictedItems(); recentDepictedItemList = getRecentDepictedItems();
} }
return repository.searchAllEntities(querystring)
.subscribeOn(ioScheduler)
.map { repository.selectedDepictions + it + recentDepictedItemList }
.map { it.filterNot { item -> WikidataDisambiguationItems.isDisambiguationItem(item.instanceOfs) } }
.map { it.distinctBy(DepictedItem::id) }
}
if (media == null) {
return repository.searchAllEntities(querystring)
.subscribeOn(ioScheduler)
.map { repository.selectedDepictions + it + recentDepictedItemList }
.map { it.filterNot { item -> WikidataDisambiguationItems.isDisambiguationItem(item.instanceOfs) } }
.map { it.distinctBy(DepictedItem::id) }
} else {
return Flowable.zip(repository.getDepictions(repository.selectedExistingDepictions)
.map { list -> list.map {
DepictedItem(it.name, it.description, it.imageUrl, it.instanceOfs,
it.commonsCategories, true, it.id)
}
},
repository.searchAllEntities(querystring),
{ it1, it2 ->
it1 + it2
}
)
.subscribeOn(ioScheduler)
.map { repository.selectedDepictions + it + recentDepictedItemList }
.map { it.filterNot { item -> WikidataDisambiguationItems.isDisambiguationItem(item.instanceOfs) } }
.map { it.distinctBy(DepictedItem::id) }
}
}
override fun onDetachView() { override fun onDetachView() {
view = DUMMY view = DUMMY
media = null
compositeDisposable.clear() compositeDisposable.clear()
} }
@ -102,7 +130,7 @@ class DepictsPresenter @Inject constructor(
private fun selectNewDepictions(toSelect: List<DepictedItem>) { private fun selectNewDepictions(toSelect: List<DepictedItem>) {
toSelect.forEach { toSelect.forEach {
it.isSelected = true it.isSelected = true
repository.onDepictItemClicked(it) repository.onDepictItemClicked(it, media)
} }
// Add the new selections to the list of depicted items so that the selections appear // Add the new selections to the list of depicted items so that the selections appear
@ -113,12 +141,16 @@ class DepictsPresenter @Inject constructor(
?.let { depictedItems.value = it } ?.let { depictedItems.value = it }
} }
override fun clearPreviousSelection() {
repository.cleanup()
}
override fun onPreviousButtonClicked() { override fun onPreviousButtonClicked() {
view.goToPreviousScreen() view.goToPreviousScreen()
} }
override fun onDepictItemClicked(depictedItem: DepictedItem) { override fun onDepictItemClicked(depictedItem: DepictedItem) {
repository.onDepictItemClicked(depictedItem) repository.onDepictItemClicked(depictedItem, media)
} }
override fun getDepictedItems(): LiveData<List<DepictedItem>> { override fun getDepictedItems(): LiveData<List<DepictedItem>> {
@ -149,6 +181,76 @@ class DepictsPresenter @Inject constructor(
} }
} }
/**
* Gets the selected depicts and send them for posting to the server
* and saves them in local storage
*/
@SuppressLint("CheckResult")
override fun updateDepictions(media: Media) {
if (repository.selectedDepictions.isNotEmpty()
|| repository.selectedExistingDepictions.size != view.existingDepictions.size
) {
view.showProgressDialog()
val selectedDepictions: MutableList<String> =
(repository.selectedDepictions.map { it.id }.toMutableList()
+ repository.selectedExistingDepictions).toMutableList()
if (selectedDepictions.isNotEmpty()) {
if (::depictsDao.isInitialized) {
//save all the selected Depicted item in room Database
depictsDao.savingDepictsInRoomDataBase(repository.selectedDepictions)
}
compositeDisposable.add(
depictsHelper.makeDepictionEdit(view.fragmentContext, media, selectedDepictions)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
media.depictionIds = selectedDepictions
repository.cleanup()
view.dismissProgressDialog()
view.updateDepicts()
view.goBackToPreviousScreen()
})
{
Timber.e(
"Failed to update depictions"
)
}
)
}
} else {
repository.cleanup()
view.noDepictionSelected()
}
}
override fun onAttachViewWithMedia(view: DepictsContract.View, media: Media) {
this.view = view
this.media = media
repository.selectedExistingDepictions = view.existingDepictions
compositeDisposable.add(
searchTerm
.observeOn(mainThreadScheduler)
.doOnNext { view.showProgress(true) }
.switchMap(::searchResultsWithTerm)
.observeOn(mainThreadScheduler)
.subscribe(
{ (results, term) ->
view.showProgress(false)
view.showError(results.isEmpty() && term.isNotEmpty())
depictedItems.value = results
},
{ t: Throwable? ->
view.showProgress(false)
view.showError(true)
Timber.e(t)
}
)
)
}
/** /**
* Get the depicts from DepictsRoomdataBase * Get the depicts from DepictsRoomdataBase
*/ */

View file

@ -1,7 +1,7 @@
package fr.free.nrw.commons.wikidata; package fr.free.nrw.commons.wikidata;
import static fr.free.nrw.commons.media.MediaClientKt.PAGE_ID_PREFIX;
import static fr.free.nrw.commons.di.NetworkingModule.NAMED_COMMONS_CSRF; import static fr.free.nrw.commons.di.NetworkingModule.NAMED_COMMONS_CSRF;
import static fr.free.nrw.commons.media.MediaClientKt.PAGE_ID_PREFIX;
import fr.free.nrw.commons.upload.UploadResult; import fr.free.nrw.commons.upload.UploadResult;
import fr.free.nrw.commons.upload.WikiBaseInterface; import fr.free.nrw.commons.upload.WikiBaseInterface;
@ -35,6 +35,20 @@ public class WikiBaseClient {
.map(response -> (response.getSuccessVal() == 1))); .map(response -> (response.getSuccessVal() == 1)));
} }
/**
* Makes the server call for posting new depicts
*
* @param filename name of the file
* @param data data of the depicts to be uploaded
* @return Observable<Boolean>
*/
public Observable<Boolean> postEditEntityByFilename(final String filename, final String data) {
return csrfToken()
.switchMap(editToken -> wikiBaseInterface.postEditEntityByFilename(filename,
editToken, data)
.map(response -> (response.getSuccessVal() == 1)));
}
public Observable<Long> getFileEntityId(UploadResult uploadResult) { public Observable<Long> getFileEntityId(UploadResult uploadResult) {
return wikiBaseInterface.getFileEntityId(uploadResult.createCanonicalFileName()) return wikiBaseInterface.getFileEntityId(uploadResult.createCanonicalFileName())
.map(response -> (long) (response.query().pages().get(0).pageId())); .map(response -> (long) (response.query().pages().get(0).pageId()));

View file

@ -16,7 +16,6 @@ import fr.free.nrw.commons.upload.WikidataPlace;
import fr.free.nrw.commons.utils.ConfigUtils; import fr.free.nrw.commons.utils.ConfigUtils;
import fr.free.nrw.commons.utils.ViewUtil; import fr.free.nrw.commons.utils.ViewUtil;
import io.reactivex.Observable; import io.reactivex.Observable;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers; import io.reactivex.schedulers.Schedulers;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -73,11 +72,12 @@ public class WikidataEditService {
*/ */
@SuppressLint("CheckResult") @SuppressLint("CheckResult")
private Observable<Boolean> addDepictsProperty(final String fileEntityId, private Observable<Boolean> addDepictsProperty(final String fileEntityId,
final WikidataItem depictedItem) { final List<String> depictedItems) {
final EditClaim data = editClaim( final EditClaim data = editClaim(
ConfigUtils.isBetaFlavour() ? "Q10" // Wikipedia:Sandbox (Q10) ConfigUtils.isBetaFlavour() ? Collections.singletonList("Q10")
: depictedItem.getId() // Wikipedia:Sandbox (Q10)
: depictedItems
); );
return wikiBaseClient.postEditEntity(PAGE_ID_PREFIX + fileEntityId, gson.toJson(data)) return wikiBaseClient.postEditEntity(PAGE_ID_PREFIX + fileEntityId, gson.toJson(data))
@ -95,8 +95,42 @@ public class WikidataEditService {
.subscribeOn(Schedulers.io()); .subscribeOn(Schedulers.io());
} }
private EditClaim editClaim(final String entityId) { /**
return EditClaim.from(entityId, WikidataProperties.DEPICTS.getPropertyName()); * Takes depicts ID as a parameter and create a uploadable data with the Id
* and send the data for POST operation
*
* @param filename name of the file
* @param depictedItems ID of the selected depict item
* @return Observable<Boolean>
*/
@SuppressLint("CheckResult")
public Observable<Boolean> updateDepictsProperty(final String filename,
final List<String> depictedItems) {
final EditClaim data = editClaim(
ConfigUtils.isBetaFlavour() ? Collections.singletonList("Q10")
// Wikipedia:Sandbox (Q10)
: depictedItems
);
return wikiBaseClient.postEditEntityByFilename(filename,
gson.toJson(data))
.doOnNext(success -> {
if (success) {
Timber.d("DEPICTS property was set successfully for %s", filename);
} else {
Timber.d("Unable to set DEPICTS property for %s", filename);
}
})
.doOnError(throwable -> {
Timber.e(throwable, "Error occurred while setting DEPICTS property");
ViewUtil.showLongToast(context, throwable.toString());
})
.subscribeOn(Schedulers.io());
}
private EditClaim editClaim(final List<String> entityIds) {
return EditClaim.from(entityIds, WikidataProperties.DEPICTS.getPropertyName());
} }
/** /**
@ -209,7 +243,12 @@ public class WikidataEditService {
} }
private Observable<Boolean> depictionEdits(Contribution contribution, Long fileEntityId) { private Observable<Boolean> depictionEdits(Contribution contribution, Long fileEntityId) {
return Observable.fromIterable(contribution.getDepictedItems()) final List<String> depictIDs = new ArrayList<>();
.concatMap(wikidataItem -> addDepictsProperty(fileEntityId.toString(), wikidataItem)); for (final WikidataItem wikidataItem :
contribution.getDepictedItems()) {
depictIDs.add(wikidataItem.getId());
}
return addDepictsProperty(fileEntityId.toString(), depictIDs);
} }
} }

View file

@ -336,6 +336,15 @@
android:orientation="vertical" /> android:orientation="vertical" />
</LinearLayout> </LinearLayout>
<Button
android:id="@+id/depictionsEditButton"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_margin="@dimen/standard_gap"
android:layout_gravity="end"
android:visibility="gone"
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

@ -4,9 +4,9 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_marginBottom="8dp"
android:orientation="vertical" android:orientation="vertical"
tools:showIn="@layout/activity_upload"> android:background="?attr/mainBackground">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"

View file

@ -524,6 +524,14 @@ Upload your first media by tapping on the add button.</string>
<string name="category_edit_helper_edit_message_else">Could not add categories.</string> <string name="category_edit_helper_edit_message_else">Could not add categories.</string>
<string name="category_edit_button_text">Update categories</string> <string name="category_edit_button_text">Update categories</string>
<string name="depictions_edit_helper_make_edit_toast">Trying to update depictions.</string>
<string name="depictions_edit_helper_show_edit_title">Edit depictions</string>
<plurals name="depictions_edit_helper_show_edit_message_if">
<item quantity="one">Depiction %1$s is added.</item>
<item quantity="other">Depictions %1$s are added.</item>
</plurals>
<string name="depictions_edit_helper_edit_message_else">Could not add depictions.</string>
<string name="coordinates_edit_helper_make_edit_toast">Trying to update coordinates.</string> <string name="coordinates_edit_helper_make_edit_toast">Trying to update coordinates.</string>
<string name="coordinates_edit_helper_show_edit_title">Coordinates update</string> <string name="coordinates_edit_helper_show_edit_title">Coordinates update</string>
<string name="description_edit_helper_show_edit_title">Description update</string> <string name="description_edit_helper_show_edit_title">Description update</string>
@ -681,6 +689,7 @@ Upload your first media by tapping on the add button.</string>
<string name="contributions_of_user">Contributions of User: %s</string> <string name="contributions_of_user">Contributions of User: %s</string>
<string name="achievements_of_user">Achievements of User: %s</string> <string name="achievements_of_user">Achievements of User: %s</string>
<string name="menu_view_user_page">View user page</string> <string name="menu_view_user_page">View user page</string>
<string name="edit_depictions">Edit depictions</string>
<string name="advanced_options">Advanced Options</string> <string name="advanced_options">Advanced Options</string>
<string name="advanced_query_info_text">You can customize the Nearby query. If you get errors, reset and apply.</string> <string name="advanced_query_info_text">You can customize the Nearby query. If you get errors, reset and apply.</string>
<string name="apply">Apply</string> <string name="apply">Apply</string>

View file

@ -9,6 +9,7 @@ import android.os.Bundle
import android.os.Looper import android.os.Looper
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.View.GONE
import android.view.ViewTreeObserver import android.view.ViewTreeObserver
import android.webkit.WebView import android.webkit.WebView
import android.widget.* import android.widget.*
@ -19,7 +20,6 @@ import com.facebook.drawee.backends.pipeline.Fresco
import com.facebook.drawee.generic.GenericDraweeHierarchy import com.facebook.drawee.generic.GenericDraweeHierarchy
import com.facebook.drawee.view.SimpleDraweeView import com.facebook.drawee.view.SimpleDraweeView
import com.facebook.soloader.SoLoader import com.facebook.soloader.SoLoader
import fr.free.nrw.commons.*
import fr.free.nrw.commons.LocationPicker.LocationPickerActivity import fr.free.nrw.commons.LocationPicker.LocationPickerActivity
import org.robolectric.Shadows.shadowOf import org.robolectric.Shadows.shadowOf
import fr.free.nrw.commons.category.CategoryEditSearchRecyclerViewAdapter import fr.free.nrw.commons.category.CategoryEditSearchRecyclerViewAdapter
@ -34,10 +34,8 @@ import fr.free.nrw.commons.TestCommonsApplication
import fr.free.nrw.commons.R import fr.free.nrw.commons.R
import fr.free.nrw.commons.TestAppAdapter import fr.free.nrw.commons.TestAppAdapter
import fr.free.nrw.commons.Media import fr.free.nrw.commons.Media
import fr.free.nrw.commons.contributions.ContributionViewHolder
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.utils.ImageUtils
import io.reactivex.Single import io.reactivex.Single
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
@ -218,6 +216,7 @@ class MediaDetailFragmentUnitTests {
Whitebox.setInternalState(fragment, "showCaptionAndDescriptionContainer", linearLayout) Whitebox.setInternalState(fragment, "showCaptionAndDescriptionContainer", linearLayout)
Whitebox.setInternalState(fragment, "updateCategoriesButton", button) Whitebox.setInternalState(fragment, "updateCategoriesButton", button)
Whitebox.setInternalState(fragment, "editDescription", button) Whitebox.setInternalState(fragment, "editDescription", button)
Whitebox.setInternalState(fragment, "depictEditButton", button)
Whitebox.setInternalState(fragment, "categoryContainer", linearLayout) Whitebox.setInternalState(fragment, "categoryContainer", linearLayout)
Whitebox.setInternalState(fragment, "categorySearchView", searchView) Whitebox.setInternalState(fragment, "categorySearchView", searchView)
Whitebox.setInternalState(fragment, "progressBarDeletion", progressBarDeletion) Whitebox.setInternalState(fragment, "progressBarDeletion", progressBarDeletion)
@ -656,4 +655,17 @@ class MediaDetailFragmentUnitTests {
MediaDetailFragment.forMedia(0, true, true, true) MediaDetailFragment.forMedia(0, true, true, true)
} }
@Test
@Throws(Exception::class)
fun testOnDepictEditButtonClicked() {
fragment.onDepictionsEditButtonClicked()
verify(linearLayout).removeAllViews()
verify(button).visibility = GONE
}
@Test
@Throws(Exception::class)
fun testOnDeleteButtonClicked() {
fragment.onDeleteButtonClicked()
}
} }

View file

@ -1,10 +1,11 @@
package fr.free.nrw.commons.upload package fr.free.nrw.commons.upload
import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.jraska.livedata.test import com.nhaarman.mockitokotlin2.times
import com.nhaarman.mockitokotlin2.verify import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.whenever import com.nhaarman.mockitokotlin2.whenever
import depictedItem import depictedItem
import fr.free.nrw.commons.Media
import fr.free.nrw.commons.explore.depictions.DepictsClient import fr.free.nrw.commons.explore.depictions.DepictsClient
import fr.free.nrw.commons.repository.UploadRepository import fr.free.nrw.commons.repository.UploadRepository
import fr.free.nrw.commons.upload.depicts.DepictsContract import fr.free.nrw.commons.upload.depicts.DepictsContract
@ -16,7 +17,10 @@ import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.mockito.Mock import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.MockitoAnnotations import org.mockito.MockitoAnnotations
import org.powermock.reflect.Whitebox
import java.lang.reflect.Method
class DepictsPresenterTest { class DepictsPresenterTest {
@ -37,6 +41,9 @@ class DepictsPresenterTest {
@Mock @Mock
lateinit var depictsClient: DepictsClient lateinit var depictsClient: DepictsClient
@Mock
private lateinit var media: Media
/** /**
* initial setup * initial setup
*/ */
@ -92,9 +99,6 @@ class DepictsPresenterTest {
testScheduler.triggerActions() testScheduler.triggerActions()
verify(view).showProgress(false) verify(view).showProgress(false)
verify(view).showError(true) verify(view).showError(true)
depictsPresenter.depictedItems
.test()
.assertValue(emptyList())
} }
@Test @Test
@ -116,7 +120,7 @@ class DepictsPresenterTest {
fun `onDepictItemClicked calls repository`() { fun `onDepictItemClicked calls repository`() {
val depictedItem = depictedItem() val depictedItem = depictedItem()
depictsPresenter.onDepictItemClicked(depictedItem) depictsPresenter.onDepictItemClicked(depictedItem)
verify(repository).onDepictItemClicked(depictedItem) verify(repository).onDepictItemClicked(depictedItem, null)
} }
@Test @Test
@ -132,4 +136,63 @@ class DepictsPresenterTest {
depictsPresenter.verifyDepictions() depictsPresenter.verifyDepictions()
verify(view).noDepictionSelected() verify(view).noDepictionSelected()
} }
@Test
fun testOnAttachViewWithMedia() {
depictsPresenter.onAttachViewWithMedia(view, Mockito.mock(Media::class.java))
}
@Test
fun testUpdateDepicts() {
depictsPresenter.updateDepictions(Mockito.mock(Media::class.java))
}
@Test
fun `Test searchResults when media is null`() {
whenever(repository.searchAllEntities("querystring"))
.thenReturn(Flowable.just(listOf(depictedItem())))
val method: Method = DepictsPresenter::class.java.getDeclaredMethod(
"searchResults",
String::class.java
)
method.isAccessible = true
method.invoke(depictsPresenter, "querystring")
verify(repository, times(1)).searchAllEntities("querystring")
}
@Test
fun `Test searchResults when media is not null`() {
Whitebox.setInternalState(depictsPresenter, "media", media)
whenever(repository.getDepictions(repository.selectedExistingDepictions))
.thenReturn(Flowable.just(listOf(depictedItem())))
whenever(repository.searchAllEntities("querystring"))
.thenReturn(Flowable.just(listOf(depictedItem())))
val method: Method = DepictsPresenter::class.java.getDeclaredMethod(
"searchResults",
String::class.java
)
method.isAccessible = true
method.invoke(depictsPresenter, "querystring")
verify(repository, times(1)).searchAllEntities("querystring")
}
@Test
fun testSelectNewDepictions() {
Whitebox.setInternalState(depictsPresenter, "media", media)
val method: Method = DepictsPresenter::class.java.getDeclaredMethod(
"selectNewDepictions",
List::class.java
)
method.isAccessible = true
method.invoke(depictsPresenter, listOf(depictedItem()))
}
@Test
fun testClearPreviousSelection() {
val method: Method = DepictsPresenter::class.java.getDeclaredMethod(
"clearPreviousSelection"
)
method.isAccessible = true
method.invoke(depictsPresenter)
}
} }

View file

@ -0,0 +1,126 @@
package fr.free.nrw.commons.upload
import android.content.Context
import fr.free.nrw.commons.auth.SessionManager
import fr.free.nrw.commons.kvstore.JsonKvStore
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
import media
import org.junit.Before
import org.junit.Test
import org.mockito.Mockito.mock
import org.mockito.MockitoAnnotations
class UploadModelUnitTest {
private lateinit var uploadModel: UploadModel
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
uploadModel = UploadModel(
listOf(),
mock(JsonKvStore::class.java),
mapOf(),
mock(Context::class.java),
mock(SessionManager::class.java),
mock(FileProcessor::class.java),
mock(ImageProcessingService::class.java)
)
}
@Test
fun `Test onDepictItemClicked when DepictedItem is selected`(){
uploadModel.onDepictItemClicked(
DepictedItem(
"Test",
"Test",
"test",
listOf(),
listOf(),
true,
"depictionId"
), media(filename = "File:Example.jpg"))
}
@Test
fun `Test onDepictItemClicked when DepictedItem is not selected`(){
uploadModel.onDepictItemClicked(
DepictedItem(
"Test",
"Test",
"test",
listOf(),
listOf(),
false,
"depictionId"
), media(filename = "File:Example.jpg")
)
}
@Test
fun `Test onDepictItemClicked when DepictedItem is not selected and not included in media`(){
uploadModel.onDepictItemClicked(
DepictedItem(
"Test",
"Test",
"test",
listOf(),
listOf(),
false,
"id"
), media(filename = "File:Example.jpg")
)
}
@Test
fun `Test onDepictItemClicked when media is null and DepictedItem is not selected`(){
uploadModel.onDepictItemClicked(
DepictedItem(
"Test",
"Test",
"test",
listOf(),
listOf(),
false,
"id"
), null)
}
@Test
fun `Test onDepictItemClicked when media is not null and DepictedItem is selected`(){
uploadModel.onDepictItemClicked(
DepictedItem(
"Test",
"Test",
"test",
listOf(),
listOf(),
true,
"id"
), media(filename = "File:Example.jpg"))
}
@Test
fun `Test onDepictItemClicked when media is null and DepictedItem is selected`(){
uploadModel.onDepictItemClicked(
DepictedItem(
"Test",
"Test",
"test",
listOf(),
listOf(),
true,
"id"
), null)
}
@Test
fun testGetSelectedExistingDepictions(){
uploadModel.selectedExistingDepictions
}
@Test
fun testSetSelectedExistingDepictions(){
uploadModel.selectedExistingDepictions = listOf("")
}
}

View file

@ -1,5 +1,6 @@
package fr.free.nrw.commons.upload package fr.free.nrw.commons.upload
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.verify import com.nhaarman.mockitokotlin2.verify
import fr.free.nrw.commons.category.CategoriesModel import fr.free.nrw.commons.category.CategoriesModel
import fr.free.nrw.commons.category.CategoryItem import fr.free.nrw.commons.category.CategoryItem
@ -13,12 +14,15 @@ import fr.free.nrw.commons.repository.UploadRepository
import fr.free.nrw.commons.upload.structure.depictions.DepictModel import fr.free.nrw.commons.upload.structure.depictions.DepictModel
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
import io.reactivex.Completable import io.reactivex.Completable
import io.reactivex.Single
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
import org.mockito.Mock import org.mockito.Mock
import org.mockito.Mockito.`when` import org.mockito.Mockito.`when`
import org.mockito.Mockito.mock
import org.mockito.MockitoAnnotations import org.mockito.MockitoAnnotations
import java.lang.reflect.Method
class UploadRepositoryUnitTest { class UploadRepositoryUnitTest {
@ -204,11 +208,17 @@ class UploadRepositoryUnitTest {
assertEquals(repository.setSelectedLicense(""), uploadModel.setSelectedLicense("")) assertEquals(repository.setSelectedLicense(""), uploadModel.setSelectedLicense(""))
} }
@Test
fun testSetSelectedExistingDepictions() {
assertEquals(repository.setSelectedExistingDepictions(listOf("")),
uploadModel.setSelectedExistingDepictions(listOf("")))
}
@Test @Test
fun testOnDepictItemClicked() { fun testOnDepictItemClicked() {
assertEquals( assertEquals(
repository.onDepictItemClicked(depictedItem), repository.onDepictItemClicked(depictedItem, mock()),
uploadModel.onDepictItemClicked(depictedItem) uploadModel.onDepictItemClicked(depictedItem, mock())
) )
} }
@ -217,6 +227,11 @@ class UploadRepositoryUnitTest {
assertEquals(repository.selectedDepictions, uploadModel.selectedDepictions) assertEquals(repository.selectedDepictions, uploadModel.selectedDepictions)
} }
@Test
fun testGetSelectedExistingDepictions() {
assertEquals(repository.selectedExistingDepictions, uploadModel.selectedExistingDepictions)
}
@Test @Test
fun testSearchAllEntities() { fun testSearchAllEntities() {
assertEquals( assertEquals(
@ -292,4 +307,36 @@ class UploadRepositoryUnitTest {
) )
} }
@Test
fun testGetDepictions() {
`when`(depictModel.getDepictions("Q12"))
.thenReturn(Single.just(listOf(mock(DepictedItem::class.java))))
val method: Method = UploadRepository::class.java.getDeclaredMethod(
"getDepictions",
List::class.java
)
method.isAccessible = true
method.invoke(repository, listOf("Q12"))
}
@Test
fun testJoinIDs() {
val method: Method = UploadRepository::class.java.getDeclaredMethod(
"joinQIDs",
List::class.java
)
method.isAccessible = true
method.invoke(repository, listOf("Q12", "Q23"))
}
@Test
fun `test joinIDs when depictIDs is null`() {
val method: Method = UploadRepository::class.java.getDeclaredMethod(
"joinQIDs",
List::class.java
)
method.isAccessible = true
method.invoke(repository, null)
}
} }

View file

@ -0,0 +1,109 @@
package fr.free.nrw.commons.upload.depicts
import android.content.Context
import com.nhaarman.mockitokotlin2.whenever
import fr.free.nrw.commons.Media
import fr.free.nrw.commons.R
import fr.free.nrw.commons.TestCommonsApplication
import fr.free.nrw.commons.notification.NotificationHelper
import fr.free.nrw.commons.utils.ViewUtilWrapper
import fr.free.nrw.commons.wikidata.WikidataEditService
import io.reactivex.Observable
import org.junit.Assert
import org.junit.Before
import org.junit.Test
import org.junit.jupiter.api.Assertions
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.MockitoAnnotations
import org.powermock.reflect.Whitebox
import org.robolectric.RobolectricTestRunner
import org.robolectric.RuntimeEnvironment
import org.robolectric.annotation.Config
import org.robolectric.annotation.LooperMode
import java.lang.reflect.Method
@RunWith(RobolectricTestRunner::class)
@Config(sdk = [21], application = TestCommonsApplication::class)
@LooperMode(LooperMode.Mode.PAUSED)
class DepictEditHelperUnitTest {
private lateinit var context: Context
private lateinit var helper: DepictEditHelper
@Mock
private lateinit var notificationHelper: NotificationHelper
@Mock
private lateinit var wikidataEditService: WikidataEditService
@Mock
private lateinit var viewUtilWrapper: ViewUtilWrapper
@Mock
private lateinit var media: Media
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
context = RuntimeEnvironment.application.applicationContext
helper = DepictEditHelper(notificationHelper, wikidataEditService, viewUtilWrapper)
Whitebox.setInternalState(helper, "viewUtilWrapper", viewUtilWrapper)
Whitebox.setInternalState(helper, "notificationHelper", notificationHelper)
Whitebox.setInternalState(helper, "wikidataEditService", wikidataEditService)
}
@Test
@Throws(Exception::class)
fun checkNotNull() {
Assert.assertNotNull(helper)
}
@Test
@Throws(Exception::class)
fun testMakeDepictEdit() {
whenever(wikidataEditService.updateDepictsProperty(media.filename, listOf("Q12")))
.thenReturn(Observable.just(true))
helper.makeDepictionEdit(context, media, listOf("Q12"))
Mockito.verify(viewUtilWrapper, Mockito.times(1)).showShortToast(
context,
context.getString(R.string.depictions_edit_helper_make_edit_toast)
)
}
@Test
@Throws(Exception::class)
fun testShowCoordinatesEditNotificationCaseTrue() {
whenever(media.depictionIds).thenReturn(listOf("id", "id2"))
val method: Method = DepictEditHelper::class.java.getDeclaredMethod(
"showDepictionEditNotification",
Context::class.java,
Media::class.java,
Boolean::class.java
)
method.isAccessible = true
Assertions.assertEquals(
method.invoke(helper, context, media, true),
true
)
}
@Test
@Throws(Exception::class)
fun testShowCoordinatesEditNotificationCaseFalse() {
val method: Method = DepictEditHelper::class.java.getDeclaredMethod(
"showDepictionEditNotification",
Context::class.java,
Media::class.java,
Boolean::class.java
)
method.isAccessible = true
Assertions.assertEquals(
method.invoke(helper, context, media, false),
false
)
}
}

View file

@ -1,9 +1,11 @@
package fr.free.nrw.commons.upload.depicts package fr.free.nrw.commons.upload.depicts
import android.app.ProgressDialog
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.widget.Button
import android.widget.ImageView import android.widget.ImageView
import android.widget.ProgressBar import android.widget.ProgressBar
import android.widget.TextView import android.widget.TextView
@ -11,9 +13,13 @@ import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentTransaction import androidx.fragment.app.FragmentTransaction
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import com.nhaarman.mockitokotlin2.whenever
import depictedItem
import fr.free.nrw.commons.Media
import fr.free.nrw.commons.R import fr.free.nrw.commons.R
import fr.free.nrw.commons.TestAppAdapter import fr.free.nrw.commons.TestAppAdapter
import fr.free.nrw.commons.TestCommonsApplication import fr.free.nrw.commons.TestCommonsApplication
import fr.free.nrw.commons.kvstore.JsonKvStore
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
@ -62,6 +68,9 @@ class DepictsFragmentUnitTests {
@Mock @Mock
private lateinit var progressBar: ProgressBar private lateinit var progressBar: ProgressBar
@Mock
private lateinit var button: Button
@Mock @Mock
private lateinit var textInputLayout: TextInputLayout private lateinit var textInputLayout: TextInputLayout
@ -74,6 +83,15 @@ class DepictsFragmentUnitTests {
@Mock @Mock
private lateinit var adapter: UploadDepictsAdapter private lateinit var adapter: UploadDepictsAdapter
@Mock
private lateinit var applicationKvStore: JsonKvStore
@Mock
private lateinit var media: Media
@Mock
private lateinit var progressDialog: ProgressDialog
@Before @Before
fun setUp() { fun setUp() {
MockitoAnnotations.initMocks(this) MockitoAnnotations.initMocks(this)
@ -95,6 +113,8 @@ class DepictsFragmentUnitTests {
Whitebox.setInternalState(fragment, "depictsTitle", textView) Whitebox.setInternalState(fragment, "depictsTitle", textView)
Whitebox.setInternalState(fragment, "callback", callback) Whitebox.setInternalState(fragment, "callback", callback)
Whitebox.setInternalState(fragment, "tooltip", imageView) Whitebox.setInternalState(fragment, "tooltip", imageView)
Whitebox.setInternalState(fragment, "btnNext", button)
Whitebox.setInternalState(fragment, "btnPrevious", button)
Whitebox.setInternalState(fragment, "depictsSubTitle", textView) Whitebox.setInternalState(fragment, "depictsSubTitle", textView)
Whitebox.setInternalState(fragment, "depictsRecyclerView", recyclerView) Whitebox.setInternalState(fragment, "depictsRecyclerView", recyclerView)
Whitebox.setInternalState(fragment, "depictsSearch", textInputEditText) Whitebox.setInternalState(fragment, "depictsSearch", textInputEditText)
@ -126,6 +146,17 @@ class DepictsFragmentUnitTests {
method.invoke(fragment) method.invoke(fragment)
} }
@Test
@Throws(Exception::class)
fun `Test init when media is not null`() {
Whitebox.setInternalState(fragment, "media", media)
val method: Method = DepictsFragment::class.java.getDeclaredMethod(
"init"
)
method.isAccessible = true
method.invoke(fragment)
}
@Test @Test
@Throws(Exception::class) @Throws(Exception::class)
fun testOnBecameVisible() { fun testOnBecameVisible() {
@ -184,6 +215,20 @@ class DepictsFragmentUnitTests {
fragment.setDepictsList(listOf()) fragment.setDepictsList(listOf())
} }
@Test
@Throws(Exception::class)
fun `Test setDepictsList when list is not empty`() {
fragment.setDepictsList(listOf(depictedItem()))
}
@Test
@Throws(Exception::class)
fun `Test setDepictsList when applicationKvStore returns true`() {
Whitebox.setInternalState(fragment, "applicationKvStore", applicationKvStore)
whenever(applicationKvStore.getBoolean("first_edit_depict")).thenReturn(true)
fragment.setDepictsList(listOf(depictedItem()))
}
@Test @Test
@Throws(Exception::class) @Throws(Exception::class)
fun testOnNextButtonClicked() { fun testOnNextButtonClicked() {
@ -207,4 +252,61 @@ class DepictsFragmentUnitTests {
method.invoke(fragment, "") method.invoke(fragment, "")
} }
@Test
@Throws(Exception::class)
fun testOnResume() {
fragment.onResume()
}
@Test
@Throws(Exception::class)
fun testOnStop() {
fragment.onStop()
}
@Test
@Throws(Exception::class)
fun testInitRecyclerView() {
val method: Method = DepictsFragment::class.java.getDeclaredMethod(
"initRecyclerView"
)
method.isAccessible = true
method.invoke(fragment)
}
@Test
@Throws(Exception::class)
fun `Test initRecyclerView when media is not null`() {
Whitebox.setInternalState(fragment, "media", media)
val method: Method = DepictsFragment::class.java.getDeclaredMethod(
"initRecyclerView"
)
method.isAccessible = true
method.invoke(fragment)
}
@Test
@Throws(Exception::class)
fun testGetFragmentContext() {
fragment.fragmentContext
}
@Test
@Throws(Exception::class)
fun testGoBackToPreviousScreen() {
fragment.goBackToPreviousScreen()
}
@Test
@Throws(Exception::class)
fun testShowProgressDialog() {
fragment.showProgressDialog()
}
@Test
@Throws(Exception::class)
fun testDismissProgressDialog() {
Whitebox.setInternalState(fragment, "progressDialog", progressDialog)
fragment.dismissProgressDialog()
}
} }

View file

@ -0,0 +1,31 @@
package fr.free.nrw.commons.wikidata
import org.junit.Before
import org.junit.Test
import org.mockito.InjectMocks
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.MockitoAnnotations
import org.wikipedia.csrf.CsrfTokenClient
class WikiBaseClientUnitTest {
@Mock
internal var csrfTokenClient: CsrfTokenClient? = null
@InjectMocks
var wikiBaseClient: WikiBaseClient? = null
@Before
@Throws(Exception::class)
fun setUp() {
MockitoAnnotations.initMocks(this)
Mockito.`when`(csrfTokenClient!!.tokenBlocking)
.thenReturn("test")
}
@Test
fun testPostEditEntityByFilename() {
wikiBaseClient?.postEditEntityByFilename("File:Example.jpg", "data")
}
}

View file

@ -15,7 +15,9 @@ import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyString import org.mockito.ArgumentMatchers.anyString
import org.mockito.InjectMocks import org.mockito.InjectMocks
import org.mockito.Mock import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.MockitoAnnotations import org.mockito.MockitoAnnotations
import org.wikipedia.wikidata.EditClaim
class WikidataEditServiceTest { class WikidataEditServiceTest {
@Mock @Mock
@ -48,6 +50,13 @@ class WikidataEditServiceTest {
verifyZeroInteractions(wikidataClient) verifyZeroInteractions(wikidataClient)
} }
@Test
fun testUpdateDepictsProperty() {
whenever(wikibaseClient.postEditEntityByFilename("Test.jpg",
gson.toJson(Mockito.mock(EditClaim::class.java)))).thenReturn(Observable.just(true))
wikidataEditService.updateDepictsProperty("Test.jpg", listOf())
}
@Test @Test
fun createImageClaim() { fun createImageClaim() {
whenever(directKvStore.getBoolean("Picture_Has_Correct_Location", true)) whenever(directKvStore.getBoolean("Picture_Has_Correct_Location", true))

View file

@ -7,9 +7,11 @@ data class EditClaim(val claims: List<Statement_partial>) {
companion object { companion object {
@JvmStatic @JvmStatic
fun from(entityId: String, propertyName: String) = fun from(entityIds: List<String>, propertyName: String): EditClaim {
EditClaim(
listOf( val list = mutableListOf<Statement_partial>()
entityIds.forEach {
list.add(
Statement_partial( Statement_partial(
Snak_partial( Snak_partial(
"value", "value",
@ -17,8 +19,8 @@ data class EditClaim(val claims: List<Statement_partial>) {
EntityId( EntityId(
WikiBaseEntityValue( WikiBaseEntityValue(
"item", "item",
entityId, it,
entityId.removePrefix("Q").toLong() it.removePrefix("Q").toLong()
) )
) )
), ),
@ -26,6 +28,8 @@ data class EditClaim(val claims: List<Statement_partial>) {
"preferred" "preferred"
) )
) )
) }
return EditClaim(list)
}
} }
} }