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
This commit is contained in:
tiennm99 committed 2026-05-10 02:03:16 +07:00
1 parent 0d3d822b36
commit 7840aa419d
315 files changed
+2672 -4836

No files matched your search

@@ -1,46 +0,0 @@
package com.miti99.loto
import androidx.compose.ui.test.assertCountEquals
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onAllNodesWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
/**
* Compose UI smoke test — verifies the full cold-start → generate-grid flow.
*
* Requires a connected device or emulator. Run via:
* ./gradlew :app:connectedDebugAndroidTest
*
* Phase 11 spec: launch MainActivity, assert "Tạo bảng mới" shown,
* click it, assert all 81 player_cell nodes are rendered.
*/
@RunWith(AndroidJUnit4::class)
class MainActivityComposeSmokeTest {
@get:Rule
val rule = createAndroidComposeRule<MainActivity>()
@Test
fun generate_button_is_visible_on_cold_start() {
rule.onNodeWithText("Tạo bảng mới").assertExists()
}
@Test
fun generate_button_creates_81_player_cells() {
// Tap the generate button — no confirmation dialog on first tap (grid is null)
rule.onNodeWithText("Tạo bảng mới").performClick()
// Wait up to 5 s for all 81 cells to appear (grid generation is synchronous
// but Compose may take a frame to recompose)
rule.waitUntil(timeoutMillis = 5_000) {
rule.onAllNodesWithTag("player_cell").fetchSemanticsNodes().size == 81
}
rule.onAllNodesWithTag("player_cell").assertCountEquals(81)
}
}
-31
View File
@@ -1,31 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.VIBRATE" />
<application
android:name=".LotoApp"
android:allowBackup="false"
android:dataExtractionRules="@xml/backup_rules"
android:fullBackupContent="false"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="false"
android:theme="@style/Theme.Loto">
<activity
android:name=".MainActivity"
android:exported="true"
android:configChanges="orientation|screenSize|keyboardHidden|uiMode"
android:screenOrientation="portrait"
android:theme="@style/Theme.Loto">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,16 +0,0 @@
{
"voices": [
{
"id": "hoai-my",
"edgeName": "vi-VN-HoaiMyNeural",
"label": "Hoai My (nữ)",
"gender": "female"
},
{
"id": "nam-minh",
"edgeName": "vi-VN-NamMinhNeural",
"label": "Nam Minh (nam)",
"gender": "male"
}
]
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loaded 100 of 315 files, more files were not shown because too many files have changed in this diff. Show more