A 'zoom <level>' chip, centred above the slider, appears with it and updates
live while dragging so the target level is visible before release.
Signed-off-by: Dan Vybiral <dan.vybiral@greencode.cz>
Add a one-glyph FontAwesome font (zoom_font.c, U+F002 magnifying-glass, 16 px bpp4,
generated with lv_font_conv from fa-solid-900.ttf) and splice it into the g_font_16
fallback chain alongside person_font. The map zoom button now shows a magnifier
(TOUCH_SYM_ZOOM) instead of the borrowed + glyph.
Signed-off-by: Dan Vybiral <dan.vybiral@greencode.cz>
Battery chart: stats line spans the full width so the CPU MHz figure no longer
wraps; the CPU series + chip now use the RF-monitor noise-floor grey (0x4A5256).
Telemetry: the window no longer auto-requests on open — it shows history and a
Request button (async refresh glyph, left of the gear) sends a poll. Legend text
recoloured to the axis-note grey instead of near-black.
Map: the + / - overlay buttons are replaced by a single zoom button that toggles
a full-width zoom slider along the bottom of the map (tap again to hide); the
chosen level is persisted across reboots and wins over the first-open auto-snap.
New 'Night mode (invert)' toggle in Map options inverts tile colours at render
time only (cache untouched), persisted.
Snake: pause/resume button next to the close X.
Status bar: hold for 3 s to capture a full-screen screenshot to
/screenshots/<time>.bmp on the SD card (16-bit BMP — fastest viewable format;
the hold suppresses the control-center tap).
Prefs: cfg v5 adds map_night + map_zoom.
Signed-off-by: Dan Vybiral <dan.vybiral@greencode.cz>
The card was a fixed 246 px — taller than the screen — and not scrollable, so its
bottom-anchored estimate/stats/points labels were clipped and overlapped (the
narrower text width also let the estimate wrap onto the stats line). Switch the
card to content height with a max-height cap + vertical scrolling, and place the
chart, the three text lines and the CPU/trash buttons with a running y so they
stack cleanly and the window scrolls when it doesn't fit.
Signed-off-by: Dan Vybiral <dan.vybiral@greencode.cz>
Battery chart: a CPU chip beside the trash button shows/hides the CPU-MHz
series; the secondary axis, series and bottom-line CPU figure are dropped when
hidden and the chart redraws immediately.
Telemetry window: a gear opens a settings panel spawned on top with checkboxes
for the battery / temperature / humidity series (the chart underneath redraws
live as they toggle), a manual auto-poll interval field (minutes, 0 = off,
replacing the cycling button) and the clear-history button. The main window now
holds only the reading, chart and time-range inputs; its layout was reworked so
the chart clears the wrapped reading line and the time-range fields no longer
overlap their labels.
Signed-off-by: Dan Vybiral <dan.vybiral@greencode.cz>
- Manual telemetry opens a per-node window immediately (request-in-progress),
then updates to the reading on reply or "last request failed" on timeout — the
window shows history either way (issue: see history even on failure).
- Chart from /telemetry/<id>.log: battery V (left) + temperature/humidity (right),
RF-monitor-style axis ticks + grid, point markers.
- Display window chosen with two fields (now-X h .. now-Y h ago); "Show" reloads.
- Auto-poll per node (toggle + 1/5/15/30/120 min), persisted to /telemetry/poll.cfg;
due nodes are polled from UITask::loop — emits the async spinner, logs the reply,
does NOT open the window.
- Clear-history buttons (with confirmation) on both the telemetry and battery windows.
Signed-off-by: Dan Vybiral <dan.vybiral@greencode.cz>
- trackball now steers Snake (UITask routes motion via SnakeGame::isOpen()/steer()
and hides the soft cursor on that page).
- a "New game" button gates the start so it doesn't move on open; tap restarts
after a game over.
- slower tick (220 -> 300 ms).
- playfield fills the unoccupied screen (dynamic cols x rows at kCellPx instead of
a small fixed square).
- the tab bar is hard-locked while Snake is open so a stray swipe/tap can't slip
through to the app switcher (e.g. into the map).
Signed-off-by: Dan Vybiral <dan.vybiral@greencode.cz>
- contacts: the "?" (unknown last-heard) sink-to-end now mirrors formatAgeBadge's
exact condition (also future/garbage/>400-day timestamps, not just ==0), using
the clock captured before the sort. Fixes "no change in behaviour".
- battery chart: add a universal close X (addCloseXBadge); lighter grid + time
(vertical) division lines; axis notes (V left, MHz right, time on X); point
dots so a single/sparse sample renders; second ORANGE CPU line on a secondary
Y axis. battery.log gains a CPU-MHz column (5th field); pre-upgrade 4-col lines
read as a CPU gap.
- telemetry window: add the same universal close X.
- hide device name: park the clock CENTRED on every screen (not just Home),
clear of the left-zone title (chat / Files / map credit) and the right icons.
Signed-off-by: Dan Vybiral <dan.vybiral@greencode.cz>
A small amber dot in the status bar (just left of the Wi-Fi glyph) lights
for ~180 ms after any microSD access: SD-backed tile cache, SD tile packs,
and card mount. markSdIo() stamps a timestamp on the I/O path (one 32-bit
store, safe from either core); UITask::loop toggles the dot edge-triggered,
so an idle device pays a single comparison per loop and never relayouts.
The ble / clock / keyboard-layout labels shift left to free the slot.
- New Monitor app (app drawer): live RF sniffer — recently-heard feed
(type / RSSI / SNR / hops, colour-coded), link-margin grade, packet rate,
and an RSSI + noise-floor scope with an on-screen dBm scale; portrait /
landscape aware. Backed by a recent-RX ring buffer in MyMesh (logRxRaw).
- Chat: DM person icon / channel group icon; block non-contact senders by
name; keep scroll position when opening a thread; contacts sort popup
closes on select; long-press popup close-X no longer overlaps buttons.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Core bump: lib_deps -> meshcomod core-v1.16.3 (BLE advertises the node
name not "NimBLE"; GPS won't push a pre-fix ~1902 date).
Tim's quick-wins: Enter-key-sends toggle, 12-hour clock, lock-when-screen-off,
reverse scrollball.
Launcher / map: fix Wi-Fi tiles never downloading under Launcher (the SD-fallback
cache made tilesFsLowSpace() read the unmounted LittleFS as "full"); plot contact
markers before the tile decode so dots aren't ~2s late.
RTC / time: anchored-time guard re-applies UTC when the clock reads garbage
(GPS clobber); blank pre-2020 timestamps so empty channels don't show 1969/1970;
timezone picker (named zones with correct DST) replacing the CET-only base, with
a v2->v3 config migration mapping prior manual offsets onto a Custom zone.
BLE: persist the random pairing PIN (was re-rolled every boot).
Chat: don't auto-jump to latest on a new message when scrolled up; block-by-name
for non-contact room/channel senders (un-blockable room bots).
Power: drop CPU to 80 MHz when the screen is off; enable Wi-Fi modem-sleep only
once associated (enabling it on the unassociated STA broke the setup-wizard scan).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Community PR by @Vybo (Dan Vybiral). Fixes Launcher SD-fallback tile caching (tilesFsLowSpace read the unmounted LittleFS partition as full and dropped every download), plus write-verification, read/write cap match, SD PNG-pack support, and on-screen fetch diagnostics.
- Boot splash now shows MESHCOMOD (small, same size as TOUCH BETA) between the
WADAMESH wordmark and the TOUCH BETA channel line, with its own fade-in.
- Control-center power menu gains a 'Download mode' button: flushes chat, then
sets RTC_CNTL_FORCE_DOWNLOAD_BOOT + restarts so the device drops into the USB
serial bootloader for reflashing — no need to hold BOOT + tap RST. Card resized
to fit the 4th button on both boards.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The HUD pill (hop name + replay button) was full-width top-center, so its replay
button landed under the map's settings gear / zoom column (right edge, x~sw-36).
Anchor it top-LEFT and cap its width to stop before that column.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The topbar reorder moved the clock left, and the name's short-name path used
LV_SIZE_CONTENT with no width cap — a name <=11 chars but with wide glyphs (W/M)
overran into the clock on the narrow V4. Always cap the label to a fixed window
(100 px T-Deck / 74 px V4); LONG_SCROLL_CIRCULAR stays static when the name fits
and only marquees when it overflows, so no char-count threshold is needed. Also
slow the marquee (anim_speed 14) for readability.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ble_en defaults ON, so a fresh device co-inits BLE at boot and the first-boot
setup ran with both radios up. On the fresh-flash path only (setupWizardMaybeOpen,
which returns early once setup is done) force BLE off + persist ble_en=0, before
the UI is interactive — so first-boot setup is Wi-Fi-only and the user opts BLE in
later via the control center. Already-set-up devices return early, so their BLE
state is untouched.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Coredump from a reboot during a route replay: the replay re-centred the map onto
an un-cached area, the fetch task wrote new z16 tiles, and FS::mkdir faulted
inside lfs_alloc — a full/fragmented LittleFS faults rather than returning NOSPC.
The tile cache had no free-space guard or eviction, so heavy map use fills the
4.75 MB partition until the next mkdir reboots the device.
Add tilesFsLowSpace() (cached ~5s, fetch-task-only) and skip tile writes when the
cache has < 320 KB free. Reads keep working so cached areas still display; only
new tiles stop caching once full. The crashing mkdir is never reached.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Tapping 'Replay route on map' now hides the Info popup immediately (was
lingering over the map transition while the async delete caught up).
- Route-replay HUD: a top banner shows the current hop's repeater name +
identifier (hash) as each node is revealed, and a replay button (↻) re-runs the
reveal animation from the first hop. HUD lives on lv_layer_top while the route
is shown and is torn down when leaving the map.
- RouteNode now carries the resolved name + hex id (routeFillId); 'You' for the
ME endpoint.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Audit of the per-tick UI refresh (runs 4x/s on the UI thread) found ~8 labels
re-written every tick unconditionally — lv_label_set_text always invalidates +
re-lays-out (it never compares), so byte-identical text was redrawn 4x/s on Home
and the control center. Add setLabelIfChanged() (compares against the label's
current text, so it's safe across home/tab rebuilds) and route the status/home/CC
labels through it: CC sysinfo + GPS, home state/IP, RAM/PSRAM, unread, signal
chip, traffic legend, settings footer.
Also coalesce the Wi-Fi tile-arrived re-render: the dirty flag is set per
delivered tile, so a download burst drove back-to-back full-grid tile read+decode
passes on the UI thread — cap to ~2.5x/s (flag stays set; next tick catches up).
Structural levers (DMA flush, double buffer, bigger draw buffer) were left alone:
the audit confirmed they were already tried and reverted for Wi-Fi/BLE RAM, so
they are NOT no-risk. CPU is already 240MHz, tick is esp_timer, touch is 125Hz
async — all already optimal.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Theme reverting on reboot: the boot wordmark reads a pref (UI rotation) BEFORE
SdNvsPrefs::useFile() switches the backend to files, so the settings blob got
cached from legacy NVS and file-saved values (accent, brightness, language…)
were ignored every boot. Add touchPrefsReload(), called right after useFile(),
to re-read from the file backend. (main.cpp now quote-includes TouchPrefsStore.h
so it sees the new decl, not the monorepo lib's stale copy.)
- Control center: a settings page opened via a chip long-press now returns to the
dropdown (not Home) when you tap the bar to go Back — tracked with
s_settings_from_cc.
- Control center now follows the theme accent: brightness slider indicator/knob
and the toggle chips use COLOR_ACCENT (chips: solid on / faint off) instead of
a fixed green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Home: split the unread count onto its own row with an envelope icon; tapping it
jumps to the Chats inbox. Memory (RAM/PSRAM) drops to the line below.
- Control center: long-pressing a chip opens that feature's full settings page
(Wi-Fi, Bluetooth, GPS->Radio & Mesh, Theme->Display, Keyboard, Lock, Sound).
A short tap still toggles; lv_indev_wait_release swallows the release click so
a long-press doesn't also flip the radio.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The route list appended each resolved hop's raw hash hex after its name. With
long repeater names the line wrapped, so a hop's trailing hash glued onto the
next hop's number (hop 2 hash '31' + hop 3 '3.' rendered as '313.'). Now show
just the name when the hash resolves to a known repeater; the hash is a fallback
only for unknown hops. Also drop the 6-hop '+N more' cap and grow the body
buffer so the whole path lists — the popup already scrolls. Same name-only
cleanup applied to the outgoing 'Repeats heard' list.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When charging the battery shows the bolt only (no % text), but the % label still
reserved its slot, leaving a %-wide gap between the signal bars and the bolt.
Slide the signal/Wi-Fi/Bluetooth/clock cluster right by the reclaimed width on
the charging transition so it stays snug against the bolt; slide back on unplug.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Charging now shows just the lightning-bolt glyph (batteryGlyphForMv already
returns it); the 'CHG' text is dropped.
- Right-to-left order is now battery%, signal bars, Wi-Fi, Bluetooth, time.
Wi-Fi and Bluetooth are split into independent glyphs (were one combined
label) so each sits in its own slot.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Starting a route replay inside a channel jumps to the map via goToTab(), which
hides the chat overlay but didn't clear s_chat_title — so the channel name + its
settings cog lingered in the status bar afterwards (e.g. on Home). Clear the chat
title in onTabChanged's leaving-inbox path, so a tab tap, swipe, or replay jump
all drop it (was only cleared by the explicit chat-close button).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- The 'tap for details' cue moves to the graph's top-right corner (was bottom).
- Signal now acquires quickly: the discover probe is a FLOOD advert (repeaters
re-broadcast it -> we measure the echo's SNR), fired ~4 s after boot and re-
evaluated every 60 s but only actually sent when we have no recent signal
(no RX, or older than 100 s). Zero-hop adverts produced nothing to measure
since they aren't re-broadcast -> the icon sat on 'Sig --'. A chatty mesh that
already feeds us traffic keeps it quiet; a quiet one refreshes via the echo.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>