diff --git a/app/src/main/java/fr/free/nrw/commons/campaigns/CampaignsPresenter.kt b/app/src/main/java/fr/free/nrw/commons/campaigns/CampaignsPresenter.kt index ffbf92540..4743e0e54 100644 --- a/app/src/main/java/fr/free/nrw/commons/campaigns/CampaignsPresenter.kt +++ b/app/src/main/java/fr/free/nrw/commons/campaigns/CampaignsPresenter.kt @@ -52,12 +52,12 @@ class CampaignsPresenter @Inject constructor( return } - okHttpJsonApiClient.campaigns + okHttpJsonApiClient.getCampaigns() .observeOn(mainThreadScheduler) .subscribeOn(ioScheduler) .doOnSubscribe { disposable = it } .subscribe({ campaignResponseDTO -> - val campaigns = campaignResponseDTO.campaigns?.toMutableList() + val campaigns = campaignResponseDTO?.campaigns?.toMutableList() if (campaigns.isNullOrEmpty()) { Timber.e("The campaigns list is empty") view!!.showCampaigns(null) diff --git a/app/src/main/java/fr/free/nrw/commons/mwapi/CategoryApi.java b/app/src/main/java/fr/free/nrw/commons/mwapi/CategoryApi.java deleted file mode 100644 index f587893c5..000000000 --- a/app/src/main/java/fr/free/nrw/commons/mwapi/CategoryApi.java +++ /dev/null @@ -1,99 +0,0 @@ -package fr.free.nrw.commons.mwapi; - -import static fr.free.nrw.commons.category.CategoryClientKt.CATEGORY_PREFIX; - -import com.google.gson.Gson; -import fr.free.nrw.commons.BuildConfig; -import fr.free.nrw.commons.category.CategoryItem; -import fr.free.nrw.commons.wikidata.mwapi.MwQueryPage; -import fr.free.nrw.commons.wikidata.mwapi.MwQueryResponse; -import io.reactivex.Single; -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import javax.inject.Inject; -import okhttp3.HttpUrl; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.ResponseBody; -import timber.log.Timber; - -/** - * Uses the OkHttp library to implement calls to the Commons MediaWiki API to match GPS coordinates - * with nearby Commons categories. Parses the results using GSON to obtain a list of relevant - * categories. Note: that caller is responsible for executing the request() method on a background - * thread. - */ -public class CategoryApi { - - private final OkHttpClient okHttpClient; - private final Gson gson; - - @Inject - public CategoryApi(final OkHttpClient okHttpClient, final Gson gson) { - this.okHttpClient = okHttpClient; - this.gson = gson; - } - - public Single> request(String coords) { - return Single.fromCallable(() -> { - HttpUrl apiUrl = buildUrl(coords); - Timber.d("URL: %s", apiUrl.toString()); - - Request request = new Request.Builder().get().url(apiUrl).build(); - Response response = okHttpClient.newCall(request).execute(); - ResponseBody body = response.body(); - if (body == null) { - return Collections.emptyList(); - } - - MwQueryResponse apiResponse = gson.fromJson(body.charStream(), MwQueryResponse.class); - Set categories = new LinkedHashSet<>(); - if (apiResponse != null && apiResponse.query() != null && apiResponse.query().pages() != null) { - for (MwQueryPage page : apiResponse.query().pages()) { - if (page.categories() != null) { - for (MwQueryPage.Category category : page.categories()) { - categories.add(new CategoryItem(category.title().replace(CATEGORY_PREFIX, ""), "", "", false)); - } - } - } - } - return new ArrayList<>(categories); - }); - } - - /** - * Builds URL with image coords for MediaWiki API calls - * Example URL: https://commons.wikimedia.org/w/api.php?action=query&prop=categories|coordinates|pageprops&format=json&clshow=!hidden&coprop=type|name|dim|country|region|globe&codistancefrompoint=38.11386944444445|13.356263888888888&generator=geosearch&redirects=&ggscoord=38.11386944444445|1.356263888888888&ggsradius=100&ggslimit=10&ggsnamespace=6&ggsprop=type|name|dim|country|region|globe&ggsprimary=all&formatversion=2 - * - * @param coords Coordinates to build query with - * @return URL for API query - */ - private HttpUrl buildUrl(final String coords) { - return HttpUrl - .parse(BuildConfig.WIKIMEDIA_API_HOST) - .newBuilder() - .addQueryParameter("action", "query") - .addQueryParameter("prop", "categories|coordinates|pageprops") - .addQueryParameter("format", "json") - .addQueryParameter("clshow", "!hidden") - .addQueryParameter("coprop", "type|name|dim|country|region|globe") - .addQueryParameter("codistancefrompoint", coords) - .addQueryParameter("generator", "geosearch") - .addQueryParameter("ggscoord", coords) - .addQueryParameter("ggsradius", "10000") - .addQueryParameter("ggslimit", "10") - .addQueryParameter("ggsnamespace", "6") - .addQueryParameter("ggsprop", "type|name|dim|country|region|globe") - .addQueryParameter("ggsprimary", "all") - .addQueryParameter("formatversion", "2") - .build(); - } - -} - - - diff --git a/app/src/main/java/fr/free/nrw/commons/mwapi/CategoryApi.kt b/app/src/main/java/fr/free/nrw/commons/mwapi/CategoryApi.kt new file mode 100644 index 000000000..1f8c51187 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/mwapi/CategoryApi.kt @@ -0,0 +1,83 @@ +package fr.free.nrw.commons.mwapi + +import com.google.gson.Gson +import fr.free.nrw.commons.BuildConfig +import fr.free.nrw.commons.category.CATEGORY_PREFIX +import fr.free.nrw.commons.category.CategoryItem +import fr.free.nrw.commons.wikidata.mwapi.MwQueryResponse +import io.reactivex.Single +import okhttp3.HttpUrl +import okhttp3.HttpUrl.Companion.toHttpUrlOrNull +import okhttp3.OkHttpClient +import okhttp3.Request +import timber.log.Timber +import javax.inject.Inject + +/** + * Uses the OkHttp library to implement calls to the Commons MediaWiki API to match GPS coordinates + * with nearby Commons categories. Parses the results using GSON to obtain a list of relevant + * categories. Note: that caller is responsible for executing the request() method on a background + * thread. + */ +class CategoryApi @Inject constructor( + private val okHttpClient: OkHttpClient, + private val gson: Gson +) { + private val apiUrl : HttpUrl by lazy { BuildConfig.WIKIMEDIA_API_HOST.toHttpUrlOrNull()!! } + + fun request(coords: String): Single> = Single.fromCallable { + val apiUrl = buildUrl(coords) + Timber.d("URL: %s", apiUrl.toString()) + + val request: Request = Request.Builder().get().url(apiUrl).build() + val response = okHttpClient.newCall(request).execute() + val body = response.body ?: return@fromCallable emptyList() + + val apiResponse = gson.fromJson(body.charStream(), MwQueryResponse::class.java) + val categories: MutableSet = mutableSetOf() + if (apiResponse?.query() != null && apiResponse.query()!!.pages() != null) { + for (page in apiResponse.query()!!.pages()!!) { + if (page.categories() != null) { + for (category in page.categories()!!) { + categories.add( + CategoryItem( + name = category.title().replace(CATEGORY_PREFIX, ""), + description = "", + thumbnail = "", + isSelected = false + ) + ) + } + } + } + } + ArrayList(categories) + } + + /** + * Builds URL with image coords for MediaWiki API calls + * Example URL: https://commons.wikimedia.org/w/api.php?action=query&prop=categories|coordinates|pageprops&format=json&clshow=!hidden&coprop=type|name|dim|country|region|globe&codistancefrompoint=38.11386944444445|13.356263888888888&generator=geosearch&redirects=&ggscoord=38.11386944444445|1.356263888888888&ggsradius=100&ggslimit=10&ggsnamespace=6&ggsprop=type|name|dim|country|region|globe&ggsprimary=all&formatversion=2 + * + * @param coords Coordinates to build query with + * @return URL for API query + */ + private fun buildUrl(coords: String): HttpUrl = apiUrl.newBuilder() + .addQueryParameter("action", "query") + .addQueryParameter("prop", "categories|coordinates|pageprops") + .addQueryParameter("format", "json") + .addQueryParameter("clshow", "!hidden") + .addQueryParameter("coprop", "type|name|dim|country|region|globe") + .addQueryParameter("codistancefrompoint", coords) + .addQueryParameter("generator", "geosearch") + .addQueryParameter("ggscoord", coords) + .addQueryParameter("ggsradius", "10000") + .addQueryParameter("ggslimit", "10") + .addQueryParameter("ggsnamespace", "6") + .addQueryParameter("ggsprop", "type|name|dim|country|region|globe") + .addQueryParameter("ggsprimary", "all") + .addQueryParameter("formatversion", "2") + .build() +} + + + diff --git a/app/src/main/java/fr/free/nrw/commons/mwapi/OkHttpJsonApiClient.java b/app/src/main/java/fr/free/nrw/commons/mwapi/OkHttpJsonApiClient.java deleted file mode 100644 index 8ed37a293..000000000 --- a/app/src/main/java/fr/free/nrw/commons/mwapi/OkHttpJsonApiClient.java +++ /dev/null @@ -1,677 +0,0 @@ -package fr.free.nrw.commons.mwapi; - -import static fr.free.nrw.commons.profile.leaderboard.LeaderboardConstants.LEADERBOARD_END_POINT; -import static fr.free.nrw.commons.profile.leaderboard.LeaderboardConstants.UPDATE_AVATAR_END_POINT; - -import android.text.TextUtils; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import com.google.gson.Gson; -import fr.free.nrw.commons.campaigns.CampaignResponseDTO; -import fr.free.nrw.commons.explore.depictions.DepictsClient; -import fr.free.nrw.commons.location.LatLng; -import fr.free.nrw.commons.nearby.Place; -import fr.free.nrw.commons.nearby.model.ItemsClass; -import fr.free.nrw.commons.nearby.model.NearbyResponse; -import fr.free.nrw.commons.nearby.model.NearbyResultItem; -import fr.free.nrw.commons.nearby.model.PlaceBindings; -import fr.free.nrw.commons.profile.achievements.FeaturedImages; -import fr.free.nrw.commons.profile.achievements.FeedbackResponse; -import fr.free.nrw.commons.profile.leaderboard.LeaderboardResponse; -import fr.free.nrw.commons.profile.leaderboard.UpdateAvatarResponse; -import fr.free.nrw.commons.upload.FileUtils; -import fr.free.nrw.commons.upload.structure.depictions.DepictedItem; -import fr.free.nrw.commons.utils.ConfigUtils; -import fr.free.nrw.commons.wikidata.model.GetWikidataEditCountResponse; -import io.reactivex.Observable; -import io.reactivex.Single; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import javax.inject.Inject; -import javax.inject.Singleton; -import okhttp3.HttpUrl; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.ResponseBody; -import org.jetbrains.annotations.NotNull; -import timber.log.Timber; - -/** - * Test methods in ok http api client - */ -@Singleton -public class OkHttpJsonApiClient { - - private final OkHttpClient okHttpClient; - private final DepictsClient depictsClient; - private final HttpUrl wikiMediaToolforgeUrl; - private final String sparqlQueryUrl; - private final String campaignsUrl; - private final Gson gson; - - - @Inject - public OkHttpJsonApiClient(OkHttpClient okHttpClient, - DepictsClient depictsClient, - HttpUrl wikiMediaToolforgeUrl, - String sparqlQueryUrl, - String campaignsUrl, - Gson gson) { - this.okHttpClient = okHttpClient; - this.depictsClient = depictsClient; - this.wikiMediaToolforgeUrl = wikiMediaToolforgeUrl; - this.sparqlQueryUrl = sparqlQueryUrl; - this.campaignsUrl = campaignsUrl; - this.gson = gson; - } - - /** - * The method will gradually calls the leaderboard API and fetches the leaderboard - * - * @param userName username of leaderboard user - * @param duration duration for leaderboard - * @param category category for leaderboard - * @param limit page size limit for list - * @param offset offset for the list - * @return LeaderboardResponse object - */ - @NonNull - public Observable getLeaderboard(String userName, String duration, - String category, String limit, String offset) { - final String fetchLeaderboardUrlTemplate = wikiMediaToolforgeUrl - + LEADERBOARD_END_POINT; - String url = String.format(Locale.ENGLISH, - fetchLeaderboardUrlTemplate, - userName, - duration, - category, - limit, - offset); - HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder(); - urlBuilder.addQueryParameter("user", userName); - urlBuilder.addQueryParameter("duration", duration); - urlBuilder.addQueryParameter("category", category); - urlBuilder.addQueryParameter("limit", limit); - urlBuilder.addQueryParameter("offset", offset); - Timber.i("Url %s", urlBuilder.toString()); - Request request = new Request.Builder() - .url(urlBuilder.toString()) - .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 LeaderboardResponse(); - } - Timber.d("Response for leaderboard is %s", json); - try { - return gson.fromJson(json, LeaderboardResponse.class); - } catch (Exception e) { - return new LeaderboardResponse(); - } - } - return new LeaderboardResponse(); - }); - } - - /** - * This method will update the leaderboard user avatar - * - * @param username username to update - * @param avatar url of the new avatar - * @return UpdateAvatarResponse object - */ - @NonNull - public Single setAvatar(String username, String avatar) { - final String urlTemplate = wikiMediaToolforgeUrl - + UPDATE_AVATAR_END_POINT; - return Single.fromCallable(() -> { - String url = String.format(Locale.ENGLISH, - urlTemplate, - username, - avatar); - HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder(); - urlBuilder.addQueryParameter("user", username); - urlBuilder.addQueryParameter("avatar", avatar); - 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; - } - try { - return gson.fromJson(json, UpdateAvatarResponse.class); - } catch (Exception e) { - return new UpdateAvatarResponse(); - } - } - return null; - }); - } - - @NonNull - public Single getUploadCount(String userName) { - HttpUrl.Builder urlBuilder = wikiMediaToolforgeUrl.newBuilder(); - urlBuilder - .addPathSegments("uploadsbyuser.py") - .addQueryParameter("user", userName); - - if (ConfigUtils.isBetaFlavour()) { - urlBuilder.addQueryParameter("labs", "commonswiki"); - } - - Request request = new Request.Builder() - .url(urlBuilder.build()) - .build(); - - return Single.fromCallable(() -> { - Response response = okHttpClient.newCall(request).execute(); - if (response != null && response.isSuccessful()) { - ResponseBody responseBody = response.body(); - if (null != responseBody) { - String responseBodyString = responseBody.string().trim(); - if (!TextUtils.isEmpty(responseBodyString)) { - try { - return Integer.parseInt(responseBodyString); - } catch (NumberFormatException e) { - Timber.e(e); - } - } - } - } - return 0; - }); - } - - @NonNull - public Single getWikidataEdits(String userName) { - HttpUrl.Builder urlBuilder = wikiMediaToolforgeUrl.newBuilder(); - urlBuilder - .addPathSegments("wikidataedits.py") - .addQueryParameter("user", userName); - - if (ConfigUtils.isBetaFlavour()) { - urlBuilder.addQueryParameter("labs", "commonswiki"); - } - - Request request = new Request.Builder() - .url(urlBuilder.build()) - .build(); - - return Single.fromCallable(() -> { - Response response = okHttpClient.newCall(request).execute(); - if (response != null && - response.isSuccessful() && response.body() != null) { - String json = response.body().string(); - if (json == null) { - return 0; - } - // Extract JSON from response - json = json.substring(json.indexOf('{')); - GetWikidataEditCountResponse countResponse = gson - .fromJson(json, GetWikidataEditCountResponse.class); - if (null != countResponse) { - return countResponse.getWikidataEditCount(); - } - } - 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 getAchievements(String userName) { - final String fetchAchievementUrlTemplate = - wikiMediaToolforgeUrl + (ConfigUtils.isBetaFlavour() ? "/feedback.py?labs=commonswiki" - : "/feedback.py"); - return Single.fromCallable(() -> { - String url = String.format( - Locale.ENGLISH, - fetchAchievementUrlTemplate, - userName); - HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder(); - urlBuilder.addQueryParameter("user", userName); - 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; - } - // Extract JSON from response - json = json.substring(json.indexOf('{')); - 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, ""); - } - - - } - return null; - }); - } - - /** - * Make API Call to get Nearby Places - * - * @param cur Search lat long - * @param language Language - * @param radius Search Radius - * @return - * @throws Exception - */ - @Nullable - public List getNearbyPlaces(final LatLng cur, final String language, final double radius, - final String customQuery) - throws Exception { - - Timber.d("Fetching nearby items at radius %s", radius); - Timber.d("CUSTOM_SPARQL: %s", String.valueOf(customQuery != null)); - final String wikidataQuery; - if (customQuery != null) { - wikidataQuery = customQuery; - } else { - wikidataQuery = FileUtils.readFromResource( - "/queries/radius_query_for_upload_wizard.rq"); - } - final 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}", language); - - final HttpUrl.Builder urlBuilder = HttpUrl - .parse(sparqlQueryUrl) - .newBuilder() - .addQueryParameter("query", query) - .addQueryParameter("format", "json"); - - final Request request = new Request.Builder() - .url(urlBuilder.build()) - .build(); - - final Response response = okHttpClient.newCall(request).execute(); - if (response.body() != null && response.isSuccessful()) { - final String json = response.body().string(); - final NearbyResponse nearbyResponse = gson.fromJson(json, NearbyResponse.class); - final List bindings = nearbyResponse.getResults().getBindings(); - final List places = new ArrayList<>(); - for (final NearbyResultItem item : bindings) { - final Place placeFromNearbyItem = Place.from(item); - placeFromNearbyItem.setMonument(false); - places.add(placeFromNearbyItem); - } - return places; - } - throw new Exception(response.message()); - } - - /** - * Retrieves nearby places based on screen coordinates and optional query parameters. - * - * @param screenTopRight The top right corner of the screen (latitude, longitude). - * @param screenBottomLeft The bottom left corner of the screen (latitude, longitude). - * @param language The language for the query. - * @param shouldQueryForMonuments Flag indicating whether to include monuments in the query. - * @param customQuery Optional custom SPARQL query to use instead of default - * queries. - * @return A list of nearby places. - * @throws Exception If an error occurs during the retrieval process. - */ - @Nullable - public List getNearbyPlaces( - final fr.free.nrw.commons.location.LatLng screenTopRight, - final fr.free.nrw.commons.location.LatLng screenBottomLeft, final String language, - final boolean shouldQueryForMonuments, final String customQuery) - throws Exception { - - Timber.d("CUSTOM_SPARQL: %s", String.valueOf(customQuery != null)); - - final String wikidataQuery; - if (customQuery != null) { - wikidataQuery = customQuery; - } else if (!shouldQueryForMonuments) { - wikidataQuery = FileUtils.readFromResource("/queries/rectangle_query_for_nearby.rq"); - } else { - wikidataQuery = FileUtils.readFromResource( - "/queries/rectangle_query_for_nearby_monuments.rq"); - } - - final double westCornerLat = screenTopRight.getLatitude(); - final double westCornerLong = screenTopRight.getLongitude(); - final double eastCornerLat = screenBottomLeft.getLatitude(); - final double eastCornerLong = screenBottomLeft.getLongitude(); - - final String query = wikidataQuery - .replace("${LAT_WEST}", String.format(Locale.ROOT, "%.4f", westCornerLat)) - .replace("${LONG_WEST}", String.format(Locale.ROOT, "%.4f", westCornerLong)) - .replace("${LAT_EAST}", String.format(Locale.ROOT, "%.4f", eastCornerLat)) - .replace("${LONG_EAST}", String.format(Locale.ROOT, "%.4f", eastCornerLong)) - .replace("${LANG}", language); - final HttpUrl.Builder urlBuilder = HttpUrl - .parse(sparqlQueryUrl) - .newBuilder() - .addQueryParameter("query", query) - .addQueryParameter("format", "json"); - - final Request request = new Request.Builder() - .url(urlBuilder.build()) - .build(); - - final Response response = okHttpClient.newCall(request).execute(); - if (response.body() != null && response.isSuccessful()) { - final String json = response.body().string(); - final NearbyResponse nearbyResponse = gson.fromJson(json, NearbyResponse.class); - final List bindings = nearbyResponse.getResults().getBindings(); - final List places = new ArrayList<>(); - for (final NearbyResultItem item : bindings) { - final Place placeFromNearbyItem = Place.from(item); - if (shouldQueryForMonuments && item.getMonument() != null) { - placeFromNearbyItem.setMonument(true); - } else { - placeFromNearbyItem.setMonument(false); - } - places.add(placeFromNearbyItem); - } - return places; - } - throw new Exception(response.message()); - } - - /** - * Retrieves a list of places based on the provided list of places and language. - * - * @param placeList A list of Place objects for which to fetch information. - * @param language The language code to use for the query. - * @return A list of Place objects with additional information retrieved from Wikidata, or null - * if an error occurs. - * @throws IOException If there is an issue with reading the resource file or executing the HTTP - * request. - */ - @Nullable - public List getPlaces( - final List placeList, final String language) throws IOException { - final String wikidataQuery = FileUtils.readFromResource("/queries/query_for_item.rq"); - String qids = ""; - for (final Place place : placeList) { - qids += "\n" + ("wd:" + place.getWikiDataEntityId()); - } - final String query = wikidataQuery - .replace("${ENTITY}", qids) - .replace("${LANG}", language); - final HttpUrl.Builder urlBuilder = HttpUrl - .parse(sparqlQueryUrl) - .newBuilder() - .addQueryParameter("query", query) - .addQueryParameter("format", "json"); - - final Request request = new Request.Builder() - .url(urlBuilder.build()) - .build(); - - try (Response response = okHttpClient.newCall(request).execute()) { - if (response.isSuccessful()) { - final String json = response.body().string(); - final NearbyResponse nearbyResponse = gson.fromJson(json, NearbyResponse.class); - final List bindings = nearbyResponse.getResults().getBindings(); - final List places = new ArrayList<>(); - for (final NearbyResultItem item : bindings) { - final Place placeFromNearbyItem = Place.from(item); - places.add(placeFromNearbyItem); - } - return places; - } else { - throw new IOException("Unexpected response code: " + response.code()); - } - } - } - - /** - * Make API Call to get Places - * - * @param leftLatLng Left lat long - * @param rightLatLng Right lat long - * @return - * @throws Exception - */ - @Nullable - public String getPlacesAsKML(final LatLng leftLatLng, final LatLng rightLatLng) - throws Exception { - String kmlString = "\n" + - "\n" + - "\n" + - " "; - List placeBindings = runQuery(leftLatLng, - rightLatLng); - if (placeBindings != null) { - for (PlaceBindings item : placeBindings) { - if (item.getItem() != null && item.getLabel() != null && item.getClas() != null) { - String input = item.getLocation().getValue(); - Pattern pattern = Pattern.compile( - "Point\\(([-+]?[0-9]*\\.?[0-9]+) ([-+]?[0-9]*\\.?[0-9]+)\\)"); - Matcher matcher = pattern.matcher(input); - - if (matcher.find()) { - String longStr = matcher.group(1); - String latStr = matcher.group(2); - String itemUrl = item.getItem().getValue(); - String itemName = item.getLabel().getValue().replace("&", "&"); - String itemLatitude = latStr; - String itemLongitude = longStr; - String itemClass = item.getClas().getValue(); - - String formattedItemName = - !itemClass.isEmpty() ? itemName + " (" + itemClass + ")" - : itemName; - - String kmlEntry = "\n \n" + - " " + formattedItemName + "\n" + - " " + itemUrl + "\n" + - " \n" + - " " + itemLongitude + "," - + itemLatitude - + "\n" + - " \n" + - " "; - kmlString = kmlString + kmlEntry; - } else { - Timber.e("No match found"); - } - } - } - } - kmlString = kmlString + "\n \n" + - "\n"; - return kmlString; - } - - /** - * Make API Call to get Places - * - * @param leftLatLng Left lat long - * @param rightLatLng Right lat long - * @return - * @throws Exception - */ - @Nullable - public String getPlacesAsGPX(final LatLng leftLatLng, final LatLng rightLatLng) - throws Exception { - String gpxString = "\n" + - "" - + "\n"; - - List placeBindings = runQuery(leftLatLng, rightLatLng); - if (placeBindings != null) { - for (PlaceBindings item : placeBindings) { - if (item.getItem() != null && item.getLabel() != null && item.getClas() != null) { - String input = item.getLocation().getValue(); - Pattern pattern = Pattern.compile( - "Point\\(([-+]?[0-9]*\\.?[0-9]+) ([-+]?[0-9]*\\.?[0-9]+)\\)"); - Matcher matcher = pattern.matcher(input); - - if (matcher.find()) { - String longStr = matcher.group(1); - String latStr = matcher.group(2); - String itemUrl = item.getItem().getValue(); - String itemName = item.getLabel().getValue().replace("&", "&"); - String itemLatitude = latStr; - String itemLongitude = longStr; - String itemClass = item.getClas().getValue(); - - String formattedItemName = - !itemClass.isEmpty() ? itemName + " (" + itemClass + ")" - : itemName; - - String gpxEntry = - "\n \n" + - " " + itemName + "\n" + - " " + itemUrl + "\n" + - " "; - gpxString = gpxString + gpxEntry; - - } else { - Timber.e("No match found"); - } - } - } - - } - gpxString = gpxString + "\n"; - return gpxString; - } - - private List runQuery(final LatLng currentLatLng, final LatLng nextLatLng) - throws IOException { - - final String wikidataQuery = FileUtils.readFromResource("/queries/places_query.rq"); - final String query = wikidataQuery - .replace("${LONGITUDE}", - String.format(Locale.ROOT, "%.2f", currentLatLng.getLongitude())) - .replace("${LATITUDE}", String.format(Locale.ROOT, "%.4f", currentLatLng.getLatitude())) - .replace("${NEXT_LONGITUDE}", - String.format(Locale.ROOT, "%.4f", nextLatLng.getLongitude())) - .replace("${NEXT_LATITUDE}", - String.format(Locale.ROOT, "%.4f", nextLatLng.getLatitude())); - - final HttpUrl.Builder urlBuilder = HttpUrl - .parse(sparqlQueryUrl) - .newBuilder() - .addQueryParameter("query", query) - .addQueryParameter("format", "json"); - - final Request request = new Request.Builder() - .url(urlBuilder.build()) - .build(); - - final Response response = okHttpClient.newCall(request).execute(); - if (response.body() != null && response.isSuccessful()) { - final String json = response.body().string(); - final ItemsClass item = gson.fromJson(json, ItemsClass.class); - return item.getResults().getBindings(); - } else { - return null; - } - } - - /** - * Make API Call to get Nearby Places Implementation does not expects a custom query - * - * @param cur Search lat long - * @param language Language - * @param radius Search Radius - * @return - * @throws Exception - */ - @Nullable - public List getNearbyPlaces(final LatLng cur, final String language, final double radius) - throws Exception { - return getNearbyPlaces(cur, language, radius, null); - } - - /** - * Get the QIDs of all Wikidata items that are subclasses of the given Wikidata item. Example: - * bridge -> suspended bridge, aqueduct, etc - */ - public Single> getChildDepictions(String qid, int startPosition, - int limit) throws IOException { - return depictedItemsFrom( - sparqlQuery(qid, startPosition, limit, "/queries/subclasses_query.rq")); - } - - /** - * Get the QIDs of all Wikidata items that are subclasses of the given Wikidata item. Example: - * bridge -> suspended bridge, aqueduct, etc - */ - public Single> getParentDepictions(String qid, int startPosition, - int limit) throws IOException { - return depictedItemsFrom(sparqlQuery(qid, startPosition, limit, - "/queries/parentclasses_query.rq")); - } - - private Single> depictedItemsFrom(Request request) { - return depictsClient.toDepictions(Single.fromCallable(() -> { - try (ResponseBody body = okHttpClient.newCall(request).execute().body()) { - return gson.fromJson(body.string(), SparqlResponse.class); - } - }).doOnError(Timber::e)); - } - - @NotNull - private Request sparqlQuery(String qid, int startPosition, int limit, String fileName) - throws IOException { - String query = FileUtils.readFromResource(fileName) - .replace("${QID}", qid) - .replace("${LANG}", "\"" + Locale.getDefault().getLanguage() + "\"") - .replace("${LIMIT}", "" + limit) - .replace("${OFFSET}", "" + startPosition); - HttpUrl.Builder urlBuilder = HttpUrl - .parse(sparqlQueryUrl) - .newBuilder() - .addQueryParameter("query", query) - .addQueryParameter("format", "json"); - return new Request.Builder() - .url(urlBuilder.build()) - .build(); - } - - public Single 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; - }); - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/mwapi/OkHttpJsonApiClient.kt b/app/src/main/java/fr/free/nrw/commons/mwapi/OkHttpJsonApiClient.kt new file mode 100644 index 000000000..2908498c0 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/mwapi/OkHttpJsonApiClient.kt @@ -0,0 +1,543 @@ +package fr.free.nrw.commons.mwapi + +import android.text.TextUtils +import com.google.gson.Gson +import fr.free.nrw.commons.campaigns.CampaignResponseDTO +import fr.free.nrw.commons.explore.depictions.DepictsClient +import fr.free.nrw.commons.location.LatLng +import fr.free.nrw.commons.nearby.Place +import fr.free.nrw.commons.nearby.model.ItemsClass +import fr.free.nrw.commons.nearby.model.NearbyResponse +import fr.free.nrw.commons.nearby.model.PlaceBindings +import fr.free.nrw.commons.profile.achievements.FeaturedImages +import fr.free.nrw.commons.profile.achievements.FeedbackResponse +import fr.free.nrw.commons.profile.leaderboard.LeaderboardConstants +import fr.free.nrw.commons.profile.leaderboard.LeaderboardResponse +import fr.free.nrw.commons.profile.leaderboard.UpdateAvatarResponse +import fr.free.nrw.commons.upload.FileUtils +import fr.free.nrw.commons.upload.structure.depictions.DepictedItem +import fr.free.nrw.commons.utils.ConfigUtils.isBetaFlavour +import fr.free.nrw.commons.wikidata.model.GetWikidataEditCountResponse +import io.reactivex.Observable +import io.reactivex.Single +import okhttp3.HttpUrl +import okhttp3.HttpUrl.Companion.toHttpUrlOrNull +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import timber.log.Timber +import java.io.IOException +import java.util.Locale +import java.util.regex.Pattern +import javax.inject.Inject +import javax.inject.Singleton + +/** + * Test methods in ok http api client + */ +@Singleton +class OkHttpJsonApiClient @Inject constructor( + private val okHttpClient: OkHttpClient, + private val depictsClient: DepictsClient, + private val wikiMediaToolforgeUrl: HttpUrl, + private val sparqlQueryUrl: String, + private val campaignsUrl: String, + private val gson: Gson +) { + fun getLeaderboard( + userName: String, duration: String?, + category: String?, limit: String?, offset: String? + ): Observable { + val fetchLeaderboardUrlTemplate = + wikiMediaToolforgeUrl.toString() + LeaderboardConstants.LEADERBOARD_END_POINT + val url = String.format(Locale.ENGLISH, + fetchLeaderboardUrlTemplate, userName, duration, category, limit, offset) + val urlBuilder: HttpUrl.Builder = url.toHttpUrlOrNull()!!.newBuilder() + .addQueryParameter("user", userName) + .addQueryParameter("duration", duration) + .addQueryParameter("category", category) + .addQueryParameter("limit", limit) + .addQueryParameter("offset", offset) + Timber.i("Url %s", urlBuilder.toString()) + val request: Request = Request.Builder() + .url(urlBuilder.toString()) + .build() + return Observable.fromCallable({ + val response: Response = okHttpClient.newCall(request).execute() + if (response.body != null && response.isSuccessful) { + val json: String = response.body!!.string() + Timber.d("Response for leaderboard is %s", json) + try { + return@fromCallable gson.fromJson( + json, + LeaderboardResponse::class.java + ) + } catch (e: Exception) { + return@fromCallable LeaderboardResponse() + } + } + LeaderboardResponse() + }) + } + + fun setAvatar(username: String, avatar: String?): Single { + val urlTemplate = wikiMediaToolforgeUrl + .toString() + LeaderboardConstants.UPDATE_AVATAR_END_POINT + return Single.fromCallable({ + val url = String.format(Locale.ENGLISH, urlTemplate, username, avatar) + val urlBuilder: HttpUrl.Builder = url.toHttpUrlOrNull()!!.newBuilder() + .addQueryParameter("user", username) + .addQueryParameter("avatar", avatar) + Timber.i("Url %s", urlBuilder.toString()) + val request: Request = Request.Builder() + .url(urlBuilder.toString()) + .build() + val response: Response = okHttpClient.newCall(request).execute() + if (response.body != null && response.isSuccessful) { + val json: String = response.body!!.string() ?: return@fromCallable null + try { + return@fromCallable gson.fromJson( + json, + UpdateAvatarResponse::class.java + ) + } catch (e: Exception) { + return@fromCallable UpdateAvatarResponse() + } + } + null + }) + } + + fun getUploadCount(userName: String?): Single { + val urlBuilder: HttpUrl.Builder = wikiMediaToolforgeUrl.newBuilder() + .addPathSegments("uploadsbyuser.py") + .addQueryParameter("user", userName) + + if (isBetaFlavour) { + urlBuilder.addQueryParameter("labs", "commonswiki") + } + + val request: Request = Request.Builder() + .url(urlBuilder.build()) + .build() + + return Single.fromCallable({ + val response: Response = okHttpClient.newCall(request).execute() + if (response != null && response.isSuccessful) { + val responseBody = response.body + if (null != responseBody) { + val responseBodyString = responseBody.string().trim { it <= ' ' } + if (!TextUtils.isEmpty(responseBodyString)) { + try { + return@fromCallable responseBodyString.toInt() + } catch (e: NumberFormatException) { + Timber.e(e) + } + } + } + } + 0 + }) + } + + fun getWikidataEdits(userName: String?): Single { + val urlBuilder: HttpUrl.Builder = wikiMediaToolforgeUrl.newBuilder() + .addPathSegments("wikidataedits.py") + .addQueryParameter("user", userName) + + if (isBetaFlavour) { + urlBuilder.addQueryParameter("labs", "commonswiki") + } + + val request: Request = Request.Builder() + .url(urlBuilder.build()) + .build() + + return Single.fromCallable({ + val response: Response = okHttpClient.newCall(request).execute() + if (response != null && response.isSuccessful && response.body != null) { + var json: String = response.body!!.string() + // Extract JSON from response + json = json.substring(json.indexOf('{')) + val countResponse = gson + .fromJson( + json, + GetWikidataEditCountResponse::class.java + ) + if (null != countResponse) { + return@fromCallable countResponse.wikidataEditCount + } + } + 0 + }) + } + + fun getAchievements(userName: String?): Single { + val suffix = if (isBetaFlavour) "/feedback.py?labs=commonswiki" else "/feedback.py" + val fetchAchievementUrlTemplate = wikiMediaToolforgeUrl.toString() + suffix + return Single.fromCallable({ + val url = String.format( + Locale.ENGLISH, + fetchAchievementUrlTemplate, + userName + ) + val urlBuilder: HttpUrl.Builder = url.toHttpUrlOrNull()!!.newBuilder() + .addQueryParameter("user", userName) + val request: Request = Request.Builder() + .url(urlBuilder.toString()) + .build() + val response: Response = okHttpClient.newCall(request).execute() + if (response.body != null && response.isSuccessful) { + var json: String = response.body!!.string() + // Extract JSON from response + json = json.substring(json.indexOf('{')) + Timber.d("Response for achievements is %s", json) + try { + return@fromCallable gson.fromJson( + json, + FeedbackResponse::class.java + ) + } catch (e: Exception) { + return@fromCallable FeedbackResponse(0, 0, 0, FeaturedImages(0, 0), 0, "") + } + } + null + }) + } + + @JvmOverloads + @Throws(Exception::class) + fun getNearbyPlaces( + cur: LatLng, language: String, radius: Double, + customQuery: String? = null + ): List? { + Timber.d("Fetching nearby items at radius %s", radius) + Timber.d("CUSTOM_SPARQL: %s", (customQuery != null).toString()) + val wikidataQuery: String = if (customQuery != null) { + customQuery + } else { + FileUtils.readFromResource("/queries/radius_query_for_upload_wizard.rq") + } + val query = wikidataQuery + .replace("\${RAD}", String.format(Locale.ROOT, "%.2f", radius)) + .replace("\${LAT}", String.format(Locale.ROOT, "%.4f", cur.latitude)) + .replace("\${LONG}", String.format(Locale.ROOT, "%.4f", cur.longitude)) + .replace("\${LANG}", language) + + val urlBuilder: HttpUrl.Builder = sparqlQueryUrl.toHttpUrlOrNull()!! + .newBuilder() + .addQueryParameter("query", query) + .addQueryParameter("format", "json") + + val request: Request = Request.Builder() + .url(urlBuilder.build()) + .build() + + val response = okHttpClient.newCall(request).execute() + if (response.body != null && response.isSuccessful) { + val json = response.body!!.string() + val nearbyResponse = gson.fromJson(json, NearbyResponse::class.java) + val bindings = nearbyResponse.results.bindings + val places: MutableList = ArrayList() + for (item in bindings) { + val placeFromNearbyItem = Place.from(item) + placeFromNearbyItem.isMonument = false + places.add(placeFromNearbyItem) + } + return places + } + throw Exception(response.message) + } + + @Throws(Exception::class) + fun getNearbyPlaces( + screenTopRight: LatLng, + screenBottomLeft: LatLng, language: String, + shouldQueryForMonuments: Boolean, customQuery: String? + ): List? { + Timber.d("CUSTOM_SPARQL: %s", (customQuery != null).toString()) + + val wikidataQuery: String = if (customQuery != null) { + customQuery + } else if (!shouldQueryForMonuments) { + FileUtils.readFromResource("/queries/rectangle_query_for_nearby.rq") + } else { + FileUtils.readFromResource("/queries/rectangle_query_for_nearby_monuments.rq") + } + + val westCornerLat = screenTopRight.latitude + val westCornerLong = screenTopRight.longitude + val eastCornerLat = screenBottomLeft.latitude + val eastCornerLong = screenBottomLeft.longitude + + val query = wikidataQuery + .replace("\${LAT_WEST}", String.format(Locale.ROOT, "%.4f", westCornerLat)) + .replace("\${LONG_WEST}", String.format(Locale.ROOT, "%.4f", westCornerLong)) + .replace("\${LAT_EAST}", String.format(Locale.ROOT, "%.4f", eastCornerLat)) + .replace("\${LONG_EAST}", String.format(Locale.ROOT, "%.4f", eastCornerLong)) + .replace("\${LANG}", language) + val urlBuilder: HttpUrl.Builder = sparqlQueryUrl.toHttpUrlOrNull()!! + .newBuilder() + .addQueryParameter("query", query) + .addQueryParameter("format", "json") + + val request: Request = Request.Builder() + .url(urlBuilder.build()) + .build() + + val response = okHttpClient.newCall(request).execute() + if (response.body != null && response.isSuccessful) { + val json = response.body!!.string() + val nearbyResponse = gson.fromJson(json, NearbyResponse::class.java) + val bindings = nearbyResponse.results.bindings + val places: MutableList = ArrayList() + for (item in bindings) { + val placeFromNearbyItem = Place.from(item) + if (shouldQueryForMonuments && item.getMonument() != null) { + placeFromNearbyItem.isMonument = true + } else { + placeFromNearbyItem.isMonument = false + } + places.add(placeFromNearbyItem) + } + return places + } + throw Exception(response.message) + } + + @Throws(IOException::class) + fun getPlaces( + placeList: List, language: String + ): List? { + val wikidataQuery = FileUtils.readFromResource("/queries/query_for_item.rq") + var qids = "" + for (place in placeList) { + qids += """ +${"wd:" + place.wikiDataEntityId}""" + } + val query = wikidataQuery + .replace("\${ENTITY}", qids) + .replace("\${LANG}", language) + val urlBuilder: HttpUrl.Builder = sparqlQueryUrl.toHttpUrlOrNull()!! + .newBuilder() + .addQueryParameter("query", query) + .addQueryParameter("format", "json") + + val request: Request = Request.Builder().url(urlBuilder.build()).build() + + okHttpClient.newCall(request).execute().use { response -> + if (response.isSuccessful) { + val json = response.body!!.string() + val nearbyResponse = gson.fromJson(json, NearbyResponse::class.java) + val bindings = nearbyResponse.results.bindings + val places: MutableList = ArrayList() + for (item in bindings) { + val placeFromNearbyItem = Place.from(item) + places.add(placeFromNearbyItem) + } + return places + } else { + throw IOException("Unexpected response code: " + response.code) + } + } + } + + @Throws(Exception::class) + fun getPlacesAsKML(leftLatLng: LatLng, rightLatLng: LatLng): String? { + var kmlString = """ + + + """ + val placeBindings = runQuery( + leftLatLng, + rightLatLng + ) + if (placeBindings != null) { + for ((item1, label, location, clas) in placeBindings) { + if (item1 != null && label != null && clas != null) { + val input = location.value + val pattern = Pattern.compile( + "Point\\(([-+]?[0-9]*\\.?[0-9]+) ([-+]?[0-9]*\\.?[0-9]+)\\)" + ) + val matcher = pattern.matcher(input) + + if (matcher.find()) { + val longStr = matcher.group(1) + val latStr = matcher.group(2) + val itemUrl = item1.value + val itemName = label.value.replace("&", "&") + val itemLatitude = latStr + val itemLongitude = longStr + val itemClass = clas.value + + val formattedItemName = + if (!itemClass.isEmpty()) + "$itemName ($itemClass)" + else + itemName + + val kmlEntry = (""" + + $formattedItemName + $itemUrl + + $itemLongitude,$itemLatitude + + """) + kmlString = kmlString + kmlEntry + } else { + Timber.e("No match found") + } + } + } + } + kmlString = """$kmlString + + +""" + return kmlString + } + + @Throws(Exception::class) + fun getPlacesAsGPX(leftLatLng: LatLng, rightLatLng: LatLng): String? { + var gpxString = (""" + +""") + + val placeBindings = runQuery(leftLatLng, rightLatLng) + if (placeBindings != null) { + for ((item1, label, location, clas) in placeBindings) { + if (item1 != null && label != null && clas != null) { + val input = location.value + val pattern = Pattern.compile( + "Point\\(([-+]?[0-9]*\\.?[0-9]+) ([-+]?[0-9]*\\.?[0-9]+)\\)" + ) + val matcher = pattern.matcher(input) + + if (matcher.find()) { + val longStr = matcher.group(1) + val latStr = matcher.group(2) + val itemUrl = item1.value + val itemName = label.value.replace("&", "&") + val itemLatitude = latStr + val itemLongitude = longStr + val itemClass = clas.value + + val formattedItemName = if (!itemClass.isEmpty()) + "$itemName ($itemClass)" + else + itemName + + val gpxEntry = + (""" + + $itemName + $itemUrl + """) + gpxString = gpxString + gpxEntry + } else { + Timber.e("No match found") + } + } + } + } + gpxString = "$gpxString\n" + return gpxString + } + + @Throws(IOException::class) + fun getChildDepictions( + qid: String, startPosition: Int, + limit: Int + ): Single> = + depictedItemsFrom(sparqlQuery(qid, startPosition, limit, "/queries/subclasses_query.rq")) + + @Throws(IOException::class) + fun getParentDepictions( + qid: String, startPosition: Int, + limit: Int + ): Single> = depictedItemsFrom( + sparqlQuery( + qid, + startPosition, + limit, + "/queries/parentclasses_query.rq" + ) + ) + + fun getCampaigns(): Single { + return Single.fromCallable({ + val request: Request = Request.Builder().url(campaignsUrl).build() + val response: Response = okHttpClient.newCall(request).execute() + if (response.body != null && response.isSuccessful) { + val json: String = response.body!!.string() + return@fromCallable gson.fromJson( + json, + CampaignResponseDTO::class.java + ) + } + null + }) + } + + private fun depictedItemsFrom(request: Request): Single> { + return depictsClient.toDepictions(Single.fromCallable({ + okHttpClient.newCall(request).execute().body.use { body -> + return@fromCallable gson.fromJson( + body!!.string(), + SparqlResponse::class.java + ) + } + }).doOnError({ t: Throwable? -> Timber.e(t) })) + } + + @Throws(IOException::class) + private fun sparqlQuery( + qid: String, + startPosition: Int, + limit: Int, + fileName: String + ): Request { + val query = FileUtils.readFromResource(fileName) + .replace("\${QID}", qid) + .replace("\${LANG}", "\"" + Locale.getDefault().language + "\"") + .replace("\${LIMIT}", "" + limit) + .replace("\${OFFSET}", "" + startPosition) + val urlBuilder: HttpUrl.Builder = sparqlQueryUrl.toHttpUrlOrNull()!! + .newBuilder() + .addQueryParameter("query", query) + .addQueryParameter("format", "json") + return Request.Builder().url(urlBuilder.build()).build() + } + + @Throws(IOException::class) + private fun runQuery(currentLatLng: LatLng, nextLatLng: LatLng): List? { + val wikidataQuery = FileUtils.readFromResource("/queries/places_query.rq") + val query = wikidataQuery + .replace("\${LONGITUDE}", String.format(Locale.ROOT, "%.2f", currentLatLng.longitude)) + .replace("\${LATITUDE}", String.format(Locale.ROOT, "%.4f", currentLatLng.latitude)) + .replace("\${NEXT_LONGITUDE}", String.format(Locale.ROOT, "%.4f", nextLatLng.longitude)) + .replace("\${NEXT_LATITUDE}", String.format(Locale.ROOT, "%.4f", nextLatLng.latitude)) + + val urlBuilder: HttpUrl.Builder = sparqlQueryUrl.toHttpUrlOrNull()!! + .newBuilder() + .addQueryParameter("query", query) + .addQueryParameter("format", "json") + + val request: Request = Request.Builder().url(urlBuilder.build()).build() + + val response = okHttpClient.newCall(request).execute() + if (response.body != null && response.isSuccessful) { + val json = response.body!!.string() + val item = gson.fromJson(json, ItemsClass::class.java) + return item.results.bindings + } else { + return null + } + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/upload/FileProcessor.kt b/app/src/main/java/fr/free/nrw/commons/upload/FileProcessor.kt index 68c6f13fb..d51ab1796 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/FileProcessor.kt +++ b/app/src/main/java/fr/free/nrw/commons/upload/FileProcessor.kt @@ -194,7 +194,7 @@ class FileProcessor requireNotNull(imageCoordinates.decimalCoords) compositeDisposable.add( apiCall - .request(imageCoordinates.decimalCoords) + .request(imageCoordinates.decimalCoords!!) .subscribeOn(Schedulers.io()) .observeOn(Schedulers.io()) .subscribe( @@ -220,7 +220,7 @@ class FileProcessor .concatMap { Observable.fromCallable { okHttpJsonApiClient.getNearbyPlaces( - imageCoordinates.latLng, + imageCoordinates.latLng!!, Locale.getDefault().language, it, ) diff --git a/app/src/test/kotlin/fr/free/nrw/commons/campaigns/CampaignsPresenterTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/campaigns/CampaignsPresenterTest.kt index ec3ad82f1..f876916b6 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/campaigns/CampaignsPresenterTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/campaigns/CampaignsPresenterTest.kt @@ -49,13 +49,13 @@ class CampaignsPresenterTest { campaignsSingle = Single.just(campaignResponseDTO) campaignsPresenter = CampaignsPresenter(okHttpJsonApiClient, testScheduler, testScheduler) campaignsPresenter.onAttachView(view) - Mockito.`when`(okHttpJsonApiClient.campaigns).thenReturn(campaignsSingle) + Mockito.`when`(okHttpJsonApiClient.getCampaigns()).thenReturn(campaignsSingle) } @Test fun getCampaignsTestNoCampaigns() { campaignsPresenter.getCampaigns() - verify(okHttpJsonApiClient).campaigns + verify(okHttpJsonApiClient).getCampaigns() testScheduler.triggerActions() verify(view).showCampaigns(null) } @@ -77,7 +77,7 @@ class CampaignsPresenterTest { Mockito.`when`(campaign.endDate).thenReturn(endDateString) Mockito.`when`(campaign.startDate).thenReturn(startDateString) Mockito.`when`(campaignResponseDTO.campaigns).thenReturn(campaigns) - verify(okHttpJsonApiClient).campaigns + verify(okHttpJsonApiClient).getCampaigns() testScheduler.triggerActions() verify(view).showCampaigns(campaign) }