named router with constants to prevent silent bugs

This commit is contained in:
2026-05-09 20:42:42 +02:00
parent 5f39d5b1ad
commit 385c3da0a5
8 changed files with 64 additions and 59 deletions

View File

@@ -4,6 +4,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flux/core/blocs/session/session_cubit.dart'; import 'package:flux/core/blocs/session/session_cubit.dart';
import 'package:flux/core/data/core_repository.dart'; import 'package:flux/core/data/core_repository.dart';
import 'package:flux/core/layout/app_shell.dart'; import 'package:flux/core/layout/app_shell.dart';
import 'package:flux/core/routes/routes.dart';
import 'package:flux/core/widgets/image_upload/blocs/image_upload_cubit.dart'; import 'package:flux/core/widgets/image_upload/blocs/image_upload_cubit.dart';
import 'package:flux/core/widgets/image_upload/ui/image_upload_screen.dart'; import 'package:flux/core/widgets/image_upload/ui/image_upload_screen.dart';
import 'package:flux/core/widgets/set_password_screen.dart'; import 'package:flux/core/widgets/set_password_screen.dart';
@@ -42,27 +43,6 @@ import 'package:flux/features/tickets/ui/ticket_list_screen.dart';
import 'package:get_it/get_it.dart'; import 'package:get_it/get_it.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
const String loginRoute = 'login';
const String setPasswordRoute = 'set-password';
const String onboardingRoute = 'onboarding';
const String homeRoute = 'home';
const String masterDataRoute = 'master-data';
const String productsRoute = 'products';
const String companySettingsRoute = 'company-settings';
const String staffRoute = 'staff';
const String storesRoute = 'stores';
const String providersRoute = 'providers';
const String settingsRoute = 'settings';
const String themeRoute = 'theme';
const String operationsRoute = 'operations';
const String customersRoute = 'customers';
const String ticketsRoute = 'tickets';
const String ticketFormRoute = 'ticket-form';
const String operationFormRoute = 'operation-form';
const String uploadSuccessRoute = 'upload-success';
const String customerFormRoute = 'customer-form';
const String uploadRoute = 'upload';
class AppRouter { class AppRouter {
static GoRouter createRouter(SessionCubit sessionCubit) { static GoRouter createRouter(SessionCubit sessionCubit) {
return GoRouter( return GoRouter(
@@ -113,17 +93,17 @@ class AppRouter {
// --- ROTTE DI SERVIZIO (FUORI DALLA SHELL) --- // --- ROTTE DI SERVIZIO (FUORI DALLA SHELL) ---
GoRoute( GoRoute(
path: '/login', path: '/login',
name: 'login', name: Routes.login,
builder: (context, state) => const AuthScreen(), builder: (context, state) => const AuthScreen(),
), ),
GoRoute( GoRoute(
path: '/set-password', path: '/set-password',
name: 'set-password', name: Routes.setPassword,
builder: (context, state) => const SetPasswordScreen(), builder: (context, state) => const SetPasswordScreen(),
), ),
GoRoute( GoRoute(
path: '/onboarding', path: '/onboarding',
name: 'onboarding', name: Routes.onboarding,
builder: (context, state) => BlocProvider( builder: (context, state) => BlocProvider(
create: (context) => OnboardingCubit( create: (context) => OnboardingCubit(
GetIt.I.get<SessionCubit>(), GetIt.I.get<SessionCubit>(),
@@ -140,19 +120,19 @@ class AppRouter {
// 1. DASHBOARD // 1. DASHBOARD
GoRoute( GoRoute(
path: '/', path: '/',
name: homeRoute, name: Routes.home,
builder: (context, state) => const HomeScreen(), builder: (context, state) => const HomeScreen(),
), ),
// 2. HUB ANAGRAFICHE E SOTTO-ROTTE // 2. HUB ANAGRAFICHE E SOTTO-ROTTE
GoRoute( GoRoute(
path: '/master-data', path: '/master-data',
name: masterDataRoute, name: Routes.masterData,
builder: (context, state) => const MasterDataHubScreen(), builder: (context, state) => const MasterDataHubScreen(),
routes: [ routes: [
GoRoute( GoRoute(
path: 'products', // Diventa /master-data/products path: 'products', // Diventa /master-data/products
name: 'products', name: Routes.products,
builder: (context, state) { builder: (context, state) {
context.read<ProductsCubit>().refreshCubit(); context.read<ProductsCubit>().refreshCubit();
@@ -161,7 +141,7 @@ class AppRouter {
), ),
GoRoute( GoRoute(
path: 'company-settings', path: 'company-settings',
name: companySettingsRoute, name: Routes.companySettings,
builder: (context, state) => BlocProvider( builder: (context, state) => BlocProvider(
create: (context) => CompanySettingsCubit(), create: (context) => CompanySettingsCubit(),
child: const CompanySettingsScreen(), child: const CompanySettingsScreen(),
@@ -169,17 +149,17 @@ class AppRouter {
), ),
GoRoute( GoRoute(
path: 'staff', path: 'staff',
name: staffRoute, // Diventa /master-data/staff name: Routes.staff, // Diventa /master-data/staff
builder: (context, state) => const StaffScreen(), builder: (context, state) => const StaffScreen(),
), ),
GoRoute( GoRoute(
path: storesRoute, path: Routes.stores,
name: 'stores', // Diventa /master-data/stores name: 'stores', // Diventa /master-data/stores
builder: (context, state) => const StoresScreen(), builder: (context, state) => const StoresScreen(),
), ),
GoRoute( GoRoute(
path: 'providers', path: 'providers',
name: providersRoute, // Diventa /master-data/providers name: Routes.providers, // Diventa /master-data/providers
builder: (context, state) => builder: (context, state) =>
const ProvidersMasterDataScreen(), const ProvidersMasterDataScreen(),
), ),
@@ -189,19 +169,19 @@ class AppRouter {
// 3. IMPOSTAZIONI // 3. IMPOSTAZIONI
GoRoute( GoRoute(
path: '/settings', path: '/settings',
name: settingsRoute, name: Routes.settings,
builder: (context, state) => const SettingsView(), builder: (context, state) => const SettingsView(),
routes: [ routes: [
GoRoute( GoRoute(
path: 'theme', path: 'themeSettings',
name: themeRoute, name: Routes.themeSettings,
builder: (context, state) => const ThemeSettingsView(), builder: (context, state) => const ThemeSettingsView(),
), ),
], ],
), ),
GoRoute( GoRoute(
path: '/operations', path: '/operations',
name: operationsRoute, name: Routes.operations,
builder: (context, state) => BlocProvider( builder: (context, state) => BlocProvider(
create: (context) => OperationListCubit(), create: (context) => OperationListCubit(),
child: const OperationListScreen(), child: const OperationListScreen(),
@@ -209,13 +189,13 @@ class AppRouter {
), ),
GoRoute( GoRoute(
path: '/customers', path: '/customers',
name: customersRoute, name: Routes.customers,
builder: (context, state) => builder: (context, state) =>
const CustomersContent(), // O come si chiama il tuo widget della lista! const CustomersContent(), // O come si chiama il tuo widget della lista!
), ),
GoRoute( GoRoute(
path: '/tickets', path: '/tickets',
name: ticketsRoute, name: Routes.tickets,
builder: (context, state) => BlocProvider( builder: (context, state) => BlocProvider(
create: (context) => TicketListCubit(), create: (context) => TicketListCubit(),
child: const TicketListScreen(), child: const TicketListScreen(),
@@ -228,7 +208,7 @@ class AppRouter {
GoRoute( GoRoute(
// Il path sarà es. /tickets/form/123 oppure /tickets/form/new // Il path sarà es. /tickets/form/123 oppure /tickets/form/new
path: '/tickets/form/:id', path: '/tickets/form/:id',
name: ticketFormRoute, name: Routes.ticketForm,
builder: (context, state) { builder: (context, state) {
// 1. Leggiamo l'ID dall'URL // 1. Leggiamo l'ID dall'URL
final String pathId = state.pathParameters['id'] ?? 'new'; final String pathId = state.pathParameters['id'] ?? 'new';
@@ -265,7 +245,7 @@ class AppRouter {
), ),
GoRoute( GoRoute(
path: '/upload-success', path: '/upload-success',
name: uploadSuccessRoute, name: Routes.uploadSuccess,
builder: (context, state) => const UploadSuccessScreen(), builder: (context, state) => const UploadSuccessScreen(),
), ),
GoRoute( GoRoute(
@@ -285,7 +265,7 @@ class AppRouter {
GoRoute( GoRoute(
path: '/operations/form/:id', path: '/operations/form/:id',
name: operationFormRoute, name: Routes.operationForm,
builder: (context, state) { builder: (context, state) {
final String pathId = state.pathParameters['id'] ?? 'new'; final String pathId = state.pathParameters['id'] ?? 'new';
final OperationModel? operationFromExtra = final OperationModel? operationFromExtra =
@@ -324,7 +304,7 @@ class AppRouter {
GoRoute( GoRoute(
path: '/upload/:type/:id', path: '/upload/:type/:id',
name: uploadRoute, name: Routes.upload,
builder: (context, state) { builder: (context, state) {
final typeString = state.pathParameters['type']!; final typeString = state.pathParameters['type']!;
final id = state.pathParameters['id']!; final id = state.pathParameters['id']!;

View File

@@ -0,0 +1,22 @@
class Routes {
static const String login = 'login';
static const String setPassword = 'set-password';
static const String onboarding = 'onboarding';
static const String home = 'home';
static const String masterData = 'master-data';
static const String products = 'products';
static const String companySettings = 'company-settings';
static const String staff = 'staff';
static const String stores = 'stores';
static const String providers = 'providers';
static const String settings = 'settings';
static const String themeSettings = 'themeSettings';
static const String operations = 'operations';
static const String customers = 'customers';
static const String tickets = 'tickets';
static const String ticketForm = 'ticket-form';
static const String operationForm = 'operation-form';
static const String uploadSuccess = 'upload-success';
static const String customerForm = 'customer-form';
static const String upload = 'upload';
}

View File

@@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flux/core/blocs/session/session_cubit.dart'; import 'package:flux/core/blocs/session/session_cubit.dart';
import 'package:flux/core/routes/app_router.dart'; import 'package:flux/core/routes/routes.dart';
import 'package:flux/core/theme/theme.dart'; import 'package:flux/core/theme/theme.dart';
import 'package:flux/features/customers/blocs/customers_cubit.dart'; import 'package:flux/features/customers/blocs/customers_cubit.dart';
import 'package:flux/features/customers/models/customer_model.dart'; import 'package:flux/features/customers/models/customer_model.dart';
@@ -111,7 +111,7 @@ class _CustomersContentState extends State<CustomersContent> {
return _CustomerTile( return _CustomerTile(
customer: customer, customer: customer,
onTap: () => context.pushNamed( onTap: () => context.pushNamed(
customerFormRoute, Routes.customerForm,
pathParameters: {'id': customer.id!}, pathParameters: {'id': customer.id!},
extra: customer, extra: customer,
), ),

View File

@@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flux/core/blocs/session/session_cubit.dart'; import 'package:flux/core/blocs/session/session_cubit.dart';
import 'package:flux/core/routes/app_router.dart'; import 'package:flux/core/routes/routes.dart';
import 'package:flux/core/theme/theme.dart'; import 'package:flux/core/theme/theme.dart';
import 'package:flux/core/utils/extensions.dart'; import 'package:flux/core/utils/extensions.dart';
import 'package:flux/features/home/latest_store_operations/bloc/latest_store_operations_bloc.dart'; import 'package:flux/features/home/latest_store_operations/bloc/latest_store_operations_bloc.dart';
@@ -49,7 +49,7 @@ class _LatestOperationsCardContent extends StatelessWidget {
side: BorderSide(color: theme.dividerColor.withValues(alpha: 0.5)), side: BorderSide(color: theme.dividerColor.withValues(alpha: 0.5)),
), ),
child: InkWell( child: InkWell(
onTap: () => context.pushNamed(operationsRoute), onTap: () => context.pushNamed(Routes.operations),
child: Padding( child: Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
child: Column( child: Column(
@@ -136,7 +136,7 @@ class _LatestOperationsCardContent extends StatelessWidget {
final operation = state.operations[index]; final operation = state.operations[index];
return InkWell( return InkWell(
onTap: () => context.pushNamed( onTap: () => context.pushNamed(
operationFormRoute, Routes.operationForm,
pathParameters: {'id': operation.id!}, pathParameters: {'id': operation.id!},
extra: operation, extra: operation,
), ),

View File

@@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flux/core/blocs/session/session_cubit.dart'; import 'package:flux/core/blocs/session/session_cubit.dart';
import 'package:flux/core/routes/app_router.dart'; import 'package:flux/core/routes/routes.dart';
import 'package:flux/core/theme/theme.dart'; import 'package:flux/core/theme/theme.dart';
import 'package:flux/core/utils/extensions.dart'; import 'package:flux/core/utils/extensions.dart';
import 'package:flux/features/home/latest_store_operations/ui/latest_store_operations_card.dart'; import 'package:flux/features/home/latest_store_operations/ui/latest_store_operations_card.dart';
@@ -83,8 +83,9 @@ class HomeScreen extends StatelessWidget {
icon: Icons.support_agent_outlined, icon: Icons.support_agent_outlined,
color: Colors.purple, color: Colors.purple,
context: context, context: context,
onTap: () => onTap: () => context.pushNamed(
context.pushNamed(ticketsRoute), // <-- Aggiunto! Routes.tickets,
), // <-- Aggiunto!
), ),
]), ]),
), ),
@@ -188,7 +189,7 @@ class HomeScreen extends StatelessWidget {
onTap: () { onTap: () {
// Entriamo nel form! Nessun parametro extra = Nuovo Servizio // Entriamo nel form! Nessun parametro extra = Nuovo Servizio
context.pushNamed( context.pushNamed(
operationFormRoute, Routes.operationForm,
pathParameters: {'id': 'New'}, pathParameters: {'id': 'New'},
); );
}, },
@@ -200,7 +201,10 @@ class HomeScreen extends StatelessWidget {
color: Colors.redAccent, color: Colors.redAccent,
onTap: () { onTap: () {
// Andiamo alla lista! (Da lì poi aggiungeremo il tasto "+" per il form) // Andiamo alla lista! (Da lì poi aggiungeremo il tasto "+" per il form)
context.pushNamed(ticketFormRoute, pathParameters: {'id': 'New'}); context.pushNamed(
Routes.ticketForm,
pathParameters: {'id': 'New'},
);
}, },
), ),
const SizedBox(width: 12), const SizedBox(width: 12),

View File

@@ -1,5 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flux/core/routes/app_router.dart'; import 'package:flux/core/routes/routes.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
// Mantieni i tuoi import per il tema se usi le estensioni (es. context.accent) // Mantieni i tuoi import per il tema se usi le estensioni (es. context.accent)
// import 'package:flux/core/theme/theme.dart'; // import 'package:flux/core/theme/theme.dart';
@@ -66,7 +66,7 @@ class MasterDataHubScreen extends StatelessWidget {
color: Colors.orange, color: Colors.orange,
// Usiamo .push() perché avevamo detto che i clienti // Usiamo .push() perché avevamo detto che i clienti
// stanno FUORI dalla Shell (niente BottomBar) // stanno FUORI dalla Shell (niente BottomBar)
onTap: () => context.pushNamed(customersRoute), onTap: () => context.pushNamed(Routes.customers),
), ),
_buildHubCard( _buildHubCard(
context, context,

View File

@@ -2,7 +2,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flux/core/blocs/session/session_cubit.dart'; import 'package:flux/core/blocs/session/session_cubit.dart';
import 'package:flux/core/routes/app_router.dart'; import 'package:flux/core/routes/routes.dart';
import 'package:flux/core/theme/theme.dart'; import 'package:flux/core/theme/theme.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
@@ -29,7 +29,7 @@ class SettingsView extends StatelessWidget {
icon: Icons.business, icon: Icons.business,
subtitle: 'Configura i dati aziendali', subtitle: 'Configura i dati aziendali',
context: context, context: context,
onTap: () => context.pushNamed(companySettingsRoute), onTap: () => context.pushNamed(Routes.companySettings),
), ),
]), ]),
const SizedBox(height: 16), const SizedBox(height: 16),
@@ -39,7 +39,7 @@ class SettingsView extends StatelessWidget {
title: 'Tema (FLUX Dark)', title: 'Tema (FLUX Dark)',
subtitle: 'Configurazione visiva', subtitle: 'Configurazione visiva',
context: context, context: context,
onTap: () => context.pushNamed(themeRoute), onTap: () => context.pushNamed(Routes.themeSettings),
), ),
]), ]),
const SizedBox(height: 24), const SizedBox(height: 24),

View File

@@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flux/core/routes/app_router.dart'; import 'package:flux/core/routes/routes.dart';
import 'package:flux/features/tickets/blocs/ticket_list_cubit.dart'; import 'package:flux/features/tickets/blocs/ticket_list_cubit.dart';
import 'package:flux/features/tickets/blocs/ticket_list_state.dart'; import 'package:flux/features/tickets/blocs/ticket_list_state.dart';
import 'package:flux/features/tickets/models/ticket_model.dart'; import 'package:flux/features/tickets/models/ticket_model.dart';
@@ -149,7 +149,7 @@ class _TicketListScreenState extends State<TicketListScreen> {
), ),
floatingActionButton: FloatingActionButton.extended( floatingActionButton: FloatingActionButton.extended(
onPressed: () { onPressed: () {
context.pushNamed(ticketFormRoute, pathParameters: {'id': 'New'}); context.pushNamed(Routes.ticketForm, pathParameters: {'id': 'New'});
}, },
icon: const Icon(Icons.add), icon: const Icon(Icons.add),
label: const Text('Nuovo Ticket'), label: const Text('Nuovo Ticket'),
@@ -286,7 +286,6 @@ class _TicketCard extends StatelessWidget {
pathParameters: {'id': ticket.id!}, pathParameters: {'id': ticket.id!},
extra: extra:
ticket, // <-- LA MAGIA È QUI: Passa l'oggetto intero! ticket, // <-- LA MAGIA È QUI: Passa l'oggetto intero!
// Teniamo anche il parametro URL per coerenza di routing // Teniamo anche il parametro URL per coerenza di routing
); );
}, },