Native Android, Kotlin + Jetpack Compose, single Gradle module packaged by feature. MVVM with strict unidirectional data flow (UDF).
com.comicify
├── app App entry, single Activity, navigation host, theme
├── core Shared building blocks (no feature knowledge)
│ ├── ui Design system: theme, tokens, reusable composables
│ ├── image Coil setup, decoding helpers, dominant-color extraction
│ ├── storage Room database, DAOs, DataStore preferences
│ └── window Foldable posture tracking (androidx.window wrappers)
├── feature
│ ├── reader The reading surfaces (full page, spread, guided)
│ ├── library Collection grid, import, continue-reading
│ └── settings Preferences UI
└── domain Pure models + use cases shared across features
Each feature/* is internally layered:
feature/reader
├── ui Composables + ReaderViewModel + ReaderUiState (immutable)
├── domain Use cases, pure models (Page, Panel, Posture-agnostic state)
└── data Repositories, archive readers, decoders (side effects live here)
User gesture ──▶ ViewModel event ──▶ Use case ──▶ Repository/data
│ │
└────── new immutable UiState ◀──────┘
│
StateFlow ──▶ Composable renders
UiState and emit events. No business
logic, no direct data access.ViewModel owns state as StateFlow<UiState>. UiState is an immutable data
class. State is only ever replaced, never mutated in place.data layer
and invoked through use cases.UiState; the reader picks a rendering strategy
from it. See foldable.md.androidTest
assets.feature → domain → core. Features never depend on each other. core never
depends on feature. domain holds no Android framework types where avoidable.