v2.3.2: Add info button to WiFi Manager screen explaining multi-network support

This commit is contained in:
Eiswolf-BG
2026-08-05 16:01:29 +02:00
parent 55c8c6be0b
commit 0b66101858
8 changed files with 139 additions and 0 deletions

View File

@@ -145,6 +145,10 @@ enum class StringId : uint8_t {
WIFI_NETWORKS_TITLE,
WIFI_ADD_NETWORK,
WIFI_EMPTY_SLOT,
WIFI_INFO_TITLE,
WIFI_INFO_PARA1,
WIFI_INFO_PARA2,
WIFI_INFO_PARA3,
COUNT
};

View File

@@ -145,6 +145,11 @@ static const char* const I18N_DE[] = {
"WLAN-Netzwerke",
"Netzwerk hinzufügen",
"(leer)",
"Wie WLAN-Netzwerke funktionieren",
"Du kannst bis zu 3 WLAN-Netzwerke speichern. Beim Start verbindet sich das Gerät automatisch mit dem, das gerade erreichbar ist.",
"Praktisch für unterwegs (Büro, Freunde, Ferienhaus), für Gast- und Haupt-WLAN am selben Router, oder wenn du mehrere Access Points im Haus hast.",
"Über das rote 'X' kannst du ein gespeichertes Netzwerk jederzeit wieder entfernen, um Platz für ein neues zu schaffen.",
};
static_assert(sizeof(I18N_DE) / sizeof(I18N_DE[0]) == (size_t)StringId::COUNT,

View File

@@ -146,6 +146,11 @@ static const char* const I18N_EN[] = {
"WiFi networks",
"Add network",
"(empty)",
"How WiFi networks work",
"You can save up to 3 WiFi networks. On boot, the device automatically connects to whichever one it can currently reach.",
"Handy for portable use (office, friends, holiday home), for guest + main WiFi on the same router, or if you have multiple access points around the house.",
"Remove a saved network anytime via the red 'X' to free up a slot for a new one.",
};
static_assert(sizeof(I18N_EN) / sizeof(I18N_EN[0]) == (size_t)StringId::COUNT,

View File

@@ -145,6 +145,11 @@ static const char* const I18N_ES[] = {
"Redes WiFi",
"Añadir red",
"(vacío)",
"Cómo funcionan las redes WiFi",
"Puede guardar hasta 3 redes WiFi. Al iniciar, el dispositivo se conecta automáticamente a la que pueda alcanzar en ese momento.",
"Útil para uso móvil (oficina, amigos, casa de vacaciones), para WiFi de invitados + principal en el mismo router, o si tiene varios puntos de acceso en casa.",
"Elimine una red guardada en cualquier momento con la 'X' roja para liberar un espacio para una nueva.",
};
static_assert(sizeof(I18N_ES) / sizeof(I18N_ES[0]) == (size_t)StringId::COUNT,

View File

@@ -145,6 +145,11 @@ static const char* const I18N_FR[] = {
"Réseaux WiFi",
"Ajouter un réseau",
"(vide)",
"Comment marchent les réseaux WiFi",
"Vous pouvez enregistrer jusqu'à 3 réseaux WiFi. Au démarrage, l'appareil se connecte automatiquement à celui qu'il peut atteindre.",
"Pratique en déplacement (bureau, amis, maison de vacances), pour un WiFi invité + principal sur le même routeur, ou si vous avez plusieurs points d'accès.",
"Supprimez un réseau enregistré à tout moment via le 'X' rouge pour libérer une place pour un nouveau.",
};
static_assert(sizeof(I18N_FR) / sizeof(I18N_FR[0]) == (size_t)StringId::COUNT,

View File

@@ -145,6 +145,11 @@ static const char* const I18N_IT[] = {
"Reti WiFi",
"Aggiungi rete",
"(vuoto)",
"Come funzionano le reti WiFi",
"Puoi salvare fino a 3 reti WiFi. All'avvio, il dispositivo si connette automaticamente a quella che riesce a raggiungere in quel momento.",
"Utile per l'uso portatile (ufficio, amici, casa vacanze), per WiFi ospiti + principale sullo stesso router, o se hai più punti di accesso in casa.",
"Rimuovi una rete salvata in qualsiasi momento tramite la 'X' rossa per liberare uno slot per una nuova.",
};
static_assert(sizeof(I18N_IT) / sizeof(I18N_IT[0]) == (size_t)StringId::COUNT,

View File

@@ -145,6 +145,11 @@ static const char* const I18N_TR[] = {
"WiFi ağları",
"Ağ ekle",
"(boş)",
"WiFi ağları nasıl çalışır",
"3 adete kadar WiFi ağı kaydedebilirsiniz. Başlangıçta cihaz, o an erişebildiği ağa otomatik olarak bağlanır.",
"Taşınabilir kullanım için kullanışlıdır (ofis, arkadaşlar, tatil evi), aynı yönlendiricide misafir + ana WiFi için, veya evde birden fazla erişim noktanız varsa.",
"Kayıtlı bir ağı istediğiniz zaman kırmızı 'X' ile kaldırarak yeni bir ağ için yer açabilirsiniz.",
};
static_assert(sizeof(I18N_TR) / sizeof(I18N_TR[0]) == (size_t)StringId::COUNT,

View File

@@ -25,6 +25,104 @@ namespace {
tft.drawString(label, r.x + r.w / 2, r.y + r.h / 2);
tft.setTextDatum(TL_DATUM);
}
int16_t layoutWrapped(TFT_eSPI& tft, int16_t x, int16_t startY, int16_t maxWidth,
int16_t lineHeight, const String& text, int16_t scrollY,
int16_t viewTop, int16_t viewBottom, bool draw) {
int16_t y = startY;
int32_t start = 0;
int32_t len = text.length();
while (start < len) {
while (start < len && text[start] == ' ') start++;
if (start >= len) break;
String line = text.substring(start, len);
while (tft.textWidth(line) > maxWidth) {
int32_t lastSpace = line.lastIndexOf(' ');
if (lastSpace <= 0) break;
line = line.substring(0, lastSpace);
}
if (draw) {
int16_t screenY = y - scrollY;
if (screenY >= viewTop && screenY <= viewBottom) {
tft.setCursor(x, screenY);
tft.print(line);
}
}
y += lineHeight;
start += line.length();
}
return y;
}
void runInfoScreen(TFT_eSPI& tft) {
MenuStars::reset();
constexpr int16_t textMaxWidth = Config::SCREEN_WIDTH - 20;
constexpr int16_t LINE_H = 16;
constexpr int16_t VIEW_TOP = 40;
Rect backBtn = {10, (int16_t)(Config::SCREEN_HEIGHT - 50), (int16_t)(Config::SCREEN_WIDTH - 20), 40};
constexpr int16_t VIEW_BOTTOM = Config::SCREEN_HEIGHT - 60;
int16_t totalH = VIEW_TOP;
totalH = layoutWrapped(tft, 10, totalH, textMaxWidth, LINE_H, I18n::t(StringId::WIFI_INFO_PARA1), 0, 0, 0, false);
totalH += 8;
totalH = layoutWrapped(tft, 10, totalH, textMaxWidth, LINE_H, I18n::t(StringId::WIFI_INFO_PARA2), 0, 0, 0, false);
totalH += 8;
totalH = layoutWrapped(tft, 10, totalH, textMaxWidth, LINE_H, I18n::t(StringId::WIFI_INFO_PARA3), 0, 0, 0, false);
int16_t maxScroll = totalH - VIEW_BOTTOM;
if (maxScroll < 0) maxScroll = 0;
bool scrollable = maxScroll > 0;
int16_t scrollY = 0;
Rect upBtn = {(int16_t)(Config::SCREEN_WIDTH - 34), 4, 30, 24};
Rect downBtn = {(int16_t)(Config::SCREEN_WIDTH - 34), 30, 30, 24};
constexpr int16_t SCROLL_STEP = 48;
auto redraw = [&]() {
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.setCursor(10, 14);
tft.println(I18n::t(StringId::WIFI_INFO_TITLE));
if (scrollable) {
drawButton(tft, upBtn, "^");
drawButton(tft, downBtn, "v");
}
tft.setTextColor(TFT_GREEN, TFT_BLACK);
int16_t y = VIEW_TOP;
y = layoutWrapped(tft, 10, y, textMaxWidth, LINE_H, I18n::t(StringId::WIFI_INFO_PARA1), scrollY, VIEW_TOP, VIEW_BOTTOM, true);
y += 8;
y = layoutWrapped(tft, 10, y, textMaxWidth, LINE_H, I18n::t(StringId::WIFI_INFO_PARA2), scrollY, VIEW_TOP, VIEW_BOTTOM, true);
y += 8;
layoutWrapped(tft, 10, y, textMaxWidth, LINE_H, I18n::t(StringId::WIFI_INFO_PARA3), scrollY, VIEW_TOP, VIEW_BOTTOM, true);
drawButton(tft, backBtn, I18n::t(StringId::BACK));
};
redraw();
while (true) {
TouchInput::Point tap;
if (TouchInput::wasTapped(tap)) {
if (backBtn.contains(tap.x, tap.y)) return;
if (scrollable && upBtn.contains(tap.x, tap.y) && scrollY > 0) {
scrollY -= SCROLL_STEP;
if (scrollY < 0) scrollY = 0;
redraw();
} else if (scrollable && downBtn.contains(tap.x, tap.y) && scrollY < maxScroll) {
scrollY += SCROLL_STEP;
if (scrollY > maxScroll) scrollY = maxScroll;
redraw();
}
}
MenuStars::update(tft);
delay(20);
}
}
}
void run(TFT_eSPI& tft) {
@@ -40,6 +138,9 @@ void run(TFT_eSPI& tft) {
tft.setCursor(10, 14);
tft.println(I18n::t(StringId::WIFI_NETWORKS_TITLE));
Rect infoBtn = {(int16_t)(Config::SCREEN_WIDTH - 40), 2, 30, 24};
drawButton(tft, infoBtn, "?");
uint8_t count = WifiMgr::networkCount();
int16_t y = 40;
@@ -84,6 +185,10 @@ void run(TFT_eSPI& tft) {
}
bool handled = false;
if (infoBtn.contains(tap.x, tap.y)) {
runInfoScreen(tft);
handled = true;
}
for (uint8_t i = 0; i < count && !handled; i++) {
if (removeRects[i].contains(tap.x, tap.y)) {
WifiMgr::removeNetwork(i);