From 06e1dcdbfc44a3c34a5945a12da397631ed1a9af Mon Sep 17 00:00:00 2001 From: 3urobeat Date: Sun, 6 Sep 2026 00:26:28 +0200 Subject: [PATCH] feat: Add lockpass setting to configure lock screen password --- examples/companion_radio/DataStore.cpp | 10 ++++- examples/companion_radio/NodePrefs.h | 11 +++++- .../companion_radio/ui-new/SettingsScreen.h | 37 ++++++++++++++++++- 3 files changed, 53 insertions(+), 5 deletions(-) diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index da67ecb0..ee714f89 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -90,7 +90,7 @@ void DataStore::begin() { #include #elif defined(EXTRAFS) #include - #else + #else #include #endif #endif @@ -622,6 +622,11 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no rd(&_prefs.loc_share_duration_idx, sizeof(_prefs.loc_share_duration_idx)); if (_prefs.loc_share_duration_idx >= NodePrefs::LOC_SHARE_DURATION_COUNT) _prefs.loc_share_duration_idx = 0; + // append the lock-screen password. Should be empty by default + // since struct was zero initialized in begin(), meaning password is disabled + rd(_prefs.lock_screen_password, sizeof(_prefs.lock_screen_password)); + _prefs.lock_screen_password[sizeof(_prefs.lock_screen_password) - 1] = '\0'; + // Schema sentinel: bumped on layout changes. Mismatch means an older file // (or a different schema); rd() and the clamps above already keep every // field within its valid range regardless, so we just log it here — @@ -819,6 +824,7 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_ file.write((uint8_t *)&_prefs.contact_expiry_idx, sizeof(_prefs.contact_expiry_idx)); file.write((uint8_t *)&_prefs.loc_share_scope, sizeof(_prefs.loc_share_scope)); file.write((uint8_t *)&_prefs.loc_share_duration_idx, sizeof(_prefs.loc_share_duration_idx)); + file.write((uint8_t *)_prefs.lock_screen_password, sizeof(_prefs.lock_screen_password)); // Tail sentinel — must be last. See NodePrefs::SCHEMA_SENTINEL. Its write is // the one we check: once the flash fills, writes return 0, so a good @@ -1360,7 +1366,7 @@ bool DataStore::deleteBlobByKey(const uint8_t key[], int key_len) { makeBlobPath(key, key_len, path, sizeof(path)); _fs->remove(path); - + return true; // return true even if file did not exist } #endif diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index 8ee65e4c..8dbc7ba6 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -536,6 +536,11 @@ struct NodePrefs { // persisted to file // ── Custom messages ──────────────────────────────────────────────────── char custom_msgs[10][140]; // user-defined quick messages (supports {loc}, {time}) + // Lock-screen password, empty by default. If set, a password is required to + // unlock the lock screen. + static const uint8_t LOCK_PASSWORD_MAX_LEN = 32; + char lock_screen_password[LOCK_PASSWORD_MAX_LEN]; + // Single source of truth for the live-share option tables (shared by the Map // UI labels and the auto-send engine in UITask). static const uint8_t LOC_SHARE_MOVE_COUNT = 4; @@ -623,7 +628,7 @@ struct NodePrefs { // persisted to file // repeat_* fields) instead of at the tail, which shifted every field after // them by 25 bytes when loading an older file. Never released, but a dev // build wrote it, so the number must not be reused for anything else. - static const uint32_t SCHEMA_SENTINEL = 0xC0DE002E; + static const uint32_t SCHEMA_SENTINEL = 0xC0DE002F; // Bit-index for each home page. Used by page_order (entries store bit+1) and // by home_pages_mask. Single source of truth — both HomeScreen::pageBit/bitToPage @@ -763,6 +768,7 @@ struct NodePrefs { // persisted to file // msg_wake_screen_off (0xC0DE002A) landed in the 1 byte of padding the // 0xC0DE0029 bump left over -- confirmed via a real sim_companion_radio // (native) build, sizeof unchanged at 2760. + // repeat_extra_scope_mask + ch_scope_idx[64] (0xC0DE002B) added 64 bytes, // not 66 -- the struct had 2 bytes of spare tail padding left over from an // earlier bump -- confirmed via a real sim_companion_radio (native) build @@ -777,7 +783,8 @@ struct NodePrefs { // persisted to file // WioTrackerL1_companion_solo_dual (nRF52/ARM) and Heltec_v3_companion_radio_ble // (ESP32) builds, sizeof unchanged at 2824. loc_share_duration_idx (0xC0DE002E) // likewise (sim build; see the check below). -static_assert(sizeof(NodePrefs) == 2824, +// 0xC0DE002F 32 byte bump for lock_screen_password +static_assert(sizeof(NodePrefs) == 2856, "NodePrefs layout changed — sync DataStore save/load + clamp, bump " "SCHEMA_SENTINEL, then update this size (see steps above)."); diff --git a/examples/companion_radio/ui-new/SettingsScreen.h b/examples/companion_radio/ui-new/SettingsScreen.h index 9bb7bb14..2ae38c42 100644 --- a/examples/companion_radio/ui-new/SettingsScreen.h +++ b/examples/companion_radio/ui-new/SettingsScreen.h @@ -22,6 +22,7 @@ class SettingsScreen : public UIScreen { AUTO_OFF, #endif AUTO_LOCK, + LOCK_PASSWORD, BATT_DISPLAY, #if FEAT_CLOCK_SECONDS_SETTING CLOCK_SECONDS, @@ -589,6 +590,10 @@ class SettingsScreen : public UIScreen { display.print("Auto lock"); display.setCursor(valCol(display), y); display.print((p && p->auto_lock) ? "ON" : "OFF"); + } else if (item == LOCK_PASSWORD) { + display.print("LockPass"); + display.setCursor(valCol(display), y); + display.print((p && p->lock_screen_password[0]) ? "ON" : "OFF"); } else if (item == TIMEZONE) { display.print("Time zone"); char buf[8]; @@ -715,6 +720,7 @@ class SettingsScreen : public UIScreen { // Keyboard state for editing message slots int _edit_slot = -1; // -1 = not editing, 0..9 = slot being edited bool _edit_name = false; // editing DEVICE_NAME via the keyboard + bool _edit_lock_pass = false; // editing the lock-screen password via the keyboard KeyboardWidget* _kb; // Scope list management (SCOPE_NAME row -> a full-screen add/rename/ @@ -786,6 +792,7 @@ public: _scope_action_menu.active = false; _scope_delete_confirm_active = false; _prune_confirm.active = false; + _edit_lock_pass = false; resetList(); _editor.freq.active = false; } @@ -793,7 +800,7 @@ public: int render(DisplayDriver& display) override { display.setTextSize(1); - if (_edit_slot >= 0 || _edit_name || _scope_rename_idx != -2 || _picker.saving) { + if (_edit_slot >= 0 || _edit_name || _edit_lock_pass || _scope_rename_idx != -2 || _picker.saving) { return _kb->render(display); } @@ -861,6 +868,22 @@ public: return true; } + // Keyboard editing mode for the lock-screen password + if (_edit_lock_pass) { + auto res = _kb->handleInput(c); + if (res == KeyboardWidget::DONE) { + if (p) { + strncpy(p->lock_screen_password, _kb->buf, sizeof(p->lock_screen_password) - 1); + p->lock_screen_password[sizeof(p->lock_screen_password) - 1] = '\0'; + _dirty = true; // savePrefsIfDirty persists new password + } + _edit_lock_pass = false; + } else if (res == KeyboardWidget::CANCELLED) { + _edit_lock_pass = false; + } + return true; + } + // Keyboard editing mode for adding/renaming a scope-list entry if (_scope_rename_idx != -2) { auto res = _kb->handleInput(c); @@ -1111,6 +1134,18 @@ public: _dirty = true; return true; } + // LockPass: Clear password if defined or get input from keyboard + if (_selected == LOCK_PASSWORD && p && enter) { + if (p->lock_screen_password[0]) { + p->lock_screen_password[0] = '\0'; + _dirty = true; + } else { + _edit_lock_pass = true; + _kb->begin("", (int)sizeof(p->lock_screen_password) - 1); + _kb->clearPlaceholders(); // a password is literal, not a template message + } + return true; + } if (_selected == TIMEZONE && p) { if (right && p->tz_offset_hours < 14) { p->tz_offset_hours++; _dirty = true; return true; } if (left && p->tz_offset_hours > -12) { p->tz_offset_hours--; _dirty = true; return true; }