Compare commits

...

14 commits
main ... v3.1.0

Author SHA1 Message Date
Josephine Lim
b41882639e
Update changelog.md 2021-09-02 19:08:42 +10:00
Josephine Lim
6ea6f3ebfd Versioning for v3.1.0 2021-09-02 19:07:58 +10:00
Josephine Lim
c973114ded Add countries supported by WLM2021 template, except Italy 2021-09-01 20:12:45 +10:00
Ashish
74e8472d91
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
2021-09-01 19:59:56 +10:00
Ashish
7deaf8dbae
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
2021-09-01 19:59:00 +10:00
Madhur Gupta
b47ed0546e
Fix bug #4585 by updating kotlin and acra version (#4592) 2021-08-31 19:40:39 +10:00
Madhur Gupta
51746934fc
Fix bug #4583 (#4591) 2021-08-31 19:34:02 +10:00
Josephine Lim
a98badff66 Modify string for WLM upload notice 2021-08-26 00:42:46 +10:00
Josephine Lim
12221df75e
Move WLM template below geolocation template (#4582) 2021-08-25 20:02:48 +05:30
Ashish
e666e768c2
Bug fix- current location marker (#4580) 2021-08-25 16:54:10 +10:00
Josephine Lim
e8556a8ecd Modify parameters for Nearby query 2021-08-24 21:31:18 +10:00
Ashish
678bd33410
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
2021-08-24 21:25:07 +10:00
Madhur Gupta
cba99ae5e3
Fix notification bug #4547 (#4570) 2021-08-24 02:38:28 +10:00
Ashish
bb3f8f3801
Cherry-Picked NPE fix from master (#4569) 2021-08-23 19:09:52 +10:00
31 changed files with 309 additions and 280 deletions

View file

@ -1,5 +1,8 @@
# Wikimedia Commons for Android
## 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'
@ -150,8 +150,8 @@ android {
defaultConfig {
//applicationId 'fr.free.nrw.commons'
versionCode 1021
versionName '3.0.2'
versionCode 1024
versionName '3.1.0'
setProperty("archivesBaseName", "app-commons-v$versionName-" + getBranchName())
minSdkVersion 19
@ -221,7 +221,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

@ -39,7 +39,8 @@ data class Contribution constructor(
var dataLength: Long = 0,
var dateCreated: Date? = null,
var dateModified: Date? = null,
var hasInvalidLocation : Int = 0
var hasInvalidLocation : Int = 0,
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

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

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,107 +265,54 @@ 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
final String wikidataQuery;
if (!shouldQueryForMonuments) {
wikidataQuery = FileUtils.readFromResource("/queries/nearby_query.rq");
} else {
wikidataQuery = FileUtils.readFromResource("/queries/nearby_query_monuments.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);
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();
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<>();
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<NearbyResultItem> bindings = nearbyResponse.getResults().getBindings();
final List<Place> 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);
}
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;
places.add(placeFromNearbyItem);
}
return new ArrayList<>();
});
return places;
}
throw new Exception(response.message());
}
/**
* 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";
}
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();
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()) {
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);
}
return places;
}
return new ArrayList<>();
});
} catch (final IOException e) {
e.printStackTrace();
return Observable.error(e);
}
}
/**
* Get the QIDs of all Wikidata items that are subclasses of the given Wikidata item. Example:
* bridge -> suspended bridge, aqueduct, etc

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

@ -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

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

View file

@ -26,24 +26,28 @@ class PageContentsCreator {
private final Context context;
@Inject
public PageContentsCreator(Context context) {
public PageContentsCreator(final Context context) {
this.context = context;
}
public String createFrom(Contribution contribution, String countryCode) {
public String createFrom(final Contribution contribution) {
StringBuilder buffer = new StringBuilder();
final Media media = contribution.getMedia();
buffer
.append("== {{int:filedesc}} ==\n")
.append("{{Information\n")
.append("|description=").append(media.getFallbackDescription())
.append("{{ on Wikidata|").append(contribution.getWikidataPlace().getId()).append("}}")
.append("|description=").append(media.getFallbackDescription()).append("\n");
if (contribution.getWikidataPlace() != null) {
buffer.append("{{ on Wikidata|").append(contribution.getWikidataPlace().getId())
.append("}}");
}
buffer
.append("\n")
.append("|source=").append("{{own}}\n")
.append("|author=[[User:").append(media.getAuthor()).append("|")
.append(media.getAuthor()).append("]]\n");
String templatizedCreatedDate = getTemplatizedCreatedDate(
final String templatizedCreatedDate = getTemplatizedCreatedDate(
contribution.getDateCreated(), contribution.getDateCreatedSource());
if (!StringUtils.isBlank(templatizedCreatedDate)) {
buffer.append("|date=").append(templatizedCreatedDate);
@ -51,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,11 +210,11 @@ 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 -> {

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;
@SuppressLint("CheckResult")
@ -87,6 +89,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)) {
@ -121,4 +131,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

@ -154,6 +154,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

@ -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

@ -274,19 +274,10 @@ 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!!) {
countryCode =
reverseGeoCode(contribution.wikidataPlace?.location!!)?.toLowerCase()
}
}
val uploadResult = uploadClient.uploadFileFromStash(
contribution, uniqueFileName, stashUploadResult.fileKey, countryCode
contribution, uniqueFileName, stashUploadResult.fileKey
).blockingSingle()
if (uploadResult.isSuccessful()) {
@ -301,13 +292,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")
@ -349,26 +340,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

@ -640,7 +640,7 @@ Upload your first media by tapping on the add button.</string>
<string name="image_location">Image Location</string>
<string name="check_whether_location_is_correct">Check whether location is correct</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

@ -0,0 +1,71 @@
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(?monument) as ?monument)
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.
}
OPTIONAL {
{ ?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 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" }
}
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,5 +1,5 @@
buildscript {
ext.kotlin_version = '1.3.31'
ext.kotlin_version = '1.5.10'
repositories {
jcenter()
google()

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