T-Watch: standalone channel config screen (region, notifications, delete)

The SettingsScreen SUB_CHANNELS sub-screen drives every per-channel
action from physical-keyboard hotkeys (Ent: scope, N: notif, T: tone,
X/Hold: delete), so on the touch-only watches the channel list rendered
but nothing was actionable. Replace it on MECK_TWATCH builds with a
standalone WatchChannelConfigScreen following the WatchAlarmScreen /
WatchNotesScreen conventions. Desktop builds are unchanged.

LIST shows each channel with its region scope tag and notification
state (tap to select, tap again or button to open, paged like Notes).
DETAIL offers: Region -- opens the watch keyboard via a new TWKB_SCOPE
purpose, pre-filled with the current scope, committed through the same
setChannel + saveChannels path as desktop (empty submission clears);
Clear region -- reverts to the device default directly; Notify --
tap-cycles All -> Mentions -> Off with savePrefs, matching the desktop
N-key order. Delete is button-driven rather than touch: a short press
(PWR on the S3, boot button on the Plus) arms it, a second press
within 3s confirms. Channel 0 remains protected. The tone picker is
deliberately absent -- the watch has no audio path.

Settings hands off via a _wantsWatchChannels flag polled by
UITask::loop (mirrors the _needsTextVKB handshake); Back returns to
Settings via gotoSettingsScreen. Keyboard results route through the
existing consumeSendRequest purpose dispatch.

Note: deleteChannel replicates SettingsScreen's compaction verbatim,
including its existing quirk of not shifting channel_notif[] -- kept
faithful rather than silently diverging; fix both together if desired.

Files: WatchChannelConfigScreen.h (new, both variant dirs),
TWatchComposeScreens.h (both variant dirs, TWKB_SCOPE),
SettingsScreen.h, UITask.h, UITask.cpp, main.cpp
This commit is contained in:
pelgraine
2026-07-14 21:02:49 +10:00
parent e647fde02d
commit 5ce0540486
8 changed files with 817 additions and 3 deletions
+8
View File
@@ -1130,6 +1130,7 @@ static uint32_t _atoi(const char* sp) {
#endif
#if defined(MECK_TWATCH)
#include "WatchNotesScreen.h" // After UITask.h -- needs NodePrefs
#include "WatchChannelConfigScreen.h" // After UITask.h -- needs NodePrefs, the_mesh
#endif
#if defined(MECK_TWATCH) && HAS_GPS
#include "WatchMapScreen.h" // After BLE -- PNGdec headers conflict with BLE if included earlier
@@ -1385,6 +1386,13 @@ static void lastHeardToggleContact() {
if (wn) wn->handleTap(x, y);
return 0;
}
// Channel config screen: list rows, detail action rows and the footer are
// all tap targets, in the same logical coords as the alarm/notes screens.
if (ui_task.isOnWatchChannelConfigScreen()) {
WatchChannelConfigScreen* wc = (WatchChannelConfigScreen*)ui_task.getWatchChannelConfigScreen();
if (wc) wc->handleTap(x, y);
return 0;
}
#endif
#if defined(MECK_TWATCH)
// Watch: tiles open on long-press and pages change on swipe, so a plain tap
@@ -341,6 +341,7 @@ private:
// T5S3: signal UITask to open VKB when entering text edit mode
bool _needsTextVKB;
bool _wantsWatchChannels; // Watch: hand off to WatchChannelConfigScreen (polled by UITask)
// 4G modem state (runtime cache of config)
#ifdef HAS_4G_MODEM
@@ -698,7 +699,7 @@ public:
_editMode(EDIT_NONE), _editPos(0), _editPickerIdx(0),
_editFloat(0), _editInt(0), _fontPickerOriginal(0), _confirmAction(0),
_onboarding(false), _subScreen(SUB_NONE), _savedTopCursor(0),
_radioChanged(false), _needsTextVKB(false) {
_radioChanged(false), _needsTextVKB(false), _wantsWatchChannels(false) {
memset(_editBuf, 0, sizeof(_editBuf));
#ifdef HAS_SDCARD
_savedExportCursor = 0;
@@ -939,6 +940,8 @@ public:
// T5S3 VKB integration for text editing (channel name, device name, freq, APN)
bool needsTextVKB() const { return _needsTextVKB; }
void clearTextNeedsVKB() { _needsTextVKB = false; }
bool wantsWatchChannels() const { return _wantsWatchChannels; }
void clearWantsWatchChannels() { _wantsWatchChannels = false; }
const char* getEditBuf() const { return _editBuf; }
SettingsRowType getCurrentRowType() const { return _rows[_cursor].type; }
void submitEditText(const char* text) {
@@ -3798,12 +3801,18 @@ public:
Serial.println("Settings: entered Contacts sub-screen");
break;
case ROW_CHANNELS_SUBMENU:
#if defined(MECK_TWATCH)
// Watch: the desktop sub-screen's per-channel actions are keyboard
// hotkeys; hand off to the standalone WatchChannelConfigScreen.
_wantsWatchChannels = true; // UITask::loop() polls and opens it
#else
_savedTopCursor = _cursor;
_subScreen = SUB_CHANNELS;
_cursor = 0;
_scrollTop = 0;
rebuildRows();
Serial.println("Settings: entered Channels sub-screen");
#endif
break;
case ROW_RXLOG:
@@ -84,6 +84,7 @@
#endif
#if defined(MECK_TWATCH)
#include "WatchNotesScreen.h" // After UITask.h -- needs NodePrefs
#include "WatchChannelConfigScreen.h" // After UITask.h -- needs NodePrefs, the_mesh
#endif
#ifdef TWATCH_COMPOSE_ENABLED
#include "TWatchComposeScreens.h"
@@ -1798,6 +1799,7 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no
#if defined(MECK_TWATCH)
// LittleFS ("/maps" partition) is mounted in setup() before UITask::begin().
watch_notes_screen = new WatchNotesScreen(this, &rtc_clock, node_prefs);
watch_channel_cfg_screen = new WatchChannelConfigScreen(this, node_prefs);
#endif
#ifdef TWATCH_COMPOSE_ENABLED
tw_picker = new TWatchChannelPicker(_display);
@@ -2613,6 +2615,13 @@ if (curr) curr->poll();
if (watch_notes_screen) setCurrScreen(watch_notes_screen);
else gotoHomeScreen();
if (nerr) showAlert(nerr, 1200);
} else if (purpose == TWatchKeyboardScreen::TWKB_SCOPE) {
WatchChannelConfigScreen* wc = (WatchChannelConfigScreen*)watch_channel_cfg_screen;
const char* serr = wc ? wc->applyComposedScope(sendText) : "No channel cfg";
kb->clearOutBuf();
if (watch_channel_cfg_screen) setCurrScreen(watch_channel_cfg_screen);
else gotoHomeScreen();
if (serr) showAlert(serr, 1200);
} else { // TWKB_ADMIN_PASSWORD or TWKB_ADMIN_CLI
RepeaterAdminScreen* admin = (RepeaterAdminScreen*)getRepeaterAdminScreen();
if (admin) {
@@ -2642,6 +2651,27 @@ if (curr) curr->poll();
}
}
}
else if (curr == watch_channel_cfg_screen && watch_channel_cfg_screen != nullptr) {
WatchChannelConfigScreen* wc = (WatchChannelConfigScreen*)watch_channel_cfg_screen;
if (wc->wantsKeyboard()) {
wc->clearWantKeyboard();
openTWatchKeyboard(TWatchKeyboardScreen::TWKB_SCOPE, 0);
((TWatchKeyboardScreen*)tw_keyboard)->setInitialText(wc->getEditText());
} else if (wc->wantsExit()) {
wc->clearWantExit();
gotoSettingsScreen();
}
}
else if (curr == settings_screen && settings_screen != nullptr) {
SettingsScreen* ss = (SettingsScreen*)settings_screen;
if (ss->wantsWatchChannels()) {
ss->clearWantsWatchChannels();
if (watch_channel_cfg_screen) {
((WatchChannelConfigScreen*)watch_channel_cfg_screen)->enter();
setCurrScreen(watch_channel_cfg_screen);
}
}
}
#endif
if (_display != NULL && _display->isOn()) {
+3
View File
@@ -144,6 +144,7 @@ class UITask : public AbstractUITask {
#endif
#if defined(MECK_TWATCH)
UIScreen* watch_notes_screen; // LittleFS note pad (shared NotesScreen needs SD)
UIScreen* watch_channel_cfg_screen; // Per-channel region/notif/delete (touch UI)
#endif
UIScreen* _screenBeforeLock = nullptr;
bool _locked = false;
@@ -325,6 +326,7 @@ public:
#endif
#if defined(MECK_TWATCH)
bool isOnWatchNotesScreen() const { return curr == watch_notes_screen; }
bool isOnWatchChannelConfigScreen() const { return curr == watch_channel_cfg_screen; }
#endif
bool isOnMinesweeperScreen() const { return curr == minesweeper_screen; }
bool isOnMapScreen() const { return curr == map_screen; }
@@ -424,6 +426,7 @@ public:
#endif
#if defined(MECK_TWATCH)
UIScreen* getWatchNotesScreen() const { return watch_notes_screen; }
UIScreen* getWatchChannelConfigScreen() const { return watch_channel_cfg_screen; }
#endif
UIScreen* getMinesweeperScreen() const { return minesweeper_screen; }
UIScreen* getMapScreen() const { return map_screen; }