11ca208ec2ffb31ac94b8d359d11f7249d8800b0
209
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
11ca208ec2 |
chore: change label while on settings tab actions (#1325)
## Summary * **What is the goal of this PR?** The "Toggle" label while on the "tab" actions of settings screen was misleading. Will show "Select" now , while "Toggle" remains in place for all 'real' settings * **What changes are included?** ## Additional Context <img width="240" alt="1" src="https://github.com/user-attachments/assets/dc198716-0aad-4c75-96fe-52595625e69d" /> <img width="240" alt="2" src="https://github.com/user-attachments/assets/85ce5368-801c-489d-aa94-51f126c3ddc8" /> --- ### 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? _**NO **_ |
||
|
|
7a28f90dad |
fix: back button in settings returns to tab bar first (#1354)
## Summary - Pressing Back while browsing settings within a category now jumps focus to the category tab bar - Pressing Back again from the tab bar exits settings to home - Previously, Back always exited directly to home regardless of scroll position Closes #797 ## Test plan - [ ] Scroll deep into a settings category → press Back → tab bar is focused - [ ] Press Back again from tab bar → exits to home screen - [ ] Use category switching (continuous hold) → still works as before - [ ] Settings are saved on exit (not on tab-bar jump) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: bkb <bkb@arcnode.xyz> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
f1e9dc7f30 |
perf: font-compression improvements (#1056)
## Purpose This PR includes some preparatory changes that are needed for an upcoming performant CJK font feature. The changes have no impact on render time and heap allocation for latin text. **Despite this, I think these changes stand on their own as a better font compression/decompression implementation.** ## Summary - Font decompressor rewrite: Replaced the 4-slot LRU group cache with a two-tier system — a page buffer (glyphs prewarmed before rendering begins) and a hot-group fallback (last decompressed group retained for non-prewarmed glyphs). - Byte-aligned compressed bitmap format: Glyph bitmaps within compressed groups are now stored row-padded rather than tightly packed before DEFLATE compression, improving compression ratios by making identical pixel rows produce identical byte patterns. Glyphs are compacted back to packed format on demand at render time. Reduces flash size by 155 KB. - Page prewarm system: Added `Page::collectText` and `Page::getDominantStyle` to extract per-style glyph requirements before rendering, and `GfxRenderer::prewarmFontCache` to pre-decompress only the groups needed for the dominant style — eliminating mid-render decompression for the common case. - UTF-8 robustness fixes: `utf8NextCodepoint` now validates continuation bytes and returns a replacement glyph on malformed input; `ChapterHtmlSlimParser` correctly preserves incomplete multi-byte sequences across word-buffer flush boundaries rather than splitting them. --- ### 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**_ Architecture and design was done by me, refined a bit by Claude. Code mostly by Claude, but not entirely. |
||
|
|
cd508d27d5 |
refactor: reader utils (#1329)
## Summary Extract shared reader utilities (`ReaderUtils.h`) to reduce duplication across `EpubReaderActivity`, `TxtReaderActivity`, and (upcoming) `MarkdownReaderActivity`. Utilities extracted: - `applyOrientation()` — orientation switch logic - `detectPageTurn()` — page navigation input detection - `renderAntiAliased()` — grayscale anti-aliasing pass - `displayWithRefreshCycle()` — refresh mode cadence - `GO_HOME_MS` — back button timing constant ## Impact Flash: 32 bytes saved (6006441 → 6006409 bytes). Minimal immediate gain, but meaningful once markdown reader and future reader types share these functions. Code quality: Eliminates ~100 lines of duplicated logic spread across multiple files. All readers now follow the same patterns for orientation, input handling, and rendering. ## Rationale This refactor is preparation for markdown support, which requires identical input and rendering logic. Instead of copy-pasting these patterns a third time, all readers now share a single, tested implementation. Future reader types can reuse `ReaderUtils` without duplication. --- ## AI Usage Did you use AI tools to help write this code? YES Claude extracted the code, under my guidance. Tested on my device and seems to work fine. |
||
|
|
4d22256745 |
feat: footnote anchor navigation (#1245)
## Summary: Enable footnote anchor navigation in EPUB reader This PR extracts the core anchor-to-page mapping mechanism from PR #1143 (TOC fragment navigation) to provide immediate footnote navigation support. By merging this focused subset first, users get a complete footnote experience now while simplifying the eventual review and merge of the full #1143 PR. --- ## What this extracts from PR #1143 PR #1143 implements comprehensive TOC fragment navigation for EPUBs with multi-chapter spine files. This PR takes only the anchor resolution infrastructure: - Anchor-to-page mapping in section cache: During page layout, ChapterHtmlSlimParser records which page each HTML id attribute lands on, serializing the map into the .bin cache file. - Anchor resolution in `EpubReaderActivity`: When navigating to a footnote link with a fragment (e.g., `chapter2.xhtml#note1`), the reader resolves the anchor to a page number and jumps directly to it. - Section file format change: Bumped to version 15, adds anchor map offset in header. --- ## Simplified scope vs. PR #1143 To minimize conflicts and complexity, this PR differs from #1143 in key ways: * **Anchors tracked** * **Origin:** Only TOC anchors (passed via `std::set`) * **This branch:** All `id` attributes * **Page breaks** * **Origin**: Forces new page at TOC chapter boundaries * **This branch:** None — natural flow * **TOC integration** * **Origin**: `tocBoundaries`, `getTocIndexForPage()`, chapter skip * **This branch:** None — just footnote links * **Bug fix** * **This branch:** Fixed anchor page off-by-1/2 bug The anchor recording bug (recording page number before `makePages()` flushes previous block) was identified and fixed during this extraction. The fix uses a deferred `pendingAnchorId` pattern that records the anchor after page completion. --- ## Positioning for future merge Changes are structured to minimize conflicts when #1143 eventually merges: - `ChapterHtmlSlimParser.cpp` `startElement()`: Both branches rewrite the same if `(!idAttr.empty())` block. The merged version will combine both approaches (TOC anchors get page breaks + immediate recording; footnote anchors get deferred recording). - `EpubReaderActivity.cpp` `render()`: The `pendingAnchor` resolution block is positioned at the exact same insertion point where #1143 places its `pendingTocIndex` block (line 596, right after `nextPageNumber` assignment). During merge, both blocks will sit side-by-side. --- ## Why merge separately? 1. Immediate user value: Footnote navigation works now without waiting for the full TOC overhaul 2. Easier review: ~100 lines vs. 500+ lines in #1143 3. Bug fix included: The page recording bug is fixed here and will carry into #1143 4. Minimal conflicts: Structured for clean merge — both PRs touch the same files but in complementary ways --- ### AI Usage Did you use AI tools to help write this code? _**< YES >**_ Done by Claude Opus 4.6 |
||
|
|
c3f1dbfa09 |
perf: Avoid creating strings for file extension checks (#1303)
## Summary **What is the goal of this PR?** This change avoids the pattern of creating a `std::string` using `.substr` in order to compare against a file extension literal. ```c++ std::string path; if (path.length() >= 4 && path.substr(path.length() - 4) == ".ext") ``` The `checkFileExtension` utility has moved from StringUtils to FsHelpers, to be available to code in lib/. The signature now accepts a `std::string_view` instead of `std::string`, which makes the single implementation reusable for Arduino `String`. Added utility functions for commonly repeated extensions. These changes **save about 2 KB of flash (5,999,427 to 5,997,343)**. --- ### 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? _**NO**_ |
||
|
|
ce0b439aa3 |
feat: User setting for image display (#1291)
## Summary **What is the goal of this PR?** Add a user setting to decide image support: display, show placeholder instead, supress fully Fixes #1289 --- ### 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? _**< NO >**_ |
||
|
|
307a6608f0 |
chore: remove rendundant xTaskCreate (#1264)
## Summary Ref discussion: https://github.com/crosspoint-reader/crosspoint-reader/pull/1222#discussion_r2865402110 Important note that this is a bug-for-bug fix. In reality, this branch `WiFi.status() == WL_CONNECTED` is pretty much a dead code because the entry point of these 2 activities don't use wifi. It is better to refactor the management of network though, but it's better to be a dedicated PR. --- ### 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? **NO** |
||
|
|
ef02737c89 |
feat: Prefer ".sleep" over "sleep" for custom image directory (#948)
## Summary * Custom sleep screen images now load from /.sleep directory (preferred), falling back to /sleep for backwards compatibility. The dot-prefix keeps the directory hidden from the file browser. * Rewrote User Guide section 3.6 to document all six sleep screen modes, cover settings, and the updated custom image setup. ## Additional Context * The sleep directoy entry while browsing files was distracting. --- ### 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? _NO_ |
||
|
|
f0a549b680 |
refactor: rename MyLibrary to FileBrowser (#1260)
## Summary * Renames MyLibrary component to FileBrowser, as it better reflects what it is, in my opinion ## Additional Context * Frees the Library name for possible future library component that can cache metadata, provide other ways of browsing than filesystem structure, etc --- ### AI Usage Did you use AI tools to help write this code? _**< YES >**_ |
||
|
|
80d1856330 |
perf: Removed unused ConfirmationActivity member (#1234)
## Summary **What is the goal of this PR?** Small follow up to #909, removing an unused member variable and some temporary debug logging. --- ### 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? _**NO**_ |
||
|
|
76681201bf |
fix: Hide unusable button hints when viewing empty directory (#1253)
## Summary * **What is the goal of this PR?** Increase accuracy of button hints and text description in the file browser when viewing empty directory. * **What changes are included?** Adjusted button label hint rendering logic in file browser to hide the "Open", "Up", and "Down" hints when the they are not available due to an empty directory. I also changed the NO_BOOKS_FOUND string to NO_FILES_FOUND and updated translations. File browser shows more than just books so seeing "No Books Found" really doesn't make sense. ## Additional Context Very Simple change, here is what that looks like on my device. <img width="1318" height="879" alt="Untitled (7)" src="https://github.com/user-attachments/assets/6416c8c8-795d-41a5-9b9f-28d2c26666a0" /> --- ### 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? _**NO**_ |
||
|
|
04242fa221 |
refactor: Simplify new setting introduction (#1086)
## Summary
* **What is the goal of this PR?** Eliminate the 3-file / 4-location
overhead for adding a new setting. Previously, every new setting
required manually editing JsonSettingsIO.cpp in two places (save +
load), duplicating knowledge already present in SettingsList.h. After
this PR, JsonSettingsIO.cpp never needs to be touched again for standard
settings.
* **What changes are included?**
* `SettingInfo` (in `SettingsActivity.h`) gains one new field: `bool
obfuscated` (base64 save/load for passwords), with a fluent builder
method `.withObfuscated()`. The previously proposed
`defaultValue`/`withDefault()` approach was dropped in favour of reading
the struct field's own initializer value as the fallback (see below).
* `SettingsList.h` entries are annotated with `.withObfuscated()` on the
OPDS password entry. The list is now returned as a `static const`
singleton (`const std::vector<SettingInfo>&`), so it is constructed
exactly once. A missing `key`/`category` on the
`statusBarProgressBarThickness` entry was also fixed — it was previously
skipped by the generic save loop, so changes were silently lost on
restart.
* `JsonSettingsIO::saveSettings` and `loadSettings` replace their ~90
lines of manual per-field code with a single generic loop over
`getSettingsList()`. The loop uses `info.key`,
`info.valuePtr`/`info.stringOffset`+`info.stringMaxLen` (for char-array
string fields), `info.enumValues.size()` (for enum clamping), and
`info.obfuscated`.
* **Default values**: instead of a duplicated `defaultValue` field in
`SettingInfo`, `loadSettings` reads `s.*(info.valuePtr)` *before*
overwriting it. Because `CrossPointSettings` is default-constructed
before `loadSettings` is called, this captures each field's struct
initializer value as the JSON-absent fallback. The single source of
truth for defaults is `CrossPointSettings.h`.
* One post-loop special case remains explicitly: the four `frontButton*`
remap fields (managed by the RemapFrontButtons sub-activity, not in
SettingsList) and `validateFrontButtonMapping()`.
* One pre-loop migration guard handles legacy settings files that
predate the status bar refactor: if `statusBarChapterPageCount` is
absent from the JSON, `applyLegacyStatusBarSettings()` is called first
so the generic loop picks up the migrated values as defaults and applies
its normal clamping.
* OPDS password backward-compat migration (plain `opdsPassword` →
obfuscated `opdsPassword_obf`) is preserved inside the generic
obfuscated-string path.
## Additional Context
Say we want to add a new `bookmarkStyle` enum setting with options
`DOT`, `LINE`, `NONE` and a default of `DOT`:
1. `src/CrossPointSettings.h` — add enum and member:
```cpp
enum BOOKMARK_STYLE { BOOKMARK_DOT = 0, BOOKMARK_LINE = 1, BOOKMARK_NONE = 2 };
uint8_t bookmarkStyle = BOOKMARK_DOT;
```
2. `lib/I18n/translations/english.yaml` — add display strings:
```yaml
STR_BOOKMARK_STYLE: "Bookmark Style"
STR_BOOKMARK_DOT: "Dot"
STR_BOOKMARK_LINE: "Line"
```
(Other language files will fall back to English if not translated. Run
`gen_i18n.py` to regenerate `I18nKeys.h`.)
3. `src/SettingsList.h` — add one entry in the appropriate category:
```cpp
SettingInfo::Enum(StrId::STR_BOOKMARK_STYLE, &CrossPointSettings::bookmarkStyle,
{StrId::STR_BOOKMARK_DOT, StrId::STR_BOOKMARK_LINE, StrId::STR_NONE_OPT},
"bookmarkStyle", StrId::STR_CAT_READER),
```
That's it — no default annotation needed anywhere, because
`bookmarkStyle = BOOKMARK_DOT` in the struct already provides the
fallback. The setting will automatically persist to JSON on save, load
with clamping on boot, appear in the device settings UI under the Reader
category, and be exposed via the web API — all with no further changes.
---
### 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? _**< PARTIALLY>**_
|
||
|
|
a57c62f0b4 |
fix: properly implement requestUpdateAndWait() (#1218)
## Summary Properly implement `requestUpdateAndWait()` using freeRTOS direct task notification. FWIW, I think most of the current use cases of `requestUpdateAndWait()` are redundant, better to be replaced by `requestUpdate(true)`. But just keeping them in case we can find a proper use case for it in the future. --- ### 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**, it's trivial, so I asked an AI to write the code --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> |
||
|
|
5e95d9a36f |
feat: Long Click for File Deletion through File Browser (#909)
## Summary * **What is the goal of this PR?** (e.g., Implements the new feature for file uploading.) Allow users to better manage their epub library by offloading unwanted or finished books and other files. Resolves #893 * **What changes are included?** Added Delete Book shortcut in the fil browser. Delete function implements the new ConfirmationActivity to show file name and solicit user interaction before either returning to the file browser on a press of the back button, or proceeding to delete. Delete function then deletes the file and returns user to the file browser menu at the current directory. Video of it working on my machine attached here: https://github.com/user-attachments/assets/329b0198-9e97-45ad-82aa-c39894351667 ## Additional Context * Add any other information that might be helpful for the reviewer (e.g., performance implications, potential risks, specific areas to focus on). Certainly potential risks associated with file deletion. Please let me know if there are any concerns that need to be better addressed. I think this is a very good feature to have to go along with the new screenshots so you don't get stuck with a bunch of extra files on your device. Also I did add this to the user guide. --- ### 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**_ --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: Егор Мартынов <martynovegorOF@yandex.ru> Co-authored-by: Arthur Tazhitdinov <lisnake@gmail.com> Co-authored-by: Zach Nelson <zach@zdnelson.com> |
||
|
|
45a228a645 |
fix: use HTTPClient::writeToStream for downloading files from OPDS (#1207)
## Summary * Refactored `HttpDownloader::downloadToFile` to use `FileWriteStream` and `HTTPClient::writeToStream`, removing manual chunked downloading logic, which was error-prone. * Fixes https://github.com/crosspoint-reader/crosspoint-reader/issues/632 ## Additional Context * Tested downloading files from OPDS with a size up to 10 mb. --- ### 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 >**_ |
||
|
|
050a3bd1b6 |
fix: ActivityManager tweaks (#1220)
## Summary **What is the goal of this PR?** Small tweaks to #1016: - Only Activity and ActivityManager can access activityResultHandler and activityResult - `[[maybe_unused]]` in RenderLock constructor - Only ActivityManager and RenderLock can access renderingMutex - Missing renderUpdate after failed wifi selection - Standardize on activities calling finish instead of activityManager.popActivity - Hold RenderLock while mutating state in EpubReaderActivity result handlers --- ### 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? _**NO**_ |
||
|
|
3b4f2a1129 |
feat: Auto Page Turn for Epub Reader (#1219)
## Summary * **What is the goal of this PR?** (e.g., Implements the new feature for file uploading.) - Implements auto page turn feature for epub reader in the reader submenu * **What changes are included?** - added auto page turn feature in epub reader in the submenu - currently there are 5 settings, `OFF, 1, 3, 6, 12` pages per minute ## Additional Context * Add any other information that might be helpful for the reviewer (e.g., performance implications, potential risks, specific areas to focus on). - Replacement PR for #723 - when auto turn is enabled, space reserved for chapter title will be used to indicate auto page turn being active - Back and Confirm button is used to disable it --- ### 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? _**Partially (mainly code reviews)**_ |
||
|
|
09cef70709 |
fix: clarity issue with ambiguous string SET (#1169)
## Summary * **What is the goal of this PR?** (e.g., Implements the new feature for file uploading.) Fixes a clarity issue regarding the translation string `STR_SET`. The issue lies in the fact that the english word can have different meanings. The only time the string is used is in the language selectio screen, where it has the meaning of _selected_. (As in _The language has been **set** to French_). Another meaning can be _configured_. (As in _The KOReader username has been __set__). This is the meaning many of the translations have taken. The reason that the string is right above `STR_NOT_SET` (which is meant as _not configured_). With this PR I propose to explicitly use the term "_Selected_". There are two good reasons for this: + it removes the confusion and the misleading translations + it is consistent with the button label `Select`, communicating the link between the two (the row will be marked `Selected` if you press the buttpn `Select`. Much clearer than now) * **What changes are included?** Removed the unused strings and added translations for the new string `STR_SELECTED` for the languages I know. tagging the translators for feedback: fr: @Spigaw @CaptainFrito de: @DavidOrtmann cs: @brbla pt: @yagofarias it: @andreaturchet @fargolinux ru: @madebykir @mrtnvgr es: @yeyeto2788 @Skrzakk @pablohc sv: @dawiik ca: @angeldenom uj: @mirus-ua be: @dexif ## Additional Context * Add any other information that might be helpful for the reviewer (e.g., performance implications, potential risks, specific areas to focus on). the Issue was introduced in #1020. Previously, if a language was selected it was marked with `[ON]` (`STR_ON_MARKER`). I considered reverting it back to that, but the solution I described above seemed superior. --- ### 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? _**< NO >**_ --------- Co-authored-by: Егор Мартынов <martynovegorOF@yandex.ru> Co-authored-by: Mirus <mirusim@gmail.com> |
||
|
|
c4fc4effbd |
refactor: implement ActivityManager (#1016)
## Summary Ref comment: https://github.com/crosspoint-reader/crosspoint-reader/pull/1010#pullrequestreview-3828854640 This PR introduces `ActivityManager`, which mirrors the same concept of Activity in Android, where an activity represents a single screen of the UI. The manager is responsible for launching activities, and ensuring that only one activity is active at a time. Main differences from Android's ActivityManager: - No concept of Bundle or Intent extras - No onPause/onResume, since we don't have a concept of background activities - onActivityResult is implemented via a callback instead of a separate method, for simplicity ## Key changes - Single `renderTask` shared across all activities - No more sub-activity, we manage them using a stack; Results can be passed via `startActivityForResult` and `setResult` - Activity can call `finish()` to destroy themself, but the actual deletion will be handled by `ActivityManager` to avoid `delete this` pattern As a bonus: the manager will automatically call `requestUpdate()` when returning from another activity ## Example usage **BEFORE**: ```cpp // caller enterNewActivity(new WifiSelectionActivity(renderer, mappedInput, [this](const bool connected) { onWifiSelectionComplete(connected); })); // subactivity onComplete(true); // will eventually call exitActivity(), which deletes the caller instance (dangerous behavior) ``` **AFTER**: (mirrors the `startActivityForResult` and `setResult` from android) ```cpp // caller startActivityForResult(new NetworkModeSelectionActivity(renderer, mappedInput), [this](const ActivityResult& result) { onNetworkModeSelected(result.selectedNetworkMode); }); // subactivity ActivityResult result; result.isCancelled = false; result.selectedNetworkMode = mode; setResult(result); finish(); // signals to ActivityManager to go back to last activity AFTER this function returns ``` TODO: - [x] Reconsider if the `Intent` is really necessary or it should be removed (note: it's inspired by [Intent](https://developer.android.com/guide/components/intents-common) from Android API) ==> I decided to keep this pattern fr clarity - [x] Verify if behavior is still correct (i.e. back from sub-activity) - [x] Refactor the `ActivityWithSubactivity` to just simple `Activity` --> We are using a stack for keeping track of sub-activity now - [x] Use single task for rendering --> avoid allocating 8KB stack per activity - [x] Implement the idea of [Activity result](https://developer.android.com/training/basics/intents/result) --> Allow sub-activity like Wifi to report back the status (connected, failed, etc) --- ### 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? **PARTIALLY**, some repetitive migrations are done by Claude, but I'm the one how ultimately approve it --------- Co-authored-by: Zach Nelson <zach@zdnelson.com> |
||
|
|
5b11e45a36 |
fix: prevent infinite render loop in Calibre Wireless after file transfer (#1070)
## Summary * **What is the goal of this PR?** Fix an infinite render loop bug in CalibreConnectActivity that caused the e-ink display to refresh continuously every ~421ms after a file transfer completed. * **What changes are included?** - Added lastProcessedCompleteAt member variable to track which server completion timestamp has already been processed - Modified the completion status update logic to only accept new values from the server, preventing re-processing of old timestamps - Added clarifying comments explaining the fix ## Problem Description After receiving a file via Calibre Wireless, the activity displays "Received: [filename]" for 6 seconds, then clears the message. However, the web server's wsLastCompleteAt timestamp persists indefinitely and is never cleared. This created a race condition: After 6 seconds, lastCompleteAt is set to 0 (timeout) In the next loop iteration, status.lastCompleteAt (still has the old timestamp) ≠ lastCompleteAt (0) The code restores lastCompleteAt from the server value Immediately, the 6-second timeout condition is met again This creates an infinite cycle causing unnecessary e-ink refreshes ## Solution The fix introduces lastProcessedCompleteAt to track which server timestamp value has already been processed: Only accept a new status.lastCompleteAt if it differs from lastProcessedCompleteAt Update lastProcessedCompleteAt when processing a new value Do NOT reset lastProcessedCompleteAt when the 6-second timeout clears lastCompleteAt This prevents re-processing the same old server value after the timeout. ## Testing Tested on device with multiple file transfer scenarios: ✅ File received message appears correctly after transfer ✅ Message clears after 6 seconds as expected ✅ No infinite render loop after timeout ✅ Multiple consecutive transfers work correctly ✅ Exiting and re-entering Calibre Wireless works as expected ## Performance Impact Before: Infinite refreshes every ~421ms after timeout (high battery drain, display wear) After: 2-3 refreshes after timeout, then stops (normal behavior) ## Additional Context This is a targeted fix that only affects the Calibre Wireless file transfer screen. The root cause is the architectural difference between the persistent web server state (wsLastCompleteAt) and the per-activity display state (lastCompleteAt). An alternative fix would be to clear wsLastCompleteAt in the web server after some timeout, but that would affect all consumers of the web server status. The chosen solution keeps the fix localized to CalibreConnectActivity. --- ### AI Usage Did you use AI tools to help write this code? _**< YES >**_ |
||
|
|
30d8a8d011 |
feat: slim footnotes support (#1031)
## Summary **What is the goal of this PR?** Implement support for footnotes in epub files. It is based on #553, but simplified — removed the parts which complicated the code and burden the CPU/RAM. This version supports basic footnotes and lets the user jump from location to location inside the epub. **What changes are included?** - `FootnoteEntry` struct — A small POD struct (number[24], href[64]) shared between parser, page storage, and UI. - Parser: `<a href>` detection (`ChapterHtmlSlimParser`) — During a single parsing pass, internal epub links are detected and collected as footnotes. The link text is underlined to hint navigability. Bracket/whitespace normalization is applied to the display label (e.g. [1] → 1). - Footnote-to-page assignment (`ChapterHtmlSlimParser`, `Page`) — Footnotes are attached to the exact page where their anchor word appears, tracked via a cumulative word counter during layout, surviving paragraph splits and the 750-word mid-paragraph safety flush. - Page serialization (`Page`, `Section`) — Footnotes are serialized/deserialized per page (max 16 per page). Section cache version bumped to 14 to force a clean rebuild. - Href → spine resolution (`Epub`) — `resolveHrefToSpineIndex()` maps an href (e.g. `chapter2.xhtml#note1`) to its spine index by filename matching. - Footnotes menu + activity (`EpubReaderMenuActivity`, `EpubReaderFootnotesActivity`) — A new "Footnotes" entry in the reader menu lists all footnote links found on the current page. The user scrolls and selects to navigate. - Navigate & restore (`EpubReaderActivity`) — `navigateToHref()` saves the current spine index and page number, then jumps to the target. The Back button restores the saved position when the user is done reading the footnote. **Additional Context** **What was removed vs #553:** virtual spine items (`addVirtualSpineItem`, `isVirtualSpineItem`), two-pass parsing, `<aside>` content extraction to temp HTML files, `<p class="note">` paragraph note extraction, `replaceHtmlEntities` (master already has `lookupHtmlEntity`), `footnotePages` / `buildFilteredChapterList`, `noterefCallback` / `Noteref` struct, and the stack size increase from 8 KB to 24 KB (not needed without two-pass parsing and virtual file I/O on the render task). **Performance:** Single-pass parsing. No new heap allocations in the hot path — footnote text is collected into fixed stack buffers (char[24], char[64]). Active runtime memory is ~2.8 KB worst-case (one page × 16 footnotes × 88 bytes, mirrored in `currentPageFootnotes`). Flash usage is unchanged at 97.4%; RAM stays at 31%. **Known limitations:** When clicking a footnote, it jumps to the start of the HTML file instead of the specific anchor. This could be problematic for books that don't have separate files for each footnote. (no element-id-to-page mapping yet - will be another PR soon). --- ### AI Usage Did you use AI tools to help write this code? _**< PARTIALLY>**_ Claude Opus 4.6 was used to do most of the migration, I checked manually its work, and fixed some stuff, but I haven't review all the changes yet, so feedback is welcomed. --------- Co-authored-by: Arthur Tazhitdinov <lisnake@gmail.com> |
||
|
|
451774ddf8 |
fix: broken translations in status bar settings (#1188)
## Summary * **What is the goal of this PR?** (e.g., Implements the new feature for file uploading.) The strings for `Show` and `Hide` were always showing in English, regardless of which language was selected. * **What changes are included?** Replace the variables in the lambda by direct calls to the `tr` macro ## Additional Context * 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? _**<NO >**_ |
||
|
|
7e214ea760 |
feat: sort languages in selection menu (#1071)
## Summary * **What is the goal of this PR?** (e.g., Implements the new feature for file uploading.) Currently we are displaying the languages in the order they were added (as in the `Language` enum). However, as new languages are coming in, this will quickly be confusing to the users. But we can't just change the ordering of the enum if we want to respect bakwards compatibility. So my proposal is to add a mapping of the alphabetical order of the languages. I've made it so that it's generated by the `gen_i18n.py` script, which will be used when a new language is added. * **What changes are included?** Added the array from the python script and changed `LanguageSelectActivity` to use the indices from there. Also commited the generated `I18nKeys.h` ## Additional Context * Add any other information that might be helpful for the reviewer (e.g., performance implications, potential risks, specific areas to focus on). I was wondering if there is a better way to sort it. Currently, it's by unicode value and Czech and Russian are last, which I don't know it it's the most intuitive. The current order is: `Català, Deutsch, English, Español, Français, Português (Brasil), Română, Svenska, Čeština, Русский` --- ### 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? _**< PARTIALLY >**_ |
||
|
|
b695a48af6 |
fix: WiFi lifecycle and hyphenation heap defragmentation for KOReader sync (#1151)
## Summary * **What is the goal of this PR?** KOReader sync on a German-language book would fail with an out-of-memory error when trying to open the destination chapter after applying remote progress. The root cause was a chain of two independent bugs that combined to exhaust the contiguous heap needed by the EPUB inflate pipeline. * **What changes are included?** ## Fix 1 — Hyphenation heap defragmentation (LiangHyphenation.cpp) ### What was happening AugmentedWord, the internal struct used during Liang pattern matching, held three std::vector<> members (bytes, charByteOffsets, byteToCharIndex) plus a separate scores vector — a total of 4 heap allocations per word during page layout. For a German-language section with hundreds of words, thousands of small malloc/free cycles fragmented the heap. Total free memory was adequate (~108 KB) but the largest contiguous block shrank well below the 32 KB needed for the INFLATE ring buffer used during EPUB decompression. The failure was invisible with hyphenation disabled, where MaxAlloc stayed at ~77 KB; enabling German hyphenation silently destroyed the contiguity the allocator needed. ### What changed The three std::vector<> members of AugmentedWord and the scores vector are replaced with fixed-size C arrays on the render-task stack: ``` uint8_t bytes[160] // was std::vector<uint8_t> size_t charByteOffsets[70] // was std::vector<size_t> int32_t byteToCharIndex[160] // was std::vector<int32_t> uint8_t scores[70] // was std::vector<uint8_t> (local in liangBreakIndexes) ``` Sizing is based on the longest known German word (~63 codepoints × 2 UTF-8 bytes + 2 sentinel dots = 128 bytes); MAX_WORD_BYTES=160 and MAX_WORD_CHARS=70 give comfortable headroom. The same analysis holds for all seven supported languages (en, fr, de, es, it, ru, uk) — every accepted letter encodes to at most 2 UTF-8 bytes after case-folding. Words exceeding the limits are silently skipped (no hyphenation applied), which is correct behaviour. The struct lives on the 8 KB render-task stack so no permanent DRAM is consumed. Verification: after the fix, MaxAlloc reads 77,812 bytes with German hyphenation enabled — identical to the figure previously achievable only with hyphenation off. ## Fix 2 — WiFi lifecycle in KOReaderSyncActivity (KOReaderSyncActivity.cpp) ### What was happening onEnter() called WiFi.mode(WIFI_STA) unconditionally before delegating to WifiSelectionActivity. WifiSelectionActivity manages WiFi mode internally (it calls WiFi.mode(WIFI_STA) again at scan start and at connection attempt). The pre-emptive call from KOReaderSyncActivity interfered with the sub-activity's own state machine, causing intermittent connection failures that were difficult to reproduce. Additionally, WiFi was only shut down in onExit(). If the user chose "Apply remote progress" the activity exited without turning WiFi off first, leaving the radio on and its memory allocated while the EPUB was being decompressed — unnecessarily consuming the contiguous heap headroom that inflate needed. ### What changed * WiFi.mode(WIFI_STA) removed from onEnter(). WifiSelectionActivity owns WiFi mode; KOReaderSyncActivity should not touch it before the sub-activity runs. * A wifiOff() helper (SNTP stop + disconnect + WIFI_OFF with settling delays) is extracted into the anonymous namespace and called at every web-session exit point: - "Apply remote" path in loop() — before onSyncComplete() - performUpload() success path - performUpload() failure path - onExit() (safety net for all other exit paths) ## Additional Context * 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**_ and two days of blood, sweat and heavy swearing... |
||
|
|
2d49c7b7b4 |
feat: split status bar setting (#733)
## Summary This PR aims to reduce the complexity of the status bar by splitting the setting into 5: - Chapter Page Count - Book Progress % - Progress Bar - Chapter Title - Battery Indicator These are located within the new StausBarSettings activity, which also shows a preview of the bar the user has created <img width="513" height="806" alt="image" src="https://github.com/user-attachments/assets/cdf852fb-15d8-4da2-a74f-fd69294d7b05" /> <img width="483" height="797" alt="image" src="https://github.com/user-attachments/assets/66fc0c0d-ee51-4d31-b70d-e2bc043205d1" /> When updating from a previous version, the user's past settings are honoured. ## Additional Context The PR aims to remove any duplication of status bar code where possible, and extracts the status bar rendering into a new component - StatusBar It also adds a new (optional) padding option to the progress bar to allow the status bar to be shifted upwards - this is only intended for use in the settings. --- ### 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? No - although did help to decode some C++ errors --------- Co-authored-by: Arthur Tazhitdinov <lisnake@gmail.com> |
||
|
|
128eb614a6 |
feat: Current page as QR (#1099)
## Summary * **What is the goal of this PR?** Implements QR text of the current page * **What changes are included?** ## Additional Context I saw this feature request at #982 It made sense to me so I implemented. But if the team thinks it is not necessary please let me know and we can close the PR. | Page | Menu | QR | |------|-------|----| |  |  |  | --- ### AI Usage Did you use AI tools to help write this code? _** YES --------- Co-authored-by: Eliz Kilic <elizk@google.com> |
||
|
|
31396da064 |
fix: Update activity was missing "Back" button label (#1128)
## Summary **What is the goal of this PR?** When update activity finds no update or fails, the "Back" button label was missing. Fixes #1089. --- ### 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? _**NO**_ |
||
|
|
bfdf0a4f78 |
feat: set WiFi hostname to CrossPoint-Reader-XXXXXXXXXXXX (#1107)
## Summary Replace the default esp32-XXXXXXXXXXXX hostname with CrossPoint-Reader-AABBCCDDEEFF (full MAC address) so the device is easily identifiable on the router's client list. |
||
|
|
410c70ab89 |
perf: UITheme::getMetrics const and const-ref usage (#1094)
## Summary **What is the goal of this PR?** Small cleanup to make getTheme and getMetrics methods on UITheme const. They return const refs, so updated call sites to use `const auto&`. Realistically this won't make much performance difference, but it better conveys the nature of theme metrics being shared const state. --- ### 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? _**NO**_ |
||
|
|
a6aead660a |
feat: Themed language screen (#1020)
## Summary Added theme support to the language screen  ## Additional Context --- ### AI Usage Did you use AI tools to help write this code? _**NO**_ |
||
|
|
3cb60aa231 |
fix: Close leaked file descriptors in SleepActivity and web server (#869)
## Summary - **SleepActivity.cpp**: Add missing `file.close()` calls in 3 code paths that open BMP files for sleep screen rendering but never close them before returning. Affects random custom sleep images, the `/sleep.bmp` fallback, and book cover sleep screens. - **CrossPointWebServer.cpp**: Add missing `dir.close()` in the delete handler when `Storage.open()` returns a valid `FsFile` that is not a directory. ## Context SdFat is configured with `DESTRUCTOR_CLOSES_FILE=0`, which means `FsFile` objects are **not** automatically closed when they go out of scope. Every opened file must be explicitly closed. The SleepActivity leaks are particularly impactful because they occur on every sleep cycle. While ESP32 deep sleep clears RAM on wake, these leaks can still affect the current session if sleep screen rendering is triggered multiple times (e.g., cover preview, or if deep sleep fails to engage). The web server leak in `handleDelete()` is a minor edge case (directory path that opens successfully but `isDirectory()` returns false), but it's still worth fixing for correctness. ## Test plan - [x] Verify sleep screen still renders correctly (custom BMP, fallback, cover modes) - [x] Verify folder deletion still works via the web UI - [ ] Monitor free heap before/after sleep screen rendering to confirm no leak 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Jan Bažant <janbazant@Jan--Mac-mini.local> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Dave Allie <dave@daveallie.com> |
||
|
|
c1fad16e10 |
feat: Take screenshots (#759)
## Summary * **What is the goal of this PR?** Implements a take-screenshot feature * **What changes are included?** - Quick press Power button and Down button at the same time to take a screenshot - Screenshots are saved in `screenshots` folder ## Additional Context - Currently it does not use the device orientation. --- Example screenshots:  [screenshot-6771.bmp](https://github.com/user-attachments/files/25157071/screenshot-6771.bmp)  [screenshot-14158.bmp](https://github.com/user-attachments/files/25157073/screenshot-14158.bmp) ### AI Usage Did you use AI tools to help write this code? _** YES --------- Co-authored-by: Eliz Kilic <elizk@google.com> Co-authored-by: Xuan Son Nguyen <son@huggingface.co> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Arthur Tazhitdinov <lisnake@gmail.com> |
||
|
|
7717ae2683 |
feat: Added BmpViewer activity for viewing .bmp images in file browser (#887)
## Summary * **What is the goal of this PR?** (e.g., Implements the new feature for file uploading.) Implements new feature for viewing .bmp files directly from the "Browse Files" menu. * **What changes are included?** You can now view .bmp files when browsing. You can click the select button to open the file, and then click back to close it and continue browsing in the same location. Once open a file will display on the screen with no additional options to interact outside of exiting with the back button. The attached video shows this feature in action: https://github.com/user-attachments/assets/9659b6da-abf7-4458-b158-e11c248c8bef ## Additional Context * Add any other information that might be helpful for the reviewer (e.g., performance implications, potential risks, specific areas to focus on). The changes implemented in #884 are also present here as this feature is actually what led to me noticing this issue. I figured I would add that PR as a separate request in case that one could be more easily merged given this feature is significantly more complicated and will likely be subject to more intense review. --- ### 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** --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> |
||
|
|
2a38bfd8af |
fix: use double FAST_REFRESH to prevent washout on large grey images (#957)
## Summary Fixes https://github.com/crosspoint-reader/crosspoint-reader/issues/1011 Use double FAST_REFRESH for image pages to prevent grayscale washout, HALF_REFRESH sets e-ink particles too firmly for the grayscale LUT to adjust, causing washed-out images (especially large, light-gray ones). Replace HALF_REFRESH with @pablohc's double FAST_REFRESH technique: blank only the image bounding box area, then re-render with images. This clears ghosting while keeping particles loosely set for grayscale. ## Additional Context --- ### 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? _**< PARTIALLY >**_ |
||
|
|
cabbfcfd7e |
fix: Use HalPowerManager for battery percentage (#1005)
## Summary The introduction of `HalGPIO` moved the `BatteryMonitor battery` object into the member function `HalGPIO::getBatteryPercentage()`. Then, with the introduction of `HalPowerManager`, this function was moved to `HalPowerManager::getBatteryPercentage()`. However, the original `BatteryMonitor battery` object is still utilized by themes for displaying the battery percentage. This PR replaces these deprecated uses of `BatteryMonitor battery` with the new `HalPowerManager::getBatteryPercentage()` function. --- ### 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? _**NO **_ |
||
|
|
10b7769865 |
fix: go to prev page on the first one, get teleported to the end of book (#970)
## Summary 1. Go to the first page in a .epub file. 2. Hit `Up` button 3. Get teleported to the last page :) `TxtRenderActivity` seems to have this if check, but EPUB one does not. --- ### 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? _**NO**_ |
||
|
|
00e25b1a90 |
fix: Fix kosync repositioning issue (#783)
## Summary * Original implementation had inconsistent positioning logic: - When XPath parsing succeeded: incorrectly set pageNumber = 0 (always beginning of chapter) - When XPath parsing failed: used percentage for positioning (worked correctly) - Result: Positions restored to wrong locations depending on XPath parsing success - Mentioned in Issue #581 * Solution - Unified ProgressMapper::toCrossPoint() to use percentage-based positioning exclusively for both spine identification and intra-chapter page calculation, eliminating unreliable XPath parsing entirely. ## Additional Context * ProgressMapper.cpp: Simplified toCrossPoint() to always use percentage for positioning, removed parseDocFragmentIndex() function * ProgressMapper.h: Updated comments and removed unused function declaration * Tests confirmed appropriate positioning * __Notabene: the syncing to another device will (most probably) end up at the current chapter of crosspoints reading position. There is not much we can do about it, as KOReader needs to have the correct XPath information - we can only provide an apporximate position (plus percentage) - the percentage information is not used in KOReaders current implementation__ --- ### 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 |
||
|
|
fdcd71e94d |
feat: Lyra Icons (#725)
/!\ This PR depends on https://github.com/crosspoint-reader/crosspoint-reader/pull/732 being merged first Also requires the https://github.com/open-x4-epaper/community-sdk/pull/18 PR ## Summary Lyra theme icons on the home menu, in the file browser and on empty book covers     ## Additional Context - Added a function to the open-x4-sdk renderer to draw transparent images - Added a scripts/convert_icon.py script to convert svg/png icons into a C array that can be directly imported into the project. Usage: ```bash python ./scripts/convert_icon.py 'path/to/icon.png' cover 32 32 ``` This will create a components/icons/cover.h file with a C array called CoverIcon, of size 32x32px. Lyra uses icons from https://lucide.dev/icons with a stroke width of 2px, that can be downloaded with any desired size on the site. > The file browser is noticeably slower with the addition of icons, and using an image buffer like on the home page doesn't help very much. Any suggestions to optimize this are welcome. --- ### 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? _**PARTIALLY**_ The icon conversion python script was generated by Copilot as I am not a python dev. --------- Co-authored-by: Dave Allie <dave@daveallie.com> |
||
|
|
e7ee6ff05e |
feat: Lyra screens (#732)
## Summary Implements Lyra theme for some more Crosspoint screens:       ## Additional Context - A bit of refactoring for list scrolling logic --- ### 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? _**NO**_ --------- Co-authored-by: Dave Allie <dave@daveallie.com> |
||
|
|
47aa0dda76 |
perf: Reduce overall flash usage by 30.7% by compressing built-in fonts (#831)
## Summary **What is the goal of this PR?** Compress reader font bitmaps to reduce flash usage by 30.7%. **What changes are included?** - New `EpdFontGroup` struct and extended `EpdFontData` with `groups`/`groupCount` fields - `--compress` flag in `fontconvert.py`: groups glyphs (ASCII base group + groups of 8) and compresses each with raw DEFLATE - `FontDecompressor` class with 4-slot LRU cache for on-demand decompression during rendering - `GfxRenderer` transparently routes bitmap access through `getGlyphBitmap()` (compressed or direct flash) - Uses `uzlib` for decompression with minimal heap overhead. - 48 reader fonts (Bookerly, NotoSans 12-18pt, OpenDyslexic) regenerated with compression; 5 UI fonts unchanged - Round-trip verification script (`verify_compression.py`) runs as part of font generation ## Additional Context ## Flash & RAM | | baseline | font-compression | Difference | |--|--------|-----------------|------------| | Flash (ELF) | 6,302,476 B (96.2%) | 4,365,022 B (66.6%) | -1,937,454 B (-30.7%) | | firmware.bin | 6,468,192 B | 4,531,008 B | -1,937,184 B (-29.9%) | | RAM | 101,700 B (31.0%) | 103,076 B (31.5%) | +1,376 B (+0.5%) | ## Script-Based Grouping (Cold Cache) Comparison of uncompressed baseline vs script-based group compression (4-slot LRU cache, cleared each page). Glyphs are grouped by Unicode block (ASCII, Latin-1, Latin Extended-A, Combining Marks, Cyrillic, General Punctuation, etc.) instead of sequential groups of 8. ### Render Time | | Baseline | Compressed (cold cache) | Difference | |---|---|---|---| | **Median** | 414.9 ms | 431.6 ms | +16.7 ms (+4.0%) | | **Pages** | 37 | 37 | | ### Memory Usage | | Baseline | Compressed (cold cache) | Difference | |---|---|---|---| | **Heap free (median)** | 187.0 KB | 176.3 KB | -10.7 KB | | **Heap free (min)** | 186.0 KB | 166.5 KB | -19.5 KB | | **Largest block (median)** | 148.0 KB | 128.0 KB | -20.0 KB | | **Largest block (min)** | 148.0 KB | 120.0 KB | -28.0 KB | ### Cache Effectiveness | | Misses/page | Hit rate | |---|---|---| | **Compressed (cold cache)** | 2.1 | 99.85% | ------ ### 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**_ Implementation was done by Claude Code (Opus 4.6) based on a plan developed collaboratively. All generated font headers were verified with an automated round-trip decompression test. The firmware was compiled successfully but has not yet been tested on-device. --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
f16c0e52fd |
feat: Tweak Lyra popup UI (#768)
## Summary I want to preface this PR by stating that the proposed changes are subjective to people's opinions. The following is just my suggestion, but I'm of course open to changes. The popups in the currently implemented version of the Lyra theme feel a bit out of place. This PR suggests an updated version which looks a bit more polished and in line with the rest of the theme. I've also taken the liberty to remove the ellipsis behind the text of the popups, as they made the popup feel a bit off balance (example below). With the applied changes, popups will look like this.  The vertical position is (more or less) aligned to be in line with the sleep button. I'm aware the popup is used for other purposes aside from the sleep message, but this still felt like a good place. It's also a place where your eyes naturally 'rest'. The popup has a small 2px white outline, neatly separating it from whatever is behind it. ### Alternatives considered and rationale behind proposal Initially I started out worked off the Figma design for the Lyra theme, which [moves the popups](https://www.figma.com/design/UhxoV4DgUnfrDQgMPPTXog/Lyra-Theme?node-id=2011-19296&t=Ppj6B2MrFRfUo9YX-1) to the bottom of the screen. To me, this results in popups that are much too easy to miss:  After this, I tried moving the popup back up (to the position of the sleep button), but to me it still kinda disappeared into the text of the book:  Inverting the colors of the popup made things stand out the perfect amount in my opinion. The white outline separates the popup from what is behind it.  This looked much better to me. The only thing that felt a bit off to me, was the balance due to the ellipsis at the end of the popup text. Also, "Entering Sleep..." felt a bit.. engineer-y. I felt something a bit more 'conversational' makes at all feel a bit more human-centric. But I'm no copywriter, and English is not even my native language. So feel free to chip in! After tweaking that, I ended up with the final result: _(Same picture as the first one shown in this PR)_  ## Additional Context * Figma design: https://www.figma.com/design/UhxoV4DgUnfrDQgMPPTXog/Lyra-Theme?node-id=2011-19296&t=Ppj6B2MrFRfUo9YX-1 --- ### 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? _**NO**_ |
||
|
|
d02e21a48f |
fix: Added missing up/down button labels (#935)
## Summary **What is the goal of this PR?** In some places, button labels are omitted intentionally because the button has no purpose in the activity. I noticed a few obvious cases, like Home > File Transfer and Settings > System > Language, where the up and down button labels were missing. This change fixes those and all similar instances I could find. --- ### 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? _**NO**_ |
||
|
|
6ec5fc5603 |
feat: lower CPU freq on idle, add HalPowerManager (#852)
## Summary Continue my experiment from https://github.com/crosspoint-reader/crosspoint-reader/pull/801 This PR add the ability to lower the CPU frequency on extended idle period (currently set to 3 seconds). By default, the esp32c3 CPU is set to 160MHz, and now on idle, we can reduce it to just 10MHz. Note that while this functionality is already provided by [esp power management](https://docs.espressif.com/projects/esp-idf/en/v4.3/esp32c3/api-reference/system/power_management.html), the current Arduino build lacks of this, and enabling it is just too complicated (not worth the effort compared to this PR) Update: more info in https://github.com/crosspoint-reader/crosspoint-reader/pull/852#issuecomment-3904562827 ## Testing Pre-condition for each test case: the battery is charged to 100%, and is left plugged in after fully charged for an extra 1 hour. The table below shows how much battery is **used** for a given duration: | case / duration | 6 hrs | 12 hrs | | --- | --- | --- | | `delay(10)` | 26% | 48% | | `delay(50)`, PR https://github.com/crosspoint-reader/crosspoint-reader/pull/801 | 20% | Not tested | | `delay(50)` + low CPU freq (This PR) | Not tested | 25% | | `delay(10)` + low CPU freq (1) | Not tested | Not tested | (1) I decided not to test this case because it may not make sense. The problem is that CPU frequency vs power consumption do not follow a linear relationship, see [this](https://www.arrow.com/en/research-and-events/articles/esp32-power-consumption-can-be-reduced-with-sleep-modes) as an example. So, tight loop (10ms) + lower CPU freq significantly impact battery life, because the active CPU time is now much higher compared to the wall time. **So in conclusion, this PR improves ~150% to ~200% battery use time per charge.** The projected battery life is now: ~36-48 hrs of reading time (normal reading, no wifi) --- ### 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? **NO** |
||
|
|
dc6562a51c |
fix: Fix a dangling pointer (#939)
## Summary * **What is the goal of this PR?** Fix a dangling pointer issue caused by using `.c_str()` on a temporary `std::string`. `basepath.substr()` creates a temporary `std::string`, and calling `.c_str()` on it returns a pointer to its internal buffer (not a copy). Since the temporary string is destroyed at the end of the full expression, `folderName` ends up holding a dangling pointer, leading to undefined behavior. To solve this, we stores the result in a persistent `std::string` object, ensuring the underlying buffer remains valid for the duration of its use. A similar pattern caused the behavior reported in https://github.com/crosspoint-reader/crosspoint-reader/pull/728#issuecomment-3902529697 --- ### 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? _**< NO >**_ |
||
|
|
d6f38d4441 |
fix: align battery icon based on context (UI / Reader) (#796)
Issues solved: #729 and #739 ## Summary * **What is the goal of this PR?** Currently, the battery icon and charge percentage were aligned to the left even for the UI, where they were positioned on the right side of the screen. This meant that when changing values of different numbers of digits, the battery would shift, creating a block of icons and text that was illegible. * **What changes are included?** - Add drawBatteryUi() method for right-aligned battery display in UI headers - Keep drawBattery() for left-aligned display in reader mode - Extract drawBatteryIcon() helper to reduce code duplication - Battery icon now stays fixed at right edge regardless of percentage digits - Text adjusts to left of icon in UI mode, to right of icon in reader mode ## Additional Context * Add any other information that might be helpful for the reviewer * This fix applies to both themes (Base and Lyra). --- ### 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 >**_ |
||
|
|
7ba5978848 |
feat: User-Interface I18n System (#728)
## Summary **What is the goal of this PR?** This PR introduces Internationalization (i18n) support, enabling users to switch the UI language dynamically. **What changes are included?** - Core Logic: Added I18n class (`lib/I18n/I18n.h/cpp`) to manage language state and string retrieval. - Data Structures: - `lib/I18n/I18nStrings.h/cpp`: Static string arrays for each supported language. - `lib/I18n/I18nKeys.h`: Enum definitions for type-safe string access. - `lib/I18n/translations.csv`: single source of truth. - Documentation: Added `docs/i18n.md` detailing the workflow for developers and translators. - New Settings activity: `src/activities/settings/LanguageSelectActivity.h/cpp` ## Additional Context This implementation (building on concepts from #505) prioritizes performance and memory efficiency. The core approach is to store all localized strings for each language in dedicated arrays and access them via enums. This provides O(1) access with zero runtime overhead, and avoids the heap allocations, hashing, and collision handling required by `std::map` or `std::unordered_map`. The main trade-off is that enums and string arrays must remain perfectly synchronized—any mismatch would result in incorrect strings being displayed in the UI. To eliminate this risk, I added a Python script that automatically generates `I18nStrings.h/.cpp` and `I18nKeys.h` from a CSV file, which will serve as the single source of truth for all translations. The full design and workflow are documented in `docs/i18n.md`. ### Next Steps - [x] Python script `generate_i18n.py` to auto-generate C++ files from CSV - [x] Populate translations.csv with initial translations. Currently available translations: English, Español, Français, Deutsch, Čeština, Português (Brasil), Русский, Svenska. Thanks, community! **Status:** EDIT: ready to be merged. As a proof of concept, the SPANISH strings currently mirror the English ones, but are fully uppercased. --- ### AI Usage Did you use AI tools to help write this code? _**< PARTIALLY >**_ I used AI for the black work of replacing strings with I18n references across the project, and for generating the documentation. EDIT: also some help with merging changes from master. --------- Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> Co-authored-by: yeyeto2788 <juanernestobiondi@gmail.com> |
||
|
|
3d47c081f2 |
fix: use RAII render lock everywhere (#916)
## Summary Follow-up to https://github.com/crosspoint-reader/crosspoint-reader/pull/774 --- ### 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? **NO** <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Release Notes * **Refactor** * Modernized internal synchronization mechanisms across multiple components to improve code reliability and maintainability. All functionality remains unchanged. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
a616f42cb4 |
refactor: move render() to Activity super class, use freeRTOS notification (#774)
## Summary Currently, each activity has to manage their own `displayTaskLoop` which adds redundant boilerplate code. The loop is a wait loop which is also not the best practice, as the `updateRequested` boolean is not protected by a mutex. In this PR: - Move `displayTaskLoop` to the super `Activity` class - Replace `updateRequested` with freeRTOS's [direct to task notification](https://www.freertos.org/Documentation/02-Kernel/02-Kernel-features/03-Direct-to-task-notifications/01-Task-notifications) - For `ActivityWithSubactivity`, whenever a sub-activity is present, the parent's `render()` automatically goes inactive With this change, activities now only need to expose `render()` function, and anywhere in the code base can call `requestUpdate()` to request a new rendering pass. ## Additional Context In theory, this change may also make the battery life a bit better, since one wait loop is removed. Although the equipment in my home lab wasn't been able to verify it (the electric current is too noisy and small). Would appreciate if anyone has any insights on this subject. Update: I managed to hack [a small piece of code](https://github.com/ngxson/crosspoint-reader/tree/xsn/measure_cpu_usage) that allow tracking CPU idle time. The CPU load does decrease a bit (1.47% down to 1.39%), which make sense, because the display task is now sleeping most of the time unless notified. This should translate to a slightly increase in battery life in the long run. ``` PR: [40012] [MEM] Free: 185856 bytes, Total: 231004 bytes, Min Free: 123316 bytes [40012] [IDLE] Idle time: 98.61% (CPU load: 1.39%) [50017] [MEM] Free: 185856 bytes, Total: 231004 bytes, Min Free: 123316 bytes [50017] [IDLE] Idle time: 98.61% (CPU load: 1.39%) [60022] [MEM] Free: 185856 bytes, Total: 231004 bytes, Min Free: 123316 bytes [60022] [IDLE] Idle time: 98.61% (CPU load: 1.39%) master: [20012] [MEM] Free: 195016 bytes, Total: 231532 bytes, Min Free: 132460 bytes [20012] [IDLE] Idle time: 98.53% (CPU load: 1.47%) [30017] [MEM] Free: 195016 bytes, Total: 231532 bytes, Min Free: 132460 bytes [30017] [IDLE] Idle time: 98.53% (CPU load: 1.47%) [40022] [MEM] Free: 195016 bytes, Total: 231532 bytes, Min Free: 132460 bytes [40022] [IDLE] Idle time: 98.53% (CPU load: 1.47%) ``` --- ### 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? **NO** <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Refactor** * Streamlined rendering architecture by consolidating update mechanisms across all activities, improving efficiency and consistency. * Modernized synchronization patterns for display updates to ensure reliable, conflict-free rendering. * **Bug Fixes** * Enhanced rendering stability through improved locking mechanisms and explicit update requests. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: znelson <znelson@users.noreply.github.com> |
||
|
|
6c3a615fac |
feat: add png jpeg support (#556)
## Summary
- Add embedded image support to EPUB rendering with JPEG and PNG
decoders
- Implement pixel caching system to cache decoded/dithered images to SD
card for faster re-rendering
- Add 4-level grayscale support for display
## Changes
### New Image Rendering System
- Add `ImageBlock` class to represent an image with its cached path and
display dimensions
- Add `PageImage` class as a new `PageElement` type for images on pages
- Add `ImageToFramebufferDecoder` interface for format-specific image
decoders
- Add `JpegToFramebufferConverter` - JPEG decoder with Bayer dithering
and scaling
- Add `PngToFramebufferConverter` - PNG decoder with Bayer dithering and
scaling
- Add `ImageDecoderFactory` to select appropriate decoder based on file
extension
- Add `getRenderMode()` to GfxRenderer for grayscale render mode queries
### Dithering and Grayscale
- Implement 4x4 Bayer ordered dithering for 4-level grayscale output
- Stateless algorithm works correctly with MCU block decoding
- Handles scaling without artifacts
- Add grayscale render mode support (BW, GRAYSCALE_LSB, GRAYSCALE_MSB)
- Image decoders and cache renderer respect current render mode
- Enables proper 4-level e-ink grayscale when anti-aliasing is enabled
### Pixel Caching
- Cache decoded/dithered images to `.pxc` files on SD card
- Cache format: 2-bit packed pixels (4 pixels per byte) with
width/height header
- On subsequent renders, load directly from cache instead of re-decoding
- Cache renderer supports grayscale render modes for multi-pass
rendering
- Significantly improves page navigation speed for image-heavy EPUBs
### HTML Parser Integration
- Update `ChapterHtmlSlimParser` to process `<img>` tags and extract
images from EPUB
- Resolve relative image paths within EPUB ZIP structure
- Extract images to cache directory before decoding
- Create `PageImage` elements with proper scaling to fit viewport
- Fall back to alt text display if image processing fails
### Build Configuration
- Add `PNG_MAX_BUFFERED_PIXELS=6402` to support up to 800px wide images
### Test Script
- Generate test EPUBs with annotated JPEG and PNG images
- Test cases cover: grayscale (4 levels), centering, scaling, cache
performance
## Test plan
- [x] Open EPUB with JPEG images - verify images display with proper
grayscale
- [x] Open EPUB with PNG images - verify images display correctly and no
crash
- [x] Navigate away from image page and back - verify faster load from
cache
- [x] Verify grayscale tones render correctly (not just black/white
dithering)
- [x] Verify large images are scaled down to fit screen
- [x] Verify images are centered horizontally
- [x] Verify page serialization/deserialization works with images
- [x] Verify images rendered in landscape mode
## Test Results
[png](https://photos.app.goo.gl/5zFUb8xA8db3dPd19)
[jpeg](https://photos.app.goo.gl/SwtwaL2DSQwKybhw7)








---
### AI Usage
Did you use AI tools to help write this code? _**< YES >**_
---------
Co-authored-by: Matthías Páll Gissurarson <mpg@mpg.is>
Co-authored-by: Dave Allie <dave@daveallie.com>
|