From cda046b95cfbae9936390c18e7728c2a1025852d Mon Sep 17 00:00:00 2001 From: kallanreed <3761006+kallanreed@users.noreply.github.com> Date: Thu, 30 Oct 2025 08:23:49 -0700 Subject: [PATCH 1/8] Clean up UI button handling --- examples/companion_radio/ui-new/UITask.cpp | 81 ++++++++++++---------- examples/companion_radio/ui-new/UITask.h | 8 ++- 2 files changed, 49 insertions(+), 40 deletions(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 7c75a0892..0978d9028 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -4,7 +4,7 @@ #include "target.h" #ifndef AUTO_OFF_MILLIS - #define AUTO_OFF_MILLIS 15000 // 15 seconds + #define AUTO_OFF_MILLIS 15000 // 15 seconds #endif #define BOOT_SCREEN_MILLIS 3000 // 3 seconds @@ -663,52 +663,51 @@ bool UITask::isButtonPressed() const { } void UITask::loop() { - char c = 0; #if UI_HAS_JOYSTICK int ev = user_btn.check(); if (ev == BUTTON_EVENT_CLICK) { - c = checkDisplayOn(KEY_ENTER); + handleSingleClick(KEY_ENTER); } else if (ev == BUTTON_EVENT_LONG_PRESS) { - c = handleLongPress(KEY_ENTER); // REVISIT: could be mapped to different key code + handleLongPress(KEY_ENTER); // REVISIT: could be mapped to different key code } ev = joystick_left.check(); if (ev == BUTTON_EVENT_CLICK) { - c = checkDisplayOn(KEY_LEFT); + handleSingleClick(KEY_LEFT); } else if (ev == BUTTON_EVENT_LONG_PRESS) { - c = handleLongPress(KEY_LEFT); + handleLongPress(KEY_LEFT); } ev = joystick_right.check(); if (ev == BUTTON_EVENT_CLICK) { - c = checkDisplayOn(KEY_RIGHT); + handleSingleClick(KEY_RIGHT); } else if (ev == BUTTON_EVENT_LONG_PRESS) { - c = handleLongPress(KEY_RIGHT); + handleLongPress(KEY_RIGHT); } ev = back_btn.check(); if (ev == BUTTON_EVENT_TRIPLE_CLICK) { - c = handleTripleClick(KEY_SELECT); + handleTripleClick(KEY_SELECT); } #elif defined(PIN_USER_BTN) int ev = user_btn.check(); if (ev == BUTTON_EVENT_CLICK) { - c = checkDisplayOn(KEY_NEXT); + handleSingleClick(KEY_NEXT); } else if (ev == BUTTON_EVENT_LONG_PRESS) { - c = handleLongPress(KEY_ENTER); + handleLongPress(KEY_ENTER); } else if (ev == BUTTON_EVENT_DOUBLE_CLICK) { - c = handleDoubleClick(KEY_PREV); + handleDoubleClick(KEY_PREV); } else if (ev == BUTTON_EVENT_TRIPLE_CLICK) { - c = handleTripleClick(KEY_SELECT); + handleTripleClick(KEY_SELECT); } #endif #if defined(PIN_USER_BTN_ANA) ev = analog_btn.check(); if (ev == BUTTON_EVENT_CLICK) { - c = checkDisplayOn(KEY_NEXT); + handleSingleClick(KEY_NEXT); } else if (ev == BUTTON_EVENT_LONG_PRESS) { - c = handleLongPress(KEY_ENTER); + handleLongPress(KEY_ENTER); } else if (ev == BUTTON_EVENT_DOUBLE_CLICK) { - c = handleDoubleClick(KEY_PREV); + handleDoubleClick(KEY_PREV); } else if (ev == BUTTON_EVENT_TRIPLE_CLICK) { - c = handleTripleClick(KEY_SELECT); + handleTripleClick(KEY_SELECT); } #endif #if defined(DISP_BACKLIGHT) && defined(BACKLIGHT_BTN) @@ -719,12 +718,6 @@ void UITask::loop() { } #endif - if (c != 0 && curr) { - curr->handleInput(c); - _auto_off = millis() + AUTO_OFF_MILLIS; // extend auto-off timer - _next_refresh = 100; // trigger refresh - } - userLedHandler(); #ifdef PIN_BUZZER @@ -792,35 +785,49 @@ void UITask::loop() { char UITask::checkDisplayOn(char c) { if (_display != NULL) { if (!_display->isOn()) { - _display->turnOn(); // turn display on and consume event + _display->turnOn(); // turn display on and consume event c = 0; } - _auto_off = millis() + AUTO_OFF_MILLIS; // extend auto-off timer + _auto_off = millis() + AUTO_OFF_MILLIS; // extend auto-off timer _next_refresh = 0; // trigger refresh } return c; } -char UITask::handleLongPress(char c) { - if (millis() - ui_started_at < 8000) { // long press in first 8 seconds since startup -> CLI/rescue +void UITask::handleLongPress(char c) { + if (millis() - ui_started_at < 8000) { // long press in first 8 seconds since startup -> CLI/rescue the_mesh.enterCLIRescue(); - c = 0; // consume event + } else { + MESH_DEBUG_PRINTLN("UITask: long press triggered"); + uiHandleKey(c); } - return c; } -char UITask::handleDoubleClick(char c) { +void UITask::handleSingleClick(char c) { + MESH_DEBUG_PRINTLN("UITask: single click triggered"); + uiHandleKey(c); +} + +void UITask::handleDoubleClick(char c) { MESH_DEBUG_PRINTLN("UITask: double click triggered"); - checkDisplayOn(c); - return c; + uiHandleKey(c); } -char UITask::handleTripleClick(char c) { +void UITask::handleTripleClick(char c) { MESH_DEBUG_PRINTLN("UITask: triple click triggered"); - checkDisplayOn(c); - toggleBuzzer(); - c = 0; - return c; + if (!uiHandleKey(c)) toggleBuzzer(); +} + +bool UITask::uiHandleKey(char c) { + bool handled = false; + c = checkDisplayOn(c); + + if (c != 0 && curr) { + handled = curr->handleInput(c); + _auto_off = millis() + AUTO_OFF_MILLIS; // extend auto-off timer + _next_refresh = 100; // trigger refresh + } + return handled; } bool UITask::getGPSState() { diff --git a/examples/companion_radio/ui-new/UITask.h b/examples/companion_radio/ui-new/UITask.h index c24d33a48..64688e3f8 100644 --- a/examples/companion_radio/ui-new/UITask.h +++ b/examples/companion_radio/ui-new/UITask.h @@ -49,9 +49,11 @@ class UITask : public AbstractUITask { // Button action handlers char checkDisplayOn(char c); - char handleLongPress(char c); - char handleDoubleClick(char c); - char handleTripleClick(char c); + void handleLongPress(char c); + void handleSingleClick(char c); + void handleDoubleClick(char c); + void handleTripleClick(char c); + bool uiHandleKey(char c); void setCurrScreen(UIScreen* c); From 4cc2e74237fadf0940d88b06eb5b878eb4cab722 Mon Sep 17 00:00:00 2001 From: kallanreed <3761006+kallanreed@users.noreply.github.com> Date: Thu, 30 Oct 2025 09:12:59 -0700 Subject: [PATCH 2/8] Simplify checkDispOn --- examples/companion_radio/ui-new/UITask.cpp | 16 ++++++++++------ examples/companion_radio/ui-new/UITask.h | 2 +- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 0978d9028..d2e0636d2 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -782,16 +782,20 @@ void UITask::loop() { #endif } -char UITask::checkDisplayOn(char c) { +bool UITask::checkDisplayOn() { + // ensures that the display is on and the timer is reset + // returns false if the display was previously off + bool display_on = false; if (_display != NULL) { if (!_display->isOn()) { - _display->turnOn(); // turn display on and consume event - c = 0; + _display->turnOn(); // turn display on + } else { + display_on = true; } _auto_off = millis() + AUTO_OFF_MILLIS; // extend auto-off timer _next_refresh = 0; // trigger refresh } - return c; + return display_on; } void UITask::handleLongPress(char c) { @@ -820,9 +824,9 @@ void UITask::handleTripleClick(char c) { bool UITask::uiHandleKey(char c) { bool handled = false; - c = checkDisplayOn(c); + bool display_on = checkDisplayOn(); - if (c != 0 && curr) { + if (c != 0 && display_on && curr) { handled = curr->handleInput(c); _auto_off = millis() + AUTO_OFF_MILLIS; // extend auto-off timer _next_refresh = 100; // trigger refresh diff --git a/examples/companion_radio/ui-new/UITask.h b/examples/companion_radio/ui-new/UITask.h index 64688e3f8..e6287de26 100644 --- a/examples/companion_radio/ui-new/UITask.h +++ b/examples/companion_radio/ui-new/UITask.h @@ -48,7 +48,7 @@ class UITask : public AbstractUITask { void userLedHandler(); // Button action handlers - char checkDisplayOn(char c); + bool checkDisplayOn(); void handleLongPress(char c); void handleSingleClick(char c); void handleDoubleClick(char c); From b9d17817923383492c7b49a956626bb80142c0b9 Mon Sep 17 00:00:00 2001 From: kallanreed <3761006+kallanreed@users.noreply.github.com> Date: Thu, 30 Oct 2025 10:43:35 -0700 Subject: [PATCH 3/8] undo unnecessary WS change --- examples/companion_radio/ui-new/UITask.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index d2e0636d2..5e9e60875 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -4,7 +4,7 @@ #include "target.h" #ifndef AUTO_OFF_MILLIS - #define AUTO_OFF_MILLIS 15000 // 15 seconds + #define AUTO_OFF_MILLIS 15000 // 15 seconds #endif #define BOOT_SCREEN_MILLIS 3000 // 3 seconds From a579a0814ca20e06681703e34c4e883c26e61567 Mon Sep 17 00:00:00 2001 From: kallanreed <3761006+kallanreed@users.noreply.github.com> Date: Thu, 30 Oct 2025 22:35:31 -0700 Subject: [PATCH 4/8] Implement Quick Message UI --- examples/companion_radio/ui-new/QuickMsg.cpp | 127 +++++++++++++++++++ examples/companion_radio/ui-new/QuickMsg.h | 37 ++++++ examples/companion_radio/ui-new/UITask.cpp | 32 ++++- examples/companion_radio/ui-new/UITask.h | 13 +- variants/heltec_t114/platformio.ini | 1 + 5 files changed, 202 insertions(+), 8 deletions(-) create mode 100644 examples/companion_radio/ui-new/QuickMsg.cpp create mode 100644 examples/companion_radio/ui-new/QuickMsg.h diff --git a/examples/companion_radio/ui-new/QuickMsg.cpp b/examples/companion_radio/ui-new/QuickMsg.cpp new file mode 100644 index 000000000..bdb7dfd3b --- /dev/null +++ b/examples/companion_radio/ui-new/QuickMsg.cpp @@ -0,0 +1,127 @@ +#include "QuickMsg.h" + +#if UI_QUICK_MSG +#include "../MyMesh.h" + +#ifndef COUNTOF + #define COUNTOF(x) sizeof(x) / sizeof(x[0]) +#endif + +static constexpr const char* messages[] { + "ping", "ack", "yes", "no", "test" +}; +static constexpr size_t messages_count = COUNTOF(messages); + +QuickMsgScreen::QuickMsgScreen(UITask* task) + : _task(task) { +} + +int QuickMsgScreen::render(DisplayDriver& display) { + display.setColor(DisplayDriver::YELLOW); + display.setTextSize(2); + display.drawTextCentered(display.width() / 2, 2, "quick messages"); + + display.setColor(DisplayDriver::GREEN); + display.setTextSize(1); + display.setCursor(2, _row_defs[0]); + display.print("message:"); + display.setCursor(42, _row_defs[0]); + display.print(getMessageText()); + + display.setCursor(2, _row_defs[1]); + display.print("channel:"); + display.setCursor(42, _row_defs[1]); + display.print(getChannelName()); + + display.drawTextCentered(display.width() / 2, _row_defs[2], "[send]"); + + auto cursor_row = _row_defs[_row]; + display.drawRect(0, cursor_row - 1, display.width(), 12); + return 1000; +} + +bool QuickMsgScreen::handleInput(char c) { + switch (c) { + case KEY_ENTER: + _task->gotoHomeScreen(); + return true; + + // Move cursor + case KEY_PREV: + case KEY_LEFT: + _row = (_row + 1) % Row::Count; + return true; + + // Update value/Use button + case KEY_NEXT: + case KEY_RIGHT: + if (_row == Row::MSG) + nextMessage(); + else if (_row == Row::CHANNEL) + nextChannel(); + else if (_row == Row::SEND) + sendMessage(); + else + MESH_DEBUG_PRINTLN("Bad row index"); + return true; + + default: + _task->showAlert(PRESS_LABEL " to exit", 1000); + break; + } + + return false; +} + +void QuickMsgScreen::nextMessage() { + _msg_ix = (_msg_ix + 1) % messages_count; +} + +void QuickMsgScreen::nextChannel() { +#ifndef MAX_GROUP_CHANNELS + _channel_ix = 0; + return; +#else + ChannelDetails details; + _channel_ix = _channel_ix + 1 % MAX_GROUP_CHANNELS; + + the_mesh.getChannel(_channel_ix, details); + + // Channel slots are static, only cycle through valid entries. + if (strlen(details.name) == 0) + _channel_ix = 0; + else + strcpy(_channel_name, details.name); +#endif +} + +void QuickMsgScreen::sendMessage() { + ChannelDetails details; + bool sent = false; + + if (the_mesh.getChannel(_channel_ix, details)) { + auto now = the_mesh.getRTCClock()->getCurrentTime(); + auto name = the_mesh.getNodeName(); + auto text = getMessageText(); + auto len = strlen(text); + + if (the_mesh.sendGroupMessage(now, details.channel, name, text, len)) + sent = true; + } + + if (sent) + _task->showAlert("Message sent!", 1000); + else + _task->showAlert("Message failed.", 1000); +} + +const char* QuickMsgScreen::getMessageText() { + // TODO: special messages like GPS position and node name. + return messages[_msg_ix]; +} + +const char* QuickMsgScreen::getChannelName() { + return _channel_ix == 0 ? "public" : _channel_name; +} + +#endif \ No newline at end of file diff --git a/examples/companion_radio/ui-new/QuickMsg.h b/examples/companion_radio/ui-new/QuickMsg.h new file mode 100644 index 000000000..626974f04 --- /dev/null +++ b/examples/companion_radio/ui-new/QuickMsg.h @@ -0,0 +1,37 @@ +#pragma once + +#include "UITask.h" + +#if UI_QUICK_MSG +#include +#include + +class QuickMsgScreen : public UIScreen { + enum Row { + MSG, + CHANNEL, + SEND, + Count + }; + + UITask* _task; + uint8_t _msg_ix = 0; + uint8_t _channel_ix = 0; + uint8_t _row = 0; + uint8_t _row_defs[Row::Count] { 20, 35, 50 }; + char _channel_name[32]; // see ChannelDetails.h + + void nextMessage(); + void nextChannel(); + void sendMessage(); + + const char* getMessageText(); + const char* getChannelName(); + +public: + QuickMsgScreen(UITask* task); + int render(DisplayDriver& display) override; + bool handleInput(char c) override; +}; + +#endif \ No newline at end of file diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 5e9e60875..f2f1c3889 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -3,6 +3,10 @@ #include "../MyMesh.h" #include "target.h" +#if UI_QUICK_MSG +#include "QuickMsg.h" +#endif + #ifndef AUTO_OFF_MILLIS #define AUTO_OFF_MILLIS 15000 // 15 seconds #endif @@ -20,12 +24,6 @@ #define UI_RECENT_LIST_SIZE 4 #endif -#if UI_HAS_JOYSTICK - #define PRESS_LABEL "press Enter" -#else - #define PRESS_LABEL "long press" -#endif - #include "icons.h" class SplashScreen : public UIScreen { @@ -84,6 +82,9 @@ class HomeScreen : public UIScreen { #endif #if UI_SENSORS_PAGE == 1 SENSORS, +#endif +#if UI_QUICK_MSG + QUICK_MSG, #endif SHUTDOWN, Count // keep as last @@ -356,6 +357,14 @@ class HomeScreen : public UIScreen { } if (sensors_scroll) sensors_scroll_offset = (sensors_scroll_offset+1)%sensors_nb; else sensors_scroll_offset = 0; +#endif +#if UI_QUICK_MSG + } else if (_page == HomePage::QUICK_MSG) { + display.setColor(DisplayDriver::YELLOW); + display.setTextSize(2); + display.drawTextCentered(display.width() / 2, 24, "quick messages"); + display.setTextSize(1); + display.drawTextCentered(display.width() / 2, 40, "enter/exit: " PRESS_LABEL); #endif } else if (_page == HomePage::SHUTDOWN) { display.setColor(DisplayDriver::GREEN); @@ -364,7 +373,7 @@ class HomeScreen : public UIScreen { display.drawTextCentered(display.width() / 2, 34, "hibernating..."); } else { display.drawXbm((display.width() - 32) / 2, 18, power_icon, 32, 32); - display.drawTextCentered(display.width() / 2, 64 - 11, "hibernate:" PRESS_LABEL); + display.drawTextCentered(display.width() / 2, 64 - 11, "hibernate: " PRESS_LABEL); } } return 5000; // next render after 5000 ms @@ -411,6 +420,12 @@ class HomeScreen : public UIScreen { next_sensors_refresh=0; return true; } +#endif +#if UI_QUICK_MSG + if (c == KEY_ENTER && _page == HomePage::QUICK_MSG) { + _task->gotoQuickMsgScreen(); + return true; + } #endif if (c == KEY_ENTER && _page == HomePage::SHUTDOWN) { _shutdown_init = true; // need to wait for button to be released @@ -544,6 +559,9 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no splash = new SplashScreen(this); home = new HomeScreen(this, &rtc_clock, sensors, node_prefs); msg_preview = new MsgPreviewScreen(this, &rtc_clock); +#if UI_QUICK_MSG + quick_msg = new QuickMsgScreen(this); +#endif setCurrScreen(splash); } diff --git a/examples/companion_radio/ui-new/UITask.h b/examples/companion_radio/ui-new/UITask.h index e6287de26..0e0fd4a7d 100644 --- a/examples/companion_radio/ui-new/UITask.h +++ b/examples/companion_radio/ui-new/UITask.h @@ -15,6 +15,12 @@ #include #endif +#if UI_HAS_JOYSTICK + #define PRESS_LABEL "press Enter" +#else + #define PRESS_LABEL "long press" +#endif + #include "../AbstractUITask.h" #include "../NodePrefs.h" @@ -43,6 +49,9 @@ class UITask : public AbstractUITask { UIScreen* splash; UIScreen* home; UIScreen* msg_preview; +#if UI_QUICK_MSG + UIScreen* quick_msg; +#endif UIScreen* curr; void userLedHandler(); @@ -67,6 +76,9 @@ class UITask : public AbstractUITask { void begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* node_prefs); void gotoHomeScreen() { setCurrScreen(home); } +#if UI_QUICK_MSG + void gotoQuickMsgScreen() { setCurrScreen(quick_msg); } +#endif void showAlert(const char* text, int duration_millis); int getMsgCount() const { return _msgcount; } bool hasDisplay() const { return _display != NULL; } @@ -76,7 +88,6 @@ class UITask : public AbstractUITask { bool getGPSState(); void toggleGPS(); - // from AbstractUITask void msgRead(int msgcount) override; void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) override; diff --git a/variants/heltec_t114/platformio.ini b/variants/heltec_t114/platformio.ini index c482a30ad..850535c3e 100644 --- a/variants/heltec_t114/platformio.ini +++ b/variants/heltec_t114/platformio.ini @@ -171,6 +171,7 @@ build_flags = -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 -D ENV_INCLUDE_GPS=1 ; enable the GPS page in UI + -D UI_QUICK_MSG=1 ; enable quick messages in UI ; -D BLE_DEBUG_LOGGING=1 -D OFFLINE_QUEUE_SIZE=256 ; -D MESH_PACKET_LOGGING=1 From 59130309102d7f7cc88d1aac503f4f75eded2a8e Mon Sep 17 00:00:00 2001 From: kallanreed <3761006+kallanreed@users.noreply.github.com> Date: Fri, 31 Oct 2025 08:43:17 -0700 Subject: [PATCH 5/8] Add quick message for GPS --- examples/companion_radio/ui-new/QuickMsg.cpp | 37 +++++++++++++++++--- examples/companion_radio/ui-new/QuickMsg.h | 11 ++++++ 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/examples/companion_radio/ui-new/QuickMsg.cpp b/examples/companion_radio/ui-new/QuickMsg.cpp index bdb7dfd3b..450b6329e 100644 --- a/examples/companion_radio/ui-new/QuickMsg.cpp +++ b/examples/companion_radio/ui-new/QuickMsg.cpp @@ -8,7 +8,8 @@ #endif static constexpr const char* messages[] { - "ping", "ack", "yes", "no", "test" + "test", "ping", "hello", "ack", "yes", "no", "share location", + "come to me", "going to you", "help", "SOS" }; static constexpr size_t messages_count = COUNTOF(messages); @@ -74,7 +75,19 @@ bool QuickMsgScreen::handleInput(char c) { } void QuickMsgScreen::nextMessage() { - _msg_ix = (_msg_ix + 1) % messages_count; + auto msg_count = messages_count; + _kind = MsgKind::TEXT; + ++_msg_ix; + +#if ENV_INCLUDE_GPS + // Add a fake index for GPS at the end if enabled. + if (_msg_ix == msg_count && _task->getGPSState()) { + msg_count += 1; + _kind = MsgKind::GPS; + } +#endif + + _msg_ix = _msg_ix % msg_count; } void QuickMsgScreen::nextChannel() { @@ -116,8 +129,24 @@ void QuickMsgScreen::sendMessage() { } const char* QuickMsgScreen::getMessageText() { - // TODO: special messages like GPS position and node name. - return messages[_msg_ix]; +#if ENV_INCLUDE_GPS + if (_kind == MsgKind::GPS) { + LocationProvider* nmea = sensors.getLocationProvider(); + if (!nmea) { + sprintf(_msg_text, "GPS Error"); + } else { + if (nmea->isValid()) { + sprintf(_msg_text, "%.4f %.4f", + nmea->getLatitude()/1000000., nmea->getLongitude()/1000000.); + } else { + sprintf(_msg_text, "No GPS fix"); + } + } + return _msg_text; + } +#endif + + return _msg_ix < messages_count ? messages[_msg_ix] : "???"; } const char* QuickMsgScreen::getChannelName() { diff --git a/examples/companion_radio/ui-new/QuickMsg.h b/examples/companion_radio/ui-new/QuickMsg.h index 626974f04..7b2525572 100644 --- a/examples/companion_radio/ui-new/QuickMsg.h +++ b/examples/companion_radio/ui-new/QuickMsg.h @@ -14,12 +14,23 @@ class QuickMsgScreen : public UIScreen { Count }; + enum MsgKind { + TEXT, +#if ENV_INCLUDE_GPS + GPS, +#endif + }; + UITask* _task; uint8_t _msg_ix = 0; uint8_t _channel_ix = 0; + uint8_t _kind = MsgKind::TEXT; uint8_t _row = 0; uint8_t _row_defs[Row::Count] { 20, 35, 50 }; char _channel_name[32]; // see ChannelDetails.h +#if ENV_INCLUDE_GPS + char _msg_text[32]; // Buffer for dynamic messages. +#endif void nextMessage(); void nextChannel(); From 4a1795e405c31f957e354fdb819261d395605665 Mon Sep 17 00:00:00 2001 From: kallanreed <3761006+kallanreed@users.noreply.github.com> Date: Fri, 31 Oct 2025 09:53:34 -0700 Subject: [PATCH 6/8] Fix msg idx wrap bug when GPS enabled --- examples/companion_radio/ui-new/QuickMsg.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/examples/companion_radio/ui-new/QuickMsg.cpp b/examples/companion_radio/ui-new/QuickMsg.cpp index 450b6329e..93cf9680b 100644 --- a/examples/companion_radio/ui-new/QuickMsg.cpp +++ b/examples/companion_radio/ui-new/QuickMsg.cpp @@ -80,10 +80,13 @@ void QuickMsgScreen::nextMessage() { ++_msg_ix; #if ENV_INCLUDE_GPS - // Add a fake index for GPS at the end if enabled. - if (_msg_ix == msg_count && _task->getGPSState()) { + if (_task->getGPSState()) { + // Index at end of messages, add fake GPS entry. + if (_msg_ix == msg_count) + _kind = MsgKind::GPS; + + // Account for the fake entry. msg_count += 1; - _kind = MsgKind::GPS; } #endif From aafe075e56bcc7b66f5bf8e202481ae785625853 Mon Sep 17 00:00:00 2001 From: kallanreed <3761006+kallanreed@users.noreply.github.com> Date: Sat, 1 Nov 2025 20:14:08 -0700 Subject: [PATCH 7/8] Support going backward through values --- examples/companion_radio/ui-new/QuickMsg.cpp | 55 +++++++++++++------- examples/companion_radio/ui-new/QuickMsg.h | 4 +- 2 files changed, 38 insertions(+), 21 deletions(-) diff --git a/examples/companion_radio/ui-new/QuickMsg.cpp b/examples/companion_radio/ui-new/QuickMsg.cpp index 93cf9680b..f57269efa 100644 --- a/examples/companion_radio/ui-new/QuickMsg.cpp +++ b/examples/companion_radio/ui-new/QuickMsg.cpp @@ -53,7 +53,7 @@ bool QuickMsgScreen::handleInput(char c) { _row = (_row + 1) % Row::Count; return true; - // Update value/Use button + // Next value/Use button case KEY_NEXT: case KEY_RIGHT: if (_row == Row::MSG) @@ -65,6 +65,16 @@ bool QuickMsgScreen::handleInput(char c) { else MESH_DEBUG_PRINTLN("Bad row index"); return true; + + // Prev value + case KEY_SELECT: + if (_row == Row::MSG) + nextMessage(/*fwd=*/false); + else if (_row == Row::CHANNEL) + nextChannel(/*fwd=*/false); + else + MESH_DEBUG_PRINTLN("Bad row index"); + return true; default: _task->showAlert(PRESS_LABEL " to exit", 1000); @@ -74,39 +84,46 @@ bool QuickMsgScreen::handleInput(char c) { return false; } -void QuickMsgScreen::nextMessage() { +void QuickMsgScreen::nextMessage(bool fwd) { auto msg_count = messages_count; - _kind = MsgKind::TEXT; - ++_msg_ix; #if ENV_INCLUDE_GPS - if (_task->getGPSState()) { - // Index at end of messages, add fake GPS entry. - if (_msg_ix == msg_count) - _kind = MsgKind::GPS; - - // Account for the fake entry. + // Make fake index for GPS if enabled. + if (_task->getGPSState()) msg_count += 1; - } #endif - _msg_ix = _msg_ix % msg_count; + _kind = MsgKind::TEXT; + _msg_ix = (_msg_ix + (fwd ? 1 : msg_count - 1)) % msg_count; + +#if ENV_INCLUDE_GPS + // Index at end of messages, add fake GPS entry. + if (_msg_ix == messages_count) + _kind = MsgKind::GPS; +#endif } -void QuickMsgScreen::nextChannel() { +void QuickMsgScreen::nextChannel(bool fwd) { #ifndef MAX_GROUP_CHANNELS _channel_ix = 0; return; #else ChannelDetails details; - _channel_ix = _channel_ix + 1 % MAX_GROUP_CHANNELS; - + _channel_ix = (_channel_ix + (fwd ? 1 : MAX_GROUP_CHANNELS - 1)) % MAX_GROUP_CHANNELS; the_mesh.getChannel(_channel_ix, details); - // Channel slots are static, only cycle through valid entries. - if (strlen(details.name) == 0) - _channel_ix = 0; - else + if (fwd) { + // Moving forward. Reset to 0 on first invalid channel. + if (details.name[0] == 0) + _channel_ix = 0; + } else { + // Moving backward. Find a valid channel or 0. + while (_channel_ix > 0 && details.name[0] == 0) + the_mesh.getChannel(--_channel_ix, details); + } + + // Copy valid names to the UI buffer. + if (details.name[0] != 0) strcpy(_channel_name, details.name); #endif } diff --git a/examples/companion_radio/ui-new/QuickMsg.h b/examples/companion_radio/ui-new/QuickMsg.h index 7b2525572..696cbe681 100644 --- a/examples/companion_radio/ui-new/QuickMsg.h +++ b/examples/companion_radio/ui-new/QuickMsg.h @@ -32,8 +32,8 @@ class QuickMsgScreen : public UIScreen { char _msg_text[32]; // Buffer for dynamic messages. #endif - void nextMessage(); - void nextChannel(); + void nextMessage(bool fwd = true); + void nextChannel(bool fwd = true); void sendMessage(); const char* getMessageText(); From fa3c2e742f998b3eee3a3f673e5ec63bee09cbe5 Mon Sep 17 00:00:00 2001 From: kallanreed <3761006+kallanreed@users.noreply.github.com> Date: Fri, 7 Nov 2025 08:10:38 -0800 Subject: [PATCH 8/8] Use triple-click for "next field" to be consistent with forward/backward with single/double. Fix channel search. --- examples/companion_radio/ui-new/QuickMsg.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/examples/companion_radio/ui-new/QuickMsg.cpp b/examples/companion_radio/ui-new/QuickMsg.cpp index f57269efa..b1e406212 100644 --- a/examples/companion_radio/ui-new/QuickMsg.cpp +++ b/examples/companion_radio/ui-new/QuickMsg.cpp @@ -48,8 +48,7 @@ bool QuickMsgScreen::handleInput(char c) { return true; // Move cursor - case KEY_PREV: - case KEY_LEFT: + case KEY_SELECT: _row = (_row + 1) % Row::Count; return true; @@ -67,7 +66,8 @@ bool QuickMsgScreen::handleInput(char c) { return true; // Prev value - case KEY_SELECT: + case KEY_PREV: + case KEY_LEFT: if (_row == Row::MSG) nextMessage(/*fwd=*/false); else if (_row == Row::CHANNEL) @@ -113,8 +113,10 @@ void QuickMsgScreen::nextChannel(bool fwd) { the_mesh.getChannel(_channel_ix, details); if (fwd) { - // Moving forward. Reset to 0 on first invalid channel. - if (details.name[0] == 0) + // Moving forward. Find next valid channel or 0. + while (_channel_ix < MAX_GROUP_CHANNELS && details.name[0] == 0) + the_mesh.getChannel(++_channel_ix, details); + if (_channel_ix >= MAX_GROUP_CHANNELS) _channel_ix = 0; } else { // Moving backward. Find a valid channel or 0.