Navegação
GoRouter, menu inferior, onde o app abre em debug vs produção e transições.
Onde o app abre? (debug vs produção)
Em debug (kasy run) o app pode voltar na última tela em que você estava (ex.: /admin, /components). Isso é intencional: facilita o dia a dia e o hot restart (R), que reinicia o app do zero sem perder o contexto.
Em produção (build de release publicado na loja ou na web) esse comportamento não existe. O app não "lembra" a última tela entre sessões.
| Situação | O que acontece |
|---|---|
| Nativo em produção (fecha e abre de novo) | Sempre começa em /. Se logado, vai para Home. Se não, login ou onboarding |
Web em produção (seudominio.com/) | Abre na URL que o usuário digitou. Raiz / → Home (após auth). /settings → Configurações |
Debug (kasy run, hot restart R) | Pode retomar a última rota visitada, inclusive telas internas como admin |
Resumo: debug e produção não funcionam igual na tela inicial. Se você rodou kasy run --web, abriu localhost:5555 e caiu no admin, isso não significa que o usuário final vai ver isso ao abrir o app.
Web: URL é o endereço da tela
Na web publicada, a barra de endereço manda. Bookmark, F5 e link compartilhado funcionam porque cada rota tem uma URL (/premium, /admin/kanban, etc.). Rotas protegidas (ex.: admin) ainda passam pelo redirect de auth: quem não é admin é mandado para Home.
Deep links
Para abrir uma tela específica em produção:
- Web: use a URL direta (
https://seuapp.com/settings) - Push: envie o campo
routeno payload da notificação (veja Push) - Nativo (link externo): configure universal links / app links no iOS e Android para o OS entregar a URL ao app
Como testar como produção no seu Mac
flutter run --release # nativo
flutter run --release -d chrome # web em releaseEm release, o app sempre cold-start em / (nativo) ou na URL aberta (web), como o usuário final vai ver.
Três camadas de navegação
| Camada | Arquivo | Uso |
|---|---|---|
| App global | lib/router.dart | context.go('/premium'), context.push('/feedback') |
| Abas inferiores | lib/core/bottom_menu/ | Home, Wishlist, Notificações, Configurações |
| Onboarding | onboarding_page.dart | Navigator interno (feature_1 → paywall) |
Sem BuildContext (notifiers)
ref.read(goRouterProvider).go('/signin');
ref.read(goRouterProvider).push('/premium');Mudar animações de todo o app
Um arquivo controla os padrões:
lib/core/navigation/kasy_navigation_config.dart
class KasyNavigationConfig {
static KasyTransitionKind push = KasyTransitionKind.fade; // maioria das telas
static KasyTransitionKind replace = KasyTransitionKind.fade;
static KasyTransitionKind authPeer = KasyTransitionKind.sharedAxisScaled; // login e cadastro
static KasyTransitionKind bottomTab = KasyTransitionKind.fade; // abas do menu
static KasyTransitionKind onboardingStep = KasyTransitionKind.fade; // passos do onboarding
static const Duration duration = Duration(milliseconds: 250);
static const Curve curve = Curves.easeOutCubic;
}Tipos disponíveis: fade, fadeThrough, sharedAxisScaled, sharedAxisHorizontal, none.
| Quero mudar | Onde mexer |
|---|---|
| A animação padrão do app | push |
| A troca entre login e cadastro | authPeer |
| A troca de abas do menu inferior | bottomTab |
| Os passos do onboarding | onboardingStep |
| Velocidade ou curva de tudo | duration e curve |
Tela nova com a transição do kit
Em rota nova use sempre pageBuilder com kasyTransitionPage, nunca só builder:
GoRoute(
path: '/minha_tela',
pageBuilder: (context, state) => kasyTransitionPage(
key: state.pageKey,
child: const MinhaTelaPage(),
),
),Para fugir do padrão em uma rota específica, passe transition::
pageBuilder: (context, state) => kasyTransitionPage(
key: state.pageKey,
transition: KasyTransitionKind.none,
child: const MinhaRotaTecnica(),
),O que fica fora da padronização (de propósito)
| Tipo | Exemplos | Motivo |
|---|---|---|
| Diálogos | showAppDialog, showGeneralDialog | Animação nativa do sistema |
| Bottom sheets | showModalBottomSheet | Comportamento do sistema |
| Animação dentro da tela | AnimatedSwitcher, flutter_animate | É UX local, não troca de rota |
O ganho de padronizar está na troca de telas cheias. Não force kasyTransitionPage em diálogo ou sheet.
Sub-telas dentro de uma aba
Telas que abrem dentro de uma aba do menu inferior (por exemplo Configurações e Lembretes) não são rotas soltas: elas usam o caminho interno da aba, com bartInnerPath('settings', 'reminder') ou o atalho settingsInnerPath('reminder'). O primeiro segmento tem que ser o id da aba (home, wishlist, notifications, settings), senão a barra de abas perde a referência e quebra. Telas globais (Premium, Feedback, Assistente) continuam com context.push('/rota') no GoRouter.
Quem decide para onde ir
Redirect e guard não definem efeito visual, só destino:
| Arquivo | Função |
|---|---|
lib/core/navigation/auth_redirect.dart | Redirect global do GoRouter: manda para login, onboarding ou Home conforme a sessão |
lib/core/navigation/auth_route_policy.dart | As regras que esse redirect consulta (onboarding concluído, sessão de convidado, etc.) |
lib/core/guards/guard.dart | Guard de widget: espera uma checagem assíncrona e cai numa rota de fallback |
lib/core/security/biometric_guard.dart | Exige Face ID / biometria antes de mostrar a tela |
lib/environments.dart e lib/core/config/features.dart | Regras por plataforma e por feature ligada |
Barras que somem ao rolar
No celular, ao rolar para baixo a app bar (topo) e o menu inferior somem juntos; ao voltar para cima, ou ao chegar no topo, eles reaparecem. É o comportamento de Instagram e Facebook, e dá mais área de conteúdo. No tablet e no desktop a navegação é a sidebar, que nunca se esconde.
Por padrão o app mostra um toggle para o usuário final em Configurações → Preferências → "Ocultar barras ao rolar". Quem cria o projeto controla isso por duas constantes em lib/core/chrome/chrome_visibility.dart:
/// Mostra (true) ou esconde (false) o toggle na tela de Configurações.
const bool kShowHideChromeOnScrollSetting = true;
/// Comportamento padrão: true = esconde ao rolar, false = barras fixas.
const bool kHideChromeOnScrollDefault = true;| Objetivo | O que fazer |
|---|---|
| Deixar o usuário final escolher (padrão) | kShowHideChromeOnScrollSetting = true |
| Tirar a opção da tela de Configurações | kShowHideChromeOnScrollSetting = false |
| Padrão "esconder ao rolar" | kHideChromeOnScrollDefault = true |
| Padrão "barras sempre fixas" | kHideChromeOnScrollDefault = false |
Com o toggle escondido, o comportamento fica travado no valor de kHideChromeOnScrollDefault e o usuário final não consegue mudar.
Android, iOS e web iguais
As transições são centralizadas. Não fica Zoom no Android e Cupertino no iOS por acidente.
Atualizado em 03/11/2026

