Guide for developing and maintaining the Paperand React Native manga reader application
This skill provides comprehensive guidance for working with the Paperand project - an ad-free manga reader built with Expo and React Native, compatible with Paperback extensions.
Paperand is a cross-platform manga reader app that:
| Technology | Purpose |
|---|---|
| Expo SDK 54 | Development framework |
| React Native 0.81 | Cross-platform mobile development |
| TypeScript | Type-safe JavaScript |
| React Navigation 7 | Navigation library (stack + bottom tabs) |
| AsyncStorage | Persistent local storage |
| expo-image | Optimized image loading with caching |
| WebView | Extension runtime for Paperback sources |
| i18n-js | Internationalization |
paperback-android/
āāā App.tsx # Main app entry, providers setup
āāā index.ts # Expo entry point
āāā app.json # Expo configuration
āāā package.json # Dependencies
āāā src/
ā āāā components/ # Reusable UI components
ā ā āāā MangaCard.tsx # Manga cover card component
ā ā āāā ChapterListItem.tsx # Chapter list item
ā ā āāā ExtensionRunner.tsx # WebView extension runtime
ā ā āāā PickerModal.tsx # Custom picker modal
ā ā āāā ...
ā āāā context/ # React Context providers
ā ā āāā ThemeContext.tsx # Theme management
ā ā āāā LibraryContext.tsx # Library & progress state
ā āāā navigation/ # Navigation configuration
ā ā āāā AppNavigator.tsx # Main stack navigator
ā ā āāā BottomTabNavigator.tsx # Bottom tab navigation
ā āāā screens/ # App screens
ā ā āāā LibraryScreen.tsx # User library
ā ā āāā DiscoverScreen.tsx # Browse sources
ā ā āāā SearchScreen.tsx # Multi-source search
ā ā āāā MangaDetailScreen.tsx # Manga details & chapters
ā ā āāā ReaderScreen.tsx # Manga reader (core feature)
ā ā āāā DownloadManagerScreen.tsx # Download management
ā ā āāā ExtensionsScreen.tsx # Manage extensions
ā ā āāā ...
ā āāā services/ # Core business logic
ā ā āāā sourceService.ts # Extension API bridge
ā ā āāā extensionService.ts # Extension management
ā ā āāā cacheService.ts # Image caching
ā ā āāā downloadService.ts # Chapter downloads
ā ā āāā themeService.ts # Theme file parsing
ā ā āāā i18nService.ts # Internationalization
ā ā āāā updateService.ts # App update checks
ā ā āāā ...
ā āāā hooks/ # Custom React hooks
ā āāā types/ # TypeScript type definitions
ā āāā constants/ # App constants
ā āāā locales/ # Translation files (17 languages)
āāā android/ # Android native code
āāā assets/ # Images, icons, fonts
āāā plugins/ # Custom Expo config plugins
āāā credentials/ # Build credentials
# Install dependencies
npm install
# or
bun install
# Start development server
npx expo start
# Run on Android
npm run android
# Run on iOS
npm run ios
# Run on web
npm run web
The app uses a WebView-based extension runtime (ExtensionRunner.tsx) to execute Paperback-compatible extensions. Extensions are JavaScript bundles that provide manga source implementations.
.pbcolors themesAll business logic is encapsulated in service files:
sourceService.ts - Bridge between app and extensionscacheService.ts - Image caching with configurable limitsdownloadService.ts - Background chapter downloadsi18nService.ts - Multi-language supportStack Navigator (AppNavigator)
āāā Bottom Tab Navigator
ā āāā Library
ā āāā Discover
ā āāā History
ā āāā More
āāā MangaDetail
āāā Reader
āāā Search
āāā Extensions
āāā Settings
āāā ...
The app supports 17+ languages. Translation files are in src/locales/ as JSON files.
Adding a new translation:
src/locales/ (e.g., de.json)en.jsoni18nService.tsThe app supports custom themes via .pbcolors files (Paperback theme format).
Theme configuration:
themeService.tsThemeContextOn push to main or manual trigger:
# Android APK
eas build --platform android --profile preview
# iOS
eas build --platform ios --profile preview
src/screens/src/screens/index.tssrc/navigation/AppNavigator.tsxsrc/components/src/components/index.tsThemeContext for stylingsrc/services/useTheme())i18n.t('key') for all user-facing stringsconsole.log (visible in Metro bundler)// Theme & Styles
import { useTheme } from '../context/ThemeContext';
// Library & State
import { useLibrary } from '../context/LibraryContext';
// Navigation
import { useNavigation, useRoute } from '@react-navigation/native';
// Components
import { MangaCard, LoadingIndicator, EmptyState } from '../components';
// Services
import { sourceService } from '../services/sourceService';
import { cacheService } from '../services/cacheService';
// i18n
import { i18n } from '../services/i18nService';
import React from 'react';
import { View, StyleSheet } from 'react-native';
import { useTheme } from '../context/ThemeContext';
export function NewScreen() {
const { colors } = useTheme();
return (
<View style={[styles.container, { backgroundColor: colors.background }]}>
{/* Content */}
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1 },
});
// Navigate with params
navigation.navigate('MangaDetail', { manga, sourceId });
navigation.navigate('Reader', { manga, chapter, sourceId });
// Get params
const { manga, sourceId } = useRoute().params;
const { colors, isDark } = useTheme();
// colors.background, colors.text, colors.primary, colors.card, colors.border
// Card style
{ backgroundColor: colors.card, borderRadius: 12, padding: 16 }
// Text styles
{ color: colors.text, fontSize: 16 }
{ color: colors.textSecondary, fontSize: 14 }
// Separator
{ height: 1, backgroundColor: colors.border }
const {
library, // Manga[] - saved manga
favorites, // string[] - favorite manga IDs
readingProgress, // { [mangaId]: { chapterId, page } }
addToLibrary, // (manga) => void
removeFromLibrary, // (mangaId) => void
toggleFavorite, // (mangaId) => void
updateProgress, // (mangaId, chapterId, page) => void
} = useLibrary();
| Key | Type | Description |
|---|---|---|
@library |
Manga[] | Saved manga |
@favorites |
string[] | Favorite IDs |
@reading_progress |
object | Reading positions |
@extensions |
Extension[] | Installed extensions |
@settings |
object | User settings |
@history |
HistoryItem[] | Reading history |
// Get manga list
await sourceService.getDiscoverSection(sourceId, sectionId);
// Search manga
await sourceService.searchManga(sourceId, query);
// Get manga details
await sourceService.getMangaDetails(sourceId, mangaId);
// Get chapters
await sourceService.getChapters(sourceId, mangaId);
// Get chapter pages
await sourceService.getChapterPages(sourceId, chapterId);
try {
const data = await sourceService.getMangaDetails(sourceId, mangaId);
} catch (error) {
console.error('Failed to load manga:', error);
// Show error state or toast
}
expo-image with cachePolicy="memory-disk"FlatList with keyExtractor and getItemLayoutuseMemo/useCallback for expensive operations| Type | Convention | Example |
|---|---|---|
| Screen | *Screen.tsx |
LibraryScreen.tsx |
| Component | PascalCase.tsx |
MangaCard.tsx |
| Service | *Service.ts |
cacheService.ts |
| Hook | use*.ts |
useDebounce.ts |
| Context | *Context.tsx |
ThemeContext.tsx |
| Types | index.ts |
types/index.ts |
| Issue | Solution |
|---|---|
| Extension not loading | Check WebView logs, verify extension URL |
| Images not caching | Check cache size limits in settings |
| Build fails | Run npx expo prebuild --clean |
| Metro bundler stuck | Clear cache: npx expo start -c |
| Android foreground service crash | Check withBackgroundActionsServiceType plugin |
plugins/withBackgroundActionsServiceType.jsapp.json under android.permissionspaperback:// and paperand:// schemesfetch enabledCFBundleLocalizationscom.chiraitori.paperand.ios