feat: app-only settings on the device: privacy, adding contacts, advanced radio

- Settings schema gains Privacy (position in adverts, who can ask for
  status / location / sensors), Adding contacts (all or by type, max
  hops, replace oldest) and Advanced radio (path hash size, double ACK,
  listen before talk, interference threshold, RX boost, RX delay,
  airtime factor); both UIs build their rows from it
- Auto-advert carries position only when Position in adverts is on
- ui-new accordion holds 16 sections and 128 rows
- docs: settings, tools, feature matrix

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-10-10 00:47:03 +02:00
co-authored by Claude Opus 5.5
parent a0a76fbc60
commit 6fba3dfca7
10 changed files with 181 additions and 30 deletions
@@ -22,15 +22,15 @@ public:
enum Result { IGNORED, HANDLED, ACTIVATED };
static const int MAX_SECTIONS = 8; // collapse bitmask is 8 bits
static const int MAX_ROWS = 64;
static const int MAX_SECTIONS = 16; // collapse bitmask is 16 bits
static const int MAX_ROWS = 128;
// (Re)initialise from per-section item counts. `collapsed` sets the initial
// fold state for every section (Settings opens folded, so default true).
void begin(const uint8_t* section_sizes, int section_count, bool collapsed = true) {
_count = section_count > MAX_SECTIONS ? MAX_SECTIONS : section_count;
for (int i = 0; i < _count; i++) _sizes[i] = section_sizes[i];
_collapsed = collapsed ? 0xFF : 0x00;
_collapsed = collapsed ? 0xFFFF : 0x0000;
_sel = 0; _scroll = 0;
rebuild();
}
@@ -68,7 +68,7 @@ public:
// the cursor never jumps to an unrelated row when items appear/disappear.
void toggle(int sec) {
if (sec < 0 || sec >= _count) return;
_collapsed ^= (uint8_t)(1u << sec);
_collapsed ^= (uint16_t)(1u << sec);
rebuild();
for (int i = 0; i < _vis_count; i++)
if (_rows[i].sec == sec && _rows[i].item < 0) { _sel = i; break; }
@@ -77,7 +77,7 @@ public:
private:
uint8_t _sizes[MAX_SECTIONS];
int _count = 0;
uint8_t _collapsed = 0xFF;
uint16_t _collapsed = 0xFFFF;
Row _rows[MAX_ROWS];
int _vis_count = 0;
int _sel = 0, _scroll = 0;
@@ -46,6 +46,7 @@ class SettingsScreen : public UIScreen {
#endif
TX_APC,
SCOPE_NAME,
SCHEMA_RADIO, // the advanced radio options
// System section
SECTION_SYSTEM,
DEVICE_NAME,
@@ -61,7 +62,9 @@ class SettingsScreen : public UIScreen {
KEYBOARD_CARDKB_COMPACT,
#endif
// Contacts section
SECTION_CONTACTS, DM_FILTER, CH_FILTER, ROOM_FILTER, SCHEMA_CONTACTS, PRUNE_NOW,
SECTION_CONTACTS, DM_FILTER, CH_FILTER, ROOM_FILTER, SCHEMA_CONTACTS, PRUNE_NOW, SCHEMA_AUTO_ADD,
// Privacy section: what adverts carry, who may ask for telemetry
SECTION_PRIVACY, SCHEMA_PRIVACY,
// Messages section
SECTION_MESSAGES,
SCHEMA_MESSAGES,
@@ -83,8 +86,8 @@ class SettingsScreen : public UIScreen {
bool _keep_place = false; // set when opening a screen from a row, so Back lands on it
AccordionList _acc;
static const int NUM_SECTIONS = 8;
static const int MAX_PER_SEC = 16;
static const int NUM_SECTIONS = 9;
static const int MAX_PER_SEC = 20;
uint8_t _sec_items[NUM_SECTIONS][MAX_PER_SEC]; // SettingItem per (section, row)
uint8_t _sec_count[NUM_SECTIONS];
uint8_t _sec_header[NUM_SECTIONS]; // the SECTION_* enum for each section
@@ -136,7 +139,8 @@ class SettingsScreen : public UIScreen {
item == SECTION_HOME_PAGES ||
item == SECTION_RADIO || item == SECTION_SYSTEM ||
item == SECTION_KEYBOARD ||
item == SECTION_CONTACTS || item == SECTION_MESSAGES;
item == SECTION_CONTACTS || item == SECTION_MESSAGES ||
item == SECTION_PRIVACY;
}
const char* sectionName(int item) const {
@@ -148,6 +152,7 @@ class SettingsScreen : public UIScreen {
if (item == SECTION_KEYBOARD) return "Keyboard";
if (item == SECTION_CONTACTS) return "Contacts";
if (item == SECTION_MESSAGES) return "Messages";
if (item == SECTION_PRIVACY) return "Privacy";
return "";
}
@@ -178,7 +183,8 @@ class SettingsScreen : public UIScreen {
static bool isSchemaGroup(int item) {
return item == SCHEMA_DISPLAY || item == SCHEMA_SOUND || item == SCHEMA_SYSTEM ||
item == SCHEMA_CONTACTS || item == SCHEMA_MESSAGES;
item == SCHEMA_CONTACTS || item == SCHEMA_MESSAGES || item == SCHEMA_RADIO || item == SCHEMA_AUTO_ADD ||
item == SCHEMA_PRIVACY;
}
// Which schema sections a placeholder stands for.
static bool schemaIn(int group, uint8_t sec) {
@@ -188,6 +194,9 @@ class SettingsScreen : public UIScreen {
case SCHEMA_SOUND: return sec == SEC_SOUND || sec == SEC_QUIET || sec == SEC_SOUND_FOR;
case SCHEMA_SYSTEM: return sec == SEC_POWER || sec == SEC_UNITS;
case SCHEMA_CONTACTS: return sec == SEC_CONTACTS;
case SCHEMA_AUTO_ADD: return sec == SEC_AUTO_ADD;
case SCHEMA_RADIO: return sec == SEC_RADIO_ADV;
case SCHEMA_PRIVACY: return sec == SEC_PRIVACY || sec == SEC_TELEMETRY;
case SCHEMA_MESSAGES: return sec == SEC_MESSAGES;
}
return false;