Closes the 2 MINOR findings from the G1 re-review (#76):
- MINOR-A: ObserverConfigService.refresh() no longer wipes lastError / clears
stale on a PARTIAL read. A null from any getFlat is a failed read; the
snapshot is marked stale and the error kept (error-visibility).
- MINOR-B: the settings pane validates status_interval against the firmware
range (10-3600) before sending; an out-of-range value is not put on the wire
and the user is told.
TDD: both tests RED first (current sent '5'; refresh wiped the error), GREEN
after. Full observer suite (37 tests) green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Hardened the staged-save pane (settings_screen capability gate already correct):
- Normalize an unexpected device rotation to a valid SegmentedButton segment
{0,180} so the control always has a valid selection and never echoes an
out-of-range value back on save.
- Disable Save during a refresh (no save/refresh race on the controllers).
- digitsOnly formatter on status-interval (firmware still range-checks).
- Keys on the SSID/password fields for robust widget-test targeting.
4 widget tests pin the security-sensitive behavior: saves send ONLY changed
keys, a blank password keeps the stored secret, an entered password is sent as
wifi.pwd then the field clears, rotation is normalized, stale/error surfaced
(SAFELANE §6). Full observer suite (33 tests) green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Gemini MAJOR #4 (offset unverified / short-frame robustness):
- Offset VERIFIED against firmware MyMesh.cpp: the device-info tail is three
consecutive out_frame[i++] writes -- client_repeat@80, path_hash_mode@81,
offband_caps@82. Bytes 80/81 are shipping (path-hash), so caps@82 is correct
by adjacency, not a guess.
- Extracted MeshCoreConnector.parseOffbandCaps (pure, bounds-checked) so the
guard + offset are unit-testable; _handleDeviceInfo delegates to it.
- 4 tests pin: pre-v14 short frame -> null, len-83 reads byte 82 verbatim,
observer bit round-trips supportsConfig, hostile truncated frames never OOB.
Live observer node confirms end-to-end at G2.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Greens A2's adversarial tests, fixing the Gemini findings:
- BLOCKER #1 (race): a chain-lock (_serialized) serializes every transport
round-trip, so overlapping refresh()/setFlat() can no longer cross responses
(firmware has no request id; responses correlate by order).
- MINOR #6 (secret leak): error messages redact secret key names
(wifi.pwd / *.password / *.pwd -> "<secret>").
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- screens/settings/observer_settings_view.dart: staged-save pane for WiFi
(ssid/pwd/enabled), MQTT region (iata/status_interval), display
(always_on/rotation 0|180), with Save (changed keys only) + Refresh +
stale/error banners; the broker pool renders read-only (enable/clear await
the firmware F-task).
- settings_screen.dart: register the Observer category, gated on
ObserverConfigService.supported (re-evaluated as the connector's offband_caps
flips on connect/disconnect).
- connector/meshcore_connector.dart: parse the offband_caps byte (device-info
byte 82, after path_hash_mode) + expose offbandCaps.
- services/observer_config_service.dart: supported computed live from the
connector (version gate + cap bit).
- main.dart: provide ObserverConfigService.
Strings are English-only pending l10n ARB keys.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reflow the systemDevices.map(...) chain after the .where() removal so
`dart format --set-exit-if-changed .` is clean. No behavior change.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The scanner filtered advertisements by device-name keyword
(withKeywords: deviceNamePrefixes), so any MeshCore/Offband device
whose advertised name fell outside the hardcoded prefix list was
invisible to the scanner — even though the official MeshCore app,
which scans by the Nordic UART service UUID, found them. Switch
startScan to withServices: [Guid(service)] and drop the now-redundant
name filter on the already-service-scoped systemDevices fallback.
Device-tested on a Samsung S25: a previously-invisible node now
appears in the scan.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CI's `dart format --set-exit-if-changed .` step is repo-wide. My A/B/C
commit hand-wrote the .catchError blocks without running the formatter,
and 2 settings views carried pre-existing format drift from #28. dart
format reflows all three (whitespace only, identical logic):
- lib/connector/meshcore_connector.dart (this PR's blocks)
- lib/screens/settings/app_settings_view.dart (pre-existing, #28)
- lib/screens/settings/message_settings_view.dart (pre-existing, #28)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
settings_screen action handlers fire-and-forgot or awaited-without-catch several connector calls, and _loadVersionInfo called setState after an await with no mounted guard. Add a mounted guard to _loadVersionInfo; try/catch + error snackbar to the GPS-enable toggles (inline + _editLocation dialog) and getContacts; await syncTime in _syncTime so the 'synchronized' snackbar shows only on success (it previously fired before the await); and in _editLocation's save, capture the messenger before the pop and wrap the setCustomVar/setNodeLocation/refreshDeviceInfo awaits.
Verified non-issues: deleteAllPaths is void, _PrivacySection._apply() already has a try/catch, and exportGPX catches internally and returns gpxExportFailed. Completes #28; error strings English for now (separate l10n sweep).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Settings onChanged/onTap callbacks fire-and-forgot AppSettingsService Future<void> setters, so a persist failure rejected unhandled and was swallowed. Add a shared persistSetting(context, action) helper that awaits the setter in a try/catch and shows a dismissible error SnackBar, capturing the messenger before the await so it survives a picker-dialog pop. Wrap the 27 onChanged/onTap setter sites in app_settings_view + message_settings_view; setAppDebugLogEnabled and setNotificationsEnabled (which await + show their own confirmation) get an inline try/catch instead.
Covers the two onChanged panes; the settings_screen action flows (GPX export, location dialogs, version-info mounted guard) follow in a second pass, so #28 stays open. Error strings are English for now (separate settings l10n sweep).
Adds a persistSetting widget test.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
DirectRepeater.ranking added recencyScore (0..1.8M) to the SNR term (1000/dB), so recency dominated (~1 dB == 1 second), contradicting the comment that recency only breaks ties. Scale recency to a 0-999 bonus so SNR (1000/dB) is primary and recency only decides sub-dB ties.
Offset SNR by its floor (+32; SX126x SNR spans -32..+31.75 dB) so every live ranking stays >= 0, above the -1 stale sentinel; otherwise the scaled-down recency leaves live rankings negative and a stale repeater would outrank a live one.
Adds DirectRepeater.ranking tests: SNR-first, recency tie-break, stale=-1, live-always-outranks-stale.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
disconnect() never resets the channel store scope (publicKeyHex), so a channel write in the post-disconnect async gap still persists -- to the last radio's scope, and into a different radio's scope after reconnecting (cross-radio bleed). Add if (!isConnected) return; to the UI-triggered persisters setChannelUnreadCount, markChannelRead, setChannelOrder. Chosen over clearing _channels, which would risk a racing mutator persisting an empty list and wiping stored channels.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PathHistoryService capped each contact's recentPaths at 100 but dropped any new distinct path once full, so the list froze on the first 100 ever seen and never refreshed with newer/better routes. Evict the lowest-_scorePathRecord entry to make room, keeping the strongest 100. Adds a regression test (full history evicts the weakest to admit a new path).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Query FlutterBluePlus.systemDevices on every native platform (was Linux-only)
so a radio already connected to another central -- e.g. Liam's MeshCore app --
appears in the picker and we attach to the existing link. A connected BLE radio
stops advertising, so a scan alone never finds it.
Parse a leading @[Name] in the message body and render it as a rounded
pill that sets the name apart from the rest, matching the reference
client. Handles a missing space after the mention. Lives in
TranslatedMessageContent so channel + direct chat both get it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Clock setting (System / 12h / 24h) in App Settings; chat times follow it + locale.
- Channel tile: always-on metadata row - time + hops on inbound, time + cell_tower
heard count + status on outbound - decoupled from the message-tracing toggle. The
via-path stays behind tracing.
- Drop the auto reply-to block and the most-recent-sender guess; @mentions now
render inline in the message text.
- Direct chat: same clock-aware time formatting.
Closes#37
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Match the typed text anywhere in the shortcode name (not just the prefix),
ranked exact > prefix > substring, so `:check` surfaces the checkmarks (✅ is
`white_check_mark`). Add check/checkmark/tick aliases for ✅ in a separate,
regen-safe map the chat + channel composers consume.
Closes#45
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two more connector silent-failures from the review. requestRadioStats had an empty 'catch (_) {}' (now logs). setChannelUnreadCount fired unawaited(saveChannels) with no error path (now .catchError + logs). Verified: setContactUnreadCount calls a void debounced store method -- no call-site Future (Gemini false positive); the actual swallow is inside unread_store's debounce timer, flagged separately.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
BLE onValueReceived.listen had no onError, while USB (L1557) and TCP (L1669) both attach one that logs and gracefully disconnects; an error emission on the BLE notify stream was unhandled. Add a matching onError. Also await sendMessageWithRetry in the reaction branch of sendMessage -- the normal path already awaits it; the reaction path's unawaited call swallowed failures, contradicting its own 'same as normal messages' comment.
Verified, not assumed: Gemini's 'disconnect() not try-caught' was a FALSE POSITIVE (already wrapped at L2484-2489); 'deleteAllPaths silent void' is a sync in-memory clear (no async failure path). flutter analyze clean; Gemini-reviewed with no critique of these two changes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Human testing found Privacy controls slow to respond. _PrivacySection._apply called refreshDeviceInfo() after every toggle/dropdown change -- a 5-frame device re-query (deviceQuery, appStart, battery, customVars, autoAddFlags) that does not even re-fetch telemetry. setTelemetryModeBase already updates the connector's local state and calls notifyListeners(), so the refresh was pure overhead. Removed it; apply is now a single setOtherParams frame, matching the auto-add section's responsiveness.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bring radio config up a level: render the form directly in the Radio Settings pane with a full-width Save button, instead of a tile that opens a dialog. _RadioSettingsDialog -> _RadioSettingsForm; build returns an inline Column of the (verbatim) fields + Save; _saveSettings drops the Navigator.pop (no dialog), which also resolves the unguarded-pop-after-await finding. Removed _showRadioSettings. Field widgets, controllers, preset matching, and off-grid snapshot logic unchanged.
Key the form on the device's current radio values (freq/bw/sf/cr/txpower/clientRepeat) so a device-reported change re-seeds it -- the dialog got this for free by reopening; the persistent inline form needs the key.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the Privacy save/cancel dialog with an inline _PrivacySection in the pane: advert-location + multi-ACKs toggles and the three telemetry-mode dropdowns now apply on change. All five commit through one setTelemetryModeBase call, so each control sends the full current state (coalesced via an apply guard) and reverts to the device's values on failure with an error snackbar -- folding in the #28 error handling for this pane. Removes the category->tile->dialog hop; drops _privacySettings.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Flatten two more redundant layers in the settings IA. Radio Stats: extract CompanionRadioStatsScreen's body into an embeddable CompanionRadioStatsBody (carrying the 1s-polling acquire/release lifecycle), add a top-level Radio Stats category that renders it directly, and remove the nested Radio Stats tile from the Radio Settings pane (the screen is kept; still pushed from radio_stats_entry.dart). Rename the Contact Settings category to Contacts (reusing contacts_title) with a contacts icon; rename _messagesContactsPane to _contactsPane.
Also fixes a pre-existing chart-freeze surfaced by review: _NoiseChartPainter compared only sample-list length, so the noise chart stopped repainting once the 120-sample buffer filled. Now compares contents via listEquals.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Split the monolithic AppSettingsScreen (reached via a redundant tile->push from the App Settings category) into two embeddable views rendered directly in the shell panes: AppSettingsView (appearance, translation, battery, map, Cyr2Lat, debug) and MessageSettingsView (notifications + message handling, a new top-level 'Message Settings' category after Contacts). Removes the category->tile->separate-screen layer; each category now lands directly on its settings. Deletes app_settings_screen.dart (no remaining refs). Adds settings_messageSettings(+Subtitle) l10n keys (en template; other locales fall back pending the l10n sweep). Card bodies moved verbatim -- behavior preserved.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the Contact-settings tile->modal dialog with an inline _AutoAddSection: five SwitchListTiles that apply on toggle (send config frame + refetch flags). Apply path has a coalescing guard (re-applies the latest state instead of overlapping sends) plus inline error handling (revert to device flags + snackbar). _PathHashSizeTile onChanged wrapped in try/catch + error snackbar.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Run dart format across the repo to clean drift in 5 files (channel_chat_screen, settings_screen, settings_shell, gif_picker, mention_autocomplete) from earlier straight-to-dev commits that never hit the format CI. No logic change.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the monolithic card+dialog settings screen with a responsive master-detail shell (rail+pane when wide, list+push when narrow; layout chosen by width via LayoutBuilder). Regroup the existing settings into intent categories and surface Path Hash Size (1/2/3-byte) in Radio and range, the first device-config-parity gap (previously repeater/CLI-only).
No settings lost: every existing control stays reachable. Phase 0 of #7.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
gif_picker.dart reads GIPHY_API_KEY from String.fromEnvironment instead of a
hardcoded constant - key is supplied at build time (--dart-define-from-file=
dart_defines.json, gitignored), never in source or history. Adds
dart_defines.example.json template + .gitignore entry; picker shows a hint
when no key is configured.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Integrates the Offband rebrand (chore/offband-rebrand, 98a7728) on top of
dev, which already carries Batch-1 UX (#2) + emoji :shortcode: (#5) via
fast-forward of feat/batch1-ux.
pubspec version conflict resolved to 9.1.0+14 (emoji MINOR over rebrand
PATCH 9.0.1; build kept at the higher +14).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The maintainer's keyboard can't type emoji, so the composer now accepts
:shortcode: input with an inline autocomplete:
- Type `:` to open a bottom-anchored picker; arrows/Tab/click select, Esc
dismisses. Best/exact match sits at the bottom, nearest the caret.
- Enter completes an unambiguous emoji (single OR exact match) then sends,
so `:rofl` + Enter ships the glyph, not the literal text. Ambiguous
matches still send raw; Tab picks one deliberately.
- A completed `:name:` (closing colon) auto-converts in place.
- 1913 gemoji shortcodes bundled (lib/helpers/emoji_shortcodes.dart, MIT).
- Byte-count aware via the existing ByteCountedTextField; shares the
MentionAutocompleteField with @-mention input (both composers wired).
Version 9.0.0+12 -> 9.1.0+14 (MINOR feature; build kept monotonic after
the rebrand's +13 on the parallel chore/offband-rebrand branch).
Grid/tap picker deferred (#5 follow-up).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Freeze permanent applicationId/bundle id to app.offband.meshcore
(three-segment so app.offband.* reserves the brand family; a future
app.offband.meshtastic won't collide).
Display name "Offband Meshcore" across: Android android:label, iOS
CFBundleDisplayName, Windows + Linux window titles, MaterialApp title,
Windows VERSIONINFO (ProductName/FileDescription/CompanyName), pubspec
description, GPX creator. iOS/macOS bundle ids + Linux APPLICATION_ID
set to app.offband.meshcore.
Version 9.0.0+12 -> 9.0.1+13 (PATCH milestone + build).
Deferred (unchanged): internal Dart package name (meshcore_open imports),
Android namespace + MainActivity package, exe/bundle filenames, docs prose
(README/privacy/documentation still say MeshCore Open - separate sweep).
MIT attribution to zjs81/MeshCore preserved (LICENSE untouched).
Verified: flutter analyze clean; Android debug APK built green;
aapt2 dump packagename = app.offband.meshcore; Windows compiled clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Typing @ at the start of a word opens a bottom-anchored dropdown above
the composer, filtered live by the text after @. Recent channel senders
sort to the bottom (most recent closest to the input, auto-highlighted);
other contacts stack above in alpha order (A nearest the input), with a
Recent divider between. Up/Down move the highlight, Tab or click selects
(inserting @[ExactName] at the cursor), Esc dismisses, Enter sends.
- New MentionAutocompleteField wrapping ByteCountedTextField with an
Overlay dropdown and FocusNode.onKeyEvent key handling
- _buildMentionCandidates sources recent senders from the channel
history plus contacts, deduped (recent wins)
Verified on desktop, including wrapped/multi-line input.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The firmware path-length byte packs hop count (low 6 bits) and hash
width (high 2 bits) into one byte. The UI used it raw, so a 4-hop/2-byte
path showed as "68 hops" and the path list rendered per byte (8 entries
for 4 hops), and a 2-byte region mismatched a 1-byte one.
- Add pathHopCount/pathHashSizeBytes decoders (meshcore_protocol.dart)
- Add ChannelMessage.hopCount / pathHashSize getters; decode at display
and leave the stored pathLength untouched so the duplicate-merge in
the connector stays self-consistent
- Add an always-visible per-message hop-count badge in the channel chat
- Group path bytes into hash-width hashes in the chat "via" line and the
Message Details screen, using each packet's own width so mixed
1/2/3-byte feeds render correctly side by side
Verified against live BLE traffic (mixed 1-byte and 2-byte senders).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- channel_chat: always-visible HH:MM timestamps (theme-aware dark-mode contrast)
- channel_chat: Today/Yesterday/date day dividers between message days
- contacts: pin favorites to the top, preserving the active sort within each group
Fork of zjs81/meshcore-open (MIT). Tracking #2 (migrated from Strycher/LoRa#343).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The telemetry parser has been expanded and should now support significantly more metrics. It has been ported from the Python implementation of meshcore_py.
At the top of the channel chat screen is an icon, indicating the
channel type.
Previously, the public icon was used correctly, but the
hashtag icon was used for all other types.
Now, consistent with the channels screen, we use the lock icon for
private channels, and the composite icons for community public &
community hashtag types.
The fix for private channels was trivial, as we can identify hashtag
channels by their name. Finding out whether a channel belongs to a
community is much more involved. All the hard-working code was copied
from channels_screen.dart. (I tried refactoring to reduce duplication,
but my results were complex and not worth it.)
Closes#432
Reflect the set value immediately so UI bound to currentCustomVars
(e.g. the GPS toggle in settings) updates on tap rather than waiting
for a later device-info refresh.
- Updated _RepeaterStatusScreenState to load status after the first frame to avoid mid-build notifyListeners() calls.
- Removed unused _statusRequestedAt variable and adjusted _clockText() to use repeaterClockAtLogin for time display.
- Enhanced _SettingsScreenState with a GPS toggle switch that updates custom variables for GPS settings.
- Cleaned up RepeaterCommandService by removing redundant pending command checks and adjusted command ID generation.
- Removed jni plugin from generated_plugins.cmake for both Linux and Windows platforms.
The function emitted two consecutive 8-byte position blocks instead of
one, producing a frame 8 bytes longer than the documented layout. When
a caller passed lastModified, the firmware parsed the duplicated second
lat as the timestamp, giving wildly wrong "last seen" values on
imported contacts.
Delete the unconditional first block; keep the conditional block that
correctly skips the optional tail when neither location nor
lastModified is set, zero-fills position slots when only lastModified
is present, and appends the optional timestamp.
Adds test/connector/build_update_contact_path_frame_test.dart with
five cases covering all four optional-tail combinations plus the
fixed-point lat/lon encoding.
Fixes#427
Per issue #418, this commmit removes channel subtitles from the channel
list and from the map screen (deep in the marker sharing). This reduces
visual clutter and allows for more compact lists, and the type of
channel is already indicated by the leading icon.
The subtitles simply said "Public channel", "Hashtag channel", or
"Private channel".
We also remove the relevant localization strings.
- Remove stray Navigator.pop(context) in _markAsUnread for both contact
and channel chats so the action no longer exits the conversation
- Thread initialUnreadCount through map discovered-contact "Open Chat"
button so the unread divider/jump still fires from that entry point
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add "Mark as Unread" option to message context menu in both
contact and channel chats
- Show "New messages" divider line between read and unread messages
- Add setContactUnreadCount/setChannelUnreadCount methods to connector
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Pass initialUnreadCount to chat screens before markRead clears it
- Use two-phase scroll: jumpTo estimated offset to build lazy items,
then ensureVisible for precise positioning
- Await ensureVisible before clearing scroll guard to prevent
scrollToBottomIfAtBottom from overriding the animation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Updated localization files for multiple languages to change the representation of multi-ACK settings from a string with a placeholder to a simple string.
- Removed unnecessary placeholder definitions for multi-ACK in localization files.
- Adjusted the settings screen to replace the slider for multi-ACK with a switch, simplifying the user interface.
- Updated the Podfile.lock to remove the wakelock_plus dependency.
this adds the actual last modified timestamp when present, before we used
last advert time as last modified in error
also sets _pendingInitialContactsSync to true on first connect over BLE
Resolved conflicts by accepting refactored state management from main:
- list_filter_widget.dart: Adopt sealed class pattern for filter actions
- contacts_screen.dart: Move state to UiViewStateService instead of local setState
- device_screen.dart: Accept deletion (consolidated into other screens in main)
Main branch includes significant improvements:
- TCP and USB transport support
- Service-based state management with UiViewStateService
- Translation support with message translation buttons
- Signal UI consistency improvements
- Additional language support (hu, ja, ko)
- Comprehensive test coverage
- Discovery screen refactoring
When contacts are removed in removeContact, _handleContact, or _handleContactAdvert,
subtract their unread count from _cachedContactsUnreadTotal immediately so badge
counts reflect the true total without waiting for a full reload.
adds a new widget that counts bytes during entry
configurable limit and shows user both count and limit
provides color feedback
use new widget in chat and channel text entry
- Added translations for "Send message", "Guest information", and "Guest tools" in Bulgarian, German, Spanish, French, Hungarian, Italian, Japanese, Korean, Dutch, Polish, Portuguese, Russian, Slovak, Slovenian, Swedish, Ukrainian, and Chinese.
- Updated the "Clock synchronization after login" feature subtitle in all affected languages.
- Removed untranslated keys from the untranslated.json file as they have now been localized.
this adds a generator showDismissibleSnackBar which by default allows
tapping to clear snack bar toasts. all SnackBar properties are still
available and the
all callers should now use showDismissibleSnackBar() instead of calling
ScaffoldMessenger.of(context).showSnackBar(SnackBar())
Introduced a new setting for automatic clock synchronization after a successful repeater login.
Added localization support for the new feature in multiple languages (Bulgarian, German, English, Spanish, French, Hungarian, Italian, Japanese, Korean, Dutch, Polish, Portuguese, Russian, Slovak, Slovenian, Swedish, Ukrainian, Chinese).
Implemented storage service methods to manage the new setting.
Updated the repeater settings screen to include a toggle for the new feature.
Enhanced the repeater login dialog to trigger clock synchronization automatically if the setting is enabled.
- Replace floating-point epsilon frequency comparison with integer Hz
- Add frequencyHz getter and fromMeshCoreSnapshot/toMeshCoreSnapshot
conversion methods on _RadioSettingsSnapshot
- Move _toUiCodingRate/_toDeviceCodingRate to documented top-level functions
- Gate _logRadioSettingsState behind kDebugMode
- Use integer Hz in == and hashCode for _RadioSettingsSnapshot
Addresses code review findings on preset/off-grid repeat toggle PR.
* Refactor contact filtering and improve localization strings; enhance path trace handling
* Add localization for new CLI commands and update existing strings
* Enhance contact handling and UI updates across multiple screens
add unfiltered contact access and improve last seen resolution
* Add polling interval configuration and improve contact handling
* Reorder command constants for better organization and clarity
* Refactor contact handling by removing unnecessary mapping and improving clarity across multiple screens
* Moved RadioStatsIconButton in chat screen for improved UI consistency
* Added indicators to AppBar for channels
* Ignore contacts with self public key in contact handling
* Simplify path removal logic and clean up unused imports in path management dialog
* Enhance path hop resolution by adding distance checks to improve candidate selection accuracy
* Remove unnecessary reset of radio stats poll reference count in polling interval setter
* Refactor contact filtering and improve localization strings; enhance path trace handling
* Add localization for new CLI commands and update existing strings
* Enhance contact handling and UI updates across multiple screens
add unfiltered contact access and improve last seen resolution
* Add polling interval configuration and improve contact handling
* Reorder command constants for better organization and clarity
* Refactor contact handling by removing unnecessary mapping and improving clarity across multiple screens
* Moved RadioStatsIconButton in chat screen for improved UI consistency
* Added indicators to AppBar for channels
* Ignore contacts with self public key in contact handling
* Simplify path removal logic and clean up unused imports in path management dialog
* Enhance path hop resolution by adding distance checks to improve candidate selection accuracy
* Remove unnecessary reset of radio stats poll reference count in polling interval setter
This adds Giphy page URLs and `media.giphy.com` URLs (with and without
protocols) as *accepted* encodings for GIF messages, alongside the `g:`
syntax.
When someone posts such a URL by itself as a message, it will be rendered inline just like `g:` messages are now.
This does not change the encoding that GIF messages are *sent* in; that
is still the `g:` syntax.
* Refactor contact filtering and improve localization strings; enhance path trace handling
* Add localization for new CLI commands and update existing strings
* Enhance contact handling and UI updates across multiple screens
add unfiltered contact access and improve last seen resolution
* Add polling interval configuration and improve contact handling
* Reorder command constants for better organization and clarity
* Refactor contact handling by removing unnecessary mapping and improving clarity across multiple screens
* Moved RadioStatsIconButton in chat screen for improved UI consistency
* Added indicators to AppBar for channels
* Ignore contacts with self public key in contact handling
* Simplify path removal logic and clean up unused imports in path management dialog
* Enhance path hop resolution by adding distance checks to improve candidate selection accuracy
* Remove unnecessary reset of radio stats poll reference count in polling interval setter
- Introduced translation functionality in chat screen, allowing users to translate messages before sending.
- Added MessageTranslationButton to the input bar for enabling/disabling translation.
- Implemented translation service to handle incoming and outgoing text translations using llama models.
- Enhanced message storage to include original and translated text, language codes, and translation status.
- Created UI components for displaying translated messages and managing translation options.
- Added translation model management, including downloading and storing models locally.
- Updated app settings to manage translation preferences and model selections.
- Replace floating-point epsilon frequency comparison with integer Hz
- Add frequencyHz getter and fromMeshCoreSnapshot/toMeshCoreSnapshot
conversion methods on _RadioSettingsSnapshot
- Move _toUiCodingRate/_toDeviceCodingRate to documented top-level functions
- Gate _logRadioSettingsState behind kDebugMode
- Use integer Hz in == and hashCode for _RadioSettingsSnapshot
Addresses code review findings on preset/off-grid repeat toggle PR.
* Refactor contact filtering and improve localization strings; enhance path trace handling
* Add localization for new CLI commands and update existing strings
* Enhance contact handling and UI updates across multiple screens
add unfiltered contact access and improve last seen resolution
* Add polling interval configuration and improve contact handling
* Reorder command constants for better organization and clarity
* Refactor contact handling by removing unnecessary mapping and improving clarity across multiple screens
* Moved RadioStatsIconButton in chat screen for improved UI consistency
* Added indicators to AppBar for channels
* Ignore contacts with self public key in contact handling
* Simplify path removal logic and clean up unused imports in path management dialog
* Enhance path hop resolution by adding distance checks to improve candidate selection accuracy
* Remove unnecessary reset of radio stats poll reference count in polling interval setter
- Reintroduced Bluetooth pairing PIN title, prompt, show, and hide strings in English, Spanish, French, Hungarian, Italian, Japanese, Korean, Dutch, Polish, Portuguese, Russian, Slovak, Slovenian, Swedish, Ukrainian, and Chinese.
- Updated localization files to ensure consistency and clarity in user prompts related to Bluetooth pairing.
- Companion radio stats: poll and display noise floor, RSSI, SNR, airtime
with dedicated ValueNotifier and ref-counted polling
- Adaptive RF-aware TX backoff based on radio conditions instead of fixed 5s
- Variable-width path hash support (1-3 bytes per hop)
- Air activity dot indicator in app bar with tap to open stats screen
- Jump to oldest unread setting for chat screens
- 1s send cooldown on DM and channel messages
- Link style: theme-aware orange, added EmailLinkifier
- New languages: Hungarian, Japanese, Korean
- Remove dead DeviceScreen and BatteryIndicatorChip
- Remove wakelock_plus dependency
- TX power fields now read as signed int8
- Implemented radio statistics features in multiple screens including chat, channels, and settings.
- Added localization for new strings in Swedish, Ukrainian, and Chinese.
- Introduced a setting to jump to the oldest unread message in chat and channels.
- Enhanced path management and display for contacts and messages.
- Updated app settings to include new boolean for jumping to the oldest unread message.
- Improved battery indicator and radio stats display in the app bar.
- Removed unused wakelock_plus dependency and updated plugin registrations.
Add Linux BLE pairing helper that drives bluetoothctl for pair/trust/PIN
entry, with Completer-based flow control, explicit retry loop, and named
timeout constants.
- LinuxBlePairingService: pair-and-trust with up to 2 retries
- LinuxBleErrorClassifier: map bluetoothctl stderr to user-facing errors
- Conditional import stub for web builds (dart.library.io gate)
- Scanner screen: PIN dialog integration for Linux pairing flow
- MeshCoreConnector: Linux pairing/recovery/reconnect wiring
- l10n: 4 new pairing keys across all 14 locales
- 12 unit tests (pairing service + error classifier)
- Added storageUsedKb and storageTotalKb properties to MeshCoreConnector.
- Updated battery and storage frame parsing with improved error handling.
- Refactored log RX data handling to use BufferReader for better readability and error management.
- Enhanced message parsing in ChannelMessage and Message classes to utilize BufferReader.
- Introduced new text type for signed messages in meshcore_protocol.dart.
- Updated BLE debug log screen to use BufferReader for payload parsing.
- Refactored message retry service to handle ACK hashes as integers instead of Uint8List.
- Improved message storage serialization and deserialization to accommodate new expectedAckHash type.
- Added wasPulled property to Contact model for better state management.
* feat: Enhance privacy settings and telemetry
- Implemented telemetry options for contacts, allowing users to enable or disable telemetry data sharing.
- Introduced a clear chat option in the chat interface for better message management.
- Updated the telemetry screen to handle telemetry data for contacts, including battery level.
- Refactored contact settings to include telemetry options and improved UI for better user experience.
* feat: Refactor repeater resolution logic across multiple screens
* feat: Enhance privacy settings and telemetry
- Implemented telemetry options for contacts, allowing users to enable or disable telemetry data sharing.
- Introduced a clear chat option in the chat interface for better message management.
- Updated the telemetry screen to handle telemetry data for contacts, including battery level.
- Refactored contact settings to include telemetry options and improved UI for better user experience.
* feat: Refactor repeater resolution logic across multiple screens
- Fix operator precedence bug in _handleAutoAddConfig where `flags &
flag != 0` was parsed as `flags & (flag != 0)`, always checking bit 0
instead of the correct flag bit
- Populate _contacts from cache in loadContactCache() so contacts
persist across app restarts
- Toggle DTR low→high on USB connect to force device to see a fresh
connection
- Add 10ms inter-frame delay for USB sends to prevent missed responses
- Deassert DTR before closing USB port on disconnect/dispose
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix asymmetric lat/lon validation in _handleContactAdvert (was checking
longitude != 0 for latitude; now uses (latitude != 0 || longitude != 0)
for both)
- Remove duplicate targetGuessed assignment in path_trace_map
- Rename public target field to private _targetContact, use local variable
to avoid unnecessary null-aware operators
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Move ContactSortOption/ContactTypeFilter enums to dedicated
contact_filter_types.dart (re-exported from contact_search.dart)
- Migrate ContactsFilterMenu and DiscoveryContactsFilterMenu to use
sealed class action types with SortFilterMenu<T> generics, replacing
int action constants and switch statements
- Guard _closeDropdownAndRun with ModalRoute.isCurrent check to prevent
accidental dismissal of parent routes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Clamp ML predictions between physics floor (raw airtime) and ceiling
(worst-case formula) so model can never produce unsafe timeouts
- Replace hourOfDay feature with secondsSinceLastRx for network activity
- Remove unused _ContactStats.stdDev and dead model persistence code
- Debounce observation writes (2s) instead of writing on every delivery
- Skip recording observations when pathLength is null to avoid corrupting
training data
- Add comment explaining global (not per-contact) RX time tracking
- Remove notifyListeners from retrain to avoid unnecessary widget rebuilds
- Run dart format
Train a linear regression model on actual message delivery times to
predict tighter timeouts, replacing worst-case physics estimates.
Features: path length, message bytes, seconds since last RX, flood mode.
Global model with per-contact blending after 10+ observations per contact.
Falls back to existing physics formula when model has insufficient data.
Add "Set as my location" option to the map long-press bottom sheet,
allowing users to set their device position directly from the map.
Includes connector, chat, contacts, and message retry service improvements.
- Replace thin MeshCoreTcpManager facade with a proper MeshCoreTcpConnector
that owns TcpTransportService and the frame subscription, mirroring
MeshCoreUsbManager. The connector no longer holds a raw TcpTransportService
or a _tcpFrameSubscription field.
- Remove hardcoded default host IP from TcpScreen (keep port 5000 hint).
- Disable connect button during scanning state, not just connecting state.
- Fix tcpPortLabel mistranslated as nautical "port/harbor" in de, it, pt,
nl, sv, sk, sl, zh; fix corrupted Slovak tcpPortHint ("5 000" → "5000").
- Remove unused tcpStatus_connecting string from all 15 locale arb files
and all generated app_localizations_*.dart files.
- Add extendedPadding to TCP screen FABs to match USB screen.
- Add Key to connect button; update tests to use byKey and assert
onPressed == null when button is disabled during scanning.
* Refactor contact handling: replace DiscoveryContact with Contact, update related methods and settings
* Enhance contact handling: include latitude, longitude, and last modified timestamp in contact updates; refactor path handling to accommodate discovered contacts across multiple screens
* Enhance SNRIndicator: include discovered contacts in name resolution for repeaters
* Refactor path handling: replace addReturnPath with buildPath to improve path construction logic and handle target contact types
* Update lib/screens/map_screen.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Add localization for "Show Discovery Contacts" in multiple languages and refactor location plausibility check in map screen
* Enhance contact management: update discovered contacts' active status and improve contact handling with flags and raw packet data
* Refactor ChannelsScreen: pass ChannelMessageStore to buildExpandedContent and ensure messages are cleared after channel creation
* Update MapScreen: adjust label zoom threshold and refactor guessed marker building to include labels
* Refactor ChannelsScreen: change channelMessageStore to a private getter and update its usage in buildExpandedContent calls
* Enhance location plausibility check: add latitude and longitude bounds to ensure valid coordinates
* Update lib/connector/meshcore_connector.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Refactor MeshCoreConnector and related stores: update discovered contacts handling, migrate legacy keys, and set public key in community store
* Refactor MeshCoreConnector and ChannelsScreen: update discovered contacts handling and set public key in community store; enhance location plausibility check in MapScreen
* Update CMD_ADD_UPDATE_CONTACT frame format to include optional latitude and longitude fields
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
- add connectTcp cancellation guards after socket connect and connect delay so handshake does not proceed when transport/state changed
- ignore late TCP connect errors after manual cancel or transport switch to avoid spurious second disconnect paths
- keep TCP action hidden only on web and show Bluetooth action on USB screen across platforms for navigation consistency
- Implemented TCP transport service for native platforms.
- Added TCP connection screen with input fields for host and port.
- Integrated TCP connection options into the scanner and USB screens.
- Updated localization files for new TCP-related strings.
- Added tests for TCP connection flow and error handling.
- Enhanced USB screen to include TCP connection option.
- Improved layout to ensure no overflow in narrow widths for scanner and USB screens.
On web BLE, contact sync is deferred until channel 0 arrives via
_handleChannelInfo. If channel 0 times out or channel sync completes
without it, _pendingInitialContactsSync stays true and contacts never
load. Add fallback in _cleanupChannelSync to trigger getContacts() if
the flag is still set when channel sync ends.
- Rewrite UsbScreen to mirror ScannerScreen patterns (status bar,
tap-to-connect port list, bottom FABs, SnackBar errors)
- Extract MeshCoreUsbManager from MeshCoreConnector for cleaner
USB transport ownership
- Add debug logging throughout USB connection flow (connector,
manager, web/native services)
- Print debug logs to console in debug mode even when app debug
log setting is disabled
- Localize remaining hardcoded strings (Web Serial Device fallback
label, USB status bar keys, companion firmware timeout hint)
- Fix Swedish misspelling in translations (stöderliga → stödda)
- Guard Linux notification init against missing D-Bus session bus
- Fix SNRIndicator hit-test error by adding minimum size constraints
- Update USB flow tests for new UI patterns
Adds map_showGuessedLocations and map_guessedLocation to app_en.arb and translates them across all 14 supported locales. Regenerates dart localizations.