diff --git a/lib/main.dart b/lib/main.dart index d1833ea..5dee9a6 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -32,15 +32,17 @@ Future main() async { ); } -class MyApp extends StatelessWidget { +class MyApp extends ConsumerWidget { const MyApp({ super.key, }); // This widget is the root of your application. @override - Widget build(BuildContext context) { - return MaterialApp( + Widget build(BuildContext context, WidgetRef ref) { + final router = ref.watch(routerProvider); + + return MaterialApp.router( title: 'XpeApp Admin', locale: const Locale('fr'), supportedLocales: const [ @@ -57,7 +59,7 @@ class MyApp extends StatelessWidget { ), useMaterial3: true, ), - routes: routes, + routerConfig: router, ); } } diff --git a/lib/presentation/pages/auth_guard.dart b/lib/presentation/pages/auth_guard.dart index b7a1d6d..70f80ca 100644 --- a/lib/presentation/pages/auth_guard.dart +++ b/lib/presentation/pages/auth_guard.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; import 'package:xpeapp_admin/providers.dart'; class AuthGuard extends ConsumerWidget { @@ -17,7 +18,7 @@ class AuthGuard extends ConsumerWidget { // Redirect to login page if not authenticated and sign out WidgetsBinding.instance.addPostFrameCallback((_) { ref.read(userProvider.notifier).signOut(); - Navigator.pushReplacementNamed(context, '/'); + context.go('/'); }); // Clear QVST menu diff --git a/lib/presentation/pages/home_page.dart b/lib/presentation/pages/home_page.dart index 41a7953..93eb6f0 100644 --- a/lib/presentation/pages/home_page.dart +++ b/lib/presentation/pages/home_page.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; import 'package:xpeapp_admin/data/colors.dart'; import 'package:xpeapp_admin/data/entities/menu_entity.dart'; import 'package:xpeapp_admin/presentation/pages/agenda/agenda_page.dart'; @@ -42,7 +43,7 @@ class HomePage extends ConsumerWidget { // Sign out ref.watch(loginProvider).signOut(), // Redirect to login page - Navigator.pushNamed(context, '/') + context.go('/') }, icon: const Icon( Icons.logout, @@ -183,7 +184,7 @@ class HomePage extends ConsumerWidget { onPressed: () { switch (menuSelected.id) { case menuNewsletter: - Navigator.pushNamed(context, '/newsletter/add'); + context.push('/newsletter/add'); break; default: break; diff --git a/lib/presentation/pages/login_page.dart b/lib/presentation/pages/login_page.dart index a8f1712..cee857b 100644 --- a/lib/presentation/pages/login_page.dart +++ b/lib/presentation/pages/login_page.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; import 'package:xpeapp_admin/data/colors.dart'; import 'package:xpeapp_admin/data/entities/xpeho_user.dart'; import 'package:xpeapp_admin/presentation/widgets/app_loader.dart'; @@ -115,10 +116,7 @@ class LoginPage extends ConsumerWidget { // Naviguez vers la page d'accueil en réinitialisant le menu ref.read(menuSelectedProvider.notifier).reset(); - Navigator.pushNamed( - context, - '/home', - ); + context.go('/home'); } catch (error) { // En cas d'erreur, désactivez également le loader ref.read(loaderStateProvider.notifier).hideLoader(); diff --git a/lib/presentation/pages/newsletters/newsletter_add_page.dart b/lib/presentation/pages/newsletters/newsletter_add_page.dart index 4887168..3bbe2df 100644 --- a/lib/presentation/pages/newsletters/newsletter_add_page.dart +++ b/lib/presentation/pages/newsletters/newsletter_add_page.dart @@ -4,6 +4,7 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; import 'package:http_parser/http_parser.dart'; import 'package:intl/intl.dart'; import 'package:xpeapp_admin/data/colors.dart'; @@ -279,7 +280,7 @@ class _NewsletterAddOrEditPageState await updateNewsletter(newsletterEntity); } // Redirect to the previous page - Navigator.pop(context); + context.pop(); } } : null, diff --git a/lib/presentation/pages/newsletters/newsletter_detail_page.dart b/lib/presentation/pages/newsletters/newsletter_detail_page.dart index e2ed402..86544f7 100644 --- a/lib/presentation/pages/newsletters/newsletter_detail_page.dart +++ b/lib/presentation/pages/newsletters/newsletter_detail_page.dart @@ -1,6 +1,7 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; import 'package:intl/intl.dart'; import 'package:xpeapp_admin/data/entities/newsletter_entity.dart'; import 'package:xpeapp_admin/presentation/pages/template/scaffold_template.dart'; @@ -34,14 +35,13 @@ class NewsletterDetailPage extends ConsumerWidget { return ScaffoldTemplate( appBarTitle: 'Contenu de la newsletter', appBarLeading: IconButton( - onPressed: () => Navigator.pop(context), + onPressed: () => context.pop(), icon: const Icon(Icons.arrow_back), ), floatingActionButton: FloatingActionButton( - onPressed: () => Navigator.pushNamed( - context, + onPressed: () => context.push( '/newsletter/edit', - arguments: newsletter, + extra: newsletter, ), backgroundColor: Colors.grey, child: const Icon( diff --git a/lib/presentation/pages/qvst/content/qvst_content_theme.dart b/lib/presentation/pages/qvst/content/qvst_content_theme.dart index e10b6a5..0ae14fb 100644 --- a/lib/presentation/pages/qvst/content/qvst_content_theme.dart +++ b/lib/presentation/pages/qvst/content/qvst_content_theme.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; import 'package:xpeapp_admin/data/colors.dart'; import 'package:xpeapp_admin/data/enum/qvst_menu.dart'; import 'package:xpeapp_admin/presentation/pages/qvst/content/qvst_table_view.dart'; @@ -162,10 +163,7 @@ class _QvstContentThemeState extends ConsumerState { child: FloatingActionButton( onPressed: () { ref.watch(qvstThemesSelectionProvider.notifier).setTheme(theme); - Navigator.pushNamed( - context, - '/qvst/add', - ); + context.push('/qvst/add'); }, backgroundColor: kDefaultXpehoColor, child: const Icon( diff --git a/lib/presentation/pages/qvst/qvst_content_page.dart b/lib/presentation/pages/qvst/qvst_content_page.dart index 844c783..0c1f993 100644 --- a/lib/presentation/pages/qvst/qvst_content_page.dart +++ b/lib/presentation/pages/qvst/qvst_content_page.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; import 'package:xpeapp_admin/data/entities/qvst/qvst_question_entity.dart'; import 'package:xpeapp_admin/env/extensions/string.dart'; @@ -36,13 +37,7 @@ class QvstContentPage extends ConsumerWidget { elevation: 10, color: Colors.white, child: ListTile( - onTap: () => Navigator.pushNamed( - context, - '/qvst/detail', - arguments: { - 'id': question.id, - }, - ), + onTap: () => context.push('/qvst/detail/${question.id}'), title: Text( question.question.cleanText(), style: const TextStyle( diff --git a/lib/presentation/pages/qvst/qvst_page.dart b/lib/presentation/pages/qvst/qvst_page.dart index 9d23830..50cfb9d 100644 --- a/lib/presentation/pages/qvst/qvst_page.dart +++ b/lib/presentation/pages/qvst/qvst_page.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; import 'package:xpeapp_admin/data/colors.dart'; import 'package:xpeapp_admin/data/enum/qvst_menu.dart'; import 'package:xpeapp_admin/presentation/pages/qvst/content/qvst_content_campaign.dart'; @@ -38,7 +39,7 @@ class _QvstPageState extends ConsumerState { Align( alignment: Alignment.topLeft, child: IconButton( - onPressed: () => Navigator.pop(context), + onPressed: () => context.pop(), icon: const Icon( Icons.home, color: Colors.white, diff --git a/lib/presentation/widgets/newsletter_card.dart b/lib/presentation/widgets/newsletter_card.dart index b9499a9..c684eb5 100644 --- a/lib/presentation/widgets/newsletter_card.dart +++ b/lib/presentation/widgets/newsletter_card.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; import 'package:intl/intl.dart'; import 'package:xpeapp_admin/data/entities/newsletter_entity.dart'; @@ -13,11 +14,7 @@ class NewsletterCard extends StatelessWidget { @override Widget build(BuildContext context) { return InkWell( - onTap: () => Navigator.pushNamed( - context, - '/newsletter/detail', - arguments: newsletter.id, - ), + onTap: () => context.push('/newsletter/detail/${newsletter.id}'), child: Container( margin: const EdgeInsets.symmetric( horizontal: 20, diff --git a/lib/routes.dart b/lib/routes.dart index 2037b56..55788f9 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -1,8 +1,20 @@ import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import 'package:xpeapp_admin/data/entities/agenda/birthday_entity.dart'; +import 'package:xpeapp_admin/data/entities/agenda/events_entity.dart'; +import 'package:xpeapp_admin/data/entities/agenda/events_type_entity.dart'; import 'package:xpeapp_admin/data/entities/newsletter_entity.dart'; +import 'package:xpeapp_admin/data/enum/crud_page_mode.dart'; +import 'package:xpeapp_admin/presentation/pages/agenda/agenda_page.dart'; +import 'package:xpeapp_admin/presentation/pages/agenda/birthday/birthday_add_or_edit_page.dart'; +import 'package:xpeapp_admin/presentation/pages/agenda/events/event_add_or_edit_page.dart'; +import 'package:xpeapp_admin/presentation/pages/agenda/types/types_add_or_edit_page.dart'; import 'package:xpeapp_admin/presentation/pages/auth_guard.dart'; import 'package:xpeapp_admin/presentation/pages/feature_flipping/feature_flipping_page.dart'; import 'package:xpeapp_admin/presentation/pages/home_page.dart'; +import 'package:xpeapp_admin/presentation/pages/idea_box/idea_box_page.dart'; +import 'package:xpeapp_admin/presentation/pages/last_connection_page.dart'; import 'package:xpeapp_admin/presentation/pages/login_page.dart'; import 'package:xpeapp_admin/presentation/pages/newsletters/newsletter_add_page.dart'; import 'package:xpeapp_admin/presentation/pages/newsletters/newsletter_detail_page.dart'; @@ -10,43 +22,216 @@ import 'package:xpeapp_admin/presentation/pages/newsletters/newsletters_page.dar import 'package:xpeapp_admin/presentation/pages/qvst/qvst_add_page.dart'; import 'package:xpeapp_admin/presentation/pages/qvst/qvst_detail_page.dart'; import 'package:xpeapp_admin/presentation/pages/qvst/qvst_page.dart'; +import 'package:xpeapp_admin/providers.dart'; -Map routes = { - '/': (context) => const LoginPage(), - '/home': (context) => const AuthGuard(child: HomePage()), - '/newsletters': (context) => const AuthGuard( - child: NewslettersPage(), +/// Provider pour le router go_router +final routerProvider = Provider((ref) { + return GoRouter( + initialLocation: '/', + redirect: (context, state) { + final loginRepo = ref.read(loginProvider); + final isLoggedIn = loginRepo.isUserLoggedIn(); + final isLoginPage = state.matchedLocation == '/'; + + // Si non connecté et pas sur la page de login, rediriger vers login + if (!isLoggedIn && !isLoginPage) { + return '/'; + } + + // Si connecté et sur la page de login, rediriger vers home + if (isLoggedIn && isLoginPage) { + return '/home'; + } + + return null; + }, + routes: [ + // ============ AUTH ============ + GoRoute( + path: '/', + name: 'login', + builder: (context, state) => const LoginPage(), + ), + + // ============ HOME ============ + GoRoute( + path: '/home', + name: 'home', + builder: (context, state) => const AuthGuard(child: HomePage()), + ), + + // ============ NEWSLETTERS ============ + GoRoute( + path: '/newsletters', + name: 'newsletters', + builder: (context, state) => const AuthGuard(child: NewslettersPage()), ), - '/newsletter/detail': (context) => AuthGuard( - child: NewsletterDetailPage( - id: ModalRoute.of(context)?.settings.arguments as String?, + GoRoute( + path: '/newsletter/detail/:id', + name: 'newsletter-detail', + builder: (context, state) => AuthGuard( + child: NewsletterDetailPage( + id: state.pathParameters['id'], + ), ), ), - '/newsletter/add': (context) => const AuthGuard( - child: NewsletterAddOrEditPage( - typePage: NewsletterTypePage.add, + GoRoute( + path: '/newsletter/add', + name: 'newsletter-add', + builder: (context, state) => const AuthGuard( + child: NewsletterAddOrEditPage( + typePage: NewsletterTypePage.add, + ), ), ), - '/newsletter/edit': (context) => AuthGuard( - child: NewsletterAddOrEditPage( - typePage: NewsletterTypePage.edit, - newsletter: - ModalRoute.of(context)!.settings.arguments as NewsletterEntity?, + GoRoute( + path: '/newsletter/edit', + name: 'newsletter-edit', + builder: (context, state) { + final newsletter = state.extra as NewsletterEntity?; + return AuthGuard( + child: NewsletterAddOrEditPage( + typePage: NewsletterTypePage.edit, + newsletter: newsletter, + ), + ); + }, + ), + + // ============ FEATURE FLIPPING ============ + GoRoute( + path: '/featureFlipping', + name: 'feature-flipping', + builder: (context, state) => + const AuthGuard(child: FeatureFlippingPage()), + ), + + // ============ QVST ============ + GoRoute( + path: '/qvst', + name: 'qvst', + builder: (context, state) => const AuthGuard(child: QvstPage()), + ), + GoRoute( + path: '/qvst/detail/:id', + name: 'qvst-detail', + builder: (context, state) => AuthGuard( + child: QvstDetailPage( + id: state.pathParameters['id'] ?? '', + ), ), ), - '/featureFlipping': (context) => - const AuthGuard(child: FeatureFlippingPage()), - '/qvst': (context) => const AuthGuard(child: QvstPage()), - '/qvst/detail': (context) { - final args = - ModalRoute.of(context)?.settings.arguments as Map?; - return AuthGuard( - child: QvstDetailPage( - id: args?['id'] as String? ?? '', - ), - ); - }, - '/qvst/add': (context) => const AuthGuard( - child: QvstAddPage(), - ), -}; + GoRoute( + path: '/qvst/add', + name: 'qvst-add', + builder: (context, state) => const AuthGuard(child: QvstAddPage()), + ), + + // ============ AGENDA ============ + GoRoute( + path: '/agenda', + name: 'agenda', + builder: (context, state) => const AuthGuard(child: AgendaPage()), + ), + // Events + GoRoute( + path: '/agenda/event/add', + name: 'agenda-event-add', + builder: (context, state) { + final onDismissed = state.extra as VoidCallback? ?? () {}; + return AuthGuard( + child: EventAddOrEditPage( + pageMode: CrudPageMode.create, + onDismissed: onDismissed, + ), + ); + }, + ), + GoRoute( + path: '/agenda/event/edit', + name: 'agenda-event-edit', + builder: (context, state) { + final args = state.extra as Map?; + return AuthGuard( + child: EventAddOrEditPage( + pageMode: CrudPageMode.edit, + event: args?['event'] as EventsEntity?, + onDismissed: args?['onDismissed'] as VoidCallback? ?? () {}, + ), + ); + }, + ), + // Birthday + GoRoute( + path: '/agenda/birthday/add', + name: 'agenda-birthday-add', + builder: (context, state) { + final onDismissed = state.extra as VoidCallback? ?? () {}; + return AuthGuard( + child: BirthdayAddOrEditPage( + pageMode: CrudPageMode.create, + onDismissed: onDismissed, + ), + ); + }, + ), + GoRoute( + path: '/agenda/birthday/edit', + name: 'agenda-birthday-edit', + builder: (context, state) { + final args = state.extra as Map?; + return AuthGuard( + child: BirthdayAddOrEditPage( + pageMode: CrudPageMode.edit, + birthday: args?['birthday'] as BirthdayEntity?, + onDismissed: args?['onDismissed'] as VoidCallback? ?? () {}, + ), + ); + }, + ), + // Types + GoRoute( + path: '/agenda/type/add', + name: 'agenda-type-add', + builder: (context, state) { + final onDismissed = state.extra as VoidCallback? ?? () {}; + return AuthGuard( + child: EventTypesAddOrEditPage( + pageMode: CrudPageMode.create, + onDismissed: onDismissed, + ), + ); + }, + ), + GoRoute( + path: '/agenda/type/edit', + name: 'agenda-type-edit', + builder: (context, state) { + final args = state.extra as Map?; + return AuthGuard( + child: EventTypesAddOrEditPage( + pageMode: CrudPageMode.edit, + eventType: args?['eventType'] as EventsTypeEntity?, + onDismissed: args?['onDismissed'] as VoidCallback? ?? () {}, + ), + ); + }, + ), + + // ============ IDEA BOX ============ + GoRoute( + path: '/ideabox', + name: 'idea-box', + builder: (context, state) => const AuthGuard(child: IdeaBoxPage()), + ), + + // ============ LAST CONNECTION ============ + GoRoute( + path: '/lastConnection', + name: 'last-connection', + builder: (context, state) => + const AuthGuard(child: LastConnectionPage()), + ), + ], + ); +}); diff --git a/pubspec.yaml b/pubspec.yaml index e56e223..56f9a72 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -42,6 +42,7 @@ dependencies: http_parser: ^4.1.2 syncfusion_flutter_charts: ^32.1.19 universal_html: ^2.3.0 + go_router: ^17.0.1 dev_dependencies: flutter_test: diff --git a/test/presentation/widgets/newsletter_card_test.dart b/test/presentation/widgets/newsletter_card_test.dart index 3a3f524..387c7f8 100644 --- a/test/presentation/widgets/newsletter_card_test.dart +++ b/test/presentation/widgets/newsletter_card_test.dart @@ -1,6 +1,7 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:go_router/go_router.dart'; import 'package:xpeapp_admin/data/entities/newsletter_entity.dart'; import 'package:xpeapp_admin/presentation/widgets/newsletter_card.dart'; @@ -10,24 +11,36 @@ void main() { (tester) async { final dateTesting = DateTime(2021, 10, 10); final newsletter = NewsletterEntity( - id: 'id', + id: 'test-id', date: Timestamp.fromDate(dateTesting), summary: 'summary', pdfUrl: 'pdfUrl', publicationDate: Timestamp.fromDate(dateTesting), ); - await tester.pumpWidget( - MaterialApp( - routes: { - '/newsletter/detail': (context) => const Scaffold( - body: Text('/newsletter/detail'), - ), - }, - home: Scaffold( - body: NewsletterCard( - newsletter: newsletter, + + final router = GoRouter( + initialLocation: '/', + routes: [ + GoRoute( + path: '/', + builder: (context, state) => Scaffold( + body: NewsletterCard( + newsletter: newsletter, + ), + ), + ), + GoRoute( + path: '/newsletter/detail/:id', + builder: (context, state) => const Scaffold( + body: Text('/newsletter/detail'), ), ), + ], + ); + + await tester.pumpWidget( + MaterialApp.router( + routerConfig: router, ), );