Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jan 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -83,6 +89,7 @@ public class ContributionsFragment
OnBackStackChangedListener,
LocationUpdateListener,
MediaDetailProvider,
SensorEventListener,
ICampaignsView, ContributionsContract.View, Callback{
@Inject @Named("default_preferences") JsonKvStore store;
@Inject NearbyController nearbyController;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -162,6 +173,8 @@ public void onCreate(@Nullable Bundle savedInstanceState) {
userName = getArguments().getString(KEY_USERNAME);
isUserProfile = true;
}
mSensorManager = (SensorManager) getActivity().getSystemService(SENSOR_SERVICE);
mLight = mSensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);
}

@Nullable
Expand Down Expand Up @@ -428,6 +441,7 @@ public void onPause() {
super.onPause();
locationManager.removeLocationListener(this);
locationManager.unregisterLocationManager();
mSensorManager.unregisterListener(this);
}

@Override
Expand Down Expand Up @@ -464,6 +478,7 @@ public void onResume() {
fetchCampaigns();
}
}
mSensorManager.registerListener(this, mLight, SensorManager.SENSOR_DELAY_UI);
}

private void checkPermissionsAndShowNearbyCardView() {
Expand Down Expand Up @@ -521,7 +536,8 @@ private void updateNearbyNotification(@Nullable NearbyController.NearbyPlacesInf
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);
Expand Down Expand Up @@ -793,6 +809,17 @@ public void onClick(View view) {
}
};

/**
* 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.
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -64,6 +65,7 @@ private void init() {
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);

Expand Down Expand Up @@ -111,10 +113,11 @@ private void succeeded() {
}

/**
* 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;
Expand All @@ -129,7 +132,7 @@ public void updateContent(Place place) {
notificationIcon.setVisibility(VISIBLE);
notificationTitle.setText(place.name);
notificationDistance.setText(place.distance);

notificationCompass.setRotation(direction);
}

@Override
Expand All @@ -151,6 +154,7 @@ protected void onVisibilityChanged(@NonNull View changedView, int visibility) {
notificationTitle.setVisibility(VISIBLE);
notificationDistance.setVisibility(VISIBLE);
notificationIcon.setVisibility(VISIBLE);
notificationCompass.setVisibility(VISIBLE);
break;
case LOADING:
permissionRequestButton.setVisibility(GONE);
Expand All @@ -160,6 +164,7 @@ protected void onVisibilityChanged(@NonNull View changedView, int visibility) {
notificationTitle.setVisibility(GONE);
notificationDistance.setVisibility(GONE);
notificationIcon.setVisibility(GONE);
notificationCompass.setVisibility(GONE);
permissionRequestButton.setVisibility(GONE);
break;
case ASK_PERMISSION:
Expand Down Expand Up @@ -192,4 +197,14 @@ public enum PermissionType {
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));
}
}
19 changes: 19 additions & 0 deletions app/src/main/java/fr/free/nrw/commons/utils/LengthUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -123,4 +123,23 @@ private static double hav(double x) {
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;
}
}
5 changes: 5 additions & 0 deletions app/src/main/res/drawable/baseline_arrow_upward_24.xml
Original file line number Diff line number Diff line change
@@ -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>
7 changes: 7 additions & 0 deletions app/src/main/res/layout/nearby_card_view.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
27 changes: 27 additions & 0 deletions app/src/test/kotlin/fr/free/nrw/commons/utils/LengthUtilsTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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) =
Expand All @@ -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)
}