mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-27 21:03:54 +01:00
With option for associating image with wikipedia article (#3783)
This commit is contained in:
parent
a4379fde02
commit
e4190f3f7d
28 changed files with 979 additions and 479 deletions
|
|
@ -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.
|
||||
* </p>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<String,String> objectList) {
|
||||
public static String mapObjectToString(HashMap<String,String> objectList) {
|
||||
return writeObjectToString(objectList);
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
public static Map<String,String> stringToMap(String objectList) {
|
||||
return readObjectWithTypeToken(objectList, new TypeToken<Map<String,String>>(){});
|
||||
public static HashMap<String,String> stringToMap(String objectList) {
|
||||
return readObjectWithTypeToken(objectList, new TypeToken<HashMap<String,String>>(){});
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Boolean> 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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<PageMediaListResponse?>?
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package fr.free.nrw.commons.media.model
|
||||
|
||||
data class PageMediaListResponse(
|
||||
val revision: String,
|
||||
val tid: String,
|
||||
val items: List<PageMediaListItem>
|
||||
)
|
||||
|
||||
data class PageMediaListItem(val title: String)
|
||||
|
|
@ -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/", "");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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<String> unfinishedUploads = new HashSet<>();
|
||||
/**
|
||||
* The filePath names of unfinished uploads, used to prevent overwriting
|
||||
*/
|
||||
private Set<String> 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<Contribution> 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<Contribution> 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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<String> = wikipediaArticle.split("/".toRegex()).toTypedArray()
|
||||
return split[split.size - 1]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue