12 Commits

Author SHA1 Message Date
Jakub
cf3c2c0353 fix(ui): revert corner-anchored context menus, centre unread badge, add room logout
- Context menus (PopupMenu, Nearby/QuickMsg call sites) go back to centring on
  screen; the header's discoverable-menu glyph stays, but dropping the popup
  out of its corner looked bad on some screens.
- Unread pill badge digit wasn't centred: Adafruit_GFX's classic built-in font
  (SH1106/SSD1306) always pads a measured string by one trailing advance
  column regardless of the glyph drawn, so centring on the raw width left 1px
  more slack on the right than the left. New DisplayDriver::
  textWidthTrailingGap() (0 by default) corrects for it on those two backends.
- On-device room Logout: mirrors the app's CMD_LOGOUT (drops keep-alive
  tracking, forgets the saved password) so a room can be deliberately signed
  out of from the Room options menu, not just re-logged-in.

Not build-verified — no PlatformIO toolchain available this session.
2026-07-10 16:01:14 +02:00
Jakub
a1ee67ae94 feat(ui): discoverable context-menu hint — header glyph, highlight, corner dropdown
Hold-Enter context menus were invisible. Add a menu affordance to every screen
that has one:

- A small ≡ glyph in the header's top-right, via new DisplayDriver::
  drawContextMenuHint() and an optional menu_hint arg on drawCenteredHeader() /
  drawInvertedHeader() (reserves the corner so the title never runs under it).
- The glyph highlights (corner cell filled, bars knocked out) while the menu is
  open, tying the hint to the popup it spawned — driven by a menu_open flag the
  screens pass from their live popup state.
- The context menu now drops out of that corner: PopupMenu gains an opt-in
  top-right anchor (begin(..., anchor_top_right)); it right-aligns under the
  header instead of centring, so the menu reads as emerging from the ≡. Default
  stays centred, so non-context popups (Tools/Settings/etc.) are untouched.

Enabled on Nodes (list/scan/detail) and Messages (mode select, contact/room and
channel pickers, DM and channel history).

Both solo envs build green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 10:31:01 +02:00
Jakub
32c50d1cfe feat(repeater): politeness controls, dedicated radio profile, and diagnostics
Settings > Radio gains a repeater toggle, 16 community-suggested radio
presets plus manual Freq/SF/BW/CR tuning (digit-by-digit Freq editor),
4 persisted user preset slots, and the packet pool bumped 16->32 so
queued retransmits stop starving incoming-packet allocation while
relaying.

Four opt-in politeness knobs (skip-advert, max-hops, yield, min-SNR),
all flood-only and off by default, plus overhear suppression that
cancels a queued retransmit if a peer relays the same packet first.
Adaptive Power Control is suppressed while relaying (pins TX power to
the ceiling) and duty-cycle RX is forced off, since a repeater needs
to hear and relay at consistent power.

A dedicated Tools > Repeater screen consolidates the toggle, the
politeness knobs, and live forwarding stats, plus an optional "Custom"
radio profile — a dedicated frequency/SF/BW/CR for relaying, separate
from the companion's own network, band-matched to the companion
frequency by default and used everywhere a radio change can happen
(boot, on-device toggle, app-driven CMD_SET_RADIO_PARAMS, Settings
radio edits) so the device never silently falls back to the wrong
params mid-relay.

Diagnostics gains the actually-forwarded packet count, real heap-free
via mallinfo(), a reset-counters popup, and hardened loop-detect
bounds. Also: a frequency-floor fix and a float-equality preset-match
fix in the repeater profile logic, deduped BW-table/valCol/profile-
seeding helpers shared between Settings and the Repeater screen, and
a build.sh fix tolerating control characters in `pio project config`'s
JSON dump.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 09:07:00 +02:00
Jakub
99cbe51797 fix(ui): popup selection bar stops before the scroll-indicator gutter
Matches every other list (drawList's row width - reserve): the highlight
shouldn't paint over the indicator's column, just frame it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 18:06:59 +02:00
Jakub
b85522b2c2 fix(ui): popup menu uses the shared scroll indicator, not raw ^/v glyphs
PopupMenu drew its own unscaled ^/v text markers instead of the
track+thumb indicator the rest of the UI uses, so it didn't show position/
proportion and looked inconsistent on e-ink. drawScrollIndicator(Px) assumed
a full-width list (anchored to d.width()), which doesn't hold for a centred,
narrower popup box — added a right_x-aware overload so it can anchor to the
box's own edge instead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 18:00:06 +02:00
Jakub
1bf90ed2fb fix(ui): drop popup menu's header gap, tighten row tiling
PopupMenu is compact and stands alone (no full-screen header to separate
from), so the gap meant for listStart() just wasted vertical space and let
the selection bar's edges miss the box border by a few px.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 15:33:18 +02:00
Jakub
e5ff2de3e4 fix(ui): self-sizing popup menu, scaled for landscape e-ink
The popup menu used fixed pixel constants (box width, item height, text
positions) sized for the OLED 128x64. On landscape e-ink the font is ~2x, so
rows overlapped, text ran past the box edge, and the box never adapted to its
contents.

Rebuild render() from live font metrics: box width fits the widest title/item
(clamped to the screen, centred), row height and title bar derive from the line
height, items and title are ellipsized so nothing overflows, and the scroll
arrows live in a reserved gutter. The title separator uses the scaled sepH()
with a 2px gap before the first row so content no longer touches the line.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 21:35:40 +02:00
MarekZegare4
4145185031 chore: address code-audit hygiene (stale refs, dead TODO, popup scroll)
- FEATURES audit: refresh references the Nearby/Trail refactors made stale —
  M4 (renderDiscoverDetail → renderScanDetail), L1 (SNR %.1f now in scan detail
  + ping; list cards show RSSI), Trail grid (round-step renderGrid + MIN_GRID_PX).
- MyMesh: drop the obsolete `// TODO: add expected ACK to table` — the code
  right below already populates expected_ack_table.
- PopupMenu: render() is now the single source of truth for _scroll, clamping
  the selection into view from the live-height cap, so handleInput() only moves
  _sel and never depends on a stale _cap (M3). Behaviour-preserving.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 10:00:48 +02:00
MarekZegare4
b8a7b5dcc0 refactor: dedup persistence header, harden action menu, tidy small items
- Persist.h: shared writeHeader()/readHeader() for the magic+version+count
  on-disk header; Waypoint and Trail snapshots now share one implementation
  (byte layout unchanged; each keeps its own count clamp/reject + extra fields).
- TrailScreen: pushAction() guards _act_map / PopupMenu capacity, so adding a
  new action can't silently overrun the fixed array.
- PopupMenu: add count()/setSelected() accessors; replace the remaining direct
  _sel/_count field pokes in NearbyScreen, TrailScreen and QuickMsgScreen.
- Trail summary: unit-suffixed time ("1h 05m" / "5m 03s") so hours can't be
  misread as minutes.
- Trail::haversineMeters delegates to geo::haversineKm — single Haversine
  implementation (also trims a little flash).

Verified: GAT562 (SSD1306) and WioTrackerL1Eink (GxEPD) solo builds compile clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 13:37:03 +02:00
Jakub
db8c52ea9a fix(ui): standardize context menu cycling + PopupMenu height clamping
PopupMenu:
- _cap now uses max_by_height as a hard ceiling (never draw outside screen).
  Previously max(_visible, max_by_height) let callers exceed the screen on
  OLED 64px. Now: OLED→4 items, landscape e-ink→10, portrait e-ink→22.

QuickMsgScreen:
- Contact context menu: Notif and Melody cycle with LEFT/RIGHT in-place;
  ENTER closes without re-cycling.
- Channel context menu: Notif, Melody and Fav same pattern.

RingtoneEditorScreen:
- Migrated from bespoke menu to PopupMenu (same pattern as everywhere else).
- Duration (1/4…1/32) and BPM (60…180) are now single rows that cycle with
  LEFT/RIGHT; separate BPM+/BPM- rows removed.
- Fixed _menu_dur_label buffer too small for "Duration: 1/16" (14→16 bytes).

NearbyScreen:
- Removed redundant "Back" item from context menu (Cancel key navigates back).

TrailScreen:
- Grid toggle responds to LEFT/RIGHT in addition to ENTER.
- Export labels: "Export GPX (live/saved)" → "Export (live/saved)" to fit PM_BW.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 00:25:02 +02:00
Jakub
95795a663c feat(ui): popup menu auto-expands on tall displays (portrait e-ink)
On a 250px-tall portrait e-ink panel the menu previously showed only 3-4
items and required scrolling. render() now computes the maximum items that
fit from display.height() and uses that instead of the hardcoded caller
hint whenever the screen is taller. handleInput() uses the same _cap value
so scroll tracking stays in sync.

OLED (64px): fits 4 items — unchanged behaviour.
Portrait e-ink (250px): fits up to 22 items (capped at PM_MAX_ITEMS=16),
no scroll needed for typical context menus (4-6 items).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 23:20:15 +02:00
Jakub
2f32683494 refactor: extract popup menu into reusable PopupMenu struct with wrap-around navigation
- Add PopupMenu.h: generic scrollable overlay with wrap-around nav (up from
  first wraps to last, down from last wraps to first), closes on ENTER or
  CANCEL/CONTEXT_MENU. Default constructor ensures active=false on creation.
- KeyboardWidget: replace inline placeholder overlay (~50 lines) with PopupMenu
- UITask QuickMsgScreen: replace inline context menus in CONTACT_PICK and
  CHANNEL_PICK with PopupMenu; notif label stored in _ctx_notif_item at open time
- BotScreen: clear placeholder list for trigger field (placeholders are
  expansion tokens that would never match incoming literal text)
- MsgExpand: fix APPEND macro off-by-one (oi+_l < out_len-1 → < out_len)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 08:06:07 +02:00