Skip to content

Commit 0a6257b

Browse files
Adds a 'Remove Location' button to the UploadWizard commons-app#5247 (commons-app#5672)
* Implemented basic flow to remove ___location * Fixed and added new tests and enhanced UX
1 parent 7dd00ef commit 0a6257b

File tree

8 files changed

+150
-21
lines changed

8 files changed

+150
-21
lines changed

app/src/main/java/fr/free/nrw/commons/LocationPicker/LocationPickerActivity.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import fr.free.nrw.commons.___location.LocationPermissionsHelper.LocationPermissionCallback;
4242
import fr.free.nrw.commons.___location.LocationServiceManager;
4343
import fr.free.nrw.commons.theme.BaseActivity;
44+
import fr.free.nrw.commons.utils.DialogUtil;
4445
import fr.free.nrw.commons.utils.SystemThemeUtils;
4546
import io.reactivex.android.schedulers.AndroidSchedulers;
4647
import io.reactivex.schedulers.Schedulers;
@@ -95,6 +96,10 @@ public class LocationPickerActivity extends BaseActivity implements
9596
* modifyLocationButton : button for start editing ___location
9697
*/
9798
Button modifyLocationButton;
99+
/**
100+
* removeLocationButton : button to remove ___location metadata
101+
*/
102+
Button removeLocationButton;
98103
/**
99104
* showInMapButton : button for showing in map
100105
*/
@@ -205,6 +210,7 @@ protected void onCreate(@Nullable final Bundle savedInstanceState) {
205210
if ("UploadActivity".equals(activity)) {
206211
placeSelectedButton.setVisibility(View.GONE);
207212
modifyLocationButton.setVisibility(View.VISIBLE);
213+
removeLocationButton.setVisibility(View.VISIBLE);
208214
showInMapButton.setVisibility(View.VISIBLE);
209215
largeToolbarText.setText(getResources().getString(R.string.image_location));
210216
smallToolbarText.setText(getResources().
@@ -257,6 +263,7 @@ private void bindViews() {
257263
markerImage = findViewById(R.id.location_picker_image_view_marker);
258264
tvAttribution = findViewById(R.id.tv_attribution);
259265
modifyLocationButton = findViewById(R.id.modify_location);
266+
removeLocationButton = findViewById(R.id.remove_location);
260267
showInMapButton = findViewById(R.id.show_in_map);
261268
showInMapButton.setText(getResources().getString(R.string.show_in_map_app).toUpperCase());
262269
shadow = findViewById(R.id.location_picker_image_view_shadow);
@@ -275,6 +282,7 @@ private void getToolbarUI() {
275282
private void setupMapView() {
276283
adjustCameraBasedOnOptions();
277284
modifyLocationButton.setOnClickListener(v -> onClickModifyLocation());
285+
removeLocationButton.setOnClickListener(v -> onClickRemoveLocation());
278286
showInMapButton.setOnClickListener(v -> showInMap());
279287
darkThemeSetup();
280288
requestLocationPermissions();
@@ -286,6 +294,7 @@ private void setupMapView() {
286294
private void onClickModifyLocation() {
287295
placeSelectedButton.setVisibility(View.VISIBLE);
288296
modifyLocationButton.setVisibility(View.GONE);
297+
removeLocationButton.setVisibility(View.GONE);
289298
showInMapButton.setVisibility(View.GONE);
290299
markerImage.setVisibility(View.VISIBLE);
291300
shadow.setVisibility(View.VISIBLE);
@@ -301,6 +310,35 @@ private void onClickModifyLocation() {
301310
}
302311
}
303312

313+
/**
314+
* Handles onclick event of removeLocationButton
315+
*/
316+
private void onClickRemoveLocation() {
317+
DialogUtil.showAlertDialog(this,
318+
getString(R.string.remove_location_warning_title),
319+
getString(R.string.remove_location_warning_desc),
320+
getString(R.string.continue_message),
321+
getString(R.string.cancel), () -> removeLocationFromImage(), null);
322+
}
323+
324+
/**
325+
* Method to remove the ___location from the picture
326+
*/
327+
private void removeLocationFromImage() {
328+
if (media != null) {
329+
compositeDisposable.add(coordinateEditHelper.makeCoordinatesEdit(getApplicationContext()
330+
, media, "0.0", "0.0", "0.0f")
331+
.subscribeOn(Schedulers.io())
332+
.observeOn(AndroidSchedulers.mainThread())
333+
.subscribe(s -> {
334+
Timber.d("Coordinates are removed from the image");
335+
}));
336+
}
337+
final Intent returningIntent = new Intent();
338+
setResult(AppCompatActivity.RESULT_OK, returningIntent);
339+
finish();
340+
}
341+
304342
/**
305343
* Show the ___location in map app
306344
*/

app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.java

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import android.widget.Toast;
2121
import androidx.annotation.NonNull;
2222
import androidx.annotation.Nullable;
23+
import androidx.exifinterface.media.ExifInterface;
2324
import androidx.recyclerview.widget.LinearLayoutManager;
2425
import fr.free.nrw.commons.CameraPosition;
2526
import fr.free.nrw.commons.LocationPicker.LocationPicker;
@@ -77,6 +78,11 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
7778

7879
public static final String UPLOAD_MEDIA_DETAILS = "upload_media_detail_adapter";
7980

81+
/**
82+
* True when user removes ___location from the current image
83+
*/
84+
private boolean hasUserRemovedLocation;
85+
8086

8187
private UploadMediaDetailAdapter uploadMediaDetailAdapter;
8288

@@ -295,7 +301,7 @@ public void onNextButtonClicked() {
295301
if (callback == null) {
296302
return;
297303
}
298-
presenter.displayLocDialog(indexOfFragment, inAppPictureLocation);
304+
presenter.displayLocDialog(indexOfFragment, inAppPictureLocation, hasUserRemovedLocation);
299305
}
300306

301307
public void onPreviousButtonClicked() {
@@ -634,14 +640,15 @@ public void onActivityResult(final int requestCode, final int resultCode,
634640
final double zoom = cameraPosition.getZoom();
635641

636642
editLocation(latitude, longitude, zoom);
637-
/*
638-
If isMissingLocationDialog is true, it means that the user has already tapped the
639-
"Next" button, so go directly to the next step.
640-
*/
643+
// If isMissingLocationDialog is true, it means that the user has already tapped the
644+
// "Next" button, so go directly to the next step.
641645
if (isMissingLocationDialog) {
642646
isMissingLocationDialog = false;
643647
onNextButtonClicked();
644648
}
649+
} else {
650+
// If camera position is null means ___location is removed by the user
651+
removeLocation();
645652
}
646653
}
647654
if (requestCode == REQUEST_CODE_FOR_EDIT_ACTIVITY && resultCode == RESULT_OK) {
@@ -673,6 +680,46 @@ else if (requestCode == REQUEST_CODE_FOR_VOICE_INPUT) {
673680
}
674681
}
675682

683+
/**
684+
* Removes the ___location data from the image, by setting them to null
685+
*/
686+
public void removeLocation() {
687+
editableUploadItem.getGpsCoords().setDecimalCoords(null);
688+
try {
689+
ExifInterface sourceExif = new ExifInterface(uploadableFile.getFilePath());
690+
String[] exifTags = {
691+
ExifInterface.TAG_GPS_LATITUDE,
692+
ExifInterface.TAG_GPS_LATITUDE_REF,
693+
ExifInterface.TAG_GPS_LONGITUDE,
694+
ExifInterface.TAG_GPS_LONGITUDE_REF,
695+
};
696+
697+
for (String tag : exifTags) {
698+
sourceExif.setAttribute(tag, null);
699+
}
700+
sourceExif.saveAttributes();
701+
702+
Drawable mapQuestion = getResources().getDrawable(R.drawable.ic_map_not_available_20dp);
703+
704+
if (binding != null) {
705+
binding.locationImageView.setImageDrawable(mapQuestion);
706+
binding.locationTextView.setText(R.string.add_location);
707+
}
708+
709+
editableUploadItem.getGpsCoords().setDecLatitude(0.0);
710+
editableUploadItem.getGpsCoords().setDecLongitude(0.0);
711+
editableUploadItem.getGpsCoords().setImageCoordsExists(false);
712+
hasUserRemovedLocation = true;
713+
714+
Toast.makeText(getContext(), getString(R.string.location_removed), Toast.LENGTH_LONG)
715+
.show();
716+
} catch (Exception e) {
717+
Timber.d(e);
718+
Toast.makeText(getContext(), "Location could not be removed due to internal error",
719+
Toast.LENGTH_LONG).show();
720+
}
721+
}
722+
676723
/**
677724
* Update the old coordinates with new one
678725
* @param latitude new latitude
@@ -694,7 +741,7 @@ public void editLocation(final String latitude, final String longitude, final do
694741
binding.locationTextView.setText(R.string.edit_location);
695742
}
696743

697-
Toast.makeText(getContext(), "Location Updated", Toast.LENGTH_LONG).show();
744+
Toast.makeText(getContext(), getString(R.string.location_updated), Toast.LENGTH_LONG).show();
698745

699746
}
700747

app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailsContract.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,16 @@ interface UserActionListener extends BasePresenter<View> {
7171
boolean getImageQuality(int uploadItemIndex, LatLng inAppPictureLocation, Activity activity);
7272

7373
/**
74-
* Checks if the image has a ___location or not. Displays a dialog alerting user that no
75-
* ___location has been to added to the image and asking them to add one
74+
* Checks if the image has a ___location. Displays a dialog alerting user that no ___location has
75+
* been to added to the image and asking them to add one, if ___location was not removed by the
76+
* user
7677
*
7778
* @param uploadItemIndex Index of the uploadItem which has no ___location
7879
* @param inAppPictureLocation In app picture ___location (if any)
80+
* @param hasUserRemovedLocation True if user has removed ___location from the image
7981
*/
80-
void displayLocDialog(int uploadItemIndex, LatLng inAppPictureLocation);
82+
void displayLocDialog(int uploadItemIndex, LatLng inAppPictureLocation,
83+
boolean hasUserRemovedLocation);
8184

8285
/**
8386
* Used to check image quality from stored qualities and display dialogs

app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaPresenter.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -202,17 +202,21 @@ private void checkNearbyPlaces(final UploadItem uploadItem) {
202202
}
203203

204204
/**
205-
* Checks if the image has a ___location or not. Displays a dialog alerting user that no
206-
* ___location has been to added to the image and asking them to add one
205+
* Checks if the image has a ___location. Displays a dialog alerting user that no
206+
* ___location has been to added to the image and asking them to add one, if ___location was not
207+
* removed by the user
207208
*
208209
* @param uploadItemIndex Index of the uploadItem which has no ___location
209210
* @param inAppPictureLocation In app picture ___location (if any)
211+
* @param hasUserRemovedLocation True if user has removed ___location from the image
210212
*/
211213
@Override
212-
public void displayLocDialog(int uploadItemIndex, LatLng inAppPictureLocation) {
214+
public void displayLocDialog(int uploadItemIndex, LatLng inAppPictureLocation,
215+
boolean hasUserRemovedLocation) {
213216
final List<UploadItem> uploadItems = repository.getUploads();
214217
UploadItem uploadItem = uploadItems.get(uploadItemIndex);
215-
if (uploadItem.getGpsCoords().getDecimalCoords() == null && inAppPictureLocation == null) {
218+
if (uploadItem.getGpsCoords().getDecimalCoords() == null && inAppPictureLocation == null
219+
&& !hasUserRemovedLocation) {
216220
final Runnable onSkipClicked = () -> {
217221
verifyCaptionQuality(uploadItem);
218222
};

app/src/main/res/layout/bottom_container_location_picker.xml

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,28 @@
5454
app:layout_constraintEnd_toEndOf="parent"
5555
app:layout_constraintStart_toStartOf="parent">
5656

57+
<Button
58+
android:id="@+id/remove_location"
59+
style="@style/Widget.AppCompat.Button"
60+
android:layout_width="0dp"
61+
android:layout_height="wrap_content"
62+
android:layout_margin="5dp"
63+
android:backgroundTint="@color/deleteRed"
64+
android:text="@string/remove_location"
65+
android:textColor="@color/white"
66+
android:visibility="gone"
67+
app:layout_constraintBottom_toBottomOf="@id/map_bottom_layout"
68+
app:layout_constraintEnd_toStartOf="@+id/guideline2"
69+
app:layout_constraintStart_toStartOf="@id/map_bottom_layout"
70+
app:layout_constraintTop_toTopOf="@id/map_bottom_layout" />
71+
72+
<androidx.constraintlayout.widget.Guideline
73+
android:id="@+id/guideline2"
74+
android:layout_width="wrap_content"
75+
android:layout_height="wrap_content"
76+
android:orientation="vertical"
77+
app:layout_constraintGuide_percent="0.35" />
78+
5779
<Button
5880
android:id="@+id/modify_location"
5981
android:layout_width="0dp"
@@ -64,15 +86,15 @@
6486
android:layout_margin="5dp"
6587
app:layout_constraintBottom_toBottomOf="@id/map_bottom_layout"
6688
app:layout_constraintEnd_toStartOf="@+id/guideline3"
67-
app:layout_constraintStart_toStartOf="@id/map_bottom_layout"
89+
app:layout_constraintStart_toEndOf="@+id/guideline2"
6890
app:layout_constraintTop_toTopOf="@id/map_bottom_layout" />
6991

7092
<androidx.constraintlayout.widget.Guideline
7193
android:id="@+id/guideline3"
7294
android:layout_width="wrap_content"
7395
android:layout_height="wrap_content"
7496
android:orientation="vertical"
75-
app:layout_constraintGuide_percent="0.5" />
97+
app:layout_constraintGuide_percent="0.7" />
7698

7799
<TextView
78100
android:id="@+id/show_in_map"

app/src/main/res/values/strings.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -790,6 +790,11 @@ Upload your first media by tapping on the add button.</string>
790790
<string name="see_your_achievements">See your achievements</string>
791791
<string name="edit_image">Edit Image</string>
792792
<string name="edit_location">Edit Location</string>
793+
<string name="location_updated">Location updated!</string>
794+
<string name="remove_location">Remove Location</string>
795+
<string name="remove_location_warning_title">Remove Location Warning</string>
796+
<string name="remove_location_warning_desc">Location makes pictures more useful and findable. Do you really wish to remove ___location from this picture?</string>
797+
<string name="location_removed">Location removed!</string>
793798
<string name="send_thanks_to_author">Thank the author</string>
794799
<string name="error_sending_thanks">Error sending thanks to author.</string>
795800
<string name="invalid_login_message">Your log-in has expired. Please log in again.</string>

app/src/test/kotlin/fr/free/nrw/commons/locationpicker/LocationPickerActivityUnitTests.kt

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package fr.free.nrw.commons.locationpicker
22

33
import android.content.Context
44
import android.os.Looper
5-
import android.util.Log
65
import android.view.View
76
import android.widget.Button
87
import android.widget.ImageView
@@ -11,10 +10,8 @@ import androidx.appcompat.widget.AppCompatTextView
1110
import com.google.android.material.floatingactionbutton.FloatingActionButton
1211
import com.nhaarman.mockitokotlin2.times
1312
import com.nhaarman.mockitokotlin2.verify
14-
import com.nhaarman.mockitokotlin2.whenever
1513
import fr.free.nrw.commons.CameraPosition
1614
import fr.free.nrw.commons.LocationPicker.LocationPickerActivity
17-
import fr.free.nrw.commons.Media
1815
import fr.free.nrw.commons.TestCommonsApplication
1916
import fr.free.nrw.commons.kvstore.JsonKvStore
2017
import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.LAST_LOCATION
@@ -29,7 +26,6 @@ import org.junit.runner.RunWith
2926
import org.mockito.Mock
3027
import org.mockito.Mockito.*
3128
import org.mockito.MockitoAnnotations
32-
import org.osmdroid.api.IMapController
3329
import org.osmdroid.util.GeoPoint
3430
import org.powermock.reflect.Whitebox
3531
import org.robolectric.Robolectric
@@ -38,7 +34,6 @@ import org.robolectric.RuntimeEnvironment
3834
import org.robolectric.Shadows
3935
import org.robolectric.annotation.Config
4036
import org.robolectric.annotation.LooperMode
41-
import java.lang.reflect.InvocationTargetException
4237
import java.lang.reflect.Method
4338

4439
@RunWith(RobolectricTestRunner::class)
@@ -61,6 +56,9 @@ class LocationPickerActivityUnitTests {
6156
@Mock
6257
private lateinit var modifyLocationButton: Button
6358

59+
@Mock
60+
private lateinit var removeLocationButton: Button
61+
6462
@Mock
6563
private lateinit var placeSelectedButton: FloatingActionButton
6664

@@ -96,6 +94,7 @@ class LocationPickerActivityUnitTests {
9694
Whitebox.setInternalState(activity, "applicationKvStore", applicationKvStore)
9795
Whitebox.setInternalState(activity, "cameraPosition", cameraPosition)
9896
Whitebox.setInternalState(activity, "modifyLocationButton", modifyLocationButton)
97+
Whitebox.setInternalState(activity, "removeLocationButton", removeLocationButton)
9998
Whitebox.setInternalState(activity, "placeSelectedButton", placeSelectedButton)
10099
Whitebox.setInternalState(activity, "showInMapButton", showInMapButton)
101100
Whitebox.setInternalState(activity, "markerImage", markerImage)
@@ -134,6 +133,7 @@ class LocationPickerActivityUnitTests {
134133
method.invoke(activity)
135134
verify(placeSelectedButton, times(1)).visibility = View.VISIBLE
136135
verify(modifyLocationButton, times(1)).visibility = View.GONE
136+
verify(removeLocationButton, times(1)).visibility = View.GONE
137137
verify(showInMapButton, times(1)).visibility = View.GONE
138138
verify(markerImage, times(1)).visibility = View.VISIBLE
139139
verify(shadow, times(1)).visibility = View.VISIBLE
@@ -142,6 +142,16 @@ class LocationPickerActivityUnitTests {
142142
verify(fabCenterOnLocation, times(1)).visibility = View.VISIBLE
143143
}
144144

145+
@Test
146+
@Throws(Exception::class)
147+
fun testOnClickRemoveLocation() {
148+
val method: Method = LocationPickerActivity::class.java.getDeclaredMethod(
149+
"onClickRemoveLocation"
150+
)
151+
method.isAccessible = true
152+
method.invoke(activity)
153+
}
154+
145155
@Test
146156
@Throws(Exception::class)
147157
fun testPlaceSelected() {

app/src/test/kotlin/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragmentUnitTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ class UploadMediaDetailFragmentUnitTest {
382382
`when`(latLng.longitude).thenReturn(0.0)
383383
`when`(uploadItem.gpsCoords).thenReturn(imageCoordinates)
384384
fragment.onActivityResult(1211, Activity.RESULT_OK, intent)
385-
Mockito.verify(presenter, Mockito.times(1)).displayLocDialog(0, null)
385+
Mockito.verify(presenter, Mockito.times(1)).displayLocDialog(0, null, false)
386386
}
387387

388388
@Test

0 commit comments

Comments
 (0)