5.1 KiB
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.ActioncomPendingIntentpara umBroadcastReceiver - iOS:
UNNotificationActiontratada 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
409mostra 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)