From 0269894c64078a42337efa1c487a3f7e0b2c2b96 Mon Sep 17 00:00:00 2001 From: Ayan Sarkar <71203077+Ayan-10@users.noreply.github.com> Date: Tue, 7 Dec 2021 01:20:54 +0530 Subject: [PATCH] Fix 4615: Option for editing caption and description (#4672) * DescriptionEditHelper implemented * Description extracted * Description editable * No description condition handled * Code cleanup * Added javadocs * toolbar added * API call done * Caption edit available * Progress dialog added * Log * Problem with ButterKnife * Caption is editable * Removed unused import * Manifest file reverted * Manifest file reverted * Manifest file reverted * View binding added * Post operation test added * Java docs added * Java docs added * MediaDetailFragment unit tests added * Test added --- app/src/main/AndroidManifest.xml | 4 + .../fr/free/nrw/commons/MediaDataExtractor.kt | 5 + .../nrw/commons/actions/PageEditClient.kt | 18 ++ .../nrw/commons/actions/PageEditInterface.kt | 13 ++ .../description/DescriptionEditActivity.kt | 181 +++++++++++++++ .../description/DescriptionEditHelper.java | 137 +++++++++++ .../description/EditDescriptionConstants.kt | 10 + .../fr/free/nrw/commons/media/MediaClient.kt | 11 + .../commons/media/MediaDetailFragment.java | 214 +++++++++++++++++- .../commons/media/MediaDetailInterface.java | 16 ++ .../notification/NotificationHelper.java | 1 + .../nrw/commons/upload/UploadMediaDetail.kt | 5 +- .../upload/UploadMediaDetailAdapter.java | 27 ++- .../res/layout/activity_description_edit.xml | 80 +++++++ .../main/res/layout/fragment_media_detail.xml | 40 +++- app/src/main/res/values/strings.xml | 12 +- .../nrw/commons/MediaDataExtractorTest.kt | 8 + .../nrw/commons/actions/PageEditClientTest.kt | 11 + .../free/nrw/commons/media/MediaClientTest.kt | 6 + .../media/MediaDetailFragmentUnitTests.kt | 70 ++++++ 20 files changed, 855 insertions(+), 14 deletions(-) create mode 100644 app/src/main/java/fr/free/nrw/commons/description/DescriptionEditActivity.kt create mode 100644 app/src/main/java/fr/free/nrw/commons/description/DescriptionEditHelper.java create mode 100644 app/src/main/java/fr/free/nrw/commons/description/EditDescriptionConstants.kt create mode 100644 app/src/main/res/layout/activity_description_edit.xml 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"> + + + +