Kapow

View the Project on GitHub sanchezpaco/kapow

App settings

The gear in the library header opens AppSettingsScreen (feature/settings/ui), a full-screen page in the same visual family as the per-comic settings (docs/library.md). It is the only home of the global preferences; the reader HUD still toggles some of them in place.

Shape of the page

Every section is a grouped surface: the accent eyebrow + title + hairline (core/ui/SectionHeader.kt) sits above a raised card (palette.raised, 20 dp radius, no shadow) whose rows are separated by hairlines. The row vocabulary lives in core/ui/SettingsControls.kt and is shared with the per-comic screen:

Width. Below 840 dp the sections are one centred column capped at 640 dp. From 840 dp (the unfolded Fold) they split into two columns of at most 480 dp each — Reading + Screen on the left, Library + Appearance + About on the right — centred as a block, so the unfolded screen is no longer 40 % empty.

The scroll position is hoisted into KapowRoot (appSettingsScroll) because AnimatedContent drops the screen from composition when the Licences page opens; coming back now lands where you left.

Sections

Backup

android:fullBackupContent="@xml/backup_rules" (API 30) and android:dataExtractionRules="@xml/data_extraction_rules" (API 31+) back up only the Room database (comicify.db: library, progress, per-comic settings, detections) and the DataStore files (files/datastore/: folder Uri, preferences, theme, onboarding flag). Covers, the sample comic, the copied model files and the cache are left out; they are regenerated. After a restore the document grants are gone, so the library reports AccessLost until the folder is chosen again, and fillMissingDetails also regenerates covers whose file no longer exists.

Everything is stored in ReaderPreferencesRepository (DataStore reader_preferences); the folder Uri stays in LibraryPreferences. AppSettingsViewModel combines them into one immutable AppSettingsUiState and writes straight through, so every change is a live preview.

Theme

ThemeChoice(ground, accent) (core/ui/theme/ThemeChoice.kt):

KapowTheme(choice) derives a KapowPalette (accent, deep/pale variants for gradients, accent-tinted inks and raised surface, fixed secondary amber / good / danger / hairlines) and exposes it as KapowTheme.palette (LocalKapowPalette); it also fills MaterialTheme.colorScheme (primary = accent, background = ground, error = danger) for the Material components and the reader HUD, which only uses colorScheme.primary. LibraryPalette.kt keeps its old names (Accent, Surface2, InkDim, …) as composable getters over the palette so the library code reads as before. MainActivity collects ReaderPreferencesRepository.theme and re-themes the whole tree; the system bars are transparent so the ground shows behind them.

The ground is picked from three mini-preview tiles: a rounded rectangle painted in that ground’s own background with a dot in the current accent, the name under it, and an accent ring on the selected one — you see the page you are choosing instead of reading a word for it.

The accent swatches are split-diagonal circles: the current ground on the upper-left half, the accent on the lower-right, a hairline outline so the black half reads on a black page, and a ring on the selected one. The Material You swatch shows the resolved wallpaper colour with a sparkle glyph.

Launcher icon

The last row of Appearance is “App icon”: five round tiles, one per stage of the logo’s page grid (Blue, Ink, Red, Violet, Mix — same bubble and K), with a muted line saying the launcher may take a moment to update. Each tile draws the real AdaptiveIconDrawable of that stage’s mipmap at 48 dp clipped round, so you pick the icon by looking at it, and the selected one carries the accent ring the ground tiles use.

The choice is not a preference: it is which activity-alias is enabled. LauncherIcon (feature/settings/domain) names the five aliases and their mipmaps; LauncherIconRepository (feature/settings/data, @Singleton, injected like the other repositories) reads the state back from PackageManager.getComponentEnabledSetting — treating STATE_DEFAULT as “enabled” only for Blue, the one the manifest ships enabled — and switches by enabling the new alias first and then disabling the others, both with DONT_KILL_APP. AppSettingsViewModel seeds a MutableStateFlow from the repository and folds it into AppSettingsUiState, so the tiles follow the same immutable-state / events-up flow as everything else. See docs/release.md for the stages themselves and what the aliases do to the launcher.

The hero and settings-header glows blend the cover’s ambient with the ground (not black), so they read on paper as well.

The reader keeps its page, ambient backdrop and black chrome regardless of the theme; only the accent (active toggles, progress, guided stops) follows.