4.1 KiB
Android (Capacitor)
Requirements
- Node >= 22
- JDK 17+ (21 used in practice)
- Android SDK with platform 36 + build-tools 36.0.0
- SDK location:
/usr/lib/android-sdk, also set inandroid/local.properties
Config
capacitor.config.ts—appId: chat.vojo.app,webDir: distandroid/— generated Android Studio project,targetSdkVersion 36,compileSdkVersion 36,minSdkVersion 24
Build scripts
npm run build:android:debug # full chain: build → sync → debug APK
npm run build:android:release # full chain: build → sync → release APK
npm run build:android:aab # full chain: build → sync → release AAB
npm run android:sync # sync dist/ → android assets
npm run android:apk:debug # gradle debug build only
APK output: android/app/build/outputs/apk/debug/app-debug.apk
Versioning
versionCode and versionName auto-derived from package.json version:
versionCode = major * 1_000_000 + minor * 1_000 + patch
Key architecture decisions
- Bundled build.
dist/is copied into the APK — not loaded remotely in a WebView. - Service Worker stays active. Critical for authenticated Matrix media (MSC3916 / Matrix spec v1.11+). DO NOT disable.
resolveServiceWorkerRequestsdefaulttrue. - Edge-to-edge.
EdgeToEdge.enable()inMainActivity.java+windowLayoutInDisplayCutoutMode: shortEdges. - External links. Opened via
@capacitor/browserplugin — seesrc/app/utils/capacitor.ts. - Safe-area coloring.
bodybackground-color reads--vojo-safe-area-bg(set on:rootinsrc/app/styles/global.css.ts, default#0d0e11= chat-list tone).Room.tsxretunes the var to#181a20(chat-surface tone) while a chat is mounted so the status-bar / gesture-bar zones never show a seam against the active surface. - Safe-area insets — top / left / right only on
#root. Bottom inset is intentionally not applied at#rootso the app renders edge-to-edge under the Android gesture pill / 3-button bar / iOS home indicator (mirrors WhatsApp / Telegram). Components that anchor interactive UI at the screen bottom MUST addpadding-bottom: var(--vojo-safe-bottom)themselves — covered: chat composer (RoomView.css.ts), PageNav inner column (Page.tsx→ catches SelfRow / WorkspaceFooter / etc.), bottom call rail (HorseshoeContainer.css.ts), AuthFooter (auth/styles.css.ts). New screens with a bottom CTA must follow this rule or the button lands behind a system 3-button nav bar.
VSCode tasks
See .vscode/tasks.json:
Deploy to vojo.chat(Ctrl+Shift+D) — web deployDeploy to Android (ADB)(Ctrl+Shift+A) — build +adb install
Push string resources (generated)
Push notification text for Android is generated from public/locales/{en,ru}.json (namespace Push) by scripts/gen-push-strings.mjs. The Gradle build runs this automatically via GeneratePushStringsTask registered in android/app/build.gradle through AGP addGeneratedSourceDirectory — output goes to build/generated/res/push/<variant>/values{,-ru}/push_strings.xml. No manual step needed; ./gradlew assembleDebug handles it.
The task requires node in PATH. Terminal builds and CI inherit it from the shell. macOS Android Studio with nvm/fnm: the GUI app may not see nvm-managed node. Workaround: set NODE_BIN=/path/to/node in android/gradle.properties (the task reads it via project.findProperty('NODE_BIN')) or launch AS from a shell that sources your node manager (open -a "Android Studio").
ADB wireless workflow
- On the phone, enable Wireless debugging, tap "Pair device with pairing code" — note IP, port, 6-digit code.
adb pair <ip>:<pair-port> <code>adb connect <ip>:<connect-port>
The pair port and the connect port are different — don't mix them up.