Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,35 @@ jobs:
with:
path: dist

# The Android shell must keep compiling on every change — an unbuildable
# android/ dir is exactly the silent rot §14 of the iOS standard warns about.
# Debug build, no signing, no upload; the signed release lives in
# release-android.yml.
android:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '21'
- uses: android-actions/setup-android@v3
with:
packages: platform-tools platforms;android-36 build-tools;36.0.0
- run: npm ci
- run: npm run build
- run: npx cap sync android
- name: Gradle assembleDebug
working-directory: android
run: |
chmod +x gradlew
./gradlew --no-daemon assembleDebug
ls -la app/build/outputs/apk/debug/

deploy:
needs: test
if: github.ref == 'refs/heads/main' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch')
Expand Down
158 changes: 158 additions & 0 deletions .github/workflows/release-android.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
# Android release (Flywheel Standard §7 — docs/standards/android-app.md).
# Tag `android-vX.Y.Z` (or run by hand) → web build → cap sync → signed AAB +
# APK → GitHub Release assets → (when PLAY_SERVICE_ACCOUNT_JSON exists) upload
# to the Google Play internal track. Ships dark without the secrets: the build
# still runs and the unsigned artifacts are attached, so CI never blocks on
# credentials that live outside the repo (Standard §3).
#
# Secrets (repo → Settings → Secrets → Actions), all set by scripts/android-keystore.sh:
# ANDROID_KEYSTORE_BASE64 the upload keystore, base64
# ANDROID_KEYSTORE_PASSWORD · ANDROID_KEY_ALIAS · ANDROID_KEY_PASSWORD
# Optional (Play Console → Setup → API access → service account JSON):
# PLAY_SERVICE_ACCOUNT_JSON
# Plus the web build's VITE_SUPABASE_URL / VITE_SUPABASE_PUBLISHABLE_KEY.
name: Release Android

on:
push:
tags: ['android-v*']
workflow_dispatch:
inputs:
version:
description: versionName (e.g. 1.2.0); defaults to the tag or build.gradle
required: false
default: ''
track:
description: Play track when PLAY_SERVICE_ACCOUNT_JSON is set
required: false
default: internal

permissions:
contents: write

concurrency:
group: release-android
cancel-in-progress: false

jobs:
build:
runs-on: ubuntu-latest
env:
VERSION: ${{ inputs.version != '' && inputs.version || (startsWith(github.ref, 'refs/tags/android-v') && github.ref_name || '') }}
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: '22'
cache: npm

- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '21'

- uses: android-actions/setup-android@v3
with:
packages: platform-tools platforms;android-36 build-tools;36.0.0

- name: Web build (same env as the site — analytics keys are publishable)
env:
VITE_SUPABASE_URL: ${{ secrets.VITE_SUPABASE_URL }}
VITE_SUPABASE_PUBLISHABLE_KEY: ${{ secrets.VITE_SUPABASE_PUBLISHABLE_KEY }}
run: |
npm ci
npm run build
npx cap sync android

- name: Resolve version
id: v
run: |
name="${VERSION#android-v}"
if [ -z "$name" ]; then
name=$(sed -n "s/.*findProperty('versionName') ?: '\([^']*\)'.*/\1/p" android/app/build.gradle)
fi
# monotonic; +100 keeps clear of the iOS build numbers (75 at the time of writing)
code=$((100 + GITHUB_RUN_NUMBER))
echo "name=$name" >> "$GITHUB_OUTPUT"
echo "code=$code" >> "$GITHUB_OUTPUT"
echo "versionName=$name versionCode=$code"

- name: Upload keystore (from secrets; absent → unsigned build, loud notice)
id: ks
env:
KS_B64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
run: |
if [ -z "$KS_B64" ]; then
echo "::warning::ANDROID_KEYSTORE_BASE64 not set — building UNSIGNED. Run scripts/android-keystore.sh once to create + store the upload key."
echo "signed=false" >> "$GITHUB_OUTPUT"; exit 0
fi
echo "$KS_B64" | base64 -d > "$RUNNER_TEMP/upload.jks"
echo "path=$RUNNER_TEMP/upload.jks" >> "$GITHUB_OUTPUT"
echo "signed=true" >> "$GITHUB_OUTPUT"

- name: Gradle — bundleRelease + assembleRelease
working-directory: android
env:
ANDROID_KEYSTORE_PATH: ${{ steps.ks.outputs.path }}
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
run: |
chmod +x gradlew
./gradlew --no-daemon bundleRelease assembleRelease \
-PversionName="${{ steps.v.outputs.name }}" -PversionCode="${{ steps.v.outputs.code }}"
ls -la app/build/outputs/bundle/release app/build/outputs/apk/release

- name: Rename artifacts
run: |
v="${{ steps.v.outputs.name }}"; c="${{ steps.v.outputs.code }}"
mkdir -p out
cp android/app/build/outputs/bundle/release/app-release.aab "out/kam-na-msi-${v}-${c}.aab"
apk=$(ls android/app/build/outputs/apk/release/*.apk | head -1)
cp "$apk" "out/kam-na-msi-${v}-${c}.apk"
ls -la out

- name: Read back — the bundle carries the expected versionName/versionCode + permissions
run: |
aapt=$(ls -d "$ANDROID_HOME"/build-tools/36.0.0/aapt2)
"$aapt" dump badging out/*.apk | grep -E "^package:|uses-permission" | head -8
"$aapt" dump badging out/*.apk | grep -q "versionName='${{ steps.v.outputs.name }}'" || { echo "::error::versionName mismatch"; exit 1; }
if [ "${{ steps.ks.outputs.signed }}" = "true" ]; then
"$ANDROID_HOME"/build-tools/36.0.0/apksigner verify --print-certs out/*.apk | head -3
fi

- uses: actions/upload-artifact@v4
with:
name: kam-na-msi-android-${{ steps.v.outputs.name }}
path: out/*

- name: GitHub Release (tag builds only)
if: startsWith(github.ref, 'refs/tags/android-v')
uses: softprops/action-gh-release@v2
with:
name: Android ${{ steps.v.outputs.name }}
files: out/*
body: |
Kam na mši for Android — versionName ${{ steps.v.outputs.name }}, versionCode ${{ steps.v.outputs.code }}.
`.aab` is the Play Store upload; `.apk` installs directly (sideload / testers).
Signed: ${{ steps.ks.outputs.signed }}.

- name: Google Play — upload to ${{ inputs.track || 'internal' }} (only when the service account exists)
if: steps.ks.outputs.signed == 'true' && env.PLAY_JSON != ''
env:
PLAY_JSON: ${{ secrets.PLAY_SERVICE_ACCOUNT_JSON }}
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJsonPlainText: ${{ secrets.PLAY_SERVICE_ACCOUNT_JSON }}
packageName: org.dravec.bohosluzby
releaseFiles: out/*.aab
track: ${{ inputs.track || 'internal' }}
status: completed
whatsNewDirectory: store-assets/android/whatsnew

- name: Play upload skipped
if: steps.ks.outputs.signed != 'true' || env.PLAY_JSON == ''
env:
PLAY_JSON: ${{ secrets.PLAY_SERVICE_ACCOUNT_JSON }}
run: echo "::notice::Play upload skipped (signed=${{ steps.ks.outputs.signed }}, PLAY_SERVICE_ACCOUNT_JSON set=$([ -n "$PLAY_JSON" ] && echo yes || echo no)). Upload out/*.aab by hand in Play Console, or add the secret (docs/store/play-metadata.md §API)."
14 changes: 14 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,20 @@ offline. Language values are normalized to Czech lowercase at decode (`normalize
build_type=workflow, cname set via API; DNS CNAME managed outside this repo).
Build reads `VITE_SUPABASE_URL`, `VITE_SUPABASE_PUBLISHABLE_KEY` from repo secrets
(set; shared flywheel-core project); the app works fully without them.
- **iOS** → App Store via Capacitor 8 (`ios/`), `fastlane ios release` from `ios/App/`;
listing + privacy answers in `docs/store/ios-metadata.md`, human steps in
`docs/store/asc-submission-checklist.md`.
- **Android** → Google Play via the same Capacitor shell (`android/`, committed; icons/splash
from `resources/` via `npx capacitor-assets generate --android`). Release =
`git tag android-vX.Y.Z && git push origin android-vX.Y.Z` → `.github/workflows/release-android.yml`
builds the web app, `cap sync`, produces a **signed `.aab` + `.apk`** on a GitHub Release and
reads the bundle back (versionName, permissions, signature). Signing needs the four
`ANDROID_*` secrets from a one-time `scripts/android-keystore.sh`; without them it builds
unsigned and says so. `PLAY_SERVICE_ACCOUNT_JSON` (optional) makes the workflow upload to the
Play internal track itself. Listing, data-safety and content-rating answers, and the
owner-only console steps: `docs/store/play-metadata.md`. Screenshots: the `phone` device in
`e2e/store-shots.spec.ts` (1080×1920). Local build check: `cd android && ./gradlew assembleDebug`
(needs `ANDROID_HOME`; CI runs it on every push as the `android` job).
Adoption is read from web KPIs — no extra telemetry needed.

## Analytics (Common Platform)
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ npx playwright test # e2e journeys + screenshoty do e2e/shots/
Build/test/release kontrakt: `AGENTS.md`. Vizuální kontrakt (misálová
typografie, liturgické barvy): `docs/DESIGN-BRIEF.md`.

## Mobilní aplikace

Stejná aplikace běží jako nativní obal (Capacitor 8) pro **iOS** (`ios/`, App Store)
i **Android** (`android/`, Google Play). Android vydání: `git tag android-vX.Y.Z`
→ workflow `release-android.yml` sestaví podepsaný `.aab` + `.apk` na GitHub Release.
Podklady pro obchody: `docs/store/`.

## Licence

Kód: [MIT](LICENSE). Údaje o bohoslužbách: rejstřík ČBK
Expand Down
101 changes: 101 additions & 0 deletions android/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Using Android gitignore template: https://github.com/github/gitignore/blob/HEAD/Android.gitignore

# Built application files
*.apk
*.aar
*.ap_
*.aab

# Files for the ART/Dalvik VM
*.dex

# Java class files
*.class

# Generated files
bin/
gen/
out/
# Uncomment the following line in case you need and you don't have the release build type files in your app
# release/

# Gradle files
.gradle/
build/

# Local configuration file (sdk path, etc)
local.properties

# Proguard folder generated by Eclipse
proguard/

# Log Files
*.log

# Android Studio Navigation editor temp files
.navigation/

# Android Studio captures folder
captures/

# IntelliJ
*.iml
.idea/workspace.xml
.idea/tasks.xml
.idea/gradle.xml
.idea/assetWizardSettings.xml
.idea/dictionaries
.idea/libraries
# Android Studio 3 in .gitignore file.
.idea/caches
.idea/modules.xml
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
.idea/navEditor.xml

# Keystore files
# Uncomment the following lines if you do not want to check your keystore files in.
#*.jks
#*.keystore

# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild
.cxx/

# Google Services (e.g. APIs or Firebase)
# google-services.json

# Freeline
freeline.py
freeline/
freeline_project_description.json

# fastlane
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
fastlane/readme.md

# Version control
vcs.xml

# lint
lint/intermediates/
lint/generated/
lint/outputs/
lint/tmp/
# lint/reports/

# Android Profiling
*.hprof

# Cordova plugins for Capacitor
capacitor-cordova-android-plugins

# Copied web assets
app/src/main/assets/public

# Generated Config files
app/src/main/assets/capacitor.config.json
app/src/main/assets/capacitor.plugins.json
app/src/main/res/xml/config.xml
2 changes: 2 additions & 0 deletions android/app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/build/*
!/build/.npmkeep
Loading
Loading