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>
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user