diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 4048abca0..52ba2b233 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -36,6 +36,10 @@
android:requestLegacyExternalStorage = "true"
tools:ignore="GoogleAppIndexingWarning">
+
+
{
+ return try {
+ pageEditInterface.postCaptions(summary, title, language,
+ value, csrfTokenClient.tokenBlocking).map { it.success }
+ } catch (throwable: Throwable) {
+ Observable.just(0)
+ }
+ }
+
/**
* Get whole WikiText of required file
* @param title : Name of the file
diff --git a/app/src/main/java/fr/free/nrw/commons/actions/PageEditInterface.kt b/app/src/main/java/fr/free/nrw/commons/actions/PageEditInterface.kt
index 91e8230bf..99f8b7d47 100644
--- a/app/src/main/java/fr/free/nrw/commons/actions/PageEditInterface.kt
+++ b/app/src/main/java/fr/free/nrw/commons/actions/PageEditInterface.kt
@@ -5,6 +5,7 @@ import io.reactivex.Single
import org.wikipedia.dataclient.Service
import org.wikipedia.dataclient.mwapi.MwQueryResponse
import org.wikipedia.edit.Edit
+import org.wikipedia.wikidata.Entities
import retrofit2.http.*
/**
@@ -73,6 +74,18 @@ interface PageEditInterface {
@Field("token") token: String
): Observable
+
+ @FormUrlEncoded
+ @Headers("Cache-Control: no-cache")
+ @POST(Service.MW_API_PREFIX + "action=wbsetlabel&format=json&site=commonswiki&formatversion=2")
+ fun postCaptions(
+ @Field("summary") summary: String,
+ @Field("title") title: String,
+ @Field("language") language: String,
+ @Field("value") value: String,
+ @Field("token") token: String
+ ): Observable
+
/**
* Get wiki text for provided file names
* @param titles : Name of the file
diff --git a/app/src/main/java/fr/free/nrw/commons/description/DescriptionEditActivity.kt b/app/src/main/java/fr/free/nrw/commons/description/DescriptionEditActivity.kt
new file mode 100644
index 000000000..1389e3c0f
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/description/DescriptionEditActivity.kt
@@ -0,0 +1,181 @@
+package fr.free.nrw.commons.description
+
+import android.app.ProgressDialog
+import android.content.Intent
+import android.os.Bundle
+import android.os.Parcelable
+import android.view.View
+import androidx.appcompat.app.AppCompatActivity
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import fr.free.nrw.commons.R
+import fr.free.nrw.commons.databinding.ActivityDescriptionEditBinding
+import fr.free.nrw.commons.description.EditDescriptionConstants.LIST_OF_DESCRIPTION_AND_CAPTION
+import fr.free.nrw.commons.description.EditDescriptionConstants.UPDATED_WIKITEXT
+import fr.free.nrw.commons.description.EditDescriptionConstants.WIKITEXT
+import fr.free.nrw.commons.kvstore.JsonKvStore
+import fr.free.nrw.commons.settings.Prefs
+import fr.free.nrw.commons.upload.UploadMediaDetail
+import fr.free.nrw.commons.upload.UploadMediaDetailAdapter
+import fr.free.nrw.commons.utils.DialogUtil.showAlertDialog
+import java.util.*
+import javax.inject.Inject
+import javax.inject.Named
+
+/**
+ * Activity for populating and editing existing description and caption
+ */
+class DescriptionEditActivity : AppCompatActivity(), UploadMediaDetailAdapter.EventListener {
+ /**
+ * Adapter for showing UploadMediaDetail in the activity
+ */
+ private lateinit var uploadMediaDetailAdapter: UploadMediaDetailAdapter
+
+ /**
+ * For getting default preference
+ */
+ @JvmField
+ @Inject
+ @Named("default_preferences")
+ var defaultKvStore: JsonKvStore? = null
+
+ /**
+ * Recyclerview for recycling data in views
+ */
+ @JvmField
+ var rvDescriptions: RecyclerView? = null
+
+ /**
+ * Current wikitext
+ */
+ var wikiText: String? = null
+
+ /**
+ * For showing progress dialog
+ */
+ private var progressDialog: ProgressDialog? = null
+
+ private lateinit var binding: ActivityDescriptionEditBinding
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ binding = ActivityDescriptionEditBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+
+ val bundle = intent.extras
+ val descriptionAndCaptions: ArrayList =
+ bundle!!.getParcelableArrayList(LIST_OF_DESCRIPTION_AND_CAPTION)!!
+ wikiText = bundle.getString(WIKITEXT)
+ initRecyclerView(descriptionAndCaptions)
+
+ binding.btnAddDescription.setOnClickListener(::onButtonAddDescriptionClicked)
+ binding.btnEditSubmit.setOnClickListener(::onSubmitButtonClicked)
+ binding.toolbarBackButton.setOnClickListener(::onBackButtonClicked)
+ }
+
+ /**
+ * Initializes the RecyclerView
+ * @param descriptionAndCaptions list of description and caption
+ */
+ private fun initRecyclerView(descriptionAndCaptions: ArrayList?) {
+ uploadMediaDetailAdapter = UploadMediaDetailAdapter(
+ defaultKvStore?.getString(Prefs.DESCRIPTION_LANGUAGE, ""),
+ descriptionAndCaptions)
+ uploadMediaDetailAdapter.setCallback { titleStringID: Int, messageStringId: Int ->
+ showInfoAlert(
+ titleStringID,
+ messageStringId
+ )
+ }
+ uploadMediaDetailAdapter.setEventListener(this)
+ rvDescriptions = binding.rvDescriptionsCaptions
+ rvDescriptions!!.layoutManager = LinearLayoutManager(this)
+ rvDescriptions!!.adapter = uploadMediaDetailAdapter
+ }
+
+ /**
+ * show dialog with info
+ * @param titleStringID Title ID
+ * @param messageStringId Message ID
+ */
+ private fun showInfoAlert(titleStringID: Int, messageStringId: Int) {
+ showAlertDialog(
+ this, getString(titleStringID),
+ getString(messageStringId), getString(android.R.string.ok),
+ null, true
+ )
+ }
+
+ override fun onPrimaryCaptionTextChange(isNotEmpty: Boolean) {}
+
+ private fun onBackButtonClicked(view: View) {
+ onBackPressed()
+ }
+
+ private fun onButtonAddDescriptionClicked(view: View) {
+ val uploadMediaDetail = UploadMediaDetail()
+ uploadMediaDetail.isManuallyAdded = true //This was manually added by the user
+ uploadMediaDetailAdapter.addDescription(uploadMediaDetail)
+ rvDescriptions!!.smoothScrollToPosition(uploadMediaDetailAdapter.itemCount - 1)
+ }
+
+ private fun onSubmitButtonClicked(view: View) {
+ showLoggingProgressBar()
+ val uploadMediaDetails = uploadMediaDetailAdapter.items
+ updateDescription(uploadMediaDetails)
+ finish()
+ }
+
+ /**
+ * Updates newly added descriptions in the wikiText and send to calling fragment
+ * @param uploadMediaDetails descriptions and captions
+ */
+ private fun updateDescription(uploadMediaDetails: List) {
+ var descriptionIndex = wikiText!!.indexOf("description=")
+ if (descriptionIndex == -1) {
+ descriptionIndex = wikiText!!.indexOf("Description=")
+ }
+ val buffer = StringBuilder()
+ if (descriptionIndex != -1) {
+ val descriptionStart = wikiText!!.substring(0, descriptionIndex + 12)
+ val descriptionToEnd = wikiText!!.substring(descriptionIndex + 12)
+ val descriptionEndIndex = descriptionToEnd.indexOf("\n")
+ val descriptionEnd = wikiText!!.substring(
+ descriptionStart.length
+ + descriptionEndIndex
+ )
+ buffer.append(descriptionStart)
+ for (i in uploadMediaDetails.indices) {
+ val uploadDetails = uploadMediaDetails[i]
+ if (uploadDetails!!.descriptionText != "") {
+ buffer.append("{{")
+ buffer.append(uploadDetails.languageCode)
+ buffer.append("|1=")
+ buffer.append(uploadDetails.descriptionText)
+ buffer.append("}}, ")
+ }
+ }
+ buffer.deleteCharAt(buffer.length - 1)
+ buffer.deleteCharAt(buffer.length - 1)
+ buffer.append(descriptionEnd)
+ }
+ val returningIntent = Intent()
+ returningIntent.putExtra(UPDATED_WIKITEXT, buffer.toString())
+ returningIntent.putParcelableArrayListExtra(
+ LIST_OF_DESCRIPTION_AND_CAPTION,
+ uploadMediaDetails as ArrayList
+ )
+ setResult(RESULT_OK, returningIntent)
+ finish()
+ }
+
+ private fun showLoggingProgressBar() {
+ progressDialog = ProgressDialog(this)
+ progressDialog!!.isIndeterminate = true
+ progressDialog!!.setTitle(getString(R.string.updating_caption_title))
+ progressDialog!!.setMessage(getString(R.string.updating_caption_message))
+ progressDialog!!.setCanceledOnTouchOutside(false)
+ progressDialog!!.show()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/description/DescriptionEditHelper.java b/app/src/main/java/fr/free/nrw/commons/description/DescriptionEditHelper.java
new file mode 100644
index 000000000..05f6e9f2d
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/description/DescriptionEditHelper.java
@@ -0,0 +1,137 @@
+package fr.free.nrw.commons.description;
+
+import static fr.free.nrw.commons.notification.NotificationHelper.NOTIFICATION_EDIT_DESCRIPTION;
+
+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.actions.PageEditClient;
+import fr.free.nrw.commons.notification.NotificationHelper;
+import io.reactivex.Single;
+import java.util.Objects;
+import javax.inject.Inject;
+import javax.inject.Named;
+import timber.log.Timber;
+
+/**
+ * Helper class for edit and update given descriptions and showing notification upgradation
+ */
+public class DescriptionEditHelper {
+
+ /**
+ * notificationHelper: helps creating notification
+ */
+ private final NotificationHelper notificationHelper;
+ /**
+ * * pageEditClient: methods provided by this member posts the edited descriptions
+ * to the Media wiki api
+ */
+ public final PageEditClient pageEditClient;
+
+ @Inject
+ public DescriptionEditHelper(final NotificationHelper notificationHelper,
+ @Named("commons-page-edit") final PageEditClient pageEditClient) {
+ this.notificationHelper = notificationHelper;
+ this.pageEditClient = pageEditClient;
+ }
+
+ /**
+ * Replaces new descriptions
+ *
+ * @param context context
+ * @param media to be added
+ * @param appendText to be added
+ * @return Observable
+ */
+ public Single addDescription(final Context context, final Media media,
+ final String appendText) {
+ Timber.d("thread is description adding %s", Thread.currentThread().getName());
+ final String summary = "Updating Description";
+
+ return pageEditClient.edit(Objects.requireNonNull(media.getFilename()),
+ appendText, summary)
+ .flatMapSingle(result -> Single.just(showDescriptionEditNotification(context,
+ media, result)))
+ .firstOrError();
+ }
+
+ /**
+ * Adds new captions
+ *
+ * @param context context
+ * @param media to be added
+ * @param language to be added
+ * @param value to be added
+ * @return Observable
+ */
+ public Single addCaption(final Context context, final Media media,
+ final String language, final String value) {
+ Timber.d("thread is caption adding %s", Thread.currentThread().getName());
+ final String summary = "Updating Caption";
+
+ return pageEditClient.setCaptions(summary, Objects.requireNonNull(media.getFilename()),
+ language, value)
+ .flatMapSingle(result -> Single.just(showCaptionEditNotification(context,
+ media, result)))
+ .firstOrError();
+ }
+
+ /**
+ * Update captions and shows notification about captions update
+ * @param context to be added
+ * @param media to be added
+ * @param result to be added
+ * @return boolean
+ */
+ private boolean showCaptionEditNotification(final Context context, final Media media,
+ final int result) {
+ final String message;
+ String title = context.getString(R.string.caption_edit_helper_show_edit_title);
+
+ if (result == 1) {
+ title += ": " + context
+ .getString(R.string.coordinates_edit_helper_show_edit_title_success);
+ message = context.getString(R.string.caption_edit_helper_show_edit_message);
+ } else {
+ title += ": " + context.getString(R.string.caption_edit_helper_show_edit_title);
+ message = context.getString(R.string.caption_edit_helper_edit_message_else) ;
+ }
+
+ final String urlForFile = BuildConfig.COMMONS_URL + "/wiki/" + media.getFilename();
+ final Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(urlForFile));
+ notificationHelper.showNotification(context, title, message, NOTIFICATION_EDIT_DESCRIPTION,
+ browserIntent);
+ return result == 1;
+ }
+
+ /**
+ * Update descriptions and shows notification about descriptions update
+ * @param context to be added
+ * @param media to be added
+ * @param result to be added
+ * @return boolean
+ */
+ private boolean showDescriptionEditNotification(final Context context, final Media media,
+ final boolean result) {
+ final String message;
+ String title = context.getString(R.string.description_edit_helper_show_edit_title);
+
+ if (result) {
+ title += ": " + context
+ .getString(R.string.coordinates_edit_helper_show_edit_title_success);
+ message = context.getString(R.string.description_edit_helper_show_edit_message);
+ } else {
+ title += ": " + context.getString(R.string.description_edit_helper_show_edit_title);
+ message = context.getString(R.string.description_edit_helper_edit_message_else) ;
+ }
+
+ final String urlForFile = BuildConfig.COMMONS_URL + "/wiki/" + media.getFilename();
+ final Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(urlForFile));
+ notificationHelper.showNotification(context, title, message, NOTIFICATION_EDIT_DESCRIPTION,
+ browserIntent);
+ return result;
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/description/EditDescriptionConstants.kt b/app/src/main/java/fr/free/nrw/commons/description/EditDescriptionConstants.kt
new file mode 100644
index 000000000..7502a10ed
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/description/EditDescriptionConstants.kt
@@ -0,0 +1,10 @@
+package fr.free.nrw.commons.description
+
+/**
+ * For storing required constants for editing descriptions
+ */
+object EditDescriptionConstants {
+ const val LIST_OF_DESCRIPTION_AND_CAPTION = "description.descriptionAndCaption"
+ const val WIKITEXT = "description.wikiText"
+ const val UPDATED_WIKITEXT = "description.updatedWikiText";
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaClient.kt b/app/src/main/java/fr/free/nrw/commons/media/MediaClient.kt
index 5ca20b946..b4ab6ec01 100644
--- a/app/src/main/java/fr/free/nrw/commons/media/MediaClient.kt
+++ b/app/src/main/java/fr/free/nrw/commons/media/MediaClient.kt
@@ -157,6 +157,17 @@ class MediaClient @Inject constructor(
fun resetUserNameContinuation(userName: String) =
resetUserContinuation("user_", userName)
+ /**
+ * Get whole WikiText of required file
+ * @param title : Name of the file
+ * @return Observable
+ */
+ fun getCurrentWikiText(title: String): Single {
+ return mediaDetailInterface.getWikiText(title).map {
+ it.query()?.pages()?.get(0)?.revisions()?.get(0)?.content()
+ }
+ }
+
override fun responseMapper(
networkResult: Single,
key: String?
diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java
index 17e9581ce..4df44a79d 100644
--- a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java
+++ b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java
@@ -6,6 +6,9 @@ import static android.view.View.GONE;
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_UNCATEGORISED;
+import static fr.free.nrw.commons.description.EditDescriptionConstants.LIST_OF_DESCRIPTION_AND_CAPTION;
+import static fr.free.nrw.commons.description.EditDescriptionConstants.UPDATED_WIKITEXT;
+import static fr.free.nrw.commons.description.EditDescriptionConstants.WIKITEXT;
import android.annotation.SuppressLint;
import android.app.AlertDialog;
@@ -71,18 +74,23 @@ import fr.free.nrw.commons.contributions.ContributionsFragment;
import fr.free.nrw.commons.coordinates.CoordinateEditHelper;
import fr.free.nrw.commons.delete.DeleteHelper;
import fr.free.nrw.commons.delete.ReasonBuilder;
+import fr.free.nrw.commons.description.DescriptionEditActivity;
+import fr.free.nrw.commons.description.DescriptionEditHelper;
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
import fr.free.nrw.commons.explore.depictions.WikidataItemDetailsActivity;
import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.nearby.Label;
import fr.free.nrw.commons.profile.ProfileActivity;
import fr.free.nrw.commons.ui.widget.HtmlTextView;
+import fr.free.nrw.commons.upload.UploadMediaDetail;
import fr.free.nrw.commons.utils.ViewUtilWrapper;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import java.util.ArrayList;
import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -99,6 +107,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
CategoryEditHelper.Callback {
private static final int REQUEST_CODE = 1001 ;
+ private static final int REQUEST_CODE_EDIT_DESCRIPTION = 1002 ;
private boolean editable;
private boolean isCategoryImage;
private MediaDetailPagerFragment.MediaDetailProvider detailProvider;
@@ -136,6 +145,8 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
@Inject
CoordinateEditHelper coordinateEditHelper;
@Inject
+ DescriptionEditHelper descriptionEditHelper;
+ @Inject
ViewUtilWrapper viewUtil;
@Inject
CategoryClient categoryClient;
@@ -225,6 +236,10 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
String descriptionHtmlCode;
@BindView(R.id.progressBarDeletion)
ProgressBar progressBarDeletion;
+ @BindView(R.id.progressBarEdit)
+ ProgressBar progressBarEditDescription;
+ @BindView(R.id.description_edit)
+ Button editDescription;
private ArrayList categoryNames = new ArrayList<>();
private String categorySearchQuery;
@@ -819,8 +834,155 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
.build(getActivity()), REQUEST_CODE);
}
+ @OnClick(R.id.description_edit)
+ public void onDescriptionEditClicked() {
+ progressBarEditDescription.setVisibility(VISIBLE);
+ editDescription.setVisibility(GONE);
+ getDescriptionList();
+ }
+
/**
- * Get the coordinates and update the existing coordinates.
+ * Gets descriptions from wikitext
+ */
+ private void getDescriptionList() {
+ compositeDisposable.add(mediaDataExtractor.getCurrentWikiText(
+ Objects.requireNonNull(media.getFilename()))
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(this::extractCaptionDescription, Timber::e));
+ }
+
+ /**
+ * Gets captions and descriptions and merge them according to language code and arranges it in a
+ * single list.
+ * Send the list to DescriptionEditActivity
+ * @param s wikitext
+ */
+ private void extractCaptionDescription(final String s) {
+ final LinkedHashMap descriptions = getDescriptions(s);
+ final LinkedHashMap captions = getCaptionsList();
+
+ final ArrayList descriptionAndCaptions = new ArrayList<>();
+
+ if(captions.size() >= descriptions.size()) {
+ for (final Map.Entry mapElement : captions.entrySet()) {
+
+ final String language = (String) mapElement.getKey();
+ if (descriptions.containsKey(language)) {
+ descriptionAndCaptions.add(
+ new UploadMediaDetail(language,
+ Objects.requireNonNull(descriptions.get(language)),
+ (String) mapElement.getValue())
+ );
+ } else {
+ descriptionAndCaptions.add(
+ new UploadMediaDetail(language, "",
+ (String) mapElement.getValue())
+ );
+ }
+ }
+ for (final Map.Entry mapElement : descriptions.entrySet()) {
+
+ final String language = (String) mapElement.getKey();
+ if (!captions.containsKey(language)) {
+ descriptionAndCaptions.add(
+ new UploadMediaDetail(language,
+ Objects.requireNonNull(descriptions.get(language)),
+ "")
+ );
+ }
+ }
+ } else {
+ for (final Map.Entry mapElement : descriptions.entrySet()) {
+
+ final String language = (String) mapElement.getKey();
+ if (captions.containsKey(language)) {
+ descriptionAndCaptions.add(
+ new UploadMediaDetail(language, (String) mapElement.getValue(),
+ Objects.requireNonNull(captions.get(language)))
+ );
+ } else {
+ descriptionAndCaptions.add(
+ new UploadMediaDetail(language, (String) mapElement.getValue(),
+ "")
+ );
+ }
+ }
+ for (final Map.Entry mapElement : captions.entrySet()) {
+
+ final String language = (String) mapElement.getKey();
+ if (!descriptions.containsKey(language)) {
+ descriptionAndCaptions.add(
+ new UploadMediaDetail(language,
+ "",
+ Objects.requireNonNull(descriptions.get(language)))
+ );
+ }
+ }
+ }
+ final Intent intent = new Intent(requireContext(), DescriptionEditActivity.class);
+ final Bundle bundle = new Bundle();
+ bundle.putParcelableArrayList(LIST_OF_DESCRIPTION_AND_CAPTION, descriptionAndCaptions);
+ bundle.putString(WIKITEXT, s);
+ intent.putExtras(bundle);
+ startActivityForResult(intent, REQUEST_CODE_EDIT_DESCRIPTION);
+ }
+
+ /**
+ * Filters descriptions from current wikiText and arranges it in LinkedHashmap according to the
+ * language code
+ * @param s wikitext
+ * @return LinkedHashMap
+ */
+ private LinkedHashMap getDescriptions(String s) {
+ int descriptionIndex = s.indexOf("description=");
+ if(descriptionIndex == -1){
+ descriptionIndex = s.indexOf("Description=");
+ }
+
+ if( descriptionIndex == -1 ){
+ return new LinkedHashMap<>();
+ }
+ final String descriptionToEnd = s.substring(descriptionIndex+12);
+ final int descriptionEndIndex = descriptionToEnd.indexOf("\n");
+ final String description = s.substring(descriptionIndex+12, descriptionIndex+12+descriptionEndIndex);
+
+ final String[] arr = description.trim().split(",");
+ final LinkedHashMap descriptionList = new LinkedHashMap<>();
+
+ if (!description.equals("")) {
+ for (final String string :
+ arr) {
+ final int startCode = string.indexOf("{{");
+ final int endCode = string.indexOf("|");
+ final String languageCode = string.substring(startCode + 2, endCode).trim();
+ final int startDescription = string.indexOf("=");
+ final int endDescription = string.indexOf("}}");
+ final String languageDescription = string
+ .substring(startDescription + 1, endDescription);
+ descriptionList.put(languageCode, languageDescription);
+ }
+ }
+ return descriptionList;
+ }
+
+ /**
+ * Gets list of caption and arranges it in a LinkedHashmap according to the language code
+ * @return LinkedHashMap
+ */
+ private LinkedHashMap getCaptionsList() {
+ final LinkedHashMap captionList = new LinkedHashMap<>();
+ final Map captions = media.getCaptions();
+ for (final Map.Entry map : captions.entrySet()) {
+ final String language = map.getKey();
+ final String languageCaption = map.getValue();
+ captionList.put(language, languageCaption);
+ }
+ return captionList;
+ }
+
+ /**
+ * Get the result from another activity and act accordingly.
* @param requestCode
* @param resultCode
* @param data
@@ -854,13 +1016,61 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
updateCoordinates(latitude, longitude, accuracy);
}
}
- } else if (resultCode == RESULT_CANCELED) {
+
+ } else if (requestCode == REQUEST_CODE_EDIT_DESCRIPTION && resultCode == RESULT_OK) {
+ final String updatedWikiText = data.getStringExtra(UPDATED_WIKITEXT);
+ compositeDisposable.add(descriptionEditHelper.addDescription(getContext(), media,
+ updatedWikiText)
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(s -> {
+ Timber.d("Descriptions are added.");
+ }));
+
+ final ArrayList uploadMediaDetails
+ = data.getParcelableArrayListExtra(LIST_OF_DESCRIPTION_AND_CAPTION);
+
+ LinkedHashMap updatedCaptions = new LinkedHashMap<>();
+ for (UploadMediaDetail mediaDetail:
+ uploadMediaDetails) {
+ compositeDisposable.add(descriptionEditHelper.addCaption(getContext(), media,
+ mediaDetail.getLanguageCode(), mediaDetail.getCaptionText())
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(s -> {
+ updateCaptions(mediaDetail, updatedCaptions);
+ Timber.d("Caption is added.");
+ }));
+ }
+ progressBarEditDescription.setVisibility(GONE);
+ editDescription.setVisibility(VISIBLE);
+
+ } else if (requestCode == REQUEST_CODE && resultCode == RESULT_CANCELED) {
viewUtil.showShortToast(getContext(),
Objects.requireNonNull(getContext())
.getString(R.string.coordinates_picking_unsuccessful));
+
+ } else if (requestCode == REQUEST_CODE_EDIT_DESCRIPTION && resultCode == RESULT_CANCELED) {
+ progressBarEditDescription.setVisibility(GONE);
+ editDescription.setVisibility(VISIBLE);
+
+ viewUtil.showShortToast(getContext(),
+ Objects.requireNonNull(getContext())
+ .getString(R.string.descriptions_picking_unsuccessful));
}
}
+ /**
+ * Adds caption to the map and updates captions
+ * @param mediaDetail UploadMediaDetail
+ * @param updatedCaptions updated captionds
+ */
+ private void updateCaptions(UploadMediaDetail mediaDetail,
+ LinkedHashMap updatedCaptions) {
+ updatedCaptions.put(mediaDetail.getLanguageCode(), mediaDetail.getCaptionText());
+ media.setCaptions(updatedCaptions);
+ }
+
@OnClick(R.id.update_categories_button)
public void onUpdateCategoriesClicked() {
updateCategories(categoryEditSearchRecyclerViewAdapter.getNewCategories());
diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailInterface.java b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailInterface.java
index 86be5c875..f595f5a83 100644
--- a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailInterface.java
+++ b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailInterface.java
@@ -2,6 +2,8 @@ package fr.free.nrw.commons.media;
import io.reactivex.Observable;
import io.reactivex.Single;
+import org.wikipedia.dataclient.Service;
+import org.wikipedia.dataclient.mwapi.MwQueryResponse;
import org.wikipedia.wikidata.Entities;
import retrofit2.Call;
import retrofit2.http.GET;
@@ -35,4 +37,18 @@ public interface MediaDetailInterface {
*/
@GET("/w/api.php?action=wbgetentities&props=labels&format=json&languagefallback=1&sites=commonswiki")
Observable getEntityForImage(@Query("languages") String language, @Query("ids") String wikibaseIdentifier);
+
+ /**
+ * Fetches current wikitext
+ * @param title file name
+ * @return Single
+ */
+ @GET(
+ Service.MW_API_PREFIX +
+ "action=query&prop=revisions&rvprop=content|timestamp&rvlimit=1&converttitles="
+ )
+ Single getWikiText(
+ @Query("titles") String title
+ );
+
}
diff --git a/app/src/main/java/fr/free/nrw/commons/notification/NotificationHelper.java b/app/src/main/java/fr/free/nrw/commons/notification/NotificationHelper.java
index a23f350bd..f7e3592a0 100644
--- a/app/src/main/java/fr/free/nrw/commons/notification/NotificationHelper.java
+++ b/app/src/main/java/fr/free/nrw/commons/notification/NotificationHelper.java
@@ -26,6 +26,7 @@ public class NotificationHelper {
public static final int NOTIFICATION_DELETE = 1;
public static final int NOTIFICATION_EDIT_CATEGORY = 2;
public static final int NOTIFICATION_EDIT_COORDINATES = 3;
+ public static final int NOTIFICATION_EDIT_DESCRIPTION = 4;
private NotificationManager notificationManager;
private NotificationCompat.Builder notificationBuilder;
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadMediaDetail.kt b/app/src/main/java/fr/free/nrw/commons/upload/UploadMediaDetail.kt
index 160f135ea..9ca587800 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/UploadMediaDetail.kt
+++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadMediaDetail.kt
@@ -1,10 +1,13 @@
package fr.free.nrw.commons.upload
+import android.os.Parcelable
import fr.free.nrw.commons.nearby.Place
+import kotlinx.android.parcel.Parcelize
/**
* Holds a description of an item being uploaded by [UploadActivity]
*/
+@Parcelize
data class UploadMediaDetail constructor(
/**
* @return The language code ie. "en" or "fr"
@@ -15,7 +18,7 @@ data class UploadMediaDetail constructor(
var languageCode: String? = null,
var descriptionText: String = "",
var captionText: String = ""
-) {
+) : Parcelable {
fun javaCopy() = copy()
constructor(place: Place) : this(
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadMediaDetailAdapter.java b/app/src/main/java/fr/free/nrw/commons/upload/UploadMediaDetailAdapter.java
index 0eff105e8..289c816bb 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/UploadMediaDetailAdapter.java
+++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadMediaDetailAdapter.java
@@ -37,6 +37,13 @@ public class UploadMediaDetailAdapter extends RecyclerView.Adapter uploadMediaDetails) {
+ this.uploadMediaDetails = uploadMediaDetails;
+ selectedLanguages = new HashMap<>();
+ this.savedLanguageValue = savedLanguageValue;
+ }
+
public void setCallback(Callback callback) {
this.callback = callback;
}
@@ -51,6 +58,10 @@ public class UploadMediaDetailAdapter extends RecyclerView.Adapter getItems(){
+ return uploadMediaDetails;
+ }
+
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
@@ -178,14 +189,14 @@ public class UploadMediaDetailAdapter extends RecyclerView.Adapter
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_media_detail.xml b/app/src/main/res/layout/fragment_media_detail.xml
index cfbb01094..393981e1c 100644
--- a/app/src/main/res/layout/fragment_media_detail.xml
+++ b/app/src/main/res/layout/fragment_media_detail.xml
@@ -204,16 +204,44 @@
android:textSize="@dimen/description_text_size"
android:textIsSelectable="true"
tools:text="Description of the media goes here. This can potentially be fairly long, and will need to wrap across multiple lines. We hope it looks nice though." />
+
-
+ android:orientation="horizontal">
+
+
+
+
+
+
Sign up
Logging in
Please wait…
+ Updating captions and descriptions
+ Please wait…
Login success!
Login failed!
File not found. Please try another file.
@@ -371,7 +373,7 @@
Next
Previous
Submit
- A file with the file name %1$s exists. Are you sure you want to proceed?\n\nNote: A suitable suffix will be added to the file name automatically.
+ A file with the file name %1$s exists. Are you sure you want to proceed?\n\nNote: A suitable suffix will be added to the file name automatically.
No compatible map application could be found on your device. Please install a map application to use this feature.
Pictures
Locations
@@ -521,10 +523,18 @@ Upload your first media by tapping on the add button.
Trying to update coordinates.
Coordinates update
+ Description update
+ Caption update
Success
Coordinates %1$s are added.
+ Descriptions are added.
+ Caption is added.
Could not add coordinates.
+ Could not add descriptions.
+ Could not add caption.
Unable to get coordinates.
+ Unable to get descriptions.
+ Edit descriptions and captions
Share image via
You haven\'t made any contributions yet
diff --git a/app/src/test/kotlin/fr/free/nrw/commons/MediaDataExtractorTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/MediaDataExtractorTest.kt
index 281afbe4f..2045c2731 100644
--- a/app/src/test/kotlin/fr/free/nrw/commons/MediaDataExtractorTest.kt
+++ b/app/src/test/kotlin/fr/free/nrw/commons/MediaDataExtractorTest.kt
@@ -1,5 +1,6 @@
package fr.free.nrw.commons
+import com.nhaarman.mockitokotlin2.whenever
import fr.free.nrw.commons.media.MediaClient
import io.reactivex.Single
import org.junit.Assert.assertTrue
@@ -11,6 +12,7 @@ import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.mockito.Mockito.mock
import org.mockito.MockitoAnnotations
+import org.wikipedia.dataclient.mwapi.MwQueryResponse
/**
* Test methods in media data extractor
@@ -49,4 +51,10 @@ class MediaDataExtractorTest {
//assertTrue(fetchMediaDetails is Media)
}
+
+ @Test
+ fun getWikiText() {
+ `when`(mediaDataExtractor?.getCurrentWikiText(ArgumentMatchers.anyString()))
+ .thenReturn(Single.just("Test"))
+ }
}
\ No newline at end of file
diff --git a/app/src/test/kotlin/fr/free/nrw/commons/actions/PageEditClientTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/actions/PageEditClientTest.kt
index 5477d9549..994ab5593 100644
--- a/app/src/test/kotlin/fr/free/nrw/commons/actions/PageEditClientTest.kt
+++ b/app/src/test/kotlin/fr/free/nrw/commons/actions/PageEditClientTest.kt
@@ -81,4 +81,15 @@ class PageEditClientTest {
pageEditClient.prependEdit("test", "test", "test")
verify(pageEditInterface).postPrependEdit(eq("test"), eq("test"), eq("test"), eq("test"))
}
+
+ /**
+ * Test setCaptions
+ */
+ @Test
+ fun testSetCaptions() {
+ Mockito.`when`(csrfTokenClient.tokenBlocking).thenReturn("test")
+ pageEditClient.setCaptions("test", "test", "en", "test")
+ verify(pageEditInterface).postCaptions(eq("test"), eq("test"), eq("en"),
+ eq("test"), eq("test"))
+ }
}
\ No newline at end of file
diff --git a/app/src/test/kotlin/fr/free/nrw/commons/media/MediaClientTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/media/MediaClientTest.kt
index d6b09a11b..81ec11806 100644
--- a/app/src/test/kotlin/fr/free/nrw/commons/media/MediaClientTest.kt
+++ b/app/src/test/kotlin/fr/free/nrw/commons/media/MediaClientTest.kt
@@ -199,6 +199,12 @@ class MediaClientTest {
mediaClient.doesPageContainMedia("").test().assertValue(true)
}
+ @Test
+ fun getWikiText() {
+ val wikiText = mock()
+ whenever(mediaDetailInterface.getWikiText("File:Test.jpg")).thenReturn(Single.just(wikiText))
+ }
+
private fun mockQuery(queryReceiver: MwQueryResult.() -> Unit): MwQueryResponse {
val mwQueryResponse = mock()
val mwQueryResult = mock()
diff --git a/app/src/test/kotlin/fr/free/nrw/commons/media/MediaDetailFragmentUnitTests.kt b/app/src/test/kotlin/fr/free/nrw/commons/media/MediaDetailFragmentUnitTests.kt
index e54762405..b29d91fd9 100644
--- a/app/src/test/kotlin/fr/free/nrw/commons/media/MediaDetailFragmentUnitTests.kt
+++ b/app/src/test/kotlin/fr/free/nrw/commons/media/MediaDetailFragmentUnitTests.kt
@@ -1,6 +1,8 @@
package fr.free.nrw.commons.media
+import android.app.Activity
import android.content.Context
+import android.content.Intent
import android.content.res.Configuration
import android.os.Bundle
import android.view.LayoutInflater
@@ -49,6 +51,9 @@ import kotlin.collections.HashMap
@LooperMode(LooperMode.Mode.PAUSED)
class MediaDetailFragmentUnitTests {
+
+ private val REQUEST_CODE = 1001
+ private val REQUEST_CODE_EDIT_DESCRIPTION = 1002
private lateinit var fragment: MediaDetailFragment
private lateinit var fragmentManager: FragmentManager
private lateinit var layoutInflater: LayoutInflater
@@ -106,6 +111,9 @@ class MediaDetailFragmentUnitTests {
@Mock
private lateinit var searchView: SearchView
+ @Mock
+ private lateinit var intent: Intent
+
@Before
fun setUp() {
@@ -140,6 +148,7 @@ class MediaDetailFragmentUnitTests {
Whitebox.setInternalState(fragment, "media", media)
Whitebox.setInternalState(fragment, "progressBar", progressBar)
+ Whitebox.setInternalState(fragment, "progressBarEditDescription", progressBar)
Whitebox.setInternalState(fragment, "captionsListView", listView)
Whitebox.setInternalState(fragment, "descriptionWebView", webView)
Whitebox.setInternalState(fragment, "detailProvider", detailProvider)
@@ -159,6 +168,7 @@ class MediaDetailFragmentUnitTests {
Whitebox.setInternalState(fragment, "dummyCategoryEditContainer", linearLayout)
Whitebox.setInternalState(fragment, "showCaptionAndDescriptionContainer", linearLayout)
Whitebox.setInternalState(fragment, "updateCategoriesButton", button)
+ Whitebox.setInternalState(fragment, "editDescription", button)
Whitebox.setInternalState(fragment, "categoryContainer", linearLayout)
Whitebox.setInternalState(fragment, "categorySearchView", searchView)
Whitebox.setInternalState(fragment, "mediaDiscussion", textView)
@@ -188,6 +198,24 @@ class MediaDetailFragmentUnitTests {
fragment.onCreateView(layoutInflater, null, savedInstanceState)
}
+ @Test
+ @Throws(Exception::class)
+ fun testOnActivityResultLocationPickerActivity() {
+ fragment.onActivityResult(REQUEST_CODE, Activity.RESULT_CANCELED, intent)
+ }
+
+ @Test
+ @Throws(Exception::class)
+ fun `test OnActivity Result Cancelled LocationPickerActivity`() {
+ fragment.onActivityResult(REQUEST_CODE, Activity.RESULT_CANCELED, intent)
+ }
+
+ @Test
+ @Throws(Exception::class)
+ fun `test OnActivity Result Cancelled DescriptionEditActivity`() {
+ fragment.onActivityResult(REQUEST_CODE, Activity.RESULT_OK, intent)
+ }
+
@Test
@Throws(Exception::class)
fun testOnSaveInstanceState() {
@@ -252,6 +280,17 @@ class MediaDetailFragmentUnitTests {
method.invoke(fragment)
}
+ @Test
+ @Throws(Exception::class)
+ fun testGetDescriptionList() {
+ `when`(media.filename).thenReturn("")
+ val method: Method = MediaDetailFragment::class.java.getDeclaredMethod(
+ "getDescriptionList"
+ )
+ method.isAccessible = true
+ method.invoke(fragment)
+ }
+
@Test
@Throws(Exception::class)
fun testGetCaptions() {
@@ -301,6 +340,15 @@ class MediaDetailFragmentUnitTests {
Assert.assertEquals(fragment.updateCategoryDisplay(listOf()), true)
}
+ @Test
+ @Throws(Exception::class)
+ fun testDescriptionEditClicked() {
+ `when`(progressBar.visibility).thenReturn(View.VISIBLE)
+ `when`(button.visibility).thenReturn(View.GONE)
+ `when`(media.filename).thenReturn("")
+ fragment.onDescriptionEditClicked()
+ }
+
@Test
@Throws(Exception::class)
fun testShowCaptionAndDescriptionCaseVisible() {
@@ -397,6 +445,28 @@ class MediaDetailFragmentUnitTests {
method.invoke(fragment, "mock")
}
+ @Test
+ @Throws(Exception::class)
+ fun testExtractCaptionDescription() {
+ val method: Method = MediaDetailFragment::class.java.getDeclaredMethod(
+ "extractCaptionDescription",
+ String::class.java
+ )
+ method.isAccessible = true
+ method.invoke(fragment, "mock")
+ }
+
+ @Test
+ @Throws(Exception::class)
+ fun testGetDescriptions() {
+ val method: Method = MediaDetailFragment::class.java.getDeclaredMethod(
+ "getDescriptions",
+ String::class.java
+ )
+ method.isAccessible = true
+ method.invoke(fragment, "mock")
+ }
+
@Test
@Throws(Exception::class)
fun testPrettyCaptionCaseEmpty() {