15 Commits
Author SHA1 Message Date
Jakub f40748ba61 fix(ui): unify the four navigate views, add Set as target everywhere
Continuing the consistency pass: the four screens that show the
distance/bearing "navigate to a point" view (Nodes, Waypoints, Trail's
Track back, and navigating to a location shared in a message) had drifted
apart in three ways.

Only two of the four passed an EtaTracker to navview::draw(), so only
Nodes and Track back showed the closing-speed/ETA line -- navigating to a
waypoint or a shared location left it off for no reason. All four get one
now. They also left the view on three different key sets (Back alone,
Back+LEFT/RIGHT, Back+Enter); Back is now the only way out of any of them,
so a stray sideways nudge can't drop you out of a running track-back.
Messages' renderNav() also switched from reading node_prefs directly to
the shared useImperial() helper the other three already used.

Set as target -- the row Nodes and Waypoints both offer for a coordinate
-- was missing from the message-location Options menu; added alongside
Navigate and Save waypoint.

Nodes' own Set as target required a full 32-byte public key, which a
name-only live-track entry (someone sharing position on a channel who
isn't a saved contact -- the group-outing case this exists for) never
has. One flag was doing two jobs: "can be pinged" (needs the full key)
and "can be identified" (needs only the 6-byte prefix a person target
actually uses). Split into has_key/has_prefix; Set as target now only
needs a position, resolving to a person target (follows them) when a
prefix is available and a place target (pinned where they were) when it
isn't -- the same distinction Locator's own picker already draws.

Locator's target picker separately still listed the people pinned to the
Favourites Dial as its privileged top tier, which stopped making sense
once pinning and favouriting became separate concepts. It now leads with
favourites instead, matching every other list in the firmware.
2026-08-31 19:25:08 +02:00
JakubandClaude Sonnet 5 1bb28296c2 feat(ui): marquee-scroll selected long names/labels instead of static ellipsis
Selecting a row whose ellipsized text overflows now animates a "swing"
marquee: holds at the start, scrolls to reveal the full tail, holds
there, then scrolls back and repeats. Unselected/non-overflowing text
is unchanged (still a static "..."). E-ink gets slower, coarser steps
(fewer, cheaper partial refreshes) than OLED; unchanged frames are
already skipped by the display's CRC diff, so idle holds are free.

Wired into every screen with a selectable row: home favourites, DM/
channel lists and message bodies, Settings, popup menus, Bot, Admin,
Nearby, Waypoints, Locator, Live Share, and the alarm screen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-30 17:17:55 +02:00
MarekZegare4 55cb5d25d3 fix(gps): hold GPS awake during waypoint nav/avg/track-back and message-location navigate
These views run the same live bearing/distance readout as Compass/Nearby's
navigate mode, which already held GPS awake -- these three didn't, so
duty-cycling could leave them stuck on a stale fix until the next scheduled
wake (up to the configured sleep interval).
2026-08-14 00:40:50 +02:00
MarekZegare4andClaude Opus 4.8 4d795ade51 feat(companion): track-back — retrace the recorded trail to its start
Add Tools › Trail → Hold Enter → Track back (shown when the trail has ≥2
points). It reuses NavView but, instead of a single fixed target, snaps onto
the route at the nearest recorded breadcrumb and walks the points in reverse,
auto-advancing the target as each is reached (within 20 m). The header shows
points remaining ("Back: 12 pt" → "Trail start"); reaching the start toasts
"Back at start" and exits. Cancel leaves at any time.

Lives in WaypointsView as a new sub-mode; advancement runs in poll()
(forwarded from TrailScreen) so it tracks GPS independently of the render
cadence. An EtaTracker drives the approach/ETA line and is reset on each
breadcrumb advance to avoid a bogus closing-speed spike.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 19:27:54 +02:00
MarekZegare4andClaude Opus 4.8 c60a7a7f64 feat(companion): GPS averaging for waypoint marking (Mark avg)
Add a "Mark avg" trail setting (Off/5/10/30 s). When set, Tools › Trail ›
Mark here samples the GPS fix once a second for the chosen window and stores
the mean position instead of one instantaneous fix — a steadier, more
accurate mark for a precise spot. A progress screen shows time left + sample
count; Cancel aborts; the window then opens the label keyboard as usual.
Off (default) keeps marking instant.

Sampling runs in WaypointsView::poll() (forwarded from TrailScreen::poll())
so it ticks on the main loop, independent of the slow e-ink render cadence;
int64 accumulators avoid overflow over 30 samples.

Persisted as NodePrefs::gps_avg_idx (schema 0xC0DE0016): struct field +
option table, DataStore rd/write/clamp in lockstep. sizeof unchanged (the
uint8_t fits existing tail padding) so the 2488 tripwire still holds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 19:19:43 +02:00
MarekZegare4andClaude Opus 4.8 cc88e89a64 refactor(companion): extract drawRowSelection() for the canonical list-row bar
Every drawList/AccordionList row opened with the same hand-written
display.drawSelectionRow(0, y-1, width-reserve, lineStep-1, sel) line —
14 copies of the same geometry and magic offsets. Add a drawRowSelection(d,
y, sel, reserve) helper in icons.h next to drawList and route the canonical
sites through it (Bot/LiveShare/Locator/Repeater/Settings/Tools/Nearby/
QuickMsg/Waypoints).

Rows that intentionally differ (full-width DashboardConfig/QuickMsg, the
keyboard/ringtone grids) keep their explicit drawSelectionRow() call — the
helper is opt-in rather than baked into drawList, so legitimate variants
aren't forced into one geometry. Framework doc updated to match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 18:53:18 +02:00
MarekZegare4andClaude Opus 4.8 089048a036 refactor(ui): align screens to the documented UI conventions
Audit pass against docs/design/solo_ui_framework.md, converging deviations:

- prev/next: replace hand-rolled (c == KEY_LEFT || c == KEY_PREV) with
  keyIsPrev()/keyIsNext() in QuickMsgScreen, RingtoneEditor, TrailScreen,
  FullscreenMsgView. Two were real rotary-encoder gaps, not just style:
  NearbyScreen's filter and WaypointsView's hemisphere toggle used raw
  KEY_LEFT/RIGHT with no encoder twin, so the encoder couldn't drive them.
- NearbyScreen's list right column used a manual setCursor(width -
  getTextWidth(...)) instead of the existing drawTextRightAlign() helper
  (which also UTF-8-translates) — now uses it, like Diagnostics/Repeater.
- RepeaterScreen hand-rolled the whole drawList skeleton (scroll clamp +
  reserve + row loop + indicator); collapsed onto drawList().

No D-pad behaviour change; more uniform encoder support. Net -19 lines.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 18:01:29 +02:00
MarekZegare4andClaude Sonnet 4.6 5c4607cca9 fix(companion): clear stale Locator/Live Share/favourite refs on delete
Deleting a waypoint left the Locator pointed at coordinates that no longer
existed (it's a coordinate snapshot, so nothing noticed). Removing a contact
was worse: nothing cleared its favourite slot, its Locator/Live Share target,
or its per-contact mute/melody entry, so all four kept referencing a pubkey
that no longer resolved to anything.

- WaypointsView's Delete now calls UITask::clearTargetIfWaypoint() first.
- New AbstractUITask::onContactRemoved() hook, called from MyMesh.cpp's
  CMD_REMOVE_CONTACT handler, clears the favourite slot, the Locator target,
  and dm_notif/dm_melody entries for that pubkey. Live Share's DM target
  turns auto-share off instead of guessing a new recipient.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-26 17:48:05 +02:00
MarekZegare4andClaude Opus 4.8 57774d41f3 feat(companion): live location sharing, Locator geofencing, trail auto-pause
Squash merge of feat/location-beacon-alerts-autopause (v1.21).

Features:
- Live Location Sharing — broadcast position as movement-gated [LOC]
  messages to a channel or contact; live shares show as map pins with
  distance/bearing in Nearby Nodes and a status-bar indicator. [LOC] is
  parsed in DMs, channel messages and room messages; DM shares name the
  sender.
- Locator (geofence) — arm a geofence around a saved waypoint or a person
  (live [LOC] or last-known position), alert on arrive/leave or near/far,
  with an optional homing beeper (gated to arrive/both modes). Arm from
  Tools > Locator, Nearby Nodes, or Waypoints; target picker lists
  favourites first, clearable via a "None" entry. Active target is drawn
  as a flag on the map.
- One active target shared across Locator / Navigate / Map via a single
  resolver (resolvePersonPos / activeTargetPos) that prefers a live
  [LOC] share over the last-advertised GPS fix.
- Follow live contacts — Navigate to a live-sharing contact follows them
  as they move and adds an ETA line; quick-share your own position from
  the Map.
- Map & status-bar upgrades — home mini-map gets a north marker, scale
  tick, and a connected trail line (was disconnected dots); status line
  shows tracked-node count, an arrow + distance to the active Locator/Nav
  target (falling back to the nearest live-tracked contact); GPS fix icon
  in the top status bar, shown only on GPS boards with GPS enabled.
- Trail auto-pause — recording freezes on stops (banking elapsed time,
  breaking the map line across the idle gap) and resumes on movement
  without ending the session.
- Streaming trail simplification — GPS points are simplified in-stream
  via a fixed-corridor (Reumann-Witkam) pass tuned for fidelity: straight
  runs collapse to their endpoints, curves stay bounded to the Min-dist
  tolerance, so the 512-point buffer covers a far longer route than a
  flat point budget would suggest.
- Collapsible Tools (Location / Comms / System sections, fold-in-place
  like Settings) and page-indicator icons on the home carousel.
- Waypoint coordinate editor — add a waypoint by scroll-editing lat/lon
  digit by digit.

Fixes:
- Critical: low-heap hang and contact loss on RAM-tight builds. Halved
  message-history scrollback rings (recovering ~14 KB free heap) and
  made contacts/channels/prefs persistence atomic (temp file + rename),
  so an interrupted save can no longer corrupt or wipe the store.
- Serial.write() bounded so a stalled USB host can't hang the device.
- Nearby Nodes: live [LOC] senders respect the type filter, sort by
  shared position, and the list refreshes so live shares bubble up.
- Map: live contacts are labelled before waypoints.
- GPS status icon hidden when GPS is off in Settings.
- Splash screen no longer truncates a pre-release tag's own dash (e.g.
  v1.21-rc1) when stripping the build's commit-hash suffix.
- Null-guarded the Locator target picker; clamped loc-share channel
  index on load.

Under the hood:
- -Os size optimisation on the e-ink and GAT562 30S solo envs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 19:04:21 +02:00
JakubandClaude Sonnet 4.6 92643d1721 fix(ui): scale trail-map markers for e-ink, add wrap-around nav
Trail map dots/diamonds/cross/start markers and the north indicator were
drawn as raw unscaled pixels, so they shrank to near-invisible specks on
landscape e-ink's larger font scale; they now route through the mini-icon
framework (icons.h) like the rest of the UI, and the grid intersection dots
scale with it too. The keyboard's preview/grid separator had the same bug
(hardcoded 1px) and now uses display.sepH().

Also makes UP/DOWN (and the keyboard's LEFT/RIGHT/UP/DOWN) wrap top<->bottom
across every list, menu and form selector, matching the behavior PopupMenu
already had.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 09:16:57 +02:00
JakubandClaude Opus 4.8 f591cddf4f refactor(ui): audit-pass fixes + dead-code/redundancy cleanup
Fixes from a full ui-new audit (OLED + e-ink both build green):
- remove dead DisplayDriver::drawScrollArrows (all lists use drawScrollIndicator)
- SettingsScreen selection bar -> lineStep()-1, matching the drawList screens
- HomeScreen: don't force the 1s blink refresh on the CLOCK page (status icons
  are hidden there anyway)
- FullscreenMsgView: clamp KEY_DOWN scroll to a cached _max_scroll instead of
  over-incrementing and leaning on the next render to clamp
- DataStore: clamp use_lemon_font (>1 -> 0) on load, like the other enum fields
- move the ~1.5KB wrap scratch (trans/lines) off the render stack into shared
  file-scope statics in FullscreenMsgView (single-threaded UI; the fullscreen
  view and history list never lay out in the same frame)

Cleanup:
- drop dead members RingtoneEditorScreen::DUR_VALS and HomeScreen::sensors_scroll
- delete redundant manual scroll-clamps in handleInput across the drawList
  screens (drawList already reclamps each render); remove the now-unused _visible
  from QuickMsgScreen/NearbyScreen/BotScreen and BotScreen::scrollToSel()

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 08:54:11 +02:00
JakubandClaude Opus 4.8 9da730e3b6 refactor(ui): shared drawList + header + key-decode helpers
- drawList(): scrollable list skeleton (visible window, keep-sel-in-view,
  scroll-column reserve, per-row callback, indicator). Migrate Tools, Bot,
  Nearby, Waypoints list and the three QuickMsg lists onto it; centralises the
  scroll-clamp/reserve/indicator that each had open-coded (and inconsistently).
- drawCenteredHeader(): centred title + separator, replacing the duplicated
  two-liner across ~10 screens (incl. QuickMsg, which used width()/2 spacing and
  was missed before); unifies the separator at headerH()-sepH().
- keyIsPrev()/keyIsNext() in UIScreen.h for the LEFT||PREV / RIGHT||NEXT idiom;
  applied to 6 screens, behaviour-preserving. Cancel/context-menu stay
  per-screen (they mean different things on different screens).
- rotLabel() dedups the 0/90/180/270 array in Settings.
- Unify selection-row height to lineStep()-1 (Tools/Bot matched the rest).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 21:23:00 +02:00
MarekZegare4andClaude Opus 4.8 6e4177f3d6 feat(ui): proportional scrollbar with mini-icon caps; reclaim row space
Replace the text ^/v scroll arrows with a font-scaling indicator drawn in
icons.h: a proportional thumb between fixed up/down triangle mini-icon caps.
drawScrollIndicator() lives in a ~5px right-edge column and scales via
miniIconScale (1x OLED, 2x landscape e-ink).

- Mini-icons: ICON_SCROLL_UP/DOWN; miniIconDrawTop (exact placement) and
  miniIconDrawHalo (shape-hugging 1px DARK halo, clip_top to spare the header
  separator) so caps stay visible on the LIGHT selection bar without a box.
- Thumb is 3*s wide so it centres on the triangle column; caps are static end
  markers (always drawn) so neither vanishes at the extremes.
- scrollIndicatorReserve(): right-edge gutter (0 when the list fits). Content,
  the selection bar and the message-history bubbles are all narrowed by it so
  nothing renders under the scrollbar. Portrait e-ink wrap width subtracts the
  reserve in both the box-height pass and render so wrapped text can't spill.
- Drop the redundant ">" selection marker (the highlight bar already shows
  selection) and shift rows to x=2, reclaiming left-edge space.
- Wire the indicator into every scrollable list (Bot, Settings, Nearby, Tools,
  Trail, QuickMsg lists + DM/channel history) and add one to WaypointsView,
  which scrolled with no indicator before.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 14:41:46 +02:00
MarekZegare4andClaude Opus 4.8 8c0ccaed73 feat(ui): keyboard overhaul — shared instance, icon keys, symbols page
- Unify on the shared keyboard: WaypointsView drives UITask::keyboard()
  instead of its own instance; openKb() clears {loc}/{time} for literal
  fields (labels, coordinates). Reclaims the duplicate widget from the heap.
- Icon glyphs on the special row: caps ⇧, space ⎵ (two halves), delete ⌫,
  OK ✓ (reuses ICON_CHECK); {} and the page toggle stay text.
- Second character page (symbols), toggled by a "#@"/"abc" key. Tidy the
  letters page: drop the duplicate grid space, add the comma, group
  punctuation as . , ! ?
- Harden the preview buffers (KB_PREVIEW_CAP) against very wide displays.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 10:47:50 +02:00
MarekZegare4andClaude Opus 4.8 38e9273ec7 refactor(ui): reorganize Tools › Trail (popup, map, waypoints)
Tidies the Trail screen end to end, with no functional change beyond the
intended UX cleanups. Squashed from refactor/trail-screen.

Popup:
- Two-level action menu (Hold Enter): short main menu (Start/Stop, Mark
  here, Waypoints…, Trail file…, Settings…) plus "Trail file…" and
  "Settings…" submenus, replacing one flat ~12-item list that mixed
  settings and actions. One interaction pattern per level; Reset lives in
  Trail file…, away from a stray Enter. Settings submenu is view-aware
  (Grid only on Map, Readout only on Summary).

Map:
- MapProjection (geo→pixel) built once and shared by the map, grid and
  marker drawing — removes the duplicated projection math and renderGrid's
  11 scalar params.
- renderMap split into computeBounds() and drawMarkers() helpers.

Grid:
- Square cells fitted to the frame: the shorter side is divided into a
  whole number of equal cells (grid touches that pair of borders), and the
  longer side centres the whole cells that fit — square, symmetric, no
  one-sided drift. Drawn as one dot per intersection (legible on OLED).

Waypoints:
- Extracted the whole waypoint management UI (list / navigate / add-by-
  coords / mark / rename / delete / send) into a self-contained
  WaypointsView component that TrailScreen owns and delegates to.
- Dropped the redundant bulk "Clear all" (per-waypoint Delete covers it).

Docs updated (tools_screen.md) + design rationale (docs/design/
trail_redesign.md). Builds clean on solo envs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 00:05:49 +02:00