amora

Amora — shell nativo (Capacitor)

Empacota o mesmo app web (web/) num app nativo iOS/Android cujo único ganho sobre o PWA é localização em segundo plano: a Localização ao vivo continua transmitindo com a tela apagada / app em segundo plano — o cenário que nenhum navegador cobre (no iOS o watchPosition é suspenso segundos após o bloqueio; no Android a página é congelada).

O app web segue 100% utilizável no navegador. Este shell é um empacotamento adicional, não um substituto. Só instale-o quem precisa transmitir com a tela apagada.

Como funciona

Pré-requisitos

Setup (uma vez)

cd capacitor
npm install
npx cap add ios
npx cap add android
./run-ios.sh --prepare   # cap sync ios + as chaves do Info.plist
npx cap sync android

cap add gera os projetos ios/ e android/ (gitignorados — recriáveis). cap sync instala os plugins nativos e copia a config e o www/ (a tela sem conexão). O www/ é versionado — é o webDir e guarda só essa tela.

Versões em package.json são um ponto de partida. Se o npm install reclamar de incompatibilidade, alinhe tudo na mesma major: npm install @capacitor/core@latest @capacitor/cli@latest @capacitor/ios@latest @capacitor/android@latest @capacitor-community/background-geolocation@latest e rode npx cap sync. As chaves da config abaixo foram conferidas no código do Capacitor 6.2.

Ícones e splash

As artes-fonte ficam versionadas em assets/: icon.png (1024×1024) e splash.png (2732×2732, ícone centralizado no fundo escuro #0f1721). O splash é escuro por identidade visual, mas o app é claro (barra branca): a WebView tem fundo branco (ios.backgroundColor) e a barra de status usa ícones escuros (UIStatusBarStyleDarkContent) — no instante do splash eles somem no fundo escuro, e é só isso. splash-dark.png hoje é idêntico ao splash.png; só vale mantê-lo se um dia for genuinamente distinto. Gere os ícones e telas nativos com:

npm run icons   # capacitor-assets generate

Rode depois do npx cap add (e sempre que trocar as artes), seguido de npx cap sync. Trocar o ícone/splash exige rebuild nativo — não basta republicar o web/.

Config nativa obrigatória

iOS — ios/App/App/Info.plist

O run-ios.sh aplica tudo isto sozinho a cada execução (plutil -replace, idempotente): o ios/ é gerado e o template do Capacitor não traz NENHUMA destas chaves, então todo npx cap add ios as perde. Pelo Xcode, rode ./run-ios.sh --prepare antes do npx cap open ios. Os textos moram no run-ios.sh — mude lá e aqui juntos.

<!-- Localização ao vivo (plugin background-geolocation) -->
<key>NSLocationWhenInUseUsageDescription</key>
<string>Mostra sua posição no mapa e a compartilha ao vivo enquanto você usa o app.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Mantém o compartilhamento da sua localização ao vivo durante o pedal, mesmo com a tela apagada. Você liga e desliga quando quiser.</string>
<key>UIBackgroundModes</key>
<array>
  <string>location</string>
</array>

<!-- Câmera/microfone do seletor de arquivos e gravação nas Fotos -->
<key>NSCameraUsageDescription</key>
<string>Tira fotos e grava vídeos na hora, quando você escolhe a câmera para enviar imagens ao acervo do pedal.</string>
<key>NSMicrophoneUsageDescription</key>
<string>Grava o som dos vídeos que você filma pela câmera do app para enviar ao acervo do pedal.</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>Salva nas suas Fotos as imagens que você pedir para guardar, como a colagem do álbum.</string>

<!-- Ícones escuros na barra de status (a barra do app é branca) -->
<key>UIStatusBarStyle</key>
<string>UIStatusBarStyleDarkContent</string>

<!-- Service worker na WKWebView — só junto com ios.limitsNavigationsToAppBoundDomains -->
<key>WKAppBoundDomains</key>
<array>
  <string>amora.pedalhidrografi.co</string>
  <string>localhost</string>
</array>

A App Store revisa “Always location” com rigor. Justifique com o caso real (compartilhar posição ao vivo durante pedais em grupo), deixe claro que é opt-in e que para na hora ao desligar. Tenha um vídeo/print do toggle pronto.

Android — android/app/src/main/AndroidManifest.xml

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />

O plugin sobe um foreground service com notificação persistente enquanto o watcher está ativo (requisito do Android 10+ para localização em background). O texto da notificação vem de backgroundTitle/backgroundMessage no addWatcher(...) em web/app.js. O Play Console exige uma declaração de uso de localização em background + revisão.

capacitor.config.json — o que cada chave faz

Mudar a config exige cap sync + rebuild nativo (ela vai empacotada no app).

Rodar em device

Pela GUI:

./run-ios.sh --prepare   # sync + Info.plist — sempre, antes do Xcode
npx cap open ios         # abre o Xcode → Run num iPhone físico
npx cap sync android
npx cap open android     # abre o Android Studio → Run num device

Sem abrir a GUI (sync + build + install + launch num device conectado):

./run-ios.sh --list           # lista devices e UDIDs
./run-ios.sh <UDID>           # ou IOS_UDID=<UDID> ./run-ios.sh
./run-android.sh <serial>     # ou ANDROID_SERIAL=<serial> ./run-android.sh

run-ios.sh ainda exige o Xcode instalado e a assinatura configurada uma vez (time de desenvolvimento — ver acima; conta Apple grátis serve, mas o app expira em 7 dias). npx cap run passa pelo xcodebuild e instala no device (devicectl no iOS 17+/Xcode 15+; ios-deploy no iOS ≤16). Como o app carrega o site remoto (server.url), edições só de web/ dispensam rebuild nativo — basta publicar o web/ (ver abaixo quando a versão nova aparece).

Limitações do shell

Teste de aceitação (o que importa: tela apagada)

  1. Instale em um device físico e conceda localização “Sempre”.
  2. Em Configurações → Localização ao vivo, ligue Transmitir minha localização e ponha um apelido.
  3. Bloqueie o telefone e ponha no bolso. De outro aparelho (ou navegador), confirme que o marcador continua se movendo.
  4. Confirme a notificação persistente (Android).
  5. Desligue o toggle → as atualizações param na hora. O marcador e o rastro permanecem visíveis até expirar a retenção escolhida no modal (default 3 h) — desligar só interrompe novos envios. O /live-location/stop (apagar o rastro na hora) existe mas não é disparado automaticamente.

E o resto do shell (iOS):

  1. 📤 enviar imgs → Tirar Foto: aparece o pedido de acesso à câmera (e não o app fechando). Idem toque longo numa imagem → Salvar nas Fotos.
  2. Modo Escuro: relógio e bateria visíveis (escuros) sobre a barra branca.
  3. Service worker: no Safari do Mac (Desenvolvedor → o iPhone → Amora), navigator.serviceWorker.controller não é null a partir da 2ª abertura.
  4. Sem sinal: com o SW já instalado, modo avião + fechar + abrir → o mapa abre do cache. Na primeira abertura da vida sem sinal → tela “Sem conexão”; ao sair do modo avião ela volta pro mapa sozinha.

Publicação (resumo)

Custo real desse caminho: contas de loja, ciclos de revisão e manutenção nativa contínua. Por isso o substrato web (Partes A/B do plano) já entrega a feature para uso em foreground antes deste investimento.