Lô Tô
Lô tô hội chợ — Vietnamese fairground bingo. One number caller, two shipping targets: a static web app and a fully-offline native Android app.
Live: tiennm99.github.io/loto
Layout
| Path | What it is |
|---|---|
web/ |
SvelteKit static app — the game itself. npm, Vitest, deployed to GitHub Pages and Firebase Hosting. |
android/ |
Native Kotlin + Jetpack Compose app at feature parity with web/. Pure Gradle — no Node. |
The two apps share the voice clips: the Android build mounts
web/static/audio/ straight into the APK's assets, so the MP3s exist once in
the repo. Game logic is hand-ported — a behavior change in web/src/lib/
needs a matching Kotlin change (the web app is the behavioral spec).
Quick start
# web
cd web && npm install && npm run dev
# android debug APK (requires JDK 21 + Android SDK)
cd android && ./gradlew :app:assembleDebug
Per-project detail lives in web/README.md and
android/README.md.
CI
Workflows live at the repository root in .github/workflows/. Because both
subprojects ship from the same commit, ordinary CI is a single ci.yml;
only the tag-driven release stands apart.
| Workflow | Trigger | Result |
|---|---|---|
ci |
push to main or PR touching web/ or android/ |
see the job graph below |
android-release |
tag v*.*.* |
versionCode guard, lint + tests, then a signed AAB + APK on the GH Release, plus Play Store closed-testing upload |
ci builds the web app exactly twice — once per base path — and the Android
job builds independently from the same checkout:
test (npm test)
└── build (root, base "") ── deploy-firebase push to main
│ └─ preview-firebase PR from this repo
└── build (gh, base /loto) ── deploy-pages push to main
android-debug (gradle lint + test + assembleDebug) ── unsigned APK artifact
Nothing deploys unless test is green. Deploy jobs are skipped on pull
requests, and the Firebase preview is skipped for PRs from forks, which
cannot read the service-account secret.
Shared toolchain setup lives in .github/actions/setup-web (Node) and
.github/actions/setup-android (JDK 21 + Gradle caching) — composite actions
used by both workflows.
Release/secret setup for the Play Store pipeline is documented in
docs/play-store-publishing.md.
History
This repository is the merge of the former tiennm99/loto (now web/) and
tiennm99/loto-android (now android/). Both histories are preserved commit
for commit and interleaved in commit-time order under a single root, so
git log reads as one timeline and git log -- web/ or git log -- android/
each resolve on the current paths.
android/ has lived three lives: a native Kotlin/Compose port (retired
2026-05-10), a Capacitor WebView wrapper around the web app, and — since
2026-08-31 — a fresh native Kotlin/Compose rewrite that replaced the wrapper.
All three are preserved in history.
License
Apache-2.0 — see LICENSE.