Fix address search keyboard hint/punctuation and add setup-complete screen

The address search screen previously gave no hint about the expected
input format and had no punctuation keys at all on either keyboard
page, making addresses like a house number "45/3" impossible to type.
Adds a localized format-hint line under the title, comma/hyphen keys
on the main keyboard, and swaps four rarely-needed accented letters on
the special-character page for "/", ".", "'", "-". The header and
input field position are now measured dynamically (getCursorY())
instead of assuming a fixed pixel offset, so longer translations of
the hint text don't overlap the keyboard.

Adds a new FirstRunCompleteScreen shown after the location setup step
on first boot: confirms setup is done, names the SD card folder all
data is stored in, and notes that WiFi will reconnect automatically
from now on, with a large "Flightradar" button leading into the app.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Eiswolf-BG
2026-08-11 01:02:23 +02:00
parent 4099aecb8d
commit 795c008af6
11 changed files with 182 additions and 18 deletions

View File

@@ -97,16 +97,19 @@ namespace {
constexpr const char* DIGITS = "1234567890"; constexpr const char* DIGITS = "1234567890";
constexpr const char* ROW1 = "QWERTYUIOP"; constexpr const char* ROW1 = "QWERTYUIOP";
constexpr const char* ROW2 = "ASDFGHJKL"; constexpr const char* ROW2 = "ASDFGHJKL";
constexpr const char* ROW3 = "ZXCVBNM"; // Um Komma und Bindestrich erweitert (7->9 Tasten) - Adressen wie eine
// Hausnummer "45/3" oder "Strasse 12, Ort" waren sonst gar nicht
// eintippbar, da die Tastatur bisher keinerlei Satzzeichen enthielt.
constexpr const char* ROW3 = "ZXCVBNM,-";
// Sonderzeichen-Seite: nur Zeichen, die bereits an anderer Stelle in // Sonderzeichen-Seite: gegenueber der ersten Version wurden die seltenen
// der App verwendet werden (i18n_de/fr/tr/es/it.h) - der Font // Â/Ë/Î/Û gegen die fuer Adressen wichtigen Satzzeichen "/", ".", "'", "-"
// (UiFont11pt, deckt U+0020-U+015F ab) stellt sie garantiert korrekt // getauscht (Hausnummern wie "45/3", Abkuerzungen wie "Str.", Apostroph-
// dar. Deckt die in DE/FR/ES/IT/TR-Adressen gaengigsten Sonderzeichen ab. // Namen).
constexpr const char* SPEC0[6] = {"À", "Á", "Â", "Ä", "Ç", "É"}; constexpr const char* SPEC0[6] = {"À", "Á", "Ä", "Ç", "É", "È"};
constexpr const char* SPEC1[6] = {"È", "Ê", "Ë", "Í", "Î", "Ñ"}; constexpr const char* SPEC1[6] = {"Ê", "Í", "Ñ", "Ó", "Ò", "Ô"};
constexpr const char* SPEC2[6] = {"Ó", "Ò", "Ô", "Ö", "Ù", "Ú"}; constexpr const char* SPEC2[6] = {"Ö", "Ù", "Ú", "Ü", "ß", "Ğ"};
constexpr const char* SPEC3[6] = {"Û", "Ü", "ß", "Ğ", "İ", "Ş"}; constexpr const char* SPEC3[6] = {"İ", "Ş", "/", ".", "'", "-"};
// Gibt die eingegebene Adresse zurueck, oder einen leeren String, wenn // Gibt die eingegebene Adresse zurueck, oder einen leeren String, wenn
// der Nutzer abgebrochen hat. // der Nutzer abgebrochen hat.
@@ -119,7 +122,22 @@ namespace {
constexpr int16_t KEY_H = 30; constexpr int16_t KEY_H = 30;
constexpr int16_t KEY_GAP = 3; constexpr int16_t KEY_GAP = 3;
constexpr int16_t ROW0_Y = 78; constexpr int16_t FIELD_H = 34;
// Kopfbereich (Titel + Format-Hinweis) einmal zeichnen, um seine
// tatsaechliche Hoehe per getCursorY() zu messen (variiert je nach
// Sprache/Uebersetzungslaenge) - Eingabefeld und Tastatur-Start
// werden daraus abgeleitet statt wie zuvor eine feste Pixelposition
// (78) anzunehmen. redraw() unten zeichnet denselben Kopf bei jedem
// Aufruf identisch neu.
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.setCursor(10, 14);
tft.println(I18n::t(StringId::ADDRESS_SEARCH_TITLE));
tft.setTextColor(TFT_CYAN, TFT_BLACK);
tft.println(I18n::t(StringId::ADDRESS_SEARCH_HINT));
int16_t fieldY = (int16_t)(tft.getCursorY() + 4);
int16_t ROW0_Y = (int16_t)(fieldY + FIELD_H + 8);
auto layoutRow = [&](int16_t y, Rect* outRects, uint8_t n) { auto layoutRow = [&](int16_t y, Rect* outRects, uint8_t n) {
int16_t usableW = Config::SCREEN_WIDTH - 8; int16_t usableW = Config::SCREEN_WIDTH - 8;
@@ -131,11 +149,11 @@ namespace {
} }
}; };
Rect digitRects[10], row1Rects[10], row2Rects[9], row3Rects[7]; Rect digitRects[10], row1Rects[10], row2Rects[9], row3Rects[9];
layoutRow(ROW0_Y, digitRects, 10); layoutRow(ROW0_Y, digitRects, 10);
layoutRow((int16_t)(ROW0_Y + (KEY_H + KEY_GAP)), row1Rects, 10); layoutRow((int16_t)(ROW0_Y + (KEY_H + KEY_GAP)), row1Rects, 10);
layoutRow((int16_t)(ROW0_Y + 2 * (KEY_H + KEY_GAP)), row2Rects, 9); layoutRow((int16_t)(ROW0_Y + 2 * (KEY_H + KEY_GAP)), row2Rects, 9);
layoutRow((int16_t)(ROW0_Y + 3 * (KEY_H + KEY_GAP)), row3Rects, 7); layoutRow((int16_t)(ROW0_Y + 3 * (KEY_H + KEY_GAP)), row3Rects, 9);
Rect specRects[4][6]; Rect specRects[4][6];
for (uint8_t r = 0; r < 4; r++) { for (uint8_t r = 0; r < 4; r++) {
@@ -145,7 +163,7 @@ namespace {
Rect spaceBtn = {4, (int16_t)(ROW0_Y + 4 * (KEY_H + KEY_GAP)), 150, KEY_H}; Rect spaceBtn = {4, (int16_t)(ROW0_Y + 4 * (KEY_H + KEY_GAP)), 150, KEY_H};
Rect backspaceBtn = {158, (int16_t)(ROW0_Y + 4 * (KEY_H + KEY_GAP)), (int16_t)(Config::SCREEN_WIDTH - 8 - 154), KEY_H}; Rect backspaceBtn = {158, (int16_t)(ROW0_Y + 4 * (KEY_H + KEY_GAP)), (int16_t)(Config::SCREEN_WIDTH - 8 - 154), KEY_H};
constexpr int16_t BTN_ROW_Y = ROW0_Y + 5 * (KEY_H + KEY_GAP); int16_t BTN_ROW_Y = (int16_t)(ROW0_Y + 5 * (KEY_H + KEY_GAP));
constexpr int16_t BTN_W = (Config::SCREEN_WIDTH - 8 - 2 * KEY_GAP) / 3; constexpr int16_t BTN_W = (Config::SCREEN_WIDTH - 8 - 2 * KEY_GAP) / 3;
Rect toggleBtn = {4, BTN_ROW_Y, BTN_W, KEY_H}; Rect toggleBtn = {4, BTN_ROW_Y, BTN_W, KEY_H};
Rect cancelBtn = {(int16_t)(4 + BTN_W + KEY_GAP), BTN_ROW_Y, BTN_W, KEY_H}; Rect cancelBtn = {(int16_t)(4 + BTN_W + KEY_GAP), BTN_ROW_Y, BTN_W, KEY_H};
@@ -160,12 +178,14 @@ namespace {
tft.setTextColor(TFT_GREEN, TFT_BLACK); tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.setCursor(10, 14); tft.setCursor(10, 14);
tft.println(I18n::t(StringId::ADDRESS_SEARCH_TITLE)); tft.println(I18n::t(StringId::ADDRESS_SEARCH_TITLE));
tft.setTextColor(TFT_CYAN, TFT_BLACK);
tft.println(I18n::t(StringId::ADDRESS_SEARCH_HINT));
tft.fillRect(8, 40, Config::SCREEN_WIDTH - 16, 34, TFT_BLACK); tft.fillRect(8, fieldY, Config::SCREEN_WIDTH - 16, FIELD_H, TFT_BLACK);
tft.drawRect(8, 40, Config::SCREEN_WIDTH - 16, 34, TFT_GREEN); tft.drawRect(8, fieldY, Config::SCREEN_WIDTH - 16, FIELD_H, TFT_GREEN);
tft.setTextSize(2); tft.setTextSize(2);
tft.setTextColor(TFT_GREEN, TFT_BLACK); tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.setCursor(14, 66); tft.setCursor(14, (int16_t)(fieldY + 26));
tft.print(visibleTail(tft, buf, (int16_t)(Config::SCREEN_WIDTH - 28))); tft.print(visibleTail(tft, buf, (int16_t)(Config::SCREEN_WIDTH - 28)));
tft.setTextSize(1); tft.setTextSize(1);
@@ -173,7 +193,7 @@ namespace {
for (uint8_t i = 0; i < 10; i++) drawButton(tft, digitRects[i], String(DIGITS[i])); for (uint8_t i = 0; i < 10; i++) drawButton(tft, digitRects[i], String(DIGITS[i]));
for (uint8_t i = 0; i < 10; i++) drawButton(tft, row1Rects[i], String(ROW1[i])); for (uint8_t i = 0; i < 10; i++) drawButton(tft, row1Rects[i], String(ROW1[i]));
for (uint8_t i = 0; i < 9; i++) drawButton(tft, row2Rects[i], String(ROW2[i])); for (uint8_t i = 0; i < 9; i++) drawButton(tft, row2Rects[i], String(ROW2[i]));
for (uint8_t i = 0; i < 7; i++) drawButton(tft, row3Rects[i], String(ROW3[i])); for (uint8_t i = 0; i < 9; i++) drawButton(tft, row3Rects[i], String(ROW3[i]));
} else { } else {
for (uint8_t i = 0; i < 6; i++) drawButton(tft, specRects[0][i], SPEC0[i]); for (uint8_t i = 0; i < 6; i++) drawButton(tft, specRects[0][i], SPEC0[i]);
for (uint8_t i = 0; i < 6; i++) drawButton(tft, specRects[1][i], SPEC1[i]); for (uint8_t i = 0; i < 6; i++) drawButton(tft, specRects[1][i], SPEC1[i]);
@@ -217,7 +237,7 @@ namespace {
handled = true; handled = true;
} }
} }
for (uint8_t i = 0; i < 7 && !handled; i++) { for (uint8_t i = 0; i < 9 && !handled; i++) {
if (row3Rects[i].contains(tap.x, tap.y)) { if (row3Rects[i].contains(tap.x, tap.y)) {
char s[2] = {ROW3[i], 0}; char s[2] = {ROW3[i], 0};
appendUtf8(buf, len, CAP, s); appendUtf8(buf, len, CAP, s);

View File

@@ -0,0 +1,82 @@
#include "first_run_complete_screen.h"
#include "touch_input.h"
#include "menu_stars.h"
#include "config.h"
#include "i18n.h"
namespace FirstRunCompleteScreen {
namespace {
struct Rect {
int16_t x, y, w, h;
bool contains(int16_t px, int16_t py) const {
return px >= x && px < x + w && py >= y && py < y + h;
}
};
int16_t layoutWrapped(TFT_eSPI& tft, int16_t x, int16_t startY, int16_t maxWidth,
int16_t lineHeight, const String& text) {
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);
}
tft.setCursor(x, y);
tft.print(line);
y += lineHeight;
start += line.length();
}
return y;
}
void drawStartButton(TFT_eSPI& tft, const Rect& r) {
tft.fillRoundRect(r.x, r.y, r.w, r.h, 6, TFT_GREEN);
tft.drawRoundRect(r.x, r.y, r.w, r.h, 6, TFT_GREEN);
tft.setTextDatum(MC_DATUM);
tft.setTextColor(TFT_BLACK, TFT_GREEN);
tft.setTextSize(2);
int16_t cx = r.x + r.w / 2;
int16_t cy = r.y + r.h / 2;
tft.drawString("Flightradar", cx, cy);
tft.drawString("Flightradar", (int16_t)(cx + 1), cy);
tft.setTextSize(1);
tft.setTextDatum(TL_DATUM);
}
}
void run(TFT_eSPI& tft) {
MenuStars::reset();
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.setCursor(10, 14);
tft.println(I18n::t(StringId::FIRST_RUN_COMPLETE_TITLE));
tft.setTextColor(TFT_WHITE, TFT_BLACK);
String body = String(I18n::t(StringId::FIRST_RUN_COMPLETE_BODY1)) +
Config::SD_ROOT_DIR +
I18n::t(StringId::FIRST_RUN_COMPLETE_BODY2);
int16_t textEndY = layoutWrapped(tft, 10, 40, (int16_t)(Config::SCREEN_WIDTH - 20), 18, body);
constexpr int16_t BTN_H = 60;
int16_t btnY = (int16_t)(textEndY + 20);
int16_t maxBtnY = (int16_t)(Config::SCREEN_HEIGHT - (BTN_H + 20));
if (btnY > maxBtnY) btnY = maxBtnY;
Rect startBtn = {10, btnY, (int16_t)(Config::SCREEN_WIDTH - 20), BTN_H};
drawStartButton(tft, startBtn);
while (true) {
TouchInput::Point tap;
if (!TouchInput::wasTapped(tap)) { MenuStars::update(tft); delay(20); continue; }
if (startBtn.contains(tap.x, tap.y)) return;
}
}
}

View File

@@ -0,0 +1,7 @@
#pragma once
#include <Arduino.h>
#include <TFT_eSPI.h>
namespace FirstRunCompleteScreen {
void run(TFT_eSPI& tft);
}

View File

@@ -254,6 +254,20 @@ enum class StringId : uint8_t {
// Ersteinrichtungs-Screen (FIRST_RUN_LOCATION_BODY) und im README. // Ersteinrichtungs-Screen (FIRST_RUN_LOCATION_BODY) und im README.
LOCATION_INFO_PARA6, LOCATION_INFO_PARA6,
// Format-Hinweis unter dem Titel der Adresssuche (AddressSearchScreen)
// - ein Beispiel im lokalen Format (Strasse Hausnr., PLZ Ort), da das
// einzelne Freitextfeld sonst ohne jeden Hinweis war und Nutzer nicht
// wussten, was/in welcher Reihenfolge sie eingeben sollen.
ADDRESS_SEARCH_HINT,
// Letzter Screen der Ersteinrichtung (nach der Standort-Adresssuche,
// main.cpp) - bestaetigt den Abschluss der Einrichtung, nennt den
// SD-Karten-Ordner, in dem alle Daten liegen, und weist darauf hin,
// dass sich das WLAN ab dem naechsten Start automatisch verbindet.
FIRST_RUN_COMPLETE_TITLE,
FIRST_RUN_COMPLETE_BODY1,
FIRST_RUN_COMPLETE_BODY2,
COUNT COUNT
}; };

View File

@@ -221,6 +221,12 @@ static const char* const I18N_DE[] = {
"Überspringen", "Überspringen",
"Tipp: Über den Button \"Per Adresse suchen\" beim Anlegen eines Presets (\"+\") bekommst du sofort exakte Koordinaten für deine Adresse - viel genauer als die automatische IP-Standortbestimmung, die leicht 20-30 km danebenliegen kann. Mit dem exakten Standort als Preset blinkt die Näherungs-LED wirklich im Takt mit dem Flugzeug, das du gerade über dir hörst.", "Tipp: Über den Button \"Per Adresse suchen\" beim Anlegen eines Presets (\"+\") bekommst du sofort exakte Koordinaten für deine Adresse - viel genauer als die automatische IP-Standortbestimmung, die leicht 20-30 km danebenliegen kann. Mit dem exakten Standort als Preset blinkt die Näherungs-LED wirklich im Takt mit dem Flugzeug, das du gerade über dir hörst.",
"z.B. Musterstraße 12, 12345 Musterstadt",
"Einrichtung abgeschlossen",
"Der Einrichtungsprozess ist jetzt abgeschlossen. Alle Daten wurden auf der SD-Karte im Ordner ",
" abgespeichert. Ab dem nächsten Start verbindet sich das WLAN automatisch.",
}; };
static_assert(sizeof(I18N_DE) / sizeof(I18N_DE[0]) == (size_t)StringId::COUNT, static_assert(sizeof(I18N_DE) / sizeof(I18N_DE[0]) == (size_t)StringId::COUNT,

View File

@@ -222,6 +222,12 @@ static const char* const I18N_EN[] = {
"Skip", "Skip",
"Tip: use the \"Search by address\" button when adding a preset (\"+\") to get exact coordinates for your address instantly - much more accurate than automatic IP-based location, which can easily be 20-30 km off. With your exact location set, the proximity LED really does blink in sync with the aircraft you can hear overhead.", "Tip: use the \"Search by address\" button when adding a preset (\"+\") to get exact coordinates for your address instantly - much more accurate than automatic IP-based location, which can easily be 20-30 km off. With your exact location set, the proximity LED really does blink in sync with the aircraft you can hear overhead.",
"e.g. Main Street 12, 12345 Springfield",
"Setup Complete",
"The setup process is now complete. All data has been saved to the SD card in the folder ",
". From the next boot, WiFi will connect automatically.",
}; };
static_assert(sizeof(I18N_EN) / sizeof(I18N_EN[0]) == (size_t)StringId::COUNT, static_assert(sizeof(I18N_EN) / sizeof(I18N_EN[0]) == (size_t)StringId::COUNT,

View File

@@ -221,6 +221,12 @@ static const char* const I18N_ES[] = {
"Omitir", "Omitir",
"Consejo: usa el botón \"Buscar por dirección\" al añadir un preset (\"+\") para obtener al instante coordenadas exactas de tu dirección - mucho más preciso que la geolocalización automática por IP, que puede fallar fácilmente por 20-30 km. Con tu ubicación exacta configurada, el LED de proximidad realmente parpadea al ritmo del avión que oyes encima de ti.", "Consejo: usa el botón \"Buscar por dirección\" al añadir un preset (\"+\") para obtener al instante coordenadas exactas de tu dirección - mucho más preciso que la geolocalización automática por IP, que puede fallar fácilmente por 20-30 km. Con tu ubicación exacta configurada, el LED de proximidad realmente parpadea al ritmo del avión que oyes encima de ti.",
"ej. Calle Ejemplo 12, 28001 Madrid",
"Configuración completada",
"El proceso de configuración ya ha finalizado. Todos los datos se han guardado en la tarjeta SD, en la carpeta ",
". A partir del próximo inicio, el WiFi se conectará automáticamente.",
}; };
static_assert(sizeof(I18N_ES) / sizeof(I18N_ES[0]) == (size_t)StringId::COUNT, static_assert(sizeof(I18N_ES) / sizeof(I18N_ES[0]) == (size_t)StringId::COUNT,

View File

@@ -221,6 +221,12 @@ static const char* const I18N_FR[] = {
"Passer", "Passer",
"Astuce : utilisez le bouton \"Rechercher par adresse\" lors de l'ajout d'un preset (\"+\") pour obtenir instantanément des coordonnées exactes pour votre adresse - bien plus précis que la localisation automatique par IP, qui peut facilement se tromper de 20 à 30 km. Avec votre position exacte enregistrée, la LED de proximité clignote vraiment en rythme avec l'avion que vous entendez au-dessus de vous.", "Astuce : utilisez le bouton \"Rechercher par adresse\" lors de l'ajout d'un preset (\"+\") pour obtenir instantanément des coordonnées exactes pour votre adresse - bien plus précis que la localisation automatique par IP, qui peut facilement se tromper de 20 à 30 km. Avec votre position exacte enregistrée, la LED de proximité clignote vraiment en rythme avec l'avion que vous entendez au-dessus de vous.",
"ex. Rue Example 12, 75000 Paris",
"Configuration terminée",
"Le processus de configuration est maintenant terminé. Toutes les données ont été enregistrées sur la carte SD dans le dossier ",
". À partir du prochain démarrage, le WiFi se connectera automatiquement.",
}; };
static_assert(sizeof(I18N_FR) / sizeof(I18N_FR[0]) == (size_t)StringId::COUNT, static_assert(sizeof(I18N_FR) / sizeof(I18N_FR[0]) == (size_t)StringId::COUNT,

View File

@@ -221,6 +221,12 @@ static const char* const I18N_IT[] = {
"Salta", "Salta",
"Suggerimento: usa il pulsante \"Cerca per indirizzo\" quando aggiungi un preset (\"+\") per ottenere subito coordinate esatte per il tuo indirizzo - molto più preciso della geolocalizzazione IP automatica, che può sbagliare facilmente di 20-30 km. Con la tua posizione esatta impostata, il LED di prossimità lampeggia davvero al ritmo dell'aereo che senti sopra di te.", "Suggerimento: usa il pulsante \"Cerca per indirizzo\" quando aggiungi un preset (\"+\") per ottenere subito coordinate esatte per il tuo indirizzo - molto più preciso della geolocalizzazione IP automatica, che può sbagliare facilmente di 20-30 km. Con la tua posizione esatta impostata, il LED di prossimità lampeggia davvero al ritmo dell'aereo che senti sopra di te.",
"es. Via Esempio 12, 00100 Roma",
"Configurazione completata",
"Il processo di configurazione è ora completato. Tutti i dati sono stati salvati sulla scheda SD nella cartella ",
". Dal prossimo avvio, il WiFi si connetterà automaticamente.",
}; };
static_assert(sizeof(I18N_IT) / sizeof(I18N_IT[0]) == (size_t)StringId::COUNT, static_assert(sizeof(I18N_IT) / sizeof(I18N_IT[0]) == (size_t)StringId::COUNT,

View File

@@ -221,6 +221,12 @@ static const char* const I18N_TR[] = {
"Atla", "Atla",
"İpucu: bir ön ayar eklerken (\"+\") \"Adresle ara\" düğmesini kullanarak adresiniz için anında tam koordinatlar elde edin - otomatik IP tabanlı konum belirlemeden çok daha doğru, o kolayca 20-30 km sapabilir. Tam konumunuz ayarlandığında, yakınlık LED'i üstünüzden duyduğunuz uçakla gerçekten aynı ritimde yanıp söner.", "İpucu: bir ön ayar eklerken (\"+\") \"Adresle ara\" düğmesini kullanarak adresiniz için anında tam koordinatlar elde edin - otomatik IP tabanlı konum belirlemeden çok daha doğru, o kolayca 20-30 km sapabilir. Tam konumunuz ayarlandığında, yakınlık LED'i üstünüzden duyduğunuz uçakla gerçekten aynı ritimde yanıp söner.",
"örn. Örnek Cad. 12, 34000 İstanbul",
"Kurulum Tamamlandı",
"Kurulum işlemi artık tamamlandı. Tüm veriler SD karttaki şu klasöre kaydedildi: ",
". Bir sonraki başlatmadan itibaren WiFi otomatik olarak bağlanacak.",
}; };
static_assert(sizeof(I18N_TR) / sizeof(I18N_TR[0]) == (size_t)StringId::COUNT, static_assert(sizeof(I18N_TR) / sizeof(I18N_TR[0]) == (size_t)StringId::COUNT,

View File

@@ -31,6 +31,7 @@
#include "i18n.h" #include "i18n.h"
#include "first_run_language_screen.h" #include "first_run_language_screen.h"
#include "first_run_location_screen.h" #include "first_run_location_screen.h"
#include "first_run_complete_screen.h"
#include "menu_stars.h" #include "menu_stars.h"
#include "ui_font.h" #include "ui_font.h"
#include <math.h> #include <math.h>
@@ -496,6 +497,10 @@ void setup() {
// oben (die Adresssuche braucht eine Internetverbindung) - // oben (die Adresssuche braucht eine Internetverbindung) -
// ueberspringbar, siehe first_run_location_screen.cpp. // ueberspringbar, siehe first_run_location_screen.cpp.
FirstRunLocationScreen::run(tft); FirstRunLocationScreen::run(tft);
// Abschluss-Screen: bestaetigt das Ende der Ersteinrichtung, nennt
// den SD-Ordner mit allen gespeicherten Daten und weist auf den
// automatischen WLAN-Verbindungsaufbau ab dem naechsten Start hin.
FirstRunCompleteScreen::run(tft);
SplashScreen::begin(tft); SplashScreen::begin(tft);
MenuStars::update(tft); MenuStars::update(tft);
SplashScreen::setStatusLine(tft, 0, I18n::t(StringId::SPLASH_SD_OK), TFT_WHITE); SplashScreen::setStatusLine(tft, 0, I18n::t(StringId::SPLASH_SD_OK), TFT_WHITE);