mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-11-02 10:51:57 +00:00
feat(l2): settings put in one consistent order and look
Settings: Name and Radio first, then Bluetooth, WiFi and GPS side by side (a tap on each row opens its details: the pairing PIN, the networks, the GPS screen with a new Options tab for power saving and the fixed position), then Device, Mesh & data (Adverts & privacy now holds Auto-advert) and System. Time & units, Power as one group. Row symbols get a column of their own, value labels are as wide as their text, group headings are uppercase everywhere, the auto-add node types show only with "By type". SOS in three groups, WiFi with its switch, Map tools with the layers (Packet flow among them) under the trail. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -51,7 +51,7 @@ adverts solid, direct messages long dashes, channels short dashes, the rest
|
||||
dash and dot.
|
||||
|
||||
> [!NOTE]
|
||||
> **Wio Tracker L2:** **Packet flow** is in the map's options (On the map).
|
||||
> **Wio Tracker L2:** **Packet flow** is in the map's tools, under Layers.
|
||||
> Every packet heard shows. One that can be placed runs along its line as a
|
||||
> dot and fades. One that can't (its sender or repeaters have no position)
|
||||
> is a ring around you. Colours by kind: adverts blue, direct messages
|
||||
|
||||
+17
-11
@@ -107,19 +107,25 @@ if your mesh agrees on it.
|
||||
| **Airtime** | The pause after sending, as a multiple of the time on air |
|
||||
|
||||
> [!NOTE]
|
||||
> **Wio Tracker L2:** one list of pages:
|
||||
> **Wio Tracker L2:** one list of pages, the same pieces everywhere: a
|
||||
> switch turns a thing on and off, a chevron opens its page, and a page with
|
||||
> one group has no heading over it.
|
||||
>
|
||||
> - At the top: **Name** and **Radio** (with **Advanced** at the bottom).
|
||||
> - **Connections**: Bluetooth, WiFi and GPS, their switches side by side.
|
||||
> A tap on a row opens its details: Bluetooth the **Pairing PIN**, WiFi the
|
||||
> saved networks, GPS the satellites and an **Options** tab with GPS power
|
||||
> saving and the **Fixed position**.
|
||||
> - **Device**: Display (including the lock, **Tap to wake**, the **Theme**,
|
||||
> dark or light, which restarts the device, and the accent colour), Power
|
||||
> (battery, GPS, and the **Battery curve** as a chart whose points you
|
||||
> drag), Sound (with the melody editor), Keyboard, Messages & contacts.
|
||||
> - **Connections**: Radio (with **Advanced** at the bottom), Privacy,
|
||||
> Bluetooth, WiFi, GPS. Adding contacts is in Messages & contacts.
|
||||
> - **Map & data**: Map (trail, live sharing and arrival alert options),
|
||||
> Storage.
|
||||
> - **System**: Name, Time, Units, Firmware update, About; Reboot, Power off
|
||||
> and Factory reset. Privacy holds **Fixed position** and, under
|
||||
> Bluetooth, the **Pairing PIN**.
|
||||
> dark or light, which restarts the device, and the accent colour), Sound
|
||||
> (with the melody editor), Power (battery, and the **Battery curve** as a
|
||||
> chart whose points you drag), Time & units, Keyboard.
|
||||
> - **Mesh & data**: Messages & contacts (adding contacts too; the node types
|
||||
> show only with **By type**), Map (trail, live sharing and arrival alert
|
||||
> options), Adverts & privacy (**Auto-advert** with the rest of what an
|
||||
> advert carries), Storage.
|
||||
> - **System**: Firmware update, About; then Reboot, Power off and Factory
|
||||
> reset.
|
||||
|
||||
## Console
|
||||
|
||||
|
||||
+8
-3
@@ -215,6 +215,9 @@ your position if Settings › Privacy › **Advert pos** is on), so others see y
|
||||
and Settings › Sound › **AD sound** set, each device beeps when it hears the
|
||||
other: a hands-free "still in range".
|
||||
|
||||
> **Wio Tracker L2:** in Settings › Adverts & privacy, and in the Advert
|
||||
> popup of the map and Nodes.
|
||||
|
||||
## Repeater
|
||||
|
||||
**Tools › Repeater** makes the device relay other people's packets while it
|
||||
@@ -277,10 +280,12 @@ locator) keeps it on.
|
||||
> [!NOTE]
|
||||
> **Wio Tracker L2:** the Diagnostics app, with a **Noise** tab that
|
||||
> measures the noise floor over time and history charts you can touch for the
|
||||
> value at that moment, and the **GPS** app, on three tabs: **Sky** (a plot
|
||||
> value at that moment, and the **GPS** app, on four tabs: **Sky** (a plot
|
||||
> of the satellites and the fix), **Signal** (a bar per satellite with its
|
||||
> strength, and the constellations) and **Details** (position, precision,
|
||||
> time to first fix and what the receiver reports).
|
||||
> strength, and the constellations), **Details** (position, precision,
|
||||
> time to first fix and what the receiver reports) and **Options** (GPS power
|
||||
> saving and the **Fixed position**, which stands in for the GPS while it's
|
||||
> off).
|
||||
> Your contact's QR code and the manual's are in Settings › About.
|
||||
|
||||
## GPIO
|
||||
|
||||
@@ -241,7 +241,7 @@ static const Setting ALL[] = {
|
||||
#endif
|
||||
IDX("Auto-pause", "Pause when standing still", SEC_TRAIL, trail_autopause_idx,
|
||||
NodePrefs::TRAIL_AUTOPAUSE_COUNT, optAutoPause, nullptr),
|
||||
SW("Save on low battery", "Save it on low battery", SEC_TRAIL, trail_autosave_lowbatt, nullptr),
|
||||
SW("Save on low battery", "The trail, before the battery runs out", SEC_TRAIL, trail_autosave_lowbatt, nullptr),
|
||||
IDX("Waypoint averaging", "Average fixes when marking", SEC_TRAIL, gps_avg_idx,
|
||||
NodePrefs::GPS_AVG_COUNT, optGpsAvg, nullptr),
|
||||
|
||||
@@ -262,7 +262,7 @@ static const Setting ALL[] = {
|
||||
NodePrefs::LOCATOR_MODE_COUNT, optLocMode, rearmLocator),
|
||||
SW("Proximity beeper", "Ticks faster when closer", SEC_LOCATOR, locator_beeper, nullptr),
|
||||
|
||||
SW("Packet flow", "What the radio hears, as lines between nodes", SEC_MAP_VIEW, flow_show, nullptr),
|
||||
SW("Packet flow", "What the radio hears, node to node", SEC_MAP_VIEW, flow_show, nullptr),
|
||||
|
||||
MAP("Auto-advert", "Your advert, on a timer", SEC_ADVERT, advert_auto_interval_sec, AUTO_ADVERT,
|
||||
optAutoAdvert, nullptr),
|
||||
|
||||
@@ -195,9 +195,9 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
// Settings > Privacy > Position: where adverts say you are while the GPS is
|
||||
// off -- the sensor manager's node_lat / node_lon, saved with the prefs as
|
||||
// the app's CMD_SET_ADVERT_LATLON does. With the GPS on, each fix overwrites it.
|
||||
// The fixed position (L2: GPS > Options): where adverts say you are while the
|
||||
// GPS is off -- the sensor manager's node_lat / node_lon, saved with the prefs
|
||||
// as the app's CMD_SET_ADVERT_LATLON does. With the GPS on, each fix overwrites it.
|
||||
bool fixedPosition(int32_t& lat, int32_t& lon) const {
|
||||
lat = (int32_t)lround(_sensors->node_lat * 1e6);
|
||||
lon = (int32_t)lround(_sensors->node_lon * 1e6);
|
||||
@@ -209,9 +209,9 @@ public:
|
||||
the_mesh.savePrefs();
|
||||
}
|
||||
|
||||
// Settings > Privacy > Bluetooth PIN: NodePrefs::ble_pin, six digits, or 0
|
||||
// for a new random one each start (shown on screen). Used from the next
|
||||
// start, like the app's CMD_SET_DEVICE_PIN.
|
||||
// The Bluetooth PIN (L2: a tap on Settings > Bluetooth): NodePrefs::ble_pin,
|
||||
// six digits, or 0 for a new random one each start (shown on screen). Used
|
||||
// from the next start, like the app's CMD_SET_DEVICE_PIN.
|
||||
static bool validBlePin(uint32_t pin) { return pin == 0 || (pin >= 100000 && pin <= 999999); }
|
||||
bool setBlePin(uint32_t pin) {
|
||||
if (!_prefs || !validBlePin(pin)) return false;
|
||||
|
||||
@@ -87,7 +87,7 @@ static void botShowAllowed(NodePrefs* p) {
|
||||
if (!off || !botview::s_allowed_lbl) continue;
|
||||
char val[16];
|
||||
botcmd::shown(*off, botcfg::actionsOn(p, k), val, sizeof(val));
|
||||
lv_label_set_text(botview::s_allowed_lbl, val);
|
||||
pickSet(botview::s_allowed_lbl, val);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
namespace gpsview {
|
||||
|
||||
enum : uint8_t { TAB_SKY, TAB_SIGNAL, TAB_DETAILS, TAB_COUNT };
|
||||
enum : uint8_t { TAB_SKY, TAB_SIGNAL, TAB_DETAILS, TAB_OPTIONS, TAB_COUNT };
|
||||
static uint8_t s_tab = TAB_SKY; // kept across visits
|
||||
|
||||
static lv_obj_t* s_sky = nullptr; // sky plot (drawn)
|
||||
@@ -258,7 +258,7 @@ void UITask::buildGps() {
|
||||
for (lv_obj_t*& p : s_sys_pill) p = nullptr;
|
||||
lv_obj_set_style_pad_row(body, 6, 0);
|
||||
{ // the tabs first under the title bar, as on every screen with tabs
|
||||
static const char* TABS[] = { "Sky", "Signal", "Details", "" };
|
||||
static const char* TABS[] = { "Sky", "Signal", "Details", "Options", "" };
|
||||
lv_obj_t* tabs = segmented(body, TABS, s_tab, LV_PCT(100), theme::TAB_H, theme::SURFACE);
|
||||
lv_obj_add_event_cb(tabs, onGpsTab, LV_EVENT_VALUE_CHANGED, NULL);
|
||||
}
|
||||
@@ -351,6 +351,13 @@ void UITask::buildGps() {
|
||||
s_sys_pill[k] = p;
|
||||
}
|
||||
noteLabel(body, "Solid = used in the fix. 30+ is strong; below 25, a fix is unlikely.");
|
||||
} else if (s_tab == TAB_OPTIONS) { // the GPS's own settings, in one place
|
||||
lv_obj_t* g = group(body, nullptr);
|
||||
schemaRow(g, SETTING(gps_interval));
|
||||
char sub[40];
|
||||
ownPositionText(sub, sizeof(sub));
|
||||
listRow(g, "Fixed position", sub, onOwnPos, NULL);
|
||||
groupNote(body, "The fixed position stands in for the GPS while it's off: on the map and in adverts.");
|
||||
} else { // Details: a row once the receiver reports it
|
||||
lv_obj_t* g = infoCard(body);
|
||||
s_d_fix = infoRow(g, "Fix", "");
|
||||
|
||||
@@ -1027,6 +1027,23 @@ void UITask::navToolsPopup(bool back) {
|
||||
listRow(group(list, nullptr), "Trail options", "Point spacing, auto-pause, low battery", onNavTool,
|
||||
(void*)(uintptr_t)navmap::TL_OPT_TRAIL);
|
||||
|
||||
// What the map shows, right under the trail: used more than what follows.
|
||||
lv_obj_t* g = group(list, "LAYERS");
|
||||
choiceRow(g, "Map", "Topo, light, dark or satellite", mapview::LAYER_OPTS, mapview::s_layer, onMapLayer);
|
||||
lv_obj_t* tsw = switchRow(g, "Hiking trails", "Marked routes in their colours", nullptr);
|
||||
if (lvport::trailsOn()) lv_obj_add_state(tsw, LV_STATE_CHECKED);
|
||||
lv_obj_add_event_cb(tsw, onTrails, LV_EVENT_VALUE_CHANGED, NULL);
|
||||
schemaRow(g, SETTING(flow_show));
|
||||
#ifdef MAP_VECTOR // unfinished (LvglPort.h vectorOn)
|
||||
lv_obj_t* vsw = switchRow(g, "Vector map (test)", "Drawn on the device: trails, contours", nullptr);
|
||||
if (lvport::vectorOn()) lv_obj_add_state(vsw, LV_STATE_CHECKED);
|
||||
lv_obj_add_event_cb(vsw, onVectorMap, LV_EVENT_VALUE_CHANGED, NULL);
|
||||
char vr[48];
|
||||
int nv = mapview::s_vpacks.count();
|
||||
snprintf(vr, sizeof(vr), nv ? "%d on the card" : "None - packs go in /vmap", nv);
|
||||
listRow(g, "Vector regions", vr, onNavTool, (void*)(uintptr_t)navmap::TL_REGIONS);
|
||||
#endif
|
||||
|
||||
sectionTitle(list, "LIVE SHARE");
|
||||
_nav_share_lbl = noteLabel(list, "", THEME_FONT_SMALL, theme::TEXT);
|
||||
// Target: channels (as named -- "#" marks a hashtag channel), then
|
||||
@@ -1064,7 +1081,7 @@ void UITask::navToolsPopup(bool back) {
|
||||
|
||||
listRow(group(list, nullptr), "Share options", "How often, for how long", onNavTool, (void*)(uintptr_t)navmap::TL_OPT_SHARE);
|
||||
|
||||
lv_obj_t* g = group(list, "ARRIVAL ALERT");
|
||||
g = group(list, "ARRIVAL ALERT");
|
||||
schemaRow(g, SETTING(locator_enabled));
|
||||
char al[48], rad[12];
|
||||
uint16_t m = NodePrefs::locatorRadiusMeters(_prefs->locator_radius_idx);
|
||||
@@ -1081,20 +1098,6 @@ void UITask::navToolsPopup(bool back) {
|
||||
if (lvport::liveTiles()) lv_obj_add_state(lsw, LV_STATE_CHECKED);
|
||||
lv_obj_add_event_cb(lsw, onLiveTiles, LV_EVENT_VALUE_CHANGED, NULL);
|
||||
|
||||
g = group(list, "LAYERS");
|
||||
choiceRow(g, "Map", "Topo, light, dark or satellite", mapview::LAYER_OPTS, mapview::s_layer, onMapLayer);
|
||||
lv_obj_t* tsw = switchRow(g, "Hiking trails", "Marked routes in their colours", nullptr);
|
||||
if (lvport::trailsOn()) lv_obj_add_state(tsw, LV_STATE_CHECKED);
|
||||
lv_obj_add_event_cb(tsw, onTrails, LV_EVENT_VALUE_CHANGED, NULL);
|
||||
#ifdef MAP_VECTOR // unfinished (LvglPort.h vectorOn)
|
||||
lv_obj_t* vsw = switchRow(g, "Vector map (test)", "Drawn on the device: trails, contours", nullptr);
|
||||
if (lvport::vectorOn()) lv_obj_add_state(vsw, LV_STATE_CHECKED);
|
||||
lv_obj_add_event_cb(vsw, onVectorMap, LV_EVENT_VALUE_CHANGED, NULL);
|
||||
char vr[48];
|
||||
int nv = mapview::s_vpacks.count();
|
||||
snprintf(vr, sizeof(vr), nv ? "%d on the card" : "None - packs go in /vmap", nv);
|
||||
listRow(g, "Vector regions", vr, onNavTool, (void*)(uintptr_t)navmap::TL_REGIONS);
|
||||
#endif
|
||||
|
||||
refreshNavTools();
|
||||
if (back) { // scrolled where it was left
|
||||
|
||||
@@ -291,7 +291,7 @@ void UITask::openNode(int row) {
|
||||
|
||||
static lv_obj_t* s_nd_fav = nullptr;
|
||||
static void nodeFavText() {
|
||||
if (s_nd_fav) lv_label_set_text(s_nd_fav, s_node.fav ? UI_SYMBOL_STAR " Remove from favourites" : UI_SYMBOL_STAR " Add to favourites");
|
||||
if (s_nd_fav) lv_label_set_text(s_nd_fav, s_node.fav ? "Remove from favourites" : "Add to favourites"); // the star in its icon column
|
||||
}
|
||||
|
||||
static lv_obj_t* switchRow(lv_obj_t* parent, const char* text, const char* sub, uint8_t* pref); // below
|
||||
@@ -341,9 +341,9 @@ void UITask::buildNode() {
|
||||
s_nd_id = infoRow(_node_info, "ID", "");
|
||||
|
||||
if (pos || contact) {
|
||||
lv_obj_t* g = group(info, "More");
|
||||
lv_obj_t* g = group(info, "MORE");
|
||||
if (pos) actionRow(g, UI_SYMBOL_FLAG " Save as waypoint", onNodeAction, (void*)(uintptr_t)NODE_WAYPOINT);
|
||||
if (contact) s_nd_fav = actionRow(g, "", onNodeAction, (void*)(uintptr_t)NODE_FAV);
|
||||
if (contact) s_nd_fav = actionRow(g, UI_SYMBOL_STAR " ", onNodeAction, (void*)(uintptr_t)NODE_FAV);
|
||||
nodeFavText();
|
||||
if (contact && e.has_key) actionRow(g, UI_SYMBOL_TACK " Pin to Home", onNodeAction, (void*)(uintptr_t)NODE_PIN);
|
||||
ContactInfo* ci = (contact && e.has_key) ? the_mesh.lookupContactByPubKey(e.pub_key, PUB_KEY_SIZE) : nullptr;
|
||||
@@ -358,7 +358,7 @@ void UITask::buildNode() {
|
||||
// Where Settings > Privacy says "Allowed", what this contact may ask for.
|
||||
ContactInfo* ci = (contact && e.has_key) ? the_mesh.lookupContactByPubKey(e.pub_key, PUB_KEY_SIZE) : nullptr;
|
||||
if (ci && contactctl::telemetryPerContact(_prefs)) {
|
||||
lv_obj_t* g = group(info, "Can ask for");
|
||||
lv_obj_t* g = group(info, "CAN ASK FOR");
|
||||
static const struct { const char* text; const char* sub; uint8_t bit; } T[] = {
|
||||
{ "Status", "Battery; the two below need it", TELEM_PERM_BASE },
|
||||
{ "Location", nullptr, TELEM_PERM_LOCATION },
|
||||
@@ -524,7 +524,7 @@ void UITask::nodeAction(uint8_t action) {
|
||||
showToast(contactctl::share(e.pub_key) ? "Sent to nodes in range" : "No advert saved to send");
|
||||
break;
|
||||
case NODE_DELETE:
|
||||
if (!tapConfirmed(_node_delete_lbl, LV_SYMBOL_TRASH " Tap again to delete")) break;
|
||||
if (!tapConfirmed(_node_delete_lbl, "Tap again to delete")) break;
|
||||
if (the_mesh.deleteContactByKey(e.pub_key)) {
|
||||
_screen = SCR_NEARBY;
|
||||
buildNearby();
|
||||
|
||||
@@ -196,7 +196,8 @@ void UITask::advertSend(bool flood) {
|
||||
|
||||
// ── Settings > Bluetooth ──────────────────────────────────────────────────────
|
||||
// BLE only: the USB link to a computer stays on. Not kept over a reboot
|
||||
// (Bluetooth starts on, as on L1).
|
||||
// (Bluetooth starts on, as on L1). As WiFi and GPS: the switch turns it on /
|
||||
// off, the row opens its details -- here the pairing PIN.
|
||||
|
||||
static void bluetoothRow(lv_obj_t* body) {
|
||||
char sub[48];
|
||||
@@ -204,6 +205,13 @@ static void bluetoothRow(lv_obj_t* body) {
|
||||
lv_obj_t* sw = switchRow(body, LV_SYMBOL_BLUETOOTH " Bluetooth", sub, nullptr);
|
||||
if (s_ui->isBluetoothEnabled()) lv_obj_add_state(sw, LV_STATE_CHECKED);
|
||||
lv_obj_add_event_cb(sw, qview::onBtSwitch, LV_EVENT_VALUE_CHANGED, NULL);
|
||||
#ifdef BLE_PIN_CODE
|
||||
lv_obj_t* row = lv_obj_get_parent(sw);
|
||||
rowPressable(row);
|
||||
lv_obj_add_event_cb(row, [](lv_event_t* e) {
|
||||
if (lv_event_get_target(e) == lv_event_get_current_target(e)) onBlePin(e); // not the switch
|
||||
}, LV_EVENT_CLICKED, NULL);
|
||||
#endif
|
||||
}
|
||||
|
||||
void UITask::setBluetooth(bool on) {
|
||||
|
||||
@@ -98,7 +98,7 @@ void UITask::pruneContacts() {
|
||||
return;
|
||||
}
|
||||
char ask[40];
|
||||
snprintf(ask, sizeof(ask), LV_SYMBOL_TRASH " Remove %d contact%s?", n, n == 1 ? "" : "s");
|
||||
snprintf(ask, sizeof(ask), "Remove %d contact%s?", n, n == 1 ? "" : "s");
|
||||
if (!tapConfirmed(_prune_lbl, ask)) return;
|
||||
int removed = the_mesh.pruneStaleContacts();
|
||||
char t[40];
|
||||
@@ -144,8 +144,10 @@ void UITask::setTapWake(bool on) {
|
||||
}
|
||||
|
||||
void UITask::buildSchemaSettings() {
|
||||
static const char* const OWN[] = { "Keyboard", "About", "Display", "Power", "Time" };
|
||||
const char* title = _settings_page >= PG_KEYBOARD ? OWN[_settings_page - PG_KEYBOARD] : settings::pageTitle(_settings_page);
|
||||
static const char* const OWN[] = { "Keyboard", "About", "Display", "Power", "Time & units" };
|
||||
const char* title = _settings_page >= PG_KEYBOARD ? OWN[_settings_page - PG_KEYBOARD]
|
||||
: _settings_page == settings::PG_PRIVACY ? "Adverts & privacy" // with Auto-advert here
|
||||
: settings::pageTitle(_settings_page);
|
||||
if (_settings_page == settings::PG_NAV && s_nav_section >= 0)
|
||||
title = s_nav_section == settings::SEC_TRAIL ? "Trail options"
|
||||
: s_nav_section == settings::SEC_LIVE_SHARE ? "Share options" : "Alert options";
|
||||
@@ -170,29 +172,28 @@ void UITask::buildSchemaSettings() {
|
||||
accentRow(g); // DeviceScreen.h
|
||||
return;
|
||||
}
|
||||
case PG_POWER: {
|
||||
lv_obj_t* g = group(body, "BATTERY");
|
||||
case PG_POWER: { // one group: the page title says it (the GPS's power saving is under GPS > Options)
|
||||
lv_obj_t* g = group(body, nullptr);
|
||||
schemaRow(g, SETTING(batt_display_mode));
|
||||
schemaRow(g, SETTING(low_batt_mv));
|
||||
listRow(g, "Battery curve", _prefs && battery::validCurve(_prefs->batt_curve_mv) ? "Your own" : "LiPo (built in)",
|
||||
onBattCurve, NULL);
|
||||
g = group(body, "GPS");
|
||||
schemaRow(g, SETTING(gps_interval));
|
||||
return;
|
||||
}
|
||||
case PG_TIME: {
|
||||
lv_obj_t* g = group(body, nullptr);
|
||||
for (int i = 0; i < settings::COUNT; i++) if (settings::ALL[i].section == settings::SEC_TIME) schemaRow(g, i);
|
||||
for (uint8_t sec : { settings::SEC_TIME, settings::SEC_UNITS }) {
|
||||
lv_obj_t* g = group(body, settings::sectionTitle(sec));
|
||||
for (int i = 0; i < settings::COUNT; i++) if (settings::ALL[i].section == sec) schemaRow(g, i);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
schemaRows(body, _settings_page);
|
||||
if (_settings_page == settings::PG_SOUND) then(&UITask::schemaExtras, settings::PG_SOUND); // after its groups
|
||||
if (_settings_page == settings::PG_MESSAGES) then(&UITask::schemaExtras, settings::PG_MESSAGES);
|
||||
if (_settings_page == settings::PG_PRIVACY) then(&UITask::schemaExtras, settings::PG_PRIVACY);
|
||||
}
|
||||
|
||||
// ── Settings > Privacy: position, Bluetooth PIN; factory reset ─────────────────
|
||||
// ── GPS > Options: the fixed position; Bluetooth: its PIN; factory reset ───────
|
||||
|
||||
static void onOwnPos(lv_event_t* e) { (void)e; s_ui->ownPositionPopup(); }
|
||||
static void onOwnPosPick(lv_event_t* e) { s_ui->ownPositionPick((int)(uintptr_t)lv_event_get_user_data(e)); }
|
||||
@@ -202,6 +203,14 @@ static void onBlePinKb(lv_event_t* e) { s_ui->blePinDone(lv_event_get_code(e)
|
||||
|
||||
enum : int { OWNPOS_TYPE, OWNPOS_WAYPOINTS, OWNPOS_CLEAR, OWNPOS_WP0 = 100 };
|
||||
|
||||
// The Fixed position row's value (GPS > Options).
|
||||
void UITask::ownPositionText(char* sub, size_t n) {
|
||||
int32_t lat, lon;
|
||||
if (_core->gpsEnabled()) snprintf(sub, n, "From the GPS while it's on");
|
||||
else if (_core->fixedPosition(lat, lon)) snprintf(sub, n, "%.5f, %.5f", lat / 1e6, lon / 1e6);
|
||||
else snprintf(sub, n, "Not set");
|
||||
}
|
||||
|
||||
void UITask::ownPositionPopup() {
|
||||
if (_core->gpsEnabled()) { showToast("The GPS sets your position while it's on", 2500); return; }
|
||||
int32_t lat, lon;
|
||||
@@ -246,7 +255,7 @@ void UITask::ownPositionPick(int code) {
|
||||
else if (code >= OWNPOS_WP0 && code - OWNPOS_WP0 < wp.count())
|
||||
_core->setFixedPosition(wp.at(code - OWNPOS_WP0).lat_1e6, wp.at(code - OWNPOS_WP0).lon_1e6);
|
||||
navClosePopup();
|
||||
if (_screen == SCR_SETTINGS_NAV) rebuildSchemaSettings();
|
||||
if (_screen == SCR_GPS) buildGps();
|
||||
}
|
||||
|
||||
void UITask::ownPositionDone(bool ok) {
|
||||
@@ -259,7 +268,7 @@ void UITask::ownPositionDone(bool ok) {
|
||||
_core->setFixedPosition(lat, lon);
|
||||
}
|
||||
navClosePopup();
|
||||
if (ok && _screen == SCR_SETTINGS_NAV) rebuildSchemaSettings();
|
||||
if (ok && _screen == SCR_GPS) buildGps();
|
||||
}
|
||||
|
||||
void UITask::blePinPopup() {
|
||||
@@ -285,7 +294,6 @@ void UITask::blePinDone(bool ok) {
|
||||
showToast("Used from the next start", 2000);
|
||||
}
|
||||
navClosePopup();
|
||||
if (ok && _screen == SCR_SETTINGS_NAV) rebuildSchemaSettings();
|
||||
}
|
||||
|
||||
void UITask::factoryResetStep(int stage) {
|
||||
@@ -314,22 +322,6 @@ bool UITask::cliCommand(const char* command, char* reply, int n) {
|
||||
|
||||
// What a page adds to its schema groups, once they are there.
|
||||
void UITask::schemaExtras(int page) {
|
||||
if (page == settings::PG_PRIVACY) {
|
||||
if (lv_obj_t* c = s_sec_card[settings::SEC_PRIVACY]) { // with "Position in adverts"
|
||||
char sub[40];
|
||||
int32_t lat, lon;
|
||||
if (_core->gpsEnabled()) snprintf(sub, sizeof(sub), "From the GPS while it's on");
|
||||
else if (_core->fixedPosition(lat, lon)) snprintf(sub, sizeof(sub), "%.5f, %.5f", lat / 1e6, lon / 1e6);
|
||||
else snprintf(sub, sizeof(sub), "Not set");
|
||||
listRow(c, "Fixed position", sub, onOwnPos, NULL);
|
||||
}
|
||||
#ifdef BLE_PIN_CODE
|
||||
char pin[40];
|
||||
if (_prefs && _prefs->ble_pin) snprintf(pin, sizeof(pin), "%06lu", (unsigned long)_prefs->ble_pin);
|
||||
else snprintf(pin, sizeof(pin), "Random each start, shown on screen");
|
||||
listRow(group(_body, "BLUETOOTH"), "Pairing PIN", pin, onBlePin, NULL);
|
||||
#endif
|
||||
}
|
||||
if (page == settings::PG_SOUND) buildSoundRows(_body, false); // the melodies
|
||||
if (page == settings::PG_MESSAGES) {
|
||||
lv_obj_t* c = s_sec_card[settings::SEC_CONTACTS]; // the action that goes with "Contact expiry"
|
||||
@@ -452,6 +444,7 @@ void UITask::schemaRows(lv_obj_t* body, uint8_t page) {
|
||||
const settings::Setting& st = settings::ALL[i];
|
||||
if (settings::sectionPage(st.section) != page || st.section == sec) continue;
|
||||
if (s_schema_one && st.section != s_nav_section) continue;
|
||||
if (st.section == settings::SEC_MAP_VIEW) continue; // Packet flow: Map tools > Layers, with the other layers
|
||||
sec = st.section;
|
||||
if (first || body != _body) schemaSection(i);
|
||||
else fill(&UITask::schemaSection, i, i + 1);
|
||||
@@ -464,9 +457,13 @@ void UITask::schemaSection(int i) {
|
||||
uint8_t sec = settings::ALL[i].section;
|
||||
lv_obj_t* card = s_sec_card[sec] = group(s_schema_body, s_schema_bare ? nullptr : settings::sectionTitle(sec));
|
||||
if (sec == settings::SEC_SOUND) buildSoundRows(card, true); // On / Off / Auto
|
||||
if (sec == settings::SEC_PRIVACY) schemaRow(card, SETTING(advert_auto_interval_sec)); // all an advert carries, together
|
||||
for (int k = i; k < settings::COUNT && settings::ALL[k].section == sec; k++) {
|
||||
if (settings::ALL[k].offset == offsetof(NodePrefs, lock_compact)) continue; // ui-new's lock look; the slide card has one
|
||||
if (settings::ALL[k].offset == offsetof(NodePrefs, msg_alert)) { msgBannerRow(card); continue; } // no corner envelope here
|
||||
const settings::Setting& st = settings::ALL[k];
|
||||
if (st.offset == offsetof(NodePrefs, lock_compact)) continue; // ui-new's lock look; the slide card has one
|
||||
if (st.offset == offsetof(NodePrefs, msg_alert)) { msgBannerRow(card); continue; } // no corner envelope here
|
||||
if (st.offset == offsetof(NodePrefs, autoadd_config) && st.bit > 0x01 && _prefs && !_prefs->manual_add_contacts)
|
||||
continue; // the node types count only with "By type"
|
||||
schemaRow(card, k);
|
||||
}
|
||||
}
|
||||
@@ -515,7 +512,8 @@ void UITask::setSchemaValue(int idx, int v) {
|
||||
settings::set(*_prefs, st, (uint8_t)v);
|
||||
if (st.changed) st.changed(*_core);
|
||||
prefsSave();
|
||||
if (st.offset == offsetof(NodePrefs, units_imperial) && _screen == SCR_SETTINGS_NAV) // other labels depend on it
|
||||
if ((st.offset == offsetof(NodePrefs, units_imperial) || st.offset == offsetof(NodePrefs, manual_add_contacts))
|
||||
&& _screen == SCR_SETTINGS_NAV) // other labels depend on it, or rows come and go
|
||||
rebuildSoon(&UITask::rebuildSchemaSettings);
|
||||
}
|
||||
|
||||
@@ -552,7 +550,9 @@ static void onOpenRadio(lv_event_t* e); // RadioScreen.h
|
||||
static void onOpenStorage(lv_event_t* e); // StorageScreen.h
|
||||
|
||||
// Settings: groups of rows, each opening a page (chevron) or switching
|
||||
// something on the spot. What the app is for first, the system last.
|
||||
// something on the spot. Who you are and on which network first, then the
|
||||
// links (their switches side by side), the device, what the mesh carries, and
|
||||
// the system last. Every row has its symbol; one-group pages have no heading.
|
||||
void UITask::buildSettings() {
|
||||
bool restore = _nav_back; // back from one of its pages: where it was
|
||||
lv_obj_t* body = newScreen("Settings", true);
|
||||
@@ -566,48 +566,26 @@ void UITask::settingsGroup(int i) {
|
||||
lv_obj_t* body = _body;
|
||||
char sub[48];
|
||||
lv_obj_t* g;
|
||||
char v1[24], v2[24];
|
||||
char v1[24];
|
||||
switch (i) {
|
||||
case 0:
|
||||
if (!_prefs) break;
|
||||
g = group(body, "DEVICE");
|
||||
listRow(g, LV_SYMBOL_EYE_OPEN " Display", "Brightness, screen off, lock, colour",
|
||||
onOpenSchemaPage, (void*)(uintptr_t)PG_DISPLAY);
|
||||
snprintf(sub, sizeof(sub), "Shutdown %s - GPS: %s", settingText(*_prefs, SETTING(low_batt_mv), v1, sizeof(v1)),
|
||||
settingText(*_prefs, SETTING(gps_interval), v2, sizeof(v2)));
|
||||
listRow(g, LV_SYMBOL_BATTERY_FULL " Power", sub, onOpenSchemaPage, (void*)(uintptr_t)PG_POWER);
|
||||
{
|
||||
char vol[12];
|
||||
settings::optVolume(_prefs->buzzer_volume, vol, sizeof(vol), *_prefs);
|
||||
snprintf(sub, sizeof(sub), "%s, %s", soundctl::modeLabel(soundctl::mode(_prefs)), vol);
|
||||
}
|
||||
listRow(g, LV_SYMBOL_VOLUME_MAX " Sound", sub, onOpenSchemaPage, (void*)(uintptr_t)settings::PG_SOUND);
|
||||
{
|
||||
static const char* const SCRIPT[] = { "Latin", "Cyrillic", "Greek" };
|
||||
uint8_t ma = _prefs->keyboard_main_alphabet % NodePrefs::KB_ALPHABET_COUNT;
|
||||
uint8_t aa = _prefs->keyboard_alt_alphabet % NodePrefs::KB_ALPHABET_COUNT;
|
||||
snprintf(sub, sizeof(sub), aa == ma ? "%s" : "%s + %s", SCRIPT[ma], SCRIPT[aa]);
|
||||
}
|
||||
listRow(g, LV_SYMBOL_KEYBOARD " Keyboard", sub, onOpenSchemaPage, (void*)(uintptr_t)PG_KEYBOARD);
|
||||
listRow(g, LV_SYMBOL_ENVELOPE " Messages & contacts", "Resend, adding, expiry, quick messages",
|
||||
onOpenSchemaPage, (void*)(uintptr_t)settings::PG_MESSAGES);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
g = group(body, "CONNECTIONS");
|
||||
g = group(body, nullptr);
|
||||
listRow(g, LV_SYMBOL_EDIT " Name", the_mesh.getNodeName(), onNodeName, NULL);
|
||||
if (_prefs) {
|
||||
int pi = radioctl::currentPreset(_prefs);
|
||||
const char* pn = "Custom"; float f, b; uint8_t sf, cr;
|
||||
if (pi >= 0) radioctl::presetAt(_prefs, pi, pn, f, b, sf, cr);
|
||||
snprintf(sub, sizeof(sub), "%s - %.3f MHz, %d dBm", pn, _prefs->freq, _prefs->tx_power_dbm);
|
||||
listRow(g, UI_SYMBOL_RADIO " Radio", sub, onOpenRadio, NULL);
|
||||
listRow(g, LV_SYMBOL_EYE_CLOSE " Privacy", "Position, who can ask for telemetry, Bluetooth PIN",
|
||||
onOpenSchemaPage, (void*)(uintptr_t)settings::PG_PRIVACY);
|
||||
}
|
||||
break;
|
||||
|
||||
case 1: // the switch turns each on / off, the row opens its details
|
||||
g = group(body, "CONNECTIONS");
|
||||
bluetoothRow(g);
|
||||
wifiRow(g);
|
||||
if (_core->gpsAvailable()) { // as WiFi: the switch turns it on / off, the row opens its details
|
||||
lv_obj_t* sw = switchRow(g, LV_SYMBOL_GPS " GPS", "Tap for satellites and signal", nullptr);
|
||||
if (_core->gpsAvailable()) {
|
||||
lv_obj_t* sw = switchRow(g, LV_SYMBOL_GPS " GPS", "Tap for satellites and options", nullptr);
|
||||
if (_core->gpsEnabled()) lv_obj_add_state(sw, LV_STATE_CHECKED);
|
||||
lv_obj_add_event_cb(sw, onGpsSwitch, LV_EVENT_VALUE_CHANGED, NULL);
|
||||
lv_obj_t* row = lv_obj_get_parent(sw);
|
||||
@@ -619,26 +597,51 @@ void UITask::settingsGroup(int i) {
|
||||
break;
|
||||
|
||||
case 2:
|
||||
g = group(body, "MAP & DATA");
|
||||
if (_prefs) listRow(g, UI_SYMBOL_MAP " Map", "Trail, live sharing, arrival alert",
|
||||
onOpenSchemaPage, (void*)(uintptr_t)settings::PG_NAV);
|
||||
listRow(g, LV_SYMBOL_SD_CARD " Storage", "SD card, message history", onOpenStorage, NULL);
|
||||
if (!_prefs) break;
|
||||
g = group(body, "DEVICE");
|
||||
listRow(g, LV_SYMBOL_EYE_OPEN " Display", "Brightness, screen off, lock, colour",
|
||||
onOpenSchemaPage, (void*)(uintptr_t)PG_DISPLAY);
|
||||
{
|
||||
char vol[12];
|
||||
settings::optVolume(_prefs->buzzer_volume, vol, sizeof(vol), *_prefs);
|
||||
snprintf(sub, sizeof(sub), "%s, %s", soundctl::modeLabel(soundctl::mode(_prefs)), vol);
|
||||
}
|
||||
listRow(g, LV_SYMBOL_VOLUME_MAX " Sound", sub, onOpenSchemaPage, (void*)(uintptr_t)settings::PG_SOUND);
|
||||
snprintf(sub, sizeof(sub), "Battery display, shutdown at %s, curve", settingText(*_prefs, SETTING(low_batt_mv), v1, sizeof(v1)));
|
||||
listRow(g, LV_SYMBOL_BATTERY_FULL " Power", sub, onOpenSchemaPage, (void*)(uintptr_t)PG_POWER);
|
||||
snprintf(sub, sizeof(sub), "%s%s, %s, %s", settingText(*_prefs, SETTING(tz_offset_hours), v1, sizeof(v1)),
|
||||
inSummerTime(*_prefs, rtc_clock.getCurrentTime()) ? " summer" : "", _prefs->clock_12h ? "12 h" : "24 h",
|
||||
_prefs->units_imperial ? "imperial" : "metric");
|
||||
listRow(g, UI_SYMBOL_CLOCK " Time & units", sub, onOpenSchemaPage, (void*)(uintptr_t)PG_TIME);
|
||||
{
|
||||
static const char* const SCRIPT[] = { "Latin", "Cyrillic", "Greek" };
|
||||
uint8_t ma = _prefs->keyboard_main_alphabet % NodePrefs::KB_ALPHABET_COUNT;
|
||||
uint8_t aa = _prefs->keyboard_alt_alphabet % NodePrefs::KB_ALPHABET_COUNT;
|
||||
snprintf(sub, sizeof(sub), aa == ma ? "%s" : "%s + %s", SCRIPT[ma], SCRIPT[aa]);
|
||||
}
|
||||
listRow(g, LV_SYMBOL_KEYBOARD " Keyboard", sub, onOpenSchemaPage, (void*)(uintptr_t)PG_KEYBOARD);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
g = group(body, "SYSTEM");
|
||||
listRow(g, LV_SYMBOL_EDIT " Name", the_mesh.getNodeName(), onNodeName, NULL);
|
||||
g = group(body, "MESH & DATA");
|
||||
if (_prefs) {
|
||||
snprintf(sub, sizeof(sub), "%s%s, %s", settingText(*_prefs, SETTING(tz_offset_hours), v1, sizeof(v1)),
|
||||
inSummerTime(*_prefs, rtc_clock.getCurrentTime()) ? " summer" : "", _prefs->clock_12h ? "12 h" : "24 h");
|
||||
listRow(g, UI_SYMBOL_CLOCK " Time", sub, onOpenSchemaPage, (void*)(uintptr_t)PG_TIME);
|
||||
schemaRow(g, SETTING(units_imperial));
|
||||
listRow(g, LV_SYMBOL_ENVELOPE " Messages & contacts", "Resend, adding, expiry, quick messages",
|
||||
onOpenSchemaPage, (void*)(uintptr_t)settings::PG_MESSAGES);
|
||||
listRow(g, UI_SYMBOL_MAP " Map", "Trail, live sharing, arrival alert",
|
||||
onOpenSchemaPage, (void*)(uintptr_t)settings::PG_NAV);
|
||||
listRow(g, LV_SYMBOL_EYE_CLOSE " Adverts & privacy", "Auto-advert, position, telemetry",
|
||||
onOpenSchemaPage, (void*)(uintptr_t)settings::PG_PRIVACY);
|
||||
}
|
||||
listRow(g, LV_SYMBOL_SD_CARD " Storage", "SD card, message history", onOpenStorage, NULL);
|
||||
break;
|
||||
|
||||
case 4:
|
||||
g = group(body, "SYSTEM");
|
||||
listRow(g, LV_SYMBOL_DOWNLOAD " Firmware update", FIRMWARE_VERSION, onOpenOta, NULL);
|
||||
listRow(g, LV_SYMBOL_LIST " About", "Node, your contact QR, manual, credits", onOpenSchemaPage, (void*)(uintptr_t)PG_ABOUT);
|
||||
break;
|
||||
|
||||
case 4:
|
||||
case 5:
|
||||
g = group(body, nullptr);
|
||||
actionRow(g, LV_SYMBOL_REFRESH " Reboot", onPowerRow, (void*)(uintptr_t)1);
|
||||
actionRow(g, LV_SYMBOL_POWER " Power off", onPowerRow, (void*)(uintptr_t)0, theme::FAIL);
|
||||
|
||||
@@ -284,16 +284,12 @@ void UITask::buildSos() {
|
||||
NodePrefs* p = _prefs;
|
||||
if (!p) return;
|
||||
lv_obj_t* list = scrollList(body);
|
||||
lv_obj_t* g = group(list, nullptr);
|
||||
|
||||
lv_obj_t* g = group(list, "START"); // how it begins
|
||||
switchRow(g, "Top button", "Hold 5 s for the SOS slider", &p->sos_enabled);
|
||||
lv_obj_t* seg = segmented(settingRow(g, "Countdown", nullptr), COUNTDOWNS, p->sos_countdown_idx, 130, 32);
|
||||
lv_obj_add_event_cb(seg, onSosSeg, LV_EVENT_VALUE_CHANGED, (void*)(uintptr_t)P_COUNTDOWN);
|
||||
seg = segmented(settingRow(g, "Repeat", nullptr), REPEATS, p->sos_repeat_idx, 200, 32);
|
||||
lv_obj_add_event_cb(seg, onSosSeg, LV_EVENT_VALUE_CHANGED, (void*)(uintptr_t)P_REPEAT);
|
||||
seg = segmented(settingRow(g, "Beep", "To be found by"), BEEPS, p->sos_beep_idx, 170, 32);
|
||||
lv_obj_add_event_cb(seg, onSosSeg, LV_EVENT_VALUE_CHANGED, (void*)(uintptr_t)P_BEEP);
|
||||
|
||||
g = group(list, "SENT TO"); // where it goes, and what it says
|
||||
char val[40];
|
||||
snprintf(val, sizeof(val), "%d on", SosEngine::channels(p));
|
||||
s_ch_lbl = sosValueRow(g, "Channels", nullptr, val, R_CHANNELS);
|
||||
@@ -304,9 +300,15 @@ void UITask::buildSos() {
|
||||
snprintf(name, sizeof(name), "Contact %d", i + 1);
|
||||
sosValueRow(g, name, nullptr, c ? c->name : "-", R_DM1 + i);
|
||||
}
|
||||
sosValueRow(g, "ACK word", "The reply that means received", p->sos_ack[0] ? p->sos_ack : "-", R_ACK);
|
||||
sosValueRow(g, "Note", "Added to the message", p->sos_note[0] ? p->sos_note : "-", R_NOTE);
|
||||
|
||||
g = group(list, "WHILE IT RUNS"); // until someone answers, and after
|
||||
seg = segmented(settingRow(g, "Repeat", nullptr), REPEATS, p->sos_repeat_idx, 200, 32);
|
||||
lv_obj_add_event_cb(seg, onSosSeg, LV_EVENT_VALUE_CHANGED, (void*)(uintptr_t)P_REPEAT);
|
||||
seg = segmented(settingRow(g, "Beep", "To be found by"), BEEPS, p->sos_beep_idx, 170, 32);
|
||||
lv_obj_add_event_cb(seg, onSosSeg, LV_EVENT_VALUE_CHANGED, (void*)(uintptr_t)P_BEEP);
|
||||
sosValueRow(g, "ACK word", "The reply that means received", p->sos_ack[0] ? p->sos_ack : "-", R_ACK);
|
||||
|
||||
sosWideButton(list, "Send SOS", LV_PCT(100), 44, theme::FAIL, 0xFFFFFF, onSosSend);
|
||||
}
|
||||
|
||||
@@ -327,7 +329,7 @@ void UITask::sosChannel(int idx, bool on) {
|
||||
prefsSave();
|
||||
char val[16];
|
||||
snprintf(val, sizeof(val), "%d on", SosEngine::channels(_prefs));
|
||||
if (s_ch_lbl) lv_label_set_text(s_ch_lbl, val);
|
||||
if (s_ch_lbl) pickSet(s_ch_lbl, val);
|
||||
}
|
||||
|
||||
void UITask::sosRow(int row) {
|
||||
|
||||
@@ -360,7 +360,7 @@ void UITask::storageKeep(int idx) {
|
||||
|
||||
void UITask::storageClearHistory() {
|
||||
using namespace storeview;
|
||||
if (!tapConfirmed(s_clear_lbl, LV_SYMBOL_TRASH " Tap again to delete")) return;
|
||||
if (!tapConfirmed(s_clear_lbl, "Tap again to delete")) return;
|
||||
s_archive.clearAll();
|
||||
_core->history.clearAll();
|
||||
_core->markAllRead();
|
||||
@@ -375,7 +375,7 @@ void UITask::storageLiveCap(int idx) {
|
||||
|
||||
void UITask::storageClearLive() {
|
||||
using namespace storeview;
|
||||
if (!tapConfirmed(s_live_lbl, LV_SYMBOL_TRASH " Tap again to delete")) return;
|
||||
if (!tapConfirmed(s_live_lbl, "Tap again to delete")) return;
|
||||
mapview::s_live_cache.clearAll(); // a few files per loop (mapDownloadTick)
|
||||
showToast("Deleting live map tiles");
|
||||
}
|
||||
|
||||
@@ -101,9 +101,11 @@ public:
|
||||
void otaAction();
|
||||
void adminPick(int row);
|
||||
void pruneContacts();
|
||||
// Settings > Privacy: the position adverts carry with the GPS off, the
|
||||
// Bluetooth PIN; Settings: factory reset, asked twice (stage 1, 2; 3 erases).
|
||||
// GPS > Options: the position that stands in for the GPS while it's off;
|
||||
// Settings > Bluetooth (a tap on its row): the pairing PIN; Settings: factory
|
||||
// reset, asked twice (stage 1, 2; 3 erases).
|
||||
void ownPositionPopup();
|
||||
void ownPositionText(char* sub, size_t n);
|
||||
void ownPositionPick(int code);
|
||||
void ownPositionDone(bool ok);
|
||||
void blePinPopup();
|
||||
@@ -449,7 +451,7 @@ private:
|
||||
void fillFlush();
|
||||
int fillPending() const;
|
||||
void buildSettings();
|
||||
static const int SETTINGS_GROUPS = 5;
|
||||
static const int SETTINGS_GROUPS = 6;
|
||||
void settingsGroup(int i);
|
||||
void buildSchemaSettings();
|
||||
void schemaRows(lv_obj_t* body, uint8_t page);
|
||||
|
||||
@@ -553,8 +553,27 @@ static lv_obj_t* groupLine(lv_obj_t* card, bool tappable) {
|
||||
return r;
|
||||
}
|
||||
|
||||
// A row title led by a symbol ("<symbol> Text"): the symbol gets a column of
|
||||
// its own, the same width on every row, so the titles and hints under them
|
||||
// line up whatever the glyph's width. Returns the text after it, or nullptr.
|
||||
static const int ICON_COL_W = 22;
|
||||
static const char* rowIconSplit(const char* text, char* icon) {
|
||||
const uint8_t* u = (const uint8_t*)text;
|
||||
if (!u || u[0] != 0xEF || !u[1] || !u[2] || u[3] != ' ' || u[4] != ' ') return nullptr; // U+F000.. symbols
|
||||
memcpy(icon, text, 3);
|
||||
icon[3] = '\0';
|
||||
return text + 5;
|
||||
}
|
||||
|
||||
// The label (and the hint under it, wrapping) filling a group row's left side.
|
||||
static lv_obj_t* groupText(lv_obj_t* row, const char* text, const char* hint, uint32_t col = theme::TEXT) {
|
||||
char icon[4];
|
||||
if (const char* rest = rowIconSplit(text, icon)) {
|
||||
lv_obj_t* i = label(row, icon, THEME_FONT_BODY, col);
|
||||
lv_obj_set_width(i, ICON_COL_W);
|
||||
lv_obj_set_style_text_align(i, LV_TEXT_ALIGN_CENTER, 0);
|
||||
text = rest;
|
||||
}
|
||||
lv_obj_t* t = flexBox(row, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_width(t, 1);
|
||||
lv_obj_set_flex_grow(t, 1);
|
||||
@@ -910,11 +929,23 @@ static lv_obj_t* choiceRow(lv_obj_t* parent, const char* text, const char* hint,
|
||||
|
||||
// A value picked or typed behind the row: cut to `w`, with a chevron, the whole
|
||||
// row is the tap. Returns the value's label.
|
||||
// rowPick()'s value: as wide as its text, up to the width it was given (kept
|
||||
// as its max width), so a short value leaves the hint beside it room.
|
||||
static void pickSet(lv_obj_t* v, const char* text) {
|
||||
lv_label_set_text(v, text);
|
||||
const lv_font_t* f = lv_obj_get_style_text_font(v, LV_PART_MAIN);
|
||||
lv_point_t sz;
|
||||
lv_text_get_size(&sz, text, f, 0, 0, LV_COORD_MAX, LV_TEXT_FLAG_NONE);
|
||||
int32_t w = sz.x + 1;
|
||||
int32_t max = lv_obj_get_style_max_width(v, LV_PART_MAIN);
|
||||
lv_obj_set_width(v, w < max ? w : max); // a px width: LONG_DOT needs one
|
||||
}
|
||||
static lv_obj_t* rowPick(lv_obj_t* row, const char* text, int w, lv_event_cb_t cb, void* user = nullptr) {
|
||||
lv_obj_t* v = label(row, text, THEME_FONT_BODY, theme::ACCENT);
|
||||
lv_obj_t* v = label(row, "", THEME_FONT_BODY, theme::ACCENT);
|
||||
lv_label_set_long_mode(v, LV_LABEL_LONG_DOT);
|
||||
lv_obj_set_width(v, w); // fixed: LONG_DOT needs a width
|
||||
lv_obj_set_style_max_width(v, w, 0);
|
||||
lv_obj_set_style_text_align(v, LV_TEXT_ALIGN_RIGHT, 0);
|
||||
pickSet(v, text);
|
||||
label(row, LV_SYMBOL_RIGHT, THEME_FONT_SMALL, theme::TEXT_MUTED);
|
||||
rowPressable(row);
|
||||
lv_obj_add_event_cb(row, cb, LV_EVENT_CLICKED, user);
|
||||
|
||||
@@ -38,7 +38,7 @@ static void wifiHint(char* sub, size_t n) {
|
||||
if (!lvport::wifiAllowed()) snprintf(sub, n, "Off");
|
||||
else if (saved == 1) snprintf(sub, n, "%s", lvport::wifiSaved(0)->ssid);
|
||||
else if (saved > 1) snprintf(sub, n, "%d saved networks", saved);
|
||||
else snprintf(sub, n, "Tap to pick a network");
|
||||
else snprintf(sub, n, "On - no network saved yet"); // Settings and the WiFi screen both
|
||||
}
|
||||
static void onWifiAllowed(lv_event_t* e) {
|
||||
lv_obj_t* sw = (lv_obj_t*)lv_event_get_target(e);
|
||||
@@ -69,14 +69,20 @@ void UITask::buildWifi() {
|
||||
lv_obj_t* body = newScreen("WiFi", true);
|
||||
s_wifi_forget_btn = nullptr;
|
||||
lv_obj_set_style_pad_row(body, 4, 0);
|
||||
label(body, lvport::wifiAllowed() ? "For map downloads, live map tiles and updates." : "WiFi is off (Settings).",
|
||||
THEME_FONT_SMALL, theme::TEXT_MUTED);
|
||||
{ // the same switch as Settings' row
|
||||
char sub[48];
|
||||
wifiHint(sub, sizeof(sub));
|
||||
lv_obj_t* sw = switchRow(group(body, nullptr), LV_SYMBOL_WIFI " WiFi", sub, nullptr);
|
||||
if (lvport::wifiAllowed()) lv_obj_add_state(sw, LV_STATE_CHECKED);
|
||||
lv_obj_add_event_cb(sw, onWifiAllowed, LV_EVENT_VALUE_CHANGED, NULL);
|
||||
groupNote(body, "For map downloads, live map tiles and updates.");
|
||||
}
|
||||
|
||||
// Saved networks: a tap loads one into the form below; Forget takes two taps.
|
||||
int n = lvport::wifiSavedCount();
|
||||
char joined[33];
|
||||
lvport::netSsid(joined, sizeof(joined));
|
||||
lv_obj_t* card = group(body, "Saved networks");
|
||||
lv_obj_t* card = group(body, "SAVED NETWORKS");
|
||||
for (int i = 0; i < n; i++) {
|
||||
const lvport::WifiNet* w = lvport::wifiSaved(i);
|
||||
lv_obj_t* row = groupLine(card, true);
|
||||
@@ -94,7 +100,7 @@ void UITask::buildWifi() {
|
||||
if (!n) groupText(groupLine(card, false), "None yet", "Add one below.", theme::TEXT_MUTED);
|
||||
if (n > 1) groupNote(body, "The strongest one in range is used.");
|
||||
|
||||
sectionTitle(body, n >= lvport::WIFI_SAVED_MAX ? "Add a network (replaces the oldest)" : "Add a network");
|
||||
sectionTitle(body, n >= lvport::WIFI_SAVED_MAX ? "ADD A NETWORK (REPLACES THE OLDEST)" : "ADD A NETWORK");
|
||||
lv_obj_t* row = lv_obj_create(body);
|
||||
styleSurface(row, theme::BG);
|
||||
lv_obj_remove_flag(row, LV_OBJ_FLAG_SCROLLABLE);
|
||||
@@ -196,7 +202,7 @@ void UITask::wifiSetAllowed(bool on) {
|
||||
|
||||
void UITask::wifiScan() {
|
||||
if (_wifi_scanning) return;
|
||||
if (!lvport::wifiAllowed()) { lv_label_set_text(_wifi_status, "Turn WiFi on in Settings first."); return; }
|
||||
if (!lvport::wifiAllowed()) { lv_label_set_text(_wifi_status, "Turn WiFi on first."); return; }
|
||||
if (wifiInUse()) { lv_label_set_text(_wifi_status, "WiFi busy (a download or an update) - try later."); return; }
|
||||
lvport::scanStart();
|
||||
_wifi_scanning = true;
|
||||
|
||||
Reference in New Issue
Block a user