Files
backspace/.github/workflows/android.yml
T
devsyncwrldandClaude Opus 5 f808f2f535 feat(mobile): Android app shell with Capacitor
The web client already has a full mobile UI (MobileShell and 17 sibling
screens) and ships as a PWA, so what was missing to get an APK was a native
container, not an interface.

The WebView loads the instance over the network instead of bundling a copy of
the web build. Bundling would mean republishing an APK on every site deploy —
there is no electron-updater equivalent here — and it would move the page to
https://localhost, turning every same-origin /api and /ws call into a
cross-origin one. The cost is no offline mode, which for a realtime chat app
costs nothing: public/index.html covers the unreachable-server case.

packages/mobile sits outside the pnpm workspace on purpose. Capacitor's CLI and
Gradle both assume a flat node_modules, and keeping it out leaves pnpm-lock.yaml
and the existing web/desktop CI untouched by it.

Icons are generated from the existing brand art. The launch screen is a
layer-list over the brand background rather than Capacitor's per-density
splash.png set, which is light and flashed white before the WebView painted.

CI builds an *unsigned* APK. The signing key stays on the VPS and is applied
there — an APK signature is what proves an update came from us, so the key has
no reason to exist on a second machine.

Background voice (foreground service) is not in this commit: a plain WebView is
throttled when the screen goes off and the call drops. That is the next phase.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 13:09:15 -03:00

87 lines
3.1 KiB
YAML

# Compila o APK do Android no GitHub e o devolve como artefato.
#
# O runner entra só como máquina de build, pelo mesmo motivo do desktop: a VPS
# tem ~2 GB de RAM livres e roda a produção, então um Gradle com o SDK do
# Android ali dentro arrisca derrubar as chamadas de quem está usando o app.
#
# O APK sai **sem assinatura**. A chave de assinatura fica na VPS e não sobe
# para cá: quem assina é /opt/ops/release-android.sh, depois de baixar este
# artefato. Um APK assinado é instalável por qualquer um, e a chave é o que
# prova que uma atualização veio de nós — ela não tem por que existir em duas
# máquinas.
name: Android APK
on:
workflow_dispatch:
push:
tags: ['android-v*']
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '21'
- name: Setup Node.js
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
with:
node-version: 22
# npm, não pnpm: packages/mobile fica fora do workspace de propósito
# (ver pnpm-workspace.yaml). Ele tem o próprio package-lock.json.
- name: Install Capacitor
working-directory: packages/mobile
run: npm ci
# Copia a config e os arquivos de public/ para dentro do projeto nativo.
# Sem isso o APK sai com o que estiver commitado em assets/, que pode
# estar velho — esses arquivos são gerados, não versionados.
- name: Sync Capacitor
working-directory: packages/mobile
run: npx cap sync android
- name: Cache Gradle
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ hashFiles('packages/mobile/android/**/*.gradle', 'packages/mobile/android/gradle/wrapper/gradle-wrapper.properties') }}
restore-keys: gradle-
- name: Build unsigned release APK
working-directory: packages/mobile/android
run: ./gradlew assembleRelease --no-daemon --stacktrace
# O nome do arquivo muda conforme a versão do AGP (app-release-unsigned.apk
# ou app-release.apk quando não há config de assinatura). Localiza em vez
# de supor, e falha alto se nada saiu — um artefato vazio só apareceria
# como erro lá na frente, na hora de assinar.
- name: Collect APK
run: |
set -euo pipefail
mkdir -p out
found=$(find packages/mobile/android/app/build/outputs/apk/release -name '*.apk' | head -1)
[ -n "$found" ] || { echo "::error::o build não produziu APK nenhum"; exit 1; }
cp "$found" out/resenhacord-unsigned.apk
ls -l out/
- name: Upload APK artifact
uses: actions/upload-artifact@v4
with:
name: resenhacord-apk-unsigned
path: out/resenhacord-unsigned.apk
retention-days: 14
if-no-files-found: error