Offline-first product catalog for Android, iOS & Web — one Compose Multiplatform codebase (Fake Store API)
Translation pending
CommerceKotlin · HTMLGitHub @chinmay-tayadeOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Loading the full README. The saved excerpt is shown below.
An offline-first product catalog — built once in Kotlin, shipped to Android, iOS & Web. ⬇️ Download the Android APK · Releases · Architecture · Build & runRead the full README ↗
⬇️ Download the Android APK · Releases · Architecture · Build & run
Kaleido starts from the classic "Product Catalog" brief — list the Fake Store API
products, open a detail screen — and takes it as far as the data allows: one Compose
Multiplatform codebase, a real offline-first data layer, and a set of features the
API's price / rating fields quietly make possible (value scoring, side‑by‑side
compare, category analytics), wrapped in a quick‑commerce UI that runs identically on
a phone, a tablet and a desktop browser.
| Platform | Entry point | Status |
|---|---|---|
| Android | composeApp/src/androidMain → MainActivity | ✅ Download APK · minSdk 24 |
| iOS | iosApp/ (SwiftUI shell) + KaleidoKit.framework | ✅ framework builds — open iosApp/iosApp.xcodeproj in Xcode |
| Web | composeApp/src/wasmJsMain (Kotlin/Wasm + Skia) | ✅ prebuilt in web/ — deploy to Vercel |
The APK is a debug build signed with the standard Android debug key. Enable Install unknown apps for your browser or file manager to sideload it.
| 🔌 Offline‑first | The last good catalog is persisted locally and shown instantly on a cold start. A failed refresh keeps the cached data on screen and flips a visible "showing your saved copy" banner instead of erroring. |
| 🛒 Wishlist · Cart · Compare | Favourite anything, adjust cart quantities, compare 2–3 products side by side. All on‑device — survives process death and airplane mode, no account, no backend. |
| ⚡ Quick‑commerce UI | ADD button on every card that morphs into an inline − qty + stepper; a sticky View cart bar; a 2‑row category tile grid; "Deals of the day" rail; pull‑to‑refresh; an "Added to cart · VIEW CART" snackbar; a sort bottom sheet; grid items animate on reorder and press. |
| 💎 Deal Score & "For You" | An explainable 0–100 value metric per product — rewards a good rating (shrunk toward the mean when reviews are thin) and punishes a high price. Drives the badges, the default sort, and a diverse per‑category "For You" rail. |
| 📊 Category insights | Average price & rating, price ranges, a rating histogram and a per‑category "best value" — charts drawn in Compose. |
| 📐 Responsive scaling | A cross‑platform sdp / ssp system (see below) so every dimension and font size scales cleanly from a small phone to a desktop browser. |
MVVM · unidirectional state · clean‑ish layering. Every screen exposes a single
StateFlow<XxxUiState> from a multiplatform ViewModel and renders it with
collectAsStateWithLifecycle. The domain layer is plain Kotlin — no Compose, no Ktor,
no platform types — and is where the interesting logic lives, which is why it's the part
that's unit‑tested.
flowchart LR
subgraph UI["ui · Compose Multiplatform"]
S[Screens] --> VM[ViewModels]
end
subgraph DOMAIN["domain · pure Kotlin"]
M[Models + Engines<br/>DealScore · CatalogEngine · InsightsEngine]
R[[CatalogRepository]]
end
subgraph DATA["data"]
IMPL[CatalogRepositoryImpl<br/>cache-then-network]
API[FakeStoreApi<br/>Ktor 3]
CACHE[CatalogCache + ShelfStore]
KV[(KeyValueStore<br/>SharedPreferences / NSUserDefaults / localStorage)]
end
VM --> M
VM --> R
IMPL -. implements .-> R
IMPL --> API
IMPL --> CACHE
CACHE --> KV
composeApp/src/commonMain/kotlin/com/kaleido/app/
├─ domain/ pure Kotlin — no Compose / Ktor / platform types
│ ├─ Product.kt model + dealScore() + synthetic MRP/discount
│ ├─ Catalog.kt CatalogEngine: filter / sort / "for you"
│ ├─ Insights.kt InsightsEngine: category aggregates, histograms
│ ├─ Cart.kt CartSummary totals
│ └─ CatalogRepository.kt interface + DataStatus
│
├─ data/
│ ├─ remote/ Ktor client + hand-rolled JSON mapping for the Fake Store API
│ ├─ local/ KeyValueStore (interface) + CatalogCache + ShelfStore
│ └─ CatalogRepositoryImpl.kt cache-then-network, status tracking
│
├─ di/ Koin modules (expect/actual platformModule per target)
│
└─ ui/
├─ theme/ Kaleido colour system + commerce tokens + Scale.kt (sdp/ssp)
├─ components/ ProductCard, AddButton, RatingChip, PriceRow, charts, states
├─ catalog/ detail/ store/ compare/ insights/ screen + ViewModel per feature
└─ nav/ string route table
Dependency rule: ui → domain ← data. domain depends on nothing platform‑specific.
DI is Koin, with an expect val platformModule supplying the HTTP engine
(OkHttp / Darwin / JS) and the key‑value store per platform.
The Android‑only com.intuit.sdp / com.intuit.ssp
libraries are XML‑dimen resources — they don't exist for iOS or Web.
ui/theme/Scale.kt
reimplements the idea for Compose Multiplatform:
12.sdp / 13.ssp read like the sdp/ssp API but resolve identically
on phone, tablet and desktop web.On wide screens the primary content column is also centred and width‑capped, like every real store's web layout.
| Area | Choice |
|---|---|
| Language | Kotlin 2.4.10 · coroutines / Flow |
| UI | Compose Multiplatform 1.11 (Material 3) · Navigation Compose |
| Networking | Ktor 3 client · kotlinx.serialization JSON‑tree parsing |
| DI | Koin 4 |
| Images | Coil 3 (multiplatform, Ktor fetcher) |
| Persistence | platform key‑value stores behind one interface |
| Build | Gradle 9 · AGP 9 · version catalog |
| Tests | kotlin-test · kotlinx-coroutines-test · Turbine · Ktor MockEngine |
# Android — APK or straight onto a device / emulator
./gradlew :composeApp:assembleDebug
./gradlew :composeApp:installDebug
# Web — static bundle in composeApp/build/dist/wasmJs/productionExecutable
./gradlew :composeApp:wasmJsBrowserDistribution
./gradlew :composeApp:wasmJsBrowserDevelopmentRun # live dev server on :8080
# iOS — build the shared framework, then open the Xcode project
./gradlew :composeApp:linkDebugFrameworkIosSimulatorArm64
open iosApp/iosApp.xcodeproj # pick a simulator and Run
# Tests — domain, repository, stores, DI graph (32 tests)
./gradlew :composeApp:testDebugUnitTest
The Fake Store API needs no key.
NSAppTransportSecurityis relaxed in the iOSInfo.plistbecause the API is plain HTTP‑friendly.
The Kotlin/Wasm build is committed prebuilt to web/, so Vercel serves it
with no build step.
Live demo: add your
*.vercel.appURL here after the first deploy
chinmay-tayade/Kaleido)vercel.json — Output Directory web, no build command,
.wasm MIME + cache headers already set. Just click Deploy.main redeploys.Refresh the bundle after changing app code:
./scripts/build-web.sh # rebuilds web/ from source
git commit -am "rebuild web" && git push
The Build web bundle GitHub Action (.github/workflows/web.yml)
also does this automatically whenever composeApp/src/** changes.
commonTest covers the parts that carry logic:
DealScoreTest — monotonicity (cheaper ⇒ higher, better‑rated ⇒ higher, more reviews ⇒ higher) and boundsCatalogEngineTest — search, category filter, every sort mode, "For You" diversityCartAndInsightsTest — cart totals & smart‑basket savings, category aggregates, histogram coverageCatalogRepositoryTest — warm‑start emits cache before any network call; refresh success ⇒ Live; refresh failure keeps cache ⇒ Offline (Ktor MockEngine)ProductPricingTest — synthetic MRP / discount % is deterministic, in range, and always above the sale priceShelfStoreTest — wishlist / cart / compare persistence round‑tripsDiGraphTest — every Koin definition resolves@Serializable is avoided in shared code — the kotlinx.serialization compiler
plugin currently crashes on the Kotlin/Wasm target, so DTOs are mapped by hand from the
JSON tree and navigation uses string routes. Small cost, keeps Web alive.iosArm64 + iosSimulatorArm64) — Compose Multiplatform
no longer publishes iosX64.web/ + vercel.json)Built with Compose Multiplatform · Report an issue