From 87287012baf3bcf7b6a9c0a295cd2c7bb39dcadf Mon Sep 17 00:00:00 2001 From: Samuel Carpentier Date: Mon, 12 Jan 2026 17:58:08 +0900 Subject: [PATCH 1/8] Updated user guide (sleep screens list) (#293) ## Summary Updated sleep screens list by adding the newly available "Blank" option --- USER_GUIDE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/USER_GUIDE.md b/USER_GUIDE.md index 1af7e129..70a765ba 100644 --- a/USER_GUIDE.md +++ b/USER_GUIDE.md @@ -67,6 +67,7 @@ The Settings screen allows you to configure the device's behavior. There are a f - "Light" - The same default sleep screen, on a white background - "Custom" - Custom images from the SD card, see [Sleep Screen](#36-sleep-screen) below for more information - "Cover" - The book cover image (Note: this is experimental and may not work as expected) + - "Blank" - A blank screen - **Status Bar**: Configure the status bar displayed while reading: - "None" - No status bar - "No Progress" - Show status bar without reading progress From 66811bf50babc726d54509454f836b5c60eddc21 Mon Sep 17 00:00:00 2001 From: Seth <32936504+selunders@users.noreply.github.com> Date: Mon, 12 Jan 2026 00:59:02 -0800 Subject: [PATCH 2/8] Add navigation hints to ChapterSelectionActivities (#294) ## Summary Add navigation hints to Chapter Select - #190 ### Before ![Mi 11X_20260108_214114_lmc_8 4](https://github.com/user-attachments/assets/45031d21-2c6c-4b7d-a5cc-6ad111bf5a70) ### After ![Mi 11X_20260108_213803_lmc_8 4](https://github.com/user-attachments/assets/1fa4ef22-63e4-4adb-8fc5-5fb8c7fa79fa) --- .../reader/EpubReaderChapterSelectionActivity.cpp | 7 ++++++- .../reader/XtcReaderChapterSelectionActivity.cpp | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/activities/reader/EpubReaderChapterSelectionActivity.cpp b/src/activities/reader/EpubReaderChapterSelectionActivity.cpp index 63f1e5a7..8f3ecb80 100644 --- a/src/activities/reader/EpubReaderChapterSelectionActivity.cpp +++ b/src/activities/reader/EpubReaderChapterSelectionActivity.cpp @@ -16,7 +16,9 @@ int EpubReaderChapterSelectionActivity::getPageItems() const { constexpr int lineHeight = 30; const int screenHeight = renderer.getScreenHeight(); - const int availableHeight = screenHeight - startY; + const int endY = screenHeight - lineHeight; + + const int availableHeight = endY - startY; int items = availableHeight / lineHeight; // Ensure we always have at least one item per page to avoid division by zero @@ -134,5 +136,8 @@ void EpubReaderChapterSelectionActivity::renderScreen() { tocIndex != selectorIndex); } + const auto labels = mappedInput.mapLabels("« Back", "Select", "Up", "Down"); + renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4); + renderer.displayBuffer(); } diff --git a/src/activities/reader/XtcReaderChapterSelectionActivity.cpp b/src/activities/reader/XtcReaderChapterSelectionActivity.cpp index fd732924..b2cfecaa 100644 --- a/src/activities/reader/XtcReaderChapterSelectionActivity.cpp +++ b/src/activities/reader/XtcReaderChapterSelectionActivity.cpp @@ -14,7 +14,9 @@ int XtcReaderChapterSelectionActivity::getPageItems() const { constexpr int lineHeight = 30; const int screenHeight = renderer.getScreenHeight(); - const int availableHeight = screenHeight - startY; + const int endY = screenHeight - lineHeight; + + const int availableHeight = endY - startY; int items = availableHeight / lineHeight; if (items < 1) { items = 1; @@ -147,5 +149,8 @@ void XtcReaderChapterSelectionActivity::renderScreen() { renderer.drawText(UI_10_FONT_ID, 20, 60 + (i % pageItems) * 30, title, i != selectorIndex); } + const auto labels = mappedInput.mapLabels("« Back", "Select", "Up", "Down"); + renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4); + renderer.displayBuffer(); } From 97c4871316a8f6ec8e15ef562bbfed567f2177af Mon Sep 17 00:00:00 2001 From: David Fischer <85546373+fischer-hub@users.noreply.github.com> Date: Mon, 12 Jan 2026 10:07:26 +0100 Subject: [PATCH 3/8] Add page turn on power button press (#286) ## Summary * **What is the goal of this PR?** * This PR adds a setting to (additionally) map the forward page turn onto the powerbutton when in `EPUBReaderActivity` and powerbutton short press is not mapped to sleep mode. I find the powerbutton to be exactly where my thumb is while reading so it is very convenient to map the forwardpage turn to that. Maybe Im not alone with this ^^ * **What changes are included?** ## Additional Context * Add any other information that might be helpful for the reviewer (e.g., performance implications, potential risks, specific areas to focus on). --- src/CrossPointSettings.h | 11 ++++++++--- src/activities/reader/EpubReaderActivity.cpp | 2 ++ src/activities/reader/XtcReaderActivity.cpp | 2 ++ src/activities/settings/SettingsActivity.cpp | 2 +- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/CrossPointSettings.h b/src/CrossPointSettings.h index 3a2a3503..ed6ad642 100644 --- a/src/CrossPointSettings.h +++ b/src/CrossPointSettings.h @@ -52,6 +52,9 @@ class CrossPointSettings { // E-ink refresh frequency (pages between full refreshes) enum REFRESH_FREQUENCY { REFRESH_1 = 0, REFRESH_5 = 1, REFRESH_10 = 2, REFRESH_15 = 3, REFRESH_30 = 4 }; + // Short power button press actions + enum SHORT_PWRBTN { IGNORE = 0, SLEEP = 1, PAGE_TURN = 2 }; + // Sleep screen settings uint8_t sleepScreen = DARK; // Sleep screen cover mode settings @@ -61,8 +64,8 @@ class CrossPointSettings { // Text rendering settings uint8_t extraParagraphSpacing = 1; uint8_t textAntiAliasing = 1; - // Duration of the power button press - uint8_t shortPwrBtn = 0; + // Short power button click behaviour + uint8_t shortPwrBtn = IGNORE; // EPUB reading orientation settings // 0 = portrait (default), 1 = landscape clockwise, 2 = inverted, 3 = landscape counter-clockwise uint8_t orientation = PORTRAIT; @@ -88,7 +91,9 @@ class CrossPointSettings { // Get singleton instance static CrossPointSettings& getInstance() { return instance; } - uint16_t getPowerButtonDuration() const { return shortPwrBtn ? 10 : 400; } + uint16_t getPowerButtonDuration() const { + return (shortPwrBtn == CrossPointSettings::SHORT_PWRBTN::SLEEP) ? 10 : 400; + } int getReaderFontId() const; bool saveToFile() const; diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index ac0ffd51..819bcc6f 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -152,6 +152,8 @@ void EpubReaderActivity::loop() { const bool prevReleased = mappedInput.wasReleased(MappedInputManager::Button::PageBack) || mappedInput.wasReleased(MappedInputManager::Button::Left); const bool nextReleased = mappedInput.wasReleased(MappedInputManager::Button::PageForward) || + (SETTINGS.shortPwrBtn == CrossPointSettings::SHORT_PWRBTN::PAGE_TURN && + mappedInput.wasReleased(MappedInputManager::Button::Power)) || mappedInput.wasReleased(MappedInputManager::Button::Right); if (!prevReleased && !nextReleased) { diff --git a/src/activities/reader/XtcReaderActivity.cpp b/src/activities/reader/XtcReaderActivity.cpp index c0580cf6..9cdf5c97 100644 --- a/src/activities/reader/XtcReaderActivity.cpp +++ b/src/activities/reader/XtcReaderActivity.cpp @@ -112,6 +112,8 @@ void XtcReaderActivity::loop() { const bool prevReleased = mappedInput.wasReleased(MappedInputManager::Button::PageBack) || mappedInput.wasReleased(MappedInputManager::Button::Left); const bool nextReleased = mappedInput.wasReleased(MappedInputManager::Button::PageForward) || + (SETTINGS.shortPwrBtn == CrossPointSettings::SHORT_PWRBTN::PAGE_TURN && + mappedInput.wasReleased(MappedInputManager::Button::Power)) || mappedInput.wasReleased(MappedInputManager::Button::Right); if (!prevReleased && !nextReleased) { diff --git a/src/activities/settings/SettingsActivity.cpp b/src/activities/settings/SettingsActivity.cpp index 702db172..1252efbf 100644 --- a/src/activities/settings/SettingsActivity.cpp +++ b/src/activities/settings/SettingsActivity.cpp @@ -21,7 +21,7 @@ const SettingInfo settingsList[settingsCount] = { SettingInfo::Enum("Status Bar", &CrossPointSettings::statusBar, {"None", "No Progress", "Full"}), SettingInfo::Toggle("Extra Paragraph Spacing", &CrossPointSettings::extraParagraphSpacing), SettingInfo::Toggle("Text Anti-Aliasing", &CrossPointSettings::textAntiAliasing), - SettingInfo::Toggle("Short Power Button Click", &CrossPointSettings::shortPwrBtn), + SettingInfo::Enum("Short Power Button Click", &CrossPointSettings::shortPwrBtn, {"Ignore", "Sleep", "Page Turn"}), SettingInfo::Enum("Reading Orientation", &CrossPointSettings::orientation, {"Portrait", "Landscape CW", "Inverted", "Landscape CCW"}), SettingInfo::Enum("Front Button Layout", &CrossPointSettings::frontButtonLayout, From 88d0d904713fff02f20ed345496c102fcdc9d218 Mon Sep 17 00:00:00 2001 From: Jonas Diemer Date: Mon, 12 Jan 2026 10:53:58 +0100 Subject: [PATCH 4/8] Add option to hide battery percentage. (#297) with option to always hide or hide in reader only. Co-authored-by: Dave Allie --- src/CrossPointSettings.cpp | 3 +++ src/CrossPointSettings.h | 5 +++++ src/ScreenComponents.cpp | 5 +++-- src/ScreenComponents.h | 2 +- src/activities/home/HomeActivity.cpp | 10 ++++++++-- src/activities/reader/EpubReaderActivity.cpp | 4 +++- src/activities/settings/SettingsActivity.cpp | 3 ++- 7 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/CrossPointSettings.cpp b/src/CrossPointSettings.cpp index cd8b56f7..1ca9ea74 100644 --- a/src/CrossPointSettings.cpp +++ b/src/CrossPointSettings.cpp @@ -46,6 +46,7 @@ bool CrossPointSettings::saveToFile() const { serialization::writePod(outputFile, sleepScreenCoverMode); serialization::writeString(outputFile, std::string(opdsServerUrl)); serialization::writePod(outputFile, textAntiAliasing); + serialization::writePod(outputFile, hideBatteryPercentage); outputFile.close(); Serial.printf("[%lu] [CPS] Settings saved to file\n", millis()); @@ -110,6 +111,8 @@ bool CrossPointSettings::loadFromFile() { } serialization::readPod(inputFile, textAntiAliasing); if (++settingsRead >= fileSettingsCount) break; + serialization::readPod(inputFile, hideBatteryPercentage); + if (++settingsRead >= fileSettingsCount) break; } while (false); inputFile.close(); diff --git a/src/CrossPointSettings.h b/src/CrossPointSettings.h index ed6ad642..d5f91039 100644 --- a/src/CrossPointSettings.h +++ b/src/CrossPointSettings.h @@ -55,6 +55,9 @@ class CrossPointSettings { // Short power button press actions enum SHORT_PWRBTN { IGNORE = 0, SLEEP = 1, PAGE_TURN = 2 }; + // Hide battery percentage + enum HIDE_BATTERY_PERCENTAGE { HIDE_NEVER = 0, HIDE_READER = 1, HIDE_ALWAYS = 2 }; + // Sleep screen settings uint8_t sleepScreen = DARK; // Sleep screen cover mode settings @@ -85,6 +88,8 @@ class CrossPointSettings { uint8_t screenMargin = 5; // OPDS browser settings char opdsServerUrl[128] = ""; + // Hide battery percentage + uint8_t hideBatteryPercentage = HIDE_NEVER; ~CrossPointSettings() = default; diff --git a/src/ScreenComponents.cpp b/src/ScreenComponents.cpp index 3c359c0e..42b6ef7b 100644 --- a/src/ScreenComponents.cpp +++ b/src/ScreenComponents.cpp @@ -8,10 +8,11 @@ #include "Battery.h" #include "fontIds.h" -void ScreenComponents::drawBattery(const GfxRenderer& renderer, const int left, const int top) { +void ScreenComponents::drawBattery(const GfxRenderer& renderer, const int left, const int top, + const bool showPercentage) { // Left aligned battery icon and percentage const uint16_t percentage = battery.readPercentage(); - const auto percentageText = std::to_string(percentage) + "%"; + const auto percentageText = showPercentage ? std::to_string(percentage) + "%" : ""; renderer.drawText(SMALL_FONT_ID, left + 20, top, percentageText.c_str()); // 1 column on left, 2 columns on right, 5 columns of battery body diff --git a/src/ScreenComponents.h b/src/ScreenComponents.h index d938beea..150fb0c8 100644 --- a/src/ScreenComponents.h +++ b/src/ScreenComponents.h @@ -7,7 +7,7 @@ class GfxRenderer; class ScreenComponents { public: - static void drawBattery(const GfxRenderer& renderer, int left, int top); + static void drawBattery(const GfxRenderer& renderer, int left, int top, bool showPercentage = true); /** * Draw a progress bar with percentage text. diff --git a/src/activities/home/HomeActivity.cpp b/src/activities/home/HomeActivity.cpp index f34283d4..9c519d76 100644 --- a/src/activities/home/HomeActivity.cpp +++ b/src/activities/home/HomeActivity.cpp @@ -7,6 +7,7 @@ #include #include +#include "Battery.h" #include "CrossPointSettings.h" #include "CrossPointState.h" #include "MappedInputManager.h" @@ -332,8 +333,13 @@ void HomeActivity::render() const { const auto labels = mappedInput.mapLabels("", "Confirm", "Up", "Down"); renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4); - const auto batteryX = pageWidth - 25 - renderer.getTextWidth(SMALL_FONT_ID, "100 %"); - ScreenComponents::drawBattery(renderer, batteryX, 10); + const bool showBatteryPercentage = + SETTINGS.hideBatteryPercentage != CrossPointSettings::HIDE_BATTERY_PERCENTAGE::HIDE_ALWAYS; + // get percentage so we can align text properly + const uint16_t percentage = battery.readPercentage(); + const auto percentageText = showBatteryPercentage ? std::to_string(percentage) + "%" : ""; + const auto batteryX = pageWidth - 25 - renderer.getTextWidth(SMALL_FONT_ID, percentageText.c_str()); + ScreenComponents::drawBattery(renderer, batteryX, 10, showBatteryPercentage); renderer.displayBuffer(); } diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index 819bcc6f..f51cf9bf 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -419,6 +419,8 @@ void EpubReaderActivity::renderStatusBar(const int orientedMarginRight, const in SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::FULL; const bool showChapterTitle = SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::NO_PROGRESS || SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::FULL; + const bool showBatteryPercentage = + SETTINGS.hideBatteryPercentage == CrossPointSettings::HIDE_BATTERY_PERCENTAGE::HIDE_NEVER; // Position status bar near the bottom of the logical screen, regardless of orientation const auto screenHeight = renderer.getScreenHeight(); @@ -439,7 +441,7 @@ void EpubReaderActivity::renderStatusBar(const int orientedMarginRight, const in } if (showBattery) { - ScreenComponents::drawBattery(renderer, orientedMarginLeft + 1, textY); + ScreenComponents::drawBattery(renderer, orientedMarginLeft + 1, textY, showBatteryPercentage); } if (showChapterTitle) { diff --git a/src/activities/settings/SettingsActivity.cpp b/src/activities/settings/SettingsActivity.cpp index 1252efbf..f22850a9 100644 --- a/src/activities/settings/SettingsActivity.cpp +++ b/src/activities/settings/SettingsActivity.cpp @@ -13,12 +13,13 @@ // Define the static settings list namespace { -constexpr int settingsCount = 18; +constexpr int settingsCount = 19; const SettingInfo settingsList[settingsCount] = { // Should match with SLEEP_SCREEN_MODE SettingInfo::Enum("Sleep Screen", &CrossPointSettings::sleepScreen, {"Dark", "Light", "Custom", "Cover", "None"}), SettingInfo::Enum("Sleep Screen Cover Mode", &CrossPointSettings::sleepScreenCoverMode, {"Fit", "Crop"}), SettingInfo::Enum("Status Bar", &CrossPointSettings::statusBar, {"None", "No Progress", "Full"}), + SettingInfo::Enum("Hide Battery %", &CrossPointSettings::hideBatteryPercentage, {"Never", "In Reader", "Always"}), SettingInfo::Toggle("Extra Paragraph Spacing", &CrossPointSettings::extraParagraphSpacing), SettingInfo::Toggle("Text Anti-Aliasing", &CrossPointSettings::textAntiAliasing), SettingInfo::Enum("Short Power Button Click", &CrossPointSettings::shortPwrBtn, {"Ignore", "Sleep", "Page Turn"}), From a9242fe61fcee99c92ecc7fe281b26f487d59ef0 Mon Sep 17 00:00:00 2001 From: Jonas Diemer Date: Mon, 12 Jan 2026 10:55:47 +0100 Subject: [PATCH 5/8] Generate different .bmp for cropped covers so settings have effect. (#330) Addresses https://github.com/daveallie/crosspoint-reader/pull/225#issuecomment-3735150337 --- lib/Epub/Epub.cpp | 13 ++++++++----- lib/Epub/Epub.h | 4 ++-- src/activities/boot_sleep/SleepActivity.cpp | 5 +++-- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/lib/Epub/Epub.cpp b/lib/Epub/Epub.cpp index 234344d7..9c4b058b 100644 --- a/lib/Epub/Epub.cpp +++ b/lib/Epub/Epub.cpp @@ -345,11 +345,14 @@ const std::string& Epub::getAuthor() const { return bookMetadataCache->coreMetadata.author; } -std::string Epub::getCoverBmpPath() const { return cachePath + "/cover.bmp"; } +std::string Epub::getCoverBmpPath(bool cropped) const { + const auto coverFileName = "cover" + cropped ? "_crop" : ""; + return cachePath + "/" + coverFileName + ".bmp"; +} -bool Epub::generateCoverBmp() const { +bool Epub::generateCoverBmp(bool cropped) const { // Already generated, return true - if (SdMan.exists(getCoverBmpPath().c_str())) { + if (SdMan.exists(getCoverBmpPath(cropped).c_str())) { return true; } @@ -381,7 +384,7 @@ bool Epub::generateCoverBmp() const { } FsFile coverBmp; - if (!SdMan.openFileForWrite("EBP", getCoverBmpPath(), coverBmp)) { + if (!SdMan.openFileForWrite("EBP", getCoverBmpPath(cropped), coverBmp)) { coverJpg.close(); return false; } @@ -392,7 +395,7 @@ bool Epub::generateCoverBmp() const { if (!success) { Serial.printf("[%lu] [EBP] Failed to generate BMP from JPG cover image\n", millis()); - SdMan.remove(getCoverBmpPath().c_str()); + SdMan.remove(getCoverBmpPath(cropped).c_str()); } Serial.printf("[%lu] [EBP] Generated BMP from JPG cover image, success: %s\n", millis(), success ? "yes" : "no"); return success; diff --git a/lib/Epub/Epub.h b/lib/Epub/Epub.h index a6555e7e..047c955a 100644 --- a/lib/Epub/Epub.h +++ b/lib/Epub/Epub.h @@ -44,8 +44,8 @@ class Epub { const std::string& getPath() const; const std::string& getTitle() const; const std::string& getAuthor() const; - std::string getCoverBmpPath() const; - bool generateCoverBmp() const; + std::string getCoverBmpPath(bool cropped = false) const; + bool generateCoverBmp(bool cropped = false) const; uint8_t* readItemContentsToBytes(const std::string& itemHref, size_t* size = nullptr, bool trailingNullByte = false) const; bool readItemContentsToStream(const std::string& itemHref, Print& out, size_t chunkSize) const; diff --git a/src/activities/boot_sleep/SleepActivity.cpp b/src/activities/boot_sleep/SleepActivity.cpp index 43e8e60b..7c79fcb1 100644 --- a/src/activities/boot_sleep/SleepActivity.cpp +++ b/src/activities/boot_sleep/SleepActivity.cpp @@ -199,6 +199,7 @@ void SleepActivity::renderCoverSleepScreen() const { } std::string coverBmpPath; + bool cropped = SETTINGS.sleepScreenCoverMode == CrossPointSettings::SLEEP_SCREEN_COVER_MODE::CROP; if (StringUtils::checkFileExtension(APP_STATE.openEpubPath, ".xtc") || StringUtils::checkFileExtension(APP_STATE.openEpubPath, ".xtch")) { @@ -223,12 +224,12 @@ void SleepActivity::renderCoverSleepScreen() const { return renderDefaultSleepScreen(); } - if (!lastEpub.generateCoverBmp()) { + if (!lastEpub.generateCoverBmp(cropped)) { Serial.println("[SLP] Failed to generate cover bmp"); return renderDefaultSleepScreen(); } - coverBmpPath = lastEpub.getCoverBmpPath(); + coverBmpPath = lastEpub.getCoverBmpPath(cropped); } else { return renderDefaultSleepScreen(); } From 82f21f3c1d02862720cf2ad775f1e78fe37b5967 Mon Sep 17 00:00:00 2001 From: Dave Allie Date: Mon, 12 Jan 2026 21:35:18 +1100 Subject: [PATCH 6/8] Add AI usage question to the PR template --- .github/PULL_REQUEST_TEMPLATE.md | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index c3866016..ee374b73 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,9 +1,22 @@ ## Summary -* **What is the goal of this PR?** (e.g., Fixes a bug in the user authentication module, Implements the new feature for - file uploading.) +* **What is the goal of this PR?** (e.g., Implements the new feature for file uploading.) * **What changes are included?** ## Additional Context -* Add any other information that might be helpful for the reviewer (e.g., performance implications, potential risks, specific areas to focus on). +* Add any other information that might be helpful for the reviewer (e.g., performance implications, potential risks, + specific areas to focus on). + +--- + +### AI Usage + +While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it +helps set the right context for reviewers. + +Did you use AI tools to help write this code? + +- [ ] Yes +- [ ] Partially +- [ ] No From 41bda438997061f1dbf8115893da0ef9cd35eb9a Mon Sep 17 00:00:00 2001 From: Andrew Brandt Date: Mon, 12 Jan 2026 04:37:23 -0600 Subject: [PATCH 7/8] chore: update formatting in github workflows (#320) **Description**: The purpose of this change is to modify the spacing in the `.github/workflow` files to ensure consistency. **Related Issue(s)**: Implements #319 Signed-off-by: Andrew Brandt --- .github/workflows/ci.yml | 2 +- .github/workflows/release.yml | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index be9a6e59..286f14aa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,11 +7,11 @@ name: CI jobs: build: runs-on: ubuntu-latest - steps: - uses: actions/checkout@v6 with: submodules: recursive + - uses: actions/setup-python@v6 with: python-version: '3.14' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c8d1c830..df8d6679 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,17 +7,18 @@ on: jobs: build-release: runs-on: ubuntu-latest - steps: - uses: actions/checkout@v6 with: submodules: recursive + - uses: actions/cache@v5 with: path: | ~/.cache/pip ~/.platformio/.cache key: ${{ runner.os }}-pio + - uses: actions/setup-python@v6 with: python-version: '3.14' From 66b100c6ca1c9a8545246dd6a03967e468b7d6d5 Mon Sep 17 00:00:00 2001 From: danoob <2942485+danoooob@users.noreply.github.com> Date: Mon, 12 Jan 2026 17:49:42 +0700 Subject: [PATCH 8/8] fix: Wi-Fi Selection on Calibre Library launch (#313) ## Summary * **What is the goal of this PR?** Fixes the Wi-Fi connection issue when launching the Calibre Library (OPDS browser). The previous implementation always attempted to connect using the first saved WiFi credential, which caused connection failures when users were in locations where only other saved networks (not the first one) were available. Now, the activity launches a WiFi selection screen allowing users to choose from available networks. * **What changes are included?** ## Additional Context **Bug Fixed**: Previously, the code used `credentials[0]` (always the first saved WiFi), so users in areas with only their secondary/tertiary saved networks available could never connect. --------- Co-authored-by: danoooob --- .../browser/OpdsBookBrowserActivity.cpp | 74 +++++++++++-------- .../browser/OpdsBookBrowserActivity.h | 20 +++-- 2 files changed, 54 insertions(+), 40 deletions(-) diff --git a/src/activities/browser/OpdsBookBrowserActivity.cpp b/src/activities/browser/OpdsBookBrowserActivity.cpp index b9dbac8e..4e0a08d2 100644 --- a/src/activities/browser/OpdsBookBrowserActivity.cpp +++ b/src/activities/browser/OpdsBookBrowserActivity.cpp @@ -7,7 +7,7 @@ #include "CrossPointSettings.h" #include "MappedInputManager.h" #include "ScreenComponents.h" -#include "WifiCredentialStore.h" +#include "activities/network/WifiSelectionActivity.h" #include "fontIds.h" #include "network/HttpDownloader.h" #include "util/StringUtils.h" @@ -25,7 +25,7 @@ void OpdsBookBrowserActivity::taskTrampoline(void* param) { } void OpdsBookBrowserActivity::onEnter() { - Activity::onEnter(); + ActivityWithSubactivity::onEnter(); renderingMutex = xSemaphoreCreateMutex(); state = BrowserState::CHECK_WIFI; @@ -49,7 +49,7 @@ void OpdsBookBrowserActivity::onEnter() { } void OpdsBookBrowserActivity::onExit() { - Activity::onExit(); + ActivityWithSubactivity::onExit(); // Turn off WiFi when exiting WiFi.mode(WIFI_OFF); @@ -66,13 +66,28 @@ void OpdsBookBrowserActivity::onExit() { } void OpdsBookBrowserActivity::loop() { + // Handle WiFi selection subactivity + if (state == BrowserState::WIFI_SELECTION) { + ActivityWithSubactivity::loop(); + return; + } + // Handle error state - Confirm retries, Back goes back or home if (state == BrowserState::ERROR) { if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { - state = BrowserState::LOADING; - statusMessage = "Loading..."; - updateRequired = true; - fetchFeed(currentPath); + // Check if WiFi is still connected + if (WiFi.status() == WL_CONNECTED && WiFi.localIP() != IPAddress(0, 0, 0, 0)) { + // WiFi connected - just retry fetching the feed + Serial.printf("[%lu] [OPDS] Retry: WiFi connected, retrying fetch\n", millis()); + state = BrowserState::LOADING; + statusMessage = "Loading..."; + updateRequired = true; + fetchFeed(currentPath); + } else { + // WiFi not connected - launch WiFi selection + Serial.printf("[%lu] [OPDS] Retry: WiFi not connected, launching selection\n", millis()); + launchWifiSelection(); + } } else if (mappedInput.wasReleased(MappedInputManager::Button::Back)) { navigateBack(); } @@ -350,8 +365,8 @@ void OpdsBookBrowserActivity::downloadBook(const OpdsEntry& book) { } void OpdsBookBrowserActivity::checkAndConnectWifi() { - // Already connected? - if (WiFi.status() == WL_CONNECTED) { + // Already connected? Verify connection is valid by checking IP + if (WiFi.status() == WL_CONNECTED && WiFi.localIP() != IPAddress(0, 0, 0, 0)) { state = BrowserState::LOADING; statusMessage = "Loading..."; updateRequired = true; @@ -359,38 +374,33 @@ void OpdsBookBrowserActivity::checkAndConnectWifi() { return; } - // Try to connect using saved credentials - statusMessage = "Connecting to WiFi..."; + // Not connected - launch WiFi selection screen directly + launchWifiSelection(); +} + +void OpdsBookBrowserActivity::launchWifiSelection() { + state = BrowserState::WIFI_SELECTION; updateRequired = true; - WIFI_STORE.loadFromFile(); - const auto& credentials = WIFI_STORE.getCredentials(); - if (credentials.empty()) { - state = BrowserState::ERROR; - errorMessage = "No WiFi credentials saved"; - updateRequired = true; - return; - } + enterNewActivity(new WifiSelectionActivity(renderer, mappedInput, + [this](const bool connected) { onWifiSelectionComplete(connected); })); +} - // Use the first saved credential - const auto& cred = credentials[0]; - WiFi.mode(WIFI_STA); - WiFi.begin(cred.ssid.c_str(), cred.password.c_str()); +void OpdsBookBrowserActivity::onWifiSelectionComplete(const bool connected) { + exitActivity(); - // Wait for connection with timeout - constexpr int WIFI_TIMEOUT_MS = 10000; - const unsigned long startTime = millis(); - while (WiFi.status() != WL_CONNECTED && millis() - startTime < WIFI_TIMEOUT_MS) { - vTaskDelay(100 / portTICK_PERIOD_MS); - } - - if (WiFi.status() == WL_CONNECTED) { - Serial.printf("[%lu] [OPDS] WiFi connected: %s\n", millis(), WiFi.localIP().toString().c_str()); + if (connected) { + Serial.printf("[%lu] [OPDS] WiFi connected via selection, fetching feed\n", millis()); state = BrowserState::LOADING; statusMessage = "Loading..."; updateRequired = true; fetchFeed(currentPath); } else { + Serial.printf("[%lu] [OPDS] WiFi selection cancelled/failed\n", millis()); + // Force disconnect to ensure clean state for next retry + // This prevents stale connection status from interfering + WiFi.disconnect(); + WiFi.mode(WIFI_OFF); state = BrowserState::ERROR; errorMessage = "WiFi connection failed"; updateRequired = true; diff --git a/src/activities/browser/OpdsBookBrowserActivity.h b/src/activities/browser/OpdsBookBrowserActivity.h index efda294c..b08d9c2a 100644 --- a/src/activities/browser/OpdsBookBrowserActivity.h +++ b/src/activities/browser/OpdsBookBrowserActivity.h @@ -8,25 +8,27 @@ #include #include -#include "../Activity.h" +#include "../ActivityWithSubactivity.h" /** * Activity for browsing and downloading books from an OPDS server. * Supports navigation through catalog hierarchy and downloading EPUBs. + * When WiFi connection fails, launches WiFi selection to let user connect. */ -class OpdsBookBrowserActivity final : public Activity { +class OpdsBookBrowserActivity final : public ActivityWithSubactivity { public: enum class BrowserState { - CHECK_WIFI, // Checking WiFi connection - LOADING, // Fetching OPDS feed - BROWSING, // Displaying entries (navigation or books) - DOWNLOADING, // Downloading selected EPUB - ERROR // Error state with message + CHECK_WIFI, // Checking WiFi connection + WIFI_SELECTION, // WiFi selection subactivity is active + LOADING, // Fetching OPDS feed + BROWSING, // Displaying entries (navigation or books) + DOWNLOADING, // Downloading selected EPUB + ERROR // Error state with message }; explicit OpdsBookBrowserActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, const std::function& onGoHome) - : Activity("OpdsBookBrowser", renderer, mappedInput), onGoHome(onGoHome) {} + : ActivityWithSubactivity("OpdsBookBrowser", renderer, mappedInput), onGoHome(onGoHome) {} void onEnter() override; void onExit() override; @@ -54,6 +56,8 @@ class OpdsBookBrowserActivity final : public Activity { void render() const; void checkAndConnectWifi(); + void launchWifiSelection(); + void onWifiSelectionComplete(bool connected); void fetchFeed(const std::string& path); void navigateToEntry(const OpdsEntry& entry); void navigateBack();