# 010 — Mobile EPUB Reader **Status:** Implemented **Created:** 2026-06-04 ## Objective Mirror the web reading experience (`frontend/src/pages/ReadingPage.tsx` and `frontend/src/components/reader/*`) inside the Expo app so users can open their uploaded library, read EPUBs with persisted progress and typography settings, and manage bookmarks/highlights — reusing the same Django REST API as the web client. The web EPUB renderer (`react-reader` / epub.js) is DOM-only, so mobile renders EPUBs with `@epubjs-react-native/core`, which runs epub.js inside a `react-native-webview`. This keeps behavior (CFI locations, themes, font controls, TOC, annotations) close to web while staying inside the managed Expo workflow. ## Scope ### Mirrored from web - EPUB rendering with swipe pagination (`flow: "paginated"`). - Resume position and debounced progress save (CFI + percentage). - Table of contents drawer with jump-to-chapter. - Reading settings: theme presets (light/sepia/paper/dark), font family, font size, line spacing — persisted to `/api/reader/settings/`. - Bookmarks: bookmark the current page, list/jump/delete, and create highlights from a text selection. Highlights are re-applied on open (best-effort). ### Deferred (not in this pass) - PDF reading. PDF books show a placeholder pointing to the web reader. - Brightness and orientation-lock controls. - Per-highlight color picker (highlights use a single default color). - App-wide internationalization (web uses `react-i18n-lite`). ## Architecture ``` LibraryScreen (EBook list) | navigate("BookDetail", { ebookId }) v BookDetailScreen ----------------> ReaderScreen ({ ebookId }) | GET /api/books/ebooks/:id/ (format guard) | epub? --------------------- pdf? -> placeholder | EpubReaderView (ReaderProvider) | expo-file-system downloadAsync(file/, Bearer token) -> file:// uri | | onLocationChange -> debounce 800ms -> PATCH progress/ onSelected -> POST bookmarks/ (+ highlight annotation) useReader().toc -> goToLocation(href) settings change -> changeTheme / changeFontSize / changeFontFamily + PATCH /api/reader/settings/ (debounced) ``` The ebook file is downloaded to the app cache with an `Authorization` header (the `/file/` endpoint is JWT-protected) and the local `file://` URI is handed to the renderer — mirroring how the web client downloads a blob rather than using a public/signed URL. ## Mobile changes | File | Role | |------|------| | `mobile/src/api/client.ts` | Adds `apiClient`, `saveTokens`, `loadTokens`, `getApiBaseUrl` | | `mobile/src/api/ebooks.ts` | `/api/books/ebooks/` list/detail/toc + `getFileUrl(id)` | | `mobile/src/api/reader.ts` | Reader settings + per-book progress (mirrors web `api/reader.ts`) | | `mobile/src/api/annotations.ts` | Bookmarks CRUD against `/api/annotations/bookmarks/` | | `mobile/src/types/reader.ts` | `ReadingSettings`, `ReadingProgress` (full reader shapes) | | `mobile/src/types/index.ts` | `AppStackParamList`, `Bookmark`, `CreateMarkerPayload` | | `mobile/src/hooks/useReadingSettings.ts` | Loads + debounced-saves reader settings | | `mobile/src/utils/epubTheme.ts` | Font stacks, theme palettes, `buildEpubTheme()` | | `mobile/src/navigation/AppStack.tsx` | Native stack: Tabs / BookDetail / Reader | | `mobile/src/screens/LibraryScreen.tsx` | Lists user EBooks (`ebooksApi.list`) | | `mobile/src/screens/BookDetailScreen.tsx` | Metadata + start/resume button | | `mobile/src/screens/ReaderScreen.tsx` | Format guard: EPUB view vs PDF placeholder | | `mobile/src/components/reader/EpubReaderView.tsx` | Reader, progress, bookmarks, settings wiring | | `mobile/src/components/reader/ReaderToolbar.tsx` | Title, chapter, progress bar, action buttons | | `mobile/src/components/reader/TocModal.tsx` | Table of contents sheet | | `mobile/src/components/reader/ReadingSettingsModal.tsx` | Theme/font/size/spacing controls | | `mobile/src/components/reader/BookmarksModal.tsx` | Bookmarks & highlights list | | `mobile/App.tsx` | Wraps the tree in `GestureHandlerRootView` | Removed unused scaffolding: `mobile/src/navigation/AppNavigator.tsx`, `mobile/src/navigation/MainTabs.tsx`. ## API contracts (consumed) | Method | Path | Purpose | |--------|------|---------| | GET | `/api/books/ebooks/` | User library list | | GET | `/api/books/ebooks/:id/` | EBook detail (format, progress, cover) | | GET | `/api/books/ebooks/:id/file/` | Stream EPUB bytes (JWT, owner) | | GET/PATCH | `/api/books/ebooks/:id/progress/` | Reading progress (`current_position`, `last_page`, `epub_location`) | | GET/PATCH | `/api/reader/settings/` | Reader typography/theme settings | | GET/POST/DELETE | `/api/annotations/bookmarks/` | Bookmarks & highlights (`ebook`, `epub_cfi`, `chapter_index`, ...) | ## Settings mapping (web -> mobile) | Reader setting | Web (epub.js) | Mobile (`@epubjs-react-native/core`) | |----------------|---------------|--------------------------------------| | `theme` / colors | `themes.register/select` | `changeTheme(buildEpubTheme())` + `defaultTheme` | | `font_size` | `themes.fontSize` | `changeFontSize("Npx")` | | `font_family` | body font-family | `changeFontFamily(stack)` | | `line_height` | body line-height | `buildEpubTheme()` CSS rule | | `margin_width` | gap-based padding | not applied (deferred) | | `brightness` / `orientation_lock` | applied on web | deferred | ## Dependencies added - `@epubjs-react-native/core@1.4.7` - `@epubjs-react-native/expo-file-system@1.1.4` - `react-native-webview@13.12.5` (`react-native-gesture-handler`, `react-native-reanimated`, and `expo-file-system` were already present.) ## Configuration | Env var | Purpose | |---------|---------| | `EXPO_PUBLIC_API_URL` | Backend base URL (e.g. `http://10.0.2.2:8000` on Android emulator, LAN IP on a device) | ## Compatibility notes - The Expo file-system adapter (`@epubjs-react-native/expo-file-system`) depends only on `expo-file-system`, so the reader runs in Expo Go. (The library's bare adapter pulls native `@dr.pogodin/react-native-fs`; that path is not used here.) - React 19 / Expo SDK 52 may surface peer-dependency warnings for the `@epubjs-react-native/*` packages. ## Verification - [ ] Log in; Library lists the user's uploaded EBooks with covers/progress. - [ ] Open an EPUB; it renders and paginates by swipe. - [ ] Reopen a book; it resumes at the last position. - [ ] Change theme/font/size/spacing; the page updates and persists across reopen. - [ ] Open the TOC and jump to a chapter. - [ ] Bookmark the current page; it appears in the bookmarks list and can be re-opened/deleted. - [ ] Select text to create a highlight; it persists and re-renders on reopen. - [ ] Open a PDF book; the placeholder is shown instead of a crash.