From f4ced6ac7bdedebd7a7fa56c50b1e7edea3f65d5 Mon Sep 17 00:00:00 2001 From: Stanislav Khromov Date: Wed, 14 Jan 2026 01:36:37 +0100 Subject: [PATCH] feat: format sd card screen --- .../settings/FormatSDCardActivity.cpp | 137 ++++++++++++++++++ .../settings/FormatSDCardActivity.h | 37 +++++ src/activities/settings/SettingsActivity.cpp | 12 +- 3 files changed, 185 insertions(+), 1 deletion(-) create mode 100644 src/activities/settings/FormatSDCardActivity.cpp create mode 100644 src/activities/settings/FormatSDCardActivity.h diff --git a/src/activities/settings/FormatSDCardActivity.cpp b/src/activities/settings/FormatSDCardActivity.cpp new file mode 100644 index 00000000..e4698065 --- /dev/null +++ b/src/activities/settings/FormatSDCardActivity.cpp @@ -0,0 +1,137 @@ +#include "FormatSDCardActivity.h" + +#include +#include +#include + +#include "MappedInputManager.h" +#include "fontIds.h" + +void FormatSDCardActivity::taskTrampoline(void* param) { + auto* self = static_cast(param); + self->displayTaskLoop(); +} + +void FormatSDCardActivity::onEnter() { + ActivityWithSubactivity::onEnter(); + renderingMutex = xSemaphoreCreateMutex(); + updateRequired = true; + + xTaskCreate(&FormatSDCardActivity::taskTrampoline, "FormatSDCardTask", + 4096, // Stack size + this, // Parameters + 1, // Priority + &displayTaskHandle // Task handle + ); +} + +void FormatSDCardActivity::onExit() { + ActivityWithSubactivity::onExit(); + + xSemaphoreTake(renderingMutex, portMAX_DELAY); + if (displayTaskHandle) { + vTaskDelete(displayTaskHandle); + displayTaskHandle = nullptr; + } + vSemaphoreDelete(renderingMutex); + renderingMutex = nullptr; +} + +void FormatSDCardActivity::displayTaskLoop() { + while (true) { + if (updateRequired) { + updateRequired = false; + xSemaphoreTake(renderingMutex, portMAX_DELAY); + render(); + xSemaphoreGive(renderingMutex); + } + vTaskDelay(10 / portTICK_PERIOD_MS); + } +} + +void FormatSDCardActivity::render() { + if (subActivity) return; + + renderer.clearScreen(); + + const auto pageWidth = renderer.getScreenWidth(); + + renderer.drawCenteredText(UI_12_FONT_ID, 15, "Format SD Card", true, EpdFontFamily::BOLD); + + if (state == WAITING_CONFIRMATION) { + renderer.drawCenteredText(UI_10_FONT_ID, 150, "WARNING!", true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_10_FONT_ID, 200, "This will ERASE ALL DATA"); + renderer.drawCenteredText(UI_10_FONT_ID, 230, "on the SD card including:"); + renderer.drawCenteredText(UI_10_FONT_ID, 270, "- All books and documents"); + renderer.drawCenteredText(UI_10_FONT_ID, 300, "- Reading progress"); + renderer.drawCenteredText(UI_10_FONT_ID, 330, "- Cached data"); + renderer.drawCenteredText(UI_10_FONT_ID, 380, "This action CANNOT be undone."); + + const auto labels = mappedInput.mapLabels("Cancel", "FORMAT", "", ""); + renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4); + } else if (state == FORMATTING) { + renderer.drawCenteredText(UI_10_FONT_ID, 300, "Formatting...", true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_10_FONT_ID, 350, "Please wait, do not power off"); + } else if (state == SUCCESS) { + renderer.drawCenteredText(UI_10_FONT_ID, 280, "Format Complete!", true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_10_FONT_ID, 330, "SD card has been formatted."); + renderer.drawCenteredText(UI_10_FONT_ID, 380, "Device will restart..."); + } else if (state == FAILED) { + renderer.drawCenteredText(UI_10_FONT_ID, 300, "Format Failed", true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_10_FONT_ID, 350, "Please try again or check SD card."); + + const auto labels = mappedInput.mapLabels("« Back", "", "", ""); + renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4); + } + + renderer.displayBuffer(); +} + +void FormatSDCardActivity::loop() { + if (subActivity) { + subActivity->loop(); + return; + } + + if (state == WAITING_CONFIRMATION) { + if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) { + xSemaphoreTake(renderingMutex, portMAX_DELAY); + state = FORMATTING; + xSemaphoreGive(renderingMutex); + updateRequired = true; + vTaskDelay(10 / portTICK_PERIOD_MS); + performFormat(); + } + if (mappedInput.wasPressed(MappedInputManager::Button::Back)) { + goBack(); + } + return; + } + + if (state == FAILED) { + if (mappedInput.wasPressed(MappedInputManager::Button::Back)) { + goBack(); + } + return; + } + + if (state == SUCCESS) { + // Auto-restart after brief delay + vTaskDelay(2000 / portTICK_PERIOD_MS); + esp_restart(); + } +} + +void FormatSDCardActivity::performFormat() { + Serial.printf("[%lu] [FORMAT] Starting SD card format...\n", millis()); + + // Call the format method on SDCardManager + bool success = SdMan.format(&Serial); + + xSemaphoreTake(renderingMutex, portMAX_DELAY); + state = success ? SUCCESS : FAILED; + xSemaphoreGive(renderingMutex); + updateRequired = true; + + Serial.printf("[%lu] [FORMAT] Format %s\n", millis(), success ? "succeeded" : "failed"); +} diff --git a/src/activities/settings/FormatSDCardActivity.h b/src/activities/settings/FormatSDCardActivity.h new file mode 100644 index 00000000..3f87511b --- /dev/null +++ b/src/activities/settings/FormatSDCardActivity.h @@ -0,0 +1,37 @@ +#pragma once +#include +#include +#include + +#include + +#include "activities/ActivityWithSubactivity.h" + +class FormatSDCardActivity final : public ActivityWithSubactivity { + enum State { + WAITING_CONFIRMATION, + FORMATTING, + SUCCESS, + FAILED + }; + + TaskHandle_t displayTaskHandle = nullptr; + SemaphoreHandle_t renderingMutex = nullptr; + bool updateRequired = false; + const std::function goBack; + State state = WAITING_CONFIRMATION; + + static void taskTrampoline(void* param); + [[noreturn]] void displayTaskLoop(); + void render(); + void performFormat(); + + public: + explicit FormatSDCardActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, + const std::function& goBack) + : ActivityWithSubactivity("FormatSDCard", renderer, mappedInput), goBack(goBack) {} + void onEnter() override; + void onExit() override; + void loop() override; + bool preventAutoSleep() override { return state == FORMATTING; } +}; diff --git a/src/activities/settings/SettingsActivity.cpp b/src/activities/settings/SettingsActivity.cpp index f22850a9..9f959139 100644 --- a/src/activities/settings/SettingsActivity.cpp +++ b/src/activities/settings/SettingsActivity.cpp @@ -7,6 +7,7 @@ #include "CalibreSettingsActivity.h" #include "CrossPointSettings.h" +#include "FormatSDCardActivity.h" #include "MappedInputManager.h" #include "OtaUpdateActivity.h" #include "fontIds.h" @@ -41,7 +42,8 @@ const SettingInfo settingsList[settingsCount] = { SettingInfo::Enum("Refresh Frequency", &CrossPointSettings::refreshFrequency, {"1 page", "5 pages", "10 pages", "15 pages", "30 pages"}), SettingInfo::Action("Calibre Settings"), - SettingInfo::Action("Check for updates")}; + SettingInfo::Action("Check for updates"), + SettingInfo::Action("Format SD Card")}; } // namespace void SettingsActivity::taskTrampoline(void* param) { @@ -154,6 +156,14 @@ void SettingsActivity::toggleCurrentSetting() { updateRequired = true; })); xSemaphoreGive(renderingMutex); + } else if (strcmp(setting.name, "Format SD Card") == 0) { + xSemaphoreTake(renderingMutex, portMAX_DELAY); + exitActivity(); + enterNewActivity(new FormatSDCardActivity(renderer, mappedInput, [this] { + exitActivity(); + updateRequired = true; + })); + xSemaphoreGive(renderingMutex); } } else { // Only toggle if it's a toggle type and has a value pointer