diff --git a/CLAUDE.md b/CLAUDE.md index d3b0cad0..6b706653 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -41,7 +41,7 @@ lib/ ├── models/ # Plain data classes (Contact, Channel, Message, Community, …) ├── services/ # ChangeNotifier services + IO services (retry, translation, ML, …) ├── storage/ # SharedPreferences-backed stores, scoped per device key -├── helpers/ # Pure utilities (Smaz compression, GIF parsing, scroll helpers) +├── helpers/ # Pure utilities (Smaz compression, GIF parsing, scroll helpers, path hop resolution) ├── utils/ # Platform / IO / UX utilities (logger, GPX export, dialogs) ├── theme/ # MeshPalette (defined, not yet wired in main.dart) ├── l10n/ # ARB localization for 18 locales @@ -194,14 +194,14 @@ enum MeshCoreConnectionState { ## Dependencies -App version: `8.0.0+11` — Dart SDK constraint: `^3.9.2` +App version: `9.5.0+13` — Dart SDK constraint: `^3.9.2` **Connectivity** | Package | Version | Purpose | |---------|---------|---------| | flutter_blue_plus | ^2.1.0 | BLE scanning, connecting, and UART data transfer | -| flutter_blue_plus_platform_interface | ^8.2.1 | Platform-interface layer required by flutter_blue_plus | +| flutter_blue_plus_platform_interface | ^9.0.2 | Platform-interface layer required by flutter_blue_plus | | flserial | git (MeshEnvy fork) | USB serial transport for wired device connections (TODO: upstream pending) | **State / Storage** @@ -232,7 +232,7 @@ App version: `8.0.0+11` — Dart SDK constraint: `^3.9.2` | Package | Version | Purpose | |---------|---------|---------| -| material_symbols_icons | ^4.2906.0 | Extended Material Symbols icon set (line-of-sight, etc.) | +| material_symbols_icons | ^4.2928.1 | Extended Material Symbols icon set (line-of-sight, etc.) | | flutter_svg | ^2.0.10+1 | Renders SVG assets (custom icons such as LoS indicator) | | cached_network_image | ^3.4.1 | Caches map tile images downloaded over the network | | flutter_cache_manager | ^3.4.1 | Underlying cache manager used by cached_network_image | @@ -246,7 +246,7 @@ App version: `8.0.0+11` — Dart SDK constraint: `^3.9.2` | Package | Version | Purpose | |---------|---------|---------| -| flutter_local_notifications | ^20.1.0 | Shows local push notifications for incoming messages | +| flutter_local_notifications | ^22.0.0 | Shows local push notifications for incoming messages | | flutter_foreground_task | ^9.2.0 | Keeps the app alive in background to maintain BLE/USB connection | **ML / AI** @@ -255,7 +255,8 @@ App version: `8.0.0+11` — Dart SDK constraint: `^3.9.2` |---------|---------|---------| | ml_algo | ^16.0.0 | OLS regression used in `timeout_prediction_service.dart` to predict message ACK timeouts | | ml_dataframe | ^1.0.0 | DataFrame input format required by ml_algo | -| llamadart | >=0.6.8 <0.7.0 | On-device LLM inference used in `translation_service.dart` for message translation | +| llamadart | ^0.8.0 | On-device LLM inference used in `translation_service.dart` for message translation | +| flutter_langdetect | ^0.0.1 | Detects a message's source language in `translation_service.dart` before translating | **Misc** @@ -263,8 +264,8 @@ App version: `8.0.0+11` — Dart SDK constraint: `^3.9.2` |---------|---------|---------| | http | ^1.2.0 | Fetches tile URLs and any remote API calls | | url_launcher | ^6.3.0 | Opens URLs in the system browser from linkified chat text | -| share_plus | ^12.0.1 | Shares files (e.g. exported GPX tracks) via the system share sheet | -| package_info_plus | ^9.0.0 | Reads app version/build number displayed in settings | +| share_plus | ^13.1.0 | Shares files (e.g. exported GPX tracks) via the system share sheet | +| package_info_plus | ^10.1.0 | Reads app version/build number displayed in settings | | web | ^1.1.1 | Web-platform APIs for USB serial and browser detection on Flutter Web | | intl | any | Internationalization and locale formatting (required by flutter_localizations) | | build_pipe | ^0.3.1 | CI/CD build pipeline configuration (web release builds with versioned assets) | @@ -333,4 +334,4 @@ PWA scaffold present but boilerplate (`manifest.json` and `index.html` are unmod | `lib/services/translation_service.dart` | On-device LLM translation (llamadart) | | `lib/storage/prefs_manager.dart` | SharedPreferences singleton initialized in `main()` | | `lib/screens/scanner_screen.dart` | Home screen — BLE scan and connect | -| `pubspec.yaml` | Dependencies and project metadata (current version `8.0.0+11`) | +| `pubspec.yaml` | Dependencies and project metadata (current version `9.5.0+13`) | diff --git a/README.md b/README.md index 041ea6e9..acf1215a 100644 --- a/README.md +++ b/README.md @@ -94,12 +94,12 @@ MeshCore Open is a cross-platform mobile application for communicating with Mesh |---------|---------| | flutter_blue_plus | Bluetooth Low Energy communication | | provider | State management | -| sqflite | Local database storage | +| shared_preferences | Local key-value storage (scoped per device) | | flutter_map | Interactive map display | | latlong2 | Geographic coordinate handling | | flutter_local_notifications | Background notification support | -| smaz | Message compression | | pointycastle | Cryptographic operations | +| llamadart | On-device LLM message translation | | intl | Internationalization and date formatting | ## Getting Started diff --git a/android/gradle.properties b/android/gradle.properties index f018a618..475a6280 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,3 +1,7 @@ org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true +# This builtInKotlin flag was added automatically by Flutter migrator +android.builtInKotlin=false +# This newDsl flag was added automatically by Flutter migrator +android.newDsl=false diff --git a/documentation/additional-features.md b/documentation/additional-features.md index 5dfd2686..997c2c11 100644 --- a/documentation/additional-features.md +++ b/documentation/additional-features.md @@ -198,6 +198,7 @@ Tap the translate button on any received message. On first use, the GGUF model f ### How It Works - Model files are managed by `TranslationFileStore`; download progress is shown in-place +- Before translating, the source language is automatically detected using the `flutter_langdetect` package. If the detected language already matches the target language, translation is skipped - Translation runs via `TranslationService` using the llamadart CPU backend (arm64 and x64 on Android) - Translated text is shown in `TranslatedMessageContent` as an inline overlay on the original message bubble - Each translation is cached; re-tapping shows the cached result without re-running inference diff --git a/documentation/ble-protocol.md b/documentation/ble-protocol.md index f9d2acb3..9e72b459 100644 --- a/documentation/ble-protocol.md +++ b/documentation/ble-protocol.md @@ -56,6 +56,7 @@ enum MeshCoreConnectionState { - `Lilygo` - `HT-` - `LowMesh_MC_` + - `NRF52` 2. **Connect** with 15-second timeout (6 seconds on Linux) 3. **Request MTU** 185 bytes (non-web only) 4. **Discover services** and locate NUS @@ -115,13 +116,15 @@ On unexpected disconnection, auto-reconnect with exponential backoff: | 32 | CMD_SET_CHANNEL | Set channel name and PSK | | 36 | CMD_SEND_TRACE_PATH | Request path trace | | 38 | CMD_SET_OTHER_PARAMS | Set misc parameters | -| 39 | CMD_GET_TELEMETRY_REQ | Request sensor telemetry | +| 39 | CMD_SEND_TELEMETRY_REQ | Request sensor telemetry | | 40 | CMD_GET_CUSTOM_VAR | Get custom variables | | 41 | CMD_SET_CUSTOM_VAR | Set a custom variable | | 50 | CMD_SEND_BINARY_REQ | Send binary request | +| 56 | CMD_GET_STATS | Request companion radio stats | | 57 | CMD_SEND_ANON_REQ | Send anonymous request | | 58 | CMD_SET_AUTO_ADD_CONFIG | Set auto-add configuration | | 59 | CMD_GET_AUTO_ADD_CONFIG | Get auto-add configuration | +| 61 | CMD_SET_PATH_HASH_MODE | Set path hash width (bytes per hop) | ## Response / Push Codes (Device → App) @@ -145,6 +148,7 @@ On unexpected disconnection, auto-reconnect with exponential backoff: | 17 | RESP_CODE_CHANNEL_MSG_RECV_V3 | Incoming channel message (v3) | | 18 | RESP_CODE_CHANNEL_INFO | Channel definition | | 21 | RESP_CODE_CUSTOM_VARS | Custom variables | +| 24 | RESP_CODE_STATS | Companion radio stats | | 25 | RESP_CODE_AUTO_ADD_CONFIG | Auto-add flags | | 0x80 | PUSH_CODE_ADVERT | Known contact re-seen | | 0x81 | PUSH_CODE_PATH_UPDATED | Better path found; carries the 32-byte public key of the updated contact | diff --git a/documentation/channels.md b/documentation/channels.md index 49db176a..ff915882 100644 --- a/documentation/channels.md +++ b/documentation/channels.md @@ -4,7 +4,7 @@ Channels are broadcast group-chat spaces secured by a 16-byte pre-shared key (PSK). Any device with the same channel index and PSK will receive and decrypt channel messages. Unlike direct messages, channel messages are broadcast to the entire mesh. -Up to 8 channels (indices 0–7) can be active simultaneously on one device. +The number of active channels is determined by the firmware (default 40); the device reports its actual limit at login. ## How to Access @@ -17,7 +17,7 @@ QuickSwitchBar tab 1 (middle) from any main screen. | Public | Globe | Green | Fixed well-known PSK; any device can join | | Hashtag | Hash tag | Blue | PSK derived from the hashtag name via SHA-256; discoverable by convention | | Private | Lock | Blue | Random PSK; requires out-of-band sharing of the 32-hex key | -| Community | Groups/Tag | Purple | PSK derived via HMAC-SHA256 from a community's shared secret | +| Community | Groups/Tag | Magenta | PSK derived via HMAC-SHA256 from a community's shared secret | ## Channels List Screen @@ -26,12 +26,12 @@ QuickSwitchBar tab 1 (middle) from any main screen. - **Search bar** with live text filtering (300ms debounce) - **Sort/filter button** - **Scrollable list of channel cards**, each showing: - - Type icon with color coding (purple badge overlay for community channels) + - Type icon with color coding (magenta badge overlay for community channels) - Channel name (or "Channel N" if unnamed) - Unread badge (if messages are unread) - Drag handle (when manual sort is active) - **"+" FAB** to add a new channel -- **Overflow menu**: Disconnect, Manage Communities (only shown when at least one community exists), Settings +- **Overflow menu**: Disconnect, Manage Communities, Settings If no channels exist, an empty state with an "Add Public Channel" shortcut is shown. If a search produces no results, a separate "no results" empty state with a search-off icon is shown. @@ -59,7 +59,7 @@ Tap the "+" FAB to open a dialog with six options: | Action | Description | |---|---| -| Edit | Change name, PSK (with a dice icon to generate a random PSK), or SMAZ compression toggle (compresses outgoing messages to allow longer text within the byte limit) | +| Edit | Change name, PSK (with a dice icon to generate a random PSK), SMAZ compression toggle (compresses outgoing messages to allow longer text within the byte limit), or Cyr2Lat encoding toggle (transliterates Cyrillic to Latin for compatibility) | | Mute / Unmute | Toggle push notification suppression for this channel | | Delete | Remove the channel from the device (confirmation required) | @@ -100,8 +100,7 @@ Tap a channel card to open the channel chat screen. ### Message Path Viewing -- **Mobile**: Tap a message bubble to view its routing path -- **Desktop**: Long-press/right-click → "Path" (tapping the bubble does nothing on desktop) +- **All platforms**: Long-press (or right-click on desktop) a message bubble → "Path" - Opens the Channel Message Path Screen (see [Additional Features](additional-features.md)) ### Context Actions (Long-Press / Right-Click) @@ -109,7 +108,7 @@ Tap a channel card to open the channel chat screen. | Action | Availability | Description | |---|---|---| | Reply | All messages | Triggers reply mode | -| Path | Desktop only | Opens message path view | +| Path | All messages | Opens message path view | | Add Reaction | Incoming messages only | Opens emoji picker (cannot react to your own messages) | | Copy | All messages | Copies text to clipboard | | Mark as Unread | Incoming messages only | Marks this message and all subsequent incoming messages as unread | @@ -142,7 +141,7 @@ From the channels screen overflow menu → "Manage Communities". Opens a draggab - **Tap a community** to directly show its QR code for sharing - **Popup menu** per community: - **Show QR** — displays the QR code for sharing with new members - - **Delete** — removes the community locally and deletes all associated device channels (confirmation dialog warns how many channels will be removed) + - **Leave Community** — removes the community locally and deletes all associated device channels (confirmation dialog warns how many channels will be removed) ## How Channels Differ from Direct Messages diff --git a/documentation/chat-and-messaging.md b/documentation/chat-and-messaging.md index baf2ab8d..0905326d 100644 --- a/documentation/chat-and-messaging.md +++ b/documentation/chat-and-messaging.md @@ -18,17 +18,15 @@ From the Contacts screen, tap any Chat-type contact to open the ChatScreen. - **Title**: Contact name - **Subtitle**: Current routing path label (e.g., "2 hops", "flood (auto)", "direct (forced)") and unread count. Tapping the subtitle shows the full path details. -- **Action buttons**: - - **Routing mode** (waves icon): Switch between Auto, Direct, and Flood routing - - **Path management** (timeline icon): View recent paths with hop count, round-trip time, age, and success count. Paths are color-coded by direct repeater (green/yellow/red/blue for ranked repeaters, grey for unknown). Tap a path to activate it (the device verifies and confirms via snackbar), long-press to view full path details, set custom paths, or force flood mode. A warning banner appears when history reaches 100 entries. - - **Info** (info icon): Contact info dialog showing type, path, GPS coordinates, public key, and SMAZ compression toggle +- **Action button**: + - **Overflow menu** (⋮ icon): Contains Routing, Info, Telemetry, Settings, and Clear Chat. Routing opens the routing sheet where you can switch between Auto, Direct, and Flood routing and manage recent paths (hop count, round-trip time, age, success count, color-coded by repeater). Info shows a dialog with contact type, path, GPS coordinates, and public key. ### Message List - Scrollable list with newest messages at the bottom - **Outgoing messages**: Right-aligned, primary color background. **Failed messages** change to a red-toned error container background - **Incoming messages**: Left-aligned, grey background with a colored avatar (initial letter or first emoji of sender name; color is deterministic from a hash of the sender name) -- Bubble width capped at 65% of screen width +- Bubble width capped at 72% of screen width - Hyperlinks rendered as tappable green underlined text - **Pinch-to-zoom**: Two-finger zoom (0.8x–1.8x) and double-tap to reset - **Jump to bottom**: Floating button appears when scrolled away from the bottom @@ -87,7 +85,7 @@ When a direct message is sent: 1. The app computes an expected ACK hash: `SHA256([timestamp][attempt][text][selfPubKey])[0:4]` — matching the firmware's hash calculation. If SMAZ compression is enabled, the compressed text (not the original) is hashed 2. On device acknowledgment (`RESP_CODE_SENT`), the message transitions to "sent" and a timeout timer starts -3. **Timeout duration**: Preferably from the ML timeout prediction service; otherwise calculated from LoRa airtime physics: `500 + (airtime × 6 + 250) × (pathLength + 1)` ms for direct paths, `500 + 16 × airtime` ms for flood (airtime is estimated from the radio's current spreading factor, bandwidth, and coding rate) +3. **Timeout duration**: Preferably from the ML timeout prediction service; otherwise from the device's own `est_timeout` in `RESP_CODE_SENT` (clamped to the physics range); otherwise calculated from LoRa airtime physics: `500 + (airtime × 6 + 250) × (pathLength + 1)` ms for direct paths, `500 + 16 × airtime` ms for flood (airtime is estimated from the radio's current spreading factor, bandwidth, and coding rate). The result is capped at 45 seconds. 4. On timeout, the message is retried with **exponential backoff**: `1000 × 2^retryCount` ms (1s, 2s, 4s, 8s, 16s...) 5. **Max retries**: Configurable (default 5, range 2–10) 6. After max retries, the message is marked "failed" — but a **30-second grace window** remains during which a late ACK can still resolve the message to "delivered" @@ -114,8 +112,9 @@ Add emoji reactions to incoming messages (not your own): | Action | Availability | Description | |---|---|---| | Add reaction | Incoming messages only | Opens emoji picker | -| View path | Mobile: tap bubble directly; Desktop: long-press/right-click menu | Shows message routing path | +| View path | All platforms: long-press/right-click menu | Shows message routing path | | Copy | All messages | Copies text to clipboard | +| Translate | Incoming messages only (when translation is enabled and not yet translated) | Translates the message on-demand using the on-device model | | Mark as Unread | Incoming messages only | Marks this message and all subsequent incoming messages as unread | | Delete | All messages | Removes locally (not from mesh) | | Retry | Failed outgoing messages | Re-sends the message | diff --git a/documentation/contacts.md b/documentation/contacts.md index 7ff5ad79..2a875d27 100644 --- a/documentation/contacts.md +++ b/documentation/contacts.md @@ -6,18 +6,17 @@ The Contacts screen is the primary hub for managing mesh nodes your radio has a ## How to Access -- Automatically shown after connecting to a device -- QuickSwitchBar tab 0 (leftmost) from Channels or Map screens +- QuickSwitchBar tab 0 (leftmost) from Channels or Map screens (Channels is shown first after connecting) - Back navigation from Chat or Settings screens ## Contact Types | Type | Avatar Color | Icon | Description | |---|---|---|---| -| Chat | Blue | Chat bubble | Another user's mesh radio | -| Repeater | Orange | Cell tower | A mesh repeater/relay node | -| Room | Purple | Group | A room server for group chat | -| Sensor | Green | Sensors | A sensor device | +| Chat | Blue | Initials / emoji | Another user's mesh radio | +| Repeater | Amber | Cell tower | A mesh repeater/relay node | +| Room | Magenta | Meeting room | A room server for group chat | +| Sensor | Teal | Sensors | A sensor device | ## Contact List @@ -73,42 +72,42 @@ Groups are stored per radio identity (scoped by public key). | Action | Availability | Description | |---|---|---| -| Ping | Repeaters (always) | Opens PathTraceMapScreen targeting the repeater | -| Path Trace | Rooms (always); Chat/Sensor if `pathLength > 0` | Opens PathTraceMapScreen. For rooms, label shows "Ping" when no path bytes are known, "Path Trace" when path bytes are available | +| Ping | Repeaters only | Opens PathTraceMapScreen targeting the repeater | +| Path Trace | Rooms (always); Chat/Sensor only if `pathLength > 0` | Opens PathTraceMapScreen. For rooms, label shows "Ping" when no path bytes are known, "Path Trace" when path bytes are available | | Manage Repeater | Repeaters only | Login dialog → RepeaterHubScreen | | Room Login | Rooms only | Login dialog → ChatScreen | | Room Management | Rooms only | Login dialog → RepeaterHubScreen (management mode) | -| Open Chat | Chat/Sensor | Same as single tap | | Add/Remove Favorite | All types | Toggles the favorite flag | -| Share Contact | All types | Copies `meshcore://` URI to clipboard | +| Share Contact | All types | Requests advert from device → copies `meshcore://` URI to clipboard | | Share Contact Zero-Hop | All types | Broadcasts the contact's advertisement one hop | | Delete Contact | All types | Confirmation dialog → removes from device and clears messages | ## App Bar Menus -The Contacts screen has **two separate popup menus** in the app bar: +The Contacts screen has a single **three-dot overflow menu** (`⋮`) in the app bar: -**Antenna icon menu** (contact sharing): +- Discovered Contacts — opens the DiscoveryScreen +- Add Contact from Clipboard — reads a `meshcore://` URI from clipboard and imports it +- *(divider)* - Zero-Hop Advert — broadcasts your advertisement to immediately adjacent nodes - Flood Advert — broadcasts across the full mesh network - Copy Advert to Clipboard — copies your `meshcore://` URI for sharing externally -- Add Contact from Clipboard — reads a `meshcore://` URI from clipboard and imports it - -**Three-dot overflow menu**: +- *(divider)* - Disconnect — disconnects from the device -- Discovered Contacts — opens the DiscoveryScreen - Settings — opens the Settings screen +A **floating action button** (person-add icon) provides a shortcut sheet to "Add Contact from Clipboard" or "Discovered Contacts". + ## Adding Contacts ### Automatic (Passive) When the radio hears an advertisement, the contact appears automatically if auto-add is enabled for that type (configurable in Settings → Contact Settings). ### Import from Clipboard -Antenna menu → "Add Contact from Clipboard". Reads a `meshcore://` URI from clipboard and imports it to the device. +Overflow menu (or the FAB shortcut) → "Add Contact from Clipboard". Reads a `meshcore://` URI from clipboard and imports it to the device. ### Import from Discovered Contacts -Overflow menu → "Discovered Contacts". Shows nodes heard passively that haven't been added yet. Tap to immediately import (no confirmation dialog), or long-press for more options (Add, Copy URI, Delete). The Discovery screen has its own search bar, type filters (Users, Repeaters, Rooms, Favorites), and sort options (Last Seen, A-Z). An overflow "Delete All" option clears all discovered contacts. +Overflow menu → "Discovered Contacts". Shows nodes heard passively that haven't been added yet. Tap to immediately import (no confirmation dialog), or long-press for more options (Copy URI, Delete). The Discovery screen has its own search bar, type filters (Users, Repeaters, Rooms), and sort options (Last Seen, A-Z). An overflow "Delete All" option clears all discovered contacts. ## Contact Sharing Format diff --git a/documentation/map-and-location.md b/documentation/map-and-location.md index 93ba6345..08bd1cf8 100644 --- a/documentation/map-and-location.md +++ b/documentation/map-and-location.md @@ -35,9 +35,9 @@ Location pins shared in chat messages are displayed as flags: Tap a pin to see its info. Options to "Hide" (session only) or "Remove" (persistent). -### Predicted / Guessed Locations (Semi-Transparent) +### Predicted / Guessed Locations -Many contacts on the mesh don't have GPS hardware, so the map has no explicit coordinates for them. Instead of leaving these contacts invisible, the app **infers an approximate position** by analyzing the repeater path the contact's messages travel through. These inferred positions are displayed as semi-transparent markers with a `not_listed_location` icon, visually distinct from confirmed-location markers. +Many contacts on the mesh don't have GPS hardware, so the map has no explicit coordinates for them. Instead of leaving these contacts invisible, the app **infers an approximate position** by analyzing the repeater path the contact's messages travel through. These inferred positions are displayed as markers with a `not_listed_location` icon and a muted grey or colored border, visually distinct from confirmed-location markers. #### Why guessed locations exist @@ -55,19 +55,19 @@ In a mesh network, every message hops through one or more repeaters on its way t 5. **Compute the estimated position**: - **Single anchor**: The contact is placed on a small circle (330m radius) around the repeater. The angle on the circle is deterministic — derived from an FNV-1a hash of the contact's public key — so the same contact always appears at the same offset, preventing markers from stacking on top of each other. - - **Two or more anchors**: The position is the average (centroid) of all anchor coordinates, with a smaller offset radius (80–120m) applied for visual separation. + - **Two or more anchors**: The position is a weighted average of all anchor coordinates (each subsequent anchor weighted at half the previous one, biasing toward the first), with a smaller offset radius (120m for 2 anchors, 80m for 3+) applied for visual separation. 6. **Assign confidence level**: - - **High confidence** (2+ anchors): Displayed at 55% opacity. - - **Low confidence** (1 anchor): Displayed at 30% opacity. + - **High confidence** (2+ anchors): The marker border uses the node's type color (brighter border). + - **Low confidence** (1 anchor): The marker border is rendered in a muted grey. 7. **Cache the result**: The computation is cached using a key derived from the contact's paths, anchor positions, path-history version, and radio parameters. The cache is only invalidated when any of these inputs change, avoiding recomputation on every UI rebuild. #### How to read guessed locations on the map -- **Semi-transparent marker** with a `not_listed_location` icon: This is a guessed position, not a confirmed GPS fix. -- **More opaque** (55%): Higher confidence — the contact was seen through 2 or more repeaters with known positions. -- **More transparent** (30%): Lower confidence — based on a single repeater anchor only. +- **Marker with `not_listed_location` icon**: This is a guessed position, not a confirmed GPS fix. +- **Colored border** (type color): Higher confidence — the contact was seen through 2 or more repeaters with known positions. +- **Grey border**: Lower confidence — based on a single repeater anchor only. - Coordinates shown in the marker info dialog are prefixed with `~` to indicate they are estimated. - Guessed locations can be toggled on/off in the map filter dialog (FAB → "Guessed locations" toggle). @@ -110,9 +110,16 @@ A map with a polyline showing the route from your node through repeater hops to - **Green circles**: Hops with known GPS coordinates - **Orange circles** (`~HH`): Inferred positions (no GPS but deducible from contacts) - **Red endpoint**: Target contact with known GPS -- **Purple semi-transparent endpoint**: Target with guessed position +- **Magenta endpoint**: Target with guessed position -A legend card at the bottom lists each hop pair with SNR quality icons and total path distance. +A bottom panel shows each hop pair with SNR quality icons and total path distance. When multiple observed paths are available, a **Single / Combined** toggle appears at the top of the map. In Combined view, all paths are overlaid; shared segments are highlighted with a white halo and a path count badge appears on shared nodes. + +The bottom panel also provides **packet animation controls**: +- **Animation toggle** (on/off) +- **Step back / Play / Step forward / Replay** buttons +- **Follow packet lock** — keeps the map camera centered on the moving packet dot +- **Speed selector** (0.5×, 1×, 2×, 4×) +- A live **"Hop x of y · from → to"** label that tracks the active segment ### How It Works Sends a trace request frame over the mesh. The repeater network traces the path hop-by-hop and returns per-hop SNR data. For hops without GPS, positions are inferred by averaging GPS coordinates of contacts sharing that last-hop byte. @@ -125,17 +132,17 @@ Sends a trace request frame over the mesh. The repeater network traces the path From the main map, tap the terrain/antenna icon. ### What the User Sees -A full-screen map with a collapsible control panel containing: -- **Elevation profile chart**: Terrain fill (green), LOS beam line (white), radio horizon line (yellow) -- **Status**: Clear (green) or blocked (red) with distance and minimum clearance -- **Options panel**: Node toggles, endpoint dropdowns, antenna height sliders (0–400 ft), Run LOS button +A full-screen map with a draggable bottom sheet containing: +- **Elevation profile chart**: Terrain fill (green), LOS beam line (white), radio horizon line (yellow); obstruction points are marked as clickable dots on the chart +- **Status summary**: Clear (green), Marginal (amber, within 5 m of obstruction), or Blocked (red) with distance and clearance/obstruction amount +- **Options section** (collapsible): Node toggles, endpoint dropdowns, antenna height sliders (0–400 ft), Run LOS button ### Key Interactions -- **Long-press the map** to add custom endpoints (orange pushpin markers, renameable/deleteable) +- **Long-press the map** to add custom endpoints (pushpin markers, renameable/deleteable) - **Tap a marker** to select it as Point A or B; LOS runs automatically when both are set - **Antenna heights** are adjustable for both endpoints -- **Map line** between endpoints is colored green (clear) or red (blocked) -- Terrain elevation is fetched from the Open-Meteo API (21–81 sample points, cached 24 hours) +- **Map line** between endpoints is colored green (clear), amber (marginal), or red (blocked) +- Terrain elevation is fetched from the Open-Meteo API (21, 41, or 81 sample points depending on link distance, cached 24 hours) - K-factor is adjusted per radio frequency from a baseline of 4/3 at 915 MHz --- @@ -149,7 +156,7 @@ Settings → App Settings → Map Display → Offline Map Cache - Map with a blue polygon overlay showing previously selected cache bounds - Bounding box coordinates card - **Cache Area** controls: "Use Current View" and Clear buttons -- **Zoom Range** slider (3–18) with estimated tile count +- **Zoom Range** range slider (3–18, dual-handle for min and max) with estimated tile count - **Download progress** bar (when downloading) - **Download Tiles** and **Clear Cache** buttons diff --git a/documentation/navigation.md b/documentation/navigation.md index b0994fd4..1d8f05a2 100644 --- a/documentation/navigation.md +++ b/documentation/navigation.md @@ -5,7 +5,7 @@ The app follows this general flow: ``` -Launch → Scanner Screen → [Connect via BLE/USB/TCP] → Contacts Screen +Launch → Scanner Screen → [Connect via BLE/USB/TCP] → Channels Screen ``` After connecting, the three main screens (Contacts, Channels, Map) are accessible via a persistent bottom navigation bar called the **QuickSwitchBar**. @@ -24,7 +24,7 @@ Tapping a tab replaces the current screen with a subtle fade + slight horizontal ## Disconnection -- The disconnect button (available in the Settings screen and other main screens) shows a confirmation dialog before disconnecting +- The disconnect button (available in the overflow menu of each main screen) shows a confirmation dialog before disconnecting - If the device disconnects unexpectedly, the app automatically navigates back to the Scanner screen (fires after the current frame completes via a post-frame callback) - This auto-navigation behavior (`DisconnectNavigationMixin`) is shared across all main screens @@ -38,11 +38,11 @@ Tapping a tab replaces the current screen with a subtle fade + slight horizontal ``` ScannerScreen (root, always on stack) - ├─ [BLE connect] → push → ContactsScreen - ├─ [TCP FAB] → push → TcpScreen - │ └─ [TCP connected] → pushReplacement → ContactsScreen - └─ [USB FAB] → push → UsbScreen - └─ [USB connected] → pushReplacement → ContactsScreen + ├─ [BLE connect] → push → ChannelsScreen + ├─ [TCP icon button] → push → TcpScreen + │ └─ [TCP connected] → pushReplacement → ChannelsScreen + └─ [USB icon button] → push → UsbScreen + └─ [USB connected] → pushReplacement → ChannelsScreen ContactsScreen (selected=0) ├─ [quick-switch 1] → pushReplacement → ChannelsScreen @@ -60,9 +60,9 @@ ChannelsScreen (selected=1) MapScreen (selected=2) ├─ [quick-switch 0] → pushReplacement → ContactsScreen ├─ [quick-switch 1] → pushReplacement → ChannelsScreen - ├─ [radar button] → push → PathTraceMapScreen - ├─ [terrain button] → push → LineOfSightMapScreen - └─ [long-press] → share marker / set location + ├─ [radar menu item] → enters in-map path trace mode (push → PathTraceMapScreen after path is built) + ├─ [terrain menu item] → push → LineOfSightMapScreen + └─ [long-press] → share marker sheet Settings (push from any main screen) └─ [App Settings] → push → AppSettingsScreen diff --git a/documentation/notifications.md b/documentation/notifications.md index 7db9f0fa..00de3cde 100644 --- a/documentation/notifications.md +++ b/documentation/notifications.md @@ -22,7 +22,7 @@ MeshCore Open provides both **system notifications** (push-style OS alerts) and ### 3. Advertisement Notifications - **Triggered when**: A new node is discovered on the mesh for the first time -- **Title**: "New [type] discovered" (e.g., "New chat node discovered") +- **Title**: "New [type] discovered" (e.g., "New Chat discovered") - **Body**: Contact's name - **Priority**: Default - **Android channel**: `adverts` @@ -43,7 +43,7 @@ Red numeric badges appear throughout the UI: - **Contacts list**: Each contact row shows a red pill badge (e.g., "3") for unread messages - **Channels list**: Each channel row shows an unread badge - **Chat screen subtitle**: Shows unread count inline -- Badges cap at "99+" for display +- Badges cap at "9999+" for display ### How Unread Counts Work diff --git a/documentation/repeater-management.md b/documentation/repeater-management.md index 754d4e28..a05bcd82 100644 --- a/documentation/repeater-management.md +++ b/documentation/repeater-management.md @@ -34,8 +34,8 @@ The central management screen showing: |---|---|---| | Status | Repeater Status Screen | All users | | Telemetry | Telemetry Screen | All users | -| CLI | Repeater CLI Screen | Admin only | | Neighbors | Neighbors Screen | All users | +| CLI | Repeater CLI Screen | Admin only | | Settings | Repeater Settings Screen | Admin only | The battery chemistry selector and CLI/Settings cards are hidden from guest users. @@ -89,7 +89,7 @@ A terminal-style interface for sending commands directly to the repeater. - Type a command and press send (or Enter on desktop) - Up/down arrows navigate through command history - Quick-command buttons populate and send common commands -- Bug report icon: Shows raw frame debug info for the next typed command (shows error snackbar if input field is empty) +- Overflow menu (three-dot icon): "Debug next command" option shows raw frame debug info for the next typed command (shows error snackbar if input field is empty) - Help icon: Opens a scrollable reference of all known CLI commands. Tapping any command populates the input field immediately - Clear icon: Wipes the command/response history - Failed/timed-out commands are automatically retried once @@ -112,7 +112,9 @@ The in-app help reference (help icon) documents all known commands. Categories: **Power Management**: `get pwrmgt.support`, `get pwrmgt.source`, `get pwrmgt.bootreason`, `get pwrmgt.bootmv` -**Sensors**: `sensor get {key}` +**Sensors**: `sensor get {key}`, `sensor set {key} {value}`, `sensor list [start]` + +**GPS Management**: `gps`, `gps {on|off}`, `gps sync`, `gps setloc`, `gps advert`, `gps advert {none|share|prefs}` **Region Management**: `region`, `region load`, `region get`, `region put`, `region remove`, `region allowf`, `region denyf`, `region home`, `region save`, `region default`, `region list allowed`, `region list denied` @@ -207,7 +209,7 @@ Nine configuration cards, each with its own per-field refresh button(s): **Danger Zone** (red-styled card) - Reboot repeater (sends `reboot` with confirmation dialog) -- Erase filesystem (serial-only; shows informational snackbar only — no command is sent over the air) +- Erase filesystem (serial-only; shows a confirmation dialog, then an informational snackbar — no command is sent over the air) ### Key Interactions - **Settings are NOT auto-fetched on open**. Name is pre-filled from cached contact data. Each section has its own refresh button to fetch live values from the repeater diff --git a/documentation/scanner-and-connection.md b/documentation/scanner-and-connection.md index 080e4483..c73457b8 100644 --- a/documentation/scanner-and-connection.md +++ b/documentation/scanner-and-connection.md @@ -28,9 +28,11 @@ The BLE Scanner is the app's home screen, displayed immediately on launch. **Device List**: When no devices are found, shows a large Bluetooth icon with a prompt. The prompt text is dynamic: "Searching for devices..." while actively scanning, or "Tap Scan to search" when idle. When devices are found, shows a scrollable list of `DeviceTile` widgets. -**Bottom FAB Row**: Up to three floating action buttons: -- **USB** button - Opens USB connection screen (Android, Windows, Linux, macOS, Chrome web only) -- **TCP/IP** button - Opens TCP connection screen (all non-web platforms) +**App Bar Actions**: Icon buttons in the top-right corner of the app bar: +- **USB** icon button - Opens USB connection screen (Android, Windows, Linux, macOS, Chrome web only) +- **TCP/IP** icon button - Opens TCP connection screen (all non-web platforms) + +**Bottom FAB**: A single floating action button: - **BLE Scan** button - Toggles BLE scanning on/off; shows a spinner when scanning. **Disabled** (greyed out, not tappable) when Bluetooth is off ### Device Tile @@ -51,7 +53,7 @@ Note: The weak (-80 to -90 dBm) and poor (< -90 dBm) tiers share the same icon s ### How Scanning Works -- Filters for devices with names starting with one of the known prefixes: `MeshCore-`, `Whisper-`, `WisCore-`, `Seeed`, `Lilygo`, `HT-`, `LowMesh_MC_` +- Filters for devices advertising the Nordic UART Service UUID (so community forks with non-standard names are still found). Known name prefixes used by stock firmware builds for reference: `MeshCore-`, `Whisper-`, `WisCore-`, `Seeed`, `Lilygo`, `HT-`, `LowMesh_MC_`, `NRF52` - Uses low-latency scan mode on Android - Scans for 10 seconds then auto-stops - On iOS/macOS, waits for BLE adapter initialization before starting @@ -65,7 +67,7 @@ Tap a device tile or its Connect button: 3. Requests MTU 185 bytes for optimal throughput 4. Discovers BLE services and locates the Nordic UART Service 5. Subscribes to TX notifications for receiving data -6. On success, automatically navigates to the Contacts screen +6. On success, automatically navigates to the Channels screen 7. On failure, shows a red error snackbar --- @@ -74,7 +76,7 @@ Tap a device tile or its Connect button: ### How to Access -From the Scanner screen, tap the **USB** FAB button. +From the Scanner screen, tap the **USB** icon button in the app bar. ### What the User Sees @@ -82,15 +84,15 @@ From the Scanner screen, tap the **USB** FAB button. - A list of detected USB serial ports, each showing: - Friendly display name - Raw port name (subtitle, only shown when it differs from the display name) - - "Connect" button -- FABs at the bottom to switch to BLE or TCP (these use `pushReplacement`, so back navigation returns to Scanner, not between USB/TCP) + - Chevron trailing icon (the entire tile is tappable to connect) +- Transport switcher buttons (outlined, not FABs) to switch to BLE or TCP (these use `pushReplacement`, so back navigation returns to Scanner, not between USB/TCP) ### Key Interactions - On desktop (Windows, Linux, macOS): ports are polled every 2 seconds for hot-plug detection (polling pauses while connecting/connected) - On mobile: tap the "Scan" FAB to manually refresh -- Tap a port or its Connect button to connect -- On successful connection, navigates to Contacts screen +- Tap a port tile to connect +- On successful connection, navigates to Channels screen - On connection failure, the port list automatically refreshes - Platform-specific error messages for common USB failures (permission denied, device missing, device detached, device busy, driver missing, port invalid, timeout, and more) @@ -100,7 +102,7 @@ From the Scanner screen, tap the **USB** FAB button. ### How to Access -From the Scanner screen, tap the **TCP/IP** FAB button. +From the Scanner screen, tap the **TCP/IP** icon button in the app bar. ### What the User Sees @@ -108,7 +110,7 @@ From the Scanner screen, tap the **TCP/IP** FAB button. - **Host address** text field - **Port number** text field - **Connect** button -- FABs at the bottom to switch to USB or BLE +- Transport switcher buttons (outlined, not FABs) to switch to USB or BLE ### Key Interactions @@ -119,6 +121,6 @@ From the Scanner screen, tap the **TCP/IP** FAB button. - Validation errors are shown as red snackbars - The Connect button shows a spinner and "Connecting..." label while in progress - The status bar shows the specific host:port being connected to (e.g., "Connecting to 192.168.1.1:5000") -- On success, navigates to Contacts screen and saves the host/port to settings +- On success, navigates to Channels screen and saves the host/port to settings - On connection, the status bar shows the active TCP endpoint (e.g., "Connected to 192.168.1.1:5000") - Error messages for timeout, unsupported platform, and connection failures diff --git a/documentation/settings.md b/documentation/settings.md index 74e8c933..a2f4192a 100644 --- a/documentation/settings.md +++ b/documentation/settings.md @@ -12,12 +12,13 @@ Settings are only accessible while a device is connected. The settings screen is a scrollable list of cards: 1. [Device Info](#device-info) -2. [App Settings](#app-settings) (link to sub-screen) -3. [Node Settings](#node-settings) -4. [Actions](#actions) -5. [Debug](#debug) +2. [Node Settings](#node-settings) +3. [Location](#location) +4. [App Settings](#app-settings) (link to sub-screen) +5. [Actions](#actions) 6. [Export](#export) -7. [About](#about) +7. [Debug](#debug) +8. [About](#about) --- @@ -40,56 +41,6 @@ Battery shows an alert icon and orange text when at 15% or below. The toggle onl --- -## App Settings - -A dedicated sub-screen for app-level preferences (nothing here is sent to the device). All settings persist locally via SharedPreferences. - -### Appearance -- **Theme**: System / Light / Dark -- **Language**: System default or one of 18 languages (English, French, Spanish, German, Polish, Slovenian, Portuguese, Italian, Chinese, Swedish, Dutch, Slovak, Bulgarian, Russian, Ukrainian, Hungarian, Japanese, Korean) -- **Enable Message Tracing**: Shows path trace overlays and extra metadata on messages - -### Notifications -- **Master enable/disable**: Requests OS permission when enabling -- **Message notifications**: New direct message alerts -- **Channel message notifications**: New channel message alerts -- **Advertisement notifications**: New node discovery alerts - -### Messaging -- **Clear Path on Max Retry**: Erases the stored routing path after all retries fail -- **Jump to Oldest Unread**: When opening a chat, scrolls to the oldest unread message instead of the newest -- **Auto Route Rotation**: Enables weighted routing algorithm. When enabled, expands to show five slider sub-settings (hidden when off): - - Max Route Weight (1–10, default 5, integer steps) - - Initial Route Weight (0.5–5.0, default 3.0) - - Success Increment (0.1–2.0, default 0.5, 0.1 steps) - - Failure Decrement (0.1–2.0, default 0.2, 0.1 steps) - - Max Message Retries (2–10, default 5) - -### Battery -- **Battery Chemistry**: NMC / LiFePO4 / LiPo (per device, used to calibrate percentage from voltage) - -### Translation -Not shown on web. Controls on-device message translation powered by a locally-downloaded ML model: -- **Enable Translation**: Translates incoming messages into the selected target language -- **Translate Composer**: Translates outgoing messages from the target language back before sending -- **Target Language**: Language to translate into (searchable list; defaults to the app language) -- **Downloaded Model**: Dropdown to select among already-downloaded translation models -- **Preset Model**: Download a curated preset model with one tap -- **Custom Model URL**: Enter a URL to download a custom GGUF-format model; shows download progress and a cancel button - -### Map Display -- **Show Repeaters**: Toggle repeater markers on map -- **Show Chat Nodes**: Toggle chat node markers -- **Show Other Nodes**: Toggle room/sensor markers -- **Time Filter**: All time / Last 1h / Last 6h / Last 24h / Last week -- **Units**: Metric / Imperial -- **Offline Map Cache**: Navigate to tile download screen - -### Debug -- **App Debug Logging**: Enable the in-app debug log - ---- - ## Node Settings These settings are sent directly to the connected device firmware. @@ -101,7 +52,7 @@ These settings are sent directly to the connected device firmware. ### Radio Settings Opens a dialog pre-populated with the device's current radio settings. Contains: -- **Preset dropdown**: 19 regional presets — selecting a preset immediately fills all fields below. Full list: Australia, Australia (Narrow), Australia SA, WA, QLD, Czech Republic, EU 433MHz, EU/UK (Long Range), EU/UK (Medium Range), EU/UK (Narrow), New Zealand, New Zealand (Narrow), Portugal 433, Portugal 869, Switzerland, USA Arizona, USA/Canada, Vietnam, Off-Grid 433, Off-Grid 869, Off-Grid 918 +- **Preset dropdown**: Regional presets — selecting a preset immediately fills all fields below. Includes presets for Australia, Australia (Narrow), Australia SA WA QLD, Czech Republic, EU 433MHz, EU/UK (Long Range), EU/UK (Medium Range), EU/UK (Narrow), New Zealand, New Zealand (Narrow), Portugal 433, Portugal 869, numerous Russia city presets, Switzerland, USA Arizona, USA/Canada, and Vietnam - **Frequency** (MHz): Free text, validated 300–2500 MHz - **Bandwidth**: Dropdown (7.8 / 10.4 / 15.6 / 20.8 / 31.25 / 41.7 / 62.5 / 125 / 250 / 500 kHz) - **Spreading Factor**: SF5–SF12 @@ -109,6 +60,13 @@ Opens a dialog pre-populated with the device's current radio settings. Contains: - **TX Power** (dBm): Validated 0 to device max (typically 22 dBm) - **Client Repeat** toggle: Only shown on firmware v9+; requires frequency to be exactly 433.000, 869.000, or 918.000 MHz (the Off-Grid presets). Save is blocked with a warning if enabled on other frequencies +### Companion Radio Stats +Opens the RF statistics screen (RSSI, SNR, packet counts) for the paired radio. Only enabled when connected to a device that supports companion radio stats. + +--- + +## Location + ### Location Opens a dialog pre-populated with the device's current coordinates (if known): - Latitude and longitude fields (decimal, 6 decimal places). If only one field is provided, the other uses the device's current value @@ -125,8 +83,68 @@ Five toggles controlling which node types are auto-added when heard: - Auto-add Sensors - Overwrite Oldest (when contact list is full) -### Privacy Mode -Opens a confirmation dialog with three buttons: Cancel, Enable, and Disable. Both states can be set from the same dialog regardless of current state. A snackbar confirms which state was applied. When on, the node stops broadcasting its location in advertisements. +### Privacy +Opens a dialog with controls for how the node shares telemetry and location data: +- **Advert Location**: Toggle whether the node broadcasts its location in advertisements +- **Multi-Ack**: Toggle multi-ack delivery confirmations +- **Telemetry Base Mode**: Deny All / Allow by Contact / Allow All +- **Telemetry Location Mode**: Deny All / Allow by Contact / Allow All +- **Telemetry Environment Mode**: Deny All / Allow by Contact / Allow All + +Settings take effect when saved. A snackbar confirms the update. + +--- + +## App Settings + +A dedicated sub-screen for app-level preferences (nothing here is sent to the device). All settings persist locally via SharedPreferences. + +### Appearance +- **Theme**: System / Light / Dark +- **Language**: System default or one of 18 languages (English, French, Spanish, German, Polish, Slovenian, Portuguese, Italian, Chinese, Swedish, Dutch, Slovak, Bulgarian, Russian, Ukrainian, Hungarian, Japanese, Korean) + +### Notifications +- **Master enable/disable**: Requests OS permission when enabling +- **Message notifications**: New direct message alerts +- **Channel message notifications**: New channel message alerts +- **Advertisement notifications**: New node discovery alerts + +### Messaging +- **Clear Path on Max Retry**: Erases the stored routing path after all retries fail +- **Jump to Oldest Unread**: When opening a chat, scrolls to the oldest unread message instead of the newest +- **Auto Route Rotation**: Enables weighted routing algorithm. When enabled, expands to show five slider sub-settings (hidden when off): + - Max Route Weight (1–10, default 5, integer steps) + - Initial Route Weight (0.5–5.0, default 3.0) + - Success Increment (0.1–2.0, default 0.5, 0.1 steps) + - Failure Decrement (0.1–2.0, default 0.2, 0.1 steps) + - Max Message Retries (2–10, default 5) +- **Enable Message Tracing**: Shows path trace overlays and extra metadata on messages + +### Battery +- **Battery Chemistry**: NMC / LiFePO4 / LiPo (per device, used to calibrate percentage from voltage) + +### Map Display +- **Show Repeaters**: Toggle repeater markers on map +- **Show Chat Nodes**: Toggle chat node markers +- **Show Other Nodes**: Toggle room/sensor markers +- **Time Filter**: All time / Last 1h / Last 6h / Last 24h / Last week +- **Units**: Metric / Imperial +- **Offline Map Cache**: Navigate to tile download screen + +### Translation +Not shown on web. Controls on-device message translation powered by a locally-downloaded ML model: +- **Enable Translation**: Translates incoming messages into the selected target language +- **Translate Composer**: Translates outgoing messages from the target language back before sending +- **Target Language**: Language to translate into (searchable list; defaults to the app language) +- **Downloaded Model**: Dropdown to select among already-downloaded translation models +- **Preset Model**: Download a curated preset model with one tap +- **Custom Model URL**: Enter a URL to download a custom GGUF-format model; shows download progress and a cancel button + +### Cyrillic-to-Latin (Cyr2Lat) +Controls character substitution profiles used to render Cyrillic text in Latin characters. A dropdown selects the active profile; Add, Edit, and Delete buttons manage the profile list (the last remaining profile cannot be deleted). Each profile stores a JSON character map. + +### Debug +- **App Debug Logging**: Enable the in-app debug log --- @@ -136,10 +154,24 @@ One-tap device operations: | Action | Description | |---|---| -| Send Advertisement | Floods the mesh with your node's advertisement | | Sync Time | Sends current Unix timestamp to the device | | Refresh Contacts | Re-requests the full contact list | -| Reboot Device | Confirmation dialog → reboots the device (shown in orange) | +| Reboot Device | Confirmation dialog → reboots the device (shown in warning color) | +| Delete All Paths | Confirmation dialog → clears all stored routing paths (shown in alert color) | + +--- + +## Export + +Three GPX export options (not available on web): + +| Option | Exports | +|---|---| +| Export Repeaters | Repeaters and Rooms with GPS coordinates | +| Export Contacts | Chat contacts with GPS coordinates | +| Export All | All contacts with GPS coordinates | + +Each creates a `.gpx` file and opens the OS share sheet. Feedback via snackbar for four outcomes: success, no contacts with coordinates, feature not available (web), or error. --- @@ -160,20 +192,6 @@ Structured log entries (Info / Warning / Error), with tag, message, and timestam --- -## Export - -Three GPX export options (not available on web): - -| Option | Exports | -|---|---| -| Export Repeaters | Repeaters and Rooms with GPS coordinates | -| Export Contacts | Chat contacts with GPS coordinates | -| Export All | All contacts with GPS coordinates | - -Each creates a `.gpx` file and opens the OS share sheet. Feedback via snackbar for four outcomes: success, no contacts with coordinates, feature not available (web), or error. - ---- - ## About Shows the standard Flutter about dialog with app name, version, and legal notice.