From 3307d87e9cdeb39cc6b9b22e7fe2de28940ea6ff Mon Sep 17 00:00:00 2001 From: Tesso M Costa Date: Mon, 24 Aug 2026 09:03:41 -0600 Subject: [PATCH] touch: add master "At a glance" toggle, move while-locked switch under it Options > Display previously only had "At a glance while locked" (opt-in, off by default). Add a master "At a glance" switch above it (on by default, matching the previously-unconditional behavior) that gates the whole feature, and move the while-locked switch out of Options > Lock screen to sit under it. The while-locked switch's own saved preference is left alone when the master is toggled off -- only its editability follows the master, so re-enabling the master always restores its true prior state rather than resetting it. Signed-off-by: Tesso M Costa --- src/helpers/esp32/TouchPrefsStore.cpp | 2 + src/helpers/esp32/TouchPrefsStore.h | 2 + src/ui-touch/UITask.cpp | 91 ++++++++++++++++++++------- 3 files changed, 71 insertions(+), 24 deletions(-) diff --git a/src/helpers/esp32/TouchPrefsStore.cpp b/src/helpers/esp32/TouchPrefsStore.cpp index f2c8a2a..133ae40 100644 --- a/src/helpers/esp32/TouchPrefsStore.cpp +++ b/src/helpers/esp32/TouchPrefsStore.cpp @@ -1835,6 +1835,8 @@ bool touchPrefsGetLockOnScreenOff() { if (!s_begun) touchPrefsBegin(); return s_ void touchPrefsSetLockOnScreenOff(bool on) { if (!s_begun) touchPrefsBegin(); prefsPutUChar("lock_off", on ? 1 : 0); } bool touchPrefsGetGlanceWhenLocked() { if (!s_begun) touchPrefsBegin(); return s_prefs.getUChar("glance_lck", 0) != 0; } void touchPrefsSetGlanceWhenLocked(bool on) { if (!s_begun) touchPrefsBegin(); prefsPutUChar("glance_lck", on ? 1 : 0); } +bool touchPrefsGetGlanceEnabled() { if (!s_begun) touchPrefsBegin(); return s_prefs.getUChar("glance_en", 1) != 0; } +void touchPrefsSetGlanceEnabled(bool on) { if (!s_begun) touchPrefsBegin(); prefsPutUChar("glance_en", on ? 1 : 0); } #if defined(HAS_TANMATSU) // only the Tanmatsu has the message LED — keep S3 (T-Deck/V4) bins unchanged bool touchPrefsGetMsgLed() { if (!s_begun) touchPrefsBegin(); return s_prefs.getUChar("msg_led", 1) != 0; } // default ON diff --git a/src/helpers/esp32/TouchPrefsStore.h b/src/helpers/esp32/TouchPrefsStore.h index 9be91d3..4d7205f 100644 --- a/src/helpers/esp32/TouchPrefsStore.h +++ b/src/helpers/esp32/TouchPrefsStore.h @@ -430,6 +430,8 @@ bool touchPrefsGetLockOnScreenOff(); // idle screen-off auto-locks; on void touchPrefsSetLockOnScreenOff(bool on); bool touchPrefsGetGlanceWhenLocked(); // "at a glance" also fires while manually/idle locked, not just unlocked+dimmed (default false) void touchPrefsSetGlanceWhenLocked(bool on); +bool touchPrefsGetGlanceEnabled(); // master "at a glance" feature toggle (default true) +void touchPrefsSetGlanceEnabled(bool on); /** Per-channel mute, keyed by channel name. Bit 0 = mute messages, bit 1 = * mute @-mentions. Suppresses the notification SOUND for that channel (the diff --git a/src/ui-touch/UITask.cpp b/src/ui-touch/UITask.cpp index b902cc4..0ca8f6b 100644 --- a/src/ui-touch/UITask.cpp +++ b/src/ui-touch/UITask.cpp @@ -2105,6 +2105,8 @@ struct SettingsModalState { lv_obj_t* exp_boost_sw; lv_obj_t* exp_dc_sw; lv_obj_t* wifi_sw; + lv_obj_t* glance_en_sw; + lv_obj_t* glance_locked_sw; lv_obj_t* wifi_ssid_ta; lv_obj_t* wifi_pwd_ta; /** Transports modal: live STA line (IP when connected, else Arduino WiFi status). */ @@ -10851,6 +10853,23 @@ static void glanceWhenLockedToggleCb(lv_event_t* e) { } } +// Master "At a glance" toggle (Options > Display): gates the whole feature. +// The secondary "while locked" switch's own saved preference is left +// alone -- only its editability follows the master, so re-enabling always +// restores its true prior state. +static void glanceEnabledToggleCb(lv_event_t* e) { + if (lv_event_get_code(e) != LV_EVENT_VALUE_CHANGED) return; + const bool on = lv_obj_has_state(lv_event_get_target(e), LV_STATE_CHECKED); +#if defined(ESP32) + touchPrefsSetGlanceEnabled(on); +#endif + if (g_set_modal.glance_locked_sw) { + if (on) lv_obj_clear_state(g_set_modal.glance_locked_sw, LV_STATE_DISABLED); + else lv_obj_add_state(g_set_modal.glance_locked_sw, LV_STATE_DISABLED); + } + if (g_lv.task) g_lv.task->showAlert(on ? TR("At a glance enabled") : TR("At a glance disabled"), 1200); +} + #if CAP_TRACKBALL // Invert the scrollball direction everywhere it drives motion (cursor, map pan, // emoji selector). Cached in s_tb_reverse so the per-tick poll never hits NVS. @@ -11927,6 +11946,41 @@ static void buildDeviceSettings(int sec) { y += LV_MAX(40, h + 12); } + /* At a glance: master enable for the "at a glance" message-preview overlay + (see atGlanceShow()/newMsgImpl()) -- on by default, matching the + previously-unconditional behavior. Gates the "while locked" switch below. */ + bool glance_on = true; + { + int h = settingsRowLabel(body, y, 6, TR("At a glance"), COLOR_SUB, nullptr, 56); + g_set_modal.glance_en_sw = lv_switch_create(body); + lv_obj_align(g_set_modal.glance_en_sw, LV_ALIGN_TOP_RIGHT, 0, y); +#if defined(ESP32) + glance_on = touchPrefsGetGlanceEnabled(); + if (glance_on) lv_obj_add_state(g_set_modal.glance_en_sw, LV_STATE_CHECKED); +#endif + lv_obj_add_event_cb(g_set_modal.glance_en_sw, glanceEnabledToggleCb, LV_EVENT_VALUE_CHANGED, nullptr); + y += LV_MAX(40, h + 12); + } + +#if !defined(HAS_TDISPLAY_P4) + /* "At a glance" also fires while manually/idle locked, not just while + unlocked+dimmed -- off by default since it's a real trade-off (message + text becomes readable off a locked device). Disabled (not cleared) while + the master switch above is off. Moved here from Options > Lock screen so + it sits with the feature it modifies. */ + { + int h = settingsRowLabel(body, y, 6, TR("At a glance while locked"), COLOR_SUB, nullptr, 56); + g_set_modal.glance_locked_sw = lv_switch_create(body); + lv_obj_align(g_set_modal.glance_locked_sw, LV_ALIGN_TOP_RIGHT, 0, y); +#if defined(ESP32) + if (touchPrefsGetGlanceWhenLocked()) lv_obj_add_state(g_set_modal.glance_locked_sw, LV_STATE_CHECKED); + if (!glance_on) lv_obj_add_state(g_set_modal.glance_locked_sw, LV_STATE_DISABLED); +#endif + lv_obj_add_event_cb(g_set_modal.glance_locked_sw, glanceWhenLockedToggleCb, LV_EVENT_VALUE_CHANGED, nullptr); + y += LV_MAX(40, h + 12); + } +#endif + #if defined(HAS_EXPANSION_KIT) /* Show Sensors tab (V4 Expansion Kit): toggles the bottom Sensors tab + the Home env widget. Also auto-hidden when no environment sensor is attached. @@ -12416,21 +12470,6 @@ static void buildDeviceSettings(int sec) { lv_obj_add_event_cb(sw, lockOnScreenOffToggleCb, LV_EVENT_VALUE_CHANGED, nullptr); y += LV_MAX(40, h + 12); } - - // "At a glance" (see atGlanceShow()/newMsgImpl()) normally only fires while - // unlocked-but-idle-dimmed; this opts in to it firing while manually locked - // too, i.e. message previews become readable off a locked device -- off by - // default since that's a real trade-off, not just a convenience toggle. - { - int h = settingsRowLabel(body, y, 6, TR("At a glance while locked"), COLOR_SUB, nullptr, 56); - lv_obj_t* sw = lv_switch_create(body); - lv_obj_align(sw, LV_ALIGN_TOP_RIGHT, 0, y); -#if defined(ESP32) - if (touchPrefsGetGlanceWhenLocked()) lv_obj_add_state(sw, LV_STATE_CHECKED); -#endif - lv_obj_add_event_cb(sw, glanceWhenLockedToggleCb, LV_EVENT_VALUE_CHANGED, nullptr); - y += LV_MAX(40, h + 12); - } #endif @@ -45933,20 +45972,24 @@ void UITask::newMsgImpl(uint8_t path_len, const char* from_name, const char* tex #if defined(HAS_TOUCH_UI) // "At a glance": device unlocked but idle-dimmed to off -- light the plain // black glance overlay instead of the full app (see atGlanceShow() above). - // Skipped if DND is on. Manually locked is ALSO skipped unless the user - // opted in via Options > Lock screen > "At a glance while locked" - // (touchPrefsGetGlanceWhenLocked()) -- off by default since it's a real - // trade-off (message text becomes readable off a locked device). `_screen_off` - // covers the first message of a burst (wake + show); `s_glance_lit_ms` - // (already showing, from an earlier message in the same window) covers later - // ones -- update the text and restart the 5 s window instead of dropping - // them, since our own wake already cleared _screen_off by then. + // Master-disabled via Options > Display > "At a glance" + // (touchPrefsGetGlanceEnabled()) skips this entirely. Skipped if DND is on. + // Manually locked is ALSO skipped unless the user opted in via Options > + // Display > "At a glance while locked" (touchPrefsGetGlanceWhenLocked()) + // -- off by default since it's a real trade-off (message text becomes + // readable off a locked device). `_screen_off` covers the first message of + // a burst (wake + show); `s_glance_lit_ms` (already showing, from an + // earlier message in the same window) covers later ones -- update the text + // and restart the 5 s window instead of dropping them, since our own wake + // already cleared _screen_off by then. #if defined(ESP32) + const bool glance_enabled_ok = touchPrefsGetGlanceEnabled(); const bool glance_locked_ok = !_manual_lock || touchPrefsGetGlanceWhenLocked(); #else + const bool glance_enabled_ok = true; const bool glance_locked_ok = !_manual_lock; #endif - if (!dndActive() && glance_locked_ok && (_screen_off || s_glance_lit_ms)) { + if (glance_enabled_ok && !dndActive() && glance_locked_ok && (_screen_off || s_glance_lit_ms)) { const bool was_off = _screen_off; atGlanceShow(thread, body, was_off); // fade in only on the initial reveal of a burst if (was_off) {