685d4e88f9745007124a034386f045d00d932856
493
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
243ae8b408 |
feat: show crash reason on boot (#1453)
## Summary If the system reboots from crash, display the reason and tell user to include `crash_report.txt` file. To test this, simply add an `assert(false)` somewhere inside the code.  --- ### 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** |
||
|
|
4e9c7a787f |
feat: show full path bar in file browser (#1411)
 ## Summary Adds a full path display at the bottom of the file browser with a separator line matching the header style. Path uses the small font, left-truncates to always show the deepest folder when path is too long. Toggleable via Settings > System > Show Full Path (default 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? _**< PARTIALLY >**_ --------- Co-authored-by: Patryk Radtke <patryk@Patryks-MacBook-Pro.local> |
||
|
|
05f8e6e12d |
fix: webserver /delete API backward compatibility (#1475)
## Summary
* **What is the goal of this PR?** (e.g., Implements the new feature for
file uploading.)
This fixes #682 by restoring API compatibility for external users, eg
deleting on-device books in bulk via calibre using the crosspoint plugin
works now.
* **What changes are included?**
The `/delete` webserver API now accepts the old `path` argument and maps
it to a JSON array element. It is made an error to provide both
arguments.
## 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 have compiled, flashed, and tested this on master at commit
|
||
|
|
405ce0c3c8 |
feat: Rework "Cover + Custom" sleep screens to show covers only when currently reading (#1256)
## Summary The cover/custom setting was a bit misleading to me. Usually after you start reading a book you never see your customs again unless a cover fails to render for some reason. With this, you can easily show your custom images by just sleeping from the menus instead of the reader ## 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 >**_ |
||
|
|
9c11f3e4a2 |
feat: enable manual screen refresh on power button short press (#1626)
## Summary Adds an option to allow manual screen refresh on power button short press. ## Additional Context there's an option to refresh the screen after a set number of pages. but sometimes a manual refresh is needed to clear up stale ink pixels. this works everywhere both in and out of reading mode. resolves #550 --- ### 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**_, to understand the project structure. |
||
|
|
fa2a3d2539 |
feat: add OPDS search support & next/prev page navigation (#1462)
## Summary **What is the goal of this PR?** Adds OPDS search support, allowing users to search a catalog directly from the book browser when the server exposes an OpenSearch template. **What changes are included?** - `OpdsParser`: parses the OpenSearch template URL from feed-level `<link rel="search">` elements and exposes it via `getSearchTemplate()` - `OpdsBookBrowserActivity`: fetches and stores the search template after each feed load; shows a Search hint on the Left button when a template is available; launches the existing `KeyboardEntryActivity` for query input; URL-encodes the query and fetches the result feed - Absolute search result URLs are handled correctly in `fetchFeed` (skips prepending the server base URL) - A `consumeConfirm` guard prevents the Confirm release that submits the keyboard from immediately triggering a book download on the first browsing frame after search results load ## Additional Context - Search is silently unavailable if the server does not advertise an OpenSearch template — no UI change in that case - Tested against a Calibre-Web OPDS endpoint which exposes `<link rel="search" type="application/opensearchdescription+xml">` - The inline URL encoder in `performSearch` was necessary as `StringUtils` has no such utility; worth considering extracting to `StringUtils` in a follow-up - No new dependencies introduced --- ### 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: kira <rammah@tuta.io> Co-authored-by: Justin Mitchell <justin@jmitch.com> |
||
|
|
5c12f2f01e |
fix: avoid skipping chapter after screenshot (#1625)
## Summary * **What is the goal of this PR?** Fixes skipping chapter when it's enabled in Settings and you take a Screenshot. * **What changes are included?** A simple return if Power and Down were released before the skipChapter. ## Additional Context * There is an Issue related #1595. * Checked that it kept the Power button funcionality for Next page and Suspend. --- ### 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**_ |
||
|
|
8d6b35b8e7 |
fix: back navigation from BMPViewer (#1597)
## Summary This fixes navigating back from the BMP Viewer to the FileBrowser which was broken when moving to the new ActivityManager This is fixed by making FileBrowserActivity able to take a full file path on enter and splitting the basePath and fileName from it and navigating to the correct place. fixes: https://github.com/crosspoint-reader/crosspoint-reader/issues/1553 duplicates: https://github.com/crosspoint-reader/crosspoint-reader/pull/910 to some extend but mine has the file cursor at the correct file instead of the first one in the folder ## 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? _**PARTIALLY**_ --------- Co-authored-by: Jan Ivanov <jan.ivanov@sirma.com> |
||
|
|
5ba85290ab |
refactor: Deduplicated BMP header writing in Xtc (#1439)
## Summary **What is the goal of this PR?** Replaced manual 1-bit BMP header logic in `Xtc::generateCoverBmp()` and `Xtc::generateThumbBmp()` with calls to the existing `createBmpHeader()` utility. Added a `BmpRowOrder` enum and param to support the top-down row order of XTC cover images. --- ### 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**_ |
||
|
|
5349e81723 |
feat: Display file extensions in File Browser (#1019)
## Summary    ## Additional Context Do we want a setting to toggle this? --- ### AI Usage Did you use AI tools to help write this code? _**NO**_ |
||
|
|
825ef56ad8 |
feat: X3 grayscale antialiasing improvements (#1607)
## Summary Improves text antialiasing quality on the Xteink X3 (SSD1677) display to bring it closer to X4 rendering quality. Addresses white lines through letter strokes and ghosting artifacts during page turns and screen transitions. ## Changes ### Display Driver (open-x4-sdk) - Dedicated X3 grayscale LUTs with tuned VDL drive strengths for dark gray (2 time units) and light gray (3 time units), with active GND hold on non-gray transitions to prevent floating source crosstalk - Tight scan timing: TP2/TP3 reduced to 1 (total gate-on 7 units vs 17), minimizing parasitic charge leakage that caused white lines through letter strokes - Fast diff BB reinforcement: Added mild VDH reinforcing pulse to lut_x3_bb_full so black pixels are actively driven during differential refreshes, clearing gray residue/ghosting - displayGrayBuffer() updated to use the dedicated gray LUT bank instead of full refresh LUTs for X3 ### Rendering Pipeline - Re-enabled light gray rendering for X3 text and images, now safe with dedicated gray LUTs providing proper 4-level gray - Removed isLightGrayRestricted() gating that was limiting X3 to 3-level gray - Runtime display dimensions in DirectPixelWriter and ScreenshotUtil, replaced hardcoded constants with runtime getters to support X3 792x528 resolution ### Note The open-x4-sdk submodule references a commit on juicecultus/community-sdk. A corresponding PR to open-x4-epaper/community-sdk should be merged first so the submodule ref resolves on upstream. ## Testing Tested on physical X3 hardware. White lines through letters significantly reduced, in-book ghosting improved via BB reinforcement, antialiasing visually closer to X4 quality. ## AI Disclosure Yes, AI was used to assist with the development of these changes. --------- |
||
|
|
ed0811c898 |
fix: Fix failing very first wifi connection attempt (#1521)
## Summary * **What is the goal of this PR?** The very first Wifi connection attempt with saved credentials failed, subsequent attempts succeeded. This PR fixes the first-attempt-issue. * **What changes are included?** ## Additional Context Claude analysis for WifiSelectionActivity In attemptConnection() ,there's no WiFi.disconnect() before WiFi.begin() — unlike the scan path earlier which does do a disconnect first. The root cause on ESP32 is the built-in auto-connect feature: the ESP32 WiFi stack saves credentials to NVS flash and automatically starts trying to connect on boot before your application code runs. When your attemptConnection() then calls WiFi.begin(), the stack is already in a transitional CONNECTING state, and the new begin() call either gets ignored or collides with the in-progress attempt. The fix is to add WiFi.disconnect(true) + a short delay in attemptConnection() before calling WiFi.begin(), and optionally call WiFi.persistent(false) to stop the ESP32 from auto-connecting on its own. --- ### 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 >**_ |
||
|
|
d29b8ee2f9 |
feat: Adjust Navigation at End of Book (#1425)
## Summary * **What is the goal of this PR? (e.g., Implements the new feature for file uploading.)** Currently, pressing forward at the end of a book loops back to the last page. This change will instead sends you to the home page instead. * **What changes are included?** Applies the change to the three supported format: EPUB, XTC, TXT ## Additional Context * This is more of a QOL improvement than a new feature. If there's interest, we could extend this to track a completed state for ebooks. --- ### 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**_ |
||
|
|
6cd19f5619 |
fix: epub images not rendering correctly on x3 (#1572)
Replace hardcoded DISPLAY_WIDTH, DISPLAY_HEIGHT, and DISPLAY_WIDTH_BYTES constants with runtime values from display object to support multiple device models (X3 and X4) with different screen dimensions. |
||
|
|
11984f8fef |
refactor: Use C++20 'requires' in ActivityResult constructor (#1420)
## Summary **What is the goal of this PR?** Replace SFINAE std::enable_if_t with a C++20 `requires` clause for clearer constraint expression and better compiler diagnostics on mismatch. --- ### 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**_ |
||
|
|
9b3885135f |
feat: Initial support for the x3 (#875)
## Summary Adds Xteink X3 hardware support to CrossPoint Reader. The X3 uses the same SSD1677 e-ink controller as the X4 but with a different panel (792x528 vs 800x480), different button layout, and an I2C fuel gauge (BQ27220) instead of ADC-based battery reading. All X3-specific behavior is gated by runtime device detection — X4 behavior is unchanged. Depends on community-sdk X3 support: open-x4-epaper/community-sdk#19 (merged). ## Changes ### HAL Layer **HalGPIO** (`lib/hal/HalGPIO.cpp/.h`) - I2C-based device fingerprinting at boot: probes for BQ27220 fuel gauge, DS3231 RTC, and QMI8658 IMU to distinguish X3 from X4 - Detection result cached in NVS for fast subsequent boots - Exposes `deviceIsX3()` / `deviceIsX4()` helpers used throughout the codebase - X3 button mapping (7 GPIOs vs X4's layout) - USB connection detection and wake classification for X3 **HalDisplay** (`lib/hal/HalDisplay.cpp/.h`) - Calls `einkDisplay.setDisplayX3()` before init when X3 is detected - Requests display resync after power button / flash wake events - Runtime display dimension accessors (`getDisplayWidth()`, `getDisplayHeight()`, `getBufferSize()`) - Exposed as global `display` instance for use by image converters **HalPowerManager** (`lib/hal/HalPowerManager.cpp/.h`) - X3 battery reading via I2C fuel gauge (BQ27220 at 0x55, SOC register) - X3 power button uses GPIO hold for deep sleep ### Display & Rendering **GfxRenderer** (`lib/GfxRenderer/GfxRenderer.cpp/.h`) - Buffer size and display dimensions are now runtime values (not compile-time constants) to support both panel sizes - X3 anti-aliasing tuning: only the darker grayscale level is applied to avoid washed-out text on the X3 panel. X4 retains both levels via `deviceIsX4()` gate **Image Converters** (`lib/JpegToBmpConverter`, `lib/PngToBmpConverter`) - Cover image prescale target uses runtime display dimensions from HAL instead of hardcoded 800x480 ### UI Themes **BaseTheme / LyraTheme** (`src/components/themes/`) - X3 button position mapping for the different physical layout - Adjusted UI element positioning for 792x528 viewport ### Boot & Init **main.cpp** - X3 hardware detection logging - Adjusted init sequence for X3 (no `HalSystem::begin()` dependency on X3 path) **HomeActivity** - Uses runtime `renderer.getBufferSize()` instead of static `GfxRenderer::getBufferSize()` FYI I did not add support for the gyro page turner. That can be it's own PR. |
||
|
|
710055f02c |
feat: Make directories stand out more in local file browser: "[dir]" instead of "dir" (#1339)
## Summary * **What is the goal of this PR?** It's difficult to distinguish directory names from normal file entries, so they are displayed now as "[dir]" instead of "dir" for classic theme * **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). --- ### 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**_ |
||
|
|
0cbfaa007d |
fix: Overlapping battery percentage on image pages with anti-aliasing (#1452)
## Summary **What is the goal of this PR?** When viewing a page with images and anti-aliasing enabled, the `imagePageWithAA` path renders the page twice with fast refreshes (blank image area, then restore). Both passes called `renderStatusBar()`, which reads the battery percentage live. If the value changed between the two renders (e.g. 88% -> 87%), the digits would overlap on screen. Fix: Removed the redundant `renderStatusBar()` from the second BW render. The status bar is already drawn and displayed in the first pass, and only the image area needs restoration. --- ### 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**_ |
||
|
|
7d56810ee6 |
feat: integrated epub optimizer (#1224)
## Problem
Many e-ink readers have limited image decoder support natively.
EPUBs with images in other formats than **baseline JPEG** frequently
cause:
- **Broken images**: pages render as blank, corrupted noise, or never
load
- **Slow rendering**: unoptimized images cause severe delays on e-ink
hardware, up to 7 seconds per page turn, with cover images taking up to
59 seconds to render
- **Broken covers**: the book thumbnail never generates
Fixing this today requires external tools before uploading.
---
## What this PR does
Adds an **optional, on-demand EPUB optimizer** to the file upload flow.
When enabled,
it converts all images to baseline JPEG directly in the browser — no
server, no internet,
no external tools needed.
**Conversion is opt-in. The standard upload flow is unchanged.**
---
## Real-world impact
The optimizer was applied in batch to **61 EPUBs**:
- 60 standard EPUBs: 198 MB → 55 MB (**−72.2%**, 143 MB saved)
- Text-dominant books: 8–46% smaller (covers and inline images
converted)
- Image-heavy / illustrated books: 65–93% smaller
- 1 Large manga volume (594 MB): 594 MB → 72 MB (**−87.8%**, 522 MB
saved)
- EPUB structural integrity fully maintained — zero new validation
issues introduced across all 61 books
*Size and integrity analysis:
[epub-comparator](https://github.com/pablohc/epub-comparator)*
From that set, **17 books were selected** as a representative sample
covering different content
types: image-heavy novels, pure manga, light novels with broken images,
and text-dominant books.
Each was benchmarked on two devices running in parallel, one on `master`
and one
on `PR#1224` — measuring render time across ~30 pages per book on
average.
### Rendering bugs fixed
| Book | Problem (original) | After optimization |
|------|--------------------|--------------------|
| Fairy Tale — Stephen King | Cover took **59.7 s** to render | 2.1 s
(−96%) |
| Cycle of the Werewolf — Stephen King | Cover took **23.3 s** to render
| 1.7 s (−93%) |
| Tomie: Complete Deluxe Ed. — Junji Ito | Cover took **18.3 s** to
render | 2.0 s (−89%) |
| Joel Dicker — El tigre (Ed. Ilustrada) | Cover took **14.5 s** to
render | 1.4 s (−90%) |
| Jackson, Holly — Asesinato para principiantes | Cover failed
completely (blank) | 2.0 s ✓ |
| Sentenced to Be a Hero — Yen Press | Cover failed, **8 images failed
to load** | All fixed ✓ |
| Flynn, Gillian — Perdida | Cover failed completely (blank) | 1.6 s ✓ |
| Chandler, Raymond — Asesino en la lluvia | Cover failed completely
(blank) | 2.0 s ✓ |
### Page render times — image-heavy EPUBs (avg per page)
| Book | Pages | Avg original | Avg optimized | Improvement | File size
|
|------|-------|-------------|---------------|-------------|-----------|
| Fairy Tale — Stephen King | 30 | 3,028 ms | 1,066 ms | **−64.8%** |
32.4 MB → 9.1 MB (−72%) |
| Cycle of the Werewolf — Stephen King | 33 | 3,026 ms | 1,558 ms |
**−48.5%** | 35.1 MB → 2.9 MB (−92%) |
| Joel Dicker — El tigre (Ed. Ilustrada) | 16 | 1,846 ms | 1,051 ms |
**−43.1%** | 5.3 MB → 0.4 MB (−93%) |
| Tomie: Complete Deluxe Ed. — Junji Ito | 30 | 4,817 ms | 2,802 ms |
**−41.8%** | 593.8 MB → 72.2 MB (−87.8%) |
| Sentenced to Be a Hero — Yen Press | 30 | 1,719 ms | 1,388 ms |
**−19.2%** | 15.2 MB → 1.6 MB (−90%) |
### Text-heavy EPUBs — no regression
| Book | Pages | Avg original | Avg optimized | Delta |
|------|-------|-------------|---------------|-------|
| Christie — Asesinato en el Orient Express | 30 | 1,672 ms | 1,646 ms |
−1.6% |
| Flynn — Perdida | 30 | 1,327 ms | 1,291 ms | −2.7% |
| Dicker — La verdad sobre el caso Harry Quebert | 30 | 1,132 ms | 1,084
ms | −4.2% |
| Hammett — El halcón maltés | 30 | 1,009 ms | 966 ms | −4.3% |
| Chandler — Asesino en la lluvia | 30 | 989 ms | 1,007 ms | +1.8% |
*Differences within ±5% — consistent with device measurement noise.*
*Render time benchmark:
[epub-optimization-benchmark](https://github.com/pablohc/epub-optimization-benchmark)*
---
## How to use it
**Single file:**
1. Click **Upload** (top of the page) — a modal opens. Use **Choose
files** to select one EPUB from your device.
2. Check **Optimize**.
- *(Optional)* Expand **Advanced Mode** — adjust quality, rotation, or
overlap; set individual images to H-Split / V-Split / Rotate.
3. Click **Optimize & Upload**.
**Batch (2+ files):**
1. Click **Upload** (top of the page) — a modal opens. Use **Choose
files** to select multiple EPUBs from your device.
2. Check **Optimize**.
- *(Optional)* Expand **Advanced Mode** — adjust quality.
3. Click **Upload** — all files are converted and uploaded sequentially.
Upload a batch of files, without optimization:
<img width="810" height="671" alt="image"
src="https://github.com/user-attachments/assets/d892ae13-0b87-4ea4-b6b8-340d56efc763"
/>
Batch file upload, with standard optimization:
<img width="809" height="707" alt="image"
src="https://github.com/user-attachments/assets/d32dbc88-1208-4555-bfcf-330ab91d2174"
/>
Optimization Phase (1/2):
<img width="807" height="1055" alt="image"
src="https://github.com/user-attachments/assets/fd4cd5f9-e56e-4ca1-9777-6926b9baf2bb"
/>
Upload Phase (2/2):
<img width="805" height="1065" alt="image"
src="https://github.com/user-attachments/assets/483294f0-02f0-4569-ae11-c10b3581d747"
/>
Batch upload successfully confirmed:
<img width="812" height="1043" alt="image"
src="https://github.com/user-attachments/assets/80c135bf-05c3-4c80-8755-2a04c68235bc"
/>
---
## Options
**Always active when the converter is enabled:**
- Converts PNG, WebP, BMP, GIF → baseline JPEG
- Smart downscaling to 480×800 px max (preserves aspect ratio)
- True grayscale for e-ink (BT.709 luminance, always on)
- SVG cover fix + OPF/NCX compliance repairs
**Advanced Mode (opt-in) — single file:**
- JPEG quality presets: 30% / 45% / 60% / 75% / **85%** (default) / 95%
- Rotation direction for split images: CW (default) / CCW
- Min overlap when splitting: 5% (default) / 10% / 15%
- Auto-download conversion log toggle (detailed stats per image)
- Per-image picker: set Normal / H-Split / V-Split / Rotate per image
individually,
with "Apply to all" for bulk assignment
**Advanced Mode (opt-in) — batch (2+ files):**
- JPEG quality presets: 30% / 45% / 60% / 75% / **85%** (default) / 95%
- Auto-download conversion log toggle (aggregated stats for all files)
---
## ⚠️ Known limitations
**KoReader hash-based sync will break** for converted files. The file
content changes,
so the hash no longer matches the original. Filename-based sync is
unaffected.
If you rely on KoReader hash sync, use the Calibre plugin or the web
tool instead.
---
## Build size impact
| Metric | master (
|
||
|
|
8dd365b4da |
feat: Implement silent pre-indexing for the next chapter in EpubReaderActivity (#979)
## Summary * A simple tweak to pre-index the next chapter silently during normal reading. * Triggers silent pre-indexing of the next chapter when the penultimate page of a chapter is rendered to reduce visible interruptions. * Keeps existing indexing with popup when a reader jumps directly into an unindexed chapter. ## Additional Context * Reader input is temporarily blocked during silent indexing to avoid navigation/index state conflicts. * The penultimate page is used because readers typically spend longer there than on the final page. * This change optimizes linear reading flow while preserving reliable indexing for non-linear navigation. ## Possible Improvements * Add a setting for First Page Indexing vs Penultimate Page Pre-indexing * Display an indexing icon in the status bar instead of using a popup that overlaps book text. Tested on device: https://www.dropbox.com/scl/fi/29g5kjqgsi5e4hgujv38u/Silent-Indexing.MOV?rlkey=yemi4mosmev5vicaa7gpe49qw&dl=0 --- ### 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: Jake Kenneally <jakekenneally@gmail.com> |
||
|
|
71719e1d94 |
feat: battery charging indicator (mirroring PR #537) (#1427)
## Summary * **What is the goal of this PR?** All praise goes to @didacta for his PR #537. Just picked up the reviewer comments to contain the changes as suggested (there was no response for more than 6 weeks, so I wanted to reanimate this feature). Just one addition: should recognize usb cable plug ins / retractions and update the icon immediately * **What changes are included?** ## Additional Context see #537 --- ### 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 >**_ |
||
|
|
f9286709d1 |
feat: Show hidden directories in browser (#1288)
## Summary * **What is the goal of this PR?** Add setting to display hidden files / directories in filebrowser / web file browser * **What changes are included?** ## 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? _**< NO >**_ |
||
|
|
79b54b3a75 |
fix: Init lastSleepImage (edge case) (#1360)
## Summary * **What is the goal of this PR?** fix edge case for definition ## Additional Context If loadFromFile() returns false (no state file exists — first boot, or SD missing), lastSleepImage is never set and contains garbage. [SleepActivity.cpp:83] then uses it in a while comparison to avoid repeating the same image. The JSON path (doc["lastSleepImage"] | (uint8_t)0) handles it, but only if the file exists. --- ### 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 **_ |
||
|
|
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 |
||
|
|
18b36efbae |
feat: dump crash report to sdcard (#1145)
## Summary This allow dumping crash message (i.e. assertion fail) and stack trace to `crash_report.txt` file on sdcard. The stack trace can then be decoded using https://esphome.github.io/esp-stacktrace-decoder/ Could be useful to debug things like https://github.com/crosspoint-reader/crosspoint-reader/issues/1137 where error doesn't always happen. May also be useful to show a screen to tell what happen (show on next boot after crash), similar to [flipper zero crash message](https://www.reddit.com/r/flipperzero/comments/10f8m3f/anyone_who_can_tell_me_why_this_message_pops_up/) , but this is better to be a dedicated PR (I'm missing the `drawTextWrapped` function, too lazy to code it ; update: exactly what I need in https://github.com/crosspoint-reader/crosspoint-reader/pull/1141) To test this: - Option 1: add an `assert(false)` somewhere in the code - Option 2: try dereferencing a nullptr - Option 3: try `throw` an exception Example of a crash report: ``` CrossPoint version: 1.1.0-dev Panic reason: abort() was called at PC 0x4214585b on core 0 Recent logs: [196] [DBG] [GFX] Time = 2 ms from clearScreen to displayBuffer [1831] [DBG] [RBS] Recent books loaded from file (7 entries) [1832] [DBG] [ACT] Exiting activity: Boot [1832] [DBG] [ACT] Entering activity: Home [1891] [DBG] [GFX] Time = 54 ms from clearScreen to displayBuffer [2521] [DBG] [GFX] Time = 46 ms from clearScreen to displayBuffer [4839] [DBG] [PWR] Going to low-power mode [10048] [INF] [MEM] Free: 134164 bytes, Total: 232372 bytes, Min Free: 133664 bytes [20060] [INF] [MEM] Free: 134164 bytes, Total: 232372 bytes, Min Free: 133664 bytes [30072] [INF] [MEM] Free: 134164 bytes, Total: 232372 bytes, Min Free: 133664 bytes [34453] [DBG] [PWR] Restoring normal CPU frequency [34485] [DBG] [GFX] Time = 30 ms from clearScreen to displayBuffer [35182] [DBG] [GFX] Time = 31 ms from clearScreen to displayBuffer [36675] [DBG] [GFX] Time = 30 ms from clearScreen to displayBuffer [38800] [DBG] [GFX] Time = 30 ms from clearScreen to displayBuffer [40079] [INF] [MEM] Free: 134164 bytes, Total: 232372 bytes, Min Free: 133664 bytes Stack memory: 0x3FCB0650: 0x00000000 0x00000000 0x3FCB0668 0x4038DBB6 0x00000000 0x00000000 0x3FCA0030 0x3FC936D0 0x3FCB0670: 0x3FCB067C 0x3FC936EC 0x3FCB0668 0x34313234 0x62353835 0x00000000 0x726F6261 0x20292874 0x3FCB0690: 0x20736177 0x6C6C6163 0x61206465 0x43502074 0x34783020 0x35343132 0x20623538 0x63206E6F 0x3FCB06B0: 0x2065726F 0x00000030 0x3FCA0000 0xB37A603F 0x00000001 0x3FCA7000 0x3FCABCDC 0x4214585E 0x3FCB06D0: 0x3FCA7000 0x3FCA7000 0x3FCABCDC 0x421458AA 0x3FCABCDC 0x3FCA7000 0x3FCABCDC 0x421459CC 0x3FCB06F0: 0x3FCA7000 0x3FCA7000 0x42145D5A 0x3C205624 0x40388560 0x3FCA7000 0x3FCABCFC 0x42079866 0x3FCB0710: 0x3FCA7000 0x3FCA7000 0x00009C9A 0x4207B7F6 0x3FCA7000 0x42090000 0x001B7740 0x00000001 0x3FCB0730: 0x3FCA7000 0x3FCA7000 0x00000001 0x600C0028 0x00000001 0x3FCA1000 0x00000000 0x00000000 0x3FCB0750: 0x00000000 0x00000000 0x00000000 0xB37A603F 0x00000000 0x00000000 0x00000000 0x00000000 0x3FCB0770: 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x42090000 0x3FCA7000 0x4208F9C4 0x3FCB0790: 0x00000000 0x00000000 0x00000000 0x40388368 0x00000000 0x00000000 0x00000000 0x00000000 0x3FCB07B0: 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0xA5A5A5A5 0xA5A5A5A5 0xA5A5A5A5 0x3FCB07D0: 0xA5A5A5A5 0xA5A5A5A5 0xA5A5A5A5 0xA5A5A5A5 0xBAAD5678 0xDA6D3601 0x5EB5B9C5 0x2602E480 0x3FCB07F0: 0x2BCDD33F 0x15556D4A 0x1F2140A0 0x5D59BEE3 0x8E76449F 0x6FB2D0CE 0xF5F46FAC 0x0112946A 0x3FCB0810: 0x3B0B32E0 0x7A52B537 0x46801DB4 0xDA85DF9F 0x37E83D20 0x12861028 0x47A702BB 0x287A3C8A 0x3FCB0830: 0x03632209 0xD44C5489 0x5E258453 0xFDA77529 0xE6748E23 0xADCF1394 0x67AD6778 0x2C208663 0x3FCB0850: 0xC7985786 0xD4AA3AB2 0x312E1760 0xEC7AEAAE 0x1857020E 0x48003E7E 0xD6CB8763 0x9B4A3F66 0x3FCB0870: 0x4B79E9F6 0xCBF739F0 0x3794C641 0xD0DBA3CB 0x95B9BE15 0x581C9983 0xDE62EFB6 0x20C67C5B 0x3FCB0890: 0x1E4A3DF3 0xFB317C74 0xC0D86103 0x1D79ED56 0x72FE0862 0x3D38B0C8 0xD27EB587 0x0E0A4C40 0x3FCB08B0: 0xF643ADC0 0x56D114D7 0x703AF879 0xAC7F3075 0x89C78C23 0xEDA86814 0xF767B3E3 0x0528838F 0x3FCB08D0: 0x50ED4662 0x11FD38E7 0x8A5A83BB 0x658159BD 0x781AF696 0x8A700F79 0x526DDE23 0xC8472505 0x3FCB08F0: 0x21AACC02 0xCB89369E 0xB82E5BE2 0x4C6C9D7D 0x9E724D9B 0xDC1067F7 0x84478FBC 0x4E89C444 0x3FCB0910: 0x973F4229 0x49F93DA8 0xE30200F6 0xD1B5C391 0x8363A89F 0x2409E74C 0x3AFF7B52 0xCBEC2349 0x3FCB0930: 0xD38F6695 0xBC3EA980 0xF067EBB1 0x7F87D167 0x92B3823B 0x9F0617D7 0xA7537C57 0x12CAB3D4 0x3FCB0950: 0xC82EEE37 0x84D4B4BC 0xE1E2261C 0x488F0ADA 0x96EAF2FF 0x0BC493A0 0xCE614467 0x3829053D 0x3FCB0970: 0xA41156BE 0x2747B77D 0x64DEA90B 0xE704AB0A 0xE4B01006 0x8D51903C 0x56CD3CF2 0x07E0A8E8 0x3FCB0990: 0xD1DE05CE 0x33368522 0xD1889988 0x3A3097F4 0xB0796D09 0xC78948AA 0x6DEFC56E 0xD5C2E1D9 0x3FCB09B0: 0xFD6DD8FA 0xA957B675 0xC202D80D 0x733FF8F4 0xA1484913 0x0B9AFBA6 0x330C07EA 0x2C09AD4C 0x3FCB09D0: 0x3B1E08F7 0x3FCAE7D0 0x00000170 0xABBA1234 0x0000015C 0x3FCB00E0 0x00009C93 0x3FCA13C4 0x3FCB09F0: 0x3FCA13C4 0x3FCB09E4 0x3FCA13BC 0x00000018 0x00000000 0x00000000 0x3FCB09E4 0x00000000 0x3FCB0A10: 0x00000001 0x3FCAE7E0 0x706F6F6C 0x6B736154 0x00000000 0x00000000 0x3FCB07D0 0x00000005 0x3FCB0A30: 0x00000000 0x00000001 0x00000000 0x3FCAB444 0x4209AFF0 0x0017E38F 0x00000000 0x3FCA7BD0 ``` --- ### 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: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> |
||
|
|
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 >**_ |
||
|
|
6ff5fcd9a7 |
fix: make file system operations thread-safe (HalFile) (#1212)
## Summary Fix https://github.com/crosspoint-reader/crosspoint-reader/issues/1137 Introducing `HalFile`, a thin wrapper around `FsFile` that uses a global mutex to protect file operations. To test this PR, place the code below somewhere in the code base (I placed it in `onGoToRecentBooks`) ```cpp static auto testTask = [](void* param) { for (int i = 0; i < 10; i++) { String json = Storage.readFile("/.crosspoint/settings.json"); LOG_DBG("TEST_TASK", "Read settings.json, bytes read: %u", json.length()); } vTaskDelete(nullptr); }; xTaskCreate(testTask, "test0", 8192, nullptr, 1, nullptr); xTaskCreate(testTask, "test1", 8192, nullptr, 1, nullptr); xTaskCreate(testTask, "test2", 8192, nullptr, 1, nullptr); xTaskCreate(testTask, "test3", 8192, nullptr, 1, nullptr); delay(1000); ``` It will reliably lead to crash on `master`, but will function correctly with this PR. A macro renaming trick is used to avoid changing too many downstream code files. --- ### 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**, only to help with tedious copy-paste tasks --------- Co-authored-by: Zach Nelson <zach@zdnelson.com> |
||
|
|
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... |