mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-25 21:03:56 +02: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(
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
android:focusableInTouchMode="true"
|
||||
android:minHeight="72dp">
|
||||
|
||||
<ImageView
|
||||
<com.facebook.drawee.view.SimpleDraweeView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
android:layout_marginTop="@dimen/standard_gap"
|
||||
android:background="@android:color/white"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/empty_photo"
|
||||
tools:src="@drawable/empty_photo"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
|
|
@ -27,6 +27,7 @@
|
|||
android:layout_marginLeft="@dimen/standard_gap"
|
||||
android:layout_marginRight="@dimen/standard_gap"
|
||||
android:layout_marginTop="@dimen/standard_gap"
|
||||
tools:text="900m"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Caption"
|
||||
/>
|
||||
|
||||
|
|
@ -54,6 +55,7 @@
|
|||
android:layout_toStartOf="@id/distance"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
tools:text="St. Paul's School"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
|
||||
/>
|
||||
|
||||
|
|
@ -69,6 +71,7 @@
|
|||
android:layout_marginBottom="@dimen/standard_gap"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="4"
|
||||
tools:text="school"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
|
||||
/>
|
||||
|
||||
|
|
|
|||
|
|
@ -45,7 +45,6 @@ class MockCommonsApplicationModule(appContext: Context) : CommonsApplicationModu
|
|||
val mockSessionManager: SessionManager = mock()
|
||||
val locationServiceManager: LocationServiceManager = mock()
|
||||
val mockDbOpenHelper: DBOpenHelper = mock()
|
||||
val nearbyPlaces: NearbyPlaces = mock()
|
||||
val lruCache: LruCache<String, String> = mock()
|
||||
val gson: Gson = Gson()
|
||||
val categoryClient: ContentProviderClient = mock()
|
||||
|
|
@ -77,7 +76,5 @@ class MockCommonsApplicationModule(appContext: Context) : CommonsApplicationModu
|
|||
|
||||
override fun provideDBOpenHelper(context: Context): DBOpenHelper = mockDbOpenHelper
|
||||
|
||||
override fun provideNearbyPlaces(): NearbyPlaces = nearbyPlaces
|
||||
|
||||
override fun provideLruCache(): LruCache<String, String> = lruCache
|
||||
}
|
||||
|
|
@ -41,8 +41,7 @@ class ApacheHttpClientMediaWikiApiTest {
|
|||
okHttpClient = OkHttpClient()
|
||||
sharedPreferences = mock(BasicKvStore::class.java)
|
||||
categoryPreferences = mock(BasicKvStore::class.java)
|
||||
testObject = ApacheHttpClientMediaWikiApi(RuntimeEnvironment.application, "http://" + server.hostName + ":" + server.port + "/", "http://" + wikidataServer.hostName + ":" + wikidataServer.port + "/", sharedPreferences, categoryPreferences, Gson(), okHttpClient)
|
||||
testObject.setWikiMediaToolforgeUrl("http://" + server.hostName + ":" + server.port + "/")
|
||||
testObject = ApacheHttpClientMediaWikiApi(RuntimeEnvironment.application, "http://" + server.hostName + ":" + server.port + "/", "http://" + wikidataServer.hostName + ":" + wikidataServer.port + "/", sharedPreferences, categoryPreferences, Gson())
|
||||
}
|
||||
|
||||
@After
|
||||
|
|
@ -243,17 +242,6 @@ class ApacheHttpClientMediaWikiApiTest {
|
|||
assertFalse(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getUploadCount() {
|
||||
server.enqueue(MockResponse().setBody("23\n"))
|
||||
|
||||
val testObserver = testObject.getUploadCount("testUsername").test()
|
||||
|
||||
assertEquals("testUsername", parseQueryParams(server.takeRequest())["user"])
|
||||
assertEquals(1, testObserver.valueCount())
|
||||
assertEquals(23, testObserver.values()[0])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isUserBlockedFromCommonsForInfinitelyBlockedUser() {
|
||||
server.enqueue(MockResponse().setBody("<?xml version=\"1.0\"?><api><query><userinfo id=\"1000\" name=\"testusername\" blockid=\"3000\" blockedby=\"blockerusername\" blockedbyid=\"1001\" blockreason=\"testing\" blockedtimestamp=\"2018-05-24T15:32:09Z\" blockexpiry=\"infinite\"></userinfo></query></api>"))
|
||||
|
|
|
|||
|
|
@ -25,8 +25,8 @@ Dagger will resolve the method arguments on provider methods in a module (or the
|
|||
```java
|
||||
@Provides
|
||||
@Singleton
|
||||
public SessionManager providesSessionManager(MediaWikiApi mediaWikiApi) {
|
||||
return new SessionManager(application, mediaWikiApi);
|
||||
public SessionManager providesSessionManager(MediaWikiApi okHttpJsonApiClient) {
|
||||
return new SessionManager(application, okHttpJsonApiClient);
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue