Pular para o conteúdo principal

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

ArquivoResponsabilidade
services/permissions.service.tsDefine as permissões do onboarding e como checar/pedir cada uma
features/permission/permission.modal.tsxO componente que de fato aparece hoje, montado direto na raiz do app
storage/background.storage.tsGuarda 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ãoTítulo exibidoPlataforma
locationLocalizaçãoAmbas
cameraCâmeraAmbas
mediaLibraryArquivos e fotosAmbas
notificationsNotificaçõesAmbas
batteryOptimizationExecução em segundo planoSó Android
backgroundRefreshAtualização em segundo planoSó 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.