Archived
151 lines
6.9 KiB
Markdown
151 lines
6.9 KiB
Markdown
# 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
|
|
|
|
|
<Reader src=file:// fileSystem=useFileSystem flow="paginated" />
|
|
|
|
|
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.
|