← Projects

BookVally

A cross-platform reading app built natively for Android (Kotlin + Jetpack Compose) and iOS (Swift + SwiftUI), backed by a Rust/Axum REST API with 76 endpoints. Features Readium-powered EPUB rendering, offline-first sync with outbox pattern, on-device AI recap, OAuth social login, in-app purchases, TTS with background playback, and a Focus Lock mode that blocks other apps until a reading goal is met.

KotlinJetpack ComposeSwiftSwiftUIRustAndroidiOS

Architecture

Both platforms share the same domain contract — identical entities, repository interfaces, and error taxonomy — re-implemented natively in Kotlin and Swift. No KMP. Each app stays fully idiomatic while staying behaviourally identical.

Android — Clean Architecture in a mono-app structure inspired by the Odyn micro-app framework. Features are vertical slices (data / domain / presentation) inside a single Gradle module. Cross-feature calls go through a typed Kotlin EventBus (replacing ARL string contracts). DI is compile-time via Hilt. domain is pure Kotlin — no Android imports, fully unit-testable.

iOS — Mirrors Android decision-for-decision in Swift idioms. @Observable ViewModels, NavigationStack with type-safe value routes, URLSession + Codable, SwiftData for local persistence. Cross-feature messaging via AsyncStream-based event bus.

Backend (BookVault) — Rust/Axum REST API, 76 endpoints across Auth, Catalog, Library, Reader, Stats, Social, and Search. Async runtime via Tokio, SQLx + PostgreSQL, Redis for caching, Kafka for event streaming, AWS S3 for asset storage. OAuth (Google / Apple / Twitter PKCE), JWT + refresh token rotation, Argon2 password hashing, OpenTelemetry tracing. Server-validates every IAP transaction.

Key Technical Decisions

ConcernAndroidiOS
UIJetpack ComposeSwiftUI (iOS 17+)
StateStateFlow + collectAsStateWithLifecycle@Observable / @State
AsyncCoroutines + Flowasync/await + AsyncStream
DIHilt (compile-time graph)Lightweight container + @Environment
NavigationNavigation Compose type-safe routesNavigationStack + value routes
HTTPRetrofit + kotlinx-serializationURLSession + Codable
Local storeRoomSwiftData / GRDB
Cross-featureTyped EventBus (Kotlin)AsyncStream event bus (Swift)

Notable Features

Readium Reader Engine — EPUB pagination, reading position tracking, highlights and annotations with conflict resolution across devices.

Offline-First Sync — Outbox pattern: writes are persisted locally first, synced when online. Conflict resolution rules defined at the domain layer, applied identically on both platforms. Background sync via WorkManager (Android) and BGTaskScheduler (iOS).

On-Device AI Recap — Spoiler-safe book summaries generated entirely on-device. Uses charOffset boundaries to prevent summarising ahead of the reader’s current position. Android: ML Kit GenAI / Gemini Nano. iOS: Apple Foundation Models (iOS 26+). Capability-gated with graceful degradation.

Focus Lock — Reading commitment mode that blocks other apps until a reading goal (minutes) is met. Android: UsageStats API + overlay foreground service, degrades gracefully without PACKAGE_USAGE_STATS permission. iOS: Screen Time / Family Controls API with entitlement gate.

TTS & Audio — Text-to-speech with background playback. Android: MediaSession + sentence prefetch. iOS: AVAudioSession + Now Playing integration.

Security — TLS certificate pinning, Readium LCP DRM for protected books, FLAG_SECURE / capture protection, biometric-guarded Keychain storage, data-at-rest encryption.

Release Pipeline — Fastlane + GitHub Actions. Lint, test, performance, and schema gates on every PR. Single-sourced versioning. CI-injected signing (match for iOS, upload-key for Android). Staged rollout to Play and App Store.

Shared Domain Error Model

Typed AppError taxonomy defined once, implemented in both Kotlin (sealed class) and Swift (enum): network, unauthorized, forbidden, notFound, validation(field, message), conflict, rateLimited(retryAfter), server. Repositories return Result<T> — never throw. UI maps error category to treatment (inline, banner, dialog, full-screen) via a severity × surface matrix.