6.9 KiB
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.4react-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 onexpo-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.