feat(ui): page the fullscreen message view in reading order

LEFT went to the newer message and RIGHT to the older one, which reads
backwards against the page metaphor the "<" / ">" markers set up. Swap it:
LEFT turns back to the older message, RIGHT forward to the newer one, and
the markers follow (they were keyed to the opposite flags).

PREV/NEXT are named in message order, not screen order -- MessagesScreen's
_hist_sel counts newest-first, so PREV is the older message -- so only the
key mapping and the two marker conditions change; the caller side is
untouched. Applies to both the DM and channel fullscreen views, which share
handleInput(). AdminScreen's reply view treats every non-NONE result as
"close", so it is unaffected.

Docs and release notes updated to match.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-08-24 12:13:41 +02:00
co-authored by Claude Opus 5
parent 51c06b78c1
commit 13fb4a189f
3 changed files with 12 additions and 5 deletions
@@ -74,7 +74,7 @@ Messages appear as chat bubbles sized to their content — **right**-anchored fo
| :-----------------------: | :-----------------------: |
| ![](./fullscreen_oled.png) | ![](./fullscreen_eink.png) |
Navigate between messages with **LEFT** (newer) and **RIGHT** (older). Long messages scroll with **UP/DOWN**.
Navigate between messages like pages in a book — **LEFT** goes back to the older message, **RIGHT** forward to the newer one. The `<` / `>` markers along the bottom edge show which directions still have a message. Long messages scroll with **UP/DOWN**.
If the message is a reply addressed to someone (`@[nick]`), a **To: nick** bar is shown below the sender name and the body is displayed without the address prefix.
@@ -157,12 +157,14 @@ struct FullscreenMsgView {
display.print(s_wrap_lines[scroll + i]);
}
drawScrollIndicator(display, startY, visible * lineH, lcount, visible, scroll);
// Page markers read like a book: the older message is back to the left,
// the newer one forward to the right (see handleInput).
const int nav_y = display.height() - lineH;
if (has_next) {
if (has_prev) {
display.setCursor(0, nav_y);
display.print("<");
}
if (has_prev) {
if (has_next) {
display.setCursor(display.width() - cw, nav_y);
display.print(">");
}
@@ -172,8 +174,12 @@ struct FullscreenMsgView {
Result handleInput(char c) {
if (c == KEY_UP) { if (scroll > 0) scroll--; return NONE; }
if (c == KEY_DOWN) { if (scroll < _max_scroll) scroll++; return NONE; }
if (keyIsPrev(c)) return NEXT; // page between messages (encoder too)
if (keyIsNext(c)) return PREV;
// Page between messages (encoder too), in reading order: left goes back to
// the older message, right forward to the newer one. PREV/NEXT are named in
// message order, and MessagesScreen's _hist_sel counts newest-first, so PREV
// (older) is what a left press means.
if (keyIsPrev(c)) return PREV;
if (keyIsNext(c)) return NEXT;
if (c == KEY_CONTEXT_MENU) return REPLY;
if (c == KEY_ENTER || c == KEY_CANCEL) return CLOSE;
return NONE;
+1
View File
@@ -6,6 +6,7 @@
### Fixes
- **Paging through the fullscreen message view now reads like a book** — **RIGHT** moves forward to the newer message, **LEFT** back to the older one, and the `<` / `>` markers along the bottom edge follow suit. It was the other way round before.
- **Hardware CAD and RSSI interference-threshold detection were silently hardcoded off on companion_radio**, regardless of any setting. CAD now auto-enables whenever RX power-save (duty-cycle) is active — the noise floor isn't kept fresh during duty-cycle sleep, so a fresh hardware channel scan before TX is needed instead of the RSSI-threshold check.
- **A long message could split a multi-byte UTF-8 character in half when truncated to fit the send frame** (e.g. a Polish/accented character landing right at the cut-off), delivering a mangled trailing byte to the app. Truncation now stops at the last complete character.
- **Deleting the default "Public" channel didn't stick — it came back on every reboot.** A deleted channel is correctly left out of the saved channel file (to avoid rewriting it every save), but the Public channel was unconditionally re-added at every boot *before* the saved list was loaded, so its absence from the file never got a chance to matter. Now only seeded on a genuinely fresh device (no channel file yet) — once you've saved any channel state at all, your own list is authoritative.