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