mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-10-09 03:06:39 +00:00
- Map tools hold trail, live share and arrival alert, with their options (schema page "Map options") behind one button; back returns to the map - Nearby gets an advert button: send now (nearby / everyone) + auto-advert - Home page 3: Repeater, Admin (list of repeaters and room servers), Diagnostics - Settings: display, sound, radio (Bluetooth, WiFi), system, keyboard, contacts & messages; units moved to Display & power - Status bar: Bluetooth, GPS, alarm, mute, then auto-advert, trail, live share, repeater, arrival alert in the accent colour - Fonts: key and route glyphs Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
433 lines
17 KiB
C++
433 lines
17 KiB
C++
#pragma once
|
|
// Remote admin of a repeater / room server (node detail > Admin, or the Home
|
|
// tile's list) -- ui-new's Tools > Admin. The session (login with the saved
|
|
// password, CLI round trips, timeouts, field table) is the UI Core's AdminSession; this draws it:
|
|
// password entry, tabs of fields, and a popup per field -- a switch, a
|
|
// -/+ stepper, a row of choices (SF / bandwidth / coding rate), or a text
|
|
// field with the keyboard (text, frequency, custom command); replies in a popup.
|
|
//
|
|
// Single-TU fragment: included by ui-lvgl/UITask.cpp after ChannelScreen.h.
|
|
|
|
namespace adminview {
|
|
|
|
static uint8_t s_tab = 0;
|
|
static lv_obj_t* s_status = nullptr; // "Logging in..." / "Waiting for reply..."
|
|
static lv_obj_t* s_list = nullptr; // the tab's rows
|
|
static lv_obj_t* s_tabs = nullptr;
|
|
static lv_obj_t* s_pw = nullptr; // password field (NEED_PASSWORD)
|
|
static lv_obj_t* s_kb = nullptr;
|
|
static lv_obj_t* s_val_lbl = nullptr; // stepper value in the popup
|
|
static lv_obj_t* s_choice = nullptr; // SF / BW / CR choices in the popup
|
|
static const admin::Field* s_confirm = nullptr; // Reboot / OTA waiting for "yes"
|
|
static char s_btn_map[24][8];
|
|
static const char* s_map[28];
|
|
|
|
enum : uint8_t { V_MINUS, V_PLUS, V_SAVE, V_TOGGLE, V_CONFIRM, V_CLOSE };
|
|
|
|
static void clear() { s_status = s_list = s_tabs = s_pw = s_kb = s_val_lbl = s_choice = nullptr; }
|
|
|
|
// Choice buttons for a radio field: labels into s_btn_map, map into s_map
|
|
// (a "\n" after every `per_row`), selected index out.
|
|
static int radioChoices(const admin::Field& f, const AdminSession& S, int& per_row) {
|
|
int n = 0, sel = 0, m = 0;
|
|
if (f.kind == admin::K_RADIO_SF) {
|
|
per_row = 8;
|
|
for (int v = 5; v <= 12; v++, n++) { snprintf(s_btn_map[n], 8, "%d", v); if (v == S.radio_sf) sel = n; }
|
|
} else if (f.kind == admin::K_RADIO_CR) {
|
|
per_row = 4;
|
|
for (int v = 5; v <= 8; v++, n++) { snprintf(s_btn_map[n], 8, "4/%d", v); if (v == S.radio_cr) sel = n; }
|
|
} else {
|
|
per_row = 5;
|
|
sel = nearestBwIndex(S.radio_bw);
|
|
for (; n < LORA_BW_OPT_COUNT; n++) snprintf(s_btn_map[n], 8, "%g", (double)LORA_BW_OPTS[n]);
|
|
}
|
|
for (int i = 0; i < n; i++) {
|
|
if (i && i % per_row == 0) s_map[m++] = "\n";
|
|
s_map[m++] = s_btn_map[i];
|
|
}
|
|
s_map[m] = "";
|
|
return sel;
|
|
}
|
|
|
|
} // namespace adminview
|
|
|
|
static void onAdminTab(lv_event_t* e) {
|
|
s_ui->adminTab((int)lv_buttonmatrix_get_selected_button((lv_obj_t*)lv_event_get_target(e)));
|
|
}
|
|
static void onAdminRow(lv_event_t* e) { s_ui->adminRow((int)(uintptr_t)lv_event_get_user_data(e)); }
|
|
static void onAdminValue(lv_event_t* e) { s_ui->adminValue((uint8_t)(uintptr_t)lv_event_get_user_data(e)); }
|
|
static void onAdminChoice(lv_event_t* e) {
|
|
s_ui->adminChoice((int)lv_buttonmatrix_get_selected_button((lv_obj_t*)lv_event_get_target(e)));
|
|
}
|
|
static void onAdminPwKb(lv_event_t* e) { s_ui->adminLogin(lv_event_get_code(e) == LV_EVENT_READY); }
|
|
static void onAdminTextKb(lv_event_t* e) { s_ui->adminTextDone(lv_event_get_code(e) == LV_EVENT_READY); }
|
|
static void onAdminCancelWait(lv_event_t* e) { (void)e; s_ui->adminCancelWait(); }
|
|
static void onOpenAdminPick(lv_event_t* e) { (void)e; s_ui->showAdminPick(); }
|
|
static void onAdminPickRow(lv_event_t* e) { s_ui->adminPick((int)(uintptr_t)lv_event_get_user_data(e)); }
|
|
|
|
namespace adminview {
|
|
static const int PICK_MAX = 64;
|
|
static uint8_t (*s_pick)[PUB_KEY_SIZE] = psramBuf<uint8_t[PUB_KEY_SIZE]>(PICK_MAX);
|
|
}
|
|
|
|
// Home > Admin: every repeater and room server, favourites first (ui-new's
|
|
// Tools > Admin picker); the session is the same as from node detail.
|
|
void UITask::showAdminPick() {
|
|
_screen = SCR_ADMIN_PICK;
|
|
buildAdminPick();
|
|
}
|
|
|
|
void UITask::buildAdminPick() {
|
|
lv_obj_t* body = newScreen("Admin", true);
|
|
int rows = 0;
|
|
for (int pass = 0; pass < 2; pass++) {
|
|
for (int i = 0; i < the_mesh.getNumContacts() && rows < adminview::PICK_MAX; i++) {
|
|
ContactInfo c;
|
|
if (!the_mesh.getContactByIdx(MAX_ANON_CONTACTS + i, c)) continue;
|
|
if (c.type != ADV_TYPE_REPEATER && c.type != ADV_TYPE_ROOM) continue;
|
|
bool fav = contactctl::favourite(c);
|
|
if (fav != (pass == 0)) continue;
|
|
if (!rows) sectionTitle(body, "LOG IN TO");
|
|
char name[48];
|
|
snprintf(name, sizeof(name), "%s%s", fav ? UI_SYMBOL_STAR " " : "", c.name);
|
|
memcpy(adminview::s_pick[rows], c.id.pub_key, PUB_KEY_SIZE);
|
|
listRow(body, name, c.type == ADV_TYPE_ROOM ? "Room server" : "Repeater", onAdminPickRow, (void*)(uintptr_t)rows);
|
|
rows++;
|
|
}
|
|
}
|
|
if (!rows) {
|
|
lv_obj_t* t = label(body, "No repeaters or room servers yet. They show up here once their advert is heard.",
|
|
THEME_FONT_BODY, theme::TEXT_MUTED);
|
|
lv_label_set_long_mode(t, LV_LABEL_LONG_WRAP);
|
|
lv_obj_set_width(t, LV_PCT(100));
|
|
}
|
|
}
|
|
|
|
void UITask::adminPick(int row) {
|
|
if (row < 0 || row >= adminview::PICK_MAX) return;
|
|
openAdmin(adminview::s_pick[row]);
|
|
}
|
|
|
|
// Node detail > Admin.
|
|
void UITask::openAdmin(const uint8_t* pub_key) {
|
|
ContactInfo ci;
|
|
if (!MessageHistory::contactByPrefix(pub_key, ci)) return;
|
|
_core->admin.start(ci);
|
|
_admin_from_pick = _screen == SCR_ADMIN_PICK;
|
|
adminview::s_tab = admin::TAB_SYSTEM;
|
|
_screen = SCR_ADMIN;
|
|
buildAdmin();
|
|
}
|
|
|
|
void UITask::adminLeave() {
|
|
_core->admin.end();
|
|
navClosePopup();
|
|
if (_admin_from_pick) { showAdminPick(); return; }
|
|
_screen = SCR_NODE;
|
|
buildNode();
|
|
}
|
|
|
|
void UITask::buildAdmin() {
|
|
using namespace adminview;
|
|
AdminSession& S = _core->admin;
|
|
lv_obj_t* body = newScreen(S.target().name, true);
|
|
lv_obj_set_style_pad_row(body, 4, 0);
|
|
clear();
|
|
AdminSession::State st = S.state();
|
|
|
|
if (st == AdminSession::NEED_PASSWORD) {
|
|
label(body, "Admin password for this node", THEME_FONT_SMALL, theme::TEXT_MUTED);
|
|
s_pw = wifiField(body, "Password", true);
|
|
lv_obj_remove_event_cb(s_pw, onWifiField);
|
|
lv_textarea_set_max_length(s_pw, 15);
|
|
lv_obj_add_state(s_pw, LV_STATE_FOCUSED);
|
|
s_kb = kb::create(lv_screen_active(), _prefs);
|
|
lv_obj_set_size(s_kb, LV_PCT(100), 124);
|
|
lv_obj_align(s_kb, LV_ALIGN_BOTTOM_MID, 0, 0);
|
|
lv_keyboard_set_textarea(s_kb, s_pw);
|
|
lv_obj_add_event_cb(s_kb, onAdminPwKb, LV_EVENT_READY, NULL);
|
|
lv_obj_add_event_cb(s_kb, onAdminPwKb, LV_EVENT_CANCEL, NULL);
|
|
return;
|
|
}
|
|
if (st == AdminSession::LOGGING_IN || st == AdminSession::IDLE) {
|
|
s_status = label(body, st == AdminSession::LOGGING_IN ? LV_SYMBOL_REFRESH " Logging in..." : "", THEME_FONT_BODY, theme::TEXT);
|
|
return;
|
|
}
|
|
|
|
static const char* TABS[admin::TAB_COUNT + 1];
|
|
for (int i = 0; i < admin::TAB_COUNT; i++) TABS[i] = admin::TAB_LABELS[i];
|
|
TABS[admin::TAB_COUNT] = "";
|
|
s_tabs = segmented(body, TABS, s_tab, LV_PCT(100), 34, theme::SURFACE);
|
|
lv_obj_add_event_cb(s_tabs, onAdminTab, LV_EVENT_VALUE_CHANGED, NULL);
|
|
|
|
s_status = label(body, "", THEME_FONT_SMALL, theme::ACCENT);
|
|
lv_obj_add_flag(s_status, LV_OBJ_FLAG_CLICKABLE); // tap: stop waiting
|
|
lv_obj_add_event_cb(s_status, onAdminCancelWait, LV_EVENT_CLICKED, NULL);
|
|
lv_obj_add_flag(s_status, LV_OBJ_FLAG_HIDDEN);
|
|
|
|
s_list = lv_obj_create(body);
|
|
styleSurface(s_list, theme::BG);
|
|
lv_obj_set_width(s_list, LV_PCT(100));
|
|
lv_obj_set_flex_grow(s_list, 1);
|
|
lv_obj_set_flex_flow(s_list, LV_FLEX_FLOW_COLUMN);
|
|
lv_obj_set_style_pad_row(s_list, theme::GAP, 0);
|
|
lv_obj_set_scrollbar_mode(s_list, LV_SCROLLBAR_MODE_ACTIVE);
|
|
for (int i = 0; i < admin::rowCount(s_tab); i++) {
|
|
const admin::Field& f = admin::field(s_tab, i);
|
|
const char* hint = f.isCustom() ? "Any CLI command" : f.isAction() ? nullptr
|
|
: f.isSetOnly() ? "Set a new one" : "Tap to read and change";
|
|
lv_obj_t* row = listRow(s_list, f.label, hint, onAdminRow, (void*)(uintptr_t)i);
|
|
if (f.isAction() && admin::confirmPrompt(f))
|
|
lv_obj_align(label(row, LV_SYMBOL_WARNING, THEME_FONT_SMALL, theme::TEXT_MUTED), LV_ALIGN_RIGHT_MID, -theme::PAD, 0);
|
|
}
|
|
refreshAdminStatus();
|
|
}
|
|
|
|
void UITask::refreshAdminStatus() {
|
|
using namespace adminview;
|
|
if (!s_status || !s_list) return;
|
|
AdminSession& S = _core->admin;
|
|
bool waiting = S.state() == AdminSession::WAITING;
|
|
if (waiting) {
|
|
lv_label_set_text(s_status, S.fetching() ? LV_SYMBOL_REFRESH " Reading... (tap to stop)"
|
|
: LV_SYMBOL_REFRESH " Waiting for reply... (tap to stop)");
|
|
lv_obj_remove_flag(s_status, LV_OBJ_FLAG_HIDDEN);
|
|
} else {
|
|
lv_obj_add_flag(s_status, LV_OBJ_FLAG_HIDDEN);
|
|
}
|
|
if (waiting) lv_obj_add_state(s_list, LV_STATE_DISABLED); else lv_obj_remove_state(s_list, LV_STATE_DISABLED);
|
|
}
|
|
|
|
void UITask::adminTab(int tab) {
|
|
if (tab < 0 || tab >= admin::TAB_COUNT) return;
|
|
adminview::s_tab = (uint8_t)tab;
|
|
buildAdmin();
|
|
}
|
|
|
|
void UITask::adminLogin(bool ok) {
|
|
using namespace adminview;
|
|
if (!ok) { adminLeave(); return; }
|
|
if (!s_pw) return;
|
|
_core->admin.login(lv_textarea_get_text(s_pw)); // a send failure comes back as LOGIN_FAILED
|
|
buildAdmin();
|
|
}
|
|
|
|
void UITask::adminCancelWait() {
|
|
_core->admin.cancelWait();
|
|
refreshAdminStatus();
|
|
}
|
|
|
|
void UITask::adminRow(int row) {
|
|
using namespace adminview;
|
|
AdminSession& S = _core->admin;
|
|
if (S.state() != AdminSession::READY || row < 0 || row >= admin::rowCount(s_tab)) return;
|
|
const admin::Field& f = admin::field(s_tab, row);
|
|
const char* prompt = admin::confirmPrompt(f);
|
|
if (prompt) { // takes the node out of service for a while: ask first
|
|
s_confirm = &f;
|
|
lv_obj_t* panel = navPopupPanel(prompt, false);
|
|
lv_obj_t* t = label(panel, "It's offline until it comes back.",
|
|
THEME_FONT_SMALL, theme::TEXT_MUTED);
|
|
lv_label_set_long_mode(t, LV_LABEL_LONG_WRAP);
|
|
lv_obj_set_width(t, LV_PCT(100));
|
|
lv_obj_t* b = lv_button_create(panel);
|
|
lv_obj_set_size(b, LV_PCT(100), 40);
|
|
lv_obj_set_style_shadow_width(b, 0, 0);
|
|
lv_obj_set_style_radius(b, theme::RADIUS, 0);
|
|
lv_obj_set_style_bg_color(b, lv_color_hex(theme::FAIL), 0);
|
|
lv_obj_add_event_cb(b, onAdminValue, LV_EVENT_CLICKED, (void*)(uintptr_t)V_CONFIRM);
|
|
lv_obj_center(label(b, f.label, THEME_FONT_BODY, theme::TEXT));
|
|
return;
|
|
}
|
|
S.run(f);
|
|
refreshAdminStatus();
|
|
}
|
|
|
|
// Popup buttons: stepper, save, confirm.
|
|
void UITask::adminValue(uint8_t act) {
|
|
using namespace adminview;
|
|
AdminSession& S = _core->admin;
|
|
const admin::Field* f = S.currentField();
|
|
switch (act) {
|
|
case V_MINUS: case V_PLUS:
|
|
S.stepValue(act == V_PLUS ? 1 : -1);
|
|
if (s_val_lbl) lv_label_set_text_fmt(s_val_lbl, "%d", (int)S.value);
|
|
break;
|
|
case V_TOGGLE:
|
|
S.stepValue(1);
|
|
break;
|
|
case V_SAVE:
|
|
navClosePopup();
|
|
if (f) S.sendValue();
|
|
refreshAdminStatus();
|
|
break;
|
|
case V_CONFIRM:
|
|
navClosePopup();
|
|
if (s_confirm) S.run(*s_confirm);
|
|
s_confirm = nullptr;
|
|
refreshAdminStatus();
|
|
break;
|
|
default:
|
|
navClosePopup();
|
|
break;
|
|
}
|
|
}
|
|
|
|
void UITask::adminChoice(int idx) {
|
|
AdminSession& S = _core->admin;
|
|
const admin::Field* f = S.currentField();
|
|
if (!f || idx < 0) return;
|
|
if (f->kind == admin::K_RADIO_SF) S.radio_sf = (uint8_t)(5 + idx);
|
|
else if (f->kind == admin::K_RADIO_CR) S.radio_cr = (uint8_t)(5 + idx);
|
|
else if (f->kind == admin::K_RADIO_BW && idx < LORA_BW_OPT_COUNT) S.radio_bw = LORA_BW_OPTS[idx];
|
|
}
|
|
|
|
static lv_obj_t* adminPopupButton(lv_obj_t* parent, const char* text, uint8_t act, bool accent) {
|
|
lv_obj_t* b = lv_button_create(parent);
|
|
lv_obj_set_height(b, 40);
|
|
lv_obj_set_flex_grow(b, 1);
|
|
lv_obj_set_style_shadow_width(b, 0, 0);
|
|
lv_obj_set_style_radius(b, theme::RADIUS, 0);
|
|
lv_obj_set_style_bg_color(b, lv_color_hex(accent ? theme::ACCENT_DIM : theme::SURFACE), 0);
|
|
lv_obj_add_event_cb(b, onAdminValue, LV_EVENT_CLICKED, (void*)(uintptr_t)act);
|
|
lv_obj_center(label(b, text, act == adminview::V_MINUS || act == adminview::V_PLUS ? THEME_FONT_LARGE : THEME_FONT_BODY, theme::TEXT));
|
|
return b;
|
|
}
|
|
|
|
static lv_obj_t* adminRowBox(lv_obj_t* parent) {
|
|
lv_obj_t* r = lv_obj_create(parent);
|
|
styleSurface(r, theme::BG);
|
|
lv_obj_remove_flag(r, LV_OBJ_FLAG_SCROLLABLE);
|
|
lv_obj_set_size(r, LV_PCT(100), LV_SIZE_CONTENT);
|
|
lv_obj_set_flex_flow(r, LV_FLEX_FLOW_ROW);
|
|
lv_obj_set_flex_align(r, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
|
lv_obj_set_style_pad_column(r, theme::GAP, 0);
|
|
return r;
|
|
}
|
|
|
|
// A typed value arrived: its editor.
|
|
void UITask::adminValuePopup() {
|
|
using namespace adminview;
|
|
AdminSession& S = _core->admin;
|
|
const admin::Field* f = S.currentField();
|
|
if (!f) return;
|
|
if (f->kind == admin::K_RADIO_FREQ) { // typed, like text
|
|
char fs[16];
|
|
snprintf(fs, sizeof(fs), "%.3f", S.radio_freq);
|
|
adminTextPopup(fs, true);
|
|
return;
|
|
}
|
|
lv_obj_t* panel = navPopupPanel(f->label, false);
|
|
s_val_lbl = s_choice = nullptr;
|
|
if (f->kind == admin::K_ONOFF) {
|
|
lv_obj_t* r = adminRowBox(panel);
|
|
label(r, "Off", THEME_FONT_BODY, theme::TEXT_MUTED);
|
|
lv_obj_t* sw = lv_switch_create(r);
|
|
lv_obj_set_size(sw, 56, 28);
|
|
if (S.value != 0) lv_obj_add_state(sw, LV_STATE_CHECKED);
|
|
lv_obj_add_event_cb(sw, onAdminValue, LV_EVENT_VALUE_CHANGED, (void*)(uintptr_t)V_TOGGLE);
|
|
label(r, "On", THEME_FONT_BODY, theme::TEXT_MUTED);
|
|
} else if (f->kind == admin::K_NUMBER) {
|
|
lv_obj_t* r = adminRowBox(panel);
|
|
adminPopupButton(r, LV_SYMBOL_MINUS, V_MINUS, false);
|
|
s_val_lbl = label(r, "", THEME_FONT_LARGE, theme::TEXT);
|
|
lv_obj_set_width(s_val_lbl, 80);
|
|
lv_obj_set_style_text_align(s_val_lbl, LV_TEXT_ALIGN_CENTER, 0);
|
|
lv_label_set_text_fmt(s_val_lbl, "%d", (int)S.value);
|
|
adminPopupButton(r, LV_SYMBOL_PLUS, V_PLUS, false);
|
|
} else { // SF / bandwidth / coding rate
|
|
int per_row = 1;
|
|
int sel = radioChoices(*f, S, per_row);
|
|
int rows = f->kind == admin::K_RADIO_BW ? 2 : 1;
|
|
s_choice = segmented(panel, s_map, sel, LV_PCT(100), rows * 34 + (rows - 1) * 4, theme::SURFACE);
|
|
lv_obj_add_event_cb(s_choice, onAdminChoice, LV_EVENT_VALUE_CHANGED, NULL);
|
|
}
|
|
if (f->isRadio()) {
|
|
lv_obj_t* note = label(panel, "A mismatch cuts the node off.", THEME_FONT_SMALL, theme::TEXT_MUTED);
|
|
lv_label_set_long_mode(note, LV_LABEL_LONG_WRAP);
|
|
lv_obj_set_width(note, LV_PCT(100));
|
|
}
|
|
lv_obj_t* r = adminRowBox(panel);
|
|
adminPopupButton(r, LV_SYMBOL_OK " Save", V_SAVE, true);
|
|
}
|
|
|
|
// Text to edit (a fetched value, a new password, a custom command, the frequency).
|
|
void UITask::adminTextPopup(const char* text, bool digits) {
|
|
AdminSession& S = _core->admin;
|
|
const admin::Field* f = S.currentField();
|
|
lv_obj_t* panel = navPopupPanel(f ? f->label : "Command", false);
|
|
lv_obj_align(panel, LV_ALIGN_TOP_MID, 0, theme::STATUS_H + 4); // above the keyboard
|
|
_nav_ta = textField(panel);
|
|
lv_textarea_set_max_length(_nav_ta, 150);
|
|
if (digits) lv_textarea_set_accepted_chars(_nav_ta, "0123456789.");
|
|
if (f && f->isCustom()) lv_textarea_set_placeholder_text(_nav_ta, "e.g. get advert.interval");
|
|
lv_textarea_set_text(_nav_ta, text);
|
|
lv_obj_add_state(_nav_ta, LV_STATE_FOCUSED);
|
|
_nav_kb = kb::create(_nav_overlay, _prefs);
|
|
lv_obj_set_size(_nav_kb, LV_PCT(100), 124);
|
|
lv_obj_align(_nav_kb, LV_ALIGN_BOTTOM_MID, 0, 0);
|
|
lv_keyboard_set_textarea(_nav_kb, _nav_ta);
|
|
if (digits) kb::apply(_nav_kb, kb::L_SYM);
|
|
lv_obj_add_event_cb(_nav_kb, onAdminTextKb, LV_EVENT_READY, NULL);
|
|
lv_obj_add_event_cb(_nav_kb, onAdminTextKb, LV_EVENT_CANCEL, NULL);
|
|
}
|
|
|
|
void UITask::adminTextDone(bool ok) {
|
|
AdminSession& S = _core->admin;
|
|
const admin::Field* f = S.currentField();
|
|
if (ok && _nav_ta && f) {
|
|
const char* t = lv_textarea_get_text(_nav_ta);
|
|
if (f->kind == admin::K_RADIO_FREQ) {
|
|
float v = strtof(t, nullptr);
|
|
if (v < f->min_val || v > f->max_val) { showToast("Frequency out of range"); return; }
|
|
S.radio_freq = v;
|
|
S.sendValue();
|
|
} else {
|
|
S.sendSet(t);
|
|
}
|
|
}
|
|
navClosePopup();
|
|
refreshAdminStatus();
|
|
}
|
|
|
|
void UITask::adminReplyPopup(const char* text) {
|
|
bool long_text = strlen(text) > 90; // e.g. "neighbors": full height, scrolls
|
|
lv_obj_t* panel = navPopupPanel("Reply", long_text);
|
|
lv_obj_t* box = panel;
|
|
if (long_text) {
|
|
box = lv_obj_create(panel);
|
|
styleSurface(box, theme::BG);
|
|
lv_obj_set_width(box, LV_PCT(100));
|
|
lv_obj_set_flex_grow(box, 1);
|
|
lv_obj_set_scrollbar_mode(box, LV_SCROLLBAR_MODE_ACTIVE);
|
|
}
|
|
lv_obj_t* l = label(box, text[0] ? text : "(empty)", THEME_FONT_BODY, theme::TEXT);
|
|
lv_label_set_long_mode(l, LV_LABEL_LONG_WRAP);
|
|
lv_obj_set_width(l, LV_PCT(100));
|
|
lv_obj_t* r = adminRowBox(panel);
|
|
adminPopupButton(r, "OK", adminview::V_CLOSE, false);
|
|
}
|
|
|
|
// From loop() on SCR_ADMIN: the session's results.
|
|
void UITask::adminPoll() {
|
|
AdminSession& S = _core->admin;
|
|
const admin::Field* f = S.currentField();
|
|
switch (S.take()) {
|
|
case AdminSession::NONE: return;
|
|
case AdminSession::LOGGED_IN: showToast("Logged in as admin", 1500); buildAdmin(); break;
|
|
case AdminSession::NOT_ADMIN: showToast("Not an admin on this node"); adminLeave(); break;
|
|
case AdminSession::LOGIN_FAILED: showToast("Login failed - wrong password?", 3000); adminLeave(); break;
|
|
case AdminSession::LOGIN_TIMEOUT: showToast("No answer to the login - out of range?"); adminLeave(); break;
|
|
case AdminSession::SEND_FAILED: showToast("Send failed"); break;
|
|
case AdminSession::FETCH_FAILED: showToast("Couldn't read the value; try again"); break;
|
|
case AdminSession::TIMEOUT: showToast("No reply (timeout)"); break;
|
|
case AdminSession::REPLY: adminReplyPopup(S.reply()); break;
|
|
case AdminSession::VALUE_READY: adminValuePopup(); break;
|
|
case AdminSession::TEXT_READY:
|
|
if (f && f->get_cmd && !S.text()[0]) showToast("Couldn't read the value; enter a new one");
|
|
adminTextPopup(S.text(), false);
|
|
break;
|
|
}
|
|
refreshAdminStatus();
|
|
}
|