46 Commits
Author SHA1 Message Date
tiennm99 79b26a5af3 fix(android): restore/user-action race, confetti-in-dialog, settings-loaded gate, color rounding, backup rules, manifest updates 2026-09-01 00:11:29 +07:00
tiennm99 cd2ad435a6 fix(ci): restore the executable bit on android/gradlew 2026-08-31 13:30:59 +07:00
tiennm99 e314ae01c8 feat(android)!: replace the Capacitor wrapper with a native Kotlin/Compose app
Fresh Kotlin 2.2 + Jetpack Compose app at feature parity with web/
(game logic, settings contract, voice semantics ported one-to-one with
twinned tests; 104 unit + 9 instrumentation tests). Fully offline:
VIBRATE is the only permission; Media3's ACCESS_NETWORK_STATE is
stripped. Voice clips mount from web/static/audio at build time.

Same applicationId, signing env contract, and Play alpha track;
versionCode 7 / 0.2.0. CI Android job is Gradle-only (lint, test,
debug + R8 release APK) and the release workflow fails fast on an
unbumped versionCode.

BREAKING CHANGE: WebView localStorage (settings, in-progress round)
is not migrated; wrapper users start clean.
2026-08-31 13:28:48 +07:00
tiennm99 f1832a9d2c chore(android): bump version to 0.1.2 (versionCode 6)
Play rejects duplicate versionCodes.

First release since the workflow was pointed at the closed testing
track, so this tag doubles as the check that "alpha" is the right
track id.
2026-08-20 09:14:39 +07:00
tiennm99 6587df5209 ci(android): publish tagged releases to the closed testing track
Internal testing is a separate track and does not count toward the
12-tester, 14-day requirement for production access -- only a closed
test does. Releases were landing on internal, so reaching the testers
meant promoting each build by hand in the Console.

Point tracks: at alpha, the closed track, and update the publishing
guide and android README to match. An unknown track id fails the step
with the list of valid tracks rather than publishing somewhere
unintended, so the id is checked at upload time.

Builds up to v0.1.1 remain on the internal track.
2026-08-20 09:11:14 +07:00
tiennm99 20dbeb191f chore(android): bump version to 0.1.1 (versionCode 5)
Play rejects duplicate versionCodes. Still an internal-track build, so
0.1.1 rather than 1.0.0.

Nothing in the app itself changed since v0.1.0 -- the release carries
the toolchain migration from pnpm to npm plus the plans and publishing
docs consolidation.
2026-08-19 16:33:45 +07:00
tiennm99 0691665b3a chore(plans): consolidate all plans at repo root
Plans were split across three trees: plans/, web/plans/, and
android/plans/. Merge them into plans/ at the repo root.

Sweep the shipped Android experience pass and the two reports for it,
carrying the residual forward into plans/todo.md: the device QA
checklist (open only because this environment has no device or
emulator), the clipped web PWA icons defect, and the untested native
assumptions.

Merge android/plans/todo.md in the same pass, dropping items the
experience pass already delivered -- haptics, back button, safe-area
insets, launcher icon, and splash -- each verified against
MainActivity.java, web/src/app.css, and the res/ tree. Correct two
stale claims while merging: web/ is a plain directory in this monorepo,
not a git submodule, and the serialize-javascript override is held by
@rollup/plugin-terser rather than workbox-build.

The retired native Kotlin/Compose port plan moves as-is; it is
superseded by the Capacitor wrapper but not yet swept.
2026-08-19 16:09:56 +07:00
tiennm99 1c1bab8733 build: move web/ from pnpm to npm
Replace pnpm-lock.yaml with package-lock.json and drop pnpm-workspace.yaml.
The three security overrides move to package.json#overrides, which npm reads
natively; allowBuilds for esbuild is unnecessary because npm runs dependency
build scripts by default.

android/build:web drove the web build through corepack pnpm, so it would have
broken once web/pnpm-lock.yaml was gone. It now uses npm --prefix.

Fresh resolution picks up patched versions the old lockfile had pinned below,
so npm audit reports no advisories where pnpm audit reported eight.
2026-08-17 11:54:24 +07:00
tiennm99 6dcbdbd51d fix(android): remove "--" from XML comments in colors.xml
XML forbids "--" inside a comment, and both comments referenced the CSS
custom property by its "--background" name, so mergeDebugResources
failed to parse them.
2026-08-14 15:24:41 +07:00
tiennm99 8aff576fe2 chore(android): bump version to 0.1.0 (versionCode 4)
Play Console rejects duplicate versionCodes. Still an internal-track
build, so 0.1.0 rather than 1.0.0.
2026-08-14 15:21:03 +07:00
tiennm99 48d56eee30 docs(android): document permissions, back behaviour, and icon pipeline
Records what a maintainer cannot infer from the wrapper: why VIBRATE is
declared when the web build needs no equivalent, when the wake lock is
held and why it re-acquires on visibilitychange, how back maps to
overlay history, and why the textZoom pin and the in-app size setting
ship together.

Also documents where launcher art comes from, that regenerating needs
Roboto Condensed converted out of the .woff fontsource ships, and the
clipping bug still present in source.svg.

The "Why no INTERNET permission?" section is unchanged — the offline
guarantee still holds.
2026-08-14 15:21:03 +07:00
tiennm99 f4d7e3c274 feat(android): use the brand mark for the launcher icon and splash
The APK shipped the Capacitor template's artwork: the launcher icon,
round icon, adaptive foreground and splash were all the stock blue mark
on a teal grid plate, and the adaptive background was the template's
white. The brand icon existed only under web/static/icons, so the
published app showed another project's logo in the launcher and on cold
start.

Regenerated from web/static/icons/source.svg — legacy, round, adaptive
foreground and a monochrome layer for Android 13+ themed icons, at all
five densities. The adaptive background becomes a vector carrying the
rose → amber gradient.

source.svg sets font-size="240", which renders "Lô tô" wider than its
512px canvas, and the committed PNGs under web/static/icons were
rendered without Roboto Condensed so a wider fallback pushed the text
past the edge — they are visibly clipped on both sides. The Android art
is rendered at a corrected size with the real font. The web PWA icons
still carry the original bug.

The splash becomes a layer-list over a themed colour, with a night
variant, replacing eleven density-specific PNGs; API 31+ uses
windowSplashScreenBackground / windowSplashScreenAnimatedIcon instead.
The app has a full dark theme but the splash always flashed light.

Drops the now-unused template robot vector and the white
@color/ic_launcher_background.
2026-08-14 15:20:50 +07:00
tiennm99 3bb6b3d631 feat(android): close behaviour gaps between the APK and the browser
Several behaviours diverged between the web build and the Capacitor
wrapper, none of them reproducible on a desktop browser.

- Declare VIBRATE. PlayerBoard calls navigator.vibrate() on every cell
  tap; a WebView app must hold the permission itself, where a browser
  holds it on the page's behalf. Taps were silent in the APK.
- Hold a screen wake lock while numbers remain to be called. Auto-call
  advances on a timer with no touch input, so a round could run 15
  minutes untouched — long enough for the display to sleep and the
  WebView to throttle the interval. Android drops the lock whenever the
  page hides, so wake-lock.js re-acquires on visibilitychange.
- Handle back through OnBackPressedCallback. Android 16 (targetSdk 36)
  no longer calls onBackPressed() nor dispatches KEYCODE_BACK. Each open
  overlay pushes one history entry, so "the WebView can go back" means
  "an overlay is open": back closes the bingo modal or settings sheet,
  and only at the root does it confirm before quitting. Browsers get the
  overlay behaviour too.
- Inset content by env(safe-area-inset-*) with viewport-fit=cover.
  targetSdk 36 forces edge-to-edge, so the WebView painted under the
  status bar and gesture nav. Also fixes the iOS PWA notch.
- Pin the WebView's textZoom to 100 and add a "Cỡ chữ bảng" setting.
  The player card is a fixed 9-column grid that clips at large system
  font scales. Pinning the zoom overrides an accessibility control, so
  the app now owns an equivalent one; the two belong together.
- Point the volume rocker at the media stream, so it works before the
  first clip plays.

strings.xml also gains the exit-dialog strings and picks up the
diacritics in app_name.
2026-08-14 15:20:32 +07:00
tiennm99 c6c839fd95 ci: consolidate web and android workflows into one pipeline
Both subprojects ship from the same commit, so ordinary CI is now a single
ci.yml; only the tag-driven release stands apart. The web app is built twice
per run — once per base path — and every consumer downloads the artifact
instead of rebuilding, replacing three redundant base-"" builds on main.

Nothing deploys unless the test job is green, and android-release runs the
suite before signing (ci.yml does not fire on tags, so it was the only gap).

Shared toolchain setup moves into composite actions, which puts the web build
and the APK on the same Node version for the first time. The Firebase PR path
was still on npm ci against a stale web/package-lock.json that could resolve a
different tree than pnpm-lock.yaml; drop the lockfile and the npm path with it.

Also: least-privilege permissions widened per job, persist-credentials off on
every checkout, concurrency groups that cancel superseded PRs but never a live
deploy, npm caching for android, and the Firebase action pinned by commit SHA
to match how the release actions were already pinned.
2026-08-09 15:21:23 +07:00
tiennm99 dfc9f67e0c chore(android): bump version to 0.0.3 (versionCode 3) 2026-08-09 11:52:53 +07:00
tiennm99 7eabfa27f9 docs: add Play Store auto-publish setup guide 2026-08-05 14:00:35 +07:00
tiennm99 d9be3cb66f chore(android): bump version to 0.0.2 (versionCode 2) 2026-08-05 13:36:33 +07:00
tiennm99 cf7690e46f docs: describe the web/ + android/ layout at the repository root
Adds a root README covering both subprojects and the CI matrix, folds the two
identical Apache-2.0 copies into a single root LICENSE, and updates the web
docs that named workflow files by their pre-move paths.
2026-08-03 18:06:49 +07:00
tiennm99 59b884c47e ci: move workflows to the repository root
GitHub only runs workflows from .github/workflows at the root, so the six
workflows inherited from the two projects have to live there rather than under
web/ and android/. Each one gets a path filter so web-only changes do not
trigger Android builds, a working directory for its subproject, and a prefixed
filename to keep the two sets apart.

Gradle now sits at android/android, the Firebase action reads its config from
web/ via entryPoint, and the Android checkout no longer needs submodules.
2026-08-03 18:04:39 +07:00
tiennm99 d1b68b2fe4 build(android): build from web/ instead of the loto submodule
web/ and android/ now live in one repository, so the pinned submodule at
android/loto is redundant. Point Capacitor's webDir and the build script at
../web directly, and drop the pin-bumping workflow from the docs.
2026-08-03 18:02:13 +07:00
tiennm99 0219f88e53 chore: update loto submodule 2026-07-21 22:25:14 +07:00
tiennm99 165020b25b feat(assets): add Google Play store graphics 2026-07-21 21:48:39 +07:00
tiennm99 cc14048ab9 chore(release): prepare v0.0.1 2026-07-21 14:38:25 +07:00
tiennm99 fe639c13c5 docs: document Android release signing 2026-07-21 14:38:08 +07:00
tiennm99 8c5388732d ci: update actions to Node 24 runtimes 2026-07-21 11:27:22 +07:00
tiennm99 90a5fda452 ci: name debug artifact as apk 2026-07-21 09:24:34 +07:00
dependabot[bot]andGitHub a2030686f0 chore(deps-dev): bump brace-expansion from 5.0.6 to 5.0.7
Bumps [brace-expansion](https://github.com/juliangruber/brace-expansion) from 5.0.6 to 5.0.7.
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/v5.0.6...v5.0.7)

---
updated-dependencies:
- dependency-name: brace-expansion
  dependency-version: 5.0.7
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-21 02:08:14 +00:00
tiennm99 6364d93067 chore(deps-dev): bump tar to 7.5.20 to resolve advisory 2026-07-17 18:00:18 +07:00
tiennm99 4c8fff0a25 fix: build loto submodule with pnpm 2026-07-02 09:16:14 +07:00
tiennm99 a4e603bb59 fix: update loto submodule for vite patch 2026-07-02 09:11:24 +07:00
tiennm99 6c9433c02e docs(plans): record Play Store rollout sequence + session progress 2026-05-10 03:01:35 +07:00
tiennm99 07bbefee0b feat(ci): optional Play Store auto-publish on release tags
Adds a gated step in release.yml that uploads the signed AAB to the
Google Play Console internal track when PLAY_SERVICE_ACCOUNT_JSON is
configured. No-op if the secret is missing, so existing tag releases
keep working unchanged.

Default track is 'internal' for safety; change to alpha/beta/production
once trusted. Promotion can also be done via Play Console UI.

First Play Store upload must still be manual (Google policy).
2026-05-10 02:57:35 +07:00
tiennm99 f842deb46c feat(android): make APK installable on BlueStacks and other emulators
- Declare touchscreen/faketouch/screen.portrait/screen.landscape as
  uses-feature required=false so emulators (BlueStacks reports faketouch)
  and Play Store device filters do not exclude the app.
- Drop explicit webContentsDebuggingEnabled override so Capacitor
  auto-enables WebView inspection in debug builds (chrome://inspect over
  ADB) and disables in release.
- Document the install + troubleshoot path in README.
2026-05-10 02:45:26 +07:00
tiennm99 df1bcdced1 fix(ci): bump JDK to 21 (Capacitor 8 compiles with sourceCompatibility 21) 2026-05-10 02:11:32 +07:00
tiennm99 b2c839c902 fix(ci): bump Node to 22 (Capacitor 8 CLI requires >=22) 2026-05-10 02:05:06 +07:00
tiennm99 7840aa419d feat: migrate to Capacitor wrapper for fully-offline Android
Replace the native Kotlin/Compose port with a Capacitor 8 wrapper around
the upstream loto SvelteKit PWA. Loto is consumed as a git submodule and
its static build is bundled into the APK at sync time. The wrapper has
no INTERNET permission, so fully offline is enforced rather than
conventional.

- loto/ submodule pinned to dfb1c1e
- android/ scaffolded by cap add android; signing wired via env vars
- All 184 voice MP3s + service worker bundled in assets/public/
- CI checkouts submodule, runs npm build, then Gradle inside android/
- Native port preserved in git history at e7fb3d0 and 8b8d46e
2026-05-10 02:03:16 +07:00
tiennm99 7f5b4bac95 chore: add Apache-2.0 license 2026-04-29 21:33:26 +07:00
tiennm99 e80087c339 chore(plans): record 260429-1511 upstream-sync plan + reports
Plan directory and recon/code-review reports for the 6-commit
upstream sync from tiennm99/loto @ 2fb35f2.
2026-04-29 16:26:23 +07:00
tiennm99 593c270211 refactor: KinhModal title color to solid amber (palette refactor)
- Remove gradient brush from "Kinh!" title, use solid BrandAmber600.
- Align with solid-color palette refactor (phase 03).
- Update button and text colors to new palette.

Upstream: a60ea08
2026-04-29 16:25:01 +07:00
tiennm99 f1bc58a6df chore: refactor master/player UI components for consistency
- Minor UI alignment and spacing adjustments across master panel and
  player board screens.
- Improve code consistency and readability in Compose components.
- Update project todo list with implementation notes.

Upstream: a60ea08 (sync completion)
2026-04-29 16:24:49 +07:00
tiennm99 60df1dd7d0 feat: per-mode voice-master hint copy in settings
- Add mode-specific voice hint messages (player vs master mode).
- Update VoiceToggles to show contextual hint based on current mode.
- Improve UX clarity in settings sheet.

Upstream: a60ea08
2026-04-29 16:24:38 +07:00
tiennm99 11825315e0 refactor: solid-color palette refactor (BrandSky, removal of gradients)
- Replace BrandPink/BrandIndigo with BrandSky variants (light/dark).
- Add BrandRose600, BrandAmber600, BrandEmerald600.
- Add BackgroundCream for light mode background.
- Switch light theme background from white to BackgroundCream.
- Keep wordmark gradient (only intentional gradient in brand).

Upstream: a60ea08
2026-04-29 16:24:27 +07:00
tiennm99 793788c78c feat: section "Chờ" ring on label with pulse animation
- Add sectionHasWaiting state to track which sections have rows one cell
  from bingo (in Chờ state).
- Render amber ring + 1.5s pulse on section labels when section has waiting.
- Skip animation if accessibility motion is disabled.
- Add BrandAmberLight color for the ring.

Upstream: a60ea08
2026-04-29 16:24:16 +07:00
tiennm99 ec0a8567dd feat: confetti celebration tier rule and emoji set
- Fire confetti on 2nd bingo OR 1st bingo when another row is in Chờ
  (waiting for one number). Replaces old ≥3 threshold that rarely fired.
- Add Vietnamese hội-chợ emoji: 🥢 🎋 🏮 (chopsticks, bamboo, lantern)
- Per-particle size jitter (1.5–2.4x) for visual interest

Upstream: a60ea08
2026-04-29 16:24:05 +07:00
miti99 f7cbb6e1c7 docs: add post-implementation todo list
Tracks build verification, missing tests, polish items, distribution
prep, and future-feature backlog. Replaces the implemented phase docs
which were deleted post-shipping.
2026-04-27 20:21:37 +07:00
miti99 90a9dd1d1a feat: initial Android port of tiennm99/loto
Native Android port of the SvelteKit Lô tô hội chợ Tân Tân web app.

Stack:
- Kotlin 2.1 + Jetpack Compose + Material 3
- Single Activity, single Gradle module (:app)
- Audio: AndroidX Media3 ExoPlayer with bundled MP3 voice clips
- Settings: DataStore Preferences with legacy masterMode migration
- minSdk 24, targetSdk 35, JDK 17

Game logic:
- 9x9 player card with exactly 5 numbers per row AND per column
- Soft constraint: no 3 consecutive filled columns (rejection-sampled)
- 11x9 ones-digit-aligned master tracking board
- Bingo / Cho ("waiting") state machine ported from PlayerBoard.svelte
- Forward-only auto-tick from MasterPanel via app-scoped CallBus

Audio:
- 184 MP3s pre-generated with Microsoft Edge TTS (Hoai My, Nam Minh)
- ExoPlayer playlist for cho + N gapless sequence
- Token-based cancellation matching the web voice.js semantics

CI:
- build-debug.yml: lint + test + assembleDebug on push/PR
- release.yml: signed AAB+APK on v* tag, env-driven keystore
2026-04-27 19:33:16 +07:00