Governed substrate for autonomous agents: scoped identity (passports), audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
171 lines
5.6 KiB
Dart
171 lines
5.6 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
import '../features/auth/domain/auth_state.dart';
|
|
import '../features/auth/providers/auth_provider.dart';
|
|
import '../features/auth/ui/login_screen.dart';
|
|
import '../features/chat/ui/chat_screen.dart';
|
|
import '../features/chat/ui/conversation_screen.dart';
|
|
import '../features/crm/ui/tickets_screen.dart';
|
|
import '../features/crm/ui/contacts_screen.dart';
|
|
import '../features/frame/ui/frame_connect_screen.dart';
|
|
import '../features/frame/ui/voice_mode_screen.dart';
|
|
import '../features/frame/ui/debug_panel_screen.dart';
|
|
import '../features/documents/ui/documents_screen.dart';
|
|
import '../features/modules/ui/modules_screen.dart';
|
|
import '../features/pes/ui/pes_welcome_screen.dart';
|
|
import '../features/settings/ui/settings_screen.dart';
|
|
import '../shared/widgets/main_shell.dart';
|
|
|
|
/// Route paths.
|
|
class Routes {
|
|
Routes._();
|
|
static const String login = '/login';
|
|
static const String modules = '/modules';
|
|
static const String pes = '/pes';
|
|
static const String pesWelcome = '/pes/welcome';
|
|
static const String chat = '/chat';
|
|
static const String conversation = '/chat/:id';
|
|
static const String frame = '/frame';
|
|
static const String voiceMode = '/voice';
|
|
static const String tickets = '/tickets';
|
|
static const String contacts = '/contacts';
|
|
static const String documents = '/documents';
|
|
static const String settings = '/settings';
|
|
static const String debug = '/debug';
|
|
|
|
// Legacy — kept for compatibility, redirects to /tickets
|
|
static const String crm = '/crm';
|
|
}
|
|
|
|
/// GoRouter provider with auth redirect.
|
|
final routerProvider = Provider<GoRouter>((ref) {
|
|
final authState = ref.watch(authStateProvider);
|
|
|
|
return GoRouter(
|
|
initialLocation: Routes.modules,
|
|
debugLogDiagnostics: false,
|
|
redirect: (context, state) {
|
|
// Don't redirect while auth state is still loading (prevents race condition
|
|
// with deep link callback where token is being processed asynchronously)
|
|
if (authState.isLoading) return null;
|
|
|
|
final authData = authState.valueOrNull;
|
|
// Also don't redirect while auth is in AuthLoading state (Google callback in progress)
|
|
if (authData is AuthLoading) return null;
|
|
|
|
final isLoggedIn = authData?.isAuthenticated ?? false;
|
|
final isLoginRoute = state.matchedLocation == Routes.login;
|
|
|
|
if (!isLoggedIn && !isLoginRoute) {
|
|
return Routes.login;
|
|
}
|
|
if (isLoggedIn && isLoginRoute) {
|
|
return Routes.modules;
|
|
}
|
|
|
|
// Legacy /crm redirect to /tickets
|
|
if (state.matchedLocation == Routes.crm) {
|
|
return Routes.tickets;
|
|
}
|
|
|
|
return null;
|
|
},
|
|
routes: [
|
|
// Login (no shell)
|
|
GoRoute(
|
|
path: Routes.login,
|
|
builder: (context, state) => const LoginScreen(),
|
|
),
|
|
|
|
// Main app with bottom navigation shell
|
|
ShellRoute(
|
|
builder: (context, state, child) => MainShell(child: child),
|
|
routes: [
|
|
// Modules tab (main/home)
|
|
GoRoute(
|
|
path: Routes.modules,
|
|
builder: (context, state) => const ModulesScreen(),
|
|
),
|
|
|
|
// PES (accessible from Modules)
|
|
// Stage 1 of onboarding: temporarily route /pes to the welcome
|
|
// screen so NIKITRON sees the vessel-heart immediately. Stage 6
|
|
// will introduce the real "no pet → welcome, has pet → home"
|
|
// gate and restore PesHomeScreen as the /pes target.
|
|
GoRoute(
|
|
path: Routes.pes,
|
|
builder: (context, state) => const PesWelcomeScreen(),
|
|
),
|
|
GoRoute(
|
|
path: Routes.pesWelcome,
|
|
builder: (context, state) => const PesWelcomeScreen(),
|
|
),
|
|
|
|
// Chat tab
|
|
GoRoute(
|
|
path: Routes.chat,
|
|
builder: (context, state) => const ChatScreen(),
|
|
routes: [
|
|
GoRoute(
|
|
path: ':id',
|
|
builder: (context, state) {
|
|
final id = state.pathParameters['id']!;
|
|
return ConversationScreen(conversationId: int.parse(id));
|
|
},
|
|
),
|
|
],
|
|
),
|
|
|
|
// Voice Mode tab
|
|
GoRoute(
|
|
path: Routes.voiceMode,
|
|
builder: (context, state) => const VoiceModeScreen(),
|
|
),
|
|
|
|
// Tickets tab (standalone)
|
|
GoRoute(
|
|
path: Routes.tickets,
|
|
builder: (context, state) => const TicketsScreen(),
|
|
),
|
|
|
|
// Contacts tab (standalone)
|
|
GoRoute(
|
|
path: Routes.contacts,
|
|
builder: (context, state) => const ContactsScreen(),
|
|
),
|
|
|
|
// Documents tab
|
|
GoRoute(
|
|
path: Routes.documents,
|
|
builder: (context, state) => const DocumentsScreen(),
|
|
),
|
|
|
|
// Frame (accessible from Voice/Settings, not in bottom nav)
|
|
GoRoute(
|
|
path: Routes.frame,
|
|
builder: (context, state) => const FrameConnectScreen(),
|
|
),
|
|
|
|
// Settings tab
|
|
GoRoute(
|
|
path: Routes.settings,
|
|
builder: (context, state) => const SettingsScreen(),
|
|
),
|
|
|
|
// Debug Panel (accessible from Frame screen)
|
|
GoRoute(
|
|
path: Routes.debug,
|
|
builder: (context, state) => const DebugPanelScreen(),
|
|
),
|
|
|
|
// Legacy CRM route — redirect handled in redirect callback
|
|
GoRoute(
|
|
path: Routes.crm,
|
|
redirect: (_, __) => Routes.tickets,
|
|
),
|
|
],
|
|
),
|
|
],
|
|
);
|
|
});
|