Make P18 edits to corresponding wikidata entity on uploading from Nearby (#1495)

* Localisation updates from https://translatewiki.net.

* Integrate API for displaying featured images (#1456)

* Integrate API for displaying featured images

* Add pagination and refactor code so that it can be reused for category images

* Add license info to the images

* Fix author view

* Remove unused values

* Fix minor issues with featured images

* Fix null license url issue

* Remove some log lines

* Fix back navigation issue

* fix tests

* fix test inits

* Gracefully handling various error situations

* Added java docs

* Update pull_request_template.md (#1476)

* Update pull_request_template.md

* Remove Javadocs mention

* Added required/optional notes

* resolves #1464 : MediaDataExtractor is making inefficient (redundant) server calls (#1496)

* Open map of place where picture was taken (#1360)

* Intent to map added

*  Merge conflicts resolved

*  Added the functionality to hide FAB incase of null coordinate

*  Merge Conflict resolved

*  Improve pr quality

* Improve Quality

*  Added nested FAB animations

* Nested FAB implemented

*  Improve Quality

*  Added up arrow

*  Javadocs Added

* Add nearby tutorial (#1467)

* Add dependency for MaterialShowcase

* Add actionview class to get a reference to material showcase

* Create a NearbyMaterialShowcaseSequence class

* Apply sequence steps

* Add first three steps of nearby showcase

* Add sequence id constants to make sure they will be displayed only once

* Add last step of sequence to explain plus fab

* Create an object to prevent customize all sequences every time

* Fix typo

* Code cleanup

* Add strings to strings.xml

* Code cleanup

* Revert irrelevant change

* Revert irrelevant change

* Remove showcaseview for recenter button

* Use single showcaseView instead of sequence

* Add single showcase view insted of sequence to be able to edit text style

* Make sure it will be displayed only once

* Cleanup

* Update strings

* Change dismiss text style

* CONTRIBUTING: fix formatting of the gist of the guidelines (#1453)

* CONTRIBUTING: fix formatting of the gist of the guidelines

First level headings for a gist seems to be overkill.

So, replace first level headings with an ordered-list which
sounds more meaningful.

* CONTRIBUTING: specify clearly that 'blame' is a feature of "Git"

The contributing file specifies about the ability to know who wrote
something without the need of @author javadoc tags but incorrectly
attributes the feature to GitHub.

Correctly attribute the feature to where it belongs, Git, and specify
the name of the feature to help users easily take advantage of it.

* Feature/switch to butterknife (#1494)

* Implemented butterknife in MediaDetailFragment [issue #1491]

* Implemented butterknife in MediaDetailPagerFragment [[issue #1491]]

* post merge upstream master wip [[issue #1491]]

* Localisation updates from https://translatewiki.net.

* Bug fix #1504 (#1506)

* Bug fix #1504

* Filtered messages with ConnectException [issue #1504]

* A generalised message for exceptions in Nearby Activity [issue #1504]

* Localisation updates from https://translatewiki.net.

* Fix security exception crash while accessing network location provider (#1498)

* Fix security exception crash while accessing network location provider

* Added java docs

* Localisation updates from https://translatewiki.net.

* Log P18 edits to wikidata corresponding wikidata entity on uploading a nearby image

* Added java docs

* Fix test build

* Refresh nearby

* Refresh nearby list on successful edit

* Java docs

* Make authenticated wikidata edits

* Updated toast message to show entity name that was edited
This commit is contained in:
Vivek Maskara 2018-05-31 06:30:17 +05:30 committed by Josephine Lim
parent 677f85a097
commit 4815e93fc9
21 changed files with 276 additions and 38 deletions

View file

@ -130,6 +130,7 @@ android {
productFlavors {
prod {
buildConfigField "String", "WIKIMEDIA_API_HOST", "\"https://commons.wikimedia.org/w/api.php\""
buildConfigField "String", "WIKIDATA_API_HOST", "\"https://www.wikidata.org/w/api.php\""
buildConfigField "String", "WIKIMEDIA_FORGE_API_HOST", "\"https://tools.wmflabs.org/\""
buildConfigField "String", "IMAGE_URL_BASE", "\"https://upload.wikimedia.org/wikipedia/commons\""
buildConfigField "String", "HOME_URL", "\"https://commons.wikimedia.org/wiki/\""
@ -146,6 +147,7 @@ android {
beta {
// What values do we need to hit the BETA versions of the site / api ?
buildConfigField "String", "WIKIMEDIA_API_HOST", "\"https://commons.wikimedia.beta.wmflabs.org/w/api.php\""
buildConfigField "String", "WIKIDATA_API_HOST", "\"https://www.wikidata.org/w/api.php\""
buildConfigField "String", "WIKIMEDIA_FORGE_API_HOST", "\"https://tools.wmflabs.org/\""
buildConfigField "String", "IMAGE_URL_BASE", "\"https://upload.beta.wmflabs.org/wikipedia/commons\""
buildConfigField "String", "HOME_URL", "\"https://commons.wikimedia.beta.wmflabs.org/wiki/\""

View file

@ -178,6 +178,7 @@ public class Utils {
}
public static void handleWebUrl(Context context, Uri url) {
Timber.d("Launching web url %s", url.toString());
Intent browserIntent = new Intent(Intent.ACTION_VIEW, url);
if (browserIntent.resolveActivity(context.getPackageManager()) == null) {
Toast toast = Toast.makeText(context, context.getString(R.string.no_web_browser), LENGTH_SHORT);

View file

@ -45,6 +45,7 @@ public class Contribution extends Media {
private long transferred;
private String decimalCoords;
private boolean isMultiple;
private String wikiDataEntityId;
public Contribution(Uri contentUri, String filename, Uri localUri, String imageUrl, Date timestamp,
int state, long dataLength, Date dateUploaded, long transferred,
@ -222,4 +223,17 @@ public class Contribution extends Media {
throw new RuntimeException("Unrecognized license value: " + license);
}
public String getWikiDataEntityId() {
return wikiDataEntityId;
}
/**
* When the corresponding wikidata entity is known as in case of nearby uploads, it can be set
* using the setter method
* @param wikiDataEntityId
*/
public void setWikiDataEntityId(String wikiDataEntityId) {
this.wikiDataEntityId = wikiDataEntityId;
}
}

View file

@ -90,7 +90,7 @@ public class ContributionController {
fragment.startActivityForResult(pickImageIntent, SELECT_FROM_GALLERY);
}
public void handleImagePicked(int requestCode, Intent data, boolean isDirectUpload) {
public void handleImagePicked(int requestCode, Intent data, boolean isDirectUpload, String wikiDataEntityId) {
FragmentActivity activity = fragment.getActivity();
Timber.d("handleImagePicked() called with onActivityResult()");
Intent shareIntent = new Intent(activity, ShareActivity.class);
@ -102,9 +102,6 @@ public class ContributionController {
shareIntent.setType(activity.getContentResolver().getType(imageData));
shareIntent.putExtra(EXTRA_STREAM, imageData);
shareIntent.putExtra(EXTRA_SOURCE, SOURCE_GALLERY);
if (isDirectUpload) {
shareIntent.putExtra("isDirectUpload", true);
}
break;
case SELECT_FROM_CAMERA:
//FIXME: Find out appropriate mime type
@ -113,9 +110,6 @@ public class ContributionController {
shareIntent.setType("image/jpeg");
shareIntent.putExtra(EXTRA_STREAM, lastGeneratedCaptureUri);
shareIntent.putExtra(EXTRA_SOURCE, SOURCE_CAMERA);
if (isDirectUpload) {
shareIntent.putExtra("isDirectUpload", true);
}
break;
default:
@ -123,6 +117,10 @@ public class ContributionController {
}
Timber.i("Image selected");
try {
shareIntent.putExtra("isDirectUpload", isDirectUpload);
if (wikiDataEntityId != null && !wikiDataEntityId.equals("")) {
shareIntent.putExtra("wikiDataEntityId", wikiDataEntityId);
}
activity.startActivity(shareIntent);
} catch (SecurityException e) {
Timber.e(e, "Security Exception");

View file

@ -117,7 +117,7 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment {
if (resultCode == RESULT_OK) {
Timber.d("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
requestCode, resultCode, data);
controller.handleImagePicked(requestCode, data, false);
controller.handleImagePicked(requestCode, data, false, null);
} else {
Timber.e("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
requestCode, resultCode, data);

View file

@ -24,6 +24,8 @@ import fr.free.nrw.commons.mwapi.ApacheHttpClientMediaWikiApi;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
import fr.free.nrw.commons.nearby.NearbyPlaces;
import fr.free.nrw.commons.upload.UploadController;
import fr.free.nrw.commons.wikidata.WikidataEditListener;
import fr.free.nrw.commons.wikidata.WikidataEditListenerImpl;
import static android.content.Context.MODE_PRIVATE;
import static fr.free.nrw.commons.contributions.ContributionsContentProvider.CONTRIBUTION_AUTHORITY;
@ -123,7 +125,7 @@ public class CommonsApplicationModule {
@Named("default_preferences") SharedPreferences defaultPreferences,
@Named("category_prefs") SharedPreferences categoryPrefs,
Gson gson) {
return new ApacheHttpClientMediaWikiApi(context, BuildConfig.WIKIMEDIA_API_HOST, defaultPreferences, categoryPrefs, gson);
return new ApacheHttpClientMediaWikiApi(context, BuildConfig.WIKIMEDIA_API_HOST, BuildConfig.WIKIDATA_API_HOST, defaultPreferences, categoryPrefs, gson);
}
@Provides
@ -165,4 +167,10 @@ public class CommonsApplicationModule {
public LruCache<String, String> provideLruCache() {
return new LruCache<>(1024);
}
@Provides
@Singleton
public WikidataEditListener provideWikidataEditListener() {
return new WikidataEditListenerImpl();
}
}

View file

@ -284,6 +284,7 @@ public class LocationServiceManager implements LocationListener {
LOCATION_SIGNIFICANTLY_CHANGED, //Went out of borders of nearby markers
LOCATION_SLIGHTLY_CHANGED, //User might be walking or driving
LOCATION_NOT_CHANGED,
PERMISSION_JUST_GRANTED
PERMISSION_JUST_GRANTED,
MAP_UPDATED
}
}

View file

@ -25,6 +25,8 @@ import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.util.EntityUtils;
import org.mediawiki.api.ApiResult;
import org.mediawiki.api.MWApi;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import java.io.IOException;
@ -62,6 +64,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
private static final String THUMB_SIZE = "640";
private AbstractHttpClient httpClient;
private MWApi api;
private MWApi wikidataApi;
private Context context;
private SharedPreferences defaultPreferences;
private SharedPreferences categoryPreferences;
@ -69,6 +72,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
public ApacheHttpClientMediaWikiApi(Context context,
String apiURL,
String wikidatApiURL,
SharedPreferences defaultPreferences,
SharedPreferences categoryPreferences,
Gson gson) {
@ -82,6 +86,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
params.setParameter(CoreProtocolPNames.USER_AGENT, getUserAgent());
httpClient = new DefaultHttpClient(cm, params);
api = new MWApi(apiURL, httpClient);
wikidataApi = new MWApi(wikidatApiURL, httpClient);
this.defaultPreferences = defaultPreferences;
this.categoryPreferences = categoryPreferences;
this.gson = gson;
@ -206,6 +211,15 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
return api.getEditToken();
}
@Override
public String getCentralAuthToken() throws IOException {
String centralAuthToken = api.action("centralauthtoken")
.get()
.getString("/api/centralauthtoken/@centralauthtoken");
Timber.d("MediaWiki Central auth token is %s", centralAuthToken);
return centralAuthToken;
}
@Override
public boolean fileExistsWithName(String fileName) throws IOException {
return api.action("query")
@ -351,6 +365,65 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
}).flatMapObservable(Observable::fromIterable);
}
/**
* Get the edit token for making wiki data edits
* https://www.mediawiki.org/wiki/API:Tokens
* @return
* @throws IOException
*/
private String getWikidataEditToken() throws IOException {
return wikidataApi.getEditToken();
}
@Override
public String getWikidataCsrfToken() throws IOException {
String wikidataCsrfToken = wikidataApi.action("query")
.param("action", "query")
.param("centralauthtoken", getCentralAuthToken())
.param("meta", "tokens")
.post()
.getString("/api/query/tokens/@csrftoken");
Timber.d("Wikidata csrf token is %s", wikidataCsrfToken);
return wikidataCsrfToken;
}
/**
* Creates a new claim using the wikidata API
* https://www.mediawiki.org/wiki/Wikibase/API
* @param entityId the wikidata entity to be edited
* @param property the property to be edited, for eg P18 for images
* @param snaktype the type of value stored for that property
* @param value the actual value to be stored for the property, for eg filename in case of P18
* @return returns true if the claim is successfully created
* @throws IOException
*/
@Nullable
@Override
public boolean wikidatCreateClaim(String entityId, String property, String snaktype, String value) throws IOException {
ApiResult result = wikidataApi.action("wbcreateclaim")
.param("entity", entityId)
.param("centralauthtoken", getCentralAuthToken())
.param("token", getWikidataCsrfToken())
.param("snaktype", snaktype)
.param("property", property)
.param("value", value)
.post();
if (result == null || result.getNode("api") == null) {
return false;
}
Node node = result.getNode("api").getDocument();
Element element = (Element) node;
if (element != null && element.getAttribute("success").equals("1")) {
return true;
} else {
Timber.e(result.getString("api/error/@code") + " " + result.getString("api/error/@info"));
}
return false;
}
@Override
@NonNull
public Observable<String> searchTitles(String title, int searchCatsLimit) {
@ -586,6 +659,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
String resultStatus = result.getString("/api/upload/@result");
if (!resultStatus.equals("Success")) {
String errorCode = result.getString("/api/error/@code");
Timber.e(errorCode);
return new UploadResult(resultStatus, errorCode);
} else {
Date dateUploaded = parseMWDate(result.getString("/api/upload/imageinfo/@timestamp"));

View file

@ -27,6 +27,10 @@ public interface MediaWikiApi {
String getEditToken() throws IOException;
String getWikidataCsrfToken() throws IOException;
String getCentralAuthToken() throws IOException;
boolean fileExistsWithName(String fileName) throws IOException;
boolean pageExists(String pageName) throws IOException;
@ -49,6 +53,9 @@ public interface MediaWikiApi {
@Nullable
String appendEdit(String editToken, String processedPageContent, String filename, String summary) throws IOException;
@Nullable
boolean wikidatCreateClaim(String entityId, String property, String snaktype, String value) throws IOException;
@NonNull
MediaResult fetchMediaByFilename(String filename) throws IOException;

View file

@ -42,11 +42,13 @@ import butterknife.ButterKnife;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.location.LocationServiceManager;
import fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType;
import fr.free.nrw.commons.location.LocationUpdateListener;
import fr.free.nrw.commons.theme.NavigationBaseActivity;
import fr.free.nrw.commons.utils.NetworkUtils;
import fr.free.nrw.commons.utils.UriSerializer;
import fr.free.nrw.commons.utils.ViewUtil;
import fr.free.nrw.commons.wikidata.WikidataEditListener;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
@ -55,8 +57,12 @@ import timber.log.Timber;
import uk.co.deanwild.materialshowcaseview.IShowcaseListener;
import uk.co.deanwild.materialshowcaseview.MaterialShowcaseView;
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.*;
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.MAP_UPDATED;
public class NearbyActivity extends NavigationBaseActivity implements LocationUpdateListener {
public class NearbyActivity extends NavigationBaseActivity implements LocationUpdateListener,
WikidataEditListener.WikidataP18EditListener {
private static final int LOCATION_REQUEST = 1;
@ -76,6 +82,8 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
LocationServiceManager locationManager;
@Inject
NearbyController nearbyController;
@Inject WikidataEditListener wikidataEditListener;
@Inject
@Named("application_preferences") SharedPreferences applicationPrefs;
private LatLng curLatLng;
@ -110,6 +118,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
initBottomSheetBehaviour();
initDrawer();
wikidataEditListener.setAuthenticationStateListener(this);
}
private void resumeFragment() {
@ -219,7 +228,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
//Still need to check if GPS is enabled
checkGps();
lastKnownLocation = locationManager.getLKL();
refreshView(LocationServiceManager.LocationChangeType.PERMISSION_JUST_GRANTED);
refreshView(PERMISSION_JUST_GRANTED);
} else {
//If permission not granted, go to page that says Nearby Places cannot be displayed
hideProgressBar();
@ -279,7 +288,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
private void checkLocationPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (locationManager.isLocationPermissionGranted()) {
refreshView(LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED);
refreshView(LOCATION_SIGNIFICANTLY_CHANGED);
} else {
// Should we show an explanation?
if (locationManager.isPermissionExplanationRequired(this)) {
@ -305,7 +314,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
}
}
} else {
refreshView(LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED);
refreshView(LOCATION_SIGNIFICANTLY_CHANGED);
}
}
@ -314,7 +323,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 1) {
Timber.d("User is back from Settings page");
refreshView(LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED);
refreshView(LOCATION_SIGNIFICANTLY_CHANGED);
}
}
@ -373,8 +382,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
@Override
public void onReceive(Context context, Intent intent) {
if (NetworkUtils.isInternetConnectionEstablished(NearbyActivity.this)) {
refreshView(LocationServiceManager
.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED);
refreshView(LOCATION_SIGNIFICANTLY_CHANGED);
} else {
ViewUtil.showLongToast(NearbyActivity.this, getString(R.string.no_internet));
}
@ -390,7 +398,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
*
* @param locationChangeType defines if location shanged significantly or slightly
*/
private void refreshView(LocationServiceManager.LocationChangeType locationChangeType) {
private void refreshView(LocationChangeType locationChangeType) {
if (lockNearbyView) {
return;
}
@ -403,12 +411,13 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
registerLocationUpdates();
LatLng lastLocation = locationManager.getLastLocation();
if (curLatLng != null && curLatLng.equals(lastLocation)) { //refresh view only if location has changed
if (curLatLng != null && curLatLng.equals(lastLocation)
&& !locationChangeType.equals(MAP_UPDATED)) { //refresh view only if location has changed
return;
}
curLatLng = lastLocation;
if (locationChangeType.equals(LocationServiceManager.LocationChangeType.PERMISSION_JUST_GRANTED)) {
if (locationChangeType.equals(PERMISSION_JUST_GRANTED)) {
curLatLng = lastKnownLocation;
}
@ -417,8 +426,9 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
return;
}
if (locationChangeType.equals(LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED)
|| locationChangeType.equals(LocationServiceManager.LocationChangeType.PERMISSION_JUST_GRANTED)) {
if (locationChangeType.equals(LOCATION_SIGNIFICANTLY_CHANGED)
|| locationChangeType.equals(PERMISSION_JUST_GRANTED)
|| locationChangeType.equals(MAP_UPDATED)) {
progressBar.setVisibility(View.VISIBLE);
//TODO: This hack inserts curLatLng before populatePlaces is called (see #1440). Ideally a proper fix should be found
@ -440,7 +450,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
progressBar.setVisibility(View.GONE);
});
} else if (locationChangeType
.equals(LocationServiceManager.LocationChangeType.LOCATION_SLIGHTLY_CHANGED)) {
.equals(LOCATION_SLIGHTLY_CHANGED)) {
Gson gson = new GsonBuilder()
.registerTypeAdapter(Uri.class, new UriSerializer())
.create();
@ -685,12 +695,12 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
@Override
public void onLocationChangedSignificantly(LatLng latLng) {
refreshView(LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED);
refreshView(LOCATION_SIGNIFICANTLY_CHANGED);
}
@Override
public void onLocationChangedSlightly(LatLng latLng) {
refreshView(LocationServiceManager.LocationChangeType.LOCATION_SLIGHTLY_CHANGED);
refreshView(LOCATION_SLIGHTLY_CHANGED);
}
public void prepareViewsForSheetPosition(int bottomSheetState) {
@ -700,4 +710,9 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
private void showErrorMessage(String message) {
ViewUtil.showLongToast(NearbyActivity.this, message);
}
@Override
public void onWikidataEditSuccessful() {
refreshView(MAP_UPDATED);
}
}

View file

@ -2,6 +2,7 @@ package fr.free.nrw.commons.nearby;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
@ -21,6 +22,9 @@ import java.lang.reflect.Type;
import java.util.Collections;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
import dagger.android.support.AndroidSupportInjection;
import dagger.android.support.DaggerFragment;
import fr.free.nrw.commons.R;
@ -47,6 +51,11 @@ public class NearbyListFragment extends DaggerFragment {
private RecyclerView recyclerView;
private ContributionController controller;
@Inject
@Named("direct_nearby_upload_prefs")
SharedPreferences directPrefs;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -137,7 +146,7 @@ public class NearbyListFragment extends DaggerFragment {
if (resultCode == RESULT_OK) {
Timber.d("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
requestCode, resultCode, data);
controller.handleImagePicked(requestCode, data, true);
controller.handleImagePicked(requestCode, data, true, directPrefs.getString("WikiDataEntityId", null));
} else {
Timber.e("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
requestCode, resultCode, data);

View file

@ -731,6 +731,7 @@ public class NearbyMapFragment extends DaggerFragment {
editor.putString("Title", place.getName());
editor.putString("Desc", place.getLongDescription());
editor.putString("Category", place.getCategory());
editor.putString("WikiDataEntityId", place.getWikiDataEntityId());
editor.apply();
}
@ -766,7 +767,7 @@ public class NearbyMapFragment extends DaggerFragment {
if (resultCode == RESULT_OK) {
Timber.d("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
requestCode, resultCode, data);
controller.handleImagePicked(requestCode, data, true);
controller.handleImagePicked(requestCode, data, true, directPrefs.getString("WikiDataEntityId", null));
} else {
Timber.e("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
requestCode, resultCode, data);

View file

@ -3,6 +3,7 @@ package fr.free.nrw.commons.nearby;
import android.graphics.Bitmap;
import android.net.Uri;
import android.support.annotation.DrawableRes;
import android.support.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
@ -50,6 +51,20 @@ public class Place {
this.distance = distance;
}
/**
* Extracts the entity id from the wikidata link
* @return returns the entity id if wikidata link exists
*/
@Nullable
public String getWikiDataEntityId() {
if (!hasWikidataLink()) {
return null;
}
String wikiDataLink = siteLinks.getWikidataLink().toString();
return wikiDataLink.replace("http://www.wikidata.org/entity/", "");
}
public boolean hasWikipediaLink() {
return !(siteLinks == null || Uri.EMPTY.equals(siteLinks.getWikipediaLink()));
}

View file

@ -16,7 +16,6 @@ import android.widget.RelativeLayout;
import com.pedrogomez.renderers.RVRendererAdapter;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.List;
@ -26,6 +25,7 @@ import butterknife.BindView;
import butterknife.ButterKnife;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
import fr.free.nrw.commons.theme.NavigationBaseActivity;
import fr.free.nrw.commons.utils.NetworkUtils;
import fr.free.nrw.commons.utils.ViewUtil;
@ -46,6 +46,8 @@ public class NotificationActivity extends NavigationBaseActivity {
@BindView(R.id.container) RelativeLayout relativeLayout;
@Inject NotificationController controller;
@Inject
MediaWikiApi mediaWikiApi;
private static final String TAG_NOTIFICATION_WORKER_FRAGMENT = "NotificationWorkerFragment";
private NotificationWorkerFragment mNotificationWorkerFragment;
@ -81,7 +83,6 @@ public class NotificationActivity extends NavigationBaseActivity {
}
}
@SuppressLint("CheckResult")
private void addNotifications() {
Timber.d("Add notifications");

View file

@ -8,7 +8,6 @@ import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
@ -69,14 +68,11 @@ import fr.free.nrw.commons.caching.CacheController;
import fr.free.nrw.commons.category.CategorizationFragment;
import fr.free.nrw.commons.category.OnCategoriesSaveHandler;
import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.contributions.ContributionsActivity;
import fr.free.nrw.commons.modifications.CategoryModifier;
import fr.free.nrw.commons.modifications.ModificationsContentProvider;
import fr.free.nrw.commons.modifications.ModifierSequence;
import fr.free.nrw.commons.modifications.ModifierSequenceDao;
import fr.free.nrw.commons.modifications.TemplateRemoveModifier;
import fr.free.nrw.commons.utils.ImageUtils;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
import fr.free.nrw.commons.utils.ViewUtil;
import timber.log.Timber;
@ -135,6 +131,7 @@ public class ShareActivity
private String title;
private String description;
private String wikiDataEntityId;
private Snackbar snackbar;
private boolean duplicateCheckPassed = false;
@ -194,7 +191,7 @@ public class ShareActivity
Timber.d("Cache the categories found");
}
uploadController.startUpload(title, mediaUri, description, mimeType, source, decimalCoords, c -> {
uploadController.startUpload(title, mediaUri, description, mimeType, source, decimalCoords, wikiDataEntityId, c -> {
ShareActivity.this.contribution = c;
showPostUpload();
});
@ -278,7 +275,10 @@ public class ShareActivity
}
if (intent.hasExtra("isDirectUpload")) {
Timber.d("This was initiated by a direct upload from Nearby");
isNearbyUpload = true;
isNearbyUpload = intent.getBooleanExtra("isDirectUpload", false);
}
if (intent.hasExtra("wikiDataEntityId")) {
wikiDataEntityId = intent.getStringExtra("wikiDataEntityId");
}
mimeType = intent.getType();
}
@ -446,7 +446,6 @@ public class ShareActivity
});
}
@Override
public void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions, @NonNull int[] grantResults) {

View file

@ -91,7 +91,7 @@ public class UploadController {
* @param decimalCoords the coordinates in decimal. (e.g. "37.51136|-77.602615")
* @param onComplete the progress tracker
*/
public void startUpload(String title, Uri mediaUri, String description, String mimeType, String source, String decimalCoords, ContributionUploadProgress onComplete) {
public void startUpload(String title, Uri mediaUri, String description, String mimeType, String source, String decimalCoords, String wikiDataEntityId, ContributionUploadProgress onComplete) {
Contribution contribution;
//TODO: Modify this to include coords
@ -101,6 +101,7 @@ public class UploadController {
contribution.setTag("mimeType", mimeType);
contribution.setSource(source);
contribution.setWikiDataEntityId(wikiDataEntityId);
//Calls the next overloaded method
startUpload(contribution, onComplete);

View file

@ -18,6 +18,7 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -36,6 +37,11 @@ import fr.free.nrw.commons.contributions.ContributionsContentProvider;
import fr.free.nrw.commons.modifications.ModificationsContentProvider;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
import fr.free.nrw.commons.mwapi.UploadResult;
import fr.free.nrw.commons.utils.ViewUtil;
import fr.free.nrw.commons.wikidata.WikidataEditListener;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import timber.log.Timber;
public class UploadService extends HandlerService<Contribution> {
@ -49,9 +55,11 @@ public class UploadService extends HandlerService<Contribution> {
public static final String EXTRA_CAMPAIGN = EXTRA_PREFIX + ".campaign";
@Inject MediaWikiApi mwApi;
@Inject WikidataEditListener wikidataEditListener;
@Inject SessionManager sessionManager;
@Inject @Named("default_preferences") SharedPreferences prefs;
@Inject ContributionDao contributionDao;
@Inject @Named("direct_nearby_upload_prefs") SharedPreferences directPrefs;
private NotificationManager notificationManager;
private NotificationCompat.Builder curProgressNotification;
@ -137,6 +145,7 @@ public class UploadService extends HandlerService<Contribution> {
@Override
public void queue(int what, Contribution contribution) {
Timber.d("Upload service queue has contribution with wiki data entity id as %s", contribution.getWikiDataEntityId());
switch (what) {
case ACTION_UPLOAD_FILE:
@ -253,6 +262,7 @@ public class UploadService extends HandlerService<Contribution> {
if (!resultStatus.equals("Success")) {
showFailedNotification(contribution);
} else {
editWikidataProperty(contribution);
contribution.setFilename(uploadResult.getCanonicalFilename());
contribution.setImageUrl(uploadResult.getImageUrl());
contribution.setState(Contribution.STATE_COMPLETED);
@ -275,6 +285,48 @@ public class UploadService extends HandlerService<Contribution> {
}
}
/**
* Edits the wikidata entity by adding the P18 property to it.
* Adding the P18 edit requires calling the wikidata API to create a claim against the entity
* @param contribution
*/
@SuppressLint("CheckResult")
private void editWikidataProperty(Contribution contribution) {
Timber.d("Upload successful with wiki data entity id as %s", contribution.getWikiDataEntityId());
Timber.d("Attempting to edit Wikidata property %s", contribution.getWikiDataEntityId());
Observable.fromCallable(() -> mwApi.wikidatCreateClaim(contribution.getWikiDataEntityId(), "P18", "value", getFileName(contribution)))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(result -> handleClaimResult(contribution, result), throwable -> {
Timber.e(throwable, "Error occurred while making claim");
ViewUtil.showLongToast(getBaseContext(), getResources().getString(R.string.wikidata_edit_failure));
});
}
private void handleClaimResult(Contribution contribution, Boolean result) {
if(result) {
wikidataEditListener.onSuccessfulWikidataEdit();
String title = directPrefs.getString("Title", "");
String successStringTemplate = getResources().getString(R.string.successful_wikidata_edit);
String successMessage = String.format(Locale.getDefault(), successStringTemplate, title);
ViewUtil.showLongToast(getBaseContext(), successMessage);
} else {
Timber.d("Unable to make wiki data edit for entity %s", contribution.getWikiDataEntityId());
ViewUtil.showLongToast(getBaseContext(), getResources().getString(R.string.wikidata_edit_failure));
}
}
/**
* Formats and returns the filename as accepted by the wiki base API
* https://www.mediawiki.org/wiki/Wikibase/API#wbcreateclaim
* @param contribution
* @return
*/
private String getFileName(Contribution contribution) {
String filename = String.format("\"%s\"", contribution.getFilename().replace("File:", ""));
return filename;
}
@SuppressLint("StringFormatInvalid")
private void showFailedNotification(Contribution contribution) {
Notification failureNotification = new NotificationCompat.Builder(this).setAutoCancel(true)

View file

@ -0,0 +1,16 @@
package fr.free.nrw.commons.wikidata;
public abstract class WikidataEditListener {
protected WikidataP18EditListener wikidataP18EditListener;
public abstract void onSuccessfulWikidataEdit();
public void setAuthenticationStateListener(WikidataP18EditListener wikidataP18EditListener) {
this.wikidataP18EditListener = wikidataP18EditListener;
}
public interface WikidataP18EditListener {
void onWikidataEditSuccessful();
}
}

View file

@ -0,0 +1,20 @@
package fr.free.nrw.commons.wikidata;
/**
* Listener for wikidata edits
*/
public class WikidataEditListenerImpl extends WikidataEditListener {
public WikidataEditListenerImpl() {
}
/**
* Fired when wikidata P18 edit is successful. If there's an active listener, then it is fired
*/
@Override
public void onSuccessfulWikidataEdit() {
if (wikidataP18EditListener != null) {
wikidataP18EditListener.onWikidataEditSuccessful();
}
}
}

View file

@ -284,4 +284,6 @@
<string name="share_coordinates_not_present">Coordinates were not specified during image selection</string>
<string name="error_fetching_nearby_places">Error fetching nearby places.</string>
<string name="successful_wikidata_edit">Image successfully added to %1$s on Wikidata!</string>
<string name="wikidata_edit_failure">Failed to update corresponding wiki data entity!</string>
</resources>

View file

@ -26,15 +26,17 @@ class ApacheHttpClientMediaWikiApiTest {
private lateinit var testObject: ApacheHttpClientMediaWikiApi
private lateinit var server: MockWebServer
private lateinit var wikidataServer: MockWebServer
private lateinit var sharedPreferences: SharedPreferences
private lateinit var categoryPreferences: SharedPreferences
@Before
fun setUp() {
server = MockWebServer()
wikidataServer = MockWebServer()
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(RuntimeEnvironment.application)
categoryPreferences = PreferenceManager.getDefaultSharedPreferences(RuntimeEnvironment.application)
testObject = ApacheHttpClientMediaWikiApi(RuntimeEnvironment.application, "http://" + server.hostName + ":" + server.port + "/", sharedPreferences, categoryPreferences, Gson())
testObject = ApacheHttpClientMediaWikiApi(RuntimeEnvironment.application, "http://" + server.hostName + ":" + server.port + "/", "http://" + wikidataServer.hostName + ":" + wikidataServer.port + "/", sharedPreferences, categoryPreferences, Gson())
testObject.setWikiMediaToolforgeUrl("http://" + server.hostName + ":" + server.port + "/")
}