Commit Graph

2934 Commits

Author SHA1 Message Date
Jakub
900f2d59e9 refactor(ui): tag SettingsScreen ifdefs with semantic FEAT_* macros
Extends Features.h with preprocessor flags (FEAT_BRIGHTNESS_SETTING,
FEAT_CLOCK_SECONDS_SETTING, FEAT_DISPLAY_ROTATION_SETTING,
FEAT_JOYSTICK_ROTATION_SETTING, FEAT_FULL_REFRESH_SETTING) since
constexpr can't gate enum members or class fields. SettingsScreen.h
now uses #if FEAT_X instead of #if defined(EINK_DISPLAY_MODEL) — grep
on the feature name finds the toggle and every use site, and the
condition names *what* the gate controls.

Driver headers (DisplayDriver.h, GxEPDDisplay.h) keep raw
EINK_DISPLAY_MODEL — they're below the companion_radio layer and the
ifdef there gates on the build's panel type, not a UI feature.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 22:20:42 +02:00
Jakub
7c81ea0f52 refactor(ui): centralise EINK_DISPLAY_MODEL toggles in Features.h
Five sites in UITask.cpp + MyMesh.cpp branched on EINK_DISPLAY_MODEL
purely for runtime-shape decisions (blink rate, default pref values,
refresh intervals). Replace with a constexpr Features namespace:
IS_EINK, BLINK_INDICATORS, CLOCK_HIDE_SECONDS_DEFAULT, HOME_REFRESH_MS,
LOCKSCREEN_REFRESH_MS. Compiler dead-branch-eliminates so cost is
identical to the preprocessor branch, but the code stays reachable to
tooling and the policy lives in one file.

SettingsScreen and driver headers still need real #ifdef — they
condition enum members and class field layouts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
v1.12
2026-05-24 20:41:25 +02:00
Jakub
e16ecbd690 style(ui): sprintf → snprintf with sizeof in UI render paths
Mechanical sweep across UITask.cpp and SettingsScreen.h. All current
format strings already fit their buffers, but a future format change
would silently overflow with sprintf. snprintf with sizeof makes the
buffer bound explicit and traceable to the array declaration.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 20:35:49 +02:00
Jakub
559fe86f20 style(ui): use NSDMI for SH1106Display::_use_lemon default
Match the rest of the file's defaults; constructor body no longer
re-initialises a field that the in-class initializer already set.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 20:33:59 +02:00
Jakub
d981f93e6e refactor(ui): drawSelectionRow helper for the 5-line invert/restore pattern
Eight screens repeated the same if/else block to invert the row
background for selected items. Add DisplayDriver::drawSelectionRow that
sets LIGHT, optionally fills the rect, and leaves the colour as DARK
when sel (so the caller's text renders inverted). 61 lines removed
across SettingsScreen, KeyboardWidget (cells + special row), ToolsScreen,
DashboardConfigScreen, BotScreen, RingtoneEditorScreen (note slots +
menu), NearbyScreen, QuickMsgScreen (4 list views).

Card-style rows (QuickMsg hist, NearbyScreen discover, RingtoneEditor
notes display) keep their original drawRect/partial-fill outline for
unselected — they don't match the simple-invert pattern.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 20:33:02 +02:00
Jakub
52a7081a13 refactor(ui): dedupe UTF-8 codepoint decoder
Three copies of the same routine: DisplayDriver::decodeCodepoint (added
in the FullscreenMsgView wrap fix) plus per-driver duplicates in
SH1106Display and GxEPDDisplay. Consolidate on DisplayDriver and have
translateUTF8Static also route through it instead of its own inline
decoder. Net: 63 lines removed, single source of truth.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 20:25:35 +02:00
Jakub
74764aed2b perf(ui): O(n) FullscreenMsgView wrap via getCodepointWidth virtual
The wrap loop used to memcpy a growing substring and call getTextWidth
on it for every appended codepoint — O(n²) UTF-8 decoding with Lemon
font, ~5-20 ms on a full e-ink redraw of a long message.

Add DisplayDriver::getCodepointWidth(cp) (default: build a UTF-8 buffer
and forward to getTextWidth) plus DisplayDriver::decodeCodepoint() as
the shared UTF-8 walker. Override getCodepointWidth in GxEPDDisplay
(direct lemonXAdvance for Lemon size 1) and SH1106Display (lemonXAdvance
for Lemon, fixed 6*sz for built-in font). FullscreenMsgView now walks
codepoints once and sums widths incrementally.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 20:22:16 +02:00
Jakub
87bdc5cb9f feat(prefs): tail schema sentinel for layout-change detection
Old saves (without sentinel) load unchanged — rd() lambda zero-inits
anything not on disk. New saves append a 4-byte sentinel that encodes
the schema revision; the low byte is bumped on every layout change in
loadPrefsInt/savePrefs. Mismatch on load logs a warning and continues
with defaults; the next savePrefs writes the current sentinel so the
file self-heals.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 20:18:50 +02:00
Jakub
f82d23b200 perf(ui): replace transliterateCodepoint switch with sorted-table binary search
Old 80+ case switch generated a jump table around ~600-1200 B of flash;
new layout is two parallel static-const arrays (uint16 codepoint + char
ascii, 144 entries each = 432 B) and a 5-line bsearch loop. Same input
range, same output, faster lookup (~log2(144) ≈ 7 compares vs jump-table
indirection + bounds check).

Adds a static_assert that the two arrays stay in sync.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 20:14:01 +02:00
Jakub
71e1cb7a23 perf(ui): drop redundant getTextWidth and contact re-fetch in QuickMsg
- DM/channel history rows precomputed age_w (= getTextWidth(age) + 3) but
  then called getTextWidth(age) again to place the cursor. Reuse age_w.
- Contact-by-message-count sort fetched each contact twice: once when
  building _sorted, once when filling counts[]. Fold counts[] into the
  build loop so each contact is fetched once.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 20:07:46 +02:00
Jakub
6410a346f2 refactor(eink): centralise scale() helper, hoist out of Lemon hot loop
Per-char recomputation of (width() >= height() ? 2 : 1) in drawLemonChar
and lemonXAdvance is now passed in by the caller. print() and
getTextWidth() compute scale() once and feed it to every glyph. All
other callers in the file route through the helper for consistency.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 20:01:46 +02:00
Jakub
bf724ce692 perf(bot): lower body once and strstr instead of O(n*m) per-offset tolower
For a 140-byte channel msg with a 64-byte trigger, the old loop did
~8960 tolower() calls per inbound message; now it's 140 tolower calls
plus one strstr.

perf(buzzer): skip nRF52 STOPPED wait when PWM was not running

TASKS_STOP on a disabled PWM never fires EVENTS_STOPPED, so the wait
always timed out at 2 ms. Gating on _pwm_on removes the wait on the
first note of a melody and after pauses, where there's nothing to wait
for.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 19:59:21 +02:00
Jakub
e2d59b9a3d fix(prefs): use magic sentinel to validate page_order
Previously page_order_set status was inferred from page_order[0] being
in 1..11, so a single junk byte in that range falsely triggered
custom-order mode. Add explicit page_order_set magic (0xA5) written
alongside the array; pre-magic saves are migrated once on load when
their first byte still passes the legacy range check.

Also fix(ui): keep "@[nick] " prefix out of placeholder expansion in
QuickMsgScreen reply path, so a nick containing a token like {loc}
isn't substituted.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 19:53:05 +02:00
Jakub
10b2629b36 fix(eink): gate Lemon print path on _text_sz == 1
setTextSize(2/3) switches GFX to non-Lemon fonts (built-in scaled or
FreeSans18pt). The Lemon path bypassed GFX entirely and rendered glyphs
at size 1 scale, shrinking splash version and clock when Lemon font
was active.

Refresh CODE_REVIEW.md: remove resolved issues (signed-int timing vars,
abs() no-op, XBM CRC, loadPrefs nesting, lock-screen LPP thrash, reply
prefix raw UTF-8), add new findings (upstream-merge surface, virtual
width()/height() per glyph, page_order sentinel), list enhancement
ideas.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 19:45:19 +02:00
Jakub
7dcb763d83 fix(eink): align unknown-glyph fallback box to top of cell
The placeholder rectangle for codepoints outside U+0020-U+04FF was drawn
1 scale unit too low (y-7*sc instead of y-8*sc relative to the GFX
baseline), causing it to sit below full-height glyphs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 16:55:50 +02:00
Jakub
9c03e9a2db fix(eink): render Lemon font UTF-8 correctly by bypassing GFX write()
Adafruit GFX write(uint8_t) treats each byte as a glyph index and cannot
decode multi-byte UTF-8 sequences, so Cyrillic and other non-ASCII chars
were rendered as garbage even with the Lemon font selected.

Add decodeUtf8 + drawLemonChar + lemonXAdvance to GxEPDDisplay, mirroring
the existing SH1106Display implementation. Override print() for Lemon mode
to decode UTF-8 codepoints and draw each glyph directly; override
getTextWidth() to sum per-codepoint advances.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 16:39:41 +02:00
Jakub
5fde79fae7 fix(ui): translate keyboard preview buffer through UTF-8 handler
KeyboardWidget printed the text preview directly via display.print()
without calling translateUTF8ToBlocks first. When a reply prefix
contained a UTF-8 sender name (e.g. Cyrillic), raw multi-byte sequences
reached the display driver on non-Lemon renders.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 15:28:10 +02:00
Jakub
c375c4b217 fix(eink): pass UTF-8 through unchanged when Lemon font is active
drawTextCentered/Left/Right/Ellipsized all call translateUTF8ToBlocks
before passing text to print(), which converted Cyrillic (and other
Unicode) to block characters even when the Lemon font was selected.

Override translateUTF8ToBlocks in GxEPDDisplay to skip conversion when
_use_lemon is true — the Lemon/GFX font handles UTF-8 natively.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 14:47:20 +02:00
Jakub
1cfe7c168a Update README with case model links
Added links to E-ink and Oled case models.
2026-05-24 11:04:51 +02:00
Jakub
a6bf3ee08f feat(eink): full refresh interval setting in Settings › Display
Add "Full rfsh" setting (e-ink only) with options off/5/10/20/30
partial refreshes between full refreshes. Prevents ghosting on panels
that accumulate artifacts after many partial updates.

GxEPDDisplay counts partial refreshes and calls display.display(false)
every N frames when the interval is set. Persisted in DataStore and
applied at startup via applyFullRefreshInterval().

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 10:22:03 +02:00
Jakub
8012278483 fix(ui): remove space in hibernate hint formatting 2026-05-24 00:56:43 +02:00
Jakub
3a08eedfec fix(ci): fix double-v in version for v* style tags
setup-build-environment was using v${TAG#*-v} which prepended an extra
"v" when the tag already starts with "v" (e.g. v1.11 -> vv1.11).
Now uses the tag name directly for v* tags; strips name-v prefix only
for compound tags like room-server-v1.2.3.

Remove redundant Extract Version steps from the wio firmware workflow
since setup-build-environment already handles this.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
v1.11
2026-05-24 00:50:10 +02:00
Jakub
34fcab6bd5 feat(ui): show MeshCore version large and plus version in splash bar
Add MESHCORE_VERSION build flag ("1.15") to companion base configs.
Splash screen now shows MESHCORE_VERSION in the large text and the
plus firmware version (stripped of commit hash) in the Plus bar,
e.g. "1.15" large + "Plus v1.11 for Wio" below.

Change CI tag pattern to v* and simplify version extraction so tag
v1.11 maps directly to FIRMWARE_VERSION=v1.11.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 00:35:07 +02:00
Jakub
62cae314f7 ci: change tag pattern to plus_* and fix version extraction
Trigger workflow on tags matching plus_* instead of wio-tracker-v*.
Extract version as v<x.y> from plus_<x.y> tag format.
Also update firmware filenames in README to match renamed env names.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 00:18:31 +02:00
Jakub
137b0234d5 ci: rename dual envs and fix tag-triggered builds for OLED and e-ink
Rename WioTrackerL1_companion_radio_dual_settings → WioTrackerL1_companion_dual
and WioTrackerL1Eink_companion_radio_dual → WioTrackerL1Eink_companion_dual.

Fix CI workflow: remove hardcoded branch refs so both build jobs check out
the tagged commit; add version extraction to each build job (previously
GIT_TAG_VERSION was only set in the release job, leaving FIRMWARE_VERSION
empty during builds); update build commands to use new env names.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 00:13:34 +02:00
Jakub
631bd543bf add image to readme 2026-05-23 23:52:04 +02:00
Jakub
31179d2175 fix(ui): repair missing Clock entry in page_order and show positions immediately
ensurePageOrderInit now scans for CLOCK (bit 0, stored as 1) after the
existing page_order[0] validity check — if Clock is absent (partial or
corrupted saved order), it resets the array and re-initialises all pages.

renderItem calls ensurePageOrderInit before displaying home page positions
so that position numbers appear immediately on entering Settings rather
than only after the first reorder action.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 23:50:25 +02:00
Jakub
b279b95b2d refactor(ui): apply code review fixes — bugs, risk, optimisations, cleanup
Fixes 7 bugs (timing UB on unsigned long, abs() no-op, XBM CRC PROGMEM
scan, CayenneLPP heap thrash ×2, UTF-8 reply prefix, lock-seq reset on
wake), 3 risk items (notif melody stop guard, bot trigger pre-lowercase,
lock-seq cleared on display wake), plus optimisations (fmtMsgAge/
getTextWidth caching, RTTTL builder consolidated to NodePrefs, HP_*
constants and homePageLabel table moved to NodePrefs, DataStore flat
lambda schema, translateUTF8Static factored out) and minor cleanup.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 20:03:45 +02:00
Jakub
05335420e3 fix(ui): home page order and visibility bugs
- home_pages_mask XOR toggle initialises to HP_ALL before flipping bit,
  preventing accidental hiding of all other pages when mask starts at 0
- buildVisibleOrder appends any visible page missing from custom page_order,
  so pages absent from migrated/corrupted prefs remain navigable

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 18:11:24 +02:00
Jakub
52faa0e2f6 fix(ui): restrict joystick rotation setting to e-ink builds only
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 15:33:03 +02:00
Jakub
fea1be8e81 feat(ui): runtime joystick rotation independent of display rotation
Add NodePrefs::joystick_rotation (0-3 steps CW), persisted in DataStore
alongside display_rotation. rotateJoystickKey() now takes a runtime rot
parameter instead of compile-time JOYSTICK_ROTATION macro. UITask::loop()
reads joystick_rotation from NodePrefs each frame. Settings screen exposes
"Joystick" rotation item on any build with UI_HAS_JOYSTICK. JOYSTICK_ROTATION
macro still works as compile-time default for legacy/non-prefs builds.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 15:20:40 +02:00
Jakub
85cfb6f1af fix(ui): unified unknown-char block rendering and display layout fixes
- GxEPDDisplay::print() renders '\xDB' as filled rect for Lemon font,
  matching OLED Lemon block behavior
- Remove translateUTF8ToBlocks override (base class passthrough sufficient)
- transliterateCodepoint fallback changed '?' → '\xDB' for consistency
- drawTextCentered/Right/LeftAlign now call translateUTF8ToBlocks
- isLemonFont() added to DisplayDriver, SH1106Display, GxEPDDisplay
- isLandscape() guarded by EINK_DISPLAY_MODEL to distinguish OLED vs e-ink
- ind_h trims indicator box height only for Lemon font
- getTextWidth disables wrap before getTextBounds, restores after
- QuickMsgScreen: tighter hist_item padding; 2 messages visible with Lemon
- Hibernate hint fits on one line when width allows, splits otherwise
- BLE PIN page shows toggle hint on landscape e-ink
- Remove unused muted_icon bitmap

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 14:45:24 +02:00
Jakub
6cdd9d01d4 Merge branch 'wio-tracker-l1-improvements' into wio-eink-unified 2026-05-23 10:22:52 +02:00
Jakub
88412f1b9d merge: bring eink-ui into wio-eink-unified 2026-05-23 10:21:23 +02:00
Jakub
a0a74fe5d3 fix(eink): replace mute bitmap icon with text letter M
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 10:14:05 +02:00
Jakub
2b7fc37491 fix(eink): 30s clock/lock refresh, static auto-advert indicator
- Clock page: 60s → 30s refresh (time shown without seconds, 30s is sufficient)
- Lock screen: 60s → 30s refresh
- Auto-advert "A" indicator: remove blink on e-ink (always shown when active);
  blink preserved on OLED via EINK_DISPLAY_MODEL guard

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 10:06:22 +02:00
Jakub
93e3a29b0d fix: align ON/OFF/always to fixed column flush with right edge in Home Pages
Column at width()-6*charWidth so "always" touches the right edge;
ON and OFF start at the same x position.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 09:54:31 +02:00
Jakub
4f68e5ffba fix: fixed-width position prefix in Home Pages settings to prevent label shift
Use %2d (right-aligned, always 3 chars) instead of %d so label x-position
stays constant when going from single-digit (1-9) to double-digit (10-11).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 09:46:28 +02:00
Jakub
d3589e293f feat: sort DM contact list by message count (most messages first)
Contacts with at least one message float to the top, ordered by
total message count descending. Contacts with no messages keep their
original relative order. Sorting is applied each time the message
screen is opened (buildContactList), so the list always reflects the
current session's activity.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 09:19:27 +02:00
Jakub
ddac8fe3ee docs: update README for v1.10 — e-ink support, font switcher, home page ordering
- Add Firmware Variants table (OLED vs e-ink) with filenames
- Add E-ink Display section covering adaptive layout, rotation, scaled dots,
  suppressed clock seconds
- Update Settings > Display: add Display rotation (e-ink only) entry, update
  Font entry to reflect it's now built into all builds
- Update Settings > Home Pages: document LEFT/RIGHT ordering and always-visible
  Settings/Messages pages
- Replace "Font Switcher Build" section with "Font Switcher" — font is now
  built into every variant, no separate download needed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 00:07:41 +02:00
Jakub
7510878f9e fix: center-crop splash logo in portrait mode via negative x offset
Revert logo width clamp — instead let x go negative so GxEPD's drawPixel
clips symmetrically. In portrait (122px wide) x = (122-128)/2 = -3,
cropping 3px from each side rather than 6px from the right only.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 23:53:16 +02:00
Jakub
4dd36f685d fix: apply rotation before splash screen, clip logo to display width
- Move applyRotation() before setCurrScreen(splash) so e-ink startup
  screen respects the saved display_rotation preference
- Clamp meshcore logo width to display.width() so it doesn't overflow
  in portrait mode (122px screen vs 128px logo)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 23:50:21 +02:00
Jakub
bad9fae419 merge: bring home page ordering feature and CI dual-build from wio-tracker-l1-improvements
Resolved conflicts to preserve eink-ui layout helpers (valCol, lineStep, sepH)
while integrating the page_order persistence, buildVisibleOrder(), and
Settings/Messages ordering UI from the wio-tracker-l1-improvements branch.
display_rotation is now written before page_order in the save/load sequence.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 23:46:35 +02:00
Jakub
bb94a6f81f feat: home screen ordering — reorder all screens from Settings
NodePrefs: add page_order[11] (1-indexed bit-index, 0=end/legacy).
DataStore: persist page_order appended after use_lemon_font.

UITask/HomeScreen:
- bitToPage(): maps stable bit indices 0-10 to HomePage enum values
- buildVisibleOrder(): respects page_order when initialised, else
  falls back to default enum sequence
- navPage() and navigation dots use buildVisibleOrder()

SettingsScreen Home Pages section:
- Add Settings (bit 9) and Messages (bit 10) — show as "always"
- LEFT/RIGHT on any home page item moves it earlier/later in order
- ENTER toggles ON/OFF for pages that can be disabled
- Position number shown before label once custom order is active
- ensurePageOrderInit() seeds default order on first reorder action

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
wio-tracker-v1.15-plus.1.10
2026-05-22 23:33:06 +02:00
Jakub
7519740d72 ci: build dual OLED and dual e-ink firmwares in parallel
Split the single build job into two independent jobs:
- build-oled: checks out wio-tracker-l1-improvements, builds WioTrackerL1_companion_radio_dual_settings
- build-eink: checks out eink-ui, builds WioTrackerL1Eink_companion_radio_dual

Both artifacts are merged into a single draft release.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 23:20:06 +02:00
Jakub
9c671891d9 fix: e-ink portrait/landscape layout — separators, battery, keyboard, screens
- DisplayDriver: add sepH() (2px landscape, 1px OLED); fix transliterateCodepoint
  fallback '?' (was '\xDB' which broke UTF-8 word-wrap and rendered tiny block char)
- GxEPDDisplay: drawRect draws double border on landscape for visible 2px weight
- UITask: navigation dots scale with lh; content_y saves 16px by using dots_y+6;
  battery iconW=lh*2, fill margin bm scales with orientation; indicator spacing fixed;
  clock separator uses sepH(); alert popup tight and vertically centred
- KeyboardWidget: compact cell height (no stretch), multi-line preview, cursor always
  on last visible line
- BotScreen / NearbyScreen detail: use lineStep() as natural item height, shrink only
  when items don't fit (fixes portrait stretch)
- QuickMsgScreen: send button pinned to display bottom (height-lh-2), not floating
- SettingsScreen: AUTO_OFF guarded with #if AUTO_OFF_MILLIS>0; default sel=AUTO_LOCK
- All screens: separators updated to sepH()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 23:13:27 +02:00
Jakub
118f29b0a1 fix: landscape e-ink layout corrections — clock overlap, keyboard overflow, bar sizing, nearby detail
- GxEPDDisplay: fontAscender only applies to sz=1 with Lemon; sz=2 (NULL font)
  was incorrectly getting a 16px cursor offset, causing clock text to render
  16px lower than expected and overlap the date line
- KeyboardWidget: compute cell_h from available screen height instead of lh+1;
  tighten sep_y to lh so the grid fits within 122px even with Lemon font (lh=20)
- SettingsScreen renderBar: constrain box size to available width so the 5th
  buzzer-volume square doesn't overflow past the right edge with Lemon font
- NearbyScreen discover detail: manually truncate b64 public key by charWidth
  to guarantee one-line rendering; use dynamic step = (height-hdr)/5 so
  Status line doesn't fall off the bottom of the screen
- NearbyScreen contacts detail: merge dist+az into one line and use dynamic
  step to fit 5 lines within the display; removes the off-screen Seen: row
- AutoAdvertScreen: replace lineStep() gap before hints with 4px fixed gap
  so both hint lines fit within the display with large fonts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 19:11:25 +02:00
Jakub
1d8ca25469 fix: adaptive layout for e-ink landscape across all remaining screens
- GxEPDDisplay: sz2 uses 4× scale on landscape (clock 32px vs body 16px)
- UITask: clock date_y and splash date_y computed from lh2 after setTextSize(2)
- UITask: battery/mute/BT/advert indicators scale with lh; muted uses text 'M'
- UITask: hibernate hint split into two lines to prevent wrapping
- ToolsScreen: full refactor — headerH, listStart, lineStep, getCharWidth
- KeyboardWidget: all cell sizes computed from display metrics (cell_w, cell_h, spec_w)
- RingtoneEditorScreen: cell_w = charWidth×2+6 (was hardcoded 18)
- SettingsScreen: BRIGHTNESS hidden on e-ink; renderBar boxes scale with lh

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:34:20 +02:00
Jakub
f8fb1dac99 fix: align SettingsScreen item label to dynamic char width
Replace hardcoded x=8 with getCharWidth()+2 so the label doesn't
overlap the '>' selector on displays with larger fonts (e-ink sz1 scale=2).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:13:27 +02:00
Jakub
744f263932 refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.

On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00