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.
This commit is contained in:
tiennm99 committed 2026-08-31 13:28:48 +07:00
1 parent 8f9b03ca05
commit e314ae01c8
126 files changed
+6390 -2275

No files matched your search

+39 -19
View File
@@ -23,23 +23,43 @@ jobs:
- uses: actions/checkout@v7
with:
persist-credentials: false
# Full history + tags for the versionCode guard below.
fetch-depth: 0
- uses: ./.github/actions/setup-web
# ci.yml does not run on tags, so this is the only gate before signing.
- name: Test web
run: npm test
working-directory: web
# Base "" — the APK loads the bundle from the domain root.
- name: Build web
run: npm run build
working-directory: web
# Play rejects an upload whose versionCode is not higher than the last
# one, but only after the whole build has run — fail fast instead.
# Compares this tag's versionCode against the previous v*.*.* tag,
# reading the wrapper-era path for tags that predate the native app.
- name: Guard against an unbumped versionCode
run: |
set -eu
current=$(grep -oP 'versionCode = \K\d+' android/app/build.gradle.kts)
echo "versionCode at ${GITHUB_REF_NAME}: $current"
prev_tag=$(git tag --list 'v*.*.*' --sort=-v:refname | grep -Fvx "${GITHUB_REF_NAME}" | head -n1 || true)
if [ -z "$prev_tag" ]; then
echo "No previous release tag; skipping comparison."
exit 0
fi
prev=$(git show "$prev_tag:android/app/build.gradle.kts" 2>/dev/null | grep -oP 'versionCode = \K\d+' || true)
if [ -z "$prev" ]; then
# Wrapper-era layout (Capacitor project under android/android/).
prev=$(git show "$prev_tag:android/android/app/build.gradle" 2>/dev/null | grep -oP 'versionCode \K\d+' || true)
fi
if [ -z "$prev" ]; then
echo "::warning::Could not read versionCode at $prev_tag; skipping comparison."
exit 0
fi
echo "versionCode at $prev_tag: $prev"
if [ "$current" -le "$prev" ]; then
echo "::error::versionCode $current at ${GITHUB_REF_NAME} must be greater than $prev at $prev_tag. Bump it in android/app/build.gradle.kts before tagging."
exit 1
fi
- uses: ./.github/actions/setup-android
- name: Sync web bundle into the native project
run: npm run sync
# ci.yml does not run on tags, so this is the only gate before signing.
- name: Lint and test
run: ./gradlew :app:lint :app:test
working-directory: android
- name: Decode keystore
@@ -48,7 +68,7 @@ jobs:
run: printf '%s' "$KEYSTORE_BASE64" | base64 --decode > "$GITHUB_WORKSPACE/keystore.p12"
- name: Build signed AAB + APK
working-directory: android/android
working-directory: android
env:
LOTO_KEYSTORE_PATH: ${{ github.workspace }}/keystore.p12
LOTO_KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
@@ -60,8 +80,8 @@ jobs:
uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2
with:
files: |
android/android/app/build/outputs/apk/release/*.apk
android/android/app/build/outputs/bundle/release/*.aab
android/app/build/outputs/apk/release/*.apk
android/app/build/outputs/bundle/release/*.aab
# Auto-publish to Google Play (gated on the service-account secret).
# Skips silently if PLAY_SERVICE_ACCOUNT_JSON is not configured, so
@@ -89,8 +109,8 @@ jobs:
with:
serviceAccountJsonPlainText: ${{ secrets.PLAY_SERVICE_ACCOUNT_JSON }}
packageName: com.miti99.loto
releaseFiles: android/android/app/build/outputs/bundle/release/*.aab
releaseFiles: android/app/build/outputs/bundle/release/*.aab
tracks: alpha
status: completed
# Bumps versionCode automatically? No — must be incremented
# manually in android/android/app/build.gradle before tagging.
# versionCode is bumped manually in android/app/build.gradle.kts
# before tagging; the guard step above catches a forgotten bump.