# Book's Story — Bookshelf Fork > Форк `Acclorite/book-story` под Bookshelf: audiobookshelf + bookshelf-api + TTS + offline. ## Stack - **Язык:** Kotlin 100% - **UI:** Jetpack Compose + Material3 - **DI:** Dagger Hilt - **БД:** Room - **Настройки:** DataStore - **Сеть:** Retrofit + OkHttp + Kotlinx Serialization - **Аудио:** ExoPlayer (Media3) + MediaSession - **Фоновые задачи:** WorkManager - **Читалка:** собственная Book's Story (jsoup, pdfbox, commonmark) - **minSdk:** 31 - **compileSdk/targetSdk:** 36 ## Goal - Сохранить Material You reader Book's Story. - Добавить поддержку bookshelf-api: библиотеки, поиск, TTS, подкасты, Яндекс/YouTube. - Добавить аудиоплеер для аудиокниг из Audiobookshelf. - Добавить offline-кэш и синхронизацию прогресса. ## Layout - `app/src/main/java/org/dueattendant149/bookshelf/` — основной код - `core/` — crash handler, typealias, commonmark parser - `data/` — repository, Room, DataStore, mappers, parsers, DI - `domain/` — models, use cases, repository interfaces - `presentation/` — ViewModels, MVI screens, navigator - `ui/` — Compose UI - `bookshelf-api` backend находится в `~/bookshelf/bookshelf-api/` (отдельный проект) - Lissen (который дорабатывается параллельно) — `~/bookshelf/lissen/` ## API endpoints - **bookshelf-api:** `http://bookshelf-api:8073` (или `https://tts.dueattendant149.org` после DNS) - `GET /api/v1/books/libraries` - `GET /api/v1/books/{item_id}` - `GET /api/v1/books/library/{id}/search?q=...` - `POST /api/v1/search` - `POST /api/v1/tts`, `GET /api/v1/tts/{job_id}`, `GET /api/v1/tts/{job_id}/download` - `GET /api/v1/tts/engines`, `GET /api/v1/tts/voices` - `POST /api/v1/download`, `GET /api/v1/download/list` - `POST /api/v1/podcasts`, `POST /api/v1/sources/yandex` - **Audiobookshelf (напрямую):** для аудио файлов и обложек - `GET /api/items/{id}/file/{ino}` - `GET /api/items/{id}/download` - `GET /api/items/{id}/cover` ## Git - **origin:** `https://git.dueattendant149.org/Atte149/book-story.git` (Forgejo) - **upstream:** `https://github.com/Acclorite/book-story.git` - **Лицензия:** GPL-3.0-only (как у оригинала) ## Build ```bash ./gradlew :app:assembleDebug ``` ## Current Status - Phase 1: fork + package rename completed. - Phase 2: network layer (Retrofit, OkHttp, bookshelf-api + ABS clients) completed. - Phase 3: data layer (remote Book fields, audio/ebook/cache tables, migration 16→17→18) completed. - Phase 4: domain layer (ServerSettings, RemoteLibrary repository, use cases) completed. - Phase 5a: UI scaffold (server settings screen, RemoteLibrary tab) completed. - Phase 5b: UI/UX polish (icons, string resources, pull-to-refresh, empty/error states) completed. - Phase 5c: server settings validation (health check, derive ABS URL, credential checks) completed. - Phase 6: feature integration on `feature/ui-polish` completed: - RemoteLibrary cache UI (offline-only filter, download/delete, progress indicator). - Remote ebook opening in the existing reader. - Audiobook player with ExoPlayer + MediaSession. - TTS jobs screen via bookshelf-api. - Reading and playback progress sync with periodic WorkManager worker. - Current: `feature/ui-polish` builds and produces `app-debug.apk` (~58 MB). Debug APK uploaded as `KRait-debug-15.apk` to ownCloud + caddy download site. - Polished: PlayerContent and TtsContent labels moved to `strings.xml`; TTS WorkManager observer leak fixed (`TtsModel` removes observer in `onCleared`). - Raised `minSdk` from 26 to 31. - Phase 7 (nav redesign): `MainActivity` tabs = Книги | Аудиокниги | Поиск | Настройки. - `BooksModel` / `AudiobooksModel` extend `RemoteLibraryViewModel` with `mediaTypeFilter`. - Library filter uses name fallback ("Books" / "Audiobooks") because ABS returns `mediaType: "book"` for both. - `SearchModel` + `SearchContent` for global Librarr search (`/api/v1/search`). - `ServerStatusMonitor` (singleton, pings `/health` every 30 s) → auto offline-only when down. - `SettingsScreen` is a root tab — back button hidden when stack size ≤ 1. - Phase 8 (RSVP speed reading): core tokenizer + engine + UI. - `domain/model/rsvp/` — `RsvpToken`, `OrpTable`, `RsvpTokenizer`, `RsvpEngine`. - `presentation/rsvp/` — `RsvpScreen`, `RsvpModel`, `RsvpState/Event/Effect`. - `ui/rsvp/RsvpContent.kt` — word stage with prefix/pivot/suffix, focus guide ▲▼, controls bar. - `ReaderTopBar` got a ⚡ button → `RsvpScreen(bookId)`. - Persists reading progress on close via `UpdateBookUseCase`. - Settings: `rsvpWpm` (100–900), `rsvpFontSize` (24–96 sp), `rsvpPauseOnParagraph`, `rsvpPauseOnChapter`, `rsvpPauseOnLongWords`, `rsvpShowFocusGuide`. - Phase 9 (reader pagination + polish): - Kindle-style paginated reading mode with `HorizontalPager`. - Volume keys (Up/Down) mapped to page navigation via `VolumeKeyWindowCallback`. - Pagination measurement/render synchronization: exact page geometry, real inter-item spacing, image sizing, chapter title extras. - Algorithm improvements: `TextLayoutResult.getLineForVerticalPosition()` split point, better widow/orphan handling. - Phase 10 (bugfixes): - Fixed search crash caused by duplicate LazyColumn keys when search results have empty `guid`. - Fixed `BookCard` intrinsic-measurement crash risk by switching cover to fixed-height vertical variant. - Phase 11 (audio player hardening): - ExoPlayer streaming cache via `SimpleCache` + `CacheDataSource` (512 MB external cache). - Unified `AuthorizationInterceptor` for player, backed by `PlaybackAuthProvider` refreshed from `ServerSettings`. - Current APK: `KRait-debug-27.apk` (1.9.11) on `books.dueattendant149.org/download`. - Next: final APK rebuild after pushing to Forgejo. ## WARNs - **Не пушить в `upstream` (Acclorite/book-story).** Только в `origin` (Forgejo). - **Lissen остаётся живым проектом.** Book's Story fork — отдельная ветка развития. - **Секреты** (ABS token, API keys) хранить только в `.env` / `credentials.json` / DataStore, никогда в коде. - **Package:** `org.dueattendant149.bookshelf`. Все ссылки на `ua.acclorite.book_story` в коде заменены; README оставлен с оригинальными F-Droid ссылками. ## Typical tasks - Добавить новый endpoint bookshelf-api - Расширить `Book` / `BookEntity` для remote полей - Добавить аудиоплеер экран - Добавить TTS экран - Поднять minSdk, добавить новые разрешения - Обновить AGENTS.md при существенных изменениях архитектуры