Skip to content

Conversation

@jakevis
Copy link

@jakevis jakevis commented Dec 3, 2025

This ports the “Compass view” from Meshtastic-Apple PR pull/1504 (thanks @RCGV1) to Android. It keeps the phone’s location as origin and guides you to the selected node via a modal bottom sheet on Node Detail → Position → “Open Compass.”

What changed

  • Add localized strings for compass title/action, distance/bearing display, and sensor/permission/location warnings.
  • New providers: CompassHeadingProvider (rotation vector with accel+mag fallback) and PhoneLocationProvider (LocationManager flow with permission/provider state).
  • CompassViewModel/CompassUiState combine heading + phone location with the target node, computing distance, bearing, alignment, and elapsed “last position update.”
  • Compose CompassSheetContent: dial ~2/3 width, red north reference, black target bearing, larger cardinal labels + quarter/eighth ticks, distance/bearing rows, elapsed time, and warnings with CTAs (request permission, open location settings, request position).
  • Node Detail integration: Position section gets “Open Compass”; sheet starts/stops providers with visibility, caches target node, and includes “Request Position” inside the sheet.

Why
Per the iOS PR: adds a compass view to quickly navigate to a node—useful off-grid when locating a friend, dog, or tracker.

Testing

  • Built and ran on Android emulator (API 35); opened Node Detail → Position → Open Compass; verified sheet renders and warnings appear when sensors/permissions are missing. (No automated tests run in this PR.)

@CLAassistant
Copy link

CLAassistant commented Dec 3, 2025

CLA assistant check
All committers have signed the CLA.

@CLAassistant
Copy link

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@jakevis
Copy link
Author

jakevis commented Dec 3, 2025

Screenshots of the changes:

Screenshot_1764802949 Screenshot_1764802956 Screenshot_1764802954

@DaneEvans
Copy link
Collaborator

Why lines not a compass rose? And why are there 3 lines for a single bearing?

@jakevis
Copy link
Author

jakevis commented Dec 4, 2025

Why lines not a compass rose? And why are there 3 lines for a single bearing?

Personal preference and re-using some old code I had from another project. Red is fixed line on the N marker; the thin under it is a heading line. Align the black lines and your walking directly to the node (accessibility feature mostly from the old project).

Orignal code and design I was playing with had support for multiple nodes on the rose (and that might a future PR - with a rose accessible from somewhere else). Totem Compass style - show where you stared nodes are both distance and direction from you.

For the moment; happy to drop the heading and red lines if folks prefer.

@mdecourcy
Copy link
Collaborator

Should there perhaps be a message displayed when the target node has a high degree of precision bits? So we don't send the users on a wild goosechase

@jakevis
Copy link
Author

jakevis commented Dec 4, 2025

Should there perhaps be a message displayed when the target node has a high degree of precision bits? So we don't send the users on a wild goosechase

Let me see how hard it would be to tell the user the degree of error on the screen.. if its communicated somewhere..

@jakevis
Copy link
Author

jakevis commented Dec 4, 2025

@DaneEvans @mdecourcy - ok simplified the rose and did implement an accuracy cone-

  • Data source: We use gps_accuracy (mm) and DOP (PDOP, or HDOP/VDOP fallback) from the node’s Position. If these are missing, uncertainty is “unknown.”
  • Visual: A translucent blue wedge around the bearing shows ±angular error (error radius vs. your distance). Close range + poor accuracy = wide wedge; good accuracy or long distance = narrow wedge.
  • Messaging (beneath the dial, next to distance/bearing):
    • Known: “Estimated area: ±X (±Y°)” — rendered from compass_uncertainty.
    • Unknown: “Estimated area: unknown accuracy” — rendered from compass_uncertainty_unknown.

I need to test this on some more physical devices though- only tested this in the emulator right now- but would love feedback.

Screenshot_1764870764

@jakevis jakevis marked this pull request as draft December 4, 2025 22:16
@jakevis
Copy link
Author

jakevis commented Dec 4, 2025

Converting to draft pending physical device testing of the error cone.

@RCGV1
Copy link
Member

RCGV1 commented Dec 7, 2025

@jakevis I made a pr into your fork repo to improve the view of the compass.

@jakevis
Copy link
Author

jakevis commented Dec 7, 2025

@jakevis I made a pr into your fork repo to improve the view of the compass.

Looks amazing! Thank you!

@jamesarich
Copy link
Collaborator

How y'all feeling about this, @jakevis @RCGV1 ?

Should we let copilot do a review pass?

@RCGV1
Copy link
Member

RCGV1 commented Dec 7, 2025

The only thing I have not fully tested is the uncertainty feature, also I did not figure out how to localize distance since right now it only shows km

@jakevis
Copy link
Author

jakevis commented Dec 8, 2025

I have a todo item tomorrow to test this on a couple physical devices now I'm back from the weekend. I want to check the uncertainty cone some more- it's a tad hard to do in the emulator.

I have ran codex-max over it before I pushed to my branch; but yes - I would certainly say copilot should triple check 😀 - I can ask codex to take a pass at the localization as well.

- fix compass cone math by drawing in canvas space (0°=east) without double heading adjustment so wedge/dot rotate with device heading
- normalize cone start angle to prevent wraparound artifacts and keep shading aligned
- rebuild compass shading (filled wedge + edges) using marker color for consistent visual cues
- include angle normalization helper for safer bearing math
Copy link
Author

@jakevis jakevis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok a few things tested and everything seems to be functional.

I just pushed a fix for the cone alignment (wedge now tracks heading and target dot), and some changes around when to show time since update and accuracy. Localization of miles vs km also seems to function fine (based on your app settings).

Happy for copilot to have at it and see how we go.

@jakevis jakevis marked this pull request as ready for review December 9, 2025 21:36
@jakevis
Copy link
Author

jakevis commented Dec 9, 2025

4E68B13F-F422-4D48-B9AD-132394896253_1_102_o
EAEAD6AD-A1CF-4AA7-9E30-6A827387F94C_1_102_o

@jamesarich jamesarich requested a review from Copilot December 9, 2025 22:31
@jamesarich
Copy link
Collaborator

@jakevis you'll need to run ./gradlew spotlessApply detekt and clean up any errors

@jakevis
Copy link
Author

jakevis commented Dec 9, 2025

🤦‍♂️ Will do, sorry about that.

@jamesarich
Copy link
Collaborator

🤦‍♂️ Will do, sorry about that.

no worries, it's a common gotcha most new contribs miss - so much so that we thought about scripting an action to remind folks when detekt or spotless fail on ci 😂

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR ports the compass view feature from Meshtastic-Apple to Android, enabling users to navigate to mesh nodes using a visual compass interface with distance and bearing information. The implementation provides a modal bottom sheet accessible from the Node Detail screen's Position section.

Key Changes:

  • New compass providers for device heading (sensors) and phone location (GPS/Network)
  • CompassViewModel that combines heading, location, and target node data to compute bearing, distance, and alignment
  • Compose-based UI with rotating compass dial, cardinal markers, and uncertainty cone visualization
  • Integration with Node Detail screen via modal bottom sheet with proper permission handling

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
NodeDetailAction.kt Adds OpenCompass action to open compass sheet with target node and display units
NodeDetailScreen.kt Adds action handler stub for compass (actual handling in NodeDetailList)
NodeDetailList.kt Integrates compass sheet with lifecycle management, permission launchers, and ViewModel
PositionSection.kt Adds "Open Compass" button when node has valid position
CompassBottomSheet.kt Implements compass UI with rotating dial, warnings, distance/bearing display, and uncertainty visualization
PhoneLocationProvider.kt Provides location updates via LocationManager with permission/provider state tracking
CompassHeadingProvider.kt Provides device heading from rotation vector or accelerometer+magnetometer sensors
CompassViewModel.kt Coordinates heading and location streams, computes bearing/distance, manages state
CompassUiState.kt Defines render-ready state and warning enum for compass UI
strings.xml Adds localized strings for compass title, actions, distances, bearings, and warnings

*
* @param location Optional Location from the phone's location provider.
*/
fun headingUpdates(location: Location? = null): Flow<HeadingState> = callbackFlow {
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The headingUpdates function accepts a location parameter but never uses it because it's captured once at function creation time. However, the location needs to be continuously updated for accurate true north correction as the phone moves. Consider either removing the parameter and obtaining location dynamically, or restructuring to accept a Flow<Location?> that can be combined with sensor updates.

Copilot uses AI. Check for mistakes.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had codex implement a suggested fix (on next push)

}
}

LaunchedEffect(showCompassSheet) { if (!showCompassSheet) compassViewModel?.stop() }
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The LaunchedEffect that calls compassViewModel?.stop() should be placed inside the if (showCompassSheet && compassViewModel != null) block to ensure it's properly tied to the sheet's lifecycle. Currently, it's outside the conditional, which means the effect will run even when the sheet is never shown. Additionally, calling stop() when the sheet is dismissed should happen in the onDismiss callback to ensure immediate cleanup, not in LaunchedEffect which may have a delay.

Copilot uses AI. Check for mistakes.
@jamesarich jamesarich added this to the 2.7.10 milestone Dec 10, 2025
jakevis and others added 5 commits December 11, 2025 22:08
@jakevis
Copy link
Author

jakevis commented Dec 12, 2025

@jakevis you'll need to run ./gradlew spotlessApply detekt and clean up any errors

Ok - we should be set here; and I think all the copilot fixes are implemented

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants