Permissões — Visão Geral
Última atualização: 16 de julho de 2026
O que é este fluxo
É o onboarding de permissões nativas do dispositivo: localização, câmera, galeria de arquivos/fotos, notificações e execução em segundo plano (que no Android significa pedir isenção da otimização de bateria, e no iOS significa habilitar "Atualização em Segundo Plano"). Um modal global pede essas permissões uma a uma, com uma tela de progresso, logo depois do app carregar as fontes e antes de qualquer interação normal com o resto do sistema.
Arquivos-chave
| Arquivo | Responsabilidade |
|---|---|
services/permissions.service.ts | Define as permissões do onboarding e como checar/pedir cada uma |
features/permission/permission.modal.tsx | O componente que de fato aparece hoje, montado direto na raiz do app |
storage/background.storage.ts | Guarda se o usuário já foi levado ao intent de bateria do Android |
Onde o modal é montado e quando ele aparece
PermissionModal é renderizado incondicionalmente em app/_layout.tsx, fora da <Stack> de rotas e depois de todos os providers (autenticação, organização, assistente virtual), funcionando como um overlay por cima de qualquer tela ativa:
// app/_layout.tsx
<Stack ...>...</Stack>
<PermissionModal />
<StatusBar style="dark" />
Ele só fica visível de fato se sobrar alguma permissão pendente ao montar:
// permission.modal.tsx
useEffect(() => {
(async () => {
const missing = await refresh(); // chama getMissingPermissions()
if (missing.length > 0) setVisible(true);
setLoaded(true);
})();
}, []);
Há também um listener de AppState que reavalia toda vez que o app volta a ficar active, fechando o modal sozinho se o usuário concedeu a permissão pendente pelas Configurações do sistema e voltou:
const sub = AppState.addEventListener("change", (next) => {
if (next !== "active") return;
refresh().then((missing) => {
if (missing.length === 0) setVisible(false);
});
});
Não é bloqueante
Mesmo aparecendo como um Modal de tela cheia, existe um botão "Agora não" que apenas fecha a exibição sem persistir nenhuma escolha, e o botão físico de voltar do Android tem o mesmo efeito (onRequestClose={handleSkipForNow}). O vendedor sempre consegue pular o onboarding e seguir usando o resto do app com permissões pendentes.
Como o estado de "visível" é só useState local do componente, sem nenhuma persistência, o modal reaparece em todo cold start do app enquanto sobrar alguma permissão não concedida. Não existe cooldown, contagem de tentativas ou "não mostrar de novo" para nenhuma das permissões, com uma única exceção tratada em Fluxo por Permissão e Particularidades de Plataforma.
As permissões pedidas
// permissions.service.ts
export type PermissionId =
| "location"
| "camera"
| "mediaLibrary"
| "notifications"
| "batteryOptimization"
| "backgroundRefresh";
| Permissão | Título exibido | Plataforma |
|---|---|---|
location | Localização | Ambas |
camera | Câmera | Ambas |
mediaLibrary | Arquivos e fotos | Ambas |
notifications | Notificações | Ambas |
batteryOptimization | Execução em segundo plano | Só Android |
backgroundRefresh | Atualização em segundo plano | Só iOS |
PERMISSION_STEPS monta essa lista combinando os quatro passos comuns com o passo específico da plataforma em tempo de execução (Platform.OS === "android" ? ANDROID_STEPS : IOS_STEPS).
Armadilhas conhecidas
Nem toda permissão pedida aqui acaba sendo de fato usada em algum lugar do app, e existe até uma tela e uma rota inteiras desse fluxo que ficaram órfãs de um refactor anterior. Ambos os casos são detalhados em Uso Real e Armadilhas Conhecidas.