Commit Graph

121 Commits

Author SHA1 Message Date
Jakub
9ff42a9296 feat: screen lock — hold Back + 3×Enter to lock/unlock
- Long press Back starts lock sequence; 3 Enter presses within 3s toggle lock
- Locked screen wakes for 5 seconds on any key, showing clock and unlock hint
- Incoming messages do not wake the display while locked
- Auto Lock setting in Display section: locks automatically when screen turns off
- Lock state persisted in UITask; NodePrefs gains auto_lock flag

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 09:55:51 +02:00
Jakub
b068318bc5 refactor: extract SettingsScreen and QuickMsgScreen to separate header files
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 00:12:40 +02:00
Jakub
a60ec376d7 perf: increase render interval on static screens from 300ms to 2000ms
Static screens (SettingsScreen, BotScreen, FullscreenMsgView, message
list, contact picker) were re-rendering every 300ms with no visible
benefit. New messages still trigger immediate refresh via newMsg() →
_next_refresh=100, so responsiveness is unchanged. Reduces unnecessary
SPI transfers and CPU wakeups ~6x on idle static screens.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 23:42:52 +02:00
Jakub
b91afa05b9 fix: reduce low battery shutdown text size to fit 128px OLED
Size 2 rendered text wider than display width. Switched to size 1
and adjusted y-positions to keep both lines visible.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 17:47:22 +02:00
Jakub
fbe636a05a feat: show plus version number on splash screen
Splash screen bar changes from "Plus for Wio" to "Plus 1.4 for Wio"
by extracting the suffix after "plus." from FIRMWARE_VERSION.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 00:33:13 +02:00
Jakub
9c40a253e2 update README and fix quick message / melody naming collision
- Expand README: ringtone editor now documents two melody slots, full
  note spec, and melody assignment to DM/channel notifications; settings
  section lists DM Melody and Channel Melody options; context menu
  description updated with melody override detail
- Rename quick reply template labels from M1-M10 to Q1-Q10 to avoid
  ambiguity with melody slot identifiers M1/M2
- Increase DM message history buffer from 32 to 64 entries

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 00:09:57 +02:00
Jakub
1d04930c1e quick fix 2026-05-15 16:33:19 +02:00
Jakub
ec601ef118 fix: renderBar always shows slot outlines regardless of value
Previously empty slots used drawRect and filled slots used fillRect,
so at max value (e.g. volume=4) no drawRect was called and no slot
borders were visible when selected (inverted colors). Brightness at
default (2/5) always had visible outlines, making the two bars look
inconsistent.

Now drawRect is called for every slot, with fillRect(+1,+1,w-2,h-2)
on top for filled ones. All 5 slots are always visually distinct.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 15:48:05 +02:00
Jakub
484f0891b1 fix: three bugs found in code review
- DataStore: validate ringtone2_len <= 32 after loading from flash
  (same guard as ringtone_len on line 252; we introduced this field
  but forgot the validation)
- UITask: fix shutdown buzzer timeout to conventional unsigned form
  (millis() - start) < timeout to avoid unsigned underflow if called
  within first 2.5s of boot
- UITask: guard _last_notif_ch_idx < 64 before 1ULL shift in notify()
  (defensive; MAX_GROUP_CHANNELS=40 so currently unreachable, but
  prevents UB if the value is ever unexpectedly out of range)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 15:34:10 +02:00
Jakub
d02c1cf098 feat: play preview tone when buzzer volume changes in settings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 11:46:48 +02:00
Jakub
b3c6416874 feat: melody assignment per channel/DM, dual ringtone slots, volume cleanup
Add second ringtone slot (ringtone2_*) to NodePrefs and DataStore.
Add per-channel and per-DM melody assignment (built-in / melody 1 / 2)
stored in NodePrefs bitmasks and DmMelodyEntry table.
Settings screen exposes DM/CH sound items; RingtoneEditorScreen supports
switching between slots. notify() uses buildMelodyFromPrefs() to select
the correct RTTTL string per contact.

Remove broken nRF52 volume control from buzzer.cpp: tone() uses
NRF_PWM2 with DECODER.MODE=Auto so TASKS_NEXTSTEP has no effect and
duty cannot be changed before the first DMA read without patching the
Arduino core. applyVolume() is now a no-op on nRF52.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 11:43:38 +02:00
Jakub
c86e6c803e fix: reverse fullscreen message nav order and remove channel # prefix
- Swap left/right navigation in fullscreen view: left=newer, right=older
- Update arrow indicators to match new direction
- Remove # prefix from channel name in message and compose view titles

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 20:21:13 +02:00
Jakub
1fcad7776a feat: add unread message count field to clock dashboard
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 18:20:49 +02:00
Jakub
9426c86e79 feat: polish status bar and home screen layout
- Move "A" (auto-advert) indicator left of BT "B" in status bar
- Ellipsize node name to fit available width instead of overflowing
- Shrink A and B background rectangles by 1px right, 2px bottom
- Change A blink to 2s on / 2s off at 1000ms refresh rate
- Auto-advert sends GPS position directly via createSelfAdvert/sendZeroHop
- Align Settings and Tools home page titles to y=22, same as Messages

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 18:13:18 +02:00
Jakub
b36c970a2d fix: auto-advert indicator - 300ms refresh, background shifted 1px left
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 17:45:26 +02:00
Jakub
e205f3712d fix: auto-advert indicator - inverted A, 50% duty 1s blink, 1s refresh
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 17:41:33 +02:00
Jakub
20151cc609 fix: align status bar icons to y=0, reduce battery icon to 8px height
BT indicator and mute icon were at y=1 (1px below text baseline).
Battery icon height reduced 10→8px to match text height.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 17:34:57 +02:00
Jakub
8efff38a4f fix: auto-advert indicator - capital A with space, slow blink, proper refresh
- Changed ~ to " A" (space + capital A, no inverted background)
- Blink: on 500ms / off 1500ms (once per 2s)
- Home screen refresh rate drops to 500ms when auto-advert is active
  so the blink is actually visible

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 17:25:27 +02:00
Jakub
e38dc87c25 feat: blinking ~ indicator in status bar when auto-advert is active
Blinks at 4Hz (250ms toggle) with inverted background so it's clearly
visible without being distracting.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 16:51:57 +02:00
Jakub
09ac455806 feat: add periodic auto-advert with GPS position (Auto-Advert tool)
Adds configurable periodic 0-hop self-advert that includes GPS coordinates,
making the device's position visible in other nodes' Nearby Nodes screen.
Options: off / 1min / 2min / 5min / 10min / 30min / 1h.

Accessible via Tools → Auto-Advert. Timer runs in MyMesh::loop().

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 16:08:33 +02:00
Jakub
10307d3a03 fix: ping RTT 0ms, degree symbol, clock page dots overlap
- Ping: add _ping_ack_seen flag; RTT is measured only after ACK entry
  was first confirmed present in the table, preventing false 0ms when
  isAckPending() returns false before the entry is registered
- NearbyScreen: replace unsupported degree symbol \xb0 with 'd'
  in bearing format ("Dist:1.5km 45d")
- Clock page: hide page indicator dots (already hides node name/battery);
  eliminates overlap with size-2 clock text drawn at y=0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 13:28:52 +02:00
Jakub
24474f67fa feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
  type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
  ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
  page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
  Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
  y=19, separator at y=28, three data field rows at y=31/41/51;
  hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
  saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
  eliminating double libm and saving ~5-10 KB flash

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
Jakub
7b63af1068 refactor: use PopupMenu in keyboard and context menus, fix bugs
- KeyboardWidget: replace inline placeholder overlay with PopupMenu
- UITask QuickMsgScreen: replace inline CONTACT_PICK/CHANNEL_PICK context
  menus with PopupMenu; notif label stored in _ctx_notif_item at open time
- BotScreen: clear placeholder list for trigger field (expansion tokens
  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:26 +02:00
Jakub
42b51935b1 feat: sensor placeholders in messages and keyboard picker
expandMsg now expands {temp}, {hum}, {pres}, {batt}, {alt}, {lux}, {dist},
{co2} from live sensor readings. The [{}] picker in the on-screen keyboard
shows only sensors currently detected by SensorManager and scrolls when
more than 3 items fit the overlay.

New SensorPlaceholders.h helper keeps sensor-to-keyboard wiring out of
UITask.cpp. MsgExpand.h handles all expansion logic centrally; MyMeshBot
and QuickMsgScreen pass &sensors and batt_volts to each call.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 00:12:07 +02:00
Jakub
26d3d7ab00 refactor: extract fullscreen message view into reusable FullscreenMsgView module
Shared struct handles rendering and input for both DM and channel fullscreen
views, replacing ~80 lines of duplicated inline code. Fixes navigation arrow
indicators being swapped in channel fullscreen.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 23:54:26 +02:00
Jakub
c7887a940d feat: DM history fullscreen message view on ENTER
Pressing ENTER on a selected DM message now opens a fullscreen view
(matching channel history behavior) instead of going directly to compose.
Fullscreen supports UP/DOWN scrolling for long messages, LEFT/RIGHT to
navigate between messages, and ENTER/CANCEL to return to the list.
Compose is still accessible via [+ send] (ENTER when nothing selected).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 23:40:15 +02:00
Jakub
464f5cd7a2 fix: remove label prefix from keyboard preview for visual consistency
All three keyboard instances (Settings, QuickMsg, Bot) now show plain
text + cursor without any field-name prefix. Removes the label field
from KeyboardWidget entirely — the screen title already indicates context.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 21:09:47 +02:00
Jakub
07bd5f1167 refactor: extract on-screen keyboard into reusable KeyboardWidget module
All three keyboard implementations (SettingsScreen, QuickMsgScreen, BotScreen)
now share a single KeyboardWidget struct defined in KeyboardWidget.h, eliminating
~480 lines of duplicated render/input logic. Also fixes the SettingsScreen bug
where navigation keys were checked before the placeholder overlay, causing cursor
movement when the overlay was open.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 21:09:47 +02:00
Jakub
60867a4c9e fix: check getContactByIdx return value in DM context menu handler
Prevents using uninitialized ContactInfo if the lookup fails.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 21:09:47 +02:00
Jakub
eb0f436924 feat: DM per-contact notification settings and mark-as-read
Long-press ENTER in the DM contact list opens a context menu with:
- "Mark as read": clears unread counter for that contact
- "Notif: default/OFF/ON": cycles notification state (same as channels)

Notification state is persisted in NodePrefs (dm_notif[16] table,
keyed by 4-byte pub_key prefix). When muted, the buzzer is silenced
for that contact's messages; when force-on, buzzer plays even in
quiet mode. Default follows the global buzzer setting.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 21:09:47 +02:00
Jakub
b50f21060c fix: clear DM and channel unread counters on app sync completion
When companion app finishes syncing all messages (msgRead(0)),
clear both _dm_unread_table and _ch_unread so indicators match
the app's read state.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 21:09:47 +02:00
Jakub
4a4ff76f10 feat: DM chat history, per-contact unread badges, bot history, contact index fix
- Add DM_HIST phase in QuickMsgScreen: 32-entry ring buffer tracks incoming
  and outgoing DMs per contact; selecting a contact now opens history view
  before compose
- Per-contact DM unread badge in contact list (replaces hop count display)
- Bot replies (DM and channel) stored in history so they appear on-screen
- Fix contact index truncation: _sorted was uint8_t (max 255), causing
  contacts at indices 256-349 to map to wrong entries; changed to uint16_t
- Add addDMMsg() virtual to AbstractUITask; wire through MyMesh.cpp
- Incoming chat DMs call addDMMsg(); outgoing after successful send too
- msgRead(0) from companion app clears per-contact unread table

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 21:09:47 +02:00
Jakub
71635e5e6f Bot: DM+channel modes with separate replies; buzzer auto-mute; UX fixes
- Bot redesign: DM bot active for all private messages (no pubkey filter),
  channel bot optional with separate enable flag; both can run simultaneously
- Bot: one shared trigger, separate reply texts for DM and channel
- Buzzer: add Auto mode (mutes when BT connected, unmutes on disconnect);
  per-channel force-on overrides still work as intended
- Settings: defer flash writes to menu exit (only when dirty)
- Settings: add Seconds toggle (hide clock seconds, refresh drops 1s→60s)
- Fullscreen msg preview: fix truncation (fmsg buf 79→140 chars)
- RingtoneEditor: fix melody playback cut after first note (RTTTL lib holds
  pointer — moved play buffer to member variable)
- BotScreen: remove key hint footer
- .gitattributes: protect README.md from upstream merge conflicts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 21:09:47 +02:00
Jakub
d51d8c0a75 Extract RingtoneEditorScreen, BotScreen, ToolsScreen to separate headers
Move three custom screens out of UITask.cpp into their own .h files
(RingtoneEditorScreen.h, BotScreen.h, ToolsScreen.h) and replace them
with three #include lines. UITask.cpp now only contains screens that
overlap with upstream (SplashScreen, SettingsScreen, QuickMsgScreen,
HomeScreen), making future upstream merges easier — custom screens live
in files that upstream will never touch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 21:09:47 +02:00
Jakub
dcccc9733b Bot: global keyboard in BotScreen, placeholder expansion via MsgExpand.h
- Replace BotScreen's custom embedded keyboard with the shared global
  keyboard layout (KB_* constants): char rows at KB_CHARS_Y, special row
  at KB_SPECIAL_Y=48 with 5 buttons ([^][Sp][Del][{}][OK]), fixing the
  previous layout that rendered below the 64 px display boundary.
  Proportional column scaling between char rows (10 cols) and special row
  (5 cols). [{}] opens the placeholder picker overlay.

- Add MsgExpand.h: standalone inline expandMsg() with no framework
  dependencies, expanding {loc} and {time} placeholders. Used by both
  MyMesh (bot channel/DM replies) and QuickMsgScreen, replacing the
  duplicate expandBotMsg() static and the inline expandMsg() method.

- Bump FIRMWARE_VERSION to v1.15.3.

- ToolsScreen: remove key-hint footer line.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 21:09:47 +02:00
Jakub
ac44630cb8 ToolsScreen: match list style of other screens; bot DM: favourites only
ToolsScreen now follows the same rendering convention as QuickMsgScreen
and SettingsScreen: START_Y=12, '>' prefix on selected item, explicit
setColor per item (light fill → dark text for selection).

Bot DM contact picker now filters to favourites-only (flags & 0x01),
consistent with the DM contact list behaviour elsewhere in the UI.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 21:09:47 +02:00
Jakub
e638955898 Bot: add DM contact target alongside channel
The bot can now reply to a specific DM contact instead of a channel.

UI (BotScreen): item 1 cycles through all channels followed by all DM
contacts (ADV_TYPE_CHAT). The label switches between "Channel" / "Contact"
and the value shows the channel or contact name. Left/right/enter navigate
the combined list.

Logic (MyMesh::onMessageRecv): when bot_target_type==1, match the trigger
against incoming DM text and reply via sendMessage(). A 4-byte pubkey
prefix identifies the target; all-zero prefix matches any DM sender.

Persistence: bot_target_type and bot_dm_pubkey[4] appended to prefs with
backward-compat read guard (old firmware defaults to channel mode).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 21:09:47 +02:00
Jakub
9756bb062c Fix 4 bugs found in code review
- BotScreen keyboard: add explicit null-terminator after strncpy when
  trigger/reply text reaches max length (strncpy skips it in that case)
- BotScreen keyboard: clamp _kb_col to 3 when moving DOWN to special row,
  so the cursor doesn't become invisible (col > 3 has no special-row item)
- MyMesh: initialise _bot_last_reply_ms = 0 in constructor (was relying on
  BSS zero-init, which is technically UB for a non-trivial class member)
- RingtoneEditor: exit via CANCEL/Save/Discard goes to ToolsScreen instead
  of HomeScreen — consistent with BotScreen and expected navigation flow

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 21:09:47 +02:00
Jakub
58afc23df3 Add Auto-Reply Bot and Tools screen to home carousel
- New 'Tools' page on the home screen carousel with Ringtone Editor and Auto-Reply Bot items
- Bot settings: enable/disable toggle, channel picker, trigger phrase, reply text (keyboard with pre-fill)
- Bot logic in MyMesh: case-insensitive substring match, 10 s cooldown to prevent reply loops
- Settings: Home Pages visibility control (which carousel pages are shown; Settings and Messages always visible)
- DataStore: persist home_pages_mask and bot fields with backward-compat guards on read

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 21:09:47 +02:00
Jakub
f0f142890f Fix home_pages_mask=0 inconsistency and navPage fallback
- Remove mask==0 guard from isPageVisible/homePageVisible/toggle:
  mask=0 now correctly means "all optional pages hidden" instead of
  silently reverting to all-visible. Initialization always sets 0x01FF
  before loadPrefs so 0 only occurs if user explicitly hides everything.
- Simplify toggle handler: direct XOR on home_pages_mask.
- HomeScreen: use navPage(+1) as fallback when current page becomes
  hidden, instead of always jumping to i=0.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 21:09:47 +02:00
Jakub
2fc9e094e7 Settings: add Home Pages visibility control
New "Home Pages" section in Settings lets the user toggle which pages
appear in the home screen carousel. Settings and Messages are always
shown; Clock, Recent, Radio, Bluetooth, Advert, GPS, Sensors, Tools
and Shutdown can each be hidden individually.

- NodePrefs: home_pages_mask uint16_t (bit per page, 0=all visible)
- HomeScreen: isPageVisible/navPage skip hidden pages; dots indicator
  counts only visible pages; enforces visible page on re-entry
- SettingsScreen: SECTION_HOME_PAGES with per-page ON/OFF toggles
- DataStore: persisted at offset 1598
- Default: all pages visible (0x01FF)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 21:09:47 +02:00
Jakub
95805da4b2 RingtoneEditor: play note preview on pitch/octave change
When UP/DOWN changes pitch or ENTER cycles octave, a short 1/16-note
preview (BPM 240) is played immediately so the user hears the current
note while composing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 21:09:47 +02:00
Jakub
4891a36c72 Add ringtone editor, buzzer volume, unread fixes and buzzer notification fixes
- RingtoneEditorScreen: 32-note step sequencer accessible via new Tools page on home screen. U/D=pitch, ENTER=octave cycle, L/R=cursor, MENU=options (play, duration, BPM, insert/delete, save). Notes stored packed in NodePrefs and persisted via DataStore.
- ToolsScreen: new "Tools" home page entry launching the editor
- Buzzer volume: 0-4 bar control in Settings > Sound, persisted to prefs
- Buzzer notifications: fixed notify() firing inside wrong serial-guard branch and before addChannelMsg set the channel index
- Unread counter: fixed newest-first index direction; watermark formula now correct
- OLED brightness: wider range via pre-charge register 0xD9 combined with contrast

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 21:09:47 +02:00
Jakub
ffa568938f Settings: add buzzer volume control (5 levels, like brightness)
- NodePrefs: add buzzer_volume field (0=min..4=max, default 4)
- DataStore: persist buzzer_volume at end of prefs file (backward-compat)
- buzzer: setVolume()/getVolume(); applyVolume() sets PWM duty cycle via
  analogWrite() after each rtttl::play() call to control output amplitude
- Settings UI: new BzrVol bar item in Sound section (left/right to adjust)
- UITask: apply saved volume on startup alongside brightness

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 21:09:47 +02:00
Jakub
861a65d4b1 Fix buzzer notifications, unread tracking, OLED dimming; remove dead code
- MyMesh: notify() now always fires for channel/contact messages regardless
  of serial connection; called after addChannelMsg so channel index is set
- ChannelHist: fix unread watermark direction (histEntry is newest-first,
  index 0=newest); counter now decrements correctly as user reads messages
- ChannelHist: reset unread to 0 after sending (user is active in channel)
- SH1106Display: add pre-charge register (0xD9) to setBrightness for wider
  dimming range; re-apply contrast+precharge in turnOn(); tune curve
- UITask: remove MsgPreviewScreen dead code (~180 lines, never shown)
- UITask: fix strcpy→snprintf in showAlert()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 21:09:47 +02:00
Jakub
40f470ebf2 Sensors page: show placeholders for all connected sensors; support all LPP types
- SensorManager::getAvailableLPPTypes() reports which LPP types will be
  present based on initialized sensor flags (temp, humidity, pressure, etc.)
- Sensors page renders one row per available type; shows "--" for any sensor
  that hasn't produced a reading yet instead of hiding the row entirely
- LPPReader: add readLuminosity, readPercentage, readConcentration, readDistance
- All known LPP types now have proper names and units in the sensor display

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 21:09:47 +02:00
Jakub
9d92fb62df Buzzer: add playForced(); fix per-channel force-on notification ignoring global mute
buzzer.play() was returning early when _is_quiet=true, so channels with
explicit notification=ON were silent even though the logic correctly set
play=true. playForced() bypasses the quiet check for these overrides.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 21:09:47 +02:00
Jakub
1281ec5c35 ChannelHist: lazy unread counter + fullscreen left/right message navigation
- Unread badge now clears gradually as the user navigates through messages
  instead of instantly on channel entry; counter tracks the highest-seen
  message watermark so navigating back doesn't re-add unread count
- Fullscreen view: LEFT/RIGHT switch between messages in the thread (resets
  scroll); < > arrows shown at bottom corners when adjacent messages exist
- Navigating in list (UP/DOWN) or entering fullscreen also marks messages read

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 21:09:47 +02:00
Jakub
f603d82b0c MsgPreview: fix nav-to-home from chat screen; add fullscreen left/right message switching
- msgRead(0) now only navigates to home when curr == msg_preview, preventing
  unexpected redirects while the user is in the chat selection screen
- Fullscreen message view: LEFT/RIGHT navigate between unread messages (resets
  scroll to top); < > hints shown at bottom corners when more messages available

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 21:09:47 +02:00
Jakub
eea9254212 Fix several bugs found in code review
- DataStore: persist custom_msgs, ch_notif_override/muted, dm_show_all,
  room_fav_only — none were being saved/loaded, all settings lost on reboot.
  Load uses file.available() guard for backward compat with old saves.
- QuickMsgScreen reset(): clear _ctx_open/_ctx_dirty/_ctx_sel so context
  menu state is clean when re-entering the messaging screen.
- Remove unused _ctx_ch_idx field (code always uses _channel_indices[_channel_sel]).
- _room_unread: cap at _msgcount before incrementing to prevent badge
  going negative (getMsgCount() - getRoomUnreadCount()).

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