diff --git a/app/src/main/java/fr/free/nrw/commons/Media.java b/app/src/main/java/fr/free/nrw/commons/Media.java index 214660334..b3e18fe11 100644 --- a/app/src/main/java/fr/free/nrw/commons/Media.java +++ b/app/src/main/java/fr/free/nrw/commons/Media.java @@ -518,6 +518,14 @@ public class Media implements Parcelable { this.coordinates = coordinates; } + /** + * Returns wikicode to use the media file on a MediaWiki site + * @return + */ + public String getWikiCode() { + return String.format("[[%s|thumb|%s]]", filename, thumbnailTitle); + } + /** * Sets the categories the file falls under. *

diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/Contribution.java b/app/src/main/java/fr/free/nrw/commons/contributions/Contribution.java index 5461ccfca..4b969be03 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/Contribution.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/Contribution.java @@ -39,7 +39,7 @@ public class Contribution extends Media { * and value is the caption in the corresponding language Ex: key = "en", value: "" key = "de" , value: "" */ - private Map captions = new HashMap<>(); + private HashMap captions = new HashMap<>(); public Contribution() { } @@ -51,7 +51,7 @@ public class Contribution extends Media { UploadMediaDetail.formatList(item.getUploadMediaDetails()), sessionManager.getAuthorName(), categories); - captions = UploadMediaDetail.formatCaptions(item.getUploadMediaDetails()); + captions = new HashMap<>(UploadMediaDetail.formatCaptions(item.getUploadMediaDetails())); decimalCoords = item.getGpsCoords().getDecimalCoords(); dateCreatedSource = ""; this.depictedItems = depictedItems; @@ -127,11 +127,11 @@ public class Contribution extends Media { *

* returns list of captions stored in hashmap */ - public Map getCaptions() { + public HashMap getCaptions() { return captions; } - public void setCaptions(Map captions) { + public void setCaptions(HashMap captions) { this.captions = captions; } @@ -147,7 +147,7 @@ public class Contribution extends Media { dest.writeLong(transferred); dest.writeString(decimalCoords); dest.writeString(dateCreatedSource); - dest.writeSerializable((HashMap) captions); + dest.writeSerializable(captions); } /** diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionDao.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionDao.java index e288a84af..20de34eb9 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionDao.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionDao.java @@ -53,6 +53,9 @@ public abstract class ContributionDao { @Query("SELECT * from contribution WHERE filename=:fileName") public abstract List getContributionWithTitle(String fileName); + @Query("SELECT * from contribution WHERE pageId=:pageId") + public abstract Contribution getContribution(String pageId); + @Query("UPDATE contribution SET state=:state WHERE state in (:toUpdateStates)") public abstract Single updateStates(int state, int[] toUpdateStates); diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionViewHolder.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionViewHolder.java index a5de53bb1..1064d86ca 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionViewHolder.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionViewHolder.java @@ -5,8 +5,10 @@ import static fr.free.nrw.commons.depictions.Media.DepictedImagesFragment.PAGE_I import android.net.Uri; import android.text.TextUtils; import android.view.View; +import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.ProgressBar; +import android.widget.RelativeLayout; import android.widget.TextView; import androidx.annotation.Nullable; import androidx.recyclerview.widget.RecyclerView; @@ -22,147 +24,203 @@ import fr.free.nrw.commons.media.MediaClient; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.CompositeDisposable; import io.reactivex.schedulers.Schedulers; +import org.wikipedia.dataclient.WikiSite; import timber.log.Timber; public class ContributionViewHolder extends RecyclerView.ViewHolder { - private final Callback callback; - @BindView(R.id.contributionImage) - SimpleDraweeView imageView; - @BindView(R.id.contributionTitle) TextView titleView; - @BindView(R.id.contributionState) TextView stateView; - @BindView(R.id.contributionSequenceNumber) TextView seqNumView; - @BindView(R.id.contributionProgress) ProgressBar progressView; - @BindView(R.id.failed_image_options) LinearLayout failedImageOptions; + private final Callback callback; + @BindView(R.id.contributionImage) + SimpleDraweeView imageView; + @BindView(R.id.contributionTitle) + TextView titleView; + @BindView(R.id.contributionState) + TextView stateView; + @BindView(R.id.contributionSequenceNumber) + TextView seqNumView; + @BindView(R.id.contributionProgress) + ProgressBar progressView; + @BindView(R.id.image_options) + RelativeLayout imageOptions; + @BindView(R.id.wikipediaButton) + ImageButton addToWikipediaButton; + @BindView(R.id.retryButton) + ImageButton retryButton; + @BindView(R.id.cancelButton) + ImageButton cancelButton; - private int position; - private Contribution contribution; - private final CompositeDisposable compositeDisposable = new CompositeDisposable(); - private final MediaClient mediaClient; + private int position; + private Contribution contribution; + private final CompositeDisposable compositeDisposable = new CompositeDisposable(); + private final MediaClient mediaClient; - ContributionViewHolder(final View parent, final Callback callback, - final MediaClient mediaClient) { - super(parent); - this.mediaClient = mediaClient; - ButterKnife.bind(this, parent); - this.callback=callback; + ContributionViewHolder(final View parent, final Callback callback, + final MediaClient mediaClient) { + super(parent); + this.mediaClient = mediaClient; + ButterKnife.bind(this, parent); + this.callback = callback; + } + + public void init(final int position, final Contribution contribution) { + this.contribution = contribution; + fetchAndDisplayCaption(contribution); + this.position = position; + final String imageSource = chooseImageSource(contribution.getThumbUrl(), + contribution.getLocalUri()); + if (!TextUtils.isEmpty(imageSource)) { + final ImageRequest imageRequest = + ImageRequestBuilder.newBuilderWithSource(Uri.parse(imageSource)) + .setProgressiveRenderingEnabled(true) + .build(); + imageView.setImageRequest(imageRequest); } - public void init(final int position, final Contribution contribution) { - this.contribution = contribution; - fetchAndDisplayCaption(contribution); - this.position = position; - final String imageSource = chooseImageSource(contribution.getThumbUrl(), contribution.getLocalUri()); - if (!TextUtils.isEmpty(imageSource)) { - final ImageRequest imageRequest = - ImageRequestBuilder.newBuilderWithSource(Uri.parse(imageSource)) - .setProgressiveRenderingEnabled(true) - .build(); - imageView.setImageRequest(imageRequest); - } + seqNumView.setText(String.valueOf(position + 1)); + seqNumView.setVisibility(View.VISIBLE); - seqNumView.setText(String.valueOf(position + 1)); - seqNumView.setVisibility(View.VISIBLE); - - switch (contribution.getState()) { - case Contribution.STATE_COMPLETED: - stateView.setVisibility(View.GONE); - progressView.setVisibility(View.GONE); - failedImageOptions.setVisibility(View.GONE); - stateView.setText(""); - break; - case Contribution.STATE_QUEUED: - stateView.setVisibility(View.VISIBLE); - progressView.setVisibility(View.GONE); - stateView.setText(R.string.contribution_state_queued); - failedImageOptions.setVisibility(View.GONE); - break; - case Contribution.STATE_IN_PROGRESS: - stateView.setVisibility(View.GONE); - progressView.setVisibility(View.VISIBLE); - failedImageOptions.setVisibility(View.GONE); - final long total = contribution.getDataLength(); - final long transferred = contribution.getTransferred(); - if (transferred == 0 || transferred >= total) { - progressView.setIndeterminate(true); - } else { - progressView.setProgress((int)(((double)transferred / (double)total) * 100)); - } - break; - case Contribution.STATE_FAILED: - stateView.setVisibility(View.VISIBLE); - stateView.setText(R.string.contribution_state_failed); - progressView.setVisibility(View.GONE); - failedImageOptions.setVisibility(View.VISIBLE); - break; - } - } - - /** - * In contributions first we show the title for the image stored in cache, - * then we fetch captions associated with the image and replace title on the thumbnail with caption - * - * @param contribution - */ - private void fetchAndDisplayCaption(final Contribution contribution) { - if ((contribution.getState() != Contribution.STATE_COMPLETED)) { - titleView.setText(contribution.getDisplayTitle()); + addToWikipediaButton.setVisibility(View.GONE); + switch (contribution.getState()) { + case Contribution.STATE_COMPLETED: + stateView.setVisibility(View.GONE); + progressView.setVisibility(View.GONE); + imageOptions.setVisibility(View.GONE); + stateView.setText(""); + checkIfMediaExistsOnWikipediaPage(contribution); + break; + case Contribution.STATE_QUEUED: + stateView.setVisibility(View.VISIBLE); + progressView.setVisibility(View.GONE); + stateView.setText(R.string.contribution_state_queued); + imageOptions.setVisibility(View.GONE); + break; + case Contribution.STATE_IN_PROGRESS: + stateView.setVisibility(View.GONE); + progressView.setVisibility(View.VISIBLE); + imageOptions.setVisibility(View.GONE); + final long total = contribution.getDataLength(); + final long transferred = contribution.getTransferred(); + if (transferred == 0 || transferred >= total) { + progressView.setIndeterminate(true); } else { - final String pageId = contribution.getPageId(); - if (pageId != null) { - Timber.d("Fetching caption for %s", contribution.getFilename()); - final String wikibaseMediaId = PAGE_ID_PREFIX - + pageId; // Create Wikibase media id from the page id. Example media id: M80618155 for https://commons.wikimedia.org/wiki/File:Tantanmen.jpeg with has the pageid 80618155 - compositeDisposable.add(mediaClient.getCaptionByWikibaseIdentifier(wikibaseMediaId) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(subscriber -> { - if (!subscriber.trim().equals(MediaClient.NO_CAPTION)) { - titleView.setText(subscriber); - } else { - titleView.setText(contribution.getDisplayTitle()); - } - })); - } else { - titleView.setText(contribution.getDisplayTitle()); - } + progressView.setProgress((int) (((double) transferred / (double) total) * 100)); } + break; + case Contribution.STATE_FAILED: + stateView.setVisibility(View.VISIBLE); + stateView.setText(R.string.contribution_state_failed); + progressView.setVisibility(View.GONE); + imageOptions.setVisibility(View.VISIBLE); + break; } + } - /** - * Returns the image source for the image view, first preference is given to thumbUrl if that is - * null, moves to local uri and if both are null return null - * - * @param thumbUrl - * @param localUri - * @return - */ - @Nullable - private String chooseImageSource(final String thumbUrl, final Uri localUri) { - return !TextUtils.isEmpty(thumbUrl) ? thumbUrl : - localUri != null ? localUri.toString() : - null; + /** + * In contributions first we show the title for the image stored in cache, then we fetch captions + * associated with the image and replace title on the thumbnail with caption + * + * @param contribution + */ + private void fetchAndDisplayCaption(final Contribution contribution) { + if ((contribution.getState() != Contribution.STATE_COMPLETED)) { + titleView.setText(contribution.getDisplayTitle()); + } else { + final String pageId = contribution.getPageId(); + if (pageId != null) { + Timber.d("Fetching caption for %s", contribution.getFilename()); + final String wikibaseMediaId = PAGE_ID_PREFIX + + pageId; // Create Wikibase media id from the page id. Example media id: M80618155 for https://commons.wikimedia.org/wiki/File:Tantanmen.jpeg with has the pageid 80618155 + compositeDisposable.add(mediaClient.getCaptionByWikibaseIdentifier(wikibaseMediaId) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(subscriber -> { + if (!subscriber.trim().equals(MediaClient.NO_CAPTION)) { + titleView.setText(subscriber); + } else { + titleView.setText(contribution.getDisplayTitle()); + } + })); + } else { + titleView.setText(contribution.getDisplayTitle()); + } } + } - /** - * Retry upload when it is failed - */ - @OnClick(R.id.retryButton) - public void retryUpload() { - callback.retryUpload(contribution); + /** + * Checks if a media exists on the corresponding Wikipedia article Currently the check is made for + * the device's current language Wikipedia + * + * @param contribution + */ + private void checkIfMediaExistsOnWikipediaPage(final Contribution contribution) { + if (contribution.getWikidataPlace() == null + || contribution.getWikidataPlace().getWikipediaArticle() == null) { + return; } + final String wikipediaArticle = contribution.getWikidataPlace().getWikipediaPageTitle(); + compositeDisposable.add(mediaClient.doesPageContainMedia(wikipediaArticle) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(mediaExists -> { + displayWikipediaButton(mediaExists); + })); + } - /** - * Delete a failed upload attempt - */ - @OnClick(R.id.cancelButton) - public void deleteUpload() { - callback.deleteUpload(contribution); + /** + * Handle action buttons visibility if the corresponding wikipedia page doesn't contain any media. + * This method needs to control the state of just the scenario where media does not exists as + * other scenarios are already handled in the init method. + * + * @param mediaExists + */ + private void displayWikipediaButton(Boolean mediaExists) { + if (!mediaExists) { + addToWikipediaButton.setVisibility(View.VISIBLE); + cancelButton.setVisibility(View.GONE); + retryButton.setVisibility(View.GONE); + imageOptions.setVisibility(View.VISIBLE); } + } - @OnClick(R.id.contributionImage) - public void imageClicked(){ - callback.openMediaDetail(position); - } + /** + * Returns the image source for the image view, first preference is given to thumbUrl if that is + * null, moves to local uri and if both are null return null + * + * @param thumbUrl + * @param localUri + * @return + */ + @Nullable + private String chooseImageSource(final String thumbUrl, final Uri localUri) { + return !TextUtils.isEmpty(thumbUrl) ? thumbUrl : + localUri != null ? localUri.toString() : + null; + } + + /** + * Retry upload when it is failed + */ + @OnClick(R.id.retryButton) + public void retryUpload() { + callback.retryUpload(contribution); + } + + /** + * Delete a failed upload attempt + */ + @OnClick(R.id.cancelButton) + public void deleteUpload() { + callback.deleteUpload(contribution); + } + + @OnClick(R.id.contributionImage) + public void imageClicked() { + callback.openMediaDetail(position); + } + + @OnClick(R.id.wikipediaButton) + public void wikipediaButtonClicked() { + callback.addImageToWikipedia(contribution); + } } diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListAdapter.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListAdapter.java index a0c8e1088..0b2556f0e 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListAdapter.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListAdapter.java @@ -7,8 +7,9 @@ import androidx.paging.PagedListAdapter; import androidx.recyclerview.widget.DiffUtil; import fr.free.nrw.commons.R; import fr.free.nrw.commons.media.MediaClient; +import org.wikipedia.dataclient.WikiSite; -/** + /** * Represents The View Adapter for the List of Contributions */ public class ContributionsListAdapter extends @@ -64,7 +65,8 @@ public class ContributionsListAdapter extends final int viewType) { final ContributionViewHolder viewHolder = new ContributionViewHolder( LayoutInflater.from(parent.getContext()) - .inflate(R.layout.layout_contribution, parent, false), callback, mediaClient); + .inflate(R.layout.layout_contribution, parent, false), + callback, mediaClient); return viewHolder; } @@ -75,5 +77,7 @@ public class ContributionsListAdapter extends void deleteUpload(Contribution contribution); void openMediaDetail(int contribution); + + void addImageToWikipedia(Contribution contribution); } } diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java index ad859fd36..e1bb1968e 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java @@ -2,9 +2,11 @@ package fr.free.nrw.commons.contributions; import static android.view.View.GONE; import static android.view.View.VISIBLE; +import static fr.free.nrw.commons.di.NetworkingModule.NAMED_LANGUAGE_WIKI_PEDIA_WIKI_SITE; import android.content.Context; import android.content.res.Configuration; +import android.net.Uri; import android.os.Bundle; import android.os.Parcelable; import android.view.LayoutInflater; @@ -17,25 +19,29 @@ import android.widget.ProgressBar; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.fragment.app.FragmentManager; import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.RecyclerView; -import androidx.recyclerview.widget.RecyclerView.LayoutManager; import butterknife.BindView; import butterknife.ButterKnife; import com.google.android.material.floatingactionbutton.FloatingActionButton; import fr.free.nrw.commons.Media; import fr.free.nrw.commons.R; +import fr.free.nrw.commons.Utils; import fr.free.nrw.commons.di.CommonsDaggerSupportFragment; import fr.free.nrw.commons.media.MediaClient; -import fr.free.nrw.commons.media.MediaDetailPagerFragment; +import fr.free.nrw.commons.utils.DialogUtil; +import java.util.Locale; import javax.inject.Inject; +import javax.inject.Named; +import org.wikipedia.dataclient.WikiSite; /** * Created by root on 01.06.2018. */ public class ContributionsListFragment extends CommonsDaggerSupportFragment implements - ContributionsListContract.View, ContributionsListAdapter.Callback { + ContributionsListContract.View, ContributionsListAdapter.Callback, WikipediaInstructionsDialogFragment.Callback { private static final String RV_STATE = "rv_scroll_state"; @@ -59,6 +65,10 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl @Inject MediaClient mediaClient; + @Named(NAMED_LANGUAGE_WIKI_PEDIA_WIKI_SITE) + @Inject + WikiSite languageWikipediaSite; + @Inject ContributionsListPresenter contributionsListPresenter; @@ -200,7 +210,8 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl @Override public void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); - final GridLayoutManager layoutManager = (GridLayoutManager) rvContributionsList.getLayoutManager(); + final GridLayoutManager layoutManager = (GridLayoutManager) rvContributionsList + .getLayoutManager(); outState.putParcelable(RV_STATE, layoutManager.onSaveInstanceState()); } @@ -232,6 +243,39 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl } } + /** + * Handle callback for wikipedia icon clicked + * + * @param contribution + */ + @Override + public void addImageToWikipedia(Contribution contribution) { + DialogUtil.showAlertDialog(getActivity(), + getString(R.string.add_picture_to_wikipedia_article_title), + String.format(getString(R.string.add_picture_to_wikipedia_article_desc), + Locale.getDefault().getDisplayLanguage()), + () -> { + showAddImageToWikipediaInstructions(contribution); + }, () -> { + // do nothing + }); + } + + /** + * Display confirmation dialog with instructions when the user tries to add image to wikipedia + * + * @param contribution + */ + private void showAddImageToWikipediaInstructions(Contribution contribution) { + FragmentManager fragmentManager = getFragmentManager(); + WikipediaInstructionsDialogFragment fragment = WikipediaInstructionsDialogFragment + .newInstance(contribution); + fragment.setCallback(this::onConfirmClicked); + fragment.show(fragmentManager, "WikimediaFragment"); + } + + + public Media getMediaAtPosition(final int i) { return adapter.getContributionForPosition(i); } @@ -240,6 +284,23 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl return adapter.getItemCount(); } + /** + * Open the editor for the language Wikipedia + * + * @param contribution + */ + @Override + public void onConfirmClicked(@Nullable Contribution contribution, boolean copyWikicode) { + if(copyWikicode) { + String wikicode = contribution.getWikiCode(); + Utils.copy("wikicode", wikicode, getContext()); + } + + final String url = languageWikipediaSite.mobileUrl() + "/wiki/" + contribution.getWikidataPlace() + .getWikipediaPageTitle(); + Utils.handleWebUrl(getContext(), Uri.parse(url)); + } + public interface Callback { void retryUpload(Contribution contribution); diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsLocalDataSource.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsLocalDataSource.java index 3b94d0fa7..5e21940b5 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsLocalDataSource.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsLocalDataSource.java @@ -2,6 +2,7 @@ package fr.free.nrw.commons.contributions; import androidx.paging.DataSource.Factory; import io.reactivex.Completable; +import java.util.ArrayList; import java.util.List; import javax.inject.Inject; @@ -67,7 +68,15 @@ class ContributionsLocalDataSource { } public Single> saveContributions(List contributions) { - return contributionDao.save(contributions); + List contributionList = new ArrayList<>(); + for(Contribution contribution: contributions) { + Contribution oldContribution = contributionDao.getContribution(contribution.getPageId()); + if(oldContribution != null) { + contribution.setWikidataPlace(oldContribution.getWikidataPlace()); + } + contributionList.add(contribution); + } + return contributionDao.save(contributionList); } public void set(String key, long value) { diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/WikipediaInstructionsDialogFragment.kt b/app/src/main/java/fr/free/nrw/commons/contributions/WikipediaInstructionsDialogFragment.kt new file mode 100644 index 000000000..3032f718e --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/contributions/WikipediaInstructionsDialogFragment.kt @@ -0,0 +1,67 @@ +package fr.free.nrw.commons.contributions + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager +import androidx.fragment.app.DialogFragment +import fr.free.nrw.commons.R +import kotlinx.android.synthetic.main.dialog_add_to_wikipedia_instructions.* + +/** + * Dialog fragment for displaying instructions for editing wikipedia + */ +class WikipediaInstructionsDialogFragment : DialogFragment() { + + var contribution: Contribution? = null + var callback: Callback? = null + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.dialog_add_to_wikipedia_instructions, container) + } + + override fun onViewCreated( + view: View, + savedInstanceState: Bundle? + ) { + super.onViewCreated(view, savedInstanceState) + contribution = arguments!!.getParcelable(ARG_CONTRIBUTION) + tv_wikicode.setText(contribution?.wikiCode) + instructions_cancel.setOnClickListener { + dismiss() + } + + instructions_confirm.setOnClickListener { + callback?.onConfirmClicked(contribution, checkbox_copy_wikicode.isChecked) + } + + dialog!!.window.setSoftInputMode( + WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN + ) + } + + /** + * Callback for handling confirm button clicked + */ + interface Callback { + fun onConfirmClicked(contribution: Contribution?, copyWikicode: Boolean) + } + + companion object { + + val ARG_CONTRIBUTION = "contribution" + + @JvmStatic + fun newInstance(contribution: Contribution): WikipediaInstructionsDialogFragment { + val frag = WikipediaInstructionsDialogFragment() + val args = Bundle() + args.putParcelable(ARG_CONTRIBUTION, contribution) + frag.arguments = args + return frag + } + } +} \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/db/Converters.java b/app/src/main/java/fr/free/nrw/commons/db/Converters.java index 94311c67c..b5326f777 100644 --- a/app/src/main/java/fr/free/nrw/commons/db/Converters.java +++ b/app/src/main/java/fr/free/nrw/commons/db/Converters.java @@ -11,6 +11,7 @@ import fr.free.nrw.commons.media.Depictions; import fr.free.nrw.commons.upload.WikidataPlace; import fr.free.nrw.commons.upload.structure.depictions.DepictedItem; import java.util.Date; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -54,13 +55,13 @@ public class Converters { } @TypeConverter - public static String mapObjectToString(Map objectList) { + public static String mapObjectToString(HashMap objectList) { return writeObjectToString(objectList); } @TypeConverter - public static Map stringToMap(String objectList) { - return readObjectWithTypeToken(objectList, new TypeToken>(){}); + public static HashMap stringToMap(String objectList) { + return readObjectWithTypeToken(objectList, new TypeToken>(){}); } @TypeConverter diff --git a/app/src/main/java/fr/free/nrw/commons/di/NetworkingModule.java b/app/src/main/java/fr/free/nrw/commons/di/NetworkingModule.java index f2a95b9d4..495a8d01e 100644 --- a/app/src/main/java/fr/free/nrw/commons/di/NetworkingModule.java +++ b/app/src/main/java/fr/free/nrw/commons/di/NetworkingModule.java @@ -13,6 +13,7 @@ import fr.free.nrw.commons.explore.depictions.DepictsClient; import fr.free.nrw.commons.kvstore.JsonKvStore; import fr.free.nrw.commons.media.MediaDetailInterface; import fr.free.nrw.commons.media.MediaInterface; +import fr.free.nrw.commons.media.PageMediaInterface; import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient; import fr.free.nrw.commons.mwapi.UserInterface; import fr.free.nrw.commons.review.ReviewInterface; @@ -21,6 +22,7 @@ import fr.free.nrw.commons.upload.WikiBaseInterface; import fr.free.nrw.commons.upload.depicts.DepictsInterface; import fr.free.nrw.commons.wikidata.WikidataInterface; import java.io.File; +import java.util.Locale; import java.util.concurrent.TimeUnit; import javax.inject.Named; import javax.inject.Singleton; @@ -49,6 +51,9 @@ public class NetworkingModule { public static final String NAMED_COMMONS_WIKI_SITE = "commons-wikisite"; private static final String NAMED_WIKI_DATA_WIKI_SITE = "wikidata-wikisite"; + private static final String NAMED_WIKI_PEDIA_WIKI_SITE = "wikipedia-wikisite"; + + public static final String NAMED_LANGUAGE_WIKI_PEDIA_WIKI_SITE = "language-wikipedia-wikisite"; public static final String NAMED_COMMONS_CSRF = "commons-csrf"; @@ -234,4 +239,21 @@ public class NetworkingModule { public WikidataInterface provideWikidataInterface(@Named(NAMED_WIKI_DATA_WIKI_SITE) WikiSite wikiDataWikiSite) { return ServiceFactory.get(wikiDataWikiSite, BuildConfig.WIKIDATA_URL, WikidataInterface.class); } + + /** + * Add provider for PageMediaInterface + * It creates a retrofit service for the wiki site using device's current language + */ + @Provides + @Singleton + public PageMediaInterface providePageMediaInterface(@Named(NAMED_LANGUAGE_WIKI_PEDIA_WIKI_SITE) WikiSite wikiSite) { + return ServiceFactory.get(wikiSite, wikiSite.url(), PageMediaInterface.class); + } + + @Provides + @Singleton + @Named(NAMED_LANGUAGE_WIKI_PEDIA_WIKI_SITE) + public WikiSite provideLanguageWikipediaSite() { + return WikiSite.forLanguageCode(Locale.getDefault().getLanguage()); + } } diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaClient.java b/app/src/main/java/fr/free/nrw/commons/media/MediaClient.java index 0ddd9fc70..d23f3842e 100644 --- a/app/src/main/java/fr/free/nrw/commons/media/MediaClient.java +++ b/app/src/main/java/fr/free/nrw/commons/media/MediaClient.java @@ -28,6 +28,7 @@ import timber.log.Timber; public class MediaClient { private final MediaInterface mediaInterface; + private final PageMediaInterface pageMediaInterface; private final MediaDetailInterface mediaDetailInterface; //OkHttpJsonApiClient used JsonKvStore for this. I don't know why. @@ -37,8 +38,11 @@ public class MediaClient { private static final String NO_DEPICTION = "No depiction"; @Inject - public MediaClient(MediaInterface mediaInterface, MediaDetailInterface mediaDetailInterface) { + public MediaClient(MediaInterface mediaInterface, + PageMediaInterface pageMediaInterface, + MediaDetailInterface mediaDetailInterface) { this.mediaInterface = mediaInterface; + this.pageMediaInterface = pageMediaInterface; this.mediaDetailInterface = mediaDetailInterface; this.continuationStore = new HashMap<>(); this.continuationExists = new HashMap<>(); @@ -222,6 +226,13 @@ public class MediaClient { .singleOrError(); } + public Single doesPageContainMedia(String title) { + return pageMediaInterface.getMediaList(title) + .map(pageMediaListResponse -> { + return pageMediaListResponse.getItems().size() > 0; + }).singleOrError(); + } + private boolean isSuccess(Entities response) { return response != null && response.getSuccess() == 1 && response.entities() != null; } diff --git a/app/src/main/java/fr/free/nrw/commons/media/PageMediaInterface.kt b/app/src/main/java/fr/free/nrw/commons/media/PageMediaInterface.kt new file mode 100644 index 000000000..0be03b736 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/media/PageMediaInterface.kt @@ -0,0 +1,19 @@ +package fr.free.nrw.commons.media + +import fr.free.nrw.commons.media.model.PageMediaListResponse +import io.reactivex.Observable +import retrofit2.http.GET +import retrofit2.http.Path + +/** + * Interface for MediaWiki Page REST APIs + */ +interface PageMediaInterface { + /** + * Get a list of media used on a page + * + * @param title the title of the page + */ + @GET("api/rest_v1/page/media-list/{title}") + fun getMediaList(@Path("title") title: String?): Observable? +} \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/media/model/PageMediaListResponse.kt b/app/src/main/java/fr/free/nrw/commons/media/model/PageMediaListResponse.kt new file mode 100644 index 000000000..01306f05c --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/media/model/PageMediaListResponse.kt @@ -0,0 +1,9 @@ +package fr.free.nrw.commons.media.model + +data class PageMediaListResponse( + val revision: String, + val tid: String, + val items: List +) + +data class PageMediaListItem(val title: String) diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/Place.java b/app/src/main/java/fr/free/nrw/commons/nearby/Place.java index 1af1c3af6..1da57e48e 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/Place.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/Place.java @@ -125,7 +125,6 @@ public class Place implements Parcelable { } String wikiDataLink = siteLinks.getWikidataLink().toString(); - Timber.d("Wikidata entity is %s", wikiDataLink); return wikiDataLink.replace("http://www.wikidata.org/entity/", ""); } diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.java b/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.java index 9094d8547..90fe7f598 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.java @@ -1419,14 +1419,17 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment if (fabGallery.isShown()) { Timber.d("Gallery button tapped. Place: %s", selectedPlace.toString()); storeSharedPrefs(selectedPlace); + controller.initiateGalleryPick(getActivity(), false); } }); } private void storeSharedPrefs(final Place selectedPlace) { - Timber.d("Store place object %s", selectedPlace.toString()); applicationKvStore.putJson(PLACE_OBJECT, selectedPlace); + Place place = applicationKvStore.getJson(PLACE_OBJECT, Place.class); + + Timber.d("Stored place object %s", place.toString()); } private void updateBookmarkButtonImage(final Place place) { diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadService.java b/app/src/main/java/fr/free/nrw/commons/upload/UploadService.java index 0f88be382..58c8631d8 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/UploadService.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadService.java @@ -47,300 +47,335 @@ import timber.log.Timber; public class UploadService extends CommonsDaggerService { - private static final String EXTRA_PREFIX = "fr.free.nrw.commons.upload"; + private static final String EXTRA_PREFIX = "fr.free.nrw.commons.upload"; - public static final String ACTION_START_SERVICE = EXTRA_PREFIX + ".upload"; - public static final String EXTRA_FILES = EXTRA_PREFIX + ".files"; - @Inject WikidataEditService wikidataEditService; - @Inject SessionManager sessionManager; - @Inject - ContributionDao contributionDao; - @Inject UploadClient uploadClient; - @Inject MediaClient mediaClient; - @Inject - @Named(CommonsApplicationModule.MAIN_THREAD) - Scheduler mainThreadScheduler; - @Inject - @Named(CommonsApplicationModule.IO_THREAD) Scheduler ioThreadScheduler; + public static final String ACTION_START_SERVICE = EXTRA_PREFIX + ".upload"; + public static final String EXTRA_FILES = EXTRA_PREFIX + ".files"; + @Inject + WikidataEditService wikidataEditService; + @Inject + SessionManager sessionManager; + @Inject + ContributionDao contributionDao; + @Inject + UploadClient uploadClient; + @Inject + MediaClient mediaClient; + @Inject + @Named(CommonsApplicationModule.MAIN_THREAD) + Scheduler mainThreadScheduler; + @Inject + @Named(CommonsApplicationModule.IO_THREAD) + Scheduler ioThreadScheduler; - private NotificationManagerCompat notificationManager; - private NotificationCompat.Builder curNotification; - private int toUpload; - private CompositeDisposable compositeDisposable; + private NotificationManagerCompat notificationManager; + private NotificationCompat.Builder curNotification; + private int toUpload; + private CompositeDisposable compositeDisposable; - /** - * The filePath names of unfinished uploads, used to prevent overwriting - */ - private Set unfinishedUploads = new HashSet<>(); + /** + * The filePath names of unfinished uploads, used to prevent overwriting + */ + private Set unfinishedUploads = new HashSet<>(); - // DO NOT HAVE NOTIFICATION ID OF 0 FOR ANYTHING - // See http://stackoverflow.com/questions/8725909/startforeground-does-not-show-my-notification - // Seriously, Android? - public static final int NOTIFICATION_UPLOAD_IN_PROGRESS = 1; - public static final int NOTIFICATION_UPLOAD_FAILED = 3; + // DO NOT HAVE NOTIFICATION ID OF 0 FOR ANYTHING + // See http://stackoverflow.com/questions/8725909/startforeground-does-not-show-my-notification + // Seriously, Android? + public static final int NOTIFICATION_UPLOAD_IN_PROGRESS = 1; + public static final int NOTIFICATION_UPLOAD_FAILED = 3; - protected class NotificationUpdateProgressListener{ + protected class NotificationUpdateProgressListener { - String notificationTag; - boolean notificationTitleChanged; - Contribution contribution; + String notificationTag; + boolean notificationTitleChanged; + Contribution contribution; - String notificationProgressTitle; - String notificationFinishingTitle; - - NotificationUpdateProgressListener(String notificationTag, String notificationProgressTitle, String notificationFinishingTitle, Contribution contribution) { - this.notificationTag = notificationTag; - this.notificationProgressTitle = notificationProgressTitle; - this.notificationFinishingTitle = notificationFinishingTitle; - this.contribution = contribution; - } - - public void onProgress(long transferred, long total) { - if (!notificationTitleChanged) { - curNotification.setContentTitle(notificationProgressTitle); - notificationTitleChanged = true; - contribution.setState(Contribution.STATE_IN_PROGRESS); - } - if (transferred == total) { - // Completed! - curNotification.setContentTitle(notificationFinishingTitle) - .setTicker(notificationFinishingTitle) - .setProgress(0, 100, true); - } else { - curNotification.setProgress(100, (int) (((double) transferred / (double) total) * 100), false); - } - notificationManager.notify(notificationTag, NOTIFICATION_UPLOAD_IN_PROGRESS, curNotification.build()); - - contribution.setTransferred(transferred); - - compositeDisposable.add(contributionDao.update(contribution) - .subscribeOn(ioThreadScheduler) - .subscribe()); - } + String notificationProgressTitle; + String notificationFinishingTitle; + NotificationUpdateProgressListener(String notificationTag, String notificationProgressTitle, + String notificationFinishingTitle, Contribution contribution) { + this.notificationTag = notificationTag; + this.notificationProgressTitle = notificationProgressTitle; + this.notificationFinishingTitle = notificationFinishingTitle; + this.contribution = contribution; } - @Override - public void onDestroy() { - super.onDestroy(); - compositeDisposable.dispose(); - Timber.d("UploadService.onDestroy; %s are yet to be uploaded", unfinishedUploads); + public void onProgress(long transferred, long total) { + if (!notificationTitleChanged) { + curNotification.setContentTitle(notificationProgressTitle); + notificationTitleChanged = true; + contribution.setState(Contribution.STATE_IN_PROGRESS); + } + if (transferred == total) { + // Completed! + curNotification.setContentTitle(notificationFinishingTitle) + .setTicker(notificationFinishingTitle) + .setProgress(0, 100, true); + } else { + curNotification + .setProgress(100, (int) (((double) transferred / (double) total) * 100), false); + } + notificationManager + .notify(notificationTag, NOTIFICATION_UPLOAD_IN_PROGRESS, curNotification.build()); + + contribution.setTransferred(transferred); + + compositeDisposable.add(contributionDao.update(contribution) + .subscribeOn(ioThreadScheduler) + .subscribe()); } - public class UploadServiceLocalBinder extends Binder { - public UploadService getService() { - return UploadService.this; - } + } + + @Override + public void onDestroy() { + super.onDestroy(); + compositeDisposable.dispose(); + Timber.d("UploadService.onDestroy; %s are yet to be uploaded", unfinishedUploads); + } + + public class UploadServiceLocalBinder extends Binder { + + public UploadService getService() { + return UploadService.this; + } + } + + private final IBinder localBinder = new UploadServiceLocalBinder(); + + private PublishProcessor contributionsToUpload; + + @Override + public IBinder onBind(Intent intent) { + return localBinder; + } + + @Override + public void onCreate() { + super.onCreate(); + CommonsApplication.createNotificationChannel(getApplicationContext()); + compositeDisposable = new CompositeDisposable(); + notificationManager = NotificationManagerCompat.from(this); + curNotification = getNotificationBuilder(CommonsApplication.NOTIFICATION_CHANNEL_ID_ALL); + contributionsToUpload = PublishProcessor.create(); + compositeDisposable.add(contributionsToUpload.subscribe(this::handleUpload)); + } + + public void handleUpload(Contribution contribution) { + contribution.setState(Contribution.STATE_QUEUED); + contribution.setTransferred(0); + toUpload++; + if (curNotification != null && toUpload != 1) { + curNotification.setContentText(getResources() + .getQuantityString(R.plurals.uploads_pending_notification_indicator, toUpload, toUpload)); + Timber.d("%d uploads left", toUpload); + notificationManager + .notify(contribution.getLocalUri().toString(), NOTIFICATION_UPLOAD_IN_PROGRESS, + curNotification.build()); } - private final IBinder localBinder = new UploadServiceLocalBinder(); + compositeDisposable.add(contributionDao + .save(contribution) + .subscribeOn(ioThreadScheduler) + .subscribe(() -> uploadContribution(contribution))); + } - private PublishProcessor contributionsToUpload; + private boolean freshStart = true; - @Override - public IBinder onBind(Intent intent) { - return localBinder; + public void queue(Contribution contribution) { + contributionsToUpload.offer(contribution); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + if (ACTION_START_SERVICE.equals(intent.getAction()) && freshStart) { + compositeDisposable.add(contributionDao.updateStates(Contribution.STATE_FAILED, + new int[]{Contribution.STATE_QUEUED, Contribution.STATE_IN_PROGRESS}) + .observeOn(mainThreadScheduler) + .subscribeOn(ioThreadScheduler) + .subscribe()); + freshStart = false; } + return START_REDELIVER_INTENT; + } - @Override - public void onCreate() { - super.onCreate(); - CommonsApplication.createNotificationChannel(getApplicationContext()); - compositeDisposable = new CompositeDisposable(); - notificationManager = NotificationManagerCompat.from(this); - curNotification = getNotificationBuilder(CommonsApplication.NOTIFICATION_CHANNEL_ID_ALL); - contributionsToUpload = PublishProcessor.create(); - compositeDisposable.add(contributionsToUpload.subscribe(this::handleUpload)); + @SuppressLint("StringFormatInvalid") + private NotificationCompat.Builder getNotificationBuilder(String channelId) { + return new NotificationCompat.Builder(this, channelId).setAutoCancel(true) + .setSmallIcon(R.drawable.ic_launcher) + .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher)) + .setAutoCancel(true) + .setOnlyAlertOnce(true) + .setProgress(100, 0, true) + .setOngoing(true) + .setContentIntent( + PendingIntent.getActivity(this, 0, new Intent(this, MainActivity.class), 0)); + } + + @SuppressLint("CheckResult") + private void uploadContribution(Contribution contribution) { + Uri localUri = contribution.getLocalUri(); + if (localUri == null || localUri.getPath() == null) { + Timber.d("localUri/path is null"); + return; } + String notificationTag = localUri.toString(); + File localFile = new File(localUri.getPath()); - public void handleUpload(Contribution contribution) { - contribution.setState(Contribution.STATE_QUEUED); - contribution.setTransferred(0); - toUpload++; - if (curNotification != null && toUpload != 1) { - curNotification.setContentText(getResources().getQuantityString(R.plurals.uploads_pending_notification_indicator, toUpload, toUpload)); - Timber.d("%d uploads left", toUpload); - notificationManager.notify(contribution.getLocalUri().toString(), NOTIFICATION_UPLOAD_IN_PROGRESS, curNotification.build()); - } + Timber.d("Before execution!"); + curNotification.setContentTitle(getString(R.string.upload_progress_notification_title_start, + contribution.getDisplayTitle())) + .setContentText(getResources() + .getQuantityString(R.plurals.uploads_pending_notification_indicator, toUpload, + toUpload)) + .setTicker(getString(R.string.upload_progress_notification_title_in_progress, + contribution.getDisplayTitle())) + .setOngoing(true); + notificationManager + .notify(notificationTag, NOTIFICATION_UPLOAD_IN_PROGRESS, curNotification.build()); - compositeDisposable.add(contributionDao - .save(contribution) - .subscribeOn(ioThreadScheduler) - .subscribe(() -> uploadContribution(contribution))); - } + String filename = contribution.getFilename(); - private boolean freshStart = true; + NotificationUpdateProgressListener notificationUpdater = new NotificationUpdateProgressListener( + notificationTag, + getString(R.string.upload_progress_notification_title_in_progress, + contribution.getDisplayTitle()), + getString(R.string.upload_progress_notification_title_finishing, + contribution.getDisplayTitle()), + contribution + ); - public void queue(Contribution contribution) { - contributionsToUpload.offer(contribution); - } + Observable.fromCallable(() -> "Temp_" + contribution.hashCode() + filename) + .flatMap(stashFilename -> uploadClient + .uploadFileToStash(getApplicationContext(), stashFilename, localFile, + notificationUpdater)) + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.io()) + .doFinally(() -> { + if (filename != null) { + unfinishedUploads.remove(filename); + } + toUpload--; + if (toUpload == 0) { + // Sync modifications right after all uploads are processed + ContentResolver + .requestSync(sessionManager.getCurrentAccount(), BuildConfig.MODIFICATION_AUTHORITY, + new Bundle()); + stopForeground(true); + } + }) + .flatMap(uploadStash -> { + notificationManager.cancel(notificationTag, NOTIFICATION_UPLOAD_IN_PROGRESS); - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - if (ACTION_START_SERVICE.equals(intent.getAction()) && freshStart) { - compositeDisposable.add(contributionDao.updateStates(Contribution.STATE_FAILED, new int[]{Contribution.STATE_QUEUED, Contribution.STATE_IN_PROGRESS}) - .observeOn(mainThreadScheduler) - .subscribeOn(ioThreadScheduler) - .subscribe()); - freshStart = false; - } - return START_REDELIVER_INTENT; - } + Timber.d("Stash upload response 1 is %s", uploadStash.toString()); - @SuppressLint("StringFormatInvalid") - private NotificationCompat.Builder getNotificationBuilder(String channelId) { - return new NotificationCompat.Builder(this, channelId).setAutoCancel(true) - .setSmallIcon(R.drawable.ic_launcher) - .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher)) - .setAutoCancel(true) - .setOnlyAlertOnce(true) - .setProgress(100, 0, true) - .setOngoing(true) - .setContentIntent(PendingIntent.getActivity(this, 0, new Intent(this, MainActivity.class), 0)); - } - - @SuppressLint("CheckResult") - private void uploadContribution(Contribution contribution) { - Uri localUri = contribution.getLocalUri(); - if (localUri == null || localUri.getPath() == null) { - Timber.d("localUri/path is null"); - return; - } - String notificationTag = localUri.toString(); - File localFile = new File(localUri.getPath()); - - Timber.d("Before execution!"); - curNotification.setContentTitle(getString(R.string.upload_progress_notification_title_start, contribution.getDisplayTitle())) - .setContentText(getResources().getQuantityString(R.plurals.uploads_pending_notification_indicator, toUpload, toUpload)) - .setTicker(getString(R.string.upload_progress_notification_title_in_progress, contribution.getDisplayTitle())) - .setOngoing(true); - notificationManager - .notify(notificationTag, NOTIFICATION_UPLOAD_IN_PROGRESS, curNotification.build()); - - String filename = contribution.getFilename(); - - NotificationUpdateProgressListener notificationUpdater = new NotificationUpdateProgressListener(notificationTag, - getString(R.string.upload_progress_notification_title_in_progress, contribution.getDisplayTitle()), - getString(R.string.upload_progress_notification_title_finishing, contribution.getDisplayTitle()), - contribution - ); - - Observable.fromCallable(() -> "Temp_" + contribution.hashCode() + filename) - .flatMap(stashFilename -> uploadClient - .uploadFileToStash(getApplicationContext(), stashFilename, localFile, - notificationUpdater)) - .subscribeOn(Schedulers.io()) - .observeOn(Schedulers.io()) - .doFinally(() -> { - if (filename != null) { - unfinishedUploads.remove(filename); - } - toUpload--; - if (toUpload == 0) { - // Sync modifications right after all uploads are processed - ContentResolver.requestSync(sessionManager.getCurrentAccount(), BuildConfig.MODIFICATION_AUTHORITY, new Bundle()); - stopForeground(true); - } - }) - .flatMap(uploadStash -> { - notificationManager.cancel(notificationTag, NOTIFICATION_UPLOAD_IN_PROGRESS); - - Timber.d("Stash upload response 1 is %s", uploadStash.toString()); - - if (uploadStash.isSuccessful()) { - Timber.d("making sure of uniqueness of name: %s", filename); - String uniqueFilename = findUniqueFilename(filename); - unfinishedUploads.add(uniqueFilename); - return uploadClient.uploadFileFromStash( - getApplicationContext(), - contribution, - uniqueFilename, - uploadStash.getFilekey()); - } else { - Timber.d("Contribution upload failed. Wikidata entity won't be edited"); - showFailedNotification(contribution); - return Observable.never(); - } - }) - .subscribe( - uploadResult -> onUpload(contribution, notificationTag, uploadResult), - throwable -> { - Timber.w(throwable, "Exception during upload"); - notificationManager.cancel(notificationTag, NOTIFICATION_UPLOAD_IN_PROGRESS); - showFailedNotification(contribution); - }); - } - - private void onUpload(Contribution contribution, String notificationTag, - UploadResult uploadResult) { - Timber.d("Stash upload response 2 is %s", uploadResult.toString()); - - notificationManager.cancel(notificationTag, NOTIFICATION_UPLOAD_IN_PROGRESS); - - if (uploadResult.isSuccessful()) { - onSuccessfulUpload(contribution, uploadResult); - } else { + if (uploadStash.isSuccessful()) { + Timber.d("making sure of uniqueness of name: %s", filename); + String uniqueFilename = findUniqueFilename(filename); + unfinishedUploads.add(uniqueFilename); + return uploadClient.uploadFileFromStash( + getApplicationContext(), + contribution, + uniqueFilename, + uploadStash.getFilekey()); + } else { Timber.d("Contribution upload failed. Wikidata entity won't be edited"); showFailedNotification(contribution); - } - } + return Observable.never(); + } + }) + .subscribe( + uploadResult -> onUpload(contribution, notificationTag, uploadResult), + throwable -> { + Timber.w(throwable, "Exception during upload"); + notificationManager.cancel(notificationTag, NOTIFICATION_UPLOAD_IN_PROGRESS); + showFailedNotification(contribution); + }); + } - private void onSuccessfulUpload(Contribution contribution, UploadResult uploadResult) { - compositeDisposable - .add(wikidataEditService.addDepictionsAndCaptions(uploadResult, contribution)); - WikidataPlace wikidataPlace = contribution.getWikidataPlace(); - if (wikidataPlace != null && wikidataPlace.getImageValue() == null) { - wikidataEditService.createImageClaim(wikidataPlace, uploadResult); - } - saveCompletedContribution(contribution, uploadResult); - } + private void onUpload(Contribution contribution, String notificationTag, + UploadResult uploadResult) { + Timber.d("Stash upload response 2 is %s", uploadResult.toString()); - private void saveCompletedContribution(Contribution contribution, UploadResult uploadResult) { - compositeDisposable.add(mediaClient.getMedia("File:" + uploadResult.getFilename()) - .map(media -> new Contribution(media, Contribution.STATE_COMPLETED)) - .flatMapCompletable(newContribution -> contributionDao.saveAndDelete(contribution, newContribution)) + notificationManager.cancel(notificationTag, NOTIFICATION_UPLOAD_IN_PROGRESS); + + if (uploadResult.isSuccessful()) { + onSuccessfulUpload(contribution, uploadResult); + } else { + Timber.d("Contribution upload failed. Wikidata entity won't be edited"); + showFailedNotification(contribution); + } + } + + private void onSuccessfulUpload(Contribution contribution, UploadResult uploadResult) { + compositeDisposable + .add(wikidataEditService.addDepictionsAndCaptions(uploadResult, contribution)); + WikidataPlace wikidataPlace = contribution.getWikidataPlace(); + Timber.d("Wikidata place %s", wikidataPlace.toString()); + if (wikidataPlace != null && wikidataPlace.getImageValue() == null) { + wikidataEditService.createImageClaim(wikidataPlace, uploadResult); + } + saveCompletedContribution(contribution, uploadResult); + } + + private void saveCompletedContribution(Contribution contribution, UploadResult uploadResult) { + compositeDisposable.add(mediaClient.getMedia("File:" + uploadResult.getFilename()) + .map(media -> { + Contribution newContribution = new Contribution(media, Contribution.STATE_COMPLETED); + if (contribution.getWikidataPlace() != null) { + newContribution.setWikidataPlace(contribution.getWikidataPlace()); + } + return newContribution; + }) + .flatMapCompletable( + newContribution -> contributionDao.saveAndDelete(contribution, newContribution)) .subscribe()); - } + } - @SuppressLint("StringFormatInvalid") - @SuppressWarnings("deprecation") - private void showFailedNotification(Contribution contribution) { - curNotification.setTicker(getString(R.string.upload_failed_notification_title, contribution.getDisplayTitle())) - .setContentTitle(getString(R.string.upload_failed_notification_title, contribution.getDisplayTitle())) - .setContentText(getString(R.string.upload_failed_notification_subtitle)) - .setProgress(0, 0, false) - .setOngoing(false); - notificationManager.notify(contribution.getLocalUri().toString(), NOTIFICATION_UPLOAD_FAILED, curNotification.build()); + @SuppressLint("StringFormatInvalid") + @SuppressWarnings("deprecation") + private void showFailedNotification(Contribution contribution) { + curNotification.setTicker( + getString(R.string.upload_failed_notification_title, contribution.getDisplayTitle())) + .setContentTitle( + getString(R.string.upload_failed_notification_title, contribution.getDisplayTitle())) + .setContentText(getString(R.string.upload_failed_notification_subtitle)) + .setProgress(0, 0, false) + .setOngoing(false); + notificationManager.notify(contribution.getLocalUri().toString(), NOTIFICATION_UPLOAD_FAILED, + curNotification.build()); - contribution.setState(Contribution.STATE_FAILED); + contribution.setState(Contribution.STATE_FAILED); - compositeDisposable.add(contributionDao - .update(contribution) - .subscribeOn(ioThreadScheduler) - .subscribe()); - } + compositeDisposable.add(contributionDao + .update(contribution) + .subscribeOn(ioThreadScheduler) + .subscribe()); + } - private String findUniqueFilename(String fileName) throws IOException { - String sequenceFileName; - for (int sequenceNumber = 1; true; sequenceNumber++) { - if (sequenceNumber == 1) { - sequenceFileName = fileName; - } else { - if (fileName.indexOf('.') == -1) { - // We really should have appended a filePath type suffix already. - // But... we might not. - sequenceFileName = fileName + " " + sequenceNumber; - } else { - Pattern regex = Pattern.compile("^(.*)(\\..+?)$"); - Matcher regexMatcher = regex.matcher(fileName); - sequenceFileName = regexMatcher.replaceAll("$1 " + sequenceNumber + "$2"); - } - } - if (!mediaClient.checkPageExistsUsingTitle(String.format("File:%s",sequenceFileName)).blockingGet() - && !unfinishedUploads.contains(sequenceFileName)) { - break; - } + private String findUniqueFilename(String fileName) throws IOException { + String sequenceFileName; + for (int sequenceNumber = 1; true; sequenceNumber++) { + if (sequenceNumber == 1) { + sequenceFileName = fileName; + } else { + if (fileName.indexOf('.') == -1) { + // We really should have appended a filePath type suffix already. + // But... we might not. + sequenceFileName = fileName + " " + sequenceNumber; + } else { + Pattern regex = Pattern.compile("^(.*)(\\..+?)$"); + Matcher regexMatcher = regex.matcher(fileName); + sequenceFileName = regexMatcher.replaceAll("$1 " + sequenceNumber + "$2"); } - return sequenceFileName; + } + if (!mediaClient.checkPageExistsUsingTitle(String.format("File:%s", sequenceFileName)) + .blockingGet() + && !unfinishedUploads.contains(sequenceFileName)) { + break; + } } + return sequenceFileName; + } } diff --git a/app/src/main/java/fr/free/nrw/commons/upload/WikidataPlace.kt b/app/src/main/java/fr/free/nrw/commons/upload/WikidataPlace.kt index 4f11d0000..88f9d131c 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/WikidataPlace.kt +++ b/app/src/main/java/fr/free/nrw/commons/upload/WikidataPlace.kt @@ -5,12 +5,19 @@ import fr.free.nrw.commons.nearby.Place import kotlinx.android.parcel.Parcelize @Parcelize -internal data class WikidataPlace(override val id: String, override val name: String, val imageValue: String?) : - WikidataItem,Parcelable { +internal data class WikidataPlace( + override val id: String, + override val name: String, + val imageValue: String?, + val wikipediaArticle: String? +) : + WikidataItem, Parcelable { constructor(place: Place) : this( place.wikiDataEntityId!!, place.name, - place.pic.takeIf { it.isNotBlank() }) + place.pic.takeIf { it.isNotBlank() }, + if (place.siteLinks.wikipediaLink == null) "" else place.siteLinks.wikipediaLink.toString() + ) companion object { @JvmStatic @@ -18,4 +25,12 @@ internal data class WikidataPlace(override val id: String, override val name: St return place?.let { WikidataPlace(it) } } } + + fun getWikipediaPageTitle(): String? { + if (wikipediaArticle == null) { + return null + } + val split: Array = wikipediaArticle.split("/".toRegex()).toTypedArray() + return split[split.size - 1] + } } diff --git a/app/src/main/res/drawable-hdpi/ic_wikipedia.png b/app/src/main/res/drawable-hdpi/ic_wikipedia.png new file mode 100644 index 000000000..2fa42ff7d Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_wikipedia.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_wikipedia.png b/app/src/main/res/drawable-mdpi/ic_wikipedia.png new file mode 100644 index 000000000..679100a6d Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_wikipedia.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_wikipedia.png b/app/src/main/res/drawable-xhdpi/ic_wikipedia.png new file mode 100644 index 000000000..525abba82 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_wikipedia.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_wikipedia.png b/app/src/main/res/drawable-xxhdpi/ic_wikipedia.png new file mode 100644 index 000000000..40daa3955 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_wikipedia.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_wikipedia.png b/app/src/main/res/drawable-xxxhdpi/ic_wikipedia.png new file mode 100644 index 000000000..6e3f9443a Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_wikipedia.png differ diff --git a/app/src/main/res/layout/dialog_add_to_wikipedia_instructions.xml b/app/src/main/res/layout/dialog_add_to_wikipedia_instructions.xml new file mode 100644 index 000000000..d46affaf4 --- /dev/null +++ b/app/src/main/res/layout/dialog_add_to_wikipedia_instructions.xml @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + +