With option for associating image with wikipedia article (#3783)

This commit is contained in:
Vivek Maskara 2020-06-16 06:51:06 -07:00 committed by GitHub
parent a4379fde02
commit e4190f3f7d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 979 additions and 479 deletions

View file

@ -39,7 +39,7 @@ public class Contribution extends Media {
* and value is the caption in the corresponding language Ex: key = "en", value: "<caption in
* short in English>" key = "de" , value: "<caption in german>"
*/
private Map<String, String> captions = new HashMap<>();
private HashMap<String, String> 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 {
* <p>
* returns list of captions stored in hashmap
*/
public Map<String, String> getCaptions() {
public HashMap<String, String> getCaptions() {
return captions;
}
public void setCaptions(Map<String, String> captions) {
public void setCaptions(HashMap<String, String> 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);
}
/**

View file

@ -53,6 +53,9 @@ public abstract class ContributionDao {
@Query("SELECT * from contribution WHERE filename=:fileName")
public abstract List<Contribution> 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<Integer> updateStates(int state, int[] toUpdateStates);

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);

View file

@ -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<List<Long>> saveContributions(List<Contribution> contributions) {
return contributionDao.save(contributions);
List<Contribution> 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) {

View file

@ -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
}
}
}