diff --git a/lib/I18n/translations/english.yaml b/lib/I18n/translations/english.yaml index 3e904089..12b6ebf4 100644 --- a/lib/I18n/translations/english.yaml +++ b/lib/I18n/translations/english.yaml @@ -442,5 +442,7 @@ STR_UPTIME: "Uptime" STR_CHARGING: "Charging" STR_GATHERING_DATA: "Gathering data..." STR_READING: "Reading..." +STR_SET_SLEEP_SCREEN: "Set Sleep" +STR_SLEEP_SCREEN_SET: "Sleep screen updated!" STR_WEATHER_MOON_INFO: "Moon" STR_WEATHER_SUN_INFO: "Sun" \ No newline at end of file diff --git a/lib/hal/HalStorage.cpp b/lib/hal/HalStorage.cpp index 82b6e03c..43c4b917 100644 --- a/lib/hal/HalStorage.cpp +++ b/lib/hal/HalStorage.cpp @@ -7,6 +7,7 @@ #include #include +#include #define SDCard SDCardManager::getInstance() @@ -138,6 +139,35 @@ bool HalStorage::openFileForWrite(const char* moduleName, const String& path, Ha bool HalStorage::removeDir(const char* path) { HAL_STORAGE_WRAPPED_CALL(removeDir, path); } +bool HalStorage::copyFile(const char* moduleName, const std::string& srcPath, const char* dstPath) { + HalFile src, dst; + if (!openFileForRead(moduleName, srcPath, src)) return false; + if (!openFileForWrite(moduleName, dstPath, dst)) { + src.close(); + return false; + } + constexpr size_t BUF_SIZE = 4096; + auto* buf = new (std::nothrow) uint8_t[BUF_SIZE]; + if (!buf) { + dst.close(); + src.close(); + return false; + } + bool ok = true; + while (src.available()) { + const auto bytesRead = src.read(buf, BUF_SIZE); + if (bytesRead <= 0) break; + if (dst.write(buf, bytesRead) != static_cast(bytesRead)) { + ok = false; + break; + } + } + delete[] buf; + dst.close(); + src.close(); + return ok; +} + // HalFile implementation // Allow doing file operations while ensuring thread safety via HalStorage's mutex. // Please keep the list below in sync with the HalFile.h header diff --git a/lib/hal/HalStorage.h b/lib/hal/HalStorage.h index d9bee706..4a59b37e 100644 --- a/lib/hal/HalStorage.h +++ b/lib/hal/HalStorage.h @@ -44,6 +44,7 @@ class HalStorage { bool openFileForWrite(const char* moduleName, const std::string& path, HalFile& file); bool openFileForWrite(const char* moduleName, const String& path, HalFile& file); bool removeDir(const char* path); + bool copyFile(const char* moduleName, const std::string& srcPath, const char* dstPath); uint64_t sdTotalBytes() const; uint64_t sdUsedBytes(); diff --git a/src/activities/util/BmpViewerActivity.cpp b/src/activities/util/BmpViewerActivity.cpp index cdcb15b0..dae4fe62 100644 --- a/src/activities/util/BmpViewerActivity.cpp +++ b/src/activities/util/BmpViewerActivity.cpp @@ -5,9 +5,15 @@ #include #include +#include "../reader/ReaderUtils.h" +#include "CrossPointSettings.h" #include "components/UITheme.h" #include "fontIds.h" +namespace { +constexpr const char* SLEEP_BMP_PATH = "/sleep.bmp"; +} // namespace + BmpViewerActivity::BmpViewerActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::string path) : Activity("BmpViewer", renderer, mappedInput), filePath(std::move(path)) {} @@ -49,7 +55,7 @@ void BmpViewerActivity::onEnter() { } // 4. Prepare Rendering - const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", ""); + const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", tr(STR_SET_SLEEP_SCREEN)); GUI.fillPopupProgress(renderer, popupRect, 50); renderer.clearScreen(); @@ -89,12 +95,44 @@ void BmpViewerActivity::onExit() { renderer.displayBuffer(HalDisplay::FULL_REFRESH); } +void BmpViewerActivity::setAsSleepScreen() { + // Only copy if the source isn't already /sleep.bmp + if (filePath != SLEEP_BMP_PATH) { + if (!Storage.copyFile("BMP", filePath, SLEEP_BMP_PATH)) { + LOG_ERR("BMP", "Failed to copy %s to %s", filePath.c_str(), SLEEP_BMP_PATH); + return; + } + } + + // Switch sleep screen mode to CUSTOM so the copied image is used + SETTINGS.sleepScreen = CrossPointSettings::SLEEP_SCREEN_MODE::CUSTOM; + SETTINGS.saveToFile(); + LOG_INF("BMP", "Set %s as sleep screen", filePath.c_str()); + + GUI.drawPopup(renderer, tr(STR_SLEEP_SCREEN_SET)); + renderer.displayBuffer(HalDisplay::HALF_REFRESH); +} + void BmpViewerActivity::loop() { // Keep CPU awake/polling so 1st click works Activity::loop(); - if (mappedInput.wasReleased(MappedInputManager::Button::Back)) { + // Long press BACK (1s+) goes to home screen + if (mappedInput.isPressed(MappedInputManager::Button::Back) && mappedInput.getHeldTime() >= ReaderUtils::GO_HOME_MS) { onGoHome(); return; } + + // Short press BACK returns to the calling activity + if (mappedInput.wasReleased(MappedInputManager::Button::Back) && + mappedInput.getHeldTime() < ReaderUtils::GO_HOME_MS) { + finish(); + return; + } + + // Next/Right button: set this image as the sleep screen + if (mappedInput.wasReleased(MappedInputManager::Button::Right)) { + setAsSleepScreen(); + return; + } } \ No newline at end of file diff --git a/src/activities/util/BmpViewerActivity.h b/src/activities/util/BmpViewerActivity.h index feac448e..2f19d236 100644 --- a/src/activities/util/BmpViewerActivity.h +++ b/src/activities/util/BmpViewerActivity.h @@ -16,4 +16,5 @@ class BmpViewerActivity final : public Activity { private: std::string filePath; + void setAsSleepScreen(); }; \ No newline at end of file