diff --git a/docs/solo/contacts.md b/docs/solo/contacts.md index 17fcd28b..a5a23cf0 100644 --- a/docs/solo/contacts.md +++ b/docs/solo/contacts.md @@ -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 diff --git a/docs/solo/settings.md b/docs/solo/settings.md index 87745609..6e92ab0f 100644 --- a/docs/solo/settings.md +++ b/docs/solo/settings.md @@ -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 diff --git a/docs/solo/tools.md b/docs/solo/tools.md index 25d0e4f2..d4fc8150 100644 --- a/docs/solo/tools.md +++ b/docs/solo/tools.md @@ -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 diff --git a/examples/companion_radio/ui-core/SettingsSchema.h b/examples/companion_radio/ui-core/SettingsSchema.h index 0668e4e0..f05fed9e 100644 --- a/examples/companion_radio/ui-core/SettingsSchema.h +++ b/examples/companion_radio/ui-core/SettingsSchema.h @@ -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), diff --git a/examples/companion_radio/ui-core/UiCore.h b/examples/companion_radio/ui-core/UiCore.h index f2c03be9..66a26ed8 100644 --- a/examples/companion_radio/ui-core/UiCore.h +++ b/examples/companion_radio/ui-core/UiCore.h @@ -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; diff --git a/examples/companion_radio/ui-lvgl/BotScreen.h b/examples/companion_radio/ui-lvgl/BotScreen.h index 9d76dee1..02094ded 100644 --- a/examples/companion_radio/ui-lvgl/BotScreen.h +++ b/examples/companion_radio/ui-lvgl/BotScreen.h @@ -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); } } diff --git a/examples/companion_radio/ui-lvgl/GpsScreen.h b/examples/companion_radio/ui-lvgl/GpsScreen.h index cbe61f7b..8b38e315 100644 --- a/examples/companion_radio/ui-lvgl/GpsScreen.h +++ b/examples/companion_radio/ui-lvgl/GpsScreen.h @@ -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", ""); diff --git a/examples/companion_radio/ui-lvgl/NavMap.h b/examples/companion_radio/ui-lvgl/NavMap.h index ca6f5eff..fcc2c5c3 100644 --- a/examples/companion_radio/ui-lvgl/NavMap.h +++ b/examples/companion_radio/ui-lvgl/NavMap.h @@ -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 diff --git a/examples/companion_radio/ui-lvgl/NodesView.h b/examples/companion_radio/ui-lvgl/NodesView.h index 3a3f5ebc..f890e317 100644 --- a/examples/companion_radio/ui-lvgl/NodesView.h +++ b/examples/companion_radio/ui-lvgl/NodesView.h @@ -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(); diff --git a/examples/companion_radio/ui-lvgl/QuickScreen.h b/examples/companion_radio/ui-lvgl/QuickScreen.h index 354d8b77..e9055b8b 100644 --- a/examples/companion_radio/ui-lvgl/QuickScreen.h +++ b/examples/companion_radio/ui-lvgl/QuickScreen.h @@ -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) { diff --git a/examples/companion_radio/ui-lvgl/SettingsPages.h b/examples/companion_radio/ui-lvgl/SettingsPages.h index 8031e414..907b012d 100644 --- a/examples/companion_radio/ui-lvgl/SettingsPages.h +++ b/examples/companion_radio/ui-lvgl/SettingsPages.h @@ -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); diff --git a/examples/companion_radio/ui-lvgl/SosScreen.h b/examples/companion_radio/ui-lvgl/SosScreen.h index cf8afc96..046f9001 100644 --- a/examples/companion_radio/ui-lvgl/SosScreen.h +++ b/examples/companion_radio/ui-lvgl/SosScreen.h @@ -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) { diff --git a/examples/companion_radio/ui-lvgl/StorageScreen.h b/examples/companion_radio/ui-lvgl/StorageScreen.h index ebc8f5ea..f43df592 100644 --- a/examples/companion_radio/ui-lvgl/StorageScreen.h +++ b/examples/companion_radio/ui-lvgl/StorageScreen.h @@ -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"); } diff --git a/examples/companion_radio/ui-lvgl/UITask.h b/examples/companion_radio/ui-lvgl/UITask.h index 2f532fca..2e1dd1ad 100644 --- a/examples/companion_radio/ui-lvgl/UITask.h +++ b/examples/companion_radio/ui-lvgl/UITask.h @@ -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); diff --git a/examples/companion_radio/ui-lvgl/Widgets.h b/examples/companion_radio/ui-lvgl/Widgets.h index 82fc0b68..721faeec 100644 --- a/examples/companion_radio/ui-lvgl/Widgets.h +++ b/examples/companion_radio/ui-lvgl/Widgets.h @@ -553,8 +553,27 @@ static lv_obj_t* groupLine(lv_obj_t* card, bool tappable) { return r; } +// A row title led by a 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); diff --git a/examples/companion_radio/ui-lvgl/WifiScreen.h b/examples/companion_radio/ui-lvgl/WifiScreen.h index d3f929fe..ddda9377 100644 --- a/examples/companion_radio/ui-lvgl/WifiScreen.h +++ b/examples/companion_radio/ui-lvgl/WifiScreen.h @@ -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;