92 lines
5.1 KiB
Markdown
92 lines
5.1 KiB
Markdown
# FASE 5 — App do morador
|
||
|
||
## Objetivo
|
||
|
||
App Compose Multiplatform (Android + iOS) que **toca de verdade** com o app fechado, permite resolver entrega em um toque na notificação, e atende videochamada.
|
||
|
||
## Pré-requisitos
|
||
|
||
FASES 0–4 concluídas. Ler `docs/04-DESIGN-SYSTEM-UX.md` §4.
|
||
|
||
## O ponto que define esta fase
|
||
|
||
**No iOS, notificação comum não faz o telefone tocar como chamada.** Só PushKit + CallKit fazem. Sem isso o app não funciona como portaria — o morador vê a chamada quando abrir o celular, dez minutos depois, e o visitante já foi embora.
|
||
|
||
E a Apple **obriga**: ao receber um push do PushKit, o app precisa reportar a chamada ao CallKit imediatamente, na mesma execução. Não reportar faz o sistema matar o app e, com reincidência, revogar o direito de receber VoIP push. Não há meio-termo aqui.
|
||
|
||
## Tarefas
|
||
|
||
### 1. Estrutura
|
||
|
||
`apps/composeApp` com Compose Multiplatform, dependendo de `:shared`. Arquitetura MVVM com `ViewModel` compartilhado. Ktor Client para HTTP e WebSocket. Material 3 Expressive com o `Theme.kt` gerado dos tokens.
|
||
|
||
Um único app com dois perfis (morador e operador), decididos por `GET /me`.
|
||
|
||
### 2. Notificação de chamada — Android
|
||
|
||
FCM com prioridade `high`. Notificação `CallStyle` com full-screen intent para visita; notificação com ações para entrega.
|
||
|
||
Permissões necessárias: `POST_NOTIFICATIONS` (13+), `USE_FULL_SCREEN_INTENT` (14+), `FOREGROUND_SERVICE_MICROPHONE` e `FOREGROUND_SERVICE_CAMERA`.
|
||
|
||
**Otimização de bateria é o inimigo silencioso.** Xiaomi, Samsung e Huawei matam apps agressivamente e o push nunca chega. O app precisa detectar e orientar o usuário a isentá-lo — e o painel admin precisa mostrar quem está com push falhando (`05-INFRA-DOCKER.md` §6).
|
||
|
||
### 3. Notificação de chamada — iOS
|
||
|
||
**Visita** → PushKit VoIP push → `CXProvider.reportNewIncomingCall()` **imediatamente**, antes de qualquer chamada de rede. Só depois busque os detalhes da visita.
|
||
|
||
**Entrega** → notificação comum com `UNNotificationCategory` e ações. CallKit para uma entrega seria intrusivo e é risco de reprovação na App Review.
|
||
|
||
Entitlement de VoIP configurado. Chave APNs `.p8` no backend. Áudio configurado via `AVAudioSession` com categoria `playAndRecord`.
|
||
|
||
**Nota de App Review:** documente na submissão que o app é VoIP legítimo de controle de acesso. Descreva o fluxo. Apps que usam CallKit para notificação genérica são reprovados — este não é o caso, mas o revisor precisa entender por quê.
|
||
|
||
### 4. Ações de um toque
|
||
|
||
O morador resolve **sem abrir o app**:
|
||
|
||
- Android: `Notification.Action` com `PendingIntent` para um `BroadcastReceiver`
|
||
- iOS: `UNNotificationAction` tratada na extensão
|
||
|
||
Ambas chamam `POST /visits/{id}/resolve` com `Idempotency-Key` gerada no dispositivo. **Trate a falha de rede com retry**: a ação disparada em rede ruim que falha em silêncio é a pior falha possível deste app — o morador acha que autorizou e o entregador continua parado.
|
||
|
||
### 5. Telas
|
||
|
||
**Home** — pendências (recados não resolvidos) no topo, histórico abaixo, estado de conexão visível.
|
||
|
||
**Chamada** — vídeo do visitante em tela cheia, auto-preview no canto. Sobreposto: nome informado, unidade de destino, e o **selo de geofence** (`✓ Na entrada` verde / `⚠ A 340m da portaria` âmbar). Esse selo é a informação de segurança mais útil da tela — alguém acionando de longe é sinal claro. Ações: `Autorizar` (verde, primária), `Negar`, mudo, encerrar.
|
||
|
||
**Detalhe da visita** — foto, mapa do ponto, timeline de `visit_attempts`, player do recado.
|
||
|
||
**Configurações** — regra de entrega por unidade, ordem de toque, dispositivos ativos, notificações.
|
||
|
||
### 6. Registro de dispositivo
|
||
|
||
`POST /devices` no login e a cada renovação de token, enviando `push_token` e — no iOS — também `voip_token`. Renove sempre que o sistema emitir novo token; token expirado é a causa número um de "não recebi a chamada".
|
||
|
||
### 7. Concorrência entre moradores
|
||
|
||
Ao receber `409 VISIT_ALREADY_RESOLVED`, mostre **"Maria já autorizou"** com horário. Nunca um erro técnico — dois moradores respondendo juntos é situação normal, não falha.
|
||
|
||
### 8. Tempo real
|
||
|
||
WebSocket enquanto o app está em foreground, com fallback para push. Ao voltar do background, reconcilia estado via REST antes de confiar no WS.
|
||
|
||
## Critérios de aceite
|
||
|
||
- [ ] **iOS: chamada toca em tela cheia com o app fechado e o telefone bloqueado**
|
||
- [ ] **Android: full-screen intent aparece com o app fechado e a tela apagada**
|
||
- [ ] Entrega resolvida pela notificação, sem abrir o app, em ambas as plataformas
|
||
- [ ] Teste com o app morto por otimização de bateria (Xiaomi/Samsung)
|
||
- [ ] Videochamada funciona com o app vindo do background
|
||
- [ ] `409` mostra quem já resolveu
|
||
- [ ] Ação em rede ruim tem retry e não falha em silêncio
|
||
- [ ] Registro de dispositivo renova o token corretamente
|
||
- [ ] Testado em dispositivos físicos — simulador não recebe push
|
||
- [ ] Acessibilidade: TalkBack e VoiceOver navegam a tela de chamada
|
||
|
||
## Não faça nesta fase
|
||
|
||
- App do operador completo (FASE 7) — perfil de operador fica oculto
|
||
- WhatsApp (FASE 8)
|
||
- Autorizações recorrentes (v2)
|