feat(waiter): Capacitor 8 Android project, native build mode, signed APK tooling

- capacitor.config.json: appId gr.bonamin.xenia, webDir dist-native,
  androidScheme http (origin http://localhost - no mixed-content block when
  calling venue servers over plain HTTP on the LAN)
- vite: `--mode native` builds to dist-native with the PWA plugin disabled
  (no service worker inside the app)
- Android: minSdk 24 / target 36; CAMERA permission for QR pairing;
  cleartext allowed via network_security_config (LAN IPs can't be listed
  per-domain); allowBackup=false so backups never carry login tokens
- Release signing reads ~/.xenia/keystore.properties (override with
  XENIA_KEYSTORE_PROPS) - the key never enters the repo; versionName 1.0.0 /
  versionCode 1 with a bump-both rule for sideloaded updates
- npm scripts build:native, apk:debug, apk:release (scripts/build-apk.mjs);
  APKs land in releases/ (gitignored); release APK is also copied to
  public/downloads/ so venue servers serve it at /downloads/xenia-waiter.apk
- waiter nginx: /downloads/ served as application/vnd.android.package-archive,
  real 404 when missing (never falls through to index.html)
- launcher icons + splash generated from the app icon (assets/ is the source)
- .gitattributes: gradlew LF, *.bat CRLF

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-28 13:31:23 +03:00
co-authored by Claude Opus 5.5
parent 5230606144
commit 0b0f5c60c3
88 changed files with 2167 additions and 46 deletions
+81
View File
@@ -0,0 +1,81 @@
apply plugin: 'com.android.application'
// Release signing key lives OUTSIDE the repo (never commit it):
// %USERPROFILE%\.xenia\keystore.properties (storeFile, storePassword, keyAlias, keyPassword)
// Override the location with the XENIA_KEYSTORE_PROPS env var. Every APK handed to
// a venue must be signed with this same key, or phones refuse to install updates.
def keystorePropsFile = file(System.getenv('XENIA_KEYSTORE_PROPS') ?: "${System.getProperty('user.home')}/.xenia/keystore.properties")
def keystoreProps = new Properties()
if (keystorePropsFile.exists()) {
keystorePropsFile.withInputStream { keystoreProps.load(it) }
}
android {
namespace = "gr.bonamin.xenia"
compileSdk = rootProject.ext.compileSdkVersion
defaultConfig {
applicationId "gr.bonamin.xenia"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
// Bump BOTH for every APK handed out: Android only installs an update over
// an existing install when versionCode is higher.
versionCode 1
versionName "1.0.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
// Default: https://android.googlesource.com/platform/frameworks/base/+/282e181b58cf72b6ca770dc7ca5f91f135444502/tools/aapt/AaptAssets.cpp#61
ignoreAssetsPattern = '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~'
}
}
signingConfigs {
release {
if (keystoreProps['storeFile']) {
storeFile file(keystoreProps['storeFile'])
storePassword keystoreProps['storePassword']
keyAlias keystoreProps['keyAlias']
keyPassword keystoreProps['keyPassword']
}
}
}
buildTypes {
release {
if (keystoreProps['storeFile']) {
signingConfig signingConfigs.release
} else {
logger.warn("Xenia: no keystore at ${keystorePropsFile} - release APK will be UNSIGNED")
}
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
repositories {
flatDir{
dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs'
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
implementation "androidx.coordinatorlayout:coordinatorlayout:$androidxCoordinatorLayoutVersion"
implementation "androidx.core:core-splashscreen:$coreSplashScreenVersion"
implementation project(':capacitor-android')
testImplementation "junit:junit:$junitVersion"
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
implementation project(':capacitor-cordova-android-plugins')
}
apply from: 'capacitor.build.gradle'
try {
def servicesJSON = file('google-services.json')
if (servicesJSON.text) {
apply plugin: 'com.google.gms.google-services'
}
} catch(Exception e) {
logger.info("google-services.json not found, google-services plugin not applied. Push Notifications won't work")
}