mirror of
				https://github.com/commons-app/apps-android-commons.git
				synced 2025-10-30 22:34:02 +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
	
	 Vivek Maskara
						Vivek Maskara