refactor(admin): make Admin remote-only, move device settings to Settings

A device administering itself via the Admin tool read as awkward, so the
local mode is gone and its options relocated to Settings.

- AdminScreen is remote-only: drop the CHOOSER phase, _local_mode, the
  LocalField/LocalKind model and activateLocalField/commitLocalField.
  Phases collapse to LOGIN/COMMAND/REPLY, entered only via startFor().
- Tools > Admin opens straight to the Nodes picker (ACT_ADMIN ->
  pickAdminTarget()). Backing out of a command screen returns to that
  picker; the picker's Cancel returns to Tools. gotoAdminScreen() removed.
- Settings > System gains Name (keyboard-edited node_name) and Reboot
  (action row, placed last so it isn't the default cursor). Radio + TX
  power were already in Settings; Send advert is the home ADVERT page.

Docs: tools_screen.md Admin section rewritten remote-only; settings_screen.md
System table gains Name + Reboot.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-07-13 09:10:24 +02:00
co-authored by Claude Opus 4.8
parent 9bfccf4cf4
commit 56aaff1f76
8 changed files with 110 additions and 283 deletions
@@ -64,9 +64,11 @@ class SettingsScreen : public UIScreen {
TX_APC,
// System section
SECTION_SYSTEM,
DEVICE_NAME,
TIMEZONE,
LOW_BAT,
UNITS,
REBOOT,
// Keyboard section
SECTION_KEYBOARD,
KEYBOARD_TYPE,
@@ -554,6 +556,12 @@ class SettingsScreen : public UIScreen {
display.print("Units");
display.setCursor(valCol(display), y);
display.print((p && p->units_imperial) ? "Imperial" : "Metric");
} else if (item == DEVICE_NAME) {
display.print("Name");
int vx = valCol(display);
display.drawTextEllipsized(vx, y, display.width() - vx - _reserve, the_mesh.getNodeName());
} else if (item == REBOOT) {
display.print("Reboot"); // action row: Enter reboots this device
} else if (item == KEYBOARD_TYPE) {
display.print("Type");
display.setCursor(valCol(display), y);
@@ -632,6 +640,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
KeyboardWidget* _kb;
// Radio preset picker — names are too long for the value column, so Enter on
@@ -654,6 +663,7 @@ public:
void onShow() override {
_dirty = false;
_edit_name = false;
resetList();
_editor.freq.active = false;
}
@@ -661,7 +671,7 @@ public:
int render(DisplayDriver& display) override {
display.setTextSize(1);
if (_edit_slot >= 0 || _picker.saving) {
if (_edit_slot >= 0 || _edit_name || _picker.saving) {
return _kb->render(display);
}
@@ -708,6 +718,22 @@ public:
return true;
}
// Keyboard editing mode for the device name
if (_edit_name) {
auto res = _kb->handleInput(c);
if (res == KeyboardWidget::DONE) {
if (p) {
strncpy(p->node_name, _kb->buf, sizeof(p->node_name) - 1);
p->node_name[sizeof(p->node_name) - 1] = '\0';
_dirty = true; // savePrefsIfDirty on exit; getNodeName()/self-advert read node_name live
}
_edit_name = false;
} else if (res == KeyboardWidget::CANCELLED) {
_edit_name = false;
}
return true;
}
// Digit-by-digit Freq editor
if (_editor.active()) {
if (_editor.handleFreqInput(c) && p) { _task->applyRadioParams(); _dirty = true; }
@@ -886,6 +912,18 @@ public:
_dirty = true;
return true;
}
if (_selected == DEVICE_NAME && p && enter) {
_edit_name = true;
_kb->begin(the_mesh.getNodeName(), (int)sizeof(p->node_name) - 1);
_kb->clearPlaceholders(); // a device name is literal, not a message
return true;
}
if (_selected == REBOOT && enter) {
_task->savePrefsIfDirty(_dirty); // don't lose pending edits across the restart
_task->showAlert("Rebooting...", 800);
board.reboot();
return true;
}
if (_selected == KEYBOARD_TYPE && p && (left || right || enter)) {
p->keyboard_type ^= 1;
_dirty = true;