KasyKasy

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çãoO 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.

Para abrir uma tela específica em produção:

  • Web: use a URL direta (https://seuapp.com/settings)
  • Push: envie o campo route no 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 release

Em 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

CamadaArquivoUso
App globallib/router.dartcontext.go('/premium'), context.push('/feedback')
Abas inferioreslib/core/bottom_menu/Home, Wishlist, Notificações, Configurações
Onboardingonboarding_page.dartNavigator 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 mudarOnde mexer
A animação padrão do apppush
A troca entre login e cadastroauthPeer
A troca de abas do menu inferiorbottomTab
Os passos do onboardingonboardingStep
Velocidade ou curva de tudoduration 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)

TipoExemplosMotivo
DiálogosshowAppDialog, showGeneralDialogAnimação nativa do sistema
Bottom sheetsshowModalBottomSheetComportamento do sistema
Animação dentro da telaAnimatedSwitcher, 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:

ArquivoFunção
lib/core/navigation/auth_redirect.dartRedirect global do GoRouter: manda para login, onboarding ou Home conforme a sessão
lib/core/navigation/auth_route_policy.dartAs regras que esse redirect consulta (onboarding concluído, sessão de convidado, etc.)
lib/core/guards/guard.dartGuard de widget: espera uma checagem assíncrona e cai numa rota de fallback
lib/core/security/biometric_guard.dartExige Face ID / biometria antes de mostrar a tela
lib/environments.dart e lib/core/config/features.dartRegras 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;
ObjetivoO que fazer
Deixar o usuário final escolher (padrão)kShowHideChromeOnScrollSetting = true
Tirar a opção da tela de ConfiguraçõeskShowHideChromeOnScrollSetting = 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