Files
Projeto-Portaria/prompts/FASE-5-app-morador.md
2026-07-22 15:55:55 -03:00

5.1 KiB
Raw Permalink Blame History

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 04 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)