fix: several bookmarks UX improvments (#2372)

## Summary

This PR enhances the EPUB reader's bookmark system with two
complementary improvements: a per-page bookmark indicator icon and
toggle behavior on the existing long-press action.

---

### What Changed

**Bookmark Toggle (was: add-only)**

The long-press Confirm action now toggles bookmarks rather than always
adding. `addBookmark()` checks whether a bookmark with the same xpath
already exists in the in-memory cache:
- If found → removes it and shows "Bookmark removed."
- If not found → adds it and shows "Bookmark added."

A new `STR_BOOKMARK_REMOVED` translation string was added to support the
removal message.

**Bookmark Icon Indicator**

A `BookmarkIcon` is now drawn at the top-right corner of the page
whenever the current page has a bookmark. `updateBookmarkFlag()` is
called at render time to determine whether the current page is
bookmarked.

**In-Memory Bookmark Cache**

Bookmarks are now loaded into `cachedBookmarks` on `onEnter()` rather
than being re-read from disk on every toggle. All subsequent add/remove
operations work against this cache and flush to disk, avoiding redundant
file reads on each bookmark action.

**Faster bookmarks list**

Previously, calculating "page X/Y" for each entry required decompressing
the entire spine item. We now persist `si`/`pc`/`pp` (spine index, page
count, and page progress) in the bookmark JSON when saving, and restore
them when loading. This avoids the expensive `toCrossPoint()` loop in
`onEnter()`, significantly reducing the cost of initializing the
bookmarks list.

---

### Files Changed

- `EpubReaderActivity.cpp` — `addBookmark()` toggle logic,
`updateBookmarkFlag()` (new), icon rendering in `renderContents()`,
cache initialization in `onEnter()`
- `EpubReaderActivity.h` — new fields: `currentPageBookmarked`,
`bookmarkRemoved`, `cachedBookmarks`; new method declaration
`updateBookmarkFlag()`

---

### 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: Julia Nguyen <julia@uxj.io>
This commit is contained in:
Uri Tauber
2026-06-23 14:55:01 -04:00
committed by GitHub
co-authored by Julia Nguyen
parent 362dcb2a65
commit 1db1442319
36 changed files with 272 additions and 92 deletions
+3
View File
@@ -176,6 +176,9 @@ STR_EXIT: "« Выхад"
STR_HOME: "« Галоўная" STR_HOME: "« Галоўная"
STR_SELECT: "Абраць" STR_SELECT: "Абраць"
STR_TOGGLE: "Выбар" STR_TOGGLE: "Выбар"
STR_TOGGLE_BOOKMARK: "Пераключыць закладку"
STR_BOOKMARK_REMOVED: "Закладка выдалена."
STR_HOLD_OPEN_TO_DELETE: "Утрымлівайце Адкрыць, каб выдаліць"
STR_CONFIRM: "Пацв." STR_CONFIRM: "Пацв."
STR_CANCEL: "Адмена" STR_CANCEL: "Адмена"
STR_CONNECT: "Падкл." STR_CONNECT: "Падкл."
+3 -2
View File
@@ -175,8 +175,7 @@ STR_DOWNLOADING: "S'està baixant..."
STR_DOWNLOAD_FAILED: "Ha fallat la baixada" STR_DOWNLOAD_FAILED: "Ha fallat la baixada"
STR_ERROR_MSG: "Error:" STR_ERROR_MSG: "Error:"
STR_UNNAMED: "Sense nom" STR_UNNAMED: "Sense nom"
STR_HOLD_CONFIRM_TO_DELETE: "Manteniu premut Confirma per esborrar" STR_HOLD_OPEN_TO_DELETE: "Manteniu premut Obre per esborrar"
STR_BOOKMARK_INSTRUCTIONS: "Manteniu premut Confirma al lector per crear un punt de llibre."
STR_NO_SERVER_URL: "No s'ha configurat cap URL de servidor" STR_NO_SERVER_URL: "No s'ha configurat cap URL de servidor"
STR_FETCH_FEED_FAILED: "Ha fallat l'obtenció del feed" STR_FETCH_FEED_FAILED: "Ha fallat l'obtenció del feed"
STR_PARSE_FEED_FAILED: "Ha fallat l'anàlisi del feed" STR_PARSE_FEED_FAILED: "Ha fallat l'anàlisi del feed"
@@ -192,6 +191,7 @@ STR_HOME: "« Inici"
STR_SELECT: "Selecciona" STR_SELECT: "Selecciona"
STR_SELECTED: "Seleccionat" STR_SELECTED: "Seleccionat"
STR_TOGGLE: "Canvia" STR_TOGGLE: "Canvia"
STR_TOGGLE_BOOKMARK: "Commuta punt de llibre"
STR_CONFIRM: "Confirma" STR_CONFIRM: "Confirma"
STR_CANCEL: "Cancel·la" STR_CANCEL: "Cancel·la"
STR_CONNECT: "Connecta" STR_CONNECT: "Connecta"
@@ -236,6 +236,7 @@ STR_QUICK_RESUME_TIMEOUT: "Represa ràpida després del temps"
STR_REMAP_FRONT_BUTTONS: "Reassigna els botons frontals" STR_REMAP_FRONT_BUTTONS: "Reassigna els botons frontals"
STR_BOOKMARKS: "Punts de llibre" STR_BOOKMARKS: "Punts de llibre"
STR_BOOKMARK_ADDED: "S'ha afegit el punt de llibre." STR_BOOKMARK_ADDED: "S'ha afegit el punt de llibre."
STR_BOOKMARK_REMOVED: "S'ha eliminat el punt de llibre."
STR_OPDS_BROWSER: "Navegador OPDS" STR_OPDS_BROWSER: "Navegador OPDS"
STR_COVER_CUSTOM: "Portada + Personalitzat" STR_COVER_CUSTOM: "Portada + Personalitzat"
STR_QUICK_RESUME: "Represa ràpida" STR_QUICK_RESUME: "Represa ràpida"
+3
View File
@@ -181,6 +181,9 @@ STR_EXIT: "« Konec"
STR_HOME: "« Domů" STR_HOME: "« Domů"
STR_SELECT: "Vybrat" STR_SELECT: "Vybrat"
STR_TOGGLE: "Přepnout" STR_TOGGLE: "Přepnout"
STR_TOGGLE_BOOKMARK: "Přepnout záložku"
STR_BOOKMARK_REMOVED: "Záložka odstraněna."
STR_HOLD_OPEN_TO_DELETE: "Podržte Otevřít pro smazání"
STR_CONFIRM: "Potvrdit" STR_CONFIRM: "Potvrdit"
STR_CANCEL: "Zrušit" STR_CANCEL: "Zrušit"
STR_CONNECT: "Připojit" STR_CONNECT: "Připojit"
+3
View File
@@ -187,6 +187,9 @@ STR_HOME: "« Hjem"
STR_SELECT: "Vælg" STR_SELECT: "Vælg"
STR_SELECTED: "Valgt" STR_SELECTED: "Valgt"
STR_TOGGLE: "Skift" STR_TOGGLE: "Skift"
STR_TOGGLE_BOOKMARK: "Skift bogmærke"
STR_BOOKMARK_REMOVED: "Bogmærke fjernet."
STR_HOLD_OPEN_TO_DELETE: "Hold Åbn nede for at slette"
STR_CONFIRM: "Bekræft" STR_CONFIRM: "Bekræft"
STR_CANCEL: "Annuller" STR_CANCEL: "Annuller"
STR_CONNECT: "Forbind" STR_CONNECT: "Forbind"
+3
View File
@@ -187,6 +187,9 @@ STR_HOME: "« Home"
STR_SELECT: "Kies" STR_SELECT: "Kies"
STR_SELECTED: "Geselecteerd" STR_SELECTED: "Geselecteerd"
STR_TOGGLE: "Wissel" STR_TOGGLE: "Wissel"
STR_TOGGLE_BOOKMARK: "Bladwijzer wisselen"
STR_BOOKMARK_REMOVED: "Bladwijzer verwijderd."
STR_HOLD_OPEN_TO_DELETE: "Houd Openen ingedrukt om te verwijderen"
STR_CONFIRM: "Bevestig" STR_CONFIRM: "Bevestig"
STR_CANCEL: "Annuleer" STR_CANCEL: "Annuleer"
STR_CONNECT: "Verbind" STR_CONNECT: "Verbind"
+3 -2
View File
@@ -179,8 +179,7 @@ STR_DOWNLOADING: "Downloading..."
STR_DOWNLOAD_FAILED: "Download failed" STR_DOWNLOAD_FAILED: "Download failed"
STR_ERROR_MSG: "Error:" STR_ERROR_MSG: "Error:"
STR_UNNAMED: "Unnamed" STR_UNNAMED: "Unnamed"
STR_HOLD_CONFIRM_TO_DELETE: "Hold Confirm to Delete" STR_HOLD_OPEN_TO_DELETE: "Hold Open to Delete"
STR_BOOKMARK_INSTRUCTIONS: "Hold Confirm from the reader to create a bookmark."
STR_NO_SERVER_URL: "No server URL configured" STR_NO_SERVER_URL: "No server URL configured"
STR_FETCH_FEED_FAILED: "Failed to fetch feed" STR_FETCH_FEED_FAILED: "Failed to fetch feed"
STR_PARSE_FEED_FAILED: "Failed to parse feed" STR_PARSE_FEED_FAILED: "Failed to parse feed"
@@ -198,6 +197,7 @@ STR_HOME: "« Home"
STR_SELECT: "Select" STR_SELECT: "Select"
STR_SELECTED: "Selected" STR_SELECTED: "Selected"
STR_TOGGLE: "Toggle" STR_TOGGLE: "Toggle"
STR_TOGGLE_BOOKMARK: "Toggle Bookmark"
STR_CONFIRM: "Confirm" STR_CONFIRM: "Confirm"
STR_CANCEL: "Cancel" STR_CANCEL: "Cancel"
STR_CONNECT: "Connect" STR_CONNECT: "Connect"
@@ -260,6 +260,7 @@ STR_SUNLIGHT_FADING_FIX: "Sunlight Fading Fix"
STR_REMAP_FRONT_BUTTONS: "Remap Front Buttons" STR_REMAP_FRONT_BUTTONS: "Remap Front Buttons"
STR_BOOKMARKS: "Bookmarks" STR_BOOKMARKS: "Bookmarks"
STR_BOOKMARK_ADDED: "Bookmark added." STR_BOOKMARK_ADDED: "Bookmark added."
STR_BOOKMARK_REMOVED: "Bookmark removed."
STR_OPDS_BROWSER: "OPDS Browser" STR_OPDS_BROWSER: "OPDS Browser"
STR_SEARCH: "Search" STR_SEARCH: "Search"
STR_COVER_CUSTOM: "Cover + Custom" STR_COVER_CUSTOM: "Cover + Custom"
+3
View File
@@ -181,6 +181,9 @@ STR_EXIT: "« Poistu"
STR_HOME: "« Koti" STR_HOME: "« Koti"
STR_SELECT: "Valitse" STR_SELECT: "Valitse"
STR_TOGGLE: "Vaihda" STR_TOGGLE: "Vaihda"
STR_TOGGLE_BOOKMARK: "Vaihda kirjanmerkki"
STR_BOOKMARK_REMOVED: "Kirjanmerkki poistettu."
STR_HOLD_OPEN_TO_DELETE: "Pidä Avaa painettuna poistaaksesi"
STR_CONFIRM: "Vahvista" STR_CONFIRM: "Vahvista"
STR_CANCEL: "Peruuta" STR_CANCEL: "Peruuta"
STR_CONNECT: "Yhdistä" STR_CONNECT: "Yhdistä"
+3
View File
@@ -187,6 +187,9 @@ STR_HOME: "« Accueil"
STR_SELECT: "OK" STR_SELECT: "OK"
STR_SELECTED: "Sélectionné" STR_SELECTED: "Sélectionné"
STR_TOGGLE: "Modifier" STR_TOGGLE: "Modifier"
STR_TOGGLE_BOOKMARK: "Basculer le marque-page"
STR_BOOKMARK_REMOVED: "Marque-page supprimé."
STR_HOLD_OPEN_TO_DELETE: "Maintenir Ouvrir pour supprimer"
STR_CONFIRM: "Confirmer" STR_CONFIRM: "Confirmer"
STR_CANCEL: "Annuler" STR_CANCEL: "Annuler"
STR_CONNECT: "Connecter" STR_CONNECT: "Connecter"
+3 -2
View File
@@ -168,8 +168,7 @@ STR_DOWNLOADING: "Herunterladen…"
STR_DOWNLOAD_FAILED: "Ladefehler" STR_DOWNLOAD_FAILED: "Ladefehler"
STR_ERROR_MSG: "Fehler:" STR_ERROR_MSG: "Fehler:"
STR_UNNAMED: "Unbenannt" STR_UNNAMED: "Unbenannt"
STR_HOLD_CONFIRM_TO_DELETE: "Halte Bestätigen zum Löschen" STR_HOLD_OPEN_TO_DELETE: "Halte Öffnen zum Löschen"
STR_BOOKMARK_INSTRUCTIONS: "Halte Bestätigen im Lesemodus um ein Lesezeichen anzulegen."
STR_NO_SERVER_URL: "Keine Server-URL konfiguriert" STR_NO_SERVER_URL: "Keine Server-URL konfiguriert"
STR_FETCH_FEED_FAILED: "Feedfehler" STR_FETCH_FEED_FAILED: "Feedfehler"
STR_PARSE_FEED_FAILED: "Feed-Format ungültig" STR_PARSE_FEED_FAILED: "Feed-Format ungültig"
@@ -187,6 +186,7 @@ STR_HOME: "« Start"
STR_SELECT: "Auswahl" STR_SELECT: "Auswahl"
STR_SELECTED: "Ausgewählt" STR_SELECTED: "Ausgewählt"
STR_TOGGLE: "Ändern" STR_TOGGLE: "Ändern"
STR_TOGGLE_BOOKMARK: "Lesezeichen umschalten"
STR_CONFIRM: "Bestätigen" STR_CONFIRM: "Bestätigen"
STR_CANCEL: "Abbrechen" STR_CANCEL: "Abbrechen"
STR_CONNECT: "Verbinden" STR_CONNECT: "Verbinden"
@@ -250,6 +250,7 @@ STR_QUICK_RESUME_TIMEOUT: "Schnelles Fortsetzen nach Timeout"
STR_REMAP_FRONT_BUTTONS: "Vordere Tasten belegen" STR_REMAP_FRONT_BUTTONS: "Vordere Tasten belegen"
STR_BOOKMARKS: "Lesezeichen" STR_BOOKMARKS: "Lesezeichen"
STR_BOOKMARK_ADDED: "Lesezeichen hinzugefügt." STR_BOOKMARK_ADDED: "Lesezeichen hinzugefügt."
STR_BOOKMARK_REMOVED: "Lesezeichen entfernt."
STR_SEARCH: "Suche" STR_SEARCH: "Suche"
STR_OPDS_BROWSER: "OPDS-Browser" STR_OPDS_BROWSER: "OPDS-Browser"
STR_COVER_CUSTOM: "Cover + Eigenes" STR_COVER_CUSTOM: "Cover + Eigenes"
+3 -2
View File
@@ -189,6 +189,7 @@ STR_HOME: "מסך הבית »"
STR_SELECT: "בחר" STR_SELECT: "בחר"
STR_SELECTED: "נבחר" STR_SELECTED: "נבחר"
STR_TOGGLE: "בחר" STR_TOGGLE: "בחר"
STR_TOGGLE_BOOKMARK: "הוסף/הסר סימנייה"
STR_CONFIRM: "אישור" STR_CONFIRM: "אישור"
STR_CANCEL: "ביטול" STR_CANCEL: "ביטול"
STR_CONNECT: "התחבר" STR_CONNECT: "התחבר"
@@ -360,8 +361,7 @@ STR_FRONT_BTN_FOLLOW_ORIENTATION: "התאמת לחצנים קדמיים לכיו
STR_REMOVE_READ_FROM_RECENTS: "הסר ספרים שנקראו מרשימת האחרונים" STR_REMOVE_READ_FROM_RECENTS: "הסר ספרים שנקראו מרשימת האחרונים"
STR_MOVE_FINISHED_TO_READ: "העבר ספרים שהסתיימו לתיקיית 'נקראו'" STR_MOVE_FINISHED_TO_READ: "העבר ספרים שהסתיימו לתיקיית 'נקראו'"
STR_DISABLED: "מבוטל" STR_DISABLED: "מבוטל"
STR_HOLD_CONFIRM_TO_DELETE: "החזק לחוץ על אישור כדי למחוק" STR_HOLD_OPEN_TO_DELETE: "לחיצה ארוכה על 'פתח' כדי למחוק"
STR_BOOKMARK_INSTRUCTIONS: "החזק לחוץ על כפתור אישור בזמן הקריאה כדי להוסיף סימנייה"
STR_CLOCK: "שעון" STR_CLOCK: "שעון"
STR_CLOCK_UTC_OFFSET: "הפרש זמן UTC" STR_CLOCK_UTC_OFFSET: "הפרש זמן UTC"
STR_CLOCK_FORMAT: "תבנית השעון" STR_CLOCK_FORMAT: "תבנית השעון"
@@ -379,6 +379,7 @@ STR_CLOCK_SYNC_NO_WIFI_HINT: "התחבר תחילה לרשת אלחוטית, ו
STR_CLOCK_SYNCED: "השעון סונכרן" STR_CLOCK_SYNCED: "השעון סונכרן"
STR_BOOKMARKS: "סימניות" STR_BOOKMARKS: "סימניות"
STR_BOOKMARK_ADDED: "הסימנייה התווספה" STR_BOOKMARK_ADDED: "הסימנייה התווספה"
STR_BOOKMARK_REMOVED: "הסימנייה הוסרה"
STR_QUICK_RESUME: "חזרה מהירה" STR_QUICK_RESUME: "חזרה מהירה"
STR_REMOVE_FROM_RECENTS: "להסיר מרשימת הספרים האחרונים?" STR_REMOVE_FROM_RECENTS: "להסיר מרשימת הספרים האחרונים?"
STR_CONFIRM_DELETE_BOOKMARK: "למחוק סימנייה זו?" STR_CONFIRM_DELETE_BOOKMARK: "למחוק סימנייה זו?"
+3
View File
@@ -184,6 +184,9 @@ STR_HOME: "« Főoldal"
STR_SELECT: "Kiválasztás" STR_SELECT: "Kiválasztás"
STR_SELECTED: "Kiválasztva" STR_SELECTED: "Kiválasztva"
STR_TOGGLE: "Váltás" STR_TOGGLE: "Váltás"
STR_TOGGLE_BOOKMARK: "Könyvjelző váltása"
STR_BOOKMARK_REMOVED: "Könyvjelző eltávolítva."
STR_HOLD_OPEN_TO_DELETE: "Tartsa lenyomva a Megnyitás gombot a törléshez"
STR_CONFIRM: "Megerősítés" STR_CONFIRM: "Megerősítés"
STR_CANCEL: "Mégse" STR_CANCEL: "Mégse"
STR_CONNECT: "Csatlakozás" STR_CONNECT: "Csatlakozás"
+3 -2
View File
@@ -192,6 +192,7 @@ STR_HOME: "« Home"
STR_SELECT: "Seleziona" STR_SELECT: "Seleziona"
STR_SELECTED: "Selezionato" STR_SELECTED: "Selezionato"
STR_TOGGLE: "Cambia" STR_TOGGLE: "Cambia"
STR_TOGGLE_BOOKMARK: "Attiva/disattiva segnalibro"
STR_CONFIRM: "Conferma" STR_CONFIRM: "Conferma"
STR_CANCEL: "Annulla" STR_CANCEL: "Annulla"
STR_CONNECT: "Connetti" STR_CONNECT: "Connetti"
@@ -357,9 +358,9 @@ STR_FIRMWARE_WRITE_FAILED: "Aggiornamento firmware non riuscito"
STR_FIRMWARE_UPDATE_DO_NOT_POWER_OFF: "Non spegnere il dispositivo!" STR_FIRMWARE_UPDATE_DO_NOT_POWER_OFF: "Non spegnere il dispositivo!"
STR_RECOVERY_MODE: "Modalità ripristino" STR_RECOVERY_MODE: "Modalità ripristino"
STR_RECOVERY_MODE_HINT: "Metti firmware.bin nella scheda SD e selezionalo" STR_RECOVERY_MODE_HINT: "Metti firmware.bin nella scheda SD e selezionalo"
STR_BOOKMARK_INSTRUCTIONS: "Tieni premuto Conferma nel lettore per creare un segnalibro"
STR_BOOKMARKS: "Segnalibri" STR_BOOKMARKS: "Segnalibri"
STR_BOOKMARK_ADDED: "Segnalibro aggiunto" STR_BOOKMARK_ADDED: "Segnalibro aggiunto"
STR_BOOKMARK_REMOVED: "Segnalibro rimosso"
STR_CONFIRM_DELETE_BOOKMARK: "Eliminare questo segnalibro?" STR_CONFIRM_DELETE_BOOKMARK: "Eliminare questo segnalibro?"
STR_CLOCK_SYNC_NO_WIFI_HINT: "Connettiti prima al Wi-Fi e poi riprova" STR_CLOCK_SYNC_NO_WIFI_HINT: "Connettiti prima al Wi-Fi e poi riprova"
STR_CLOCK_SYNC_OK: "Orologio sincronizzato" STR_CLOCK_SYNC_OK: "Orologio sincronizzato"
@@ -374,7 +375,7 @@ STR_CLOCK_SYNCING: "Sincronizzazione con il server NTP..."
STR_CLOCK_SYNC_FAIL: "Sincronizzazione non riuscita" STR_CLOCK_SYNC_FAIL: "Sincronizzazione non riuscita"
STR_CLOCK_SYNC_NOW: "Sincronizza l'orologio adesso" STR_CLOCK_SYNC_NOW: "Sincronizza l'orologio adesso"
STR_CLOCK_SYNC_NO_WIFI: "Wi-Fi non connesso" STR_CLOCK_SYNC_NO_WIFI: "Wi-Fi non connesso"
STR_HOLD_CONFIRM_TO_DELETE: "Tieni premuto Conferma per cancellare" STR_HOLD_OPEN_TO_DELETE: "Tieni premuto Apri per eliminare"
STR_NEXT_FIELD: "Succ." STR_NEXT_FIELD: "Succ."
STR_CURRENT_TIME: "Ora attuale: " STR_CURRENT_TIME: "Ora attuale: "
STR_DISABLED: "Disattivato" STR_DISABLED: "Disattivato"
+3
View File
@@ -177,6 +177,9 @@ STR_EXIT: "« Шығу"
STR_HOME: "« Басты" STR_HOME: "« Басты"
STR_SELECT: "Таңдау" STR_SELECT: "Таңдау"
STR_TOGGLE: "Ауыстыру" STR_TOGGLE: "Ауыстыру"
STR_TOGGLE_BOOKMARK: "Бетбелгіні ауыстыру"
STR_BOOKMARK_REMOVED: "Бетбелгі жойылды."
STR_HOLD_OPEN_TO_DELETE: "Жою үшін Ашу түймесін ұстап тұрыңыз"
STR_CONFIRM: "Растау" STR_CONFIRM: "Растау"
STR_CANCEL: "Болдырмау" STR_CANCEL: "Болдырмау"
STR_CONNECT: "Қосылу" STR_CONNECT: "Қосылу"
+3
View File
@@ -184,6 +184,9 @@ STR_HOME: "« Pradžia"
STR_SELECT: "Rinktis" STR_SELECT: "Rinktis"
STR_SELECTED: "Pasirinkta" STR_SELECTED: "Pasirinkta"
STR_TOGGLE: "Keisti" STR_TOGGLE: "Keisti"
STR_TOGGLE_BOOKMARK: "Perjungti žymę"
STR_BOOKMARK_REMOVED: "Žymė pašalinta."
STR_HOLD_OPEN_TO_DELETE: "Laikykite Atidaryti, kad ištrintumėte"
STR_CONFIRM: "Gerai" STR_CONFIRM: "Gerai"
STR_CANCEL: "Atšaukti" STR_CANCEL: "Atšaukti"
STR_CONNECT: "Jungtis" STR_CONNECT: "Jungtis"
+3
View File
@@ -191,6 +191,9 @@ STR_HOME: "« Home"
STR_SELECT: "Wybierz" STR_SELECT: "Wybierz"
STR_SELECTED: "Wybrano" STR_SELECTED: "Wybrano"
STR_TOGGLE: "Zmień" STR_TOGGLE: "Zmień"
STR_TOGGLE_BOOKMARK: "Przełącz zakładkę"
STR_BOOKMARK_REMOVED: "Zakładka usunięta."
STR_HOLD_OPEN_TO_DELETE: "Przytrzymaj Otwórz, aby usunąć"
STR_CONFIRM: "Potwierdź" STR_CONFIRM: "Potwierdź"
STR_CANCEL: "Anuluj" STR_CANCEL: "Anuluj"
STR_CONNECT: "Połącz" STR_CONNECT: "Połącz"
+3
View File
@@ -181,6 +181,9 @@ STR_EXIT: "« Sair"
STR_HOME: "« Início" STR_HOME: "« Início"
STR_SELECT: "Escolher" STR_SELECT: "Escolher"
STR_TOGGLE: "Alternar" STR_TOGGLE: "Alternar"
STR_TOGGLE_BOOKMARK: "Alternar marcador"
STR_BOOKMARK_REMOVED: "Marcador removido."
STR_HOLD_OPEN_TO_DELETE: "Mantenha Abrir pressionado para excluir"
STR_CONFIRM: "Confirmar" STR_CONFIRM: "Confirmar"
STR_CANCEL: "Cancelar" STR_CANCEL: "Cancelar"
STR_CONNECT: "Conectar" STR_CONNECT: "Conectar"
+3
View File
@@ -187,6 +187,9 @@ STR_HOME: "« Acasă"
STR_SELECT: "Selectează" STR_SELECT: "Selectează"
STR_SELECTED: "Selectat" STR_SELECTED: "Selectat"
STR_TOGGLE: "Schimbă" STR_TOGGLE: "Schimbă"
STR_TOGGLE_BOOKMARK: "Comută marcajul"
STR_BOOKMARK_REMOVED: "Marcaj eliminat."
STR_HOLD_OPEN_TO_DELETE: "Țineți apăsat Deschideți pentru a șterge"
STR_CONFIRM: "Confirmă" STR_CONFIRM: "Confirmă"
STR_CANCEL: "Anulare" STR_CANCEL: "Anulare"
STR_CONNECT: "Conectare" STR_CONNECT: "Conectare"
+3 -2
View File
@@ -180,8 +180,7 @@ STR_DOWNLOADING: "Загрузка..."
STR_DOWNLOAD_FAILED: "Ошибка загрузки" STR_DOWNLOAD_FAILED: "Ошибка загрузки"
STR_ERROR_MSG: "Ошибка:" STR_ERROR_MSG: "Ошибка:"
STR_UNNAMED: "Без имени" STR_UNNAMED: "Без имени"
STR_HOLD_CONFIRM_TO_DELETE: "Удерживайте ОТКРЫТЬ для удаления закладки" STR_HOLD_OPEN_TO_DELETE: "Удерживайте Открыть для удаления"
STR_BOOKMARK_INSTRUCTIONS: "Удерживайте ВЫБРАТЬ для добавления закладки"
STR_NO_SERVER_URL: "URL сервера не настроен" STR_NO_SERVER_URL: "URL сервера не настроен"
STR_FETCH_FEED_FAILED: "Не удалось получить ленту" STR_FETCH_FEED_FAILED: "Не удалось получить ленту"
STR_PARSE_FEED_FAILED: "Не удалось обработать ленту" STR_PARSE_FEED_FAILED: "Не удалось обработать ленту"
@@ -199,6 +198,7 @@ STR_HOME: "« Главная"
STR_SELECT: "Выбрать" STR_SELECT: "Выбрать"
STR_SELECTED: "Выбран" STR_SELECTED: "Выбран"
STR_TOGGLE: "Выбор" STR_TOGGLE: "Выбор"
STR_TOGGLE_BOOKMARK: "Переключить закладку"
STR_CONFIRM: "Подтв." STR_CONFIRM: "Подтв."
STR_CANCEL: "Отмена" STR_CANCEL: "Отмена"
STR_CONNECT: "Подкл." STR_CONNECT: "Подкл."
@@ -261,6 +261,7 @@ STR_SUNLIGHT_FADING_FIX: "Компенсация выцветания"
STR_REMAP_FRONT_BUTTONS: "Переназначить передние кнопки" STR_REMAP_FRONT_BUTTONS: "Переназначить передние кнопки"
STR_BOOKMARKS: "Закладки" STR_BOOKMARKS: "Закладки"
STR_BOOKMARK_ADDED: "Закладка добавлена" STR_BOOKMARK_ADDED: "Закладка добавлена"
STR_BOOKMARK_REMOVED: "Закладка удалена"
STR_OPDS_BROWSER: "OPDS браузер" STR_OPDS_BROWSER: "OPDS браузер"
STR_SEARCH: "Поиск" STR_SEARCH: "Поиск"
STR_COVER_CUSTOM: "Обложка + Свой" STR_COVER_CUSTOM: "Обложка + Свой"
+3 -2
View File
@@ -176,8 +176,7 @@ STR_DOWNLOADING: "Sťahovanie..."
STR_DOWNLOAD_FAILED: "Sťahovanie zlyhalo" STR_DOWNLOAD_FAILED: "Sťahovanie zlyhalo"
STR_ERROR_MSG: "Chyba:" STR_ERROR_MSG: "Chyba:"
STR_UNNAMED: "Nepomenované" STR_UNNAMED: "Nepomenované"
STR_HOLD_CONFIRM_TO_DELETE: "Podrž potvrdiť pre vymazanie" STR_HOLD_OPEN_TO_DELETE: "Podržte Otvoriť pre vymazanie"
STR_BOOKMARK_INSTRUCTIONS: "Podrž tlačidlo Potvrdiť pre vytvorenie záložky."
STR_NO_SERVER_URL: "Nie je nakonfigurovaná URL adresa servera" STR_NO_SERVER_URL: "Nie je nakonfigurovaná URL adresa servera"
STR_FETCH_FEED_FAILED: "Načítanie kanála zlyhalo" STR_FETCH_FEED_FAILED: "Načítanie kanála zlyhalo"
STR_PARSE_FEED_FAILED: "Spracovanie kanála zlyhalo" STR_PARSE_FEED_FAILED: "Spracovanie kanála zlyhalo"
@@ -195,6 +194,7 @@ STR_HOME: "« Domov"
STR_SELECT: "Vybrať" STR_SELECT: "Vybrať"
STR_SELECTED: "Vybrané" STR_SELECTED: "Vybrané"
STR_TOGGLE: "Prepnúť" STR_TOGGLE: "Prepnúť"
STR_TOGGLE_BOOKMARK: "Prepnúť záložku"
STR_CONFIRM: "Potvrdiť" STR_CONFIRM: "Potvrdiť"
STR_CANCEL: "Zrušiť" STR_CANCEL: "Zrušiť"
STR_CONNECT: "Pripojiť" STR_CONNECT: "Pripojiť"
@@ -257,6 +257,7 @@ STR_SUNLIGHT_FADING_FIX: "Oprava blednutia na slnku"
STR_REMAP_FRONT_BUTTONS: "Premapovať predné tlačidlá" STR_REMAP_FRONT_BUTTONS: "Premapovať predné tlačidlá"
STR_BOOKMARKS: "Záložky" STR_BOOKMARKS: "Záložky"
STR_BOOKMARK_ADDED: "Záložka pridaná." STR_BOOKMARK_ADDED: "Záložka pridaná."
STR_BOOKMARK_REMOVED: "Záložka odstránená."
STR_OPDS_BROWSER: "Prehliadač OPDS" STR_OPDS_BROWSER: "Prehliadač OPDS"
STR_SEARCH: "Hľadať" STR_SEARCH: "Hľadať"
STR_COVER_CUSTOM: "Obálka + Vlastné" STR_COVER_CUSTOM: "Obálka + Vlastné"
+3
View File
@@ -184,6 +184,9 @@ STR_HOME: "« Domov"
STR_SELECT: "Izberi" STR_SELECT: "Izberi"
STR_SELECTED: "Izbrano" STR_SELECTED: "Izbrano"
STR_TOGGLE: "Preklopi" STR_TOGGLE: "Preklopi"
STR_TOGGLE_BOOKMARK: "Preklopi zaznamek"
STR_BOOKMARK_REMOVED: "Zaznamek odstranjen."
STR_HOLD_OPEN_TO_DELETE: "Držite Odpri za brisanje"
STR_CONFIRM: "Potrdi" STR_CONFIRM: "Potrdi"
STR_CANCEL: "Prekliči" STR_CANCEL: "Prekliči"
STR_CONNECT: "Poveži" STR_CONNECT: "Poveži"
+3 -2
View File
@@ -178,8 +178,7 @@ STR_DOWNLOADING: "Descargando..."
STR_DOWNLOAD_FAILED: "Fallo de descarga" STR_DOWNLOAD_FAILED: "Fallo de descarga"
STR_ERROR_MSG: "Error:" STR_ERROR_MSG: "Error:"
STR_UNNAMED: "Sin nombre" STR_UNNAMED: "Sin nombre"
STR_HOLD_CONFIRM_TO_DELETE: "Mantenga pulsado Confirmar para borrar" STR_HOLD_OPEN_TO_DELETE: "Mantenga pulsado Abrir para borrar"
STR_BOOKMARK_INSTRUCTIONS: "Mantenga pulsado Confirmar en el lector para crear un marcador."
STR_NO_SERVER_URL: "No se configuró URL de servidor" STR_NO_SERVER_URL: "No se configuró URL de servidor"
STR_FETCH_FEED_FAILED: "Fallo al obtener el feed" STR_FETCH_FEED_FAILED: "Fallo al obtener el feed"
STR_PARSE_FEED_FAILED: "Fallo al procesar el feed" STR_PARSE_FEED_FAILED: "Fallo al procesar el feed"
@@ -197,6 +196,7 @@ STR_HOME: "« Inicio"
STR_SELECT: "Selecc." STR_SELECT: "Selecc."
STR_SELECTED: "Seleccionado" STR_SELECTED: "Seleccionado"
STR_TOGGLE: "Cambiar" STR_TOGGLE: "Cambiar"
STR_TOGGLE_BOOKMARK: "Alternar marcador"
STR_CONFIRM: "Confirmar" STR_CONFIRM: "Confirmar"
STR_CANCEL: "Cancelar" STR_CANCEL: "Cancelar"
STR_CONNECT: "Conectar" STR_CONNECT: "Conectar"
@@ -260,6 +260,7 @@ STR_QUICK_RESUME_TIMEOUT: "Reanudación rápida tras tiempo"
STR_REMAP_FRONT_BUTTONS: "Reconfigurar botones frontales" STR_REMAP_FRONT_BUTTONS: "Reconfigurar botones frontales"
STR_BOOKMARKS: "Marcadores" STR_BOOKMARKS: "Marcadores"
STR_BOOKMARK_ADDED: "Marcador añadido." STR_BOOKMARK_ADDED: "Marcador añadido."
STR_BOOKMARK_REMOVED: "Marcador eliminado."
STR_OPDS_BROWSER: "Navegador OPDS" STR_OPDS_BROWSER: "Navegador OPDS"
STR_SEARCH: "Buscar" STR_SEARCH: "Buscar"
STR_COVER_CUSTOM: "Portada + Pers." STR_COVER_CUSTOM: "Portada + Pers."
+3 -2
View File
@@ -176,8 +176,7 @@ STR_DOWNLOADING: "Laddar ner…"
STR_DOWNLOAD_FAILED: "Nedladdning misslyckades" STR_DOWNLOAD_FAILED: "Nedladdning misslyckades"
STR_ERROR_MSG: "Fel:" STR_ERROR_MSG: "Fel:"
STR_UNNAMED: "Ej namngiven" STR_UNNAMED: "Ej namngiven"
STR_HOLD_CONFIRM_TO_DELETE: "Håll ned Bekräfta för att radera" STR_HOLD_OPEN_TO_DELETE: "Håll ned Öppna för att radera"
STR_BOOKMARK_INSTRUCTIONS: "Håll Bekräfta i läsaren för att skapa ett bokmärke."
STR_NO_SERVER_URL: "Ingen serveradress konfigurerad" STR_NO_SERVER_URL: "Ingen serveradress konfigurerad"
STR_FETCH_FEED_FAILED: "Misslyckades att hämta flöde" STR_FETCH_FEED_FAILED: "Misslyckades att hämta flöde"
STR_PARSE_FEED_FAILED: "Misslyckades att analysera flöde" STR_PARSE_FEED_FAILED: "Misslyckades att analysera flöde"
@@ -195,6 +194,7 @@ STR_HOME: "« Hem"
STR_SELECT: "Välj " STR_SELECT: "Välj "
STR_SELECTED: "Vald" STR_SELECTED: "Vald"
STR_TOGGLE: "Växla" STR_TOGGLE: "Växla"
STR_TOGGLE_BOOKMARK: "Växla bokmärke"
STR_CONFIRM: "Bekräfta" STR_CONFIRM: "Bekräfta"
STR_CANCEL: "Avbryt" STR_CANCEL: "Avbryt"
STR_CONNECT: "Anslut" STR_CONNECT: "Anslut"
@@ -258,6 +258,7 @@ STR_QUICK_RESUME_TIMEOUT: "Snabb återupptagning efter timeout"
STR_REMAP_FRONT_BUTTONS: "Ändra frontknappar" STR_REMAP_FRONT_BUTTONS: "Ändra frontknappar"
STR_BOOKMARKS: "Bokmärken" STR_BOOKMARKS: "Bokmärken"
STR_BOOKMARK_ADDED: "Bokmärke tillagt." STR_BOOKMARK_ADDED: "Bokmärke tillagt."
STR_BOOKMARK_REMOVED: "Bokmärke borttaget."
STR_OPDS_BROWSER: "OPDS-webbläsare" STR_OPDS_BROWSER: "OPDS-webbläsare"
STR_SEARCH: "Sök" STR_SEARCH: "Sök"
STR_COVER_CUSTOM: "Omslag + Valfri" STR_COVER_CUSTOM: "Omslag + Valfri"
+3
View File
@@ -181,6 +181,9 @@ STR_EXIT: "« Çıkış"
STR_HOME: "« Ana Sayfa" STR_HOME: "« Ana Sayfa"
STR_SELECT: "Seç" STR_SELECT: "Seç"
STR_TOGGLE: "Değiştir" STR_TOGGLE: "Değiştir"
STR_TOGGLE_BOOKMARK: "Yer imini değiştir"
STR_BOOKMARK_REMOVED: "Yer imi kaldırıldı."
STR_HOLD_OPEN_TO_DELETE: "Silmek için Aç düğmesini basılı tutun"
STR_CONFIRM: "Onayla" STR_CONFIRM: "Onayla"
STR_CANCEL: "İptal" STR_CANCEL: "İptal"
STR_CONNECT: "Bağlan" STR_CONNECT: "Bağlan"
+3 -2
View File
@@ -176,8 +176,7 @@ STR_DOWNLOADING: "Завантаження..."
STR_DOWNLOAD_FAILED: "Завантаження не вдалося" STR_DOWNLOAD_FAILED: "Завантаження не вдалося"
STR_ERROR_MSG: "Помилка:" STR_ERROR_MSG: "Помилка:"
STR_UNNAMED: "Без назви" STR_UNNAMED: "Без назви"
STR_HOLD_CONFIRM_TO_DELETE: "Утрим. Підтвердити, щоб Видалити" STR_HOLD_OPEN_TO_DELETE: "Утримуйте Відкрити, щоб видалити"
STR_BOOKMARK_INSTRUCTIONS: "Утрим. Підтвердити в книзі, щоб створ. закладку"
STR_NO_SERVER_URL: "URL сервера не налаштовано" STR_NO_SERVER_URL: "URL сервера не налаштовано"
STR_FETCH_FEED_FAILED: "Не вдалося отримати стрічку" STR_FETCH_FEED_FAILED: "Не вдалося отримати стрічку"
STR_PARSE_FEED_FAILED: "Не вдалося розпарсити стрічку" STR_PARSE_FEED_FAILED: "Не вдалося розпарсити стрічку"
@@ -195,6 +194,7 @@ STR_HOME: "« Додому"
STR_SELECT: "Вибрати" STR_SELECT: "Вибрати"
STR_SELECTED: "Вибрано" STR_SELECTED: "Вибрано"
STR_TOGGLE: "Обрати" STR_TOGGLE: "Обрати"
STR_TOGGLE_BOOKMARK: "Перемкнути закладку"
STR_CONFIRM: "Підтвердити" STR_CONFIRM: "Підтвердити"
STR_CANCEL: "Скасувати" STR_CANCEL: "Скасувати"
STR_CONNECT: "Приєдн." STR_CONNECT: "Приєдн."
@@ -257,6 +257,7 @@ STR_SUNLIGHT_FADING_FIX: "Виправлення вицвітання на со
STR_REMAP_FRONT_BUTTONS: "Налаштувати передні кнопки" STR_REMAP_FRONT_BUTTONS: "Налаштувати передні кнопки"
STR_BOOKMARKS: "Закладки" STR_BOOKMARKS: "Закладки"
STR_BOOKMARK_ADDED: "Закладку додано" STR_BOOKMARK_ADDED: "Закладку додано"
STR_BOOKMARK_REMOVED: "Закладку видалено"
STR_OPDS_BROWSER: "Браузер OPDS" STR_OPDS_BROWSER: "Браузер OPDS"
STR_SEARCH: "Пошук" STR_SEARCH: "Пошук"
STR_COVER_CUSTOM: "Обкл. + власне" STR_COVER_CUSTOM: "Обкл. + власне"
+3 -2
View File
@@ -180,8 +180,7 @@ STR_DOWNLOADING: "S'està baixant..."
STR_DOWNLOAD_FAILED: "Ha fallat la baixada" STR_DOWNLOAD_FAILED: "Ha fallat la baixada"
STR_ERROR_MSG: "Error:" STR_ERROR_MSG: "Error:"
STR_UNNAMED: "Sense nom" STR_UNNAMED: "Sense nom"
STR_HOLD_CONFIRM_TO_DELETE: "Manteniu premut Confirma per esborrar" STR_HOLD_OPEN_TO_DELETE: "Manteniu premut Obre per esborrar"
STR_BOOKMARK_INSTRUCTIONS: "Manteniu premut Confirma al lector per crear un punt de llibre."
STR_NO_SERVER_URL: "No s'ha configurat cap URL de servidor" STR_NO_SERVER_URL: "No s'ha configurat cap URL de servidor"
STR_FETCH_FEED_FAILED: "Ha fallat l'obtenció del feed" STR_FETCH_FEED_FAILED: "Ha fallat l'obtenció del feed"
STR_PARSE_FEED_FAILED: "Ha fallat l'anàlisi del feed" STR_PARSE_FEED_FAILED: "Ha fallat l'anàlisi del feed"
@@ -197,6 +196,7 @@ STR_HOME: "« Inici"
STR_SELECT: "Selecciona" STR_SELECT: "Selecciona"
STR_SELECTED: "Seleccionat" STR_SELECTED: "Seleccionat"
STR_TOGGLE: "Canvia" STR_TOGGLE: "Canvia"
STR_TOGGLE_BOOKMARK: "Canvia punt de llibre"
STR_CONFIRM: "Confirma" STR_CONFIRM: "Confirma"
STR_CANCEL: "Cancel·la" STR_CANCEL: "Cancel·la"
STR_CONNECT: "Connecta" STR_CONNECT: "Connecta"
@@ -240,6 +240,7 @@ STR_SUNLIGHT_FADING_FIX: "Correcció de l'esvaïment pel sol"
STR_REMAP_FRONT_BUTTONS: "Reassigna els botons frontals" STR_REMAP_FRONT_BUTTONS: "Reassigna els botons frontals"
STR_BOOKMARKS: "Punts de llibre" STR_BOOKMARKS: "Punts de llibre"
STR_BOOKMARK_ADDED: "S'ha afegit el punt de llibre." STR_BOOKMARK_ADDED: "S'ha afegit el punt de llibre."
STR_BOOKMARK_REMOVED: "S'ha eliminat el punt de llibre."
STR_OPDS_BROWSER: "Navegador OPDS" STR_OPDS_BROWSER: "Navegador OPDS"
STR_COVER_CUSTOM: "Portada + Personalitzat" STR_COVER_CUSTOM: "Portada + Personalitzat"
STR_QUICK_RESUME: "Represa ràpida" STR_QUICK_RESUME: "Represa ràpida"
+3 -2
View File
@@ -176,8 +176,7 @@ STR_DOWNLOADING: "Đang tải về..."
STR_DOWNLOAD_FAILED: "Tải về thất bại" STR_DOWNLOAD_FAILED: "Tải về thất bại"
STR_ERROR_MSG: "Lỗi:" STR_ERROR_MSG: "Lỗi:"
STR_UNNAMED: "Không tên" STR_UNNAMED: "Không tên"
STR_HOLD_CONFIRM_TO_DELETE: "Giữ Xác nhận để xóa" STR_HOLD_OPEN_TO_DELETE: "Giữ Mở để xóa"
STR_BOOKMARK_INSTRUCTIONS: "Giữ Xác nhận trong trình đọc để tạo dấu trang."
STR_NO_SERVER_URL: "Chưa cấu hình URL máy chủ" STR_NO_SERVER_URL: "Chưa cấu hình URL máy chủ"
STR_FETCH_FEED_FAILED: "Không tải được nguồn cấp" STR_FETCH_FEED_FAILED: "Không tải được nguồn cấp"
STR_PARSE_FEED_FAILED: "Không phân tích được nguồn cấp" STR_PARSE_FEED_FAILED: "Không phân tích được nguồn cấp"
@@ -195,6 +194,7 @@ STR_HOME: "« Thư viện"
STR_SELECT: "Chọn" STR_SELECT: "Chọn"
STR_SELECTED: "Đã chọn" STR_SELECTED: "Đã chọn"
STR_TOGGLE: "Bật/Tắt" STR_TOGGLE: "Bật/Tắt"
STR_TOGGLE_BOOKMARK: "Bật/tắt dấu trang"
STR_CONFIRM: "Xác nhận" STR_CONFIRM: "Xác nhận"
STR_CANCEL: "Hủy" STR_CANCEL: "Hủy"
STR_CONNECT: "Kết nối" STR_CONNECT: "Kết nối"
@@ -257,6 +257,7 @@ STR_SUNLIGHT_FADING_FIX: "Khắc phục mờ dưới nắng"
STR_REMAP_FRONT_BUTTONS: "Gán lại nút mặt trước" STR_REMAP_FRONT_BUTTONS: "Gán lại nút mặt trước"
STR_BOOKMARKS: "Dấu trang" STR_BOOKMARKS: "Dấu trang"
STR_BOOKMARK_ADDED: "Đã thêm dấu trang." STR_BOOKMARK_ADDED: "Đã thêm dấu trang."
STR_BOOKMARK_REMOVED: "Đã xóa dấu trang."
STR_OPDS_BROWSER: "Trình duyệt OPDS" STR_OPDS_BROWSER: "Trình duyệt OPDS"
STR_SEARCH: "Tìm kiếm" STR_SEARCH: "Tìm kiếm"
STR_COVER_CUSTOM: "Ảnh bìa + Tùy chỉnh" STR_COVER_CUSTOM: "Ảnh bìa + Tùy chỉnh"
+2 -2
View File
@@ -238,8 +238,8 @@ class CrossPointSettings {
// Long-press page turn button behavior // Long-press page turn button behavior
uint8_t longPressButtonBehavior = OFF; uint8_t longPressButtonBehavior = OFF;
// Long-press Confirm function in EPUB reader (cycles through LONG_PRESS_MENU_FUNCTION values). // Long-press Confirm function in EPUB reader (cycles through LONG_PRESS_MENU_FUNCTION values).
// Defaults to Bookmark to preserve the upstream long-press-Confirm-adds-bookmark behavior. // Defaults to Disabled so shortcut-based bookmark toggling remains opt-in.
uint8_t longPressMenuFunction = LP_MENU_BOOKMARK; uint8_t longPressMenuFunction = LP_MENU_DISABLED;
// UI Theme // UI Theme
uint8_t uiTheme = LYRA; uint8_t uiTheme = LYRA;
// Sunlight fading compensation // Sunlight fading compensation
+6
View File
@@ -423,6 +423,9 @@ bool JsonSettingsIO::saveBookmarks(const std::vector<BookmarkEntry>& bookmarks,
obj["xpath"] = bookmark.xpath; obj["xpath"] = bookmark.xpath;
obj["percentage"] = bookmark.percentage; obj["percentage"] = bookmark.percentage;
obj["summary"] = bookmark.summary; obj["summary"] = bookmark.summary;
obj["si"] = bookmark.computedSpineIndex;
obj["pc"] = bookmark.computedChapterPageCount;
obj["pp"] = bookmark.computedChapterProgress;
} }
String json; String json;
@@ -447,6 +450,9 @@ bool JsonSettingsIO::loadBookmarks(std::vector<BookmarkEntry>& bookmarks, const
bookmark.xpath = obj["xpath"] | std::string(""); bookmark.xpath = obj["xpath"] | std::string("");
bookmark.percentage = obj["percentage"] | static_cast<float>(0); bookmark.percentage = obj["percentage"] | static_cast<float>(0);
bookmark.summary = obj["summary"] | std::string(""); bookmark.summary = obj["summary"] | std::string("");
bookmark.computedSpineIndex = obj["si"] | static_cast<uint16_t>(0);
bookmark.computedChapterPageCount = obj["pc"] | static_cast<uint16_t>(0);
bookmark.computedChapterProgress = obj["pp"] | static_cast<uint16_t>(0);
} }
LOG_DBG("BKM", "Loaded %zu bookmarks from file", bookmarks.size()); LOG_DBG("BKM", "Loaded %zu bookmarks from file", bookmarks.size());
+99 -27
View File
@@ -41,6 +41,8 @@ namespace {
// pagesPerRefresh now comes from SETTINGS.getRefreshFrequency() // pagesPerRefresh now comes from SETTINGS.getRefreshFrequency()
// pages per minute, first item is 1 to prevent division by zero if accessed // pages per minute, first item is 1 to prevent division by zero if accessed
constexpr int PAGE_TURN_RATES[] = {1, 1, 3, 6, 12}; constexpr int PAGE_TURN_RATES[] = {1, 1, 3, 6, 12};
constexpr size_t initialBookmarkCacheCapacity = 16;
constexpr float bookmarkProgressEpsilon = 0.0001f;
int clampPercent(int percent) { int clampPercent(int percent) {
if (percent < 0) { if (percent < 0) {
@@ -63,6 +65,35 @@ bool isInReadFolder(const std::string& path) {
return path.size() > n && path.compare(0, n, READ_FOLDER) == 0 && path[n] == '/'; return path.size() > n && path.compare(0, n, READ_FOLDER) == 0 && path[n] == '/';
} }
struct ProgressRange {
float start;
float end;
};
ProgressRange getPageProgressRange(const std::shared_ptr<Epub>& epub, const int spineIndex, const int page,
const int pageCount) {
if (pageCount <= 1) {
return {epub->calculateProgress(spineIndex, 0.0f), epub->calculateProgress(spineIndex, 1.0f)};
}
const float step = 1.0f / static_cast<float>(pageCount - 1);
const float anchor = std::clamp(static_cast<float>(page) * step, 0.0f, 1.0f);
const float start = std::max(0.0f, anchor - (step * 0.5f));
const float end = std::min(1.0f, anchor + (step * 0.5f));
return {epub->calculateProgress(spineIndex, start), epub->calculateProgress(spineIndex, end)};
}
bool bookmarkMatchesProgress(const BookmarkEntry& bookmark, const SavedProgressPosition& progress,
const ProgressRange& pageRange) {
if (bookmark.xpath == progress.xpath) {
return true;
}
const float bookmarkProgress = std::clamp(bookmark.percentage, 0.0f, 1.0f);
return bookmarkProgress + bookmarkProgressEpsilon >= pageRange.start &&
bookmarkProgress - bookmarkProgressEpsilon <= pageRange.end;
}
// Pick a non-colliding destination path inside /Read/ for a finished book. // Pick a non-colliding destination path inside /Read/ for a finished book.
// Mirrors the suffixing scheme used elsewhere: "name.epub" -> "name (2).epub", etc. // Mirrors the suffixing scheme used elsewhere: "name.epub" -> "name (2).epub", etc.
std::string buildReadFolderDestination(const std::string& srcPath) { std::string buildReadFolderDestination(const std::string& srcPath) {
@@ -165,6 +196,8 @@ void EpubReaderActivity::onEnter() {
APP_STATE.saveToFile(); APP_STATE.saveToFile();
RECENT_BOOKS.addBook(epub->getPath(), epub->getTitle(), epub->getAuthor(), epub->getThumbBmpPath()); RECENT_BOOKS.addBook(epub->getPath(), epub->getTitle(), epub->getAuthor(), epub->getThumbBmpPath());
loadCachedBookmarks();
// Trigger first update // Trigger first update
requestUpdate(); requestUpdate();
} }
@@ -281,7 +314,7 @@ void EpubReaderActivity::loop() {
const int bookProgressPercent = clampPercent(static_cast<int>(bookProgress + 0.5f)); const int bookProgressPercent = clampPercent(static_cast<int>(bookProgress + 0.5f));
startActivityForResult(std::make_unique<EpubReaderMenuActivity>( startActivityForResult(std::make_unique<EpubReaderMenuActivity>(
renderer, mappedInput, epub->getTitle(), currentPage, totalPages, bookProgressPercent, renderer, mappedInput, epub->getTitle(), currentPage, totalPages, bookProgressPercent,
SETTINGS.orientation, !currentPageFootnotes.empty()), SETTINGS.orientation, !currentPageFootnotes.empty(), !cachedBookmarks.empty()),
[this](const ActivityResult& result) { [this](const ActivityResult& result) {
// Always apply orientation change even if the menu was cancelled // Always apply orientation change even if the menu was cancelled
const auto& menu = std::get<MenuResult>(result.data); const auto& menu = std::get<MenuResult>(result.data);
@@ -500,6 +533,7 @@ void EpubReaderActivity::jumpToPercent(int percent) {
void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction action) { void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction action) {
auto progressChangeResultHandler = [this](const ActivityResult& result) { auto progressChangeResultHandler = [this](const ActivityResult& result) {
loadCachedBookmarks();
if (!result.isCancelled) { if (!result.isCancelled) {
const auto& sync = std::get<ProgressChangeResult>(result.data); const auto& sync = std::get<ProgressChangeResult>(result.data);
if (currentSpineIndex != sync.spineIndex || (section && section->currentPage != sync.page)) { if (currentSpineIndex != sync.spineIndex || (section && section->currentPage != sync.page)) {
@@ -615,6 +649,10 @@ void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction
progressChangeResultHandler); progressChangeResultHandler);
break; break;
} }
case EpubReaderMenuActivity::MenuAction::TOGGLE_BOOKMARK: {
addBookmark();
break;
}
} }
} }
@@ -902,6 +940,8 @@ void EpubReaderActivity::render(RenderLock&& lock) {
return; return;
} }
updateBookmarkFlag();
{ {
auto p = section->loadPageFromSectionFile(); auto p = section->loadPageFromSectionFile();
if (!p) { if (!p) {
@@ -933,7 +973,7 @@ void EpubReaderActivity::render(RenderLock&& lock) {
} }
if (showBookmarkMessage) { if (showBookmarkMessage) {
GUI.drawPopup(renderer, tr(STR_BOOKMARK_ADDED)); GUI.drawPopup(renderer, bookmarkRemoved ? tr(STR_BOOKMARK_REMOVED) : tr(STR_BOOKMARK_ADDED));
} }
} }
@@ -1172,7 +1212,7 @@ void EpubReaderActivity::renderStatusBar() const {
title = epub->getTitle(); title = epub->getTitle();
} }
GUI.drawStatusBar(renderer, bookProgress, currentPage, pageCount, title, 0, textYOffset); GUI.drawStatusBar(renderer, bookProgress, currentPage, pageCount, title, 0, textYOffset, true, currentPageBookmarked);
} }
void EpubReaderActivity::navigateToHref(const std::string& hrefStr, const bool savePosition) { void EpubReaderActivity::navigateToHref(const std::string& hrefStr, const bool savePosition) {
@@ -1234,11 +1274,31 @@ void EpubReaderActivity::restoreSavedPosition() {
requestUpdate(); requestUpdate();
} }
void EpubReaderActivity::loadCachedBookmarks() {
cachedBookmarks.clear();
if (cachedBookmarks.capacity() < initialBookmarkCacheCapacity) {
cachedBookmarks.reserve(initialBookmarkCacheCapacity);
}
if (!epub) {
currentPageBookmarked = false;
return;
}
const std::string bmPath = BookmarkUtil::getBookmarkPath(epub->getPath());
if (Storage.exists(bmPath.c_str())) {
String json = Storage.readFile(bmPath.c_str());
if (!json.isEmpty()) {
JsonSettingsIO::loadBookmarks(cachedBookmarks, json.c_str());
}
}
updateBookmarkFlag();
}
void EpubReaderActivity::addBookmark() { void EpubReaderActivity::addBookmark() {
if (!section || !epub) { if (!section || !epub) {
return; return;
} }
LOG_DBG("ERS", "Adding bookmark at spine %d, page %d", currentSpineIndex, section ? section->currentPage : -1); LOG_DBG("ERS", "Toggle bookmark at spine %d, page %d", currentSpineIndex, section ? section->currentPage : -1);
int currentPage; int currentPage;
int pageCount; int pageCount;
{ {
@@ -1247,45 +1307,57 @@ void EpubReaderActivity::addBookmark() {
currentPage = section->currentPage; currentPage = section->currentPage;
} }
SavedProgressPosition progress = ProgressMapper::toSavedProgress(epub, getCurrentPosition());
const ProgressRange pageRange = getPageProgressRange(epub, currentSpineIndex, currentPage, pageCount);
const size_t bookmarkCountBeforeToggle = cachedBookmarks.size();
cachedBookmarks.erase(
std::remove_if(cachedBookmarks.begin(), cachedBookmarks.end(),
[&](const BookmarkEntry& b) { return bookmarkMatchesProgress(b, progress, pageRange); }),
cachedBookmarks.end());
if (cachedBookmarks.size() != bookmarkCountBeforeToggle) {
bookmarkRemoved = true;
currentPageBookmarked = false;
} else {
std::string pageText; std::string pageText;
if (currentPage >= 0 && currentPage < pageCount) { if (currentPage >= 0 && currentPage < pageCount) {
pageText = section->getTextFromSectionFile(); pageText = section->getTextFromSectionFile();
} }
SavedProgressPosition progress = ProgressMapper::toSavedProgress(epub, getCurrentPosition());
BookmarkEntry entry; BookmarkEntry entry;
entry.percentage = progress.percentage; entry.percentage = progress.percentage;
entry.xpath = progress.xpath; entry.xpath = progress.xpath;
entry.summary = BookmarkUtil::sanitizeBookmarkSummary(pageText); entry.summary = BookmarkUtil::sanitizeBookmarkSummary(pageText);
entry.computedSpineIndex = currentSpineIndex;
entry.computedChapterPageCount = pageCount;
entry.computedChapterProgress = currentPage;
cachedBookmarks.insert(cachedBookmarks.begin(), entry);
bookmarkRemoved = false;
currentPageBookmarked = true;
}
// Add bookmark
const std::string path = BookmarkUtil::getBookmarkPath(epub->getPath()); const std::string path = BookmarkUtil::getBookmarkPath(epub->getPath());
LOG_DBG("ERS", "Bookmark path: %s", path.c_str());
const std::string bookmarksDir = BookmarkUtil::getBookmarksDir(); const std::string bookmarksDir = BookmarkUtil::getBookmarksDir();
Storage.mkdir(bookmarksDir.c_str()); Storage.mkdir(bookmarksDir.c_str());
std::vector<BookmarkEntry> bookmarks; const bool ok = JsonSettingsIO::saveBookmarks(cachedBookmarks, path.c_str());
if (Storage.exists(path.c_str())) { if (!ok) {
LOG_DBG("ERS", "Existing bookmark file found, loading bookmarks"); LOG_ERR("ERS", "Failed to save bookmarks to: %s", path.c_str());
String json = Storage.readFile(path.c_str());
if (!json.isEmpty()) {
JsonSettingsIO::loadBookmarks(bookmarks, json.c_str());
} }
} else {
LOG_DBG("ERS", "No existing bookmark file, starting with empty bookmark list");
}
bookmarks.insert(bookmarks.begin(), entry);
LOG_DBG("ERS", "Saving bookmark to file: %s", path.c_str());
const bool ok = JsonSettingsIO::saveBookmarks(bookmarks, path.c_str());
if (ok) {
showBookmarkMessage = true;
} else {
LOG_ERR("ERS", "Failed to save bookmark to: %s", path.c_str());
}
requestUpdate(); requestUpdate();
} }
void EpubReaderActivity::updateBookmarkFlag() {
if (!section || !epub || cachedBookmarks.empty()) {
currentPageBookmarked = false;
return;
}
SavedProgressPosition progress = ProgressMapper::toSavedProgress(epub, getCurrentPosition());
const ProgressRange pageRange =
getPageProgressRange(epub, currentSpineIndex, section->currentPage, section->pageCount);
currentPageBookmarked = std::any_of(cachedBookmarks.begin(), cachedBookmarks.end(), [&](const BookmarkEntry& b) {
return bookmarkMatchesProgress(b, progress, pageRange);
});
}
ScreenshotInfo EpubReaderActivity::getScreenshotInfo() const { ScreenshotInfo EpubReaderActivity::getScreenshotInfo() const {
ScreenshotInfo info; ScreenshotInfo info;
info.readerType = ScreenshotInfo::ReaderType::Epub; info.readerType = ScreenshotInfo::ReaderType::Epub;
@@ -5,6 +5,7 @@
#include <optional> #include <optional>
#include "BookmarkEntry.h"
#include "EpubReaderMenuActivity.h" #include "EpubReaderMenuActivity.h"
#include "ProgressMapper.h" #include "ProgressMapper.h"
#include "activities/Activity.h" #include "activities/Activity.h"
@@ -34,6 +35,9 @@ class EpubReaderActivity final : public Activity {
bool automaticPageTurnActive = false; bool automaticPageTurnActive = false;
bool showBookmarkMessage = false; bool showBookmarkMessage = false;
bool ignoreNextConfirmRelease = false; bool ignoreNextConfirmRelease = false;
bool currentPageBookmarked = false;
bool bookmarkRemoved = false; // true when last toggle removed (controls popup text)
std::vector<BookmarkEntry> cachedBookmarks;
// Tracks whether this book is currently removed from Recent Books by the // Tracks whether this book is currently removed from Recent Books by the
// removeReadBooksFromRecents feature (set at End-of-Book, cleared if paged back in). // removeReadBooksFromRecents feature (set at End-of-Book, cleared if paged back in).
bool recentsEntryRemoved = false; bool recentsEntryRemoved = false;
@@ -66,7 +70,9 @@ class EpubReaderActivity final : public Activity {
void applyOrientation(uint8_t orientation); void applyOrientation(uint8_t orientation);
void toggleAutoPageTurn(uint8_t selectedPageTurnOption); void toggleAutoPageTurn(uint8_t selectedPageTurnOption);
void pageTurn(bool isForwardTurn); void pageTurn(bool isForwardTurn);
void loadCachedBookmarks();
void addBookmark(); void addBookmark();
void updateBookmarkFlag();
// Footnote navigation // Footnote navigation
void navigateToHref(const std::string& href, bool savePosition = false); void navigateToHref(const std::string& href, bool savePosition = false);
@@ -39,14 +39,6 @@ void EpubReaderBookmarksActivity::onEnter() {
bookmarks.shrink_to_fit(); bookmarks.shrink_to_fit();
} else { } else {
JsonSettingsIO::loadBookmarks(bookmarks, json.c_str()); JsonSettingsIO::loadBookmarks(bookmarks, json.c_str());
// pre-compute bookmark page values for quicker rendering
for (auto& bookmark : bookmarks) {
CrossPointPosition pos = ProgressMapper::toCrossPoint(epub, {bookmark.xpath, bookmark.percentage}, renderer);
bookmark.computedSpineIndex = pos.spineIndex;
bookmark.computedChapterPageCount = pos.totalPages;
bookmark.computedChapterProgress = pos.pageNumber;
}
} }
} else { } else {
LOG_DBG("EPB", "No bookmark file found at %s, starting with empty bookmarks", path.c_str()); LOG_DBG("EPB", "No bookmark file found at %s, starting with empty bookmarks", path.c_str());
@@ -93,6 +85,14 @@ void EpubReaderBookmarksActivity::loop() {
selectorIndex--; selectorIndex--;
} }
if (bookmarks.empty()) {
ActivityResult result;
result.isCancelled = true;
setResult(std::move(result));
finish();
return;
}
requestUpdate(); requestUpdate();
confirmingDelete = DELETE_MODE_OFF; confirmingDelete = DELETE_MODE_OFF;
return; return;
@@ -186,9 +186,12 @@ void EpubReaderBookmarksActivity::render(RenderLock&&) {
auto bookmark = bookmarks.at(confirmingDelete >= DELETE_MODE_DISPLAY ? selectorIndex : index); auto bookmark = bookmarks.at(confirmingDelete >= DELETE_MODE_DISPLAY ? selectorIndex : index);
auto tocIndex = epub->getTocIndexForSpineIndex(bookmark.computedSpineIndex); auto tocIndex = epub->getTocIndexForSpineIndex(bookmark.computedSpineIndex);
auto tocTitle = (tocIndex >= 0) ? (epub->getTocItem(tocIndex)).title : tr(STR_UNNAMED); auto tocTitle = (tocIndex >= 0) ? (epub->getTocItem(tocIndex)).title : tr(STR_UNNAMED);
return std::to_string((int)(std::clamp(bookmark.percentage, 0.0f, 1.0f) * 100.0f + 0.5f)) + "% - " + std::string subtitle = std::to_string((int)(std::clamp(bookmark.percentage, 0.0f, 1.0f) * 100.0f + 0.5f)) + "% - ";
std::to_string(bookmark.computedChapterProgress + 1) + "/" + if (bookmark.computedChapterPageCount > 0) {
std::to_string(bookmark.computedChapterPageCount) + " - " + tocTitle; subtitle += std::to_string(bookmark.computedChapterProgress + 1) + "/" +
std::to_string(bookmark.computedChapterPageCount) + " - ";
}
return subtitle + tocTitle;
}; };
const auto getBookmarkIcon = [isPortrait](int index) { const auto getBookmarkIcon = [isPortrait](int index) {
// only enabled icon in portrait mode due to limitation with rotating icons for other orientations // only enabled icon in portrait mode due to limitation with rotating icons for other orientations
@@ -208,11 +211,8 @@ void EpubReaderBookmarksActivity::render(RenderLock&&) {
getBookmarkTitle, getBookmarkSubtitle, getBookmarkIcon); getBookmarkTitle, getBookmarkSubtitle, getBookmarkIcon);
GUI.drawHelpText(renderer, Rect{contentX, pageHeight - hintGutterBottom, contentWidth, LINE_HEIGHT}, GUI.drawHelpText(renderer, Rect{contentX, pageHeight - hintGutterBottom, contentWidth, LINE_HEIGHT},
tr(STR_HOLD_CONFIRM_TO_DELETE)); tr(STR_HOLD_OPEN_TO_DELETE));
} }
} else {
GUI.drawHelpText(renderer, Rect{contentX, LINE_HEIGHT * 2, contentWidth, LINE_HEIGHT},
tr(STR_BOOKMARK_INSTRUCTIONS));
} }
const auto backLabel = confirmingDelete >= DELETE_MODE_DISPLAY ? tr(STR_CANCEL) : tr(STR_BACK); const auto backLabel = confirmingDelete >= DELETE_MODE_DISPLAY ? tr(STR_CANCEL) : tr(STR_BACK);
@@ -10,23 +10,27 @@
EpubReaderMenuActivity::EpubReaderMenuActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, EpubReaderMenuActivity::EpubReaderMenuActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
const std::string& title, const int currentPage, const int totalPages, const std::string& title, const int currentPage, const int totalPages,
const int bookProgressPercent, const uint8_t currentOrientation, const int bookProgressPercent, const uint8_t currentOrientation,
const bool hasFootnotes) const bool hasFootnotes, const bool hasBookmarks)
: Activity("EpubReaderMenu", renderer, mappedInput), : Activity("EpubReaderMenu", renderer, mappedInput),
menuItems(buildMenuItems(hasFootnotes)), menuItems(buildMenuItems(hasFootnotes, hasBookmarks)),
title(title), title(title),
pendingOrientation(currentOrientation), pendingOrientation(currentOrientation),
currentPage(currentPage), currentPage(currentPage),
totalPages(totalPages), totalPages(totalPages),
bookProgressPercent(bookProgressPercent) {} bookProgressPercent(bookProgressPercent) {}
std::vector<EpubReaderMenuActivity::MenuItem> EpubReaderMenuActivity::buildMenuItems(bool hasFootnotes) { std::vector<EpubReaderMenuActivity::MenuItem> EpubReaderMenuActivity::buildMenuItems(bool hasFootnotes,
bool hasBookmarks) {
std::vector<MenuItem> items; std::vector<MenuItem> items;
items.reserve(11); items.reserve(12);
items.push_back({MenuAction::SELECT_CHAPTER, StrId::STR_SELECT_CHAPTER}); items.push_back({MenuAction::SELECT_CHAPTER, StrId::STR_SELECT_CHAPTER});
if (hasFootnotes) { if (hasFootnotes) {
items.push_back({MenuAction::FOOTNOTES, StrId::STR_FOOTNOTES}); items.push_back({MenuAction::FOOTNOTES, StrId::STR_FOOTNOTES});
} }
if (hasBookmarks) {
items.push_back({MenuAction::BOOKMARKS, StrId::STR_BOOKMARKS}); items.push_back({MenuAction::BOOKMARKS, StrId::STR_BOOKMARKS});
}
items.push_back({MenuAction::TOGGLE_BOOKMARK, StrId::STR_TOGGLE_BOOKMARK});
items.push_back({MenuAction::ROTATE_SCREEN, StrId::STR_ORIENTATION}); items.push_back({MenuAction::ROTATE_SCREEN, StrId::STR_ORIENTATION});
items.push_back({MenuAction::AUTO_PAGE_TURN, StrId::STR_AUTO_TURN_PAGES_PER_MIN}); items.push_back({MenuAction::AUTO_PAGE_TURN, StrId::STR_AUTO_TURN_PAGES_PER_MIN});
items.push_back({MenuAction::GO_TO_PERCENT, StrId::STR_GO_TO_PERCENT}); items.push_back({MenuAction::GO_TO_PERCENT, StrId::STR_GO_TO_PERCENT});
@@ -18,6 +18,7 @@ class EpubReaderMenuActivity final : public Activity {
AUTO_PAGE_TURN, AUTO_PAGE_TURN,
ROTATE_SCREEN, ROTATE_SCREEN,
BOOKMARKS, BOOKMARKS,
TOGGLE_BOOKMARK,
SCREENSHOT, SCREENSHOT,
DISPLAY_QR, DISPLAY_QR,
GO_HOME, GO_HOME,
@@ -27,7 +28,7 @@ class EpubReaderMenuActivity final : public Activity {
explicit EpubReaderMenuActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, const std::string& title, explicit EpubReaderMenuActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, const std::string& title,
const int currentPage, const int totalPages, const int bookProgressPercent, const int currentPage, const int totalPages, const int bookProgressPercent,
const uint8_t currentOrientation, const bool hasFootnotes); const uint8_t currentOrientation, const bool hasFootnotes, bool hasBookmarks);
void onEnter() override; void onEnter() override;
void onExit() override; void onExit() override;
@@ -40,7 +41,7 @@ class EpubReaderMenuActivity final : public Activity {
StrId labelId; StrId labelId;
}; };
static std::vector<MenuItem> buildMenuItems(bool hasFootnotes); static std::vector<MenuItem> buildMenuItems(bool hasFootnotes, bool hasBookmarks);
// Fixed menu layout // Fixed menu layout
const std::vector<MenuItem> menuItems; const std::vector<MenuItem> menuItems;
+5
View File
@@ -10,3 +10,8 @@ static const uint8_t BookmarkIcon[] = {
0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x01, 0xC0, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x01, 0xC0, 0x00, 0x00, 0x03, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
// size: 16x16
static const uint8_t BookmarkStatusIcon[] = {0x0F, 0xF8, 0x0F, 0xF8, 0x0F, 0xF8, 0x0F, 0xF8, 0x0F, 0xF8, 0x0F,
0xF8, 0x0F, 0xF8, 0x0F, 0xF8, 0x0F, 0xF8, 0x0F, 0xF8, 0x0F, 0xF8,
0x0F, 0x78, 0x0E, 0x38, 0x0C, 0x18, 0x08, 0x08, 0x00, 0x00};
+37 -5
View File
@@ -13,6 +13,7 @@
#include "I18n.h" #include "I18n.h"
#include "RecentBooksStore.h" #include "RecentBooksStore.h"
#include "components/UITheme.h" #include "components/UITheme.h"
#include "components/icons/bookmark.h"
#include "fontIds.h" #include "fontIds.h"
// Internal constants // Internal constants
@@ -20,6 +21,27 @@ namespace {
constexpr int homeMenuMargin = 20; constexpr int homeMenuMargin = 20;
constexpr int homeMarginTop = 30; constexpr int homeMarginTop = 30;
constexpr int subtitleY = 738; constexpr int subtitleY = 738;
constexpr int bookmarkStatusIconWidth = 16;
constexpr int bookmarkStatusIconHeight = 14;
constexpr int bookmarkStatusIconGap = 4;
constexpr int bookmarkStatusIconTopCrop = 2;
bool statusBarTextLaneVisible() {
return SETTINGS.statusBarChapterPageCount || SETTINGS.statusBarBookProgressPercentage ||
SETTINGS.statusBarTitle != CrossPointSettings::STATUS_BAR_TITLE::HIDE_TITLE || SETTINGS.statusBarBattery ||
(SETTINGS.statusBarClock && halClock.isAvailable());
}
void drawBookmarkStatusIcon(const GfxRenderer& renderer, const int x, const int y) {
constexpr int bytesPerRow = bookmarkStatusIconWidth / 8;
for (int row = 0; row < bookmarkStatusIconHeight; ++row) {
for (int col = 0; col < bookmarkStatusIconWidth; ++col) {
const uint8_t byte = BookmarkStatusIcon[(row + bookmarkStatusIconTopCrop) * bytesPerRow + col / 8];
const uint8_t mask = 1U << (7 - (col % 8));
renderer.drawPixel(x + col, y + row, (byte & mask) != 0);
}
}
}
} // namespace } // namespace
@@ -727,11 +749,12 @@ void BaseTheme::fillPopupProgress(const GfxRenderer& renderer, const Rect& layou
void BaseTheme::drawStatusBar(GfxRenderer& renderer, const float bookProgress, const int currentPage, void BaseTheme::drawStatusBar(GfxRenderer& renderer, const float bookProgress, const int currentPage,
const int pageCount, std::string title, const int paddingBottom, const int textYOffset, const int pageCount, std::string title, const int paddingBottom, const int textYOffset,
const bool fillMargin) const { const bool fillMargin, const bool isPageBookmarked) const {
auto metrics = UITheme::getInstance().getMetrics(); auto metrics = UITheme::getInstance().getMetrics();
int orientedMarginTop, orientedMarginRight, orientedMarginBottom, orientedMarginLeft; int orientedMarginTop, orientedMarginRight, orientedMarginBottom, orientedMarginLeft;
renderer.getOrientedViewableTRBL(&orientedMarginTop, &orientedMarginRight, &orientedMarginBottom, renderer.getOrientedViewableTRBL(&orientedMarginTop, &orientedMarginRight, &orientedMarginBottom,
&orientedMarginLeft); &orientedMarginLeft);
const bool showStatusBarTextLane = statusBarTextLaneVisible();
// Draw Progress Text // Draw Progress Text
const auto screenHeight = renderer.getScreenHeight(); const auto screenHeight = renderer.getScreenHeight();
@@ -777,14 +800,24 @@ void BaseTheme::drawStatusBar(GfxRenderer& renderer, const float bookProgress, c
renderer.fillRect(barMarginLeft, progressBarY, barWidth, barHeight, true); renderer.fillRect(barMarginLeft, progressBarY, barWidth, barHeight, true);
} }
// Draw Bookmark
const int leftClusterX = metrics.statusBarHorizontalMargin + orientedMarginLeft + 1;
const bool showBookmarkIcon = showStatusBarTextLane && isPageBookmarked;
const int bookmarkReserveWidth = showBookmarkIcon ? (bookmarkStatusIconWidth + bookmarkStatusIconGap) : 0;
if (showBookmarkIcon) {
const int bookmarkY = textY + 5;
drawBookmarkStatusIcon(renderer, leftClusterX, bookmarkY);
}
// Draw Battery // Draw Battery
const bool showBatteryPercentage = const bool showBatteryPercentage =
SETTINGS.hideBatteryPercentage == CrossPointSettings::HIDE_BATTERY_PERCENTAGE::HIDE_NEVER; SETTINGS.hideBatteryPercentage == CrossPointSettings::HIDE_BATTERY_PERCENTAGE::HIDE_NEVER;
int leftClusterWidth = bookmarkReserveWidth;
if (SETTINGS.statusBarBattery) { if (SETTINGS.statusBarBattery) {
GUI.drawBatteryLeft(renderer, GUI.drawBatteryLeft(renderer,
Rect{metrics.statusBarHorizontalMargin + orientedMarginLeft + 1, textY, metrics.batteryWidth, Rect{leftClusterX + bookmarkReserveWidth, textY, metrics.batteryWidth, metrics.batteryHeight},
metrics.batteryHeight},
showBatteryPercentage); showBatteryPercentage);
leftClusterWidth += showBatteryPercentage ? 50 : 20;
} }
// Draw Clock (X3 only — DS3231 RTC) // Draw Clock (X3 only — DS3231 RTC)
@@ -808,8 +841,7 @@ void BaseTheme::drawStatusBar(GfxRenderer& renderer, const float bookProgress, c
const int rendererableScreenWidth = const int rendererableScreenWidth =
renderer.getScreenWidth() - (metrics.statusBarHorizontalMargin * 2) - orientedMarginLeft - orientedMarginRight; renderer.getScreenWidth() - (metrics.statusBarHorizontalMargin * 2) - orientedMarginLeft - orientedMarginRight;
const int batterySize = SETTINGS.statusBarBattery ? (showBatteryPercentage ? 50 : 20) : 0; const int titleMarginLeft = leftClusterWidth + 30;
const int titleMarginLeft = batterySize + 30;
const int clockReserve = clockTextWidth > 0 ? (clockTextWidth + 10) : 0; const int clockReserve = clockTextWidth > 0 ? (clockTextWidth + 10) : 0;
const int titleMarginRight = progressTextWidth + clockReserve + 30; const int titleMarginRight = progressTextWidth + clockReserve + 30;
+1 -1
View File
@@ -210,7 +210,7 @@ class BaseTheme {
virtual void fillPopupProgress(const GfxRenderer& renderer, const Rect& layout, const int progress) const; virtual void fillPopupProgress(const GfxRenderer& renderer, const Rect& layout, const int progress) const;
void drawStatusBar(GfxRenderer& renderer, const float bookProgress, const int currentPage, const int pageCount, void drawStatusBar(GfxRenderer& renderer, const float bookProgress, const int currentPage, const int pageCount,
std::string title, const int paddingBottom = 0, const int textYOffset = 0, std::string title, const int paddingBottom = 0, const int textYOffset = 0,
const bool fillMargin = true) const; const bool fillMargin = true, const bool isPageBookmarked = false) const;
void drawHelpText(const GfxRenderer& renderer, Rect rect, const char* label) const; void drawHelpText(const GfxRenderer& renderer, Rect rect, const char* label) const;
virtual void drawTextField(const GfxRenderer& renderer, Rect rect, const int textWidth, bool cursorMode = false, virtual void drawTextField(const GfxRenderer& renderer, Rect rect, const int textWidth, bool cursorMode = false,
int contentStartX = 0, int contentWidth = 0) const; int contentStartX = 0, int contentWidth = 0) const;