Add Backup & Reset sub-page with factory reset (developer feature)
Moves the previously standalone "Backup settings"/"Restore settings" buttons out of the System menu into a new "Backup & Reset" sub-page (replaced in the System menu by a single folder button, placed directly above "Info"), and adds a third entry "Reset settings" - a factory reset for developers/testers that deletes the entire Flightradar folder from the SD card (settings, logbook, location presets, WiFi, calibration - everything) and restarts the device. The next boot picks up isFirstRun automatically (via the missing SD_SETTINGS_FILE) and runs the full first-time setup again, exactly like on the very first boot - no need to pull the SD card. Since this is irreversible, it requires a full-screen red warning confirmation first, reusing the same overlay previously built only for the flight-logbook enable warning (confirmLogbookEnable() generalized to confirmWarningScreen(), taking title/body StringIds as parameters). New SdStorage::deleteDirectoryRecursive() and SettingsBackup::factoryReset() implement the actual deletion + restart. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
15
src/i18n.h
15
src/i18n.h
@@ -282,6 +282,21 @@ enum class StringId : uint8_t {
|
|||||||
// Hoehen-Farbstufen erscheinen.
|
// Hoehen-Farbstufen erscheinen.
|
||||||
LEGEND_GROUND_VEHICLE,
|
LEGEND_GROUND_VEHICLE,
|
||||||
|
|
||||||
|
// Neue "Sicherung & Reset"-Unterseite im System-Menue (fasst die
|
||||||
|
// bisher einzeln im System-Menue stehenden Backup/Restore-Buttons
|
||||||
|
// zusammen und ergaenzt einen dritten Punkt "Einstellungen
|
||||||
|
// zuruecksetzen" - ein kompletter Werksreset fuer Entwickler/Tester,
|
||||||
|
// der den gesamten Flightradar-Ordner von der SD-Karte loescht und
|
||||||
|
// neu startet, siehe settings_backup.cpp::factoryReset()). Titel und
|
||||||
|
// Button-Label teilen sich MENU_BACKUP_RESET, genau wie
|
||||||
|
// MENU_CATEGORY_SYSTEM sowohl fuer den Menue-Button als auch den
|
||||||
|
// Bildschirmtitel der System-Seite verwendet wird.
|
||||||
|
MENU_BACKUP_RESET,
|
||||||
|
MENU_FACTORY_RESET,
|
||||||
|
MENU_FACTORY_RESET_WARNING_BODY,
|
||||||
|
MENU_FACTORY_RESET_DELETING,
|
||||||
|
MENU_FACTORY_RESET_FAILED,
|
||||||
|
|
||||||
COUNT
|
COUNT
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -231,6 +231,12 @@ static const char* const I18N_DE[] = {
|
|||||||
"z.B. Zuhause, Arbeit, Ferienhaus",
|
"z.B. Zuhause, Arbeit, Ferienhaus",
|
||||||
|
|
||||||
"Bodenfahrzeug",
|
"Bodenfahrzeug",
|
||||||
|
|
||||||
|
"Sicherung & Reset",
|
||||||
|
"Einstellungen zurücksetzen",
|
||||||
|
"Löscht ALLE Daten von der SD-Karte (Einstellungen, Flugbuch, Standort-Presets, WLAN, Kalibrierung) und startet das Gerät neu. Danach läuft die komplette Ersteinrichtung erneut - wie beim allerersten Start. Das kann NICHT rückgängig gemacht werden!",
|
||||||
|
"Lösche alle Daten...",
|
||||||
|
"Zurücksetzen fehlgeschlagen.",
|
||||||
};
|
};
|
||||||
|
|
||||||
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,
|
||||||
|
|||||||
@@ -232,6 +232,12 @@ static const char* const I18N_EN[] = {
|
|||||||
"e.g. Home, Work, Holiday home",
|
"e.g. Home, Work, Holiday home",
|
||||||
|
|
||||||
"Ground vehicle",
|
"Ground vehicle",
|
||||||
|
|
||||||
|
"Backup & Reset",
|
||||||
|
"Reset settings",
|
||||||
|
"Deletes ALL data from the SD card (settings, logbook, location presets, WiFi, calibration) and restarts the device. The full first-time setup will then run again, just like on the very first boot. This CANNOT be undone!",
|
||||||
|
"Deleting all data...",
|
||||||
|
"Reset failed.",
|
||||||
};
|
};
|
||||||
|
|
||||||
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,
|
||||||
|
|||||||
@@ -231,6 +231,12 @@ static const char* const I18N_ES[] = {
|
|||||||
"ej. Casa, Trabajo, Casa de vacaciones",
|
"ej. Casa, Trabajo, Casa de vacaciones",
|
||||||
|
|
||||||
"Vehículo terrestre",
|
"Vehículo terrestre",
|
||||||
|
|
||||||
|
"Copia de seguridad y reinicio",
|
||||||
|
"Reiniciar ajustes",
|
||||||
|
"Elimina TODOS los datos de la tarjeta SD (ajustes, registro de vuelos, ubicaciones guardadas, WiFi, calibración) y reinicia el dispositivo. Después se ejecutará de nuevo la configuración inicial completa, como en el primer arranque. ¡Esto NO se puede deshacer!",
|
||||||
|
"Eliminando todos los datos...",
|
||||||
|
"Error al reiniciar.",
|
||||||
};
|
};
|
||||||
|
|
||||||
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,
|
||||||
|
|||||||
@@ -231,6 +231,12 @@ static const char* const I18N_FR[] = {
|
|||||||
"ex. Maison, Travail, Maison de vacances",
|
"ex. Maison, Travail, Maison de vacances",
|
||||||
|
|
||||||
"Véhicule au sol",
|
"Véhicule au sol",
|
||||||
|
|
||||||
|
"Sauvegarde et réinitialisation",
|
||||||
|
"Réinitialiser réglages",
|
||||||
|
"Supprime TOUTES les données de la carte SD (réglages, journal, préréglages de position, WiFi, calibrage) et redémarre l'appareil. La configuration initiale complète s'exécutera alors à nouveau, comme au tout premier démarrage. Cette action est IRRÉVERSIBLE !",
|
||||||
|
"Suppression de toutes les données...",
|
||||||
|
"Échec de la réinitialisation.",
|
||||||
};
|
};
|
||||||
|
|
||||||
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,
|
||||||
|
|||||||
@@ -231,6 +231,12 @@ static const char* const I18N_IT[] = {
|
|||||||
"es. Casa, Lavoro, Casa vacanze",
|
"es. Casa, Lavoro, Casa vacanze",
|
||||||
|
|
||||||
"Veicolo a terra",
|
"Veicolo a terra",
|
||||||
|
|
||||||
|
"Backup e reimpostazione",
|
||||||
|
"Reimposta impostazioni",
|
||||||
|
"Elimina TUTTI i dati dalla scheda SD (impostazioni, registro voli, posizioni preimpostate, WiFi, calibrazione) e riavvia il dispositivo. Verrà quindi eseguita di nuovo la configurazione iniziale completa, come al primo avvio. Questa azione NON può essere annullata!",
|
||||||
|
"Eliminazione di tutti i dati...",
|
||||||
|
"Reimpostazione fallita.",
|
||||||
};
|
};
|
||||||
|
|
||||||
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,
|
||||||
|
|||||||
@@ -231,6 +231,12 @@ static const char* const I18N_TR[] = {
|
|||||||
"örn. Ev, İş, Tatil evi",
|
"örn. Ev, İş, Tatil evi",
|
||||||
|
|
||||||
"Kara aracı",
|
"Kara aracı",
|
||||||
|
|
||||||
|
"Yedekleme ve Sıfırlama",
|
||||||
|
"Ayarları sıfırla",
|
||||||
|
"SD karttaki TÜM verileri (ayarlar, uçuş kayıt defteri, konum ön ayarları, WiFi, kalibrasyon) siler ve cihazı yeniden başlatır. Ardından ilk çalıştırmadaki gibi kurulum işlemi tekrar başlar. Bu işlem GERİ ALINAMAZ!",
|
||||||
|
"Tüm veriler siliniyor...",
|
||||||
|
"Sıfırlama başarısız.",
|
||||||
};
|
};
|
||||||
|
|
||||||
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,
|
||||||
|
|||||||
@@ -80,7 +80,12 @@ namespace {
|
|||||||
// Bildschirmflaeche errechnet, sodass die Buttons den Platz bis knapp
|
// Bildschirmflaeche errechnet, sodass die Buttons den Platz bis knapp
|
||||||
// ueber den Bildschirmrand ausfuellen - andere Seiten bleiben bei ihren
|
// ueber den Bildschirmrand ausfuellen - andere Seiten bleiben bei ihren
|
||||||
// eigenen, unveraenderten Zeilenhoehen.
|
// eigenen, unveraenderten Zeilenhoehen.
|
||||||
constexpr uint8_t SYSTEM_ROW_COUNT = 10;
|
// War 10, bevor "Sicherung"/"Wiederherstellen" in die neue
|
||||||
|
// "Sicherung & Reset"-Unterseite ausgelagert wurden (siehe
|
||||||
|
// BACKUP_RESET_ROW_COUNT unten) und dort durch einen einzelnen
|
||||||
|
// Ordner-Button ersetzt wurden: -2 (Backup/Restore raus) +1 (neuer
|
||||||
|
// Ordner-Button) = 9.
|
||||||
|
constexpr uint8_t SYSTEM_ROW_COUNT = 9;
|
||||||
constexpr int16_t SYSTEM_ROW_GAP = 4;
|
constexpr int16_t SYSTEM_ROW_GAP = 4;
|
||||||
constexpr int16_t SYSTEM_START_Y = 18;
|
constexpr int16_t SYSTEM_START_Y = 18;
|
||||||
constexpr int16_t SYSTEM_END_Y = Config::SCREEN_HEIGHT - 10;
|
constexpr int16_t SYSTEM_END_Y = Config::SCREEN_HEIGHT - 10;
|
||||||
@@ -91,6 +96,21 @@ namespace {
|
|||||||
(int16_t)(Config::SCREEN_WIDTH - 20), SYSTEM_ROW_H};
|
(int16_t)(Config::SCREEN_WIDTH - 20), SYSTEM_ROW_H};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Eigene Zeilenhoehe fuer die neue "Sicherung & Reset"-Unterseite (4
|
||||||
|
// Eintraege: Sichern/Wiederherstellen/Zuruecksetzen/Zurueck) - gleiches
|
||||||
|
// "aus verfuegbarem Platz errechnen"-Muster wie bei SYSTEM_ROW_H.
|
||||||
|
// ROW_START_Y wird mitbenutzt (siehe oben, gemeinsam mit rowRect()/
|
||||||
|
// flightRowRect()), nur GAP/COUNT/H sind eigene Werte.
|
||||||
|
constexpr uint8_t BACKUP_RESET_ROW_COUNT = 4;
|
||||||
|
constexpr int16_t BACKUP_RESET_ROW_GAP = 10;
|
||||||
|
constexpr int16_t BACKUP_RESET_END_Y = Config::SCREEN_HEIGHT - 10;
|
||||||
|
constexpr int16_t BACKUP_RESET_ROW_H =
|
||||||
|
(BACKUP_RESET_END_Y - ROW_START_Y - (BACKUP_RESET_ROW_COUNT - 1) * BACKUP_RESET_ROW_GAP) / BACKUP_RESET_ROW_COUNT;
|
||||||
|
Rect backupResetRowRect(uint8_t index) {
|
||||||
|
return {10, (int16_t)(ROW_START_Y + index * (BACKUP_RESET_ROW_H + BACKUP_RESET_ROW_GAP)),
|
||||||
|
(int16_t)(Config::SCREEN_WIDTH - 20), BACKUP_RESET_ROW_H};
|
||||||
|
}
|
||||||
|
|
||||||
void drawButton(TFT_eSPI& tft, const Rect& r, const String& label,
|
void drawButton(TFT_eSPI& tft, const Rect& r, const String& label,
|
||||||
bool active = false, bool danger = false) {
|
bool active = false, bool danger = false) {
|
||||||
uint16_t accent = danger ? TFT_RED : TFT_GREEN;
|
uint16_t accent = danger ? TFT_RED : TFT_GREEN;
|
||||||
@@ -161,16 +181,21 @@ namespace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Warn-Ueberlage, die praktisch den kompletten Bildschirm einnimmt (nur
|
// Warn-Ueberlage, die praktisch den kompletten Bildschirm einnimmt (nur
|
||||||
// ein paar Pixel Rand) und vor dem Einschalten des Flugbuchs erklaert,
|
// ein paar Pixel Rand) - urspruenglich nur fuers Einschalten des
|
||||||
// warum es sich nach 24h automatisch wieder abschaltet. "Achtung!!!"
|
// Flugbuchs gebaut (erklaert, warum es sich nach 24h automatisch
|
||||||
// steht ganz oben, mit einer Leerzeile Abstand zum Fliesstext darunter;
|
// wieder abschaltet), jetzt generisch mit uebergebenen Titel-/Text-
|
||||||
// der Text scrollt bei Bedarf (laengere Uebersetzungen) ueber eigene
|
// StringIds, damit sie auch fuer die "Einstellungen zuruecksetzen"-
|
||||||
// Pfeil-Buttons, OK/Zurueck bleiben dabei immer unten fix und
|
// Bestaetigung (Werksreset) wiederverwendet werden kann - beides sind
|
||||||
|
// seltene, potenziell folgenreiche Aktionen, die dieselbe deutliche
|
||||||
|
// Warnung verdienen. "Achtung!!!" (titleId) steht ganz oben, mit einer
|
||||||
|
// Leerzeile Abstand zum Fliesstext (bodyId) darunter; der Text
|
||||||
|
// scrollt bei Bedarf (laengere Uebersetzungen) ueber eigene Pfeil-
|
||||||
|
// Buttons, OK/Zurueck bleiben dabei immer unten fix und
|
||||||
// kollisionsfrei sichtbar. Sternchen laufen im Hintergrund mit, wie auf
|
// kollisionsfrei sichtbar. Sternchen laufen im Hintergrund mit, wie auf
|
||||||
// allen anderen Menue-Screens (nur der Radar-Screen selbst spart sich
|
// allen anderen Menue-Screens (nur der Radar-Screen selbst spart sich
|
||||||
// das wegen der CPU-Last durch Abfragen/Zeichnen). Gibt true zurueck,
|
// das wegen der CPU-Last durch Abfragen/Zeichnen). Gibt true zurueck,
|
||||||
// wenn "OK" angetippt wurde, false bei "Zurueck".
|
// wenn "OK" angetippt wurde, false bei "Zurueck".
|
||||||
bool confirmLogbookEnable(TFT_eSPI& tft) {
|
bool confirmWarningScreen(TFT_eSPI& tft, StringId titleId, StringId bodyId) {
|
||||||
constexpr int16_t BOX_X = 4;
|
constexpr int16_t BOX_X = 4;
|
||||||
constexpr int16_t BOX_Y = 4;
|
constexpr int16_t BOX_Y = 4;
|
||||||
constexpr int16_t BOX_W = Config::SCREEN_WIDTH - 2 * BOX_X;
|
constexpr int16_t BOX_W = Config::SCREEN_WIDTH - 2 * BOX_X;
|
||||||
@@ -195,7 +220,7 @@ namespace {
|
|||||||
constexpr int16_t VIEW_BOTTOM_NO_SCROLL = OK_Y - 8;
|
constexpr int16_t VIEW_BOTTOM_NO_SCROLL = OK_Y - 8;
|
||||||
constexpr int16_t VIEW_BOTTOM_SCROLL = VIEW_BOTTOM_NO_SCROLL - SCROLL_ROW_H - SCROLL_ROW_GAP;
|
constexpr int16_t VIEW_BOTTOM_SCROLL = VIEW_BOTTOM_NO_SCROLL - SCROLL_ROW_H - SCROLL_ROW_GAP;
|
||||||
|
|
||||||
String body = I18n::t(StringId::MENU_LOGBOOK_WARNING_BODY);
|
String body = I18n::t(bodyId);
|
||||||
int16_t totalH = layoutWrapped(tft, BOX_X + 10, VIEW_TOP, TEXT_MAX_WIDTH, LINE_H, body, 0, 0, 0, false);
|
int16_t totalH = layoutWrapped(tft, BOX_X + 10, VIEW_TOP, TEXT_MAX_WIDTH, LINE_H, body, 0, 0, 0, false);
|
||||||
|
|
||||||
bool scrollable = (totalH - VIEW_BOTTOM_NO_SCROLL) > 0;
|
bool scrollable = (totalH - VIEW_BOTTOM_NO_SCROLL) > 0;
|
||||||
@@ -220,7 +245,7 @@ namespace {
|
|||||||
tft.setTextDatum(MC_DATUM);
|
tft.setTextDatum(MC_DATUM);
|
||||||
tft.setTextColor(TFT_RED, TFT_BLACK);
|
tft.setTextColor(TFT_RED, TFT_BLACK);
|
||||||
tft.setTextSize(2);
|
tft.setTextSize(2);
|
||||||
tft.drawString(I18n::t(StringId::MENU_LOGBOOK_WARNING_TITLE), BOX_X + BOX_W / 2, TITLE_Y);
|
tft.drawString(I18n::t(titleId), BOX_X + BOX_W / 2, TITLE_Y);
|
||||||
tft.setTextSize(1);
|
tft.setTextSize(1);
|
||||||
tft.setTextDatum(TL_DATUM);
|
tft.setTextDatum(TL_DATUM);
|
||||||
|
|
||||||
@@ -257,7 +282,7 @@ namespace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class Page { Main, Region, System, Flight };
|
enum class Page { Main, Region, System, Flight, BackupReset };
|
||||||
}
|
}
|
||||||
|
|
||||||
void run(TFT_eSPI& tft) {
|
void run(TFT_eSPI& tft) {
|
||||||
@@ -342,14 +367,15 @@ void run(TFT_eSPI& tft) {
|
|||||||
Rect brightnessBtn = systemRowRect(2);
|
Rect brightnessBtn = systemRowRect(2);
|
||||||
Rect timeoutBtn = systemRowRect(3);
|
Rect timeoutBtn = systemRowRect(3);
|
||||||
Rect nightDimBtn = systemRowRect(4);
|
Rect nightDimBtn = systemRowRect(4);
|
||||||
Rect backupBtn = systemRowRect(5);
|
Rect webuiBtn = systemRowRect(5);
|
||||||
Rect restoreBtn = systemRowRect(6);
|
// "Sicherung & Reset" (Backup/Restore/Werksreset, siehe
|
||||||
Rect webuiBtn = systemRowRect(7);
|
// Page::BackupReset unten) steht bewusst direkt ueber "Info",
|
||||||
// "Info" (About) steht bewusst als letzter Punkt direkt ueber dem
|
// das seinerseits als letzter Punkt direkt ueber dem Zurueck-
|
||||||
// Zurueck-Button - so bleibt seine Position stabil, egal wie
|
// Button steht - so bleiben beide Positionen stabil, egal wie
|
||||||
// viele weitere Punkte davor noch dazukommen.
|
// viele weitere Punkte davor noch dazukommen.
|
||||||
Rect aboutBtn = systemRowRect(8);
|
Rect backupResetBtn = systemRowRect(6);
|
||||||
Rect backBtn = systemRowRect(9);
|
Rect aboutBtn = systemRowRect(7);
|
||||||
|
Rect backBtn = systemRowRect(8);
|
||||||
|
|
||||||
drawButton(tft, calibBtn, I18n::t(StringId::MENU_CALIBRATE));
|
drawButton(tft, calibBtn, I18n::t(StringId::MENU_CALIBRATE));
|
||||||
|
|
||||||
@@ -361,9 +387,8 @@ void run(TFT_eSPI& tft) {
|
|||||||
drawButton(tft, brightnessBtn, brightnessLabel(SettingsStore::brightnessPercent()));
|
drawButton(tft, brightnessBtn, brightnessLabel(SettingsStore::brightnessPercent()));
|
||||||
drawButton(tft, timeoutBtn, screenTimeoutLabel(SettingsStore::screenTimeoutMinutes()));
|
drawButton(tft, timeoutBtn, screenTimeoutLabel(SettingsStore::screenTimeoutMinutes()));
|
||||||
drawButton(tft, nightDimBtn, I18n::t(StringId::MENU_NIGHT_DIMMING) + onOff(SettingsStore::nightDimmingEnabled()));
|
drawButton(tft, nightDimBtn, I18n::t(StringId::MENU_NIGHT_DIMMING) + onOff(SettingsStore::nightDimmingEnabled()));
|
||||||
drawButton(tft, backupBtn, I18n::t(StringId::MENU_BACKUP));
|
|
||||||
drawButton(tft, restoreBtn, I18n::t(StringId::MENU_RESTORE));
|
|
||||||
drawButton(tft, webuiBtn, I18n::t(StringId::MENU_LOGBOOK_WEBUI));
|
drawButton(tft, webuiBtn, I18n::t(StringId::MENU_LOGBOOK_WEBUI));
|
||||||
|
drawButton(tft, backupResetBtn, I18n::t(StringId::MENU_BACKUP_RESET));
|
||||||
drawButton(tft, aboutBtn, I18n::t(StringId::MENU_ABOUT));
|
drawButton(tft, aboutBtn, I18n::t(StringId::MENU_ABOUT));
|
||||||
drawButton(tft, backBtn, I18n::t(StringId::BACK_ARROW));
|
drawButton(tft, backBtn, I18n::t(StringId::BACK_ARROW));
|
||||||
|
|
||||||
@@ -388,7 +413,42 @@ void run(TFT_eSPI& tft) {
|
|||||||
SettingsStore::setScreenTimeoutMinutes(next);
|
SettingsStore::setScreenTimeoutMinutes(next);
|
||||||
} else if (nightDimBtn.contains(tap.x, tap.y)) {
|
} else if (nightDimBtn.contains(tap.x, tap.y)) {
|
||||||
SettingsStore::setNightDimmingEnabled(!SettingsStore::nightDimmingEnabled());
|
SettingsStore::setNightDimmingEnabled(!SettingsStore::nightDimmingEnabled());
|
||||||
} else if (backupBtn.contains(tap.x, tap.y)) {
|
} else if (backupResetBtn.contains(tap.x, tap.y)) {
|
||||||
|
page = Page::BackupReset;
|
||||||
|
} else if (aboutBtn.contains(tap.x, tap.y)) {
|
||||||
|
AboutScreen::run(tft);
|
||||||
|
} else if (webuiBtn.contains(tap.x, tap.y)) {
|
||||||
|
WebUiScreen::run(tft);
|
||||||
|
} else if (backBtn.contains(tap.x, tap.y)) {
|
||||||
|
page = Page::Main;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (page == Page::BackupReset) {
|
||||||
|
tft.setTextColor(TFT_GREEN, TFT_BLACK);
|
||||||
|
tft.setCursor(10, 14);
|
||||||
|
tft.println(I18n::t(StringId::MENU_BACKUP_RESET));
|
||||||
|
|
||||||
|
Rect backupBtn = backupResetRowRect(0);
|
||||||
|
Rect restoreBtn = backupResetRowRect(1);
|
||||||
|
Rect resetBtn = backupResetRowRect(2);
|
||||||
|
Rect backBtn = backupResetRowRect(3);
|
||||||
|
|
||||||
|
drawButton(tft, backupBtn, I18n::t(StringId::MENU_BACKUP));
|
||||||
|
drawButton(tft, restoreBtn, I18n::t(StringId::MENU_RESTORE));
|
||||||
|
// Danger-Akzent (rot) - deutlich von Sichern/Wiederherstellen
|
||||||
|
// abgesetzt, da diese Aktion (nach Bestaetigung) ALLE Daten
|
||||||
|
// unwiderruflich loescht, siehe confirmWarningScreen() unten.
|
||||||
|
drawButton(tft, resetBtn, I18n::t(StringId::MENU_FACTORY_RESET), false, true);
|
||||||
|
drawButton(tft, backBtn, I18n::t(StringId::BACK_ARROW));
|
||||||
|
|
||||||
|
TouchInput::Point tap;
|
||||||
|
while (true) {
|
||||||
|
if (TouchInput::wasTapped(tap)) break;
|
||||||
|
MenuStars::update(tft);
|
||||||
|
delay(20);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (backupBtn.contains(tap.x, tap.y)) {
|
||||||
bool ok = SettingsBackup::backup();
|
bool ok = SettingsBackup::backup();
|
||||||
showBriefMessage(tft, I18n::t(ok ? StringId::MENU_BACKUP_SAVED : StringId::MENU_BACKUP_FAILED),
|
showBriefMessage(tft, I18n::t(ok ? StringId::MENU_BACKUP_SAVED : StringId::MENU_BACKUP_FAILED),
|
||||||
ok ? TFT_GREEN : TFT_RED);
|
ok ? TFT_GREEN : TFT_RED);
|
||||||
@@ -398,12 +458,26 @@ void run(TFT_eSPI& tft) {
|
|||||||
showBriefMessage(tft, I18n::t(ok ? StringId::MENU_RESTORED : StringId::MENU_RESTORE_FAILED),
|
showBriefMessage(tft, I18n::t(ok ? StringId::MENU_RESTORED : StringId::MENU_RESTORE_FAILED),
|
||||||
ok ? TFT_GREEN : TFT_RED);
|
ok ? TFT_GREEN : TFT_RED);
|
||||||
}
|
}
|
||||||
} else if (aboutBtn.contains(tap.x, tap.y)) {
|
} else if (resetBtn.contains(tap.x, tap.y)) {
|
||||||
AboutScreen::run(tft);
|
if (confirmWarningScreen(tft, StringId::MENU_LOGBOOK_WARNING_TITLE,
|
||||||
} else if (webuiBtn.contains(tap.x, tap.y)) {
|
StringId::MENU_FACTORY_RESET_WARNING_BODY)) {
|
||||||
WebUiScreen::run(tft);
|
tft.fillScreen(TFT_BLACK);
|
||||||
|
tft.setTextDatum(MC_DATUM);
|
||||||
|
tft.setTextColor(TFT_RED, TFT_BLACK);
|
||||||
|
tft.drawString(I18n::t(StringId::MENU_FACTORY_RESET_DELETING),
|
||||||
|
Config::SCREEN_WIDTH / 2, Config::SCREEN_HEIGHT / 2);
|
||||||
|
tft.setTextDatum(TL_DATUM);
|
||||||
|
// Erfolgsfall: factoryReset() startet das Geraet neu und
|
||||||
|
// kehrt nie zurueck - dieser Code danach laeuft nur im
|
||||||
|
// (seltenen) Fehlerfall (SD nicht eingehaengt) ueberhaupt
|
||||||
|
// weiter.
|
||||||
|
bool ok = SettingsBackup::factoryReset();
|
||||||
|
if (!ok) {
|
||||||
|
showBriefMessage(tft, I18n::t(StringId::MENU_FACTORY_RESET_FAILED), TFT_RED);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if (backBtn.contains(tap.x, tap.y)) {
|
} else if (backBtn.contains(tap.x, tap.y)) {
|
||||||
page = Page::Main;
|
page = Page::System;
|
||||||
}
|
}
|
||||||
|
|
||||||
} else { // Page::Flight
|
} else { // Page::Flight
|
||||||
@@ -486,7 +560,8 @@ void run(TFT_eSPI& tft) {
|
|||||||
SettingsStore::setFlightLogbookEnabled(false);
|
SettingsStore::setFlightLogbookEnabled(false);
|
||||||
SettingsStore::setFlightLogbookEnabledAtEpoch(0);
|
SettingsStore::setFlightLogbookEnabledAtEpoch(0);
|
||||||
SettingsStore::setFlightLogbookSessionFile("");
|
SettingsStore::setFlightLogbookSessionFile("");
|
||||||
} else if (confirmLogbookEnable(tft)) {
|
} else if (confirmWarningScreen(tft, StringId::MENU_LOGBOOK_WARNING_TITLE,
|
||||||
|
StringId::MENU_LOGBOOK_WARNING_BODY)) {
|
||||||
SettingsStore::setFlightLogbookEnabled(true);
|
SettingsStore::setFlightLogbookEnabled(true);
|
||||||
SettingsStore::setFlightLogbookEnabledAtEpoch((uint32_t)time(nullptr));
|
SettingsStore::setFlightLogbookEnabledAtEpoch((uint32_t)time(nullptr));
|
||||||
// Leerer Eintrag erzwingt eine frische Sitzungsdatei beim
|
// Leerer Eintrag erzwingt eine frische Sitzungsdatei beim
|
||||||
|
|||||||
@@ -143,4 +143,35 @@ void logEvent(const char* csvLine) {
|
|||||||
f.close();
|
f.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool deleteDirectoryRecursive(const char* path) {
|
||||||
|
File dir = SD.open(path);
|
||||||
|
if (!dir || !dir.isDirectory()) {
|
||||||
|
if (dir) dir.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gleiches vorsichtiges "entry.name() koennte relativ ODER absolut
|
||||||
|
// sein"-Muster wie in flight_logbook.cpp::resetAllData() - anders als
|
||||||
|
// dort aber mit echter Rekursion in Unterordner (statt sie zu
|
||||||
|
// ueberspringen), da der Flightradar-Ordner welche enthaelt (logs/,
|
||||||
|
// screenshots/).
|
||||||
|
File entry = dir.openNextFile();
|
||||||
|
while (entry) {
|
||||||
|
bool isDir = entry.isDirectory();
|
||||||
|
String name = String(entry.name());
|
||||||
|
entry.close();
|
||||||
|
|
||||||
|
String fullPath = name.startsWith("/") ? name : String(path) + "/" + name;
|
||||||
|
if (isDir) {
|
||||||
|
deleteDirectoryRecursive(fullPath.c_str());
|
||||||
|
} else {
|
||||||
|
SD.remove(fullPath);
|
||||||
|
}
|
||||||
|
entry = dir.openNextFile();
|
||||||
|
}
|
||||||
|
dir.close();
|
||||||
|
|
||||||
|
return SD.rmdir(path);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -8,4 +8,12 @@ namespace SdStorage {
|
|||||||
void seedDefaultDataFiles();
|
void seedDefaultDataFiles();
|
||||||
void logEvent(const char* csvLine);
|
void logEvent(const char* csvLine);
|
||||||
|
|
||||||
|
// Loescht rekursiv einen kompletten Ordner (alle Dateien und
|
||||||
|
// Unterordner) inklusive des Ordners selbst. Fuer den Menuepunkt
|
||||||
|
// "Einstellungen zuruecksetzen" (settings_backup.cpp::factoryReset()) -
|
||||||
|
// der einzige aktuelle Aufrufer, der damit den kompletten
|
||||||
|
// Flightradar-Ordner von der SD-Karte entfernt. Gibt false zurueck,
|
||||||
|
// wenn der Pfad nicht existiert/kein Ordner ist.
|
||||||
|
bool deleteDirectoryRecursive(const char* path);
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -57,4 +57,24 @@ bool hasBackup() {
|
|||||||
return SD.exists(SETTINGS_BACKUP_FILE) || SD.exists(WIFI_BACKUP_FILE);
|
return SD.exists(SETTINGS_BACKUP_FILE) || SD.exists(WIFI_BACKUP_FILE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool factoryReset() {
|
||||||
|
if (!SdStorage::isMounted()) return false;
|
||||||
|
|
||||||
|
bool ok;
|
||||||
|
{
|
||||||
|
// Guard-Block bewusst vor dem Neustart wieder verlassen (statt
|
||||||
|
// ueber die Funktion hinweg zu halten) - reine Vorsicht, auch wenn
|
||||||
|
// ESP.restart() den Chip ohnehin sofort zuruecksetzt.
|
||||||
|
SdMutex::Guard guard;
|
||||||
|
ok = SdStorage::deleteDirectoryRecursive(Config::SD_ROOT_DIR);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ok) {
|
||||||
|
delay(200);
|
||||||
|
ESP.restart();
|
||||||
|
// Wird nie erreicht - ESP.restart() kehrt nicht zurueck.
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -5,4 +5,12 @@ namespace SettingsBackup {
|
|||||||
bool backup();
|
bool backup();
|
||||||
bool restore();
|
bool restore();
|
||||||
bool hasBackup();
|
bool hasBackup();
|
||||||
|
|
||||||
|
// Loescht den kompletten Flightradar-Ordner von der SD-Karte und
|
||||||
|
// startet das Geraet neu - beim naechsten Boot laeuft dadurch wieder
|
||||||
|
// die komplette Ersteinrichtung (siehe main.cpp: isFirstRun-Erkennung
|
||||||
|
// ueber Config::SD_SETTINGS_FILE). Kehrt nur im Fehlerfall zurueck
|
||||||
|
// (z.B. SD nicht eingehaengt) - im Erfolgsfall startet das Geraet
|
||||||
|
// neu, bevor die Funktion "zurueckkehrt" (ESP.restart()).
|
||||||
|
bool factoryReset();
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user