diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index f61f53ae..3f7094fd 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -228,6 +228,7 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no file.read((uint8_t *)&_prefs.gps_enabled, sizeof(_prefs.gps_enabled)); // 85 file.read((uint8_t *)&_prefs.gps_interval, sizeof(_prefs.gps_interval)); // 86 file.read((uint8_t *)&_prefs.autoadd_config, sizeof(_prefs.autoadd_config)); // 87 + file.read((uint8_t *)&_prefs.utc_offset_hours, sizeof(_prefs.utc_offset_hours)); // 88 file.close(); } @@ -263,6 +264,7 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_ file.write((uint8_t *)&_prefs.gps_enabled, sizeof(_prefs.gps_enabled)); // 85 file.write((uint8_t *)&_prefs.gps_interval, sizeof(_prefs.gps_interval)); // 86 file.write((uint8_t *)&_prefs.autoadd_config, sizeof(_prefs.autoadd_config)); // 87 + file.write((uint8_t *)&_prefs.utc_offset_hours, sizeof(_prefs.utc_offset_hours)); // 88 file.close(); } @@ -598,4 +600,4 @@ bool DataStore::putBlobByKey(const uint8_t key[], int key_len, const uint8_t src } return false; // error } -#endif +#endif \ No newline at end of file diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index eb9c815b..7e85823d 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -523,13 +523,6 @@ void MyMesh::onCommandDataRecv(const ContactInfo &from, mesh::Packet *pkt, uint3 const char *text) { markConnectionActive(from); // in case this is from a server, and we have a connection queueMessage(from, TXT_TYPE_CLI_DATA, pkt, sender_timestamp, NULL, 0, text); - -#ifdef DISPLAY_CLASS - // Route CLI responses to admin UI if active - if (_ui && _admin_contact_idx >= 0) { - _ui->onAdminCliResponse(from.name, text); - } -#endif } void MyMesh::onSignedMessageRecv(const ContactInfo &from, mesh::Packet *pkt, uint32_t sender_timestamp, @@ -657,55 +650,6 @@ bool MyMesh::uiSendDirectMessage(uint32_t contact_idx, const char* text) { return true; } -bool MyMesh::uiLoginToRepeater(uint32_t contact_idx, const char* password) { - ContactInfo contact; - if (!getContactByIdx(contact_idx, contact)) return false; - - ContactInfo* recipient = lookupContactByPubKey(contact.id.pub_key, PUB_KEY_SIZE); - if (!recipient) return false; - - uint32_t est_timeout; - int result = sendLogin(*recipient, password, est_timeout); - - if (result == MSG_SEND_FAILED) { - MESH_DEBUG_PRINTLN("UI: Admin login send failed to %s", recipient->name); - return false; - } - - clearPendingReqs(); - memcpy(&pending_login, recipient->id.pub_key, 4); - _admin_contact_idx = contact_idx; - - MESH_DEBUG_PRINTLN("UI: Admin login sent to %s (%s), timeout=%dms", - recipient->name, result == MSG_SEND_SENT_FLOOD ? "flood" : "direct", - est_timeout); - return true; -} - -bool MyMesh::uiSendCliCommand(uint32_t contact_idx, const char* command) { - ContactInfo contact; - if (!getContactByIdx(contact_idx, contact)) return false; - - ContactInfo* recipient = lookupContactByPubKey(contact.id.pub_key, PUB_KEY_SIZE); - if (!recipient) return false; - - uint32_t timestamp = getRTCClock()->getCurrentTimeUnique(); - uint32_t est_timeout; - int result = sendCommandData(*recipient, timestamp, 0, command, est_timeout); - - if (result == MSG_SEND_FAILED) { - MESH_DEBUG_PRINTLN("UI: CLI command send failed to %s: %s", recipient->name, command); - return false; - } - - _admin_contact_idx = contact_idx; - - MESH_DEBUG_PRINTLN("UI: CLI command sent to %s (%s): %s, timeout=%dms", - recipient->name, result == MSG_SEND_SENT_FLOOD ? "flood" : "direct", - command, est_timeout); - return true; -} - uint8_t MyMesh::onContactRequest(const ContactInfo &contact, uint32_t sender_timestamp, const uint8_t *data, uint8_t len, uint8_t *reply) { if (data[0] == REQ_TYPE_GET_TELEMETRY_DATA) { @@ -764,11 +708,6 @@ void MyMesh::onContactResponse(const ContactInfo &contact, const uint8_t *data, out_frame[i++] = 0; // legacy: is_admin = false memcpy(&out_frame[i], contact.id.pub_key, 6); i += 6; // pub_key_prefix - -#ifdef DISPLAY_CLASS - // Notify UI of successful legacy login - if (_ui) _ui->onAdminLoginResult(true, 0, tag); -#endif } else if (data[4] == RESP_SERVER_LOGIN_OK) { // new login response uint16_t keep_alive_secs = ((uint16_t)data[5]) * 16; if (keep_alive_secs > 0) { @@ -782,21 +721,11 @@ void MyMesh::onContactResponse(const ContactInfo &contact, const uint8_t *data, i += 4; // NEW: include server timestamp out_frame[i++] = data[7]; // NEW (v7): ACL permissions out_frame[i++] = data[12]; // FIRMWARE_VER_LEVEL - -#ifdef DISPLAY_CLASS - // Notify UI of successful login - if (_ui) _ui->onAdminLoginResult(true, data[6], tag); -#endif } else { out_frame[i++] = PUSH_CODE_LOGIN_FAIL; out_frame[i++] = 0; // reserved memcpy(&out_frame[i], contact.id.pub_key, 6); i += 6; // pub_key_prefix - -#ifdef DISPLAY_CLASS - // Notify UI of login failure - if (_ui) _ui->onAdminLoginResult(false, 0, 0); -#endif } _serial->writeFrame(out_frame, i); } else if (len > 4 && // check for status response @@ -968,7 +897,6 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe memset(send_scope.key, 0, sizeof(send_scope.key)); memset(_sent_track, 0, sizeof(_sent_track)); _sent_track_idx = 0; - _admin_contact_idx = -1; // defaults memset(&_prefs, 0, sizeof(_prefs)); @@ -1022,6 +950,7 @@ void MyMesh::begin(bool has_display) { _prefs.buzzer_quiet = constrain(_prefs.buzzer_quiet, 0, 1); // Ensure boolean 0 or 1 _prefs.gps_enabled = constrain(_prefs.gps_enabled, 0, 1); // Ensure boolean 0 or 1 _prefs.gps_interval = constrain(_prefs.gps_interval, 0, 86400); // Max 24 hours + _prefs.utc_offset_hours = constrain(_prefs.utc_offset_hours, -12, 14); // Valid timezone range #ifdef BLE_PIN_CODE // 123456 by default if (_prefs.ble_pin == 0) { @@ -1805,6 +1734,12 @@ void MyMesh::handleCmdFrame(size_t len) { savePrefs(); } #endif + // UTC offset for local clock display (works regardless of GPS) + if (strcmp(sp, "utc_offset") == 0) { + int offset = atoi(np); + _prefs.utc_offset_hours = constrain(offset, -12, 14); + savePrefs(); + } writeOKFrame(); } else { writeErrFrame(ERR_CODE_ILLEGAL_ARG); diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index c989427e..5a76b0d6 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -12,7 +12,7 @@ #endif #ifndef FIRMWARE_VERSION -#define FIRMWARE_VERSION "Meck v0.8.1" +#define FIRMWARE_VERSION "Meck v0.8.2" #endif #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index 62cd4164..65511bde 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -28,4 +28,5 @@ struct NodePrefs { // persisted to file uint8_t gps_enabled; // GPS enabled flag (0=disabled, 1=enabled) uint32_t gps_interval; // GPS read interval in seconds uint8_t autoadd_config; // bitmask for auto-add contacts config + int8_t utc_offset_hours; // UTC offset in hours (-12 to +14), default 0 }; \ No newline at end of file diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index c1b6aa84..294d6ead 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -10,7 +10,6 @@ #include #include "TextReaderScreen.h" #include "ContactsScreen.h" - #include "RepeaterAdminScreen.h" extern SPIClass displaySpi; // From GxEPDDisplay.cpp, shared SPI bus TCA8418Keyboard keyboard(I2C_ADDR_KEYBOARD, &Wire); @@ -380,6 +379,13 @@ void setup() { MESH_DEBUG_PRINTLN("setup() - GPS enabled by default"); #endif + // T-Deck Pro: BLE starts disabled for standalone-first operation + // User can toggle it on from the Bluetooth home page (Enter or long-press) + #if defined(LilyGo_TDeck_Pro) && defined(BLE_PIN_CODE) + serial_interface.disable(); + MESH_DEBUG_PRINTLN("setup() - BLE disabled at boot (standalone mode)"); + #endif + MESH_DEBUG_PRINTLN("=== setup() - COMPLETE ==="); } @@ -631,56 +637,6 @@ void handleKeyboardInput() { return; } - // *** REPEATER ADMIN MODE *** - if (ui_task.isOnRepeaterAdmin()) { - RepeaterAdminScreen* admin = (RepeaterAdminScreen*)ui_task.getRepeaterAdminScreen(); - RepeaterAdminScreen::AdminState astate = admin->getState(); - - // In password entry, pass all printable chars and special keys through - // Q only navigates back if password is empty (handleInput returns false) - if (astate == RepeaterAdminScreen::STATE_PASSWORD_ENTRY) { - if (key == 'q' || key == 'Q') { - // Try passing to screen - if not handled (empty password), navigate back - bool handled = admin->handleInput(key); - if (!handled) { - Serial.println("Nav: Back to contacts from admin login"); - ui_task.gotoContactsScreen(); - } - ui_task.forceRefresh(); - } else { - ui_task.injectKey(key); - } - return; - } - - // In menu state - if (astate == RepeaterAdminScreen::STATE_MENU) { - if (key == 'q' || key == 'Q') { - Serial.println("Nav: Back to contacts from admin menu"); - ui_task.gotoContactsScreen(); - return; - } - // C key: allow entering compose mode from admin menu - if (key == 'c' || key == 'C') { - composeDM = false; - composeDMContactIdx = -1; - composeMode = true; - composeBuffer[0] = '\0'; - composePos = 0; - drawComposeScreen(); - lastComposeRefresh = millis(); - return; - } - // All other keys pass to admin screen - ui_task.injectKey(key); - return; - } - - // In waiting/response/error states, pass all keys through - ui_task.injectKey(key); - return; - } - // Normal mode - not composing switch (key) { case 'c': @@ -741,7 +697,7 @@ void handleKeyboardInput() { case 'w': case 'W': // Navigate up/previous (scroll on channel screen) - if (ui_task.isOnChannelScreen() || ui_task.isOnContactsScreen() || ui_task.isOnRepeaterAdmin()) { + if (ui_task.isOnChannelScreen() || ui_task.isOnContactsScreen()) { ui_task.injectKey('w'); // Pass directly for channel/contacts switching } else { Serial.println("Nav: Previous"); @@ -752,7 +708,7 @@ void handleKeyboardInput() { case 's': case 'S': // Navigate down/next (scroll on channel screen) - if (ui_task.isOnChannelScreen() || ui_task.isOnContactsScreen() || ui_task.isOnRepeaterAdmin()) { + if (ui_task.isOnChannelScreen() || ui_task.isOnContactsScreen()) { ui_task.injectKey('s'); // Pass directly for channel/contacts switching } else { Serial.println("Nav: Next"); @@ -784,7 +740,6 @@ void handleKeyboardInput() { case '\r': // Select/Enter - if on contacts screen, enter DM compose for chat contacts - // or repeater admin for repeater contacts if (ui_task.isOnContactsScreen()) { ContactsScreen* cs = (ContactsScreen*)ui_task.getContactsScreen(); int idx = cs->getSelectedContactIdx(); @@ -799,15 +754,9 @@ void handleKeyboardInput() { Serial.printf("Entering DM compose to %s (idx %d)\n", composeDMName, idx); drawComposeScreen(); lastComposeRefresh = millis(); - } else if (idx >= 0 && ctype == ADV_TYPE_REPEATER) { - // Open repeater admin screen - char rname[32]; - cs->getSelectedContactName(rname, sizeof(rname)); - Serial.printf("Opening repeater admin for %s (idx %d)\n", rname, idx); - ui_task.gotoRepeaterAdmin(idx); } else if (idx >= 0) { - // Non-chat, non-repeater contact (room, sensor, etc.) - future use - Serial.printf("Selected contact type=%d idx=%d\n", ctype, idx); + // Non-chat contact selected (repeater, room, etc.) - future use + Serial.printf("Selected non-chat contact type=%d idx=%d\n", ctype, idx); } } else { Serial.println("Nav: Enter/Select"); @@ -818,9 +767,21 @@ void handleKeyboardInput() { case 'q': case 'Q': case '\b': - // Go back to home screen (admin mode handled above) - Serial.println("Nav: Back to home"); - ui_task.gotoHomeScreen(); + // If editing UTC offset on GPS page, pass through to cancel + if (ui_task.isEditingHomeScreen()) { + ui_task.injectKey('q'); + } else { + // Go back to home screen + Serial.println("Nav: Back to home"); + ui_task.gotoHomeScreen(); + } + break; + + case 'u': + case 'U': + // UTC offset editing (on GPS home page) + Serial.println("Nav: UTC offset"); + ui_task.injectKey('u'); break; case ' ': diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index e5567e10..47d08ea0 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -2,7 +2,6 @@ #include #include "../MyMesh.h" #include "target.h" -#include "RepeaterAdminScreen.h" #ifdef WIFI_SSID #include #endif @@ -103,6 +102,8 @@ class HomeScreen : public UIScreen { NodePrefs* _node_prefs; uint8_t _page; bool _shutdown_init; + bool _editing_utc; + int8_t _saved_utc_offset; // for cancel/undo AdvertPath recent[UI_RECENT_LIST_SIZE]; @@ -184,7 +185,15 @@ void renderBatteryIndicator(DisplayDriver& display, uint16_t batteryMilliVolts) public: HomeScreen(UITask* task, mesh::RTCClock* rtc, SensorManager* sensors, NodePrefs* node_prefs) : _task(task), _rtc(rtc), _sensors(sensors), _node_prefs(node_prefs), _page(0), - _shutdown_init(false), sensors_lpp(200) { } + _shutdown_init(false), _editing_utc(false), _saved_utc_offset(0), sensors_lpp(200) { } + + bool isEditingUTC() const { return _editing_utc; } + void cancelEditUTC() { + if (_editing_utc) { + _node_prefs->utc_offset_hours = _saved_utc_offset; + _editing_utc = false; + } + } void poll() override { if (_shutdown_init && !_task->isButtonPressed()) { // must wait for USR button to be released @@ -205,6 +214,29 @@ public: // battery voltage renderBatteryIndicator(display, _task->getBattMilliVolts()); + // centered clock (tinyfont) - only show when time is valid + { + uint32_t now = _rtc->getCurrentTime(); + if (now > 1700000000) { // valid timestamp (after ~Nov 2023) + // Apply UTC offset from prefs + int32_t local = (int32_t)now + ((int32_t)_node_prefs->utc_offset_hours * 3600); + int hrs = (local / 3600) % 24; + if (hrs < 0) hrs += 24; + int mins = (local / 60) % 60; + if (mins < 0) mins += 60; + + char timeBuf[6]; + sprintf(timeBuf, "%02d:%02d", hrs, mins); + + display.setTextSize(0); // tinyfont + display.setColor(DisplayDriver::LIGHT); + uint16_t tw = display.getTextWidth(timeBuf); + int clockX = (display.width() - tw) / 2; + display.setCursor(clockX, -3); // align with battery text Y + display.print(timeBuf); + display.setTextSize(1); // restore + } + } // curr page indicator int y = 14; int x = display.width() / 2 - 5 * (HomePage::Count-1); @@ -332,6 +364,42 @@ public: display.drawTextRightAlign(display.width()-1, y, buf); y = y + 12; } + // Show RTC time and UTC offset on GPS page + { + uint32_t now = _rtc->getCurrentTime(); + if (now > 1700000000) { + int32_t local = (int32_t)now + ((int32_t)_node_prefs->utc_offset_hours * 3600); + int hrs = (local / 3600) % 24; + if (hrs < 0) hrs += 24; + int mins = (local / 60) % 60; + if (mins < 0) mins += 60; + display.drawTextLeftAlign(0, y, "time(U)"); + sprintf(buf, "%02d:%02d UTC%+d", hrs, mins, _node_prefs->utc_offset_hours); + display.drawTextRightAlign(display.width()-1, y, buf); + } else { + display.drawTextLeftAlign(0, y, "time(U)"); + display.drawTextRightAlign(display.width()-1, y, "no sync"); + } + } + // UTC offset editor overlay + if (_editing_utc) { + // Draw background box + int bx = 4, by = 20, bw = display.width() - 8, bh = 40; + display.setColor(DisplayDriver::DARK); + display.fillRect(bx, by, bw, bh); + display.setColor(DisplayDriver::LIGHT); + display.drawRect(bx, by, bw, bh); + + // Show current offset value + display.setTextSize(2); + sprintf(buf, "UTC%+d", _node_prefs->utc_offset_hours); + display.drawTextCentered(display.width() / 2, by + 4, buf); + + // Show controls hint + display.setTextSize(0); + display.drawTextCentered(display.width() / 2, by + bh - 10, "W/S:adj Enter:ok Q:cancel"); + display.setTextSize(1); + } #endif #if UI_SENSORS_PAGE == 1 } else if (_page == HomePage::SENSORS) { @@ -415,10 +483,44 @@ public: display.drawTextCentered(display.width() / 2, 64 - 11, "hibernate:" PRESS_LABEL); } } - return 5000; // next render after 5000 ms + return _editing_utc ? 700 : 5000; // match e-ink refresh cycle while editing UTC } bool handleInput(char c) override { + // UTC offset editing mode - intercept all keys + if (_editing_utc) { + if (c == 'w' || c == KEY_PREV) { + // Increment offset + if (_node_prefs->utc_offset_hours < 14) { + _node_prefs->utc_offset_hours++; + } + return true; + } + if (c == 's' || c == KEY_NEXT) { + // Decrement offset + if (_node_prefs->utc_offset_hours > -12) { + _node_prefs->utc_offset_hours--; + } + return true; + } + if (c == KEY_ENTER) { + // Save and exit + Serial.printf("UTC offset saving: %d\n", _node_prefs->utc_offset_hours); + the_mesh.savePrefs(); + _editing_utc = false; + _task->showAlert("UTC offset saved", 800); + Serial.println("UTC offset save complete"); + return true; + } + if (c == 'q' || c == 'u') { + // Cancel - restore original value + _node_prefs->utc_offset_hours = _saved_utc_offset; + _editing_utc = false; + return true; + } + return true; // Consume all other keys while editing + } + if (c == KEY_LEFT || c == KEY_PREV) { _page = (_page + HomePage::Count - 1) % HomePage::Count; return true; @@ -452,6 +554,11 @@ public: _task->toggleGPS(); return true; } + if (c == 'u' && _page == HomePage::GPS) { + _editing_utc = true; + _saved_utc_offset = _node_prefs->utc_offset_hours; + return true; + } #endif #if UI_SENSORS_PAGE == 1 if (c == KEY_ENTER && _page == HomePage::SENSORS) { @@ -609,7 +716,6 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no channel_screen = new ChannelScreen(this, &rtc_clock); contacts_screen = new ContactsScreen(this, &rtc_clock); text_reader = new TextReaderScreen(this); - repeater_admin = new RepeaterAdminScreen(this, &rtc_clock); setCurrScreen(splash); } @@ -651,8 +757,8 @@ switch(t){ void UITask::msgRead(int msgcount) { _msgcount = msgcount; - if (msgcount == 0 && curr == msg_preview) { - gotoHomeScreen(); // only leave msg_preview when queue is empty + if (msgcount == 0) { + gotoHomeScreen(); } } @@ -988,11 +1094,22 @@ void UITask::injectKey(char c) { } curr->handleInput(c); _auto_off = millis() + AUTO_OFF_MILLIS; // extend auto-off timer - _next_refresh = 100; // trigger refresh + // Debounce refresh when editing UTC offset - e-ink takes 644ms per refresh + // so don't queue another render until the current one could have finished + if (isEditingHomeScreen()) { + unsigned long earliest = millis() + 700; + if (_next_refresh < earliest) { + _next_refresh = earliest; + } + } else { + _next_refresh = 100; // trigger refresh + } } } void UITask::gotoHomeScreen() { + // Cancel any active editing state when navigating to home + ((HomeScreen *) home)->cancelEditUTC(); setCurrScreen(home); if (_display != NULL && !_display->isOn()) { _display->turnOn(); @@ -1001,6 +1118,10 @@ void UITask::gotoHomeScreen() { _next_refresh = 100; } +bool UITask::isEditingHomeScreen() const { + return curr == home && ((HomeScreen *) home)->isEditingUTC(); +} + void UITask::gotoChannelScreen() { ((ChannelScreen *) channel_screen)->resetScroll(); setCurrScreen(channel_screen); @@ -1045,38 +1166,4 @@ void UITask::addSentChannelMessage(uint8_t channel_idx, const char* sender, cons // Add to channel history with path_len=0 (local message) ((ChannelScreen *) channel_screen)->addMessage(channel_idx, 0, sender, formattedMsg); -} - -void UITask::gotoRepeaterAdmin(int contactIdx) { - // Get contact name for the screen header - ContactInfo contact; - char name[32] = "Unknown"; - if (the_mesh.getContactByIdx(contactIdx, contact)) { - strncpy(name, contact.name, sizeof(name) - 1); - name[sizeof(name) - 1] = '\0'; - } - - RepeaterAdminScreen* admin = (RepeaterAdminScreen*)repeater_admin; - admin->openForContact(contactIdx, name); - setCurrScreen(repeater_admin); - - if (_display != NULL && !_display->isOn()) { - _display->turnOn(); - } - _auto_off = millis() + AUTO_OFF_MILLIS; - _next_refresh = 100; -} - -void UITask::onAdminLoginResult(bool success, uint8_t permissions, uint32_t server_time) { - if (isOnRepeaterAdmin()) { - ((RepeaterAdminScreen*)repeater_admin)->onLoginResult(success, permissions, server_time); - _next_refresh = 100; // trigger re-render - } -} - -void UITask::onAdminCliResponse(const char* from_name, const char* text) { - if (isOnRepeaterAdmin()) { - ((RepeaterAdminScreen*)repeater_admin)->onCliResponse(text); - _next_refresh = 100; // trigger re-render - } } \ No newline at end of file diff --git a/examples/companion_radio/ui-new/UITask.h b/examples/companion_radio/ui-new/UITask.h index 1afd0511..a93eed1b 100644 --- a/examples/companion_radio/ui-new/UITask.h +++ b/examples/companion_radio/ui-new/UITask.h @@ -54,7 +54,6 @@ class UITask : public AbstractUITask { UIScreen* channel_screen; // Channel message history screen UIScreen* contacts_screen; // Contacts list screen UIScreen* text_reader; // *** NEW: Text reader screen *** - UIScreen* repeater_admin; // Repeater admin screen UIScreen* curr; void userLedHandler(); @@ -80,7 +79,6 @@ public: void gotoChannelScreen(); // Navigate to channel message screen void gotoContactsScreen(); // Navigate to contacts list void gotoTextReader(); // *** NEW: Navigate to text reader *** - void gotoRepeaterAdmin(int contactIdx); // Navigate to repeater admin void showAlert(const char* text, int duration_millis) override; void forceRefresh() override { _next_refresh = 100; } int getMsgCount() const { return _msgcount; } @@ -89,7 +87,7 @@ public: bool isOnChannelScreen() const { return curr == channel_screen; } bool isOnContactsScreen() const { return curr == contacts_screen; } bool isOnTextReader() const { return curr == text_reader; } // *** NEW *** - bool isOnRepeaterAdmin() const { return curr == repeater_admin; } + bool isEditingHomeScreen() const; // UTC offset editing on GPS page uint8_t getChannelScreenViewIdx() const; void toggleBuzzer(); @@ -101,17 +99,12 @@ public: // Add a sent message to the channel screen history void addSentChannelMessage(uint8_t channel_idx, const char* sender, const char* text) override; - - // Repeater admin callbacks - void onAdminLoginResult(bool success, uint8_t permissions, uint32_t server_time) override; - void onAdminCliResponse(const char* from_name, const char* text) override; // Get current screen for checking state UIScreen* getCurrentScreen() const { return curr; } UIScreen* getMsgPreviewScreen() const { return msg_preview; } UIScreen* getTextReaderScreen() const { return text_reader; } // *** NEW *** UIScreen* getContactsScreen() const { return contacts_screen; } - UIScreen* getRepeaterAdminScreen() const { return repeater_admin; } // from AbstractUITask void msgRead(int msgcount) override;