From ed984e437bdc4a085e3155490d0a85f1305434ae Mon Sep 17 00:00:00 2001 From: Jakub <106778416+MarekZegare4@users.noreply.github.com> Date: Wed, 3 Jun 2026 14:00:16 +0200 Subject: [PATCH] feat(nav): node detail Options menu (Navigate/Ping); ping rows no longer blank Two fixes from on-device testing: - Node navigation was bound to a bare Enter in the detail view, which was undiscoverable. Hold Enter now opens an Options popup (Navigate / Ping), consistent with the rest of the UI. Navigate opens the shared NavView; Ping runs the ping and shows its result. - The ping popup always carried three result rows (RTT / SNR out / SNR back) even before a ping, so the user could scroll onto blank rows. The menu is now rebuilt to contain only "Send" plus the result lines that are actually populated, growing in place as the reply arrives. Co-Authored-By: Claude Opus 4.8 --- .../companion_radio/ui-new/NearbyScreen.h | 63 ++++++++++++++----- 1 file changed, 49 insertions(+), 14 deletions(-) diff --git a/examples/companion_radio/ui-new/NearbyScreen.h b/examples/companion_radio/ui-new/NearbyScreen.h index 897406fe..a236b63b 100644 --- a/examples/companion_radio/ui-new/NearbyScreen.h +++ b/examples/companion_radio/ui-new/NearbyScreen.h @@ -41,6 +41,7 @@ class NearbyScreen : public UIScreen { static const unsigned long DETAIL_REFRESH_MS = 10000UL; PopupMenu _ctx_menu; + PopupMenu _opts; // detail-view Options: Navigate / Ping PopupMenu _ping_menu; char _ping_time_str[24]; // For storing ping RTT result char _ping_snr_out_str[24]; // For storing ping SNR out result @@ -179,14 +180,24 @@ class NearbyScreen : public UIScreen { _ping_snr_back_str[0] = '\0'; } - void openPingMenu() { + // Number of rows the ping menu should currently show: "Send" plus only the + // result lines that are populated (avoids blank scrollable rows). + int pingRowCount() const { + return 1 + (_ping_time_str[0] ? 1 : 0) + + (_ping_snr_out_str[0] ? 1 : 0) + + (_ping_snr_back_str[0] ? 1 : 0); + } + + void rebuildPingMenu() { _ping_menu.begin("Ping", 4); _ping_menu.addItem("Send"); - _ping_menu.addItem(_ping_time_str); - _ping_menu.addItem(_ping_snr_out_str); - _ping_menu.addItem(_ping_snr_back_str); + if (_ping_time_str[0]) _ping_menu.addItem(_ping_time_str); + if (_ping_snr_out_str[0]) _ping_menu.addItem(_ping_snr_out_str); + if (_ping_snr_back_str[0]) _ping_menu.addItem(_ping_snr_back_str); } + void openPingMenu() { rebuildPingMenu(); } + bool renderPingMenuIfActive(DisplayDriver& display) { if (!_ping_menu.active) return false; _ping_menu.render(display); @@ -217,6 +228,7 @@ class NearbyScreen : public UIScreen { int16_t snr_out = 0, snr_back = 0; uint32_t rtt = 0; _task->getPingResult(snr_out, snr_back, rtt); + int before = pingRowCount(); if (_pinging && (snr_out != 0 || snr_back != 0 || rtt != 0)) { if (rtt > 0 && rtt < 10000) { @@ -238,6 +250,9 @@ class NearbyScreen : public UIScreen { _pinging = false; if (_task) _task->clearPing(); } + // Grow the menu in place as result lines populate, so they never show as + // blank rows beforehand. + if (pingRowCount() != before) rebuildPingMenu(); } bool handlePingMenuInput(char c, const uint8_t* pub_key, bool allow_enter_to_open = false) { @@ -379,6 +394,7 @@ class NearbyScreen : public UIScreen { display.drawTextEllipsized(2, hdr + step * 4, display.width() - 4, buf); updatePingMenuState(); + if (_opts.active) { _opts.render(display); return true; } if (renderPingMenuIfActive(display)) return true; return true; } @@ -544,6 +560,7 @@ public: _ddetail = false; _dsel = 0; _ctx_menu.active = false; + _opts.active = false; _ping_menu.active = false; _pinging = false; _ping_time_str[0] = '\0'; @@ -653,20 +670,38 @@ public: // ── detail view ───────────────────────────────────────────────────────── if (_detail) { - if (c == KEY_CANCEL) { - _detail = false; - closePingMenu(); + // Options popup (Hold Enter) — Navigate / Ping. + if (_opts.active) { + auto res = _opts.handleInput(c); + if (res == PopupMenu::SELECTED) { + if (_opts.selectedIndex() == 0) { // Navigate + if (_sel < _count) { + const Entry& e = _entries[_sel]; + if (e.lat_e6 != 0 || e.lon_e6 != 0) _nav = true; + else _task->showAlert("No node GPS", 1000); + } + } else { // Ping + uint8_t pk[PUB_KEY_SIZE]; + openPingMenu(); + if (selectedStoredPubKey(pk)) startPingForKey(pk); + } + } return true; } - uint8_t pub_key[PUB_KEY_SIZE]; - if (selectedStoredPubKey(pub_key) && handlePingMenuInput(c, pub_key)) return true; + // Ping popup (opened from Options) consumes input while active. + if (_ping_menu.active) { + uint8_t pk[PUB_KEY_SIZE]; selectedStoredPubKey(pk); + handlePingMenuInput(c, pk); + return true; + } - // Enter (ping menu closed) → navigate to this node's last-known position. - if (c == KEY_ENTER && _sel < _count) { - const Entry& e = _entries[_sel]; - if (e.lat_e6 != 0 || e.lon_e6 != 0) _nav = true; - else _task->showAlert("No node GPS", 1000); + if (c == KEY_CANCEL) { _detail = false; closePingMenu(); return true; } + if (c == KEY_CONTEXT_MENU) { + _opts.begin("Options", 2); + _opts.addItem("Navigate"); + _opts.addItem("Ping"); + return true; } return true; }