diff --git a/src/main.cpp b/src/main.cpp index eb3bc0b..39a14b3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -21,8 +21,10 @@ #include "screens/EpubReaderScreen.h" #include "screens/FileSelectionScreen.h" #include "screens/FullScreenMessageScreen.h" +#include "screens/HomeScreen.h" #include "screens/SettingsScreen.h" #include "screens/SleepScreen.h" +#include "screens/UploadFileScreen.h" #define SPI_FQ 40000000 // Display SPI pins (custom pins for XteinkX4, not hardware SPI defaults) @@ -150,6 +152,7 @@ void enterDeepSleep() { } void onGoHome(); +void onGoToFileSelection(); void onSelectEpubFile(const std::string& path) { exitScreen(); enterNewScreen(new FullScreenMessageScreen(renderer, inputManager, "Loading...")); @@ -165,18 +168,28 @@ void onSelectEpubFile(const std::string& path) { enterNewScreen( new FullScreenMessageScreen(renderer, inputManager, "Failed to load epub", REGULAR, EInkDisplay::HALF_REFRESH)); delay(2000); - onGoHome(); + onGoToFileSelection(); } } +void onGoToFileSelection() { + exitScreen(); + enterNewScreen(new FileSelectionScreen(renderer, inputManager, onSelectEpubFile, onGoHome)); +} + void onGoToSettings() { exitScreen(); enterNewScreen(new SettingsScreen(renderer, inputManager, onGoHome)); } +void onGoToUploadFile() { + exitScreen(); + enterNewScreen(new UploadFileScreen(renderer, inputManager, onGoHome)); +} + void onGoHome() { exitScreen(); - enterNewScreen(new FileSelectionScreen(renderer, inputManager, onSelectEpubFile, onGoToSettings)); + enterNewScreen(new HomeScreen(renderer, inputManager, onGoToFileSelection, onGoToSettings, onGoToUploadFile)); } void setup() { @@ -221,8 +234,7 @@ void setup() { } } - exitScreen(); - enterNewScreen(new FileSelectionScreen(renderer, inputManager, onSelectEpubFile, onGoToSettings)); + onGoHome(); // Ensure we're not still holding the power button before leaving setup waitForPowerRelease(); @@ -233,8 +245,8 @@ void loop() { static unsigned long lastMemPrint = 0; if (Serial && millis() - lastMemPrint >= 10000) { - Serial.printf("[%lu] [MEM] Free: %d bytes, Total: %d bytes, Min Free: %d bytes\n", millis(), ESP.getFreeHeap(), - ESP.getHeapSize(), ESP.getMinFreeHeap()); + Serial.printf("[%lu] [MEM] Free: %d bytes, Total: %d bytes, Min Free: %d bytes, Max alloc: %d\n", millis(), + ESP.getFreeHeap(), ESP.getHeapSize(), ESP.getMinFreeHeap(), ESP.getMaxAllocHeap()); lastMemPrint = millis(); } diff --git a/src/screens/FileSelectionScreen.cpp b/src/screens/FileSelectionScreen.cpp index 09f2efc..ce4d000 100644 --- a/src/screens/FileSelectionScreen.cpp +++ b/src/screens/FileSelectionScreen.cpp @@ -98,8 +98,8 @@ void FileSelectionScreen::handleInput() { loadFiles(); updateRequired = true; } else { - // At root level, go to settings - onSettingsOpen(); + // At root level, go back home + onGoHome(); } } else if (prevPressed) { selectorIndex = (selectorIndex + files.size() - 1) % files.size(); @@ -129,7 +129,7 @@ void FileSelectionScreen::render() const { renderer.drawCenteredText(READER_FONT_ID, 10, "CrossPoint Reader", true, BOLD); // Help text - renderer.drawText(SMALL_FONT_ID, 20, GfxRenderer::getScreenHeight() - 30, "Press BACK for Settings"); + renderer.drawText(SMALL_FONT_ID, 20, GfxRenderer::getScreenHeight() - 30, "Press BACK for Home"); if (files.empty()) { renderer.drawText(UI_FONT_ID, 20, 60, "No EPUBs found"); diff --git a/src/screens/FileSelectionScreen.h b/src/screens/FileSelectionScreen.h index 00947a5..aaa076d 100644 --- a/src/screens/FileSelectionScreen.h +++ b/src/screens/FileSelectionScreen.h @@ -17,7 +17,7 @@ class FileSelectionScreen final : public Screen { int selectorIndex = 0; bool updateRequired = false; const std::function onSelect; - const std::function onSettingsOpen; + const std::function onGoHome; static void taskTrampoline(void* param); [[noreturn]] void displayTaskLoop(); @@ -27,8 +27,8 @@ class FileSelectionScreen final : public Screen { public: explicit FileSelectionScreen(GfxRenderer& renderer, InputManager& inputManager, const std::function& onSelect, - const std::function& onSettingsOpen) - : Screen(renderer, inputManager), onSelect(onSelect), onSettingsOpen(onSettingsOpen) {} + const std::function& onGoHome) + : Screen(renderer, inputManager), onSelect(onSelect), onGoHome(onGoHome) {} void onEnter() override; void onExit() override; void handleInput() override; diff --git a/src/screens/HomeScreen.cpp b/src/screens/HomeScreen.cpp new file mode 100644 index 0000000..052bcd0 --- /dev/null +++ b/src/screens/HomeScreen.cpp @@ -0,0 +1,87 @@ +#include "HomeScreen.h" + +#include +#include + +#include "config.h" + +void HomeScreen::taskTrampoline(void* param) { + auto* self = static_cast(param); + self->displayTaskLoop(); +} + +void HomeScreen::onEnter() { + renderingMutex = xSemaphoreCreateMutex(); + + selectorIndex = 0; + + // Trigger first update + updateRequired = true; + + xTaskCreate(&HomeScreen::taskTrampoline, "HomeScreenTask", + 2048, // Stack size + this, // Parameters + 1, // Priority + &displayTaskHandle // Task handle + ); +} + +void HomeScreen::onExit() { + // Wait until not rendering to delete task to avoid killing mid-instruction to EPD + xSemaphoreTake(renderingMutex, portMAX_DELAY); + if (displayTaskHandle) { + vTaskDelete(displayTaskHandle); + displayTaskHandle = nullptr; + } + vSemaphoreDelete(renderingMutex); + renderingMutex = nullptr; +} + +void HomeScreen::handleInput() { + const bool prevPressed = + inputManager.wasPressed(InputManager::BTN_UP) || inputManager.wasPressed(InputManager::BTN_LEFT); + const bool nextPressed = + inputManager.wasPressed(InputManager::BTN_DOWN) || inputManager.wasPressed(InputManager::BTN_RIGHT); + + if (inputManager.wasPressed(InputManager::BTN_CONFIRM)) { + if (selectorIndex == 0) { + onFileSelectionOpen(); + } else if (selectorIndex == 1) { + onUploadFileOpen(); + } else if (selectorIndex == 2) { + onSettingsOpen(); + } + } else if (prevPressed) { + selectorIndex = (selectorIndex + menuItemCount - 1) % menuItemCount; + updateRequired = true; + } else if (nextPressed) { + selectorIndex = (selectorIndex + 1) % menuItemCount; + updateRequired = true; + } +} + +void HomeScreen::displayTaskLoop() { + while (true) { + if (updateRequired) { + updateRequired = false; + xSemaphoreTake(renderingMutex, portMAX_DELAY); + render(); + xSemaphoreGive(renderingMutex); + } + vTaskDelay(10 / portTICK_PERIOD_MS); + } +} + +void HomeScreen::render() const { + renderer.clearScreen(); + + const auto pageWidth = GfxRenderer::getScreenWidth(); + renderer.drawCenteredText(READER_FONT_ID, 10, "CrossPoint Reader", true, BOLD); + + // Draw selection + renderer.fillRect(0, 60 + selectorIndex * 30 + 2, pageWidth - 1, 30); + renderer.drawText(UI_FONT_ID, 20, 60, "Read", selectorIndex != 0); + renderer.drawText(UI_FONT_ID, 20, 90, "Upload", selectorIndex != 1); + renderer.drawText(UI_FONT_ID, 20, 120, "Settings", selectorIndex != 2); + renderer.displayBuffer(); +} diff --git a/src/screens/HomeScreen.h b/src/screens/HomeScreen.h new file mode 100644 index 0000000..755cb37 --- /dev/null +++ b/src/screens/HomeScreen.h @@ -0,0 +1,38 @@ +#pragma once +#include +#include +#include + +#include +#include +#include + +#include "Screen.h" + +class HomeScreen final : public Screen { + TaskHandle_t displayTaskHandle = nullptr; + SemaphoreHandle_t renderingMutex = nullptr; + int selectorIndex = 0; + bool updateRequired = false; + const std::function onFileSelectionOpen; + const std::function onSettingsOpen; + const std::function onUploadFileOpen; + + static constexpr int menuItemCount = 3; + + static void taskTrampoline(void* param); + [[noreturn]] void displayTaskLoop(); + void render() const; + + public: + explicit HomeScreen(GfxRenderer& renderer, InputManager& inputManager, + const std::function& onFileSelectionOpen, const std::function& onSettingsOpen, + const std::function& onUploadFileOpen) + : Screen(renderer, inputManager), + onFileSelectionOpen(onFileSelectionOpen), + onSettingsOpen(onSettingsOpen), + onUploadFileOpen(onUploadFileOpen) {} + void onEnter() override; + void onExit() override; + void handleInput() override; +}; diff --git a/src/screens/UploadFileScreen.cpp b/src/screens/UploadFileScreen.cpp index df41289..899a47d 100644 --- a/src/screens/UploadFileScreen.cpp +++ b/src/screens/UploadFileScreen.cpp @@ -34,7 +34,8 @@ void UploadFileScreen::render() const { renderer.drawCenteredText(SMALL_FONT_ID, pageHeight / 2 + 95, "UPLOADING"); if (currentUploadStatus == InProgress) { - const double complete = static_cast(currentUploadCompleteSize) / static_cast(currentUploadTotalSize); + const double complete = + static_cast(currentUploadCompleteSize) / static_cast(currentUploadTotalSize); renderer.drawRect(20, pageHeight / 2 + 110, pageWidth - 40, 50); renderer.fillRect(22, pageHeight / 2 + 112, (pageWidth - 44) * complete, 46); } @@ -69,9 +70,11 @@ void UploadFileScreen::onFileUploadPart(AsyncWebServerRequest* request, const ui request->_tempFile.write(data, len); xSemaphoreGive(renderingMutex); - const int oldPercent = static_cast(currentUploadCompleteSize) / static_cast(currentUploadTotalSize) * 100; + const int oldPercent = + static_cast(currentUploadCompleteSize) / static_cast(currentUploadTotalSize) * 100; currentUploadCompleteSize += len; - const int newPercent = static_cast(currentUploadCompleteSize) / static_cast(currentUploadTotalSize) * 100; + const int newPercent = + static_cast(currentUploadCompleteSize) / static_cast(currentUploadTotalSize) * 100; // Only update the screen at most every 5% to avoid blocking the SPI channel if (oldPercent / 5 < newPercent / 5) { diff --git a/src/server/UploadServer.cpp b/src/server/UploadServer.cpp index 4e18ec6..c5c38ff 100644 --- a/src/server/UploadServer.cpp +++ b/src/server/UploadServer.cpp @@ -15,10 +15,7 @@ void UploadServer::begin() { server->on("/upload", HTTP_GET, [](AsyncWebServerRequest* request) { request->send(200, "text/html", UploadHtml); }); server->on( - "/upload", HTTP_POST, - [](AsyncWebServerRequest* request) { - request->send(200, "text/html", UploadSuccessHtml); - }, + "/upload", HTTP_POST, [](AsyncWebServerRequest* request) { request->send(200, "text/html", UploadSuccessHtml); }, [this](AsyncWebServerRequest* request, const String& filename, const size_t index, const uint8_t* data, const size_t len, const bool final) { // This function is called multiple times as data chunks are received