Resolves #2239 by adding a compass arrow for direction of nearest item (#5433)

* Resolves issue #2239 by adding an arrow for direction

* Removed unnecessary change in styles.xml

* spacing

* javadoc

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
This commit is contained in:
Shashwat Kedia 2024-01-15 10:27:44 +05:30 committed by GitHub
parent e99ff1c044
commit e5c789e874
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 105 additions and 5 deletions

View file

@ -1,15 +1,21 @@
package fr.free.nrw.commons.contributions;
import static android.content.Context.SENSOR_SERVICE;
import static fr.free.nrw.commons.contributions.Contribution.STATE_FAILED;
import static fr.free.nrw.commons.contributions.Contribution.STATE_PAUSED;
import static fr.free.nrw.commons.nearby.fragments.NearbyParentFragment.WLM_URL;
import static fr.free.nrw.commons.profile.ProfileActivity.KEY_USERNAME;
import static fr.free.nrw.commons.utils.LengthUtils.computeBearing;
import static fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween;
import android.Manifest;
import android.Manifest.permission;
import android.annotation.SuppressLint;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
@ -83,6 +89,7 @@ public class ContributionsFragment
OnBackStackChangedListener,
LocationUpdateListener,
MediaDetailProvider,
SensorEventListener,
ICampaignsView, ContributionsContract.View, Callback{
@Inject @Named("default_preferences") JsonKvStore store;
@Inject NearbyController nearbyController;
@ -122,6 +129,10 @@ public class ContributionsFragment
String userName;
private boolean isUserProfile;
private SensorManager mSensorManager;
private Sensor mLight;
private float direction;
private ActivityResultLauncher<String[]> nearbyLocationPermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), new ActivityResultCallback<Map<String, Boolean>>() {
@Override
public void onActivityResult(Map<String, Boolean> result) {
@ -162,6 +173,8 @@ public class ContributionsFragment
userName = getArguments().getString(KEY_USERNAME);
isUserProfile = true;
}
mSensorManager = (SensorManager) getActivity().getSystemService(SENSOR_SERVICE);
mLight = mSensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);
}
@Nullable
@ -428,6 +441,7 @@ public class ContributionsFragment
super.onPause();
locationManager.removeLocationListener(this);
locationManager.unregisterLocationManager();
mSensorManager.unregisterListener(this);
}
@Override
@ -464,6 +478,7 @@ public class ContributionsFragment
fetchCampaigns();
}
}
mSensorManager.registerListener(this, mLight, SensorManager.SENSOR_DELAY_UI);
}
private void checkPermissionsAndShowNearbyCardView() {
@ -521,7 +536,8 @@ public class ContributionsFragment
Place closestNearbyPlace = nearbyPlacesInfo.placeList.get(0);
String distance = formatDistanceBetween(curLatLng, closestNearbyPlace.location);
closestNearbyPlace.setDistance(distance);
nearbyNotificationCardView.updateContent(closestNearbyPlace);
direction = (float) computeBearing(curLatLng, closestNearbyPlace.location);
nearbyNotificationCardView.updateContent(closestNearbyPlace, direction);
} else {
// Means that no close nearby place is found
nearbyNotificationCardView.setVisibility(View.GONE);
@ -793,6 +809,17 @@ public class ContributionsFragment
}
};
/**
* When the device rotates, rotate the Nearby banner's compass arrow in tandem.
*/
@Override
public void onSensorChanged(SensorEvent event) {
float rotateDegree = Math.round(event.values[0]);
nearbyNotificationCardView.rotateCompass(rotateDegree, direction);
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// Nothing to do.
}
}

View file

@ -27,6 +27,7 @@ public class NearbyNotificationCardView extends SwipableCardView {
private TextView notificationTitle;
private TextView notificationDistance;
private ImageView notificationIcon;
private ImageView notificationCompass;
private ProgressBar progressBar;
public CardViewVisibilityState cardViewVisibilityState;
@ -64,6 +65,7 @@ public class NearbyNotificationCardView extends SwipableCardView {
notificationDistance = rootView.findViewById(R.id.nearby_distance);
notificationIcon = rootView.findViewById(R.id.nearby_icon);
notificationCompass = rootView.findViewById(R.id.nearby_compass);
progressBar = rootView.findViewById(R.id.progressBar);
@ -111,10 +113,11 @@ public class NearbyNotificationCardView extends SwipableCardView {
}
/**
* Pass place information to views.
* Pass place information to views and set compass arrow direction
* @param place Closes place where we will get information from
* @param direction Direction in which compass arrow needs to be set
*/
public void updateContent(Place place) {
public void updateContent(Place place, float direction) {
Timber.d("Update nearby card notification content");
this.setVisibility(VISIBLE);
cardViewVisibilityState = CardViewVisibilityState.READY;
@ -129,7 +132,7 @@ public class NearbyNotificationCardView extends SwipableCardView {
notificationIcon.setVisibility(VISIBLE);
notificationTitle.setText(place.name);
notificationDistance.setText(place.distance);
notificationCompass.setRotation(direction);
}
@Override
@ -151,6 +154,7 @@ public class NearbyNotificationCardView extends SwipableCardView {
notificationTitle.setVisibility(VISIBLE);
notificationDistance.setVisibility(VISIBLE);
notificationIcon.setVisibility(VISIBLE);
notificationCompass.setVisibility(VISIBLE);
break;
case LOADING:
permissionRequestButton.setVisibility(GONE);
@ -160,6 +164,7 @@ public class NearbyNotificationCardView extends SwipableCardView {
notificationTitle.setVisibility(GONE);
notificationDistance.setVisibility(GONE);
notificationIcon.setVisibility(GONE);
notificationCompass.setVisibility(GONE);
permissionRequestButton.setVisibility(GONE);
break;
case ASK_PERMISSION:
@ -192,4 +197,14 @@ public class NearbyNotificationCardView extends SwipableCardView {
ENABLE_LOCATION_PERMISSION, // For only after Marshmallow
NO_PERMISSION_NEEDED
}
/**
* Rotates the compass arrow in tandem with the rotation of device
*
* @param rotateDegree Degree by which device was rotated
* @param direction Direction in which arrow has to point
*/
public void rotateCompass(float rotateDegree, float direction){
notificationCompass.setRotation(-(rotateDegree-direction));
}
}

View file

@ -123,4 +123,23 @@ public class LengthUtils {
double sinHalf = Math.sin(x * 0.5D);
return sinHalf * sinHalf;
}
/**
* Computes bearing between the two given points
*
* @see <a href="https://www.movable-type.co.uk/scripts/latlong.html">Bearing</a>
* @param point1 Coordinates of first point
* @param point2 Coordinates of second point
* @return Bearing between the two end points in degrees
* @throws NullPointerException if one or both the points are null
*/
public static double computeBearing(@NonNull LatLng point1, @NonNull LatLng point2) {
double diffLongitute = Math.toRadians(point2.getLongitude() - point1.getLongitude());
double lat1 = Math.toRadians(point1.getLatitude());
double lat2 = Math.toRadians(point2.getLatitude());
double y = Math.sin(diffLongitute) * Math.cos(lat2);
double x = Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2) * Math.cos(diffLongitute);
double bearing = Math.atan2(y, x);
return (Math.toDegrees(bearing) + 360) % 360;
}
}

View file

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="?attr/tabTextColor"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="?attr/tabTextColor" android:pathData="M4,12l1.41,1.41L11,7.83V20h2V7.83l5.58,5.59L20,12l-8,-8 -8,8z"/>
</vector>

View file

@ -93,6 +93,13 @@
</TextView>
<ImageView
android:id="@+id/nearby_compass"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginEnd="16dp"
app:srcCompat="@drawable/baseline_arrow_upward_24"/>
</LinearLayout>
</LinearLayout>

View file

@ -106,6 +106,29 @@ class LengthUtilsTest {
assertDistanceBetween(20015115.07, pointA, pointB)
}
// Test LengthUtils.formatDistanceBetween()
@Test
fun testBearingPoleToPole() {
val pointA = LatLng(90.0, 0.0, 0f)
val pointB = LatLng(-90.0, 0.0, 0f)
assertBearing(180.00, pointA, pointB)
}
@Test
fun testBearingRandomPoints() {
val pointA = LatLng(27.17, 78.04, 0f)
val pointB = LatLng(-40.69, 04.13, 0f)
assertBearing(227.46, pointA, pointB)
}
@Test
fun testBearingSamePlace() {
val pointA = LatLng(90.0, 0.0, 0f)
val pointB = LatLng(90.0, 0.0, 0f)
assertBearing(0.0, pointA, pointB)
}
// Test assertion helper functions
private fun assertFormattedDistanceBetween(expected: String, pointA: LatLng, pointB: LatLng) =
@ -117,4 +140,8 @@ class LengthUtilsTest {
private fun assertDistanceBetween(expected: Double, pointA: LatLng, pointB: LatLng) =
// Acceptable error: 1cm
assertEquals(expected, LengthUtils.computeDistanceBetween(pointA, pointB), 0.01)
private fun assertBearing(expected: Double, pointA: LatLng, pointB: LatLng) =
// Acceptable error: 1 degree
assertEquals(expected, LengthUtils.computeBearing(pointA,pointB), 1.0)
}