mirror of
				https://github.com/commons-app/apps-android-commons.git
				synced 2025-10-30 22:34:02 +01:00 
			
		
		
		
	Use JSON SPARQL query for fetching nearby places (#2398)
* Use JSON response for nearby places * Move okhttp calls to a different class * wip * Fetch picture of the day using JSON API * Search images using JSON APIs * tests * Fix injection based on code review comments
This commit is contained in:
		
							parent
							
								
									323527b3be
								
							
						
					
					
						commit
						f12837650a
					
				
					 44 changed files with 1472 additions and 418 deletions
				
			
		|  | @ -1,5 +1,7 @@ | |||
| package fr.free.nrw.commons; | ||||
| 
 | ||||
| import android.content.Context; | ||||
| 
 | ||||
| /** | ||||
|  * Base presenter, enforcing contracts to atach and detach view | ||||
|  */ | ||||
|  |  | |||
|  | @ -14,6 +14,10 @@ import java.util.regex.Matcher; | |||
| import java.util.regex.Pattern; | ||||
| 
 | ||||
| import fr.free.nrw.commons.location.LatLng; | ||||
| import fr.free.nrw.commons.media.model.ImageInfo; | ||||
| import fr.free.nrw.commons.media.model.MwQueryPage; | ||||
| import fr.free.nrw.commons.utils.DateUtils; | ||||
| import fr.free.nrw.commons.utils.StringUtils; | ||||
| 
 | ||||
| public class Media implements Parcelable { | ||||
| 
 | ||||
|  | @ -428,4 +432,24 @@ public class Media implements Parcelable { | |||
|     public boolean getRequestedDeletion(){ | ||||
|         return requestedDeletion; | ||||
|     } | ||||
| 
 | ||||
|     public static Media from(MwQueryPage page) { | ||||
|         ImageInfo imageInfo = page.imageInfo(); | ||||
|         if(imageInfo == null) { | ||||
|             return null; | ||||
|         } | ||||
|         Media media = new Media(null, | ||||
|                 imageInfo.getOriginalUrl(), | ||||
|                 page.title(), | ||||
|                 imageInfo.getMetadata().imageDescription().value(), | ||||
|                 0, | ||||
|                 DateUtils.getDateFromString(imageInfo.getMetadata().getDateTimeOriginal().value()), | ||||
|                 DateUtils.getDateFromString(imageInfo.getMetadata().getDateTime().value()), | ||||
|                 StringUtils.getParsedStringFromHtml(imageInfo.getMetadata().getArtist().value()) | ||||
|         ); | ||||
| 
 | ||||
|         media.setLicense(imageInfo.getMetadata().getLicenseShortName().value()); | ||||
| 
 | ||||
|         return media; | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -43,6 +43,7 @@ import fr.free.nrw.commons.R; | |||
| import fr.free.nrw.commons.Utils; | ||||
| import fr.free.nrw.commons.auth.SessionManager; | ||||
| import fr.free.nrw.commons.mwapi.MediaWikiApi; | ||||
| import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient; | ||||
| import fr.free.nrw.commons.theme.NavigationBaseActivity; | ||||
| import fr.free.nrw.commons.utils.ViewUtil; | ||||
| import io.reactivex.Single; | ||||
|  | @ -100,7 +101,7 @@ public class AchievementsActivity extends NavigationBaseActivity { | |||
|     @Inject | ||||
|     SessionManager sessionManager; | ||||
|     @Inject | ||||
|     MediaWikiApi mediaWikiApi; | ||||
|     OkHttpJsonApiClient okHttpJsonApiClient; | ||||
|     MenuItem item; | ||||
| 
 | ||||
|     private CompositeDisposable compositeDisposable = new CompositeDisposable(); | ||||
|  | @ -201,7 +202,7 @@ public class AchievementsActivity extends NavigationBaseActivity { | |||
|         if (checkAccount()) { | ||||
|             try{ | ||||
| 
 | ||||
|                 compositeDisposable.add(mediaWikiApi | ||||
|                 compositeDisposable.add(okHttpJsonApiClient | ||||
|                         .getAchievements(Objects.requireNonNull(sessionManager.getCurrentAccount()).name) | ||||
|                         .subscribeOn(Schedulers.io()) | ||||
|                         .observeOn(AndroidSchedulers.mainThread()) | ||||
|  | @ -247,7 +248,7 @@ public class AchievementsActivity extends NavigationBaseActivity { | |||
|      */ | ||||
|     private void setUploadCount(Achievements achievements) { | ||||
|         if (checkAccount()) { | ||||
|             compositeDisposable.add(mediaWikiApi | ||||
|             compositeDisposable.add(okHttpJsonApiClient | ||||
|                     .getUploadCount(Objects.requireNonNull(sessionManager.getCurrentAccount()).name) | ||||
|                     .subscribeOn(Schedulers.io()) | ||||
|                     .observeOn(AndroidSchedulers.mainThread()) | ||||
|  |  | |||
|  | @ -8,20 +8,21 @@ import javax.inject.Singleton; | |||
| 
 | ||||
| import fr.free.nrw.commons.Media; | ||||
| import fr.free.nrw.commons.bookmarks.Bookmark; | ||||
| import fr.free.nrw.commons.mwapi.MediaWikiApi; | ||||
| import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient; | ||||
| 
 | ||||
| @Singleton | ||||
| public class BookmarkPicturesController { | ||||
| 
 | ||||
|     private MediaWikiApi mediaWikiApi; | ||||
| 
 | ||||
|     @Inject | ||||
|     BookmarkPicturesDao bookmarkDao; | ||||
|     private final OkHttpJsonApiClient okHttpJsonApiClient; | ||||
|     private final BookmarkPicturesDao bookmarkDao; | ||||
| 
 | ||||
|     private List<Bookmark> currentBookmarks; | ||||
| 
 | ||||
|     @Inject public BookmarkPicturesController(MediaWikiApi mediaWikiApi) { | ||||
|         this.mediaWikiApi = mediaWikiApi; | ||||
|     @Inject | ||||
|     public BookmarkPicturesController(OkHttpJsonApiClient okHttpJsonApiClient, | ||||
|                                       BookmarkPicturesDao bookmarkDao) { | ||||
|         this.okHttpJsonApiClient = okHttpJsonApiClient; | ||||
|         this.bookmarkDao = bookmarkDao; | ||||
|         currentBookmarks = new ArrayList<>(); | ||||
|     } | ||||
| 
 | ||||
|  | @ -34,7 +35,9 @@ public class BookmarkPicturesController { | |||
|         currentBookmarks = bookmarks; | ||||
|         ArrayList<Media> medias = new ArrayList<>(); | ||||
|         for (Bookmark bookmark : bookmarks) { | ||||
|             List<Media> tmpMedias = mediaWikiApi.searchImages(bookmark.getMediaName(), 0); | ||||
|             List<Media> tmpMedias = okHttpJsonApiClient | ||||
|                     .searchImages(bookmark.getMediaName(), 0) | ||||
|                     .blockingGet(); | ||||
|             for (Media m : tmpMedias) { | ||||
|                 if (m.getCreator().trim().equals(bookmark.getMediaCreator().trim())) { | ||||
|                     medias.add(m); | ||||
|  |  | |||
|  | @ -13,11 +13,13 @@ import java.util.List; | |||
| import javax.inject.Inject; | ||||
| import javax.inject.Named; | ||||
| import javax.inject.Provider; | ||||
| import javax.inject.Singleton; | ||||
| 
 | ||||
| import fr.free.nrw.commons.bookmarks.Bookmark; | ||||
| 
 | ||||
| import static fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesContentProvider.BASE_URI; | ||||
| 
 | ||||
| @Singleton | ||||
| public class BookmarkPicturesDao { | ||||
| 
 | ||||
|     private final Provider<ContentProviderClient> clientProvider; | ||||
|  |  | |||
|  | @ -1,5 +1,7 @@ | |||
| package fr.free.nrw.commons.campaigns; | ||||
| 
 | ||||
| import android.annotation.SuppressLint; | ||||
| import android.content.Context; | ||||
| import android.util.Log; | ||||
| 
 | ||||
| import java.text.ParseException; | ||||
|  | @ -8,9 +10,13 @@ import java.util.Collections; | |||
| import java.util.Date; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import javax.inject.Inject; | ||||
| import javax.inject.Singleton; | ||||
| 
 | ||||
| import fr.free.nrw.commons.BasePresenter; | ||||
| import fr.free.nrw.commons.MvpView; | ||||
| import fr.free.nrw.commons.mwapi.MediaWikiApi; | ||||
| import fr.free.nrw.commons.di.ApplicationlessInjection; | ||||
| import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient; | ||||
| import io.reactivex.Single; | ||||
| import io.reactivex.SingleObserver; | ||||
| import io.reactivex.android.schedulers.AndroidSchedulers; | ||||
|  | @ -21,16 +27,22 @@ import io.reactivex.schedulers.Schedulers; | |||
|  * The presenter for the campaigns view, fetches the campaigns from the api and informs the view on | ||||
|  * success and error | ||||
|  */ | ||||
| @Singleton | ||||
| public class CampaignsPresenter implements BasePresenter { | ||||
|     private final OkHttpJsonApiClient okHttpJsonApiClient; | ||||
| 
 | ||||
|     private final String TAG = "#CampaignsPresenter#"; | ||||
|     private ICampaignsView view; | ||||
|     private MediaWikiApi mediaWikiApi; | ||||
|     private Disposable disposable; | ||||
|     private Campaign campaign; | ||||
| 
 | ||||
|     @Inject | ||||
|     public CampaignsPresenter(OkHttpJsonApiClient okHttpJsonApiClient) { | ||||
|         this.okHttpJsonApiClient = okHttpJsonApiClient; | ||||
|     } | ||||
| 
 | ||||
|     @Override public void onAttachView(MvpView view) { | ||||
|         this.view = (ICampaignsView) view; | ||||
|         this.mediaWikiApi = ((ICampaignsView) view).getMediaWikiApi(); | ||||
|     } | ||||
| 
 | ||||
|     @Override public void onDetachView() { | ||||
|  | @ -43,14 +55,15 @@ public class CampaignsPresenter implements BasePresenter { | |||
|     /** | ||||
|      * make the api call to fetch the campaigns | ||||
|      */ | ||||
|     @SuppressLint("CheckResult") | ||||
|     public void getCampaigns() { | ||||
|         if (view != null && mediaWikiApi != null) { | ||||
|         if (view != null && okHttpJsonApiClient != null) { | ||||
|             //If we already have a campaign, lets not make another call | ||||
|             if (this.campaign != null) { | ||||
|                 view.showCampaigns(campaign); | ||||
|                 return; | ||||
|             } | ||||
|             Single<CampaignResponseDTO> campaigns = mediaWikiApi.getCampaigns(); | ||||
|             Single<CampaignResponseDTO> campaigns = okHttpJsonApiClient.getCampaigns(); | ||||
|             campaigns.observeOn(AndroidSchedulers.mainThread()) | ||||
|                 .subscribeOn(Schedulers.io()) | ||||
|                 .subscribeWith(new SingleObserver<CampaignResponseDTO>() { | ||||
|  |  | |||
|  | @ -7,7 +7,5 @@ import fr.free.nrw.commons.mwapi.MediaWikiApi; | |||
|  * Interface which defines the view contracts of the campaign view | ||||
|  */ | ||||
| public interface ICampaignsView extends MvpView { | ||||
|     MediaWikiApi getMediaWikiApi(); | ||||
| 
 | ||||
|     void showCampaigns(Campaign campaign); | ||||
| } | ||||
|  |  | |||
|  | @ -51,10 +51,10 @@ import fr.free.nrw.commons.location.LocationServiceManager; | |||
| import fr.free.nrw.commons.location.LocationUpdateListener; | ||||
| import fr.free.nrw.commons.media.MediaDetailPagerFragment; | ||||
| import fr.free.nrw.commons.mwapi.MediaWikiApi; | ||||
| import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient; | ||||
| import fr.free.nrw.commons.nearby.NearbyController; | ||||
| import fr.free.nrw.commons.nearby.NearbyNotificationCardView; | ||||
| import fr.free.nrw.commons.nearby.Place; | ||||
| import fr.free.nrw.commons.notification.NotificationController; | ||||
| import fr.free.nrw.commons.settings.Prefs; | ||||
| import fr.free.nrw.commons.upload.UploadService; | ||||
| import fr.free.nrw.commons.utils.ConfigUtils; | ||||
|  | @ -80,12 +80,14 @@ public class ContributionsFragment | |||
|                     MediaDetailPagerFragment.MediaDetailProvider, | ||||
|                     FragmentManager.OnBackStackChangedListener, | ||||
|                     ContributionsListFragment.SourceRefresher, | ||||
|                     LocationUpdateListener,ICampaignsView | ||||
|                     { | ||||
|                     LocationUpdateListener, | ||||
|                     ICampaignsView { | ||||
|     @Inject @Named("default_preferences") BasicKvStore defaultKvStore; | ||||
|     @Inject ContributionDao contributionDao; | ||||
|     @Inject MediaWikiApi mediaWikiApi; | ||||
|                         @Inject NearbyController nearbyController; | ||||
|     @Inject NearbyController nearbyController; | ||||
|     @Inject OkHttpJsonApiClient okHttpJsonApiClient; | ||||
|     @Inject CampaignsPresenter presenter; | ||||
| 
 | ||||
|     private ArrayList<DataSetObserver> observersWaitingForLoad = new ArrayList<>(); | ||||
|     private UploadService uploadService; | ||||
|  | @ -109,7 +111,6 @@ public class ContributionsFragment | |||
|     private boolean isFragmentAttachedBefore = false; | ||||
|     private View checkBoxView; | ||||
|     private CheckBox checkBox; | ||||
|     private CampaignsPresenter presenter; | ||||
| 
 | ||||
|     /** | ||||
|      * Since we will need to use parent activity on onAuthCookieAcquired, we have to wait | ||||
|  | @ -144,7 +145,6 @@ public class ContributionsFragment | |||
|     public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { | ||||
|         View view = inflater.inflate(R.layout.fragment_contributions, container, false); | ||||
|         ButterKnife.bind(this, view); | ||||
|         presenter = new CampaignsPresenter(); | ||||
|         presenter.onAttachView(this); | ||||
|         campaignView.setVisibility(View.GONE); | ||||
|         checkBoxView = View.inflate(getActivity(), R.layout.nearby_permission_dialog, null); | ||||
|  | @ -451,7 +451,7 @@ public class ContributionsFragment | |||
|     @SuppressWarnings("ConstantConditions") | ||||
|     private void setUploadCount() { | ||||
| 
 | ||||
|         compositeDisposable.add(mediaWikiApi | ||||
|         compositeDisposable.add(okHttpJsonApiClient | ||||
|                 .getUploadCount(((MainActivity)getActivity()).sessionManager.getCurrentAccount().name) | ||||
|                 .subscribeOn(Schedulers.io()) | ||||
|                 .observeOn(AndroidSchedulers.mainThread()) | ||||
|  | @ -695,10 +695,6 @@ public class ContributionsFragment | |||
|         Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show(); | ||||
|     } | ||||
| 
 | ||||
|     @Override public MediaWikiApi getMediaWikiApi() { | ||||
|         return mediaWikiApi; | ||||
|     } | ||||
| 
 | ||||
|     @Override public void showCampaigns(Campaign campaign) { | ||||
|         if (campaign != null) { | ||||
|             campaignView.setCampaign(campaign); | ||||
|  |  | |||
|  | @ -2,50 +2,37 @@ package fr.free.nrw.commons.delete; | |||
| 
 | ||||
| import android.accounts.Account; | ||||
| import android.content.Context; | ||||
| import android.util.Log; | ||||
| 
 | ||||
| import com.google.gson.JsonObject; | ||||
| 
 | ||||
| import org.json.JSONException; | ||||
| import org.json.JSONObject; | ||||
| 
 | ||||
| import java.text.SimpleDateFormat; | ||||
| import java.util.Date; | ||||
| import java.util.Locale; | ||||
| 
 | ||||
| import javax.inject.Inject; | ||||
| import javax.inject.Singleton; | ||||
| 
 | ||||
| import fr.free.nrw.commons.Media; | ||||
| import fr.free.nrw.commons.R; | ||||
| import fr.free.nrw.commons.achievements.FeedbackResponse; | ||||
| import fr.free.nrw.commons.auth.SessionManager; | ||||
| import fr.free.nrw.commons.mwapi.MediaWikiApi; | ||||
| import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient; | ||||
| import fr.free.nrw.commons.utils.ViewUtil; | ||||
| import io.reactivex.android.schedulers.AndroidSchedulers; | ||||
| import io.reactivex.disposables.CompositeDisposable; | ||||
| import io.reactivex.schedulers.Schedulers; | ||||
| import io.reactivex.Single; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
| @Singleton | ||||
| public class ReasonBuilder { | ||||
| 
 | ||||
|     private SessionManager sessionManager; | ||||
|     private MediaWikiApi mediaWikiApi; | ||||
|     private CompositeDisposable compositeDisposable = new CompositeDisposable(); | ||||
| 
 | ||||
|     private String reason; | ||||
|     private OkHttpJsonApiClient okHttpJsonApiClient; | ||||
|     private Context context; | ||||
|     private Media media; | ||||
| 
 | ||||
|     public ReasonBuilder(String reason, | ||||
|                          Context context, | ||||
|                          Media media, | ||||
|     @Inject | ||||
|     public ReasonBuilder(Context context, | ||||
|                          SessionManager sessionManager, | ||||
|                          MediaWikiApi mediaWikiApi){ | ||||
|         this.reason = reason; | ||||
|                          OkHttpJsonApiClient okHttpJsonApiClient) { | ||||
|         this.context = context; | ||||
|         this.media = media; | ||||
|         this.sessionManager = sessionManager; | ||||
|         this.mediaWikiApi = mediaWikiApi; | ||||
|         this.okHttpJsonApiClient = okHttpJsonApiClient; | ||||
|     } | ||||
| 
 | ||||
|     private String prettyUploadedDate(Media media) { | ||||
|  | @ -57,31 +44,27 @@ public class ReasonBuilder { | |||
|         return formatter.format(date); | ||||
|     } | ||||
| 
 | ||||
|     private void fetchArticleNumber() { | ||||
|     private Single<String> fetchArticleNumber(Media media, String reason) { | ||||
|         if (checkAccount()) { | ||||
|             compositeDisposable.add(mediaWikiApi | ||||
|             return okHttpJsonApiClient | ||||
|                     .getAchievements(sessionManager.getCurrentAccount().name) | ||||
|                     .subscribeOn(Schedulers.io()) | ||||
|                     .observeOn(AndroidSchedulers.mainThread()) | ||||
|                     .subscribe( | ||||
|                             this::appendArticlesUsed, | ||||
|                             t -> Timber.e(t, "Fetching achievements statistics failed") | ||||
|                     )); | ||||
|                     .map(feedbackResponse -> appendArticlesUsed(feedbackResponse, media, reason)); | ||||
|         } | ||||
|         return Single.just(""); | ||||
|     } | ||||
| 
 | ||||
|     private void appendArticlesUsed(FeedbackResponse object){ | ||||
|         reason += context.getString(R.string.uploaded_by_myself).toString() + prettyUploadedDate(media); | ||||
|         reason += context.getString(R.string.used_by).toString() | ||||
|     private String appendArticlesUsed(FeedbackResponse object, Media media, String reason) { | ||||
|         reason += context.getString(R.string.uploaded_by_myself) + prettyUploadedDate(media); | ||||
|         reason += context.getString(R.string.used_by) | ||||
|                 + object.getArticlesUsingImages() | ||||
|                 + context.getString(R.string.articles).toString(); | ||||
|         Log.i("New Reason", reason); | ||||
|                 + context.getString(R.string.articles); | ||||
|         Timber.i("New Reason %s", reason); | ||||
|         return reason; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     public String getReason(){ | ||||
|         fetchArticleNumber(); | ||||
|         return reason; | ||||
|     public Single<String> getReason(Media media, String reason) { | ||||
|         return fetchArticleNumber(media, reason); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  |  | |||
|  | @ -9,6 +9,7 @@ import dagger.android.support.AndroidSupportInjectionModule; | |||
| import fr.free.nrw.commons.CommonsApplication; | ||||
| import fr.free.nrw.commons.MediaWikiImageView; | ||||
| import fr.free.nrw.commons.auth.LoginActivity; | ||||
| import fr.free.nrw.commons.campaigns.CampaignsPresenter; | ||||
| import fr.free.nrw.commons.contributions.ContributionsSyncAdapter; | ||||
| import fr.free.nrw.commons.delete.DeleteTask; | ||||
| import fr.free.nrw.commons.modifications.ModificationsSyncAdapter; | ||||
|  |  | |||
|  | @ -191,12 +191,6 @@ public class CommonsApplicationModule { | |||
|         return new DBOpenHelper(context); | ||||
|     } | ||||
| 
 | ||||
|     @Provides | ||||
|     @Singleton | ||||
|     public NearbyPlaces provideNearbyPlaces() { | ||||
|         return new NearbyPlaces(); | ||||
|     } | ||||
| 
 | ||||
|     @Provides | ||||
|     @Singleton | ||||
|     public LruCache<String, String> provideLruCache() { | ||||
|  |  | |||
|  | @ -19,36 +19,68 @@ import fr.free.nrw.commons.BuildConfig; | |||
| import fr.free.nrw.commons.kvstore.BasicKvStore; | ||||
| import fr.free.nrw.commons.mwapi.ApacheHttpClientMediaWikiApi; | ||||
| import fr.free.nrw.commons.mwapi.MediaWikiApi; | ||||
| import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient; | ||||
| import fr.free.nrw.commons.utils.UriDeserializer; | ||||
| import fr.free.nrw.commons.utils.UriSerializer; | ||||
| import okhttp3.Cache; | ||||
| import okhttp3.HttpUrl; | ||||
| import okhttp3.OkHttpClient; | ||||
| import okhttp3.logging.HttpLoggingInterceptor; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
| @Module | ||||
| @SuppressWarnings({"WeakerAccess", "unused"}) | ||||
| public class NetworkingModule { | ||||
|     private static final String WIKIDATA_SPARQL_QUERY_URL = "https://query.wikidata.org/sparql"; | ||||
|     private final String WIKIMEDIA_CAMPAIGNS_BASE_URL = | ||||
|             "https://raw.githubusercontent.com/commons-app/campaigns/master/campaigns.json"; | ||||
|     private static final String TOOLS_FORGE_URL = "https://tools.wmflabs.org/"; | ||||
| 
 | ||||
|     public static final long OK_HTTP_CACHE_SIZE = 10 * 1024 * 1024; | ||||
| 
 | ||||
|     @Provides | ||||
|     @Singleton | ||||
|     public OkHttpClient provideOkHttpClient(Context context) { | ||||
|     public OkHttpClient provideOkHttpClient(Context context, | ||||
|                                             HttpLoggingInterceptor httpLoggingInterceptor) { | ||||
|         File dir = new File(context.getCacheDir(), "okHttpCache"); | ||||
|         return new OkHttpClient.Builder().connectTimeout(60, TimeUnit.SECONDS) | ||||
|             .writeTimeout(60, TimeUnit.SECONDS) | ||||
|                 .addInterceptor(httpLoggingInterceptor) | ||||
|             .readTimeout(60, TimeUnit.SECONDS) | ||||
|             .cache(new Cache(dir, OK_HTTP_CACHE_SIZE)) | ||||
|             .build(); | ||||
|     } | ||||
| 
 | ||||
|     @Provides | ||||
|     @Singleton | ||||
|     public HttpLoggingInterceptor provideHttpLoggingInterceptor() { | ||||
|         HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(message -> { | ||||
|             Timber.tag("OkHttp").d(message); | ||||
|         }); | ||||
|         httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY); | ||||
|         return httpLoggingInterceptor; | ||||
|     } | ||||
| 
 | ||||
|     @Provides | ||||
|     @Singleton | ||||
|     public MediaWikiApi provideMediaWikiApi(Context context, | ||||
|                                             @Named("default_preferences") BasicKvStore defaultKvStore, | ||||
|                                             @Named("category_prefs") BasicKvStore categoryKvStore, | ||||
|                                             Gson gson, | ||||
|                                             OkHttpClient okHttpClient) { | ||||
|         return new ApacheHttpClientMediaWikiApi(context, BuildConfig.WIKIMEDIA_API_HOST, BuildConfig.WIKIDATA_API_HOST, defaultKvStore, categoryKvStore, gson, okHttpClient); | ||||
|                                             Gson gson) { | ||||
|         return new ApacheHttpClientMediaWikiApi(context, BuildConfig.WIKIMEDIA_API_HOST, BuildConfig.WIKIDATA_API_HOST, defaultKvStore, categoryKvStore, gson); | ||||
|     } | ||||
| 
 | ||||
|     @Provides | ||||
|     @Singleton | ||||
|     public OkHttpJsonApiClient provideOkHttpJsonApiClient(OkHttpClient okHttpClient, | ||||
|                                                           @Named("tools_force") HttpUrl toolsForgeUrl, | ||||
|                                                           Gson gson) { | ||||
|         return new OkHttpJsonApiClient(okHttpClient, | ||||
|                 toolsForgeUrl, | ||||
|                 WIKIDATA_SPARQL_QUERY_URL, | ||||
|                 WIKIMEDIA_CAMPAIGNS_BASE_URL, | ||||
|                 BuildConfig.WIKIMEDIA_API_HOST, | ||||
|                 gson); | ||||
|     } | ||||
| 
 | ||||
|     @Provides | ||||
|  | @ -59,6 +91,14 @@ public class NetworkingModule { | |||
|         return HttpUrl.parse(BuildConfig.COMMONS_URL); | ||||
|     } | ||||
| 
 | ||||
|     @Provides | ||||
|     @Named("tools_force") | ||||
|     @NonNull | ||||
|     @SuppressWarnings("ConstantConditions") | ||||
|     public HttpUrl provideToolsForgeUrl() { | ||||
|         return HttpUrl.parse(TOOLS_FORGE_URL); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Gson objects are very heavy. The app should ideally be using just one instance of it instead of creating new instances everywhere. | ||||
|      * @return returns a singleton Gson instance | ||||
|  |  | |||
|  | @ -33,10 +33,9 @@ import fr.free.nrw.commons.explore.SearchActivity; | |||
| import fr.free.nrw.commons.explore.recentsearches.RecentSearch; | ||||
| import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao; | ||||
| import fr.free.nrw.commons.kvstore.BasicKvStore; | ||||
| import fr.free.nrw.commons.mwapi.MediaWikiApi; | ||||
| import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient; | ||||
| import fr.free.nrw.commons.utils.NetworkUtils; | ||||
| import fr.free.nrw.commons.utils.ViewUtil; | ||||
| import io.reactivex.Observable; | ||||
| import io.reactivex.android.schedulers.AndroidSchedulers; | ||||
| import io.reactivex.schedulers.Schedulers; | ||||
| import timber.log.Timber; | ||||
|  | @ -63,7 +62,8 @@ public class SearchImageFragment extends CommonsDaggerSupportFragment { | |||
|     ProgressBar bottomProgressBar; | ||||
| 
 | ||||
|     @Inject RecentSearchesDao recentSearchesDao; | ||||
|     @Inject MediaWikiApi mwApi; | ||||
|     @Inject | ||||
|     OkHttpJsonApiClient okHttpJsonApiClient; | ||||
|     @Inject @Named("default_preferences") BasicKvStore defaultKvStore; | ||||
| 
 | ||||
|     private RVRendererAdapter<Media> imagesAdapter; | ||||
|  | @ -140,7 +140,7 @@ public class SearchImageFragment extends CommonsDaggerSupportFragment { | |||
|         bottomProgressBar.setVisibility(GONE); | ||||
|         queryList.clear(); | ||||
|         imagesAdapter.clear(); | ||||
|         Observable.fromCallable(() -> mwApi.searchImages(query,queryList.size())) | ||||
|         okHttpJsonApiClient.searchImages(query, queryList.size()) | ||||
|                 .subscribeOn(Schedulers.io()) | ||||
|                 .observeOn(AndroidSchedulers.mainThread()) | ||||
|                 .timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS) | ||||
|  | @ -156,7 +156,7 @@ public class SearchImageFragment extends CommonsDaggerSupportFragment { | |||
|         this.query = query; | ||||
|         bottomProgressBar.setVisibility(View.VISIBLE); | ||||
|         progressBar.setVisibility(GONE); | ||||
|         Observable.fromCallable(() -> mwApi.searchImages(query,queryList.size())) | ||||
|         okHttpJsonApiClient.searchImages(query, queryList.size()) | ||||
|                 .subscribeOn(Schedulers.io()) | ||||
|                 .observeOn(AndroidSchedulers.mainThread()) | ||||
|                 .timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS) | ||||
|  |  | |||
|  | @ -0,0 +1,34 @@ | |||
| package fr.free.nrw.commons.json; | ||||
| 
 | ||||
| import com.google.gson.Gson; | ||||
| import com.google.gson.TypeAdapter; | ||||
| import com.google.gson.TypeAdapterFactory; | ||||
| import com.google.gson.reflect.TypeToken; | ||||
| import com.google.gson.stream.JsonReader; | ||||
| import com.google.gson.stream.JsonWriter; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| 
 | ||||
| public class PostProcessingTypeAdapter implements TypeAdapterFactory { | ||||
|     public interface PostProcessable { | ||||
|         void postProcess(); | ||||
|     } | ||||
| 
 | ||||
|     public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) { | ||||
|         final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type); | ||||
| 
 | ||||
|         return new TypeAdapter<T>() { | ||||
|             public void write(JsonWriter out, T value) throws IOException { | ||||
|                 delegate.write(out, value); | ||||
|             } | ||||
| 
 | ||||
|             public T read(JsonReader in) throws IOException { | ||||
|                 T obj = delegate.read(in); | ||||
|                 if (obj instanceof PostProcessable) { | ||||
|                     ((PostProcessable)obj).postProcess(); | ||||
|                 } | ||||
|                 return obj; | ||||
|             } | ||||
|         }; | ||||
|     } | ||||
| } | ||||
|  | @ -1,9 +1,9 @@ | |||
| package fr.free.nrw.commons.media; | ||||
| 
 | ||||
| import android.annotation.SuppressLint; | ||||
| import android.app.AlertDialog; | ||||
| import android.content.ClipData; | ||||
| import android.content.ClipboardManager; | ||||
| import android.content.DialogInterface; | ||||
| import android.content.Intent; | ||||
| import android.database.DataSetObserver; | ||||
| import android.net.Uri; | ||||
|  | @ -12,7 +12,6 @@ import android.os.Bundle; | |||
| import android.support.annotation.Nullable; | ||||
| import android.text.Html; | ||||
| import android.text.TextUtils; | ||||
| import android.text.format.DateFormat; | ||||
| import android.util.TypedValue; | ||||
| import android.view.LayoutInflater; | ||||
| import android.view.View; | ||||
|  | @ -27,7 +26,6 @@ import android.widget.TextView; | |||
| import android.widget.Toast; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| import java.text.SimpleDateFormat; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Date; | ||||
| import java.util.Locale; | ||||
|  | @ -54,8 +52,11 @@ import fr.free.nrw.commons.di.CommonsDaggerSupportFragment; | |||
| import fr.free.nrw.commons.location.LatLng; | ||||
| import fr.free.nrw.commons.mwapi.MediaWikiApi; | ||||
| import fr.free.nrw.commons.ui.widget.CompatTextView; | ||||
| import timber.log.Timber; | ||||
| import fr.free.nrw.commons.utils.DateUtils; | ||||
| import io.reactivex.Single; | ||||
| import io.reactivex.android.schedulers.AndroidSchedulers; | ||||
| import io.reactivex.schedulers.Schedulers; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
| import static android.content.Context.CLIPBOARD_SERVICE; | ||||
| import static android.view.View.GONE; | ||||
|  | @ -93,6 +94,8 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment { | |||
|     MediaWikiApi mwApi; | ||||
|     @Inject | ||||
|     SessionManager sessionManager; | ||||
|     @Inject | ||||
|     ReasonBuilder reasonBuilder; | ||||
| 
 | ||||
|     private int initialListTop = 0; | ||||
| 
 | ||||
|  | @ -404,19 +407,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment { | |||
|         AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); | ||||
|         builder.setView(spinner); | ||||
|         builder.setTitle(R.string.nominate_delete) | ||||
|                 .setPositiveButton(R.string.about_translate_proceed, (dialog, which) -> { | ||||
|                     String reason = spinner.getSelectedItem().toString(); | ||||
|                     ReasonBuilder reasonBuilder = new ReasonBuilder(reason, | ||||
|                             getActivity(), | ||||
|                             media, | ||||
|                             sessionManager, | ||||
|                             mwApi); | ||||
|                     reason = reasonBuilder.getReason(); | ||||
|                     DeleteTask deleteTask = new DeleteTask(getActivity(), media, reason); | ||||
|                     deleteTask.execute(); | ||||
|                     isDeleted = true; | ||||
|                     enableDeleteButton(false); | ||||
|                 }); | ||||
|                 .setPositiveButton(R.string.about_translate_proceed, (dialog, which) -> onDeleteClicked(spinner)); | ||||
|         builder.setNegativeButton(R.string.about_translate_cancel, (dialog, which) -> dialog.dismiss()); | ||||
|         AlertDialog dialog = builder.create(); | ||||
|         dialog.show(); | ||||
|  | @ -425,6 +416,21 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @SuppressLint("CheckResult") | ||||
|     private void onDeleteClicked(Spinner spinner) { | ||||
|         String reason = spinner.getSelectedItem().toString(); | ||||
|         Single<String> deletionReason = reasonBuilder.getReason(media, reason); | ||||
|         deletionReason | ||||
|                 .subscribeOn(Schedulers.io()) | ||||
|                 .observeOn(AndroidSchedulers.mainThread()) | ||||
|                 .subscribe(s -> { | ||||
|                     DeleteTask deleteTask = new DeleteTask(getActivity(), media, reason); | ||||
|                     deleteTask.execute(); | ||||
|                     isDeleted = true; | ||||
|                     enableDeleteButton(false); | ||||
|                 }); | ||||
|     } | ||||
| 
 | ||||
|     @OnClick(R.id.seeMore) | ||||
|     public void onSeeMoreClicked(){ | ||||
|         if (nominatedForDeletion.getVisibility()== VISIBLE) { | ||||
|  |  | |||
|  | @ -0,0 +1,161 @@ | |||
| package fr.free.nrw.commons.media.model; | ||||
| 
 | ||||
| import android.support.annotation.NonNull; | ||||
| import android.support.annotation.Nullable; | ||||
| 
 | ||||
| import com.google.gson.annotations.SerializedName; | ||||
| 
 | ||||
| public class ExtMetadata { | ||||
|     @SuppressWarnings("unused") @SerializedName("DateTime") @Nullable | ||||
|     private Values dateTime; | ||||
|     @SuppressWarnings("unused") @SerializedName("ObjectName") @Nullable private Values objectName; | ||||
|     @SuppressWarnings("unused") @SerializedName("CommonsMetadataExtension") @Nullable private Values commonsMetadataExtension; | ||||
|     @SuppressWarnings("unused") @SerializedName("Categories") @Nullable private Values categories; | ||||
|     @SuppressWarnings("unused") @SerializedName("Assessments") @Nullable private Values assessments; | ||||
|     @SuppressWarnings("unused") @SerializedName("ImageDescription") @Nullable private Values imageDescription; | ||||
|     @SuppressWarnings("unused") @SerializedName("DateTimeOriginal") @Nullable private Values dateTimeOriginal; | ||||
|     @SuppressWarnings("unused") @SerializedName("Artist") @Nullable private Values artist; | ||||
|     @SuppressWarnings("unused") @SerializedName("Credit") @Nullable private Values credit; | ||||
|     @SuppressWarnings("unused") @SerializedName("Permission") @Nullable private Values permission; | ||||
|     @SuppressWarnings("unused") @SerializedName("AuthorCount") @Nullable private Values authorCount; | ||||
|     @SuppressWarnings("unused") @SerializedName("LicenseShortName") @Nullable private Values licenseShortName; | ||||
|     @SuppressWarnings("unused") @SerializedName("UsageTerms") @Nullable private Values usageTerms; | ||||
|     @SuppressWarnings("unused") @SerializedName("LicenseUrl") @Nullable private Values licenseUrl; | ||||
|     @SuppressWarnings("unused") @SerializedName("AttributionRequired") @Nullable private Values attributionRequired; | ||||
|     @SuppressWarnings("unused") @SerializedName("Copyrighted") @Nullable private Values copyrighted; | ||||
|     @SuppressWarnings("unused") @SerializedName("Restrictions") @Nullable private Values restrictions; | ||||
|     @SuppressWarnings("unused") @SerializedName("License") @Nullable private Values license; | ||||
| 
 | ||||
|     @Nullable public Values licenseShortName() { | ||||
|         return licenseShortName; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable public Values licenseUrl() { | ||||
|         return licenseUrl; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable public Values license() { | ||||
|         return license; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable public Values imageDescription() { | ||||
|         return imageDescription; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     public Values getDateTime() { | ||||
|         return dateTime; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     public Values getObjectName() { | ||||
|         return objectName; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     public Values getCommonsMetadataExtension() { | ||||
|         return commonsMetadataExtension; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     public Values getCategories() { | ||||
|         return categories; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     public Values getAssessments() { | ||||
|         return assessments; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     public Values getImageDescription() { | ||||
|         return imageDescription; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     public Values getDateTimeOriginal() { | ||||
|         return dateTimeOriginal; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     public Values getArtist() { | ||||
|         return artist; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     public Values getCredit() { | ||||
|         return credit; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     public Values getPermission() { | ||||
|         return permission; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     public Values getAuthorCount() { | ||||
|         return authorCount; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     public Values getLicenseShortName() { | ||||
|         return licenseShortName; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     public Values getUsageTerms() { | ||||
|         return usageTerms; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     public Values getLicenseUrl() { | ||||
|         return licenseUrl; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     public Values getAttributionRequired() { | ||||
|         return attributionRequired; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     public Values getCopyrighted() { | ||||
|         return copyrighted; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     public Values getRestrictions() { | ||||
|         return restrictions; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     public Values getLicense() { | ||||
|         return license; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable public Values objectName() { | ||||
|         return objectName; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable public Values usageTerms() { | ||||
|         return usageTerms; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable public Values artist() { | ||||
|         return artist; | ||||
|     } | ||||
| 
 | ||||
|     public class Values { | ||||
|         @SuppressWarnings("unused,NullableProblems") @NonNull | ||||
|         private String value; | ||||
|         @SuppressWarnings("unused,NullableProblems") @NonNull private String source; | ||||
|         @SuppressWarnings("unused,NullableProblems") @NonNull private String hidden; | ||||
| 
 | ||||
|         @NonNull public String value() { | ||||
|             return value; | ||||
|         } | ||||
| 
 | ||||
|         @NonNull public String source() { | ||||
|             return source; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,63 @@ | |||
| package fr.free.nrw.commons.media.model; | ||||
| 
 | ||||
| import android.support.annotation.NonNull; | ||||
| import android.support.annotation.Nullable; | ||||
| 
 | ||||
| import com.google.gson.annotations.SerializedName; | ||||
| 
 | ||||
| import java.io.Serializable; | ||||
| 
 | ||||
| import fr.free.nrw.commons.utils.StringUtils; | ||||
| 
 | ||||
| /** | ||||
|  * Gson POJO for a standard image info object as returned by the API ImageInfo module | ||||
|  */ | ||||
| public class ImageInfo implements Serializable { | ||||
|     @SuppressWarnings("unused") private int size; | ||||
|     @SuppressWarnings("unused") private int width; | ||||
|     @SuppressWarnings("unused") private int height; | ||||
|     @SuppressWarnings("unused,NullableProblems") @Nullable | ||||
|     private String source; | ||||
|     @SuppressWarnings("unused") @SerializedName("thumburl") @Nullable private String thumbUrl; | ||||
|     @SuppressWarnings("unused") @SerializedName("thumbwidth") private int thumbWidth; | ||||
|     @SuppressWarnings("unused") @SerializedName("thumbheight") private int thumbHeight; | ||||
|     @SuppressWarnings("unused") @SerializedName("url") @Nullable private String originalUrl; | ||||
|     @SuppressWarnings("unused") @SerializedName("descriptionurl") @Nullable private String descriptionUrl; | ||||
|     @SuppressWarnings("unused") @SerializedName("descriptionshorturl") @Nullable private String descriptionShortUrl; | ||||
|     @SuppressWarnings("unused,NullableProblems") @SerializedName("mime") @NonNull | ||||
|     private String mimeType = "*/*"; | ||||
|     @SuppressWarnings("unused") @SerializedName("extmetadata")@Nullable private ExtMetadata metadata; | ||||
| 
 | ||||
|     @NonNull | ||||
|     public String getSource() { | ||||
|         return StringUtils.defaultString(source); | ||||
|     } | ||||
| 
 | ||||
|     public void setSource(@Nullable String source) { | ||||
|         this.source = source; | ||||
|     } | ||||
| 
 | ||||
|     public int getSize() { | ||||
|         return size; | ||||
|     } | ||||
| 
 | ||||
|     public int getWidth() { | ||||
|         return width; | ||||
|     } | ||||
| 
 | ||||
|     public int getHeight() { | ||||
|         return height; | ||||
|     } | ||||
| 
 | ||||
|     @NonNull public String getThumbUrl() { | ||||
|         return StringUtils.defaultString(thumbUrl); | ||||
|     } | ||||
| 
 | ||||
|     @NonNull public String getOriginalUrl() { | ||||
|         return StringUtils.defaultString(originalUrl); | ||||
|     } | ||||
| 
 | ||||
|     @Nullable public ExtMetadata getMetadata() { | ||||
|         return metadata; | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,292 @@ | |||
| package fr.free.nrw.commons.media.model; | ||||
| 
 | ||||
| import android.support.annotation.NonNull; | ||||
| import android.support.annotation.Nullable; | ||||
| 
 | ||||
| import com.google.gson.annotations.SerializedName; | ||||
| 
 | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import fr.free.nrw.commons.utils.StringUtils; | ||||
| 
 | ||||
| /** | ||||
|  * A class representing a standard page object as returned by the MediaWiki API. | ||||
|  */ | ||||
| public class MwQueryPage { | ||||
|     @SuppressWarnings("unused") | ||||
|     private int pageid; | ||||
|     @SuppressWarnings("unused") | ||||
|     private int ns; | ||||
|     @SuppressWarnings("unused") | ||||
|     private int index; | ||||
|     @SuppressWarnings("unused,NullableProblems") | ||||
|     @NonNull | ||||
|     private String title; | ||||
|     @SuppressWarnings("unused") | ||||
|     @Nullable | ||||
|     private List<LangLink> langlinks; | ||||
|     @SuppressWarnings("unused") | ||||
|     @Nullable | ||||
|     private List<Revision> revisions; | ||||
|     @SuppressWarnings("unused") | ||||
|     @Nullable | ||||
|     private List<Coordinates> coordinates; | ||||
|     @SuppressWarnings("unused") | ||||
|     @Nullable | ||||
|     private List<Category> categories; | ||||
|     @SuppressWarnings("unused") | ||||
|     @Nullable | ||||
|     private PageProps pageprops; | ||||
|     @SuppressWarnings("unused") | ||||
|     @Nullable | ||||
|     private String extract; | ||||
|     @SuppressWarnings("unused") | ||||
|     @Nullable | ||||
|     private Thumbnail thumbnail; | ||||
|     @SuppressWarnings("unused") | ||||
|     @Nullable | ||||
|     private String description; | ||||
|     @SuppressWarnings("unused") | ||||
|     @SerializedName("descriptionsource") | ||||
|     @Nullable | ||||
|     private String descriptionSource; | ||||
|     @SuppressWarnings("unused") | ||||
|     @SerializedName("imageinfo") | ||||
|     @Nullable | ||||
|     private List<ImageInfo> imageInfo; | ||||
|     @Nullable | ||||
|     private String redirectFrom; | ||||
|     @Nullable | ||||
|     private String convertedFrom; | ||||
|     @Nullable | ||||
|     private String convertedTo; | ||||
| 
 | ||||
|     @NonNull | ||||
|     public String title() { | ||||
|         return title; | ||||
|     } | ||||
| 
 | ||||
|     public int index() { | ||||
|         return index; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     public List<LangLink> langLinks() { | ||||
|         return langlinks; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     public List<Revision> revisions() { | ||||
|         return revisions; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     public List<Category> categories() { | ||||
|         return categories; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     public List<Coordinates> coordinates() { | ||||
|         // TODO: Handle null values in lists during deserialization, perhaps with a new | ||||
|         // @RequiredElements annotation and corresponding TypeAdapter | ||||
|         if (coordinates != null) { | ||||
|             coordinates.removeAll(Collections.singleton(null)); | ||||
|         } | ||||
|         return coordinates; | ||||
|     } | ||||
| 
 | ||||
|     public int pageId() { | ||||
|         return pageid; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     public PageProps pageProps() { | ||||
|         return pageprops; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     public String extract() { | ||||
|         return extract; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     public String thumbUrl() { | ||||
|         return thumbnail != null ? thumbnail.source() : null; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     public String description() { | ||||
|         return description; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     public String descriptionSource() { | ||||
|         return descriptionSource; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     public ImageInfo imageInfo() { | ||||
|         return imageInfo != null ? imageInfo.get(0) : null; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     public String redirectFrom() { | ||||
|         return redirectFrom; | ||||
|     } | ||||
| 
 | ||||
|     public void redirectFrom(@Nullable String from) { | ||||
|         redirectFrom = from; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     public String convertedFrom() { | ||||
|         return convertedFrom; | ||||
|     } | ||||
| 
 | ||||
|     public void convertedFrom(@Nullable String from) { | ||||
|         convertedFrom = from; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     public String convertedTo() { | ||||
|         return convertedTo; | ||||
|     } | ||||
| 
 | ||||
|     public void convertedTo(@Nullable String to) { | ||||
|         convertedTo = to; | ||||
|     } | ||||
| 
 | ||||
|     public void appendTitleFragment(@Nullable String fragment) { | ||||
|         title += "#" + fragment; | ||||
|     } | ||||
| 
 | ||||
|     public static class Revision { | ||||
|         @SuppressWarnings("unused,NullableProblems") | ||||
|         @SerializedName("contentformat") | ||||
|         @NonNull | ||||
|         private String contentFormat; | ||||
|         @SuppressWarnings("unused,NullableProblems") | ||||
|         @SerializedName("contentmodel") | ||||
|         @NonNull | ||||
|         private String contentModel; | ||||
|         @SuppressWarnings("unused,NullableProblems") | ||||
|         @SerializedName("timestamp") | ||||
|         @NonNull | ||||
|         private String timeStamp; | ||||
|         @SuppressWarnings("unused,NullableProblems") | ||||
|         @NonNull | ||||
|         private String content; | ||||
| 
 | ||||
|         @NonNull | ||||
|         public String content() { | ||||
|             return content; | ||||
|         } | ||||
| 
 | ||||
|         @NonNull | ||||
|         public String timeStamp() { | ||||
|             return StringUtils.defaultString(timeStamp); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public static class LangLink { | ||||
|         @SuppressWarnings("unused,NullableProblems") | ||||
|         @NonNull | ||||
|         private String lang; | ||||
|         @SuppressWarnings("unused,NullableProblems") | ||||
|         @NonNull | ||||
|         private String title; | ||||
| 
 | ||||
|         @NonNull | ||||
|         public String lang() { | ||||
|             return lang; | ||||
|         } | ||||
| 
 | ||||
|         @NonNull | ||||
|         public String title() { | ||||
|             return title; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public static class Coordinates { | ||||
|         @SuppressWarnings("unused") | ||||
|         @Nullable | ||||
|         private Double lat; | ||||
|         @SuppressWarnings("unused") | ||||
|         @Nullable | ||||
|         private Double lon; | ||||
| 
 | ||||
|         @Nullable | ||||
|         public Double lat() { | ||||
|             return lat; | ||||
|         } | ||||
| 
 | ||||
|         @Nullable | ||||
|         public Double lon() { | ||||
|             return lon; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     static class Thumbnail { | ||||
|         @SuppressWarnings("unused") | ||||
|         private String source; | ||||
|         @SuppressWarnings("unused") | ||||
|         private int width; | ||||
|         @SuppressWarnings("unused") | ||||
|         private int height; | ||||
| 
 | ||||
|         String source() { | ||||
|             return source; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public static class PageProps { | ||||
|         @SuppressWarnings("unused") | ||||
|         @SerializedName("wikibase_item") | ||||
|         @Nullable | ||||
|         private String wikiBaseItem; | ||||
|         @SuppressWarnings("unused") | ||||
|         @Nullable | ||||
|         private String displaytitle; | ||||
|         @SuppressWarnings("unused") | ||||
|         @Nullable | ||||
|         private String disambiguation; | ||||
| 
 | ||||
|         @Nullable | ||||
|         public String getDisplayTitle() { | ||||
|             return displaytitle; | ||||
|         } | ||||
| 
 | ||||
|         @NonNull | ||||
|         public String getWikiBaseItem() { | ||||
|             return StringUtils.defaultString(wikiBaseItem); | ||||
|         } | ||||
| 
 | ||||
|         public boolean isDisambiguation() { | ||||
|             return disambiguation != null; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public static class Category { | ||||
|         @SuppressWarnings("unused") | ||||
|         private int ns; | ||||
|         @SuppressWarnings("unused,NullableProblems") | ||||
|         @Nullable | ||||
|         private String title; | ||||
|         @SuppressWarnings("unused") | ||||
|         private boolean hidden; | ||||
| 
 | ||||
|         public int ns() { | ||||
|             return ns; | ||||
|         } | ||||
| 
 | ||||
|         @NonNull | ||||
|         public String title() { | ||||
|             return StringUtils.defaultString(title); | ||||
|         } | ||||
| 
 | ||||
|         public boolean hidden() { | ||||
|             return hidden; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -5,7 +5,6 @@ import android.net.Uri; | |||
| import android.os.Build; | ||||
| import android.support.annotation.NonNull; | ||||
| import android.support.annotation.Nullable; | ||||
| import android.support.annotation.VisibleForTesting; | ||||
| import android.text.TextUtils; | ||||
| 
 | ||||
| import com.google.gson.Gson; | ||||
|  | @ -21,7 +20,6 @@ import org.apache.http.impl.client.DefaultHttpClient; | |||
| import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; | ||||
| import org.apache.http.params.BasicHttpParams; | ||||
| import org.apache.http.params.CoreProtocolPNames; | ||||
| import org.apache.http.util.EntityUtils; | ||||
| import org.w3c.dom.Element; | ||||
| import org.w3c.dom.Node; | ||||
| import org.w3c.dom.NodeList; | ||||
|  | @ -41,12 +39,8 @@ import java.util.concurrent.Callable; | |||
| 
 | ||||
| import fr.free.nrw.commons.BuildConfig; | ||||
| import fr.free.nrw.commons.Media; | ||||
| import fr.free.nrw.commons.PageTitle; | ||||
| import fr.free.nrw.commons.R; | ||||
| import fr.free.nrw.commons.achievements.FeaturedImages; | ||||
| import fr.free.nrw.commons.achievements.FeedbackResponse; | ||||
| import fr.free.nrw.commons.auth.AccountUtil; | ||||
| import fr.free.nrw.commons.campaigns.CampaignResponseDTO; | ||||
| import fr.free.nrw.commons.category.CategoryImageUtils; | ||||
| import fr.free.nrw.commons.category.QueryContinue; | ||||
| import fr.free.nrw.commons.kvstore.BasicKvStore; | ||||
|  | @ -59,10 +53,6 @@ import fr.free.nrw.commons.utils.ViewUtil; | |||
| import in.yuvi.http.fluent.Http; | ||||
| import io.reactivex.Observable; | ||||
| import io.reactivex.Single; | ||||
| import okhttp3.HttpUrl; | ||||
| import okhttp3.OkHttpClient; | ||||
| import okhttp3.Request; | ||||
| import okhttp3.Response; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
| import static fr.free.nrw.commons.utils.ContinueUtils.getQueryContinue; | ||||
|  | @ -71,8 +61,6 @@ import static fr.free.nrw.commons.utils.ContinueUtils.getQueryContinue; | |||
|  * @author Addshore | ||||
|  */ | ||||
| public class ApacheHttpClientMediaWikiApi implements MediaWikiApi { | ||||
|     private String wikiMediaToolforgeUrl = "https://tools.wmflabs.org/"; | ||||
| 
 | ||||
|     private static final String THUMB_SIZE = "640"; | ||||
|     private AbstractHttpClient httpClient; | ||||
|     private CustomMwApi api; | ||||
|  | @ -81,9 +69,6 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi { | |||
|     private BasicKvStore defaultKvStore; | ||||
|     private BasicKvStore categoryKvStore; | ||||
|     private Gson gson; | ||||
|     private final OkHttpClient okHttpClient; | ||||
|     private final String WIKIMEDIA_CAMPAIGNS_BASE_URL = | ||||
|             "https://raw.githubusercontent.com/commons-app/campaigns/master/campaigns.json"; | ||||
| 
 | ||||
|     private final String ERROR_CODE_BAD_TOKEN = "badtoken"; | ||||
| 
 | ||||
|  | @ -92,10 +77,8 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi { | |||
|                                         String wikidatApiURL, | ||||
|                                         BasicKvStore defaultKvStore, | ||||
|                                         BasicKvStore categoryKvStore, | ||||
|                                         Gson gson, | ||||
|                                         OkHttpClient okHttpClient) { | ||||
|                                         Gson gson) { | ||||
|         this.context = context; | ||||
|         this.okHttpClient = okHttpClient; | ||||
|         BasicHttpParams params = new BasicHttpParams(); | ||||
|         SchemeRegistry schemeRegistry = new SchemeRegistry(); | ||||
|         schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); | ||||
|  | @ -120,11 +103,6 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi { | |||
|         return "Commons/" + ConfigUtils.getVersionNameWithSha(context) + " (https://mediawiki.org/wiki/Apps/Commons) Android/" + Build.VERSION.RELEASE; | ||||
|     } | ||||
| 
 | ||||
|     @VisibleForTesting | ||||
|     public void setWikiMediaToolforgeUrl(String wikiMediaToolforgeUrl) { | ||||
|         this.wikiMediaToolforgeUrl = wikiMediaToolforgeUrl; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param username String | ||||
|      * @param password String | ||||
|  | @ -760,44 +738,6 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi { | |||
|         return CategoryImageUtils.getMediaList(childNodes); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * This method takes search keyword as input and returns a list of  Media objects filtered using search query | ||||
|      * It uses the generator query API to get the images searched using a query, 25 at a time. | ||||
|      * @param query keyword to search images on commons | ||||
|      * @return | ||||
|      */ | ||||
|     @Override | ||||
|     @NonNull | ||||
|     public List<Media> searchImages(String query, int offset) { | ||||
|         CustomApiResult apiResult=null; | ||||
|         try { | ||||
|             apiResult= api.action("query") | ||||
|                     .param("format", "xml") | ||||
|                     .param("generator", "search") | ||||
|                     .param("gsrwhat", "text") | ||||
|                     .param("gsrnamespace", "6") | ||||
|                     .param("gsrlimit", "25") | ||||
|                     .param("gsroffset",offset) | ||||
|                     .param("gsrsearch", query) | ||||
|                     .param("prop", "imageinfo") | ||||
|                     .param("iiprop", "url|extmetadata") | ||||
|                     .get(); | ||||
|         } catch (IOException e) { | ||||
|             Timber.e(e, "Failed to obtain searchImages"); | ||||
|         } | ||||
| 
 | ||||
|         CustomApiResult searchImagesNode = apiResult.getNode("/api/query/pages"); | ||||
|         if (searchImagesNode == null | ||||
|                 || searchImagesNode.getDocument() == null | ||||
|                 || searchImagesNode.getDocument().getChildNodes() == null | ||||
|                 || searchImagesNode.getDocument().getChildNodes().getLength() == 0) { | ||||
|             return new ArrayList<>(); | ||||
|         } | ||||
| 
 | ||||
|         NodeList childNodes = searchImagesNode.getDocument().getChildNodes(); | ||||
|         return CategoryImageUtils.getMediaList(childNodes); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * This method takes search keyword as input and returns a list of categories objects filtered using search query | ||||
|      * It uses the generator query API to get the categories searched using a query, 25 at a time. | ||||
|  | @ -924,25 +864,6 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @NonNull | ||||
|     public Single<Integer> getUploadCount(String userName) { | ||||
|         final String uploadCountUrlTemplate = | ||||
|                 wikiMediaToolforgeUrl + "urbanecmbot/commonsmisc/uploadsbyuser.py"; | ||||
| 
 | ||||
|         return Single.fromCallable(() -> { | ||||
|             String url = String.format( | ||||
|                     Locale.ENGLISH, | ||||
|                     uploadCountUrlTemplate, | ||||
|                     new PageTitle(userName).getText()); | ||||
|             HttpResponse response = Http.get(url).use(httpClient) | ||||
|                     .data("user", userName) | ||||
|                     .asResponse(); | ||||
|             String uploadCount = EntityUtils.toString(response.getEntity()).trim(); | ||||
|             return Integer.parseInt(uploadCount); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| 
 | ||||
|      * Checks to see if a user is currently blocked from Commons | ||||
|  | @ -976,86 +897,6 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi { | |||
|         return userBlocked; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * This takes userName as input, which is then used to fetch the feedback/achievements | ||||
|      * statistics using OkHttp and JavaRx. This function return JSONObject | ||||
|      * @param userName MediaWiki user name | ||||
|      * @return | ||||
|      */ | ||||
|     @Override | ||||
|     public Single<FeedbackResponse> getAchievements(String userName) { | ||||
|         final String fetchAchievementUrlTemplate = | ||||
|                 wikiMediaToolforgeUrl + "urbanecmbot/commonsmisc/feedback.py"; | ||||
|         return Single.fromCallable(() -> { | ||||
|             String url = String.format( | ||||
|                     Locale.ENGLISH, | ||||
|                     fetchAchievementUrlTemplate, | ||||
|                     new PageTitle(userName).getText()); | ||||
|             HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder(); | ||||
|             urlBuilder.addQueryParameter("user", userName); | ||||
|             Timber.i("Url %s", urlBuilder.toString()); | ||||
|             Request request = new Request.Builder() | ||||
|                     .url(urlBuilder.toString()) | ||||
|                     .build(); | ||||
|             Response response = okHttpClient.newCall(request).execute(); | ||||
|             if (response != null && response.body() != null && response.isSuccessful()) { | ||||
|                 String json = response.body().string(); | ||||
|                 if (json == null) { | ||||
|                     return null; | ||||
|                 } | ||||
|                 Timber.d("Response for achievements is %s", json); | ||||
|                 try { | ||||
|                     return gson.fromJson(json, FeedbackResponse.class); | ||||
|                 } | ||||
|                 catch (Exception e){ | ||||
|                     return new FeedbackResponse("",0,0,0,new FeaturedImages(0,0),0,"",0); | ||||
|                 } | ||||
| 
 | ||||
| 
 | ||||
|             } | ||||
|             return null; | ||||
|         }); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * The method returns the picture of the day | ||||
|      * | ||||
|      * @return Media object corresponding to the picture of the day | ||||
|      */ | ||||
|     @Override | ||||
|     @Nullable | ||||
|     public Single<Media> getPictureOfTheDay() { | ||||
|         return Single.fromCallable(() -> { | ||||
|             CustomApiResult apiResult = null; | ||||
|             try { | ||||
|                 String template = "Template:Potd/" + DateUtils.getCurrentDate(); | ||||
|                 CustomMwApi.RequestBuilder requestBuilder = api.action("query") | ||||
|                         .param("generator", "images") | ||||
|                         .param("format", "xml") | ||||
|                         .param("titles", template) | ||||
|                         .param("prop", "imageinfo") | ||||
|                         .param("iiprop", "url|extmetadata"); | ||||
| 
 | ||||
|                 apiResult = requestBuilder.get(); | ||||
|             } catch (IOException e) { | ||||
|                 Timber.e(e, "Failed to obtain searchCategories"); | ||||
|             } | ||||
| 
 | ||||
|             if (apiResult == null) { | ||||
|                 return null; | ||||
|             } | ||||
| 
 | ||||
|             CustomApiResult imageNode = apiResult.getNode("/api/query/pages/page"); | ||||
|             if (imageNode == null | ||||
|                     || imageNode.getDocument() == null) { | ||||
|                 return null; | ||||
|             } | ||||
| 
 | ||||
|             return CategoryImageUtils.getMediaFromPage(imageNode.getDocument()); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     private Date parseMWDate(String mwDate) { | ||||
|         SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH); // Assuming MW always gives me UTC | ||||
|         isoFormat.setTimeZone(TimeZone.getTimeZone("UTC")); | ||||
|  | @ -1076,19 +917,4 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi { | |||
|             Timber.e(e, "Error occurred while logging out"); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override public Single<CampaignResponseDTO> getCampaigns() { | ||||
|         return Single.fromCallable(() -> { | ||||
|             Request request = new Request.Builder().url(WIKIMEDIA_CAMPAIGNS_BASE_URL).build(); | ||||
|             Response response = okHttpClient.newCall(request).execute(); | ||||
|             if (response != null && response.body() != null && response.isSuccessful()) { | ||||
|                 String json = response.body().string(); | ||||
|                 if (json == null) { | ||||
|                     return null; | ||||
|                 } | ||||
|                 return gson.fromJson(json, CampaignResponseDTO.class); | ||||
|             } | ||||
|             return null; | ||||
|         }); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -11,6 +11,8 @@ import java.util.List; | |||
| 
 | ||||
| import fr.free.nrw.commons.Media; | ||||
| import fr.free.nrw.commons.achievements.FeedbackResponse; | ||||
| import fr.free.nrw.commons.location.LatLng; | ||||
| import fr.free.nrw.commons.nearby.Place; | ||||
| import fr.free.nrw.commons.notification.Notification; | ||||
| import io.reactivex.Observable; | ||||
| import io.reactivex.Single; | ||||
|  | @ -48,9 +50,6 @@ public interface MediaWikiApi { | |||
| 
 | ||||
|     List<String> getParentCategoryList(String categoryName); | ||||
| 
 | ||||
|     @NonNull | ||||
|     List<Media> searchImages(String title, int offset); | ||||
| 
 | ||||
|     @NonNull | ||||
|     List<String> searchCategory(String title, int offset); | ||||
| 
 | ||||
|  | @ -98,19 +97,11 @@ public interface MediaWikiApi { | |||
|     @NonNull | ||||
|     LogEventResult logEvents(String user, String lastModified, String queryContinue, int limit) throws IOException; | ||||
| 
 | ||||
|     @NonNull | ||||
|     Single<Integer> getUploadCount(String userName); | ||||
| 
 | ||||
|     boolean isUserBlockedFromCommons(); | ||||
| 
 | ||||
|     Single<FeedbackResponse> getAchievements(String userName); | ||||
| 
 | ||||
|     Single<Media> getPictureOfTheDay(); | ||||
| 
 | ||||
|     void logout(); | ||||
| 
 | ||||
|     Single<CampaignResponseDTO> getCampaigns(); | ||||
| 
 | ||||
|     interface ProgressListener { | ||||
|         void onProgress(long transferred, long total); | ||||
|     } | ||||
|  |  | |||
|  | @ -0,0 +1,253 @@ | |||
| package fr.free.nrw.commons.mwapi; | ||||
| 
 | ||||
| import android.support.annotation.NonNull; | ||||
| import android.support.annotation.Nullable; | ||||
| 
 | ||||
| import com.google.gson.Gson; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| import java.util.Locale; | ||||
| 
 | ||||
| import javax.inject.Inject; | ||||
| import javax.inject.Singleton; | ||||
| 
 | ||||
| import fr.free.nrw.commons.Media; | ||||
| import fr.free.nrw.commons.PageTitle; | ||||
| import fr.free.nrw.commons.achievements.FeaturedImages; | ||||
| import fr.free.nrw.commons.achievements.FeedbackResponse; | ||||
| import fr.free.nrw.commons.campaigns.CampaignResponseDTO; | ||||
| import fr.free.nrw.commons.location.LatLng; | ||||
| import fr.free.nrw.commons.media.model.MwQueryPage; | ||||
| import fr.free.nrw.commons.mwapi.model.MwQueryResponse; | ||||
| import fr.free.nrw.commons.nearby.Place; | ||||
| import fr.free.nrw.commons.nearby.model.NearbyResponse; | ||||
| import fr.free.nrw.commons.nearby.model.NearbyResultItem; | ||||
| import fr.free.nrw.commons.upload.FileUtils; | ||||
| import fr.free.nrw.commons.utils.DateUtils; | ||||
| import io.reactivex.Observable; | ||||
| import io.reactivex.Single; | ||||
| import okhttp3.HttpUrl; | ||||
| import okhttp3.OkHttpClient; | ||||
| import okhttp3.Request; | ||||
| import okhttp3.Response; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
| @Singleton | ||||
| public class OkHttpJsonApiClient { | ||||
|     private final OkHttpClient okHttpClient; | ||||
|     private final HttpUrl wikiMediaToolforgeUrl; | ||||
|     private final String sparqlQueryUrl; | ||||
|     private final String campaignsUrl; | ||||
|     private final String commonsBaseUrl; | ||||
|     private Gson gson; | ||||
| 
 | ||||
| 
 | ||||
|     @Inject | ||||
|     public OkHttpJsonApiClient(OkHttpClient okHttpClient, | ||||
|                                HttpUrl wikiMediaToolforgeUrl, | ||||
|                                String sparqlQueryUrl, | ||||
|                                String campaignsUrl, | ||||
|                                String commonsBaseUrl, | ||||
|                                Gson gson) { | ||||
|         this.okHttpClient = okHttpClient; | ||||
|         this.wikiMediaToolforgeUrl = wikiMediaToolforgeUrl; | ||||
|         this.sparqlQueryUrl = sparqlQueryUrl; | ||||
|         this.campaignsUrl = campaignsUrl; | ||||
|         this.commonsBaseUrl = commonsBaseUrl; | ||||
|         this.gson = gson; | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     public Single<Integer> getUploadCount(String userName) { | ||||
|         HttpUrl.Builder urlBuilder = wikiMediaToolforgeUrl.newBuilder(); | ||||
|         urlBuilder | ||||
|                 .addPathSegments("urbanecmbot/commonsmisc/uploadsbyuser.py") | ||||
|                 .addQueryParameter("user", userName); | ||||
|         Request request = new Request.Builder() | ||||
|                 .url(urlBuilder.build()) | ||||
|                 .build(); | ||||
| 
 | ||||
|         return Single.fromCallable(() -> { | ||||
|             Response response = okHttpClient.newCall(request).execute(); | ||||
|             if (response != null && response.isSuccessful()) { | ||||
|                 return Integer.parseInt(response.body().string().trim()); | ||||
|             } | ||||
|             return 0; | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * This takes userName as input, which is then used to fetch the feedback/achievements | ||||
|      * statistics using OkHttp and JavaRx. This function return JSONObject | ||||
|      * | ||||
|      * @param userName MediaWiki user name | ||||
|      * @return | ||||
|      */ | ||||
|     public Single<FeedbackResponse> getAchievements(String userName) { | ||||
|         final String fetchAchievementUrlTemplate = | ||||
|                 wikiMediaToolforgeUrl + "urbanecmbot/commonsmisc/feedback.py"; | ||||
|         return Single.fromCallable(() -> { | ||||
|             String url = String.format( | ||||
|                     Locale.ENGLISH, | ||||
|                     fetchAchievementUrlTemplate, | ||||
|                     new PageTitle(userName).getText()); | ||||
|             HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder(); | ||||
|             urlBuilder.addQueryParameter("user", userName); | ||||
|             Timber.i("Url %s", urlBuilder.toString()); | ||||
|             Request request = new Request.Builder() | ||||
|                     .url(urlBuilder.toString()) | ||||
|                     .build(); | ||||
|             Response response = okHttpClient.newCall(request).execute(); | ||||
|             if (response != null && response.body() != null && response.isSuccessful()) { | ||||
|                 String json = response.body().string(); | ||||
|                 if (json == null) { | ||||
|                     return null; | ||||
|                 } | ||||
|                 Timber.d("Response for achievements is %s", json); | ||||
|                 try { | ||||
|                     return gson.fromJson(json, FeedbackResponse.class); | ||||
|                 } catch (Exception e) { | ||||
|                     return new FeedbackResponse("", 0, 0, 0, new FeaturedImages(0, 0), 0, "", 0); | ||||
|                 } | ||||
| 
 | ||||
| 
 | ||||
|             } | ||||
|             return null; | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     public Observable<List<Place>> getNearbyPlaces(LatLng cur, String lang, double radius) throws IOException { | ||||
|         String wikidataQuery = FileUtils.readFromResource("/queries/nearby_query.rq"); | ||||
|         String query = wikidataQuery | ||||
|                 .replace("${RAD}", String.format(Locale.ROOT, "%.2f", radius)) | ||||
|                 .replace("${LAT}", String.format(Locale.ROOT, "%.4f", cur.getLatitude())) | ||||
|                 .replace("${LONG}", String.format(Locale.ROOT, "%.4f", cur.getLongitude())) | ||||
|                 .replace("${LANG}", lang); | ||||
| 
 | ||||
|         HttpUrl.Builder urlBuilder = HttpUrl | ||||
|                 .parse(sparqlQueryUrl) | ||||
|                 .newBuilder() | ||||
|                 .addQueryParameter("query", query) | ||||
|                 .addQueryParameter("format", "json"); | ||||
| 
 | ||||
|         Request request = new Request.Builder() | ||||
|                 .url(urlBuilder.build()) | ||||
|                 .build(); | ||||
| 
 | ||||
|         return Observable.fromCallable(() -> { | ||||
|             Response response = okHttpClient.newCall(request).execute(); | ||||
|             if (response != null && response.body() != null && response.isSuccessful()) { | ||||
|                 String json = response.body().string(); | ||||
|                 if (json == null) { | ||||
|                     return new ArrayList<>(); | ||||
|                 } | ||||
|                 NearbyResponse nearbyResponse = gson.fromJson(json, NearbyResponse.class); | ||||
|                 List<NearbyResultItem> bindings = nearbyResponse.getResults().getBindings(); | ||||
|                 List<Place> places = new ArrayList<>(); | ||||
|                 for (NearbyResultItem item : bindings) { | ||||
|                     places.add(Place.from(item)); | ||||
|                 } | ||||
|                 return places; | ||||
|             } | ||||
|             return new ArrayList<>(); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     public Single<CampaignResponseDTO> getCampaigns() { | ||||
|         return Single.fromCallable(() -> { | ||||
|             Request request = new Request.Builder().url(campaignsUrl) | ||||
|                     .build(); | ||||
|             Response response = okHttpClient.newCall(request).execute(); | ||||
|             if (response != null && response.body() != null && response.isSuccessful()) { | ||||
|                 String json = response.body().string(); | ||||
|                 if (json == null) { | ||||
|                     return null; | ||||
|                 } | ||||
|                 return gson.fromJson(json, CampaignResponseDTO.class); | ||||
|             } | ||||
|             return null; | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * The method returns the picture of the day | ||||
|      * | ||||
|      * @return Media object corresponding to the picture of the day | ||||
|      */ | ||||
|     @Nullable | ||||
|     public Single<Media> getPictureOfTheDay() { | ||||
|         String template = "Template:Potd/" + DateUtils.getCurrentDate(); | ||||
|         HttpUrl.Builder urlBuilder = HttpUrl | ||||
|                 .parse(commonsBaseUrl) | ||||
|                 .newBuilder() | ||||
|                 .addQueryParameter("action", "query") | ||||
|                 .addQueryParameter("generator", "images") | ||||
|                 .addQueryParameter("format", "json") | ||||
|                 .addQueryParameter("titles", template) | ||||
|                 .addQueryParameter("prop", "imageinfo") | ||||
|                 .addQueryParameter("iiprop", "url|extmetadata"); | ||||
| 
 | ||||
|         Request request = new Request.Builder() | ||||
|                 .url(urlBuilder.build()) | ||||
|                 .build(); | ||||
| 
 | ||||
|         return Single.fromCallable(() -> { | ||||
|             Response response = okHttpClient.newCall(request).execute(); | ||||
|             if (response != null && response.body() != null && response.isSuccessful()) { | ||||
|                 String json = response.body().string(); | ||||
|                 if (json == null) { | ||||
|                     return null; | ||||
|                 } | ||||
|                 MwQueryResponse mwQueryPage = gson.fromJson(json, MwQueryResponse.class); | ||||
|                 return Media.from(mwQueryPage.query().firstPage()); | ||||
|             } | ||||
|             return null; | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * This method takes search keyword as input and returns a list of  Media objects filtered using search query | ||||
|      * It uses the generator query API to get the images searched using a query, 25 at a time. | ||||
|      * @param query keyword to search images on commons | ||||
|      * @return | ||||
|      */ | ||||
|     @Nullable | ||||
|     public Single<List<Media>> searchImages(String query, int offset) { | ||||
|         HttpUrl.Builder urlBuilder = HttpUrl | ||||
|                 .parse(commonsBaseUrl) | ||||
|                 .newBuilder() | ||||
|                 .addQueryParameter("action", "query") | ||||
|                 .addQueryParameter("generator", "search") | ||||
|                 .addQueryParameter("format", "json") | ||||
|                 .addQueryParameter("gsrwhat", "text") | ||||
|                 .addQueryParameter("gsrnamespace", "6") | ||||
|                 .addQueryParameter("gsrlimit", "25") | ||||
|                 .addQueryParameter("gsroffset", String.valueOf(offset)) | ||||
|                 .addQueryParameter("gsrsearch", query) | ||||
|                 .addQueryParameter("prop", "imageinfo") | ||||
|                 .addQueryParameter("iiprop", "url|extmetadata"); | ||||
| 
 | ||||
|         Request request = new Request.Builder() | ||||
|                 .url(urlBuilder.build()) | ||||
|                 .build(); | ||||
| 
 | ||||
|         return Single.fromCallable(() -> { | ||||
|             Response response = okHttpClient.newCall(request).execute(); | ||||
|             List<Media> mediaList = new ArrayList<>(); | ||||
|             if (response != null && response.body() != null && response.isSuccessful()) { | ||||
|                 String json = response.body().string(); | ||||
|                 if (json == null) { | ||||
|                     return mediaList; | ||||
|                 } | ||||
|                 MwQueryResponse mwQueryResponse = gson.fromJson(json, MwQueryResponse.class); | ||||
|                 List<MwQueryPage> pages = mwQueryResponse.query().pages(); | ||||
|                 for (MwQueryPage page : pages) { | ||||
|                     mediaList.add(Media.from(page)); | ||||
|                 } | ||||
|             } | ||||
|             return mediaList; | ||||
|         }); | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,30 @@ | |||
| package fr.free.nrw.commons.mwapi.model; | ||||
| 
 | ||||
| import android.support.annotation.NonNull; | ||||
| import android.support.annotation.Nullable; | ||||
| 
 | ||||
| public class MwException extends RuntimeException { | ||||
|     @SuppressWarnings("unused") | ||||
|     @NonNull | ||||
|     private final MwServiceError error; | ||||
| 
 | ||||
|     public MwException(@NonNull MwServiceError error) { | ||||
|         this.error = error; | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     public MwServiceError getError() { | ||||
|         return error; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     public String getTitle() { | ||||
|         return error.getTitle(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @Nullable | ||||
|     public String getMessage() { | ||||
|         return error.getDetails(); | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,39 @@ | |||
| package fr.free.nrw.commons.mwapi.model; | ||||
| 
 | ||||
| import android.support.annotation.Nullable; | ||||
| import android.support.annotation.VisibleForTesting; | ||||
| 
 | ||||
| import com.google.gson.annotations.SerializedName; | ||||
| 
 | ||||
| import java.util.Map; | ||||
| 
 | ||||
| public class MwQueryResponse extends MwResponse { | ||||
| 
 | ||||
|     @SuppressWarnings("unused") @SerializedName("batchcomplete") private boolean batchComplete; | ||||
| 
 | ||||
|     @SuppressWarnings("unused") @SerializedName("continue") @Nullable | ||||
|     private Map<String, String> continuation; | ||||
| 
 | ||||
|     @Nullable private MwQueryResult query; | ||||
| 
 | ||||
|     public boolean batchComplete() { | ||||
|         return batchComplete; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable public Map<String, String> continuation() { | ||||
|         return continuation; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable public MwQueryResult query() { | ||||
|         return query; | ||||
|     } | ||||
| 
 | ||||
|     public boolean success() { | ||||
|         return query != null; | ||||
|     } | ||||
| 
 | ||||
|     @VisibleForTesting | ||||
|     protected void setQuery(@Nullable MwQueryResult query) { | ||||
|         this.query = query; | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,44 @@ | |||
| package fr.free.nrw.commons.mwapi.model; | ||||
| 
 | ||||
| import android.support.annotation.NonNull; | ||||
| import android.support.annotation.Nullable; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| 
 | ||||
| import fr.free.nrw.commons.media.model.ImageInfo; | ||||
| import fr.free.nrw.commons.media.model.MwQueryPage; | ||||
| 
 | ||||
| public class MwQueryResult { | ||||
|     @SuppressWarnings("unused") | ||||
|     @Nullable | ||||
|     private HashMap<String, MwQueryPage> pages; | ||||
| 
 | ||||
|     @NonNull | ||||
|     public List<MwQueryPage> pages() { | ||||
|         if (pages == null) { | ||||
|             return new ArrayList<>(); | ||||
|         } | ||||
|         return new ArrayList<>(pages.values()); | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     public MwQueryPage firstPage() { | ||||
|         return pages().get(0); | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     public Map<String, ImageInfo> images() { | ||||
|         Map<String, ImageInfo> result = new HashMap<>(); | ||||
|         if (pages != null) { | ||||
|             for (MwQueryPage page : pages()) { | ||||
|                 if (page.imageInfo() != null) { | ||||
|                     result.put(page.title(), page.imageInfo()); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return result; | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,38 @@ | |||
| package fr.free.nrw.commons.mwapi.model; | ||||
| 
 | ||||
| import android.support.annotation.NonNull; | ||||
| import android.support.annotation.Nullable; | ||||
| 
 | ||||
| import com.google.gson.annotations.SerializedName; | ||||
| 
 | ||||
| import java.util.Map; | ||||
| 
 | ||||
| import fr.free.nrw.commons.json.PostProcessingTypeAdapter; | ||||
| 
 | ||||
| public abstract class MwResponse implements PostProcessingTypeAdapter.PostProcessable { | ||||
|     @SuppressWarnings("unused") | ||||
|     @Nullable | ||||
|     private MwServiceError error; | ||||
| 
 | ||||
|     @SuppressWarnings("unused") | ||||
|     @Nullable | ||||
|     private Map<String, Warning> warnings; | ||||
| 
 | ||||
|     @SuppressWarnings("unused,NullableProblems") | ||||
|     @SerializedName("servedby") | ||||
|     @NonNull | ||||
|     private String servedBy; | ||||
| 
 | ||||
|     @Override | ||||
|     public void postProcess() { | ||||
|         if (error != null) { | ||||
|             throw new MwException(error); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private class Warning { | ||||
|         @SuppressWarnings("unused,NullableProblems") | ||||
|         @NonNull | ||||
|         private String warnings; | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,85 @@ | |||
| package fr.free.nrw.commons.mwapi.model; | ||||
| 
 | ||||
| import android.support.annotation.NonNull; | ||||
| import android.support.annotation.Nullable; | ||||
| 
 | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import fr.free.nrw.commons.utils.StringUtils; | ||||
| 
 | ||||
| /** | ||||
|  * Gson POJO for a MediaWiki API error. | ||||
|  */ | ||||
| public class MwServiceError implements ServiceError { | ||||
|     @SuppressWarnings("unused") | ||||
|     @Nullable | ||||
|     private String code; | ||||
|     @SuppressWarnings("unused") | ||||
|     @Nullable | ||||
|     private String info; | ||||
|     @SuppressWarnings("unused") | ||||
|     @Nullable | ||||
|     private String docref; | ||||
|     @SuppressWarnings("unused") | ||||
|     @NonNull | ||||
|     private List<Message> messages = Collections.emptyList(); | ||||
| 
 | ||||
|     @Override | ||||
|     @NonNull | ||||
|     public String getTitle() { | ||||
|         return StringUtils.defaultString(code); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @NonNull | ||||
|     public String getDetails() { | ||||
|         return StringUtils.defaultString(info); | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     public String getDocRef() { | ||||
|         return docref; | ||||
|     } | ||||
| 
 | ||||
|     public boolean badToken() { | ||||
|         return "badtoken".equals(code); | ||||
|     } | ||||
| 
 | ||||
|     public boolean badLoginState() { | ||||
|         return "assertuserfailed".equals(code); | ||||
|     } | ||||
| 
 | ||||
|     public boolean hasMessageName(@NonNull String messageName) { | ||||
|         for (Message msg : messages) { | ||||
|             if (messageName.equals(msg.name)) { | ||||
|                 return true; | ||||
|             } | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     public String getMessageHtml(@NonNull String messageName) { | ||||
|         for (Message msg : messages) { | ||||
|             if (messageName.equals(msg.name)) { | ||||
|                 return msg.html(); | ||||
|             } | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     private static final class Message { | ||||
|         @SuppressWarnings("unused") | ||||
|         @Nullable | ||||
|         private String name; | ||||
|         @SuppressWarnings("unused") | ||||
|         @Nullable | ||||
|         private String html; | ||||
| 
 | ||||
|         @NonNull | ||||
|         private String html() { | ||||
|             return StringUtils.defaultString(html); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,13 @@ | |||
| package fr.free.nrw.commons.mwapi.model; | ||||
| 
 | ||||
| import android.support.annotation.NonNull; | ||||
| 
 | ||||
| /** | ||||
|  * The API reported an error in the payload. | ||||
|  */ | ||||
| public interface ServiceError { | ||||
|     @NonNull | ||||
|     String getTitle(); | ||||
| 
 | ||||
|     @NonNull String getDetails(); | ||||
| } | ||||
|  | @ -112,7 +112,7 @@ public class NearbyController { | |||
|      * @param placeList list of nearby places in Place data type | ||||
|      * @return Place list that holds nearby places | ||||
|      */ | ||||
|     public static List<Place> loadAttractionsFromLocationToPlaces( | ||||
|     static List<Place> loadAttractionsFromLocationToPlaces( | ||||
|             LatLng curLatLng, | ||||
|             List<Place> placeList) { | ||||
|         placeList = placeList.subList(0, Math.min(placeList.size(), MAX_RESULTS)); | ||||
|  |  | |||
|  | @ -1,49 +1,37 @@ | |||
| package fr.free.nrw.commons.nearby; | ||||
| 
 | ||||
| import android.net.Uri; | ||||
| 
 | ||||
| import java.io.BufferedReader; | ||||
| import java.io.IOException; | ||||
| import java.io.InputStreamReader; | ||||
| import java.io.InterruptedIOException; | ||||
| import java.net.URL; | ||||
| import java.net.URLConnection; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Arrays; | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| import java.util.Locale; | ||||
| import java.util.regex.Matcher; | ||||
| import java.util.regex.Pattern; | ||||
| 
 | ||||
| import fr.free.nrw.commons.Utils; | ||||
| import javax.inject.Inject; | ||||
| import javax.inject.Singleton; | ||||
| 
 | ||||
| import fr.free.nrw.commons.location.LatLng; | ||||
| import fr.free.nrw.commons.upload.FileUtils; | ||||
| import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
| /** | ||||
|  * Handles the Wikidata query to obtain Places around search location | ||||
|  */ | ||||
| @Singleton | ||||
| public class NearbyPlaces { | ||||
| 
 | ||||
|     private static final double INITIAL_RADIUS = 1.0; // in kilometers | ||||
|     private static final double RADIUS_MULTIPLIER = 1.618; | ||||
|     private static final Uri WIKIDATA_QUERY_URL = Uri.parse("https://query.wikidata.org/sparql"); | ||||
|     private static final Uri WIKIDATA_QUERY_UI_URL = Uri.parse("https://query.wikidata.org/"); | ||||
|     private final String wikidataQuery; | ||||
|     public double radius = INITIAL_RADIUS; | ||||
| 
 | ||||
|     private final OkHttpJsonApiClient okHttpJsonApiClient; | ||||
| 
 | ||||
|     /** | ||||
|      * Reads Wikidata query to check nearby wikidata items which needs picture, with a circular | ||||
|      * search. As a point is center of a circle with a radius will be set later. | ||||
|      * @param okHttpJsonApiClient | ||||
|      */ | ||||
|     public NearbyPlaces() { | ||||
|         try { | ||||
|             wikidataQuery = FileUtils.readFromResource("/queries/nearby_query.rq"); | ||||
|             Timber.v(wikidataQuery); | ||||
|         } catch (IOException e) { | ||||
|             throw new RuntimeException(e); | ||||
|         } | ||||
|     @Inject | ||||
|     public NearbyPlaces(OkHttpJsonApiClient okHttpJsonApiClient) { | ||||
|         this.okHttpJsonApiClient = okHttpJsonApiClient; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | @ -104,81 +92,6 @@ public class NearbyPlaces { | |||
|      * @throws IOException if query fails | ||||
|      */ | ||||
|     private List<Place> getFromWikidataQuery(LatLng cur, String lang, double radius) throws IOException { | ||||
|         List<Place> places = new ArrayList<>(); | ||||
| 
 | ||||
|         String query = wikidataQuery | ||||
|                 .replace("${RAD}", String.format(Locale.ROOT, "%.2f", radius)) | ||||
|                 .replace("${LAT}", String.format(Locale.ROOT, "%.4f", cur.getLatitude())) | ||||
|                 .replace("${LONG}", String.format(Locale.ROOT, "%.4f", cur.getLongitude())) | ||||
|                 .replace("${LANG}", lang); | ||||
| 
 | ||||
|         Timber.v("# Wikidata query: \n" + query); | ||||
| 
 | ||||
|         // format as a URL | ||||
|         Timber.d(WIKIDATA_QUERY_UI_URL.buildUpon().fragment(query).build().toString()); | ||||
|         String url = WIKIDATA_QUERY_URL.buildUpon().appendQueryParameter("query", query).build().toString(); | ||||
|         URLConnection conn = new URL(url).openConnection(); | ||||
|         conn.setRequestProperty("Accept", "text/tab-separated-values"); | ||||
|         BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream())); | ||||
| 
 | ||||
|         String line; | ||||
|         Timber.d("Reading from query result..."); | ||||
|         while ((line = in.readLine()) != null) { | ||||
|             Timber.v(line); | ||||
|             line = line + "\n"; // to pad columns and make fields a fixed size | ||||
|             if (!line.startsWith("\"Point")) { | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             String[] fields = line.split("\t"); | ||||
|             Timber.v("Fields: " + Arrays.toString(fields)); | ||||
|             String point = fields[0]; | ||||
|             String wikiDataLink = Utils.stripLocalizedString(fields[1]); | ||||
|             String name = Utils.stripLocalizedString(fields[2]); | ||||
| 
 | ||||
|             //getting icon link here | ||||
|             String identifier = Utils.stripLocalizedString(fields[3]); | ||||
|             //getting the ID which is at the end of link | ||||
|             identifier = identifier.split("/")[Utils.stripLocalizedString(fields[3]).split("/").length-1]; | ||||
|             //replaced the extra > char from fields | ||||
|             identifier = identifier.replace(">",""); | ||||
| 
 | ||||
|             String type = Utils.stripLocalizedString(fields[4]); | ||||
|             String icon = fields[5]; | ||||
|             String wikipediaSitelink = Utils.stripLocalizedString(fields[7]); | ||||
|             String commonsSitelink = Utils.stripLocalizedString(fields[8]); | ||||
|             String category = Utils.stripLocalizedString(fields[9]); | ||||
|             Timber.v("Name: " + name + ", type: " + type + ", category: " + category + ", wikipediaSitelink: " + wikipediaSitelink + ", commonsSitelink: " + commonsSitelink); | ||||
| 
 | ||||
|             double latitude; | ||||
|             double longitude; | ||||
|             Matcher matcher = Pattern.compile("Point\\(([^ ]+) ([^ ]+)\\)").matcher(point); | ||||
|             if (!matcher.find()) { | ||||
|                 continue; | ||||
|             } | ||||
|             try { | ||||
|                 longitude = Double.parseDouble(matcher.group(1)); | ||||
|                 latitude = Double.parseDouble(matcher.group(2)); | ||||
|             } catch (NumberFormatException e) { | ||||
|                 throw new RuntimeException("LatLng parse error: " + point); | ||||
|             } | ||||
| 
 | ||||
|             places.add(new Place( | ||||
|                     name, | ||||
|                     Label.fromText(identifier), // list | ||||
|                     type, // details | ||||
|                     Uri.parse(icon), | ||||
|                     new LatLng(latitude, longitude, 0), | ||||
|                     category, | ||||
|                     new Sitelinks.Builder() | ||||
|                             .setWikipediaLink(wikipediaSitelink) | ||||
|                             .setCommonsLink(commonsSitelink) | ||||
|                             .setWikidataLink(wikiDataLink) | ||||
|                             .build() | ||||
|             )); | ||||
|         } | ||||
|         in.close(); | ||||
| 
 | ||||
|         return places; | ||||
|         return okHttpJsonApiClient.getNearbyPlaces(cur, lang, radius).blockingSingle(); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -7,6 +7,9 @@ import android.os.Parcelable; | |||
| import android.support.annotation.Nullable; | ||||
| 
 | ||||
| import fr.free.nrw.commons.location.LatLng; | ||||
| import fr.free.nrw.commons.nearby.model.NearbyResultItem; | ||||
| import fr.free.nrw.commons.utils.PlaceUtils; | ||||
| import fr.free.nrw.commons.utils.StringUtils; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
| /** | ||||
|  | @ -48,6 +51,26 @@ public class Place implements Parcelable { | |||
|         this.siteLinks = in.readParcelable(Sitelinks.class.getClassLoader()); | ||||
|     } | ||||
| 
 | ||||
|     public static Place from(NearbyResultItem item) { | ||||
|         String itemClass = item.getClassName().getValue(); | ||||
|         String classEntityId = ""; | ||||
|         if(!StringUtils.isNullOrWhiteSpace(itemClass)) { | ||||
|             classEntityId = itemClass.replace("http://www.wikidata.org/entity/", ""); | ||||
|         } | ||||
|         return new Place( | ||||
|                 item.getLabel().getValue(), | ||||
|                 Label.fromText(classEntityId), // list | ||||
|                 item.getClassLabel().getValue(), // details | ||||
|                 Uri.parse(item.getIcon().getValue()), | ||||
|                 PlaceUtils.latLngFromPointString(item.getLocation().getValue()), | ||||
|                 item.getCommonsCategory().getValue(), | ||||
|                 new Sitelinks.Builder() | ||||
|                         .setWikipediaLink(item.getWikipediaArticle().getValue()) | ||||
|                         .setCommonsLink(item.getCommonsArticle().getValue()) | ||||
|                         .setWikidataLink(item.getItem().getValue()) | ||||
|                         .build()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Gets the name of the place | ||||
|      * @return name | ||||
|  |  | |||
|  | @ -15,6 +15,7 @@ import android.widget.ImageView; | |||
| import android.widget.LinearLayout; | ||||
| import android.widget.TextView; | ||||
| 
 | ||||
| import com.facebook.drawee.view.SimpleDraweeView; | ||||
| import com.pedrogomez.renderers.Renderer; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
|  | @ -43,7 +44,7 @@ public class PlaceRenderer extends Renderer<Place> { | |||
|     @BindView(R.id.tvName) TextView tvName; | ||||
|     @BindView(R.id.tvDesc) TextView tvDesc; | ||||
|     @BindView(R.id.distance) TextView distance; | ||||
|     @BindView(R.id.icon) ImageView icon; | ||||
|     @BindView(R.id.icon) SimpleDraweeView icon; | ||||
|     @BindView(R.id.buttonLayout) LinearLayout buttonLayout; | ||||
|     @BindView(R.id.cameraButton) LinearLayout cameraButton; | ||||
| 
 | ||||
|  | @ -215,7 +216,9 @@ public class PlaceRenderer extends Renderer<Place> { | |||
|         } | ||||
|         tvDesc.setText(descriptionText); | ||||
|         distance.setText(place.distance); | ||||
|         icon.setImageResource(place.getLabel().getIcon()); | ||||
| 
 | ||||
| 
 | ||||
|         icon.setImageURI(place.getSecondaryImageUrl()); | ||||
| 
 | ||||
|         directionsButton.setOnClickListener(view -> { | ||||
|             //Open map app at given position | ||||
|  |  | |||
|  | @ -0,0 +1,13 @@ | |||
| package fr.free.nrw.commons.nearby.model; | ||||
| 
 | ||||
| public class NearbyResponse { | ||||
|     private final NearbyResults results; | ||||
| 
 | ||||
|     public NearbyResponse(NearbyResults results) { | ||||
|         this.results = results; | ||||
|     } | ||||
| 
 | ||||
|     public NearbyResults getResults() { | ||||
|         return results; | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,70 @@ | |||
| package fr.free.nrw.commons.nearby.model; | ||||
| 
 | ||||
| import com.google.gson.annotations.SerializedName; | ||||
| 
 | ||||
| public class NearbyResultItem { | ||||
|     private final ResultTuple item; | ||||
|     private final ResultTuple wikipediaArticle; | ||||
|     private final ResultTuple commonsArticle; | ||||
|     private final ResultTuple location; | ||||
|     private final ResultTuple label; | ||||
|     private final ResultTuple icon; | ||||
|     @SerializedName("class") private final ResultTuple className; | ||||
|     @SerializedName("class_label") private final ResultTuple classLabel; | ||||
|     @SerializedName("Commons_category") private final ResultTuple commonsCategory; | ||||
| 
 | ||||
|     public NearbyResultItem(ResultTuple item, | ||||
|                             ResultTuple wikipediaArticle, | ||||
|                             ResultTuple commonsArticle, | ||||
|                             ResultTuple location, | ||||
|                             ResultTuple label, | ||||
|                             ResultTuple icon, ResultTuple className, | ||||
|                             ResultTuple classLabel, | ||||
|                             ResultTuple commonsCategory) { | ||||
|         this.item = item; | ||||
|         this.wikipediaArticle = wikipediaArticle; | ||||
|         this.commonsArticle = commonsArticle; | ||||
|         this.location = location; | ||||
|         this.label = label; | ||||
|         this.icon = icon; | ||||
|         this.className = className; | ||||
|         this.classLabel = classLabel; | ||||
|         this.commonsCategory = commonsCategory; | ||||
|     } | ||||
| 
 | ||||
|     public ResultTuple getItem() { | ||||
|         return item == null ? new ResultTuple(): item; | ||||
|     } | ||||
| 
 | ||||
|     public ResultTuple getWikipediaArticle() { | ||||
|         return wikipediaArticle == null ? new ResultTuple():wikipediaArticle; | ||||
|     } | ||||
| 
 | ||||
|     public ResultTuple getCommonsArticle() { | ||||
|         return commonsArticle == null ? new ResultTuple():commonsArticle; | ||||
|     } | ||||
| 
 | ||||
|     public ResultTuple getLocation() { | ||||
|         return location == null ? new ResultTuple():location; | ||||
|     } | ||||
| 
 | ||||
|     public ResultTuple getLabel() { | ||||
|         return label == null ? new ResultTuple():label; | ||||
|     } | ||||
| 
 | ||||
|     public ResultTuple getIcon() { | ||||
|         return icon == null ? new ResultTuple():icon; | ||||
|     } | ||||
| 
 | ||||
|     public ResultTuple getClassName() { | ||||
|         return className == null ? new ResultTuple():className; | ||||
|     } | ||||
| 
 | ||||
|     public ResultTuple getClassLabel() { | ||||
|         return classLabel == null ? new ResultTuple():classLabel; | ||||
|     } | ||||
| 
 | ||||
|     public ResultTuple getCommonsCategory() { | ||||
|         return commonsCategory == null ? new ResultTuple():commonsCategory; | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,15 @@ | |||
| package fr.free.nrw.commons.nearby.model; | ||||
| 
 | ||||
| import java.util.List; | ||||
| 
 | ||||
| public class NearbyResults { | ||||
|     private final List<NearbyResultItem> bindings; | ||||
| 
 | ||||
|     public NearbyResults(List<NearbyResultItem> bindings) { | ||||
|         this.bindings = bindings; | ||||
|     } | ||||
| 
 | ||||
|     public List<NearbyResultItem> getBindings() { | ||||
|         return bindings; | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,24 @@ | |||
| package fr.free.nrw.commons.nearby.model; | ||||
| 
 | ||||
| public class ResultTuple { | ||||
|     private final String type; | ||||
|     private final String value; | ||||
| 
 | ||||
|     public ResultTuple(String type, String value) { | ||||
|         this.type = type; | ||||
|         this.value = value; | ||||
|     } | ||||
| 
 | ||||
|     public ResultTuple() { | ||||
|         this.type = ""; | ||||
|         this.value = ""; | ||||
|     } | ||||
| 
 | ||||
|     public String getType() { | ||||
|         return type; | ||||
|     } | ||||
| 
 | ||||
|     public String getValue() { | ||||
|         return value; | ||||
|     } | ||||
| } | ||||
|  | @ -7,7 +7,7 @@ import android.support.v7.app.AlertDialog.Builder; | |||
| import fr.free.nrw.commons.R; | ||||
| import fr.free.nrw.commons.WelcomeActivity; | ||||
| import fr.free.nrw.commons.kvstore.BasicKvStore; | ||||
| import fr.free.nrw.commons.mwapi.MediaWikiApi; | ||||
| import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient; | ||||
| import io.reactivex.android.schedulers.AndroidSchedulers; | ||||
| import io.reactivex.disposables.CompositeDisposable; | ||||
| import io.reactivex.schedulers.Schedulers; | ||||
|  | @ -28,7 +28,7 @@ public class QuizChecker { | |||
|     private CompositeDisposable compositeDisposable = new CompositeDisposable(); | ||||
|     public Context context; | ||||
|     private String userName; | ||||
|     private MediaWikiApi mediaWikiApi; | ||||
|     private OkHttpJsonApiClient okHttpJsonApiClient; | ||||
|     private BasicKvStore revertKvStore; | ||||
|     private BasicKvStore countKvStore; | ||||
| 
 | ||||
|  | @ -41,16 +41,16 @@ public class QuizChecker { | |||
|      * constructor to set the parameters for quiz | ||||
|      * @param context context | ||||
|      * @param userName Commons user name | ||||
|      * @param mediaWikiApi instance of MediaWikiApi | ||||
|      * @param okHttpJsonApiClient instance of MediaWikiApi | ||||
|      */ | ||||
|     public QuizChecker(Context context, | ||||
|                        String userName, | ||||
|                        MediaWikiApi mediaWikiApi, | ||||
|                        OkHttpJsonApiClient okHttpJsonApiClient, | ||||
|                        BasicKvStore revertKvStore, | ||||
|                        BasicKvStore countKvStore) { | ||||
|         this.context = context; | ||||
|         this.userName = userName; | ||||
|         this.mediaWikiApi = mediaWikiApi; | ||||
|         this.okHttpJsonApiClient = okHttpJsonApiClient; | ||||
|         this.revertKvStore = revertKvStore; | ||||
|         this.countKvStore = countKvStore; | ||||
|         setUploadCount(); | ||||
|  | @ -61,7 +61,7 @@ public class QuizChecker { | |||
|      * to fet the total number of images uploaded | ||||
|      */ | ||||
|     private void setUploadCount() { | ||||
|             compositeDisposable.add(mediaWikiApi | ||||
|         compositeDisposable.add(okHttpJsonApiClient | ||||
|                     .getUploadCount(userName) | ||||
|                     .subscribeOn(Schedulers.io()) | ||||
|                     .observeOn(AndroidSchedulers.mainThread()) | ||||
|  | @ -89,7 +89,7 @@ public class QuizChecker { | |||
|      * To call the API to get reverts count in form of JSONObject | ||||
|      */ | ||||
|     private void setRevertCount() { | ||||
|             compositeDisposable.add(mediaWikiApi | ||||
|         compositeDisposable.add(okHttpJsonApiClient | ||||
|                     .getAchievements(userName) | ||||
|                     .subscribeOn(Schedulers.io()) | ||||
|                     .observeOn(AndroidSchedulers.mainThread()) | ||||
|  |  | |||
|  | @ -2,6 +2,7 @@ package fr.free.nrw.commons.utils; | |||
| 
 | ||||
| import android.text.format.DateFormat; | ||||
| 
 | ||||
| import java.text.ParseException; | ||||
| import java.text.SimpleDateFormat; | ||||
| import java.util.Calendar; | ||||
| import java.util.Date; | ||||
|  | @ -38,11 +39,21 @@ public class DateUtils { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public static Date getDateFromString(String dateString) { | ||||
|         SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.US); | ||||
|         try { | ||||
|             return dateFormat.parse(dateString); | ||||
|         } catch (ParseException e) { | ||||
|             return null; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public static String getCurrentDate() { | ||||
|         SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.US); | ||||
|         Date date = new Date(); | ||||
|         return dateFormat.format(date); | ||||
|     } | ||||
| 
 | ||||
|     public static String dateInLocaleFormat(Date date){ | ||||
|         String formatter; | ||||
|         if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2) { | ||||
|  |  | |||
|  | @ -1,5 +1,8 @@ | |||
| package fr.free.nrw.commons.utils; | ||||
| 
 | ||||
| import java.util.regex.Matcher; | ||||
| import java.util.regex.Pattern; | ||||
| 
 | ||||
| import fr.free.nrw.commons.location.LatLng; | ||||
| 
 | ||||
| public class PlaceUtils { | ||||
|  | @ -22,4 +25,21 @@ public class PlaceUtils { | |||
|         String[] parts = latLngString.split("/"); | ||||
|         return new LatLng(Double.parseDouble(parts[0]), Double.parseDouble(parts[1]), 0); | ||||
|     } | ||||
| 
 | ||||
|     public static LatLng latLngFromPointString(String pointString) { | ||||
|         double latitude; | ||||
|         double longitude; | ||||
|         Matcher matcher = Pattern.compile("Point\\(([^ ]+) ([^ ]+)\\)").matcher(pointString); | ||||
|         if (!matcher.find()) { | ||||
|             return null; | ||||
|         } | ||||
|         try { | ||||
|             longitude = Double.parseDouble(matcher.group(1)); | ||||
|             latitude = Double.parseDouble(matcher.group(2)); | ||||
|         } catch (NumberFormatException e) { | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         return new LatLng(latitude, longitude, 0); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -4,6 +4,8 @@ import android.os.Build; | |||
| import android.text.Html; | ||||
| 
 | ||||
| public class StringUtils { | ||||
|     public static final String EMPTY = ""; | ||||
| 
 | ||||
|     public static String getParsedStringFromHtml(String source) { | ||||
|         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { | ||||
|             return Html.fromHtml(source, Html.FROM_HTML_MODE_LEGACY).toString(); | ||||
|  | @ -16,4 +18,45 @@ public class StringUtils { | |||
|     public static boolean isNullOrWhiteSpace(String value) { | ||||
|         return value == null || value.trim().isEmpty(); | ||||
|     } | ||||
| 
 | ||||
|     // Defaults | ||||
|     //----------------------------------------------------------------------- | ||||
|     /** | ||||
|      * <p>Returns either the passed in String, | ||||
|      * or if the String is {@code null}, an empty String ("").</p> | ||||
|      * | ||||
|      * <pre> | ||||
|      * StringUtils.defaultString(null)  = "" | ||||
|      * StringUtils.defaultString("")    = "" | ||||
|      * StringUtils.defaultString("bat") = "bat" | ||||
|      * </pre> | ||||
|      * | ||||
|      * @see String#valueOf(Object) | ||||
|      * @param str  the String to check, may be null | ||||
|      * @return the passed in String, or the empty String if it | ||||
|      *  was {@code null} | ||||
|      */ | ||||
|     public static String defaultString(final String str) { | ||||
|         return defaultString(str, EMPTY); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * <p>Returns either the passed in String, or if the String is | ||||
|      * {@code null}, the value of {@code defaultStr}.</p> | ||||
|      * | ||||
|      * <pre> | ||||
|      * StringUtils.defaultString(null, "NULL")  = "NULL" | ||||
|      * StringUtils.defaultString("", "NULL")    = "" | ||||
|      * StringUtils.defaultString("bat", "NULL") = "bat" | ||||
|      * </pre> | ||||
|      * | ||||
|      * @see String#valueOf(Object) | ||||
|      * @param str  the String to check, may be null | ||||
|      * @param defaultStr  the default String to return | ||||
|      *  if the input is {@code null}, may be null | ||||
|      * @return the passed in String, or the default if it was {@code null} | ||||
|      */ | ||||
|     public static String defaultString(final String str, final String defaultStr) { | ||||
|         return str == null ? defaultStr : str; | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -24,7 +24,7 @@ import javax.inject.Inject; | |||
| 
 | ||||
| import fr.free.nrw.commons.R; | ||||
| import fr.free.nrw.commons.di.ApplicationlessInjection; | ||||
| import fr.free.nrw.commons.mwapi.MediaWikiApi; | ||||
| import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient; | ||||
| import io.reactivex.android.schedulers.AndroidSchedulers; | ||||
| import io.reactivex.disposables.CompositeDisposable; | ||||
| import io.reactivex.schedulers.Schedulers; | ||||
|  | @ -37,8 +37,7 @@ public class PicOfDayAppWidget extends AppWidgetProvider { | |||
| 
 | ||||
|     private CompositeDisposable compositeDisposable = new CompositeDisposable(); | ||||
| 
 | ||||
|     @Inject | ||||
|     MediaWikiApi mediaWikiApi; | ||||
|     @Inject OkHttpJsonApiClient okHttpJsonApiClient; | ||||
| 
 | ||||
|     void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) { | ||||
|         RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.pic_of_day_app_widget); | ||||
|  | @ -56,7 +55,7 @@ public class PicOfDayAppWidget extends AppWidgetProvider { | |||
|                                      RemoteViews views, | ||||
|                                      AppWidgetManager appWidgetManager, | ||||
|                                      int appWidgetId) { | ||||
|         compositeDisposable.add(mediaWikiApi.getPictureOfTheDay() | ||||
|         compositeDisposable.add(okHttpJsonApiClient.getPictureOfTheDay() | ||||
|                 .subscribeOn(Schedulers.io()) | ||||
|                 .observeOn(AndroidSchedulers.mainThread()) | ||||
|                 .subscribe( | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Vivek Maskara
						Vivek Maskara