From 687a31e657e150adae810b77256fbd9c739380a4 Mon Sep 17 00:00:00 2001 From: Jakub <106778416+MarekZegare4@users.noreply.github.com> Date: Fri, 2 Oct 2026 13:49:26 +0200 Subject: [PATCH] refactor(ui): remove dead code and fix stale comments after the review - ui-core: Diagnostics::systemLines, NearbyModel::typeShort, rptctl::currentPresetName, RoomSessions::pendingKey, settings::isSwitch, UiCore::reconcileDMUnread. - ui-new: the retired Recent page, fmtAlt, the battMvToPercent wrapper, setFavouriteChannelSlot, AccordionList onHeader/visibleCount, drawBoxedIcon and seven unused icons; info::row folded into info::valueRow. - ui-lvgl: onOpenNav, TileDownloader::withTrails; the trail header size is one constant (TRAIL_HDR) for live and saved trails. - Home quick panels save prefs only when a value changed. - Comments brought in line with the code (status bar reserve, settings rows, page order, SH1106 frame skip, UiCore header). Co-Authored-By: Claude Opus 5.5 --- examples/companion_radio/main.cpp | 16 ++--- .../companion_radio/ui-core/Diagnostics.h | 18 ------ .../companion_radio/ui-core/NearbyModel.h | 10 --- .../companion_radio/ui-core/RepeaterControl.h | 5 -- .../companion_radio/ui-core/RoomSessions.h | 1 - .../companion_radio/ui-core/SettingsSchema.h | 2 - examples/companion_radio/ui-core/UiCore.h | 4 +- examples/companion_radio/ui-lvgl/NavMap.h | 4 +- examples/companion_radio/ui-lvgl/UITask.cpp | 1 - .../ui-lvgl/map/TileDownloader.h | 1 - .../companion_radio/ui-new/AccordionList.h | 2 - examples/companion_radio/ui-new/InfoKit.h | 19 ++---- .../companion_radio/ui-new/SettingsScreen.h | 5 +- .../companion_radio/ui-new/StatusScreen.h | 6 +- examples/companion_radio/ui-new/TrailScreen.h | 6 +- examples/companion_radio/ui-new/UITask.cpp | 61 ++++++++----------- examples/companion_radio/ui-new/UITask.h | 1 - examples/companion_radio/ui-new/icons.h | 61 +------------------ src/helpers/ui/SH1106Display.h | 12 ++-- 19 files changed, 58 insertions(+), 177 deletions(-) diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 4f3f2222..8271f4e4 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -543,14 +543,6 @@ extern "C" EMSCRIPTEN_KEEPALIVE int sim_test_set_timezone_hours(int hours) { return 1; } -// auto_off_secs (NodePrefs.h) defaults to 15 -- a real device's screen -// (and, if auto_lock is also set, the UI itself) turns off/locks 15s -// after the last input, real power-saving behaviour a battery-powered -// board actually needs. UITask::autoOffMillis() (ui-new/UITask.h) treats -// 0 as "never" and skips the whole turnOff()/auto_lock branch in -// UITask::loop() entirely -- there's no separate flag to touch. A demo -// running on a visitor's screen has no battery to save and no reason to -// go dark or lock itself while they're reading it. // Renames this instance (NodePrefs::node_name, what Settings > Name sets) so // a host page's peers have readable names in screenshots, instead of the // default hex of their public key. In memory only; the next advert carries it. @@ -562,6 +554,14 @@ extern "C" EMSCRIPTEN_KEEPALIVE int sim_test_set_node_name(const char* name) { return 1; } +// auto_off_secs (NodePrefs.h) defaults to 15 -- a real device's screen +// (and, if auto_lock is also set, the UI itself) turns off/locks 15s +// after the last input, real power-saving behaviour a battery-powered +// board actually needs. UITask::autoOffMillis() (ui-new/UITask.h) treats +// 0 as "never" and skips the whole turnOff()/auto_lock branch in +// UITask::loop() entirely -- there's no separate flag to touch. A demo +// running on a visitor's screen has no battery to save and no reason to +// go dark or lock itself while they're reading it. extern "C" EMSCRIPTEN_KEEPALIVE int sim_test_disable_screen_timeout() { if (!g_sim_ready) return 0; NodePrefs* prefs = the_mesh.getNodePrefs(); diff --git a/examples/companion_radio/ui-core/Diagnostics.h b/examples/companion_radio/ui-core/Diagnostics.h index 37717732..276d0c27 100644 --- a/examples/companion_radio/ui-core/Diagnostics.h +++ b/examples/companion_radio/ui-core/Diagnostics.h @@ -118,24 +118,6 @@ inline void shortVersion(char* out, size_t n) { out[len] = '\0'; } -// Static system card, one full-width line each. Returns the line count. -inline int systemLines(Line* lines) { - int n = 0; - char sv[24]; - shortVersion(sv, sizeof(sv)); - addLine(lines, n, "FW %s", sv); - addLine(lines, n, "Built %s", FIRMWARE_BUILD_DATE); - addLine(lines, n, "Dev %s", board.getManufacturerName()); - addLine(lines, n, "Node %s", the_mesh.getNodeName()); - NodePrefs* p = the_mesh.getNodePrefs(); - if (p) { - addLine(lines, n, "Freq %.3f MHz", p->freq); - addLine(lines, n, "SF%u BW%.0f CR%u", (unsigned)p->sf, p->bw, (unsigned)p->cr); - addLine(lines, n, "TX %d dBm", (int)p->tx_power_dbm); - } - return n; -} - // One sample per script the UI font covers, to check coverage by eye. "Acc" // takes one letter from each of the keyboard's accent-popup groups. inline int fontLines(Line* lines) { diff --git a/examples/companion_radio/ui-core/NearbyModel.h b/examples/companion_radio/ui-core/NearbyModel.h index ce730b3f..ce49c1ff 100644 --- a/examples/companion_radio/ui-core/NearbyModel.h +++ b/examples/companion_radio/ui-core/NearbyModel.h @@ -68,16 +68,6 @@ public: } } - static const char* typeShort(uint8_t t) { - switch (t) { - case ADV_TYPE_REPEATER: return "Rpt"; - case ADV_TYPE_SENSOR: return "Snsr"; - case ADV_TYPE_ROOM: return "Room"; - case ADV_TYPE_CHAT: return "Comp"; - default: return "?"; - } - } - void bindModel(UiCore* core, NodePrefs* prefs) { _core = core; _prefs = prefs; } int count() const { return _count; } diff --git a/examples/companion_radio/ui-core/RepeaterControl.h b/examples/companion_radio/ui-core/RepeaterControl.h index 0f40d03a..23dcb173 100644 --- a/examples/companion_radio/ui-core/RepeaterControl.h +++ b/examples/companion_radio/ui-core/RepeaterControl.h @@ -61,11 +61,6 @@ static int currentPreset(const NodePrefs* p) { return -1; } -static const char* currentPresetName(const NodePrefs* p) { - const char* name; float f, b; uint8_t s, c; - return radioctl::presetAt(p, currentPreset(p), name, f, b, s, c) ? name : "Custom"; -} - static bool choosePreset(NodePrefs* p, int idx) { const char* name; float f, b; uint8_t s, c; if (!p || !radioctl::presetAt(p, idx, name, f, b, s, c)) return false; diff --git a/examples/companion_radio/ui-core/RoomSessions.h b/examples/companion_radio/ui-core/RoomSessions.h index e8870433..55adcf50 100644 --- a/examples/companion_radio/ui-core/RoomSessions.h +++ b/examples/companion_radio/ui-core/RoomSessions.h @@ -43,7 +43,6 @@ public: bool loggingIn() const { return _pending; } bool loggingIn(const uint8_t* pub_key) const { return _pending && memcmp(_pending_key, pub_key, 4) == 0; } - const uint8_t* pendingKey() const { return _pending_key; } // Local only: the room ACL has no session to tear down (MyMesh::logoutRoom()). void logout(const uint8_t* pub_key) { diff --git a/examples/companion_radio/ui-core/SettingsSchema.h b/examples/companion_radio/ui-core/SettingsSchema.h index 8c3edfe0..13ef8ba5 100644 --- a/examples/companion_radio/ui-core/SettingsSchema.h +++ b/examples/companion_radio/ui-core/SettingsSchema.h @@ -249,8 +249,6 @@ static const char* shortLabel(const Setting& s) { for (auto& l : SHORT_LABELS) if (l.offset == s.offset) return l.text; return s.label; } -// A switch: two values, no labels of its own (on / off, however it's stored). -static bool isSwitch(const Setting& s) { return !s.option && s.count == 2; } // The index of the setting over NodePrefs field `offset`; -1 if none. static int indexOf(uint16_t offset) { for (int i = 0; i < COUNT; i++) if (ALL[i].offset == offset) return i; diff --git a/examples/companion_radio/ui-core/UiCore.h b/examples/companion_radio/ui-core/UiCore.h index 1d079af0..7927712a 100644 --- a/examples/companion_radio/ui-core/UiCore.h +++ b/examples/companion_radio/ui-core/UiCore.h @@ -1,6 +1,7 @@ #pragma once // UI Core: hardware-independent UI state and logic shared by every frontend -// (ui-new today, ui-lvgl later). See docs/solo/developer/ui-core.md. +// (ui-new on the OLED / e-ink boards, ui-lvgl on the L2). See +// docs/solo/developer/ui-core.md. // // The Core is MyMesh's Listener: it files incoming/outgoing messages into the // history, keeps the unread counters, runs the engines, and tells the frontend @@ -89,7 +90,6 @@ public: void clearAllDMUnread() { dm_unread.clearAll(); } // Every conversation read: direct, channels, rooms. void markAllRead() { _room_unread = 0; dm_unread.clearAll(); history.clearAllChannelUnread(); } - void reconcileDMUnread() { dm_unread.reconcile(history); } // ── Actions ─────────────────────────────────────────────────────────────── // Send a DM composed on the device and file it (with end-to-end ACK tracking diff --git a/examples/companion_radio/ui-lvgl/NavMap.h b/examples/companion_radio/ui-lvgl/NavMap.h index 8d8134ec..463dec93 100644 --- a/examples/companion_radio/ui-lvgl/NavMap.h +++ b/examples/companion_radio/ui-lvgl/NavMap.h @@ -1131,7 +1131,7 @@ bool UITask::saveTrailToCard(char* name_out, size_t n) { // ring (the oldest points dropping off) is rewritten, at most every 10 min. namespace navmap { static const char* const LIVE_TRAIL = "/sdcard/trails/.live.trl"; -static const long LIVE_HDR = 12; // magic, version, reserved, count (uint16), time (uint32) +static const long TRAIL_HDR = 12; // a trail file's header (TrailStore::writeTo): magic, version, flags, count (uint16), time (uint32) static uint32_t s_j_gen = 0xFFFFFFFF, s_j_seq = 0, s_j_next = 0, s_j_full_at = 0; static int s_j_n = 0; static uint8_t s_j_rec = 0; // the "recording" byte last written @@ -1202,7 +1202,7 @@ void UITask::savedTrailsPopup() { navmap::trailTitle(navmap::s_st_names[i], title, sizeof(title)); snprintf(path, sizeof(path), "%s/%s", navmap::TRAILS_DIR, navmap::s_st_names[i]); struct stat st; - long pts = stat(path, &st) == 0 ? ((long)st.st_size - 12) / (long)sizeof(TrailPoint) : 0; + long pts = stat(path, &st) == 0 ? ((long)st.st_size - navmap::TRAIL_HDR) / (long)sizeof(TrailPoint) : 0; snprintf(sub, sizeof(sub), "%ld points", pts > 0 ? pts : 0); listRow(list, title, sub, onSavedTrail, (void*)(intptr_t)i); } diff --git a/examples/companion_radio/ui-lvgl/UITask.cpp b/examples/companion_radio/ui-lvgl/UITask.cpp index 0439da97..32d6125e 100644 --- a/examples/companion_radio/ui-lvgl/UITask.cpp +++ b/examples/companion_radio/ui-lvgl/UITask.cpp @@ -2786,7 +2786,6 @@ static void onOpenSettings(lv_event_t* e) { (void)e; s_ui->showSettings(); } static void onOpenNearby(lv_event_t* e) { (void)e; s_ui->showNearby(); } static void onOpenNodesMap(lv_event_t* e) { (void)e; s_ui->openMap(false); } -static void onOpenNav(lv_event_t* e) { (void)e; s_ui->openMap(true); } static void onOpenClock(lv_event_t* e); // ClockScreen.h static void onOpenBot(lv_event_t* e); // BotScreen.h static void onOpenCompass(lv_event_t* e); // CompassScreen.h diff --git a/examples/companion_radio/ui-lvgl/map/TileDownloader.h b/examples/companion_radio/ui-lvgl/map/TileDownloader.h index d8a0af52..0e7963c3 100644 --- a/examples/companion_radio/ui-lvgl/map/TileDownloader.h +++ b/examples/companion_radio/ui-lvgl/map/TileDownloader.h @@ -95,7 +95,6 @@ public: State state() const { return _state; } const TileArea& area() const { return _area; } // of the current / last job - bool withTrails() const { return _trails; } bool active() const { return _state == CONNECTING || _state == RUNNING; } uint32_t total() const { return _total; } uint32_t processed() const { return _done + _skipped + _failed; } diff --git a/examples/companion_radio/ui-new/AccordionList.h b/examples/companion_radio/ui-new/AccordionList.h index 05d3fe26..0b5529e0 100644 --- a/examples/companion_radio/ui-new/AccordionList.h +++ b/examples/companion_radio/ui-new/AccordionList.h @@ -35,10 +35,8 @@ public: rebuild(); } - int visibleCount() const { return _vis_count; } bool collapsed(int sec) const { return (_collapsed >> sec) & 1; } const Row& selected() const { return _rows[_sel]; } - bool onHeader() const { return _vis_count && _rows[_sel].item < 0; } // Paint via the shared drawList skeleton. `header(sec, y, sel, reserve, // collapsed)` and `item(sec, item, y, sel, reserve)` each draw one row diff --git a/examples/companion_radio/ui-new/InfoKit.h b/examples/companion_radio/ui-new/InfoKit.h index 9cd90e02..95f7d0c9 100644 --- a/examples/companion_radio/ui-new/InfoKit.h +++ b/examples/companion_radio/ui-new/InfoKit.h @@ -11,9 +11,8 @@ namespace info { -// Label at the left, value flush right with its unit after a space ("50 m"). -// `reserve` keeps the value clear of a scroll column. // Whether a value fits on its label's line (else rowH() gives it a line below). +// `reserve` keeps the value clear of a scroll column. inline bool rowFits(DisplayDriver& d, const char* label, const char* value, int reserve = 0, int x0 = 1) { if (!value || !*value) return true; const int room = d.width() - reserve - 2 - (x0 + d.getTextWidth(label) + d.getCharWidth()); @@ -40,17 +39,6 @@ inline void rowWrapped(DisplayDriver& d, int y, const char* label, const char* v else d.drawTextEllipsized(right - room, vy, room, value); } -// A value too long for the space left beside its label ends in an ellipsis. -inline void row(DisplayDriver& d, int y, const char* label, const char* value, int reserve = 0, int x0 = 1) { - d.setCursor(x0, y); - d.print(label); - if (!value || !*value) return; - const int right = d.width() - reserve - 2; - const int room = right - (x0 + d.getTextWidth(label) + d.getCharWidth()); - if ((int)d.getTextWidth(value) <= room) d.drawTextRightAlign(right, y, value); - else if (room > d.getCharWidth()) d.drawTextEllipsized(right - room, y, room, value); -} - // A dotted rule across [x, x + w), the separator between categories. inline void rule(DisplayDriver& d, int x, int y, int w) { const int s = miniIconScale(d); @@ -146,8 +134,9 @@ inline void toggle(DisplayDriver& d, int x_right, int y, bool on, bool inv = fal } // ── List rows (call after drawRowSelection(); text is already in the row's ink) -// A setting or field: label at x0, value flush right. A value too long for the -// room beside the label is cut, and scrolls while the row is selected. +// A setting or field: label at x0, value flush right with its unit after a +// space ("50 m"). A value too long for the room beside the label is cut, and +// scrolls while the row is selected (sel). // Returns that marquee's next-step delay (0: nothing moving). inline int valueRow(DisplayDriver& d, int y, const char* label, const char* value, bool sel, int reserve, int x0 = 2) { diff --git a/examples/companion_radio/ui-new/SettingsScreen.h b/examples/companion_radio/ui-new/SettingsScreen.h index cac5c9ba..e62b4155 100644 --- a/examples/companion_radio/ui-new/SettingsScreen.h +++ b/examples/companion_radio/ui-new/SettingsScreen.h @@ -400,8 +400,9 @@ class SettingsScreen : public UIScreen { } } // Default: CLOCK FAVOURITES RECENT RADIO BT ADVERT [GPS] [SENSORS] SETTINGS - // MAP TOOLS MESSAGES SHUTDOWN — mirrors the home-carousel enum order. Every - // page has an explicit slot now (PAGE_ORDER_LEN == HPB_COUNT). + // MAP TOOLS MESSAGES SHUTDOWN. Every bit keeps a slot (PAGE_ORDER_LEN == + // HPB_COUNT), retired ones too: RECENT shows nowhere, and Status stands in + // the RADIO slot for the old Radio, GPS and Sensors pages. int j = 0; p->page_order[j++] = NodePrefs::HPB_CLOCK + 1; p->page_order[j++] = NodePrefs::HPB_FAVOURITES + 1; diff --git a/examples/companion_radio/ui-new/StatusScreen.h b/examples/companion_radio/ui-new/StatusScreen.h index ea14e686..6cc217c6 100644 --- a/examples/companion_radio/ui-new/StatusScreen.h +++ b/examples/companion_radio/ui-new/StatusScreen.h @@ -107,7 +107,7 @@ private: if (f.place(d.lineStep())) { // label, the last half hour, the value now const int nf = (int)radio_driver.getNoiseFloor(); if (nf) snprintf(a, sizeof(a), "%d dBm", nf); else strcpy(a, "-"); - info::row(d, f.at, "Noise", a, rsv); + info::valueRow(d, f.at, "Noise", a, false, rsv, 1); const int x0 = 1 + d.getTextWidth("Noise") + 6; const int x1 = d.width() - rsv - 2 - d.getTextWidth(a) - 6; if (x1 - x0 > 12) info::spark(d, x0, f.at, x1 - x0, lh - 1, _noise); @@ -115,7 +115,7 @@ private: if (f.place(d.lineStep())) { const float snr = radio_driver.getLastSNR(); snprintf(b, sizeof(b), "%d dBm", (int)radio_driver.getLastRSSI()); - info::row(d, f.at, "Last RX", b, rsv); + info::valueRow(d, f.at, "Last RX", b, false, rsv, 1); drawSignalBars(d, d.width() - rsv - 2 - d.getTextWidth(b) - 5, f.at, (int)(snr * 4)); } snprintf(b, sizeof(b), "%.1f dB", radio_driver.getLastSNR()); @@ -128,7 +128,7 @@ private: const int lh = d.getLineHeight(); const bool on = _task->getGPSState(); if (f.place(d.lineStep())) { - info::row(d, f.at, "GPS", nullptr, rsv); + info::valueRow(d, f.at, "GPS", nullptr, false, rsv, 1); info::toggle(d, d.width() - rsv - 2, f.at, on); } LocationProvider* loc = sensors.getLocationProvider(); diff --git a/examples/companion_radio/ui-new/TrailScreen.h b/examples/companion_radio/ui-new/TrailScreen.h index f99b9f5d..3ad11395 100644 --- a/examples/companion_radio/ui-new/TrailScreen.h +++ b/examples/companion_radio/ui-new/TrailScreen.h @@ -525,15 +525,15 @@ private: } if (f.place(d.lineStep())) { snprintf(a, sizeof(a), "%d", _store->count()); - info::row(d, f.at, "Points", a, rsv); + info::valueRow(d, f.at, "Points", a, false, rsv, 1); const int x0 = 1 + d.getTextWidth("Points") + 6, x1 = right - d.getTextWidth("000") - 6; if (x1 - x0 > 10) info::meter(d, x0, f.at, x1 - x0, lh - 1, (float)_store->count() / TrailStore::CAPACITY); } if (f.place(d.lineStep())) { formatAvgPaceOrSpeed(a, sizeof(a), _task->getNodePrefs()); char* c = strstr(a, ": "); // "Avg: 5 km/h" -> label + value - if (c) { *c = 0; info::row(d, f.at, a, c + 2, rsv); } - else info::row(d, f.at, a, nullptr, rsv); + if (c) { *c = 0; info::valueRow(d, f.at, a, c + 2, false, rsv, 1); } + else info::valueRow(d, f.at, a, nullptr, false, rsv, 1); } } diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 7546febc..b17206e0 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -48,10 +48,6 @@ #define LONG_PRESS_MILLIS 1200 -#ifndef UI_RECENT_LIST_SIZE - #define UI_RECENT_LIST_SIZE 4 -#endif - #include "icons.h" #include "../ui-core/Lettering.h" // boot splash wordmark + lettering (shared with the L2) @@ -140,6 +136,11 @@ public: static const int QUICK_MSGS_MAX = 10; +// Telemetry (ui-core/Telemetry.h) in this display's font and width: tight for +// the dashboard's short fields, spaced ("49 m") on the Status screen's rows. +static const telemetry::Style L1_TELEMETRY = { "\xc2\xb0", false, false, 3 }; +static const telemetry::Style L1_INFO = { "\xc2\xb0", true, false, 5 }; + // ── Screen fragments — included into THIS translation unit only ─────────────── // These headers are not standalone: they are compiled solely as part of // UITask.cpp, in the order below. Two consequences a new screen must respect: @@ -151,14 +152,6 @@ static const int QUICK_MSGS_MAX = 10; // scope (e.g. NearbyScreen::FILTER_LABELS), so including any of them from a // second .cpp is a duplicate-symbol link error. Keep them UITask-internal; // anything genuinely shareable belongs in a real header (icons.h, GeoUtils.h). -// Telemetry (ui-core/Telemetry.h) in this display's font and width: tight for -// the dashboard's short fields, spaced ("49 m") on the Status screen's rows. -static const telemetry::Style L1_TELEMETRY = { "\xc2\xb0", false, false, 3 }; -static const telemetry::Style L1_INFO = { "\xc2\xb0", true, false, 5 }; -// Altitude (baro or GPS) in Settings > System > Units, always the small unit. -static void fmtAlt(char* buf, int n, float meters, bool imperial) { - telemetry::altText(meters, imperial, L1_TELEMETRY, buf, n); -} #include "FullscreenMsgView.h" #include "../ui-core/MessageText.h" @@ -192,9 +185,6 @@ static void fmtAlt(char* buf, int n, float meters, bool imperial) { #include "../ui-core/Battery.h" -// Voltage -> battery %, from the Core (top bar and dashboard Batt% alike). -static int battMvToPercent(int mv, int low_mv) { return battery::percent(mv, low_mv); } - // The time on a tall portrait panel (e-ink in portrait -- height > width): HH // and MM stacked on two lines in the huge built-in font (size 4, ~56 px tall) // so the digits fill the narrow width. Returns the y just below it. @@ -300,7 +290,6 @@ class HomeScreen : public UIScreen { enum HomePage { CLOCK, FAVOURITES, - RECENT, STATUS, // radio, GPS, power, mesh at a glance (took over Radio / GPS / Sensors) BLUETOOTH, ADVERT, @@ -393,6 +382,7 @@ class HomeScreen : public UIScreen { }; uint8_t _quick_sel = 0; bool _quick_edit = false; // the selected row is being changed with Left/Right + bool _quick_dirty = false; // ...and has moved: saved once when it ends static bool isQuickPage(int p) { return p == SETTINGS || p == QUICK_MSG || p == BLUETOOTH || p == ADVERT || p == TOOLS || p == SHUTDOWN; } @@ -536,7 +526,7 @@ class HomeScreen : public UIScreen { // The charge left, which is what you weigh before switching off. const int mv = _task->getBattMilliVolts(); snprintf(rows[n - 1].value, sizeof(rows[0].value), "%d%%%s", - battMvToPercent(mv, _node_prefs ? (int)_node_prefs->low_batt_mv : 0), + battery::percent(mv, _node_prefs ? (int)_node_prefs->low_batt_mv : 0), board.isExternalPowered() ? " USB" : ""); } return n; @@ -637,7 +627,6 @@ class HomeScreen : public UIScreen { int pageBit(int page) const { if (page == CLOCK) return NodePrefs::HPB_CLOCK; if (page == FAVOURITES) return NodePrefs::HPB_FAVOURITES; - if (page == RECENT) return NodePrefs::HPB_RECENT; if (page == STATUS) return NodePrefs::HPB_RADIO; // the old Radio slot if (page == BLUETOOTH) return NodePrefs::HPB_BLUETOOTH; if (page == ADVERT) return NodePrefs::HPB_ADVERT; @@ -653,7 +642,6 @@ class HomeScreen : public UIScreen { switch (bit) { case NodePrefs::HPB_CLOCK: return CLOCK; case NodePrefs::HPB_FAVOURITES: return FAVOURITES; - case NodePrefs::HPB_RECENT: return RECENT; case NodePrefs::HPB_RADIO: return STATUS; case NodePrefs::HPB_BLUETOOTH: return BLUETOOTH; case NodePrefs::HPB_ADVERT: return ADVERT; @@ -667,7 +655,6 @@ class HomeScreen : public UIScreen { } bool isPageVisible(int page) const { - if (page == RECENT) return false; // Recent adverts folded into Nearby Nodes; page retired int bit = pageBit(page); if (bit < 0) return true; uint16_t mask = (_node_prefs && _node_prefs->home_pages_mask) ? _node_prefs->home_pages_mask : NodePrefs::HP_ALL; @@ -711,13 +698,11 @@ class HomeScreen : public UIScreen { } // reserve_left: how much width from x=0 must stay clear of status icons -- - // the node name on every other page (name_min below), or the LOCK page's - // own clock (see the LOCK branch in render(), which passes its actual - // footprint here so the icon row sheds low-priority icons instead of - // drawing over the clock). -1 = use the normal name reserve. + // the time / node name on the pages (name_min below); the lock page passes + // 0, its clock sits under the bar. -1 = use the normal name reserve. int renderBatteryIndicator(DisplayDriver& display, uint16_t batteryMilliVolts, int reserve_left = -1) { int low_mv = _node_prefs ? (int)_node_prefs->low_batt_mv : 0; - int pct = battMvToPercent((int)batteryMilliVolts, low_mv); + int pct = battery::percent((int)batteryMilliVolts, low_mv); uint8_t mode = battery::mode(_node_prefs ? _node_prefs->batt_display_mode : 0); @@ -760,10 +745,9 @@ class HomeScreen : public UIScreen { } // Secondary status icons, laid out right→left in PRIORITY order so a crowded - // bar sheds its least-important cues instead of crushing the node name. Once - // an icon won't fit above the reserved name area, every lower-priority icon - // after it is dropped too (the list is ordered high→low). A blinking icon - // still reserves its slot while off, so the name width doesn't flicker. + // bar sheds its least-important cues instead of crushing the time. Once an + // icon won't fit above the reserved area, every lower-priority icon after + // it is dropped too (the list is ordered high→low). // // Priority: BT > GPS fix > alarm > mute > auto-advert > trail > live-share > // repeater. Battery (drawn above) is always rightmost. The background modes @@ -1260,7 +1244,7 @@ public: #endif const int mv = _task->getBattMilliVolts(); - const int pct = battMvToPercent(mv, _node_prefs ? (int)_node_prefs->low_batt_mv : 0); + const int pct = battery::percent(mv, _node_prefs ? (int)_node_prefs->low_batt_mv : 0); snprintf(h, sizeof(h), "%d%%", pct); snprintf(t, sizeof(t), "%d.%02d V%s", mv / 1000, (mv % 1000) / 10, board.isExternalPowered() ? " USB" : ""); tile(0, 1, nullptr, pct / 100.0f, h, t); @@ -1279,7 +1263,6 @@ public: switch (page) { case CLOCK: return &ICON_PG_CLOCK; case FAVOURITES: return &ICON_PG_STAR; - case RECENT: return &ICON_PG_RECENT; case STATUS: return &ICON_CHART; case BLUETOOTH: return &ICON_PG_BT; case ADVERT: return &ICON_PG_ADVERT; @@ -1611,16 +1594,24 @@ public: const int d = c == KEY_RIGHT ? 1 : -1; if (act == QA_BRIGHTNESS) { uint8_t& b = _node_prefs->display_brightness; - b = (uint8_t)constrain((int)b + d, 0, 4); - _task->applyDisplayPrefs(); + const uint8_t nb = (uint8_t)constrain((int)b + d, 0, 4); + if (nb != b) { b = nb; _quick_dirty = true; _task->applyDisplayPrefs(); } } else if (act == QA_AUTO_ADVERT) { const int i = constrain(AutoAdvertScreen::indexOf(_node_prefs->advert_auto_interval_sec) + d, 0, AutoAdvertScreen::OPT_COUNT - 1); - _node_prefs->advert_auto_interval_sec = AutoAdvertScreen::OPTS[i]; + if (_node_prefs->advert_auto_interval_sec != AutoAdvertScreen::OPTS[i]) { + _node_prefs->advert_auto_interval_sec = AutoAdvertScreen::OPTS[i]; + _quick_dirty = true; + } } return true; } - if (c == KEY_ENTER || c == KEY_CANCEL) { _quick_edit = false; the_mesh.savePrefs(); return true; } + if (c == KEY_ENTER || c == KEY_CANCEL) { + _quick_edit = false; + _task->savePrefsIfDirty(_quick_dirty); + _quick_dirty = false; + return true; + } return true; // nothing else while editing } if (isQuickPage(_page)) { diff --git a/examples/companion_radio/ui-new/UITask.h b/examples/companion_radio/ui-new/UITask.h index 7ad6f417..935ea4da 100644 --- a/examples/companion_radio/ui-new/UITask.h +++ b/examples/companion_radio/ui-new/UITask.h @@ -418,7 +418,6 @@ public: int findFavouriteChannelSlot(uint8_t ch_idx) const { return favslots::findChannel(_node_prefs, ch_idx); } bool isFavouriteSlotEmpty(int slot) const { return favslots::isEmpty(_node_prefs, slot); } void setFavouriteSlot(int slot, const uint8_t* pub_key) { favslots::setContact(_node_prefs, slot, pub_key); } - void setFavouriteChannelSlot(int slot, uint8_t ch_idx) { favslots::setChannel(_node_prefs, slot, ch_idx); } void clearFavouriteSlot(int slot) { favslots::clear(_node_prefs, slot); } bool isButtonPressed() const; diff --git a/examples/companion_radio/ui-new/icons.h b/examples/companion_radio/ui-new/icons.h index 763b7adb..5192ce20 100644 --- a/examples/companion_radio/ui-new/icons.h +++ b/examples/companion_radio/ui-new/icons.h @@ -93,16 +93,6 @@ inline void drawSlotIcon(DisplayDriver& d, int x, int box_w, int box_h, const Mi miniIconDrawTop(d, ix, iy, ic); } -// Inverted status indicator: fill the box [x, 0, box_w, box_h] LIGHT, draw the -// glyph DARK and centred, then restore ink to LIGHT. -inline void drawBoxedIcon(DisplayDriver& d, int x, int box_w, int box_h, const MiniIcon& ic) { - d.setColor(DisplayDriver::LIGHT); - d.fillRect(x, 0, box_w, box_h); - d.setColor(DisplayDriver::DARK); - drawSlotIcon(d, x, box_w, box_h, ic); - d.setColor(DisplayDriver::LIGHT); -} - // Horizontal row of `count` square dots (scaled, vertically centred). Used by // the "awaiting ACK" marker, where the dot count = number of send attempts. inline void miniIconDotRow(DisplayDriver& d, int x, int top_y, int count) { @@ -275,40 +265,14 @@ MINI_ICON(ICON_GPS_SEARCH, 5, // the reticle broken, no dot: GPS on, still sea packRow("#...#"), packRow(".#.#.")); -// Tools-menu glyphs (auto-reply bot, ringtone editor, diagnostics, system). -MINI_ICON(ICON_BOT, 5, // robot head: antenna + eyes + grille (auto-reply bot) - packRow("..#.."), - packRow("#####"), - packRow("#.#.#"), - packRow("#####"), - packRow("#.#.#"), - packRow("#####")); -MINI_ICON(ICON_NOTE, 5, // ♪ quaver — ringtone editor - packRow("...##"), - packRow("...##"), - packRow("...#."), - packRow("...#."), - packRow("...#."), - packRow("####."), - packRow("####.")); -MINI_ICON(ICON_CHART, 5, // ascending bars — diagnostics / stats +// The Status page's glyph. +MINI_ICON(ICON_CHART, 5, // ascending bars — Home › Status packRow("....#"), packRow("..#.#"), packRow("..#.#"), packRow("#.#.#"), packRow("#.#.#"), packRow("#####")); -MINI_ICON(ICON_KEY, 5, // padlock — remote admin (privileged/password-gated access) - packRow(".###."), - packRow("#...#"), - packRow("#...#"), - packRow("#####"), - packRow("##.##"), - packRow("#####")); -MINI_ICON(ICON_PINS, 5, // 3-pin header — GPIO - packRow("#.#.#"), - packRow("#.#.#"), - packRow("#####")); // Home-carousel page glyphs — a uniform 5x5 set, deliberately smaller than the // menu/status icons above, used in place of the page-indicator dots. One per @@ -325,12 +289,6 @@ MINI_ICON(ICON_PG_STAR, 5, // favourites packRow(".###."), packRow("##.##"), packRow("#...#")); -MINI_ICON(ICON_PG_RECENT, 5, // stacked lines — recent list - packRow("#####"), - packRow("....."), - packRow("#####"), - packRow("....."), - packRow("#####")); MINI_ICON(ICON_PG_RADIO, 5, // antenna tower — radio params packRow("..#.."), packRow(".###."), @@ -421,12 +379,6 @@ MINI_ICON(ICON_MAP_NORTH, 5, // "N" with a peaked roof — compass north ma packRow("#.#.#"), packRow("#..##"), packRow("#...#")); -MINI_ICON(ICON_MAP_ARROW, 5, // → distance-to-nearest-tracked-contact indicator - packRow("..#.."), - packRow("...#."), - packRow("#####"), - packRow("...#."), - packRow("..#..")); MINI_ICON(ICON_MAP_TARGET, 5, // ⚑ flag on a pole — the active Locator/Nav target packRow("####."), packRow("#..#."), @@ -469,15 +421,6 @@ MINI_ICON(ICON_SPACE_R, 8, packRow(".......#"), packRow("########")); -// Scroll-indicator caps — small up/down triangles (authored on the 1× grid). -MINI_ICON(ICON_SCROLL_UP, 5, // ▲ - packRow("..#.."), - packRow(".###."), - packRow("#####")); -MINI_ICON(ICON_SCROLL_DOWN, 5, // ▼ - packRow("#####"), - packRow(".###."), - packRow("..#..")); // Width of the right-edge column drawScrollIndicator occupies, or 0 when the // list fits and no indicator is drawn. Subtract from a row's content width so diff --git a/src/helpers/ui/SH1106Display.h b/src/helpers/ui/SH1106Display.h index 7ccd0654..ee654dc5 100644 --- a/src/helpers/ui/SH1106Display.h +++ b/src/helpers/ui/SH1106Display.h @@ -33,13 +33,11 @@ class SH1106Display : public DisplayDriver uint8_t _precharge; bool _single_font = true; // OLED is single-font (misc-fixed 6x9); the Lemon/default switch is retired here int _text_sz; - // Frame-skip: endFrame() hashes the GFX buffer (FNV-1a, no external dep — the - // CRC32 lib is only wired into e-ink builds) and skips the I²C flush when it's - // byte-identical to the last one pushed. _force_redraw guarantees the first - // frame and the frame after turnOn()/clear() always flush. - // Each frame is hashed per 32-column block of each 8-px page (FNV-1a), and - // only blocks whose hash changed are sent: a clock tick or a moved selection - // is a few blocks, not the whole screen. No frame copy needed. + // Frame-skip: endFrame() hashes each 32-column block of each 8-px page + // (FNV-1a, no external dep) and sends only the blocks whose hash changed: a + // clock tick or a moved selection is a few blocks, not the whole screen, and + // an unchanged frame sends nothing. No frame copy needed. _force_redraw + // guarantees the first frame and the frame after turnOn()/clear() go out whole. static const uint8_t BLOCK_W = 32; static const uint8_t BLOCKS = (128 / BLOCK_W) * (64 / 8); uint32_t _block_hash[BLOCKS] = {};