diff --git a/app/build.gradle b/app/build.gradle
index 4c562e7de..752f4b718 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -39,7 +39,7 @@ dependencies {
implementation 'fr.avianey.com.viewpagerindicator:library:2.4.1.1@aar'
implementation 'com.github.chrisbanes:PhotoView:2.0.0'
implementation 'com.github.pedrovgs:renderers:3.3.3'
- implementation 'com.mapbox.mapboxsdk:mapbox-android-sdk:8.6.2'
+ implementation 'com.mapbox.mapboxsdk:mapbox-android-sdk:9.1.0'
implementation 'com.mapbox.mapboxsdk:mapbox-android-plugin-localization-v8:0.11.0'
implementation 'com.mapbox.mapboxsdk:mapbox-android-plugin-scalebar-v9:0.4.0'
implementation 'com.github.deano2390:MaterialShowcaseView:1.2.0'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 47d102fdc..036299c66 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -137,7 +137,11 @@
android:name=".review.ReviewActivity"
android:label="@string/title_activity_review" />
-
+
+
diff --git a/app/src/main/java/fr/free/nrw/commons/LocationPicker/LocationPicker.java b/app/src/main/java/fr/free/nrw/commons/LocationPicker/LocationPicker.java
new file mode 100644
index 000000000..bd890d6ef
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/LocationPicker/LocationPicker.java
@@ -0,0 +1,54 @@
+package fr.free.nrw.commons.LocationPicker;
+
+import android.app.Activity;
+import android.content.Intent;
+import androidx.annotation.NonNull;
+import com.mapbox.mapboxsdk.camera.CameraPosition;
+
+/**
+ * Helper class for starting the activity
+ */
+public final class LocationPicker {
+
+ /**
+ * Getting camera position from the intent using constants
+ * @param data intent
+ * @return CameraPosition
+ */
+ public static CameraPosition getCameraPosition(final Intent data) {
+ return data.getParcelableExtra(LocationPickerConstants.MAP_CAMERA_POSITION);
+ }
+
+ public static class IntentBuilder {
+
+ private final Intent intent;
+
+ /**
+ * Creates a new builder that creates an intent to launch the place picker activity.
+ */
+ public IntentBuilder() {
+ intent = new Intent();
+ }
+
+ /**
+ * Gets and puts location in intent
+ * @param position CameraPosition
+ * @return LocationPicker.IntentBuilder
+ */
+ public LocationPicker.IntentBuilder defaultLocation(
+ final CameraPosition position) {
+ intent.putExtra(LocationPickerConstants.MAP_CAMERA_POSITION, position);
+ return this;
+ }
+
+ /**
+ * Gets and sets the activity
+ * @param activity Activity
+ * @return Intent
+ */
+ public Intent build(final Activity activity) {
+ intent.setClass(activity, LocationPickerActivity.class);
+ return intent;
+ }
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/LocationPicker/LocationPickerActivity.java b/app/src/main/java/fr/free/nrw/commons/LocationPicker/LocationPickerActivity.java
new file mode 100644
index 000000000..57da44a42
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/LocationPicker/LocationPickerActivity.java
@@ -0,0 +1,288 @@
+package fr.free.nrw.commons.LocationPicker;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.text.Html;
+import android.text.method.LinkMovementMethod;
+import android.view.Window;
+import android.view.animation.OvershootInterpolator;
+import android.widget.ImageView;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.ActionBar;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.AppCompatTextView;
+import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.lifecycle.Observer;
+import androidx.lifecycle.ViewModelProvider;
+import com.google.android.material.floatingactionbutton.FloatingActionButton;
+import com.mapbox.android.core.permissions.PermissionsManager;
+import com.mapbox.mapboxsdk.camera.CameraPosition;
+import com.mapbox.mapboxsdk.camera.CameraPosition.Builder;
+import com.mapbox.mapboxsdk.camera.CameraUpdateFactory;
+import com.mapbox.mapboxsdk.geometry.LatLng;
+import com.mapbox.mapboxsdk.location.LocationComponent;
+import com.mapbox.mapboxsdk.location.LocationComponentActivationOptions;
+import com.mapbox.mapboxsdk.location.modes.CameraMode;
+import com.mapbox.mapboxsdk.location.modes.RenderMode;
+import com.mapbox.mapboxsdk.maps.MapView;
+import com.mapbox.mapboxsdk.maps.MapboxMap;
+import com.mapbox.mapboxsdk.maps.MapboxMap.OnCameraIdleListener;
+import com.mapbox.mapboxsdk.maps.MapboxMap.OnCameraMoveStartedListener;
+import com.mapbox.mapboxsdk.maps.OnMapReadyCallback;
+import com.mapbox.mapboxsdk.maps.Style;
+import com.mapbox.mapboxsdk.maps.UiSettings;
+import fr.free.nrw.commons.R;
+import org.jetbrains.annotations.NotNull;
+import timber.log.Timber;
+
+/**
+ * Helps to pick location and return the result with an intent
+ */
+public class LocationPickerActivity extends AppCompatActivity implements OnMapReadyCallback,
+ OnCameraMoveStartedListener, OnCameraIdleListener, Observer {
+
+ /**
+ * cameraPosition : position of picker
+ */
+ private CameraPosition cameraPosition;
+ /**
+ * markerImage : picker image
+ */
+ private ImageView markerImage;
+ /**
+ * mapboxMap : map
+ */
+ private MapboxMap mapboxMap;
+ /**
+ * mapView : view of the map
+ */
+ private MapView mapView;
+ /**
+ * tvAttribution : credit
+ */
+ private AppCompatTextView tvAttribution;
+
+ @Override
+ protected void onCreate(@Nullable final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
+ final ActionBar actionBar = getSupportActionBar();
+ if (actionBar != null) {
+ actionBar.hide();
+ }
+ setContentView(R.layout.activity_location_picker);
+
+ if (savedInstanceState == null) {
+ cameraPosition = getIntent().getParcelableExtra(LocationPickerConstants.MAP_CAMERA_POSITION);
+ }
+
+ final LocationPickerViewModel viewModel = new ViewModelProvider(this)
+ .get(LocationPickerViewModel.class);
+ viewModel.getResult().observe(this, this);
+
+ bindViews();
+ addBackButtonListener();
+ addPlaceSelectedButton();
+ addCredits();
+ getToolbarUI();
+
+ mapView.onCreate(savedInstanceState);
+ mapView.getMapAsync(this);
+ }
+
+ /**
+ * For showing credits
+ */
+ private void addCredits() {
+ tvAttribution.setText(Html.fromHtml(getString(R.string.map_attribution)));
+ tvAttribution.setMovementMethod(LinkMovementMethod.getInstance());
+ }
+
+ /**
+ * Clicking back button destroy locationPickerActivity
+ */
+ private void addBackButtonListener() {
+ final ImageView backButton = findViewById(R.id.mapbox_place_picker_toolbar_back_button);
+ backButton.setOnClickListener(view -> finish());
+ }
+
+ /**
+ * Binds mapView and location picker icon
+ */
+ private void bindViews() {
+ mapView = findViewById(R.id.map_view);
+ markerImage = findViewById(R.id.location_picker_image_view_marker);
+ tvAttribution = findViewById(R.id.tv_attribution);
+ }
+
+ /**
+ * Binds the listeners
+ */
+ private void bindListeners() {
+ mapboxMap.addOnCameraMoveStartedListener(
+ this);
+ mapboxMap.addOnCameraIdleListener(
+ this);
+ }
+
+ /**
+ * Gets toolbar color
+ */
+ private void getToolbarUI() {
+ final ConstraintLayout toolbar = findViewById(R.id.location_picker_toolbar);
+ toolbar.setBackgroundColor(getResources().getColor(R.color.primaryColor));
+ }
+
+ /**
+ * Takes action when map is ready to show
+ * @param mapboxMap map
+ */
+ @Override
+ public void onMapReady(final MapboxMap mapboxMap) {
+ this.mapboxMap = mapboxMap;
+ mapboxMap.setStyle(Style.MAPBOX_STREETS, style -> {
+ adjustCameraBasedOnOptions();
+ bindListeners();
+ enableLocationComponent(style);
+ });
+ }
+
+ /**
+ * move the location to the current media coordinates
+ */
+ private void adjustCameraBasedOnOptions() {
+ mapboxMap.moveCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));
+ }
+
+ /**
+ * Enables location components
+ * @param loadedMapStyle Style
+ */
+ @SuppressWarnings( {"MissingPermission"})
+ private void enableLocationComponent(@NonNull final Style loadedMapStyle) {
+ final UiSettings uiSettings = mapboxMap.getUiSettings();
+ uiSettings.setAttributionEnabled(false);
+
+ // Check if permissions are enabled and if not request
+ if (PermissionsManager.areLocationPermissionsGranted(this)) {
+
+ // Get an instance of the component
+ final LocationComponent locationComponent = mapboxMap.getLocationComponent();
+
+ // Activate with options
+ locationComponent.activateLocationComponent(
+ LocationComponentActivationOptions.builder(this, loadedMapStyle).build());
+
+ // Enable to make component visible
+ locationComponent.setLocationComponentEnabled(true);
+
+ // Set the component's camera mode
+ locationComponent.setCameraMode(CameraMode.NONE);
+
+ // Set the component's render mode
+ locationComponent.setRenderMode(RenderMode.NORMAL);
+
+ }
+ }
+
+ /**
+ * Acts on camera moving
+ * @param reason int
+ */
+ @Override
+ public void onCameraMoveStarted(final int reason) {
+ Timber.v("Map camera has begun moving.");
+ if (markerImage.getTranslationY() == 0) {
+ markerImage.animate().translationY(-75)
+ .setInterpolator(new OvershootInterpolator()).setDuration(250).start();
+ }
+ }
+
+ /**
+ * Acts on camera idle
+ */
+ @Override
+ public void onCameraIdle() {
+ Timber.v("Map camera is now idling.");
+ markerImage.animate().translationY(0)
+ .setInterpolator(new OvershootInterpolator()).setDuration(250).start();
+ }
+
+ /**
+ * Takes action on camera position
+ * @param position position of picker
+ */
+ @Override
+ public void onChanged(@Nullable CameraPosition position) {
+ if (position == null) {
+ position = new Builder()
+ .target(new LatLng(mapboxMap.getCameraPosition().target.getLatitude(),
+ mapboxMap.getCameraPosition().target.getLongitude()))
+ .zoom(16).build();
+ }
+ cameraPosition = position;
+ }
+
+ /**
+ * Select the preferable location
+ */
+ private void addPlaceSelectedButton() {
+ final FloatingActionButton placeSelectedButton = findViewById(R.id.location_chosen_button);
+ placeSelectedButton.setOnClickListener(view -> placeSelected());
+ }
+
+ /**
+ * Return the intent with required data
+ */
+ void placeSelected() {
+ final Intent returningIntent = new Intent();
+ returningIntent.putExtra(LocationPickerConstants.MAP_CAMERA_POSITION,
+ mapboxMap.getCameraPosition());
+ setResult(AppCompatActivity.RESULT_OK, returningIntent);
+ finish();
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ mapView.onStart();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ mapView.onResume();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ mapView.onPause();
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ mapView.onStop();
+ }
+
+ @Override
+ protected void onSaveInstanceState(final @NotNull Bundle outState) {
+ super.onSaveInstanceState(outState);
+ mapView.onSaveInstanceState(outState);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ mapView.onDestroy();
+ }
+
+ @Override
+ public void onLowMemory() {
+ super.onLowMemory();
+ mapView.onLowMemory();
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/LocationPicker/LocationPickerConstants.java b/app/src/main/java/fr/free/nrw/commons/LocationPicker/LocationPickerConstants.java
new file mode 100644
index 000000000..1a2cab6d1
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/LocationPicker/LocationPickerConstants.java
@@ -0,0 +1,13 @@
+package fr.free.nrw.commons.LocationPicker;
+
+/**
+ * Constants need for location picking
+ */
+public final class LocationPickerConstants {
+
+ public static final String MAP_CAMERA_POSITION
+ = "location.picker.cameraPosition";
+
+ private LocationPickerConstants() {
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/LocationPicker/LocationPickerViewModel.java b/app/src/main/java/fr/free/nrw/commons/LocationPicker/LocationPickerViewModel.java
new file mode 100644
index 000000000..c0b91cf71
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/LocationPicker/LocationPickerViewModel.java
@@ -0,0 +1,60 @@
+package fr.free.nrw.commons.LocationPicker;
+
+import android.app.Application;
+import androidx.annotation.NonNull;
+import androidx.lifecycle.AndroidViewModel;
+import androidx.lifecycle.MutableLiveData;
+import com.mapbox.mapboxsdk.camera.CameraPosition;
+import org.jetbrains.annotations.NotNull;
+import retrofit2.Call;
+import retrofit2.Callback;
+import retrofit2.Response;
+import timber.log.Timber;
+
+/**
+ * Observes live camera position data
+ */
+public class LocationPickerViewModel extends AndroidViewModel implements Callback {
+
+ /**
+ * Wrapping CameraPosition with MutableLiveData
+ */
+ private final MutableLiveData result = new MutableLiveData<>();
+
+ /**
+ * Constructor for this class
+ * @param application Application
+ */
+ public LocationPickerViewModel(@NonNull final Application application) {
+ super(application);
+ }
+
+ /**
+ * Responses on camera position changing
+ * @param call Call
+ * @param response Response
+ */
+ @Override
+ public void onResponse(final @NotNull Call call,
+ final Response response) {
+ if(response.body()==null){
+ result.setValue(null);
+ return;
+ }
+ result.setValue(response.body());
+ }
+
+ @Override
+ public void onFailure(final @NotNull Call call, final @NotNull Throwable t) {
+ Timber.e(t);
+ }
+
+ /**
+ * Gets live CameraPosition
+ * @return MutableLiveData
+ */
+ public MutableLiveData getResult() {
+ return result;
+ }
+
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/Media.kt b/app/src/main/java/fr/free/nrw/commons/Media.kt
index 090d8e230..3987acb37 100644
--- a/app/src/main/java/fr/free/nrw/commons/Media.kt
+++ b/app/src/main/java/fr/free/nrw/commons/Media.kt
@@ -74,7 +74,7 @@ class Media constructor(
* Gets the coordinates of where the file was created.
* @return file coordinates as a LatLng
*/
- val coordinates: LatLng? = null,
+ var coordinates: LatLng? = null,
val captions: Map = emptyMap(),
val descriptions: Map = emptyMap(),
val depictionIds: List = emptyList()
diff --git a/app/src/main/java/fr/free/nrw/commons/actions/PageEditClient.kt b/app/src/main/java/fr/free/nrw/commons/actions/PageEditClient.kt
index 0ae7e42de..4dfce0f88 100644
--- a/app/src/main/java/fr/free/nrw/commons/actions/PageEditClient.kt
+++ b/app/src/main/java/fr/free/nrw/commons/actions/PageEditClient.kt
@@ -1,6 +1,7 @@
package fr.free.nrw.commons.actions
import io.reactivex.Observable
+import io.reactivex.Single
import org.wikipedia.csrf.CsrfTokenClient
/**
@@ -62,4 +63,15 @@ class PageEditClient(
Observable.just(false)
}
}
+
+ /**
+ * Get whole WikiText of required file
+ * @param title : Name of the file
+ * @return Observable
+ */
+ fun getCurrentWikiText(title: String): Single {
+ return pageEditInterface.getWikiText(title).map {
+ it.query()?.pages()?.get(0)?.revisions()?.get(0)?.content()
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/actions/PageEditInterface.kt b/app/src/main/java/fr/free/nrw/commons/actions/PageEditInterface.kt
index 8b27d1288..91e8230bf 100644
--- a/app/src/main/java/fr/free/nrw/commons/actions/PageEditInterface.kt
+++ b/app/src/main/java/fr/free/nrw/commons/actions/PageEditInterface.kt
@@ -1,12 +1,11 @@
package fr.free.nrw.commons.actions
import io.reactivex.Observable
+import io.reactivex.Single
import org.wikipedia.dataclient.Service
+import org.wikipedia.dataclient.mwapi.MwQueryResponse
import org.wikipedia.edit.Edit
-import retrofit2.http.Field
-import retrofit2.http.FormUrlEncoded
-import retrofit2.http.Headers
-import retrofit2.http.POST
+import retrofit2.http.*
/**
* This interface facilitates wiki commons page editing services to the Networking module
@@ -73,4 +72,17 @@ interface PageEditInterface {
@Field("prependtext") prependText: String,
@Field("token") token: String
): Observable
+
+ /**
+ * Get wiki text for provided file names
+ * @param titles : Name of the file
+ * @return Single
+ */
+ @GET(
+ Service.MW_API_PREFIX +
+ "action=query&prop=revisions&rvprop=content|timestamp&rvlimit=1&converttitles="
+ )
+ fun getWikiText(
+ @Query("titles") title: String
+ ): Single
}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/coordinates/CoordinateEditHelper.java b/app/src/main/java/fr/free/nrw/commons/coordinates/CoordinateEditHelper.java
new file mode 100644
index 000000000..1f3a75029
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/coordinates/CoordinateEditHelper.java
@@ -0,0 +1,197 @@
+package fr.free.nrw.commons.coordinates;
+
+import static fr.free.nrw.commons.notification.NotificationHelper.NOTIFICATION_EDIT_COORDINATES;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import fr.free.nrw.commons.BuildConfig;
+import fr.free.nrw.commons.Media;
+import fr.free.nrw.commons.R;
+import fr.free.nrw.commons.actions.PageEditClient;
+import fr.free.nrw.commons.notification.NotificationHelper;
+import fr.free.nrw.commons.utils.ViewUtilWrapper;
+import io.reactivex.Observable;
+import io.reactivex.Single;
+import io.reactivex.schedulers.Schedulers;
+import java.util.Objects;
+import javax.inject.Inject;
+import javax.inject.Named;
+import org.apache.commons.lang3.StringUtils;
+import timber.log.Timber;
+
+/**
+ * Helper class for edit and update given coordinates and showing notification about new coordinates
+ * upgradation
+ */
+public class CoordinateEditHelper {
+
+ /**
+ * notificationHelper: helps creating notification
+ */
+ private final NotificationHelper notificationHelper;
+ /**
+ * * pageEditClient: methods provided by this member posts the edited coordinates
+ * to the Media wiki api
+ */
+ public final PageEditClient pageEditClient;
+ /**
+ * viewUtil: helps to show Toast
+ */
+ private final ViewUtilWrapper viewUtil;
+
+ @Inject
+ public CoordinateEditHelper(final NotificationHelper notificationHelper,
+ @Named("commons-page-edit") final PageEditClient pageEditClient,
+ final ViewUtilWrapper viewUtil) {
+ this.notificationHelper = notificationHelper;
+ this.pageEditClient = pageEditClient;
+ this.viewUtil = viewUtil;
+ }
+
+ /**
+ * Public interface to edit coordinates
+ * @param context to be added
+ * @param media to be added
+ * @param Accuracy to be added
+ * @return Single
+ */
+ public Single makeCoordinatesEdit(final Context context, final Media media,
+ final String Latitude, final String Longitude, final String Accuracy) {
+ viewUtil.showShortToast(context,
+ context.getString(R.string.coordinates_edit_helper_make_edit_toast));
+ return addCoordinates(media, Latitude, Longitude, Accuracy)
+ .flatMapSingle(result -> Single.just(showCoordinatesEditNotification(context, media,
+ Latitude, Longitude, Accuracy, result)))
+ .firstOrError();
+ }
+
+ /**
+ * Replaces new coordinates
+ * @param media to be added
+ * @param Latitude to be added
+ * @param Longitude to be added
+ * @param Accuracy to be added
+ * @return Observable
+ */
+ private Observable addCoordinates(final Media media, final String Latitude,
+ final String Longitude, final String Accuracy) {
+ Timber.d("thread is coordinates adding %s", Thread.currentThread().getName());
+ final String summary = "Adding Coordinates";
+
+ final StringBuilder buffer = new StringBuilder();
+
+ final String wikiText = pageEditClient.getCurrentWikiText(media.getFilename())
+ .subscribeOn(Schedulers.io())
+ .blockingGet();
+
+ if (Latitude != null) {
+ buffer.append("\n{{Location|").append(Latitude).append("|").append(Longitude)
+ .append("|").append(Accuracy).append("}}");
+ }
+
+ final String editedLocation = buffer.toString();
+ final String appendText = getFormattedWikiText(wikiText, editedLocation);
+
+ return pageEditClient.edit(Objects.requireNonNull(media.getFilename())
+ , appendText, summary);
+ }
+
+ /**
+ * Helps to get formatted wikitext with upgraded location
+ * @param wikiText current wikitext
+ * @param editedLocation new location
+ * @return String
+ */
+ private String getFormattedWikiText(final String wikiText, final String editedLocation){
+
+ if (wikiText.contains("filedesc") && wikiText.contains("Location")) {
+
+ final String fromLocationToEnd = wikiText.substring(wikiText.indexOf("{{Location"));
+ final String firstHalf = wikiText.substring(0, wikiText.indexOf("{{Location"));
+ final String lastHalf = fromLocationToEnd.substring(
+ fromLocationToEnd.indexOf("}}") + 2);
+
+ final int startOfSecondSection = StringUtils.ordinalIndexOf(wikiText,
+ "==", 3);
+ final StringBuilder buffer = new StringBuilder();
+ if (wikiText.charAt(wikiText.indexOf("{{Location")-1) == '\n') {
+ buffer.append(editedLocation.substring(1));
+ } else {
+ buffer.append(editedLocation);
+ }
+ if (startOfSecondSection != -1 && wikiText.charAt(startOfSecondSection-1)!= '\n') {
+ buffer.append("\n");
+ }
+
+ return firstHalf + buffer + lastHalf;
+
+ }
+ if (wikiText.contains("filedesc") && !wikiText.contains("Location")) {
+
+ final int startOfSecondSection = StringUtils.ordinalIndexOf(wikiText,
+ "==", 3);
+
+ if (startOfSecondSection != -1) {
+ final String firstHalf = wikiText.substring(0, startOfSecondSection);
+ final String lastHalf = wikiText.substring(startOfSecondSection);
+ final String buffer = editedLocation.substring(1)
+ + "\n";
+ return firstHalf + buffer + lastHalf;
+ }
+
+ return wikiText + editedLocation;
+ }
+ if (!wikiText.contains("filedesc") && !wikiText.contains("Location")) {
+
+ return "== {{int:filedesc}} ==" + editedLocation + wikiText;
+
+ }
+ if (!wikiText.contains("filedesc") && wikiText.contains("Location")) {
+
+ return "== {{int:filedesc}} ==" + editedLocation + wikiText;
+
+ }
+ return null;
+ }
+
+ /**
+ * Update coordinates and shows notification about coordinates update
+ * @param context to be added
+ * @param media to be added
+ * @param latitude to be added
+ * @param longitude to be added
+ * @param Accuracy to be added
+ * @param result to be added
+ * @return boolean
+ */
+ private boolean showCoordinatesEditNotification(final Context context, final Media media,
+ final String latitude, final String longitude, final String Accuracy,
+ final boolean result) {
+ final String message;
+ String title = context.getString(R.string.coordinates_edit_helper_show_edit_title);
+
+ if (result) {
+ media.setCoordinates(
+ new fr.free.nrw.commons.location.LatLng(Double.parseDouble(latitude),
+ Double.parseDouble(longitude),
+ Float.parseFloat(Accuracy)));
+ title += ": " + context
+ .getString(R.string.coordinates_edit_helper_show_edit_title_success);
+ final StringBuilder coordinatesInMessage = new StringBuilder();
+ final String mediaCoordinate = String.valueOf(media.getCoordinates());
+ coordinatesInMessage.append(mediaCoordinate);
+ message = context.getString(R.string.coordinates_edit_helper_show_edit_message,
+ coordinatesInMessage.toString());
+ } else {
+ title += ": " + context.getString(R.string.coordinates_edit_helper_show_edit_title);
+ message = context.getString(R.string.coordinates_edit_helper_edit_message_else) ;
+ }
+
+ final String urlForFile = BuildConfig.COMMONS_URL + "/wiki/" + media.getFilename();
+ final Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(urlForFile));
+ notificationHelper.showNotification(context, title, message, NOTIFICATION_EDIT_COORDINATES,
+ browserIntent);
+ return result;
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java
index 6c9472ddd..8e6518c44 100644
--- a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java
+++ b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java
@@ -1,5 +1,7 @@
package fr.free.nrw.commons.media;
+import static android.app.Activity.RESULT_CANCELED;
+import static android.app.Activity.RESULT_OK;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static fr.free.nrw.commons.category.CategoryClientKt.CATEGORY_NEEDING_CATEGORIES;
@@ -16,11 +18,8 @@ import android.os.Bundle;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
-import android.util.Log;
-import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
-import android.view.View.OnKeyListener;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
@@ -50,6 +49,9 @@ import com.facebook.imagepipeline.image.ImageInfo;
import com.facebook.imagepipeline.request.ImageRequest;
import com.jakewharton.rxbinding2.view.RxView;
import com.jakewharton.rxbinding2.widget.RxSearchView;
+import com.mapbox.mapboxsdk.camera.CameraPosition;
+import com.mapbox.mapboxsdk.geometry.LatLng;
+import fr.free.nrw.commons.LocationPicker.LocationPicker;
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.MediaDataExtractor;
import fr.free.nrw.commons.R;
@@ -62,10 +64,11 @@ import fr.free.nrw.commons.category.CategoryEditHelper;
import fr.free.nrw.commons.category.CategoryEditSearchRecyclerViewAdapter;
import fr.free.nrw.commons.category.CategoryEditSearchRecyclerViewAdapter.Callback;
import fr.free.nrw.commons.contributions.ContributionsFragment;
+import fr.free.nrw.commons.coordinates.CoordinateEditHelper;
import fr.free.nrw.commons.delete.DeleteHelper;
import fr.free.nrw.commons.delete.ReasonBuilder;
-import fr.free.nrw.commons.explore.depictions.WikidataItemDetailsActivity;
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
+import fr.free.nrw.commons.explore.depictions.WikidataItemDetailsActivity;
import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.nearby.Label;
import fr.free.nrw.commons.ui.widget.HtmlTextView;
@@ -77,8 +80,9 @@ import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
-import java.util.concurrent.TimeUnit;
import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Named;
import org.apache.commons.lang3.StringUtils;
@@ -88,6 +92,7 @@ import timber.log.Timber;
public class MediaDetailFragment extends CommonsDaggerSupportFragment implements Callback,
CategoryEditHelper.Callback {
+ private static final int REQUEST_CODE = 1001 ;
private boolean editable;
private boolean isCategoryImage;
private MediaDetailPagerFragment.MediaDetailProvider detailProvider;
@@ -125,6 +130,8 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
@Inject
CategoryEditHelper categoryEditHelper;
@Inject
+ CoordinateEditHelper coordinateEditHelper;
+ @Inject
ViewUtilWrapper viewUtil;
@Inject
CategoryClient categoryClient;
@@ -760,6 +767,75 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
}
}
+ @OnClick(R.id.coordinate_edit)
+ public void onUpdateCoordinatesClicked(){
+ goToLocationPickerActivity();
+ }
+
+ /**
+ * Start location picker activity with a request code and get the coordinates from the activity.
+ */
+ private void goToLocationPickerActivity() {
+ /*
+ If location is not provided in media this coordinates will act as a placeholder in
+ location picker activity
+ */
+ double defaultLatitude = 37.773972;
+ double defaultLongitude = -122.431297;
+
+ if (media.getCoordinates() != null) {
+ defaultLatitude = media.getCoordinates().getLatitude();
+ defaultLongitude = media.getCoordinates().getLongitude();
+ }
+ startActivityForResult(new LocationPicker.IntentBuilder()
+ .defaultLocation(new CameraPosition.Builder()
+ .target(new LatLng(defaultLatitude, defaultLongitude))
+ .zoom(16).build())
+ .build(getActivity()), REQUEST_CODE);
+ }
+
+ /**
+ * Get the coordinates and update the existing coordinates.
+ * @param requestCode
+ * @param resultCode
+ * @param data
+ */
+ @Override
+ public void onActivityResult(final int requestCode, final int resultCode,
+ @Nullable final Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+
+ if (requestCode == REQUEST_CODE && resultCode == RESULT_OK) {
+
+ assert data != null;
+ final CameraPosition cameraPosition = LocationPicker.getCameraPosition(data);
+
+ if (cameraPosition != null) {
+
+ final String latitude = String.valueOf(cameraPosition.target.getLatitude());
+ final String longitude = String.valueOf(cameraPosition.target.getLongitude());
+ final String accuracy = String.valueOf(cameraPosition.target.getAltitude());
+ String currentLatitude = null;
+ String currentLongitude = null;
+
+ if (media.getCoordinates() != null) {
+ currentLatitude = String.valueOf(media.getCoordinates().getLatitude());
+ currentLongitude = String.valueOf(media.getCoordinates().getLongitude());
+ }
+
+ if (!latitude.equals(currentLatitude) || !longitude.equals(currentLongitude)) {
+ updateCoordinates(latitude, longitude, accuracy);
+ } else if (media.getCoordinates() == null) {
+ updateCoordinates(latitude, longitude, accuracy);
+ }
+ }
+ } else if (resultCode == RESULT_CANCELED) {
+ viewUtil.showShortToast(getContext(),
+ Objects.requireNonNull(getContext())
+ .getString(R.string.coordinates_picking_unsuccessful));
+ }
+ }
+
@OnClick(R.id.update_categories_button)
public void onUpdateCategoriesClicked() {
updateCategories(categoryEditSearchRecyclerViewAdapter.getNewCategories());
@@ -783,6 +859,24 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
}));
}
+ /**
+ * Fetched coordinates are replaced with existing coordinates by a POST API call.
+ * @param Latitude to be added
+ * @param Longitude to be added
+ * @param Accuracy to be added
+ */
+ public void updateCoordinates(final String Latitude, final String Longitude,
+ final String Accuracy) {
+ compositeDisposable.add(coordinateEditHelper.makeCoordinatesEdit(getContext(), media,
+ Latitude, Longitude, Accuracy)
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(s -> {
+ Timber.d("Coordinates are added.");
+ coordinates.setText(prettyCoordinates(media));
+ }));
+ }
+
@SuppressLint("StringFormatInvalid")
@OnClick(R.id.nominateDeletion)
public void onDeleteButtonClicked(){
diff --git a/app/src/main/java/fr/free/nrw/commons/notification/NotificationHelper.java b/app/src/main/java/fr/free/nrw/commons/notification/NotificationHelper.java
index 76b51bb57..a23f350bd 100644
--- a/app/src/main/java/fr/free/nrw/commons/notification/NotificationHelper.java
+++ b/app/src/main/java/fr/free/nrw/commons/notification/NotificationHelper.java
@@ -25,6 +25,7 @@ public class NotificationHelper {
public static final int NOTIFICATION_DELETE = 1;
public static final int NOTIFICATION_EDIT_CATEGORY = 2;
+ public static final int NOTIFICATION_EDIT_COORDINATES = 3;
private NotificationManager notificationManager;
private NotificationCompat.Builder notificationBuilder;
diff --git a/app/src/main/res/drawable-hdpi/map_default_map_marker.png b/app/src/main/res/drawable-hdpi/map_default_map_marker.png
new file mode 100644
index 000000000..ef993a0f5
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/map_default_map_marker.png differ
diff --git a/app/src/main/res/drawable-hdpi/map_default_map_marker_shadow.png b/app/src/main/res/drawable-hdpi/map_default_map_marker_shadow.png
new file mode 100644
index 000000000..c841453c1
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/map_default_map_marker_shadow.png differ
diff --git a/app/src/main/res/drawable-hdpi/mapbox_logo_icon.png b/app/src/main/res/drawable-hdpi/mapbox_logo_icon.png
new file mode 100644
index 000000000..7568387a8
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/mapbox_logo_icon.png differ
diff --git a/app/src/main/res/drawable-mdpi/map_default_map_marker.png b/app/src/main/res/drawable-mdpi/map_default_map_marker.png
new file mode 100644
index 000000000..45ca4ab58
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/map_default_map_marker.png differ
diff --git a/app/src/main/res/drawable-mdpi/map_default_map_marker_shadow.png b/app/src/main/res/drawable-mdpi/map_default_map_marker_shadow.png
new file mode 100644
index 000000000..e8b49eefe
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/map_default_map_marker_shadow.png differ
diff --git a/app/src/main/res/drawable-mdpi/mapbox_logo_icon.png b/app/src/main/res/drawable-mdpi/mapbox_logo_icon.png
new file mode 100644
index 000000000..9cdec5151
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/mapbox_logo_icon.png differ
diff --git a/app/src/main/res/drawable-xhdpi/map_default_map_marker.png b/app/src/main/res/drawable-xhdpi/map_default_map_marker.png
new file mode 100644
index 000000000..19a1f66a8
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/map_default_map_marker.png differ
diff --git a/app/src/main/res/drawable-xhdpi/map_default_map_marker_shadow.png b/app/src/main/res/drawable-xhdpi/map_default_map_marker_shadow.png
new file mode 100644
index 000000000..b994fc39d
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/map_default_map_marker_shadow.png differ
diff --git a/app/src/main/res/drawable-xhdpi/mapbox_logo_icon.png b/app/src/main/res/drawable-xhdpi/mapbox_logo_icon.png
new file mode 100644
index 000000000..7eec45f4b
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/mapbox_logo_icon.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/map_default_map_marker.png b/app/src/main/res/drawable-xxhdpi/map_default_map_marker.png
new file mode 100644
index 000000000..4a9abb9ee
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/map_default_map_marker.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/map_default_map_marker_shadow.png b/app/src/main/res/drawable-xxhdpi/map_default_map_marker_shadow.png
new file mode 100644
index 000000000..7218f0caf
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/map_default_map_marker_shadow.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/mapbox_logo_icon.png b/app/src/main/res/drawable-xxhdpi/mapbox_logo_icon.png
new file mode 100644
index 000000000..5876056f1
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/mapbox_logo_icon.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/map_default_map_marker.png b/app/src/main/res/drawable-xxxhdpi/map_default_map_marker.png
new file mode 100644
index 000000000..65fe36d1b
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/map_default_map_marker.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/map_default_map_marker_shadow.png b/app/src/main/res/drawable-xxxhdpi/map_default_map_marker_shadow.png
new file mode 100644
index 000000000..b0027d86b
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/map_default_map_marker_shadow.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/mapbox_logo_icon.png b/app/src/main/res/drawable-xxxhdpi/mapbox_logo_icon.png
new file mode 100644
index 000000000..e5780ccc0
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/mapbox_logo_icon.png differ
diff --git a/app/src/main/res/layout/activity_location_picker.xml b/app/src/main/res/layout/activity_location_picker.xml
new file mode 100644
index 000000000..a6aa85d71
--- /dev/null
+++ b/app/src/main/res/layout/activity_location_picker.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/bottom_container_location_picker.xml b/app/src/main/res/layout/bottom_container_location_picker.xml
new file mode 100644
index 000000000..95c0a2431
--- /dev/null
+++ b/app/src/main/res/layout/bottom_container_location_picker.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/content_location_picker.xml b/app/src/main/res/layout/content_location_picker.xml
new file mode 100644
index 000000000..2f846759c
--- /dev/null
+++ b/app/src/main/res/layout/content_location_picker.xml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_media_detail.xml b/app/src/main/res/layout/fragment_media_detail.xml
index d4fb2d18e..ef691c300 100644
--- a/app/src/main/res/layout/fragment_media_detail.xml
+++ b/app/src/main/res/layout/fragment_media_detail.xml
@@ -249,6 +249,16 @@
android:drawablePadding="@dimen/tiny_gap"
android:drawableStart="?attr/iconMap24"
tools:text="Coordinates link" />
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index e893d9f9f..fcd15aed0 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -513,6 +513,13 @@ Upload your first media by tapping on the add button.
Could not add categories.
Update categories
+ Trying to update coordinates.
+ Coordinates update
+ Success
+ Coordinates %1$s are added.
+ Could not add coordinates.
+ Unable to get coordinates.
+
Share image via
You haven\'t made any contributions yet
Account created!
@@ -615,5 +622,11 @@ Upload your first media by tapping on the add button.
App user interface language
Removes a caption and description
Read more
+ Choose a location
+ Pan and zoom to adjust
+ Exit location picker
+ Select location
+ Location picker image view
+ Location picker_image_view_shadow