fix(ui-lvgl): smooth WiFi/Bluetooth switches; retry 404 tiles

- The WiFi and Bluetooth rows rebuilt the whole Settings page to update
  their hint, cutting the switch's animation short; the hint is now
  rewritten in place (rowHintSet). The "Bluetooth on" echo toast is gone.
- Map download: a 404 gets the same one retry as other errors
  (OpenTopoMap sends the odd 404 for a tile not rendered yet), and a
  download that ends with gaps says "Done, N missing - download again".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-09-29 10:41:18 +02:00
co-authored by Claude Opus 5.5
parent 890ee6533e
commit bc266a4f5c
4 changed files with 47 additions and 33 deletions
+14 -14
View File
@@ -46,8 +46,18 @@ static void onQuickEditPh(lv_event_t* e) { s_ui->quickEditInsert((int)(uintptr_
static void onQuickEditKb(lv_event_t* e) { s_ui->quickEditDone(lv_event_get_code(e) == LV_EVENT_READY); }
static void onQuickList(lv_event_t* e) { (void)e; s_ui->showQuickMsgs(); }
static void onAdvertGo(lv_event_t* e) { s_ui->advertSend((uintptr_t)lv_event_get_user_data(e) != 0); }
static void bluetoothHint(char* sub, size_t n) {
if (!s_ui->isSerialEnabled()) snprintf(sub, n, "Off");
else if (s_ui->hasConnection()) snprintf(sub, n, "App connected");
else if (the_mesh.getBLEPin()) snprintf(sub, n, "PIN %06lu", (unsigned long)the_mesh.getBLEPin());
else snprintf(sub, n, "Waiting for the app");
}
static void onBtSwitch(lv_event_t* e) {
s_ui->setBluetooth(lv_obj_has_state((lv_obj_t*)lv_event_get_target(e), LV_STATE_CHECKED));
lv_obj_t* sw = (lv_obj_t*)lv_event_get_target(e);
s_ui->setBluetooth(lv_obj_has_state(sw, LV_STATE_CHECKED));
char sub[48];
bluetoothHint(sub, sizeof(sub));
rowHintSet(lv_obj_get_parent(sw), sub);
}
} // namespace qview
@@ -210,25 +220,15 @@ void UITask::advertSend(bool flood) {
static void bluetoothRow(lv_obj_t* body) {
char sub[48];
bool on = s_ui->isSerialEnabled();
if (!on) snprintf(sub, sizeof(sub), "Off");
else if (s_ui->hasConnection()) snprintf(sub, sizeof(sub), "App connected");
else if (the_mesh.getBLEPin()) snprintf(sub, sizeof(sub), "PIN %06lu", (unsigned long)the_mesh.getBLEPin());
else snprintf(sub, sizeof(sub), "Waiting for the app");
qview::bluetoothHint(sub, sizeof(sub));
lv_obj_t* sw = switchRow(body, LV_SYMBOL_BLUETOOTH " Bluetooth", sub, nullptr);
if (on) lv_obj_add_state(sw, LV_STATE_CHECKED);
if (s_ui->isSerialEnabled()) lv_obj_add_state(sw, LV_STATE_CHECKED);
lv_obj_add_event_cb(sw, qview::onBtSwitch, LV_EVENT_VALUE_CHANGED, NULL);
}
void UITask::setBluetooth(bool on) {
if (on) enableSerial(); else disableSerial();
showToast(on ? "Bluetooth on" : "Bluetooth off - USB still works", 1500);
if (_screen == SCR_SETTINGS) { // the row's subtitle
lv_obj_t* body = _body;
int32_t y = body ? lv_obj_get_scroll_y(body) : 0;
buildSettings();
if (_body) { lv_obj_update_layout(_body); lv_obj_scroll_to_y(_body, y, LV_ANIM_OFF); }
}
if (!on) showToast("Bluetooth off - USB still works", 1500);
refreshStatusBar();
}
+8 -2
View File
@@ -373,6 +373,13 @@ static lv_obj_t* groupText(lv_obj_t* row, const char* text, const char* hint, ui
// The title label of a settingRow() / listRow(), to recolour or rename it.
static lv_obj_t* rowTitle(lv_obj_t* row) { return (lv_obj_t*)lv_obj_get_user_data(row); }
// Rewrites the hint under a row's title in place (the label made right after
// it) -- a rebuild would cut short the animation of the switch just tapped.
static void rowHintSet(lv_obj_t* row, const char* hint) {
lv_obj_t* h = lv_obj_get_sibling(rowTitle(row), 1);
if (h && lv_obj_check_type(h, &lv_label_class)) lv_label_set_text(h, hint);
}
// A settings row: the label left (a muted hint under it, cut at `hint_w`),
// room on the right for a switch / dropdown / slider.
static lv_obj_t* settingRow(lv_obj_t* parent, const char* text, const char* hint, int hint_w = 150) {
@@ -2983,8 +2990,7 @@ void UITask::refreshThread() {
if (n == 0 && _thread_skip > 0) { // the page went away (history trimmed)
_thread_skip = 0;
n = loadThreadPage(total);
}
if (total > _thread_skip + n) {
} if (total > _thread_skip + n) {
char t[40];
snprintf(t, sizeof(t), LV_SYMBOL_UP " Older messages (%d)", total - _thread_skip - n);
pageButton(_thread_list, t, 1);
+14 -14
View File
@@ -33,8 +33,19 @@ static void onWifiForget(lv_event_t* e) {
s_wifi_forget_btn = nullptr;
s_ui->wifiForget((int)(uintptr_t)lv_event_get_user_data(e));
}
static void wifiHint(char* sub, size_t n) {
int saved = lvport::wifiSavedCount();
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");
}
static void onWifiAllowed(lv_event_t* e) {
s_ui->wifiSetAllowed(lv_obj_has_state((lv_obj_t*)lv_event_get_target(e), LV_STATE_CHECKED));
lv_obj_t* sw = (lv_obj_t*)lv_event_get_target(e);
s_ui->wifiSetAllowed(lv_obj_has_state(sw, LV_STATE_CHECKED));
char sub[48];
wifiHint(sub, sizeof(sub));
rowHintSet(lv_obj_get_parent(sw), sub);
}
static void onWifiField(lv_event_t* e) { s_ui->wifiEdit((lv_obj_t*)lv_event_get_target(e)); }
static void onWifiKb(lv_event_t* e) {
@@ -165,14 +176,9 @@ void UITask::wifiKeyboardHide() {
// on / off, the row opens the network settings.
static void wifiRow(lv_obj_t* body) {
char sub[48];
int n = lvport::wifiSavedCount();
bool on = lvport::wifiAllowed();
if (!on) snprintf(sub, sizeof(sub), "Off");
else if (n == 1) snprintf(sub, sizeof(sub), "%s", lvport::wifiSaved(0)->ssid);
else if (n > 1) snprintf(sub, sizeof(sub), "%d saved networks", n);
else snprintf(sub, sizeof(sub), "Tap to pick a network");
wifiHint(sub, sizeof(sub));
lv_obj_t* sw = switchRow(body, LV_SYMBOL_WIFI " WiFi", sub, nullptr);
if (on) lv_obj_add_state(sw, LV_STATE_CHECKED);
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);
lv_obj_add_flag(row, LV_OBJ_FLAG_CLICKABLE);
@@ -189,12 +195,6 @@ void UITask::wifiSetAllowed(bool on) {
if (mapview::s_dl.active()) mapDownloadStop();
if (_wifi_scanning) { _wifi_scanning = false; lvport::netEnd(); }
}
if (_screen == SCR_SETTINGS) { // the row's subtitle
lv_obj_t* body = _body;
int32_t y = body ? lv_obj_get_scroll_y(body) : 0;
buildSettings();
if (_body) { lv_obj_update_layout(_body); lv_obj_scroll_to_y(_body, y, LV_ANIM_OFF); }
}
}
void UITask::wifiScan() {
@@ -228,9 +228,11 @@ public:
else if (isPlaceholder(data, len)) { lvport::fetchRelease(); finish(FAILED, "Server sends a placeholder (blocked / key?)"); return; }
else if (!writeTile(_z, _x, _y, data, len)) { lvport::fetchRelease(); finish(FAILED, "Can't write to the SD card"); return; }
else { _done++; _consec_fail = 0; }
} else if (!_retried && r != -403 && r != -401 && r != -404) {
} else if (!_retried && r != -403 && r != -401) {
// One more try after a pause: a dropped keep-alive connection or a
// busy server usually answers the second time.
// busy server usually answers the second time. A 404 too: both tile
// servers answer every tile of the world, and OpenTopoMap sends the
// odd 404 for a tile it hasn't rendered yet.
lvport::fetchRelease();
_retried = true;
_last_start = millis() + 1000;
@@ -247,7 +249,13 @@ public:
// Skip tiles already on the card (a few stat()s per pass), then start the next GET.
for (int i = 0; i < 16 && _state == RUNNING; i++) {
if (_z > _area.zmax) { finish(DONE, "Done"); return; }
if (_z > _area.zmax) {
// Tiles on the card are skipped, so the same download again fetches just the missing ones.
char m[48];
if (_failed) snprintf(m, sizeof(m), "Done, %lu missing - download again", (unsigned long)_failed);
finish(DONE, _failed ? m : "Done");
return;
}
char path[96];
tilePath(path, sizeof(path), _z, _x, _y, _layer);
struct stat st;