From 0b66101858f838f8ca5699ab7e33b4dac421f7d4 Mon Sep 17 00:00:00 2001 From: Eiswolf-BG <37225519+Eiswolf-BG@users.noreply.github.com> Date: Wed, 5 Aug 2026 16:01:29 +0200 Subject: [PATCH] v2.3.2: Add info button to WiFi Manager screen explaining multi-network support --- src/i18n.h | 4 ++ src/i18n_de.h | 5 ++ src/i18n_en.h | 5 ++ src/i18n_es.h | 5 ++ src/i18n_fr.h | 5 ++ src/i18n_it.h | 5 ++ src/i18n_tr.h | 5 ++ src/wifi_manage_screen.cpp | 105 +++++++++++++++++++++++++++++++++++++ 8 files changed, 139 insertions(+) diff --git a/src/i18n.h b/src/i18n.h index cb3e2fe..b2a7032 100644 --- a/src/i18n.h +++ b/src/i18n.h @@ -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 }; diff --git a/src/i18n_de.h b/src/i18n_de.h index 0482f34..1e8a812 100644 --- a/src/i18n_de.h +++ b/src/i18n_de.h @@ -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, diff --git a/src/i18n_en.h b/src/i18n_en.h index fcbb6df..12f1111 100644 --- a/src/i18n_en.h +++ b/src/i18n_en.h @@ -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, diff --git a/src/i18n_es.h b/src/i18n_es.h index 68baa87..4ecf8a5 100644 --- a/src/i18n_es.h +++ b/src/i18n_es.h @@ -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, diff --git a/src/i18n_fr.h b/src/i18n_fr.h index 16a9f52..7622a50 100644 --- a/src/i18n_fr.h +++ b/src/i18n_fr.h @@ -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, diff --git a/src/i18n_it.h b/src/i18n_it.h index 1491ac6..7f167de 100644 --- a/src/i18n_it.h +++ b/src/i18n_it.h @@ -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, diff --git a/src/i18n_tr.h b/src/i18n_tr.h index 7c61b69..d03351a 100644 --- a/src/i18n_tr.h +++ b/src/i18n_tr.h @@ -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, diff --git a/src/wifi_manage_screen.cpp b/src/wifi_manage_screen.cpp index d588795..c390b61 100644 --- a/src/wifi_manage_screen.cpp +++ b/src/wifi_manage_screen.cpp @@ -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);