diff --git a/lib/core/routes/app_router.dart b/lib/core/routes/app_router.dart new file mode 100644 index 0000000..f07a855 --- /dev/null +++ b/lib/core/routes/app_router.dart @@ -0,0 +1,82 @@ +import 'package:flutter/material.dart'; +import 'package:flux/core/blocs/session/session_bloc.dart'; +import 'package:flux/features/auth/ui/auth_screen.dart'; +import 'package:flux/features/company/ui/create_company_screen.dart'; +import 'package:flux/features/dashboard/ui/dashboard_screen.dart'; +import 'package:flux/features/store/ui/create_store_screen.dart'; +import 'package:go_router/go_router.dart'; +import 'dart:async'; + +class AppRouter { + // Funzione statica per creare il router + static GoRouter createRouter(SessionBloc sessionBloc) { + return GoRouter( + initialLocation: '/', + // Ascolta i cambiamenti del Bloc per scatenare il redirect + refreshListenable: _GoRouterRefreshStream(sessionBloc.stream), + redirect: (context, state) { + final sessionState = sessionBloc.state; + + // Logica di redirezione basata sugli stati del SessionBloc + final bool isUnknown = sessionState.status == SessionStatus.unknown; + final bool isUnauthenticated = + sessionState.status == SessionStatus.unauthenticated; + final bool isNoCompany = + sessionState.status == SessionStatus.authenticatedNoCompany; + final bool isNoStore = + sessionState.status == SessionStatus.authenticatedNoStore; + final bool isReady = sessionState.status == SessionStatus.ready; + + final String location = state.matchedLocation; + + if (isUnknown) return null; // Aspetta che l'app si svegli + + if (isUnauthenticated && location != '/login') return '/login'; + + if (isNoCompany && location != '/create-company') + return '/create-company'; + + if (isNoStore && location != '/create-store') return '/create-store'; + + // Se sono loggato e sto cercando di andare alla login, vai in dashboard + if (isReady && location == '/login') return '/'; + + return null; + }, + routes: [ + GoRoute( + path: '/', + builder: (context, state) => const DashboardScreen(), + ), + GoRoute( + path: '/login', + builder: (context, state) => const AuthScreen(), + ), + GoRoute( + path: '/create-company', + builder: (context, state) => const CreateCompanyScreen(), + ), + GoRoute( + path: '/create-store', + builder: (context, state) => const CreateStoreScreen(), + ), + ], + ); + } +} + +// Classe di supporto per convertire lo Stream del Bloc in un Listenable per GoRouter +class _GoRouterRefreshStream extends ChangeNotifier { + _GoRouterRefreshStream(Stream stream) { + notifyListeners(); + _subscription = stream.asBroadcastStream().listen((_) => notifyListeners()); + } + + late final StreamSubscription _subscription; + + @override + void dispose() { + _subscription.cancel(); + super.dispose(); + } +} diff --git a/lib/features/dashboard/ui/dashboard_screen.dart b/lib/features/dashboard/ui/dashboard_screen.dart new file mode 100644 index 0000000..0c488a6 --- /dev/null +++ b/lib/features/dashboard/ui/dashboard_screen.dart @@ -0,0 +1,200 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flux/core/blocs/session/session_bloc.dart'; +import 'package:flux/core/theme/theme.dart'; + +class DashboardScreen extends StatelessWidget { + const DashboardScreen({super.key}); + + @override + Widget build(BuildContext context) { + // Ascoltiamo il SessionBloc per avere i dati in tempo reale + return BlocBuilder( + builder: (context, state) { + final store = state.selectedStore; + final company = state.company; + + return Scaffold( + body: CustomScrollView( + slivers: [ + // Un'AppBar elegante che si rimpicciolisce + SliverAppBar( + expandedHeight: 120.0, + floating: false, + pinned: true, + flexibleSpace: FlexibleSpaceBar( + title: Text( + store?.nome ?? 'Flux Dashboard', + style: TextStyle(color: context.primaryText), + ), + background: Container(color: context.background), + ), + actions: [ + IconButton( + icon: const Icon(Icons.settings_outlined), + onPressed: () => Navigator.pushNamed(context, '/settings'), + ), + ], + ), + + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildWelcomeHeader(context, company?.ragioneSociale), + const SizedBox(height: 30), + _SectionTitle(title: 'AZIONI RAPIDE'), + const SizedBox(height: 16), + _buildGridActions(context), + const SizedBox(height: 32), + _SectionTitle(title: 'STATO NEGOZIO'), + const SizedBox(height: 16), + _buildInfoCard(context, store), + ], + ), + ), + ), + ], + ), + ); + }, + ); + } + + Widget _buildWelcomeHeader(BuildContext context, String? companyName) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Bentornato,', + style: TextStyle(color: context.secondaryText, fontSize: 16), + ), + Text( + companyName ?? 'La tua Azienda', + style: TextStyle( + color: context.primaryText, + fontSize: 24, + fontWeight: FontWeight.bold, + ), + ), + ], + ); + } + + Widget _buildGridActions(BuildContext context) { + return GridView.count( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + crossAxisCount: 2, + mainAxisSpacing: 16, + crossAxisSpacing: 16, + childAspectRatio: 1.5, + children: [ + _ActionCard( + label: 'Nuova Op', + icon: Icons.add_task_rounded, + color: context.accent, + onTap: () {}, + ), + _ActionCard( + label: 'Clienti', + icon: Icons.people_alt_rounded, + color: Colors.orange, + onTap: () {}, + ), + _ActionCard( + label: 'Campagne', + icon: Icons.campaign_rounded, + color: Colors.purple, + onTap: () {}, + ), + _ActionCard( + label: 'Report', + icon: Icons.analytics_rounded, + color: Colors.teal, + onTap: () {}, + ), + ], + ); + } + + Widget _buildInfoCard(BuildContext context, dynamic store) { + return Container( + width: double.infinity, + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: context.accent.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(20), + ), + child: Row( + children: [ + Icon(Icons.location_on_rounded, color: context.accent), + const SizedBox(width: 12), + Expanded( + child: Text( + '${store?.indirizzo}, ${store?.comune}', + style: TextStyle(color: context.primaryText), + ), + ), + ], + ), + ); + } +} + +class _ActionCard extends StatelessWidget { + final String label; + final IconData icon; + final Color color; + final VoidCallback onTap; + + const _ActionCard({ + required this.label, + required this.icon, + required this.color, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(20), + child: Container( + decoration: BoxDecoration( + color: context.background, + borderRadius: BorderRadius.circular(20), + border: Border.all(color: context.accent.withValues(alpha: 0.1)), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(icon, color: color, size: 32), + const SizedBox(height: 8), + Text(label, style: const TextStyle(fontWeight: FontWeight.bold)), + ], + ), + ), + ); + } +} + +class _SectionTitle extends StatelessWidget { + final String title; + const _SectionTitle({required this.title}); + + @override + Widget build(BuildContext context) { + return Text( + title, + style: TextStyle( + color: context.secondaryText, + fontWeight: FontWeight.bold, + fontSize: 12, + letterSpacing: 1.1, + ), + ); + } +} diff --git a/lib/main.dart b/lib/main.dart index 73e296a..b5283c2 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flux/core/blocs/session/session_bloc.dart'; +import 'package:flux/core/routes/app_router.dart'; import 'package:flux/core/theme/theme.dart'; import 'package:flux/core/theme/bloc/theme_bloc.dart'; import 'package:flux/features/auth/bloc/auth_bloc.dart'; @@ -63,47 +64,23 @@ class FluxApp extends StatelessWidget { Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { - return MaterialApp( - title: 'FLUX Gestionale', - debugShowCheckedModeBanner: false, - theme: fluxLightTheme, - darkTheme: fluxDarkTheme, - themeMode: state.currentTheme.themeMode, // Applica il tema FLUX - home: const AuthGuard(), + // Creiamo il router passando il SessionBloc che è già nell'albero grazie al MultiBlocProvider + final router = AppRouter.createRouter(context.read()); + + return BlocBuilder( + builder: (context, state) { + return MaterialApp.router( + // <--- Diventa .router + title: 'FLUX Gestionale', + debugShowCheckedModeBanner: false, + theme: fluxLightTheme, + darkTheme: fluxDarkTheme, + themeMode: state.currentTheme.themeMode, + routerConfig: router, // <--- Configurazione GoRouter + ); + }, ); }, ); } } - -class AuthGuard extends StatelessWidget { - const AuthGuard({super.key}); - - @override - Widget build(BuildContext context) { - return BlocBuilder( - builder: (context, state) { - switch (state.status) { - case SessionStatus.unauthenticated: - return const AuthScreen(); - - case SessionStatus.authenticatedNoCompany: - // Pagina forzata per inserimento P.IVA e Ragione Sociale - return const CreateCompanyScreen(); - - case SessionStatus.authenticatedNoStore: - // Pagina forzata per creare il primo punto vendita - return const CreateStoreScreen(); - - case SessionStatus.ready: - return const HomeScreen(); // Entra direttamente nel negozio salvato - - default: - return const Scaffold( - body: Center(child: CircularProgressIndicator()), - ); - } - }, - ); - } -} diff --git a/pubspec.lock b/pubspec.lock index 0114c31..9fbb496 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -216,6 +216,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.3" + go_router: + dependency: "direct main" + description: + name: go_router + sha256: "48fb2f42ad057476fa4b733cb95e9f9ea7b0b010bb349ea491dca7dbdb18ffc4" + url: "https://pub.dev" + source: hosted + version: "17.2.0" google_fonts: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 554e4eb..487a4e2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -13,6 +13,7 @@ dependencies: flutter_bloc: ^9.1.1 flutter_svg: ^2.2.4 get_it: ^9.2.1 + go_router: ^17.2.0 google_fonts: ^8.0.2 intl: ^0.20.2 shared_preferences: ^2.5.5