mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-11-02 19:02:00 +00:00
refactor: one copy of shared logic and widgets; GPS is asked for, not switched on silently
Duplicates found between the L1 and L2 frontends, and inside the L2 one:
- geo::fmtLocShare / fmtLatLon / turnDeg / isAhead (GeoUtils.h): the [LOC]
text was built in five places, the turn angle and its "ahead" threshold in
both navigations, coordinates by hand in nine. Tested.
- LiveShareEngine::setEnabled: live share was switched in three places.
- L1 waypoint share uses WaypointModel::shareText; Trail's "Share my pos"
goes through UITask::quickShareMyLocation, so it now sends to a running
live-share session as the home Map page does.
- L2 Widgets.h: switchCreate (one switch size, was four), switchRow with a
state and callback (16 hand-wired copies), switchRowOpens (Bluetooth, WiFi,
GPS), primaryButton (one height for Save / Measure / New area / Update).
GPS: with it off, starting the trail, live share or track back, sharing or
marking a position, or centring the map asks first ("GPS is off") on both
L1 and L2; Cancel leaves it undone. A share or a mark waits for a fresh fix
(a switched-off module may still report its last one), which also fixes the
L1 "Share my pos" sending a stale position. SOS still turns the GPS on itself.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -309,10 +309,7 @@ void UITask::adminValuePopup() {
|
||||
if (f->kind == admin::K_ONOFF) {
|
||||
lv_obj_t* r = centerRow(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);
|
||||
switchCreate(r, S.value != 0, onAdminValue, (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 = centerRow(panel);
|
||||
|
||||
@@ -58,12 +58,6 @@ static void botChip(const char* ph, void* parent) {
|
||||
lv_obj_add_event_cb(b, onBotPlaceholder, LV_EVENT_CLICKED, NULL);
|
||||
lv_obj_center(label(b, ph, THEME_FONT_SMALL, theme::TEXT));
|
||||
}
|
||||
// A switchRow() for something that isn't a plain pref: `cb` gets `idx`.
|
||||
static void switchRowIdx(lv_obj_t* parent, const char* text, const char* hint, bool on, lv_event_cb_t cb, int idx) {
|
||||
lv_obj_t* sw = switchRow(parent, text, hint, nullptr);
|
||||
if (on) lv_obj_add_state(sw, LV_STATE_CHECKED);
|
||||
lv_obj_add_event_cb(sw, cb, LV_EVENT_VALUE_CHANGED, (void*)(uintptr_t)idx);
|
||||
}
|
||||
static void onOpenBot(lv_event_t* e) { (void)e; s_ui->showBot(); }
|
||||
|
||||
static lv_obj_t* botHourButton(lv_obj_t* parent, const char* text, uint8_t act, bool accent) {
|
||||
@@ -148,7 +142,7 @@ void UITask::buildBot() {
|
||||
const botcfg::Row& r = botcfg::row(s_tab, i);
|
||||
uint8_t* fl = botcfg::flag(p, r.kind);
|
||||
if (fl && !botcfg::isScope(r.kind)) {
|
||||
switchRowIdx(g, r.label, r.hint, *fl, onBotSwitch, i);
|
||||
switchRow(g, r.label, r.hint, *fl, onBotSwitch, (void*)(uintptr_t)i);
|
||||
continue;
|
||||
}
|
||||
uint16_t* cmd_off = botcfg::cmdOff(p, r.kind);
|
||||
@@ -255,7 +249,7 @@ void UITask::botRow(int row) {
|
||||
if (uint16_t* off = botcfg::cmdOff(p, r.kind)) {
|
||||
lv_obj_t* g = group(scrollList(navPopupPanel("Allowed commands", true)), nullptr);
|
||||
for (int i = 0; i < botcmd::count(botcfg::actionsOn(p, r.kind)); i++)
|
||||
switchRowIdx(g, botcmd::CMDS[i].label, botcmd::CMDS[i].hint, !(*off & botcmd::flag(i)), onBotCmd, i);
|
||||
switchRow(g, botcmd::CMDS[i].label, botcmd::CMDS[i].hint, !(*off & botcmd::flag(i)), onBotCmd, (void*)(uintptr_t)i);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -186,19 +186,9 @@ void UITask::buildChannelEdit() {
|
||||
s_secret = chanField(body, "Passphrase");
|
||||
lv_textarea_set_max_length(s_secret, 32);
|
||||
|
||||
lv_obj_t* row = settingRow(body, "Hex key", "32 characters");
|
||||
s_hex_sw = lv_switch_create(row);
|
||||
lv_obj_set_size(s_hex_sw, 46, 24);
|
||||
lv_obj_align(s_hex_sw, LV_ALIGN_RIGHT_MID, -theme::PAD, 0);
|
||||
lv_obj_add_event_cb(s_hex_sw, onChanHex, LV_EVENT_VALUE_CHANGED, NULL);
|
||||
s_hex_sw = switchRow(body, "Hex key", "32 characters", false, onChanHex);
|
||||
|
||||
lv_obj_t* save = lv_button_create(body);
|
||||
lv_obj_set_size(save, LV_PCT(100), 38);
|
||||
lv_obj_set_style_shadow_width(save, 0, 0);
|
||||
lv_obj_set_style_radius(save, theme::RADIUS, 0);
|
||||
lv_obj_add_event_cb(save, onChanSave, LV_EVENT_CLICKED, NULL);
|
||||
lv_obj_center(label(save, LV_SYMBOL_OK " Save", THEME_FONT_BODY, theme::TEXT));
|
||||
stylePrimary(save);
|
||||
primaryButton(body, LV_SYMBOL_OK " Save", onChanSave);
|
||||
s_status = noteLabel(body, "", THEME_FONT_SMALL, theme::FAIL);
|
||||
|
||||
if (edit) {
|
||||
|
||||
@@ -314,10 +314,8 @@ void UITask::buildClock() {
|
||||
lv_obj_set_flex_align(on, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||
lv_obj_set_style_pad_column(on, 8, 0);
|
||||
lv_obj_set_style_pad_bottom(on, 4, 0);
|
||||
lv_obj_t* sw = s_alarm_sw = lv_switch_create(on);
|
||||
lv_obj_t* sw = s_alarm_sw = switchCreate(on, _prefs->alarm_on, nullptr, nullptr);
|
||||
label(on, "Alarm on", THEME_FONT_SMALL, theme::TEXT_MUTED);
|
||||
lv_obj_set_size(sw, 50, 26);
|
||||
if (_prefs->alarm_on) lv_obj_add_state(sw, LV_STATE_CHECKED);
|
||||
lv_obj_add_event_cb(sw, onAlarmSwitch, LV_EVENT_VALUE_CHANGED, NULL);
|
||||
label(col, "Repeat", THEME_FONT_SMALL, theme::TEXT_MUTED);
|
||||
char opts[48];
|
||||
|
||||
@@ -366,14 +366,8 @@ void UITask::buildDiag() {
|
||||
if (s_tab == TAB_NOISE) {
|
||||
noteLabel(s_list, "What the radio hears with nothing on air, on the mesh frequency and 1.1 MHz "
|
||||
"either side. Takes about 12 seconds.");
|
||||
lv_obj_t* b = lv_button_create(s_list);
|
||||
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_add_event_cb(b, onNoiseRun, LV_EVENT_CLICKED, NULL);
|
||||
s_noise_status = label(b, s_noise_have ? LV_SYMBOL_REFRESH " Measure again" : LV_SYMBOL_PLAY " Measure", THEME_FONT_BODY, theme::TEXT);
|
||||
lv_obj_center(s_noise_status);
|
||||
stylePrimary(b);
|
||||
lv_obj_t* b = primaryButton(s_list, s_noise_have ? LV_SYMBOL_REFRESH " Measure again" : LV_SYMBOL_PLAY " Measure", onNoiseRun);
|
||||
s_noise_status = lv_obj_get_child(b, 0);
|
||||
if (s_noise_have) {
|
||||
float mesh_f = _prefs ? _prefs->freq : 0;
|
||||
lv_obj_t* card = infoCard(s_list);
|
||||
|
||||
@@ -99,6 +99,49 @@ static void onOpenGps(lv_event_t* e) { (void)e; s_ui->showGps(false); }
|
||||
static void onOpenGpsFromSettings(lv_event_t* e) { (void)e; s_ui->showGps(true); }
|
||||
static void onGpsTurnOn(lv_event_t* e) { (void)e; s_ui->setGps(true); s_ui->refreshGps(); }
|
||||
|
||||
// ── "GPS is off" ──────────────────────────────────────────────────────────────
|
||||
// Something that needs the GPS while it's off asks first, as on the L1: Turn
|
||||
// on does it, the close button leaves it undone.
|
||||
|
||||
static void onGpsAsk(lv_event_t* e) {
|
||||
UITask::GpsAsk what = (UITask::GpsAsk)(uintptr_t)lv_event_get_user_data(e);
|
||||
s_ui->navClosePopup();
|
||||
s_ui->setGps(true);
|
||||
s_ui->gpsAskDone(what);
|
||||
}
|
||||
|
||||
bool UITask::gpsAsked(GpsAsk what) {
|
||||
if (!_core->gpsAvailable() || _core->gpsEnabled()) return false;
|
||||
static const char* const NEEDS[] = {
|
||||
"The trail is recorded from the GPS.", "Live share sends the GPS position.", "Track back follows the GPS.",
|
||||
"The map finds you by the GPS.", "This needs a GPS fix.",
|
||||
};
|
||||
lv_obj_t* panel = navPopupPanel("GPS is off", false);
|
||||
noteLabel(panel, NEEDS[what]);
|
||||
primaryButton(panel, LV_SYMBOL_GPS " Turn on GPS", onGpsAsk, (void*)(uintptr_t)what);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Just turned on: what asked for it. A position needs a fresh fix first (its
|
||||
// last one may be old), so a share or a mark is left for then.
|
||||
void UITask::gpsAskDone(GpsAsk what) {
|
||||
switch (what) {
|
||||
case GA_TRAIL:
|
||||
_core->trail.setActive(true);
|
||||
showToast("GPS on, recording the trail");
|
||||
break;
|
||||
case GA_SHARE:
|
||||
_core->live_share.setEnabled(true);
|
||||
prefsSave();
|
||||
showToast("GPS on, sharing your position");
|
||||
break;
|
||||
case GA_TRACKBACK: navToolAction(navmap::TL_TRACKBACK); break;
|
||||
case GA_CENTRE: mapCenterOnMe(); break;
|
||||
case GA_FIX: showToast("GPS on, waiting for a fix"); break;
|
||||
}
|
||||
refreshStatusBar();
|
||||
}
|
||||
|
||||
// Sky plot: horizon ring, 30 / 60 degree rings, N-S / E-W lines, one dot per
|
||||
// satellite at its azimuth (N up) and elevation (overhead in the centre).
|
||||
static void onGpsSkyDraw(lv_event_t* e) {
|
||||
@@ -480,7 +523,7 @@ void UITask::refreshGps() {
|
||||
if (g->utc_valid) snprintf(utc, sizeof(utc), "%02u:%02u:%02u", g->utc_h, g->utc_m, g->utc_s);
|
||||
int32_t lat, lon;
|
||||
if (fix && _core->course.currentLocation(lat, lon)) {
|
||||
snprintf(pos, sizeof(pos), "%.5f, %.5f", lat / 1e6, lon / 1e6);
|
||||
geo::fmtLatLon(pos, sizeof(pos), lat, lon, true);
|
||||
locator(lat / 1e6, lon / 1e6, grid, sizeof(grid));
|
||||
}
|
||||
if (fix && g->alt_valid) snprintf(alt, sizeof(alt), "%.0f m", g->alt_m);
|
||||
|
||||
@@ -388,13 +388,7 @@ void UITask::mapAreasPopup() {
|
||||
} else {
|
||||
noteLabel(list, "No areas yet. Maps downloaded before this list are still on the card.");
|
||||
}
|
||||
lv_obj_t* add = lv_button_create(list);
|
||||
lv_obj_set_size(add, LV_PCT(100), 38);
|
||||
lv_obj_set_style_radius(add, theme::RADIUS, 0);
|
||||
lv_obj_set_style_shadow_width(add, 0, 0);
|
||||
lv_obj_add_event_cb(add, onNewArea, LV_EVENT_CLICKED, NULL);
|
||||
lv_obj_center(label(add, LV_SYMBOL_PLUS " New area", THEME_FONT_BODY, theme::TEXT));
|
||||
stylePrimary(add);
|
||||
primaryButton(list, LV_SYMBOL_PLUS " New area", onNewArea);
|
||||
}
|
||||
|
||||
void UITask::mapAreaPopup(int idx) {
|
||||
|
||||
@@ -1292,7 +1292,8 @@ void UITask::mapZoom(int delta) {
|
||||
|
||||
void UITask::mapCenterOnMe() {
|
||||
int32_t lat, lon;
|
||||
if (!ensureGps() || !_core->course.currentLocation(lat, lon)) { _map_follow = true; followShown(_map_center_btn, true); return; } // centres once a fix comes
|
||||
if (gpsAsked(GA_CENTRE)) return;
|
||||
if (!ensureGps(GA_CENTRE) || !_core->course.currentLocation(lat, lon)) { _map_follow = true; followShown(_map_center_btn, true); return; } // centres once a fix comes
|
||||
_map_follow = true;
|
||||
_map_cx = mapview::lonToTileX(lon / 1e6, _map_z);
|
||||
_map_cy = mapview::latToTileY(lat / 1e6, _map_z) + mapCenterBias();
|
||||
|
||||
@@ -100,9 +100,8 @@ static void onTrailDraw(lv_event_t* e) {
|
||||
static lv_obj_t* s_dial = nullptr;
|
||||
static int32_t s_dial_ang = 0; // shown: degrees clockwise from the dial's top
|
||||
static bool s_dial_course = false; // a course is known: the top is ahead, else north
|
||||
static bool s_dial_ahead = false; // within AHEAD_DEG of straight on
|
||||
static bool s_dial_ahead = false; // within geo::AHEAD_DEG of straight on
|
||||
static const int DIAL = 38;
|
||||
static const int AHEAD_DEG = 12;
|
||||
static const uint32_t DIAL_MS = 300;
|
||||
|
||||
static void setDialAng(void* o, int32_t v) {
|
||||
@@ -159,7 +158,7 @@ static void setDial(int deg, bool course) {
|
||||
deg = ((deg % 360) + 360) % 360;
|
||||
const bool snap = course != s_dial_course || lv_obj_has_flag(s_dial, LV_OBJ_FLAG_HIDDEN);
|
||||
s_dial_course = course;
|
||||
s_dial_ahead = course && (deg <= AHEAD_DEG || deg >= 360 - AHEAD_DEG);
|
||||
s_dial_ahead = course && geo::isAhead(geo::turnDeg(deg, 0));
|
||||
lv_obj_remove_flag(s_dial, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_set_style_border_color(s_dial, lv_color_hex(s_dial_ahead ? theme::OK : theme::SURFACE_2), 0);
|
||||
lv_anim_delete(s_dial, setDialAng);
|
||||
@@ -517,7 +516,7 @@ void UITask::refreshNavBar() {
|
||||
navmap::setDial(course ? to - cog : to, course);
|
||||
// Walking: how far to turn (the dial shows it); standing: where the target lies.
|
||||
if (course) {
|
||||
int turn = ((to - cog + 540) % 360) - 180; // -180..179, right is positive
|
||||
int turn = geo::turnDeg(to, cog);
|
||||
if (navmap::s_dial_ahead) snprintf(what, sizeof(what), "straight ahead");
|
||||
else snprintf(what, sizeof(what), "turn %s %d\xC2\xB0", turn > 0 ? "right" : "left", turn > 0 ? turn : -turn);
|
||||
} else {
|
||||
@@ -732,7 +731,7 @@ static void navRowRight(lv_obj_t* row, const char* text, uint32_t col, int right
|
||||
void UITask::placeCard(lv_obj_t* parent, int32_t lat, int32_t lon) {
|
||||
lv_obj_t* card = infoCard(parent);
|
||||
char t[40];
|
||||
snprintf(t, sizeof(t), "%.5f, %.5f", lat / 1e6, lon / 1e6);
|
||||
geo::fmtLatLon(t, sizeof(t), lat, lon, true);
|
||||
infoRow(card, "Coordinates", t);
|
||||
int32_t mlat, mlon;
|
||||
if (!_core->course.currentLocation(mlat, mlon)) return;
|
||||
@@ -831,7 +830,7 @@ void UITask::navAddWaypoint(int32_t lat, int32_t lon) {
|
||||
|
||||
void UITask::navMarkHere() {
|
||||
int32_t lat, lon;
|
||||
if (!ensureGps() || !_core->course.currentLocation(lat, lon)) return;
|
||||
if (!ensureGps(GA_FIX) || !_core->course.currentLocation(lat, lon)) return;
|
||||
uint16_t secs = NodePrefs::gpsAvgSecs(_prefs ? _prefs->gps_avg_idx : 0);
|
||||
if (secs == 0) { navAddWaypoint(lat, lon); return; }
|
||||
if (_core->waypoints.full()) { showToast(waypointsFull()); return; }
|
||||
@@ -1030,14 +1029,10 @@ void UITask::navToolsPopup(bool back) {
|
||||
// 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);
|
||||
switchRow(g, "Hiking trails", "Marked routes in their colours", lvport::trailsOn(), onTrails);
|
||||
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);
|
||||
switchRow(g, "Vector map (test)", "Drawn on the device: trails, contours", lvport::vectorOn(), onVectorMap);
|
||||
char vr[48];
|
||||
int nv = mapview::s_vpacks.count();
|
||||
snprintf(vr, sizeof(vr), nv ? "%d on the card" : "None - packs go in /vmap", nv);
|
||||
@@ -1094,9 +1089,7 @@ void UITask::navToolsPopup(bool back) {
|
||||
int na = mapview::s_areas.count();
|
||||
snprintf(ar, sizeof(ar), na ? "%d on the card - new, rename, refresh, delete" : "None yet - pick one with a frame", na);
|
||||
listRow(g, "Map areas", ar, onNavTool, (void*)(uintptr_t)navmap::TL_AREAS);
|
||||
lv_obj_t* lsw = switchRow(g, "Live tiles", "Load missing tiles over WiFi", nullptr);
|
||||
if (lvport::liveTiles()) lv_obj_add_state(lsw, LV_STATE_CHECKED);
|
||||
lv_obj_add_event_cb(lsw, onLiveTiles, LV_EVENT_VALUE_CHANGED, NULL);
|
||||
switchRow(g, "Live tiles", "Load missing tiles over WiFi", lvport::liveTiles(), onLiveTiles);
|
||||
|
||||
|
||||
refreshNavTools();
|
||||
@@ -1360,10 +1353,11 @@ void UITask::navToolAction(uint8_t act) {
|
||||
switch (act) {
|
||||
case navmap::TL_TRAIL_TOGGLE:
|
||||
if (!tr.isActive()) {
|
||||
if (gpsAsked(GA_TRAIL)) return; // its popup in place of this one
|
||||
tr.setActive(true);
|
||||
int32_t lat, lon;
|
||||
if (_core->course.currentLocation(lat, lon)) showToast("Recording the trail");
|
||||
else ensureGps(); // turns GPS on, or says it's waiting
|
||||
else ensureGps(GA_TRAIL); // says it's waiting
|
||||
} else {
|
||||
tr.setActive(false);
|
||||
showToast("Trail stopped");
|
||||
@@ -1402,11 +1396,12 @@ void UITask::navToolAction(uint8_t act) {
|
||||
break;
|
||||
case navmap::TL_TRACKBACK: {
|
||||
if (navmap::s_tb.active()) { navmap::s_tb.stop(); showToast("Track back stopped"); break; }
|
||||
if (gpsAsked(GA_TRACKBACK)) return; // its popup in place of this one
|
||||
int32_t lat = 0, lon = 0;
|
||||
bool fix = _core->course.currentLocation(lat, lon);
|
||||
if (!navmap::s_tb.start(tr.store(), fix, lat, lon)) { showToast("No trail to walk back"); break; }
|
||||
navmap::s_eta.reset();
|
||||
if (!fix) ensureGps();
|
||||
if (!fix) ensureGps(GA_TRACKBACK); // says it's waiting
|
||||
navClosePopup();
|
||||
navFrameTarget();
|
||||
refreshNavBar();
|
||||
@@ -1421,24 +1416,24 @@ void UITask::navToolAction(uint8_t act) {
|
||||
}
|
||||
case navmap::TL_SHARE_TOGGLE:
|
||||
if (_prefs->loc_share_enabled) {
|
||||
_prefs->loc_share_enabled = 0;
|
||||
_core->live_share.setEnabled(false);
|
||||
showToast("Live share stopped");
|
||||
} else {
|
||||
_prefs->loc_share_enabled = 1;
|
||||
_core->live_share.restartSession();
|
||||
if (gpsAsked(GA_SHARE)) return;
|
||||
_core->live_share.setEnabled(true);
|
||||
int32_t lat, lon;
|
||||
if (_core->course.currentLocation(lat, lon)) showToast("Sharing your position");
|
||||
else ensureGps();
|
||||
else ensureGps(GA_SHARE); // says it's waiting
|
||||
}
|
||||
prefsSave();
|
||||
break;
|
||||
case navmap::TL_SHARE_ONCE: {
|
||||
int32_t lat, lon;
|
||||
if (!ensureGps() || !_core->course.currentLocation(lat, lon)) break;
|
||||
if (!ensureGps(GA_FIX) || !_core->course.currentLocation(lat, lon)) break;
|
||||
// With a session on, straight to its target; otherwise pick a conversation.
|
||||
if (_prefs->loc_share_enabled && _core->live_share.send(lat, lon)) { showToast("Position sent"); break; }
|
||||
char text[40];
|
||||
snprintf(text, sizeof(text), LOCATION_MSG_TAG "%.5f,%.5f", lat / 1e6, lon / 1e6);
|
||||
geo::fmtLocShare(text, sizeof(text), lat, lon);
|
||||
navClosePopup();
|
||||
shareToMessage(text);
|
||||
return;
|
||||
@@ -1520,7 +1515,7 @@ void UITask::navTargetsPopup() {
|
||||
const Waypoint& w = wp.at(i);
|
||||
char title[WAYPOINT_LABEL_LEN + 8], sub[40], d[12];
|
||||
snprintf(title, sizeof(title), UI_SYMBOL_FLAG " %s", w.label[0] ? w.label : "(unnamed)");
|
||||
snprintf(sub, sizeof(sub), "%.5f, %.5f", w.lat_1e6 / 1e6, w.lon_1e6 / 1e6);
|
||||
geo::fmtLatLon(sub, sizeof(sub), w.lat_1e6, w.lon_1e6, true);
|
||||
lv_obj_t* row = listRow(list, title, sub, onNavPick, (void*)(uintptr_t)navmap::code(navmap::T_WAYPOINT, i));
|
||||
lv_obj_t* more = lv_button_create(row); // the waypoint menu
|
||||
lv_obj_set_size(more, 40, 34);
|
||||
|
||||
@@ -294,8 +294,6 @@ static void nodeFavText() {
|
||||
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
|
||||
|
||||
// One of the node's "Can ask for" switches; user data = its TELEM_PERM_* bit.
|
||||
static void onNodeTelemSwitch(lv_event_t* e) {
|
||||
ContactInfo* ci = the_mesh.lookupContactByPubKey(s_node.pub_key, PUB_KEY_SIZE);
|
||||
@@ -366,9 +364,7 @@ void UITask::buildNode() {
|
||||
};
|
||||
uint8_t perms = contactctl::telemetry(*ci);
|
||||
for (const auto& t : T) {
|
||||
lv_obj_t* sw = switchRow(g, t.text, t.sub, nullptr);
|
||||
if (perms & t.bit) lv_obj_add_state(sw, LV_STATE_CHECKED);
|
||||
lv_obj_add_event_cb(sw, onNodeTelemSwitch, LV_EVENT_VALUE_CHANGED, (void*)(uintptr_t)t.bit);
|
||||
switchRow(g, t.text, t.sub, perms & t.bit, onNodeTelemSwitch, (void*)(uintptr_t)t.bit);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -408,7 +404,7 @@ void UITask::refreshNode() {
|
||||
bearing = az;
|
||||
snprintf(dist, sizeof(dist), "%s %d\xC2\xB0 %s", d, az, geo::bearingCardinal(az));
|
||||
}
|
||||
snprintf(pos, sizeof(pos), "%.5f, %.5f", e.lat_e6 / 1e6, e.lon_e6 / 1e6);
|
||||
geo::fmtLatLon(pos, sizeof(pos), e.lat_e6, e.lon_e6, true);
|
||||
} else if (!_node_from_scan) {
|
||||
snprintf(pos, sizeof(pos), "Not shared");
|
||||
}
|
||||
|
||||
@@ -198,15 +198,8 @@ void UITask::buildOta() {
|
||||
lv_bar_set_range(_ota_bar, 0, 1000);
|
||||
lv_obj_set_style_bg_color(_ota_bar, lv_color_hex(theme::SURFACE), LV_PART_MAIN);
|
||||
lv_obj_set_style_bg_color(_ota_bar, lv_color_hex(theme::ACCENT), LV_PART_INDICATOR);
|
||||
lv_obj_t* b = lv_button_create(body);
|
||||
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_add_event_cb(b, onOtaButton, LV_EVENT_CLICKED, NULL);
|
||||
_ota_btn = b;
|
||||
_ota_btn_lbl = label(b, "", THEME_FONT_BODY, theme::TEXT);
|
||||
lv_obj_center(_ota_btn_lbl);
|
||||
stylePrimary(b);
|
||||
_ota_btn = primaryButton(body, "", onOtaButton);
|
||||
_ota_btn_lbl = lv_obj_get_child(_ota_btn, 0);
|
||||
groupNote(body, "From github.com/" OTA_REPO " releases, over WiFi. About a minute; keep the device "
|
||||
"charged. Settings, contacts and messages are kept.");
|
||||
refreshOta();
|
||||
|
||||
@@ -371,15 +371,18 @@ void UITask::quickPanelTile(int t, bool hold) {
|
||||
#endif
|
||||
break;
|
||||
case T_TRAIL:
|
||||
_core->trail.setActive(!_core->trail.isActive());
|
||||
if (_core->trail.isActive()) ensureGps(); // turns GPS on, or says it's waiting
|
||||
break;
|
||||
case T_SHARE:
|
||||
case T_SHARE: {
|
||||
if (!_prefs) break;
|
||||
_prefs->loc_share_enabled = !_prefs->loc_share_enabled;
|
||||
if (_prefs->loc_share_enabled) { _core->live_share.restartSession(); ensureGps(); }
|
||||
prefsSave();
|
||||
const bool on = t == T_TRAIL ? _core->trail.isActive() : _prefs->loc_share_enabled;
|
||||
if (!on && _core->gpsAvailable() && !_core->gpsEnabled()) { // "GPS is off", over the screen
|
||||
quickPanelClose(false);
|
||||
gpsAsked(t == T_TRAIL ? GA_TRAIL : GA_SHARE);
|
||||
return;
|
||||
}
|
||||
if (t == T_TRAIL) _core->trail.setActive(!on);
|
||||
else { _core->live_share.setEnabled(!on); prefsSave(); }
|
||||
break;
|
||||
}
|
||||
}
|
||||
refreshStatusBar();
|
||||
quickPanelRefresh();
|
||||
|
||||
@@ -202,15 +202,11 @@ void UITask::advertSend(bool flood) {
|
||||
static void bluetoothRow(lv_obj_t* body) {
|
||||
char sub[48];
|
||||
qview::bluetoothHint(sub, sizeof(sub));
|
||||
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);
|
||||
lv_obj_t* sw = switchRow(body, LV_SYMBOL_BLUETOOTH " Bluetooth", sub, s_ui->isBluetoothEnabled(), qview::onBtSwitch);
|
||||
#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);
|
||||
switchRowOpens(sw, onBlePin);
|
||||
#else
|
||||
(void)sw;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -111,10 +111,9 @@ void UITask::radioGroup(int i) {
|
||||
int tx = p->tx_power_dbm < TX_MIN ? TX_MIN : p->tx_power_dbm > TX_MAX ? TX_MAX : p->tx_power_dbm;
|
||||
rowDropdown(settingRow(g, "TX power", p->tx_apc ? "Ceiling for auto power" : nullptr), s_opts, tx - TX_MIN, 0,
|
||||
onRadioDropdown, R_TX);
|
||||
lv_obj_t* sw = switchRow(g, "Auto power", p->client_repeat ? "Off while repeating" : "Lowers power on good links", nullptr);
|
||||
if (p->tx_apc) lv_obj_add_state(sw, LV_STATE_CHECKED);
|
||||
lv_obj_t* sw = switchRow(g, "Auto power", p->client_repeat ? "Off while repeating" : "Lowers power on good links",
|
||||
p->tx_apc, onRadioSwitch, (void*)(uintptr_t)R_APC);
|
||||
if (p->client_repeat) lv_obj_add_state(sw, LV_STATE_DISABLED);
|
||||
lv_obj_add_event_cb(sw, onRadioSwitch, LV_EVENT_VALUE_CHANGED, (void*)(uintptr_t)R_APC);
|
||||
}
|
||||
|
||||
void UITask::radioSet(int which, int v) {
|
||||
|
||||
@@ -54,9 +54,7 @@ void UITask::buildRepeater() {
|
||||
NodePrefs* p = _prefs;
|
||||
|
||||
lv_obj_t* g = group(body, nullptr);
|
||||
lv_obj_t* sw = switchRow(g, "Repeater", p->client_repeat ? "Relaying for others" : "Relay others' messages", nullptr);
|
||||
if (p->client_repeat) lv_obj_add_state(sw, LV_STATE_CHECKED);
|
||||
lv_obj_add_event_cb(sw, onRptSwitch, LV_EVENT_VALUE_CHANGED, (void*)(uintptr_t)RP_ON);
|
||||
lv_obj_t* sw = switchRow(g, "Repeater", p->client_repeat ? "Relaying for others" : "Relay others' messages", p->client_repeat, onRptSwitch, (void*)(uintptr_t)RP_ON);
|
||||
s_on_sub = rowSub(lv_obj_get_parent(sw));
|
||||
groupNote(body, "Uses more battery; auto power pauses.");
|
||||
fill(&UITask::repeaterGroup, 1, 3); // the rest as the loop goes
|
||||
|
||||
@@ -12,30 +12,10 @@ static void onKeyboardAlphabet(lv_event_t* e) {
|
||||
s_ui->setKeyboardAlphabets(choiceSelected(s_kb_main_dd), choiceSelected(s_kb_alt_dd));
|
||||
}
|
||||
|
||||
// A preference toggle (0/1 byte in NodePrefs), saved on change.
|
||||
static void onPrefSwitch(lv_event_t* e) {
|
||||
uint8_t* pref = (uint8_t*)lv_event_get_user_data(e);
|
||||
*pref = lv_obj_has_state((lv_obj_t*)lv_event_get_target(e), LV_STATE_CHECKED) ? 1 : 0;
|
||||
prefsSave();
|
||||
}
|
||||
|
||||
static void onGpsSwitch(lv_event_t* e) {
|
||||
s_ui->setGps(lv_obj_has_state((lv_obj_t*)lv_event_get_target(e), LV_STATE_CHECKED));
|
||||
}
|
||||
|
||||
// pref == nullptr: the caller wires the switch itself.
|
||||
static lv_obj_t* switchRow(lv_obj_t* parent, const char* text, const char* sub, uint8_t* pref) {
|
||||
lv_obj_t* row = settingRow(parent, text, sub, 236); // the hint clear of the switch
|
||||
lv_obj_t* sw = lv_switch_create(row);
|
||||
lv_obj_set_size(sw, 46, 24);
|
||||
lv_obj_align(sw, LV_ALIGN_RIGHT_MID, -theme::PAD, 0);
|
||||
if (pref) {
|
||||
if (*pref) lv_obj_add_state(sw, LV_STATE_CHECKED);
|
||||
lv_obj_add_event_cb(sw, onPrefSwitch, LV_EVENT_VALUE_CHANGED, pref);
|
||||
}
|
||||
return sw;
|
||||
}
|
||||
|
||||
// ── Schema-driven settings (ui-core/SettingsSchema.h) ─────────────────────────
|
||||
|
||||
// Settings > Display: ui-new's Message alert as a switch -- the banner, or
|
||||
@@ -44,9 +24,7 @@ static void onMsgBannerSwitch(lv_event_t* e) {
|
||||
s_ui->setMsgBanner(lv_obj_has_state((lv_obj_t*)lv_event_get_target(e), LV_STATE_CHECKED));
|
||||
}
|
||||
void UITask::msgBannerRow(lv_obj_t* card) {
|
||||
lv_obj_t* sw = switchRow(card, "Message banner", "A new message drops in from the top", nullptr);
|
||||
if (!_prefs || _prefs->msg_alert != NodePrefs::MSG_ALERT_OFF) lv_obj_add_state(sw, LV_STATE_CHECKED);
|
||||
lv_obj_add_event_cb(sw, onMsgBannerSwitch, LV_EVENT_VALUE_CHANGED, NULL);
|
||||
switchRow(card, "Message banner", "A new message drops in from the top", !_prefs || _prefs->msg_alert != NodePrefs::MSG_ALERT_OFF, onMsgBannerSwitch);
|
||||
}
|
||||
void UITask::setMsgBanner(bool on) {
|
||||
if (!_prefs) return;
|
||||
@@ -161,9 +139,7 @@ void UITask::buildSchemaSettings() {
|
||||
schemaRow(g, SETTING(display_brightness));
|
||||
schemaRow(g, SETTING(auto_off_secs));
|
||||
schemaRow(g, SETTING(msg_wake));
|
||||
lv_obj_t* tw = switchRow(g, "Tap to wake", "Off: only the top button wakes it", nullptr); // in NVS
|
||||
if (_tap_wake) lv_obj_add_state(tw, LV_STATE_CHECKED);
|
||||
lv_obj_add_event_cb(tw, onTapWake, LV_EVENT_VALUE_CHANGED, NULL);
|
||||
switchRow(g, "Tap to wake", "Off: only the top button wakes it", _tap_wake, onTapWake);
|
||||
g = group(body, "LOCK");
|
||||
schemaRow(g, SETTING(auto_lock));
|
||||
listRow(g, "Screen PIN", pinSet() ? "On - asked when the screen wakes" : "Off", onPinSetup, NULL);
|
||||
@@ -207,7 +183,7 @@ enum : int { OWNPOS_TYPE, OWNPOS_WAYPOINTS, OWNPOS_CLEAR, OWNPOS_WP0 = 100 };
|
||||
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 if (_core->fixedPosition(lat, lon)) geo::fmtLatLon(sub, n, lat, lon, true);
|
||||
else snprintf(sub, n, "Not set");
|
||||
}
|
||||
|
||||
@@ -230,7 +206,7 @@ void UITask::ownPositionPick(int code) {
|
||||
if (code == OWNPOS_TYPE) {
|
||||
int32_t lat, lon;
|
||||
char cur[32] = "";
|
||||
if (_core->fixedPosition(lat, lon)) snprintf(cur, sizeof(cur), "%.5f, %.5f", lat / 1e6, lon / 1e6);
|
||||
if (_core->fixedPosition(lat, lon)) geo::fmtLatLon(cur, sizeof(cur), lat, lon, true);
|
||||
TextEntry t = { "Fixed position", onOwnPosKb };
|
||||
t.text = cur;
|
||||
t.hint = "50.06143, 19.93658";
|
||||
@@ -246,7 +222,7 @@ void UITask::ownPositionPick(int code) {
|
||||
const Waypoint& w = wp.at(i);
|
||||
char title[WAYPOINT_LABEL_LEN + 8], sub[40];
|
||||
snprintf(title, sizeof(title), UI_SYMBOL_FLAG " %s", w.label[0] ? w.label : "(unnamed)");
|
||||
snprintf(sub, sizeof(sub), "%.5f, %.5f", w.lat_1e6 / 1e6, w.lon_1e6 / 1e6);
|
||||
geo::fmtLatLon(sub, sizeof(sub), w.lat_1e6, w.lon_1e6, true);
|
||||
listRow(list, title, sub, onOwnPosPick, (void*)(uintptr_t)(OWNPOS_WP0 + i));
|
||||
}
|
||||
return;
|
||||
@@ -488,9 +464,7 @@ void UITask::schemaRow(lv_obj_t* card, int i) {
|
||||
return;
|
||||
}
|
||||
if (!st.option) {
|
||||
lv_obj_t* sw = switchRow(card, st.label, st.hint, nullptr);
|
||||
if (v) lv_obj_add_state(sw, LV_STATE_CHECKED);
|
||||
lv_obj_add_event_cb(sw, onSchemaSwitch, LV_EVENT_VALUE_CHANGED, (void*)(uintptr_t)i);
|
||||
switchRow(card, st.label, st.hint, v, onSchemaSwitch, (void*)(uintptr_t)i);
|
||||
return;
|
||||
}
|
||||
// The longest list, the time zone, is 27 options and 167 bytes: at 160
|
||||
@@ -584,16 +558,9 @@ void UITask::settingsGroup(int i) {
|
||||
g = group(body, "CONNECTIONS");
|
||||
bluetoothRow(g);
|
||||
wifiRow(g);
|
||||
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);
|
||||
rowPressable(row);
|
||||
lv_obj_add_event_cb(row, [](lv_event_t* e) {
|
||||
if (lv_event_get_target(e) == lv_event_get_current_target(e)) onOpenGpsFromSettings(e); // not the switch
|
||||
}, LV_EVENT_CLICKED, NULL);
|
||||
}
|
||||
if (_core->gpsAvailable())
|
||||
switchRowOpens(switchRow(g, LV_SYMBOL_GPS " GPS", "Tap for satellites and options", _core->gpsEnabled(), onGpsSwitch),
|
||||
onOpenGpsFromSettings);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
|
||||
@@ -352,7 +352,7 @@ void UITask::sosRow(int row) {
|
||||
for (int i = 0; i < MAX_GROUP_CHANNELS; i++) {
|
||||
ChannelDetails ch;
|
||||
if (!the_mesh.getChannel(i, ch) || !ch.name[0]) continue;
|
||||
switchRowIdx(g, ch.name, nullptr, p->sos_ch_mask & (1ULL << i), onSosChannel, i);
|
||||
switchRow(g, ch.name, nullptr, (p->sos_ch_mask >> i) & 1, onSosChannel, (void*)(uintptr_t)i);
|
||||
n++;
|
||||
}
|
||||
if (n == 0) label(list, "No channels.", THEME_FONT_SMALL, theme::TEXT_MUTED);
|
||||
|
||||
@@ -930,9 +930,9 @@ void UITask::setGps(bool on) {
|
||||
|
||||
void UITask::botSetGPS(bool on) { if (_core->setGpsEnabled(on)) refreshStatusBar(); }
|
||||
|
||||
bool UITask::ensureGps() {
|
||||
bool UITask::ensureGps(GpsAsk what) {
|
||||
int32_t lat, lon;
|
||||
if (_core->gpsAvailable() && !_core->gpsEnabled()) { setGps(true); return false; } // off, its last fix still reads valid
|
||||
if (gpsAsked(what)) return false; // off, its last fix still reads valid
|
||||
if (_core->course.currentLocation(lat, lon)) return true;
|
||||
showToast("Waiting for a GPS fix");
|
||||
return false;
|
||||
@@ -1516,6 +1516,7 @@ extern "C" EMSCRIPTEN_KEEPALIVE void sim_open(const char* name) {
|
||||
if (!strncmp(name, "accent@", 7)) { theme::setAccent(atoi(name + 7)); return; } // "accent@4": Cyan
|
||||
if (!strncmp(name, "map@", 4)) { s_ui->simMapAt(name + 4); return; } // "map@lat,lon,z"
|
||||
if (!strncmp(name, "channel@", 8)) { s_ui->openChannel(atoi(name + 8)); return; }
|
||||
if (!strcmp(name, "chanedit")) { s_ui->showChannelEdit(-1); return; } // a new channel
|
||||
if (!strncmp(name, "compose@", 8)) { s_ui->simCompose(name + 8); return; }
|
||||
if (!strcmp(name, "dm")) { s_ui->simDm(); return; }
|
||||
if (!strcmp(name, "send")) { s_ui->simSend(); return; }
|
||||
|
||||
@@ -404,7 +404,12 @@ public:
|
||||
void pinPopup(bool channel, uint8_t ch_idx, const uint8_t* pub_key);
|
||||
void pinTo(int slot);
|
||||
void setGps(bool on);
|
||||
bool ensureGps(); // true with a fix; else turns GPS on / says it's waiting
|
||||
// What needs the GPS while it's off: asked first ("GPS is off"), then done
|
||||
// once it's turned on (GpsScreen.h).
|
||||
enum GpsAsk : uint8_t { GA_TRAIL, GA_SHARE, GA_TRACKBACK, GA_CENTRE, GA_FIX };
|
||||
bool gpsAsked(GpsAsk what); // true: it was off, the question is up
|
||||
bool ensureGps(GpsAsk what); // true with a fix; else asks, or says it's waiting
|
||||
void gpsAskDone(GpsAsk what);
|
||||
|
||||
// Kept for main.cpp's sim hooks (ui-new API).
|
||||
void openContactDM(const ContactInfo& ci) { openDM(ci.id.pub_key); }
|
||||
|
||||
@@ -1071,6 +1071,19 @@ static void stylePrimary(lv_obj_t* b) {
|
||||
lv_obj_set_style_text_color(lv_obj_get_child(b, i), lv_color_hex(theme::ON_ACCENT), 0);
|
||||
}
|
||||
|
||||
// The one main action under a form or a list, full width (Save, Measure ...).
|
||||
// Its label is child 0.
|
||||
static lv_obj_t* primaryButton(lv_obj_t* parent, const char* text, lv_event_cb_t cb, void* user = nullptr) {
|
||||
lv_obj_t* b = lv_button_create(parent);
|
||||
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_add_event_cb(b, cb, LV_EVENT_CLICKED, user);
|
||||
lv_obj_center(label(b, text, THEME_FONT_BODY, theme::TEXT));
|
||||
stylePrimary(b);
|
||||
return b;
|
||||
}
|
||||
|
||||
// Something selected / on (a tab, a chip, a row): SEL_BG with SEL_FG text
|
||||
// (Theme.h), or back to the surface with TEXT. Every label under it follows.
|
||||
static void textUnder(lv_obj_t* o, uint32_t col) {
|
||||
@@ -1256,6 +1269,52 @@ static void prefsFlush() {
|
||||
the_mesh.savePrefs();
|
||||
}
|
||||
|
||||
// A switch, one size on every screen.
|
||||
static lv_obj_t* switchCreate(lv_obj_t* parent, bool on, lv_event_cb_t cb, void* user) {
|
||||
lv_obj_t* sw = lv_switch_create(parent);
|
||||
lv_obj_set_size(sw, 46, 24);
|
||||
if (on) lv_obj_add_state(sw, LV_STATE_CHECKED);
|
||||
if (cb) lv_obj_add_event_cb(sw, cb, LV_EVENT_VALUE_CHANGED, user);
|
||||
return sw;
|
||||
}
|
||||
|
||||
// A preference toggle (0/1 byte in NodePrefs), saved on change.
|
||||
static void onPrefSwitch(lv_event_t* e) {
|
||||
uint8_t* pref = (uint8_t*)lv_event_get_user_data(e);
|
||||
*pref = lv_obj_has_state((lv_obj_t*)lv_event_get_target(e), LV_STATE_CHECKED) ? 1 : 0;
|
||||
prefsSave();
|
||||
}
|
||||
|
||||
// A settings row with a switch on the right. pref == nullptr: the caller wires
|
||||
// the switch itself.
|
||||
static lv_obj_t* switchRow(lv_obj_t* parent, const char* text, const char* sub, uint8_t* pref) {
|
||||
lv_obj_t* row = settingRow(parent, text, sub, 236); // the hint clear of the switch
|
||||
lv_obj_t* sw = switchCreate(row, pref && *pref, pref ? onPrefSwitch : nullptr, pref);
|
||||
lv_obj_align(sw, LV_ALIGN_RIGHT_MID, -theme::PAD, 0);
|
||||
return sw;
|
||||
}
|
||||
|
||||
// The same for a switch that isn't a plain pref: `cb` gets `user` on a change.
|
||||
static lv_obj_t* switchRow(lv_obj_t* parent, const char* text, const char* sub, bool on, lv_event_cb_t cb,
|
||||
void* user = nullptr) {
|
||||
lv_obj_t* sw = switchRow(parent, text, sub, nullptr);
|
||||
if (on) lv_obj_add_state(sw, LV_STATE_CHECKED);
|
||||
lv_obj_add_event_cb(sw, cb, LV_EVENT_VALUE_CHANGED, user);
|
||||
return sw;
|
||||
}
|
||||
|
||||
// A switch row whose text opens the details (`open`) while the switch only
|
||||
// switches: Bluetooth, WiFi, GPS.
|
||||
static void onSwitchRowTap(lv_event_t* e) {
|
||||
if (lv_event_get_target(e) != lv_event_get_current_target(e)) return; // the switch
|
||||
((lv_event_cb_t)lv_event_get_user_data(e))(e);
|
||||
}
|
||||
static void switchRowOpens(lv_obj_t* sw, lv_event_cb_t open) {
|
||||
lv_obj_t* row = lv_obj_get_parent(sw);
|
||||
rowPressable(row);
|
||||
lv_obj_add_event_cb(row, onSwitchRowTap, LV_EVENT_CLICKED, (void*)open);
|
||||
}
|
||||
|
||||
// The big clock in the boot splash's lettering, as ui-new draws it on the
|
||||
// OLED and e-ink (ui-core/Lettering.h: block digits in 3 px strokes): HH:MM
|
||||
// at SC x, the seconds after them at SC_SEC x on the same baseline. The
|
||||
|
||||
@@ -72,9 +72,7 @@ void UITask::buildWifi() {
|
||||
{ // 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);
|
||||
switchRow(group(body, nullptr), LV_SYMBOL_WIFI " WiFi", sub, lvport::wifiAllowed(), onWifiAllowed);
|
||||
groupNote(body, "For map downloads, live map tiles and updates.");
|
||||
}
|
||||
|
||||
@@ -129,13 +127,7 @@ void UITask::buildWifi() {
|
||||
|
||||
_wifi_pass = wifiField(body, "Password", true);
|
||||
|
||||
lv_obj_t* save = lv_button_create(body);
|
||||
lv_obj_set_size(save, LV_PCT(100), 38);
|
||||
lv_obj_set_style_shadow_width(save, 0, 0);
|
||||
lv_obj_set_style_radius(save, theme::RADIUS, 0);
|
||||
lv_obj_add_event_cb(save, onWifiSave, LV_EVENT_CLICKED, NULL);
|
||||
lv_obj_center(label(save, LV_SYMBOL_OK " Save", THEME_FONT_BODY, theme::TEXT));
|
||||
stylePrimary(save);
|
||||
primaryButton(body, LV_SYMBOL_OK " Save", onWifiSave);
|
||||
_wifi_status = label(body, "", THEME_FONT_SMALL, theme::TEXT_MUTED);
|
||||
|
||||
// Keyboard over the bottom of the screen; the body shrinks above it while
|
||||
@@ -182,14 +174,8 @@ void UITask::wifiKeyboardHide() {
|
||||
static void wifiRow(lv_obj_t* body) {
|
||||
char sub[48];
|
||||
wifiHint(sub, sizeof(sub));
|
||||
lv_obj_t* sw = switchRow(body, 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);
|
||||
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)) s_ui->showWifi(); // not the switch
|
||||
}, LV_EVENT_CLICKED, NULL);
|
||||
switchRowOpens(switchRow(body, LV_SYMBOL_WIFI " WiFi", sub, lvport::wifiAllowed(), onWifiAllowed),
|
||||
[](lv_event_t* e) { (void)e; s_ui->showWifi(); });
|
||||
}
|
||||
|
||||
void UITask::wifiSetAllowed(bool on) {
|
||||
|
||||
Reference in New Issue
Block a user