7 Commits

7 changed files with 281 additions and 91 deletions

View File

@@ -266,8 +266,8 @@ Press **S** from the home screen to open settings. On first boot (when the devic
| Key | Action |
|-----|--------|
| W / S | Navigate up / down through settings |
| Enter | Edit selected setting |
| Q | Back to home screen |
| Enter | Edit selected setting, or enter a sub-screen |
| Q | Back one level (sub-screen → top level → home screen) |
**Available settings:**
@@ -275,17 +275,25 @@ Press **S** from the home screen to open settings. On first boot (when the devic
|---------|-------------|
| Device Name | Text entry — type a name, Enter to confirm |
| Radio Preset | A / D to cycle presets (MeshCore Default, Long Range, Fast/Short, EU Default), Enter to apply |
| Frequency | W / S to adjust, Enter to confirm |
| Frequency | Text entry — type exact value (e.g. 916.575), Enter to confirm |
| Bandwidth | W / S to cycle standard values (31.25 / 62.5 / 125 / 250 / 500 kHz), Enter to confirm |
| Spreading Factor | W / S to adjust (512), Enter to confirm |
| Coding Rate | W / S to adjust (58), Enter to confirm |
| TX Power | W / S to adjust (120 dBm), Enter to confirm |
| UTC Offset | W / S to adjust (-12 to +14), Enter to confirm |
| Path Hash Mode | A / D to cycle (0 = 1-byte, 1 = 2-byte, 2 = 3-byte), Enter to confirm |
| Channels | View existing channels, add hashtag channels, or delete non-primary channels (X) |
| Msg Rcvd LED Pulse | Toggle keyboard backlight flash on new message (Enter to toggle) |
| GPS Baud Rate | A / D to cycle (Default 38400 / 4800 / 9600 / 19200 / 38400 / 57600 / 115200), Enter to confirm. **Requires reboot to take effect.** |
| Path Hash Mode | W / S to cycle (1-byte / 2-byte / 3-byte), Enter to confirm |
| Dark Mode | Toggle inverted display — white text on black background (Enter to toggle) |
| Contacts >> | Opens the Contacts sub-screen (see below) |
| Channels >> | Opens the Channels sub-screen (see below) |
| Device Info | Public key and firmware version (read-only) |
The bottom of the settings screen also displays your node ID and firmware version. On the 4G variant, IMEI, carrier name, and APN details are shown here as well.
**Contacts sub-screen** — press Enter on the `Contacts >>` row to open. Contains the contact auto-add mode picker (Auto All / Custom / Manual) and, when set to Custom, per-type toggles for Chat, Repeater, Room Server, Sensor, and an Overwrite Oldest option. Press Q to return to the top-level settings list.
**Channels sub-screen** — press Enter on the `Channels >>` row to open. Lists all current channels, with an option to add hashtag channels or delete non-primary channels (X). Press Q to return to the top-level settings list.
The top-level settings screen also displays your node ID and firmware version. On the 4G variant, IMEI, carrier name, and APN details are shown here as well.
When adding a hashtag channel, type the channel name and press Enter. The channel secret is automatically derived from the name via SHA-256, matching the standard MeshCore hashtag convention.
@@ -437,12 +445,12 @@ Tap keys to type. Tap **Enter** to submit, or press the **Boot button** to cance
### Display Settings
The T5S3 Settings screen includes two additional options not available on the T-Deck Pro:
The T5S3 Settings screen includes one additional display option not available on the T-Deck Pro:
| Setting | Description |
|---------|-------------|
| **Dark Mode** | Inverts the display — white text on black background. Tap to toggle on/off. |
| **Portrait Mode** | Rotates the display 90° from landscape (960×540) to portrait (540×960). Touch coordinates are automatically remapped. Text reader layout recalculates on orientation change. |
| **Dark Mode** | Inverts the display — white text on black background. Tap to toggle on/off. Available on both T-Deck Pro and T5S3. |
| **Portrait Mode** | Rotates the display 90° from landscape (960×540) to portrait (540×960). Touch coordinates are automatically remapped. Text reader layout recalculates on orientation change. T5S3 only. |
These settings are persisted and survive reboots.

View File

@@ -1193,12 +1193,24 @@ void MyMesh::begin(bool has_display) {
_prefs.gps_baudrate != 9600 && _prefs.gps_baudrate != 19200 &&
_prefs.gps_baudrate != 38400 && _prefs.gps_baudrate != 57600 &&
_prefs.gps_baudrate != 115200) {
Serial.printf("PREFS: invalid gps_baudrate=%lu — reset to 0 (default)\n",
(unsigned long)_prefs.gps_baudrate);
_prefs.gps_baudrate = 0; // reset to default if invalid
}
// interference_threshold: 0 = disabled, minimum functional value is 14
// interference_threshold: 0 = disabled, minimum functional value is 14, max sane ~30
if (_prefs.interference_threshold > 0 && _prefs.interference_threshold < 14) {
_prefs.interference_threshold = 0;
}
if (_prefs.interference_threshold > 50) {
Serial.printf("PREFS: invalid interference_threshold=%d — reset to 0 (disabled)\n",
_prefs.interference_threshold);
_prefs.interference_threshold = 0; // garbage from prefs upgrade — disable
}
// Clamp remaining v1.0 fields that may contain garbage after upgrade from older firmware
if (_prefs.path_hash_mode > 2) _prefs.path_hash_mode = 0;
if (_prefs.autoadd_max_hops > 64) _prefs.autoadd_max_hops = 0;
if (_prefs.dark_mode > 1) _prefs.dark_mode = 0;
if (_prefs.portrait_mode > 1) _prefs.portrait_mode = 0;
#ifdef BLE_PIN_CODE // 123456 by default
if (_prefs.ble_pin == 0) {

View File

@@ -8,11 +8,11 @@
#define FIRMWARE_VER_CODE 10
#ifndef FIRMWARE_BUILD_DATE
#define FIRMWARE_BUILD_DATE "13 March 2026"
#define FIRMWARE_BUILD_DATE "15 March 2026"
#endif
#ifndef FIRMWARE_VERSION
#define FIRMWARE_VERSION "Meck v1.0"
#define FIRMWARE_VERSION "Meck v1.1"
#endif
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)

View File

@@ -832,7 +832,27 @@ MyMesh the_mesh(radio_driver, fast_rng, rtc_clock, tables, store
}
}
// Default: enter/select (settings toggle, etc.)
// Settings screen: context-dependent long press
if (ui_task.isOnSettingsScreen()) {
SettingsScreen* ss = (SettingsScreen*)ui_task.getSettingsScreen();
if (ss) {
if (ss->isEditingText()) {
// Open VKB pre-populated with current edit buffer
ui_task.showVirtualKeyboard(VKB_SETTINGS_TEXT, ss->getEditLabel(),
ss->getEditBuf(), SETTINGS_TEXT_BUF - 1);
return 0;
}
if (ss->isEditingNumOrPicker()) {
return 0; // Consume — don't confirm prematurely
}
if (ss->isCursorOnDeletableChannel()) {
return 'x'; // Triggers existing X key → EDIT_CONFIRM delete flow
}
}
return KEY_ENTER; // All other settings rows: toggle/edit as normal
}
// Default: enter/select
return KEY_ENTER;
}
#endif
@@ -1144,15 +1164,18 @@ void setup() {
sensors.begin();
MESH_DEBUG_PRINTLN("setup() - sensors.begin() done");
// IMPORTANT: sensors.begin() calls initBasicGPS() which steals the GPS pins for Serial1
// We need to reinitialize Serial2 to reclaim them
// IMPORTANT: sensors.begin() calls initBasicGPS() which steals the GPS pins for Serial1.
// We must end Serial1 first, then reclaim the pins for Serial2 (which feeds gpsStream).
#if HAS_GPS
Serial2.end(); // Close any existing Serial2
Serial1.end(); // Release GPS pins from Serial1's UART + ISR
Serial2.end(); // Close any existing Serial2
{
uint32_t gps_baud = the_mesh.getNodePrefs()->gps_baudrate;
Serial.printf("GPS: prefs gps_baudrate=%lu (0=use default)\n", (unsigned long)gps_baud);
if (gps_baud == 0) gps_baud = GPS_BAUDRATE;
Serial2.begin(gps_baud, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN);
MESH_DEBUG_PRINTLN("setup() - Reinitialized Serial2 for GPS at %lu baud", (unsigned long)gps_baud);
Serial.printf("GPS: Serial2 started at %lu baud (RX=%d TX=%d)\n",
(unsigned long)gps_baud, GPS_RX_PIN, GPS_TX_PIN);
}
#endif
@@ -1353,10 +1376,12 @@ void setup() {
}
#endif
// GPS power — honour saved pref, default to enabled on first boot
// GPS power — honour saved pref, default to enabled on first boot.
// GPS is critical for timesync on standalone variants without 4G.
#if HAS_GPS
{
bool gps_wanted = the_mesh.getNodePrefs()->gps_enabled;
Serial.printf("GPS: pref gps_enabled=%d\n", (int)gps_wanted);
if (gps_wanted) {
#ifdef PIN_GPS_EN
digitalWrite(PIN_GPS_EN, GPS_EN_ACTIVE);
@@ -1368,7 +1393,7 @@ void setup() {
#endif
sensors.setSettingValue("gps", "0");
}
MESH_DEBUG_PRINTLN("setup() - GPS power %s", gps_wanted ? "on" : "off");
Serial.printf("GPS: power %s, PIN_GPS_EN=%d\n", gps_wanted ? "ON" : "OFF", PIN_GPS_EN);
}
#endif
@@ -1390,6 +1415,21 @@ void loop() {
sensors.loop();
// GPS diagnostic — print sentence count every 30s so we can tell if Serial2 is receiving data
#if HAS_GPS
{
static unsigned long lastGpsDiag = 0;
if (millis() - lastGpsDiag >= 30000) {
lastGpsDiag = millis();
uint32_t sentences = gpsStream.getSentenceCount();
uint16_t perSec = gpsStream.getSentencesPerSec();
Serial.printf("GPS diag: %lu sentences total, %u/sec, Serial2.available=%d, lat=%.6f lon=%.6f\n",
(unsigned long)sentences, perSec, Serial2.available(),
sensors.node_lat, sensors.node_lon);
}
}
#endif
// Map screen: periodically update own GPS position and contact markers
#if HAS_GPS
if (ui_task.isOnMapScreen()) {

View File

@@ -51,6 +51,25 @@ extern MyMesh the_mesh;
// ---------------------------------------------------------------------------
#include "RadioPresets.h"
// ---------------------------------------------------------------------------
// GPS baud rate options (shared with UITask GPS home page overlay)
// ---------------------------------------------------------------------------
static const uint32_t GPS_BAUD_OPTIONS[] = { 0, 4800, 9600, 19200, 38400, 57600, 115200 };
#define GPS_BAUD_OPTION_COUNT 7
static inline const char* gpsBaudLabel(uint32_t baud, char* buf, int bufLen) {
if (baud == 0) return "Default (38400)";
snprintf(buf, bufLen, "%lu", (unsigned long)baud);
return buf;
}
static inline int findGpsBaudIndex(uint32_t baud) {
for (int i = 0; i < GPS_BAUD_OPTION_COUNT; i++) {
if (GPS_BAUD_OPTIONS[i] == baud) return i;
}
return 0;
}
// ---------------------------------------------------------------------------
// Settings row types
// ---------------------------------------------------------------------------
@@ -68,6 +87,7 @@ enum SettingsRowType : uint8_t {
#if defined(LilyGo_T5S3_EPaper_Pro)
ROW_PORTRAIT_MODE, // Portrait orientation toggle
#endif
ROW_GPS_BAUD, // GPS baud rate picker (requires reboot)
ROW_PATH_HASH_SIZE, // Path hash size (1, 2, or 3 bytes per hop)
#ifdef MECK_WIFI_COMPANION
ROW_WIFI_SETUP, // WiFi SSID/password configuration
@@ -84,6 +104,8 @@ enum SettingsRowType : uint8_t {
ROW_AUTOADD_ROOM, // Toggle: auto-add Room Servers
ROW_AUTOADD_SENSOR, // Toggle: auto-add Sensors
ROW_AUTOADD_OVERWRITE, // Toggle: overwrite oldest non-favourite when full
ROW_CONTACTS_SUBMENU, // Folder row → enters Contacts sub-screen
ROW_CHANNELS_SUBMENU, // Folder row → enters Channels sub-screen
ROW_CH_HEADER, // "--- Channels ---" separator
ROW_CHANNEL, // A channel entry (dynamic, index stored separately)
ROW_ADD_CHANNEL, // "+ Add Hashtag Channel"
@@ -111,6 +133,15 @@ enum EditMode : uint8_t {
#endif
};
// ---------------------------------------------------------------------------
// Settings sub-screens (collapsible sections)
// ---------------------------------------------------------------------------
enum SubScreen : uint8_t {
SUB_NONE, // Top-level settings list
SUB_CONTACTS, // Contacts settings sub-screen
SUB_CHANNELS, // Channels management sub-screen
};
// Max rows in the settings list (increased for contact sub-toggles + WiFi)
#if defined(HAS_4G_MODEM) && defined(MECK_WIFI_COMPANION)
#define SETTINGS_MAX_ROWS 56 // Extra rows for IMEI, Carrier, APN, contacts, WiFi
@@ -153,6 +184,10 @@ private:
// Onboarding mode
bool _onboarding;
// Sub-screen navigation
SubScreen _subScreen;
int _savedTopCursor; // cursor position to restore when leaving sub-screen
// Dirty flag for radio params — prompt to apply
bool _radioChanged;
@@ -240,66 +275,68 @@ private:
void rebuildRows() {
_numRows = 0;
addRow(ROW_NAME);
addRow(ROW_RADIO_PRESET);
addRow(ROW_FREQ);
addRow(ROW_BW);
addRow(ROW_SF);
addRow(ROW_CR);
addRow(ROW_TX_POWER);
addRow(ROW_UTC_OFFSET);
addRow(ROW_MSG_NOTIFY);
addRow(ROW_PATH_HASH_SIZE);
addRow(ROW_DARK_MODE);
#if defined(LilyGo_T5S3_EPaper_Pro)
addRow(ROW_PORTRAIT_MODE);
#endif
#ifdef MECK_WIFI_COMPANION
addRow(ROW_WIFI_SETUP);
addRow(ROW_WIFI_TOGGLE);
#endif
#ifdef HAS_4G_MODEM
addRow(ROW_MODEM_TOGGLE);
// addRow(ROW_RINGTONE);
#endif
// --- Contacts section ---
addRow(ROW_CONTACT_HEADER);
addRow(ROW_CONTACT_MODE);
// Show per-type sub-toggles only in Custom mode
if (getContactMode() == CONTACT_MODE_CUSTOM) {
addRow(ROW_AUTOADD_CHAT);
addRow(ROW_AUTOADD_REPEATER);
addRow(ROW_AUTOADD_ROOM);
addRow(ROW_AUTOADD_SENSOR);
addRow(ROW_AUTOADD_OVERWRITE);
}
// --- Channels section ---
addRow(ROW_CH_HEADER);
// Enumerate current channels
for (uint8_t i = 0; i < MAX_GROUP_CHANNELS; i++) {
ChannelDetails ch;
if (the_mesh.getChannel(i, ch) && ch.name[0] != '\0') {
addRow(ROW_CHANNEL, i);
} else {
break; // channels are contiguous
if (_subScreen == SUB_CONTACTS) {
// --- Contacts sub-screen: only contact-related rows ---
addRow(ROW_CONTACT_MODE);
if (getContactMode() == CONTACT_MODE_CUSTOM) {
addRow(ROW_AUTOADD_CHAT);
addRow(ROW_AUTOADD_REPEATER);
addRow(ROW_AUTOADD_ROOM);
addRow(ROW_AUTOADD_SENSOR);
addRow(ROW_AUTOADD_OVERWRITE);
}
} else if (_subScreen == SUB_CHANNELS) {
// --- Channels sub-screen: only channel-related rows ---
for (uint8_t i = 0; i < MAX_GROUP_CHANNELS; i++) {
ChannelDetails ch;
if (the_mesh.getChannel(i, ch) && ch.name[0] != '\0') {
addRow(ROW_CHANNEL, i);
} else {
break;
}
}
addRow(ROW_ADD_CHANNEL);
} else {
// --- Top-level settings list ---
addRow(ROW_NAME);
addRow(ROW_RADIO_PRESET);
addRow(ROW_FREQ);
addRow(ROW_BW);
addRow(ROW_SF);
addRow(ROW_CR);
addRow(ROW_TX_POWER);
addRow(ROW_UTC_OFFSET);
addRow(ROW_MSG_NOTIFY);
addRow(ROW_GPS_BAUD);
addRow(ROW_PATH_HASH_SIZE);
addRow(ROW_DARK_MODE);
#if defined(LilyGo_T5S3_EPaper_Pro)
addRow(ROW_PORTRAIT_MODE);
#endif
#ifdef MECK_WIFI_COMPANION
addRow(ROW_WIFI_SETUP);
addRow(ROW_WIFI_TOGGLE);
#endif
#ifdef HAS_4G_MODEM
addRow(ROW_MODEM_TOGGLE);
#endif
// Folder rows for sub-screens
addRow(ROW_CONTACTS_SUBMENU);
addRow(ROW_CHANNELS_SUBMENU);
// Info section (stays at top level)
addRow(ROW_INFO_HEADER);
addRow(ROW_PUB_KEY);
addRow(ROW_FIRMWARE);
#ifdef HAS_4G_MODEM
addRow(ROW_IMEI);
addRow(ROW_OPERATOR_INFO);
addRow(ROW_APN);
#endif
}
addRow(ROW_ADD_CHANNEL);
addRow(ROW_INFO_HEADER);
addRow(ROW_PUB_KEY);
addRow(ROW_FIRMWARE);
#ifdef HAS_4G_MODEM
addRow(ROW_IMEI);
addRow(ROW_OPERATOR_INFO);
addRow(ROW_APN);
#endif
// Clamp cursor
if (_cursor >= _numRows) _cursor = _numRows - 1;
if (_cursor < 0) _cursor = 0;
@@ -321,7 +358,7 @@ private:
#ifdef HAS_4G_MODEM
&& t != ROW_IMEI && t != ROW_OPERATOR_INFO
#endif
;
; // ROW_CONTACTS_SUBMENU and ROW_CHANNELS_SUBMENU ARE selectable
}
void skipNonSelectable(int dir) {
@@ -439,12 +476,15 @@ public:
_numRows(0), _cursor(0), _scrollTop(0),
_editMode(EDIT_NONE), _editPos(0), _editPickerIdx(0),
_editFloat(0), _editInt(0), _confirmAction(0),
_onboarding(false), _radioChanged(false) {
_onboarding(false), _subScreen(SUB_NONE), _savedTopCursor(0),
_radioChanged(false) {
memset(_editBuf, 0, sizeof(_editBuf));
}
void enter() {
_editMode = EDIT_NONE;
_subScreen = SUB_NONE;
_savedTopCursor = 0;
_cursor = 0;
_scrollTop = 0;
_radioChanged = false;
@@ -628,6 +668,10 @@ public:
display.setCursor(0, 0);
if (_onboarding) {
display.print("Welcome! Setup");
} else if (_subScreen == SUB_CONTACTS) {
display.print("Settings > Contacts");
} else if (_subScreen == SUB_CHANNELS) {
display.print("Settings > Channels");
} else {
display.print("Settings");
}
@@ -773,6 +817,19 @@ public:
display.print(tmp);
break;
case ROW_GPS_BAUD: {
char baudStr[16];
if (editing && _editMode == EDIT_PICKER) {
snprintf(tmp, sizeof(tmp), "< GPS Baud: %s > *",
gpsBaudLabel(GPS_BAUD_OPTIONS[_editPickerIdx], baudStr, sizeof(baudStr)));
} else {
snprintf(tmp, sizeof(tmp), "GPS Baud: %s *",
gpsBaudLabel(_prefs->gps_baudrate, baudStr, sizeof(baudStr)));
}
display.print(tmp);
break;
}
case ROW_DARK_MODE:
snprintf(tmp, sizeof(tmp), "Dark Mode: %s",
_prefs->dark_mode ? "ON" : "OFF");
@@ -817,6 +874,17 @@ public:
// break;
#endif
// --- Submenu folder rows ---
case ROW_CONTACTS_SUBMENU:
display.setColor(selected ? DisplayDriver::DARK : DisplayDriver::GREEN);
display.print("Contacts >>");
break;
case ROW_CHANNELS_SUBMENU:
display.setColor(selected ? DisplayDriver::DARK : DisplayDriver::GREEN);
display.print("Channels >>");
break;
// --- Contacts section ---
case ROW_CONTACT_HEADER:
display.setColor(DisplayDriver::YELLOW);
@@ -1093,10 +1161,17 @@ public:
#if defined(LilyGo_T5S3_EPaper_Pro)
if (_editMode == EDIT_NONE) {
display.print("Swipe:Scroll");
const char* r = "Tap:Toggle Hold:Edit";
display.setCursor(display.width() - display.getTextWidth(r) - 2, footerY);
display.print(r);
if (_subScreen != SUB_NONE) {
display.print("Boot:Back");
const char* r = "Tap:Toggle Hold:Edit";
display.setCursor(display.width() - display.getTextWidth(r) - 2, footerY);
display.print(r);
} else {
display.print("Swipe:Scroll");
const char* r = "Tap:Toggle Hold:Edit";
display.setCursor(display.width() - display.getTextWidth(r) - 2, footerY);
display.print(r);
}
} else if (_editMode == EDIT_NUMBER) {
display.print("Swipe:Adjust");
const char* r = "Tap:OK Boot:Cancel";
@@ -1155,7 +1230,11 @@ public:
} else if (_editMode == EDIT_CONFIRM) {
// Footer already covered by overlay
} else {
display.print("Q:Bck");
if (_subScreen != SUB_NONE) {
display.print("Q:Back");
} else {
display.print("Q:Bck");
}
const char* r = "W/S:Up/Dwn Entr:Chng";
display.setCursor(display.width() - display.getTextWidth(r) - 2, footerY);
display.print(r);
@@ -1382,6 +1461,9 @@ public:
if (type == ROW_CONTACT_MODE) {
_editPickerIdx--;
if (_editPickerIdx < 0) _editPickerIdx = CONTACT_MODE_COUNT - 1;
} else if (type == ROW_GPS_BAUD) {
_editPickerIdx--;
if (_editPickerIdx < 0) _editPickerIdx = GPS_BAUD_OPTION_COUNT - 1;
} else {
// Radio preset
_editPickerIdx--;
@@ -1393,6 +1475,9 @@ public:
if (type == ROW_CONTACT_MODE) {
_editPickerIdx++;
if (_editPickerIdx >= CONTACT_MODE_COUNT) _editPickerIdx = 0;
} else if (type == ROW_GPS_BAUD) {
_editPickerIdx++;
if (_editPickerIdx >= GPS_BAUD_OPTION_COUNT) _editPickerIdx = 0;
} else {
// Radio preset
_editPickerIdx++;
@@ -1404,6 +1489,12 @@ public:
if (type == ROW_CONTACT_MODE) {
applyContactMode(_editPickerIdx);
_editMode = EDIT_NONE;
} else if (type == ROW_GPS_BAUD) {
_prefs->gps_baudrate = GPS_BAUD_OPTIONS[_editPickerIdx];
the_mesh.savePrefs();
_editMode = EDIT_NONE;
Serial.printf("Settings: GPS baud set to %lu (reboot to apply)\n",
(unsigned long)_prefs->gps_baudrate);
} else {
// Apply radio preset
if (_editPickerIdx >= 0 && _editPickerIdx < (int)NUM_RADIO_PRESETS) {
@@ -1583,6 +1674,9 @@ public:
case ROW_PATH_HASH_SIZE:
startEditInt(_prefs->path_hash_mode + 1); // display as 1-3
break;
case ROW_GPS_BAUD:
startEditPicker(findGpsBaudIndex(_prefs->gps_baudrate));
break;
case ROW_DARK_MODE:
_prefs->dark_mode = _prefs->dark_mode ? 0 : 1;
the_mesh.savePrefs();
@@ -1704,6 +1798,24 @@ public:
(_prefs->autoadd_config & AUTO_ADD_OVERWRITE_OLDEST) ? "ON" : "OFF");
break;
// --- Submenu folder rows ---
case ROW_CONTACTS_SUBMENU:
_savedTopCursor = _cursor;
_subScreen = SUB_CONTACTS;
_cursor = 0;
_scrollTop = 0;
rebuildRows();
Serial.println("Settings: entered Contacts sub-screen");
break;
case ROW_CHANNELS_SUBMENU:
_savedTopCursor = _cursor;
_subScreen = SUB_CHANNELS;
_cursor = 0;
_scrollTop = 0;
rebuildRows();
Serial.println("Settings: entered Channels sub-screen");
break;
case ROW_ADD_CHANNEL:
startEditText("");
break;
@@ -1727,8 +1839,18 @@ public:
}
}
// Q: back — if radio changed, prompt to apply first
// Q: back -- if in sub-screen, return to top level; else exit settings
if (c == 'q' || c == 'Q') {
if (_subScreen != SUB_NONE) {
// Return to top-level settings list
_subScreen = SUB_NONE;
rebuildRows();
_cursor = _savedTopCursor;
if (_cursor >= _numRows) _cursor = _numRows - 1;
skipNonSelectable(1);
Serial.println("Settings: back to top level");
return true;
}
if (_radioChanged) {
_editMode = EDIT_CONFIRM;
_confirmAction = 2;

View File

@@ -138,6 +138,7 @@ class HomeScreen : public UIScreen {
unsigned long _shutdown_at; // earliest time to proceed with shutdown (after e-ink refresh)
bool _editing_utc;
int8_t _saved_utc_offset; // for cancel/undo
AdvertPath recent[UI_RECENT_LIST_SIZE];
@@ -254,7 +255,7 @@ public:
_shutdown_init(false), _shutdown_at(0), _editing_utc(false), _saved_utc_offset(0), sensors_lpp(200) { }
bool isEditingUTC() const { return _editing_utc; }
void cancelEditUTC() {
void cancelEditing() {
if (_editing_utc) {
_node_prefs->utc_offset_hours = _saved_utc_offset;
_editing_utc = false;
@@ -666,7 +667,9 @@ public:
display.drawTextLeftAlign(0, y, "time(U)");
display.drawTextRightAlign(display.width()-1, y, "no sync");
}
y = y + 12;
}
// UTC offset editor overlay
if (_editing_utc) {
// Draw background box
@@ -686,6 +689,7 @@ public:
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) {
@@ -842,7 +846,7 @@ public:
#endif
}
}
return _editing_utc ? 700 : 5000; // match e-ink refresh cycle while editing UTC
return _editing_utc ? 700 : 5000;
}
bool handleInput(char c) override {
@@ -1175,16 +1179,18 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no
map_screen = nullptr;
#endif
// Apply saved dark mode preference before first render
if (_node_prefs->dark_mode) {
::display.setDarkMode(true);
}
#if defined(LilyGo_T5S3_EPaper_Pro)
// Apply saved display preferences before first render
if (_node_prefs->portrait_mode) {
::display.setPortraitMode(true);
}
#endif
// Apply saved dark mode preference (both T-Deck Pro and T5S3)
if (_node_prefs->dark_mode) {
::display.setDarkMode(true);
}
setCurrScreen(splash);
}
@@ -1546,6 +1552,7 @@ if (curr) curr->poll();
display.setDarkMode(_node_prefs->dark_mode != 0);
}
#if defined(LilyGo_T5S3_EPaper_Pro)
// Sync portrait mode with prefs (T5S3 only)
if (_node_prefs && display.isPortraitMode() != (_node_prefs->portrait_mode != 0)) {
display.setPortraitMode(_node_prefs->portrait_mode != 0);
// Text reader layout depends on orientation — force recalculation
@@ -1987,7 +1994,7 @@ void UITask::injectKey(char c) {
void UITask::gotoHomeScreen() {
// Cancel any active editing state when navigating to home
((HomeScreen *) home)->cancelEditUTC();
((HomeScreen *) home)->cancelEditing();
setCurrScreen(home);
if (_display != NULL && !_display->isOn()) {
_display->turnOn();

View File

@@ -29,6 +29,7 @@ enum VKBPurpose {
VKB_ADMIN_CLI, // Repeater admin CLI command
VKB_NOTES, // Insert text into notes
VKB_SETTINGS_NAME, // Edit node name
VKB_SETTINGS_TEXT, // Generic settings text edit (channel name, freq, APN)
VKB_WIFI_PASSWORD, // WiFi password entry (settings screen)
#ifdef MECK_WEB_READER
VKB_WEB_URL, // Web reader URL entry