Repository navigation
feat(designsystem): content-shaped skeletons, Coil crossfade pipeline… - #12
Merged
Merged
Conversation
…, dark theme fix, nav safety guards, bundled fonts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
…, dark theme fix, nav safety guards, bundled fonts
core:designsystem Polish — 2026 Production Loading UX
Summary
Upgrades loading states across Catalog, Detail, and the design system to content-shaped skeleton screens, a unified Coil image pipeline, and crossfade-based state transitions — replacing spinner-based loading with the pattern used by production catalog/e-commerce apps. Also fixes several real bugs surfaced during implementation: a silent no-op refresh, a dead retry button, a dark-mode theming bug affecting every screen, and double-tap navigation crashes.
What's included
Skeleton loading system
ProductCardSkeleton, SearchResultSkeleton, DetailSkeleton, CategoryChipRowSkeleton — content-shaped shimmer skeletons structurally locked to their real counterparts via shared *Defaults objects (ProductCardDefaults, SearchResultCardDefaults, DetailBottomBarDefaults) promoted to core:designsystem, eliminating drift risk between loading and loaded states
AnimatedContent-driven crossfades (Loading/Error/Empty/Content) on Catalog and Detail, keyed on enum-based contentState() derived state instead of raw booleans
Image pipeline
CatalogAsyncImage — unified Coil wrapper with theme-aware tonal placeholder and a distinct error state (muted broken-image icon over the same tonal background), replacing raw AsyncImage calls across feature:catalog, feature:detail, feature:search
Non-success visuals grouped into CatalogImagePlaceholders to keep the composable's parameter count under Detekt's LongParameterList threshold
Navigation
NavHost enter/exit/pop transitions with FastOutSlowInEasing
navigateSafely() / popBackStackSafely() extensions on NavController, gating navigation on back-stack entry RESUMED state — prevents duplicate navigation from rapid double-taps or multiple simultaneous triggers (two product cards tapped in quick succession, back button double-tapped)
Root Surface background added around NavHost to eliminate a white flash during slide transitions (window background gap)
Bug fixes found during implementation
CatalogViewModel.refreshProducts() was a no-op (set isRefreshing = true then immediately false with no actual fetch) — replaced with shared fetchProducts(showAsRefresh: Boolean), verified via verify(exactly = 2) { getProductsUseCase() } (previous test passed against the broken version — replaced, not just patched)
DetailViewModel had no RetryLoad intent — retry button on the error state did nothing; added intent + currentProductId tracking
DarkColorScheme had surfaceContainerLow/surfaceContainerLowest/surfaceContainerHigh wired to their light-mode color values instead of the *Dark variants — root cause of every screen (top bar, cards, bottom bar, chips) showing white/light backgrounds in dark mode despite correct dark values already being defined and unused
Other
Detekt fixes: CatalogAsyncImage parameter grouping (LongParameterList), DetailSkeleton split into sub-composables (LongMethod)
Testing
CatalogViewModelTest: replaced weak refresh test with call-count verification + a test asserting a failed refresh doesn't wipe existing products
Manual on-device pass: skeleton→content crossfades (light/dark), pull-to-refresh, rapid-tap navigation guard, retry flow, dark mode across all screens, image placeholder/error states under throttled network
Deferred (tracked, not lost)
App cold-start investigation (~7s gap on Android 13-and-below emulator, ViewModel init → first render) — narrowed to likely debug-build classloading cost; pending release-build/real-device timing confirmation. Baseline Profiles identified as the fix if confirmed.
Nav transition perceived lag — deferred to a dedicated Profiler session (Find CPU Hotspots)
Search screen loading skeleton — scoped out; existing debounce/typing rhythm judged sufficient
Detail bottom bar skeleton — implemented then reverted (low-dwell UI, snap is imperceptible; simplified via AnimatedVisibility reasoning, then dropped by scope decision)
lastUpdated staleness timestamp — implemented then removed; doesn't serve FakeStore's static data, reserved for WorkManager sync feedback later
Checklist
[] lintDevDebug passing
[]detekt passing
[]testDevDebugUnitTest passing
[]jacocoTestReport / jacocoTestCoverageVerification passing
[]assembleDevDebug passing
[]Manual verification on emulator (light + dark mode)