{"id":49896463,"url":"https://github.com/stanza-redux/stanza-redux","last_synced_at":"2026-06-25T22:00:34.907Z","repository":{"id":279997804,"uuid":"940699381","full_name":"Stanza-Redux/Stanza-Redux","owner":"Stanza-Redux","description":"A native ebook reader for iOS and Android based on the Readium toolkit","archived":false,"fork":false,"pushed_at":"2026-05-22T22:29:14.000Z","size":6235,"stargazers_count":6,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-05-23T00:27:58.498Z","etag":null,"topics":["ebook","skip"],"latest_commit_sha":null,"homepage":"http://stanza-redux.app/","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Stanza-Redux.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.GPL","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-02-28T16:27:37.000Z","updated_at":"2026-05-22T22:29:19.000Z","dependencies_parsed_at":null,"dependency_job_id":"48d9b264-9643-496c-a9a8-f0e68a09c110","html_url":"https://github.com/Stanza-Redux/Stanza-Redux","commit_stats":null,"previous_names":["stanza-redux/stanza-redux"],"tags_count":15,"template":false,"template_full_name":null,"purl":"pkg:github/Stanza-Redux/Stanza-Redux","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Stanza-Redux%2FStanza-Redux","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Stanza-Redux%2FStanza-Redux/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Stanza-Redux%2FStanza-Redux/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Stanza-Redux%2FStanza-Redux/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Stanza-Redux","download_url":"https://codeload.github.com/Stanza-Redux/Stanza-Redux/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Stanza-Redux%2FStanza-Redux/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34793951,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-25T02:00:05.521Z","response_time":101,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ebook","skip"],"created_at":"2026-05-16T00:12:20.466Z","updated_at":"2026-06-25T22:00:34.897Z","avatar_url":"https://github.com/Stanza-Redux.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Stanza Redux\n\nStanza Redux is a cross-platform ebook reader for iOS and Android, built with [Skip](https://skip.dev) and powered by the [Readium SDK](https://readium.org/development/readium-sdk-overview/). A single Swift codebase powers a platform-native app that utilizes SwiftUI on iOS and Jetpack Compose on Android, while each platform uses its own native Readium toolkit for EPUB parsing, rendering, and navigation.\n\n\u003cdiv align=\"center\"\u003e\n  \u003ca href=\"https://play.google.com/store/apps/details?id=org.appfair.app.Stanza_Redux\" style=\"display: inline-block;\"\u003e\u003cimg src=\"https://appfair.org/assets/badges/google-play-store.svg\" alt=\"Download on the Google Play Store\" style=\"height: 60px; vertical-align: middle; object-fit: contain;\" /\u003e\u003c/a\u003e\n  \u003ca href=\"https://apps.apple.com/us/app/stanza-redux/id1639831676\" style=\"display: inline-block;\"\u003e\u003cimg src=\"https://appfair.org/assets/badges/apple-app-store.svg\" alt=\"Download on the Apple App Store\" style=\"height: 60px; vertical-align: middle; object-fit: contain;\" /\u003e\u003c/a\u003e\n\u003c/div\u003e\n\n\n## Architecture\n\nStanza Redux demonstrates how Skip can bridge platform-specific native libraries from a shared codebase. The app is organized into two Swift Package Manager modules:\n\n- **`Stanza`** — The UI layer (SwiftUI views), built in `Sources/Stanza/`\n- **`StanzaModel`** — The data layer (database, OPDS parsing, settings), built in `Sources/StanzaModel/`\n\n### Readium Integration\n\nThe Readium SDK is published as two independent toolkits:\n\n- [**readium/swift-toolkit**](https://github.com/readium/swift-toolkit) — Used on iOS via Swift Package Manager\n- [**readium/kotlin-toolkit**](https://github.com/readium/kotlin-toolkit) — Used on Android via Gradle dependencies\n\nStanza Redux uses `#if SKIP` / `#if !SKIP` conditional compilation to call the appropriate platform SDK while sharing all UI and business logic. Platform-specific types are abstracted behind thin wrappers defined in `StanzaModel`:\n\n| Wrapper | iOS Type | Android Type |\n|---------|----------|--------------|\n| `Pub` | `ReadiumShared.Publication` | `org.readium.r2.shared.publication.Publication` |\n| `Loc` | `ReadiumShared.Locator` | `org.readium.r2.shared.publication.Locator` |\n| `Lnk` | `ReadiumShared.Link` | `org.readium.r2.shared.publication.Link` |\n| `Man` | `ReadiumShared.Manifest` | `org.readium.r2.shared.publication.Manifest` |\n\nThese wrappers expose cross-platform properties (title, href, progression, etc.) that the UI layer consumes without needing to know which platform SDK is providing the data.\n\nThe EPUB navigator is embedded differently on each platform:\n\n- **iOS**: `EPUBNavigatorViewController` is wrapped in a `UIViewControllerRepresentable` and hosted in a SwiftUI view\n- **Android**: `EpubNavigatorFragment` is embedded via Jetpack Compose's `AndroidFragment` composable within a `ComposeView`\n\n### Data Flow\n\n```\nStanzaApp (entry point)\n  └── ContentView (tab bar)\n        ├── LibraryView (book management)\n        ├── BrowseView (OPDS catalogs)\n        └── SettingsView (preferences)\n              └── AdvancedSettingsView\n```\n\nShared state is managed through the SwiftUI environment:\n\n- **`StanzaSettings`** — `@Observable` class persisting user preferences to `UserDefaults`\n- **`LibraryManager`** — `@Observable` class managing the book database and file operations\n- **`ErrorManager`** — `@Observable` class providing centralized error alert presentation\n\n## Library\n\nThe Library tab is the main screen of the app. It displays all imported books with cover art, titles, authors, and reading progress.\n\n\u003cimg height=\"500\" alt=\"Screenshot 2026-03-23 at 17 52 36\" src=\"https://github.com/user-attachments/assets/06ff9e5a-2b14-4cfb-b4e0-c28aa2b7f88d\" /\u003e\n\u003cimg height=\"500\" alt=\"Screenshot 2026-03-23 at 17 53 26\" src=\"https://github.com/user-attachments/assets/d891307f-4e1c-475e-9427-c6c8d982c489\" /\u003e\n\n\n### Features\n\n- **Import books** from the device's file system using the system document picker\n- **Cover art extraction** — automatically extracts and caches cover images from EPUB files\n- **Reading progress** — displays percentage complete for each book\n- **Search** — filter the library by title or author\n- **Book management** — long-press context menu to view details, edit metadata, or delete books\n- **Resume reading** — tap a book to open it in the reader; the app remembers your last reading position\n- **Sample book** — a bundled copy of *Alice's Adventures in Wonderland* can be imported to try the reader immediately\n\n\u003c!-- TODO: Screenshot of Library empty state with Import Sample Book button --\u003e\n\n### Book Detail\n\nEach book has a detail view showing metadata (title, author, identifier), reading progress, chapter count, and file path. An edit mode allows modifying the title, author, and identifier.\n\n\u003c!-- TODO: Screenshot of Book Detail view --\u003e\n\n## Reader\n\nThe reader presents EPUB content in a paginated view with customizable typography and an overlay HUD for navigation controls.\n\n\u003cimg height=\"500\" alt=\"Stanza_Reader_Android\" src=\"https://github.com/user-attachments/assets/e05784a0-69e4-4805-8ca6-a2deb490c8d3\" /\u003e\n\u003cimg height=\"500\" alt=\"Stanza_Reader_iOS\" src=\"https://github.com/user-attachments/assets/14c60926-1508-4a7d-b543-e957e242ae63\" /\u003e\n\n### Navigation\n\n- **Tap zones** — tap the left or right third of the screen to go backward or forward; tap the center to toggle the HUD\n- **Animated page turns** — smooth page transition animations on both platforms\n- **Table of Contents** — hierarchical chapter navigation with the current chapter highlighted\n- **Bookmarks** — add, view, edit notes on, and navigate to bookmarks\n- **Reading position persistence** — your position is saved on every page turn and restored on next launch\n\n### HUD Controls\n\nThe heads-up display provides:\n\n- **Progress bar** with chapter title and percentage\n- **Font size** controls (increase/decrease buttons)\n- **Font picker** — horizontal scrolling panel with font previews\n- **Spacing controls** — cycle through presets for line height, character spacing, word spacing, and page margins\n- **Table of Contents** and **Bookmark** buttons\n\n\u003c!-- TODO: Screenshot of Reader HUD with extended font/spacing panel open --\u003e\n\n### Themes and Appearance\n\n- **Light, Dark, and Sepia** reading themes\n- **System appearance** follows the device setting\n- **Status bar hiding** for immersive reading (iOS)\n\n## Catalogs\n\nThe Catalogs tab (enabled via Advanced Settings) allows browsing and downloading books from [OPDS](https://opds.io/) catalog feeds.\n\n\u003cimg height=\"500\" alt=\"Stanza_Browse_Android\" src=\"https://github.com/user-attachments/assets/691e249a-d4ed-41c7-abb1-a4ec66a59c99\" /\u003e\n\u003cimg height=\"500\" alt=\"Stanza_Browse_iOS\" src=\"https://github.com/user-attachments/assets/0450099f-78a2-4a9a-9268-c2e8bb58f807\" /\u003e\n\n### Features\n\n- **Pre-configured catalogs** — Standard Ebooks, Project Gutenberg, and Ebooks Gratuits\n- **Custom catalogs** — add any OPDS feed URL\n- **Catalog browsing** — navigate categories, groups, and facets\n- **Search** — search within catalogs that support OpenSearch\n- **Book detail** — view cover art, author, summary, and available download formats\n- **Multiple format support** — when a book is available in multiple EPUB variants (e.g., \"Recommended compatible epub\", \"Advanced epub\"), a menu lets you choose which to download\n- **Direct download** — download and import books directly into the library\n- **About This Catalog** — displays feed metadata including icon, description, total book count, and informational links\n\n\u003c!-- TODO: Screenshot of Catalog book detail with multiple download formats --\u003e\n\n### OPDS Implementation\n\nThe OPDS service (`OPDSService`) handles:\n\n- Parsing both OPDS 1 (Atom/XML) and OPDS 2 (JSON) feeds via the Readium OPDS parsers\n- Extracting navigation images from feed entries using raw XML parsing with [SkipXML](https://skip.dev/docs/modules/skip-xml/)\n- Promoting Atom `rel=\"enclosure\"` entries to proper publication entries for feeds that don't use standard OPDS acquisition links\n- OpenSearch template resolution for catalog search\n- HTML-to-Markdown conversion of book summaries using `HTMLMarkdown`\n\n## Settings\n\nThe Settings tab provides controls for reading preferences, text layout, and spacing.\n\n\u003cimg height=\"500\" alt=\"Stanza_Settings_Android\" src=\"https://github.com/user-attachments/assets/b8735a4b-e219-47b6-9187-29608bf0476c\" /\u003e\n\u003cimg height=\"500\" alt=\"Stanza_Settings_iOS\" src=\"https://github.com/user-attachments/assets/4bc6a3a1-aadc-4184-ba06-6faa700bda8c\" /\u003e\n\n### Reading Preferences\n\n- **Appearance** — System, Light, or Dark mode\n- **Sepia Theme** — warm-toned reading theme\n- **Font** — choose from system fonts and bundled custom fonts (Montserrat, Noto Serif, Noto Sans)\n- **Font Size** — adjustable from 50% to 300%\n- **Animate Page Turns** — toggle animated transitions\n- **Left Tap Advances** — swap the left-tap direction\n- **Hide Status Bar in Reader** — immersive reading mode\n- **Open Web Pages in Embedded Browser** — use SFSafariViewController (iOS) or Chrome Custom Tabs (Android)\n\n### Text Layout\n\n- Columns (auto, one, two)\n- Content fit (auto, page, width)\n- Hyphenation, text alignment, text normalization\n- Publisher styles toggle\n\n### Spacing\n\n- Line height, page margins, paragraph spacing, word spacing (all with slider controls)\n\n### Advanced Settings\n\n- **Enable Catalogs** — show/hide the Catalogs tab\n\n\u003c!-- TODO: Screenshot of Advanced Settings --\u003e\n\n## Error Handling\n\nThe app uses a centralized `ErrorManager` that provides consistent error alerts across all screens. When an error occurs anywhere in the app, `errorManager.errorOccurred(info:)` is called with structured `ErrorInfo` containing a title, message, underlying error, and optional help URL. The error manager logs the error and presents an alert with \"OK\" to dismiss and \"Help\" to search the project's issue tracker.\n\nThe `ErrorManager` is safe to call from any thread — it dispatches to the main actor internally.\n\n## Building\n\nThis project is both a stand-alone Swift Package Manager module,\nas well as an Xcode project that builds and translates the project\ninto a Kotlin Gradle project for Android using the skipstone plugin.\n\n## Testing\n\nThe module can be tested using the standard `swift test` command\nor by running the test target for the macOS destination in Xcode,\nwhich will run the Swift tests as well as the transpiled\nKotlin JUnit tests in the Robolectric Android simulation environment.\n\nParity testing can be performed with `skip test`,\nwhich will output a table of the test results for both platforms.\n\n## Running\n\nXcode and Android Studio must be downloaded and installed in order to\nrun the app in the iOS simulator / Android emulator.\nAn Android emulator must already be running, which can be launched from\nAndroid Studio's Device Manager.\n\nTo run both the Swift and Kotlin apps simultaneously,\nlaunch the StanzaApp target from Xcode.\nA build phases runs the \"Launch Android APK\" script that\nwill deploy the transpiled app a running Android emulator or connected device.\nLogging output for the iOS app can be viewed in the Xcode console, and in\nAndroid Studio's logcat tab for the transpiled Kotlin app.\n\n## License\n\nThis software is licensed under the [GNU General Public License v2.0 or later](https://spdx.org/licenses/GPL-2.0-or-later.html).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstanza-redux%2Fstanza-redux","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstanza-redux%2Fstanza-redux","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstanza-redux%2Fstanza-redux/lists"}