Merge changes from 3.1-release (#4629)

* Cherry-Picked NPE fix from master (#4569)

* Fix notification bug #4547 (#4570)

* Make Single Query for Nearby and WLM pins (#4573)

* Merge nearby and monument queries

* Bug Fix- query resource path change on shouldQueryForMonuments

* Bug Fixes
1. Propagate exceptions for nearby API calls to caller
2. Fix too much work on main thread exception in NearbyParentFragment

* Modify parameters for Nearby query

* Bug fix- current location marker (#4580)

* Move WLM template below geolocation template (#4582)

* Modify string for WLM upload notice

* Fix bug #4583 (#4591)

* Fix bug #4585 by updating kotlin and acra version (#4592)

* Fixes #4554 - only use WLM2021 template for countries that are included in it (#4574)

* Fixes #4554
1. For WLM uploads reverse geo code and see if the country code is supported -only then is the WLM upload flow triggered, otherwise usual nearby uploads happen
2. Bug Fix - Current Location marker and area

* Fixed compile error added after rebasing

* Bug fix for country code in reverse geo code

* Update WLM camaign dates [Do not merge now, merge only after alpha release] (#4584)

* Updates dates for WML campaign

* Bug fix- campaign dates

* Fixed logic for WLM enablement - stick to the month of September

* Add countries supported by WLM2021 template, except Italy

* Versioning for v3.1.0

* Update changelog.md

* Fix empty default lang bug (#4608)

* Fix bug #4583

* Fix empty default lang bug

* Fixes #4595 - Updated nearby query (#4622)

* Fixes #4595 - Updated nearby query

* Removed logic to replace local language in nearby query - that might acccidentally replace other strings

* Fetch property location in usual nearby query

* Remove duplicate line (#4626)

* Change "learn more" link to new wiki

* Add Sweden's P3426 to property filter

* Fixes #4601 - 1. Handle possible exceptions in upload file from stash 2. Modify MWException, as error is nullable, update getTitle and getMessage to rever that (#4627)

* Versioning for v3.1.1

* Update changelog.md

* Updated DB version to rever integrity

Co-authored-by: Madhur Gupta <30932899+madhurgupta10@users.noreply.github.com>
Co-authored-by: Josephine Lim <josephinelim86@gmail.com>
This commit is contained in:
Ashish 2021-09-16 18:39:29 +05:30 committed by GitHub
parent b91222329e
commit 7476b0a24d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
38 changed files with 383 additions and 409 deletions

View file

@ -1,5 +1,14 @@
# Wikimedia Commons for Android
## v3.1.1
- Optimized Nearby query
- Added Sweden's property for WLM 2021
- Added link to wiki explaining how to contribute to WLM through app
- Fixed various bugs and crashes
## v3.1.0
- Added Wiki Loves Monuments integration for WLM 2021
## v3.0.2
- Fixed crash when uploading high res image
- Fixed crash when viewing images in Explore

View file

@ -58,8 +58,8 @@ dependencies {
implementation "com.squareup.okhttp3:okhttp-ws:$OKHTTP_VERSION"
// Logging
implementation 'ch.acra:acra-dialog:5.8.1-beta11'
implementation 'ch.acra:acra-mail:5.8.1-beta11'
implementation 'ch.acra:acra-dialog:5.8.4'
implementation 'ch.acra:acra-mail:5.8.4'
implementation 'org.slf4j:slf4j-api:1.7.25'
api('com.github.tony19:logback-android-classic:1.1.1-6') {
exclude group: 'com.google.android', module: 'android'
@ -155,8 +155,8 @@ android {
defaultConfig {
//applicationId 'fr.free.nrw.commons'
versionCode 1021
versionName '3.0.2'
versionCode 1025
versionName '3.1.1'
setProperty("archivesBaseName", "app-commons-v$versionName-" + getBranchName())
minSdkVersion 19
@ -226,7 +226,7 @@ android {
}
configurations.all {
resolutionStrategy.force 'androidx.annotation:annotation:1.0.2'
resolutionStrategy.force 'androidx.annotation:annotation:1.1.0'
exclude module: 'okhttp-ws'
}
flavorDimensions 'tier'

View file

@ -12,19 +12,19 @@ class Media constructor(
* @return pageId for the current media object
* Wikibase Identifier associated with media files
*/
val pageId: String = UUID.randomUUID().toString(),
val thumbUrl: String? = null,
var pageId: String = UUID.randomUUID().toString(),
var thumbUrl: String? = null,
/**
* Gets image URL
* @return Image URL
*/
val imageUrl: String? = null,
var imageUrl: String? = null,
/**
* Gets the name of the file.
* @return file name as a string
*/
val filename: String? = null,
var filename: String? = null,
/**
* Gets the file description.
* @return file description as a string
@ -41,7 +41,7 @@ class Media constructor(
* Can be null.
* @return upload date as a Date
*/
val dateUploaded: Date? = null,
var dateUploaded: Date? = null,
/**
* Gets the license name of the file.
* @return license as a String
@ -52,7 +52,7 @@ class Media constructor(
* @param license license name as a String
*/
var license: String? = null,
val licenseUrl: String? = null,
var licenseUrl: String? = null,
/**
* Gets the name of the creator of the file.
* @return author name as a String
@ -69,15 +69,15 @@ class Media constructor(
* Gets the categories the file falls under.
* @return file categories as an ArrayList of Strings
*/
val categories: List<String>? = null,
var categories: List<String>? = null,
/**
* Gets the coordinates of where the file was created.
* @return file coordinates as a LatLng
*/
var coordinates: LatLng? = null,
val captions: Map<String, String> = emptyMap(),
val descriptions: Map<String, String> = emptyMap(),
val depictionIds: List<String> = emptyList()
var captions: Map<String, String> = emptyMap(),
var descriptions: Map<String, String> = emptyMap(),
var depictionIds: List<String> = emptyList()
) : Parcelable {
constructor(

View file

@ -218,12 +218,11 @@ public class Utils {
* @param date
* @return
*/
public static boolean isMonumentsEnabled(final Date date, final JsonKvStore store){
if(date.getDay()>=1 && date.getMonth()>=9 && date.getDay()<=31 && date.getMonth()<=10 ){
public static boolean isMonumentsEnabled(final Date date) {
if (date.getMonth() == 8) {
return true;
}
return store.getBoolean(CAMPAIGNS_DEFAULT_PREFERENCE) || true ;
return false;
}
/**
@ -241,7 +240,7 @@ public class Utils {
* @return
*/
public static String getWLMEndDate() {
return "31 Oct";
return "30 Sep";
}
}

View file

@ -117,9 +117,8 @@ public class CampaignView extends SwipableCardView {
.parse(campaign.getStartDate());
final Date endDate = CommonsDateUtil.getIso8601DateFormatShort()
.parse(campaign.getEndDate());
tvDates.setText(
String.format("%1s - %2s", startDate,
endDate));
tvDates.setText(String.format("%1s - %2s", DateUtil.getExtraShortDateString(startDate),
DateUtil.getExtraShortDateString(endDate)));
}
} catch (final ParseException e) {
e.printStackTrace();

View file

@ -40,7 +40,8 @@ data class Contribution constructor(
var dateCreated: Date? = null,
var dateModified: Date? = null,
var hasInvalidLocation : Int = 0,
var contentUri: Uri? = null
var contentUri: Uri? = null,
var countryCode : String? = null
) : Parcelable {
fun completeWith(media: Media): Contribution {

View file

@ -454,7 +454,7 @@ public class ContributionsFragment
private void updateClosestNearbyCardViewInfo() {
curLatLng = locationManager.getLastLocation();
compositeDisposable.add(Observable.fromCallable(() -> nearbyController
.loadAttractionsFromLocation(curLatLng, curLatLng, true, false)) // thanks to boolean, it will only return closest result
.loadAttractionsFromLocation(curLatLng, curLatLng, true, false, false)) // thanks to boolean, it will only return closest result
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::updateNearbyNotification,
@ -528,7 +528,7 @@ public class ContributionsFragment
* of campaigns on the campaigns card
*/
private void fetchCampaigns() {
if (Utils.isMonumentsEnabled(new Date(), store)) {
if (Utils.isMonumentsEnabled(new Date())) {
campaignView.setCampaign(wlmCampaign);
campaignView.setVisibility(View.VISIBLE);
} else if (store.getBoolean(CampaignView.CAMPAIGNS_DEFAULT_PREFERENCE, true)) {

View file

@ -13,7 +13,7 @@ import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao;
public class DBOpenHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "commons.db";
private static final int DATABASE_VERSION = 18;
private static final int DATABASE_VERSION = 19;
public static final String CONTRIBUTIONS_TABLE = "contributions";
private final String DROP_TABLE_STATEMENT="DROP TABLE IF EXISTS %s";

View file

@ -14,7 +14,7 @@ import fr.free.nrw.commons.upload.depicts.DepictsDao
* The database for accessing the respective DAOs
*
*/
@Database(entities = [Contribution::class, Depicts::class, UploadedStatus::class], version = 9, exportSchema = false)
@Database(entities = [Contribution::class, Depicts::class, UploadedStatus::class], version = 10, exportSchema = false)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun contributionDao(): ContributionDao

View file

@ -373,8 +373,6 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
media = getArguments().getParcelable("media");
}
media = detailProvider.getMediaAtPosition(index);
if(media != null && applicationKvStore.getBoolean(String.format(NOMINATING_FOR_DELETION_MEDIA, media.getImageUrl()), false)) {
enableProgressBar();
}

View file

@ -5,6 +5,7 @@ import static fr.free.nrw.commons.profile.leaderboard.LeaderboardConstants.UPDAT
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;
@ -264,105 +265,52 @@ public class OkHttpJsonApiClient {
});
}
public Observable<List<Place>> getNearbyPlaces(LatLng cur, String language, double radius)
@Nullable
public List<Place> getNearbyPlaces(final LatLng cur, final String language, final double radius,
final boolean shouldQueryForMonuments)
throws Exception {
Timber.d("Fetching nearby items at radius %s", radius);
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}", language);
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<>();
});
}
/**
* Wikidata query to fetch monuments
*
* @param cur : The current location coordinates
* @param language : The language
* @param radius : The radius around the current location within which we expect the results
* @return
* @throws IOException
*/
public Observable<List<Place>> getNearbyMonuments(LatLng cur, String language, final double radius){
Timber.d("Fetching monuments at radius %s", radius);
final String wikidataQuery;
try {
wikidataQuery = FileUtils.readFromResource("/queries/monuments_query.rq");
if (TextUtils.isEmpty(language)) {
language = "en";
if (!shouldQueryForMonuments) {
wikidataQuery = FileUtils.readFromResource("/queries/nearby_query.rq");
} else {
wikidataQuery = FileUtils.readFromResource("/queries/nearby_query_monuments.rq");
}
String query = wikidataQuery
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);
HttpUrl.Builder urlBuilder = HttpUrl
final HttpUrl.Builder urlBuilder = HttpUrl
.parse(sparqlQueryUrl)
.newBuilder()
.addQueryParameter("query", query)
.addQueryParameter("format", "json");
Request request = new Request.Builder()
final Request request = new Request.Builder()
.url(urlBuilder.build())
.build();
Timber.d("Monuments URL: %s", request.url().toString());
return Observable.fromCallable(() -> {
final Response response = okHttpClient.newCall(request).execute();
if (response != null && response.body() != null && response.isSuccessful()) {
if (response.body() != null && response.isSuccessful()) {
final String json = response.body().string();
if (json == null) {
return new ArrayList<>();
}
final NearbyResponse nearbyResponse = gson.fromJson(json, NearbyResponse.class);
final List<NearbyResultItem> bindings = nearbyResponse.getResults().getBindings();
final List<Place> places = new ArrayList<>();
for (final NearbyResultItem item : bindings) {
final Place place = Place.from(item);
place.setMonument(true);
places.add(place);
final Place placeFromNearbyItem = Place.from(item);
if (shouldQueryForMonuments && item.getMonument() != null) {
placeFromNearbyItem.setMonument(true);
} else {
placeFromNearbyItem.setMonument(false);
}
places.add(placeFromNearbyItem);
}
return places;
}
return new ArrayList<>();
});
} catch (final IOException e) {
e.printStackTrace();
return Observable.error(e);
}
throw new Exception(response.message());
}
/**

View file

@ -57,7 +57,9 @@ public class NearbyController {
* @return NearbyPlacesInfo a variable holds Place list without distance information
* and boundary coordinates of current Place List
*/
public NearbyPlacesInfo loadAttractionsFromLocation(LatLng curLatLng, LatLng searchLatLng, boolean returnClosestResult, boolean checkingAroundCurrentLocation) throws IOException {
public NearbyPlacesInfo loadAttractionsFromLocation(final LatLng curLatLng, final LatLng searchLatLng,
final boolean returnClosestResult, final boolean checkingAroundCurrentLocation,
final boolean shouldQueryForMonuments) throws Exception {
Timber.d("Loading attractions near %s", searchLatLng);
NearbyPlacesInfo nearbyPlacesInfo = new NearbyPlacesInfo();
@ -66,7 +68,9 @@ public class NearbyController {
Timber.d("Loading attractions nearby, but curLatLng is null");
return null;
}
List<Place> places = nearbyPlaces.radiusExpander(searchLatLng, Locale.getDefault().getLanguage(), returnClosestResult);
List<Place> places = nearbyPlaces
.radiusExpander(searchLatLng, Locale.getDefault().getLanguage(), returnClosestResult,
shouldQueryForMonuments);
if (null != places && places.size() > 0) {
LatLng[] boundaryCoordinates = {places.get(0).location, // south
@ -128,11 +132,6 @@ public class NearbyController {
}
}
public Observable<List<Place>> queryWikiDataForMonuments(
final LatLng latLng, final String language) {
return nearbyPlaces.queryWikiDataForMonuments(latLng, language);
}
/**
* Loads attractions from location for list view, we need to return Place data type.
*

View file

@ -1,8 +1,6 @@
package fr.free.nrw.commons.nearby;
import io.reactivex.Observable;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.util.Collections;
import java.util.List;
@ -19,8 +17,8 @@ import timber.log.Timber;
@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 double INITIAL_RADIUS = 0.3; // in kilometers
private static final double RADIUS_MULTIPLIER = 2.0;
public double radius = INITIAL_RADIUS;
private final OkHttpJsonApiClient okHttpJsonApiClient;
@ -41,12 +39,12 @@ public class NearbyPlaces {
* @param lang user's language
* @param returnClosestResult true if only the nearest point is desired
* @return list of places obtained
* @throws IOException if query fails
*/
List<Place> radiusExpander(LatLng curLatLng, String lang, boolean returnClosestResult) throws IOException {
List<Place> radiusExpander(final LatLng curLatLng, final String lang, final boolean returnClosestResult
, final boolean shouldQueryForMonuments) throws Exception {
int minResults;
double maxRadius;
final int minResults;
final double maxRadius;
List<Place> places = Collections.emptyList();
@ -57,19 +55,14 @@ public class NearbyPlaces {
maxRadius = 5; // Return places only in 5 km area
radius = INITIAL_RADIUS; // refresh radius again, otherwise increased radius is grater than MAX_RADIUS, thus returns null
} else {
minResults = 40;
minResults = 20;
maxRadius = 300.0; // in kilometers
radius = INITIAL_RADIUS;
}
// Increase the radius gradually to find a satisfactory number of nearby places
while (radius <= maxRadius) {
try {
places = getFromWikidataQuery(curLatLng, lang, radius);
} catch (final Exception e) {
Timber.e(e, "Exception in fetching nearby places");
break;
}
places = getFromWikidataQuery(curLatLng, lang, radius, shouldQueryForMonuments);
Timber.d("%d results at radius: %f", places.size(), radius);
if (places.size() >= minResults) {
break;
@ -89,16 +82,12 @@ public class NearbyPlaces {
* @param cur coordinates of search location
* @param lang user's language
* @param radius radius for search, as determined by radiusExpander()
* @param shouldQueryForMonuments should the query include properites for monuments
* @return list of places obtained
* @throws IOException if query fails
*/
public List<Place> getFromWikidataQuery(LatLng cur, String lang, double radius) throws Exception {
return okHttpJsonApiClient.getNearbyPlaces(cur, lang, radius).blockingSingle();
}
public Observable<List<Place>> queryWikiDataForMonuments(
LatLng latLng, String language) {
return okHttpJsonApiClient
.getNearbyMonuments(latLng, language, radius);
public List<Place> getFromWikidataQuery(final LatLng cur, final String lang,
final double radius, final boolean shouldQueryForMonuments) throws Exception {
return okHttpJsonApiClient.getNearbyPlaces(cur, lang, radius, shouldQueryForMonuments);
}
}

View file

@ -245,7 +245,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
/**
* WLM URL
*/
public static final String WLM_URL = "https://www.wikilovesmonuments.org/";
public static final String WLM_URL = "https://commons.wikimedia.org/wiki/Commons:Mobile_app/Contributing_to_WLM_using_the_app";
@NonNull
public static NearbyParentFragment newInstance() {
@ -284,7 +284,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
isDarkTheme = systemThemeUtils.isDeviceInNightMode();
if (Utils.isMonumentsEnabled(new Date(), applicationKvStore)) {
if (Utils.isMonumentsEnabled(new Date())) {
rlContainerWLMMonthMessage.setVisibility(View.VISIBLE);
} else {
rlContainerWLMMonthMessage.setVisibility(View.GONE);
@ -513,7 +513,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
setBottomSheetCallbacks();
decideButtonVisibilities();
addActionToTitle();
if(!Utils.isMonumentsEnabled(new Date(), applicationKvStore)){
if (!Utils.isMonumentsEnabled(new Date())) {
chipWlm.setVisibility(View.GONE);
}
}
@ -883,29 +883,11 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
final Observable<NearbyPlacesInfo> nearbyPlacesInfoObservable = Observable
.fromCallable(() -> nearbyController
.loadAttractionsFromLocation(curlatLng, searchLatLng, false, true));
.loadAttractionsFromLocation(curlatLng, searchLatLng,
false, true, Utils.isMonumentsEnabled(new Date())));
Observable<List<Place>> observableWikidataMonuments = Observable.empty();
if(Utils.isMonumentsEnabled(new Date(), applicationKvStore)){
observableWikidataMonuments =
nearbyController
.queryWikiDataForMonuments(searchLatLng, Locale.getDefault().getLanguage());
}
compositeDisposable.add(Observable.zip(nearbyPlacesInfoObservable
, observableWikidataMonuments.onErrorReturn(throwable -> {
showErrorMessage(getString(R.string.error_fetching_nearby_monuments) + throwable
.getLocalizedMessage());
return new ArrayList<>();
}),
(nearbyPlacesInfo, monuments) -> {
final List<Place> places = mergeNearbyPlacesAndMonuments(nearbyPlacesInfo.placeList,
monuments);
nearbyPlacesInfo.placeList.clear();
nearbyPlacesInfo.placeList.addAll(places);
return nearbyPlacesInfo;
}).subscribeOn(Schedulers.io())
compositeDisposable.add(nearbyPlacesInfoObservable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(nearbyPlacesInfo -> {
updateMapMarkers(nearbyPlacesInfo, true);
@ -925,28 +907,11 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
final Observable<NearbyPlacesInfo> nearbyPlacesInfoObservable = Observable
.fromCallable(() -> nearbyController
.loadAttractionsFromLocation(curlatLng, searchLatLng, false, false));
.loadAttractionsFromLocation(curlatLng, searchLatLng,
false, true, Utils.isMonumentsEnabled(new Date())));
Observable<List<Place>> observableWikidataMonuments = Observable.empty();
if (Utils.isMonumentsEnabled(new Date(), applicationKvStore)) {
observableWikidataMonuments = nearbyController
.queryWikiDataForMonuments(searchLatLng, Locale.getDefault().getLanguage());
}
compositeDisposable.add(Observable.zip(nearbyPlacesInfoObservable
, observableWikidataMonuments.onErrorReturn(throwable -> {
showErrorMessage(getString(R.string.error_fetching_nearby_monuments) + throwable
.getLocalizedMessage());
return new ArrayList<>();
}),
(nearbyPlacesInfo, monuments) -> {
final List<Place> places = mergeNearbyPlacesAndMonuments(nearbyPlacesInfo.placeList,
monuments);
nearbyPlacesInfo.placeList.clear();
nearbyPlacesInfo.placeList.addAll(places);
return nearbyPlacesInfo;
}).subscribeOn(Schedulers.io())
compositeDisposable.add(nearbyPlacesInfoObservable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(nearbyPlacesInfo -> {
updateMapMarkers(nearbyPlacesInfo, false);
@ -961,25 +926,6 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
}));
}
/**
* If a nearby place happens to be a monument as well, don't make the Pin's overlap, instead
* show it as a monument
*
* @param nearbyPlaces
* @param monuments
* @return
*/
private List<Place> mergeNearbyPlacesAndMonuments(List<Place> nearbyPlaces, List<Place> monuments){
List<Place> allPlaces= new ArrayList<>();
allPlaces.addAll(monuments);
for (Place place : nearbyPlaces){
if(!allPlaces.contains(place)){
allPlaces.add(place);
}
}
return allPlaces;
}
/**
* Populates places for your location, should be used for finding nearby places around a
* location where you are.
@ -1248,8 +1194,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
.position(new LatLng(curLatLng.getLatitude(),
curLatLng.getLongitude()));
currentLocationMarkerOptions.setIcon(icon); // Set custom icon
mapView.post(() -> currentLocationMarker = mapBox.addMarker(currentLocationMarkerOptions));
mapView.post(
() -> currentLocationMarker = mapBox.addMarker(currentLocationMarkerOptions));
final List<LatLng> circle = UiUtils
.createCircleArray(curLatLng.getLatitude(), curLatLng.getLongitude(),
@ -1259,8 +1205,9 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
.addAll(circle)
.strokeColor(getResources().getColor(R.color.current_marker_stroke))
.fillColor(getResources().getColor(R.color.current_marker_fill));
mapView.post(() -> currentLocationPolygon = mapBox.addPolygon(currentLocationPolygonOptions));
mapView.post(
() -> currentLocationPolygon = mapBox
.addPolygon(currentLocationPolygonOptions));
});
} else {
Timber.d("not adding current location marker..current location is null");

View file

@ -14,7 +14,8 @@ class NearbyResultItem(private val item: ResultTuple?,
@field:SerializedName("pic") private val pic: ResultTuple?,
@field:SerializedName("destroyed") private val destroyed: ResultTuple?,
@field:SerializedName("description") private val description: ResultTuple?,
@field:SerializedName("endTime") private val endTime: ResultTuple?) {
@field:SerializedName("endTime") private val endTime: ResultTuple?,
@field:SerializedName("monument") private val monument: ResultTuple?) {
fun getItem(): ResultTuple {
return item ?: ResultTuple()
@ -71,4 +72,8 @@ class NearbyResultItem(private val item: ResultTuple?,
fun getAddress(): String {
return address?.value?:""
}
fun getMonument():ResultTuple?{
return monument
}
}

View file

@ -283,13 +283,14 @@ public class UploadRepository {
* @return
*/
@Nullable
public Place checkNearbyPlaces(double decLatitude, double decLongitude) {
public Place checkNearbyPlaces(final double decLatitude, final double decLongitude) {
try {
List<Place> fromWikidataQuery = nearbyPlaces.getFromWikidataQuery(new LatLng(
final List<Place> fromWikidataQuery = nearbyPlaces.getFromWikidataQuery(new LatLng(
decLatitude, decLongitude, 0.0f),
Locale.getDefault().getLanguage(),
NEARBY_RADIUS_IN_KILO_METERS);
return fromWikidataQuery.size() > 0 ? fromWikidataQuery.get(0) : null;
NEARBY_RADIUS_IN_KILO_METERS, false);
return (fromWikidataQuery != null && fromWikidataQuery.size() > 0) ? fromWikidataQuery
.get(0) : null;
}catch (final Exception e) {
Timber.e("Error fetching nearby places: %s", e.getMessage());
return null;
@ -299,4 +300,8 @@ public class UploadRepository {
public void useSimilarPictureCoordinates(ImageCoordinates imageCoordinates, int uploadItemIndex) {
uploadModel.useSimilarPictureCoordinates(imageCoordinates, uploadItemIndex);
}
public boolean isWMLSupportedForThisPlace() {
return uploadModel.getItems().get(0).isWLMUpload();
}
}

View file

@ -7,7 +7,7 @@ public class Prefs {
public static final String DEFAULT_LICENSE = "defaultLicense";
public static final String UPLOADS_SHOWING = "uploadsshowing";
public static final String MANAGED_EXIF_TAGS = "managed_exif_tags";
public static final String DESCRIPTION_LANGUAGE = "descriptionLanguage";
public static final String DESCRIPTION_LANGUAGE = "languageDescription";
public static final String APP_UI_LANGUAGE = "appUiLanguage";
public static final String KEY_THEME_VALUE = "appThemePref";
public static final String TELEMETRY_PREFERENCE = "telemetryPref";

View file

@ -229,7 +229,10 @@ public class SettingsFragment extends PreferenceFragmentCompat {
/**
* Changing the default app language with selected one and save it to SharedPreferences
*/
public void setLocale(final Activity activity, final String userSelectedValue) {
public void setLocale(final Activity activity, String userSelectedValue) {
if (userSelectedValue.equals("")) {
userSelectedValue = Locale.getDefault().getLanguage();
}
final Locale locale = new Locale(userSelectedValue);
Locale.setDefault(locale);
final Configuration configuration = new Configuration();

View file

@ -199,12 +199,15 @@ class FileProcessor @Inject constructor(
private fun suggestNearbyDepictions(imageCoordinates: ImageCoordinates): Disposable {
return Observable.fromIterable(radiiProgressionInMetres.map { it / 1000.0 })
.concatMap {
Observable.fromCallable {
okHttpJsonApiClient.getNearbyPlaces(
imageCoordinates.latLng,
Locale.getDefault().language,
it
it,
false
)
}
}
.subscribeOn(Schedulers.io())
.filter { it.size >= MIN_NEARBY_RESULTS }
.take(1)

View file

@ -30,7 +30,7 @@ class PageContentsCreator {
this.context = context;
}
public String createFrom(final Contribution contribution, final String countryCode) {
public String createFrom(final Contribution contribution) {
StringBuilder buffer = new StringBuilder();
final Media media = contribution.getMedia();
buffer
@ -55,18 +55,18 @@ class PageContentsCreator {
buffer.append("}}").append("\n");
if (contribution.getWikidataPlace()!=null && contribution.getWikidataPlace().isMonumentUpload()) {
buffer.append("{{Wiki Loves Monuments 2021|1= ")
.append(countryCode)
.append("}}").append("\n");
}
//Only add Location template (e.g. {{Location|37.51136|-77.602615}} ) if coords is not null
final String decimalCoords = contribution.getDecimalCoords();
if (decimalCoords != null) {
buffer.append("{{Location|").append(decimalCoords).append("}}").append("\n");
}
if (contribution.getWikidataPlace()!=null && contribution.getWikidataPlace().isMonumentUpload()) {
buffer.append("{{Wiki Loves Monuments 2021|1= ")
.append(contribution.getCountryCode())
.append("}}").append("\n");
}
buffer.append("== {{int:license-header}} ==\n")
.append(licenseTemplateFor(media.getLicense())).append("\n\n")
.append("{{Uploaded from Mobile|platform=Android|version=")

View file

@ -210,21 +210,21 @@ public class UploadClient {
public Observable<UploadResult> uploadFileFromStash(
final Contribution contribution,
final String uniqueFileName,
final String fileKey, @Nullable String countryCode) {
final String fileKey) {
try {
return uploadInterface
.uploadFileFromStash(csrfTokenClient.getTokenBlocking(),
pageContentsCreator.createFrom(contribution, countryCode),
pageContentsCreator.createFrom(contribution),
CommonsApplication.DEFAULT_EDIT_SUMMARY,
uniqueFileName,
fileKey).map(uploadResponse -> {
UploadResponse uploadResult = gson
final UploadResponse uploadResult = gson
.fromJson(uploadResponse, UploadResponse.class);
if (uploadResult.getUpload() == null) {
final MwException exception = gson
.fromJson(uploadResponse, MwException.class);
Timber.e(exception, "Error in uploading file from stash");
throw new RuntimeException(exception.getErrorCode());
throw new Exception(exception.getErrorCode());
}
return uploadResult.getUpload();
});

View file

@ -23,6 +23,8 @@ public class UploadItem {
private final String createdTimestampSource;
private final BehaviorSubject<Integer> imageQuality;
private boolean hasInvalidLocation;
private boolean isWLMUpload = false;
private String countryCode;
/**
* Uri of uploadItem
@ -102,6 +104,14 @@ public class UploadItem {
this.uploadMediaDetails = uploadMediaDetails;
}
public void setWLMUpload(final boolean WLMUpload) {
isWLMUpload = WLMUpload;
}
public boolean isWLMUpload() {
return isWLMUpload;
}
@Override
public boolean equals(@Nullable final Object obj) {
if (!(obj instanceof UploadItem)) {
@ -136,4 +146,13 @@ public class UploadItem {
public boolean hasInvalidLocation() {
return hasInvalidLocation;
}
public void setCountryCode(final String countryCode) {
this.countryCode = countryCode;
}
@Nullable
public String getCountryCode() {
return countryCode;
}
}

View file

@ -155,6 +155,15 @@ public class UploadModel {
contribution.setDateCreatedSource(item.getCreatedTimestampSource());
//Set the date only if you have it, else the upload service is gonna try it the other way
}
if (contribution.getWikidataPlace() != null) {
if (item.isWLMUpload()) {
contribution.getWikidataPlace().setMonumentUpload(true);
} else {
contribution.getWikidataPlace().setMonumentUpload(false);
}
}
contribution.setCountryCode(item.getCountryCode());
return contribution;
});
}

View file

@ -1,3 +1,3 @@
package fr.free.nrw.commons.upload
class UploadResponse(val upload: UploadResult)
class UploadResponse(val upload: UploadResult?)

View file

@ -12,7 +12,7 @@ data class WikidataPlace(
val imageValue: String?,
val wikipediaArticle: String?,
val location: LatLng? = null,
val isMonumentUpload : Boolean =false
var isMonumentUpload : Boolean =false
) :
WikidataItem, Parcelable {
constructor(place: Place) : this(

View file

@ -21,6 +21,8 @@ public interface MediaLicenseContract {
void getLicenses();
void selectLicense(String licenseName);
boolean isWLMSupportedForThisPlace();
}
}

View file

@ -90,16 +90,11 @@ public class MediaLicenseFragment extends UploadBaseFragment implements MediaLic
initPresenter();
initLicenseSpinner();
presenter.getLicenses();
/**
* Show the wlm info message if the upload is a WLM upload
*/
if(callback.isWLMUpload()){
//TODO : Update the info message logo
llInfoMonumentUpload.setVisibility(View.VISIBLE);
}else{
llInfoMonumentUpload.setVisibility(View.GONE);
}
@Override
public void onResume() {
super.onResume();
}
/**
@ -229,4 +224,16 @@ public class MediaLicenseFragment extends UploadBaseFragment implements MediaLic
callback.onNextButtonClicked(callback.getIndexInViewFlipper(this));
}
@Override
protected void onBecameVisible() {
super.onBecameVisible();
/**
* Show the wlm info message if the upload is a WLM upload
*/
if(callback.isWLMUpload() && presenter.isWLMSupportedForThisPlace()){
llInfoMonumentUpload.setVisibility(View.VISIBLE);
}else{
llInfoMonumentUpload.setVisibility(View.GONE);
}
}
}

View file

@ -74,4 +74,9 @@ public class MediaLicensePresenter implements MediaLicenseContract.UserActionLis
repository.setSelectedLicense(licenseName);
view.updateLicenseSummary(repository.getSelectedLicense(), repository.getCount());
}
@Override
public boolean isWLMSupportedForThisPlace() {
return repository.isWMLSupportedForThisPlace();
}
}

View file

@ -7,9 +7,14 @@ import static fr.free.nrw.commons.utils.ImageUtils.FILE_NAME_EXISTS;
import static fr.free.nrw.commons.utils.ImageUtils.IMAGE_KEEP;
import static fr.free.nrw.commons.utils.ImageUtils.IMAGE_OK;
import android.location.Address;
import android.location.Geocoder;
import androidx.annotation.Nullable;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.filepicker.UploadableFile;
import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.nearby.Place;
import fr.free.nrw.commons.repository.UploadRepository;
import fr.free.nrw.commons.upload.ImageCoordinates;
@ -22,10 +27,13 @@ import io.reactivex.Maybe;
import io.reactivex.Scheduler;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import java.io.IOException;
import java.lang.reflect.Proxy;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import javax.inject.Inject;
import javax.inject.Named;
import org.jetbrains.annotations.NotNull;
@ -48,6 +56,8 @@ public class UploadMediaPresenter implements UserActionListener, SimilarImageInt
private Scheduler ioScheduler;
private Scheduler mainThreadScheduler;
private final List<String> WLM_SUPPORTED_COUNTRIES= Arrays.asList("am","at","az","br","hr","sv","fi","fr","de","gh","in","ie","il","mk","my","mt","pk","pe","pl","ru","rw","si","es","se","tw","ug","ua","us");
@Inject
public UploadMediaPresenter(UploadRepository uploadRepository,
@Named("default_preferences") JsonKvStore defaultKVStore,
@ -77,18 +87,31 @@ public class UploadMediaPresenter implements UserActionListener, SimilarImageInt
* @param place
*/
@Override
public void receiveImage(UploadableFile uploadableFile, Place place) {
public void receiveImage(final UploadableFile uploadableFile, final Place place) {
view.showProgress(true);
compositeDisposable.add(
repository
.preProcessImage(uploadableFile, place, this)
.map(uploadItem -> {
if(place!=null && place.isMonument()){
if (place.location != null) {
final String countryCode = reverseGeoCode(place.location);
if (countryCode != null && WLM_SUPPORTED_COUNTRIES
.contains(countryCode.toLowerCase())) {
uploadItem.setWLMUpload(true);
uploadItem.setCountryCode(countryCode.toLowerCase());
}
}
}
return uploadItem;
})
.subscribeOn(ioScheduler)
.observeOn(mainThreadScheduler)
.subscribe(uploadItem ->
{
view.onImageProcessed(uploadItem, place);
view.updateMediaDetails(uploadItem.getUploadMediaDetails());
ImageCoordinates gpsCoords = uploadItem.getGpsCoords();
final ImageCoordinates gpsCoords = uploadItem.getGpsCoords();
final boolean hasImageCoordinates =
gpsCoords != null && gpsCoords.getImageCoordsExists();
view.showMapWithImageCoordinates(hasImageCoordinates);
@ -100,12 +123,32 @@ public class UploadMediaPresenter implements UserActionListener, SimilarImageInt
throwable -> Timber.e(throwable, "Error occurred in processing images")));
}
@Nullable
private String reverseGeoCode(final LatLng latLng){
final Geocoder geocoder = new Geocoder(
CommonsApplication.getInstance().getApplicationContext(), Locale
.getDefault());
try {
final List<Address> addresses = geocoder
.getFromLocation(latLng.getLatitude(), latLng.getLongitude(), 1);
for (final Address address : addresses) {
if (address != null && address.getCountryCode() != null) {
String countryCode = address.getCountryCode();
return countryCode;
}
}
} catch (final IOException e) {
Timber.e(e);
}
return null;
}
/**
* This method checks for the nearest location that needs images and suggests it to the user.
* @param uploadItem
*/
private void checkNearbyPlaces(UploadItem uploadItem) {
Disposable checkNearbyPlaces = Maybe.fromCallable(() -> repository
private void checkNearbyPlaces(final UploadItem uploadItem) {
final Disposable checkNearbyPlaces = Maybe.fromCallable(() -> repository
.checkNearbyPlaces(uploadItem.getGpsCoords().getDecLatitude(),
uploadItem.getGpsCoords().getDecLongitude()))
.subscribeOn(ioScheduler)

View file

@ -23,6 +23,7 @@ import fr.free.nrw.commons.customselector.database.UploadedStatusDao
import fr.free.nrw.commons.di.ApplicationlessInjection
import fr.free.nrw.commons.location.LatLng
import fr.free.nrw.commons.media.MediaClient
import fr.free.nrw.commons.upload.StashUploadResult
import fr.free.nrw.commons.upload.FileUtilsWrapper
import fr.free.nrw.commons.upload.StashUploadState
import fr.free.nrw.commons.upload.UploadClient
@ -276,7 +277,9 @@ class UploadWorker(var appContext: Context, workerParams: WorkerParameters) :
//Upload the file to stash
val stashUploadResult = uploadClient.uploadFileToStash(
appContext, filename, contribution, notificationProgressUpdater
).blockingSingle()
).onErrorReturn{
return@onErrorReturn StashUploadResult(StashUploadState.FAILED,fileKey = null)
}.blockingSingle()
when (stashUploadResult.state) {
StashUploadState.SUCCESS -> {
@ -285,22 +288,15 @@ class UploadWorker(var appContext: Context, workerParams: WorkerParameters) :
Timber.d("Ensure uniqueness of filename");
val uniqueFileName = findUniqueFileName(filename!!)
try {
//Upload the file from stash
var countryCode: String? =null
with(contribution.wikidataPlace?.location){
if (contribution.wikidataPlace?.isMonumentUpload == true) {
countryCode =
reverseGeoCode(contribution.wikidataPlace?.location!!)?.toLowerCase()
}
}
val uploadResult = uploadClient.uploadFileFromStash(
contribution, uniqueFileName, stashUploadResult.fileKey, countryCode
).blockingSingle()
contribution, uniqueFileName, stashUploadResult.fileKey
).onErrorReturn {
return@onErrorReturn null
}.blockingSingle()
if (uploadResult.isSuccessful()) {
if (null != uploadResult && uploadResult.isSuccessful()) {
Timber.d(
"Stash Upload success..proceeding to make wikidata edit"
)
@ -312,13 +308,13 @@ class UploadWorker(var appContext: Context, workerParams: WorkerParameters) :
"WikiDataEdit not required, upload success"
)
saveCompletedContribution(contribution,uploadResult)
showSuccessNotification(contribution)
}else{
Timber.d(
"WikiDataEdit not required, making wikidata edit"
)
makeWikiDataEdit(uploadResult, contribution)
}
showSuccessNotification(contribution)
} else {
Timber.e("Stash Upload failed")
@ -333,6 +329,7 @@ class UploadWorker(var appContext: Context, workerParams: WorkerParameters) :
Timber.e("Upload from stash failed for contribution : $filename")
showFailedNotification(contribution)
contribution.state=Contribution.STATE_FAILED
contributionDao.saveSynchronous(contribution)
if (STASH_ERROR_CODES.contains(exception.message)) {
clearChunks(contribution)
}
@ -360,26 +357,6 @@ class UploadWorker(var appContext: Context, workerParams: WorkerParameters) :
}
}
private fun reverseGeoCode(latLng: LatLng): String? {
val geocoder = Geocoder(
CommonsApplication.getInstance().applicationContext, Locale
.getDefault()
)
try {
val addresses =
geocoder.getFromLocation(latLng.latitude, latLng.longitude, 1)
for (address in addresses) {
if (address != null && address.locale.isO3Country != null) {
return address.locale.country
}
}
} catch (e: IOException) {
Timber.e(e)
}
return null
}
private fun clearChunks(contribution: Contribution) {
contribution.chunkInfo=null
contributionDao.saveSynchronous(contribution)

View file

@ -650,7 +650,7 @@ Upload your first media by tapping on the add button.</string>
<string name="welcome_custom_selector_ok">Awesome</string>
<string name="custom_selector_already_uploaded_image_text">This image has already been uploaded to Commons.</string>
<string name="place_state_wlm">WLM</string>
<string name="wlm_upload_info">You are contributing to Wiki Loves Monuments Campaign. Related templates will be added accordingly.</string>
<string name="wlm_upload_info">This image will be entered into the Wiki Loves Monuments 2021 contest</string>
<string name="display_monuments">Display monuments</string>
<string name="wlm_month_message">It\'s Wiki Loves Monuments month!</string>
<string name="learn_more">LEARN MORE</string>

View file

@ -1,60 +0,0 @@
SELECT
(SAMPLE(?location) as ?location)
?item
(SAMPLE(COALESCE(?itemLabelPreferredLanguage, ?itemLabelAnyLanguage)) as ?label)
(SAMPLE(COALESCE(?itemDescriptionPreferredLanguage, ?itemDescriptionAnyLanguage, "?")) as ?description)
(SAMPLE(?classId) as ?class)
(SAMPLE(COALESCE(?classLabelPreferredLanguage, ?classLabelAnyLanguage, "?")) as ?classLabel)
(SAMPLE(COALESCE(?icon0, ?icon1)) as ?icon)
?wikipediaArticle
?commonsArticle
(SAMPLE(?commonsCategory) as ?commonsCategory)
(SAMPLE(?pic) as ?pic)
WHERE {
# Around given location...
SERVICE wikibase:around {
?item wdt:P625 ?location.
bd:serviceParam wikibase:center "Point(${LONG} ${LAT})"^^geo:wktLiteral.
bd:serviceParam wikibase:radius "${RAD}" . # Radius in kilometers.
}
{ ?item p:P1435 ?monument } UNION { ?item p:P2186 ?monument } UNION { ?item p:P1459 ?monument } UNION { ?item p:P1460 ?monument } UNION { ?item p:P1216 ?monument } UNION { ?item p:P709 ?monument } UNION { ?item p:P718 ?monument } UNION { ?item p:P5694 ?monument }
# Get Commons category (P373)
OPTIONAL { ?item wdt:P373 ?commonsCategory. }
# Get (P18)
OPTIONAL { ?item wdt:P18 ?pic. }
# Get the label in the preferred language of the user, or any other language if no label is available in that language.
OPTIONAL {?item rdfs:label ?itemLabelPreferredLanguage. FILTER (lang(?itemLabelPreferredLanguage) = "${LANG}")}
OPTIONAL {?item rdfs:label ?itemLabelAnyLanguage}
# Get the description in the preferred language of the user, or any other language if no description is available in that language.
OPTIONAL {?item schema:description ?itemDescriptionPreferredLanguage. FILTER (lang(?itemDescriptionPreferredLanguage) = "${LANG}")}
OPTIONAL {?item schema:description ?itemDescriptionAnyLanguage }
# Get the class label in the preferred language of the user, or any other language if no label is available in that language.
OPTIONAL {
?item p:P31/ps:P31 ?classId.
OPTIONAL {?classId rdfs:label ?classLabelPreferredLanguage. FILTER (lang(?classLabelPreferredLanguage) = "")}
OPTIONAL {?classId rdfs:label ?classLabelAnyLanguage}
OPTIONAL {
?wikipediaArticle schema:about ?item ;
schema:isPartOf <https://.wikipedia.org/> .
}
OPTIONAL {
?wikipediaArticle schema:about ?item ;
schema:isPartOf <https://en.wikipedia.org/> .
SERVICE wikibase:label { bd:serviceParam wikibase:language "en" }
}
OPTIONAL {
?commonsArticle schema:about ?item ;
schema:isPartOf <https://commons.wikimedia.org/> .
SERVICE wikibase:label { bd:serviceParam wikibase:language "en" }
}
}
}
GROUP BY ?item ?wikipediaArticle ?commonsArticle

View file

@ -1,66 +1,56 @@
SELECT
(SAMPLE(?location) as ?location)
?item
(SAMPLE(COALESCE(?itemLabelPreferredLanguage, ?itemLabelAnyLanguage)) as ?label)
(SAMPLE(COALESCE(?itemDescriptionPreferredLanguage, ?itemDescriptionAnyLanguage, "?")) as ?description)
(SAMPLE(?classId) as ?class)
(SAMPLE(COALESCE(?classLabelPreferredLanguage, ?classLabelAnyLanguage, "?")) as ?classLabel)
(SAMPLE(COALESCE(?icon0, ?icon1)) as ?icon)
?wikipediaArticle
?commonsArticle
(SAMPLE(?commonsCategory) as ?commonsCategory)
(SAMPLE(?pic) as ?pic)
(SAMPLE(?destroyed) as ?destroyed)
(SAMPLE(?endTime) as ?endTime)
(SAMPLE(?location) as ?location)
(SAMPLE(?label) AS ?label)
(SAMPLE(?description) AS ?description)
(SAMPLE(?class) AS ?class)
(SAMPLE(?classLabel) AS ?classLabel)
(SAMPLE(?pic) AS ?pic)
(SAMPLE(?destroyed) AS ?destroyed)
(SAMPLE(?endTime) AS ?endTime)
(SAMPLE(?wikipediaArticle) AS ?wikipediaArticle)
(SAMPLE(?commonsArticle) AS ?commonsArticle)
(SAMPLE(?commonsCategory) AS ?commonsCategory)
WHERE {
# Around given location...
# Around given location
SERVICE wikibase:around {
?item wdt:P625 ?location.
bd:serviceParam wikibase:center "Point(${LONG} ${LAT})"^^geo:wktLiteral.
bd:serviceParam wikibase:center "Point(${LONG} ${LAT})"^^geo:wktLiteral. # Longitude latitude
bd:serviceParam wikibase:radius "${RAD}". # Radius in kilometers.
}
# Get the label in the preferred language of the user, or any other language if no label is available in that language.
OPTIONAL {?item rdfs:label ?itemLabelPreferredLanguage. FILTER (lang(?itemLabelPreferredLanguage) = "${LANG}")}
OPTIONAL {?item rdfs:label ?itemLabelAnyLanguage}
# Get the description in the preferred language of the user, or any other language if no description is available in that language.
OPTIONAL {?item schema:description ?itemDescriptionPreferredLanguage. FILTER (lang(?itemDescriptionPreferredLanguage) = "${LANG}")}
OPTIONAL {?item schema:description ?itemDescriptionAnyLanguage }
# Get Commons category (P373)
OPTIONAL { ?item wdt:P373 ?commonsCategory. }
# Get (P18)
OPTIONAL { ?item wdt:P18 ?pic. }
# Get (P576)
OPTIONAL { ?item wdt:P576 ?destroyed. }
# Get (P582)
OPTIONAL { ?item wdt:P582 ?endTime. }
# Get the class label in the preferred language of the user, or any other language if no label is available in that language.
OPTIONAL {
?item p:P31/ps:P31 ?classId.
OPTIONAL {?classId rdfs:label ?classLabelPreferredLanguage. FILTER (lang(?classLabelPreferredLanguage) = "${LANG}")}
OPTIONAL {?classId rdfs:label ?classLabelAnyLanguage}
OPTIONAL {
?wikipediaArticle schema:about ?item ;
schema:isPartOf <https://${LANG}.wikipedia.org/> .
}
OPTIONAL {
?wikipediaArticle schema:about ?item ;
schema:isPartOf <https://en.wikipedia.org/> .
SERVICE wikibase:label { bd:serviceParam wikibase:language "en" }
?item p:P31/ps:P31 ?class.
}
# Get picture
OPTIONAL {?item wdt:P18 ?pic}
# Get existence
OPTIONAL {?item wdt:P576 ?destroyed}
OPTIONAL {?item wdt:P582 ?endTime}
# Get Commons category
OPTIONAL {?item wdt:P373 ?commonsCategory}
# Get Wikipedia article
OPTIONAL {
?commonsArticle schema:about ?item ;
schema:isPartOf <https://commons.wikimedia.org/> .
SERVICE wikibase:label { bd:serviceParam wikibase:language "en" }
?wikipediaArticle schema:about ?item.
?wikipediaArticle schema:isPartOf <https://${LANG}.wikipedia.org/>.
}
# Get Commons article
OPTIONAL {
?commonsArticle schema:about ?item.
?commonsArticle schema:isPartOf <https://commons.wikimedia.org/>.
}
# Labels and descriptions
SERVICE wikibase:label {
bd:serviceParam wikibase:language "${LANG},en,fr,de,es,ja,ru,it,zh,pt,ar,fa,pl,nl,id,uk,he,sv,cs,ko,vi,ca,no,fi,hu,tr,th,hi,bn,ceb,ro,sw,kk,da,eo,sr,lt,sk,bg,sl,eu,et,hr,ms,el,arz,ur,ta,te,nn,gl,az,af,bs,be,ml,ka,is,sq,uz,la,br,mk,lv,azb,mr,sh,tl,cy,ckb,ast,be-tarask,zh-yue,hy,pa,as,my,kn,ne,si,tt,ha,war,zh-min-nan,vo,min,lmo,ht,lb,gu,tg,sco,ku,new,bpy,nds,io,pms,su,oc,jv,nap,ba,scn,wa,bar,an,ksh,szl,fy,frr,als,ia,ga,yi,mg,gd,vec,ce,sa,mai,xmf,sd,wuu,mrj,mhr,km,roa-tara,am,roa-rup,map-bms,bh,mnw,shn,bcl,co,cv,dv,nds-nl,fo,hif,fur,gan,glk,hak,ilo,pam,csb,avk,lij,li,gv,mi,mt,nah,nrm,se,nov,qu,os,pi,pag,ps,pdc,rm,bat-smg,sc,to,tk,hsb,fiu-vro,vls,yo,diq,zh-classical,frp,lad,kw,mn,haw,ang,ln,ie,wo,tpi,ty,crh,nv,jbo,ay,pcd,zea,eml,ky,ig,or,cbk-zam,kg,arc,rmy,ab,gn,so,kab,ug,stq,udm,ext,mzn,pap,cu,sah,tet,sn,lo,pnb,iu,na,got,bo,dsb,chr,cdo,om,sm,ee,ti,av,bm,zu,pnt,cr,pih,ss,ve,bi,rw,ch,xh,kl,ik,bug,dz,ts,tn,kv,tum,xal,st,tw,bxr,ak,ny,fj,lbe,za,ks,ff,lg,sg,rn,chy,mwl,lez,bjn,gom,tyv,vep,nso,kbd,ltg,rue,pfl,gag,koi,krc,ace,olo,kaa,mdf,myv,srn,ady,jam,tcy,dty,atj,kbp,din,lfn,gor,inh,sat,hyw,nqo,ban,szy,awa,ary,lld,smn,skr,mad,dag,shi,nia,ki,gcr".
?item rdfs:label ?label.
?item schema:description ?description.
?class rdfs:label ?classLabel.
}
}
}
GROUP BY ?item ?wikipediaArticle ?commonsArticle
GROUP BY ?item

View file

@ -0,0 +1,68 @@
SELECT
?item
(SAMPLE(?location) as ?location)
(SAMPLE(?label) AS ?label)
(SAMPLE(?description) AS ?description)
(SAMPLE(?class) AS ?class)
(SAMPLE(?classLabel) AS ?classLabel)
(SAMPLE(?pic) AS ?pic)
(SAMPLE(?destroyed) AS ?destroyed)
(SAMPLE(?endTime) AS ?endTime)
(SAMPLE(?wikipediaArticle) AS ?wikipediaArticle)
(SAMPLE(?commonsArticle) AS ?commonsArticle)
(SAMPLE(?commonsCategory) AS ?commonsCategory)
(SAMPLE(?monument) AS ?monument)
WHERE {
# Around given location
SERVICE wikibase:around {
?item wdt:P625 ?location.
bd:serviceParam wikibase:center "Point(${LONG} ${LAT})"^^geo:wktLiteral. # Longitude latitude
bd:serviceParam wikibase:radius "${RAD}". # Radius in kilometers.
}
OPTIONAL {
?item p:P31/ps:P31 ?class.
}
# Get picture
OPTIONAL {?item wdt:P18 ?pic}
# Get existence
OPTIONAL {?item wdt:P576 ?destroyed}
OPTIONAL {?item wdt:P582 ?endTime}
# Get Commons category
OPTIONAL {?item wdt:P373 ?commonsCategory}
# Get Wikipedia article
OPTIONAL {
?wikipediaArticle schema:about ?item.
?wikipediaArticle schema:isPartOf <https://${LANG}.wikipedia.org/>.
}
# Get Commons article
OPTIONAL {
?commonsArticle schema:about ?item.
?commonsArticle schema:isPartOf <https://commons.wikimedia.org/>.
}
# Wiki Loves Monuments
OPTIONAL {?item p:P1435 ?monument}
OPTIONAL {?item p:P2186 ?monument}
OPTIONAL {?item p:P1459 ?monument}
OPTIONAL {?item p:P1460 ?monument}
OPTIONAL {?item p:P1216 ?monument}
OPTIONAL {?item p:P709 ?monument}
OPTIONAL {?item p:P718 ?monument}
OPTIONAL {?item p:P5694 ?monument}
OPTIONAL {?item p:P3426 ?monument}
# Labels and descriptions
SERVICE wikibase:label {
bd:serviceParam wikibase:language "${LANG},en,fr,de,es,ja,ru,it,zh,pt,ar,fa,pl,nl,id,uk,he,sv,cs,ko,vi,ca,no,fi,hu,tr,th,hi,bn,ceb,ro,sw,kk,da,eo,sr,lt,sk,bg,sl,eu,et,hr,ms,el,arz,ur,ta,te,nn,gl,az,af,bs,be,ml,ka,is,sq,uz,la,br,mk,lv,azb,mr,sh,tl,cy,ckb,ast,be-tarask,zh-yue,hy,pa,as,my,kn,ne,si,tt,ha,war,zh-min-nan,vo,min,lmo,ht,lb,gu,tg,sco,ku,new,bpy,nds,io,pms,su,oc,jv,nap,ba,scn,wa,bar,an,ksh,szl,fy,frr,als,ia,ga,yi,mg,gd,vec,ce,sa,mai,xmf,sd,wuu,mrj,mhr,km,roa-tara,am,roa-rup,map-bms,bh,mnw,shn,bcl,co,cv,dv,nds-nl,fo,hif,fur,gan,glk,hak,ilo,pam,csb,avk,lij,li,gv,mi,mt,nah,nrm,se,nov,qu,os,pi,pag,ps,pdc,rm,bat-smg,sc,to,tk,hsb,fiu-vro,vls,yo,diq,zh-classical,frp,lad,kw,mn,haw,ang,ln,ie,wo,tpi,ty,crh,nv,jbo,ay,pcd,zea,eml,ky,ig,or,cbk-zam,kg,arc,rmy,ab,gn,so,kab,ug,stq,udm,ext,mzn,pap,cu,sah,tet,sn,lo,pnb,iu,na,got,bo,dsb,chr,cdo,om,sm,ee,ti,av,bm,zu,pnt,cr,pih,ss,ve,bi,rw,ch,xh,kl,ik,bug,dz,ts,tn,kv,tum,xal,st,tw,bxr,ak,ny,fj,lbe,za,ks,ff,lg,sg,rn,chy,mwl,lez,bjn,gom,tyv,vep,nso,kbd,ltg,rue,pfl,gag,koi,krc,ace,olo,kaa,mdf,myv,srn,ady,jam,tcy,dty,atj,kbp,din,lfn,gor,inh,sat,hyw,nqo,ban,szy,awa,ary,lld,smn,skr,mad,dag,shi,nia,ki,gcr".
?item rdfs:label ?label.
?item schema:description ?description.
?class rdfs:label ?classLabel.
}
}
GROUP BY ?item

View file

@ -1,5 +1,5 @@
buildscript {
ext.kotlin_version = '1.3.31'
ext.kotlin_version = '1.5.10'
repositories {
jcenter()
google()

View file

@ -31,11 +31,20 @@ public class MwException extends RuntimeException {
return error;
}
@Nullable public String getTitle() {
@Nullable
public String getTitle() {
if (error != null) {
return error.getTitle();
}
return errors != null ? errors.get(0).getTitle() : null;
}
@Override @Nullable public String getMessage() {
@Override
@Nullable
public String getMessage() {
if (error != null) {
return error.getDetails();
}
return errors != null ? errors.get(0).getDetails() : null;
}
}

View file

@ -16,11 +16,11 @@
org.gradle.jvmargs=-Xmx1536M
android.enableBuildCache=true
KOTLIN_VERSION=1.3.72
KOTLIN_VERSION=1.5.10
BUTTERKNIFE_VERSION=10.1.0
LEAK_CANARY_VERSION=1.6.2
DAGGER_VERSION=2.23
ROOM_VERSION=2.2.3
ROOM_VERSION=2.3.0
PREFERENCE_VERSION=1.1.0
CORE_KTX_VERSION=1.2.0
ADAPTER_DELEGATES_VERSION=4.3.0

View file

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.2.2-all.zip
distributionUrl=https://services.gradle.org/distributions/gradle-6.9-all.zip