# Android App — Design & Roadmap This document describes the planned native Android client for Bookhoard: a thin, offline-first reading app that treats the Bookhoard server as its backend. The relationship is the same as any dedicated reader app to its server: the server owns the library, sync, and conflict resolution; the app is a dedicated, mobile-first reading frontend with its own UI, designed independently of the web interface. **Status**: Planning / pre-development **Companion repo**: `bookhoard-app` (separate repository, AGPL-3.0) --- ## 🎯 Product Vision - An **amazing ereader** first and foremost — rendering polish, latency, and reading UX are the product - **Mobile-first UI** designed from scratch for phones; not a wrapper around the web app - **Thin client**: the server remains authoritative for all sync, book matching, and conflict resolution - **v1 formats**: EPUB (ebooks) and CBZ (comics/manga); PDF comes nearly free via the reader toolkit - **Android first**. iOS is a real roadmap item but unscheduled — likely contributor-driven --- ## 🛠 Tech Stack | Concern | Choice | | -------------- | ------------------------------------------------- | | Language | Kotlin | | UI | Jetpack Compose + Material 3 | | Reader engine | [Readium Kotlin toolkit](https://github.com/readium/kotlin-toolkit) | | Local database | Room | | Networking | OkHttp / Retrofit + WebSocket | | Background | WorkManager | | Images | Coil | | Settings | DataStore | ### Why native Android - The quality bar is a first-class native reader app. Page-turn latency, text layout fidelity, PDF rendering (`PdfRenderer`), and comic/manga image pipelines are platform-level strengths — and they are the *hard* parts in a WebView, not the easy parts. - Android-first removes the "share one codebase across two platforms simultaneously" constraint that motivates hybrid stacks. - Solo, AI-assisted development compresses the cost of native (code volume), while native's failure modes (well-documented platform APIs) are far easier to debug — alone or with AI — than cross-framework bridge/plugin bugs. - The target audience is the self-hosted community, best reached via GitHub Releases and F-Droid rather than app-store optimization. ### Alternatives considered - **Capacitor / WebView shell** (the audiobookshelf-app model): excellent when a self-contained SPA already exists; a poor fit here. Bookhoard's web UI is server-rendered HTMX and cannot be packaged, comics rendering in a WebView caps the polish target, and deep offline support fights the shell. - **Flutter**: strong middle ground, but no Readium port and a weaker EPUB/PDF plugin ecosystem than the native toolkits. - **Kotlin Multiplatform**: only pays off with a committed near-term iOS effort. Revisit if iOS becomes active; until then it would constrain v1 for a hypothetical. --- ## 🏗 Architecture Thin, offline-first client. The server API is the contract (see [API Reference](api/api-reference.md) and [WebSocket API](websocket-api.md)). ### Module layout ``` :app Compose UI, navigation, dependency injection :core:domain Pure Kotlin — models, sync logic, use cases (no Android deps) :core:data Room, Retrofit/OkHttp, downloads and file storage :feature:reader Readium navigator integration and reading UI ``` Keeping `:core:domain` free of Android dependencies preserves optionality: a future iOS client, a KMP extraction, or a desktop client can reuse or port the domain logic without touching the UI. ### Offline-first sync flow 1. UI writes go to the local Room mirror **first** (never blocked on network) 2. A WorkManager queue replays changes to the existing REST endpoints (`/api/progress`, `/api/media-items/:id/notes`, `/highlights`, etc.) 3. Conflicts are resolved by the server's existing mechanisms — the client never invents its own merge logic 4. While online, a WebSocket connection receives realtime updates pushed by other devices (web reader, KOReader) 5. Books are downloaded to app storage for fully offline reading, with storage management UI ### Authentication & device identity - **Primary auth: username/password login** via the existing endpoints (`POST /api/auth/login` + refresh). The app is a full user client — browse, collections, ratings, and annotation management all live behind the user JWT, which device tokens cannot reach - After login, the app registers itself as a **device** (`device_type: mobile`) and **self-approves** its registration using its own JWT — approval only requires a logged-in user. The phone then appears on the Devices page with sync attribution, per-device settings, and individually revocable access, with no QR ceremony - Netflix-style QR pairing as a zero-typing sign-in option: post-v1 (see below) --- ## 📖 Reader Engine [Readium](https://github.com/readium/kotlin-toolkit) provides EPUB, PDF, and CBZ through one publication model and navigator — production-hardened by real reading apps. This avoids building and maintaining three renderers. Planned reading features: - Custom fonts (including user-loaded), adjustable margins and line height - Themes including OLED true-black for battery - Paginated and scroll modes; gesture and volume-key page turns - Keep-screen-awake while reading - Highlights, notes, and bookmarks synced via existing APIs (including deleted-annotation restore) - Resume to exact position using EPUB CFI, consistent with universal sync ### Comics & manga UX - RTL reading direction and double-page spreads with correct cover/single-page handling - Per-book reading-mode overrides (a manga library can default to RTL) - Zoom and pan; aggressive preloading of adjacent pages - Webtoon / continuous vertical mode: post-v1 ### Reader settings parity with the web reader The web reader (`web/src/reader/`) is the reference implementation for reading ergonomics — its font selection, reading themes, and highlight system are considered well-designed; only its desktop-oriented presentation is being replaced on mobile. The Android reader should reuse the same settings model (stored in the `reader_settings` table and synced via the settings endpoint) rather than inventing a parallel one: - **Fonts**: the same roster of variable fonts, self-hosted under `/static/fonts/` — Literata (default), Crimson Pro, Source Serif 4, EB Garamond, Libertinus Serif, Noto Serif, Charis SIL, IBM Plex Serif (`FONT_MAP` in `web/src/reader/reader.ts`) - **Typography**: `font_size` (default 18), `line_height` (1.6), `margin_width`, `double_page_spread` - **Themes**: `chrome_theme` (default `tokyo-night`) for app chrome vs `reading_theme`/`reading_mode` for the page surface, plus the fx stack (`fx_brightness`, `fx_contrast`, `fx_invert`) - **Navigation**: `tap_zones_enabled` + `tap_zone_size`, `reading_direction`, `progress_mode` - **Highlights**: per-annotation color (default `#ffd54f`), matching the web palette Settings chosen on one device should follow the user everywhere — mobile changes write back through the same sync. --- ## 🍎 iOS Posture iOS is a real roadmap item but not near-term. The strategy is **not** to pre-pay for it with KMP or a cross-platform framework. Instead: - The documented REST/WebSocket API is the sharing mechanism — a future iOS client is a *new client over the same contract*, never a rewrite of shared logic - A contributor-driven Swift/SwiftUI client is welcome; the server needs no changes to support it --- ## 📦 Distribution & Licensing - **License**: AGPL-3.0, matching the Bookhoard server - **Channels**: GitHub Releases and F-Droid; Play Store optional later --- ## 🚧 Milestones 1. **Scaffold** — app shell, auth + QR device pairing, library browsing, book downloads 2. **EPUB reading** — Readium integration, CFI progress sync, offline-first reading 3. **Annotations** — highlights/notes/bookmarks sync with offline queue 4. **Comics** — CBZ navigator with manga modes (RTL, spreads, zoom) 5. **Polish** — OLED themes, gestures, background sync, storage management --- ## 🔭 Post-v1 Ideas ### QR pairing sign-in (Netflix-style) "Add device" on the web (while logged in) displays a QR code; a fresh app install scans it and is **fully signed in** — no server URL, no password, nothing typed on the phone. - **QR is a full login**: the claim endpoint returns JWT + refresh token (plus the device token for sync identity) - **Typed-code fallback** (GitHub/Netflix device-flow style: app displays a short code, user enters it on the web) for phones with broken cameras or no camera - **KOReader keeps its existing flow unchanged** — no typed-code pairing there; it is already as convenient as it can be - **Use the configured `BASE_URL`, never a detected LAN IP** — if the server is published at `https://public.domain`, pairing must work identically from outside the LAN - Requires small server additions: `pair`/`claim` endpoints backed by single-use pairing sessions with a short TTL (in-memory like `pendingRegistrations`) ### Other ideas - Webtoon / continuous vertical reading mode - Home-screen widgets and app shortcuts ("continue reading") - Text-to-speech - OPDS feed consumption from other servers --- ## Related Documentation - **[API Reference](api/api-reference.md)** - Complete REST API - **[WebSocket API](websocket-api.md)** - Real-time sync events - **[Sync Guide](../user/sync-guide.md)** - How universal sync works - **[Devices API](api/devices/get_devices.md)** - Device registration and approval