diff --git a/USER_GUIDE.md b/USER_GUIDE.md index ebb4c006..0f438fbf 100644 --- a/USER_GUIDE.md +++ b/USER_GUIDE.md @@ -84,7 +84,8 @@ See [Reading Mode](#4-reading-mode) below for more information. The Browse Files screen acts as a file and folder browser. * **Navigate List:** Use **Left** (or **Volume Up**), or **Right** (or **Volume Down**) to move the selection cursor up and down through folders and books. You can also long-press these buttons to scroll a full page up or down. -* **Open Selection:** Press **Confirm** to open a folder or read a selected book. +* **Open Selection:** Press **Confirm** to open a folder or read a selected book. +* **Delete Files:** Hold and release **Confirm** to delete the selected file. You will be given an option to either confirm or cancel deletion. Folder deletion is not supported. ### 3.4 Recent Books Screen diff --git a/lib/I18n/translations/czech.yaml b/lib/I18n/translations/czech.yaml index f6294420..12fbda81 100644 --- a/lib/I18n/translations/czech.yaml +++ b/lib/I18n/translations/czech.yaml @@ -279,6 +279,7 @@ STR_GO_TO_PERCENT: "Přejít na %" STR_GO_HOME_BUTTON: "Přejít Domů" STR_SYNC_PROGRESS: "Průběh synchronizace" STR_DELETE_CACHE: "Smazat mezipaměť knihy" +STR_DELETE: "Smazat" STR_CHAPTER_PREFIX: "Kapitola:" STR_PAGES_SEPARATOR: "stránek |" STR_BOOK_PREFIX: "Kniha:" diff --git a/lib/I18n/translations/english.yaml b/lib/I18n/translations/english.yaml index 8bc6105b..391f7c09 100644 --- a/lib/I18n/translations/english.yaml +++ b/lib/I18n/translations/english.yaml @@ -297,6 +297,7 @@ STR_GO_TO_PERCENT: "Go to %" STR_GO_HOME_BUTTON: "Go Home" STR_SYNC_PROGRESS: "Sync Progress" STR_DELETE_CACHE: "Delete Book Cache" +STR_DELETE: "Delete" STR_DISPLAY_QR: "Show page as QR" STR_CHAPTER_PREFIX: "Chapter: " STR_PAGES_SEPARATOR: " pages | " diff --git a/lib/I18n/translations/french.yaml b/lib/I18n/translations/french.yaml index 316691c8..cad52604 100644 --- a/lib/I18n/translations/french.yaml +++ b/lib/I18n/translations/french.yaml @@ -279,6 +279,7 @@ STR_GO_TO_PERCENT: "Aller à %" STR_GO_HOME_BUTTON: "Retour Accueil" STR_SYNC_PROGRESS: "Synchro progression" STR_DELETE_CACHE: "Supprimer cache livre" +STR_DELETE: "Supprimer" STR_CHAPTER_PREFIX: "Chapitre : " STR_PAGES_SEPARATOR: " pages | " STR_BOOK_PREFIX: "Livre : " diff --git a/lib/I18n/translations/german.yaml b/lib/I18n/translations/german.yaml index 90db203b..ecd83ac7 100644 --- a/lib/I18n/translations/german.yaml +++ b/lib/I18n/translations/german.yaml @@ -280,6 +280,7 @@ STR_GO_TO_PERCENT: "Gehe zu %" STR_GO_HOME_BUTTON: "Zum Anfang" STR_SYNC_PROGRESS: "Fortschritt synchronisieren" STR_DELETE_CACHE: "Buch-Cache leeren" +STR_DELETE: "Löschen" STR_CHAPTER_PREFIX: "Kapitel:" STR_PAGES_SEPARATOR: " Seiten | " STR_BOOK_PREFIX: "Buch: " diff --git a/lib/I18n/translations/portuguese.yaml b/lib/I18n/translations/portuguese.yaml index fee77f3f..a305bfa7 100644 --- a/lib/I18n/translations/portuguese.yaml +++ b/lib/I18n/translations/portuguese.yaml @@ -279,6 +279,7 @@ STR_GO_TO_PERCENT: "Ir para %" STR_GO_HOME_BUTTON: "Ir para o início" STR_SYNC_PROGRESS: "Sincronizar progresso" STR_DELETE_CACHE: "Excluir cache do livro" +STR_DELETE: "Excluir" STR_CHAPTER_PREFIX: "Capítulo:" STR_PAGES_SEPARATOR: "páginas |" STR_BOOK_PREFIX: "Livro:" diff --git a/lib/I18n/translations/russian.yaml b/lib/I18n/translations/russian.yaml index 2198e9b6..d748f9c4 100644 --- a/lib/I18n/translations/russian.yaml +++ b/lib/I18n/translations/russian.yaml @@ -296,6 +296,7 @@ STR_GO_TO_PERCENT: "Перейти к %" STR_GO_HOME_BUTTON: "На главную" STR_SYNC_PROGRESS: "Синхронизировать прогресс" STR_DELETE_CACHE: "Удалить кэш книги" +STR_DELETE: "Удалить" STR_CHAPTER_PREFIX: "Глава:" STR_DISPLAY_QR: "Показать страницу в виде QR-кода" STR_PAGES_SEPARATOR: "стр. |" diff --git a/lib/I18n/translations/spanish.yaml b/lib/I18n/translations/spanish.yaml index fa2b238d..5b6e4c0e 100644 --- a/lib/I18n/translations/spanish.yaml +++ b/lib/I18n/translations/spanish.yaml @@ -277,6 +277,7 @@ STR_HW_LEFT_LABEL: "Izq. (Tercer botón)" STR_HW_RIGHT_LABEL: "Der. (Cuarto botón)" STR_GO_TO_PERCENT: "Ir a %" STR_GO_HOME_BUTTON: "Volver a inicio" +STR_DELETE: "Borrar" STR_SYNC_PROGRESS: "Sincronizar progreso de lectura" STR_DELETE_CACHE: "Borrar caché del libro" STR_CHAPTER_PREFIX: "Cap.:" diff --git a/lib/I18n/translations/swedish.yaml b/lib/I18n/translations/swedish.yaml index 1eca643e..fd60a663 100644 --- a/lib/I18n/translations/swedish.yaml +++ b/lib/I18n/translations/swedish.yaml @@ -279,6 +279,7 @@ STR_GO_TO_PERCENT: "Gå till %" STR_GO_HOME_BUTTON: "Gå Hem" STR_SYNC_PROGRESS: "Synkroniseringsframsteg" STR_DELETE_CACHE: "Radera bokcache" +STR_DELETE: "Radera" STR_CHAPTER_PREFIX: "Kapitel:" STR_PAGES_SEPARATOR: " sidor | " STR_BOOK_PREFIX: "Bok:" diff --git a/src/activities/home/MyLibraryActivity.cpp b/src/activities/home/MyLibraryActivity.cpp index 84428153..dcf88ac8 100644 --- a/src/activities/home/MyLibraryActivity.cpp +++ b/src/activities/home/MyLibraryActivity.cpp @@ -1,11 +1,13 @@ #include "MyLibraryActivity.h" +#include #include #include #include #include +#include "../util/ConfirmationActivity.h" #include "MappedInputManager.h" #include "components/UITheme.h" #include "fontIds.h" @@ -116,6 +118,14 @@ void MyLibraryActivity::onExit() { files.clear(); } +void MyLibraryActivity::clearFileMetadata(const std::string& fullPath) { + // Only clear cache for .epub files + if (StringUtils::checkFileExtension(fullPath, ".epub")) { + Epub(fullPath, "/.crosspoint").clearCache(); + LOG_DBG("MyLibrary", "Cleared metadata cache for: %s", fullPath.c_str()); + } +} + void MyLibraryActivity::loop() { // Long press BACK (1s+) goes to root folder if (mappedInput.isPressed(MappedInputManager::Button::Back) && mappedInput.getHeldTime() >= GO_HOME_MS && @@ -129,20 +139,58 @@ void MyLibraryActivity::loop() { const int pageItems = UITheme::getInstance().getNumberOfItemsPerPage(renderer, true, false, true, false); if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { - if (files.empty()) { - return; - } + if (files.empty()) return; - if (basepath.back() != '/') basepath += "/"; - if (files[selectorIndex].back() == '/') { - basepath += files[selectorIndex].substr(0, files[selectorIndex].length() - 1); - loadFiles(); - selectorIndex = 0; - requestUpdate(); - } else { - onSelectBook(basepath + files[selectorIndex]); + const std::string& entry = files[selectorIndex]; + bool isDirectory = (entry.back() == '/'); + + if (mappedInput.getHeldTime() >= GO_HOME_MS && !isDirectory) { + // --- LONG PRESS ACTION: DELETE FILE --- + std::string cleanBasePath = basepath; + if (cleanBasePath.back() != '/') cleanBasePath += "/"; + const std::string fullPath = cleanBasePath + entry; + + auto handler = [this, fullPath](const ActivityResult& res) { + if (!res.isCancelled) { + LOG_DBG("MyLibrary", "Attempting to delete: %s", fullPath.c_str()); + clearFileMetadata(fullPath); + if (Storage.remove(fullPath.c_str())) { + LOG_DBG("MyLibrary", "Deleted successfully"); + loadFiles(); + if (files.empty()) { + selectorIndex = 0; + } else if (selectorIndex >= files.size()) { + // Move selection to the new "last" item + selectorIndex = files.size() - 1; + } + + requestUpdate(true); + } else { + LOG_ERR("MyLibrary", "Failed to delete file: %s", fullPath.c_str()); + } + } else { + LOG_DBG("MyLibrary", "Delete cancelled by user"); + } + }; + + std::string heading = tr(STR_DELETE) + std::string("? "); + + startActivityForResult(std::make_unique(renderer, mappedInput, heading, entry), handler); return; + } else { + // --- SHORT PRESS ACTION: OPEN/NAVIGATE --- + if (basepath.back() != '/') basepath += "/"; + + if (isDirectory) { + basepath += entry.substr(0, entry.length() - 1); + loadFiles(); + selectorIndex = 0; + requestUpdate(); + } else { + onSelectBook(basepath + entry); + } } + return; } if (mappedInput.wasReleased(MappedInputManager::Button::Back)) { diff --git a/src/activities/home/MyLibraryActivity.h b/src/activities/home/MyLibraryActivity.h index 6bc1faaa..d3fa7866 100644 --- a/src/activities/home/MyLibraryActivity.h +++ b/src/activities/home/MyLibraryActivity.h @@ -1,4 +1,5 @@ #pragma once + #include #include #include @@ -9,6 +10,10 @@ class MyLibraryActivity final : public Activity { private: + // Deletion + bool pendingSubActivityExit = false; + void clearFileMetadata(const std::string& fullPath); + ButtonNavigator buttonNavigator; size_t selectorIndex = 0; diff --git a/src/activities/util/ConfirmationActivity.cpp b/src/activities/util/ConfirmationActivity.cpp new file mode 100644 index 00000000..c11475c9 --- /dev/null +++ b/src/activities/util/ConfirmationActivity.cpp @@ -0,0 +1,76 @@ +#include "ConfirmationActivity.h" + +#include + +#include "../../components/UITheme.h" +#include "HalDisplay.h" + +ConfirmationActivity::ConfirmationActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, + const std::string& heading, const std::string& body) + : Activity("Confirmation", renderer, mappedInput), heading(heading), body(body) {} + +void ConfirmationActivity::onEnter() { + Activity::onEnter(); + + lineHeight = renderer.getLineHeight(fontId); + const int maxWidth = renderer.getScreenWidth() - (margin * 2); + + if (!heading.empty()) { + safeHeading = renderer.truncatedText(fontId, heading.c_str(), maxWidth, EpdFontFamily::BOLD); + } + if (!body.empty()) { + safeBody = renderer.truncatedText(fontId, body.c_str(), maxWidth, EpdFontFamily::REGULAR); + } + + int totalHeight = 0; + if (!safeHeading.empty()) totalHeight += lineHeight; + if (!safeBody.empty()) totalHeight += lineHeight; + if (!safeHeading.empty() && !safeBody.empty()) totalHeight += spacing; + + startY = (renderer.getScreenHeight() - totalHeight) / 2; + LOG_DBG("CONF", "startY: %d", startY); + LOG_DBG("CONF", "Heading: %s", safeHeading.c_str()); + + requestUpdate(true); +} + +void ConfirmationActivity::render(RenderLock&& lock) { + renderer.clearScreen(); + + int currentY = startY; + LOG_DBG("CONF", "currentY: %d", currentY); + // Draw Heading + if (!safeHeading.empty()) { + renderer.drawCenteredText(fontId, currentY, safeHeading.c_str(), true, EpdFontFamily::BOLD); + currentY += lineHeight + spacing; + } + + // Draw Body + if (!safeBody.empty()) { + renderer.drawCenteredText(fontId, currentY, safeBody.c_str(), true, EpdFontFamily::REGULAR); + } + + // Draw UI Elements + const auto labels = mappedInput.mapLabels("", "", I18N.get(StrId::STR_CANCEL), I18N.get(StrId::STR_CONFIRM)); + GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); + + renderer.displayBuffer(HalDisplay::RefreshMode::FAST_REFRESH); +} + +void ConfirmationActivity::loop() { + if (mappedInput.wasReleased(MappedInputManager::Button::Right)) { + ActivityResult res; + res.isCancelled = false; + setResult(std::move(res)); + finish(); + return; + } + + if (mappedInput.wasReleased(MappedInputManager::Button::Left)) { + ActivityResult res; + res.isCancelled = true; + setResult(std::move(res)); + finish(); + return; + } +} \ No newline at end of file diff --git a/src/activities/util/ConfirmationActivity.h b/src/activities/util/ConfirmationActivity.h new file mode 100644 index 00000000..9f024d92 --- /dev/null +++ b/src/activities/util/ConfirmationActivity.h @@ -0,0 +1,30 @@ +#pragma once +#include +#include + +#include "../../fontIds.h" +#include "../Activity.h" + +class ConfirmationActivity : public Activity { + private: + // Input data + std::string heading; + std::string body; + + const int margin = 20; + const int spacing = 30; + const int fontId = UI_10_FONT_ID; + + std::string safeHeading; + std::string safeBody; + int startY = 0; + int lineHeight = 0; + + public: + ConfirmationActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, const std::string& heading, + const std::string& body); + + void onEnter() override; + void loop() override; + void render(RenderLock&& lock) override; +}; \ No newline at end of file