From 5f199bd9103aed7a878b88e0d57a5940c7b947eb Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 15 Jun 2025 15:02:23 +0200 Subject: [PATCH 001/473] feat: adding color palette --- lib/tools/constants.dart | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/tools/constants.dart b/lib/tools/constants.dart index 43233ef1fa..961ecce1f0 100644 --- a/lib/tools/constants.dart +++ b/lib/tools/constants.dart @@ -7,6 +7,16 @@ class ColorConstants { static const Color background2 = Color(0xFF222643); static const Color deactivated1 = Color(0xFF9E9E9E); static const Color deactivated2 = Color(0xFFC0C0C0); + + static const Color background = Color(0xFFffffff); + static const Color onBackground = Color(0xffb4b4b4); + static const Color secondary = Color(0xFFb1b2b5); + static const Color tertiary = Color(0xFF424242); + static const Color onTertiary = Color(0xFF212121); + static const Color title = Color(0xFF000000); + static const Color main = Color(0xFFed0000); + static const Color onMain = Color(0xFFaa0202); + static const Color mainBorder = Color(0xFF950303); } class TextConstants { From c9fe802cdf6087a5420ac8193cee011d7423b531 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 15 Jun 2025 15:19:52 +0200 Subject: [PATCH 002/473] feat: adding main button --- lib/tools/ui/styleguide/button.dart | 111 ++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 lib/tools/ui/styleguide/button.dart diff --git a/lib/tools/ui/styleguide/button.dart b/lib/tools/ui/styleguide/button.dart new file mode 100644 index 0000000000..83bbc65060 --- /dev/null +++ b/lib/tools/ui/styleguide/button.dart @@ -0,0 +1,111 @@ +import 'package:flutter/material.dart'; +import 'package:myecl/tools/constants.dart'; + +enum ButtonType { main, danger, onDanger, secondary } + +class Button extends StatelessWidget { + final ButtonType type; + final String text; + final bool? disabled; + final Function() onPressed; + + const Button({ + super.key, + this.type = ButtonType.main, + required this.text, + required this.onPressed, + this.disabled = false, + }); + + const Button.danger({ + super.key, + required this.text, + required this.onPressed, + this.disabled = false, + }) : type = ButtonType.danger; + + const Button.onDanger({ + super.key, + required this.text, + required this.onPressed, + this.disabled = false, + }) : type = ButtonType.onDanger; + + const Button.secondary({ + super.key, + required this.text, + required this.onPressed, + this.disabled = false, + }) : type = ButtonType.secondary; + + Color get backgroundColor { + switch (type) { + case ButtonType.main: + return ColorConstants.tertiary; + case ButtonType.danger: + return ColorConstants.main; + case ButtonType.onDanger: + return ColorConstants.onMain; + case ButtonType.secondary: + return ColorConstants.background; + } + } + + Color get borderColor { + switch (type) { + case ButtonType.main: + return ColorConstants.onTertiary; + case ButtonType.danger: + return ColorConstants.mainBorder; + case ButtonType.onDanger: + return ColorConstants.mainBorder; + case ButtonType.secondary: + return ColorConstants.onBackground; + } + } + + Color get textColor { + Color color; + switch (type) { + case ButtonType.main: + color = ColorConstants.background; + case ButtonType.onDanger: + color = ColorConstants.background; + case ButtonType.danger: + color = ColorConstants.background; + case ButtonType.secondary: + color = ColorConstants.tertiary; + } + if (disabled == true) { + return color.withAlpha(150); + } + return color; + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: disabled == true ? null : onPressed, + behavior: HitTestBehavior.opaque, + child: Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: borderColor), + ), + child: Center( + child: Text( + text, + style: TextStyle( + color: textColor, + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + ); + } +} From 06b100c2b4ed056ed9ff837adf29a534bac8f7f8 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 15 Jun 2025 15:19:58 +0200 Subject: [PATCH 003/473] feat: adding list item --- lib/tools/ui/styleguide/list_item.dart | 62 ++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 lib/tools/ui/styleguide/list_item.dart diff --git a/lib/tools/ui/styleguide/list_item.dart b/lib/tools/ui/styleguide/list_item.dart new file mode 100644 index 0000000000..c0e2511735 --- /dev/null +++ b/lib/tools/ui/styleguide/list_item.dart @@ -0,0 +1,62 @@ +import 'package:flutter/material.dart'; +import 'package:heroicons/heroicons.dart'; +import 'package:myecl/tools/constants.dart'; + +class ListItem extends StatelessWidget { + final String title; + final String? subtitle; + final Widget? icon; + final Function()? onTap; + + const ListItem({ + super.key, + required this.title, + this.subtitle, + this.icon, + this.onTap, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + behavior: HitTestBehavior.opaque, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 4), + child: Row( + children: [ + if (icon != null) ...[icon!, const SizedBox(width: 10)], + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: ColorConstants.tertiary, + ), + ), + if (subtitle != null) + Text( + subtitle!, + style: TextStyle( + fontSize: 12, + color: ColorConstants.onTertiary, + ), + ), + ], + ), + ), + const SizedBox(width: 10), + const HeroIcon( + HeroIcons.chevronRight, + color: ColorConstants.tertiary, + ), + ], + ), + ), + ); + } +} From 7c9d495c85af0fb402af0d9c5e5db62a22a456f4 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 15 Jun 2025 16:42:37 +0200 Subject: [PATCH 004/473] feat: adding navbar --- lib/tools/ui/styleguide/navbar.dart | 209 ++++++++++++++++++++++++++++ 1 file changed, 209 insertions(+) create mode 100644 lib/tools/ui/styleguide/navbar.dart diff --git a/lib/tools/ui/styleguide/navbar.dart b/lib/tools/ui/styleguide/navbar.dart new file mode 100644 index 0000000000..58582bf45b --- /dev/null +++ b/lib/tools/ui/styleguide/navbar.dart @@ -0,0 +1,209 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:myecl/tools/constants.dart'; + +class FloatingNavbarItem { + final Function()? onTap; + final String title; + + FloatingNavbarItem({this.onTap, required this.title}); +} + +class FloatingNavbar extends HookWidget { + final List items; + const FloatingNavbar({super.key, required this.items}); + @override + Widget build(BuildContext context) { + final currentIndex = useState(0); + final borderRadius = 25.0; + + // Animation controller for all animations + final animationController = useAnimationController( + duration: const Duration(milliseconds: 300), + ); + + // Slide animation reference + final slideAnimation = useRef?>(null); + + // Track previous index for animation + final previousIndex = useRef( + 0, + ); // Store the latest calculated item width to use in animations + final itemWidthRef = useRef(0.0); + + // Update animation when index changes - this needs to be in the build method, not in LayoutBuilder + useEffect(() { + if (previousIndex.value != currentIndex.value && itemWidthRef.value > 0) { + // Create tween from previous to current position + slideAnimation.value = + Tween( + begin: previousIndex.value * itemWidthRef.value, + end: currentIndex.value * itemWidthRef.value, + ).animate( + CurvedAnimation( + parent: animationController, + curve: Curves.easeOutCubic, // Smoother easing curve + ), + ); + + // Reset and start animation + animationController.reset(); + animationController.forward(); + + // Store current index as previous for next change + previousIndex.value = currentIndex.value; + } + return null; + }, [currentIndex.value, itemWidthRef.value]); + + // Use LayoutBuilder for proper sizing + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 16), + child: Material( + elevation: 10, + shadowColor: ColorConstants.main.withOpacity(0.2), + borderRadius: BorderRadius.circular(borderRadius), + color: ColorConstants.main, + child: Container( + height: borderRadius * 2, + padding: const EdgeInsets.symmetric(horizontal: 8), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(borderRadius), + ), + child: LayoutBuilder( + builder: (context, constraints) { + // Calculate item width based on actual available width + final availableWidth = constraints.maxWidth; + final itemWidth = availableWidth / items.length; + + // Store the width for use in the useEffect hook + itemWidthRef.value = itemWidth; + + return Stack( + children: [ + // Animated selection indicator with smooth slide + AnimatedBuilder( + animation: animationController, + builder: (context, _) { + // Get current position from slide animation or fall back to current index + final leftPosition = slideAnimation.value != null + ? slideAnimation.value!.value + : itemWidth * currentIndex.value; + + return Positioned( + left: leftPosition, + top: 4, + bottom: 4, + width: itemWidth, + child: Container( + decoration: BoxDecoration( + color: ColorConstants.background, + borderRadius: BorderRadius.circular(borderRadius), + ), + ), + ); + }, + ), + // Items row + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: items.asMap().entries.map((entry) { + final index = entry.key; + final item = entry.value; + final isSelected = index == currentIndex.value; + + // Use AnimatedBuilder for text color to sync with indicator animation + return Expanded( + child: Material( + color: Colors.transparent, + borderRadius: BorderRadius.circular(borderRadius), + child: InkWell( + borderRadius: BorderRadius.circular(borderRadius), + onTap: () { + item.onTap?.call(); + currentIndex.value = index; + }, + child: Container( + padding: const EdgeInsets.all(8), + child: AnimatedBuilder( + animation: animationController, + builder: (context, child) { + // Calculate color and weight based on selection and animation + Color textColor; + FontWeight textWeight; + + if (previousIndex.value == + currentIndex.value) { + // No transition happening + textColor = isSelected + ? ColorConstants.main + : ColorConstants.background; + textWeight = isSelected + ? FontWeight.w600 + : FontWeight.normal; + } else { + // During transition, determine if this item is involved + bool isInvolved = + index == previousIndex.value || + index == currentIndex.value; + + if (!isInvolved) { + // Not involved in transition + textColor = ColorConstants.background; + textWeight = FontWeight.normal; + } else if (index == currentIndex.value) { + // Transitioning to selected + final progress = + animationController.value; + textColor = Color.lerp( + ColorConstants.background, + ColorConstants.main, + progress, + )!; + // Use a simpler approach for font weight transition + textWeight = progress < 0.5 + ? FontWeight.normal + : FontWeight.w600; + } else { + // Transitioning from selected + final progress = + animationController.value; + textColor = Color.lerp( + ColorConstants.main, + ColorConstants.background, + progress, + )!; + // Use a simpler approach for font weight transition + textWeight = progress < 0.5 + ? FontWeight.w600 + : FontWeight.normal; + } + } + + return Center( + child: Text( + item.title, + style: TextStyle( + color: textColor, + fontSize: 14, + fontWeight: textWeight, + ), + ), + ); + }, + ), + ), + ), + ), + ); + }).toList(), + ), + ], + ); + }, + ), + ), + ), + ); + } +} From f6e84d8fa75d3db81502ba1922ce3e19dd281c6f Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 15 Jun 2025 16:42:49 +0200 Subject: [PATCH 005/473] feat: adding style guide page --- lib/router.dart | 2 + lib/tools/ui/styleguide/button.dart | 4 +- lib/tools/ui/styleguide/router.dart | 23 ++ lib/tools/ui/styleguide/styleguide_page.dart | 320 +++++++++++++++++++ 4 files changed, 347 insertions(+), 2 deletions(-) create mode 100644 lib/tools/ui/styleguide/router.dart create mode 100644 lib/tools/ui/styleguide/styleguide_page.dart diff --git a/lib/router.dart b/lib/router.dart index 6db942612d..ce3cd38ae4 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -26,6 +26,7 @@ import 'package:titan/settings/router.dart'; import 'package:titan/raffle/router.dart'; import 'package:titan/tools/middlewares/authenticated_middleware.dart'; import 'package:titan/tools/middlewares/deferred_middleware.dart'; +import 'package:titan/tools/ui/styleguide/router.dart'; import 'package:titan/vote/router.dart'; import 'package:qlevar_router/qlevar_router.dart'; @@ -90,6 +91,7 @@ class AppRouter { RaffleRouter(ref).route(), RecommendationRouter(ref).route(), SettingsRouter(ref).route(), + StyleGuideRouter(ref).route(), VoteRouter(ref).route(), SeedLibraryRouter(ref).route(), ]; diff --git a/lib/tools/ui/styleguide/button.dart b/lib/tools/ui/styleguide/button.dart index 83bbc65060..1b1c0f6b2f 100644 --- a/lib/tools/ui/styleguide/button.dart +++ b/lib/tools/ui/styleguide/button.dart @@ -84,9 +84,9 @@ class Button extends StatelessWidget { @override Widget build(BuildContext context) { - return GestureDetector( + return InkWell( onTap: disabled == true ? null : onPressed, - behavior: HitTestBehavior.opaque, + borderRadius: BorderRadius.circular(8), child: Container( width: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), diff --git a/lib/tools/ui/styleguide/router.dart b/lib/tools/ui/styleguide/router.dart new file mode 100644 index 0000000000..654b089c8e --- /dev/null +++ b/lib/tools/ui/styleguide/router.dart @@ -0,0 +1,23 @@ +import 'package:either_dart/either.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:heroicons/heroicons.dart'; +import 'package:myecl/drawer/class/module.dart'; +import 'package:myecl/tools/ui/styleguide/styleguide_page.dart'; +import 'package:qlevar_router/qlevar_router.dart'; + +class StyleGuideRouter { + final Ref ref; + static const String root = '/styleguide'; + static final Module module = Module( + name: "Style Guide", + icon: const Left(HeroIcons.paintBrush), + root: StyleGuideRouter.root, + selected: false, + ); + StyleGuideRouter(this.ref); + QRoute route() => QRoute( + name: "styleguide", + path: StyleGuideRouter.root, + builder: () => const StyleGuidePage(), + ); +} diff --git a/lib/tools/ui/styleguide/styleguide_page.dart b/lib/tools/ui/styleguide/styleguide_page.dart new file mode 100644 index 0000000000..92c9be3170 --- /dev/null +++ b/lib/tools/ui/styleguide/styleguide_page.dart @@ -0,0 +1,320 @@ +import 'package:flutter/material.dart'; +import 'package:heroicons/heroicons.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:myecl/tools/constants.dart'; +import 'package:myecl/tools/ui/styleguide/button.dart'; +import 'package:myecl/tools/ui/styleguide/list_item.dart'; +import 'package:myecl/tools/ui/styleguide/navbar.dart'; +import 'package:myecl/tools/ui/styleguide/router.dart'; +import 'package:myecl/tools/ui/widgets/top_bar.dart'; + +class StyleGuidePage extends HookConsumerWidget { + const StyleGuidePage({super.key}); + + Widget sectionHeader(String title, String description) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: const TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: ColorConstants.title, + ), + ), + const SizedBox(height: 8), + Text( + description, + style: const TextStyle(fontSize: 16, color: ColorConstants.tertiary), + ), + ], + ); + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + return SafeArea( + child: Column( + children: [ + TopBar(title: "Style Guide", root: StyleGuideRouter.root), + Expanded( + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Section title + const Text( + "Components", + style: TextStyle( + fontSize: 28, + fontWeight: FontWeight.bold, + color: ColorConstants.title, + ), + ), + const SizedBox(height: 30), + + // Floating Navbar Section + sectionHeader( + "1. Floating Navigation Bar", + "A customizable navigation bar with a floating design and rounded corners", + ), + + // Floating Navbar Example + Container( + margin: const EdgeInsets.symmetric(vertical: 20), + decoration: BoxDecoration( + color: Colors.grey.shade100, + borderRadius: BorderRadius.circular(12), + ), + child: FloatingNavbar( + items: [ + FloatingNavbarItem( + title: 'Home', + onTap: () { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Home tapped')), + ); + }, + ), + FloatingNavbarItem( + title: 'Search', + onTap: () { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Search tapped')), + ); + }, + ), + FloatingNavbarItem( + title: 'Favorites', + onTap: () { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Favorites tapped'), + ), + ); + }, + ), + FloatingNavbarItem( + title: 'Profile', + onTap: () { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Profile tapped')), + ); + }, + ), + ], + ), + ), + + // Divider + const Divider(height: 40), + + // Buttons Section + sectionHeader( + "2. Buttons", + "Collection of styled buttons for different actions and states", + ), + + // Button Examples + Container( + margin: const EdgeInsets.symmetric(vertical: 20), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.grey.shade100, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + "Main Button:", + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + Button( + text: "Main Action", + onPressed: () { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Main button pressed'), + ), + ); + }, + ), + + const SizedBox(height: 16), + const Text( + "Danger Button:", + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + Button.danger( + text: "Delete", + onPressed: () { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Danger button pressed'), + ), + ); + }, + ), + + const SizedBox(height: 16), + const Text( + "On Danger Button:", + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + Button.onDanger( + text: "Confirm Delete", + onPressed: () { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('On danger button pressed'), + ), + ); + }, + ), + + const SizedBox(height: 16), + const Text( + "Secondary Button:", + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + Button.secondary( + text: "Cancel", + onPressed: () { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Secondary button pressed'), + ), + ); + }, + ), + + const SizedBox(height: 16), + const Text( + "Disabled Button:", + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + Button( + text: "Disabled", + disabled: true, + onPressed: () {}, + ), + ], + ), + ), + + // Divider + const Divider(height: 40), + + // List Items Section + sectionHeader( + "3. List Items", + "Consistent list items for displaying information with optional icon and subtitle", + ), + + // List Items Example + Container( + margin: const EdgeInsets.symmetric(vertical: 20), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.grey.shade100, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + "Basic List Item:", + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + ListItem( + title: "Settings", + onTap: () { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Settings tapped'), + ), + ); + }, + ), + + const SizedBox(height: 16), + const Text( + "List Item with Subtitle:", + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + ListItem( + title: "Account", + subtitle: "Manage your account details", + onTap: () { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Account tapped')), + ); + }, + ), + + const SizedBox(height: 16), + const Text( + "List Item with Icon:", + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + ListItem( + title: "Notifications", + icon: const HeroIcon( + HeroIcons.bell, + color: ColorConstants.tertiary, + ), + onTap: () { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Notifications tapped'), + ), + ); + }, + ), + + const SizedBox(height: 16), + const Text( + "Complete List Item:", + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + ListItem( + title: "Profile", + subtitle: "Edit your personal information", + icon: const HeroIcon( + HeroIcons.user, + color: ColorConstants.tertiary, + ), + onTap: () { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Profile tapped')), + ); + }, + ), + ], + ), + ), + + const SizedBox(height: 40), + ], + ), + ), + ), + ), + ], + ), + ); + } +} From 8b3f13e77e7827457bb7f39114874ddac7b6eb50 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Tue, 24 Jun 2025 20:35:32 +0200 Subject: [PATCH 006/473] fix: renaming remaining files --- lib/tools/ui/styleguide/button.dart | 2 +- lib/tools/ui/styleguide/list_item.dart | 2 +- lib/tools/ui/styleguide/navbar.dart | 2 +- lib/tools/ui/styleguide/router.dart | 4 ++-- lib/tools/ui/styleguide/styleguide_page.dart | 12 ++++++------ 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/tools/ui/styleguide/button.dart b/lib/tools/ui/styleguide/button.dart index 1b1c0f6b2f..8e2785e2b8 100644 --- a/lib/tools/ui/styleguide/button.dart +++ b/lib/tools/ui/styleguide/button.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:myecl/tools/constants.dart'; +import 'package:titan/tools/constants.dart'; enum ButtonType { main, danger, onDanger, secondary } diff --git a/lib/tools/ui/styleguide/list_item.dart b/lib/tools/ui/styleguide/list_item.dart index c0e2511735..1950d028a9 100644 --- a/lib/tools/ui/styleguide/list_item.dart +++ b/lib/tools/ui/styleguide/list_item.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; -import 'package:myecl/tools/constants.dart'; +import 'package:titan/tools/constants.dart'; class ListItem extends StatelessWidget { final String title; diff --git a/lib/tools/ui/styleguide/navbar.dart b/lib/tools/ui/styleguide/navbar.dart index 58582bf45b..0512924187 100644 --- a/lib/tools/ui/styleguide/navbar.dart +++ b/lib/tools/ui/styleguide/navbar.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:myecl/tools/constants.dart'; +import 'package:titan/tools/constants.dart'; class FloatingNavbarItem { final Function()? onTap; diff --git a/lib/tools/ui/styleguide/router.dart b/lib/tools/ui/styleguide/router.dart index 654b089c8e..4d926623ee 100644 --- a/lib/tools/ui/styleguide/router.dart +++ b/lib/tools/ui/styleguide/router.dart @@ -1,8 +1,8 @@ import 'package:either_dart/either.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:heroicons/heroicons.dart'; -import 'package:myecl/drawer/class/module.dart'; -import 'package:myecl/tools/ui/styleguide/styleguide_page.dart'; +import 'package:titan/drawer/class/module.dart'; +import 'package:titan/tools/ui/styleguide/styleguide_page.dart'; import 'package:qlevar_router/qlevar_router.dart'; class StyleGuideRouter { diff --git a/lib/tools/ui/styleguide/styleguide_page.dart b/lib/tools/ui/styleguide/styleguide_page.dart index 92c9be3170..64de62a818 100644 --- a/lib/tools/ui/styleguide/styleguide_page.dart +++ b/lib/tools/ui/styleguide/styleguide_page.dart @@ -1,12 +1,12 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:myecl/tools/constants.dart'; -import 'package:myecl/tools/ui/styleguide/button.dart'; -import 'package:myecl/tools/ui/styleguide/list_item.dart'; -import 'package:myecl/tools/ui/styleguide/navbar.dart'; -import 'package:myecl/tools/ui/styleguide/router.dart'; -import 'package:myecl/tools/ui/widgets/top_bar.dart'; +import 'package:titan/tools/constants.dart'; +import 'package:titan/tools/ui/styleguide/button.dart'; +import 'package:titan/tools/ui/styleguide/list_item.dart'; +import 'package:titan/tools/ui/styleguide/navbar.dart'; +import 'package:titan/tools/ui/styleguide/router.dart'; +import 'package:titan/tools/ui/widgets/top_bar.dart'; class StyleGuidePage extends HookConsumerWidget { const StyleGuidePage({super.key}); From b26ae4a37889dbe4344af57dd2959b77cb23039f Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Tue, 24 Jun 2025 21:51:00 +0200 Subject: [PATCH 007/473] feat: adding searchbar --- lib/tools/ui/styleguide/searchbar.dart | 82 ++++++++++++++++++++ lib/tools/ui/styleguide/styleguide_page.dart | 79 +++++++++++++++++++ 2 files changed, 161 insertions(+) create mode 100644 lib/tools/ui/styleguide/searchbar.dart diff --git a/lib/tools/ui/styleguide/searchbar.dart b/lib/tools/ui/styleguide/searchbar.dart new file mode 100644 index 0000000000..8b2d218a28 --- /dev/null +++ b/lib/tools/ui/styleguide/searchbar.dart @@ -0,0 +1,82 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:heroicons/heroicons.dart'; +import 'package:titan/tools/constants.dart'; + +class CustomSearchBar extends HookWidget { + final String hintText; + final Function() onFilter; + final Function(String) onSearch; + final bool autofocus; + const CustomSearchBar({ + super.key, + this.hintText = 'Rechercher', + required this.onFilter, + required this.onSearch, + this.autofocus = false, + }); + + @override + Widget build(BuildContext context) { + final textController = useTextEditingController(); + + return Material( + elevation: 4, + borderRadius: BorderRadius.circular(50), + color: ColorConstants.background, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 4.0), + child: Row( + children: [ + HeroIcon( + HeroIcons.magnifyingGlass, + color: ColorConstants.tertiary, + size: 24, + ), + const SizedBox(width: 8), + Expanded( + child: TextField( + controller: textController, + autofocus: autofocus, + onChanged: (value) { + onSearch(value); + }, + style: TextStyle(color: ColorConstants.tertiary, fontSize: 16), + decoration: InputDecoration( + hintText: hintText, + hintStyle: TextStyle( + color: ColorConstants.secondary, + fontSize: 16, + ), + border: InputBorder.none, + contentPadding: const EdgeInsets.symmetric(vertical: 12), + ), + ), + ), + if (textController.text.isNotEmpty) + IconButton( + icon: HeroIcon( + HeroIcons.xMark, + color: ColorConstants.tertiary, + size: 20, + ), + onPressed: () { + textController.clear(); + }, + splashRadius: 20, + ), + IconButton( + icon: HeroIcon( + HeroIcons.adjustmentsHorizontal, + color: ColorConstants.tertiary, + size: 20, + ), + onPressed: onFilter, + splashRadius: 20, + ), + ], + ), + ), + ); + } +} diff --git a/lib/tools/ui/styleguide/styleguide_page.dart b/lib/tools/ui/styleguide/styleguide_page.dart index 64de62a818..3ab0a43309 100644 --- a/lib/tools/ui/styleguide/styleguide_page.dart +++ b/lib/tools/ui/styleguide/styleguide_page.dart @@ -6,6 +6,7 @@ import 'package:titan/tools/ui/styleguide/button.dart'; import 'package:titan/tools/ui/styleguide/list_item.dart'; import 'package:titan/tools/ui/styleguide/navbar.dart'; import 'package:titan/tools/ui/styleguide/router.dart'; +import 'package:titan/tools/ui/styleguide/searchbar.dart'; import 'package:titan/tools/ui/widgets/top_bar.dart'; class StyleGuidePage extends HookConsumerWidget { @@ -307,6 +308,84 @@ class StyleGuidePage extends HookConsumerWidget { ), ), + // Divider + const Divider(height: 40), + + // SearchBar Section + sectionHeader( + "4. SearchBar", + "A customizable search component with filtering capabilities", + ), + + // SearchBar Examples + Container( + margin: const EdgeInsets.symmetric(vertical: 20), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.grey.shade100, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + "Basic SearchBar:", + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + CustomSearchBar( + hintText: "Search something...", + onSearch: (_) { + // Handle search logic in parent component + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Search callback triggered'), + duration: Duration(seconds: 1), + ), + ); + }, + onFilter: () { + // Handle filter logic in parent component + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Filter button clicked'), + duration: Duration(seconds: 1), + ), + ); + }, + ), + + const SizedBox(height: 24), + const Text( + "SearchBar with Filters:", + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + CustomSearchBar( + hintText: "Search users...", + onSearch: (_) { + // This is where you would handle search queries + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('User search triggered'), + duration: Duration(seconds: 1), + ), + ); + }, + onFilter: () { + // This is where you would handle filter button clicks + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('User filter dialog opened'), + duration: Duration(seconds: 1), + ), + ); + }, + ), + ], + ), + ), + const SizedBox(height: 40), ], ), From be2e95289b40a230837f4c12a64dd7f3b75c042e Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Thu, 26 Jun 2025 22:33:24 +0200 Subject: [PATCH 008/473] feat: adding bottom modal and horizontal select --- .../ui/styleguide/bottom_modal_template.dart | 102 +++ lib/tools/ui/styleguide/button.dart | 7 +- .../styleguide/horizontal_multi_select.dart | 47 ++ lib/tools/ui/styleguide/item_chip.dart | 32 + lib/tools/ui/styleguide/navbar.dart | 3 +- lib/tools/ui/styleguide/styleguide_page.dart | 617 +++++++++++++++++- 6 files changed, 785 insertions(+), 23 deletions(-) create mode 100644 lib/tools/ui/styleguide/bottom_modal_template.dart create mode 100644 lib/tools/ui/styleguide/horizontal_multi_select.dart create mode 100644 lib/tools/ui/styleguide/item_chip.dart diff --git a/lib/tools/ui/styleguide/bottom_modal_template.dart b/lib/tools/ui/styleguide/bottom_modal_template.dart new file mode 100644 index 0000000000..a7ba10fb3f --- /dev/null +++ b/lib/tools/ui/styleguide/bottom_modal_template.dart @@ -0,0 +1,102 @@ +import 'package:flutter/material.dart'; +import 'package:titan/tools/constants.dart'; + +enum BottomModalType { main, danger } + +class BottomModalTemplate extends StatelessWidget { + final Widget child; + final String title; + final String? description; + final List? actions; + final BottomModalType type; + final String? animationKey; + + const BottomModalTemplate({ + super.key, + required this.child, + this.type = BottomModalType.main, + this.animationKey, + required this.title, + this.description, + this.actions, + }); + + const BottomModalTemplate.danger({ + super.key, + required this.child, + this.animationKey, + required this.title, + this.description, + this.actions, + }) : type = BottomModalType.danger; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Center( + child: Container( + margin: const EdgeInsets.only(bottom: 12), + width: 120, + height: 4, + decoration: BoxDecoration( + color: ColorConstants.onTertiary, + borderRadius: BorderRadius.circular(2), + boxShadow: [ + BoxShadow( + color: ColorConstants.onTertiary.withAlpha(50), + blurRadius: 4, + spreadRadius: 1, + ), + ], + ), + ), + ), + Hero( + tag: animationKey ?? 'bottom_modal', + child: Container( + decoration: BoxDecoration( + color: type == BottomModalType.main + ? ColorConstants.background + : ColorConstants.main, + borderRadius: BorderRadius.vertical(top: Radius.circular(30)), + ), + padding: EdgeInsets.all(50), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + title, + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w900, + color: type == BottomModalType.main + ? ColorConstants.tertiary + : ColorConstants.background, + ), + ), + SizedBox(height: 10), + if (description != null) + Text( + description!, + style: TextStyle( + fontSize: 15, + color: type == BottomModalType.main + ? ColorConstants.tertiary + : ColorConstants.background, + ), + ), + child, + if (actions != null && actions!.isNotEmpty) + Column(children: actions!), + ], + ), + ), + ), + ], + ); + } +} diff --git a/lib/tools/ui/styleguide/button.dart b/lib/tools/ui/styleguide/button.dart index 8e2785e2b8..d8ca74c000 100644 --- a/lib/tools/ui/styleguide/button.dart +++ b/lib/tools/ui/styleguide/button.dart @@ -84,9 +84,8 @@ class Button extends StatelessWidget { @override Widget build(BuildContext context) { - return InkWell( + return GestureDetector( onTap: disabled == true ? null : onPressed, - borderRadius: BorderRadius.circular(8), child: Container( width: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), @@ -100,8 +99,8 @@ class Button extends StatelessWidget { text, style: TextStyle( color: textColor, - fontSize: 16, - fontWeight: FontWeight.w500, + fontSize: 18, + fontWeight: FontWeight.w900, ), ), ), diff --git a/lib/tools/ui/styleguide/horizontal_multi_select.dart b/lib/tools/ui/styleguide/horizontal_multi_select.dart new file mode 100644 index 0000000000..b28e934218 --- /dev/null +++ b/lib/tools/ui/styleguide/horizontal_multi_select.dart @@ -0,0 +1,47 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:titan/tools/ui/styleguide/item_chip.dart'; + +class HorizontalMultiSelect extends HookWidget { + final List items; + final Widget Function(BuildContext context, T item, int index, bool selected) + itemBuilder; + final Widget? firstChild; + final Function(T item)? onItemSelected; + final Function(T item)? onLongPress; + final Widget? title; + const HorizontalMultiSelect({ + super.key, + required this.items, + required this.itemBuilder, + this.firstChild, + this.onItemSelected, + this.onLongPress, + this.title, + }); + + @override + Widget build(BuildContext context) { + final selected = useState(null); + return ListView.builder( + scrollDirection: Axis.horizontal, + clipBehavior: Clip.none, + itemCount: items.length + (firstChild != null ? 1 : 0), + itemBuilder: (context, index) { + if (index == 0 && firstChild != null) { + return firstChild!; + } + final item = items[index - (firstChild != null ? 1 : 0)]; + return ItemChip( + selected: selected.value == index, + onTap: () { + selected.value = index; + onItemSelected?.call(item); + }, + onLongPress: () => onLongPress?.call(item), + child: itemBuilder(context, item, index, selected.value == index), + ); + }, + ); + } +} diff --git a/lib/tools/ui/styleguide/item_chip.dart b/lib/tools/ui/styleguide/item_chip.dart new file mode 100644 index 0000000000..e4cbe8ad87 --- /dev/null +++ b/lib/tools/ui/styleguide/item_chip.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; + +class ItemChip extends StatelessWidget { + final bool selected; + final Function()? onTap; + final Function()? onLongPress; + final Widget child; + const ItemChip({ + super.key, + this.selected = false, + this.onTap, + this.onLongPress, + required this.child, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + onLongPress: onLongPress, + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 10.0), + padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 20.0), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(30.0), + color: selected ? Colors.black : Colors.grey.shade200, + ), + child: child, + ), + ); + } +} diff --git a/lib/tools/ui/styleguide/navbar.dart b/lib/tools/ui/styleguide/navbar.dart index 0512924187..c0df6b0ca6 100644 --- a/lib/tools/ui/styleguide/navbar.dart +++ b/lib/tools/ui/styleguide/navbar.dart @@ -117,8 +117,7 @@ class FloatingNavbar extends HookWidget { child: Material( color: Colors.transparent, borderRadius: BorderRadius.circular(borderRadius), - child: InkWell( - borderRadius: BorderRadius.circular(borderRadius), + child: GestureDetector( onTap: () { item.onTap?.call(); currentIndex.value = index; diff --git a/lib/tools/ui/styleguide/styleguide_page.dart b/lib/tools/ui/styleguide/styleguide_page.dart index 3ab0a43309..a02fa19a75 100644 --- a/lib/tools/ui/styleguide/styleguide_page.dart +++ b/lib/tools/ui/styleguide/styleguide_page.dart @@ -2,7 +2,10 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/tools/constants.dart'; +import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; import 'package:titan/tools/ui/styleguide/button.dart'; +import 'package:titan/tools/ui/styleguide/horizontal_multi_select.dart'; +import 'package:titan/tools/ui/styleguide/item_chip.dart'; import 'package:titan/tools/ui/styleguide/list_item.dart'; import 'package:titan/tools/ui/styleguide/navbar.dart'; import 'package:titan/tools/ui/styleguide/router.dart'; @@ -335,17 +338,15 @@ class StyleGuidePage extends HookConsumerWidget { const SizedBox(height: 8), CustomSearchBar( hintText: "Search something...", - onSearch: (_) { - // Handle search logic in parent component + onSearch: (query) { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Search callback triggered'), - duration: Duration(seconds: 1), + SnackBar( + content: Text('Searching for: "$query"'), + duration: const Duration(seconds: 1), ), ); }, onFilter: () { - // Handle filter logic in parent component ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Filter button clicked'), @@ -357,27 +358,70 @@ class StyleGuidePage extends HookConsumerWidget { const SizedBox(height: 24), const Text( - "SearchBar with Filters:", + "SearchBar with Filter Dialog:", style: TextStyle(fontWeight: FontWeight.bold), ), const SizedBox(height: 8), CustomSearchBar( hintText: "Search users...", - onSearch: (_) { - // This is where you would handle search queries + onSearch: (query) { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('User search triggered'), - duration: Duration(seconds: 1), + SnackBar( + content: Text('User search: "$query"'), + duration: const Duration(seconds: 1), ), ); }, onFilter: () { - // This is where you would handle filter button clicks - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('User filter dialog opened'), - duration: Duration(seconds: 1), + // Show filter dialog + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text("Filter Options"), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ListTile( + title: const Text("Name"), + onTap: () { + Navigator.pop(context); + ScaffoldMessenger.of( + context, + ).showSnackBar( + const SnackBar( + content: Text('Filter by Name'), + ), + ); + }, + ), + ListTile( + title: const Text("Role"), + onTap: () { + Navigator.pop(context); + ScaffoldMessenger.of( + context, + ).showSnackBar( + const SnackBar( + content: Text('Filter by Role'), + ), + ); + }, + ), + ListTile( + title: const Text("Age"), + onTap: () { + Navigator.pop(context); + ScaffoldMessenger.of( + context, + ).showSnackBar( + const SnackBar( + content: Text('Filter by Age'), + ), + ); + }, + ), + ], + ), ), ); }, @@ -386,6 +430,545 @@ class StyleGuidePage extends HookConsumerWidget { ), ), + // Divider + const Divider(height: 40), + + // Bottom Modal Template Section + sectionHeader( + "5. Bottom Modal Template", + "A reusable bottom sheet modal with a handle and customizable content", + ), + + // Bottom Modal Example + Container( + margin: const EdgeInsets.symmetric(vertical: 20), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.grey.shade100, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + "Bottom Modal Example:", + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + Button( + text: "Show Bottom Modal", + onPressed: () { + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (context) { + return BottomModalTemplate( + title: "Example Modal", + description: + "This is a customizable bottom modal template that can contain any content.", + actions: [ + Button( + text: "Close Modal", + onPressed: () => Navigator.pop(context), + ), + ], + child: Container( + height: 150, + alignment: Alignment.center, + child: const Text( + "Modal Content Area", + style: TextStyle( + fontSize: 16, + color: ColorConstants.tertiary, + ), + ), + ), + ); + }, + ); + }, + ), + + const SizedBox(height: 16), + Button.danger( + text: "Show Danger Modal", + onPressed: () { + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (context) { + return BottomModalTemplate.danger( + title: "Confirm Deletion", + description: + "This action cannot be undone. All data will be permanently deleted.", + actions: [ + Button.onDanger( + text: "Delete Permanently", + onPressed: () => Navigator.pop(context), + ), + const SizedBox(height: 8), + Button.secondary( + text: "Cancel", + onPressed: () => Navigator.pop(context), + ), + ], + child: Container( + height: 100, + alignment: Alignment.center, + child: const Text( + "Danger Content Area", + style: TextStyle( + fontSize: 16, + color: ColorConstants.background, + ), + ), + ), + ); + }, + ); + }, + ), + + const SizedBox(height: 16), + Button.secondary( + text: "Show Member Management Modal", + onPressed: () { + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (context) { + return BottomModalTemplate( + title: "Member Management", + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: + CrossAxisAlignment.stretch, + children: [ + // Search bar + Padding( + padding: const EdgeInsets.all(16.0), + child: CustomSearchBar( + hintText: "Search members...", + onSearch: (query) {}, + onFilter: () {}, + ), + ), + + ListItem( + title: "Add member", + icon: const HeroIcon( + HeroIcons.plus, + color: ColorConstants.tertiary, + ), + onTap: () { + Navigator.pop(context); + ScaffoldMessenger.of( + context, + ).showSnackBar( + const SnackBar( + content: Text( + 'Add member tapped', + ), + ), + ); + }, + ), + + const Divider(), + + ListItem( + title: "Zoto - Prez", + subtitle: "Jules Barra", + onTap: () { + Navigator.pop(context); + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: + Colors.transparent, + builder: (context) { + return BottomModalTemplate( + title: "Member Details", + description: "Jules Barra", + actions: [ + Button.secondary( + text: "Modify Role", + onPressed: () { + Navigator.pop(context); + ScaffoldMessenger.of( + context, + ).showSnackBar( + const SnackBar( + content: Text( + 'Modify role tapped', + ), + ), + ); + }, + ), + const SizedBox(height: 8), + Button.danger( + text: "Remove Role", + onPressed: () { + Navigator.pop(context); + ScaffoldMessenger.of( + context, + ).showSnackBar( + const SnackBar( + content: Text( + 'Remove role tapped', + ), + ), + ); + }, + ), + ], + child: const Center( + child: Padding( + padding: EdgeInsets.all( + 24.0, + ), + child: Text( + "Role: Zoto - Prez", + style: TextStyle( + fontSize: 18, + fontWeight: + FontWeight.bold, + color: ColorConstants + .title, + ), + ), + ), + ), + ); + }, + ); + }, + ), + + ListItem( + title: "Corpo", + subtitle: + "Gère toutes les autres assos", + onTap: () { + // Show member details + }, + ), + + ListItem( + title: "Biero", + subtitle: "Nathan Guigui", + onTap: () { + // Show member details + }, + ), + ], + ), + ); + }, + ); + }, + ), + + const SizedBox(height: 16), + const Text( + "Usage Example:", + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.grey.shade200, + borderRadius: BorderRadius.circular(8), + ), + child: const Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "showModalBottomSheet(", + style: TextStyle(fontFamily: "monospace"), + ), + Text( + " context: context,", + style: TextStyle(fontFamily: "monospace"), + ), + Text( + " isScrollControlled: true,", + style: TextStyle(fontFamily: "monospace"), + ), + Text( + " backgroundColor: Colors.transparent,", + style: TextStyle(fontFamily: "monospace"), + ), + Text( + " builder: (context) => BottomModalTemplate(", + style: TextStyle(fontFamily: "monospace"), + ), + Text( + " child: YourContent(),", + style: TextStyle(fontFamily: "monospace"), + ), + Text( + " ),", + style: TextStyle(fontFamily: "monospace"), + ), + Text( + ");", + style: TextStyle(fontFamily: "monospace"), + ), + ], + ), + ), + ], + ), + ), + + const SizedBox(height: 40), + + // Horizontal Multi Select Section + sectionHeader( + "6. Horizontal Multi Select", + "A horizontally scrollable list of selectable items with customizable appearance", + ), + + // Horizontal Multi Select Examples + Container( + margin: const EdgeInsets.symmetric(vertical: 20), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.grey.shade100, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + "Basic Multi Select:", + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + SizedBox( + height: 50, + child: HorizontalMultiSelect( + items: const [ + "Apple", + "Banana", + "Cherry", + "Date", + "Fig", + "Grape", + ], + itemBuilder: (context, item, index, selected) { + return Text( + item, + style: TextStyle( + fontSize: 16, + color: selected + ? Colors.white + : Colors.black, + ), + ); + }, + onItemSelected: (item) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Selected: $item'), + duration: const Duration(seconds: 1), + ), + ); + }, + ), + ), + + const SizedBox(height: 24), + const Text( + "With Custom First Child:", + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 10), + SizedBox( + height: 50, + child: HorizontalMultiSelect( + items: const [ + Colors.red, + Colors.orange, + Colors.yellow, + Colors.green, + Colors.blue, + Colors.purple, + ], + firstChild: ItemChip( + child: const HeroIcon( + HeroIcons.plus, + size: 24, + color: Colors.black, + ), + onTap: () { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Add new color'), + duration: Duration(seconds: 1), + ), + ); + }, + ), + itemBuilder: (context, color, index, selected) { + return Container( + width: 30, + decoration: BoxDecoration( + color: color, + borderRadius: BorderRadius.circular(25), + ), + ); + }, + onItemSelected: (color) { + final colorName = color == Colors.red + ? "Red" + : color == Colors.orange + ? "Orange" + : color == Colors.yellow + ? "Yellow" + : color == Colors.green + ? "Green" + : color == Colors.blue + ? "Blue" + : "Purple"; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Selected: $colorName'), + duration: const Duration(seconds: 1), + ), + ); + }, + ), + ), + + const SizedBox(height: 24), + const Text( + "With Long Press Support:", + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + SizedBox( + height: 70, + child: HorizontalMultiSelect>( + items: const [ + {"name": "John", "role": "Admin"}, + {"name": "Emma", "role": "Editor"}, + {"name": "Michael", "role": "Viewer"}, + {"name": "Sarah", "role": "Admin"}, + {"name": "David", "role": "Editor"}, + ], + itemBuilder: (context, user, index, selected) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + user["name"], + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: selected + ? Colors.blue + : Colors.black, + ), + ), + Text( + user["role"], + style: TextStyle( + fontSize: 12, + color: selected + ? Colors.blue.shade700 + : Colors.grey.shade600, + ), + ), + ], + ); + }, + onItemSelected: (user) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Selected: ${user["name"]}'), + duration: const Duration(seconds: 1), + ), + ); + }, + onLongPress: (user) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + 'Long press on ${user["name"]}', + ), + backgroundColor: Colors.orange, + duration: const Duration(seconds: 1), + ), + ); + }, + ), + ), + + const SizedBox(height: 16), + const Text( + "Usage Example:", + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.grey.shade200, + borderRadius: BorderRadius.circular(8), + ), + child: const Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "HorizontalMultiSelect(", + style: TextStyle(fontFamily: "monospace"), + ), + Text( + " items: ['Item 1', 'Item 2', 'Item 3'],", + style: TextStyle(fontFamily: "monospace"), + ), + Text( + " itemBuilder: (context, item, index) {", + style: TextStyle(fontFamily: "monospace"), + ), + Text( + " return Text(item);", + style: TextStyle(fontFamily: "monospace"), + ), + Text( + " },", + style: TextStyle(fontFamily: "monospace"), + ), + Text( + " onItemSelected: (item) {", + style: TextStyle(fontFamily: "monospace"), + ), + Text( + " print('Selected: \$item');", + style: TextStyle(fontFamily: "monospace"), + ), + Text( + " },", + style: TextStyle(fontFamily: "monospace"), + ), + Text( + ")", + style: TextStyle(fontFamily: "monospace"), + ), + ], + ), + ), + ], + ), + ), + const SizedBox(height: 40), ], ), From 8f5d1e1be9115d985dc3051d86598177de97e723 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Fri, 27 Jun 2025 18:22:14 +0200 Subject: [PATCH 009/473] feat: adding toggle list item --- lib/tools/ui/styleguide/icon_button.dart | 87 +++++++++++++++++++ lib/tools/ui/styleguide/list_item.dart | 45 ++-------- .../ui/styleguide/list_item_template.dart | 67 ++++++++++++++ lib/tools/ui/styleguide/list_item_toggle.dart | 48 ++++++++++ 4 files changed, 210 insertions(+), 37 deletions(-) create mode 100644 lib/tools/ui/styleguide/icon_button.dart create mode 100644 lib/tools/ui/styleguide/list_item_template.dart create mode 100644 lib/tools/ui/styleguide/list_item_toggle.dart diff --git a/lib/tools/ui/styleguide/icon_button.dart b/lib/tools/ui/styleguide/icon_button.dart new file mode 100644 index 0000000000..4982c8079d --- /dev/null +++ b/lib/tools/ui/styleguide/icon_button.dart @@ -0,0 +1,87 @@ +import 'package:flutter/material.dart'; +import 'package:titan/tools/constants.dart'; + +enum CustomIconButtonType { main, danger, secondary } + +class CustomIconButton extends StatelessWidget { + final CustomIconButtonType type; + final Widget icon; + final bool? disabled; + final Function() onPressed; + + const CustomIconButton({ + super.key, + this.type = CustomIconButtonType.main, + required this.icon, + required this.onPressed, + this.disabled = false, + }); + + const CustomIconButton.danger({ + super.key, + required this.icon, + required this.onPressed, + this.disabled = false, + }) : type = CustomIconButtonType.danger; + + const CustomIconButton.secondary({ + super.key, + required this.icon, + required this.onPressed, + this.disabled = false, + }) : type = CustomIconButtonType.secondary; + + Color get backgroundColor { + switch (type) { + case CustomIconButtonType.main: + return ColorConstants.tertiary; + case CustomIconButtonType.danger: + return ColorConstants.main; + case CustomIconButtonType.secondary: + return ColorConstants.background; + } + } + + Color get borderColor { + switch (type) { + case CustomIconButtonType.main: + return ColorConstants.onTertiary; + case CustomIconButtonType.danger: + return ColorConstants.mainBorder; + case CustomIconButtonType.secondary: + return ColorConstants.onBackground; + } + } + + Color get textColor { + Color color; + switch (type) { + case CustomIconButtonType.main: + color = ColorConstants.background; + case CustomIconButtonType.danger: + color = ColorConstants.background; + case CustomIconButtonType.secondary: + color = ColorConstants.tertiary; + } + if (disabled == true) { + return color.withAlpha(150); + } + return color; + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: disabled == true ? null : onPressed, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: borderColor), + ), + child: Center(child: icon), + ), + ); + } +} diff --git a/lib/tools/ui/styleguide/list_item.dart b/lib/tools/ui/styleguide/list_item.dart index 1950d028a9..a6dfbf0ec7 100644 --- a/lib/tools/ui/styleguide/list_item.dart +++ b/lib/tools/ui/styleguide/list_item.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:titan/tools/constants.dart'; +import 'package:titan/tools/ui/styleguide/list_item_template.dart'; class ListItem extends StatelessWidget { final String title; @@ -18,44 +19,14 @@ class ListItem extends StatelessWidget { @override Widget build(BuildContext context) { - return GestureDetector( + return ListItemTemplate( onTap: onTap, - behavior: HitTestBehavior.opaque, - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 4), - child: Row( - children: [ - if (icon != null) ...[icon!, const SizedBox(width: 10)], - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - title, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: ColorConstants.tertiary, - ), - ), - if (subtitle != null) - Text( - subtitle!, - style: TextStyle( - fontSize: 12, - color: ColorConstants.onTertiary, - ), - ), - ], - ), - ), - const SizedBox(width: 10), - const HeroIcon( - HeroIcons.chevronRight, - color: ColorConstants.tertiary, - ), - ], - ), + title: title, + subtitle: subtitle, + icon: icon, + trailing: const HeroIcon( + HeroIcons.chevronRight, + color: ColorConstants.tertiary, ), ); } diff --git a/lib/tools/ui/styleguide/list_item_template.dart b/lib/tools/ui/styleguide/list_item_template.dart new file mode 100644 index 0000000000..3998af8a3f --- /dev/null +++ b/lib/tools/ui/styleguide/list_item_template.dart @@ -0,0 +1,67 @@ +import 'package:flutter/material.dart'; +import 'package:heroicons/heroicons.dart'; +import 'package:titan/tools/constants.dart'; + +class ListItemTemplate extends StatelessWidget { + final String title; + final String? subtitle; + final Widget? icon; + final Function()? onTap; + final Widget? trailing; + + const ListItemTemplate({ + super.key, + required this.title, + this.subtitle, + this.icon, + this.onTap, + this.trailing, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + behavior: HitTestBehavior.opaque, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 4), + child: Row( + children: [ + if (icon != null) ...[icon!, const SizedBox(width: 10)], + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: ColorConstants.tertiary, + ), + ), + if (subtitle != null) + Text( + subtitle!, + style: TextStyle( + fontSize: 12, + color: ColorConstants.onTertiary, + ), + ), + ], + ), + ), + const SizedBox(width: 10), + if (trailing != null) + trailing! + else + const HeroIcon( + HeroIcons.chevronRight, + color: ColorConstants.tertiary, + ), + ], + ), + ), + ); + } +} diff --git a/lib/tools/ui/styleguide/list_item_toggle.dart b/lib/tools/ui/styleguide/list_item_toggle.dart new file mode 100644 index 0000000000..1e29067702 --- /dev/null +++ b/lib/tools/ui/styleguide/list_item_toggle.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; +import 'package:heroicons/heroicons.dart'; +import 'package:titan/tools/constants.dart'; +import 'package:titan/tools/ui/styleguide/icon_button.dart'; +import 'package:titan/tools/ui/styleguide/list_item_template.dart'; + +class ToggleListItem extends StatelessWidget { + final String title; + final String? subtitle; + final Widget? icon; + final Function()? onTap; + final bool selected; + + const ToggleListItem({ + super.key, + required this.title, + this.subtitle, + this.icon, + this.onTap, + this.selected = false, + }); + + @override + Widget build(BuildContext context) { + return ListItemTemplate( + onTap: onTap, + title: title, + subtitle: subtitle, + icon: icon, + trailing: selected + ? CustomIconButton( + type: CustomIconButtonType.main, + icon: const HeroIcon( + HeroIcons.minus, + color: ColorConstants.background, + ), + onPressed: onTap ?? () {}, + ) + : CustomIconButton.secondary( + icon: const HeroIcon( + HeroIcons.plus, + color: ColorConstants.tertiary, + ), + onPressed: onTap ?? () {}, + ), + ); + } +} From 34d620d72de64a9dfd8be5dbb205b45e9d49eaf2 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Fri, 27 Jun 2025 18:32:19 +0200 Subject: [PATCH 010/473] feat: adding inputs --- lib/tools/ui/styleguide/date_entry.dart | 24 +++++ lib/tools/ui/styleguide/image_entry.dart | 24 +++++ lib/tools/ui/styleguide/text_entry.dart | 116 +++++++++++++++++++++++ 3 files changed, 164 insertions(+) create mode 100644 lib/tools/ui/styleguide/date_entry.dart create mode 100644 lib/tools/ui/styleguide/image_entry.dart create mode 100644 lib/tools/ui/styleguide/text_entry.dart diff --git a/lib/tools/ui/styleguide/date_entry.dart b/lib/tools/ui/styleguide/date_entry.dart new file mode 100644 index 0000000000..355d49f4b3 --- /dev/null +++ b/lib/tools/ui/styleguide/date_entry.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; +import 'package:heroicons/heroicons.dart'; +import 'package:titan/tools/constants.dart'; +import 'package:titan/tools/ui/styleguide/list_item_template.dart'; + +class DateEntry extends StatelessWidget { + final String title; + final String? subtitle; + final Function()? onTap; + const DateEntry({super.key, required this.title, this.subtitle, this.onTap}); + + @override + Widget build(BuildContext context) { + return ListItemTemplate( + onTap: onTap, + title: title, + subtitle: subtitle, + trailing: const HeroIcon( + HeroIcons.calendar, + color: ColorConstants.tertiary, + ), + ); + } +} diff --git a/lib/tools/ui/styleguide/image_entry.dart b/lib/tools/ui/styleguide/image_entry.dart new file mode 100644 index 0000000000..9941a144d7 --- /dev/null +++ b/lib/tools/ui/styleguide/image_entry.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; +import 'package:heroicons/heroicons.dart'; +import 'package:titan/tools/constants.dart'; +import 'package:titan/tools/ui/styleguide/list_item_template.dart'; + +class ImageEntry extends StatelessWidget { + final String title; + final String? subtitle; + final Function()? onTap; + const ImageEntry({super.key, required this.title, this.subtitle, this.onTap}); + + @override + Widget build(BuildContext context) { + return ListItemTemplate( + onTap: onTap, + title: title, + subtitle: subtitle, + trailing: const HeroIcon( + HeroIcons.phone, + color: ColorConstants.tertiary, + ), + ); + } +} diff --git a/lib/tools/ui/styleguide/text_entry.dart b/lib/tools/ui/styleguide/text_entry.dart new file mode 100644 index 0000000000..4f208303e1 --- /dev/null +++ b/lib/tools/ui/styleguide/text_entry.dart @@ -0,0 +1,116 @@ +import 'package:flutter/material.dart'; +import 'package:titan/tools/constants.dart'; + +class TextEntry extends StatelessWidget { + final String label, suffix, prefix, noValueError; + final bool isInt, isDouble, isNegative; + final bool canBeEmpty; + final bool enabled; + final TextEditingController controller; + final TextInputType keyboardType; + final Color color, enabledColor, errorColor; + final Widget? suffixIcon; + final Function(String)? onChanged; + final String? Function(String)? validator; + final int? minLines, maxLines; + final TextInputAction textInputAction; + + const TextEntry({ + super.key, + required this.label, + required this.controller, + this.onChanged, + this.validator, + this.minLines, + this.maxLines, + this.prefix = '', + this.suffix = '', + this.enabled = true, + this.isInt = false, + this.isDouble = false, + this.keyboardType = TextInputType.text, + this.canBeEmpty = false, + this.color = ColorConstants.tertiary, + this.enabledColor = ColorConstants.tertiary, + this.errorColor = ColorConstants.main, + this.noValueError = TextConstants.noValue, + this.suffixIcon, + this.isNegative = false, + this.textInputAction = TextInputAction.next, + }); + + @override + Widget build(BuildContext context) { + return TextFormField( + minLines: minLines, + maxLines: maxLines, + controller: controller, + keyboardType: keyboardType, + cursorColor: color, + onChanged: onChanged, + textInputAction: (keyboardType == TextInputType.multiline) + ? TextInputAction.newline + : TextInputAction.next, + enabled: enabled, + decoration: InputDecoration( + label: Text( + canBeEmpty ? '$label (optionnel)' : label, + style: TextStyle(color: color, height: 0.5), + ), + suffix: suffixIcon == null && suffix.isEmpty + ? null + : Container( + padding: const EdgeInsets.only(left: 10), + child: + suffixIcon ?? Text(suffix, style: TextStyle(color: color)), + ), + prefix: prefix.isEmpty + ? null + : Container( + padding: const EdgeInsets.only(right: 10), + child: Text(prefix, style: TextStyle(color: color)), + ), + floatingLabelStyle: const TextStyle( + color: Colors.black, + fontWeight: FontWeight.bold, + ), + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide(color: enabledColor), + ), + errorBorder: UnderlineInputBorder( + borderSide: BorderSide(color: errorColor, width: 2.0), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide(color: color, width: 2.0), + ), + ), + validator: (value) { + if (value == null || value.isEmpty) { + if (canBeEmpty) { + return null; + } + return noValueError; + } + + if (isInt) { + final intValue = int.tryParse(value); + if (intValue == null || (intValue < 0 && !isNegative)) { + return TextConstants.invalidNumber; + } + } + + if (isDouble) { + final doubleValue = double.tryParse(value.replaceAll(',', '.')); + if (doubleValue == null || (doubleValue < 0 && !isNegative)) { + return TextConstants.invalidNumber; + } + } + + if (validator == null) { + return null; + } + return validator!(value); + }, + ); + } +} From 92e68714a726980013f1c248e2e9753671f68d05 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Fri, 27 Jun 2025 18:56:52 +0200 Subject: [PATCH 011/473] feat: adding component to styleguide page --- lib/tools/ui/styleguide/icon_button.dart | 2 +- lib/tools/ui/styleguide/styleguide_page.dart | 324 +++++++++++++++++++ 2 files changed, 325 insertions(+), 1 deletion(-) diff --git a/lib/tools/ui/styleguide/icon_button.dart b/lib/tools/ui/styleguide/icon_button.dart index 4982c8079d..92943d87a6 100644 --- a/lib/tools/ui/styleguide/icon_button.dart +++ b/lib/tools/ui/styleguide/icon_button.dart @@ -74,7 +74,7 @@ class CustomIconButton extends StatelessWidget { return GestureDetector( onTap: disabled == true ? null : onPressed, child: Container( - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 3), decoration: BoxDecoration( color: backgroundColor, borderRadius: BorderRadius.circular(8), diff --git a/lib/tools/ui/styleguide/styleguide_page.dart b/lib/tools/ui/styleguide/styleguide_page.dart index a02fa19a75..d97e69c06d 100644 --- a/lib/tools/ui/styleguide/styleguide_page.dart +++ b/lib/tools/ui/styleguide/styleguide_page.dart @@ -4,12 +4,18 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; import 'package:titan/tools/ui/styleguide/button.dart'; +import 'package:titan/tools/ui/styleguide/date_entry.dart'; import 'package:titan/tools/ui/styleguide/horizontal_multi_select.dart'; +import 'package:titan/tools/ui/styleguide/icon_button.dart'; +import 'package:titan/tools/ui/styleguide/image_entry.dart'; import 'package:titan/tools/ui/styleguide/item_chip.dart'; import 'package:titan/tools/ui/styleguide/list_item.dart'; +import 'package:titan/tools/ui/styleguide/list_item_template.dart'; +import 'package:titan/tools/ui/styleguide/list_item_toggle.dart'; import 'package:titan/tools/ui/styleguide/navbar.dart'; import 'package:titan/tools/ui/styleguide/router.dart'; import 'package:titan/tools/ui/styleguide/searchbar.dart'; +import 'package:titan/tools/ui/styleguide/text_entry.dart'; import 'package:titan/tools/ui/widgets/top_bar.dart'; class StyleGuidePage extends HookConsumerWidget { @@ -970,6 +976,324 @@ class StyleGuidePage extends HookConsumerWidget { ), const SizedBox(height: 40), + + // Icon Button Section + const Divider(height: 40), + + sectionHeader( + "7. Icon Buttons", + "Icon-only buttons in various styles for compact UI elements", + ), + + Container( + margin: const EdgeInsets.symmetric(vertical: 20), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.grey.shade100, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + "Main Icon Button:", + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + CustomIconButton( + icon: const HeroIcon( + HeroIcons.plus, + color: ColorConstants.background, + ), + onPressed: () { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Main icon button pressed')), + ); + }, + ), + const SizedBox(width: 16), + CustomIconButton.danger( + icon: const HeroIcon( + HeroIcons.trash, + color: ColorConstants.background, + ), + onPressed: () { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Danger icon button pressed')), + ); + }, + ), + const SizedBox(width: 16), + CustomIconButton.secondary( + icon: const HeroIcon( + HeroIcons.pencil, + color: ColorConstants.tertiary, + ), + onPressed: () { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Secondary icon button pressed')), + ); + }, + ), + ], + ), + ], + ), + ), + + // List Item Template Section + const Divider(height: 40), + + sectionHeader( + "8. List Item Template", + "Base component for creating consistent list items with customizable content", + ), + + Container( + margin: const EdgeInsets.symmetric(vertical: 20), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.grey.shade100, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + "Basic List Item Template:", + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + ListItemTemplate( + title: "Template Item", + subtitle: "Customizable list item template", + icon: const HeroIcon( + HeroIcons.documentText, + color: ColorConstants.tertiary, + ), + onTap: () { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Template item tapped')), + ); + }, + ), + const SizedBox(height: 16), + const Text( + "With Custom Trailing Widget:", + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + ListItemTemplate( + title: "Custom Trailing", + subtitle: "With a badge indicator", + icon: const HeroIcon( + HeroIcons.bell, + color: ColorConstants.tertiary, + ), + trailing: Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: ColorConstants.main, + borderRadius: BorderRadius.circular(12), + ), + child: const Text( + "New", + style: TextStyle( + color: ColorConstants.background, + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + ), + onTap: () { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Custom trailing item tapped')), + ); + }, + ), + ], + ), + ), + + // Toggle List Item Section + const Divider(height: 40), + + sectionHeader( + "9. Toggle List Item", + "Toggleable list items with dynamic icons for selection state", + ), + + Container( + margin: const EdgeInsets.symmetric(vertical: 20), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.grey.shade100, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + "Unselected Item:", + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + ToggleListItem( + title: "Add to Group", + subtitle: "Tap to add this member", + icon: const HeroIcon( + HeroIcons.userGroup, + color: ColorConstants.tertiary, + ), + selected: false, + onTap: () { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Toggle item tapped')), + ); + }, + ), + const SizedBox(height: 16), + const Text( + "Selected Item:", + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + ToggleListItem( + title: "Member Added", + subtitle: "This member is already in the group", + icon: const HeroIcon( + HeroIcons.userGroup, + color: ColorConstants.tertiary, + ), + selected: true, + onTap: () { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Selected toggle item tapped')), + ); + }, + ), + ], + ), + ), + + // Text Entry Section + const Divider(height: 40), + + sectionHeader( + "10. Text Entry", + "Form input fields with validation support and customizable styling", + ), + + Container( + margin: const EdgeInsets.symmetric(vertical: 20), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.grey.shade100, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + "Basic Text Entry:", + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + TextEntry( + label: "Name", + controller: TextEditingController(), + onChanged: (value) { + // Handle text change + }, + ), + const SizedBox(height: 16), + const Text( + "Number Entry:", + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + TextEntry( + label: "Amount", + controller: TextEditingController(), + isDouble: true, + keyboardType: TextInputType.number, + suffix: "€", + ), + const SizedBox(height: 16), + const Text( + "Multiline Entry:", + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + TextEntry( + label: "Description", + controller: TextEditingController(), + minLines: 3, + maxLines: 5, + keyboardType: TextInputType.multiline, + ), + ], + ), + ), + + // Specialized Entry Fields Section + const Divider(height: 40), + + sectionHeader( + "11. Specialized Entry Fields", + "Pre-configured entry fields for dates and images", + ), + + Container( + margin: const EdgeInsets.symmetric(vertical: 20), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.grey.shade100, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + "Date Entry:", + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + DateEntry( + title: "Event Date", + subtitle: "Select a date for your event", + onTap: () { + showDatePicker( + context: context, + initialDate: DateTime.now(), + firstDate: DateTime.now(), + lastDate: DateTime.now().add(const Duration(days: 365)), + ); + }, + ), + const SizedBox(height: 16), + const Text( + "Image Entry:", + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + ImageEntry( + title: "Profile Picture", + subtitle: "Tap to select an image", + onTap: () { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Image entry tapped')), + ); + }, + ), + ], + ), + ), + + const SizedBox(height: 40), ], ), ), From cc7ef302ee45a6017ae31e1f51bc11111774119c Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sat, 28 Jun 2025 00:28:06 +0200 Subject: [PATCH 012/473] lint: applying linter --- lib/tools/ui/styleguide/image_entry.dart | 5 +- lib/tools/ui/styleguide/styleguide_page.dart | 77 +++++++++++++------- 2 files changed, 52 insertions(+), 30 deletions(-) diff --git a/lib/tools/ui/styleguide/image_entry.dart b/lib/tools/ui/styleguide/image_entry.dart index 9941a144d7..3f763c8863 100644 --- a/lib/tools/ui/styleguide/image_entry.dart +++ b/lib/tools/ui/styleguide/image_entry.dart @@ -15,10 +15,7 @@ class ImageEntry extends StatelessWidget { onTap: onTap, title: title, subtitle: subtitle, - trailing: const HeroIcon( - HeroIcons.phone, - color: ColorConstants.tertiary, - ), + trailing: const HeroIcon(HeroIcons.phone, color: ColorConstants.tertiary), ); } } diff --git a/lib/tools/ui/styleguide/styleguide_page.dart b/lib/tools/ui/styleguide/styleguide_page.dart index d97e69c06d..0a697c0a2e 100644 --- a/lib/tools/ui/styleguide/styleguide_page.dart +++ b/lib/tools/ui/styleguide/styleguide_page.dart @@ -976,15 +976,15 @@ class StyleGuidePage extends HookConsumerWidget { ), const SizedBox(height: 40), - + // Icon Button Section const Divider(height: 40), - + sectionHeader( "7. Icon Buttons", "Icon-only buttons in various styles for compact UI elements", ), - + Container( margin: const EdgeInsets.symmetric(vertical: 20), padding: const EdgeInsets.all(16), @@ -1010,7 +1010,9 @@ class StyleGuidePage extends HookConsumerWidget { ), onPressed: () { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Main icon button pressed')), + const SnackBar( + content: Text('Main icon button pressed'), + ), ); }, ), @@ -1022,7 +1024,11 @@ class StyleGuidePage extends HookConsumerWidget { ), onPressed: () { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Danger icon button pressed')), + const SnackBar( + content: Text( + 'Danger icon button pressed', + ), + ), ); }, ), @@ -1034,7 +1040,11 @@ class StyleGuidePage extends HookConsumerWidget { ), onPressed: () { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Secondary icon button pressed')), + const SnackBar( + content: Text( + 'Secondary icon button pressed', + ), + ), ); }, ), @@ -1043,15 +1053,15 @@ class StyleGuidePage extends HookConsumerWidget { ], ), ), - + // List Item Template Section const Divider(height: 40), - + sectionHeader( "8. List Item Template", "Base component for creating consistent list items with customizable content", ), - + Container( margin: const EdgeInsets.symmetric(vertical: 20), padding: const EdgeInsets.all(16), @@ -1076,7 +1086,9 @@ class StyleGuidePage extends HookConsumerWidget { ), onTap: () { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Template item tapped')), + const SnackBar( + content: Text('Template item tapped'), + ), ); }, ), @@ -1094,7 +1106,10 @@ class StyleGuidePage extends HookConsumerWidget { color: ColorConstants.tertiary, ), trailing: Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), decoration: BoxDecoration( color: ColorConstants.main, borderRadius: BorderRadius.circular(12), @@ -1110,22 +1125,24 @@ class StyleGuidePage extends HookConsumerWidget { ), onTap: () { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Custom trailing item tapped')), + const SnackBar( + content: Text('Custom trailing item tapped'), + ), ); }, ), ], ), ), - + // Toggle List Item Section const Divider(height: 40), - + sectionHeader( "9. Toggle List Item", "Toggleable list items with dynamic icons for selection state", ), - + Container( margin: const EdgeInsets.symmetric(vertical: 20), padding: const EdgeInsets.all(16), @@ -1151,7 +1168,9 @@ class StyleGuidePage extends HookConsumerWidget { selected: false, onTap: () { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Toggle item tapped')), + const SnackBar( + content: Text('Toggle item tapped'), + ), ); }, ), @@ -1171,22 +1190,24 @@ class StyleGuidePage extends HookConsumerWidget { selected: true, onTap: () { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Selected toggle item tapped')), + const SnackBar( + content: Text('Selected toggle item tapped'), + ), ); }, ), ], ), ), - + // Text Entry Section const Divider(height: 40), - + sectionHeader( "10. Text Entry", "Form input fields with validation support and customizable styling", ), - + Container( margin: const EdgeInsets.symmetric(vertical: 20), padding: const EdgeInsets.all(16), @@ -1238,15 +1259,15 @@ class StyleGuidePage extends HookConsumerWidget { ], ), ), - + // Specialized Entry Fields Section const Divider(height: 40), - + sectionHeader( "11. Specialized Entry Fields", "Pre-configured entry fields for dates and images", ), - + Container( margin: const EdgeInsets.symmetric(vertical: 20), padding: const EdgeInsets.all(16), @@ -1270,7 +1291,9 @@ class StyleGuidePage extends HookConsumerWidget { context: context, initialDate: DateTime.now(), firstDate: DateTime.now(), - lastDate: DateTime.now().add(const Duration(days: 365)), + lastDate: DateTime.now().add( + const Duration(days: 365), + ), ); }, ), @@ -1285,14 +1308,16 @@ class StyleGuidePage extends HookConsumerWidget { subtitle: "Tap to select an image", onTap: () { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Image entry tapped')), + const SnackBar( + content: Text('Image entry tapped'), + ), ); }, ), ], ), ), - + const SizedBox(height: 40), ], ), From a25d68231ff1de8bceba3476927464fbaba86eda Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Wed, 2 Jul 2025 18:18:37 +0200 Subject: [PATCH 013/473] fix: searchbar optional filter --- lib/tools/ui/styleguide/searchbar.dart | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/lib/tools/ui/styleguide/searchbar.dart b/lib/tools/ui/styleguide/searchbar.dart index 8b2d218a28..0e5a402c9b 100644 --- a/lib/tools/ui/styleguide/searchbar.dart +++ b/lib/tools/ui/styleguide/searchbar.dart @@ -5,13 +5,13 @@ import 'package:titan/tools/constants.dart'; class CustomSearchBar extends HookWidget { final String hintText; - final Function() onFilter; + final Function()? onFilter; final Function(String) onSearch; final bool autofocus; const CustomSearchBar({ super.key, this.hintText = 'Rechercher', - required this.onFilter, + this.onFilter, required this.onSearch, this.autofocus = false, }); @@ -65,15 +65,16 @@ class CustomSearchBar extends HookWidget { }, splashRadius: 20, ), - IconButton( - icon: HeroIcon( - HeroIcons.adjustmentsHorizontal, - color: ColorConstants.tertiary, - size: 20, + if (onFilter != null) + IconButton( + icon: HeroIcon( + HeroIcons.adjustmentsHorizontal, + color: ColorConstants.tertiary, + size: 20, + ), + onPressed: onFilter, + splashRadius: 20, ), - onPressed: onFilter, - splashRadius: 20, - ), ], ), ), From 025a62b23df5eec60aa2ce005756c1657bad5bb7 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sat, 28 Jun 2025 00:28:40 +0200 Subject: [PATCH 014/473] refacto: removing drawer --- lib/admin/router.dart | 10 + lib/admin/ui/admin.dart | 29 +- lib/advert/router.dart | 3 +- lib/advert/ui/components/advert_card.dart | 2 +- lib/advert/ui/pages/advert.dart | 26 +- lib/amap/router.dart | 3 +- lib/amap/ui/amap.dart | 29 +- .../list_products_page/list_products.dart | 2 +- lib/booking/router.dart | 3 +- lib/booking/ui/booking.dart | 16 +- lib/booking/ui/calendar/calendar.dart | 2 +- lib/centralisation/router.dart | 3 +- lib/centralisation/ui/centralisation.dart | 21 +- lib/cinema/router.dart | 3 +- lib/cinema/ui/cinema.dart | 24 +- lib/cinema/ui/pages/main_page/main_page.dart | 2 +- .../ui/pages/main_page/session_card.dart | 2 +- lib/drawer/class/top_bar_callback.dart | 9 - .../providers/already_displayed_popup.dart | 14 - lib/drawer/providers/animation_provider.dart | 37 -- lib/drawer/providers/swipe_provider.dart | 66 --- .../providers/top_bar_callback_provider.dart | 18 - lib/drawer/ui/bottom_bar.dart | 52 --- lib/drawer/ui/custom_drawer.dart | 61 --- lib/drawer/ui/drawer_template.dart | 136 ------ lib/drawer/ui/drawer_top_bar.dart | 279 ------------ lib/drawer/ui/email_change_popup.dart | 414 ------------------ lib/drawer/ui/fake_page.dart | 27 -- lib/drawer/ui/list_module.dart | 29 -- lib/drawer/ui/module.dart | 78 ---- lib/drawer/ui/notification_badge.dart | 29 -- lib/event/router.dart | 3 +- lib/event/ui/event.dart | 13 +- lib/flappybird/router.dart | 3 +- lib/flappybird/ui/flappybird_template.dart | 47 +- lib/home/router.dart | 3 +- lib/home/ui/home.dart | 108 ++--- lib/loan/router.dart | 3 +- lib/loan/ui/loan.dart | 30 +- lib/login/router.dart | 2 +- lib/main.dart | 80 ++-- lib/{drawer => navigation}/class/module.dart | 4 + .../providers/display_quit_popup.dart | 0 .../providers/is_web_format_provider.dart | 0 .../providers/modules_provider.dart | 2 +- .../providers/should_setup_provider.dart | 0 .../tools/constants.dart | 0 lib/navigation/ui/all_module_page.dart | 49 +++ lib/navigation/ui/drawer_template.dart | 105 +++++ .../ui/quit_dialog.dart | 4 +- lib/paiement/router.dart | 3 +- lib/paiement/ui/paiement.dart | 15 +- lib/ph/router.dart | 3 +- lib/ph/ui/pages/ph.dart | 12 +- lib/phonebook/router.dart | 3 +- lib/phonebook/ui/phonebook.dart | 35 +- lib/purchases/router.dart | 3 +- lib/purchases/ui/purchases.dart | 15 +- lib/raffle/router.dart | 3 +- lib/raffle/ui/raffle.dart | 15 +- lib/recommendation/router.dart | 3 +- .../ui/widgets/recommendation_template.dart | 16 +- lib/router.dart | 11 + lib/seed-library/router.dart | 3 +- lib/seed-library/ui/seed_library.dart | 31 +- .../providers/module_list_provider.dart | 29 +- lib/settings/router.dart | 12 + lib/settings/ui/settings.dart | 19 +- lib/tools/constants.dart | 1 + .../middlewares/authenticated_middleware.dart | 1 + .../providers/should_notify_provider.dart | 11 - lib/tools/ui/layouts/app_template.dart | 2 +- .../ui/styleguide/list_item_template.dart | 6 +- lib/tools/ui/styleguide/navbar.dart | 3 +- lib/tools/ui/styleguide/router.dart | 3 +- lib/tools/ui/styleguide/searchbar.dart | 20 +- lib/tools/ui/styleguide/styleguide_page.dart | 3 - lib/tools/ui/widgets/calendar.dart | 2 +- lib/tools/ui/widgets/top_bar.dart | 93 ---- lib/vote/router.dart | 3 +- lib/vote/ui/vote.dart | 12 +- test/drawer/drawer_test.dart | 32 -- test/drawer/swipe_provider_test.dart | 41 -- 83 files changed, 360 insertions(+), 1989 deletions(-) delete mode 100644 lib/drawer/class/top_bar_callback.dart delete mode 100644 lib/drawer/providers/already_displayed_popup.dart delete mode 100644 lib/drawer/providers/animation_provider.dart delete mode 100644 lib/drawer/providers/swipe_provider.dart delete mode 100644 lib/drawer/providers/top_bar_callback_provider.dart delete mode 100644 lib/drawer/ui/bottom_bar.dart delete mode 100644 lib/drawer/ui/custom_drawer.dart delete mode 100644 lib/drawer/ui/drawer_template.dart delete mode 100644 lib/drawer/ui/drawer_top_bar.dart delete mode 100644 lib/drawer/ui/email_change_popup.dart delete mode 100644 lib/drawer/ui/fake_page.dart delete mode 100644 lib/drawer/ui/list_module.dart delete mode 100644 lib/drawer/ui/module.dart delete mode 100644 lib/drawer/ui/notification_badge.dart rename lib/{drawer => navigation}/class/module.dart (89%) rename lib/{drawer => navigation}/providers/display_quit_popup.dart (100%) rename lib/{drawer => navigation}/providers/is_web_format_provider.dart (100%) rename lib/{drawer => navigation}/providers/modules_provider.dart (92%) rename lib/{drawer => navigation}/providers/should_setup_provider.dart (100%) rename lib/{drawer => navigation}/tools/constants.dart (100%) create mode 100644 lib/navigation/ui/all_module_page.dart create mode 100644 lib/navigation/ui/drawer_template.dart rename lib/{drawer => navigation}/ui/quit_dialog.dart (93%) delete mode 100644 lib/tools/providers/should_notify_provider.dart delete mode 100644 lib/tools/ui/widgets/top_bar.dart delete mode 100644 test/drawer/drawer_test.dart delete mode 100644 test/drawer/swipe_provider_test.dart diff --git a/lib/admin/router.dart b/lib/admin/router.dart index b98e20e3e0..b3c53169e5 100644 --- a/lib/admin/router.dart +++ b/lib/admin/router.dart @@ -1,4 +1,6 @@ +import 'package:either_dart/either.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:heroicons/heroicons.dart'; import 'package:titan/admin/providers/is_admin_provider.dart'; import 'package:titan/admin/ui/pages/groups/add_group_page/add_group_page.dart' deferred as add_group_page; @@ -28,6 +30,7 @@ import 'package:titan/admin/ui/pages/add_edit_structure_page/add_edit_structure_ deferred as add_edit_structure_page; import 'package:titan/admin/ui/pages/main_page/main_page.dart' deferred as main_page; +import 'package:titan/navigation/class/module.dart'; import 'package:titan/tools/middlewares/admin_middleware.dart'; import 'package:titan/tools/middlewares/authenticated_middleware.dart'; import 'package:titan/tools/middlewares/deferred_middleware.dart'; @@ -50,6 +53,13 @@ class AdminRouter { static const String detailAssociationMembership = '/detail_association_membership'; static const String addEditMember = '/add_edit_member'; + static final Module module = Module( + name: "Administration", + description: "Gérer les groupes, écoles et structures", + icon: const Left(HeroIcons.users), + root: AdminRouter.root, + selected: false, + ); AdminRouter(this.ref); QRoute route() => QRoute( diff --git a/lib/admin/ui/admin.dart b/lib/admin/ui/admin.dart index 4d40551181..bd4ce6216c 100644 --- a/lib/admin/ui/admin.dart +++ b/lib/admin/ui/admin.dart @@ -1,10 +1,5 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/router.dart'; -import 'package:titan/admin/tools/constants.dart'; -import 'package:titan/tools/token_expire_wrapper.dart'; -import 'package:titan/tools/ui/widgets/top_bar.dart'; -import 'package:titan/user/providers/user_provider.dart'; class AdminTemplate extends HookConsumerWidget { final Widget child; @@ -12,28 +7,6 @@ class AdminTemplate extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final meNotifier = ref.watch(asyncUserProvider.notifier); - return Scaffold( - body: Container( - decoration: const BoxDecoration(color: Colors.white), - child: SafeArea( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - TopBar( - title: AdminTextConstants.administration, - root: AdminRouter.root, - onMenu: () { - tokenExpireWrapper(ref, () async { - await meNotifier.loadMe(); - }); - }, - ), - Expanded(child: child), - ], - ), - ), - ), - ); + return child; } } diff --git a/lib/advert/router.dart b/lib/advert/router.dart index 769f6ee5ee..5d4df3e606 100644 --- a/lib/advert/router.dart +++ b/lib/advert/router.dart @@ -13,7 +13,7 @@ import 'package:titan/advert/ui/pages/form_page/add_rem_announcer_page.dart' deferred as add_rem_announcer_page; import 'package:titan/advert/ui/pages/main_page/main_page.dart' deferred as main_page; -import 'package:titan/drawer/class/module.dart'; +import 'package:titan/navigation/class/module.dart'; import 'package:titan/tools/middlewares/admin_middleware.dart'; import 'package:titan/tools/middlewares/authenticated_middleware.dart'; import 'package:titan/tools/middlewares/deferred_middleware.dart'; @@ -28,6 +28,7 @@ class AdvertRouter { static const String detail = '/detail'; static final Module module = Module( name: "Annonce", + description: "Gérer les annonces et les annonceurs", icon: const Left(HeroIcons.megaphone), root: AdvertRouter.root, selected: false, diff --git a/lib/advert/ui/components/advert_card.dart b/lib/advert/ui/components/advert_card.dart index dfbce33dfc..b21911111f 100644 --- a/lib/advert/ui/components/advert_card.dart +++ b/lib/advert/ui/components/advert_card.dart @@ -8,7 +8,7 @@ import 'package:titan/advert/providers/advert_poster_provider.dart'; import 'package:titan/advert/providers/advert_posters_provider.dart'; import 'package:titan/advert/tools/constants.dart'; import 'package:titan/cinema/tools/functions.dart'; -import 'package:titan/drawer/providers/is_web_format_provider.dart'; +import 'package:titan/navigation/providers/is_web_format_provider.dart'; import 'package:titan/tools/ui/builders/auto_loader_child.dart'; import 'package:titan/tools/ui/widgets/text_with_hyper_link.dart'; diff --git a/lib/advert/ui/pages/advert.dart b/lib/advert/ui/pages/advert.dart index f737e5dc07..74b7015722 100644 --- a/lib/advert/ui/pages/advert.dart +++ b/lib/advert/ui/pages/advert.dart @@ -1,9 +1,5 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/advert/providers/announcer_provider.dart'; -import 'package:titan/advert/router.dart'; -import 'package:titan/advert/tools/constants.dart'; -import 'package:titan/tools/ui/widgets/top_bar.dart'; class AdvertTemplate extends HookConsumerWidget { final Widget child; @@ -11,26 +7,6 @@ class AdvertTemplate extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final selectedAnnouncersNotifier = ref.read(announcerProvider.notifier); - return Scaffold( - body: Container( - color: Colors.white, - child: SafeArea( - child: Column( - children: [ - TopBar( - title: AdvertTextConstants.advert, - root: AdvertRouter.root, - onBack: () { - selectedAnnouncersNotifier.clearAnnouncer(); - }, - ), - const SizedBox(height: 30), - Expanded(child: child), - ], - ), - ), - ), - ); + return child; } } diff --git a/lib/amap/router.dart b/lib/amap/router.dart index 47cfe0129f..1fadf24bc2 100644 --- a/lib/amap/router.dart +++ b/lib/amap/router.dart @@ -18,7 +18,7 @@ import 'package:titan/amap/ui/pages/presentation_page/text.dart' deferred as presentation_page; import 'package:titan/amap/ui/pages/product_pages/add_edit_product.dart' deferred as add_edit_product; -import 'package:titan/drawer/class/module.dart'; +import 'package:titan/navigation/class/module.dart'; import 'package:titan/tools/middlewares/admin_middleware.dart'; import 'package:titan/tools/middlewares/authenticated_middleware.dart'; import 'package:titan/tools/middlewares/deferred_middleware.dart'; @@ -36,6 +36,7 @@ class AmapRouter { static const String addEditProduct = '/add_edit_product'; static final Module module = Module( name: "Amap", + description: "Gérer les livraisons et les produits", icon: const Left(HeroIcons.shoppingCart), root: AmapRouter.root, selected: false, diff --git a/lib/amap/ui/amap.dart b/lib/amap/ui/amap.dart index d2e8c6b9b9..8da5f5ca18 100644 --- a/lib/amap/ui/amap.dart +++ b/lib/amap/ui/amap.dart @@ -1,9 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:heroicons/heroicons.dart'; -import 'package:titan/amap/router.dart'; -import 'package:titan/amap/tools/constants.dart'; -import 'package:titan/tools/ui/widgets/top_bar.dart'; -import 'package:qlevar_router/qlevar_router.dart'; class AmapTemplate extends StatelessWidget { final Widget child; @@ -11,28 +6,6 @@ class AmapTemplate extends StatelessWidget { @override Widget build(BuildContext context) { - return SafeArea( - child: Column( - children: [ - TopBar( - title: AMAPTextConstants.amap, - root: AmapRouter.root, - rightIcon: QR.currentPath == AmapRouter.root - ? IconButton( - onPressed: () { - QR.to(AmapRouter.root + AmapRouter.presentation); - }, - icon: const HeroIcon( - HeroIcons.informationCircle, - color: Colors.black, - size: 40, - ), - ) - : null, - ), - Expanded(child: child), - ], - ), - ); + return child; } } diff --git a/lib/amap/ui/pages/list_products_page/list_products.dart b/lib/amap/ui/pages/list_products_page/list_products.dart index e2829189e6..4c8e9537a8 100644 --- a/lib/amap/ui/pages/list_products_page/list_products.dart +++ b/lib/amap/ui/pages/list_products_page/list_products.dart @@ -9,7 +9,7 @@ import 'package:titan/amap/providers/scroll_controller_provider.dart'; import 'package:titan/amap/tools/constants.dart'; import 'package:titan/amap/ui/pages/list_products_page/category_page.dart'; import 'package:titan/amap/ui/pages/list_products_page/web_page_navigation_button.dart'; -import 'package:titan/drawer/providers/is_web_format_provider.dart'; +import 'package:titan/navigation/providers/is_web_format_provider.dart'; class ListProducts extends HookConsumerWidget { const ListProducts({super.key}); diff --git a/lib/booking/router.dart b/lib/booking/router.dart index 27abc4f2ef..8315982e6c 100644 --- a/lib/booking/router.dart +++ b/lib/booking/router.dart @@ -17,7 +17,7 @@ import 'package:titan/booking/ui/pages/manager_page/manager_page.dart' deferred as manager_page; import 'package:titan/booking/ui/pages/admin_pages/add_edit_room_page.dart' deferred as add_edit_room_page; -import 'package:titan/drawer/class/module.dart'; +import 'package:titan/navigation/class/module.dart'; import 'package:titan/tools/middlewares/admin_middleware.dart'; import 'package:titan/tools/middlewares/authenticated_middleware.dart'; import 'package:titan/tools/middlewares/deferred_middleware.dart'; @@ -33,6 +33,7 @@ class BookingRouter { static const String room = '/room'; static final Module module = Module( name: "Réservation", + description: "Gérer les réservations, les salles et les managers", icon: const Left(HeroIcons.tableCells), root: BookingRouter.root, selected: false, diff --git a/lib/booking/ui/booking.dart b/lib/booking/ui/booking.dart index 1371781d90..47b7cc16f7 100644 --- a/lib/booking/ui/booking.dart +++ b/lib/booking/ui/booking.dart @@ -1,7 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:titan/booking/router.dart'; -import 'package:titan/booking/tools/constants.dart'; -import 'package:titan/tools/ui/widgets/top_bar.dart'; class BookingTemplate extends StatelessWidget { final Widget child; @@ -9,17 +6,6 @@ class BookingTemplate extends StatelessWidget { @override Widget build(BuildContext context) { - return SafeArea( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - const TopBar( - title: BookingTextConstants.booking, - root: BookingRouter.root, - ), - Expanded(child: child), - ], - ), - ); + return child; } } diff --git a/lib/booking/ui/calendar/calendar.dart b/lib/booking/ui/calendar/calendar.dart index 61d1559a65..ee15f535c5 100644 --- a/lib/booking/ui/calendar/calendar.dart +++ b/lib/booking/ui/calendar/calendar.dart @@ -6,7 +6,7 @@ import 'package:titan/booking/providers/confirmed_booking_list_provider.dart'; import 'package:titan/booking/providers/manager_confirmed_booking_list_provider.dart'; import 'package:titan/booking/ui/calendar/appointment_data_source.dart'; import 'package:titan/booking/ui/calendar/calendar_dialog.dart'; -import 'package:titan/drawer/providers/is_web_format_provider.dart'; +import 'package:titan/navigation/providers/is_web_format_provider.dart'; import 'package:titan/tools/constants.dart'; import 'package:syncfusion_flutter_calendar/calendar.dart'; diff --git a/lib/centralisation/router.dart b/lib/centralisation/router.dart index 160e528a33..ee9e165f11 100644 --- a/lib/centralisation/router.dart +++ b/lib/centralisation/router.dart @@ -4,7 +4,7 @@ import 'package:heroicons/heroicons.dart'; import 'package:titan/centralisation/tools/constants.dart'; import 'package:titan/centralisation/ui/pages/main_page.dart' deferred as main_page; -import 'package:titan/drawer/class/module.dart'; +import 'package:titan/navigation/class/module.dart'; import 'package:titan/tools/middlewares/authenticated_middleware.dart'; import 'package:titan/tools/middlewares/deferred_middleware.dart'; import 'package:qlevar_router/qlevar_router.dart'; @@ -13,6 +13,7 @@ class CentralisationRouter { final Ref ref; static const String root = '/centralisation'; static final Module module = Module( + description: "Gérer la centralisation des données", name: CentralisationTextConstants.centralisation, icon: const Left(HeroIcons.link), root: CentralisationRouter.root, diff --git a/lib/centralisation/ui/centralisation.dart b/lib/centralisation/ui/centralisation.dart index 3cba972886..142cb10643 100644 --- a/lib/centralisation/ui/centralisation.dart +++ b/lib/centralisation/ui/centralisation.dart @@ -1,7 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:titan/centralisation/router.dart'; -import 'package:titan/centralisation/tools/constants.dart'; -import 'package:titan/tools/ui/widgets/top_bar.dart'; class CentralisationTemplate extends StatelessWidget { final Widget child; @@ -9,22 +6,6 @@ class CentralisationTemplate extends StatelessWidget { @override Widget build(BuildContext context) { - return Scaffold( - body: Container( - color: Colors.white, - child: SafeArea( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - const TopBar( - title: CentralisationTextConstants.centralisation, - root: CentralisationRouter.root, - ), - Expanded(child: child), - ], - ), - ), - ), - ); + return child; } } diff --git a/lib/cinema/router.dart b/lib/cinema/router.dart index 01108fcdee..f92b307a92 100644 --- a/lib/cinema/router.dart +++ b/lib/cinema/router.dart @@ -10,7 +10,7 @@ import 'package:titan/cinema/ui/pages/main_page/main_page.dart' deferred as main_page; import 'package:titan/cinema/ui/pages/session_pages/add_edit_session.dart' deferred as add_edit_session_page; -import 'package:titan/drawer/class/module.dart'; +import 'package:titan/navigation/class/module.dart'; import 'package:titan/tools/middlewares/admin_middleware.dart'; import 'package:titan/tools/middlewares/authenticated_middleware.dart'; import 'package:titan/tools/middlewares/deferred_middleware.dart'; @@ -25,6 +25,7 @@ class CinemaRouter { static const String detail = '/detail'; static final Module module = Module( name: "Cinéma", + description: "Gérer les séances de cinéma", icon: const Left(HeroIcons.ticket), root: CinemaRouter.root, selected: false, diff --git a/lib/cinema/ui/cinema.dart b/lib/cinema/ui/cinema.dart index 6376ce34c6..d3cac64008 100644 --- a/lib/cinema/ui/cinema.dart +++ b/lib/cinema/ui/cinema.dart @@ -1,10 +1,5 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/cinema/providers/main_page_index_provider.dart'; -import 'package:titan/cinema/providers/scroll_provider.dart'; -import 'package:titan/cinema/router.dart'; -import 'package:titan/cinema/tools/constants.dart'; -import 'package:titan/tools/ui/widgets/top_bar.dart'; class CinemaTemplate extends HookConsumerWidget { final Widget child; @@ -12,23 +7,6 @@ class CinemaTemplate extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final initialPageNotifier = ref.watch(mainPageIndexProvider.notifier); - final scrollNotifier = ref.watch(scrollProvider.notifier); - return SafeArea( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - TopBar( - title: CinemaTextConstants.cinema, - root: CinemaRouter.root, - onMenu: () { - initialPageNotifier.reset(); - scrollNotifier.reset(); - }, - ), - Expanded(child: child), - ], - ), - ); + return child; } } diff --git a/lib/cinema/ui/pages/main_page/main_page.dart b/lib/cinema/ui/pages/main_page/main_page.dart index 1423fe525d..e0469e7680 100644 --- a/lib/cinema/ui/pages/main_page/main_page.dart +++ b/lib/cinema/ui/pages/main_page/main_page.dart @@ -12,7 +12,7 @@ import 'package:titan/cinema/router.dart'; import 'package:titan/cinema/tools/constants.dart'; import 'package:titan/cinema/ui/cinema.dart'; import 'package:titan/cinema/ui/pages/main_page/session_card.dart'; -import 'package:titan/drawer/providers/is_web_format_provider.dart'; +import 'package:titan/navigation/providers/is_web_format_provider.dart'; import 'package:titan/tools/ui/widgets/admin_button.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; diff --git a/lib/cinema/ui/pages/main_page/session_card.dart b/lib/cinema/ui/pages/main_page/session_card.dart index c1513a9ad0..4fb209e70a 100644 --- a/lib/cinema/ui/pages/main_page/session_card.dart +++ b/lib/cinema/ui/pages/main_page/session_card.dart @@ -8,7 +8,7 @@ import 'package:titan/cinema/providers/session_poster_map_provider.dart'; import 'package:titan/cinema/providers/session_poster_provider.dart'; import 'package:titan/cinema/tools/constants.dart'; import 'package:titan/cinema/tools/functions.dart'; -import 'package:titan/drawer/providers/is_web_format_provider.dart'; +import 'package:titan/navigation/providers/is_web_format_provider.dart'; import 'package:titan/tools/ui/builders/auto_loader_child.dart'; class SessionCard extends HookConsumerWidget { diff --git a/lib/drawer/class/top_bar_callback.dart b/lib/drawer/class/top_bar_callback.dart deleted file mode 100644 index 09f0a59bcf..0000000000 --- a/lib/drawer/class/top_bar_callback.dart +++ /dev/null @@ -1,9 +0,0 @@ -import 'package:flutter/material.dart'; - -class TopBarCallback { - final String moduleRoot; - final VoidCallback? onMenu; - final VoidCallback? onBack; - - TopBarCallback({this.onMenu, this.onBack, required this.moduleRoot}); -} diff --git a/lib/drawer/providers/already_displayed_popup.dart b/lib/drawer/providers/already_displayed_popup.dart deleted file mode 100644 index 67393b6838..0000000000 --- a/lib/drawer/providers/already_displayed_popup.dart +++ /dev/null @@ -1,14 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; - -class AlreadyDisplayedNotifier extends StateNotifier { - AlreadyDisplayedNotifier() : super(false); - - void setAlreadyDisplayed() { - state = true; - } -} - -final alreadyDisplayedProvider = - StateNotifierProvider((ref) { - return AlreadyDisplayedNotifier(); - }); diff --git a/lib/drawer/providers/animation_provider.dart b/lib/drawer/providers/animation_provider.dart deleted file mode 100644 index 41f766a28d..0000000000 --- a/lib/drawer/providers/animation_provider.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; - -class AnimationNotifier extends StateNotifier { - AnimationNotifier() : super(null); - - void setController(AnimationController controller) { - state = controller; - } - - void toggle() { - if (state == null) { - return; - } - if (state!.isCompleted) { - state!.reverse(); - } else { - state!.forward(); - } - } - - double get value { - if (state == null) { - return 0; - } - return state!.value; - } - - AnimationController? get animation { - return state; - } -} - -final animationProvider = - StateNotifierProvider((ref) { - return AnimationNotifier(); - }); diff --git a/lib/drawer/providers/swipe_provider.dart b/lib/drawer/providers/swipe_provider.dart deleted file mode 100644 index b4e79beba6..0000000000 --- a/lib/drawer/providers/swipe_provider.dart +++ /dev/null @@ -1,66 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; - -class SwipeControllerNotifier extends StateNotifier { - SwipeControllerNotifier(super.controller); - - static const double maxSlide = 255; - static const dragRightStartVal = 60; - static const dragLeftStartVal = maxSlide - 30; - static bool shouldDrag = false; - - void close() { - state.reverse(); - } - - void open() { - state.forward(); - } - - void toggle() { - if (state.isCompleted) { - close(); - } else { - open(); - } - } - - void onDragStart(DragStartDetails startDetails) { - bool isDraggingFromLeft = - state.isDismissed && startDetails.globalPosition.dx < dragRightStartVal; - bool isDraggingFromRight = - !state.isDismissed && startDetails.globalPosition.dx > dragLeftStartVal; - shouldDrag = isDraggingFromLeft || isDraggingFromRight; - } - - void onDragUpdate(DragUpdateDetails updateDetails) { - if (shouldDrag) { - double delta = updateDetails.primaryDelta! / maxSlide; - state.value += delta; - } - } - - void onDragEnd(DragEndDetails endDetails, double width) { - if (!state.isDismissed && !state.isCompleted) { - double minFlingVelocity = 365.0; - double dragVelocity = endDetails.velocity.pixelsPerSecond.dx.abs(); - if (dragVelocity >= minFlingVelocity) { - double visualVelocity = endDetails.velocity.pixelsPerSecond.dx / width; - state.fling(velocity: visualVelocity); - } else if (state.value < 0.5) { - close(); - } else { - open(); - } - } - } -} - -final swipeControllerProvider = - StateNotifierProvider.family< - SwipeControllerNotifier, - AnimationController, - AnimationController - >((ref, animationController) { - return SwipeControllerNotifier(animationController); - }); diff --git a/lib/drawer/providers/top_bar_callback_provider.dart b/lib/drawer/providers/top_bar_callback_provider.dart deleted file mode 100644 index a4f7644f6b..0000000000 --- a/lib/drawer/providers/top_bar_callback_provider.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/drawer/class/top_bar_callback.dart'; - -class AnimationNotifier extends StateNotifier { - AnimationNotifier() : super(TopBarCallback(moduleRoot: '')); - - void setCallBacks(TopBarCallback callbacks) { - if (state.moduleRoot == callbacks.moduleRoot) { - return; - } - state = callbacks; - } -} - -final topBarCallBackProvider = - StateNotifierProvider((ref) { - return AnimationNotifier(); - }); diff --git a/lib/drawer/ui/bottom_bar.dart b/lib/drawer/ui/bottom_bar.dart deleted file mode 100644 index 94d74e5942..0000000000 --- a/lib/drawer/ui/bottom_bar.dart +++ /dev/null @@ -1,52 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:heroicons/heroicons.dart'; -import 'package:titan/drawer/providers/display_quit_popup.dart'; -import 'package:titan/drawer/tools/constants.dart'; - -class BottomBar extends ConsumerWidget { - const BottomBar({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final displayQuitNotifier = ref.watch(displayQuitProvider.notifier); - return Column( - children: [ - SizedBox( - height: 30, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - GestureDetector( - behavior: HitTestBehavior.translucent, - onTap: () { - displayQuitNotifier.setDisplay(true); - }, - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const SizedBox(width: 25), - HeroIcon( - HeroIcons.arrowRightOnRectangle, - color: DrawerColorConstants.lightText, - size: 27, - ), - const SizedBox(width: 15), - Text( - DrawerTextConstants.logOut, - style: TextStyle( - color: DrawerColorConstants.lightText, - fontSize: 18, - ), - ), - ], - ), - ), - ], - ), - ), - const SizedBox(height: 30), - ], - ); - } -} diff --git a/lib/drawer/ui/custom_drawer.dart b/lib/drawer/ui/custom_drawer.dart deleted file mode 100644 index 6fd148196b..0000000000 --- a/lib/drawer/ui/custom_drawer.dart +++ /dev/null @@ -1,61 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/drawer/providers/is_web_format_provider.dart'; -import 'package:titan/drawer/tools/constants.dart'; -import 'package:titan/drawer/ui/bottom_bar.dart'; -import 'package:titan/drawer/ui/fake_page.dart'; -import 'package:titan/drawer/ui/list_module.dart'; -import 'package:titan/drawer/ui/drawer_top_bar.dart'; - -class CustomDrawer extends HookConsumerWidget { - const CustomDrawer({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final isWebFormat = ref.watch(isWebFormatProvider); - return Container( - decoration: const BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [ - DrawerColorConstants.lightBlue, - DrawerColorConstants.darkBlue, - ], - ), - ), - child: SafeArea( - child: Stack( - children: [ - const Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [DrawerTopBar(), BottomBar()], - ), - Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Expanded( - child: SizedBox( - width: 200, - height: MediaQuery.of(context).size.height * 4.4 / 10, - child: const ListModule(), - ), - ), - isWebFormat - ? Container( - width: MediaQuery.of(context).size.width - 220, - ) - : const FakePage(), - ], - ), - ], - ), - ], - ), - ), - ); - } -} diff --git a/lib/drawer/ui/drawer_template.dart b/lib/drawer/ui/drawer_template.dart deleted file mode 100644 index eaa11ebe05..0000000000 --- a/lib/drawer/ui/drawer_template.dart +++ /dev/null @@ -1,136 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:flutter/foundation.dart' show kIsWeb; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/auth/providers/openid_provider.dart'; -import 'package:titan/drawer/providers/animation_provider.dart'; -import 'package:titan/drawer/providers/display_quit_popup.dart'; -import 'package:titan/drawer/providers/is_web_format_provider.dart'; -import 'package:titan/drawer/providers/should_setup_provider.dart'; -import 'package:titan/drawer/providers/swipe_provider.dart'; -import 'package:titan/drawer/ui/custom_drawer.dart'; -import 'package:titan/service/tools/setup.dart'; -import 'package:titan/drawer/providers/already_displayed_popup.dart'; -import 'package:titan/drawer/ui/quit_dialog.dart'; -import 'package:titan/drawer/ui/email_change_popup.dart'; -import 'package:titan/tools/providers/should_notify_provider.dart'; -import 'package:titan/user/providers/user_provider.dart'; -import 'package:qlevar_router/qlevar_router.dart'; - -class DrawerTemplate extends HookConsumerWidget { - static Duration duration = const Duration(milliseconds: 200); - static const double maxSlide = 255; - static const dragRightStartVal = 60; - static const dragLeftStartVal = maxSlide - 20; - static bool shouldDrag = false; - final Widget child; - - const DrawerTemplate({super.key, required this.child}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - // We are logged in, so we can set up the notification - final user = ref.watch(userProvider); - final animationController = useAnimationController( - duration: duration, - initialValue: 1, - ); - final animationNotifier = ref.read(animationProvider.notifier); - final controller = ref.watch(swipeControllerProvider(animationController)); - final controllerNotifier = ref.watch( - swipeControllerProvider(animationController).notifier, - ); - final isWebFormat = ref.watch(isWebFormatProvider); - final alreadyDisplayed = ref.watch(alreadyDisplayedProvider); - final displayQuit = ref.watch(displayQuitProvider); - final shouldNotify = ref.watch(shouldNotifyProvider); - final isLoggedIn = ref.watch(isLoggedInProvider); - final shouldSetup = ref.watch(shouldSetupProvider); - final shouldSetupNotifier = ref.read(shouldSetupProvider.notifier); - if (isWebFormat) { - controllerNotifier.close(); - } - - Future(() { - animationNotifier.setController(animationController); - if (!kIsWeb && user.id != "" && shouldSetup) { - setUpNotification(ref); - shouldSetupNotifier.setShouldSetup(); - } - }); - - return Scaffold( - body: Stack( - children: [ - GestureDetector( - onHorizontalDragStart: controllerNotifier.onDragStart, - onHorizontalDragUpdate: controllerNotifier.onDragUpdate, - onHorizontalDragEnd: (details) => controllerNotifier.onDragEnd( - details, - MediaQuery.of(context).size.width, - ), - onTap: () {}, - child: AnimatedBuilder( - animation: controller, - builder: (BuildContext context, _) { - double animationVal = controller.value; - double translateVal = animationVal * maxSlide; - double scaleVal = 1 - (isWebFormat ? 0 : (animationVal * 0.3)); - double cornerVal = isWebFormat ? 0 : 30.0 * animationVal; - return Stack( - children: [ - const CustomDrawer(), - Transform( - alignment: Alignment.centerLeft, - transform: Matrix4.identity() - ..translate(translateVal) - ..scale(scaleVal), - child: GestureDetector( - onTap: () { - if (controller.isCompleted) { - controllerNotifier.close(); - } - }, - child: ClipRRect( - borderRadius: BorderRadius.circular(cornerVal), - child: Stack( - children: [ - Container( - color: Colors.white, - child: IgnorePointer( - ignoring: controller.isCompleted, - child: child, - ), - ), - MouseRegion( - cursor: SystemMouseCursors.click, - onEnter: (event) { - controllerNotifier.toggle(); - }, - child: Container( - color: Colors.transparent, - width: 20, - height: double.infinity, - ), - ), - ], - ), - ), - ), - ), - ], - ); - }, - ), - ), - if (isLoggedIn && - shouldNotify && - QR.context != null && - !alreadyDisplayed) - const EmailChangeDialog(), - if (displayQuit) const QuitDialog(), - ], - ), - ); - } -} diff --git a/lib/drawer/ui/drawer_top_bar.dart b/lib/drawer/ui/drawer_top_bar.dart deleted file mode 100644 index 2c9447b638..0000000000 --- a/lib/drawer/ui/drawer_top_bar.dart +++ /dev/null @@ -1,279 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:heroicons/heroicons.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/providers/is_admin_provider.dart'; -import 'package:titan/admin/router.dart'; -import 'package:titan/auth/providers/is_connected_provider.dart'; -import 'package:titan/drawer/providers/animation_provider.dart'; -import 'package:titan/drawer/providers/swipe_provider.dart'; -import 'package:titan/drawer/tools/constants.dart'; -import 'package:titan/home/providers/scrolled_provider.dart'; -import 'package:titan/settings/router.dart'; -import 'package:titan/tools/constants.dart'; -import 'package:titan/tools/functions.dart'; -import 'package:titan/tools/providers/path_forwarding_provider.dart'; -import 'package:titan/tools/ui/builders/async_child.dart'; -import 'package:titan/user/providers/user_provider.dart'; -import 'package:titan/user/providers/profile_picture_provider.dart'; -import 'package:qlevar_router/qlevar_router.dart'; - -class DrawerTopBar extends HookConsumerWidget { - const DrawerTopBar({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final pathForwardingNotifier = ref.read(pathForwardingProvider.notifier); - final pathForwarding = ref.watch(pathForwardingProvider); - final user = ref.watch(userProvider); - final profilePicture = ref.watch(profilePictureProvider); - final hasScrolled = ref.watch(hasScrolledProvider.notifier); - final isAdmin = ref.watch(isAdminProvider); - final isConnected = ref.watch(isConnectedProvider); - final animation = ref.watch(animationProvider); - final dropDownAnimation = useAnimationController( - duration: const Duration(milliseconds: 250), - initialValue: 0.0, - ); - - void onBack(String path) { - if (animation != null) { - final controllerNotifier = ref.watch( - swipeControllerProvider(animation).notifier, - ); - controllerNotifier.toggle(); - } - QR.to(path); - pathForwardingNotifier.forward(path); - hasScrolled.setHasScrolled(false); - } - - return Column( - children: [ - Container(height: 20), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - Container(width: 25), - GestureDetector( - onTap: () { - if (isAdmin) { - if (dropDownAnimation.isDismissed) { - dropDownAnimation.forward(); - } else { - dropDownAnimation.reverse(); - } - } else { - onBack(SettingsRouter.root); - } - }, - behavior: HitTestBehavior.opaque, - child: Row( - children: [ - AsyncChild( - value: profilePicture, - builder: (context, file) => Row( - children: [ - Stack( - children: [ - Container( - decoration: BoxDecoration( - shape: BoxShape.circle, - boxShadow: [ - BoxShadow( - color: Colors.black.withValues( - alpha: 0.1, - ), - spreadRadius: 5, - blurRadius: 10, - offset: const Offset(0, 3), - ), - ], - ), - child: CircleAvatar( - radius: 25, - backgroundImage: file.isEmpty - ? AssetImage(getTitanLogo()) - : Image.memory(file).image, - ), - ), - if (isAdmin) - Positioned( - bottom: 0, - right: 0, - child: GestureDetector( - onTap: () async {}, - child: Container( - height: 18, - width: 18, - padding: const EdgeInsets.all(3), - decoration: BoxDecoration( - shape: BoxShape.circle, - gradient: const LinearGradient( - colors: [ - ColorConstants.gradient1, - ColorConstants.gradient2, - ], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ), - boxShadow: [ - BoxShadow( - color: ColorConstants.gradient2 - .withValues(alpha: 0.3), - spreadRadius: 1, - blurRadius: 2, - offset: const Offset(1, 2), - ), - ], - ), - child: const HeroIcon( - HeroIcons.bolt, - color: Colors.white, - ), - ), - ), - ), - ], - ), - const SizedBox(width: 15), - ], - ), - ), - Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - SizedBox( - width: 200, - child: Text( - user.nickname ?? user.firstname, - style: TextStyle( - color: Colors.grey.shade100, - fontSize: 20, - fontWeight: FontWeight.bold, - ), - ), - ), - Container(height: 3), - SizedBox( - width: 200, - child: Text( - user.nickname != null - ? "${user.firstname} ${user.name}" - : user.name, - style: TextStyle( - color: Colors.grey.shade100, - fontSize: 15, - ), - ), - ), - ], - ), - ], - ), - ), - ], - ), - if (!isConnected) - Container( - margin: const EdgeInsets.only(right: 20), - child: const HeroIcon( - HeroIcons.signalSlash, - color: Colors.white, - size: 40, - ), - ), - ], - ), - AnimatedBuilder( - builder: (context, child) { - return Opacity( - opacity: dropDownAnimation.value, - child: Padding( - padding: const EdgeInsets.only(left: 25, top: 15), - child: Column( - children: [ - Transform.translate( - offset: Offset(0, -10 * (1 - dropDownAnimation.value)), - child: GestureDetector( - behavior: HitTestBehavior.translucent, - onTap: () => onBack(SettingsRouter.root), - child: Row( - children: [ - HeroIcon( - HeroIcons.cog, - color: - pathForwarding.path.startsWith( - SettingsRouter.root, - ) - ? DrawerColorConstants.selectedText - : DrawerColorConstants.lightText, - size: 25, - ), - Container(width: 15), - Text( - DrawerTextConstants.settings, - style: TextStyle( - color: - pathForwarding.path.startsWith( - SettingsRouter.root, - ) - ? DrawerColorConstants.selectedText - : DrawerColorConstants.lightText, - fontSize: 15, - ), - ), - ], - ), - ), - ), - const SizedBox(height: 10), - if (isAdmin) - Transform.translate( - offset: Offset(0, -15 * (1 - dropDownAnimation.value)), - child: GestureDetector( - behavior: HitTestBehavior.translucent, - onTap: () => onBack(AdminRouter.root), - child: Row( - children: [ - HeroIcon( - HeroIcons.userGroup, - color: - pathForwarding.path.startsWith( - AdminRouter.root, - ) - ? DrawerColorConstants.selectedText - : DrawerColorConstants.lightText, - size: 25, - ), - Container(width: 15), - Text( - DrawerTextConstants.admin, - style: TextStyle( - color: - pathForwarding.path.startsWith( - AdminRouter.root, - ) - ? DrawerColorConstants.selectedText - : DrawerColorConstants.lightText, - fontSize: 15, - ), - ), - Container(width: 25), - ], - ), - ), - ), - ], - ), - ), - ); - }, - animation: dropDownAnimation, - ), - ], - ); - } -} diff --git a/lib/drawer/ui/email_change_popup.dart b/lib/drawer/ui/email_change_popup.dart deleted file mode 100644 index 2344a06dbe..0000000000 --- a/lib/drawer/ui/email_change_popup.dart +++ /dev/null @@ -1,414 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:heroicons/heroicons.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/drawer/providers/already_displayed_popup.dart'; -import 'package:titan/loan/tools/constants.dart'; -import 'package:titan/tools/functions.dart'; -import 'package:titan/tools/providers/should_notify_provider.dart'; -import 'package:titan/tools/ui/builders/waiting_button.dart'; -import 'package:titan/user/providers/user_provider.dart'; - -class Consts { - Consts._(); - - static const double padding = 20.0; - static const double avatarRadius = 50.0; - static const Color greenGradient1 = Color(0xff79a400); - static const Color greenGradient2 = Color(0xff387200); - static const Color redGradient1 = Color(0xFF9E131F); - static const Color redGradient2 = Color(0xFF590512); - static const String description = - "L'administration a décidé de changer les adresses mails des étudiants.\nPour être sur de recevoir les mails en cas de perte du mot de passe, merci de renseigner la nouvelle (normalement elle est déjà préremplie 😉)."; - static const String descriptionMigration = - "Vous avez créé un compte avec une adresse qui n'est pas une adresse centralienne.\nPour pouvoir accéder à cette application, vous devez changer cette adresse (normalement elle est déjà préremplie, on vous laisse vérifier et valider 😉)."; -} - -class EmailChangeDialog extends HookConsumerWidget { - const EmailChangeDialog({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final user = ref.watch(userProvider); - final shouldBeUser = ref.watch(shouldNotifyProvider); - final userNotifier = ref.watch(asyncUserProvider.notifier); - final alreadyDisplayedNotifier = ref.watch( - alreadyDisplayedProvider.notifier, - ); - final newEmail = shouldBeUser - ? '${user.firstname.toLowerCase()}.${user.name.toLowerCase()}@etu.ec-lyon.fr' - : '${user.email.split('@')[0]}@etu.ec-lyon.fr'; - final emailController = useTextEditingController(text: newEmail); - final formKey = GlobalKey(); - final checkAnimationController = useAnimationController( - duration: const Duration(milliseconds: 500), - initialValue: 0, - ); - final checkAnimation = CurvedAnimation( - parent: checkAnimationController, - curve: Curves.bounceOut, - ); - final ValueNotifier currentState = useState( - AsyncError("", StackTrace.current), - ); - final displayForm = useState(true); - - useEffect(() { - if (shouldBeUser) { - emailController.text = newEmail; - } - return () {}; - }, [newEmail]); - - return GestureDetector( - onTap: alreadyDisplayedNotifier.setAlreadyDisplayed, - child: Container( - color: Colors.black54, - child: GestureDetector( - onTap: () {}, - child: Dialog( - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(20.0)), - ), - elevation: 0.0, - insetPadding: const EdgeInsets.all(20.0), - backgroundColor: Colors.transparent, - child: Stack( - clipBehavior: Clip.none, - children: [ - Container( - padding: const EdgeInsets.only( - top: Consts.avatarRadius + Consts.padding, - bottom: Consts.padding, - left: Consts.padding, - right: Consts.padding, - ), - margin: const EdgeInsets.only(top: Consts.avatarRadius), - decoration: BoxDecoration( - color: Colors.white, - shape: BoxShape.rectangle, - borderRadius: BorderRadius.circular(Consts.padding), - boxShadow: const [ - BoxShadow( - color: Colors.black26, - blurRadius: 10.0, - offset: Offset(0.0, 10.0), - ), - ], - ), - child: Column( - mainAxisSize: MainAxisSize.min, // To make the card compact - children: [ - const Text( - "Changer d'adresse mail", - style: TextStyle( - fontSize: 24.0, - fontWeight: FontWeight.w700, - ), - ), - const SizedBox(height: 12.0), - displayForm.value - ? Form( - key: formKey, - autovalidateMode: AutovalidateMode.always, - child: Column( - children: [ - Text( - shouldBeUser - ? Consts.descriptionMigration - : Consts.description, - textAlign: TextAlign.center, - style: const TextStyle(fontSize: 16.0), - ), - const SizedBox(height: 15.0), - TextFormField( - controller: emailController, - cursorColor: Colors.black, - decoration: const InputDecoration( - labelText: "Nouvelle adresse mail", - floatingLabelStyle: TextStyle( - color: Colors.black, - fontWeight: FontWeight.bold, - ), - focusedBorder: UnderlineInputBorder( - borderSide: BorderSide( - color: Colors.black, - width: 2.0, - ), - ), - ), - validator: (value) { - if (value == null) { - return LoanTextConstants.noValue; - } else if (value.isEmpty) { - return LoanTextConstants.noValue; - } else if (!isStudent(value)) { - return "Adresse mail invalide"; - } - return null; - }, - ), - const SizedBox(height: 30.0), - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - GestureDetector( - onTap: alreadyDisplayedNotifier - .setAlreadyDisplayed, - child: Container( - width: 100, - padding: const EdgeInsets.symmetric( - vertical: 16, - ), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular( - 10, - ), - gradient: const LinearGradient( - colors: [ - Colors.black87, - Colors.black, - ], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ), - boxShadow: [ - BoxShadow( - color: Colors.black.withValues( - alpha: 0.3, - ), - blurRadius: 2.0, - offset: const Offset(1.0, 2.0), - ), - ], - ), - child: const Center( - child: Text( - "Annuler", - style: TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, - ), - ), - ), - ), - ), - WaitingButton( - onTap: () async { - if (formKey.currentState! - .validate()) { - currentState.value = - const AsyncLoading(); - final result = await userNotifier - .askMailMigration( - emailController.text, - ); - if (result) { - currentState.value = - const AsyncData(""); - checkAnimationController - .forward(); - displayForm.value = false; - } else { - currentState.value = AsyncError( - "Une erreur est survenue", - StackTrace.current, - ); - } - } - }, - waitingColor: Colors.black, - builder: (child) => Container( - width: 100, - padding: const EdgeInsets.symmetric( - vertical: 16, - ), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular( - 10, - ), - color: Colors.grey.shade300, - boxShadow: [ - BoxShadow( - color: Colors.grey.withValues( - alpha: 0.3, - ), - blurRadius: 2.0, - offset: const Offset(1.0, 2.0), - ), - ], - ), - child: child, - ), - child: const Center( - child: Text( - "Confirmer", - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - ), - ), - ], - ), - ], - ), - ) - : Expanded( - child: Center( - child: Column( - children: [ - const Text( - "Un mail de confirmation a été envoyé à l'adresse suivante, pour confirmer le changement :", - textAlign: TextAlign.center, - style: TextStyle(fontSize: 16.0), - ), - const SizedBox(height: 16.0), - Text( - emailController.text, - textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 16.0, - fontWeight: FontWeight.bold, - ), - ), - const Spacer(), - GestureDetector( - onTap: alreadyDisplayedNotifier - .setAlreadyDisplayed, - child: Container( - width: 100, - padding: const EdgeInsets.symmetric( - vertical: 16, - ), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular( - 10, - ), - color: Colors.grey.shade300, - boxShadow: [ - BoxShadow( - color: Colors.grey.withValues( - alpha: 0.3, - ), - blurRadius: 2.0, - offset: const Offset(1.0, 2.0), - ), - ], - ), - child: const Center( - child: Text( - "Fermer", - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - ), - ), - ), - ], - ), - ), - ), - ], - ), - ), - Positioned( - left: Consts.padding, - right: Consts.padding, - child: currentState.value.when( - data: (data) => AnimatedBuilder( - animation: checkAnimationController, - builder: (context, child) { - return Container( - width: Consts.avatarRadius * 2, - height: Consts.avatarRadius * 2, - decoration: BoxDecoration( - shape: BoxShape.circle, - gradient: const LinearGradient( - colors: [ - Consts.greenGradient1, - Consts.greenGradient2, - ], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ), - boxShadow: [ - BoxShadow( - color: Consts.greenGradient2.withValues( - alpha: 0.3, - ), - blurRadius: 10.0, - offset: const Offset(0.0, 10.0), - ), - ], - ), - child: Center( - child: Icon( - Icons.check, - color: Colors.white, - size: 60 * checkAnimation.value, - ), - ), - ); - }, - ), - loading: () => Container( - width: Consts.avatarRadius * 2, - height: Consts.avatarRadius * 2, - decoration: BoxDecoration( - shape: BoxShape.circle, - gradient: const LinearGradient( - colors: [Consts.redGradient1, Consts.redGradient2], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ), - boxShadow: [ - BoxShadow( - color: Consts.redGradient2.withValues(alpha: 0.3), - blurRadius: 10.0, - offset: const Offset(0.0, 10.0), - ), - ], - ), - child: const Center( - child: CircularProgressIndicator(color: Colors.white), - ), - ), - error: (error, stack) => Container( - width: Consts.avatarRadius * 2, - height: Consts.avatarRadius * 2, - decoration: BoxDecoration( - shape: BoxShape.circle, - gradient: const LinearGradient( - colors: [Consts.redGradient1, Consts.redGradient2], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ), - boxShadow: [ - BoxShadow( - color: Consts.redGradient2.withValues(alpha: 0.3), - blurRadius: 10.0, - offset: const Offset(0.0, 10.0), - ), - ], - ), - child: const Center( - child: HeroIcon( - HeroIcons.exclamationCircle, - size: 60, - color: Colors.white, - ), - ), - ), - ), - ), - ], - ), - ), - ), - ), - ); - } -} diff --git a/lib/drawer/ui/fake_page.dart b/lib/drawer/ui/fake_page.dart deleted file mode 100644 index ba07cf20f5..0000000000 --- a/lib/drawer/ui/fake_page.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:titan/drawer/tools/constants.dart'; - -class FakePage extends StatelessWidget { - const FakePage({super.key}); - - @override - Widget build(BuildContext context) { - return Container( - margin: const EdgeInsets.only(top: 10, bottom: 50), - width: MediaQuery.of(context).size.width - 220, - height: 420, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(30), - boxShadow: const [ - BoxShadow( - color: DrawerColorConstants.fakePageShadow, - spreadRadius: 3, - blurRadius: 5, - offset: Offset(0, 3), - ), - ], - color: DrawerColorConstants.fakePageBlue, - ), - ); - } -} diff --git a/lib/drawer/ui/list_module.dart b/lib/drawer/ui/list_module.dart deleted file mode 100644 index b6f4009165..0000000000 --- a/lib/drawer/ui/list_module.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/drawer/providers/modules_provider.dart'; -import 'package:titan/drawer/ui/module.dart'; - -class ListModule extends HookConsumerWidget { - const ListModule({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final modules = ref.watch(listModuleProvider); - final scrollController = useScrollController(); - return Scrollbar( - controller: scrollController, - interactive: true, - radius: const Radius.circular(8), - thumbVisibility: true, - child: SingleChildScrollView( - controller: scrollController, - physics: const BouncingScrollPhysics(), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: modules.map((module) => ModuleUI(module: module)).toList(), - ), - ), - ); - } -} diff --git a/lib/drawer/ui/module.dart b/lib/drawer/ui/module.dart deleted file mode 100644 index a9b700850b..0000000000 --- a/lib/drawer/ui/module.dart +++ /dev/null @@ -1,78 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/drawer/class/module.dart'; -import 'package:titan/drawer/providers/animation_provider.dart'; -import 'package:titan/drawer/providers/swipe_provider.dart'; -import 'package:titan/drawer/tools/constants.dart'; -import 'package:titan/home/providers/scrolled_provider.dart'; -import 'package:titan/home/router.dart'; -import 'package:titan/tools/providers/path_forwarding_provider.dart'; -import 'package:qlevar_router/qlevar_router.dart'; - -class ModuleUI extends HookConsumerWidget { - const ModuleUI({super.key, required this.module}); - - static Duration duration = const Duration(milliseconds: 200); - final Module module; - - @override - Widget build(BuildContext context, WidgetRef ref) { - final pathForwardingNotifier = ref.read(pathForwardingProvider.notifier); - final pathForwarding = ref.watch(pathForwardingProvider); - final hasScrolled = ref.watch(hasScrolledProvider.notifier); - final animation = ref.watch(animationProvider); - return GestureDetector( - behavior: HitTestBehavior.translucent, - key: ValueKey(module.root), - child: Container( - margin: const EdgeInsets.only(top: 8, bottom: 8), - width: 220, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Row( - children: [ - Container(width: 25), - Center( - child: module.getIcon( - module.root == pathForwarding.path - ? DrawerColorConstants.selectedText - : DrawerColorConstants.lightText, - ), - ), - Container(width: 20), - SizedBox( - height: 50, - child: Center( - child: Text( - module.name, - style: TextStyle( - color: module.root == pathForwarding.path - ? DrawerColorConstants.selectedText - : DrawerColorConstants.lightText, - fontSize: 18, - ), - ), - ), - ), - ], - ), - ], - ), - ), - onTap: () { - QR.to(module.root); - pathForwardingNotifier.forward(module.root); - if (animation != null) { - final controllerNotifier = ref.watch( - swipeControllerProvider(animation).notifier, - ); - controllerNotifier.toggle(); - } - if (pathForwarding.path != HomeRouter.root) { - hasScrolled.setHasScrolled(false); - } - }, - ); - } -} diff --git a/lib/drawer/ui/notification_badge.dart b/lib/drawer/ui/notification_badge.dart deleted file mode 100644 index ee6886cdab..0000000000 --- a/lib/drawer/ui/notification_badge.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:badges/badges.dart' as badges; -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/tools/providers/should_notify_provider.dart'; - -class NotificationBadge extends HookConsumerWidget { - final Widget child; - - const NotificationBadge({super.key, required this.child}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final shouldNotify = ref.watch(shouldNotifyProvider); - return badges.Badge( - showBadge: shouldNotify, - position: badges.BadgePosition.topStart(top: -5, start: -10), - badgeStyle: badges.BadgeStyle( - shape: badges.BadgeShape.circle, - badgeGradient: badges.BadgeGradient.linear( - colors: [Colors.red.shade600, Colors.red.shade800], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ), - elevation: 0, - ), - child: child, - ); - } -} diff --git a/lib/event/router.dart b/lib/event/router.dart index bd9d1656a0..56a77e5e06 100644 --- a/lib/event/router.dart +++ b/lib/event/router.dart @@ -1,7 +1,7 @@ import 'package:either_dart/either.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:heroicons/heroicons.dart'; -import 'package:titan/drawer/class/module.dart'; +import 'package:titan/navigation/class/module.dart'; import 'package:titan/event/providers/is_admin_provider.dart'; import 'package:titan/event/ui/pages/detail_page/detail_page.dart' deferred as detail_page; @@ -24,6 +24,7 @@ class EventRouter { static const String detail = '/detail'; static final Module module = Module( name: "Évenements", + description: "Gérer les événements et les participants", icon: const Left(HeroIcons.calendar), root: EventRouter.root, selected: false, diff --git a/lib/event/ui/event.dart b/lib/event/ui/event.dart index 7a1a7f962b..2eeaac22fd 100644 --- a/lib/event/ui/event.dart +++ b/lib/event/ui/event.dart @@ -1,7 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:titan/event/router.dart'; -import 'package:titan/event/tools/constants.dart'; -import 'package:titan/tools/ui/widgets/top_bar.dart'; class EventTemplate extends StatelessWidget { final Widget child; @@ -9,14 +6,6 @@ class EventTemplate extends StatelessWidget { @override Widget build(BuildContext context) { - return SafeArea( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - const TopBar(title: EventTextConstants.title, root: EventRouter.root), - Expanded(child: child), - ], - ), - ); + return child; } } diff --git a/lib/flappybird/router.dart b/lib/flappybird/router.dart index 8f771df3cc..1150438458 100644 --- a/lib/flappybird/router.dart +++ b/lib/flappybird/router.dart @@ -1,6 +1,6 @@ import 'package:either_dart/either.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/drawer/class/module.dart'; +import 'package:titan/navigation/class/module.dart'; import 'package:titan/flappybird/ui/pages/game_page/game_page.dart' deferred as play_page; import 'package:titan/flappybird/ui/pages/leaderboard_page/leaderboard_page.dart' @@ -15,6 +15,7 @@ class FlappyBirdRouter { static const String leaderBoard = '/leaderboard'; static final Module module = Module( name: "FlappyBird", + description: "Jouer à Flappy Bird et consulter le classement", icon: const Right("assets/images/logo_flappybird.svg"), root: FlappyBirdRouter.root, selected: false, diff --git a/lib/flappybird/ui/flappybird_template.dart b/lib/flappybird/ui/flappybird_template.dart index efab4dea41..dfb5198112 100644 --- a/lib/flappybird/ui/flappybird_template.dart +++ b/lib/flappybird/ui/flappybird_template.dart @@ -1,12 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:google_fonts/google_fonts.dart'; -import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/flappybird/providers/score_list_provider.dart'; -import 'package:titan/flappybird/providers/user_score_provider.dart'; -import 'package:titan/flappybird/router.dart'; -import 'package:titan/tools/ui/widgets/top_bar.dart'; -import 'package:qlevar_router/qlevar_router.dart'; class FlappyBirdTemplate extends HookConsumerWidget { final Widget child; @@ -14,44 +7,6 @@ class FlappyBirdTemplate extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final leaderBoardNotifier = ref.watch(scoreListProvider.notifier); - final bestUserScoreNotifier = ref.watch(userScoreProvider.notifier); - return Container( - color: Colors.blue, - child: SafeArea( - child: Column( - children: [ - TopBar( - title: "Flappy Bird", - root: FlappyBirdRouter.root, - textStyle: GoogleFonts.silkscreen( - textStyle: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - color: Colors.white, - ), - ), - rightIcon: QR.currentPath == FlappyBirdRouter.root - ? IconButton( - onPressed: () { - leaderBoardNotifier.getLeaderboard(); - bestUserScoreNotifier.getLeaderBoardPosition(); - QR.to( - FlappyBirdRouter.root + FlappyBirdRouter.leaderBoard, - ); - }, - icon: const HeroIcon( - HeroIcons.trophy, - color: Colors.white, - size: 40, - ), - ) - : null, - ), - Expanded(child: child), - ], - ), - ), - ); + return child; } } diff --git a/lib/home/router.dart b/lib/home/router.dart index a8fbc48718..09fc0ae297 100644 --- a/lib/home/router.dart +++ b/lib/home/router.dart @@ -1,7 +1,7 @@ import 'package:either_dart/either.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:heroicons/heroicons.dart'; -import 'package:titan/drawer/class/module.dart'; +import 'package:titan/navigation/class/module.dart'; import 'package:titan/event/ui/pages/detail_page/detail_page.dart' deferred as detail_page; import 'package:titan/home/ui/home.dart' deferred as home_page; @@ -15,6 +15,7 @@ class HomeRouter { static const String detail = '/detail'; static final Module module = Module( name: "Calendrier", + description: "Consulter les événements et les activités", icon: const Left(HeroIcons.calendarDays), root: HomeRouter.root, selected: false, diff --git a/lib/home/ui/home.dart b/lib/home/ui/home.dart index 37a9e157c3..87bc068c8c 100644 --- a/lib/home/ui/home.dart +++ b/lib/home/ui/home.dart @@ -1,91 +1,69 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/event/providers/confirmed_event_list_provider.dart'; import 'package:titan/event/providers/sorted_event_list_provider.dart'; -import 'package:titan/home/router.dart'; import 'package:titan/home/tools/constants.dart'; import 'package:titan/home/ui/day_list.dart'; import 'package:titan/home/ui/days_event.dart'; import 'package:titan/home/ui/month_bar.dart'; import 'package:titan/tools/ui/widgets/align_left_text.dart'; -import 'package:titan/tools/ui/layouts/refresher.dart'; -import 'package:titan/tools/ui/widgets/top_bar.dart'; class HomePage extends HookConsumerWidget { const HomePage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - final confirmedEventListNotifier = ref.watch( - confirmedEventListProvider.notifier, - ); final sortedEventList = ref.watch(sortedEventListProvider); DateTime now = DateTime.now(); final ScrollController scrollController = useScrollController(); final daysEventScrollController = useScrollController(); - return Container( - color: Colors.white, - child: SafeArea( - child: Refresher( - onRefresh: () async { - await confirmedEventListNotifier.loadConfirmedEvent(); - now = DateTime.now(); - }, - child: Column( - children: [ - const TopBar( - title: HomeTextConstants.calendar, - root: HomeRouter.root, - ), - const SizedBox(height: 20), - MonthBar( - scrollController: scrollController, - width: MediaQuery.of(context).size.width, - ), - const SizedBox(height: 10), - DayList(scrollController, daysEventScrollController), - const SizedBox(height: 15), - const AlignLeftText( - HomeTextConstants.incomingEvents, - padding: EdgeInsets.symmetric(horizontal: 30.0), - fontSize: 25, - ), - const SizedBox(height: 10), - SizedBox( - height: MediaQuery.of(context).size.height - 320, - child: SingleChildScrollView( - physics: const BouncingScrollPhysics(), - controller: daysEventScrollController, - child: sortedEventList.keys.isNotEmpty - ? Column( - children: sortedEventList - .map( - (key, value) => MapEntry( - key, - DaysEvent(day: key, now: now, events: value), - ), - ) - .values - .toList(), - ) - : const Center( - child: Text( - HomeTextConstants.noEvents, - style: TextStyle( - fontSize: 15, - fontWeight: FontWeight.bold, - color: Colors.grey, - ), + return Column( + children: [ + const SizedBox(height: 20), + MonthBar( + scrollController: scrollController, + width: MediaQuery.of(context).size.width, + ), + const SizedBox(height: 10), + DayList(scrollController, daysEventScrollController), + const SizedBox(height: 15), + const AlignLeftText( + HomeTextConstants.incomingEvents, + padding: EdgeInsets.symmetric(horizontal: 30.0), + fontSize: 25, + ), + const SizedBox(height: 10), + SizedBox( + height: MediaQuery.of(context).size.height - 320, + child: SingleChildScrollView( + physics: const BouncingScrollPhysics(), + controller: daysEventScrollController, + child: sortedEventList.keys.isNotEmpty + ? Column( + children: sortedEventList + .map( + (key, value) => MapEntry( + key, + DaysEvent(day: key, now: now, events: value), ), - ), - ), - ), - ], + ) + .values + .toList(), + ) + : const Center( + child: Text( + HomeTextConstants.noEvents, + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.bold, + color: Colors.grey, + ), + ), + ), ), ), - ), + ], ); } } diff --git a/lib/loan/router.dart b/lib/loan/router.dart index 3b1ab07940..6771e38801 100644 --- a/lib/loan/router.dart +++ b/lib/loan/router.dart @@ -1,7 +1,7 @@ import 'package:either_dart/either.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:heroicons/heroicons.dart'; -import 'package:titan/drawer/class/module.dart'; +import 'package:titan/navigation/class/module.dart'; import 'package:titan/loan/providers/is_loan_admin_provider.dart'; import 'package:titan/loan/ui/pages/admin_page/admin_page.dart' deferred as admin_page; @@ -27,6 +27,7 @@ class LoanRouter { static const String detail = '/detail'; static final Module module = Module( name: "Prêt", + description: "Gérer les prêts et les articles", icon: const Left(HeroIcons.buildingLibrary), root: LoanRouter.root, selected: false, diff --git a/lib/loan/ui/loan.dart b/lib/loan/ui/loan.dart index ba6684d3bf..5ffd0b15f6 100644 --- a/lib/loan/ui/loan.dart +++ b/lib/loan/ui/loan.dart @@ -1,12 +1,5 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/loan/providers/item_list_provider.dart'; -import 'package:titan/loan/providers/loaner_provider.dart'; -import 'package:titan/loan/providers/loaners_items_provider.dart'; -import 'package:titan/loan/router.dart'; -import 'package:titan/loan/tools/constants.dart'; -import 'package:titan/tools/ui/widgets/top_bar.dart'; -import 'package:qlevar_router/qlevar_router.dart'; class LoanTemplate extends HookConsumerWidget { final Widget child; @@ -14,27 +7,6 @@ class LoanTemplate extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - return SafeArea( - child: Column( - children: [ - TopBar( - title: LoanTextConstants.loan, - root: LoanRouter.root, - onBack: () { - if (QR.currentPath == - LoanRouter.root + LoanRouter.admin + LoanRouter.addEditLoan) { - final loanersItemsNotifier = ref.watch( - loanersItemsProvider.notifier, - ); - final loaner = ref.watch(loanerProvider); - final itemList = ref.watch(itemListProvider); - loanersItemsNotifier.setTData(loaner, itemList); - } - }, - ), - Expanded(child: child), - ], - ), - ); + return child; } } diff --git a/lib/login/router.dart b/lib/login/router.dart index ca59e95dc8..026ce96f22 100644 --- a/lib/login/router.dart +++ b/lib/login/router.dart @@ -1,6 +1,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/drawer/providers/is_web_format_provider.dart'; +import 'package:titan/navigation/providers/is_web_format_provider.dart'; import 'package:titan/login/ui/app_sign_in.dart' deferred as app_sign_in; import 'package:titan/login/ui/pages/create_account_page/create_account_page.dart' deferred as create_account_page; diff --git a/lib/main.dart b/lib/main.dart index 95c12ab6af..62075d540c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -8,13 +8,11 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:titan/login/providers/animation_provider.dart'; import 'package:flutter/foundation.dart' show kIsWeb; -import 'package:titan/drawer/providers/animation_provider.dart'; -import 'package:titan/drawer/providers/swipe_provider.dart'; -import 'package:titan/drawer/providers/top_bar_callback_provider.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/router.dart'; import 'package:titan/service/tools/setup.dart'; +import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/plausible/plausible_observer.dart'; import 'package:titan/tools/providers/path_forwarding_provider.dart'; @@ -90,60 +88,36 @@ class MyApp extends HookConsumerWidget { }, []); } - final popScope = PopScope( - canPop: false, - onPopInvokedWithResult: (didPop, result) async { - final topBarCallBack = ref.watch(topBarCallBackProvider); - if (QR.currentPath.split('/').length <= 2) { - final animation = ref.watch(animationProvider); - if (animation != null) { - final controller = ref.watch(swipeControllerProvider(animation)); - if (controller.isCompleted) { - SystemChannels.platform.invokeMethod('SystemNavigator.pop'); - } else { - final controllerNotifier = ref.watch( - swipeControllerProvider(animation).notifier, - ); - controllerNotifier.toggle(); - topBarCallBack.onMenu?.call(); - } - } - return; + return MaterialApp.router( + debugShowCheckedModeBanner: false, + title: 'MyECL', + scrollBehavior: MyCustomScrollBehavior(), + supportedLocales: const [Locale('en', 'US'), Locale('fr', 'FR')], + localizationsDelegates: const [ + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + theme: ThemeData( + primarySwatch: Colors.red, + textTheme: GoogleFonts.latoTextTheme(Theme.of(context).textTheme), + brightness: Brightness.light, + scaffoldBackgroundColor: ColorConstants.background, + ), + routeInformationParser: const QRouteInformationParser(), + builder: (context, child) { + if (child == null) { + return const SizedBox(); } - QR.back(); - topBarCallBack.onBack?.call(); + return AppTemplate(child: child); }, - child: MaterialApp.router( - debugShowCheckedModeBanner: false, - title: 'MyECL', - scrollBehavior: MyCustomScrollBehavior(), - supportedLocales: const [Locale('en', 'US'), Locale('fr', 'FR')], - localizationsDelegates: const [ - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - GlobalCupertinoLocalizations.delegate, - ], - theme: ThemeData( - primarySwatch: Colors.orange, - textTheme: GoogleFonts.latoTextTheme(Theme.of(context).textTheme), - brightness: Brightness.light, - ), - routeInformationParser: const QRouteInformationParser(), - builder: (context, child) { - if (child == null) { - return const SizedBox(); - } - return AppTemplate(child: child); - }, - routerDelegate: QRouterDelegate( - appRouter.routes, - observers: [if (plausible != null) PlausibleObserver(plausible)], - initPath: AppRouter.root, - navKey: navigatorKey, - ), + routerDelegate: QRouterDelegate( + appRouter.routes, + observers: [if (plausible != null) PlausibleObserver(plausible)], + initPath: AppRouter.root, + navKey: navigatorKey, ), ); - return popScope; } } diff --git a/lib/drawer/class/module.dart b/lib/navigation/class/module.dart similarity index 89% rename from lib/drawer/class/module.dart rename to lib/navigation/class/module.dart index d32bcec9b8..4a2b8c7496 100644 --- a/lib/drawer/class/module.dart +++ b/lib/navigation/class/module.dart @@ -19,12 +19,14 @@ enum ModuleType { class Module { String name; + String description; Either icon; String root; bool selected; Module({ required this.name, + required this.description, required this.icon, required this.root, required this.selected, @@ -32,11 +34,13 @@ class Module { Module copy({ String? name, + String? description, Either? icon, String? root, bool? selected, }) => Module( name: name ?? this.name, + description: description ?? this.description, icon: icon ?? this.icon, root: root ?? this.root, selected: selected ?? this.selected, diff --git a/lib/drawer/providers/display_quit_popup.dart b/lib/navigation/providers/display_quit_popup.dart similarity index 100% rename from lib/drawer/providers/display_quit_popup.dart rename to lib/navigation/providers/display_quit_popup.dart diff --git a/lib/drawer/providers/is_web_format_provider.dart b/lib/navigation/providers/is_web_format_provider.dart similarity index 100% rename from lib/drawer/providers/is_web_format_provider.dart rename to lib/navigation/providers/is_web_format_provider.dart diff --git a/lib/drawer/providers/modules_provider.dart b/lib/navigation/providers/modules_provider.dart similarity index 92% rename from lib/drawer/providers/modules_provider.dart rename to lib/navigation/providers/modules_provider.dart index 04e61ae8aa..6a141f447a 100644 --- a/lib/drawer/providers/modules_provider.dart +++ b/lib/navigation/providers/modules_provider.dart @@ -1,5 +1,5 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/drawer/class/module.dart'; +import 'package:titan/navigation/class/module.dart'; import 'package:titan/settings/providers/module_list_provider.dart'; class ModuleListNotifier extends StateNotifier> { diff --git a/lib/drawer/providers/should_setup_provider.dart b/lib/navigation/providers/should_setup_provider.dart similarity index 100% rename from lib/drawer/providers/should_setup_provider.dart rename to lib/navigation/providers/should_setup_provider.dart diff --git a/lib/drawer/tools/constants.dart b/lib/navigation/tools/constants.dart similarity index 100% rename from lib/drawer/tools/constants.dart rename to lib/navigation/tools/constants.dart diff --git a/lib/navigation/ui/all_module_page.dart b/lib/navigation/ui/all_module_page.dart new file mode 100644 index 0000000000..fd04384286 --- /dev/null +++ b/lib/navigation/ui/all_module_page.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/admin/providers/is_admin_provider.dart'; +import 'package:titan/admin/router.dart'; +import 'package:titan/navigation/providers/modules_provider.dart'; +import 'package:titan/settings/router.dart'; +import 'package:titan/tools/constants.dart'; +import 'package:titan/tools/providers/path_forwarding_provider.dart'; +import 'package:titan/tools/ui/styleguide/list_item.dart'; +import 'package:titan/tools/ui/styleguide/searchbar.dart'; + +class AllModulePage extends ConsumerWidget { + const AllModulePage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final modules = ref.watch(listModuleProvider); + final isAdmin = ref.watch(isAdminProvider); + return Container( + color: ColorConstants.background, + padding: const EdgeInsets.symmetric(horizontal: 20.0), + child: Column( + children: [ + CustomSearchBar(onSearch: (String query) {}, onFilter: () {}), + SizedBox(height: 30), + ...[ + ...modules, + SettingsRouter.module, + if (isAdmin) AdminRouter.module, + ].map( + (module) => ListItem( + title: module.name, + subtitle: module.description, + onTap: () { + final pathForwardingNotifier = ref.watch( + pathForwardingProvider.notifier, + ); + pathForwardingNotifier.forward(module.root); + QR.to(module.root); + }, + ), + ), + SizedBox(height: 80), + ], + ), + ); + } +} diff --git a/lib/navigation/ui/drawer_template.dart b/lib/navigation/ui/drawer_template.dart new file mode 100644 index 0000000000..4aa0f391e9 --- /dev/null +++ b/lib/navigation/ui/drawer_template.dart @@ -0,0 +1,105 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/foundation.dart' show kIsWeb; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/navigation/providers/display_quit_popup.dart'; +import 'package:titan/navigation/providers/modules_provider.dart'; +import 'package:titan/navigation/providers/should_setup_provider.dart'; +import 'package:titan/router.dart'; +import 'package:titan/service/tools/setup.dart'; +import 'package:titan/navigation/ui/quit_dialog.dart'; +import 'package:titan/tools/providers/path_forwarding_provider.dart'; +import 'package:titan/tools/ui/styleguide/navbar.dart'; +import 'package:titan/user/providers/user_provider.dart'; + +class DrawerTemplate extends HookConsumerWidget { + static Duration duration = const Duration(milliseconds: 200); + static const double maxSlide = 255; + static const dragRightStartVal = 60; + static const dragLeftStartVal = maxSlide - 20; + static bool shouldDrag = false; + final Widget child; + + const DrawerTemplate({super.key, required this.child}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final user = ref.watch(userProvider); + final modules = ref.watch(listModuleProvider); + final displayQuit = ref.watch(displayQuitProvider); + final shouldSetup = ref.watch(shouldSetupProvider); + final shouldSetupNotifier = ref.read(shouldSetupProvider.notifier); + + Future(() { + if (!kIsWeb && user.id != "" && shouldSetup) { + setUpNotification(ref); + shouldSetupNotifier.setShouldSetup(); + } + }); + + return Scaffold( + body: SafeArea( + child: Stack( + children: [ + Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + padding: const EdgeInsets.all(15.0), + child: Center( + child: Text( + 'MyEMApp', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w900, + ), + ), + ), + ), + SizedBox(height: 10), + Expanded( + child: SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: Container(color: Colors.yellow, child: child), + ), + ), + ], + ), + Positioned( + left: 0, + bottom: 0, + right: 0, + child: FloatingNavbar( + items: [ + ...modules.sublist(0, 3).map((module) { + return FloatingNavbarItem( + title: module.name, + onTap: () { + final pathForwardingNotifier = ref.watch( + pathForwardingProvider.notifier, + ); + pathForwardingNotifier.forward(module.root); + QR.to(module.root); + }, + ); + }), + FloatingNavbarItem( + title: 'Autres', + onTap: () { + final pathForwardingNotifier = ref.watch( + pathForwardingProvider.notifier, + ); + pathForwardingNotifier.forward(AppRouter.allModules); + QR.to(AppRouter.allModules); + }, + ), + ], + ), + ), + if (displayQuit) const QuitDialog(), + ], + ), + ), + ); + } +} diff --git a/lib/drawer/ui/quit_dialog.dart b/lib/navigation/ui/quit_dialog.dart similarity index 93% rename from lib/drawer/ui/quit_dialog.dart rename to lib/navigation/ui/quit_dialog.dart index 6ed944391a..b19e485bb3 100644 --- a/lib/drawer/ui/quit_dialog.dart +++ b/lib/navigation/ui/quit_dialog.dart @@ -2,8 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/auth/providers/openid_provider.dart'; -import 'package:titan/drawer/providers/display_quit_popup.dart'; -import 'package:titan/drawer/tools/constants.dart'; +import 'package:titan/navigation/providers/display_quit_popup.dart'; +import 'package:titan/navigation/tools/constants.dart'; import 'package:titan/service/providers/firebase_token_expiration_provider.dart'; import 'package:titan/service/providers/messages_provider.dart'; import 'package:titan/tools/functions.dart'; diff --git a/lib/paiement/router.dart b/lib/paiement/router.dart index c4d106a805..76a22e47f9 100644 --- a/lib/paiement/router.dart +++ b/lib/paiement/router.dart @@ -1,7 +1,7 @@ import 'package:either_dart/either.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:heroicons/heroicons.dart'; -import 'package:titan/drawer/class/module.dart'; +import 'package:titan/navigation/class/module.dart'; import 'package:titan/paiement/providers/is_payment_admin.dart'; import 'package:titan/paiement/ui/pages/admin_page/admin_page.dart' deferred as admin_page; @@ -39,6 +39,7 @@ class PaymentRouter { static const String storeStats = '/storeStats'; static final Module module = Module( name: "MyECLPay", + description: "Gérer les paiements, les statistiques et les appareils", icon: const Left(HeroIcons.creditCard), root: PaymentRouter.root, selected: false, diff --git a/lib/paiement/ui/paiement.dart b/lib/paiement/ui/paiement.dart index d5f5ad63a4..cd107cb462 100644 --- a/lib/paiement/ui/paiement.dart +++ b/lib/paiement/ui/paiement.dart @@ -1,7 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:titan/paiement/router.dart'; -import 'package:titan/tools/ui/widgets/top_bar.dart'; -import 'package:titan/paiement/tools/constants.dart'; class PaymentTemplate extends StatelessWidget { final Widget child; @@ -9,16 +6,6 @@ class PaymentTemplate extends StatelessWidget { @override Widget build(BuildContext context) { - return SafeArea( - child: Column( - children: [ - const TopBar( - title: PaiementTextConstants.paiement, - root: PaymentRouter.root, - ), - Expanded(child: child), - ], - ), - ); + return child; } } diff --git a/lib/ph/router.dart b/lib/ph/router.dart index c9685aae76..0aaf08ce26 100644 --- a/lib/ph/router.dart +++ b/lib/ph/router.dart @@ -3,7 +3,7 @@ import 'package:either_dart/either.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:heroicons/heroicons.dart'; -import 'package:titan/drawer/class/module.dart'; +import 'package:titan/navigation/class/module.dart'; import 'package:titan/ph/providers/is_ph_admin_provider.dart'; import 'package:titan/ph/ui/pages/form_page/add_edit_ph_page.dart' deferred as add_edit_ph_page; @@ -27,6 +27,7 @@ class PhRouter { static const String add_ph = '/add_ph'; static final Module module = Module( name: "PH", + description: "Gérer les PH, les formulaires et les administrateurs", icon: const Left(HeroIcons.newspaper), root: PhRouter.root, selected: false, diff --git a/lib/ph/ui/pages/ph.dart b/lib/ph/ui/pages/ph.dart index 76d2853998..96d617bd09 100644 --- a/lib/ph/ui/pages/ph.dart +++ b/lib/ph/ui/pages/ph.dart @@ -1,7 +1,5 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/ph/router.dart'; -import 'package:titan/tools/ui/widgets/top_bar.dart'; class PhTemplate extends HookConsumerWidget { final Widget child; @@ -9,14 +7,6 @@ class PhTemplate extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - return SafeArea( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - const TopBar(title: "PH", root: PhRouter.root), - Expanded(child: child), - ], - ), - ); + return child; } } diff --git a/lib/phonebook/router.dart b/lib/phonebook/router.dart index 6df9f47b06..f8feb9ba3c 100644 --- a/lib/phonebook/router.dart +++ b/lib/phonebook/router.dart @@ -1,7 +1,7 @@ import 'package:either_dart/either.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/drawer/class/module.dart'; +import 'package:titan/navigation/class/module.dart'; import 'package:titan/phonebook/providers/phonebook_admin_provider.dart'; import 'package:titan/phonebook/ui/pages/admin_page/admin_page.dart'; import 'package:titan/phonebook/ui/pages/association_creation_page/association_creation_page.dart'; @@ -25,6 +25,7 @@ class PhonebookRouter { static const String addEditMember = '/add_edit_member'; static final Module module = Module( name: "Annuaire", + description: "Gérer les associations, les membres et les administrateurs", icon: const Left(HeroIcons.phone), root: PhonebookRouter.root, selected: false, diff --git a/lib/phonebook/ui/phonebook.dart b/lib/phonebook/ui/phonebook.dart index 4921564af0..652bae4a1f 100644 --- a/lib/phonebook/ui/phonebook.dart +++ b/lib/phonebook/ui/phonebook.dart @@ -1,10 +1,5 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/phonebook/providers/association_kind_provider.dart'; -import 'package:titan/phonebook/router.dart'; -import 'package:titan/phonebook/tools/constants.dart'; -import 'package:titan/tools/ui/widgets/top_bar.dart'; -import 'package:qlevar_router/qlevar_router.dart'; class PhonebookTemplate extends HookConsumerWidget { final Widget child; @@ -12,34 +7,6 @@ class PhonebookTemplate extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final kindNotifier = ref.watch(associationKindProvider.notifier); - return SafeArea( - child: Column( - children: [ - TopBar( - title: PhonebookTextConstants.phonebook, - root: PhonebookRouter.root, - onBack: () { - if (QR.currentPath != - PhonebookRouter.root + - PhonebookRouter.admin + - PhonebookRouter.editAssociation + - PhonebookRouter.addEditMember) { - kindNotifier.setKind(''); - } - if (QR.currentPath == - PhonebookRouter.root + - PhonebookRouter.admin + - PhonebookRouter.editAssociation) { - QR.to( - PhonebookRouter.root + PhonebookRouter.admin, - ); // Used on back after adding an association - } - }, - ), - Expanded(child: child), - ], - ), - ); + return child; } } diff --git a/lib/purchases/router.dart b/lib/purchases/router.dart index fa3de16eb5..438a3a4663 100644 --- a/lib/purchases/router.dart +++ b/lib/purchases/router.dart @@ -1,7 +1,7 @@ import 'package:either_dart/either.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/drawer/class/module.dart'; +import 'package:titan/navigation/class/module.dart'; import 'package:titan/purchases/providers/purchases_admin_provider.dart'; import 'package:titan/purchases/ui/pages/history_page/history_page.dart'; import 'package:titan/purchases/ui/pages/main_page/main_page.dart'; @@ -23,6 +23,7 @@ class PurchasesRouter { static const String purchase = '/purchase'; static final Module module = Module( name: "Achats", + description: "Gérer les achats, les tickets et l'historique", icon: const Left(HeroIcons.shoppingBag), root: PurchasesRouter.root, selected: false, diff --git a/lib/purchases/ui/purchases.dart b/lib/purchases/ui/purchases.dart index 8e10186783..a65787d3ca 100644 --- a/lib/purchases/ui/purchases.dart +++ b/lib/purchases/ui/purchases.dart @@ -1,8 +1,5 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/purchases/router.dart'; -import 'package:titan/purchases/tools/constants.dart'; -import 'package:titan/tools/ui/widgets/top_bar.dart'; class PurchasesTemplate extends HookConsumerWidget { final Widget child; @@ -10,16 +7,6 @@ class PurchasesTemplate extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - return SafeArea( - child: Column( - children: [ - const TopBar( - title: PurchasesTextConstants.purchases, - root: PurchasesRouter.root, - ), - Expanded(child: child), - ], - ), - ); + return child; } } diff --git a/lib/raffle/router.dart b/lib/raffle/router.dart index 2f0797698b..ca0dd578e9 100644 --- a/lib/raffle/router.dart +++ b/lib/raffle/router.dart @@ -1,7 +1,7 @@ import 'package:either_dart/either.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:heroicons/heroicons.dart'; -import 'package:titan/drawer/class/module.dart'; +import 'package:titan/navigation/class/module.dart'; import 'package:titan/raffle/providers/is_raffle_admin.dart'; import 'package:titan/raffle/ui/pages/admin_module_page/admin_module_page.dart' deferred as admin_module_page; @@ -30,6 +30,7 @@ class RaffleRouter { static const String creation = '/creation'; static final Module module = Module( name: "Tombola", + description: "Gérer les tombolas, les prix et les tickets", icon: const Left(HeroIcons.gift), root: RaffleRouter.root, selected: false, diff --git a/lib/raffle/ui/raffle.dart b/lib/raffle/ui/raffle.dart index 99a1eebe6f..78b3010235 100644 --- a/lib/raffle/ui/raffle.dart +++ b/lib/raffle/ui/raffle.dart @@ -1,8 +1,5 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/raffle/router.dart'; -import 'package:titan/raffle/tools/constants.dart'; -import 'package:titan/tools/ui/widgets/top_bar.dart'; class RaffleTemplate extends HookConsumerWidget { final Widget child; @@ -10,16 +7,6 @@ class RaffleTemplate extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - return SafeArea( - child: Column( - children: [ - const TopBar( - title: RaffleTextConstants.raffle, - root: RaffleRouter.root, - ), - Expanded(child: child), - ], - ), - ); + return child; } } diff --git a/lib/recommendation/router.dart b/lib/recommendation/router.dart index 3cdce98c4c..14e1a8c750 100644 --- a/lib/recommendation/router.dart +++ b/lib/recommendation/router.dart @@ -1,7 +1,7 @@ import 'package:either_dart/either.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:heroicons/heroicons.dart'; -import 'package:titan/drawer/class/module.dart'; +import 'package:titan/navigation/class/module.dart'; import 'package:titan/recommendation/providers/is_recommendation_admin_provider.dart'; import 'package:titan/recommendation/ui/pages/main_page.dart' deferred as main_page; @@ -22,6 +22,7 @@ class RecommendationRouter { static const String addEdit = '/add_edit'; static final Module module = Module( name: "Bons plans", + description: "Gérer les recommandations, les informations et les administrateurs", icon: const Left(HeroIcons.currencyEuro), root: RecommendationRouter.root, selected: false, diff --git a/lib/recommendation/ui/widgets/recommendation_template.dart b/lib/recommendation/ui/widgets/recommendation_template.dart index 1782cfbadc..15b27def2c 100644 --- a/lib/recommendation/ui/widgets/recommendation_template.dart +++ b/lib/recommendation/ui/widgets/recommendation_template.dart @@ -1,7 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:titan/recommendation/router.dart'; -import 'package:titan/recommendation/tools/constants.dart'; -import 'package:titan/tools/ui/widgets/top_bar.dart'; class RecommendationTemplate extends StatelessWidget { final Widget child; @@ -9,17 +6,6 @@ class RecommendationTemplate extends StatelessWidget { @override Widget build(BuildContext context) { - return SafeArea( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - const TopBar( - title: RecommendationTextConstants.recommendation, - root: RecommendationRouter.root, - ), - Expanded(child: child), - ], - ), - ); + return child; } } diff --git a/lib/router.dart b/lib/router.dart index ce3cd38ae4..8f1c01d9ea 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -11,6 +11,8 @@ import 'package:titan/home/router.dart'; import 'package:titan/home/ui/home.dart' deferred as home_page; import 'package:titan/loan/router.dart'; import 'package:titan/login/router.dart'; +import 'package:titan/navigation/ui/all_module_page.dart' + deferred as all_module_page; import 'package:titan/others/ui/loading_page.dart' deferred as loading_page; import 'package:titan/others/ui/no_internet_page.dart' deferred as no_internet_page; @@ -40,6 +42,7 @@ class AppRouter { static const String update = '/update'; static const String noInternet = '/no_internet'; static const String noModule = '/no_module'; + static const String allModules = '/all_modules'; AppRouter(this.ref) { routes = [ @@ -71,6 +74,14 @@ class AppRouter { builder: () => no_module_page.NoModulePage(), middleware: [DeferredLoadingMiddleware(no_module_page.loadLibrary)], ), + QRoute( + path: allModules, + builder: () => all_module_page.AllModulePage(), + middleware: [ + AuthenticatedMiddleware(ref), + DeferredLoadingMiddleware(all_module_page.loadLibrary), + ], + ), AdminRouter(ref).route(), AdvertRouter(ref).route(), AmapRouter(ref).route(), diff --git a/lib/seed-library/router.dart b/lib/seed-library/router.dart index 6451cc95aa..bb2d1047eb 100644 --- a/lib/seed-library/router.dart +++ b/lib/seed-library/router.dart @@ -1,7 +1,7 @@ import 'package:either_dart/either.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:heroicons/heroicons.dart'; -import 'package:titan/drawer/class/module.dart'; +import 'package:titan/navigation/class/module.dart'; import 'package:titan/seed-library/providers/is_seed_library_admin_provider.dart'; import 'package:titan/seed-library/ui/pages/add_edit_species_page/add_edit_species_page.dart' deferred as add_edit_species_page; @@ -49,6 +49,7 @@ class SeedLibraryRouter { SeedLibraryRouter(this.ref); static final Module module = Module( name: "Grainothèque", + description: "Gérer les graines, les espèces et les stocks", icon: const Left(HeroIcons.inboxStack), root: SeedLibraryRouter.root, selected: false, diff --git a/lib/seed-library/ui/seed_library.dart b/lib/seed-library/ui/seed_library.dart index 74630c7aea..a39ca89f1e 100644 --- a/lib/seed-library/ui/seed_library.dart +++ b/lib/seed-library/ui/seed_library.dart @@ -1,9 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:heroicons/heroicons.dart'; -import 'package:titan/seed-library/router.dart'; -import 'package:titan/seed-library/tools/constants.dart'; -import 'package:titan/tools/ui/widgets/top_bar.dart'; -import 'package:qlevar_router/qlevar_router.dart'; class SeedLibraryTemplate extends StatelessWidget { final Widget child; @@ -11,30 +6,6 @@ class SeedLibraryTemplate extends StatelessWidget { @override Widget build(BuildContext context) { - return SafeArea( - child: Column( - children: [ - TopBar( - title: SeedLibraryTextConstants.seedLibrary, - root: SeedLibraryRouter.root, - rightIcon: QR.currentPath == SeedLibraryRouter.root - ? IconButton( - onPressed: () { - QR.to( - SeedLibraryRouter.root + SeedLibraryRouter.information, - ); - }, - icon: const HeroIcon( - HeroIcons.informationCircle, - color: Colors.black, - size: 40, - ), - ) - : null, - ), - Expanded(child: child), - ], - ), - ); + return child; } } diff --git a/lib/settings/providers/module_list_provider.dart b/lib/settings/providers/module_list_provider.dart index 6e4149cd0f..3f7063056e 100644 --- a/lib/settings/providers/module_list_provider.dart +++ b/lib/settings/providers/module_list_provider.dart @@ -1,12 +1,13 @@ import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/admin/router.dart'; import 'package:titan/advert/router.dart'; import 'package:titan/admin/providers/all_my_module_roots_list_provider.dart'; import 'package:titan/amap/router.dart'; import 'package:titan/booking/router.dart'; import 'package:titan/centralisation/router.dart'; import 'package:titan/cinema/router.dart'; -import 'package:titan/drawer/class/module.dart'; +import 'package:titan/navigation/class/module.dart'; import 'package:collection/collection.dart'; import 'package:titan/event/router.dart'; import 'package:titan/home/router.dart'; @@ -41,20 +42,20 @@ class ModulesNotifier extends StateNotifier> { List allModules = [ HomeRouter.module, AdvertRouter.module, - AmapRouter.module, - BookingRouter.module, - CentralisationRouter.module, - CinemaRouter.module, - EventRouter.module, - LoanRouter.module, + // AmapRouter.module, + // BookingRouter.module, + // CentralisationRouter.module, + // CinemaRouter.module, + // EventRouter.module, + // LoanRouter.module, PaymentRouter.module, - PhonebookRouter.module, - PhRouter.module, - PurchasesRouter.module, - RaffleRouter.module, - RecommendationRouter.module, - VoteRouter.module, - SeedLibraryRouter.module, + // PhonebookRouter.module, + // PhRouter.module, + // PurchasesRouter.module, + // RaffleRouter.module, + // RecommendationRouter.module, + // VoteRouter.module, + // SeedLibraryRouter.module, ]; ModulesNotifier() : super([]); diff --git a/lib/settings/router.dart b/lib/settings/router.dart index 2718ccf5e0..e58a8dca63 100644 --- a/lib/settings/router.dart +++ b/lib/settings/router.dart @@ -1,5 +1,9 @@ +import 'package:either_dart/either.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:heroicons/heroicons.dart'; +import 'package:titan/admin/router.dart'; +import 'package:titan/navigation/class/module.dart'; import 'package:titan/settings/ui/pages/change_pass/change_pass.dart' deferred as change_pass; import 'package:titan/settings/ui/pages/edit_user_page/edit_user_page.dart' @@ -24,6 +28,14 @@ class SettingsRouter { static const String logs = '/logs'; static const String modules = '/modules'; static const String notifications = '/notifications'; + static final Module module = Module( + name: "Paramètres", + description: "Gérer les paramètres de l'application", + icon: const Left(HeroIcons.cog), + root: AdminRouter.root, + selected: false, + ); + SettingsRouter(this.ref); QRoute route() => QRoute( diff --git a/lib/settings/ui/settings.dart b/lib/settings/ui/settings.dart index d80b9f7c4e..3291a2d201 100644 --- a/lib/settings/ui/settings.dart +++ b/lib/settings/ui/settings.dart @@ -1,7 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:titan/settings/router.dart'; -import 'package:titan/settings/tools/constants.dart'; -import 'package:titan/tools/ui/widgets/top_bar.dart'; class SettingsTemplate extends StatelessWidget { final Widget child; @@ -9,20 +6,6 @@ class SettingsTemplate extends StatelessWidget { @override Widget build(BuildContext context) { - return Container( - decoration: const BoxDecoration(color: Colors.white), - child: SafeArea( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - const TopBar( - title: SettingsTextConstants.settings, - root: SettingsRouter.root, - ), - Expanded(child: child), - ], - ), - ), - ); + return child; } } diff --git a/lib/tools/constants.dart b/lib/tools/constants.dart index 961ecce1f0..c727e33541 100644 --- a/lib/tools/constants.dart +++ b/lib/tools/constants.dart @@ -10,6 +10,7 @@ class ColorConstants { static const Color background = Color(0xFFffffff); static const Color onBackground = Color(0xffb4b4b4); + static const Color searchBar = Color(0xfffafafa); static const Color secondary = Color(0xFFb1b2b5); static const Color tertiary = Color(0xFF424242); static const Color onTertiary = Color(0xFF212121); diff --git a/lib/tools/middlewares/authenticated_middleware.dart b/lib/tools/middlewares/authenticated_middleware.dart index 7027dd4bc2..fd71f8c3b0 100644 --- a/lib/tools/middlewares/authenticated_middleware.dart +++ b/lib/tools/middlewares/authenticated_middleware.dart @@ -30,6 +30,7 @@ class AuthenticatedMiddleware extends QMiddleware { path != "/") { pathForwardingNotifier.forward(path); } + print("Redirecting to ${pathForwardingNotifier.state}"); return check.when( data: (value) { if (!value) { diff --git a/lib/tools/providers/should_notify_provider.dart b/lib/tools/providers/should_notify_provider.dart deleted file mode 100644 index f845907b31..0000000000 --- a/lib/tools/providers/should_notify_provider.dart +++ /dev/null @@ -1,11 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/tools/functions.dart'; -import 'package:titan/user/providers/user_provider.dart'; - -final shouldNotifyProvider = Provider((ref) { - final asyncUser = ref.watch(asyncUserProvider); - return asyncUser.maybeWhen( - data: (user) => !isStudent(user.email) && isNotStaff(user.email), - orElse: () => false, - ); -}); diff --git a/lib/tools/ui/layouts/app_template.dart b/lib/tools/ui/layouts/app_template.dart index 00f4dab03b..16936ac66f 100644 --- a/lib/tools/ui/layouts/app_template.dart +++ b/lib/tools/ui/layouts/app_template.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/auth/providers/openid_provider.dart'; -import 'package:titan/drawer/ui/drawer_template.dart'; +import 'package:titan/navigation/ui/drawer_template.dart'; import 'package:titan/version/providers/titan_version_provider.dart'; import 'package:titan/version/providers/version_verifier_provider.dart'; diff --git a/lib/tools/ui/styleguide/list_item_template.dart b/lib/tools/ui/styleguide/list_item_template.dart index 3998af8a3f..8517f17dfb 100644 --- a/lib/tools/ui/styleguide/list_item_template.dart +++ b/lib/tools/ui/styleguide/list_item_template.dart @@ -24,7 +24,7 @@ class ListItemTemplate extends StatelessWidget { onTap: onTap, behavior: HitTestBehavior.opaque, child: Container( - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 4), + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), child: Row( children: [ if (icon != null) ...[icon!, const SizedBox(width: 10)], @@ -36,8 +36,8 @@ class ListItemTemplate extends StatelessWidget { title, style: TextStyle( fontSize: 16, - fontWeight: FontWeight.w500, - color: ColorConstants.tertiary, + fontWeight: FontWeight.w900, + color: ColorConstants.onTertiary, ), ), if (subtitle != null) diff --git a/lib/tools/ui/styleguide/navbar.dart b/lib/tools/ui/styleguide/navbar.dart index c0df6b0ca6..5c2504ceb1 100644 --- a/lib/tools/ui/styleguide/navbar.dart +++ b/lib/tools/ui/styleguide/navbar.dart @@ -1,3 +1,4 @@ +import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:titan/tools/constants.dart'; @@ -180,7 +181,7 @@ class FloatingNavbar extends HookWidget { } return Center( - child: Text( + child: AutoSizeText( item.title, style: TextStyle( color: textColor, diff --git a/lib/tools/ui/styleguide/router.dart b/lib/tools/ui/styleguide/router.dart index 4d926623ee..a04064b840 100644 --- a/lib/tools/ui/styleguide/router.dart +++ b/lib/tools/ui/styleguide/router.dart @@ -1,7 +1,7 @@ import 'package:either_dart/either.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:heroicons/heroicons.dart'; -import 'package:titan/drawer/class/module.dart'; +import 'package:titan/navigation/class/module.dart'; import 'package:titan/tools/ui/styleguide/styleguide_page.dart'; import 'package:qlevar_router/qlevar_router.dart'; @@ -10,6 +10,7 @@ class StyleGuideRouter { static const String root = '/styleguide'; static final Module module = Module( name: "Style Guide", + description: "Explore the UI components and styles used in Titan", icon: const Left(HeroIcons.paintBrush), root: StyleGuideRouter.root, selected: false, diff --git a/lib/tools/ui/styleguide/searchbar.dart b/lib/tools/ui/styleguide/searchbar.dart index 0e5a402c9b..87d2517670 100644 --- a/lib/tools/ui/styleguide/searchbar.dart +++ b/lib/tools/ui/styleguide/searchbar.dart @@ -20,17 +20,18 @@ class CustomSearchBar extends HookWidget { Widget build(BuildContext context) { final textController = useTextEditingController(); - return Material( - elevation: 4, - borderRadius: BorderRadius.circular(50), - color: ColorConstants.background, + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(50), + color: ColorConstants.searchBar, + ), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 4.0), child: Row( children: [ HeroIcon( HeroIcons.magnifyingGlass, - color: ColorConstants.tertiary, + color: ColorConstants.onBackground, size: 24, ), const SizedBox(width: 8), @@ -41,7 +42,10 @@ class CustomSearchBar extends HookWidget { onChanged: (value) { onSearch(value); }, - style: TextStyle(color: ColorConstants.tertiary, fontSize: 16), + style: TextStyle( + color: ColorConstants.onBackground, + fontSize: 16, + ), decoration: InputDecoration( hintText: hintText, hintStyle: TextStyle( @@ -57,7 +61,7 @@ class CustomSearchBar extends HookWidget { IconButton( icon: HeroIcon( HeroIcons.xMark, - color: ColorConstants.tertiary, + color: ColorConstants.onBackground, size: 20, ), onPressed: () { @@ -69,7 +73,7 @@ class CustomSearchBar extends HookWidget { IconButton( icon: HeroIcon( HeroIcons.adjustmentsHorizontal, - color: ColorConstants.tertiary, + color: ColorConstants.onBackground, size: 20, ), onPressed: onFilter, diff --git a/lib/tools/ui/styleguide/styleguide_page.dart b/lib/tools/ui/styleguide/styleguide_page.dart index 0a697c0a2e..70b7b0ba53 100644 --- a/lib/tools/ui/styleguide/styleguide_page.dart +++ b/lib/tools/ui/styleguide/styleguide_page.dart @@ -13,10 +13,8 @@ import 'package:titan/tools/ui/styleguide/list_item.dart'; import 'package:titan/tools/ui/styleguide/list_item_template.dart'; import 'package:titan/tools/ui/styleguide/list_item_toggle.dart'; import 'package:titan/tools/ui/styleguide/navbar.dart'; -import 'package:titan/tools/ui/styleguide/router.dart'; import 'package:titan/tools/ui/styleguide/searchbar.dart'; import 'package:titan/tools/ui/styleguide/text_entry.dart'; -import 'package:titan/tools/ui/widgets/top_bar.dart'; class StyleGuidePage extends HookConsumerWidget { const StyleGuidePage({super.key}); @@ -47,7 +45,6 @@ class StyleGuidePage extends HookConsumerWidget { return SafeArea( child: Column( children: [ - TopBar(title: "Style Guide", root: StyleGuideRouter.root), Expanded( child: SingleChildScrollView( child: Padding( diff --git a/lib/tools/ui/widgets/calendar.dart b/lib/tools/ui/widgets/calendar.dart index 70b2df7288..62d10611df 100644 --- a/lib/tools/ui/widgets/calendar.dart +++ b/lib/tools/ui/widgets/calendar.dart @@ -2,7 +2,7 @@ import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/drawer/providers/is_web_format_provider.dart'; +import 'package:titan/navigation/providers/is_web_format_provider.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; diff --git a/lib/tools/ui/widgets/top_bar.dart b/lib/tools/ui/widgets/top_bar.dart deleted file mode 100644 index ed7e5badd0..0000000000 --- a/lib/tools/ui/widgets/top_bar.dart +++ /dev/null @@ -1,93 +0,0 @@ -import 'package:auto_size_text/auto_size_text.dart'; -import 'package:flutter/material.dart'; -import 'package:heroicons/heroicons.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/drawer/class/top_bar_callback.dart'; -import 'package:titan/drawer/providers/animation_provider.dart'; -import 'package:titan/drawer/providers/swipe_provider.dart'; -import 'package:titan/drawer/providers/top_bar_callback_provider.dart'; -import 'package:qlevar_router/qlevar_router.dart'; - -class TopBar extends HookConsumerWidget { - final String title; - final String root; - final VoidCallback? onMenu; - final VoidCallback? onBack; - final Widget? rightIcon; - final TextStyle? textStyle; - const TopBar({ - super.key, - required this.title, - required this.root, - this.onMenu, - this.onBack, - this.rightIcon, - this.textStyle, - }); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final animation = ref.watch(animationProvider); - final topBarCallBackNotifier = ref.watch(topBarCallBackProvider.notifier); - Future(() { - topBarCallBackNotifier.setCallBacks( - TopBarCallback(moduleRoot: root, onMenu: onMenu, onBack: onBack), - ); - }); - return Column( - children: [ - const SizedBox(height: 15), - Row( - children: [ - SizedBox( - width: 70, - child: Builder( - builder: (BuildContext appBarContext) { - return IconButton( - onPressed: () { - if (QR.currentPath == root) { - if (animation != null) { - final controllerNotifier = ref.watch( - swipeControllerProvider(animation).notifier, - ); - controllerNotifier.toggle(); - onMenu?.call(); - } - } else { - QR.back(); - onBack?.call(); - } - }, - icon: HeroIcon( - QR.currentPath == root - ? HeroIcons.bars3BottomLeft - : HeroIcons.chevronLeft, - color: textStyle?.color ?? Colors.black, - size: 30, - ), - ); - }, - ), - ), - Expanded( - child: Center( - child: AutoSizeText( - title, - maxLines: 1, - style: - textStyle ?? - const TextStyle( - fontSize: 40, - fontWeight: FontWeight.w700, - color: Colors.black, - ), - ), - ), - ), - SizedBox(width: 70, child: rightIcon), - ], - ), - ], - ); - } -} diff --git a/lib/vote/router.dart b/lib/vote/router.dart index e6ca8b0e72..0737895259 100644 --- a/lib/vote/router.dart +++ b/lib/vote/router.dart @@ -1,7 +1,7 @@ import 'package:either_dart/either.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:heroicons/heroicons.dart'; -import 'package:titan/drawer/class/module.dart'; +import 'package:titan/navigation/class/module.dart'; import 'package:titan/tools/middlewares/admin_middleware.dart'; import 'package:titan/tools/middlewares/authenticated_middleware.dart'; import 'package:titan/tools/middlewares/deferred_middleware.dart'; @@ -27,6 +27,7 @@ class VoteRouter { static const String detail = '/detail'; static final Module module = Module( name: "Vote", + description: "Gérer les votes, les sections et les candidats", icon: const Left(HeroIcons.envelopeOpen), root: VoteRouter.root, selected: false, diff --git a/lib/vote/ui/vote.dart b/lib/vote/ui/vote.dart index dfb41af8f6..44e9d75a14 100644 --- a/lib/vote/ui/vote.dart +++ b/lib/vote/ui/vote.dart @@ -1,7 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:titan/tools/ui/widgets/top_bar.dart'; -import 'package:titan/vote/router.dart'; -import 'package:titan/vote/tools/constants.dart'; class VoteTemplate extends StatelessWidget { final Widget child; @@ -9,13 +6,6 @@ class VoteTemplate extends StatelessWidget { @override Widget build(BuildContext context) { - return SafeArea( - child: Column( - children: [ - const TopBar(title: VoteTextConstants.vote, root: VoteRouter.root), - Expanded(child: child), - ], - ), - ); + return child; } } diff --git a/test/drawer/drawer_test.dart b/test/drawer/drawer_test.dart deleted file mode 100644 index 86e532db25..0000000000 --- a/test/drawer/drawer_test.dart +++ /dev/null @@ -1,32 +0,0 @@ -import 'package:either_dart/either.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:heroicons/heroicons.dart'; -import 'package:titan/drawer/class/module.dart'; - -void main() { - group('Module', () { - test( - 'copy method should return a new Module object with updated properties', - () { - final module = Module( - name: 'Calendar', - icon: const Left(HeroIcons.calendar), - selected: true, - root: '', - ); - - final copiedModule = module.copy( - name: 'Settings', - icon: const Left(HeroIcons.cog), - selected: false, - root: '/test', - ); - - expect(copiedModule.name, 'Settings'); - expect(copiedModule.icon, const Left(HeroIcons.cog)); - expect(copiedModule.root, '/test'); - expect(copiedModule.selected, false); - }, - ); - }); -} diff --git a/test/drawer/swipe_provider_test.dart b/test/drawer/swipe_provider_test.dart deleted file mode 100644 index febd2fa0d9..0000000000 --- a/test/drawer/swipe_provider_test.dart +++ /dev/null @@ -1,41 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/scheduler.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:titan/drawer/providers/swipe_provider.dart'; - -class MockTicker extends TickerProvider { - @override - Ticker createTicker(TickerCallback onTick) { - return Ticker(onTick); - } -} - -void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - group('SwipeControllerNotifier', () { - test('SwipeControllerNotifier can close animation controller', () { - final controller = AnimationController( - vsync: MockTicker(), - duration: const Duration(microseconds: 1), - ); - final swipeController = SwipeControllerNotifier(controller); - swipeController.close(); - Future.delayed(const Duration(milliseconds: 1), () { - expect(controller.value, equals(0)); - }); - }); - - test('SwipeControllerNotifier can detect drag start from left', () { - final controller = AnimationController( - vsync: MockTicker(), - duration: const Duration(seconds: 1), - ); - final swipeController = SwipeControllerNotifier(controller); - final startDetails = DragStartDetails( - globalPosition: const Offset(50, 0), - ); - swipeController.onDragStart(startDetails); - expect(SwipeControllerNotifier.shouldDrag, equals(true)); - }); - }); -} From e7f5e68e48b560da4f9164a5e27ed5ef8e26a367 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sat, 28 Jun 2025 23:41:49 +0200 Subject: [PATCH 015/473] wip: smart navbar --- lib/admin/router.dart | 2 - lib/advert/router.dart | 2 - lib/amap/router.dart | 2 - lib/booking/router.dart | 2 - lib/centralisation/router.dart | 2 - lib/cinema/router.dart | 2 - lib/event/router.dart | 2 - lib/flappybird/router.dart | 2 - lib/home/router.dart | 2 - lib/loan/router.dart | 2 - lib/navigation/class/module.dart | 34 ------ .../providers/modules_provider.dart | 27 ----- .../providers/navbar_module_list.dart | 34 ++++++ lib/navigation/ui/all_module_page.dart | 16 +-- lib/navigation/ui/drawer_template.dart | 16 ++- lib/paiement/router.dart | 2 - lib/ph/router.dart | 2 - lib/phonebook/router.dart | 2 - lib/purchases/router.dart | 2 - lib/raffle/router.dart | 2 - lib/recommendation/router.dart | 2 - lib/seed-library/router.dart | 2 - .../providers/module_list_provider.dart | 16 ++- lib/settings/router.dart | 2 - .../ui/pages/modules_page/modules_page.dart | 2 - lib/tools/ui/styleguide/navbar.dart | 108 ++++++++++++++---- lib/tools/ui/styleguide/router.dart | 2 - lib/tools/ui/styleguide/styleguide_page.dart | 68 +++++------ lib/vote/router.dart | 2 - 29 files changed, 187 insertions(+), 174 deletions(-) delete mode 100644 lib/navigation/providers/modules_provider.dart create mode 100644 lib/navigation/providers/navbar_module_list.dart diff --git a/lib/admin/router.dart b/lib/admin/router.dart index b3c53169e5..70716ab57e 100644 --- a/lib/admin/router.dart +++ b/lib/admin/router.dart @@ -56,9 +56,7 @@ class AdminRouter { static final Module module = Module( name: "Administration", description: "Gérer les groupes, écoles et structures", - icon: const Left(HeroIcons.users), root: AdminRouter.root, - selected: false, ); AdminRouter(this.ref); diff --git a/lib/advert/router.dart b/lib/advert/router.dart index 5d4df3e606..e91fb55843 100644 --- a/lib/advert/router.dart +++ b/lib/advert/router.dart @@ -29,9 +29,7 @@ class AdvertRouter { static final Module module = Module( name: "Annonce", description: "Gérer les annonces et les annonceurs", - icon: const Left(HeroIcons.megaphone), root: AdvertRouter.root, - selected: false, ); AdvertRouter(this.ref); diff --git a/lib/amap/router.dart b/lib/amap/router.dart index 1fadf24bc2..82c998a177 100644 --- a/lib/amap/router.dart +++ b/lib/amap/router.dart @@ -37,9 +37,7 @@ class AmapRouter { static final Module module = Module( name: "Amap", description: "Gérer les livraisons et les produits", - icon: const Left(HeroIcons.shoppingCart), root: AmapRouter.root, - selected: false, ); AmapRouter(this.ref); diff --git a/lib/booking/router.dart b/lib/booking/router.dart index 8315982e6c..46ab7b1c87 100644 --- a/lib/booking/router.dart +++ b/lib/booking/router.dart @@ -34,9 +34,7 @@ class BookingRouter { static final Module module = Module( name: "Réservation", description: "Gérer les réservations, les salles et les managers", - icon: const Left(HeroIcons.tableCells), root: BookingRouter.root, - selected: false, ); BookingRouter(this.ref); diff --git a/lib/centralisation/router.dart b/lib/centralisation/router.dart index ee9e165f11..3e744ca64f 100644 --- a/lib/centralisation/router.dart +++ b/lib/centralisation/router.dart @@ -15,9 +15,7 @@ class CentralisationRouter { static final Module module = Module( description: "Gérer la centralisation des données", name: CentralisationTextConstants.centralisation, - icon: const Left(HeroIcons.link), root: CentralisationRouter.root, - selected: false, ); CentralisationRouter(this.ref); diff --git a/lib/cinema/router.dart b/lib/cinema/router.dart index f92b307a92..9948e3b2a9 100644 --- a/lib/cinema/router.dart +++ b/lib/cinema/router.dart @@ -26,9 +26,7 @@ class CinemaRouter { static final Module module = Module( name: "Cinéma", description: "Gérer les séances de cinéma", - icon: const Left(HeroIcons.ticket), root: CinemaRouter.root, - selected: false, ); CinemaRouter(this.ref); diff --git a/lib/event/router.dart b/lib/event/router.dart index 56a77e5e06..557d08babb 100644 --- a/lib/event/router.dart +++ b/lib/event/router.dart @@ -25,9 +25,7 @@ class EventRouter { static final Module module = Module( name: "Évenements", description: "Gérer les événements et les participants", - icon: const Left(HeroIcons.calendar), root: EventRouter.root, - selected: false, ); EventRouter(this.ref); diff --git a/lib/flappybird/router.dart b/lib/flappybird/router.dart index 1150438458..05d5ad0019 100644 --- a/lib/flappybird/router.dart +++ b/lib/flappybird/router.dart @@ -16,9 +16,7 @@ class FlappyBirdRouter { static final Module module = Module( name: "FlappyBird", description: "Jouer à Flappy Bird et consulter le classement", - icon: const Right("assets/images/logo_flappybird.svg"), root: FlappyBirdRouter.root, - selected: false, ); FlappyBirdRouter(this.ref); diff --git a/lib/home/router.dart b/lib/home/router.dart index 09fc0ae297..2d1b5c4823 100644 --- a/lib/home/router.dart +++ b/lib/home/router.dart @@ -16,9 +16,7 @@ class HomeRouter { static final Module module = Module( name: "Calendrier", description: "Consulter les événements et les activités", - icon: const Left(HeroIcons.calendarDays), root: HomeRouter.root, - selected: false, ); HomeRouter(this.ref); diff --git a/lib/loan/router.dart b/lib/loan/router.dart index 6771e38801..0ca20e2cdc 100644 --- a/lib/loan/router.dart +++ b/lib/loan/router.dart @@ -28,9 +28,7 @@ class LoanRouter { static final Module module = Module( name: "Prêt", description: "Gérer les prêts et les articles", - icon: const Left(HeroIcons.buildingLibrary), root: LoanRouter.root, - selected: false, ); LoanRouter(this.ref); diff --git a/lib/navigation/class/module.dart b/lib/navigation/class/module.dart index 4a2b8c7496..fd2556a4e1 100644 --- a/lib/navigation/class/module.dart +++ b/lib/navigation/class/module.dart @@ -1,35 +1,15 @@ import 'package:either_dart/either.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; import 'package:heroicons/heroicons.dart'; -enum ModuleType { - calendar, - settings, - amap, - loan, - booking, - admin, - event, - vote, - tombola, - cinema, - paiement, -} - class Module { String name; String description; - Either icon; String root; - bool selected; Module({ required this.name, required this.description, - required this.icon, required this.root, - required this.selected, }); Module copy({ @@ -41,20 +21,6 @@ class Module { }) => Module( name: name ?? this.name, description: description ?? this.description, - icon: icon ?? this.icon, root: root ?? this.root, - selected: selected ?? this.selected, ); - - Widget getIcon(Color color, {double size = 30}) { - return icon.fold( - (heroIcon) => HeroIcon(heroIcon, color: color, size: size), - (svgPath) => SvgPicture.asset( - svgPath, - width: size, - height: size, - colorFilter: ColorFilter.mode(color, BlendMode.srcIn), - ), - ); - } } diff --git a/lib/navigation/providers/modules_provider.dart b/lib/navigation/providers/modules_provider.dart deleted file mode 100644 index 6a141f447a..0000000000 --- a/lib/navigation/providers/modules_provider.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/navigation/class/module.dart'; -import 'package:titan/settings/providers/module_list_provider.dart'; - -class ModuleListNotifier extends StateNotifier> { - ModuleListNotifier(super.listModule); - - void select(int i) { - List r = state.sublist(0); - - for (int j = 0; j < r.length; j++) { - if (i == j) { - r[i].selected = true; - } else { - r[j].selected = false; - } - } - - state = r; - } -} - -final listModuleProvider = - StateNotifierProvider>((ref) { - final modules = ref.watch(modulesProvider); - return ModuleListNotifier(modules); - }); diff --git a/lib/navigation/providers/navbar_module_list.dart b/lib/navigation/providers/navbar_module_list.dart new file mode 100644 index 0000000000..2054ca3cd8 --- /dev/null +++ b/lib/navigation/providers/navbar_module_list.dart @@ -0,0 +1,34 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/navigation/class/module.dart'; +import 'package:titan/settings/providers/module_list_provider.dart'; + +class ModuleListNotifier extends StateNotifier> { + final int maxNumberOfModules = 3; + ModuleListNotifier(List modules) + : listModule = List.from(modules), + super(modules.take(3).toList()); + + final List listModule; + + void pushModule(Module module) { + final existingIndex = listModule.indexWhere((m) => m.root == module.root); + + if (existingIndex == -1) { + return; + } + + if (existingIndex < maxNumberOfModules) { + return; + } + + listModule.removeAt(existingIndex); + listModule.insert(0, module); + state = listModule.take(maxNumberOfModules).toList(); + } +} + +final navbarListModuleProvider = + StateNotifierProvider>((ref) { + final modules = ref.watch(modulesProvider); + return ModuleListNotifier(modules); + }); diff --git a/lib/navigation/ui/all_module_page.dart b/lib/navigation/ui/all_module_page.dart index fd04384286..99758c007b 100644 --- a/lib/navigation/ui/all_module_page.dart +++ b/lib/navigation/ui/all_module_page.dart @@ -3,7 +3,8 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/admin/providers/is_admin_provider.dart'; import 'package:titan/admin/router.dart'; -import 'package:titan/navigation/providers/modules_provider.dart'; +import 'package:titan/navigation/providers/navbar_module_list.dart'; +import 'package:titan/settings/providers/module_list_provider.dart'; import 'package:titan/settings/router.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/providers/path_forwarding_provider.dart'; @@ -15,8 +16,10 @@ class AllModulePage extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final modules = ref.watch(listModuleProvider); - final isAdmin = ref.watch(isAdminProvider); + final modules = ref.watch(modulesProvider); + final navbarListModuleNotifier = ref.watch( + navbarListModuleProvider.notifier, + ); return Container( color: ColorConstants.background, padding: const EdgeInsets.symmetric(horizontal: 20.0), @@ -24,15 +27,12 @@ class AllModulePage extends ConsumerWidget { children: [ CustomSearchBar(onSearch: (String query) {}, onFilter: () {}), SizedBox(height: 30), - ...[ - ...modules, - SettingsRouter.module, - if (isAdmin) AdminRouter.module, - ].map( + ...modules.map( (module) => ListItem( title: module.name, subtitle: module.description, onTap: () { + navbarListModuleNotifier.pushModule(module); final pathForwardingNotifier = ref.watch( pathForwardingProvider.notifier, ); diff --git a/lib/navigation/ui/drawer_template.dart b/lib/navigation/ui/drawer_template.dart index 4aa0f391e9..891bf1d6b6 100644 --- a/lib/navigation/ui/drawer_template.dart +++ b/lib/navigation/ui/drawer_template.dart @@ -2,12 +2,14 @@ import 'package:flutter/material.dart'; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/navigation/class/module.dart'; import 'package:titan/navigation/providers/display_quit_popup.dart'; -import 'package:titan/navigation/providers/modules_provider.dart'; +import 'package:titan/navigation/providers/navbar_module_list.dart'; import 'package:titan/navigation/providers/should_setup_provider.dart'; import 'package:titan/router.dart'; import 'package:titan/service/tools/setup.dart'; import 'package:titan/navigation/ui/quit_dialog.dart'; +import 'package:titan/settings/providers/module_list_provider.dart'; import 'package:titan/tools/providers/path_forwarding_provider.dart'; import 'package:titan/tools/ui/styleguide/navbar.dart'; import 'package:titan/user/providers/user_provider.dart'; @@ -25,7 +27,10 @@ class DrawerTemplate extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final user = ref.watch(userProvider); - final modules = ref.watch(listModuleProvider); + final navbarListModule = ref.watch(navbarListModuleProvider); + final navbarListModuleNotifier = ref.watch( + navbarListModuleProvider.notifier, + ); final displayQuit = ref.watch(displayQuitProvider); final shouldSetup = ref.watch(shouldSetupProvider); final shouldSetupNotifier = ref.read(shouldSetupProvider.notifier); @@ -71,10 +76,11 @@ class DrawerTemplate extends HookConsumerWidget { right: 0, child: FloatingNavbar( items: [ - ...modules.sublist(0, 3).map((module) { + ...navbarListModule.map((module) { return FloatingNavbarItem( - title: module.name, + module: module, onTap: () { + navbarListModuleNotifier.pushModule(module); final pathForwardingNotifier = ref.watch( pathForwardingProvider.notifier, ); @@ -84,7 +90,7 @@ class DrawerTemplate extends HookConsumerWidget { ); }), FloatingNavbarItem( - title: 'Autres', + module: Module(name: 'Autres', description: '', root: AppRouter.allModules), onTap: () { final pathForwardingNotifier = ref.watch( pathForwardingProvider.notifier, diff --git a/lib/paiement/router.dart b/lib/paiement/router.dart index 76a22e47f9..15cb7d168e 100644 --- a/lib/paiement/router.dart +++ b/lib/paiement/router.dart @@ -40,9 +40,7 @@ class PaymentRouter { static final Module module = Module( name: "MyECLPay", description: "Gérer les paiements, les statistiques et les appareils", - icon: const Left(HeroIcons.creditCard), root: PaymentRouter.root, - selected: false, ); PaymentRouter(this.ref); diff --git a/lib/ph/router.dart b/lib/ph/router.dart index 0aaf08ce26..2f1ac69705 100644 --- a/lib/ph/router.dart +++ b/lib/ph/router.dart @@ -28,9 +28,7 @@ class PhRouter { static final Module module = Module( name: "PH", description: "Gérer les PH, les formulaires et les administrateurs", - icon: const Left(HeroIcons.newspaper), root: PhRouter.root, - selected: false, ); PhRouter(this.ref); QRoute route() => QRoute( diff --git a/lib/phonebook/router.dart b/lib/phonebook/router.dart index f8feb9ba3c..c0ce1210cb 100644 --- a/lib/phonebook/router.dart +++ b/lib/phonebook/router.dart @@ -26,9 +26,7 @@ class PhonebookRouter { static final Module module = Module( name: "Annuaire", description: "Gérer les associations, les membres et les administrateurs", - icon: const Left(HeroIcons.phone), root: PhonebookRouter.root, - selected: false, ); PhonebookRouter(this.ref); diff --git a/lib/purchases/router.dart b/lib/purchases/router.dart index 438a3a4663..b68c451e17 100644 --- a/lib/purchases/router.dart +++ b/lib/purchases/router.dart @@ -24,9 +24,7 @@ class PurchasesRouter { static final Module module = Module( name: "Achats", description: "Gérer les achats, les tickets et l'historique", - icon: const Left(HeroIcons.shoppingBag), root: PurchasesRouter.root, - selected: false, ); PurchasesRouter(this.ref); diff --git a/lib/raffle/router.dart b/lib/raffle/router.dart index ca0dd578e9..b68df0b767 100644 --- a/lib/raffle/router.dart +++ b/lib/raffle/router.dart @@ -31,9 +31,7 @@ class RaffleRouter { static final Module module = Module( name: "Tombola", description: "Gérer les tombolas, les prix et les tickets", - icon: const Left(HeroIcons.gift), root: RaffleRouter.root, - selected: false, ); RaffleRouter(this.ref); QRoute route() => QRoute( diff --git a/lib/recommendation/router.dart b/lib/recommendation/router.dart index 14e1a8c750..e5f9af6854 100644 --- a/lib/recommendation/router.dart +++ b/lib/recommendation/router.dart @@ -23,9 +23,7 @@ class RecommendationRouter { static final Module module = Module( name: "Bons plans", description: "Gérer les recommandations, les informations et les administrateurs", - icon: const Left(HeroIcons.currencyEuro), root: RecommendationRouter.root, - selected: false, ); RecommendationRouter(this.ref); diff --git a/lib/seed-library/router.dart b/lib/seed-library/router.dart index bb2d1047eb..72db114ced 100644 --- a/lib/seed-library/router.dart +++ b/lib/seed-library/router.dart @@ -50,9 +50,7 @@ class SeedLibraryRouter { static final Module module = Module( name: "Grainothèque", description: "Gérer les graines, les espèces et les stocks", - icon: const Left(HeroIcons.inboxStack), root: SeedLibraryRouter.root, - selected: false, ); QRoute route() => QRoute( diff --git a/lib/settings/providers/module_list_provider.dart b/lib/settings/providers/module_list_provider.dart index 3f7063056e..e721af3f94 100644 --- a/lib/settings/providers/module_list_provider.dart +++ b/lib/settings/providers/module_list_provider.dart @@ -1,5 +1,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/admin/providers/is_admin_provider.dart'; import 'package:titan/admin/router.dart'; import 'package:titan/advert/router.dart'; import 'package:titan/admin/providers/all_my_module_roots_list_provider.dart'; @@ -18,7 +19,9 @@ import 'package:titan/ph/router.dart'; import 'package:titan/purchases/router.dart'; import 'package:titan/raffle/router.dart'; import 'package:titan/recommendation/router.dart'; +import 'package:titan/router.dart'; import 'package:titan/seed-library/router.dart'; +import 'package:titan/settings/router.dart'; import 'package:titan/vote/router.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -30,7 +33,9 @@ final modulesProvider = StateNotifierProvider>(( .map((root) => '/$root') .toList(); - ModulesNotifier modulesNotifier = ModulesNotifier(); + final isAdmin = ref.watch(isAdminProvider); + + ModulesNotifier modulesNotifier = ModulesNotifier(isAdmin: isAdmin); modulesNotifier.loadModules(myModulesRoot); return modulesNotifier; }); @@ -38,6 +43,7 @@ final modulesProvider = StateNotifierProvider>(( class ModulesNotifier extends StateNotifier> { String dbModule = "modules"; String dbAllModules = "allModules"; + final bool isAdmin; final eq = const DeepCollectionEquality.unordered(); List allModules = [ HomeRouter.module, @@ -57,7 +63,7 @@ class ModulesNotifier extends StateNotifier> { // VoteRouter.module, // SeedLibraryRouter.module, ]; - ModulesNotifier() : super([]); + ModulesNotifier({required this.isAdmin}) : super([]); void saveModules() { SharedPreferences.getInstance().then((prefs) { @@ -121,6 +127,12 @@ class ModulesNotifier extends StateNotifier> { for (Module module in toDelete) { allModules.remove(module); } + allModules.addAll( + [ + SettingsRouter.module, + if (isAdmin) AdminRouter.module, + ] + ); state = allModules; } diff --git a/lib/settings/router.dart b/lib/settings/router.dart index e58a8dca63..c3d265e966 100644 --- a/lib/settings/router.dart +++ b/lib/settings/router.dart @@ -31,9 +31,7 @@ class SettingsRouter { static final Module module = Module( name: "Paramètres", description: "Gérer les paramètres de l'application", - icon: const Left(HeroIcons.cog), root: AdminRouter.root, - selected: false, ); SettingsRouter(this.ref); diff --git a/lib/settings/ui/pages/modules_page/modules_page.dart b/lib/settings/ui/pages/modules_page/modules_page.dart index 4a2dbec914..c93e24e80d 100644 --- a/lib/settings/ui/pages/modules_page/modules_page.dart +++ b/lib/settings/ui/pages/modules_page/modules_page.dart @@ -45,8 +45,6 @@ class ModulesPage extends HookConsumerWidget { padding: const EdgeInsets.symmetric(vertical: 5), child: Row( children: [ - module.getIcon(Colors.grey.shade700), - const SizedBox(width: 20), Text( module.name, style: const TextStyle( diff --git a/lib/tools/ui/styleguide/navbar.dart b/lib/tools/ui/styleguide/navbar.dart index 5c2504ceb1..7080053128 100644 --- a/lib/tools/ui/styleguide/navbar.dart +++ b/lib/tools/ui/styleguide/navbar.dart @@ -1,21 +1,71 @@ import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/navigation/class/module.dart'; import 'package:titan/tools/constants.dart'; +import 'package:titan/tools/providers/path_forwarding_provider.dart'; class FloatingNavbarItem { final Function()? onTap; - final String title; + final Module module; - FloatingNavbarItem({this.onTap, required this.title}); + FloatingNavbarItem({this.onTap, required this.module}); } -class FloatingNavbar extends HookWidget { +// Get the current route path +String getCurrentPath() { + if (QR.history.isEmpty) return ''; + + String currentPath = QR.history.last.path; + final parts = currentPath.split('/'); + if (parts.length > 1 && parts[1].isNotEmpty) { + return '/${parts[1]}'; + } + return ''; +} + +class FloatingNavbar extends HookConsumerWidget { final List items; const FloatingNavbar({super.key, required this.items}); @override - Widget build(BuildContext context) { - final currentIndex = useState(0); + Widget build(BuildContext context, WidgetRef ref) { + final pathProvider = ref.watch(pathForwardingProvider); + // Track previous index for animation + final previousIndex = useRef(0); + // Use useState to maintain internal state for animation + final currentState = useState(3); // Default to "Autres" + + // Get the path from router + final currentPath = pathProvider.path; + + // Calculate the selected index based on the current path + final routeIndex = useState(3); + + useEffect(() { + // This effect runs on every build, but we only care about the initial path + if (currentPath.isNotEmpty) { + print("Current path: $currentPath"); + print("items ${items.map((e) => e.module.root).toList()}"); + routeIndex.value = items.indexWhere((item) => item.module.root == currentPath); + // Only use found index if it's in visible range + if (routeIndex.value < 0 || routeIndex.value >= items.length) { + routeIndex.value = 3; // No match or not in visible modules + } + } + print("Selected index: $routeIndex"); + return null; + }, [currentPath]); + + + // Initialize the currentState on first render only + useEffect(() { + currentState.value = routeIndex.value; + previousIndex.value = routeIndex.value; // Initialize previous index + return null; + }, []); + final borderRadius = 25.0; // Animation controller for all animations @@ -26,20 +76,29 @@ class FloatingNavbar extends HookWidget { // Slide animation reference final slideAnimation = useRef?>(null); - // Track previous index for animation - final previousIndex = useRef( - 0, - ); // Store the latest calculated item width to use in animations + // Store the latest calculated item width to use in animations final itemWidthRef = useRef(0.0); + // Watch for path changes and update the selection with proper animation + useEffect(() { + if (currentPath.isNotEmpty && routeIndex.value != currentState.value) { + // When path changes, store current selection as previous + previousIndex.value = currentState.value; + // Then update to the new route-based selection + currentState.value = routeIndex.value; + } + return null; + }, [currentPath, routeIndex]); + // Update animation when index changes - this needs to be in the build method, not in LayoutBuilder useEffect(() { - if (previousIndex.value != currentIndex.value && itemWidthRef.value > 0) { + // Only trigger animation if we have a valid item width and the index has changed + if (previousIndex.value != currentState.value && itemWidthRef.value > 0) { // Create tween from previous to current position slideAnimation.value = Tween( begin: previousIndex.value * itemWidthRef.value, - end: currentIndex.value * itemWidthRef.value, + end: currentState.value * itemWidthRef.value, ).animate( CurvedAnimation( parent: animationController, @@ -50,12 +109,9 @@ class FloatingNavbar extends HookWidget { // Reset and start animation animationController.reset(); animationController.forward(); - - // Store current index as previous for next change - previousIndex.value = currentIndex.value; } return null; - }, [currentIndex.value, itemWidthRef.value]); + }, [currentState.value, itemWidthRef.value]); // Use LayoutBuilder for proper sizing return Padding( @@ -89,7 +145,7 @@ class FloatingNavbar extends HookWidget { // Get current position from slide animation or fall back to current index final leftPosition = slideAnimation.value != null ? slideAnimation.value!.value - : itemWidth * currentIndex.value; + : itemWidth * currentState.value; return Positioned( left: leftPosition, @@ -111,7 +167,7 @@ class FloatingNavbar extends HookWidget { children: items.asMap().entries.map((entry) { final index = entry.key; final item = entry.value; - final isSelected = index == currentIndex.value; + final isSelected = index == currentState.value; // Use AnimatedBuilder for text color to sync with indicator animation return Expanded( @@ -119,9 +175,17 @@ class FloatingNavbar extends HookWidget { color: Colors.transparent, borderRadius: BorderRadius.circular(borderRadius), child: GestureDetector( + behavior: HitTestBehavior.opaque, onTap: () { + // Only animate if this isn't already the selected item + if (index != currentState.value) { + // Store current index as previous for animation + previousIndex.value = currentState.value; + // Update the internal state immediately for animation + currentState.value = index; + } + // Then call the callback item.onTap?.call(); - currentIndex.value = index; }, child: Container( padding: const EdgeInsets.all(8), @@ -133,7 +197,7 @@ class FloatingNavbar extends HookWidget { FontWeight textWeight; if (previousIndex.value == - currentIndex.value) { + currentState.value) { // No transition happening textColor = isSelected ? ColorConstants.main @@ -145,13 +209,13 @@ class FloatingNavbar extends HookWidget { // During transition, determine if this item is involved bool isInvolved = index == previousIndex.value || - index == currentIndex.value; + index == currentState.value; if (!isInvolved) { // Not involved in transition textColor = ColorConstants.background; textWeight = FontWeight.normal; - } else if (index == currentIndex.value) { + } else if (index == currentState.value) { // Transitioning to selected final progress = animationController.value; @@ -182,7 +246,7 @@ class FloatingNavbar extends HookWidget { return Center( child: AutoSizeText( - item.title, + item.module.name, style: TextStyle( color: textColor, fontSize: 14, diff --git a/lib/tools/ui/styleguide/router.dart b/lib/tools/ui/styleguide/router.dart index a04064b840..81f7073cd4 100644 --- a/lib/tools/ui/styleguide/router.dart +++ b/lib/tools/ui/styleguide/router.dart @@ -11,9 +11,7 @@ class StyleGuideRouter { static final Module module = Module( name: "Style Guide", description: "Explore the UI components and styles used in Titan", - icon: const Left(HeroIcons.paintBrush), root: StyleGuideRouter.root, - selected: false, ); StyleGuideRouter(this.ref); QRoute route() => QRoute( diff --git a/lib/tools/ui/styleguide/styleguide_page.dart b/lib/tools/ui/styleguide/styleguide_page.dart index 70b7b0ba53..e88f94e88b 100644 --- a/lib/tools/ui/styleguide/styleguide_page.dart +++ b/lib/tools/ui/styleguide/styleguide_page.dart @@ -78,40 +78,40 @@ class StyleGuidePage extends HookConsumerWidget { ), child: FloatingNavbar( items: [ - FloatingNavbarItem( - title: 'Home', - onTap: () { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Home tapped')), - ); - }, - ), - FloatingNavbarItem( - title: 'Search', - onTap: () { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Search tapped')), - ); - }, - ), - FloatingNavbarItem( - title: 'Favorites', - onTap: () { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Favorites tapped'), - ), - ); - }, - ), - FloatingNavbarItem( - title: 'Profile', - onTap: () { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Profile tapped')), - ); - }, - ), + // FloatingNavbarItem( + // title: 'Home', + // onTap: () { + // ScaffoldMessenger.of(context).showSnackBar( + // const SnackBar(content: Text('Home tapped')), + // ); + // }, + // ), + // FloatingNavbarItem( + // title: 'Search', + // onTap: () { + // ScaffoldMessenger.of(context).showSnackBar( + // const SnackBar(content: Text('Search tapped')), + // ); + // }, + // ), + // FloatingNavbarItem( + // title: 'Favorites', + // onTap: () { + // ScaffoldMessenger.of(context).showSnackBar( + // const SnackBar( + // content: Text('Favorites tapped'), + // ), + // ); + // }, + // ), + // FloatingNavbarItem( + // title: 'Profile', + // onTap: () { + // ScaffoldMessenger.of(context).showSnackBar( + // const SnackBar(content: Text('Profile tapped')), + // ); + // }, + // ), ], ), ), diff --git a/lib/vote/router.dart b/lib/vote/router.dart index 0737895259..90ea88e8d9 100644 --- a/lib/vote/router.dart +++ b/lib/vote/router.dart @@ -28,9 +28,7 @@ class VoteRouter { static final Module module = Module( name: "Vote", description: "Gérer les votes, les sections et les candidats", - icon: const Left(HeroIcons.envelopeOpen), root: VoteRouter.root, - selected: false, ); VoteRouter(this.ref); From d02816e2c7919e7166aa2b039177206471805b79 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Tue, 1 Jul 2025 18:45:50 +0200 Subject: [PATCH 016/473] fix: navbar --- lib/navigation/ui/drawer_template.dart | 29 +++++++++++++++++----- lib/tools/ui/styleguide/navbar.dart | 34 ++++++++++++++++++++++---- 2 files changed, 52 insertions(+), 11 deletions(-) diff --git a/lib/navigation/ui/drawer_template.dart b/lib/navigation/ui/drawer_template.dart index 891bf1d6b6..9585a79d0a 100644 --- a/lib/navigation/ui/drawer_template.dart +++ b/lib/navigation/ui/drawer_template.dart @@ -9,7 +9,6 @@ import 'package:titan/navigation/providers/should_setup_provider.dart'; import 'package:titan/router.dart'; import 'package:titan/service/tools/setup.dart'; import 'package:titan/navigation/ui/quit_dialog.dart'; -import 'package:titan/settings/providers/module_list_provider.dart'; import 'package:titan/tools/providers/path_forwarding_provider.dart'; import 'package:titan/tools/ui/styleguide/navbar.dart'; import 'package:titan/user/providers/user_provider.dart'; @@ -80,23 +79,41 @@ class DrawerTemplate extends HookConsumerWidget { return FloatingNavbarItem( module: module, onTap: () { + // Use ref.read instead of ref.watch to avoid rebuilds navbarListModuleNotifier.pushModule(module); - final pathForwardingNotifier = ref.watch( + final pathForwardingNotifier = ref.read( pathForwardingProvider.notifier, ); + + // First update the path pathForwardingNotifier.forward(module.root); - QR.to(module.root); + + // Then navigate with a small delay to allow the UI to stabilize + WidgetsBinding.instance.addPostFrameCallback((_) { + QR.to(module.root); + }); }, ); }), FloatingNavbarItem( - module: Module(name: 'Autres', description: '', root: AppRouter.allModules), + module: Module( + name: 'Autres', + description: '', + root: AppRouter.allModules, + ), onTap: () { - final pathForwardingNotifier = ref.watch( + // Use ref.read instead of ref.watch to avoid rebuilds + final pathForwardingNotifier = ref.read( pathForwardingProvider.notifier, ); + + // First update the path pathForwardingNotifier.forward(AppRouter.allModules); - QR.to(AppRouter.allModules); + + // Then navigate with a small delay to allow the UI to stabilize + WidgetsBinding.instance.addPostFrameCallback((_) { + QR.to(AppRouter.allModules); + }); }, ), ], diff --git a/lib/tools/ui/styleguide/navbar.dart b/lib/tools/ui/styleguide/navbar.dart index 7080053128..cbe258f2b1 100644 --- a/lib/tools/ui/styleguide/navbar.dart +++ b/lib/tools/ui/styleguide/navbar.dart @@ -48,7 +48,9 @@ class FloatingNavbar extends HookConsumerWidget { if (currentPath.isNotEmpty) { print("Current path: $currentPath"); print("items ${items.map((e) => e.module.root).toList()}"); - routeIndex.value = items.indexWhere((item) => item.module.root == currentPath); + routeIndex.value = items.indexWhere( + (item) => item.module.root == currentPath, + ); // Only use found index if it's in visible range if (routeIndex.value < 0 || routeIndex.value >= items.length) { routeIndex.value = 3; // No match or not in visible modules @@ -58,7 +60,6 @@ class FloatingNavbar extends HookConsumerWidget { return null; }, [currentPath]); - // Initialize the currentState on first render only useEffect(() { currentState.value = routeIndex.value; @@ -68,11 +69,20 @@ class FloatingNavbar extends HookConsumerWidget { final borderRadius = 25.0; - // Animation controller for all animations + // Animation controller for all animations with proper cleanup final animationController = useAnimationController( duration: const Duration(milliseconds: 300), ); + // Ensure proper disposal + useEffect(() { + return () { + if (animationController.isAnimating) { + animationController.stop(); + } + }; + }, []); + // Slide animation reference final slideAnimation = useRef?>(null); @@ -179,13 +189,27 @@ class FloatingNavbar extends HookConsumerWidget { onTap: () { // Only animate if this isn't already the selected item if (index != currentState.value) { + // First stop any running animation + if (animationController.isAnimating) { + animationController.stop(); + } + // Store current index as previous for animation previousIndex.value = currentState.value; // Update the internal state immediately for animation currentState.value = index; + + // Schedule navigation after the current frame completes + WidgetsBinding.instance.addPostFrameCallback(( + _, + ) { + // Now it's safe to navigate + item.onTap?.call(); + }); + } else { + // If already selected, just call the callback + item.onTap?.call(); } - // Then call the callback - item.onTap?.call(); }, child: Container( padding: const EdgeInsets.all(8), From e42539981f369e435cc874d0f04de3713989aaf3 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Tue, 1 Jul 2025 19:03:26 +0200 Subject: [PATCH 017/473] lint: cleanup --- lib/admin/router.dart | 2 - lib/admin/ui/admin.dart | 3 +- lib/advert/router.dart | 2 - lib/advert/ui/pages/advert.dart | 3 +- lib/amap/router.dart | 2 - lib/amap/ui/amap.dart | 3 +- lib/booking/router.dart | 2 - lib/booking/ui/booking.dart | 3 +- lib/centralisation/router.dart | 2 - lib/centralisation/ui/centralisation.dart | 3 +- lib/cinema/router.dart | 2 - lib/cinema/ui/cinema.dart | 3 +- lib/event/router.dart | 2 - lib/event/ui/event.dart | 3 +- lib/flappybird/router.dart | 1 - lib/flappybird/ui/flappybird_template.dart | 3 +- lib/home/router.dart | 2 - lib/home/ui/home.dart | 90 ++++++++++--------- lib/loan/router.dart | 2 - lib/loan/ui/loan.dart | 3 +- lib/navigation/class/module.dart | 6 +- lib/navigation/ui/all_module_page.dart | 3 - lib/others/ui/loading_page.dart | 6 +- lib/others/ui/no_internet_page.dart | 1 + lib/others/ui/no_module.dart | 2 + lib/others/ui/update_page.dart | 2 + lib/paiement/router.dart | 2 - lib/paiement/ui/paiement.dart | 3 +- lib/ph/router.dart | 2 - lib/ph/ui/pages/ph.dart | 3 +- lib/phonebook/router.dart | 2 - lib/phonebook/ui/phonebook.dart | 3 +- lib/purchases/router.dart | 2 - lib/purchases/ui/purchases.dart | 3 +- lib/raffle/router.dart | 2 - lib/raffle/ui/raffle.dart | 3 +- lib/recommendation/router.dart | 5 +- .../ui/widgets/recommendation_template.dart | 3 +- lib/seed-library/router.dart | 2 - lib/seed-library/ui/seed_library.dart | 3 +- .../providers/module_list_provider.dart | 42 ++++----- lib/settings/router.dart | 2 - lib/settings/ui/settings.dart | 3 +- lib/tools/ui/styleguide/router.dart | 2 - lib/vote/router.dart | 2 - lib/vote/ui/vote.dart | 3 +- 46 files changed, 114 insertions(+), 134 deletions(-) diff --git a/lib/admin/router.dart b/lib/admin/router.dart index 70716ab57e..f42c845a7c 100644 --- a/lib/admin/router.dart +++ b/lib/admin/router.dart @@ -1,6 +1,4 @@ -import 'package:either_dart/either.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:heroicons/heroicons.dart'; import 'package:titan/admin/providers/is_admin_provider.dart'; import 'package:titan/admin/ui/pages/groups/add_group_page/add_group_page.dart' deferred as add_group_page; diff --git a/lib/admin/ui/admin.dart b/lib/admin/ui/admin.dart index bd4ce6216c..9664343482 100644 --- a/lib/admin/ui/admin.dart +++ b/lib/admin/ui/admin.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/tools/constants.dart'; class AdminTemplate extends HookConsumerWidget { final Widget child; @@ -7,6 +8,6 @@ class AdminTemplate extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - return child; + return Container(color: ColorConstants.background, child: child); } } diff --git a/lib/advert/router.dart b/lib/advert/router.dart index e91fb55843..126b8f9111 100644 --- a/lib/advert/router.dart +++ b/lib/advert/router.dart @@ -1,6 +1,4 @@ -import 'package:either_dart/either.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:heroicons/heroicons.dart'; import 'package:titan/admin/providers/is_admin_provider.dart'; import 'package:titan/advert/providers/is_advert_admin_provider.dart'; import 'package:titan/advert/ui/pages/admin_page/admin_page.dart' diff --git a/lib/advert/ui/pages/advert.dart b/lib/advert/ui/pages/advert.dart index 74b7015722..66d49274e5 100644 --- a/lib/advert/ui/pages/advert.dart +++ b/lib/advert/ui/pages/advert.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/tools/constants.dart'; class AdvertTemplate extends HookConsumerWidget { final Widget child; @@ -7,6 +8,6 @@ class AdvertTemplate extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - return child; + return Container(color: ColorConstants.background, child: child); } } diff --git a/lib/amap/router.dart b/lib/amap/router.dart index 82c998a177..dcb8add08b 100644 --- a/lib/amap/router.dart +++ b/lib/amap/router.dart @@ -1,6 +1,4 @@ -import 'package:either_dart/either.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:heroicons/heroicons.dart'; import 'package:titan/amap/providers/is_amap_admin_provider.dart'; import 'package:titan/amap/ui/pages/admin_page/admin_page.dart' deferred as admin_page; diff --git a/lib/amap/ui/amap.dart b/lib/amap/ui/amap.dart index 8da5f5ca18..055992c587 100644 --- a/lib/amap/ui/amap.dart +++ b/lib/amap/ui/amap.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:titan/tools/constants.dart'; class AmapTemplate extends StatelessWidget { final Widget child; @@ -6,6 +7,6 @@ class AmapTemplate extends StatelessWidget { @override Widget build(BuildContext context) { - return child; + return Container(color: ColorConstants.background, child: child); } } diff --git a/lib/booking/router.dart b/lib/booking/router.dart index 46ab7b1c87..318084b715 100644 --- a/lib/booking/router.dart +++ b/lib/booking/router.dart @@ -1,6 +1,4 @@ -import 'package:either_dart/either.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:heroicons/heroicons.dart'; import 'package:titan/booking/providers/is_admin_provider.dart'; import 'package:titan/booking/providers/is_manager_provider.dart'; import 'package:titan/booking/ui/pages/admin_pages/add_edit_manager_page.dart' diff --git a/lib/booking/ui/booking.dart b/lib/booking/ui/booking.dart index 47b7cc16f7..86fde62150 100644 --- a/lib/booking/ui/booking.dart +++ b/lib/booking/ui/booking.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:titan/tools/constants.dart'; class BookingTemplate extends StatelessWidget { final Widget child; @@ -6,6 +7,6 @@ class BookingTemplate extends StatelessWidget { @override Widget build(BuildContext context) { - return child; + return Container(color: ColorConstants.background, child: child); } } diff --git a/lib/centralisation/router.dart b/lib/centralisation/router.dart index 3e744ca64f..5ba6d14e5b 100644 --- a/lib/centralisation/router.dart +++ b/lib/centralisation/router.dart @@ -1,6 +1,4 @@ -import 'package:either_dart/either.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:heroicons/heroicons.dart'; import 'package:titan/centralisation/tools/constants.dart'; import 'package:titan/centralisation/ui/pages/main_page.dart' deferred as main_page; diff --git a/lib/centralisation/ui/centralisation.dart b/lib/centralisation/ui/centralisation.dart index 142cb10643..f7f2eaa9d7 100644 --- a/lib/centralisation/ui/centralisation.dart +++ b/lib/centralisation/ui/centralisation.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:titan/tools/constants.dart'; class CentralisationTemplate extends StatelessWidget { final Widget child; @@ -6,6 +7,6 @@ class CentralisationTemplate extends StatelessWidget { @override Widget build(BuildContext context) { - return child; + return Container(color: ColorConstants.background, child: child); } } diff --git a/lib/cinema/router.dart b/lib/cinema/router.dart index 9948e3b2a9..0bc3a3a905 100644 --- a/lib/cinema/router.dart +++ b/lib/cinema/router.dart @@ -1,6 +1,4 @@ -import 'package:either_dart/either.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:heroicons/heroicons.dart'; import 'package:titan/cinema/providers/is_cinema_admin.dart'; import 'package:titan/cinema/ui/pages/admin_page/admin_page.dart' deferred as admin_page; diff --git a/lib/cinema/ui/cinema.dart b/lib/cinema/ui/cinema.dart index d3cac64008..dc05e97a45 100644 --- a/lib/cinema/ui/cinema.dart +++ b/lib/cinema/ui/cinema.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/tools/constants.dart'; class CinemaTemplate extends HookConsumerWidget { final Widget child; @@ -7,6 +8,6 @@ class CinemaTemplate extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - return child; + return Container(color: ColorConstants.background, child: child); } } diff --git a/lib/event/router.dart b/lib/event/router.dart index 557d08babb..31f3a9304a 100644 --- a/lib/event/router.dart +++ b/lib/event/router.dart @@ -1,6 +1,4 @@ -import 'package:either_dart/either.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:heroicons/heroicons.dart'; import 'package:titan/navigation/class/module.dart'; import 'package:titan/event/providers/is_admin_provider.dart'; import 'package:titan/event/ui/pages/detail_page/detail_page.dart' diff --git a/lib/event/ui/event.dart b/lib/event/ui/event.dart index 2eeaac22fd..44a8c11462 100644 --- a/lib/event/ui/event.dart +++ b/lib/event/ui/event.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:titan/tools/constants.dart'; class EventTemplate extends StatelessWidget { final Widget child; @@ -6,6 +7,6 @@ class EventTemplate extends StatelessWidget { @override Widget build(BuildContext context) { - return child; + return Container(color: ColorConstants.background, child: child); } } diff --git a/lib/flappybird/router.dart b/lib/flappybird/router.dart index 05d5ad0019..238ee4d3c9 100644 --- a/lib/flappybird/router.dart +++ b/lib/flappybird/router.dart @@ -1,4 +1,3 @@ -import 'package:either_dart/either.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/navigation/class/module.dart'; import 'package:titan/flappybird/ui/pages/game_page/game_page.dart' diff --git a/lib/flappybird/ui/flappybird_template.dart b/lib/flappybird/ui/flappybird_template.dart index dfb5198112..f1f7fcffbd 100644 --- a/lib/flappybird/ui/flappybird_template.dart +++ b/lib/flappybird/ui/flappybird_template.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/tools/constants.dart'; class FlappyBirdTemplate extends HookConsumerWidget { final Widget child; @@ -7,6 +8,6 @@ class FlappyBirdTemplate extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - return child; + return Container(color: ColorConstants.background, child: child); } } diff --git a/lib/home/router.dart b/lib/home/router.dart index 2d1b5c4823..8707cf6c3d 100644 --- a/lib/home/router.dart +++ b/lib/home/router.dart @@ -1,6 +1,4 @@ -import 'package:either_dart/either.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:heroicons/heroicons.dart'; import 'package:titan/navigation/class/module.dart'; import 'package:titan/event/ui/pages/detail_page/detail_page.dart' deferred as detail_page; diff --git a/lib/home/ui/home.dart b/lib/home/ui/home.dart index 87bc068c8c..eac1cd15fe 100644 --- a/lib/home/ui/home.dart +++ b/lib/home/ui/home.dart @@ -6,6 +6,7 @@ import 'package:titan/home/tools/constants.dart'; import 'package:titan/home/ui/day_list.dart'; import 'package:titan/home/ui/days_event.dart'; import 'package:titan/home/ui/month_bar.dart'; +import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/widgets/align_left_text.dart'; class HomePage extends HookConsumerWidget { @@ -18,52 +19,55 @@ class HomePage extends HookConsumerWidget { final ScrollController scrollController = useScrollController(); final daysEventScrollController = useScrollController(); - return Column( - children: [ - const SizedBox(height: 20), - MonthBar( - scrollController: scrollController, - width: MediaQuery.of(context).size.width, - ), - const SizedBox(height: 10), - DayList(scrollController, daysEventScrollController), - const SizedBox(height: 15), - const AlignLeftText( - HomeTextConstants.incomingEvents, - padding: EdgeInsets.symmetric(horizontal: 30.0), - fontSize: 25, - ), - const SizedBox(height: 10), - SizedBox( - height: MediaQuery.of(context).size.height - 320, - child: SingleChildScrollView( - physics: const BouncingScrollPhysics(), - controller: daysEventScrollController, - child: sortedEventList.keys.isNotEmpty - ? Column( - children: sortedEventList - .map( - (key, value) => MapEntry( - key, - DaysEvent(day: key, now: now, events: value), - ), - ) - .values - .toList(), - ) - : const Center( - child: Text( - HomeTextConstants.noEvents, - style: TextStyle( - fontSize: 15, - fontWeight: FontWeight.bold, - color: Colors.grey, + return Container( + color: ColorConstants.background, + child: Column( + children: [ + const SizedBox(height: 20), + MonthBar( + scrollController: scrollController, + width: MediaQuery.of(context).size.width, + ), + const SizedBox(height: 10), + DayList(scrollController, daysEventScrollController), + const SizedBox(height: 15), + const AlignLeftText( + HomeTextConstants.incomingEvents, + padding: EdgeInsets.symmetric(horizontal: 30.0), + fontSize: 25, + ), + const SizedBox(height: 10), + SizedBox( + height: MediaQuery.of(context).size.height - 320, + child: SingleChildScrollView( + physics: const BouncingScrollPhysics(), + controller: daysEventScrollController, + child: sortedEventList.keys.isNotEmpty + ? Column( + children: sortedEventList + .map( + (key, value) => MapEntry( + key, + DaysEvent(day: key, now: now, events: value), + ), + ) + .values + .toList(), + ) + : const Center( + child: Text( + HomeTextConstants.noEvents, + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.bold, + color: Colors.grey, + ), ), ), - ), + ), ), - ), - ], + ], + ), ); } } diff --git a/lib/loan/router.dart b/lib/loan/router.dart index 0ca20e2cdc..b38c09ce02 100644 --- a/lib/loan/router.dart +++ b/lib/loan/router.dart @@ -1,6 +1,4 @@ -import 'package:either_dart/either.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:heroicons/heroicons.dart'; import 'package:titan/navigation/class/module.dart'; import 'package:titan/loan/providers/is_loan_admin_provider.dart'; import 'package:titan/loan/ui/pages/admin_page/admin_page.dart' diff --git a/lib/loan/ui/loan.dart b/lib/loan/ui/loan.dart index 5ffd0b15f6..5338e613de 100644 --- a/lib/loan/ui/loan.dart +++ b/lib/loan/ui/loan.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/tools/constants.dart'; class LoanTemplate extends HookConsumerWidget { final Widget child; @@ -7,6 +8,6 @@ class LoanTemplate extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - return child; + return Container(color: ColorConstants.background, child: child); } } diff --git a/lib/navigation/class/module.dart b/lib/navigation/class/module.dart index fd2556a4e1..9ad4c6eb3f 100644 --- a/lib/navigation/class/module.dart +++ b/lib/navigation/class/module.dart @@ -6,11 +6,7 @@ class Module { String description; String root; - Module({ - required this.name, - required this.description, - required this.root, - }); + Module({required this.name, required this.description, required this.root}); Module copy({ String? name, diff --git a/lib/navigation/ui/all_module_page.dart b/lib/navigation/ui/all_module_page.dart index 99758c007b..36682080ee 100644 --- a/lib/navigation/ui/all_module_page.dart +++ b/lib/navigation/ui/all_module_page.dart @@ -1,11 +1,8 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:qlevar_router/qlevar_router.dart'; -import 'package:titan/admin/providers/is_admin_provider.dart'; -import 'package:titan/admin/router.dart'; import 'package:titan/navigation/providers/navbar_module_list.dart'; import 'package:titan/settings/providers/module_list_provider.dart'; -import 'package:titan/settings/router.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/providers/path_forwarding_provider.dart'; import 'package:titan/tools/ui/styleguide/list_item.dart'; diff --git a/lib/others/ui/loading_page.dart b/lib/others/ui/loading_page.dart index ae2d8acfd5..e0c98dc3dc 100644 --- a/lib/others/ui/loading_page.dart +++ b/lib/others/ui/loading_page.dart @@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/auth/providers/openid_provider.dart'; import 'package:titan/login/router.dart'; import 'package:titan/router.dart'; +import 'package:titan/tools/constants.dart'; import 'package:titan/tools/providers/path_forwarding_provider.dart'; import 'package:titan/tools/ui/widgets/loader.dart'; import 'package:titan/user/providers/user_provider.dart'; @@ -44,6 +45,9 @@ class LoadingPage extends ConsumerWidget { loading: () {}, error: (error, stack) => QR.to(AppRouter.noInternet), ); - return const Scaffold(body: Loader()); + return const Scaffold( + backgroundColor: ColorConstants.background, + body: Loader(), + ); } } diff --git a/lib/others/ui/no_internet_page.dart b/lib/others/ui/no_internet_page.dart index 01010cb7a8..5446f864b1 100644 --- a/lib/others/ui/no_internet_page.dart +++ b/lib/others/ui/no_internet_page.dart @@ -16,6 +16,7 @@ class NoInternetPage extends HookConsumerWidget { final isConnectedNotifier = ref.watch(isConnectedProvider.notifier); return Scaffold( body: Container( + color: ColorConstants.background, padding: const EdgeInsets.all(30), height: MediaQuery.of(context).size.height * 0.90, child: Center( diff --git a/lib/others/ui/no_module.dart b/lib/others/ui/no_module.dart index 78bbedcef4..aea5abbe3e 100644 --- a/lib/others/ui/no_module.dart +++ b/lib/others/ui/no_module.dart @@ -3,6 +3,7 @@ import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/providers/module_root_list_provider.dart'; import 'package:titan/others/tools/constants.dart'; +import 'package:titan/tools/constants.dart'; import 'package:titan/tools/providers/path_forwarding_provider.dart'; import 'package:qlevar_router/qlevar_router.dart'; @@ -20,6 +21,7 @@ class NoModulePage extends HookConsumerWidget { orElse: () {}, ); return const Scaffold( + backgroundColor: ColorConstants.background, body: Padding( padding: EdgeInsets.symmetric(horizontal: 30), child: Column( diff --git a/lib/others/ui/update_page.dart b/lib/others/ui/update_page.dart index 5e6013d8b8..fe98e5e966 100644 --- a/lib/others/ui/update_page.dart +++ b/lib/others/ui/update_page.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/others/tools/constants.dart'; +import 'package:titan/tools/constants.dart'; import 'package:titan/version/providers/titan_version_provider.dart'; class UpdatePage extends HookConsumerWidget { @@ -11,6 +12,7 @@ class UpdatePage extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final titanVersion = ref.watch(titanVersionProvider); return Scaffold( + backgroundColor: ColorConstants.background, body: Padding( padding: const EdgeInsets.symmetric(horizontal: 30), child: Column( diff --git a/lib/paiement/router.dart b/lib/paiement/router.dart index 15cb7d168e..69afea1664 100644 --- a/lib/paiement/router.dart +++ b/lib/paiement/router.dart @@ -1,6 +1,4 @@ -import 'package:either_dart/either.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:heroicons/heroicons.dart'; import 'package:titan/navigation/class/module.dart'; import 'package:titan/paiement/providers/is_payment_admin.dart'; import 'package:titan/paiement/ui/pages/admin_page/admin_page.dart' diff --git a/lib/paiement/ui/paiement.dart b/lib/paiement/ui/paiement.dart index cd107cb462..ef7c6772e1 100644 --- a/lib/paiement/ui/paiement.dart +++ b/lib/paiement/ui/paiement.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:titan/tools/constants.dart'; class PaymentTemplate extends StatelessWidget { final Widget child; @@ -6,6 +7,6 @@ class PaymentTemplate extends StatelessWidget { @override Widget build(BuildContext context) { - return child; + return Container(color: ColorConstants.background, child: child); } } diff --git a/lib/ph/router.dart b/lib/ph/router.dart index 2f1ac69705..8bc6c27d81 100644 --- a/lib/ph/router.dart +++ b/lib/ph/router.dart @@ -1,8 +1,6 @@ // ignore_for_file: constant_identifier_names -import 'package:either_dart/either.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:heroicons/heroicons.dart'; import 'package:titan/navigation/class/module.dart'; import 'package:titan/ph/providers/is_ph_admin_provider.dart'; import 'package:titan/ph/ui/pages/form_page/add_edit_ph_page.dart' diff --git a/lib/ph/ui/pages/ph.dart b/lib/ph/ui/pages/ph.dart index 96d617bd09..3ca34f170f 100644 --- a/lib/ph/ui/pages/ph.dart +++ b/lib/ph/ui/pages/ph.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/tools/constants.dart'; class PhTemplate extends HookConsumerWidget { final Widget child; @@ -7,6 +8,6 @@ class PhTemplate extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - return child; + return Container(color: ColorConstants.background, child: child); } } diff --git a/lib/phonebook/router.dart b/lib/phonebook/router.dart index c0ce1210cb..5d443b1b42 100644 --- a/lib/phonebook/router.dart +++ b/lib/phonebook/router.dart @@ -1,5 +1,3 @@ -import 'package:either_dart/either.dart'; -import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/navigation/class/module.dart'; import 'package:titan/phonebook/providers/phonebook_admin_provider.dart'; diff --git a/lib/phonebook/ui/phonebook.dart b/lib/phonebook/ui/phonebook.dart index 652bae4a1f..7312c930f6 100644 --- a/lib/phonebook/ui/phonebook.dart +++ b/lib/phonebook/ui/phonebook.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/tools/constants.dart'; class PhonebookTemplate extends HookConsumerWidget { final Widget child; @@ -7,6 +8,6 @@ class PhonebookTemplate extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - return child; + return Container(color: ColorConstants.background, child: child); } } diff --git a/lib/purchases/router.dart b/lib/purchases/router.dart index b68c451e17..3685122c8d 100644 --- a/lib/purchases/router.dart +++ b/lib/purchases/router.dart @@ -1,5 +1,3 @@ -import 'package:either_dart/either.dart'; -import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/navigation/class/module.dart'; import 'package:titan/purchases/providers/purchases_admin_provider.dart'; diff --git a/lib/purchases/ui/purchases.dart b/lib/purchases/ui/purchases.dart index a65787d3ca..545940681b 100644 --- a/lib/purchases/ui/purchases.dart +++ b/lib/purchases/ui/purchases.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/tools/constants.dart'; class PurchasesTemplate extends HookConsumerWidget { final Widget child; @@ -7,6 +8,6 @@ class PurchasesTemplate extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - return child; + return Container(color: ColorConstants.background, child: child); } } diff --git a/lib/raffle/router.dart b/lib/raffle/router.dart index b68df0b767..e7ac4b3df5 100644 --- a/lib/raffle/router.dart +++ b/lib/raffle/router.dart @@ -1,6 +1,4 @@ -import 'package:either_dart/either.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:heroicons/heroicons.dart'; import 'package:titan/navigation/class/module.dart'; import 'package:titan/raffle/providers/is_raffle_admin.dart'; import 'package:titan/raffle/ui/pages/admin_module_page/admin_module_page.dart' diff --git a/lib/raffle/ui/raffle.dart b/lib/raffle/ui/raffle.dart index 78b3010235..b9838e0fe6 100644 --- a/lib/raffle/ui/raffle.dart +++ b/lib/raffle/ui/raffle.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/tools/constants.dart'; class RaffleTemplate extends HookConsumerWidget { final Widget child; @@ -7,6 +8,6 @@ class RaffleTemplate extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - return child; + return Container(color: ColorConstants.background, child: child); } } diff --git a/lib/recommendation/router.dart b/lib/recommendation/router.dart index e5f9af6854..5893ad79cb 100644 --- a/lib/recommendation/router.dart +++ b/lib/recommendation/router.dart @@ -1,6 +1,4 @@ -import 'package:either_dart/either.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:heroicons/heroicons.dart'; import 'package:titan/navigation/class/module.dart'; import 'package:titan/recommendation/providers/is_recommendation_admin_provider.dart'; import 'package:titan/recommendation/ui/pages/main_page.dart' @@ -22,7 +20,8 @@ class RecommendationRouter { static const String addEdit = '/add_edit'; static final Module module = Module( name: "Bons plans", - description: "Gérer les recommandations, les informations et les administrateurs", + description: + "Gérer les recommandations, les informations et les administrateurs", root: RecommendationRouter.root, ); diff --git a/lib/recommendation/ui/widgets/recommendation_template.dart b/lib/recommendation/ui/widgets/recommendation_template.dart index 15b27def2c..ba2ab7153c 100644 --- a/lib/recommendation/ui/widgets/recommendation_template.dart +++ b/lib/recommendation/ui/widgets/recommendation_template.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:titan/tools/constants.dart'; class RecommendationTemplate extends StatelessWidget { final Widget child; @@ -6,6 +7,6 @@ class RecommendationTemplate extends StatelessWidget { @override Widget build(BuildContext context) { - return child; + return Container(color: ColorConstants.background, child: child); } } diff --git a/lib/seed-library/router.dart b/lib/seed-library/router.dart index 72db114ced..a024aad829 100644 --- a/lib/seed-library/router.dart +++ b/lib/seed-library/router.dart @@ -1,6 +1,4 @@ -import 'package:either_dart/either.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:heroicons/heroicons.dart'; import 'package:titan/navigation/class/module.dart'; import 'package:titan/seed-library/providers/is_seed_library_admin_provider.dart'; import 'package:titan/seed-library/ui/pages/add_edit_species_page/add_edit_species_page.dart' diff --git a/lib/seed-library/ui/seed_library.dart b/lib/seed-library/ui/seed_library.dart index a39ca89f1e..60dc8b102e 100644 --- a/lib/seed-library/ui/seed_library.dart +++ b/lib/seed-library/ui/seed_library.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:titan/tools/constants.dart'; class SeedLibraryTemplate extends StatelessWidget { final Widget child; @@ -6,6 +7,6 @@ class SeedLibraryTemplate extends StatelessWidget { @override Widget build(BuildContext context) { - return child; + return Container(color: ColorConstants.background, child: child); } } diff --git a/lib/settings/providers/module_list_provider.dart b/lib/settings/providers/module_list_provider.dart index e721af3f94..c68023584c 100644 --- a/lib/settings/providers/module_list_provider.dart +++ b/lib/settings/providers/module_list_provider.dart @@ -8,22 +8,21 @@ import 'package:titan/amap/router.dart'; import 'package:titan/booking/router.dart'; import 'package:titan/centralisation/router.dart'; import 'package:titan/cinema/router.dart'; +import 'package:titan/event/router.dart'; +import 'package:titan/loan/router.dart'; import 'package:titan/navigation/class/module.dart'; import 'package:collection/collection.dart'; -import 'package:titan/event/router.dart'; import 'package:titan/home/router.dart'; -import 'package:titan/loan/router.dart'; import 'package:titan/paiement/router.dart'; -import 'package:titan/phonebook/router.dart'; import 'package:titan/ph/router.dart'; +import 'package:titan/phonebook/router.dart'; import 'package:titan/purchases/router.dart'; import 'package:titan/raffle/router.dart'; import 'package:titan/recommendation/router.dart'; -import 'package:titan/router.dart'; import 'package:titan/seed-library/router.dart'; import 'package:titan/settings/router.dart'; -import 'package:titan/vote/router.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:titan/vote/router.dart'; final modulesProvider = StateNotifierProvider>(( ref, @@ -48,20 +47,20 @@ class ModulesNotifier extends StateNotifier> { List allModules = [ HomeRouter.module, AdvertRouter.module, - // AmapRouter.module, - // BookingRouter.module, - // CentralisationRouter.module, - // CinemaRouter.module, - // EventRouter.module, - // LoanRouter.module, + AmapRouter.module, + BookingRouter.module, + CentralisationRouter.module, + CinemaRouter.module, + EventRouter.module, + LoanRouter.module, PaymentRouter.module, - // PhonebookRouter.module, - // PhRouter.module, - // PurchasesRouter.module, - // RaffleRouter.module, - // RecommendationRouter.module, - // VoteRouter.module, - // SeedLibraryRouter.module, + PhonebookRouter.module, + PhRouter.module, + PurchasesRouter.module, + RaffleRouter.module, + RecommendationRouter.module, + VoteRouter.module, + SeedLibraryRouter.module, ]; ModulesNotifier({required this.isAdmin}) : super([]); @@ -127,12 +126,7 @@ class ModulesNotifier extends StateNotifier> { for (Module module in toDelete) { allModules.remove(module); } - allModules.addAll( - [ - SettingsRouter.module, - if (isAdmin) AdminRouter.module, - ] - ); + allModules.addAll([SettingsRouter.module, if (isAdmin) AdminRouter.module]); state = allModules; } diff --git a/lib/settings/router.dart b/lib/settings/router.dart index c3d265e966..8da36c5caa 100644 --- a/lib/settings/router.dart +++ b/lib/settings/router.dart @@ -1,7 +1,5 @@ -import 'package:either_dart/either.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:heroicons/heroicons.dart'; import 'package:titan/admin/router.dart'; import 'package:titan/navigation/class/module.dart'; import 'package:titan/settings/ui/pages/change_pass/change_pass.dart' diff --git a/lib/settings/ui/settings.dart b/lib/settings/ui/settings.dart index 3291a2d201..8f61e9d218 100644 --- a/lib/settings/ui/settings.dart +++ b/lib/settings/ui/settings.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:titan/tools/constants.dart'; class SettingsTemplate extends StatelessWidget { final Widget child; @@ -6,6 +7,6 @@ class SettingsTemplate extends StatelessWidget { @override Widget build(BuildContext context) { - return child; + return Container(color: ColorConstants.background, child: child); } } diff --git a/lib/tools/ui/styleguide/router.dart b/lib/tools/ui/styleguide/router.dart index 81f7073cd4..633e01ba0b 100644 --- a/lib/tools/ui/styleguide/router.dart +++ b/lib/tools/ui/styleguide/router.dart @@ -1,6 +1,4 @@ -import 'package:either_dart/either.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:heroicons/heroicons.dart'; import 'package:titan/navigation/class/module.dart'; import 'package:titan/tools/ui/styleguide/styleguide_page.dart'; import 'package:qlevar_router/qlevar_router.dart'; diff --git a/lib/vote/router.dart b/lib/vote/router.dart index 90ea88e8d9..f89d51be5c 100644 --- a/lib/vote/router.dart +++ b/lib/vote/router.dart @@ -1,6 +1,4 @@ -import 'package:either_dart/either.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:heroicons/heroicons.dart'; import 'package:titan/navigation/class/module.dart'; import 'package:titan/tools/middlewares/admin_middleware.dart'; import 'package:titan/tools/middlewares/authenticated_middleware.dart'; diff --git a/lib/vote/ui/vote.dart b/lib/vote/ui/vote.dart index 44e9d75a14..107aff21ec 100644 --- a/lib/vote/ui/vote.dart +++ b/lib/vote/ui/vote.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:titan/tools/constants.dart'; class VoteTemplate extends StatelessWidget { final Widget child; @@ -6,6 +7,6 @@ class VoteTemplate extends StatelessWidget { @override Widget build(BuildContext context) { - return child; + return Container(color: ColorConstants.background, child: child); } } From c1c737ff9acc02ca5bbf99ca89c47236d0bcdd7a Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sat, 5 Jul 2025 01:58:33 +0200 Subject: [PATCH 018/473] feat: adding feed module and main page --- lib/feed/class/feed_item.dart | 49 ++++++++++ lib/feed/router.dart | 29 ++++++ lib/feed/ui/feed.dart | 12 +++ .../pages/main_page/dotted_vertical_line.dart | 82 +++++++++++++++++ lib/feed/ui/pages/main_page/event_action.dart | 79 ++++++++++++++++ lib/feed/ui/pages/main_page/event_card.dart | 72 +++++++++++++++ .../ui/pages/main_page/feed_timeline.dart | 28 ++++++ lib/feed/ui/pages/main_page/main_page.dart | 62 +++++++++++++ .../ui/pages/main_page/time_line_item.dart | 92 +++++++++++++++++++ .../providers/navbar_module_list.dart | 4 +- lib/navigation/ui/drawer_template.dart | 18 ++++ lib/router.dart | 2 + .../providers/module_list_provider.dart | 2 +- .../middlewares/authenticated_middleware.dart | 8 +- 14 files changed, 531 insertions(+), 8 deletions(-) create mode 100644 lib/feed/class/feed_item.dart create mode 100644 lib/feed/router.dart create mode 100644 lib/feed/ui/feed.dart create mode 100644 lib/feed/ui/pages/main_page/dotted_vertical_line.dart create mode 100644 lib/feed/ui/pages/main_page/event_action.dart create mode 100644 lib/feed/ui/pages/main_page/event_card.dart create mode 100644 lib/feed/ui/pages/main_page/feed_timeline.dart create mode 100644 lib/feed/ui/pages/main_page/main_page.dart create mode 100644 lib/feed/ui/pages/main_page/time_line_item.dart diff --git a/lib/feed/class/feed_item.dart b/lib/feed/class/feed_item.dart new file mode 100644 index 0000000000..2de4e80425 --- /dev/null +++ b/lib/feed/class/feed_item.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; + +class FeedItem { + final String title; + final String subtitle; + final DateTime date; + final String? location; + final String? imageUrl; + final bool isTerminated; + final bool needsRegistration; + final int? participantsCount; + final VoidCallback? onRegister; + + const FeedItem({ + required this.title, + required this.subtitle, + required this.date, + this.location, + this.imageUrl, + this.isTerminated = false, + this.needsRegistration = false, + this.participantsCount, + this.onRegister, + }); + + static List getFakeItems() { + return [ + FeedItem( + title: 'Weekly diplo', + subtitle: '55€', + date: DateTime(2025, 12, 18), + ), + FeedItem( + title: 'H11', + subtitle: '19:30 - 20:30 • Foyer', + date: DateTime(2025, 11, 8), + isTerminated: true, + needsRegistration: true, + participantsCount: 33, + onRegister: () {}, + ), + FeedItem( + title: 'Weekly diplo', + subtitle: '55€', + date: DateTime(2025, 10, 31), + ), + ]; + } +} diff --git a/lib/feed/router.dart b/lib/feed/router.dart new file mode 100644 index 0000000000..a2671b439a --- /dev/null +++ b/lib/feed/router.dart @@ -0,0 +1,29 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/navigation/class/module.dart'; +import 'package:titan/feed/ui/pages/main_page/main_page.dart' deferred as main_page; +import 'package:titan/tools/middlewares/authenticated_middleware.dart'; +import 'package:titan/tools/middlewares/deferred_middleware.dart'; +import 'package:qlevar_router/qlevar_router.dart'; + +class FeedRouter { + final Ref ref; + + static const String root = '/feed'; + static final Module module = Module( + name: "Feed", + description: "Consulter les actualités et mises à jour", + root: FeedRouter.root, + ); + + FeedRouter(this.ref); + + QRoute route() => QRoute( + name: "feed", + path: FeedRouter.root, + builder: () => main_page.FeedMainPage(), + middleware: [ + AuthenticatedMiddleware(ref), + DeferredLoadingMiddleware(main_page.loadLibrary), + ], + ); +} diff --git a/lib/feed/ui/feed.dart b/lib/feed/ui/feed.dart new file mode 100644 index 0000000000..ba6ef1dd9a --- /dev/null +++ b/lib/feed/ui/feed.dart @@ -0,0 +1,12 @@ +import 'package:flutter/material.dart'; +import 'package:titan/tools/constants.dart'; + +class FeedTemplate extends StatelessWidget { + final Widget child; + const FeedTemplate({super.key, required this.child}); + + @override + Widget build(BuildContext context) { + return Container(color: ColorConstants.background, child: child); + } +} diff --git a/lib/feed/ui/pages/main_page/dotted_vertical_line.dart b/lib/feed/ui/pages/main_page/dotted_vertical_line.dart new file mode 100644 index 0000000000..dc967dab7a --- /dev/null +++ b/lib/feed/ui/pages/main_page/dotted_vertical_line.dart @@ -0,0 +1,82 @@ +import 'package:flutter/material.dart'; +import 'package:titan/tools/constants.dart'; + +/// A widget that displays a vertical line of evenly spaced dots. +/// The number of dots is calculated based on the height of the widget. +class DottedVerticalLine extends StatelessWidget { + /// The spacing between each dot in logical pixels. + final double dotSpacing; + + /// The diameter of each dot in logical pixels. + final double dotSize; + + /// The color of the dots. Defaults to [ColorConstants.secondary]. + final Color? dotColor; + + const DottedVerticalLine({ + super.key, + this.dotSpacing = 6.0, + this.dotSize = 2.0, + this.dotColor, + }); + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (context, constraints) { + final height = constraints.maxHeight; + + // Calculate how many dots we can fit in the height + // We add 1 to dotSpacing to account for the space between dots plus the dot itself + final dotsCount = (height / (dotSize + dotSpacing)).floor(); + + return CustomPaint( + size: Size(dotSize, height), + painter: _DottedLinePainter( + dotSpacing: dotSpacing, + dotSize: dotSize, + dotsCount: dotsCount, + dotColor: dotColor ?? ColorConstants.secondary, + ), + ); + }, + ); + } +} + +class _DottedLinePainter extends CustomPainter { + final double dotSpacing; + final double dotSize; + final int dotsCount; + final Color dotColor; + + _DottedLinePainter({ + required this.dotSpacing, + required this.dotSize, + required this.dotsCount, + required this.dotColor, + }); + + @override + void paint(Canvas canvas, Size size) { + final paint = Paint() + ..color = dotColor + ..strokeCap = StrokeCap.round + ..strokeWidth = dotSize; + + // Start position + double startY = 0; + + // Draw dots + for (int i = 0; i < dotsCount; i++) { + // Calculate y position for this dot + double yPosition = startY + (i * (dotSize + dotSpacing)); + + // Draw the dot + canvas.drawCircle(Offset(size.width / 2, yPosition), dotSize / 2, paint); + } + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => false; +} diff --git a/lib/feed/ui/pages/main_page/event_action.dart b/lib/feed/ui/pages/main_page/event_action.dart new file mode 100644 index 0000000000..d33bc8f5a9 --- /dev/null +++ b/lib/feed/ui/pages/main_page/event_action.dart @@ -0,0 +1,79 @@ +import 'package:flutter/material.dart'; +import 'package:titan/tools/constants.dart'; + +/// A widget that displays event actions such as +/// invitation status and registration button. +class EventAction extends StatelessWidget { + /// The text to display for the invitation status. + final String invitationText; + + /// The number of participants (null if not applicable). + final int? participantsCount; + + /// Callback when the action button is pressed. + final VoidCallback? onActionPressed; + + /// Text to display on the action button. + final String actionButtonText; + + const EventAction({ + super.key, + this.invitationText = 'Tu es invité', + this.participantsCount, + this.onActionPressed, + this.actionButtonText = 'Prendre ma place', + }); + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + // Invitation text with participant count + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + invitationText, + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: ColorConstants.tertiary, + ), + overflow: TextOverflow.ellipsis, + ), + Text( + '$participantsCount participants', + style: const TextStyle( + fontSize: 10, + color: ColorConstants.secondary, + ), + overflow: TextOverflow.ellipsis, + ), + ], + ), + + const SizedBox(width: 8), + + // Action button + GestureDetector( + onTap: onActionPressed, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: ColorConstants.tertiary, + borderRadius: BorderRadius.circular(20), + ), + child: Text( + actionButtonText, + style: const TextStyle( + fontSize: 12, + color: ColorConstants.background, + ), + ), + ), + ), + ], + ); + } +} diff --git a/lib/feed/ui/pages/main_page/event_card.dart b/lib/feed/ui/pages/main_page/event_card.dart new file mode 100644 index 0000000000..60a9551cc2 --- /dev/null +++ b/lib/feed/ui/pages/main_page/event_card.dart @@ -0,0 +1,72 @@ +import 'package:flutter/material.dart'; +import 'package:titan/feed/class/feed_item.dart'; +import 'package:titan/tools/constants.dart'; + +class EventCard extends StatelessWidget { + final FeedItem item; + + const EventCard({super.key, required this.item}); + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(15), + color: ColorConstants.secondary, + ), + child: Stack( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 50), + Text( + item.title, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w900, + color: ColorConstants.background, + ), + ), + Text( + item.subtitle, + style: const TextStyle( + fontSize: 12, + color: ColorConstants.background, + ), + ), + ], + ), + ), + if (item.isTerminated) + Positioned( + top: 0, + right: 0, + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 5, + ), + decoration: const BoxDecoration( + color: ColorConstants.main, + borderRadius: BorderRadius.only( + topRight: Radius.circular(15), + bottomLeft: Radius.circular(15), + ), + ), + child: const Text( + 'Terminé', + style: TextStyle( + color: ColorConstants.background, + fontSize: 10, + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/feed/ui/pages/main_page/feed_timeline.dart b/lib/feed/ui/pages/main_page/feed_timeline.dart new file mode 100644 index 0000000000..5607d6ce21 --- /dev/null +++ b/lib/feed/ui/pages/main_page/feed_timeline.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; +import 'package:titan/feed/class/feed_item.dart'; +import 'package:titan/feed/ui/pages/main_page/time_line_item.dart'; +import 'package:titan/tools/ui/widgets/dotted_vertical_line.dart'; + +class FeedTimeline extends StatelessWidget { + final List items; + final Function(FeedItem item)? onItemTap; + + const FeedTimeline({super.key, required this.items, this.onItemTap}); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + ...items.map((item) { + final index = items.indexOf(item); + return TimelineItem( + item: item, + onTap: onItemTap != null ? () => onItemTap!(item) : null, + isLast: index == items.length - 1, + ); + }), + SizedBox(height: 80), + ], + ); + } +} diff --git a/lib/feed/ui/pages/main_page/main_page.dart b/lib/feed/ui/pages/main_page/main_page.dart new file mode 100644 index 0000000000..c794377281 --- /dev/null +++ b/lib/feed/ui/pages/main_page/main_page.dart @@ -0,0 +1,62 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/feed/class/feed_item.dart'; +import 'package:titan/feed/ui/feed.dart'; +import 'package:titan/feed/ui/pages/main_page/feed_timeline.dart'; +import 'package:titan/tools/constants.dart'; +import 'package:titan/tools/ui/styleguide/searchbar.dart'; + +class FeedMainPage extends HookConsumerWidget { + const FeedMainPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final feedItems = useState>(FeedItem.getFakeItems()); + final filteredItems = useState>(feedItems.value); + + return FeedTemplate( + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Search bar + CustomSearchBar(onFilter: () {}, onSearch: (_) {}), + + const SizedBox(height: 20), + + // Title + const Text( + "Actualité", + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: ColorConstants.title, + ), + ), + + const SizedBox(height: 20), + + // // Timeline + SizedBox( + height: MediaQuery.of(context).size.height - 190, + child: SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: FeedTimeline( + items: filteredItems.value, + onItemTap: (item) { + // TODO: Handle item tap + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Event tapped: ${item.title}')), + ); + }, + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/feed/ui/pages/main_page/time_line_item.dart b/lib/feed/ui/pages/main_page/time_line_item.dart new file mode 100644 index 0000000000..c621f0fa54 --- /dev/null +++ b/lib/feed/ui/pages/main_page/time_line_item.dart @@ -0,0 +1,92 @@ +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:titan/feed/class/feed_item.dart'; +import 'package:titan/feed/ui/pages/main_page/event_action.dart'; +import 'package:titan/feed/ui/pages/main_page/event_card.dart'; +import 'package:titan/tools/constants.dart'; +import 'package:titan/feed/ui/pages/main_page/dotted_vertical_line.dart'; + +class TimelineItem extends StatelessWidget { + final FeedItem item; + final VoidCallback? onTap; + final bool isLast; + + const TimelineItem({ + super.key, + required this.item, + this.onTap, + this.isLast = false, + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 15), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(left: 10, right: 30), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + DateFormat('d').format(item.date), + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: ColorConstants.main, + ), + ), + Text( + DateFormat('MMM').format(item.date).toUpperCase(), + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: ColorConstants.onTertiary, + ), + ), + // Expanded(child: DottedVerticalLine()), + ], + ), + ), + Expanded( + child: GestureDetector( + onTap: onTap, + child: EventCard(item: item), + ), + ), + ], + ), + if (item.needsRegistration) + Padding( + padding: const EdgeInsets.only(top: 10), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(left: 15, right: 45), + child: Container( + width: 12, + height: 12, + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: ColorConstants.secondary, + width: 2, + ), + ), + ), + ), + Expanded(child: EventAction()), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/navigation/providers/navbar_module_list.dart b/lib/navigation/providers/navbar_module_list.dart index 2054ca3cd8..046ce728f1 100644 --- a/lib/navigation/providers/navbar_module_list.dart +++ b/lib/navigation/providers/navbar_module_list.dart @@ -3,10 +3,10 @@ import 'package:titan/navigation/class/module.dart'; import 'package:titan/settings/providers/module_list_provider.dart'; class ModuleListNotifier extends StateNotifier> { - final int maxNumberOfModules = 3; + final int maxNumberOfModules = 2; ModuleListNotifier(List modules) : listModule = List.from(modules), - super(modules.take(3).toList()); + super(modules.take(2).toList()); final List listModule; diff --git a/lib/navigation/ui/drawer_template.dart b/lib/navigation/ui/drawer_template.dart index 9585a79d0a..e4c628199d 100644 --- a/lib/navigation/ui/drawer_template.dart +++ b/lib/navigation/ui/drawer_template.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/feed/router.dart'; import 'package:titan/navigation/class/module.dart'; import 'package:titan/navigation/providers/display_quit_popup.dart'; import 'package:titan/navigation/providers/navbar_module_list.dart'; @@ -75,6 +76,23 @@ class DrawerTemplate extends HookConsumerWidget { right: 0, child: FloatingNavbar( items: [ + FloatingNavbarItem( + module: FeedRouter.module, + onTap: () { + // Use ref.read instead of ref.watch to avoid rebuilds + final pathForwardingNotifier = ref.read( + pathForwardingProvider.notifier, + ); + + // First update the path + pathForwardingNotifier.forward(FeedRouter.root); + + // Then navigate with a small delay to allow the UI to stabilize + WidgetsBinding.instance.addPostFrameCallback((_) { + QR.to(FeedRouter.root); + }); + }, + ), ...navbarListModule.map((module) { return FloatingNavbarItem( module: module, diff --git a/lib/router.dart b/lib/router.dart index 8f1c01d9ea..3195cf7df6 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -6,6 +6,7 @@ import 'package:titan/booking/router.dart'; import 'package:titan/centralisation/router.dart'; import 'package:titan/cinema/router.dart'; import 'package:titan/event/router.dart'; +import 'package:titan/feed/router.dart'; import 'package:titan/flappybird/router.dart'; import 'package:titan/home/router.dart'; import 'package:titan/home/ui/home.dart' deferred as home_page; @@ -90,6 +91,7 @@ class AppRouter { CinemaRouter(ref).route(), EventRouter(ref).route(), FlappyBirdRouter(ref).route(), + FeedRouter(ref).route(), HomeRouter(ref).route(), LoanRouter(ref).route(), LoginRouter(ref).accountRoute(), diff --git a/lib/settings/providers/module_list_provider.dart b/lib/settings/providers/module_list_provider.dart index c68023584c..d679c32d2e 100644 --- a/lib/settings/providers/module_list_provider.dart +++ b/lib/settings/providers/module_list_provider.dart @@ -20,8 +20,8 @@ import 'package:titan/purchases/router.dart'; import 'package:titan/raffle/router.dart'; import 'package:titan/recommendation/router.dart'; import 'package:titan/seed-library/router.dart'; -import 'package:titan/settings/router.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:titan/settings/router.dart'; import 'package:titan/vote/router.dart'; final modulesProvider = StateNotifierProvider>(( diff --git a/lib/tools/middlewares/authenticated_middleware.dart b/lib/tools/middlewares/authenticated_middleware.dart index fd71f8c3b0..8f280616f9 100644 --- a/lib/tools/middlewares/authenticated_middleware.dart +++ b/lib/tools/middlewares/authenticated_middleware.dart @@ -2,6 +2,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/auth/providers/openid_provider.dart'; +import 'package:titan/feed/router.dart'; import 'package:titan/login/router.dart'; import 'package:titan/router.dart'; import 'package:titan/settings/providers/module_list_provider.dart'; @@ -48,11 +49,8 @@ class AuthenticatedMiddleware extends QMiddleware { pathForwardingNotifier.login(); } if (pathForwardingNotifier.state.path == "/") { - if (modules.isEmpty) { - return AppRouter.noModule; - } - pathForwardingNotifier.forward(modules.first.root); - return modules.first.root; + pathForwardingNotifier.forward(FeedRouter.root); + return FeedRouter.root; } if (pathForwardingNotifier.state.path != path) { return pathForwardingNotifier.state.path; From 58ee3c8e0dd865d55b3526dce9e1c7b9ef0fc259 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sat, 5 Jul 2025 01:58:33 +0200 Subject: [PATCH 019/473] feat: placing tag properly and handling other feed type --- lib/feed/class/feed_item.dart | 23 +++++++- lib/feed/ui/pages/main_page/event_action.dart | 57 ++++++++++--------- lib/feed/ui/pages/main_page/event_card.dart | 32 +++++++---- .../ui/pages/main_page/feed_timeline.dart | 1 - lib/feed/ui/pages/main_page/main_page.dart | 2 +- .../ui/pages/main_page/time_line_item.dart | 18 +++++- lib/navigation/ui/drawer_template.dart | 7 +-- 7 files changed, 88 insertions(+), 52 deletions(-) diff --git a/lib/feed/class/feed_item.dart b/lib/feed/class/feed_item.dart index 2de4e80425..91f4dd08bd 100644 --- a/lib/feed/class/feed_item.dart +++ b/lib/feed/class/feed_item.dart @@ -1,23 +1,33 @@ import 'package:flutter/material.dart'; +enum FeedItemType { + event, + action, + announcement, +} + class FeedItem { + final FeedItemType type; final String title; final String subtitle; final DateTime date; final String? location; final String? imageUrl; final bool isTerminated; + final bool isOngoing; final bool needsRegistration; final int? participantsCount; final VoidCallback? onRegister; const FeedItem({ + required this.type, required this.title, required this.subtitle, required this.date, this.location, this.imageUrl, this.isTerminated = false, + this.isOngoing = false, this.needsRegistration = false, this.participantsCount, this.onRegister, @@ -26,11 +36,13 @@ class FeedItem { static List getFakeItems() { return [ FeedItem( + type: FeedItemType.announcement, title: 'Weekly diplo', subtitle: '55€', date: DateTime(2025, 12, 18), ), FeedItem( + type: FeedItemType.event, title: 'H11', subtitle: '19:30 - 20:30 • Foyer', date: DateTime(2025, 11, 8), @@ -40,9 +52,14 @@ class FeedItem { onRegister: () {}, ), FeedItem( - title: 'Weekly diplo', - subtitle: '55€', - date: DateTime(2025, 10, 31), + type: FeedItemType.action, + title: 'Campagne', + subtitle: 'Jusqu\'à minuit', + date: DateTime(2025, 11, 8), + isOngoing: true, + needsRegistration: true, + participantsCount: 33, + onRegister: () {}, ), ]; } diff --git a/lib/feed/ui/pages/main_page/event_action.dart b/lib/feed/ui/pages/main_page/event_action.dart index d33bc8f5a9..56a7e12880 100644 --- a/lib/feed/ui/pages/main_page/event_action.dart +++ b/lib/feed/ui/pages/main_page/event_action.dart @@ -1,27 +1,18 @@ import 'package:flutter/material.dart'; import 'package:titan/tools/constants.dart'; -/// A widget that displays event actions such as -/// invitation status and registration button. class EventAction extends StatelessWidget { - /// The text to display for the invitation status. - final String invitationText; - - /// The number of participants (null if not applicable). - final int? participantsCount; - - /// Callback when the action button is pressed. + final String title, subtitle, actionButtonText; final VoidCallback? onActionPressed; - - /// Text to display on the action button. - final String actionButtonText; + final bool isActionEnabled; const EventAction({ super.key, - this.invitationText = 'Tu es invité', - this.participantsCount, + required this.title, + required this.subtitle, this.onActionPressed, - this.actionButtonText = 'Prendre ma place', + required this.actionButtonText, + required this.isActionEnabled, }); @override @@ -29,23 +20,22 @@ class EventAction extends StatelessWidget { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - // Invitation text with participant count Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - invitationText, + title, style: const TextStyle( - fontSize: 12, + fontSize: 13, fontWeight: FontWeight.bold, - color: ColorConstants.tertiary, + color: ColorConstants.onTertiary, ), overflow: TextOverflow.ellipsis, ), Text( - '$participantsCount participants', + subtitle, style: const TextStyle( - fontSize: 10, + fontSize: 11, color: ColorConstants.secondary, ), overflow: TextOverflow.ellipsis, @@ -57,18 +47,29 @@ class EventAction extends StatelessWidget { // Action button GestureDetector( - onTap: onActionPressed, + onTap: () { + if (isActionEnabled) onActionPressed!.call(); + }, child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + width: 100, decoration: BoxDecoration( - color: ColorConstants.tertiary, + color: isActionEnabled + ? ColorConstants.background + : ColorConstants.tertiary, borderRadius: BorderRadius.circular(20), + border: Border.all(color: ColorConstants.tertiary, width: 2), ), - child: Text( - actionButtonText, - style: const TextStyle( - fontSize: 12, - color: ColorConstants.background, + child: Center( + child: Text( + actionButtonText, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: isActionEnabled + ? ColorConstants.tertiary + : ColorConstants.background, + ), ), ), ), diff --git a/lib/feed/ui/pages/main_page/event_card.dart b/lib/feed/ui/pages/main_page/event_card.dart index 60a9551cc2..d00e085d9e 100644 --- a/lib/feed/ui/pages/main_page/event_card.dart +++ b/lib/feed/ui/pages/main_page/event_card.dart @@ -21,7 +21,7 @@ class EventCard extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - SizedBox(height: 50), + SizedBox(height: 70), Text( item.title, style: const TextStyle( @@ -42,19 +42,13 @@ class EventCard extends StatelessWidget { ), if (item.isTerminated) Positioned( - top: 0, - right: 0, + bottom: 53, + left: 15, child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 10, - vertical: 5, - ), + padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 3), decoration: const BoxDecoration( color: ColorConstants.main, - borderRadius: BorderRadius.only( - topRight: Radius.circular(15), - bottomLeft: Radius.circular(15), - ), + borderRadius: BorderRadius.all(Radius.circular(5)), ), child: const Text( 'Terminé', @@ -65,6 +59,22 @@ class EventCard extends StatelessWidget { ), ), ), + if (item.isOngoing) + Positioned( + bottom: 53, + left: 15, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 3), + decoration: const BoxDecoration( + color: ColorConstants.background, + borderRadius: BorderRadius.all(Radius.circular(5)), + ), + child: const Text( + 'En cours', + style: TextStyle(color: ColorConstants.main, fontSize: 10), + ), + ), + ), ], ), ); diff --git a/lib/feed/ui/pages/main_page/feed_timeline.dart b/lib/feed/ui/pages/main_page/feed_timeline.dart index 5607d6ce21..30348943fb 100644 --- a/lib/feed/ui/pages/main_page/feed_timeline.dart +++ b/lib/feed/ui/pages/main_page/feed_timeline.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:titan/feed/class/feed_item.dart'; import 'package:titan/feed/ui/pages/main_page/time_line_item.dart'; -import 'package:titan/tools/ui/widgets/dotted_vertical_line.dart'; class FeedTimeline extends StatelessWidget { final List items; diff --git a/lib/feed/ui/pages/main_page/main_page.dart b/lib/feed/ui/pages/main_page/main_page.dart index c794377281..2c983a7da4 100644 --- a/lib/feed/ui/pages/main_page/main_page.dart +++ b/lib/feed/ui/pages/main_page/main_page.dart @@ -40,7 +40,7 @@ class FeedMainPage extends HookConsumerWidget { // // Timeline SizedBox( - height: MediaQuery.of(context).size.height - 190, + height: MediaQuery.of(context).size.height - 193, child: SingleChildScrollView( physics: const BouncingScrollPhysics(), child: FeedTimeline( diff --git a/lib/feed/ui/pages/main_page/time_line_item.dart b/lib/feed/ui/pages/main_page/time_line_item.dart index c621f0fa54..008b50464a 100644 --- a/lib/feed/ui/pages/main_page/time_line_item.dart +++ b/lib/feed/ui/pages/main_page/time_line_item.dart @@ -61,7 +61,7 @@ class TimelineItem extends StatelessWidget { ), ], ), - if (item.needsRegistration) + if (item.type != FeedItemType.announcement) Padding( padding: const EdgeInsets.only(top: 10), child: Row( @@ -81,7 +81,21 @@ class TimelineItem extends StatelessWidget { ), ), ), - Expanded(child: EventAction()), + Expanded( + child: EventAction( + title: item.type == FeedItemType.action + ? 'Tu peux voter' + : 'Tu es invité', + subtitle: item.type == FeedItemType.action + ? '254 votants' + : '75 participants', + onActionPressed: item.onRegister, + actionButtonText: item.type == FeedItemType.action + ? 'Participer' + : 'Voter', + isActionEnabled: true, + ), + ), ], ), ), diff --git a/lib/navigation/ui/drawer_template.dart b/lib/navigation/ui/drawer_template.dart index e4c628199d..974dd09bbc 100644 --- a/lib/navigation/ui/drawer_template.dart +++ b/lib/navigation/ui/drawer_template.dart @@ -62,12 +62,7 @@ class DrawerTemplate extends HookConsumerWidget { ), ), SizedBox(height: 10), - Expanded( - child: SingleChildScrollView( - physics: const BouncingScrollPhysics(), - child: Container(color: Colors.yellow, child: child), - ), - ), + Expanded(child: child), ], ), Positioned( From 68e0829566bdd7d5722327b5e595ea8c976c2d57 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sat, 5 Jul 2025 01:58:34 +0200 Subject: [PATCH 020/473] feat: dotted line --- lib/feed/ui/pages/main_page/event_action.dart | 4 +- .../ui/pages/main_page/feed_timeline.dart | 10 +- .../ui/pages/main_page/time_line_item.dart | 153 +++++++++--------- 3 files changed, 86 insertions(+), 81 deletions(-) diff --git a/lib/feed/ui/pages/main_page/event_action.dart b/lib/feed/ui/pages/main_page/event_action.dart index 56a7e12880..4efb8d2fc2 100644 --- a/lib/feed/ui/pages/main_page/event_action.dart +++ b/lib/feed/ui/pages/main_page/event_action.dart @@ -43,7 +43,7 @@ class EventAction extends StatelessWidget { ], ), - const SizedBox(width: 8), + const SizedBox(width: 10), // Action button GestureDetector( @@ -51,7 +51,7 @@ class EventAction extends StatelessWidget { if (isActionEnabled) onActionPressed!.call(); }, child: Container( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 5), width: 100, decoration: BoxDecoration( color: isActionEnabled diff --git a/lib/feed/ui/pages/main_page/feed_timeline.dart b/lib/feed/ui/pages/main_page/feed_timeline.dart index 30348943fb..bf7aca1439 100644 --- a/lib/feed/ui/pages/main_page/feed_timeline.dart +++ b/lib/feed/ui/pages/main_page/feed_timeline.dart @@ -12,14 +12,12 @@ class FeedTimeline extends StatelessWidget { Widget build(BuildContext context) { return Column( children: [ - ...items.map((item) { - final index = items.indexOf(item); - return TimelineItem( + ...items.map( + (item) => TimelineItem( item: item, onTap: onItemTap != null ? () => onItemTap!(item) : null, - isLast: index == items.length - 1, - ); - }), + ), + ), SizedBox(height: 80), ], ); diff --git a/lib/feed/ui/pages/main_page/time_line_item.dart b/lib/feed/ui/pages/main_page/time_line_item.dart index 008b50464a..ab607faf8b 100644 --- a/lib/feed/ui/pages/main_page/time_line_item.dart +++ b/lib/feed/ui/pages/main_page/time_line_item.dart @@ -9,96 +9,103 @@ import 'package:titan/feed/ui/pages/main_page/dotted_vertical_line.dart'; class TimelineItem extends StatelessWidget { final FeedItem item; final VoidCallback? onTap; - final bool isLast; - const TimelineItem({ - super.key, - required this.item, - this.onTap, - this.isLast = false, - }); + const TimelineItem({super.key, required this.item, this.onTap}); @override Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 15), - child: Column( - mainAxisSize: MainAxisSize.min, + return SizedBox( + height: item.type == FeedItemType.announcement ? 160 : 200, + child: Stack( children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.only(left: 10, right: 30), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, + Padding( + padding: const EdgeInsets.only(left: 23), + child: DottedVerticalLine(), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 15), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - DateFormat('d').format(item.date), - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: ColorConstants.main, + Container( + padding: const EdgeInsets.only(left: 10, right: 30), + color: ColorConstants.background, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + DateFormat('d').format(item.date), + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: ColorConstants.main, + ), + ), + Text( + DateFormat('MMM').format(item.date).toUpperCase(), + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: ColorConstants.onTertiary, + ), + ), + // Expanded(child: DottedVerticalLine()), + ], ), ), - Text( - DateFormat('MMM').format(item.date).toUpperCase(), - style: const TextStyle( - fontSize: 12, - fontWeight: FontWeight.bold, - color: ColorConstants.onTertiary, + Expanded( + child: GestureDetector( + onTap: onTap, + child: EventCard(item: item), ), ), - // Expanded(child: DottedVerticalLine()), ], ), - ), - Expanded( - child: GestureDetector( - onTap: onTap, - child: EventCard(item: item), - ), - ), - ], - ), - if (item.type != FeedItemType.announcement) - Padding( - padding: const EdgeInsets.only(top: 10), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ + if (item.type != FeedItemType.announcement) Padding( - padding: const EdgeInsets.only(left: 15, right: 45), - child: Container( - width: 12, - height: 12, - decoration: BoxDecoration( - shape: BoxShape.circle, - border: Border.all( - color: ColorConstants.secondary, - width: 2, + padding: const EdgeInsets.only(top: 8), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(left: 14, right: 45), + child: Container( + width: 20, + height: 20, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: ColorConstants.background, + border: Border.all( + color: ColorConstants.secondary, + width: 2, + ), + ), + ), ), - ), - ), - ), - Expanded( - child: EventAction( - title: item.type == FeedItemType.action - ? 'Tu peux voter' - : 'Tu es invité', - subtitle: item.type == FeedItemType.action - ? '254 votants' - : '75 participants', - onActionPressed: item.onRegister, - actionButtonText: item.type == FeedItemType.action - ? 'Participer' - : 'Voter', - isActionEnabled: true, + Expanded( + child: EventAction( + title: item.type == FeedItemType.action + ? 'Tu peux voter' + : 'Tu es invité', + subtitle: item.type == FeedItemType.action + ? '254 votants' + : '75 participants', + onActionPressed: item.onRegister, + actionButtonText: item.type == FeedItemType.action + ? 'Participer' + : 'Voter', + isActionEnabled: true, + ), + ), + ], ), ), - ], - ), + ], ), + ), ], ), ); From a7f5bf8184abddd4deac74e36e0f4794100a4162 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sat, 5 Jul 2025 01:58:34 +0200 Subject: [PATCH 021/473] fix: disappearing navbar on bottom modal opening --- lib/feed/ui/feed.dart | 10 +- lib/feed/ui/pages/main_page/main_page.dart | 17 +- .../ui/pages/main_page/time_line_item.dart | 1 - lib/main.dart | 9 + .../providers/navbar_animation.dart | 38 ++++ lib/navigation/ui/all_module_page.dart | 55 +++--- lib/navigation/ui/drawer_template.dart | 178 +++++++++--------- lib/navigation/ui/top_bar.dart | 24 +++ .../ui/styleguide/bottom_modal_template.dart | 24 +++ 9 files changed, 245 insertions(+), 111 deletions(-) create mode 100644 lib/navigation/providers/navbar_animation.dart create mode 100644 lib/navigation/ui/top_bar.dart diff --git a/lib/feed/ui/feed.dart b/lib/feed/ui/feed.dart index ba6ef1dd9a..a828802dc6 100644 --- a/lib/feed/ui/feed.dart +++ b/lib/feed/ui/feed.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:titan/tools/constants.dart'; +import 'package:titan/navigation/ui/top_bar.dart'; class FeedTemplate extends StatelessWidget { final Widget child; @@ -7,6 +7,12 @@ class FeedTemplate extends StatelessWidget { @override Widget build(BuildContext context) { - return Container(color: ColorConstants.background, child: child); + return Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + TopBar(), + Expanded(child: child), + ], + ); } } diff --git a/lib/feed/ui/pages/main_page/main_page.dart b/lib/feed/ui/pages/main_page/main_page.dart index 2c983a7da4..cb93131ebe 100644 --- a/lib/feed/ui/pages/main_page/main_page.dart +++ b/lib/feed/ui/pages/main_page/main_page.dart @@ -4,7 +4,10 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/feed/class/feed_item.dart'; import 'package:titan/feed/ui/feed.dart'; import 'package:titan/feed/ui/pages/main_page/feed_timeline.dart'; +import 'package:titan/navigation/providers/navbar_animation.dart'; +import 'package:titan/navigation/ui/drawer_template.dart'; import 'package:titan/tools/constants.dart'; +import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; import 'package:titan/tools/ui/styleguide/searchbar.dart'; class FeedMainPage extends HookConsumerWidget { @@ -22,7 +25,19 @@ class FeedMainPage extends HookConsumerWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ // Search bar - CustomSearchBar(onFilter: () {}, onSearch: (_) {}), + CustomSearchBar( + onFilter: () async { + await showCustomBottomModal( + modal: BottomModalTemplate( + title: 'Filtrer', + child: Container(), + ), + context: context, + ref: ref, + ); + }, + onSearch: (_) {}, + ), const SizedBox(height: 20), diff --git a/lib/feed/ui/pages/main_page/time_line_item.dart b/lib/feed/ui/pages/main_page/time_line_item.dart index ab607faf8b..6bb4b6bf9a 100644 --- a/lib/feed/ui/pages/main_page/time_line_item.dart +++ b/lib/feed/ui/pages/main_page/time_line_item.dart @@ -52,7 +52,6 @@ class TimelineItem extends StatelessWidget { color: ColorConstants.onTertiary, ), ), - // Expanded(child: DottedVerticalLine()), ], ), ), diff --git a/lib/main.dart b/lib/main.dart index 62075d540c..2cbe38a11e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -10,6 +10,7 @@ import 'package:titan/login/providers/animation_provider.dart'; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:google_fonts/google_fonts.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/navigation/providers/navbar_animation.dart'; import 'package:titan/router.dart'; import 'package:titan/service/tools/setup.dart'; import 'package:titan/tools/constants.dart'; @@ -50,11 +51,19 @@ class MyApp extends HookConsumerWidget { final animationController = useAnimationController( duration: const Duration(seconds: 2), ); + final navbarAnimationController = useAnimationController( + duration: const Duration(milliseconds: 200), + initialValue: 1.0, + ); final animationNotifier = ref.read(backgroundAnimationProvider.notifier); + final navbarAnimationNotifier = ref.read(navbarAnimationProvider.notifier); final navigatorKey = GlobalKey(); final plausible = getPlausible(); final pathForwardingNotifier = ref.watch(pathForwardingProvider.notifier); Future(() => animationNotifier.setController(animationController)); + Future( + () => navbarAnimationNotifier.setController(navbarAnimationController), + ); if (!kIsWeb) { useEffect(() { diff --git a/lib/navigation/providers/navbar_animation.dart b/lib/navigation/providers/navbar_animation.dart new file mode 100644 index 0000000000..be0de3bbd7 --- /dev/null +++ b/lib/navigation/providers/navbar_animation.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class NavbarAnimationProvider extends StateNotifier { + NavbarAnimationProvider() : super(null); + + void setController(AnimationController controller) { + state = controller; + } + + void toggle() { + if (state == null) { + return; + } + if (state!.isCompleted) { + state!.reverse(); + } else { + state!.forward(); + } + } + + double get value { + if (state == null) { + return 0; + } + return state!.value; + } + + AnimationController? get animation { + return state; + } +} + +final navbarAnimationProvider = StateNotifierProvider(( + ref, +) { + return NavbarAnimationProvider(); +}); diff --git a/lib/navigation/ui/all_module_page.dart b/lib/navigation/ui/all_module_page.dart index 36682080ee..396178fa76 100644 --- a/lib/navigation/ui/all_module_page.dart +++ b/lib/navigation/ui/all_module_page.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/navigation/providers/navbar_module_list.dart'; +import 'package:titan/navigation/ui/top_bar.dart'; import 'package:titan/settings/providers/module_list_provider.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/providers/path_forwarding_provider.dart'; @@ -17,30 +18,40 @@ class AllModulePage extends ConsumerWidget { final navbarListModuleNotifier = ref.watch( navbarListModuleProvider.notifier, ); - return Container( - color: ColorConstants.background, - padding: const EdgeInsets.symmetric(horizontal: 20.0), - child: Column( - children: [ - CustomSearchBar(onSearch: (String query) {}, onFilter: () {}), - SizedBox(height: 30), - ...modules.map( - (module) => ListItem( - title: module.name, - subtitle: module.description, - onTap: () { - navbarListModuleNotifier.pushModule(module); - final pathForwardingNotifier = ref.watch( - pathForwardingProvider.notifier, - ); - pathForwardingNotifier.forward(module.root); - QR.to(module.root); - }, + return Column( + children: [ + TopBar(), + Expanded( + child: SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: Container( + color: ColorConstants.background, + padding: const EdgeInsets.symmetric(horizontal: 20.0), + child: Column( + children: [ + CustomSearchBar(onSearch: (String query) {}, onFilter: () {}), + SizedBox(height: 30), + ...modules.map( + (module) => ListItem( + title: module.name, + subtitle: module.description, + onTap: () { + navbarListModuleNotifier.pushModule(module); + final pathForwardingNotifier = ref.watch( + pathForwardingProvider.notifier, + ); + pathForwardingNotifier.forward(module.root); + QR.to(module.root); + }, + ), + ), + SizedBox(height: 80), + ], + ), ), ), - SizedBox(height: 80), - ], - ), + ), + ], ); } } diff --git a/lib/navigation/ui/drawer_template.dart b/lib/navigation/ui/drawer_template.dart index 974dd09bbc..2f7877a8ef 100644 --- a/lib/navigation/ui/drawer_template.dart +++ b/lib/navigation/ui/drawer_template.dart @@ -5,6 +5,7 @@ import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/feed/router.dart'; import 'package:titan/navigation/class/module.dart'; import 'package:titan/navigation/providers/display_quit_popup.dart'; +import 'package:titan/navigation/providers/navbar_animation.dart'; import 'package:titan/navigation/providers/navbar_module_list.dart'; import 'package:titan/navigation/providers/should_setup_provider.dart'; import 'package:titan/router.dart'; @@ -14,6 +15,9 @@ import 'package:titan/tools/providers/path_forwarding_provider.dart'; import 'package:titan/tools/ui/styleguide/navbar.dart'; import 'package:titan/user/providers/user_provider.dart'; +// Global navigator key that can be used to ensure bottom sheets appear above the navbar +final GlobalKey rootNavigatorKey = GlobalKey(); + class DrawerTemplate extends HookConsumerWidget { static Duration duration = const Duration(milliseconds: 200); static const double maxSlide = 255; @@ -34,6 +38,7 @@ class DrawerTemplate extends HookConsumerWidget { final displayQuit = ref.watch(displayQuitProvider); final shouldSetup = ref.watch(shouldSetupProvider); final shouldSetupNotifier = ref.read(shouldSetupProvider.notifier); + final animation = ref.watch(navbarAnimationProvider); Future(() { if (!kIsWeb && user.id != "" && shouldSetup) { @@ -42,100 +47,103 @@ class DrawerTemplate extends HookConsumerWidget { } }); - return Scaffold( - body: SafeArea( - child: Stack( - children: [ - Column( - mainAxisAlignment: MainAxisAlignment.start, + return Builder( + builder: (context) { + return Scaffold( + body: SafeArea( + child: Stack( children: [ - Container( - padding: const EdgeInsets.all(15.0), - child: Center( - child: Text( - 'MyEMApp', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w900, - ), - ), - ), - ), - SizedBox(height: 10), - Expanded(child: child), - ], - ), - Positioned( - left: 0, - bottom: 0, - right: 0, - child: FloatingNavbar( - items: [ - FloatingNavbarItem( - module: FeedRouter.module, - onTap: () { - // Use ref.read instead of ref.watch to avoid rebuilds - final pathForwardingNotifier = ref.read( - pathForwardingProvider.notifier, - ); + child, + Positioned( + left: 0, + bottom: 0, + right: 0, + child: Visibility( + visible: animation!.isCompleted && animation.value == 1.0, + child: AnimatedBuilder( + animation: animation, + builder: (context, child) => Opacity( + opacity: animation.value, + child: FloatingNavbar( + items: [ + FloatingNavbarItem( + module: FeedRouter.module, + onTap: () { + // Use ref.read instead of ref.watch to avoid rebuilds + final pathForwardingNotifier = ref.read( + pathForwardingProvider.notifier, + ); - // First update the path - pathForwardingNotifier.forward(FeedRouter.root); + // First update the path + pathForwardingNotifier.forward(FeedRouter.root); - // Then navigate with a small delay to allow the UI to stabilize - WidgetsBinding.instance.addPostFrameCallback((_) { - QR.to(FeedRouter.root); - }); - }, - ), - ...navbarListModule.map((module) { - return FloatingNavbarItem( - module: module, - onTap: () { - // Use ref.read instead of ref.watch to avoid rebuilds - navbarListModuleNotifier.pushModule(module); - final pathForwardingNotifier = ref.read( - pathForwardingProvider.notifier, - ); + // Then navigate with a small delay to allow the UI to stabilize + WidgetsBinding.instance.addPostFrameCallback(( + _, + ) { + QR.to(FeedRouter.root); + }); + }, + ), + ...navbarListModule.map((module) { + return FloatingNavbarItem( + module: module, + onTap: () { + // Use ref.read instead of ref.watch to avoid rebuilds + navbarListModuleNotifier.pushModule(module); + final pathForwardingNotifier = ref.read( + pathForwardingProvider.notifier, + ); - // First update the path - pathForwardingNotifier.forward(module.root); + // First update the path + pathForwardingNotifier.forward(module.root); - // Then navigate with a small delay to allow the UI to stabilize - WidgetsBinding.instance.addPostFrameCallback((_) { - QR.to(module.root); - }); - }, - ); - }), - FloatingNavbarItem( - module: Module( - name: 'Autres', - description: '', - root: AppRouter.allModules, - ), - onTap: () { - // Use ref.read instead of ref.watch to avoid rebuilds - final pathForwardingNotifier = ref.read( - pathForwardingProvider.notifier, - ); + // Then navigate with a small delay to allow the UI to stabilize + WidgetsBinding.instance.addPostFrameCallback(( + _, + ) { + QR.to(module.root); + }); + }, + ); + }), + FloatingNavbarItem( + module: Module( + name: 'Autres', + description: '', + root: AppRouter.allModules, + ), + onTap: () { + // Use ref.read instead of ref.watch to avoid rebuilds + final pathForwardingNotifier = ref.read( + pathForwardingProvider.notifier, + ); - // First update the path - pathForwardingNotifier.forward(AppRouter.allModules); + // First update the path + pathForwardingNotifier.forward( + AppRouter.allModules, + ); - // Then navigate with a small delay to allow the UI to stabilize - WidgetsBinding.instance.addPostFrameCallback((_) { - QR.to(AppRouter.allModules); - }); - }, + // Then navigate with a small delay to allow the UI to stabilize + WidgetsBinding.instance.addPostFrameCallback(( + _, + ) { + QR.to(AppRouter.allModules); + }); + }, + ), + ], + ), + ), + ), ), - ], - ), + ), + if (displayQuit) const QuitDialog(), + ], ), - if (displayQuit) const QuitDialog(), - ], - ), - ), + ), + ); + }, ); } } diff --git a/lib/navigation/ui/top_bar.dart b/lib/navigation/ui/top_bar.dart new file mode 100644 index 0000000000..810da3197e --- /dev/null +++ b/lib/navigation/ui/top_bar.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; + +class TopBar extends StatelessWidget { + const TopBar({super.key}); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + padding: const EdgeInsets.all(15.0), + child: Center( + child: Text( + 'MyEMApp', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.w900), + ), + ), + ), + SizedBox(height: 10), + ], + ); + } +} diff --git a/lib/tools/ui/styleguide/bottom_modal_template.dart b/lib/tools/ui/styleguide/bottom_modal_template.dart index a7ba10fb3f..802001badd 100644 --- a/lib/tools/ui/styleguide/bottom_modal_template.dart +++ b/lib/tools/ui/styleguide/bottom_modal_template.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/navigation/providers/navbar_animation.dart'; import 'package:titan/tools/constants.dart'; enum BottomModalType { main, danger } @@ -92,6 +94,7 @@ class BottomModalTemplate extends StatelessWidget { child, if (actions != null && actions!.isNotEmpty) Column(children: actions!), + SizedBox(height: 20), ], ), ), @@ -100,3 +103,24 @@ class BottomModalTemplate extends StatelessWidget { ); } } + +Future showCustomBottomModal({ + required BuildContext context, + required Widget modal, + required WidgetRef ref, + Function? onCloseCallback, +}) async { + final navbarAnimationNotifier = ref.watch(navbarAnimationProvider.notifier); + navbarAnimationNotifier.toggle(); + await showModalBottomSheet( + elevation: 3, + backgroundColor: Colors.transparent, + isScrollControlled: true, + useRootNavigator: true, + context: context, + builder: (_) => modal, + ).then((value) { + navbarAnimationNotifier.toggle(); + onCloseCallback?.call(); + }); +} From 11ad651a8c04866201e92d42a46406a55114286b Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sat, 5 Jul 2025 01:58:34 +0200 Subject: [PATCH 022/473] fix: cleanup --- lib/feed/ui/pages/main_page/main_page.dart | 10 +--- lib/main.dart | 4 +- lib/navigation/ui/drawer_template.dart | 25 +--------- lib/tools/ui/styleguide/navbar.dart | 55 ++-------------------- 4 files changed, 7 insertions(+), 87 deletions(-) diff --git a/lib/feed/ui/pages/main_page/main_page.dart b/lib/feed/ui/pages/main_page/main_page.dart index cb93131ebe..e9519bc665 100644 --- a/lib/feed/ui/pages/main_page/main_page.dart +++ b/lib/feed/ui/pages/main_page/main_page.dart @@ -4,8 +4,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/feed/class/feed_item.dart'; import 'package:titan/feed/ui/feed.dart'; import 'package:titan/feed/ui/pages/main_page/feed_timeline.dart'; -import 'package:titan/navigation/providers/navbar_animation.dart'; -import 'package:titan/navigation/ui/drawer_template.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; import 'package:titan/tools/ui/styleguide/searchbar.dart'; @@ -53,19 +51,13 @@ class FeedMainPage extends HookConsumerWidget { const SizedBox(height: 20), - // // Timeline SizedBox( height: MediaQuery.of(context).size.height - 193, child: SingleChildScrollView( physics: const BouncingScrollPhysics(), child: FeedTimeline( items: filteredItems.value, - onItemTap: (item) { - // TODO: Handle item tap - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Event tapped: ${item.title}')), - ); - }, + onItemTap: (item) {}, ), ), ), diff --git a/lib/main.dart b/lib/main.dart index 2cbe38a11e..0fb36be3ce 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -61,9 +61,7 @@ class MyApp extends HookConsumerWidget { final plausible = getPlausible(); final pathForwardingNotifier = ref.watch(pathForwardingProvider.notifier); Future(() => animationNotifier.setController(animationController)); - Future( - () => navbarAnimationNotifier.setController(navbarAnimationController), - ); + Future(() => navbarAnimationNotifier.setController(navbarAnimationController)); if (!kIsWeb) { useEffect(() { diff --git a/lib/navigation/ui/drawer_template.dart b/lib/navigation/ui/drawer_template.dart index 2f7877a8ef..7c47dfccff 100644 --- a/lib/navigation/ui/drawer_template.dart +++ b/lib/navigation/ui/drawer_template.dart @@ -39,6 +39,7 @@ class DrawerTemplate extends HookConsumerWidget { final shouldSetup = ref.watch(shouldSetupProvider); final shouldSetupNotifier = ref.read(shouldSetupProvider.notifier); final animation = ref.watch(navbarAnimationProvider); + final pathForwardingNotifier = ref.read(pathForwardingProvider.notifier); Future(() { if (!kIsWeb && user.id != "" && shouldSetup) { @@ -69,15 +70,7 @@ class DrawerTemplate extends HookConsumerWidget { FloatingNavbarItem( module: FeedRouter.module, onTap: () { - // Use ref.read instead of ref.watch to avoid rebuilds - final pathForwardingNotifier = ref.read( - pathForwardingProvider.notifier, - ); - - // First update the path pathForwardingNotifier.forward(FeedRouter.root); - - // Then navigate with a small delay to allow the UI to stabilize WidgetsBinding.instance.addPostFrameCallback(( _, ) { @@ -89,16 +82,8 @@ class DrawerTemplate extends HookConsumerWidget { return FloatingNavbarItem( module: module, onTap: () { - // Use ref.read instead of ref.watch to avoid rebuilds navbarListModuleNotifier.pushModule(module); - final pathForwardingNotifier = ref.read( - pathForwardingProvider.notifier, - ); - - // First update the path pathForwardingNotifier.forward(module.root); - - // Then navigate with a small delay to allow the UI to stabilize WidgetsBinding.instance.addPostFrameCallback(( _, ) { @@ -114,17 +99,9 @@ class DrawerTemplate extends HookConsumerWidget { root: AppRouter.allModules, ), onTap: () { - // Use ref.read instead of ref.watch to avoid rebuilds - final pathForwardingNotifier = ref.read( - pathForwardingProvider.notifier, - ); - - // First update the path pathForwardingNotifier.forward( AppRouter.allModules, ); - - // Then navigate with a small delay to allow the UI to stabilize WidgetsBinding.instance.addPostFrameCallback(( _, ) { diff --git a/lib/tools/ui/styleguide/navbar.dart b/lib/tools/ui/styleguide/navbar.dart index cbe258f2b1..f895fe612a 100644 --- a/lib/tools/ui/styleguide/navbar.dart +++ b/lib/tools/ui/styleguide/navbar.dart @@ -14,7 +14,6 @@ class FloatingNavbarItem { FloatingNavbarItem({this.onTap, required this.module}); } -// Get the current route path String getCurrentPath() { if (QR.history.isEmpty) return ''; @@ -32,49 +31,36 @@ class FloatingNavbar extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final pathProvider = ref.watch(pathForwardingProvider); - // Track previous index for animation final previousIndex = useRef(0); - // Use useState to maintain internal state for animation - final currentState = useState(3); // Default to "Autres" + final currentState = useState(3); - // Get the path from router final currentPath = pathProvider.path; - - // Calculate the selected index based on the current path final routeIndex = useState(3); useEffect(() { - // This effect runs on every build, but we only care about the initial path if (currentPath.isNotEmpty) { - print("Current path: $currentPath"); - print("items ${items.map((e) => e.module.root).toList()}"); routeIndex.value = items.indexWhere( (item) => item.module.root == currentPath, ); - // Only use found index if it's in visible range if (routeIndex.value < 0 || routeIndex.value >= items.length) { - routeIndex.value = 3; // No match or not in visible modules + routeIndex.value = 3; } } - print("Selected index: $routeIndex"); return null; }, [currentPath]); - // Initialize the currentState on first render only useEffect(() { currentState.value = routeIndex.value; - previousIndex.value = routeIndex.value; // Initialize previous index + previousIndex.value = routeIndex.value; return null; }, []); final borderRadius = 25.0; - // Animation controller for all animations with proper cleanup final animationController = useAnimationController( duration: const Duration(milliseconds: 300), ); - // Ensure proper disposal useEffect(() { return () { if (animationController.isAnimating) { @@ -83,28 +69,19 @@ class FloatingNavbar extends HookConsumerWidget { }; }, []); - // Slide animation reference final slideAnimation = useRef?>(null); - - // Store the latest calculated item width to use in animations final itemWidthRef = useRef(0.0); - // Watch for path changes and update the selection with proper animation useEffect(() { if (currentPath.isNotEmpty && routeIndex.value != currentState.value) { - // When path changes, store current selection as previous previousIndex.value = currentState.value; - // Then update to the new route-based selection currentState.value = routeIndex.value; } return null; }, [currentPath, routeIndex]); - // Update animation when index changes - this needs to be in the build method, not in LayoutBuilder useEffect(() { - // Only trigger animation if we have a valid item width and the index has changed if (previousIndex.value != currentState.value && itemWidthRef.value > 0) { - // Create tween from previous to current position slideAnimation.value = Tween( begin: previousIndex.value * itemWidthRef.value, @@ -112,18 +89,15 @@ class FloatingNavbar extends HookConsumerWidget { ).animate( CurvedAnimation( parent: animationController, - curve: Curves.easeOutCubic, // Smoother easing curve + curve: Curves.easeOutCubic, ), ); - - // Reset and start animation animationController.reset(); animationController.forward(); } return null; }, [currentState.value, itemWidthRef.value]); - // Use LayoutBuilder for proper sizing return Padding( padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 16), child: Material( @@ -139,20 +113,16 @@ class FloatingNavbar extends HookConsumerWidget { ), child: LayoutBuilder( builder: (context, constraints) { - // Calculate item width based on actual available width final availableWidth = constraints.maxWidth; final itemWidth = availableWidth / items.length; - // Store the width for use in the useEffect hook itemWidthRef.value = itemWidth; return Stack( children: [ - // Animated selection indicator with smooth slide AnimatedBuilder( animation: animationController, builder: (context, _) { - // Get current position from slide animation or fall back to current index final leftPosition = slideAnimation.value != null ? slideAnimation.value!.value : itemWidth * currentState.value; @@ -171,7 +141,6 @@ class FloatingNavbar extends HookConsumerWidget { ); }, ), - // Items row Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: items.asMap().entries.map((entry) { @@ -179,7 +148,6 @@ class FloatingNavbar extends HookConsumerWidget { final item = entry.value; final isSelected = index == currentState.value; - // Use AnimatedBuilder for text color to sync with indicator animation return Expanded( child: Material( color: Colors.transparent, @@ -187,27 +155,20 @@ class FloatingNavbar extends HookConsumerWidget { child: GestureDetector( behavior: HitTestBehavior.opaque, onTap: () { - // Only animate if this isn't already the selected item if (index != currentState.value) { - // First stop any running animation if (animationController.isAnimating) { animationController.stop(); } - // Store current index as previous for animation previousIndex.value = currentState.value; - // Update the internal state immediately for animation currentState.value = index; - // Schedule navigation after the current frame completes WidgetsBinding.instance.addPostFrameCallback(( _, ) { - // Now it's safe to navigate item.onTap?.call(); }); } else { - // If already selected, just call the callback item.onTap?.call(); } }, @@ -216,13 +177,11 @@ class FloatingNavbar extends HookConsumerWidget { child: AnimatedBuilder( animation: animationController, builder: (context, child) { - // Calculate color and weight based on selection and animation Color textColor; FontWeight textWeight; if (previousIndex.value == currentState.value) { - // No transition happening textColor = isSelected ? ColorConstants.main : ColorConstants.background; @@ -230,17 +189,14 @@ class FloatingNavbar extends HookConsumerWidget { ? FontWeight.w600 : FontWeight.normal; } else { - // During transition, determine if this item is involved bool isInvolved = index == previousIndex.value || index == currentState.value; if (!isInvolved) { - // Not involved in transition textColor = ColorConstants.background; textWeight = FontWeight.normal; } else if (index == currentState.value) { - // Transitioning to selected final progress = animationController.value; textColor = Color.lerp( @@ -248,12 +204,10 @@ class FloatingNavbar extends HookConsumerWidget { ColorConstants.main, progress, )!; - // Use a simpler approach for font weight transition textWeight = progress < 0.5 ? FontWeight.normal : FontWeight.w600; } else { - // Transitioning from selected final progress = animationController.value; textColor = Color.lerp( @@ -261,7 +215,6 @@ class FloatingNavbar extends HookConsumerWidget { ColorConstants.background, progress, )!; - // Use a simpler approach for font weight transition textWeight = progress < 0.5 ? FontWeight.w600 : FontWeight.normal; From 80b213a9f913d7a95adf9dcc2703315120559b52 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sat, 5 Jul 2025 01:58:35 +0200 Subject: [PATCH 023/473] fix: background color --- lib/feed/ui/feed.dart | 16 ++++--- lib/navigation/ui/all_module_page.dart | 65 ++++++++++++++------------ 2 files changed, 45 insertions(+), 36 deletions(-) diff --git a/lib/feed/ui/feed.dart b/lib/feed/ui/feed.dart index a828802dc6..13bc174bd7 100644 --- a/lib/feed/ui/feed.dart +++ b/lib/feed/ui/feed.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:titan/navigation/ui/top_bar.dart'; +import 'package:titan/tools/constants.dart'; class FeedTemplate extends StatelessWidget { final Widget child; @@ -7,12 +8,15 @@ class FeedTemplate extends StatelessWidget { @override Widget build(BuildContext context) { - return Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - TopBar(), - Expanded(child: child), - ], + return Container( + color: ColorConstants.background, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + TopBar(), + Expanded(child: child), + ], + ), ); } } diff --git a/lib/navigation/ui/all_module_page.dart b/lib/navigation/ui/all_module_page.dart index 396178fa76..b4742666ef 100644 --- a/lib/navigation/ui/all_module_page.dart +++ b/lib/navigation/ui/all_module_page.dart @@ -18,40 +18,45 @@ class AllModulePage extends ConsumerWidget { final navbarListModuleNotifier = ref.watch( navbarListModuleProvider.notifier, ); - return Column( - children: [ - TopBar(), - Expanded( - child: SingleChildScrollView( - physics: const BouncingScrollPhysics(), - child: Container( - color: ColorConstants.background, - padding: const EdgeInsets.symmetric(horizontal: 20.0), - child: Column( - children: [ - CustomSearchBar(onSearch: (String query) {}, onFilter: () {}), - SizedBox(height: 30), - ...modules.map( - (module) => ListItem( - title: module.name, - subtitle: module.description, - onTap: () { - navbarListModuleNotifier.pushModule(module); - final pathForwardingNotifier = ref.watch( - pathForwardingProvider.notifier, - ); - pathForwardingNotifier.forward(module.root); - QR.to(module.root); - }, + return Container( + color: ColorConstants.background, + child: Column( + children: [ + TopBar(), + Expanded( + child: SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + child: Column( + children: [ + CustomSearchBar( + onSearch: (String query) {}, + onFilter: () {}, ), - ), - SizedBox(height: 80), - ], + SizedBox(height: 30), + ...modules.map( + (module) => ListItem( + title: module.name, + subtitle: module.description, + onTap: () { + navbarListModuleNotifier.pushModule(module); + final pathForwardingNotifier = ref.watch( + pathForwardingProvider.notifier, + ); + pathForwardingNotifier.forward(module.root); + QR.to(module.root); + }, + ), + ), + SizedBox(height: 80), + ], + ), ), ), ), - ), - ], + ], + ), ); } } From 126bb4d31c4fec34dcd53bdc3566d30cf585f8a3 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sat, 5 Jul 2025 01:58:35 +0200 Subject: [PATCH 024/473] fix: navbar padding --- lib/tools/ui/styleguide/navbar.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/tools/ui/styleguide/navbar.dart b/lib/tools/ui/styleguide/navbar.dart index f895fe612a..6f821149a3 100644 --- a/lib/tools/ui/styleguide/navbar.dart +++ b/lib/tools/ui/styleguide/navbar.dart @@ -99,7 +99,7 @@ class FloatingNavbar extends HookConsumerWidget { }, [currentState.value, itemWidthRef.value]); return Padding( - padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 16), + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), child: Material( elevation: 10, shadowColor: ColorConstants.main.withOpacity(0.2), @@ -107,7 +107,7 @@ class FloatingNavbar extends HookConsumerWidget { color: ColorConstants.main, child: Container( height: borderRadius * 2, - padding: const EdgeInsets.symmetric(horizontal: 8), + padding: const EdgeInsets.symmetric(horizontal: 5), decoration: BoxDecoration( borderRadius: BorderRadius.circular(borderRadius), ), From 9755c019d6a874771d9a45529822fe6140b4d62a Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sat, 5 Jul 2025 01:58:35 +0200 Subject: [PATCH 025/473] fix: display navbar only when logged in --- lib/navigation/ui/drawer_template.dart | 99 +++++++++++++------------- 1 file changed, 51 insertions(+), 48 deletions(-) diff --git a/lib/navigation/ui/drawer_template.dart b/lib/navigation/ui/drawer_template.dart index 7c47dfccff..59cac2491f 100644 --- a/lib/navigation/ui/drawer_template.dart +++ b/lib/navigation/ui/drawer_template.dart @@ -39,6 +39,7 @@ class DrawerTemplate extends HookConsumerWidget { final shouldSetup = ref.watch(shouldSetupProvider); final shouldSetupNotifier = ref.read(shouldSetupProvider.notifier); final animation = ref.watch(navbarAnimationProvider); + final pathForwarding = ref.read(pathForwardingProvider); final pathForwardingNotifier = ref.read(pathForwardingProvider.notifier); Future(() { @@ -55,66 +56,68 @@ class DrawerTemplate extends HookConsumerWidget { child: Stack( children: [ child, - Positioned( - left: 0, - bottom: 0, - right: 0, - child: Visibility( - visible: animation!.isCompleted && animation.value == 1.0, - child: AnimatedBuilder( - animation: animation, - builder: (context, child) => Opacity( - opacity: animation.value, - child: FloatingNavbar( - items: [ - FloatingNavbarItem( - module: FeedRouter.module, - onTap: () { - pathForwardingNotifier.forward(FeedRouter.root); - WidgetsBinding.instance.addPostFrameCallback(( - _, - ) { - QR.to(FeedRouter.root); - }); - }, - ), - ...navbarListModule.map((module) { - return FloatingNavbarItem( - module: module, + if (pathForwarding.isLoggedIn) + Positioned( + left: 0, + bottom: 0, + right: 0, + child: Visibility( + visible: animation!.isCompleted && animation.value == 1.0, + child: AnimatedBuilder( + animation: animation, + builder: (context, child) => Opacity( + opacity: animation.value, + child: FloatingNavbar( + items: [ + FloatingNavbarItem( + module: FeedRouter.module, onTap: () { - navbarListModuleNotifier.pushModule(module); - pathForwardingNotifier.forward(module.root); + pathForwardingNotifier.forward( + FeedRouter.root, + ); WidgetsBinding.instance.addPostFrameCallback(( _, ) { - QR.to(module.root); + QR.to(FeedRouter.root); }); }, - ); - }), - FloatingNavbarItem( - module: Module( - name: 'Autres', - description: '', - root: AppRouter.allModules, ), - onTap: () { - pathForwardingNotifier.forward( - AppRouter.allModules, + ...navbarListModule.map((module) { + return FloatingNavbarItem( + module: module, + onTap: () { + navbarListModuleNotifier.pushModule(module); + pathForwardingNotifier.forward(module.root); + WidgetsBinding.instance + .addPostFrameCallback((_) { + QR.to(module.root); + }); + }, ); - WidgetsBinding.instance.addPostFrameCallback(( - _, - ) { - QR.to(AppRouter.allModules); - }); - }, - ), - ], + }), + FloatingNavbarItem( + module: Module( + name: 'Autres', + description: '', + root: AppRouter.allModules, + ), + onTap: () { + pathForwardingNotifier.forward( + AppRouter.allModules, + ); + WidgetsBinding.instance.addPostFrameCallback(( + _, + ) { + QR.to(AppRouter.allModules); + }); + }, + ), + ], + ), ), ), ), ), - ), if (displayQuit) const QuitDialog(), ], ), From 980e18726d97698e9725b08ca9f6e42f0279ff73 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sat, 5 Jul 2025 01:58:35 +0200 Subject: [PATCH 026/473] feat: adding modal detail --- lib/feed/ui/pages/main_page/main_page.dart | 37 ++++++++++++++++++- .../ui/styleguide/bottom_modal_template.dart | 4 +- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/lib/feed/ui/pages/main_page/main_page.dart b/lib/feed/ui/pages/main_page/main_page.dart index e9519bc665..792871f9a7 100644 --- a/lib/feed/ui/pages/main_page/main_page.dart +++ b/lib/feed/ui/pages/main_page/main_page.dart @@ -5,7 +5,10 @@ import 'package:titan/feed/class/feed_item.dart'; import 'package:titan/feed/ui/feed.dart'; import 'package:titan/feed/ui/pages/main_page/feed_timeline.dart'; import 'package:titan/tools/constants.dart'; +import 'package:titan/tools/ui/layouts/horizontal_list_view.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; +import 'package:titan/tools/ui/styleguide/button.dart'; +import 'package:titan/tools/ui/styleguide/item_chip.dart'; import 'package:titan/tools/ui/styleguide/searchbar.dart'; class FeedMainPage extends HookConsumerWidget { @@ -28,7 +31,39 @@ class FeedMainPage extends HookConsumerWidget { await showCustomBottomModal( modal: BottomModalTemplate( title: 'Filtrer', - child: Container(), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Groupes d\'association'), + SizedBox(height: 10), + HorizontalListView( + height: 50, + children: [ + ItemChip(child: Text('Option 1')), + ItemChip(child: Text('Option 2')), + ItemChip(child: Text('Option 3')), + ], + ), + SizedBox(height: 30), + Text('Associations'), + SizedBox(height: 10), + HorizontalListView( + height: 50, + children: [ + ItemChip(child: Text('Association 1')), + ItemChip(child: Text('Association 2')), + ItemChip(child: Text('Association 3')), + ], + ), + SizedBox(height: 40), + Button( + text: 'Appliquer', + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ), ), context: context, ref: ref, diff --git a/lib/tools/ui/styleguide/bottom_modal_template.dart b/lib/tools/ui/styleguide/bottom_modal_template.dart index 802001badd..933dcf1c00 100644 --- a/lib/tools/ui/styleguide/bottom_modal_template.dart +++ b/lib/tools/ui/styleguide/bottom_modal_template.dart @@ -65,7 +65,7 @@ class BottomModalTemplate extends StatelessWidget { : ColorConstants.main, borderRadius: BorderRadius.vertical(top: Radius.circular(30)), ), - padding: EdgeInsets.all(50), + padding: EdgeInsets.all(20), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, @@ -80,7 +80,7 @@ class BottomModalTemplate extends StatelessWidget { : ColorConstants.background, ), ), - SizedBox(height: 10), + SizedBox(height: 20), if (description != null) Text( description!, From c729e5f720033461ec7031db59a59369eecca2af Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sat, 5 Jul 2025 01:58:35 +0200 Subject: [PATCH 027/473] lint: applying linter --- lib/feed/class/feed_item.dart | 6 +----- lib/feed/router.dart | 3 ++- lib/main.dart | 4 +++- lib/navigation/providers/navbar_animation.dart | 9 ++++----- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/lib/feed/class/feed_item.dart b/lib/feed/class/feed_item.dart index 91f4dd08bd..9be1c12e33 100644 --- a/lib/feed/class/feed_item.dart +++ b/lib/feed/class/feed_item.dart @@ -1,10 +1,6 @@ import 'package:flutter/material.dart'; -enum FeedItemType { - event, - action, - announcement, -} +enum FeedItemType { event, action, announcement } class FeedItem { final FeedItemType type; diff --git a/lib/feed/router.dart b/lib/feed/router.dart index a2671b439a..29049b8687 100644 --- a/lib/feed/router.dart +++ b/lib/feed/router.dart @@ -1,6 +1,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/navigation/class/module.dart'; -import 'package:titan/feed/ui/pages/main_page/main_page.dart' deferred as main_page; +import 'package:titan/feed/ui/pages/main_page/main_page.dart' + deferred as main_page; import 'package:titan/tools/middlewares/authenticated_middleware.dart'; import 'package:titan/tools/middlewares/deferred_middleware.dart'; import 'package:qlevar_router/qlevar_router.dart'; diff --git a/lib/main.dart b/lib/main.dart index 0fb36be3ce..2cbe38a11e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -61,7 +61,9 @@ class MyApp extends HookConsumerWidget { final plausible = getPlausible(); final pathForwardingNotifier = ref.watch(pathForwardingProvider.notifier); Future(() => animationNotifier.setController(animationController)); - Future(() => navbarAnimationNotifier.setController(navbarAnimationController)); + Future( + () => navbarAnimationNotifier.setController(navbarAnimationController), + ); if (!kIsWeb) { useEffect(() { diff --git a/lib/navigation/providers/navbar_animation.dart b/lib/navigation/providers/navbar_animation.dart index be0de3bbd7..b489ea7736 100644 --- a/lib/navigation/providers/navbar_animation.dart +++ b/lib/navigation/providers/navbar_animation.dart @@ -31,8 +31,7 @@ class NavbarAnimationProvider extends StateNotifier { } } -final navbarAnimationProvider = StateNotifierProvider(( - ref, -) { - return NavbarAnimationProvider(); -}); +final navbarAnimationProvider = + StateNotifierProvider((ref) { + return NavbarAnimationProvider(); + }); From 087524a8caaeb271919256523feec4850e892bb0 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sat, 5 Jul 2025 01:58:36 +0200 Subject: [PATCH 028/473] fix: analyzer reports --- lib/paiement/tools/key_service.dart | 2 +- .../main_page/account_card/account_card.dart | 32 +++++++++++-------- lib/paiement/ui/pages/scan_page/scanner.dart | 30 +++++++++-------- .../middlewares/authenticated_middleware.dart | 3 -- lib/tools/ui/styleguide/navbar.dart | 2 +- 5 files changed, 37 insertions(+), 32 deletions(-) diff --git a/lib/paiement/tools/key_service.dart b/lib/paiement/tools/key_service.dart index 2f07d4bd4d..246b541b81 100644 --- a/lib/paiement/tools/key_service.dart +++ b/lib/paiement/tools/key_service.dart @@ -3,7 +3,7 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart'; class KeyService { final FlutterSecureStorage _secureStorage = const FlutterSecureStorage( - aOptions: AndroidOptions(encryptedSharedPreferences: true), + aOptions: AndroidOptions(), iOptions: IOSOptions( // A service name is required for iOS KeyChain accountName: 'fr.titan.myecl', diff --git a/lib/paiement/ui/pages/main_page/account_card/account_card.dart b/lib/paiement/ui/pages/main_page/account_card/account_card.dart index b15a39d7f0..124842673a 100644 --- a/lib/paiement/ui/pages/main_page/account_card/account_card.dart +++ b/lib/paiement/ui/pages/main_page/account_card/account_card.dart @@ -74,6 +74,23 @@ class AccountCard extends HookConsumerWidget { }); } + void showNotRegisteredDeviceDialog() async { + await showDialog( + context: context, + builder: (context) { + return DeviceDialogBox( + title: 'Appareil non enregistré', + descriptions: + 'Votre appareil n\'est pas encore enregistré. \nPour l\'enregistrer, veuillez vous rendre sur la page des appareils.', + buttonText: 'Accéder à la page', + onClick: () { + QR.to(PaymentRouter.root + PaymentRouter.devices); + }, + ); + }, + ); + } + return MainCardTemplate( colors: const [ Color.fromARGB(255, 9, 103, 103), @@ -108,20 +125,7 @@ class AccountCard extends HookConsumerWidget { } String? keyId = await keyService.getKeyId(); if (keyId == null) { - await showDialog( - context: context, - builder: (context) { - return DeviceDialogBox( - title: 'Appareil non enregistré', - descriptions: - 'Votre appareil n\'est pas encore enregistré. \nPour l\'enregistrer, veuillez vous rendre sur la page des appareils.', - buttonText: 'Accéder à la page', - onClick: () { - QR.to(PaymentRouter.root + PaymentRouter.devices); - }, - ); - }, - ); + showNotRegisteredDeviceDialog(); return; } final device = await deviceNotifier.getDevice(keyId); diff --git a/lib/paiement/ui/pages/scan_page/scanner.dart b/lib/paiement/ui/pages/scan_page/scanner.dart index 8235f9572b..a9711fdbac 100644 --- a/lib/paiement/ui/pages/scan_page/scanner.dart +++ b/lib/paiement/ui/pages/scan_page/scanner.dart @@ -112,6 +112,22 @@ class ScannerState extends ConsumerState with WidgetsBindingObserver { } } + void showCameraPermissionDeniedDialog() async { + await showDialog( + context: context, + builder: (context) => CustomDialogBox( + title: 'Permission caméra requise', + descriptions: + 'Pour scanner des QR codes, l\'application a besoin d\'accéder à votre caméra. Veuillez accorder cette permission dans les paramètres de votre appareil.', + onYes: () async { + Navigator.of(context).pop(); + await openAppSettings(); + }, + yesText: 'Paramètres', + ), + ); + } + @override void initState() { super.initState(); @@ -122,19 +138,7 @@ class ScannerState extends ConsumerState with WidgetsBindingObserver { unawaited(() async { await controller.start(); if (!controller.value.hasCameraPermission) { - showDialog( - context: context, - builder: (context) => CustomDialogBox( - title: 'Permission caméra requise', - descriptions: - 'Pour scanner des QR codes, l\'application a besoin d\'accéder à votre caméra. Veuillez accorder cette permission dans les paramètres de votre appareil.', - onYes: () async { - Navigator.of(context).pop(); - await openAppSettings(); - }, - yesText: 'Paramètres', - ), - ); + showCameraPermissionDeniedDialog(); } }()); } diff --git a/lib/tools/middlewares/authenticated_middleware.dart b/lib/tools/middlewares/authenticated_middleware.dart index 8f280616f9..7e3fe1b76c 100644 --- a/lib/tools/middlewares/authenticated_middleware.dart +++ b/lib/tools/middlewares/authenticated_middleware.dart @@ -5,7 +5,6 @@ import 'package:titan/auth/providers/openid_provider.dart'; import 'package:titan/feed/router.dart'; import 'package:titan/login/router.dart'; import 'package:titan/router.dart'; -import 'package:titan/settings/providers/module_list_provider.dart'; import 'package:titan/tools/providers/path_forwarding_provider.dart'; import 'package:titan/version/providers/titan_version_provider.dart'; import 'package:titan/version/providers/version_verifier_provider.dart'; @@ -22,7 +21,6 @@ class AuthenticatedMiddleware extends QMiddleware { final versionVerifier = ref.watch(versionVerifierProvider); final titanVersion = ref.watch(titanVersionProvider); final isLoggedIn = ref.watch(isLoggedInProvider); - final modules = ref.read(modulesProvider); final check = versionVerifier.whenData( (value) => value.minimalTitanVersion <= titanVersion, ); @@ -31,7 +29,6 @@ class AuthenticatedMiddleware extends QMiddleware { path != "/") { pathForwardingNotifier.forward(path); } - print("Redirecting to ${pathForwardingNotifier.state}"); return check.when( data: (value) { if (!value) { diff --git a/lib/tools/ui/styleguide/navbar.dart b/lib/tools/ui/styleguide/navbar.dart index 6f821149a3..32c373c82c 100644 --- a/lib/tools/ui/styleguide/navbar.dart +++ b/lib/tools/ui/styleguide/navbar.dart @@ -102,7 +102,7 @@ class FloatingNavbar extends HookConsumerWidget { padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), child: Material( elevation: 10, - shadowColor: ColorConstants.main.withOpacity(0.2), + shadowColor: ColorConstants.main.withValues(alpha: 0.2), borderRadius: BorderRadius.circular(borderRadius), color: ColorConstants.main, child: Container( From 4efe88ef17aef11a28ec16216be086f19a1d00a4 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sat, 5 Jul 2025 01:58:36 +0200 Subject: [PATCH 029/473] feat: adding admin page --- lib/feed/router.dart | 16 +++++++++ lib/feed/ui/pages/admin_page/admin_page.dart | 11 ++++++ lib/feed/ui/pages/main_page/main_page.dart | 36 +++++++++++++++----- lib/tools/ui/styleguide/icon_button.dart | 4 +-- 4 files changed, 57 insertions(+), 10 deletions(-) create mode 100644 lib/feed/ui/pages/admin_page/admin_page.dart diff --git a/lib/feed/router.dart b/lib/feed/router.dart index 29049b8687..c952d470e2 100644 --- a/lib/feed/router.dart +++ b/lib/feed/router.dart @@ -1,7 +1,11 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/admin/providers/is_admin_provider.dart'; import 'package:titan/navigation/class/module.dart'; +import 'package:titan/feed/ui/pages/admin_page/admin_page.dart' + deferred as admin_page; import 'package:titan/feed/ui/pages/main_page/main_page.dart' deferred as main_page; +import 'package:titan/tools/middlewares/admin_middleware.dart'; import 'package:titan/tools/middlewares/authenticated_middleware.dart'; import 'package:titan/tools/middlewares/deferred_middleware.dart'; import 'package:qlevar_router/qlevar_router.dart'; @@ -10,6 +14,7 @@ class FeedRouter { final Ref ref; static const String root = '/feed'; + static const String admin = '/admin'; static final Module module = Module( name: "Feed", description: "Consulter les actualités et mises à jour", @@ -26,5 +31,16 @@ class FeedRouter { AuthenticatedMiddleware(ref), DeferredLoadingMiddleware(main_page.loadLibrary), ], + children: [ + QRoute( + path: admin, + builder: () => admin_page.AdminPage(), + middleware: [ + AuthenticatedMiddleware(ref), + AdminMiddleware(ref, isAdminProvider), + DeferredLoadingMiddleware(admin_page.loadLibrary), + ], + ), + ], ); } diff --git a/lib/feed/ui/pages/admin_page/admin_page.dart b/lib/feed/ui/pages/admin_page/admin_page.dart new file mode 100644 index 0000000000..bc8f6b5ab4 --- /dev/null +++ b/lib/feed/ui/pages/admin_page/admin_page.dart @@ -0,0 +1,11 @@ +import 'package:flutter/material.dart'; +import 'package:titan/feed/ui/feed.dart'; + +class AdminPage extends StatelessWidget { + const AdminPage({super.key}); + + @override + Widget build(BuildContext context) { + return FeedTemplate(child: Container()); + } +} diff --git a/lib/feed/ui/pages/main_page/main_page.dart b/lib/feed/ui/pages/main_page/main_page.dart index 792871f9a7..3723cc6778 100644 --- a/lib/feed/ui/pages/main_page/main_page.dart +++ b/lib/feed/ui/pages/main_page/main_page.dart @@ -1,13 +1,18 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/admin/providers/is_admin_provider.dart'; import 'package:titan/feed/class/feed_item.dart'; +import 'package:titan/feed/router.dart'; import 'package:titan/feed/ui/feed.dart'; import 'package:titan/feed/ui/pages/main_page/feed_timeline.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/layouts/horizontal_list_view.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; import 'package:titan/tools/ui/styleguide/button.dart'; +import 'package:titan/tools/ui/styleguide/icon_button.dart'; import 'package:titan/tools/ui/styleguide/item_chip.dart'; import 'package:titan/tools/ui/styleguide/searchbar.dart'; @@ -18,6 +23,7 @@ class FeedMainPage extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final feedItems = useState>(FeedItem.getFakeItems()); final filteredItems = useState>(feedItems.value); + final isAdmin = ref.watch(isAdminProvider); return FeedTemplate( child: Container( @@ -74,14 +80,28 @@ class FeedMainPage extends HookConsumerWidget { const SizedBox(height: 20), - // Title - const Text( - "Actualité", - style: TextStyle( - fontSize: 24, - fontWeight: FontWeight.bold, - color: ColorConstants.title, - ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + "Actualité", + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: ColorConstants.title, + ), + ), + if (isAdmin) + CustomIconButton( + icon: HeroIcon( + HeroIcons.plus, + color: ColorConstants.background, + ), + onPressed: () { + QR.to(FeedRouter.root + FeedRouter.admin); + }, + ), + ], ), const SizedBox(height: 20), diff --git a/lib/tools/ui/styleguide/icon_button.dart b/lib/tools/ui/styleguide/icon_button.dart index 92943d87a6..616fd13660 100644 --- a/lib/tools/ui/styleguide/icon_button.dart +++ b/lib/tools/ui/styleguide/icon_button.dart @@ -77,8 +77,8 @@ class CustomIconButton extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 3), decoration: BoxDecoration( color: backgroundColor, - borderRadius: BorderRadius.circular(8), - border: Border.all(color: borderColor), + borderRadius: BorderRadius.circular(10), + border: Border.all(color: borderColor, width: 2), ), child: Center(child: icon), ), From 5fa77862c38fba2ece8581016229ec81826a8826 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sat, 5 Jul 2025 01:58:36 +0200 Subject: [PATCH 030/473] feat: adding admin page --- lib/feed/ui/pages/admin_page/admin_page.dart | 98 +++++++- lib/feed/ui/pages/admin_page/event_form.dart | 142 +++++++++++ lib/feed/ui/pages/admin_page/post_form.dart | 62 +++++ .../ui/pages/admin_page/tab_navigation.dart | 220 ++++++++++++++++++ .../middlewares/authenticated_middleware.dart | 1 + 5 files changed, 520 insertions(+), 3 deletions(-) create mode 100644 lib/feed/ui/pages/admin_page/event_form.dart create mode 100644 lib/feed/ui/pages/admin_page/post_form.dart create mode 100644 lib/feed/ui/pages/admin_page/tab_navigation.dart diff --git a/lib/feed/ui/pages/admin_page/admin_page.dart b/lib/feed/ui/pages/admin_page/admin_page.dart index bc8f6b5ab4..62082d15d0 100644 --- a/lib/feed/ui/pages/admin_page/admin_page.dart +++ b/lib/feed/ui/pages/admin_page/admin_page.dart @@ -1,11 +1,103 @@ import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/feed/ui/feed.dart'; +import 'package:titan/feed/ui/pages/admin_page/event_form.dart'; +import 'package:titan/feed/ui/pages/admin_page/post_form.dart'; +import 'package:titan/feed/ui/pages/admin_page/tab_navigation.dart'; -class AdminPage extends StatelessWidget { +class AdminPage extends HookConsumerWidget { const AdminPage({super.key}); @override - Widget build(BuildContext context) { - return FeedTemplate(child: Container()); + Widget build(BuildContext context, WidgetRef ref) { + // Use hooks to manage tab selection state + final selectedTabIndex = useState(0); + final previousTabIndex = useRef(0); + final pageController = usePageController(); + + // Controllers for Post form + final postTitleController = useTextEditingController(); + final postDescriptionController = useTextEditingController(); + + // Controllers for Event form + final eventTitleController = useTextEditingController(); + final eventDescriptionController = useTextEditingController(); + final eventLocationController = useTextEditingController(); + final eventPlacesController = useTextEditingController(); + final eventExternalLinkController = useTextEditingController(); + + // Selected date state for event + final eventStartDateController = useTextEditingController(); + final eventEndDateController = useTextEditingController(); + + // Handle tab selection changes + useEffect(() { + if (pageController.hasClients) { + pageController.animateToPage( + selectedTabIndex.value, + duration: const Duration(milliseconds: 300), + curve: Curves.easeOutCubic, + ); + } + return null; + }, [selectedTabIndex.value]); + + return FeedTemplate( + child: SafeArea( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // Tab Bar + TabNavigation( + selectedTabIndex: selectedTabIndex.value, + onTabChanged: (index) { + previousTabIndex.value = selectedTabIndex.value; + selectedTabIndex.value = index; + }, + tabLabels: const ['Post', 'Événement'], + ), + + const SizedBox(height: 30), + + // PageView for tab content with actual PageView widget + Expanded( + child: PageView( + controller: pageController, + physics: const BouncingScrollPhysics(), + onPageChanged: (index) { + previousTabIndex.value = selectedTabIndex.value; + selectedTabIndex.value = index; + }, + children: [ + // Post form + SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: PostForm( + titleController: postTitleController, + descriptionController: postDescriptionController, + ), + ), + + // Event form + SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: EventForm( + titleController: eventTitleController, + descriptionController: eventDescriptionController, + startDateController: eventStartDateController, + endDateController: eventEndDateController, + locationController: eventLocationController, + placesController: eventPlacesController, + externalLinkController: eventExternalLinkController, + ), + ), + ], + ), + ), + ], + ), + ), + ); } } diff --git a/lib/feed/ui/pages/admin_page/event_form.dart b/lib/feed/ui/pages/admin_page/event_form.dart new file mode 100644 index 0000000000..2122363a38 --- /dev/null +++ b/lib/feed/ui/pages/admin_page/event_form.dart @@ -0,0 +1,142 @@ +import 'package:flutter/material.dart'; +import 'package:titan/tools/ui/styleguide/button.dart'; +import 'package:titan/tools/ui/styleguide/text_entry.dart'; +import 'package:titan/tools/ui/widgets/date_entry.dart'; + +class EventForm extends StatelessWidget { + final TextEditingController titleController; + final TextEditingController descriptionController; + final TextEditingController startDateController; + final TextEditingController endDateController; + final TextEditingController locationController; + final TextEditingController placesController; + final TextEditingController externalLinkController; + + const EventForm({ + super.key, + required this.titleController, + required this.descriptionController, + required this.startDateController, + required this.endDateController, + required this.locationController, + required this.placesController, + required this.externalLinkController, + }); + + @override + Widget build(BuildContext context) { + return Padding( + // Add padding at the bottom for the navbar + padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 16.0), + child: Column( + key: const ValueKey('event_form'), + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // Event Form - Event with more details including dates + TextEntry(label: "Titre", controller: titleController), + const SizedBox(height: 20), + TextEntry( + label: "Description", + controller: descriptionController, + maxLines: 5, + minLines: 3, + ), + const SizedBox(height: 20), + DateEntry( + onTap: () async { + final pickedDate = await showDatePicker( + context: context, + initialDate: DateTime.now(), + firstDate: DateTime.now(), + lastDate: DateTime.now().add(const Duration(days: 365)), + ); + if (pickedDate != null) { + final formattedDate = + "${pickedDate.day.toString().padLeft(2, '0')}/${pickedDate.month.toString().padLeft(2, '0')}/${pickedDate.year}"; + startDateController.text = formattedDate; + } + }, + controller: startDateController, + label: "Date de début", + ), + const SizedBox(height: 20), + DateEntry( + onTap: () async { + // Parse start date if available + DateTime startDate = DateTime.now(); + if (startDateController.text.isNotEmpty) { + try { + final parts = startDateController.text.split('/'); + startDate = DateTime( + int.parse(parts[2]), // year + int.parse(parts[1]), // month + int.parse(parts[0]), // day + ); + } catch (e) { + // Use current date as fallback + } + } + + final pickedDate = await showDatePicker( + context: context, + initialDate: startDate, + firstDate: startDate, + lastDate: startDate.add(const Duration(days: 365)), + ); + if (pickedDate != null) { + final formattedDate = + "${pickedDate.day.toString().padLeft(2, '0')}/${pickedDate.month.toString().padLeft(2, '0')}/${pickedDate.year}"; + endDateController.text = formattedDate; + } + }, + controller: endDateController, + label: "Date de fin", + ), + const SizedBox(height: 20), + TextEntry(label: "Lieu", controller: locationController), + const SizedBox(height: 20), + TextEntry( + label: "Nombre de places", + controller: placesController, + keyboardType: TextInputType.number, + isInt: true, + ), + const SizedBox(height: 20), + TextEntry( + label: "Lien externe", + controller: externalLinkController, + canBeEmpty: true, + ), + const SizedBox(height: 40), + Button( + text: "Créer l'événement", + onPressed: () { + // Validate required fields + if (titleController.text.isEmpty || + descriptionController.text.isEmpty || + startDateController.text.isEmpty || + endDateController.text.isEmpty || + locationController.text.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + 'Veuillez remplir tous les champs obligatoires', + ), + ), + ); + return; + } + + // TODO: Implement event submission + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Événement créé avec succès')), + ); + }, + ), + // Add extra bottom padding for navbar + const SizedBox(height: 80), + ], + ), + ); + } +} diff --git a/lib/feed/ui/pages/admin_page/post_form.dart b/lib/feed/ui/pages/admin_page/post_form.dart new file mode 100644 index 0000000000..9b23a59836 --- /dev/null +++ b/lib/feed/ui/pages/admin_page/post_form.dart @@ -0,0 +1,62 @@ +import 'package:flutter/material.dart'; +import 'package:titan/tools/ui/styleguide/button.dart'; +import 'package:titan/tools/ui/styleguide/text_entry.dart'; + +class PostForm extends StatelessWidget { + final TextEditingController titleController; + final TextEditingController descriptionController; + + const PostForm({ + super.key, + required this.titleController, + required this.descriptionController, + }); + + @override + Widget build(BuildContext context) { + return Padding( + // Add padding at the bottom for the navbar + padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 16.0), + child: Column( + key: const ValueKey('post_form'), + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // Post Form - Simple post with title and description + TextEntry(label: "Titre", controller: titleController), + const SizedBox(height: 20), + TextEntry( + label: "Description", + controller: descriptionController, + maxLines: 5, + minLines: 3, + ), + const SizedBox(height: 40), + Button( + text: "Publier", + onPressed: () { + // Validate required fields + if (titleController.text.isEmpty || + descriptionController.text.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + 'Veuillez remplir tous les champs obligatoires', + ), + ), + ); + return; + } + + // TODO: Implement post submission + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Post créé avec succès')), + ); + }, + ), + // Add extra bottom padding for navbar + const SizedBox(height: 80), + ], + ), + ); + } +} diff --git a/lib/feed/ui/pages/admin_page/tab_navigation.dart b/lib/feed/ui/pages/admin_page/tab_navigation.dart new file mode 100644 index 0000000000..9b21d37245 --- /dev/null +++ b/lib/feed/ui/pages/admin_page/tab_navigation.dart @@ -0,0 +1,220 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:titan/tools/constants.dart'; + +class TabNavigation extends HookWidget { + final int selectedTabIndex; + final Function(int) onTabChanged; + final List tabLabels; + + const TabNavigation({ + super.key, + required this.selectedTabIndex, + required this.onTabChanged, + required this.tabLabels, + }); + + @override + Widget build(BuildContext context) { + // Track previous and current tab indices + final previousIndex = useRef(selectedTabIndex); + final currentState = useState(selectedTabIndex); + + // Update current state when selectedTabIndex changes externally + useEffect(() { + currentState.value = selectedTabIndex; + return null; + }, [selectedTabIndex]); + + // Main animation controller for sliding effect + final animationController = useAnimationController( + duration: const Duration(milliseconds: 300), + ); + + // Clean up animation controller if component is destroyed while animating + useEffect(() { + return () { + if (animationController.isAnimating) { + animationController.stop(); + } + }; + }, []); + + // Slide animation reference + final slideAnimation = useRef?>(null); + final itemWidthRef = useRef(0.0); + + // Update animation when tab changes + useEffect(() { + if (previousIndex.value != currentState.value && itemWidthRef.value > 0) { + slideAnimation.value = + Tween( + begin: previousIndex.value * itemWidthRef.value, + end: currentState.value * itemWidthRef.value, + ).animate( + CurvedAnimation( + parent: animationController, + curve: Curves.easeOutCubic, + ), + ); + animationController.reset(); + animationController.forward(); + previousIndex.value = currentState.value; + } + return null; + }, [currentState.value, itemWidthRef.value]); + + final borderRadius = 25.0; + + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), + child: Material( + elevation: 10, + shadowColor: ColorConstants.tertiary.withValues(alpha: 0.2), + borderRadius: BorderRadius.circular(borderRadius), + color: ColorConstants.tertiary, + child: Container( + height: borderRadius * 2, + padding: const EdgeInsets.symmetric(horizontal: 5), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(borderRadius), + ), + child: LayoutBuilder( + builder: (context, constraints) { + final availableWidth = constraints.maxWidth; + final itemWidth = availableWidth / tabLabels.length; + + itemWidthRef.value = itemWidth; + + return Stack( + children: [ + // Animated sliding indicator + AnimatedBuilder( + animation: animationController, + builder: (context, _) { + final leftPosition = slideAnimation.value != null + ? slideAnimation.value!.value + : itemWidth * currentState.value; + + return Positioned( + left: leftPosition, + top: 4, + bottom: 4, + width: itemWidth, + child: Container( + decoration: BoxDecoration( + color: ColorConstants.background, + borderRadius: BorderRadius.circular(borderRadius), + ), + ), + ); + }, + ), + + // Tab buttons row + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: tabLabels.asMap().entries.map((entry) { + final index = entry.key; + final label = entry.value; + final isSelected = index == currentState.value; + + return Expanded( + child: Material( + color: Colors.transparent, + borderRadius: BorderRadius.circular(borderRadius), + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + if (index != currentState.value) { + if (animationController.isAnimating) { + animationController.stop(); + } + + previousIndex.value = currentState.value; + currentState.value = index; + + // Notify parent about the tab change + WidgetsBinding.instance.addPostFrameCallback(( + _, + ) { + onTabChanged(index); + }); + } + }, + child: Container( + padding: const EdgeInsets.all(8), + child: AnimatedBuilder( + animation: animationController, + builder: (context, child) { + Color textColor; + FontWeight textWeight; + + if (previousIndex.value == + currentState.value) { + textColor = isSelected + ? ColorConstants.tertiary + : ColorConstants.background; + textWeight = isSelected + ? FontWeight.w600 + : FontWeight.normal; + } else { + bool isInvolved = + index == previousIndex.value || + index == currentState.value; + + if (!isInvolved) { + textColor = ColorConstants.background; + textWeight = FontWeight.normal; + } else if (index == currentState.value) { + final progress = + animationController.value; + textColor = Color.lerp( + ColorConstants.background, + ColorConstants.tertiary, + progress, + )!; + textWeight = progress < 0.5 + ? FontWeight.normal + : FontWeight.w600; + } else { + final progress = + animationController.value; + textColor = Color.lerp( + ColorConstants.tertiary, + ColorConstants.background, + progress, + )!; + textWeight = progress < 0.5 + ? FontWeight.w600 + : FontWeight.normal; + } + } + + return Center( + child: Text( + label, + style: TextStyle( + color: textColor, + fontSize: 14, + fontWeight: textWeight, + ), + ), + ); + }, + ), + ), + ), + ), + ); + }).toList(), + ), + ], + ); + }, + ), + ), + ), + ); + } +} diff --git a/lib/tools/middlewares/authenticated_middleware.dart b/lib/tools/middlewares/authenticated_middleware.dart index 7e3fe1b76c..605bd5ebc7 100644 --- a/lib/tools/middlewares/authenticated_middleware.dart +++ b/lib/tools/middlewares/authenticated_middleware.dart @@ -50,6 +50,7 @@ class AuthenticatedMiddleware extends QMiddleware { return FeedRouter.root; } if (pathForwardingNotifier.state.path != path) { + pathForwardingNotifier.forward(path); return pathForwardingNotifier.state.path; } return null; From acff705252b7f484a381525785a39b2c40be0d9c Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sat, 5 Jul 2025 01:58:37 +0200 Subject: [PATCH 031/473] feat: adding missing fields --- lib/feed/ui/pages/admin_page/admin_page.dart | 6 +- lib/feed/ui/pages/admin_page/event_form.dart | 72 ++++++++++++-------- lib/feed/ui/pages/admin_page/post_form.dart | 39 +++++++++-- lib/tools/ui/styleguide/navbar.dart | 19 ++---- 4 files changed, 85 insertions(+), 51 deletions(-) diff --git a/lib/feed/ui/pages/admin_page/admin_page.dart b/lib/feed/ui/pages/admin_page/admin_page.dart index 62082d15d0..436291efe8 100644 --- a/lib/feed/ui/pages/admin_page/admin_page.dart +++ b/lib/feed/ui/pages/admin_page/admin_page.dart @@ -19,12 +19,13 @@ class AdminPage extends HookConsumerWidget { // Controllers for Post form final postTitleController = useTextEditingController(); final postDescriptionController = useTextEditingController(); + final postStartDateController = useTextEditingController(); // Controllers for Event form final eventTitleController = useTextEditingController(); final eventDescriptionController = useTextEditingController(); final eventLocationController = useTextEditingController(); - final eventPlacesController = useTextEditingController(); + final shotgunDateController = useTextEditingController(); final eventExternalLinkController = useTextEditingController(); // Selected date state for event @@ -76,6 +77,7 @@ class AdminPage extends HookConsumerWidget { child: PostForm( titleController: postTitleController, descriptionController: postDescriptionController, + startDateController: postStartDateController, ), ), @@ -88,7 +90,7 @@ class AdminPage extends HookConsumerWidget { startDateController: eventStartDateController, endDateController: eventEndDateController, locationController: eventLocationController, - placesController: eventPlacesController, + shotgunDateController: shotgunDateController, externalLinkController: eventExternalLinkController, ), ), diff --git a/lib/feed/ui/pages/admin_page/event_form.dart b/lib/feed/ui/pages/admin_page/event_form.dart index 2122363a38..0748e2512d 100644 --- a/lib/feed/ui/pages/admin_page/event_form.dart +++ b/lib/feed/ui/pages/admin_page/event_form.dart @@ -1,7 +1,8 @@ import 'package:flutter/material.dart'; import 'package:titan/tools/ui/styleguide/button.dart'; +import 'package:titan/tools/ui/styleguide/image_entry.dart'; import 'package:titan/tools/ui/styleguide/text_entry.dart'; -import 'package:titan/tools/ui/widgets/date_entry.dart'; +import 'package:titan/tools/ui/styleguide/date_entry.dart'; class EventForm extends StatelessWidget { final TextEditingController titleController; @@ -9,7 +10,7 @@ class EventForm extends StatelessWidget { final TextEditingController startDateController; final TextEditingController endDateController; final TextEditingController locationController; - final TextEditingController placesController; + final TextEditingController shotgunDateController; final TextEditingController externalLinkController; const EventForm({ @@ -19,20 +20,18 @@ class EventForm extends StatelessWidget { required this.startDateController, required this.endDateController, required this.locationController, - required this.placesController, + required this.shotgunDateController, required this.externalLinkController, }); @override Widget build(BuildContext context) { return Padding( - // Add padding at the bottom for the navbar padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 16.0), child: Column( key: const ValueKey('event_form'), crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - // Event Form - Event with more details including dates TextEntry(label: "Titre", controller: titleController), const SizedBox(height: 20), TextEntry( @@ -56,25 +55,20 @@ class EventForm extends StatelessWidget { startDateController.text = formattedDate; } }, - controller: startDateController, - label: "Date de début", + title: "Date de début", + subtitle: "Sélectionnez une date", ), const SizedBox(height: 20), DateEntry( onTap: () async { - // Parse start date if available DateTime startDate = DateTime.now(); if (startDateController.text.isNotEmpty) { - try { - final parts = startDateController.text.split('/'); - startDate = DateTime( - int.parse(parts[2]), // year - int.parse(parts[1]), // month - int.parse(parts[0]), // day - ); - } catch (e) { - // Use current date as fallback - } + final parts = startDateController.text.split('/'); + startDate = DateTime( + int.parse(parts[2]), + int.parse(parts[1]), + int.parse(parts[0]), + ); } final pickedDate = await showDatePicker( @@ -89,29 +83,50 @@ class EventForm extends StatelessWidget { endDateController.text = formattedDate; } }, - controller: endDateController, - label: "Date de fin", + title: "Date de fin", + subtitle: "Sélectionnez une date", ), const SizedBox(height: 20), TextEntry(label: "Lieu", controller: locationController), const SizedBox(height: 20), - TextEntry( - label: "Nombre de places", - controller: placesController, - keyboardType: TextInputType.number, - isInt: true, + DateEntry( + onTap: () async { + final pickedDate = await showDatePicker( + context: context, + initialDate: DateTime.now(), + firstDate: DateTime.now(), + lastDate: DateTime.now().add(const Duration(days: 365)), + ); + if (pickedDate != null) { + final formattedDate = + "${pickedDate.day.toString().padLeft(2, '0')}/${pickedDate.month.toString().padLeft(2, '0')}/${pickedDate.year}"; + shotgunDateController.text = formattedDate; + } + }, + title: "Date et heure du SG", + subtitle: "Sélectionnez une date", ), const SizedBox(height: 20), TextEntry( - label: "Lien externe", + label: "Lien externe pour le SG", controller: externalLinkController, canBeEmpty: true, ), + const SizedBox(height: 20), + ImageEntry( + title: "Image", + subtitle: "Sélectionnez une image", + onTap: () { + // Logic to add an image + ScaffoldMessenger.of( + context, + ).showSnackBar(const SnackBar(content: Text('Image ajoutée'))); + }, + ), const SizedBox(height: 40), Button( text: "Créer l'événement", onPressed: () { - // Validate required fields if (titleController.text.isEmpty || descriptionController.text.isEmpty || startDateController.text.isEmpty || @@ -126,14 +141,11 @@ class EventForm extends StatelessWidget { ); return; } - - // TODO: Implement event submission ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Événement créé avec succès')), ); }, ), - // Add extra bottom padding for navbar const SizedBox(height: 80), ], ), diff --git a/lib/feed/ui/pages/admin_page/post_form.dart b/lib/feed/ui/pages/admin_page/post_form.dart index 9b23a59836..5f96a89a67 100644 --- a/lib/feed/ui/pages/admin_page/post_form.dart +++ b/lib/feed/ui/pages/admin_page/post_form.dart @@ -1,27 +1,29 @@ import 'package:flutter/material.dart'; import 'package:titan/tools/ui/styleguide/button.dart'; +import 'package:titan/tools/ui/styleguide/date_entry.dart'; +import 'package:titan/tools/ui/styleguide/image_entry.dart'; import 'package:titan/tools/ui/styleguide/text_entry.dart'; class PostForm extends StatelessWidget { final TextEditingController titleController; final TextEditingController descriptionController; + final TextEditingController startDateController; const PostForm({ super.key, required this.titleController, required this.descriptionController, + required this.startDateController, }); @override Widget build(BuildContext context) { return Padding( - // Add padding at the bottom for the navbar padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 16.0), child: Column( key: const ValueKey('post_form'), crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - // Post Form - Simple post with title and description TextEntry(label: "Titre", controller: titleController), const SizedBox(height: 20), TextEntry( @@ -30,11 +32,39 @@ class PostForm extends StatelessWidget { maxLines: 5, minLines: 3, ), + const SizedBox(height: 20), + DateEntry( + onTap: () async { + final pickedDate = await showDatePicker( + context: context, + initialDate: DateTime.now(), + firstDate: DateTime.now(), + lastDate: DateTime.now().add(const Duration(days: 365)), + ); + if (pickedDate != null) { + final formattedDate = + "${pickedDate.day.toString().padLeft(2, '0')}/${pickedDate.month.toString().padLeft(2, '0')}/${pickedDate.year}"; + startDateController.text = formattedDate; + } + }, + title: "Date de début", + subtitle: "Sélectionnez une date", + ), + const SizedBox(height: 20), + ImageEntry( + title: "Image", + subtitle: "Sélectionnez une image", + onTap: () { + // Logic to add an image + ScaffoldMessenger.of( + context, + ).showSnackBar(const SnackBar(content: Text('Image ajoutée'))); + }, + ), const SizedBox(height: 40), Button( text: "Publier", onPressed: () { - // Validate required fields if (titleController.text.isEmpty || descriptionController.text.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( @@ -46,14 +76,11 @@ class PostForm extends StatelessWidget { ); return; } - - // TODO: Implement post submission ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Post créé avec succès')), ); }, ), - // Add extra bottom padding for navbar const SizedBox(height: 80), ], ), diff --git a/lib/tools/ui/styleguide/navbar.dart b/lib/tools/ui/styleguide/navbar.dart index 32c373c82c..a936e4c52a 100644 --- a/lib/tools/ui/styleguide/navbar.dart +++ b/lib/tools/ui/styleguide/navbar.dart @@ -2,7 +2,6 @@ import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/navigation/class/module.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/providers/path_forwarding_provider.dart'; @@ -14,17 +13,6 @@ class FloatingNavbarItem { FloatingNavbarItem({this.onTap, required this.module}); } -String getCurrentPath() { - if (QR.history.isEmpty) return ''; - - String currentPath = QR.history.last.path; - final parts = currentPath.split('/'); - if (parts.length > 1 && parts[1].isNotEmpty) { - return '/${parts[1]}'; - } - return ''; -} - class FloatingNavbar extends HookConsumerWidget { final List items; const FloatingNavbar({super.key, required this.items}); @@ -39,8 +27,13 @@ class FloatingNavbar extends HookConsumerWidget { useEffect(() { if (currentPath.isNotEmpty) { + String currentPathRoot = "/"; + final parts = currentPath.split('/'); + if (parts.length > 1 && parts[1].isNotEmpty) { + currentPathRoot = '/${parts[1]}'; + } routeIndex.value = items.indexWhere( - (item) => item.module.root == currentPath, + (item) => item.module.root == currentPathRoot, ); if (routeIndex.value < 0 || routeIndex.value >= items.length) { routeIndex.value = 3; From 0cd8df565e71ac7d6bdda12f79240c8583a7d12c Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 2 Jul 2025 22:20:55 +0200 Subject: [PATCH 032/473] First modules --- l10n.yaml | 3 + lib/admin/class/school.dart | 3 +- lib/admin/tools/constants.dart | 85 - lib/admin/tools/function.dart | 8 +- .../add_edit_structure_page.dart | 20 +- .../add_edit_structure_page/search_user.dart | 4 +- .../edit_module_visibility.dart | 8 +- .../modules_expansion_panel.dart | 12 +- .../groups/add_group_page/add_group_page.dart | 19 +- .../add_loaner_page/add_loaner_page.dart | 22 +- .../edit_group_page/edit_group_page.dart | 26 +- .../pages/groups/edit_group_page/results.dart | 6 +- .../groups/edit_group_page/search_user.dart | 12 +- .../pages/groups/group_page/group_page.dart | 25 +- lib/admin/ui/pages/main_page/main_page.dart | 18 +- .../add_edit_user_membership_page.dart | 28 +- .../association_membership_detail_page.dart | 16 +- ...ciation_membership_information_editor.dart | 30 +- .../research_bar.dart | 16 +- .../search_filters.dart | 18 +- ...ssociation_membership_creation_dialog.dart | 16 +- .../association_membership_page.dart | 37 +- .../add_school_page/add_school_page.dart | 21 +- .../edit_school_page/edit_school_page.dart | 23 +- .../schools/school_page/school_page.dart | 27 +- .../pages/schools/school_page/school_ui.dart | 3 +- .../pages/structure_page/structure_page.dart | 25 +- lib/advert/tools/constants.dart | 46 - lib/advert/tools/functions.dart | 22 +- lib/advert/ui/components/advert_card.dart | 4 +- .../ui/pages/admin_page/admin_page.dart | 6 +- .../pages/form_page/add_edit_advert_page.dart | 24 +- .../form_page/add_rem_announcer_page.dart | 50 +- lib/advert/ui/pages/main_page/main_page.dart | 4 +- lib/amap/tools/constants.dart | 130 - lib/amap/tools/functions.dart | 9 +- lib/amap/ui/components/order_ui.dart | 27 +- lib/amap/ui/components/product_ui.dart | 3 +- .../ui/pages/admin_page/account_handler.dart | 3 +- .../ui/pages/admin_page/delivery_handler.dart | 7 +- lib/amap/ui/pages/admin_page/delivery_ui.dart | 88 +- .../ui/pages/admin_page/product_handler.dart | 28 +- .../ui/pages/admin_page/user_cash_ui.dart | 9 +- .../add_edit_delivery_cmd_page.dart | 37 +- .../detail_delivery_page/detail_page.dart | 19 +- .../detail_delivery_page/order_detail_ui.dart | 15 +- .../product_detail_ui.dart | 3 +- .../ui/pages/detail_page/detail_page.dart | 8 +- .../list_products_page/category_page.dart | 3 +- .../list_products_page/list_products.dart | 7 +- .../product_choice_button.dart | 19 +- .../main_page/collection_slot_selector.dart | 2 +- .../ui/pages/main_page/delivery_section.dart | 9 +- lib/amap/ui/pages/main_page/delivery_ui.dart | 5 +- lib/amap/ui/pages/main_page/main_page.dart | 17 +- .../ui/pages/main_page/orders_section.dart | 7 +- lib/amap/ui/pages/presentation_page/text.dart | 24 +- .../pages/product_pages/add_edit_product.dart | 64 +- lib/booking/tools/constants.dart | 120 +- lib/booking/tools/functions.dart | 30 +- lib/booking/ui/calendar/calendar_dialog.dart | 8 +- lib/booking/ui/components/booking_card.dart | 10 +- .../admin_pages/add_edit_manager_page.dart | 47 +- .../pages/admin_pages/add_edit_room_page.dart | 30 +- .../ui/pages/admin_pages/admin_page.dart | 18 +- .../booking_pages/add_edit_booking_page.dart | 105 +- .../ui/pages/detail_pages/detail_booking.dart | 10 +- lib/booking/ui/pages/main_page/main_page.dart | 32 +- .../ui/pages/manager_page/list_booking.dart | 10 +- .../ui/pages/manager_page/manager_page.dart | 14 +- lib/l10n/app_en.arb | 0 lib/l10n/app_fr.arb | 1103 +++ lib/l10n/app_localizations.dart | 6734 +++++++++++++++++ lib/l10n/app_localizations_en.dart | 3397 +++++++++ lib/l10n/app_localizations_fr.dart | 3397 +++++++++ .../association_creation_page.dart | 12 +- .../association_creation_page/text_entry.dart | 4 +- pubspec.yaml | 1 + test/amap/amap_test.dart | 5 +- 79 files changed, 15433 insertions(+), 884 deletions(-) create mode 100644 l10n.yaml create mode 100644 lib/l10n/app_en.arb create mode 100644 lib/l10n/app_fr.arb create mode 100644 lib/l10n/app_localizations.dart create mode 100644 lib/l10n/app_localizations_en.dart create mode 100644 lib/l10n/app_localizations_fr.dart diff --git a/l10n.yaml b/l10n.yaml new file mode 100644 index 0000000000..20442b9d8a --- /dev/null +++ b/l10n.yaml @@ -0,0 +1,3 @@ +arb-dir: lib/l10n +template-arb-file: app_fr.arb +output-localization-file: app_localizations.dart diff --git a/lib/admin/class/school.dart b/lib/admin/class/school.dart index 47694bb973..c2aef50959 100644 --- a/lib/admin/class/school.dart +++ b/lib/admin/class/school.dart @@ -1,4 +1,3 @@ -import 'package:titan/admin/tools/function.dart'; class School { School({required this.name, required this.id, required this.emailRegex}); @@ -7,7 +6,7 @@ class School { late final String emailRegex; School.fromJson(Map json) { - name = getSchoolNameFromId(json['id'], json['name']); + name = json['name']; id = json['id']; emailRegex = json['email_regex']; } diff --git a/lib/admin/tools/constants.dart b/lib/admin/tools/constants.dart index aa9eef2746..eaca405e25 100644 --- a/lib/admin/tools/constants.dart +++ b/lib/admin/tools/constants.dart @@ -1,88 +1,3 @@ -class AdminTextConstants { - static const String accountTypes = "Types de compte"; - static const String add = "Ajouter"; - static const String addGroup = "Ajouter un groupe"; - static const String addMember = "Ajouter un membre"; - static const String addedGroup = "Groupe créé"; - static const String addedLoaner = "Préteur ajouté"; - static const String addedMember = "Membre ajouté"; - static const String addingError = "Erreur lors de l'ajout"; - static const String addingMember = "Ajout d'un membre"; - static const String addLoaningGroup = "Ajouter un groupe de prêt"; - static const String addSchool = "Ajouter une école"; - static const String addStructure = "Ajouter une structure"; - static const String addedSchool = "École créée"; - static const String addedStructure = "Structure ajoutée"; - static const String editedStructure = "Structure modifiée"; - static const String administration = "Administration"; - static const String associationMembership = "Adhésion"; - static const String associationMembershipName = "Nom de l'adhésion"; - static const String associationsMemberships = "Adhésions"; - static const String clearFilters = "Effacer les filtres"; - static const String createAssociationMembership = "Créer une adhésion"; - static const String createdAssociationMembership = "Adhésion créée"; - static const String creationError = "Erreur lors de la création"; - static const String dateError = - "La date de début doit être avant la date de fin"; - static const String delete = "Supprimer"; - static const String deleteAssociationMembership = "Supprimer l'adhésion ?"; - static const String deletedAssociationMembership = "Adhésion supprimée"; - static const String deleteGroup = "Supprimer le groupe ?"; - static const String deletedGroup = "Groupe supprimé"; - static const String deleteSchool = "Supprimer l'école ?"; - static const String deletedSchool = "École supprimée"; - static const String deleting = "Suppression"; - static const String deletingError = "Erreur lors de la suppression"; - static const String description = "Description"; - static const String eclSchool = "Centrale Lyon"; - static const String edit = "Modifier"; - static const String editStructure = "Modifier la structure"; - static const String editMembership = "Modifier l'adhésion"; - static const String emptyDate = "Date vide"; - static const String emptyFieldError = "Le nom ne peut pas être vide"; - static const String emailRegex = "Email Regex"; - static const String emptyUser = "Utilisateur vide"; - static const String endDate = "Date de fin"; - static const String endDateMaximal = "Date de fin maximale"; - static const String endDateMinimal = "Date de fin minimale"; - static const String error = "Erreur"; - static const String filters = "Filtres"; - static const String group = "Groupe"; - static const String groups = "Groupes"; - static const String loaningGroup = "Groupe de prêt"; - static const String looking = "Recherche"; - static const String manager = "Administrateur de la structure"; - static const String maximum = "Maximum"; - static const String members = "Membres"; - static const String membershipAddingError = - "Erreur lors de l'ajout (surement dû à une superposition de dates)"; - static const String memberships = "Adhésions"; - static String membershipUpdatingError = - "Erreur lors de la modification (surement dû à une superposition de dates)"; - static const String minimum = "Minimum"; - static const String modifyModuleVisibility = "Visibilité des modules"; - static const String myEclPay = "MyECLPay"; - static const String name = "Nom"; - static const String noManager = "Aucun manager n'est sélectionné"; - static const String noMember = "Aucun membre"; - static const String noMoreLoaner = "Aucun prêteur n'est disponible"; - static const String noSchool = "Sans école"; - static const String removeGroupMember = "Supprimer le membre du groupe ?"; - static const String research = "Recherche"; - static const String schools = "Écoles"; - static const String structures = "Structures"; - static const String startDate = "Date de début"; - static const String startDateMaximal = "Date de début maximale"; - static const String startDateMinimal = "Date de début minimale"; - static const String updatedAssociationMembership = "Adhésion modifiée"; - static const String updatedGroup = "Groupe modifié"; - static const String updatedMembership = "Adhésion modifiée"; - static const String updatingError = "Erreur lors de la modification"; - static const String user = "Utilisateur"; - static const String validateFilters = "Valider les filtres"; - static const String visibilities = "Visibilités"; -} - enum SchoolIdConstant { noSchool("dce19aa2-8863-4c93-861e-fb7be8f610ed"), eclSchool("d9772da7-1142-4002-8b86-b694b431dfed"); diff --git a/lib/admin/tools/function.dart b/lib/admin/tools/function.dart index dbeff77c60..511d8d21c4 100644 --- a/lib/admin/tools/function.dart +++ b/lib/admin/tools/function.dart @@ -1,11 +1,13 @@ +import 'package:flutter/widgets.dart'; import 'package:titan/admin/tools/constants.dart'; +import 'package:titan/l10n/app_localizations.dart'; -String getSchoolNameFromId(String id, String name) { +String getSchoolNameFromId(String id, String name, BuildContext context) { if (id == SchoolIdConstant.noSchool.value) { - return AdminTextConstants.noSchool; + return AppLocalizations.of(context)!.adminNoSchool; } if (id == SchoolIdConstant.eclSchool.value) { - return AdminTextConstants.eclSchool; + return AppLocalizations.of(context)!.adminEclSchool; } return name; } diff --git a/lib/admin/ui/pages/add_edit_structure_page/add_edit_structure_page.dart b/lib/admin/ui/pages/add_edit_structure_page/add_edit_structure_page.dart index c9fb207b80..65b6184bae 100644 --- a/lib/admin/ui/pages/add_edit_structure_page/add_edit_structure_page.dart +++ b/lib/admin/ui/pages/add_edit_structure_page/add_edit_structure_page.dart @@ -5,7 +5,6 @@ import 'package:titan/admin/class/association_membership_simple.dart'; import 'package:titan/admin/providers/association_membership_list_provider.dart'; import 'package:titan/admin/providers/structure_manager_provider.dart'; import 'package:titan/admin/providers/structure_provider.dart'; -import 'package:titan/admin/tools/constants.dart'; import 'package:titan/admin/ui/admin.dart'; import 'package:titan/admin/ui/components/admin_button.dart'; import 'package:titan/admin/ui/components/text_editing.dart'; @@ -22,6 +21,7 @@ import 'package:titan/tools/ui/widgets/align_left_text.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; import 'package:titan/user/class/simple_users.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class AddEditStructurePage extends HookConsumerWidget { const AddEditStructurePage({super.key}); @@ -63,11 +63,11 @@ class AddEditStructurePage extends HookConsumerWidget { const SizedBox(height: 20), AlignLeftText( isEdit - ? AdminTextConstants.editStructure - : AdminTextConstants.addStructure, + ? AppLocalizations.of(context)!.adminEditStructure + : AppLocalizations.of(context)!.adminAddStructure, ), const SizedBox(height: 20), - TextEditing(controller: name, label: AdminTextConstants.name), + TextEditing(controller: name, label: AppLocalizations.of(context)!.adminName), AsyncChild( value: allAssociationMembershipList, builder: (context, allAssociationMembershipList) { @@ -103,7 +103,7 @@ class AddEditStructurePage extends HookConsumerWidget { ? Column( children: [ Text( - AdminTextConstants.manager, + AppLocalizations.of(context)!.adminManager, style: TextStyle( color: ColorConstants.gradient1, fontSize: 20, @@ -132,7 +132,7 @@ class AddEditStructurePage extends HookConsumerWidget { if (structureManager.id.isEmpty && !isEdit) { displayToastWithContext( TypeMsg.error, - AdminTextConstants.noManager, + AppLocalizations.of(context)!.adminNoManager, ); return; } @@ -163,13 +163,13 @@ class AddEditStructurePage extends HookConsumerWidget { displayToastWithContext( TypeMsg.msg, isEdit - ? AdminTextConstants.editedStructure - : AdminTextConstants.addedStructure, + ? AppLocalizations.of(context)!.adminEditedStructure + : AppLocalizations.of(context)!.adminAddedStructure, ); } else { displayToastWithContext( TypeMsg.error, - AdminTextConstants.addingError, + AppLocalizations.of(context)!.adminAddingError, ); } }); @@ -177,7 +177,7 @@ class AddEditStructurePage extends HookConsumerWidget { }, builder: (child) => AdminButton(child: child), child: Text( - isEdit ? AdminTextConstants.edit : AdminTextConstants.add, + isEdit ? AppLocalizations.of(context)!.adminEdit : AppLocalizations.of(context)!.adminAdd, style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, diff --git a/lib/admin/ui/pages/add_edit_structure_page/search_user.dart b/lib/admin/ui/pages/add_edit_structure_page/search_user.dart index 688a12cd00..d40c9515bd 100644 --- a/lib/admin/ui/pages/add_edit_structure_page/search_user.dart +++ b/lib/admin/ui/pages/add_edit_structure_page/search_user.dart @@ -3,12 +3,12 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/providers/structure_manager_provider.dart'; -import 'package:titan/admin/tools/constants.dart'; import 'package:titan/admin/ui/pages/add_edit_structure_page/results.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/widgets/styled_search_bar.dart'; import 'package:titan/user/class/simple_users.dart'; import 'package:titan/user/providers/user_list_provider.dart'; +import 'package:titan/l10n/app_localizations.dart'; class SearchUser extends HookConsumerWidget { const SearchUser({super.key}); @@ -21,7 +21,7 @@ class SearchUser extends HookConsumerWidget { return Column( children: [ StyledSearchBar( - label: AdminTextConstants.manager, + label: AppLocalizations.of(context)!.adminManager, color: ColorConstants.gradient1, padding: const EdgeInsets.all(0), editingController: useTextEditingController( diff --git a/lib/admin/ui/pages/edit_module_visibility/edit_module_visibility.dart b/lib/admin/ui/pages/edit_module_visibility/edit_module_visibility.dart index 6b52a806b2..f9c0b0ac5f 100644 --- a/lib/admin/ui/pages/edit_module_visibility/edit_module_visibility.dart +++ b/lib/admin/ui/pages/edit_module_visibility/edit_module_visibility.dart @@ -3,12 +3,12 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/providers/all_account_types_list_provider.dart'; import 'package:titan/admin/providers/all_groups_list_provider.dart'; import 'package:titan/admin/providers/module_visibility_list_provider.dart'; -import 'package:titan/admin/tools/constants.dart'; import 'package:titan/admin/ui/admin.dart'; import 'package:titan/admin/ui/pages/edit_module_visibility/modules_expansion_panel.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/widgets/loader.dart'; +import 'package:titan/l10n/app_localizations.dart'; class EditModulesVisibilityPage extends HookConsumerWidget { const EditModulesVisibilityPage({super.key}); @@ -28,10 +28,12 @@ class EditModulesVisibilityPage extends HookConsumerWidget { SizedBox( child: Column( children: [ - const Align( + Align( alignment: Alignment.centerLeft, child: Text( - AdminTextConstants.modifyModuleVisibility, + AppLocalizations.of( + context, + )!.adminModifyModuleVisibility, style: TextStyle( fontSize: 20, fontWeight: FontWeight.w700, diff --git a/lib/admin/ui/pages/edit_module_visibility/modules_expansion_panel.dart b/lib/admin/ui/pages/edit_module_visibility/modules_expansion_panel.dart index 11686afeff..09e70358e4 100644 --- a/lib/admin/ui/pages/edit_module_visibility/modules_expansion_panel.dart +++ b/lib/admin/ui/pages/edit_module_visibility/modules_expansion_panel.dart @@ -7,7 +7,7 @@ import 'package:titan/admin/providers/all_account_types_list_provider.dart'; import 'package:titan/admin/providers/all_groups_list_provider.dart'; import 'package:titan/admin/providers/is_expanded_list_provider.dart'; import 'package:titan/admin/providers/module_visibility_list_provider.dart'; -import 'package:titan/admin/tools/constants.dart'; +import 'package:titan/l10n/app_localizations.dart'; class ModulesExpansionPanel extends HookConsumerWidget { final List modules; @@ -52,8 +52,8 @@ class ModulesExpansionPanel extends HookConsumerWidget { Column( children: [ const Divider(), - const Text( - AdminTextConstants.accountTypes, + Text( + AppLocalizations.of(context)!.adminAccountTypes, style: TextStyle( color: Color.fromARGB(255, 0, 0, 0), fontSize: 20, @@ -133,9 +133,9 @@ class ModulesExpansionPanel extends HookConsumerWidget { const Divider(), Column( children: [ - const Text( - AdminTextConstants.groups, - style: TextStyle( + Text( + AppLocalizations.of(context)!.adminGroups, + style: const TextStyle( color: Color.fromARGB(255, 0, 0, 0), fontSize: 20, fontWeight: FontWeight.w900, diff --git a/lib/admin/ui/pages/groups/add_group_page/add_group_page.dart b/lib/admin/ui/pages/groups/add_group_page/add_group_page.dart index a42957311a..3da3c17077 100644 --- a/lib/admin/ui/pages/groups/add_group_page/add_group_page.dart +++ b/lib/admin/ui/pages/groups/add_group_page/add_group_page.dart @@ -3,7 +3,6 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/class/simple_group.dart'; import 'package:titan/admin/providers/group_list_provider.dart'; -import 'package:titan/admin/tools/constants.dart'; import 'package:titan/admin/ui/admin.dart'; import 'package:titan/admin/ui/components/admin_button.dart'; import 'package:titan/admin/ui/components/text_editing.dart'; @@ -12,6 +11,7 @@ import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/widgets/align_left_text.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class AddGroupPage extends HookConsumerWidget { const AddGroupPage({super.key}); @@ -37,12 +37,15 @@ class AddGroupPage extends HookConsumerWidget { key: key, child: Column( children: [ - const AlignLeftText(AdminTextConstants.addGroup), + AlignLeftText(AppLocalizations.of(context)!.adminAddGroup), const SizedBox(height: 30), - TextEditing(controller: name, label: AdminTextConstants.name), + TextEditing( + controller: name, + label: AppLocalizations.of(context)!.adminName, + ), TextEditing( controller: description, - label: AdminTextConstants.description, + label: AppLocalizations.of(context)!.adminDescription, ), WaitingButton( onTap: () async { @@ -58,19 +61,19 @@ class AddGroupPage extends HookConsumerWidget { QR.back(); displayToastWithContext( TypeMsg.msg, - AdminTextConstants.addedGroup, + AppLocalizations.of(context)!.adminAddedGroup, ); } else { displayToastWithContext( TypeMsg.error, - AdminTextConstants.addingError, + AppLocalizations.of(context)!.adminAddingError, ); } }); }, builder: (child) => AdminButton(child: child), - child: const Text( - AdminTextConstants.add, + child: Text( + AppLocalizations.of(context)!.adminAdd, style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, diff --git a/lib/admin/ui/pages/groups/add_loaner_page/add_loaner_page.dart b/lib/admin/ui/pages/groups/add_loaner_page/add_loaner_page.dart index 058a9e6216..91e43585fb 100644 --- a/lib/admin/ui/pages/groups/add_loaner_page/add_loaner_page.dart +++ b/lib/admin/ui/pages/groups/add_loaner_page/add_loaner_page.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/providers/group_list_provider.dart'; -import 'package:titan/admin/tools/constants.dart'; import 'package:titan/admin/ui/admin.dart'; import 'package:titan/loan/class/loaner.dart'; import 'package:titan/loan/providers/all_loaner_list_provider.dart'; @@ -12,6 +11,7 @@ import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/widgets/align_left_text.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class AddLoanerPage extends HookConsumerWidget { const AddLoanerPage({super.key}); @@ -38,7 +38,9 @@ class AddLoanerPage extends HookConsumerWidget { SizedBox( child: Column( children: [ - const AlignLeftText(AdminTextConstants.addLoaningGroup), + AlignLeftText( + AppLocalizations.of(context)!.adminAddLoaningGroup, + ), const SizedBox(height: 30), AsyncChild( value: associations, @@ -65,12 +67,16 @@ class AddLoanerPage extends HookConsumerWidget { QR.back(); displayToastWithContext( TypeMsg.msg, - AdminTextConstants.addedLoaner, + AppLocalizations.of( + context, + )!.adminAddedLoaner, ); } else { displayToastWithContext( TypeMsg.error, - AdminTextConstants.addingError, + AppLocalizations.of( + context, + )!.adminAddingError, ); } }); @@ -102,8 +108,12 @@ class AddLoanerPage extends HookConsumerWidget { ) .toList(), ) - : const Center( - child: Text(AdminTextConstants.noMoreLoaner), + : Center( + child: Text( + AppLocalizations.of( + context, + )!.adminNoMoreLoaner, + ), ); }, ), diff --git a/lib/admin/ui/pages/groups/edit_group_page/edit_group_page.dart b/lib/admin/ui/pages/groups/edit_group_page/edit_group_page.dart index a906c41534..5d52bff192 100644 --- a/lib/admin/ui/pages/groups/edit_group_page/edit_group_page.dart +++ b/lib/admin/ui/pages/groups/edit_group_page/edit_group_page.dart @@ -7,7 +7,6 @@ import 'package:titan/admin/providers/group_id_provider.dart'; import 'package:titan/admin/providers/group_list_provider.dart'; import 'package:titan/admin/providers/group_provider.dart'; import 'package:titan/admin/providers/simple_groups_groups_provider.dart'; -import 'package:titan/admin/tools/constants.dart'; import 'package:titan/admin/ui/admin.dart'; import 'package:titan/admin/ui/components/admin_button.dart'; import 'package:titan/admin/ui/pages/groups/edit_group_page/search_user.dart'; @@ -19,6 +18,7 @@ import 'package:titan/tools/ui/widgets/align_left_text.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; import 'package:titan/tools/ui/widgets/text_entry.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class EditGroupPage extends HookConsumerWidget { const EditGroupPage({super.key}); @@ -60,8 +60,8 @@ class EditGroupPage extends HookConsumerWidget { description.text = group.description; return Column( children: [ - const AlignLeftText( - AdminTextConstants.edit, + AlignLeftText( + AppLocalizations.of(context)!.adminEdit, fontSize: 20, color: ColorConstants.gradient1, ), @@ -76,7 +76,7 @@ class EditGroupPage extends HookConsumerWidget { child: TextEntry( controller: name, color: ColorConstants.gradient1, - label: AdminTextConstants.name, + label: AppLocalizations.of(context)!.adminName, suffixIcon: const HeroIcon(HeroIcons.pencil), enabledColor: Colors.transparent, ), @@ -87,7 +87,9 @@ class EditGroupPage extends HookConsumerWidget { child: TextEntry( controller: description, color: ColorConstants.gradient1, - label: AdminTextConstants.description, + label: AppLocalizations.of( + context, + )!.adminDescription, suffixIcon: const HeroIcon(HeroIcons.pencil), enabledColor: Colors.transparent, ), @@ -111,20 +113,24 @@ class EditGroupPage extends HookConsumerWidget { QR.back(); displayToastWithContext( TypeMsg.msg, - AdminTextConstants.updatedGroup, + AppLocalizations.of( + context, + )!.adminUpdatedGroup, ); } else { displayToastWithContext( TypeMsg.msg, - AdminTextConstants.updatingError, + AppLocalizations.of( + context, + )!.adminUpdatingError, ); } }); }, builder: (child) => AdminButton(child: child), - child: const Text( - AdminTextConstants.edit, - style: TextStyle( + child: Text( + AppLocalizations.of(context)!.adminEdit, + style: const TextStyle( fontSize: 18, fontWeight: FontWeight.w600, color: Colors.white, diff --git a/lib/admin/ui/pages/groups/edit_group_page/results.dart b/lib/admin/ui/pages/groups/edit_group_page/results.dart index 9c3709453c..eaa6a0f8d2 100644 --- a/lib/admin/ui/pages/groups/edit_group_page/results.dart +++ b/lib/admin/ui/pages/groups/edit_group_page/results.dart @@ -4,13 +4,13 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/class/group.dart'; import 'package:titan/admin/providers/group_provider.dart'; import 'package:titan/admin/providers/simple_groups_groups_provider.dart'; -import 'package:titan/admin/tools/constants.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; import 'package:titan/user/providers/user_list_provider.dart'; +import 'package:titan/l10n/app_localizations.dart'; class MemberResults extends HookConsumerWidget { const MemberResults({super.key}); @@ -64,12 +64,12 @@ class MemberResults extends HookConsumerWidget { ); displayToastWithContext( TypeMsg.msg, - AdminTextConstants.addedMember, + AppLocalizations.of(context)!.adminAddedMember, ); } else { displayToastWithContext( TypeMsg.error, - AdminTextConstants.addingError, + AppLocalizations.of(context)!.adminAddingError, ); } }); diff --git a/lib/admin/ui/pages/groups/edit_group_page/search_user.dart b/lib/admin/ui/pages/groups/edit_group_page/search_user.dart index bc5f1723f4..21b2c78c9a 100644 --- a/lib/admin/ui/pages/groups/edit_group_page/search_user.dart +++ b/lib/admin/ui/pages/groups/edit_group_page/search_user.dart @@ -6,7 +6,6 @@ import 'package:titan/admin/class/group.dart'; import 'package:titan/admin/providers/group_id_provider.dart'; import 'package:titan/admin/providers/group_provider.dart'; import 'package:titan/admin/providers/simple_groups_groups_provider.dart'; -import 'package:titan/admin/tools/constants.dart'; import 'package:titan/admin/ui/pages/groups/edit_group_page/results.dart'; import 'package:titan/admin/ui/components/user_ui.dart'; import 'package:titan/tools/constants.dart'; @@ -17,6 +16,7 @@ import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/widgets/loader.dart'; import 'package:titan/tools/ui/widgets/styled_search_bar.dart'; import 'package:titan/user/providers/user_list_provider.dart'; +import 'package:titan/l10n/app_localizations.dart'; class SearchUser extends HookConsumerWidget { const SearchUser({super.key}); @@ -47,7 +47,7 @@ class SearchUser extends HookConsumerWidget { return Column( children: [ StyledSearchBar( - label: AdminTextConstants.members, + label: AppLocalizations.of(context)!.adminMembers, color: ColorConstants.gradient1, padding: const EdgeInsets.all(0), onChanged: (value) async { @@ -110,8 +110,8 @@ class SearchUser extends HookConsumerWidget { showDialog( context: context, builder: (BuildContext context) => CustomDialogBox( - descriptions: AdminTextConstants.removeGroupMember, - title: AdminTextConstants.deleting, + descriptions: AppLocalizations.of(context)!.adminRemoveGroupMember, + title: AppLocalizations.of(context)!.adminDeleting, onYes: () async { await tokenExpireWrapper(ref, () async { Group newGroup = g[0].copyWith( @@ -130,12 +130,12 @@ class SearchUser extends HookConsumerWidget { ); displayToastWithContext( TypeMsg.msg, - AdminTextConstants.updatedGroup, + AppLocalizations.of(context)!.adminUpdatedGroup, ); } else { displayToastWithContext( TypeMsg.msg, - AdminTextConstants.updatingError, + AppLocalizations.of(context)!.adminUpdatingError, ); } }); diff --git a/lib/admin/ui/pages/groups/group_page/group_page.dart b/lib/admin/ui/pages/groups/group_page/group_page.dart index d563677205..fa4efb6d1f 100644 --- a/lib/admin/ui/pages/groups/group_page/group_page.dart +++ b/lib/admin/ui/pages/groups/group_page/group_page.dart @@ -8,7 +8,6 @@ import 'package:titan/admin/ui/admin.dart'; import 'package:titan/admin/ui/components/item_card_ui.dart'; import 'package:titan/admin/ui/pages/groups/group_page/group_ui.dart'; import 'package:titan/loan/providers/loaner_list_provider.dart'; -import 'package:titan/admin/tools/constants.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; @@ -17,6 +16,7 @@ import 'package:titan/tools/ui/layouts/refresher.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/user/providers/user_list_provider.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class GroupsPage extends HookConsumerWidget { const GroupsPage({super.key}); @@ -49,11 +49,11 @@ class GroupsPage extends HookConsumerWidget { child: Column( children: [ const SizedBox(height: 20), - const Align( + Align( alignment: Alignment.centerLeft, child: Text( - AdminTextConstants.groups, - style: TextStyle( + AppLocalizations.of(context)!.adminGroups, + style: const TextStyle( fontSize: 20, fontWeight: FontWeight.w700, color: ColorConstants.gradient1, @@ -143,9 +143,12 @@ class GroupsPage extends HookConsumerWidget { context: context, builder: (context) { return CustomDialogBox( - title: AdminTextConstants.deleting, - descriptions: - AdminTextConstants.deleteGroup, + title: AppLocalizations.of( + context, + )!.adminDeleting, + descriptions: AppLocalizations.of( + context, + )!.adminDeleteGroup, onYes: () async { tokenExpireWrapper(ref, () async { final value = await groupsNotifier @@ -153,12 +156,16 @@ class GroupsPage extends HookConsumerWidget { if (value) { displayToastWithContext( TypeMsg.msg, - AdminTextConstants.deletedGroup, + AppLocalizations.of( + context, + )!.adminDeletedGroup, ); } else { displayToastWithContext( TypeMsg.error, - AdminTextConstants.deletingError, + AppLocalizations.of( + context, + )!.adminDeletingError, ); } }); diff --git a/lib/admin/ui/pages/main_page/main_page.dart b/lib/admin/ui/pages/main_page/main_page.dart index a458200eb1..73bf14c928 100644 --- a/lib/admin/ui/pages/main_page/main_page.dart +++ b/lib/admin/ui/pages/main_page/main_page.dart @@ -3,11 +3,11 @@ import 'package:flutter/widgets.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/router.dart'; -import 'package:titan/admin/tools/constants.dart'; import 'package:titan/admin/ui/admin.dart'; import 'package:titan/admin/ui/pages/main_page/menu_card_ui.dart'; import 'package:titan/user/providers/user_list_provider.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class AdminMainPage extends HookConsumerWidget { const AdminMainPage({super.key}); @@ -38,8 +38,8 @@ class AdminMainPage extends HookConsumerWidget { onTap: () { QR.to(AdminRouter.root + AdminRouter.editModuleVisibility); }, - child: const MenuCardUi( - text: AdminTextConstants.visibilities, + child: MenuCardUi( + text: AppLocalizations.of(context)!.adminVisibilities, icon: HeroIcons.eye, ), ), @@ -47,8 +47,8 @@ class AdminMainPage extends HookConsumerWidget { onTap: () { QR.to(AdminRouter.root + AdminRouter.groups); }, - child: const MenuCardUi( - text: AdminTextConstants.groups, + child: MenuCardUi( + text: AppLocalizations.of(context)!.adminGroups, icon: HeroIcons.users, ), ), @@ -56,8 +56,8 @@ class AdminMainPage extends HookConsumerWidget { onTap: () { QR.to(AdminRouter.root + AdminRouter.schools); }, - child: const MenuCardUi( - text: AdminTextConstants.schools, + child: MenuCardUi( + text: AppLocalizations.of(context)!.adminSchools, icon: HeroIcons.academicCap, ), ), @@ -65,8 +65,8 @@ class AdminMainPage extends HookConsumerWidget { onTap: () { QR.to(AdminRouter.root + AdminRouter.structures); }, - child: const MenuCardUi( - text: AdminTextConstants.myEclPay, + child: MenuCardUi( + text: AppLocalizations.of(context)!.adminMyEclPay, icon: HeroIcons.creditCard, ), ), diff --git a/lib/admin/ui/pages/memberships/add_edit_user_membership_page/add_edit_user_membership_page.dart b/lib/admin/ui/pages/memberships/add_edit_user_membership_page/add_edit_user_membership_page.dart index 3ec8fa38b3..8ffb5903ec 100644 --- a/lib/admin/ui/pages/memberships/add_edit_user_membership_page/add_edit_user_membership_page.dart +++ b/lib/admin/ui/pages/memberships/add_edit_user_membership_page/add_edit_user_membership_page.dart @@ -5,7 +5,6 @@ import 'package:titan/admin/class/user_association_membership.dart'; import 'package:titan/admin/class/user_association_membership_base.dart'; import 'package:titan/admin/providers/association_membership_members_list_provider.dart'; import 'package:titan/admin/providers/user_association_membership_provider.dart'; -import 'package:titan/admin/tools/constants.dart'; import 'package:titan/admin/ui/admin.dart'; import 'package:titan/admin/ui/pages/memberships/add_edit_user_membership_page/search_result.dart'; import 'package:titan/tools/constants.dart'; @@ -18,6 +17,7 @@ import 'package:titan/tools/ui/widgets/date_entry.dart'; import 'package:titan/tools/ui/widgets/styled_search_bar.dart'; import 'package:titan/user/providers/user_list_provider.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class AddEditUserMembershipPage extends HookConsumerWidget { const AddEditUserMembershipPage({super.key}); @@ -50,14 +50,14 @@ class AddEditUserMembershipPage extends HookConsumerWidget { children: [ AlignLeftText( isEdit - ? AdminTextConstants.editMembership - : AdminTextConstants.addMember, + ? AppLocalizations.of(context)!.adminEditMembership + : AppLocalizations.of(context)!.adminAddMember, ), const SizedBox(height: 20), if (!isEdit) ...[ StyledSearchBar( padding: EdgeInsets.zero, - label: AdminTextConstants.user, + label: AppLocalizations.of(context)!.adminUser, editingController: queryController, onChanged: (value) async { tokenExpireWrapper(ref, () async { @@ -80,7 +80,7 @@ class AddEditUserMembershipPage extends HookConsumerWidget { ), const SizedBox(height: 10), DateEntry( - label: AdminTextConstants.startDate, + label: AppLocalizations.of(context)!.adminStartDate, controller: start, onTap: () => getOnlyDayDate( context, @@ -91,7 +91,7 @@ class AddEditUserMembershipPage extends HookConsumerWidget { ), const SizedBox(height: 50), DateEntry( - label: AdminTextConstants.endDate, + label: AppLocalizations.of(context)!.adminEndDate, controller: end, onTap: () => getOnlyDayDate( context, @@ -110,7 +110,7 @@ class AddEditUserMembershipPage extends HookConsumerWidget { child: child, ), child: Text( - !isEdit ? AdminTextConstants.add : AdminTextConstants.edit, + !isEdit ? AppLocalizations.of(context)!.adminAdd : AppLocalizations.of(context)!.adminEdit, style: const TextStyle( fontSize: 18, fontWeight: FontWeight.w600, @@ -121,14 +121,14 @@ class AddEditUserMembershipPage extends HookConsumerWidget { if (membership.user.id == "") { displayToastWithContext( TypeMsg.msg, - AdminTextConstants.emptyUser, + AppLocalizations.of(context)!.adminEmptyUser, ); return; } if (start.text.isEmpty || end.text.isEmpty) { displayToastWithContext( TypeMsg.msg, - AdminTextConstants.emptyDate, + AppLocalizations.of(context)!.adminEmptyDate, ); return; } @@ -139,7 +139,7 @@ class AddEditUserMembershipPage extends HookConsumerWidget { ).isAfter(DateTime.parse(processDateBack(end.text)))) { displayToastWithContext( TypeMsg.error, - AdminTextConstants.dateError, + AppLocalizations.of(context)!.adminDateError, ); return; } @@ -158,13 +158,13 @@ class AddEditUserMembershipPage extends HookConsumerWidget { if (value) { displayToastWithContext( TypeMsg.msg, - AdminTextConstants.updatedMembership, + AppLocalizations.of(context)!.adminUpdatedMembership, ); QR.back(); } else { displayToastWithContext( TypeMsg.error, - AdminTextConstants.membershipUpdatingError, + AppLocalizations.of(context)!.adminMembershipUpdatingError, ); } } else { @@ -182,13 +182,13 @@ class AddEditUserMembershipPage extends HookConsumerWidget { if (value) { displayToastWithContext( TypeMsg.msg, - AdminTextConstants.addedMember, + AppLocalizations.of(context)!.adminAddedMember, ); QR.back(); } else { displayToastWithContext( TypeMsg.error, - AdminTextConstants.membershipAddingError, + AppLocalizations.of(context)!.adminMembershipAddingError, ); } } diff --git a/lib/admin/ui/pages/memberships/association_membership_detail_page/association_membership_detail_page.dart b/lib/admin/ui/pages/memberships/association_membership_detail_page/association_membership_detail_page.dart index 87b343fff3..de9d716c63 100644 --- a/lib/admin/ui/pages/memberships/association_membership_detail_page/association_membership_detail_page.dart +++ b/lib/admin/ui/pages/memberships/association_membership_detail_page/association_membership_detail_page.dart @@ -7,7 +7,6 @@ import 'package:titan/admin/providers/association_membership_members_list_provid import 'package:titan/admin/providers/association_membership_provider.dart'; import 'package:titan/admin/providers/user_association_membership_provider.dart'; import 'package:titan/admin/router.dart'; -import 'package:titan/admin/tools/constants.dart'; import 'package:titan/admin/ui/admin.dart'; import 'package:titan/admin/ui/pages/memberships/association_membership_detail_page/association_membership_information_editor.dart'; import 'package:titan/admin/ui/pages/memberships/association_membership_detail_page/association_membership_member_editable_card.dart'; @@ -17,6 +16,7 @@ import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class AssociationMembershipEditorPage extends HookConsumerWidget { final scrollKey = GlobalKey(); @@ -49,7 +49,7 @@ class AssociationMembershipEditorPage extends HookConsumerWidget { Container( alignment: Alignment.centerLeft, child: Text( - "${AdminTextConstants.associationMembership} ${associationMembership.name}", + "${AppLocalizations.of(context)!.adminAssociationMembership} ${associationMembership.name}", style: TextStyle( fontSize: 20, fontWeight: FontWeight.w700, @@ -61,9 +61,9 @@ class AssociationMembershipEditorPage extends HookConsumerWidget { const SizedBox(height: 30), Row( children: [ - const Text( - AdminTextConstants.members, - style: TextStyle( + Text( + AppLocalizations.of(context)!.adminMembers, + style: const TextStyle( fontSize: 20, fontWeight: FontWeight.w700, color: ColorConstants.gradient1, @@ -71,7 +71,7 @@ class AssociationMembershipEditorPage extends HookConsumerWidget { ), const SizedBox(width: 10), Text( - "(${associationMembershipFilteredList.length} ${AdminTextConstants.members})", + "(${associationMembershipFilteredList.length} ${AppLocalizations.of(context)!.adminMembers})", style: const TextStyle( fontSize: 20, fontWeight: FontWeight.w700, @@ -113,14 +113,14 @@ class AssociationMembershipEditorPage extends HookConsumerWidget { ), const SizedBox(height: 10), ExpansionTile( - title: const Text(AdminTextConstants.filters), + title: Text(AppLocalizations.of(context)!.adminFilters), children: const [SearchFilters()], ), const SizedBox(height: 20), ResearchBar(), const SizedBox(height: 10), associationMembershipFilteredList.isEmpty - ? const Text(AdminTextConstants.noMember) + ? Text(AppLocalizations.of(context)!.adminNoMember) : SizedBox( height: 400, child: ListView.builder( diff --git a/lib/admin/ui/pages/memberships/association_membership_detail_page/association_membership_information_editor.dart b/lib/admin/ui/pages/memberships/association_membership_detail_page/association_membership_information_editor.dart index 1b19086843..2416a8a98e 100644 --- a/lib/admin/ui/pages/memberships/association_membership_detail_page/association_membership_information_editor.dart +++ b/lib/admin/ui/pages/memberships/association_membership_detail_page/association_membership_information_editor.dart @@ -5,12 +5,12 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/providers/all_groups_list_provider.dart'; import 'package:titan/admin/providers/association_membership_list_provider.dart'; import 'package:titan/admin/providers/association_membership_provider.dart'; -import 'package:titan/admin/tools/constants.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; import 'package:titan/tools/ui/layouts/add_edit_button_layout.dart'; +import 'package:titan/l10n/app_localizations.dart'; class AssociationMembershipInformationEditor extends HookConsumerWidget { final scrollKey = GlobalKey(); @@ -55,7 +55,7 @@ class AssociationMembershipInformationEditor extends HookConsumerWidget { controller: name, cursorColor: ColorConstants.gradient1, decoration: InputDecoration( - labelText: AdminTextConstants.name, + labelText: AppLocalizations.of(context)!.adminName, labelStyle: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, @@ -75,7 +75,9 @@ class AssociationMembershipInformationEditor extends HookConsumerWidget { ), validator: (value) { if (value == null || value.isEmpty) { - return AdminTextConstants.emptyFieldError; + return AppLocalizations.of( + context, + )!.adminEmptyFieldError; } return null; }, @@ -84,11 +86,11 @@ class AssociationMembershipInformationEditor extends HookConsumerWidget { ], ), ), - const Align( + Align( alignment: Alignment.centerLeft, child: Text( - AdminTextConstants.group, - style: TextStyle(fontWeight: FontWeight.bold), + AppLocalizations.of(context)!.adminGroup, + style: const TextStyle(fontWeight: FontWeight.bold), ), ), DropdownButtonFormField( @@ -104,8 +106,8 @@ class AssociationMembershipInformationEditor extends HookConsumerWidget { ), ) .toList(), - decoration: const InputDecoration( - hintText: AdminTextConstants.group, + decoration: InputDecoration( + hintText: AppLocalizations.of(context)!.adminGroup, ), ), const SizedBox(height: 20), @@ -136,19 +138,21 @@ class AssociationMembershipInformationEditor extends HookConsumerWidget { ); displayToastWithContext( TypeMsg.msg, - AdminTextConstants.updatedAssociationMembership, + AppLocalizations.of( + context, + )!.adminUpdatedAssociationMembership, ); } else { displayToastWithContext( TypeMsg.msg, - AdminTextConstants.updatingError, + AppLocalizations.of(context)!.adminUpdatingError, ); } }); }, - child: const Text( - AdminTextConstants.edit, - style: TextStyle( + child: Text( + AppLocalizations.of(context)!.adminEdit, + style: const TextStyle( fontSize: 18, fontWeight: FontWeight.w600, color: Color.fromARGB(255, 255, 255, 255), diff --git a/lib/admin/ui/pages/memberships/association_membership_detail_page/research_bar.dart b/lib/admin/ui/pages/memberships/association_membership_detail_page/research_bar.dart index 783cb553fb..84b1f5accf 100644 --- a/lib/admin/ui/pages/memberships/association_membership_detail_page/research_bar.dart +++ b/lib/admin/ui/pages/memberships/association_membership_detail_page/research_bar.dart @@ -2,8 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/providers/research_filter_provider.dart'; -import 'package:titan/admin/tools/constants.dart'; import 'package:titan/tools/constants.dart'; +import 'package:titan/l10n/app_localizations.dart'; class ResearchBar extends HookConsumerWidget { const ResearchBar({super.key}); @@ -21,14 +21,18 @@ class ResearchBar extends HookConsumerWidget { focusNode: focusNode, controller: editingController, cursorColor: Color(0xFF1D1D1D), - decoration: const InputDecoration( + decoration: InputDecoration( isDense: true, - suffixIcon: Icon(Icons.search, color: Color(0xFF1D1D1D), size: 30), + suffixIcon: const Icon( + Icons.search, + color: Color(0xFF1D1D1D), + size: 30, + ), label: Text( - AdminTextConstants.research, - style: TextStyle(color: Color(0xFF1D1D1D)), + AppLocalizations.of(context)!.adminResearch, + style: const TextStyle(color: Color(0xFF1D1D1D)), ), - focusedBorder: UnderlineInputBorder( + focusedBorder: const UnderlineInputBorder( borderSide: BorderSide(color: ColorConstants.gradient1), ), ), diff --git a/lib/admin/ui/pages/memberships/association_membership_detail_page/search_filters.dart b/lib/admin/ui/pages/memberships/association_membership_detail_page/search_filters.dart index 694d366265..3fbea81199 100644 --- a/lib/admin/ui/pages/memberships/association_membership_detail_page/search_filters.dart +++ b/lib/admin/ui/pages/memberships/association_membership_detail_page/search_filters.dart @@ -3,13 +3,13 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/providers/association_membership_members_list_provider.dart'; import 'package:titan/admin/providers/association_membership_provider.dart'; -import 'package:titan/admin/tools/constants.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; import 'package:titan/tools/ui/layouts/add_edit_button_layout.dart'; import 'package:titan/tools/ui/widgets/date_entry.dart'; +import 'package:titan/l10n/app_localizations.dart'; class SearchFilters extends HookConsumerWidget { const SearchFilters({super.key}); @@ -38,11 +38,11 @@ class SearchFilters extends HookConsumerWidget { child: Column( children: [ Text( - AdminTextConstants.startDate, + AppLocalizations.of(context)!.adminStartDate, style: const TextStyle(fontSize: 18), ), DateEntry( - label: AdminTextConstants.startDateMinimal, + label: AppLocalizations.of(context)!.adminStartDateMinimal, controller: startMinimal, onTap: () => getOnlyDayDate( context, @@ -53,7 +53,7 @@ class SearchFilters extends HookConsumerWidget { ), const SizedBox(height: 20), DateEntry( - label: AdminTextConstants.startDateMaximal, + label: AppLocalizations.of(context)!.adminStartDateMaximal, controller: startMaximal, onTap: () => getOnlyDayDate( context, @@ -71,11 +71,11 @@ class SearchFilters extends HookConsumerWidget { child: Column( children: [ Text( - AdminTextConstants.endDate, + AppLocalizations.of(context)!.adminEndDate, style: const TextStyle(fontSize: 18), ), DateEntry( - label: AdminTextConstants.endDateMinimal, + label: AppLocalizations.of(context)!.adminEndDateMinimal, controller: endMinimal, onTap: () => getOnlyDayDate( context, @@ -86,7 +86,7 @@ class SearchFilters extends HookConsumerWidget { ), const SizedBox(height: 20), DateEntry( - label: AdminTextConstants.endDateMaximal, + label: AppLocalizations.of(context)!.adminEndDateMaximal, controller: endMaximal, onTap: () => getOnlyDayDate( context, @@ -139,7 +139,7 @@ class SearchFilters extends HookConsumerWidget { child: child, ), child: Text( - AdminTextConstants.validateFilters, + AppLocalizations.of(context)!.adminValidateFilters, style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, @@ -173,7 +173,7 @@ class SearchFilters extends HookConsumerWidget { child: child, ), child: Text( - AdminTextConstants.clearFilters, + AppLocalizations.of(context)!.adminClearFilters, style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, diff --git a/lib/admin/ui/pages/memberships/association_membership_page/association_membership_creation_dialog.dart b/lib/admin/ui/pages/memberships/association_membership_page/association_membership_creation_dialog.dart index 9e0559b996..39d485c27d 100644 --- a/lib/admin/ui/pages/memberships/association_membership_page/association_membership_creation_dialog.dart +++ b/lib/admin/ui/pages/memberships/association_membership_page/association_membership_creation_dialog.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:titan/admin/class/simple_group.dart'; -import 'package:titan/admin/tools/constants.dart'; import 'package:titan/tools/constants.dart'; +import 'package:titan/l10n/app_localizations.dart'; class MembershipCreationDialogBox extends StatelessWidget { static const Color titleColor = ColorConstants.gradient1; @@ -68,7 +68,9 @@ class MembershipCreationDialogBox extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Text( - AdminTextConstants.createAssociationMembership, + AppLocalizations.of( + context, + )!.adminCreateAssociationMembership, style: const TextStyle( fontSize: 25, fontWeight: FontWeight.w800, @@ -78,8 +80,10 @@ class MembershipCreationDialogBox extends StatelessWidget { const SizedBox(height: 15), TextField( controller: nameController, - decoration: const InputDecoration( - hintText: AdminTextConstants.associationMembershipName, + decoration: InputDecoration( + hintText: AppLocalizations.of( + context, + )!.adminAssociationMembershipName, ), ), const SizedBox(height: 20), @@ -96,8 +100,8 @@ class MembershipCreationDialogBox extends StatelessWidget { ), ) .toList(), - decoration: const InputDecoration( - hintText: AdminTextConstants.group, + decoration: InputDecoration( + hintText: AppLocalizations.of(context)!.adminGroup, ), ), const SizedBox(height: 20), diff --git a/lib/admin/ui/pages/memberships/association_membership_page/association_membership_page.dart b/lib/admin/ui/pages/memberships/association_membership_page/association_membership_page.dart index 8ae53d5400..b44c059335 100644 --- a/lib/admin/ui/pages/memberships/association_membership_page/association_membership_page.dart +++ b/lib/admin/ui/pages/memberships/association_membership_page/association_membership_page.dart @@ -9,7 +9,6 @@ import 'package:titan/admin/providers/association_membership_provider.dart'; import 'package:titan/admin/router.dart'; import 'package:titan/admin/ui/admin.dart'; import 'package:titan/admin/ui/components/item_card_ui.dart'; -import 'package:titan/admin/tools/constants.dart'; import 'package:titan/admin/ui/pages/memberships/association_membership_page/association_membership_creation_dialog.dart'; import 'package:titan/admin/ui/pages/memberships/association_membership_page/association_membership_ui.dart'; import 'package:titan/tools/constants.dart'; @@ -19,6 +18,7 @@ import 'package:titan/tools/functions.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class AssociationMembershipsPage extends HookConsumerWidget { const AssociationMembershipsPage({super.key}); @@ -55,18 +55,18 @@ class AssociationMembershipsPage extends HookConsumerWidget { child: Column( children: [ const SizedBox(height: 20), - const Align( + Align( alignment: Alignment.centerLeft, child: Text( - AdminTextConstants.associationsMemberships, - style: TextStyle( + AppLocalizations.of(context)!.adminAssociationsMemberships, + style: const TextStyle( fontSize: 20, fontWeight: FontWeight.w700, color: ColorConstants.gradient1, ), ), ), - const SizedBox(height: 30), + SizedBox(height: 30), AsyncChild( value: associationsMemberships, builder: (context, g) { @@ -104,13 +104,16 @@ class AssociationMembershipsPage extends HookConsumerWidget { if (value) { displayToastWithContext( TypeMsg.msg, - AdminTextConstants - .createdAssociationMembership, + AppLocalizations.of( + context, + )!.adminCreatedAssociationMembership, ); } else { displayToastWithContext( TypeMsg.error, - AdminTextConstants.creationError, + AppLocalizations.of( + context, + )!.adminCreationError, ); } }); @@ -154,9 +157,12 @@ class AssociationMembershipsPage extends HookConsumerWidget { context: context, builder: (context) { return CustomDialogBox( - title: AdminTextConstants.deleting, - descriptions: AdminTextConstants - .deleteAssociationMembership, + title: AppLocalizations.of( + context, + )!.adminDeleting, + descriptions: AppLocalizations.of( + context, + )!.adminDeleteAssociationMembership, onYes: () async { tokenExpireWrapper(ref, () async { final value = @@ -167,13 +173,16 @@ class AssociationMembershipsPage extends HookConsumerWidget { if (value) { displayToastWithContext( TypeMsg.msg, - AdminTextConstants - .deletedAssociationMembership, + AppLocalizations.of( + context, + )!.adminDeletedAssociationMembership, ); } else { displayToastWithContext( TypeMsg.error, - AdminTextConstants.deletingError, + AppLocalizations.of( + context, + )!.adminDeletingError, ); } }); diff --git a/lib/admin/ui/pages/schools/add_school_page/add_school_page.dart b/lib/admin/ui/pages/schools/add_school_page/add_school_page.dart index 7ae21c8247..26a8caf55a 100644 --- a/lib/admin/ui/pages/schools/add_school_page/add_school_page.dart +++ b/lib/admin/ui/pages/schools/add_school_page/add_school_page.dart @@ -3,7 +3,6 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/class/school.dart'; import 'package:titan/admin/providers/school_list_provider.dart'; -import 'package:titan/admin/tools/constants.dart'; import 'package:titan/admin/ui/admin.dart'; import 'package:titan/admin/ui/components/admin_button.dart'; import 'package:titan/admin/ui/components/text_editing.dart'; @@ -12,6 +11,7 @@ import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/widgets/align_left_text.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class AddSchoolPage extends HookConsumerWidget { const AddSchoolPage({super.key}); @@ -37,12 +37,15 @@ class AddSchoolPage extends HookConsumerWidget { key: key, child: Column( children: [ - const AlignLeftText(AdminTextConstants.addSchool), + AlignLeftText(AppLocalizations.of(context)!.adminAddSchool), const SizedBox(height: 30), - TextEditing(controller: name, label: AdminTextConstants.name), + TextEditing( + controller: name, + label: AppLocalizations.of(context)!.adminName, + ), TextEditing( controller: emailRegex, - label: AdminTextConstants.emailRegex, + label: AppLocalizations.of(context)!.adminEmailRegex, ), WaitingButton( onTap: () async { @@ -58,20 +61,20 @@ class AddSchoolPage extends HookConsumerWidget { QR.back(); displayToastWithContext( TypeMsg.msg, - AdminTextConstants.addedSchool, + AppLocalizations.of(context)!.adminAddedSchool, ); } else { displayToastWithContext( TypeMsg.error, - AdminTextConstants.addingError, + AppLocalizations.of(context)!.adminAddingError, ); } }); }, builder: (child) => AdminButton(child: child), - child: const Text( - AdminTextConstants.add, - style: TextStyle( + child: Text( + AppLocalizations.of(context)!.adminAdd, + style: const TextStyle( fontSize: 18, fontWeight: FontWeight.w600, color: Colors.white, diff --git a/lib/admin/ui/pages/schools/edit_school_page/edit_school_page.dart b/lib/admin/ui/pages/schools/edit_school_page/edit_school_page.dart index 4253ff6305..36eb843ec6 100644 --- a/lib/admin/ui/pages/schools/edit_school_page/edit_school_page.dart +++ b/lib/admin/ui/pages/schools/edit_school_page/edit_school_page.dart @@ -5,7 +5,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/class/school.dart'; import 'package:titan/admin/providers/school_list_provider.dart'; import 'package:titan/admin/providers/school_provider.dart'; -import 'package:titan/admin/tools/constants.dart'; +import 'package:titan/admin/tools/function.dart'; import 'package:titan/admin/ui/admin.dart'; import 'package:titan/admin/ui/components/admin_button.dart'; import 'package:titan/tools/constants.dart'; @@ -15,6 +15,7 @@ import 'package:titan/tools/ui/widgets/align_left_text.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; import 'package:titan/tools/ui/widgets/text_entry.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class EditSchoolPage extends HookConsumerWidget { const EditSchoolPage({super.key}); @@ -32,7 +33,7 @@ class EditSchoolPage extends HookConsumerWidget { displayToast(context, type, msg); } - name.text = school.name; + name.text = getSchoolNameFromId(school.id, school.name, context); emailRegex.text = school.emailRegex; return AdminTemplate( @@ -40,8 +41,8 @@ class EditSchoolPage extends HookConsumerWidget { padding: const EdgeInsets.symmetric(horizontal: 30.0), child: Column( children: [ - const AlignLeftText( - AdminTextConstants.edit, + AlignLeftText( + AppLocalizations.of(context)!.adminEdit, fontSize: 20, color: ColorConstants.gradient1, ), @@ -56,7 +57,7 @@ class EditSchoolPage extends HookConsumerWidget { child: TextEntry( controller: name, color: ColorConstants.gradient1, - label: AdminTextConstants.name, + label: AppLocalizations.of(context)!.adminName, suffixIcon: const HeroIcon(HeroIcons.pencil), enabledColor: Colors.transparent, ), @@ -67,7 +68,7 @@ class EditSchoolPage extends HookConsumerWidget { child: TextEntry( controller: emailRegex, color: ColorConstants.gradient1, - label: AdminTextConstants.emailRegex, + label: AppLocalizations.of(context)!.adminEmailRegex, suffixIcon: const HeroIcon(HeroIcons.pencil), enabledColor: Colors.transparent, ), @@ -91,20 +92,20 @@ class EditSchoolPage extends HookConsumerWidget { QR.back(); displayToastWithContext( TypeMsg.msg, - AdminTextConstants.updatedGroup, + AppLocalizations.of(context)!.adminUpdatedGroup, ); } else { displayToastWithContext( TypeMsg.msg, - AdminTextConstants.updatingError, + AppLocalizations.of(context)!.adminUpdatingError, ); } }); }, builder: (child) => AdminButton(child: child), - child: const Text( - AdminTextConstants.edit, - style: TextStyle( + child: Text( + AppLocalizations.of(context)!.adminEdit, + style: const TextStyle( fontSize: 18, fontWeight: FontWeight.w600, color: Colors.white, diff --git a/lib/admin/ui/pages/schools/school_page/school_page.dart b/lib/admin/ui/pages/schools/school_page/school_page.dart index e7c12830d0..68c393b83c 100644 --- a/lib/admin/ui/pages/schools/school_page/school_page.dart +++ b/lib/admin/ui/pages/schools/school_page/school_page.dart @@ -7,7 +7,6 @@ import 'package:titan/admin/router.dart'; import 'package:titan/admin/ui/admin.dart'; import 'package:titan/admin/ui/components/item_card_ui.dart'; import 'package:titan/admin/ui/pages/schools/school_page/school_ui.dart'; -import 'package:titan/admin/tools/constants.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; @@ -16,6 +15,7 @@ import 'package:titan/tools/ui/layouts/refresher.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/user/providers/user_list_provider.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class SchoolsPage extends HookConsumerWidget { const SchoolsPage({super.key}); @@ -40,11 +40,11 @@ class SchoolsPage extends HookConsumerWidget { child: Column( children: [ const SizedBox(height: 20), - const Align( + Align( alignment: Alignment.centerLeft, child: Text( - AdminTextConstants.schools, - style: TextStyle( + AppLocalizations.of(context)!.adminSchools, + style: const TextStyle( fontSize: 20, fontWeight: FontWeight.w700, color: ColorConstants.gradient1, @@ -99,22 +99,31 @@ class SchoolsPage extends HookConsumerWidget { context: context, builder: (context) { return CustomDialogBox( - title: AdminTextConstants.deleting, - descriptions: - AdminTextConstants.deleteSchool, + title: AppLocalizations.of( + context, + )!.adminDeleting, + descriptions: AppLocalizations.of( + context, + )!.adminDeleteSchool, onYes: () async { + final deletedMsg = AppLocalizations.of( + context, + )!.adminDeletedSchool; + final errorMsg = AppLocalizations.of( + context, + )!.adminDeletingError; tokenExpireWrapper(ref, () async { final value = await schoolsNotifier .deleteSchool(school); if (value) { displayToastWithContext( TypeMsg.msg, - AdminTextConstants.deletedSchool, + deletedMsg, ); } else { displayToastWithContext( TypeMsg.error, - AdminTextConstants.deletingError, + errorMsg, ); } }); diff --git a/lib/admin/ui/pages/schools/school_page/school_ui.dart b/lib/admin/ui/pages/schools/school_page/school_ui.dart index 54f788df7b..669472ae17 100644 --- a/lib/admin/ui/pages/schools/school_page/school_ui.dart +++ b/lib/admin/ui/pages/schools/school_page/school_ui.dart @@ -3,6 +3,7 @@ import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/class/school.dart'; import 'package:titan/admin/tools/constants.dart'; +import 'package:titan/admin/tools/function.dart'; import 'package:titan/admin/ui/components/item_card_ui.dart'; import 'package:titan/admin/ui/pages/schools/school_page/school_button.dart'; import 'package:titan/tools/constants.dart'; @@ -26,7 +27,7 @@ class SchoolUi extends HookConsumerWidget { const SizedBox(width: 10), Expanded( child: Text( - school.name, + getSchoolNameFromId(school.id, school.name, context), style: const TextStyle( color: Colors.black, fontSize: 20, diff --git a/lib/admin/ui/pages/structure_page/structure_page.dart b/lib/admin/ui/pages/structure_page/structure_page.dart index 82b20900f7..3e4f873a40 100644 --- a/lib/admin/ui/pages/structure_page/structure_page.dart +++ b/lib/admin/ui/pages/structure_page/structure_page.dart @@ -7,7 +7,6 @@ import 'package:titan/admin/router.dart'; import 'package:titan/admin/ui/admin.dart'; import 'package:titan/admin/ui/components/item_card_ui.dart'; import 'package:titan/admin/ui/pages/structure_page/structure_ui.dart'; -import 'package:titan/admin/tools/constants.dart'; import 'package:titan/paiement/class/structure.dart'; import 'package:titan/paiement/providers/structure_list_provider.dart'; import 'package:titan/tools/constants.dart'; @@ -19,6 +18,7 @@ import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/user/class/simple_users.dart'; import 'package:titan/user/providers/user_list_provider.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class StructurePage extends HookConsumerWidget { const StructurePage({super.key}); @@ -47,11 +47,11 @@ class StructurePage extends HookConsumerWidget { child: Column( children: [ const SizedBox(height: 20), - const Align( + Align( alignment: Alignment.centerLeft, child: Text( - AdminTextConstants.structures, - style: TextStyle( + AppLocalizations.of(context)!.adminStructures, + style: const TextStyle( fontSize: 20, fontWeight: FontWeight.w700, color: ColorConstants.gradient1, @@ -113,9 +113,12 @@ class StructurePage extends HookConsumerWidget { context: context, builder: (context) { return CustomDialogBox( - title: AdminTextConstants.deleting, - descriptions: - AdminTextConstants.deleteGroup, + title: AppLocalizations.of( + context, + )!.adminDeleting, + descriptions: AppLocalizations.of( + context, + )!.adminDeleteGroup, onYes: () async { tokenExpireWrapper(ref, () async { final value = await structuresNotifier @@ -123,12 +126,16 @@ class StructurePage extends HookConsumerWidget { if (value) { displayToastWithContext( TypeMsg.msg, - AdminTextConstants.deletedGroup, + AppLocalizations.of( + context, + )!.adminDeletedGroup, ); } else { displayToastWithContext( TypeMsg.error, - AdminTextConstants.deletingError, + AppLocalizations.of( + context, + )!.adminDeletingError, ); } }); diff --git a/lib/advert/tools/constants.dart b/lib/advert/tools/constants.dart index d1f8eb8100..43a97337d8 100644 --- a/lib/advert/tools/constants.dart +++ b/lib/advert/tools/constants.dart @@ -1,51 +1,5 @@ import 'dart:ui'; -class AdvertTextConstants { - static const String add = 'Ajouter'; - static const String addedAdvert = 'Annonce publiée'; - static const String addedAnnouncer = 'Annonceur ajouté'; - static const String addingError = 'Erreur lors de l\'ajout'; - static const String admin = 'Admin'; - static const String advert = 'Annonce'; - static const String choosingAnnouncer = 'Veuillez choisir un annonceur'; - static const String choosingPoster = 'Veuillez choisir une image'; - static const String content = 'Contenu'; - static const String deleteAdvert = 'Supprimer l\'annonce ?'; - static const String deleteAnnouncer = 'Supprimer l\'annonceur ?'; - static const String deleting = 'Suppression'; - static const String edit = 'Modifier'; - static const String editedAdvert = 'Annonce modifiée'; - static const String editingError = 'Erreur lors de la modification'; - static const String groupAdvert = 'Groupe'; - static const String incorrectOrMissingFields = - 'Champs incorrects ou manquants'; - static const String invalidNumber = 'Veuillez entrer un nombre'; - static const String management = 'Gestion'; - static const String modifyAnnouncingGroup = 'Modifier un groupe d\'annonce'; - static const String noMoreAnnouncer = 'Aucun annonceur n\'est disponible'; - static const String noValue = 'Veuillez entrer une valeur'; - static const String positiveNumber = 'Veuillez entrer un nombre positif'; - static const String removedAnnouncer = 'Annonceur supprimé'; - static const String removingError = 'Erreur lors de la suppression'; - static const String tags = 'Tags'; - static const String title = 'Titre'; - - static const List months = [ - 'Janv.', - 'Févr.', - 'Mars', - 'Avr.', - 'Mai', - 'Juin', - 'Juill.', - 'Août', - 'Sept.', - 'Oct.', - 'Nov.', - 'Déc.', - ]; -} - class AdvertColorConstants { static const Color redGradient1 = Color(0xFF9E131F); static const Color redGradient2 = Color(0xFF590512); diff --git a/lib/advert/tools/functions.dart b/lib/advert/tools/functions.dart index fd26da6d2e..7a497e6549 100644 --- a/lib/advert/tools/functions.dart +++ b/lib/advert/tools/functions.dart @@ -1,4 +1,6 @@ -import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:titan/l10n/app_localizations.dart'; Color invert(Color color) { return Color.from( @@ -18,3 +20,21 @@ Color generateColor(String uuid) { double luminance = color.computeLuminance(); return luminance < 0.5 ? color : invert(color); } + +List getLocalizedMonths(BuildContext context) { + final l10n = AppLocalizations.of(context)!; + return [ + l10n.advertMonthJan, + l10n.advertMonthFeb, + l10n.advertMonthMar, + l10n.advertMonthApr, + l10n.advertMonthMay, + l10n.advertMonthJun, + l10n.advertMonthJul, + l10n.advertMonthAug, + l10n.advertMonthSep, + l10n.advertMonthOct, + l10n.advertMonthNov, + l10n.advertMonthDec, + ]; +} diff --git a/lib/advert/ui/components/advert_card.dart b/lib/advert/ui/components/advert_card.dart index b21911111f..43d06906d9 100644 --- a/lib/advert/ui/components/advert_card.dart +++ b/lib/advert/ui/components/advert_card.dart @@ -6,7 +6,7 @@ import 'package:intl/intl.dart'; import 'package:titan/advert/class/advert.dart'; import 'package:titan/advert/providers/advert_poster_provider.dart'; import 'package:titan/advert/providers/advert_posters_provider.dart'; -import 'package:titan/advert/tools/constants.dart'; +import 'package:titan/advert/tools/functions.dart'; import 'package:titan/cinema/tools/functions.dart'; import 'package:titan/navigation/providers/is_web_format_provider.dart'; import 'package:titan/tools/ui/builders/auto_loader_child.dart'; @@ -249,7 +249,7 @@ class AdvertCard extends HookConsumerWidget { ), ), AutoSizeText( - AdvertTextConstants.months[int.parse( + getLocalizedMonths(context)[int.parse( DateFormat('MM').format(advert.date), ) - 1], diff --git a/lib/advert/ui/pages/admin_page/admin_page.dart b/lib/advert/ui/pages/admin_page/admin_page.dart index 6092ee5bc9..9ea0149243 100644 --- a/lib/advert/ui/pages/admin_page/admin_page.dart +++ b/lib/advert/ui/pages/admin_page/admin_page.dart @@ -8,7 +8,6 @@ import 'package:titan/advert/providers/advert_posters_provider.dart'; import 'package:titan/advert/providers/advert_provider.dart'; import 'package:titan/advert/providers/announcer_list_provider.dart'; import 'package:titan/advert/providers/announcer_provider.dart'; -import 'package:titan/advert/tools/constants.dart'; import 'package:titan/advert/ui/pages/admin_page/admin_advert_card.dart'; import 'package:titan/advert/ui/pages/advert.dart'; import 'package:titan/advert/router.dart'; @@ -18,6 +17,7 @@ import 'package:titan/tools/ui/layouts/card_layout.dart'; import 'package:titan/tools/ui/layouts/column_refresher.dart'; import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class AdvertAdminPage extends HookConsumerWidget { const AdvertAdminPage({super.key}); @@ -120,8 +120,8 @@ class AdvertAdminPage extends HookConsumerWidget { context: context, builder: (context) { return CustomDialogBox( - title: AdvertTextConstants.deleting, - descriptions: AdvertTextConstants.deleteAdvert, + title: AppLocalizations.of(context)!.advertDeleting, + descriptions: AppLocalizations.of(context)!.advertDeleteAdvert, onYes: () { advertListNotifier.deleteAdvert(advert); advertPostersNotifier.deleteE(advert.id, 0); diff --git a/lib/advert/ui/pages/form_page/add_edit_advert_page.dart b/lib/advert/ui/pages/form_page/add_edit_advert_page.dart index 0cac9788ce..aef9bfa0b8 100644 --- a/lib/advert/ui/pages/form_page/add_edit_advert_page.dart +++ b/lib/advert/ui/pages/form_page/add_edit_advert_page.dart @@ -13,7 +13,6 @@ import 'package:titan/advert/providers/advert_poster_provider.dart'; import 'package:titan/advert/providers/advert_posters_provider.dart'; import 'package:titan/advert/providers/advert_provider.dart'; import 'package:titan/advert/providers/announcer_provider.dart'; -import 'package:titan/advert/tools/constants.dart'; import 'package:titan/advert/ui/pages/advert.dart'; import 'package:titan/advert/ui/components/announcer_bar.dart'; import 'package:titan/tools/functions.dart'; @@ -23,6 +22,7 @@ import 'package:titan/tools/ui/layouts/add_edit_button_layout.dart'; import 'package:titan/tools/ui/widgets/image_picker_on_tap.dart'; import 'package:titan/tools/ui/widgets/text_entry.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class AdvertAddEditAdvertPage extends HookConsumerWidget { const AdvertAddEditAdvertPage({super.key}); @@ -72,14 +72,14 @@ class AdvertAddEditAdvertPage extends HookConsumerWidget { children: [ TextEntry( maxLines: 1, - label: AdvertTextConstants.title, + label: AppLocalizations.of(context)!.advertTitle, controller: title, ), const SizedBox(height: 20), FormField( validator: (e) { if (poster.value == null && !isEdit) { - return AdvertTextConstants.choosingPoster; + return AppLocalizations.of(context)!.advertChoosingPoster; } return null; }, @@ -166,7 +166,7 @@ class AdvertAddEditAdvertPage extends HookConsumerWidget { minLines: 5, maxLines: 50, keyboardType: TextInputType.multiline, - label: AdvertTextConstants.content, + label: AppLocalizations.of(context)!.advertContent, controller: content, ), ], @@ -176,7 +176,7 @@ class AdvertAddEditAdvertPage extends HookConsumerWidget { FormField>( validator: (e) { if (selectedAnnouncers.isEmpty) { - return AdvertTextConstants.choosingAnnouncer; + return AppLocalizations.of(context)!.advertChoosingAnnouncer; } return null; }, @@ -208,7 +208,7 @@ class AdvertAddEditAdvertPage extends HookConsumerWidget { children: [ TextEntry( maxLines: 1, - label: AdvertTextConstants.tags, + label: AppLocalizations.of(context)!.advertTags, canBeEmpty: true, controller: textTagsController, ), @@ -241,7 +241,7 @@ class AdvertAddEditAdvertPage extends HookConsumerWidget { if (isEdit) { displayAdvertToastWithContext( TypeMsg.msg, - AdvertTextConstants.editedAdvert, + AppLocalizations.of(context)!.advertEditedAdvert, ); advertList.maybeWhen( data: (list) { @@ -257,7 +257,7 @@ class AdvertAddEditAdvertPage extends HookConsumerWidget { } else { displayAdvertToastWithContext( TypeMsg.msg, - AdvertTextConstants.addedAdvert, + AppLocalizations.of(context)!.advertAddedAdvert, ); advertList.maybeWhen( data: (list) { @@ -273,7 +273,7 @@ class AdvertAddEditAdvertPage extends HookConsumerWidget { } else { displayAdvertToastWithContext( TypeMsg.error, - AdvertTextConstants.editingError, + AppLocalizations.of(context)!.advertEditingError, ); } }); @@ -281,14 +281,14 @@ class AdvertAddEditAdvertPage extends HookConsumerWidget { displayToast( context, TypeMsg.error, - AdvertTextConstants.incorrectOrMissingFields, + AppLocalizations.of(context)!.advertIncorrectOrMissingFields, ); } }, child: Text( isEdit - ? AdvertTextConstants.edit - : AdvertTextConstants.add, + ? AppLocalizations.of(context)!.advertEdit + : AppLocalizations.of(context)!.advertAdd, style: const TextStyle( color: Colors.white, fontSize: 25, diff --git a/lib/advert/ui/pages/form_page/add_rem_announcer_page.dart b/lib/advert/ui/pages/form_page/add_rem_announcer_page.dart index f3843d7288..5911655cbb 100644 --- a/lib/advert/ui/pages/form_page/add_rem_announcer_page.dart +++ b/lib/advert/ui/pages/form_page/add_rem_announcer_page.dart @@ -5,7 +5,6 @@ import 'package:titan/admin/providers/group_list_provider.dart'; import 'package:titan/advert/class/announcer.dart'; import 'package:titan/advert/providers/all_announcer_list_provider.dart'; import 'package:titan/advert/providers/announcer_list_provider.dart'; -import 'package:titan/advert/tools/constants.dart'; import 'package:titan/advert/ui/pages/advert.dart'; import 'package:titan/advert/ui/pages/form_page/announcer_card.dart'; import 'package:titan/tools/constants.dart'; @@ -13,6 +12,7 @@ import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; +import 'package:titan/l10n/app_localizations.dart'; class AddRemAnnouncerPage extends HookConsumerWidget { const AddRemAnnouncerPage({super.key}); @@ -40,11 +40,13 @@ class AddRemAnnouncerPage extends HookConsumerWidget { SizedBox( child: Column( children: [ - const Align( + Align( alignment: Alignment.centerLeft, child: Text( - AdvertTextConstants.modifyAnnouncingGroup, - style: TextStyle( + AppLocalizations.of( + context, + )!.advertModifyAnnouncingGroup, + style: const TextStyle( fontSize: 20, fontWeight: FontWeight.w700, color: ColorConstants.gradient1, @@ -75,6 +77,14 @@ class AddRemAnnouncerPage extends HookConsumerWidget { name: e.name, ); tokenExpireWrapper(ref, () async { + final addedMessage = + AppLocalizations.of( + context, + )!.advertAddedAnnouncer; + final errorMessage = + AppLocalizations.of( + context, + )!.advertAddingError; final value = await announcerListNotifier .addAnnouncer( @@ -83,14 +93,12 @@ class AddRemAnnouncerPage extends HookConsumerWidget { if (value) { displayToastWithContext( TypeMsg.msg, - AdvertTextConstants - .addedAnnouncer, + addedMessage, ); } else { displayToastWithContext( TypeMsg.error, - AdvertTextConstants - .addingError, + errorMessage, ); } announcerListNotifier @@ -112,11 +120,13 @@ class AddRemAnnouncerPage extends HookConsumerWidget { context: context, builder: (context) { return CustomDialogBox( - title: AdvertTextConstants - .deleting, + title: AppLocalizations.of( + context, + )!.advertDeleting, descriptions: - AdvertTextConstants - .deleteAnnouncer, + AppLocalizations.of( + context, + )!.advertDeleteAnnouncer, onYes: () { tokenExpireWrapper(ref, () async { final value = await announcerListNotifier @@ -132,14 +142,16 @@ class AddRemAnnouncerPage extends HookConsumerWidget { if (value) { displayToastWithContext( TypeMsg.msg, - AdvertTextConstants - .removedAnnouncer, + AppLocalizations.of( + context, + )!.advertRemovedAnnouncer, ); } else { displayToastWithContext( TypeMsg.error, - AdvertTextConstants - .removingError, + AppLocalizations.of( + context, + )!.advertRemovingError, ); } announcerListNotifier @@ -158,9 +170,11 @@ class AddRemAnnouncerPage extends HookConsumerWidget { ) .toList(), ) - : const Center( + : Center( child: Text( - AdvertTextConstants.noMoreAnnouncer, + AppLocalizations.of( + context, + )!.advertNoMoreAnnouncer, ), ); }, diff --git a/lib/advert/ui/pages/main_page/main_page.dart b/lib/advert/ui/pages/main_page/main_page.dart index 0479c2bdc1..91221579f9 100644 --- a/lib/advert/ui/pages/main_page/main_page.dart +++ b/lib/advert/ui/pages/main_page/main_page.dart @@ -15,7 +15,7 @@ import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/column_refresher.dart'; import 'package:titan/tools/ui/widgets/admin_button.dart'; import 'package:qlevar_router/qlevar_router.dart'; -import 'package:titan/advert/tools/constants.dart'; +import 'package:titan/l10n/app_localizations.dart'; class AdvertMainPage extends HookConsumerWidget { const AdvertMainPage({super.key}); @@ -72,7 +72,7 @@ class AdvertMainPage extends HookConsumerWidget { AdvertRouter.addRemAnnouncer, ); }, - text: AdvertTextConstants.management, + text: AppLocalizations.of(context)!.advertManagement, ), ], ), diff --git a/lib/amap/tools/constants.dart b/lib/amap/tools/constants.dart index db49483f8c..65fc60d714 100644 --- a/lib/amap/tools/constants.dart +++ b/lib/amap/tools/constants.dart @@ -20,133 +20,3 @@ class AMAPColorConstants extends ColorConstants { static const Color redGradient1 = Color(0xFF9E131F); static const Color redGradient2 = Color(0xFF590512); } - -class AMAPTextConstants { - static const String accounts = "Comptes"; - static const String add = "Ajouter"; - static const String addDelivery = "Ajouter une livraison"; - static const String addedCommand = "Commande ajoutée"; - static const String addedOrder = "Commande ajoutée"; - static const String addedProduct = "Produit ajouté"; - static const String addedUser = "Utilisateur ajouté"; - static const String addProduct = "Ajouter un produit"; - static const String addUser = "Ajouter un utilisateur"; - static const String addingACommand = "Ajouter une commande"; - static const String addingCommand = "Ajouter la commande"; - static const String addingError = "Erreur lors de l'ajout"; - static const String addingProduct = "Ajouter un produit"; - static const String addOrder = "Ajouter une commande"; - static const String admin = "Admin"; - static const String alreadyExistCommand = - "Il existe déjà une commande à cette date"; - static const String amap = "Amap"; - static const String amount = "Solde"; - static const String archive = "Archiver"; - static const String archiveDelivery = "Archiver"; - static const String archivingDelivery = "Archivage de la livraison"; - static const String category = "Catégorie"; - static const String closeDelivery = "Verrouiller"; - static const String commandDate = "Date de la commande"; - static const String commandProducts = "Produits de la commande"; - static const String confirm = "Confirmer"; - static const String contact = "Contacts associatifs "; - static const String createCategory = "Créer une catégorie"; - static const String delete = "Supprimer"; - static const String deleteDelivery = "Supprimer la livraison ?"; - static const String deleteDeliveryDescription = - "Voulez-vous vraiment supprimer cette livraison ?"; - static const String deletedDelivery = "Livraison supprimée"; - static const String deletedOrder = "Commande supprimée"; - static const String deletedProduct = "Produit supprimé"; - static const String deleteProduct = "Supprimer le produit ?"; - static const String deleteProductDescription = - "Voulez-vous vraiment supprimer ce produit ?"; - static const String deleting = "Suppression"; - static const String deletingDelivery = "Supprimer la livraison ?"; - static const String deletingError = "Erreur lors de la suppression"; - static const String deletingOrder = "Supprimer la commande ?"; - static const String deletingProduct = "Supprimer le produit ?"; - static const String deliver = "Livraison teminée ?"; - static const String deliveries = "Livraisons"; - static const String deliveringDelivery = - "Toutes les commandes sont livrées ?"; - static const String delivery = "Livraison"; - static const String deliveryArchived = "Livraison archivée"; - static const String deliveryDate = "Date de livraison"; - static const String deliveryDelivered = "Livraison effectuée"; - static const String deliveryHistory = "Historique des livraisons"; - static const String deliveryList = "Liste des livraisons"; - static const String deliveryLocked = "Livraison verrouillée"; - static const String deliveryOn = "Livraison le"; - static const String deliveryOpened = "Livraison ouverte"; - static const String deliveryNotArchived = "Livraison non archivée"; - static const String deliveryNotLocked = "Livraison non verrouillée"; - static const String deliveryNotDelivered = "Livraison non effectuée"; - static const String deliveryNotOpened = "Livraison non ouverte"; - static const String editDelivery = "Modifier la livraison"; - static const String editedCommand = "Commande modifiée"; - static const String editingError = "Erreur lors de la modification"; - static const String editProduct = "Modifier le produit"; - static const String endingDelivery = "Fin de la livraison"; - static const String error = "Erreur"; - static const String errorLink = "Erreur lors de l'ouverture du lien"; - static const String errorLoadingUser = - "Erreur lors du chargement des utilisateurs"; - static const String evening = "Soir"; - static const String expectingNumber = "Veuillez entrer un nombre"; - static const String fillField = "Veuillez remplir ce champ"; - static const String handlingAccount = "Gérer les comptes"; - static const String loading = "Chargement..."; - static const String loadingError = "Erreur lors du chargement"; - static const String lock = "Verrouiller"; - static const String locked = "Verrouillée"; - static const String lockedDelivery = "Livraison verrouillée"; - static const String lockedOrder = "Commande verrouillée"; - static const String looking = "Rechercher"; - static const String lockingDelivery = "Verrouiller la livraison ?"; - static const String midDay = "Midi"; - static const String myOrders = "Mes commandes"; - static const String name = "Nom"; - static const String nextStep = "Étape suivante"; - static const String noProduct = "Pas de produit"; - static const String noCurrentOrder = "Pas de commande en cours"; - static const String noMoney = "Pas assez d'argent"; - static const String noOpennedDelivery = "Pas de livraison ouverte"; - static const String noOrder = "Pas de commande"; - static const String noSelectedDelivery = "Pas de livraison sélectionnée"; - static const String notEnoughMoney = "Pas assez d'argent"; - static const String notPlannedDelivery = "Pas de livraison planifiée"; - static const String oneOrder = "commande"; - static const String openDelivery = "Ouvrir"; - static const String opened = "Ouverte"; - static const String openningDelivery = "Ouvrir la livraison ?"; - static const String order = "Commander"; - static const String orders = "Commandes"; - static const String pickChooseCategory = - "Veuillez entrer une valeur ou choisir une catégorie existante"; - static const String pickDeliveryMoment = "Choisissez un moment de livraison"; - static const String presentation = "Présentation"; - static const String presentation1 = - "L'AMAP (association pour le maintien d'une agriculture paysanne) est un service proposé par l'association Planet&Co de l'ECL. Vous pouvez ainsi recevoir des produits (paniers de fruits et légumes, jus, confitures...) directement sur le campus !\n\nLes commandes doivent être passées avant le vendredi 21h et sont livrées sur le campus le mardi de 13h à 13h45 (ou de 18h15 à 18h30 si vous ne pouvez pas passer le midi) dans le hall du M16.\n\nVous ne pouvez commander que si votre solde le permet. Vous pouvez recharger votre solde via la collecte Lydia ou bien avec un chèque que vous pouvez nous transmettre lors des permanences.\n\nLien vers la collecte Lydia pour le rechargement : "; - static const String presentation2 = - "\n\nN'hésitez pas à nous contacter en cas de problème !"; - static const String price = "Prix"; - static const String product = "produit"; - static const String products = "Produits"; - static const String productInDelivery = - "Produit dans une livraison non terminée"; - static const String quantity = "Quantité"; - static const String requiredDate = "La date est requise"; - static const String seeMore = "Voir plus"; - static const String the = "Le"; - static const String unlock = "Dévérouiller"; - static const String unlockedDelivery = "Livraison dévérouillée"; - static const String unlockingDelivery = "Dévérouiller la livraison ?"; - static const String update = "Modifier"; - static const String updatedAmount = "Solde modifié"; - static const String updatedOrder = "Commande modifiée"; - static const String updatedProduct = "Produit modifié"; - static const String updatingError = "Echec de la modification"; - static const String usersNotFound = "Aucun utilisateur trouvé"; - static const String waiting = "En attente"; -} diff --git a/lib/amap/tools/functions.dart b/lib/amap/tools/functions.dart index a3b5eba7dc..11088e67d8 100644 --- a/lib/amap/tools/functions.dart +++ b/lib/amap/tools/functions.dart @@ -1,14 +1,15 @@ +import 'package:flutter/widgets.dart'; import 'package:titan/amap/class/delivery.dart'; import 'package:titan/amap/class/order.dart'; -import 'package:titan/amap/tools/constants.dart'; +import 'package:titan/l10n/app_localizations.dart'; // Slots in Titan UI must changed based on language -String uiCollectionSlotToString(CollectionSlot slot) { +String uiCollectionSlotToString(CollectionSlot slot, BuildContext context) { switch (slot) { case CollectionSlot.midDay: - return AMAPTextConstants.midDay; + return AppLocalizations.of(context)!.amapMidDay; case CollectionSlot.evening: - return AMAPTextConstants.evening; + return AppLocalizations.of(context)!.amapEvening; } } diff --git a/lib/amap/ui/components/order_ui.dart b/lib/amap/ui/components/order_ui.dart index 6c92e6bbd3..1a67e295ce 100644 --- a/lib/amap/ui/components/order_ui.dart +++ b/lib/amap/ui/components/order_ui.dart @@ -13,6 +13,7 @@ import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; +import 'package:titan/l10n/app_localizations.dart'; class OrderUI extends HookConsumerWidget { final Order order; @@ -53,7 +54,7 @@ class OrderUI extends HookConsumerWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - '${AMAPTextConstants.the} ${processDate(order.deliveryDate)}', + '${AppLocalizations.of(context)!.amapThe} ${processDate(order.deliveryDate)}', style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, @@ -78,7 +79,7 @@ class OrderUI extends HookConsumerWidget { Row( children: [ Text( - "${order.products.length} ${AMAPTextConstants.product}${order.products.length != 1 ? "s" : ""}", + "${order.products.length} ${AppLocalizations.of(context)!.amapProduct}${order.products.length != 1 ? "s" : ""}", style: const TextStyle( fontSize: 17, fontWeight: FontWeight.w700, @@ -98,7 +99,7 @@ class OrderUI extends HookConsumerWidget { ), const SizedBox(height: 3), Text( - uiCollectionSlotToString(order.collectionSlot), + uiCollectionSlotToString(order.collectionSlot, context), style: const TextStyle( fontSize: 17, fontWeight: FontWeight.w700, @@ -132,8 +133,10 @@ class OrderUI extends HookConsumerWidget { await showDialog( context: context, builder: ((context) => CustomDialogBox( - title: AMAPTextConstants.delete, - descriptions: AMAPTextConstants.deletingOrder, + title: AppLocalizations.of(context)!.amapDelete, + descriptions: AppLocalizations.of( + context, + )!.amapDeletingOrder, onYes: () async { await tokenExpireWrapper(ref, () async { orderListNotifier.deleteOrder(order).then(( @@ -143,12 +146,16 @@ class OrderUI extends HookConsumerWidget { balanceNotifier.updateCash(order.amount); displayToastWithContext( TypeMsg.msg, - AMAPTextConstants.deletedOrder, + AppLocalizations.of( + context, + )!.amapDeletedOrder, ); } else { displayToastWithContext( TypeMsg.error, - AMAPTextConstants.deletingError, + AppLocalizations.of( + context, + )!.amapDeletingError, ); } }); @@ -171,9 +178,9 @@ class OrderUI extends HookConsumerWidget { ), ], ) - : const Text( - AMAPTextConstants.locked, - style: TextStyle( + : Text( + AppLocalizations.of(context)!.amapLocked, + style: const TextStyle( fontSize: 17, fontWeight: FontWeight.w700, color: AMAPColorConstants.textDark, diff --git a/lib/amap/ui/components/product_ui.dart b/lib/amap/ui/components/product_ui.dart index 3c3ef3c323..68d2901568 100644 --- a/lib/amap/ui/components/product_ui.dart +++ b/lib/amap/ui/components/product_ui.dart @@ -6,6 +6,7 @@ import 'package:titan/amap/tools/constants.dart'; import 'package:titan/tools/ui/layouts/card_button.dart'; import 'package:titan/tools/ui/layouts/card_layout.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; +import 'package:titan/l10n/app_localizations.dart'; class ProductCard extends StatelessWidget { final Product product; @@ -103,7 +104,7 @@ class ProductCard extends StatelessWidget { : Container( margin: const EdgeInsets.only(bottom: 5), child: Text( - "${AMAPTextConstants.quantity} : ${product.quantity}", + "${AppLocalizations.of(context)!.amapQuantity} : ${product.quantity}", style: const TextStyle( fontSize: 15, fontWeight: FontWeight.bold, diff --git a/lib/amap/ui/pages/admin_page/account_handler.dart b/lib/amap/ui/pages/admin_page/account_handler.dart index 3f20ade3d5..3a4b9dff46 100644 --- a/lib/amap/ui/pages/admin_page/account_handler.dart +++ b/lib/amap/ui/pages/admin_page/account_handler.dart @@ -11,6 +11,7 @@ import 'package:titan/tools/ui/layouts/card_layout.dart'; import 'package:titan/tools/ui/layouts/horizontal_list_view.dart'; import 'package:titan/tools/ui/widgets/styled_search_bar.dart'; import 'package:titan/user/providers/user_list_provider.dart'; +import 'package:titan/l10n/app_localizations.dart'; class AccountHandler extends HookConsumerWidget { const AccountHandler({super.key}); @@ -28,7 +29,7 @@ class AccountHandler extends HookConsumerWidget { return Column( children: [ StyledSearchBar( - label: AMAPTextConstants.accounts, + label: AppLocalizations.of(context)!.amapAccounts, color: AMAPColorConstants.textDark, onChanged: (value) async { if (!searchingAmapUser) { diff --git a/lib/amap/ui/pages/admin_page/delivery_handler.dart b/lib/amap/ui/pages/admin_page/delivery_handler.dart index b500fabeba..60be021992 100644 --- a/lib/amap/ui/pages/admin_page/delivery_handler.dart +++ b/lib/amap/ui/pages/admin_page/delivery_handler.dart @@ -13,6 +13,7 @@ import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/card_layout.dart'; import 'package:titan/tools/ui/layouts/horizontal_list_view.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class DeliveryHandler extends HookConsumerWidget { const DeliveryHandler({super.key}); @@ -24,9 +25,9 @@ class DeliveryHandler extends HookConsumerWidget { final selectedNotifier = ref.watch(selectedListProvider.notifier); return Column( children: [ - const AlignLeftText( - AMAPTextConstants.deliveries, - padding: EdgeInsets.symmetric(horizontal: 30), + AlignLeftText( + AppLocalizations.of(context)!.amapDeliveries, + padding: const EdgeInsets.symmetric(horizontal: 30), color: AMAPColorConstants.textDark, ), const SizedBox(height: 10), diff --git a/lib/amap/ui/pages/admin_page/delivery_ui.dart b/lib/amap/ui/pages/admin_page/delivery_ui.dart index 809115ea2b..2d10a81261 100644 --- a/lib/amap/ui/pages/admin_page/delivery_ui.dart +++ b/lib/amap/ui/pages/admin_page/delivery_ui.dart @@ -19,6 +19,7 @@ import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class DeliveryUi extends HookConsumerWidget { final Delivery delivery; @@ -67,7 +68,7 @@ class DeliveryUi extends HookConsumerWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - '${AMAPTextConstants.the} ${processDate(delivery.deliveryDate)}', + '${AppLocalizations.of(context)!.amapThe} ${processDate(delivery.deliveryDate)}', style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, @@ -102,8 +103,8 @@ class DeliveryUi extends HookConsumerWidget { dataBuilder: (context, orders) { return Text( orders.isEmpty - ? AMAPTextConstants.noCurrentOrder - : '${orders.length} ${AMAPTextConstants.oneOrder}${orders.length != 1 ? "s" : ""}', + ? AppLocalizations.of(context)!.amapNoCurrentOrder + : '${orders.length} ${AppLocalizations.of(context)!.amapOneOrder}${orders.length != 1 ? "s" : ""}', style: const TextStyle( fontSize: 15, fontWeight: FontWeight.bold, @@ -113,7 +114,7 @@ class DeliveryUi extends HookConsumerWidget { }, ), Text( - "${delivery.products.length} ${AMAPTextConstants.product}${delivery.products.length != 1 ? "s" : ""}", + "${delivery.products.length} ${AppLocalizations.of(context)!.amapProduct}${delivery.products.length != 1 ? "s" : ""}", style: const TextStyle( fontSize: 15, fontWeight: FontWeight.w700, @@ -177,9 +178,12 @@ class DeliveryUi extends HookConsumerWidget { await showDialog( context: context, builder: ((context) => CustomDialogBox( - title: AMAPTextConstants.deleteDelivery, - descriptions: - AMAPTextConstants.deleteDeliveryDescription, + title: AppLocalizations.of( + context, + )!.amapDeleteDelivery, + descriptions: AppLocalizations.of( + context, + )!.amapDeleteDeliveryDescription, onYes: () async { await tokenExpireWrapper(ref, () async { deliveryListNotifier @@ -188,12 +192,16 @@ class DeliveryUi extends HookConsumerWidget { if (value) { displayVoteWithContext( TypeMsg.msg, - AMAPTextConstants.deletedDelivery, + AppLocalizations.of( + context, + )!.amapDeletedDelivery, ); } else { displayVoteWithContext( TypeMsg.error, - AMAPTextConstants.deletingError, + AppLocalizations.of( + context, + )!.amapDeletingError, ); } }); @@ -221,19 +229,23 @@ class DeliveryUi extends HookConsumerWidget { context: context, builder: ((context) => CustomDialogBox( title: delivery.status == DeliveryStatus.creation - ? AMAPTextConstants.openDelivery + ? AppLocalizations.of(context)!.amapOpenDelivery : delivery.status == DeliveryStatus.available - ? AMAPTextConstants.lock + ? AppLocalizations.of(context)!.amapLock : delivery.status == DeliveryStatus.locked - ? AMAPTextConstants.deliver - : AMAPTextConstants.archive, + ? AppLocalizations.of(context)!.amapDeliver + : AppLocalizations.of(context)!.amapArchive, descriptions: delivery.status == DeliveryStatus.creation - ? AMAPTextConstants.openningDelivery + ? AppLocalizations.of(context)!.amapOpenningDelivery : delivery.status == DeliveryStatus.available - ? AMAPTextConstants.lockingDelivery + ? AppLocalizations.of(context)!.amapLockingDelivery : delivery.status == DeliveryStatus.locked - ? AMAPTextConstants.deliveringDelivery - : AMAPTextConstants.archivingDelivery, + ? AppLocalizations.of( + context, + )!.amapDeliveringDelivery + : AppLocalizations.of( + context, + )!.amapArchivingDelivery, onYes: () async { await tokenExpireWrapper(ref, () async { switch (delivery.status) { @@ -243,12 +255,16 @@ class DeliveryUi extends HookConsumerWidget { if (value) { displayVoteWithContext( TypeMsg.msg, - AMAPTextConstants.deliveryOpened, + AppLocalizations.of( + context, + )!.amapDeliveryOpened, ); } else { displayVoteWithContext( TypeMsg.error, - AMAPTextConstants.deliveryNotOpened, + AppLocalizations.of( + context, + )!.amapDeliveryNotOpened, ); } break; @@ -258,12 +274,16 @@ class DeliveryUi extends HookConsumerWidget { if (value) { displayVoteWithContext( TypeMsg.msg, - AMAPTextConstants.deliveryLocked, + AppLocalizations.of( + context, + )!.amapDeliveryLocked, ); } else { displayVoteWithContext( TypeMsg.error, - AMAPTextConstants.deliveryNotLocked, + AppLocalizations.of( + context, + )!.amapDeliveryNotLocked, ); } break; @@ -273,12 +293,16 @@ class DeliveryUi extends HookConsumerWidget { if (value) { displayVoteWithContext( TypeMsg.msg, - AMAPTextConstants.deliveryDelivered, + AppLocalizations.of( + context, + )!.amapDeliveryDelivered, ); } else { displayVoteWithContext( TypeMsg.error, - AMAPTextConstants.deliveryNotDelivered, + AppLocalizations.of( + context, + )!.amapDeliveryNotDelivered, ); } break; @@ -288,12 +312,16 @@ class DeliveryUi extends HookConsumerWidget { if (value) { displayVoteWithContext( TypeMsg.msg, - AMAPTextConstants.deliveryArchived, + AppLocalizations.of( + context, + )!.amapDeliveryArchived, ); } else { displayVoteWithContext( TypeMsg.error, - AMAPTextConstants.deliveryNotArchived, + AppLocalizations.of( + context, + )!.amapDeliveryNotArchived, ); } break; @@ -345,12 +373,14 @@ class DeliveryUi extends HookConsumerWidget { padding: const EdgeInsets.only(bottom: 2), child: Text( delivery.status == DeliveryStatus.creation - ? AMAPTextConstants.openDelivery + ? AppLocalizations.of(context)!.amapOpenDelivery : delivery.status == DeliveryStatus.available - ? AMAPTextConstants.closeDelivery + ? AppLocalizations.of(context)!.amapCloseDelivery : delivery.status == DeliveryStatus.locked - ? AMAPTextConstants.endingDelivery - : AMAPTextConstants.archiveDelivery, + ? AppLocalizations.of(context)!.amapEndingDelivery + : AppLocalizations.of( + context, + )!.amapArchiveDelivery, style: const TextStyle( color: Colors.white, fontSize: 20, diff --git a/lib/amap/ui/pages/admin_page/product_handler.dart b/lib/amap/ui/pages/admin_page/product_handler.dart index 57abe7c04f..ea65fec74d 100644 --- a/lib/amap/ui/pages/admin_page/product_handler.dart +++ b/lib/amap/ui/pages/admin_page/product_handler.dart @@ -15,6 +15,7 @@ import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/layouts/horizontal_list_view.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class ProductHandler extends HookConsumerWidget { const ProductHandler({super.key}); @@ -37,9 +38,9 @@ class ProductHandler extends HookConsumerWidget { return Column( children: [ - const AlignLeftText( - padding: EdgeInsets.symmetric(horizontal: 30), - AMAPTextConstants.products, + AlignLeftText( + padding: const EdgeInsets.symmetric(horizontal: 30), + AppLocalizations.of(context)!.amapProducts, color: AMAPColorConstants.textDark, ), const SizedBox(height: 10), @@ -74,7 +75,9 @@ class ProductHandler extends HookConsumerWidget { ), ), products.isEmpty - ? const Center(child: Text(AMAPTextConstants.noProduct)) + ? Center( + child: Text(AppLocalizations.of(context)!.amapNoProduct), + ) : Row( children: products .map( @@ -84,9 +87,12 @@ class ProductHandler extends HookConsumerWidget { await showDialog( context: context, builder: (context) => CustomDialogBox( - title: AMAPTextConstants.deleteProduct, - descriptions: AMAPTextConstants - .deleteProductDescription, + title: AppLocalizations.of( + context, + )!.amapDeleteProduct, + descriptions: AppLocalizations.of( + context, + )!.amapDeleteProductDescription, onYes: () { tokenExpireWrapper(ref, () async { final value = await productsNotifier @@ -94,12 +100,16 @@ class ProductHandler extends HookConsumerWidget { if (value) { displayToastWithContext( TypeMsg.msg, - AMAPTextConstants.deletedProduct, + AppLocalizations.of( + context, + )!.amapDeletedProduct, ); } else { displayToastWithContext( TypeMsg.error, - AMAPTextConstants.productInDelivery, + AppLocalizations.of( + context, + )!.amapProductInDelivery, ); } }); diff --git a/lib/amap/ui/pages/admin_page/user_cash_ui.dart b/lib/amap/ui/pages/admin_page/user_cash_ui.dart index 898f85f058..d8a5135dc7 100644 --- a/lib/amap/ui/pages/admin_page/user_cash_ui.dart +++ b/lib/amap/ui/pages/admin_page/user_cash_ui.dart @@ -13,6 +13,7 @@ import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; import 'package:titan/tools/ui/widgets/text_entry.dart'; +import 'package:titan/l10n/app_localizations.dart'; class UserCashUi extends HookConsumerWidget { final Cash cash; @@ -168,12 +169,16 @@ class UserCashUi extends HookConsumerWidget { toggle(); displayVoteWithContext( TypeMsg.msg, - AMAPTextConstants.updatedAmount, + AppLocalizations.of( + context, + )!.amapUpdatedAmount, ); } else { displayVoteWithContext( TypeMsg.error, - AMAPTextConstants.updatingError, + AppLocalizations.of( + context, + )!.amapUpdatingError, ); } }); diff --git a/lib/amap/ui/pages/delivery_pages/add_edit_delivery_cmd_page.dart b/lib/amap/ui/pages/delivery_pages/add_edit_delivery_cmd_page.dart index a7a01c23ba..8e57441b6b 100644 --- a/lib/amap/ui/pages/delivery_pages/add_edit_delivery_cmd_page.dart +++ b/lib/amap/ui/pages/delivery_pages/add_edit_delivery_cmd_page.dart @@ -19,6 +19,7 @@ import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/widgets/date_entry.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class AddEditDeliveryPage extends HookConsumerWidget { const AddEditDeliveryPage({super.key}); @@ -55,23 +56,23 @@ class AddEditDeliveryPage extends HookConsumerWidget { child: Column( children: [ const SizedBox(height: 20), - const AlignLeftText( - AMAPTextConstants.addDelivery, + AlignLeftText( + AppLocalizations.of(context)!.amapAddDelivery, color: AMAPColorConstants.green2, ), Container( margin: const EdgeInsets.symmetric(vertical: 30), child: DateEntry( onTap: () => getOnlyDayDate(context, dateController), - label: AMAPTextConstants.commandDate, + label: AppLocalizations.of(context)!.amapCommandDate, controller: dateController, enabledColor: AMAPColorConstants.enabled, color: AMAPColorConstants.greenGradient2, ), ), const SizedBox(height: 15), - const AlignLeftText( - AMAPTextConstants.commandProducts, + AlignLeftText( + AppLocalizations.of(context)!.amapCommandProducts, fontSize: 25, ), const SizedBox(height: 35), @@ -158,7 +159,9 @@ class AddEditDeliveryPage extends HookConsumerWidget { if (isEdit) { displayToastWithContext( TypeMsg.msg, - AMAPTextConstants.editedCommand, + AppLocalizations.of( + context, + )!.amapEditedCommand, ); } else { final deliveryOrdersNotifier = ref.watch( @@ -174,19 +177,25 @@ class AddEditDeliveryPage extends HookConsumerWidget { }); displayToastWithContext( TypeMsg.msg, - AMAPTextConstants.addedCommand, + AppLocalizations.of( + context, + )!.amapAddedCommand, ); } } else { if (isEdit) { displayToastWithContext( TypeMsg.error, - AMAPTextConstants.editingError, + AppLocalizations.of( + context, + )!.amapEditingError, ); } else { displayToastWithContext( TypeMsg.error, - AMAPTextConstants.alreadyExistCommand, + AppLocalizations.of( + context, + )!.amapAlreadyExistCommand, ); } } @@ -195,14 +204,18 @@ class AddEditDeliveryPage extends HookConsumerWidget { displayToast( context, TypeMsg.error, - AMAPTextConstants.addingError, + AppLocalizations.of(context)!.amapAddingError, ); } }, child: Text( isEdit - ? AMAPTextConstants.editDelivery - : AMAPTextConstants.addDelivery, + ? AppLocalizations.of( + context, + )!.amapEditDelivery + : AppLocalizations.of( + context, + )!.amapAddDelivery, style: TextStyle( fontSize: 20, fontWeight: FontWeight.w700, diff --git a/lib/amap/ui/pages/detail_delivery_page/detail_page.dart b/lib/amap/ui/pages/detail_delivery_page/detail_page.dart index b84ed8b469..31337385fe 100644 --- a/lib/amap/ui/pages/detail_delivery_page/detail_page.dart +++ b/lib/amap/ui/pages/detail_delivery_page/detail_page.dart @@ -17,6 +17,7 @@ import 'package:titan/tools/ui/widgets/align_left_text.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/widgets/loader.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; +import 'package:titan/l10n/app_localizations.dart'; class DetailDeliveryPage extends HookConsumerWidget { const DetailDeliveryPage({super.key}); @@ -47,15 +48,15 @@ class DetailDeliveryPage extends HookConsumerWidget { child: Column( children: [ Text( - "${AMAPTextConstants.deliveryDate} : ${processDate(delivery.deliveryDate)}", + "${AppLocalizations.of(context)!.amapDeliveryDate} : ${processDate(delivery.deliveryDate)}", style: const TextStyle( fontSize: 20, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 20), - const AlignLeftText( - "${AMAPTextConstants.products} :", + AlignLeftText( + "${AppLocalizations.of(context)!.amapProducts} :", color: AMAPColorConstants.textDark, ), ], @@ -111,9 +112,9 @@ class DetailDeliveryPage extends HookConsumerWidget { ); }).values, const SizedBox(height: 20), - const AlignLeftText( - "${AMAPTextConstants.orders} :", - padding: EdgeInsets.only(left: 30), + AlignLeftText( + "${AppLocalizations.of(context)!.amapOrders} :", + padding: const EdgeInsets.only(left: 30), color: AMAPColorConstants.textDark, ), const SizedBox(height: 30), @@ -128,8 +129,10 @@ class DetailDeliveryPage extends HookConsumerWidget { if (data.isEmpty) { return Container( margin: const EdgeInsets.only(bottom: 50), - child: const Center( - child: Text(AMAPTextConstants.noOrder), + child: Center( + child: Text( + AppLocalizations.of(context)!.amapNoOrder, + ), ), ); } diff --git a/lib/amap/ui/pages/detail_delivery_page/order_detail_ui.dart b/lib/amap/ui/pages/detail_delivery_page/order_detail_ui.dart index 73963f39f3..7da24dda40 100644 --- a/lib/amap/ui/pages/detail_delivery_page/order_detail_ui.dart +++ b/lib/amap/ui/pages/detail_delivery_page/order_detail_ui.dart @@ -14,6 +14,7 @@ import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; +import 'package:titan/l10n/app_localizations.dart'; class DetailOrderUI extends HookConsumerWidget { final Order order; @@ -105,7 +106,7 @@ class DetailOrderUI extends HookConsumerWidget { Row( children: [ Text( - "${order.products.fold(0, (value, product) => value + product.quantity)} ${AMAPTextConstants.product}${order.products.fold(0, (value, product) => value + product.quantity) != 1 ? "s" : ""}", + "${order.products.fold(0, (value, product) => value + product.quantity)} ${AppLocalizations.of(context)!.amapProduct}${order.products.fold(0, (value, product) => value + product.quantity) != 1 ? "s" : ""}", style: const TextStyle( fontSize: 17, fontWeight: FontWeight.w700, @@ -127,7 +128,7 @@ class DetailOrderUI extends HookConsumerWidget { Row( children: [ Text( - "${AMAPTextConstants.amount} : ${userCash.balance.toStringAsFixed(2)}€", + "${AppLocalizations.of(context)!.amapAmount} : ${userCash.balance.toStringAsFixed(2)}€", style: const TextStyle( fontSize: 17, fontWeight: FontWeight.w700, @@ -140,8 +141,10 @@ class DetailOrderUI extends HookConsumerWidget { await showDialog( context: context, builder: ((context) => CustomDialogBox( - title: AMAPTextConstants.delete, - descriptions: AMAPTextConstants.deletingOrder, + title: AppLocalizations.of(context)!.amapDelete, + descriptions: AppLocalizations.of( + context, + )!.amapDeletingOrder, onYes: () async { await tokenExpireWrapper(ref, () async { final index = orderList.maybeWhen( @@ -167,12 +170,12 @@ class DetailOrderUI extends HookConsumerWidget { ); displayToastWithContext( TypeMsg.msg, - AMAPTextConstants.deletedOrder, + AppLocalizations.of(context)!.amapDeletedOrder, ); } else { displayToastWithContext( TypeMsg.error, - AMAPTextConstants.deletingError, + AppLocalizations.of(context)!.amapDeletingError, ); } }); diff --git a/lib/amap/ui/pages/detail_delivery_page/product_detail_ui.dart b/lib/amap/ui/pages/detail_delivery_page/product_detail_ui.dart index 08db18c5d5..6376a197b3 100644 --- a/lib/amap/ui/pages/detail_delivery_page/product_detail_ui.dart +++ b/lib/amap/ui/pages/detail_delivery_page/product_detail_ui.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:titan/amap/class/product.dart'; import 'package:titan/amap/tools/constants.dart'; import 'package:titan/tools/ui/layouts/card_layout.dart'; +import 'package:titan/l10n/app_localizations.dart'; class ProductDetailCard extends StatelessWidget { final Product product; @@ -41,7 +42,7 @@ class ProductDetailCard extends StatelessWidget { ), const SizedBox(height: 4), AutoSizeText( - "${AMAPTextConstants.quantity} : $quantity", + "${AppLocalizations.of(context)!.amapQuantity} : $quantity", maxLines: 2, overflow: TextOverflow.ellipsis, style: const TextStyle( diff --git a/lib/amap/ui/pages/detail_page/detail_page.dart b/lib/amap/ui/pages/detail_page/detail_page.dart index cdb4009ade..3e204b7226 100644 --- a/lib/amap/ui/pages/detail_page/detail_page.dart +++ b/lib/amap/ui/pages/detail_page/detail_page.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/amap/providers/order_provider.dart'; -import 'package:titan/amap/tools/constants.dart'; import 'package:titan/amap/ui/amap.dart'; import 'package:titan/amap/ui/components/order_ui.dart'; import 'package:titan/amap/ui/components/product_ui.dart'; import 'package:titan/tools/ui/widgets/align_left_text.dart'; +import 'package:titan/l10n/app_localizations.dart'; class DetailPage extends HookConsumerWidget { const DetailPage({super.key}); @@ -39,9 +39,9 @@ class DetailPage extends HookConsumerWidget { child: Column( children: [ const SizedBox(height: 50), - const AlignLeftText( - padding: EdgeInsets.symmetric(horizontal: 20), - AMAPTextConstants.products, + AlignLeftText( + padding: const EdgeInsets.symmetric(horizontal: 20), + AppLocalizations.of(context)!.amapProducts, fontSize: 25, ), const SizedBox(height: 10), diff --git a/lib/amap/ui/pages/list_products_page/category_page.dart b/lib/amap/ui/pages/list_products_page/category_page.dart index a390f1472e..9afb0a22cf 100644 --- a/lib/amap/ui/pages/list_products_page/category_page.dart +++ b/lib/amap/ui/pages/list_products_page/category_page.dart @@ -10,6 +10,7 @@ import 'package:titan/amap/tools/constants.dart'; import 'package:titan/amap/ui/pages/list_products_page/product_ui_list.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/ui/widgets/align_left_text.dart'; +import 'package:titan/l10n/app_localizations.dart'; class CategoryPage extends HookConsumerWidget { final String category; @@ -160,7 +161,7 @@ class CategoryPage extends HookConsumerWidget { color: AMAPColorConstants.background, ), Text( - AMAPTextConstants.seeMore, + AppLocalizations.of(context)!.amapSeeMore, style: TextStyle( fontSize: 18, color: AMAPColorConstants.background, diff --git a/lib/amap/ui/pages/list_products_page/list_products.dart b/lib/amap/ui/pages/list_products_page/list_products.dart index 4c8e9537a8..c0a27f9be7 100644 --- a/lib/amap/ui/pages/list_products_page/list_products.dart +++ b/lib/amap/ui/pages/list_products_page/list_products.dart @@ -6,7 +6,6 @@ import 'package:titan/amap/providers/scroll_provider.dart'; import 'package:titan/amap/providers/sorted_delivery_product.dart'; import 'package:titan/amap/providers/page_controller_provider.dart'; import 'package:titan/amap/providers/scroll_controller_provider.dart'; -import 'package:titan/amap/tools/constants.dart'; import 'package:titan/amap/ui/pages/list_products_page/category_page.dart'; import 'package:titan/amap/ui/pages/list_products_page/web_page_navigation_button.dart'; import 'package:titan/navigation/providers/is_web_format_provider.dart'; @@ -48,10 +47,10 @@ class ListProducts extends HookConsumerWidget { physics: const BouncingScrollPhysics(), children: sortedDeliveryProductsList.isEmpty ? [ - const Center( + Center( child: Text( - AMAPTextConstants.noProduct, - style: TextStyle( + AppLocalizations.of(context)!.amapNoProduct, + style: const TextStyle( fontSize: 20, fontWeight: FontWeight.bold, ), diff --git a/lib/amap/ui/pages/list_products_page/product_choice_button.dart b/lib/amap/ui/pages/list_products_page/product_choice_button.dart index 789ba76192..63ddc512a3 100644 --- a/lib/amap/ui/pages/list_products_page/product_choice_button.dart +++ b/lib/amap/ui/pages/list_products_page/product_choice_button.dart @@ -13,6 +13,7 @@ import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; import 'package:titan/user/providers/user_provider.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class ProductChoiceButton extends HookConsumerWidget { const ProductChoiceButton({super.key}); @@ -69,7 +70,7 @@ class ProductChoiceButton extends HookConsumerWidget { displayToast( context, TypeMsg.error, - AMAPTextConstants.noProduct, + AppLocalizations.of(context)!.amapNoProduct, ); } else { Order newOrder = order.copyWith( @@ -89,24 +90,24 @@ class ProductChoiceButton extends HookConsumerWidget { if (isEdit) { displayToastWithContext( TypeMsg.msg, - AMAPTextConstants.updatedOrder, + AppLocalizations.of(context)!.amapUpdatedOrder, ); } else { displayToastWithContext( TypeMsg.msg, - AMAPTextConstants.addedOrder, + AppLocalizations.of(context)!.amapAddedOrder, ); } } else { if (isEdit) { displayToastWithContext( TypeMsg.error, - AMAPTextConstants.updatingError, + AppLocalizations.of(context)!.amapUpdatingError, ); } else { displayToastWithContext( TypeMsg.error, - AMAPTextConstants.addingError, + AppLocalizations.of(context)!.amapAddingError, ); } } @@ -114,7 +115,7 @@ class ProductChoiceButton extends HookConsumerWidget { } }, child: Text( - "${AMAPTextConstants.confirm} (${order.amount.toStringAsFixed(2)}€)", + "${AppLocalizations.of(context)!.amapConfirm} (${order.amount.toStringAsFixed(2)}€)", style: TextStyle( fontSize: 20, fontWeight: FontWeight.w700, @@ -159,8 +160,10 @@ class ProductChoiceButton extends HookConsumerWidget { showDialog( context: context, builder: (BuildContext context) => CustomDialogBox( - descriptions: AMAPTextConstants.deletingOrder, - title: AMAPTextConstants.deleting, + descriptions: AppLocalizations.of( + context, + )!.amapDeletingOrder, + title: AppLocalizations.of(context)!.amapDeleting, onYes: () { orderNotifier.setOrder(Order.empty()); QR.back(); diff --git a/lib/amap/ui/pages/main_page/collection_slot_selector.dart b/lib/amap/ui/pages/main_page/collection_slot_selector.dart index 9de132519b..3a541075da 100644 --- a/lib/amap/ui/pages/main_page/collection_slot_selector.dart +++ b/lib/amap/ui/pages/main_page/collection_slot_selector.dart @@ -38,7 +38,7 @@ class CollectionSlotSelector extends HookConsumerWidget { ), child: Center( child: Text( - capitalize(uiCollectionSlotToString(collectionSlot)), + capitalize(uiCollectionSlotToString(collectionSlot, context)), style: TextStyle( fontSize: 25, fontWeight: FontWeight.bold, diff --git a/lib/amap/ui/pages/main_page/delivery_section.dart b/lib/amap/ui/pages/main_page/delivery_section.dart index 65aba3ecfd..0921d0010e 100644 --- a/lib/amap/ui/pages/main_page/delivery_section.dart +++ b/lib/amap/ui/pages/main_page/delivery_section.dart @@ -7,6 +7,7 @@ import 'package:titan/amap/tools/constants.dart'; import 'package:titan/amap/ui/pages/main_page/delivery_ui.dart'; import 'package:titan/tools/ui/widgets/align_left_text.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; +import 'package:titan/l10n/app_localizations.dart'; class DeliverySection extends HookConsumerWidget { final bool showSelected; @@ -30,7 +31,7 @@ class DeliverySection extends HookConsumerWidget { return Column( children: [ AlignLeftText( - AMAPTextConstants.deliveries, + AppLocalizations.of(context)!.amapDeliveries, padding: const EdgeInsets.symmetric(horizontal: 30), color: showSelected ? Colors.white : AMAPColorConstants.textDark, ), @@ -38,8 +39,10 @@ class DeliverySection extends HookConsumerWidget { value: deliveries, builder: (context, data) { if (availableDeliveries.isEmpty) { - return const Center( - child: Text(AMAPTextConstants.notPlannedDelivery), + return Center( + child: Text( + AppLocalizations.of(context)!.amapNotPlannedDelivery, + ), ); } return SingleChildScrollView( diff --git a/lib/amap/ui/pages/main_page/delivery_ui.dart b/lib/amap/ui/pages/main_page/delivery_ui.dart index 14d82a026e..5d0c3ea425 100644 --- a/lib/amap/ui/pages/main_page/delivery_ui.dart +++ b/lib/amap/ui/pages/main_page/delivery_ui.dart @@ -4,6 +4,7 @@ import 'package:titan/amap/class/delivery.dart'; import 'package:titan/amap/providers/delivery_provider.dart'; import 'package:titan/amap/tools/constants.dart'; import 'package:titan/tools/functions.dart'; +import 'package:titan/l10n/app_localizations.dart'; class DeliveryUi extends HookConsumerWidget { final Delivery delivery; @@ -55,7 +56,7 @@ class DeliveryUi extends HookConsumerWidget { children: [ const SizedBox(width: 10), Text( - '${AMAPTextConstants.the} ${processDate(delivery.deliveryDate)}', + '${AppLocalizations.of(context)!.amapThe} ${processDate(delivery.deliveryDate)}', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, @@ -66,7 +67,7 @@ class DeliveryUi extends HookConsumerWidget { ), const Spacer(), Text( - "${delivery.products.length} ${AMAPTextConstants.product}${delivery.products.length != 1 ? "s" : ""}", + "${delivery.products.length} ${AppLocalizations.of(context)!.amapProduct}${delivery.products.length != 1 ? "s" : ""}", style: TextStyle( fontSize: 18, fontWeight: FontWeight.w700, diff --git a/lib/amap/ui/pages/main_page/main_page.dart b/lib/amap/ui/pages/main_page/main_page.dart index 1d5c673abf..91eb61c468 100644 --- a/lib/amap/ui/pages/main_page/main_page.dart +++ b/lib/amap/ui/pages/main_page/main_page.dart @@ -26,6 +26,7 @@ import 'package:titan/tools/ui/layouts/refresher.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/user/providers/user_provider.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class AmapMainPage extends HookConsumerWidget { const AmapMainPage({super.key}); @@ -83,7 +84,7 @@ class AmapMainPage extends HookConsumerWidget { child: AsyncChild( value: balance, builder: (context, s) => Text( - "${AMAPTextConstants.amount} : ${s.balance.toStringAsFixed(2)}€", + "${AppLocalizations.of(context)!.amapAmount} : ${s.balance.toStringAsFixed(2)}€", style: const TextStyle( fontSize: 20, fontWeight: FontWeight.bold, @@ -181,8 +182,8 @@ class AmapMainPage extends HookConsumerWidget { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - const AlignLeftText( - AMAPTextConstants.addOrder, + AlignLeftText( + AppLocalizations.of(context)!.amapAddOrder, color: Colors.white, ), IconButton( @@ -234,7 +235,9 @@ class AmapMainPage extends HookConsumerWidget { } else { displayToastWithoutContext( TypeMsg.error, - AMAPTextConstants.noSelectedDelivery, + AppLocalizations.of( + context, + )!.amapNoSelectedDelivery, ); } }, @@ -267,10 +270,10 @@ class AmapMainPage extends HookConsumerWidget { child: Container( padding: const EdgeInsets.only(bottom: 5), width: double.infinity, - child: const Center( + child: Center( child: Text( - AMAPTextConstants.nextStep, - style: TextStyle( + AppLocalizations.of(context)!.amapNextStep, + style: const TextStyle( fontSize: 25, fontWeight: FontWeight.w900, color: Colors.white, diff --git a/lib/amap/ui/pages/main_page/orders_section.dart b/lib/amap/ui/pages/main_page/orders_section.dart index bbfca64b5d..f59c684f73 100644 --- a/lib/amap/ui/pages/main_page/orders_section.dart +++ b/lib/amap/ui/pages/main_page/orders_section.dart @@ -13,6 +13,7 @@ import 'package:titan/tools/ui/widgets/align_left_text.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/card_layout.dart'; import 'package:titan/tools/ui/layouts/horizontal_list_view.dart'; +import 'package:titan/l10n/app_localizations.dart'; class OrderSection extends HookConsumerWidget { final VoidCallback onTap, addOrder, onEdit; @@ -38,9 +39,9 @@ class OrderSection extends HookConsumerWidget { return Column( children: [ - const AlignLeftText( - AMAPTextConstants.orders, - padding: EdgeInsets.symmetric(horizontal: 30), + AlignLeftText( + AppLocalizations.of(context)!.amapOrders, + padding: const EdgeInsets.symmetric(horizontal: 30), color: AMAPColorConstants.textDark, ), const SizedBox(height: 10), diff --git a/lib/amap/ui/pages/presentation_page/text.dart b/lib/amap/ui/pages/presentation_page/text.dart index 16caacedf2..42b1cdcead 100644 --- a/lib/amap/ui/pages/presentation_page/text.dart +++ b/lib/amap/ui/pages/presentation_page/text.dart @@ -7,6 +7,7 @@ import 'package:titan/amap/ui/amap.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:url_launcher/url_launcher.dart'; +import 'package:titan/l10n/app_localizations.dart'; class PresentationPage extends HookConsumerWidget { const PresentationPage({super.key}); @@ -31,7 +32,7 @@ class PresentationPage extends HookConsumerWidget { text: TextSpan( children: [ TextSpan( - text: AMAPTextConstants.presentation1, + text: AppLocalizations.of(context)!.amapPresentation1, style: TextStyle( fontSize: 15, fontWeight: FontWeight.w600, @@ -68,23 +69,22 @@ class PresentationPage extends HookConsumerWidget { } catch (e) { displayToastWithContext( TypeMsg.msg, - AMAPTextConstants.errorLink, + AppLocalizations.of(context)!.amapErrorLink, ); } }, ), - error: (Object error, StackTrace stackTrace) => - const TextSpan( - text: AMAPTextConstants.loadingError, - style: TextStyle(color: Colors.red), - ), - loading: () => const TextSpan( - text: AMAPTextConstants.loading, - style: TextStyle(color: Colors.red), + error: (Object error, StackTrace stackTrace) => TextSpan( + text: AppLocalizations.of(context)!.amapLoadingError, + style: const TextStyle(color: Colors.red), + ), + loading: () => TextSpan( + text: AppLocalizations.of(context)!.amapLoading, + style: const TextStyle(color: Colors.red), ), ), TextSpan( - text: AMAPTextConstants.presentation2, + text: AppLocalizations.of(context)!.amapPresentation2, style: TextStyle( fontSize: 15, fontWeight: FontWeight.w600, @@ -109,7 +109,7 @@ class PresentationPage extends HookConsumerWidget { AsyncChild( value: information, builder: (context, info) => Text( - "${AMAPTextConstants.contact} : ${info.manager} ", + "${AppLocalizations.of(context)!.amapContact} : ${info.manager} ", style: const TextStyle( fontSize: 15, fontWeight: FontWeight.w600, diff --git a/lib/amap/ui/pages/product_pages/add_edit_product.dart b/lib/amap/ui/pages/product_pages/add_edit_product.dart index efdec80220..61c6950ff3 100644 --- a/lib/amap/ui/pages/product_pages/add_edit_product.dart +++ b/lib/amap/ui/pages/product_pages/add_edit_product.dart @@ -16,6 +16,7 @@ import 'package:titan/tools/ui/widgets/align_left_text.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; import 'package:titan/tools/ui/widgets/text_entry.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class AddEditProduct extends HookConsumerWidget { const AddEditProduct({super.key}); @@ -34,7 +35,7 @@ class AddEditProduct extends HookConsumerWidget { ); final beginState = isEdit ? product.category - : AMAPTextConstants.createCategory; + : AppLocalizations.of(context)!.amapCreateCategory; final categoryController = ref.watch(selectedCategoryProvider(beginState)); final categoryNotifier = ref.watch( selectedCategoryProvider(beginState).notifier, @@ -57,8 +58,8 @@ class AddEditProduct extends HookConsumerWidget { const SizedBox(height: 10), AlignLeftText( isEdit - ? AMAPTextConstants.editProduct - : AMAPTextConstants.addProduct, + ? AppLocalizations.of(context)!.amapEditProduct + : AppLocalizations.of(context)!.amapAddProduct, color: AMAPColorConstants.green2, ), const SizedBox(height: 40), @@ -66,7 +67,7 @@ class AddEditProduct extends HookConsumerWidget { children: [ Center( child: TextEntry( - label: AMAPTextConstants.name, + label: AppLocalizations.of(context)!.amapName, controller: nameController, color: AMAPColorConstants.greenGradient2, enabledColor: AMAPColorConstants.enabled, @@ -75,7 +76,7 @@ class AddEditProduct extends HookConsumerWidget { const SizedBox(height: 30), Center( child: TextEntry( - label: AMAPTextConstants.price, + label: AppLocalizations.of(context)!.amapPrice, isDouble: true, color: AMAPColorConstants.greenGradient2, enabledColor: AMAPColorConstants.enabled, @@ -84,8 +85,8 @@ class AddEditProduct extends HookConsumerWidget { ), ), const SizedBox(height: 30), - const AlignLeftText( - AMAPTextConstants.category, + AlignLeftText( + AppLocalizations.of(context)!.amapCategory, fontSize: 20, color: AMAPColorConstants.greenGradient2, ), @@ -110,32 +111,41 @@ class AddEditProduct extends HookConsumerWidget { ), ), ), - items: [AMAPTextConstants.createCategory, ...categories] - .map((String value) { + items: + [ + AppLocalizations.of(context)!.amapCreateCategory, + ...categories, + ].map((String value) { return DropdownMenuItem( value: value, child: Text(value), ); - }) - .toList(), + }).toList(), onChanged: (value) { categoryNotifier.setText( - value ?? AMAPTextConstants.createCategory, + value ?? + AppLocalizations.of( + context, + )!.amapCreateCategory, ); newCategory.text = ""; }, ), ), if (categoryController == - AMAPTextConstants.createCategory) ...[ + AppLocalizations.of(context)!.amapCreateCategory) ...[ const SizedBox(height: 30), Center( child: TextEntry( - label: AMAPTextConstants.createCategory, - noValueError: AMAPTextConstants.pickChooseCategory, + label: AppLocalizations.of( + context, + )!.amapCreateCategory, + noValueError: AppLocalizations.of( + context, + )!.amapPickChooseCategory, enabled: categoryController == - AMAPTextConstants.createCategory, + AppLocalizations.of(context)!.amapCreateCategory, onChanged: (value) { newCategory.text = value; newCategory.selection = TextSelection.fromPosition( @@ -162,7 +172,9 @@ class AddEditProduct extends HookConsumerWidget { if (formKey.currentState!.validate()) { String cate = categoryController == - AMAPTextConstants.createCategory + AppLocalizations.of( + context, + )!.amapCreateCategory ? newCategory.text : categoryController; Product newProduct = Product( @@ -185,7 +197,9 @@ class AddEditProduct extends HookConsumerWidget { formKey.currentState!.reset(); displayToastWithContext( TypeMsg.msg, - AMAPTextConstants.updatedProduct, + AppLocalizations.of( + context, + )!.amapUpdatedProduct, ); } else { ref @@ -198,19 +212,23 @@ class AddEditProduct extends HookConsumerWidget { ); displayToastWithContext( TypeMsg.msg, - AMAPTextConstants.addedProduct, + AppLocalizations.of( + context, + )!.amapAddedProduct, ); } } else { if (isEdit) { displayToastWithContext( TypeMsg.error, - AMAPTextConstants.updatingError, + AppLocalizations.of( + context, + )!.amapUpdatingError, ); } else { displayToastWithContext( TypeMsg.error, - AMAPTextConstants.addingError, + AppLocalizations.of(context)!.amapAddingError, ); } } @@ -220,8 +238,8 @@ class AddEditProduct extends HookConsumerWidget { }, child: Text( isEdit - ? AMAPTextConstants.update - : AMAPTextConstants.add, + ? AppLocalizations.of(context)!.amapUpdate + : AppLocalizations.of(context)!.amapAdd, style: TextStyle( fontSize: 20, fontWeight: FontWeight.w700, diff --git a/lib/booking/tools/constants.dart b/lib/booking/tools/constants.dart index c45327ae84..d34e7df9f4 100644 --- a/lib/booking/tools/constants.dart +++ b/lib/booking/tools/constants.dart @@ -1,113 +1,11 @@ import 'package:syncfusion_flutter_calendar/calendar.dart'; -class BookingTextConstants { - static const String add = "Ajouter"; - static const String addBookingPage = "Demande"; - static const String addRoom = "Ajouter une salle"; - static const String addBooking = "Ajouter une réservation"; - static const String addedBooking = "Demande ajoutée"; - static const String addedRoom = "Salle ajoutée"; - static const String addedManager = "Gestionnaire ajouté"; - static const String addingError = "Erreur lors de l'ajout"; - static const String addManager = "Ajouter un gestionnaire"; - static const String adminPage = "Administrateur"; - static const String allDay = "Toute la journée"; - static const String bookedfor = "Réservé pour"; - static const String booking = "Réservation"; - static const String bookingCreated = "Réservation créée"; - static const String bookingDemand = "Demande de réservation"; - static const String bookingNote = "Note de la réservation"; - static const String bookingPage = "Réservation"; - static const String bookingReason = "Motif de la réservation"; - static const String by = "par"; - static const String confirm = "Confirmer"; - static const String confirmation = "Confirmation"; - static const String confirmBooking = "Confirmer la réservation ?"; - static const String confirmed = "Validée"; - static const String dates = "Dates"; - static const String decline = "Refuser"; - static const String declineBooking = "Refuser la réservation ?"; - static const String declined = "Refusée"; - static const String delete = "Supprimer"; - static const String deleting = "Suppression"; - static const String deleteBooking = "Suppression"; - static const String deleteBookingConfirmation = - "Êtes-vous sûr de vouloir supprimer cette réservation ?"; - static const String deletedBooking = "Réservation supprimée"; - static const String deletedRoom = "Salle supprimée"; - static const String deletedManager = "Gestionnaire supprimé"; - static const String deleteRoomConfirmation = - "Êtes-vous sûr de vouloir supprimer cette salle ?\n\nLa salle ne doit avoir aucune réservation en cours ou à venir pour être supprimée"; - static const String deleteManagerConfirmation = - "Êtes-vous sûr de vouloir supprimer ce gestionnaire ?\n\nLe gestionnaire ne doit être associé à aucune salle pour pouvoir être supprimé"; - static const String deletingBooking = "Supprimer la réservation ?"; - static const String deletingError = "Erreur lors de la suppression"; - static const String deletingRoom = "Supprimer la salle ?"; - static const String edit = "Modifier"; - static const String editBooking = "Modifier une réservation"; - static const String editionError = "Erreur lors de la modification"; - static const String editedBooking = "Réservation modifiée"; - static const String editedRoom = "Salle modifiée"; - static const String editedManager = "Gestionnaire modifié"; - static const String editManager = "Modifier ou supprimer un gestionnaire"; - static const String editRoom = "Modifier ou supprimer une salle"; - static const String endDate = "Date de fin"; - static const String endHour = "Heure de fin"; - static const String entity = "Pour qui ?"; - static const String error = "Erreur"; - static const String eventEvery = "Tous les"; - static const String historyPage = "Historique"; - static const String incorrectOrMissingFields = - "Champs incorrects ou manquants"; - static const String interval = "Intervalle"; - static const String invalidIntervalError = "Intervalle invalide"; - static const String invalidDates = "Dates invalides"; - static const String invalidRoom = "Salle invalide"; - static const String keysRequested = "Clés demandées"; - static const String management = "Gestion"; - static const String manager = "Gestionnaire"; - static const String managerName = "Nom du gestionnaire"; - static const String multipleDay = "Plusieurs jours"; - static const String myBookings = "Mes réservations"; - static const String necessaryKey = "Clé nécessaire"; - static const String next = "Suivant"; - static const String no = "Non"; - static const String noCurrentBooking = "Pas de réservation en cours"; - static const String noDateError = "Veuillez choisir une date"; - static const String noAppointmentInReccurence = - "Aucun créneau existe avec ces paramètres de récurrence"; - static const String noDaySelected = "Aucun jour sélectionné"; - static const String noDescriptionError = "Veuillez entrer une description"; - static const String noKeys = "Aucune clé"; - static const String noNoteError = "Veuillez entrer une note"; - static const String noPhoneRegistered = "Numéro non renseigné"; - static const String noReasonError = "Veuillez entrer un motif"; - static const String noRoomFoundError = "Aucune salle enregistrée"; - static const String noRoomFound = "Aucune salle trouvée"; - static const String note = "Note"; - static const String other = "Autre"; - static const String pending = "En attente"; - static const String previous = "Précédent"; - static const String reason = "Motif"; - static const String recurrence = "Récurrence"; - static const String recurrenceDays = "Jours de récurrence"; - static const String recurrenceEndDate = "Date de fin de récurrence"; - static const String recurrent = "Récurrent"; - static const String registeredRooms = "Salles enregistrées"; - static const String room = "Salle"; - static const String roomName = "Nom de la salle"; - static const String startDate = "Date de début"; - static const String startHour = "Heure de début"; - static const String weeks = "Semaines"; - static const String yes = "Oui"; - - static const List weekDaysOrdered = [ - WeekDays.monday, - WeekDays.tuesday, - WeekDays.wednesday, - WeekDays.thursday, - WeekDays.friday, - WeekDays.saturday, - WeekDays.sunday, - ]; -} +final weekDaysOrdered = [ + WeekDays.monday, + WeekDays.tuesday, + WeekDays.wednesday, + WeekDays.thursday, + WeekDays.friday, + WeekDays.saturday, + WeekDays.sunday, +]; diff --git a/lib/booking/tools/functions.dart b/lib/booking/tools/functions.dart index e65867d478..92caafbf6b 100644 --- a/lib/booking/tools/functions.dart +++ b/lib/booking/tools/functions.dart @@ -1,33 +1,35 @@ -import 'package:titan/booking/tools/constants.dart'; +import 'package:flutter/material.dart'; import 'package:titan/tools/functions.dart'; import 'package:syncfusion_flutter_calendar/calendar.dart'; +import 'package:titan/l10n/app_localizations.dart'; -String decisionToString(Decision d) { +String decisionToString(Decision d, BuildContext context) { switch (d) { case Decision.approved: - return BookingTextConstants.confirmed; + return AppLocalizations.of(context)!.bookingConfirmed; case Decision.declined: - return BookingTextConstants.declined; + return AppLocalizations.of(context)!.bookingDeclined; case Decision.pending: - return BookingTextConstants.pending; + return AppLocalizations.of(context)!.bookingPending; } } -String weekDayToString(WeekDays day) { +String weekDayToLocalizedString(BuildContext context, WeekDays day) { + final loc = AppLocalizations.of(context)!; switch (day) { - case WeekDays.sunday: - return "Dimanche"; case WeekDays.monday: - return "Lundi"; + return loc.bookingWeekDayMon; case WeekDays.tuesday: - return "Mardi"; + return loc.bookingWeekDayTue; case WeekDays.wednesday: - return "Mercredi"; + return loc.bookingWeekDayWed; case WeekDays.thursday: - return "Jeudi"; + return loc.bookingWeekDayThu; case WeekDays.friday: - return "Vendredi"; + return loc.bookingWeekDayFri; case WeekDays.saturday: - return "Samedi"; + return loc.bookingWeekDaySat; + case WeekDays.sunday: + return loc.bookingWeekDaySun; } } diff --git a/lib/booking/ui/calendar/calendar_dialog.dart b/lib/booking/ui/calendar/calendar_dialog.dart index 9a8b0027f3..873a266731 100644 --- a/lib/booking/ui/calendar/calendar_dialog.dart +++ b/lib/booking/ui/calendar/calendar_dialog.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:titan/booking/class/booking.dart'; -import 'package:titan/booking/tools/constants.dart'; import 'package:titan/booking/ui/calendar/calendar_dialog_button.dart'; import 'package:titan/tools/functions.dart'; +import 'package:titan/l10n/app_localizations.dart'; class CalendarDialog extends StatelessWidget { final Booking booking; @@ -50,7 +50,7 @@ class CalendarDialog extends StatelessWidget { ), const SizedBox(height: 10), Text( - "${BookingTextConstants.bookedfor} ${booking.entity} ${BookingTextConstants.by} ${booking.applicant.getName()}", + "${AppLocalizations.of(context)!.bookingBookedFor} ${booking.entity} ${AppLocalizations.of(context)!.bookingBy} ${booking.applicant.getName()}", style: const TextStyle( fontWeight: FontWeight.w400, fontSize: 15, @@ -103,7 +103,9 @@ class CalendarDialog extends StatelessWidget { Flexible( child: Text( booking.applicant.phone ?? - BookingTextConstants.noPhoneRegistered, + AppLocalizations.of( + context, + )!.bookingNoPhoneRegistered, style: const TextStyle( fontWeight: FontWeight.w400, fontSize: 15, diff --git a/lib/booking/ui/components/booking_card.dart b/lib/booking/ui/components/booking_card.dart index fb3df46a66..468bc18c9a 100644 --- a/lib/booking/ui/components/booking_card.dart +++ b/lib/booking/ui/components/booking_card.dart @@ -2,12 +2,12 @@ import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:titan/booking/class/booking.dart'; -import 'package:titan/booking/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; import 'package:titan/tools/ui/layouts/card_button.dart'; import 'package:titan/tools/ui/layouts/card_layout.dart'; import 'package:syncfusion_flutter_calendar/calendar.dart'; +import 'package:titan/l10n/app_localizations.dart'; class BookingCard extends StatelessWidget { final Booking booking; @@ -153,10 +153,10 @@ class BookingCard extends StatelessWidget { children: [ Text( booking.decision == Decision.pending - ? BookingTextConstants.pending + ? AppLocalizations.of(context)!.bookingPending : booking.decision == Decision.approved - ? BookingTextConstants.confirmed - : BookingTextConstants.declined, + ? AppLocalizations.of(context)!.bookingConfirmed + : AppLocalizations.of(context)!.bookingDeclined, style: TextStyle( fontSize: 13, fontWeight: FontWeight.bold, @@ -164,7 +164,7 @@ class BookingCard extends StatelessWidget { ), ), Text( - '${BookingTextConstants.keysRequested}: ${booking.key ? BookingTextConstants.yes : BookingTextConstants.no}', + '${AppLocalizations.of(context)!.bookingKeysRequested}: ${booking.key ? AppLocalizations.of(context)!.bookingYes : AppLocalizations.of(context)!.bookingNo}', style: TextStyle( fontSize: 13, fontWeight: FontWeight.bold, diff --git a/lib/booking/ui/pages/admin_pages/add_edit_manager_page.dart b/lib/booking/ui/pages/admin_pages/add_edit_manager_page.dart index e89d0c1405..0d24815ebb 100644 --- a/lib/booking/ui/pages/admin_pages/add_edit_manager_page.dart +++ b/lib/booking/ui/pages/admin_pages/add_edit_manager_page.dart @@ -6,7 +6,6 @@ import 'package:titan/admin/providers/group_id_provider.dart'; import 'package:titan/booking/class/manager.dart'; import 'package:titan/booking/providers/manager_list_provider.dart'; import 'package:titan/booking/providers/manager_provider.dart'; -import 'package:titan/booking/tools/constants.dart'; import 'package:titan/booking/ui/booking.dart'; import 'package:titan/booking/ui/pages/admin_pages/admin_entry.dart'; import 'package:titan/booking/ui/pages/admin_pages/admin_scroll_chips.dart'; @@ -17,6 +16,7 @@ import 'package:titan/tools/ui/layouts/item_chip.dart'; import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/admin/providers/group_list_provider.dart'; +import 'package:titan/l10n/app_localizations.dart'; class AddEditManagerPage extends HookConsumerWidget { final GlobalKey dataKey = GlobalKey(); @@ -46,8 +46,8 @@ class AddEditManagerPage extends HookConsumerWidget { alignment: Alignment.centerLeft, child: Text( isEdit - ? BookingTextConstants.editManager - : BookingTextConstants.addManager, + ? AppLocalizations.of(context)!.bookingEditManager + : AppLocalizations.of(context)!.bookingAddManager, style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, @@ -61,7 +61,7 @@ class AddEditManagerPage extends HookConsumerWidget { children: [ const SizedBox(height: 50), AdminEntry( - name: BookingTextConstants.managerName, + name: AppLocalizations.of(context)!.bookingManagerName, nameController: name, ), const SizedBox(height: 50), @@ -115,28 +115,36 @@ class AddEditManagerPage extends HookConsumerWidget { isEdit ? displayToastWithContext( TypeMsg.msg, - BookingTextConstants.editedManager, + AppLocalizations.of( + context, + )!.bookingEditedManager, ) : displayToastWithContext( TypeMsg.msg, - BookingTextConstants.addedManager, + AppLocalizations.of( + context, + )!.bookingAddedManager, ); } else { isEdit ? displayToastWithContext( TypeMsg.error, - BookingTextConstants.editionError, + AppLocalizations.of( + context, + )!.bookingEditionError, ) : displayToastWithContext( TypeMsg.error, - BookingTextConstants.addingError, + AppLocalizations.of( + context, + )!.bookingAddingError, ); } }); }, buttonText: isEdit - ? BookingTextConstants.edit - : BookingTextConstants.add, + ? AppLocalizations.of(context)!.bookingEdit + : AppLocalizations.of(context)!.bookingAdd, ), if (isEdit) ...[ const SizedBox(height: 30), @@ -146,8 +154,9 @@ class AddEditManagerPage extends HookConsumerWidget { await showDialog( context: context, builder: (context) => CustomDialogBox( - descriptions: BookingTextConstants - .deleteManagerConfirmation, + descriptions: AppLocalizations.of( + context, + )!.bookingDeleteManagerConfirmation, onYes: () async { final value = await managerListNotifier .deleteManager(manager); @@ -155,21 +164,27 @@ class AddEditManagerPage extends HookConsumerWidget { QR.back(); displayToastWithContext( TypeMsg.msg, - BookingTextConstants.deletedManager, + AppLocalizations.of( + context, + )!.bookingDeletedManager, ); } else { displayToastWithContext( TypeMsg.error, - BookingTextConstants.deletingError, + AppLocalizations.of( + context, + )!.bookingDeletingError, ); } }, - title: BookingTextConstants.deleting, + title: AppLocalizations.of( + context, + )!.bookingDeleting, ), ); }); }, - buttonText: BookingTextConstants.delete, + buttonText: AppLocalizations.of(context)!.bookingDelete, ), ], const SizedBox(height: 30), diff --git a/lib/booking/ui/pages/admin_pages/add_edit_room_page.dart b/lib/booking/ui/pages/admin_pages/add_edit_room_page.dart index 009859b011..66f89d055a 100644 --- a/lib/booking/ui/pages/admin_pages/add_edit_room_page.dart +++ b/lib/booking/ui/pages/admin_pages/add_edit_room_page.dart @@ -7,7 +7,6 @@ import 'package:titan/booking/providers/manager_list_provider.dart'; import 'package:titan/booking/providers/manager_id_provider.dart'; import 'package:titan/service/providers/room_list_provider.dart'; import 'package:titan/booking/providers/room_provider.dart'; -import 'package:titan/booking/tools/constants.dart'; import 'package:titan/booking/ui/booking.dart'; import 'package:titan/booking/ui/pages/admin_pages/admin_entry.dart'; import 'package:titan/booking/ui/pages/admin_pages/admin_scroll_chips.dart'; @@ -17,6 +16,7 @@ import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/layouts/item_chip.dart'; import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class AddEditRoomPage extends HookConsumerWidget { final dataKey = GlobalKey(); @@ -46,8 +46,8 @@ class AddEditRoomPage extends HookConsumerWidget { alignment: Alignment.centerLeft, child: Text( isEdit - ? BookingTextConstants.editRoom - : BookingTextConstants.addRoom, + ? AppLocalizations.of(context)!.bookingEditRoom + : AppLocalizations.of(context)!.bookingAddRoom, style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, @@ -61,7 +61,7 @@ class AddEditRoomPage extends HookConsumerWidget { children: [ const SizedBox(height: 50), AdminEntry( - name: BookingTextConstants.roomName, + name: AppLocalizations.of(context)!.bookingRoomName, nameController: name, ), const SizedBox(height: 50), @@ -113,28 +113,28 @@ class AddEditRoomPage extends HookConsumerWidget { isEdit ? displayToastWithContext( TypeMsg.msg, - BookingTextConstants.editedRoom, + AppLocalizations.of(context)!.bookingEditedRoom, ) : displayToastWithContext( TypeMsg.msg, - BookingTextConstants.addedRoom, + AppLocalizations.of(context)!.bookingAddedRoom, ); } else { isEdit ? displayToastWithContext( TypeMsg.error, - BookingTextConstants.editionError, + AppLocalizations.of(context)!.bookingEditionError, ) : displayToastWithContext( TypeMsg.error, - BookingTextConstants.addingError, + AppLocalizations.of(context)!.bookingAddingError, ); } }); }, buttonText: isEdit - ? BookingTextConstants.edit - : BookingTextConstants.add, + ? AppLocalizations.of(context)!.bookingEdit + : AppLocalizations.of(context)!.bookingAdd, ), if (isEdit) ...[ const SizedBox(height: 30), @@ -145,7 +145,7 @@ class AddEditRoomPage extends HookConsumerWidget { context: context, builder: (context) => CustomDialogBox( descriptions: - BookingTextConstants.deleteRoomConfirmation, + AppLocalizations.of(context)!.bookingDeleteRoomConfirmation, onYes: () async { final value = await roomListNotifier.deleteRoom( room, @@ -154,21 +154,21 @@ class AddEditRoomPage extends HookConsumerWidget { QR.back(); displayToastWithContext( TypeMsg.msg, - BookingTextConstants.deletedRoom, + AppLocalizations.of(context)!.bookingDeletedRoom, ); } else { displayToastWithContext( TypeMsg.error, - BookingTextConstants.deletingError, + AppLocalizations.of(context)!.bookingDeletingError, ); } }, - title: BookingTextConstants.deleteBooking, + title: AppLocalizations.of(context)!.bookingDeleteBooking, ), ); }); }, - buttonText: BookingTextConstants.delete, + buttonText: AppLocalizations.of(context)!.bookingDelete, ), ], const SizedBox(height: 30), diff --git a/lib/booking/ui/pages/admin_pages/admin_page.dart b/lib/booking/ui/pages/admin_pages/admin_page.dart index ef481dac5e..7c196517e2 100644 --- a/lib/booking/ui/pages/admin_pages/admin_page.dart +++ b/lib/booking/ui/pages/admin_pages/admin_page.dart @@ -13,13 +13,13 @@ import 'package:titan/booking/providers/manager_provider.dart'; import 'package:titan/service/providers/room_list_provider.dart'; import 'package:titan/booking/providers/room_provider.dart'; import 'package:titan/booking/router.dart'; -import 'package:titan/booking/tools/constants.dart'; import 'package:titan/booking/ui/booking.dart'; import 'package:titan/booking/ui/calendar/calendar.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/ui/layouts/item_chip.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class AdminPage extends HookConsumerWidget { const AdminPage({super.key}); @@ -53,13 +53,13 @@ class AdminPage extends HookConsumerWidget { const SizedBox(height: 20), const Expanded(child: Calendar(isManagerPage: false)), const SizedBox(height: 30), - const Padding( - padding: EdgeInsets.symmetric(horizontal: 30.0), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 30.0), child: Align( alignment: Alignment.centerLeft, child: Text( - BookingTextConstants.room, - style: TextStyle( + AppLocalizations.of(context)!.bookingRoom, + style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Color.fromARGB(255, 149, 149, 149), @@ -123,13 +123,13 @@ class AdminPage extends HookConsumerWidget { }, ), const SizedBox(height: 20), - const Padding( - padding: EdgeInsets.symmetric(horizontal: 30.0), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 30.0), child: Align( alignment: Alignment.centerLeft, child: Text( - BookingTextConstants.manager, - style: TextStyle( + AppLocalizations.of(context)!.bookingManager, + style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Color.fromARGB(255, 149, 149, 149), diff --git a/lib/booking/ui/pages/booking_pages/add_edit_booking_page.dart b/lib/booking/ui/pages/booking_pages/add_edit_booking_page.dart index ff76e882a5..c0a5398f13 100644 --- a/lib/booking/ui/pages/booking_pages/add_edit_booking_page.dart +++ b/lib/booking/ui/pages/booking_pages/add_edit_booking_page.dart @@ -29,6 +29,7 @@ import 'package:titan/tools/ui/widgets/text_entry.dart'; import 'package:titan/user/providers/user_provider.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:syncfusion_flutter_calendar/calendar.dart'; +import 'package:titan/l10n/app_localizations.dart'; class AddEditBookingPage extends HookConsumerWidget { final dataKey = GlobalKey(); @@ -106,8 +107,8 @@ class AddEditBookingPage extends HookConsumerWidget { padding: const EdgeInsets.symmetric(horizontal: 30), child: AlignLeftText( isEdit - ? BookingTextConstants.editBooking - : BookingTextConstants.addBooking, + ? AppLocalizations.of(context)!.bookingEditBooking + : AppLocalizations.of(context)!.bookingAddBooking, color: Colors.grey, ), ), @@ -153,27 +154,27 @@ class AddEditBookingPage extends HookConsumerWidget { children: [ TextEntry( controller: entity, - label: BookingTextConstants.entity, + label: AppLocalizations.of(context)!.bookingEntity, ), const SizedBox(height: 30), TextEntry( controller: motif, - label: BookingTextConstants.reason, + label: AppLocalizations.of(context)!.bookingReason, ), const SizedBox(height: 30), TextEntry( - label: BookingTextConstants.note, + label: AppLocalizations.of(context)!.bookingNote, controller: note, canBeEmpty: true, ), const SizedBox(height: 20), CheckBoxEntry( - title: BookingTextConstants.necessaryKey, + title: AppLocalizations.of(context)!.bookingNecessaryKey, valueNotifier: keyRequired, ), const SizedBox(height: 20), CheckBoxEntry( - title: BookingTextConstants.recurrence, + title: AppLocalizations.of(context)!.bookingRecurrence, valueNotifier: recurrent, onChanged: () { start.text = ""; @@ -183,7 +184,7 @@ class AddEditBookingPage extends HookConsumerWidget { ), const SizedBox(height: 20), CheckBoxEntry( - title: BookingTextConstants.allDay, + title: AppLocalizations.of(context)!.bookingAllDay, valueNotifier: allDay, onChanged: () { start.text = ""; @@ -195,13 +196,15 @@ class AddEditBookingPage extends HookConsumerWidget { recurrent.value ? Column( children: [ - const Text( - BookingTextConstants.recurrenceDays, - style: TextStyle(color: Colors.black), + Text( + AppLocalizations.of( + context, + )!.bookingRecurrenceDays, + style: const TextStyle(color: Colors.black), ), const SizedBox(height: 10), Column( - children: BookingTextConstants.weekDaysOrdered + children: weekDaysOrdered .map( (e) => GestureDetector( onTap: () { @@ -213,7 +216,10 @@ class AddEditBookingPage extends HookConsumerWidget { MainAxisAlignment.spaceBetween, children: [ Text( - weekDayToString(e), + weekDayToLocalizedString( + context, + e, + ), style: TextStyle( color: Colors.grey.shade700, fontSize: 16, @@ -234,15 +240,21 @@ class AddEditBookingPage extends HookConsumerWidget { .toList(), ), const SizedBox(height: 20), - const Text( - BookingTextConstants.interval, + Text( + AppLocalizations.of(context)!.bookingInterval, style: TextStyle(color: Colors.black), ), const SizedBox(height: 10), TextEntry( - label: BookingTextConstants.interval, - prefix: BookingTextConstants.eventEvery, - suffix: BookingTextConstants.weeks, + label: AppLocalizations.of( + context, + )!.bookingInterval, + prefix: AppLocalizations.of( + context, + )!.bookingEventEvery, + suffix: AppLocalizations.of( + context, + )!.bookingWeeks, controller: interval, isInt: true, ), @@ -254,14 +266,18 @@ class AddEditBookingPage extends HookConsumerWidget { onTap: () => getOnlyHourDate(context, start), controller: start, - label: BookingTextConstants.startHour, + label: AppLocalizations.of( + context, + )!.bookingStartHour, ), const SizedBox(height: 30), DateEntry( onTap: () => getOnlyHourDate(context, end), controller: end, - label: BookingTextConstants.endHour, + label: AppLocalizations.of( + context, + )!.bookingEndHour, ), const SizedBox(height: 30), ], @@ -270,7 +286,9 @@ class AddEditBookingPage extends HookConsumerWidget { onTap: () => getOnlyDayDate(context, recurrenceEndDate), controller: recurrenceEndDate, - label: BookingTextConstants.recurrenceEndDate, + label: AppLocalizations.of( + context, + )!.bookingRecurrenceEndDate, ), ], ) @@ -281,7 +299,9 @@ class AddEditBookingPage extends HookConsumerWidget { ? getOnlyDayDate(context, start) : getFullDate(context, start), controller: start, - label: BookingTextConstants.startDate, + label: AppLocalizations.of( + context, + )!.bookingStartDate, ), const SizedBox(height: 30), DateEntry( @@ -289,7 +309,9 @@ class AddEditBookingPage extends HookConsumerWidget { ? getOnlyDayDate(context, end) : getFullDate(context, end), controller: end, - label: BookingTextConstants.endDate, + label: AppLocalizations.of( + context, + )!.bookingEndDate, ), ], ), @@ -313,19 +335,21 @@ class AddEditBookingPage extends HookConsumerWidget { displayToast( context, TypeMsg.error, - BookingTextConstants.invalidDates, + AppLocalizations.of(context)!.bookingInvalidDates, ); } else if (room.value.id.isEmpty) { displayToast( context, TypeMsg.error, - BookingTextConstants.invalidRoom, + AppLocalizations.of(context)!.bookingInvalidRoom, ); } else if (recurrent.value && selectedDays.isEmpty) { displayToast( context, TypeMsg.error, - BookingTextConstants.noDaySelected, + AppLocalizations.of( + context, + )!.bookingNoDaySelected, ); } else { String recurrenceRule = ""; @@ -366,8 +390,9 @@ class AddEditBookingPage extends HookConsumerWidget { displayToast( context, TypeMsg.error, - BookingTextConstants - .noAppointmentInReccurence, + AppLocalizations.of( + context, + )!.bookingNoAppointmentInReccurence, ); return; } @@ -429,24 +454,32 @@ class AddEditBookingPage extends HookConsumerWidget { if (isEdit) { displayToastWithContext( TypeMsg.msg, - BookingTextConstants.editedBooking, + AppLocalizations.of( + context, + )!.bookingEditedBooking, ); } else { displayToastWithContext( TypeMsg.msg, - BookingTextConstants.addedBooking, + AppLocalizations.of( + context, + )!.bookingAddedBooking, ); } } else { if (isEdit) { displayToastWithContext( TypeMsg.error, - BookingTextConstants.editionError, + AppLocalizations.of( + context, + )!.bookingEditionError, ); } else { displayToastWithContext( TypeMsg.error, - BookingTextConstants.addingError, + AppLocalizations.of( + context, + )!.bookingAddingError, ); } } @@ -456,14 +489,16 @@ class AddEditBookingPage extends HookConsumerWidget { displayToast( context, TypeMsg.error, - BookingTextConstants.incorrectOrMissingFields, + AppLocalizations.of( + context, + )!.bookingIncorrectOrMissingFields, ); } }, child: Text( isEdit - ? BookingTextConstants.edit - : BookingTextConstants.add, + ? AppLocalizations.of(context)!.bookingEdit + : AppLocalizations.of(context)!.bookingAdd, style: const TextStyle( color: Colors.white, fontSize: 25, diff --git a/lib/booking/ui/pages/detail_pages/detail_booking.dart b/lib/booking/ui/pages/detail_pages/detail_booking.dart index b8715de07a..094dcf7fcb 100644 --- a/lib/booking/ui/pages/detail_pages/detail_booking.dart +++ b/lib/booking/ui/pages/detail_pages/detail_booking.dart @@ -3,13 +3,13 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/booking/providers/booking_provider.dart'; -import 'package:titan/booking/tools/constants.dart'; import 'package:titan/booking/tools/functions.dart'; import 'package:titan/booking/ui/booking.dart'; import 'package:titan/booking/ui/components/booking_card.dart'; import 'package:titan/booking/ui/pages/detail_pages/contact_button.dart'; import 'package:titan/tools/functions.dart'; import 'package:url_launcher/url_launcher.dart'; +import 'package:titan/l10n/app_localizations.dart'; class DetailBookingPage extends HookConsumerWidget { final bool isAdmin; @@ -56,7 +56,7 @@ class DetailBookingPage extends HookConsumerWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - decisionToString(booking.decision), + decisionToString(booking.decision, context), style: const TextStyle( fontSize: 25, fontWeight: FontWeight.bold, @@ -115,7 +115,7 @@ class DetailBookingPage extends HookConsumerWidget { Column( children: [ AutoSizeText( - "${BookingTextConstants.bookedfor} ${booking.entity}", + "${AppLocalizations.of(context)!.bookingBookedFor} ${booking.entity}", style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, @@ -126,7 +126,9 @@ class DetailBookingPage extends HookConsumerWidget { const SizedBox(height: 50), Text( booking.applicant.phone ?? - BookingTextConstants.noPhoneRegistered, + AppLocalizations.of( + context, + )!.bookingNoPhoneRegistered, style: const TextStyle(fontSize: 25), ), const SizedBox(height: 50), diff --git a/lib/booking/ui/pages/main_page/main_page.dart b/lib/booking/ui/pages/main_page/main_page.dart index 3282ce988d..3a4a9387dd 100644 --- a/lib/booking/ui/pages/main_page/main_page.dart +++ b/lib/booking/ui/pages/main_page/main_page.dart @@ -12,7 +12,6 @@ import 'package:titan/booking/providers/manager_booking_list_provider.dart'; import 'package:titan/booking/providers/selected_days_provider.dart'; import 'package:titan/booking/providers/user_booking_list_provider.dart'; import 'package:titan/booking/router.dart'; -import 'package:titan/booking/tools/constants.dart'; import 'package:titan/booking/ui/booking.dart'; import 'package:titan/booking/ui/calendar/calendar.dart'; import 'package:titan/booking/ui/components/booking_card.dart'; @@ -26,6 +25,7 @@ import 'package:titan/tools/ui/layouts/refresher.dart'; import 'package:titan/tools/ui/layouts/horizontal_list_view.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:syncfusion_flutter_calendar/calendar.dart'; +import 'package:titan/l10n/app_localizations.dart'; class BookingMainPage extends HookConsumerWidget { const BookingMainPage({super.key}); @@ -80,7 +80,7 @@ class BookingMainPage extends HookConsumerWidget { children: [ if (isManager) AdminButton( - text: BookingTextConstants.management, + text: AppLocalizations.of(context)!.bookingManagement, onTap: () { QR.to(BookingRouter.root + BookingRouter.manager); }, @@ -97,13 +97,13 @@ class BookingMainPage extends HookConsumerWidget { const SizedBox(height: 10), const Expanded(child: Calendar(isManagerPage: false)), const SizedBox(height: 30), - const Padding( - padding: EdgeInsets.symmetric(horizontal: 30.0), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 30.0), child: Align( alignment: Alignment.centerLeft, child: Text( - BookingTextConstants.myBookings, - style: TextStyle( + AppLocalizations.of(context)!.bookingMyBookings, + style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Color.fromARGB(255, 149, 149, 149), @@ -151,11 +151,19 @@ class BookingMainPage extends HookConsumerWidget { await showDialog( context: context, builder: (context) => CustomDialogBox( - descriptions: BookingTextConstants - .deleteBookingConfirmation, + descriptions: AppLocalizations.of( + context, + )!.bookingDeleteBookingConfirmation, onYes: () async { + final deleteMsg = AppLocalizations.of( + context, + )!.bookingDeleteBooking; + final errorMsg = AppLocalizations.of( + context, + )!.bookingDeletingError; final value = await bookingsNotifier .deleteBooking(e); + if (value) { ref .read( @@ -164,16 +172,18 @@ class BookingMainPage extends HookConsumerWidget { .loadUserManageBookings; displayToastWithContext( TypeMsg.msg, - BookingTextConstants.deleteBooking, + deleteMsg, ); } else { displayToastWithContext( TypeMsg.error, - BookingTextConstants.deletingError, + errorMsg, ); } }, - title: BookingTextConstants.deleteBooking, + title: AppLocalizations.of( + context, + )!.bookingDeleteBooking, ), ); }); diff --git a/lib/booking/ui/pages/manager_page/list_booking.dart b/lib/booking/ui/pages/manager_page/list_booking.dart index 559337eef2..f7609a2fc7 100644 --- a/lib/booking/ui/pages/manager_page/list_booking.dart +++ b/lib/booking/ui/pages/manager_page/list_booking.dart @@ -10,7 +10,6 @@ import 'package:titan/booking/providers/manager_confirmed_booking_list_provider. import 'package:titan/booking/providers/user_booking_list_provider.dart'; import 'package:titan/booking/providers/selected_days_provider.dart'; import 'package:titan/booking/router.dart'; -import 'package:titan/booking/tools/constants.dart'; import 'package:titan/booking/ui/components/booking_card.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; @@ -18,6 +17,7 @@ import 'package:titan/tools/ui/layouts/horizontal_list_view.dart'; import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:syncfusion_flutter_calendar/calendar.dart'; +import 'package:titan/l10n/app_localizations.dart'; class ListBooking extends HookConsumerWidget { final List bookings; @@ -119,8 +119,8 @@ class ListBooking extends HookConsumerWidget { context: context, builder: (context) { return CustomDialogBox( - title: BookingTextConstants.confirm, - descriptions: BookingTextConstants.confirmBooking, + title: AppLocalizations.of(context)!.bookingConfirm, + descriptions: AppLocalizations.of(context)!.bookingConfirmBooking, onYes: () async { await tokenExpireWrapper(ref, () async { Booking newBooking = e.copyWith( @@ -156,8 +156,8 @@ class ListBooking extends HookConsumerWidget { context: context, builder: (context) { return CustomDialogBox( - title: BookingTextConstants.decline, - descriptions: BookingTextConstants.declineBooking, + title: AppLocalizations.of(context)!.bookingDecline, + descriptions: AppLocalizations.of(context)!.bookingDeclineBooking, onYes: () async { await tokenExpireWrapper(ref, () async { Booking newBooking = e.copyWith( diff --git a/lib/booking/ui/pages/manager_page/manager_page.dart b/lib/booking/ui/pages/manager_page/manager_page.dart index b10f5ac4e0..4f7c0546a0 100644 --- a/lib/booking/ui/pages/manager_page/manager_page.dart +++ b/lib/booking/ui/pages/manager_page/manager_page.dart @@ -4,12 +4,12 @@ import 'package:titan/booking/class/booking.dart'; import 'package:titan/booking/providers/manager_booking_list_provider.dart'; import 'package:titan/booking/providers/manager_confirmed_booking_list_provider.dart'; import 'package:titan/service/providers/room_list_provider.dart'; -import 'package:titan/booking/tools/constants.dart'; import 'package:titan/booking/ui/booking.dart'; import 'package:titan/booking/ui/calendar/calendar.dart'; import 'package:titan/booking/ui/pages/manager_page/list_booking.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; +import 'package:titan/l10n/app_localizations.dart'; class ManagerPage extends HookConsumerWidget { const ManagerPage({super.key}); @@ -63,10 +63,10 @@ class ManagerPage extends HookConsumerWidget { if (pendingBookings.isEmpty && confirmedBookings.isEmpty && canceledBookings.isEmpty) - const Center( + Center( child: Text( - BookingTextConstants.noCurrentBooking, - style: TextStyle( + AppLocalizations.of(context)!.bookingNoCurrentBooking, + style: const TextStyle( color: Colors.black, fontSize: 20, fontWeight: FontWeight.bold, @@ -74,16 +74,16 @@ class ManagerPage extends HookConsumerWidget { ), ), ListBooking( - title: BookingTextConstants.pending, + title: AppLocalizations.of(context)!.bookingPending, bookings: pendingBookings, canToggle: false, ), ListBooking( - title: BookingTextConstants.confirmed, + title: AppLocalizations.of(context)!.bookingConfirmed, bookings: confirmedBookings, ), ListBooking( - title: BookingTextConstants.declined, + title: AppLocalizations.of(context)!.bookingDeclined, bookings: canceledBookings, ), const SizedBox(height: 30), diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb new file mode 100644 index 0000000000..3e6fb91d9c --- /dev/null +++ b/lib/l10n/app_fr.arb @@ -0,0 +1,1103 @@ +{ + "@@locale": "fr", + "adminAccountTypes": "Types de compte", + "adminAdd": "Ajouter", + "adminAddGroup": "Ajouter un groupe", + "adminAddMember": "Ajouter un membre", + "adminAddedGroup": "Groupe créé", + "adminAddedLoaner": "Préteur ajouté", + "adminAddedMember": "Membre ajouté", + "adminAddingError": "Erreur lors de l'ajout", + "adminAddingMember": "Ajout d'un membre", + "adminAddLoaningGroup": "Ajouter un groupe de prêt", + "adminAddSchool": "Ajouter une école", + "adminAddStructure": "Ajouter une structure", + "adminAddedSchool": "École créée", + "adminAddedStructure": "Structure ajoutée", + "adminEditedStructure": "Structure modifiée", + "adminAdministration": "Administration", + "adminAssociationMembership": "Adhésion", + "adminAssociationMembershipName": "Nom de l'adhésion", + "adminAssociationsMemberships": "Adhésions", + "adminClearFilters": "Effacer les filtres", + "adminCreateAssociationMembership": "Créer une adhésion", + "adminCreatedAssociationMembership": "Adhésion créée", + "adminCreationError": "Erreur lors de la création", + "adminDateError": "La date de début doit être avant la date de fin", + "adminDelete": "Supprimer", + "adminDeleteAssociationMembership": "Supprimer l'adhésion ?", + "adminDeletedAssociationMembership": "Adhésion supprimée", + "adminDeleteGroup": "Supprimer le groupe ?", + "adminDeletedGroup": "Groupe supprimé", + "adminDeleteSchool": "Supprimer l'école ?", + "adminDeletedSchool": "École supprimée", + "adminDeleting": "Suppression", + "adminDeletingError": "Erreur lors de la suppression", + "adminDescription": "Description", + "adminEclSchool": "Centrale Lyon", + "adminEdit": "Modifier", + "adminEditStructure": "Modifier la structure", + "adminEditMembership": "Modifier l'adhésion", + "adminEmptyDate": "Date vide", + "adminEmptyFieldError": "Le nom ne peut pas être vide", + "adminEmailRegex": "Email Regex", + "adminEmptyUser": "Utilisateur vide", + "adminEndDate": "Date de fin", + "adminEndDateMaximal": "Date de fin maximale", + "adminEndDateMinimal": "Date de fin minimale", + "adminError": "Erreur", + "adminFilters": "Filtres", + "adminGroup": "Groupe", + "adminGroups": "Groupes", + "adminLoaningGroup": "Groupe de prêt", + "adminLooking": "Recherche", + "adminManager": "Administrateur de la structure", + "adminMaximum": "Maximum", + "adminMembers": "Membres", + "adminMembershipAddingError": "Erreur lors de l'ajout (surement dû à une superposition de dates)", + "adminMemberships": "Adhésions", + "adminMembershipUpdatingError": "Erreur lors de la modification (surement dû à une superposition de dates)", + "adminMinimum": "Minimum", + "adminModifyModuleVisibility": "Visibilité des modules", + "adminMyEclPay": "MyECLPay", + "adminName": "Nom", + "adminNoManager": "Aucun manager n'est sélectionné", + "adminNoMember": "Aucun membre", + "adminNoMoreLoaner": "Aucun prêteur n'est disponible", + "adminNoSchool": "Sans école", + "adminRemoveGroupMember": "Supprimer le membre du groupe ?", + "adminResearch": "Recherche", + "adminSchools": "Écoles", + "adminStructures": "Structures", + "adminStartDate": "Date de début", + "adminStartDateMaximal": "Date de début maximale", + "adminStartDateMinimal": "Date de début minimale", + "adminUpdatedAssociationMembership": "Adhésion modifiée", + "adminUpdatedGroup": "Groupe modifié", + "adminUpdatedMembership": "Adhésion modifiée", + "adminUpdatingError": "Erreur lors de la modification", + "adminUser": "Utilisateur", + "adminValidateFilters": "Valider les filtres", + "adminVisibilities": "Visibilités", + "advertAdd": "Ajouter", + "advertAddedAdvert": "Annonce publiée", + "advertAddedAnnouncer": "Annonceur ajouté", + "advertAddingError": "Erreur lors de l'ajout", + "advertAdmin": "Admin", + "advertAdvert": "Annonce", + "advertChoosingAnnouncer": "Veuillez choisir un annonceur", + "advertChoosingPoster": "Veuillez choisir une image", + "advertContent": "Contenu", + "advertDeleteAdvert": "Supprimer l'annonce ?", + "advertDeleteAnnouncer": "Supprimer l'annonceur ?", + "advertDeleting": "Suppression", + "advertEdit": "Modifier", + "advertEditedAdvert": "Annonce modifiée", + "advertEditingError": "Erreur lors de la modification", + "advertGroupAdvert": "Groupe", + "advertIncorrectOrMissingFields": "Champs incorrects ou manquants", + "advertInvalidNumber": "Veuillez entrer un nombre", + "advertManagement": "Gestion", + "advertModifyAnnouncingGroup": "Modifier un groupe d'annonce", + "advertNoMoreAnnouncer": "Aucun annonceur n'est disponible", + "advertNoValue": "Veuillez entrer une valeur", + "advertPositiveNumber": "Veuillez entrer un nombre positif", + "advertRemovedAnnouncer": "Annonceur supprimé", + "advertRemovingError": "Erreur lors de la suppression", + "advertTags": "Tags", + "advertTitle": "Titre", + "advertMonthJan": "Janv", + "advertMonthFeb": "Févr.", + "advertMonthMar": "Mars", + "advertMonthApr": "Avr.", + "advertMonthMay": "Mai", + "advertMonthJun": "Juin", + "advertMonthJul": "Juill.", + "advertMonthAug": "Août", + "advertMonthSep": "Sept.", + "advertMonthOct": "Oct.", + "advertMonthNov": "Nov.", + "advertMonthDec": "Déc.", + "amapAccounts": "Comptes", + "amapAdd": "Ajouter", + "amapAddDelivery": "Ajouter une livraison", + "amapAddedCommand": "Commande ajoutée", + "amapAddedOrder": "Commande ajoutée", + "amapAddedProduct": "Produit ajouté", + "amapAddedUser": "Utilisateur ajouté", + "amapAddProduct": "Ajouter un produit", + "amapAddUser": "Ajouter un utilisateur", + "amapAddingACommand": "Ajouter une commande", + "amapAddingCommand": "Ajouter la commande", + "amapAddingError": "Erreur lors de l'ajout", + "amapAddingProduct": "Ajouter un produit", + "amapAddOrder": "Ajouter une commande", + "amapAdmin": "Admin", + "amapAlreadyExistCommand": "Il existe déjà une commande à cette date", + "amapAmap": "Amap", + "amapAmount": "Solde", + "amapArchive": "Archiver", + "amapArchiveDelivery": "Archiver", + "amapArchivingDelivery": "Archivage de la livraison", + "amapCategory": "Catégorie", + "amapCloseDelivery": "Verrouiller", + "amapCommandDate": "Date de la commande", + "amapCommandProducts": "Produits de la commande", + "amapConfirm": "Confirmer", + "amapContact": "Contacts associatifs ", + "amapCreateCategory": "Créer une catégorie", + "amapDelete": "Supprimer", + "amapDeleteDelivery": "Supprimer la livraison ?", + "amapDeleteDeliveryDescription": "Voulez-vous vraiment supprimer cette livraison ?", + "amapDeletedDelivery": "Livraison supprimée", + "amapDeletedOrder": "Commande supprimée", + "amapDeletedProduct": "Produit supprimé", + "amapDeleteProduct": "Supprimer le produit ?", + "amapDeleteProductDescription": "Voulez-vous vraiment supprimer ce produit ?", + "amapDeleting": "Suppression", + "amapDeletingDelivery": "Supprimer la livraison ?", + "amapDeletingError": "Erreur lors de la suppression", + "amapDeletingOrder": "Supprimer la commande ?", + "amapDeletingProduct": "Supprimer le produit ?", + "amapDeliver": "Livraison teminée ?", + "amapDeliveries": "Livraisons", + "amapDeliveringDelivery": "Toutes les commandes sont livrées ?", + "amapDelivery": "Livraison", + "amapDeliveryArchived": "Livraison archivée", + "amapDeliveryDate": "Date de livraison", + "amapDeliveryDelivered": "Livraison effectuée", + "amapDeliveryHistory": "Historique des livraisons", + "amapDeliveryList": "Liste des livraisons", + "amapDeliveryLocked": "Livraison verrouillée", + "amapDeliveryOn": "Livraison le", + "amapDeliveryOpened": "Livraison ouverte", + "amapDeliveryNotArchived": "Livraison non archivée", + "amapDeliveryNotLocked": "Livraison non verrouillée", + "amapDeliveryNotDelivered": "Livraison non effectuée", + "amapDeliveryNotOpened": "Livraison non ouverte", + "amapEditDelivery": "Modifier la livraison", + "amapEditedCommand": "Commande modifiée", + "amapEditingError": "Erreur lors de la modification", + "amapEditProduct": "Modifier le produit", + "amapEndingDelivery": "Fin de la livraison", + "amapError": "Erreur", + "amapErrorLink": "Erreur lors de l'ouverture du lien", + "amapErrorLoadingUser": "Erreur lors du chargement des utilisateurs", + "amapEvening": "Soir", + "amapExpectingNumber": "Veuillez entrer un nombre", + "amapFillField": "Veuillez remplir ce champ", + "amapHandlingAccount": "Gérer les comptes", + "amapLoading": "Chargement...", + "amapLoadingError": "Erreur lors du chargement", + "amapLock": "Verrouiller", + "amapLocked": "Verrouillée", + "amapLockedDelivery": "Livraison verrouillée", + "amapLockedOrder": "Commande verrouillée", + "amapLooking": "Rechercher", + "amapLockingDelivery": "Verrouiller la livraison ?", + "amapMidDay": "Midi", + "amapMyOrders": "Mes commandes", + "amapName": "Nom", + "amapNextStep": "Étape suivante", + "amapNoProduct": "Pas de produit", + "amapNoCurrentOrder": "Pas de commande en cours", + "amapNoMoney": "Pas assez d'argent", + "amapNoOpennedDelivery": "Pas de livraison ouverte", + "amapNoOrder": "Pas de commande", + "amapNoSelectedDelivery": "Pas de livraison sélectionnée", + "amapNotEnoughMoney": "Pas assez d'argent", + "amapNotPlannedDelivery": "Pas de livraison planifiée", + "amapOneOrder": "commande", + "amapOpenDelivery": "Ouvrir", + "amapOpened": "Ouverte", + "amapOpenningDelivery": "Ouvrir la livraison ?", + "amapOrder": "Commander", + "amapOrders": "Commandes", + "amapPickChooseCategory": "Veuillez entrer une valeur ou choisir une catégorie existante", + "amapPickDeliveryMoment": "Choisissez un moment de livraison", + "amapPresentation": "Présentation", + "amapPresentation1": "L'AMAP (association pour le maintien d'une agriculture paysanne) est un service proposé par l'association Planet&Co de l'ECL. Vous pouvez ainsi recevoir des produits (paniers de fruits et légumes, jus, confitures...) directement sur le campus !\n\nLes commandes doivent être passées avant le vendredi 21h et sont livrées sur le campus le mardi de 13h à 13h45 (ou de 18h15 à 18h30 si vous ne pouvez pas passer le midi) dans le hall du M16.\n\nVous ne pouvez commander que si votre solde le permet. Vous pouvez recharger votre solde via la collecte Lydia ou bien avec un chèque que vous pouvez nous transmettre lors des permanences.\n\nLien vers la collecte Lydia pour le rechargement : ", + "amapPresentation2": "\n\nN'hésitez pas à nous contacter en cas de problème !", + "amapPrice": "Prix", + "amapProduct": "produit", + "amapProducts": "Produits", + "amapProductInDelivery": "Produit dans une livraison non terminée", + "amapQuantity": "Quantité", + "amapRequiredDate": "La date est requise", + "amapSeeMore": "Voir plus", + "amapThe": "Le", + "amapUnlock": "Dévérouiller", + "amapUnlockedDelivery": "Livraison dévérouillée", + "amapUnlockingDelivery": "Dévérouiller la livraison ?", + "amapUpdate": "Modifier", + "amapUpdatedAmount": "Solde modifié", + "amapUpdatedOrder": "Commande modifiée", + "amapUpdatedProduct": "Produit modifié", + "amapUpdatingError": "Echec de la modification", + "amapUsersNotFound": "Aucun utilisateur trouvé", + "amapWaiting": "En attente", + "bookingAdd": "Ajouter", + "bookingAddBookingPage": "Demande", + "bookingAddRoom": "Ajouter une salle", + "bookingAddBooking": "Ajouter une réservation", + "bookingAddedBooking": "Demande ajoutée", + "bookingAddedRoom": "Salle ajoutée", + "bookingAddedManager": "Gestionnaire ajouté", + "bookingAddingError": "Erreur lors de l'ajout", + "bookingAddManager": "Ajouter un gestionnaire", + "bookingAdminPage": "Administrateur", + "bookingAllDay": "Toute la journée", + "bookingBookedFor": "Réservé pour", + "bookingBooking": "Réservation", + "bookingBookingCreated": "Réservation créée", + "bookingBookingDemand": "Demande de réservation", + "bookingBookingNote": "Note de la réservation", + "bookingBookingPage": "Réservation", + "bookingBookingReason": "Motif de la réservation", + "bookingBy": "par", + "bookingConfirm": "Confirmer", + "bookingConfirmation": "Confirmation", + "bookingConfirmBooking": "Confirmer la réservation ?", + "bookingConfirmed": "Validée", + "bookingDates": "Dates", + "bookingDecline": "Refuser", + "bookingDeclineBooking": "Refuser la réservation ?", + "bookingDeclined": "Refusée", + "bookingDelete": "Supprimer", + "bookingDeleting": "Suppression", + "bookingDeleteBooking": "Suppression", + "bookingDeleteBookingConfirmation": "Êtes-vous sûr de vouloir supprimer cette réservation ?", + "bookingDeletedBooking": "Réservation supprimée", + "bookingDeletedRoom": "Salle supprimée", + "bookingDeletedManager": "Gestionnaire supprimé", + "bookingDeleteRoomConfirmation": "Êtes-vous sûr de vouloir supprimer cette salle ?\n\nLa salle ne doit avoir aucune réservation en cours ou à venir pour être supprimée", + "bookingDeleteManagerConfirmation": "Êtes-vous sûr de vouloir supprimer ce gestionnaire ?\n\nLe gestionnaire ne doit être associé à aucune salle pour pouvoir être supprimé", + "bookingDeletingBooking": "Supprimer la réservation ?", + "bookingDeletingError": "Erreur lors de la suppression", + "bookingDeletingRoom": "Supprimer la salle ?", + "bookingEdit": "Modifier", + "bookingEditBooking": "Modifier une réservation", + "bookingEditionError": "Erreur lors de la modification", + "bookingEditedBooking": "Réservation modifiée", + "bookingEditedRoom": "Salle modifiée", + "bookingEditedManager": "Gestionnaire modifié", + "bookingEditManager": "Modifier ou supprimer un gestionnaire", + "bookingEditRoom": "Modifier ou supprimer une salle", + "bookingEndDate": "Date de fin", + "bookingEndHour": "Heure de fin", + "bookingEntity": "Pour qui ?", + "bookingError": "Erreur", + "bookingEventEvery": "Tous les", + "bookingHistoryPage": "Historique", + "bookingIncorrectOrMissingFields": "Champs incorrects ou manquants", + "bookingInterval": "Intervalle", + "bookingInvalidIntervalError": "Intervalle invalide", + "bookingInvalidDates": "Dates invalides", + "bookingInvalidRoom": "Salle invalide", + "bookingKeysRequested": "Clés demandées", + "bookingManagement": "Gestion", + "bookingManager": "Gestionnaire", + "bookingManagerName": "Nom du gestionnaire", + "bookingMultipleDay": "Plusieurs jours", + "bookingMyBookings": "Mes réservations", + "bookingNecessaryKey": "Clé nécessaire", + "bookingNext": "Suivant", + "bookingNo": "Non", + "bookingNoCurrentBooking": "Pas de réservation en cours", + "bookingNoDateError": "Veuillez choisir une date", + "bookingNoAppointmentInReccurence": "Aucun créneau existe avec ces paramètres de récurrence", + "bookingNoDaySelected": "Aucun jour sélectionné", + "bookingNoDescriptionError": "Veuillez entrer une description", + "bookingNoKeys": "Aucune clé", + "bookingNoNoteError": "Veuillez entrer une note", + "bookingNoPhoneRegistered": "Numéro non renseigné", + "bookingNoReasonError": "Veuillez entrer un motif", + "bookingNoRoomFoundError": "Aucune salle enregistrée", + "bookingNoRoomFound": "Aucune salle trouvée", + "bookingNote": "Note", + "bookingOther": "Autre", + "bookingPending": "En attente", + "bookingPrevious": "Précédent", + "bookingReason": "Motif", + "bookingRecurrence": "Récurrence", + "bookingRecurrenceDays": "Jours de récurrence", + "bookingRecurrenceEndDate": "Date de fin de récurrence", + "bookingRecurrent": "Récurrent", + "bookingRegisteredRooms": "Salles enregistrées", + "bookingRoom": "Salle", + "bookingRoomName": "Nom de la salle", + "bookingStartDate": "Date de début", + "bookingStartHour": "Heure de début", + "bookingWeeks": "Semaines", + "bookingYes": "Oui", + "bookingWeekDayMon": "Lundi", + "bookingWeekDayTue": "Mardi", + "bookingWeekDayWed": "Mercredi", + "bookingWeekDayThu": "Jeudi", + "bookingWeekDayFri": "Vendredi", + "bookingWeekDaySat": "Samedi", + "bookingWeekDaySun": "Dimanche", + "cinemaAdd": "Ajouter", + "cinemaAddedSession": "Séance ajoutée", + "cinemaAddingError": "Erreur lors de l'ajout", + "cinemaAddSession": "Ajouter une séance", + "cinemaCinema": "Cinéma", + "cinemaDeleteSession": "Supprimer la séance ?", + "cinemaDeleting": "Suppression", + "cinemaDuration": "Durée", + "cinemaEdit": "Modifier", + "cinemaEditedSession": "Séance modifiée", + "cinemaEditingError": "Erreur lors de la modification", + "cinemaEditSession": "Modifier la séance", + "cinemaEmptyUrl": "Veuillez entrer une URL", + "cinemaImportFromTMDB": "Importer depuis TMDB", + "cinemaIncomingSession": "A l'affiche", + "cinemaIncorrectOrMissingFields": "Champs incorrects ou manquants", + "cinemaInvalidUrl": "URL invalide", + "cinemaGenre": "Genre", + "cinemaName": "Nom", + "cinemaNoDateError": "Veuillez entrer une date", + "cinemaNoDuration": "Veuillez entrer une durée", + "cinemaNoOverview": "Aucun synopsis", + "cinemaNoPoster": "Aucune affiche", + "cinemaNoSession": "Aucune séance", + "cinemaOverview": "Synopsis", + "cinemaPosterUrl": "URL de l'affiche", + "cinemaSessionDate": "Jour de la séance", + "cinemaStartHour": "Heure de début", + "cinemaTagline": "Slogan", + "cinemaThe": "Le", + "drawerAdmin": "Administration", + "drawerAndroidAppLink": "https://play.google.com/store/apps/details?id=fr.myecl.titan", + "drawerCopied": "Copié !", + "drawerDownloadAppOnMobileDevice": "Ce site est la version Web de l'application MyECL. Nous vous invitons à télécharger l'application. N'utilisez ce site qu'en cas de problème avec l'application.\n", + "drawerIosAppLink": "https://apps.apple.com/fr/app/myecl/id6444443430", + "drawerLoginOut": "Voulez-vous vous déconnecter ?", + "drawerLogOut": "Déconnexion", + "drawerOr": " ou ", + "drawerSettings": "Paramètres", + "eventAdd": "Ajouter", + "eventAddEvent": "Ajouter un événement", + "eventAddedEvent": "Événement ajouté", + "eventAddingError": "Erreur lors de l'ajout", + "eventAllDay": "Toute la journée", + "eventConfirm": "Confirmer", + "eventConfirmEvent": "Confirmer l'événement ?", + "eventConfirmation": "Confirmation", + "eventConfirmed": "Confirmé", + "eventDates": "Dates", + "eventDecline": "Refuser", + "eventDeclineEvent": "Refuser l'événement ?", + "eventDeclined": "Refusé", + "eventDelete": "Supprimer", + "eventDeletedEvent": "Événement supprimé", + "eventDeleting": "Suppression", + "eventDeletingError": "Erreur lors de la suppression", + "eventDeletingEvent": "Supprimer l'événement ?", + "eventDescription": "Description", + "eventEdit": "Modifier", + "eventEditEvent": "Modifier un événement", + "eventEditedEvent": "Événement modifié", + "eventEditingError": "Erreur lors de la modification", + "eventEndDate": "Date de fin", + "eventEndHour": "Heure de fin", + "eventError": "Erreur", + "eventEventList": "Liste des événements", + "eventEventType": "Type d'événement", + "eventEvery": "Tous les", + "eventHistory": "Historique", + "eventIncorrectOrMissingFields": "Certains champs sont incorrects ou manquants", + "eventInterval": "Intervalle", + "eventInvalidDates": "La date de fin doit être après la date de début", + "eventInvalidIntervalError": "Veuillez entrer un intervalle valide", + "eventLocation": "Lieu", + "eventMyEvents": "Mes événements", + "eventName": "Nom", + "eventNext": "Suivant", + "eventNo": "Non", + "eventNoCurrentEvent": "Aucun événement en cours", + "eventNoDateError": "Veuillez entrer une date", + "eventNoDaySelected": "Aucun jour sélectionné", + "eventNoDescriptionError": "Veuillez entrer une description", + "eventNoEvent": "Aucun événement", + "eventNoNameError": "Veuillez entrer un nom", + "eventNoOrganizerError": "Veuillez entrer un organisateur", + "eventNoPlaceError": "Veuillez entrer un lieu", + "eventNoPhoneRegistered": "Numéro non renseigné", + "eventNoRuleError": "Veuillez entrer une règle de récurrence", + "eventOrganizer": "Organisateur", + "eventOther": "Autre", + "eventPending": "En attente", + "eventPrevious": "Précédent", + "eventRecurrence": "Récurrence", + "eventRecurrenceDays": "Jours de récurrence", + "eventRecurrenceEndDate": "Date de fin de la récurrence", + "eventRecurrenceRule": "Règle de récurrence", + "eventRoom": "Salle", + "eventStartDate": "Date de début", + "eventStartHour": "Heure de début", + "eventTitle": "Événements", + "eventYes": "Oui", + "eventEventEvery": "Toutes les", + "eventWeeks": "semaines", + "eventDayMon": "Lundi", + "eventDayTue": "Mardi", + "eventDayWed": "Mercredi", + "eventDayThu": "Jeudi", + "eventDayFri": "Vendredi", + "eventDaySat": "Samedi", + "eventDaySun": "Dimanche", + "homeCalendar": "Calendrier", + "homeEventOf": "Évènements du", + "homeIncomingEvents": "Évènements à venir", + "homeLastInfos": "Dernières annonces", + "homeNoEvents": "Aucun évènement", + "homeTranslateDayShortMon": "Lun", + "homeTranslateDayShortTue": "Mar", + "homeTranslateDayShortWed": "Mer", + "homeTranslateDayShortThu": "Jeu", + "homeTranslateDayShortFri": "Ven", + "homeTranslateDayShortSat": "Sam", + "homeTranslateDayShortSun": "Dim", + "loanAdd": "Ajouter", + "loanAddLoan": "Ajouter un prêt", + "loanAddObject": "Ajouter un objet", + "loanAddedLoan": "Prêt ajouté", + "loanAddedObject": "Objet ajouté", + "loanAddedRoom": "Salle ajoutée", + "loanAddingError": "Erreur lors de l'ajout", + "loanAdmin": "Administrateur", + "loanAvailable": "Disponible", + "loanAvailableMultiple": "Disponibles", + "loanBorrowed": "Emprunté", + "loanBorrowedMultiple": "Empruntés", + "loanAnd": "et", + "loanAssociation": "Association", + "loanAvailableItems": "Objets disponibles", + "loanBeginDate": "Date du début du prêt", + "loanBorrower": "Emprunteur", + "loanCaution": "Caution", + "loanCancel": "Annuler", + "loanConfirm": "Confirmer", + "loanConfirmation": "Confirmation", + "loanDates": "Dates", + "loanDays": "Jours", + "loanDelay": "Délai de la prolongation", + "loanDelete": "Supprimer", + "loanDeletingLoan": "Supprimer le prêt ?", + "loanDeletedItem": "Objet supprimé", + "loanDeletedLoan": "Prêt supprimé", + "loanDeleting": "Suppression", + "loanDeletingError": "Erreur lors de la suppression", + "loanDeletingItem": "Supprimer l'objet ?", + "loanDuration": "Durée", + "loanEdit": "Modifier", + "loanEditItem": "Modifier l'objet", + "loanEditLoan": "Modifier le prêt", + "loanEditedRoom": "Salle modifiée", + "loanEndDate": "Date de fin du prêt", + "loanEnded": "Terminé", + "loanEnterDate": "Veuillez entrer une date", + "loanExtendedLoan": "Prêt prolongé", + "loanExtendingError": "Erreur lors de la prolongation", + "loanHistory": "Historique", + "loanIncorrectOrMissingFields": "Des champs sont manquants ou incorrects", + "loanInvalidNumber": "Veuillez entrer un nombre", + "loanInvalidDates": "Les dates ne sont pas valides", + "loanItem": "Objet", + "loanItems": "Objets", + "loanItemHandling": "Gestion des objets", + "loanItemSelected": "objet sélectionné", + "loanItemsSelected": "objets sélectionnés", + "loanLendingDuration": "Durée possible du prêt", + "loanLoan": "Prêt", + "loanLoanHandling": "Gestion des prêts", + "loanLooking": "Rechercher", + "loanName": "Nom", + "loanNext": "Suivant", + "loanNo": "Non", + "loanNoAssociationsFounded": "Aucune association trouvée", + "loanNoAvailableItems": "Aucun objet disponible", + "loanNoBorrower": "Aucun emprunteur", + "loanNoItems": "Aucun objet", + "loanNoItemSelected": "Aucun objet sélectionné", + "loanNoLoan": "Aucun prêt", + "loanNoReturnedDate": "Pas de date de retour", + "loanQuantity": "Quantité", + "loanNone": "Aucun", + "loanNote": "Note", + "loanNoValue": "Veuillez entrer une valeur", + "loanOnGoing": "En cours", + "loanOnGoingLoan": "Prêt en cours", + "loanOthers": "autres", + "loanPaidCaution": "Caution payée", + "loanPositiveNumber": "Veuillez entrer un nombre positif", + "loanPrevious": "Précédent", + "loanReturned": "Rendu", + "loanReturnedLoan": "Prêt rendu", + "loanReturningError": "Erreur lors du retour", + "loanReturningLoan": "Retour", + "loanReturnLoan": "Rendre le prêt ?", + "loanReturnLoanDescription": "Voulez-vous rendre ce prêt ?", + "loanToReturn": "A rendre", + "loanUnavailable": "Indisponible", + "loanUpdate": "Modifier", + "loanUpdatedItem": "Objet modifié", + "loanUpdatedLoan": "Prêt modifié", + "loanUpdatingError": "Erreur lors de la modification", + "loanYes": "Oui", + "loginAccountActivated": "Compte activé", + "loginAccountNotActivated": "Compte non activé", + "loginActivationCode": "Code d'activation", + "loginBirthday": "Date de naissance", + "loginCanBeEmpty": "Ce champ peut être vide", + "loginConfirmPassword": "Confirmer le mot de passe", + "loginCreate": "Créer", + "loginCreateAccount": "Créer un compte", + "loginCreateAccountTitle": "Créer un\ncompte", + "loginEmail": "Email", + "loginEmailEmpty": "Veuillez entrer une adresse mail", + "loginEmailInvalid": "Veuillez entrer une adresse mail de centrale.\nSi vous n'en possédez pas, veuillez contacter Éclair", + "loginEmptyFieldError": "Ce champ ne peut pas être vide", + "loginEndActivation": "Finaliser l'activation", + "loginEndResetPassword": "Finaliser la \nréinitialisation", + "loginErrorResetPassword": "Erreur lors de la réinitialisation", + "loginExpectingDate": "Une date est attendue", + "loginFillAllFields": "Veuillez remplir tous les champs", + "loginFirstname": "Prénom", + "loginFloor": "Étage", + "loginForgetPassword": "Mot de passe\noublié", + "loginForgotPassword": "Mot de passe oublié ?", + "loginInvalidToken": "Code d'activation invalide", + "loginLoginFailed": "Échec de la connexion", + "loginMailSendingError": "Erreur lors de la création du compte", + "loginMustBeIntError": "Ce champ doit être un entier", + "loginName": "Nom", + "loginNewPassword": "Nouveau mot de passe", + "loginPassword": "Mot de passe", + "loginPasswordLengthError": "Le mot de passe doit faire au moins 6 caractères", + "loginPasswordUppercaseError": "Le mot de passe doit contenir au moins une majuscule", + "loginPasswordLowercaseError": "Le mot de passe doit contenir au moins une minucule", + "loginPasswordNumberError": "Le mot de passe doit contenir au moins un chiffre", + "loginPasswordSpecialCaracterError": "Le mot de passe doit contenir au moins un caractère spécial", + "loginPasswordMustMatch": "Les mots de passe doivent correspondre", + "loginPasswordStrengthVeryWeak": "Très faible", + "loginPasswordStrengthWeak": "Faible", + "loginPasswordStrengthMedium": "Moyen", + "loginPasswordStrengthStrong": "Fort", + "loginPasswordStrengthVeryStrong": "Très fort", + "loginPhone": "Téléphone", + "loginPromo": "Promo entrante (ex : 2023)", + "loginSendedMail": "Mail de confirmation envoyé", + "loginSendedResetMail": "Mail de réinitialisation envoyé", + "loginSignIn": "Se connecter", + "loginRegister": "S'inscrire", + "loginRecievedMail": "J'ai reçu le mail", + "loginRecover": "Réinitialiser", + "loginResetedPassword": "Mot de passe réinitialisé", + "loginResetPasswordTitle": "Réinitialiser\nle mot de \npasse", + "loginNickname": "Surnom", + "loginWelcomeBack": "Bienvenue", + "loginAppName": "MyECL", + "othersCheckInternetConnection": "Veuillez vérifier votre connexion internet", + "othersRetry": "Réessayer", + "othersTooOldVersion": "Votre version de l'application est trop ancienne.\n\nVeuillez mettre à jour l'application.", + "othersUnableToConnectToServer": "Impossible de se connecter au serveur", + "othersVersion": "Version", + "othersNoModule": "Aucun module disponible, veuillez réessayer ultérieurement 😢😢", + "othersAdmin": "Admin", + "othersError": "Une erreur est survenue", + "othersNoValue": "Veuillez entrer une valeur", + "othersInvalidNumber": "Veuillez entrer un nombre", + "othersNoDateError": "Veuillez entrer une date", + "othersImageSizeTooBig": "La taille de l'image ne doit pas dépasser 4 Mio", + "othersImageError": "Erreur lors de l'ajout de l'image", + "phAddNewJournal": "Ajouter un nouveau journal", + "phNameField": "Nom : ", + "phDateField": "Date : ", + "phDelete": "Voulez-vous vraiment supprimer ce journal ?", + "phIrreversibleAction": "Cette action est irréversible", + "phToHeavyFile": "Fichier trop volumineux", + "phAddPdfFile": "Ajouter un fichier PDF", + "phEditPdfFile": "Modifier le fichier PDF", + "phPhName": "Nom du PH", + "phDate": "Date", + "phAdded": "Ajouté", + "phEdited": "Modifié", + "phAddingFileError": "Erreur d'ajout", + "phMissingInformatonsOrPdf": "Informations manquantes ou fichier PDF manquant", + "phAdd": "Ajouter", + "phEdit": "Modifier", + "phSeePreviousJournal": "Voir les anciens journaux", + "phNoJournalInDatabase": "Pas encore de PH dans la base de donnée", + "phSuccesDowloading": "Téléchargé avec succès", + "phonebookActiveMandate": "Mandat actif :", + "phonebookAdd": "Ajouter", + "phonebookAddAssociation": "Ajouter une association", + "phonebookAddedAssociation": "Association ajoutée", + "phonebookAddedMember": "Membre ajouté", + "phonebookAddingError": "Erreur lors de l'ajout", + "phonebookAddMember": "Ajouter un membre", + "phonebookAddRole": "Ajouter un rôle", + "phonebookAdmin": "Admin", + "phonebookAdminPage": "Page Administrateur", + "phonebookAll": "Toutes", + "phonebookApparentName": "Nom public du rôle :", + "phonebookAssociation": "Association :", + "phonebookAssociationDetail": "Détail de l'association :", + "phonebookAssociationKind": "Type d'association :", + "phonebookAssociationPure": "Association", + "phonebookAssociationPureSearch": " Association", + "phonebookAssociations": "Associations :", + "phonebookCancel": "Annuler", + "phonebookChangeMandate": "Passer au mandat ", + "phonebookChangeMandateConfirm": "Êtes-vous sûr de vouloir changer tout le mandat ?\nCette action est irréversible !", + "phonebookCopied": "Copié dans le presse-papier", + "phonebookDeactivateAssociation": "Êtes-vous sûr de vouloir désactiver cette association ?\nCette action est irréversible !", + "phonebookDeactivatedAssociation": "Association désactivée", + "phonebookDeactivatedAssociationWarning": "Attention, cette association est désactivée, vous ne pouvez pas la modifier", + "phonebookDeactivating": "Désactiver l'association ?", + "phonebookDeactivatingError": "Erreur lors de la désactivation", + "phonebookDetail": "Détail :", + "phonebookDeleteAssociation": "Supprimer l'association ?\nCela va effacer tout l'historique de l'association", + "phonebookDeletedAssociation": "Association supprimée", + "phonebookDeletedMember": "Membre supprimé", + "phonebookDeleting": "Suppression", + "phonebookDeletingError": "Erreur lors de la suppression", + "phonebookDescription": "Description", + "phonebookEdit": "Modifier", + "phonebookEditMembership": "Modifier le rôle", + "phonebookEmail": "Email :", + "phonebookEmailCopied": "Email copié dans le presse-papier", + "phonebookEmptyApparentName": "Veuillez entrer un nom de role", + "phonebookEmptyFieldError": "Un champ n'est pas rempli", + "phonebookEmptyKindError": "Veuillez choisir un type d'association", + "phonebookEmptyMember": "Aucun membre sélectionné", + "phonebookErrorAssociationLoading": "Erreur lors du chargement de l'association", + "phonebookErrorAssociationNameEmpty": "Veuillez entrer un nom d'association", + "phonebookErrorAssociationPicture": "Erreur lors de la modification de la photo d'association", + "phonebookErrorKindsLoading": "Erreur lors du chargement des types d'association", + "phonebookErrorLoadAssociationList": "Erreur lors du chargement de la liste des associations", + "phonebookErrorLoadAssociationMember": "Erreur lors du chargement des membres de l'association", + "phonebookErrorLoadAssociationPicture": "Erreur lors du chargement de la photo d'association", + "phonebookErrorLoadProfilePicture": "Erreur", + "phonebookErrorRoleTagsLoading": "Erreur lors du chargement des tags de rôle", + "phonebookExistingMembership": "Ce membre est déjà dans le mandat actuel", + "phonebookFirstname": "Prénom :", + "phonebookGroups": "Groupes associés :", + "phonebookMandateChangingError": "Erreur lors du changement de mandat", + "phonebookMember": "Membre", + "phonebookMemberReordered": "Membre réordonné", + "phonebookMembers": "Membres", + "phonebookMembershipAssociationError": "Veuillez choisir une association", + "phonebookMembershipRole": "Rôle :", + "phonebookMembershipRoleError": "Veuillez choisir un rôle", + "phonebookName": "Nom :", + "phonebookNameCopied": "Nom et prénom copié dans le presse-papier", + "phonebookNamePure": "Nom", + "phonebookNewMandate": "Nouveau mandat", + "phonebookNewMandateConfirmed": "Mandat changé", + "phonebookNickname": "Surnom :", + "phonebookNicknameCopied": "Surnom copié dans le presse-papier", + "phonebookNoAssociationFound": "Aucune association trouvée", + "phonebookNoMember": "Aucun membre", + "phonebookNoMemberRole": "Aucun role trouvé", + "phonebookPhone": "Téléphone :", + "phonebookPhonebook": "Annuaire", + "phonebookPhonebookSearch": "Rechercher", + "phonebookPhonebookSearchAssociation": "Association", + "phonebookPhonebookSearchField": "Rechercher :", + "phonebookPhonebookSearchName": "Nom/Prénom/Surnom", + "phonebookPhonebookSearchRole": "Poste", + "phonebookPresidentRoleTag": "Prez'", + "phonebookPromoNotGiven": "Promo non renseignée", + "phonebookPromotion": "Promotion :", + "phonebookReorderingError": "Erreur lors du réordonnement", + "phonebookResearch": "Rechercher", + "phonebookRolePure": "Rôle", + "phonebookTooHeavyAssociationPicture": "L'image est trop lourde (max 4Mo)", + "phonebookUpdateGroups": "Mettre à jour les groupes", + "phonebookUpdatedAssociation": "Association modifiée", + "phonebookUpdatedAssociationPicture": "La photo d'association a été changée", + "phonebookUpdatedGroups": "Groupes mis à jour", + "phonebookUpdatedMember": "Membre modifié", + "phonebookUpdatingError": "Erreur lors de la modification", + "phonebookValidation": "Valider", + "purchasesPurchases": "Achats", + "purchasesResearch": "Rechercher", + "purchasesNoPurchasesFound": "Aucun achat trouvé", + "purchasesNoTickets": "Aucun ticket", + "purchasesTicketsError": "Erreur lors du chargement des tickets", + "purchasesPurchasesError": "Erreur lors du chargement des achats", + "purchasesNoPurchases": "Aucun achat", + "purchasesTimes": "fois", + "purchasesAlreadyUsed": "Déjà utilisé", + "purchasesNotPaid": "Non validé", + "purchasesPleaseSelectProduct": "Veuillez sélectionner un produit", + "purchasesProducts": "Produits", + "purchasesCancel": "Annuler", + "purchasesValidate": "Valider", + "purchasesLeftScan": "Scans restants", + "purchasesTag": "Tag", + "purchasesHistory": "Historique", + "purchasesPleaseSelectSeller": "Veuillez sélectionner un vendeur", + "purchasesNoTagGiven": "Attention, aucun tag n'a été entré", + "purchasesTickets": "Tickets", + "purchasesNoScannableProducts": "Aucun produit scannable", + "purchasesLoading": "En attente de scan", + "purchasesScan": "Scanner", + "raffleRaffle": "Tombola", + "rafflePrize": "Lot", + "rafflePrizes": "Lots", + "raffleActualRaffles": "Tombola en cours", + "rafflePastRaffles": "Tombola passés", + "raffleYourTickets": "Tous vos tickets", + "raffleCreateMenu": "Menu de Création", + "raffleNextRaffles": "Prochaines tombolas", + "raffleNoTicket": "Vous n'avez pas de ticket", + "raffleSeeRaffleDetail": "Voir lots/tickets", + "raffleActualPrize": "Lots actuels", + "raffleMajorPrize": "Lot Majeurs", + "raffleTakeTickets": "Prendre vos tickets", + "raffleNoTicketBuyable": "Vous ne pouvez pas achetez de billets pour l'instant", + "raffleNoCurrentPrize": "Il n'y a aucun lots actuellement", + "raffleModifTombola": "Vous pouvez modifiez vos tombolas ou en créer de nouvelles, toute décision doit ensuite être prise par les admins", + "raffleCreateYourRaffle": "Votre menu de création de tombolas", + "rafflePossiblePrice": "Prix possible", + "raffleInformation": "Information et Statistiques", + "raffleAccounts": "Comptes", + "raffleAdd": "Ajouter", + "raffleUpdatedAmount": "Montant mis à jour", + "raffleUpdatingError": "Erreur lors de la mise à jour", + "raffleDeletedPrize": "Lot supprimé", + "raffleDeletingError": "Erreur lors de la suppression", + "raffleQuantity": "Quantité", + "raffleClose": "Fermer", + "raffleOpen": "Ouvrir", + "raffleAddTypeTicketSimple": "Ajouter", + "raffleAddingError": "Erreur lors de l'ajout", + "raffleEditTypeTicketSimple": "Modifier", + "raffleFillField": "Le champ ne peut pas être vide", + "raffleWaiting": "Chargement", + "raffleEditingError": "Erreur lors de la modification", + "raffleAddedTicket": "Ticket ajouté", + "raffleEditedTicket": "Ticket modifié", + "raffleAlreadyExistTicket": "Le ticket existe déjà", + "raffleNumberExpected": "Un entier est attendu", + "raffleDeletedTicket": "Ticket supprimé", + "raffleAddPrize": "Ajouter", + "raffleEditPrize": "Modifier", + "raffleOpenRaffle": "Ouvrir la tombola", + "raffleCloseRaffle": "Fermer la tombola", + "raffleOpenRaffleDescription": "Vous allez ouvrir la tombola, les utilisateurs pourront acheter des tickets. Vous ne pourrez plus modifier la tombola. Êtes-vous sûr de vouloir continuer ?", + "raffleCloseRaffleDescription": "Vous allez fermer la tombola, les utilisateurs ne pourront plus acheter de tickets. Êtes-vous sûr de vouloir continuer ?", + "raffleNoCurrentRaffle": "Il n'y a aucune tombola en cours", + "raffleBoughtTicket": "Ticket acheté", + "raffleDrawingError": "Erreur lors du tirage", + "raffleInvalidPrice": "Le prix doit être supérieur à 0", + "raffleMustBePositive": "Le nombre doit être strictement positif", + "raffleDraw": "Tirer", + "raffleDrawn": "Tiré", + "raffleError": "Erreur", + "raffleGathered": "Récolté", + "raffleTickets": "Tickets", + "raffleTicket": "ticket", + "raffleWinner": "Gagnant", + "raffleNoPrize": "Aucun lot", + "raffleDeletePrize": "Supprimer le lot", + "raffleDeletePrizeDescription": "Vous allez supprimer le lot, êtes-vous sûr de vouloir continuer ?", + "raffleDrawing": "Tirage", + "raffleDrawingDescription": "Tirer le gagnant du lot ?", + "raffleDeleteTicket": "Supprimer le ticket", + "raffleDeleteTicketDescription": "Vous allez supprimer le ticket, êtes-vous sûr de vouloir continuer ?", + "raffleWinningTickets": "Tickets gagnants", + "raffleNoWinningTicketYet": "Les tickets gagnants seront affichés ici", + "raffleName": "Nom", + "raffleDescription": "Description", + "raffleBuyThisTicket": "Acheter ce ticket", + "raffleLockedRaffle": "Tombola verrouillée", + "raffleUnavailableRaffle": "Tombola indisponible", + "raffleNotEnoughMoney": "Vous n'avez pas assez d'argent", + "raffleWinnable": "gagnable", + "raffleNoDescription": "Aucune description", + "raffleAmount": "Solde", + "raffleLoading": "Chargement", + "raffleTicketNumber": "Nombre de ticket", + "rafflePrice": "Prix", + "raffleEditRaffle": "Modifier la tombola", + "raffleEdit": "Modifier", + "raffleAddPackTicket": "Ajouter un pack de ticket", + "recommendationRecommendation": "Bons plans", + "recommendationTitle": "Titre", + "recommendationLogo": "Logo", + "recommendationCode": "Code", + "recommendationSummary": "Court résumé", + "recommendationDescription": "Description", + "recommendationAdd": "Ajouter", + "recommendationEdit": "Modifier", + "recommendationDelete": "Supprimer", + "recommendationAddImage": "Veuillez ajouter une image", + "recommendationAddedRecommendation": "Bon plan ajouté", + "recommendationEditedRecommendation": "Bon plan modifié", + "recommendationDeleteRecommendationConfirmation": "Êtes-vous sûr de vouloir supprimer ce bon plan ?", + "recommendationDeleteRecommendation": "Suppresion", + "recommendationDeletingRecommendationError": "Erreur lors de la suppression", + "recommendationDeletedRecommendation": "Bon plan supprimé", + "recommendationIncorrectOrMissingFields": "Champs incorrects ou manquants", + "recommendationEditingError": "Échec de la modification", + "recommendationAddingError": "Échec de l'ajout", + "recommendationCopiedCode": "Code de réduction copié", + "seedLibraryAdd": "Ajouter", + "seedLibraryAddedPlant": "Plante ajoutée", + "seedLibraryAddedSpecies": "Espèce ajoutée", + "seedLibraryAddingError": "Erreur lors de l'ajout", + "seedLibraryAddPlant": "Déposer une plante", + "seedLibraryAddSpecies": "Ajouter une espèce", + "seedLibraryAll": "Toutes", + "seedLibraryAncestor": "Ancêtre", + "seedLibraryAround": "environ", + "seedLibraryAutumn": "Automne", + "seedLibraryBorrowedPlant": "Plante empruntée", + "seedLibraryBorrowingDate": "Date d'emprunt :", + "seedLibraryBorrowPlant": "Emprunter la plante", + "seedLibraryCard": "Carte", + "seedLibraryChoosingAncestor": "Veuillez choisir un ancêtre", + "seedLibraryChoosingSpecies": "Veuillez choisir une espèce", + "seedLibraryChoosingSpeciesOrAncestor": "Veuillez choisir une espèce ou un ancêtre", + "seedLibraryContact": "Contact :", + "seedLibraryDays": "jours", + "seedLibraryDeadMsg": "Voulez-vous déclarer la plante morte ?", + "seedLibraryDeadPlant": "Plante morte", + "seedLibraryDeathDate": "Date de mort", + "seedLibraryDeletedSpecies": "Espèce supprimée", + "seedLibraryDeleteSpecies": "Supprimer l'espèce ?", + "seedLibraryDeleting": "Suppression", + "seedLibraryDeletingError": "Erreur lors de la suppression", + "seedLibraryDepositNotAvailable": "Le dépôt de plantes n'est pas possible sans emprunter une plante au préalable", + "seedLibraryDescription": "Description", + "seedLibraryDifficulty": "Difficulté :", + "seedLibraryEdit": "Modifier", + "seedLibraryEditedPlant": "Plante modifiée", + "seedLibraryEditInformation": "Modifier les informations", + "seedLibraryEditingError": "Erreur lors de la modification", + "seedLibraryEditSpecies": "Modifier l'espèce", + "seedLibraryEmptyDifficultyError": "Veuillez choisir une difficulté", + "seedLibraryEmptyFieldError": "Veuillez remplir tous les champs", + "seedLibraryEmptyTypeError": "Veuillez choisir un type de plante", + "seedLibraryEndMonth": "Mois de fin :", + "seedLibraryFacebookUrl": "Lien Facebook", + "seedLibraryFilters": "Filtres", + "seedLibraryForum": "Oskour maman j'ai tué ma plante - Forum d'aide", + "seedLibraryForumUrl": "Lien Forum", + "seedLibraryHelpSheets": "Fiches sur les plantes", + "seedLibraryInformation": "Informations :", + "seedLibraryMaturationTime": "Temps de maturation", + "seedLibraryMonthJan": "Janvier", + "seedLibraryMonthFeb": "Février", + "seedLibraryMonthMar": "Mars", + "seedLibraryMonthApr": "Avril", + "seedLibraryMonthMay": "Mai", + "seedLibraryMonthJun": "Juin", + "seedLibraryMonthJul": "Juillet", + "seedLibraryMonthAug": "Août", + "seedLibraryMonthSep": "Septembre", + "seedLibraryMonthOct": "Octobre", + "seedLibraryMonthNov": "Novembre", + "seedLibraryMonthDec": "Décembre", + "seedLibraryMyPlants": "Mes plantes", + "seedLibraryName": "Nom", + "seedLibraryNbSeedsRecommended": "Nombre de graines recommandées", + "seedLibraryNbSeedsRecommendedError": "Veuillez entrer un nombre de graines recommandé supérieur à 0", + "seedLibraryNoDateError": "Veuillez entrer une date", + "seedLibraryNoFilteredPlants": "Aucune plante ne correspond à votre recherche. Essayez d'autres filtres.", + "seedLibraryNoMorePlant": "Aucune plante n'est disponible", + "seedLibraryNoPersonalPlants": "Vous n'avez pas encore de plantes dans votre grainothèque. Vous pouvez en ajouter en allant dans les stocks.", + "seedLibraryNoSpecies": "Aucune espèce trouvée", + "seedLibraryNoStockPlants": "Aucune plante disponible dans le stock", + "seedLibraryNotes": "Notes", + "seedLibraryOk": "OK", + "seedLibraryPlantationPeriod": "Période de plantation :", + "seedLibraryPlantationType": "Type de plantation :", + "seedLibraryPlantDetail": "Détail de la plante", + "seedLibraryPlantingDate": "Date de plantation", + "seedLibraryPlantingNow": "Je la plante maintenant", + "seedLibraryPrefix": "Préfixe", + "seedLibraryPrefixError": "Prefixe déjà utilisé", + "seedLibraryPrefixLengthError": "Le préfixe doit faire 3 caractères", + "seedLibraryPropagationMethod": "Méthode de propagation :", + "seedLibraryReference": "Référence :", + "seedLibraryRemovedPlant": "Plante supprimée", + "seedLibraryRemovingError": "Erreur lors de la suppression", + "seedLibraryResearch": "Recherche", + "seedLibrarySaveChanges": "Sauvegarder les modifications", + "seedLibrarySeason": "Saison :", + "seedLibrarySeed": "Graine", + "seedLibrarySeeds": "graines", + "seedLibrarySeedDeposit": "Dépôt de plantes", + "seedLibrarySeedLibrary": "Grainothèque", + "seedLibrarySeedQuantitySimple": "Quantité de graines", + "seedLibrarySeedQuantity": "Quantité de graines :", + "seedLibraryShowDeadPlants": "Afficher les plantes mortes", + "seedLibrarySpecies": "Espèce :", + "seedLibrarySpeciesHelp": "Aide sur l'espèce", + "seedLibrarySpeciesPlural": "Espèces", + "seedLibrarySpeciesSimple": "Espèce", + "seedLibrarySpeciesType": "Type d'espèce :", + "seedLibrarySpring": "Printemps", + "seedLibraryStartMonth": "Mois de début :", + "seedLibraryStock": "Stock disponible", + "seedLibrarySummer": "Été", + "seedLibraryStocks": "Stocks", + "seedLibraryTimeUntilMaturation": "Temps avant maturation :", + "seedLibraryType": "Type :", + "seedLibraryUnableToOpen": "Impossible d'ouvrir le lien", + "seedLibraryUpdate": "Modifier", + "seedLibraryUpdatedInformation": "Informations modifiées", + "seedLibraryUpdatedSpecies": "Espèce modifiée", + "seedLibraryUpdatedPlant": "Plante modifiée", + "seedLibraryUpdatingError": "Erreur lors de la modification", + "seedLibraryWinter": "Hiver", + "seedLibraryWriteReference": "Veuillez écrire la référence suivante : ", + "settingsAccount": "Compte", + "settingsAddProfilePicture": "Ajouter une photo", + "settingsAdmin": "Administrateur", + "settingsAskHelp": "Demander de l'aide", + "settingsAssociation": "Association", + "settingsBirthday": "Date de naissance", + "settingsBugs": "Bugs", + "settingsChangePassword": "Changer de mot de passe", + "settingsChangingPassword": "Voulez-vous vraiment changer votre mot de passe ?", + "settingsConfirmPassword": "Confirmer le mot de passe", + "settingsCopied": "Copié !", + "settingsDarkMode": "Mode sombre", + "settingsDarkModeOff": "Désactivé", + "settingsDeleteLogs": "Supprimer les logs ?", + "settingsDeleteNotificationLogs": "Supprimer les logs des notifications ?", + "settingsDetelePersonalData": "Supprimer mes données personnelles", + "settingsDetelePersonalDataDesc": "Cette action notifie l'administrateur que vous souhaitez supprimer vos données personnelles.", + "settingsDeleting": "Suppresion", + "settingsEdit": "Modifier", + "settingsEditAccount": "Modifier le compte", + "settingsEditPassword": "Modifier le mot de passe", + "settingsEmail": "Email", + "settingsEmptyField": "Ce champ ne peut pas être vide", + "settingsErrorProfilePicture": "Erreur lors de la modification de la photo de profil", + "settingsErrorSendingDemand": "Erreur lors de l'envoi de la demande", + "settingsEventsIcal": "Lien Ical des événements", + "settingsExpectingDate": "Date de naissance attendue", + "settingsFirstname": "Prénom", + "settingsFloor": "Étage", + "settingsHelp": "Aide", + "settingsIcalCopied": "Lien Ical copié !", + "settingsLanguage": "Langue", + "settingsLanguageFr": "Français", + "settingsLogs": "Logs", + "settingsModules": "Modules", + "settingsMyIcs": "Mon lien Ical", + "settingsName": "Nom", + "settingsNewPassword": "Nouveau mot de passe", + "settingsNickname": "Surnom", + "settingsNotifications": "Notifications", + "settingsOldPassword": "Ancien mot de passe", + "settingsPasswordChanged": "Mot de passe changé", + "settingsPasswordsNotMatch": "Les mots de passe ne correspondent pas", + "settingsPersonalData": "Données personnelles", + "settingsPersonalisation": "Personnalisation", + "settingsPhone": "Téléphone", + "settingsProfilePicture": "Photo de profil", + "settingsPromo": "Promotion", + "settingsRepportBug": "Signaler un bug", + "settingsSave": "Enregistrer", + "settingsSecurity": "Sécurité", + "settingsSendedDemand": "Demande envoyée", + "settingsSettings": "Paramètres", + "settingsTooHeavyProfilePicture": "L'image est trop lourde (max 4Mo)", + "settingsUpdatedProfile": "Profil modifié", + "settingsUpdatedProfilePicture": "Photo de profil modifiée", + "settingsUpdateNotification": "Mettre à jour les notifications", + "settingsUpdatingError": "Erreur lors de la modification du profil", + "settingsVersion": "Version", + "settingsPasswordStrength": "Force du mot de passe", + "settingsPasswordStrengthVeryWeak": "Très faible", + "settingsPasswordStrengthWeak": "Faible", + "settingsPasswordStrengthMedium": "Moyen", + "settingsPasswordStrengthStrong": "Fort", + "settingsPasswordStrengthVeryStrong": "Très fort", + "voteAdd": "Ajouter", + "voteAddMember": "Ajouter un membre", + "voteAddedPretendance": "Liste ajoutée", + "voteAddedSection": "Section ajoutée", + "voteAddingError": "Erreur lors de l'ajout", + "voteAddPretendance": "Ajouter une liste", + "voteAddSection": "Ajouter une section", + "voteAll": "Tous", + "voteAlreadyAddedMember": "Membre déjà ajouté", + "voteAlreadyVoted": "Vote enregistré", + "voteChooseList": "Choisir une liste", + "voteClear": "Réinitialiser", + "voteClearVotes": "Réinitialiser les votes", + "voteClosedVote": "Votes clos", + "voteCloseVote": "Fermer les votes", + "voteConfirmVote": "Confirmer le vote", + "voteCountVote": "Dépouiller les votes", + "voteDeletedAll": "Tout supprimé", + "voteDeletedPipo": "Listes pipos supprimées", + "voteDeletedSection": "Section supprimée", + "voteDeleteAll": "Supprimer tout", + "voteDeleteAllDescription": "Voulez-vous vraiment supprimer tout ?", + "voteDeletePipo": "Supprimer les listes pipos", + "voteDeletePipoDescription": "Voulez-vous vraiment supprimer les listes pipos ?", + "voteDeletePretendance": "Supprimer la liste", + "voteDeletePretendanceDesc": "Voulez-vous vraiment supprimer cette liste ?", + "voteDeleteSection": "Supprimer la section", + "voteDeleteSectionDescription": "Voulez-vous vraiment supprimer cette section ?", + "voteDeletingError": "Erreur lors de la suppression", + "voteDescription": "Description", + "voteEdit": "Modifier", + "voteEditedPretendance": "Liste modifiée", + "voteEditedSection": "Section modifiée", + "voteEditingError": "Erreur lors de la modification", + "voteErrorClosingVotes": "Erreur lors de la fermeture des votes", + "voteErrorCountingVotes": "Erreur lors du dépouillement des votes", + "voteErrorResetingVotes": "Erreur lors de la réinitialisation des votes", + "voteErrorOpeningVotes": "Erreur lors de l'ouverture des votes", + "voteIncorrectOrMissingFields": "Champs incorrects ou manquants", + "voteMembers": "Membres", + "voteName": "Nom", + "voteNoPretendanceList": "Aucune liste de prétendance", + "voteNoSection": "Aucune section", + "voteCanNotVote": "Vous ne pouvez pas voter", + "voteNoSectionList": "Aucune section", + "voteNotOpenedVote": "Vote non ouvert", + "voteOnGoingCount": "Dépouillement en cours", + "voteOpenVote": "Ouvrir les votes", + "votePipo": "Pipo", + "votePretendance": "Listes", + "votePretendanceDeleted": "Prétendance supprimée", + "votePretendanceNotDeleted": "Erreur lors de la suppression", + "voteProgram": "Programme", + "votePublish": "Publier", + "votePublishVoteDescription": "Voulez-vous vraiment publier les votes ?", + "voteResetedVotes": "Votes réinitialisés", + "voteResetVote": "Réinitialiser les votes", + "voteResetVoteDescription": "Que voulez-vous faire ?", + "voteRole": "Rôle", + "voteSectionDescription": "Description de la section", + "voteSection": "Section", + "voteSectionName": "Nom de la section", + "voteSeeMore": "Voir plus", + "voteSelected": "Sélectionné", + "voteShowVotes": "Voir les votes", + "voteVote": "Vote", + "voteVoteError": "Erreur lors de l'enregistrement du vote", + "voteVoteFor": "Voter pour ", + "voteVoteNotStarted": "Vote non ouvert", + "voteVoters": "Groupes votants", + "voteVoteSuccess": "Vote enregistré", + "voteVotes": "Voix", + "voteVotesClosed": "Votes clos", + "voteVotesCounted": "Votes dépouillés", + "voteVotesOpened": "Votes ouverts", + "voteWarning": "Attention", + "voteWarningMessage": "La sélection ne sera pas sauvegardée.\nVoulez-vous continuer ?" +} \ No newline at end of file diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart new file mode 100644 index 0000000000..e76239be15 --- /dev/null +++ b/lib/l10n/app_localizations.dart @@ -0,0 +1,6734 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:intl/intl.dart' as intl; + +import 'app_localizations_en.dart'; +import 'app_localizations_fr.dart'; + +// ignore_for_file: type=lint + +/// Callers can lookup localized strings with an instance of AppLocalizations +/// returned by `AppLocalizations.of(context)`. +/// +/// Applications need to include `AppLocalizations.delegate()` in their app's +/// `localizationDelegates` list, and the locales they support in the app's +/// `supportedLocales` list. For example: +/// +/// ```dart +/// import 'l10n/app_localizations.dart'; +/// +/// return MaterialApp( +/// localizationsDelegates: AppLocalizations.localizationsDelegates, +/// supportedLocales: AppLocalizations.supportedLocales, +/// home: MyApplicationHome(), +/// ); +/// ``` +/// +/// ## Update pubspec.yaml +/// +/// Please make sure to update your pubspec.yaml to include the following +/// packages: +/// +/// ```yaml +/// dependencies: +/// # Internationalization support. +/// flutter_localizations: +/// sdk: flutter +/// intl: any # Use the pinned version from flutter_localizations +/// +/// # Rest of dependencies +/// ``` +/// +/// ## iOS Applications +/// +/// iOS applications define key application metadata, including supported +/// locales, in an Info.plist file that is built into the application bundle. +/// To configure the locales supported by your app, you’ll need to edit this +/// file. +/// +/// First, open your project’s ios/Runner.xcworkspace Xcode workspace file. +/// Then, in the Project Navigator, open the Info.plist file under the Runner +/// project’s Runner folder. +/// +/// Next, select the Information Property List item, select Add Item from the +/// Editor menu, then select Localizations from the pop-up menu. +/// +/// Select and expand the newly-created Localizations item then, for each +/// locale your application supports, add a new item and select the locale +/// you wish to add from the pop-up menu in the Value field. This list should +/// be consistent with the languages listed in the AppLocalizations.supportedLocales +/// property. +abstract class AppLocalizations { + AppLocalizations(String locale) + : localeName = intl.Intl.canonicalizedLocale(locale.toString()); + + final String localeName; + + static AppLocalizations? of(BuildContext context) { + return Localizations.of(context, AppLocalizations); + } + + static const LocalizationsDelegate delegate = + _AppLocalizationsDelegate(); + + /// A list of this localizations delegate along with the default localizations + /// delegates. + /// + /// Returns a list of localizations delegates containing this delegate along with + /// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, + /// and GlobalWidgetsLocalizations.delegate. + /// + /// Additional delegates can be added by appending to this list in + /// MaterialApp. This list does not have to be used at all if a custom list + /// of delegates is preferred or required. + static const List> localizationsDelegates = + >[ + delegate, + GlobalMaterialLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ]; + + /// A list of this localizations delegate's supported locales. + static const List supportedLocales = [ + Locale('en'), + Locale('fr'), + ]; + + /// No description provided for @adminAccountTypes. + /// + /// In fr, this message translates to: + /// **'Types de compte'** + String get adminAccountTypes; + + /// No description provided for @adminAdd. + /// + /// In fr, this message translates to: + /// **'Ajouter'** + String get adminAdd; + + /// No description provided for @adminAddGroup. + /// + /// In fr, this message translates to: + /// **'Ajouter un groupe'** + String get adminAddGroup; + + /// No description provided for @adminAddMember. + /// + /// In fr, this message translates to: + /// **'Ajouter un membre'** + String get adminAddMember; + + /// No description provided for @adminAddedGroup. + /// + /// In fr, this message translates to: + /// **'Groupe créé'** + String get adminAddedGroup; + + /// No description provided for @adminAddedLoaner. + /// + /// In fr, this message translates to: + /// **'Préteur ajouté'** + String get adminAddedLoaner; + + /// No description provided for @adminAddedMember. + /// + /// In fr, this message translates to: + /// **'Membre ajouté'** + String get adminAddedMember; + + /// No description provided for @adminAddingError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de l\'ajout'** + String get adminAddingError; + + /// No description provided for @adminAddingMember. + /// + /// In fr, this message translates to: + /// **'Ajout d\'un membre'** + String get adminAddingMember; + + /// No description provided for @adminAddLoaningGroup. + /// + /// In fr, this message translates to: + /// **'Ajouter un groupe de prêt'** + String get adminAddLoaningGroup; + + /// No description provided for @adminAddSchool. + /// + /// In fr, this message translates to: + /// **'Ajouter une école'** + String get adminAddSchool; + + /// No description provided for @adminAddStructure. + /// + /// In fr, this message translates to: + /// **'Ajouter une structure'** + String get adminAddStructure; + + /// No description provided for @adminAddedSchool. + /// + /// In fr, this message translates to: + /// **'École créée'** + String get adminAddedSchool; + + /// No description provided for @adminAddedStructure. + /// + /// In fr, this message translates to: + /// **'Structure ajoutée'** + String get adminAddedStructure; + + /// No description provided for @adminEditedStructure. + /// + /// In fr, this message translates to: + /// **'Structure modifiée'** + String get adminEditedStructure; + + /// No description provided for @adminAdministration. + /// + /// In fr, this message translates to: + /// **'Administration'** + String get adminAdministration; + + /// No description provided for @adminAssociationMembership. + /// + /// In fr, this message translates to: + /// **'Adhésion'** + String get adminAssociationMembership; + + /// No description provided for @adminAssociationMembershipName. + /// + /// In fr, this message translates to: + /// **'Nom de l\'adhésion'** + String get adminAssociationMembershipName; + + /// No description provided for @adminAssociationsMemberships. + /// + /// In fr, this message translates to: + /// **'Adhésions'** + String get adminAssociationsMemberships; + + /// No description provided for @adminClearFilters. + /// + /// In fr, this message translates to: + /// **'Effacer les filtres'** + String get adminClearFilters; + + /// No description provided for @adminCreateAssociationMembership. + /// + /// In fr, this message translates to: + /// **'Créer une adhésion'** + String get adminCreateAssociationMembership; + + /// No description provided for @adminCreatedAssociationMembership. + /// + /// In fr, this message translates to: + /// **'Adhésion créée'** + String get adminCreatedAssociationMembership; + + /// No description provided for @adminCreationError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de la création'** + String get adminCreationError; + + /// No description provided for @adminDateError. + /// + /// In fr, this message translates to: + /// **'La date de début doit être avant la date de fin'** + String get adminDateError; + + /// No description provided for @adminDelete. + /// + /// In fr, this message translates to: + /// **'Supprimer'** + String get adminDelete; + + /// No description provided for @adminDeleteAssociationMembership. + /// + /// In fr, this message translates to: + /// **'Supprimer l\'adhésion ?'** + String get adminDeleteAssociationMembership; + + /// No description provided for @adminDeletedAssociationMembership. + /// + /// In fr, this message translates to: + /// **'Adhésion supprimée'** + String get adminDeletedAssociationMembership; + + /// No description provided for @adminDeleteGroup. + /// + /// In fr, this message translates to: + /// **'Supprimer le groupe ?'** + String get adminDeleteGroup; + + /// No description provided for @adminDeletedGroup. + /// + /// In fr, this message translates to: + /// **'Groupe supprimé'** + String get adminDeletedGroup; + + /// No description provided for @adminDeleteSchool. + /// + /// In fr, this message translates to: + /// **'Supprimer l\'école ?'** + String get adminDeleteSchool; + + /// No description provided for @adminDeletedSchool. + /// + /// In fr, this message translates to: + /// **'École supprimée'** + String get adminDeletedSchool; + + /// No description provided for @adminDeleting. + /// + /// In fr, this message translates to: + /// **'Suppression'** + String get adminDeleting; + + /// No description provided for @adminDeletingError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de la suppression'** + String get adminDeletingError; + + /// No description provided for @adminDescription. + /// + /// In fr, this message translates to: + /// **'Description'** + String get adminDescription; + + /// No description provided for @adminEclSchool. + /// + /// In fr, this message translates to: + /// **'Centrale Lyon'** + String get adminEclSchool; + + /// No description provided for @adminEdit. + /// + /// In fr, this message translates to: + /// **'Modifier'** + String get adminEdit; + + /// No description provided for @adminEditStructure. + /// + /// In fr, this message translates to: + /// **'Modifier la structure'** + String get adminEditStructure; + + /// No description provided for @adminEditMembership. + /// + /// In fr, this message translates to: + /// **'Modifier l\'adhésion'** + String get adminEditMembership; + + /// No description provided for @adminEmptyDate. + /// + /// In fr, this message translates to: + /// **'Date vide'** + String get adminEmptyDate; + + /// No description provided for @adminEmptyFieldError. + /// + /// In fr, this message translates to: + /// **'Le nom ne peut pas être vide'** + String get adminEmptyFieldError; + + /// No description provided for @adminEmailRegex. + /// + /// In fr, this message translates to: + /// **'Email Regex'** + String get adminEmailRegex; + + /// No description provided for @adminEmptyUser. + /// + /// In fr, this message translates to: + /// **'Utilisateur vide'** + String get adminEmptyUser; + + /// No description provided for @adminEndDate. + /// + /// In fr, this message translates to: + /// **'Date de fin'** + String get adminEndDate; + + /// No description provided for @adminEndDateMaximal. + /// + /// In fr, this message translates to: + /// **'Date de fin maximale'** + String get adminEndDateMaximal; + + /// No description provided for @adminEndDateMinimal. + /// + /// In fr, this message translates to: + /// **'Date de fin minimale'** + String get adminEndDateMinimal; + + /// No description provided for @adminError. + /// + /// In fr, this message translates to: + /// **'Erreur'** + String get adminError; + + /// No description provided for @adminFilters. + /// + /// In fr, this message translates to: + /// **'Filtres'** + String get adminFilters; + + /// No description provided for @adminGroup. + /// + /// In fr, this message translates to: + /// **'Groupe'** + String get adminGroup; + + /// No description provided for @adminGroups. + /// + /// In fr, this message translates to: + /// **'Groupes'** + String get adminGroups; + + /// No description provided for @adminLoaningGroup. + /// + /// In fr, this message translates to: + /// **'Groupe de prêt'** + String get adminLoaningGroup; + + /// No description provided for @adminLooking. + /// + /// In fr, this message translates to: + /// **'Recherche'** + String get adminLooking; + + /// No description provided for @adminManager. + /// + /// In fr, this message translates to: + /// **'Administrateur de la structure'** + String get adminManager; + + /// No description provided for @adminMaximum. + /// + /// In fr, this message translates to: + /// **'Maximum'** + String get adminMaximum; + + /// No description provided for @adminMembers. + /// + /// In fr, this message translates to: + /// **'Membres'** + String get adminMembers; + + /// No description provided for @adminMembershipAddingError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de l\'ajout (surement dû à une superposition de dates)'** + String get adminMembershipAddingError; + + /// No description provided for @adminMemberships. + /// + /// In fr, this message translates to: + /// **'Adhésions'** + String get adminMemberships; + + /// No description provided for @adminMembershipUpdatingError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de la modification (surement dû à une superposition de dates)'** + String get adminMembershipUpdatingError; + + /// No description provided for @adminMinimum. + /// + /// In fr, this message translates to: + /// **'Minimum'** + String get adminMinimum; + + /// No description provided for @adminModifyModuleVisibility. + /// + /// In fr, this message translates to: + /// **'Visibilité des modules'** + String get adminModifyModuleVisibility; + + /// No description provided for @adminMyEclPay. + /// + /// In fr, this message translates to: + /// **'MyECLPay'** + String get adminMyEclPay; + + /// No description provided for @adminName. + /// + /// In fr, this message translates to: + /// **'Nom'** + String get adminName; + + /// No description provided for @adminNoManager. + /// + /// In fr, this message translates to: + /// **'Aucun manager n\'est sélectionné'** + String get adminNoManager; + + /// No description provided for @adminNoMember. + /// + /// In fr, this message translates to: + /// **'Aucun membre'** + String get adminNoMember; + + /// No description provided for @adminNoMoreLoaner. + /// + /// In fr, this message translates to: + /// **'Aucun prêteur n\'est disponible'** + String get adminNoMoreLoaner; + + /// No description provided for @adminNoSchool. + /// + /// In fr, this message translates to: + /// **'Sans école'** + String get adminNoSchool; + + /// No description provided for @adminRemoveGroupMember. + /// + /// In fr, this message translates to: + /// **'Supprimer le membre du groupe ?'** + String get adminRemoveGroupMember; + + /// No description provided for @adminResearch. + /// + /// In fr, this message translates to: + /// **'Recherche'** + String get adminResearch; + + /// No description provided for @adminSchools. + /// + /// In fr, this message translates to: + /// **'Écoles'** + String get adminSchools; + + /// No description provided for @adminStructures. + /// + /// In fr, this message translates to: + /// **'Structures'** + String get adminStructures; + + /// No description provided for @adminStartDate. + /// + /// In fr, this message translates to: + /// **'Date de début'** + String get adminStartDate; + + /// No description provided for @adminStartDateMaximal. + /// + /// In fr, this message translates to: + /// **'Date de début maximale'** + String get adminStartDateMaximal; + + /// No description provided for @adminStartDateMinimal. + /// + /// In fr, this message translates to: + /// **'Date de début minimale'** + String get adminStartDateMinimal; + + /// No description provided for @adminUpdatedAssociationMembership. + /// + /// In fr, this message translates to: + /// **'Adhésion modifiée'** + String get adminUpdatedAssociationMembership; + + /// No description provided for @adminUpdatedGroup. + /// + /// In fr, this message translates to: + /// **'Groupe modifié'** + String get adminUpdatedGroup; + + /// No description provided for @adminUpdatedMembership. + /// + /// In fr, this message translates to: + /// **'Adhésion modifiée'** + String get adminUpdatedMembership; + + /// No description provided for @adminUpdatingError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de la modification'** + String get adminUpdatingError; + + /// No description provided for @adminUser. + /// + /// In fr, this message translates to: + /// **'Utilisateur'** + String get adminUser; + + /// No description provided for @adminValidateFilters. + /// + /// In fr, this message translates to: + /// **'Valider les filtres'** + String get adminValidateFilters; + + /// No description provided for @adminVisibilities. + /// + /// In fr, this message translates to: + /// **'Visibilités'** + String get adminVisibilities; + + /// No description provided for @advertAdd. + /// + /// In fr, this message translates to: + /// **'Ajouter'** + String get advertAdd; + + /// No description provided for @advertAddedAdvert. + /// + /// In fr, this message translates to: + /// **'Annonce publiée'** + String get advertAddedAdvert; + + /// No description provided for @advertAddedAnnouncer. + /// + /// In fr, this message translates to: + /// **'Annonceur ajouté'** + String get advertAddedAnnouncer; + + /// No description provided for @advertAddingError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de l\'ajout'** + String get advertAddingError; + + /// No description provided for @advertAdmin. + /// + /// In fr, this message translates to: + /// **'Admin'** + String get advertAdmin; + + /// No description provided for @advertAdvert. + /// + /// In fr, this message translates to: + /// **'Annonce'** + String get advertAdvert; + + /// No description provided for @advertChoosingAnnouncer. + /// + /// In fr, this message translates to: + /// **'Veuillez choisir un annonceur'** + String get advertChoosingAnnouncer; + + /// No description provided for @advertChoosingPoster. + /// + /// In fr, this message translates to: + /// **'Veuillez choisir une image'** + String get advertChoosingPoster; + + /// No description provided for @advertContent. + /// + /// In fr, this message translates to: + /// **'Contenu'** + String get advertContent; + + /// No description provided for @advertDeleteAdvert. + /// + /// In fr, this message translates to: + /// **'Supprimer l\'annonce ?'** + String get advertDeleteAdvert; + + /// No description provided for @advertDeleteAnnouncer. + /// + /// In fr, this message translates to: + /// **'Supprimer l\'annonceur ?'** + String get advertDeleteAnnouncer; + + /// No description provided for @advertDeleting. + /// + /// In fr, this message translates to: + /// **'Suppression'** + String get advertDeleting; + + /// No description provided for @advertEdit. + /// + /// In fr, this message translates to: + /// **'Modifier'** + String get advertEdit; + + /// No description provided for @advertEditedAdvert. + /// + /// In fr, this message translates to: + /// **'Annonce modifiée'** + String get advertEditedAdvert; + + /// No description provided for @advertEditingError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de la modification'** + String get advertEditingError; + + /// No description provided for @advertGroupAdvert. + /// + /// In fr, this message translates to: + /// **'Groupe'** + String get advertGroupAdvert; + + /// No description provided for @advertIncorrectOrMissingFields. + /// + /// In fr, this message translates to: + /// **'Champs incorrects ou manquants'** + String get advertIncorrectOrMissingFields; + + /// No description provided for @advertInvalidNumber. + /// + /// In fr, this message translates to: + /// **'Veuillez entrer un nombre'** + String get advertInvalidNumber; + + /// No description provided for @advertManagement. + /// + /// In fr, this message translates to: + /// **'Gestion'** + String get advertManagement; + + /// No description provided for @advertModifyAnnouncingGroup. + /// + /// In fr, this message translates to: + /// **'Modifier un groupe d\'annonce'** + String get advertModifyAnnouncingGroup; + + /// No description provided for @advertNoMoreAnnouncer. + /// + /// In fr, this message translates to: + /// **'Aucun annonceur n\'est disponible'** + String get advertNoMoreAnnouncer; + + /// No description provided for @advertNoValue. + /// + /// In fr, this message translates to: + /// **'Veuillez entrer une valeur'** + String get advertNoValue; + + /// No description provided for @advertPositiveNumber. + /// + /// In fr, this message translates to: + /// **'Veuillez entrer un nombre positif'** + String get advertPositiveNumber; + + /// No description provided for @advertRemovedAnnouncer. + /// + /// In fr, this message translates to: + /// **'Annonceur supprimé'** + String get advertRemovedAnnouncer; + + /// No description provided for @advertRemovingError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de la suppression'** + String get advertRemovingError; + + /// No description provided for @advertTags. + /// + /// In fr, this message translates to: + /// **'Tags'** + String get advertTags; + + /// No description provided for @advertTitle. + /// + /// In fr, this message translates to: + /// **'Titre'** + String get advertTitle; + + /// No description provided for @advertMonthJan. + /// + /// In fr, this message translates to: + /// **'Janv'** + String get advertMonthJan; + + /// No description provided for @advertMonthFeb. + /// + /// In fr, this message translates to: + /// **'Févr.'** + String get advertMonthFeb; + + /// No description provided for @advertMonthMar. + /// + /// In fr, this message translates to: + /// **'Mars'** + String get advertMonthMar; + + /// No description provided for @advertMonthApr. + /// + /// In fr, this message translates to: + /// **'Avr.'** + String get advertMonthApr; + + /// No description provided for @advertMonthMay. + /// + /// In fr, this message translates to: + /// **'Mai'** + String get advertMonthMay; + + /// No description provided for @advertMonthJun. + /// + /// In fr, this message translates to: + /// **'Juin'** + String get advertMonthJun; + + /// No description provided for @advertMonthJul. + /// + /// In fr, this message translates to: + /// **'Juill.'** + String get advertMonthJul; + + /// No description provided for @advertMonthAug. + /// + /// In fr, this message translates to: + /// **'Août'** + String get advertMonthAug; + + /// No description provided for @advertMonthSep. + /// + /// In fr, this message translates to: + /// **'Sept.'** + String get advertMonthSep; + + /// No description provided for @advertMonthOct. + /// + /// In fr, this message translates to: + /// **'Oct.'** + String get advertMonthOct; + + /// No description provided for @advertMonthNov. + /// + /// In fr, this message translates to: + /// **'Nov.'** + String get advertMonthNov; + + /// No description provided for @advertMonthDec. + /// + /// In fr, this message translates to: + /// **'Déc.'** + String get advertMonthDec; + + /// No description provided for @amapAccounts. + /// + /// In fr, this message translates to: + /// **'Comptes'** + String get amapAccounts; + + /// No description provided for @amapAdd. + /// + /// In fr, this message translates to: + /// **'Ajouter'** + String get amapAdd; + + /// No description provided for @amapAddDelivery. + /// + /// In fr, this message translates to: + /// **'Ajouter une livraison'** + String get amapAddDelivery; + + /// No description provided for @amapAddedCommand. + /// + /// In fr, this message translates to: + /// **'Commande ajoutée'** + String get amapAddedCommand; + + /// No description provided for @amapAddedOrder. + /// + /// In fr, this message translates to: + /// **'Commande ajoutée'** + String get amapAddedOrder; + + /// No description provided for @amapAddedProduct. + /// + /// In fr, this message translates to: + /// **'Produit ajouté'** + String get amapAddedProduct; + + /// No description provided for @amapAddedUser. + /// + /// In fr, this message translates to: + /// **'Utilisateur ajouté'** + String get amapAddedUser; + + /// No description provided for @amapAddProduct. + /// + /// In fr, this message translates to: + /// **'Ajouter un produit'** + String get amapAddProduct; + + /// No description provided for @amapAddUser. + /// + /// In fr, this message translates to: + /// **'Ajouter un utilisateur'** + String get amapAddUser; + + /// No description provided for @amapAddingACommand. + /// + /// In fr, this message translates to: + /// **'Ajouter une commande'** + String get amapAddingACommand; + + /// No description provided for @amapAddingCommand. + /// + /// In fr, this message translates to: + /// **'Ajouter la commande'** + String get amapAddingCommand; + + /// No description provided for @amapAddingError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de l\'ajout'** + String get amapAddingError; + + /// No description provided for @amapAddingProduct. + /// + /// In fr, this message translates to: + /// **'Ajouter un produit'** + String get amapAddingProduct; + + /// No description provided for @amapAddOrder. + /// + /// In fr, this message translates to: + /// **'Ajouter une commande'** + String get amapAddOrder; + + /// No description provided for @amapAdmin. + /// + /// In fr, this message translates to: + /// **'Admin'** + String get amapAdmin; + + /// No description provided for @amapAlreadyExistCommand. + /// + /// In fr, this message translates to: + /// **'Il existe déjà une commande à cette date'** + String get amapAlreadyExistCommand; + + /// No description provided for @amapAmap. + /// + /// In fr, this message translates to: + /// **'Amap'** + String get amapAmap; + + /// No description provided for @amapAmount. + /// + /// In fr, this message translates to: + /// **'Solde'** + String get amapAmount; + + /// No description provided for @amapArchive. + /// + /// In fr, this message translates to: + /// **'Archiver'** + String get amapArchive; + + /// No description provided for @amapArchiveDelivery. + /// + /// In fr, this message translates to: + /// **'Archiver'** + String get amapArchiveDelivery; + + /// No description provided for @amapArchivingDelivery. + /// + /// In fr, this message translates to: + /// **'Archivage de la livraison'** + String get amapArchivingDelivery; + + /// No description provided for @amapCategory. + /// + /// In fr, this message translates to: + /// **'Catégorie'** + String get amapCategory; + + /// No description provided for @amapCloseDelivery. + /// + /// In fr, this message translates to: + /// **'Verrouiller'** + String get amapCloseDelivery; + + /// No description provided for @amapCommandDate. + /// + /// In fr, this message translates to: + /// **'Date de la commande'** + String get amapCommandDate; + + /// No description provided for @amapCommandProducts. + /// + /// In fr, this message translates to: + /// **'Produits de la commande'** + String get amapCommandProducts; + + /// No description provided for @amapConfirm. + /// + /// In fr, this message translates to: + /// **'Confirmer'** + String get amapConfirm; + + /// No description provided for @amapContact. + /// + /// In fr, this message translates to: + /// **'Contacts associatifs '** + String get amapContact; + + /// No description provided for @amapCreateCategory. + /// + /// In fr, this message translates to: + /// **'Créer une catégorie'** + String get amapCreateCategory; + + /// No description provided for @amapDelete. + /// + /// In fr, this message translates to: + /// **'Supprimer'** + String get amapDelete; + + /// No description provided for @amapDeleteDelivery. + /// + /// In fr, this message translates to: + /// **'Supprimer la livraison ?'** + String get amapDeleteDelivery; + + /// No description provided for @amapDeleteDeliveryDescription. + /// + /// In fr, this message translates to: + /// **'Voulez-vous vraiment supprimer cette livraison ?'** + String get amapDeleteDeliveryDescription; + + /// No description provided for @amapDeletedDelivery. + /// + /// In fr, this message translates to: + /// **'Livraison supprimée'** + String get amapDeletedDelivery; + + /// No description provided for @amapDeletedOrder. + /// + /// In fr, this message translates to: + /// **'Commande supprimée'** + String get amapDeletedOrder; + + /// No description provided for @amapDeletedProduct. + /// + /// In fr, this message translates to: + /// **'Produit supprimé'** + String get amapDeletedProduct; + + /// No description provided for @amapDeleteProduct. + /// + /// In fr, this message translates to: + /// **'Supprimer le produit ?'** + String get amapDeleteProduct; + + /// No description provided for @amapDeleteProductDescription. + /// + /// In fr, this message translates to: + /// **'Voulez-vous vraiment supprimer ce produit ?'** + String get amapDeleteProductDescription; + + /// No description provided for @amapDeleting. + /// + /// In fr, this message translates to: + /// **'Suppression'** + String get amapDeleting; + + /// No description provided for @amapDeletingDelivery. + /// + /// In fr, this message translates to: + /// **'Supprimer la livraison ?'** + String get amapDeletingDelivery; + + /// No description provided for @amapDeletingError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de la suppression'** + String get amapDeletingError; + + /// No description provided for @amapDeletingOrder. + /// + /// In fr, this message translates to: + /// **'Supprimer la commande ?'** + String get amapDeletingOrder; + + /// No description provided for @amapDeletingProduct. + /// + /// In fr, this message translates to: + /// **'Supprimer le produit ?'** + String get amapDeletingProduct; + + /// No description provided for @amapDeliver. + /// + /// In fr, this message translates to: + /// **'Livraison teminée ?'** + String get amapDeliver; + + /// No description provided for @amapDeliveries. + /// + /// In fr, this message translates to: + /// **'Livraisons'** + String get amapDeliveries; + + /// No description provided for @amapDeliveringDelivery. + /// + /// In fr, this message translates to: + /// **'Toutes les commandes sont livrées ?'** + String get amapDeliveringDelivery; + + /// No description provided for @amapDelivery. + /// + /// In fr, this message translates to: + /// **'Livraison'** + String get amapDelivery; + + /// No description provided for @amapDeliveryArchived. + /// + /// In fr, this message translates to: + /// **'Livraison archivée'** + String get amapDeliveryArchived; + + /// No description provided for @amapDeliveryDate. + /// + /// In fr, this message translates to: + /// **'Date de livraison'** + String get amapDeliveryDate; + + /// No description provided for @amapDeliveryDelivered. + /// + /// In fr, this message translates to: + /// **'Livraison effectuée'** + String get amapDeliveryDelivered; + + /// No description provided for @amapDeliveryHistory. + /// + /// In fr, this message translates to: + /// **'Historique des livraisons'** + String get amapDeliveryHistory; + + /// No description provided for @amapDeliveryList. + /// + /// In fr, this message translates to: + /// **'Liste des livraisons'** + String get amapDeliveryList; + + /// No description provided for @amapDeliveryLocked. + /// + /// In fr, this message translates to: + /// **'Livraison verrouillée'** + String get amapDeliveryLocked; + + /// No description provided for @amapDeliveryOn. + /// + /// In fr, this message translates to: + /// **'Livraison le'** + String get amapDeliveryOn; + + /// No description provided for @amapDeliveryOpened. + /// + /// In fr, this message translates to: + /// **'Livraison ouverte'** + String get amapDeliveryOpened; + + /// No description provided for @amapDeliveryNotArchived. + /// + /// In fr, this message translates to: + /// **'Livraison non archivée'** + String get amapDeliveryNotArchived; + + /// No description provided for @amapDeliveryNotLocked. + /// + /// In fr, this message translates to: + /// **'Livraison non verrouillée'** + String get amapDeliveryNotLocked; + + /// No description provided for @amapDeliveryNotDelivered. + /// + /// In fr, this message translates to: + /// **'Livraison non effectuée'** + String get amapDeliveryNotDelivered; + + /// No description provided for @amapDeliveryNotOpened. + /// + /// In fr, this message translates to: + /// **'Livraison non ouverte'** + String get amapDeliveryNotOpened; + + /// No description provided for @amapEditDelivery. + /// + /// In fr, this message translates to: + /// **'Modifier la livraison'** + String get amapEditDelivery; + + /// No description provided for @amapEditedCommand. + /// + /// In fr, this message translates to: + /// **'Commande modifiée'** + String get amapEditedCommand; + + /// No description provided for @amapEditingError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de la modification'** + String get amapEditingError; + + /// No description provided for @amapEditProduct. + /// + /// In fr, this message translates to: + /// **'Modifier le produit'** + String get amapEditProduct; + + /// No description provided for @amapEndingDelivery. + /// + /// In fr, this message translates to: + /// **'Fin de la livraison'** + String get amapEndingDelivery; + + /// No description provided for @amapError. + /// + /// In fr, this message translates to: + /// **'Erreur'** + String get amapError; + + /// No description provided for @amapErrorLink. + /// + /// In fr, this message translates to: + /// **'Erreur lors de l\'ouverture du lien'** + String get amapErrorLink; + + /// No description provided for @amapErrorLoadingUser. + /// + /// In fr, this message translates to: + /// **'Erreur lors du chargement des utilisateurs'** + String get amapErrorLoadingUser; + + /// No description provided for @amapEvening. + /// + /// In fr, this message translates to: + /// **'Soir'** + String get amapEvening; + + /// No description provided for @amapExpectingNumber. + /// + /// In fr, this message translates to: + /// **'Veuillez entrer un nombre'** + String get amapExpectingNumber; + + /// No description provided for @amapFillField. + /// + /// In fr, this message translates to: + /// **'Veuillez remplir ce champ'** + String get amapFillField; + + /// No description provided for @amapHandlingAccount. + /// + /// In fr, this message translates to: + /// **'Gérer les comptes'** + String get amapHandlingAccount; + + /// No description provided for @amapLoading. + /// + /// In fr, this message translates to: + /// **'Chargement...'** + String get amapLoading; + + /// No description provided for @amapLoadingError. + /// + /// In fr, this message translates to: + /// **'Erreur lors du chargement'** + String get amapLoadingError; + + /// No description provided for @amapLock. + /// + /// In fr, this message translates to: + /// **'Verrouiller'** + String get amapLock; + + /// No description provided for @amapLocked. + /// + /// In fr, this message translates to: + /// **'Verrouillée'** + String get amapLocked; + + /// No description provided for @amapLockedDelivery. + /// + /// In fr, this message translates to: + /// **'Livraison verrouillée'** + String get amapLockedDelivery; + + /// No description provided for @amapLockedOrder. + /// + /// In fr, this message translates to: + /// **'Commande verrouillée'** + String get amapLockedOrder; + + /// No description provided for @amapLooking. + /// + /// In fr, this message translates to: + /// **'Rechercher'** + String get amapLooking; + + /// No description provided for @amapLockingDelivery. + /// + /// In fr, this message translates to: + /// **'Verrouiller la livraison ?'** + String get amapLockingDelivery; + + /// No description provided for @amapMidDay. + /// + /// In fr, this message translates to: + /// **'Midi'** + String get amapMidDay; + + /// No description provided for @amapMyOrders. + /// + /// In fr, this message translates to: + /// **'Mes commandes'** + String get amapMyOrders; + + /// No description provided for @amapName. + /// + /// In fr, this message translates to: + /// **'Nom'** + String get amapName; + + /// No description provided for @amapNextStep. + /// + /// In fr, this message translates to: + /// **'Étape suivante'** + String get amapNextStep; + + /// No description provided for @amapNoProduct. + /// + /// In fr, this message translates to: + /// **'Pas de produit'** + String get amapNoProduct; + + /// No description provided for @amapNoCurrentOrder. + /// + /// In fr, this message translates to: + /// **'Pas de commande en cours'** + String get amapNoCurrentOrder; + + /// No description provided for @amapNoMoney. + /// + /// In fr, this message translates to: + /// **'Pas assez d\'argent'** + String get amapNoMoney; + + /// No description provided for @amapNoOpennedDelivery. + /// + /// In fr, this message translates to: + /// **'Pas de livraison ouverte'** + String get amapNoOpennedDelivery; + + /// No description provided for @amapNoOrder. + /// + /// In fr, this message translates to: + /// **'Pas de commande'** + String get amapNoOrder; + + /// No description provided for @amapNoSelectedDelivery. + /// + /// In fr, this message translates to: + /// **'Pas de livraison sélectionnée'** + String get amapNoSelectedDelivery; + + /// No description provided for @amapNotEnoughMoney. + /// + /// In fr, this message translates to: + /// **'Pas assez d\'argent'** + String get amapNotEnoughMoney; + + /// No description provided for @amapNotPlannedDelivery. + /// + /// In fr, this message translates to: + /// **'Pas de livraison planifiée'** + String get amapNotPlannedDelivery; + + /// No description provided for @amapOneOrder. + /// + /// In fr, this message translates to: + /// **'commande'** + String get amapOneOrder; + + /// No description provided for @amapOpenDelivery. + /// + /// In fr, this message translates to: + /// **'Ouvrir'** + String get amapOpenDelivery; + + /// No description provided for @amapOpened. + /// + /// In fr, this message translates to: + /// **'Ouverte'** + String get amapOpened; + + /// No description provided for @amapOpenningDelivery. + /// + /// In fr, this message translates to: + /// **'Ouvrir la livraison ?'** + String get amapOpenningDelivery; + + /// No description provided for @amapOrder. + /// + /// In fr, this message translates to: + /// **'Commander'** + String get amapOrder; + + /// No description provided for @amapOrders. + /// + /// In fr, this message translates to: + /// **'Commandes'** + String get amapOrders; + + /// No description provided for @amapPickChooseCategory. + /// + /// In fr, this message translates to: + /// **'Veuillez entrer une valeur ou choisir une catégorie existante'** + String get amapPickChooseCategory; + + /// No description provided for @amapPickDeliveryMoment. + /// + /// In fr, this message translates to: + /// **'Choisissez un moment de livraison'** + String get amapPickDeliveryMoment; + + /// No description provided for @amapPresentation. + /// + /// In fr, this message translates to: + /// **'Présentation'** + String get amapPresentation; + + /// No description provided for @amapPresentation1. + /// + /// In fr, this message translates to: + /// **'L\'AMAP (association pour le maintien d\'une agriculture paysanne) est un service proposé par l\'association Planet&Co de l\'ECL. Vous pouvez ainsi recevoir des produits (paniers de fruits et légumes, jus, confitures...) directement sur le campus !\n\nLes commandes doivent être passées avant le vendredi 21h et sont livrées sur le campus le mardi de 13h à 13h45 (ou de 18h15 à 18h30 si vous ne pouvez pas passer le midi) dans le hall du M16.\n\nVous ne pouvez commander que si votre solde le permet. Vous pouvez recharger votre solde via la collecte Lydia ou bien avec un chèque que vous pouvez nous transmettre lors des permanences.\n\nLien vers la collecte Lydia pour le rechargement : '** + String get amapPresentation1; + + /// No description provided for @amapPresentation2. + /// + /// In fr, this message translates to: + /// **'\n\nN\'hésitez pas à nous contacter en cas de problème !'** + String get amapPresentation2; + + /// No description provided for @amapPrice. + /// + /// In fr, this message translates to: + /// **'Prix'** + String get amapPrice; + + /// No description provided for @amapProduct. + /// + /// In fr, this message translates to: + /// **'produit'** + String get amapProduct; + + /// No description provided for @amapProducts. + /// + /// In fr, this message translates to: + /// **'Produits'** + String get amapProducts; + + /// No description provided for @amapProductInDelivery. + /// + /// In fr, this message translates to: + /// **'Produit dans une livraison non terminée'** + String get amapProductInDelivery; + + /// No description provided for @amapQuantity. + /// + /// In fr, this message translates to: + /// **'Quantité'** + String get amapQuantity; + + /// No description provided for @amapRequiredDate. + /// + /// In fr, this message translates to: + /// **'La date est requise'** + String get amapRequiredDate; + + /// No description provided for @amapSeeMore. + /// + /// In fr, this message translates to: + /// **'Voir plus'** + String get amapSeeMore; + + /// No description provided for @amapThe. + /// + /// In fr, this message translates to: + /// **'Le'** + String get amapThe; + + /// No description provided for @amapUnlock. + /// + /// In fr, this message translates to: + /// **'Dévérouiller'** + String get amapUnlock; + + /// No description provided for @amapUnlockedDelivery. + /// + /// In fr, this message translates to: + /// **'Livraison dévérouillée'** + String get amapUnlockedDelivery; + + /// No description provided for @amapUnlockingDelivery. + /// + /// In fr, this message translates to: + /// **'Dévérouiller la livraison ?'** + String get amapUnlockingDelivery; + + /// No description provided for @amapUpdate. + /// + /// In fr, this message translates to: + /// **'Modifier'** + String get amapUpdate; + + /// No description provided for @amapUpdatedAmount. + /// + /// In fr, this message translates to: + /// **'Solde modifié'** + String get amapUpdatedAmount; + + /// No description provided for @amapUpdatedOrder. + /// + /// In fr, this message translates to: + /// **'Commande modifiée'** + String get amapUpdatedOrder; + + /// No description provided for @amapUpdatedProduct. + /// + /// In fr, this message translates to: + /// **'Produit modifié'** + String get amapUpdatedProduct; + + /// No description provided for @amapUpdatingError. + /// + /// In fr, this message translates to: + /// **'Echec de la modification'** + String get amapUpdatingError; + + /// No description provided for @amapUsersNotFound. + /// + /// In fr, this message translates to: + /// **'Aucun utilisateur trouvé'** + String get amapUsersNotFound; + + /// No description provided for @amapWaiting. + /// + /// In fr, this message translates to: + /// **'En attente'** + String get amapWaiting; + + /// No description provided for @bookingAdd. + /// + /// In fr, this message translates to: + /// **'Ajouter'** + String get bookingAdd; + + /// No description provided for @bookingAddBookingPage. + /// + /// In fr, this message translates to: + /// **'Demande'** + String get bookingAddBookingPage; + + /// No description provided for @bookingAddRoom. + /// + /// In fr, this message translates to: + /// **'Ajouter une salle'** + String get bookingAddRoom; + + /// No description provided for @bookingAddBooking. + /// + /// In fr, this message translates to: + /// **'Ajouter une réservation'** + String get bookingAddBooking; + + /// No description provided for @bookingAddedBooking. + /// + /// In fr, this message translates to: + /// **'Demande ajoutée'** + String get bookingAddedBooking; + + /// No description provided for @bookingAddedRoom. + /// + /// In fr, this message translates to: + /// **'Salle ajoutée'** + String get bookingAddedRoom; + + /// No description provided for @bookingAddedManager. + /// + /// In fr, this message translates to: + /// **'Gestionnaire ajouté'** + String get bookingAddedManager; + + /// No description provided for @bookingAddingError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de l\'ajout'** + String get bookingAddingError; + + /// No description provided for @bookingAddManager. + /// + /// In fr, this message translates to: + /// **'Ajouter un gestionnaire'** + String get bookingAddManager; + + /// No description provided for @bookingAdminPage. + /// + /// In fr, this message translates to: + /// **'Administrateur'** + String get bookingAdminPage; + + /// No description provided for @bookingAllDay. + /// + /// In fr, this message translates to: + /// **'Toute la journée'** + String get bookingAllDay; + + /// No description provided for @bookingBookedFor. + /// + /// In fr, this message translates to: + /// **'Réservé pour'** + String get bookingBookedFor; + + /// No description provided for @bookingBooking. + /// + /// In fr, this message translates to: + /// **'Réservation'** + String get bookingBooking; + + /// No description provided for @bookingBookingCreated. + /// + /// In fr, this message translates to: + /// **'Réservation créée'** + String get bookingBookingCreated; + + /// No description provided for @bookingBookingDemand. + /// + /// In fr, this message translates to: + /// **'Demande de réservation'** + String get bookingBookingDemand; + + /// No description provided for @bookingBookingNote. + /// + /// In fr, this message translates to: + /// **'Note de la réservation'** + String get bookingBookingNote; + + /// No description provided for @bookingBookingPage. + /// + /// In fr, this message translates to: + /// **'Réservation'** + String get bookingBookingPage; + + /// No description provided for @bookingBookingReason. + /// + /// In fr, this message translates to: + /// **'Motif de la réservation'** + String get bookingBookingReason; + + /// No description provided for @bookingBy. + /// + /// In fr, this message translates to: + /// **'par'** + String get bookingBy; + + /// No description provided for @bookingConfirm. + /// + /// In fr, this message translates to: + /// **'Confirmer'** + String get bookingConfirm; + + /// No description provided for @bookingConfirmation. + /// + /// In fr, this message translates to: + /// **'Confirmation'** + String get bookingConfirmation; + + /// No description provided for @bookingConfirmBooking. + /// + /// In fr, this message translates to: + /// **'Confirmer la réservation ?'** + String get bookingConfirmBooking; + + /// No description provided for @bookingConfirmed. + /// + /// In fr, this message translates to: + /// **'Validée'** + String get bookingConfirmed; + + /// No description provided for @bookingDates. + /// + /// In fr, this message translates to: + /// **'Dates'** + String get bookingDates; + + /// No description provided for @bookingDecline. + /// + /// In fr, this message translates to: + /// **'Refuser'** + String get bookingDecline; + + /// No description provided for @bookingDeclineBooking. + /// + /// In fr, this message translates to: + /// **'Refuser la réservation ?'** + String get bookingDeclineBooking; + + /// No description provided for @bookingDeclined. + /// + /// In fr, this message translates to: + /// **'Refusée'** + String get bookingDeclined; + + /// No description provided for @bookingDelete. + /// + /// In fr, this message translates to: + /// **'Supprimer'** + String get bookingDelete; + + /// No description provided for @bookingDeleting. + /// + /// In fr, this message translates to: + /// **'Suppression'** + String get bookingDeleting; + + /// No description provided for @bookingDeleteBooking. + /// + /// In fr, this message translates to: + /// **'Suppression'** + String get bookingDeleteBooking; + + /// No description provided for @bookingDeleteBookingConfirmation. + /// + /// In fr, this message translates to: + /// **'Êtes-vous sûr de vouloir supprimer cette réservation ?'** + String get bookingDeleteBookingConfirmation; + + /// No description provided for @bookingDeletedBooking. + /// + /// In fr, this message translates to: + /// **'Réservation supprimée'** + String get bookingDeletedBooking; + + /// No description provided for @bookingDeletedRoom. + /// + /// In fr, this message translates to: + /// **'Salle supprimée'** + String get bookingDeletedRoom; + + /// No description provided for @bookingDeletedManager. + /// + /// In fr, this message translates to: + /// **'Gestionnaire supprimé'** + String get bookingDeletedManager; + + /// No description provided for @bookingDeleteRoomConfirmation. + /// + /// In fr, this message translates to: + /// **'Êtes-vous sûr de vouloir supprimer cette salle ?\n\nLa salle ne doit avoir aucune réservation en cours ou à venir pour être supprimée'** + String get bookingDeleteRoomConfirmation; + + /// No description provided for @bookingDeleteManagerConfirmation. + /// + /// In fr, this message translates to: + /// **'Êtes-vous sûr de vouloir supprimer ce gestionnaire ?\n\nLe gestionnaire ne doit être associé à aucune salle pour pouvoir être supprimé'** + String get bookingDeleteManagerConfirmation; + + /// No description provided for @bookingDeletingBooking. + /// + /// In fr, this message translates to: + /// **'Supprimer la réservation ?'** + String get bookingDeletingBooking; + + /// No description provided for @bookingDeletingError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de la suppression'** + String get bookingDeletingError; + + /// No description provided for @bookingDeletingRoom. + /// + /// In fr, this message translates to: + /// **'Supprimer la salle ?'** + String get bookingDeletingRoom; + + /// No description provided for @bookingEdit. + /// + /// In fr, this message translates to: + /// **'Modifier'** + String get bookingEdit; + + /// No description provided for @bookingEditBooking. + /// + /// In fr, this message translates to: + /// **'Modifier une réservation'** + String get bookingEditBooking; + + /// No description provided for @bookingEditionError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de la modification'** + String get bookingEditionError; + + /// No description provided for @bookingEditedBooking. + /// + /// In fr, this message translates to: + /// **'Réservation modifiée'** + String get bookingEditedBooking; + + /// No description provided for @bookingEditedRoom. + /// + /// In fr, this message translates to: + /// **'Salle modifiée'** + String get bookingEditedRoom; + + /// No description provided for @bookingEditedManager. + /// + /// In fr, this message translates to: + /// **'Gestionnaire modifié'** + String get bookingEditedManager; + + /// No description provided for @bookingEditManager. + /// + /// In fr, this message translates to: + /// **'Modifier ou supprimer un gestionnaire'** + String get bookingEditManager; + + /// No description provided for @bookingEditRoom. + /// + /// In fr, this message translates to: + /// **'Modifier ou supprimer une salle'** + String get bookingEditRoom; + + /// No description provided for @bookingEndDate. + /// + /// In fr, this message translates to: + /// **'Date de fin'** + String get bookingEndDate; + + /// No description provided for @bookingEndHour. + /// + /// In fr, this message translates to: + /// **'Heure de fin'** + String get bookingEndHour; + + /// No description provided for @bookingEntity. + /// + /// In fr, this message translates to: + /// **'Pour qui ?'** + String get bookingEntity; + + /// No description provided for @bookingError. + /// + /// In fr, this message translates to: + /// **'Erreur'** + String get bookingError; + + /// No description provided for @bookingEventEvery. + /// + /// In fr, this message translates to: + /// **'Tous les'** + String get bookingEventEvery; + + /// No description provided for @bookingHistoryPage. + /// + /// In fr, this message translates to: + /// **'Historique'** + String get bookingHistoryPage; + + /// No description provided for @bookingIncorrectOrMissingFields. + /// + /// In fr, this message translates to: + /// **'Champs incorrects ou manquants'** + String get bookingIncorrectOrMissingFields; + + /// No description provided for @bookingInterval. + /// + /// In fr, this message translates to: + /// **'Intervalle'** + String get bookingInterval; + + /// No description provided for @bookingInvalidIntervalError. + /// + /// In fr, this message translates to: + /// **'Intervalle invalide'** + String get bookingInvalidIntervalError; + + /// No description provided for @bookingInvalidDates. + /// + /// In fr, this message translates to: + /// **'Dates invalides'** + String get bookingInvalidDates; + + /// No description provided for @bookingInvalidRoom. + /// + /// In fr, this message translates to: + /// **'Salle invalide'** + String get bookingInvalidRoom; + + /// No description provided for @bookingKeysRequested. + /// + /// In fr, this message translates to: + /// **'Clés demandées'** + String get bookingKeysRequested; + + /// No description provided for @bookingManagement. + /// + /// In fr, this message translates to: + /// **'Gestion'** + String get bookingManagement; + + /// No description provided for @bookingManager. + /// + /// In fr, this message translates to: + /// **'Gestionnaire'** + String get bookingManager; + + /// No description provided for @bookingManagerName. + /// + /// In fr, this message translates to: + /// **'Nom du gestionnaire'** + String get bookingManagerName; + + /// No description provided for @bookingMultipleDay. + /// + /// In fr, this message translates to: + /// **'Plusieurs jours'** + String get bookingMultipleDay; + + /// No description provided for @bookingMyBookings. + /// + /// In fr, this message translates to: + /// **'Mes réservations'** + String get bookingMyBookings; + + /// No description provided for @bookingNecessaryKey. + /// + /// In fr, this message translates to: + /// **'Clé nécessaire'** + String get bookingNecessaryKey; + + /// No description provided for @bookingNext. + /// + /// In fr, this message translates to: + /// **'Suivant'** + String get bookingNext; + + /// No description provided for @bookingNo. + /// + /// In fr, this message translates to: + /// **'Non'** + String get bookingNo; + + /// No description provided for @bookingNoCurrentBooking. + /// + /// In fr, this message translates to: + /// **'Pas de réservation en cours'** + String get bookingNoCurrentBooking; + + /// No description provided for @bookingNoDateError. + /// + /// In fr, this message translates to: + /// **'Veuillez choisir une date'** + String get bookingNoDateError; + + /// No description provided for @bookingNoAppointmentInReccurence. + /// + /// In fr, this message translates to: + /// **'Aucun créneau existe avec ces paramètres de récurrence'** + String get bookingNoAppointmentInReccurence; + + /// No description provided for @bookingNoDaySelected. + /// + /// In fr, this message translates to: + /// **'Aucun jour sélectionné'** + String get bookingNoDaySelected; + + /// No description provided for @bookingNoDescriptionError. + /// + /// In fr, this message translates to: + /// **'Veuillez entrer une description'** + String get bookingNoDescriptionError; + + /// No description provided for @bookingNoKeys. + /// + /// In fr, this message translates to: + /// **'Aucune clé'** + String get bookingNoKeys; + + /// No description provided for @bookingNoNoteError. + /// + /// In fr, this message translates to: + /// **'Veuillez entrer une note'** + String get bookingNoNoteError; + + /// No description provided for @bookingNoPhoneRegistered. + /// + /// In fr, this message translates to: + /// **'Numéro non renseigné'** + String get bookingNoPhoneRegistered; + + /// No description provided for @bookingNoReasonError. + /// + /// In fr, this message translates to: + /// **'Veuillez entrer un motif'** + String get bookingNoReasonError; + + /// No description provided for @bookingNoRoomFoundError. + /// + /// In fr, this message translates to: + /// **'Aucune salle enregistrée'** + String get bookingNoRoomFoundError; + + /// No description provided for @bookingNoRoomFound. + /// + /// In fr, this message translates to: + /// **'Aucune salle trouvée'** + String get bookingNoRoomFound; + + /// No description provided for @bookingNote. + /// + /// In fr, this message translates to: + /// **'Note'** + String get bookingNote; + + /// No description provided for @bookingOther. + /// + /// In fr, this message translates to: + /// **'Autre'** + String get bookingOther; + + /// No description provided for @bookingPending. + /// + /// In fr, this message translates to: + /// **'En attente'** + String get bookingPending; + + /// No description provided for @bookingPrevious. + /// + /// In fr, this message translates to: + /// **'Précédent'** + String get bookingPrevious; + + /// No description provided for @bookingReason. + /// + /// In fr, this message translates to: + /// **'Motif'** + String get bookingReason; + + /// No description provided for @bookingRecurrence. + /// + /// In fr, this message translates to: + /// **'Récurrence'** + String get bookingRecurrence; + + /// No description provided for @bookingRecurrenceDays. + /// + /// In fr, this message translates to: + /// **'Jours de récurrence'** + String get bookingRecurrenceDays; + + /// No description provided for @bookingRecurrenceEndDate. + /// + /// In fr, this message translates to: + /// **'Date de fin de récurrence'** + String get bookingRecurrenceEndDate; + + /// No description provided for @bookingRecurrent. + /// + /// In fr, this message translates to: + /// **'Récurrent'** + String get bookingRecurrent; + + /// No description provided for @bookingRegisteredRooms. + /// + /// In fr, this message translates to: + /// **'Salles enregistrées'** + String get bookingRegisteredRooms; + + /// No description provided for @bookingRoom. + /// + /// In fr, this message translates to: + /// **'Salle'** + String get bookingRoom; + + /// No description provided for @bookingRoomName. + /// + /// In fr, this message translates to: + /// **'Nom de la salle'** + String get bookingRoomName; + + /// No description provided for @bookingStartDate. + /// + /// In fr, this message translates to: + /// **'Date de début'** + String get bookingStartDate; + + /// No description provided for @bookingStartHour. + /// + /// In fr, this message translates to: + /// **'Heure de début'** + String get bookingStartHour; + + /// No description provided for @bookingWeeks. + /// + /// In fr, this message translates to: + /// **'Semaines'** + String get bookingWeeks; + + /// No description provided for @bookingYes. + /// + /// In fr, this message translates to: + /// **'Oui'** + String get bookingYes; + + /// No description provided for @bookingWeekDayMon. + /// + /// In fr, this message translates to: + /// **'Lundi'** + String get bookingWeekDayMon; + + /// No description provided for @bookingWeekDayTue. + /// + /// In fr, this message translates to: + /// **'Mardi'** + String get bookingWeekDayTue; + + /// No description provided for @bookingWeekDayWed. + /// + /// In fr, this message translates to: + /// **'Mercredi'** + String get bookingWeekDayWed; + + /// No description provided for @bookingWeekDayThu. + /// + /// In fr, this message translates to: + /// **'Jeudi'** + String get bookingWeekDayThu; + + /// No description provided for @bookingWeekDayFri. + /// + /// In fr, this message translates to: + /// **'Vendredi'** + String get bookingWeekDayFri; + + /// No description provided for @bookingWeekDaySat. + /// + /// In fr, this message translates to: + /// **'Samedi'** + String get bookingWeekDaySat; + + /// No description provided for @bookingWeekDaySun. + /// + /// In fr, this message translates to: + /// **'Dimanche'** + String get bookingWeekDaySun; + + /// No description provided for @cinemaAdd. + /// + /// In fr, this message translates to: + /// **'Ajouter'** + String get cinemaAdd; + + /// No description provided for @cinemaAddedSession. + /// + /// In fr, this message translates to: + /// **'Séance ajoutée'** + String get cinemaAddedSession; + + /// No description provided for @cinemaAddingError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de l\'ajout'** + String get cinemaAddingError; + + /// No description provided for @cinemaAddSession. + /// + /// In fr, this message translates to: + /// **'Ajouter une séance'** + String get cinemaAddSession; + + /// No description provided for @cinemaCinema. + /// + /// In fr, this message translates to: + /// **'Cinéma'** + String get cinemaCinema; + + /// No description provided for @cinemaDeleteSession. + /// + /// In fr, this message translates to: + /// **'Supprimer la séance ?'** + String get cinemaDeleteSession; + + /// No description provided for @cinemaDeleting. + /// + /// In fr, this message translates to: + /// **'Suppression'** + String get cinemaDeleting; + + /// No description provided for @cinemaDuration. + /// + /// In fr, this message translates to: + /// **'Durée'** + String get cinemaDuration; + + /// No description provided for @cinemaEdit. + /// + /// In fr, this message translates to: + /// **'Modifier'** + String get cinemaEdit; + + /// No description provided for @cinemaEditedSession. + /// + /// In fr, this message translates to: + /// **'Séance modifiée'** + String get cinemaEditedSession; + + /// No description provided for @cinemaEditingError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de la modification'** + String get cinemaEditingError; + + /// No description provided for @cinemaEditSession. + /// + /// In fr, this message translates to: + /// **'Modifier la séance'** + String get cinemaEditSession; + + /// No description provided for @cinemaEmptyUrl. + /// + /// In fr, this message translates to: + /// **'Veuillez entrer une URL'** + String get cinemaEmptyUrl; + + /// No description provided for @cinemaImportFromTMDB. + /// + /// In fr, this message translates to: + /// **'Importer depuis TMDB'** + String get cinemaImportFromTMDB; + + /// No description provided for @cinemaIncomingSession. + /// + /// In fr, this message translates to: + /// **'A l\'affiche'** + String get cinemaIncomingSession; + + /// No description provided for @cinemaIncorrectOrMissingFields. + /// + /// In fr, this message translates to: + /// **'Champs incorrects ou manquants'** + String get cinemaIncorrectOrMissingFields; + + /// No description provided for @cinemaInvalidUrl. + /// + /// In fr, this message translates to: + /// **'URL invalide'** + String get cinemaInvalidUrl; + + /// No description provided for @cinemaGenre. + /// + /// In fr, this message translates to: + /// **'Genre'** + String get cinemaGenre; + + /// No description provided for @cinemaName. + /// + /// In fr, this message translates to: + /// **'Nom'** + String get cinemaName; + + /// No description provided for @cinemaNoDateError. + /// + /// In fr, this message translates to: + /// **'Veuillez entrer une date'** + String get cinemaNoDateError; + + /// No description provided for @cinemaNoDuration. + /// + /// In fr, this message translates to: + /// **'Veuillez entrer une durée'** + String get cinemaNoDuration; + + /// No description provided for @cinemaNoOverview. + /// + /// In fr, this message translates to: + /// **'Aucun synopsis'** + String get cinemaNoOverview; + + /// No description provided for @cinemaNoPoster. + /// + /// In fr, this message translates to: + /// **'Aucune affiche'** + String get cinemaNoPoster; + + /// No description provided for @cinemaNoSession. + /// + /// In fr, this message translates to: + /// **'Aucune séance'** + String get cinemaNoSession; + + /// No description provided for @cinemaOverview. + /// + /// In fr, this message translates to: + /// **'Synopsis'** + String get cinemaOverview; + + /// No description provided for @cinemaPosterUrl. + /// + /// In fr, this message translates to: + /// **'URL de l\'affiche'** + String get cinemaPosterUrl; + + /// No description provided for @cinemaSessionDate. + /// + /// In fr, this message translates to: + /// **'Jour de la séance'** + String get cinemaSessionDate; + + /// No description provided for @cinemaStartHour. + /// + /// In fr, this message translates to: + /// **'Heure de début'** + String get cinemaStartHour; + + /// No description provided for @cinemaTagline. + /// + /// In fr, this message translates to: + /// **'Slogan'** + String get cinemaTagline; + + /// No description provided for @cinemaThe. + /// + /// In fr, this message translates to: + /// **'Le'** + String get cinemaThe; + + /// No description provided for @drawerAdmin. + /// + /// In fr, this message translates to: + /// **'Administration'** + String get drawerAdmin; + + /// No description provided for @drawerAndroidAppLink. + /// + /// In fr, this message translates to: + /// **'https://play.google.com/store/apps/details?id=fr.myecl.titan'** + String get drawerAndroidAppLink; + + /// No description provided for @drawerCopied. + /// + /// In fr, this message translates to: + /// **'Copié !'** + String get drawerCopied; + + /// No description provided for @drawerDownloadAppOnMobileDevice. + /// + /// In fr, this message translates to: + /// **'Ce site est la version Web de l\'application MyECL. Nous vous invitons à télécharger l\'application. N\'utilisez ce site qu\'en cas de problème avec l\'application.\n'** + String get drawerDownloadAppOnMobileDevice; + + /// No description provided for @drawerIosAppLink. + /// + /// In fr, this message translates to: + /// **'https://apps.apple.com/fr/app/myecl/id6444443430'** + String get drawerIosAppLink; + + /// No description provided for @drawerLoginOut. + /// + /// In fr, this message translates to: + /// **'Voulez-vous vous déconnecter ?'** + String get drawerLoginOut; + + /// No description provided for @drawerLogOut. + /// + /// In fr, this message translates to: + /// **'Déconnexion'** + String get drawerLogOut; + + /// No description provided for @drawerOr. + /// + /// In fr, this message translates to: + /// **' ou '** + String get drawerOr; + + /// No description provided for @drawerSettings. + /// + /// In fr, this message translates to: + /// **'Paramètres'** + String get drawerSettings; + + /// No description provided for @eventAdd. + /// + /// In fr, this message translates to: + /// **'Ajouter'** + String get eventAdd; + + /// No description provided for @eventAddEvent. + /// + /// In fr, this message translates to: + /// **'Ajouter un événement'** + String get eventAddEvent; + + /// No description provided for @eventAddedEvent. + /// + /// In fr, this message translates to: + /// **'Événement ajouté'** + String get eventAddedEvent; + + /// No description provided for @eventAddingError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de l\'ajout'** + String get eventAddingError; + + /// No description provided for @eventAllDay. + /// + /// In fr, this message translates to: + /// **'Toute la journée'** + String get eventAllDay; + + /// No description provided for @eventConfirm. + /// + /// In fr, this message translates to: + /// **'Confirmer'** + String get eventConfirm; + + /// No description provided for @eventConfirmEvent. + /// + /// In fr, this message translates to: + /// **'Confirmer l\'événement ?'** + String get eventConfirmEvent; + + /// No description provided for @eventConfirmation. + /// + /// In fr, this message translates to: + /// **'Confirmation'** + String get eventConfirmation; + + /// No description provided for @eventConfirmed. + /// + /// In fr, this message translates to: + /// **'Confirmé'** + String get eventConfirmed; + + /// No description provided for @eventDates. + /// + /// In fr, this message translates to: + /// **'Dates'** + String get eventDates; + + /// No description provided for @eventDecline. + /// + /// In fr, this message translates to: + /// **'Refuser'** + String get eventDecline; + + /// No description provided for @eventDeclineEvent. + /// + /// In fr, this message translates to: + /// **'Refuser l\'événement ?'** + String get eventDeclineEvent; + + /// No description provided for @eventDeclined. + /// + /// In fr, this message translates to: + /// **'Refusé'** + String get eventDeclined; + + /// No description provided for @eventDelete. + /// + /// In fr, this message translates to: + /// **'Supprimer'** + String get eventDelete; + + /// No description provided for @eventDeletedEvent. + /// + /// In fr, this message translates to: + /// **'Événement supprimé'** + String get eventDeletedEvent; + + /// No description provided for @eventDeleting. + /// + /// In fr, this message translates to: + /// **'Suppression'** + String get eventDeleting; + + /// No description provided for @eventDeletingError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de la suppression'** + String get eventDeletingError; + + /// No description provided for @eventDeletingEvent. + /// + /// In fr, this message translates to: + /// **'Supprimer l\'événement ?'** + String get eventDeletingEvent; + + /// No description provided for @eventDescription. + /// + /// In fr, this message translates to: + /// **'Description'** + String get eventDescription; + + /// No description provided for @eventEdit. + /// + /// In fr, this message translates to: + /// **'Modifier'** + String get eventEdit; + + /// No description provided for @eventEditEvent. + /// + /// In fr, this message translates to: + /// **'Modifier un événement'** + String get eventEditEvent; + + /// No description provided for @eventEditedEvent. + /// + /// In fr, this message translates to: + /// **'Événement modifié'** + String get eventEditedEvent; + + /// No description provided for @eventEditingError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de la modification'** + String get eventEditingError; + + /// No description provided for @eventEndDate. + /// + /// In fr, this message translates to: + /// **'Date de fin'** + String get eventEndDate; + + /// No description provided for @eventEndHour. + /// + /// In fr, this message translates to: + /// **'Heure de fin'** + String get eventEndHour; + + /// No description provided for @eventError. + /// + /// In fr, this message translates to: + /// **'Erreur'** + String get eventError; + + /// No description provided for @eventEventList. + /// + /// In fr, this message translates to: + /// **'Liste des événements'** + String get eventEventList; + + /// No description provided for @eventEventType. + /// + /// In fr, this message translates to: + /// **'Type d\'événement'** + String get eventEventType; + + /// No description provided for @eventEvery. + /// + /// In fr, this message translates to: + /// **'Tous les'** + String get eventEvery; + + /// No description provided for @eventHistory. + /// + /// In fr, this message translates to: + /// **'Historique'** + String get eventHistory; + + /// No description provided for @eventIncorrectOrMissingFields. + /// + /// In fr, this message translates to: + /// **'Certains champs sont incorrects ou manquants'** + String get eventIncorrectOrMissingFields; + + /// No description provided for @eventInterval. + /// + /// In fr, this message translates to: + /// **'Intervalle'** + String get eventInterval; + + /// No description provided for @eventInvalidDates. + /// + /// In fr, this message translates to: + /// **'La date de fin doit être après la date de début'** + String get eventInvalidDates; + + /// No description provided for @eventInvalidIntervalError. + /// + /// In fr, this message translates to: + /// **'Veuillez entrer un intervalle valide'** + String get eventInvalidIntervalError; + + /// No description provided for @eventLocation. + /// + /// In fr, this message translates to: + /// **'Lieu'** + String get eventLocation; + + /// No description provided for @eventMyEvents. + /// + /// In fr, this message translates to: + /// **'Mes événements'** + String get eventMyEvents; + + /// No description provided for @eventName. + /// + /// In fr, this message translates to: + /// **'Nom'** + String get eventName; + + /// No description provided for @eventNext. + /// + /// In fr, this message translates to: + /// **'Suivant'** + String get eventNext; + + /// No description provided for @eventNo. + /// + /// In fr, this message translates to: + /// **'Non'** + String get eventNo; + + /// No description provided for @eventNoCurrentEvent. + /// + /// In fr, this message translates to: + /// **'Aucun événement en cours'** + String get eventNoCurrentEvent; + + /// No description provided for @eventNoDateError. + /// + /// In fr, this message translates to: + /// **'Veuillez entrer une date'** + String get eventNoDateError; + + /// No description provided for @eventNoDaySelected. + /// + /// In fr, this message translates to: + /// **'Aucun jour sélectionné'** + String get eventNoDaySelected; + + /// No description provided for @eventNoDescriptionError. + /// + /// In fr, this message translates to: + /// **'Veuillez entrer une description'** + String get eventNoDescriptionError; + + /// No description provided for @eventNoEvent. + /// + /// In fr, this message translates to: + /// **'Aucun événement'** + String get eventNoEvent; + + /// No description provided for @eventNoNameError. + /// + /// In fr, this message translates to: + /// **'Veuillez entrer un nom'** + String get eventNoNameError; + + /// No description provided for @eventNoOrganizerError. + /// + /// In fr, this message translates to: + /// **'Veuillez entrer un organisateur'** + String get eventNoOrganizerError; + + /// No description provided for @eventNoPlaceError. + /// + /// In fr, this message translates to: + /// **'Veuillez entrer un lieu'** + String get eventNoPlaceError; + + /// No description provided for @eventNoPhoneRegistered. + /// + /// In fr, this message translates to: + /// **'Numéro non renseigné'** + String get eventNoPhoneRegistered; + + /// No description provided for @eventNoRuleError. + /// + /// In fr, this message translates to: + /// **'Veuillez entrer une règle de récurrence'** + String get eventNoRuleError; + + /// No description provided for @eventOrganizer. + /// + /// In fr, this message translates to: + /// **'Organisateur'** + String get eventOrganizer; + + /// No description provided for @eventOther. + /// + /// In fr, this message translates to: + /// **'Autre'** + String get eventOther; + + /// No description provided for @eventPending. + /// + /// In fr, this message translates to: + /// **'En attente'** + String get eventPending; + + /// No description provided for @eventPrevious. + /// + /// In fr, this message translates to: + /// **'Précédent'** + String get eventPrevious; + + /// No description provided for @eventRecurrence. + /// + /// In fr, this message translates to: + /// **'Récurrence'** + String get eventRecurrence; + + /// No description provided for @eventRecurrenceDays. + /// + /// In fr, this message translates to: + /// **'Jours de récurrence'** + String get eventRecurrenceDays; + + /// No description provided for @eventRecurrenceEndDate. + /// + /// In fr, this message translates to: + /// **'Date de fin de la récurrence'** + String get eventRecurrenceEndDate; + + /// No description provided for @eventRecurrenceRule. + /// + /// In fr, this message translates to: + /// **'Règle de récurrence'** + String get eventRecurrenceRule; + + /// No description provided for @eventRoom. + /// + /// In fr, this message translates to: + /// **'Salle'** + String get eventRoom; + + /// No description provided for @eventStartDate. + /// + /// In fr, this message translates to: + /// **'Date de début'** + String get eventStartDate; + + /// No description provided for @eventStartHour. + /// + /// In fr, this message translates to: + /// **'Heure de début'** + String get eventStartHour; + + /// No description provided for @eventTitle. + /// + /// In fr, this message translates to: + /// **'Événements'** + String get eventTitle; + + /// No description provided for @eventYes. + /// + /// In fr, this message translates to: + /// **'Oui'** + String get eventYes; + + /// No description provided for @eventEventEvery. + /// + /// In fr, this message translates to: + /// **'Toutes les'** + String get eventEventEvery; + + /// No description provided for @eventWeeks. + /// + /// In fr, this message translates to: + /// **'semaines'** + String get eventWeeks; + + /// No description provided for @eventDayMon. + /// + /// In fr, this message translates to: + /// **'Lundi'** + String get eventDayMon; + + /// No description provided for @eventDayTue. + /// + /// In fr, this message translates to: + /// **'Mardi'** + String get eventDayTue; + + /// No description provided for @eventDayWed. + /// + /// In fr, this message translates to: + /// **'Mercredi'** + String get eventDayWed; + + /// No description provided for @eventDayThu. + /// + /// In fr, this message translates to: + /// **'Jeudi'** + String get eventDayThu; + + /// No description provided for @eventDayFri. + /// + /// In fr, this message translates to: + /// **'Vendredi'** + String get eventDayFri; + + /// No description provided for @eventDaySat. + /// + /// In fr, this message translates to: + /// **'Samedi'** + String get eventDaySat; + + /// No description provided for @eventDaySun. + /// + /// In fr, this message translates to: + /// **'Dimanche'** + String get eventDaySun; + + /// No description provided for @homeCalendar. + /// + /// In fr, this message translates to: + /// **'Calendrier'** + String get homeCalendar; + + /// No description provided for @homeEventOf. + /// + /// In fr, this message translates to: + /// **'Évènements du'** + String get homeEventOf; + + /// No description provided for @homeIncomingEvents. + /// + /// In fr, this message translates to: + /// **'Évènements à venir'** + String get homeIncomingEvents; + + /// No description provided for @homeLastInfos. + /// + /// In fr, this message translates to: + /// **'Dernières annonces'** + String get homeLastInfos; + + /// No description provided for @homeNoEvents. + /// + /// In fr, this message translates to: + /// **'Aucun évènement'** + String get homeNoEvents; + + /// No description provided for @homeTranslateDayShortMon. + /// + /// In fr, this message translates to: + /// **'Lun'** + String get homeTranslateDayShortMon; + + /// No description provided for @homeTranslateDayShortTue. + /// + /// In fr, this message translates to: + /// **'Mar'** + String get homeTranslateDayShortTue; + + /// No description provided for @homeTranslateDayShortWed. + /// + /// In fr, this message translates to: + /// **'Mer'** + String get homeTranslateDayShortWed; + + /// No description provided for @homeTranslateDayShortThu. + /// + /// In fr, this message translates to: + /// **'Jeu'** + String get homeTranslateDayShortThu; + + /// No description provided for @homeTranslateDayShortFri. + /// + /// In fr, this message translates to: + /// **'Ven'** + String get homeTranslateDayShortFri; + + /// No description provided for @homeTranslateDayShortSat. + /// + /// In fr, this message translates to: + /// **'Sam'** + String get homeTranslateDayShortSat; + + /// No description provided for @homeTranslateDayShortSun. + /// + /// In fr, this message translates to: + /// **'Dim'** + String get homeTranslateDayShortSun; + + /// No description provided for @loanAdd. + /// + /// In fr, this message translates to: + /// **'Ajouter'** + String get loanAdd; + + /// No description provided for @loanAddLoan. + /// + /// In fr, this message translates to: + /// **'Ajouter un prêt'** + String get loanAddLoan; + + /// No description provided for @loanAddObject. + /// + /// In fr, this message translates to: + /// **'Ajouter un objet'** + String get loanAddObject; + + /// No description provided for @loanAddedLoan. + /// + /// In fr, this message translates to: + /// **'Prêt ajouté'** + String get loanAddedLoan; + + /// No description provided for @loanAddedObject. + /// + /// In fr, this message translates to: + /// **'Objet ajouté'** + String get loanAddedObject; + + /// No description provided for @loanAddedRoom. + /// + /// In fr, this message translates to: + /// **'Salle ajoutée'** + String get loanAddedRoom; + + /// No description provided for @loanAddingError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de l\'ajout'** + String get loanAddingError; + + /// No description provided for @loanAdmin. + /// + /// In fr, this message translates to: + /// **'Administrateur'** + String get loanAdmin; + + /// No description provided for @loanAvailable. + /// + /// In fr, this message translates to: + /// **'Disponible'** + String get loanAvailable; + + /// No description provided for @loanAvailableMultiple. + /// + /// In fr, this message translates to: + /// **'Disponibles'** + String get loanAvailableMultiple; + + /// No description provided for @loanBorrowed. + /// + /// In fr, this message translates to: + /// **'Emprunté'** + String get loanBorrowed; + + /// No description provided for @loanBorrowedMultiple. + /// + /// In fr, this message translates to: + /// **'Empruntés'** + String get loanBorrowedMultiple; + + /// No description provided for @loanAnd. + /// + /// In fr, this message translates to: + /// **'et'** + String get loanAnd; + + /// No description provided for @loanAssociation. + /// + /// In fr, this message translates to: + /// **'Association'** + String get loanAssociation; + + /// No description provided for @loanAvailableItems. + /// + /// In fr, this message translates to: + /// **'Objets disponibles'** + String get loanAvailableItems; + + /// No description provided for @loanBeginDate. + /// + /// In fr, this message translates to: + /// **'Date du début du prêt'** + String get loanBeginDate; + + /// No description provided for @loanBorrower. + /// + /// In fr, this message translates to: + /// **'Emprunteur'** + String get loanBorrower; + + /// No description provided for @loanCaution. + /// + /// In fr, this message translates to: + /// **'Caution'** + String get loanCaution; + + /// No description provided for @loanCancel. + /// + /// In fr, this message translates to: + /// **'Annuler'** + String get loanCancel; + + /// No description provided for @loanConfirm. + /// + /// In fr, this message translates to: + /// **'Confirmer'** + String get loanConfirm; + + /// No description provided for @loanConfirmation. + /// + /// In fr, this message translates to: + /// **'Confirmation'** + String get loanConfirmation; + + /// No description provided for @loanDates. + /// + /// In fr, this message translates to: + /// **'Dates'** + String get loanDates; + + /// No description provided for @loanDays. + /// + /// In fr, this message translates to: + /// **'Jours'** + String get loanDays; + + /// No description provided for @loanDelay. + /// + /// In fr, this message translates to: + /// **'Délai de la prolongation'** + String get loanDelay; + + /// No description provided for @loanDelete. + /// + /// In fr, this message translates to: + /// **'Supprimer'** + String get loanDelete; + + /// No description provided for @loanDeletingLoan. + /// + /// In fr, this message translates to: + /// **'Supprimer le prêt ?'** + String get loanDeletingLoan; + + /// No description provided for @loanDeletedItem. + /// + /// In fr, this message translates to: + /// **'Objet supprimé'** + String get loanDeletedItem; + + /// No description provided for @loanDeletedLoan. + /// + /// In fr, this message translates to: + /// **'Prêt supprimé'** + String get loanDeletedLoan; + + /// No description provided for @loanDeleting. + /// + /// In fr, this message translates to: + /// **'Suppression'** + String get loanDeleting; + + /// No description provided for @loanDeletingError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de la suppression'** + String get loanDeletingError; + + /// No description provided for @loanDeletingItem. + /// + /// In fr, this message translates to: + /// **'Supprimer l\'objet ?'** + String get loanDeletingItem; + + /// No description provided for @loanDuration. + /// + /// In fr, this message translates to: + /// **'Durée'** + String get loanDuration; + + /// No description provided for @loanEdit. + /// + /// In fr, this message translates to: + /// **'Modifier'** + String get loanEdit; + + /// No description provided for @loanEditItem. + /// + /// In fr, this message translates to: + /// **'Modifier l\'objet'** + String get loanEditItem; + + /// No description provided for @loanEditLoan. + /// + /// In fr, this message translates to: + /// **'Modifier le prêt'** + String get loanEditLoan; + + /// No description provided for @loanEditedRoom. + /// + /// In fr, this message translates to: + /// **'Salle modifiée'** + String get loanEditedRoom; + + /// No description provided for @loanEndDate. + /// + /// In fr, this message translates to: + /// **'Date de fin du prêt'** + String get loanEndDate; + + /// No description provided for @loanEnded. + /// + /// In fr, this message translates to: + /// **'Terminé'** + String get loanEnded; + + /// No description provided for @loanEnterDate. + /// + /// In fr, this message translates to: + /// **'Veuillez entrer une date'** + String get loanEnterDate; + + /// No description provided for @loanExtendedLoan. + /// + /// In fr, this message translates to: + /// **'Prêt prolongé'** + String get loanExtendedLoan; + + /// No description provided for @loanExtendingError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de la prolongation'** + String get loanExtendingError; + + /// No description provided for @loanHistory. + /// + /// In fr, this message translates to: + /// **'Historique'** + String get loanHistory; + + /// No description provided for @loanIncorrectOrMissingFields. + /// + /// In fr, this message translates to: + /// **'Des champs sont manquants ou incorrects'** + String get loanIncorrectOrMissingFields; + + /// No description provided for @loanInvalidNumber. + /// + /// In fr, this message translates to: + /// **'Veuillez entrer un nombre'** + String get loanInvalidNumber; + + /// No description provided for @loanInvalidDates. + /// + /// In fr, this message translates to: + /// **'Les dates ne sont pas valides'** + String get loanInvalidDates; + + /// No description provided for @loanItem. + /// + /// In fr, this message translates to: + /// **'Objet'** + String get loanItem; + + /// No description provided for @loanItems. + /// + /// In fr, this message translates to: + /// **'Objets'** + String get loanItems; + + /// No description provided for @loanItemHandling. + /// + /// In fr, this message translates to: + /// **'Gestion des objets'** + String get loanItemHandling; + + /// No description provided for @loanItemSelected. + /// + /// In fr, this message translates to: + /// **'objet sélectionné'** + String get loanItemSelected; + + /// No description provided for @loanItemsSelected. + /// + /// In fr, this message translates to: + /// **'objets sélectionnés'** + String get loanItemsSelected; + + /// No description provided for @loanLendingDuration. + /// + /// In fr, this message translates to: + /// **'Durée possible du prêt'** + String get loanLendingDuration; + + /// No description provided for @loanLoan. + /// + /// In fr, this message translates to: + /// **'Prêt'** + String get loanLoan; + + /// No description provided for @loanLoanHandling. + /// + /// In fr, this message translates to: + /// **'Gestion des prêts'** + String get loanLoanHandling; + + /// No description provided for @loanLooking. + /// + /// In fr, this message translates to: + /// **'Rechercher'** + String get loanLooking; + + /// No description provided for @loanName. + /// + /// In fr, this message translates to: + /// **'Nom'** + String get loanName; + + /// No description provided for @loanNext. + /// + /// In fr, this message translates to: + /// **'Suivant'** + String get loanNext; + + /// No description provided for @loanNo. + /// + /// In fr, this message translates to: + /// **'Non'** + String get loanNo; + + /// No description provided for @loanNoAssociationsFounded. + /// + /// In fr, this message translates to: + /// **'Aucune association trouvée'** + String get loanNoAssociationsFounded; + + /// No description provided for @loanNoAvailableItems. + /// + /// In fr, this message translates to: + /// **'Aucun objet disponible'** + String get loanNoAvailableItems; + + /// No description provided for @loanNoBorrower. + /// + /// In fr, this message translates to: + /// **'Aucun emprunteur'** + String get loanNoBorrower; + + /// No description provided for @loanNoItems. + /// + /// In fr, this message translates to: + /// **'Aucun objet'** + String get loanNoItems; + + /// No description provided for @loanNoItemSelected. + /// + /// In fr, this message translates to: + /// **'Aucun objet sélectionné'** + String get loanNoItemSelected; + + /// No description provided for @loanNoLoan. + /// + /// In fr, this message translates to: + /// **'Aucun prêt'** + String get loanNoLoan; + + /// No description provided for @loanNoReturnedDate. + /// + /// In fr, this message translates to: + /// **'Pas de date de retour'** + String get loanNoReturnedDate; + + /// No description provided for @loanQuantity. + /// + /// In fr, this message translates to: + /// **'Quantité'** + String get loanQuantity; + + /// No description provided for @loanNone. + /// + /// In fr, this message translates to: + /// **'Aucun'** + String get loanNone; + + /// No description provided for @loanNote. + /// + /// In fr, this message translates to: + /// **'Note'** + String get loanNote; + + /// No description provided for @loanNoValue. + /// + /// In fr, this message translates to: + /// **'Veuillez entrer une valeur'** + String get loanNoValue; + + /// No description provided for @loanOnGoing. + /// + /// In fr, this message translates to: + /// **'En cours'** + String get loanOnGoing; + + /// No description provided for @loanOnGoingLoan. + /// + /// In fr, this message translates to: + /// **'Prêt en cours'** + String get loanOnGoingLoan; + + /// No description provided for @loanOthers. + /// + /// In fr, this message translates to: + /// **'autres'** + String get loanOthers; + + /// No description provided for @loanPaidCaution. + /// + /// In fr, this message translates to: + /// **'Caution payée'** + String get loanPaidCaution; + + /// No description provided for @loanPositiveNumber. + /// + /// In fr, this message translates to: + /// **'Veuillez entrer un nombre positif'** + String get loanPositiveNumber; + + /// No description provided for @loanPrevious. + /// + /// In fr, this message translates to: + /// **'Précédent'** + String get loanPrevious; + + /// No description provided for @loanReturned. + /// + /// In fr, this message translates to: + /// **'Rendu'** + String get loanReturned; + + /// No description provided for @loanReturnedLoan. + /// + /// In fr, this message translates to: + /// **'Prêt rendu'** + String get loanReturnedLoan; + + /// No description provided for @loanReturningError. + /// + /// In fr, this message translates to: + /// **'Erreur lors du retour'** + String get loanReturningError; + + /// No description provided for @loanReturningLoan. + /// + /// In fr, this message translates to: + /// **'Retour'** + String get loanReturningLoan; + + /// No description provided for @loanReturnLoan. + /// + /// In fr, this message translates to: + /// **'Rendre le prêt ?'** + String get loanReturnLoan; + + /// No description provided for @loanReturnLoanDescription. + /// + /// In fr, this message translates to: + /// **'Voulez-vous rendre ce prêt ?'** + String get loanReturnLoanDescription; + + /// No description provided for @loanToReturn. + /// + /// In fr, this message translates to: + /// **'A rendre'** + String get loanToReturn; + + /// No description provided for @loanUnavailable. + /// + /// In fr, this message translates to: + /// **'Indisponible'** + String get loanUnavailable; + + /// No description provided for @loanUpdate. + /// + /// In fr, this message translates to: + /// **'Modifier'** + String get loanUpdate; + + /// No description provided for @loanUpdatedItem. + /// + /// In fr, this message translates to: + /// **'Objet modifié'** + String get loanUpdatedItem; + + /// No description provided for @loanUpdatedLoan. + /// + /// In fr, this message translates to: + /// **'Prêt modifié'** + String get loanUpdatedLoan; + + /// No description provided for @loanUpdatingError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de la modification'** + String get loanUpdatingError; + + /// No description provided for @loanYes. + /// + /// In fr, this message translates to: + /// **'Oui'** + String get loanYes; + + /// No description provided for @loginAccountActivated. + /// + /// In fr, this message translates to: + /// **'Compte activé'** + String get loginAccountActivated; + + /// No description provided for @loginAccountNotActivated. + /// + /// In fr, this message translates to: + /// **'Compte non activé'** + String get loginAccountNotActivated; + + /// No description provided for @loginActivationCode. + /// + /// In fr, this message translates to: + /// **'Code d\'activation'** + String get loginActivationCode; + + /// No description provided for @loginBirthday. + /// + /// In fr, this message translates to: + /// **'Date de naissance'** + String get loginBirthday; + + /// No description provided for @loginCanBeEmpty. + /// + /// In fr, this message translates to: + /// **'Ce champ peut être vide'** + String get loginCanBeEmpty; + + /// No description provided for @loginConfirmPassword. + /// + /// In fr, this message translates to: + /// **'Confirmer le mot de passe'** + String get loginConfirmPassword; + + /// No description provided for @loginCreate. + /// + /// In fr, this message translates to: + /// **'Créer'** + String get loginCreate; + + /// No description provided for @loginCreateAccount. + /// + /// In fr, this message translates to: + /// **'Créer un compte'** + String get loginCreateAccount; + + /// No description provided for @loginCreateAccountTitle. + /// + /// In fr, this message translates to: + /// **'Créer un\ncompte'** + String get loginCreateAccountTitle; + + /// No description provided for @loginEmail. + /// + /// In fr, this message translates to: + /// **'Email'** + String get loginEmail; + + /// No description provided for @loginEmailEmpty. + /// + /// In fr, this message translates to: + /// **'Veuillez entrer une adresse mail'** + String get loginEmailEmpty; + + /// No description provided for @loginEmailInvalid. + /// + /// In fr, this message translates to: + /// **'Veuillez entrer une adresse mail de centrale.\nSi vous n\'en possédez pas, veuillez contacter Éclair'** + String get loginEmailInvalid; + + /// No description provided for @loginEmptyFieldError. + /// + /// In fr, this message translates to: + /// **'Ce champ ne peut pas être vide'** + String get loginEmptyFieldError; + + /// No description provided for @loginEndActivation. + /// + /// In fr, this message translates to: + /// **'Finaliser l\'activation'** + String get loginEndActivation; + + /// No description provided for @loginEndResetPassword. + /// + /// In fr, this message translates to: + /// **'Finaliser la \nréinitialisation'** + String get loginEndResetPassword; + + /// No description provided for @loginErrorResetPassword. + /// + /// In fr, this message translates to: + /// **'Erreur lors de la réinitialisation'** + String get loginErrorResetPassword; + + /// No description provided for @loginExpectingDate. + /// + /// In fr, this message translates to: + /// **'Une date est attendue'** + String get loginExpectingDate; + + /// No description provided for @loginFillAllFields. + /// + /// In fr, this message translates to: + /// **'Veuillez remplir tous les champs'** + String get loginFillAllFields; + + /// No description provided for @loginFirstname. + /// + /// In fr, this message translates to: + /// **'Prénom'** + String get loginFirstname; + + /// No description provided for @loginFloor. + /// + /// In fr, this message translates to: + /// **'Étage'** + String get loginFloor; + + /// No description provided for @loginForgetPassword. + /// + /// In fr, this message translates to: + /// **'Mot de passe\noublié'** + String get loginForgetPassword; + + /// No description provided for @loginForgotPassword. + /// + /// In fr, this message translates to: + /// **'Mot de passe oublié ?'** + String get loginForgotPassword; + + /// No description provided for @loginInvalidToken. + /// + /// In fr, this message translates to: + /// **'Code d\'activation invalide'** + String get loginInvalidToken; + + /// No description provided for @loginLoginFailed. + /// + /// In fr, this message translates to: + /// **'Échec de la connexion'** + String get loginLoginFailed; + + /// No description provided for @loginMailSendingError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de la création du compte'** + String get loginMailSendingError; + + /// No description provided for @loginMustBeIntError. + /// + /// In fr, this message translates to: + /// **'Ce champ doit être un entier'** + String get loginMustBeIntError; + + /// No description provided for @loginName. + /// + /// In fr, this message translates to: + /// **'Nom'** + String get loginName; + + /// No description provided for @loginNewPassword. + /// + /// In fr, this message translates to: + /// **'Nouveau mot de passe'** + String get loginNewPassword; + + /// No description provided for @loginPassword. + /// + /// In fr, this message translates to: + /// **'Mot de passe'** + String get loginPassword; + + /// No description provided for @loginPasswordLengthError. + /// + /// In fr, this message translates to: + /// **'Le mot de passe doit faire au moins 6 caractères'** + String get loginPasswordLengthError; + + /// No description provided for @loginPasswordUppercaseError. + /// + /// In fr, this message translates to: + /// **'Le mot de passe doit contenir au moins une majuscule'** + String get loginPasswordUppercaseError; + + /// No description provided for @loginPasswordLowercaseError. + /// + /// In fr, this message translates to: + /// **'Le mot de passe doit contenir au moins une minucule'** + String get loginPasswordLowercaseError; + + /// No description provided for @loginPasswordNumberError. + /// + /// In fr, this message translates to: + /// **'Le mot de passe doit contenir au moins un chiffre'** + String get loginPasswordNumberError; + + /// No description provided for @loginPasswordSpecialCaracterError. + /// + /// In fr, this message translates to: + /// **'Le mot de passe doit contenir au moins un caractère spécial'** + String get loginPasswordSpecialCaracterError; + + /// No description provided for @loginPasswordMustMatch. + /// + /// In fr, this message translates to: + /// **'Les mots de passe doivent correspondre'** + String get loginPasswordMustMatch; + + /// No description provided for @loginPasswordStrengthVeryWeak. + /// + /// In fr, this message translates to: + /// **'Très faible'** + String get loginPasswordStrengthVeryWeak; + + /// No description provided for @loginPasswordStrengthWeak. + /// + /// In fr, this message translates to: + /// **'Faible'** + String get loginPasswordStrengthWeak; + + /// No description provided for @loginPasswordStrengthMedium. + /// + /// In fr, this message translates to: + /// **'Moyen'** + String get loginPasswordStrengthMedium; + + /// No description provided for @loginPasswordStrengthStrong. + /// + /// In fr, this message translates to: + /// **'Fort'** + String get loginPasswordStrengthStrong; + + /// No description provided for @loginPasswordStrengthVeryStrong. + /// + /// In fr, this message translates to: + /// **'Très fort'** + String get loginPasswordStrengthVeryStrong; + + /// No description provided for @loginPhone. + /// + /// In fr, this message translates to: + /// **'Téléphone'** + String get loginPhone; + + /// No description provided for @loginPromo. + /// + /// In fr, this message translates to: + /// **'Promo entrante (ex : 2023)'** + String get loginPromo; + + /// No description provided for @loginSendedMail. + /// + /// In fr, this message translates to: + /// **'Mail de confirmation envoyé'** + String get loginSendedMail; + + /// No description provided for @loginSendedResetMail. + /// + /// In fr, this message translates to: + /// **'Mail de réinitialisation envoyé'** + String get loginSendedResetMail; + + /// No description provided for @loginSignIn. + /// + /// In fr, this message translates to: + /// **'Se connecter'** + String get loginSignIn; + + /// No description provided for @loginRegister. + /// + /// In fr, this message translates to: + /// **'S\'inscrire'** + String get loginRegister; + + /// No description provided for @loginRecievedMail. + /// + /// In fr, this message translates to: + /// **'J\'ai reçu le mail'** + String get loginRecievedMail; + + /// No description provided for @loginRecover. + /// + /// In fr, this message translates to: + /// **'Réinitialiser'** + String get loginRecover; + + /// No description provided for @loginResetedPassword. + /// + /// In fr, this message translates to: + /// **'Mot de passe réinitialisé'** + String get loginResetedPassword; + + /// No description provided for @loginResetPasswordTitle. + /// + /// In fr, this message translates to: + /// **'Réinitialiser\nle mot de \npasse'** + String get loginResetPasswordTitle; + + /// No description provided for @loginNickname. + /// + /// In fr, this message translates to: + /// **'Surnom'** + String get loginNickname; + + /// No description provided for @loginWelcomeBack. + /// + /// In fr, this message translates to: + /// **'Bienvenue'** + String get loginWelcomeBack; + + /// No description provided for @loginAppName. + /// + /// In fr, this message translates to: + /// **'MyECL'** + String get loginAppName; + + /// No description provided for @othersCheckInternetConnection. + /// + /// In fr, this message translates to: + /// **'Veuillez vérifier votre connexion internet'** + String get othersCheckInternetConnection; + + /// No description provided for @othersRetry. + /// + /// In fr, this message translates to: + /// **'Réessayer'** + String get othersRetry; + + /// No description provided for @othersTooOldVersion. + /// + /// In fr, this message translates to: + /// **'Votre version de l\'application est trop ancienne.\n\nVeuillez mettre à jour l\'application.'** + String get othersTooOldVersion; + + /// No description provided for @othersUnableToConnectToServer. + /// + /// In fr, this message translates to: + /// **'Impossible de se connecter au serveur'** + String get othersUnableToConnectToServer; + + /// No description provided for @othersVersion. + /// + /// In fr, this message translates to: + /// **'Version'** + String get othersVersion; + + /// No description provided for @othersNoModule. + /// + /// In fr, this message translates to: + /// **'Aucun module disponible, veuillez réessayer ultérieurement 😢😢'** + String get othersNoModule; + + /// No description provided for @othersAdmin. + /// + /// In fr, this message translates to: + /// **'Admin'** + String get othersAdmin; + + /// No description provided for @othersError. + /// + /// In fr, this message translates to: + /// **'Une erreur est survenue'** + String get othersError; + + /// No description provided for @othersNoValue. + /// + /// In fr, this message translates to: + /// **'Veuillez entrer une valeur'** + String get othersNoValue; + + /// No description provided for @othersInvalidNumber. + /// + /// In fr, this message translates to: + /// **'Veuillez entrer un nombre'** + String get othersInvalidNumber; + + /// No description provided for @othersNoDateError. + /// + /// In fr, this message translates to: + /// **'Veuillez entrer une date'** + String get othersNoDateError; + + /// No description provided for @othersImageSizeTooBig. + /// + /// In fr, this message translates to: + /// **'La taille de l\'image ne doit pas dépasser 4 Mio'** + String get othersImageSizeTooBig; + + /// No description provided for @othersImageError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de l\'ajout de l\'image'** + String get othersImageError; + + /// No description provided for @phAddNewJournal. + /// + /// In fr, this message translates to: + /// **'Ajouter un nouveau journal'** + String get phAddNewJournal; + + /// No description provided for @phNameField. + /// + /// In fr, this message translates to: + /// **'Nom : '** + String get phNameField; + + /// No description provided for @phDateField. + /// + /// In fr, this message translates to: + /// **'Date : '** + String get phDateField; + + /// No description provided for @phDelete. + /// + /// In fr, this message translates to: + /// **'Voulez-vous vraiment supprimer ce journal ?'** + String get phDelete; + + /// No description provided for @phIrreversibleAction. + /// + /// In fr, this message translates to: + /// **'Cette action est irréversible'** + String get phIrreversibleAction; + + /// No description provided for @phToHeavyFile. + /// + /// In fr, this message translates to: + /// **'Fichier trop volumineux'** + String get phToHeavyFile; + + /// No description provided for @phAddPdfFile. + /// + /// In fr, this message translates to: + /// **'Ajouter un fichier PDF'** + String get phAddPdfFile; + + /// No description provided for @phEditPdfFile. + /// + /// In fr, this message translates to: + /// **'Modifier le fichier PDF'** + String get phEditPdfFile; + + /// No description provided for @phPhName. + /// + /// In fr, this message translates to: + /// **'Nom du PH'** + String get phPhName; + + /// No description provided for @phDate. + /// + /// In fr, this message translates to: + /// **'Date'** + String get phDate; + + /// No description provided for @phAdded. + /// + /// In fr, this message translates to: + /// **'Ajouté'** + String get phAdded; + + /// No description provided for @phEdited. + /// + /// In fr, this message translates to: + /// **'Modifié'** + String get phEdited; + + /// No description provided for @phAddingFileError. + /// + /// In fr, this message translates to: + /// **'Erreur d\'ajout'** + String get phAddingFileError; + + /// No description provided for @phMissingInformatonsOrPdf. + /// + /// In fr, this message translates to: + /// **'Informations manquantes ou fichier PDF manquant'** + String get phMissingInformatonsOrPdf; + + /// No description provided for @phAdd. + /// + /// In fr, this message translates to: + /// **'Ajouter'** + String get phAdd; + + /// No description provided for @phEdit. + /// + /// In fr, this message translates to: + /// **'Modifier'** + String get phEdit; + + /// No description provided for @phSeePreviousJournal. + /// + /// In fr, this message translates to: + /// **'Voir les anciens journaux'** + String get phSeePreviousJournal; + + /// No description provided for @phNoJournalInDatabase. + /// + /// In fr, this message translates to: + /// **'Pas encore de PH dans la base de donnée'** + String get phNoJournalInDatabase; + + /// No description provided for @phSuccesDowloading. + /// + /// In fr, this message translates to: + /// **'Téléchargé avec succès'** + String get phSuccesDowloading; + + /// No description provided for @phonebookActiveMandate. + /// + /// In fr, this message translates to: + /// **'Mandat actif :'** + String get phonebookActiveMandate; + + /// No description provided for @phonebookAdd. + /// + /// In fr, this message translates to: + /// **'Ajouter'** + String get phonebookAdd; + + /// No description provided for @phonebookAddAssociation. + /// + /// In fr, this message translates to: + /// **'Ajouter une association'** + String get phonebookAddAssociation; + + /// No description provided for @phonebookAddedAssociation. + /// + /// In fr, this message translates to: + /// **'Association ajoutée'** + String get phonebookAddedAssociation; + + /// No description provided for @phonebookAddedMember. + /// + /// In fr, this message translates to: + /// **'Membre ajouté'** + String get phonebookAddedMember; + + /// No description provided for @phonebookAddingError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de l\'ajout'** + String get phonebookAddingError; + + /// No description provided for @phonebookAddMember. + /// + /// In fr, this message translates to: + /// **'Ajouter un membre'** + String get phonebookAddMember; + + /// No description provided for @phonebookAddRole. + /// + /// In fr, this message translates to: + /// **'Ajouter un rôle'** + String get phonebookAddRole; + + /// No description provided for @phonebookAdmin. + /// + /// In fr, this message translates to: + /// **'Admin'** + String get phonebookAdmin; + + /// No description provided for @phonebookAdminPage. + /// + /// In fr, this message translates to: + /// **'Page Administrateur'** + String get phonebookAdminPage; + + /// No description provided for @phonebookAll. + /// + /// In fr, this message translates to: + /// **'Toutes'** + String get phonebookAll; + + /// No description provided for @phonebookApparentName. + /// + /// In fr, this message translates to: + /// **'Nom public du rôle :'** + String get phonebookApparentName; + + /// No description provided for @phonebookAssociation. + /// + /// In fr, this message translates to: + /// **'Association :'** + String get phonebookAssociation; + + /// No description provided for @phonebookAssociationDetail. + /// + /// In fr, this message translates to: + /// **'Détail de l\'association :'** + String get phonebookAssociationDetail; + + /// No description provided for @phonebookAssociationKind. + /// + /// In fr, this message translates to: + /// **'Type d\'association :'** + String get phonebookAssociationKind; + + /// No description provided for @phonebookAssociationPure. + /// + /// In fr, this message translates to: + /// **'Association'** + String get phonebookAssociationPure; + + /// No description provided for @phonebookAssociationPureSearch. + /// + /// In fr, this message translates to: + /// **' Association'** + String get phonebookAssociationPureSearch; + + /// No description provided for @phonebookAssociations. + /// + /// In fr, this message translates to: + /// **'Associations :'** + String get phonebookAssociations; + + /// No description provided for @phonebookCancel. + /// + /// In fr, this message translates to: + /// **'Annuler'** + String get phonebookCancel; + + /// No description provided for @phonebookChangeMandate. + /// + /// In fr, this message translates to: + /// **'Passer au mandat '** + String get phonebookChangeMandate; + + /// No description provided for @phonebookChangeMandateConfirm. + /// + /// In fr, this message translates to: + /// **'Êtes-vous sûr de vouloir changer tout le mandat ?\nCette action est irréversible !'** + String get phonebookChangeMandateConfirm; + + /// No description provided for @phonebookCopied. + /// + /// In fr, this message translates to: + /// **'Copié dans le presse-papier'** + String get phonebookCopied; + + /// No description provided for @phonebookDeactivateAssociation. + /// + /// In fr, this message translates to: + /// **'Êtes-vous sûr de vouloir désactiver cette association ?\nCette action est irréversible !'** + String get phonebookDeactivateAssociation; + + /// No description provided for @phonebookDeactivatedAssociation. + /// + /// In fr, this message translates to: + /// **'Association désactivée'** + String get phonebookDeactivatedAssociation; + + /// No description provided for @phonebookDeactivatedAssociationWarning. + /// + /// In fr, this message translates to: + /// **'Attention, cette association est désactivée, vous ne pouvez pas la modifier'** + String get phonebookDeactivatedAssociationWarning; + + /// No description provided for @phonebookDeactivating. + /// + /// In fr, this message translates to: + /// **'Désactiver l\'association ?'** + String get phonebookDeactivating; + + /// No description provided for @phonebookDeactivatingError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de la désactivation'** + String get phonebookDeactivatingError; + + /// No description provided for @phonebookDetail. + /// + /// In fr, this message translates to: + /// **'Détail :'** + String get phonebookDetail; + + /// No description provided for @phonebookDeleteAssociation. + /// + /// In fr, this message translates to: + /// **'Supprimer l\'association ?\nCela va effacer tout l\'historique de l\'association'** + String get phonebookDeleteAssociation; + + /// No description provided for @phonebookDeletedAssociation. + /// + /// In fr, this message translates to: + /// **'Association supprimée'** + String get phonebookDeletedAssociation; + + /// No description provided for @phonebookDeletedMember. + /// + /// In fr, this message translates to: + /// **'Membre supprimé'** + String get phonebookDeletedMember; + + /// No description provided for @phonebookDeleting. + /// + /// In fr, this message translates to: + /// **'Suppression'** + String get phonebookDeleting; + + /// No description provided for @phonebookDeletingError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de la suppression'** + String get phonebookDeletingError; + + /// No description provided for @phonebookDescription. + /// + /// In fr, this message translates to: + /// **'Description'** + String get phonebookDescription; + + /// No description provided for @phonebookEdit. + /// + /// In fr, this message translates to: + /// **'Modifier'** + String get phonebookEdit; + + /// No description provided for @phonebookEditMembership. + /// + /// In fr, this message translates to: + /// **'Modifier le rôle'** + String get phonebookEditMembership; + + /// No description provided for @phonebookEmail. + /// + /// In fr, this message translates to: + /// **'Email :'** + String get phonebookEmail; + + /// No description provided for @phonebookEmailCopied. + /// + /// In fr, this message translates to: + /// **'Email copié dans le presse-papier'** + String get phonebookEmailCopied; + + /// No description provided for @phonebookEmptyApparentName. + /// + /// In fr, this message translates to: + /// **'Veuillez entrer un nom de role'** + String get phonebookEmptyApparentName; + + /// No description provided for @phonebookEmptyFieldError. + /// + /// In fr, this message translates to: + /// **'Un champ n\'est pas rempli'** + String get phonebookEmptyFieldError; + + /// No description provided for @phonebookEmptyKindError. + /// + /// In fr, this message translates to: + /// **'Veuillez choisir un type d\'association'** + String get phonebookEmptyKindError; + + /// No description provided for @phonebookEmptyMember. + /// + /// In fr, this message translates to: + /// **'Aucun membre sélectionné'** + String get phonebookEmptyMember; + + /// No description provided for @phonebookErrorAssociationLoading. + /// + /// In fr, this message translates to: + /// **'Erreur lors du chargement de l\'association'** + String get phonebookErrorAssociationLoading; + + /// No description provided for @phonebookErrorAssociationNameEmpty. + /// + /// In fr, this message translates to: + /// **'Veuillez entrer un nom d\'association'** + String get phonebookErrorAssociationNameEmpty; + + /// No description provided for @phonebookErrorAssociationPicture. + /// + /// In fr, this message translates to: + /// **'Erreur lors de la modification de la photo d\'association'** + String get phonebookErrorAssociationPicture; + + /// No description provided for @phonebookErrorKindsLoading. + /// + /// In fr, this message translates to: + /// **'Erreur lors du chargement des types d\'association'** + String get phonebookErrorKindsLoading; + + /// No description provided for @phonebookErrorLoadAssociationList. + /// + /// In fr, this message translates to: + /// **'Erreur lors du chargement de la liste des associations'** + String get phonebookErrorLoadAssociationList; + + /// No description provided for @phonebookErrorLoadAssociationMember. + /// + /// In fr, this message translates to: + /// **'Erreur lors du chargement des membres de l\'association'** + String get phonebookErrorLoadAssociationMember; + + /// No description provided for @phonebookErrorLoadAssociationPicture. + /// + /// In fr, this message translates to: + /// **'Erreur lors du chargement de la photo d\'association'** + String get phonebookErrorLoadAssociationPicture; + + /// No description provided for @phonebookErrorLoadProfilePicture. + /// + /// In fr, this message translates to: + /// **'Erreur'** + String get phonebookErrorLoadProfilePicture; + + /// No description provided for @phonebookErrorRoleTagsLoading. + /// + /// In fr, this message translates to: + /// **'Erreur lors du chargement des tags de rôle'** + String get phonebookErrorRoleTagsLoading; + + /// No description provided for @phonebookExistingMembership. + /// + /// In fr, this message translates to: + /// **'Ce membre est déjà dans le mandat actuel'** + String get phonebookExistingMembership; + + /// No description provided for @phonebookFirstname. + /// + /// In fr, this message translates to: + /// **'Prénom :'** + String get phonebookFirstname; + + /// No description provided for @phonebookGroups. + /// + /// In fr, this message translates to: + /// **'Groupes associés :'** + String get phonebookGroups; + + /// No description provided for @phonebookMandateChangingError. + /// + /// In fr, this message translates to: + /// **'Erreur lors du changement de mandat'** + String get phonebookMandateChangingError; + + /// No description provided for @phonebookMember. + /// + /// In fr, this message translates to: + /// **'Membre'** + String get phonebookMember; + + /// No description provided for @phonebookMemberReordered. + /// + /// In fr, this message translates to: + /// **'Membre réordonné'** + String get phonebookMemberReordered; + + /// No description provided for @phonebookMembers. + /// + /// In fr, this message translates to: + /// **'Membres'** + String get phonebookMembers; + + /// No description provided for @phonebookMembershipAssociationError. + /// + /// In fr, this message translates to: + /// **'Veuillez choisir une association'** + String get phonebookMembershipAssociationError; + + /// No description provided for @phonebookMembershipRole. + /// + /// In fr, this message translates to: + /// **'Rôle :'** + String get phonebookMembershipRole; + + /// No description provided for @phonebookMembershipRoleError. + /// + /// In fr, this message translates to: + /// **'Veuillez choisir un rôle'** + String get phonebookMembershipRoleError; + + /// No description provided for @phonebookName. + /// + /// In fr, this message translates to: + /// **'Nom :'** + String get phonebookName; + + /// No description provided for @phonebookNameCopied. + /// + /// In fr, this message translates to: + /// **'Nom et prénom copié dans le presse-papier'** + String get phonebookNameCopied; + + /// No description provided for @phonebookNamePure. + /// + /// In fr, this message translates to: + /// **'Nom'** + String get phonebookNamePure; + + /// No description provided for @phonebookNewMandate. + /// + /// In fr, this message translates to: + /// **'Nouveau mandat'** + String get phonebookNewMandate; + + /// No description provided for @phonebookNewMandateConfirmed. + /// + /// In fr, this message translates to: + /// **'Mandat changé'** + String get phonebookNewMandateConfirmed; + + /// No description provided for @phonebookNickname. + /// + /// In fr, this message translates to: + /// **'Surnom :'** + String get phonebookNickname; + + /// No description provided for @phonebookNicknameCopied. + /// + /// In fr, this message translates to: + /// **'Surnom copié dans le presse-papier'** + String get phonebookNicknameCopied; + + /// No description provided for @phonebookNoAssociationFound. + /// + /// In fr, this message translates to: + /// **'Aucune association trouvée'** + String get phonebookNoAssociationFound; + + /// No description provided for @phonebookNoMember. + /// + /// In fr, this message translates to: + /// **'Aucun membre'** + String get phonebookNoMember; + + /// No description provided for @phonebookNoMemberRole. + /// + /// In fr, this message translates to: + /// **'Aucun role trouvé'** + String get phonebookNoMemberRole; + + /// No description provided for @phonebookPhone. + /// + /// In fr, this message translates to: + /// **'Téléphone :'** + String get phonebookPhone; + + /// No description provided for @phonebookPhonebook. + /// + /// In fr, this message translates to: + /// **'Annuaire'** + String get phonebookPhonebook; + + /// No description provided for @phonebookPhonebookSearch. + /// + /// In fr, this message translates to: + /// **'Rechercher'** + String get phonebookPhonebookSearch; + + /// No description provided for @phonebookPhonebookSearchAssociation. + /// + /// In fr, this message translates to: + /// **'Association'** + String get phonebookPhonebookSearchAssociation; + + /// No description provided for @phonebookPhonebookSearchField. + /// + /// In fr, this message translates to: + /// **'Rechercher :'** + String get phonebookPhonebookSearchField; + + /// No description provided for @phonebookPhonebookSearchName. + /// + /// In fr, this message translates to: + /// **'Nom/Prénom/Surnom'** + String get phonebookPhonebookSearchName; + + /// No description provided for @phonebookPhonebookSearchRole. + /// + /// In fr, this message translates to: + /// **'Poste'** + String get phonebookPhonebookSearchRole; + + /// No description provided for @phonebookPresidentRoleTag. + /// + /// In fr, this message translates to: + /// **'Prez\''** + String get phonebookPresidentRoleTag; + + /// No description provided for @phonebookPromoNotGiven. + /// + /// In fr, this message translates to: + /// **'Promo non renseignée'** + String get phonebookPromoNotGiven; + + /// No description provided for @phonebookPromotion. + /// + /// In fr, this message translates to: + /// **'Promotion :'** + String get phonebookPromotion; + + /// No description provided for @phonebookReorderingError. + /// + /// In fr, this message translates to: + /// **'Erreur lors du réordonnement'** + String get phonebookReorderingError; + + /// No description provided for @phonebookResearch. + /// + /// In fr, this message translates to: + /// **'Rechercher'** + String get phonebookResearch; + + /// No description provided for @phonebookRolePure. + /// + /// In fr, this message translates to: + /// **'Rôle'** + String get phonebookRolePure; + + /// No description provided for @phonebookTooHeavyAssociationPicture. + /// + /// In fr, this message translates to: + /// **'L\'image est trop lourde (max 4Mo)'** + String get phonebookTooHeavyAssociationPicture; + + /// No description provided for @phonebookUpdateGroups. + /// + /// In fr, this message translates to: + /// **'Mettre à jour les groupes'** + String get phonebookUpdateGroups; + + /// No description provided for @phonebookUpdatedAssociation. + /// + /// In fr, this message translates to: + /// **'Association modifiée'** + String get phonebookUpdatedAssociation; + + /// No description provided for @phonebookUpdatedAssociationPicture. + /// + /// In fr, this message translates to: + /// **'La photo d\'association a été changée'** + String get phonebookUpdatedAssociationPicture; + + /// No description provided for @phonebookUpdatedGroups. + /// + /// In fr, this message translates to: + /// **'Groupes mis à jour'** + String get phonebookUpdatedGroups; + + /// No description provided for @phonebookUpdatedMember. + /// + /// In fr, this message translates to: + /// **'Membre modifié'** + String get phonebookUpdatedMember; + + /// No description provided for @phonebookUpdatingError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de la modification'** + String get phonebookUpdatingError; + + /// No description provided for @phonebookValidation. + /// + /// In fr, this message translates to: + /// **'Valider'** + String get phonebookValidation; + + /// No description provided for @purchasesPurchases. + /// + /// In fr, this message translates to: + /// **'Achats'** + String get purchasesPurchases; + + /// No description provided for @purchasesResearch. + /// + /// In fr, this message translates to: + /// **'Rechercher'** + String get purchasesResearch; + + /// No description provided for @purchasesNoPurchasesFound. + /// + /// In fr, this message translates to: + /// **'Aucun achat trouvé'** + String get purchasesNoPurchasesFound; + + /// No description provided for @purchasesNoTickets. + /// + /// In fr, this message translates to: + /// **'Aucun ticket'** + String get purchasesNoTickets; + + /// No description provided for @purchasesTicketsError. + /// + /// In fr, this message translates to: + /// **'Erreur lors du chargement des tickets'** + String get purchasesTicketsError; + + /// No description provided for @purchasesPurchasesError. + /// + /// In fr, this message translates to: + /// **'Erreur lors du chargement des achats'** + String get purchasesPurchasesError; + + /// No description provided for @purchasesNoPurchases. + /// + /// In fr, this message translates to: + /// **'Aucun achat'** + String get purchasesNoPurchases; + + /// No description provided for @purchasesTimes. + /// + /// In fr, this message translates to: + /// **'fois'** + String get purchasesTimes; + + /// No description provided for @purchasesAlreadyUsed. + /// + /// In fr, this message translates to: + /// **'Déjà utilisé'** + String get purchasesAlreadyUsed; + + /// No description provided for @purchasesNotPaid. + /// + /// In fr, this message translates to: + /// **'Non validé'** + String get purchasesNotPaid; + + /// No description provided for @purchasesPleaseSelectProduct. + /// + /// In fr, this message translates to: + /// **'Veuillez sélectionner un produit'** + String get purchasesPleaseSelectProduct; + + /// No description provided for @purchasesProducts. + /// + /// In fr, this message translates to: + /// **'Produits'** + String get purchasesProducts; + + /// No description provided for @purchasesCancel. + /// + /// In fr, this message translates to: + /// **'Annuler'** + String get purchasesCancel; + + /// No description provided for @purchasesValidate. + /// + /// In fr, this message translates to: + /// **'Valider'** + String get purchasesValidate; + + /// No description provided for @purchasesLeftScan. + /// + /// In fr, this message translates to: + /// **'Scans restants'** + String get purchasesLeftScan; + + /// No description provided for @purchasesTag. + /// + /// In fr, this message translates to: + /// **'Tag'** + String get purchasesTag; + + /// No description provided for @purchasesHistory. + /// + /// In fr, this message translates to: + /// **'Historique'** + String get purchasesHistory; + + /// No description provided for @purchasesPleaseSelectSeller. + /// + /// In fr, this message translates to: + /// **'Veuillez sélectionner un vendeur'** + String get purchasesPleaseSelectSeller; + + /// No description provided for @purchasesNoTagGiven. + /// + /// In fr, this message translates to: + /// **'Attention, aucun tag n\'a été entré'** + String get purchasesNoTagGiven; + + /// No description provided for @purchasesTickets. + /// + /// In fr, this message translates to: + /// **'Tickets'** + String get purchasesTickets; + + /// No description provided for @purchasesNoScannableProducts. + /// + /// In fr, this message translates to: + /// **'Aucun produit scannable'** + String get purchasesNoScannableProducts; + + /// No description provided for @purchasesLoading. + /// + /// In fr, this message translates to: + /// **'En attente de scan'** + String get purchasesLoading; + + /// No description provided for @purchasesScan. + /// + /// In fr, this message translates to: + /// **'Scanner'** + String get purchasesScan; + + /// No description provided for @raffleRaffle. + /// + /// In fr, this message translates to: + /// **'Tombola'** + String get raffleRaffle; + + /// No description provided for @rafflePrize. + /// + /// In fr, this message translates to: + /// **'Lot'** + String get rafflePrize; + + /// No description provided for @rafflePrizes. + /// + /// In fr, this message translates to: + /// **'Lots'** + String get rafflePrizes; + + /// No description provided for @raffleActualRaffles. + /// + /// In fr, this message translates to: + /// **'Tombola en cours'** + String get raffleActualRaffles; + + /// No description provided for @rafflePastRaffles. + /// + /// In fr, this message translates to: + /// **'Tombola passés'** + String get rafflePastRaffles; + + /// No description provided for @raffleYourTickets. + /// + /// In fr, this message translates to: + /// **'Tous vos tickets'** + String get raffleYourTickets; + + /// No description provided for @raffleCreateMenu. + /// + /// In fr, this message translates to: + /// **'Menu de Création'** + String get raffleCreateMenu; + + /// No description provided for @raffleNextRaffles. + /// + /// In fr, this message translates to: + /// **'Prochaines tombolas'** + String get raffleNextRaffles; + + /// No description provided for @raffleNoTicket. + /// + /// In fr, this message translates to: + /// **'Vous n\'avez pas de ticket'** + String get raffleNoTicket; + + /// No description provided for @raffleSeeRaffleDetail. + /// + /// In fr, this message translates to: + /// **'Voir lots/tickets'** + String get raffleSeeRaffleDetail; + + /// No description provided for @raffleActualPrize. + /// + /// In fr, this message translates to: + /// **'Lots actuels'** + String get raffleActualPrize; + + /// No description provided for @raffleMajorPrize. + /// + /// In fr, this message translates to: + /// **'Lot Majeurs'** + String get raffleMajorPrize; + + /// No description provided for @raffleTakeTickets. + /// + /// In fr, this message translates to: + /// **'Prendre vos tickets'** + String get raffleTakeTickets; + + /// No description provided for @raffleNoTicketBuyable. + /// + /// In fr, this message translates to: + /// **'Vous ne pouvez pas achetez de billets pour l\'instant'** + String get raffleNoTicketBuyable; + + /// No description provided for @raffleNoCurrentPrize. + /// + /// In fr, this message translates to: + /// **'Il n\'y a aucun lots actuellement'** + String get raffleNoCurrentPrize; + + /// No description provided for @raffleModifTombola. + /// + /// In fr, this message translates to: + /// **'Vous pouvez modifiez vos tombolas ou en créer de nouvelles, toute décision doit ensuite être prise par les admins'** + String get raffleModifTombola; + + /// No description provided for @raffleCreateYourRaffle. + /// + /// In fr, this message translates to: + /// **'Votre menu de création de tombolas'** + String get raffleCreateYourRaffle; + + /// No description provided for @rafflePossiblePrice. + /// + /// In fr, this message translates to: + /// **'Prix possible'** + String get rafflePossiblePrice; + + /// No description provided for @raffleInformation. + /// + /// In fr, this message translates to: + /// **'Information et Statistiques'** + String get raffleInformation; + + /// No description provided for @raffleAccounts. + /// + /// In fr, this message translates to: + /// **'Comptes'** + String get raffleAccounts; + + /// No description provided for @raffleAdd. + /// + /// In fr, this message translates to: + /// **'Ajouter'** + String get raffleAdd; + + /// No description provided for @raffleUpdatedAmount. + /// + /// In fr, this message translates to: + /// **'Montant mis à jour'** + String get raffleUpdatedAmount; + + /// No description provided for @raffleUpdatingError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de la mise à jour'** + String get raffleUpdatingError; + + /// No description provided for @raffleDeletedPrize. + /// + /// In fr, this message translates to: + /// **'Lot supprimé'** + String get raffleDeletedPrize; + + /// No description provided for @raffleDeletingError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de la suppression'** + String get raffleDeletingError; + + /// No description provided for @raffleQuantity. + /// + /// In fr, this message translates to: + /// **'Quantité'** + String get raffleQuantity; + + /// No description provided for @raffleClose. + /// + /// In fr, this message translates to: + /// **'Fermer'** + String get raffleClose; + + /// No description provided for @raffleOpen. + /// + /// In fr, this message translates to: + /// **'Ouvrir'** + String get raffleOpen; + + /// No description provided for @raffleAddTypeTicketSimple. + /// + /// In fr, this message translates to: + /// **'Ajouter'** + String get raffleAddTypeTicketSimple; + + /// No description provided for @raffleAddingError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de l\'ajout'** + String get raffleAddingError; + + /// No description provided for @raffleEditTypeTicketSimple. + /// + /// In fr, this message translates to: + /// **'Modifier'** + String get raffleEditTypeTicketSimple; + + /// No description provided for @raffleFillField. + /// + /// In fr, this message translates to: + /// **'Le champ ne peut pas être vide'** + String get raffleFillField; + + /// No description provided for @raffleWaiting. + /// + /// In fr, this message translates to: + /// **'Chargement'** + String get raffleWaiting; + + /// No description provided for @raffleEditingError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de la modification'** + String get raffleEditingError; + + /// No description provided for @raffleAddedTicket. + /// + /// In fr, this message translates to: + /// **'Ticket ajouté'** + String get raffleAddedTicket; + + /// No description provided for @raffleEditedTicket. + /// + /// In fr, this message translates to: + /// **'Ticket modifié'** + String get raffleEditedTicket; + + /// No description provided for @raffleAlreadyExistTicket. + /// + /// In fr, this message translates to: + /// **'Le ticket existe déjà'** + String get raffleAlreadyExistTicket; + + /// No description provided for @raffleNumberExpected. + /// + /// In fr, this message translates to: + /// **'Un entier est attendu'** + String get raffleNumberExpected; + + /// No description provided for @raffleDeletedTicket. + /// + /// In fr, this message translates to: + /// **'Ticket supprimé'** + String get raffleDeletedTicket; + + /// No description provided for @raffleAddPrize. + /// + /// In fr, this message translates to: + /// **'Ajouter'** + String get raffleAddPrize; + + /// No description provided for @raffleEditPrize. + /// + /// In fr, this message translates to: + /// **'Modifier'** + String get raffleEditPrize; + + /// No description provided for @raffleOpenRaffle. + /// + /// In fr, this message translates to: + /// **'Ouvrir la tombola'** + String get raffleOpenRaffle; + + /// No description provided for @raffleCloseRaffle. + /// + /// In fr, this message translates to: + /// **'Fermer la tombola'** + String get raffleCloseRaffle; + + /// No description provided for @raffleOpenRaffleDescription. + /// + /// In fr, this message translates to: + /// **'Vous allez ouvrir la tombola, les utilisateurs pourront acheter des tickets. Vous ne pourrez plus modifier la tombola. Êtes-vous sûr de vouloir continuer ?'** + String get raffleOpenRaffleDescription; + + /// No description provided for @raffleCloseRaffleDescription. + /// + /// In fr, this message translates to: + /// **'Vous allez fermer la tombola, les utilisateurs ne pourront plus acheter de tickets. Êtes-vous sûr de vouloir continuer ?'** + String get raffleCloseRaffleDescription; + + /// No description provided for @raffleNoCurrentRaffle. + /// + /// In fr, this message translates to: + /// **'Il n\'y a aucune tombola en cours'** + String get raffleNoCurrentRaffle; + + /// No description provided for @raffleBoughtTicket. + /// + /// In fr, this message translates to: + /// **'Ticket acheté'** + String get raffleBoughtTicket; + + /// No description provided for @raffleDrawingError. + /// + /// In fr, this message translates to: + /// **'Erreur lors du tirage'** + String get raffleDrawingError; + + /// No description provided for @raffleInvalidPrice. + /// + /// In fr, this message translates to: + /// **'Le prix doit être supérieur à 0'** + String get raffleInvalidPrice; + + /// No description provided for @raffleMustBePositive. + /// + /// In fr, this message translates to: + /// **'Le nombre doit être strictement positif'** + String get raffleMustBePositive; + + /// No description provided for @raffleDraw. + /// + /// In fr, this message translates to: + /// **'Tirer'** + String get raffleDraw; + + /// No description provided for @raffleDrawn. + /// + /// In fr, this message translates to: + /// **'Tiré'** + String get raffleDrawn; + + /// No description provided for @raffleError. + /// + /// In fr, this message translates to: + /// **'Erreur'** + String get raffleError; + + /// No description provided for @raffleGathered. + /// + /// In fr, this message translates to: + /// **'Récolté'** + String get raffleGathered; + + /// No description provided for @raffleTickets. + /// + /// In fr, this message translates to: + /// **'Tickets'** + String get raffleTickets; + + /// No description provided for @raffleTicket. + /// + /// In fr, this message translates to: + /// **'ticket'** + String get raffleTicket; + + /// No description provided for @raffleWinner. + /// + /// In fr, this message translates to: + /// **'Gagnant'** + String get raffleWinner; + + /// No description provided for @raffleNoPrize. + /// + /// In fr, this message translates to: + /// **'Aucun lot'** + String get raffleNoPrize; + + /// No description provided for @raffleDeletePrize. + /// + /// In fr, this message translates to: + /// **'Supprimer le lot'** + String get raffleDeletePrize; + + /// No description provided for @raffleDeletePrizeDescription. + /// + /// In fr, this message translates to: + /// **'Vous allez supprimer le lot, êtes-vous sûr de vouloir continuer ?'** + String get raffleDeletePrizeDescription; + + /// No description provided for @raffleDrawing. + /// + /// In fr, this message translates to: + /// **'Tirage'** + String get raffleDrawing; + + /// No description provided for @raffleDrawingDescription. + /// + /// In fr, this message translates to: + /// **'Tirer le gagnant du lot ?'** + String get raffleDrawingDescription; + + /// No description provided for @raffleDeleteTicket. + /// + /// In fr, this message translates to: + /// **'Supprimer le ticket'** + String get raffleDeleteTicket; + + /// No description provided for @raffleDeleteTicketDescription. + /// + /// In fr, this message translates to: + /// **'Vous allez supprimer le ticket, êtes-vous sûr de vouloir continuer ?'** + String get raffleDeleteTicketDescription; + + /// No description provided for @raffleWinningTickets. + /// + /// In fr, this message translates to: + /// **'Tickets gagnants'** + String get raffleWinningTickets; + + /// No description provided for @raffleNoWinningTicketYet. + /// + /// In fr, this message translates to: + /// **'Les tickets gagnants seront affichés ici'** + String get raffleNoWinningTicketYet; + + /// No description provided for @raffleName. + /// + /// In fr, this message translates to: + /// **'Nom'** + String get raffleName; + + /// No description provided for @raffleDescription. + /// + /// In fr, this message translates to: + /// **'Description'** + String get raffleDescription; + + /// No description provided for @raffleBuyThisTicket. + /// + /// In fr, this message translates to: + /// **'Acheter ce ticket'** + String get raffleBuyThisTicket; + + /// No description provided for @raffleLockedRaffle. + /// + /// In fr, this message translates to: + /// **'Tombola verrouillée'** + String get raffleLockedRaffle; + + /// No description provided for @raffleUnavailableRaffle. + /// + /// In fr, this message translates to: + /// **'Tombola indisponible'** + String get raffleUnavailableRaffle; + + /// No description provided for @raffleNotEnoughMoney. + /// + /// In fr, this message translates to: + /// **'Vous n\'avez pas assez d\'argent'** + String get raffleNotEnoughMoney; + + /// No description provided for @raffleWinnable. + /// + /// In fr, this message translates to: + /// **'gagnable'** + String get raffleWinnable; + + /// No description provided for @raffleNoDescription. + /// + /// In fr, this message translates to: + /// **'Aucune description'** + String get raffleNoDescription; + + /// No description provided for @raffleAmount. + /// + /// In fr, this message translates to: + /// **'Solde'** + String get raffleAmount; + + /// No description provided for @raffleLoading. + /// + /// In fr, this message translates to: + /// **'Chargement'** + String get raffleLoading; + + /// No description provided for @raffleTicketNumber. + /// + /// In fr, this message translates to: + /// **'Nombre de ticket'** + String get raffleTicketNumber; + + /// No description provided for @rafflePrice. + /// + /// In fr, this message translates to: + /// **'Prix'** + String get rafflePrice; + + /// No description provided for @raffleEditRaffle. + /// + /// In fr, this message translates to: + /// **'Modifier la tombola'** + String get raffleEditRaffle; + + /// No description provided for @raffleEdit. + /// + /// In fr, this message translates to: + /// **'Modifier'** + String get raffleEdit; + + /// No description provided for @raffleAddPackTicket. + /// + /// In fr, this message translates to: + /// **'Ajouter un pack de ticket'** + String get raffleAddPackTicket; + + /// No description provided for @recommendationRecommendation. + /// + /// In fr, this message translates to: + /// **'Bons plans'** + String get recommendationRecommendation; + + /// No description provided for @recommendationTitle. + /// + /// In fr, this message translates to: + /// **'Titre'** + String get recommendationTitle; + + /// No description provided for @recommendationLogo. + /// + /// In fr, this message translates to: + /// **'Logo'** + String get recommendationLogo; + + /// No description provided for @recommendationCode. + /// + /// In fr, this message translates to: + /// **'Code'** + String get recommendationCode; + + /// No description provided for @recommendationSummary. + /// + /// In fr, this message translates to: + /// **'Court résumé'** + String get recommendationSummary; + + /// No description provided for @recommendationDescription. + /// + /// In fr, this message translates to: + /// **'Description'** + String get recommendationDescription; + + /// No description provided for @recommendationAdd. + /// + /// In fr, this message translates to: + /// **'Ajouter'** + String get recommendationAdd; + + /// No description provided for @recommendationEdit. + /// + /// In fr, this message translates to: + /// **'Modifier'** + String get recommendationEdit; + + /// No description provided for @recommendationDelete. + /// + /// In fr, this message translates to: + /// **'Supprimer'** + String get recommendationDelete; + + /// No description provided for @recommendationAddImage. + /// + /// In fr, this message translates to: + /// **'Veuillez ajouter une image'** + String get recommendationAddImage; + + /// No description provided for @recommendationAddedRecommendation. + /// + /// In fr, this message translates to: + /// **'Bon plan ajouté'** + String get recommendationAddedRecommendation; + + /// No description provided for @recommendationEditedRecommendation. + /// + /// In fr, this message translates to: + /// **'Bon plan modifié'** + String get recommendationEditedRecommendation; + + /// No description provided for @recommendationDeleteRecommendationConfirmation. + /// + /// In fr, this message translates to: + /// **'Êtes-vous sûr de vouloir supprimer ce bon plan ?'** + String get recommendationDeleteRecommendationConfirmation; + + /// No description provided for @recommendationDeleteRecommendation. + /// + /// In fr, this message translates to: + /// **'Suppresion'** + String get recommendationDeleteRecommendation; + + /// No description provided for @recommendationDeletingRecommendationError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de la suppression'** + String get recommendationDeletingRecommendationError; + + /// No description provided for @recommendationDeletedRecommendation. + /// + /// In fr, this message translates to: + /// **'Bon plan supprimé'** + String get recommendationDeletedRecommendation; + + /// No description provided for @recommendationIncorrectOrMissingFields. + /// + /// In fr, this message translates to: + /// **'Champs incorrects ou manquants'** + String get recommendationIncorrectOrMissingFields; + + /// No description provided for @recommendationEditingError. + /// + /// In fr, this message translates to: + /// **'Échec de la modification'** + String get recommendationEditingError; + + /// No description provided for @recommendationAddingError. + /// + /// In fr, this message translates to: + /// **'Échec de l\'ajout'** + String get recommendationAddingError; + + /// No description provided for @recommendationCopiedCode. + /// + /// In fr, this message translates to: + /// **'Code de réduction copié'** + String get recommendationCopiedCode; + + /// No description provided for @seedLibraryAdd. + /// + /// In fr, this message translates to: + /// **'Ajouter'** + String get seedLibraryAdd; + + /// No description provided for @seedLibraryAddedPlant. + /// + /// In fr, this message translates to: + /// **'Plante ajoutée'** + String get seedLibraryAddedPlant; + + /// No description provided for @seedLibraryAddedSpecies. + /// + /// In fr, this message translates to: + /// **'Espèce ajoutée'** + String get seedLibraryAddedSpecies; + + /// No description provided for @seedLibraryAddingError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de l\'ajout'** + String get seedLibraryAddingError; + + /// No description provided for @seedLibraryAddPlant. + /// + /// In fr, this message translates to: + /// **'Déposer une plante'** + String get seedLibraryAddPlant; + + /// No description provided for @seedLibraryAddSpecies. + /// + /// In fr, this message translates to: + /// **'Ajouter une espèce'** + String get seedLibraryAddSpecies; + + /// No description provided for @seedLibraryAll. + /// + /// In fr, this message translates to: + /// **'Toutes'** + String get seedLibraryAll; + + /// No description provided for @seedLibraryAncestor. + /// + /// In fr, this message translates to: + /// **'Ancêtre'** + String get seedLibraryAncestor; + + /// No description provided for @seedLibraryAround. + /// + /// In fr, this message translates to: + /// **'environ'** + String get seedLibraryAround; + + /// No description provided for @seedLibraryAutumn. + /// + /// In fr, this message translates to: + /// **'Automne'** + String get seedLibraryAutumn; + + /// No description provided for @seedLibraryBorrowedPlant. + /// + /// In fr, this message translates to: + /// **'Plante empruntée'** + String get seedLibraryBorrowedPlant; + + /// No description provided for @seedLibraryBorrowingDate. + /// + /// In fr, this message translates to: + /// **'Date d\'emprunt :'** + String get seedLibraryBorrowingDate; + + /// No description provided for @seedLibraryBorrowPlant. + /// + /// In fr, this message translates to: + /// **'Emprunter la plante'** + String get seedLibraryBorrowPlant; + + /// No description provided for @seedLibraryCard. + /// + /// In fr, this message translates to: + /// **'Carte'** + String get seedLibraryCard; + + /// No description provided for @seedLibraryChoosingAncestor. + /// + /// In fr, this message translates to: + /// **'Veuillez choisir un ancêtre'** + String get seedLibraryChoosingAncestor; + + /// No description provided for @seedLibraryChoosingSpecies. + /// + /// In fr, this message translates to: + /// **'Veuillez choisir une espèce'** + String get seedLibraryChoosingSpecies; + + /// No description provided for @seedLibraryChoosingSpeciesOrAncestor. + /// + /// In fr, this message translates to: + /// **'Veuillez choisir une espèce ou un ancêtre'** + String get seedLibraryChoosingSpeciesOrAncestor; + + /// No description provided for @seedLibraryContact. + /// + /// In fr, this message translates to: + /// **'Contact :'** + String get seedLibraryContact; + + /// No description provided for @seedLibraryDays. + /// + /// In fr, this message translates to: + /// **'jours'** + String get seedLibraryDays; + + /// No description provided for @seedLibraryDeadMsg. + /// + /// In fr, this message translates to: + /// **'Voulez-vous déclarer la plante morte ?'** + String get seedLibraryDeadMsg; + + /// No description provided for @seedLibraryDeadPlant. + /// + /// In fr, this message translates to: + /// **'Plante morte'** + String get seedLibraryDeadPlant; + + /// No description provided for @seedLibraryDeathDate. + /// + /// In fr, this message translates to: + /// **'Date de mort'** + String get seedLibraryDeathDate; + + /// No description provided for @seedLibraryDeletedSpecies. + /// + /// In fr, this message translates to: + /// **'Espèce supprimée'** + String get seedLibraryDeletedSpecies; + + /// No description provided for @seedLibraryDeleteSpecies. + /// + /// In fr, this message translates to: + /// **'Supprimer l\'espèce ?'** + String get seedLibraryDeleteSpecies; + + /// No description provided for @seedLibraryDeleting. + /// + /// In fr, this message translates to: + /// **'Suppression'** + String get seedLibraryDeleting; + + /// No description provided for @seedLibraryDeletingError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de la suppression'** + String get seedLibraryDeletingError; + + /// No description provided for @seedLibraryDepositNotAvailable. + /// + /// In fr, this message translates to: + /// **'Le dépôt de plantes n\'est pas possible sans emprunter une plante au préalable'** + String get seedLibraryDepositNotAvailable; + + /// No description provided for @seedLibraryDescription. + /// + /// In fr, this message translates to: + /// **'Description'** + String get seedLibraryDescription; + + /// No description provided for @seedLibraryDifficulty. + /// + /// In fr, this message translates to: + /// **'Difficulté :'** + String get seedLibraryDifficulty; + + /// No description provided for @seedLibraryEdit. + /// + /// In fr, this message translates to: + /// **'Modifier'** + String get seedLibraryEdit; + + /// No description provided for @seedLibraryEditedPlant. + /// + /// In fr, this message translates to: + /// **'Plante modifiée'** + String get seedLibraryEditedPlant; + + /// No description provided for @seedLibraryEditInformation. + /// + /// In fr, this message translates to: + /// **'Modifier les informations'** + String get seedLibraryEditInformation; + + /// No description provided for @seedLibraryEditingError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de la modification'** + String get seedLibraryEditingError; + + /// No description provided for @seedLibraryEditSpecies. + /// + /// In fr, this message translates to: + /// **'Modifier l\'espèce'** + String get seedLibraryEditSpecies; + + /// No description provided for @seedLibraryEmptyDifficultyError. + /// + /// In fr, this message translates to: + /// **'Veuillez choisir une difficulté'** + String get seedLibraryEmptyDifficultyError; + + /// No description provided for @seedLibraryEmptyFieldError. + /// + /// In fr, this message translates to: + /// **'Veuillez remplir tous les champs'** + String get seedLibraryEmptyFieldError; + + /// No description provided for @seedLibraryEmptyTypeError. + /// + /// In fr, this message translates to: + /// **'Veuillez choisir un type de plante'** + String get seedLibraryEmptyTypeError; + + /// No description provided for @seedLibraryEndMonth. + /// + /// In fr, this message translates to: + /// **'Mois de fin :'** + String get seedLibraryEndMonth; + + /// No description provided for @seedLibraryFacebookUrl. + /// + /// In fr, this message translates to: + /// **'Lien Facebook'** + String get seedLibraryFacebookUrl; + + /// No description provided for @seedLibraryFilters. + /// + /// In fr, this message translates to: + /// **'Filtres'** + String get seedLibraryFilters; + + /// No description provided for @seedLibraryForum. + /// + /// In fr, this message translates to: + /// **'Oskour maman j\'ai tué ma plante - Forum d\'aide'** + String get seedLibraryForum; + + /// No description provided for @seedLibraryForumUrl. + /// + /// In fr, this message translates to: + /// **'Lien Forum'** + String get seedLibraryForumUrl; + + /// No description provided for @seedLibraryHelpSheets. + /// + /// In fr, this message translates to: + /// **'Fiches sur les plantes'** + String get seedLibraryHelpSheets; + + /// No description provided for @seedLibraryInformation. + /// + /// In fr, this message translates to: + /// **'Informations :'** + String get seedLibraryInformation; + + /// No description provided for @seedLibraryMaturationTime. + /// + /// In fr, this message translates to: + /// **'Temps de maturation'** + String get seedLibraryMaturationTime; + + /// No description provided for @seedLibraryMonthJan. + /// + /// In fr, this message translates to: + /// **'Janvier'** + String get seedLibraryMonthJan; + + /// No description provided for @seedLibraryMonthFeb. + /// + /// In fr, this message translates to: + /// **'Février'** + String get seedLibraryMonthFeb; + + /// No description provided for @seedLibraryMonthMar. + /// + /// In fr, this message translates to: + /// **'Mars'** + String get seedLibraryMonthMar; + + /// No description provided for @seedLibraryMonthApr. + /// + /// In fr, this message translates to: + /// **'Avril'** + String get seedLibraryMonthApr; + + /// No description provided for @seedLibraryMonthMay. + /// + /// In fr, this message translates to: + /// **'Mai'** + String get seedLibraryMonthMay; + + /// No description provided for @seedLibraryMonthJun. + /// + /// In fr, this message translates to: + /// **'Juin'** + String get seedLibraryMonthJun; + + /// No description provided for @seedLibraryMonthJul. + /// + /// In fr, this message translates to: + /// **'Juillet'** + String get seedLibraryMonthJul; + + /// No description provided for @seedLibraryMonthAug. + /// + /// In fr, this message translates to: + /// **'Août'** + String get seedLibraryMonthAug; + + /// No description provided for @seedLibraryMonthSep. + /// + /// In fr, this message translates to: + /// **'Septembre'** + String get seedLibraryMonthSep; + + /// No description provided for @seedLibraryMonthOct. + /// + /// In fr, this message translates to: + /// **'Octobre'** + String get seedLibraryMonthOct; + + /// No description provided for @seedLibraryMonthNov. + /// + /// In fr, this message translates to: + /// **'Novembre'** + String get seedLibraryMonthNov; + + /// No description provided for @seedLibraryMonthDec. + /// + /// In fr, this message translates to: + /// **'Décembre'** + String get seedLibraryMonthDec; + + /// No description provided for @seedLibraryMyPlants. + /// + /// In fr, this message translates to: + /// **'Mes plantes'** + String get seedLibraryMyPlants; + + /// No description provided for @seedLibraryName. + /// + /// In fr, this message translates to: + /// **'Nom'** + String get seedLibraryName; + + /// No description provided for @seedLibraryNbSeedsRecommended. + /// + /// In fr, this message translates to: + /// **'Nombre de graines recommandées'** + String get seedLibraryNbSeedsRecommended; + + /// No description provided for @seedLibraryNbSeedsRecommendedError. + /// + /// In fr, this message translates to: + /// **'Veuillez entrer un nombre de graines recommandé supérieur à 0'** + String get seedLibraryNbSeedsRecommendedError; + + /// No description provided for @seedLibraryNoDateError. + /// + /// In fr, this message translates to: + /// **'Veuillez entrer une date'** + String get seedLibraryNoDateError; + + /// No description provided for @seedLibraryNoFilteredPlants. + /// + /// In fr, this message translates to: + /// **'Aucune plante ne correspond à votre recherche. Essayez d\'autres filtres.'** + String get seedLibraryNoFilteredPlants; + + /// No description provided for @seedLibraryNoMorePlant. + /// + /// In fr, this message translates to: + /// **'Aucune plante n\'est disponible'** + String get seedLibraryNoMorePlant; + + /// No description provided for @seedLibraryNoPersonalPlants. + /// + /// In fr, this message translates to: + /// **'Vous n\'avez pas encore de plantes dans votre grainothèque. Vous pouvez en ajouter en allant dans les stocks.'** + String get seedLibraryNoPersonalPlants; + + /// No description provided for @seedLibraryNoSpecies. + /// + /// In fr, this message translates to: + /// **'Aucune espèce trouvée'** + String get seedLibraryNoSpecies; + + /// No description provided for @seedLibraryNoStockPlants. + /// + /// In fr, this message translates to: + /// **'Aucune plante disponible dans le stock'** + String get seedLibraryNoStockPlants; + + /// No description provided for @seedLibraryNotes. + /// + /// In fr, this message translates to: + /// **'Notes'** + String get seedLibraryNotes; + + /// No description provided for @seedLibraryOk. + /// + /// In fr, this message translates to: + /// **'OK'** + String get seedLibraryOk; + + /// No description provided for @seedLibraryPlantationPeriod. + /// + /// In fr, this message translates to: + /// **'Période de plantation :'** + String get seedLibraryPlantationPeriod; + + /// No description provided for @seedLibraryPlantationType. + /// + /// In fr, this message translates to: + /// **'Type de plantation :'** + String get seedLibraryPlantationType; + + /// No description provided for @seedLibraryPlantDetail. + /// + /// In fr, this message translates to: + /// **'Détail de la plante'** + String get seedLibraryPlantDetail; + + /// No description provided for @seedLibraryPlantingDate. + /// + /// In fr, this message translates to: + /// **'Date de plantation'** + String get seedLibraryPlantingDate; + + /// No description provided for @seedLibraryPlantingNow. + /// + /// In fr, this message translates to: + /// **'Je la plante maintenant'** + String get seedLibraryPlantingNow; + + /// No description provided for @seedLibraryPrefix. + /// + /// In fr, this message translates to: + /// **'Préfixe'** + String get seedLibraryPrefix; + + /// No description provided for @seedLibraryPrefixError. + /// + /// In fr, this message translates to: + /// **'Prefixe déjà utilisé'** + String get seedLibraryPrefixError; + + /// No description provided for @seedLibraryPrefixLengthError. + /// + /// In fr, this message translates to: + /// **'Le préfixe doit faire 3 caractères'** + String get seedLibraryPrefixLengthError; + + /// No description provided for @seedLibraryPropagationMethod. + /// + /// In fr, this message translates to: + /// **'Méthode de propagation :'** + String get seedLibraryPropagationMethod; + + /// No description provided for @seedLibraryReference. + /// + /// In fr, this message translates to: + /// **'Référence :'** + String get seedLibraryReference; + + /// No description provided for @seedLibraryRemovedPlant. + /// + /// In fr, this message translates to: + /// **'Plante supprimée'** + String get seedLibraryRemovedPlant; + + /// No description provided for @seedLibraryRemovingError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de la suppression'** + String get seedLibraryRemovingError; + + /// No description provided for @seedLibraryResearch. + /// + /// In fr, this message translates to: + /// **'Recherche'** + String get seedLibraryResearch; + + /// No description provided for @seedLibrarySaveChanges. + /// + /// In fr, this message translates to: + /// **'Sauvegarder les modifications'** + String get seedLibrarySaveChanges; + + /// No description provided for @seedLibrarySeason. + /// + /// In fr, this message translates to: + /// **'Saison :'** + String get seedLibrarySeason; + + /// No description provided for @seedLibrarySeed. + /// + /// In fr, this message translates to: + /// **'Graine'** + String get seedLibrarySeed; + + /// No description provided for @seedLibrarySeeds. + /// + /// In fr, this message translates to: + /// **'graines'** + String get seedLibrarySeeds; + + /// No description provided for @seedLibrarySeedDeposit. + /// + /// In fr, this message translates to: + /// **'Dépôt de plantes'** + String get seedLibrarySeedDeposit; + + /// No description provided for @seedLibrarySeedLibrary. + /// + /// In fr, this message translates to: + /// **'Grainothèque'** + String get seedLibrarySeedLibrary; + + /// No description provided for @seedLibrarySeedQuantitySimple. + /// + /// In fr, this message translates to: + /// **'Quantité de graines'** + String get seedLibrarySeedQuantitySimple; + + /// No description provided for @seedLibrarySeedQuantity. + /// + /// In fr, this message translates to: + /// **'Quantité de graines :'** + String get seedLibrarySeedQuantity; + + /// No description provided for @seedLibraryShowDeadPlants. + /// + /// In fr, this message translates to: + /// **'Afficher les plantes mortes'** + String get seedLibraryShowDeadPlants; + + /// No description provided for @seedLibrarySpecies. + /// + /// In fr, this message translates to: + /// **'Espèce :'** + String get seedLibrarySpecies; + + /// No description provided for @seedLibrarySpeciesHelp. + /// + /// In fr, this message translates to: + /// **'Aide sur l\'espèce'** + String get seedLibrarySpeciesHelp; + + /// No description provided for @seedLibrarySpeciesPlural. + /// + /// In fr, this message translates to: + /// **'Espèces'** + String get seedLibrarySpeciesPlural; + + /// No description provided for @seedLibrarySpeciesSimple. + /// + /// In fr, this message translates to: + /// **'Espèce'** + String get seedLibrarySpeciesSimple; + + /// No description provided for @seedLibrarySpeciesType. + /// + /// In fr, this message translates to: + /// **'Type d\'espèce :'** + String get seedLibrarySpeciesType; + + /// No description provided for @seedLibrarySpring. + /// + /// In fr, this message translates to: + /// **'Printemps'** + String get seedLibrarySpring; + + /// No description provided for @seedLibraryStartMonth. + /// + /// In fr, this message translates to: + /// **'Mois de début :'** + String get seedLibraryStartMonth; + + /// No description provided for @seedLibraryStock. + /// + /// In fr, this message translates to: + /// **'Stock disponible'** + String get seedLibraryStock; + + /// No description provided for @seedLibrarySummer. + /// + /// In fr, this message translates to: + /// **'Été'** + String get seedLibrarySummer; + + /// No description provided for @seedLibraryStocks. + /// + /// In fr, this message translates to: + /// **'Stocks'** + String get seedLibraryStocks; + + /// No description provided for @seedLibraryTimeUntilMaturation. + /// + /// In fr, this message translates to: + /// **'Temps avant maturation :'** + String get seedLibraryTimeUntilMaturation; + + /// No description provided for @seedLibraryType. + /// + /// In fr, this message translates to: + /// **'Type :'** + String get seedLibraryType; + + /// No description provided for @seedLibraryUnableToOpen. + /// + /// In fr, this message translates to: + /// **'Impossible d\'ouvrir le lien'** + String get seedLibraryUnableToOpen; + + /// No description provided for @seedLibraryUpdate. + /// + /// In fr, this message translates to: + /// **'Modifier'** + String get seedLibraryUpdate; + + /// No description provided for @seedLibraryUpdatedInformation. + /// + /// In fr, this message translates to: + /// **'Informations modifiées'** + String get seedLibraryUpdatedInformation; + + /// No description provided for @seedLibraryUpdatedSpecies. + /// + /// In fr, this message translates to: + /// **'Espèce modifiée'** + String get seedLibraryUpdatedSpecies; + + /// No description provided for @seedLibraryUpdatedPlant. + /// + /// In fr, this message translates to: + /// **'Plante modifiée'** + String get seedLibraryUpdatedPlant; + + /// No description provided for @seedLibraryUpdatingError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de la modification'** + String get seedLibraryUpdatingError; + + /// No description provided for @seedLibraryWinter. + /// + /// In fr, this message translates to: + /// **'Hiver'** + String get seedLibraryWinter; + + /// No description provided for @seedLibraryWriteReference. + /// + /// In fr, this message translates to: + /// **'Veuillez écrire la référence suivante : '** + String get seedLibraryWriteReference; + + /// No description provided for @settingsAccount. + /// + /// In fr, this message translates to: + /// **'Compte'** + String get settingsAccount; + + /// No description provided for @settingsAddProfilePicture. + /// + /// In fr, this message translates to: + /// **'Ajouter une photo'** + String get settingsAddProfilePicture; + + /// No description provided for @settingsAdmin. + /// + /// In fr, this message translates to: + /// **'Administrateur'** + String get settingsAdmin; + + /// No description provided for @settingsAskHelp. + /// + /// In fr, this message translates to: + /// **'Demander de l\'aide'** + String get settingsAskHelp; + + /// No description provided for @settingsAssociation. + /// + /// In fr, this message translates to: + /// **'Association'** + String get settingsAssociation; + + /// No description provided for @settingsBirthday. + /// + /// In fr, this message translates to: + /// **'Date de naissance'** + String get settingsBirthday; + + /// No description provided for @settingsBugs. + /// + /// In fr, this message translates to: + /// **'Bugs'** + String get settingsBugs; + + /// No description provided for @settingsChangePassword. + /// + /// In fr, this message translates to: + /// **'Changer de mot de passe'** + String get settingsChangePassword; + + /// No description provided for @settingsChangingPassword. + /// + /// In fr, this message translates to: + /// **'Voulez-vous vraiment changer votre mot de passe ?'** + String get settingsChangingPassword; + + /// No description provided for @settingsConfirmPassword. + /// + /// In fr, this message translates to: + /// **'Confirmer le mot de passe'** + String get settingsConfirmPassword; + + /// No description provided for @settingsCopied. + /// + /// In fr, this message translates to: + /// **'Copié !'** + String get settingsCopied; + + /// No description provided for @settingsDarkMode. + /// + /// In fr, this message translates to: + /// **'Mode sombre'** + String get settingsDarkMode; + + /// No description provided for @settingsDarkModeOff. + /// + /// In fr, this message translates to: + /// **'Désactivé'** + String get settingsDarkModeOff; + + /// No description provided for @settingsDeleteLogs. + /// + /// In fr, this message translates to: + /// **'Supprimer les logs ?'** + String get settingsDeleteLogs; + + /// No description provided for @settingsDeleteNotificationLogs. + /// + /// In fr, this message translates to: + /// **'Supprimer les logs des notifications ?'** + String get settingsDeleteNotificationLogs; + + /// No description provided for @settingsDetelePersonalData. + /// + /// In fr, this message translates to: + /// **'Supprimer mes données personnelles'** + String get settingsDetelePersonalData; + + /// No description provided for @settingsDetelePersonalDataDesc. + /// + /// In fr, this message translates to: + /// **'Cette action notifie l\'administrateur que vous souhaitez supprimer vos données personnelles.'** + String get settingsDetelePersonalDataDesc; + + /// No description provided for @settingsDeleting. + /// + /// In fr, this message translates to: + /// **'Suppresion'** + String get settingsDeleting; + + /// No description provided for @settingsEdit. + /// + /// In fr, this message translates to: + /// **'Modifier'** + String get settingsEdit; + + /// No description provided for @settingsEditAccount. + /// + /// In fr, this message translates to: + /// **'Modifier le compte'** + String get settingsEditAccount; + + /// No description provided for @settingsEditPassword. + /// + /// In fr, this message translates to: + /// **'Modifier le mot de passe'** + String get settingsEditPassword; + + /// No description provided for @settingsEmail. + /// + /// In fr, this message translates to: + /// **'Email'** + String get settingsEmail; + + /// No description provided for @settingsEmptyField. + /// + /// In fr, this message translates to: + /// **'Ce champ ne peut pas être vide'** + String get settingsEmptyField; + + /// No description provided for @settingsErrorProfilePicture. + /// + /// In fr, this message translates to: + /// **'Erreur lors de la modification de la photo de profil'** + String get settingsErrorProfilePicture; + + /// No description provided for @settingsErrorSendingDemand. + /// + /// In fr, this message translates to: + /// **'Erreur lors de l\'envoi de la demande'** + String get settingsErrorSendingDemand; + + /// No description provided for @settingsEventsIcal. + /// + /// In fr, this message translates to: + /// **'Lien Ical des événements'** + String get settingsEventsIcal; + + /// No description provided for @settingsExpectingDate. + /// + /// In fr, this message translates to: + /// **'Date de naissance attendue'** + String get settingsExpectingDate; + + /// No description provided for @settingsFirstname. + /// + /// In fr, this message translates to: + /// **'Prénom'** + String get settingsFirstname; + + /// No description provided for @settingsFloor. + /// + /// In fr, this message translates to: + /// **'Étage'** + String get settingsFloor; + + /// No description provided for @settingsHelp. + /// + /// In fr, this message translates to: + /// **'Aide'** + String get settingsHelp; + + /// No description provided for @settingsIcalCopied. + /// + /// In fr, this message translates to: + /// **'Lien Ical copié !'** + String get settingsIcalCopied; + + /// No description provided for @settingsLanguage. + /// + /// In fr, this message translates to: + /// **'Langue'** + String get settingsLanguage; + + /// No description provided for @settingsLanguageFr. + /// + /// In fr, this message translates to: + /// **'Français'** + String get settingsLanguageFr; + + /// No description provided for @settingsLogs. + /// + /// In fr, this message translates to: + /// **'Logs'** + String get settingsLogs; + + /// No description provided for @settingsModules. + /// + /// In fr, this message translates to: + /// **'Modules'** + String get settingsModules; + + /// No description provided for @settingsMyIcs. + /// + /// In fr, this message translates to: + /// **'Mon lien Ical'** + String get settingsMyIcs; + + /// No description provided for @settingsName. + /// + /// In fr, this message translates to: + /// **'Nom'** + String get settingsName; + + /// No description provided for @settingsNewPassword. + /// + /// In fr, this message translates to: + /// **'Nouveau mot de passe'** + String get settingsNewPassword; + + /// No description provided for @settingsNickname. + /// + /// In fr, this message translates to: + /// **'Surnom'** + String get settingsNickname; + + /// No description provided for @settingsNotifications. + /// + /// In fr, this message translates to: + /// **'Notifications'** + String get settingsNotifications; + + /// No description provided for @settingsOldPassword. + /// + /// In fr, this message translates to: + /// **'Ancien mot de passe'** + String get settingsOldPassword; + + /// No description provided for @settingsPasswordChanged. + /// + /// In fr, this message translates to: + /// **'Mot de passe changé'** + String get settingsPasswordChanged; + + /// No description provided for @settingsPasswordsNotMatch. + /// + /// In fr, this message translates to: + /// **'Les mots de passe ne correspondent pas'** + String get settingsPasswordsNotMatch; + + /// No description provided for @settingsPersonalData. + /// + /// In fr, this message translates to: + /// **'Données personnelles'** + String get settingsPersonalData; + + /// No description provided for @settingsPersonalisation. + /// + /// In fr, this message translates to: + /// **'Personnalisation'** + String get settingsPersonalisation; + + /// No description provided for @settingsPhone. + /// + /// In fr, this message translates to: + /// **'Téléphone'** + String get settingsPhone; + + /// No description provided for @settingsProfilePicture. + /// + /// In fr, this message translates to: + /// **'Photo de profil'** + String get settingsProfilePicture; + + /// No description provided for @settingsPromo. + /// + /// In fr, this message translates to: + /// **'Promotion'** + String get settingsPromo; + + /// No description provided for @settingsRepportBug. + /// + /// In fr, this message translates to: + /// **'Signaler un bug'** + String get settingsRepportBug; + + /// No description provided for @settingsSave. + /// + /// In fr, this message translates to: + /// **'Enregistrer'** + String get settingsSave; + + /// No description provided for @settingsSecurity. + /// + /// In fr, this message translates to: + /// **'Sécurité'** + String get settingsSecurity; + + /// No description provided for @settingsSendedDemand. + /// + /// In fr, this message translates to: + /// **'Demande envoyée'** + String get settingsSendedDemand; + + /// No description provided for @settingsSettings. + /// + /// In fr, this message translates to: + /// **'Paramètres'** + String get settingsSettings; + + /// No description provided for @settingsTooHeavyProfilePicture. + /// + /// In fr, this message translates to: + /// **'L\'image est trop lourde (max 4Mo)'** + String get settingsTooHeavyProfilePicture; + + /// No description provided for @settingsUpdatedProfile. + /// + /// In fr, this message translates to: + /// **'Profil modifié'** + String get settingsUpdatedProfile; + + /// No description provided for @settingsUpdatedProfilePicture. + /// + /// In fr, this message translates to: + /// **'Photo de profil modifiée'** + String get settingsUpdatedProfilePicture; + + /// No description provided for @settingsUpdateNotification. + /// + /// In fr, this message translates to: + /// **'Mettre à jour les notifications'** + String get settingsUpdateNotification; + + /// No description provided for @settingsUpdatingError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de la modification du profil'** + String get settingsUpdatingError; + + /// No description provided for @settingsVersion. + /// + /// In fr, this message translates to: + /// **'Version'** + String get settingsVersion; + + /// No description provided for @settingsPasswordStrength. + /// + /// In fr, this message translates to: + /// **'Force du mot de passe'** + String get settingsPasswordStrength; + + /// No description provided for @settingsPasswordStrengthVeryWeak. + /// + /// In fr, this message translates to: + /// **'Très faible'** + String get settingsPasswordStrengthVeryWeak; + + /// No description provided for @settingsPasswordStrengthWeak. + /// + /// In fr, this message translates to: + /// **'Faible'** + String get settingsPasswordStrengthWeak; + + /// No description provided for @settingsPasswordStrengthMedium. + /// + /// In fr, this message translates to: + /// **'Moyen'** + String get settingsPasswordStrengthMedium; + + /// No description provided for @settingsPasswordStrengthStrong. + /// + /// In fr, this message translates to: + /// **'Fort'** + String get settingsPasswordStrengthStrong; + + /// No description provided for @settingsPasswordStrengthVeryStrong. + /// + /// In fr, this message translates to: + /// **'Très fort'** + String get settingsPasswordStrengthVeryStrong; + + /// No description provided for @voteAdd. + /// + /// In fr, this message translates to: + /// **'Ajouter'** + String get voteAdd; + + /// No description provided for @voteAddMember. + /// + /// In fr, this message translates to: + /// **'Ajouter un membre'** + String get voteAddMember; + + /// No description provided for @voteAddedPretendance. + /// + /// In fr, this message translates to: + /// **'Liste ajoutée'** + String get voteAddedPretendance; + + /// No description provided for @voteAddedSection. + /// + /// In fr, this message translates to: + /// **'Section ajoutée'** + String get voteAddedSection; + + /// No description provided for @voteAddingError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de l\'ajout'** + String get voteAddingError; + + /// No description provided for @voteAddPretendance. + /// + /// In fr, this message translates to: + /// **'Ajouter une liste'** + String get voteAddPretendance; + + /// No description provided for @voteAddSection. + /// + /// In fr, this message translates to: + /// **'Ajouter une section'** + String get voteAddSection; + + /// No description provided for @voteAll. + /// + /// In fr, this message translates to: + /// **'Tous'** + String get voteAll; + + /// No description provided for @voteAlreadyAddedMember. + /// + /// In fr, this message translates to: + /// **'Membre déjà ajouté'** + String get voteAlreadyAddedMember; + + /// No description provided for @voteAlreadyVoted. + /// + /// In fr, this message translates to: + /// **'Vote enregistré'** + String get voteAlreadyVoted; + + /// No description provided for @voteChooseList. + /// + /// In fr, this message translates to: + /// **'Choisir une liste'** + String get voteChooseList; + + /// No description provided for @voteClear. + /// + /// In fr, this message translates to: + /// **'Réinitialiser'** + String get voteClear; + + /// No description provided for @voteClearVotes. + /// + /// In fr, this message translates to: + /// **'Réinitialiser les votes'** + String get voteClearVotes; + + /// No description provided for @voteClosedVote. + /// + /// In fr, this message translates to: + /// **'Votes clos'** + String get voteClosedVote; + + /// No description provided for @voteCloseVote. + /// + /// In fr, this message translates to: + /// **'Fermer les votes'** + String get voteCloseVote; + + /// No description provided for @voteConfirmVote. + /// + /// In fr, this message translates to: + /// **'Confirmer le vote'** + String get voteConfirmVote; + + /// No description provided for @voteCountVote. + /// + /// In fr, this message translates to: + /// **'Dépouiller les votes'** + String get voteCountVote; + + /// No description provided for @voteDeletedAll. + /// + /// In fr, this message translates to: + /// **'Tout supprimé'** + String get voteDeletedAll; + + /// No description provided for @voteDeletedPipo. + /// + /// In fr, this message translates to: + /// **'Listes pipos supprimées'** + String get voteDeletedPipo; + + /// No description provided for @voteDeletedSection. + /// + /// In fr, this message translates to: + /// **'Section supprimée'** + String get voteDeletedSection; + + /// No description provided for @voteDeleteAll. + /// + /// In fr, this message translates to: + /// **'Supprimer tout'** + String get voteDeleteAll; + + /// No description provided for @voteDeleteAllDescription. + /// + /// In fr, this message translates to: + /// **'Voulez-vous vraiment supprimer tout ?'** + String get voteDeleteAllDescription; + + /// No description provided for @voteDeletePipo. + /// + /// In fr, this message translates to: + /// **'Supprimer les listes pipos'** + String get voteDeletePipo; + + /// No description provided for @voteDeletePipoDescription. + /// + /// In fr, this message translates to: + /// **'Voulez-vous vraiment supprimer les listes pipos ?'** + String get voteDeletePipoDescription; + + /// No description provided for @voteDeletePretendance. + /// + /// In fr, this message translates to: + /// **'Supprimer la liste'** + String get voteDeletePretendance; + + /// No description provided for @voteDeletePretendanceDesc. + /// + /// In fr, this message translates to: + /// **'Voulez-vous vraiment supprimer cette liste ?'** + String get voteDeletePretendanceDesc; + + /// No description provided for @voteDeleteSection. + /// + /// In fr, this message translates to: + /// **'Supprimer la section'** + String get voteDeleteSection; + + /// No description provided for @voteDeleteSectionDescription. + /// + /// In fr, this message translates to: + /// **'Voulez-vous vraiment supprimer cette section ?'** + String get voteDeleteSectionDescription; + + /// No description provided for @voteDeletingError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de la suppression'** + String get voteDeletingError; + + /// No description provided for @voteDescription. + /// + /// In fr, this message translates to: + /// **'Description'** + String get voteDescription; + + /// No description provided for @voteEdit. + /// + /// In fr, this message translates to: + /// **'Modifier'** + String get voteEdit; + + /// No description provided for @voteEditedPretendance. + /// + /// In fr, this message translates to: + /// **'Liste modifiée'** + String get voteEditedPretendance; + + /// No description provided for @voteEditedSection. + /// + /// In fr, this message translates to: + /// **'Section modifiée'** + String get voteEditedSection; + + /// No description provided for @voteEditingError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de la modification'** + String get voteEditingError; + + /// No description provided for @voteErrorClosingVotes. + /// + /// In fr, this message translates to: + /// **'Erreur lors de la fermeture des votes'** + String get voteErrorClosingVotes; + + /// No description provided for @voteErrorCountingVotes. + /// + /// In fr, this message translates to: + /// **'Erreur lors du dépouillement des votes'** + String get voteErrorCountingVotes; + + /// No description provided for @voteErrorResetingVotes. + /// + /// In fr, this message translates to: + /// **'Erreur lors de la réinitialisation des votes'** + String get voteErrorResetingVotes; + + /// No description provided for @voteErrorOpeningVotes. + /// + /// In fr, this message translates to: + /// **'Erreur lors de l\'ouverture des votes'** + String get voteErrorOpeningVotes; + + /// No description provided for @voteIncorrectOrMissingFields. + /// + /// In fr, this message translates to: + /// **'Champs incorrects ou manquants'** + String get voteIncorrectOrMissingFields; + + /// No description provided for @voteMembers. + /// + /// In fr, this message translates to: + /// **'Membres'** + String get voteMembers; + + /// No description provided for @voteName. + /// + /// In fr, this message translates to: + /// **'Nom'** + String get voteName; + + /// No description provided for @voteNoPretendanceList. + /// + /// In fr, this message translates to: + /// **'Aucune liste de prétendance'** + String get voteNoPretendanceList; + + /// No description provided for @voteNoSection. + /// + /// In fr, this message translates to: + /// **'Aucune section'** + String get voteNoSection; + + /// No description provided for @voteCanNotVote. + /// + /// In fr, this message translates to: + /// **'Vous ne pouvez pas voter'** + String get voteCanNotVote; + + /// No description provided for @voteNoSectionList. + /// + /// In fr, this message translates to: + /// **'Aucune section'** + String get voteNoSectionList; + + /// No description provided for @voteNotOpenedVote. + /// + /// In fr, this message translates to: + /// **'Vote non ouvert'** + String get voteNotOpenedVote; + + /// No description provided for @voteOnGoingCount. + /// + /// In fr, this message translates to: + /// **'Dépouillement en cours'** + String get voteOnGoingCount; + + /// No description provided for @voteOpenVote. + /// + /// In fr, this message translates to: + /// **'Ouvrir les votes'** + String get voteOpenVote; + + /// No description provided for @votePipo. + /// + /// In fr, this message translates to: + /// **'Pipo'** + String get votePipo; + + /// No description provided for @votePretendance. + /// + /// In fr, this message translates to: + /// **'Listes'** + String get votePretendance; + + /// No description provided for @votePretendanceDeleted. + /// + /// In fr, this message translates to: + /// **'Prétendance supprimée'** + String get votePretendanceDeleted; + + /// No description provided for @votePretendanceNotDeleted. + /// + /// In fr, this message translates to: + /// **'Erreur lors de la suppression'** + String get votePretendanceNotDeleted; + + /// No description provided for @voteProgram. + /// + /// In fr, this message translates to: + /// **'Programme'** + String get voteProgram; + + /// No description provided for @votePublish. + /// + /// In fr, this message translates to: + /// **'Publier'** + String get votePublish; + + /// No description provided for @votePublishVoteDescription. + /// + /// In fr, this message translates to: + /// **'Voulez-vous vraiment publier les votes ?'** + String get votePublishVoteDescription; + + /// No description provided for @voteResetedVotes. + /// + /// In fr, this message translates to: + /// **'Votes réinitialisés'** + String get voteResetedVotes; + + /// No description provided for @voteResetVote. + /// + /// In fr, this message translates to: + /// **'Réinitialiser les votes'** + String get voteResetVote; + + /// No description provided for @voteResetVoteDescription. + /// + /// In fr, this message translates to: + /// **'Que voulez-vous faire ?'** + String get voteResetVoteDescription; + + /// No description provided for @voteRole. + /// + /// In fr, this message translates to: + /// **'Rôle'** + String get voteRole; + + /// No description provided for @voteSectionDescription. + /// + /// In fr, this message translates to: + /// **'Description de la section'** + String get voteSectionDescription; + + /// No description provided for @voteSection. + /// + /// In fr, this message translates to: + /// **'Section'** + String get voteSection; + + /// No description provided for @voteSectionName. + /// + /// In fr, this message translates to: + /// **'Nom de la section'** + String get voteSectionName; + + /// No description provided for @voteSeeMore. + /// + /// In fr, this message translates to: + /// **'Voir plus'** + String get voteSeeMore; + + /// No description provided for @voteSelected. + /// + /// In fr, this message translates to: + /// **'Sélectionné'** + String get voteSelected; + + /// No description provided for @voteShowVotes. + /// + /// In fr, this message translates to: + /// **'Voir les votes'** + String get voteShowVotes; + + /// No description provided for @voteVote. + /// + /// In fr, this message translates to: + /// **'Vote'** + String get voteVote; + + /// No description provided for @voteVoteError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de l\'enregistrement du vote'** + String get voteVoteError; + + /// No description provided for @voteVoteFor. + /// + /// In fr, this message translates to: + /// **'Voter pour '** + String get voteVoteFor; + + /// No description provided for @voteVoteNotStarted. + /// + /// In fr, this message translates to: + /// **'Vote non ouvert'** + String get voteVoteNotStarted; + + /// No description provided for @voteVoters. + /// + /// In fr, this message translates to: + /// **'Groupes votants'** + String get voteVoters; + + /// No description provided for @voteVoteSuccess. + /// + /// In fr, this message translates to: + /// **'Vote enregistré'** + String get voteVoteSuccess; + + /// No description provided for @voteVotes. + /// + /// In fr, this message translates to: + /// **'Voix'** + String get voteVotes; + + /// No description provided for @voteVotesClosed. + /// + /// In fr, this message translates to: + /// **'Votes clos'** + String get voteVotesClosed; + + /// No description provided for @voteVotesCounted. + /// + /// In fr, this message translates to: + /// **'Votes dépouillés'** + String get voteVotesCounted; + + /// No description provided for @voteVotesOpened. + /// + /// In fr, this message translates to: + /// **'Votes ouverts'** + String get voteVotesOpened; + + /// No description provided for @voteWarning. + /// + /// In fr, this message translates to: + /// **'Attention'** + String get voteWarning; + + /// No description provided for @voteWarningMessage. + /// + /// In fr, this message translates to: + /// **'La sélection ne sera pas sauvegardée.\nVoulez-vous continuer ?'** + String get voteWarningMessage; +} + +class _AppLocalizationsDelegate + extends LocalizationsDelegate { + const _AppLocalizationsDelegate(); + + @override + Future load(Locale locale) { + return SynchronousFuture(lookupAppLocalizations(locale)); + } + + @override + bool isSupported(Locale locale) => + ['en', 'fr'].contains(locale.languageCode); + + @override + bool shouldReload(_AppLocalizationsDelegate old) => false; +} + +AppLocalizations lookupAppLocalizations(Locale locale) { + // Lookup logic when only language code is specified. + switch (locale.languageCode) { + case 'en': + return AppLocalizationsEn(); + case 'fr': + return AppLocalizationsFr(); + } + + throw FlutterError( + 'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely ' + 'an issue with the localizations generation tool. Please file an issue ' + 'on GitHub with a reproducible sample app and the gen-l10n configuration ' + 'that was used.', + ); +} diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart new file mode 100644 index 0000000000..3582190db8 --- /dev/null +++ b/lib/l10n/app_localizations_en.dart @@ -0,0 +1,3397 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'app_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for English (`en`). +class AppLocalizationsEn extends AppLocalizations { + AppLocalizationsEn([String locale = 'en']) : super(locale); + + @override + String get adminAccountTypes => 'Types de compte'; + + @override + String get adminAdd => 'Ajouter'; + + @override + String get adminAddGroup => 'Ajouter un groupe'; + + @override + String get adminAddMember => 'Ajouter un membre'; + + @override + String get adminAddedGroup => 'Groupe créé'; + + @override + String get adminAddedLoaner => 'Préteur ajouté'; + + @override + String get adminAddedMember => 'Membre ajouté'; + + @override + String get adminAddingError => 'Erreur lors de l\'ajout'; + + @override + String get adminAddingMember => 'Ajout d\'un membre'; + + @override + String get adminAddLoaningGroup => 'Ajouter un groupe de prêt'; + + @override + String get adminAddSchool => 'Ajouter une école'; + + @override + String get adminAddStructure => 'Ajouter une structure'; + + @override + String get adminAddedSchool => 'École créée'; + + @override + String get adminAddedStructure => 'Structure ajoutée'; + + @override + String get adminEditedStructure => 'Structure modifiée'; + + @override + String get adminAdministration => 'Administration'; + + @override + String get adminAssociationMembership => 'Adhésion'; + + @override + String get adminAssociationMembershipName => 'Nom de l\'adhésion'; + + @override + String get adminAssociationsMemberships => 'Adhésions'; + + @override + String get adminClearFilters => 'Effacer les filtres'; + + @override + String get adminCreateAssociationMembership => 'Créer une adhésion'; + + @override + String get adminCreatedAssociationMembership => 'Adhésion créée'; + + @override + String get adminCreationError => 'Erreur lors de la création'; + + @override + String get adminDateError => + 'La date de début doit être avant la date de fin'; + + @override + String get adminDelete => 'Supprimer'; + + @override + String get adminDeleteAssociationMembership => 'Supprimer l\'adhésion ?'; + + @override + String get adminDeletedAssociationMembership => 'Adhésion supprimée'; + + @override + String get adminDeleteGroup => 'Supprimer le groupe ?'; + + @override + String get adminDeletedGroup => 'Groupe supprimé'; + + @override + String get adminDeleteSchool => 'Supprimer l\'école ?'; + + @override + String get adminDeletedSchool => 'École supprimée'; + + @override + String get adminDeleting => 'Suppression'; + + @override + String get adminDeletingError => 'Erreur lors de la suppression'; + + @override + String get adminDescription => 'Description'; + + @override + String get adminEclSchool => 'Centrale Lyon'; + + @override + String get adminEdit => 'Modifier'; + + @override + String get adminEditStructure => 'Modifier la structure'; + + @override + String get adminEditMembership => 'Modifier l\'adhésion'; + + @override + String get adminEmptyDate => 'Date vide'; + + @override + String get adminEmptyFieldError => 'Le nom ne peut pas être vide'; + + @override + String get adminEmailRegex => 'Email Regex'; + + @override + String get adminEmptyUser => 'Utilisateur vide'; + + @override + String get adminEndDate => 'Date de fin'; + + @override + String get adminEndDateMaximal => 'Date de fin maximale'; + + @override + String get adminEndDateMinimal => 'Date de fin minimale'; + + @override + String get adminError => 'Erreur'; + + @override + String get adminFilters => 'Filtres'; + + @override + String get adminGroup => 'Groupe'; + + @override + String get adminGroups => 'Groupes'; + + @override + String get adminLoaningGroup => 'Groupe de prêt'; + + @override + String get adminLooking => 'Recherche'; + + @override + String get adminManager => 'Administrateur de la structure'; + + @override + String get adminMaximum => 'Maximum'; + + @override + String get adminMembers => 'Membres'; + + @override + String get adminMembershipAddingError => + 'Erreur lors de l\'ajout (surement dû à une superposition de dates)'; + + @override + String get adminMemberships => 'Adhésions'; + + @override + String get adminMembershipUpdatingError => + 'Erreur lors de la modification (surement dû à une superposition de dates)'; + + @override + String get adminMinimum => 'Minimum'; + + @override + String get adminModifyModuleVisibility => 'Visibilité des modules'; + + @override + String get adminMyEclPay => 'MyECLPay'; + + @override + String get adminName => 'Nom'; + + @override + String get adminNoManager => 'Aucun manager n\'est sélectionné'; + + @override + String get adminNoMember => 'Aucun membre'; + + @override + String get adminNoMoreLoaner => 'Aucun prêteur n\'est disponible'; + + @override + String get adminNoSchool => 'Sans école'; + + @override + String get adminRemoveGroupMember => 'Supprimer le membre du groupe ?'; + + @override + String get adminResearch => 'Recherche'; + + @override + String get adminSchools => 'Écoles'; + + @override + String get adminStructures => 'Structures'; + + @override + String get adminStartDate => 'Date de début'; + + @override + String get adminStartDateMaximal => 'Date de début maximale'; + + @override + String get adminStartDateMinimal => 'Date de début minimale'; + + @override + String get adminUpdatedAssociationMembership => 'Adhésion modifiée'; + + @override + String get adminUpdatedGroup => 'Groupe modifié'; + + @override + String get adminUpdatedMembership => 'Adhésion modifiée'; + + @override + String get adminUpdatingError => 'Erreur lors de la modification'; + + @override + String get adminUser => 'Utilisateur'; + + @override + String get adminValidateFilters => 'Valider les filtres'; + + @override + String get adminVisibilities => 'Visibilités'; + + @override + String get advertAdd => 'Ajouter'; + + @override + String get advertAddedAdvert => 'Annonce publiée'; + + @override + String get advertAddedAnnouncer => 'Annonceur ajouté'; + + @override + String get advertAddingError => 'Erreur lors de l\'ajout'; + + @override + String get advertAdmin => 'Admin'; + + @override + String get advertAdvert => 'Annonce'; + + @override + String get advertChoosingAnnouncer => 'Veuillez choisir un annonceur'; + + @override + String get advertChoosingPoster => 'Veuillez choisir une image'; + + @override + String get advertContent => 'Contenu'; + + @override + String get advertDeleteAdvert => 'Supprimer l\'annonce ?'; + + @override + String get advertDeleteAnnouncer => 'Supprimer l\'annonceur ?'; + + @override + String get advertDeleting => 'Suppression'; + + @override + String get advertEdit => 'Modifier'; + + @override + String get advertEditedAdvert => 'Annonce modifiée'; + + @override + String get advertEditingError => 'Erreur lors de la modification'; + + @override + String get advertGroupAdvert => 'Groupe'; + + @override + String get advertIncorrectOrMissingFields => 'Champs incorrects ou manquants'; + + @override + String get advertInvalidNumber => 'Veuillez entrer un nombre'; + + @override + String get advertManagement => 'Gestion'; + + @override + String get advertModifyAnnouncingGroup => 'Modifier un groupe d\'annonce'; + + @override + String get advertNoMoreAnnouncer => 'Aucun annonceur n\'est disponible'; + + @override + String get advertNoValue => 'Veuillez entrer une valeur'; + + @override + String get advertPositiveNumber => 'Veuillez entrer un nombre positif'; + + @override + String get advertRemovedAnnouncer => 'Annonceur supprimé'; + + @override + String get advertRemovingError => 'Erreur lors de la suppression'; + + @override + String get advertTags => 'Tags'; + + @override + String get advertTitle => 'Titre'; + + @override + String get advertMonthJan => 'Janv'; + + @override + String get advertMonthFeb => 'Févr.'; + + @override + String get advertMonthMar => 'Mars'; + + @override + String get advertMonthApr => 'Avr.'; + + @override + String get advertMonthMay => 'Mai'; + + @override + String get advertMonthJun => 'Juin'; + + @override + String get advertMonthJul => 'Juill.'; + + @override + String get advertMonthAug => 'Août'; + + @override + String get advertMonthSep => 'Sept.'; + + @override + String get advertMonthOct => 'Oct.'; + + @override + String get advertMonthNov => 'Nov.'; + + @override + String get advertMonthDec => 'Déc.'; + + @override + String get amapAccounts => 'Comptes'; + + @override + String get amapAdd => 'Ajouter'; + + @override + String get amapAddDelivery => 'Ajouter une livraison'; + + @override + String get amapAddedCommand => 'Commande ajoutée'; + + @override + String get amapAddedOrder => 'Commande ajoutée'; + + @override + String get amapAddedProduct => 'Produit ajouté'; + + @override + String get amapAddedUser => 'Utilisateur ajouté'; + + @override + String get amapAddProduct => 'Ajouter un produit'; + + @override + String get amapAddUser => 'Ajouter un utilisateur'; + + @override + String get amapAddingACommand => 'Ajouter une commande'; + + @override + String get amapAddingCommand => 'Ajouter la commande'; + + @override + String get amapAddingError => 'Erreur lors de l\'ajout'; + + @override + String get amapAddingProduct => 'Ajouter un produit'; + + @override + String get amapAddOrder => 'Ajouter une commande'; + + @override + String get amapAdmin => 'Admin'; + + @override + String get amapAlreadyExistCommand => + 'Il existe déjà une commande à cette date'; + + @override + String get amapAmap => 'Amap'; + + @override + String get amapAmount => 'Solde'; + + @override + String get amapArchive => 'Archiver'; + + @override + String get amapArchiveDelivery => 'Archiver'; + + @override + String get amapArchivingDelivery => 'Archivage de la livraison'; + + @override + String get amapCategory => 'Catégorie'; + + @override + String get amapCloseDelivery => 'Verrouiller'; + + @override + String get amapCommandDate => 'Date de la commande'; + + @override + String get amapCommandProducts => 'Produits de la commande'; + + @override + String get amapConfirm => 'Confirmer'; + + @override + String get amapContact => 'Contacts associatifs '; + + @override + String get amapCreateCategory => 'Créer une catégorie'; + + @override + String get amapDelete => 'Supprimer'; + + @override + String get amapDeleteDelivery => 'Supprimer la livraison ?'; + + @override + String get amapDeleteDeliveryDescription => + 'Voulez-vous vraiment supprimer cette livraison ?'; + + @override + String get amapDeletedDelivery => 'Livraison supprimée'; + + @override + String get amapDeletedOrder => 'Commande supprimée'; + + @override + String get amapDeletedProduct => 'Produit supprimé'; + + @override + String get amapDeleteProduct => 'Supprimer le produit ?'; + + @override + String get amapDeleteProductDescription => + 'Voulez-vous vraiment supprimer ce produit ?'; + + @override + String get amapDeleting => 'Suppression'; + + @override + String get amapDeletingDelivery => 'Supprimer la livraison ?'; + + @override + String get amapDeletingError => 'Erreur lors de la suppression'; + + @override + String get amapDeletingOrder => 'Supprimer la commande ?'; + + @override + String get amapDeletingProduct => 'Supprimer le produit ?'; + + @override + String get amapDeliver => 'Livraison teminée ?'; + + @override + String get amapDeliveries => 'Livraisons'; + + @override + String get amapDeliveringDelivery => 'Toutes les commandes sont livrées ?'; + + @override + String get amapDelivery => 'Livraison'; + + @override + String get amapDeliveryArchived => 'Livraison archivée'; + + @override + String get amapDeliveryDate => 'Date de livraison'; + + @override + String get amapDeliveryDelivered => 'Livraison effectuée'; + + @override + String get amapDeliveryHistory => 'Historique des livraisons'; + + @override + String get amapDeliveryList => 'Liste des livraisons'; + + @override + String get amapDeliveryLocked => 'Livraison verrouillée'; + + @override + String get amapDeliveryOn => 'Livraison le'; + + @override + String get amapDeliveryOpened => 'Livraison ouverte'; + + @override + String get amapDeliveryNotArchived => 'Livraison non archivée'; + + @override + String get amapDeliveryNotLocked => 'Livraison non verrouillée'; + + @override + String get amapDeliveryNotDelivered => 'Livraison non effectuée'; + + @override + String get amapDeliveryNotOpened => 'Livraison non ouverte'; + + @override + String get amapEditDelivery => 'Modifier la livraison'; + + @override + String get amapEditedCommand => 'Commande modifiée'; + + @override + String get amapEditingError => 'Erreur lors de la modification'; + + @override + String get amapEditProduct => 'Modifier le produit'; + + @override + String get amapEndingDelivery => 'Fin de la livraison'; + + @override + String get amapError => 'Erreur'; + + @override + String get amapErrorLink => 'Erreur lors de l\'ouverture du lien'; + + @override + String get amapErrorLoadingUser => + 'Erreur lors du chargement des utilisateurs'; + + @override + String get amapEvening => 'Soir'; + + @override + String get amapExpectingNumber => 'Veuillez entrer un nombre'; + + @override + String get amapFillField => 'Veuillez remplir ce champ'; + + @override + String get amapHandlingAccount => 'Gérer les comptes'; + + @override + String get amapLoading => 'Chargement...'; + + @override + String get amapLoadingError => 'Erreur lors du chargement'; + + @override + String get amapLock => 'Verrouiller'; + + @override + String get amapLocked => 'Verrouillée'; + + @override + String get amapLockedDelivery => 'Livraison verrouillée'; + + @override + String get amapLockedOrder => 'Commande verrouillée'; + + @override + String get amapLooking => 'Rechercher'; + + @override + String get amapLockingDelivery => 'Verrouiller la livraison ?'; + + @override + String get amapMidDay => 'Midi'; + + @override + String get amapMyOrders => 'Mes commandes'; + + @override + String get amapName => 'Nom'; + + @override + String get amapNextStep => 'Étape suivante'; + + @override + String get amapNoProduct => 'Pas de produit'; + + @override + String get amapNoCurrentOrder => 'Pas de commande en cours'; + + @override + String get amapNoMoney => 'Pas assez d\'argent'; + + @override + String get amapNoOpennedDelivery => 'Pas de livraison ouverte'; + + @override + String get amapNoOrder => 'Pas de commande'; + + @override + String get amapNoSelectedDelivery => 'Pas de livraison sélectionnée'; + + @override + String get amapNotEnoughMoney => 'Pas assez d\'argent'; + + @override + String get amapNotPlannedDelivery => 'Pas de livraison planifiée'; + + @override + String get amapOneOrder => 'commande'; + + @override + String get amapOpenDelivery => 'Ouvrir'; + + @override + String get amapOpened => 'Ouverte'; + + @override + String get amapOpenningDelivery => 'Ouvrir la livraison ?'; + + @override + String get amapOrder => 'Commander'; + + @override + String get amapOrders => 'Commandes'; + + @override + String get amapPickChooseCategory => + 'Veuillez entrer une valeur ou choisir une catégorie existante'; + + @override + String get amapPickDeliveryMoment => 'Choisissez un moment de livraison'; + + @override + String get amapPresentation => 'Présentation'; + + @override + String get amapPresentation1 => + 'L\'AMAP (association pour le maintien d\'une agriculture paysanne) est un service proposé par l\'association Planet&Co de l\'ECL. Vous pouvez ainsi recevoir des produits (paniers de fruits et légumes, jus, confitures...) directement sur le campus !\n\nLes commandes doivent être passées avant le vendredi 21h et sont livrées sur le campus le mardi de 13h à 13h45 (ou de 18h15 à 18h30 si vous ne pouvez pas passer le midi) dans le hall du M16.\n\nVous ne pouvez commander que si votre solde le permet. Vous pouvez recharger votre solde via la collecte Lydia ou bien avec un chèque que vous pouvez nous transmettre lors des permanences.\n\nLien vers la collecte Lydia pour le rechargement : '; + + @override + String get amapPresentation2 => + '\n\nN\'hésitez pas à nous contacter en cas de problème !'; + + @override + String get amapPrice => 'Prix'; + + @override + String get amapProduct => 'produit'; + + @override + String get amapProducts => 'Produits'; + + @override + String get amapProductInDelivery => 'Produit dans une livraison non terminée'; + + @override + String get amapQuantity => 'Quantité'; + + @override + String get amapRequiredDate => 'La date est requise'; + + @override + String get amapSeeMore => 'Voir plus'; + + @override + String get amapThe => 'Le'; + + @override + String get amapUnlock => 'Dévérouiller'; + + @override + String get amapUnlockedDelivery => 'Livraison dévérouillée'; + + @override + String get amapUnlockingDelivery => 'Dévérouiller la livraison ?'; + + @override + String get amapUpdate => 'Modifier'; + + @override + String get amapUpdatedAmount => 'Solde modifié'; + + @override + String get amapUpdatedOrder => 'Commande modifiée'; + + @override + String get amapUpdatedProduct => 'Produit modifié'; + + @override + String get amapUpdatingError => 'Echec de la modification'; + + @override + String get amapUsersNotFound => 'Aucun utilisateur trouvé'; + + @override + String get amapWaiting => 'En attente'; + + @override + String get bookingAdd => 'Ajouter'; + + @override + String get bookingAddBookingPage => 'Demande'; + + @override + String get bookingAddRoom => 'Ajouter une salle'; + + @override + String get bookingAddBooking => 'Ajouter une réservation'; + + @override + String get bookingAddedBooking => 'Demande ajoutée'; + + @override + String get bookingAddedRoom => 'Salle ajoutée'; + + @override + String get bookingAddedManager => 'Gestionnaire ajouté'; + + @override + String get bookingAddingError => 'Erreur lors de l\'ajout'; + + @override + String get bookingAddManager => 'Ajouter un gestionnaire'; + + @override + String get bookingAdminPage => 'Administrateur'; + + @override + String get bookingAllDay => 'Toute la journée'; + + @override + String get bookingBookedFor => 'Réservé pour'; + + @override + String get bookingBooking => 'Réservation'; + + @override + String get bookingBookingCreated => 'Réservation créée'; + + @override + String get bookingBookingDemand => 'Demande de réservation'; + + @override + String get bookingBookingNote => 'Note de la réservation'; + + @override + String get bookingBookingPage => 'Réservation'; + + @override + String get bookingBookingReason => 'Motif de la réservation'; + + @override + String get bookingBy => 'par'; + + @override + String get bookingConfirm => 'Confirmer'; + + @override + String get bookingConfirmation => 'Confirmation'; + + @override + String get bookingConfirmBooking => 'Confirmer la réservation ?'; + + @override + String get bookingConfirmed => 'Validée'; + + @override + String get bookingDates => 'Dates'; + + @override + String get bookingDecline => 'Refuser'; + + @override + String get bookingDeclineBooking => 'Refuser la réservation ?'; + + @override + String get bookingDeclined => 'Refusée'; + + @override + String get bookingDelete => 'Supprimer'; + + @override + String get bookingDeleting => 'Suppression'; + + @override + String get bookingDeleteBooking => 'Suppression'; + + @override + String get bookingDeleteBookingConfirmation => + 'Êtes-vous sûr de vouloir supprimer cette réservation ?'; + + @override + String get bookingDeletedBooking => 'Réservation supprimée'; + + @override + String get bookingDeletedRoom => 'Salle supprimée'; + + @override + String get bookingDeletedManager => 'Gestionnaire supprimé'; + + @override + String get bookingDeleteRoomConfirmation => + 'Êtes-vous sûr de vouloir supprimer cette salle ?\n\nLa salle ne doit avoir aucune réservation en cours ou à venir pour être supprimée'; + + @override + String get bookingDeleteManagerConfirmation => + 'Êtes-vous sûr de vouloir supprimer ce gestionnaire ?\n\nLe gestionnaire ne doit être associé à aucune salle pour pouvoir être supprimé'; + + @override + String get bookingDeletingBooking => 'Supprimer la réservation ?'; + + @override + String get bookingDeletingError => 'Erreur lors de la suppression'; + + @override + String get bookingDeletingRoom => 'Supprimer la salle ?'; + + @override + String get bookingEdit => 'Modifier'; + + @override + String get bookingEditBooking => 'Modifier une réservation'; + + @override + String get bookingEditionError => 'Erreur lors de la modification'; + + @override + String get bookingEditedBooking => 'Réservation modifiée'; + + @override + String get bookingEditedRoom => 'Salle modifiée'; + + @override + String get bookingEditedManager => 'Gestionnaire modifié'; + + @override + String get bookingEditManager => 'Modifier ou supprimer un gestionnaire'; + + @override + String get bookingEditRoom => 'Modifier ou supprimer une salle'; + + @override + String get bookingEndDate => 'Date de fin'; + + @override + String get bookingEndHour => 'Heure de fin'; + + @override + String get bookingEntity => 'Pour qui ?'; + + @override + String get bookingError => 'Erreur'; + + @override + String get bookingEventEvery => 'Tous les'; + + @override + String get bookingHistoryPage => 'Historique'; + + @override + String get bookingIncorrectOrMissingFields => + 'Champs incorrects ou manquants'; + + @override + String get bookingInterval => 'Intervalle'; + + @override + String get bookingInvalidIntervalError => 'Intervalle invalide'; + + @override + String get bookingInvalidDates => 'Dates invalides'; + + @override + String get bookingInvalidRoom => 'Salle invalide'; + + @override + String get bookingKeysRequested => 'Clés demandées'; + + @override + String get bookingManagement => 'Gestion'; + + @override + String get bookingManager => 'Gestionnaire'; + + @override + String get bookingManagerName => 'Nom du gestionnaire'; + + @override + String get bookingMultipleDay => 'Plusieurs jours'; + + @override + String get bookingMyBookings => 'Mes réservations'; + + @override + String get bookingNecessaryKey => 'Clé nécessaire'; + + @override + String get bookingNext => 'Suivant'; + + @override + String get bookingNo => 'Non'; + + @override + String get bookingNoCurrentBooking => 'Pas de réservation en cours'; + + @override + String get bookingNoDateError => 'Veuillez choisir une date'; + + @override + String get bookingNoAppointmentInReccurence => + 'Aucun créneau existe avec ces paramètres de récurrence'; + + @override + String get bookingNoDaySelected => 'Aucun jour sélectionné'; + + @override + String get bookingNoDescriptionError => 'Veuillez entrer une description'; + + @override + String get bookingNoKeys => 'Aucune clé'; + + @override + String get bookingNoNoteError => 'Veuillez entrer une note'; + + @override + String get bookingNoPhoneRegistered => 'Numéro non renseigné'; + + @override + String get bookingNoReasonError => 'Veuillez entrer un motif'; + + @override + String get bookingNoRoomFoundError => 'Aucune salle enregistrée'; + + @override + String get bookingNoRoomFound => 'Aucune salle trouvée'; + + @override + String get bookingNote => 'Note'; + + @override + String get bookingOther => 'Autre'; + + @override + String get bookingPending => 'En attente'; + + @override + String get bookingPrevious => 'Précédent'; + + @override + String get bookingReason => 'Motif'; + + @override + String get bookingRecurrence => 'Récurrence'; + + @override + String get bookingRecurrenceDays => 'Jours de récurrence'; + + @override + String get bookingRecurrenceEndDate => 'Date de fin de récurrence'; + + @override + String get bookingRecurrent => 'Récurrent'; + + @override + String get bookingRegisteredRooms => 'Salles enregistrées'; + + @override + String get bookingRoom => 'Salle'; + + @override + String get bookingRoomName => 'Nom de la salle'; + + @override + String get bookingStartDate => 'Date de début'; + + @override + String get bookingStartHour => 'Heure de début'; + + @override + String get bookingWeeks => 'Semaines'; + + @override + String get bookingYes => 'Oui'; + + @override + String get bookingWeekDayMon => 'Lundi'; + + @override + String get bookingWeekDayTue => 'Mardi'; + + @override + String get bookingWeekDayWed => 'Mercredi'; + + @override + String get bookingWeekDayThu => 'Jeudi'; + + @override + String get bookingWeekDayFri => 'Vendredi'; + + @override + String get bookingWeekDaySat => 'Samedi'; + + @override + String get bookingWeekDaySun => 'Dimanche'; + + @override + String get cinemaAdd => 'Ajouter'; + + @override + String get cinemaAddedSession => 'Séance ajoutée'; + + @override + String get cinemaAddingError => 'Erreur lors de l\'ajout'; + + @override + String get cinemaAddSession => 'Ajouter une séance'; + + @override + String get cinemaCinema => 'Cinéma'; + + @override + String get cinemaDeleteSession => 'Supprimer la séance ?'; + + @override + String get cinemaDeleting => 'Suppression'; + + @override + String get cinemaDuration => 'Durée'; + + @override + String get cinemaEdit => 'Modifier'; + + @override + String get cinemaEditedSession => 'Séance modifiée'; + + @override + String get cinemaEditingError => 'Erreur lors de la modification'; + + @override + String get cinemaEditSession => 'Modifier la séance'; + + @override + String get cinemaEmptyUrl => 'Veuillez entrer une URL'; + + @override + String get cinemaImportFromTMDB => 'Importer depuis TMDB'; + + @override + String get cinemaIncomingSession => 'A l\'affiche'; + + @override + String get cinemaIncorrectOrMissingFields => 'Champs incorrects ou manquants'; + + @override + String get cinemaInvalidUrl => 'URL invalide'; + + @override + String get cinemaGenre => 'Genre'; + + @override + String get cinemaName => 'Nom'; + + @override + String get cinemaNoDateError => 'Veuillez entrer une date'; + + @override + String get cinemaNoDuration => 'Veuillez entrer une durée'; + + @override + String get cinemaNoOverview => 'Aucun synopsis'; + + @override + String get cinemaNoPoster => 'Aucune affiche'; + + @override + String get cinemaNoSession => 'Aucune séance'; + + @override + String get cinemaOverview => 'Synopsis'; + + @override + String get cinemaPosterUrl => 'URL de l\'affiche'; + + @override + String get cinemaSessionDate => 'Jour de la séance'; + + @override + String get cinemaStartHour => 'Heure de début'; + + @override + String get cinemaTagline => 'Slogan'; + + @override + String get cinemaThe => 'Le'; + + @override + String get drawerAdmin => 'Administration'; + + @override + String get drawerAndroidAppLink => + 'https://play.google.com/store/apps/details?id=fr.myecl.titan'; + + @override + String get drawerCopied => 'Copié !'; + + @override + String get drawerDownloadAppOnMobileDevice => + 'Ce site est la version Web de l\'application MyECL. Nous vous invitons à télécharger l\'application. N\'utilisez ce site qu\'en cas de problème avec l\'application.\n'; + + @override + String get drawerIosAppLink => + 'https://apps.apple.com/fr/app/myecl/id6444443430'; + + @override + String get drawerLoginOut => 'Voulez-vous vous déconnecter ?'; + + @override + String get drawerLogOut => 'Déconnexion'; + + @override + String get drawerOr => ' ou '; + + @override + String get drawerSettings => 'Paramètres'; + + @override + String get eventAdd => 'Ajouter'; + + @override + String get eventAddEvent => 'Ajouter un événement'; + + @override + String get eventAddedEvent => 'Événement ajouté'; + + @override + String get eventAddingError => 'Erreur lors de l\'ajout'; + + @override + String get eventAllDay => 'Toute la journée'; + + @override + String get eventConfirm => 'Confirmer'; + + @override + String get eventConfirmEvent => 'Confirmer l\'événement ?'; + + @override + String get eventConfirmation => 'Confirmation'; + + @override + String get eventConfirmed => 'Confirmé'; + + @override + String get eventDates => 'Dates'; + + @override + String get eventDecline => 'Refuser'; + + @override + String get eventDeclineEvent => 'Refuser l\'événement ?'; + + @override + String get eventDeclined => 'Refusé'; + + @override + String get eventDelete => 'Supprimer'; + + @override + String get eventDeletedEvent => 'Événement supprimé'; + + @override + String get eventDeleting => 'Suppression'; + + @override + String get eventDeletingError => 'Erreur lors de la suppression'; + + @override + String get eventDeletingEvent => 'Supprimer l\'événement ?'; + + @override + String get eventDescription => 'Description'; + + @override + String get eventEdit => 'Modifier'; + + @override + String get eventEditEvent => 'Modifier un événement'; + + @override + String get eventEditedEvent => 'Événement modifié'; + + @override + String get eventEditingError => 'Erreur lors de la modification'; + + @override + String get eventEndDate => 'Date de fin'; + + @override + String get eventEndHour => 'Heure de fin'; + + @override + String get eventError => 'Erreur'; + + @override + String get eventEventList => 'Liste des événements'; + + @override + String get eventEventType => 'Type d\'événement'; + + @override + String get eventEvery => 'Tous les'; + + @override + String get eventHistory => 'Historique'; + + @override + String get eventIncorrectOrMissingFields => + 'Certains champs sont incorrects ou manquants'; + + @override + String get eventInterval => 'Intervalle'; + + @override + String get eventInvalidDates => + 'La date de fin doit être après la date de début'; + + @override + String get eventInvalidIntervalError => + 'Veuillez entrer un intervalle valide'; + + @override + String get eventLocation => 'Lieu'; + + @override + String get eventMyEvents => 'Mes événements'; + + @override + String get eventName => 'Nom'; + + @override + String get eventNext => 'Suivant'; + + @override + String get eventNo => 'Non'; + + @override + String get eventNoCurrentEvent => 'Aucun événement en cours'; + + @override + String get eventNoDateError => 'Veuillez entrer une date'; + + @override + String get eventNoDaySelected => 'Aucun jour sélectionné'; + + @override + String get eventNoDescriptionError => 'Veuillez entrer une description'; + + @override + String get eventNoEvent => 'Aucun événement'; + + @override + String get eventNoNameError => 'Veuillez entrer un nom'; + + @override + String get eventNoOrganizerError => 'Veuillez entrer un organisateur'; + + @override + String get eventNoPlaceError => 'Veuillez entrer un lieu'; + + @override + String get eventNoPhoneRegistered => 'Numéro non renseigné'; + + @override + String get eventNoRuleError => 'Veuillez entrer une règle de récurrence'; + + @override + String get eventOrganizer => 'Organisateur'; + + @override + String get eventOther => 'Autre'; + + @override + String get eventPending => 'En attente'; + + @override + String get eventPrevious => 'Précédent'; + + @override + String get eventRecurrence => 'Récurrence'; + + @override + String get eventRecurrenceDays => 'Jours de récurrence'; + + @override + String get eventRecurrenceEndDate => 'Date de fin de la récurrence'; + + @override + String get eventRecurrenceRule => 'Règle de récurrence'; + + @override + String get eventRoom => 'Salle'; + + @override + String get eventStartDate => 'Date de début'; + + @override + String get eventStartHour => 'Heure de début'; + + @override + String get eventTitle => 'Événements'; + + @override + String get eventYes => 'Oui'; + + @override + String get eventEventEvery => 'Toutes les'; + + @override + String get eventWeeks => 'semaines'; + + @override + String get eventDayMon => 'Lundi'; + + @override + String get eventDayTue => 'Mardi'; + + @override + String get eventDayWed => 'Mercredi'; + + @override + String get eventDayThu => 'Jeudi'; + + @override + String get eventDayFri => 'Vendredi'; + + @override + String get eventDaySat => 'Samedi'; + + @override + String get eventDaySun => 'Dimanche'; + + @override + String get homeCalendar => 'Calendrier'; + + @override + String get homeEventOf => 'Évènements du'; + + @override + String get homeIncomingEvents => 'Évènements à venir'; + + @override + String get homeLastInfos => 'Dernières annonces'; + + @override + String get homeNoEvents => 'Aucun évènement'; + + @override + String get homeTranslateDayShortMon => 'Lun'; + + @override + String get homeTranslateDayShortTue => 'Mar'; + + @override + String get homeTranslateDayShortWed => 'Mer'; + + @override + String get homeTranslateDayShortThu => 'Jeu'; + + @override + String get homeTranslateDayShortFri => 'Ven'; + + @override + String get homeTranslateDayShortSat => 'Sam'; + + @override + String get homeTranslateDayShortSun => 'Dim'; + + @override + String get loanAdd => 'Ajouter'; + + @override + String get loanAddLoan => 'Ajouter un prêt'; + + @override + String get loanAddObject => 'Ajouter un objet'; + + @override + String get loanAddedLoan => 'Prêt ajouté'; + + @override + String get loanAddedObject => 'Objet ajouté'; + + @override + String get loanAddedRoom => 'Salle ajoutée'; + + @override + String get loanAddingError => 'Erreur lors de l\'ajout'; + + @override + String get loanAdmin => 'Administrateur'; + + @override + String get loanAvailable => 'Disponible'; + + @override + String get loanAvailableMultiple => 'Disponibles'; + + @override + String get loanBorrowed => 'Emprunté'; + + @override + String get loanBorrowedMultiple => 'Empruntés'; + + @override + String get loanAnd => 'et'; + + @override + String get loanAssociation => 'Association'; + + @override + String get loanAvailableItems => 'Objets disponibles'; + + @override + String get loanBeginDate => 'Date du début du prêt'; + + @override + String get loanBorrower => 'Emprunteur'; + + @override + String get loanCaution => 'Caution'; + + @override + String get loanCancel => 'Annuler'; + + @override + String get loanConfirm => 'Confirmer'; + + @override + String get loanConfirmation => 'Confirmation'; + + @override + String get loanDates => 'Dates'; + + @override + String get loanDays => 'Jours'; + + @override + String get loanDelay => 'Délai de la prolongation'; + + @override + String get loanDelete => 'Supprimer'; + + @override + String get loanDeletingLoan => 'Supprimer le prêt ?'; + + @override + String get loanDeletedItem => 'Objet supprimé'; + + @override + String get loanDeletedLoan => 'Prêt supprimé'; + + @override + String get loanDeleting => 'Suppression'; + + @override + String get loanDeletingError => 'Erreur lors de la suppression'; + + @override + String get loanDeletingItem => 'Supprimer l\'objet ?'; + + @override + String get loanDuration => 'Durée'; + + @override + String get loanEdit => 'Modifier'; + + @override + String get loanEditItem => 'Modifier l\'objet'; + + @override + String get loanEditLoan => 'Modifier le prêt'; + + @override + String get loanEditedRoom => 'Salle modifiée'; + + @override + String get loanEndDate => 'Date de fin du prêt'; + + @override + String get loanEnded => 'Terminé'; + + @override + String get loanEnterDate => 'Veuillez entrer une date'; + + @override + String get loanExtendedLoan => 'Prêt prolongé'; + + @override + String get loanExtendingError => 'Erreur lors de la prolongation'; + + @override + String get loanHistory => 'Historique'; + + @override + String get loanIncorrectOrMissingFields => + 'Des champs sont manquants ou incorrects'; + + @override + String get loanInvalidNumber => 'Veuillez entrer un nombre'; + + @override + String get loanInvalidDates => 'Les dates ne sont pas valides'; + + @override + String get loanItem => 'Objet'; + + @override + String get loanItems => 'Objets'; + + @override + String get loanItemHandling => 'Gestion des objets'; + + @override + String get loanItemSelected => 'objet sélectionné'; + + @override + String get loanItemsSelected => 'objets sélectionnés'; + + @override + String get loanLendingDuration => 'Durée possible du prêt'; + + @override + String get loanLoan => 'Prêt'; + + @override + String get loanLoanHandling => 'Gestion des prêts'; + + @override + String get loanLooking => 'Rechercher'; + + @override + String get loanName => 'Nom'; + + @override + String get loanNext => 'Suivant'; + + @override + String get loanNo => 'Non'; + + @override + String get loanNoAssociationsFounded => 'Aucune association trouvée'; + + @override + String get loanNoAvailableItems => 'Aucun objet disponible'; + + @override + String get loanNoBorrower => 'Aucun emprunteur'; + + @override + String get loanNoItems => 'Aucun objet'; + + @override + String get loanNoItemSelected => 'Aucun objet sélectionné'; + + @override + String get loanNoLoan => 'Aucun prêt'; + + @override + String get loanNoReturnedDate => 'Pas de date de retour'; + + @override + String get loanQuantity => 'Quantité'; + + @override + String get loanNone => 'Aucun'; + + @override + String get loanNote => 'Note'; + + @override + String get loanNoValue => 'Veuillez entrer une valeur'; + + @override + String get loanOnGoing => 'En cours'; + + @override + String get loanOnGoingLoan => 'Prêt en cours'; + + @override + String get loanOthers => 'autres'; + + @override + String get loanPaidCaution => 'Caution payée'; + + @override + String get loanPositiveNumber => 'Veuillez entrer un nombre positif'; + + @override + String get loanPrevious => 'Précédent'; + + @override + String get loanReturned => 'Rendu'; + + @override + String get loanReturnedLoan => 'Prêt rendu'; + + @override + String get loanReturningError => 'Erreur lors du retour'; + + @override + String get loanReturningLoan => 'Retour'; + + @override + String get loanReturnLoan => 'Rendre le prêt ?'; + + @override + String get loanReturnLoanDescription => 'Voulez-vous rendre ce prêt ?'; + + @override + String get loanToReturn => 'A rendre'; + + @override + String get loanUnavailable => 'Indisponible'; + + @override + String get loanUpdate => 'Modifier'; + + @override + String get loanUpdatedItem => 'Objet modifié'; + + @override + String get loanUpdatedLoan => 'Prêt modifié'; + + @override + String get loanUpdatingError => 'Erreur lors de la modification'; + + @override + String get loanYes => 'Oui'; + + @override + String get loginAccountActivated => 'Compte activé'; + + @override + String get loginAccountNotActivated => 'Compte non activé'; + + @override + String get loginActivationCode => 'Code d\'activation'; + + @override + String get loginBirthday => 'Date de naissance'; + + @override + String get loginCanBeEmpty => 'Ce champ peut être vide'; + + @override + String get loginConfirmPassword => 'Confirmer le mot de passe'; + + @override + String get loginCreate => 'Créer'; + + @override + String get loginCreateAccount => 'Créer un compte'; + + @override + String get loginCreateAccountTitle => 'Créer un\ncompte'; + + @override + String get loginEmail => 'Email'; + + @override + String get loginEmailEmpty => 'Veuillez entrer une adresse mail'; + + @override + String get loginEmailInvalid => + 'Veuillez entrer une adresse mail de centrale.\nSi vous n\'en possédez pas, veuillez contacter Éclair'; + + @override + String get loginEmptyFieldError => 'Ce champ ne peut pas être vide'; + + @override + String get loginEndActivation => 'Finaliser l\'activation'; + + @override + String get loginEndResetPassword => 'Finaliser la \nréinitialisation'; + + @override + String get loginErrorResetPassword => 'Erreur lors de la réinitialisation'; + + @override + String get loginExpectingDate => 'Une date est attendue'; + + @override + String get loginFillAllFields => 'Veuillez remplir tous les champs'; + + @override + String get loginFirstname => 'Prénom'; + + @override + String get loginFloor => 'Étage'; + + @override + String get loginForgetPassword => 'Mot de passe\noublié'; + + @override + String get loginForgotPassword => 'Mot de passe oublié ?'; + + @override + String get loginInvalidToken => 'Code d\'activation invalide'; + + @override + String get loginLoginFailed => 'Échec de la connexion'; + + @override + String get loginMailSendingError => 'Erreur lors de la création du compte'; + + @override + String get loginMustBeIntError => 'Ce champ doit être un entier'; + + @override + String get loginName => 'Nom'; + + @override + String get loginNewPassword => 'Nouveau mot de passe'; + + @override + String get loginPassword => 'Mot de passe'; + + @override + String get loginPasswordLengthError => + 'Le mot de passe doit faire au moins 6 caractères'; + + @override + String get loginPasswordUppercaseError => + 'Le mot de passe doit contenir au moins une majuscule'; + + @override + String get loginPasswordLowercaseError => + 'Le mot de passe doit contenir au moins une minucule'; + + @override + String get loginPasswordNumberError => + 'Le mot de passe doit contenir au moins un chiffre'; + + @override + String get loginPasswordSpecialCaracterError => + 'Le mot de passe doit contenir au moins un caractère spécial'; + + @override + String get loginPasswordMustMatch => 'Les mots de passe doivent correspondre'; + + @override + String get loginPasswordStrengthVeryWeak => 'Très faible'; + + @override + String get loginPasswordStrengthWeak => 'Faible'; + + @override + String get loginPasswordStrengthMedium => 'Moyen'; + + @override + String get loginPasswordStrengthStrong => 'Fort'; + + @override + String get loginPasswordStrengthVeryStrong => 'Très fort'; + + @override + String get loginPhone => 'Téléphone'; + + @override + String get loginPromo => 'Promo entrante (ex : 2023)'; + + @override + String get loginSendedMail => 'Mail de confirmation envoyé'; + + @override + String get loginSendedResetMail => 'Mail de réinitialisation envoyé'; + + @override + String get loginSignIn => 'Se connecter'; + + @override + String get loginRegister => 'S\'inscrire'; + + @override + String get loginRecievedMail => 'J\'ai reçu le mail'; + + @override + String get loginRecover => 'Réinitialiser'; + + @override + String get loginResetedPassword => 'Mot de passe réinitialisé'; + + @override + String get loginResetPasswordTitle => 'Réinitialiser\nle mot de \npasse'; + + @override + String get loginNickname => 'Surnom'; + + @override + String get loginWelcomeBack => 'Bienvenue'; + + @override + String get loginAppName => 'MyECL'; + + @override + String get othersCheckInternetConnection => + 'Veuillez vérifier votre connexion internet'; + + @override + String get othersRetry => 'Réessayer'; + + @override + String get othersTooOldVersion => + 'Votre version de l\'application est trop ancienne.\n\nVeuillez mettre à jour l\'application.'; + + @override + String get othersUnableToConnectToServer => + 'Impossible de se connecter au serveur'; + + @override + String get othersVersion => 'Version'; + + @override + String get othersNoModule => + 'Aucun module disponible, veuillez réessayer ultérieurement 😢😢'; + + @override + String get othersAdmin => 'Admin'; + + @override + String get othersError => 'Une erreur est survenue'; + + @override + String get othersNoValue => 'Veuillez entrer une valeur'; + + @override + String get othersInvalidNumber => 'Veuillez entrer un nombre'; + + @override + String get othersNoDateError => 'Veuillez entrer une date'; + + @override + String get othersImageSizeTooBig => + 'La taille de l\'image ne doit pas dépasser 4 Mio'; + + @override + String get othersImageError => 'Erreur lors de l\'ajout de l\'image'; + + @override + String get phAddNewJournal => 'Ajouter un nouveau journal'; + + @override + String get phNameField => 'Nom : '; + + @override + String get phDateField => 'Date : '; + + @override + String get phDelete => 'Voulez-vous vraiment supprimer ce journal ?'; + + @override + String get phIrreversibleAction => 'Cette action est irréversible'; + + @override + String get phToHeavyFile => 'Fichier trop volumineux'; + + @override + String get phAddPdfFile => 'Ajouter un fichier PDF'; + + @override + String get phEditPdfFile => 'Modifier le fichier PDF'; + + @override + String get phPhName => 'Nom du PH'; + + @override + String get phDate => 'Date'; + + @override + String get phAdded => 'Ajouté'; + + @override + String get phEdited => 'Modifié'; + + @override + String get phAddingFileError => 'Erreur d\'ajout'; + + @override + String get phMissingInformatonsOrPdf => + 'Informations manquantes ou fichier PDF manquant'; + + @override + String get phAdd => 'Ajouter'; + + @override + String get phEdit => 'Modifier'; + + @override + String get phSeePreviousJournal => 'Voir les anciens journaux'; + + @override + String get phNoJournalInDatabase => 'Pas encore de PH dans la base de donnée'; + + @override + String get phSuccesDowloading => 'Téléchargé avec succès'; + + @override + String get phonebookActiveMandate => 'Mandat actif :'; + + @override + String get phonebookAdd => 'Ajouter'; + + @override + String get phonebookAddAssociation => 'Ajouter une association'; + + @override + String get phonebookAddedAssociation => 'Association ajoutée'; + + @override + String get phonebookAddedMember => 'Membre ajouté'; + + @override + String get phonebookAddingError => 'Erreur lors de l\'ajout'; + + @override + String get phonebookAddMember => 'Ajouter un membre'; + + @override + String get phonebookAddRole => 'Ajouter un rôle'; + + @override + String get phonebookAdmin => 'Admin'; + + @override + String get phonebookAdminPage => 'Page Administrateur'; + + @override + String get phonebookAll => 'Toutes'; + + @override + String get phonebookApparentName => 'Nom public du rôle :'; + + @override + String get phonebookAssociation => 'Association :'; + + @override + String get phonebookAssociationDetail => 'Détail de l\'association :'; + + @override + String get phonebookAssociationKind => 'Type d\'association :'; + + @override + String get phonebookAssociationPure => 'Association'; + + @override + String get phonebookAssociationPureSearch => ' Association'; + + @override + String get phonebookAssociations => 'Associations :'; + + @override + String get phonebookCancel => 'Annuler'; + + @override + String get phonebookChangeMandate => 'Passer au mandat '; + + @override + String get phonebookChangeMandateConfirm => + 'Êtes-vous sûr de vouloir changer tout le mandat ?\nCette action est irréversible !'; + + @override + String get phonebookCopied => 'Copié dans le presse-papier'; + + @override + String get phonebookDeactivateAssociation => + 'Êtes-vous sûr de vouloir désactiver cette association ?\nCette action est irréversible !'; + + @override + String get phonebookDeactivatedAssociation => 'Association désactivée'; + + @override + String get phonebookDeactivatedAssociationWarning => + 'Attention, cette association est désactivée, vous ne pouvez pas la modifier'; + + @override + String get phonebookDeactivating => 'Désactiver l\'association ?'; + + @override + String get phonebookDeactivatingError => 'Erreur lors de la désactivation'; + + @override + String get phonebookDetail => 'Détail :'; + + @override + String get phonebookDeleteAssociation => + 'Supprimer l\'association ?\nCela va effacer tout l\'historique de l\'association'; + + @override + String get phonebookDeletedAssociation => 'Association supprimée'; + + @override + String get phonebookDeletedMember => 'Membre supprimé'; + + @override + String get phonebookDeleting => 'Suppression'; + + @override + String get phonebookDeletingError => 'Erreur lors de la suppression'; + + @override + String get phonebookDescription => 'Description'; + + @override + String get phonebookEdit => 'Modifier'; + + @override + String get phonebookEditMembership => 'Modifier le rôle'; + + @override + String get phonebookEmail => 'Email :'; + + @override + String get phonebookEmailCopied => 'Email copié dans le presse-papier'; + + @override + String get phonebookEmptyApparentName => 'Veuillez entrer un nom de role'; + + @override + String get phonebookEmptyFieldError => 'Un champ n\'est pas rempli'; + + @override + String get phonebookEmptyKindError => + 'Veuillez choisir un type d\'association'; + + @override + String get phonebookEmptyMember => 'Aucun membre sélectionné'; + + @override + String get phonebookErrorAssociationLoading => + 'Erreur lors du chargement de l\'association'; + + @override + String get phonebookErrorAssociationNameEmpty => + 'Veuillez entrer un nom d\'association'; + + @override + String get phonebookErrorAssociationPicture => + 'Erreur lors de la modification de la photo d\'association'; + + @override + String get phonebookErrorKindsLoading => + 'Erreur lors du chargement des types d\'association'; + + @override + String get phonebookErrorLoadAssociationList => + 'Erreur lors du chargement de la liste des associations'; + + @override + String get phonebookErrorLoadAssociationMember => + 'Erreur lors du chargement des membres de l\'association'; + + @override + String get phonebookErrorLoadAssociationPicture => + 'Erreur lors du chargement de la photo d\'association'; + + @override + String get phonebookErrorLoadProfilePicture => 'Erreur'; + + @override + String get phonebookErrorRoleTagsLoading => + 'Erreur lors du chargement des tags de rôle'; + + @override + String get phonebookExistingMembership => + 'Ce membre est déjà dans le mandat actuel'; + + @override + String get phonebookFirstname => 'Prénom :'; + + @override + String get phonebookGroups => 'Groupes associés :'; + + @override + String get phonebookMandateChangingError => + 'Erreur lors du changement de mandat'; + + @override + String get phonebookMember => 'Membre'; + + @override + String get phonebookMemberReordered => 'Membre réordonné'; + + @override + String get phonebookMembers => 'Membres'; + + @override + String get phonebookMembershipAssociationError => + 'Veuillez choisir une association'; + + @override + String get phonebookMembershipRole => 'Rôle :'; + + @override + String get phonebookMembershipRoleError => 'Veuillez choisir un rôle'; + + @override + String get phonebookName => 'Nom :'; + + @override + String get phonebookNameCopied => 'Nom et prénom copié dans le presse-papier'; + + @override + String get phonebookNamePure => 'Nom'; + + @override + String get phonebookNewMandate => 'Nouveau mandat'; + + @override + String get phonebookNewMandateConfirmed => 'Mandat changé'; + + @override + String get phonebookNickname => 'Surnom :'; + + @override + String get phonebookNicknameCopied => 'Surnom copié dans le presse-papier'; + + @override + String get phonebookNoAssociationFound => 'Aucune association trouvée'; + + @override + String get phonebookNoMember => 'Aucun membre'; + + @override + String get phonebookNoMemberRole => 'Aucun role trouvé'; + + @override + String get phonebookPhone => 'Téléphone :'; + + @override + String get phonebookPhonebook => 'Annuaire'; + + @override + String get phonebookPhonebookSearch => 'Rechercher'; + + @override + String get phonebookPhonebookSearchAssociation => 'Association'; + + @override + String get phonebookPhonebookSearchField => 'Rechercher :'; + + @override + String get phonebookPhonebookSearchName => 'Nom/Prénom/Surnom'; + + @override + String get phonebookPhonebookSearchRole => 'Poste'; + + @override + String get phonebookPresidentRoleTag => 'Prez\''; + + @override + String get phonebookPromoNotGiven => 'Promo non renseignée'; + + @override + String get phonebookPromotion => 'Promotion :'; + + @override + String get phonebookReorderingError => 'Erreur lors du réordonnement'; + + @override + String get phonebookResearch => 'Rechercher'; + + @override + String get phonebookRolePure => 'Rôle'; + + @override + String get phonebookTooHeavyAssociationPicture => + 'L\'image est trop lourde (max 4Mo)'; + + @override + String get phonebookUpdateGroups => 'Mettre à jour les groupes'; + + @override + String get phonebookUpdatedAssociation => 'Association modifiée'; + + @override + String get phonebookUpdatedAssociationPicture => + 'La photo d\'association a été changée'; + + @override + String get phonebookUpdatedGroups => 'Groupes mis à jour'; + + @override + String get phonebookUpdatedMember => 'Membre modifié'; + + @override + String get phonebookUpdatingError => 'Erreur lors de la modification'; + + @override + String get phonebookValidation => 'Valider'; + + @override + String get purchasesPurchases => 'Achats'; + + @override + String get purchasesResearch => 'Rechercher'; + + @override + String get purchasesNoPurchasesFound => 'Aucun achat trouvé'; + + @override + String get purchasesNoTickets => 'Aucun ticket'; + + @override + String get purchasesTicketsError => 'Erreur lors du chargement des tickets'; + + @override + String get purchasesPurchasesError => 'Erreur lors du chargement des achats'; + + @override + String get purchasesNoPurchases => 'Aucun achat'; + + @override + String get purchasesTimes => 'fois'; + + @override + String get purchasesAlreadyUsed => 'Déjà utilisé'; + + @override + String get purchasesNotPaid => 'Non validé'; + + @override + String get purchasesPleaseSelectProduct => 'Veuillez sélectionner un produit'; + + @override + String get purchasesProducts => 'Produits'; + + @override + String get purchasesCancel => 'Annuler'; + + @override + String get purchasesValidate => 'Valider'; + + @override + String get purchasesLeftScan => 'Scans restants'; + + @override + String get purchasesTag => 'Tag'; + + @override + String get purchasesHistory => 'Historique'; + + @override + String get purchasesPleaseSelectSeller => 'Veuillez sélectionner un vendeur'; + + @override + String get purchasesNoTagGiven => 'Attention, aucun tag n\'a été entré'; + + @override + String get purchasesTickets => 'Tickets'; + + @override + String get purchasesNoScannableProducts => 'Aucun produit scannable'; + + @override + String get purchasesLoading => 'En attente de scan'; + + @override + String get purchasesScan => 'Scanner'; + + @override + String get raffleRaffle => 'Tombola'; + + @override + String get rafflePrize => 'Lot'; + + @override + String get rafflePrizes => 'Lots'; + + @override + String get raffleActualRaffles => 'Tombola en cours'; + + @override + String get rafflePastRaffles => 'Tombola passés'; + + @override + String get raffleYourTickets => 'Tous vos tickets'; + + @override + String get raffleCreateMenu => 'Menu de Création'; + + @override + String get raffleNextRaffles => 'Prochaines tombolas'; + + @override + String get raffleNoTicket => 'Vous n\'avez pas de ticket'; + + @override + String get raffleSeeRaffleDetail => 'Voir lots/tickets'; + + @override + String get raffleActualPrize => 'Lots actuels'; + + @override + String get raffleMajorPrize => 'Lot Majeurs'; + + @override + String get raffleTakeTickets => 'Prendre vos tickets'; + + @override + String get raffleNoTicketBuyable => + 'Vous ne pouvez pas achetez de billets pour l\'instant'; + + @override + String get raffleNoCurrentPrize => 'Il n\'y a aucun lots actuellement'; + + @override + String get raffleModifTombola => + 'Vous pouvez modifiez vos tombolas ou en créer de nouvelles, toute décision doit ensuite être prise par les admins'; + + @override + String get raffleCreateYourRaffle => 'Votre menu de création de tombolas'; + + @override + String get rafflePossiblePrice => 'Prix possible'; + + @override + String get raffleInformation => 'Information et Statistiques'; + + @override + String get raffleAccounts => 'Comptes'; + + @override + String get raffleAdd => 'Ajouter'; + + @override + String get raffleUpdatedAmount => 'Montant mis à jour'; + + @override + String get raffleUpdatingError => 'Erreur lors de la mise à jour'; + + @override + String get raffleDeletedPrize => 'Lot supprimé'; + + @override + String get raffleDeletingError => 'Erreur lors de la suppression'; + + @override + String get raffleQuantity => 'Quantité'; + + @override + String get raffleClose => 'Fermer'; + + @override + String get raffleOpen => 'Ouvrir'; + + @override + String get raffleAddTypeTicketSimple => 'Ajouter'; + + @override + String get raffleAddingError => 'Erreur lors de l\'ajout'; + + @override + String get raffleEditTypeTicketSimple => 'Modifier'; + + @override + String get raffleFillField => 'Le champ ne peut pas être vide'; + + @override + String get raffleWaiting => 'Chargement'; + + @override + String get raffleEditingError => 'Erreur lors de la modification'; + + @override + String get raffleAddedTicket => 'Ticket ajouté'; + + @override + String get raffleEditedTicket => 'Ticket modifié'; + + @override + String get raffleAlreadyExistTicket => 'Le ticket existe déjà'; + + @override + String get raffleNumberExpected => 'Un entier est attendu'; + + @override + String get raffleDeletedTicket => 'Ticket supprimé'; + + @override + String get raffleAddPrize => 'Ajouter'; + + @override + String get raffleEditPrize => 'Modifier'; + + @override + String get raffleOpenRaffle => 'Ouvrir la tombola'; + + @override + String get raffleCloseRaffle => 'Fermer la tombola'; + + @override + String get raffleOpenRaffleDescription => + 'Vous allez ouvrir la tombola, les utilisateurs pourront acheter des tickets. Vous ne pourrez plus modifier la tombola. Êtes-vous sûr de vouloir continuer ?'; + + @override + String get raffleCloseRaffleDescription => + 'Vous allez fermer la tombola, les utilisateurs ne pourront plus acheter de tickets. Êtes-vous sûr de vouloir continuer ?'; + + @override + String get raffleNoCurrentRaffle => 'Il n\'y a aucune tombola en cours'; + + @override + String get raffleBoughtTicket => 'Ticket acheté'; + + @override + String get raffleDrawingError => 'Erreur lors du tirage'; + + @override + String get raffleInvalidPrice => 'Le prix doit être supérieur à 0'; + + @override + String get raffleMustBePositive => 'Le nombre doit être strictement positif'; + + @override + String get raffleDraw => 'Tirer'; + + @override + String get raffleDrawn => 'Tiré'; + + @override + String get raffleError => 'Erreur'; + + @override + String get raffleGathered => 'Récolté'; + + @override + String get raffleTickets => 'Tickets'; + + @override + String get raffleTicket => 'ticket'; + + @override + String get raffleWinner => 'Gagnant'; + + @override + String get raffleNoPrize => 'Aucun lot'; + + @override + String get raffleDeletePrize => 'Supprimer le lot'; + + @override + String get raffleDeletePrizeDescription => + 'Vous allez supprimer le lot, êtes-vous sûr de vouloir continuer ?'; + + @override + String get raffleDrawing => 'Tirage'; + + @override + String get raffleDrawingDescription => 'Tirer le gagnant du lot ?'; + + @override + String get raffleDeleteTicket => 'Supprimer le ticket'; + + @override + String get raffleDeleteTicketDescription => + 'Vous allez supprimer le ticket, êtes-vous sûr de vouloir continuer ?'; + + @override + String get raffleWinningTickets => 'Tickets gagnants'; + + @override + String get raffleNoWinningTicketYet => + 'Les tickets gagnants seront affichés ici'; + + @override + String get raffleName => 'Nom'; + + @override + String get raffleDescription => 'Description'; + + @override + String get raffleBuyThisTicket => 'Acheter ce ticket'; + + @override + String get raffleLockedRaffle => 'Tombola verrouillée'; + + @override + String get raffleUnavailableRaffle => 'Tombola indisponible'; + + @override + String get raffleNotEnoughMoney => 'Vous n\'avez pas assez d\'argent'; + + @override + String get raffleWinnable => 'gagnable'; + + @override + String get raffleNoDescription => 'Aucune description'; + + @override + String get raffleAmount => 'Solde'; + + @override + String get raffleLoading => 'Chargement'; + + @override + String get raffleTicketNumber => 'Nombre de ticket'; + + @override + String get rafflePrice => 'Prix'; + + @override + String get raffleEditRaffle => 'Modifier la tombola'; + + @override + String get raffleEdit => 'Modifier'; + + @override + String get raffleAddPackTicket => 'Ajouter un pack de ticket'; + + @override + String get recommendationRecommendation => 'Bons plans'; + + @override + String get recommendationTitle => 'Titre'; + + @override + String get recommendationLogo => 'Logo'; + + @override + String get recommendationCode => 'Code'; + + @override + String get recommendationSummary => 'Court résumé'; + + @override + String get recommendationDescription => 'Description'; + + @override + String get recommendationAdd => 'Ajouter'; + + @override + String get recommendationEdit => 'Modifier'; + + @override + String get recommendationDelete => 'Supprimer'; + + @override + String get recommendationAddImage => 'Veuillez ajouter une image'; + + @override + String get recommendationAddedRecommendation => 'Bon plan ajouté'; + + @override + String get recommendationEditedRecommendation => 'Bon plan modifié'; + + @override + String get recommendationDeleteRecommendationConfirmation => + 'Êtes-vous sûr de vouloir supprimer ce bon plan ?'; + + @override + String get recommendationDeleteRecommendation => 'Suppresion'; + + @override + String get recommendationDeletingRecommendationError => + 'Erreur lors de la suppression'; + + @override + String get recommendationDeletedRecommendation => 'Bon plan supprimé'; + + @override + String get recommendationIncorrectOrMissingFields => + 'Champs incorrects ou manquants'; + + @override + String get recommendationEditingError => 'Échec de la modification'; + + @override + String get recommendationAddingError => 'Échec de l\'ajout'; + + @override + String get recommendationCopiedCode => 'Code de réduction copié'; + + @override + String get seedLibraryAdd => 'Ajouter'; + + @override + String get seedLibraryAddedPlant => 'Plante ajoutée'; + + @override + String get seedLibraryAddedSpecies => 'Espèce ajoutée'; + + @override + String get seedLibraryAddingError => 'Erreur lors de l\'ajout'; + + @override + String get seedLibraryAddPlant => 'Déposer une plante'; + + @override + String get seedLibraryAddSpecies => 'Ajouter une espèce'; + + @override + String get seedLibraryAll => 'Toutes'; + + @override + String get seedLibraryAncestor => 'Ancêtre'; + + @override + String get seedLibraryAround => 'environ'; + + @override + String get seedLibraryAutumn => 'Automne'; + + @override + String get seedLibraryBorrowedPlant => 'Plante empruntée'; + + @override + String get seedLibraryBorrowingDate => 'Date d\'emprunt :'; + + @override + String get seedLibraryBorrowPlant => 'Emprunter la plante'; + + @override + String get seedLibraryCard => 'Carte'; + + @override + String get seedLibraryChoosingAncestor => 'Veuillez choisir un ancêtre'; + + @override + String get seedLibraryChoosingSpecies => 'Veuillez choisir une espèce'; + + @override + String get seedLibraryChoosingSpeciesOrAncestor => + 'Veuillez choisir une espèce ou un ancêtre'; + + @override + String get seedLibraryContact => 'Contact :'; + + @override + String get seedLibraryDays => 'jours'; + + @override + String get seedLibraryDeadMsg => 'Voulez-vous déclarer la plante morte ?'; + + @override + String get seedLibraryDeadPlant => 'Plante morte'; + + @override + String get seedLibraryDeathDate => 'Date de mort'; + + @override + String get seedLibraryDeletedSpecies => 'Espèce supprimée'; + + @override + String get seedLibraryDeleteSpecies => 'Supprimer l\'espèce ?'; + + @override + String get seedLibraryDeleting => 'Suppression'; + + @override + String get seedLibraryDeletingError => 'Erreur lors de la suppression'; + + @override + String get seedLibraryDepositNotAvailable => + 'Le dépôt de plantes n\'est pas possible sans emprunter une plante au préalable'; + + @override + String get seedLibraryDescription => 'Description'; + + @override + String get seedLibraryDifficulty => 'Difficulté :'; + + @override + String get seedLibraryEdit => 'Modifier'; + + @override + String get seedLibraryEditedPlant => 'Plante modifiée'; + + @override + String get seedLibraryEditInformation => 'Modifier les informations'; + + @override + String get seedLibraryEditingError => 'Erreur lors de la modification'; + + @override + String get seedLibraryEditSpecies => 'Modifier l\'espèce'; + + @override + String get seedLibraryEmptyDifficultyError => + 'Veuillez choisir une difficulté'; + + @override + String get seedLibraryEmptyFieldError => 'Veuillez remplir tous les champs'; + + @override + String get seedLibraryEmptyTypeError => 'Veuillez choisir un type de plante'; + + @override + String get seedLibraryEndMonth => 'Mois de fin :'; + + @override + String get seedLibraryFacebookUrl => 'Lien Facebook'; + + @override + String get seedLibraryFilters => 'Filtres'; + + @override + String get seedLibraryForum => + 'Oskour maman j\'ai tué ma plante - Forum d\'aide'; + + @override + String get seedLibraryForumUrl => 'Lien Forum'; + + @override + String get seedLibraryHelpSheets => 'Fiches sur les plantes'; + + @override + String get seedLibraryInformation => 'Informations :'; + + @override + String get seedLibraryMaturationTime => 'Temps de maturation'; + + @override + String get seedLibraryMonthJan => 'Janvier'; + + @override + String get seedLibraryMonthFeb => 'Février'; + + @override + String get seedLibraryMonthMar => 'Mars'; + + @override + String get seedLibraryMonthApr => 'Avril'; + + @override + String get seedLibraryMonthMay => 'Mai'; + + @override + String get seedLibraryMonthJun => 'Juin'; + + @override + String get seedLibraryMonthJul => 'Juillet'; + + @override + String get seedLibraryMonthAug => 'Août'; + + @override + String get seedLibraryMonthSep => 'Septembre'; + + @override + String get seedLibraryMonthOct => 'Octobre'; + + @override + String get seedLibraryMonthNov => 'Novembre'; + + @override + String get seedLibraryMonthDec => 'Décembre'; + + @override + String get seedLibraryMyPlants => 'Mes plantes'; + + @override + String get seedLibraryName => 'Nom'; + + @override + String get seedLibraryNbSeedsRecommended => 'Nombre de graines recommandées'; + + @override + String get seedLibraryNbSeedsRecommendedError => + 'Veuillez entrer un nombre de graines recommandé supérieur à 0'; + + @override + String get seedLibraryNoDateError => 'Veuillez entrer une date'; + + @override + String get seedLibraryNoFilteredPlants => + 'Aucune plante ne correspond à votre recherche. Essayez d\'autres filtres.'; + + @override + String get seedLibraryNoMorePlant => 'Aucune plante n\'est disponible'; + + @override + String get seedLibraryNoPersonalPlants => + 'Vous n\'avez pas encore de plantes dans votre grainothèque. Vous pouvez en ajouter en allant dans les stocks.'; + + @override + String get seedLibraryNoSpecies => 'Aucune espèce trouvée'; + + @override + String get seedLibraryNoStockPlants => + 'Aucune plante disponible dans le stock'; + + @override + String get seedLibraryNotes => 'Notes'; + + @override + String get seedLibraryOk => 'OK'; + + @override + String get seedLibraryPlantationPeriod => 'Période de plantation :'; + + @override + String get seedLibraryPlantationType => 'Type de plantation :'; + + @override + String get seedLibraryPlantDetail => 'Détail de la plante'; + + @override + String get seedLibraryPlantingDate => 'Date de plantation'; + + @override + String get seedLibraryPlantingNow => 'Je la plante maintenant'; + + @override + String get seedLibraryPrefix => 'Préfixe'; + + @override + String get seedLibraryPrefixError => 'Prefixe déjà utilisé'; + + @override + String get seedLibraryPrefixLengthError => + 'Le préfixe doit faire 3 caractères'; + + @override + String get seedLibraryPropagationMethod => 'Méthode de propagation :'; + + @override + String get seedLibraryReference => 'Référence :'; + + @override + String get seedLibraryRemovedPlant => 'Plante supprimée'; + + @override + String get seedLibraryRemovingError => 'Erreur lors de la suppression'; + + @override + String get seedLibraryResearch => 'Recherche'; + + @override + String get seedLibrarySaveChanges => 'Sauvegarder les modifications'; + + @override + String get seedLibrarySeason => 'Saison :'; + + @override + String get seedLibrarySeed => 'Graine'; + + @override + String get seedLibrarySeeds => 'graines'; + + @override + String get seedLibrarySeedDeposit => 'Dépôt de plantes'; + + @override + String get seedLibrarySeedLibrary => 'Grainothèque'; + + @override + String get seedLibrarySeedQuantitySimple => 'Quantité de graines'; + + @override + String get seedLibrarySeedQuantity => 'Quantité de graines :'; + + @override + String get seedLibraryShowDeadPlants => 'Afficher les plantes mortes'; + + @override + String get seedLibrarySpecies => 'Espèce :'; + + @override + String get seedLibrarySpeciesHelp => 'Aide sur l\'espèce'; + + @override + String get seedLibrarySpeciesPlural => 'Espèces'; + + @override + String get seedLibrarySpeciesSimple => 'Espèce'; + + @override + String get seedLibrarySpeciesType => 'Type d\'espèce :'; + + @override + String get seedLibrarySpring => 'Printemps'; + + @override + String get seedLibraryStartMonth => 'Mois de début :'; + + @override + String get seedLibraryStock => 'Stock disponible'; + + @override + String get seedLibrarySummer => 'Été'; + + @override + String get seedLibraryStocks => 'Stocks'; + + @override + String get seedLibraryTimeUntilMaturation => 'Temps avant maturation :'; + + @override + String get seedLibraryType => 'Type :'; + + @override + String get seedLibraryUnableToOpen => 'Impossible d\'ouvrir le lien'; + + @override + String get seedLibraryUpdate => 'Modifier'; + + @override + String get seedLibraryUpdatedInformation => 'Informations modifiées'; + + @override + String get seedLibraryUpdatedSpecies => 'Espèce modifiée'; + + @override + String get seedLibraryUpdatedPlant => 'Plante modifiée'; + + @override + String get seedLibraryUpdatingError => 'Erreur lors de la modification'; + + @override + String get seedLibraryWinter => 'Hiver'; + + @override + String get seedLibraryWriteReference => + 'Veuillez écrire la référence suivante : '; + + @override + String get settingsAccount => 'Compte'; + + @override + String get settingsAddProfilePicture => 'Ajouter une photo'; + + @override + String get settingsAdmin => 'Administrateur'; + + @override + String get settingsAskHelp => 'Demander de l\'aide'; + + @override + String get settingsAssociation => 'Association'; + + @override + String get settingsBirthday => 'Date de naissance'; + + @override + String get settingsBugs => 'Bugs'; + + @override + String get settingsChangePassword => 'Changer de mot de passe'; + + @override + String get settingsChangingPassword => + 'Voulez-vous vraiment changer votre mot de passe ?'; + + @override + String get settingsConfirmPassword => 'Confirmer le mot de passe'; + + @override + String get settingsCopied => 'Copié !'; + + @override + String get settingsDarkMode => 'Mode sombre'; + + @override + String get settingsDarkModeOff => 'Désactivé'; + + @override + String get settingsDeleteLogs => 'Supprimer les logs ?'; + + @override + String get settingsDeleteNotificationLogs => + 'Supprimer les logs des notifications ?'; + + @override + String get settingsDetelePersonalData => 'Supprimer mes données personnelles'; + + @override + String get settingsDetelePersonalDataDesc => + 'Cette action notifie l\'administrateur que vous souhaitez supprimer vos données personnelles.'; + + @override + String get settingsDeleting => 'Suppresion'; + + @override + String get settingsEdit => 'Modifier'; + + @override + String get settingsEditAccount => 'Modifier le compte'; + + @override + String get settingsEditPassword => 'Modifier le mot de passe'; + + @override + String get settingsEmail => 'Email'; + + @override + String get settingsEmptyField => 'Ce champ ne peut pas être vide'; + + @override + String get settingsErrorProfilePicture => + 'Erreur lors de la modification de la photo de profil'; + + @override + String get settingsErrorSendingDemand => + 'Erreur lors de l\'envoi de la demande'; + + @override + String get settingsEventsIcal => 'Lien Ical des événements'; + + @override + String get settingsExpectingDate => 'Date de naissance attendue'; + + @override + String get settingsFirstname => 'Prénom'; + + @override + String get settingsFloor => 'Étage'; + + @override + String get settingsHelp => 'Aide'; + + @override + String get settingsIcalCopied => 'Lien Ical copié !'; + + @override + String get settingsLanguage => 'Langue'; + + @override + String get settingsLanguageFr => 'Français'; + + @override + String get settingsLogs => 'Logs'; + + @override + String get settingsModules => 'Modules'; + + @override + String get settingsMyIcs => 'Mon lien Ical'; + + @override + String get settingsName => 'Nom'; + + @override + String get settingsNewPassword => 'Nouveau mot de passe'; + + @override + String get settingsNickname => 'Surnom'; + + @override + String get settingsNotifications => 'Notifications'; + + @override + String get settingsOldPassword => 'Ancien mot de passe'; + + @override + String get settingsPasswordChanged => 'Mot de passe changé'; + + @override + String get settingsPasswordsNotMatch => + 'Les mots de passe ne correspondent pas'; + + @override + String get settingsPersonalData => 'Données personnelles'; + + @override + String get settingsPersonalisation => 'Personnalisation'; + + @override + String get settingsPhone => 'Téléphone'; + + @override + String get settingsProfilePicture => 'Photo de profil'; + + @override + String get settingsPromo => 'Promotion'; + + @override + String get settingsRepportBug => 'Signaler un bug'; + + @override + String get settingsSave => 'Enregistrer'; + + @override + String get settingsSecurity => 'Sécurité'; + + @override + String get settingsSendedDemand => 'Demande envoyée'; + + @override + String get settingsSettings => 'Paramètres'; + + @override + String get settingsTooHeavyProfilePicture => + 'L\'image est trop lourde (max 4Mo)'; + + @override + String get settingsUpdatedProfile => 'Profil modifié'; + + @override + String get settingsUpdatedProfilePicture => 'Photo de profil modifiée'; + + @override + String get settingsUpdateNotification => 'Mettre à jour les notifications'; + + @override + String get settingsUpdatingError => + 'Erreur lors de la modification du profil'; + + @override + String get settingsVersion => 'Version'; + + @override + String get settingsPasswordStrength => 'Force du mot de passe'; + + @override + String get settingsPasswordStrengthVeryWeak => 'Très faible'; + + @override + String get settingsPasswordStrengthWeak => 'Faible'; + + @override + String get settingsPasswordStrengthMedium => 'Moyen'; + + @override + String get settingsPasswordStrengthStrong => 'Fort'; + + @override + String get settingsPasswordStrengthVeryStrong => 'Très fort'; + + @override + String get voteAdd => 'Ajouter'; + + @override + String get voteAddMember => 'Ajouter un membre'; + + @override + String get voteAddedPretendance => 'Liste ajoutée'; + + @override + String get voteAddedSection => 'Section ajoutée'; + + @override + String get voteAddingError => 'Erreur lors de l\'ajout'; + + @override + String get voteAddPretendance => 'Ajouter une liste'; + + @override + String get voteAddSection => 'Ajouter une section'; + + @override + String get voteAll => 'Tous'; + + @override + String get voteAlreadyAddedMember => 'Membre déjà ajouté'; + + @override + String get voteAlreadyVoted => 'Vote enregistré'; + + @override + String get voteChooseList => 'Choisir une liste'; + + @override + String get voteClear => 'Réinitialiser'; + + @override + String get voteClearVotes => 'Réinitialiser les votes'; + + @override + String get voteClosedVote => 'Votes clos'; + + @override + String get voteCloseVote => 'Fermer les votes'; + + @override + String get voteConfirmVote => 'Confirmer le vote'; + + @override + String get voteCountVote => 'Dépouiller les votes'; + + @override + String get voteDeletedAll => 'Tout supprimé'; + + @override + String get voteDeletedPipo => 'Listes pipos supprimées'; + + @override + String get voteDeletedSection => 'Section supprimée'; + + @override + String get voteDeleteAll => 'Supprimer tout'; + + @override + String get voteDeleteAllDescription => + 'Voulez-vous vraiment supprimer tout ?'; + + @override + String get voteDeletePipo => 'Supprimer les listes pipos'; + + @override + String get voteDeletePipoDescription => + 'Voulez-vous vraiment supprimer les listes pipos ?'; + + @override + String get voteDeletePretendance => 'Supprimer la liste'; + + @override + String get voteDeletePretendanceDesc => + 'Voulez-vous vraiment supprimer cette liste ?'; + + @override + String get voteDeleteSection => 'Supprimer la section'; + + @override + String get voteDeleteSectionDescription => + 'Voulez-vous vraiment supprimer cette section ?'; + + @override + String get voteDeletingError => 'Erreur lors de la suppression'; + + @override + String get voteDescription => 'Description'; + + @override + String get voteEdit => 'Modifier'; + + @override + String get voteEditedPretendance => 'Liste modifiée'; + + @override + String get voteEditedSection => 'Section modifiée'; + + @override + String get voteEditingError => 'Erreur lors de la modification'; + + @override + String get voteErrorClosingVotes => 'Erreur lors de la fermeture des votes'; + + @override + String get voteErrorCountingVotes => 'Erreur lors du dépouillement des votes'; + + @override + String get voteErrorResetingVotes => + 'Erreur lors de la réinitialisation des votes'; + + @override + String get voteErrorOpeningVotes => 'Erreur lors de l\'ouverture des votes'; + + @override + String get voteIncorrectOrMissingFields => 'Champs incorrects ou manquants'; + + @override + String get voteMembers => 'Membres'; + + @override + String get voteName => 'Nom'; + + @override + String get voteNoPretendanceList => 'Aucune liste de prétendance'; + + @override + String get voteNoSection => 'Aucune section'; + + @override + String get voteCanNotVote => 'Vous ne pouvez pas voter'; + + @override + String get voteNoSectionList => 'Aucune section'; + + @override + String get voteNotOpenedVote => 'Vote non ouvert'; + + @override + String get voteOnGoingCount => 'Dépouillement en cours'; + + @override + String get voteOpenVote => 'Ouvrir les votes'; + + @override + String get votePipo => 'Pipo'; + + @override + String get votePretendance => 'Listes'; + + @override + String get votePretendanceDeleted => 'Prétendance supprimée'; + + @override + String get votePretendanceNotDeleted => 'Erreur lors de la suppression'; + + @override + String get voteProgram => 'Programme'; + + @override + String get votePublish => 'Publier'; + + @override + String get votePublishVoteDescription => + 'Voulez-vous vraiment publier les votes ?'; + + @override + String get voteResetedVotes => 'Votes réinitialisés'; + + @override + String get voteResetVote => 'Réinitialiser les votes'; + + @override + String get voteResetVoteDescription => 'Que voulez-vous faire ?'; + + @override + String get voteRole => 'Rôle'; + + @override + String get voteSectionDescription => 'Description de la section'; + + @override + String get voteSection => 'Section'; + + @override + String get voteSectionName => 'Nom de la section'; + + @override + String get voteSeeMore => 'Voir plus'; + + @override + String get voteSelected => 'Sélectionné'; + + @override + String get voteShowVotes => 'Voir les votes'; + + @override + String get voteVote => 'Vote'; + + @override + String get voteVoteError => 'Erreur lors de l\'enregistrement du vote'; + + @override + String get voteVoteFor => 'Voter pour '; + + @override + String get voteVoteNotStarted => 'Vote non ouvert'; + + @override + String get voteVoters => 'Groupes votants'; + + @override + String get voteVoteSuccess => 'Vote enregistré'; + + @override + String get voteVotes => 'Voix'; + + @override + String get voteVotesClosed => 'Votes clos'; + + @override + String get voteVotesCounted => 'Votes dépouillés'; + + @override + String get voteVotesOpened => 'Votes ouverts'; + + @override + String get voteWarning => 'Attention'; + + @override + String get voteWarningMessage => + 'La sélection ne sera pas sauvegardée.\nVoulez-vous continuer ?'; +} diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart new file mode 100644 index 0000000000..b7403438e1 --- /dev/null +++ b/lib/l10n/app_localizations_fr.dart @@ -0,0 +1,3397 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'app_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for French (`fr`). +class AppLocalizationsFr extends AppLocalizations { + AppLocalizationsFr([String locale = 'fr']) : super(locale); + + @override + String get adminAccountTypes => 'Types de compte'; + + @override + String get adminAdd => 'Ajouter'; + + @override + String get adminAddGroup => 'Ajouter un groupe'; + + @override + String get adminAddMember => 'Ajouter un membre'; + + @override + String get adminAddedGroup => 'Groupe créé'; + + @override + String get adminAddedLoaner => 'Préteur ajouté'; + + @override + String get adminAddedMember => 'Membre ajouté'; + + @override + String get adminAddingError => 'Erreur lors de l\'ajout'; + + @override + String get adminAddingMember => 'Ajout d\'un membre'; + + @override + String get adminAddLoaningGroup => 'Ajouter un groupe de prêt'; + + @override + String get adminAddSchool => 'Ajouter une école'; + + @override + String get adminAddStructure => 'Ajouter une structure'; + + @override + String get adminAddedSchool => 'École créée'; + + @override + String get adminAddedStructure => 'Structure ajoutée'; + + @override + String get adminEditedStructure => 'Structure modifiée'; + + @override + String get adminAdministration => 'Administration'; + + @override + String get adminAssociationMembership => 'Adhésion'; + + @override + String get adminAssociationMembershipName => 'Nom de l\'adhésion'; + + @override + String get adminAssociationsMemberships => 'Adhésions'; + + @override + String get adminClearFilters => 'Effacer les filtres'; + + @override + String get adminCreateAssociationMembership => 'Créer une adhésion'; + + @override + String get adminCreatedAssociationMembership => 'Adhésion créée'; + + @override + String get adminCreationError => 'Erreur lors de la création'; + + @override + String get adminDateError => + 'La date de début doit être avant la date de fin'; + + @override + String get adminDelete => 'Supprimer'; + + @override + String get adminDeleteAssociationMembership => 'Supprimer l\'adhésion ?'; + + @override + String get adminDeletedAssociationMembership => 'Adhésion supprimée'; + + @override + String get adminDeleteGroup => 'Supprimer le groupe ?'; + + @override + String get adminDeletedGroup => 'Groupe supprimé'; + + @override + String get adminDeleteSchool => 'Supprimer l\'école ?'; + + @override + String get adminDeletedSchool => 'École supprimée'; + + @override + String get adminDeleting => 'Suppression'; + + @override + String get adminDeletingError => 'Erreur lors de la suppression'; + + @override + String get adminDescription => 'Description'; + + @override + String get adminEclSchool => 'Centrale Lyon'; + + @override + String get adminEdit => 'Modifier'; + + @override + String get adminEditStructure => 'Modifier la structure'; + + @override + String get adminEditMembership => 'Modifier l\'adhésion'; + + @override + String get adminEmptyDate => 'Date vide'; + + @override + String get adminEmptyFieldError => 'Le nom ne peut pas être vide'; + + @override + String get adminEmailRegex => 'Email Regex'; + + @override + String get adminEmptyUser => 'Utilisateur vide'; + + @override + String get adminEndDate => 'Date de fin'; + + @override + String get adminEndDateMaximal => 'Date de fin maximale'; + + @override + String get adminEndDateMinimal => 'Date de fin minimale'; + + @override + String get adminError => 'Erreur'; + + @override + String get adminFilters => 'Filtres'; + + @override + String get adminGroup => 'Groupe'; + + @override + String get adminGroups => 'Groupes'; + + @override + String get adminLoaningGroup => 'Groupe de prêt'; + + @override + String get adminLooking => 'Recherche'; + + @override + String get adminManager => 'Administrateur de la structure'; + + @override + String get adminMaximum => 'Maximum'; + + @override + String get adminMembers => 'Membres'; + + @override + String get adminMembershipAddingError => + 'Erreur lors de l\'ajout (surement dû à une superposition de dates)'; + + @override + String get adminMemberships => 'Adhésions'; + + @override + String get adminMembershipUpdatingError => + 'Erreur lors de la modification (surement dû à une superposition de dates)'; + + @override + String get adminMinimum => 'Minimum'; + + @override + String get adminModifyModuleVisibility => 'Visibilité des modules'; + + @override + String get adminMyEclPay => 'MyECLPay'; + + @override + String get adminName => 'Nom'; + + @override + String get adminNoManager => 'Aucun manager n\'est sélectionné'; + + @override + String get adminNoMember => 'Aucun membre'; + + @override + String get adminNoMoreLoaner => 'Aucun prêteur n\'est disponible'; + + @override + String get adminNoSchool => 'Sans école'; + + @override + String get adminRemoveGroupMember => 'Supprimer le membre du groupe ?'; + + @override + String get adminResearch => 'Recherche'; + + @override + String get adminSchools => 'Écoles'; + + @override + String get adminStructures => 'Structures'; + + @override + String get adminStartDate => 'Date de début'; + + @override + String get adminStartDateMaximal => 'Date de début maximale'; + + @override + String get adminStartDateMinimal => 'Date de début minimale'; + + @override + String get adminUpdatedAssociationMembership => 'Adhésion modifiée'; + + @override + String get adminUpdatedGroup => 'Groupe modifié'; + + @override + String get adminUpdatedMembership => 'Adhésion modifiée'; + + @override + String get adminUpdatingError => 'Erreur lors de la modification'; + + @override + String get adminUser => 'Utilisateur'; + + @override + String get adminValidateFilters => 'Valider les filtres'; + + @override + String get adminVisibilities => 'Visibilités'; + + @override + String get advertAdd => 'Ajouter'; + + @override + String get advertAddedAdvert => 'Annonce publiée'; + + @override + String get advertAddedAnnouncer => 'Annonceur ajouté'; + + @override + String get advertAddingError => 'Erreur lors de l\'ajout'; + + @override + String get advertAdmin => 'Admin'; + + @override + String get advertAdvert => 'Annonce'; + + @override + String get advertChoosingAnnouncer => 'Veuillez choisir un annonceur'; + + @override + String get advertChoosingPoster => 'Veuillez choisir une image'; + + @override + String get advertContent => 'Contenu'; + + @override + String get advertDeleteAdvert => 'Supprimer l\'annonce ?'; + + @override + String get advertDeleteAnnouncer => 'Supprimer l\'annonceur ?'; + + @override + String get advertDeleting => 'Suppression'; + + @override + String get advertEdit => 'Modifier'; + + @override + String get advertEditedAdvert => 'Annonce modifiée'; + + @override + String get advertEditingError => 'Erreur lors de la modification'; + + @override + String get advertGroupAdvert => 'Groupe'; + + @override + String get advertIncorrectOrMissingFields => 'Champs incorrects ou manquants'; + + @override + String get advertInvalidNumber => 'Veuillez entrer un nombre'; + + @override + String get advertManagement => 'Gestion'; + + @override + String get advertModifyAnnouncingGroup => 'Modifier un groupe d\'annonce'; + + @override + String get advertNoMoreAnnouncer => 'Aucun annonceur n\'est disponible'; + + @override + String get advertNoValue => 'Veuillez entrer une valeur'; + + @override + String get advertPositiveNumber => 'Veuillez entrer un nombre positif'; + + @override + String get advertRemovedAnnouncer => 'Annonceur supprimé'; + + @override + String get advertRemovingError => 'Erreur lors de la suppression'; + + @override + String get advertTags => 'Tags'; + + @override + String get advertTitle => 'Titre'; + + @override + String get advertMonthJan => 'Janv'; + + @override + String get advertMonthFeb => 'Févr.'; + + @override + String get advertMonthMar => 'Mars'; + + @override + String get advertMonthApr => 'Avr.'; + + @override + String get advertMonthMay => 'Mai'; + + @override + String get advertMonthJun => 'Juin'; + + @override + String get advertMonthJul => 'Juill.'; + + @override + String get advertMonthAug => 'Août'; + + @override + String get advertMonthSep => 'Sept.'; + + @override + String get advertMonthOct => 'Oct.'; + + @override + String get advertMonthNov => 'Nov.'; + + @override + String get advertMonthDec => 'Déc.'; + + @override + String get amapAccounts => 'Comptes'; + + @override + String get amapAdd => 'Ajouter'; + + @override + String get amapAddDelivery => 'Ajouter une livraison'; + + @override + String get amapAddedCommand => 'Commande ajoutée'; + + @override + String get amapAddedOrder => 'Commande ajoutée'; + + @override + String get amapAddedProduct => 'Produit ajouté'; + + @override + String get amapAddedUser => 'Utilisateur ajouté'; + + @override + String get amapAddProduct => 'Ajouter un produit'; + + @override + String get amapAddUser => 'Ajouter un utilisateur'; + + @override + String get amapAddingACommand => 'Ajouter une commande'; + + @override + String get amapAddingCommand => 'Ajouter la commande'; + + @override + String get amapAddingError => 'Erreur lors de l\'ajout'; + + @override + String get amapAddingProduct => 'Ajouter un produit'; + + @override + String get amapAddOrder => 'Ajouter une commande'; + + @override + String get amapAdmin => 'Admin'; + + @override + String get amapAlreadyExistCommand => + 'Il existe déjà une commande à cette date'; + + @override + String get amapAmap => 'Amap'; + + @override + String get amapAmount => 'Solde'; + + @override + String get amapArchive => 'Archiver'; + + @override + String get amapArchiveDelivery => 'Archiver'; + + @override + String get amapArchivingDelivery => 'Archivage de la livraison'; + + @override + String get amapCategory => 'Catégorie'; + + @override + String get amapCloseDelivery => 'Verrouiller'; + + @override + String get amapCommandDate => 'Date de la commande'; + + @override + String get amapCommandProducts => 'Produits de la commande'; + + @override + String get amapConfirm => 'Confirmer'; + + @override + String get amapContact => 'Contacts associatifs '; + + @override + String get amapCreateCategory => 'Créer une catégorie'; + + @override + String get amapDelete => 'Supprimer'; + + @override + String get amapDeleteDelivery => 'Supprimer la livraison ?'; + + @override + String get amapDeleteDeliveryDescription => + 'Voulez-vous vraiment supprimer cette livraison ?'; + + @override + String get amapDeletedDelivery => 'Livraison supprimée'; + + @override + String get amapDeletedOrder => 'Commande supprimée'; + + @override + String get amapDeletedProduct => 'Produit supprimé'; + + @override + String get amapDeleteProduct => 'Supprimer le produit ?'; + + @override + String get amapDeleteProductDescription => + 'Voulez-vous vraiment supprimer ce produit ?'; + + @override + String get amapDeleting => 'Suppression'; + + @override + String get amapDeletingDelivery => 'Supprimer la livraison ?'; + + @override + String get amapDeletingError => 'Erreur lors de la suppression'; + + @override + String get amapDeletingOrder => 'Supprimer la commande ?'; + + @override + String get amapDeletingProduct => 'Supprimer le produit ?'; + + @override + String get amapDeliver => 'Livraison teminée ?'; + + @override + String get amapDeliveries => 'Livraisons'; + + @override + String get amapDeliveringDelivery => 'Toutes les commandes sont livrées ?'; + + @override + String get amapDelivery => 'Livraison'; + + @override + String get amapDeliveryArchived => 'Livraison archivée'; + + @override + String get amapDeliveryDate => 'Date de livraison'; + + @override + String get amapDeliveryDelivered => 'Livraison effectuée'; + + @override + String get amapDeliveryHistory => 'Historique des livraisons'; + + @override + String get amapDeliveryList => 'Liste des livraisons'; + + @override + String get amapDeliveryLocked => 'Livraison verrouillée'; + + @override + String get amapDeliveryOn => 'Livraison le'; + + @override + String get amapDeliveryOpened => 'Livraison ouverte'; + + @override + String get amapDeliveryNotArchived => 'Livraison non archivée'; + + @override + String get amapDeliveryNotLocked => 'Livraison non verrouillée'; + + @override + String get amapDeliveryNotDelivered => 'Livraison non effectuée'; + + @override + String get amapDeliveryNotOpened => 'Livraison non ouverte'; + + @override + String get amapEditDelivery => 'Modifier la livraison'; + + @override + String get amapEditedCommand => 'Commande modifiée'; + + @override + String get amapEditingError => 'Erreur lors de la modification'; + + @override + String get amapEditProduct => 'Modifier le produit'; + + @override + String get amapEndingDelivery => 'Fin de la livraison'; + + @override + String get amapError => 'Erreur'; + + @override + String get amapErrorLink => 'Erreur lors de l\'ouverture du lien'; + + @override + String get amapErrorLoadingUser => + 'Erreur lors du chargement des utilisateurs'; + + @override + String get amapEvening => 'Soir'; + + @override + String get amapExpectingNumber => 'Veuillez entrer un nombre'; + + @override + String get amapFillField => 'Veuillez remplir ce champ'; + + @override + String get amapHandlingAccount => 'Gérer les comptes'; + + @override + String get amapLoading => 'Chargement...'; + + @override + String get amapLoadingError => 'Erreur lors du chargement'; + + @override + String get amapLock => 'Verrouiller'; + + @override + String get amapLocked => 'Verrouillée'; + + @override + String get amapLockedDelivery => 'Livraison verrouillée'; + + @override + String get amapLockedOrder => 'Commande verrouillée'; + + @override + String get amapLooking => 'Rechercher'; + + @override + String get amapLockingDelivery => 'Verrouiller la livraison ?'; + + @override + String get amapMidDay => 'Midi'; + + @override + String get amapMyOrders => 'Mes commandes'; + + @override + String get amapName => 'Nom'; + + @override + String get amapNextStep => 'Étape suivante'; + + @override + String get amapNoProduct => 'Pas de produit'; + + @override + String get amapNoCurrentOrder => 'Pas de commande en cours'; + + @override + String get amapNoMoney => 'Pas assez d\'argent'; + + @override + String get amapNoOpennedDelivery => 'Pas de livraison ouverte'; + + @override + String get amapNoOrder => 'Pas de commande'; + + @override + String get amapNoSelectedDelivery => 'Pas de livraison sélectionnée'; + + @override + String get amapNotEnoughMoney => 'Pas assez d\'argent'; + + @override + String get amapNotPlannedDelivery => 'Pas de livraison planifiée'; + + @override + String get amapOneOrder => 'commande'; + + @override + String get amapOpenDelivery => 'Ouvrir'; + + @override + String get amapOpened => 'Ouverte'; + + @override + String get amapOpenningDelivery => 'Ouvrir la livraison ?'; + + @override + String get amapOrder => 'Commander'; + + @override + String get amapOrders => 'Commandes'; + + @override + String get amapPickChooseCategory => + 'Veuillez entrer une valeur ou choisir une catégorie existante'; + + @override + String get amapPickDeliveryMoment => 'Choisissez un moment de livraison'; + + @override + String get amapPresentation => 'Présentation'; + + @override + String get amapPresentation1 => + 'L\'AMAP (association pour le maintien d\'une agriculture paysanne) est un service proposé par l\'association Planet&Co de l\'ECL. Vous pouvez ainsi recevoir des produits (paniers de fruits et légumes, jus, confitures...) directement sur le campus !\n\nLes commandes doivent être passées avant le vendredi 21h et sont livrées sur le campus le mardi de 13h à 13h45 (ou de 18h15 à 18h30 si vous ne pouvez pas passer le midi) dans le hall du M16.\n\nVous ne pouvez commander que si votre solde le permet. Vous pouvez recharger votre solde via la collecte Lydia ou bien avec un chèque que vous pouvez nous transmettre lors des permanences.\n\nLien vers la collecte Lydia pour le rechargement : '; + + @override + String get amapPresentation2 => + '\n\nN\'hésitez pas à nous contacter en cas de problème !'; + + @override + String get amapPrice => 'Prix'; + + @override + String get amapProduct => 'produit'; + + @override + String get amapProducts => 'Produits'; + + @override + String get amapProductInDelivery => 'Produit dans une livraison non terminée'; + + @override + String get amapQuantity => 'Quantité'; + + @override + String get amapRequiredDate => 'La date est requise'; + + @override + String get amapSeeMore => 'Voir plus'; + + @override + String get amapThe => 'Le'; + + @override + String get amapUnlock => 'Dévérouiller'; + + @override + String get amapUnlockedDelivery => 'Livraison dévérouillée'; + + @override + String get amapUnlockingDelivery => 'Dévérouiller la livraison ?'; + + @override + String get amapUpdate => 'Modifier'; + + @override + String get amapUpdatedAmount => 'Solde modifié'; + + @override + String get amapUpdatedOrder => 'Commande modifiée'; + + @override + String get amapUpdatedProduct => 'Produit modifié'; + + @override + String get amapUpdatingError => 'Echec de la modification'; + + @override + String get amapUsersNotFound => 'Aucun utilisateur trouvé'; + + @override + String get amapWaiting => 'En attente'; + + @override + String get bookingAdd => 'Ajouter'; + + @override + String get bookingAddBookingPage => 'Demande'; + + @override + String get bookingAddRoom => 'Ajouter une salle'; + + @override + String get bookingAddBooking => 'Ajouter une réservation'; + + @override + String get bookingAddedBooking => 'Demande ajoutée'; + + @override + String get bookingAddedRoom => 'Salle ajoutée'; + + @override + String get bookingAddedManager => 'Gestionnaire ajouté'; + + @override + String get bookingAddingError => 'Erreur lors de l\'ajout'; + + @override + String get bookingAddManager => 'Ajouter un gestionnaire'; + + @override + String get bookingAdminPage => 'Administrateur'; + + @override + String get bookingAllDay => 'Toute la journée'; + + @override + String get bookingBookedFor => 'Réservé pour'; + + @override + String get bookingBooking => 'Réservation'; + + @override + String get bookingBookingCreated => 'Réservation créée'; + + @override + String get bookingBookingDemand => 'Demande de réservation'; + + @override + String get bookingBookingNote => 'Note de la réservation'; + + @override + String get bookingBookingPage => 'Réservation'; + + @override + String get bookingBookingReason => 'Motif de la réservation'; + + @override + String get bookingBy => 'par'; + + @override + String get bookingConfirm => 'Confirmer'; + + @override + String get bookingConfirmation => 'Confirmation'; + + @override + String get bookingConfirmBooking => 'Confirmer la réservation ?'; + + @override + String get bookingConfirmed => 'Validée'; + + @override + String get bookingDates => 'Dates'; + + @override + String get bookingDecline => 'Refuser'; + + @override + String get bookingDeclineBooking => 'Refuser la réservation ?'; + + @override + String get bookingDeclined => 'Refusée'; + + @override + String get bookingDelete => 'Supprimer'; + + @override + String get bookingDeleting => 'Suppression'; + + @override + String get bookingDeleteBooking => 'Suppression'; + + @override + String get bookingDeleteBookingConfirmation => + 'Êtes-vous sûr de vouloir supprimer cette réservation ?'; + + @override + String get bookingDeletedBooking => 'Réservation supprimée'; + + @override + String get bookingDeletedRoom => 'Salle supprimée'; + + @override + String get bookingDeletedManager => 'Gestionnaire supprimé'; + + @override + String get bookingDeleteRoomConfirmation => + 'Êtes-vous sûr de vouloir supprimer cette salle ?\n\nLa salle ne doit avoir aucune réservation en cours ou à venir pour être supprimée'; + + @override + String get bookingDeleteManagerConfirmation => + 'Êtes-vous sûr de vouloir supprimer ce gestionnaire ?\n\nLe gestionnaire ne doit être associé à aucune salle pour pouvoir être supprimé'; + + @override + String get bookingDeletingBooking => 'Supprimer la réservation ?'; + + @override + String get bookingDeletingError => 'Erreur lors de la suppression'; + + @override + String get bookingDeletingRoom => 'Supprimer la salle ?'; + + @override + String get bookingEdit => 'Modifier'; + + @override + String get bookingEditBooking => 'Modifier une réservation'; + + @override + String get bookingEditionError => 'Erreur lors de la modification'; + + @override + String get bookingEditedBooking => 'Réservation modifiée'; + + @override + String get bookingEditedRoom => 'Salle modifiée'; + + @override + String get bookingEditedManager => 'Gestionnaire modifié'; + + @override + String get bookingEditManager => 'Modifier ou supprimer un gestionnaire'; + + @override + String get bookingEditRoom => 'Modifier ou supprimer une salle'; + + @override + String get bookingEndDate => 'Date de fin'; + + @override + String get bookingEndHour => 'Heure de fin'; + + @override + String get bookingEntity => 'Pour qui ?'; + + @override + String get bookingError => 'Erreur'; + + @override + String get bookingEventEvery => 'Tous les'; + + @override + String get bookingHistoryPage => 'Historique'; + + @override + String get bookingIncorrectOrMissingFields => + 'Champs incorrects ou manquants'; + + @override + String get bookingInterval => 'Intervalle'; + + @override + String get bookingInvalidIntervalError => 'Intervalle invalide'; + + @override + String get bookingInvalidDates => 'Dates invalides'; + + @override + String get bookingInvalidRoom => 'Salle invalide'; + + @override + String get bookingKeysRequested => 'Clés demandées'; + + @override + String get bookingManagement => 'Gestion'; + + @override + String get bookingManager => 'Gestionnaire'; + + @override + String get bookingManagerName => 'Nom du gestionnaire'; + + @override + String get bookingMultipleDay => 'Plusieurs jours'; + + @override + String get bookingMyBookings => 'Mes réservations'; + + @override + String get bookingNecessaryKey => 'Clé nécessaire'; + + @override + String get bookingNext => 'Suivant'; + + @override + String get bookingNo => 'Non'; + + @override + String get bookingNoCurrentBooking => 'Pas de réservation en cours'; + + @override + String get bookingNoDateError => 'Veuillez choisir une date'; + + @override + String get bookingNoAppointmentInReccurence => + 'Aucun créneau existe avec ces paramètres de récurrence'; + + @override + String get bookingNoDaySelected => 'Aucun jour sélectionné'; + + @override + String get bookingNoDescriptionError => 'Veuillez entrer une description'; + + @override + String get bookingNoKeys => 'Aucune clé'; + + @override + String get bookingNoNoteError => 'Veuillez entrer une note'; + + @override + String get bookingNoPhoneRegistered => 'Numéro non renseigné'; + + @override + String get bookingNoReasonError => 'Veuillez entrer un motif'; + + @override + String get bookingNoRoomFoundError => 'Aucune salle enregistrée'; + + @override + String get bookingNoRoomFound => 'Aucune salle trouvée'; + + @override + String get bookingNote => 'Note'; + + @override + String get bookingOther => 'Autre'; + + @override + String get bookingPending => 'En attente'; + + @override + String get bookingPrevious => 'Précédent'; + + @override + String get bookingReason => 'Motif'; + + @override + String get bookingRecurrence => 'Récurrence'; + + @override + String get bookingRecurrenceDays => 'Jours de récurrence'; + + @override + String get bookingRecurrenceEndDate => 'Date de fin de récurrence'; + + @override + String get bookingRecurrent => 'Récurrent'; + + @override + String get bookingRegisteredRooms => 'Salles enregistrées'; + + @override + String get bookingRoom => 'Salle'; + + @override + String get bookingRoomName => 'Nom de la salle'; + + @override + String get bookingStartDate => 'Date de début'; + + @override + String get bookingStartHour => 'Heure de début'; + + @override + String get bookingWeeks => 'Semaines'; + + @override + String get bookingYes => 'Oui'; + + @override + String get bookingWeekDayMon => 'Lundi'; + + @override + String get bookingWeekDayTue => 'Mardi'; + + @override + String get bookingWeekDayWed => 'Mercredi'; + + @override + String get bookingWeekDayThu => 'Jeudi'; + + @override + String get bookingWeekDayFri => 'Vendredi'; + + @override + String get bookingWeekDaySat => 'Samedi'; + + @override + String get bookingWeekDaySun => 'Dimanche'; + + @override + String get cinemaAdd => 'Ajouter'; + + @override + String get cinemaAddedSession => 'Séance ajoutée'; + + @override + String get cinemaAddingError => 'Erreur lors de l\'ajout'; + + @override + String get cinemaAddSession => 'Ajouter une séance'; + + @override + String get cinemaCinema => 'Cinéma'; + + @override + String get cinemaDeleteSession => 'Supprimer la séance ?'; + + @override + String get cinemaDeleting => 'Suppression'; + + @override + String get cinemaDuration => 'Durée'; + + @override + String get cinemaEdit => 'Modifier'; + + @override + String get cinemaEditedSession => 'Séance modifiée'; + + @override + String get cinemaEditingError => 'Erreur lors de la modification'; + + @override + String get cinemaEditSession => 'Modifier la séance'; + + @override + String get cinemaEmptyUrl => 'Veuillez entrer une URL'; + + @override + String get cinemaImportFromTMDB => 'Importer depuis TMDB'; + + @override + String get cinemaIncomingSession => 'A l\'affiche'; + + @override + String get cinemaIncorrectOrMissingFields => 'Champs incorrects ou manquants'; + + @override + String get cinemaInvalidUrl => 'URL invalide'; + + @override + String get cinemaGenre => 'Genre'; + + @override + String get cinemaName => 'Nom'; + + @override + String get cinemaNoDateError => 'Veuillez entrer une date'; + + @override + String get cinemaNoDuration => 'Veuillez entrer une durée'; + + @override + String get cinemaNoOverview => 'Aucun synopsis'; + + @override + String get cinemaNoPoster => 'Aucune affiche'; + + @override + String get cinemaNoSession => 'Aucune séance'; + + @override + String get cinemaOverview => 'Synopsis'; + + @override + String get cinemaPosterUrl => 'URL de l\'affiche'; + + @override + String get cinemaSessionDate => 'Jour de la séance'; + + @override + String get cinemaStartHour => 'Heure de début'; + + @override + String get cinemaTagline => 'Slogan'; + + @override + String get cinemaThe => 'Le'; + + @override + String get drawerAdmin => 'Administration'; + + @override + String get drawerAndroidAppLink => + 'https://play.google.com/store/apps/details?id=fr.myecl.titan'; + + @override + String get drawerCopied => 'Copié !'; + + @override + String get drawerDownloadAppOnMobileDevice => + 'Ce site est la version Web de l\'application MyECL. Nous vous invitons à télécharger l\'application. N\'utilisez ce site qu\'en cas de problème avec l\'application.\n'; + + @override + String get drawerIosAppLink => + 'https://apps.apple.com/fr/app/myecl/id6444443430'; + + @override + String get drawerLoginOut => 'Voulez-vous vous déconnecter ?'; + + @override + String get drawerLogOut => 'Déconnexion'; + + @override + String get drawerOr => ' ou '; + + @override + String get drawerSettings => 'Paramètres'; + + @override + String get eventAdd => 'Ajouter'; + + @override + String get eventAddEvent => 'Ajouter un événement'; + + @override + String get eventAddedEvent => 'Événement ajouté'; + + @override + String get eventAddingError => 'Erreur lors de l\'ajout'; + + @override + String get eventAllDay => 'Toute la journée'; + + @override + String get eventConfirm => 'Confirmer'; + + @override + String get eventConfirmEvent => 'Confirmer l\'événement ?'; + + @override + String get eventConfirmation => 'Confirmation'; + + @override + String get eventConfirmed => 'Confirmé'; + + @override + String get eventDates => 'Dates'; + + @override + String get eventDecline => 'Refuser'; + + @override + String get eventDeclineEvent => 'Refuser l\'événement ?'; + + @override + String get eventDeclined => 'Refusé'; + + @override + String get eventDelete => 'Supprimer'; + + @override + String get eventDeletedEvent => 'Événement supprimé'; + + @override + String get eventDeleting => 'Suppression'; + + @override + String get eventDeletingError => 'Erreur lors de la suppression'; + + @override + String get eventDeletingEvent => 'Supprimer l\'événement ?'; + + @override + String get eventDescription => 'Description'; + + @override + String get eventEdit => 'Modifier'; + + @override + String get eventEditEvent => 'Modifier un événement'; + + @override + String get eventEditedEvent => 'Événement modifié'; + + @override + String get eventEditingError => 'Erreur lors de la modification'; + + @override + String get eventEndDate => 'Date de fin'; + + @override + String get eventEndHour => 'Heure de fin'; + + @override + String get eventError => 'Erreur'; + + @override + String get eventEventList => 'Liste des événements'; + + @override + String get eventEventType => 'Type d\'événement'; + + @override + String get eventEvery => 'Tous les'; + + @override + String get eventHistory => 'Historique'; + + @override + String get eventIncorrectOrMissingFields => + 'Certains champs sont incorrects ou manquants'; + + @override + String get eventInterval => 'Intervalle'; + + @override + String get eventInvalidDates => + 'La date de fin doit être après la date de début'; + + @override + String get eventInvalidIntervalError => + 'Veuillez entrer un intervalle valide'; + + @override + String get eventLocation => 'Lieu'; + + @override + String get eventMyEvents => 'Mes événements'; + + @override + String get eventName => 'Nom'; + + @override + String get eventNext => 'Suivant'; + + @override + String get eventNo => 'Non'; + + @override + String get eventNoCurrentEvent => 'Aucun événement en cours'; + + @override + String get eventNoDateError => 'Veuillez entrer une date'; + + @override + String get eventNoDaySelected => 'Aucun jour sélectionné'; + + @override + String get eventNoDescriptionError => 'Veuillez entrer une description'; + + @override + String get eventNoEvent => 'Aucun événement'; + + @override + String get eventNoNameError => 'Veuillez entrer un nom'; + + @override + String get eventNoOrganizerError => 'Veuillez entrer un organisateur'; + + @override + String get eventNoPlaceError => 'Veuillez entrer un lieu'; + + @override + String get eventNoPhoneRegistered => 'Numéro non renseigné'; + + @override + String get eventNoRuleError => 'Veuillez entrer une règle de récurrence'; + + @override + String get eventOrganizer => 'Organisateur'; + + @override + String get eventOther => 'Autre'; + + @override + String get eventPending => 'En attente'; + + @override + String get eventPrevious => 'Précédent'; + + @override + String get eventRecurrence => 'Récurrence'; + + @override + String get eventRecurrenceDays => 'Jours de récurrence'; + + @override + String get eventRecurrenceEndDate => 'Date de fin de la récurrence'; + + @override + String get eventRecurrenceRule => 'Règle de récurrence'; + + @override + String get eventRoom => 'Salle'; + + @override + String get eventStartDate => 'Date de début'; + + @override + String get eventStartHour => 'Heure de début'; + + @override + String get eventTitle => 'Événements'; + + @override + String get eventYes => 'Oui'; + + @override + String get eventEventEvery => 'Toutes les'; + + @override + String get eventWeeks => 'semaines'; + + @override + String get eventDayMon => 'Lundi'; + + @override + String get eventDayTue => 'Mardi'; + + @override + String get eventDayWed => 'Mercredi'; + + @override + String get eventDayThu => 'Jeudi'; + + @override + String get eventDayFri => 'Vendredi'; + + @override + String get eventDaySat => 'Samedi'; + + @override + String get eventDaySun => 'Dimanche'; + + @override + String get homeCalendar => 'Calendrier'; + + @override + String get homeEventOf => 'Évènements du'; + + @override + String get homeIncomingEvents => 'Évènements à venir'; + + @override + String get homeLastInfos => 'Dernières annonces'; + + @override + String get homeNoEvents => 'Aucun évènement'; + + @override + String get homeTranslateDayShortMon => 'Lun'; + + @override + String get homeTranslateDayShortTue => 'Mar'; + + @override + String get homeTranslateDayShortWed => 'Mer'; + + @override + String get homeTranslateDayShortThu => 'Jeu'; + + @override + String get homeTranslateDayShortFri => 'Ven'; + + @override + String get homeTranslateDayShortSat => 'Sam'; + + @override + String get homeTranslateDayShortSun => 'Dim'; + + @override + String get loanAdd => 'Ajouter'; + + @override + String get loanAddLoan => 'Ajouter un prêt'; + + @override + String get loanAddObject => 'Ajouter un objet'; + + @override + String get loanAddedLoan => 'Prêt ajouté'; + + @override + String get loanAddedObject => 'Objet ajouté'; + + @override + String get loanAddedRoom => 'Salle ajoutée'; + + @override + String get loanAddingError => 'Erreur lors de l\'ajout'; + + @override + String get loanAdmin => 'Administrateur'; + + @override + String get loanAvailable => 'Disponible'; + + @override + String get loanAvailableMultiple => 'Disponibles'; + + @override + String get loanBorrowed => 'Emprunté'; + + @override + String get loanBorrowedMultiple => 'Empruntés'; + + @override + String get loanAnd => 'et'; + + @override + String get loanAssociation => 'Association'; + + @override + String get loanAvailableItems => 'Objets disponibles'; + + @override + String get loanBeginDate => 'Date du début du prêt'; + + @override + String get loanBorrower => 'Emprunteur'; + + @override + String get loanCaution => 'Caution'; + + @override + String get loanCancel => 'Annuler'; + + @override + String get loanConfirm => 'Confirmer'; + + @override + String get loanConfirmation => 'Confirmation'; + + @override + String get loanDates => 'Dates'; + + @override + String get loanDays => 'Jours'; + + @override + String get loanDelay => 'Délai de la prolongation'; + + @override + String get loanDelete => 'Supprimer'; + + @override + String get loanDeletingLoan => 'Supprimer le prêt ?'; + + @override + String get loanDeletedItem => 'Objet supprimé'; + + @override + String get loanDeletedLoan => 'Prêt supprimé'; + + @override + String get loanDeleting => 'Suppression'; + + @override + String get loanDeletingError => 'Erreur lors de la suppression'; + + @override + String get loanDeletingItem => 'Supprimer l\'objet ?'; + + @override + String get loanDuration => 'Durée'; + + @override + String get loanEdit => 'Modifier'; + + @override + String get loanEditItem => 'Modifier l\'objet'; + + @override + String get loanEditLoan => 'Modifier le prêt'; + + @override + String get loanEditedRoom => 'Salle modifiée'; + + @override + String get loanEndDate => 'Date de fin du prêt'; + + @override + String get loanEnded => 'Terminé'; + + @override + String get loanEnterDate => 'Veuillez entrer une date'; + + @override + String get loanExtendedLoan => 'Prêt prolongé'; + + @override + String get loanExtendingError => 'Erreur lors de la prolongation'; + + @override + String get loanHistory => 'Historique'; + + @override + String get loanIncorrectOrMissingFields => + 'Des champs sont manquants ou incorrects'; + + @override + String get loanInvalidNumber => 'Veuillez entrer un nombre'; + + @override + String get loanInvalidDates => 'Les dates ne sont pas valides'; + + @override + String get loanItem => 'Objet'; + + @override + String get loanItems => 'Objets'; + + @override + String get loanItemHandling => 'Gestion des objets'; + + @override + String get loanItemSelected => 'objet sélectionné'; + + @override + String get loanItemsSelected => 'objets sélectionnés'; + + @override + String get loanLendingDuration => 'Durée possible du prêt'; + + @override + String get loanLoan => 'Prêt'; + + @override + String get loanLoanHandling => 'Gestion des prêts'; + + @override + String get loanLooking => 'Rechercher'; + + @override + String get loanName => 'Nom'; + + @override + String get loanNext => 'Suivant'; + + @override + String get loanNo => 'Non'; + + @override + String get loanNoAssociationsFounded => 'Aucune association trouvée'; + + @override + String get loanNoAvailableItems => 'Aucun objet disponible'; + + @override + String get loanNoBorrower => 'Aucun emprunteur'; + + @override + String get loanNoItems => 'Aucun objet'; + + @override + String get loanNoItemSelected => 'Aucun objet sélectionné'; + + @override + String get loanNoLoan => 'Aucun prêt'; + + @override + String get loanNoReturnedDate => 'Pas de date de retour'; + + @override + String get loanQuantity => 'Quantité'; + + @override + String get loanNone => 'Aucun'; + + @override + String get loanNote => 'Note'; + + @override + String get loanNoValue => 'Veuillez entrer une valeur'; + + @override + String get loanOnGoing => 'En cours'; + + @override + String get loanOnGoingLoan => 'Prêt en cours'; + + @override + String get loanOthers => 'autres'; + + @override + String get loanPaidCaution => 'Caution payée'; + + @override + String get loanPositiveNumber => 'Veuillez entrer un nombre positif'; + + @override + String get loanPrevious => 'Précédent'; + + @override + String get loanReturned => 'Rendu'; + + @override + String get loanReturnedLoan => 'Prêt rendu'; + + @override + String get loanReturningError => 'Erreur lors du retour'; + + @override + String get loanReturningLoan => 'Retour'; + + @override + String get loanReturnLoan => 'Rendre le prêt ?'; + + @override + String get loanReturnLoanDescription => 'Voulez-vous rendre ce prêt ?'; + + @override + String get loanToReturn => 'A rendre'; + + @override + String get loanUnavailable => 'Indisponible'; + + @override + String get loanUpdate => 'Modifier'; + + @override + String get loanUpdatedItem => 'Objet modifié'; + + @override + String get loanUpdatedLoan => 'Prêt modifié'; + + @override + String get loanUpdatingError => 'Erreur lors de la modification'; + + @override + String get loanYes => 'Oui'; + + @override + String get loginAccountActivated => 'Compte activé'; + + @override + String get loginAccountNotActivated => 'Compte non activé'; + + @override + String get loginActivationCode => 'Code d\'activation'; + + @override + String get loginBirthday => 'Date de naissance'; + + @override + String get loginCanBeEmpty => 'Ce champ peut être vide'; + + @override + String get loginConfirmPassword => 'Confirmer le mot de passe'; + + @override + String get loginCreate => 'Créer'; + + @override + String get loginCreateAccount => 'Créer un compte'; + + @override + String get loginCreateAccountTitle => 'Créer un\ncompte'; + + @override + String get loginEmail => 'Email'; + + @override + String get loginEmailEmpty => 'Veuillez entrer une adresse mail'; + + @override + String get loginEmailInvalid => + 'Veuillez entrer une adresse mail de centrale.\nSi vous n\'en possédez pas, veuillez contacter Éclair'; + + @override + String get loginEmptyFieldError => 'Ce champ ne peut pas être vide'; + + @override + String get loginEndActivation => 'Finaliser l\'activation'; + + @override + String get loginEndResetPassword => 'Finaliser la \nréinitialisation'; + + @override + String get loginErrorResetPassword => 'Erreur lors de la réinitialisation'; + + @override + String get loginExpectingDate => 'Une date est attendue'; + + @override + String get loginFillAllFields => 'Veuillez remplir tous les champs'; + + @override + String get loginFirstname => 'Prénom'; + + @override + String get loginFloor => 'Étage'; + + @override + String get loginForgetPassword => 'Mot de passe\noublié'; + + @override + String get loginForgotPassword => 'Mot de passe oublié ?'; + + @override + String get loginInvalidToken => 'Code d\'activation invalide'; + + @override + String get loginLoginFailed => 'Échec de la connexion'; + + @override + String get loginMailSendingError => 'Erreur lors de la création du compte'; + + @override + String get loginMustBeIntError => 'Ce champ doit être un entier'; + + @override + String get loginName => 'Nom'; + + @override + String get loginNewPassword => 'Nouveau mot de passe'; + + @override + String get loginPassword => 'Mot de passe'; + + @override + String get loginPasswordLengthError => + 'Le mot de passe doit faire au moins 6 caractères'; + + @override + String get loginPasswordUppercaseError => + 'Le mot de passe doit contenir au moins une majuscule'; + + @override + String get loginPasswordLowercaseError => + 'Le mot de passe doit contenir au moins une minucule'; + + @override + String get loginPasswordNumberError => + 'Le mot de passe doit contenir au moins un chiffre'; + + @override + String get loginPasswordSpecialCaracterError => + 'Le mot de passe doit contenir au moins un caractère spécial'; + + @override + String get loginPasswordMustMatch => 'Les mots de passe doivent correspondre'; + + @override + String get loginPasswordStrengthVeryWeak => 'Très faible'; + + @override + String get loginPasswordStrengthWeak => 'Faible'; + + @override + String get loginPasswordStrengthMedium => 'Moyen'; + + @override + String get loginPasswordStrengthStrong => 'Fort'; + + @override + String get loginPasswordStrengthVeryStrong => 'Très fort'; + + @override + String get loginPhone => 'Téléphone'; + + @override + String get loginPromo => 'Promo entrante (ex : 2023)'; + + @override + String get loginSendedMail => 'Mail de confirmation envoyé'; + + @override + String get loginSendedResetMail => 'Mail de réinitialisation envoyé'; + + @override + String get loginSignIn => 'Se connecter'; + + @override + String get loginRegister => 'S\'inscrire'; + + @override + String get loginRecievedMail => 'J\'ai reçu le mail'; + + @override + String get loginRecover => 'Réinitialiser'; + + @override + String get loginResetedPassword => 'Mot de passe réinitialisé'; + + @override + String get loginResetPasswordTitle => 'Réinitialiser\nle mot de \npasse'; + + @override + String get loginNickname => 'Surnom'; + + @override + String get loginWelcomeBack => 'Bienvenue'; + + @override + String get loginAppName => 'MyECL'; + + @override + String get othersCheckInternetConnection => + 'Veuillez vérifier votre connexion internet'; + + @override + String get othersRetry => 'Réessayer'; + + @override + String get othersTooOldVersion => + 'Votre version de l\'application est trop ancienne.\n\nVeuillez mettre à jour l\'application.'; + + @override + String get othersUnableToConnectToServer => + 'Impossible de se connecter au serveur'; + + @override + String get othersVersion => 'Version'; + + @override + String get othersNoModule => + 'Aucun module disponible, veuillez réessayer ultérieurement 😢😢'; + + @override + String get othersAdmin => 'Admin'; + + @override + String get othersError => 'Une erreur est survenue'; + + @override + String get othersNoValue => 'Veuillez entrer une valeur'; + + @override + String get othersInvalidNumber => 'Veuillez entrer un nombre'; + + @override + String get othersNoDateError => 'Veuillez entrer une date'; + + @override + String get othersImageSizeTooBig => + 'La taille de l\'image ne doit pas dépasser 4 Mio'; + + @override + String get othersImageError => 'Erreur lors de l\'ajout de l\'image'; + + @override + String get phAddNewJournal => 'Ajouter un nouveau journal'; + + @override + String get phNameField => 'Nom : '; + + @override + String get phDateField => 'Date : '; + + @override + String get phDelete => 'Voulez-vous vraiment supprimer ce journal ?'; + + @override + String get phIrreversibleAction => 'Cette action est irréversible'; + + @override + String get phToHeavyFile => 'Fichier trop volumineux'; + + @override + String get phAddPdfFile => 'Ajouter un fichier PDF'; + + @override + String get phEditPdfFile => 'Modifier le fichier PDF'; + + @override + String get phPhName => 'Nom du PH'; + + @override + String get phDate => 'Date'; + + @override + String get phAdded => 'Ajouté'; + + @override + String get phEdited => 'Modifié'; + + @override + String get phAddingFileError => 'Erreur d\'ajout'; + + @override + String get phMissingInformatonsOrPdf => + 'Informations manquantes ou fichier PDF manquant'; + + @override + String get phAdd => 'Ajouter'; + + @override + String get phEdit => 'Modifier'; + + @override + String get phSeePreviousJournal => 'Voir les anciens journaux'; + + @override + String get phNoJournalInDatabase => 'Pas encore de PH dans la base de donnée'; + + @override + String get phSuccesDowloading => 'Téléchargé avec succès'; + + @override + String get phonebookActiveMandate => 'Mandat actif :'; + + @override + String get phonebookAdd => 'Ajouter'; + + @override + String get phonebookAddAssociation => 'Ajouter une association'; + + @override + String get phonebookAddedAssociation => 'Association ajoutée'; + + @override + String get phonebookAddedMember => 'Membre ajouté'; + + @override + String get phonebookAddingError => 'Erreur lors de l\'ajout'; + + @override + String get phonebookAddMember => 'Ajouter un membre'; + + @override + String get phonebookAddRole => 'Ajouter un rôle'; + + @override + String get phonebookAdmin => 'Admin'; + + @override + String get phonebookAdminPage => 'Page Administrateur'; + + @override + String get phonebookAll => 'Toutes'; + + @override + String get phonebookApparentName => 'Nom public du rôle :'; + + @override + String get phonebookAssociation => 'Association :'; + + @override + String get phonebookAssociationDetail => 'Détail de l\'association :'; + + @override + String get phonebookAssociationKind => 'Type d\'association :'; + + @override + String get phonebookAssociationPure => 'Association'; + + @override + String get phonebookAssociationPureSearch => ' Association'; + + @override + String get phonebookAssociations => 'Associations :'; + + @override + String get phonebookCancel => 'Annuler'; + + @override + String get phonebookChangeMandate => 'Passer au mandat '; + + @override + String get phonebookChangeMandateConfirm => + 'Êtes-vous sûr de vouloir changer tout le mandat ?\nCette action est irréversible !'; + + @override + String get phonebookCopied => 'Copié dans le presse-papier'; + + @override + String get phonebookDeactivateAssociation => + 'Êtes-vous sûr de vouloir désactiver cette association ?\nCette action est irréversible !'; + + @override + String get phonebookDeactivatedAssociation => 'Association désactivée'; + + @override + String get phonebookDeactivatedAssociationWarning => + 'Attention, cette association est désactivée, vous ne pouvez pas la modifier'; + + @override + String get phonebookDeactivating => 'Désactiver l\'association ?'; + + @override + String get phonebookDeactivatingError => 'Erreur lors de la désactivation'; + + @override + String get phonebookDetail => 'Détail :'; + + @override + String get phonebookDeleteAssociation => + 'Supprimer l\'association ?\nCela va effacer tout l\'historique de l\'association'; + + @override + String get phonebookDeletedAssociation => 'Association supprimée'; + + @override + String get phonebookDeletedMember => 'Membre supprimé'; + + @override + String get phonebookDeleting => 'Suppression'; + + @override + String get phonebookDeletingError => 'Erreur lors de la suppression'; + + @override + String get phonebookDescription => 'Description'; + + @override + String get phonebookEdit => 'Modifier'; + + @override + String get phonebookEditMembership => 'Modifier le rôle'; + + @override + String get phonebookEmail => 'Email :'; + + @override + String get phonebookEmailCopied => 'Email copié dans le presse-papier'; + + @override + String get phonebookEmptyApparentName => 'Veuillez entrer un nom de role'; + + @override + String get phonebookEmptyFieldError => 'Un champ n\'est pas rempli'; + + @override + String get phonebookEmptyKindError => + 'Veuillez choisir un type d\'association'; + + @override + String get phonebookEmptyMember => 'Aucun membre sélectionné'; + + @override + String get phonebookErrorAssociationLoading => + 'Erreur lors du chargement de l\'association'; + + @override + String get phonebookErrorAssociationNameEmpty => + 'Veuillez entrer un nom d\'association'; + + @override + String get phonebookErrorAssociationPicture => + 'Erreur lors de la modification de la photo d\'association'; + + @override + String get phonebookErrorKindsLoading => + 'Erreur lors du chargement des types d\'association'; + + @override + String get phonebookErrorLoadAssociationList => + 'Erreur lors du chargement de la liste des associations'; + + @override + String get phonebookErrorLoadAssociationMember => + 'Erreur lors du chargement des membres de l\'association'; + + @override + String get phonebookErrorLoadAssociationPicture => + 'Erreur lors du chargement de la photo d\'association'; + + @override + String get phonebookErrorLoadProfilePicture => 'Erreur'; + + @override + String get phonebookErrorRoleTagsLoading => + 'Erreur lors du chargement des tags de rôle'; + + @override + String get phonebookExistingMembership => + 'Ce membre est déjà dans le mandat actuel'; + + @override + String get phonebookFirstname => 'Prénom :'; + + @override + String get phonebookGroups => 'Groupes associés :'; + + @override + String get phonebookMandateChangingError => + 'Erreur lors du changement de mandat'; + + @override + String get phonebookMember => 'Membre'; + + @override + String get phonebookMemberReordered => 'Membre réordonné'; + + @override + String get phonebookMembers => 'Membres'; + + @override + String get phonebookMembershipAssociationError => + 'Veuillez choisir une association'; + + @override + String get phonebookMembershipRole => 'Rôle :'; + + @override + String get phonebookMembershipRoleError => 'Veuillez choisir un rôle'; + + @override + String get phonebookName => 'Nom :'; + + @override + String get phonebookNameCopied => 'Nom et prénom copié dans le presse-papier'; + + @override + String get phonebookNamePure => 'Nom'; + + @override + String get phonebookNewMandate => 'Nouveau mandat'; + + @override + String get phonebookNewMandateConfirmed => 'Mandat changé'; + + @override + String get phonebookNickname => 'Surnom :'; + + @override + String get phonebookNicknameCopied => 'Surnom copié dans le presse-papier'; + + @override + String get phonebookNoAssociationFound => 'Aucune association trouvée'; + + @override + String get phonebookNoMember => 'Aucun membre'; + + @override + String get phonebookNoMemberRole => 'Aucun role trouvé'; + + @override + String get phonebookPhone => 'Téléphone :'; + + @override + String get phonebookPhonebook => 'Annuaire'; + + @override + String get phonebookPhonebookSearch => 'Rechercher'; + + @override + String get phonebookPhonebookSearchAssociation => 'Association'; + + @override + String get phonebookPhonebookSearchField => 'Rechercher :'; + + @override + String get phonebookPhonebookSearchName => 'Nom/Prénom/Surnom'; + + @override + String get phonebookPhonebookSearchRole => 'Poste'; + + @override + String get phonebookPresidentRoleTag => 'Prez\''; + + @override + String get phonebookPromoNotGiven => 'Promo non renseignée'; + + @override + String get phonebookPromotion => 'Promotion :'; + + @override + String get phonebookReorderingError => 'Erreur lors du réordonnement'; + + @override + String get phonebookResearch => 'Rechercher'; + + @override + String get phonebookRolePure => 'Rôle'; + + @override + String get phonebookTooHeavyAssociationPicture => + 'L\'image est trop lourde (max 4Mo)'; + + @override + String get phonebookUpdateGroups => 'Mettre à jour les groupes'; + + @override + String get phonebookUpdatedAssociation => 'Association modifiée'; + + @override + String get phonebookUpdatedAssociationPicture => + 'La photo d\'association a été changée'; + + @override + String get phonebookUpdatedGroups => 'Groupes mis à jour'; + + @override + String get phonebookUpdatedMember => 'Membre modifié'; + + @override + String get phonebookUpdatingError => 'Erreur lors de la modification'; + + @override + String get phonebookValidation => 'Valider'; + + @override + String get purchasesPurchases => 'Achats'; + + @override + String get purchasesResearch => 'Rechercher'; + + @override + String get purchasesNoPurchasesFound => 'Aucun achat trouvé'; + + @override + String get purchasesNoTickets => 'Aucun ticket'; + + @override + String get purchasesTicketsError => 'Erreur lors du chargement des tickets'; + + @override + String get purchasesPurchasesError => 'Erreur lors du chargement des achats'; + + @override + String get purchasesNoPurchases => 'Aucun achat'; + + @override + String get purchasesTimes => 'fois'; + + @override + String get purchasesAlreadyUsed => 'Déjà utilisé'; + + @override + String get purchasesNotPaid => 'Non validé'; + + @override + String get purchasesPleaseSelectProduct => 'Veuillez sélectionner un produit'; + + @override + String get purchasesProducts => 'Produits'; + + @override + String get purchasesCancel => 'Annuler'; + + @override + String get purchasesValidate => 'Valider'; + + @override + String get purchasesLeftScan => 'Scans restants'; + + @override + String get purchasesTag => 'Tag'; + + @override + String get purchasesHistory => 'Historique'; + + @override + String get purchasesPleaseSelectSeller => 'Veuillez sélectionner un vendeur'; + + @override + String get purchasesNoTagGiven => 'Attention, aucun tag n\'a été entré'; + + @override + String get purchasesTickets => 'Tickets'; + + @override + String get purchasesNoScannableProducts => 'Aucun produit scannable'; + + @override + String get purchasesLoading => 'En attente de scan'; + + @override + String get purchasesScan => 'Scanner'; + + @override + String get raffleRaffle => 'Tombola'; + + @override + String get rafflePrize => 'Lot'; + + @override + String get rafflePrizes => 'Lots'; + + @override + String get raffleActualRaffles => 'Tombola en cours'; + + @override + String get rafflePastRaffles => 'Tombola passés'; + + @override + String get raffleYourTickets => 'Tous vos tickets'; + + @override + String get raffleCreateMenu => 'Menu de Création'; + + @override + String get raffleNextRaffles => 'Prochaines tombolas'; + + @override + String get raffleNoTicket => 'Vous n\'avez pas de ticket'; + + @override + String get raffleSeeRaffleDetail => 'Voir lots/tickets'; + + @override + String get raffleActualPrize => 'Lots actuels'; + + @override + String get raffleMajorPrize => 'Lot Majeurs'; + + @override + String get raffleTakeTickets => 'Prendre vos tickets'; + + @override + String get raffleNoTicketBuyable => + 'Vous ne pouvez pas achetez de billets pour l\'instant'; + + @override + String get raffleNoCurrentPrize => 'Il n\'y a aucun lots actuellement'; + + @override + String get raffleModifTombola => + 'Vous pouvez modifiez vos tombolas ou en créer de nouvelles, toute décision doit ensuite être prise par les admins'; + + @override + String get raffleCreateYourRaffle => 'Votre menu de création de tombolas'; + + @override + String get rafflePossiblePrice => 'Prix possible'; + + @override + String get raffleInformation => 'Information et Statistiques'; + + @override + String get raffleAccounts => 'Comptes'; + + @override + String get raffleAdd => 'Ajouter'; + + @override + String get raffleUpdatedAmount => 'Montant mis à jour'; + + @override + String get raffleUpdatingError => 'Erreur lors de la mise à jour'; + + @override + String get raffleDeletedPrize => 'Lot supprimé'; + + @override + String get raffleDeletingError => 'Erreur lors de la suppression'; + + @override + String get raffleQuantity => 'Quantité'; + + @override + String get raffleClose => 'Fermer'; + + @override + String get raffleOpen => 'Ouvrir'; + + @override + String get raffleAddTypeTicketSimple => 'Ajouter'; + + @override + String get raffleAddingError => 'Erreur lors de l\'ajout'; + + @override + String get raffleEditTypeTicketSimple => 'Modifier'; + + @override + String get raffleFillField => 'Le champ ne peut pas être vide'; + + @override + String get raffleWaiting => 'Chargement'; + + @override + String get raffleEditingError => 'Erreur lors de la modification'; + + @override + String get raffleAddedTicket => 'Ticket ajouté'; + + @override + String get raffleEditedTicket => 'Ticket modifié'; + + @override + String get raffleAlreadyExistTicket => 'Le ticket existe déjà'; + + @override + String get raffleNumberExpected => 'Un entier est attendu'; + + @override + String get raffleDeletedTicket => 'Ticket supprimé'; + + @override + String get raffleAddPrize => 'Ajouter'; + + @override + String get raffleEditPrize => 'Modifier'; + + @override + String get raffleOpenRaffle => 'Ouvrir la tombola'; + + @override + String get raffleCloseRaffle => 'Fermer la tombola'; + + @override + String get raffleOpenRaffleDescription => + 'Vous allez ouvrir la tombola, les utilisateurs pourront acheter des tickets. Vous ne pourrez plus modifier la tombola. Êtes-vous sûr de vouloir continuer ?'; + + @override + String get raffleCloseRaffleDescription => + 'Vous allez fermer la tombola, les utilisateurs ne pourront plus acheter de tickets. Êtes-vous sûr de vouloir continuer ?'; + + @override + String get raffleNoCurrentRaffle => 'Il n\'y a aucune tombola en cours'; + + @override + String get raffleBoughtTicket => 'Ticket acheté'; + + @override + String get raffleDrawingError => 'Erreur lors du tirage'; + + @override + String get raffleInvalidPrice => 'Le prix doit être supérieur à 0'; + + @override + String get raffleMustBePositive => 'Le nombre doit être strictement positif'; + + @override + String get raffleDraw => 'Tirer'; + + @override + String get raffleDrawn => 'Tiré'; + + @override + String get raffleError => 'Erreur'; + + @override + String get raffleGathered => 'Récolté'; + + @override + String get raffleTickets => 'Tickets'; + + @override + String get raffleTicket => 'ticket'; + + @override + String get raffleWinner => 'Gagnant'; + + @override + String get raffleNoPrize => 'Aucun lot'; + + @override + String get raffleDeletePrize => 'Supprimer le lot'; + + @override + String get raffleDeletePrizeDescription => + 'Vous allez supprimer le lot, êtes-vous sûr de vouloir continuer ?'; + + @override + String get raffleDrawing => 'Tirage'; + + @override + String get raffleDrawingDescription => 'Tirer le gagnant du lot ?'; + + @override + String get raffleDeleteTicket => 'Supprimer le ticket'; + + @override + String get raffleDeleteTicketDescription => + 'Vous allez supprimer le ticket, êtes-vous sûr de vouloir continuer ?'; + + @override + String get raffleWinningTickets => 'Tickets gagnants'; + + @override + String get raffleNoWinningTicketYet => + 'Les tickets gagnants seront affichés ici'; + + @override + String get raffleName => 'Nom'; + + @override + String get raffleDescription => 'Description'; + + @override + String get raffleBuyThisTicket => 'Acheter ce ticket'; + + @override + String get raffleLockedRaffle => 'Tombola verrouillée'; + + @override + String get raffleUnavailableRaffle => 'Tombola indisponible'; + + @override + String get raffleNotEnoughMoney => 'Vous n\'avez pas assez d\'argent'; + + @override + String get raffleWinnable => 'gagnable'; + + @override + String get raffleNoDescription => 'Aucune description'; + + @override + String get raffleAmount => 'Solde'; + + @override + String get raffleLoading => 'Chargement'; + + @override + String get raffleTicketNumber => 'Nombre de ticket'; + + @override + String get rafflePrice => 'Prix'; + + @override + String get raffleEditRaffle => 'Modifier la tombola'; + + @override + String get raffleEdit => 'Modifier'; + + @override + String get raffleAddPackTicket => 'Ajouter un pack de ticket'; + + @override + String get recommendationRecommendation => 'Bons plans'; + + @override + String get recommendationTitle => 'Titre'; + + @override + String get recommendationLogo => 'Logo'; + + @override + String get recommendationCode => 'Code'; + + @override + String get recommendationSummary => 'Court résumé'; + + @override + String get recommendationDescription => 'Description'; + + @override + String get recommendationAdd => 'Ajouter'; + + @override + String get recommendationEdit => 'Modifier'; + + @override + String get recommendationDelete => 'Supprimer'; + + @override + String get recommendationAddImage => 'Veuillez ajouter une image'; + + @override + String get recommendationAddedRecommendation => 'Bon plan ajouté'; + + @override + String get recommendationEditedRecommendation => 'Bon plan modifié'; + + @override + String get recommendationDeleteRecommendationConfirmation => + 'Êtes-vous sûr de vouloir supprimer ce bon plan ?'; + + @override + String get recommendationDeleteRecommendation => 'Suppresion'; + + @override + String get recommendationDeletingRecommendationError => + 'Erreur lors de la suppression'; + + @override + String get recommendationDeletedRecommendation => 'Bon plan supprimé'; + + @override + String get recommendationIncorrectOrMissingFields => + 'Champs incorrects ou manquants'; + + @override + String get recommendationEditingError => 'Échec de la modification'; + + @override + String get recommendationAddingError => 'Échec de l\'ajout'; + + @override + String get recommendationCopiedCode => 'Code de réduction copié'; + + @override + String get seedLibraryAdd => 'Ajouter'; + + @override + String get seedLibraryAddedPlant => 'Plante ajoutée'; + + @override + String get seedLibraryAddedSpecies => 'Espèce ajoutée'; + + @override + String get seedLibraryAddingError => 'Erreur lors de l\'ajout'; + + @override + String get seedLibraryAddPlant => 'Déposer une plante'; + + @override + String get seedLibraryAddSpecies => 'Ajouter une espèce'; + + @override + String get seedLibraryAll => 'Toutes'; + + @override + String get seedLibraryAncestor => 'Ancêtre'; + + @override + String get seedLibraryAround => 'environ'; + + @override + String get seedLibraryAutumn => 'Automne'; + + @override + String get seedLibraryBorrowedPlant => 'Plante empruntée'; + + @override + String get seedLibraryBorrowingDate => 'Date d\'emprunt :'; + + @override + String get seedLibraryBorrowPlant => 'Emprunter la plante'; + + @override + String get seedLibraryCard => 'Carte'; + + @override + String get seedLibraryChoosingAncestor => 'Veuillez choisir un ancêtre'; + + @override + String get seedLibraryChoosingSpecies => 'Veuillez choisir une espèce'; + + @override + String get seedLibraryChoosingSpeciesOrAncestor => + 'Veuillez choisir une espèce ou un ancêtre'; + + @override + String get seedLibraryContact => 'Contact :'; + + @override + String get seedLibraryDays => 'jours'; + + @override + String get seedLibraryDeadMsg => 'Voulez-vous déclarer la plante morte ?'; + + @override + String get seedLibraryDeadPlant => 'Plante morte'; + + @override + String get seedLibraryDeathDate => 'Date de mort'; + + @override + String get seedLibraryDeletedSpecies => 'Espèce supprimée'; + + @override + String get seedLibraryDeleteSpecies => 'Supprimer l\'espèce ?'; + + @override + String get seedLibraryDeleting => 'Suppression'; + + @override + String get seedLibraryDeletingError => 'Erreur lors de la suppression'; + + @override + String get seedLibraryDepositNotAvailable => + 'Le dépôt de plantes n\'est pas possible sans emprunter une plante au préalable'; + + @override + String get seedLibraryDescription => 'Description'; + + @override + String get seedLibraryDifficulty => 'Difficulté :'; + + @override + String get seedLibraryEdit => 'Modifier'; + + @override + String get seedLibraryEditedPlant => 'Plante modifiée'; + + @override + String get seedLibraryEditInformation => 'Modifier les informations'; + + @override + String get seedLibraryEditingError => 'Erreur lors de la modification'; + + @override + String get seedLibraryEditSpecies => 'Modifier l\'espèce'; + + @override + String get seedLibraryEmptyDifficultyError => + 'Veuillez choisir une difficulté'; + + @override + String get seedLibraryEmptyFieldError => 'Veuillez remplir tous les champs'; + + @override + String get seedLibraryEmptyTypeError => 'Veuillez choisir un type de plante'; + + @override + String get seedLibraryEndMonth => 'Mois de fin :'; + + @override + String get seedLibraryFacebookUrl => 'Lien Facebook'; + + @override + String get seedLibraryFilters => 'Filtres'; + + @override + String get seedLibraryForum => + 'Oskour maman j\'ai tué ma plante - Forum d\'aide'; + + @override + String get seedLibraryForumUrl => 'Lien Forum'; + + @override + String get seedLibraryHelpSheets => 'Fiches sur les plantes'; + + @override + String get seedLibraryInformation => 'Informations :'; + + @override + String get seedLibraryMaturationTime => 'Temps de maturation'; + + @override + String get seedLibraryMonthJan => 'Janvier'; + + @override + String get seedLibraryMonthFeb => 'Février'; + + @override + String get seedLibraryMonthMar => 'Mars'; + + @override + String get seedLibraryMonthApr => 'Avril'; + + @override + String get seedLibraryMonthMay => 'Mai'; + + @override + String get seedLibraryMonthJun => 'Juin'; + + @override + String get seedLibraryMonthJul => 'Juillet'; + + @override + String get seedLibraryMonthAug => 'Août'; + + @override + String get seedLibraryMonthSep => 'Septembre'; + + @override + String get seedLibraryMonthOct => 'Octobre'; + + @override + String get seedLibraryMonthNov => 'Novembre'; + + @override + String get seedLibraryMonthDec => 'Décembre'; + + @override + String get seedLibraryMyPlants => 'Mes plantes'; + + @override + String get seedLibraryName => 'Nom'; + + @override + String get seedLibraryNbSeedsRecommended => 'Nombre de graines recommandées'; + + @override + String get seedLibraryNbSeedsRecommendedError => + 'Veuillez entrer un nombre de graines recommandé supérieur à 0'; + + @override + String get seedLibraryNoDateError => 'Veuillez entrer une date'; + + @override + String get seedLibraryNoFilteredPlants => + 'Aucune plante ne correspond à votre recherche. Essayez d\'autres filtres.'; + + @override + String get seedLibraryNoMorePlant => 'Aucune plante n\'est disponible'; + + @override + String get seedLibraryNoPersonalPlants => + 'Vous n\'avez pas encore de plantes dans votre grainothèque. Vous pouvez en ajouter en allant dans les stocks.'; + + @override + String get seedLibraryNoSpecies => 'Aucune espèce trouvée'; + + @override + String get seedLibraryNoStockPlants => + 'Aucune plante disponible dans le stock'; + + @override + String get seedLibraryNotes => 'Notes'; + + @override + String get seedLibraryOk => 'OK'; + + @override + String get seedLibraryPlantationPeriod => 'Période de plantation :'; + + @override + String get seedLibraryPlantationType => 'Type de plantation :'; + + @override + String get seedLibraryPlantDetail => 'Détail de la plante'; + + @override + String get seedLibraryPlantingDate => 'Date de plantation'; + + @override + String get seedLibraryPlantingNow => 'Je la plante maintenant'; + + @override + String get seedLibraryPrefix => 'Préfixe'; + + @override + String get seedLibraryPrefixError => 'Prefixe déjà utilisé'; + + @override + String get seedLibraryPrefixLengthError => + 'Le préfixe doit faire 3 caractères'; + + @override + String get seedLibraryPropagationMethod => 'Méthode de propagation :'; + + @override + String get seedLibraryReference => 'Référence :'; + + @override + String get seedLibraryRemovedPlant => 'Plante supprimée'; + + @override + String get seedLibraryRemovingError => 'Erreur lors de la suppression'; + + @override + String get seedLibraryResearch => 'Recherche'; + + @override + String get seedLibrarySaveChanges => 'Sauvegarder les modifications'; + + @override + String get seedLibrarySeason => 'Saison :'; + + @override + String get seedLibrarySeed => 'Graine'; + + @override + String get seedLibrarySeeds => 'graines'; + + @override + String get seedLibrarySeedDeposit => 'Dépôt de plantes'; + + @override + String get seedLibrarySeedLibrary => 'Grainothèque'; + + @override + String get seedLibrarySeedQuantitySimple => 'Quantité de graines'; + + @override + String get seedLibrarySeedQuantity => 'Quantité de graines :'; + + @override + String get seedLibraryShowDeadPlants => 'Afficher les plantes mortes'; + + @override + String get seedLibrarySpecies => 'Espèce :'; + + @override + String get seedLibrarySpeciesHelp => 'Aide sur l\'espèce'; + + @override + String get seedLibrarySpeciesPlural => 'Espèces'; + + @override + String get seedLibrarySpeciesSimple => 'Espèce'; + + @override + String get seedLibrarySpeciesType => 'Type d\'espèce :'; + + @override + String get seedLibrarySpring => 'Printemps'; + + @override + String get seedLibraryStartMonth => 'Mois de début :'; + + @override + String get seedLibraryStock => 'Stock disponible'; + + @override + String get seedLibrarySummer => 'Été'; + + @override + String get seedLibraryStocks => 'Stocks'; + + @override + String get seedLibraryTimeUntilMaturation => 'Temps avant maturation :'; + + @override + String get seedLibraryType => 'Type :'; + + @override + String get seedLibraryUnableToOpen => 'Impossible d\'ouvrir le lien'; + + @override + String get seedLibraryUpdate => 'Modifier'; + + @override + String get seedLibraryUpdatedInformation => 'Informations modifiées'; + + @override + String get seedLibraryUpdatedSpecies => 'Espèce modifiée'; + + @override + String get seedLibraryUpdatedPlant => 'Plante modifiée'; + + @override + String get seedLibraryUpdatingError => 'Erreur lors de la modification'; + + @override + String get seedLibraryWinter => 'Hiver'; + + @override + String get seedLibraryWriteReference => + 'Veuillez écrire la référence suivante : '; + + @override + String get settingsAccount => 'Compte'; + + @override + String get settingsAddProfilePicture => 'Ajouter une photo'; + + @override + String get settingsAdmin => 'Administrateur'; + + @override + String get settingsAskHelp => 'Demander de l\'aide'; + + @override + String get settingsAssociation => 'Association'; + + @override + String get settingsBirthday => 'Date de naissance'; + + @override + String get settingsBugs => 'Bugs'; + + @override + String get settingsChangePassword => 'Changer de mot de passe'; + + @override + String get settingsChangingPassword => + 'Voulez-vous vraiment changer votre mot de passe ?'; + + @override + String get settingsConfirmPassword => 'Confirmer le mot de passe'; + + @override + String get settingsCopied => 'Copié !'; + + @override + String get settingsDarkMode => 'Mode sombre'; + + @override + String get settingsDarkModeOff => 'Désactivé'; + + @override + String get settingsDeleteLogs => 'Supprimer les logs ?'; + + @override + String get settingsDeleteNotificationLogs => + 'Supprimer les logs des notifications ?'; + + @override + String get settingsDetelePersonalData => 'Supprimer mes données personnelles'; + + @override + String get settingsDetelePersonalDataDesc => + 'Cette action notifie l\'administrateur que vous souhaitez supprimer vos données personnelles.'; + + @override + String get settingsDeleting => 'Suppresion'; + + @override + String get settingsEdit => 'Modifier'; + + @override + String get settingsEditAccount => 'Modifier le compte'; + + @override + String get settingsEditPassword => 'Modifier le mot de passe'; + + @override + String get settingsEmail => 'Email'; + + @override + String get settingsEmptyField => 'Ce champ ne peut pas être vide'; + + @override + String get settingsErrorProfilePicture => + 'Erreur lors de la modification de la photo de profil'; + + @override + String get settingsErrorSendingDemand => + 'Erreur lors de l\'envoi de la demande'; + + @override + String get settingsEventsIcal => 'Lien Ical des événements'; + + @override + String get settingsExpectingDate => 'Date de naissance attendue'; + + @override + String get settingsFirstname => 'Prénom'; + + @override + String get settingsFloor => 'Étage'; + + @override + String get settingsHelp => 'Aide'; + + @override + String get settingsIcalCopied => 'Lien Ical copié !'; + + @override + String get settingsLanguage => 'Langue'; + + @override + String get settingsLanguageFr => 'Français'; + + @override + String get settingsLogs => 'Logs'; + + @override + String get settingsModules => 'Modules'; + + @override + String get settingsMyIcs => 'Mon lien Ical'; + + @override + String get settingsName => 'Nom'; + + @override + String get settingsNewPassword => 'Nouveau mot de passe'; + + @override + String get settingsNickname => 'Surnom'; + + @override + String get settingsNotifications => 'Notifications'; + + @override + String get settingsOldPassword => 'Ancien mot de passe'; + + @override + String get settingsPasswordChanged => 'Mot de passe changé'; + + @override + String get settingsPasswordsNotMatch => + 'Les mots de passe ne correspondent pas'; + + @override + String get settingsPersonalData => 'Données personnelles'; + + @override + String get settingsPersonalisation => 'Personnalisation'; + + @override + String get settingsPhone => 'Téléphone'; + + @override + String get settingsProfilePicture => 'Photo de profil'; + + @override + String get settingsPromo => 'Promotion'; + + @override + String get settingsRepportBug => 'Signaler un bug'; + + @override + String get settingsSave => 'Enregistrer'; + + @override + String get settingsSecurity => 'Sécurité'; + + @override + String get settingsSendedDemand => 'Demande envoyée'; + + @override + String get settingsSettings => 'Paramètres'; + + @override + String get settingsTooHeavyProfilePicture => + 'L\'image est trop lourde (max 4Mo)'; + + @override + String get settingsUpdatedProfile => 'Profil modifié'; + + @override + String get settingsUpdatedProfilePicture => 'Photo de profil modifiée'; + + @override + String get settingsUpdateNotification => 'Mettre à jour les notifications'; + + @override + String get settingsUpdatingError => + 'Erreur lors de la modification du profil'; + + @override + String get settingsVersion => 'Version'; + + @override + String get settingsPasswordStrength => 'Force du mot de passe'; + + @override + String get settingsPasswordStrengthVeryWeak => 'Très faible'; + + @override + String get settingsPasswordStrengthWeak => 'Faible'; + + @override + String get settingsPasswordStrengthMedium => 'Moyen'; + + @override + String get settingsPasswordStrengthStrong => 'Fort'; + + @override + String get settingsPasswordStrengthVeryStrong => 'Très fort'; + + @override + String get voteAdd => 'Ajouter'; + + @override + String get voteAddMember => 'Ajouter un membre'; + + @override + String get voteAddedPretendance => 'Liste ajoutée'; + + @override + String get voteAddedSection => 'Section ajoutée'; + + @override + String get voteAddingError => 'Erreur lors de l\'ajout'; + + @override + String get voteAddPretendance => 'Ajouter une liste'; + + @override + String get voteAddSection => 'Ajouter une section'; + + @override + String get voteAll => 'Tous'; + + @override + String get voteAlreadyAddedMember => 'Membre déjà ajouté'; + + @override + String get voteAlreadyVoted => 'Vote enregistré'; + + @override + String get voteChooseList => 'Choisir une liste'; + + @override + String get voteClear => 'Réinitialiser'; + + @override + String get voteClearVotes => 'Réinitialiser les votes'; + + @override + String get voteClosedVote => 'Votes clos'; + + @override + String get voteCloseVote => 'Fermer les votes'; + + @override + String get voteConfirmVote => 'Confirmer le vote'; + + @override + String get voteCountVote => 'Dépouiller les votes'; + + @override + String get voteDeletedAll => 'Tout supprimé'; + + @override + String get voteDeletedPipo => 'Listes pipos supprimées'; + + @override + String get voteDeletedSection => 'Section supprimée'; + + @override + String get voteDeleteAll => 'Supprimer tout'; + + @override + String get voteDeleteAllDescription => + 'Voulez-vous vraiment supprimer tout ?'; + + @override + String get voteDeletePipo => 'Supprimer les listes pipos'; + + @override + String get voteDeletePipoDescription => + 'Voulez-vous vraiment supprimer les listes pipos ?'; + + @override + String get voteDeletePretendance => 'Supprimer la liste'; + + @override + String get voteDeletePretendanceDesc => + 'Voulez-vous vraiment supprimer cette liste ?'; + + @override + String get voteDeleteSection => 'Supprimer la section'; + + @override + String get voteDeleteSectionDescription => + 'Voulez-vous vraiment supprimer cette section ?'; + + @override + String get voteDeletingError => 'Erreur lors de la suppression'; + + @override + String get voteDescription => 'Description'; + + @override + String get voteEdit => 'Modifier'; + + @override + String get voteEditedPretendance => 'Liste modifiée'; + + @override + String get voteEditedSection => 'Section modifiée'; + + @override + String get voteEditingError => 'Erreur lors de la modification'; + + @override + String get voteErrorClosingVotes => 'Erreur lors de la fermeture des votes'; + + @override + String get voteErrorCountingVotes => 'Erreur lors du dépouillement des votes'; + + @override + String get voteErrorResetingVotes => + 'Erreur lors de la réinitialisation des votes'; + + @override + String get voteErrorOpeningVotes => 'Erreur lors de l\'ouverture des votes'; + + @override + String get voteIncorrectOrMissingFields => 'Champs incorrects ou manquants'; + + @override + String get voteMembers => 'Membres'; + + @override + String get voteName => 'Nom'; + + @override + String get voteNoPretendanceList => 'Aucune liste de prétendance'; + + @override + String get voteNoSection => 'Aucune section'; + + @override + String get voteCanNotVote => 'Vous ne pouvez pas voter'; + + @override + String get voteNoSectionList => 'Aucune section'; + + @override + String get voteNotOpenedVote => 'Vote non ouvert'; + + @override + String get voteOnGoingCount => 'Dépouillement en cours'; + + @override + String get voteOpenVote => 'Ouvrir les votes'; + + @override + String get votePipo => 'Pipo'; + + @override + String get votePretendance => 'Listes'; + + @override + String get votePretendanceDeleted => 'Prétendance supprimée'; + + @override + String get votePretendanceNotDeleted => 'Erreur lors de la suppression'; + + @override + String get voteProgram => 'Programme'; + + @override + String get votePublish => 'Publier'; + + @override + String get votePublishVoteDescription => + 'Voulez-vous vraiment publier les votes ?'; + + @override + String get voteResetedVotes => 'Votes réinitialisés'; + + @override + String get voteResetVote => 'Réinitialiser les votes'; + + @override + String get voteResetVoteDescription => 'Que voulez-vous faire ?'; + + @override + String get voteRole => 'Rôle'; + + @override + String get voteSectionDescription => 'Description de la section'; + + @override + String get voteSection => 'Section'; + + @override + String get voteSectionName => 'Nom de la section'; + + @override + String get voteSeeMore => 'Voir plus'; + + @override + String get voteSelected => 'Sélectionné'; + + @override + String get voteShowVotes => 'Voir les votes'; + + @override + String get voteVote => 'Vote'; + + @override + String get voteVoteError => 'Erreur lors de l\'enregistrement du vote'; + + @override + String get voteVoteFor => 'Voter pour '; + + @override + String get voteVoteNotStarted => 'Vote non ouvert'; + + @override + String get voteVoters => 'Groupes votants'; + + @override + String get voteVoteSuccess => 'Vote enregistré'; + + @override + String get voteVotes => 'Voix'; + + @override + String get voteVotesClosed => 'Votes clos'; + + @override + String get voteVotesCounted => 'Votes dépouillés'; + + @override + String get voteVotesOpened => 'Votes ouverts'; + + @override + String get voteWarning => 'Attention'; + + @override + String get voteWarningMessage => + 'La sélection ne sera pas sauvegardée.\nVoulez-vous continuer ?'; +} diff --git a/lib/phonebook/ui/pages/association_creation_page/association_creation_page.dart b/lib/phonebook/ui/pages/association_creation_page/association_creation_page.dart index 9da39dafdc..42dbd2b9ed 100644 --- a/lib/phonebook/ui/pages/association_creation_page/association_creation_page.dart +++ b/lib/phonebook/ui/pages/association_creation_page/association_creation_page.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/tools/constants.dart'; import 'package:titan/phonebook/class/association.dart'; import 'package:titan/phonebook/providers/association_kind_provider.dart'; import 'package:titan/phonebook/providers/association_list_provider.dart'; @@ -17,6 +16,7 @@ import 'package:titan/tools/ui/builders/waiting_button.dart'; import 'package:titan/tools/ui/layouts/add_edit_button_layout.dart'; import 'package:titan/tools/ui/widgets/text_entry.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class AssociationCreationPage extends HookConsumerWidget { final scrollKey = GlobalKey(); @@ -68,13 +68,13 @@ class AssociationCreationPage extends HookConsumerWidget { Container(margin: const EdgeInsets.symmetric(vertical: 10)), TextEntry( controller: name, - label: AdminTextConstants.name, + label: AppLocalizations.of(context)!.adminName, canBeEmpty: false, ), const SizedBox(height: 30), TextEntry( controller: description, - label: AdminTextConstants.description, + label: AppLocalizations.of(context)!.adminDescription, canBeEmpty: true, ), const SizedBox(height: 50), @@ -134,13 +134,13 @@ class AssociationCreationPage extends HookConsumerWidget { } else { displayToastWithContext( TypeMsg.error, - AdminTextConstants.addingError, + AppLocalizations.of(context)!.adminAddingError, ); } }); }, - child: const Text( - AdminTextConstants.add, + child: Text( + AppLocalizations.of(context)!.adminAdd, style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, diff --git a/lib/phonebook/ui/pages/association_creation_page/text_entry.dart b/lib/phonebook/ui/pages/association_creation_page/text_entry.dart index f3d011e7a4..f594c0f80d 100644 --- a/lib/phonebook/ui/pages/association_creation_page/text_entry.dart +++ b/lib/phonebook/ui/pages/association_creation_page/text_entry.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:titan/admin/tools/constants.dart'; import 'package:titan/tools/constants.dart'; +import 'package:titan/l10n/app_localizations.dart'; class AddAssociationTextEntry extends StatelessWidget { final TextEditingController controller; @@ -45,7 +45,7 @@ class AddAssociationTextEntry extends StatelessWidget { ? null : (value) { if (value == null || value.isEmpty) { - return AdminTextConstants.emptyFieldError; + return AppLocalizations.of(context)!.adminEmptyFieldError; } return null; }, diff --git a/pubspec.yaml b/pubspec.yaml index 05a55334ff..129a18faeb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -108,3 +108,4 @@ flutter: - assets/images/helloasso.svg uses-material-design: true + generate: true diff --git a/test/amap/amap_test.dart b/test/amap/amap_test.dart index ea5678bf13..44fa94375e 100644 --- a/test/amap/amap_test.dart +++ b/test/amap/amap_test.dart @@ -15,6 +15,7 @@ import 'package:titan/amap/repositories/product_repository.dart'; import 'package:titan/amap/tools/constants.dart'; import 'package:titan/amap/tools/functions.dart'; import 'package:titan/user/class/simple_users.dart'; +import 'package:titan/l10n/app_localizations.dart'; class MockAmapUserRespository extends Mock implements AmapUserRepository {} @@ -431,11 +432,11 @@ void main() { test('Should return the correct string', () async { expect( uiCollectionSlotToString(CollectionSlot.midDay), - AMAPTextConstants.midDay, + AppLocalizations.of(context)!.amapMidDay, ); expect( uiCollectionSlotToString(CollectionSlot.evening), - AMAPTextConstants.evening, + AppLocalizations.of(context)!.amapEvening, ); }); test('Should return a string', () async { From ee0feb2e65aa21f5ab7284f4e34c15d4bac566d5 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 2 Jul 2025 22:21:57 +0200 Subject: [PATCH 033/473] module cinema --- .../ui/pages/admin_page/admin_page.dart | 6 +-- .../ui/pages/detail_page/detail_page.dart | 4 +- lib/cinema/ui/pages/main_page/main_page.dart | 14 +++---- .../ui/pages/main_page/session_card.dart | 4 +- .../pages/session_pages/add_edit_session.dart | 40 +++++++++---------- 5 files changed, 34 insertions(+), 34 deletions(-) diff --git a/lib/cinema/ui/pages/admin_page/admin_page.dart b/lib/cinema/ui/pages/admin_page/admin_page.dart index e32adf28e7..ece68e808b 100644 --- a/lib/cinema/ui/pages/admin_page/admin_page.dart +++ b/lib/cinema/ui/pages/admin_page/admin_page.dart @@ -5,13 +5,13 @@ import 'package:titan/cinema/class/session.dart'; import 'package:titan/cinema/providers/session_list_provider.dart'; import 'package:titan/cinema/providers/session_provider.dart'; import 'package:titan/cinema/router.dart'; -import 'package:titan/cinema/tools/constants.dart'; import 'package:titan/cinema/ui/cinema.dart'; import 'package:titan/cinema/ui/pages/admin_page/admin_session_card.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/card_layout.dart'; import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class AdminPage extends HookConsumerWidget { const AdminPage({super.key}); @@ -60,8 +60,8 @@ class AdminPage extends HookConsumerWidget { context: context, builder: (context) { return CustomDialogBox( - title: CinemaTextConstants.deleting, - descriptions: CinemaTextConstants.deleteSession, + title: AppLocalizations.of(context)!.cinemaDeleting, + descriptions: AppLocalizations.of(context)!.cinemaDeleteSession, onYes: () { sessionListNotifier.deleteSession(session); }, diff --git a/lib/cinema/ui/pages/detail_page/detail_page.dart b/lib/cinema/ui/pages/detail_page/detail_page.dart index b9d8976307..d9e959e64e 100644 --- a/lib/cinema/ui/pages/detail_page/detail_page.dart +++ b/lib/cinema/ui/pages/detail_page/detail_page.dart @@ -9,7 +9,6 @@ import 'package:titan/cinema/providers/cinema_topic_provider.dart'; import 'package:titan/cinema/providers/session_poster_map_provider.dart'; import 'package:titan/cinema/providers/session_poster_provider.dart'; import 'package:titan/cinema/providers/session_provider.dart'; -import 'package:titan/cinema/tools/constants.dart'; import 'package:titan/cinema/tools/functions.dart'; import 'package:titan/service/class/message.dart'; import 'package:titan/service/local_notification_service.dart'; @@ -17,6 +16,7 @@ import 'package:titan/tools/functions.dart'; import 'package:titan/tools/ui/builders/auto_loader_child.dart'; import 'package:titan/tools/ui/layouts/horizontal_list_view.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class DetailPage extends HookConsumerWidget { const DetailPage({super.key}); @@ -153,7 +153,7 @@ class DetailPage extends HookConsumerWidget { child: Text( session.overview != null ? session.overview! - : CinemaTextConstants.noOverview, + : AppLocalizations.of(context)!.cinemaNoOverview, textAlign: TextAlign.left, style: const TextStyle(fontSize: 15), ), diff --git a/lib/cinema/ui/pages/main_page/main_page.dart b/lib/cinema/ui/pages/main_page/main_page.dart index e0469e7680..f6fa825a1d 100644 --- a/lib/cinema/ui/pages/main_page/main_page.dart +++ b/lib/cinema/ui/pages/main_page/main_page.dart @@ -9,7 +9,6 @@ import 'package:titan/cinema/providers/session_list_provider.dart'; import 'package:titan/cinema/providers/session_poster_map_provider.dart'; import 'package:titan/cinema/providers/session_provider.dart'; import 'package:titan/cinema/router.dart'; -import 'package:titan/cinema/tools/constants.dart'; import 'package:titan/cinema/ui/cinema.dart'; import 'package:titan/cinema/ui/pages/main_page/session_card.dart'; import 'package:titan/navigation/providers/is_web_format_provider.dart'; @@ -17,6 +16,7 @@ import 'package:titan/tools/ui/widgets/admin_button.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class CinemaMainPage extends HookConsumerWidget { const CinemaMainPage({super.key}); @@ -63,9 +63,9 @@ class CinemaMainPage extends HookConsumerWidget { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - const Text( - CinemaTextConstants.incomingSession, - style: TextStyle( + Text( + AppLocalizations.of(context)!.cinemaIncomingSession, + style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.grey, @@ -88,12 +88,12 @@ class CinemaMainPage extends HookConsumerWidget { builder: (context, data) { data.sort((a, b) => a.start.compareTo(b.start)); if (data.isEmpty) { - return const SizedBox( + return SizedBox( height: 200, child: Center( child: Text( - CinemaTextConstants.noSession, - style: TextStyle( + AppLocalizations.of(context)!.cinemaNoSession, + style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black, diff --git a/lib/cinema/ui/pages/main_page/session_card.dart b/lib/cinema/ui/pages/main_page/session_card.dart index 4fb209e70a..87934b23d1 100644 --- a/lib/cinema/ui/pages/main_page/session_card.dart +++ b/lib/cinema/ui/pages/main_page/session_card.dart @@ -6,10 +6,10 @@ import 'package:titan/cinema/providers/cinema_topic_provider.dart'; import 'package:titan/cinema/providers/scroll_provider.dart'; import 'package:titan/cinema/providers/session_poster_map_provider.dart'; import 'package:titan/cinema/providers/session_poster_provider.dart'; -import 'package:titan/cinema/tools/constants.dart'; import 'package:titan/cinema/tools/functions.dart'; import 'package:titan/navigation/providers/is_web_format_provider.dart'; import 'package:titan/tools/ui/builders/auto_loader_child.dart'; +import 'package:titan/l10n/app_localizations.dart'; class SessionCard extends HookConsumerWidget { final Session session; @@ -164,7 +164,7 @@ class SessionCard extends HookConsumerWidget { const SizedBox(height: 10), Text( session.overview ?? - CinemaTextConstants.noOverview, + AppLocalizations.of(context)!.cinemaNoOverview, textAlign: TextAlign.center, style: const TextStyle(fontSize: 16), ), diff --git a/lib/cinema/ui/pages/session_pages/add_edit_session.dart b/lib/cinema/ui/pages/session_pages/add_edit_session.dart index 86e350a3ec..47b40a18fe 100644 --- a/lib/cinema/ui/pages/session_pages/add_edit_session.dart +++ b/lib/cinema/ui/pages/session_pages/add_edit_session.dart @@ -10,7 +10,6 @@ import 'package:titan/cinema/providers/session_poster_map_provider.dart'; import 'package:titan/cinema/providers/session_poster_provider.dart'; import 'package:titan/cinema/providers/session_provider.dart'; import 'package:titan/cinema/providers/the_movie_db_genre_provider.dart'; -import 'package:titan/cinema/tools/constants.dart'; import 'package:titan/cinema/tools/functions.dart'; import 'package:titan/cinema/ui/cinema.dart'; import 'package:titan/cinema/ui/pages/session_pages/tmdb_button.dart'; @@ -22,6 +21,7 @@ import 'package:titan/tools/ui/widgets/date_entry.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; import 'package:titan/tools/ui/widgets/text_entry.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class AddEditSessionPage extends HookConsumerWidget { const AddEditSessionPage({super.key}); @@ -74,8 +74,8 @@ class AddEditSessionPage extends HookConsumerWidget { children: [ AlignLeftText( isEdit - ? CinemaTextConstants.editSession - : CinemaTextConstants.addSession, + ? AppLocalizations.of(context)!.cinemaEditSession + : AppLocalizations.of(context)!.cinemaAddSession, color: Colors.grey, ), const SizedBox(height: 30), @@ -83,7 +83,7 @@ class AddEditSessionPage extends HookConsumerWidget { controller: tmdbUrl, cursorColor: Colors.black, decoration: InputDecoration( - labelText: CinemaTextConstants.importFromTMDB, + labelText: AppLocalizations.of(context)!.cinemaImportFromTMDB, labelStyle: const TextStyle( color: Colors.black, fontSize: 20, @@ -95,7 +95,7 @@ class AddEditSessionPage extends HookConsumerWidget { if (tmdbUrl.text.isEmpty) { displayToastWithContext( TypeMsg.error, - CinemaTextConstants.emptyUrl, + AppLocalizations.of(context)!.cinemaEmptyUrl, ); return; } @@ -135,7 +135,7 @@ class AddEditSessionPage extends HookConsumerWidget { } on FormatException catch (_) { displayToastWithContext( TypeMsg.error, - CinemaTextConstants.invalidUrl, + AppLocalizations.of(context)!.cinemaInvalidUrl, ); return; } @@ -183,10 +183,10 @@ class AddEditSessionPage extends HookConsumerWidget { ) : Image.memory(logo.value!, fit: BoxFit.cover), const SizedBox(height: 30), - TextEntry(label: CinemaTextConstants.name, controller: name), + TextEntry(label: AppLocalizations.of(context)!.cinemaName, controller: name), const SizedBox(height: 30), TextEntry( - label: CinemaTextConstants.posterUrl, + label: AppLocalizations.of(context)!.cinemaPosterUrl, controller: posterUrl, onChanged: (value) async { logo.value = await getFromUrl(posterUrl.text); @@ -196,30 +196,30 @@ class AddEditSessionPage extends HookConsumerWidget { const SizedBox(height: 30), DateEntry( onTap: () => getFullDate(context, start), - label: CinemaTextConstants.sessionDate, + label: AppLocalizations.of(context)!.cinemaSessionDate, controller: start, ), const SizedBox(height: 30), DateEntry( onTap: () => getOnlyHourDate(context, duration), - label: CinemaTextConstants.duration, + label: AppLocalizations.of(context)!.cinemaDuration, controller: duration, ), const SizedBox(height: 30), TextEntry( - label: CinemaTextConstants.genre, + label: AppLocalizations.of(context)!.cinemaGenre, controller: genre, canBeEmpty: true, ), const SizedBox(height: 30), TextEntry( - label: CinemaTextConstants.overview, + label: AppLocalizations.of(context)!.cinemaOverview, controller: overview, canBeEmpty: true, ), const SizedBox(height: 30), TextEntry( - label: CinemaTextConstants.tagline, + label: AppLocalizations.of(context)!.cinemaTagline, controller: tagline, canBeEmpty: true, ), @@ -234,7 +234,7 @@ class AddEditSessionPage extends HookConsumerWidget { if (logo.value == null && logoFile.value == null) { displayToastWithContext( TypeMsg.error, - CinemaTextConstants.noPoster, + AppLocalizations.of(context)!.cinemaNoPoster, ); return; } @@ -279,7 +279,7 @@ class AddEditSessionPage extends HookConsumerWidget { ); displayToastWithContext( TypeMsg.msg, - CinemaTextConstants.editedSession, + AppLocalizations.of(context)!.cinemaEditedSession, ); } else { sessionList.maybeWhen( @@ -302,19 +302,19 @@ class AddEditSessionPage extends HookConsumerWidget { ); displayToastWithContext( TypeMsg.msg, - CinemaTextConstants.addedSession, + AppLocalizations.of(context)!.cinemaAddedSession, ); } } else { if (isEdit) { displayToastWithContext( TypeMsg.error, - CinemaTextConstants.editingError, + AppLocalizations.of(context)!.cinemaEditingError, ); } else { displayToastWithContext( TypeMsg.error, - CinemaTextConstants.addingError, + AppLocalizations.of(context)!.cinemaAddingError, ); } } @@ -323,12 +323,12 @@ class AddEditSessionPage extends HookConsumerWidget { displayToast( context, TypeMsg.error, - CinemaTextConstants.incorrectOrMissingFields, + AppLocalizations.of(context)!.cinemaIncorrectOrMissingFields, ); } }, child: Text( - isEdit ? CinemaTextConstants.edit : CinemaTextConstants.add, + isEdit ? AppLocalizations.of(context)!.cinemaEdit : AppLocalizations.of(context)!.cinemaAdd, style: const TextStyle( color: Colors.white, fontSize: 25, From c9f417a5078cbe1c03f6151bd5914d2c8e584aeb Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 2 Jul 2025 22:23:01 +0200 Subject: [PATCH 034/473] drawer --- lib/navigation/ui/quit_dialog.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/navigation/ui/quit_dialog.dart b/lib/navigation/ui/quit_dialog.dart index b19e485bb3..f8ecb1619b 100644 --- a/lib/navigation/ui/quit_dialog.dart +++ b/lib/navigation/ui/quit_dialog.dart @@ -8,6 +8,7 @@ import 'package:titan/service/providers/firebase_token_expiration_provider.dart' import 'package:titan/service/providers/messages_provider.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; +import 'package:titan/l10n/app_localizations.dart'; class QuitDialog extends HookConsumerWidget { const QuitDialog({super.key}); @@ -26,8 +27,8 @@ class QuitDialog extends HookConsumerWidget { child: GestureDetector( onTap: () {}, child: CustomDialogBox( - descriptions: DrawerTextConstants.loginOut, - title: DrawerTextConstants.logOut, + descriptions: AppLocalizations.of(context)!.drawerLoginOut, + title: AppLocalizations.of(context)!.drawerLogOut, onYes: () { auth.deleteToken(); if (!kIsWeb) { @@ -35,7 +36,7 @@ class QuitDialog extends HookConsumerWidget { ref.watch(firebaseTokenExpirationProvider.notifier).reset(); } isCachingNotifier.set(false); - displayToast(context, TypeMsg.msg, DrawerTextConstants.logOut); + displayToast(context, TypeMsg.msg, AppLocalizations.of(context)!.drawerLogOut); displayQuitNotifier.setDisplay(false); }, onNo: () { From 36e14bc2f711721c1410c90e198b7b7669579439 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 2 Jul 2025 22:27:35 +0200 Subject: [PATCH 035/473] event --- lib/event/tools/constants.dart | 88 ++--------- lib/event/tools/functions.dart | 33 +++- lib/event/ui/components/event_ui.dart | 24 ++- lib/event/ui/pages/admin_page/admin_page.dart | 16 +- lib/event/ui/pages/admin_page/list_event.dart | 9 +- .../ui/pages/detail_page/detail_page.dart | 3 +- .../event_pages/add_edit_event_page.dart | 148 +++++++++++------- lib/event/ui/pages/main_page/main_page.dart | 5 +- 8 files changed, 163 insertions(+), 163 deletions(-) diff --git a/lib/event/tools/constants.dart b/lib/event/tools/constants.dart index cd62124273..9152f84640 100644 --- a/lib/event/tools/constants.dart +++ b/lib/event/tools/constants.dart @@ -1,79 +1,9 @@ -class EventTextConstants { - static const String add = "Ajouter"; - static const String addEvent = "Ajouter un événement"; - static const String addedEvent = "Événement ajouté"; - static const String addingError = "Erreur lors de l'ajout"; - static const String allDay = "Toute la journée"; - static const String confirm = "Confirmer"; - static const String confirmEvent = "Confirmer l'événement ?"; - static const String confirmation = "Confirmation"; - static const String confirmed = "Confirmé"; - static const String dates = "Dates"; - static const String decline = "Refuser"; - static const String declineEvent = "Refuser l'événement ?"; - static const String declined = "Refusé"; - static const String delete = "Supprimer"; - static const String deletedEvent = "Événement supprimé"; - static const String deleting = "Suppression"; - static const String deletingError = "Erreur lors de la suppression"; - static const String deletingEvent = "Supprimer l'événement ?"; - static const String description = "Description"; - static const String edit = "Modifier"; - static const String editEvent = "Modifier un événement"; - static const String editedEvent = "Événement modifié"; - static const String editingError = "Erreur lors de la modification"; - static const String endDate = "Date de fin"; - static const String endHour = "Heure de fin"; - static const String error = "Erreur"; - static const String eventList = "Liste des événements"; - static const String eventType = "Type d'événement"; - static const String every = "Tous les"; - static const String history = "Historique"; - static const String incorrectOrMissingFields = - "Certains champs sont incorrects ou manquants"; - static const String interval = "Intervalle"; - static const String invalidDates = - "La date de fin doit être après la date de début"; - static const String invalidIntervalError = - "Veuillez entrer un intervalle valide"; - static const String location = "Lieu"; - static const String myEvents = "Mes événements"; - static const String name = "Nom"; - static const String next = "Suivant"; - static const String no = "Non"; - static const String noCurrentEvent = "Aucun événement en cours"; - static const String noDateError = "Veuillez entrer une date"; - static const String noDaySelected = "Aucun jour sélectionné"; - static const String noDescriptionError = "Veuillez entrer une description"; - static const String noEvent = "Aucun événement"; - static const String noNameError = "Veuillez entrer un nom"; - static const String noOrganizerError = "Veuillez entrer un organisateur"; - static const String noPlaceError = "Veuillez entrer un lieu"; - static const String noPhoneRegistered = "Numéro non renseigné"; - static const String noRuleError = "Veuillez entrer une règle de récurrence"; - static const String organizer = "Organisateur"; - static const String other = "Autre"; - static const String pending = "En attente"; - static const String previous = "Précédent"; - static const String recurrence = "Récurrence"; - static const String recurrenceDays = "Jours de récurrence"; - static const String recurrenceEndDate = "Date de fin de la récurrence"; - static const String recurrenceRule = "Règle de récurrence"; - static const String room = "Salle"; - static const String startDate = "Date de début"; - static const String startHour = "Heure de début"; - static const String title = "Événements"; - static const String yes = "Oui"; - static const String eventEvery = "Toutes les"; - static const String weeks = "semaines"; - - static const List dayList = [ - 'Lundi', - 'Mardi', - 'Mercredi', - 'Jeudi', - 'Vendredi', - 'Samedi', - 'Dimanche', - ]; -} +const eventDayKeys = [ + 'eventDayMon', + 'eventDayTue', + 'eventDayWed', + 'eventDayThu', + 'eventDayFri', + 'eventDaySat', + 'eventDaySun', +]; diff --git a/lib/event/tools/functions.dart b/lib/event/tools/functions.dart index d74bfa5ff3..b0c1d65d2c 100644 --- a/lib/event/tools/functions.dart +++ b/lib/event/tools/functions.dart @@ -1,17 +1,18 @@ import 'dart:math'; +import 'package:flutter/material.dart'; import 'package:titan/event/class/event.dart'; -import 'package:titan/event/tools/constants.dart'; import 'package:titan/tools/functions.dart'; +import 'package:titan/l10n/app_localizations.dart'; -String decisionToString(Decision d) { +String decisionToString(Decision d, BuildContext context) { switch (d) { case Decision.approved: - return EventTextConstants.confirmed; + return AppLocalizations.of(context)!.eventConfirmed; case Decision.declined: - return EventTextConstants.declined; + return AppLocalizations.of(context)!.eventDeclined; case Decision.pending: - return EventTextConstants.pending; + return AppLocalizations.of(context)!.eventPending; } } @@ -80,3 +81,25 @@ String formatDelayToToday(DateTime date, DateTime now) { } return "Dans ${date.year - now.year} ans"; } + +String getLocalizedEventDay(BuildContext context, String key) { + final loc = AppLocalizations.of(context)!; + switch (key) { + case 'eventDayMon': + return loc.eventDayMon; + case 'eventDayTue': + return loc.eventDayTue; + case 'eventDayWed': + return loc.eventDayWed; + case 'eventDayThu': + return loc.eventDayThu; + case 'eventDayFri': + return loc.eventDayFri; + case 'eventDaySat': + return loc.eventDaySat; + case 'eventDaySun': + return loc.eventDaySun; + default: + return key; + } +} diff --git a/lib/event/ui/components/event_ui.dart b/lib/event/ui/components/event_ui.dart index f5f0aafc92..dacf93bf84 100644 --- a/lib/event/ui/components/event_ui.dart +++ b/lib/event/ui/components/event_ui.dart @@ -15,6 +15,7 @@ import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class EventUi extends ConsumerWidget { final Event event; @@ -175,7 +176,7 @@ class EventUi extends ConsumerWidget { Align( alignment: Alignment.center, child: Text( - decisionToString(event.decision), + decisionToString(event.decision, context), overflow: TextOverflow.ellipsis, style: TextStyle( color: textColor, @@ -209,7 +210,7 @@ class EventUi extends ConsumerWidget { : Colors.grey.shade300, child: Center( child: Text( - EventTextConstants.edit, + AppLocalizations.of(context)!.eventEdit, style: TextStyle( color: textColor, fontSize: 15, @@ -228,24 +229,31 @@ class EventUi extends ConsumerWidget { context: context, builder: (BuildContext context) { return CustomDialogBox( - descriptions: - EventTextConstants.deletingEvent, + descriptions: AppLocalizations.of( + context, + )!.eventDeletingEvent, onYes: () async { final value = await eventListNotifier .deleteEvent(event); if (value) { displayToastWithContext( TypeMsg.msg, - EventTextConstants.deletedEvent, + AppLocalizations.of( + context, + )!.eventDeletedEvent, ); } else { displayToastWithContext( TypeMsg.error, - EventTextConstants.deletingError, + AppLocalizations.of( + context, + )!.eventDeletingError, ); } }, - title: EventTextConstants.deleting, + title: AppLocalizations.of( + context, + )!.eventDeleting, ); }, ); @@ -265,7 +273,7 @@ class EventUi extends ConsumerWidget { ), child: Center( child: Text( - EventTextConstants.delete, + AppLocalizations.of(context)!.eventDelete, style: TextStyle( color: textColor, fontSize: 15, diff --git a/lib/event/ui/pages/admin_page/admin_page.dart b/lib/event/ui/pages/admin_page/admin_page.dart index e3d3e9df18..ba9eee208a 100644 --- a/lib/event/ui/pages/admin_page/admin_page.dart +++ b/lib/event/ui/pages/admin_page/admin_page.dart @@ -3,12 +3,12 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/event/ui/event.dart'; import 'package:titan/event/class/event.dart'; import 'package:titan/event/providers/event_list_provider.dart'; -import 'package:titan/event/tools/constants.dart'; import 'package:titan/event/ui/pages/admin_page/list_event.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; import 'package:titan/tools/ui/widgets/calendar.dart'; import 'package:syncfusion_flutter_calendar/calendar.dart'; +import 'package:titan/l10n/app_localizations.dart'; class AdminPage extends HookConsumerWidget { const AdminPage({super.key}); @@ -90,10 +90,10 @@ class AdminPage extends HookConsumerWidget { if (pendingEvents.isEmpty && confirmedEvents.isEmpty && canceledEvents.isEmpty) - const Center( + Center( child: Text( - EventTextConstants.noCurrentEvent, - style: TextStyle( + AppLocalizations.of(context)!.eventNoCurrentEvent, + style: const TextStyle( color: Colors.black, fontSize: 20, fontWeight: FontWeight.bold, @@ -101,20 +101,20 @@ class AdminPage extends HookConsumerWidget { ), ), ListEvent( - title: EventTextConstants.pending, + title: AppLocalizations.of(context)!.eventPending, events: pendingEvents, canToggle: false, ), ListEvent( - title: EventTextConstants.confirmed, + title: AppLocalizations.of(context)!.eventConfirmed, events: confirmedEvents, ), ListEvent( - title: EventTextConstants.declined, + title: AppLocalizations.of(context)!.eventDeclined, events: canceledEvents, ), ListEvent( - title: EventTextConstants.history, + title: AppLocalizations.of(context)!.eventHistory, events: confirmedEvents + canceledEvents + pendingEvents, isHistory: true, ), diff --git a/lib/event/ui/pages/admin_page/list_event.dart b/lib/event/ui/pages/admin_page/list_event.dart index 3a5cdffd9b..c12725f72c 100644 --- a/lib/event/ui/pages/admin_page/list_event.dart +++ b/lib/event/ui/pages/admin_page/list_event.dart @@ -15,6 +15,7 @@ import 'package:titan/tools/ui/widgets/align_left_text.dart'; import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; import 'package:titan/tools/ui/layouts/horizontal_list_view.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class ListEvent extends HookConsumerWidget { final List events; @@ -102,8 +103,8 @@ class ListEvent extends HookConsumerWidget { context: context, builder: (context) { return CustomDialogBox( - title: EventTextConstants.confirm, - descriptions: EventTextConstants.confirmEvent, + title: AppLocalizations.of(context)!.eventConfirm, + descriptions: AppLocalizations.of(context)!.eventConfirmEvent, onYes: () async { await tokenExpireWrapper(ref, () async { eventListNotifier @@ -126,8 +127,8 @@ class ListEvent extends HookConsumerWidget { context: context, builder: (context) { return CustomDialogBox( - title: EventTextConstants.decline, - descriptions: EventTextConstants.declineEvent, + title: AppLocalizations.of(context)!.eventDecline, + descriptions: AppLocalizations.of(context)!.eventDeclineEvent, onYes: () async { await tokenExpireWrapper(ref, () async { eventListNotifier diff --git a/lib/event/ui/pages/detail_page/detail_page.dart b/lib/event/ui/pages/detail_page/detail_page.dart index d49673500a..23d81a2e47 100644 --- a/lib/event/ui/pages/detail_page/detail_page.dart +++ b/lib/event/ui/pages/detail_page/detail_page.dart @@ -7,6 +7,7 @@ import 'package:titan/event/ui/event.dart'; import 'package:titan/event/ui/components/event_ui.dart'; import 'package:titan/tools/functions.dart'; import 'package:url_launcher/url_launcher.dart'; +import 'package:titan/l10n/app_localizations.dart'; class DetailPage extends HookConsumerWidget { final bool isAdmin; @@ -97,7 +98,7 @@ class DetailPage extends HookConsumerWidget { const SizedBox(height: 30), Text( event.applicant.phone ?? - EventTextConstants.noPhoneRegistered, + AppLocalizations.of(context)!.eventNoPhoneRegistered, style: const TextStyle(fontSize: 25), ), const SizedBox(height: 50), diff --git a/lib/event/ui/pages/event_pages/add_edit_event_page.dart b/lib/event/ui/pages/event_pages/add_edit_event_page.dart index 27c8055eb3..9c146350d1 100644 --- a/lib/event/ui/pages/event_pages/add_edit_event_page.dart +++ b/lib/event/ui/pages/event_pages/add_edit_event_page.dart @@ -23,6 +23,7 @@ import 'package:titan/tools/ui/widgets/text_entry.dart'; import 'package:titan/user/providers/user_provider.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:syncfusion_flutter_calendar/calendar.dart'; +import 'package:titan/l10n/app_localizations.dart'; class AddEditEventPage extends HookConsumerWidget { final eventTypeScrollKey = GlobalKey(); @@ -105,8 +106,8 @@ class AddEditEventPage extends HookConsumerWidget { const SizedBox(height: 40), AlignLeftText( isEdit - ? EventTextConstants.editEvent - : EventTextConstants.addEvent, + ? AppLocalizations.of(context)!.eventEditEvent + : AppLocalizations.of(context)!.eventAddEvent, padding: const EdgeInsets.symmetric(horizontal: 30), color: Colors.grey, ), @@ -141,16 +142,16 @@ class AddEditEventPage extends HookConsumerWidget { children: [ TextEntry( controller: name, - label: EventTextConstants.name, + label: AppLocalizations.of(context)!.eventName, ), const SizedBox(height: 30), TextEntry( controller: organizer, - label: EventTextConstants.organizer, + label: AppLocalizations.of(context)!.eventOrganizer, ), const SizedBox(height: 30), CheckBoxEntry( - title: EventTextConstants.recurrence, + title: AppLocalizations.of(context)!.eventRecurrence, valueNotifier: recurrent, onChanged: () { start.text = ""; @@ -160,7 +161,7 @@ class AddEditEventPage extends HookConsumerWidget { ), const SizedBox(height: 20), CheckBoxEntry( - title: EventTextConstants.allDay, + title: AppLocalizations.of(context)!.eventAllDay, valueNotifier: allDay, onChanged: () { start.text = ""; @@ -174,21 +175,33 @@ class AddEditEventPage extends HookConsumerWidget { children: [ Column( children: [ - const Text( - EventTextConstants.recurrenceDays, - style: TextStyle(color: Colors.black), + Text( + AppLocalizations.of( + context, + )!.eventRecurrenceDays, + style: const TextStyle( + color: Colors.black, + ), ), const SizedBox(height: 10), Column( - children: EventTextConstants.dayList - .map( - (e) => GestureDetector( - onTap: () { - selectedDaysNotifier.toggle( - EventTextConstants.dayList - .indexOf(e), + children: eventDayKeys + .asMap() + .entries + .map((entry) { + final index = entry.key; + final key = entry.value; + final localizedLabel = + getLocalizedEventDay( + context, + key, ); - }, + + return GestureDetector( + onTap: () => + selectedDaysNotifier.toggle( + index, + ), behavior: HitTestBehavior.opaque, child: Row( @@ -197,7 +210,7 @@ class AddEditEventPage extends HookConsumerWidget { .spaceBetween, children: [ Text( - e, + localizedLabel, style: TextStyle( color: Colors .grey @@ -209,35 +222,38 @@ class AddEditEventPage extends HookConsumerWidget { checkColor: Colors.white, activeColor: Colors.black, value: - selectedDays[EventTextConstants - .dayList - .indexOf(e)], - onChanged: (value) { - selectedDaysNotifier - .toggle( - EventTextConstants - .dayList - .indexOf(e), - ); - }, + selectedDays[index], + onChanged: (_) => + selectedDaysNotifier + .toggle(index), ), ], ), - ), - ) + ); + }) .toList(), ), const SizedBox(height: 20), - const Text( - EventTextConstants.interval, - style: TextStyle(color: Colors.black), + Text( + AppLocalizations.of( + context, + )!.eventInterval, + style: const TextStyle( + color: Colors.black, + ), ), const SizedBox(height: 10), TextEntry( - label: EventTextConstants.interval, + label: AppLocalizations.of( + context, + )!.eventInterval, controller: interval, - prefix: EventTextConstants.eventEvery, - suffix: EventTextConstants.weeks, + prefix: AppLocalizations.of( + context, + )!.eventEventEvery, + suffix: AppLocalizations.of( + context, + )!.eventWeeks, isInt: true, keyboardType: TextInputType.number, ), @@ -251,15 +267,18 @@ class AddEditEventPage extends HookConsumerWidget { start, ), controller: start, - label: - EventTextConstants.startHour, + label: AppLocalizations.of( + context, + )!.eventStartHour, ), const SizedBox(height: 30), DateEntry( onTap: () => getOnlyHourDate(context, end), controller: end, - label: EventTextConstants.endHour, + label: AppLocalizations.of( + context, + )!.eventEndHour, ), const SizedBox(height: 30), ], @@ -270,8 +289,9 @@ class AddEditEventPage extends HookConsumerWidget { recurrenceEndDate, ), controller: recurrenceEndDate, - label: EventTextConstants - .recurrenceEndDate, + label: AppLocalizations.of( + context, + )!.eventRecurrenceEndDate, ), ], ), @@ -284,7 +304,9 @@ class AddEditEventPage extends HookConsumerWidget { ? getOnlyDayDate(context, start) : getFullDate(context, start), controller: start, - label: EventTextConstants.startDate, + label: AppLocalizations.of( + context, + )!.eventStartDate, ), const SizedBox(height: 30), DateEntry( @@ -292,7 +314,9 @@ class AddEditEventPage extends HookConsumerWidget { ? getOnlyDayDate(context, end) : getFullDate(context, end), controller: end, - label: EventTextConstants.endDate, + label: AppLocalizations.of( + context, + )!.eventEndDate, ), ], ), @@ -309,7 +333,7 @@ class AddEditEventPage extends HookConsumerWidget { }, selected: isRoom.value, child: Text( - EventTextConstants.room, + AppLocalizations.of(context)!.eventRoom, style: TextStyle( color: isRoom.value ? Colors.white : Colors.black, fontWeight: FontWeight.bold, @@ -322,7 +346,7 @@ class AddEditEventPage extends HookConsumerWidget { }, selected: !isRoom.value, child: Text( - EventTextConstants.other, + AppLocalizations.of(context)!.eventOther, style: TextStyle( color: isRoom.value ? Colors.black : Colors.white, fontWeight: FontWeight.bold, @@ -369,7 +393,7 @@ class AddEditEventPage extends HookConsumerWidget { padding: const EdgeInsets.symmetric(horizontal: 30), child: TextEntry( controller: location, - label: EventTextConstants.location, + label: AppLocalizations.of(context)!.eventLocation, ), ), const SizedBox(height: 30), @@ -379,7 +403,7 @@ class AddEditEventPage extends HookConsumerWidget { children: [ TextEntry( controller: description, - label: EventTextConstants.description, + label: AppLocalizations.of(context)!.eventDescription, keyboardType: TextInputType.multiline, ), const SizedBox(height: 50), @@ -404,7 +428,9 @@ class AddEditEventPage extends HookConsumerWidget { displayToast( context, TypeMsg.error, - EventTextConstants.invalidDates, + AppLocalizations.of( + context, + )!.eventInvalidDates, ); } else if (recurrent.value && selectedDays @@ -413,7 +439,9 @@ class AddEditEventPage extends HookConsumerWidget { displayToast( context, TypeMsg.error, - EventTextConstants.noDaySelected, + AppLocalizations.of( + context, + )!.eventNoDaySelected, ); } else { await tokenExpireWrapper(ref, () async { @@ -491,24 +519,32 @@ class AddEditEventPage extends HookConsumerWidget { if (isEdit) { displayToastWithContext( TypeMsg.msg, - EventTextConstants.editedEvent, + AppLocalizations.of( + context, + )!.eventEditedEvent, ); } else { displayToastWithContext( TypeMsg.msg, - EventTextConstants.addedEvent, + AppLocalizations.of( + context, + )!.eventAddedEvent, ); } } else { if (isEdit) { displayToastWithContext( TypeMsg.error, - EventTextConstants.editingError, + AppLocalizations.of( + context, + )!.eventEditingError, ); } else { displayToastWithContext( TypeMsg.error, - EventTextConstants.addingError, + AppLocalizations.of( + context, + )!.eventAddingError, ); } } @@ -518,8 +554,8 @@ class AddEditEventPage extends HookConsumerWidget { }, child: Text( isEdit - ? EventTextConstants.edit - : EventTextConstants.add, + ? AppLocalizations.of(context)!.eventEdit + : AppLocalizations.of(context)!.eventAdd, style: const TextStyle( color: Colors.white, fontSize: 25, diff --git a/lib/event/ui/pages/main_page/main_page.dart b/lib/event/ui/pages/main_page/main_page.dart index 384ef3df81..76c6a7b18b 100644 --- a/lib/event/ui/pages/main_page/main_page.dart +++ b/lib/event/ui/pages/main_page/main_page.dart @@ -14,6 +14,7 @@ import 'package:titan/tools/ui/widgets/admin_button.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/card_layout.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class EventMainPage extends HookConsumerWidget { const EventMainPage({super.key}); @@ -44,8 +45,8 @@ class EventMainPage extends HookConsumerWidget { children: [ Text( eventList.isEmpty - ? EventTextConstants.noEvent - : EventTextConstants.myEvents, + ? AppLocalizations.of(context)!.eventNoEvent + : AppLocalizations.of(context)!.eventMyEvents, style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, From c68019ed3a631a15286a94db9eee10cba1ed6dc0 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 2 Jul 2025 22:31:52 +0200 Subject: [PATCH 036/473] home --- lib/home/tools/functions.dart | 27 +++++++++++++++++++++++++++ lib/home/ui/day_card.dart | 6 +++--- 2 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 lib/home/tools/functions.dart diff --git a/lib/home/tools/functions.dart b/lib/home/tools/functions.dart new file mode 100644 index 0000000000..f1613d1f72 --- /dev/null +++ b/lib/home/tools/functions.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:titan/l10n/app_localizations.dart'; + +String getShortDayLabel(BuildContext context, DateTime day) { + final loc = AppLocalizations.of(context)!; + final String weekday = DateFormat('E').format(day); // ex: Mon, Tue, etc. + + switch (weekday) { + case 'Mon': + return loc.homeTranslateDayShortMon; + case 'Tue': + return loc.homeTranslateDayShortTue; + case 'Wed': + return loc.homeTranslateDayShortWed; + case 'Thu': + return loc.homeTranslateDayShortThu; + case 'Fri': + return loc.homeTranslateDayShortFri; + case 'Sat': + return loc.homeTranslateDayShortSat; + case 'Sun': + return loc.homeTranslateDayShortSun; + default: + return weekday; + } +} diff --git a/lib/home/ui/day_card.dart b/lib/home/ui/day_card.dart index 85a0468ea1..13caed34b8 100644 --- a/lib/home/ui/day_card.dart +++ b/lib/home/ui/day_card.dart @@ -3,6 +3,8 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:intl/intl.dart'; import 'package:titan/home/providers/selected_day.dart'; import 'package:titan/home/tools/constants.dart'; +import 'package:titan/home/tools/functions.dart'; +import 'package:titan/l10n/app_localizations.dart'; class DayCard extends HookConsumerWidget { final bool isToday; @@ -81,9 +83,7 @@ class DayCard extends HookConsumerWidget { SizedBox( height: 15, child: Text( - HomeTextConstants.translateDayShort[DateFormat( - 'E', - ).format(day)]!, + getShortDayLabel(context, day), textAlign: TextAlign.center, style: TextStyle( color: isToday ? Colors.white : Colors.black, From 3c9249667f88d1b2a9aa5d8eb85693ccd9c729fb Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 2 Jul 2025 22:34:11 +0200 Subject: [PATCH 037/473] loan --- lib/loan/tools/functions.dart | 19 ++++++------- .../ui/pages/admin_page/delay_dialog.dart | 20 +++++++------- lib/loan/ui/pages/admin_page/item_card.dart | 5 ++-- lib/loan/ui/pages/admin_page/loan_card.dart | 11 ++++---- .../ui/pages/admin_page/loan_history.dart | 3 ++- .../ui/pages/admin_page/loaners_items.dart | 16 ++++++----- .../ui/pages/admin_page/on_going_loan.dart | 15 ++++++----- .../pages/detail_pages/item_card_in_loan.dart | 3 ++- .../item_group_page/add_edit_item_page.dart | 27 ++++++++++--------- .../loan_group_page/add_edit_button.dart | 19 ++++++------- .../loan_group_page/add_edit_loan_page.dart | 13 ++++----- .../loan_group_page/check_item_card.dart | 7 ++--- .../pages/loan_group_page/end_date_entry.dart | 3 ++- .../ui/pages/loan_group_page/item_bar.dart | 17 +++++++----- .../loan_group_page/number_selected_text.dart | 1 + .../loan_group_page/start_date_entry.dart | 3 ++- lib/loan/ui/pages/main_page/main_page.dart | 7 ++--- 17 files changed, 104 insertions(+), 85 deletions(-) diff --git a/lib/loan/tools/functions.dart b/lib/loan/tools/functions.dart index 6c51b476d5..e814f4b116 100644 --- a/lib/loan/tools/functions.dart +++ b/lib/loan/tools/functions.dart @@ -1,13 +1,14 @@ +import 'package:flutter/material.dart'; import 'package:titan/loan/class/item_quantity.dart'; -import 'package:titan/loan/tools/constants.dart'; +import 'package:titan/l10n/app_localizations.dart'; -String formatItems(List itemsQty) { +String formatItems(List itemsQty, BuildContext context) { if (itemsQty.length == 2) { - return "${itemsQty[0].quantity} ${itemsQty[0].itemSimple.name} ${LoanTextConstants.and} ${itemsQty[1].quantity} ${itemsQty[1].itemSimple.name}"; + return "${itemsQty[0].quantity} ${itemsQty[0].itemSimple.name} ${AppLocalizations.of(context)!.loanAnd} ${itemsQty[1].quantity} ${itemsQty[1].itemSimple.name}"; } else if (itemsQty.length == 3) { - return "${itemsQty[0].quantity} ${itemsQty[0].itemSimple.name}, ${itemsQty[1].quantity} ${itemsQty[1].itemSimple.name} ${LoanTextConstants.and} ${itemsQty[2].quantity} ${itemsQty[2].itemSimple.name}"; + return "${itemsQty[0].quantity} ${itemsQty[0].itemSimple.name}, ${itemsQty[1].quantity} ${itemsQty[1].itemSimple.name} ${AppLocalizations.of(context)!.loanAnd} ${itemsQty[2].quantity} ${itemsQty[2].itemSimple.name}"; } else if (itemsQty.length > 3) { - return "${itemsQty[0].quantity} ${itemsQty[0].itemSimple.name}, ${itemsQty[1].quantity} ${itemsQty[1].itemSimple.name} ${LoanTextConstants.and} ${itemsQty.length - 2} ${LoanTextConstants.others}"; + return "${itemsQty[0].quantity} ${itemsQty[0].itemSimple.name}, ${itemsQty[1].quantity} ${itemsQty[1].itemSimple.name} ${AppLocalizations.of(context)!.loanAnd} ${itemsQty.length - 2} ${AppLocalizations.of(context)!.loanOthers}"; } else if (itemsQty.length == 1) { return "${itemsQty[0].quantity} ${itemsQty[0].itemSimple.name}"; } else { @@ -15,12 +16,12 @@ String formatItems(List itemsQty) { } } -String formatNumberItems(int n) { +String formatNumberItems(int n, BuildContext context) { if (n >= 2) { - return "$n ${LoanTextConstants.itemsSelected}"; + return "$n ${AppLocalizations.of(context)!.loanItemsSelected}"; } else if (n == 1) { - return "$n ${LoanTextConstants.itemSelected} "; + return "$n ${AppLocalizations.of(context)!.loanItemSelected} "; } else { - return LoanTextConstants.noItemSelected; + return AppLocalizations.of(context)!.loanNoItemSelected; } } diff --git a/lib/loan/ui/pages/admin_page/delay_dialog.dart b/lib/loan/ui/pages/admin_page/delay_dialog.dart index bcc37de33c..3d54421d6a 100644 --- a/lib/loan/ui/pages/admin_page/delay_dialog.dart +++ b/lib/loan/ui/pages/admin_page/delay_dialog.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:titan/loan/tools/constants.dart'; import 'package:numberpicker/numberpicker.dart'; +import 'package:titan/l10n/app_localizations.dart'; class DelayDialog extends StatefulWidget { final void Function(int i) onYes; @@ -43,9 +43,9 @@ class IntegerExampleState extends State { child: Column( mainAxisSize: MainAxisSize.min, children: [ - const Text( - LoanTextConstants.delay, - style: TextStyle( + Text( + AppLocalizations.of(context)!.loanDelay, + style: const TextStyle( fontSize: 25, fontWeight: FontWeight.w800, color: Colors.black, @@ -70,9 +70,9 @@ class IntegerExampleState extends State { onPressed: () { Navigator.of(context).pop(); }, - child: const Text( - LoanTextConstants.cancel, - style: TextStyle( + child: Text( + AppLocalizations.of(context)!.loanCancel, + style: const TextStyle( fontSize: 18, fontWeight: FontWeight.w600, color: Colors.black, @@ -84,9 +84,9 @@ class IntegerExampleState extends State { Navigator.of(context).pop(); widget.onYes(_currentIntValue); }, - child: const Text( - LoanTextConstants.confirm, - style: TextStyle( + child: Text( + AppLocalizations.of(context)!.loanConfirm, + style: const TextStyle( fontSize: 18, fontWeight: FontWeight.w600, color: Colors.black, diff --git a/lib/loan/ui/pages/admin_page/item_card.dart b/lib/loan/ui/pages/admin_page/item_card.dart index b2ad78f726..186672092a 100644 --- a/lib/loan/ui/pages/admin_page/item_card.dart +++ b/lib/loan/ui/pages/admin_page/item_card.dart @@ -6,6 +6,7 @@ import 'package:titan/loan/tools/constants.dart'; import 'package:titan/tools/ui/layouts/card_button.dart'; import 'package:titan/tools/ui/layouts/card_layout.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; +import 'package:titan/l10n/app_localizations.dart'; class ItemCard extends StatelessWidget { final Item item; @@ -46,8 +47,8 @@ class ItemCard extends StatelessWidget { const SizedBox(height: 5), Text( availableQuantity > 0 - ? '$availableQuantity ${availableQuantity <= 1 ? LoanTextConstants.available : LoanTextConstants.availableMultiple}' - : LoanTextConstants.unavailable, + ? '$availableQuantity ${availableQuantity <= 1 ? AppLocalizations.of(context)!.loanAvailable : AppLocalizations.of(context)!.loanAvailableMultiple}' + : AppLocalizations.of(context)!.loanUnavailable, style: TextStyle( fontSize: 13, fontWeight: FontWeight.bold, diff --git a/lib/loan/ui/pages/admin_page/loan_card.dart b/lib/loan/ui/pages/admin_page/loan_card.dart index 1046fda50a..c4dfcb8d5b 100644 --- a/lib/loan/ui/pages/admin_page/loan_card.dart +++ b/lib/loan/ui/pages/admin_page/loan_card.dart @@ -8,6 +8,7 @@ import 'package:titan/tools/functions.dart'; import 'package:titan/tools/ui/layouts/card_button.dart'; import 'package:titan/tools/ui/layouts/card_layout.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; +import 'package:titan/l10n/app_localizations.dart'; class LoanCard extends StatelessWidget { final Loan loan; @@ -101,7 +102,7 @@ class LoanCard extends StatelessWidget { ), const SizedBox(height: 7), Text( - formatItems(loan.itemsQuantity), + formatItems(loan.itemsQuantity, context), style: TextStyle( fontSize: 13, fontWeight: FontWeight.bold, @@ -125,10 +126,10 @@ class LoanCard extends StatelessWidget { children: [ Text( loan.returned - ? LoanTextConstants.returned + ? AppLocalizations.of(context)!.loanReturned : shouldReturn - ? LoanTextConstants.toReturn - : LoanTextConstants.onGoing, + ? AppLocalizations.of(context)!.loanToReturn + : AppLocalizations.of(context)!.loanOnGoing, style: TextStyle( fontSize: 13, fontWeight: FontWeight.bold, @@ -141,7 +142,7 @@ class LoanCard extends StatelessWidget { (loan.returned) ? loan.returnedDate != null ? processDate(loan.returnedDate!) - : LoanTextConstants.noReturnedDate + : AppLocalizations.of(context)!.loanNoReturnedDate : processDate(loan.end), style: TextStyle( fontSize: 13, diff --git a/lib/loan/ui/pages/admin_page/loan_history.dart b/lib/loan/ui/pages/admin_page/loan_history.dart index 944612165d..853c664402 100644 --- a/lib/loan/ui/pages/admin_page/loan_history.dart +++ b/lib/loan/ui/pages/admin_page/loan_history.dart @@ -14,6 +14,7 @@ import 'package:titan/tools/ui/layouts/horizontal_list_view.dart'; import 'package:titan/tools/ui/widgets/loader.dart'; import 'package:titan/tools/ui/widgets/styled_search_bar.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class HistoryLoan extends HookConsumerWidget { const HistoryLoan({super.key}); @@ -49,7 +50,7 @@ class HistoryLoan extends HookConsumerWidget { return Column( children: [ StyledSearchBar( - label: LoanTextConstants.history, + label: AppLocalizations.of(context)!.loanHistory, onChanged: (value) async { if (value.isNotEmpty) { adminHistoryLoanListNotifier.setTData( diff --git a/lib/loan/ui/pages/admin_page/loaners_items.dart b/lib/loan/ui/pages/admin_page/loaners_items.dart index 54beb7dc43..1d5b22a23c 100644 --- a/lib/loan/ui/pages/admin_page/loaners_items.dart +++ b/lib/loan/ui/pages/admin_page/loaners_items.dart @@ -9,7 +9,6 @@ import 'package:titan/loan/providers/item_provider.dart'; import 'package:titan/loan/providers/loaner_provider.dart'; import 'package:titan/loan/providers/loaners_items_provider.dart'; import 'package:titan/loan/router.dart'; -import 'package:titan/loan/tools/constants.dart'; import 'package:titan/loan/ui/pages/admin_page/item_card.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/card_layout.dart'; @@ -19,6 +18,7 @@ import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/layouts/horizontal_list_view.dart'; import 'package:titan/tools/ui/widgets/styled_search_bar.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class LoanersItems extends HookConsumerWidget { const LoanersItems({super.key}); @@ -43,7 +43,7 @@ class LoanersItems extends HookConsumerWidget { final item = loanersItems[loaner]; if (item == null) { - return const Center(child: Text(LoanTextConstants.noItems)); + return Center(child: Text(AppLocalizations.of(context)!.loanNoItems)); } return AsyncChild( value: item, @@ -54,7 +54,7 @@ class LoanersItems extends HookConsumerWidget { return Column( children: [ StyledSearchBar( - label: LoanTextConstants.itemHandling, + label: AppLocalizations.of(context)!.loanItemHandling, onChanged: (value) async { if (value.isNotEmpty) { loanersItemsNotifier.setTData( @@ -97,7 +97,9 @@ class LoanersItems extends HookConsumerWidget { context: context, builder: (BuildContext context) { return CustomDialogBox( - descriptions: LoanTextConstants.deletingItem, + descriptions: AppLocalizations.of( + context, + )!.loanDeletingItem, onYes: () { tokenExpireWrapper(ref, () async { final value = await itemListNotifier.deleteItem( @@ -110,17 +112,17 @@ class LoanersItems extends HookConsumerWidget { }); displayToastWithContext( TypeMsg.msg, - LoanTextConstants.deletedItem, + AppLocalizations.of(context)!.loanDeletedItem, ); } else { displayToastWithContext( TypeMsg.error, - LoanTextConstants.deletingError, + AppLocalizations.of(context)!.loanDeletingError, ); } }); }, - title: LoanTextConstants.delete, + title: AppLocalizations.of(context)!.loanDelete, ); }, ); diff --git a/lib/loan/ui/pages/admin_page/on_going_loan.dart b/lib/loan/ui/pages/admin_page/on_going_loan.dart index 3da8eef0ad..98bd662727 100644 --- a/lib/loan/ui/pages/admin_page/on_going_loan.dart +++ b/lib/loan/ui/pages/admin_page/on_going_loan.dart @@ -26,6 +26,7 @@ import 'package:titan/tools/ui/layouts/horizontal_list_view.dart'; import 'package:titan/tools/ui/widgets/loader.dart'; import 'package:titan/tools/ui/widgets/styled_search_bar.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class OnGoingLoan extends HookConsumerWidget { const OnGoingLoan({super.key}); @@ -66,7 +67,7 @@ class OnGoingLoan extends HookConsumerWidget { children: [ StyledSearchBar( label: - '${data.isEmpty ? LoanTextConstants.none : data.length} ${LoanTextConstants.loan.toLowerCase()}${data.length > 1 ? 's' : ''} ${LoanTextConstants.onGoing.toLowerCase()}', + '${data.isEmpty ? AppLocalizations.of(context)!.loanNone : data.length} ${AppLocalizations.of(context)!.loanLoan.toLowerCase()}${data.length > 1 ? 's' : ''} ${AppLocalizations.of(context)!.loanOnGoing.toLowerCase()}', onChanged: (value) async { if (value.isNotEmpty) { adminLoanListNotifier.setTData( @@ -138,12 +139,12 @@ class OnGoingLoan extends HookConsumerWidget { ); displayToastWithContext( TypeMsg.msg, - LoanTextConstants.extendedLoan, + AppLocalizations.of(context)!.loanExtendedLoan, ); } else { displayToastWithContext( TypeMsg.error, - LoanTextConstants.extendingError, + AppLocalizations.of(context)!.loanExtendingError, ); } }); @@ -156,8 +157,8 @@ class OnGoingLoan extends HookConsumerWidget { await showDialog( context: context, builder: (context) => CustomDialogBox( - title: LoanTextConstants.returnLoan, - descriptions: LoanTextConstants.returnLoanDescription, + title: AppLocalizations.of(context)!.loanReturnLoan, + descriptions: AppLocalizations.of(context)!.loanReturnLoanDescription, onYes: () async { await tokenExpireWrapper(ref, () async { final loanItemsId = e.itemsQuantity @@ -188,12 +189,12 @@ class OnGoingLoan extends HookConsumerWidget { ); displayToastWithContext( TypeMsg.msg, - LoanTextConstants.returnedLoan, + AppLocalizations.of(context)!.loanReturnedLoan, ); } else { displayToastWithContext( TypeMsg.msg, - LoanTextConstants.returningError, + AppLocalizations.of(context)!.loanReturningError, ); } }); diff --git a/lib/loan/ui/pages/detail_pages/item_card_in_loan.dart b/lib/loan/ui/pages/detail_pages/item_card_in_loan.dart index e303cfef6f..3fdd978076 100644 --- a/lib/loan/ui/pages/detail_pages/item_card_in_loan.dart +++ b/lib/loan/ui/pages/detail_pages/item_card_in_loan.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:titan/loan/class/item_quantity.dart'; import 'package:titan/loan/tools/constants.dart'; import 'package:titan/tools/ui/layouts/card_layout.dart'; +import 'package:titan/l10n/app_localizations.dart'; class ItemCardInLoan extends StatelessWidget { final ItemQuantity itemQty; @@ -32,7 +33,7 @@ class ItemCardInLoan extends StatelessWidget { ), const SizedBox(height: 10), Text( - '${itemQty.quantity} ${itemQty.quantity <= 1 ? LoanTextConstants.borrowed : LoanTextConstants.borrowedMultiple}', + '${itemQty.quantity} ${itemQty.quantity <= 1 ? AppLocalizations.of(context)!.loanBorrowed : AppLocalizations.of(context)!.loanBorrowedMultiple}', style: TextStyle( fontSize: 13, fontWeight: FontWeight.bold, diff --git a/lib/loan/ui/pages/item_group_page/add_edit_item_page.dart b/lib/loan/ui/pages/item_group_page/add_edit_item_page.dart index 3ee3b53931..a48ec2ff49 100644 --- a/lib/loan/ui/pages/item_group_page/add_edit_item_page.dart +++ b/lib/loan/ui/pages/item_group_page/add_edit_item_page.dart @@ -15,6 +15,7 @@ import 'package:titan/tools/ui/widgets/align_left_text.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; import 'package:titan/tools/ui/widgets/text_entry.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class AddEditItemPage extends HookConsumerWidget { const AddEditItemPage({super.key}); @@ -52,8 +53,8 @@ class AddEditItemPage extends HookConsumerWidget { const SizedBox(height: 30), AlignLeftText( isEdit - ? LoanTextConstants.editItem - : LoanTextConstants.addObject, + ? AppLocalizations.of(context)!.loanEditItem + : AppLocalizations.of(context)!.loanAddObject, padding: const EdgeInsets.symmetric(horizontal: 30), color: Colors.grey, ), @@ -62,11 +63,11 @@ class AddEditItemPage extends HookConsumerWidget { child: Column( children: [ const SizedBox(height: 30), - TextEntry(label: LoanTextConstants.name, controller: name), + TextEntry(label: AppLocalizations.of(context)!.loanName, controller: name), const SizedBox(height: 30), TextEntry( keyboardType: TextInputType.number, - label: LoanTextConstants.quantity, + label: AppLocalizations.of(context)!.loanQuantity, isInt: true, controller: quantity, ), @@ -75,7 +76,7 @@ class AddEditItemPage extends HookConsumerWidget { keyboardType: TextInputType.number, controller: caution, isInt: true, - label: LoanTextConstants.caution, + label: AppLocalizations.of(context)!.loanCaution, suffix: '€', ), const SizedBox(height: 30), @@ -83,8 +84,8 @@ class AddEditItemPage extends HookConsumerWidget { keyboardType: TextInputType.number, controller: lendingDuration, isInt: true, - label: LoanTextConstants.lendingDuration, - suffix: LoanTextConstants.days, + label: AppLocalizations.of(context)!.loanLendingDuration, + suffix: AppLocalizations.of(context)!.loanDays, ), const SizedBox(height: 50), WaitingButton( @@ -126,24 +127,24 @@ class AddEditItemPage extends HookConsumerWidget { if (isEdit) { displayToastWithContext( TypeMsg.msg, - LoanTextConstants.updatedItem, + AppLocalizations.of(context)!.loanUpdatedItem, ); } else { displayToastWithContext( TypeMsg.msg, - LoanTextConstants.addedObject, + AppLocalizations.of(context)!.loanAddedObject, ); } } else { if (isEdit) { displayToastWithContext( TypeMsg.error, - LoanTextConstants.updatingError, + AppLocalizations.of(context)!.loanUpdatingError, ); } else { displayToastWithContext( TypeMsg.error, - LoanTextConstants.addingError, + AppLocalizations.of(context)!.loanAddingError, ); } } @@ -152,12 +153,12 @@ class AddEditItemPage extends HookConsumerWidget { displayToast( context, TypeMsg.error, - LoanTextConstants.incorrectOrMissingFields, + AppLocalizations.of(context)!.loanIncorrectOrMissingFields, ); } }, child: Text( - isEdit ? LoanTextConstants.edit : LoanTextConstants.add, + isEdit ? AppLocalizations.of(context)!.loanEdit : AppLocalizations.of(context)!.loanAdd, style: const TextStyle( color: Colors.white, fontSize: 25, diff --git a/lib/loan/ui/pages/loan_group_page/add_edit_button.dart b/lib/loan/ui/pages/loan_group_page/add_edit_button.dart index 09137c3fbc..c83c8fa4d5 100644 --- a/lib/loan/ui/pages/loan_group_page/add_edit_button.dart +++ b/lib/loan/ui/pages/loan_group_page/add_edit_button.dart @@ -18,6 +18,7 @@ import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/layouts/add_edit_button_layout.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class AddEditButton extends HookConsumerWidget { final TextEditingController note; @@ -55,10 +56,10 @@ class AddEditButton extends HookConsumerWidget { displayToast( context, TypeMsg.error, - LoanTextConstants.invalidDates, + AppLocalizations.of(context)!.loanInvalidDates, ); } else if (borrower.id.isEmpty) { - displayToast(context, TypeMsg.error, LoanTextConstants.noBorrower); + displayToast(context, TypeMsg.error, AppLocalizations.of(context)!.loanNoBorrower); } else { await items.when( data: (itemList) async { @@ -99,31 +100,31 @@ class AddEditButton extends HookConsumerWidget { if (isEdit) { displayToastWithContext( TypeMsg.msg, - LoanTextConstants.updatedLoan, + AppLocalizations.of(context)!.loanUpdatedLoan, ); } else { displayToastWithContext( TypeMsg.msg, - LoanTextConstants.addedLoan, + AppLocalizations.of(context)!.loanAddedLoan, ); } } else { if (isEdit) { displayToastWithContext( TypeMsg.error, - LoanTextConstants.updatingError, + AppLocalizations.of(context)!.loanUpdatingError, ); } else { displayToastWithContext( TypeMsg.error, - LoanTextConstants.addingError, + AppLocalizations.of(context)!.loanAddingError, ); } } } else { displayToastWithContext( TypeMsg.error, - LoanTextConstants.noItemSelected, + AppLocalizations.of(context)!.loanNoItemSelected, ); } }); @@ -135,7 +136,7 @@ class AddEditButton extends HookConsumerWidget { displayToast( context, TypeMsg.error, - LoanTextConstants.addingError, + AppLocalizations.of(context)!.loanAddingError, ); }, ); @@ -143,7 +144,7 @@ class AddEditButton extends HookConsumerWidget { }); }, child: Text( - isEdit ? LoanTextConstants.edit : LoanTextConstants.add, + isEdit ? AppLocalizations.of(context)!.loanEdit : AppLocalizations.of(context)!.loanAdd, style: const TextStyle( color: Colors.white, fontSize: 25, diff --git a/lib/loan/ui/pages/loan_group_page/add_edit_loan_page.dart b/lib/loan/ui/pages/loan_group_page/add_edit_loan_page.dart index fd7a0a1a06..f36a1429ab 100644 --- a/lib/loan/ui/pages/loan_group_page/add_edit_loan_page.dart +++ b/lib/loan/ui/pages/loan_group_page/add_edit_loan_page.dart @@ -21,6 +21,7 @@ import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/widgets/styled_search_bar.dart'; import 'package:titan/tools/ui/widgets/text_entry.dart'; import 'package:titan/user/providers/user_list_provider.dart'; +import 'package:titan/l10n/app_localizations.dart'; class AddEditLoanPage extends HookConsumerWidget { const AddEditLoanPage({super.key}); @@ -58,8 +59,8 @@ class AddEditLoanPage extends HookConsumerWidget { const SizedBox(height: 30), StyledSearchBar( label: isEdit - ? LoanTextConstants.editLoan - : LoanTextConstants.addLoan, + ? AppLocalizations.of(context)!.loanEditLoan + : AppLocalizations.of(context)!.loanAddLoan, onChanged: (value) async { if (value.isNotEmpty) { loanersItemsNotifier.setTData( @@ -81,7 +82,7 @@ class AddEditLoanPage extends HookConsumerWidget { const NumberSelectedText(), const SizedBox(height: 20), TextEntry( - label: LoanTextConstants.borrower, + label: AppLocalizations.of(context)!.loanBorrower, onChanged: (value) { tokenExpireWrapper(ref, () async { if (queryController.text.isNotEmpty) { @@ -104,13 +105,13 @@ class AddEditLoanPage extends HookConsumerWidget { const EndDateEntry(), const SizedBox(height: 30), TextEntry( - label: LoanTextConstants.note, + label: AppLocalizations.of(context)!.loanNote, controller: note, canBeEmpty: true, ), const SizedBox(height: 30), TextEntry( - label: LoanTextConstants.caution, + label: AppLocalizations.of(context)!.loanCaution, controller: caution, canBeEmpty: true, ), @@ -128,7 +129,7 @@ class AddEditLoanPage extends HookConsumerWidget { displayToast( context, TypeMsg.error, - LoanTextConstants.incorrectOrMissingFields, + AppLocalizations.of(context)!.loanIncorrectOrMissingFields, ); } }, diff --git a/lib/loan/ui/pages/loan_group_page/check_item_card.dart b/lib/loan/ui/pages/loan_group_page/check_item_card.dart index 10abb8cf46..9c5db9b739 100644 --- a/lib/loan/ui/pages/loan_group_page/check_item_card.dart +++ b/lib/loan/ui/pages/loan_group_page/check_item_card.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:titan/loan/class/item.dart'; import 'package:titan/loan/tools/constants.dart'; import 'package:titan/tools/ui/layouts/card_layout.dart'; +import 'package:titan/l10n/app_localizations.dart'; class CheckItemCard extends StatelessWidget { final Item item; @@ -40,8 +41,8 @@ class CheckItemCard extends StatelessWidget { const SizedBox(height: 5), Text( item.loanedQuantity < item.totalQuantity - ? '${item.totalQuantity - item.loanedQuantity} ${LoanTextConstants.available}' - : LoanTextConstants.unavailable, + ? '${item.totalQuantity - item.loanedQuantity} ${AppLocalizations.of(context)!.loanAvailable}' + : AppLocalizations.of(context)!.loanUnavailable, style: TextStyle( fontSize: 13, fontWeight: FontWeight.bold, @@ -62,7 +63,7 @@ class CheckItemCard extends StatelessWidget { ), const SizedBox(height: 5), AutoSizeText( - '${LoanTextConstants.duration} : ${item.suggestedLendingDuration.toInt()} ${LoanTextConstants.days}', + '${AppLocalizations.of(context)!.loanDuration} : ${item.suggestedLendingDuration.toInt()} ${AppLocalizations.of(context)!.loanDays}', maxLines: 1, style: TextStyle( fontSize: 13, diff --git a/lib/loan/ui/pages/loan_group_page/end_date_entry.dart b/lib/loan/ui/pages/loan_group_page/end_date_entry.dart index a18d9e3a8a..12245a1980 100644 --- a/lib/loan/ui/pages/loan_group_page/end_date_entry.dart +++ b/lib/loan/ui/pages/loan_group_page/end_date_entry.dart @@ -5,6 +5,7 @@ import 'package:titan/loan/providers/initial_date_provider.dart'; import 'package:titan/loan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/ui/widgets/date_entry.dart'; +import 'package:titan/l10n/app_localizations.dart'; class EndDateEntry extends HookConsumerWidget { const EndDateEntry({super.key}); @@ -22,7 +23,7 @@ class EndDateEntry extends HookConsumerWidget { initialDate: initialDate, ), controller: TextEditingController(text: end), - label: LoanTextConstants.endDate, + label: AppLocalizations.of(context)!.loanEndDate, ); } } diff --git a/lib/loan/ui/pages/loan_group_page/item_bar.dart b/lib/loan/ui/pages/loan_group_page/item_bar.dart index efd2e41cc3..9493423b1b 100644 --- a/lib/loan/ui/pages/loan_group_page/item_bar.dart +++ b/lib/loan/ui/pages/loan_group_page/item_bar.dart @@ -9,11 +9,11 @@ import 'package:titan/loan/providers/loaner_provider.dart'; import 'package:titan/loan/providers/loaners_items_provider.dart'; import 'package:titan/loan/providers/selected_items_provider.dart'; import 'package:titan/loan/providers/start_provider.dart'; -import 'package:titan/loan/tools/constants.dart'; import 'package:titan/loan/ui/pages/loan_group_page/check_item_card.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/horizontal_list_view.dart'; +import 'package:titan/l10n/app_localizations.dart'; class ItemBar extends HookConsumerWidget { final bool isEdit; @@ -36,12 +36,15 @@ class ItemBar extends HookConsumerWidget { loaderColor: ColorConstants.background2, builder: (context, data) { if (loanersItems[loaner] == null) { - return const SizedBox( + return SizedBox( height: 180, child: Center( child: Text( - LoanTextConstants.noItems, - style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500), + AppLocalizations.of(context)!.loanNoItems, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.w500, + ), ), ), ); @@ -51,12 +54,12 @@ class ItemBar extends HookConsumerWidget { loaderColor: ColorConstants.background2, builder: (context, itemList) { if (itemList.isEmpty) { - return const SizedBox( + return SizedBox( height: 180, child: Center( child: Text( - LoanTextConstants.noItems, - style: TextStyle( + AppLocalizations.of(context)!.loanNoItems, + style: const TextStyle( fontSize: 18, fontWeight: FontWeight.w500, ), diff --git a/lib/loan/ui/pages/loan_group_page/number_selected_text.dart b/lib/loan/ui/pages/loan_group_page/number_selected_text.dart index 94933f86ec..77a7ced90e 100644 --- a/lib/loan/ui/pages/loan_group_page/number_selected_text.dart +++ b/lib/loan/ui/pages/loan_group_page/number_selected_text.dart @@ -15,6 +15,7 @@ class NumberSelectedText extends HookConsumerWidget { 0, (previousValue, element) => previousValue + element, ), + context, ), ); } diff --git a/lib/loan/ui/pages/loan_group_page/start_date_entry.dart b/lib/loan/ui/pages/loan_group_page/start_date_entry.dart index 6adaf9b7b4..8aa654bd38 100644 --- a/lib/loan/ui/pages/loan_group_page/start_date_entry.dart +++ b/lib/loan/ui/pages/loan_group_page/start_date_entry.dart @@ -9,6 +9,7 @@ import 'package:titan/loan/providers/start_provider.dart'; import 'package:titan/loan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/ui/widgets/date_entry.dart'; +import 'package:titan/l10n/app_localizations.dart'; class StartDateEntry extends HookConsumerWidget { const StartDateEntry({super.key}); @@ -47,7 +48,7 @@ class StartDateEntry extends HookConsumerWidget { : now, firstDate: DateTime(now.year - 1, now.month, now.day), ), - label: LoanTextConstants.beginDate, + label: AppLocalizations.of(context)!.loanBeginDate, controller: TextEditingController(text: start), ); } diff --git a/lib/loan/ui/pages/main_page/main_page.dart b/lib/loan/ui/pages/main_page/main_page.dart index 544555a48d..d6b70ae74b 100644 --- a/lib/loan/ui/pages/main_page/main_page.dart +++ b/lib/loan/ui/pages/main_page/main_page.dart @@ -16,6 +16,7 @@ import 'package:titan/tools/ui/widgets/align_left_text.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; import 'package:titan/tools/ui/layouts/horizontal_list_view.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class LoanMainPage extends HookConsumerWidget { const LoanMainPage({super.key}); @@ -64,7 +65,7 @@ class LoanMainPage extends HookConsumerWidget { ? Column( children: [ AlignLeftText( - '${onGoingLoan.length} ${LoanTextConstants.loan.toLowerCase()}${onGoingLoan.length > 1 ? 's' : ''} ${LoanTextConstants.onGoing.toLowerCase()}', + '${onGoingLoan.length} ${AppLocalizations.of(context)!.loanLoan.toLowerCase()}${onGoingLoan.length > 1 ? 's' : ''} ${AppLocalizations.of(context)!.loanOnGoing.toLowerCase()}', padding: const EdgeInsets.symmetric( horizontal: 30.0, ), @@ -93,7 +94,7 @@ class LoanMainPage extends HookConsumerWidget { Expanded( child: Center( child: Text( - LoanTextConstants.noLoan, + AppLocalizations.of(context)!.loanNoLoan, style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, @@ -112,7 +113,7 @@ class LoanMainPage extends HookConsumerWidget { children: [ const SizedBox(height: 30), AlignLeftText( - '${returnedLoan.length} ${LoanTextConstants.loan.toLowerCase()}${returnedLoan.length > 1 ? 's' : ''} ${LoanTextConstants.returned.toLowerCase()}${returnedLoan.length > 1 ? 's' : ''}', + '${returnedLoan.length} ${AppLocalizations.of(context)!.loanLoan.toLowerCase()}${returnedLoan.length > 1 ? 's' : ''} ${AppLocalizations.of(context)!.loanReturned.toLowerCase()}${returnedLoan.length > 1 ? 's' : ''}', padding: const EdgeInsets.symmetric(horizontal: 30.0), color: Colors.grey, ), From 69692996577a36008b703b060e7cd1f5b2eb1736 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 2 Jul 2025 22:42:14 +0200 Subject: [PATCH 038/473] login --- lib/login/tools/constants.dart | 67 +------------------ lib/login/ui/app_sign_in.dart | 19 +++--- lib/login/ui/components/login_field.dart | 29 ++++---- .../create_account_page.dart | 45 +++++++------ .../ui/pages/forget_page/forget_page.dart | 24 +++---- .../recover_password_page.dart | 15 +++-- .../ui/pages/register_page/register_page.dart | 39 ++++++----- .../ui/pages/sign_in_page/sign_in_page.dart | 25 ++++--- lib/login/ui/web/left_panel.dart | 21 +++--- 9 files changed, 122 insertions(+), 162 deletions(-) diff --git a/lib/login/tools/constants.dart b/lib/login/tools/constants.dart index 97873f33c4..17dde12393 100644 --- a/lib/login/tools/constants.dart +++ b/lib/login/tools/constants.dart @@ -1,65 +1,2 @@ -class LoginTextConstants { - static const String accountActivated = 'Compte activé'; - static const String accountNotActivated = 'Compte non activé'; - static const String activationCode = 'Code d\'activation'; - static const String birthday = 'Date de naissance'; - static const String canBeEmpty = 'Ce champ peut être vide'; - static const String confirmPassword = 'Confirmer le mot de passe'; - static const String create = 'Créer'; - static const String createAccount = 'Créer un compte'; - static const String createAccountTitle = 'Créer un\ncompte'; - static const String email = 'Email'; - static const String emailEmpty = 'Veuillez entrer une adresse mail'; - static const String emailInvalid = - 'Veuillez entrer une adresse mail de centrale.\nSi vous n\'en possédez pas, veuillez contacter Éclair'; - static const String emailRegExp = - r'^[\w\-.]*@(((etu(-enise)?)\.)?ec-lyon\.fr|centraliens-lyon\.net)$'; - static const String emptyFieldError = 'Ce champ ne peut pas être vide'; - static const String endActivation = 'Finaliser l\'activation'; - static const String endResetPassword = 'Finaliser la \nréinitialisation'; - static const String errorResetPassword = 'Erreur lors de la réinitialisation'; - static const String expectingDate = 'Une date est attendue'; - static const String fillAllFields = 'Veuillez remplir tous les champs'; - static const String firstname = 'Prénom'; - static const String floor = 'Étage'; - static const String forgetPassword = 'Mot de passe\noublié'; - static const String forgotPassword = 'Mot de passe oublié ?'; - static const String invalidToken = 'Code d\'activation invalide'; - static const String loginFailed = 'Échec de la connexion'; - static const String mailSendingError = 'Erreur lors de la création du compte'; - static const String mustBeIntError = 'Ce champ doit être un entier'; - static const String name = 'Nom'; - static const String newPassword = 'Nouveau mot de passe'; - static const String password = 'Mot de passe'; - static const String passwordLengthError = - 'Le mot de passe doit faire au moins 6 caractères'; - static const String passwordUppercaseError = - 'Le mot de passe doit contenir au moins une majuscule'; - static const String passwordLowercaseError = - 'Le mot de passe doit contenir au moins une minucule'; - static const String passwordNumberError = - 'Le mot de passe doit contenir au moins un chiffre'; - static const String passwordSpecialCaracterError = - 'Le mot de passe doit contenir au moins un caractère spécial'; - static const String passwordMustMatch = - 'Les mots de passe doivent correspondre'; - static const String passwordStrengthVeryWeak = 'Très faible'; - static const String passwordStrengthWeak = 'Faible'; - static const String passwordStrengthMedium = 'Moyen'; - static const String passwordStrengthStrong = 'Fort'; - static const String passwordStrengthVeryStrong = 'Très fort'; - static const String phone = 'Téléphone'; - static const String promo = 'Promo entrante (ex : 2023)'; - static const String sendedMail = 'Mail de confirmation envoyé'; - static const String sendedResetMail = 'Mail de réinitialisation envoyé'; - static const String signIn = 'Se connecter'; - static const String register = 'S\'inscrire'; - static const String recievedMail = 'J\'ai reçu le mail'; - static const String recover = 'Réinitialiser'; - static const String resetedPassword = 'Mot de passe réinitialisé'; - static const String resetPasswordTitle = 'Réinitialiser\nle mot de \npasse'; - static const String nickname = 'Surnom'; - static const String welcomeBack = 'Bienvenue'; - - static const String appName = "MyECL"; -} +const String emailRegExp = + r'^[\w\-.]*@(((etu(-enise)?)\.)?ec-lyon\.fr|centraliens-lyon\.net)$'; diff --git a/lib/login/ui/app_sign_in.dart b/lib/login/ui/app_sign_in.dart index c88ffc159e..01010a3b8e 100644 --- a/lib/login/ui/app_sign_in.dart +++ b/lib/login/ui/app_sign_in.dart @@ -12,6 +12,7 @@ import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/providers/path_forwarding_provider.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class AppSignIn extends HookConsumerWidget { const AppSignIn({super.key}); @@ -66,7 +67,7 @@ class AppSignIn extends HookConsumerWidget { data: (data) => data, orElse: () => false, ), - label: LoginTextConstants.signIn, + label: AppLocalizations.of(context)!.loginSignIn, onPressed: () async { await authNotifier.getTokenFromRequest(); ref @@ -79,7 +80,9 @@ class AppSignIn extends HookConsumerWidget { displayToast( context, TypeMsg.error, - LoginTextConstants.loginFailed, + AppLocalizations.of( + context, + )!.loginLoginFailed, ); }, loading: () {}, @@ -108,9 +111,9 @@ class AppSignIn extends HookConsumerWidget { QR.to(LoginRouter.createAccount); controller?.forward(); }, - child: const Text( - LoginTextConstants.createAccount, - style: TextStyle( + child: Text( + AppLocalizations.of(context)!.loginCreateAccount, + style: const TextStyle( color: Colors.white, fontWeight: FontWeight.w800, decoration: TextDecoration.underline, @@ -128,9 +131,9 @@ class AppSignIn extends HookConsumerWidget { QR.to(LoginRouter.forgotPassword); controller?.forward(); }, - child: const Text( - LoginTextConstants.forgotPassword, - style: TextStyle( + child: Text( + AppLocalizations.of(context)!.loginForgotPassword, + style: const TextStyle( color: Colors.white, fontWeight: FontWeight.w800, decoration: TextDecoration.underline, diff --git a/lib/login/ui/components/login_field.dart b/lib/login/ui/components/login_field.dart index c4a303d925..7ff054153e 100644 --- a/lib/login/ui/components/login_field.dart +++ b/lib/login/ui/components/login_field.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/login/tools/constants.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/widgets/align_left_text.dart'; +import 'package:titan/l10n/app_localizations.dart'; class CreateAccountField extends HookConsumerWidget { final TextEditingController controller; @@ -19,14 +19,7 @@ class CreateAccountField extends HookConsumerWidget { final bool isPassword; final bool mustBeInt; final String? Function(String?)? validator; - final dict = { - RegExp(r'[A-Z]'): LoginTextConstants.passwordUppercaseError, - RegExp(r'[a-z]'): LoginTextConstants.passwordLowercaseError, - RegExp(r'[0-9]'): LoginTextConstants.passwordNumberError, - RegExp(r'[!@#$%^&*(),.?":{}|<>\-_[\]+=;]'): - LoginTextConstants.passwordSpecialCaracterError, - }; - CreateAccountField({ + const CreateAccountField({ super.key, required this.controller, required this.label, @@ -45,6 +38,18 @@ class CreateAccountField extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final dict = { + RegExp(r'[A-Z]'): AppLocalizations.of( + context, + )!.loginPasswordUppercaseError, + RegExp(r'[a-z]'): AppLocalizations.of( + context, + )!.loginPasswordLowercaseError, + RegExp(r'[0-9]'): AppLocalizations.of(context)!.loginPasswordNumberError, + RegExp(r'[!@#$%^&*(),.?":{}|<>\-_[\]+=;]'): AppLocalizations.of( + context, + )!.loginPasswordSpecialCaracterError, + }; final isPassword = keyboardType == TextInputType.visiblePassword; final hidePassword = useState(isPassword); return Column( @@ -112,9 +117,9 @@ class CreateAccountField extends HookConsumerWidget { return null; } if (value == null || value.isEmpty) { - return LoginTextConstants.emptyFieldError; + return AppLocalizations.of(context)!.loginEmptyFieldError; } else if (isPassword && value.length < 6) { - return LoginTextConstants.passwordLengthError; + return AppLocalizations.of(context)!.loginPasswordLengthError; } for (var key in dict.keys) { if (isPassword && !value.contains(key)) { @@ -122,7 +127,7 @@ class CreateAccountField extends HookConsumerWidget { } } if (mustBeInt && int.tryParse(value) == null) { - return LoginTextConstants.mustBeIntError; + return AppLocalizations.of(context)!.loginMustBeIntError; } return validator?.call(value); }, diff --git a/lib/login/ui/pages/create_account_page/create_account_page.dart b/lib/login/ui/pages/create_account_page/create_account_page.dart index 8d64afb70b..39f4693824 100644 --- a/lib/login/ui/pages/create_account_page/create_account_page.dart +++ b/lib/login/ui/pages/create_account_page/create_account_page.dart @@ -19,6 +19,7 @@ import 'package:titan/tools/ui/widgets/date_entry.dart'; import 'package:titan/user/class/floors.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:smooth_page_indicator/smooth_page_indicator.dart'; +import 'package:titan/l10n/app_localizations.dart'; class CreateAccountPage extends HookConsumerWidget { const CreateAccountPage({super.key}); @@ -71,7 +72,7 @@ class CreateAccountPage extends HookConsumerWidget { List steps = [ CreateAccountField( controller: activationCode, - label: LoginTextConstants.activationCode, + label: AppLocalizations.of(context)!.loginActivationCode, index: 1, pageController: pageController, currentPage: currentPage, @@ -81,7 +82,7 @@ class CreateAccountPage extends HookConsumerWidget { children: [ CreateAccountField( controller: password, - label: LoginTextConstants.password, + label: AppLocalizations.of(context)!.loginPassword, index: 2, pageController: pageController, currentPage: currentPage, @@ -100,7 +101,7 @@ class CreateAccountPage extends HookConsumerWidget { children: [ CreateAccountField( controller: passwordConfirmation, - label: LoginTextConstants.confirmPassword, + label: AppLocalizations.of(context)!.loginConfirmPassword, index: 3, pageController: pageController, currentPage: currentPage, @@ -108,7 +109,7 @@ class CreateAccountPage extends HookConsumerWidget { keyboardType: TextInputType.visiblePassword, validator: (value) { if (value != password.text) { - return LoginTextConstants.passwordMustMatch; + return AppLocalizations.of(context)!.loginPasswordMustMatch; } return null; }, @@ -123,7 +124,7 @@ class CreateAccountPage extends HookConsumerWidget { ), CreateAccountField( controller: name, - label: LoginTextConstants.name, + label: AppLocalizations.of(context)!.loginName, index: 4, pageController: pageController, currentPage: currentPage, @@ -133,7 +134,7 @@ class CreateAccountPage extends HookConsumerWidget { ), CreateAccountField( controller: firstname, - label: LoginTextConstants.firstname, + label: AppLocalizations.of(context)!.loginFirstname, index: 5, pageController: pageController, currentPage: currentPage, @@ -143,21 +144,21 @@ class CreateAccountPage extends HookConsumerWidget { ), CreateAccountField( controller: nickname, - label: LoginTextConstants.nickname, + label: AppLocalizations.of(context)!.loginNickname, index: 6, pageController: pageController, currentPage: currentPage, formKey: formKeys[5], keyboardType: TextInputType.name, canBeEmpty: true, - hint: LoginTextConstants.canBeEmpty, + hint: AppLocalizations.of(context)!.loginCanBeEmpty, ), Column( mainAxisAlignment: MainAxisAlignment.start, children: [ const SizedBox(height: 9), - const AlignLeftText( - LoginTextConstants.birthday, + AlignLeftText( + AppLocalizations.of(context)!.loginBirthday, fontSize: 20, color: ColorConstants.background2, ), @@ -176,7 +177,7 @@ class CreateAccountPage extends HookConsumerWidget { lastDate: DateTime.now(), ); }, - label: LoginTextConstants.birthday, + label: AppLocalizations.of(context)!.loginBirthday, controller: birthday, color: Colors.white, enabledColor: ColorConstants.background2, @@ -187,7 +188,7 @@ class CreateAccountPage extends HookConsumerWidget { ), CreateAccountField( controller: phone, - label: LoginTextConstants.phone, + label: AppLocalizations.of(context)!.loginPhone, index: 8, pageController: pageController, currentPage: currentPage, @@ -195,11 +196,11 @@ class CreateAccountPage extends HookConsumerWidget { keyboardType: TextInputType.phone, autofillHints: const [AutofillHints.telephoneNumber], canBeEmpty: true, - hint: LoginTextConstants.canBeEmpty, + hint: AppLocalizations.of(context)!.loginCanBeEmpty, ), CreateAccountField( controller: promo, - label: LoginTextConstants.promo, + label: AppLocalizations.of(context)!.loginPromo, index: 9, pageController: pageController, currentPage: currentPage, @@ -207,14 +208,14 @@ class CreateAccountPage extends HookConsumerWidget { keyboardType: TextInputType.number, canBeEmpty: true, mustBeInt: true, - hint: LoginTextConstants.canBeEmpty, + hint: AppLocalizations.of(context)!.loginCanBeEmpty, ), Column( mainAxisAlignment: MainAxisAlignment.start, children: [ const SizedBox(height: 8), - const AlignLeftText( - LoginTextConstants.floor, + AlignLeftText( + AppLocalizations.of(context)!.loginFloor, fontSize: 20, color: ColorConstants.background2, ), @@ -248,7 +249,7 @@ class CreateAccountPage extends HookConsumerWidget { ], ), SignInUpBar( - label: LoginTextConstants.endActivation, + label: AppLocalizations.of(context)!.loginEndActivation, isLoading: false, onPressed: () async { if (name.text.isNotEmpty && @@ -277,14 +278,14 @@ class CreateAccountPage extends HookConsumerWidget { if (value) { displayToastWithContext( TypeMsg.msg, - LoginTextConstants.accountActivated, + AppLocalizations.of(context)!.loginAccountActivated, ); authTokenNotifier.deleteToken(); QR.to(LoginRouter.root); } else { displayToastWithContext( TypeMsg.error, - LoginTextConstants.accountNotActivated, + AppLocalizations.of(context)!.loginAccountNotActivated, ); } } catch (e) { @@ -293,7 +294,7 @@ class CreateAccountPage extends HookConsumerWidget { } else { displayToastWithContext( TypeMsg.error, - LoginTextConstants.fillAllFields, + AppLocalizations.of(context)!.loginFillAllFields, ); } }, @@ -330,7 +331,7 @@ class CreateAccountPage extends HookConsumerWidget { child: Align( alignment: Alignment.centerLeft, child: Text( - LoginTextConstants.createAccountTitle, + AppLocalizations.of(context)!.loginCreateAccountTitle, style: GoogleFonts.elMessiri( textStyle: const TextStyle( fontSize: 30, diff --git a/lib/login/ui/pages/forget_page/forget_page.dart b/lib/login/ui/pages/forget_page/forget_page.dart index 674918fc93..61c4780008 100644 --- a/lib/login/ui/pages/forget_page/forget_page.dart +++ b/lib/login/ui/pages/forget_page/forget_page.dart @@ -6,12 +6,12 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/auth/providers/openid_provider.dart'; import 'package:titan/login/providers/sign_up_provider.dart'; import 'package:titan/login/router.dart'; -import 'package:titan/login/tools/constants.dart'; import 'package:titan/login/ui/auth_page.dart'; import 'package:titan/login/ui/components/sign_in_up_bar.dart'; import 'package:titan/login/ui/components/text_from_decoration.dart'; import 'package:titan/tools/functions.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class ForgetPassword extends HookConsumerWidget { const ForgetPassword({super.key}); @@ -55,7 +55,7 @@ class ForgetPassword extends HookConsumerWidget { child: Align( alignment: Alignment.centerLeft, child: Text( - LoginTextConstants.forgetPassword, + AppLocalizations.of(context)!.loginForgetPassword, style: GoogleFonts.elMessiri( textStyle: const TextStyle( fontSize: 30, @@ -82,7 +82,7 @@ class ForgetPassword extends HookConsumerWidget { ), decoration: signInRegisterInputDecoration( isSignIn: false, - hintText: LoginTextConstants.email, + hintText: AppLocalizations.of(context)!.loginEmail, ), keyboardType: TextInputType.emailAddress, autofillHints: const [AutofillHints.email], @@ -91,7 +91,7 @@ class ForgetPassword extends HookConsumerWidget { ), const SizedBox(height: 30), SignInUpBar( - label: LoginTextConstants.recover, + label: AppLocalizations.of(context)!.loginRecover, isLoading: ref .watch(loadingProvider) .maybeWhen(data: (data) => data, orElse: () => false), @@ -102,7 +102,7 @@ class ForgetPassword extends HookConsumerWidget { if (value) { displayToastWithContext( TypeMsg.msg, - LoginTextConstants.sendedResetMail, + AppLocalizations.of(context)!.loginSendedResetMail, ); email.clear(); QR.to( @@ -112,7 +112,7 @@ class ForgetPassword extends HookConsumerWidget { } else { displayToastWithContext( TypeMsg.error, - LoginTextConstants.mailSendingError, + AppLocalizations.of(context)!.loginMailSendingError, ); } }, @@ -129,9 +129,9 @@ class ForgetPassword extends HookConsumerWidget { onTap: () { QR.to(LoginRouter.root); }, - child: const Text( - LoginTextConstants.signIn, - style: TextStyle( + child: Text( + AppLocalizations.of(context)!.loginSignIn, + style: const TextStyle( color: Colors.white, fontWeight: FontWeight.w800, decoration: TextDecoration.underline, @@ -151,9 +151,9 @@ class ForgetPassword extends HookConsumerWidget { LoginRouter.mailReceived, ); }, - child: const Text( - LoginTextConstants.recievedMail, - style: TextStyle( + child: Text( + AppLocalizations.of(context)!.loginRecievedMail, + style: const TextStyle( color: Colors.white, fontWeight: FontWeight.w800, decoration: TextDecoration.underline, diff --git a/lib/login/ui/pages/recover_password/recover_password_page.dart b/lib/login/ui/pages/recover_password/recover_password_page.dart index 87fde95a66..a9edf2feaf 100644 --- a/lib/login/ui/pages/recover_password/recover_password_page.dart +++ b/lib/login/ui/pages/recover_password/recover_password_page.dart @@ -16,6 +16,7 @@ import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:smooth_page_indicator/smooth_page_indicator.dart'; +import 'package:titan/l10n/app_localizations.dart'; class RecoverPasswordPage extends HookConsumerWidget { const RecoverPasswordPage({super.key}); @@ -41,7 +42,7 @@ class RecoverPasswordPage extends HookConsumerWidget { List steps = [ CreateAccountField( controller: activationCode, - label: LoginTextConstants.activationCode, + label: AppLocalizations.of(context)!.loginActivationCode, index: 1, pageController: pageController, currentPage: currentPage, @@ -51,7 +52,7 @@ class RecoverPasswordPage extends HookConsumerWidget { children: [ CreateAccountField( controller: password, - label: LoginTextConstants.newPassword, + label: AppLocalizations.of(context)!.loginNewPassword, index: 2, pageController: pageController, currentPage: currentPage, @@ -67,7 +68,7 @@ class RecoverPasswordPage extends HookConsumerWidget { ], ), SignInUpBar( - label: LoginTextConstants.endResetPassword, + label: AppLocalizations.of(context)!.loginEndResetPassword, isLoading: false, onPressed: () async { if (password.text.isNotEmpty && activationCode.text.isNotEmpty) { @@ -79,20 +80,20 @@ class RecoverPasswordPage extends HookConsumerWidget { if (value) { displayToastWithContext( TypeMsg.msg, - LoginTextConstants.resetedPassword, + AppLocalizations.of(context)!.loginResetedPassword, ); authTokenNotifier.deleteToken(); QR.to(LoginRouter.root); } else { displayToastWithContext( TypeMsg.error, - LoginTextConstants.invalidToken, + AppLocalizations.of(context)!.loginInvalidToken, ); } } else { displayToastWithContext( TypeMsg.error, - LoginTextConstants.fillAllFields, + AppLocalizations.of(context)!.loginFillAllFields, ); } }, @@ -129,7 +130,7 @@ class RecoverPasswordPage extends HookConsumerWidget { child: Align( alignment: Alignment.centerLeft, child: Text( - LoginTextConstants.resetPasswordTitle, + AppLocalizations.of(context)!.loginResetPasswordTitle, style: GoogleFonts.elMessiri( textStyle: const TextStyle( fontSize: 30, diff --git a/lib/login/ui/pages/register_page/register_page.dart b/lib/login/ui/pages/register_page/register_page.dart index 51803c6392..b9754c1126 100644 --- a/lib/login/ui/pages/register_page/register_page.dart +++ b/lib/login/ui/pages/register_page/register_page.dart @@ -13,6 +13,7 @@ import 'package:titan/login/ui/components/sign_in_up_bar.dart'; import 'package:titan/login/ui/components/text_from_decoration.dart'; import 'package:titan/tools/functions.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class Register extends HookConsumerWidget { const Register({super.key}); @@ -58,7 +59,7 @@ class Register extends HookConsumerWidget { child: Align( alignment: Alignment.centerLeft, child: Text( - LoginTextConstants.createAccountTitle, + AppLocalizations.of(context)!.loginCreateAccountTitle, style: GoogleFonts.elMessiri( textStyle: const TextStyle( fontSize: 30, @@ -85,19 +86,21 @@ class Register extends HookConsumerWidget { ), decoration: signInRegisterInputDecoration( isSignIn: false, - hintText: LoginTextConstants.email, + hintText: AppLocalizations.of(context)!.loginEmail, ), keyboardType: TextInputType.emailAddress, autofillHints: const [AutofillHints.email], validator: (value) { if (value == null || value.isEmpty) { - return LoginTextConstants.emailEmpty; + return AppLocalizations.of( + context, + )!.loginEmailEmpty; } - RegExp regExp = RegExp( - LoginTextConstants.emailRegExp, - ); + RegExp regExp = RegExp(emailRegExp); if (!regExp.hasMatch(value)) { - return LoginTextConstants.emailInvalid; + return AppLocalizations.of( + context, + )!.loginEmailInvalid; } return null; }, @@ -106,7 +109,7 @@ class Register extends HookConsumerWidget { ), const SizedBox(height: 30), SignInUpBar( - label: LoginTextConstants.create, + label: AppLocalizations.of(context)!.loginCreate, isLoading: ref .watch(loadingProvider) .maybeWhen(data: (data) => data, orElse: () => false), @@ -125,18 +128,20 @@ class Register extends HookConsumerWidget { ); displayToastWithContext( TypeMsg.msg, - LoginTextConstants.sendedMail, + AppLocalizations.of(context)!.loginSendedMail, ); } else { displayToastWithContext( TypeMsg.error, - LoginTextConstants.mailSendingError, + AppLocalizations.of( + context, + )!.loginMailSendingError, ); } } else { displayToastWithContext( TypeMsg.error, - LoginTextConstants.emailInvalid, + AppLocalizations.of(context)!.loginEmailInvalid, ); } }, @@ -153,9 +158,9 @@ class Register extends HookConsumerWidget { onTap: () { QR.to(LoginRouter.root); }, - child: const Text( - LoginTextConstants.signIn, - style: TextStyle( + child: Text( + AppLocalizations.of(context)!.loginSignIn, + style: const TextStyle( color: Colors.white, fontWeight: FontWeight.w800, decoration: TextDecoration.underline, @@ -175,9 +180,9 @@ class Register extends HookConsumerWidget { LoginRouter.mailReceived, ); }, - child: const Text( - LoginTextConstants.recievedMail, - style: TextStyle( + child: Text( + AppLocalizations.of(context)!.loginRecievedMail, + style: const TextStyle( color: Colors.white, fontWeight: FontWeight.w800, decoration: TextDecoration.underline, diff --git a/lib/login/ui/pages/sign_in_page/sign_in_page.dart b/lib/login/ui/pages/sign_in_page/sign_in_page.dart index a9ef2e5f73..b8760c8679 100644 --- a/lib/login/ui/pages/sign_in_page/sign_in_page.dart +++ b/lib/login/ui/pages/sign_in_page/sign_in_page.dart @@ -11,6 +11,7 @@ import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/providers/path_forwarding_provider.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class SignIn extends HookConsumerWidget { const SignIn({super.key}); @@ -38,7 +39,7 @@ class SignIn extends HookConsumerWidget { child: Align( alignment: Alignment.centerLeft, child: Text( - LoginTextConstants.appName, + AppLocalizations.of(context)!.loginAppName, style: GoogleFonts.elMessiri( textStyle: const TextStyle( fontSize: 40, @@ -67,7 +68,7 @@ class SignIn extends HookConsumerWidget { data: (data) => data, orElse: () => false, ), - label: LoginTextConstants.signIn, + label: AppLocalizations.of(context)!.loginSignIn, onPressed: () async { await authNotifier.getTokenFromRequest(); ref @@ -80,7 +81,9 @@ class SignIn extends HookConsumerWidget { displayToast( context, TypeMsg.error, - LoginTextConstants.loginFailed, + AppLocalizations.of( + context, + )!.loginLoginFailed, ); }, loading: () {}, @@ -113,9 +116,11 @@ class SignIn extends HookConsumerWidget { onTap: () { QR.to(LoginRouter.createAccount); }, - child: const Text( - LoginTextConstants.createAccount, - style: TextStyle( + child: Text( + AppLocalizations.of( + context, + )!.loginCreateAccount, + style: const TextStyle( color: Colors.white, fontWeight: FontWeight.w800, decoration: TextDecoration.underline, @@ -137,9 +142,11 @@ class SignIn extends HookConsumerWidget { onTap: () { QR.to(LoginRouter.forgotPassword); }, - child: const Text( - LoginTextConstants.forgotPassword, - style: TextStyle( + child: Text( + AppLocalizations.of( + context, + )!.loginForgotPassword, + style: const TextStyle( color: Colors.white, fontWeight: FontWeight.w800, decoration: TextDecoration.underline, diff --git a/lib/login/ui/web/left_panel.dart b/lib/login/ui/web/left_panel.dart index b743cbff5c..9c263c1bfe 100644 --- a/lib/login/ui/web/left_panel.dart +++ b/lib/login/ui/web/left_panel.dart @@ -10,6 +10,7 @@ import 'package:titan/tools/functions.dart'; import 'package:titan/tools/providers/path_forwarding_provider.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class LeftPanel extends HookConsumerWidget { const LeftPanel({super.key}); @@ -93,7 +94,7 @@ class LeftPanel extends HookConsumerWidget { displayToast( context, TypeMsg.error, - LoginTextConstants.loginFailed, + AppLocalizations.of(context)!.loginLoginFailed, ); }, loading: () {}, @@ -131,9 +132,9 @@ class LeftPanel extends HookConsumerWidget { child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - const Text( - LoginTextConstants.signIn, - style: TextStyle( + Text( + AppLocalizations.of(context)!.loginSignIn, + style: const TextStyle( fontSize: 30, fontWeight: FontWeight.bold, color: Colors.white, @@ -168,9 +169,9 @@ class LeftPanel extends HookConsumerWidget { QR.to(LoginRouter.createAccount); controller?.forward(); }, - child: const Text( - LoginTextConstants.createAccount, - style: TextStyle( + child: Text( + AppLocalizations.of(context)!.loginCreateAccount, + style: const TextStyle( fontSize: 15, fontWeight: FontWeight.bold, decoration: TextDecoration.underline, @@ -184,9 +185,9 @@ class LeftPanel extends HookConsumerWidget { QR.to(LoginRouter.forgotPassword); controller?.forward(); }, - child: const Text( - LoginTextConstants.forgotPassword, - style: TextStyle( + child: Text( + AppLocalizations.of(context)!.loginForgotPassword, + style: const TextStyle( fontSize: 15, fontWeight: FontWeight.bold, decoration: TextDecoration.underline, From 7c316139b6f835f54349e070cec54bad5527b631 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 2 Jul 2025 22:44:10 +0200 Subject: [PATCH 039/473] others --- lib/others/ui/no_internet_page.dart | 21 ++++++++++++--------- lib/others/ui/no_module.dart | 17 +++++++++-------- lib/others/ui/update_page.dart | 9 +++++---- 3 files changed, 26 insertions(+), 21 deletions(-) diff --git a/lib/others/ui/no_internet_page.dart b/lib/others/ui/no_internet_page.dart index 5446f864b1..ddb2429992 100644 --- a/lib/others/ui/no_internet_page.dart +++ b/lib/others/ui/no_internet_page.dart @@ -6,6 +6,7 @@ import 'package:titan/home/router.dart'; import 'package:titan/others/tools/constants.dart'; import 'package:titan/tools/constants.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class NoInternetPage extends HookConsumerWidget { const NoInternetPage({super.key}); @@ -25,16 +26,18 @@ class NoInternetPage extends HookConsumerWidget { children: [ const HeroIcon(HeroIcons.signalSlash, size: 150), const SizedBox(height: 20), - const Center( + Center( child: Text( - OthersTextConstants.unableToConnectToServer, + AppLocalizations.of(context)!.othersUnableToConnectToServer, textAlign: TextAlign.center, - style: TextStyle(fontSize: 20), + style: const TextStyle(fontSize: 20), ), ), const SizedBox(height: 20), - const Center( - child: Text(OthersTextConstants.checkInternetConnection), + Center( + child: Text( + AppLocalizations.of(context)!.othersCheckInternetConnection, + ), ), const SizedBox(height: 40), GestureDetector( @@ -65,17 +68,17 @@ class NoInternetPage extends HookConsumerWidget { ], borderRadius: BorderRadius.circular(15), ), - child: const Row( + child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - HeroIcon( + const HeroIcon( HeroIcons.arrowPath, size: 35, color: Colors.white, ), Text( - OthersTextConstants.retry, - style: TextStyle( + AppLocalizations.of(context)!.othersRetry, + style: const TextStyle( fontSize: 25, color: Colors.white, fontWeight: FontWeight.bold, diff --git a/lib/others/ui/no_module.dart b/lib/others/ui/no_module.dart index aea5abbe3e..5073aa1dcc 100644 --- a/lib/others/ui/no_module.dart +++ b/lib/others/ui/no_module.dart @@ -6,6 +6,7 @@ import 'package:titan/others/tools/constants.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/providers/path_forwarding_provider.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class NoModulePage extends HookConsumerWidget { const NoModulePage({super.key}); @@ -23,21 +24,21 @@ class NoModulePage extends HookConsumerWidget { return const Scaffold( backgroundColor: ColorConstants.background, body: Padding( - padding: EdgeInsets.symmetric(horizontal: 30), + padding: const EdgeInsets.symmetric(horizontal: 30), child: Column( children: [ - Spacer(flex: 2), - HeroIcon(HeroIcons.cubeTransparent, size: 100), - SizedBox(height: 50), + const Spacer(flex: 2), + const HeroIcon(HeroIcons.cubeTransparent, size: 100), + const SizedBox(height: 50), Center( child: Text( - OthersTextConstants.noModule, + AppLocalizations.of(context)!.othersNoModule, textAlign: TextAlign.center, - style: TextStyle(fontSize: 20), + style: const TextStyle(fontSize: 20), ), ), - Spacer(flex: 3), - SizedBox(height: 20), + const Spacer(flex: 3), + const SizedBox(height: 20), ], ), ), diff --git a/lib/others/ui/update_page.dart b/lib/others/ui/update_page.dart index fe98e5e966..25e535eb0c 100644 --- a/lib/others/ui/update_page.dart +++ b/lib/others/ui/update_page.dart @@ -4,6 +4,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/others/tools/constants.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/version/providers/titan_version_provider.dart'; +import 'package:titan/l10n/app_localizations.dart'; class UpdatePage extends HookConsumerWidget { const UpdatePage({super.key}); @@ -20,16 +21,16 @@ class UpdatePage extends HookConsumerWidget { const Spacer(flex: 2), const HeroIcon(HeroIcons.bellAlert, size: 100), const SizedBox(height: 50), - const Center( + Center( child: Text( - OthersTextConstants.tooOldVersion, + AppLocalizations.of(context)!.othersTooOldVersion, textAlign: TextAlign.center, - style: TextStyle(fontSize: 20), + style: const TextStyle(fontSize: 20), ), ), const Spacer(flex: 3), Text( - "${OthersTextConstants.version} $titanVersion", + "${AppLocalizations.of(context)!.othersVersion} $titanVersion", style: const TextStyle( fontSize: 15, fontWeight: FontWeight.w500, From d82f4e0b26e4f12bf572fb08625be3e153e0866d Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 2 Jul 2025 22:44:16 +0200 Subject: [PATCH 040/473] ph --- lib/ph/ui/pages/admin_page/admin_page.dart | 5 ++++- lib/ph/ui/pages/admin_page/admin_ph_card.dart | 13 +++++++------ lib/ph/ui/pages/admin_page/admin_ph_list.dart | 5 +++-- lib/ph/ui/pages/file_picker/pdf_picker.dart | 7 ++++--- lib/ph/ui/pages/form_page/add_edit_ph_page.dart | 15 ++++++++------- lib/ph/ui/pages/main_page/main_page.dart | 9 +++++++-- .../ui/pages/past_ph_selection_page/ph_card.dart | 3 ++- 7 files changed, 35 insertions(+), 22 deletions(-) diff --git a/lib/ph/ui/pages/admin_page/admin_page.dart b/lib/ph/ui/pages/admin_page/admin_page.dart index f99f34daba..dc2c95ad0e 100644 --- a/lib/ph/ui/pages/admin_page/admin_page.dart +++ b/lib/ph/ui/pages/admin_page/admin_page.dart @@ -13,6 +13,7 @@ import 'package:titan/ph/ui/components/year_bar.dart'; import 'package:titan/ph/ui/pages/admin_page/admin_ph_list.dart'; import 'package:titan/ph/ui/pages/ph.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class AdminPage extends HookConsumerWidget { const AdminPage({super.key}); @@ -35,7 +36,9 @@ class AdminPage extends HookConsumerWidget { resultNotifier.setFilePickerResult(null); QR.to(PhRouter.root + PhRouter.admin + PhRouter.add_ph); }, - child: const MyButton(text: PhTextConstants.addNewJournal), + child: MyButton( + text: AppLocalizations.of(context)!.phAddNewJournal, + ), ), const SizedBox(height: 20), ], diff --git a/lib/ph/ui/pages/admin_page/admin_ph_card.dart b/lib/ph/ui/pages/admin_page/admin_ph_card.dart index e03b0fd8b6..59889e77d8 100644 --- a/lib/ph/ui/pages/admin_page/admin_ph_card.dart +++ b/lib/ph/ui/pages/admin_page/admin_ph_card.dart @@ -5,6 +5,7 @@ import 'package:titan/ph/tools/constants.dart'; import 'package:titan/ph/tools/functions.dart'; import 'package:titan/tools/ui/layouts/card_button.dart'; import 'package:titan/tools/ui/layouts/card_layout.dart'; +import 'package:titan/l10n/app_localizations.dart'; class AdminPhCard extends StatelessWidget { final VoidCallback onEdit, onDelete; @@ -29,9 +30,9 @@ class AdminPhCard extends StatelessWidget { children: [ Row( children: [ - const Text( - PhTextConstants.nameField, - style: TextStyle(fontWeight: FontWeight.bold), + Text( + AppLocalizations.of(context)!.phNameField, + style: const TextStyle(fontWeight: FontWeight.bold), textAlign: TextAlign.left, ), Text(shortenText(ph.name, 28)), @@ -39,9 +40,9 @@ class AdminPhCard extends StatelessWidget { ), Row( children: [ - const Text( - PhTextConstants.dateField, - style: TextStyle(fontWeight: FontWeight.bold), + Text( + AppLocalizations.of(context)!.phDateField, + style: const TextStyle(fontWeight: FontWeight.bold), textAlign: TextAlign.left, ), Text(shortenText(phFormatDate(ph.date), 28)), diff --git a/lib/ph/ui/pages/admin_page/admin_ph_list.dart b/lib/ph/ui/pages/admin_page/admin_ph_list.dart index 92f747ce47..3e1bd3fc4d 100644 --- a/lib/ph/ui/pages/admin_page/admin_ph_list.dart +++ b/lib/ph/ui/pages/admin_page/admin_ph_list.dart @@ -9,6 +9,7 @@ import 'package:titan/ph/ui/pages/admin_page/admin_ph_card.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class AdminPhList extends HookConsumerWidget { const AdminPhList({super.key}); @@ -41,8 +42,8 @@ class AdminPhList extends HookConsumerWidget { context: context, builder: (context) { return CustomDialogBox( - title: PhTextConstants.delete, - descriptions: PhTextConstants.irreversibleAction, + title: AppLocalizations.of(context)!.phDelete, + descriptions: AppLocalizations.of(context)!.phIrreversibleAction, onYes: () { phListNotifier.deletePh(ph); }, diff --git a/lib/ph/ui/pages/file_picker/pdf_picker.dart b/lib/ph/ui/pages/file_picker/pdf_picker.dart index 3e696db197..0c8f9983e8 100644 --- a/lib/ph/ui/pages/file_picker/pdf_picker.dart +++ b/lib/ph/ui/pages/file_picker/pdf_picker.dart @@ -11,6 +11,7 @@ import 'package:titan/ph/providers/ph_send_pdf_provider.dart'; import 'package:titan/ph/tools/constants.dart'; import 'package:titan/ph/ui/button.dart'; import 'package:titan/tools/functions.dart'; +import 'package:titan/l10n/app_localizations.dart'; class PdfPicker extends HookConsumerWidget { final bool isEdit; @@ -48,7 +49,7 @@ class PdfPicker extends HookConsumerWidget { } else { displayToastWithContext( TypeMsg.error, - PhTextConstants.toHeavyFile, + AppLocalizations.of(context)!.phToHeavyFile, ); } } @@ -58,10 +59,10 @@ class PdfPicker extends HookConsumerWidget { }, child: MyButton( text: isEdit - ? PhTextConstants.editPdfFile + ? AppLocalizations.of(context)!.phEditPdfFile : (result != null) ? result.files.single.name - : PhTextConstants.addPdfFile, + : AppLocalizations.of(context)!.phAddPdfFile, ), ), ); diff --git a/lib/ph/ui/pages/form_page/add_edit_ph_page.dart b/lib/ph/ui/pages/form_page/add_edit_ph_page.dart index 37a3cf4c4f..751c2df013 100644 --- a/lib/ph/ui/pages/form_page/add_edit_ph_page.dart +++ b/lib/ph/ui/pages/form_page/add_edit_ph_page.dart @@ -20,6 +20,7 @@ import 'package:titan/tools/ui/layouts/add_edit_button_layout.dart'; import 'package:titan/tools/ui/widgets/date_entry.dart'; import 'package:titan/tools/ui/widgets/text_entry.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class PhAddEditPhPage extends HookConsumerWidget { const PhAddEditPhPage({super.key}); @@ -55,7 +56,7 @@ class PhAddEditPhPage extends HookConsumerWidget { children: [ TextEntry( maxLines: 1, - label: PhTextConstants.phName, + label: AppLocalizations.of(context)!.phPhName, controller: name, textInputAction: TextInputAction.done, ), @@ -64,7 +65,7 @@ class PhAddEditPhPage extends HookConsumerWidget { child: Column( children: [ DateEntry( - label: PhTextConstants.date, + label: AppLocalizations.of(context)!.phDate, controller: dateController, onTap: () { getOnlyDayDate( @@ -135,15 +136,15 @@ class PhAddEditPhPage extends HookConsumerWidget { displayPhToastWithContext( TypeMsg.msg, isEdit - ? PhTextConstants.edited - : PhTextConstants.added, + ? AppLocalizations.of(context)!.phEdited + : AppLocalizations.of(context)!.phAdded, ); editPdfNotifier.editPdf(false); } } else { displayPhToastWithContext( TypeMsg.error, - PhTextConstants.addingFileError, + AppLocalizations.of(context)!.phAddingFileError, ); } }); @@ -151,12 +152,12 @@ class PhAddEditPhPage extends HookConsumerWidget { displayToast( context, TypeMsg.error, - PhTextConstants.missingInformatonsOrPdf, + AppLocalizations.of(context)!.phMissingInformatonsOrPdf, ); } }, child: Text( - isEdit ? PhTextConstants.edit : PhTextConstants.add, + isEdit ? AppLocalizations.of(context)!.phEdit : AppLocalizations.of(context)!.phAdd, style: const TextStyle( color: Colors.white, fontSize: 25, diff --git a/lib/ph/ui/pages/main_page/main_page.dart b/lib/ph/ui/pages/main_page/main_page.dart index 45379c091b..945d727106 100644 --- a/lib/ph/ui/pages/main_page/main_page.dart +++ b/lib/ph/ui/pages/main_page/main_page.dart @@ -12,6 +12,7 @@ import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/widgets/admin_button.dart'; import 'package:pdfx/pdfx.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class PhMainPage extends HookConsumerWidget { const PhMainPage({super.key}); @@ -40,7 +41,9 @@ class PhMainPage extends HookConsumerWidget { onTap: () { QR.to(PhRouter.root + PhRouter.past_ph_selection); }, - child: const MyButton(text: PhTextConstants.seePreviousJournal), + child: MyButton( + text: AppLocalizations.of(context)!.phSeePreviousJournal, + ), ), ), const SizedBox(height: 10), @@ -49,7 +52,9 @@ class PhMainPage extends HookConsumerWidget { builder: (context, phs) { phs.sort((a, b) => a.date.compareTo(b.date)); if (phs.isEmpty) { - return const Text(PhTextConstants.noJournalInDatabase); + return Text( + AppLocalizations.of(context)!.phNoJournalInDatabase, + ); } else { final idLastPh = phs.last.id; final lastPhPdf = ref.watch(phPdfProvider(idLastPh)); diff --git a/lib/ph/ui/pages/past_ph_selection_page/ph_card.dart b/lib/ph/ui/pages/past_ph_selection_page/ph_card.dart index 1e1f90ec26..954733ec1d 100644 --- a/lib/ph/ui/pages/past_ph_selection_page/ph_card.dart +++ b/lib/ph/ui/pages/past_ph_selection_page/ph_card.dart @@ -14,6 +14,7 @@ import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/card_button.dart'; import 'package:titan/tools/ui/layouts/card_layout.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class PhCard extends HookConsumerWidget { final Ph ph; @@ -80,7 +81,7 @@ class PhCard extends HookConsumerWidget { if (path != null) { displayPhToastWithContext( TypeMsg.msg, - PhTextConstants.succesDowloading, + AppLocalizations.of(context)!.phSuccesDowloading, ); } }, From 1884d7feca87bc8d35b07b11a8b09312be866d96 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 2 Jul 2025 22:53:43 +0200 Subject: [PATCH 041/473] phonebook (still an issue) --- ...ation_membership_member_editable_card.dart | 5 +- .../providers/phonebook_admin_provider.dart | 3 +- lib/phonebook/tools/constants.dart | 124 +----------------- .../ui/components/copiabled_text.dart | 3 +- .../ui/components/text_input_dialog.dart | 5 +- .../ui/pages/admin_page/admin_page.dart | 57 +++++--- .../admin_page/association_research_bar.dart | 11 +- .../association_creation_page.dart | 33 +++-- .../association_editor_page.dart | 55 +++++--- .../association_information_editor.dart | 68 ++++++---- .../member_editable_card.dart | 5 +- .../association_page/association_page.dart | 5 +- .../pages/association_page/member_card.dart | 3 +- .../association_page/web_member_card.dart | 25 ++-- .../ui/pages/main_page/main_page.dart | 9 +- .../ui/pages/main_page/research_bar.dart | 11 +- .../member_detail_page/element_field.dart | 3 +- .../member_detail_page.dart | 19 +-- .../membership_editor_page.dart | 27 ++-- 19 files changed, 213 insertions(+), 258 deletions(-) diff --git a/lib/admin/ui/pages/memberships/association_membership_detail_page/association_membership_member_editable_card.dart b/lib/admin/ui/pages/memberships/association_membership_detail_page/association_membership_member_editable_card.dart index 1552f951e6..cd27e270d0 100644 --- a/lib/admin/ui/pages/memberships/association_membership_detail_page/association_membership_member_editable_card.dart +++ b/lib/admin/ui/pages/memberships/association_membership_detail_page/association_membership_member_editable_card.dart @@ -11,6 +11,7 @@ import 'package:titan/tools/functions.dart'; import 'package:titan/phonebook/tools/constants.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class MemberEditableCard extends HookConsumerWidget { const MemberEditableCard({super.key, required this.associationMembership}); @@ -94,12 +95,12 @@ class MemberEditableCard extends HookConsumerWidget { if (result) { displayToastWithContext( TypeMsg.msg, - PhonebookTextConstants.deletedMember, + AppLocalizations.of(context)!.phonebookDeletedMember, ); } else { displayToastWithContext( TypeMsg.error, - PhonebookTextConstants.deletingError, + AppLocalizations.of(context)!.phonebookDeletingError, ); } }); diff --git a/lib/phonebook/providers/phonebook_admin_provider.dart b/lib/phonebook/providers/phonebook_admin_provider.dart index bdf53bd438..abfd9f80af 100644 --- a/lib/phonebook/providers/phonebook_admin_provider.dart +++ b/lib/phonebook/providers/phonebook_admin_provider.dart @@ -4,6 +4,7 @@ import 'package:titan/phonebook/providers/association_member_list_provider.dart' import 'package:titan/phonebook/providers/association_provider.dart'; import 'package:titan/phonebook/tools/constants.dart'; import 'package:titan/user/providers/user_provider.dart'; +import 'package:titan/l10n/app_localizations.dart'; final isPhonebookAdminProvider = StateProvider((ref) { final user = ref.watch(userProvider); @@ -38,7 +39,7 @@ final isAssociationPresidentProvider = StateProvider((ref) { (membership) => membership.associationId == association.id, ) .rolesTags - .contains(PhonebookTextConstants.presidentRoleTag)) { + .contains(presidentRoleTag)) { isPresident = true; } } diff --git a/lib/phonebook/tools/constants.dart b/lib/phonebook/tools/constants.dart index d065a5a769..a60cf19c08 100644 --- a/lib/phonebook/tools/constants.dart +++ b/lib/phonebook/tools/constants.dart @@ -1,128 +1,6 @@ import 'package:flutter/material.dart'; -class PhonebookTextConstants { - static const String activeMandate = "Mandat actif :"; - static const String add = "Ajouter"; - static const String addAssociation = "Ajouter une association"; - static const String addedAssociation = "Association ajoutée"; - static const String addedMember = "Membre ajouté"; - static const String addingError = "Erreur lors de l'ajout"; - static const String addMember = "Ajouter un membre"; - static const String addRole = "Ajouter un rôle"; - static const String admin = "Admin"; - static const String adminPage = "Page Administrateur"; - static const String all = "Toutes"; - static const String apparentName = "Nom public du rôle :"; - static const String association = "Association :"; - static const String associationDetail = "Détail de l'association :"; - static const String associationKind = "Type d'association :"; - static const String associationPure = "Association"; - static const String associationPureSearch = " Association"; - static const String associations = "Associations :"; - - static const String cancel = "Annuler"; - static const String changeMandate = "Passer au mandat "; - static const String changeMandateConfirm = - "Êtes-vous sûr de vouloir changer tout le mandat ?\nCette action est irréversible !"; - static const String copied = "Copié dans le presse-papier"; - - static const String deactivateAssociation = - "Êtes-vous sûr de vouloir désactiver cette association ?\nCette action est irréversible !"; - static const String deactivatedAssociation = "Association désactivée"; - static const String deactivatedAssociationWarning = - "Attention, cette association est désactivée, vous ne pouvez pas la modifier"; - static const String deactivating = "Désactiver l'association ?"; - static const String deactivatingError = "Erreur lors de la désactivation"; - static const String detail = "Détail :"; - static const String deleteAssociation = - "Supprimer l'association ?\nCela va effacer tout l'historique de l'association"; - static const String deletedAssociation = "Association supprimée"; - static const String deletedMember = "Membre supprimé"; - static const String deleting = "Suppression"; - static const String deletingError = "Erreur lors de la suppression"; - static const String description = "Description"; - - static const String edit = "Modifier"; - static const String editMembership = "Modifier le rôle"; - static const String email = "Email :"; - static const String emailCopied = "Email copié dans le presse-papier"; - static const String emptyApparentName = "Veuillez entrer un nom de role"; - static const String emptyFieldError = "Un champ n'est pas rempli"; - static const String emptyKindError = "Veuillez choisir un type d'association"; - static const String emptyMember = "Aucun membre sélectionné"; - static const String errorAssociationLoading = - "Erreur lors du chargement de l'association"; - static const String errorAssociationNameEmpty = - "Veuillez entrer un nom d'association"; - static const String errorAssociationPicture = - "Erreur lors de la modification de la photo d'association"; - static const String errorKindsLoading = - "Erreur lors du chargement des types d'association"; - static const String errorLoadAssociationList = - "Erreur lors du chargement de la liste des associations"; - static const String errorLoadAssociationMember = - "Erreur lors du chargement des membres de l'association"; - static const String errorLoadAssociationPicture = - "Erreur lors du chargement de la photo d'association"; - static const String errorLoadProfilePicture = "Erreur"; - static const String errorRoleTagsLoading = - "Erreur lors du chargement des tags de rôle"; - static const String existingMembership = - "Ce membre est déjà dans le mandat actuel"; - - static const String firstname = "Prénom :"; - - static const String groups = "Groupes associés :"; - - static const String mandateChangingError = - "Erreur lors du changement de mandat"; - static const String member = "Membre"; - static const String memberReordered = "Membre réordonné"; - static const String members = "Membres"; - static const String membershipAssociationError = - "Veuillez choisir une association"; - static const String membershipRole = "Rôle :"; - static const String membershipRoleError = "Veuillez choisir un rôle"; - - static const String name = "Nom :"; - static const String nameCopied = "Nom et prénom copié dans le presse-papier"; - static const String namePure = "Nom"; - static const String newMandate = "Nouveau mandat"; - static const String newMandateConfirmed = "Mandat changé"; - static const String nickname = "Surnom :"; - static const String nicknameCopied = "Surnom copié dans le presse-papier"; - static const String noAssociationFound = "Aucune association trouvée"; - static const String noMember = "Aucun membre"; - static const String noMemberRole = "Aucun role trouvé"; - - static const String phone = "Téléphone :"; - static const String phonebook = "Annuaire"; - static const String phonebookSearch = "Rechercher"; - static const String phonebookSearchAssociation = "Association"; - static const String phonebookSearchField = "Rechercher :"; - static const String phonebookSearchName = "Nom/Prénom/Surnom"; - static const String phonebookSearchRole = "Poste"; - static const String presidentRoleTag = "Prez'"; - static const String promoNotGiven = "Promo non renseignée"; - static const String promotion = "Promotion :"; - - static const String reorderingError = "Erreur lors du réordonnement"; - static const String research = "Rechercher"; - static const String rolePure = "Rôle"; - - static const String tooHeavyAssociationPicture = - "L'image est trop lourde (max 4Mo)"; - - static const String updateGroups = "Mettre à jour les groupes"; - static const String updatedAssociation = "Association modifiée"; - static const String updatedAssociationPicture = - "La photo d'association a été changée"; - static const String updatedGroups = "Groupes mis à jour"; - static const String updatedMember = "Membre modifié"; - static const String updatingError = "Erreur lors de la modification"; - - static const String validation = "Valider"; -} +const presidentRoleTag = 'president'; class PhonebookColorConstants { static const Color textDark = Color(0xFF1D1D1D); diff --git a/lib/phonebook/ui/components/copiabled_text.dart b/lib/phonebook/ui/components/copiabled_text.dart index d146e7f4e9..16bf2e32b2 100644 --- a/lib/phonebook/ui/components/copiabled_text.dart +++ b/lib/phonebook/ui/components/copiabled_text.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:titan/phonebook/tools/constants.dart'; import 'package:titan/tools/functions.dart'; +import 'package:titan/l10n/app_localizations.dart'; class CopiabledText extends StatelessWidget { const CopiabledText( @@ -27,7 +28,7 @@ class CopiabledText extends StatelessWidget { style: style, onTap: () { Clipboard.setData(ClipboardData(text: data)); - displayToastWithContext(TypeMsg.msg, PhonebookTextConstants.copied); + displayToastWithContext(TypeMsg.msg, AppLocalizations.of(context)!.phonebookCopied); }, ); } diff --git a/lib/phonebook/ui/components/text_input_dialog.dart b/lib/phonebook/ui/components/text_input_dialog.dart index 2b7bd0beeb..9775e2207c 100644 --- a/lib/phonebook/ui/components/text_input_dialog.dart +++ b/lib/phonebook/ui/components/text_input_dialog.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/phonebook/tools/constants.dart'; +import 'package:titan/l10n/app_localizations.dart'; class TextInputDialog extends HookConsumerWidget { const TextInputDialog({ @@ -47,14 +48,14 @@ class TextInputDialog extends HookConsumerWidget { onPressed: () { Navigator.of(context).pop(); }, - child: const Text(PhonebookTextConstants.cancel), + child: Text(AppLocalizations.of(context)!.phonebookCancel), ), TextButton( onPressed: () { Navigator.of(context).pop(); onConfirm(); }, - child: const Text(PhonebookTextConstants.validation), + child: Text(AppLocalizations.of(context)!.phonebookValidation), ), ], ); diff --git a/lib/phonebook/ui/pages/admin_page/admin_page.dart b/lib/phonebook/ui/pages/admin_page/admin_page.dart index e2743badd4..1d7397f07d 100644 --- a/lib/phonebook/ui/pages/admin_page/admin_page.dart +++ b/lib/phonebook/ui/pages/admin_page/admin_page.dart @@ -20,6 +20,7 @@ import 'package:titan/tools/ui/layouts/card_layout.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class AdminPage extends HookConsumerWidget { const AdminPage({super.key}); @@ -89,8 +90,12 @@ class AdminPage extends HookConsumerWidget { ), const SizedBox(height: 30), if (associations.isEmpty) - const Center( - child: Text(PhonebookTextConstants.noAssociationFound), + Center( + child: Text( + AppLocalizations.of( + context, + )!.phonebookNoAssociationFound, + ), ) else ...associationFilteredList.map( @@ -114,11 +119,21 @@ class AdminPage extends HookConsumerWidget { context: context, builder: (context) { return CustomDialogBox( - title: - PhonebookTextConstants.deleting, - descriptions: PhonebookTextConstants - .deleteAssociation, + title: AppLocalizations.of( + context, + )!.phonebookDeleting, + descriptions: AppLocalizations.of( + context, + )!.phonebookDeleteAssociation, onYes: () async { + final deletedAssociationMsg = + AppLocalizations.of( + context, + )!.phonebookDeletedAssociation; + final deletingErrorMsg = + AppLocalizations.of( + context, + )!.phonebookDeletingError; final result = await associationListNotifier .deleteAssociation( @@ -127,14 +142,12 @@ class AdminPage extends HookConsumerWidget { if (result) { displayToastWithContext( TypeMsg.msg, - PhonebookTextConstants - .deletedAssociation, + deletedAssociationMsg, ); } else { displayToastWithContext( TypeMsg.error, - PhonebookTextConstants - .deletingError, + deletingErrorMsg, ); } }, @@ -145,11 +158,21 @@ class AdminPage extends HookConsumerWidget { context: context, builder: (context) { return CustomDialogBox( - title: PhonebookTextConstants - .deactivating, - descriptions: PhonebookTextConstants - .deactivateAssociation, + title: AppLocalizations.of( + context, + )!.phonebookDeactivating, + descriptions: AppLocalizations.of( + context, + )!.phonebookDeactivateAssociation, onYes: () async { + final deactivatedAssociationMsg = + AppLocalizations.of( + context, + )!.phonebookDeactivatedAssociation; + final deactivatingErrorMsg = + AppLocalizations.of( + context, + )!.phonebookDeactivatingError; final result = await associationListNotifier .deactivateAssociation( @@ -158,14 +181,12 @@ class AdminPage extends HookConsumerWidget { if (result) { displayToastWithContext( TypeMsg.msg, - PhonebookTextConstants - .deactivatedAssociation, + deactivatedAssociationMsg, ); } else { displayToastWithContext( TypeMsg.error, - PhonebookTextConstants - .deactivatingError, + deactivatingErrorMsg, ); } }, diff --git a/lib/phonebook/ui/pages/admin_page/association_research_bar.dart b/lib/phonebook/ui/pages/admin_page/association_research_bar.dart index 86b4a7f703..5fa79967d3 100644 --- a/lib/phonebook/ui/pages/admin_page/association_research_bar.dart +++ b/lib/phonebook/ui/pages/admin_page/association_research_bar.dart @@ -4,6 +4,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/phonebook/providers/research_filter_provider.dart'; import 'package:titan/phonebook/tools/constants.dart'; import 'package:titan/tools/constants.dart'; +import 'package:titan/l10n/app_localizations.dart'; class AssociationResearchBar extends HookConsumerWidget { const AssociationResearchBar({super.key}); @@ -21,18 +22,18 @@ class AssociationResearchBar extends HookConsumerWidget { focusNode: focusNode, controller: editingController, cursorColor: PhonebookColorConstants.textDark, - decoration: const InputDecoration( + decoration: InputDecoration( isDense: true, - suffixIcon: Icon( + suffixIcon: const Icon( Icons.search, color: PhonebookColorConstants.textDark, size: 30, ), label: Text( - PhonebookTextConstants.research, - style: TextStyle(color: PhonebookColorConstants.textDark), + AppLocalizations.of(context)!.phonebookResearch, + style: const TextStyle(color: PhonebookColorConstants.textDark), ), - focusedBorder: UnderlineInputBorder( + focusedBorder: const UnderlineInputBorder( borderSide: BorderSide(color: ColorConstants.gradient1), ), ), diff --git a/lib/phonebook/ui/pages/association_creation_page/association_creation_page.dart b/lib/phonebook/ui/pages/association_creation_page/association_creation_page.dart index 42dbd2b9ed..e450e81de3 100644 --- a/lib/phonebook/ui/pages/association_creation_page/association_creation_page.dart +++ b/lib/phonebook/ui/pages/association_creation_page/association_creation_page.dart @@ -45,13 +45,13 @@ class AssociationCreationPage extends HookConsumerWidget { child: Column( children: [ const SizedBox(height: 30), - const Padding( - padding: EdgeInsets.symmetric(horizontal: 30.0), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 30.0), child: Align( alignment: Alignment.centerLeft, child: Text( - PhonebookTextConstants.addAssociation, - style: TextStyle( + AppLocalizations.of(context)!.phonebookAddAssociation, + style: const TextStyle( fontSize: 20, fontWeight: FontWeight.w700, color: ColorConstants.gradient1, @@ -90,18 +90,28 @@ class AssociationCreationPage extends HookConsumerWidget { if (!key.currentState!.validate()) { displayToastWithContext( TypeMsg.error, - PhonebookTextConstants.emptyFieldError, + AppLocalizations.of( + context, + )!.phonebookEmptyFieldError, ); return; } if (kind == '') { displayToastWithContext( TypeMsg.error, - PhonebookTextConstants.emptyKindError, + AppLocalizations.of( + context, + )!.phonebookEmptyKindError, ); return; } await tokenExpireWrapper(ref, () async { + final addedMsg = AppLocalizations.of( + context, + )!.phonebookAddedAssociation; + final adminAddingErrorMsg = AppLocalizations.of( + context, + )!.adminAddingError; final value = await associationListNotifier .createAssociation( Association.empty().copyWith( @@ -112,10 +122,7 @@ class AssociationCreationPage extends HookConsumerWidget { ), ); if (value) { - displayToastWithContext( - TypeMsg.msg, - PhonebookTextConstants.addedAssociation, - ); + displayToastWithContext(TypeMsg.msg, addedMsg); associations.when( data: (d) { associationNotifier.setAssociation(d.last); @@ -127,14 +134,16 @@ class AssociationCreationPage extends HookConsumerWidget { }, error: (e, s) => displayToastWithContext( TypeMsg.error, - PhonebookTextConstants.errorAssociationLoading, + AppLocalizations.of( + context, + )!.phonebookErrorAssociationLoading, ), loading: () {}, ); } else { displayToastWithContext( TypeMsg.error, - AppLocalizations.of(context)!.adminAddingError, + adminAddingErrorMsg, ); } }); diff --git a/lib/phonebook/ui/pages/association_editor_page/association_editor_page.dart b/lib/phonebook/ui/pages/association_editor_page/association_editor_page.dart index 702dc02013..50e6e4a73b 100644 --- a/lib/phonebook/ui/pages/association_editor_page/association_editor_page.dart +++ b/lib/phonebook/ui/pages/association_editor_page/association_editor_page.dart @@ -27,6 +27,7 @@ import 'package:titan/tools/ui/builders/waiting_button.dart'; import 'package:titan/tools/ui/layouts/add_edit_button_layout.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class AssociationEditorPage extends HookConsumerWidget { final scrollKey = GlobalKey(); @@ -76,9 +77,9 @@ class AssociationEditorPage extends HookConsumerWidget { Container( padding: const EdgeInsets.symmetric(horizontal: 30), alignment: Alignment.centerLeft, - child: const Text( - PhonebookTextConstants.edit, - style: TextStyle( + child: Text( + AppLocalizations.of(context)!.phonebookEdit, + style: const TextStyle( fontSize: 20, fontWeight: FontWeight.w700, color: ColorConstants.gradient1, @@ -92,7 +93,7 @@ class AssociationEditorPage extends HookConsumerWidget { padding: const EdgeInsets.symmetric(horizontal: 30), child: Row( children: [ - const Text(PhonebookTextConstants.members), + Text(AppLocalizations.of(context)!.phonebookMembers), const Spacer(), WaitingButton( builder: (child) => Container( @@ -155,7 +156,7 @@ class AssociationEditorPage extends HookConsumerWidget { value: associationMemberList, builder: (context, associationMembers) => associationMembers.isEmpty - ? const Text(PhonebookTextConstants.noMember) + ? Text(AppLocalizations.of(context)!.phonebookNoMember) : (isPhonebookAdmin || isAssociationPresident) && !association.deactivated ? SizedBox( @@ -190,12 +191,16 @@ class AssociationEditorPage extends HookConsumerWidget { if (result) { displayToastWithContext( TypeMsg.msg, - PhonebookTextConstants.memberReordered, + AppLocalizations.of( + context, + )!.phonebookMemberReordered, ); } else { displayToastWithContext( TypeMsg.error, - PhonebookTextConstants.reorderingError, + AppLocalizations.of( + context, + )!.phonebookReorderingError, ); } }); @@ -245,23 +250,33 @@ class AssociationEditorPage extends HookConsumerWidget { showDialog( context: context, builder: (context) => AlertDialog( - title: const Text( - PhonebookTextConstants.newMandate, + title: Text( + AppLocalizations.of(context)!.phonebookNewMandate, ), - content: const Text( - PhonebookTextConstants.changeMandateConfirm, + content: Text( + AppLocalizations.of( + context, + )!.phonebookChangeMandateConfirm, ), actions: [ TextButton( onPressed: () { Navigator.pop(context); }, - child: const Text( - PhonebookTextConstants.cancel, + child: Text( + AppLocalizations.of(context)!.phonebookCancel, ), ), TextButton( onPressed: () async { + final newMandateConfirmedMsg = + AppLocalizations.of( + context, + )!.phonebookNewMandateConfirmed; + final mandateChangingErrorMsg = + AppLocalizations.of( + context, + )!.phonebookMandateChangingError; Navigator.pop(context); await tokenExpireWrapper(ref, () async { final value = await associationListNotifier @@ -274,8 +289,7 @@ class AssociationEditorPage extends HookConsumerWidget { if (value) { displayToastWithContext( TypeMsg.msg, - PhonebookTextConstants - .newMandateConfirmed, + newMandateConfirmedMsg, ); associationNotifier.setAssociation( association.copyWith( @@ -295,14 +309,15 @@ class AssociationEditorPage extends HookConsumerWidget { } else { displayToastWithContext( TypeMsg.error, - PhonebookTextConstants - .mandateChangingError, + mandateChangingErrorMsg, ); } }); }, - child: const Text( - PhonebookTextConstants.validation, + child: Text( + AppLocalizations.of( + context, + )!.phonebookValidation, ), ), ], @@ -311,7 +326,7 @@ class AssociationEditorPage extends HookConsumerWidget { } : () async {}, child: Text( - "${PhonebookTextConstants.changeMandate} ${association.mandateYear + 1}", + "${AppLocalizations.of(context)!.phonebookChangeMandate} ${association.mandateYear + 1}", style: const TextStyle( fontSize: 18, fontWeight: FontWeight.w600, diff --git a/lib/phonebook/ui/pages/association_editor_page/association_information_editor.dart b/lib/phonebook/ui/pages/association_editor_page/association_information_editor.dart index e839ab6366..0a430f7cc2 100644 --- a/lib/phonebook/ui/pages/association_editor_page/association_information_editor.dart +++ b/lib/phonebook/ui/pages/association_editor_page/association_information_editor.dart @@ -16,6 +16,7 @@ import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; import 'package:titan/tools/ui/layouts/add_edit_button_layout.dart'; +import 'package:titan/l10n/app_localizations.dart'; class AssociationInformationEditor extends HookConsumerWidget { final scrollKey = GlobalKey(); @@ -71,8 +72,9 @@ class AssociationInformationEditor extends HookConsumerWidget { controller: name, cursorColor: ColorConstants.gradient1, decoration: InputDecoration( - labelText: - PhonebookTextConstants.namePure, + labelText: AppLocalizations.of( + context, + )!.phonebookNamePure, labelStyle: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, @@ -94,8 +96,9 @@ class AssociationInformationEditor extends HookConsumerWidget { ), validator: (value) { if (value == null || value.isEmpty) { - return PhonebookTextConstants - .emptyFieldError; + return AppLocalizations.of( + context, + )!.phonebookEmptyFieldError; } return null; }, @@ -115,8 +118,9 @@ class AssociationInformationEditor extends HookConsumerWidget { controller: description, cursorColor: ColorConstants.gradient1, decoration: InputDecoration( - labelText: - PhonebookTextConstants.description, + labelText: AppLocalizations.of( + context, + )!.phonebookDescription, labelStyle: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, @@ -156,7 +160,9 @@ class AssociationInformationEditor extends HookConsumerWidget { if (kind == '') { displayToastWithContext( TypeMsg.error, - PhonebookTextConstants.emptyKindError, + AppLocalizations.of( + context, + )!.phonebookEmptyKindError, ); return; } @@ -172,19 +178,23 @@ class AssociationInformationEditor extends HookConsumerWidget { if (value) { displayToastWithContext( TypeMsg.msg, - PhonebookTextConstants.updatedAssociation, + AppLocalizations.of( + context, + )!.phonebookUpdatedAssociation, ); } else { displayToastWithContext( TypeMsg.msg, - PhonebookTextConstants.updatingError, + AppLocalizations.of( + context, + )!.phonebookUpdatingError, ); } }); }, - child: const Text( - PhonebookTextConstants.edit, - style: TextStyle( + child: Text( + AppLocalizations.of(context)!.phonebookEdit, + style: const TextStyle( fontSize: 18, fontWeight: FontWeight.w600, color: Color.fromARGB(255, 255, 255, 255), @@ -204,9 +214,11 @@ class AssociationInformationEditor extends HookConsumerWidget { if (association.deactivated) Container( margin: const EdgeInsets.symmetric(vertical: 10), - child: const Text( - PhonebookTextConstants.deactivatedAssociationWarning, - style: TextStyle( + child: Text( + AppLocalizations.of( + context, + )!.phonebookDeactivatedAssociationWarning, + style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.red, @@ -251,7 +263,9 @@ class AssociationInformationEditor extends HookConsumerWidget { children: [ SizedBox( child: ExpansionTile( - title: const Text(PhonebookTextConstants.groups), + title: Text( + AppLocalizations.of(context)!.phonebookGroups, + ), children: groups.maybeWhen( data: (data) { return data.map((group) { @@ -329,6 +343,12 @@ class AssociationInformationEditor extends HookConsumerWidget { ), onTap: () async { await tokenExpireWrapper(ref, () async { + final updatedGroupsMsg = AppLocalizations.of( + context, + )!.phonebookUpdatedGroups; + final updatingErrorMsg = AppLocalizations.of( + context, + )!.phonebookUpdatingError; final value = await associationListNotifier .updateAssociationGroups( association.copyWith( @@ -338,21 +358,15 @@ class AssociationInformationEditor extends HookConsumerWidget { ), ); if (value) { - displayToastWithContext( - TypeMsg.msg, - PhonebookTextConstants.updatedGroups, - ); + displayToastWithContext(TypeMsg.msg, updatedGroupsMsg); } else { - displayToastWithContext( - TypeMsg.msg, - PhonebookTextConstants.updatingError, - ); + displayToastWithContext(TypeMsg.msg, updatingErrorMsg); } }); }, - child: const Text( - PhonebookTextConstants.updateGroups, - style: TextStyle( + child: Text( + AppLocalizations.of(context)!.phonebookUpdateGroups, + style: const TextStyle( fontSize: 18, fontWeight: FontWeight.w600, color: Color.fromARGB(255, 255, 255, 255), diff --git a/lib/phonebook/ui/pages/association_editor_page/member_editable_card.dart b/lib/phonebook/ui/pages/association_editor_page/member_editable_card.dart index 587851c886..344f52c57f 100644 --- a/lib/phonebook/ui/pages/association_editor_page/member_editable_card.dart +++ b/lib/phonebook/ui/pages/association_editor_page/member_editable_card.dart @@ -19,6 +19,7 @@ import 'package:titan/tools/functions.dart'; import 'package:titan/phonebook/tools/constants.dart'; import 'package:titan/tools/ui/builders/auto_loader_child.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class MemberEditableCard extends HookConsumerWidget { const MemberEditableCard({ @@ -166,12 +167,12 @@ class MemberEditableCard extends HookConsumerWidget { if (result) { displayToastWithContext( TypeMsg.msg, - PhonebookTextConstants.deletedMember, + AppLocalizations.of(context)!.phonebookDeletedMember, ); } else { displayToastWithContext( TypeMsg.error, - PhonebookTextConstants.deletingError, + AppLocalizations.of(context)!.phonebookDeletingError, ); } }, diff --git a/lib/phonebook/ui/pages/association_page/association_page.dart b/lib/phonebook/ui/pages/association_page/association_page.dart index 0453aa117c..d2e269923d 100644 --- a/lib/phonebook/ui/pages/association_page/association_page.dart +++ b/lib/phonebook/ui/pages/association_page/association_page.dart @@ -16,6 +16,7 @@ import 'package:titan/phonebook/ui/phonebook.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class AssociationPage extends HookConsumerWidget { const AssociationPage({super.key}); @@ -70,7 +71,7 @@ class AssociationPage extends HookConsumerWidget { ), const SizedBox(height: 10), Text( - "${PhonebookTextConstants.activeMandate} ${association.mandateYear}", + "${AppLocalizations.of(context)!.phonebookActiveMandate} ${association.mandateYear}", style: const TextStyle(fontSize: 15, color: Colors.black), ), const SizedBox(height: 20), @@ -78,7 +79,7 @@ class AssociationPage extends HookConsumerWidget { value: associationMemberList, builder: (context, associationMembers) => associationMembers.isEmpty - ? const Text(PhonebookTextConstants.noMember) + ? Text(AppLocalizations.of(context)!.phonebookNoMember) : Column( children: associationMemberSortedList .map( diff --git a/lib/phonebook/ui/pages/association_page/member_card.dart b/lib/phonebook/ui/pages/association_page/member_card.dart index c1bc07159b..40d360fdb8 100644 --- a/lib/phonebook/ui/pages/association_page/member_card.dart +++ b/lib/phonebook/ui/pages/association_page/member_card.dart @@ -13,6 +13,7 @@ import 'package:titan/phonebook/tools/function.dart'; import 'package:titan/tools/ui/builders/auto_loader_child.dart'; import 'package:titan/tools/ui/layouts/card_layout.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class MemberCard extends HookConsumerWidget { const MemberCard({ @@ -131,7 +132,7 @@ class MemberCard extends HookConsumerWidget { child: Text( textAlign: TextAlign.right, assoMembership == null - ? PhonebookTextConstants.noMemberRole + ? AppLocalizations.of(context)!.phonebookNoMemberRole : assoMembership.apparentName, style: const TextStyle( fontWeight: FontWeight.bold, diff --git a/lib/phonebook/ui/pages/association_page/web_member_card.dart b/lib/phonebook/ui/pages/association_page/web_member_card.dart index d944ce67a8..7fbbd9116c 100644 --- a/lib/phonebook/ui/pages/association_page/web_member_card.dart +++ b/lib/phonebook/ui/pages/association_page/web_member_card.dart @@ -14,6 +14,7 @@ import 'package:titan/phonebook/ui/pages/association_page/card_field.dart'; import 'package:titan/tools/ui/builders/auto_loader_child.dart'; import 'package:titan/tools/ui/layouts/card_layout.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class WebMemberCard extends HookConsumerWidget { const WebMemberCard({ @@ -137,20 +138,20 @@ class WebMemberCard extends HookConsumerWidget { mainAxisAlignment: MainAxisAlignment.start, children: [ CardField( - label: PhonebookTextConstants.promotion, + label: AppLocalizations.of(context)!.phonebookPromotion, value: member.member.promotion == 0 - ? PhonebookTextConstants.promoNotGiven + ? AppLocalizations.of(context)!.phonebookPromoNotGiven : member.member.promotion < 100 ? "20${member.member.promotion}" : member.member.promotion.toString(), ), CardField( - label: PhonebookTextConstants.email, + label: AppLocalizations.of(context)!.phonebookEmail, value: member.member.email, ), if (member.member.phone != null) CardField( - label: PhonebookTextConstants.phone, + label: AppLocalizations.of(context)!.phonebookPhone, value: member.member.phone!, ), ], @@ -165,7 +166,7 @@ class WebMemberCard extends HookConsumerWidget { Text( textAlign: TextAlign.right, assoMembership == null - ? PhonebookTextConstants.noMemberRole + ? AppLocalizations.of(context)!.phonebookNoMemberRole : assoMembership.apparentName, style: const TextStyle( fontWeight: FontWeight.bold, @@ -216,9 +217,9 @@ class WebMemberCard extends HookConsumerWidget { ), const SizedBox(height: 5), CardField( - label: PhonebookTextConstants.promotion, + label: AppLocalizations.of(context)!.phonebookPromotion, value: member.member.promotion == 0 - ? PhonebookTextConstants.promoNotGiven + ? AppLocalizations.of(context)!.phonebookPromoNotGiven : member.member.promotion < 100 ? "20${member.member.promotion}" : member.member.promotion.toString(), @@ -229,12 +230,12 @@ class WebMemberCard extends HookConsumerWidget { child: Row( children: [ CardField( - label: PhonebookTextConstants.email, + label: AppLocalizations.of(context)!.phonebookEmail, value: member.member.email, ), if (member.member.phone != null) CardField( - label: PhonebookTextConstants.phone, + label: AppLocalizations.of(context)!.phonebookPhone, value: member.member.phone!, ), ], @@ -244,13 +245,13 @@ class WebMemberCard extends HookConsumerWidget { Column( children: [ CardField( - label: PhonebookTextConstants.email, + label: AppLocalizations.of(context)!.phonebookEmail, value: member.member.email, showLabel: false, ), if (member.member.phone != null) CardField( - label: PhonebookTextConstants.phone, + label: AppLocalizations.of(context)!.phonebookPhone, value: member.member.phone!, showLabel: false, ), @@ -263,7 +264,7 @@ class WebMemberCard extends HookConsumerWidget { Text( textAlign: TextAlign.right, assoMembership == null - ? PhonebookTextConstants.noMemberRole + ? AppLocalizations.of(context)!.phonebookNoMemberRole : assoMembership.apparentName, style: const TextStyle( fontWeight: FontWeight.bold, diff --git a/lib/phonebook/ui/pages/main_page/main_page.dart b/lib/phonebook/ui/pages/main_page/main_page.dart index 4be249d2de..74fcd94b92 100644 --- a/lib/phonebook/ui/pages/main_page/main_page.dart +++ b/lib/phonebook/ui/pages/main_page/main_page.dart @@ -17,6 +17,7 @@ import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; import 'package:titan/tools/ui/widgets/admin_button.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class PhonebookMainPage extends HookConsumerWidget { const PhonebookMainPage({super.key}); @@ -69,8 +70,12 @@ class PhonebookMainPage extends HookConsumerWidget { KindsBar(), const SizedBox(height: 30), if (associations.isEmpty) - const Center( - child: Text(PhonebookTextConstants.noAssociationFound), + Center( + child: Text( + AppLocalizations.of( + context, + )!.phonebookNoAssociationFound, + ), ) else ...associationFilteredList.map( diff --git a/lib/phonebook/ui/pages/main_page/research_bar.dart b/lib/phonebook/ui/pages/main_page/research_bar.dart index a2bde51a97..a0d39e526d 100644 --- a/lib/phonebook/ui/pages/main_page/research_bar.dart +++ b/lib/phonebook/ui/pages/main_page/research_bar.dart @@ -4,6 +4,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/phonebook/providers/research_filter_provider.dart'; import 'package:titan/phonebook/tools/constants.dart'; import 'package:titan/tools/constants.dart'; +import 'package:titan/l10n/app_localizations.dart'; class ResearchBar extends HookConsumerWidget { const ResearchBar({super.key}); @@ -22,18 +23,18 @@ class ResearchBar extends HookConsumerWidget { focusNode: focusNode, controller: editingController, cursorColor: PhonebookColorConstants.textDark, - decoration: const InputDecoration( + decoration: InputDecoration( isDense: true, - suffixIcon: Icon( + suffixIcon: const Icon( Icons.search, color: PhonebookColorConstants.textDark, size: 30, ), label: Text( - PhonebookTextConstants.research, - style: TextStyle(color: PhonebookColorConstants.textDark), + AppLocalizations.of(context)!.phonebookResearch, + style: const TextStyle(color: PhonebookColorConstants.textDark), ), - focusedBorder: UnderlineInputBorder( + focusedBorder: const UnderlineInputBorder( borderSide: BorderSide(color: ColorConstants.gradient1), ), ), diff --git a/lib/phonebook/ui/pages/member_detail_page/element_field.dart b/lib/phonebook/ui/pages/member_detail_page/element_field.dart index 80b0471bab..32bfd7f14c 100644 --- a/lib/phonebook/ui/pages/member_detail_page/element_field.dart +++ b/lib/phonebook/ui/pages/member_detail_page/element_field.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:titan/phonebook/tools/constants.dart'; import 'package:titan/tools/functions.dart'; +import 'package:titan/l10n/app_localizations.dart'; class ElementField extends StatelessWidget { final String label; @@ -28,7 +29,7 @@ class ElementField extends StatelessWidget { Clipboard.setData(ClipboardData(text: value)); displayToastWithContext( TypeMsg.msg, - PhonebookTextConstants.copied, + AppLocalizations.of(context)!.phonebookCopied, ); }, ), diff --git a/lib/phonebook/ui/pages/member_detail_page/member_detail_page.dart b/lib/phonebook/ui/pages/member_detail_page/member_detail_page.dart index 1cc88e0362..609715997e 100644 --- a/lib/phonebook/ui/pages/member_detail_page/member_detail_page.dart +++ b/lib/phonebook/ui/pages/member_detail_page/member_detail_page.dart @@ -11,6 +11,7 @@ import 'package:titan/phonebook/ui/phonebook.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/card_layout.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class MemberDetailPage extends HookConsumerWidget { const MemberDetailPage({super.key}); @@ -31,31 +32,31 @@ class MemberDetailPage extends HookConsumerWidget { child: Column( children: [ ElementField( - label: PhonebookTextConstants.name, + label: AppLocalizations.of(context)!.phonebookName, value: memberProvider.member.name, ), ElementField( - label: PhonebookTextConstants.firstname, + label: AppLocalizations.of(context)!.phonebookFirstname, value: memberProvider.member.firstname, ), if (memberProvider.member.nickname != null) ElementField( - label: PhonebookTextConstants.nickname, + label: AppLocalizations.of(context)!.phonebookNickname, value: memberProvider.member.nickname!, ), ElementField( - label: PhonebookTextConstants.email, + label: AppLocalizations.of(context)!.phonebookEmail, value: memberProvider.member.email, ), if (memberProvider.member.phone != null) ElementField( - label: PhonebookTextConstants.phone, + label: AppLocalizations.of(context)!.phonebookPhone, value: memberProvider.member.phone!, ), ElementField( - label: PhonebookTextConstants.promotion, + label: AppLocalizations.of(context)!.phonebookPromotion, value: memberProvider.member.promotion == 0 - ? PhonebookTextConstants.promoNotGiven + ? AppLocalizations.of(context)!.phonebookPromoNotGiven : memberProvider.member.promotion < 100 ? "20${memberProvider.member.promotion}" : memberProvider.member.promotion.toString(), @@ -68,8 +69,8 @@ class MemberDetailPage extends HookConsumerWidget { if (memberProvider.memberships.isNotEmpty) Text( memberProvider.memberships.length == 1 - ? PhonebookTextConstants.association - : PhonebookTextConstants.associations, + ? AppLocalizations.of(context)!.phonebookAssociation + : AppLocalizations.of(context)!.phonebookAssociations, style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 20, diff --git a/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart b/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart index aeef4dbc7f..45b6caf3e7 100644 --- a/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart +++ b/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart @@ -24,6 +24,7 @@ import 'package:titan/user/providers/user_list_provider.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/phonebook/providers/complete_member_provider.dart'; import 'package:titan/phonebook/ui/pages/membership_editor_page/search_result.dart'; +import 'package:titan/l10n/app_localizations.dart'; class MembershipEditorPage extends HookConsumerWidget { const MembershipEditorPage({super.key}); @@ -61,13 +62,13 @@ class MembershipEditorPage extends HookConsumerWidget { children: [ AlignLeftText( isEdit - ? PhonebookTextConstants.editMembership - : PhonebookTextConstants.addMember, + ? AppLocalizations.of(context)!.phonebookEditMembership + : AppLocalizations.of(context)!.phonebookAddMember, ), if (!isEdit) ...[ StyledSearchBar( padding: EdgeInsets.zero, - label: PhonebookTextConstants.member, + label: AppLocalizations.of(context)!.phonebookMember, editingController: queryController, onChanged: (value) async { tokenExpireWrapper(ref, () async { @@ -146,7 +147,7 @@ class MembershipEditorPage extends HookConsumerWidget { const SizedBox(height: 30), TextEntry( controller: apparentNameController, - label: PhonebookTextConstants.apparentName, + label: AppLocalizations.of(context)!.phonebookApparentName, ), const SizedBox(height: 50), WaitingButton( @@ -159,8 +160,8 @@ class MembershipEditorPage extends HookConsumerWidget { ), child: Text( !isEdit - ? PhonebookTextConstants.add - : PhonebookTextConstants.edit, + ? AppLocalizations.of(context)!.phonebookAdd + : AppLocalizations.of(context)!.phonebookEdit, style: const TextStyle( fontSize: 18, fontWeight: FontWeight.w600, @@ -171,14 +172,14 @@ class MembershipEditorPage extends HookConsumerWidget { if (member.member.id == "") { displayToastWithContext( TypeMsg.msg, - PhonebookTextConstants.emptyMember, + AppLocalizations.of(context)!.phonebookEmptyMember, ); return; } if (apparentNameController.text == "") { displayToastWithContext( TypeMsg.msg, - PhonebookTextConstants.emptyApparentName, + AppLocalizations.of(context)!.phonebookEmptyApparentName, ); return; } @@ -207,13 +208,13 @@ class MembershipEditorPage extends HookConsumerWidget { ); displayToastWithContext( TypeMsg.msg, - PhonebookTextConstants.updatedMember, + AppLocalizations.of(context)!.phonebookUpdatedMember, ); QR.back(); } else { displayToastWithContext( TypeMsg.error, - PhonebookTextConstants.updatingError, + AppLocalizations.of(context)!.phonebookUpdatingError, ); } } else { @@ -233,7 +234,7 @@ class MembershipEditorPage extends HookConsumerWidget { .isNotEmpty) { displayToastWithContext( TypeMsg.msg, - PhonebookTextConstants.existingMembership, + AppLocalizations.of(context)!.phonebookExistingMembership, ); return; } @@ -255,13 +256,13 @@ class MembershipEditorPage extends HookConsumerWidget { if (value) { displayToastWithContext( TypeMsg.msg, - PhonebookTextConstants.addedMember, + AppLocalizations.of(context)!.phonebookAddedMember, ); QR.back(); } else { displayToastWithContext( TypeMsg.error, - PhonebookTextConstants.addingError, + AppLocalizations.of(context)!.phonebookAddingError, ); } } From c8adce2e55c5c56a61985cbd372fc279d37937d2 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 2 Jul 2025 22:55:08 +0200 Subject: [PATCH 042/473] purchases --- .../ui/pages/history_page/history_page.dart | 12 ++++++++--- .../ui/pages/history_page/purchase_card.dart | 7 ++++--- .../ui/pages/history_page/research_bar.dart | 11 +++++----- .../ui/pages/main_page/main_page.dart | 21 ++++++++++++------- .../ui/pages/purchase_page/purchase_page.dart | 10 ++++++--- .../ui/pages/scan_page/scan_dialog.dart | 17 ++++++++------- .../ui/pages/scan_page/scan_page.dart | 19 +++++++++++------ .../ui/pages/ticket_page/ticket_page.dart | 3 ++- 8 files changed, 63 insertions(+), 37 deletions(-) diff --git a/lib/purchases/ui/pages/history_page/history_page.dart b/lib/purchases/ui/pages/history_page/history_page.dart index e27a5cd7bc..1d584601f5 100644 --- a/lib/purchases/ui/pages/history_page/history_page.dart +++ b/lib/purchases/ui/pages/history_page/history_page.dart @@ -12,6 +12,7 @@ import 'package:titan/tools/ui/layouts/horizontal_list_view.dart'; import 'package:titan/tools/ui/layouts/item_chip.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class HistoryPage extends HookConsumerWidget { const HistoryPage({super.key}); @@ -55,7 +56,11 @@ class HistoryPage extends HookConsumerWidget { if (children.isEmpty) { children.add( - const Center(child: Text(PurchasesTextConstants.noPurchases)), + Center( + child: Text( + AppLocalizations.of(context)!.purchasesNoPurchases, + ), + ), ); } return Column( @@ -86,8 +91,9 @@ class HistoryPage extends HookConsumerWidget { ], ); }, - errorBuilder: (error, stack) => - const Center(child: Text(PurchasesTextConstants.purchasesError)), + errorBuilder: (error, stack) => Center( + child: Text(AppLocalizations.of(context)!.purchasesPurchasesError), + ), ), ), ); diff --git a/lib/purchases/ui/pages/history_page/purchase_card.dart b/lib/purchases/ui/pages/history_page/purchase_card.dart index 8cf07a0161..5e748c55be 100644 --- a/lib/purchases/ui/pages/history_page/purchase_card.dart +++ b/lib/purchases/ui/pages/history_page/purchase_card.dart @@ -3,6 +3,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/purchases/class/purchase.dart'; import 'package:titan/purchases/tools/constants.dart'; import 'package:titan/tools/ui/layouts/card_layout.dart'; +import 'package:titan/l10n/app_localizations.dart'; class PurchaseCard extends HookConsumerWidget { const PurchaseCard({ @@ -42,9 +43,9 @@ class PurchaseCard extends HookConsumerWidget { fontWeight: FontWeight.bold, ), ) - : const Text( - PurchasesTextConstants.notPaid, - style: TextStyle( + : Text( + AppLocalizations.of(context)!.purchasesNotPaid, + style: const TextStyle( color: Colors.red, fontSize: 16, fontWeight: FontWeight.bold, diff --git a/lib/purchases/ui/pages/history_page/research_bar.dart b/lib/purchases/ui/pages/history_page/research_bar.dart index 2e8dfe2ecf..4207355a01 100644 --- a/lib/purchases/ui/pages/history_page/research_bar.dart +++ b/lib/purchases/ui/pages/history_page/research_bar.dart @@ -4,6 +4,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/phonebook/providers/research_filter_provider.dart'; import 'package:titan/purchases/tools/constants.dart'; import 'package:titan/tools/constants.dart'; +import 'package:titan/l10n/app_localizations.dart'; class ResearchBar extends HookConsumerWidget { const ResearchBar({super.key}); @@ -22,18 +23,18 @@ class ResearchBar extends HookConsumerWidget { focusNode: focusNode, controller: editingController, cursorColor: PurchasesColorConstants.textDark, - decoration: const InputDecoration( + decoration: InputDecoration( isDense: true, - suffixIcon: Icon( + suffixIcon: const Icon( Icons.search, color: PurchasesColorConstants.textDark, size: 30, ), label: Text( - PurchasesTextConstants.research, - style: TextStyle(color: PurchasesColorConstants.textDark), + AppLocalizations.of(context)!.purchasesResearch, + style: const TextStyle(color: PurchasesColorConstants.textDark), ), - focusedBorder: UnderlineInputBorder( + focusedBorder: const UnderlineInputBorder( borderSide: BorderSide(color: ColorConstants.gradient1), ), ), diff --git a/lib/purchases/ui/pages/main_page/main_page.dart b/lib/purchases/ui/pages/main_page/main_page.dart index 1ccc2047e5..b62c4e0a90 100644 --- a/lib/purchases/ui/pages/main_page/main_page.dart +++ b/lib/purchases/ui/pages/main_page/main_page.dart @@ -14,6 +14,7 @@ import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; import 'package:titan/tools/ui/widgets/align_left_text.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class PurchasesMainPage extends HookConsumerWidget { const PurchasesMainPage({super.key}); @@ -39,7 +40,7 @@ class PurchasesMainPage extends HookConsumerWidget { children: [ CustomButton( icon: HeroIcons.clock, - text: PurchasesTextConstants.history, + text: AppLocalizations.of(context)!.purchasesHistory, onTap: () { QR.to(PurchasesRouter.root + PurchasesRouter.history); }, @@ -47,7 +48,7 @@ class PurchasesMainPage extends HookConsumerWidget { if (isAdmin) CustomButton( icon: HeroIcons.viewfinderCircle, - text: PurchasesTextConstants.scan, + text: AppLocalizations.of(context)!.purchasesScan, onTap: () { QR.to(PurchasesRouter.root + PurchasesRouter.scan); }, @@ -55,9 +56,9 @@ class PurchasesMainPage extends HookConsumerWidget { ], ), ), - const AlignLeftText( - padding: EdgeInsets.only(left: 30), - PurchasesTextConstants.tickets, + AlignLeftText( + padding: const EdgeInsets.only(left: 30), + AppLocalizations.of(context)!.purchasesTickets, fontSize: 20, ), AsyncChild( @@ -66,8 +67,10 @@ class PurchasesMainPage extends HookConsumerWidget { return Column( children: [ if (tickets.isEmpty) - const Center( - child: Text(PurchasesTextConstants.noTickets), + Center( + child: Text( + AppLocalizations.of(context)!.purchasesNoTickets, + ), ) else ...ticketList.maybeWhen( @@ -86,7 +89,9 @@ class PurchasesMainPage extends HookConsumerWidget { ), ), orElse: () => [ - const Text(PurchasesTextConstants.ticketsError), + Text( + AppLocalizations.of(context)!.purchasesTicketsError, + ), ], ), ], diff --git a/lib/purchases/ui/pages/purchase_page/purchase_page.dart b/lib/purchases/ui/pages/purchase_page/purchase_page.dart index 472bf6d20b..ecee54a6c0 100644 --- a/lib/purchases/ui/pages/purchase_page/purchase_page.dart +++ b/lib/purchases/ui/pages/purchase_page/purchase_page.dart @@ -5,6 +5,7 @@ import 'package:titan/purchases/tools/constants.dart'; import 'package:titan/purchases/ui/purchases.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; +import 'package:titan/l10n/app_localizations.dart'; class PurchasePage extends HookConsumerWidget { const PurchasePage({super.key}); @@ -31,9 +32,12 @@ class PurchasePage extends HookConsumerWidget { ...!data.validated ? [ const SizedBox(height: 10), - const Text( - PurchasesTextConstants.notPaid, - style: TextStyle(fontSize: 20, color: Colors.red), + Text( + AppLocalizations.of(context)!.purchasesNotPaid, + style: const TextStyle( + fontSize: 20, + color: Colors.red, + ), ), ] : [], diff --git a/lib/purchases/ui/pages/scan_page/scan_dialog.dart b/lib/purchases/ui/pages/scan_page/scan_dialog.dart index 02066616da..88d64a70cb 100644 --- a/lib/purchases/ui/pages/scan_page/scan_dialog.dart +++ b/lib/purchases/ui/pages/scan_page/scan_dialog.dart @@ -14,6 +14,7 @@ import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/layouts/add_edit_button_layout.dart'; import 'package:titan/tools/ui/layouts/card_button.dart'; import 'package:titan/tools/ui/layouts/card_layout.dart'; +import 'package:titan/l10n/app_localizations.dart'; class ScanDialog extends HookConsumerWidget { final String sellerId; @@ -57,15 +58,15 @@ class ScanDialog extends HookConsumerWidget { tagNotifier.setTag(value); }, cursorColor: PurchasesColorConstants.textDark, - decoration: const InputDecoration( + decoration: InputDecoration( isDense: true, label: Text( - PurchasesTextConstants.tag, - style: TextStyle( + AppLocalizations.of(context)!.purchasesTag, + style: const TextStyle( color: PurchasesColorConstants.textDark, ), ), - focusedBorder: UnderlineInputBorder( + focusedBorder: const UnderlineInputBorder( borderSide: BorderSide(color: ColorConstants.gradient1), ), ), @@ -188,7 +189,7 @@ class ScanDialog extends HookConsumerWidget { ), const SizedBox(height: 10), Text( - "${data.scanLeft.toString()} / ${ticket.maxUse} ${PurchasesTextConstants.leftScan}", + "${data.scanLeft.toString()} / ${ticket.maxUse} ${AppLocalizations.of(context)!.purchasesLeftScan}", style: const TextStyle( fontSize: 16, color: Colors.black, @@ -264,9 +265,9 @@ class ScanDialog extends HookConsumerWidget { ], ); }, - loading: () => const Text( - PurchasesTextConstants.loading, - style: TextStyle(fontSize: 20, color: Colors.black), + loading: () => Text( + AppLocalizations.of(context)!.purchasesLoading, + style: const TextStyle(fontSize: 20, color: Colors.black), ), error: (error, stack) => Column( children: [ diff --git a/lib/purchases/ui/pages/scan_page/scan_page.dart b/lib/purchases/ui/pages/scan_page/scan_page.dart index c1dd743f69..de8f149fda 100644 --- a/lib/purchases/ui/pages/scan_page/scan_page.dart +++ b/lib/purchases/ui/pages/scan_page/scan_page.dart @@ -15,6 +15,7 @@ import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/horizontal_list_view.dart'; import 'package:titan/tools/ui/layouts/item_chip.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; +import 'package:titan/l10n/app_localizations.dart'; class ScanPage extends HookConsumerWidget { const ScanPage({super.key}); @@ -74,7 +75,11 @@ class ScanPage extends HookConsumerWidget { ), const SizedBox(height: 10), seller.id == "" - ? const Text(PurchasesTextConstants.pleaseSelectSeller) + ? Text( + AppLocalizations.of( + context, + )!.purchasesPleaseSelectSeller, + ) : AsyncChild( value: products, builder: (context, products) { @@ -83,8 +88,10 @@ class ScanPage extends HookConsumerWidget { product.ticketGenerators.isNotEmpty, ); if (scannableProducts.isEmpty) { - return const Text( - PurchasesTextConstants.noScannableProducts, + return Text( + AppLocalizations.of( + context, + )!.purchasesNoScannableProducts, ); } return Column( @@ -127,7 +134,7 @@ class ScanPage extends HookConsumerWidget { // decoration: const InputDecoration( // isDense: true, // label: Text( - // PurchasesTextConstants.tag, + // AppLocalizations.of(context)!.purchasesTag, // style: TextStyle( // color: PurchasesColorConstants.textDark, // ), @@ -139,12 +146,12 @@ class ScanPage extends HookConsumerWidget { // ), // tag == "" // ? const Text( - // PurchasesTextConstants.noTagGiven, + // AppLocalizations.of(context)!.purchasesNoTagGiven, // style: TextStyle(color: Colors.red), // ) // : const SizedBox(), // product.id == "" - // ? const Text(PurchasesTextConstants.pleaseSelectProduct) + // ? const Text(AppLocalizations.of(context)!.purchasesPleaseSelectProduct) // : Padding( // padding: const EdgeInsets.all(30), // child: SizedBox( diff --git a/lib/purchases/ui/pages/ticket_page/ticket_page.dart b/lib/purchases/ui/pages/ticket_page/ticket_page.dart index 80d9eb9115..976f28c2e8 100644 --- a/lib/purchases/ui/pages/ticket_page/ticket_page.dart +++ b/lib/purchases/ui/pages/ticket_page/ticket_page.dart @@ -9,6 +9,7 @@ import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; import 'package:titan/tools/ui/widgets/loader.dart'; import 'package:qr_flutter/qr_flutter.dart'; +import 'package:titan/l10n/app_localizations.dart'; class TicketPage extends HookConsumerWidget { const TicketPage({super.key}); @@ -63,7 +64,7 @@ class TicketPage extends HookConsumerWidget { ), const SizedBox(height: 10), Text( - "${PurchasesTextConstants.leftScan}: ${data.scanLeft.toString()}", + "${AppLocalizations.of(context)!.purchasesLeftScan}: ${data.scanLeft.toString()}", style: const TextStyle(fontSize: 20, color: Colors.black), ), const SizedBox(height: 10), From 46f66b82500ac7d38a087185451e983f5458b698 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 2 Jul 2025 22:56:48 +0200 Subject: [PATCH 043/473] Clean --- ...ation_membership_member_editable_card.dart | 1 - lib/cinema/tools/constants.dart | 34 ------- lib/event/ui/components/event_ui.dart | 1 - lib/event/ui/pages/admin_page/list_event.dart | 1 - .../ui/pages/detail_page/detail_page.dart | 1 - lib/event/ui/pages/main_page/main_page.dart | 1 - lib/home/tools/constants.dart | 18 ---- lib/home/ui/day_card.dart | 2 - lib/loan/tools/constants.dart | 91 ------------------- .../ui/pages/admin_page/loan_history.dart | 1 - .../ui/pages/admin_page/on_going_loan.dart | 1 - .../pages/detail_pages/item_card_in_loan.dart | 1 - .../item_group_page/add_edit_item_page.dart | 1 - .../loan_group_page/add_edit_button.dart | 1 - .../loan_group_page/add_edit_loan_page.dart | 1 - .../pages/loan_group_page/end_date_entry.dart | 1 - .../loan_group_page/start_date_entry.dart | 1 - lib/loan/ui/pages/main_page/main_page.dart | 1 - lib/login/ui/app_sign_in.dart | 1 - .../create_account_page.dart | 1 - .../recover_password_page.dart | 1 - .../ui/pages/sign_in_page/sign_in_page.dart | 1 - lib/login/ui/web/left_panel.dart | 1 - lib/navigation/tools/constants.dart | 15 --- lib/others/tools/constants.dart | 13 +-- lib/others/ui/no_internet_page.dart | 1 - lib/ph/tools/constants.dart | 23 ----- .../providers/phonebook_admin_provider.dart | 1 - .../ui/components/copiabled_text.dart | 1 - .../ui/components/text_input_dialog.dart | 1 - .../ui/pages/admin_page/admin_page.dart | 1 - .../association_creation_page.dart | 1 - .../association_editor_page.dart | 1 - .../association_information_editor.dart | 1 - .../member_editable_card.dart | 1 - .../association_page/association_page.dart | 1 - .../pages/association_page/member_card.dart | 1 - .../association_page/web_member_card.dart | 1 - .../ui/pages/main_page/main_page.dart | 1 - .../member_detail_page/element_field.dart | 1 - .../member_detail_page.dart | 1 - .../membership_editor_page.dart | 1 - lib/purchases/tools/constants.dart | 46 ---------- .../ui/pages/history_page/history_page.dart | 1 - .../ui/pages/history_page/purchase_card.dart | 1 - .../ui/pages/main_page/main_page.dart | 1 - .../ui/pages/purchase_page/purchase_page.dart | 1 - .../ui/pages/scan_page/scan_page.dart | 1 - .../ui/pages/ticket_page/ticket_page.dart | 1 - 49 files changed, 1 insertion(+), 282 deletions(-) delete mode 100644 lib/ph/tools/constants.dart diff --git a/lib/admin/ui/pages/memberships/association_membership_detail_page/association_membership_member_editable_card.dart b/lib/admin/ui/pages/memberships/association_membership_detail_page/association_membership_member_editable_card.dart index cd27e270d0..8622e6830b 100644 --- a/lib/admin/ui/pages/memberships/association_membership_detail_page/association_membership_member_editable_card.dart +++ b/lib/admin/ui/pages/memberships/association_membership_detail_page/association_membership_member_editable_card.dart @@ -8,7 +8,6 @@ import 'package:titan/admin/router.dart'; import 'package:titan/phonebook/ui/pages/admin_page/delete_button.dart'; import 'package:titan/phonebook/ui/pages/admin_page/edition_button.dart'; import 'package:titan/tools/functions.dart'; -import 'package:titan/phonebook/tools/constants.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/l10n/app_localizations.dart'; diff --git a/lib/cinema/tools/constants.dart b/lib/cinema/tools/constants.dart index 933f612c0b..2b625d283a 100644 --- a/lib/cinema/tools/constants.dart +++ b/lib/cinema/tools/constants.dart @@ -3,37 +3,3 @@ import 'package:flutter/material.dart'; class CinemaColorConstants { static const Color tmdbColor = Color(0xffe2b616); } - -class CinemaTextConstants { - static const String add = "Ajouter"; - static const String addedSession = "Séance ajoutée"; - static const String addingError = "Erreur lors de l'ajout"; - static const String addSession = "Ajouter une séance"; - static const String cinema = "Cinéma"; - static const String deleteSession = "Supprimer la séance ?"; - static const String deleting = "Suppression"; - static const String duration = "Durée"; - static const String edit = "Modifier"; - static const String editedSession = "Séance modifiée"; - static const String editingError = "Erreur lors de la modification"; - static const String editSession = "Modifier la séance"; - static const String emptyUrl = "Veuillez entrer une URL"; - static const String importFromTMDB = "Importer depuis TMDB"; - static const String incomingSession = "A l'affiche"; - static const String incorrectOrMissingFields = - "Champs incorrects ou manquants"; - static const String invalidUrl = "URL invalide"; - static const String genre = "Genre"; - static const String name = "Nom"; - static const String noDateError = "Veuillez entrer une date"; - static const String noDuration = "Veuillez entrer une durée"; - static const String noOverview = "Aucun synopsis"; - static const String noPoster = "Aucune affiche"; - static const String noSession = "Aucune séance"; - static const String overview = "Synopsis"; - static const String posterUrl = "URL de l'affiche"; - static const String sessionDate = "Jour de la séance"; - static const String startHour = "Heure de début"; - static const String tagline = "Slogan"; - static const String the = "Le"; -} diff --git a/lib/event/ui/components/event_ui.dart b/lib/event/ui/components/event_ui.dart index dacf93bf84..a2ce6abdae 100644 --- a/lib/event/ui/components/event_ui.dart +++ b/lib/event/ui/components/event_ui.dart @@ -6,7 +6,6 @@ import 'package:titan/event/class/event.dart'; import 'package:titan/event/providers/event_provider.dart'; import 'package:titan/event/providers/user_event_list_provider.dart'; import 'package:titan/event/router.dart'; -import 'package:titan/event/tools/constants.dart'; import 'package:titan/event/tools/functions.dart'; import 'package:titan/event/ui/components/edit_delete_button.dart'; import 'package:titan/tools/constants.dart'; diff --git a/lib/event/ui/pages/admin_page/list_event.dart b/lib/event/ui/pages/admin_page/list_event.dart index c12725f72c..1b105fc179 100644 --- a/lib/event/ui/pages/admin_page/list_event.dart +++ b/lib/event/ui/pages/admin_page/list_event.dart @@ -7,7 +7,6 @@ import 'package:titan/event/providers/confirmed_event_list_provider.dart'; import 'package:titan/event/providers/event_list_provider.dart'; import 'package:titan/event/providers/event_provider.dart'; import 'package:titan/event/router.dart'; -import 'package:titan/event/tools/constants.dart'; import 'package:titan/event/ui/components/event_ui.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; diff --git a/lib/event/ui/pages/detail_page/detail_page.dart b/lib/event/ui/pages/detail_page/detail_page.dart index 23d81a2e47..147020a3c4 100644 --- a/lib/event/ui/pages/detail_page/detail_page.dart +++ b/lib/event/ui/pages/detail_page/detail_page.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/event/providers/event_provider.dart'; -import 'package:titan/event/tools/constants.dart'; import 'package:titan/event/ui/event.dart'; import 'package:titan/event/ui/components/event_ui.dart'; import 'package:titan/tools/functions.dart'; diff --git a/lib/event/ui/pages/main_page/main_page.dart b/lib/event/ui/pages/main_page/main_page.dart index 76c6a7b18b..e99d9db759 100644 --- a/lib/event/ui/pages/main_page/main_page.dart +++ b/lib/event/ui/pages/main_page/main_page.dart @@ -6,7 +6,6 @@ import 'package:titan/event/providers/event_provider.dart'; import 'package:titan/event/providers/is_admin_provider.dart'; import 'package:titan/event/providers/user_event_list_provider.dart'; import 'package:titan/event/router.dart'; -import 'package:titan/event/tools/constants.dart'; import 'package:titan/event/ui/event.dart'; import 'package:titan/event/ui/components/event_ui.dart'; import 'package:titan/tools/ui/layouts/column_refresher.dart'; diff --git a/lib/home/tools/constants.dart b/lib/home/tools/constants.dart index 828bc16e30..c4bbc3957c 100644 --- a/lib/home/tools/constants.dart +++ b/lib/home/tools/constants.dart @@ -6,21 +6,3 @@ class HomeColorConstants { static const Color gradient1 = Color(0xFFfb6d10); static const Color gradient2 = Color(0xffeb3e1b); } - -class HomeTextConstants { - static const String calendar = "Calendrier"; - static const String eventOf = "Évènements du"; - static const String incomingEvents = "Évènements à venir"; - static const String lastInfos = "Dernières annonces"; - static const String noEvents = "Aucun évènement"; - - static const Map translateDayShort = { - 'Mon': 'Lun', - 'Tue': 'Mar', - 'Wed': 'Mer', - 'Thu': 'Jeu', - 'Fri': 'Ven', - 'Sat': 'Sam', - 'Sun': 'Dim', - }; -} diff --git a/lib/home/ui/day_card.dart b/lib/home/ui/day_card.dart index 13caed34b8..99385db74b 100644 --- a/lib/home/ui/day_card.dart +++ b/lib/home/ui/day_card.dart @@ -1,10 +1,8 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:intl/intl.dart'; import 'package:titan/home/providers/selected_day.dart'; import 'package:titan/home/tools/constants.dart'; import 'package:titan/home/tools/functions.dart'; -import 'package:titan/l10n/app_localizations.dart'; class DayCard extends HookConsumerWidget { final bool isToday; diff --git a/lib/loan/tools/constants.dart b/lib/loan/tools/constants.dart index e962e9a589..b4e6a9c3b9 100644 --- a/lib/loan/tools/constants.dart +++ b/lib/loan/tools/constants.dart @@ -6,94 +6,3 @@ class LoanColorConstants { static const Color redGradient2 = Color.fromARGB(255, 172, 32, 10); static const Color urgentRed = Color.fromARGB(255, 99, 13, 0); } - -class LoanTextConstants { - static const String add = "Ajouter"; - static const String addLoan = "Ajouter un prêt"; - static const String addObject = "Ajouter un objet"; - static const String addedLoan = "Prêt ajouté"; - static const String addedObject = "Objet ajouté"; - static const String addedRoom = "Salle ajoutée"; - static const String addingError = "Erreur lors de l'ajout"; - static const String admin = "Administrateur"; - static const String available = "Disponible"; - static const String availableMultiple = "Disponibles"; - static const String borrowed = "Emprunté"; - static const String borrowedMultiple = "Empruntés"; - static const String and = "et"; - static const String association = "Association"; - static const String availableItems = "Objets disponibles"; - static const String beginDate = "Date du début du prêt"; - static const String borrower = "Emprunteur"; - static const String caution = "Caution"; - static const String cancel = "Annuler"; - static const String confirm = "Confirmer"; - static const String confirmation = "Confirmation"; - static const String dates = "Dates"; - static const String days = "Jours"; - static const String delay = "Délai de la prolongation"; - static const String delete = "Supprimer"; - static const String deletingLoan = "Supprimer le prêt ?"; - static const String deletedItem = "Objet supprimé"; - static const String deletedLoan = "Prêt supprimé"; - static const String deleting = "Suppression"; - static const String deletingError = "Erreur lors de la suppression"; - static const String deletingItem = "Supprimer l'objet ?"; - static const String duration = "Durée"; - static const String edit = "Modifier"; - static const String editItem = "Modifier l'objet"; - static const String editLoan = "Modifier le prêt"; - static const String editedRoom = "Salle modifiée"; - static const String endDate = "Date de fin du prêt"; - static const String ended = "Terminé"; - static const String enterDate = "Veuillez entrer une date"; - static const String extendedLoan = "Prêt prolongé"; - static const String extendingError = "Erreur lors de la prolongation"; - static const String history = "Historique"; - static const String incorrectOrMissingFields = - "Des champs sont manquants ou incorrects"; - static const String invalidNumber = "Veuillez entrer un nombre"; - static const String invalidDates = "Les dates ne sont pas valides"; - static const String item = "Objet"; - static const String items = "Objets"; - static const String itemHandling = "Gestion des objets"; - static const String itemSelected = "objet sélectionné"; - static const String itemsSelected = "objets sélectionnés"; - static const String lendingDuration = "Durée possible du prêt"; - static const String loan = "Prêt"; - static const String loanHandling = "Gestion des prêts"; - static const String looking = "Rechercher"; - static const String name = "Nom"; - static const String next = "Suivant"; - static const String no = "Non"; - static const String noAssociationsFounded = "Aucune association trouvée"; - static const String noAvailableItems = "Aucun objet disponible"; - static const String noBorrower = "Aucun emprunteur"; - static const String noItems = "Aucun objet"; - static const String noItemSelected = "Aucun objet sélectionné"; - static const String noLoan = "Aucun prêt"; - static const String noReturnedDate = "Pas de date de retour"; - static const String quantity = "Quantité"; - static const String none = "Aucun"; - static const String note = "Note"; - static const String noValue = "Veuillez entrer une valeur"; - static const String onGoing = "En cours"; - static const String onGoingLoan = "Prêt en cours"; - static const String others = "autres"; - static const String paidCaution = "Caution payée"; - static const String positiveNumber = "Veuillez entrer un nombre positif"; - static const String previous = "Précédent"; - static const String returned = "Rendu"; - static const String returnedLoan = "Prêt rendu"; - static const String returningError = "Erreur lors du retour"; - static const String returningLoan = "Retour"; - static const String returnLoan = "Rendre le prêt ?"; - static const String returnLoanDescription = "Voulez-vous rendre ce prêt ?"; - static const String toReturn = "A rendre"; - static const String unavailable = "Indisponible"; - static const String update = "Modifier"; - static const String updatedItem = "Objet modifié"; - static const String updatedLoan = "Prêt modifié"; - static const String updatingError = "Erreur lors de la modification"; - static const String yes = "Oui"; -} diff --git a/lib/loan/ui/pages/admin_page/loan_history.dart b/lib/loan/ui/pages/admin_page/loan_history.dart index 853c664402..b2896fc99c 100644 --- a/lib/loan/ui/pages/admin_page/loan_history.dart +++ b/lib/loan/ui/pages/admin_page/loan_history.dart @@ -7,7 +7,6 @@ import 'package:titan/loan/providers/loan_focus_provider.dart'; import 'package:titan/loan/providers/loan_provider.dart'; import 'package:titan/loan/providers/loaner_provider.dart'; import 'package:titan/loan/router.dart'; -import 'package:titan/loan/tools/constants.dart'; import 'package:titan/loan/ui/pages/admin_page/loan_card.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/horizontal_list_view.dart'; diff --git a/lib/loan/ui/pages/admin_page/on_going_loan.dart b/lib/loan/ui/pages/admin_page/on_going_loan.dart index 98bd662727..8778919a2e 100644 --- a/lib/loan/ui/pages/admin_page/on_going_loan.dart +++ b/lib/loan/ui/pages/admin_page/on_going_loan.dart @@ -14,7 +14,6 @@ import 'package:titan/loan/providers/loaner_provider.dart'; import 'package:titan/loan/providers/loaners_items_provider.dart'; import 'package:titan/loan/providers/start_provider.dart'; import 'package:titan/loan/router.dart'; -import 'package:titan/loan/tools/constants.dart'; import 'package:titan/loan/ui/pages/admin_page/loan_card.dart'; import 'package:titan/loan/ui/pages/admin_page/delay_dialog.dart'; import 'package:titan/tools/functions.dart'; diff --git a/lib/loan/ui/pages/detail_pages/item_card_in_loan.dart b/lib/loan/ui/pages/detail_pages/item_card_in_loan.dart index 3fdd978076..52991f0396 100644 --- a/lib/loan/ui/pages/detail_pages/item_card_in_loan.dart +++ b/lib/loan/ui/pages/detail_pages/item_card_in_loan.dart @@ -1,7 +1,6 @@ import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter/material.dart'; import 'package:titan/loan/class/item_quantity.dart'; -import 'package:titan/loan/tools/constants.dart'; import 'package:titan/tools/ui/layouts/card_layout.dart'; import 'package:titan/l10n/app_localizations.dart'; diff --git a/lib/loan/ui/pages/item_group_page/add_edit_item_page.dart b/lib/loan/ui/pages/item_group_page/add_edit_item_page.dart index a48ec2ff49..12cd9669c4 100644 --- a/lib/loan/ui/pages/item_group_page/add_edit_item_page.dart +++ b/lib/loan/ui/pages/item_group_page/add_edit_item_page.dart @@ -6,7 +6,6 @@ import 'package:titan/loan/providers/item_list_provider.dart'; import 'package:titan/loan/providers/item_provider.dart'; import 'package:titan/loan/providers/loaner_provider.dart'; import 'package:titan/loan/providers/loaners_items_provider.dart'; -import 'package:titan/loan/tools/constants.dart'; import 'package:titan/loan/ui/loan.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; diff --git a/lib/loan/ui/pages/loan_group_page/add_edit_button.dart b/lib/loan/ui/pages/loan_group_page/add_edit_button.dart index c83c8fa4d5..25b4cc3a64 100644 --- a/lib/loan/ui/pages/loan_group_page/add_edit_button.dart +++ b/lib/loan/ui/pages/loan_group_page/add_edit_button.dart @@ -12,7 +12,6 @@ import 'package:titan/loan/providers/loaner_loan_list_provider.dart'; import 'package:titan/loan/providers/loaner_provider.dart'; import 'package:titan/loan/providers/selected_items_provider.dart'; import 'package:titan/loan/providers/start_provider.dart'; -import 'package:titan/loan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/layouts/add_edit_button_layout.dart'; diff --git a/lib/loan/ui/pages/loan_group_page/add_edit_loan_page.dart b/lib/loan/ui/pages/loan_group_page/add_edit_loan_page.dart index f36a1429ab..3c84240cd5 100644 --- a/lib/loan/ui/pages/loan_group_page/add_edit_loan_page.dart +++ b/lib/loan/ui/pages/loan_group_page/add_edit_loan_page.dart @@ -8,7 +8,6 @@ import 'package:titan/loan/providers/item_list_provider.dart'; import 'package:titan/loan/providers/loan_provider.dart'; import 'package:titan/loan/providers/loaner_provider.dart'; import 'package:titan/loan/providers/loaners_items_provider.dart'; -import 'package:titan/loan/tools/constants.dart'; import 'package:titan/loan/ui/loan.dart'; import 'package:titan/loan/ui/pages/loan_group_page/add_edit_button.dart'; import 'package:titan/loan/ui/pages/loan_group_page/end_date_entry.dart'; diff --git a/lib/loan/ui/pages/loan_group_page/end_date_entry.dart b/lib/loan/ui/pages/loan_group_page/end_date_entry.dart index 12245a1980..ae42d7c136 100644 --- a/lib/loan/ui/pages/loan_group_page/end_date_entry.dart +++ b/lib/loan/ui/pages/loan_group_page/end_date_entry.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/loan/providers/end_provider.dart'; import 'package:titan/loan/providers/initial_date_provider.dart'; -import 'package:titan/loan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/ui/widgets/date_entry.dart'; import 'package:titan/l10n/app_localizations.dart'; diff --git a/lib/loan/ui/pages/loan_group_page/start_date_entry.dart b/lib/loan/ui/pages/loan_group_page/start_date_entry.dart index 8aa654bd38..8d48aa49f3 100644 --- a/lib/loan/ui/pages/loan_group_page/start_date_entry.dart +++ b/lib/loan/ui/pages/loan_group_page/start_date_entry.dart @@ -6,7 +6,6 @@ import 'package:titan/loan/providers/initial_date_provider.dart'; import 'package:titan/loan/providers/item_list_provider.dart'; import 'package:titan/loan/providers/selected_items_provider.dart'; import 'package:titan/loan/providers/start_provider.dart'; -import 'package:titan/loan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/ui/widgets/date_entry.dart'; import 'package:titan/l10n/app_localizations.dart'; diff --git a/lib/loan/ui/pages/main_page/main_page.dart b/lib/loan/ui/pages/main_page/main_page.dart index d6b70ae74b..34b231bd45 100644 --- a/lib/loan/ui/pages/main_page/main_page.dart +++ b/lib/loan/ui/pages/main_page/main_page.dart @@ -8,7 +8,6 @@ import 'package:titan/loan/providers/loan_list_provider.dart'; import 'package:titan/loan/providers/loan_provider.dart'; import 'package:titan/loan/providers/loaner_loan_list_provider.dart'; import 'package:titan/loan/router.dart'; -import 'package:titan/loan/tools/constants.dart'; import 'package:titan/loan/ui/loan.dart'; import 'package:titan/loan/ui/pages/admin_page/loan_card.dart'; import 'package:titan/tools/ui/widgets/admin_button.dart'; diff --git a/lib/login/ui/app_sign_in.dart b/lib/login/ui/app_sign_in.dart index 01010a3b8e..75d1de6293 100644 --- a/lib/login/ui/app_sign_in.dart +++ b/lib/login/ui/app_sign_in.dart @@ -5,7 +5,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/auth/providers/openid_provider.dart'; import 'package:titan/login/providers/animation_provider.dart'; import 'package:titan/login/router.dart'; -import 'package:titan/login/tools/constants.dart'; import 'package:titan/login/ui/auth_page.dart'; import 'package:titan/login/ui/components/sign_in_up_bar.dart'; import 'package:titan/tools/constants.dart'; diff --git a/lib/login/ui/pages/create_account_page/create_account_page.dart b/lib/login/ui/pages/create_account_page/create_account_page.dart index 39f4693824..280215bfc9 100644 --- a/lib/login/ui/pages/create_account_page/create_account_page.dart +++ b/lib/login/ui/pages/create_account_page/create_account_page.dart @@ -7,7 +7,6 @@ import 'package:titan/auth/providers/openid_provider.dart'; import 'package:titan/login/class/create_account.dart'; import 'package:titan/login/providers/sign_up_provider.dart'; import 'package:titan/login/router.dart'; -import 'package:titan/login/tools/constants.dart'; import 'package:titan/login/ui/components/login_field.dart'; import 'package:titan/login/ui/auth_page.dart'; import 'package:titan/login/ui/components/sign_in_up_bar.dart'; diff --git a/lib/login/ui/pages/recover_password/recover_password_page.dart b/lib/login/ui/pages/recover_password/recover_password_page.dart index a9edf2feaf..8796dff6a3 100644 --- a/lib/login/ui/pages/recover_password/recover_password_page.dart +++ b/lib/login/ui/pages/recover_password/recover_password_page.dart @@ -7,7 +7,6 @@ import 'package:titan/auth/providers/openid_provider.dart'; import 'package:titan/login/class/recover_request.dart'; import 'package:titan/login/providers/sign_up_provider.dart'; import 'package:titan/login/router.dart'; -import 'package:titan/login/tools/constants.dart'; import 'package:titan/login/ui/components/login_field.dart'; import 'package:titan/login/ui/auth_page.dart'; import 'package:titan/login/ui/components/sign_in_up_bar.dart'; diff --git a/lib/login/ui/pages/sign_in_page/sign_in_page.dart b/lib/login/ui/pages/sign_in_page/sign_in_page.dart index b8760c8679..bf3572dec6 100644 --- a/lib/login/ui/pages/sign_in_page/sign_in_page.dart +++ b/lib/login/ui/pages/sign_in_page/sign_in_page.dart @@ -4,7 +4,6 @@ import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/auth/providers/openid_provider.dart'; import 'package:titan/login/router.dart'; -import 'package:titan/login/tools/constants.dart'; import 'package:titan/login/ui/auth_page.dart'; import 'package:titan/login/ui/components/sign_in_up_bar.dart'; import 'package:titan/tools/constants.dart'; diff --git a/lib/login/ui/web/left_panel.dart b/lib/login/ui/web/left_panel.dart index 9c263c1bfe..2178876335 100644 --- a/lib/login/ui/web/left_panel.dart +++ b/lib/login/ui/web/left_panel.dart @@ -5,7 +5,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/auth/providers/openid_provider.dart'; import 'package:titan/login/providers/animation_provider.dart'; import 'package:titan/login/router.dart'; -import 'package:titan/login/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/providers/path_forwarding_provider.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; diff --git a/lib/navigation/tools/constants.dart b/lib/navigation/tools/constants.dart index d488510137..43bf740de3 100644 --- a/lib/navigation/tools/constants.dart +++ b/lib/navigation/tools/constants.dart @@ -8,18 +8,3 @@ class DrawerColorConstants { static const Color fakePageBlue = Color.fromARGB(24, 161, 161, 161); static const Color fakePageShadow = Color.fromARGB(14, 161, 161, 161); } - -class DrawerTextConstants { - static const String admin = "Administration"; - static const String androidAppLink = - "https://play.google.com/store/apps/details?id=fr.myecl.titan"; - static const String copied = "Copié !"; - static const String downloadAppOnMobileDevice = - "Ce site est la version Web de l'application MyECL. Nous vous invitons à télécharger l'application. N'utilisez ce site qu'en cas de problème avec l'application.\n"; - static const String iosAppLink = - "https://apps.apple.com/fr/app/myecl/id6444443430"; - static const String loginOut = "Voulez-vous vous déconnecter ?"; - static const String logOut = "Déconnexion"; - static const String or = " ou "; - static const String settings = "Paramètres"; -} diff --git a/lib/others/tools/constants.dart b/lib/others/tools/constants.dart index c5e08b6afd..8b13789179 100644 --- a/lib/others/tools/constants.dart +++ b/lib/others/tools/constants.dart @@ -1,12 +1 @@ -class OthersTextConstants { - static const String checkInternetConnection = - "Veuillez vérifier votre connexion internet"; - static const String retry = "Réessayer"; - static const String tooOldVersion = - "Votre version de l'application est trop ancienne.\n\nVeuillez mettre à jour l'application."; - static const String unableToConnectToServer = - "Impossible de se connecter au serveur"; - static const String version = "Version"; - static const String noModule = - "Aucun module disponible, veuillez réessayer ultérieurement 😢😢"; -} + diff --git a/lib/others/ui/no_internet_page.dart b/lib/others/ui/no_internet_page.dart index ddb2429992..41fa322577 100644 --- a/lib/others/ui/no_internet_page.dart +++ b/lib/others/ui/no_internet_page.dart @@ -3,7 +3,6 @@ import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/auth/providers/is_connected_provider.dart'; import 'package:titan/home/router.dart'; -import 'package:titan/others/tools/constants.dart'; import 'package:titan/tools/constants.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/l10n/app_localizations.dart'; diff --git a/lib/ph/tools/constants.dart b/lib/ph/tools/constants.dart deleted file mode 100644 index 249a264f6f..0000000000 --- a/lib/ph/tools/constants.dart +++ /dev/null @@ -1,23 +0,0 @@ -class PhTextConstants { - static const String addNewJournal = "Ajouter un nouveau journal"; - static const String nameField = "Nom : "; - static const String dateField = "Date : "; - static const String delete = "Voulez-vous vraiment supprimer ce journal ?"; - static const String irreversibleAction = "Cette action est irréversible"; - static const String toHeavyFile = "Fichier trop volumineux"; - static const String addPdfFile = "Ajouter un fichier PDF"; - static const String editPdfFile = "Modifier le fichier PDF"; - static const String phName = "Nom du PH"; - static const String date = "Date"; - static const String added = "Ajouté"; - static const String edited = "Modifié"; - static const String addingFileError = "Erreur d'ajout"; - static const String missingInformatonsOrPdf = - "Informations manquantes ou fichier PDF manquant"; - static const String add = "Ajouter"; - static const String edit = "Modifier"; - static const String seePreviousJournal = "Voir les anciens journaux"; - static const String noJournalInDatabase = - "Pas encore de PH dans la base de donnée"; - static const String succesDowloading = "Téléchargé avec succès"; -} diff --git a/lib/phonebook/providers/phonebook_admin_provider.dart b/lib/phonebook/providers/phonebook_admin_provider.dart index abfd9f80af..0d6475dbc5 100644 --- a/lib/phonebook/providers/phonebook_admin_provider.dart +++ b/lib/phonebook/providers/phonebook_admin_provider.dart @@ -4,7 +4,6 @@ import 'package:titan/phonebook/providers/association_member_list_provider.dart' import 'package:titan/phonebook/providers/association_provider.dart'; import 'package:titan/phonebook/tools/constants.dart'; import 'package:titan/user/providers/user_provider.dart'; -import 'package:titan/l10n/app_localizations.dart'; final isPhonebookAdminProvider = StateProvider((ref) { final user = ref.watch(userProvider); diff --git a/lib/phonebook/ui/components/copiabled_text.dart b/lib/phonebook/ui/components/copiabled_text.dart index 16bf2e32b2..bf16087fda 100644 --- a/lib/phonebook/ui/components/copiabled_text.dart +++ b/lib/phonebook/ui/components/copiabled_text.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:titan/phonebook/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/l10n/app_localizations.dart'; diff --git a/lib/phonebook/ui/components/text_input_dialog.dart b/lib/phonebook/ui/components/text_input_dialog.dart index 9775e2207c..2db5213687 100644 --- a/lib/phonebook/ui/components/text_input_dialog.dart +++ b/lib/phonebook/ui/components/text_input_dialog.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/phonebook/tools/constants.dart'; import 'package:titan/l10n/app_localizations.dart'; class TextInputDialog extends HookConsumerWidget { diff --git a/lib/phonebook/ui/pages/admin_page/admin_page.dart b/lib/phonebook/ui/pages/admin_page/admin_page.dart index 1d7397f07d..e4637bc39a 100644 --- a/lib/phonebook/ui/pages/admin_page/admin_page.dart +++ b/lib/phonebook/ui/pages/admin_page/admin_page.dart @@ -8,7 +8,6 @@ import 'package:titan/phonebook/providers/association_provider.dart'; import 'package:titan/phonebook/providers/phonebook_admin_provider.dart'; import 'package:titan/phonebook/providers/roles_tags_provider.dart'; import 'package:titan/phonebook/router.dart'; -import 'package:titan/phonebook/tools/constants.dart'; import 'package:titan/phonebook/ui/components/kinds_bar.dart'; import 'package:titan/phonebook/ui/phonebook.dart'; import 'package:titan/phonebook/ui/pages/admin_page/association_research_bar.dart'; diff --git a/lib/phonebook/ui/pages/association_creation_page/association_creation_page.dart b/lib/phonebook/ui/pages/association_creation_page/association_creation_page.dart index e450e81de3..722952ddae 100644 --- a/lib/phonebook/ui/pages/association_creation_page/association_creation_page.dart +++ b/lib/phonebook/ui/pages/association_creation_page/association_creation_page.dart @@ -6,7 +6,6 @@ import 'package:titan/phonebook/providers/association_kind_provider.dart'; import 'package:titan/phonebook/providers/association_list_provider.dart'; import 'package:titan/phonebook/providers/association_provider.dart'; import 'package:titan/phonebook/router.dart'; -import 'package:titan/phonebook/tools/constants.dart'; import 'package:titan/phonebook/ui/components/kinds_bar.dart'; import 'package:titan/phonebook/ui/phonebook.dart'; import 'package:titan/tools/constants.dart'; diff --git a/lib/phonebook/ui/pages/association_editor_page/association_editor_page.dart b/lib/phonebook/ui/pages/association_editor_page/association_editor_page.dart index 50e6e4a73b..2d9d80cc2d 100644 --- a/lib/phonebook/ui/pages/association_editor_page/association_editor_page.dart +++ b/lib/phonebook/ui/pages/association_editor_page/association_editor_page.dart @@ -15,7 +15,6 @@ import 'package:titan/phonebook/providers/membership_provider.dart'; import 'package:titan/phonebook/providers/phonebook_admin_provider.dart'; import 'package:titan/phonebook/providers/roles_tags_provider.dart'; import 'package:titan/phonebook/router.dart'; -import 'package:titan/phonebook/tools/constants.dart'; import 'package:titan/phonebook/ui/pages/association_editor_page/association_information_editor.dart'; import 'package:titan/phonebook/ui/phonebook.dart'; import 'package:titan/phonebook/ui/pages/association_editor_page/member_editable_card.dart'; diff --git a/lib/phonebook/ui/pages/association_editor_page/association_information_editor.dart b/lib/phonebook/ui/pages/association_editor_page/association_information_editor.dart index 0a430f7cc2..526f371504 100644 --- a/lib/phonebook/ui/pages/association_editor_page/association_information_editor.dart +++ b/lib/phonebook/ui/pages/association_editor_page/association_information_editor.dart @@ -9,7 +9,6 @@ import 'package:titan/phonebook/providers/association_kind_provider.dart'; import 'package:titan/phonebook/providers/association_list_provider.dart'; import 'package:titan/phonebook/providers/association_provider.dart'; import 'package:titan/phonebook/providers/phonebook_admin_provider.dart'; -import 'package:titan/phonebook/tools/constants.dart'; import 'package:titan/phonebook/ui/components/kinds_bar.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; diff --git a/lib/phonebook/ui/pages/association_editor_page/member_editable_card.dart b/lib/phonebook/ui/pages/association_editor_page/member_editable_card.dart index 344f52c57f..f3e66f5b02 100644 --- a/lib/phonebook/ui/pages/association_editor_page/member_editable_card.dart +++ b/lib/phonebook/ui/pages/association_editor_page/member_editable_card.dart @@ -16,7 +16,6 @@ import 'package:titan/phonebook/tools/function.dart'; import 'package:titan/phonebook/ui/pages/admin_page/delete_button.dart'; import 'package:titan/phonebook/ui/pages/admin_page/edition_button.dart'; import 'package:titan/tools/functions.dart'; -import 'package:titan/phonebook/tools/constants.dart'; import 'package:titan/tools/ui/builders/auto_loader_child.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/l10n/app_localizations.dart'; diff --git a/lib/phonebook/ui/pages/association_page/association_page.dart b/lib/phonebook/ui/pages/association_page/association_page.dart index d2e269923d..b2896ee89b 100644 --- a/lib/phonebook/ui/pages/association_page/association_page.dart +++ b/lib/phonebook/ui/pages/association_page/association_page.dart @@ -9,7 +9,6 @@ import 'package:titan/phonebook/providers/association_provider.dart'; import 'package:titan/phonebook/providers/association_member_list_provider.dart'; import 'package:titan/phonebook/providers/phonebook_admin_provider.dart'; import 'package:titan/phonebook/router.dart'; -import 'package:titan/phonebook/tools/constants.dart'; import 'package:titan/phonebook/ui/pages/association_page/member_card.dart'; import 'package:titan/phonebook/ui/pages/association_page/web_member_card.dart'; import 'package:titan/phonebook/ui/phonebook.dart'; diff --git a/lib/phonebook/ui/pages/association_page/member_card.dart b/lib/phonebook/ui/pages/association_page/member_card.dart index 40d360fdb8..2c82674d78 100644 --- a/lib/phonebook/ui/pages/association_page/member_card.dart +++ b/lib/phonebook/ui/pages/association_page/member_card.dart @@ -7,7 +7,6 @@ import 'package:titan/phonebook/class/membership.dart'; import 'package:titan/phonebook/providers/member_pictures_provider.dart'; import 'package:titan/phonebook/providers/profile_picture_provider.dart'; import 'package:titan/phonebook/router.dart'; -import 'package:titan/phonebook/tools/constants.dart'; import 'package:titan/phonebook/providers/complete_member_provider.dart'; import 'package:titan/phonebook/tools/function.dart'; import 'package:titan/tools/ui/builders/auto_loader_child.dart'; diff --git a/lib/phonebook/ui/pages/association_page/web_member_card.dart b/lib/phonebook/ui/pages/association_page/web_member_card.dart index 7fbbd9116c..ae9a08244b 100644 --- a/lib/phonebook/ui/pages/association_page/web_member_card.dart +++ b/lib/phonebook/ui/pages/association_page/web_member_card.dart @@ -8,7 +8,6 @@ import 'package:titan/phonebook/providers/member_pictures_provider.dart'; import 'package:titan/phonebook/providers/profile_picture_provider.dart'; import 'package:titan/phonebook/providers/complete_member_provider.dart'; import 'package:titan/phonebook/router.dart'; -import 'package:titan/phonebook/tools/constants.dart'; import 'package:titan/phonebook/tools/function.dart'; import 'package:titan/phonebook/ui/pages/association_page/card_field.dart'; import 'package:titan/tools/ui/builders/auto_loader_child.dart'; diff --git a/lib/phonebook/ui/pages/main_page/main_page.dart b/lib/phonebook/ui/pages/main_page/main_page.dart index 74fcd94b92..00df2b5cf6 100644 --- a/lib/phonebook/ui/pages/main_page/main_page.dart +++ b/lib/phonebook/ui/pages/main_page/main_page.dart @@ -8,7 +8,6 @@ import 'package:titan/phonebook/providers/association_list_provider.dart'; import 'package:titan/phonebook/providers/association_provider.dart'; import 'package:titan/phonebook/providers/phonebook_admin_provider.dart'; import 'package:titan/phonebook/router.dart'; -import 'package:titan/phonebook/tools/constants.dart'; import 'package:titan/phonebook/ui/components/kinds_bar.dart'; import 'package:titan/phonebook/ui/pages/main_page/association_card.dart'; import 'package:titan/phonebook/ui/phonebook.dart'; diff --git a/lib/phonebook/ui/pages/member_detail_page/element_field.dart b/lib/phonebook/ui/pages/member_detail_page/element_field.dart index 32bfd7f14c..87577b7e6c 100644 --- a/lib/phonebook/ui/pages/member_detail_page/element_field.dart +++ b/lib/phonebook/ui/pages/member_detail_page/element_field.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:titan/phonebook/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/l10n/app_localizations.dart'; diff --git a/lib/phonebook/ui/pages/member_detail_page/member_detail_page.dart b/lib/phonebook/ui/pages/member_detail_page/member_detail_page.dart index 609715997e..90a11bf422 100644 --- a/lib/phonebook/ui/pages/member_detail_page/member_detail_page.dart +++ b/lib/phonebook/ui/pages/member_detail_page/member_detail_page.dart @@ -4,7 +4,6 @@ import 'package:titan/phonebook/providers/association_list_provider.dart'; import 'package:titan/phonebook/providers/association_provider.dart'; import 'package:titan/phonebook/providers/complete_member_provider.dart'; import 'package:titan/phonebook/router.dart'; -import 'package:titan/phonebook/tools/constants.dart'; import 'package:titan/phonebook/ui/pages/member_detail_page/element_field.dart'; import 'package:titan/phonebook/ui/pages/member_detail_page/membership_card.dart'; import 'package:titan/phonebook/ui/phonebook.dart'; diff --git a/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart b/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart index 45b6caf3e7..3d696106dd 100644 --- a/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart +++ b/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart @@ -10,7 +10,6 @@ import 'package:titan/phonebook/providers/member_role_tags_provider.dart'; import 'package:titan/phonebook/providers/membership_provider.dart'; import 'package:titan/phonebook/providers/phonebook_admin_provider.dart'; import 'package:titan/phonebook/providers/roles_tags_provider.dart'; -import 'package:titan/phonebook/tools/constants.dart'; import 'package:titan/phonebook/ui/phonebook.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; diff --git a/lib/purchases/tools/constants.dart b/lib/purchases/tools/constants.dart index db9af33c35..b4a56f429e 100644 --- a/lib/purchases/tools/constants.dart +++ b/lib/purchases/tools/constants.dart @@ -1,51 +1,5 @@ import 'package:flutter/material.dart'; -class PurchasesTextConstants { - static const String purchases = "Achats"; - - static const String research = "Rechercher"; - - static const String noPurchasesFound = "Aucun achat trouvé"; - - static const String noTickets = "Aucun ticket"; - - static const String ticketsError = "Erreur lors du chargement des tickets"; - static const String purchasesError = "Erreur lors du chargement des achats"; - - static const String noPurchases = "Aucun achat"; - - static const String times = "fois"; - - static const String alreadyUsed = "Déjà utilisé"; - - static const String notPaid = "Non validé"; - - static const String pleaseSelectProduct = "Veuillez sélectionner un produit"; - - static const String products = "Produits"; - - static const String cancel = "Annuler"; - - static const String validate = "Valider"; - - static const String leftScan = "Scans restants"; - static const String tag = "Tag"; - - static const String history = "Historique"; - - static const String pleaseSelectSeller = "Veuillez sélectionner un vendeur"; - - static const String noTagGiven = "Attention, aucun tag n'a été entré"; - - static const String tickets = "Tickets"; - - static const String noScannableProducts = "Aucun produit scannable"; - - static const String loading = "En attente de scan"; - - static const String scan = "Scanner"; -} - class PurchasesColorConstants { static const Color textDark = Color(0xFF1D1D1D); } diff --git a/lib/purchases/ui/pages/history_page/history_page.dart b/lib/purchases/ui/pages/history_page/history_page.dart index 1d584601f5..2117e90f32 100644 --- a/lib/purchases/ui/pages/history_page/history_page.dart +++ b/lib/purchases/ui/pages/history_page/history_page.dart @@ -4,7 +4,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/purchases/providers/purchase_list_provider.dart'; import 'package:titan/purchases/providers/purchase_provider.dart'; import 'package:titan/purchases/router.dart'; -import 'package:titan/purchases/tools/constants.dart'; import 'package:titan/purchases/ui/pages/history_page/purchase_card.dart'; import 'package:titan/purchases/ui/purchases.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; diff --git a/lib/purchases/ui/pages/history_page/purchase_card.dart b/lib/purchases/ui/pages/history_page/purchase_card.dart index 5e748c55be..976eb85260 100644 --- a/lib/purchases/ui/pages/history_page/purchase_card.dart +++ b/lib/purchases/ui/pages/history_page/purchase_card.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/purchases/class/purchase.dart'; -import 'package:titan/purchases/tools/constants.dart'; import 'package:titan/tools/ui/layouts/card_layout.dart'; import 'package:titan/l10n/app_localizations.dart'; diff --git a/lib/purchases/ui/pages/main_page/main_page.dart b/lib/purchases/ui/pages/main_page/main_page.dart index b62c4e0a90..3b55560b5e 100644 --- a/lib/purchases/ui/pages/main_page/main_page.dart +++ b/lib/purchases/ui/pages/main_page/main_page.dart @@ -5,7 +5,6 @@ import 'package:titan/purchases/providers/purchases_admin_provider.dart'; import 'package:titan/purchases/providers/ticket_list_provider.dart'; import 'package:titan/purchases/providers/ticket_provider.dart'; import 'package:titan/purchases/router.dart'; -import 'package:titan/purchases/tools/constants.dart'; import 'package:titan/purchases/ui/pages/main_page/custom_button.dart'; import 'package:titan/purchases/ui/pages/main_page/ticket_card.dart'; import 'package:titan/purchases/ui/purchases.dart'; diff --git a/lib/purchases/ui/pages/purchase_page/purchase_page.dart b/lib/purchases/ui/pages/purchase_page/purchase_page.dart index ecee54a6c0..b9f6e83cef 100644 --- a/lib/purchases/ui/pages/purchase_page/purchase_page.dart +++ b/lib/purchases/ui/pages/purchase_page/purchase_page.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/purchases/providers/purchase_provider.dart'; -import 'package:titan/purchases/tools/constants.dart'; import 'package:titan/purchases/ui/purchases.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; diff --git a/lib/purchases/ui/pages/scan_page/scan_page.dart b/lib/purchases/ui/pages/scan_page/scan_page.dart index de8f149fda..bd5de73120 100644 --- a/lib/purchases/ui/pages/scan_page/scan_page.dart +++ b/lib/purchases/ui/pages/scan_page/scan_page.dart @@ -6,7 +6,6 @@ import 'package:titan/purchases/providers/generated_ticket_provider.dart'; import 'package:titan/purchases/providers/scanner_provider.dart'; import 'package:titan/purchases/providers/seller_list_provider.dart'; import 'package:titan/purchases/providers/seller_provider.dart'; -import 'package:titan/purchases/tools/constants.dart'; import 'package:titan/purchases/ui/pages/scan_page/ticket_card.dart'; import 'package:titan/purchases/ui/pages/scan_page/scan_dialog.dart'; import 'package:titan/purchases/ui/purchases.dart'; diff --git a/lib/purchases/ui/pages/ticket_page/ticket_page.dart b/lib/purchases/ui/pages/ticket_page/ticket_page.dart index 976f28c2e8..cbb984d4f3 100644 --- a/lib/purchases/ui/pages/ticket_page/ticket_page.dart +++ b/lib/purchases/ui/pages/ticket_page/ticket_page.dart @@ -3,7 +3,6 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/purchases/providers/ticket_provider.dart'; -import 'package:titan/purchases/tools/constants.dart'; import 'package:titan/purchases/ui/purchases.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; From e519d20676e4e0a36f2566f48c5c499af5eb92f3 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 2 Jul 2025 22:57:54 +0200 Subject: [PATCH 044/473] still cleaning --- lib/ph/ui/pages/admin_page/admin_page.dart | 1 - lib/ph/ui/pages/admin_page/admin_ph_card.dart | 1 - lib/ph/ui/pages/admin_page/admin_ph_list.dart | 5 +++-- lib/ph/ui/pages/file_picker/pdf_picker.dart | 1 - lib/ph/ui/pages/form_page/add_edit_ph_page.dart | 9 ++++++--- lib/ph/ui/pages/main_page/main_page.dart | 2 +- lib/ph/ui/pages/past_ph_selection_page/ph_card.dart | 1 - 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/ph/ui/pages/admin_page/admin_page.dart b/lib/ph/ui/pages/admin_page/admin_page.dart index dc2c95ad0e..f527a7229a 100644 --- a/lib/ph/ui/pages/admin_page/admin_page.dart +++ b/lib/ph/ui/pages/admin_page/admin_page.dart @@ -7,7 +7,6 @@ import 'package:titan/ph/providers/file_picker_result_provider.dart'; import 'package:titan/ph/providers/ph_provider.dart'; import 'package:titan/ph/providers/ph_send_pdf_provider.dart'; import 'package:titan/ph/router.dart'; -import 'package:titan/ph/tools/constants.dart'; import 'package:titan/ph/ui/button.dart'; import 'package:titan/ph/ui/components/year_bar.dart'; import 'package:titan/ph/ui/pages/admin_page/admin_ph_list.dart'; diff --git a/lib/ph/ui/pages/admin_page/admin_ph_card.dart b/lib/ph/ui/pages/admin_page/admin_ph_card.dart index 59889e77d8..63eb319714 100644 --- a/lib/ph/ui/pages/admin_page/admin_ph_card.dart +++ b/lib/ph/ui/pages/admin_page/admin_ph_card.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:titan/ph/class/ph.dart'; -import 'package:titan/ph/tools/constants.dart'; import 'package:titan/ph/tools/functions.dart'; import 'package:titan/tools/ui/layouts/card_button.dart'; import 'package:titan/tools/ui/layouts/card_layout.dart'; diff --git a/lib/ph/ui/pages/admin_page/admin_ph_list.dart b/lib/ph/ui/pages/admin_page/admin_ph_list.dart index 3e1bd3fc4d..fbdf19236f 100644 --- a/lib/ph/ui/pages/admin_page/admin_ph_list.dart +++ b/lib/ph/ui/pages/admin_page/admin_ph_list.dart @@ -4,7 +4,6 @@ import 'package:titan/ph/providers/ph_list_provider.dart'; import 'package:titan/ph/providers/ph_provider.dart'; import 'package:titan/ph/providers/selected_year_list_provider.dart'; import 'package:titan/ph/router.dart'; -import 'package:titan/ph/tools/constants.dart'; import 'package:titan/ph/ui/pages/admin_page/admin_ph_card.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; @@ -43,7 +42,9 @@ class AdminPhList extends HookConsumerWidget { builder: (context) { return CustomDialogBox( title: AppLocalizations.of(context)!.phDelete, - descriptions: AppLocalizations.of(context)!.phIrreversibleAction, + descriptions: AppLocalizations.of( + context, + )!.phIrreversibleAction, onYes: () { phListNotifier.deletePh(ph); }, diff --git a/lib/ph/ui/pages/file_picker/pdf_picker.dart b/lib/ph/ui/pages/file_picker/pdf_picker.dart index 0c8f9983e8..1cdd9b85a0 100644 --- a/lib/ph/ui/pages/file_picker/pdf_picker.dart +++ b/lib/ph/ui/pages/file_picker/pdf_picker.dart @@ -8,7 +8,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/ph/providers/edit_pdf_provider.dart'; import 'package:titan/ph/providers/file_picker_result_provider.dart'; import 'package:titan/ph/providers/ph_send_pdf_provider.dart'; -import 'package:titan/ph/tools/constants.dart'; import 'package:titan/ph/ui/button.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/l10n/app_localizations.dart'; diff --git a/lib/ph/ui/pages/form_page/add_edit_ph_page.dart b/lib/ph/ui/pages/form_page/add_edit_ph_page.dart index 751c2df013..86219f9b1c 100644 --- a/lib/ph/ui/pages/form_page/add_edit_ph_page.dart +++ b/lib/ph/ui/pages/form_page/add_edit_ph_page.dart @@ -9,7 +9,6 @@ import 'package:titan/ph/providers/ph_pdf_provider.dart'; import 'package:titan/ph/providers/ph_send_pdf_provider.dart'; import 'package:titan/ph/providers/ph_provider.dart'; import 'package:titan/ph/providers/edit_pdf_provider.dart'; -import 'package:titan/ph/tools/constants.dart'; import 'package:titan/ph/tools/functions.dart'; import 'package:titan/ph/ui/pages/file_picker/pdf_picker.dart'; import 'package:titan/ph/ui/pages/ph.dart'; @@ -152,12 +151,16 @@ class PhAddEditPhPage extends HookConsumerWidget { displayToast( context, TypeMsg.error, - AppLocalizations.of(context)!.phMissingInformatonsOrPdf, + AppLocalizations.of( + context, + )!.phMissingInformatonsOrPdf, ); } }, child: Text( - isEdit ? AppLocalizations.of(context)!.phEdit : AppLocalizations.of(context)!.phAdd, + isEdit + ? AppLocalizations.of(context)!.phEdit + : AppLocalizations.of(context)!.phAdd, style: const TextStyle( color: Colors.white, fontSize: 25, diff --git a/lib/ph/ui/pages/main_page/main_page.dart b/lib/ph/ui/pages/main_page/main_page.dart index 945d727106..7ef6ec2ba4 100644 --- a/lib/ph/ui/pages/main_page/main_page.dart +++ b/lib/ph/ui/pages/main_page/main_page.dart @@ -5,7 +5,7 @@ import 'package:titan/ph/providers/is_ph_admin_provider.dart'; import 'package:titan/ph/providers/ph_list_provider.dart'; import 'package:titan/ph/providers/ph_pdf_provider.dart'; import 'package:titan/ph/router.dart'; -import 'package:titan/ph/tools/constants.dart'; + import 'package:titan/ph/ui/button.dart'; import 'package:titan/ph/ui/pages/ph.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; diff --git a/lib/ph/ui/pages/past_ph_selection_page/ph_card.dart b/lib/ph/ui/pages/past_ph_selection_page/ph_card.dart index 954733ec1d..a2096591d5 100644 --- a/lib/ph/ui/pages/past_ph_selection_page/ph_card.dart +++ b/lib/ph/ui/pages/past_ph_selection_page/ph_card.dart @@ -8,7 +8,6 @@ import 'package:titan/ph/providers/ph_cover_provider.dart'; import 'package:titan/ph/providers/ph_provider.dart'; import 'package:titan/ph/providers/ph_pdf_provider.dart'; import 'package:titan/ph/router.dart'; -import 'package:titan/ph/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/card_button.dart'; From 7fba7629c5c494168259823fc3d21916ad84b1ec Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 2 Jul 2025 23:00:28 +0200 Subject: [PATCH 045/473] raffle --- .../admin_module_page/account_handler.dart | 13 +++--- .../admin_module_page/tombola_handler.dart | 7 +-- .../creation_edit_page.dart | 39 +++++++++------- .../pages/creation_edit_page/prize_card.dart | 3 +- .../creation_edit_page/prize_handler.dart | 3 +- .../creation_edit_page/ticket_handler.dart | 5 ++- .../creation_edit_page/user_cash_ui.dart | 3 +- lib/raffle/ui/pages/main_page/main_page.dart | 38 +++++++++++----- .../ui/pages/main_page/raffle_card.dart | 13 +++--- .../ui/pages/main_page/ticket_card.dart | 6 +-- .../add_edit_pack_ticket_page.dart | 39 +++++++++++----- .../pages/prize_page/add_edit_prize_page.dart | 45 ++++++++++++------- .../raffle_page/buy_type_ticket_card.dart | 9 ++-- .../ui/pages/raffle_page/confirm_payment.dart | 5 ++- .../ui/pages/raffle_page/prize_dialog.dart | 5 ++- .../ui/pages/raffle_page/raffle_page.dart | 35 ++++++++------- 16 files changed, 168 insertions(+), 100 deletions(-) diff --git a/lib/raffle/ui/pages/admin_module_page/account_handler.dart b/lib/raffle/ui/pages/admin_module_page/account_handler.dart index 8a3a0b9a64..27c9a4c32f 100644 --- a/lib/raffle/ui/pages/admin_module_page/account_handler.dart +++ b/lib/raffle/ui/pages/admin_module_page/account_handler.dart @@ -10,6 +10,7 @@ import 'package:titan/raffle/ui/pages/admin_module_page/adding_user_container.da import 'package:titan/raffle/ui/pages/admin_module_page/cash_container.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/user/providers/user_list_provider.dart'; +import 'package:titan/l10n/app_localizations.dart'; class AccountHandler extends HookConsumerWidget { const AccountHandler({super.key}); @@ -55,22 +56,22 @@ class AccountHandler extends HookConsumerWidget { focusNode: focusNode, controller: editingController, cursorColor: RaffleColorConstants.textDark, - decoration: const InputDecoration( - labelText: RaffleTextConstants.accounts, - labelStyle: TextStyle( + decoration: InputDecoration( + labelText: AppLocalizations.of(context)!.raffleAccounts, + labelStyle: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: RaffleColorConstants.textDark, ), - suffixIcon: Icon( + suffixIcon: const Icon( Icons.search, color: RaffleColorConstants.textDark, size: 30, ), - enabledBorder: UnderlineInputBorder( + enabledBorder: const UnderlineInputBorder( borderSide: BorderSide(color: Colors.transparent), ), - focusedBorder: UnderlineInputBorder( + focusedBorder: const UnderlineInputBorder( borderSide: BorderSide(color: RaffleColorConstants.textDark), ), ), diff --git a/lib/raffle/ui/pages/admin_module_page/tombola_handler.dart b/lib/raffle/ui/pages/admin_module_page/tombola_handler.dart index 88d19687df..4af0d41b0e 100644 --- a/lib/raffle/ui/pages/admin_module_page/tombola_handler.dart +++ b/lib/raffle/ui/pages/admin_module_page/tombola_handler.dart @@ -8,6 +8,7 @@ import 'package:titan/raffle/providers/raffle_list_provider.dart'; import 'package:titan/raffle/tools/constants.dart'; import 'package:titan/raffle/ui/pages/admin_module_page/confirm_creation.dart'; import 'package:titan/raffle/ui/pages/admin_module_page/tombola_card.dart'; +import 'package:titan/l10n/app_localizations.dart'; class TombolaHandler extends HookConsumerWidget { const TombolaHandler({super.key}); @@ -77,9 +78,9 @@ class TombolaHandler extends HookConsumerWidget { Container( padding: const EdgeInsets.symmetric(horizontal: 30), alignment: Alignment.centerLeft, - child: const Text( - RaffleTextConstants.raffle, - style: TextStyle( + child: Text( + AppLocalizations.of(context)!.raffleRaffle, + style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: RaffleColorConstants.textDark, diff --git a/lib/raffle/ui/pages/creation_edit_page/creation_edit_page.dart b/lib/raffle/ui/pages/creation_edit_page/creation_edit_page.dart index 1e823017ed..6453de95f4 100644 --- a/lib/raffle/ui/pages/creation_edit_page/creation_edit_page.dart +++ b/lib/raffle/ui/pages/creation_edit_page/creation_edit_page.dart @@ -30,6 +30,7 @@ import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; import 'package:titan/tools/ui/widgets/image_picker_on_tap.dart'; import 'package:titan/tools/ui/widgets/text_entry.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class CreationPage extends HookConsumerWidget { const CreationPage({super.key}); @@ -80,13 +81,13 @@ class CreationPage extends HookConsumerWidget { child: Column( children: [ const SizedBox(height: 30), - const Padding( - padding: EdgeInsets.only(top: 10, left: 30, right: 30), + Padding( + padding: const EdgeInsets.only(top: 10, left: 30, right: 30), child: Align( alignment: Alignment.centerLeft, child: Text( - RaffleTextConstants.editRaffle, - style: TextStyle( + AppLocalizations.of(context)!.raffleEditRaffle, + style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Color.fromARGB(255, 149, 149, 149), @@ -186,7 +187,7 @@ class CreationPage extends HookConsumerWidget { child: Form( key: formKey, child: TextEntry( - label: RaffleTextConstants.name, + label: AppLocalizations.of(context)!.raffleName, enabled: raffle.raffleStatusType == RaffleStatusType.creation, controller: name, @@ -223,7 +224,7 @@ class CreationPage extends HookConsumerWidget { ); } }, - child: const Text(RaffleTextConstants.edit), + child: Text(AppLocalizations.of(context)!.raffleEdit), ), ), const SizedBox(height: 40), @@ -235,9 +236,9 @@ class CreationPage extends HookConsumerWidget { Container( padding: const EdgeInsets.symmetric(horizontal: 30), alignment: Alignment.centerLeft, - child: const Text( - RaffleTextConstants.editRaffle, - style: TextStyle( + child: Text( + AppLocalizations.of(context)!.raffleEditRaffle, + style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: RaffleColorConstants.textDark, @@ -260,13 +261,21 @@ class CreationPage extends HookConsumerWidget { title: raffle.raffleStatusType == RaffleStatusType.creation - ? RaffleTextConstants.openRaffle - : RaffleTextConstants.closeRaffle, + ? AppLocalizations.of( + context, + )!.raffleOpenRaffle + : AppLocalizations.of( + context, + )!.raffleCloseRaffle, descriptions: raffle.raffleStatusType == RaffleStatusType.creation - ? RaffleTextConstants.openRaffleDescription - : RaffleTextConstants.closeRaffleDescription, + ? AppLocalizations.of( + context, + )!.raffleOpenRaffleDescription + : AppLocalizations.of( + context, + )!.raffleCloseRaffleDescription, onYes: () async { switch (raffle.raffleStatusType) { case RaffleStatusType.creation: @@ -306,8 +315,8 @@ class CreationPage extends HookConsumerWidget { child: BlueBtn( child: Text( raffle.raffleStatusType == RaffleStatusType.open - ? RaffleTextConstants.close - : RaffleTextConstants.open, + ? AppLocalizations.of(context)!.raffleClose + : AppLocalizations.of(context)!.raffleOpen, ), ), ), diff --git a/lib/raffle/ui/pages/creation_edit_page/prize_card.dart b/lib/raffle/ui/pages/creation_edit_page/prize_card.dart index e5ce99d300..5b4ab81152 100644 --- a/lib/raffle/ui/pages/creation_edit_page/prize_card.dart +++ b/lib/raffle/ui/pages/creation_edit_page/prize_card.dart @@ -5,6 +5,7 @@ import 'package:titan/raffle/class/prize.dart'; import 'package:titan/raffle/class/raffle_status_type.dart'; import 'package:titan/raffle/tools/constants.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; +import 'package:titan/l10n/app_localizations.dart'; class PrizeCard extends StatelessWidget { final Prize lot; @@ -63,7 +64,7 @@ class PrizeCard extends StatelessWidget { const SizedBox(height: 4), AutoSizeText( lot.quantity > 0 - ? "${RaffleTextConstants.quantity} : ${lot.quantity}" + ? "${AppLocalizations.of(context)!.raffleQuantity} : ${lot.quantity}" : "", maxLines: 2, overflow: TextOverflow.ellipsis, diff --git a/lib/raffle/ui/pages/creation_edit_page/prize_handler.dart b/lib/raffle/ui/pages/creation_edit_page/prize_handler.dart index 7d19a50a5c..ef3f362784 100644 --- a/lib/raffle/ui/pages/creation_edit_page/prize_handler.dart +++ b/lib/raffle/ui/pages/creation_edit_page/prize_handler.dart @@ -15,6 +15,7 @@ import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class PrizeHandler extends HookConsumerWidget { const PrizeHandler({super.key}); @@ -174,7 +175,7 @@ class PrizeHandler extends HookConsumerWidget { if (value) { displayToastWithContext( TypeMsg.msg, - RaffleTextConstants.deletePrize, + AppLocalizations.of(context)!.raffleDeletePrize, ); } else { displayToastWithContext( diff --git a/lib/raffle/ui/pages/creation_edit_page/ticket_handler.dart b/lib/raffle/ui/pages/creation_edit_page/ticket_handler.dart index a901cb168b..d271a08886 100644 --- a/lib/raffle/ui/pages/creation_edit_page/ticket_handler.dart +++ b/lib/raffle/ui/pages/creation_edit_page/ticket_handler.dart @@ -13,6 +13,7 @@ import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class TicketHandler extends HookConsumerWidget { const TicketHandler({super.key}); @@ -124,12 +125,12 @@ class TicketHandler extends HookConsumerWidget { if (value) { displayToastWithContext( TypeMsg.msg, - RaffleTextConstants.deletedTicket, + AppLocalizations.of(context)!.raffleDeletedTicket, ); } else { displayToastWithContext( TypeMsg.error, - RaffleTextConstants.deletingError, + AppLocalizations.of(context)!.raffleDeletingError, ); } }); diff --git a/lib/raffle/ui/pages/creation_edit_page/user_cash_ui.dart b/lib/raffle/ui/pages/creation_edit_page/user_cash_ui.dart index f56b0634f2..1a3411d6b2 100644 --- a/lib/raffle/ui/pages/creation_edit_page/user_cash_ui.dart +++ b/lib/raffle/ui/pages/creation_edit_page/user_cash_ui.dart @@ -11,6 +11,7 @@ import 'package:titan/raffle/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; +import 'package:titan/l10n/app_localizations.dart'; class UserCashUi extends HookConsumerWidget { final Cash cash; @@ -195,7 +196,7 @@ class UserCashUi extends HookConsumerWidget { controller: amount, keyboardType: TextInputType.number, validator: (value) => value!.isEmpty - ? RaffleTextConstants.add + ? AppLocalizations.of(context)!.raffleAdd : null, cursorColor: RaffleColorConstants.textDark, decoration: const InputDecoration( diff --git a/lib/raffle/ui/pages/main_page/main_page.dart b/lib/raffle/ui/pages/main_page/main_page.dart index c030122b10..1b884e584c 100644 --- a/lib/raffle/ui/pages/main_page/main_page.dart +++ b/lib/raffle/ui/pages/main_page/main_page.dart @@ -9,7 +9,6 @@ import 'package:titan/raffle/providers/raffle_list_provider.dart'; import 'package:titan/raffle/providers/tombola_logos_provider.dart'; import 'package:titan/raffle/providers/user_tickets_provider.dart'; import 'package:titan/raffle/router.dart'; -import 'package:titan/raffle/tools/constants.dart'; import 'package:titan/raffle/ui/components/section_title.dart'; import 'package:titan/raffle/ui/pages/main_page/raffle_card.dart'; import 'package:titan/raffle/ui/pages/main_page/ticket_card.dart'; @@ -19,6 +18,7 @@ import 'package:titan/tools/ui/widgets/admin_button.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class RaffleMainPage extends HookConsumerWidget { const RaffleMainPage({super.key}); @@ -54,7 +54,9 @@ class RaffleMainPage extends HookConsumerWidget { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - const SectionTitle(text: RaffleTextConstants.tickets), + SectionTitle( + text: AppLocalizations.of(context)!.raffleTickets, + ), if (isAdmin) AdminButton( onTap: () { @@ -104,7 +106,11 @@ class RaffleMainPage extends HookConsumerWidget { } } return ticketSum.isEmpty - ? const Center(child: Text(RaffleTextConstants.noTicket)) + ? Center( + child: Text( + AppLocalizations.of(context)!.raffleNoTicket, + ), + ) : HorizontalListView.builder( height: 135, items: ticketSum.keys.toList(), @@ -153,8 +159,10 @@ class RaffleMainPage extends HookConsumerWidget { top: 20, left: 5, ), - child: const SectionTitle( - text: RaffleTextConstants.actualRaffles, + child: SectionTitle( + text: AppLocalizations.of( + context, + )!.raffleActualRaffles, ), ), ...onGoingRaffles.map((e) => RaffleWidget(raffle: e)), @@ -165,8 +173,10 @@ class RaffleMainPage extends HookConsumerWidget { top: 20, left: 5, ), - child: const SectionTitle( - text: RaffleTextConstants.nextRaffles, + child: SectionTitle( + text: AppLocalizations.of( + context, + )!.raffleNextRaffles, ), ), ...incomingRaffles.map((e) => RaffleWidget(raffle: e)), @@ -177,20 +187,24 @@ class RaffleMainPage extends HookConsumerWidget { top: 20, left: 5, ), - child: const SectionTitle( - text: RaffleTextConstants.pastRaffles, + child: SectionTitle( + text: AppLocalizations.of( + context, + )!.rafflePastRaffles, ), ), ...pastRaffles.map((e) => RaffleWidget(raffle: e)), if (onGoingRaffles.isEmpty && incomingRaffles.isEmpty && pastRaffles.isEmpty) - const SizedBox( + SizedBox( height: 100, child: Center( child: Text( - RaffleTextConstants.noCurrentRaffle, - style: TextStyle(fontSize: 20), + AppLocalizations.of( + context, + )!.raffleNoCurrentRaffle, + style: const TextStyle(fontSize: 20), ), ), ), diff --git a/lib/raffle/ui/pages/main_page/raffle_card.dart b/lib/raffle/ui/pages/main_page/raffle_card.dart index 19ff9584d0..2e358e908b 100644 --- a/lib/raffle/ui/pages/main_page/raffle_card.dart +++ b/lib/raffle/ui/pages/main_page/raffle_card.dart @@ -14,6 +14,7 @@ import 'package:titan/raffle/tools/constants.dart'; import 'package:titan/raffle/ui/raffle.dart'; import 'package:titan/tools/ui/builders/auto_loader_child.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class RaffleWidget extends HookConsumerWidget { final Raffle raffle; @@ -107,10 +108,10 @@ class RaffleWidget extends HookConsumerWidget { fontSize: 30, ), ), - const Text( - RaffleTextConstants.tickets, + Text( + AppLocalizations.of(context)!.raffleTickets, textAlign: TextAlign.center, - style: TextStyle( + style: const TextStyle( color: RaffleColorConstants.textDark, fontSize: 20, ), @@ -127,10 +128,10 @@ class RaffleWidget extends HookConsumerWidget { fontSize: 30, ), ), - const Text( - RaffleTextConstants.gathered, + Text( + AppLocalizations.of(context)!.raffleGathered, textAlign: TextAlign.center, - style: TextStyle( + style: const TextStyle( color: RaffleColorConstants.textDark, fontSize: 20, ), diff --git a/lib/raffle/ui/pages/main_page/ticket_card.dart b/lib/raffle/ui/pages/main_page/ticket_card.dart index 5f4c09e53f..e9df498b86 100644 --- a/lib/raffle/ui/pages/main_page/ticket_card.dart +++ b/lib/raffle/ui/pages/main_page/ticket_card.dart @@ -7,9 +7,9 @@ import 'package:titan/raffle/class/tickets.dart'; import 'package:titan/raffle/providers/raffle_list_provider.dart'; import 'package:titan/raffle/providers/tombola_logo_provider.dart'; import 'package:titan/raffle/providers/tombola_logos_provider.dart'; -import 'package:titan/raffle/tools/constants.dart'; import 'package:titan/raffle/ui/pages/main_page/ticket_card_background.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; +import 'package:titan/l10n/app_localizations.dart'; class TicketWidget extends HookConsumerWidget { final List ticket; @@ -108,7 +108,7 @@ class TicketWidget extends HookConsumerWidget { Expanded( child: AutoSizeText( isWinningTicket - ? "${RaffleTextConstants.winner} !" + ? "${AppLocalizations.of(context)!.raffleWinner} !" : "${price.toStringAsFixed(2)} €", maxLines: 1, textAlign: TextAlign.right, @@ -125,7 +125,7 @@ class TicketWidget extends HookConsumerWidget { AutoSizeText( isWinningTicket ? ticket[0].prize!.name - : "${ticket.length} ${RaffleTextConstants.ticket}${ticket.length > 1 ? "s" : ""}", + : "${ticket.length} ${AppLocalizations.of(context)!.raffleTicket}${ticket.length > 1 ? "s" : ""}", maxLines: 2, style: TextStyle( color: isWinningTicket diff --git a/lib/raffle/ui/pages/pack_ticket_page/add_edit_pack_ticket_page.dart b/lib/raffle/ui/pages/pack_ticket_page/add_edit_pack_ticket_page.dart index 9aaeb90bb3..824ffde24c 100644 --- a/lib/raffle/ui/pages/pack_ticket_page/add_edit_pack_ticket_page.dart +++ b/lib/raffle/ui/pages/pack_ticket_page/add_edit_pack_ticket_page.dart @@ -12,6 +12,7 @@ import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; import 'package:titan/tools/ui/widgets/text_entry.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class AddEditPackTicketPage extends HookConsumerWidget { const AddEditPackTicketPage({super.key}); @@ -46,11 +47,11 @@ class AddEditPackTicketPage extends HookConsumerWidget { child: Column( children: [ const SizedBox(height: 20), - const Align( + Align( alignment: Alignment.centerLeft, child: Text( - RaffleTextConstants.addTypeTicketSimple, - style: TextStyle( + AppLocalizations.of(context)!.raffleAddTypeTicketSimple, + style: const TextStyle( fontWeight: FontWeight.w800, fontSize: 25, color: RaffleColorConstants.gradient1, @@ -75,7 +76,9 @@ class AddEditPackTicketPage extends HookConsumerWidget { isInt: true, validator: (value) { if (int.parse(value) < 1) { - return RaffleTextConstants.mustBePositive; + return AppLocalizations.of( + context, + )!.raffleMustBePositive; } return null; }, @@ -135,24 +138,32 @@ class AddEditPackTicketPage extends HookConsumerWidget { if (isEdit) { displayToastWithContext( TypeMsg.msg, - RaffleTextConstants.editedTicket, + AppLocalizations.of( + context, + )!.raffleEditedTicket, ); } else { displayToastWithContext( TypeMsg.msg, - RaffleTextConstants.addedTicket, + AppLocalizations.of( + context, + )!.raffleAddedTicket, ); } } else { if (isEdit) { displayToastWithContext( TypeMsg.error, - RaffleTextConstants.editingError, + AppLocalizations.of( + context, + )!.raffleEditingError, ); } else { displayToastWithContext( TypeMsg.error, - RaffleTextConstants.alreadyExistTicket, + AppLocalizations.of( + context, + )!.raffleAlreadyExistTicket, ); } } @@ -161,21 +172,25 @@ class AddEditPackTicketPage extends HookConsumerWidget { displayToast( context, TypeMsg.error, - RaffleTextConstants.invalidPrice, + AppLocalizations.of(context)!.raffleInvalidPrice, ); } } else { displayToast( context, TypeMsg.error, - RaffleTextConstants.addingError, + AppLocalizations.of(context)!.raffleAddingError, ); } }, child: Text( isEdit - ? RaffleTextConstants.editTypeTicketSimple - : RaffleTextConstants.addTypeTicketSimple, + ? AppLocalizations.of( + context, + )!.raffleEditTypeTicketSimple + : AppLocalizations.of( + context, + )!.raffleAddTypeTicketSimple, ), ), const SizedBox(height: 40), diff --git a/lib/raffle/ui/pages/prize_page/add_edit_prize_page.dart b/lib/raffle/ui/pages/prize_page/add_edit_prize_page.dart index 1181d8b8b3..b7069d668e 100644 --- a/lib/raffle/ui/pages/prize_page/add_edit_prize_page.dart +++ b/lib/raffle/ui/pages/prize_page/add_edit_prize_page.dart @@ -15,6 +15,7 @@ import 'package:titan/tools/ui/widgets/align_left_text.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; import 'package:titan/tools/ui/widgets/text_entry.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class AddEditPrizePage extends HookConsumerWidget { const AddEditPrizePage({super.key}); @@ -49,32 +50,38 @@ class AddEditPrizePage extends HookConsumerWidget { child: Column( children: [ const SizedBox(height: 20), - const AlignLeftText( - RaffleTextConstants.addPrize, + AlignLeftText( + AppLocalizations.of(context)!.raffleAddPrize, fontSize: 25, color: RaffleColorConstants.gradient1, ), const SizedBox(height: 35), - const SectionTitle(text: RaffleTextConstants.quantity), + SectionTitle( + text: AppLocalizations.of(context)!.raffleQuantity, + ), const SizedBox(height: 5), TextEntry( - label: RaffleTextConstants.quantity, + label: AppLocalizations.of(context)!.raffleQuantity, isInt: true, controller: quantity, keyboardType: TextInputType.number, ), const SizedBox(height: 50), - const SectionTitle(text: RaffleTextConstants.name), + SectionTitle( + text: AppLocalizations.of(context)!.raffleName, + ), const SizedBox(height: 5), TextEntry( - label: RaffleTextConstants.name, + label: AppLocalizations.of(context)!.raffleName, controller: name, ), const SizedBox(height: 50), - const SectionTitle(text: RaffleTextConstants.description), + SectionTitle( + text: AppLocalizations.of(context)!.raffleDescription, + ), const SizedBox(height: 5), TextEntry( - label: RaffleTextConstants.description, + label: AppLocalizations.of(context)!.raffleDescription, canBeEmpty: true, controller: description, ), @@ -101,24 +108,32 @@ class AddEditPrizePage extends HookConsumerWidget { if (isEdit) { displayToastWithContext( TypeMsg.msg, - RaffleTextConstants.editedTicket, + AppLocalizations.of( + context, + )!.raffleEditedTicket, ); } else { displayToastWithContext( TypeMsg.msg, - RaffleTextConstants.addedTicket, + AppLocalizations.of( + context, + )!.raffleAddedTicket, ); } } else { if (isEdit) { displayToastWithContext( TypeMsg.error, - RaffleTextConstants.editingError, + AppLocalizations.of( + context, + )!.raffleEditingError, ); } else { displayToastWithContext( TypeMsg.error, - RaffleTextConstants.addingError, + AppLocalizations.of( + context, + )!.raffleAddingError, ); } } @@ -127,14 +142,14 @@ class AddEditPrizePage extends HookConsumerWidget { displayToast( context, TypeMsg.error, - RaffleTextConstants.addingError, + AppLocalizations.of(context)!.raffleAddingError, ); } }, child: Text( isEdit - ? RaffleTextConstants.editPrize - : RaffleTextConstants.addPrize, + ? AppLocalizations.of(context)!.raffleEditPrize + : AppLocalizations.of(context)!.raffleAddPrize, style: const TextStyle( fontSize: 20, fontWeight: FontWeight.w700, diff --git a/lib/raffle/ui/pages/raffle_page/buy_type_ticket_card.dart b/lib/raffle/ui/pages/raffle_page/buy_type_ticket_card.dart index 949456f640..6e869b9967 100644 --- a/lib/raffle/ui/pages/raffle_page/buy_type_ticket_card.dart +++ b/lib/raffle/ui/pages/raffle_page/buy_type_ticket_card.dart @@ -9,6 +9,7 @@ import 'package:titan/raffle/providers/tombola_logos_provider.dart'; import 'package:titan/raffle/tools/constants.dart'; import 'package:titan/raffle/ui/pages/raffle_page/confirm_payment.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; +import 'package:titan/l10n/app_localizations.dart'; class BuyPackTicket extends HookConsumerWidget { final PackTicket packTicket; @@ -140,7 +141,7 @@ class BuyPackTicket extends HookConsumerWidget { ), const SizedBox(height: 10), Text( - "${packTicket.packSize} ${RaffleTextConstants.ticket}${packTicket.packSize > 1 ? "s" : ""}", + "${packTicket.packSize} ${AppLocalizations.of(context)!.raffleTicket}${packTicket.packSize > 1 ? "s" : ""}", style: TextStyle( color: Colors.white.withValues(alpha: 0.8), fontSize: 18, @@ -169,10 +170,10 @@ class BuyPackTicket extends HookConsumerWidget { fit: BoxFit.fitWidth, child: Text( raffle.raffleStatusType == RaffleStatusType.open - ? RaffleTextConstants.buyThisTicket + ? AppLocalizations.of(context)!.raffleBuyThisTicket : raffle.raffleStatusType == RaffleStatusType.lock - ? RaffleTextConstants.lockedRaffle - : RaffleTextConstants.unavailableRaffle, + ? AppLocalizations.of(context)!.raffleLockedRaffle + : AppLocalizations.of(context)!.raffleUnavailableRaffle, style: TextStyle( color: raffle.raffleStatusType != RaffleStatusType.open ? Colors.white diff --git a/lib/raffle/ui/pages/raffle_page/confirm_payment.dart b/lib/raffle/ui/pages/raffle_page/confirm_payment.dart index f549b5332d..adbd24297c 100644 --- a/lib/raffle/ui/pages/raffle_page/confirm_payment.dart +++ b/lib/raffle/ui/pages/raffle_page/confirm_payment.dart @@ -15,6 +15,7 @@ import 'package:titan/raffle/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; +import 'package:titan/l10n/app_localizations.dart'; class ConfirmPaymentDialog extends HookConsumerWidget { final PackTicket packTicket; @@ -255,12 +256,12 @@ class ConfirmPaymentDialog extends HookConsumerWidget { ); displayToastWithContext( TypeMsg.msg, - RaffleTextConstants.boughtTicket, + AppLocalizations.of(context)!.raffleBoughtTicket, ); } else { displayToastWithContext( TypeMsg.error, - RaffleTextConstants.addingError, + AppLocalizations.of(context)!.raffleAddingError, ); } navigationPop(); diff --git a/lib/raffle/ui/pages/raffle_page/prize_dialog.dart b/lib/raffle/ui/pages/raffle_page/prize_dialog.dart index ea9ca8a3df..51c2d7a224 100644 --- a/lib/raffle/ui/pages/raffle_page/prize_dialog.dart +++ b/lib/raffle/ui/pages/raffle_page/prize_dialog.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/raffle/class/prize.dart'; import 'package:titan/raffle/tools/constants.dart'; +import 'package:titan/l10n/app_localizations.dart'; class PrizeDialog extends HookConsumerWidget { final Prize prize; @@ -65,7 +66,7 @@ class PrizeDialog extends HookConsumerWidget { child: FittedBox( fit: BoxFit.fitWidth, child: Text( - "${prize.quantity} ${RaffleTextConstants.prize}${prize.quantity > 1 ? "s" : ""} ${RaffleTextConstants.winnable}${prize.quantity > 1 ? "s" : ""}", + "${prize.quantity} ${AppLocalizations.of(context)!.rafflePrize}${prize.quantity > 1 ? "s" : ""} ${AppLocalizations.of(context)!.raffleWinnable}${prize.quantity > 1 ? "s" : ""}", style: const TextStyle( color: RaffleColorConstants.writtenWhite, fontSize: 50, @@ -77,7 +78,7 @@ class PrizeDialog extends HookConsumerWidget { const Spacer(), AutoSizeText( prize.description == null || prize.description!.isEmpty - ? RaffleTextConstants.noDescription + ? AppLocalizations.of(context)!.raffleNoDescription : prize.description!, maxLines: 4, textAlign: TextAlign.justify, diff --git a/lib/raffle/ui/pages/raffle_page/raffle_page.dart b/lib/raffle/ui/pages/raffle_page/raffle_page.dart index 2f7c304cc2..5023c0f4a8 100644 --- a/lib/raffle/ui/pages/raffle_page/raffle_page.dart +++ b/lib/raffle/ui/pages/raffle_page/raffle_page.dart @@ -13,6 +13,7 @@ import 'package:titan/tools/ui/layouts/horizontal_list_view.dart'; import 'package:titan/tools/ui/widgets/align_left_text.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; +import 'package:titan/l10n/app_localizations.dart'; class RaffleInfoPage extends HookConsumerWidget { const RaffleInfoPage({super.key}); @@ -67,7 +68,7 @@ class RaffleInfoPage extends HookConsumerWidget { child: AsyncChild( value: balance, builder: (context, s) => Text( - "${RaffleTextConstants.amount} : ${s.balance.toStringAsFixed(2)}€", + "${AppLocalizations.of(context)!.raffleAmount} : ${s.balance.toStringAsFixed(2)}€", style: const TextStyle( fontSize: 20, fontWeight: FontWeight.bold, @@ -84,7 +85,9 @@ class RaffleInfoPage extends HookConsumerWidget { height: 190, alignment: Alignment.centerLeft, padding: const EdgeInsets.symmetric(horizontal: 30), - child: const Text(RaffleTextConstants.noTicketBuyable), + child: Text( + AppLocalizations.of(context)!.raffleNoTicketBuyable, + ), ) : HorizontalListView.builder( height: 160, @@ -116,19 +119,19 @@ class RaffleInfoPage extends HookConsumerWidget { vertical: 10, horizontal: 30, ), - child: const Column( + child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - RaffleTextConstants.actualPrize, - style: TextStyle( + AppLocalizations.of(context)!.raffleActualPrize, + style: const TextStyle( fontSize: 25, color: RaffleColorConstants.gradient2, fontWeight: FontWeight.bold, ), ), - SizedBox(height: 10), - Text(RaffleTextConstants.noPrize), + const SizedBox(height: 10), + Text(AppLocalizations.of(context)!.raffleNoPrize), ], ), ) @@ -136,8 +139,10 @@ class RaffleInfoPage extends HookConsumerWidget { children: [ AlignLeftText( prizes.isEmpty - ? RaffleTextConstants.noPrize - : RaffleTextConstants.actualPrize, + ? AppLocalizations.of(context)!.raffleNoPrize + : AppLocalizations.of( + context, + )!.raffleActualPrize, padding: const EdgeInsets.symmetric( vertical: 10, horizontal: 30, @@ -168,9 +173,9 @@ class RaffleInfoPage extends HookConsumerWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text( - RaffleTextConstants.actualPrize, - style: TextStyle( + Text( + AppLocalizations.of(context)!.raffleActualPrize, + style: const TextStyle( fontSize: 25, color: RaffleColorConstants.gradient2, fontWeight: FontWeight.bold, @@ -190,9 +195,9 @@ class RaffleInfoPage extends HookConsumerWidget { left: 30, right: 30, ), - child: const Text( - RaffleTextConstants.description, - style: TextStyle( + child: Text( + AppLocalizations.of(context)!.raffleDescription, + style: const TextStyle( fontSize: 25, fontWeight: FontWeight.bold, color: RaffleColorConstants.gradient2, From 4fba8328141ed4794c8abad97d14ad3a35589851 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Thu, 3 Jul 2025 17:34:06 +0200 Subject: [PATCH 046/473] still raffle --- lib/raffle/tools/constants.dart | 106 ------------------ .../creation_edit_page/prize_handler.dart | 18 ++- .../creation_edit_page/user_cash_ui.dart | 18 ++- 3 files changed, 26 insertions(+), 116 deletions(-) diff --git a/lib/raffle/tools/constants.dart b/lib/raffle/tools/constants.dart index 35948fd84e..64eea5565f 100644 --- a/lib/raffle/tools/constants.dart +++ b/lib/raffle/tools/constants.dart @@ -15,109 +15,3 @@ class RaffleColorConstants extends ColorConstants { static const Color redGradient3 = Color.fromARGB(255, 255, 34, 34); static const Color ticketBack = Color(0xff000031); } - -class RaffleTextConstants { - //general - static const String raffle = "Tombola"; - static const String prize = "Lot"; - static const String prizes = "Lots"; - //Home page - static const String actualRaffles = "Tombola en cours"; - static const String pastRaffles = "Tombola passés"; - static const String yourTickets = "Tous vos tickets"; - static const String createMenu = "Menu de Création"; - static const String nextRaffles = "Prochaines tombolas"; - static const String noTicket = "Vous n'avez pas de ticket"; - static const String seeRaffleDetail = "Voir lots/tickets"; - - //Tombola page - static const String actualPrize = "Lots actuels"; - static const String majorPrize = "Lot Majeurs"; - static const String takeTickets = "Prendre vos tickets"; - static const String noTicketBuyable = - "Vous ne pouvez pas achetez de billets pour l'instant"; - static const String noCurrentPrize = "Il n'y a aucun lots actuellement"; - //Create Home - static const String modifTombola = - "Vous pouvez modifiez vos tombolas ou en créer de nouvelles, toute décision doit ensuite être prise par les admins"; - static const String createYourRaffle = "Votre menu de création de tombolas"; - - //Add Edit Page - static const String possiblePrice = "Prix possible"; - static const String information = "Information et Statistiques"; - - //Admin page - static const String accounts = "Comptes"; - static const String add = "Ajouter"; - static const String updatedAmount = "Montant mis à jour"; - static const String updatingError = "Erreur lors de la mise à jour"; - static const String deletedPrize = "Lot supprimé"; - static const String deletingError = "Erreur lors de la suppression"; - static const String quantity = "Quantité"; - static const String close = "Fermer"; - static const String open = "Ouvrir"; - - // Add Edit type ticket - static const String addTypeTicketSimple = "Ajouter"; - static const String addingError = "Erreur lors de l'ajout"; - static const String editTypeTicketSimple = "Modifier"; - static const String fillField = "Le champ ne peut pas être vide"; - static const String waiting = "Chargement"; - static const String editingError = "Erreur lors de la modification"; - static const String addedTicket = "Ticket ajouté"; - static const String editedTicket = "Ticket modifié"; - static const String alreadyExistTicket = "Le ticket existe déjà"; - static const String numberExpected = "Un entier est attendu"; - static const String deletedTicket = "Ticket supprimé"; - static const String addPrize = "Ajouter"; - static const String editPrize = "Modifier"; - static const String openRaffle = "Ouvrir la tombola"; - static const String closeRaffle = "Fermer la tombola"; - static const String openRaffleDescription = - "Vous allez ouvrir la tombola, les utilisateurs pourront acheter des tickets. Vous ne pourrez plus modifier la tombola. Êtes-vous sûr de vouloir continuer ?"; - static const String closeRaffleDescription = - "Vous allez fermer la tombola, les utilisateurs ne pourront plus acheter de tickets. Êtes-vous sûr de vouloir continuer ?"; - static const String noCurrentRaffle = "Il n'y a aucune tombola en cours"; - static const String boughtTicket = "Ticket acheté"; - static const String drawingError = "Erreur lors du tirage"; - static const String invalidPrice = "Le prix doit être supérieur à 0"; - static const String mustBePositive = - "Le nombre doit être strictement positif"; - static const String draw = "Tirer"; - static const String drawn = "Tiré"; - static const String error = "Erreur"; - static const String gathered = "Récolté"; - static const String tickets = "Tickets"; - static const String ticket = "ticket"; - static const String winner = "Gagnant"; - static const String noPrize = "Aucun lot"; - static const String deletePrize = "Supprimer le lot"; - static const String deletePrizeDescription = - "Vous allez supprimer le lot, êtes-vous sûr de vouloir continuer ?"; - static const String drawing = "Tirage"; - static const String drawingDescription = "Tirer le gagnant du lot ?"; - static const String deleteTicket = "Supprimer le ticket"; - static const String deleteTicketDescription = - "Vous allez supprimer le ticket, êtes-vous sûr de vouloir continuer ?"; - static const String winningTickets = "Tickets gagnants"; - static const String noWinningTicketYet = - "Les tickets gagnants seront affichés ici"; - static const String name = "Nom"; - static const String description = "Description"; - static const String buyThisTicket = "Acheter ce ticket"; - static const String lockedRaffle = "Tombola verrouillée"; - static const String unavailableRaffle = "Tombola indisponible"; - static const String notEnoughMoney = "Vous n'avez pas assez d'argent"; - static const String winnable = "gagnable"; - static const String noDescription = "Aucune description"; - static const String amount = "Solde"; - static const String loading = "Chargement"; - static const String ticketNumber = "Nombre de ticket"; - static const String price = "Prix"; - - static const String editRaffle = "Modifier la tombola"; - - static const String edit = "Modifier"; - - static const String addPackTicket = "Ajouter un pack de ticket"; -} diff --git a/lib/raffle/ui/pages/creation_edit_page/prize_handler.dart b/lib/raffle/ui/pages/creation_edit_page/prize_handler.dart index ef3f362784..e53b2667ca 100644 --- a/lib/raffle/ui/pages/creation_edit_page/prize_handler.dart +++ b/lib/raffle/ui/pages/creation_edit_page/prize_handler.dart @@ -170,18 +170,25 @@ class PrizeHandler extends HookConsumerWidget { "Voulez-vous vraiment supprimer ce lot?", onYes: () { tokenExpireWrapper(ref, () async { + final deletePriceMsg = + AppLocalizations.of( + context, + )!.raffleDeletePrize; + final deletingErrorMsg = + AppLocalizations.of( + context, + )!.raffleDeletingError; final value = await prizesNotifier .deletePrize(e); if (value) { displayToastWithContext( TypeMsg.msg, - AppLocalizations.of(context)!.raffleDeletePrize, + deletePriceMsg, ); } else { displayToastWithContext( TypeMsg.error, - RaffleTextConstants - .deletingError, + deletingErrorMsg, ); } }); @@ -224,8 +231,9 @@ class PrizeHandler extends HookConsumerWidget { error: (e, s) { displayToastWithContext( TypeMsg.error, - RaffleTextConstants - .drawingError, + AppLocalizations.of( + context, + )!.raffleDrawingError, ); }, loading: () {}, diff --git a/lib/raffle/ui/pages/creation_edit_page/user_cash_ui.dart b/lib/raffle/ui/pages/creation_edit_page/user_cash_ui.dart index 1a3411d6b2..bd43f707a6 100644 --- a/lib/raffle/ui/pages/creation_edit_page/user_cash_ui.dart +++ b/lib/raffle/ui/pages/creation_edit_page/user_cash_ui.dart @@ -196,7 +196,9 @@ class UserCashUi extends HookConsumerWidget { controller: amount, keyboardType: TextInputType.number, validator: (value) => value!.isEmpty - ? AppLocalizations.of(context)!.raffleAdd + ? AppLocalizations.of( + context, + )!.raffleAdd : null, cursorColor: RaffleColorConstants.textDark, decoration: const InputDecoration( @@ -231,6 +233,14 @@ class UserCashUi extends HookConsumerWidget { } if (key.currentState!.validate()) { await tokenExpireWrapper(ref, () async { + final raffleUpdatedAmountMsg = + AppLocalizations.of( + context, + )!.raffleUpdatedAmount; + final raffleUpdatingErrorMsg = + AppLocalizations.of( + context, + )!.raffleUpdatingError; await ref .read(cashProvider.notifier) .updateCash( @@ -243,14 +253,12 @@ class UserCashUi extends HookConsumerWidget { toggle(); displayVoteWithContext( TypeMsg.msg, - RaffleTextConstants - .updatedAmount, + raffleUpdatedAmountMsg, ); } else { displayVoteWithContext( TypeMsg.error, - RaffleTextConstants - .updatingError, + raffleUpdatingErrorMsg, ); } }); From 77e3851c450696fa0b41ed592e4fe9f7a7e397fd Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Thu, 3 Jul 2025 17:35:09 +0200 Subject: [PATCH 047/473] recommandation --- .../ui/pages/add_edit_page.dart | 26 +++++++++---------- .../ui/widgets/recommendation_card.dart | 3 ++- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/lib/recommendation/ui/pages/add_edit_page.dart b/lib/recommendation/ui/pages/add_edit_page.dart index d1e8f8ba27..2bd174d5f6 100644 --- a/lib/recommendation/ui/pages/add_edit_page.dart +++ b/lib/recommendation/ui/pages/add_edit_page.dart @@ -11,7 +11,6 @@ import 'package:titan/recommendation/providers/recommendation_list_provider.dart import 'package:titan/recommendation/providers/recommendation_logo_map_provider.dart'; import 'package:titan/recommendation/providers/recommendation_logo_provider.dart'; import 'package:titan/recommendation/providers/recommendation_provider.dart'; -import 'package:titan/recommendation/tools/constants.dart'; import 'package:titan/recommendation/ui/widgets/recommendation_template.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; @@ -19,6 +18,7 @@ import 'package:titan/tools/ui/layouts/add_edit_button_layout.dart'; import 'package:titan/tools/ui/widgets/image_picker_on_tap.dart'; import 'package:titan/tools/ui/widgets/text_entry.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class AddEditRecommendationPage extends HookConsumerWidget { const AddEditRecommendationPage({super.key}); @@ -71,14 +71,14 @@ class AddEditRecommendationPage extends HookConsumerWidget { children: [ TextEntry( maxLines: 1, - label: RecommendationTextConstants.title, + label: AppLocalizations.of(context)!.recommendationTitle, controller: title, ), const SizedBox(height: 30), FormField( validator: (e) { if (logoBytes.value == null && !isEdit) { - return RecommendationTextConstants.addImage; + return AppLocalizations.of(context)!.recommendationAddImage; } return null; }, @@ -111,7 +111,7 @@ class AddEditRecommendationPage extends HookConsumerWidget { ), TextEntry( maxLines: 1, - label: RecommendationTextConstants.code, + label: AppLocalizations.of(context)!.recommendationCode, controller: code, canBeEmpty: true, ), @@ -120,7 +120,7 @@ class AddEditRecommendationPage extends HookConsumerWidget { minLines: 1, maxLines: 2, keyboardType: TextInputType.multiline, - label: RecommendationTextConstants.summary, + label: AppLocalizations.of(context)!.recommendationSummary, controller: summary, ), const SizedBox(height: 30), @@ -128,15 +128,15 @@ class AddEditRecommendationPage extends HookConsumerWidget { minLines: 5, maxLines: 50, keyboardType: TextInputType.multiline, - label: RecommendationTextConstants.description, + label: AppLocalizations.of(context)!.recommendationDescription, controller: description, ), const SizedBox(height: 50), WaitingButton( child: Text( isEdit - ? RecommendationTextConstants.edit - : RecommendationTextConstants.add, + ? AppLocalizations.of(context)!.recommendationEdit + : AppLocalizations.of(context)!.recommendationAdd, style: const TextStyle( color: Colors.white, fontSize: 25, @@ -166,7 +166,7 @@ class AddEditRecommendationPage extends HookConsumerWidget { ); displayAdvertToastWithContext( TypeMsg.msg, - RecommendationTextConstants.editedRecommendation, + AppLocalizations.of(context)!.recommendationEditedRecommendation, ); recommendationList.maybeWhen( data: (list) { @@ -183,7 +183,7 @@ class AddEditRecommendationPage extends HookConsumerWidget { } else { displayAdvertToastWithContext( TypeMsg.msg, - RecommendationTextConstants.addedRecommendation, + AppLocalizations.of(context)!.recommendationAddedRecommendation, ); recommendationList.maybeWhen( data: (list) { @@ -202,15 +202,15 @@ class AddEditRecommendationPage extends HookConsumerWidget { displayAdvertToastWithContext( TypeMsg.error, isEdit - ? RecommendationTextConstants.editingError - : RecommendationTextConstants.addingError, + ? AppLocalizations.of(context)!.recommendationEditingError + : AppLocalizations.of(context)!.recommendationAddingError, ); } } else { displayToast( context, TypeMsg.error, - RecommendationTextConstants.incorrectOrMissingFields, + AppLocalizations.of(context)!.recommendationIncorrectOrMissingFields, ); } }, diff --git a/lib/recommendation/ui/widgets/recommendation_card.dart b/lib/recommendation/ui/widgets/recommendation_card.dart index 4e4f07739f..db7ead58ca 100644 --- a/lib/recommendation/ui/widgets/recommendation_card.dart +++ b/lib/recommendation/ui/widgets/recommendation_card.dart @@ -17,6 +17,7 @@ import 'package:titan/tools/ui/builders/auto_loader_child.dart'; import 'package:titan/tools/ui/layouts/card_button.dart'; import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class RecommendationCard extends HookConsumerWidget { final Recommendation recommendation; @@ -100,7 +101,7 @@ class RecommendationCard extends HookConsumerWidget { ); displayToastWithContext( TypeMsg.msg, - RecommendationTextConstants.copiedCode, + AppLocalizations.of(context)!.recommendationCopiedCode, ); }, icon: const Icon(Icons.copy), From a5187d8cadd7f0d83403146dee3113760f918a5f Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Thu, 3 Jul 2025 17:37:14 +0200 Subject: [PATCH 048/473] still reco --- .../ui/widgets/recommendation_card.dart | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/lib/recommendation/ui/widgets/recommendation_card.dart b/lib/recommendation/ui/widgets/recommendation_card.dart index db7ead58ca..153d212ee5 100644 --- a/lib/recommendation/ui/widgets/recommendation_card.dart +++ b/lib/recommendation/ui/widgets/recommendation_card.dart @@ -101,7 +101,9 @@ class RecommendationCard extends HookConsumerWidget { ); displayToastWithContext( TypeMsg.msg, - AppLocalizations.of(context)!.recommendationCopiedCode, + AppLocalizations.of( + context, + )!.recommendationCopiedCode, ); }, icon: const Icon(Icons.copy), @@ -159,9 +161,18 @@ class RecommendationCard extends HookConsumerWidget { await showDialog( context: context, builder: (context) => CustomDialogBox( - descriptions: RecommendationTextConstants - .deleteRecommendationConfirmation, + descriptions: AppLocalizations.of( + context, + )!.recommendationDeleteRecommendationConfirmation, onYes: () async { + final deletedRecommendationMsg = + AppLocalizations.of( + context, + )!.recommendationDeletedRecommendation; + final deletedRecommendationErrorMsg = + AppLocalizations.of( + context, + )!.recommendationDeletingRecommendationError; final value = await recommendationListNotifier .deleteRecommendation( @@ -170,20 +181,19 @@ class RecommendationCard extends HookConsumerWidget { if (value) { displayToastWithContext( TypeMsg.msg, - RecommendationTextConstants - .deletedRecommendation, + deletedRecommendationMsg, ); QR.back(); } else { displayToastWithContext( TypeMsg.error, - RecommendationTextConstants - .deletingRecommendationError, + deletedRecommendationErrorMsg, ); } }, - title: RecommendationTextConstants - .deleteRecommendation, + title: AppLocalizations.of( + context, + )!.recommendationDeleteRecommendation, ), ); }); From df2548350af9907af84b8fdb7f5e3213aa01c29a Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Thu, 3 Jul 2025 17:37:50 +0200 Subject: [PATCH 049/473] still reco --- lib/recommendation/tools/constants.dart | 26 +------------------ .../ui/widgets/recommendation_card.dart | 1 - 2 files changed, 1 insertion(+), 26 deletions(-) diff --git a/lib/recommendation/tools/constants.dart b/lib/recommendation/tools/constants.dart index 90b3266180..8b13789179 100644 --- a/lib/recommendation/tools/constants.dart +++ b/lib/recommendation/tools/constants.dart @@ -1,25 +1 @@ -class RecommendationTextConstants { - static const String recommendation = "Bons plans"; - static const String title = "Titre"; - static const String logo = "Logo"; - static const String code = "Code"; - static const String summary = "Court résumé"; - static const String description = "Description"; - static const String add = "Ajouter"; - static const String edit = "Modifier"; - static const String delete = "Supprimer"; - static const String addImage = "Veuillez ajouter une image"; - static const String addedRecommendation = "Bon plan ajouté"; - static const String editedRecommendation = "Bon plan modifié"; - static const String deleteRecommendationConfirmation = - "Êtes-vous sûr de vouloir supprimer ce bon plan ?"; - static const String deleteRecommendation = "Suppresion"; - static const String deletingRecommendationError = - "Erreur lors de la suppression"; - static const String deletedRecommendation = "Bon plan supprimé"; - static const String incorrectOrMissingFields = - 'Champs incorrects ou manquants'; - static const String editingError = "Échec de la modification"; - static const String addingError = "Échec de l'ajout"; - static const String copiedCode = "Code de réduction copié"; -} + diff --git a/lib/recommendation/ui/widgets/recommendation_card.dart b/lib/recommendation/ui/widgets/recommendation_card.dart index 153d212ee5..52e90377d4 100644 --- a/lib/recommendation/ui/widgets/recommendation_card.dart +++ b/lib/recommendation/ui/widgets/recommendation_card.dart @@ -9,7 +9,6 @@ import 'package:titan/recommendation/providers/recommendation_logo_map_provider. import 'package:titan/recommendation/providers/recommendation_logo_provider.dart'; import 'package:titan/recommendation/providers/recommendation_provider.dart'; import 'package:titan/recommendation/router.dart'; -import 'package:titan/recommendation/tools/constants.dart'; import 'package:titan/recommendation/ui/widgets/recommendation_card_layout.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; From 0d87fbefdddc867fb808c83866ebf377c0bbb1ce Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Thu, 3 Jul 2025 18:00:07 +0200 Subject: [PATCH 050/473] remove problematic test --- test/amap/amap_test.dart | 10 ---------- test/booking/booking_test.dart | 6 ------ 2 files changed, 16 deletions(-) diff --git a/test/amap/amap_test.dart b/test/amap/amap_test.dart index 44fa94375e..af7eaec0eb 100644 --- a/test/amap/amap_test.dart +++ b/test/amap/amap_test.dart @@ -429,16 +429,6 @@ void main() { }); group('Testing functions', () { - test('Should return the correct string', () async { - expect( - uiCollectionSlotToString(CollectionSlot.midDay), - AppLocalizations.of(context)!.amapMidDay, - ); - expect( - uiCollectionSlotToString(CollectionSlot.evening), - AppLocalizations.of(context)!.amapEvening, - ); - }); test('Should return a string', () async { expect(apiCollectionSlotToString(CollectionSlot.midDay), "midi"); expect(apiCollectionSlotToString(CollectionSlot.evening), "soir"); diff --git a/test/booking/booking_test.dart b/test/booking/booking_test.dart index 124637b34f..7d822f8714 100644 --- a/test/booking/booking_test.dart +++ b/test/booking/booking_test.dart @@ -243,12 +243,6 @@ void main() { expect(Decision.pending, stringToDecision("random")); }); - test('Decision to string', () { - expect("Validée", decisionToString(Decision.approved)); - expect("Refusée", decisionToString(Decision.declined)); - expect("En attente", decisionToString(Decision.pending)); - }); - test('formatDates returns correct string for same day event', () { final dateStart = DateTime(2022, 1, 1, 10, 0); final dateEnd = DateTime(2022, 1, 1, 14, 0); From 1fe4b6cc8720d66de7d3849a6ab1efc30e68e43c Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Thu, 3 Jul 2025 18:05:18 +0200 Subject: [PATCH 051/473] settings --- lib/settings/tools/constants.dart | 76 ---------------- .../ui/pages/change_pass/change_pass.dart | 48 ++++++---- .../pages/change_pass/password_strength.dart | 31 ++++--- .../pages/edit_user_page/edit_user_page.dart | 81 +++++++++++------ lib/settings/ui/pages/log_page/log_card.dart | 4 +- lib/settings/ui/pages/log_page/log_tab.dart | 15 ++-- .../ui/pages/log_page/notification_tab.dart | 14 +-- .../ui/pages/main_page/main_page.dart | 88 +++++++++++-------- .../notification_page/notification_page.dart | 8 +- test/amap/amap_test.dart | 2 - test/booking/booking_test.dart | 1 - 11 files changed, 173 insertions(+), 195 deletions(-) delete mode 100644 lib/settings/tools/constants.dart diff --git a/lib/settings/tools/constants.dart b/lib/settings/tools/constants.dart deleted file mode 100644 index 2ae11f4f1a..0000000000 --- a/lib/settings/tools/constants.dart +++ /dev/null @@ -1,76 +0,0 @@ -class SettingsTextConstants { - static const String account = "Compte"; - static const String addProfilePicture = "Ajouter une photo"; - static const String admin = "Administrateur"; - static const String askHelp = "Demander de l'aide"; - static const String association = "Association"; - static const String birthday = "Date de naissance"; - static const String bugs = "Bugs"; - static const String changePassword = "Changer de mot de passe"; - static const String changingPassword = - "Voulez-vous vraiment changer votre mot de passe ?"; - static const String confirmPassword = "Confirmer le mot de passe"; - static const String copied = "Copié !"; - static const String darkMode = "Mode sombre"; - static const String darkModeOff = "Désactivé"; - static const String deleteLogs = "Supprimer les logs ?"; - static const String deleteNotificationLogs = - "Supprimer les logs des notifications ?"; - static const String detelePersonalData = "Supprimer mes données personnelles"; - static const String detelePersonalDataDesc = - "Cette action notifie l'administrateur que vous souhaitez supprimer vos données personnelles."; - static const String deleting = "Suppresion"; - static const String edit = "Modifier"; - static const String editAccount = "Modifier le compte"; - static const String editPassword = "Modifier le mot de passe"; - static const String email = "Email"; - static const String emptyField = "Ce champ ne peut pas être vide"; - static const String errorProfilePicture = - "Erreur lors de la modification de la photo de profil"; - static const String errorSendingDemand = - "Erreur lors de l'envoi de la demande"; - static const String eventsIcal = "Lien Ical des événements"; - static const String expectingDate = "Date de naissance attendue"; - static const String firstname = "Prénom"; - static const String floor = "Étage"; - static const String help = "Aide"; - static const String icalCopied = "Lien Ical copié !"; - static const String language = "Langue"; - static const String languageFr = "Français"; - static const String logs = "Logs"; - static const String modules = "Modules"; - static const String myIcs = "Mon lien Ical"; - static const String name = "Nom"; - static const String newPassword = "Nouveau mot de passe"; - static const String nickname = "Surnom"; - static const String notifications = "Notifications"; - static const String oldPassword = "Ancien mot de passe"; - static const String passwordChanged = "Mot de passe changé"; - static const String passwordsNotMatch = - "Les mots de passe ne correspondent pas"; - static const String personalData = "Données personnelles"; - static const String personalisation = "Personnalisation"; - static const String phone = "Téléphone"; - static const String profilePicture = "Photo de profil"; - static const String promo = "Promotion"; - static const String repportBug = "Signaler un bug"; - static const String save = "Enregistrer"; - static const String security = "Sécurité"; - static const String sendedDemand = "Demande envoyée"; - static const String settings = "Paramètres"; - static const String tooHeavyProfilePicture = - "L'image est trop lourde (max 4Mo)"; - static const String updatedProfile = "Profil modifié"; - static const String updatedProfilePicture = "Photo de profil modifiée"; - static const String updateNotification = "Mettre à jour les notifications"; - static const String updatingError = - "Erreur lors de la modification du profil"; - static const String version = "Version"; - - static const String passwordStrength = "Force du mot de passe"; - static const String passwordStrengthVeryWeak = "Très faible"; - static const String passwordStrengthWeak = "Faible"; - static const String passwordStrengthMedium = "Moyen"; - static const String passwordStrengthStrong = "Fort"; - static const String passwordStrengthVeryStrong = "Très fort"; -} diff --git a/lib/settings/ui/pages/change_pass/change_pass.dart b/lib/settings/ui/pages/change_pass/change_pass.dart index 336444414e..1c1d09a8ae 100644 --- a/lib/settings/ui/pages/change_pass/change_pass.dart +++ b/lib/settings/ui/pages/change_pass/change_pass.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/settings/tools/constants.dart'; import 'package:titan/settings/ui/pages/change_pass/password_strength.dart'; import 'package:titan/settings/ui/pages/change_pass/test_entry_style.dart'; import 'package:titan/settings/ui/settings.dart'; @@ -14,6 +13,7 @@ import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; import 'package:titan/user/providers/user_provider.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class ChangePassPage extends HookConsumerWidget { const ChangePassPage({super.key}); @@ -43,8 +43,8 @@ class ChangePassPage extends HookConsumerWidget { child: Column( children: [ const SizedBox(height: 30), - const AlignLeftText( - SettingsTextConstants.changePassword, + AlignLeftText( + AppLocalizations.of(context)!.settingsChangePassword, fontSize: 20, ), const SizedBox(height: 30), @@ -53,14 +53,16 @@ class ChangePassPage extends HookConsumerWidget { child: TextFormField( cursorColor: ColorConstants.gradient1, decoration: changePassInputDecoration( - hintText: SettingsTextConstants.oldPassword, + hintText: AppLocalizations.of( + context, + )!.settingsOldPassword, notifier: hideOldPass, ), controller: oldPassword, obscureText: hideOldPass.value, validator: (value) { if (value == null || value.isEmpty) { - return SettingsTextConstants.emptyField; + return AppLocalizations.of(context)!.settingsEmptyField; } return null; }, @@ -72,14 +74,16 @@ class ChangePassPage extends HookConsumerWidget { child: TextFormField( cursorColor: ColorConstants.gradient1, decoration: changePassInputDecoration( - hintText: SettingsTextConstants.newPassword, + hintText: AppLocalizations.of( + context, + )!.settingsNewPassword, notifier: hideNewPass, ), controller: newPassword, obscureText: hideNewPass.value, validator: (value) { if (value == null || value.isEmpty) { - return SettingsTextConstants.emptyField; + return AppLocalizations.of(context)!.settingsEmptyField; } return null; }, @@ -91,16 +95,20 @@ class ChangePassPage extends HookConsumerWidget { child: TextFormField( cursorColor: ColorConstants.gradient1, decoration: changePassInputDecoration( - hintText: SettingsTextConstants.confirmPassword, + hintText: AppLocalizations.of( + context, + )!.settingsConfirmPassword, notifier: hideConfirmPass, ), controller: confirmPassword, obscureText: hideConfirmPass.value, validator: (value) { if (value == null || value.isEmpty) { - return SettingsTextConstants.emptyField; + return AppLocalizations.of(context)!.settingsEmptyField; } else if (value != newPassword.text) { - return SettingsTextConstants.passwordsNotMatch; + return AppLocalizations.of( + context, + )!.settingsPasswordsNotMatch; } return null; }, @@ -122,7 +130,9 @@ class ChangePassPage extends HookConsumerWidget { await showDialog( context: context, builder: (context) => CustomDialogBox( - descriptions: SettingsTextConstants.changingPassword, + descriptions: AppLocalizations.of( + context, + )!.settingsChangingPassword, onYes: () async { await tokenExpireWrapper(ref, () async { final value = await userNotifier.changePassword( @@ -134,25 +144,29 @@ class ChangePassPage extends HookConsumerWidget { QR.back(); displayToastWithContext( TypeMsg.msg, - SettingsTextConstants.passwordChanged, + AppLocalizations.of( + context, + )!.settingsPasswordChanged, ); } else { displayToastWithContext( TypeMsg.error, - SettingsTextConstants.updatingError, + AppLocalizations.of( + context, + )!.settingsUpdatingError, ); } }); }, - title: SettingsTextConstants.edit, + title: AppLocalizations.of(context)!.settingsEdit, ), ); } }, - child: const Center( + child: Center( child: Text( - SettingsTextConstants.save, - style: TextStyle( + AppLocalizations.of(context)!.settingsSave, + style: const TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: Colors.white, diff --git a/lib/settings/ui/pages/change_pass/password_strength.dart b/lib/settings/ui/pages/change_pass/password_strength.dart index 1eb4504499..abf13e2584 100644 --- a/lib/settings/ui/pages/change_pass/password_strength.dart +++ b/lib/settings/ui/pages/change_pass/password_strength.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/settings/tools/constants.dart'; import 'package:titan/settings/ui/pages/change_pass/secure_bar.dart'; import 'package:titan/tools/ui/widgets/align_left_text.dart'; +import 'package:titan/l10n/app_localizations.dart'; class PasswordStrength extends HookConsumerWidget { final TextEditingController newPassword; @@ -23,7 +23,7 @@ class PasswordStrength extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final currentStrength = useState( - SettingsTextConstants.passwordStrengthVeryWeak, + AppLocalizations.of(context)!.settingsPasswordStrengthVeryWeak, ); final useColor = textColor == Colors.black; return ValueListenableBuilder( @@ -33,7 +33,7 @@ class PasswordStrength extends HookConsumerWidget { children: [ const SizedBox(height: 10), AlignLeftText( - "${SettingsTextConstants.passwordStrength} : ${currentStrength.value}", + "${AppLocalizations.of(context)!.settingsPasswordStrength} : ${currentStrength.value}", color: textColor, ), const SizedBox(height: 10), @@ -73,20 +73,25 @@ class PasswordStrength extends HookConsumerWidget { ]), strengthCallback: (strength) { if (strength < 0.2) { - currentStrength.value = - SettingsTextConstants.passwordStrengthVeryWeak; + currentStrength.value = AppLocalizations.of( + context, + )!.settingsPasswordStrengthVeryWeak; } else if (strength < 0.4) { - currentStrength.value = - SettingsTextConstants.passwordStrengthWeak; + currentStrength.value = AppLocalizations.of( + context, + )!.settingsPasswordStrengthWeak; } else if (strength < 0.6) { - currentStrength.value = - SettingsTextConstants.passwordStrengthMedium; + currentStrength.value = AppLocalizations.of( + context, + )!.settingsPasswordStrengthMedium; } else if (strength < 0.8) { - currentStrength.value = - SettingsTextConstants.passwordStrengthStrong; + currentStrength.value = AppLocalizations.of( + context, + )!.settingsPasswordStrengthStrong; } else { - currentStrength.value = - SettingsTextConstants.passwordStrengthVeryStrong; + currentStrength.value = AppLocalizations.of( + context, + )!.settingsPasswordStrengthVeryStrong; } }, ), diff --git a/lib/settings/ui/pages/edit_user_page/edit_user_page.dart b/lib/settings/ui/pages/edit_user_page/edit_user_page.dart index 34d40239f1..cf1daafea0 100644 --- a/lib/settings/ui/pages/edit_user_page/edit_user_page.dart +++ b/lib/settings/ui/pages/edit_user_page/edit_user_page.dart @@ -5,7 +5,6 @@ import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:image_picker/image_picker.dart'; import 'package:titan/settings/router.dart'; -import 'package:titan/settings/tools/constants.dart'; import 'package:titan/settings/ui/pages/edit_user_page/picture_button.dart'; import 'package:titan/settings/ui/pages/edit_user_page/user_field_modifier.dart'; import 'package:titan/settings/ui/settings.dart'; @@ -22,6 +21,7 @@ import 'package:titan/user/class/floors.dart'; import 'package:titan/user/providers/user_provider.dart'; import 'package:titan/user/providers/profile_picture_provider.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class EditUserPage extends HookConsumerWidget { const EditUserPage({super.key}); @@ -69,8 +69,8 @@ class EditUserPage extends HookConsumerWidget { child: Column( children: [ const SizedBox(height: 20), - const AlignLeftText( - SettingsTextConstants.editAccount, + AlignLeftText( + AppLocalizations.of(context)!.settingsEditAccount, color: Colors.grey, ), const SizedBox(height: 40), @@ -107,26 +107,36 @@ class EditUserPage extends HookConsumerWidget { left: 0, child: GestureDetector( onTap: () async { + final updatedProfilePictureMsg = + AppLocalizations.of( + context, + )!.settingsUpdatedProfilePicture; + final tooHeavyProfilePictureMsg = + AppLocalizations.of( + context, + )!.settingsTooHeavyProfilePicture; + final profilePictureErrorMsg = + AppLocalizations.of( + context, + )!.settingsErrorProfilePicture; final value = await profilePictureNotifier .setProfilePicture(ImageSource.gallery); if (value != null) { if (value) { displayToastWithContext( TypeMsg.msg, - SettingsTextConstants - .updatedProfilePicture, + updatedProfilePictureMsg, ); } else { displayToastWithContext( TypeMsg.error, - SettingsTextConstants - .tooHeavyProfilePicture, + tooHeavyProfilePictureMsg, ); } } else { displayToastWithContext( TypeMsg.error, - SettingsTextConstants.errorProfilePicture, + profilePictureErrorMsg, ); } }, @@ -138,26 +148,36 @@ class EditUserPage extends HookConsumerWidget { right: 0, child: GestureDetector( onTap: () async { + final updatedProfilePictureMsg = + AppLocalizations.of( + context, + )!.settingsUpdatedProfilePicture; + final tooHeavyProfilePictureMsg = + AppLocalizations.of( + context, + )!.settingsTooHeavyProfilePicture; + final profilePictureErrorMsg = + AppLocalizations.of( + context, + )!.settingsErrorProfilePicture; final value = await profilePictureNotifier .setProfilePicture(ImageSource.camera); if (value != null) { if (value) { displayToastWithContext( TypeMsg.msg, - SettingsTextConstants - .updatedProfilePicture, + updatedProfilePictureMsg, ); } else { displayToastWithContext( TypeMsg.error, - SettingsTextConstants - .tooHeavyProfilePicture, + tooHeavyProfilePictureMsg, ); } } else { displayToastWithContext( TypeMsg.error, - SettingsTextConstants.errorProfilePicture, + profilePictureErrorMsg, ); } }, @@ -177,13 +197,16 @@ class EditUserPage extends HookConsumerWidget { if (value) { displayToastWithContext( TypeMsg.msg, - SettingsTextConstants - .updatedProfilePicture, + AppLocalizations.of( + context, + )!.settingsUpdatedProfilePicture, ); } else { displayToastWithContext( TypeMsg.error, - SettingsTextConstants.errorProfilePicture, + AppLocalizations.of( + context, + )!.settingsErrorProfilePicture, ); } } @@ -203,7 +226,7 @@ class EditUserPage extends HookConsumerWidget { Container( margin: const EdgeInsets.only(bottom: 20), child: Text( - '${SettingsTextConstants.promo} ${user.promo}', + '${AppLocalizations.of(context)!.settingsPromo} ${user.promo}', style: const TextStyle( fontSize: 20, fontWeight: FontWeight.w500, @@ -212,7 +235,7 @@ class EditUserPage extends HookConsumerWidget { ), ), AutoSizeText( - '${SettingsTextConstants.email} : ${user.email}', + '${AppLocalizations.of(context)!.settingsEmail} : ${user.email}', maxLines: 1, style: const TextStyle( fontSize: 18, @@ -222,13 +245,13 @@ class EditUserPage extends HookConsumerWidget { ), const SizedBox(height: 50), UserFieldModifier( - label: SettingsTextConstants.nickname, + label: AppLocalizations.of(context)!.settingsNickname, keyboardType: TextInputType.text, controller: nickNameController, ), const SizedBox(height: 50), UserFieldModifier( - label: SettingsTextConstants.phone, + label: AppLocalizations.of(context)!.settingsPhone, keyboardType: TextInputType.text, controller: phoneController, ), @@ -238,7 +261,7 @@ class EditUserPage extends HookConsumerWidget { SizedBox( width: 120, child: Text( - SettingsTextConstants.birthday, + AppLocalizations.of(context)!.settingsBirthday, style: TextStyle( fontSize: 15, fontWeight: FontWeight.w400, @@ -249,7 +272,7 @@ class EditUserPage extends HookConsumerWidget { Expanded( child: AbsorbPointer( child: TextEntry( - label: SettingsTextConstants.birthday, + label: AppLocalizations.of(context)!.settingsBirthday, controller: dateController, ), ), @@ -301,7 +324,7 @@ class EditUserPage extends HookConsumerWidget { SizedBox( width: 120, child: Text( - SettingsTextConstants.floor, + AppLocalizations.of(context)!.settingsFloor, style: TextStyle( fontSize: 15, fontWeight: FontWeight.w400, @@ -314,7 +337,7 @@ class EditUserPage extends HookConsumerWidget { items: items, value: floorController.text, hint: Text( - SettingsTextConstants.floor, + AppLocalizations.of(context)!.settingsFloor, style: TextStyle( fontSize: 15, fontWeight: FontWeight.w400, @@ -371,7 +394,7 @@ class EditUserPage extends HookConsumerWidget { if (value) { displayToastWithContext( TypeMsg.msg, - SettingsTextConstants.updatedProfile, + AppLocalizations.of(context)!.settingsUpdatedProfile, ); QR.removeNavigator( SettingsRouter.root + SettingsRouter.editAccount, @@ -379,15 +402,15 @@ class EditUserPage extends HookConsumerWidget { } else { displayToastWithContext( TypeMsg.error, - SettingsTextConstants.updatingError, + AppLocalizations.of(context)!.settingsUpdatingError, ); } }); }, - child: const Center( + child: Center( child: Text( - SettingsTextConstants.save, - style: TextStyle( + AppLocalizations.of(context)!.settingsSave, + style: const TextStyle( fontSize: 25, fontWeight: FontWeight.w400, color: Colors.white, diff --git a/lib/settings/ui/pages/log_page/log_card.dart b/lib/settings/ui/pages/log_page/log_card.dart index d601395b61..8a350a468a 100644 --- a/lib/settings/ui/pages/log_page/log_card.dart +++ b/lib/settings/ui/pages/log_page/log_card.dart @@ -1,9 +1,9 @@ import 'package:flutter/services.dart'; import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; -import 'package:titan/settings/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/logs/log.dart'; +import 'package:titan/l10n/app_localizations.dart'; class LogCard extends StatelessWidget { final Log log; @@ -66,7 +66,7 @@ class LogCard extends StatelessWidget { displayToast( context, TypeMsg.msg, - SettingsTextConstants.copied, + AppLocalizations.of(context)!.settingsCopied, ); }, child: const HeroIcon(HeroIcons.clipboard, color: Colors.white), diff --git a/lib/settings/ui/pages/log_page/log_tab.dart b/lib/settings/ui/pages/log_page/log_tab.dart index c59eb9182a..f8dcc8f9e1 100644 --- a/lib/settings/ui/pages/log_page/log_tab.dart +++ b/lib/settings/ui/pages/log_page/log_tab.dart @@ -2,10 +2,11 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/settings/providers/logs_provider.dart'; -import 'package:titan/settings/tools/constants.dart'; + import 'package:titan/settings/ui/pages/log_page/log_card.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; +import 'package:titan/l10n/app_localizations.dart'; class LogTab extends HookConsumerWidget { const LogTab({super.key}); @@ -20,9 +21,9 @@ class LogTab extends HookConsumerWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - const Text( - SettingsTextConstants.logs, - style: TextStyle( + Text( + AppLocalizations.of(context)!.settingsLogs, + style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.grey, @@ -33,8 +34,10 @@ class LogTab extends HookConsumerWidget { showDialog( context: context, builder: ((context) => CustomDialogBox( - title: SettingsTextConstants.deleting, - descriptions: SettingsTextConstants.deleteLogs, + title: AppLocalizations.of(context)!.settingsDeleting, + descriptions: AppLocalizations.of( + context, + )!.settingsDeleteLogs, onYes: (() async { logsNotifier.deleteLogs(); }), diff --git a/lib/settings/ui/pages/log_page/notification_tab.dart b/lib/settings/ui/pages/log_page/notification_tab.dart index f7da8d2f1f..5e70553521 100644 --- a/lib/settings/ui/pages/log_page/notification_tab.dart +++ b/lib/settings/ui/pages/log_page/notification_tab.dart @@ -2,10 +2,10 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/settings/providers/logs_provider.dart'; -import 'package:titan/settings/tools/constants.dart'; import 'package:titan/settings/ui/pages/log_page/log_card.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; +import 'package:titan/l10n/app_localizations.dart'; class NotificationTab extends HookConsumerWidget { const NotificationTab({super.key}); @@ -20,9 +20,9 @@ class NotificationTab extends HookConsumerWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - const Text( - SettingsTextConstants.notifications, - style: TextStyle( + Text( + AppLocalizations.of(context)!.settingsNotifications, + style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.grey, @@ -33,8 +33,10 @@ class NotificationTab extends HookConsumerWidget { showDialog( context: context, builder: ((context) => CustomDialogBox( - title: SettingsTextConstants.deleting, - descriptions: SettingsTextConstants.deleteNotificationLogs, + title: AppLocalizations.of(context)!.settingsDeleting, + descriptions: AppLocalizations.of( + context, + )!.settingsDeleteNotificationLogs, onYes: (() async { logsNotifier.deleteLogs(); }), diff --git a/lib/settings/ui/pages/main_page/main_page.dart b/lib/settings/ui/pages/main_page/main_page.dart index 77765f501b..e936763913 100644 --- a/lib/settings/ui/pages/main_page/main_page.dart +++ b/lib/settings/ui/pages/main_page/main_page.dart @@ -7,7 +7,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/flappybird/ui/flappybird_item_chip.dart'; import 'package:titan/settings/providers/logs_provider.dart'; import 'package:titan/settings/router.dart'; -import 'package:titan/settings/tools/constants.dart'; import 'package:titan/settings/ui/pages/main_page/settings_item.dart'; import 'package:titan/settings/ui/settings.dart'; import 'package:titan/tools/ui/widgets/align_left_text.dart'; @@ -22,6 +21,7 @@ import 'package:titan/user/providers/user_provider.dart'; import 'package:titan/user/providers/profile_picture_provider.dart'; import 'package:titan/version/providers/titan_version_provider.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class SettingsMainPage extends HookConsumerWidget { const SettingsMainPage({super.key}); @@ -147,8 +147,8 @@ class SettingsMainPage extends HookConsumerWidget { padding: const EdgeInsets.symmetric(horizontal: 30.0), child: Column( children: [ - const AlignLeftText( - SettingsTextConstants.account, + AlignLeftText( + AppLocalizations.of(context)!.settingsAccount, fontSize: 25, ), const SizedBox(height: 30), @@ -157,9 +157,9 @@ class SettingsMainPage extends HookConsumerWidget { onTap: () { QR.to(SettingsRouter.root + SettingsRouter.editAccount); }, - child: const Text( - SettingsTextConstants.editAccount, - style: TextStyle(fontSize: 16, color: Colors.black), + child: Text( + AppLocalizations.of(context)!.settingsEditAccount, + style: const TextStyle(fontSize: 16, color: Colors.black), ), ), const SizedBox(height: 30), @@ -171,18 +171,18 @@ class SettingsMainPage extends HookConsumerWidget { ).then((value) { displayToastWithContext( TypeMsg.msg, - SettingsTextConstants.icalCopied, + AppLocalizations.of(context)!.settingsIcalCopied, ); }); }, - child: const Text( - SettingsTextConstants.eventsIcal, - style: TextStyle(fontSize: 16, color: Colors.black), + child: Text( + AppLocalizations.of(context)!.settingsEventsIcal, + style: const TextStyle(fontSize: 16, color: Colors.black), ), ), const SizedBox(height: 50), - const AlignLeftText( - SettingsTextConstants.security, + AlignLeftText( + AppLocalizations.of(context)!.settingsSecurity, fontSize: 25, ), const SizedBox(height: 30), @@ -193,15 +193,15 @@ class SettingsMainPage extends HookConsumerWidget { SettingsRouter.root + SettingsRouter.changePassword, ); }, - child: const Text( - SettingsTextConstants.editPassword, - style: TextStyle(fontSize: 16, color: Colors.black), + child: Text( + AppLocalizations.of(context)!.settingsEditPassword, + style: const TextStyle(fontSize: 16, color: Colors.black), ), ), const SizedBox(height: 50), if (!kIsWeb) ...[ - const AlignLeftText( - SettingsTextConstants.help, + AlignLeftText( + AppLocalizations.of(context)!.settingsHelp, fontSize: 25, ), const SizedBox(height: 30), @@ -210,15 +210,18 @@ class SettingsMainPage extends HookConsumerWidget { onTap: () { QR.to(SettingsRouter.root + SettingsRouter.logs); }, - child: const Text( - SettingsTextConstants.logs, - style: TextStyle(fontSize: 16, color: Colors.black), + child: Text( + AppLocalizations.of(context)!.settingsLogs, + style: const TextStyle( + fontSize: 16, + color: Colors.black, + ), ), ), const SizedBox(height: 50), ], - const AlignLeftText( - SettingsTextConstants.personalisation, + AlignLeftText( + AppLocalizations.of(context)!.settingsPersonalisation, fontSize: 25, ), const SizedBox(height: 30), @@ -227,9 +230,9 @@ class SettingsMainPage extends HookConsumerWidget { onTap: () { QR.to(SettingsRouter.root + SettingsRouter.modules); }, - child: const Text( - SettingsTextConstants.modules, - style: TextStyle(fontSize: 16, color: Colors.black), + child: Text( + AppLocalizations.of(context)!.settingsModules, + style: const TextStyle(fontSize: 16, color: Colors.black), ), ), const SizedBox(height: 30), @@ -238,14 +241,14 @@ class SettingsMainPage extends HookConsumerWidget { onTap: () { QR.to(SettingsRouter.root + SettingsRouter.notifications); }, - child: const Text( - SettingsTextConstants.notifications, - style: TextStyle(fontSize: 16, color: Colors.black), + child: Text( + AppLocalizations.of(context)!.settingsNotifications, + style: const TextStyle(fontSize: 16, color: Colors.black), ), ), const SizedBox(height: 50), - const AlignLeftText( - SettingsTextConstants.personalData, + AlignLeftText( + AppLocalizations.of(context)!.settingsPersonalData, fontSize: 25, ), const SizedBox(height: 30), @@ -256,20 +259,27 @@ class SettingsMainPage extends HookConsumerWidget { context: context, builder: (BuildContext context) { return CustomDialogBox( - title: SettingsTextConstants.detelePersonalData, - descriptions: - SettingsTextConstants.detelePersonalDataDesc, + title: AppLocalizations.of( + context, + )!.settingsDetelePersonalData, + descriptions: AppLocalizations.of( + context, + )!.settingsDetelePersonalDataDesc, onYes: () async { final value = await meNotifier.deletePersonal(); if (value) { displayToastWithContext( TypeMsg.msg, - SettingsTextConstants.sendedDemand, + AppLocalizations.of( + context, + )!.settingsSendedDemand, ); } else { displayToastWithContext( TypeMsg.error, - SettingsTextConstants.errorSendingDemand, + AppLocalizations.of( + context, + )!.settingsErrorSendingDemand, ); } }, @@ -277,14 +287,14 @@ class SettingsMainPage extends HookConsumerWidget { }, ); }, - child: const Text( - SettingsTextConstants.detelePersonalData, - style: TextStyle(fontSize: 16, color: Colors.black), + child: Text( + AppLocalizations.of(context)!.settingsDetelePersonalData, + style: const TextStyle(fontSize: 16, color: Colors.black), ), ), const SizedBox(height: 60), Text( - "${SettingsTextConstants.version} $titanVersion", + "${AppLocalizations.of(context)!.settingsVersion} $titanVersion", style: const TextStyle( fontSize: 15, fontWeight: FontWeight.w500, diff --git a/lib/settings/ui/pages/notification_page/notification_page.dart b/lib/settings/ui/pages/notification_page/notification_page.dart index 3912ae09f1..1e6977b3a2 100644 --- a/lib/settings/ui/pages/notification_page/notification_page.dart +++ b/lib/settings/ui/pages/notification_page/notification_page.dart @@ -4,12 +4,12 @@ import 'package:load_switch/load_switch.dart'; import 'package:titan/service/class/topic.dart'; import 'package:titan/service/providers/topic_provider.dart'; import 'package:titan/service/tools/functions.dart'; -import 'package:titan/settings/tools/constants.dart'; import 'package:titan/settings/ui/settings.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/widgets/align_left_text.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; +import 'package:titan/l10n/app_localizations.dart'; class NotificationPage extends HookConsumerWidget { const NotificationPage({super.key}); @@ -27,9 +27,9 @@ class NotificationPage extends HookConsumerWidget { padding: const EdgeInsets.symmetric(horizontal: 30.0), child: Column( children: [ - const AlignLeftText( - SettingsTextConstants.updateNotification, - padding: EdgeInsets.symmetric(vertical: 30), + AlignLeftText( + AppLocalizations.of(context)!.settingsUpdateNotification, + padding: const EdgeInsets.symmetric(vertical: 30), color: Colors.grey, ), AsyncChild( diff --git a/test/amap/amap_test.dart b/test/amap/amap_test.dart index af7eaec0eb..ea484d8dd8 100644 --- a/test/amap/amap_test.dart +++ b/test/amap/amap_test.dart @@ -12,10 +12,8 @@ import 'package:titan/amap/repositories/delivery_product_list_repository.dart'; import 'package:titan/amap/repositories/information_repository.dart'; import 'package:titan/amap/repositories/order_list_repository.dart'; import 'package:titan/amap/repositories/product_repository.dart'; -import 'package:titan/amap/tools/constants.dart'; import 'package:titan/amap/tools/functions.dart'; import 'package:titan/user/class/simple_users.dart'; -import 'package:titan/l10n/app_localizations.dart'; class MockAmapUserRespository extends Mock implements AmapUserRepository {} diff --git a/test/booking/booking_test.dart b/test/booking/booking_test.dart index 7d822f8714..33a4c1728a 100644 --- a/test/booking/booking_test.dart +++ b/test/booking/booking_test.dart @@ -1,7 +1,6 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:titan/booking/class/booking.dart'; import 'package:titan/service/class/room.dart'; -import 'package:titan/booking/tools/functions.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/user/class/applicant.dart'; import 'package:titan/user/class/simple_users.dart'; From e7df368986ecb64a2b724a9c3aeae8362d3c26ce Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Thu, 3 Jul 2025 18:12:01 +0200 Subject: [PATCH 052/473] vote --- lib/vote/tools/constants.dart | 90 -------- lib/vote/ui/pages/admin_page/admin_page.dart | 197 +++++++++++------- lib/vote/ui/pages/admin_page/section_bar.dart | 10 +- .../admin_page/section_contender_items.dart | 10 +- lib/vote/ui/pages/admin_page/vote_bars.dart | 4 +- .../contender_pages/add_edit_contender.dart | 52 +++-- .../ui/pages/main_page/contender_card.dart | 8 +- .../pages/main_page/list_contender_card.dart | 10 +- .../ui/pages/main_page/list_side_item.dart | 6 +- lib/vote/ui/pages/main_page/main_page.dart | 8 +- .../ui/pages/main_page/section_title.dart | 4 +- lib/vote/ui/pages/main_page/vote_button.dart | 22 +- .../ui/pages/section_pages/add_section.dart | 20 +- 13 files changed, 208 insertions(+), 233 deletions(-) delete mode 100644 lib/vote/tools/constants.dart diff --git a/lib/vote/tools/constants.dart b/lib/vote/tools/constants.dart deleted file mode 100644 index be2ab035f0..0000000000 --- a/lib/vote/tools/constants.dart +++ /dev/null @@ -1,90 +0,0 @@ -class VoteTextConstants { - static const String add = 'Ajouter'; - static const String addMember = 'Ajouter un membre'; - static const String addedPretendance = 'Liste ajoutée'; - static const String addedSection = 'Section ajoutée'; - static const String addingError = 'Erreur lors de l\'ajout'; - static const String addPretendance = 'Ajouter une liste'; - static const String addSection = 'Ajouter une section'; - static const String all = "Tous"; - static const String alreadyAddedMember = 'Membre déjà ajouté'; - static const String alreadyVoted = "Vote enregistré"; - static const String chooseList = 'Choisir une liste'; - static const String clear = 'Réinitialiser'; - static const String clearVotes = "Réinitialiser les votes"; - static const String closedVote = 'Votes clos'; - static const String closeVote = 'Fermer les votes'; - static const String confirmVote = 'Confirmer le vote'; - static const String countVote = 'Dépouiller les votes'; - static const String deletedAll = "Tout supprimé"; - static const String deletedPipo = "Listes pipos supprimées"; - static const String deletedSection = 'Section supprimée'; - static const String deleteAll = "Supprimer tout"; - static const String deleteAllDescription = - "Voulez-vous vraiment supprimer tout ?"; - static const String deletePipo = "Supprimer les listes pipos"; - static const String deletePipoDescription = - "Voulez-vous vraiment supprimer les listes pipos ?"; - static const String deletePretendance = 'Supprimer la liste'; - static const String deletePretendanceDesc = - 'Voulez-vous vraiment supprimer cette liste ?'; - static const String deleteSection = 'Supprimer la section'; - static const String deleteSectionDescription = - 'Voulez-vous vraiment supprimer cette section ?'; - static const String deletingError = 'Erreur lors de la suppression'; - static const String description = 'Description'; - static const String edit = 'Modifier'; - static const String editedPretendance = 'Liste modifiée'; - static const String editedSection = 'Section modifiée'; - static const String editingError = 'Erreur lors de la modification'; - static const String errorClosingVotes = - 'Erreur lors de la fermeture des votes'; - static const String errorCountingVotes = - 'Erreur lors du dépouillement des votes'; - static const String errorResetingVotes = - 'Erreur lors de la réinitialisation des votes'; - static const String errorOpeningVotes = - 'Erreur lors de l\'ouverture des votes'; - static const String incorrectOrMissingFields = - 'Champs incorrects ou manquants'; - static const String members = 'Membres'; - static const String name = 'Nom'; - static const String noPretendanceList = 'Aucune liste de prétendance'; - static const String noSection = 'Aucune section'; - static const String canNotVote = 'Vous ne pouvez pas voter'; - static const String noSectionList = 'Aucune section'; - static const String notOpenedVote = 'Vote non ouvert'; - static const String onGoingCount = 'Dépouillement en cours'; - static const String openVote = 'Ouvrir les votes'; - static const String pipo = "Pipo"; - static const String pretendance = 'Listes'; - static const String pretendanceDeleted = 'Prétendance supprimée'; - static const String pretendanceNotDeleted = 'Erreur lors de la suppression'; - static const String program = 'Programme'; - static const String publish = 'Publier'; - static const String publishVoteDescription = - 'Voulez-vous vraiment publier les votes ?'; - static const String resetedVotes = 'Votes réinitialisés'; - static const String resetVote = 'Réinitialiser les votes'; - static const String resetVoteDescription = "Que voulez-vous faire ?"; - static const String role = 'Rôle'; - static const String sectionDescription = 'Description de la section'; - static const String section = 'Section'; - static const String sectionName = 'Nom de la section'; - static const String seeMore = 'Voir plus'; - static const String selected = 'Sélectionné'; - static const String showVotes = 'Voir les votes'; - static const String vote = 'Vote'; - static const String voteError = 'Erreur lors de l\'enregistrement du vote'; - static const String voteFor = 'Voter pour '; - static const String voteNotStarted = 'Vote non ouvert'; - static const String voters = 'Groupes votants'; - static const String voteSuccess = 'Vote enregistré'; - static const String votes = 'Voix'; - static const String votesClosed = 'Votes clos'; - static const String votesCounted = 'Votes dépouillés'; - static const String votesOpened = 'Votes ouverts'; - static const String warning = "Attention"; - static const String warningMessage = - "La sélection ne sera pas sauvegardée.\nVoulez-vous continuer ?"; -} diff --git a/lib/vote/ui/pages/admin_page/admin_page.dart b/lib/vote/ui/pages/admin_page/admin_page.dart index 2f2c4b9d13..bd5ea02dac 100644 --- a/lib/vote/ui/pages/admin_page/admin_page.dart +++ b/lib/vote/ui/pages/admin_page/admin_page.dart @@ -19,7 +19,6 @@ import 'package:titan/vote/providers/sections_provider.dart'; import 'package:titan/vote/providers/show_graph_provider.dart'; import 'package:titan/vote/providers/status_provider.dart'; import 'package:titan/vote/repositories/status_repository.dart'; -import 'package:titan/vote/tools/constants.dart'; import 'package:titan/vote/ui/pages/admin_page/admin_button.dart'; import 'package:titan/vote/ui/pages/admin_page/section_bar.dart'; import 'package:titan/vote/ui/pages/admin_page/section_contender_items.dart'; @@ -27,6 +26,7 @@ import 'package:titan/vote/ui/pages/admin_page/vote_bars.dart'; import 'package:titan/vote/ui/pages/admin_page/vote_count.dart'; import 'package:titan/vote/ui/pages/admin_page/voters_bar.dart'; import 'package:titan/vote/ui/vote.dart'; +import 'package:titan/l10n/app_localizations.dart'; class AdminPage extends HookConsumerWidget { const AdminPage({super.key}); @@ -85,17 +85,17 @@ class AdminPage extends HookConsumerWidget { const SizedBox(height: 20), const SectionBar(), const SizedBox(height: 30), - const AlignLeftText( - VoteTextConstants.voters, - padding: EdgeInsets.symmetric(horizontal: 30.0), - color: Color.fromARGB(255, 149, 149, 149), + AlignLeftText( + AppLocalizations.of(context)!.voteVoters, + padding: const EdgeInsets.symmetric(horizontal: 30.0), + color: const Color.fromARGB(255, 149, 149, 149), ), const SizedBox(height: 30), const VotersBar(), const SizedBox(height: 30), - const AlignLeftText( - VoteTextConstants.pretendance, - padding: EdgeInsets.symmetric(horizontal: 30.0), + AlignLeftText( + AppLocalizations.of(context)!.votePretendance, + padding: const EdgeInsets.symmetric(horizontal: 30.0), color: Colors.grey, ), const SizedBox(height: 10), @@ -108,9 +108,9 @@ class AdminPage extends HookConsumerWidget { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - const Text( - VoteTextConstants.vote, - style: TextStyle( + Text( + AppLocalizations.of(context)!.voteVote, + style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.grey, @@ -137,9 +137,12 @@ class AdminPage extends HookConsumerWidget { await showDialog( context: context, builder: (context) => CustomDialogBox( - title: VoteTextConstants.publish, - descriptions: VoteTextConstants - .publishVoteDescription, + title: AppLocalizations.of( + context, + )!.votePublish, + descriptions: AppLocalizations.of( + context, + )!.votePublishVoteDescription, onYes: () { statusNotifier.publishVote(); ref @@ -149,9 +152,9 @@ class AdminPage extends HookConsumerWidget { ), ); }, - child: const Text( - VoteTextConstants.publish, - style: TextStyle( + child: Text( + AppLocalizations.of(context)!.votePublish, + style: const TextStyle( fontSize: 13, fontWeight: FontWeight.bold, color: Colors.white, @@ -170,11 +173,22 @@ class AdminPage extends HookConsumerWidget { context: context, builder: (context) { return CustomDialogBox( - title: VoteTextConstants.resetVote, - descriptions: - VoteTextConstants.resetVoteDescription, + title: AppLocalizations.of( + context, + )!.voteResetVote, + descriptions: AppLocalizations.of( + context, + )!.voteResetVoteDescription, onYes: () async { await tokenExpireWrapper(ref, () async { + final resetedVotesMsg = + AppLocalizations.of( + context, + )!.voteResetedVotes; + final resetedVotesErrorMsg = + AppLocalizations.of( + context, + )!.voteErrorResetingVotes; final value = await statusNotifier .resetVote(); ref @@ -184,12 +198,12 @@ class AdminPage extends HookConsumerWidget { showVotesNotifier.toggle(false); displayVoteToastWithContext( TypeMsg.msg, - VoteTextConstants.resetedVotes, + resetedVotesMsg, ); } else { displayVoteToastWithContext( TypeMsg.error, - VoteTextConstants.errorResetingVotes, + resetedVotesErrorMsg, ); } }); @@ -198,9 +212,9 @@ class AdminPage extends HookConsumerWidget { }, ); }, - child: const Text( - VoteTextConstants.clear, - style: TextStyle( + child: Text( + AppLocalizations.of(context)!.voteClear, + style: const TextStyle( fontSize: 13, fontWeight: FontWeight.bold, color: Colors.white, @@ -227,19 +241,19 @@ class AdminPage extends HookConsumerWidget { showVotesNotifier.toggle(true); }, behavior: HitTestBehavior.opaque, - child: const Column( + child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - SizedBox(height: 40), - HeroIcon( + const SizedBox(height: 40), + const HeroIcon( HeroIcons.eye, size: 80.0, color: Colors.black, ), - SizedBox(height: 40), + const SizedBox(height: 40), Text( - VoteTextConstants.showVotes, - style: TextStyle( + AppLocalizations.of(context)!.voteShowVotes, + style: const TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: Colors.black, @@ -264,24 +278,30 @@ class AdminPage extends HookConsumerWidget { ), onTap: () async { await tokenExpireWrapper(ref, () async { + final votesCountedMsg = AppLocalizations.of( + context, + )!.voteVotesCounted; + final errorCountingVotesMsg = AppLocalizations.of( + context, + )!.voteErrorCountingVotes; final value = await statusNotifier.countVote(); if (value) { displayVoteToastWithContext( TypeMsg.msg, - VoteTextConstants.votesCounted, + votesCountedMsg, ); } else { displayVoteToastWithContext( TypeMsg.error, - VoteTextConstants.errorCountingVotes, + errorCountingVotesMsg, ); } }); }, - child: const Center( + child: Center( child: Text( - VoteTextConstants.countVote, - style: TextStyle( + AppLocalizations.of(context)!.voteCountVote, + style: const TextStyle( color: Colors.white, fontSize: 20, fontWeight: FontWeight.w700, @@ -308,24 +328,30 @@ class AdminPage extends HookConsumerWidget { ), onTap: () async { await tokenExpireWrapper(ref, () async { + final closeVotesMsg = AppLocalizations.of( + context, + )!.voteVotesClosed; + final errorClosingVotesMsg = AppLocalizations.of( + context, + )!.voteErrorClosingVotes; final value = await statusNotifier.closeVote(); if (value) { displayVoteToastWithContext( TypeMsg.msg, - VoteTextConstants.votesClosed, + closeVotesMsg, ); } else { displayVoteToastWithContext( TypeMsg.error, - VoteTextConstants.errorClosingVotes, + errorClosingVotesMsg, ); } }); }, - child: const Center( + child: Center( child: Text( - VoteTextConstants.closeVote, - style: TextStyle( + AppLocalizations.of(context)!.voteCloseVote, + style: const TextStyle( color: Colors.white, fontSize: 20, fontWeight: FontWeight.w700, @@ -357,6 +383,13 @@ class AdminPage extends HookConsumerWidget { ), onTap: () async { await tokenExpireWrapper(ref, () async { + final openVotesMsg = AppLocalizations.of( + context, + )!.voteVotesOpened; + final errorOpeningVotesMsg = + AppLocalizations.of( + context, + )!.voteErrorOpeningVotes; final value = await statusNotifier .openVote(); ref @@ -365,20 +398,20 @@ class AdminPage extends HookConsumerWidget { if (value) { displayVoteToastWithContext( TypeMsg.msg, - VoteTextConstants.votesOpened, + openVotesMsg, ); } else { displayVoteToastWithContext( TypeMsg.error, - VoteTextConstants.errorOpeningVotes, + errorOpeningVotesMsg, ); } }); }, - child: const Center( + child: Center( child: Text( - VoteTextConstants.openVote, - style: TextStyle( + AppLocalizations.of(context)!.voteOpenVote, + style: const TextStyle( color: Colors.black, fontSize: 20, fontWeight: FontWeight.w700, @@ -412,11 +445,21 @@ class AdminPage extends HookConsumerWidget { await showDialog( context: context, builder: (context) => CustomDialogBox( - title: - VoteTextConstants.deleteAll, - descriptions: VoteTextConstants - .deleteAllDescription, + title: AppLocalizations.of( + context, + )!.voteDeleteAll, + descriptions: AppLocalizations.of( + context, + )!.voteDeleteAllDescription, onYes: () async { + final deleteAllVotesMsg = + AppLocalizations.of( + context, + )!.voteDeletedAll; + final errorDeletingVotesMsg = + AppLocalizations.of( + context, + )!.voteDeletingError; await tokenExpireWrapper( ref, () async { @@ -429,14 +472,12 @@ class AdminPage extends HookConsumerWidget { if (value) { displayVoteToastWithContext( TypeMsg.msg, - VoteTextConstants - .deletedAll, + deleteAllVotesMsg, ); } else { displayVoteToastWithContext( TypeMsg.error, - VoteTextConstants - .deletingError, + errorDeletingVotesMsg, ); } }, @@ -445,21 +486,23 @@ class AdminPage extends HookConsumerWidget { ), ); }, - child: const Center( + child: Center( child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( - VoteTextConstants.all, - style: TextStyle( + AppLocalizations.of( + context, + )!.voteAll, + style: const TextStyle( color: Colors.white, fontSize: 20, fontWeight: FontWeight.w700, ), ), - SizedBox(width: 10), - HeroIcon( + const SizedBox(width: 10), + const HeroIcon( HeroIcons.trash, color: Colors.white, size: 20, @@ -489,11 +532,21 @@ class AdminPage extends HookConsumerWidget { await showDialog( context: context, builder: (context) => CustomDialogBox( - title: - VoteTextConstants.deletePipo, - descriptions: VoteTextConstants - .deletePipoDescription, + title: AppLocalizations.of( + context, + )!.voteDeletePipo, + descriptions: AppLocalizations.of( + context, + )!.voteDeletePipoDescription, onYes: () async { + final deletePipoVotesMsg = + AppLocalizations.of( + context, + )!.voteDeletedPipo; + final errorDeletingPipoVotesMsg = + AppLocalizations.of( + context, + )!.voteDeletingError; await tokenExpireWrapper( ref, () async { @@ -508,14 +561,12 @@ class AdminPage extends HookConsumerWidget { if (value) { displayVoteToastWithContext( TypeMsg.msg, - VoteTextConstants - .deletedPipo, + deletePipoVotesMsg, ); } else { displayVoteToastWithContext( TypeMsg.error, - VoteTextConstants - .deletingError, + errorDeletingPipoVotesMsg, ); } }, @@ -524,21 +575,23 @@ class AdminPage extends HookConsumerWidget { ), ); }, - child: const Center( + child: Center( child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( - VoteTextConstants.pipo, - style: TextStyle( + AppLocalizations.of( + context, + )!.votePipo, + style: const TextStyle( color: Colors.white, fontSize: 20, fontWeight: FontWeight.w700, ), ), - SizedBox(width: 10), - HeroIcon( + const SizedBox(width: 10), + const HeroIcon( HeroIcons.trash, color: Colors.white, size: 20, diff --git a/lib/vote/ui/pages/admin_page/section_bar.dart b/lib/vote/ui/pages/admin_page/section_bar.dart index b710adb538..6de84dbfac 100644 --- a/lib/vote/ui/pages/admin_page/section_bar.dart +++ b/lib/vote/ui/pages/admin_page/section_bar.dart @@ -12,9 +12,9 @@ import 'package:titan/vote/providers/sections_provider.dart'; import 'package:titan/vote/providers/status_provider.dart'; import 'package:titan/vote/repositories/status_repository.dart'; import 'package:titan/vote/router.dart'; -import 'package:titan/vote/tools/constants.dart'; import 'package:titan/vote/ui/pages/admin_page/section_chip.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class SectionBar extends HookConsumerWidget { const SectionBar({super.key}); @@ -62,20 +62,20 @@ class SectionBar extends HookConsumerWidget { await showDialog( context: context, builder: (context) => CustomDialogBox( - title: VoteTextConstants.deleteSection, - descriptions: VoteTextConstants.deleteSectionDescription, + title: AppLocalizations.of(context)!.voteDeleteSection, + descriptions: AppLocalizations.of(context)!.voteDeleteSectionDescription, onYes: () async { final result = await sectionsNotifier.deleteSection(key); if (result) { sectionContenderListNotifier.deleteT(key); displayVoteToastWithContext( TypeMsg.msg, - VoteTextConstants.deletedSection, + AppLocalizations.of(context)!.voteDeletedSection, ); } else { displayVoteToastWithContext( TypeMsg.error, - VoteTextConstants.deletingError, + AppLocalizations.of(context)!.voteDeletingError, ); } }, diff --git a/lib/vote/ui/pages/admin_page/section_contender_items.dart b/lib/vote/ui/pages/admin_page/section_contender_items.dart index efb9761360..f1be356d0e 100644 --- a/lib/vote/ui/pages/admin_page/section_contender_items.dart +++ b/lib/vote/ui/pages/admin_page/section_contender_items.dart @@ -16,9 +16,9 @@ import 'package:titan/vote/providers/sections_provider.dart'; import 'package:titan/vote/providers/status_provider.dart'; import 'package:titan/vote/repositories/status_repository.dart'; import 'package:titan/vote/router.dart'; -import 'package:titan/vote/tools/constants.dart'; import 'package:titan/vote/ui/pages/admin_page/contender_card.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class SectionContenderItems extends HookConsumerWidget { const SectionContenderItems({super.key}); @@ -90,8 +90,8 @@ class SectionContenderItems extends HookConsumerWidget { context: context, builder: (context) { return CustomDialogBox( - title: VoteTextConstants.deletePretendance, - descriptions: VoteTextConstants.deletePretendanceDesc, + title: AppLocalizations.of(context)!.voteDeletePretendance, + descriptions: AppLocalizations.of(context)!.voteDeletePretendanceDesc, onYes: () { tokenExpireWrapper(ref, () async { final value = await contenderListNotifier.deleteContender( @@ -100,7 +100,7 @@ class SectionContenderItems extends HookConsumerWidget { if (value) { displayVoteToastWithContext( TypeMsg.msg, - VoteTextConstants.pretendanceDeleted, + AppLocalizations.of(context)!.votePretendanceDeleted, ); contenderListNotifier.copy().then((value) { sectionContenderListNotifier.setTData(section, value); @@ -108,7 +108,7 @@ class SectionContenderItems extends HookConsumerWidget { } else { displayVoteToastWithContext( TypeMsg.error, - VoteTextConstants.pretendanceNotDeleted, + AppLocalizations.of(context)!.votePretendanceNotDeleted, ); } }); diff --git a/lib/vote/ui/pages/admin_page/vote_bars.dart b/lib/vote/ui/pages/admin_page/vote_bars.dart index 3c7c37cea8..3514ad27db 100644 --- a/lib/vote/ui/pages/admin_page/vote_bars.dart +++ b/lib/vote/ui/pages/admin_page/vote_bars.dart @@ -5,7 +5,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/vote/providers/result_provider.dart'; import 'package:titan/vote/providers/sections_contender_provider.dart'; import 'package:titan/vote/providers/sections_provider.dart'; -import 'package:titan/vote/tools/constants.dart'; +import 'package:titan/l10n/app_localizations.dart'; class VoteBars extends HookConsumerWidget { const VoteBars({super.key}); @@ -147,7 +147,7 @@ class VoteBars extends HookConsumerWidget { ), const SizedBox(height: 4), Text( - '${voteValue[sectionIds[value.toInt()]] ?? 0} ${VoteTextConstants.votes}', + '${voteValue[sectionIds[value.toInt()]] ?? 0} ${AppLocalizations.of(context)!.voteVotes}', style: const TextStyle( color: Colors.black, fontWeight: FontWeight.bold, diff --git a/lib/vote/ui/pages/contender_pages/add_edit_contender.dart b/lib/vote/ui/pages/contender_pages/add_edit_contender.dart index 5c392c4457..f8833bff50 100644 --- a/lib/vote/ui/pages/contender_pages/add_edit_contender.dart +++ b/lib/vote/ui/pages/contender_pages/add_edit_contender.dart @@ -25,12 +25,12 @@ import 'package:titan/vote/providers/contender_list_provider.dart'; import 'package:titan/vote/providers/contender_provider.dart'; import 'package:titan/vote/providers/sections_contender_provider.dart'; import 'package:titan/vote/providers/sections_provider.dart'; -import 'package:titan/vote/tools/constants.dart'; import 'package:titan/vote/ui/components/member_card.dart'; import 'package:titan/vote/ui/pages/contender_pages/search_result.dart'; import 'package:titan/vote/ui/pages/admin_page/section_chip.dart'; import 'package:titan/vote/ui/vote.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class AddEditContenderPage extends HookConsumerWidget { const AddEditContenderPage({super.key}); @@ -82,9 +82,9 @@ class AddEditContenderPage extends HookConsumerWidget { key: key, child: Column( children: [ - const AlignLeftText( - VoteTextConstants.addPretendance, - padding: EdgeInsets.only( + AlignLeftText( + AppLocalizations.of(context)!.voteAddPretendance, + padding: const EdgeInsets.only( top: 40, left: 30, right: 30, @@ -156,7 +156,7 @@ class AddEditContenderPage extends HookConsumerWidget { Padding( padding: const EdgeInsets.symmetric(horizontal: 30.0), child: TextEntry( - label: VoteTextConstants.name, + label: AppLocalizations.of(context)!.voteName, controller: name, ), ), @@ -180,7 +180,7 @@ class AddEditContenderPage extends HookConsumerWidget { child: TextEntry( keyboardType: TextInputType.multiline, controller: description, - label: VoteTextConstants.description, + label: AppLocalizations.of(context)!.voteDescription, ), ), const SizedBox(height: 30), @@ -207,7 +207,7 @@ class AddEditContenderPage extends HookConsumerWidget { focusedBorder: const UnderlineInputBorder( borderSide: BorderSide(color: Colors.black, width: 2.0), ), - labelText: VoteTextConstants.addMember, + labelText: AppLocalizations.of(context)!.voteAddMember, border: OutlineInputBorder( borderRadius: BorderRadius.circular(10.0), ), @@ -221,7 +221,9 @@ class AddEditContenderPage extends HookConsumerWidget { child: Column( children: [ TextEntry( - label: VoteTextConstants.members, + label: AppLocalizations.of( + context, + )!.voteMembers, onChanged: (newQuery) { showNotifier.setId(true); tokenExpireWrapper(ref, () async { @@ -243,7 +245,7 @@ class AddEditContenderPage extends HookConsumerWidget { queryController: queryController, ), TextEntry( - label: VoteTextConstants.role, + label: AppLocalizations.of(context)!.voteRole, controller: role, ), const SizedBox(height: 30), @@ -271,7 +273,9 @@ class AddEditContenderPage extends HookConsumerWidget { } else { displayVoteToastWithContext( TypeMsg.error, - VoteTextConstants.alreadyAddedMember, + AppLocalizations.of( + context, + )!.voteAlreadyAddedMember, ); } } @@ -300,9 +304,9 @@ class AddEditContenderPage extends HookConsumerWidget { ), ], ), - child: const Text( - VoteTextConstants.add, - style: TextStyle( + child: Text( + AppLocalizations.of(context)!.voteAdd, + style: const TextStyle( color: Colors.white, fontSize: 25, fontWeight: FontWeight.bold, @@ -323,7 +327,7 @@ class AddEditContenderPage extends HookConsumerWidget { padding: const EdgeInsets.symmetric(horizontal: 30.0), child: TextEntry( keyboardType: TextInputType.multiline, - label: VoteTextConstants.program, + label: AppLocalizations.of(context)!.voteProgram, controller: program, ), ), @@ -377,7 +381,9 @@ class AddEditContenderPage extends HookConsumerWidget { if (isEdit) { displayVoteToastWithContext( TypeMsg.msg, - VoteTextConstants.editedPretendance, + AppLocalizations.of( + context, + )!.voteEditedPretendance, ); contenderList.maybeWhen( data: (list) { @@ -398,7 +404,9 @@ class AddEditContenderPage extends HookConsumerWidget { } else { displayVoteToastWithContext( TypeMsg.msg, - VoteTextConstants.addedPretendance, + AppLocalizations.of( + context, + )!.voteAddedPretendance, ); contenderList.maybeWhen( data: (list) { @@ -426,7 +434,7 @@ class AddEditContenderPage extends HookConsumerWidget { } else { displayVoteToastWithContext( TypeMsg.error, - VoteTextConstants.editingError, + AppLocalizations.of(context)!.voteEditingError, ); } }); @@ -434,13 +442,15 @@ class AddEditContenderPage extends HookConsumerWidget { displayToast( context, TypeMsg.error, - VoteTextConstants.incorrectOrMissingFields, + AppLocalizations.of( + context, + )!.voteIncorrectOrMissingFields, ); } }, - child: const Text( - VoteTextConstants.edit, - style: TextStyle( + child: Text( + AppLocalizations.of(context)!.voteEdit, + style: const TextStyle( color: Colors.white, fontSize: 25, fontWeight: FontWeight.bold, diff --git a/lib/vote/ui/pages/main_page/contender_card.dart b/lib/vote/ui/pages/main_page/contender_card.dart index 68deb67220..96a0da6bf0 100644 --- a/lib/vote/ui/pages/main_page/contender_card.dart +++ b/lib/vote/ui/pages/main_page/contender_card.dart @@ -11,9 +11,9 @@ import 'package:titan/vote/providers/selected_contender_provider.dart'; import 'package:titan/vote/providers/status_provider.dart'; import 'package:titan/vote/repositories/status_repository.dart'; import 'package:titan/vote/router.dart'; -import 'package:titan/vote/tools/constants.dart'; import 'package:titan/vote/ui/components/contender_logo.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class ContenderCard extends HookConsumerWidget { final Contender contender; @@ -258,9 +258,9 @@ class ContenderCard extends HookConsumerWidget { ), ), ) - : const Text( - VoteTextConstants.selected, - style: TextStyle( + : Text( + AppLocalizations.of(context)!.voteSelected, + style: const TextStyle( fontSize: 12, fontWeight: FontWeight.bold, color: Colors.black, diff --git a/lib/vote/ui/pages/main_page/list_contender_card.dart b/lib/vote/ui/pages/main_page/list_contender_card.dart index 5a5f20977f..461bd0b563 100644 --- a/lib/vote/ui/pages/main_page/list_contender_card.dart +++ b/lib/vote/ui/pages/main_page/list_contender_card.dart @@ -11,8 +11,8 @@ import 'package:titan/vote/providers/sections_provider.dart'; import 'package:titan/vote/providers/status_provider.dart'; import 'package:titan/vote/providers/voted_section_provider.dart'; import 'package:titan/vote/repositories/status_repository.dart'; -import 'package:titan/vote/tools/constants.dart'; import 'package:titan/vote/ui/pages/main_page/contender_card.dart'; +import 'package:titan/l10n/app_localizations.dart'; class ListContenderCard extends HookConsumerWidget { final AnimationController animation; @@ -103,10 +103,12 @@ class ListContenderCard extends HookConsumerWidget { }).toList(), ), ) - : const SizedBox( + : SizedBox( height: 150, child: Center( - child: Text(VoteTextConstants.noPretendanceList), + child: Text( + AppLocalizations.of(context)!.voteNoPretendanceList, + ), ), ), ), @@ -180,7 +182,7 @@ class ListContenderCard extends HookConsumerWidget { ), const SizedBox(width: 10), Text( - VoteTextConstants.seeMore, + AppLocalizations.of(context)!.voteSeeMore, style: TextStyle( fontSize: 18, color: Colors.grey.shade100, diff --git a/lib/vote/ui/pages/main_page/list_side_item.dart b/lib/vote/ui/pages/main_page/list_side_item.dart index 7a4af88616..dd6a70b247 100644 --- a/lib/vote/ui/pages/main_page/list_side_item.dart +++ b/lib/vote/ui/pages/main_page/list_side_item.dart @@ -7,8 +7,8 @@ import 'package:titan/vote/providers/section_id_provider.dart'; import 'package:titan/vote/providers/sections_provider.dart'; import 'package:titan/vote/providers/selected_contender_provider.dart'; import 'package:titan/vote/providers/voted_section_provider.dart'; -import 'package:titan/vote/tools/constants.dart'; import 'package:titan/vote/ui/pages/main_page/side_item.dart'; +import 'package:titan/l10n/app_localizations.dart'; class ListSideItem extends HookConsumerWidget { final List
sectionList; @@ -47,8 +47,8 @@ class ListSideItem extends HookConsumerWidget { showDialog( context: context, builder: (context) => CustomDialogBox( - title: VoteTextConstants.warning, - descriptions: VoteTextConstants.warningMessage, + title: AppLocalizations.of(context)!.voteWarning, + descriptions: AppLocalizations.of(context)!.voteWarningMessage, onYes: () { selectedContenderNotifier.clear(); animation.forward(from: 0); diff --git a/lib/vote/ui/pages/main_page/main_page.dart b/lib/vote/ui/pages/main_page/main_page.dart index 09ae09597c..488eeb081f 100644 --- a/lib/vote/ui/pages/main_page/main_page.dart +++ b/lib/vote/ui/pages/main_page/main_page.dart @@ -16,13 +16,13 @@ import 'package:titan/vote/providers/status_provider.dart'; import 'package:titan/vote/providers/voted_section_provider.dart'; import 'package:titan/vote/repositories/status_repository.dart'; import 'package:titan/vote/router.dart'; -import 'package:titan/vote/tools/constants.dart'; import 'package:titan/vote/ui/pages/main_page/list_contender_card.dart'; import 'package:titan/vote/ui/pages/main_page/list_side_item.dart'; import 'package:titan/vote/ui/pages/main_page/section_title.dart'; import 'package:titan/vote/ui/pages/main_page/vote_button.dart'; import 'package:titan/vote/ui/vote.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class VoteMainPage extends HookConsumerWidget { const VoteMainPage({super.key}); @@ -76,11 +76,11 @@ class VoteMainPage extends HookConsumerWidget { ), ], ), - const Expanded( + Expanded( child: Center( child: Text( - VoteTextConstants.canNotVote, - style: TextStyle(fontSize: 20), + AppLocalizations.of(context)!.voteCanNotVote, + style: const TextStyle(fontSize: 20), ), ), ), diff --git a/lib/vote/ui/pages/main_page/section_title.dart b/lib/vote/ui/pages/main_page/section_title.dart index d571d9e83a..f86970e94d 100644 --- a/lib/vote/ui/pages/main_page/section_title.dart +++ b/lib/vote/ui/pages/main_page/section_title.dart @@ -3,7 +3,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/tools/ui/widgets/align_left_text.dart'; import 'package:titan/vote/class/section.dart'; import 'package:titan/vote/providers/sections_provider.dart'; -import 'package:titan/vote/tools/constants.dart'; +import 'package:titan/l10n/app_localizations.dart'; class SectionTitle extends HookConsumerWidget { final List
sectionList; @@ -15,7 +15,7 @@ class SectionTitle extends HookConsumerWidget { return AlignLeftText( section.id != Section.empty().id ? section.name - : VoteTextConstants.noSection, + : AppLocalizations.of(context)!.voteNoSection, padding: const EdgeInsets.only(left: 20), fontSize: 20, fontWeight: FontWeight.w700, diff --git a/lib/vote/ui/pages/main_page/vote_button.dart b/lib/vote/ui/pages/main_page/vote_button.dart index b9e206f20b..5414906b3d 100644 --- a/lib/vote/ui/pages/main_page/vote_button.dart +++ b/lib/vote/ui/pages/main_page/vote_button.dart @@ -10,7 +10,7 @@ import 'package:titan/vote/providers/status_provider.dart'; import 'package:titan/vote/providers/voted_section_provider.dart'; import 'package:titan/vote/providers/votes_provider.dart'; import 'package:titan/vote/repositories/status_repository.dart'; -import 'package:titan/vote/tools/constants.dart'; +import 'package:titan/l10n/app_localizations.dart'; class VoteButton extends HookConsumerWidget { const VoteButton({super.key}); @@ -54,8 +54,8 @@ class VoteButton extends HookConsumerWidget { context: context, builder: (context) { return CustomDialogBox( - title: VoteTextConstants.vote, - descriptions: VoteTextConstants.confirmVote, + title: AppLocalizations.of(context)!.voteVote, + descriptions: AppLocalizations.of(context)!.voteConfirmVote, onYes: () { tokenExpireWrapper(ref, () async { final result = await votesNotifier.addVote( @@ -66,12 +66,12 @@ class VoteButton extends HookConsumerWidget { selectedContenderNotifier.clear(); displayVoteToastWithContext( TypeMsg.msg, - VoteTextConstants.voteSuccess, + AppLocalizations.of(context)!.voteVoteSuccess, ); } else { displayVoteToastWithContext( TypeMsg.error, - VoteTextConstants.voteError, + AppLocalizations.of(context)!.voteVoteError, ); } }); @@ -107,16 +107,16 @@ class VoteButton extends HookConsumerWidget { child: Center( child: Text( selectedContender.id != "" - ? VoteTextConstants.voteFor + selectedContender.name + ? AppLocalizations.of(context)!.voteVoteFor + selectedContender.name : alreadyVotedSection.contains(section.id) - ? VoteTextConstants.alreadyVoted + ? AppLocalizations.of(context)!.voteAlreadyVoted : s == Status.open - ? VoteTextConstants.chooseList + ? AppLocalizations.of(context)!.voteChooseList : s == Status.waiting - ? VoteTextConstants.notOpenedVote + ? AppLocalizations.of(context)!.voteNotOpenedVote : s == Status.closed - ? VoteTextConstants.closedVote - : VoteTextConstants.onGoingCount, + ? AppLocalizations.of(context)!.voteClosedVote + : AppLocalizations.of(context)!.voteOnGoingCount, style: TextStyle( color: (selectedContender.id == "" && s != Status.open) || diff --git a/lib/vote/ui/pages/section_pages/add_section.dart b/lib/vote/ui/pages/section_pages/add_section.dart index 2b7777ef98..69a500fb2d 100644 --- a/lib/vote/ui/pages/section_pages/add_section.dart +++ b/lib/vote/ui/pages/section_pages/add_section.dart @@ -10,9 +10,9 @@ import 'package:titan/tools/ui/widgets/text_entry.dart'; import 'package:titan/vote/class/section.dart'; import 'package:titan/vote/providers/sections_contender_provider.dart'; import 'package:titan/vote/providers/sections_provider.dart'; -import 'package:titan/vote/tools/constants.dart'; import 'package:titan/vote/ui/vote.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; class AddSectionPage extends HookConsumerWidget { const AddSectionPage({super.key}); @@ -37,8 +37,8 @@ class AddSectionPage extends HookConsumerWidget { child: Column( children: [ const SizedBox(height: 50), - const AlignLeftText( - VoteTextConstants.addSection, + AlignLeftText( + AppLocalizations.of(context)!.voteAddSection, color: Colors.grey, ), Form( @@ -48,12 +48,12 @@ class AddSectionPage extends HookConsumerWidget { const SizedBox(height: 30), TextEntry( controller: name, - label: VoteTextConstants.sectionName, + label: AppLocalizations.of(context)!.voteSectionName, ), const SizedBox(height: 30), TextEntry( controller: description, - label: VoteTextConstants.sectionDescription, + label: AppLocalizations.of(context)!.voteSectionDescription, ), const SizedBox(height: 50), WaitingButton( @@ -74,19 +74,19 @@ class AddSectionPage extends HookConsumerWidget { }); displayVoteToastWithContext( TypeMsg.msg, - VoteTextConstants.addedSection, + AppLocalizations.of(context)!.voteAddedSection, ); } else { displayVoteToastWithContext( TypeMsg.error, - VoteTextConstants.addingError, + AppLocalizations.of(context)!.voteAddingError, ); } }); }, - child: const Text( - VoteTextConstants.add, - style: TextStyle( + child: Text( + AppLocalizations.of(context)!.voteAdd, + style: const TextStyle( color: Colors.white, fontSize: 25, fontWeight: FontWeight.bold, From ce905e8de066062d0c89b9165488b6788a205323 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Thu, 3 Jul 2025 18:17:42 +0200 Subject: [PATCH 053/473] Tools --- lib/purchases/ui/pages/main_page/custom_button.dart | 3 +-- lib/tools/constants.dart | 11 ----------- lib/tools/ui/builders/async_child.dart | 2 +- lib/tools/ui/widgets/admin_button.dart | 2 +- lib/tools/ui/widgets/date_entry.dart | 2 +- lib/tools/ui/widgets/image_picker_on_tap.dart | 2 +- lib/tools/ui/widgets/text_entry.dart | 6 +++--- 7 files changed, 8 insertions(+), 20 deletions(-) diff --git a/lib/purchases/ui/pages/main_page/custom_button.dart b/lib/purchases/ui/pages/main_page/custom_button.dart index 518fd379eb..b53f37d723 100644 --- a/lib/purchases/ui/pages/main_page/custom_button.dart +++ b/lib/purchases/ui/pages/main_page/custom_button.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; -import 'package:titan/tools/constants.dart'; class CustomButton extends StatelessWidget { final VoidCallback onTap; @@ -14,7 +13,7 @@ class CustomButton extends StatelessWidget { required this.onTap, this.textColor = Colors.white, this.color = Colors.black, - this.text = TextConstants.admin, + this.text = 'Admin', this.colors, required this.icon, }); diff --git a/lib/tools/constants.dart b/lib/tools/constants.dart index c727e33541..39473df2bc 100644 --- a/lib/tools/constants.dart +++ b/lib/tools/constants.dart @@ -20,17 +20,6 @@ class ColorConstants { static const Color mainBorder = Color(0xFF950303); } -class TextConstants { - static const String admin = 'Admin'; - static const String error = "Une erreur est survenue"; - static const String noValue = "Veuillez entrer une valeur"; - static const String invalidNumber = "Veuillez entrer un nombre"; - static const String noDateError = "Veuillez entrer une date"; - static const String imageSizeTooBig = - "La taille de l'image ne doit pas dépasser 4 Mio"; - static const String imageError = "Erreur lors de l'ajout de l'image"; -} - const String previousEmailRegex = r'^[\w\-.]*@((ecl\d{2})|(alternance\d{4})|(master)|(auditeur)).ec-lyon.fr$'; diff --git a/lib/tools/ui/builders/async_child.dart b/lib/tools/ui/builders/async_child.dart index 21f001c77f..22dc7d3069 100644 --- a/lib/tools/ui/builders/async_child.dart +++ b/lib/tools/ui/builders/async_child.dart @@ -29,7 +29,7 @@ class AsyncChild extends StatelessWidget { errorBuilder ?? (error, stack) => Center( child: Text( - "${TextConstants.error}:$error", + "An error occured:$error", style: TextStyle(color: loaderColor), ), ); diff --git a/lib/tools/ui/widgets/admin_button.dart b/lib/tools/ui/widgets/admin_button.dart index 2b90fc97e8..6caf1e0ec5 100644 --- a/lib/tools/ui/widgets/admin_button.dart +++ b/lib/tools/ui/widgets/admin_button.dart @@ -13,7 +13,7 @@ class AdminButton extends StatelessWidget { required this.onTap, this.textColor = Colors.white, this.color = Colors.black, - this.text = TextConstants.admin, + this.text = "Admin", this.colors, }); diff --git a/lib/tools/ui/widgets/date_entry.dart b/lib/tools/ui/widgets/date_entry.dart index 25c852de64..75a29d7d45 100644 --- a/lib/tools/ui/widgets/date_entry.dart +++ b/lib/tools/ui/widgets/date_entry.dart @@ -29,7 +29,7 @@ class DateEntry extends StatelessWidget { child: TextEntry( label: label, controller: controller, - noValueError: TextConstants.noDateError, + noValueError: "Date is required", enabled: enabled, color: color, enabledColor: enabledColor, diff --git a/lib/tools/ui/widgets/image_picker_on_tap.dart b/lib/tools/ui/widgets/image_picker_on_tap.dart index 779484f2ae..23d65ad679 100644 --- a/lib/tools/ui/widgets/image_picker_on_tap.dart +++ b/lib/tools/ui/widgets/image_picker_on_tap.dart @@ -37,7 +37,7 @@ class ImagePickerOnTap extends StatelessWidget { if (size > maxHyperionFileSize) { displayToastWithContext( TypeMsg.error, - TextConstants.imageSizeTooBig, + "Image size exceeds the maximum limit of 4Mio. Please select a smaller image.", ); } else { if (kIsWeb) { diff --git a/lib/tools/ui/widgets/text_entry.dart b/lib/tools/ui/widgets/text_entry.dart index 89bdae241c..6d426d45bc 100644 --- a/lib/tools/ui/widgets/text_entry.dart +++ b/lib/tools/ui/widgets/text_entry.dart @@ -33,7 +33,7 @@ class TextEntry extends StatelessWidget { this.color = Colors.black, this.enabledColor = Colors.black, this.errorColor = ColorConstants.error, - this.noValueError = TextConstants.noValue, + this.noValueError = "This field is required", this.suffixIcon, this.isNegative = false, this.textInputAction = TextInputAction.next, @@ -95,14 +95,14 @@ class TextEntry extends StatelessWidget { if (isInt) { final intValue = int.tryParse(value); if (intValue == null || (intValue < 0 && !isNegative)) { - return TextConstants.invalidNumber; + return "Invalid number"; } } if (isDouble) { final doubleValue = double.tryParse(value.replaceAll(',', '.')); if (doubleValue == null || (doubleValue < 0 && !isNegative)) { - return TextConstants.invalidNumber; + return "Invalid number"; } } From 678aacadf369d0e94b7cba27a4a8e5a8cc389c80 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Thu, 3 Jul 2025 18:22:13 +0200 Subject: [PATCH 054/473] still tools --- lib/tools/ui/builders/async_child.dart | 1 - lib/tools/ui/widgets/admin_button.dart | 1 - lib/tools/ui/widgets/date_entry.dart | 1 - 3 files changed, 3 deletions(-) diff --git a/lib/tools/ui/builders/async_child.dart b/lib/tools/ui/builders/async_child.dart index 22dc7d3069..10e59412d6 100644 --- a/lib/tools/ui/builders/async_child.dart +++ b/lib/tools/ui/builders/async_child.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/widgets/loader.dart'; class AsyncChild extends StatelessWidget { diff --git a/lib/tools/ui/widgets/admin_button.dart b/lib/tools/ui/widgets/admin_button.dart index 6caf1e0ec5..6db064ab3b 100644 --- a/lib/tools/ui/widgets/admin_button.dart +++ b/lib/tools/ui/widgets/admin_button.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; -import 'package:titan/tools/constants.dart'; class AdminButton extends StatelessWidget { final VoidCallback onTap; diff --git a/lib/tools/ui/widgets/date_entry.dart b/lib/tools/ui/widgets/date_entry.dart index 75a29d7d45..2c27630806 100644 --- a/lib/tools/ui/widgets/date_entry.dart +++ b/lib/tools/ui/widgets/date_entry.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/widgets/text_entry.dart'; class DateEntry extends StatelessWidget { From 1183e69f22427e50e9d8477632991a1b45d40c7f Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Thu, 3 Jul 2025 19:50:26 +0200 Subject: [PATCH 055/473] english trad --- lib/l10n/app_en.arb | 1103 +++++++++++++++ lib/l10n/app_localizations_en.dart | 2104 ++++++++++++++-------------- 2 files changed, 2142 insertions(+), 1065 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index e69de29bb2..6cbe7f89da 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -0,0 +1,1103 @@ + { + "@@locale": "en", + "adminAccountTypes": "Account types", + "adminAdd": "Add", + "adminAddGroup": "Add group", + "adminAddMember": "Add member", + "adminAddedGroup": "Group created", + "adminAddedLoaner": "Lender added", + "adminAddedMember": "Member added", + "adminAddingError": "Error while adding", + "adminAddingMember": "Adding a member", + "adminAddLoaningGroup": "Add loaning group", + "adminAddSchool": "Add school", + "adminAddStructure": "Add structure", + "adminAddedSchool": "School created", + "adminAddedStructure": "Structure added", + "adminEditedStructure": "Structure edited", + "adminAdministration": "Administration", + "adminAssociationMembership": "Membership", + "adminAssociationMembershipName": "Membership name", + "adminAssociationsMemberships": "Memberships", + "adminClearFilters": "Clear filters", + "adminCreateAssociationMembership": "Create membership", + "adminCreatedAssociationMembership": "Membership created", + "adminCreationError": "Error during creation", + "adminDateError": "Start date must be before end date", + "adminDelete": "Delete", + "adminDeleteAssociationMembership": "Delete membership?", + "adminDeletedAssociationMembership": "Membership deleted", + "adminDeleteGroup": "Delete group?", + "adminDeletedGroup": "Group deleted", + "adminDeleteSchool": "Delete school?", + "adminDeletedSchool": "School deleted", + "adminDeleting": "Deleting", + "adminDeletingError": "Error while deleting", + "adminDescription": "Description", + "adminEclSchool": "Centrale Lyon", + "adminEdit": "Edit", + "adminEditStructure": "Edit structure", + "adminEditMembership": "Edit membership", + "adminEmptyDate": "Empty date", + "adminEmptyFieldError": "Name cannot be empty", + "adminEmailRegex": "Email Regex", + "adminEmptyUser": "Empty user", + "adminEndDate": "End date", + "adminEndDateMaximal": "Maximum end date", + "adminEndDateMinimal": "Minimum end date", + "adminError": "Error", + "adminFilters": "Filters", + "adminGroup": "Group", + "adminGroups": "Groups", + "adminLoaningGroup": "Loaning group", + "adminLooking": "Searching", + "adminManager": "Structure administrator", + "adminMaximum": "Maximum", + "adminMembers": "Members", + "adminMembershipAddingError": "Error while adding (likely due to overlapping dates)", + "adminMemberships": "Memberships", + "adminMembershipUpdatingError": "Error while updating (likely due to overlapping dates)", + "adminMinimum": "Minimum", + "adminModifyModuleVisibility": "Module visibility", + "adminMyEclPay": "MyECLPay", + "adminName": "Name", + "adminNoManager": "No manager selected", + "adminNoMember": "No member", + "adminNoMoreLoaner": "No lender available", + "adminNoSchool": "No school", + "adminRemoveGroupMember": "Remove member from group?", + "adminResearch": "Search", + "adminSchools": "Schools", + "adminStructures": "Structures", + "adminStartDate": "Start date", + "adminStartDateMaximal": "Maximum start date", + "adminStartDateMinimal": "Minimum start date", + "adminUpdatedAssociationMembership": "Membership updated", + "adminUpdatedGroup": "Group updated", + "adminUpdatedMembership": "Membership updated", + "adminUpdatingError": "Error while updating", + "adminUser": "User", + "adminValidateFilters": "Apply filters", + "adminVisibilities": "Visibilities", + "advertAdd": "Add", + "advertAddedAdvert": "Ad published", + "advertAddedAnnouncer": "Announcer added", + "advertAddingError": "Error while adding", + "advertAdmin": "Admin", + "advertAdvert": "Ad", + "advertChoosingAnnouncer": "Please choose an announcer", + "advertChoosingPoster": "Please choose an image", + "advertContent": "Content", + "advertDeleteAdvert": "Delete ad?", + "advertDeleteAnnouncer": "Delete announcer?", + "advertDeleting": "Deleting", + "advertEdit": "Edit", + "advertEditedAdvert": "Ad edited", + "advertEditingError": "Error while editing", + "advertGroupAdvert": "Group", + "advertIncorrectOrMissingFields": "Incorrect or missing fields", + "advertInvalidNumber": "Please enter a number", + "advertManagement": "Management", + "advertModifyAnnouncingGroup": "Edit announcement group", + "advertNoMoreAnnouncer": "No more announcers available", + "advertNoValue": "Please enter a value", + "advertPositiveNumber": "Please enter a positive number", + "advertRemovedAnnouncer": "Announcer removed", + "advertRemovingError": "Error during removal", + "advertTags": "Tags", + "advertTitle": "Title", + "advertMonthJan": "Jan", + "advertMonthFeb": "Feb", + "advertMonthMar": "Mar", + "advertMonthApr": "Apr", + "advertMonthMay": "May", + "advertMonthJun": "Jun", + "advertMonthJul": "Jul", + "advertMonthAug": "Aug", + "advertMonthSep": "Sep", + "advertMonthOct": "Oct", + "advertMonthNov": "Nov", + "advertMonthDec": "Dec", + "amapAccounts": "Accounts", + "amapAdd": "Add", + "amapAddDelivery": "Add delivery", + "amapAddedCommand": "Order added", + "amapAddedOrder": "Order added", + "amapAddedProduct": "Product added", + "amapAddedUser": "User added", + "amapAddProduct": "Add product", + "amapAddUser": "Add user", + "amapAddingACommand": "Add an order", + "amapAddingCommand": "Add the order", + "amapAddingError": "Error while adding", + "amapAddingProduct": "Add a product", + "amapAddOrder": "Add an order", + "amapAdmin": "Admin", + "amapAlreadyExistCommand": "An order already exists for this date", + "amapAmap": "Amap", + "amapAmount": "Balance", + "amapArchive": "Archive", + "amapArchiveDelivery": "Archive", + "amapArchivingDelivery": "Archiving delivery", + "amapCategory": "Category", + "amapCloseDelivery": "Lock", + "amapCommandDate": "Order date", + "amapCommandProducts": "Order products", + "amapConfirm": "Confirm", + "amapContact": "Association contacts", + "amapCreateCategory": "Create category", + "amapDelete": "Delete", + "amapDeleteDelivery": "Delete delivery?", + "amapDeleteDeliveryDescription": "Are you sure you want to delete this delivery?", + "amapDeletedDelivery": "Delivery deleted", + "amapDeletedOrder": "Order deleted", + "amapDeletedProduct": "Product deleted", + "amapDeleteProduct": "Delete product?", + "amapDeleteProductDescription": "Are you sure you want to delete this product?", + "amapDeleting": "Deleting", + "amapDeletingDelivery": "Delete delivery?", + "amapDeletingError": "Error while deleting", + "amapDeletingOrder": "Delete order?", + "amapDeletingProduct": "Delete product?", + "amapDeliver": "Delivery completed?", + "amapDeliveries": "Deliveries", + "amapDeliveringDelivery": "Are all orders delivered?", + "amapDelivery": "Delivery", + "amapDeliveryArchived": "Delivery archived", + "amapDeliveryDate": "Delivery date", + "amapDeliveryDelivered": "Delivery completed", + "amapDeliveryHistory": "Delivery history", + "amapDeliveryList": "Delivery list", + "amapDeliveryLocked": "Delivery locked", + "amapDeliveryOn": "Delivery on", + "amapDeliveryOpened": "Delivery opened", + "amapDeliveryNotArchived": "Delivery not archived", + "amapDeliveryNotLocked": "Delivery not locked", + "amapDeliveryNotDelivered": "Delivery not completed", + "amapDeliveryNotOpened": "Delivery not opened", + "amapEditDelivery": "Edit delivery", + "amapEditedCommand": "Order edited", + "amapEditingError": "Error while editing", + "amapEditProduct": "Edit product", + "amapEndingDelivery": "End of delivery", + "amapError": "Error", + "amapErrorLink": "Error opening link", + "amapErrorLoadingUser": "Error loading users", + "amapEvening": "Evening", + "amapExpectingNumber": "Please enter a number", + "amapFillField": "Please fill out this field", + "amapHandlingAccount": "Manage accounts", + "amapLoading": "Loading...", + "amapLoadingError": "Loading error", + "amapLock": "Lock", + "amapLocked": "Locked", + "amapLockedDelivery": "Delivery locked", + "amapLockedOrder": "Order locked", + "amapLooking": "Search", + "amapLockingDelivery": "Lock delivery?", + "amapMidDay": "Midday", + "amapMyOrders": "My orders", + "amapName": "Name", + "amapNextStep": "Next step", + "amapNoProduct": "No product", + "amapNoCurrentOrder": "No current order", + "amapNoMoney": "Not enough money", + "amapNoOpennedDelivery": "No open delivery", + "amapNoOrder": "No order", + "amapNoSelectedDelivery": "No delivery selected", + "amapNotEnoughMoney": "Not enough money", + "amapNotPlannedDelivery": "No scheduled delivery", + "amapOneOrder": "order", + "amapOpenDelivery": "Open", + "amapOpened": "Opened", + "amapOpenningDelivery": "Open delivery?", + "amapOrder": "Order", + "amapOrders": "Orders", + "amapPickChooseCategory": "Please enter a value or choose an existing category", + "amapPickDeliveryMoment": "Choose a delivery time", + "amapPresentation": "Presentation", + "amapPresentation1": "The AMAP (association for the preservation of small-scale farming) is a service offered by the Planet&Co association of ECL. You can receive products (fruit and vegetable baskets, juices, jams...) directly on campus!\n\nOrders must be placed before Friday at 9 PM and are delivered on campus on Tuesday from 1:00 PM to 1:45 PM (or from 6:15 PM to 6:30 PM if you can't come at midday) in the M16 hall.\n\nYou can only order if your balance allows it. You can top up your balance via the Lydia collection or by cheque during office hours.\n\nLydia top-up link: ", + "amapPresentation2": "\n\nFeel free to contact us if you have any issues!", + "amapPrice": "Price", + "amapProduct": "product", + "amapProducts": "Products", + "amapProductInDelivery": "Product in an unfinished delivery", + "amapQuantity": "Quantity", + "amapRequiredDate": "Date is required", + "amapSeeMore": "See more", + "amapThe": "The", + "amapUnlock": "Unlock", + "amapUnlockedDelivery": "Delivery unlocked", + "amapUnlockingDelivery": "Unlock delivery?", + "amapUpdate": "Edit", + "amapUpdatedAmount": "Balance updated", + "amapUpdatedOrder": "Order updated", + "amapUpdatedProduct": "Product updated", + "amapUpdatingError": "Update failed", + "amapUsersNotFound": "No users found", + "amapWaiting": "Pending", + "bookingAdd": "Add", + "bookingAddBookingPage": "Request", + "bookingAddRoom": "Add room", + "bookingAddBooking": "Add booking", + "bookingAddedBooking": "Request added", + "bookingAddedRoom": "Room added", + "bookingAddedManager": "Manager added", + "bookingAddingError": "Error while adding", + "bookingAddManager": "Add manager", + "bookingAdminPage": "Admin", + "bookingAllDay": "All day", + "bookingBookedFor": "Booked for", + "bookingBooking": "Booking", + "bookingBookingCreated": "Booking created", + "bookingBookingDemand": "Booking request", + "bookingBookingNote": "Booking note", + "bookingBookingPage": "Booking", + "bookingBookingReason": "Booking reason", + "bookingBy": "by", + "bookingConfirm": "Confirm", + "bookingConfirmation": "Confirmation", + "bookingConfirmBooking": "Confirm the booking?", + "bookingConfirmed": "Confirmed", + "bookingDates": "Dates", + "bookingDecline": "Decline", + "bookingDeclineBooking": "Decline the booking?", + "bookingDeclined": "Declined", + "bookingDelete": "Delete", + "bookingDeleting": "Deleting", + "bookingDeleteBooking": "Deleting", + "bookingDeleteBookingConfirmation": "Are you sure you want to delete this booking?", + "bookingDeletedBooking": "Booking deleted", + "bookingDeletedRoom": "Room deleted", + "bookingDeletedManager": "Manager deleted", + "bookingDeleteRoomConfirmation": "Are you sure you want to delete this room?\n\nThe room must have no current or upcoming bookings to be deleted", + "bookingDeleteManagerConfirmation": "Are you sure you want to delete this manager?\n\nThe manager must not be associated with any room to be deleted", + "bookingDeletingBooking": "Delete the booking?", + "bookingDeletingError": "Error while deleting", + "bookingDeletingRoom": "Delete the room?", + "bookingEdit": "Edit", + "bookingEditBooking": "Edit a booking", + "bookingEditionError": "Error while editing", + "bookingEditedBooking": "Booking edited", + "bookingEditedRoom": "Room edited", + "bookingEditedManager": "Manager edited", + "bookingEditManager": "Edit or delete a manager", + "bookingEditRoom": "Edit or delete a room", + "bookingEndDate": "End date", + "bookingEndHour": "End hour", + "bookingEntity": "For whom?", + "bookingError": "Error", + "bookingEventEvery": "Every", + "bookingHistoryPage": "History", + "bookingIncorrectOrMissingFields": "Incorrect or missing fields", + "bookingInterval": "Interval", + "bookingInvalidIntervalError": "Invalid interval", + "bookingInvalidDates": "Invalid dates", + "bookingInvalidRoom": "Invalid room", + "bookingKeysRequested": "Keys requested", + "bookingManagement": "Management", + "bookingManager": "Manager", + "bookingManagerName": "Manager name", + "bookingMultipleDay": "Multiple days", + "bookingMyBookings": "My bookings", + "bookingNecessaryKey": "Key needed", + "bookingNext": "Next", + "bookingNo": "No", + "bookingNoCurrentBooking": "No current booking", + "bookingNoDateError": "Please choose a date", + "bookingNoAppointmentInReccurence": "No slot exists with these recurrence settings", + "bookingNoDaySelected": "No day selected", + "bookingNoDescriptionError": "Please enter a description", + "bookingNoKeys": "No keys", + "bookingNoNoteError": "Please enter a note", + "bookingNoPhoneRegistered": "Number not provided", + "bookingNoReasonError": "Please enter a reason", + "bookingNoRoomFoundError": "No room registered", + "bookingNoRoomFound": "No room found", + "bookingNote": "Note", + "bookingOther": "Other", + "bookingPending": "Pending", + "bookingPrevious": "Previous", + "bookingReason": "Reason", + "bookingRecurrence": "Recurrence", + "bookingRecurrenceDays": "Recurrence days", + "bookingRecurrenceEndDate": "Recurrence end date", + "bookingRecurrent": "Recurrent", + "bookingRegisteredRooms": "Registered rooms", + "bookingRoom": "Room", + "bookingRoomName": "Room name", + "bookingStartDate": "Start date", + "bookingStartHour": "Start hour", + "bookingWeeks": "Weeks", + "bookingYes": "Yes", + "bookingWeekDayMon": "Monday", + "bookingWeekDayTue": "Tuesday", + "bookingWeekDayWed": "Wednesday", + "bookingWeekDayThu": "Thursday", + "bookingWeekDayFri": "Friday", + "bookingWeekDaySat": "Saturday", + "bookingWeekDaySun": "Sunday", + "cinemaAdd": "Add", + "cinemaAddedSession": "Session added", + "cinemaAddingError": "Error while adding", + "cinemaAddSession": "Add a session", + "cinemaCinema": "Cinema", + "cinemaDeleteSession": "Delete the session?", + "cinemaDeleting": "Deleting", + "cinemaDuration": "Duration", + "cinemaEdit": "Edit", + "cinemaEditedSession": "Session edited", + "cinemaEditingError": "Error while editing", + "cinemaEditSession": "Edit the session", + "cinemaEmptyUrl": "Please enter a URL", + "cinemaImportFromTMDB": "Import from TMDB", + "cinemaIncomingSession": "Now showing", + "cinemaIncorrectOrMissingFields": "Incorrect or missing fields", + "cinemaInvalidUrl": "Invalid URL", + "cinemaGenre": "Genre", + "cinemaName": "Name", + "cinemaNoDateError": "Please enter a date", + "cinemaNoDuration": "Please enter a duration", + "cinemaNoOverview": "No synopsis", + "cinemaNoPoster": "No poster", + "cinemaNoSession": "No session", + "cinemaOverview": "Synopsis", + "cinemaPosterUrl": "Poster URL", + "cinemaSessionDate": "Session day", + "cinemaStartHour": "Start hour", + "cinemaTagline": "Tagline", + "cinemaThe": "The", + "drawerAdmin": "Administration", + "drawerAndroidAppLink": "https://play.google.com/store/apps/details?id=fr.myecl.titan", + "drawerCopied": "Copied!", + "drawerDownloadAppOnMobileDevice": "This site is the web version of the MyECL app. We invite you to download the app. Use this site only if you have problems with the app.\n", + "drawerIosAppLink": "https://apps.apple.com/fr/app/myecl/id6444443430", + "drawerLoginOut": "Do you want to log out?", + "drawerLogOut": "Log out", + "drawerOr": " or ", + "drawerSettings": "Settings", + "eventAdd": "Add", + "eventAddEvent": "Add an event", + "eventAddedEvent": "Event added", + "eventAddingError": "Error while adding", + "eventAllDay": "All day", + "eventConfirm": "Confirm", + "eventConfirmEvent": "Confirm the event?", + "eventConfirmation": "Confirmation", + "eventConfirmed": "Confirmed", + "eventDates": "Dates", + "eventDecline": "Decline", + "eventDeclineEvent": "Decline the event?", + "eventDeclined": "Declined", + "eventDelete": "Delete", + "eventDeletedEvent": "Event deleted", + "eventDeleting": "Deleting", + "eventDeletingError": "Error while deleting", + "eventDeletingEvent": "Delete the event?", + "eventDescription": "Description", + "eventEdit": "Edit", + "eventEditEvent": "Edit an event", + "eventEditedEvent": "Event edited", + "eventEditingError": "Error while editing", + "eventEndDate": "End date", + "eventEndHour": "End hour", + "eventError": "Error", + "eventEventList": "Event list", + "eventEventType": "Event type", + "eventEvery": "Every", + "eventHistory": "History", + "eventIncorrectOrMissingFields": "Some fields are incorrect or missing", + "eventInterval": "Interval", + "eventInvalidDates": "End date must be after start date", + "eventInvalidIntervalError": "Please enter a valid interval", + "eventLocation": "Location", + "eventMyEvents": "My events", + "eventName": "Name", + "eventNext": "Next", + "eventNo": "No", + "eventNoCurrentEvent": "No current event", + "eventNoDateError": "Please enter a date", + "eventNoDaySelected": "No day selected", + "eventNoDescriptionError": "Please enter a description", + "eventNoEvent": "No event", + "eventNoNameError": "Please enter a name", + "eventNoOrganizerError": "Please enter an organizer", + "eventNoPlaceError": "Please enter a location", + "eventNoPhoneRegistered": "Number not provided", + "eventNoRuleError": "Please enter a recurrence rule", + "eventOrganizer": "Organizer", + "eventOther": "Other", + "eventPending": "Pending", + "eventPrevious": "Previous", + "eventRecurrence": "Recurrence", + "eventRecurrenceDays": "Recurrence days", + "eventRecurrenceEndDate": "Recurrence end date", + "eventRecurrenceRule": "Recurrence rule", + "eventRoom": "Room", + "eventStartDate": "Start date", + "eventStartHour": "Start hour", + "eventTitle": "Events", + "eventYes": "Yes", + "eventEventEvery": "Every", + "eventWeeks": "weeks", + "eventDayMon": "Monday", + "eventDayTue": "Tuesday", + "eventDayWed": "Wednesday", + "eventDayThu": "Thursday", + "eventDayFri": "Friday", + "eventDaySat": "Saturday", + "eventDaySun": "Sunday", + "homeCalendar": "Calendar", + "homeEventOf": "Events of", + "homeIncomingEvents": "Upcoming events", + "homeLastInfos": "Latest announcements", + "homeNoEvents": "No events", + "homeTranslateDayShortMon": "Mon", + "homeTranslateDayShortTue": "Tue", + "homeTranslateDayShortWed": "Wed", + "homeTranslateDayShortThu": "Thu", + "homeTranslateDayShortFri": "Fri", + "homeTranslateDayShortSat": "Sat", + "homeTranslateDayShortSun": "Sun", + "loanAdd": "Add", + "loanAddLoan": "Add a loan", + "loanAddObject": "Add an object", + "loanAddedLoan": "Loan added", + "loanAddedObject": "Object added", + "loanAddedRoom": "Room added", + "loanAddingError": "Error while adding", + "loanAdmin": "Administrator", + "loanAvailable": "Available", + "loanAvailableMultiple": "Available", + "loanBorrowed": "Borrowed", + "loanBorrowedMultiple": "Borrowed", + "loanAnd": "and", + "loanAssociation": "Association", + "loanAvailableItems": "Available items", + "loanBeginDate": "Loan start date", + "loanBorrower": "Borrower", + "loanCaution": "Deposit", + "loanCancel": "Cancel", + "loanConfirm": "Confirm", + "loanConfirmation": "Confirmation", + "loanDates": "Dates", + "loanDays": "Days", + "loanDelay": "Extension delay", + "loanDelete": "Delete", + "loanDeletingLoan": "Delete the loan?", + "loanDeletedItem": "Object deleted", + "loanDeletedLoan": "Loan deleted", + "loanDeleting": "Deleting", + "loanDeletingError": "Error while deleting", + "loanDeletingItem": "Delete the object?", + "loanDuration": "Duration", + "loanEdit": "Edit", + "loanEditItem": "Edit the object", + "loanEditLoan": "Edit the loan", + "loanEditedRoom": "Room edited", + "loanEndDate": "Loan end date", + "loanEnded": "Ended", + "loanEnterDate": "Please enter a date", + "loanExtendedLoan": "Extended loan", + "loanExtendingError": "Error while extending", + "loanHistory": "History", + "loanIncorrectOrMissingFields": "Some fields are missing or incorrect", + "loanInvalidNumber": "Please enter a number", + "loanInvalidDates": "Dates are not valid", + "loanItem": "Item", + "loanItems": "Items", + "loanItemHandling": "Item management", + "loanItemSelected": "selected item", + "loanItemsSelected": "selected items", + "loanLendingDuration": "Possible loan duration", + "loanLoan": "Loan", + "loanLoanHandling": "Loan management", + "loanLooking": "Searching", + "loanName": "Name", + "loanNext": "Next", + "loanNo": "No", + "loanNoAssociationsFounded": "No associations found", + "loanNoAvailableItems": "No available items", + "loanNoBorrower": "No borrower", + "loanNoItems": "No items", + "loanNoItemSelected": "No item selected", + "loanNoLoan": "No loan", + "loanNoReturnedDate": "No return date", + "loanQuantity": "Quantity", + "loanNone": "None", + "loanNote": "Note", + "loanNoValue": "Please enter a value", + "loanOnGoing": "Ongoing", + "loanOnGoingLoan": "Ongoing loan", + "loanOthers": "others", + "loanPaidCaution": "Deposit paid", + "loanPositiveNumber": "Please enter a positive number", + "loanPrevious": "Previous", + "loanReturned": "Returned", + "loanReturnedLoan": "Returned loan", + "loanReturningError": "Error while returning", + "loanReturningLoan": "Return", + "loanReturnLoan": "Return the loan?", + "loanReturnLoanDescription": "Do you want to return this loan?", + "loanToReturn": "To return", + "loanUnavailable": "Unavailable", + "loanUpdate": "Edit", + "loanUpdatedItem": "Item updated", + "loanUpdatedLoan": "Loan updated", + "loanUpdatingError": "Error while updating", + "loanYes": "Yes", + "loginAccountActivated": "Account activated", + "loginAccountNotActivated": "Account not activated", + "loginActivationCode": "Activation code", + "loginBirthday": "Date of birth", + "loginCanBeEmpty": "This field can be empty", + "loginConfirmPassword": "Confirm password", + "loginCreate": "Create", + "loginCreateAccount": "Create an account", + "loginCreateAccountTitle": "Create an\naccount", + "loginEmail": "Email", + "loginEmailEmpty": "Please enter an email address", + "loginEmailInvalid": "Please enter a Centrale email address.\nIf you don't have one, please contact Éclair", + "loginEmptyFieldError": "This field cannot be empty", + "loginEndActivation": "Complete activation", + "loginEndResetPassword": "Complete\npassword reset", + "loginErrorResetPassword": "Error during reset", + "loginExpectingDate": "A date is expected", + "loginFillAllFields": "Please fill all fields", + "loginFirstname": "First name", + "loginFloor": "Floor", + "loginForgetPassword": "Forgot\npassword", + "loginForgotPassword": "Forgot password?", + "loginInvalidToken": "Invalid activation code", + "loginLoginFailed": "Login failed", + "loginMailSendingError": "Error during account creation", + "loginMustBeIntError": "This field must be an integer", + "loginName": "Last name", + "loginNewPassword": "New password", + "loginPassword": "Password", + "loginPasswordLengthError": "Password must be at least 6 characters", + "loginPasswordUppercaseError": "Password must contain at least one uppercase letter", + "loginPasswordLowercaseError": "Password must contain at least one lowercase letter", + "loginPasswordNumberError": "Password must contain at least one number", + "loginPasswordSpecialCaracterError": "Password must contain at least one special character", + "loginPasswordMustMatch": "Passwords must match", + "loginPasswordStrengthVeryWeak": "Very weak", + "loginPasswordStrengthWeak": "Weak", + "loginPasswordStrengthMedium": "Medium", + "loginPasswordStrengthStrong": "Strong", + "loginPasswordStrengthVeryStrong": "Very strong", + "loginPhone": "Phone", + "loginPromo": "Incoming class (e.g., 2023)", + "loginSendedMail": "Confirmation email sent", + "loginSendedResetMail": "Reset email sent", + "loginSignIn": "Sign in", + "loginRegister": "Register", + "loginRecievedMail": "I received the email", + "loginRecover": "Reset", + "loginResetedPassword": "Password reset", + "loginResetPasswordTitle": "Reset\npassword", + "loginNickname": "Nickname", + "loginWelcomeBack": "Welcome back", + "loginAppName": "MyECL", + "othersCheckInternetConnection": "Please check your internet connection", + "othersRetry": "Retry", + "othersTooOldVersion": "Your app version is too old.\n\nPlease update the app.", + "othersUnableToConnectToServer": "Unable to connect to the server", + "othersVersion": "Version", + "othersNoModule": "No modules available, please try again later 😢😢", + "othersAdmin": "Admin", + "othersError": "An error occurred", + "othersNoValue": "Please enter a value", + "othersInvalidNumber": "Please enter a number", + "othersNoDateError": "Please enter a date", + "othersImageSizeTooBig": "Image size must not exceed 4 MB", + "othersImageError": "Error adding the image", + "phAddNewJournal": "Add a new journal", + "phNameField": "Name: ", + "phDateField": "Date: ", + "phDelete": "Are you sure you want to delete this journal?", + "phIrreversibleAction": "This action is irreversible", + "phToHeavyFile": "File too large", + "phAddPdfFile": "Add a PDF file", + "phEditPdfFile": "Edit PDF file", + "phPhName": "PH name", + "phDate": "Date", + "phAdded": "Added", + "phEdited": "Edited", + "phAddingFileError": "Add error", + "phMissingInformatonsOrPdf": "Missing information or PDF file", + "phAdd": "Add", + "phEdit": "Edit", + "phSeePreviousJournal": "See previous journals", + "phNoJournalInDatabase": "No PH yet in database", + "phSuccesDowloading": "Successfully downloaded", + "phonebookActiveMandate": "Active mandate:", + "phonebookAdd": "Add", + "phonebookAddAssociation": "Add an association", + "phonebookAddedAssociation": "Association added", + "phonebookAddedMember": "Member added", + "phonebookAddingError": "Error adding", + "phonebookAddMember": "Add a member", + "phonebookAddRole": "Add a role", + "phonebookAdmin": "Admin", + "phonebookAdminPage": "Admin page", + "phonebookAll": "All", + "phonebookApparentName": "Public role name:", + "phonebookAssociation": "Association:", + "phonebookAssociationDetail": "Association details:", + "phonebookAssociationKind": "Type of association:", + "phonebookAssociationPure": "Association", + "phonebookAssociationPureSearch": " Association", + "phonebookAssociations": "Associations:", + "phonebookCancel": "Cancel", + "phonebookChangeMandate": "Switch to mandate ", + "phonebookChangeMandateConfirm": "Are you sure you want to change the entire mandate?\nThis action is irreversible!", + "phonebookCopied": "Copied to clipboard", + "phonebookDeactivateAssociation": "Are you sure you want to deactivate this association?\nThis action is irreversible!", + "phonebookDeactivatedAssociation": "Association deactivated", + "phonebookDeactivatedAssociationWarning": "Warning, this association is deactivated, you cannot modify it", + "phonebookDeactivating": "Deactivate the association?", + "phonebookDeactivatingError": "Error during deactivation", + "phonebookDetail": "Details:", + "phonebookDeleteAssociation": "Delete the association?\nThis will erase all association history", + "phonebookDeletedAssociation": "Association deleted", + "phonebookDeletedMember": "Member deleted", + "phonebookDeleting": "Deleting", + "phonebookDeletingError": "Error deleting", + "phonebookDescription": "Description", + "phonebookEdit": "Edit", + "phonebookEditMembership": "Edit role", + "phonebookEmail": "Email:", + "phonebookEmailCopied": "Email copied to clipboard", + "phonebookEmptyApparentName": "Please enter a role name", + "phonebookEmptyFieldError": "A field is not filled", + "phonebookEmptyKindError": "Please choose an association type", + "phonebookEmptyMember": "No member selected", + "phonebookErrorAssociationLoading": "Error loading association", + "phonebookErrorAssociationNameEmpty": "Please enter an association name", + "phonebookErrorAssociationPicture": "Error editing association picture", + "phonebookErrorKindsLoading": "Error loading association types", + "phonebookErrorLoadAssociationList": "Error loading association list", + "phonebookErrorLoadAssociationMember": "Error loading association members", + "phonebookErrorLoadAssociationPicture": "Error loading association picture", + "phonebookErrorLoadProfilePicture": "Error", + "phonebookErrorRoleTagsLoading": "Error loading role tags", + "phonebookExistingMembership": "This member is already in the current mandate", + "phonebookFirstname": "First name:", + "phonebookGroups": "Associated groups:", + "phonebookMandateChangingError": "Error changing mandate", + "phonebookMember": "Member", + "phonebookMemberReordered": "Member reordered", + "phonebookMembers": "Members", + "phonebookMembershipAssociationError": "Please choose an association", + "phonebookMembershipRole": "Role:", + "phonebookMembershipRoleError": "Please choose a role", + "phonebookName": "Last name:", + "phonebookNameCopied": "Name and first name copied to clipboard", + "phonebookNamePure": "Last name", + "phonebookNewMandate": "New mandate", + "phonebookNewMandateConfirmed": "Mandate changed", + "phonebookNickname": "Nickname:", + "phonebookNicknameCopied": "Nickname copied to clipboard", + "phonebookNoAssociationFound": "No association found", + "phonebookNoMember": "No member", + "phonebookNoMemberRole": "No role found", + "phonebookPhone": "Phone:", + "phonebookPhonebook": "Directory", + "phonebookPhonebookSearch": "Search", + "phonebookPhonebookSearchAssociation": "Association", + "phonebookPhonebookSearchField": "Search:", + "phonebookPhonebookSearchName": "Last name/First name/Nickname", + "phonebookPhonebookSearchRole": "Position", + "phonebookPresidentRoleTag": "Prez'", + "phonebookPromoNotGiven": "Promotion not provided", + "phonebookPromotion": "Promotion:", + "phonebookReorderingError": "Error during reordering", + "phonebookResearch": "Search", + "phonebookRolePure": "Role", + "phonebookTooHeavyAssociationPicture": "Image is too large (max 4MB)", + "phonebookUpdateGroups": "Update groups", + "phonebookUpdatedAssociation": "Association updated", + "phonebookUpdatedAssociationPicture": "Association picture has been changed", + "phonebookUpdatedGroups": "Groups updated", + "phonebookUpdatedMember": "Member updated", + "phonebookUpdatingError": "Error during update", + "phonebookValidation": "Validate", + "purchasesPurchases": "Purchases", + "purchasesResearch": "Search", + "purchasesNoPurchasesFound": "No purchases found", + "purchasesNoTickets": "No tickets", + "purchasesTicketsError": "Error loading tickets", + "purchasesPurchasesError": "Error loading purchases", + "purchasesNoPurchases": "No purchase", + "purchasesTimes": "times", + "purchasesAlreadyUsed": "Already used", + "purchasesNotPaid": "Not validated", + "purchasesPleaseSelectProduct": "Please select a product", + "purchasesProducts": "Products", + "purchasesCancel": "Cancel", + "purchasesValidate": "Validate", + "purchasesLeftScan": "Scans remaining", + "purchasesTag": "Tag", + "purchasesHistory": "History", + "purchasesPleaseSelectSeller": "Please select a seller", + "purchasesNoTagGiven": "Warning, no tag entered", + "purchasesTickets": "Tickets", + "purchasesNoScannableProducts": "No scannable products", + "purchasesLoading": "Waiting for scan", + "purchasesScan": "Scan", + "raffleRaffle": "Raffle", + "rafflePrize": "Prize", + "rafflePrizes": "Prizes", + "raffleActualRaffles": "Current raffles", + "rafflePastRaffles": "Past raffles", + "raffleYourTickets": "All your tickets", + "raffleCreateMenu": "Creation menu", + "raffleNextRaffles": "Upcoming raffles", + "raffleNoTicket": "You have no ticket", + "raffleSeeRaffleDetail": "View prizes/tickets", + "raffleActualPrize": "Current prizes", + "raffleMajorPrize": "Major prizes", + "raffleTakeTickets": "Take your tickets", + "raffleNoTicketBuyable": "You cannot buy tickets right now", + "raffleNoCurrentPrize": "There are no prizes currently", + "raffleModifTombola": "You can modify your raffles or create new ones, all decisions must then be approved by admins", + "raffleCreateYourRaffle": "Your raffle creation menu", + "rafflePossiblePrice": "Possible prize", + "raffleInformation": "Information and statistics", + "raffleAccounts": "Accounts", + "raffleAdd": "Add", + "raffleUpdatedAmount": "Amount updated", + "raffleUpdatingError": "Error during update", + "raffleDeletedPrize": "Prize deleted", + "raffleDeletingError": "Error during deletion", + "raffleQuantity": "Quantity", + "raffleClose": "Close", + "raffleOpen": "Open", + "raffleAddTypeTicketSimple": "Add", + "raffleAddingError": "Error during addition", + "raffleEditTypeTicketSimple": "Edit", + "raffleFillField": "Field cannot be empty", + "raffleWaiting": "Loading", + "raffleEditingError": "Error during editing", + "raffleAddedTicket": "Ticket added", + "raffleEditedTicket": "Ticket edited", + "raffleAlreadyExistTicket": "Ticket already exists", + "raffleNumberExpected": "An integer is expected", + "raffleDeletedTicket": "Ticket deleted", + "raffleAddPrize": "Add", + "raffleEditPrize": "Edit", + "raffleOpenRaffle": "Open raffle", + "raffleCloseRaffle": "Close raffle", + "raffleOpenRaffleDescription": "You are going to open the raffle, users will be able to buy tickets. You will no longer be able to modify the raffle. Are you sure you want to continue?", + "raffleCloseRaffleDescription": "You are going to close the raffle, users will no longer be able to buy tickets. Are you sure you want to continue?", + "raffleNoCurrentRaffle": "There is no ongoing raffle", + "raffleBoughtTicket": "Ticket purchased", + "raffleDrawingError": "Error during drawing", + "raffleInvalidPrice": "Price must be greater than 0", + "raffleMustBePositive": "Number must be strictly positive", + "raffleDraw": "Draw", + "raffleDrawn": "Drawn", + "raffleError": "Error", + "raffleGathered": "Collected", + "raffleTickets": "Tickets", + "raffleTicket": "ticket", + "raffleWinner": "Winner", + "raffleNoPrize": "No prize", + "raffleDeletePrize": "Delete prize", + "raffleDeletePrizeDescription": "You are going to delete the prize, are you sure you want to continue?", + "raffleDrawing": "Drawing", + "raffleDrawingDescription": "Draw the prize winner?", + "raffleDeleteTicket": "Delete ticket", + "raffleDeleteTicketDescription": "You are going to delete the ticket, are you sure you want to continue?", + "raffleWinningTickets": "Winning tickets", + "raffleNoWinningTicketYet": "Winning tickets will be displayed here", + "raffleName": "Name", + "raffleDescription": "Description", + "raffleBuyThisTicket": "Buy this ticket", + "raffleLockedRaffle": "Locked raffle", + "raffleUnavailableRaffle": "Unavailable raffle", + "raffleNotEnoughMoney": "You don't have enough money", + "raffleWinnable": "winnable", + "raffleNoDescription": "No description", + "raffleAmount": "Balance", + "raffleLoading": "Loading", + "raffleTicketNumber": "Number of tickets", + "rafflePrice": "Price", + "raffleEditRaffle": "Edit raffle", + "raffleEdit": "Edit", + "raffleAddPackTicket": "Add ticket pack", + "recommendationRecommendation": "Deals", + "recommendationTitle": "Title", + "recommendationLogo": "Logo", + "recommendationCode": "Code", + "recommendationSummary": "Short summary", + "recommendationDescription": "Description", + "recommendationAdd": "Add", + "recommendationEdit": "Edit", + "recommendationDelete": "Delete", + "recommendationAddImage": "Please add an image", + "recommendationAddedRecommendation": "Deal added", + "recommendationEditedRecommendation": "Deal updated", + "recommendationDeleteRecommendationConfirmation": "Are you sure you want to delete this deal?", + "recommendationDeleteRecommendation": "Delete", + "recommendationDeletingRecommendationError": "Error during deletion", + "recommendationDeletedRecommendation": "Deal deleted", + "recommendationIncorrectOrMissingFields": "Incorrect or missing fields", + "recommendationEditingError": "Edit failed", + "recommendationAddingError": "Add failed", + "recommendationCopiedCode": "Discount code copied", + "seedLibraryAdd": "Add", + "seedLibraryAddedPlant": "Plant added", + "seedLibraryAddedSpecies": "Species added", + "seedLibraryAddingError": "Error during addition", + "seedLibraryAddPlant": "Deposit a plant", + "seedLibraryAddSpecies": "Add a species", + "seedLibraryAll": "All", + "seedLibraryAncestor": "Ancestor", + "seedLibraryAround": "around", + "seedLibraryAutumn": "Autumn", + "seedLibraryBorrowedPlant": "Borrowed plant", + "seedLibraryBorrowingDate": "Borrowing date:", + "seedLibraryBorrowPlant": "Borrow plant", + "seedLibraryCard": "Card", + "seedLibraryChoosingAncestor": "Please choose an ancestor", + "seedLibraryChoosingSpecies": "Please choose a species", + "seedLibraryChoosingSpeciesOrAncestor": "Please choose a species or an ancestor", + "seedLibraryContact": "Contact:", + "seedLibraryDays": "days", + "seedLibraryDeadMsg": "Do you want to declare the plant dead?", + "seedLibraryDeadPlant": "Dead plant", + "seedLibraryDeathDate": "Date of death", + "seedLibraryDeletedSpecies": "Species deleted", + "seedLibraryDeleteSpecies": "Delete species?", + "seedLibraryDeleting": "Deleting", + "seedLibraryDeletingError": "Error during deletion", + "seedLibraryDepositNotAvailable": "Plant deposit is not possible without borrowing a plant first", + "seedLibraryDescription": "Description", + "seedLibraryDifficulty": "Difficulty:", + "seedLibraryEdit": "Edit", + "seedLibraryEditedPlant": "Plant updated", + "seedLibraryEditInformation": "Edit information", + "seedLibraryEditingError": "Error during editing", + "seedLibraryEditSpecies": "Edit species", + "seedLibraryEmptyDifficultyError": "Please choose a difficulty", + "seedLibraryEmptyFieldError": "Please fill all fields", + "seedLibraryEmptyTypeError": "Please choose a plant type", + "seedLibraryEndMonth": "End month:", + "seedLibraryFacebookUrl": "Facebook link", + "seedLibraryFilters": "Filters", + "seedLibraryForum": "Oskour mom I killed my plant - Help forum", + "seedLibraryForumUrl": "Forum link", + "seedLibraryHelpSheets": "Plant sheets", + "seedLibraryInformation": "Information:", + "seedLibraryMaturationTime": "Maturation time", + "seedLibraryMonthJan": "January", + "seedLibraryMonthFeb": "February", + "seedLibraryMonthMar": "March", + "seedLibraryMonthApr": "April", + "seedLibraryMonthMay": "May", + "seedLibraryMonthJun": "June", + "seedLibraryMonthJul": "July", + "seedLibraryMonthAug": "August", + "seedLibraryMonthSep": "September", + "seedLibraryMonthOct": "October", + "seedLibraryMonthNov": "November", + "seedLibraryMonthDec": "December", + "seedLibraryMyPlants": "My plants", + "seedLibraryName": "Name", + "seedLibraryNbSeedsRecommended": "Number of seeds recommended", + "seedLibraryNbSeedsRecommendedError": "Please enter a recommended seed number greater than 0", + "seedLibraryNoDateError": "Please enter a date", + "seedLibraryNoFilteredPlants": "No plants match your search. Try other filters.", + "seedLibraryNoMorePlant": "No plants available", + "seedLibraryNoPersonalPlants": "You don't have any plants yet in your seed library. You can add some in the stocks.", + "seedLibraryNoSpecies": "No species found", + "seedLibraryNoStockPlants": "No plants available in stock", + "seedLibraryNotes": "Notes", + "seedLibraryOk": "OK", + "seedLibraryPlantationPeriod": "Planting period:", + "seedLibraryPlantationType": "Plantation type:", + "seedLibraryPlantDetail": "Plant details", + "seedLibraryPlantingDate": "Planting date", + "seedLibraryPlantingNow": "I'm planting it now", + "seedLibraryPrefix": "Prefix", + "seedLibraryPrefixError": "Prefix already used", + "seedLibraryPrefixLengthError": "The prefix must be 3 characters", + "seedLibraryPropagationMethod": "Propagation method:", + "seedLibraryReference": "Reference:", + "seedLibraryRemovedPlant": "Plant removed", + "seedLibraryRemovingError": "Error removing plant", + "seedLibraryResearch": "Search", + "seedLibrarySaveChanges": "Save changes", + "seedLibrarySeason": "Season:", + "seedLibrarySeed": "Seed", + "seedLibrarySeeds": "seeds", + "seedLibrarySeedDeposit": "Plant deposit", + "seedLibrarySeedLibrary": "Seed library", + "seedLibrarySeedQuantitySimple": "Seed quantity", + "seedLibrarySeedQuantity": "Seed quantity:", + "seedLibraryShowDeadPlants": "Show dead plants", + "seedLibrarySpecies": "Species:", + "seedLibrarySpeciesHelp": "Help on species", + "seedLibrarySpeciesPlural": "Species", + "seedLibrarySpeciesSimple": "Species", + "seedLibrarySpeciesType": "Species type:", + "seedLibrarySpring": "Spring", + "seedLibraryStartMonth": "Start month:", + "seedLibraryStock": "Available stock", + "seedLibrarySummer": "Summer", + "seedLibraryStocks": "Stocks", + "seedLibraryTimeUntilMaturation": "Time until maturation:", + "seedLibraryType": "Type:", + "seedLibraryUnableToOpen": "Unable to open link", + "seedLibraryUpdate": "Edit", + "seedLibraryUpdatedInformation": "Information updated", + "seedLibraryUpdatedSpecies": "Species updated", + "seedLibraryUpdatedPlant": "Plant updated", + "seedLibraryUpdatingError": "Error updating", + "seedLibraryWinter": "Winter", + "seedLibraryWriteReference": "Please write the following reference: ", + "settingsAccount": "Account", + "settingsAddProfilePicture": "Add a photo", + "settingsAdmin": "Administrator", + "settingsAskHelp": "Ask for help", + "settingsAssociation": "Association", + "settingsBirthday": "Birthday", + "settingsBugs": "Bugs", + "settingsChangePassword": "Change password", + "settingsChangingPassword": "Do you really want to change your password?", + "settingsConfirmPassword": "Confirm password", + "settingsCopied": "Copied!", + "settingsDarkMode": "Dark mode", + "settingsDarkModeOff": "Off", + "settingsDeleteLogs": "Delete logs?", + "settingsDeleteNotificationLogs": "Delete notification logs?", + "settingsDetelePersonalData": "Delete my personal data", + "settingsDetelePersonalDataDesc": "This action notifies the administrator that you want to delete your personal data.", + "settingsDeleting": "Deleting", + "settingsEdit": "Edit", + "settingsEditAccount": "Edit account", + "settingsEditPassword": "Edit password", + "settingsEmail": "Email", + "settingsEmptyField": "This field cannot be empty", + "settingsErrorProfilePicture": "Error editing profile picture", + "settingsErrorSendingDemand": "Error sending request", + "settingsEventsIcal": "Ical link for events", + "settingsExpectingDate": "Expected birth date", + "settingsFirstname": "First name", + "settingsFloor": "Floor", + "settingsHelp": "Help", + "settingsIcalCopied": "Ical link copied!", + "settingsLanguage": "Language", + "settingsLanguageFr": "French", + "settingsLogs": "Logs", + "settingsModules": "Modules", + "settingsMyIcs": "My Ical link", + "settingsName": "Last name", + "settingsNewPassword": "New password", + "settingsNickname": "Nickname", + "settingsNotifications": "Notifications", + "settingsOldPassword": "Old password", + "settingsPasswordChanged": "Password changed", + "settingsPasswordsNotMatch": "Passwords do not match", + "settingsPersonalData": "Personal data", + "settingsPersonalisation": "Personalization", + "settingsPhone": "Phone", + "settingsProfilePicture": "Profile picture", + "settingsPromo": "Promotion", + "settingsRepportBug": "Report a bug", + "settingsSave": "Save", + "settingsSecurity": "Security", + "settingsSendedDemand": "Request sent", + "settingsSettings": "Settings", + "settingsTooHeavyProfilePicture": "Image is too large (max 4MB)", + "settingsUpdatedProfile": "Profile updated", + "settingsUpdatedProfilePicture": "Profile picture updated", + "settingsUpdateNotification": "Update notifications", + "settingsUpdatingError": "Error updating profile", + "settingsVersion": "Version", + "settingsPasswordStrength": "Password strength", + "settingsPasswordStrengthVeryWeak": "Very weak", + "settingsPasswordStrengthWeak": "Weak", + "settingsPasswordStrengthMedium": "Medium", + "settingsPasswordStrengthStrong": "Strong", + "settingsPasswordStrengthVeryStrong": "Very strong", + "voteAdd": "Add", + "voteAddMember": "Add a member", + "voteAddedPretendance": "List added", + "voteAddedSection": "Section added", + "voteAddingError": "Error adding", + "voteAddPretendance": "Add a list", + "voteAddSection": "Add a section", + "voteAll": "All", + "voteAlreadyAddedMember": "Member already added", + "voteAlreadyVoted": "Vote recorded", + "voteChooseList": "Choose a list", + "voteClear": "Reset", + "voteClearVotes": "Reset votes", + "voteClosedVote": "Votes closed", + "voteCloseVote": "Close votes", + "voteConfirmVote": "Confirm vote", + "voteCountVote": "Count votes", + "voteDeletedAll": "All deleted", + "voteDeletedPipo": "Fake lists deleted", + "voteDeletedSection": "Section deleted", + "voteDeleteAll": "Delete all", + "voteDeleteAllDescription": "Do you really want to delete everything?", + "voteDeletePipo": "Delete fake lists", + "voteDeletePipoDescription": "Do you really want to delete the fake lists?", + "voteDeletePretendance": "Delete the list", + "voteDeletePretendanceDesc": "Do you really want to delete this list?", + "voteDeleteSection": "Delete the section", + "voteDeleteSectionDescription": "Do you really want to delete this section?", + "voteDeletingError": "Error deleting", + "voteDescription": "Description", + "voteEdit": "Edit", + "voteEditedPretendance": "List edited", + "voteEditedSection": "Section edited", + "voteEditingError": "Error editing", + "voteErrorClosingVotes": "Error closing votes", + "voteErrorCountingVotes": "Error counting votes", + "voteErrorResetingVotes": "Error resetting votes", + "voteErrorOpeningVotes": "Error opening votes", + "voteIncorrectOrMissingFields": "Incorrect or missing fields", + "voteMembers": "Members", + "voteName": "Name", + "voteNoPretendanceList": "No list of candidates", + "voteNoSection": "No section", + "voteCanNotVote": "You cannot vote", + "voteNoSectionList": "No section", + "voteNotOpenedVote": "Vote not opened", + "voteOnGoingCount": "Counting in progress", + "voteOpenVote": "Open votes", + "votePipo": "Fake", + "votePretendance": "Lists", + "votePretendanceDeleted": "Candidate list deleted", + "votePretendanceNotDeleted": "Error deleting", + "voteProgram": "Program", + "votePublish": "Publish", + "votePublishVoteDescription": "Do you really want to publish the votes?", + "voteResetedVotes": "Votes reset", + "voteResetVote": "Reset votes", + "voteResetVoteDescription": "What do you want to do?", + "voteRole": "Role", + "voteSectionDescription": "Section description", + "voteSection": "Section", + "voteSectionName": "Section name", + "voteSeeMore": "See more", + "voteSelected": "Selected", + "voteShowVotes": "Show votes", + "voteVote": "Vote", + "voteVoteError": "Error recording vote", + "voteVoteFor": "Vote for ", + "voteVoteNotStarted": "Vote not opened", + "voteVoters": "Voting groups", + "voteVoteSuccess": "Vote recorded", + "voteVotes": "Votes", + "voteVotesClosed": "Votes closed", + "voteVotesCounted": "Votes counted", + "voteVotesOpened": "Votes opened", + "voteWarning": "Warning", + "voteWarningMessage": "Selection will not be saved.\nDo you want to continue?" + } \ No newline at end of file diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 3582190db8..63f6b66097 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -9,104 +9,103 @@ class AppLocalizationsEn extends AppLocalizations { AppLocalizationsEn([String locale = 'en']) : super(locale); @override - String get adminAccountTypes => 'Types de compte'; + String get adminAccountTypes => 'Account types'; @override - String get adminAdd => 'Ajouter'; + String get adminAdd => 'Add'; @override - String get adminAddGroup => 'Ajouter un groupe'; + String get adminAddGroup => 'Add group'; @override - String get adminAddMember => 'Ajouter un membre'; + String get adminAddMember => 'Add member'; @override - String get adminAddedGroup => 'Groupe créé'; + String get adminAddedGroup => 'Group created'; @override - String get adminAddedLoaner => 'Préteur ajouté'; + String get adminAddedLoaner => 'Lender added'; @override - String get adminAddedMember => 'Membre ajouté'; + String get adminAddedMember => 'Member added'; @override - String get adminAddingError => 'Erreur lors de l\'ajout'; + String get adminAddingError => 'Error while adding'; @override - String get adminAddingMember => 'Ajout d\'un membre'; + String get adminAddingMember => 'Adding a member'; @override - String get adminAddLoaningGroup => 'Ajouter un groupe de prêt'; + String get adminAddLoaningGroup => 'Add loaning group'; @override - String get adminAddSchool => 'Ajouter une école'; + String get adminAddSchool => 'Add school'; @override - String get adminAddStructure => 'Ajouter une structure'; + String get adminAddStructure => 'Add structure'; @override - String get adminAddedSchool => 'École créée'; + String get adminAddedSchool => 'School created'; @override - String get adminAddedStructure => 'Structure ajoutée'; + String get adminAddedStructure => 'Structure added'; @override - String get adminEditedStructure => 'Structure modifiée'; + String get adminEditedStructure => 'Structure edited'; @override String get adminAdministration => 'Administration'; @override - String get adminAssociationMembership => 'Adhésion'; + String get adminAssociationMembership => 'Membership'; @override - String get adminAssociationMembershipName => 'Nom de l\'adhésion'; + String get adminAssociationMembershipName => 'Membership name'; @override - String get adminAssociationsMemberships => 'Adhésions'; + String get adminAssociationsMemberships => 'Memberships'; @override - String get adminClearFilters => 'Effacer les filtres'; + String get adminClearFilters => 'Clear filters'; @override - String get adminCreateAssociationMembership => 'Créer une adhésion'; + String get adminCreateAssociationMembership => 'Create membership'; @override - String get adminCreatedAssociationMembership => 'Adhésion créée'; + String get adminCreatedAssociationMembership => 'Membership created'; @override - String get adminCreationError => 'Erreur lors de la création'; + String get adminCreationError => 'Error during creation'; @override - String get adminDateError => - 'La date de début doit être avant la date de fin'; + String get adminDateError => 'Start date must be before end date'; @override - String get adminDelete => 'Supprimer'; + String get adminDelete => 'Delete'; @override - String get adminDeleteAssociationMembership => 'Supprimer l\'adhésion ?'; + String get adminDeleteAssociationMembership => 'Delete membership?'; @override - String get adminDeletedAssociationMembership => 'Adhésion supprimée'; + String get adminDeletedAssociationMembership => 'Membership deleted'; @override - String get adminDeleteGroup => 'Supprimer le groupe ?'; + String get adminDeleteGroup => 'Delete group?'; @override - String get adminDeletedGroup => 'Groupe supprimé'; + String get adminDeletedGroup => 'Group deleted'; @override - String get adminDeleteSchool => 'Supprimer l\'école ?'; + String get adminDeleteSchool => 'Delete school?'; @override - String get adminDeletedSchool => 'École supprimée'; + String get adminDeletedSchool => 'School deleted'; @override - String get adminDeleting => 'Suppression'; + String get adminDeleting => 'Deleting'; @override - String get adminDeletingError => 'Erreur lors de la suppression'; + String get adminDeletingError => 'Error while deleting'; @override String get adminDescription => 'Description'; @@ -115,1014 +114,1011 @@ class AppLocalizationsEn extends AppLocalizations { String get adminEclSchool => 'Centrale Lyon'; @override - String get adminEdit => 'Modifier'; + String get adminEdit => 'Edit'; @override - String get adminEditStructure => 'Modifier la structure'; + String get adminEditStructure => 'Edit structure'; @override - String get adminEditMembership => 'Modifier l\'adhésion'; + String get adminEditMembership => 'Edit membership'; @override - String get adminEmptyDate => 'Date vide'; + String get adminEmptyDate => 'Empty date'; @override - String get adminEmptyFieldError => 'Le nom ne peut pas être vide'; + String get adminEmptyFieldError => 'Name cannot be empty'; @override String get adminEmailRegex => 'Email Regex'; @override - String get adminEmptyUser => 'Utilisateur vide'; + String get adminEmptyUser => 'Empty user'; @override - String get adminEndDate => 'Date de fin'; + String get adminEndDate => 'End date'; @override - String get adminEndDateMaximal => 'Date de fin maximale'; + String get adminEndDateMaximal => 'Maximum end date'; @override - String get adminEndDateMinimal => 'Date de fin minimale'; + String get adminEndDateMinimal => 'Minimum end date'; @override - String get adminError => 'Erreur'; + String get adminError => 'Error'; @override - String get adminFilters => 'Filtres'; + String get adminFilters => 'Filters'; @override - String get adminGroup => 'Groupe'; + String get adminGroup => 'Group'; @override - String get adminGroups => 'Groupes'; + String get adminGroups => 'Groups'; @override - String get adminLoaningGroup => 'Groupe de prêt'; + String get adminLoaningGroup => 'Loaning group'; @override - String get adminLooking => 'Recherche'; + String get adminLooking => 'Searching'; @override - String get adminManager => 'Administrateur de la structure'; + String get adminManager => 'Structure administrator'; @override String get adminMaximum => 'Maximum'; @override - String get adminMembers => 'Membres'; + String get adminMembers => 'Members'; @override String get adminMembershipAddingError => - 'Erreur lors de l\'ajout (surement dû à une superposition de dates)'; + 'Error while adding (likely due to overlapping dates)'; @override - String get adminMemberships => 'Adhésions'; + String get adminMemberships => 'Memberships'; @override String get adminMembershipUpdatingError => - 'Erreur lors de la modification (surement dû à une superposition de dates)'; + 'Error while updating (likely due to overlapping dates)'; @override String get adminMinimum => 'Minimum'; @override - String get adminModifyModuleVisibility => 'Visibilité des modules'; + String get adminModifyModuleVisibility => 'Module visibility'; @override String get adminMyEclPay => 'MyECLPay'; @override - String get adminName => 'Nom'; + String get adminName => 'Name'; @override - String get adminNoManager => 'Aucun manager n\'est sélectionné'; + String get adminNoManager => 'No manager selected'; @override - String get adminNoMember => 'Aucun membre'; + String get adminNoMember => 'No member'; @override - String get adminNoMoreLoaner => 'Aucun prêteur n\'est disponible'; + String get adminNoMoreLoaner => 'No lender available'; @override - String get adminNoSchool => 'Sans école'; + String get adminNoSchool => 'No school'; @override - String get adminRemoveGroupMember => 'Supprimer le membre du groupe ?'; + String get adminRemoveGroupMember => 'Remove member from group?'; @override - String get adminResearch => 'Recherche'; + String get adminResearch => 'Search'; @override - String get adminSchools => 'Écoles'; + String get adminSchools => 'Schools'; @override String get adminStructures => 'Structures'; @override - String get adminStartDate => 'Date de début'; + String get adminStartDate => 'Start date'; @override - String get adminStartDateMaximal => 'Date de début maximale'; + String get adminStartDateMaximal => 'Maximum start date'; @override - String get adminStartDateMinimal => 'Date de début minimale'; + String get adminStartDateMinimal => 'Minimum start date'; @override - String get adminUpdatedAssociationMembership => 'Adhésion modifiée'; + String get adminUpdatedAssociationMembership => 'Membership updated'; @override - String get adminUpdatedGroup => 'Groupe modifié'; + String get adminUpdatedGroup => 'Group updated'; @override - String get adminUpdatedMembership => 'Adhésion modifiée'; + String get adminUpdatedMembership => 'Membership updated'; @override - String get adminUpdatingError => 'Erreur lors de la modification'; + String get adminUpdatingError => 'Error while updating'; @override - String get adminUser => 'Utilisateur'; + String get adminUser => 'User'; @override - String get adminValidateFilters => 'Valider les filtres'; + String get adminValidateFilters => 'Apply filters'; @override - String get adminVisibilities => 'Visibilités'; + String get adminVisibilities => 'Visibilities'; @override - String get advertAdd => 'Ajouter'; + String get advertAdd => 'Add'; @override - String get advertAddedAdvert => 'Annonce publiée'; + String get advertAddedAdvert => 'Ad published'; @override - String get advertAddedAnnouncer => 'Annonceur ajouté'; + String get advertAddedAnnouncer => 'Announcer added'; @override - String get advertAddingError => 'Erreur lors de l\'ajout'; + String get advertAddingError => 'Error while adding'; @override String get advertAdmin => 'Admin'; @override - String get advertAdvert => 'Annonce'; + String get advertAdvert => 'Ad'; @override - String get advertChoosingAnnouncer => 'Veuillez choisir un annonceur'; + String get advertChoosingAnnouncer => 'Please choose an announcer'; @override - String get advertChoosingPoster => 'Veuillez choisir une image'; + String get advertChoosingPoster => 'Please choose an image'; @override - String get advertContent => 'Contenu'; + String get advertContent => 'Content'; @override - String get advertDeleteAdvert => 'Supprimer l\'annonce ?'; + String get advertDeleteAdvert => 'Delete ad?'; @override - String get advertDeleteAnnouncer => 'Supprimer l\'annonceur ?'; + String get advertDeleteAnnouncer => 'Delete announcer?'; @override - String get advertDeleting => 'Suppression'; + String get advertDeleting => 'Deleting'; @override - String get advertEdit => 'Modifier'; + String get advertEdit => 'Edit'; @override - String get advertEditedAdvert => 'Annonce modifiée'; + String get advertEditedAdvert => 'Ad edited'; @override - String get advertEditingError => 'Erreur lors de la modification'; + String get advertEditingError => 'Error while editing'; @override - String get advertGroupAdvert => 'Groupe'; + String get advertGroupAdvert => 'Group'; @override - String get advertIncorrectOrMissingFields => 'Champs incorrects ou manquants'; + String get advertIncorrectOrMissingFields => 'Incorrect or missing fields'; @override - String get advertInvalidNumber => 'Veuillez entrer un nombre'; + String get advertInvalidNumber => 'Please enter a number'; @override - String get advertManagement => 'Gestion'; + String get advertManagement => 'Management'; @override - String get advertModifyAnnouncingGroup => 'Modifier un groupe d\'annonce'; + String get advertModifyAnnouncingGroup => 'Edit announcement group'; @override - String get advertNoMoreAnnouncer => 'Aucun annonceur n\'est disponible'; + String get advertNoMoreAnnouncer => 'No more announcers available'; @override - String get advertNoValue => 'Veuillez entrer une valeur'; + String get advertNoValue => 'Please enter a value'; @override - String get advertPositiveNumber => 'Veuillez entrer un nombre positif'; + String get advertPositiveNumber => 'Please enter a positive number'; @override - String get advertRemovedAnnouncer => 'Annonceur supprimé'; + String get advertRemovedAnnouncer => 'Announcer removed'; @override - String get advertRemovingError => 'Erreur lors de la suppression'; + String get advertRemovingError => 'Error during removal'; @override String get advertTags => 'Tags'; @override - String get advertTitle => 'Titre'; + String get advertTitle => 'Title'; @override - String get advertMonthJan => 'Janv'; + String get advertMonthJan => 'Jan'; @override - String get advertMonthFeb => 'Févr.'; + String get advertMonthFeb => 'Feb'; @override - String get advertMonthMar => 'Mars'; + String get advertMonthMar => 'Mar'; @override - String get advertMonthApr => 'Avr.'; + String get advertMonthApr => 'Apr'; @override - String get advertMonthMay => 'Mai'; + String get advertMonthMay => 'May'; @override - String get advertMonthJun => 'Juin'; + String get advertMonthJun => 'Jun'; @override - String get advertMonthJul => 'Juill.'; + String get advertMonthJul => 'Jul'; @override - String get advertMonthAug => 'Août'; + String get advertMonthAug => 'Aug'; @override - String get advertMonthSep => 'Sept.'; + String get advertMonthSep => 'Sep'; @override - String get advertMonthOct => 'Oct.'; + String get advertMonthOct => 'Oct'; @override - String get advertMonthNov => 'Nov.'; + String get advertMonthNov => 'Nov'; @override - String get advertMonthDec => 'Déc.'; + String get advertMonthDec => 'Dec'; @override - String get amapAccounts => 'Comptes'; + String get amapAccounts => 'Accounts'; @override - String get amapAdd => 'Ajouter'; + String get amapAdd => 'Add'; @override - String get amapAddDelivery => 'Ajouter une livraison'; + String get amapAddDelivery => 'Add delivery'; @override - String get amapAddedCommand => 'Commande ajoutée'; + String get amapAddedCommand => 'Order added'; @override - String get amapAddedOrder => 'Commande ajoutée'; + String get amapAddedOrder => 'Order added'; @override - String get amapAddedProduct => 'Produit ajouté'; + String get amapAddedProduct => 'Product added'; @override - String get amapAddedUser => 'Utilisateur ajouté'; + String get amapAddedUser => 'User added'; @override - String get amapAddProduct => 'Ajouter un produit'; + String get amapAddProduct => 'Add product'; @override - String get amapAddUser => 'Ajouter un utilisateur'; + String get amapAddUser => 'Add user'; @override - String get amapAddingACommand => 'Ajouter une commande'; + String get amapAddingACommand => 'Add an order'; @override - String get amapAddingCommand => 'Ajouter la commande'; + String get amapAddingCommand => 'Add the order'; @override - String get amapAddingError => 'Erreur lors de l\'ajout'; + String get amapAddingError => 'Error while adding'; @override - String get amapAddingProduct => 'Ajouter un produit'; + String get amapAddingProduct => 'Add a product'; @override - String get amapAddOrder => 'Ajouter une commande'; + String get amapAddOrder => 'Add an order'; @override String get amapAdmin => 'Admin'; @override - String get amapAlreadyExistCommand => - 'Il existe déjà une commande à cette date'; + String get amapAlreadyExistCommand => 'An order already exists for this date'; @override String get amapAmap => 'Amap'; @override - String get amapAmount => 'Solde'; + String get amapAmount => 'Balance'; @override - String get amapArchive => 'Archiver'; + String get amapArchive => 'Archive'; @override - String get amapArchiveDelivery => 'Archiver'; + String get amapArchiveDelivery => 'Archive'; @override - String get amapArchivingDelivery => 'Archivage de la livraison'; + String get amapArchivingDelivery => 'Archiving delivery'; @override - String get amapCategory => 'Catégorie'; + String get amapCategory => 'Category'; @override - String get amapCloseDelivery => 'Verrouiller'; + String get amapCloseDelivery => 'Lock'; @override - String get amapCommandDate => 'Date de la commande'; + String get amapCommandDate => 'Order date'; @override - String get amapCommandProducts => 'Produits de la commande'; + String get amapCommandProducts => 'Order products'; @override - String get amapConfirm => 'Confirmer'; + String get amapConfirm => 'Confirm'; @override - String get amapContact => 'Contacts associatifs '; + String get amapContact => 'Association contacts'; @override - String get amapCreateCategory => 'Créer une catégorie'; + String get amapCreateCategory => 'Create category'; @override - String get amapDelete => 'Supprimer'; + String get amapDelete => 'Delete'; @override - String get amapDeleteDelivery => 'Supprimer la livraison ?'; + String get amapDeleteDelivery => 'Delete delivery?'; @override String get amapDeleteDeliveryDescription => - 'Voulez-vous vraiment supprimer cette livraison ?'; + 'Are you sure you want to delete this delivery?'; @override - String get amapDeletedDelivery => 'Livraison supprimée'; + String get amapDeletedDelivery => 'Delivery deleted'; @override - String get amapDeletedOrder => 'Commande supprimée'; + String get amapDeletedOrder => 'Order deleted'; @override - String get amapDeletedProduct => 'Produit supprimé'; + String get amapDeletedProduct => 'Product deleted'; @override - String get amapDeleteProduct => 'Supprimer le produit ?'; + String get amapDeleteProduct => 'Delete product?'; @override String get amapDeleteProductDescription => - 'Voulez-vous vraiment supprimer ce produit ?'; + 'Are you sure you want to delete this product?'; @override - String get amapDeleting => 'Suppression'; + String get amapDeleting => 'Deleting'; @override - String get amapDeletingDelivery => 'Supprimer la livraison ?'; + String get amapDeletingDelivery => 'Delete delivery?'; @override - String get amapDeletingError => 'Erreur lors de la suppression'; + String get amapDeletingError => 'Error while deleting'; @override - String get amapDeletingOrder => 'Supprimer la commande ?'; + String get amapDeletingOrder => 'Delete order?'; @override - String get amapDeletingProduct => 'Supprimer le produit ?'; + String get amapDeletingProduct => 'Delete product?'; @override - String get amapDeliver => 'Livraison teminée ?'; + String get amapDeliver => 'Delivery completed?'; @override - String get amapDeliveries => 'Livraisons'; + String get amapDeliveries => 'Deliveries'; @override - String get amapDeliveringDelivery => 'Toutes les commandes sont livrées ?'; + String get amapDeliveringDelivery => 'Are all orders delivered?'; @override - String get amapDelivery => 'Livraison'; + String get amapDelivery => 'Delivery'; @override - String get amapDeliveryArchived => 'Livraison archivée'; + String get amapDeliveryArchived => 'Delivery archived'; @override - String get amapDeliveryDate => 'Date de livraison'; + String get amapDeliveryDate => 'Delivery date'; @override - String get amapDeliveryDelivered => 'Livraison effectuée'; + String get amapDeliveryDelivered => 'Delivery completed'; @override - String get amapDeliveryHistory => 'Historique des livraisons'; + String get amapDeliveryHistory => 'Delivery history'; @override - String get amapDeliveryList => 'Liste des livraisons'; + String get amapDeliveryList => 'Delivery list'; @override - String get amapDeliveryLocked => 'Livraison verrouillée'; + String get amapDeliveryLocked => 'Delivery locked'; @override - String get amapDeliveryOn => 'Livraison le'; + String get amapDeliveryOn => 'Delivery on'; @override - String get amapDeliveryOpened => 'Livraison ouverte'; + String get amapDeliveryOpened => 'Delivery opened'; @override - String get amapDeliveryNotArchived => 'Livraison non archivée'; + String get amapDeliveryNotArchived => 'Delivery not archived'; @override - String get amapDeliveryNotLocked => 'Livraison non verrouillée'; + String get amapDeliveryNotLocked => 'Delivery not locked'; @override - String get amapDeliveryNotDelivered => 'Livraison non effectuée'; + String get amapDeliveryNotDelivered => 'Delivery not completed'; @override - String get amapDeliveryNotOpened => 'Livraison non ouverte'; + String get amapDeliveryNotOpened => 'Delivery not opened'; @override - String get amapEditDelivery => 'Modifier la livraison'; + String get amapEditDelivery => 'Edit delivery'; @override - String get amapEditedCommand => 'Commande modifiée'; + String get amapEditedCommand => 'Order edited'; @override - String get amapEditingError => 'Erreur lors de la modification'; + String get amapEditingError => 'Error while editing'; @override - String get amapEditProduct => 'Modifier le produit'; + String get amapEditProduct => 'Edit product'; @override - String get amapEndingDelivery => 'Fin de la livraison'; + String get amapEndingDelivery => 'End of delivery'; @override - String get amapError => 'Erreur'; + String get amapError => 'Error'; @override - String get amapErrorLink => 'Erreur lors de l\'ouverture du lien'; + String get amapErrorLink => 'Error opening link'; @override - String get amapErrorLoadingUser => - 'Erreur lors du chargement des utilisateurs'; + String get amapErrorLoadingUser => 'Error loading users'; @override - String get amapEvening => 'Soir'; + String get amapEvening => 'Evening'; @override - String get amapExpectingNumber => 'Veuillez entrer un nombre'; + String get amapExpectingNumber => 'Please enter a number'; @override - String get amapFillField => 'Veuillez remplir ce champ'; + String get amapFillField => 'Please fill out this field'; @override - String get amapHandlingAccount => 'Gérer les comptes'; + String get amapHandlingAccount => 'Manage accounts'; @override - String get amapLoading => 'Chargement...'; + String get amapLoading => 'Loading...'; @override - String get amapLoadingError => 'Erreur lors du chargement'; + String get amapLoadingError => 'Loading error'; @override - String get amapLock => 'Verrouiller'; + String get amapLock => 'Lock'; @override - String get amapLocked => 'Verrouillée'; + String get amapLocked => 'Locked'; @override - String get amapLockedDelivery => 'Livraison verrouillée'; + String get amapLockedDelivery => 'Delivery locked'; @override - String get amapLockedOrder => 'Commande verrouillée'; + String get amapLockedOrder => 'Order locked'; @override - String get amapLooking => 'Rechercher'; + String get amapLooking => 'Search'; @override - String get amapLockingDelivery => 'Verrouiller la livraison ?'; + String get amapLockingDelivery => 'Lock delivery?'; @override - String get amapMidDay => 'Midi'; + String get amapMidDay => 'Midday'; @override - String get amapMyOrders => 'Mes commandes'; + String get amapMyOrders => 'My orders'; @override - String get amapName => 'Nom'; + String get amapName => 'Name'; @override - String get amapNextStep => 'Étape suivante'; + String get amapNextStep => 'Next step'; @override - String get amapNoProduct => 'Pas de produit'; + String get amapNoProduct => 'No product'; @override - String get amapNoCurrentOrder => 'Pas de commande en cours'; + String get amapNoCurrentOrder => 'No current order'; @override - String get amapNoMoney => 'Pas assez d\'argent'; + String get amapNoMoney => 'Not enough money'; @override - String get amapNoOpennedDelivery => 'Pas de livraison ouverte'; + String get amapNoOpennedDelivery => 'No open delivery'; @override - String get amapNoOrder => 'Pas de commande'; + String get amapNoOrder => 'No order'; @override - String get amapNoSelectedDelivery => 'Pas de livraison sélectionnée'; + String get amapNoSelectedDelivery => 'No delivery selected'; @override - String get amapNotEnoughMoney => 'Pas assez d\'argent'; + String get amapNotEnoughMoney => 'Not enough money'; @override - String get amapNotPlannedDelivery => 'Pas de livraison planifiée'; + String get amapNotPlannedDelivery => 'No scheduled delivery'; @override - String get amapOneOrder => 'commande'; + String get amapOneOrder => 'order'; @override - String get amapOpenDelivery => 'Ouvrir'; + String get amapOpenDelivery => 'Open'; @override - String get amapOpened => 'Ouverte'; + String get amapOpened => 'Opened'; @override - String get amapOpenningDelivery => 'Ouvrir la livraison ?'; + String get amapOpenningDelivery => 'Open delivery?'; @override - String get amapOrder => 'Commander'; + String get amapOrder => 'Order'; @override - String get amapOrders => 'Commandes'; + String get amapOrders => 'Orders'; @override String get amapPickChooseCategory => - 'Veuillez entrer une valeur ou choisir une catégorie existante'; + 'Please enter a value or choose an existing category'; @override - String get amapPickDeliveryMoment => 'Choisissez un moment de livraison'; + String get amapPickDeliveryMoment => 'Choose a delivery time'; @override - String get amapPresentation => 'Présentation'; + String get amapPresentation => 'Presentation'; @override String get amapPresentation1 => - 'L\'AMAP (association pour le maintien d\'une agriculture paysanne) est un service proposé par l\'association Planet&Co de l\'ECL. Vous pouvez ainsi recevoir des produits (paniers de fruits et légumes, jus, confitures...) directement sur le campus !\n\nLes commandes doivent être passées avant le vendredi 21h et sont livrées sur le campus le mardi de 13h à 13h45 (ou de 18h15 à 18h30 si vous ne pouvez pas passer le midi) dans le hall du M16.\n\nVous ne pouvez commander que si votre solde le permet. Vous pouvez recharger votre solde via la collecte Lydia ou bien avec un chèque que vous pouvez nous transmettre lors des permanences.\n\nLien vers la collecte Lydia pour le rechargement : '; + 'The AMAP (association for the preservation of small-scale farming) is a service offered by the Planet&Co association of ECL. You can receive products (fruit and vegetable baskets, juices, jams...) directly on campus!\n\nOrders must be placed before Friday at 9 PM and are delivered on campus on Tuesday from 1:00 PM to 1:45 PM (or from 6:15 PM to 6:30 PM if you can\'t come at midday) in the M16 hall.\n\nYou can only order if your balance allows it. You can top up your balance via the Lydia collection or by cheque during office hours.\n\nLydia top-up link: '; @override String get amapPresentation2 => - '\n\nN\'hésitez pas à nous contacter en cas de problème !'; + '\n\nFeel free to contact us if you have any issues!'; @override - String get amapPrice => 'Prix'; + String get amapPrice => 'Price'; @override - String get amapProduct => 'produit'; + String get amapProduct => 'product'; @override - String get amapProducts => 'Produits'; + String get amapProducts => 'Products'; @override - String get amapProductInDelivery => 'Produit dans une livraison non terminée'; + String get amapProductInDelivery => 'Product in an unfinished delivery'; @override - String get amapQuantity => 'Quantité'; + String get amapQuantity => 'Quantity'; @override - String get amapRequiredDate => 'La date est requise'; + String get amapRequiredDate => 'Date is required'; @override - String get amapSeeMore => 'Voir plus'; + String get amapSeeMore => 'See more'; @override - String get amapThe => 'Le'; + String get amapThe => 'The'; @override - String get amapUnlock => 'Dévérouiller'; + String get amapUnlock => 'Unlock'; @override - String get amapUnlockedDelivery => 'Livraison dévérouillée'; + String get amapUnlockedDelivery => 'Delivery unlocked'; @override - String get amapUnlockingDelivery => 'Dévérouiller la livraison ?'; + String get amapUnlockingDelivery => 'Unlock delivery?'; @override - String get amapUpdate => 'Modifier'; + String get amapUpdate => 'Edit'; @override - String get amapUpdatedAmount => 'Solde modifié'; + String get amapUpdatedAmount => 'Balance updated'; @override - String get amapUpdatedOrder => 'Commande modifiée'; + String get amapUpdatedOrder => 'Order updated'; @override - String get amapUpdatedProduct => 'Produit modifié'; + String get amapUpdatedProduct => 'Product updated'; @override - String get amapUpdatingError => 'Echec de la modification'; + String get amapUpdatingError => 'Update failed'; @override - String get amapUsersNotFound => 'Aucun utilisateur trouvé'; + String get amapUsersNotFound => 'No users found'; @override - String get amapWaiting => 'En attente'; + String get amapWaiting => 'Pending'; @override - String get bookingAdd => 'Ajouter'; + String get bookingAdd => 'Add'; @override - String get bookingAddBookingPage => 'Demande'; + String get bookingAddBookingPage => 'Request'; @override - String get bookingAddRoom => 'Ajouter une salle'; + String get bookingAddRoom => 'Add room'; @override - String get bookingAddBooking => 'Ajouter une réservation'; + String get bookingAddBooking => 'Add booking'; @override - String get bookingAddedBooking => 'Demande ajoutée'; + String get bookingAddedBooking => 'Request added'; @override - String get bookingAddedRoom => 'Salle ajoutée'; + String get bookingAddedRoom => 'Room added'; @override - String get bookingAddedManager => 'Gestionnaire ajouté'; + String get bookingAddedManager => 'Manager added'; @override - String get bookingAddingError => 'Erreur lors de l\'ajout'; + String get bookingAddingError => 'Error while adding'; @override - String get bookingAddManager => 'Ajouter un gestionnaire'; + String get bookingAddManager => 'Add manager'; @override - String get bookingAdminPage => 'Administrateur'; + String get bookingAdminPage => 'Admin'; @override - String get bookingAllDay => 'Toute la journée'; + String get bookingAllDay => 'All day'; @override - String get bookingBookedFor => 'Réservé pour'; + String get bookingBookedFor => 'Booked for'; @override - String get bookingBooking => 'Réservation'; + String get bookingBooking => 'Booking'; @override - String get bookingBookingCreated => 'Réservation créée'; + String get bookingBookingCreated => 'Booking created'; @override - String get bookingBookingDemand => 'Demande de réservation'; + String get bookingBookingDemand => 'Booking request'; @override - String get bookingBookingNote => 'Note de la réservation'; + String get bookingBookingNote => 'Booking note'; @override - String get bookingBookingPage => 'Réservation'; + String get bookingBookingPage => 'Booking'; @override - String get bookingBookingReason => 'Motif de la réservation'; + String get bookingBookingReason => 'Booking reason'; @override - String get bookingBy => 'par'; + String get bookingBy => 'by'; @override - String get bookingConfirm => 'Confirmer'; + String get bookingConfirm => 'Confirm'; @override String get bookingConfirmation => 'Confirmation'; @override - String get bookingConfirmBooking => 'Confirmer la réservation ?'; + String get bookingConfirmBooking => 'Confirm the booking?'; @override - String get bookingConfirmed => 'Validée'; + String get bookingConfirmed => 'Confirmed'; @override String get bookingDates => 'Dates'; @override - String get bookingDecline => 'Refuser'; + String get bookingDecline => 'Decline'; @override - String get bookingDeclineBooking => 'Refuser la réservation ?'; + String get bookingDeclineBooking => 'Decline the booking?'; @override - String get bookingDeclined => 'Refusée'; + String get bookingDeclined => 'Declined'; @override - String get bookingDelete => 'Supprimer'; + String get bookingDelete => 'Delete'; @override - String get bookingDeleting => 'Suppression'; + String get bookingDeleting => 'Deleting'; @override - String get bookingDeleteBooking => 'Suppression'; + String get bookingDeleteBooking => 'Deleting'; @override String get bookingDeleteBookingConfirmation => - 'Êtes-vous sûr de vouloir supprimer cette réservation ?'; + 'Are you sure you want to delete this booking?'; @override - String get bookingDeletedBooking => 'Réservation supprimée'; + String get bookingDeletedBooking => 'Booking deleted'; @override - String get bookingDeletedRoom => 'Salle supprimée'; + String get bookingDeletedRoom => 'Room deleted'; @override - String get bookingDeletedManager => 'Gestionnaire supprimé'; + String get bookingDeletedManager => 'Manager deleted'; @override String get bookingDeleteRoomConfirmation => - 'Êtes-vous sûr de vouloir supprimer cette salle ?\n\nLa salle ne doit avoir aucune réservation en cours ou à venir pour être supprimée'; + 'Are you sure you want to delete this room?\n\nThe room must have no current or upcoming bookings to be deleted'; @override String get bookingDeleteManagerConfirmation => - 'Êtes-vous sûr de vouloir supprimer ce gestionnaire ?\n\nLe gestionnaire ne doit être associé à aucune salle pour pouvoir être supprimé'; + 'Are you sure you want to delete this manager?\n\nThe manager must not be associated with any room to be deleted'; @override - String get bookingDeletingBooking => 'Supprimer la réservation ?'; + String get bookingDeletingBooking => 'Delete the booking?'; @override - String get bookingDeletingError => 'Erreur lors de la suppression'; + String get bookingDeletingError => 'Error while deleting'; @override - String get bookingDeletingRoom => 'Supprimer la salle ?'; + String get bookingDeletingRoom => 'Delete the room?'; @override - String get bookingEdit => 'Modifier'; + String get bookingEdit => 'Edit'; @override - String get bookingEditBooking => 'Modifier une réservation'; + String get bookingEditBooking => 'Edit a booking'; @override - String get bookingEditionError => 'Erreur lors de la modification'; + String get bookingEditionError => 'Error while editing'; @override - String get bookingEditedBooking => 'Réservation modifiée'; + String get bookingEditedBooking => 'Booking edited'; @override - String get bookingEditedRoom => 'Salle modifiée'; + String get bookingEditedRoom => 'Room edited'; @override - String get bookingEditedManager => 'Gestionnaire modifié'; + String get bookingEditedManager => 'Manager edited'; @override - String get bookingEditManager => 'Modifier ou supprimer un gestionnaire'; + String get bookingEditManager => 'Edit or delete a manager'; @override - String get bookingEditRoom => 'Modifier ou supprimer une salle'; + String get bookingEditRoom => 'Edit or delete a room'; @override - String get bookingEndDate => 'Date de fin'; + String get bookingEndDate => 'End date'; @override - String get bookingEndHour => 'Heure de fin'; + String get bookingEndHour => 'End hour'; @override - String get bookingEntity => 'Pour qui ?'; + String get bookingEntity => 'For whom?'; @override - String get bookingError => 'Erreur'; + String get bookingError => 'Error'; @override - String get bookingEventEvery => 'Tous les'; + String get bookingEventEvery => 'Every'; @override - String get bookingHistoryPage => 'Historique'; + String get bookingHistoryPage => 'History'; @override - String get bookingIncorrectOrMissingFields => - 'Champs incorrects ou manquants'; + String get bookingIncorrectOrMissingFields => 'Incorrect or missing fields'; @override - String get bookingInterval => 'Intervalle'; + String get bookingInterval => 'Interval'; @override - String get bookingInvalidIntervalError => 'Intervalle invalide'; + String get bookingInvalidIntervalError => 'Invalid interval'; @override - String get bookingInvalidDates => 'Dates invalides'; + String get bookingInvalidDates => 'Invalid dates'; @override - String get bookingInvalidRoom => 'Salle invalide'; + String get bookingInvalidRoom => 'Invalid room'; @override - String get bookingKeysRequested => 'Clés demandées'; + String get bookingKeysRequested => 'Keys requested'; @override - String get bookingManagement => 'Gestion'; + String get bookingManagement => 'Management'; @override - String get bookingManager => 'Gestionnaire'; + String get bookingManager => 'Manager'; @override - String get bookingManagerName => 'Nom du gestionnaire'; + String get bookingManagerName => 'Manager name'; @override - String get bookingMultipleDay => 'Plusieurs jours'; + String get bookingMultipleDay => 'Multiple days'; @override - String get bookingMyBookings => 'Mes réservations'; + String get bookingMyBookings => 'My bookings'; @override - String get bookingNecessaryKey => 'Clé nécessaire'; + String get bookingNecessaryKey => 'Key needed'; @override - String get bookingNext => 'Suivant'; + String get bookingNext => 'Next'; @override - String get bookingNo => 'Non'; + String get bookingNo => 'No'; @override - String get bookingNoCurrentBooking => 'Pas de réservation en cours'; + String get bookingNoCurrentBooking => 'No current booking'; @override - String get bookingNoDateError => 'Veuillez choisir une date'; + String get bookingNoDateError => 'Please choose a date'; @override String get bookingNoAppointmentInReccurence => - 'Aucun créneau existe avec ces paramètres de récurrence'; + 'No slot exists with these recurrence settings'; @override - String get bookingNoDaySelected => 'Aucun jour sélectionné'; + String get bookingNoDaySelected => 'No day selected'; @override - String get bookingNoDescriptionError => 'Veuillez entrer une description'; + String get bookingNoDescriptionError => 'Please enter a description'; @override - String get bookingNoKeys => 'Aucune clé'; + String get bookingNoKeys => 'No keys'; @override - String get bookingNoNoteError => 'Veuillez entrer une note'; + String get bookingNoNoteError => 'Please enter a note'; @override - String get bookingNoPhoneRegistered => 'Numéro non renseigné'; + String get bookingNoPhoneRegistered => 'Number not provided'; @override - String get bookingNoReasonError => 'Veuillez entrer un motif'; + String get bookingNoReasonError => 'Please enter a reason'; @override - String get bookingNoRoomFoundError => 'Aucune salle enregistrée'; + String get bookingNoRoomFoundError => 'No room registered'; @override - String get bookingNoRoomFound => 'Aucune salle trouvée'; + String get bookingNoRoomFound => 'No room found'; @override String get bookingNote => 'Note'; @override - String get bookingOther => 'Autre'; + String get bookingOther => 'Other'; @override - String get bookingPending => 'En attente'; + String get bookingPending => 'Pending'; @override - String get bookingPrevious => 'Précédent'; + String get bookingPrevious => 'Previous'; @override - String get bookingReason => 'Motif'; + String get bookingReason => 'Reason'; @override - String get bookingRecurrence => 'Récurrence'; + String get bookingRecurrence => 'Recurrence'; @override - String get bookingRecurrenceDays => 'Jours de récurrence'; + String get bookingRecurrenceDays => 'Recurrence days'; @override - String get bookingRecurrenceEndDate => 'Date de fin de récurrence'; + String get bookingRecurrenceEndDate => 'Recurrence end date'; @override - String get bookingRecurrent => 'Récurrent'; + String get bookingRecurrent => 'Recurrent'; @override - String get bookingRegisteredRooms => 'Salles enregistrées'; + String get bookingRegisteredRooms => 'Registered rooms'; @override - String get bookingRoom => 'Salle'; + String get bookingRoom => 'Room'; @override - String get bookingRoomName => 'Nom de la salle'; + String get bookingRoomName => 'Room name'; @override - String get bookingStartDate => 'Date de début'; + String get bookingStartDate => 'Start date'; @override - String get bookingStartHour => 'Heure de début'; + String get bookingStartHour => 'Start hour'; @override - String get bookingWeeks => 'Semaines'; + String get bookingWeeks => 'Weeks'; @override - String get bookingYes => 'Oui'; + String get bookingYes => 'Yes'; @override - String get bookingWeekDayMon => 'Lundi'; + String get bookingWeekDayMon => 'Monday'; @override - String get bookingWeekDayTue => 'Mardi'; + String get bookingWeekDayTue => 'Tuesday'; @override - String get bookingWeekDayWed => 'Mercredi'; + String get bookingWeekDayWed => 'Wednesday'; @override - String get bookingWeekDayThu => 'Jeudi'; + String get bookingWeekDayThu => 'Thursday'; @override - String get bookingWeekDayFri => 'Vendredi'; + String get bookingWeekDayFri => 'Friday'; @override - String get bookingWeekDaySat => 'Samedi'; + String get bookingWeekDaySat => 'Saturday'; @override - String get bookingWeekDaySun => 'Dimanche'; + String get bookingWeekDaySun => 'Sunday'; @override - String get cinemaAdd => 'Ajouter'; + String get cinemaAdd => 'Add'; @override - String get cinemaAddedSession => 'Séance ajoutée'; + String get cinemaAddedSession => 'Session added'; @override - String get cinemaAddingError => 'Erreur lors de l\'ajout'; + String get cinemaAddingError => 'Error while adding'; @override - String get cinemaAddSession => 'Ajouter une séance'; + String get cinemaAddSession => 'Add a session'; @override - String get cinemaCinema => 'Cinéma'; + String get cinemaCinema => 'Cinema'; @override - String get cinemaDeleteSession => 'Supprimer la séance ?'; + String get cinemaDeleteSession => 'Delete the session?'; @override - String get cinemaDeleting => 'Suppression'; + String get cinemaDeleting => 'Deleting'; @override - String get cinemaDuration => 'Durée'; + String get cinemaDuration => 'Duration'; @override - String get cinemaEdit => 'Modifier'; + String get cinemaEdit => 'Edit'; @override - String get cinemaEditedSession => 'Séance modifiée'; + String get cinemaEditedSession => 'Session edited'; @override - String get cinemaEditingError => 'Erreur lors de la modification'; + String get cinemaEditingError => 'Error while editing'; @override - String get cinemaEditSession => 'Modifier la séance'; + String get cinemaEditSession => 'Edit the session'; @override - String get cinemaEmptyUrl => 'Veuillez entrer une URL'; + String get cinemaEmptyUrl => 'Please enter a URL'; @override - String get cinemaImportFromTMDB => 'Importer depuis TMDB'; + String get cinemaImportFromTMDB => 'Import from TMDB'; @override - String get cinemaIncomingSession => 'A l\'affiche'; + String get cinemaIncomingSession => 'Now showing'; @override - String get cinemaIncorrectOrMissingFields => 'Champs incorrects ou manquants'; + String get cinemaIncorrectOrMissingFields => 'Incorrect or missing fields'; @override - String get cinemaInvalidUrl => 'URL invalide'; + String get cinemaInvalidUrl => 'Invalid URL'; @override String get cinemaGenre => 'Genre'; @override - String get cinemaName => 'Nom'; + String get cinemaName => 'Name'; @override - String get cinemaNoDateError => 'Veuillez entrer une date'; + String get cinemaNoDateError => 'Please enter a date'; @override - String get cinemaNoDuration => 'Veuillez entrer une durée'; + String get cinemaNoDuration => 'Please enter a duration'; @override - String get cinemaNoOverview => 'Aucun synopsis'; + String get cinemaNoOverview => 'No synopsis'; @override - String get cinemaNoPoster => 'Aucune affiche'; + String get cinemaNoPoster => 'No poster'; @override - String get cinemaNoSession => 'Aucune séance'; + String get cinemaNoSession => 'No session'; @override String get cinemaOverview => 'Synopsis'; @override - String get cinemaPosterUrl => 'URL de l\'affiche'; + String get cinemaPosterUrl => 'Poster URL'; @override - String get cinemaSessionDate => 'Jour de la séance'; + String get cinemaSessionDate => 'Session day'; @override - String get cinemaStartHour => 'Heure de début'; + String get cinemaStartHour => 'Start hour'; @override - String get cinemaTagline => 'Slogan'; + String get cinemaTagline => 'Tagline'; @override - String get cinemaThe => 'Le'; + String get cinemaThe => 'The'; @override String get drawerAdmin => 'Administration'; @@ -1132,339 +1128,337 @@ class AppLocalizationsEn extends AppLocalizations { 'https://play.google.com/store/apps/details?id=fr.myecl.titan'; @override - String get drawerCopied => 'Copié !'; + String get drawerCopied => 'Copied!'; @override String get drawerDownloadAppOnMobileDevice => - 'Ce site est la version Web de l\'application MyECL. Nous vous invitons à télécharger l\'application. N\'utilisez ce site qu\'en cas de problème avec l\'application.\n'; + 'This site is the web version of the MyECL app. We invite you to download the app. Use this site only if you have problems with the app.\n'; @override String get drawerIosAppLink => 'https://apps.apple.com/fr/app/myecl/id6444443430'; @override - String get drawerLoginOut => 'Voulez-vous vous déconnecter ?'; + String get drawerLoginOut => 'Do you want to log out?'; @override - String get drawerLogOut => 'Déconnexion'; + String get drawerLogOut => 'Log out'; @override - String get drawerOr => ' ou '; + String get drawerOr => ' or '; @override - String get drawerSettings => 'Paramètres'; + String get drawerSettings => 'Settings'; @override - String get eventAdd => 'Ajouter'; + String get eventAdd => 'Add'; @override - String get eventAddEvent => 'Ajouter un événement'; + String get eventAddEvent => 'Add an event'; @override - String get eventAddedEvent => 'Événement ajouté'; + String get eventAddedEvent => 'Event added'; @override - String get eventAddingError => 'Erreur lors de l\'ajout'; + String get eventAddingError => 'Error while adding'; @override - String get eventAllDay => 'Toute la journée'; + String get eventAllDay => 'All day'; @override - String get eventConfirm => 'Confirmer'; + String get eventConfirm => 'Confirm'; @override - String get eventConfirmEvent => 'Confirmer l\'événement ?'; + String get eventConfirmEvent => 'Confirm the event?'; @override String get eventConfirmation => 'Confirmation'; @override - String get eventConfirmed => 'Confirmé'; + String get eventConfirmed => 'Confirmed'; @override String get eventDates => 'Dates'; @override - String get eventDecline => 'Refuser'; + String get eventDecline => 'Decline'; @override - String get eventDeclineEvent => 'Refuser l\'événement ?'; + String get eventDeclineEvent => 'Decline the event?'; @override - String get eventDeclined => 'Refusé'; + String get eventDeclined => 'Declined'; @override - String get eventDelete => 'Supprimer'; + String get eventDelete => 'Delete'; @override - String get eventDeletedEvent => 'Événement supprimé'; + String get eventDeletedEvent => 'Event deleted'; @override - String get eventDeleting => 'Suppression'; + String get eventDeleting => 'Deleting'; @override - String get eventDeletingError => 'Erreur lors de la suppression'; + String get eventDeletingError => 'Error while deleting'; @override - String get eventDeletingEvent => 'Supprimer l\'événement ?'; + String get eventDeletingEvent => 'Delete the event?'; @override String get eventDescription => 'Description'; @override - String get eventEdit => 'Modifier'; + String get eventEdit => 'Edit'; @override - String get eventEditEvent => 'Modifier un événement'; + String get eventEditEvent => 'Edit an event'; @override - String get eventEditedEvent => 'Événement modifié'; + String get eventEditedEvent => 'Event edited'; @override - String get eventEditingError => 'Erreur lors de la modification'; + String get eventEditingError => 'Error while editing'; @override - String get eventEndDate => 'Date de fin'; + String get eventEndDate => 'End date'; @override - String get eventEndHour => 'Heure de fin'; + String get eventEndHour => 'End hour'; @override - String get eventError => 'Erreur'; + String get eventError => 'Error'; @override - String get eventEventList => 'Liste des événements'; + String get eventEventList => 'Event list'; @override - String get eventEventType => 'Type d\'événement'; + String get eventEventType => 'Event type'; @override - String get eventEvery => 'Tous les'; + String get eventEvery => 'Every'; @override - String get eventHistory => 'Historique'; + String get eventHistory => 'History'; @override String get eventIncorrectOrMissingFields => - 'Certains champs sont incorrects ou manquants'; + 'Some fields are incorrect or missing'; @override - String get eventInterval => 'Intervalle'; + String get eventInterval => 'Interval'; @override - String get eventInvalidDates => - 'La date de fin doit être après la date de début'; + String get eventInvalidDates => 'End date must be after start date'; @override - String get eventInvalidIntervalError => - 'Veuillez entrer un intervalle valide'; + String get eventInvalidIntervalError => 'Please enter a valid interval'; @override - String get eventLocation => 'Lieu'; + String get eventLocation => 'Location'; @override - String get eventMyEvents => 'Mes événements'; + String get eventMyEvents => 'My events'; @override - String get eventName => 'Nom'; + String get eventName => 'Name'; @override - String get eventNext => 'Suivant'; + String get eventNext => 'Next'; @override - String get eventNo => 'Non'; + String get eventNo => 'No'; @override - String get eventNoCurrentEvent => 'Aucun événement en cours'; + String get eventNoCurrentEvent => 'No current event'; @override - String get eventNoDateError => 'Veuillez entrer une date'; + String get eventNoDateError => 'Please enter a date'; @override - String get eventNoDaySelected => 'Aucun jour sélectionné'; + String get eventNoDaySelected => 'No day selected'; @override - String get eventNoDescriptionError => 'Veuillez entrer une description'; + String get eventNoDescriptionError => 'Please enter a description'; @override - String get eventNoEvent => 'Aucun événement'; + String get eventNoEvent => 'No event'; @override - String get eventNoNameError => 'Veuillez entrer un nom'; + String get eventNoNameError => 'Please enter a name'; @override - String get eventNoOrganizerError => 'Veuillez entrer un organisateur'; + String get eventNoOrganizerError => 'Please enter an organizer'; @override - String get eventNoPlaceError => 'Veuillez entrer un lieu'; + String get eventNoPlaceError => 'Please enter a location'; @override - String get eventNoPhoneRegistered => 'Numéro non renseigné'; + String get eventNoPhoneRegistered => 'Number not provided'; @override - String get eventNoRuleError => 'Veuillez entrer une règle de récurrence'; + String get eventNoRuleError => 'Please enter a recurrence rule'; @override - String get eventOrganizer => 'Organisateur'; + String get eventOrganizer => 'Organizer'; @override - String get eventOther => 'Autre'; + String get eventOther => 'Other'; @override - String get eventPending => 'En attente'; + String get eventPending => 'Pending'; @override - String get eventPrevious => 'Précédent'; + String get eventPrevious => 'Previous'; @override - String get eventRecurrence => 'Récurrence'; + String get eventRecurrence => 'Recurrence'; @override - String get eventRecurrenceDays => 'Jours de récurrence'; + String get eventRecurrenceDays => 'Recurrence days'; @override - String get eventRecurrenceEndDate => 'Date de fin de la récurrence'; + String get eventRecurrenceEndDate => 'Recurrence end date'; @override - String get eventRecurrenceRule => 'Règle de récurrence'; + String get eventRecurrenceRule => 'Recurrence rule'; @override - String get eventRoom => 'Salle'; + String get eventRoom => 'Room'; @override - String get eventStartDate => 'Date de début'; + String get eventStartDate => 'Start date'; @override - String get eventStartHour => 'Heure de début'; + String get eventStartHour => 'Start hour'; @override - String get eventTitle => 'Événements'; + String get eventTitle => 'Events'; @override - String get eventYes => 'Oui'; + String get eventYes => 'Yes'; @override - String get eventEventEvery => 'Toutes les'; + String get eventEventEvery => 'Every'; @override - String get eventWeeks => 'semaines'; + String get eventWeeks => 'weeks'; @override - String get eventDayMon => 'Lundi'; + String get eventDayMon => 'Monday'; @override - String get eventDayTue => 'Mardi'; + String get eventDayTue => 'Tuesday'; @override - String get eventDayWed => 'Mercredi'; + String get eventDayWed => 'Wednesday'; @override - String get eventDayThu => 'Jeudi'; + String get eventDayThu => 'Thursday'; @override - String get eventDayFri => 'Vendredi'; + String get eventDayFri => 'Friday'; @override - String get eventDaySat => 'Samedi'; + String get eventDaySat => 'Saturday'; @override - String get eventDaySun => 'Dimanche'; + String get eventDaySun => 'Sunday'; @override - String get homeCalendar => 'Calendrier'; + String get homeCalendar => 'Calendar'; @override - String get homeEventOf => 'Évènements du'; + String get homeEventOf => 'Events of'; @override - String get homeIncomingEvents => 'Évènements à venir'; + String get homeIncomingEvents => 'Upcoming events'; @override - String get homeLastInfos => 'Dernières annonces'; + String get homeLastInfos => 'Latest announcements'; @override - String get homeNoEvents => 'Aucun évènement'; + String get homeNoEvents => 'No events'; @override - String get homeTranslateDayShortMon => 'Lun'; + String get homeTranslateDayShortMon => 'Mon'; @override - String get homeTranslateDayShortTue => 'Mar'; + String get homeTranslateDayShortTue => 'Tue'; @override - String get homeTranslateDayShortWed => 'Mer'; + String get homeTranslateDayShortWed => 'Wed'; @override - String get homeTranslateDayShortThu => 'Jeu'; + String get homeTranslateDayShortThu => 'Thu'; @override - String get homeTranslateDayShortFri => 'Ven'; + String get homeTranslateDayShortFri => 'Fri'; @override - String get homeTranslateDayShortSat => 'Sam'; + String get homeTranslateDayShortSat => 'Sat'; @override - String get homeTranslateDayShortSun => 'Dim'; + String get homeTranslateDayShortSun => 'Sun'; @override - String get loanAdd => 'Ajouter'; + String get loanAdd => 'Add'; @override - String get loanAddLoan => 'Ajouter un prêt'; + String get loanAddLoan => 'Add a loan'; @override - String get loanAddObject => 'Ajouter un objet'; + String get loanAddObject => 'Add an object'; @override - String get loanAddedLoan => 'Prêt ajouté'; + String get loanAddedLoan => 'Loan added'; @override - String get loanAddedObject => 'Objet ajouté'; + String get loanAddedObject => 'Object added'; @override - String get loanAddedRoom => 'Salle ajoutée'; + String get loanAddedRoom => 'Room added'; @override - String get loanAddingError => 'Erreur lors de l\'ajout'; + String get loanAddingError => 'Error while adding'; @override - String get loanAdmin => 'Administrateur'; + String get loanAdmin => 'Administrator'; @override - String get loanAvailable => 'Disponible'; + String get loanAvailable => 'Available'; @override - String get loanAvailableMultiple => 'Disponibles'; + String get loanAvailableMultiple => 'Available'; @override - String get loanBorrowed => 'Emprunté'; + String get loanBorrowed => 'Borrowed'; @override - String get loanBorrowedMultiple => 'Empruntés'; + String get loanBorrowedMultiple => 'Borrowed'; @override - String get loanAnd => 'et'; + String get loanAnd => 'and'; @override String get loanAssociation => 'Association'; @override - String get loanAvailableItems => 'Objets disponibles'; + String get loanAvailableItems => 'Available items'; @override - String get loanBeginDate => 'Date du début du prêt'; + String get loanBeginDate => 'Loan start date'; @override - String get loanBorrower => 'Emprunteur'; + String get loanBorrower => 'Borrower'; @override - String get loanCaution => 'Caution'; + String get loanCaution => 'Deposit'; @override - String get loanCancel => 'Annuler'; + String get loanCancel => 'Cancel'; @override - String get loanConfirm => 'Confirmer'; + String get loanConfirm => 'Confirm'; @override String get loanConfirmation => 'Confirmation'; @@ -1473,512 +1467,509 @@ class AppLocalizationsEn extends AppLocalizations { String get loanDates => 'Dates'; @override - String get loanDays => 'Jours'; + String get loanDays => 'Days'; @override - String get loanDelay => 'Délai de la prolongation'; + String get loanDelay => 'Extension delay'; @override - String get loanDelete => 'Supprimer'; + String get loanDelete => 'Delete'; @override - String get loanDeletingLoan => 'Supprimer le prêt ?'; + String get loanDeletingLoan => 'Delete the loan?'; @override - String get loanDeletedItem => 'Objet supprimé'; + String get loanDeletedItem => 'Object deleted'; @override - String get loanDeletedLoan => 'Prêt supprimé'; + String get loanDeletedLoan => 'Loan deleted'; @override - String get loanDeleting => 'Suppression'; + String get loanDeleting => 'Deleting'; @override - String get loanDeletingError => 'Erreur lors de la suppression'; + String get loanDeletingError => 'Error while deleting'; @override - String get loanDeletingItem => 'Supprimer l\'objet ?'; + String get loanDeletingItem => 'Delete the object?'; @override - String get loanDuration => 'Durée'; + String get loanDuration => 'Duration'; @override - String get loanEdit => 'Modifier'; + String get loanEdit => 'Edit'; @override - String get loanEditItem => 'Modifier l\'objet'; + String get loanEditItem => 'Edit the object'; @override - String get loanEditLoan => 'Modifier le prêt'; + String get loanEditLoan => 'Edit the loan'; @override - String get loanEditedRoom => 'Salle modifiée'; + String get loanEditedRoom => 'Room edited'; @override - String get loanEndDate => 'Date de fin du prêt'; + String get loanEndDate => 'Loan end date'; @override - String get loanEnded => 'Terminé'; + String get loanEnded => 'Ended'; @override - String get loanEnterDate => 'Veuillez entrer une date'; + String get loanEnterDate => 'Please enter a date'; @override - String get loanExtendedLoan => 'Prêt prolongé'; + String get loanExtendedLoan => 'Extended loan'; @override - String get loanExtendingError => 'Erreur lors de la prolongation'; + String get loanExtendingError => 'Error while extending'; @override - String get loanHistory => 'Historique'; + String get loanHistory => 'History'; @override String get loanIncorrectOrMissingFields => - 'Des champs sont manquants ou incorrects'; + 'Some fields are missing or incorrect'; @override - String get loanInvalidNumber => 'Veuillez entrer un nombre'; + String get loanInvalidNumber => 'Please enter a number'; @override - String get loanInvalidDates => 'Les dates ne sont pas valides'; + String get loanInvalidDates => 'Dates are not valid'; @override - String get loanItem => 'Objet'; + String get loanItem => 'Item'; @override - String get loanItems => 'Objets'; + String get loanItems => 'Items'; @override - String get loanItemHandling => 'Gestion des objets'; + String get loanItemHandling => 'Item management'; @override - String get loanItemSelected => 'objet sélectionné'; + String get loanItemSelected => 'selected item'; @override - String get loanItemsSelected => 'objets sélectionnés'; + String get loanItemsSelected => 'selected items'; @override - String get loanLendingDuration => 'Durée possible du prêt'; + String get loanLendingDuration => 'Possible loan duration'; @override - String get loanLoan => 'Prêt'; + String get loanLoan => 'Loan'; @override - String get loanLoanHandling => 'Gestion des prêts'; + String get loanLoanHandling => 'Loan management'; @override - String get loanLooking => 'Rechercher'; + String get loanLooking => 'Searching'; @override - String get loanName => 'Nom'; + String get loanName => 'Name'; @override - String get loanNext => 'Suivant'; + String get loanNext => 'Next'; @override - String get loanNo => 'Non'; + String get loanNo => 'No'; @override - String get loanNoAssociationsFounded => 'Aucune association trouvée'; + String get loanNoAssociationsFounded => 'No associations found'; @override - String get loanNoAvailableItems => 'Aucun objet disponible'; + String get loanNoAvailableItems => 'No available items'; @override - String get loanNoBorrower => 'Aucun emprunteur'; + String get loanNoBorrower => 'No borrower'; @override - String get loanNoItems => 'Aucun objet'; + String get loanNoItems => 'No items'; @override - String get loanNoItemSelected => 'Aucun objet sélectionné'; + String get loanNoItemSelected => 'No item selected'; @override - String get loanNoLoan => 'Aucun prêt'; + String get loanNoLoan => 'No loan'; @override - String get loanNoReturnedDate => 'Pas de date de retour'; + String get loanNoReturnedDate => 'No return date'; @override - String get loanQuantity => 'Quantité'; + String get loanQuantity => 'Quantity'; @override - String get loanNone => 'Aucun'; + String get loanNone => 'None'; @override String get loanNote => 'Note'; @override - String get loanNoValue => 'Veuillez entrer une valeur'; + String get loanNoValue => 'Please enter a value'; @override - String get loanOnGoing => 'En cours'; + String get loanOnGoing => 'Ongoing'; @override - String get loanOnGoingLoan => 'Prêt en cours'; + String get loanOnGoingLoan => 'Ongoing loan'; @override - String get loanOthers => 'autres'; + String get loanOthers => 'others'; @override - String get loanPaidCaution => 'Caution payée'; + String get loanPaidCaution => 'Deposit paid'; @override - String get loanPositiveNumber => 'Veuillez entrer un nombre positif'; + String get loanPositiveNumber => 'Please enter a positive number'; @override - String get loanPrevious => 'Précédent'; + String get loanPrevious => 'Previous'; @override - String get loanReturned => 'Rendu'; + String get loanReturned => 'Returned'; @override - String get loanReturnedLoan => 'Prêt rendu'; + String get loanReturnedLoan => 'Returned loan'; @override - String get loanReturningError => 'Erreur lors du retour'; + String get loanReturningError => 'Error while returning'; @override - String get loanReturningLoan => 'Retour'; + String get loanReturningLoan => 'Return'; @override - String get loanReturnLoan => 'Rendre le prêt ?'; + String get loanReturnLoan => 'Return the loan?'; @override - String get loanReturnLoanDescription => 'Voulez-vous rendre ce prêt ?'; + String get loanReturnLoanDescription => 'Do you want to return this loan?'; @override - String get loanToReturn => 'A rendre'; + String get loanToReturn => 'To return'; @override - String get loanUnavailable => 'Indisponible'; + String get loanUnavailable => 'Unavailable'; @override - String get loanUpdate => 'Modifier'; + String get loanUpdate => 'Edit'; @override - String get loanUpdatedItem => 'Objet modifié'; + String get loanUpdatedItem => 'Item updated'; @override - String get loanUpdatedLoan => 'Prêt modifié'; + String get loanUpdatedLoan => 'Loan updated'; @override - String get loanUpdatingError => 'Erreur lors de la modification'; + String get loanUpdatingError => 'Error while updating'; @override - String get loanYes => 'Oui'; + String get loanYes => 'Yes'; @override - String get loginAccountActivated => 'Compte activé'; + String get loginAccountActivated => 'Account activated'; @override - String get loginAccountNotActivated => 'Compte non activé'; + String get loginAccountNotActivated => 'Account not activated'; @override - String get loginActivationCode => 'Code d\'activation'; + String get loginActivationCode => 'Activation code'; @override - String get loginBirthday => 'Date de naissance'; + String get loginBirthday => 'Date of birth'; @override - String get loginCanBeEmpty => 'Ce champ peut être vide'; + String get loginCanBeEmpty => 'This field can be empty'; @override - String get loginConfirmPassword => 'Confirmer le mot de passe'; + String get loginConfirmPassword => 'Confirm password'; @override - String get loginCreate => 'Créer'; + String get loginCreate => 'Create'; @override - String get loginCreateAccount => 'Créer un compte'; + String get loginCreateAccount => 'Create an account'; @override - String get loginCreateAccountTitle => 'Créer un\ncompte'; + String get loginCreateAccountTitle => 'Create an\naccount'; @override String get loginEmail => 'Email'; @override - String get loginEmailEmpty => 'Veuillez entrer une adresse mail'; + String get loginEmailEmpty => 'Please enter an email address'; @override String get loginEmailInvalid => - 'Veuillez entrer une adresse mail de centrale.\nSi vous n\'en possédez pas, veuillez contacter Éclair'; + 'Please enter a Centrale email address.\nIf you don\'t have one, please contact Éclair'; @override - String get loginEmptyFieldError => 'Ce champ ne peut pas être vide'; + String get loginEmptyFieldError => 'This field cannot be empty'; @override - String get loginEndActivation => 'Finaliser l\'activation'; + String get loginEndActivation => 'Complete activation'; @override - String get loginEndResetPassword => 'Finaliser la \nréinitialisation'; + String get loginEndResetPassword => 'Complete\npassword reset'; @override - String get loginErrorResetPassword => 'Erreur lors de la réinitialisation'; + String get loginErrorResetPassword => 'Error during reset'; @override - String get loginExpectingDate => 'Une date est attendue'; + String get loginExpectingDate => 'A date is expected'; @override - String get loginFillAllFields => 'Veuillez remplir tous les champs'; + String get loginFillAllFields => 'Please fill all fields'; @override - String get loginFirstname => 'Prénom'; + String get loginFirstname => 'First name'; @override - String get loginFloor => 'Étage'; + String get loginFloor => 'Floor'; @override - String get loginForgetPassword => 'Mot de passe\noublié'; + String get loginForgetPassword => 'Forgot\npassword'; @override - String get loginForgotPassword => 'Mot de passe oublié ?'; + String get loginForgotPassword => 'Forgot password?'; @override - String get loginInvalidToken => 'Code d\'activation invalide'; + String get loginInvalidToken => 'Invalid activation code'; @override - String get loginLoginFailed => 'Échec de la connexion'; + String get loginLoginFailed => 'Login failed'; @override - String get loginMailSendingError => 'Erreur lors de la création du compte'; + String get loginMailSendingError => 'Error during account creation'; @override - String get loginMustBeIntError => 'Ce champ doit être un entier'; + String get loginMustBeIntError => 'This field must be an integer'; @override - String get loginName => 'Nom'; + String get loginName => 'Last name'; @override - String get loginNewPassword => 'Nouveau mot de passe'; + String get loginNewPassword => 'New password'; @override - String get loginPassword => 'Mot de passe'; + String get loginPassword => 'Password'; @override String get loginPasswordLengthError => - 'Le mot de passe doit faire au moins 6 caractères'; + 'Password must be at least 6 characters'; @override String get loginPasswordUppercaseError => - 'Le mot de passe doit contenir au moins une majuscule'; + 'Password must contain at least one uppercase letter'; @override String get loginPasswordLowercaseError => - 'Le mot de passe doit contenir au moins une minucule'; + 'Password must contain at least one lowercase letter'; @override String get loginPasswordNumberError => - 'Le mot de passe doit contenir au moins un chiffre'; + 'Password must contain at least one number'; @override String get loginPasswordSpecialCaracterError => - 'Le mot de passe doit contenir au moins un caractère spécial'; + 'Password must contain at least one special character'; @override - String get loginPasswordMustMatch => 'Les mots de passe doivent correspondre'; + String get loginPasswordMustMatch => 'Passwords must match'; @override - String get loginPasswordStrengthVeryWeak => 'Très faible'; + String get loginPasswordStrengthVeryWeak => 'Very weak'; @override - String get loginPasswordStrengthWeak => 'Faible'; + String get loginPasswordStrengthWeak => 'Weak'; @override - String get loginPasswordStrengthMedium => 'Moyen'; + String get loginPasswordStrengthMedium => 'Medium'; @override - String get loginPasswordStrengthStrong => 'Fort'; + String get loginPasswordStrengthStrong => 'Strong'; @override - String get loginPasswordStrengthVeryStrong => 'Très fort'; + String get loginPasswordStrengthVeryStrong => 'Very strong'; @override - String get loginPhone => 'Téléphone'; + String get loginPhone => 'Phone'; @override - String get loginPromo => 'Promo entrante (ex : 2023)'; + String get loginPromo => 'Incoming class (e.g., 2023)'; @override - String get loginSendedMail => 'Mail de confirmation envoyé'; + String get loginSendedMail => 'Confirmation email sent'; @override - String get loginSendedResetMail => 'Mail de réinitialisation envoyé'; + String get loginSendedResetMail => 'Reset email sent'; @override - String get loginSignIn => 'Se connecter'; + String get loginSignIn => 'Sign in'; @override - String get loginRegister => 'S\'inscrire'; + String get loginRegister => 'Register'; @override - String get loginRecievedMail => 'J\'ai reçu le mail'; + String get loginRecievedMail => 'I received the email'; @override - String get loginRecover => 'Réinitialiser'; + String get loginRecover => 'Reset'; @override - String get loginResetedPassword => 'Mot de passe réinitialisé'; + String get loginResetedPassword => 'Password reset'; @override - String get loginResetPasswordTitle => 'Réinitialiser\nle mot de \npasse'; + String get loginResetPasswordTitle => 'Reset\npassword'; @override - String get loginNickname => 'Surnom'; + String get loginNickname => 'Nickname'; @override - String get loginWelcomeBack => 'Bienvenue'; + String get loginWelcomeBack => 'Welcome back'; @override String get loginAppName => 'MyECL'; @override String get othersCheckInternetConnection => - 'Veuillez vérifier votre connexion internet'; + 'Please check your internet connection'; @override - String get othersRetry => 'Réessayer'; + String get othersRetry => 'Retry'; @override String get othersTooOldVersion => - 'Votre version de l\'application est trop ancienne.\n\nVeuillez mettre à jour l\'application.'; + 'Your app version is too old.\n\nPlease update the app.'; @override - String get othersUnableToConnectToServer => - 'Impossible de se connecter au serveur'; + String get othersUnableToConnectToServer => 'Unable to connect to the server'; @override String get othersVersion => 'Version'; @override String get othersNoModule => - 'Aucun module disponible, veuillez réessayer ultérieurement 😢😢'; + 'No modules available, please try again later 😢😢'; @override String get othersAdmin => 'Admin'; @override - String get othersError => 'Une erreur est survenue'; + String get othersError => 'An error occurred'; @override - String get othersNoValue => 'Veuillez entrer une valeur'; + String get othersNoValue => 'Please enter a value'; @override - String get othersInvalidNumber => 'Veuillez entrer un nombre'; + String get othersInvalidNumber => 'Please enter a number'; @override - String get othersNoDateError => 'Veuillez entrer une date'; + String get othersNoDateError => 'Please enter a date'; @override - String get othersImageSizeTooBig => - 'La taille de l\'image ne doit pas dépasser 4 Mio'; + String get othersImageSizeTooBig => 'Image size must not exceed 4 MB'; @override - String get othersImageError => 'Erreur lors de l\'ajout de l\'image'; + String get othersImageError => 'Error adding the image'; @override - String get phAddNewJournal => 'Ajouter un nouveau journal'; + String get phAddNewJournal => 'Add a new journal'; @override - String get phNameField => 'Nom : '; + String get phNameField => 'Name: '; @override - String get phDateField => 'Date : '; + String get phDateField => 'Date: '; @override - String get phDelete => 'Voulez-vous vraiment supprimer ce journal ?'; + String get phDelete => 'Are you sure you want to delete this journal?'; @override - String get phIrreversibleAction => 'Cette action est irréversible'; + String get phIrreversibleAction => 'This action is irreversible'; @override - String get phToHeavyFile => 'Fichier trop volumineux'; + String get phToHeavyFile => 'File too large'; @override - String get phAddPdfFile => 'Ajouter un fichier PDF'; + String get phAddPdfFile => 'Add a PDF file'; @override - String get phEditPdfFile => 'Modifier le fichier PDF'; + String get phEditPdfFile => 'Edit PDF file'; @override - String get phPhName => 'Nom du PH'; + String get phPhName => 'PH name'; @override String get phDate => 'Date'; @override - String get phAdded => 'Ajouté'; + String get phAdded => 'Added'; @override - String get phEdited => 'Modifié'; + String get phEdited => 'Edited'; @override - String get phAddingFileError => 'Erreur d\'ajout'; + String get phAddingFileError => 'Add error'; @override - String get phMissingInformatonsOrPdf => - 'Informations manquantes ou fichier PDF manquant'; + String get phMissingInformatonsOrPdf => 'Missing information or PDF file'; @override - String get phAdd => 'Ajouter'; + String get phAdd => 'Add'; @override - String get phEdit => 'Modifier'; + String get phEdit => 'Edit'; @override - String get phSeePreviousJournal => 'Voir les anciens journaux'; + String get phSeePreviousJournal => 'See previous journals'; @override - String get phNoJournalInDatabase => 'Pas encore de PH dans la base de donnée'; + String get phNoJournalInDatabase => 'No PH yet in database'; @override - String get phSuccesDowloading => 'Téléchargé avec succès'; + String get phSuccesDowloading => 'Successfully downloaded'; @override - String get phonebookActiveMandate => 'Mandat actif :'; + String get phonebookActiveMandate => 'Active mandate:'; @override - String get phonebookAdd => 'Ajouter'; + String get phonebookAdd => 'Add'; @override - String get phonebookAddAssociation => 'Ajouter une association'; + String get phonebookAddAssociation => 'Add an association'; @override - String get phonebookAddedAssociation => 'Association ajoutée'; + String get phonebookAddedAssociation => 'Association added'; @override - String get phonebookAddedMember => 'Membre ajouté'; + String get phonebookAddedMember => 'Member added'; @override - String get phonebookAddingError => 'Erreur lors de l\'ajout'; + String get phonebookAddingError => 'Error adding'; @override - String get phonebookAddMember => 'Ajouter un membre'; + String get phonebookAddMember => 'Add a member'; @override - String get phonebookAddRole => 'Ajouter un rôle'; + String get phonebookAddRole => 'Add a role'; @override String get phonebookAdmin => 'Admin'; @override - String get phonebookAdminPage => 'Page Administrateur'; + String get phonebookAdminPage => 'Admin page'; @override - String get phonebookAll => 'Toutes'; + String get phonebookAll => 'All'; @override - String get phonebookApparentName => 'Nom public du rôle :'; + String get phonebookApparentName => 'Public role name:'; @override - String get phonebookAssociation => 'Association :'; + String get phonebookAssociation => 'Association:'; @override - String get phonebookAssociationDetail => 'Détail de l\'association :'; + String get phonebookAssociationDetail => 'Association details:'; @override - String get phonebookAssociationKind => 'Type d\'association :'; + String get phonebookAssociationKind => 'Type of association:'; @override String get phonebookAssociationPure => 'Association'; @@ -1987,482 +1978,476 @@ class AppLocalizationsEn extends AppLocalizations { String get phonebookAssociationPureSearch => ' Association'; @override - String get phonebookAssociations => 'Associations :'; + String get phonebookAssociations => 'Associations:'; @override - String get phonebookCancel => 'Annuler'; + String get phonebookCancel => 'Cancel'; @override - String get phonebookChangeMandate => 'Passer au mandat '; + String get phonebookChangeMandate => 'Switch to mandate '; @override String get phonebookChangeMandateConfirm => - 'Êtes-vous sûr de vouloir changer tout le mandat ?\nCette action est irréversible !'; + 'Are you sure you want to change the entire mandate?\nThis action is irreversible!'; @override - String get phonebookCopied => 'Copié dans le presse-papier'; + String get phonebookCopied => 'Copied to clipboard'; @override String get phonebookDeactivateAssociation => - 'Êtes-vous sûr de vouloir désactiver cette association ?\nCette action est irréversible !'; + 'Are you sure you want to deactivate this association?\nThis action is irreversible!'; @override - String get phonebookDeactivatedAssociation => 'Association désactivée'; + String get phonebookDeactivatedAssociation => 'Association deactivated'; @override String get phonebookDeactivatedAssociationWarning => - 'Attention, cette association est désactivée, vous ne pouvez pas la modifier'; + 'Warning, this association is deactivated, you cannot modify it'; @override - String get phonebookDeactivating => 'Désactiver l\'association ?'; + String get phonebookDeactivating => 'Deactivate the association?'; @override - String get phonebookDeactivatingError => 'Erreur lors de la désactivation'; + String get phonebookDeactivatingError => 'Error during deactivation'; @override - String get phonebookDetail => 'Détail :'; + String get phonebookDetail => 'Details:'; @override String get phonebookDeleteAssociation => - 'Supprimer l\'association ?\nCela va effacer tout l\'historique de l\'association'; + 'Delete the association?\nThis will erase all association history'; @override - String get phonebookDeletedAssociation => 'Association supprimée'; + String get phonebookDeletedAssociation => 'Association deleted'; @override - String get phonebookDeletedMember => 'Membre supprimé'; + String get phonebookDeletedMember => 'Member deleted'; @override - String get phonebookDeleting => 'Suppression'; + String get phonebookDeleting => 'Deleting'; @override - String get phonebookDeletingError => 'Erreur lors de la suppression'; + String get phonebookDeletingError => 'Error deleting'; @override String get phonebookDescription => 'Description'; @override - String get phonebookEdit => 'Modifier'; + String get phonebookEdit => 'Edit'; @override - String get phonebookEditMembership => 'Modifier le rôle'; + String get phonebookEditMembership => 'Edit role'; @override - String get phonebookEmail => 'Email :'; + String get phonebookEmail => 'Email:'; @override - String get phonebookEmailCopied => 'Email copié dans le presse-papier'; + String get phonebookEmailCopied => 'Email copied to clipboard'; @override - String get phonebookEmptyApparentName => 'Veuillez entrer un nom de role'; + String get phonebookEmptyApparentName => 'Please enter a role name'; @override - String get phonebookEmptyFieldError => 'Un champ n\'est pas rempli'; + String get phonebookEmptyFieldError => 'A field is not filled'; @override - String get phonebookEmptyKindError => - 'Veuillez choisir un type d\'association'; + String get phonebookEmptyKindError => 'Please choose an association type'; @override - String get phonebookEmptyMember => 'Aucun membre sélectionné'; + String get phonebookEmptyMember => 'No member selected'; @override - String get phonebookErrorAssociationLoading => - 'Erreur lors du chargement de l\'association'; + String get phonebookErrorAssociationLoading => 'Error loading association'; @override String get phonebookErrorAssociationNameEmpty => - 'Veuillez entrer un nom d\'association'; + 'Please enter an association name'; @override String get phonebookErrorAssociationPicture => - 'Erreur lors de la modification de la photo d\'association'; + 'Error editing association picture'; @override - String get phonebookErrorKindsLoading => - 'Erreur lors du chargement des types d\'association'; + String get phonebookErrorKindsLoading => 'Error loading association types'; @override String get phonebookErrorLoadAssociationList => - 'Erreur lors du chargement de la liste des associations'; + 'Error loading association list'; @override String get phonebookErrorLoadAssociationMember => - 'Erreur lors du chargement des membres de l\'association'; + 'Error loading association members'; @override String get phonebookErrorLoadAssociationPicture => - 'Erreur lors du chargement de la photo d\'association'; + 'Error loading association picture'; @override - String get phonebookErrorLoadProfilePicture => 'Erreur'; + String get phonebookErrorLoadProfilePicture => 'Error'; @override - String get phonebookErrorRoleTagsLoading => - 'Erreur lors du chargement des tags de rôle'; + String get phonebookErrorRoleTagsLoading => 'Error loading role tags'; @override String get phonebookExistingMembership => - 'Ce membre est déjà dans le mandat actuel'; + 'This member is already in the current mandate'; @override - String get phonebookFirstname => 'Prénom :'; + String get phonebookFirstname => 'First name:'; @override - String get phonebookGroups => 'Groupes associés :'; + String get phonebookGroups => 'Associated groups:'; @override - String get phonebookMandateChangingError => - 'Erreur lors du changement de mandat'; + String get phonebookMandateChangingError => 'Error changing mandate'; @override - String get phonebookMember => 'Membre'; + String get phonebookMember => 'Member'; @override - String get phonebookMemberReordered => 'Membre réordonné'; + String get phonebookMemberReordered => 'Member reordered'; @override - String get phonebookMembers => 'Membres'; + String get phonebookMembers => 'Members'; @override String get phonebookMembershipAssociationError => - 'Veuillez choisir une association'; + 'Please choose an association'; @override - String get phonebookMembershipRole => 'Rôle :'; + String get phonebookMembershipRole => 'Role:'; @override - String get phonebookMembershipRoleError => 'Veuillez choisir un rôle'; + String get phonebookMembershipRoleError => 'Please choose a role'; @override - String get phonebookName => 'Nom :'; + String get phonebookName => 'Last name:'; @override - String get phonebookNameCopied => 'Nom et prénom copié dans le presse-papier'; + String get phonebookNameCopied => 'Name and first name copied to clipboard'; @override - String get phonebookNamePure => 'Nom'; + String get phonebookNamePure => 'Last name'; @override - String get phonebookNewMandate => 'Nouveau mandat'; + String get phonebookNewMandate => 'New mandate'; @override - String get phonebookNewMandateConfirmed => 'Mandat changé'; + String get phonebookNewMandateConfirmed => 'Mandate changed'; @override - String get phonebookNickname => 'Surnom :'; + String get phonebookNickname => 'Nickname:'; @override - String get phonebookNicknameCopied => 'Surnom copié dans le presse-papier'; + String get phonebookNicknameCopied => 'Nickname copied to clipboard'; @override - String get phonebookNoAssociationFound => 'Aucune association trouvée'; + String get phonebookNoAssociationFound => 'No association found'; @override - String get phonebookNoMember => 'Aucun membre'; + String get phonebookNoMember => 'No member'; @override - String get phonebookNoMemberRole => 'Aucun role trouvé'; + String get phonebookNoMemberRole => 'No role found'; @override - String get phonebookPhone => 'Téléphone :'; + String get phonebookPhone => 'Phone:'; @override - String get phonebookPhonebook => 'Annuaire'; + String get phonebookPhonebook => 'Directory'; @override - String get phonebookPhonebookSearch => 'Rechercher'; + String get phonebookPhonebookSearch => 'Search'; @override String get phonebookPhonebookSearchAssociation => 'Association'; @override - String get phonebookPhonebookSearchField => 'Rechercher :'; + String get phonebookPhonebookSearchField => 'Search:'; @override - String get phonebookPhonebookSearchName => 'Nom/Prénom/Surnom'; + String get phonebookPhonebookSearchName => 'Last name/First name/Nickname'; @override - String get phonebookPhonebookSearchRole => 'Poste'; + String get phonebookPhonebookSearchRole => 'Position'; @override String get phonebookPresidentRoleTag => 'Prez\''; @override - String get phonebookPromoNotGiven => 'Promo non renseignée'; + String get phonebookPromoNotGiven => 'Promotion not provided'; @override - String get phonebookPromotion => 'Promotion :'; + String get phonebookPromotion => 'Promotion:'; @override - String get phonebookReorderingError => 'Erreur lors du réordonnement'; + String get phonebookReorderingError => 'Error during reordering'; @override - String get phonebookResearch => 'Rechercher'; + String get phonebookResearch => 'Search'; @override - String get phonebookRolePure => 'Rôle'; + String get phonebookRolePure => 'Role'; @override String get phonebookTooHeavyAssociationPicture => - 'L\'image est trop lourde (max 4Mo)'; + 'Image is too large (max 4MB)'; @override - String get phonebookUpdateGroups => 'Mettre à jour les groupes'; + String get phonebookUpdateGroups => 'Update groups'; @override - String get phonebookUpdatedAssociation => 'Association modifiée'; + String get phonebookUpdatedAssociation => 'Association updated'; @override String get phonebookUpdatedAssociationPicture => - 'La photo d\'association a été changée'; + 'Association picture has been changed'; @override - String get phonebookUpdatedGroups => 'Groupes mis à jour'; + String get phonebookUpdatedGroups => 'Groups updated'; @override - String get phonebookUpdatedMember => 'Membre modifié'; + String get phonebookUpdatedMember => 'Member updated'; @override - String get phonebookUpdatingError => 'Erreur lors de la modification'; + String get phonebookUpdatingError => 'Error during update'; @override - String get phonebookValidation => 'Valider'; + String get phonebookValidation => 'Validate'; @override - String get purchasesPurchases => 'Achats'; + String get purchasesPurchases => 'Purchases'; @override - String get purchasesResearch => 'Rechercher'; + String get purchasesResearch => 'Search'; @override - String get purchasesNoPurchasesFound => 'Aucun achat trouvé'; + String get purchasesNoPurchasesFound => 'No purchases found'; @override - String get purchasesNoTickets => 'Aucun ticket'; + String get purchasesNoTickets => 'No tickets'; @override - String get purchasesTicketsError => 'Erreur lors du chargement des tickets'; + String get purchasesTicketsError => 'Error loading tickets'; @override - String get purchasesPurchasesError => 'Erreur lors du chargement des achats'; + String get purchasesPurchasesError => 'Error loading purchases'; @override - String get purchasesNoPurchases => 'Aucun achat'; + String get purchasesNoPurchases => 'No purchase'; @override - String get purchasesTimes => 'fois'; + String get purchasesTimes => 'times'; @override - String get purchasesAlreadyUsed => 'Déjà utilisé'; + String get purchasesAlreadyUsed => 'Already used'; @override - String get purchasesNotPaid => 'Non validé'; + String get purchasesNotPaid => 'Not validated'; @override - String get purchasesPleaseSelectProduct => 'Veuillez sélectionner un produit'; + String get purchasesPleaseSelectProduct => 'Please select a product'; @override - String get purchasesProducts => 'Produits'; + String get purchasesProducts => 'Products'; @override - String get purchasesCancel => 'Annuler'; + String get purchasesCancel => 'Cancel'; @override - String get purchasesValidate => 'Valider'; + String get purchasesValidate => 'Validate'; @override - String get purchasesLeftScan => 'Scans restants'; + String get purchasesLeftScan => 'Scans remaining'; @override String get purchasesTag => 'Tag'; @override - String get purchasesHistory => 'Historique'; + String get purchasesHistory => 'History'; @override - String get purchasesPleaseSelectSeller => 'Veuillez sélectionner un vendeur'; + String get purchasesPleaseSelectSeller => 'Please select a seller'; @override - String get purchasesNoTagGiven => 'Attention, aucun tag n\'a été entré'; + String get purchasesNoTagGiven => 'Warning, no tag entered'; @override String get purchasesTickets => 'Tickets'; @override - String get purchasesNoScannableProducts => 'Aucun produit scannable'; + String get purchasesNoScannableProducts => 'No scannable products'; @override - String get purchasesLoading => 'En attente de scan'; + String get purchasesLoading => 'Waiting for scan'; @override - String get purchasesScan => 'Scanner'; + String get purchasesScan => 'Scan'; @override - String get raffleRaffle => 'Tombola'; + String get raffleRaffle => 'Raffle'; @override - String get rafflePrize => 'Lot'; + String get rafflePrize => 'Prize'; @override - String get rafflePrizes => 'Lots'; + String get rafflePrizes => 'Prizes'; @override - String get raffleActualRaffles => 'Tombola en cours'; + String get raffleActualRaffles => 'Current raffles'; @override - String get rafflePastRaffles => 'Tombola passés'; + String get rafflePastRaffles => 'Past raffles'; @override - String get raffleYourTickets => 'Tous vos tickets'; + String get raffleYourTickets => 'All your tickets'; @override - String get raffleCreateMenu => 'Menu de Création'; + String get raffleCreateMenu => 'Creation menu'; @override - String get raffleNextRaffles => 'Prochaines tombolas'; + String get raffleNextRaffles => 'Upcoming raffles'; @override - String get raffleNoTicket => 'Vous n\'avez pas de ticket'; + String get raffleNoTicket => 'You have no ticket'; @override - String get raffleSeeRaffleDetail => 'Voir lots/tickets'; + String get raffleSeeRaffleDetail => 'View prizes/tickets'; @override - String get raffleActualPrize => 'Lots actuels'; + String get raffleActualPrize => 'Current prizes'; @override - String get raffleMajorPrize => 'Lot Majeurs'; + String get raffleMajorPrize => 'Major prizes'; @override - String get raffleTakeTickets => 'Prendre vos tickets'; + String get raffleTakeTickets => 'Take your tickets'; @override - String get raffleNoTicketBuyable => - 'Vous ne pouvez pas achetez de billets pour l\'instant'; + String get raffleNoTicketBuyable => 'You cannot buy tickets right now'; @override - String get raffleNoCurrentPrize => 'Il n\'y a aucun lots actuellement'; + String get raffleNoCurrentPrize => 'There are no prizes currently'; @override String get raffleModifTombola => - 'Vous pouvez modifiez vos tombolas ou en créer de nouvelles, toute décision doit ensuite être prise par les admins'; + 'You can modify your raffles or create new ones, all decisions must then be approved by admins'; @override - String get raffleCreateYourRaffle => 'Votre menu de création de tombolas'; + String get raffleCreateYourRaffle => 'Your raffle creation menu'; @override - String get rafflePossiblePrice => 'Prix possible'; + String get rafflePossiblePrice => 'Possible prize'; @override - String get raffleInformation => 'Information et Statistiques'; + String get raffleInformation => 'Information and statistics'; @override - String get raffleAccounts => 'Comptes'; + String get raffleAccounts => 'Accounts'; @override - String get raffleAdd => 'Ajouter'; + String get raffleAdd => 'Add'; @override - String get raffleUpdatedAmount => 'Montant mis à jour'; + String get raffleUpdatedAmount => 'Amount updated'; @override - String get raffleUpdatingError => 'Erreur lors de la mise à jour'; + String get raffleUpdatingError => 'Error during update'; @override - String get raffleDeletedPrize => 'Lot supprimé'; + String get raffleDeletedPrize => 'Prize deleted'; @override - String get raffleDeletingError => 'Erreur lors de la suppression'; + String get raffleDeletingError => 'Error during deletion'; @override - String get raffleQuantity => 'Quantité'; + String get raffleQuantity => 'Quantity'; @override - String get raffleClose => 'Fermer'; + String get raffleClose => 'Close'; @override - String get raffleOpen => 'Ouvrir'; + String get raffleOpen => 'Open'; @override - String get raffleAddTypeTicketSimple => 'Ajouter'; + String get raffleAddTypeTicketSimple => 'Add'; @override - String get raffleAddingError => 'Erreur lors de l\'ajout'; + String get raffleAddingError => 'Error during addition'; @override - String get raffleEditTypeTicketSimple => 'Modifier'; + String get raffleEditTypeTicketSimple => 'Edit'; @override - String get raffleFillField => 'Le champ ne peut pas être vide'; + String get raffleFillField => 'Field cannot be empty'; @override - String get raffleWaiting => 'Chargement'; + String get raffleWaiting => 'Loading'; @override - String get raffleEditingError => 'Erreur lors de la modification'; + String get raffleEditingError => 'Error during editing'; @override - String get raffleAddedTicket => 'Ticket ajouté'; + String get raffleAddedTicket => 'Ticket added'; @override - String get raffleEditedTicket => 'Ticket modifié'; + String get raffleEditedTicket => 'Ticket edited'; @override - String get raffleAlreadyExistTicket => 'Le ticket existe déjà'; + String get raffleAlreadyExistTicket => 'Ticket already exists'; @override - String get raffleNumberExpected => 'Un entier est attendu'; + String get raffleNumberExpected => 'An integer is expected'; @override - String get raffleDeletedTicket => 'Ticket supprimé'; + String get raffleDeletedTicket => 'Ticket deleted'; @override - String get raffleAddPrize => 'Ajouter'; + String get raffleAddPrize => 'Add'; @override - String get raffleEditPrize => 'Modifier'; + String get raffleEditPrize => 'Edit'; @override - String get raffleOpenRaffle => 'Ouvrir la tombola'; + String get raffleOpenRaffle => 'Open raffle'; @override - String get raffleCloseRaffle => 'Fermer la tombola'; + String get raffleCloseRaffle => 'Close raffle'; @override String get raffleOpenRaffleDescription => - 'Vous allez ouvrir la tombola, les utilisateurs pourront acheter des tickets. Vous ne pourrez plus modifier la tombola. Êtes-vous sûr de vouloir continuer ?'; + 'You are going to open the raffle, users will be able to buy tickets. You will no longer be able to modify the raffle. Are you sure you want to continue?'; @override String get raffleCloseRaffleDescription => - 'Vous allez fermer la tombola, les utilisateurs ne pourront plus acheter de tickets. Êtes-vous sûr de vouloir continuer ?'; + 'You are going to close the raffle, users will no longer be able to buy tickets. Are you sure you want to continue?'; @override - String get raffleNoCurrentRaffle => 'Il n\'y a aucune tombola en cours'; + String get raffleNoCurrentRaffle => 'There is no ongoing raffle'; @override - String get raffleBoughtTicket => 'Ticket acheté'; + String get raffleBoughtTicket => 'Ticket purchased'; @override - String get raffleDrawingError => 'Erreur lors du tirage'; + String get raffleDrawingError => 'Error during drawing'; @override - String get raffleInvalidPrice => 'Le prix doit être supérieur à 0'; + String get raffleInvalidPrice => 'Price must be greater than 0'; @override - String get raffleMustBePositive => 'Le nombre doit être strictement positif'; + String get raffleMustBePositive => 'Number must be strictly positive'; @override - String get raffleDraw => 'Tirer'; + String get raffleDraw => 'Draw'; @override - String get raffleDrawn => 'Tiré'; + String get raffleDrawn => 'Drawn'; @override - String get raffleError => 'Erreur'; + String get raffleError => 'Error'; @override - String get raffleGathered => 'Récolté'; + String get raffleGathered => 'Collected'; @override String get raffleTickets => 'Tickets'; @@ -2471,88 +2456,88 @@ class AppLocalizationsEn extends AppLocalizations { String get raffleTicket => 'ticket'; @override - String get raffleWinner => 'Gagnant'; + String get raffleWinner => 'Winner'; @override - String get raffleNoPrize => 'Aucun lot'; + String get raffleNoPrize => 'No prize'; @override - String get raffleDeletePrize => 'Supprimer le lot'; + String get raffleDeletePrize => 'Delete prize'; @override String get raffleDeletePrizeDescription => - 'Vous allez supprimer le lot, êtes-vous sûr de vouloir continuer ?'; + 'You are going to delete the prize, are you sure you want to continue?'; @override - String get raffleDrawing => 'Tirage'; + String get raffleDrawing => 'Drawing'; @override - String get raffleDrawingDescription => 'Tirer le gagnant du lot ?'; + String get raffleDrawingDescription => 'Draw the prize winner?'; @override - String get raffleDeleteTicket => 'Supprimer le ticket'; + String get raffleDeleteTicket => 'Delete ticket'; @override String get raffleDeleteTicketDescription => - 'Vous allez supprimer le ticket, êtes-vous sûr de vouloir continuer ?'; + 'You are going to delete the ticket, are you sure you want to continue?'; @override - String get raffleWinningTickets => 'Tickets gagnants'; + String get raffleWinningTickets => 'Winning tickets'; @override String get raffleNoWinningTicketYet => - 'Les tickets gagnants seront affichés ici'; + 'Winning tickets will be displayed here'; @override - String get raffleName => 'Nom'; + String get raffleName => 'Name'; @override String get raffleDescription => 'Description'; @override - String get raffleBuyThisTicket => 'Acheter ce ticket'; + String get raffleBuyThisTicket => 'Buy this ticket'; @override - String get raffleLockedRaffle => 'Tombola verrouillée'; + String get raffleLockedRaffle => 'Locked raffle'; @override - String get raffleUnavailableRaffle => 'Tombola indisponible'; + String get raffleUnavailableRaffle => 'Unavailable raffle'; @override - String get raffleNotEnoughMoney => 'Vous n\'avez pas assez d\'argent'; + String get raffleNotEnoughMoney => 'You don\'t have enough money'; @override - String get raffleWinnable => 'gagnable'; + String get raffleWinnable => 'winnable'; @override - String get raffleNoDescription => 'Aucune description'; + String get raffleNoDescription => 'No description'; @override - String get raffleAmount => 'Solde'; + String get raffleAmount => 'Balance'; @override - String get raffleLoading => 'Chargement'; + String get raffleLoading => 'Loading'; @override - String get raffleTicketNumber => 'Nombre de ticket'; + String get raffleTicketNumber => 'Number of tickets'; @override - String get rafflePrice => 'Prix'; + String get rafflePrice => 'Price'; @override - String get raffleEditRaffle => 'Modifier la tombola'; + String get raffleEditRaffle => 'Edit raffle'; @override - String get raffleEdit => 'Modifier'; + String get raffleEdit => 'Edit'; @override - String get raffleAddPackTicket => 'Ajouter un pack de ticket'; + String get raffleAddPackTicket => 'Add ticket pack'; @override - String get recommendationRecommendation => 'Bons plans'; + String get recommendationRecommendation => 'Deals'; @override - String get recommendationTitle => 'Titre'; + String get recommendationTitle => 'Title'; @override String get recommendationLogo => 'Logo'; @@ -2561,264 +2546,261 @@ class AppLocalizationsEn extends AppLocalizations { String get recommendationCode => 'Code'; @override - String get recommendationSummary => 'Court résumé'; + String get recommendationSummary => 'Short summary'; @override String get recommendationDescription => 'Description'; @override - String get recommendationAdd => 'Ajouter'; + String get recommendationAdd => 'Add'; @override - String get recommendationEdit => 'Modifier'; + String get recommendationEdit => 'Edit'; @override - String get recommendationDelete => 'Supprimer'; + String get recommendationDelete => 'Delete'; @override - String get recommendationAddImage => 'Veuillez ajouter une image'; + String get recommendationAddImage => 'Please add an image'; @override - String get recommendationAddedRecommendation => 'Bon plan ajouté'; + String get recommendationAddedRecommendation => 'Deal added'; @override - String get recommendationEditedRecommendation => 'Bon plan modifié'; + String get recommendationEditedRecommendation => 'Deal updated'; @override String get recommendationDeleteRecommendationConfirmation => - 'Êtes-vous sûr de vouloir supprimer ce bon plan ?'; + 'Are you sure you want to delete this deal?'; @override - String get recommendationDeleteRecommendation => 'Suppresion'; + String get recommendationDeleteRecommendation => 'Delete'; @override String get recommendationDeletingRecommendationError => - 'Erreur lors de la suppression'; + 'Error during deletion'; @override - String get recommendationDeletedRecommendation => 'Bon plan supprimé'; + String get recommendationDeletedRecommendation => 'Deal deleted'; @override String get recommendationIncorrectOrMissingFields => - 'Champs incorrects ou manquants'; + 'Incorrect or missing fields'; @override - String get recommendationEditingError => 'Échec de la modification'; + String get recommendationEditingError => 'Edit failed'; @override - String get recommendationAddingError => 'Échec de l\'ajout'; + String get recommendationAddingError => 'Add failed'; @override - String get recommendationCopiedCode => 'Code de réduction copié'; + String get recommendationCopiedCode => 'Discount code copied'; @override - String get seedLibraryAdd => 'Ajouter'; + String get seedLibraryAdd => 'Add'; @override - String get seedLibraryAddedPlant => 'Plante ajoutée'; + String get seedLibraryAddedPlant => 'Plant added'; @override - String get seedLibraryAddedSpecies => 'Espèce ajoutée'; + String get seedLibraryAddedSpecies => 'Species added'; @override - String get seedLibraryAddingError => 'Erreur lors de l\'ajout'; + String get seedLibraryAddingError => 'Error during addition'; @override - String get seedLibraryAddPlant => 'Déposer une plante'; + String get seedLibraryAddPlant => 'Deposit a plant'; @override - String get seedLibraryAddSpecies => 'Ajouter une espèce'; + String get seedLibraryAddSpecies => 'Add a species'; @override - String get seedLibraryAll => 'Toutes'; + String get seedLibraryAll => 'All'; @override - String get seedLibraryAncestor => 'Ancêtre'; + String get seedLibraryAncestor => 'Ancestor'; @override - String get seedLibraryAround => 'environ'; + String get seedLibraryAround => 'around'; @override - String get seedLibraryAutumn => 'Automne'; + String get seedLibraryAutumn => 'Autumn'; @override - String get seedLibraryBorrowedPlant => 'Plante empruntée'; + String get seedLibraryBorrowedPlant => 'Borrowed plant'; @override - String get seedLibraryBorrowingDate => 'Date d\'emprunt :'; + String get seedLibraryBorrowingDate => 'Borrowing date:'; @override - String get seedLibraryBorrowPlant => 'Emprunter la plante'; + String get seedLibraryBorrowPlant => 'Borrow plant'; @override - String get seedLibraryCard => 'Carte'; + String get seedLibraryCard => 'Card'; @override - String get seedLibraryChoosingAncestor => 'Veuillez choisir un ancêtre'; + String get seedLibraryChoosingAncestor => 'Please choose an ancestor'; @override - String get seedLibraryChoosingSpecies => 'Veuillez choisir une espèce'; + String get seedLibraryChoosingSpecies => 'Please choose a species'; @override String get seedLibraryChoosingSpeciesOrAncestor => - 'Veuillez choisir une espèce ou un ancêtre'; + 'Please choose a species or an ancestor'; @override - String get seedLibraryContact => 'Contact :'; + String get seedLibraryContact => 'Contact:'; @override - String get seedLibraryDays => 'jours'; + String get seedLibraryDays => 'days'; @override - String get seedLibraryDeadMsg => 'Voulez-vous déclarer la plante morte ?'; + String get seedLibraryDeadMsg => 'Do you want to declare the plant dead?'; @override - String get seedLibraryDeadPlant => 'Plante morte'; + String get seedLibraryDeadPlant => 'Dead plant'; @override - String get seedLibraryDeathDate => 'Date de mort'; + String get seedLibraryDeathDate => 'Date of death'; @override - String get seedLibraryDeletedSpecies => 'Espèce supprimée'; + String get seedLibraryDeletedSpecies => 'Species deleted'; @override - String get seedLibraryDeleteSpecies => 'Supprimer l\'espèce ?'; + String get seedLibraryDeleteSpecies => 'Delete species?'; @override - String get seedLibraryDeleting => 'Suppression'; + String get seedLibraryDeleting => 'Deleting'; @override - String get seedLibraryDeletingError => 'Erreur lors de la suppression'; + String get seedLibraryDeletingError => 'Error during deletion'; @override String get seedLibraryDepositNotAvailable => - 'Le dépôt de plantes n\'est pas possible sans emprunter une plante au préalable'; + 'Plant deposit is not possible without borrowing a plant first'; @override String get seedLibraryDescription => 'Description'; @override - String get seedLibraryDifficulty => 'Difficulté :'; + String get seedLibraryDifficulty => 'Difficulty:'; @override - String get seedLibraryEdit => 'Modifier'; + String get seedLibraryEdit => 'Edit'; @override - String get seedLibraryEditedPlant => 'Plante modifiée'; + String get seedLibraryEditedPlant => 'Plant updated'; @override - String get seedLibraryEditInformation => 'Modifier les informations'; + String get seedLibraryEditInformation => 'Edit information'; @override - String get seedLibraryEditingError => 'Erreur lors de la modification'; + String get seedLibraryEditingError => 'Error during editing'; @override - String get seedLibraryEditSpecies => 'Modifier l\'espèce'; + String get seedLibraryEditSpecies => 'Edit species'; @override - String get seedLibraryEmptyDifficultyError => - 'Veuillez choisir une difficulté'; + String get seedLibraryEmptyDifficultyError => 'Please choose a difficulty'; @override - String get seedLibraryEmptyFieldError => 'Veuillez remplir tous les champs'; + String get seedLibraryEmptyFieldError => 'Please fill all fields'; @override - String get seedLibraryEmptyTypeError => 'Veuillez choisir un type de plante'; + String get seedLibraryEmptyTypeError => 'Please choose a plant type'; @override - String get seedLibraryEndMonth => 'Mois de fin :'; + String get seedLibraryEndMonth => 'End month:'; @override - String get seedLibraryFacebookUrl => 'Lien Facebook'; + String get seedLibraryFacebookUrl => 'Facebook link'; @override - String get seedLibraryFilters => 'Filtres'; + String get seedLibraryFilters => 'Filters'; @override - String get seedLibraryForum => - 'Oskour maman j\'ai tué ma plante - Forum d\'aide'; + String get seedLibraryForum => 'Oskour mom I killed my plant - Help forum'; @override - String get seedLibraryForumUrl => 'Lien Forum'; + String get seedLibraryForumUrl => 'Forum link'; @override - String get seedLibraryHelpSheets => 'Fiches sur les plantes'; + String get seedLibraryHelpSheets => 'Plant sheets'; @override - String get seedLibraryInformation => 'Informations :'; + String get seedLibraryInformation => 'Information:'; @override - String get seedLibraryMaturationTime => 'Temps de maturation'; + String get seedLibraryMaturationTime => 'Maturation time'; @override - String get seedLibraryMonthJan => 'Janvier'; + String get seedLibraryMonthJan => 'January'; @override - String get seedLibraryMonthFeb => 'Février'; + String get seedLibraryMonthFeb => 'February'; @override - String get seedLibraryMonthMar => 'Mars'; + String get seedLibraryMonthMar => 'March'; @override - String get seedLibraryMonthApr => 'Avril'; + String get seedLibraryMonthApr => 'April'; @override - String get seedLibraryMonthMay => 'Mai'; + String get seedLibraryMonthMay => 'May'; @override - String get seedLibraryMonthJun => 'Juin'; + String get seedLibraryMonthJun => 'June'; @override - String get seedLibraryMonthJul => 'Juillet'; + String get seedLibraryMonthJul => 'July'; @override - String get seedLibraryMonthAug => 'Août'; + String get seedLibraryMonthAug => 'August'; @override - String get seedLibraryMonthSep => 'Septembre'; + String get seedLibraryMonthSep => 'September'; @override - String get seedLibraryMonthOct => 'Octobre'; + String get seedLibraryMonthOct => 'October'; @override - String get seedLibraryMonthNov => 'Novembre'; + String get seedLibraryMonthNov => 'November'; @override - String get seedLibraryMonthDec => 'Décembre'; + String get seedLibraryMonthDec => 'December'; @override - String get seedLibraryMyPlants => 'Mes plantes'; + String get seedLibraryMyPlants => 'My plants'; @override - String get seedLibraryName => 'Nom'; + String get seedLibraryName => 'Name'; @override - String get seedLibraryNbSeedsRecommended => 'Nombre de graines recommandées'; + String get seedLibraryNbSeedsRecommended => 'Number of seeds recommended'; @override String get seedLibraryNbSeedsRecommendedError => - 'Veuillez entrer un nombre de graines recommandé supérieur à 0'; + 'Please enter a recommended seed number greater than 0'; @override - String get seedLibraryNoDateError => 'Veuillez entrer une date'; + String get seedLibraryNoDateError => 'Please enter a date'; @override String get seedLibraryNoFilteredPlants => - 'Aucune plante ne correspond à votre recherche. Essayez d\'autres filtres.'; + 'No plants match your search. Try other filters.'; @override - String get seedLibraryNoMorePlant => 'Aucune plante n\'est disponible'; + String get seedLibraryNoMorePlant => 'No plants available'; @override String get seedLibraryNoPersonalPlants => - 'Vous n\'avez pas encore de plantes dans votre grainothèque. Vous pouvez en ajouter en allant dans les stocks.'; + 'You don\'t have any plants yet in your seed library. You can add some in the stocks.'; @override - String get seedLibraryNoSpecies => 'Aucune espèce trouvée'; + String get seedLibraryNoSpecies => 'No species found'; @override - String get seedLibraryNoStockPlants => - 'Aucune plante disponible dans le stock'; + String get seedLibraryNoStockPlants => 'No plants available in stock'; @override String get seedLibraryNotes => 'Notes'; @@ -2827,236 +2809,232 @@ class AppLocalizationsEn extends AppLocalizations { String get seedLibraryOk => 'OK'; @override - String get seedLibraryPlantationPeriod => 'Période de plantation :'; + String get seedLibraryPlantationPeriod => 'Planting period:'; @override - String get seedLibraryPlantationType => 'Type de plantation :'; + String get seedLibraryPlantationType => 'Plantation type:'; @override - String get seedLibraryPlantDetail => 'Détail de la plante'; + String get seedLibraryPlantDetail => 'Plant details'; @override - String get seedLibraryPlantingDate => 'Date de plantation'; + String get seedLibraryPlantingDate => 'Planting date'; @override - String get seedLibraryPlantingNow => 'Je la plante maintenant'; + String get seedLibraryPlantingNow => 'I\'m planting it now'; @override - String get seedLibraryPrefix => 'Préfixe'; + String get seedLibraryPrefix => 'Prefix'; @override - String get seedLibraryPrefixError => 'Prefixe déjà utilisé'; + String get seedLibraryPrefixError => 'Prefix already used'; @override - String get seedLibraryPrefixLengthError => - 'Le préfixe doit faire 3 caractères'; + String get seedLibraryPrefixLengthError => 'The prefix must be 3 characters'; @override - String get seedLibraryPropagationMethod => 'Méthode de propagation :'; + String get seedLibraryPropagationMethod => 'Propagation method:'; @override - String get seedLibraryReference => 'Référence :'; + String get seedLibraryReference => 'Reference:'; @override - String get seedLibraryRemovedPlant => 'Plante supprimée'; + String get seedLibraryRemovedPlant => 'Plant removed'; @override - String get seedLibraryRemovingError => 'Erreur lors de la suppression'; + String get seedLibraryRemovingError => 'Error removing plant'; @override - String get seedLibraryResearch => 'Recherche'; + String get seedLibraryResearch => 'Search'; @override - String get seedLibrarySaveChanges => 'Sauvegarder les modifications'; + String get seedLibrarySaveChanges => 'Save changes'; @override - String get seedLibrarySeason => 'Saison :'; + String get seedLibrarySeason => 'Season:'; @override - String get seedLibrarySeed => 'Graine'; + String get seedLibrarySeed => 'Seed'; @override - String get seedLibrarySeeds => 'graines'; + String get seedLibrarySeeds => 'seeds'; @override - String get seedLibrarySeedDeposit => 'Dépôt de plantes'; + String get seedLibrarySeedDeposit => 'Plant deposit'; @override - String get seedLibrarySeedLibrary => 'Grainothèque'; + String get seedLibrarySeedLibrary => 'Seed library'; @override - String get seedLibrarySeedQuantitySimple => 'Quantité de graines'; + String get seedLibrarySeedQuantitySimple => 'Seed quantity'; @override - String get seedLibrarySeedQuantity => 'Quantité de graines :'; + String get seedLibrarySeedQuantity => 'Seed quantity:'; @override - String get seedLibraryShowDeadPlants => 'Afficher les plantes mortes'; + String get seedLibraryShowDeadPlants => 'Show dead plants'; @override - String get seedLibrarySpecies => 'Espèce :'; + String get seedLibrarySpecies => 'Species:'; @override - String get seedLibrarySpeciesHelp => 'Aide sur l\'espèce'; + String get seedLibrarySpeciesHelp => 'Help on species'; @override - String get seedLibrarySpeciesPlural => 'Espèces'; + String get seedLibrarySpeciesPlural => 'Species'; @override - String get seedLibrarySpeciesSimple => 'Espèce'; + String get seedLibrarySpeciesSimple => 'Species'; @override - String get seedLibrarySpeciesType => 'Type d\'espèce :'; + String get seedLibrarySpeciesType => 'Species type:'; @override - String get seedLibrarySpring => 'Printemps'; + String get seedLibrarySpring => 'Spring'; @override - String get seedLibraryStartMonth => 'Mois de début :'; + String get seedLibraryStartMonth => 'Start month:'; @override - String get seedLibraryStock => 'Stock disponible'; + String get seedLibraryStock => 'Available stock'; @override - String get seedLibrarySummer => 'Été'; + String get seedLibrarySummer => 'Summer'; @override String get seedLibraryStocks => 'Stocks'; @override - String get seedLibraryTimeUntilMaturation => 'Temps avant maturation :'; + String get seedLibraryTimeUntilMaturation => 'Time until maturation:'; @override - String get seedLibraryType => 'Type :'; + String get seedLibraryType => 'Type:'; @override - String get seedLibraryUnableToOpen => 'Impossible d\'ouvrir le lien'; + String get seedLibraryUnableToOpen => 'Unable to open link'; @override - String get seedLibraryUpdate => 'Modifier'; + String get seedLibraryUpdate => 'Edit'; @override - String get seedLibraryUpdatedInformation => 'Informations modifiées'; + String get seedLibraryUpdatedInformation => 'Information updated'; @override - String get seedLibraryUpdatedSpecies => 'Espèce modifiée'; + String get seedLibraryUpdatedSpecies => 'Species updated'; @override - String get seedLibraryUpdatedPlant => 'Plante modifiée'; + String get seedLibraryUpdatedPlant => 'Plant updated'; @override - String get seedLibraryUpdatingError => 'Erreur lors de la modification'; + String get seedLibraryUpdatingError => 'Error updating'; @override - String get seedLibraryWinter => 'Hiver'; + String get seedLibraryWinter => 'Winter'; @override String get seedLibraryWriteReference => - 'Veuillez écrire la référence suivante : '; + 'Please write the following reference: '; @override - String get settingsAccount => 'Compte'; + String get settingsAccount => 'Account'; @override - String get settingsAddProfilePicture => 'Ajouter une photo'; + String get settingsAddProfilePicture => 'Add a photo'; @override - String get settingsAdmin => 'Administrateur'; + String get settingsAdmin => 'Administrator'; @override - String get settingsAskHelp => 'Demander de l\'aide'; + String get settingsAskHelp => 'Ask for help'; @override String get settingsAssociation => 'Association'; @override - String get settingsBirthday => 'Date de naissance'; + String get settingsBirthday => 'Birthday'; @override String get settingsBugs => 'Bugs'; @override - String get settingsChangePassword => 'Changer de mot de passe'; + String get settingsChangePassword => 'Change password'; @override String get settingsChangingPassword => - 'Voulez-vous vraiment changer votre mot de passe ?'; + 'Do you really want to change your password?'; @override - String get settingsConfirmPassword => 'Confirmer le mot de passe'; + String get settingsConfirmPassword => 'Confirm password'; @override - String get settingsCopied => 'Copié !'; + String get settingsCopied => 'Copied!'; @override - String get settingsDarkMode => 'Mode sombre'; + String get settingsDarkMode => 'Dark mode'; @override - String get settingsDarkModeOff => 'Désactivé'; + String get settingsDarkModeOff => 'Off'; @override - String get settingsDeleteLogs => 'Supprimer les logs ?'; + String get settingsDeleteLogs => 'Delete logs?'; @override - String get settingsDeleteNotificationLogs => - 'Supprimer les logs des notifications ?'; + String get settingsDeleteNotificationLogs => 'Delete notification logs?'; @override - String get settingsDetelePersonalData => 'Supprimer mes données personnelles'; + String get settingsDetelePersonalData => 'Delete my personal data'; @override String get settingsDetelePersonalDataDesc => - 'Cette action notifie l\'administrateur que vous souhaitez supprimer vos données personnelles.'; + 'This action notifies the administrator that you want to delete your personal data.'; @override - String get settingsDeleting => 'Suppresion'; + String get settingsDeleting => 'Deleting'; @override - String get settingsEdit => 'Modifier'; + String get settingsEdit => 'Edit'; @override - String get settingsEditAccount => 'Modifier le compte'; + String get settingsEditAccount => 'Edit account'; @override - String get settingsEditPassword => 'Modifier le mot de passe'; + String get settingsEditPassword => 'Edit password'; @override String get settingsEmail => 'Email'; @override - String get settingsEmptyField => 'Ce champ ne peut pas être vide'; + String get settingsEmptyField => 'This field cannot be empty'; @override - String get settingsErrorProfilePicture => - 'Erreur lors de la modification de la photo de profil'; + String get settingsErrorProfilePicture => 'Error editing profile picture'; @override - String get settingsErrorSendingDemand => - 'Erreur lors de l\'envoi de la demande'; + String get settingsErrorSendingDemand => 'Error sending request'; @override - String get settingsEventsIcal => 'Lien Ical des événements'; + String get settingsEventsIcal => 'Ical link for events'; @override - String get settingsExpectingDate => 'Date de naissance attendue'; + String get settingsExpectingDate => 'Expected birth date'; @override - String get settingsFirstname => 'Prénom'; + String get settingsFirstname => 'First name'; @override - String get settingsFloor => 'Étage'; + String get settingsFloor => 'Floor'; @override - String get settingsHelp => 'Aide'; + String get settingsHelp => 'Help'; @override - String get settingsIcalCopied => 'Lien Ical copié !'; + String get settingsIcalCopied => 'Ical link copied!'; @override - String get settingsLanguage => 'Langue'; + String get settingsLanguage => 'Language'; @override - String get settingsLanguageFr => 'Français'; + String get settingsLanguageFr => 'French'; @override String get settingsLogs => 'Logs'; @@ -3065,333 +3043,329 @@ class AppLocalizationsEn extends AppLocalizations { String get settingsModules => 'Modules'; @override - String get settingsMyIcs => 'Mon lien Ical'; + String get settingsMyIcs => 'My Ical link'; @override - String get settingsName => 'Nom'; + String get settingsName => 'Last name'; @override - String get settingsNewPassword => 'Nouveau mot de passe'; + String get settingsNewPassword => 'New password'; @override - String get settingsNickname => 'Surnom'; + String get settingsNickname => 'Nickname'; @override String get settingsNotifications => 'Notifications'; @override - String get settingsOldPassword => 'Ancien mot de passe'; + String get settingsOldPassword => 'Old password'; @override - String get settingsPasswordChanged => 'Mot de passe changé'; + String get settingsPasswordChanged => 'Password changed'; @override - String get settingsPasswordsNotMatch => - 'Les mots de passe ne correspondent pas'; + String get settingsPasswordsNotMatch => 'Passwords do not match'; @override - String get settingsPersonalData => 'Données personnelles'; + String get settingsPersonalData => 'Personal data'; @override - String get settingsPersonalisation => 'Personnalisation'; + String get settingsPersonalisation => 'Personalization'; @override - String get settingsPhone => 'Téléphone'; + String get settingsPhone => 'Phone'; @override - String get settingsProfilePicture => 'Photo de profil'; + String get settingsProfilePicture => 'Profile picture'; @override String get settingsPromo => 'Promotion'; @override - String get settingsRepportBug => 'Signaler un bug'; + String get settingsRepportBug => 'Report a bug'; @override - String get settingsSave => 'Enregistrer'; + String get settingsSave => 'Save'; @override - String get settingsSecurity => 'Sécurité'; + String get settingsSecurity => 'Security'; @override - String get settingsSendedDemand => 'Demande envoyée'; + String get settingsSendedDemand => 'Request sent'; @override - String get settingsSettings => 'Paramètres'; + String get settingsSettings => 'Settings'; @override - String get settingsTooHeavyProfilePicture => - 'L\'image est trop lourde (max 4Mo)'; + String get settingsTooHeavyProfilePicture => 'Image is too large (max 4MB)'; @override - String get settingsUpdatedProfile => 'Profil modifié'; + String get settingsUpdatedProfile => 'Profile updated'; @override - String get settingsUpdatedProfilePicture => 'Photo de profil modifiée'; + String get settingsUpdatedProfilePicture => 'Profile picture updated'; @override - String get settingsUpdateNotification => 'Mettre à jour les notifications'; + String get settingsUpdateNotification => 'Update notifications'; @override - String get settingsUpdatingError => - 'Erreur lors de la modification du profil'; + String get settingsUpdatingError => 'Error updating profile'; @override String get settingsVersion => 'Version'; @override - String get settingsPasswordStrength => 'Force du mot de passe'; + String get settingsPasswordStrength => 'Password strength'; @override - String get settingsPasswordStrengthVeryWeak => 'Très faible'; + String get settingsPasswordStrengthVeryWeak => 'Very weak'; @override - String get settingsPasswordStrengthWeak => 'Faible'; + String get settingsPasswordStrengthWeak => 'Weak'; @override - String get settingsPasswordStrengthMedium => 'Moyen'; + String get settingsPasswordStrengthMedium => 'Medium'; @override - String get settingsPasswordStrengthStrong => 'Fort'; + String get settingsPasswordStrengthStrong => 'Strong'; @override - String get settingsPasswordStrengthVeryStrong => 'Très fort'; + String get settingsPasswordStrengthVeryStrong => 'Very strong'; @override - String get voteAdd => 'Ajouter'; + String get voteAdd => 'Add'; @override - String get voteAddMember => 'Ajouter un membre'; + String get voteAddMember => 'Add a member'; @override - String get voteAddedPretendance => 'Liste ajoutée'; + String get voteAddedPretendance => 'List added'; @override - String get voteAddedSection => 'Section ajoutée'; + String get voteAddedSection => 'Section added'; @override - String get voteAddingError => 'Erreur lors de l\'ajout'; + String get voteAddingError => 'Error adding'; @override - String get voteAddPretendance => 'Ajouter une liste'; + String get voteAddPretendance => 'Add a list'; @override - String get voteAddSection => 'Ajouter une section'; + String get voteAddSection => 'Add a section'; @override - String get voteAll => 'Tous'; + String get voteAll => 'All'; @override - String get voteAlreadyAddedMember => 'Membre déjà ajouté'; + String get voteAlreadyAddedMember => 'Member already added'; @override - String get voteAlreadyVoted => 'Vote enregistré'; + String get voteAlreadyVoted => 'Vote recorded'; @override - String get voteChooseList => 'Choisir une liste'; + String get voteChooseList => 'Choose a list'; @override - String get voteClear => 'Réinitialiser'; + String get voteClear => 'Reset'; @override - String get voteClearVotes => 'Réinitialiser les votes'; + String get voteClearVotes => 'Reset votes'; @override - String get voteClosedVote => 'Votes clos'; + String get voteClosedVote => 'Votes closed'; @override - String get voteCloseVote => 'Fermer les votes'; + String get voteCloseVote => 'Close votes'; @override - String get voteConfirmVote => 'Confirmer le vote'; + String get voteConfirmVote => 'Confirm vote'; @override - String get voteCountVote => 'Dépouiller les votes'; + String get voteCountVote => 'Count votes'; @override - String get voteDeletedAll => 'Tout supprimé'; + String get voteDeletedAll => 'All deleted'; @override - String get voteDeletedPipo => 'Listes pipos supprimées'; + String get voteDeletedPipo => 'Fake lists deleted'; @override - String get voteDeletedSection => 'Section supprimée'; + String get voteDeletedSection => 'Section deleted'; @override - String get voteDeleteAll => 'Supprimer tout'; + String get voteDeleteAll => 'Delete all'; @override String get voteDeleteAllDescription => - 'Voulez-vous vraiment supprimer tout ?'; + 'Do you really want to delete everything?'; @override - String get voteDeletePipo => 'Supprimer les listes pipos'; + String get voteDeletePipo => 'Delete fake lists'; @override String get voteDeletePipoDescription => - 'Voulez-vous vraiment supprimer les listes pipos ?'; + 'Do you really want to delete the fake lists?'; @override - String get voteDeletePretendance => 'Supprimer la liste'; + String get voteDeletePretendance => 'Delete the list'; @override String get voteDeletePretendanceDesc => - 'Voulez-vous vraiment supprimer cette liste ?'; + 'Do you really want to delete this list?'; @override - String get voteDeleteSection => 'Supprimer la section'; + String get voteDeleteSection => 'Delete the section'; @override String get voteDeleteSectionDescription => - 'Voulez-vous vraiment supprimer cette section ?'; + 'Do you really want to delete this section?'; @override - String get voteDeletingError => 'Erreur lors de la suppression'; + String get voteDeletingError => 'Error deleting'; @override String get voteDescription => 'Description'; @override - String get voteEdit => 'Modifier'; + String get voteEdit => 'Edit'; @override - String get voteEditedPretendance => 'Liste modifiée'; + String get voteEditedPretendance => 'List edited'; @override - String get voteEditedSection => 'Section modifiée'; + String get voteEditedSection => 'Section edited'; @override - String get voteEditingError => 'Erreur lors de la modification'; + String get voteEditingError => 'Error editing'; @override - String get voteErrorClosingVotes => 'Erreur lors de la fermeture des votes'; + String get voteErrorClosingVotes => 'Error closing votes'; @override - String get voteErrorCountingVotes => 'Erreur lors du dépouillement des votes'; + String get voteErrorCountingVotes => 'Error counting votes'; @override - String get voteErrorResetingVotes => - 'Erreur lors de la réinitialisation des votes'; + String get voteErrorResetingVotes => 'Error resetting votes'; @override - String get voteErrorOpeningVotes => 'Erreur lors de l\'ouverture des votes'; + String get voteErrorOpeningVotes => 'Error opening votes'; @override - String get voteIncorrectOrMissingFields => 'Champs incorrects ou manquants'; + String get voteIncorrectOrMissingFields => 'Incorrect or missing fields'; @override - String get voteMembers => 'Membres'; + String get voteMembers => 'Members'; @override - String get voteName => 'Nom'; + String get voteName => 'Name'; @override - String get voteNoPretendanceList => 'Aucune liste de prétendance'; + String get voteNoPretendanceList => 'No list of candidates'; @override - String get voteNoSection => 'Aucune section'; + String get voteNoSection => 'No section'; @override - String get voteCanNotVote => 'Vous ne pouvez pas voter'; + String get voteCanNotVote => 'You cannot vote'; @override - String get voteNoSectionList => 'Aucune section'; + String get voteNoSectionList => 'No section'; @override - String get voteNotOpenedVote => 'Vote non ouvert'; + String get voteNotOpenedVote => 'Vote not opened'; @override - String get voteOnGoingCount => 'Dépouillement en cours'; + String get voteOnGoingCount => 'Counting in progress'; @override - String get voteOpenVote => 'Ouvrir les votes'; + String get voteOpenVote => 'Open votes'; @override - String get votePipo => 'Pipo'; + String get votePipo => 'Fake'; @override - String get votePretendance => 'Listes'; + String get votePretendance => 'Lists'; @override - String get votePretendanceDeleted => 'Prétendance supprimée'; + String get votePretendanceDeleted => 'Candidate list deleted'; @override - String get votePretendanceNotDeleted => 'Erreur lors de la suppression'; + String get votePretendanceNotDeleted => 'Error deleting'; @override - String get voteProgram => 'Programme'; + String get voteProgram => 'Program'; @override - String get votePublish => 'Publier'; + String get votePublish => 'Publish'; @override String get votePublishVoteDescription => - 'Voulez-vous vraiment publier les votes ?'; + 'Do you really want to publish the votes?'; @override - String get voteResetedVotes => 'Votes réinitialisés'; + String get voteResetedVotes => 'Votes reset'; @override - String get voteResetVote => 'Réinitialiser les votes'; + String get voteResetVote => 'Reset votes'; @override - String get voteResetVoteDescription => 'Que voulez-vous faire ?'; + String get voteResetVoteDescription => 'What do you want to do?'; @override - String get voteRole => 'Rôle'; + String get voteRole => 'Role'; @override - String get voteSectionDescription => 'Description de la section'; + String get voteSectionDescription => 'Section description'; @override String get voteSection => 'Section'; @override - String get voteSectionName => 'Nom de la section'; + String get voteSectionName => 'Section name'; @override - String get voteSeeMore => 'Voir plus'; + String get voteSeeMore => 'See more'; @override - String get voteSelected => 'Sélectionné'; + String get voteSelected => 'Selected'; @override - String get voteShowVotes => 'Voir les votes'; + String get voteShowVotes => 'Show votes'; @override String get voteVote => 'Vote'; @override - String get voteVoteError => 'Erreur lors de l\'enregistrement du vote'; + String get voteVoteError => 'Error recording vote'; @override - String get voteVoteFor => 'Voter pour '; + String get voteVoteFor => 'Vote for '; @override - String get voteVoteNotStarted => 'Vote non ouvert'; + String get voteVoteNotStarted => 'Vote not opened'; @override - String get voteVoters => 'Groupes votants'; + String get voteVoters => 'Voting groups'; @override - String get voteVoteSuccess => 'Vote enregistré'; + String get voteVoteSuccess => 'Vote recorded'; @override - String get voteVotes => 'Voix'; + String get voteVotes => 'Votes'; @override - String get voteVotesClosed => 'Votes clos'; + String get voteVotesClosed => 'Votes closed'; @override - String get voteVotesCounted => 'Votes dépouillés'; + String get voteVotesCounted => 'Votes counted'; @override - String get voteVotesOpened => 'Votes ouverts'; + String get voteVotesOpened => 'Votes opened'; @override - String get voteWarning => 'Attention'; + String get voteWarning => 'Warning'; @override String get voteWarningMessage => - 'La sélection ne sera pas sauvegardée.\nVoulez-vous continuer ?'; + 'Selection will not be saved.\nDo you want to continue?'; } From 06eff9e8e3eea93abeb4a7cbd5d2051e23df7bf7 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Thu, 3 Jul 2025 20:35:11 +0200 Subject: [PATCH 056/473] Drawer --- lib/l10n/app_en.arb | 20 +++- lib/l10n/app_fr.arb | 20 +++- lib/l10n/app_localizations.dart | 108 ++++++++++++++++++ lib/l10n/app_localizations_en.dart | 54 +++++++++ lib/l10n/app_localizations_fr.dart | 54 +++++++++ .../ui/pages/modules_page/modules_page.dart | 2 +- 6 files changed, 255 insertions(+), 3 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 6cbe7f89da..21b10f29c3 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1099,5 +1099,23 @@ "voteVotesCounted": "Votes counted", "voteVotesOpened": "Votes opened", "voteWarning": "Warning", - "voteWarningMessage": "Selection will not be saved.\nDo you want to continue?" + "voteWarningMessage": "Selection will not be saved.\nDo you want to continue?", + "moduleAdvert": "Advert", + "moduleAmap": "AMAP", + "moduleBooking": "Booking", + "moduleCalendar": "Calendar", + "moduleCentralisation": "Centralisation", + "moduleCinema": "Cinema", + "moduleEvent": "Event", + "moduleFlappyBird": "Flappy Bird", + "moduleLoan": "Loan", + "modulePhonebook": "Phonebook", + "modulePurchases": "Purchases", + "moduleRaffle": "Raffle", + "moduleRecommendation": "Recommendation", + "moduleSeedLibrary": "Seed Library", + "moduleVote": "Vote", + "modulePh": "PH", + "moduleSettings": "Settings", + "modulePayment": "Payment" } \ No newline at end of file diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 3e6fb91d9c..d443406b8c 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -1099,5 +1099,23 @@ "voteVotesCounted": "Votes dépouillés", "voteVotesOpened": "Votes ouverts", "voteWarning": "Attention", - "voteWarningMessage": "La sélection ne sera pas sauvegardée.\nVoulez-vous continuer ?" + "voteWarningMessage": "La sélection ne sera pas sauvegardée.\nVoulez-vous continuer ?", + "moduleAdvert": "Annonce", + "moduleAmap": "AMAP", + "moduleBooking": "Réservation", + "moduleCalendar": "Calendrier", + "moduleCentralisation": "Centralisation", + "moduleCinema": "Cinéma", + "moduleEvent": "Événement", + "moduleFlappyBird": "Flappy Bird", + "moduleLoan": "Prêt", + "modulePhonebook": "Annuaire", + "modulePurchases": "Achats", + "moduleRaffle": "Tombola", + "moduleRecommendation": "Bons plans", + "moduleSeedLibrary": "Grainothèque", + "moduleVote": "Vote", + "modulePh": "PH", + "moduleSettings": "Paramètres", + "modulePayment": "Paiement" } \ No newline at end of file diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index e76239be15..7da896bc38 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -6697,6 +6697,114 @@ abstract class AppLocalizations { /// In fr, this message translates to: /// **'La sélection ne sera pas sauvegardée.\nVoulez-vous continuer ?'** String get voteWarningMessage; + + /// No description provided for @moduleAdvert. + /// + /// In fr, this message translates to: + /// **'Annonce'** + String get moduleAdvert; + + /// No description provided for @moduleAmap. + /// + /// In fr, this message translates to: + /// **'AMAP'** + String get moduleAmap; + + /// No description provided for @moduleBooking. + /// + /// In fr, this message translates to: + /// **'Réservation'** + String get moduleBooking; + + /// No description provided for @moduleCalendar. + /// + /// In fr, this message translates to: + /// **'Calendrier'** + String get moduleCalendar; + + /// No description provided for @moduleCentralisation. + /// + /// In fr, this message translates to: + /// **'Centralisation'** + String get moduleCentralisation; + + /// No description provided for @moduleCinema. + /// + /// In fr, this message translates to: + /// **'Cinéma'** + String get moduleCinema; + + /// No description provided for @moduleEvent. + /// + /// In fr, this message translates to: + /// **'Événement'** + String get moduleEvent; + + /// No description provided for @moduleFlappyBird. + /// + /// In fr, this message translates to: + /// **'Flappy Bird'** + String get moduleFlappyBird; + + /// No description provided for @moduleLoan. + /// + /// In fr, this message translates to: + /// **'Prêt'** + String get moduleLoan; + + /// No description provided for @modulePhonebook. + /// + /// In fr, this message translates to: + /// **'Annuaire'** + String get modulePhonebook; + + /// No description provided for @modulePurchases. + /// + /// In fr, this message translates to: + /// **'Achats'** + String get modulePurchases; + + /// No description provided for @moduleRaffle. + /// + /// In fr, this message translates to: + /// **'Tombola'** + String get moduleRaffle; + + /// No description provided for @moduleRecommendation. + /// + /// In fr, this message translates to: + /// **'Bons plans'** + String get moduleRecommendation; + + /// No description provided for @moduleSeedLibrary. + /// + /// In fr, this message translates to: + /// **'Grainothèque'** + String get moduleSeedLibrary; + + /// No description provided for @moduleVote. + /// + /// In fr, this message translates to: + /// **'Vote'** + String get moduleVote; + + /// No description provided for @modulePh. + /// + /// In fr, this message translates to: + /// **'PH'** + String get modulePh; + + /// No description provided for @moduleSettings. + /// + /// In fr, this message translates to: + /// **'Paramètres'** + String get moduleSettings; + + /// No description provided for @modulePayment. + /// + /// In fr, this message translates to: + /// **'Paiement'** + String get modulePayment; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 63f6b66097..7c6b5ebabc 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -3368,4 +3368,58 @@ class AppLocalizationsEn extends AppLocalizations { @override String get voteWarningMessage => 'Selection will not be saved.\nDo you want to continue?'; + + @override + String get moduleAdvert => 'Advert'; + + @override + String get moduleAmap => 'AMAP'; + + @override + String get moduleBooking => 'Booking'; + + @override + String get moduleCalendar => 'Calendar'; + + @override + String get moduleCentralisation => 'Centralisation'; + + @override + String get moduleCinema => 'Cinema'; + + @override + String get moduleEvent => 'Event'; + + @override + String get moduleFlappyBird => 'Flappy Bird'; + + @override + String get moduleLoan => 'Loan'; + + @override + String get modulePhonebook => 'Phonebook'; + + @override + String get modulePurchases => 'Purchases'; + + @override + String get moduleRaffle => 'Raffle'; + + @override + String get moduleRecommendation => 'Recommendation'; + + @override + String get moduleSeedLibrary => 'Seed Library'; + + @override + String get moduleVote => 'Vote'; + + @override + String get modulePh => 'PH'; + + @override + String get moduleSettings => 'Settings'; + + @override + String get modulePayment => 'Payment'; } diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index b7403438e1..12c4a0636e 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -3394,4 +3394,58 @@ class AppLocalizationsFr extends AppLocalizations { @override String get voteWarningMessage => 'La sélection ne sera pas sauvegardée.\nVoulez-vous continuer ?'; + + @override + String get moduleAdvert => 'Annonce'; + + @override + String get moduleAmap => 'AMAP'; + + @override + String get moduleBooking => 'Réservation'; + + @override + String get moduleCalendar => 'Calendrier'; + + @override + String get moduleCentralisation => 'Centralisation'; + + @override + String get moduleCinema => 'Cinéma'; + + @override + String get moduleEvent => 'Événement'; + + @override + String get moduleFlappyBird => 'Flappy Bird'; + + @override + String get moduleLoan => 'Prêt'; + + @override + String get modulePhonebook => 'Annuaire'; + + @override + String get modulePurchases => 'Achats'; + + @override + String get moduleRaffle => 'Tombola'; + + @override + String get moduleRecommendation => 'Bons plans'; + + @override + String get moduleSeedLibrary => 'Grainothèque'; + + @override + String get moduleVote => 'Vote'; + + @override + String get modulePh => 'PH'; + + @override + String get moduleSettings => 'Paramètres'; + + @override + String get modulePayment => 'Paiement'; } diff --git a/lib/settings/ui/pages/modules_page/modules_page.dart b/lib/settings/ui/pages/modules_page/modules_page.dart index c93e24e80d..fbf6a1d336 100644 --- a/lib/settings/ui/pages/modules_page/modules_page.dart +++ b/lib/settings/ui/pages/modules_page/modules_page.dart @@ -46,7 +46,7 @@ class ModulesPage extends HookConsumerWidget { child: Row( children: [ Text( - module.name, + module.getName(context), style: const TextStyle( color: Colors.black, fontSize: 20, From ce4c35d95a990aeb586c404f260d3cd1e089f447 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Thu, 3 Jul 2025 20:39:46 +0200 Subject: [PATCH 057/473] bug fix --- lib/l10n/app_en.arb | 4 ++-- lib/l10n/app_localizations_en.dart | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 21b10f29c3..d3b0ce6d9a 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -703,7 +703,7 @@ "phonebookNoMember": "No member", "phonebookNoMemberRole": "No role found", "phonebookPhone": "Phone:", - "phonebookPhonebook": "Directory", + "phonebookPhonebook": "Phonebook", "phonebookPhonebookSearch": "Search", "phonebookPhonebookSearchAssociation": "Association", "phonebookPhonebookSearchField": "Search:", @@ -827,7 +827,7 @@ "raffleEditRaffle": "Edit raffle", "raffleEdit": "Edit", "raffleAddPackTicket": "Add ticket pack", - "recommendationRecommendation": "Deals", + "recommendationRecommendation": "Recommendation", "recommendationTitle": "Title", "recommendationLogo": "Logo", "recommendationCode": "Code", diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 7c6b5ebabc..26781b7938 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -2154,7 +2154,7 @@ class AppLocalizationsEn extends AppLocalizations { String get phonebookPhone => 'Phone:'; @override - String get phonebookPhonebook => 'Directory'; + String get phonebookPhonebook => 'Phonebook'; @override String get phonebookPhonebookSearch => 'Search'; @@ -2534,7 +2534,7 @@ class AppLocalizationsEn extends AppLocalizations { String get raffleAddPackTicket => 'Add ticket pack'; @override - String get recommendationRecommendation => 'Deals'; + String get recommendationRecommendation => 'Recommendation'; @override String get recommendationTitle => 'Title'; From 5fb1e582d8987c3d66992ef0b358f6fccbc9af21 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Thu, 3 Jul 2025 21:33:19 +0200 Subject: [PATCH 058/473] Paiement first part --- lib/l10n/app_fr.arb | 55 ++- lib/l10n/app_localizations.dart | 318 ++++++++++++++++++ lib/l10n/app_localizations_en.dart | 174 ++++++++++ lib/l10n/app_localizations_fr.dart | 174 ++++++++++ .../ui/components/transaction_card.dart | 9 +- .../ui/pages/admin_page/admin_page.dart | 3 +- .../ui/pages/admin_page/admin_store_card.dart | 18 +- .../pages/devices_page/add_device_button.dart | 3 +- .../ui/pages/devices_page/device_item.dart | 5 +- .../ui/pages/devices_page/devices_page.dart | 40 ++- .../ui/pages/fund_page/confirm_button.dart | 22 +- .../ui/pages/fund_page/fund_page.dart | 5 +- .../account_card/last_transactions.dart | 9 +- .../ui/pages/main_page/main_page.dart | 9 +- .../seller_card/store_admin_card.dart | 3 +- .../main_page/seller_card/store_card.dart | 11 +- .../main_page/seller_card/store_list.dart | 9 +- .../ui/pages/main_page/tos_dialog.dart | 9 +- 18 files changed, 824 insertions(+), 52 deletions(-) diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index d443406b8c..0c0a66de16 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -1117,5 +1117,58 @@ "moduleVote": "Vote", "modulePh": "PH", "moduleSettings": "Paramètres", - "modulePayment": "Paiement" + "modulePayment": "Paiement", + "paiementTopUp" : "Recharge", + "paiementStoreManagement" : "Gestion des associations", + "paiementDeleteStore": "Supprimer l'association", + "paiementDeleteStoreDescription": "Voulez-vous vraiment supprimer cette association ?", + "paiementDeleteStoreError": "Impossible de supprimer l'association", + "paiementStoreDeleted": "Association supprimée", + "paiementAddThisDevice": "Ajouter cet appareil", + "paiementThisDevice": "(cet appareil)", + "paiementCancelled": "Annulé", + "paiementThe" : "Le", + "paiementOf": "de", + "paiementRefundedThe": "Remboursé le", + "paiementAt": "à", + "paiementPleaseAcceptTOS" : "Veuillez accepter les Conditions Générales d'Utilisation.", + "paiementAskDeviceActivation" : "Demande d'activation de l'appareil", + "paiementDeviceActivationReceived" : "La demande d'activation est prise en compte, veuilliez consulter votre boite mail pour finaliser la démarche", + "paiementRevokeDevice" : "Révoquer l'appareil ?", + "paiementRevokeDeviceDescription" : "Vous ne pourrez plus utiliser cet appareil pour les paiements", + "paiementDeviceRevoked" : "Appareil révoqué", + "paiementDeviceRevokingError" : "Erreur lors de la révocation de l'appareil", + "paiementPleaseAcceptPopup": "Veuillez autoriser les popups", + "paiementProceedSuccessfully": "Paiement effectué avec succès", + "paiementCancelledTransaction": "Paiement annulé", + "paiementPleaseEnterMinAmount": "Veuillez entrer un montant supérieur à 1", + "paiementMaxAmount": "Le montant maximum de votre portefeuille est de", + "paiementPayWithHA" : "Payer avec HelloAsso", + "paiementBalanceAfterTopUp": "Solde après recharge :", + "paiementPersonalBalance": "Solde personnel", + "paiementDevices": "Appareils", + "paiementPay": "Payer", + "paiementDeviceNotRegistered": "Appareil non enregistré", + "paiementDeviceNotRegisteredDescription": "Votre appareil n'est pas encore enregistré. \nPour l'enregistrer, veuillez vous rendre sur la page des appareils.", + "paiementAccessPage": "Accéder à la page", + "paiementDeviceNotActivated": "Appareil non activé", + "paiementDeviceNotActivatedDescription": "Votre appareil n'est pas encore activé. \nPour l'activer, veuillez vous rendre sur la page des appareils.", + "paiementReactivateRevokedDeviceDescription": "Votre appareil a été révoqué. \nPour le réactiver, veuillez vous rendre sur la page des appareils.", + "paiementDeviceRecoveryError": "Erreur lors de la récupération de l'appareil", + "paiementStats": "Stats", + "paimentTopUpAction": "Recharger", + "paiementGetBalanceError": "Erreur lors de la récupération du solde : ", + "paiementLastTransactions": "Dernières transactions", + "paiementGetTransactionsError": "Erreur lors de la récupération des transactions : ", + "paiementStoreBalance" : "Solde associatif", + "paiementScan": "Scanner", + "paiementManagement": "Gestion", + "paiementHistory": "Historique", + "paiementHandOver": "Passation", + "paiementStores": "Associations", + "paiementAdmin": "Administrateur", + "paiementSuccededTransaction": "Paiement réussi", + "paiementNewCGU" : "Nouvelles Conditions Générales d'Utilisation", + "paiementDecline": "Refuser", + "paiementAccept": "Accepter" } \ No newline at end of file diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 7da896bc38..66fc1e6baa 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -6805,6 +6805,324 @@ abstract class AppLocalizations { /// In fr, this message translates to: /// **'Paiement'** String get modulePayment; + + /// No description provided for @paiementTopUp. + /// + /// In fr, this message translates to: + /// **'Recharge'** + String get paiementTopUp; + + /// No description provided for @paiementStoreManagement. + /// + /// In fr, this message translates to: + /// **'Gestion des associations'** + String get paiementStoreManagement; + + /// No description provided for @paiementDeleteStore. + /// + /// In fr, this message translates to: + /// **'Supprimer l\'association'** + String get paiementDeleteStore; + + /// No description provided for @paiementDeleteStoreDescription. + /// + /// In fr, this message translates to: + /// **'Voulez-vous vraiment supprimer cette association ?'** + String get paiementDeleteStoreDescription; + + /// No description provided for @paiementDeleteStoreError. + /// + /// In fr, this message translates to: + /// **'Impossible de supprimer l\'association'** + String get paiementDeleteStoreError; + + /// No description provided for @paiementStoreDeleted. + /// + /// In fr, this message translates to: + /// **'Association supprimée'** + String get paiementStoreDeleted; + + /// No description provided for @paiementAddThisDevice. + /// + /// In fr, this message translates to: + /// **'Ajouter cet appareil'** + String get paiementAddThisDevice; + + /// No description provided for @paiementThisDevice. + /// + /// In fr, this message translates to: + /// **'(cet appareil)'** + String get paiementThisDevice; + + /// No description provided for @paiementCancelled. + /// + /// In fr, this message translates to: + /// **'Annulé'** + String get paiementCancelled; + + /// No description provided for @paiementThe. + /// + /// In fr, this message translates to: + /// **'Le'** + String get paiementThe; + + /// No description provided for @paiementOf. + /// + /// In fr, this message translates to: + /// **'de'** + String get paiementOf; + + /// No description provided for @paiementRefundedThe. + /// + /// In fr, this message translates to: + /// **'Remboursé le'** + String get paiementRefundedThe; + + /// No description provided for @paiementAt. + /// + /// In fr, this message translates to: + /// **'à'** + String get paiementAt; + + /// No description provided for @paiementPleaseAcceptTOS. + /// + /// In fr, this message translates to: + /// **'Veuillez accepter les Conditions Générales d\'Utilisation.'** + String get paiementPleaseAcceptTOS; + + /// No description provided for @paiementAskDeviceActivation. + /// + /// In fr, this message translates to: + /// **'Demande d\'activation de l\'appareil'** + String get paiementAskDeviceActivation; + + /// No description provided for @paiementDeviceActivationReceived. + /// + /// In fr, this message translates to: + /// **'La demande d\'activation est prise en compte, veuilliez consulter votre boite mail pour finaliser la démarche'** + String get paiementDeviceActivationReceived; + + /// No description provided for @paiementRevokeDevice. + /// + /// In fr, this message translates to: + /// **'Révoquer l\'appareil ?'** + String get paiementRevokeDevice; + + /// No description provided for @paiementRevokeDeviceDescription. + /// + /// In fr, this message translates to: + /// **'Vous ne pourrez plus utiliser cet appareil pour les paiements'** + String get paiementRevokeDeviceDescription; + + /// No description provided for @paiementDeviceRevoked. + /// + /// In fr, this message translates to: + /// **'Appareil révoqué'** + String get paiementDeviceRevoked; + + /// No description provided for @paiementDeviceRevokingError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de la révocation de l\'appareil'** + String get paiementDeviceRevokingError; + + /// No description provided for @paiementPleaseAcceptPopup. + /// + /// In fr, this message translates to: + /// **'Veuillez autoriser les popups'** + String get paiementPleaseAcceptPopup; + + /// No description provided for @paiementProceedSuccessfully. + /// + /// In fr, this message translates to: + /// **'Paiement effectué avec succès'** + String get paiementProceedSuccessfully; + + /// No description provided for @paiementCancelledTransaction. + /// + /// In fr, this message translates to: + /// **'Paiement annulé'** + String get paiementCancelledTransaction; + + /// No description provided for @paiementPleaseEnterMinAmount. + /// + /// In fr, this message translates to: + /// **'Veuillez entrer un montant supérieur à 1'** + String get paiementPleaseEnterMinAmount; + + /// No description provided for @paiementMaxAmount. + /// + /// In fr, this message translates to: + /// **'Le montant maximum de votre portefeuille est de'** + String get paiementMaxAmount; + + /// No description provided for @paiementPayWithHA. + /// + /// In fr, this message translates to: + /// **'Payer avec HelloAsso'** + String get paiementPayWithHA; + + /// No description provided for @paiementBalanceAfterTopUp. + /// + /// In fr, this message translates to: + /// **'Solde après recharge :'** + String get paiementBalanceAfterTopUp; + + /// No description provided for @paiementPersonalBalance. + /// + /// In fr, this message translates to: + /// **'Solde personnel'** + String get paiementPersonalBalance; + + /// No description provided for @paiementDevices. + /// + /// In fr, this message translates to: + /// **'Appareils'** + String get paiementDevices; + + /// No description provided for @paiementPay. + /// + /// In fr, this message translates to: + /// **'Payer'** + String get paiementPay; + + /// No description provided for @paiementDeviceNotRegistered. + /// + /// In fr, this message translates to: + /// **'Appareil non enregistré'** + String get paiementDeviceNotRegistered; + + /// No description provided for @paiementDeviceNotRegisteredDescription. + /// + /// In fr, this message translates to: + /// **'Votre appareil n\'est pas encore enregistré. \nPour l\'enregistrer, veuillez vous rendre sur la page des appareils.'** + String get paiementDeviceNotRegisteredDescription; + + /// No description provided for @paiementAccessPage. + /// + /// In fr, this message translates to: + /// **'Accéder à la page'** + String get paiementAccessPage; + + /// No description provided for @paiementDeviceNotActivated. + /// + /// In fr, this message translates to: + /// **'Appareil non activé'** + String get paiementDeviceNotActivated; + + /// No description provided for @paiementDeviceNotActivatedDescription. + /// + /// In fr, this message translates to: + /// **'Votre appareil n\'est pas encore activé. \nPour l\'activer, veuillez vous rendre sur la page des appareils.'** + String get paiementDeviceNotActivatedDescription; + + /// No description provided for @paiementReactivateRevokedDeviceDescription. + /// + /// In fr, this message translates to: + /// **'Votre appareil a été révoqué. \nPour le réactiver, veuillez vous rendre sur la page des appareils.'** + String get paiementReactivateRevokedDeviceDescription; + + /// No description provided for @paiementDeviceRecoveryError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de la récupération de l\'appareil'** + String get paiementDeviceRecoveryError; + + /// No description provided for @paiementStats. + /// + /// In fr, this message translates to: + /// **'Stats'** + String get paiementStats; + + /// No description provided for @paimentTopUpAction. + /// + /// In fr, this message translates to: + /// **'Recharger'** + String get paimentTopUpAction; + + /// No description provided for @paiementGetBalanceError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de la récupération du solde : '** + String get paiementGetBalanceError; + + /// No description provided for @paiementLastTransactions. + /// + /// In fr, this message translates to: + /// **'Dernières transactions'** + String get paiementLastTransactions; + + /// No description provided for @paiementGetTransactionsError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de la récupération des transactions : '** + String get paiementGetTransactionsError; + + /// No description provided for @paiementStoreBalance. + /// + /// In fr, this message translates to: + /// **'Solde associatif'** + String get paiementStoreBalance; + + /// No description provided for @paiementScan. + /// + /// In fr, this message translates to: + /// **'Scanner'** + String get paiementScan; + + /// No description provided for @paiementManagement. + /// + /// In fr, this message translates to: + /// **'Gestion'** + String get paiementManagement; + + /// No description provided for @paiementHistory. + /// + /// In fr, this message translates to: + /// **'Historique'** + String get paiementHistory; + + /// No description provided for @paiementHandOver. + /// + /// In fr, this message translates to: + /// **'Passation'** + String get paiementHandOver; + + /// No description provided for @paiementStores. + /// + /// In fr, this message translates to: + /// **'Associations'** + String get paiementStores; + + /// No description provided for @paiementAdmin. + /// + /// In fr, this message translates to: + /// **'Administrateur'** + String get paiementAdmin; + + /// No description provided for @paiementSuccededTransaction. + /// + /// In fr, this message translates to: + /// **'Paiement réussi'** + String get paiementSuccededTransaction; + + /// No description provided for @paiementNewCGU. + /// + /// In fr, this message translates to: + /// **'Nouvelles Conditions Générales d\'Utilisation'** + String get paiementNewCGU; + + /// No description provided for @paiementDecline. + /// + /// In fr, this message translates to: + /// **'Refuser'** + String get paiementDecline; + + /// No description provided for @paiementAccept. + /// + /// In fr, this message translates to: + /// **'Accepter'** + String get paiementAccept; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 26781b7938..c925907e4f 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -3422,4 +3422,178 @@ class AppLocalizationsEn extends AppLocalizations { @override String get modulePayment => 'Payment'; + + @override + String get paiementTopUp => 'Recharge'; + + @override + String get paiementStoreManagement => 'Gestion des associations'; + + @override + String get paiementDeleteStore => 'Supprimer l\'association'; + + @override + String get paiementDeleteStoreDescription => + 'Voulez-vous vraiment supprimer cette association ?'; + + @override + String get paiementDeleteStoreError => + 'Impossible de supprimer l\'association'; + + @override + String get paiementStoreDeleted => 'Association supprimée'; + + @override + String get paiementAddThisDevice => 'Ajouter cet appareil'; + + @override + String get paiementThisDevice => '(cet appareil)'; + + @override + String get paiementCancelled => 'Annulé'; + + @override + String get paiementThe => 'Le'; + + @override + String get paiementOf => 'de'; + + @override + String get paiementRefundedThe => 'Remboursé le'; + + @override + String get paiementAt => 'à'; + + @override + String get paiementPleaseAcceptTOS => + 'Veuillez accepter les Conditions Générales d\'Utilisation.'; + + @override + String get paiementAskDeviceActivation => + 'Demande d\'activation de l\'appareil'; + + @override + String get paiementDeviceActivationReceived => + 'La demande d\'activation est prise en compte, veuilliez consulter votre boite mail pour finaliser la démarche'; + + @override + String get paiementRevokeDevice => 'Révoquer l\'appareil ?'; + + @override + String get paiementRevokeDeviceDescription => + 'Vous ne pourrez plus utiliser cet appareil pour les paiements'; + + @override + String get paiementDeviceRevoked => 'Appareil révoqué'; + + @override + String get paiementDeviceRevokingError => + 'Erreur lors de la révocation de l\'appareil'; + + @override + String get paiementPleaseAcceptPopup => 'Veuillez autoriser les popups'; + + @override + String get paiementProceedSuccessfully => 'Paiement effectué avec succès'; + + @override + String get paiementCancelledTransaction => 'Paiement annulé'; + + @override + String get paiementPleaseEnterMinAmount => + 'Veuillez entrer un montant supérieur à 1'; + + @override + String get paiementMaxAmount => + 'Le montant maximum de votre portefeuille est de'; + + @override + String get paiementPayWithHA => 'Payer avec HelloAsso'; + + @override + String get paiementBalanceAfterTopUp => 'Solde après recharge :'; + + @override + String get paiementPersonalBalance => 'Solde personnel'; + + @override + String get paiementDevices => 'Appareils'; + + @override + String get paiementPay => 'Payer'; + + @override + String get paiementDeviceNotRegistered => 'Appareil non enregistré'; + + @override + String get paiementDeviceNotRegisteredDescription => + 'Votre appareil n\'est pas encore enregistré. \nPour l\'enregistrer, veuillez vous rendre sur la page des appareils.'; + + @override + String get paiementAccessPage => 'Accéder à la page'; + + @override + String get paiementDeviceNotActivated => 'Appareil non activé'; + + @override + String get paiementDeviceNotActivatedDescription => + 'Votre appareil n\'est pas encore activé. \nPour l\'activer, veuillez vous rendre sur la page des appareils.'; + + @override + String get paiementReactivateRevokedDeviceDescription => + 'Votre appareil a été révoqué. \nPour le réactiver, veuillez vous rendre sur la page des appareils.'; + + @override + String get paiementDeviceRecoveryError => + 'Erreur lors de la récupération de l\'appareil'; + + @override + String get paiementStats => 'Stats'; + + @override + String get paimentTopUpAction => 'Recharger'; + + @override + String get paiementGetBalanceError => + 'Erreur lors de la récupération du solde : '; + + @override + String get paiementLastTransactions => 'Dernières transactions'; + + @override + String get paiementGetTransactionsError => + 'Erreur lors de la récupération des transactions : '; + + @override + String get paiementStoreBalance => 'Solde associatif'; + + @override + String get paiementScan => 'Scanner'; + + @override + String get paiementManagement => 'Gestion'; + + @override + String get paiementHistory => 'Historique'; + + @override + String get paiementHandOver => 'Passation'; + + @override + String get paiementStores => 'Associations'; + + @override + String get paiementAdmin => 'Administrateur'; + + @override + String get paiementSuccededTransaction => 'Paiement réussi'; + + @override + String get paiementNewCGU => 'Nouvelles Conditions Générales d\'Utilisation'; + + @override + String get paiementDecline => 'Refuser'; + + @override + String get paiementAccept => 'Accepter'; } diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 12c4a0636e..4d4d86d178 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -3448,4 +3448,178 @@ class AppLocalizationsFr extends AppLocalizations { @override String get modulePayment => 'Paiement'; + + @override + String get paiementTopUp => 'Recharge'; + + @override + String get paiementStoreManagement => 'Gestion des associations'; + + @override + String get paiementDeleteStore => 'Supprimer l\'association'; + + @override + String get paiementDeleteStoreDescription => + 'Voulez-vous vraiment supprimer cette association ?'; + + @override + String get paiementDeleteStoreError => + 'Impossible de supprimer l\'association'; + + @override + String get paiementStoreDeleted => 'Association supprimée'; + + @override + String get paiementAddThisDevice => 'Ajouter cet appareil'; + + @override + String get paiementThisDevice => '(cet appareil)'; + + @override + String get paiementCancelled => 'Annulé'; + + @override + String get paiementThe => 'Le'; + + @override + String get paiementOf => 'de'; + + @override + String get paiementRefundedThe => 'Remboursé le'; + + @override + String get paiementAt => 'à'; + + @override + String get paiementPleaseAcceptTOS => + 'Veuillez accepter les Conditions Générales d\'Utilisation.'; + + @override + String get paiementAskDeviceActivation => + 'Demande d\'activation de l\'appareil'; + + @override + String get paiementDeviceActivationReceived => + 'La demande d\'activation est prise en compte, veuilliez consulter votre boite mail pour finaliser la démarche'; + + @override + String get paiementRevokeDevice => 'Révoquer l\'appareil ?'; + + @override + String get paiementRevokeDeviceDescription => + 'Vous ne pourrez plus utiliser cet appareil pour les paiements'; + + @override + String get paiementDeviceRevoked => 'Appareil révoqué'; + + @override + String get paiementDeviceRevokingError => + 'Erreur lors de la révocation de l\'appareil'; + + @override + String get paiementPleaseAcceptPopup => 'Veuillez autoriser les popups'; + + @override + String get paiementProceedSuccessfully => 'Paiement effectué avec succès'; + + @override + String get paiementCancelledTransaction => 'Paiement annulé'; + + @override + String get paiementPleaseEnterMinAmount => + 'Veuillez entrer un montant supérieur à 1'; + + @override + String get paiementMaxAmount => + 'Le montant maximum de votre portefeuille est de'; + + @override + String get paiementPayWithHA => 'Payer avec HelloAsso'; + + @override + String get paiementBalanceAfterTopUp => 'Solde après recharge :'; + + @override + String get paiementPersonalBalance => 'Solde personnel'; + + @override + String get paiementDevices => 'Appareils'; + + @override + String get paiementPay => 'Payer'; + + @override + String get paiementDeviceNotRegistered => 'Appareil non enregistré'; + + @override + String get paiementDeviceNotRegisteredDescription => + 'Votre appareil n\'est pas encore enregistré. \nPour l\'enregistrer, veuillez vous rendre sur la page des appareils.'; + + @override + String get paiementAccessPage => 'Accéder à la page'; + + @override + String get paiementDeviceNotActivated => 'Appareil non activé'; + + @override + String get paiementDeviceNotActivatedDescription => + 'Votre appareil n\'est pas encore activé. \nPour l\'activer, veuillez vous rendre sur la page des appareils.'; + + @override + String get paiementReactivateRevokedDeviceDescription => + 'Votre appareil a été révoqué. \nPour le réactiver, veuillez vous rendre sur la page des appareils.'; + + @override + String get paiementDeviceRecoveryError => + 'Erreur lors de la récupération de l\'appareil'; + + @override + String get paiementStats => 'Stats'; + + @override + String get paimentTopUpAction => 'Recharger'; + + @override + String get paiementGetBalanceError => + 'Erreur lors de la récupération du solde : '; + + @override + String get paiementLastTransactions => 'Dernières transactions'; + + @override + String get paiementGetTransactionsError => + 'Erreur lors de la récupération des transactions : '; + + @override + String get paiementStoreBalance => 'Solde associatif'; + + @override + String get paiementScan => 'Scanner'; + + @override + String get paiementManagement => 'Gestion'; + + @override + String get paiementHistory => 'Historique'; + + @override + String get paiementHandOver => 'Passation'; + + @override + String get paiementStores => 'Associations'; + + @override + String get paiementAdmin => 'Administrateur'; + + @override + String get paiementSuccededTransaction => 'Paiement réussi'; + + @override + String get paiementNewCGU => 'Nouvelles Conditions Générales d\'Utilisation'; + + @override + String get paiementDecline => 'Refuser'; + + @override + String get paiementAccept => 'Accepter'; } diff --git a/lib/paiement/ui/components/transaction_card.dart b/lib/paiement/ui/components/transaction_card.dart index d3f2321f47..6ff9b4db93 100644 --- a/lib/paiement/ui/components/transaction_card.dart +++ b/lib/paiement/ui/components/transaction_card.dart @@ -2,6 +2,7 @@ import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:intl/intl.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/paiement/class/history.dart'; import 'package:titan/paiement/tools/functions.dart'; @@ -41,7 +42,7 @@ class TransactionCard extends StatelessWidget { final transactionName = transaction.type != HistoryType.transfer ? transaction.otherWalletName - : "Recharge"; + : AppLocalizations.of(context)!.paiementTopUp; final colors = getTransactionColors(transaction); @@ -106,7 +107,7 @@ class TransactionCard extends StatelessWidget { borderRadius: BorderRadius.circular(5), ), child: Text( - "Annulé", + AppLocalizations.of(context)!.paiementCancelled, style: TextStyle( color: const Color.fromARGB(255, 204, 70, 25), fontSize: 12, @@ -118,7 +119,7 @@ class TransactionCard extends StatelessWidget { ), if (transaction.refund == null) const SizedBox(height: 5), Text( - "Le ${DateFormat("EEE dd MMMM yyyy à HH:mm", "fr_FR").format(transaction.creation)}", + "${AppLocalizations.of(context)!.paiementThe} ${DateFormat("EEE dd MMMM yyyy ${AppLocalizations.of(context)!.paiementAt} HH:mm", "fr_FR").format(transaction.creation)}", style: const TextStyle( color: Color(0xff204550), fontSize: 12, @@ -126,7 +127,7 @@ class TransactionCard extends StatelessWidget { ), if (transaction.refund != null) Text( - "Remboursé le ${DateFormat("EEE dd MMMM yyyy à HH:mm", "fr_FR").format(transaction.refund!.creation)} de ${formatter.format(transaction.refund!.total / 100)} €", + "${AppLocalizations.of(context)!.paiementRefundedThe} ${DateFormat("EEE dd MMMM yyyy ${AppLocalizations.of(context)!.paiementAt} HH:mm", "fr_FR").format(transaction.refund!.creation)} ${AppLocalizations.of(context)!.paiementOf} ${formatter.format(transaction.refund!.total / 100)} €", style: const TextStyle( color: Color.fromARGB(255, 16, 46, 55), fontSize: 9, diff --git a/lib/paiement/ui/pages/admin_page/admin_page.dart b/lib/paiement/ui/pages/admin_page/admin_page.dart index dcf9e1a91c..bdb566a73d 100644 --- a/lib/paiement/ui/pages/admin_page/admin_page.dart +++ b/lib/paiement/ui/pages/admin_page/admin_page.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/paiement/providers/selected_structure_provider.dart'; import 'package:titan/paiement/providers/stores_list_provider.dart'; import 'package:titan/paiement/ui/pages/admin_page/add_store_card.dart'; @@ -26,7 +27,7 @@ class AdminPage extends ConsumerWidget { children: [ const SizedBox(height: 10), AlignLeftText( - "Gestion des associations ${structure.name}", + "${AppLocalizations.of(context)!} ${structure.name}", color: Colors.grey, fontSize: 20, fontWeight: FontWeight.bold, diff --git a/lib/paiement/ui/pages/admin_page/admin_store_card.dart b/lib/paiement/ui/pages/admin_page/admin_store_card.dart index b857f06412..b0bbe20e4f 100644 --- a/lib/paiement/ui/pages/admin_page/admin_store_card.dart +++ b/lib/paiement/ui/pages/admin_page/admin_store_card.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/paiement/class/store.dart'; import 'package:titan/paiement/providers/store_provider.dart'; import 'package:titan/paiement/providers/stores_list_provider.dart'; @@ -77,23 +78,30 @@ class AdminStoreCard extends ConsumerWidget { await showDialog( context: context, builder: (context) => CustomDialogBox( - title: "Supprimer l'association", - descriptions: - "Voulez-vous vraiment supprimer cette association ?", + title: AppLocalizations.of(context)!.paiementDeleteStore, + descriptions: AppLocalizations.of( + context, + )!.paiementDeleteStoreDescription, onYes: () { tokenExpireWrapper(ref, () async { + final storeDeletedMsg = AppLocalizations.of( + context, + )!.paiementStoreDeleted; + final storeDeleteErrorMsg = AppLocalizations.of( + context, + )!.paiementDeleteStoreError; final value = await storeListNotifier.deleteStore( store, ); if (value) { displayToastWithContext( TypeMsg.msg, - "Association supprimée", + storeDeletedMsg, ); } else { displayToastWithContext( TypeMsg.error, - "Impossible de supprimer l'association", + storeDeleteErrorMsg, ); } }); diff --git a/lib/paiement/ui/pages/devices_page/add_device_button.dart b/lib/paiement/ui/pages/devices_page/add_device_button.dart index 193b0a7ea9..d462e30c40 100644 --- a/lib/paiement/ui/pages/devices_page/add_device_button.dart +++ b/lib/paiement/ui/pages/devices_page/add_device_button.dart @@ -2,6 +2,7 @@ import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; class AddDeviceButton extends StatelessWidget { @@ -60,7 +61,7 @@ class AddDeviceButton extends StatelessWidget { ), Spacer(), Text( - "Ajouter cet appareil", + AppLocalizations.of(context)!.paiementAddThisDevice, style: TextStyle( color: Color(0xff204550), fontSize: 20, diff --git a/lib/paiement/ui/pages/devices_page/device_item.dart b/lib/paiement/ui/pages/devices_page/device_item.dart index 1a07051b74..8d844aa0ee 100644 --- a/lib/paiement/ui/pages/devices_page/device_item.dart +++ b/lib/paiement/ui/pages/devices_page/device_item.dart @@ -3,6 +3,7 @@ import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:heroicons/heroicons.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/paiement/class/wallet_device.dart'; import 'package:titan/paiement/tools/functions.dart'; @@ -44,8 +45,8 @@ class DeviceItem extends ConsumerWidget { ), ), if (isActual) - const Text( - '(cet appareil)', + Text( + AppLocalizations.of(context)!.paiementThisDevice, style: TextStyle( color: Color(0xff204550), fontSize: 15, diff --git a/lib/paiement/ui/pages/devices_page/devices_page.dart b/lib/paiement/ui/pages/devices_page/devices_page.dart index 8d282bab8d..a3b26c0bc2 100644 --- a/lib/paiement/ui/pages/devices_page/devices_page.dart +++ b/lib/paiement/ui/pages/devices_page/devices_page.dart @@ -5,6 +5,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/paiement/class/create_device.dart'; import 'package:titan/paiement/class/wallet_device.dart'; import 'package:titan/paiement/providers/device_list_provider.dart'; @@ -97,7 +98,9 @@ class DevicesPage extends HookConsumerWidget { if (!hasAcceptedToS) { displayToastWithContext( TypeMsg.error, - "Veuillez accepter les Conditions Générales d'Utilisation.", + AppLocalizations.of( + context, + )!.paiementPleaseAcceptTOS, ); return; } @@ -123,10 +126,12 @@ class DevicesPage extends HookConsumerWidget { context: context, builder: (context) { return DeviceDialogBox( - title: - 'Demande d\'activation de l\'appareil', - descriptions: - "La demande d'activation est prise en compte, veuilliez consulter votre boite mail pour finaliser la démarche", + title: AppLocalizations.of( + context, + )!.paiementAskDeviceActivation, + descriptions: AppLocalizations.of( + context, + )!.paiementDeviceActivationReceived, buttonText: "Ok", onClick: () { Navigator.of(context).pop(); @@ -146,7 +151,9 @@ class DevicesPage extends HookConsumerWidget { if (!hasAcceptedToS) { displayToastWithContext( TypeMsg.error, - "Veuillez accepter les Conditions Générales d'Utilisation.", + AppLocalizations.of( + context, + )!.paiementPleaseAcceptTOS, ); return; } @@ -154,11 +161,22 @@ class DevicesPage extends HookConsumerWidget { context: context, builder: (context) { return CustomDialogBox( - title: "Révoquer l'appareil ?", - descriptions: - "Vous ne pourrez plus utiliser cet appareil pour les paiements", + title: AppLocalizations.of( + context, + )!.paiementRevokeDevice, + descriptions: AppLocalizations.of( + context, + )!.paiementRevokeDeviceDescription, onYes: () async { tokenExpireWrapper(ref, () async { + final deviceRevokedMsg = + AppLocalizations.of( + context, + )!.paiementDeviceRevoked; + final deviceRevokingErrorMsg = + AppLocalizations.of( + context, + )!.paiementDeviceRevokingError; final value = await devicesNotifier .revokeDevice( device.copyWith( @@ -168,7 +186,7 @@ class DevicesPage extends HookConsumerWidget { if (value) { displayToastWithContext( TypeMsg.msg, - "Appareil révoqué", + deviceRevokedMsg, ); final savedId = await keyService .getKeyId(); @@ -178,7 +196,7 @@ class DevicesPage extends HookConsumerWidget { } else { displayToastWithContext( TypeMsg.error, - "Erreur lors de la révocation de l'appareil", + deviceRevokingErrorMsg, ); } }); diff --git a/lib/paiement/ui/pages/fund_page/confirm_button.dart b/lib/paiement/ui/pages/fund_page/confirm_button.dart index b8800a7b02..48f95fdfd9 100644 --- a/lib/paiement/ui/pages/fund_page/confirm_button.dart +++ b/lib/paiement/ui/pages/fund_page/confirm_button.dart @@ -4,6 +4,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/paiement/class/init_info.dart'; import 'package:titan/paiement/providers/fund_amount_provider.dart'; import 'package:titan/paiement/providers/funding_url_provider.dart'; @@ -69,7 +70,10 @@ class ConfirmFundButton extends ConsumerWidget { as html.WindowBase?; if (popupWin == null) { - displayToastWithContext(TypeMsg.error, "Veuillez autoriser les popups"); + displayToastWithContext( + TypeMsg.error, + AppLocalizations.of(context)!.paiementPleaseAcceptPopup, + ); return; } @@ -89,11 +93,17 @@ class ConfirmFundButton extends ConsumerWidget { final receivedUri = Uri.parse(data); final code = receivedUri.queryParameters["code"]; if (code == "succeeded") { - displayToastWithContext(TypeMsg.msg, "Paiement effectué avec succès"); + displayToastWithContext( + TypeMsg.msg, + AppLocalizations.of(context)!.paiementProceedSuccessfully, + ); myWalletNotifier.getMyWallet(); myHistoryNotifier.getHistory(); } else { - displayToastWithContext(TypeMsg.error, "Paiement annulé"); + displayToastWithContext( + TypeMsg.error, + AppLocalizations.of(context)!.paiementCancelledTransaction, + ); } popupWin.close(); Navigator.pop(context, code); @@ -111,14 +121,14 @@ class ConfirmFundButton extends ConsumerWidget { if (!minValidFundAmount) { displayToastWithContext( TypeMsg.error, - "Veuillez entrer un montant supérieur à 1€", + AppLocalizations.of(context)!.paiementPleaseEnterMinAmount, ); return; } if (!maxValidFundAmount) { displayToastWithContext( TypeMsg.error, - "Le montant maximum de votre portefeuille est de ${maxBalanceAmount.toStringAsFixed(2)}€", + "${AppLocalizations.of(context)!.paiementMaxAmount} ${maxBalanceAmount.toStringAsFixed(2)}€", ); return; } @@ -181,7 +191,7 @@ class ConfirmFundButton extends ConsumerWidget { ), ), Text( - "Payer avec HelloAsso", + AppLocalizations.of(context)!.paiementPayWithHA, style: TextStyle( color: (minValidFundAmount && maxValidFundAmount) ? const Color(0xff2e2f5e) diff --git a/lib/paiement/ui/pages/fund_page/fund_page.dart b/lib/paiement/ui/pages/fund_page/fund_page.dart index 93a04f4a14..3cec59a8f2 100644 --- a/lib/paiement/ui/pages/fund_page/fund_page.dart +++ b/lib/paiement/ui/pages/fund_page/fund_page.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:intl/intl.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/paiement/providers/fund_amount_provider.dart'; import 'package:titan/paiement/providers/my_wallet_provider.dart'; import 'package:titan/paiement/providers/tos_provider.dart'; @@ -48,7 +49,7 @@ class FundPage extends ConsumerWidget { children: [ const SizedBox(height: 20), Text( - 'Recharge', + AppLocalizations.of(context)!.paiementTopUp, style: const TextStyle( color: Colors.white, fontSize: 20, @@ -57,7 +58,7 @@ class FundPage extends ConsumerWidget { ), const SizedBox(height: 5), Text( - 'Solde après recharge : ${formatter.format(amountToAdd + currentAmount)} € (max: ${formatter.format(maxBalanceAmount)} €)', + '${AppLocalizations.of(context)!.paiementBalanceAfterTopUp} ${formatter.format(amountToAdd + currentAmount)} € (max: ${formatter.format(maxBalanceAmount)} €)', style: const TextStyle(color: Colors.white, fontSize: 15), ), Expanded( diff --git a/lib/paiement/ui/pages/main_page/account_card/last_transactions.dart b/lib/paiement/ui/pages/main_page/account_card/last_transactions.dart index da37b65679..c361dd963b 100644 --- a/lib/paiement/ui/pages/main_page/account_card/last_transactions.dart +++ b/lib/paiement/ui/pages/main_page/account_card/last_transactions.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/paiement/class/history.dart'; import 'package:titan/paiement/providers/my_history_provider.dart'; import 'package:titan/paiement/ui/pages/main_page/account_card/day_divider.dart'; @@ -23,9 +24,9 @@ class LastTransactions extends ConsumerWidget { Container( padding: const EdgeInsets.symmetric(horizontal: 30), alignment: Alignment.centerLeft, - child: const Text( - "Dernières transactions", - style: TextStyle( + child: Text( + AppLocalizations.of(context)!.paiementLastTransactions, + style: const TextStyle( color: Color(0xff204550), fontSize: 20, fontWeight: FontWeight.bold, @@ -79,7 +80,7 @@ class LastTransactions extends ConsumerWidget { }, errorBuilder: (error, stack) => Center( child: Text( - "Erreur lors de la récupération des transactions : $error", + "${AppLocalizations.of(context)!.paiementGetTransactionsError} : $error", style: TextStyle(color: Colors.red), ), ), diff --git a/lib/paiement/ui/pages/main_page/main_page.dart b/lib/paiement/ui/pages/main_page/main_page.dart index 82fa2559e3..41b705ffc2 100644 --- a/lib/paiement/ui/pages/main_page/main_page.dart +++ b/lib/paiement/ui/pages/main_page/main_page.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/paiement/providers/has_accepted_tos_provider.dart'; import 'package:titan/paiement/providers/my_wallet_provider.dart'; import 'package:titan/paiement/providers/tos_provider.dart'; @@ -54,7 +55,11 @@ class PaymentMainPage extends HookConsumerWidget { !_handledKeys.contains("code")) { _handledKeys.add("code"); WidgetsBinding.instance.addPostFrameCallback((_) { - displayToast(context, TypeMsg.msg, "Paiement réussi"); + displayToast( + context, + TypeMsg.msg, + AppLocalizations.of(context)!.paiementSuccededTransaction, + ); }); } await mySellersNotifier.getMyStores(); @@ -101,7 +106,7 @@ class PaymentMainPage extends HookConsumerWidget { orElse: () => '', data: (tos) => tos.tosContent, ), - title: "Nouvelles Conditions Générales d'Utilisation", + title: AppLocalizations.of(context)!.paiementNewCGU, onYes: () { tos.maybeWhen( orElse: () {}, diff --git a/lib/paiement/ui/pages/main_page/seller_card/store_admin_card.dart b/lib/paiement/ui/pages/main_page/seller_card/store_admin_card.dart index b0b862587b..ca467b3dca 100644 --- a/lib/paiement/ui/pages/main_page/seller_card/store_admin_card.dart +++ b/lib/paiement/ui/pages/main_page/seller_card/store_admin_card.dart @@ -2,6 +2,7 @@ import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/paiement/providers/my_structures_provider.dart'; import 'package:titan/paiement/providers/selected_structure_provider.dart'; import 'package:titan/paiement/router.dart'; @@ -33,7 +34,7 @@ class StoreAdminCard extends ConsumerWidget { SizedBox(width: 15), Expanded( child: AutoSizeText( - "Gestion des assocations ${structure.name}", + "${AppLocalizations.of(context)!.paiementStoreManagement} ${structure.name}", maxLines: 2, style: TextStyle( color: Color.fromARGB(255, 0, 29, 29), diff --git a/lib/paiement/ui/pages/main_page/seller_card/store_card.dart b/lib/paiement/ui/pages/main_page/seller_card/store_card.dart index 8d3a7b7e8e..d3f93d3b63 100644 --- a/lib/paiement/ui/pages/main_page/seller_card/store_card.dart +++ b/lib/paiement/ui/pages/main_page/seller_card/store_card.dart @@ -2,6 +2,7 @@ import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/paiement/providers/barcode_provider.dart'; import 'package:titan/paiement/providers/ongoing_transaction.dart'; import 'package:titan/paiement/providers/selected_store_provider.dart'; @@ -36,13 +37,13 @@ class StoreCard extends HookConsumerWidget { Color.fromARGB(255, 0, 68, 68), Color.fromARGB(255, 0, 29, 29), ], - title: 'Solde associatif', + title: AppLocalizations.of(context)!.paiementStoreBalance, actionButtons: [ if (store.canBank) MainCardButton( colors: buttonGradient, icon: HeroIcons.viewfinderCircle, - title: "Scanner", + title: AppLocalizations.of(context)!.paiementScan, onPressed: () async { showModalBottomSheet( context: context, @@ -65,7 +66,7 @@ class StoreCard extends HookConsumerWidget { // storeAdminListNotifier.getStoreAdminList(store.id); QR.to(PaymentRouter.root + PaymentRouter.storeAdmin); }, - title: 'Gestion', + title: AppLocalizations.of(context)!.paiementManagement, ), if (store.canSeeHistory) MainCardButton( @@ -74,7 +75,7 @@ class StoreCard extends HookConsumerWidget { onPressed: () async { QR.to(PaymentRouter.root + PaymentRouter.storeStats); }, - title: 'Historique', + title: AppLocalizations.of(context)!.paiementHistory, ), if (store.structure.managerUser.id == me.id) MainCardButton( @@ -83,7 +84,7 @@ class StoreCard extends HookConsumerWidget { onPressed: () async { QR.to(PaymentRouter.root + PaymentRouter.transferStructure); }, - title: 'Passation', + title: AppLocalizations.of(context)!.paiementHandOver, ), ], child: SizedBox.expand( diff --git a/lib/paiement/ui/pages/main_page/seller_card/store_list.dart b/lib/paiement/ui/pages/main_page/seller_card/store_list.dart index 712d1758d1..f3dbb2ea4a 100644 --- a/lib/paiement/ui/pages/main_page/seller_card/store_list.dart +++ b/lib/paiement/ui/pages/main_page/seller_card/store_list.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/paiement/class/user_store.dart'; import 'package:titan/paiement/providers/is_payment_admin.dart'; import 'package:titan/paiement/providers/my_stores_provider.dart'; @@ -25,8 +26,8 @@ class StoreList extends ConsumerWidget { Container( padding: const EdgeInsets.symmetric(horizontal: 30), alignment: Alignment.centerLeft, - child: const Text( - "Associations", + child: Text( + AppLocalizations.of(context)!.paiementStores, style: TextStyle( color: Color.fromARGB(255, 0, 29, 29), fontSize: 20, @@ -49,7 +50,9 @@ class StoreList extends ConsumerWidget { return Column( children: [ if (isAdmin) ...[ - const StoreDivider(name: "Administrateur"), + StoreDivider( + name: AppLocalizations.of(context)!.paiementAdmin, + ), const StoreAdminCard(), ], ...sortedByMembership.map((membership, stores) { diff --git a/lib/paiement/ui/pages/main_page/tos_dialog.dart b/lib/paiement/ui/pages/main_page/tos_dialog.dart index 53a2150afb..706d94697c 100644 --- a/lib/paiement/ui/pages/main_page/tos_dialog.dart +++ b/lib/paiement/ui/pages/main_page/tos_dialog.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; @@ -82,8 +83,8 @@ class TOSDialogBox extends StatelessWidget { ? Navigator.of(context).pop() : onNo?.call(); }, - child: const Text( - "Refuser", + child: Text( + AppLocalizations.of(context)!.paiementDecline, style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, @@ -99,8 +100,8 @@ class TOSDialogBox extends StatelessWidget { await onYes(); }, builder: (child) => child, - child: const Text( - "Accepter", + child: Text( + AppLocalizations.of(context)!.paiementAccept, style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, From 6ed84d3abc584fe629a790708a2511e09db9b8dd Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Fri, 4 Jul 2025 15:35:29 +0200 Subject: [PATCH 059/473] End paiement --- lib/l10n/app_fr.arb | 73 ++- lib/l10n/app_localizations.dart | 426 ++++++++++++++++++ lib/l10n/app_localizations_en.dart | 234 ++++++++++ lib/l10n/app_localizations_fr.dart | 234 ++++++++++ .../ui/pages/pay_page/confirm_button.dart | 45 +- lib/paiement/ui/pages/pay_page/pay_page.dart | 5 +- .../ui/pages/scan_page/cancel_button.dart | 3 +- .../ui/pages/scan_page/scan_page.dart | 37 +- .../ui/pages/stats_page/sum_up_chart.dart | 17 +- .../pages/stats_page/transaction_chart.dart | 5 +- .../pages/store_admin_page/search_result.dart | 36 +- .../store_admin_page/seller_right_card.dart | 64 ++- .../store_admin_page/seller_right_dialog.dart | 9 +- .../store_admin_page/store_admin_page.dart | 23 +- .../ui/pages/store_pages/add_edit_store.dart | 27 +- .../pages/store_stats_page/refund_page.dart | 9 +- .../pages/store_stats_page/summary_card.dart | 7 +- .../search_result.dart | 11 +- .../transfer_structure_page.dart | 3 +- 19 files changed, 1167 insertions(+), 101 deletions(-) diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 0c0a66de16..9cfc744e97 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -1170,5 +1170,76 @@ "paiementSuccededTransaction": "Paiement réussi", "paiementNewCGU" : "Nouvelles Conditions Générales d'Utilisation", "paiementDecline": "Refuser", - "paiementAccept": "Accepter" + "paiementAccept": "Accepter", + "paiementAmount": "Montant", + "paiementValidUntil": "Valide jusqu'à", + "paiementClose": "Fermer", + "paiementPleaseEnterValidAmount": "Veuillez entrer un montant valide", + "paiementPleaseAuthenticate": "Veuillez vous authentifier", + "paiementAthenticationRequired": "Authentification requise pour payer", + "paiementNoThanks": "Non merci", + "paiementAuthentificationFailed": "Échec de l'authentification", + "paiementPleaseAddDevice": "Veuillez ajouter cet appareil pour payer", + "paiementPayment": "Paiement", + "paiementBalanceAfterTransaction": "Solde après paiement : ", + "paiementCancel": "Annuler", + "paiementLimitedTo": "Limité à", + "paiementScanCode": "Scanner un code", + "paiementNext": "Suivant", + "paiementCancelTransaction": "Annuler la transaction", + "paiementTransactionCancelled": "Transaction annulée", + "paiementTransactionCancelledDescription": "Voulez-vous vraiment annuler la transaction de", + "paiementTransactionCancelledError": "Erreur lors de l'annulation de la transaction", + "paiementNoMembership": "Aucune adhésion", + "paiementNoMembershipDescription": "Ce produit n'est pas disponnible pour les non-adhérents. Confirmer l'encaissement ?", + "paiementQRCodeAlreadyUsed": "QR Code déjà utilisé", + "paiementCameraPermissionRequired": "Permission d'accès à la caméra requise", + "paiementCameraPerssionRequiredDescription": "Pour scanner un QR Code, vous devez autoriser l'accès à la caméra.", + "paiementSettings": "Paramètres", + "paiementReceived": "Reçu", + "paiementSpent": "Déboursé", + "paiementNoTrasactionForThisMonth": "Aucune transaction pour ce mois", + "paiementNoTransactinon": "Aucune transaction", + "paiementSellerRigths": "Droits du vendeur", + "paiementCanBank": "Peut encaisser", + "paiementCanSeeHistory": "Peut voir l'historique", + "paiementCanCancelTransaction": "Peut annuler des transactions", + "paiementCanManageSellers": "Peut gérer les vendeurs", + "paiementAddedSeller": "Vendeur ajouté", + "paiementAddingSellerError": "Erreur lors de l'ajout du vendeur", + "paiementBank": "Encaisser", + "paiementSeeHistory": "Voir l'historique", + "paiementCancelTransactions": "Annuler les transactions", + "paiementManageSellers": "Gérer les vendeurs", + "paiementStructureAdmin": "Administrateur de la structure", + "paiementRightsOf": "Droits de", + "paiementRightsUpdated": "Droits mis à jour", + "paiementRightsUpdateError": "Erreur lors de la mise à jour des droits", + "paiementDeleteSellerDescription": "Voulez-vous vraiment supprimer ce vendeur ?", + "paiementDeletedSeller": "Vendeur supprimé", + "paiementDeletingSellerError": "Erreur lors de la suppression du vendeur", + "paiementDeleteSeller": "Supprimer le vendeur", + "paiementAdd" : "Ajouter", + "paiementAddSeller": "Ajouter un vendeur", + "paiementSellerError": "Vous n'êtes pas vendeur de cette association", + "paiementSellersOf": "Les vendeurs de", + "paiementModify": "Modifier", + "paiementAStore": "une association", + "paiementStoreName": "Nom de l'association", + "paiementSuccessfullyAddedStore": "Association ajoutée avec succès", + "paiementSuccessfullyModifiedStore": "Association modifiée avec succès", + "paiementAddingStoreError": "Erreur lors de l'ajout de l'association", + "paiementModifyingStoreError": "Erreur lors de la modification de l'association", + "paiementRefund": "Remboursement", + "paiementDoneTransaction": "Transaction effectuée", + "paiementRefundAction": "Rembourser", + "paiementTotalDuringPeriod": "Total sur la période", + "paiementMean" : "Moyenne : ", + "paiementTransaction": "ransaction", + "paiementTransferStructure": "Transfert de structure", + "paiementYouAreTransferingStructureTo": "Vous êtes sur le point de transférer la structure à ", + "paiementTransferStructureDescription": "Le nouveau responsable aura accès à toutes les fonctionnalités de gestion de la structure. Vous allez recevoir un email pour valider ce transfert. Le lien ne sera actif que pendant 20 minutes. Cette action est irréversible. Êtes-vous sûr de vouloir continuer ?", + "paiementTransferStructureError": "Erreur lors du transfert de la structure", + "paiementTransferStructureSuccess": "Transfert de structure demandé avec succès", + "paiementNextAccountable": "Prochain responsable" } \ No newline at end of file diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 66fc1e6baa..0c3c49bcd2 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -7123,6 +7123,432 @@ abstract class AppLocalizations { /// In fr, this message translates to: /// **'Accepter'** String get paiementAccept; + + /// No description provided for @paiementAmount. + /// + /// In fr, this message translates to: + /// **'Montant'** + String get paiementAmount; + + /// No description provided for @paiementValidUntil. + /// + /// In fr, this message translates to: + /// **'Valide jusqu\'à'** + String get paiementValidUntil; + + /// No description provided for @paiementClose. + /// + /// In fr, this message translates to: + /// **'Fermer'** + String get paiementClose; + + /// No description provided for @paiementPleaseEnterValidAmount. + /// + /// In fr, this message translates to: + /// **'Veuillez entrer un montant valide'** + String get paiementPleaseEnterValidAmount; + + /// No description provided for @paiementPleaseAuthenticate. + /// + /// In fr, this message translates to: + /// **'Veuillez vous authentifier'** + String get paiementPleaseAuthenticate; + + /// No description provided for @paiementAthenticationRequired. + /// + /// In fr, this message translates to: + /// **'Authentification requise pour payer'** + String get paiementAthenticationRequired; + + /// No description provided for @paiementNoThanks. + /// + /// In fr, this message translates to: + /// **'Non merci'** + String get paiementNoThanks; + + /// No description provided for @paiementAuthentificationFailed. + /// + /// In fr, this message translates to: + /// **'Échec de l\'authentification'** + String get paiementAuthentificationFailed; + + /// No description provided for @paiementPleaseAddDevice. + /// + /// In fr, this message translates to: + /// **'Veuillez ajouter cet appareil pour payer'** + String get paiementPleaseAddDevice; + + /// No description provided for @paiementPayment. + /// + /// In fr, this message translates to: + /// **'Paiement'** + String get paiementPayment; + + /// No description provided for @paiementBalanceAfterTransaction. + /// + /// In fr, this message translates to: + /// **'Solde après paiement : '** + String get paiementBalanceAfterTransaction; + + /// No description provided for @paiementCancel. + /// + /// In fr, this message translates to: + /// **'Annuler'** + String get paiementCancel; + + /// No description provided for @paiementLimitedTo. + /// + /// In fr, this message translates to: + /// **'Limité à'** + String get paiementLimitedTo; + + /// No description provided for @paiementScanCode. + /// + /// In fr, this message translates to: + /// **'Scanner un code'** + String get paiementScanCode; + + /// No description provided for @paiementNext. + /// + /// In fr, this message translates to: + /// **'Suivant'** + String get paiementNext; + + /// No description provided for @paiementCancelTransaction. + /// + /// In fr, this message translates to: + /// **'Annuler la transaction'** + String get paiementCancelTransaction; + + /// No description provided for @paiementTransactionCancelled. + /// + /// In fr, this message translates to: + /// **'Transaction annulée'** + String get paiementTransactionCancelled; + + /// No description provided for @paiementTransactionCancelledDescription. + /// + /// In fr, this message translates to: + /// **'Voulez-vous vraiment annuler la transaction de'** + String get paiementTransactionCancelledDescription; + + /// No description provided for @paiementTransactionCancelledError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de l\'annulation de la transaction'** + String get paiementTransactionCancelledError; + + /// No description provided for @paiementNoMembership. + /// + /// In fr, this message translates to: + /// **'Aucune adhésion'** + String get paiementNoMembership; + + /// No description provided for @paiementNoMembershipDescription. + /// + /// In fr, this message translates to: + /// **'Ce produit n\'est pas disponnible pour les non-adhérents. Confirmer l\'encaissement ?'** + String get paiementNoMembershipDescription; + + /// No description provided for @paiementQRCodeAlreadyUsed. + /// + /// In fr, this message translates to: + /// **'QR Code déjà utilisé'** + String get paiementQRCodeAlreadyUsed; + + /// No description provided for @paiementCameraPermissionRequired. + /// + /// In fr, this message translates to: + /// **'Permission d\'accès à la caméra requise'** + String get paiementCameraPermissionRequired; + + /// No description provided for @paiementCameraPerssionRequiredDescription. + /// + /// In fr, this message translates to: + /// **'Pour scanner un QR Code, vous devez autoriser l\'accès à la caméra.'** + String get paiementCameraPerssionRequiredDescription; + + /// No description provided for @paiementSettings. + /// + /// In fr, this message translates to: + /// **'Paramètres'** + String get paiementSettings; + + /// No description provided for @paiementReceived. + /// + /// In fr, this message translates to: + /// **'Reçu'** + String get paiementReceived; + + /// No description provided for @paiementSpent. + /// + /// In fr, this message translates to: + /// **'Déboursé'** + String get paiementSpent; + + /// No description provided for @paiementNoTrasactionForThisMonth. + /// + /// In fr, this message translates to: + /// **'Aucune transaction pour ce mois'** + String get paiementNoTrasactionForThisMonth; + + /// No description provided for @paiementNoTransactinon. + /// + /// In fr, this message translates to: + /// **'Aucune transaction'** + String get paiementNoTransactinon; + + /// No description provided for @paiementSellerRigths. + /// + /// In fr, this message translates to: + /// **'Droits du vendeur'** + String get paiementSellerRigths; + + /// No description provided for @paiementCanBank. + /// + /// In fr, this message translates to: + /// **'Peut encaisser'** + String get paiementCanBank; + + /// No description provided for @paiementCanSeeHistory. + /// + /// In fr, this message translates to: + /// **'Peut voir l\'historique'** + String get paiementCanSeeHistory; + + /// No description provided for @paiementCanCancelTransaction. + /// + /// In fr, this message translates to: + /// **'Peut annuler des transactions'** + String get paiementCanCancelTransaction; + + /// No description provided for @paiementCanManageSellers. + /// + /// In fr, this message translates to: + /// **'Peut gérer les vendeurs'** + String get paiementCanManageSellers; + + /// No description provided for @paiementAddedSeller. + /// + /// In fr, this message translates to: + /// **'Vendeur ajouté'** + String get paiementAddedSeller; + + /// No description provided for @paiementAddingSellerError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de l\'ajout du vendeur'** + String get paiementAddingSellerError; + + /// No description provided for @paiementBank. + /// + /// In fr, this message translates to: + /// **'Encaisser'** + String get paiementBank; + + /// No description provided for @paiementSeeHistory. + /// + /// In fr, this message translates to: + /// **'Voir l\'historique'** + String get paiementSeeHistory; + + /// No description provided for @paiementCancelTransactions. + /// + /// In fr, this message translates to: + /// **'Annuler les transactions'** + String get paiementCancelTransactions; + + /// No description provided for @paiementManageSellers. + /// + /// In fr, this message translates to: + /// **'Gérer les vendeurs'** + String get paiementManageSellers; + + /// No description provided for @paiementStructureAdmin. + /// + /// In fr, this message translates to: + /// **'Administrateur de la structure'** + String get paiementStructureAdmin; + + /// No description provided for @paiementRightsOf. + /// + /// In fr, this message translates to: + /// **'Droits de'** + String get paiementRightsOf; + + /// No description provided for @paiementRightsUpdated. + /// + /// In fr, this message translates to: + /// **'Droits mis à jour'** + String get paiementRightsUpdated; + + /// No description provided for @paiementRightsUpdateError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de la mise à jour des droits'** + String get paiementRightsUpdateError; + + /// No description provided for @paiementDeleteSellerDescription. + /// + /// In fr, this message translates to: + /// **'Voulez-vous vraiment supprimer ce vendeur ?'** + String get paiementDeleteSellerDescription; + + /// No description provided for @paiementDeletedSeller. + /// + /// In fr, this message translates to: + /// **'Vendeur supprimé'** + String get paiementDeletedSeller; + + /// No description provided for @paiementDeletingSellerError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de la suppression du vendeur'** + String get paiementDeletingSellerError; + + /// No description provided for @paiementDeleteSeller. + /// + /// In fr, this message translates to: + /// **'Supprimer le vendeur'** + String get paiementDeleteSeller; + + /// No description provided for @paiementAdd. + /// + /// In fr, this message translates to: + /// **'Ajouter'** + String get paiementAdd; + + /// No description provided for @paiementAddSeller. + /// + /// In fr, this message translates to: + /// **'Ajouter un vendeur'** + String get paiementAddSeller; + + /// No description provided for @paiementSellerError. + /// + /// In fr, this message translates to: + /// **'Vous n\'êtes pas vendeur de cette association'** + String get paiementSellerError; + + /// No description provided for @paiementSellersOf. + /// + /// In fr, this message translates to: + /// **'Les vendeurs de'** + String get paiementSellersOf; + + /// No description provided for @paiementModify. + /// + /// In fr, this message translates to: + /// **'Modifier'** + String get paiementModify; + + /// No description provided for @paiementAStore. + /// + /// In fr, this message translates to: + /// **'une association'** + String get paiementAStore; + + /// No description provided for @paiementStoreName. + /// + /// In fr, this message translates to: + /// **'Nom de l\'association'** + String get paiementStoreName; + + /// No description provided for @paiementSuccessfullyAddedStore. + /// + /// In fr, this message translates to: + /// **'Association ajoutée avec succès'** + String get paiementSuccessfullyAddedStore; + + /// No description provided for @paiementSuccessfullyModifiedStore. + /// + /// In fr, this message translates to: + /// **'Association modifiée avec succès'** + String get paiementSuccessfullyModifiedStore; + + /// No description provided for @paiementAddingStoreError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de l\'ajout de l\'association'** + String get paiementAddingStoreError; + + /// No description provided for @paiementModifyingStoreError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de la modification de l\'association'** + String get paiementModifyingStoreError; + + /// No description provided for @paiementRefund. + /// + /// In fr, this message translates to: + /// **'Remboursement'** + String get paiementRefund; + + /// No description provided for @paiementDoneTransaction. + /// + /// In fr, this message translates to: + /// **'Transaction effectuée'** + String get paiementDoneTransaction; + + /// No description provided for @paiementRefundAction. + /// + /// In fr, this message translates to: + /// **'Rembourser'** + String get paiementRefundAction; + + /// No description provided for @paiementTotalDuringPeriod. + /// + /// In fr, this message translates to: + /// **'Total sur la période'** + String get paiementTotalDuringPeriod; + + /// No description provided for @paiementMean. + /// + /// In fr, this message translates to: + /// **'Moyenne : '** + String get paiementMean; + + /// No description provided for @paiementTransaction. + /// + /// In fr, this message translates to: + /// **'ransaction'** + String get paiementTransaction; + + /// No description provided for @paiementTransferStructure. + /// + /// In fr, this message translates to: + /// **'Transfert de structure'** + String get paiementTransferStructure; + + /// No description provided for @paiementYouAreTransferingStructureTo. + /// + /// In fr, this message translates to: + /// **'Vous êtes sur le point de transférer la structure à '** + String get paiementYouAreTransferingStructureTo; + + /// No description provided for @paiementTransferStructureDescription. + /// + /// In fr, this message translates to: + /// **'Le nouveau responsable aura accès à toutes les fonctionnalités de gestion de la structure. Vous allez recevoir un email pour valider ce transfert. Le lien ne sera actif que pendant 20 minutes. Cette action est irréversible. Êtes-vous sûr de vouloir continuer ?'** + String get paiementTransferStructureDescription; + + /// No description provided for @paiementTransferStructureError. + /// + /// In fr, this message translates to: + /// **'Erreur lors du transfert de la structure'** + String get paiementTransferStructureError; + + /// No description provided for @paiementTransferStructureSuccess. + /// + /// In fr, this message translates to: + /// **'Transfert de structure demandé avec succès'** + String get paiementTransferStructureSuccess; + + /// No description provided for @paiementNextAccountable. + /// + /// In fr, this message translates to: + /// **'Prochain responsable'** + String get paiementNextAccountable; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index c925907e4f..0f41f43003 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -3596,4 +3596,238 @@ class AppLocalizationsEn extends AppLocalizations { @override String get paiementAccept => 'Accepter'; + + @override + String get paiementAmount => 'Montant'; + + @override + String get paiementValidUntil => 'Valide jusqu\'à'; + + @override + String get paiementClose => 'Fermer'; + + @override + String get paiementPleaseEnterValidAmount => + 'Veuillez entrer un montant valide'; + + @override + String get paiementPleaseAuthenticate => 'Veuillez vous authentifier'; + + @override + String get paiementAthenticationRequired => + 'Authentification requise pour payer'; + + @override + String get paiementNoThanks => 'Non merci'; + + @override + String get paiementAuthentificationFailed => 'Échec de l\'authentification'; + + @override + String get paiementPleaseAddDevice => + 'Veuillez ajouter cet appareil pour payer'; + + @override + String get paiementPayment => 'Paiement'; + + @override + String get paiementBalanceAfterTransaction => 'Solde après paiement : '; + + @override + String get paiementCancel => 'Annuler'; + + @override + String get paiementLimitedTo => 'Limité à'; + + @override + String get paiementScanCode => 'Scanner un code'; + + @override + String get paiementNext => 'Suivant'; + + @override + String get paiementCancelTransaction => 'Annuler la transaction'; + + @override + String get paiementTransactionCancelled => 'Transaction annulée'; + + @override + String get paiementTransactionCancelledDescription => + 'Voulez-vous vraiment annuler la transaction de'; + + @override + String get paiementTransactionCancelledError => + 'Erreur lors de l\'annulation de la transaction'; + + @override + String get paiementNoMembership => 'Aucune adhésion'; + + @override + String get paiementNoMembershipDescription => + 'Ce produit n\'est pas disponnible pour les non-adhérents. Confirmer l\'encaissement ?'; + + @override + String get paiementQRCodeAlreadyUsed => 'QR Code déjà utilisé'; + + @override + String get paiementCameraPermissionRequired => + 'Permission d\'accès à la caméra requise'; + + @override + String get paiementCameraPerssionRequiredDescription => + 'Pour scanner un QR Code, vous devez autoriser l\'accès à la caméra.'; + + @override + String get paiementSettings => 'Paramètres'; + + @override + String get paiementReceived => 'Reçu'; + + @override + String get paiementSpent => 'Déboursé'; + + @override + String get paiementNoTrasactionForThisMonth => + 'Aucune transaction pour ce mois'; + + @override + String get paiementNoTransactinon => 'Aucune transaction'; + + @override + String get paiementSellerRigths => 'Droits du vendeur'; + + @override + String get paiementCanBank => 'Peut encaisser'; + + @override + String get paiementCanSeeHistory => 'Peut voir l\'historique'; + + @override + String get paiementCanCancelTransaction => 'Peut annuler des transactions'; + + @override + String get paiementCanManageSellers => 'Peut gérer les vendeurs'; + + @override + String get paiementAddedSeller => 'Vendeur ajouté'; + + @override + String get paiementAddingSellerError => 'Erreur lors de l\'ajout du vendeur'; + + @override + String get paiementBank => 'Encaisser'; + + @override + String get paiementSeeHistory => 'Voir l\'historique'; + + @override + String get paiementCancelTransactions => 'Annuler les transactions'; + + @override + String get paiementManageSellers => 'Gérer les vendeurs'; + + @override + String get paiementStructureAdmin => 'Administrateur de la structure'; + + @override + String get paiementRightsOf => 'Droits de'; + + @override + String get paiementRightsUpdated => 'Droits mis à jour'; + + @override + String get paiementRightsUpdateError => + 'Erreur lors de la mise à jour des droits'; + + @override + String get paiementDeleteSellerDescription => + 'Voulez-vous vraiment supprimer ce vendeur ?'; + + @override + String get paiementDeletedSeller => 'Vendeur supprimé'; + + @override + String get paiementDeletingSellerError => + 'Erreur lors de la suppression du vendeur'; + + @override + String get paiementDeleteSeller => 'Supprimer le vendeur'; + + @override + String get paiementAdd => 'Ajouter'; + + @override + String get paiementAddSeller => 'Ajouter un vendeur'; + + @override + String get paiementSellerError => + 'Vous n\'êtes pas vendeur de cette association'; + + @override + String get paiementSellersOf => 'Les vendeurs de'; + + @override + String get paiementModify => 'Modifier'; + + @override + String get paiementAStore => 'une association'; + + @override + String get paiementStoreName => 'Nom de l\'association'; + + @override + String get paiementSuccessfullyAddedStore => + 'Association ajoutée avec succès'; + + @override + String get paiementSuccessfullyModifiedStore => + 'Association modifiée avec succès'; + + @override + String get paiementAddingStoreError => + 'Erreur lors de l\'ajout de l\'association'; + + @override + String get paiementModifyingStoreError => + 'Erreur lors de la modification de l\'association'; + + @override + String get paiementRefund => 'Remboursement'; + + @override + String get paiementDoneTransaction => 'Transaction effectuée'; + + @override + String get paiementRefundAction => 'Rembourser'; + + @override + String get paiementTotalDuringPeriod => 'Total sur la période'; + + @override + String get paiementMean => 'Moyenne : '; + + @override + String get paiementTransaction => 'ransaction'; + + @override + String get paiementTransferStructure => 'Transfert de structure'; + + @override + String get paiementYouAreTransferingStructureTo => + 'Vous êtes sur le point de transférer la structure à '; + + @override + String get paiementTransferStructureDescription => + 'Le nouveau responsable aura accès à toutes les fonctionnalités de gestion de la structure. Vous allez recevoir un email pour valider ce transfert. Le lien ne sera actif que pendant 20 minutes. Cette action est irréversible. Êtes-vous sûr de vouloir continuer ?'; + + @override + String get paiementTransferStructureError => + 'Erreur lors du transfert de la structure'; + + @override + String get paiementTransferStructureSuccess => + 'Transfert de structure demandé avec succès'; + + @override + String get paiementNextAccountable => 'Prochain responsable'; } diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 4d4d86d178..37c1635680 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -3622,4 +3622,238 @@ class AppLocalizationsFr extends AppLocalizations { @override String get paiementAccept => 'Accepter'; + + @override + String get paiementAmount => 'Montant'; + + @override + String get paiementValidUntil => 'Valide jusqu\'à'; + + @override + String get paiementClose => 'Fermer'; + + @override + String get paiementPleaseEnterValidAmount => + 'Veuillez entrer un montant valide'; + + @override + String get paiementPleaseAuthenticate => 'Veuillez vous authentifier'; + + @override + String get paiementAthenticationRequired => + 'Authentification requise pour payer'; + + @override + String get paiementNoThanks => 'Non merci'; + + @override + String get paiementAuthentificationFailed => 'Échec de l\'authentification'; + + @override + String get paiementPleaseAddDevice => + 'Veuillez ajouter cet appareil pour payer'; + + @override + String get paiementPayment => 'Paiement'; + + @override + String get paiementBalanceAfterTransaction => 'Solde après paiement : '; + + @override + String get paiementCancel => 'Annuler'; + + @override + String get paiementLimitedTo => 'Limité à'; + + @override + String get paiementScanCode => 'Scanner un code'; + + @override + String get paiementNext => 'Suivant'; + + @override + String get paiementCancelTransaction => 'Annuler la transaction'; + + @override + String get paiementTransactionCancelled => 'Transaction annulée'; + + @override + String get paiementTransactionCancelledDescription => + 'Voulez-vous vraiment annuler la transaction de'; + + @override + String get paiementTransactionCancelledError => + 'Erreur lors de l\'annulation de la transaction'; + + @override + String get paiementNoMembership => 'Aucune adhésion'; + + @override + String get paiementNoMembershipDescription => + 'Ce produit n\'est pas disponnible pour les non-adhérents. Confirmer l\'encaissement ?'; + + @override + String get paiementQRCodeAlreadyUsed => 'QR Code déjà utilisé'; + + @override + String get paiementCameraPermissionRequired => + 'Permission d\'accès à la caméra requise'; + + @override + String get paiementCameraPerssionRequiredDescription => + 'Pour scanner un QR Code, vous devez autoriser l\'accès à la caméra.'; + + @override + String get paiementSettings => 'Paramètres'; + + @override + String get paiementReceived => 'Reçu'; + + @override + String get paiementSpent => 'Déboursé'; + + @override + String get paiementNoTrasactionForThisMonth => + 'Aucune transaction pour ce mois'; + + @override + String get paiementNoTransactinon => 'Aucune transaction'; + + @override + String get paiementSellerRigths => 'Droits du vendeur'; + + @override + String get paiementCanBank => 'Peut encaisser'; + + @override + String get paiementCanSeeHistory => 'Peut voir l\'historique'; + + @override + String get paiementCanCancelTransaction => 'Peut annuler des transactions'; + + @override + String get paiementCanManageSellers => 'Peut gérer les vendeurs'; + + @override + String get paiementAddedSeller => 'Vendeur ajouté'; + + @override + String get paiementAddingSellerError => 'Erreur lors de l\'ajout du vendeur'; + + @override + String get paiementBank => 'Encaisser'; + + @override + String get paiementSeeHistory => 'Voir l\'historique'; + + @override + String get paiementCancelTransactions => 'Annuler les transactions'; + + @override + String get paiementManageSellers => 'Gérer les vendeurs'; + + @override + String get paiementStructureAdmin => 'Administrateur de la structure'; + + @override + String get paiementRightsOf => 'Droits de'; + + @override + String get paiementRightsUpdated => 'Droits mis à jour'; + + @override + String get paiementRightsUpdateError => + 'Erreur lors de la mise à jour des droits'; + + @override + String get paiementDeleteSellerDescription => + 'Voulez-vous vraiment supprimer ce vendeur ?'; + + @override + String get paiementDeletedSeller => 'Vendeur supprimé'; + + @override + String get paiementDeletingSellerError => + 'Erreur lors de la suppression du vendeur'; + + @override + String get paiementDeleteSeller => 'Supprimer le vendeur'; + + @override + String get paiementAdd => 'Ajouter'; + + @override + String get paiementAddSeller => 'Ajouter un vendeur'; + + @override + String get paiementSellerError => + 'Vous n\'êtes pas vendeur de cette association'; + + @override + String get paiementSellersOf => 'Les vendeurs de'; + + @override + String get paiementModify => 'Modifier'; + + @override + String get paiementAStore => 'une association'; + + @override + String get paiementStoreName => 'Nom de l\'association'; + + @override + String get paiementSuccessfullyAddedStore => + 'Association ajoutée avec succès'; + + @override + String get paiementSuccessfullyModifiedStore => + 'Association modifiée avec succès'; + + @override + String get paiementAddingStoreError => + 'Erreur lors de l\'ajout de l\'association'; + + @override + String get paiementModifyingStoreError => + 'Erreur lors de la modification de l\'association'; + + @override + String get paiementRefund => 'Remboursement'; + + @override + String get paiementDoneTransaction => 'Transaction effectuée'; + + @override + String get paiementRefundAction => 'Rembourser'; + + @override + String get paiementTotalDuringPeriod => 'Total sur la période'; + + @override + String get paiementMean => 'Moyenne : '; + + @override + String get paiementTransaction => 'ransaction'; + + @override + String get paiementTransferStructure => 'Transfert de structure'; + + @override + String get paiementYouAreTransferingStructureTo => + 'Vous êtes sur le point de transférer la structure à '; + + @override + String get paiementTransferStructureDescription => + 'Le nouveau responsable aura accès à toutes les fonctionnalités de gestion de la structure. Vous allez recevoir un email pour valider ce transfert. Le lien ne sera actif que pendant 20 minutes. Cette action est irréversible. Êtes-vous sûr de vouloir continuer ?'; + + @override + String get paiementTransferStructureError => + 'Erreur lors du transfert de la structure'; + + @override + String get paiementTransferStructureSuccess => + 'Transfert de structure demandé avec succès'; + + @override + String get paiementNextAccountable => 'Prochain responsable'; } diff --git a/lib/paiement/ui/pages/pay_page/confirm_button.dart b/lib/paiement/ui/pages/pay_page/confirm_button.dart index ea756c5209..814e19c3ec 100644 --- a/lib/paiement/ui/pages/pay_page/confirm_button.dart +++ b/lib/paiement/ui/pages/pay_page/confirm_button.dart @@ -6,6 +6,7 @@ import 'package:local_auth/local_auth.dart'; import 'package:local_auth_android/local_auth_android.dart'; import 'package:local_auth_darwin/local_auth_darwin.dart'; import 'package:titan/event/tools/functions.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/paiement/providers/key_service_provider.dart'; import 'package:titan/paiement/providers/my_history_provider.dart'; import 'package:titan/paiement/providers/my_wallet_provider.dart'; @@ -64,17 +65,17 @@ class ConfirmButton extends ConsumerWidget { const SizedBox(height: 30), Row( children: [ - const SizedBox(width: 20), + SizedBox(width: 20), InfoCard( icons: HeroIcons.currencyEuro, - title: "Montant", + title: AppLocalizations.of(context)!.paiementAmount, value: '${formatter.format(double.parse(payAmount.replaceAll(',', '.')))} €', ), const SizedBox(width: 10), InfoCard( icons: HeroIcons.clock, - title: "Valide jusqu'à", + title: AppLocalizations.of(context)!.paiementValidUntil, value: processDateOnlyHour( DateTime.now().add(const Duration(minutes: 5)), ), @@ -90,8 +91,8 @@ class ConfirmButton extends ConsumerWidget { child: GestureDetector( child: AddEditButtonLayout( color: Colors.grey.shade200.withValues(alpha: 0.5), - child: const Text( - 'Fermer', + child: Text( + AppLocalizations.of(context)!.paiementClose, style: TextStyle( color: Colors.black, fontWeight: FontWeight.bold, @@ -120,32 +121,38 @@ class ConfirmButton extends ConsumerWidget { if (!enabled) { displayToastWithContext( TypeMsg.error, - 'Veuillez entrer un montant valide', + AppLocalizations.of(context)!.paiementPleaseEnterValidAmount, ); return; } + final authentificationFailedMsg = AppLocalizations.of( + context, + )!.paiementAuthentificationFailed; + final pleaseAddDeviceMsg = AppLocalizations.of( + context, + )!.paiementPleaseAddDevice; final bool didAuthenticate = await auth.authenticate( - localizedReason: 'Veuillez vous authentifier pour payer', + localizedReason: AppLocalizations.of( + context, + )!.paiementPleaseAuthenticate, authMessages: [ - const AndroidAuthMessages( - signInTitle: 'L\'authentification est requise pour payer', - cancelButton: 'Non merci', + AndroidAuthMessages( + signInTitle: AppLocalizations.of( + context, + )!.paiementAthenticationRequired, + cancelButton: AppLocalizations.of(context)!.paiementNoThanks, + ), + IOSAuthMessages( + cancelButton: AppLocalizations.of(context)!.paiementNoThanks, ), - const IOSAuthMessages(cancelButton: 'Non merci'), ], ); if (!didAuthenticate) { - displayToastWithContext( - TypeMsg.error, - 'L\'authentification a échoué', - ); + displayToastWithContext(TypeMsg.error, authentificationFailedMsg); return; } if ((await keyService.getKeyId()) == null) { - displayToastWithContext( - TypeMsg.error, - 'Veuillez ajouter cet appareil pour payer', - ); + displayToastWithContext(TypeMsg.error, pleaseAddDeviceMsg); return; } displayQRModal(); diff --git a/lib/paiement/ui/pages/pay_page/pay_page.dart b/lib/paiement/ui/pages/pay_page/pay_page.dart index d659d25576..4cae0b60cc 100644 --- a/lib/paiement/ui/pages/pay_page/pay_page.dart +++ b/lib/paiement/ui/pages/pay_page/pay_page.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:intl/intl.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/paiement/providers/my_wallet_provider.dart'; import 'package:titan/paiement/providers/pay_amount_provider.dart'; import 'package:titan/paiement/ui/pages/pay_page/confirm_button.dart'; @@ -41,7 +42,7 @@ class PayPage extends ConsumerWidget { children: [ const SizedBox(height: 20), Text( - 'Paiement', + AppLocalizations.of(context)!.paiementPayment, style: const TextStyle( color: Colors.white, fontSize: 20, @@ -50,7 +51,7 @@ class PayPage extends ConsumerWidget { ), const SizedBox(height: 5), Text( - 'Solde après paiement : ${formatter.format(currentAmount - amountToSub)} €', + '${AppLocalizations.of(context)!.paiementBalanceAfterTransaction} ${formatter.format(currentAmount - amountToSub)} €', style: const TextStyle(color: Colors.white, fontSize: 15), ), Expanded( diff --git a/lib/paiement/ui/pages/scan_page/cancel_button.dart b/lib/paiement/ui/pages/scan_page/cancel_button.dart index 5c0ce64b0e..1e56db1289 100644 --- a/lib/paiement/ui/pages/scan_page/cancel_button.dart +++ b/lib/paiement/ui/pages/scan_page/cancel_button.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; class CancelButton extends HookWidget { @@ -90,7 +91,7 @@ class CancelButton extends HookWidget { height: 50, alignment: Alignment.center, child: Text( - 'Annuler (${((1 - disablingAnimationController.value) * 30).toStringAsFixed(0)}s)', + '${AppLocalizations.of(context)!.paiementCancel} (${((1 - disablingAnimationController.value) * 30).toStringAsFixed(0)}s)', style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, diff --git a/lib/paiement/ui/pages/scan_page/scan_page.dart b/lib/paiement/ui/pages/scan_page/scan_page.dart index 9f1ee00ba5..1efdc3f270 100644 --- a/lib/paiement/ui/pages/scan_page/scan_page.dart +++ b/lib/paiement/ui/pages/scan_page/scan_page.dart @@ -3,6 +3,7 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:intl/intl.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/paiement/providers/barcode_provider.dart'; import 'package:titan/paiement/providers/bypass_provider.dart'; import 'package:titan/paiement/providers/ongoing_transaction.dart'; @@ -77,7 +78,7 @@ class ScanPage extends HookConsumerWidget { ), const SizedBox(width: 5), Text( - "Limité à ${store.structure.associationMembership.name}", + "${AppLocalizations.of(context)!.paiementLimitedTo} ${store.structure.associationMembership.name}", style: TextStyle( color: bypass ? Colors.white.withValues(alpha: 0.5) @@ -149,7 +150,9 @@ class ScanPage extends HookConsumerWidget { child: Column( children: [ Text( - "Montant", + AppLocalizations.of( + context, + )!.paiementAmount, style: TextStyle( fontSize: 13, color: Colors.white, @@ -223,8 +226,10 @@ class ScanPage extends HookConsumerWidget { builder: (context, child) { return Opacity( opacity: opacity.value, - child: const Text( - 'Scanner un code', + child: Text( + AppLocalizations.of( + context, + )!.paiementScanCode, style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, @@ -247,7 +252,7 @@ class ScanPage extends HookConsumerWidget { const Spacer(), AsyncChild( value: ongoingTransaction, - errorBuilder: (context, child) => Padding( + errorBuilder: (errorContext, child) => Padding( padding: const EdgeInsets.symmetric(horizontal: 30), child: GestureDetector( child: Container( @@ -258,8 +263,8 @@ class ScanPage extends HookConsumerWidget { borderRadius: BorderRadius.circular(10), color: Colors.white.withValues(alpha: 0.8), ), - child: const Text( - 'Suivant', + child: Text( + AppLocalizations.of(context)!.paiementNext, style: TextStyle( color: Colors.black, fontWeight: FontWeight.bold, @@ -285,9 +290,11 @@ class ScanPage extends HookConsumerWidget { context: context, builder: (context) { return CustomDialogBox( - title: "Annuler la transaction", + title: AppLocalizations.of( + context, + )!.paiementCancelTransaction, descriptions: - "Voulez-vous vraiment annuler la transaction de ${formatter.format(transaction.total / 100)} € ?", + "${AppLocalizations.of(context)!.paiementTransactionCancelledDescription} ${formatter.format(transaction.total / 100)} € ?", onYes: () async { tokenExpireWrapper(ref, () async { final value = @@ -300,7 +307,9 @@ class ScanPage extends HookConsumerWidget { if (value) { displayToastWithContext( TypeMsg.msg, - "Transaction annulée", + AppLocalizations.of( + context, + )!.paiementTransactionCancelled, ); ref .read( @@ -311,7 +320,9 @@ class ScanPage extends HookConsumerWidget { } else { displayToastWithContext( TypeMsg.error, - "Erreur lors de l'annulation", + AppLocalizations.of( + context, + )!.paiementTransactionCancelledError, ); } ongoingTransactionNotifier @@ -346,8 +357,8 @@ class ScanPage extends HookConsumerWidget { borderRadius: BorderRadius.circular(10), color: Colors.white.withValues(alpha: 0.8), ), - child: const Text( - 'Suivant', + child: Text( + AppLocalizations.of(context)!.paiementNext, style: TextStyle( color: Colors.black, fontWeight: FontWeight.bold, diff --git a/lib/paiement/ui/pages/stats_page/sum_up_chart.dart b/lib/paiement/ui/pages/stats_page/sum_up_chart.dart index ac2bd215e8..eb4a797f65 100644 --- a/lib/paiement/ui/pages/stats_page/sum_up_chart.dart +++ b/lib/paiement/ui/pages/stats_page/sum_up_chart.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:intl/intl.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/paiement/class/history.dart'; import 'package:titan/paiement/providers/my_history_provider.dart'; import 'package:titan/paiement/providers/selected_transactions_provider.dart'; @@ -40,7 +41,7 @@ class SumUpChart extends HookConsumerWidget { transaction.type == HistoryType.refundCredited) { final transactionName = transaction.type != HistoryType.transfer ? transaction.otherWalletName - : "Recharge"; + : AppLocalizations.of(context)!.paiementTopUp; creditedTransactionPerStore[transactionName] = [ ...?creditedTransactionPerStore[transactionName], transaction, @@ -115,7 +116,9 @@ class SumUpChart extends HookConsumerWidget { ); }, child: MonthSectionSummary( - title: "Reçu", + title: AppLocalizations.of( + context, + )!.paiementReceived, amount: '${formatter.format(transferTotal / 100)} €', color: const Color.fromARGB(255, 255, 119, 7), @@ -147,7 +150,9 @@ class SumUpChart extends HookConsumerWidget { ); }, child: MonthSectionSummary( - title: "Déboursé", + title: AppLocalizations.of( + context, + )!.paiementSpent, amount: '${formatter.format(total / 100)} €', color: const Color.fromARGB(255, 1, 127, 128), darkColor: const Color.fromARGB( @@ -174,8 +179,10 @@ class SumUpChart extends HookConsumerWidget { : Container( height: 300, alignment: Alignment.center, - child: const Text( - "Aucune transaction pour ce mois", + child: Text( + AppLocalizations.of( + context, + )!.paiementNoTrasactionForThisMonth, style: TextStyle(fontSize: 18, color: Colors.grey), ), ); diff --git a/lib/paiement/ui/pages/stats_page/transaction_chart.dart b/lib/paiement/ui/pages/stats_page/transaction_chart.dart index b0138820e9..1a7dda50ae 100644 --- a/lib/paiement/ui/pages/stats_page/transaction_chart.dart +++ b/lib/paiement/ui/pages/stats_page/transaction_chart.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:intl/intl.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/paiement/class/history.dart'; import 'package:titan/paiement/providers/selected_transactions_provider.dart'; import 'package:titan/paiement/tools/functions.dart'; @@ -62,9 +63,9 @@ class TransactionChart extends HookConsumerWidget { } return chartPart.isEmpty - ? const Center( + ? Center( child: Text( - "Aucune transaction", + AppLocalizations.of(context)!.paiementNoTransactinon, style: TextStyle(fontSize: 20, color: Colors.black54), ), ) diff --git a/lib/paiement/ui/pages/store_admin_page/search_result.dart b/lib/paiement/ui/pages/store_admin_page/search_result.dart index 0e36f31de5..8ffc540fb2 100644 --- a/lib/paiement/ui/pages/store_admin_page/search_result.dart +++ b/lib/paiement/ui/pages/store_admin_page/search_result.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/paiement/class/seller.dart'; import 'package:titan/paiement/providers/new_admin_provider.dart'; import 'package:titan/paiement/providers/selected_store_provider.dart'; @@ -52,17 +53,32 @@ class SearchResult extends HookConsumerWidget { builder: (context, ref, _) { final sellerRightsList = ref.watch(sellerRightsListProvider); return SellerRightDialog( - title: "Droit du vendeur", + title: AppLocalizations.of(context)!.paiementSellerRigths, child: Column( mainAxisSize: MainAxisSize.min, children: [ - RightCheckBox(title: "Peut encaisser", index: 0), - RightCheckBox(title: "Peut voir l'historique", index: 1), RightCheckBox( - title: "Peut annuler des transactions", + title: AppLocalizations.of(context)!.paiementCanBank, + index: 0, + ), + RightCheckBox( + title: AppLocalizations.of( + context, + )!.paiementCanSeeHistory, + index: 1, + ), + RightCheckBox( + title: AppLocalizations.of( + context, + )!.paiementCanCancelTransaction, index: 2, ), - RightCheckBox(title: "Peut gérer les vendeurs", index: 3), + RightCheckBox( + title: AppLocalizations.of( + context, + )!.paiementCanManageSellers, + index: 3, + ), ], ), onYes: () async { @@ -78,6 +94,12 @@ class SearchResult extends HookConsumerWidget { canCancel: sellerRightsList[2], canManageSellers: sellerRightsList[3], ); + final addedSellerMsg = AppLocalizations.of( + context, + )!.paiementAddedSeller; + final addingSellerErrorMsg = AppLocalizations.of( + context, + )!.paiementAddingSellerError; final value = await sellerStoreNotifier.createStoreSeller( seller, ); @@ -86,14 +108,14 @@ class SearchResult extends HookConsumerWidget { usersNotifier.clear(); sellerRightsListNotifier.clearRights(); newAdminNotifier.resetNewAdmin(); - displayToastWithContext(TypeMsg.msg, "Vendeur ajouté"); + displayToastWithContext(TypeMsg.msg, addedSellerMsg); if (context.mounted) { Navigator.of(context).pop(); } } else { displayToastWithContext( TypeMsg.error, - "Erreur lors de l'ajout", + addingSellerErrorMsg, ); } }); diff --git a/lib/paiement/ui/pages/store_admin_page/seller_right_card.dart b/lib/paiement/ui/pages/store_admin_page/seller_right_card.dart index e77f16f31a..65272ec9dc 100644 --- a/lib/paiement/ui/pages/store_admin_page/seller_right_card.dart +++ b/lib/paiement/ui/pages/store_admin_page/seller_right_card.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:heroicons/heroicons.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/paiement/class/seller.dart'; import 'package:titan/paiement/providers/selected_store_provider.dart'; import 'package:titan/paiement/providers/store_sellers_list_provider.dart'; @@ -67,11 +68,11 @@ class SellerRightCard extends ConsumerWidget { ); final labels = [ - "Encaisser", - "Voir l'historique", - "Annuler les transactions", - "Gérer les vendeurs", - "Administrateur de la structure", + AppLocalizations.of(context)!.paiementBank, + AppLocalizations.of(context)!.paiementSeeHistory, + AppLocalizations.of(context)!.paiementCancelTransactions, + AppLocalizations.of(context)!.paiementManageSellers, + AppLocalizations.of(context)!.paiementStructureAdmin, ]; List sellerRights = [ @@ -119,7 +120,7 @@ class SellerRightCard extends ConsumerWidget { const SizedBox(height: 20), Expanded( child: Text( - "Droits de ${storeSeller.user.nickname ?? ("${storeSeller.user.firstname} ${storeSeller.user.name}")}", + "${AppLocalizations.of(context)!.paiementRightsOf} ${storeSeller.user.nickname ?? ("${storeSeller.user.firstname} ${storeSeller.user.name}")}", overflow: TextOverflow.ellipsis, style: const TextStyle( color: Color.fromARGB(255, 0, 29, 29), @@ -155,6 +156,14 @@ class SellerRightCard extends ConsumerWidget { ), onChanged: (value) async { await tokenExpireWrapper(ref, () async { + final rightsUpdatedMsg = + AppLocalizations.of( + context, + )!.paiementRightsUpdated; + final rightsUpdateErrorMsg = + AppLocalizations.of( + context, + )!.paiementRightsUpdateError; final value = await sellerStoreNotifier .updateStoreSeller( storeSeller.copyWith( @@ -175,7 +184,7 @@ class SellerRightCard extends ConsumerWidget { if (value) { displayToastWithContext( TypeMsg.msg, - "Droits mis à jour", + rightsUpdatedMsg, ); sellerRights[i] = !sellerRights[i]; if (context.mounted) { @@ -184,7 +193,7 @@ class SellerRightCard extends ConsumerWidget { } else { displayToastWithContext( TypeMsg.error, - "Impossible de mettre à jour les droits", + rightsUpdateErrorMsg, ); } }); @@ -199,17 +208,27 @@ class SellerRightCard extends ConsumerWidget { await showDialog( context: context, builder: (context) => CustomDialogBox( - title: "Supprimer l'association", - descriptions: - "Voulez-vous vraiment supprimer ce vendeur ?", + title: AppLocalizations.of( + context, + )!.paiementDeleteStore, + descriptions: AppLocalizations.of( + context, + )!.paiementDeleteSellerDescription, onYes: () { tokenExpireWrapper(ref, () async { + final deleteSellerMsg = AppLocalizations.of( + context, + )!.paiementDeletedSeller; + final deletingSellerErrorMsg = + AppLocalizations.of( + context, + )!.paiementDeletingSellerError; final value = await sellerStoreNotifier .deleteStoreSeller(storeSeller); if (value) { displayToastWithContext( TypeMsg.msg, - "Vendeur supprimé", + deleteSellerMsg, ); if (context.mounted) { Navigator.pop(context); @@ -217,7 +236,7 @@ class SellerRightCard extends ConsumerWidget { } else { displayToastWithContext( TypeMsg.error, - "Impossible de supprimer le vendeur", + deletingSellerErrorMsg, ); } }); @@ -225,22 +244,27 @@ class SellerRightCard extends ConsumerWidget { ), ); }, - child: const Padding( - padding: EdgeInsets.symmetric(vertical: 10), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 10), child: AddEditButtonLayout( - colors: [Color(0xFF9E131F), Color(0xFF590512)], + colors: const [ + Color(0xFF9E131F), + Color(0xFF590512), + ], child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - HeroIcon( + const HeroIcon( HeroIcons.trash, color: Colors.white, size: 20, ), - SizedBox(width: 15), + const SizedBox(width: 15), Text( - "Supprimer le vendeur", - style: TextStyle( + AppLocalizations.of( + context, + )!.paiementDeleteSeller, + style: const TextStyle( color: Colors.white, fontSize: 14, ), diff --git a/lib/paiement/ui/pages/store_admin_page/seller_right_dialog.dart b/lib/paiement/ui/pages/store_admin_page/seller_right_dialog.dart index ea73482ff6..6d3cafa4ec 100644 --- a/lib/paiement/ui/pages/store_admin_page/seller_right_dialog.dart +++ b/lib/paiement/ui/pages/store_admin_page/seller_right_dialog.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; class Consts { @@ -102,10 +103,12 @@ class SellerRightDialog extends StatelessWidget { ), child: child, ), - child: const Center( + child: Center( child: Text( - "Ajouter", - style: TextStyle(fontWeight: FontWeight.bold), + AppLocalizations.of(context)!.paiementAdd, + style: const TextStyle( + fontWeight: FontWeight.bold, + ), ), ), ), diff --git a/lib/paiement/ui/pages/store_admin_page/store_admin_page.dart b/lib/paiement/ui/pages/store_admin_page/store_admin_page.dart index 4321a85809..e5dc41f531 100644 --- a/lib/paiement/ui/pages/store_admin_page/store_admin_page.dart +++ b/lib/paiement/ui/pages/store_admin_page/store_admin_page.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/paiement/providers/selected_store_provider.dart'; import 'package:titan/paiement/providers/store_sellers_list_provider.dart'; import 'package:titan/paiement/ui/pages/store_admin_page/search_result.dart'; @@ -42,7 +43,7 @@ class StoreAdminPage extends HookConsumerWidget { padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 20), alignment: Alignment.centerLeft, child: Text( - "Les vendeurs de ${store.name}", + "${AppLocalizations.of(context)!.paiementSellersOf} ${store.name}", style: const TextStyle( color: Color.fromARGB(255, 0, 29, 29), fontSize: 20, @@ -55,22 +56,22 @@ class StoreAdminPage extends HookConsumerWidget { onTap: () { isSearching.value = true; }, - child: const Padding( - padding: EdgeInsets.symmetric(horizontal: 30), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 30), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ Center( child: Text( - "Ajouter un vendeur", - style: TextStyle( + AppLocalizations.of(context)!.paiementAddSeller, + style: const TextStyle( color: Color.fromARGB(255, 0, 29, 29), fontSize: 14, ), ), ), - Spacer(), - CardButton( + const Spacer(), + const CardButton( size: 35, child: HeroIcon( HeroIcons.plus, @@ -90,8 +91,10 @@ class StoreAdminPage extends HookConsumerWidget { .where((seller) => seller.user.id == me.id) .firstOrNull; if (mySellers == null) { - return const Center( - child: Text('You are not a seller in this store'), + return Center( + child: Text( + AppLocalizations.of(context)!.paiementSellerError, + ), ); } return Column( @@ -114,7 +117,7 @@ class StoreAdminPage extends HookConsumerWidget { children: [ Expanded( child: TextEntry( - label: "Ajouter un vendeur", + label: AppLocalizations.of(context)!.paiementAddSeller, onChanged: (value) { tokenExpireWrapper(ref, () async { if (queryController.text.isNotEmpty) { diff --git a/lib/paiement/ui/pages/store_pages/add_edit_store.dart b/lib/paiement/ui/pages/store_pages/add_edit_store.dart index 1d57bb0a24..511e6f3e95 100644 --- a/lib/paiement/ui/pages/store_pages/add_edit_store.dart +++ b/lib/paiement/ui/pages/store_pages/add_edit_store.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/paiement/class/store.dart' as store_class; import 'package:titan/paiement/class/structure.dart'; import 'package:titan/paiement/providers/my_stores_provider.dart'; @@ -40,7 +41,7 @@ class AddEditStorePage extends HookConsumerWidget { children: [ const SizedBox(height: 10), AlignLeftText( - "${isEdit ? 'Modifier' : 'Ajouter'} une association ${structure.name}", + "${isEdit ? '${AppLocalizations.of(context)!.paiementModify}' : AppLocalizations.of(context)!.paiementAdd}} ${AppLocalizations.of(context)!.paiementAStore} ${structure.name}", padding: const EdgeInsets.symmetric(horizontal: 30), color: Colors.grey, ), @@ -54,7 +55,9 @@ class AddEditStorePage extends HookConsumerWidget { children: [ TextEntry( controller: name, - label: "Nom de l'association", + label: AppLocalizations.of( + context, + )!.paiementStoreName, ), const SizedBox(height: 50), WaitingButton( @@ -90,21 +93,31 @@ class AddEditStorePage extends HookConsumerWidget { displayToastWithContext( TypeMsg.msg, isEdit - ? "L'association a été modifiée avec succès" - : "L'association a été ajoutée avec succès", + ? AppLocalizations.of( + context, + )!.paiementSuccessfullyAddedStore + : AppLocalizations.of( + context, + )!.paiementSuccessfullyModifiedStore, ); } else { displayToastWithContext( TypeMsg.error, isEdit - ? "Une erreur est survenue lors de la modification de l'association" - : "Une erreur est survenue lors de l'ajout de l'association", + ? AppLocalizations.of( + context, + )!.paiementModifyingStoreError + : AppLocalizations.of( + context, + )!.paiementAddingStoreError, ); } } }, child: Text( - isEdit ? "Modifier" : "Ajouter", + isEdit + ? AppLocalizations.of(context)!.paiementModify + : AppLocalizations.of(context)!.paiementAdd, style: const TextStyle( color: Colors.white, fontSize: 25, diff --git a/lib/paiement/ui/pages/store_stats_page/refund_page.dart b/lib/paiement/ui/pages/store_stats_page/refund_page.dart index c253b5be01..9dabba9019 100644 --- a/lib/paiement/ui/pages/store_stats_page/refund_page.dart +++ b/lib/paiement/ui/pages/store_stats_page/refund_page.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:intl/intl.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/paiement/class/history.dart'; import 'package:titan/paiement/class/refund.dart'; import 'package:titan/paiement/providers/refund_amount_provider.dart'; @@ -48,7 +49,7 @@ class ReFundPage extends ConsumerWidget { children: [ const SizedBox(height: 20), Text( - 'Remboursement', + AppLocalizations.of(context)!.paiementRefund, style: const TextStyle( color: Colors.white, fontSize: 20, @@ -144,7 +145,9 @@ class ReFundPage extends ConsumerWidget { data: (value) { displayToastWithContext( TypeMsg.msg, - "Transaction effectuée", + AppLocalizations.of( + context, + )!.paiementDoneTransaction, ); ref.invalidate(sellerHistoryProvider); Navigator.of(context).pop(); @@ -164,7 +167,7 @@ class ReFundPage extends ConsumerWidget { child: child, ), child: Text( - "Rembourser", + AppLocalizations.of(context)!.paiementRefundAction, style: TextStyle( color: Colors.black, fontSize: 20, diff --git a/lib/paiement/ui/pages/store_stats_page/summary_card.dart b/lib/paiement/ui/pages/store_stats_page/summary_card.dart index a0622285f3..4f9b5f11ab 100644 --- a/lib/paiement/ui/pages/store_stats_page/summary_card.dart +++ b/lib/paiement/ui/pages/store_stats_page/summary_card.dart @@ -2,6 +2,7 @@ import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:intl/intl.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/paiement/class/history.dart'; class SummaryCard extends StatelessWidget { @@ -70,8 +71,8 @@ class SummaryCard extends StatelessWidget { children: [ Row( children: [ - const AutoSizeText( - "Total sur la période", + AutoSizeText( + AppLocalizations.of(context)!.paiementTotalDuringPeriod, maxLines: 2, style: TextStyle(color: Color(0xff204550), fontSize: 14), ), @@ -79,7 +80,7 @@ class SummaryCard extends StatelessWidget { ), const SizedBox(height: 5), Text( - "Moyenne : ${formatter.format(mean / 100)} € / transaction", + "${AppLocalizations.of(context)!.paiementMean} ${formatter.format(mean / 100)} € / ${AppLocalizations.of(context)!.paiementTransaction}", style: const TextStyle( color: Color(0xff204550), fontSize: 12, diff --git a/lib/paiement/ui/pages/transfer_structure_page/search_result.dart b/lib/paiement/ui/pages/transfer_structure_page/search_result.dart index 92885bbf95..61ba66e926 100644 --- a/lib/paiement/ui/pages/transfer_structure_page/search_result.dart +++ b/lib/paiement/ui/pages/transfer_structure_page/search_result.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/paiement/providers/selected_store_provider.dart'; import 'package:titan/paiement/providers/transfer_structure_provider.dart'; import 'package:titan/tools/constants.dart'; @@ -32,9 +33,9 @@ class SearchResult extends HookConsumerWidget { context: context, builder: (context) { return CustomDialogBox( - title: 'Transfert de structure', + title: AppLocalizations.of(context)!.paiementTransferStructure, descriptions: - 'Vous êtes sur le point de transférer la structure à ${simpleUser.getName()}. Le nouveau responsable aura accès à toutes les fonctionnalités de gestion de la structure. Vous allez recevoir un email pour valider ce transfert. Le lien ne sera actif que pendant 20 minutes. Cette action est irréversible. Êtes-vous sûr de vouloir continuer ?', + '${AppLocalizations.of(context)!.paiementYouAreTransferingStructureTo} ${simpleUser.getName()}. ${AppLocalizations.of(context)!.paiementTransferStructureDescription}', onYes: () async { final value = await transferStructureNotifier.initTransfer( selectedStore.structure, @@ -43,12 +44,14 @@ class SearchResult extends HookConsumerWidget { if (value) { displayToastWithContext( TypeMsg.msg, - "Transfert de structure demandé avec succès.", + AppLocalizations.of( + context, + )!.paiementTransferStructureSuccess, ); } else { displayToastWithContext( TypeMsg.error, - "Une erreur est survenue lors du transfert de structure.", + AppLocalizations.of(context)!.paiementTransferStructureError, ); } }, diff --git a/lib/paiement/ui/pages/transfer_structure_page/transfer_structure_page.dart b/lib/paiement/ui/pages/transfer_structure_page/transfer_structure_page.dart index 3ee0e22ab4..def4355d33 100644 --- a/lib/paiement/ui/pages/transfer_structure_page/transfer_structure_page.dart +++ b/lib/paiement/ui/pages/transfer_structure_page/transfer_structure_page.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/paiement/ui/pages/transfer_structure_page/search_result.dart'; import 'package:titan/paiement/ui/paiement.dart'; import 'package:titan/tools/ui/widgets/styled_search_bar.dart'; @@ -20,7 +21,7 @@ class TransferStructurePage extends HookConsumerWidget { child: Column( children: [ StyledSearchBar( - label: "Prochain responsable", + label: AppLocalizations.of(context)!.paiementNextAccountable, color: Color.fromARGB(255, 6, 75, 75), padding: const EdgeInsets.all(0), editingController: queryController, From 2f1e97de50e962f25d79e260feddfa4e83c8d5aa Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Fri, 4 Jul 2025 15:38:47 +0200 Subject: [PATCH 060/473] Paiement in en --- lib/l10n/app_en.arb | 126 +++++++++++++- lib/l10n/app_localizations_en.dart | 260 ++++++++++++++--------------- 2 files changed, 247 insertions(+), 139 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index d3b0ce6d9a..505dbf70c5 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1117,5 +1117,129 @@ "moduleVote": "Vote", "modulePh": "PH", "moduleSettings": "Settings", - "modulePayment": "Payment" + "modulePayment": "Payment", + "paiementTopUp": "Top-up", + "paiementStoreManagement": "Association management", + "paiementDeleteStore": "Delete association", + "paiementDeleteStoreDescription": "Are you sure you want to delete this association?", + "paiementDeleteStoreError": "Unable to delete the association", + "paiementStoreDeleted": "Association deleted", + "paiementAddThisDevice": "Add this device", + "paiementThisDevice": "(this device)", + "paiementCancelled": "Cancelled", + "paiementThe": "The", + "paiementOf": "of", + "paiementRefundedThe": "Refunded on", + "paiementAt": "at", + "paiementPleaseAcceptTOS": "Please accept the Terms of Service.", + "paiementAskDeviceActivation": "Device activation request", + "paiementDeviceActivationReceived": "The activation request has been received, please check your email to finalize the process", + "paiementRevokeDevice": "Revoke device?", + "paiementRevokeDeviceDescription": "You will no longer be able to use this device for payments", + "paiementDeviceRevoked": "Device revoked", + "paiementDeviceRevokingError": "Error while revoking device", + "paiementPleaseAcceptPopup": "Please allow popups", + "paiementProceedSuccessfully": "Payment completed successfully", + "paiementCancelledTransaction": "Payment cancelled", + "paiementPleaseEnterMinAmount": "Please enter an amount greater than 1", + "paiementMaxAmount": "The maximum wallet amount is", + "paiementPayWithHA": "Pay with HelloAsso", + "paiementBalanceAfterTopUp": "Balance after top-up:", + "paiementPersonalBalance": "Personal balance", + "paiementDevices": "Devices", + "paiementPay": "Pay", + "paiementDeviceNotRegistered": "Device not registered", + "paiementDeviceNotRegisteredDescription": "Your device is not registered yet. \nTo register it, please go to the devices page.", + "paiementAccessPage": "Access the page", + "paiementDeviceNotActivated": "Device not activated", + "paiementDeviceNotActivatedDescription": "Your device is not yet activated. \nTo activate it, please go to the devices page.", + "paiementReactivateRevokedDeviceDescription": "Your device has been revoked. \nTo reactivate it, please go to the devices page.", + "paiementDeviceRecoveryError": "Error while retrieving device", + "paiementStats": "Stats", + "paimentTopUpAction": "Top-up", + "paiementGetBalanceError": "Error while retrieving balance: ", + "paiementLastTransactions": "Latest transactions", + "paiementGetTransactionsError": "Error while retrieving transactions: ", + "paiementStoreBalance": "Association balance", + "paiementScan": "Scan", + "paiementManagement": "Management", + "paiementHistory": "History", + "paiementHandOver": "Handover", + "paiementStores": "Associations", + "paiementAdmin": "Administrator", + "paiementSuccededTransaction": "Successful payment", + "paiementNewCGU": "New Terms of Service", + "paiementDecline": "Decline", + "paiementAccept": "Accept", + "paiementAmount": "Amount", + "paiementValidUntil": "Valid until", + "paiementClose": "Close", + "paiementPleaseEnterValidAmount": "Please enter a valid amount", + "paiementPleaseAuthenticate": "Please authenticate", + "paiementAthenticationRequired": "Authentication required to pay", + "paiementNoThanks": "No thanks", + "paiementAuthentificationFailed": "Authentication failed", + "paiementPleaseAddDevice": "Please add this device to pay", + "paiementPayment": "Payment", + "paiementBalanceAfterTransaction": "Balance after payment: ", + "paiementCancel": "Cancel", + "paiementLimitedTo": "Limited to", + "paiementScanCode": "Scan a code", + "paiementNext": "Next", + "paiementCancelTransaction": "Cancel transaction", + "paiementTransactionCancelled": "Transaction cancelled", + "paiementTransactionCancelledDescription": "Are you sure you want to cancel the transaction of", + "paiementTransactionCancelledError": "Error while cancelling the transaction", + "paiementNoMembership": "No membership", + "paiementNoMembershipDescription": "This product is not available to non-members. Confirm the payment?", + "paiementQRCodeAlreadyUsed": "QR Code already used", + "paiementCameraPermissionRequired": "Camera permission required", + "paiementCameraPerssionRequiredDescription": "To scan a QR Code, you must allow camera access.", + "paiementSettings": "Settings", + "paiementReceived": "Received", + "paiementSpent": "Spent", + "paiementNoTrasactionForThisMonth": "No transactions for this month", + "paiementNoTransactinon": "No transaction", + "paiementSellerRigths": "Seller rights", + "paiementCanBank": "Can collect payments", + "paiementCanSeeHistory": "Can view history", + "paiementCanCancelTransaction": "Can cancel transactions", + "paiementCanManageSellers": "Can manage sellers", + "paiementAddedSeller": "Seller added", + "paiementAddingSellerError": "Error while adding seller", + "paiementBank": "Collect", + "paiementSeeHistory": "View history", + "paiementCancelTransactions": "Cancel transactions", + "paiementManageSellers": "Manage sellers", + "paiementStructureAdmin": "Structure administrator", + "paiementRightsOf": "Rights of", + "paiementRightsUpdated": "Rights updated", + "paiementRightsUpdateError": "Error while updating rights", + "paiementDeleteSellerDescription": "Are you sure you want to delete this seller?", + "paiementDeletedSeller": "Seller deleted", + "paiementDeletingSellerError": "Error while deleting seller", + "paiementDeleteSeller": "Delete seller", + "paiementAdd": "Add", + "paiementAddSeller": "Add seller", + "paiementSellerError": "You are not a seller of this association", + "paiementSellersOf": "Sellers of", + "paiementModify": "Edit", + "paiementAStore": "an association", + "paiementStoreName": "Association name", + "paiementSuccessfullyAddedStore": "Association successfully added", + "paiementSuccessfullyModifiedStore": "Association successfully updated", + "paiementAddingStoreError": "Error while adding the association", + "paiementModifyingStoreError": "Error while updating the association", + "paiementRefund": "Refund", + "paiementDoneTransaction": "Transaction completed", + "paiementRefundAction": "Refund", + "paiementTotalDuringPeriod": "Total during the period", + "paiementMean": "Average: ", + "paiementTransaction": "Transaction", + "paiementTransferStructure": "Structure transfer", + "paiementYouAreTransferingStructureTo": "You are about to transfer the structure to ", + "paiementTransferStructureDescription": "The new manager will have access to all structure management features. You will receive an email to confirm this transfer. The link will only be active for 20 minutes. This action is irreversible. Are you sure you want to continue?", + "paiementTransferStructureError": "Error while transferring structure", + "paiementTransferStructureSuccess": "Structure transfer requested successfully", + "paiementNextAccountable": "Next responsible" } \ No newline at end of file diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 0f41f43003..9eae54a3dc 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -3424,410 +3424,394 @@ class AppLocalizationsEn extends AppLocalizations { String get modulePayment => 'Payment'; @override - String get paiementTopUp => 'Recharge'; + String get paiementTopUp => 'Top-up'; @override - String get paiementStoreManagement => 'Gestion des associations'; + String get paiementStoreManagement => 'Association management'; @override - String get paiementDeleteStore => 'Supprimer l\'association'; + String get paiementDeleteStore => 'Delete association'; @override String get paiementDeleteStoreDescription => - 'Voulez-vous vraiment supprimer cette association ?'; + 'Are you sure you want to delete this association?'; @override - String get paiementDeleteStoreError => - 'Impossible de supprimer l\'association'; + String get paiementDeleteStoreError => 'Unable to delete the association'; @override - String get paiementStoreDeleted => 'Association supprimée'; + String get paiementStoreDeleted => 'Association deleted'; @override - String get paiementAddThisDevice => 'Ajouter cet appareil'; + String get paiementAddThisDevice => 'Add this device'; @override - String get paiementThisDevice => '(cet appareil)'; + String get paiementThisDevice => '(this device)'; @override - String get paiementCancelled => 'Annulé'; + String get paiementCancelled => 'Cancelled'; @override - String get paiementThe => 'Le'; + String get paiementThe => 'The'; @override - String get paiementOf => 'de'; + String get paiementOf => 'of'; @override - String get paiementRefundedThe => 'Remboursé le'; + String get paiementRefundedThe => 'Refunded on'; @override - String get paiementAt => 'à'; + String get paiementAt => 'at'; @override - String get paiementPleaseAcceptTOS => - 'Veuillez accepter les Conditions Générales d\'Utilisation.'; + String get paiementPleaseAcceptTOS => 'Please accept the Terms of Service.'; @override - String get paiementAskDeviceActivation => - 'Demande d\'activation de l\'appareil'; + String get paiementAskDeviceActivation => 'Device activation request'; @override String get paiementDeviceActivationReceived => - 'La demande d\'activation est prise en compte, veuilliez consulter votre boite mail pour finaliser la démarche'; + 'The activation request has been received, please check your email to finalize the process'; @override - String get paiementRevokeDevice => 'Révoquer l\'appareil ?'; + String get paiementRevokeDevice => 'Revoke device?'; @override String get paiementRevokeDeviceDescription => - 'Vous ne pourrez plus utiliser cet appareil pour les paiements'; + 'You will no longer be able to use this device for payments'; @override - String get paiementDeviceRevoked => 'Appareil révoqué'; + String get paiementDeviceRevoked => 'Device revoked'; @override - String get paiementDeviceRevokingError => - 'Erreur lors de la révocation de l\'appareil'; + String get paiementDeviceRevokingError => 'Error while revoking device'; @override - String get paiementPleaseAcceptPopup => 'Veuillez autoriser les popups'; + String get paiementPleaseAcceptPopup => 'Please allow popups'; @override - String get paiementProceedSuccessfully => 'Paiement effectué avec succès'; + String get paiementProceedSuccessfully => 'Payment completed successfully'; @override - String get paiementCancelledTransaction => 'Paiement annulé'; + String get paiementCancelledTransaction => 'Payment cancelled'; @override String get paiementPleaseEnterMinAmount => - 'Veuillez entrer un montant supérieur à 1'; + 'Please enter an amount greater than 1'; @override - String get paiementMaxAmount => - 'Le montant maximum de votre portefeuille est de'; + String get paiementMaxAmount => 'The maximum wallet amount is'; @override - String get paiementPayWithHA => 'Payer avec HelloAsso'; + String get paiementPayWithHA => 'Pay with HelloAsso'; @override - String get paiementBalanceAfterTopUp => 'Solde après recharge :'; + String get paiementBalanceAfterTopUp => 'Balance after top-up:'; @override - String get paiementPersonalBalance => 'Solde personnel'; + String get paiementPersonalBalance => 'Personal balance'; @override - String get paiementDevices => 'Appareils'; + String get paiementDevices => 'Devices'; @override - String get paiementPay => 'Payer'; + String get paiementPay => 'Pay'; @override - String get paiementDeviceNotRegistered => 'Appareil non enregistré'; + String get paiementDeviceNotRegistered => 'Device not registered'; @override String get paiementDeviceNotRegisteredDescription => - 'Votre appareil n\'est pas encore enregistré. \nPour l\'enregistrer, veuillez vous rendre sur la page des appareils.'; + 'Your device is not registered yet. \nTo register it, please go to the devices page.'; @override - String get paiementAccessPage => 'Accéder à la page'; + String get paiementAccessPage => 'Access the page'; @override - String get paiementDeviceNotActivated => 'Appareil non activé'; + String get paiementDeviceNotActivated => 'Device not activated'; @override String get paiementDeviceNotActivatedDescription => - 'Votre appareil n\'est pas encore activé. \nPour l\'activer, veuillez vous rendre sur la page des appareils.'; + 'Your device is not yet activated. \nTo activate it, please go to the devices page.'; @override String get paiementReactivateRevokedDeviceDescription => - 'Votre appareil a été révoqué. \nPour le réactiver, veuillez vous rendre sur la page des appareils.'; + 'Your device has been revoked. \nTo reactivate it, please go to the devices page.'; @override - String get paiementDeviceRecoveryError => - 'Erreur lors de la récupération de l\'appareil'; + String get paiementDeviceRecoveryError => 'Error while retrieving device'; @override String get paiementStats => 'Stats'; @override - String get paimentTopUpAction => 'Recharger'; + String get paimentTopUpAction => 'Top-up'; @override - String get paiementGetBalanceError => - 'Erreur lors de la récupération du solde : '; + String get paiementGetBalanceError => 'Error while retrieving balance: '; @override - String get paiementLastTransactions => 'Dernières transactions'; + String get paiementLastTransactions => 'Latest transactions'; @override String get paiementGetTransactionsError => - 'Erreur lors de la récupération des transactions : '; + 'Error while retrieving transactions: '; @override - String get paiementStoreBalance => 'Solde associatif'; + String get paiementStoreBalance => 'Association balance'; @override - String get paiementScan => 'Scanner'; + String get paiementScan => 'Scan'; @override - String get paiementManagement => 'Gestion'; + String get paiementManagement => 'Management'; @override - String get paiementHistory => 'Historique'; + String get paiementHistory => 'History'; @override - String get paiementHandOver => 'Passation'; + String get paiementHandOver => 'Handover'; @override String get paiementStores => 'Associations'; @override - String get paiementAdmin => 'Administrateur'; + String get paiementAdmin => 'Administrator'; @override - String get paiementSuccededTransaction => 'Paiement réussi'; + String get paiementSuccededTransaction => 'Successful payment'; @override - String get paiementNewCGU => 'Nouvelles Conditions Générales d\'Utilisation'; + String get paiementNewCGU => 'New Terms of Service'; @override - String get paiementDecline => 'Refuser'; + String get paiementDecline => 'Decline'; @override - String get paiementAccept => 'Accepter'; + String get paiementAccept => 'Accept'; @override - String get paiementAmount => 'Montant'; + String get paiementAmount => 'Amount'; @override - String get paiementValidUntil => 'Valide jusqu\'à'; + String get paiementValidUntil => 'Valid until'; @override - String get paiementClose => 'Fermer'; + String get paiementClose => 'Close'; @override - String get paiementPleaseEnterValidAmount => - 'Veuillez entrer un montant valide'; + String get paiementPleaseEnterValidAmount => 'Please enter a valid amount'; @override - String get paiementPleaseAuthenticate => 'Veuillez vous authentifier'; + String get paiementPleaseAuthenticate => 'Please authenticate'; @override - String get paiementAthenticationRequired => - 'Authentification requise pour payer'; + String get paiementAthenticationRequired => 'Authentication required to pay'; @override - String get paiementNoThanks => 'Non merci'; + String get paiementNoThanks => 'No thanks'; @override - String get paiementAuthentificationFailed => 'Échec de l\'authentification'; + String get paiementAuthentificationFailed => 'Authentication failed'; @override - String get paiementPleaseAddDevice => - 'Veuillez ajouter cet appareil pour payer'; + String get paiementPleaseAddDevice => 'Please add this device to pay'; @override - String get paiementPayment => 'Paiement'; + String get paiementPayment => 'Payment'; @override - String get paiementBalanceAfterTransaction => 'Solde après paiement : '; + String get paiementBalanceAfterTransaction => 'Balance after payment: '; @override - String get paiementCancel => 'Annuler'; + String get paiementCancel => 'Cancel'; @override - String get paiementLimitedTo => 'Limité à'; + String get paiementLimitedTo => 'Limited to'; @override - String get paiementScanCode => 'Scanner un code'; + String get paiementScanCode => 'Scan a code'; @override - String get paiementNext => 'Suivant'; + String get paiementNext => 'Next'; @override - String get paiementCancelTransaction => 'Annuler la transaction'; + String get paiementCancelTransaction => 'Cancel transaction'; @override - String get paiementTransactionCancelled => 'Transaction annulée'; + String get paiementTransactionCancelled => 'Transaction cancelled'; @override String get paiementTransactionCancelledDescription => - 'Voulez-vous vraiment annuler la transaction de'; + 'Are you sure you want to cancel the transaction of'; @override String get paiementTransactionCancelledError => - 'Erreur lors de l\'annulation de la transaction'; + 'Error while cancelling the transaction'; @override - String get paiementNoMembership => 'Aucune adhésion'; + String get paiementNoMembership => 'No membership'; @override String get paiementNoMembershipDescription => - 'Ce produit n\'est pas disponnible pour les non-adhérents. Confirmer l\'encaissement ?'; + 'This product is not available to non-members. Confirm the payment?'; @override - String get paiementQRCodeAlreadyUsed => 'QR Code déjà utilisé'; + String get paiementQRCodeAlreadyUsed => 'QR Code already used'; @override - String get paiementCameraPermissionRequired => - 'Permission d\'accès à la caméra requise'; + String get paiementCameraPermissionRequired => 'Camera permission required'; @override String get paiementCameraPerssionRequiredDescription => - 'Pour scanner un QR Code, vous devez autoriser l\'accès à la caméra.'; + 'To scan a QR Code, you must allow camera access.'; @override - String get paiementSettings => 'Paramètres'; + String get paiementSettings => 'Settings'; @override - String get paiementReceived => 'Reçu'; + String get paiementReceived => 'Received'; @override - String get paiementSpent => 'Déboursé'; + String get paiementSpent => 'Spent'; @override String get paiementNoTrasactionForThisMonth => - 'Aucune transaction pour ce mois'; + 'No transactions for this month'; @override - String get paiementNoTransactinon => 'Aucune transaction'; + String get paiementNoTransactinon => 'No transaction'; @override - String get paiementSellerRigths => 'Droits du vendeur'; + String get paiementSellerRigths => 'Seller rights'; @override - String get paiementCanBank => 'Peut encaisser'; + String get paiementCanBank => 'Can collect payments'; @override - String get paiementCanSeeHistory => 'Peut voir l\'historique'; + String get paiementCanSeeHistory => 'Can view history'; @override - String get paiementCanCancelTransaction => 'Peut annuler des transactions'; + String get paiementCanCancelTransaction => 'Can cancel transactions'; @override - String get paiementCanManageSellers => 'Peut gérer les vendeurs'; + String get paiementCanManageSellers => 'Can manage sellers'; @override - String get paiementAddedSeller => 'Vendeur ajouté'; + String get paiementAddedSeller => 'Seller added'; @override - String get paiementAddingSellerError => 'Erreur lors de l\'ajout du vendeur'; + String get paiementAddingSellerError => 'Error while adding seller'; @override - String get paiementBank => 'Encaisser'; + String get paiementBank => 'Collect'; @override - String get paiementSeeHistory => 'Voir l\'historique'; + String get paiementSeeHistory => 'View history'; @override - String get paiementCancelTransactions => 'Annuler les transactions'; + String get paiementCancelTransactions => 'Cancel transactions'; @override - String get paiementManageSellers => 'Gérer les vendeurs'; + String get paiementManageSellers => 'Manage sellers'; @override - String get paiementStructureAdmin => 'Administrateur de la structure'; + String get paiementStructureAdmin => 'Structure administrator'; @override - String get paiementRightsOf => 'Droits de'; + String get paiementRightsOf => 'Rights of'; @override - String get paiementRightsUpdated => 'Droits mis à jour'; + String get paiementRightsUpdated => 'Rights updated'; @override - String get paiementRightsUpdateError => - 'Erreur lors de la mise à jour des droits'; + String get paiementRightsUpdateError => 'Error while updating rights'; @override String get paiementDeleteSellerDescription => - 'Voulez-vous vraiment supprimer ce vendeur ?'; + 'Are you sure you want to delete this seller?'; @override - String get paiementDeletedSeller => 'Vendeur supprimé'; + String get paiementDeletedSeller => 'Seller deleted'; @override - String get paiementDeletingSellerError => - 'Erreur lors de la suppression du vendeur'; + String get paiementDeletingSellerError => 'Error while deleting seller'; @override - String get paiementDeleteSeller => 'Supprimer le vendeur'; + String get paiementDeleteSeller => 'Delete seller'; @override - String get paiementAdd => 'Ajouter'; + String get paiementAdd => 'Add'; @override - String get paiementAddSeller => 'Ajouter un vendeur'; + String get paiementAddSeller => 'Add seller'; @override - String get paiementSellerError => - 'Vous n\'êtes pas vendeur de cette association'; + String get paiementSellerError => 'You are not a seller of this association'; @override - String get paiementSellersOf => 'Les vendeurs de'; + String get paiementSellersOf => 'Sellers of'; @override - String get paiementModify => 'Modifier'; + String get paiementModify => 'Edit'; @override - String get paiementAStore => 'une association'; + String get paiementAStore => 'an association'; @override - String get paiementStoreName => 'Nom de l\'association'; + String get paiementStoreName => 'Association name'; @override - String get paiementSuccessfullyAddedStore => - 'Association ajoutée avec succès'; + String get paiementSuccessfullyAddedStore => 'Association successfully added'; @override String get paiementSuccessfullyModifiedStore => - 'Association modifiée avec succès'; + 'Association successfully updated'; @override - String get paiementAddingStoreError => - 'Erreur lors de l\'ajout de l\'association'; + String get paiementAddingStoreError => 'Error while adding the association'; @override String get paiementModifyingStoreError => - 'Erreur lors de la modification de l\'association'; + 'Error while updating the association'; @override - String get paiementRefund => 'Remboursement'; + String get paiementRefund => 'Refund'; @override - String get paiementDoneTransaction => 'Transaction effectuée'; + String get paiementDoneTransaction => 'Transaction completed'; @override - String get paiementRefundAction => 'Rembourser'; + String get paiementRefundAction => 'Refund'; @override - String get paiementTotalDuringPeriod => 'Total sur la période'; + String get paiementTotalDuringPeriod => 'Total during the period'; @override - String get paiementMean => 'Moyenne : '; + String get paiementMean => 'Average: '; @override - String get paiementTransaction => 'ransaction'; + String get paiementTransaction => 'Transaction'; @override - String get paiementTransferStructure => 'Transfert de structure'; + String get paiementTransferStructure => 'Structure transfer'; @override String get paiementYouAreTransferingStructureTo => - 'Vous êtes sur le point de transférer la structure à '; + 'You are about to transfer the structure to '; @override String get paiementTransferStructureDescription => - 'Le nouveau responsable aura accès à toutes les fonctionnalités de gestion de la structure. Vous allez recevoir un email pour valider ce transfert. Le lien ne sera actif que pendant 20 minutes. Cette action est irréversible. Êtes-vous sûr de vouloir continuer ?'; + 'The new manager will have access to all structure management features. You will receive an email to confirm this transfer. The link will only be active for 20 minutes. This action is irreversible. Are you sure you want to continue?'; @override String get paiementTransferStructureError => - 'Erreur lors du transfert de la structure'; + 'Error while transferring structure'; @override String get paiementTransferStructureSuccess => - 'Transfert de structure demandé avec succès'; + 'Structure transfer requested successfully'; @override - String get paiementNextAccountable => 'Prochain responsable'; + String get paiementNextAccountable => 'Next responsible'; } From 04ae8652d6646d669a2d491da949277dc48a55f2 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Fri, 4 Jul 2025 21:32:50 +0200 Subject: [PATCH 061/473] fix lint --- lib/paiement/ui/pages/store_pages/add_edit_store.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/paiement/ui/pages/store_pages/add_edit_store.dart b/lib/paiement/ui/pages/store_pages/add_edit_store.dart index 511e6f3e95..a76df42c21 100644 --- a/lib/paiement/ui/pages/store_pages/add_edit_store.dart +++ b/lib/paiement/ui/pages/store_pages/add_edit_store.dart @@ -41,7 +41,7 @@ class AddEditStorePage extends HookConsumerWidget { children: [ const SizedBox(height: 10), AlignLeftText( - "${isEdit ? '${AppLocalizations.of(context)!.paiementModify}' : AppLocalizations.of(context)!.paiementAdd}} ${AppLocalizations.of(context)!.paiementAStore} ${structure.name}", + "${isEdit ? AppLocalizations.of(context)!.paiementModify : AppLocalizations.of(context)!.paiementAdd}} ${AppLocalizations.of(context)!.paiementAStore} ${structure.name}", padding: const EdgeInsets.symmetric(horizontal: 30), color: Colors.grey, ), From 7e8b89a998a4d59c0e731c14b6a05ea8c7e7b361 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Fri, 4 Jul 2025 21:51:37 +0200 Subject: [PATCH 062/473] format --- lib/admin/class/school.dart | 1 - .../add_edit_structure_page.dart | 17 +++++-- .../pages/groups/edit_group_page/results.dart | 8 ++- .../groups/edit_group_page/search_user.dart | 8 ++- .../add_edit_user_membership_page.dart | 12 +++-- lib/advert/tools/functions.dart | 1 - .../ui/pages/admin_page/admin_page.dart | 4 +- .../pages/form_page/add_edit_advert_page.dart | 24 ++++++--- lib/advert/ui/pages/main_page/main_page.dart | 4 +- .../pages/admin_pages/add_edit_room_page.dart | 33 +++++++++---- .../ui/pages/manager_page/list_booking.dart | 8 ++- .../ui/pages/admin_page/admin_page.dart | 4 +- .../ui/pages/main_page/session_card.dart | 4 +- .../pages/session_pages/add_edit_session.dart | 17 +++++-- lib/event/ui/pages/admin_page/list_event.dart | 8 ++- .../ui/pages/detail_page/detail_page.dart | 4 +- .../ui/pages/admin_page/on_going_loan.dart | 8 ++- .../item_group_page/add_edit_item_page.dart | 49 +++++++++---------- .../loan_group_page/add_edit_button.dart | 10 +++- .../loan_group_page/add_edit_loan_page.dart | 4 +- lib/navigation/ui/quit_dialog.dart | 6 ++- .../ui/components/copiabled_text.dart | 5 +- .../association_creation_page/text_entry.dart | 4 +- .../association_page/web_member_card.dart | 44 ++++++++++++----- .../membership_editor_page.dart | 4 +- .../creation_edit_page/ticket_handler.dart | 8 ++- .../ui/pages/raffle_page/confirm_payment.dart | 8 ++- .../ui/pages/add_edit_page.dart | 28 ++++++++--- lib/vote/ui/pages/admin_page/section_bar.dart | 4 +- .../admin_page/section_contender_items.dart | 8 ++- .../ui/pages/main_page/list_side_item.dart | 4 +- lib/vote/ui/pages/main_page/vote_button.dart | 3 +- 32 files changed, 252 insertions(+), 102 deletions(-) diff --git a/lib/admin/class/school.dart b/lib/admin/class/school.dart index c2aef50959..db3b2c0dde 100644 --- a/lib/admin/class/school.dart +++ b/lib/admin/class/school.dart @@ -1,4 +1,3 @@ - class School { School({required this.name, required this.id, required this.emailRegex}); late final String name; diff --git a/lib/admin/ui/pages/add_edit_structure_page/add_edit_structure_page.dart b/lib/admin/ui/pages/add_edit_structure_page/add_edit_structure_page.dart index 65b6184bae..3f94f45cde 100644 --- a/lib/admin/ui/pages/add_edit_structure_page/add_edit_structure_page.dart +++ b/lib/admin/ui/pages/add_edit_structure_page/add_edit_structure_page.dart @@ -67,7 +67,10 @@ class AddEditStructurePage extends HookConsumerWidget { : AppLocalizations.of(context)!.adminAddStructure, ), const SizedBox(height: 20), - TextEditing(controller: name, label: AppLocalizations.of(context)!.adminName), + TextEditing( + controller: name, + label: AppLocalizations.of(context)!.adminName, + ), AsyncChild( value: allAssociationMembershipList, builder: (context, allAssociationMembershipList) { @@ -163,8 +166,12 @@ class AddEditStructurePage extends HookConsumerWidget { displayToastWithContext( TypeMsg.msg, isEdit - ? AppLocalizations.of(context)!.adminEditedStructure - : AppLocalizations.of(context)!.adminAddedStructure, + ? AppLocalizations.of( + context, + )!.adminEditedStructure + : AppLocalizations.of( + context, + )!.adminAddedStructure, ); } else { displayToastWithContext( @@ -177,7 +184,9 @@ class AddEditStructurePage extends HookConsumerWidget { }, builder: (child) => AdminButton(child: child), child: Text( - isEdit ? AppLocalizations.of(context)!.adminEdit : AppLocalizations.of(context)!.adminAdd, + isEdit + ? AppLocalizations.of(context)!.adminEdit + : AppLocalizations.of(context)!.adminAdd, style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, diff --git a/lib/admin/ui/pages/groups/edit_group_page/results.dart b/lib/admin/ui/pages/groups/edit_group_page/results.dart index eaa6a0f8d2..f084866013 100644 --- a/lib/admin/ui/pages/groups/edit_group_page/results.dart +++ b/lib/admin/ui/pages/groups/edit_group_page/results.dart @@ -64,12 +64,16 @@ class MemberResults extends HookConsumerWidget { ); displayToastWithContext( TypeMsg.msg, - AppLocalizations.of(context)!.adminAddedMember, + AppLocalizations.of( + context, + )!.adminAddedMember, ); } else { displayToastWithContext( TypeMsg.error, - AppLocalizations.of(context)!.adminAddingError, + AppLocalizations.of( + context, + )!.adminAddingError, ); } }); diff --git a/lib/admin/ui/pages/groups/edit_group_page/search_user.dart b/lib/admin/ui/pages/groups/edit_group_page/search_user.dart index 21b2c78c9a..0fcf035643 100644 --- a/lib/admin/ui/pages/groups/edit_group_page/search_user.dart +++ b/lib/admin/ui/pages/groups/edit_group_page/search_user.dart @@ -110,7 +110,9 @@ class SearchUser extends HookConsumerWidget { showDialog( context: context, builder: (BuildContext context) => CustomDialogBox( - descriptions: AppLocalizations.of(context)!.adminRemoveGroupMember, + descriptions: AppLocalizations.of( + context, + )!.adminRemoveGroupMember, title: AppLocalizations.of(context)!.adminDeleting, onYes: () async { await tokenExpireWrapper(ref, () async { @@ -135,7 +137,9 @@ class SearchUser extends HookConsumerWidget { } else { displayToastWithContext( TypeMsg.msg, - AppLocalizations.of(context)!.adminUpdatingError, + AppLocalizations.of( + context, + )!.adminUpdatingError, ); } }); diff --git a/lib/admin/ui/pages/memberships/add_edit_user_membership_page/add_edit_user_membership_page.dart b/lib/admin/ui/pages/memberships/add_edit_user_membership_page/add_edit_user_membership_page.dart index 8ffb5903ec..a424e36211 100644 --- a/lib/admin/ui/pages/memberships/add_edit_user_membership_page/add_edit_user_membership_page.dart +++ b/lib/admin/ui/pages/memberships/add_edit_user_membership_page/add_edit_user_membership_page.dart @@ -110,7 +110,9 @@ class AddEditUserMembershipPage extends HookConsumerWidget { child: child, ), child: Text( - !isEdit ? AppLocalizations.of(context)!.adminAdd : AppLocalizations.of(context)!.adminEdit, + !isEdit + ? AppLocalizations.of(context)!.adminAdd + : AppLocalizations.of(context)!.adminEdit, style: const TextStyle( fontSize: 18, fontWeight: FontWeight.w600, @@ -164,7 +166,9 @@ class AddEditUserMembershipPage extends HookConsumerWidget { } else { displayToastWithContext( TypeMsg.error, - AppLocalizations.of(context)!.adminMembershipUpdatingError, + AppLocalizations.of( + context, + )!.adminMembershipUpdatingError, ); } } else { @@ -188,7 +192,9 @@ class AddEditUserMembershipPage extends HookConsumerWidget { } else { displayToastWithContext( TypeMsg.error, - AppLocalizations.of(context)!.adminMembershipAddingError, + AppLocalizations.of( + context, + )!.adminMembershipAddingError, ); } } diff --git a/lib/advert/tools/functions.dart b/lib/advert/tools/functions.dart index 7a497e6549..e07b1d9741 100644 --- a/lib/advert/tools/functions.dart +++ b/lib/advert/tools/functions.dart @@ -1,4 +1,3 @@ - import 'package:flutter/material.dart'; import 'package:titan/l10n/app_localizations.dart'; diff --git a/lib/advert/ui/pages/admin_page/admin_page.dart b/lib/advert/ui/pages/admin_page/admin_page.dart index 9ea0149243..3a7e21f47e 100644 --- a/lib/advert/ui/pages/admin_page/admin_page.dart +++ b/lib/advert/ui/pages/admin_page/admin_page.dart @@ -121,7 +121,9 @@ class AdvertAdminPage extends HookConsumerWidget { builder: (context) { return CustomDialogBox( title: AppLocalizations.of(context)!.advertDeleting, - descriptions: AppLocalizations.of(context)!.advertDeleteAdvert, + descriptions: AppLocalizations.of( + context, + )!.advertDeleteAdvert, onYes: () { advertListNotifier.deleteAdvert(advert); advertPostersNotifier.deleteE(advert.id, 0); diff --git a/lib/advert/ui/pages/form_page/add_edit_advert_page.dart b/lib/advert/ui/pages/form_page/add_edit_advert_page.dart index aef9bfa0b8..5da930c001 100644 --- a/lib/advert/ui/pages/form_page/add_edit_advert_page.dart +++ b/lib/advert/ui/pages/form_page/add_edit_advert_page.dart @@ -79,7 +79,9 @@ class AdvertAddEditAdvertPage extends HookConsumerWidget { FormField( validator: (e) { if (poster.value == null && !isEdit) { - return AppLocalizations.of(context)!.advertChoosingPoster; + return AppLocalizations.of( + context, + )!.advertChoosingPoster; } return null; }, @@ -176,7 +178,9 @@ class AdvertAddEditAdvertPage extends HookConsumerWidget { FormField>( validator: (e) { if (selectedAnnouncers.isEmpty) { - return AppLocalizations.of(context)!.advertChoosingAnnouncer; + return AppLocalizations.of( + context, + )!.advertChoosingAnnouncer; } return null; }, @@ -241,7 +245,9 @@ class AdvertAddEditAdvertPage extends HookConsumerWidget { if (isEdit) { displayAdvertToastWithContext( TypeMsg.msg, - AppLocalizations.of(context)!.advertEditedAdvert, + AppLocalizations.of( + context, + )!.advertEditedAdvert, ); advertList.maybeWhen( data: (list) { @@ -257,7 +263,9 @@ class AdvertAddEditAdvertPage extends HookConsumerWidget { } else { displayAdvertToastWithContext( TypeMsg.msg, - AppLocalizations.of(context)!.advertAddedAdvert, + AppLocalizations.of( + context, + )!.advertAddedAdvert, ); advertList.maybeWhen( data: (list) { @@ -273,7 +281,9 @@ class AdvertAddEditAdvertPage extends HookConsumerWidget { } else { displayAdvertToastWithContext( TypeMsg.error, - AppLocalizations.of(context)!.advertEditingError, + AppLocalizations.of( + context, + )!.advertEditingError, ); } }); @@ -281,7 +291,9 @@ class AdvertAddEditAdvertPage extends HookConsumerWidget { displayToast( context, TypeMsg.error, - AppLocalizations.of(context)!.advertIncorrectOrMissingFields, + AppLocalizations.of( + context, + )!.advertIncorrectOrMissingFields, ); } }, diff --git a/lib/advert/ui/pages/main_page/main_page.dart b/lib/advert/ui/pages/main_page/main_page.dart index 91221579f9..f273e974b3 100644 --- a/lib/advert/ui/pages/main_page/main_page.dart +++ b/lib/advert/ui/pages/main_page/main_page.dart @@ -72,7 +72,9 @@ class AdvertMainPage extends HookConsumerWidget { AdvertRouter.addRemAnnouncer, ); }, - text: AppLocalizations.of(context)!.advertManagement, + text: AppLocalizations.of( + context, + )!.advertManagement, ), ], ), diff --git a/lib/booking/ui/pages/admin_pages/add_edit_room_page.dart b/lib/booking/ui/pages/admin_pages/add_edit_room_page.dart index 66f89d055a..4517616ee7 100644 --- a/lib/booking/ui/pages/admin_pages/add_edit_room_page.dart +++ b/lib/booking/ui/pages/admin_pages/add_edit_room_page.dart @@ -113,21 +113,29 @@ class AddEditRoomPage extends HookConsumerWidget { isEdit ? displayToastWithContext( TypeMsg.msg, - AppLocalizations.of(context)!.bookingEditedRoom, + AppLocalizations.of( + context, + )!.bookingEditedRoom, ) : displayToastWithContext( TypeMsg.msg, - AppLocalizations.of(context)!.bookingAddedRoom, + AppLocalizations.of( + context, + )!.bookingAddedRoom, ); } else { isEdit ? displayToastWithContext( TypeMsg.error, - AppLocalizations.of(context)!.bookingEditionError, + AppLocalizations.of( + context, + )!.bookingEditionError, ) : displayToastWithContext( TypeMsg.error, - AppLocalizations.of(context)!.bookingAddingError, + AppLocalizations.of( + context, + )!.bookingAddingError, ); } }); @@ -144,8 +152,9 @@ class AddEditRoomPage extends HookConsumerWidget { await showDialog( context: context, builder: (context) => CustomDialogBox( - descriptions: - AppLocalizations.of(context)!.bookingDeleteRoomConfirmation, + descriptions: AppLocalizations.of( + context, + )!.bookingDeleteRoomConfirmation, onYes: () async { final value = await roomListNotifier.deleteRoom( room, @@ -154,16 +163,22 @@ class AddEditRoomPage extends HookConsumerWidget { QR.back(); displayToastWithContext( TypeMsg.msg, - AppLocalizations.of(context)!.bookingDeletedRoom, + AppLocalizations.of( + context, + )!.bookingDeletedRoom, ); } else { displayToastWithContext( TypeMsg.error, - AppLocalizations.of(context)!.bookingDeletingError, + AppLocalizations.of( + context, + )!.bookingDeletingError, ); } }, - title: AppLocalizations.of(context)!.bookingDeleteBooking, + title: AppLocalizations.of( + context, + )!.bookingDeleteBooking, ), ); }); diff --git a/lib/booking/ui/pages/manager_page/list_booking.dart b/lib/booking/ui/pages/manager_page/list_booking.dart index f7609a2fc7..c5a928de3f 100644 --- a/lib/booking/ui/pages/manager_page/list_booking.dart +++ b/lib/booking/ui/pages/manager_page/list_booking.dart @@ -120,7 +120,9 @@ class ListBooking extends HookConsumerWidget { builder: (context) { return CustomDialogBox( title: AppLocalizations.of(context)!.bookingConfirm, - descriptions: AppLocalizations.of(context)!.bookingConfirmBooking, + descriptions: AppLocalizations.of( + context, + )!.bookingConfirmBooking, onYes: () async { await tokenExpireWrapper(ref, () async { Booking newBooking = e.copyWith( @@ -157,7 +159,9 @@ class ListBooking extends HookConsumerWidget { builder: (context) { return CustomDialogBox( title: AppLocalizations.of(context)!.bookingDecline, - descriptions: AppLocalizations.of(context)!.bookingDeclineBooking, + descriptions: AppLocalizations.of( + context, + )!.bookingDeclineBooking, onYes: () async { await tokenExpireWrapper(ref, () async { Booking newBooking = e.copyWith( diff --git a/lib/cinema/ui/pages/admin_page/admin_page.dart b/lib/cinema/ui/pages/admin_page/admin_page.dart index ece68e808b..46718e86ff 100644 --- a/lib/cinema/ui/pages/admin_page/admin_page.dart +++ b/lib/cinema/ui/pages/admin_page/admin_page.dart @@ -61,7 +61,9 @@ class AdminPage extends HookConsumerWidget { builder: (context) { return CustomDialogBox( title: AppLocalizations.of(context)!.cinemaDeleting, - descriptions: AppLocalizations.of(context)!.cinemaDeleteSession, + descriptions: AppLocalizations.of( + context, + )!.cinemaDeleteSession, onYes: () { sessionListNotifier.deleteSession(session); }, diff --git a/lib/cinema/ui/pages/main_page/session_card.dart b/lib/cinema/ui/pages/main_page/session_card.dart index 87934b23d1..afa5f5eb9d 100644 --- a/lib/cinema/ui/pages/main_page/session_card.dart +++ b/lib/cinema/ui/pages/main_page/session_card.dart @@ -164,7 +164,9 @@ class SessionCard extends HookConsumerWidget { const SizedBox(height: 10), Text( session.overview ?? - AppLocalizations.of(context)!.cinemaNoOverview, + AppLocalizations.of( + context, + )!.cinemaNoOverview, textAlign: TextAlign.center, style: const TextStyle(fontSize: 16), ), diff --git a/lib/cinema/ui/pages/session_pages/add_edit_session.dart b/lib/cinema/ui/pages/session_pages/add_edit_session.dart index 47b40a18fe..351f051a83 100644 --- a/lib/cinema/ui/pages/session_pages/add_edit_session.dart +++ b/lib/cinema/ui/pages/session_pages/add_edit_session.dart @@ -83,7 +83,9 @@ class AddEditSessionPage extends HookConsumerWidget { controller: tmdbUrl, cursorColor: Colors.black, decoration: InputDecoration( - labelText: AppLocalizations.of(context)!.cinemaImportFromTMDB, + labelText: AppLocalizations.of( + context, + )!.cinemaImportFromTMDB, labelStyle: const TextStyle( color: Colors.black, fontSize: 20, @@ -183,7 +185,10 @@ class AddEditSessionPage extends HookConsumerWidget { ) : Image.memory(logo.value!, fit: BoxFit.cover), const SizedBox(height: 30), - TextEntry(label: AppLocalizations.of(context)!.cinemaName, controller: name), + TextEntry( + label: AppLocalizations.of(context)!.cinemaName, + controller: name, + ), const SizedBox(height: 30), TextEntry( label: AppLocalizations.of(context)!.cinemaPosterUrl, @@ -323,12 +328,16 @@ class AddEditSessionPage extends HookConsumerWidget { displayToast( context, TypeMsg.error, - AppLocalizations.of(context)!.cinemaIncorrectOrMissingFields, + AppLocalizations.of( + context, + )!.cinemaIncorrectOrMissingFields, ); } }, child: Text( - isEdit ? AppLocalizations.of(context)!.cinemaEdit : AppLocalizations.of(context)!.cinemaAdd, + isEdit + ? AppLocalizations.of(context)!.cinemaEdit + : AppLocalizations.of(context)!.cinemaAdd, style: const TextStyle( color: Colors.white, fontSize: 25, diff --git a/lib/event/ui/pages/admin_page/list_event.dart b/lib/event/ui/pages/admin_page/list_event.dart index 1b105fc179..7022e09598 100644 --- a/lib/event/ui/pages/admin_page/list_event.dart +++ b/lib/event/ui/pages/admin_page/list_event.dart @@ -103,7 +103,9 @@ class ListEvent extends HookConsumerWidget { builder: (context) { return CustomDialogBox( title: AppLocalizations.of(context)!.eventConfirm, - descriptions: AppLocalizations.of(context)!.eventConfirmEvent, + descriptions: AppLocalizations.of( + context, + )!.eventConfirmEvent, onYes: () async { await tokenExpireWrapper(ref, () async { eventListNotifier @@ -127,7 +129,9 @@ class ListEvent extends HookConsumerWidget { builder: (context) { return CustomDialogBox( title: AppLocalizations.of(context)!.eventDecline, - descriptions: AppLocalizations.of(context)!.eventDeclineEvent, + descriptions: AppLocalizations.of( + context, + )!.eventDeclineEvent, onYes: () async { await tokenExpireWrapper(ref, () async { eventListNotifier diff --git a/lib/event/ui/pages/detail_page/detail_page.dart b/lib/event/ui/pages/detail_page/detail_page.dart index 147020a3c4..b7f0dc08f0 100644 --- a/lib/event/ui/pages/detail_page/detail_page.dart +++ b/lib/event/ui/pages/detail_page/detail_page.dart @@ -97,7 +97,9 @@ class DetailPage extends HookConsumerWidget { const SizedBox(height: 30), Text( event.applicant.phone ?? - AppLocalizations.of(context)!.eventNoPhoneRegistered, + AppLocalizations.of( + context, + )!.eventNoPhoneRegistered, style: const TextStyle(fontSize: 25), ), const SizedBox(height: 50), diff --git a/lib/loan/ui/pages/admin_page/on_going_loan.dart b/lib/loan/ui/pages/admin_page/on_going_loan.dart index 8778919a2e..80e2765845 100644 --- a/lib/loan/ui/pages/admin_page/on_going_loan.dart +++ b/lib/loan/ui/pages/admin_page/on_going_loan.dart @@ -143,7 +143,9 @@ class OnGoingLoan extends HookConsumerWidget { } else { displayToastWithContext( TypeMsg.error, - AppLocalizations.of(context)!.loanExtendingError, + AppLocalizations.of( + context, + )!.loanExtendingError, ); } }); @@ -157,7 +159,9 @@ class OnGoingLoan extends HookConsumerWidget { context: context, builder: (context) => CustomDialogBox( title: AppLocalizations.of(context)!.loanReturnLoan, - descriptions: AppLocalizations.of(context)!.loanReturnLoanDescription, + descriptions: AppLocalizations.of( + context, + )!.loanReturnLoanDescription, onYes: () async { await tokenExpireWrapper(ref, () async { final loanItemsId = e.itemsQuantity diff --git a/lib/loan/ui/pages/item_group_page/add_edit_item_page.dart b/lib/loan/ui/pages/item_group_page/add_edit_item_page.dart index 12cd9669c4..fd98f9048e 100644 --- a/lib/loan/ui/pages/item_group_page/add_edit_item_page.dart +++ b/lib/loan/ui/pages/item_group_page/add_edit_item_page.dart @@ -62,7 +62,10 @@ class AddEditItemPage extends HookConsumerWidget { child: Column( children: [ const SizedBox(height: 30), - TextEntry(label: AppLocalizations.of(context)!.loanName, controller: name), + TextEntry( + label: AppLocalizations.of(context)!.loanName, + controller: name, + ), const SizedBox(height: 30), TextEntry( keyboardType: TextInputType.number, @@ -93,6 +96,12 @@ class AddEditItemPage extends HookConsumerWidget { child: child, ), onTap: () async { + final updatedItemMsg = isEdit + ? AppLocalizations.of(context)!.loanUpdatedItem + : AppLocalizations.of(context)!.loanAddedObject; + final updatedItemErrorMsg = isEdit + ? AppLocalizations.of(context)!.loanUpdatingError + : AppLocalizations.of(context)!.loanAddingError; if (key.currentState == null) { return; } @@ -123,41 +132,31 @@ class AddEditItemPage extends HookConsumerWidget { loaner, await itemListNotifier.copy(), ); - if (isEdit) { - displayToastWithContext( - TypeMsg.msg, - AppLocalizations.of(context)!.loanUpdatedItem, - ); - } else { - displayToastWithContext( - TypeMsg.msg, - AppLocalizations.of(context)!.loanAddedObject, - ); - } + displayToastWithContext( + TypeMsg.msg, + updatedItemMsg, + ); } else { - if (isEdit) { - displayToastWithContext( - TypeMsg.error, - AppLocalizations.of(context)!.loanUpdatingError, - ); - } else { - displayToastWithContext( - TypeMsg.error, - AppLocalizations.of(context)!.loanAddingError, - ); - } + displayToastWithContext( + TypeMsg.error, + updatedItemErrorMsg, + ); } }); } else { displayToast( context, TypeMsg.error, - AppLocalizations.of(context)!.loanIncorrectOrMissingFields, + AppLocalizations.of( + context, + )!.loanIncorrectOrMissingFields, ); } }, child: Text( - isEdit ? AppLocalizations.of(context)!.loanEdit : AppLocalizations.of(context)!.loanAdd, + isEdit + ? AppLocalizations.of(context)!.loanEdit + : AppLocalizations.of(context)!.loanAdd, style: const TextStyle( color: Colors.white, fontSize: 25, diff --git a/lib/loan/ui/pages/loan_group_page/add_edit_button.dart b/lib/loan/ui/pages/loan_group_page/add_edit_button.dart index 25b4cc3a64..c6139db8f2 100644 --- a/lib/loan/ui/pages/loan_group_page/add_edit_button.dart +++ b/lib/loan/ui/pages/loan_group_page/add_edit_button.dart @@ -58,7 +58,11 @@ class AddEditButton extends HookConsumerWidget { AppLocalizations.of(context)!.loanInvalidDates, ); } else if (borrower.id.isEmpty) { - displayToast(context, TypeMsg.error, AppLocalizations.of(context)!.loanNoBorrower); + displayToast( + context, + TypeMsg.error, + AppLocalizations.of(context)!.loanNoBorrower, + ); } else { await items.when( data: (itemList) async { @@ -143,7 +147,9 @@ class AddEditButton extends HookConsumerWidget { }); }, child: Text( - isEdit ? AppLocalizations.of(context)!.loanEdit : AppLocalizations.of(context)!.loanAdd, + isEdit + ? AppLocalizations.of(context)!.loanEdit + : AppLocalizations.of(context)!.loanAdd, style: const TextStyle( color: Colors.white, fontSize: 25, diff --git a/lib/loan/ui/pages/loan_group_page/add_edit_loan_page.dart b/lib/loan/ui/pages/loan_group_page/add_edit_loan_page.dart index 3c84240cd5..bc3d701a73 100644 --- a/lib/loan/ui/pages/loan_group_page/add_edit_loan_page.dart +++ b/lib/loan/ui/pages/loan_group_page/add_edit_loan_page.dart @@ -128,7 +128,9 @@ class AddEditLoanPage extends HookConsumerWidget { displayToast( context, TypeMsg.error, - AppLocalizations.of(context)!.loanIncorrectOrMissingFields, + AppLocalizations.of( + context, + )!.loanIncorrectOrMissingFields, ); } }, diff --git a/lib/navigation/ui/quit_dialog.dart b/lib/navigation/ui/quit_dialog.dart index f8ecb1619b..cad74e3d43 100644 --- a/lib/navigation/ui/quit_dialog.dart +++ b/lib/navigation/ui/quit_dialog.dart @@ -36,7 +36,11 @@ class QuitDialog extends HookConsumerWidget { ref.watch(firebaseTokenExpirationProvider.notifier).reset(); } isCachingNotifier.set(false); - displayToast(context, TypeMsg.msg, AppLocalizations.of(context)!.drawerLogOut); + displayToast( + context, + TypeMsg.msg, + AppLocalizations.of(context)!.drawerLogOut, + ); displayQuitNotifier.setDisplay(false); }, onNo: () { diff --git a/lib/phonebook/ui/components/copiabled_text.dart b/lib/phonebook/ui/components/copiabled_text.dart index bf16087fda..e368100546 100644 --- a/lib/phonebook/ui/components/copiabled_text.dart +++ b/lib/phonebook/ui/components/copiabled_text.dart @@ -27,7 +27,10 @@ class CopiabledText extends StatelessWidget { style: style, onTap: () { Clipboard.setData(ClipboardData(text: data)); - displayToastWithContext(TypeMsg.msg, AppLocalizations.of(context)!.phonebookCopied); + displayToastWithContext( + TypeMsg.msg, + AppLocalizations.of(context)!.phonebookCopied, + ); }, ); } diff --git a/lib/phonebook/ui/pages/association_creation_page/text_entry.dart b/lib/phonebook/ui/pages/association_creation_page/text_entry.dart index f594c0f80d..de09a541aa 100644 --- a/lib/phonebook/ui/pages/association_creation_page/text_entry.dart +++ b/lib/phonebook/ui/pages/association_creation_page/text_entry.dart @@ -45,7 +45,9 @@ class AddAssociationTextEntry extends StatelessWidget { ? null : (value) { if (value == null || value.isEmpty) { - return AppLocalizations.of(context)!.adminEmptyFieldError; + return AppLocalizations.of( + context, + )!.adminEmptyFieldError; } return null; }, diff --git a/lib/phonebook/ui/pages/association_page/web_member_card.dart b/lib/phonebook/ui/pages/association_page/web_member_card.dart index ae9a08244b..dc63ef763f 100644 --- a/lib/phonebook/ui/pages/association_page/web_member_card.dart +++ b/lib/phonebook/ui/pages/association_page/web_member_card.dart @@ -137,20 +137,28 @@ class WebMemberCard extends HookConsumerWidget { mainAxisAlignment: MainAxisAlignment.start, children: [ CardField( - label: AppLocalizations.of(context)!.phonebookPromotion, + label: AppLocalizations.of( + context, + )!.phonebookPromotion, value: member.member.promotion == 0 - ? AppLocalizations.of(context)!.phonebookPromoNotGiven + ? AppLocalizations.of( + context, + )!.phonebookPromoNotGiven : member.member.promotion < 100 ? "20${member.member.promotion}" : member.member.promotion.toString(), ), CardField( - label: AppLocalizations.of(context)!.phonebookEmail, + label: AppLocalizations.of( + context, + )!.phonebookEmail, value: member.member.email, ), if (member.member.phone != null) CardField( - label: AppLocalizations.of(context)!.phonebookPhone, + label: AppLocalizations.of( + context, + )!.phonebookPhone, value: member.member.phone!, ), ], @@ -165,7 +173,9 @@ class WebMemberCard extends HookConsumerWidget { Text( textAlign: TextAlign.right, assoMembership == null - ? AppLocalizations.of(context)!.phonebookNoMemberRole + ? AppLocalizations.of( + context, + )!.phonebookNoMemberRole : assoMembership.apparentName, style: const TextStyle( fontWeight: FontWeight.bold, @@ -218,7 +228,9 @@ class WebMemberCard extends HookConsumerWidget { CardField( label: AppLocalizations.of(context)!.phonebookPromotion, value: member.member.promotion == 0 - ? AppLocalizations.of(context)!.phonebookPromoNotGiven + ? AppLocalizations.of( + context, + )!.phonebookPromoNotGiven : member.member.promotion < 100 ? "20${member.member.promotion}" : member.member.promotion.toString(), @@ -229,12 +241,16 @@ class WebMemberCard extends HookConsumerWidget { child: Row( children: [ CardField( - label: AppLocalizations.of(context)!.phonebookEmail, + label: AppLocalizations.of( + context, + )!.phonebookEmail, value: member.member.email, ), if (member.member.phone != null) CardField( - label: AppLocalizations.of(context)!.phonebookPhone, + label: AppLocalizations.of( + context, + )!.phonebookPhone, value: member.member.phone!, ), ], @@ -244,13 +260,17 @@ class WebMemberCard extends HookConsumerWidget { Column( children: [ CardField( - label: AppLocalizations.of(context)!.phonebookEmail, + label: AppLocalizations.of( + context, + )!.phonebookEmail, value: member.member.email, showLabel: false, ), if (member.member.phone != null) CardField( - label: AppLocalizations.of(context)!.phonebookPhone, + label: AppLocalizations.of( + context, + )!.phonebookPhone, value: member.member.phone!, showLabel: false, ), @@ -263,7 +283,9 @@ class WebMemberCard extends HookConsumerWidget { Text( textAlign: TextAlign.right, assoMembership == null - ? AppLocalizations.of(context)!.phonebookNoMemberRole + ? AppLocalizations.of( + context, + )!.phonebookNoMemberRole : assoMembership.apparentName, style: const TextStyle( fontWeight: FontWeight.bold, diff --git a/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart b/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart index 3d696106dd..4259fc9e9f 100644 --- a/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart +++ b/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart @@ -233,7 +233,9 @@ class MembershipEditorPage extends HookConsumerWidget { .isNotEmpty) { displayToastWithContext( TypeMsg.msg, - AppLocalizations.of(context)!.phonebookExistingMembership, + AppLocalizations.of( + context, + )!.phonebookExistingMembership, ); return; } diff --git a/lib/raffle/ui/pages/creation_edit_page/ticket_handler.dart b/lib/raffle/ui/pages/creation_edit_page/ticket_handler.dart index d271a08886..c6d0886d53 100644 --- a/lib/raffle/ui/pages/creation_edit_page/ticket_handler.dart +++ b/lib/raffle/ui/pages/creation_edit_page/ticket_handler.dart @@ -125,12 +125,16 @@ class TicketHandler extends HookConsumerWidget { if (value) { displayToastWithContext( TypeMsg.msg, - AppLocalizations.of(context)!.raffleDeletedTicket, + AppLocalizations.of( + context, + )!.raffleDeletedTicket, ); } else { displayToastWithContext( TypeMsg.error, - AppLocalizations.of(context)!.raffleDeletingError, + AppLocalizations.of( + context, + )!.raffleDeletingError, ); } }); diff --git a/lib/raffle/ui/pages/raffle_page/confirm_payment.dart b/lib/raffle/ui/pages/raffle_page/confirm_payment.dart index adbd24297c..a28c61797c 100644 --- a/lib/raffle/ui/pages/raffle_page/confirm_payment.dart +++ b/lib/raffle/ui/pages/raffle_page/confirm_payment.dart @@ -256,12 +256,16 @@ class ConfirmPaymentDialog extends HookConsumerWidget { ); displayToastWithContext( TypeMsg.msg, - AppLocalizations.of(context)!.raffleBoughtTicket, + AppLocalizations.of( + context, + )!.raffleBoughtTicket, ); } else { displayToastWithContext( TypeMsg.error, - AppLocalizations.of(context)!.raffleAddingError, + AppLocalizations.of( + context, + )!.raffleAddingError, ); } navigationPop(); diff --git a/lib/recommendation/ui/pages/add_edit_page.dart b/lib/recommendation/ui/pages/add_edit_page.dart index 2bd174d5f6..c0e0a12c9d 100644 --- a/lib/recommendation/ui/pages/add_edit_page.dart +++ b/lib/recommendation/ui/pages/add_edit_page.dart @@ -78,7 +78,9 @@ class AddEditRecommendationPage extends HookConsumerWidget { FormField( validator: (e) { if (logoBytes.value == null && !isEdit) { - return AppLocalizations.of(context)!.recommendationAddImage; + return AppLocalizations.of( + context, + )!.recommendationAddImage; } return null; }, @@ -128,7 +130,9 @@ class AddEditRecommendationPage extends HookConsumerWidget { minLines: 5, maxLines: 50, keyboardType: TextInputType.multiline, - label: AppLocalizations.of(context)!.recommendationDescription, + label: AppLocalizations.of( + context, + )!.recommendationDescription, controller: description, ), const SizedBox(height: 50), @@ -166,7 +170,9 @@ class AddEditRecommendationPage extends HookConsumerWidget { ); displayAdvertToastWithContext( TypeMsg.msg, - AppLocalizations.of(context)!.recommendationEditedRecommendation, + AppLocalizations.of( + context, + )!.recommendationEditedRecommendation, ); recommendationList.maybeWhen( data: (list) { @@ -183,7 +189,9 @@ class AddEditRecommendationPage extends HookConsumerWidget { } else { displayAdvertToastWithContext( TypeMsg.msg, - AppLocalizations.of(context)!.recommendationAddedRecommendation, + AppLocalizations.of( + context, + )!.recommendationAddedRecommendation, ); recommendationList.maybeWhen( data: (list) { @@ -202,15 +210,21 @@ class AddEditRecommendationPage extends HookConsumerWidget { displayAdvertToastWithContext( TypeMsg.error, isEdit - ? AppLocalizations.of(context)!.recommendationEditingError - : AppLocalizations.of(context)!.recommendationAddingError, + ? AppLocalizations.of( + context, + )!.recommendationEditingError + : AppLocalizations.of( + context, + )!.recommendationAddingError, ); } } else { displayToast( context, TypeMsg.error, - AppLocalizations.of(context)!.recommendationIncorrectOrMissingFields, + AppLocalizations.of( + context, + )!.recommendationIncorrectOrMissingFields, ); } }, diff --git a/lib/vote/ui/pages/admin_page/section_bar.dart b/lib/vote/ui/pages/admin_page/section_bar.dart index 6de84dbfac..af4c299cb3 100644 --- a/lib/vote/ui/pages/admin_page/section_bar.dart +++ b/lib/vote/ui/pages/admin_page/section_bar.dart @@ -63,7 +63,9 @@ class SectionBar extends HookConsumerWidget { context: context, builder: (context) => CustomDialogBox( title: AppLocalizations.of(context)!.voteDeleteSection, - descriptions: AppLocalizations.of(context)!.voteDeleteSectionDescription, + descriptions: AppLocalizations.of( + context, + )!.voteDeleteSectionDescription, onYes: () async { final result = await sectionsNotifier.deleteSection(key); if (result) { diff --git a/lib/vote/ui/pages/admin_page/section_contender_items.dart b/lib/vote/ui/pages/admin_page/section_contender_items.dart index f1be356d0e..90df07c1aa 100644 --- a/lib/vote/ui/pages/admin_page/section_contender_items.dart +++ b/lib/vote/ui/pages/admin_page/section_contender_items.dart @@ -91,7 +91,9 @@ class SectionContenderItems extends HookConsumerWidget { builder: (context) { return CustomDialogBox( title: AppLocalizations.of(context)!.voteDeletePretendance, - descriptions: AppLocalizations.of(context)!.voteDeletePretendanceDesc, + descriptions: AppLocalizations.of( + context, + )!.voteDeletePretendanceDesc, onYes: () { tokenExpireWrapper(ref, () async { final value = await contenderListNotifier.deleteContender( @@ -108,7 +110,9 @@ class SectionContenderItems extends HookConsumerWidget { } else { displayVoteToastWithContext( TypeMsg.error, - AppLocalizations.of(context)!.votePretendanceNotDeleted, + AppLocalizations.of( + context, + )!.votePretendanceNotDeleted, ); } }); diff --git a/lib/vote/ui/pages/main_page/list_side_item.dart b/lib/vote/ui/pages/main_page/list_side_item.dart index dd6a70b247..c17886b1f8 100644 --- a/lib/vote/ui/pages/main_page/list_side_item.dart +++ b/lib/vote/ui/pages/main_page/list_side_item.dart @@ -48,7 +48,9 @@ class ListSideItem extends HookConsumerWidget { context: context, builder: (context) => CustomDialogBox( title: AppLocalizations.of(context)!.voteWarning, - descriptions: AppLocalizations.of(context)!.voteWarningMessage, + descriptions: AppLocalizations.of( + context, + )!.voteWarningMessage, onYes: () { selectedContenderNotifier.clear(); animation.forward(from: 0); diff --git a/lib/vote/ui/pages/main_page/vote_button.dart b/lib/vote/ui/pages/main_page/vote_button.dart index 5414906b3d..cbf3a2782d 100644 --- a/lib/vote/ui/pages/main_page/vote_button.dart +++ b/lib/vote/ui/pages/main_page/vote_button.dart @@ -107,7 +107,8 @@ class VoteButton extends HookConsumerWidget { child: Center( child: Text( selectedContender.id != "" - ? AppLocalizations.of(context)!.voteVoteFor + selectedContender.name + ? AppLocalizations.of(context)!.voteVoteFor + + selectedContender.name : alreadyVotedSection.contains(section.id) ? AppLocalizations.of(context)!.voteAlreadyVoted : s == Status.open From 61d6a075edcb9389bbdbfe7300ed69cefc2b624c Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 5 Jul 2025 12:51:24 +0200 Subject: [PATCH 063/473] Fix context --- .../add_edit_structure_page.dart | 16 ++--- .../groups/add_group_page/add_group_page.dart | 16 ++--- .../add_loaner_page/add_loaner_page.dart | 16 +++-- .../edit_group_page/edit_group_page.dart | 14 ++-- .../pages/groups/edit_group_page/results.dart | 14 ++-- .../groups/edit_group_page/search_user.dart | 12 ++-- .../pages/groups/group_page/group_page.dart | 16 +++-- .../add_edit_user_membership_page.dart | 30 ++++---- ...ciation_membership_information_editor.dart | 11 +-- ...ation_membership_member_editable_card.dart | 16 ++--- .../association_membership_page.dart | 32 +++++---- .../add_school_page/add_school_page.dart | 16 ++--- .../edit_school_page/edit_school_page.dart | 13 ++-- .../pages/structure_page/structure_page.dart | 16 +++-- .../pages/form_page/add_edit_advert_page.dart | 21 +++--- .../form_page/add_rem_announcer_page.dart | 16 +++-- lib/amap/ui/components/order_ui.dart | 14 ++-- lib/amap/ui/pages/admin_page/delivery_ui.dart | 70 +++++++++++-------- .../ui/pages/admin_page/product_handler.dart | 16 +++-- .../ui/pages/admin_page/user_cash_ui.dart | 14 ++-- .../add_edit_delivery_cmd_page.dart | 29 ++++---- .../detail_delivery_page/order_detail_ui.dart | 10 ++- .../product_choice_button.dart | 29 ++++---- lib/amap/ui/pages/presentation_page/text.dart | 5 +- .../pages/product_pages/add_edit_product.dart | 45 ++++++------ .../admin_pages/add_edit_manager_page.dart | 54 ++++++-------- .../pages/admin_pages/add_edit_room_page.dart | 51 +++++--------- .../booking_pages/add_edit_booking_page.dart | 28 ++++---- .../pages/session_pages/add_edit_session.dart | 20 ++++-- lib/event/ui/components/event_ui.dart | 16 +++-- .../event_pages/add_edit_event_page.dart | 28 ++++---- lib/l10n/app_en.arb | 6 +- lib/l10n/app_localizations_en.dart | 6 +- .../ui/pages/admin_page/loaners_items.dart | 10 ++- .../ui/pages/admin_page/on_going_loan.dart | 22 ++++-- .../loan_group_page/add_edit_button.dart | 30 +++----- .../create_account_page.dart | 16 ++--- .../ui/pages/forget_page/forget_page.dart | 13 ++-- .../recover_password_page.dart | 16 ++--- .../ui/pages/register_page/register_page.dart | 15 ++-- .../ui/pages/store_pages/add_edit_store.dart | 31 ++++---- .../search_result.dart | 12 ++-- lib/ph/ui/pages/file_picker/pdf_picker.dart | 6 +- .../ui/pages/form_page/add_edit_ph_page.dart | 12 ++-- .../pages/past_ph_selection_page/ph_card.dart | 5 +- .../association_editor_page.dart | 14 ++-- .../association_information_editor.dart | 14 ++-- .../member_editable_card.dart | 16 ++--- .../membership_editor_page.dart | 29 ++++---- .../creation_edit_page/ticket_handler.dart | 16 +++-- .../add_edit_pack_ticket_page.dart | 52 ++++++-------- .../pages/prize_page/add_edit_prize_page.dart | 52 ++++++-------- .../ui/pages/raffle_page/confirm_payment.dart | 14 ++-- .../ui/pages/add_edit_page.dart | 38 +++++----- .../ui/widgets/recommendation_card.dart | 10 ++- .../ui/pages/change_pass/change_pass.dart | 14 ++-- .../pages/edit_user_page/edit_user_page.dart | 26 ++++--- .../ui/pages/main_page/main_page.dart | 22 +++--- lib/vote/ui/pages/admin_page/section_bar.dart | 10 ++- .../admin_page/section_contender_items.dart | 12 ++-- .../contender_pages/add_edit_contender.dart | 36 +++++----- lib/vote/ui/pages/main_page/vote_button.dart | 10 ++- .../ui/pages/section_pages/add_section.dart | 10 ++- 63 files changed, 713 insertions(+), 586 deletions(-) diff --git a/lib/admin/ui/pages/add_edit_structure_page/add_edit_structure_page.dart b/lib/admin/ui/pages/add_edit_structure_page/add_edit_structure_page.dart index 3f94f45cde..05808104d3 100644 --- a/lib/admin/ui/pages/add_edit_structure_page/add_edit_structure_page.dart +++ b/lib/admin/ui/pages/add_edit_structure_page/add_edit_structure_page.dart @@ -141,6 +141,12 @@ class AddEditStructurePage extends HookConsumerWidget { } if (key.currentState!.validate()) { await tokenExpireWrapper(ref, () async { + final editedStructureMsg = isEdit + ? AppLocalizations.of(context)!.adminEditedStructure + : AppLocalizations.of(context)!.adminAddedStructure; + final addedStructureErrorMsg = AppLocalizations.of( + context, + )!.adminAddingError; final value = isEdit ? await structureListNotifier.updateStructure( Structure( @@ -165,18 +171,12 @@ class AddEditStructurePage extends HookConsumerWidget { structureManagerNotifier.setUser(SimpleUser.empty()); displayToastWithContext( TypeMsg.msg, - isEdit - ? AppLocalizations.of( - context, - )!.adminEditedStructure - : AppLocalizations.of( - context, - )!.adminAddedStructure, + editedStructureMsg, ); } else { displayToastWithContext( TypeMsg.error, - AppLocalizations.of(context)!.adminAddingError, + addedStructureErrorMsg, ); } }); diff --git a/lib/admin/ui/pages/groups/add_group_page/add_group_page.dart b/lib/admin/ui/pages/groups/add_group_page/add_group_page.dart index 3da3c17077..700d8ce409 100644 --- a/lib/admin/ui/pages/groups/add_group_page/add_group_page.dart +++ b/lib/admin/ui/pages/groups/add_group_page/add_group_page.dart @@ -49,6 +49,12 @@ class AddGroupPage extends HookConsumerWidget { ), WaitingButton( onTap: () async { + final addedGroupMsg = AppLocalizations.of( + context, + )!.adminAddedGroup; + final addingErrorMsg = AppLocalizations.of( + context, + )!.adminAddingError; await tokenExpireWrapper(ref, () async { final value = await groupListNotifier.createGroup( SimpleGroup( @@ -59,15 +65,9 @@ class AddGroupPage extends HookConsumerWidget { ); if (value) { QR.back(); - displayToastWithContext( - TypeMsg.msg, - AppLocalizations.of(context)!.adminAddedGroup, - ); + displayToastWithContext(TypeMsg.msg, addedGroupMsg); } else { - displayToastWithContext( - TypeMsg.error, - AppLocalizations.of(context)!.adminAddingError, - ); + displayToastWithContext(TypeMsg.error, addingErrorMsg); } }); }, diff --git a/lib/admin/ui/pages/groups/add_loaner_page/add_loaner_page.dart b/lib/admin/ui/pages/groups/add_loaner_page/add_loaner_page.dart index 91e43585fb..364b129c5b 100644 --- a/lib/admin/ui/pages/groups/add_loaner_page/add_loaner_page.dart +++ b/lib/admin/ui/pages/groups/add_loaner_page/add_loaner_page.dart @@ -60,6 +60,14 @@ class AddLoanerPage extends HookConsumerWidget { name: e.name, ); tokenExpireWrapper(ref, () async { + final addedLoanerMsg = + AppLocalizations.of( + context, + )!.adminAddedLoaner; + final addingErrorMsg = + AppLocalizations.of( + context, + )!.adminAddingError; final value = await loanerListNotifier .addLoaner(newLoaner); @@ -67,16 +75,12 @@ class AddLoanerPage extends HookConsumerWidget { QR.back(); displayToastWithContext( TypeMsg.msg, - AppLocalizations.of( - context, - )!.adminAddedLoaner, + addedLoanerMsg, ); } else { displayToastWithContext( TypeMsg.error, - AppLocalizations.of( - context, - )!.adminAddingError, + addingErrorMsg, ); } }); diff --git a/lib/admin/ui/pages/groups/edit_group_page/edit_group_page.dart b/lib/admin/ui/pages/groups/edit_group_page/edit_group_page.dart index 5d52bff192..e1812f0dc0 100644 --- a/lib/admin/ui/pages/groups/edit_group_page/edit_group_page.dart +++ b/lib/admin/ui/pages/groups/edit_group_page/edit_group_page.dart @@ -100,6 +100,12 @@ class EditGroupPage extends HookConsumerWidget { if (!key.currentState!.validate()) { return; } + final updatedGroupMsg = AppLocalizations.of( + context, + )!.adminUpdatedGroup; + final updatingErrorMsg = AppLocalizations.of( + context, + )!.adminUpdatingError; await tokenExpireWrapper(ref, () async { Group newGroup = group.copyWith( name: name.text, @@ -113,16 +119,12 @@ class EditGroupPage extends HookConsumerWidget { QR.back(); displayToastWithContext( TypeMsg.msg, - AppLocalizations.of( - context, - )!.adminUpdatedGroup, + updatedGroupMsg, ); } else { displayToastWithContext( TypeMsg.msg, - AppLocalizations.of( - context, - )!.adminUpdatingError, + updatingErrorMsg, ); } }); diff --git a/lib/admin/ui/pages/groups/edit_group_page/results.dart b/lib/admin/ui/pages/groups/edit_group_page/results.dart index f084866013..8afeba63d6 100644 --- a/lib/admin/ui/pages/groups/edit_group_page/results.dart +++ b/lib/admin/ui/pages/groups/edit_group_page/results.dart @@ -53,6 +53,12 @@ class MemberResults extends HookConsumerWidget { Group newGroup = group.value!.copyWith( members: group.value!.members + [e], ); + final addedMemberMsg = AppLocalizations.of( + context, + )!.adminAddedMember; + final addingErrorMsg = AppLocalizations.of( + context, + )!.adminAddingError; await tokenExpireWrapper(ref, () async { groupNotifier.addMember(newGroup, e).then(( value, @@ -64,16 +70,12 @@ class MemberResults extends HookConsumerWidget { ); displayToastWithContext( TypeMsg.msg, - AppLocalizations.of( - context, - )!.adminAddedMember, + addedMemberMsg, ); } else { displayToastWithContext( TypeMsg.error, - AppLocalizations.of( - context, - )!.adminAddingError, + addingErrorMsg, ); } }); diff --git a/lib/admin/ui/pages/groups/edit_group_page/search_user.dart b/lib/admin/ui/pages/groups/edit_group_page/search_user.dart index 0fcf035643..f03120f218 100644 --- a/lib/admin/ui/pages/groups/edit_group_page/search_user.dart +++ b/lib/admin/ui/pages/groups/edit_group_page/search_user.dart @@ -115,6 +115,12 @@ class SearchUser extends HookConsumerWidget { )!.adminRemoveGroupMember, title: AppLocalizations.of(context)!.adminDeleting, onYes: () async { + final updatedGroupMsg = AppLocalizations.of( + context, + )!.adminUpdatedGroup; + final updatingErrorMsg = AppLocalizations.of( + context, + )!.adminUpdatingError; await tokenExpireWrapper(ref, () async { Group newGroup = g[0].copyWith( members: g[0].members @@ -132,14 +138,12 @@ class SearchUser extends HookConsumerWidget { ); displayToastWithContext( TypeMsg.msg, - AppLocalizations.of(context)!.adminUpdatedGroup, + updatedGroupMsg, ); } else { displayToastWithContext( TypeMsg.msg, - AppLocalizations.of( - context, - )!.adminUpdatingError, + updatingErrorMsg, ); } }); diff --git a/lib/admin/ui/pages/groups/group_page/group_page.dart b/lib/admin/ui/pages/groups/group_page/group_page.dart index fa4efb6d1f..18ed7f3c14 100644 --- a/lib/admin/ui/pages/groups/group_page/group_page.dart +++ b/lib/admin/ui/pages/groups/group_page/group_page.dart @@ -151,21 +151,25 @@ class GroupsPage extends HookConsumerWidget { )!.adminDeleteGroup, onYes: () async { tokenExpireWrapper(ref, () async { + final deletedGroupMsg = + AppLocalizations.of( + context, + )!.adminDeletedGroup; + final deletingErrorMsg = + AppLocalizations.of( + context, + )!.adminDeletingError; final value = await groupsNotifier .deleteGroup(group); if (value) { displayToastWithContext( TypeMsg.msg, - AppLocalizations.of( - context, - )!.adminDeletedGroup, + deletedGroupMsg, ); } else { displayToastWithContext( TypeMsg.error, - AppLocalizations.of( - context, - )!.adminDeletingError, + deletingErrorMsg, ); } }); diff --git a/lib/admin/ui/pages/memberships/add_edit_user_membership_page/add_edit_user_membership_page.dart b/lib/admin/ui/pages/memberships/add_edit_user_membership_page/add_edit_user_membership_page.dart index a424e36211..5eb93b4c60 100644 --- a/lib/admin/ui/pages/memberships/add_edit_user_membership_page/add_edit_user_membership_page.dart +++ b/lib/admin/ui/pages/memberships/add_edit_user_membership_page/add_edit_user_membership_page.dart @@ -146,6 +146,12 @@ class AddEditUserMembershipPage extends HookConsumerWidget { return; } if (isEdit) { + final updatedMembershipMsg = AppLocalizations.of( + context, + )!.adminUpdatedMembership; + final updatingErrorMsg = AppLocalizations.of( + context, + )!.adminMembershipUpdatingError; final value = await associationMembershipMembersNotifier .updateMember( membership.copyWith( @@ -160,15 +166,13 @@ class AddEditUserMembershipPage extends HookConsumerWidget { if (value) { displayToastWithContext( TypeMsg.msg, - AppLocalizations.of(context)!.adminUpdatedMembership, + updatedMembershipMsg, ); QR.back(); } else { displayToastWithContext( TypeMsg.error, - AppLocalizations.of( - context, - )!.adminMembershipUpdatingError, + updatingErrorMsg, ); } } else { @@ -181,21 +185,19 @@ class AddEditUserMembershipPage extends HookConsumerWidget { startDate: DateTime.parse(processDateBack(start.text)), endDate: DateTime.parse(processDateBack(end.text)), ); + final addedMemberMsg = AppLocalizations.of( + context, + )!.adminAddedMember; + final addingErrorMsg = AppLocalizations.of( + context, + )!.adminMembershipAddingError; final value = await associationMembershipMembersNotifier .addMember(membershipAdd, membership.user); if (value) { - displayToastWithContext( - TypeMsg.msg, - AppLocalizations.of(context)!.adminAddedMember, - ); + displayToastWithContext(TypeMsg.msg, addedMemberMsg); QR.back(); } else { - displayToastWithContext( - TypeMsg.error, - AppLocalizations.of( - context, - )!.adminMembershipAddingError, - ); + displayToastWithContext(TypeMsg.error, addingErrorMsg); } } }); diff --git a/lib/admin/ui/pages/memberships/association_membership_detail_page/association_membership_information_editor.dart b/lib/admin/ui/pages/memberships/association_membership_detail_page/association_membership_information_editor.dart index 2416a8a98e..03ae463783 100644 --- a/lib/admin/ui/pages/memberships/association_membership_detail_page/association_membership_information_editor.dart +++ b/lib/admin/ui/pages/memberships/association_membership_detail_page/association_membership_information_editor.dart @@ -125,6 +125,11 @@ class AssociationMembershipInformationEditor extends HookConsumerWidget { } await tokenExpireWrapper(ref, () async { + final updatedAssociationMembershipMsg = AppLocalizations.of( + context, + )!.adminUpdatedAssociationMembership; + final updatingAssociationMembershipErrorMsg = + AppLocalizations.of(context)!.adminUpdatingError; final value = await associationMembershipListNotifier .updateAssociationMembership( associationMembership.copyWith(name: name.text), @@ -138,14 +143,12 @@ class AssociationMembershipInformationEditor extends HookConsumerWidget { ); displayToastWithContext( TypeMsg.msg, - AppLocalizations.of( - context, - )!.adminUpdatedAssociationMembership, + updatedAssociationMembershipMsg, ); } else { displayToastWithContext( TypeMsg.msg, - AppLocalizations.of(context)!.adminUpdatingError, + updatingAssociationMembershipErrorMsg, ); } }); diff --git a/lib/admin/ui/pages/memberships/association_membership_detail_page/association_membership_member_editable_card.dart b/lib/admin/ui/pages/memberships/association_membership_detail_page/association_membership_member_editable_card.dart index 8622e6830b..7693ee0e75 100644 --- a/lib/admin/ui/pages/memberships/association_membership_detail_page/association_membership_member_editable_card.dart +++ b/lib/admin/ui/pages/memberships/association_membership_detail_page/association_membership_member_editable_card.dart @@ -88,19 +88,19 @@ class MemberEditableCard extends HookConsumerWidget { deactivated: false, deletion: true, onDelete: () async { + final deletedMemberMsg = AppLocalizations.of( + context, + )!.phonebookDeletedMember; + final deleteMemberErrorMsg = AppLocalizations.of( + context, + )!.phonebookDeletingError; await tokenExpireWrapper(ref, () async { final result = await associationMembershipMemberListNotifier .deleteMember(associationMembership); if (result) { - displayToastWithContext( - TypeMsg.msg, - AppLocalizations.of(context)!.phonebookDeletedMember, - ); + displayToastWithContext(TypeMsg.msg, deletedMemberMsg); } else { - displayToastWithContext( - TypeMsg.error, - AppLocalizations.of(context)!.phonebookDeletingError, - ); + displayToastWithContext(TypeMsg.error, deleteMemberErrorMsg); } }); }, diff --git a/lib/admin/ui/pages/memberships/association_membership_page/association_membership_page.dart b/lib/admin/ui/pages/memberships/association_membership_page/association_membership_page.dart index b44c059335..f6c59ca0c7 100644 --- a/lib/admin/ui/pages/memberships/association_membership_page/association_membership_page.dart +++ b/lib/admin/ui/pages/memberships/association_membership_page/association_membership_page.dart @@ -88,6 +88,14 @@ class AssociationMembershipsPage extends HookConsumerWidget { nameController: nameController, groupIdController: groupIdController, onYes: () async { + final createdAssociationMembershipMsg = + AppLocalizations.of( + context, + )!.adminCreatedAssociationMembership; + final creationErrorMsg = + AppLocalizations.of( + context, + )!.adminCreationError; tokenExpireWrapper(ref, () async { final value = await associationMembershipsNotifier @@ -104,16 +112,12 @@ class AssociationMembershipsPage extends HookConsumerWidget { if (value) { displayToastWithContext( TypeMsg.msg, - AppLocalizations.of( - context, - )!.adminCreatedAssociationMembership, + createdAssociationMembershipMsg, ); } else { displayToastWithContext( TypeMsg.error, - AppLocalizations.of( - context, - )!.adminCreationError, + creationErrorMsg, ); } }); @@ -165,6 +169,14 @@ class AssociationMembershipsPage extends HookConsumerWidget { )!.adminDeleteAssociationMembership, onYes: () async { tokenExpireWrapper(ref, () async { + final deletedAssociationMembershipMsg = + AppLocalizations.of( + context, + )!.adminDeletedAssociationMembership; + final deletingErrorMsg = + AppLocalizations.of( + context, + )!.adminDeletingError; final value = await associationMembershipsNotifier .deleteAssociationMembership( @@ -173,16 +185,12 @@ class AssociationMembershipsPage extends HookConsumerWidget { if (value) { displayToastWithContext( TypeMsg.msg, - AppLocalizations.of( - context, - )!.adminDeletedAssociationMembership, + deletedAssociationMembershipMsg, ); } else { displayToastWithContext( TypeMsg.error, - AppLocalizations.of( - context, - )!.adminDeletingError, + deletingErrorMsg, ); } }); diff --git a/lib/admin/ui/pages/schools/add_school_page/add_school_page.dart b/lib/admin/ui/pages/schools/add_school_page/add_school_page.dart index 26a8caf55a..e8b8c38e6e 100644 --- a/lib/admin/ui/pages/schools/add_school_page/add_school_page.dart +++ b/lib/admin/ui/pages/schools/add_school_page/add_school_page.dart @@ -50,6 +50,12 @@ class AddSchoolPage extends HookConsumerWidget { WaitingButton( onTap: () async { await tokenExpireWrapper(ref, () async { + final addedSchoolMsg = AppLocalizations.of( + context, + )!.adminAddedSchool; + final addingErrorMsg = AppLocalizations.of( + context, + )!.adminAddingError; final value = await schoolListNotifier.createSchool( School( name: name.text, @@ -59,15 +65,9 @@ class AddSchoolPage extends HookConsumerWidget { ); if (value) { QR.back(); - displayToastWithContext( - TypeMsg.msg, - AppLocalizations.of(context)!.adminAddedSchool, - ); + displayToastWithContext(TypeMsg.msg, addedSchoolMsg); } else { - displayToastWithContext( - TypeMsg.error, - AppLocalizations.of(context)!.adminAddingError, - ); + displayToastWithContext(TypeMsg.error, addingErrorMsg); } }); }, diff --git a/lib/admin/ui/pages/schools/edit_school_page/edit_school_page.dart b/lib/admin/ui/pages/schools/edit_school_page/edit_school_page.dart index 36eb843ec6..46d8e7ce6d 100644 --- a/lib/admin/ui/pages/schools/edit_school_page/edit_school_page.dart +++ b/lib/admin/ui/pages/schools/edit_school_page/edit_school_page.dart @@ -79,6 +79,12 @@ class EditSchoolPage extends HookConsumerWidget { if (!key.currentState!.validate()) { return; } + final updatedGroupMsg = AppLocalizations.of( + context, + )!.adminUpdatedGroup; + final updatingErrorMsg = AppLocalizations.of( + context, + )!.adminUpdatingError; await tokenExpireWrapper(ref, () async { School newSchool = school.copyWith( name: name.text, @@ -90,14 +96,11 @@ class EditSchoolPage extends HookConsumerWidget { ); if (value) { QR.back(); - displayToastWithContext( - TypeMsg.msg, - AppLocalizations.of(context)!.adminUpdatedGroup, - ); + displayToastWithContext(TypeMsg.msg, updatedGroupMsg); } else { displayToastWithContext( TypeMsg.msg, - AppLocalizations.of(context)!.adminUpdatingError, + updatingErrorMsg, ); } }); diff --git a/lib/admin/ui/pages/structure_page/structure_page.dart b/lib/admin/ui/pages/structure_page/structure_page.dart index 3e4f873a40..e5610d278a 100644 --- a/lib/admin/ui/pages/structure_page/structure_page.dart +++ b/lib/admin/ui/pages/structure_page/structure_page.dart @@ -120,22 +120,26 @@ class StructurePage extends HookConsumerWidget { context, )!.adminDeleteGroup, onYes: () async { + final deletedGroupMsg = + AppLocalizations.of( + context, + )!.adminDeletedGroup; + final deletingErrorMsg = + AppLocalizations.of( + context, + )!.adminDeletingError; tokenExpireWrapper(ref, () async { final value = await structuresNotifier .deleteStructure(structure); if (value) { displayToastWithContext( TypeMsg.msg, - AppLocalizations.of( - context, - )!.adminDeletedGroup, + deletedGroupMsg, ); } else { displayToastWithContext( TypeMsg.error, - AppLocalizations.of( - context, - )!.adminDeletingError, + deletingErrorMsg, ); } }); diff --git a/lib/advert/ui/pages/form_page/add_edit_advert_page.dart b/lib/advert/ui/pages/form_page/add_edit_advert_page.dart index 5da930c001..324c1251b7 100644 --- a/lib/advert/ui/pages/form_page/add_edit_advert_page.dart +++ b/lib/advert/ui/pages/form_page/add_edit_advert_page.dart @@ -235,6 +235,15 @@ class AdvertAddEditAdvertPage extends HookConsumerWidget { tags: textTagsController.text.split(', '), title: title.text, ); + final editedAdvertMsg = AppLocalizations.of( + context, + )!.advertEditedAdvert; + final addedAdvertMsg = AppLocalizations.of( + context, + )!.advertAddedAdvert; + final editingErrorMsg = AppLocalizations.of( + context, + )!.advertEditingError; final value = isEdit ? await advertListNotifier.updateAdvert( newAdvert, @@ -245,9 +254,7 @@ class AdvertAddEditAdvertPage extends HookConsumerWidget { if (isEdit) { displayAdvertToastWithContext( TypeMsg.msg, - AppLocalizations.of( - context, - )!.advertEditedAdvert, + editedAdvertMsg, ); advertList.maybeWhen( data: (list) { @@ -263,9 +270,7 @@ class AdvertAddEditAdvertPage extends HookConsumerWidget { } else { displayAdvertToastWithContext( TypeMsg.msg, - AppLocalizations.of( - context, - )!.advertAddedAdvert, + addedAdvertMsg, ); advertList.maybeWhen( data: (list) { @@ -281,9 +286,7 @@ class AdvertAddEditAdvertPage extends HookConsumerWidget { } else { displayAdvertToastWithContext( TypeMsg.error, - AppLocalizations.of( - context, - )!.advertEditingError, + editingErrorMsg, ); } }); diff --git a/lib/advert/ui/pages/form_page/add_rem_announcer_page.dart b/lib/advert/ui/pages/form_page/add_rem_announcer_page.dart index 5911655cbb..b79365ca47 100644 --- a/lib/advert/ui/pages/form_page/add_rem_announcer_page.dart +++ b/lib/advert/ui/pages/form_page/add_rem_announcer_page.dart @@ -128,6 +128,14 @@ class AddRemAnnouncerPage extends HookConsumerWidget { context, )!.advertDeleteAnnouncer, onYes: () { + final removedAnnouncerMsg = + AppLocalizations.of( + context, + )!.advertRemovedAnnouncer; + final removingErrorMsg = + AppLocalizations.of( + context, + )!.advertRemovingError; tokenExpireWrapper(ref, () async { final value = await announcerListNotifier .deleteAnnouncer( @@ -142,16 +150,12 @@ class AddRemAnnouncerPage extends HookConsumerWidget { if (value) { displayToastWithContext( TypeMsg.msg, - AppLocalizations.of( - context, - )!.advertRemovedAnnouncer, + removedAnnouncerMsg, ); } else { displayToastWithContext( TypeMsg.error, - AppLocalizations.of( - context, - )!.advertRemovingError, + removingErrorMsg, ); } announcerListNotifier diff --git a/lib/amap/ui/components/order_ui.dart b/lib/amap/ui/components/order_ui.dart index 1a67e295ce..751ecfcfaf 100644 --- a/lib/amap/ui/components/order_ui.dart +++ b/lib/amap/ui/components/order_ui.dart @@ -138,6 +138,12 @@ class OrderUI extends HookConsumerWidget { context, )!.amapDeletingOrder, onYes: () async { + final deletedOrderMsg = AppLocalizations.of( + context, + )!.amapDeletedOrder; + final deletingErrorMsg = AppLocalizations.of( + context, + )!.amapDeletingError; await tokenExpireWrapper(ref, () async { orderListNotifier.deleteOrder(order).then(( value, @@ -146,16 +152,12 @@ class OrderUI extends HookConsumerWidget { balanceNotifier.updateCash(order.amount); displayToastWithContext( TypeMsg.msg, - AppLocalizations.of( - context, - )!.amapDeletedOrder, + deletedOrderMsg, ); } else { displayToastWithContext( TypeMsg.error, - AppLocalizations.of( - context, - )!.amapDeletingError, + deletingErrorMsg, ); } }); diff --git a/lib/amap/ui/pages/admin_page/delivery_ui.dart b/lib/amap/ui/pages/admin_page/delivery_ui.dart index 2d10a81261..356e0b9be7 100644 --- a/lib/amap/ui/pages/admin_page/delivery_ui.dart +++ b/lib/amap/ui/pages/admin_page/delivery_ui.dart @@ -185,6 +185,12 @@ class DeliveryUi extends HookConsumerWidget { context, )!.amapDeleteDeliveryDescription, onYes: () async { + final deletedDeliveryMsg = AppLocalizations.of( + context, + )!.amapDeletedDelivery; + final deletingErrorMsg = AppLocalizations.of( + context, + )!.amapDeletingError; await tokenExpireWrapper(ref, () async { deliveryListNotifier .deleteDelivery(delivery) @@ -192,16 +198,12 @@ class DeliveryUi extends HookConsumerWidget { if (value) { displayVoteWithContext( TypeMsg.msg, - AppLocalizations.of( - context, - )!.amapDeletedDelivery, + deletedDeliveryMsg, ); } else { displayVoteWithContext( TypeMsg.error, - AppLocalizations.of( - context, - )!.amapDeletingError, + deletingErrorMsg, ); } }); @@ -247,6 +249,30 @@ class DeliveryUi extends HookConsumerWidget { context, )!.amapArchivingDelivery, onYes: () async { + final openedDeliveryMsg = AppLocalizations.of( + context, + )!.amapDeliveryOpened; + final notOpenedDeliveryMsg = AppLocalizations.of( + context, + )!.amapDeliveryNotOpened; + final lockedDeliveryMsg = AppLocalizations.of( + context, + )!.amapDeliveryLocked; + final notLockedDeliveryMsg = AppLocalizations.of( + context, + )!.amapDeliveryNotLocked; + final deliveredDeliveryMsg = AppLocalizations.of( + context, + )!.amapDeliveryDelivered; + final notDeliveredDeliveryMsg = AppLocalizations.of( + context, + )!.amapDeliveryNotDelivered; + final archivedDeliveryMsg = AppLocalizations.of( + context, + )!.amapDeliveryArchived; + final notArchivedDeliveryMsg = AppLocalizations.of( + context, + )!.amapDeliveryNotArchived; await tokenExpireWrapper(ref, () async { switch (delivery.status) { case DeliveryStatus.creation: @@ -255,16 +281,12 @@ class DeliveryUi extends HookConsumerWidget { if (value) { displayVoteWithContext( TypeMsg.msg, - AppLocalizations.of( - context, - )!.amapDeliveryOpened, + openedDeliveryMsg, ); } else { displayVoteWithContext( TypeMsg.error, - AppLocalizations.of( - context, - )!.amapDeliveryNotOpened, + notOpenedDeliveryMsg, ); } break; @@ -274,16 +296,12 @@ class DeliveryUi extends HookConsumerWidget { if (value) { displayVoteWithContext( TypeMsg.msg, - AppLocalizations.of( - context, - )!.amapDeliveryLocked, + lockedDeliveryMsg, ); } else { displayVoteWithContext( TypeMsg.error, - AppLocalizations.of( - context, - )!.amapDeliveryNotLocked, + notLockedDeliveryMsg, ); } break; @@ -293,16 +311,12 @@ class DeliveryUi extends HookConsumerWidget { if (value) { displayVoteWithContext( TypeMsg.msg, - AppLocalizations.of( - context, - )!.amapDeliveryDelivered, + deliveredDeliveryMsg, ); } else { displayVoteWithContext( TypeMsg.error, - AppLocalizations.of( - context, - )!.amapDeliveryNotDelivered, + notDeliveredDeliveryMsg, ); } break; @@ -312,16 +326,12 @@ class DeliveryUi extends HookConsumerWidget { if (value) { displayVoteWithContext( TypeMsg.msg, - AppLocalizations.of( - context, - )!.amapDeliveryArchived, + archivedDeliveryMsg, ); } else { displayVoteWithContext( TypeMsg.error, - AppLocalizations.of( - context, - )!.amapDeliveryNotArchived, + notArchivedDeliveryMsg, ); } break; diff --git a/lib/amap/ui/pages/admin_page/product_handler.dart b/lib/amap/ui/pages/admin_page/product_handler.dart index ea65fec74d..90b89117bb 100644 --- a/lib/amap/ui/pages/admin_page/product_handler.dart +++ b/lib/amap/ui/pages/admin_page/product_handler.dart @@ -94,22 +94,26 @@ class ProductHandler extends HookConsumerWidget { context, )!.amapDeleteProductDescription, onYes: () { + final deletedProductMsg = + AppLocalizations.of( + context, + )!.amapDeletedProduct; + final deletingErrorMsg = + AppLocalizations.of( + context, + )!.amapDeletingError; tokenExpireWrapper(ref, () async { final value = await productsNotifier .deleteProduct(e); if (value) { displayToastWithContext( TypeMsg.msg, - AppLocalizations.of( - context, - )!.amapDeletedProduct, + deletedProductMsg, ); } else { displayToastWithContext( TypeMsg.error, - AppLocalizations.of( - context, - )!.amapProductInDelivery, + deletingErrorMsg, ); } }); diff --git a/lib/amap/ui/pages/admin_page/user_cash_ui.dart b/lib/amap/ui/pages/admin_page/user_cash_ui.dart index d8a5135dc7..0f7f7a48e6 100644 --- a/lib/amap/ui/pages/admin_page/user_cash_ui.dart +++ b/lib/amap/ui/pages/admin_page/user_cash_ui.dart @@ -151,6 +151,12 @@ class UserCashUi extends HookConsumerWidget { if (key.currentState == null) { return; } + final updatedAmountMsg = AppLocalizations.of( + context, + )!.amapUpdatedAmount; + final updatingErrorMsg = AppLocalizations.of( + context, + )!.amapUpdatingError; if (key.currentState!.validate()) { await tokenExpireWrapper(ref, () async { await ref @@ -169,16 +175,12 @@ class UserCashUi extends HookConsumerWidget { toggle(); displayVoteWithContext( TypeMsg.msg, - AppLocalizations.of( - context, - )!.amapUpdatedAmount, + updatedAmountMsg, ); } else { displayVoteWithContext( TypeMsg.error, - AppLocalizations.of( - context, - )!.amapUpdatingError, + updatingErrorMsg, ); } }); diff --git a/lib/amap/ui/pages/delivery_pages/add_edit_delivery_cmd_page.dart b/lib/amap/ui/pages/delivery_pages/add_edit_delivery_cmd_page.dart index 8e57441b6b..6955cf1f0b 100644 --- a/lib/amap/ui/pages/delivery_pages/add_edit_delivery_cmd_page.dart +++ b/lib/amap/ui/pages/delivery_pages/add_edit_delivery_cmd_page.dart @@ -149,6 +149,19 @@ class AddEditDeliveryPage extends HookConsumerWidget { final deliveryNotifier = ref.watch( deliveryListProvider.notifier, ); + final editedCommandMsg = AppLocalizations.of( + context, + )!.amapEditedCommand; + final addedCommandMsg = AppLocalizations.of( + context, + )!.amapAddedCommand; + final editingErrorMsg = AppLocalizations.of( + context, + )!.amapEditingError; + final alreadyExistCommandMsg = + AppLocalizations.of( + context, + )!.amapAlreadyExistCommand; final value = isEdit ? await deliveryNotifier.updateDelivery( del, @@ -159,9 +172,7 @@ class AddEditDeliveryPage extends HookConsumerWidget { if (isEdit) { displayToastWithContext( TypeMsg.msg, - AppLocalizations.of( - context, - )!.amapEditedCommand, + editedCommandMsg, ); } else { final deliveryOrdersNotifier = ref.watch( @@ -177,25 +188,19 @@ class AddEditDeliveryPage extends HookConsumerWidget { }); displayToastWithContext( TypeMsg.msg, - AppLocalizations.of( - context, - )!.amapAddedCommand, + addedCommandMsg, ); } } else { if (isEdit) { displayToastWithContext( TypeMsg.error, - AppLocalizations.of( - context, - )!.amapEditingError, + editingErrorMsg, ); } else { displayToastWithContext( TypeMsg.error, - AppLocalizations.of( - context, - )!.amapAlreadyExistCommand, + alreadyExistCommandMsg, ); } } diff --git a/lib/amap/ui/pages/detail_delivery_page/order_detail_ui.dart b/lib/amap/ui/pages/detail_delivery_page/order_detail_ui.dart index 7da24dda40..0c8065e7f5 100644 --- a/lib/amap/ui/pages/detail_delivery_page/order_detail_ui.dart +++ b/lib/amap/ui/pages/detail_delivery_page/order_detail_ui.dart @@ -146,6 +146,12 @@ class DetailOrderUI extends HookConsumerWidget { context, )!.amapDeletingOrder, onYes: () async { + final deletedOrderMsg = AppLocalizations.of( + context, + )!.amapDeletedOrder; + final deletingErrorMsg = AppLocalizations.of( + context, + )!.amapDeletingError; await tokenExpireWrapper(ref, () async { final index = orderList.maybeWhen( data: (data) => data.indexWhere( @@ -170,12 +176,12 @@ class DetailOrderUI extends HookConsumerWidget { ); displayToastWithContext( TypeMsg.msg, - AppLocalizations.of(context)!.amapDeletedOrder, + deletedOrderMsg, ); } else { displayToastWithContext( TypeMsg.error, - AppLocalizations.of(context)!.amapDeletingError, + deletingErrorMsg, ); } }); diff --git a/lib/amap/ui/pages/list_products_page/product_choice_button.dart b/lib/amap/ui/pages/list_products_page/product_choice_button.dart index 63ddc512a3..07fd6634da 100644 --- a/lib/amap/ui/pages/list_products_page/product_choice_button.dart +++ b/lib/amap/ui/pages/list_products_page/product_choice_button.dart @@ -79,6 +79,18 @@ class ProductChoiceButton extends HookConsumerWidget { lastAmount: order.amount, ); await tokenExpireWrapper(ref, () async { + final updatedOrderMsg = AppLocalizations.of( + context, + )!.amapUpdatedOrder; + final addedOrderMsg = AppLocalizations.of( + context, + )!.amapAddedOrder; + final updatingErrorMsg = AppLocalizations.of( + context, + )!.amapUpdatingError; + final addingErrorMsg = AppLocalizations.of( + context, + )!.amapAddingError; final value = isEdit ? await orderListNotifier.updateOrder(newOrder) : await orderListNotifier.addOrder(newOrder); @@ -88,27 +100,18 @@ class ProductChoiceButton extends HookConsumerWidget { order.lastAmount - order.amount, ); if (isEdit) { - displayToastWithContext( - TypeMsg.msg, - AppLocalizations.of(context)!.amapUpdatedOrder, - ); + displayToastWithContext(TypeMsg.msg, updatedOrderMsg); } else { - displayToastWithContext( - TypeMsg.msg, - AppLocalizations.of(context)!.amapAddedOrder, - ); + displayToastWithContext(TypeMsg.msg, addedOrderMsg); } } else { if (isEdit) { displayToastWithContext( TypeMsg.error, - AppLocalizations.of(context)!.amapUpdatingError, + updatingErrorMsg, ); } else { - displayToastWithContext( - TypeMsg.error, - AppLocalizations.of(context)!.amapAddingError, - ); + displayToastWithContext(TypeMsg.error, addingErrorMsg); } } }); diff --git a/lib/amap/ui/pages/presentation_page/text.dart b/lib/amap/ui/pages/presentation_page/text.dart index 42b1cdcead..6933662d84 100644 --- a/lib/amap/ui/pages/presentation_page/text.dart +++ b/lib/amap/ui/pages/presentation_page/text.dart @@ -61,6 +61,9 @@ class PresentationPage extends HookConsumerWidget { ), recognizer: TapGestureRecognizer() ..onTap = () async { + final errorLinkMsg = AppLocalizations.of( + context, + )!.amapErrorLink; try { await launchUrl( Uri.parse(info.link), @@ -69,7 +72,7 @@ class PresentationPage extends HookConsumerWidget { } catch (e) { displayToastWithContext( TypeMsg.msg, - AppLocalizations.of(context)!.amapErrorLink, + errorLinkMsg, ); } }, diff --git a/lib/amap/ui/pages/product_pages/add_edit_product.dart b/lib/amap/ui/pages/product_pages/add_edit_product.dart index 61c6950ff3..4e301aec3b 100644 --- a/lib/amap/ui/pages/product_pages/add_edit_product.dart +++ b/lib/amap/ui/pages/product_pages/add_edit_product.dart @@ -187,6 +187,18 @@ class AddEditProduct extends HookConsumerWidget { quantity: 0, ); await tokenExpireWrapper(ref, () async { + final updatedProductMsg = isEdit + ? AppLocalizations.of( + context, + )!.amapUpdatedProduct + : AppLocalizations.of( + context, + )!.amapAddedProduct; + final addingErrorMsg = isEdit + ? AppLocalizations.of( + context, + )!.amapUpdatingError + : AppLocalizations.of(context)!.amapAddingError; final value = isEdit ? await productsNotifier.updateProduct( newProduct, @@ -195,12 +207,6 @@ class AddEditProduct extends HookConsumerWidget { if (value) { if (isEdit) { formKey.currentState!.reset(); - displayToastWithContext( - TypeMsg.msg, - AppLocalizations.of( - context, - )!.amapUpdatedProduct, - ); } else { ref .watch(selectedListProvider.notifier) @@ -210,27 +216,16 @@ class AddEditProduct extends HookConsumerWidget { orElse: () => [], ), ); - displayToastWithContext( - TypeMsg.msg, - AppLocalizations.of( - context, - )!.amapAddedProduct, - ); } + displayToastWithContext( + TypeMsg.msg, + updatedProductMsg, + ); } else { - if (isEdit) { - displayToastWithContext( - TypeMsg.error, - AppLocalizations.of( - context, - )!.amapUpdatingError, - ); - } else { - displayToastWithContext( - TypeMsg.error, - AppLocalizations.of(context)!.amapAddingError, - ); - } + displayToastWithContext( + TypeMsg.error, + addingErrorMsg, + ); } QR.back(); }); diff --git a/lib/booking/ui/pages/admin_pages/add_edit_manager_page.dart b/lib/booking/ui/pages/admin_pages/add_edit_manager_page.dart index 0d24815ebb..c02ac990b2 100644 --- a/lib/booking/ui/pages/admin_pages/add_edit_manager_page.dart +++ b/lib/booking/ui/pages/admin_pages/add_edit_manager_page.dart @@ -105,6 +105,12 @@ class AddEditManagerPage extends HookConsumerWidget { name: name.text, groupId: groupId, ); + final editedManagerMsg = isEdit + ? AppLocalizations.of(context)!.bookingEditedManager + : AppLocalizations.of(context)!.bookingAddedManager; + final editedManagerErrorMsg = isEdit + ? AppLocalizations.of(context)!.bookingEditionError + : AppLocalizations.of(context)!.bookingAddingError; final value = isEdit ? await managerListNotifier.updateManager( newManager, @@ -112,33 +118,15 @@ class AddEditManagerPage extends HookConsumerWidget { : await managerListNotifier.addManager(newManager); if (value) { QR.back(); - isEdit - ? displayToastWithContext( - TypeMsg.msg, - AppLocalizations.of( - context, - )!.bookingEditedManager, - ) - : displayToastWithContext( - TypeMsg.msg, - AppLocalizations.of( - context, - )!.bookingAddedManager, - ); + displayToastWithContext( + TypeMsg.msg, + editedManagerMsg, + ); } else { - isEdit - ? displayToastWithContext( - TypeMsg.error, - AppLocalizations.of( - context, - )!.bookingEditionError, - ) - : displayToastWithContext( - TypeMsg.error, - AppLocalizations.of( - context, - )!.bookingAddingError, - ); + displayToastWithContext( + TypeMsg.error, + editedManagerErrorMsg, + ); } }); }, @@ -158,22 +146,24 @@ class AddEditManagerPage extends HookConsumerWidget { context, )!.bookingDeleteManagerConfirmation, onYes: () async { + final deletedManagerMsg = AppLocalizations.of( + context, + )!.bookingDeletedManager; + final deletingErrorMsg = AppLocalizations.of( + context, + )!.bookingDeletingError; final value = await managerListNotifier .deleteManager(manager); if (value) { QR.back(); displayToastWithContext( TypeMsg.msg, - AppLocalizations.of( - context, - )!.bookingDeletedManager, + deletedManagerMsg, ); } else { displayToastWithContext( TypeMsg.error, - AppLocalizations.of( - context, - )!.bookingDeletingError, + deletingErrorMsg, ); } }, diff --git a/lib/booking/ui/pages/admin_pages/add_edit_room_page.dart b/lib/booking/ui/pages/admin_pages/add_edit_room_page.dart index 4517616ee7..17a2da96f0 100644 --- a/lib/booking/ui/pages/admin_pages/add_edit_room_page.dart +++ b/lib/booking/ui/pages/admin_pages/add_edit_room_page.dart @@ -105,38 +105,23 @@ class AddEditRoomPage extends HookConsumerWidget { name: name.text, managerId: managerId, ); + final editedRoomMsg = isEdit + ? AppLocalizations.of(context)!.bookingEditedRoom + : AppLocalizations.of(context)!.bookingAddedRoom; + final addingErrorMsg = isEdit + ? AppLocalizations.of(context)!.bookingEditionError + : AppLocalizations.of(context)!.bookingAddingError; final value = isEdit ? await roomListNotifier.updateRoom(newRoom) : await roomListNotifier.addRoom(newRoom); if (value) { QR.back(); - isEdit - ? displayToastWithContext( - TypeMsg.msg, - AppLocalizations.of( - context, - )!.bookingEditedRoom, - ) - : displayToastWithContext( - TypeMsg.msg, - AppLocalizations.of( - context, - )!.bookingAddedRoom, - ); + displayToastWithContext(TypeMsg.msg, editedRoomMsg); } else { - isEdit - ? displayToastWithContext( - TypeMsg.error, - AppLocalizations.of( - context, - )!.bookingEditionError, - ) - : displayToastWithContext( - TypeMsg.error, - AppLocalizations.of( - context, - )!.bookingAddingError, - ); + displayToastWithContext( + TypeMsg.error, + addingErrorMsg, + ); } }); }, @@ -156,6 +141,12 @@ class AddEditRoomPage extends HookConsumerWidget { context, )!.bookingDeleteRoomConfirmation, onYes: () async { + final deletedRoomMsg = AppLocalizations.of( + context, + )!.bookingDeletedRoom; + final deletingErrorMsg = AppLocalizations.of( + context, + )!.bookingDeletingError; final value = await roomListNotifier.deleteRoom( room, ); @@ -163,16 +154,12 @@ class AddEditRoomPage extends HookConsumerWidget { QR.back(); displayToastWithContext( TypeMsg.msg, - AppLocalizations.of( - context, - )!.bookingDeletedRoom, + deletedRoomMsg, ); } else { displayToastWithContext( TypeMsg.error, - AppLocalizations.of( - context, - )!.bookingDeletingError, + deletingErrorMsg, ); } }, diff --git a/lib/booking/ui/pages/booking_pages/add_edit_booking_page.dart b/lib/booking/ui/pages/booking_pages/add_edit_booking_page.dart index c0a5398f13..661cf5db3d 100644 --- a/lib/booking/ui/pages/booking_pages/add_edit_booking_page.dart +++ b/lib/booking/ui/pages/booking_pages/add_edit_booking_page.dart @@ -322,6 +322,18 @@ class AddEditBookingPage extends HookConsumerWidget { if (key.currentState == null) { return; } + final editedBookingMsg = AppLocalizations.of( + context, + )!.bookingEditedBooking; + final addedBookingMsg = AppLocalizations.of( + context, + )!.bookingAddedBooking; + final editionErrorMsg = AppLocalizations.of( + context, + )!.bookingEditionError; + final addingErrorMsg = AppLocalizations.of( + context, + )!.bookingAddingError; if (key.currentState!.validate()) { if (allDay.value) { start.text = "${start.text} 00:00"; @@ -454,32 +466,24 @@ class AddEditBookingPage extends HookConsumerWidget { if (isEdit) { displayToastWithContext( TypeMsg.msg, - AppLocalizations.of( - context, - )!.bookingEditedBooking, + editedBookingMsg, ); } else { displayToastWithContext( TypeMsg.msg, - AppLocalizations.of( - context, - )!.bookingAddedBooking, + addedBookingMsg, ); } } else { if (isEdit) { displayToastWithContext( TypeMsg.error, - AppLocalizations.of( - context, - )!.bookingEditionError, + editionErrorMsg, ); } else { displayToastWithContext( TypeMsg.error, - AppLocalizations.of( - context, - )!.bookingAddingError, + addingErrorMsg, ); } } diff --git a/lib/cinema/ui/pages/session_pages/add_edit_session.dart b/lib/cinema/ui/pages/session_pages/add_edit_session.dart index 351f051a83..5d1fee6330 100644 --- a/lib/cinema/ui/pages/session_pages/add_edit_session.dart +++ b/lib/cinema/ui/pages/session_pages/add_edit_session.dart @@ -235,6 +235,18 @@ class AddEditSessionPage extends HookConsumerWidget { if (key.currentState == null) { return; } + final editedSessionMsg = AppLocalizations.of( + context, + )!.cinemaEditedSession; + final addedSessionMsg = AppLocalizations.of( + context, + )!.cinemaAddedSession; + final editingErrorMsg = AppLocalizations.of( + context, + )!.cinemaEditingError; + final addingErrorMsg = AppLocalizations.of( + context, + )!.cinemaAddingError; if (key.currentState!.validate()) { if (logo.value == null && logoFile.value == null) { displayToastWithContext( @@ -284,7 +296,7 @@ class AddEditSessionPage extends HookConsumerWidget { ); displayToastWithContext( TypeMsg.msg, - AppLocalizations.of(context)!.cinemaEditedSession, + editedSessionMsg, ); } else { sessionList.maybeWhen( @@ -307,19 +319,19 @@ class AddEditSessionPage extends HookConsumerWidget { ); displayToastWithContext( TypeMsg.msg, - AppLocalizations.of(context)!.cinemaAddedSession, + addedSessionMsg, ); } } else { if (isEdit) { displayToastWithContext( TypeMsg.error, - AppLocalizations.of(context)!.cinemaEditingError, + editingErrorMsg, ); } else { displayToastWithContext( TypeMsg.error, - AppLocalizations.of(context)!.cinemaAddingError, + addingErrorMsg, ); } } diff --git a/lib/event/ui/components/event_ui.dart b/lib/event/ui/components/event_ui.dart index a2ce6abdae..51f48f1c1b 100644 --- a/lib/event/ui/components/event_ui.dart +++ b/lib/event/ui/components/event_ui.dart @@ -232,21 +232,25 @@ class EventUi extends ConsumerWidget { context, )!.eventDeletingEvent, onYes: () async { + final deletedEventMsg = + AppLocalizations.of( + context, + )!.eventDeletedEvent; + final deletingErrorMsg = + AppLocalizations.of( + context, + )!.eventDeletingError; final value = await eventListNotifier .deleteEvent(event); if (value) { displayToastWithContext( TypeMsg.msg, - AppLocalizations.of( - context, - )!.eventDeletedEvent, + deletedEventMsg, ); } else { displayToastWithContext( TypeMsg.error, - AppLocalizations.of( - context, - )!.eventDeletingError, + deletingErrorMsg, ); } }, diff --git a/lib/event/ui/pages/event_pages/add_edit_event_page.dart b/lib/event/ui/pages/event_pages/add_edit_event_page.dart index 9c146350d1..18fbc9d80e 100644 --- a/lib/event/ui/pages/event_pages/add_edit_event_page.dart +++ b/lib/event/ui/pages/event_pages/add_edit_event_page.dart @@ -413,6 +413,18 @@ class AddEditEventPage extends HookConsumerWidget { if (key.currentState == null) { return; } + final editedEventMsg = AppLocalizations.of( + context, + )!.eventEditedEvent; + final addedEventMsg = AppLocalizations.of( + context, + )!.eventAddedEvent; + final editingErrorMsg = AppLocalizations.of( + context, + )!.eventEditingError; + final addingErrorMsg = AppLocalizations.of( + context, + )!.eventAddingError; if (key.currentState!.validate()) { if (allDay.value) { start.text = @@ -519,32 +531,24 @@ class AddEditEventPage extends HookConsumerWidget { if (isEdit) { displayToastWithContext( TypeMsg.msg, - AppLocalizations.of( - context, - )!.eventEditedEvent, + editedEventMsg, ); } else { displayToastWithContext( TypeMsg.msg, - AppLocalizations.of( - context, - )!.eventAddedEvent, + addedEventMsg, ); } } else { if (isEdit) { displayToastWithContext( TypeMsg.error, - AppLocalizations.of( - context, - )!.eventEditingError, + editingErrorMsg, ); } else { displayToastWithContext( TypeMsg.error, - AppLocalizations.of( - context, - )!.eventAddingError, + addingErrorMsg, ); } } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 505dbf70c5..4aa3fa3f31 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -80,11 +80,11 @@ "adminValidateFilters": "Apply filters", "adminVisibilities": "Visibilities", "advertAdd": "Add", - "advertAddedAdvert": "Ad published", + "advertAddedAdvert": "Advert published", "advertAddedAnnouncer": "Announcer added", "advertAddingError": "Error while adding", "advertAdmin": "Admin", - "advertAdvert": "Ad", + "advertAdvert": "Advert", "advertChoosingAnnouncer": "Please choose an announcer", "advertChoosingPoster": "Please choose an image", "advertContent": "Content", @@ -92,7 +92,7 @@ "advertDeleteAnnouncer": "Delete announcer?", "advertDeleting": "Deleting", "advertEdit": "Edit", - "advertEditedAdvert": "Ad edited", + "advertEditedAdvert": "Advert edited", "advertEditingError": "Error while editing", "advertGroupAdvert": "Group", "advertIncorrectOrMissingFields": "Incorrect or missing fields", diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 9eae54a3dc..1b27949a89 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -251,7 +251,7 @@ class AppLocalizationsEn extends AppLocalizations { String get advertAdd => 'Add'; @override - String get advertAddedAdvert => 'Ad published'; + String get advertAddedAdvert => 'Advert published'; @override String get advertAddedAnnouncer => 'Announcer added'; @@ -263,7 +263,7 @@ class AppLocalizationsEn extends AppLocalizations { String get advertAdmin => 'Admin'; @override - String get advertAdvert => 'Ad'; + String get advertAdvert => 'Advert'; @override String get advertChoosingAnnouncer => 'Please choose an announcer'; @@ -287,7 +287,7 @@ class AppLocalizationsEn extends AppLocalizations { String get advertEdit => 'Edit'; @override - String get advertEditedAdvert => 'Ad edited'; + String get advertEditedAdvert => 'Advert edited'; @override String get advertEditingError => 'Error while editing'; diff --git a/lib/loan/ui/pages/admin_page/loaners_items.dart b/lib/loan/ui/pages/admin_page/loaners_items.dart index 1d5b22a23c..62700bc9ec 100644 --- a/lib/loan/ui/pages/admin_page/loaners_items.dart +++ b/lib/loan/ui/pages/admin_page/loaners_items.dart @@ -101,6 +101,12 @@ class LoanersItems extends HookConsumerWidget { context, )!.loanDeletingItem, onYes: () { + final deletedItemMsg = AppLocalizations.of( + context, + )!.loanDeletedItem; + final deletingErrorMsg = AppLocalizations.of( + context, + )!.loanDeletingError; tokenExpireWrapper(ref, () async { final value = await itemListNotifier.deleteItem( e, @@ -112,12 +118,12 @@ class LoanersItems extends HookConsumerWidget { }); displayToastWithContext( TypeMsg.msg, - AppLocalizations.of(context)!.loanDeletedItem, + deletedItemMsg, ); } else { displayToastWithContext( TypeMsg.error, - AppLocalizations.of(context)!.loanDeletingError, + deletingErrorMsg, ); } }); diff --git a/lib/loan/ui/pages/admin_page/on_going_loan.dart b/lib/loan/ui/pages/admin_page/on_going_loan.dart index 80e2765845..582f7dbe6b 100644 --- a/lib/loan/ui/pages/admin_page/on_going_loan.dart +++ b/lib/loan/ui/pages/admin_page/on_going_loan.dart @@ -117,6 +117,12 @@ class OnGoingLoan extends HookConsumerWidget { loanersItemsNotifier.setTData(loaner, itemList); }, onCalendar: () async { + final extendedLoanMsg = AppLocalizations.of( + context, + )!.loanExtendedLoan; + final extendedLoanErrorMsg = AppLocalizations.of( + context, + )!.loanExtendingError; await showDialog( context: context, builder: (BuildContext context) { @@ -138,14 +144,12 @@ class OnGoingLoan extends HookConsumerWidget { ); displayToastWithContext( TypeMsg.msg, - AppLocalizations.of(context)!.loanExtendedLoan, + extendedLoanMsg, ); } else { displayToastWithContext( TypeMsg.error, - AppLocalizations.of( - context, - )!.loanExtendingError, + extendedLoanErrorMsg, ); } }); @@ -163,6 +167,12 @@ class OnGoingLoan extends HookConsumerWidget { context, )!.loanReturnLoanDescription, onYes: () async { + final returningLoanMsg = AppLocalizations.of( + context, + )!.loanReturnedLoan; + final returningLoanErrorMsg = AppLocalizations.of( + context, + )!.loanReturningError; await tokenExpireWrapper(ref, () async { final loanItemsId = e.itemsQuantity .map((e) => e.itemSimple.id) @@ -192,12 +202,12 @@ class OnGoingLoan extends HookConsumerWidget { ); displayToastWithContext( TypeMsg.msg, - AppLocalizations.of(context)!.loanReturnedLoan, + returningLoanMsg, ); } else { displayToastWithContext( TypeMsg.msg, - AppLocalizations.of(context)!.loanReturningError, + returningLoanErrorMsg, ); } }); diff --git a/lib/loan/ui/pages/loan_group_page/add_edit_button.dart b/lib/loan/ui/pages/loan_group_page/add_edit_button.dart index c6139db8f2..ec3378eacf 100644 --- a/lib/loan/ui/pages/loan_group_page/add_edit_button.dart +++ b/lib/loan/ui/pages/loan_group_page/add_edit_button.dart @@ -91,6 +91,12 @@ class AddEditButton extends HookConsumerWidget { start: DateTime.parse(processDateBack(start)), returned: false, ); + final addedLoanMsg = isEdit + ? AppLocalizations.of(context)!.loanUpdatedLoan + : AppLocalizations.of(context)!.loanAddedLoan; + final addingErrorMsg = isEdit + ? AppLocalizations.of(context)!.loanUpdatingError + : AppLocalizations.of(context)!.loanAddingError; final value = isEdit ? await loanListNotifier.updateLoan(newLoan) : await loanListNotifier.addLoan(newLoan); @@ -100,29 +106,9 @@ class AddEditButton extends HookConsumerWidget { await loanListNotifier.copy(), ); QR.back(); - if (isEdit) { - displayToastWithContext( - TypeMsg.msg, - AppLocalizations.of(context)!.loanUpdatedLoan, - ); - } else { - displayToastWithContext( - TypeMsg.msg, - AppLocalizations.of(context)!.loanAddedLoan, - ); - } + displayToastWithContext(TypeMsg.msg, addedLoanMsg); } else { - if (isEdit) { - displayToastWithContext( - TypeMsg.error, - AppLocalizations.of(context)!.loanUpdatingError, - ); - } else { - displayToastWithContext( - TypeMsg.error, - AppLocalizations.of(context)!.loanAddingError, - ); - } + displayToastWithContext(TypeMsg.error, addingErrorMsg); } } else { displayToastWithContext( diff --git a/lib/login/ui/pages/create_account_page/create_account_page.dart b/lib/login/ui/pages/create_account_page/create_account_page.dart index 280215bfc9..9ce9dbd918 100644 --- a/lib/login/ui/pages/create_account_page/create_account_page.dart +++ b/lib/login/ui/pages/create_account_page/create_account_page.dart @@ -270,22 +270,22 @@ class CreateAccountPage extends HookConsumerWidget { activationToken: activationCode.text.trim(), password: password.text, ); + final accountActivatedMsg = AppLocalizations.of( + context, + )!.loginAccountActivated; + final accountNotActivatedMsg = AppLocalizations.of( + context, + )!.loginAccountNotActivated; try { final value = await signUpNotifier.activateUser( finalCreateAccount, ); if (value) { - displayToastWithContext( - TypeMsg.msg, - AppLocalizations.of(context)!.loginAccountActivated, - ); + displayToastWithContext(TypeMsg.msg, accountActivatedMsg); authTokenNotifier.deleteToken(); QR.to(LoginRouter.root); } else { - displayToastWithContext( - TypeMsg.error, - AppLocalizations.of(context)!.loginAccountNotActivated, - ); + displayToastWithContext(TypeMsg.error, accountNotActivatedMsg); } } catch (e) { displayToastWithContext(TypeMsg.error, e.toString()); diff --git a/lib/login/ui/pages/forget_page/forget_page.dart b/lib/login/ui/pages/forget_page/forget_page.dart index 61c4780008..aec493ba0f 100644 --- a/lib/login/ui/pages/forget_page/forget_page.dart +++ b/lib/login/ui/pages/forget_page/forget_page.dart @@ -96,14 +96,17 @@ class ForgetPassword extends HookConsumerWidget { .watch(loadingProvider) .maybeWhen(data: (data) => data, orElse: () => false), onPressed: () async { + final sendedResetMail = AppLocalizations.of( + context, + )!.loginSendedResetMail; + final mailSendingError = AppLocalizations.of( + context, + )!.loginMailSendingError; final value = await signUpNotifier.recoverUser( email.text, ); if (value) { - displayToastWithContext( - TypeMsg.msg, - AppLocalizations.of(context)!.loginSendedResetMail, - ); + displayToastWithContext(TypeMsg.msg, sendedResetMail); email.clear(); QR.to( LoginRouter.forgotPassword + @@ -112,7 +115,7 @@ class ForgetPassword extends HookConsumerWidget { } else { displayToastWithContext( TypeMsg.error, - AppLocalizations.of(context)!.loginMailSendingError, + mailSendingError, ); } }, diff --git a/lib/login/ui/pages/recover_password/recover_password_page.dart b/lib/login/ui/pages/recover_password/recover_password_page.dart index 8796dff6a3..fa8a0a9fb2 100644 --- a/lib/login/ui/pages/recover_password/recover_password_page.dart +++ b/lib/login/ui/pages/recover_password/recover_password_page.dart @@ -70,6 +70,12 @@ class RecoverPasswordPage extends HookConsumerWidget { label: AppLocalizations.of(context)!.loginEndResetPassword, isLoading: false, onPressed: () async { + final resetedPasswordMsg = AppLocalizations.of( + context, + )!.loginResetedPassword; + final invalidTokenMsg = AppLocalizations.of( + context, + )!.loginInvalidToken; if (password.text.isNotEmpty && activationCode.text.isNotEmpty) { RecoverRequest recoverRequest = RecoverRequest( resetToken: activationCode.text.trim(), @@ -77,17 +83,11 @@ class RecoverPasswordPage extends HookConsumerWidget { ); final value = await signUpNotifier.resetPassword(recoverRequest); if (value) { - displayToastWithContext( - TypeMsg.msg, - AppLocalizations.of(context)!.loginResetedPassword, - ); + displayToastWithContext(TypeMsg.msg, resetedPasswordMsg); authTokenNotifier.deleteToken(); QR.to(LoginRouter.root); } else { - displayToastWithContext( - TypeMsg.error, - AppLocalizations.of(context)!.loginInvalidToken, - ); + displayToastWithContext(TypeMsg.error, invalidTokenMsg); } } else { displayToastWithContext( diff --git a/lib/login/ui/pages/register_page/register_page.dart b/lib/login/ui/pages/register_page/register_page.dart index b9754c1126..250f0e4ee8 100644 --- a/lib/login/ui/pages/register_page/register_page.dart +++ b/lib/login/ui/pages/register_page/register_page.dart @@ -114,6 +114,12 @@ class Register extends HookConsumerWidget { .watch(loadingProvider) .maybeWhen(data: (data) => data, orElse: () => false), onPressed: () async { + final sendedMailMsg = AppLocalizations.of( + context, + )!.loginSendedMail; + final mailSendingErrorMsg = AppLocalizations.of( + context, + )!.loginMailSendingError; if (key.currentState!.validate()) { final value = await signUpNotifier.createUser( mail.text, @@ -126,16 +132,11 @@ class Register extends HookConsumerWidget { LoginRouter.createAccount + LoginRouter.mailReceived, ); - displayToastWithContext( - TypeMsg.msg, - AppLocalizations.of(context)!.loginSendedMail, - ); + displayToastWithContext(TypeMsg.msg, sendedMailMsg); } else { displayToastWithContext( TypeMsg.error, - AppLocalizations.of( - context, - )!.loginMailSendingError, + mailSendingErrorMsg, ); } } else { diff --git a/lib/paiement/ui/pages/store_pages/add_edit_store.dart b/lib/paiement/ui/pages/store_pages/add_edit_store.dart index a76df42c21..2cbde0a984 100644 --- a/lib/paiement/ui/pages/store_pages/add_edit_store.dart +++ b/lib/paiement/ui/pages/store_pages/add_edit_store.dart @@ -72,6 +72,21 @@ class AddEditStorePage extends HookConsumerWidget { if (key.currentState == null) { return; } + final successfullyAddedStoreMsg = isEdit + ? AppLocalizations.of( + context, + )!.paiementSuccessfullyModifiedStore + : AppLocalizations.of( + context, + )!.paiementSuccessfullyAddedStore; + final addingErrorMsg = isEdit + ? AppLocalizations.of( + context, + )!.paiementModifyingStoreError + : AppLocalizations.of( + context, + )!.paiementAddingStoreError; + if (key.currentState!.validate()) { store_class.Store newStore = store.copyWith( name: name.text, @@ -92,24 +107,12 @@ class AddEditStorePage extends HookConsumerWidget { QR.back(); displayToastWithContext( TypeMsg.msg, - isEdit - ? AppLocalizations.of( - context, - )!.paiementSuccessfullyAddedStore - : AppLocalizations.of( - context, - )!.paiementSuccessfullyModifiedStore, + successfullyAddedStoreMsg, ); } else { displayToastWithContext( TypeMsg.error, - isEdit - ? AppLocalizations.of( - context, - )!.paiementModifyingStoreError - : AppLocalizations.of( - context, - )!.paiementAddingStoreError, + addingErrorMsg, ); } } diff --git a/lib/paiement/ui/pages/transfer_structure_page/search_result.dart b/lib/paiement/ui/pages/transfer_structure_page/search_result.dart index 61ba66e926..558201fcb1 100644 --- a/lib/paiement/ui/pages/transfer_structure_page/search_result.dart +++ b/lib/paiement/ui/pages/transfer_structure_page/search_result.dart @@ -37,6 +37,12 @@ class SearchResult extends HookConsumerWidget { descriptions: '${AppLocalizations.of(context)!.paiementYouAreTransferingStructureTo} ${simpleUser.getName()}. ${AppLocalizations.of(context)!.paiementTransferStructureDescription}', onYes: () async { + final transferStructureSeccessMsg = AppLocalizations.of( + context, + )!.paiementTransferStructureSuccess; + final transferStructureErrorMsg = AppLocalizations.of( + context, + )!.paiementTransferStructureError; final value = await transferStructureNotifier.initTransfer( selectedStore.structure, simpleUser.id, @@ -44,14 +50,12 @@ class SearchResult extends HookConsumerWidget { if (value) { displayToastWithContext( TypeMsg.msg, - AppLocalizations.of( - context, - )!.paiementTransferStructureSuccess, + transferStructureSeccessMsg, ); } else { displayToastWithContext( TypeMsg.error, - AppLocalizations.of(context)!.paiementTransferStructureError, + transferStructureErrorMsg, ); } }, diff --git a/lib/ph/ui/pages/file_picker/pdf_picker.dart b/lib/ph/ui/pages/file_picker/pdf_picker.dart index 1cdd9b85a0..26ad1e842e 100644 --- a/lib/ph/ui/pages/file_picker/pdf_picker.dart +++ b/lib/ph/ui/pages/file_picker/pdf_picker.dart @@ -30,6 +30,7 @@ class PdfPicker extends HookConsumerWidget { height: 40, child: GestureDetector( onTap: () async { + final tooHeavyFileMsg = AppLocalizations.of(context)!.phToHeavyFile; final selectedFile = await FilePicker.platform.pickFiles( allowMultiple: false, type: FileType.custom, @@ -46,10 +47,7 @@ class PdfPicker extends HookConsumerWidget { if (bytes.length < 10000000) { phSendPdfNotifier.set(bytes); } else { - displayToastWithContext( - TypeMsg.error, - AppLocalizations.of(context)!.phToHeavyFile, - ); + displayToastWithContext(TypeMsg.error, tooHeavyFileMsg); } } if (isEdit) { diff --git a/lib/ph/ui/pages/form_page/add_edit_ph_page.dart b/lib/ph/ui/pages/form_page/add_edit_ph_page.dart index 86219f9b1c..95bbc7365b 100644 --- a/lib/ph/ui/pages/form_page/add_edit_ph_page.dart +++ b/lib/ph/ui/pages/form_page/add_edit_ph_page.dart @@ -95,6 +95,12 @@ class PhAddEditPhPage extends HookConsumerWidget { if (key.currentState == null) { return; } + final addedPhMsg = isEdit + ? AppLocalizations.of(context)!.phEdited + : AppLocalizations.of(context)!.phAdded; + final phAddingFileErrorMsg = AppLocalizations.of( + context, + )!.phAddingFileError; if (true && (!listEquals(phSendPdf, Uint8List(0)) || isEdit)) { await tokenExpireWrapper(ref, () async { @@ -134,16 +140,14 @@ class PhAddEditPhPage extends HookConsumerWidget { } displayPhToastWithContext( TypeMsg.msg, - isEdit - ? AppLocalizations.of(context)!.phEdited - : AppLocalizations.of(context)!.phAdded, + addedPhMsg, ); editPdfNotifier.editPdf(false); } } else { displayPhToastWithContext( TypeMsg.error, - AppLocalizations.of(context)!.phAddingFileError, + phAddingFileErrorMsg, ); } }); diff --git a/lib/ph/ui/pages/past_ph_selection_page/ph_card.dart b/lib/ph/ui/pages/past_ph_selection_page/ph_card.dart index a2096591d5..1055f966d0 100644 --- a/lib/ph/ui/pages/past_ph_selection_page/ph_card.dart +++ b/lib/ph/ui/pages/past_ph_selection_page/ph_card.dart @@ -54,6 +54,9 @@ class PhCard extends HookConsumerWidget { right: 5, child: GestureDetector( onTap: () async { + final successDownloadingMsg = AppLocalizations.of( + context, + )!.phSuccesDowloading; late final Uint8List pdfBytes; try { @@ -80,7 +83,7 @@ class PhCard extends HookConsumerWidget { if (path != null) { displayPhToastWithContext( TypeMsg.msg, - AppLocalizations.of(context)!.phSuccesDowloading, + successDownloadingMsg, ); } }, diff --git a/lib/phonebook/ui/pages/association_editor_page/association_editor_page.dart b/lib/phonebook/ui/pages/association_editor_page/association_editor_page.dart index 2d9d80cc2d..641db46f21 100644 --- a/lib/phonebook/ui/pages/association_editor_page/association_editor_page.dart +++ b/lib/phonebook/ui/pages/association_editor_page/association_editor_page.dart @@ -170,6 +170,12 @@ class AssociationEditorPage extends HookConsumerWidget { ); }, onReorder: (int oldIndex, int newIndex) async { + final memberReorderedMsg = AppLocalizations.of( + context, + )!.phonebookMemberReordered; + final reorderingErrorMsg = AppLocalizations.of( + context, + )!.phonebookReorderingError; await tokenExpireWrapper(ref, () async { final result = await associationMemberListNotifier .reorderMember( @@ -190,16 +196,12 @@ class AssociationEditorPage extends HookConsumerWidget { if (result) { displayToastWithContext( TypeMsg.msg, - AppLocalizations.of( - context, - )!.phonebookMemberReordered, + memberReorderedMsg, ); } else { displayToastWithContext( TypeMsg.error, - AppLocalizations.of( - context, - )!.phonebookReorderingError, + reorderingErrorMsg, ); } }); diff --git a/lib/phonebook/ui/pages/association_editor_page/association_information_editor.dart b/lib/phonebook/ui/pages/association_editor_page/association_information_editor.dart index 526f371504..941cf3b9d3 100644 --- a/lib/phonebook/ui/pages/association_editor_page/association_information_editor.dart +++ b/lib/phonebook/ui/pages/association_editor_page/association_information_editor.dart @@ -165,6 +165,12 @@ class AssociationInformationEditor extends HookConsumerWidget { ); return; } + final updatedAssociationMsg = AppLocalizations.of( + context, + )!.phonebookUpdatedAssociation; + final updatingErrorMsg = AppLocalizations.of( + context, + )!.phonebookUpdatingError; await tokenExpireWrapper(ref, () async { final value = await associationListNotifier .updateAssociation( @@ -177,16 +183,12 @@ class AssociationInformationEditor extends HookConsumerWidget { if (value) { displayToastWithContext( TypeMsg.msg, - AppLocalizations.of( - context, - )!.phonebookUpdatedAssociation, + updatedAssociationMsg, ); } else { displayToastWithContext( TypeMsg.msg, - AppLocalizations.of( - context, - )!.phonebookUpdatingError, + updatingErrorMsg, ); } }); diff --git a/lib/phonebook/ui/pages/association_editor_page/member_editable_card.dart b/lib/phonebook/ui/pages/association_editor_page/member_editable_card.dart index f3e66f5b02..4d59805ddc 100644 --- a/lib/phonebook/ui/pages/association_editor_page/member_editable_card.dart +++ b/lib/phonebook/ui/pages/association_editor_page/member_editable_card.dart @@ -155,6 +155,12 @@ class MemberEditableCard extends HookConsumerWidget { deactivated: deactivated, deletion: true, onDelete: () async { + final deletedMemberMsg = AppLocalizations.of( + context, + )!.phonebookDeletedMember; + final deletingErrorMsg = AppLocalizations.of( + context, + )!.phonebookDeletingError; final result = await associationMemberListNotifier.deleteMember( member, member.memberships.firstWhere( @@ -164,15 +170,9 @@ class MemberEditableCard extends HookConsumerWidget { ), ); if (result) { - displayToastWithContext( - TypeMsg.msg, - AppLocalizations.of(context)!.phonebookDeletedMember, - ); + displayToastWithContext(TypeMsg.msg, deletedMemberMsg); } else { - displayToastWithContext( - TypeMsg.error, - AppLocalizations.of(context)!.phonebookDeletingError, - ); + displayToastWithContext(TypeMsg.error, deletingErrorMsg); } }, ), diff --git a/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart b/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart index 4259fc9e9f..f1004d81d1 100644 --- a/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart +++ b/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart @@ -198,6 +198,12 @@ class MembershipEditorPage extends HookConsumerWidget { (membership) => membership.id == membershipEdit.id, )] = membershipEdit; + final updatedMemberMsg = AppLocalizations.of( + context, + )!.phonebookUpdatedMember; + final updatingErrorMsg = AppLocalizations.of( + context, + )!.phonebookUpdatingError; final value = await associationMemberListNotifier .updateMember(member, membershipEdit); if (value) { @@ -205,15 +211,12 @@ class MembershipEditorPage extends HookConsumerWidget { association.id, association.mandateYear.toString(), ); - displayToastWithContext( - TypeMsg.msg, - AppLocalizations.of(context)!.phonebookUpdatedMember, - ); + displayToastWithContext(TypeMsg.msg, updatedMemberMsg); QR.back(); } else { displayToastWithContext( TypeMsg.error, - AppLocalizations.of(context)!.phonebookUpdatingError, + updatingErrorMsg, ); } } else { @@ -252,19 +255,19 @@ class MembershipEditorPage extends HookConsumerWidget { orElse: () => 0, ), ); + final addedMemberMsg = AppLocalizations.of( + context, + )!.phonebookAddedMember; + final addingErrorMsg = AppLocalizations.of( + context, + )!.phonebookAddingError; final value = await associationMemberListNotifier .addMember(member, membershipAdd); if (value) { - displayToastWithContext( - TypeMsg.msg, - AppLocalizations.of(context)!.phonebookAddedMember, - ); + displayToastWithContext(TypeMsg.msg, addedMemberMsg); QR.back(); } else { - displayToastWithContext( - TypeMsg.error, - AppLocalizations.of(context)!.phonebookAddingError, - ); + displayToastWithContext(TypeMsg.error, addingErrorMsg); } } }); diff --git a/lib/raffle/ui/pages/creation_edit_page/ticket_handler.dart b/lib/raffle/ui/pages/creation_edit_page/ticket_handler.dart index c6d0886d53..81026195d0 100644 --- a/lib/raffle/ui/pages/creation_edit_page/ticket_handler.dart +++ b/lib/raffle/ui/pages/creation_edit_page/ticket_handler.dart @@ -120,21 +120,25 @@ class TicketHandler extends HookConsumerWidget { "Voulez-vous vraiment supprimer ce ticket?", onYes: () { tokenExpireWrapper(ref, () async { + final deletedTicketMsg = + AppLocalizations.of( + context, + )!.raffleDeletedTicket; + final deletingErrorMsg = + AppLocalizations.of( + context, + )!.raffleDeletingError; final value = await packTicketsNotifier .deletePackTicket(e); if (value) { displayToastWithContext( TypeMsg.msg, - AppLocalizations.of( - context, - )!.raffleDeletedTicket, + deletedTicketMsg, ); } else { displayToastWithContext( TypeMsg.error, - AppLocalizations.of( - context, - )!.raffleDeletingError, + deletingErrorMsg, ); } }); diff --git a/lib/raffle/ui/pages/pack_ticket_page/add_edit_pack_ticket_page.dart b/lib/raffle/ui/pages/pack_ticket_page/add_edit_pack_ticket_page.dart index 824ffde24c..a5a39491eb 100644 --- a/lib/raffle/ui/pages/pack_ticket_page/add_edit_pack_ticket_page.dart +++ b/lib/raffle/ui/pages/pack_ticket_page/add_edit_pack_ticket_page.dart @@ -126,6 +126,20 @@ class AddEditPackTicketPage extends HookConsumerWidget { final typeTicketNotifier = ref.watch( packTicketListProvider.notifier, ); + final editedTicketMsg = isEdit + ? AppLocalizations.of( + context, + )!.raffleEditedTicket + : AppLocalizations.of( + context, + )!.raffleAddedTicket; + final addingErrorMsg = isEdit + ? AppLocalizations.of( + context, + )!.raffleEditingError + : AppLocalizations.of( + context, + )!.raffleAddingError; final value = isEdit ? await typeTicketNotifier.updatePackTicket( newPackTicket, @@ -135,37 +149,15 @@ class AddEditPackTicketPage extends HookConsumerWidget { ); if (value) { QR.back(); - if (isEdit) { - displayToastWithContext( - TypeMsg.msg, - AppLocalizations.of( - context, - )!.raffleEditedTicket, - ); - } else { - displayToastWithContext( - TypeMsg.msg, - AppLocalizations.of( - context, - )!.raffleAddedTicket, - ); - } + displayToastWithContext( + TypeMsg.msg, + editedTicketMsg, + ); } else { - if (isEdit) { - displayToastWithContext( - TypeMsg.error, - AppLocalizations.of( - context, - )!.raffleEditingError, - ); - } else { - displayToastWithContext( - TypeMsg.error, - AppLocalizations.of( - context, - )!.raffleAlreadyExistTicket, - ); - } + displayToastWithContext( + TypeMsg.error, + addingErrorMsg, + ); } }); } else { diff --git a/lib/raffle/ui/pages/prize_page/add_edit_prize_page.dart b/lib/raffle/ui/pages/prize_page/add_edit_prize_page.dart index b7069d668e..8e3c98da9c 100644 --- a/lib/raffle/ui/pages/prize_page/add_edit_prize_page.dart +++ b/lib/raffle/ui/pages/prize_page/add_edit_prize_page.dart @@ -100,42 +100,34 @@ class AddEditPrizePage extends HookConsumerWidget { final prizeNotifier = ref.watch( prizeListProvider.notifier, ); + final editedTicket = isEdit + ? AppLocalizations.of( + context, + )!.raffleEditedTicket + : AppLocalizations.of( + context, + )!.raffleAddedTicket; + final addingError = isEdit + ? AppLocalizations.of( + context, + )!.raffleEditingError + : AppLocalizations.of( + context, + )!.raffleAddingError; final value = isEdit ? await prizeNotifier.updatePrize(newPrize) : await prizeNotifier.addPrize(newPrize); if (value) { QR.back(); - if (isEdit) { - displayToastWithContext( - TypeMsg.msg, - AppLocalizations.of( - context, - )!.raffleEditedTicket, - ); - } else { - displayToastWithContext( - TypeMsg.msg, - AppLocalizations.of( - context, - )!.raffleAddedTicket, - ); - } + displayToastWithContext( + TypeMsg.msg, + editedTicket, + ); } else { - if (isEdit) { - displayToastWithContext( - TypeMsg.error, - AppLocalizations.of( - context, - )!.raffleEditingError, - ); - } else { - displayToastWithContext( - TypeMsg.error, - AppLocalizations.of( - context, - )!.raffleAddingError, - ); - } + displayToastWithContext( + TypeMsg.error, + addingError, + ); } }); } else { diff --git a/lib/raffle/ui/pages/raffle_page/confirm_payment.dart b/lib/raffle/ui/pages/raffle_page/confirm_payment.dart index a28c61797c..5ba37d85de 100644 --- a/lib/raffle/ui/pages/raffle_page/confirm_payment.dart +++ b/lib/raffle/ui/pages/raffle_page/confirm_payment.dart @@ -248,6 +248,12 @@ class ConfirmPaymentDialog extends HookConsumerWidget { ); } else { await tokenExpireWrapper(ref, () async { + final boughtTicketMsg = AppLocalizations.of( + context, + )!.raffleBoughtTicket; + final boughtTicketErrorMsg = AppLocalizations.of( + context, + )!.raffleAddingError; final value = await userTicketListNotifier .buyTicket(packTicket); if (value) { @@ -256,16 +262,12 @@ class ConfirmPaymentDialog extends HookConsumerWidget { ); displayToastWithContext( TypeMsg.msg, - AppLocalizations.of( - context, - )!.raffleBoughtTicket, + boughtTicketMsg, ); } else { displayToastWithContext( TypeMsg.error, - AppLocalizations.of( - context, - )!.raffleAddingError, + boughtTicketErrorMsg, ); } navigationPop(); diff --git a/lib/recommendation/ui/pages/add_edit_page.dart b/lib/recommendation/ui/pages/add_edit_page.dart index c0e0a12c9d..fe7efb0ef2 100644 --- a/lib/recommendation/ui/pages/add_edit_page.dart +++ b/lib/recommendation/ui/pages/add_edit_page.dart @@ -157,6 +157,20 @@ class AddEditRecommendationPage extends HookConsumerWidget { summary: summary.text, description: description.text, ); + final editedRecommendationMsg = isEdit + ? AppLocalizations.of( + context, + )!.recommendationEditedRecommendation + : AppLocalizations.of( + context, + )!.recommendationAddedRecommendation; + final editingErrorMsg = isEdit + ? AppLocalizations.of( + context, + )!.recommendationEditingError + : AppLocalizations.of( + context, + )!.recommendationAddingError; final value = isEdit ? await recommendationListNotifier .updateRecommendation(newRecommendation) @@ -164,16 +178,14 @@ class AddEditRecommendationPage extends HookConsumerWidget { newRecommendation, ); if (value) { + displayAdvertToastWithContext( + TypeMsg.msg, + editedRecommendationMsg, + ); if (isEdit) { recommendationNotifier.setRecommendation( newRecommendation, ); - displayAdvertToastWithContext( - TypeMsg.msg, - AppLocalizations.of( - context, - )!.recommendationEditedRecommendation, - ); recommendationList.maybeWhen( data: (list) { if (logoBytes.value != null) { @@ -187,12 +199,6 @@ class AddEditRecommendationPage extends HookConsumerWidget { orElse: () {}, ); } else { - displayAdvertToastWithContext( - TypeMsg.msg, - AppLocalizations.of( - context, - )!.recommendationAddedRecommendation, - ); recommendationList.maybeWhen( data: (list) { final newRecommendation = list.last; @@ -209,13 +215,7 @@ class AddEditRecommendationPage extends HookConsumerWidget { } else { displayAdvertToastWithContext( TypeMsg.error, - isEdit - ? AppLocalizations.of( - context, - )!.recommendationEditingError - : AppLocalizations.of( - context, - )!.recommendationAddingError, + editingErrorMsg, ); } } else { diff --git a/lib/recommendation/ui/widgets/recommendation_card.dart b/lib/recommendation/ui/widgets/recommendation_card.dart index 52e90377d4..66880d7610 100644 --- a/lib/recommendation/ui/widgets/recommendation_card.dart +++ b/lib/recommendation/ui/widgets/recommendation_card.dart @@ -95,15 +95,13 @@ class RecommendationCard extends HookConsumerWidget { ), IconButton( onPressed: () async { + final copiedCodeMsg = AppLocalizations.of( + context, + )!.recommendationCopiedCode; await Clipboard.setData( ClipboardData(text: recommendation.code!), ); - displayToastWithContext( - TypeMsg.msg, - AppLocalizations.of( - context, - )!.recommendationCopiedCode, - ); + displayToastWithContext(TypeMsg.msg, copiedCodeMsg); }, icon: const Icon(Icons.copy), ), diff --git a/lib/settings/ui/pages/change_pass/change_pass.dart b/lib/settings/ui/pages/change_pass/change_pass.dart index 1c1d09a8ae..00c347993e 100644 --- a/lib/settings/ui/pages/change_pass/change_pass.dart +++ b/lib/settings/ui/pages/change_pass/change_pass.dart @@ -134,6 +134,12 @@ class ChangePassPage extends HookConsumerWidget { context, )!.settingsChangingPassword, onYes: () async { + final passwordChangedMsg = AppLocalizations.of( + context, + )!.settingsPasswordChanged; + final passwordChangeErrorMsg = AppLocalizations.of( + context, + )!.settingsUpdatingError; await tokenExpireWrapper(ref, () async { final value = await userNotifier.changePassword( oldPassword.text, @@ -144,16 +150,12 @@ class ChangePassPage extends HookConsumerWidget { QR.back(); displayToastWithContext( TypeMsg.msg, - AppLocalizations.of( - context, - )!.settingsPasswordChanged, + passwordChangedMsg, ); } else { displayToastWithContext( TypeMsg.error, - AppLocalizations.of( - context, - )!.settingsUpdatingError, + passwordChangeErrorMsg, ); } }); diff --git a/lib/settings/ui/pages/edit_user_page/edit_user_page.dart b/lib/settings/ui/pages/edit_user_page/edit_user_page.dart index cf1daafea0..61e83e4b8c 100644 --- a/lib/settings/ui/pages/edit_user_page/edit_user_page.dart +++ b/lib/settings/ui/pages/edit_user_page/edit_user_page.dart @@ -191,22 +191,26 @@ class EditUserPage extends HookConsumerWidget { right: 60, child: GestureDetector( onTap: () async { + final updatedProfilePictureMsg = + AppLocalizations.of( + context, + )!.settingsUpdatedProfilePicture; + final profilePictureErrorMsg = + AppLocalizations.of( + context, + )!.settingsErrorProfilePicture; final value = await profilePictureNotifier .cropImage(); if (value != null) { if (value) { displayToastWithContext( TypeMsg.msg, - AppLocalizations.of( - context, - )!.settingsUpdatedProfilePicture, + updatedProfilePictureMsg, ); } else { displayToastWithContext( TypeMsg.error, - AppLocalizations.of( - context, - )!.settingsErrorProfilePicture, + profilePictureErrorMsg, ); } } @@ -374,6 +378,12 @@ class EditUserPage extends HookConsumerWidget { child: child, ), onTap: () async { + final settingsUpdatedProfileMsg = AppLocalizations.of( + context, + )!.settingsUpdatedProfile; + final settingsUpdatingErrorMsg = AppLocalizations.of( + context, + )!.settingsUpdatingError; await tokenExpireWrapper(ref, () async { final value = await asyncUserNotifier.updateMe( user.copyWith( @@ -394,7 +404,7 @@ class EditUserPage extends HookConsumerWidget { if (value) { displayToastWithContext( TypeMsg.msg, - AppLocalizations.of(context)!.settingsUpdatedProfile, + settingsUpdatedProfileMsg, ); QR.removeNavigator( SettingsRouter.root + SettingsRouter.editAccount, @@ -402,7 +412,7 @@ class EditUserPage extends HookConsumerWidget { } else { displayToastWithContext( TypeMsg.error, - AppLocalizations.of(context)!.settingsUpdatingError, + settingsUpdatingErrorMsg, ); } }); diff --git a/lib/settings/ui/pages/main_page/main_page.dart b/lib/settings/ui/pages/main_page/main_page.dart index e936763913..e7f92e9484 100644 --- a/lib/settings/ui/pages/main_page/main_page.dart +++ b/lib/settings/ui/pages/main_page/main_page.dart @@ -166,13 +166,13 @@ class SettingsMainPage extends HookConsumerWidget { SettingsItem( icon: HeroIcons.calendarDays, onTap: () { + final icalCopiedMsg = AppLocalizations.of( + context, + )!.settingsIcalCopied; Clipboard.setData( ClipboardData(text: "${Repository.host}calendar/ical"), ).then((value) { - displayToastWithContext( - TypeMsg.msg, - AppLocalizations.of(context)!.settingsIcalCopied, - ); + displayToastWithContext(TypeMsg.msg, icalCopiedMsg); }); }, child: Text( @@ -266,20 +266,22 @@ class SettingsMainPage extends HookConsumerWidget { context, )!.settingsDetelePersonalDataDesc, onYes: () async { + final sendedDemandMsg = AppLocalizations.of( + context, + )!.settingsSendedDemand; + final errorSendingDemandMsg = AppLocalizations.of( + context, + )!.settingsErrorSendingDemand; final value = await meNotifier.deletePersonal(); if (value) { displayToastWithContext( TypeMsg.msg, - AppLocalizations.of( - context, - )!.settingsSendedDemand, + sendedDemandMsg, ); } else { displayToastWithContext( TypeMsg.error, - AppLocalizations.of( - context, - )!.settingsErrorSendingDemand, + errorSendingDemandMsg, ); } }, diff --git a/lib/vote/ui/pages/admin_page/section_bar.dart b/lib/vote/ui/pages/admin_page/section_bar.dart index af4c299cb3..11e1514f86 100644 --- a/lib/vote/ui/pages/admin_page/section_bar.dart +++ b/lib/vote/ui/pages/admin_page/section_bar.dart @@ -67,17 +67,23 @@ class SectionBar extends HookConsumerWidget { context, )!.voteDeleteSectionDescription, onYes: () async { + final deleteSectionSuccessMsg = AppLocalizations.of( + context, + )!.voteDeletedSection; + final deleteSectionErrorMsg = AppLocalizations.of( + context, + )!.voteDeletingError; final result = await sectionsNotifier.deleteSection(key); if (result) { sectionContenderListNotifier.deleteT(key); displayVoteToastWithContext( TypeMsg.msg, - AppLocalizations.of(context)!.voteDeletedSection, + deleteSectionSuccessMsg, ); } else { displayVoteToastWithContext( TypeMsg.error, - AppLocalizations.of(context)!.voteDeletingError, + deleteSectionErrorMsg, ); } }, diff --git a/lib/vote/ui/pages/admin_page/section_contender_items.dart b/lib/vote/ui/pages/admin_page/section_contender_items.dart index 90df07c1aa..46961b0eaa 100644 --- a/lib/vote/ui/pages/admin_page/section_contender_items.dart +++ b/lib/vote/ui/pages/admin_page/section_contender_items.dart @@ -95,6 +95,12 @@ class SectionContenderItems extends HookConsumerWidget { context, )!.voteDeletePretendanceDesc, onYes: () { + final pretendanceDeletedMsg = AppLocalizations.of( + context, + )!.votePretendanceDeleted; + final pretendanceNotDeletedMsg = AppLocalizations.of( + context, + )!.votePretendanceNotDeleted; tokenExpireWrapper(ref, () async { final value = await contenderListNotifier.deleteContender( e, @@ -102,7 +108,7 @@ class SectionContenderItems extends HookConsumerWidget { if (value) { displayVoteToastWithContext( TypeMsg.msg, - AppLocalizations.of(context)!.votePretendanceDeleted, + pretendanceDeletedMsg, ); contenderListNotifier.copy().then((value) { sectionContenderListNotifier.setTData(section, value); @@ -110,9 +116,7 @@ class SectionContenderItems extends HookConsumerWidget { } else { displayVoteToastWithContext( TypeMsg.error, - AppLocalizations.of( - context, - )!.votePretendanceNotDeleted, + pretendanceNotDeletedMsg, ); } }); diff --git a/lib/vote/ui/pages/contender_pages/add_edit_contender.dart b/lib/vote/ui/pages/contender_pages/add_edit_contender.dart index f8833bff50..ce267a2ee3 100644 --- a/lib/vote/ui/pages/contender_pages/add_edit_contender.dart +++ b/lib/vote/ui/pages/contender_pages/add_edit_contender.dart @@ -258,6 +258,10 @@ class AddEditContenderPage extends HookConsumerWidget { role.text == '') { return; } + final alreadyAddedMemberMsg = + AppLocalizations.of( + context, + )!.voteAlreadyAddedMember; if (addMemberKey.currentState!.validate()) { final value = await membersNotifier .addMember( @@ -273,9 +277,7 @@ class AddEditContenderPage extends HookConsumerWidget { } else { displayVoteToastWithContext( TypeMsg.error, - AppLocalizations.of( - context, - )!.voteAlreadyAddedMember, + alreadyAddedMemberMsg, ); } } @@ -369,6 +371,16 @@ class AddEditContenderPage extends HookConsumerWidget { section: section.value, program: program.text, ); + final editedPretendanceMsg = isEdit + ? AppLocalizations.of( + context, + )!.voteEditedPretendance + : AppLocalizations.of( + context, + )!.voteAddedPretendance; + final editingPretendanceErrorMsg = AppLocalizations.of( + context, + )!.voteEditingError; final value = isEdit ? await contenderListNotifier.updateContender( newContender, @@ -378,13 +390,11 @@ class AddEditContenderPage extends HookConsumerWidget { ); if (value) { QR.back(); + displayVoteToastWithContext( + TypeMsg.msg, + editedPretendanceMsg, + ); if (isEdit) { - displayVoteToastWithContext( - TypeMsg.msg, - AppLocalizations.of( - context, - )!.voteEditedPretendance, - ); contenderList.maybeWhen( data: (list) { final logoBytes = logo.value; @@ -402,12 +412,6 @@ class AddEditContenderPage extends HookConsumerWidget { orElse: () {}, ); } else { - displayVoteToastWithContext( - TypeMsg.msg, - AppLocalizations.of( - context, - )!.voteAddedPretendance, - ); contenderList.maybeWhen( data: (list) { final newContender = list.last; @@ -434,7 +438,7 @@ class AddEditContenderPage extends HookConsumerWidget { } else { displayVoteToastWithContext( TypeMsg.error, - AppLocalizations.of(context)!.voteEditingError, + editingPretendanceErrorMsg, ); } }); diff --git a/lib/vote/ui/pages/main_page/vote_button.dart b/lib/vote/ui/pages/main_page/vote_button.dart index cbf3a2782d..9c5ebd91e9 100644 --- a/lib/vote/ui/pages/main_page/vote_button.dart +++ b/lib/vote/ui/pages/main_page/vote_button.dart @@ -57,6 +57,12 @@ class VoteButton extends HookConsumerWidget { title: AppLocalizations.of(context)!.voteVote, descriptions: AppLocalizations.of(context)!.voteConfirmVote, onYes: () { + final voteSuccessMsg = AppLocalizations.of( + context, + )!.voteVoteSuccess; + final voteErrorMsg = AppLocalizations.of( + context, + )!.voteVoteError; tokenExpireWrapper(ref, () async { final result = await votesNotifier.addVote( Votes(id: selectedContender.id), @@ -66,12 +72,12 @@ class VoteButton extends HookConsumerWidget { selectedContenderNotifier.clear(); displayVoteToastWithContext( TypeMsg.msg, - AppLocalizations.of(context)!.voteVoteSuccess, + voteSuccessMsg, ); } else { displayVoteToastWithContext( TypeMsg.error, - AppLocalizations.of(context)!.voteVoteError, + voteErrorMsg, ); } }); diff --git a/lib/vote/ui/pages/section_pages/add_section.dart b/lib/vote/ui/pages/section_pages/add_section.dart index 69a500fb2d..21fd849aa1 100644 --- a/lib/vote/ui/pages/section_pages/add_section.dart +++ b/lib/vote/ui/pages/section_pages/add_section.dart @@ -59,6 +59,12 @@ class AddSectionPage extends HookConsumerWidget { WaitingButton( builder: (child) => AddEditButtonLayout(child: child), onTap: () async { + final addedSectionMsg = AppLocalizations.of( + context, + )!.voteAddedSection; + final addingErrorMsg = AppLocalizations.of( + context, + )!.voteAddingError; await tokenExpireWrapper(ref, () async { final value = await sectionListNotifier.addSection( Section( @@ -74,12 +80,12 @@ class AddSectionPage extends HookConsumerWidget { }); displayVoteToastWithContext( TypeMsg.msg, - AppLocalizations.of(context)!.voteAddedSection, + addedSectionMsg, ); } else { displayVoteToastWithContext( TypeMsg.error, - AppLocalizations.of(context)!.voteAddingError, + addingErrorMsg, ); } }); From d3ef257c9065e3a7c11e3552583f96058676f0aa Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 5 Jul 2025 16:53:20 +0200 Subject: [PATCH 064/473] Rebase --- lib/admin/router.dart | 3 ++- lib/advert/router.dart | 3 ++- lib/amap/router.dart | 3 ++- .../list_products_page/list_products.dart | 1 + lib/booking/router.dart | 3 ++- lib/centralisation/router.dart | 5 ++-- lib/cinema/router.dart | 3 ++- lib/event/router.dart | 3 ++- lib/feed/router.dart | 3 ++- lib/flappybird/router.dart | 3 ++- lib/home/router.dart | 3 ++- lib/home/ui/home.dart | 10 ++++---- lib/l10n/app_en.arb | 4 ++++ lib/l10n/app_fr.arb | 4 ++++ lib/l10n/app_localizations.dart | 24 +++++++++++++++++++ lib/l10n/app_localizations_en.dart | 12 ++++++++++ lib/l10n/app_localizations_fr.dart | 12 ++++++++++ lib/loan/router.dart | 3 ++- lib/navigation/class/module.dart | 14 +++++++---- lib/navigation/ui/all_module_page.dart | 2 +- lib/navigation/ui/drawer_template.dart | 5 +++- lib/others/ui/no_module.dart | 3 +-- lib/paiement/router.dart | 3 ++- lib/ph/router.dart | 3 ++- lib/phonebook/router.dart | 3 ++- lib/purchases/router.dart | 3 ++- lib/raffle/router.dart | 3 ++- lib/recommendation/router.dart | 3 ++- lib/seed-library/router.dart | 3 ++- lib/settings/router.dart | 3 ++- lib/tools/ui/styleguide/navbar.dart | 2 +- lib/tools/ui/styleguide/router.dart | 3 ++- lib/tools/ui/styleguide/text_entry.dart | 6 ++--- lib/vote/router.dart | 3 ++- 34 files changed, 125 insertions(+), 39 deletions(-) diff --git a/lib/admin/router.dart b/lib/admin/router.dart index f42c845a7c..6aa4df876d 100644 --- a/lib/admin/router.dart +++ b/lib/admin/router.dart @@ -28,6 +28,7 @@ import 'package:titan/admin/ui/pages/add_edit_structure_page/add_edit_structure_ deferred as add_edit_structure_page; import 'package:titan/admin/ui/pages/main_page/main_page.dart' deferred as main_page; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/navigation/class/module.dart'; import 'package:titan/tools/middlewares/admin_middleware.dart'; import 'package:titan/tools/middlewares/authenticated_middleware.dart'; @@ -52,7 +53,7 @@ class AdminRouter { '/detail_association_membership'; static const String addEditMember = '/add_edit_member'; static final Module module = Module( - name: "Administration", + getName: (context) => AppLocalizations.of(context)!.moduleAdmin, description: "Gérer les groupes, écoles et structures", root: AdminRouter.root, ); diff --git a/lib/advert/router.dart b/lib/advert/router.dart index 126b8f9111..2bf5f1585b 100644 --- a/lib/advert/router.dart +++ b/lib/advert/router.dart @@ -11,6 +11,7 @@ import 'package:titan/advert/ui/pages/form_page/add_rem_announcer_page.dart' deferred as add_rem_announcer_page; import 'package:titan/advert/ui/pages/main_page/main_page.dart' deferred as main_page; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/navigation/class/module.dart'; import 'package:titan/tools/middlewares/admin_middleware.dart'; import 'package:titan/tools/middlewares/authenticated_middleware.dart'; @@ -25,7 +26,7 @@ class AdvertRouter { static const String addRemAnnouncer = '/add_remove_announcer'; static const String detail = '/detail'; static final Module module = Module( - name: "Annonce", + getName: (context) => AppLocalizations.of(context)!.moduleAdvert, description: "Gérer les annonces et les annonceurs", root: AdvertRouter.root, ); diff --git a/lib/amap/router.dart b/lib/amap/router.dart index dcb8add08b..67d1623f8d 100644 --- a/lib/amap/router.dart +++ b/lib/amap/router.dart @@ -16,6 +16,7 @@ import 'package:titan/amap/ui/pages/presentation_page/text.dart' deferred as presentation_page; import 'package:titan/amap/ui/pages/product_pages/add_edit_product.dart' deferred as add_edit_product; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/navigation/class/module.dart'; import 'package:titan/tools/middlewares/admin_middleware.dart'; import 'package:titan/tools/middlewares/authenticated_middleware.dart'; @@ -33,7 +34,7 @@ class AmapRouter { static const String presentation = '/presentation'; static const String addEditProduct = '/add_edit_product'; static final Module module = Module( - name: "Amap", + getName: (context) => AppLocalizations.of(context)!.amapAmap, description: "Gérer les livraisons et les produits", root: AmapRouter.root, ); diff --git a/lib/amap/ui/pages/list_products_page/list_products.dart b/lib/amap/ui/pages/list_products_page/list_products.dart index c0a27f9be7..7d831a439f 100644 --- a/lib/amap/ui/pages/list_products_page/list_products.dart +++ b/lib/amap/ui/pages/list_products_page/list_products.dart @@ -8,6 +8,7 @@ import 'package:titan/amap/providers/page_controller_provider.dart'; import 'package:titan/amap/providers/scroll_controller_provider.dart'; import 'package:titan/amap/ui/pages/list_products_page/category_page.dart'; import 'package:titan/amap/ui/pages/list_products_page/web_page_navigation_button.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/navigation/providers/is_web_format_provider.dart'; class ListProducts extends HookConsumerWidget { diff --git a/lib/booking/router.dart b/lib/booking/router.dart index 318084b715..2ce4891101 100644 --- a/lib/booking/router.dart +++ b/lib/booking/router.dart @@ -15,6 +15,7 @@ import 'package:titan/booking/ui/pages/manager_page/manager_page.dart' deferred as manager_page; import 'package:titan/booking/ui/pages/admin_pages/add_edit_room_page.dart' deferred as add_edit_room_page; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/navigation/class/module.dart'; import 'package:titan/tools/middlewares/admin_middleware.dart'; import 'package:titan/tools/middlewares/authenticated_middleware.dart'; @@ -30,7 +31,7 @@ class BookingRouter { static const String detail = '/detail'; static const String room = '/room'; static final Module module = Module( - name: "Réservation", + getName: (context) => AppLocalizations.of(context)!.moduleBooking, description: "Gérer les réservations, les salles et les managers", root: BookingRouter.root, ); diff --git a/lib/centralisation/router.dart b/lib/centralisation/router.dart index 5ba6d14e5b..3076913cac 100644 --- a/lib/centralisation/router.dart +++ b/lib/centralisation/router.dart @@ -1,7 +1,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/centralisation/tools/constants.dart'; import 'package:titan/centralisation/ui/pages/main_page.dart' deferred as main_page; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/navigation/class/module.dart'; import 'package:titan/tools/middlewares/authenticated_middleware.dart'; import 'package:titan/tools/middlewares/deferred_middleware.dart'; @@ -11,8 +11,9 @@ class CentralisationRouter { final Ref ref; static const String root = '/centralisation'; static final Module module = Module( + getName: (context) => AppLocalizations.of(context)!.moduleCentralisation, description: "Gérer la centralisation des données", - name: CentralisationTextConstants.centralisation, + root: CentralisationRouter.root, ); CentralisationRouter(this.ref); diff --git a/lib/cinema/router.dart b/lib/cinema/router.dart index 0bc3a3a905..78cdea516b 100644 --- a/lib/cinema/router.dart +++ b/lib/cinema/router.dart @@ -8,6 +8,7 @@ import 'package:titan/cinema/ui/pages/main_page/main_page.dart' deferred as main_page; import 'package:titan/cinema/ui/pages/session_pages/add_edit_session.dart' deferred as add_edit_session_page; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/navigation/class/module.dart'; import 'package:titan/tools/middlewares/admin_middleware.dart'; import 'package:titan/tools/middlewares/authenticated_middleware.dart'; @@ -22,7 +23,7 @@ class CinemaRouter { static const String addEdit = '/add_edit'; static const String detail = '/detail'; static final Module module = Module( - name: "Cinéma", + getName: (context) => AppLocalizations.of(context)!.moduleCinema, description: "Gérer les séances de cinéma", root: CinemaRouter.root, ); diff --git a/lib/event/router.dart b/lib/event/router.dart index 31f3a9304a..80e96cfe8b 100644 --- a/lib/event/router.dart +++ b/lib/event/router.dart @@ -1,4 +1,5 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/navigation/class/module.dart'; import 'package:titan/event/providers/is_admin_provider.dart'; import 'package:titan/event/ui/pages/detail_page/detail_page.dart' @@ -21,7 +22,7 @@ class EventRouter { static const String addEdit = '/add_edit'; static const String detail = '/detail'; static final Module module = Module( - name: "Évenements", + getName: (context) => AppLocalizations.of(context)!.moduleEvent, description: "Gérer les événements et les participants", root: EventRouter.root, ); diff --git a/lib/feed/router.dart b/lib/feed/router.dart index c952d470e2..9f11abd64f 100644 --- a/lib/feed/router.dart +++ b/lib/feed/router.dart @@ -1,5 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/admin/providers/is_admin_provider.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/navigation/class/module.dart'; import 'package:titan/feed/ui/pages/admin_page/admin_page.dart' deferred as admin_page; @@ -16,7 +17,7 @@ class FeedRouter { static const String root = '/feed'; static const String admin = '/admin'; static final Module module = Module( - name: "Feed", + getName: (context) => AppLocalizations.of(context)!.moduleFeed, description: "Consulter les actualités et mises à jour", root: FeedRouter.root, ); diff --git a/lib/flappybird/router.dart b/lib/flappybird/router.dart index 238ee4d3c9..bcac822bb6 100644 --- a/lib/flappybird/router.dart +++ b/lib/flappybird/router.dart @@ -1,4 +1,5 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/navigation/class/module.dart'; import 'package:titan/flappybird/ui/pages/game_page/game_page.dart' deferred as play_page; @@ -13,7 +14,7 @@ class FlappyBirdRouter { static const String root = '/flappybird'; static const String leaderBoard = '/leaderboard'; static final Module module = Module( - name: "FlappyBird", + getName: (context) => AppLocalizations.of(context)!.moduleFlappyBird, description: "Jouer à Flappy Bird et consulter le classement", root: FlappyBirdRouter.root, ); diff --git a/lib/home/router.dart b/lib/home/router.dart index 8707cf6c3d..9582abb5b2 100644 --- a/lib/home/router.dart +++ b/lib/home/router.dart @@ -1,4 +1,5 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/navigation/class/module.dart'; import 'package:titan/event/ui/pages/detail_page/detail_page.dart' deferred as detail_page; @@ -12,7 +13,7 @@ class HomeRouter { static const String root = '/home'; static const String detail = '/detail'; static final Module module = Module( - name: "Calendrier", + getName: (context) => AppLocalizations.of(context)!.moduleCalendar, description: "Consulter les événements et les activités", root: HomeRouter.root, ); diff --git a/lib/home/ui/home.dart b/lib/home/ui/home.dart index eac1cd15fe..f740b1b76b 100644 --- a/lib/home/ui/home.dart +++ b/lib/home/ui/home.dart @@ -2,10 +2,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/event/providers/sorted_event_list_provider.dart'; -import 'package:titan/home/tools/constants.dart'; import 'package:titan/home/ui/day_list.dart'; import 'package:titan/home/ui/days_event.dart'; import 'package:titan/home/ui/month_bar.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/widgets/align_left_text.dart'; @@ -31,8 +31,8 @@ class HomePage extends HookConsumerWidget { const SizedBox(height: 10), DayList(scrollController, daysEventScrollController), const SizedBox(height: 15), - const AlignLeftText( - HomeTextConstants.incomingEvents, + AlignLeftText( + AppLocalizations.of(context)!.homeIncomingEvents, padding: EdgeInsets.symmetric(horizontal: 30.0), fontSize: 25, ), @@ -54,9 +54,9 @@ class HomePage extends HookConsumerWidget { .values .toList(), ) - : const Center( + : Center( child: Text( - HomeTextConstants.noEvents, + AppLocalizations.of(context)!.homeNoEvents, style: TextStyle( fontSize: 15, fontWeight: FontWeight.bold, diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 4aa3fa3f31..9a35a1a51c 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1117,6 +1117,10 @@ "moduleVote": "Vote", "modulePh": "PH", "moduleSettings": "Settings", + "moduleFeed": "Feed", + "moduleStyleGuide": "StyleGuide", + "moduleAdmin": "Adminitration", + "moduleOthers": "Others :", "modulePayment": "Payment", "paiementTopUp": "Top-up", "paiementStoreManagement": "Association management", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 9cfc744e97..833bb4f0fe 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -1117,6 +1117,10 @@ "moduleVote": "Vote", "modulePh": "PH", "moduleSettings": "Paramètres", + "moduleFeed": "Feed", + "moduleStyleGuide": "StyleGuide", + "moduleAdmin": "Adminitration", + "moduleOthers": "Autres :", "modulePayment": "Paiement", "paiementTopUp" : "Recharge", "paiementStoreManagement" : "Gestion des associations", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 0c3c49bcd2..6d580e7c03 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -6800,6 +6800,30 @@ abstract class AppLocalizations { /// **'Paramètres'** String get moduleSettings; + /// No description provided for @moduleFeed. + /// + /// In fr, this message translates to: + /// **'Feed'** + String get moduleFeed; + + /// No description provided for @moduleStyleGuide. + /// + /// In fr, this message translates to: + /// **'StyleGuide'** + String get moduleStyleGuide; + + /// No description provided for @moduleAdmin. + /// + /// In fr, this message translates to: + /// **'Adminitration'** + String get moduleAdmin; + + /// No description provided for @moduleOthers. + /// + /// In fr, this message translates to: + /// **'Autres :'** + String get moduleOthers; + /// No description provided for @modulePayment. /// /// In fr, this message translates to: diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 1b27949a89..f23932b32d 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -3420,6 +3420,18 @@ class AppLocalizationsEn extends AppLocalizations { @override String get moduleSettings => 'Settings'; + @override + String get moduleFeed => 'Feed'; + + @override + String get moduleStyleGuide => 'StyleGuide'; + + @override + String get moduleAdmin => 'Adminitration'; + + @override + String get moduleOthers => 'Others :'; + @override String get modulePayment => 'Payment'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 37c1635680..8486d1d282 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -3446,6 +3446,18 @@ class AppLocalizationsFr extends AppLocalizations { @override String get moduleSettings => 'Paramètres'; + @override + String get moduleFeed => 'Feed'; + + @override + String get moduleStyleGuide => 'StyleGuide'; + + @override + String get moduleAdmin => 'Adminitration'; + + @override + String get moduleOthers => 'Autres :'; + @override String get modulePayment => 'Paiement'; diff --git a/lib/loan/router.dart b/lib/loan/router.dart index b38c09ce02..3cdc71b67b 100644 --- a/lib/loan/router.dart +++ b/lib/loan/router.dart @@ -1,4 +1,5 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/navigation/class/module.dart'; import 'package:titan/loan/providers/is_loan_admin_provider.dart'; import 'package:titan/loan/ui/pages/admin_page/admin_page.dart' @@ -24,7 +25,7 @@ class LoanRouter { static const String addEditItem = '/add_edit_item'; static const String detail = '/detail'; static final Module module = Module( - name: "Prêt", + getName: (context) => AppLocalizations.of(context)!.moduleLoan, description: "Gérer les prêts et les articles", root: LoanRouter.root, ); diff --git a/lib/navigation/class/module.dart b/lib/navigation/class/module.dart index 9ad4c6eb3f..d49d6db9dd 100644 --- a/lib/navigation/class/module.dart +++ b/lib/navigation/class/module.dart @@ -1,21 +1,27 @@ import 'package:either_dart/either.dart'; +import 'package:flutter/widgets.dart'; import 'package:heroicons/heroicons.dart'; class Module { - String name; + final String Function(BuildContext) getName; String description; String root; - Module({required this.name, required this.description, required this.root}); + Module({ + required this.getName, + required this.description, + required this.root, + }); Module copy({ - String? name, + String Function(BuildContext)? getName, + String? description, Either? icon, String? root, bool? selected, }) => Module( - name: name ?? this.name, + getName: getName ?? this.getName, description: description ?? this.description, root: root ?? this.root, ); diff --git a/lib/navigation/ui/all_module_page.dart b/lib/navigation/ui/all_module_page.dart index b4742666ef..8e479136a8 100644 --- a/lib/navigation/ui/all_module_page.dart +++ b/lib/navigation/ui/all_module_page.dart @@ -37,7 +37,7 @@ class AllModulePage extends ConsumerWidget { SizedBox(height: 30), ...modules.map( (module) => ListItem( - title: module.name, + title: module.getName(context), subtitle: module.description, onTap: () { navbarListModuleNotifier.pushModule(module); diff --git a/lib/navigation/ui/drawer_template.dart b/lib/navigation/ui/drawer_template.dart index 59cac2491f..c8fd00c02a 100644 --- a/lib/navigation/ui/drawer_template.dart +++ b/lib/navigation/ui/drawer_template.dart @@ -3,6 +3,7 @@ import 'package:flutter/foundation.dart' show kIsWeb; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/feed/router.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/navigation/class/module.dart'; import 'package:titan/navigation/providers/display_quit_popup.dart'; import 'package:titan/navigation/providers/navbar_animation.dart'; @@ -97,7 +98,9 @@ class DrawerTemplate extends HookConsumerWidget { }), FloatingNavbarItem( module: Module( - name: 'Autres', + getName: (context) => AppLocalizations.of( + context, + )!.moduleOthers, description: '', root: AppRouter.allModules, ), diff --git a/lib/others/ui/no_module.dart b/lib/others/ui/no_module.dart index 5073aa1dcc..38532bf636 100644 --- a/lib/others/ui/no_module.dart +++ b/lib/others/ui/no_module.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/providers/module_root_list_provider.dart'; -import 'package:titan/others/tools/constants.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/providers/path_forwarding_provider.dart'; import 'package:qlevar_router/qlevar_router.dart'; @@ -21,7 +20,7 @@ class NoModulePage extends HookConsumerWidget { }, orElse: () {}, ); - return const Scaffold( + return Scaffold( backgroundColor: ColorConstants.background, body: Padding( padding: const EdgeInsets.symmetric(horizontal: 30), diff --git a/lib/paiement/router.dart b/lib/paiement/router.dart index 69afea1664..3c40b6fae8 100644 --- a/lib/paiement/router.dart +++ b/lib/paiement/router.dart @@ -1,4 +1,5 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/navigation/class/module.dart'; import 'package:titan/paiement/providers/is_payment_admin.dart'; import 'package:titan/paiement/ui/pages/admin_page/admin_page.dart' @@ -36,7 +37,7 @@ class PaymentRouter { static const String storeAdmin = '/storeAdmin'; static const String storeStats = '/storeStats'; static final Module module = Module( - name: "MyECLPay", + getName: (context) => AppLocalizations.of(context)!.modulePayment, description: "Gérer les paiements, les statistiques et les appareils", root: PaymentRouter.root, ); diff --git a/lib/ph/router.dart b/lib/ph/router.dart index 8bc6c27d81..bde6d29595 100644 --- a/lib/ph/router.dart +++ b/lib/ph/router.dart @@ -1,6 +1,7 @@ // ignore_for_file: constant_identifier_names import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/navigation/class/module.dart'; import 'package:titan/ph/providers/is_ph_admin_provider.dart'; import 'package:titan/ph/ui/pages/form_page/add_edit_ph_page.dart' @@ -24,7 +25,7 @@ class PhRouter { static const String admin = '/admin'; static const String add_ph = '/add_ph'; static final Module module = Module( - name: "PH", + getName: (context) => AppLocalizations.of(context)!.modulePh, description: "Gérer les PH, les formulaires et les administrateurs", root: PhRouter.root, ); diff --git a/lib/phonebook/router.dart b/lib/phonebook/router.dart index 5d443b1b42..4058a09cfa 100644 --- a/lib/phonebook/router.dart +++ b/lib/phonebook/router.dart @@ -1,4 +1,5 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/navigation/class/module.dart'; import 'package:titan/phonebook/providers/phonebook_admin_provider.dart'; import 'package:titan/phonebook/ui/pages/admin_page/admin_page.dart'; @@ -22,7 +23,7 @@ class PhonebookRouter { static const String memberDetail = '/member_detail'; static const String addEditMember = '/add_edit_member'; static final Module module = Module( - name: "Annuaire", + getName: (context) => AppLocalizations.of(context)!.modulePhonebook, description: "Gérer les associations, les membres et les administrateurs", root: PhonebookRouter.root, ); diff --git a/lib/purchases/router.dart b/lib/purchases/router.dart index 3685122c8d..9d5be2c51a 100644 --- a/lib/purchases/router.dart +++ b/lib/purchases/router.dart @@ -1,4 +1,5 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/navigation/class/module.dart'; import 'package:titan/purchases/providers/purchases_admin_provider.dart'; import 'package:titan/purchases/ui/pages/history_page/history_page.dart'; @@ -20,7 +21,7 @@ class PurchasesRouter { static const String ticket = '/ticket'; static const String purchase = '/purchase'; static final Module module = Module( - name: "Achats", + getName: (context) => AppLocalizations.of(context)!.modulePurchases, description: "Gérer les achats, les tickets et l'historique", root: PurchasesRouter.root, ); diff --git a/lib/raffle/router.dart b/lib/raffle/router.dart index e7ac4b3df5..dc5a5fc68a 100644 --- a/lib/raffle/router.dart +++ b/lib/raffle/router.dart @@ -1,4 +1,5 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/navigation/class/module.dart'; import 'package:titan/raffle/providers/is_raffle_admin.dart'; import 'package:titan/raffle/ui/pages/admin_module_page/admin_module_page.dart' @@ -27,7 +28,7 @@ class RaffleRouter { static const String addEditPackTicket = '/add_edit_pack_ticket'; static const String creation = '/creation'; static final Module module = Module( - name: "Tombola", + getName: (context) => AppLocalizations.of(context)!.moduleRaffle, description: "Gérer les tombolas, les prix et les tickets", root: RaffleRouter.root, ); diff --git a/lib/recommendation/router.dart b/lib/recommendation/router.dart index 5893ad79cb..dfe45b41fe 100644 --- a/lib/recommendation/router.dart +++ b/lib/recommendation/router.dart @@ -1,4 +1,5 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/navigation/class/module.dart'; import 'package:titan/recommendation/providers/is_recommendation_admin_provider.dart'; import 'package:titan/recommendation/ui/pages/main_page.dart' @@ -19,7 +20,7 @@ class RecommendationRouter { static const String information = '/information'; static const String addEdit = '/add_edit'; static final Module module = Module( - name: "Bons plans", + getName: (context) => AppLocalizations.of(context)!.moduleRecommendation, description: "Gérer les recommandations, les informations et les administrateurs", root: RecommendationRouter.root, diff --git a/lib/seed-library/router.dart b/lib/seed-library/router.dart index a024aad829..3ca34d3f37 100644 --- a/lib/seed-library/router.dart +++ b/lib/seed-library/router.dart @@ -1,4 +1,5 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/navigation/class/module.dart'; import 'package:titan/seed-library/providers/is_seed_library_admin_provider.dart'; import 'package:titan/seed-library/ui/pages/add_edit_species_page/add_edit_species_page.dart' @@ -46,7 +47,7 @@ class SeedLibraryRouter { SeedLibraryRouter(this.ref); static final Module module = Module( - name: "Grainothèque", + getName: (context) => AppLocalizations.of(context)!.moduleSeedLibrary, description: "Gérer les graines, les espèces et les stocks", root: SeedLibraryRouter.root, ); diff --git a/lib/settings/router.dart b/lib/settings/router.dart index 8da36c5caa..bde36de735 100644 --- a/lib/settings/router.dart +++ b/lib/settings/router.dart @@ -1,6 +1,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/admin/router.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/navigation/class/module.dart'; import 'package:titan/settings/ui/pages/change_pass/change_pass.dart' deferred as change_pass; @@ -27,7 +28,7 @@ class SettingsRouter { static const String modules = '/modules'; static const String notifications = '/notifications'; static final Module module = Module( - name: "Paramètres", + getName: (context) => AppLocalizations.of(context)!.moduleSettings, description: "Gérer les paramètres de l'application", root: AdminRouter.root, ); diff --git a/lib/tools/ui/styleguide/navbar.dart b/lib/tools/ui/styleguide/navbar.dart index a936e4c52a..d95bc18ee8 100644 --- a/lib/tools/ui/styleguide/navbar.dart +++ b/lib/tools/ui/styleguide/navbar.dart @@ -216,7 +216,7 @@ class FloatingNavbar extends HookConsumerWidget { return Center( child: AutoSizeText( - item.module.name, + item.module.getName(context), style: TextStyle( color: textColor, fontSize: 14, diff --git a/lib/tools/ui/styleguide/router.dart b/lib/tools/ui/styleguide/router.dart index 633e01ba0b..2eb4ce46e9 100644 --- a/lib/tools/ui/styleguide/router.dart +++ b/lib/tools/ui/styleguide/router.dart @@ -1,4 +1,5 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/navigation/class/module.dart'; import 'package:titan/tools/ui/styleguide/styleguide_page.dart'; import 'package:qlevar_router/qlevar_router.dart'; @@ -7,7 +8,7 @@ class StyleGuideRouter { final Ref ref; static const String root = '/styleguide'; static final Module module = Module( - name: "Style Guide", + getName: (context) => AppLocalizations.of(context)!.moduleStyleGuide, description: "Explore the UI components and styles used in Titan", root: StyleGuideRouter.root, ); diff --git a/lib/tools/ui/styleguide/text_entry.dart b/lib/tools/ui/styleguide/text_entry.dart index 4f208303e1..4214fab8d1 100644 --- a/lib/tools/ui/styleguide/text_entry.dart +++ b/lib/tools/ui/styleguide/text_entry.dart @@ -33,7 +33,7 @@ class TextEntry extends StatelessWidget { this.color = ColorConstants.tertiary, this.enabledColor = ColorConstants.tertiary, this.errorColor = ColorConstants.main, - this.noValueError = TextConstants.noValue, + this.noValueError = "No value", this.suffixIcon, this.isNegative = false, this.textInputAction = TextInputAction.next, @@ -95,14 +95,14 @@ class TextEntry extends StatelessWidget { if (isInt) { final intValue = int.tryParse(value); if (intValue == null || (intValue < 0 && !isNegative)) { - return TextConstants.invalidNumber; + return "Invalid number"; } } if (isDouble) { final doubleValue = double.tryParse(value.replaceAll(',', '.')); if (doubleValue == null || (doubleValue < 0 && !isNegative)) { - return TextConstants.invalidNumber; + return "Invalid number"; } } diff --git a/lib/vote/router.dart b/lib/vote/router.dart index f89d51be5c..01c6e312e6 100644 --- a/lib/vote/router.dart +++ b/lib/vote/router.dart @@ -1,4 +1,5 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/navigation/class/module.dart'; import 'package:titan/tools/middlewares/admin_middleware.dart'; import 'package:titan/tools/middlewares/authenticated_middleware.dart'; @@ -24,7 +25,7 @@ class VoteRouter { static const String addSection = '/add_edit_section'; static const String detail = '/detail'; static final Module module = Module( - name: "Vote", + getName: (context) => AppLocalizations.of(context)!.moduleVote, description: "Gérer les votes, les sections et les candidats", root: VoteRouter.root, ); From bbc559c177655aa84c8803331afa5ecd721cf0c3 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 5 Jul 2025 16:55:09 +0200 Subject: [PATCH 065/473] Unused import --- lib/navigation/ui/quit_dialog.dart | 1 - lib/others/ui/update_page.dart | 1 - 2 files changed, 2 deletions(-) diff --git a/lib/navigation/ui/quit_dialog.dart b/lib/navigation/ui/quit_dialog.dart index cad74e3d43..e23765b213 100644 --- a/lib/navigation/ui/quit_dialog.dart +++ b/lib/navigation/ui/quit_dialog.dart @@ -3,7 +3,6 @@ import 'package:flutter/foundation.dart' show kIsWeb; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/auth/providers/openid_provider.dart'; import 'package:titan/navigation/providers/display_quit_popup.dart'; -import 'package:titan/navigation/tools/constants.dart'; import 'package:titan/service/providers/firebase_token_expiration_provider.dart'; import 'package:titan/service/providers/messages_provider.dart'; import 'package:titan/tools/functions.dart'; diff --git a/lib/others/ui/update_page.dart b/lib/others/ui/update_page.dart index 25e535eb0c..a6b93f4615 100644 --- a/lib/others/ui/update_page.dart +++ b/lib/others/ui/update_page.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/others/tools/constants.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/version/providers/titan_version_provider.dart'; import 'package:titan/l10n/app_localizations.dart'; From a4a23e4dbe7a260f8781fea2a4dba385f107fb6f Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sun, 6 Jul 2025 11:35:53 +0200 Subject: [PATCH 066/473] language fixes --- lib/l10n/app_en.arb | 8 ++++---- lib/l10n/app_fr.arb | 2 +- lib/l10n/app_localizations.dart | 2 +- lib/l10n/app_localizations_en.dart | 4 ++-- lib/l10n/app_localizations_fr.dart | 2 +- lib/main.dart | 2 ++ 6 files changed, 11 insertions(+), 9 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 9a35a1a51c..4bbbec2d6d 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1117,10 +1117,10 @@ "moduleVote": "Vote", "modulePh": "PH", "moduleSettings": "Settings", - "moduleFeed": "Feed", - "moduleStyleGuide": "StyleGuide", - "moduleAdmin": "Adminitration", - "moduleOthers": "Others :", + "moduleFeed": "Feed", + "moduleStyleGuide": "StyleGuide", + "moduleAdmin": "Administration", + "moduleOthers": "Others", "modulePayment": "Payment", "paiementTopUp": "Top-up", "paiementStoreManagement": "Association management", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 833bb4f0fe..986e58e447 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -1120,7 +1120,7 @@ "moduleFeed": "Feed", "moduleStyleGuide": "StyleGuide", "moduleAdmin": "Adminitration", - "moduleOthers": "Autres :", + "moduleOthers": "Autres", "modulePayment": "Paiement", "paiementTopUp" : "Recharge", "paiementStoreManagement" : "Gestion des associations", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 6d580e7c03..8e3e7698bb 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -6821,7 +6821,7 @@ abstract class AppLocalizations { /// No description provided for @moduleOthers. /// /// In fr, this message translates to: - /// **'Autres :'** + /// **'Autres'** String get moduleOthers; /// No description provided for @modulePayment. diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index f23932b32d..2694e05cd2 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -3427,10 +3427,10 @@ class AppLocalizationsEn extends AppLocalizations { String get moduleStyleGuide => 'StyleGuide'; @override - String get moduleAdmin => 'Adminitration'; + String get moduleAdmin => 'Administration'; @override - String get moduleOthers => 'Others :'; + String get moduleOthers => 'Others'; @override String get modulePayment => 'Payment'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 8486d1d282..bf86111c65 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -3456,7 +3456,7 @@ class AppLocalizationsFr extends AppLocalizations { String get moduleAdmin => 'Adminitration'; @override - String get moduleOthers => 'Autres :'; + String get moduleOthers => 'Autres'; @override String get modulePayment => 'Paiement'; diff --git a/lib/main.dart b/lib/main.dart index 2cbe38a11e..dae22f08b0 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,6 +6,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/login/providers/animation_provider.dart'; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:google_fonts/google_fonts.dart'; @@ -103,6 +104,7 @@ class MyApp extends HookConsumerWidget { scrollBehavior: MyCustomScrollBehavior(), supportedLocales: const [Locale('en', 'US'), Locale('fr', 'FR')], localizationsDelegates: const [ + AppLocalizations.delegate, GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, GlobalCupertinoLocalizations.delegate, From 1a453194ce8d6388574a1970f06d840b014533e1 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Wed, 16 Jul 2025 18:56:22 +0200 Subject: [PATCH 067/473] feat: disappearing navbar on scroll --- lib/feed/ui/pages/main_page/main_page.dart | 16 +- .../providers/navbar_visibility_provider.dart | 131 ++++++++++++++++ lib/navigation/ui/all_module_page.dart | 61 ++++---- lib/navigation/ui/drawer_template.dart | 130 +++++++++------- lib/navigation/ui/scroll_to_hide_navbar.dart | 144 ++++++++++++++++++ 5 files changed, 396 insertions(+), 86 deletions(-) create mode 100644 lib/navigation/providers/navbar_visibility_provider.dart create mode 100644 lib/navigation/ui/scroll_to_hide_navbar.dart diff --git a/lib/feed/ui/pages/main_page/main_page.dart b/lib/feed/ui/pages/main_page/main_page.dart index 3723cc6778..ec3fbd06a6 100644 --- a/lib/feed/ui/pages/main_page/main_page.dart +++ b/lib/feed/ui/pages/main_page/main_page.dart @@ -8,6 +8,7 @@ import 'package:titan/feed/class/feed_item.dart'; import 'package:titan/feed/router.dart'; import 'package:titan/feed/ui/feed.dart'; import 'package:titan/feed/ui/pages/main_page/feed_timeline.dart'; +import 'package:titan/navigation/ui/scroll_to_hide_navbar.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/layouts/horizontal_list_view.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; @@ -24,6 +25,7 @@ class FeedMainPage extends HookConsumerWidget { final feedItems = useState>(FeedItem.getFakeItems()); final filteredItems = useState>(feedItems.value); final isAdmin = ref.watch(isAdminProvider); + final scrollController = useScrollController(); return FeedTemplate( child: Container( @@ -108,11 +110,15 @@ class FeedMainPage extends HookConsumerWidget { SizedBox( height: MediaQuery.of(context).size.height - 193, - child: SingleChildScrollView( - physics: const BouncingScrollPhysics(), - child: FeedTimeline( - items: filteredItems.value, - onItemTap: (item) {}, + child: ScrollToHideNavbar( + controller: scrollController, + child: SingleChildScrollView( + controller: scrollController, + physics: const BouncingScrollPhysics(), + child: FeedTimeline( + items: filteredItems.value, + onItemTap: (item) {}, + ), ), ), ), diff --git a/lib/navigation/providers/navbar_visibility_provider.dart b/lib/navigation/providers/navbar_visibility_provider.dart new file mode 100644 index 0000000000..45efc744f1 --- /dev/null +++ b/lib/navigation/providers/navbar_visibility_provider.dart @@ -0,0 +1,131 @@ +import 'dart:async'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +// Class to manage the navbar visibility state +class NavbarVisibilityNotifier extends StateNotifier { + NavbarVisibilityNotifier() : super(true); + + // Debounce timer to prevent rapid show/hide transitions + Timer? _debounceTimer; + Timer? _hideTimer; + + // Track the last requested state to prevent unnecessary updates + bool _lastRequestedState = true; + + // Update state with debounce to avoid flickering + void _updateState(bool visible) { + // Track the requested state + _lastRequestedState = visible; + + // Cancel any pending timers + _debounceTimer?.cancel(); + _hideTimer?.cancel(); + + // Only update if the state is actually changing + if (state != visible) { + // Faster response for showing (better UX), shorter delay for hiding + if (visible) { + // Show immediately for better responsiveness when scrolling up + _debounceTimer = Timer(const Duration(milliseconds: 50), () { + if (_lastRequestedState == true) { + state = true; + } + }); + } else { + // Shorter delay for hiding to make it more responsive to slow scrolling + _hideTimer = Timer(const Duration(milliseconds: 100), () { + if (_lastRequestedState == false) { + state = false; + } + }); + } + } + } + + void show() => _updateState(true); + void hide() => _updateState(false); + void toggle() => _updateState(!state); + + @override + void dispose() { + _debounceTimer?.cancel(); + _hideTimer?.cancel(); + super.dispose(); + } +} + +// Provider for navbar visibility +final navbarVisibilityProvider = + StateNotifierProvider((ref) { + return NavbarVisibilityNotifier(); + }); + +// Class to manage scroll direction detection +class ScrollDirectionNotifier extends StateNotifier { + ScrollDirectionNotifier() : super(ScrollDirection.idle); + + double _lastScrollOffset = 0; + DateTime _lastDirectionChange = DateTime.now(); + + // Minimum threshold for scrolling to be considered significant + final double _scrollThreshold = 5.0; + + // Minimum time between direction changes to prevent rapid toggling + final int _directionChangeThresholdMs = 50; + + void updateScrollDirection(double scrollOffset) { + // Calculate scroll delta + final double scrollDelta = (scrollOffset - _lastScrollOffset).abs(); + + // Get current time to check rate of direction changes + final now = DateTime.now(); + final timeSinceLastChange = now + .difference(_lastDirectionChange) + .inMilliseconds; + + // Handle both normal scrolling and slow scrolling: + // 1. Normal: Significant delta + time threshold + // 2. Slow: Lower delta threshold but longer time between updates + bool isSignificantScrolling = + scrollDelta > _scrollThreshold && + timeSinceLastChange > _directionChangeThresholdMs; + + bool isSlowScrolling = scrollDelta > 1.0 && timeSinceLastChange > 200; + + if (isSignificantScrolling || isSlowScrolling) { + final previousState = state; + + if (scrollOffset > _lastScrollOffset) { + // Scrolling down + state = ScrollDirection.down; + } else if (scrollOffset < _lastScrollOffset) { + // Scrolling up + state = ScrollDirection.up; + } + + // If direction changed, update the timestamp + if (previousState != state) { + _lastDirectionChange = now; + } else { + // Even if direction didn't change, update timestamp for slow scrolling + // to avoid too frequent updates + _lastDirectionChange = now; + } + + _lastScrollOffset = scrollOffset; + } + } + + void resetDirection() { + state = ScrollDirection.idle; + _lastScrollOffset = 0; + } +} + +enum ScrollDirection { up, down, idle } + +// Provider for scroll direction +final scrollDirectionProvider = + StateNotifierProvider((ref) { + return ScrollDirectionNotifier(); + }); diff --git a/lib/navigation/ui/all_module_page.dart b/lib/navigation/ui/all_module_page.dart index 8e479136a8..1623175b9d 100644 --- a/lib/navigation/ui/all_module_page.dart +++ b/lib/navigation/ui/all_module_page.dart @@ -1,7 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/navigation/providers/navbar_module_list.dart'; +import 'package:titan/navigation/ui/scroll_to_hide_navbar.dart'; import 'package:titan/navigation/ui/top_bar.dart'; import 'package:titan/settings/providers/module_list_provider.dart'; import 'package:titan/tools/constants.dart'; @@ -9,7 +11,7 @@ import 'package:titan/tools/providers/path_forwarding_provider.dart'; import 'package:titan/tools/ui/styleguide/list_item.dart'; import 'package:titan/tools/ui/styleguide/searchbar.dart'; -class AllModulePage extends ConsumerWidget { +class AllModulePage extends HookConsumerWidget { const AllModulePage({super.key}); @override @@ -18,39 +20,44 @@ class AllModulePage extends ConsumerWidget { final navbarListModuleNotifier = ref.watch( navbarListModuleProvider.notifier, ); + final scrollController = useScrollController(); return Container( color: ColorConstants.background, child: Column( children: [ TopBar(), Expanded( - child: SingleChildScrollView( - physics: const BouncingScrollPhysics(), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 20.0), - child: Column( - children: [ - CustomSearchBar( - onSearch: (String query) {}, - onFilter: () {}, - ), - SizedBox(height: 30), - ...modules.map( - (module) => ListItem( - title: module.getName(context), - subtitle: module.description, - onTap: () { - navbarListModuleNotifier.pushModule(module); - final pathForwardingNotifier = ref.watch( - pathForwardingProvider.notifier, - ); - pathForwardingNotifier.forward(module.root); - QR.to(module.root); - }, + child: ScrollToHideNavbar( + controller: scrollController, + child: SingleChildScrollView( + controller: scrollController, + physics: const BouncingScrollPhysics(), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + child: Column( + children: [ + CustomSearchBar( + onSearch: (String query) {}, + onFilter: () {}, ), - ), - SizedBox(height: 80), - ], + SizedBox(height: 30), + ...modules.map( + (module) => ListItem( + title: module.getName(context), + subtitle: module.description, + onTap: () { + navbarListModuleNotifier.pushModule(module); + final pathForwardingNotifier = ref.watch( + pathForwardingProvider.notifier, + ); + pathForwardingNotifier.forward(module.root); + QR.to(module.root); + }, + ), + ), + SizedBox(height: 80), + ], + ), ), ), ), diff --git a/lib/navigation/ui/drawer_template.dart b/lib/navigation/ui/drawer_template.dart index c8fd00c02a..1300f48d4b 100644 --- a/lib/navigation/ui/drawer_template.dart +++ b/lib/navigation/ui/drawer_template.dart @@ -8,6 +8,7 @@ import 'package:titan/navigation/class/module.dart'; import 'package:titan/navigation/providers/display_quit_popup.dart'; import 'package:titan/navigation/providers/navbar_animation.dart'; import 'package:titan/navigation/providers/navbar_module_list.dart'; +import 'package:titan/navigation/providers/navbar_visibility_provider.dart'; import 'package:titan/navigation/providers/should_setup_provider.dart'; import 'package:titan/router.dart'; import 'package:titan/service/tools/setup.dart'; @@ -62,63 +63,84 @@ class DrawerTemplate extends HookConsumerWidget { left: 0, bottom: 0, right: 0, - child: Visibility( - visible: animation!.isCompleted && animation.value == 1.0, - child: AnimatedBuilder( - animation: animation, - builder: (context, child) => Opacity( - opacity: animation.value, - child: FloatingNavbar( - items: [ - FloatingNavbarItem( - module: FeedRouter.module, - onTap: () { - pathForwardingNotifier.forward( - FeedRouter.root, - ); - WidgetsBinding.instance.addPostFrameCallback(( - _, - ) { - QR.to(FeedRouter.root); - }); - }, - ), - ...navbarListModule.map((module) { - return FloatingNavbarItem( - module: module, - onTap: () { - navbarListModuleNotifier.pushModule(module); - pathForwardingNotifier.forward(module.root); - WidgetsBinding.instance - .addPostFrameCallback((_) { - QR.to(module.root); - }); - }, - ); - }), - FloatingNavbarItem( - module: Module( - getName: (context) => AppLocalizations.of( - context, - )!.moduleOthers, - description: '', - root: AppRouter.allModules, + child: Consumer( + builder: (context, ref, child) { + final navbarVisible = ref.watch( + navbarVisibilityProvider, + ); + return AnimatedSlide( + offset: Offset(0, navbarVisible ? 0 : 1), + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + child: AnimatedOpacity( + opacity: navbarVisible ? 1.0 : 0.0, + duration: const Duration(milliseconds: 300), + child: Visibility( + visible: + animation!.isCompleted && + animation.value == 1.0, + child: AnimatedBuilder( + animation: animation, + builder: (context, child) => Opacity( + opacity: animation.value, + child: FloatingNavbar( + items: [ + FloatingNavbarItem( + module: FeedRouter.module, + onTap: () { + pathForwardingNotifier.forward( + FeedRouter.root, + ); + WidgetsBinding.instance + .addPostFrameCallback((_) { + QR.to(FeedRouter.root); + }); + }, + ), + ...navbarListModule.map((module) { + return FloatingNavbarItem( + module: module, + onTap: () { + navbarListModuleNotifier.pushModule( + module, + ); + pathForwardingNotifier.forward( + module.root, + ); + WidgetsBinding.instance + .addPostFrameCallback((_) { + QR.to(module.root); + }); + }, + ); + }), + FloatingNavbarItem( + module: Module( + getName: (context) => + AppLocalizations.of( + context, + )!.moduleOthers, + description: '', + root: AppRouter.allModules, + ), + onTap: () { + pathForwardingNotifier.forward( + AppRouter.allModules, + ); + WidgetsBinding.instance + .addPostFrameCallback((_) { + QR.to(AppRouter.allModules); + }); + }, + ), + ], + ), ), - onTap: () { - pathForwardingNotifier.forward( - AppRouter.allModules, - ); - WidgetsBinding.instance.addPostFrameCallback(( - _, - ) { - QR.to(AppRouter.allModules); - }); - }, ), - ], + ), ), - ), - ), + ); + }, ), ), if (displayQuit) const QuitDialog(), diff --git a/lib/navigation/ui/scroll_to_hide_navbar.dart b/lib/navigation/ui/scroll_to_hide_navbar.dart new file mode 100644 index 0000000000..90ca142ac2 --- /dev/null +++ b/lib/navigation/ui/scroll_to_hide_navbar.dart @@ -0,0 +1,144 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/navigation/providers/navbar_visibility_provider.dart'; + +class ScrollToHideNavbar extends ConsumerStatefulWidget { + final Widget child; + final ScrollController controller; + final Duration duration; + + const ScrollToHideNavbar({ + super.key, + required this.child, + required this.controller, + this.duration = const Duration(milliseconds: 200), + }); + + @override + ConsumerState createState() => _ScrollToHideNavbarState(); +} + +class _ScrollToHideNavbarState extends ConsumerState { + @override + void initState() { + super.initState(); + widget.controller.addListener(_scrollListener); + } + + @override + void dispose() { + widget.controller.removeListener(_scrollListener); + super.dispose(); + } + + double _previousOffset = 0; + bool _isScrollingDown = false; + final _scrollThreshold = 2.0; // Lower threshold to detect slower scrolling + final _directionChangeThreshold = + 3.0; // Threshold specifically for direction changes + bool _isOverscrollInProgress = false; + DateTime _lastDirectionChange = DateTime.now(); + DateTime _lastSlowScrollCheck = DateTime.now(); + + void _scrollListener() { + final navbarVisibilityNotifier = ref.read( + navbarVisibilityProvider.notifier, + ); + final scrollDirectionNotifier = ref.read(scrollDirectionProvider.notifier); + + // Get current scroll metrics + final ScrollPosition position = widget.controller.position; + final double currentOffset = position.pixels; + final double maxScrollExtent = position.maxScrollExtent; + final bool isAtTop = currentOffset <= 0; + final bool isAtBottom = currentOffset >= maxScrollExtent; + + // Always show navbar if at the top + if (isAtTop) { + navbarVisibilityNotifier.show(); + _previousOffset = 0; + _isOverscrollInProgress = false; + return; + } + + // Calculate scroll difference + final double scrollDelta = (currentOffset - _previousOffset).abs(); + + // Check if currently in overscroll state (for BouncingScrollPhysics) + bool isOverScrolling = position.outOfRange; + + // Mark the beginning of an overscroll + if (isOverScrolling && !_isOverscrollInProgress) { + _isOverscrollInProgress = true; + } + + // Reset overscroll state when scrolling away from edges + if (!isOverScrolling && + _isOverscrollInProgress && + !isAtTop && + !isAtBottom) { + _isOverscrollInProgress = false; + } + + // Ignore small movements (helps with bouncing physics) + if (scrollDelta < _scrollThreshold) { + _previousOffset = currentOffset; + return; + } + + // Handle scroll direction and visibility updates if not overscrolling + if (!_isOverscrollInProgress) { + bool newIsScrollingDown = currentOffset > _previousOffset; + final currentTime = DateTime.now(); + final timeSinceLastChange = currentTime + .difference(_lastDirectionChange) + .inMilliseconds; + final timeSinceLastSlowCheck = currentTime + .difference(_lastSlowScrollCheck) + .inMilliseconds; + + // Process direction changes with minimum time threshold + if (_isScrollingDown != newIsScrollingDown && + scrollDelta >= _directionChangeThreshold && + timeSinceLastChange > 100) { + _isScrollingDown = newIsScrollingDown; + _lastDirectionChange = currentTime; + + // Update direction notifier + scrollDirectionNotifier.updateScrollDirection(currentOffset); + + // Update navbar visibility based on direction change + if (newIsScrollingDown) { + // Scrolling down - hide navbar + navbarVisibilityNotifier.hide(); + } else { + // Scrolling up - show navbar + navbarVisibilityNotifier.show(); + } + } + // Handle continuous scrolling in the same direction + else if (scrollDelta >= _scrollThreshold && + timeSinceLastSlowCheck > 150) { + // Update timestamp for slow scroll check + _lastSlowScrollCheck = currentTime; + + // Handle continuous downward scrolling - ensure navbar hides + if (newIsScrollingDown) { + navbarVisibilityNotifier.hide(); + } + // For continuous upward scrolling, ensure navbar shows + else if (!newIsScrollingDown && !isAtTop) { + navbarVisibilityNotifier.show(); + } + } + } + + // Update previous offset for next comparison + _previousOffset = currentOffset; + } + + @override + Widget build(BuildContext context) { + return widget.child; + } +} From ebb7ca95ce24e19641b3c300cef00878f85e34f2 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sat, 19 Jul 2025 15:47:44 +0200 Subject: [PATCH 068/473] fix: navbar interaction --- lib/navigation/ui/drawer_template.dart | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/navigation/ui/drawer_template.dart b/lib/navigation/ui/drawer_template.dart index 1300f48d4b..b72fe74040 100644 --- a/lib/navigation/ui/drawer_template.dart +++ b/lib/navigation/ui/drawer_template.dart @@ -75,13 +75,13 @@ class DrawerTemplate extends HookConsumerWidget { child: AnimatedOpacity( opacity: navbarVisible ? 1.0 : 0.0, duration: const Duration(milliseconds: 300), - child: Visibility( - visible: - animation!.isCompleted && - animation.value == 1.0, - child: AnimatedBuilder( - animation: animation, - builder: (context, child) => Opacity( + child: AnimatedBuilder( + animation: animation!, + builder: (context, child) => Visibility( + visible: + animation.isCompleted && + animation.value == 1.0, + child: Opacity( opacity: animation.value, child: FloatingNavbar( items: [ From 4bed027dc9b71796292febd660988939cd1ad313 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sat, 19 Jul 2025 15:53:14 +0200 Subject: [PATCH 069/473] feat: comment cleanup --- .../providers/navbar_visibility_provider.dart | 25 ---------------- lib/navigation/ui/scroll_to_hide_navbar.dart | 29 +++---------------- 2 files changed, 4 insertions(+), 50 deletions(-) diff --git a/lib/navigation/providers/navbar_visibility_provider.dart b/lib/navigation/providers/navbar_visibility_provider.dart index 45efc744f1..f19e44776f 100644 --- a/lib/navigation/providers/navbar_visibility_provider.dart +++ b/lib/navigation/providers/navbar_visibility_provider.dart @@ -1,38 +1,28 @@ import 'dart:async'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -// Class to manage the navbar visibility state class NavbarVisibilityNotifier extends StateNotifier { NavbarVisibilityNotifier() : super(true); - // Debounce timer to prevent rapid show/hide transitions Timer? _debounceTimer; Timer? _hideTimer; - // Track the last requested state to prevent unnecessary updates bool _lastRequestedState = true; - // Update state with debounce to avoid flickering void _updateState(bool visible) { - // Track the requested state _lastRequestedState = visible; - // Cancel any pending timers _debounceTimer?.cancel(); _hideTimer?.cancel(); - // Only update if the state is actually changing if (state != visible) { - // Faster response for showing (better UX), shorter delay for hiding if (visible) { - // Show immediately for better responsiveness when scrolling up _debounceTimer = Timer(const Duration(milliseconds: 50), () { if (_lastRequestedState == true) { state = true; } }); } else { - // Shorter delay for hiding to make it more responsive to slow scrolling _hideTimer = Timer(const Duration(milliseconds: 100), () { if (_lastRequestedState == false) { state = false; @@ -54,38 +44,29 @@ class NavbarVisibilityNotifier extends StateNotifier { } } -// Provider for navbar visibility final navbarVisibilityProvider = StateNotifierProvider((ref) { return NavbarVisibilityNotifier(); }); -// Class to manage scroll direction detection class ScrollDirectionNotifier extends StateNotifier { ScrollDirectionNotifier() : super(ScrollDirection.idle); double _lastScrollOffset = 0; DateTime _lastDirectionChange = DateTime.now(); - // Minimum threshold for scrolling to be considered significant final double _scrollThreshold = 5.0; - // Minimum time between direction changes to prevent rapid toggling final int _directionChangeThresholdMs = 50; void updateScrollDirection(double scrollOffset) { - // Calculate scroll delta final double scrollDelta = (scrollOffset - _lastScrollOffset).abs(); - // Get current time to check rate of direction changes final now = DateTime.now(); final timeSinceLastChange = now .difference(_lastDirectionChange) .inMilliseconds; - // Handle both normal scrolling and slow scrolling: - // 1. Normal: Significant delta + time threshold - // 2. Slow: Lower delta threshold but longer time between updates bool isSignificantScrolling = scrollDelta > _scrollThreshold && timeSinceLastChange > _directionChangeThresholdMs; @@ -96,19 +77,14 @@ class ScrollDirectionNotifier extends StateNotifier { final previousState = state; if (scrollOffset > _lastScrollOffset) { - // Scrolling down state = ScrollDirection.down; } else if (scrollOffset < _lastScrollOffset) { - // Scrolling up state = ScrollDirection.up; } - // If direction changed, update the timestamp if (previousState != state) { _lastDirectionChange = now; } else { - // Even if direction didn't change, update timestamp for slow scrolling - // to avoid too frequent updates _lastDirectionChange = now; } @@ -124,7 +100,6 @@ class ScrollDirectionNotifier extends StateNotifier { enum ScrollDirection { up, down, idle } -// Provider for scroll direction final scrollDirectionProvider = StateNotifierProvider((ref) { return ScrollDirectionNotifier(); diff --git a/lib/navigation/ui/scroll_to_hide_navbar.dart b/lib/navigation/ui/scroll_to_hide_navbar.dart index 90ca142ac2..1194889d04 100644 --- a/lib/navigation/ui/scroll_to_hide_navbar.dart +++ b/lib/navigation/ui/scroll_to_hide_navbar.dart @@ -33,9 +33,8 @@ class _ScrollToHideNavbarState extends ConsumerState { double _previousOffset = 0; bool _isScrollingDown = false; - final _scrollThreshold = 2.0; // Lower threshold to detect slower scrolling - final _directionChangeThreshold = - 3.0; // Threshold specifically for direction changes + final _scrollThreshold = 2.0; + final _directionChangeThreshold = 3.0; bool _isOverscrollInProgress = false; DateTime _lastDirectionChange = DateTime.now(); DateTime _lastSlowScrollCheck = DateTime.now(); @@ -46,14 +45,12 @@ class _ScrollToHideNavbarState extends ConsumerState { ); final scrollDirectionNotifier = ref.read(scrollDirectionProvider.notifier); - // Get current scroll metrics final ScrollPosition position = widget.controller.position; final double currentOffset = position.pixels; final double maxScrollExtent = position.maxScrollExtent; final bool isAtTop = currentOffset <= 0; final bool isAtBottom = currentOffset >= maxScrollExtent; - // Always show navbar if at the top if (isAtTop) { navbarVisibilityNotifier.show(); _previousOffset = 0; @@ -61,18 +58,14 @@ class _ScrollToHideNavbarState extends ConsumerState { return; } - // Calculate scroll difference final double scrollDelta = (currentOffset - _previousOffset).abs(); - // Check if currently in overscroll state (for BouncingScrollPhysics) bool isOverScrolling = position.outOfRange; - // Mark the beginning of an overscroll if (isOverScrolling && !_isOverscrollInProgress) { _isOverscrollInProgress = true; } - // Reset overscroll state when scrolling away from edges if (!isOverScrolling && _isOverscrollInProgress && !isAtTop && @@ -80,13 +73,11 @@ class _ScrollToHideNavbarState extends ConsumerState { _isOverscrollInProgress = false; } - // Ignore small movements (helps with bouncing physics) if (scrollDelta < _scrollThreshold) { _previousOffset = currentOffset; return; } - // Handle scroll direction and visibility updates if not overscrolling if (!_isOverscrollInProgress) { bool newIsScrollingDown = currentOffset > _previousOffset; final currentTime = DateTime.now(); @@ -97,43 +88,31 @@ class _ScrollToHideNavbarState extends ConsumerState { .difference(_lastSlowScrollCheck) .inMilliseconds; - // Process direction changes with minimum time threshold if (_isScrollingDown != newIsScrollingDown && scrollDelta >= _directionChangeThreshold && timeSinceLastChange > 100) { _isScrollingDown = newIsScrollingDown; _lastDirectionChange = currentTime; - // Update direction notifier scrollDirectionNotifier.updateScrollDirection(currentOffset); - // Update navbar visibility based on direction change if (newIsScrollingDown) { - // Scrolling down - hide navbar navbarVisibilityNotifier.hide(); } else { - // Scrolling up - show navbar navbarVisibilityNotifier.show(); } - } - // Handle continuous scrolling in the same direction - else if (scrollDelta >= _scrollThreshold && + } else if (scrollDelta >= _scrollThreshold && timeSinceLastSlowCheck > 150) { - // Update timestamp for slow scroll check _lastSlowScrollCheck = currentTime; - // Handle continuous downward scrolling - ensure navbar hides if (newIsScrollingDown) { navbarVisibilityNotifier.hide(); - } - // For continuous upward scrolling, ensure navbar shows - else if (!newIsScrollingDown && !isAtTop) { + } else if (!newIsScrollingDown && !isAtTop) { navbarVisibilityNotifier.show(); } } } - // Update previous offset for next comparison _previousOffset = currentOffset; } From 9ff288dd2d754057269eea5586d62de83f503dc4 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Thu, 24 Jul 2025 21:49:43 +0200 Subject: [PATCH 070/473] feat: adding reappearing after delay --- .../providers/navbar_visibility_provider.dart | 50 +++- lib/tools/ui/styleguide/navbar.dart | 278 +++++++++--------- 2 files changed, 194 insertions(+), 134 deletions(-) diff --git a/lib/navigation/providers/navbar_visibility_provider.dart b/lib/navigation/providers/navbar_visibility_provider.dart index f19e44776f..db54d8539c 100644 --- a/lib/navigation/providers/navbar_visibility_provider.dart +++ b/lib/navigation/providers/navbar_visibility_provider.dart @@ -6,40 +6,86 @@ class NavbarVisibilityNotifier extends StateNotifier { Timer? _debounceTimer; Timer? _hideTimer; + Timer? _autoShowTimer; bool _lastRequestedState = true; + static const Duration _autoShowDelay = Duration(milliseconds: 800); + static const Duration _debounceDelay = Duration(milliseconds: 50); + static const Duration _hideDelay = Duration(milliseconds: 100); + void _updateState(bool visible) { _lastRequestedState = visible; _debounceTimer?.cancel(); _hideTimer?.cancel(); + if (visible) { + _autoShowTimer?.cancel(); + } + if (state != visible) { if (visible) { - _debounceTimer = Timer(const Duration(milliseconds: 50), () { + _debounceTimer = Timer(_debounceDelay, () { if (_lastRequestedState == true) { state = true; } }); } else { - _hideTimer = Timer(const Duration(milliseconds: 100), () { + _hideTimer = Timer(_hideDelay, () { if (_lastRequestedState == false) { state = false; + + _startAutoShowTimer(); } }); } } } + void _startAutoShowTimer() { + _autoShowTimer?.cancel(); + _autoShowTimer = Timer(_autoShowDelay, () { + if (!state) { + _lastRequestedState = true; + state = true; + } + }); + } + void show() => _updateState(true); + void hide() => _updateState(false); + void toggle() => _updateState(!state); + void forceShow() { + _debounceTimer?.cancel(); + _hideTimer?.cancel(); + _autoShowTimer?.cancel(); + _lastRequestedState = true; + state = true; + } + + void hideWithoutAutoShow() { + _debounceTimer?.cancel(); + _hideTimer?.cancel(); + _autoShowTimer?.cancel(); + _lastRequestedState = false; + state = false; + } + + void showTemporarily() { + if (!state) { + forceShow(); + } + } + @override void dispose() { _debounceTimer?.cancel(); _hideTimer?.cancel(); + _autoShowTimer?.cancel(); super.dispose(); } } diff --git a/lib/tools/ui/styleguide/navbar.dart b/lib/tools/ui/styleguide/navbar.dart index d95bc18ee8..f50e8ef01f 100644 --- a/lib/tools/ui/styleguide/navbar.dart +++ b/lib/tools/ui/styleguide/navbar.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/navigation/class/module.dart'; +import 'package:titan/navigation/providers/navbar_visibility_provider.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/providers/path_forwarding_provider.dart'; @@ -19,12 +20,22 @@ class FloatingNavbar extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final pathProvider = ref.watch(pathForwardingProvider); + final navbarVisibilityNotifier = ref.read( + navbarVisibilityProvider.notifier, + ); final previousIndex = useRef(0); final currentState = useState(3); final currentPath = pathProvider.path; final routeIndex = useState(3); + final lastInteractionTime = useRef(DateTime.now()); + + void onUserInteraction() { + lastInteractionTime.value = DateTime.now(); + navbarVisibilityNotifier.showTemporarily(); + } + useEffect(() { if (currentPath.isNotEmpty) { String currentPathRoot = "/"; @@ -91,150 +102,153 @@ class FloatingNavbar extends HookConsumerWidget { return null; }, [currentState.value, itemWidthRef.value]); - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), - child: Material( - elevation: 10, - shadowColor: ColorConstants.main.withValues(alpha: 0.2), - borderRadius: BorderRadius.circular(borderRadius), - color: ColorConstants.main, - child: Container( - height: borderRadius * 2, - padding: const EdgeInsets.symmetric(horizontal: 5), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(borderRadius), - ), - child: LayoutBuilder( - builder: (context, constraints) { - final availableWidth = constraints.maxWidth; - final itemWidth = availableWidth / items.length; - - itemWidthRef.value = itemWidth; - - return Stack( - children: [ - AnimatedBuilder( - animation: animationController, - builder: (context, _) { - final leftPosition = slideAnimation.value != null - ? slideAnimation.value!.value - : itemWidth * currentState.value; - - return Positioned( - left: leftPosition, - top: 4, - bottom: 4, - width: itemWidth, - child: Container( - decoration: BoxDecoration( - color: ColorConstants.background, - borderRadius: BorderRadius.circular(borderRadius), + return Listener( + onPointerDown: (_) => onUserInteraction(), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), + child: Material( + elevation: 10, + shadowColor: ColorConstants.main.withValues(alpha: 0.2), + borderRadius: BorderRadius.circular(borderRadius), + color: ColorConstants.main, + child: Container( + height: borderRadius * 2, + padding: const EdgeInsets.symmetric(horizontal: 5), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(borderRadius), + ), + child: LayoutBuilder( + builder: (context, constraints) { + final availableWidth = constraints.maxWidth; + final itemWidth = availableWidth / items.length; + + itemWidthRef.value = itemWidth; + + return Stack( + children: [ + AnimatedBuilder( + animation: animationController, + builder: (context, _) { + final leftPosition = slideAnimation.value != null + ? slideAnimation.value!.value + : itemWidth * currentState.value; + + return Positioned( + left: leftPosition, + top: 4, + bottom: 4, + width: itemWidth, + child: Container( + decoration: BoxDecoration( + color: ColorConstants.background, + borderRadius: BorderRadius.circular(borderRadius), + ), ), - ), - ); - }, - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: items.asMap().entries.map((entry) { - final index = entry.key; - final item = entry.value; - final isSelected = index == currentState.value; - - return Expanded( - child: Material( - color: Colors.transparent, - borderRadius: BorderRadius.circular(borderRadius), - child: GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () { - if (index != currentState.value) { - if (animationController.isAnimating) { - animationController.stop(); - } + ); + }, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: items.asMap().entries.map((entry) { + final index = entry.key; + final item = entry.value; + final isSelected = index == currentState.value; - previousIndex.value = currentState.value; - currentState.value = index; + return Expanded( + child: Material( + color: Colors.transparent, + borderRadius: BorderRadius.circular(borderRadius), + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + if (index != currentState.value) { + if (animationController.isAnimating) { + animationController.stop(); + } + + previousIndex.value = currentState.value; + currentState.value = index; - WidgetsBinding.instance.addPostFrameCallback(( - _, - ) { + WidgetsBinding.instance.addPostFrameCallback(( + _, + ) { + item.onTap?.call(); + }); + } else { item.onTap?.call(); - }); - } else { - item.onTap?.call(); - } - }, - child: Container( - padding: const EdgeInsets.all(8), - child: AnimatedBuilder( - animation: animationController, - builder: (context, child) { - Color textColor; - FontWeight textWeight; - - if (previousIndex.value == - currentState.value) { - textColor = isSelected - ? ColorConstants.main - : ColorConstants.background; - textWeight = isSelected - ? FontWeight.w600 - : FontWeight.normal; - } else { - bool isInvolved = - index == previousIndex.value || - index == currentState.value; - - if (!isInvolved) { - textColor = ColorConstants.background; - textWeight = FontWeight.normal; - } else if (index == currentState.value) { - final progress = - animationController.value; - textColor = Color.lerp( - ColorConstants.background, - ColorConstants.main, - progress, - )!; - textWeight = progress < 0.5 - ? FontWeight.normal - : FontWeight.w600; - } else { - final progress = - animationController.value; - textColor = Color.lerp( - ColorConstants.main, - ColorConstants.background, - progress, - )!; - textWeight = progress < 0.5 + } + }, + child: Container( + padding: const EdgeInsets.all(8), + child: AnimatedBuilder( + animation: animationController, + builder: (context, child) { + Color textColor; + FontWeight textWeight; + + if (previousIndex.value == + currentState.value) { + textColor = isSelected + ? ColorConstants.main + : ColorConstants.background; + textWeight = isSelected ? FontWeight.w600 : FontWeight.normal; + } else { + bool isInvolved = + index == previousIndex.value || + index == currentState.value; + + if (!isInvolved) { + textColor = ColorConstants.background; + textWeight = FontWeight.normal; + } else if (index == currentState.value) { + final progress = + animationController.value; + textColor = Color.lerp( + ColorConstants.background, + ColorConstants.main, + progress, + )!; + textWeight = progress < 0.5 + ? FontWeight.normal + : FontWeight.w600; + } else { + final progress = + animationController.value; + textColor = Color.lerp( + ColorConstants.main, + ColorConstants.background, + progress, + )!; + textWeight = progress < 0.5 + ? FontWeight.w600 + : FontWeight.normal; + } } - } - return Center( - child: AutoSizeText( - item.module.getName(context), - style: TextStyle( - color: textColor, - fontSize: 14, - fontWeight: textWeight, + return Center( + child: AutoSizeText( + item.module.getName(context), + style: TextStyle( + color: textColor, + fontSize: 14, + fontWeight: textWeight, + ), ), - ), - ); - }, + ); + }, + ), ), ), ), - ), - ); - }).toList(), - ), - ], - ); - }, + ); + }).toList(), + ), + ], + ); + }, + ), ), ), ), From 42af30563addde61e170287533268d9e1bba9de8 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 26 Jul 2025 15:13:51 +0200 Subject: [PATCH 071/473] Fix overflow --- lib/feed/class/feed_item.dart | 10 ++++++++++ lib/feed/ui/pages/main_page/main_page.dart | 3 +-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/feed/class/feed_item.dart b/lib/feed/class/feed_item.dart index 9be1c12e33..46e08bec06 100644 --- a/lib/feed/class/feed_item.dart +++ b/lib/feed/class/feed_item.dart @@ -57,6 +57,16 @@ class FeedItem { participantsCount: 33, onRegister: () {}, ), + FeedItem( + type: FeedItemType.action, + title: 'Campagne', + subtitle: 'Jusqu\'à minuit', + date: DateTime(2025, 11, 8), + isOngoing: true, + needsRegistration: true, + participantsCount: 33, + onRegister: () {}, + ), ]; } } diff --git a/lib/feed/ui/pages/main_page/main_page.dart b/lib/feed/ui/pages/main_page/main_page.dart index ec3fbd06a6..ac2702a6bb 100644 --- a/lib/feed/ui/pages/main_page/main_page.dart +++ b/lib/feed/ui/pages/main_page/main_page.dart @@ -108,8 +108,7 @@ class FeedMainPage extends HookConsumerWidget { const SizedBox(height: 20), - SizedBox( - height: MediaQuery.of(context).size.height - 193, + Expanded( child: ScrollToHideNavbar( controller: scrollController, child: SingleChildScrollView( From a24ebbc23de5bca8e1957df8cc1e24a04fc7b16b Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 26 Jul 2025 15:27:37 +0200 Subject: [PATCH 072/473] remove item --- lib/feed/class/feed_item.dart | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/lib/feed/class/feed_item.dart b/lib/feed/class/feed_item.dart index 46e08bec06..9be1c12e33 100644 --- a/lib/feed/class/feed_item.dart +++ b/lib/feed/class/feed_item.dart @@ -57,16 +57,6 @@ class FeedItem { participantsCount: 33, onRegister: () {}, ), - FeedItem( - type: FeedItemType.action, - title: 'Campagne', - subtitle: 'Jusqu\'à minuit', - date: DateTime(2025, 11, 8), - isOngoing: true, - needsRegistration: true, - participantsCount: 33, - onRegister: () {}, - ), ]; } } From f0939a9b818744cf451fc653ac69365c9145d709 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Thu, 24 Jul 2025 19:35:01 +0200 Subject: [PATCH 073/473] feat: reinstate top bar # Conflicts: # lib/navigation/ui/all_module_page.dart --- lib/admin/ui/admin.dart | 19 +++- lib/advert/ui/pages/advert.dart | 24 ++++- lib/amap/ui/amap.dart | 28 ++++- lib/booking/ui/booking.dart | 17 ++- lib/centralisation/ui/centralisation.dart | 17 ++- lib/cinema/ui/cinema.dart | 15 ++- lib/event/ui/event.dart | 17 ++- lib/feed/ui/feed.dart | 5 +- lib/feed/ui/pages/admin_page/admin_page.dart | 92 ++++++++-------- lib/flappybird/ui/flappybird_template.dart | 47 +++++++- lib/home/ui/home.dart | 102 ++++++++++-------- lib/loan/ui/loan.dart | 29 ++++- lib/main.dart | 10 +- .../providers/animation_provider.dart | 37 +++++++ lib/navigation/tools/constants.dart | 10 -- lib/navigation/ui/all_module_page.dart | 3 +- ...template.dart => navigation_template.dart} | 4 +- lib/navigation/ui/top_bar.dart | 24 ----- lib/paiement/ui/paiement.dart | 17 ++- lib/ph/ui/pages/ph.dart | 17 ++- lib/phonebook/ui/phonebook.dart | 34 +++++- lib/purchases/ui/purchases.dart | 17 ++- lib/raffle/ui/raffle.dart | 17 ++- .../ui/widgets/recommendation_template.dart | 17 ++- lib/router.dart | 10 -- lib/seed-library/ui/seed_library.dart | 30 +++++- lib/settings/ui/settings.dart | 15 ++- lib/tools/constants.dart | 1 - lib/tools/ui/layouts/app_template.dart | 4 +- .../ui/styleguide/list_item_template.dart | 6 +- lib/tools/ui/styleguide/searchbar.dart | 20 ++-- lib/tools/ui/styleguide/styleguide_page.dart | 3 + lib/tools/ui/widgets/top_bar.dart | 70 ++++++++++++ lib/vote/ui/vote.dart | 17 ++- 34 files changed, 610 insertions(+), 185 deletions(-) create mode 100644 lib/navigation/providers/animation_provider.dart delete mode 100644 lib/navigation/tools/constants.dart rename lib/navigation/ui/{drawer_template.dart => navigation_template.dart} (98%) delete mode 100644 lib/navigation/ui/top_bar.dart create mode 100644 lib/tools/ui/widgets/top_bar.dart diff --git a/lib/admin/ui/admin.dart b/lib/admin/ui/admin.dart index 9664343482..1cd11404a8 100644 --- a/lib/admin/ui/admin.dart +++ b/lib/admin/ui/admin.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/admin/router.dart'; +import 'package:titan/tools/ui/widgets/top_bar.dart'; import 'package:titan/tools/constants.dart'; class AdminTemplate extends HookConsumerWidget { @@ -8,6 +10,21 @@ class AdminTemplate extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - return Container(color: ColorConstants.background, child: child); + return Scaffold( + body: Container( + decoration: const BoxDecoration(color: ColorConstants.background), + child: SafeArea( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + TopBar( + root: AdminRouter.root, + ), + Expanded(child: child), + ], + ), + ), + ), + ); } } diff --git a/lib/advert/ui/pages/advert.dart b/lib/advert/ui/pages/advert.dart index 66d49274e5..bf862afe1b 100644 --- a/lib/advert/ui/pages/advert.dart +++ b/lib/advert/ui/pages/advert.dart @@ -1,6 +1,9 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/tools/constants.dart'; +import 'package:titan/advert/providers/announcer_provider.dart'; +import 'package:titan/advert/router.dart'; +import 'package:titan/tools/ui/widgets/top_bar.dart'; class AdvertTemplate extends HookConsumerWidget { final Widget child; @@ -8,6 +11,25 @@ class AdvertTemplate extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - return Container(color: ColorConstants.background, child: child); + final selectedAnnouncersNotifier = ref.read(announcerProvider.notifier); + return Scaffold( + body: Container( + color: ColorConstants.background, + child: SafeArea( + child: Column( + children: [ + TopBar( + root: AdvertRouter.root, + onBack: () { + selectedAnnouncersNotifier.clearAnnouncer(); + }, + ), + const SizedBox(height: 30), + Expanded(child: child), + ], + ), + ), + ), + ); } } diff --git a/lib/amap/ui/amap.dart b/lib/amap/ui/amap.dart index 055992c587..4c4023a180 100644 --- a/lib/amap/ui/amap.dart +++ b/lib/amap/ui/amap.dart @@ -1,5 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:heroicons/heroicons.dart'; +import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/amap/router.dart'; import 'package:titan/tools/constants.dart'; +import 'package:titan/tools/ui/widgets/top_bar.dart'; class AmapTemplate extends StatelessWidget { final Widget child; @@ -7,6 +11,28 @@ class AmapTemplate extends StatelessWidget { @override Widget build(BuildContext context) { - return Container(color: ColorConstants.background, child: child); + return Container( + color: ColorConstants.background, + child: Column( + children: [ + TopBar( + root: AmapRouter.root, + rightIcon: QR.currentPath == AmapRouter.root + ? IconButton( + onPressed: () { + QR.to(AmapRouter.root + AmapRouter.presentation); + }, + icon: const HeroIcon( + HeroIcons.informationCircle, + color: Colors.black, + size: 40, + ), + ) + : null, + ), + Expanded(child: child), + ], + ), + ); } } diff --git a/lib/booking/ui/booking.dart b/lib/booking/ui/booking.dart index 86fde62150..fe9a136d95 100644 --- a/lib/booking/ui/booking.dart +++ b/lib/booking/ui/booking.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:titan/booking/router.dart'; +import 'package:titan/tools/ui/widgets/top_bar.dart'; import 'package:titan/tools/constants.dart'; class BookingTemplate extends StatelessWidget { @@ -7,6 +9,19 @@ class BookingTemplate extends StatelessWidget { @override Widget build(BuildContext context) { - return Container(color: ColorConstants.background, child: child); + return Scaffold( + body: Container( + decoration: const BoxDecoration(color: ColorConstants.background), + child: SafeArea( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + const TopBar(root: BookingRouter.root), + Expanded(child: child), + ], + ), + ), + ), + ); } } diff --git a/lib/centralisation/ui/centralisation.dart b/lib/centralisation/ui/centralisation.dart index f7f2eaa9d7..21faa3339d 100644 --- a/lib/centralisation/ui/centralisation.dart +++ b/lib/centralisation/ui/centralisation.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:titan/centralisation/router.dart'; +import 'package:titan/tools/ui/widgets/top_bar.dart'; import 'package:titan/tools/constants.dart'; class CentralisationTemplate extends StatelessWidget { @@ -7,6 +9,19 @@ class CentralisationTemplate extends StatelessWidget { @override Widget build(BuildContext context) { - return Container(color: ColorConstants.background, child: child); + return Scaffold( + body: Container( + color: ColorConstants.background, + child: SafeArea( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + const TopBar(root: CentralisationRouter.root), + Expanded(child: child), + ], + ), + ), + ), + ); } } diff --git a/lib/cinema/ui/cinema.dart b/lib/cinema/ui/cinema.dart index dc05e97a45..44c6e5ad9d 100644 --- a/lib/cinema/ui/cinema.dart +++ b/lib/cinema/ui/cinema.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/cinema/router.dart'; import 'package:titan/tools/constants.dart'; +import 'package:titan/tools/ui/widgets/top_bar.dart'; class CinemaTemplate extends HookConsumerWidget { final Widget child; @@ -8,6 +10,17 @@ class CinemaTemplate extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - return Container(color: ColorConstants.background, child: child); + return Container( + color: ColorConstants.background, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + TopBar( + root: CinemaRouter.root, + ), + Expanded(child: child), + ], + ), + ); } } diff --git a/lib/event/ui/event.dart b/lib/event/ui/event.dart index 44a8c11462..b8448f5ed8 100644 --- a/lib/event/ui/event.dart +++ b/lib/event/ui/event.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:titan/event/router.dart'; +import 'package:titan/tools/ui/widgets/top_bar.dart'; import 'package:titan/tools/constants.dart'; class EventTemplate extends StatelessWidget { @@ -7,6 +9,19 @@ class EventTemplate extends StatelessWidget { @override Widget build(BuildContext context) { - return Container(color: ColorConstants.background, child: child); + return Scaffold( + body: Container( + decoration: const BoxDecoration(color: ColorConstants.background), + child: SafeArea( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + const TopBar(root: EventRouter.root), + Expanded(child: child), + ], + ), + ), + ), + ); } } diff --git a/lib/feed/ui/feed.dart b/lib/feed/ui/feed.dart index 13bc174bd7..9447e836b0 100644 --- a/lib/feed/ui/feed.dart +++ b/lib/feed/ui/feed.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:titan/navigation/ui/top_bar.dart'; +import 'package:titan/feed/router.dart'; import 'package:titan/tools/constants.dart'; +import 'package:titan/tools/ui/widgets/top_bar.dart'; class FeedTemplate extends StatelessWidget { final Widget child; @@ -13,7 +14,7 @@ class FeedTemplate extends StatelessWidget { child: Column( mainAxisAlignment: MainAxisAlignment.start, children: [ - TopBar(), + TopBar(root: FeedRouter.root), Expanded(child: child), ], ), diff --git a/lib/feed/ui/pages/admin_page/admin_page.dart b/lib/feed/ui/pages/admin_page/admin_page.dart index 436291efe8..09908229cb 100644 --- a/lib/feed/ui/pages/admin_page/admin_page.dart +++ b/lib/feed/ui/pages/admin_page/admin_page.dart @@ -45,60 +45,58 @@ class AdminPage extends HookConsumerWidget { }, [selectedTabIndex.value]); return FeedTemplate( - child: SafeArea( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - // Tab Bar - TabNavigation( - selectedTabIndex: selectedTabIndex.value, - onTabChanged: (index) { + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // Tab Bar + TabNavigation( + selectedTabIndex: selectedTabIndex.value, + onTabChanged: (index) { + previousTabIndex.value = selectedTabIndex.value; + selectedTabIndex.value = index; + }, + tabLabels: const ['Post', 'Événement'], + ), + + const SizedBox(height: 30), + + // PageView for tab content with actual PageView widget + Expanded( + child: PageView( + controller: pageController, + physics: const BouncingScrollPhysics(), + onPageChanged: (index) { previousTabIndex.value = selectedTabIndex.value; selectedTabIndex.value = index; }, - tabLabels: const ['Post', 'Événement'], - ), - - const SizedBox(height: 30), - - // PageView for tab content with actual PageView widget - Expanded( - child: PageView( - controller: pageController, - physics: const BouncingScrollPhysics(), - onPageChanged: (index) { - previousTabIndex.value = selectedTabIndex.value; - selectedTabIndex.value = index; - }, - children: [ - // Post form - SingleChildScrollView( - physics: const BouncingScrollPhysics(), - child: PostForm( - titleController: postTitleController, - descriptionController: postDescriptionController, - startDateController: postStartDateController, - ), + children: [ + // Post form + SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: PostForm( + titleController: postTitleController, + descriptionController: postDescriptionController, + startDateController: postStartDateController, ), + ), - // Event form - SingleChildScrollView( - physics: const BouncingScrollPhysics(), - child: EventForm( - titleController: eventTitleController, - descriptionController: eventDescriptionController, - startDateController: eventStartDateController, - endDateController: eventEndDateController, - locationController: eventLocationController, - shotgunDateController: shotgunDateController, - externalLinkController: eventExternalLinkController, - ), + // Event form + SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: EventForm( + titleController: eventTitleController, + descriptionController: eventDescriptionController, + startDateController: eventStartDateController, + endDateController: eventEndDateController, + locationController: eventLocationController, + shotgunDateController: shotgunDateController, + externalLinkController: eventExternalLinkController, ), - ], - ), + ), + ], ), - ], - ), + ), + ], ), ); } diff --git a/lib/flappybird/ui/flappybird_template.dart b/lib/flappybird/ui/flappybird_template.dart index f1f7fcffbd..50fb4e13d7 100644 --- a/lib/flappybird/ui/flappybird_template.dart +++ b/lib/flappybird/ui/flappybird_template.dart @@ -1,6 +1,12 @@ import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/tools/constants.dart'; +import 'package:titan/flappybird/providers/score_list_provider.dart'; +import 'package:titan/flappybird/providers/user_score_provider.dart'; +import 'package:titan/flappybird/router.dart'; +import 'package:titan/tools/ui/widgets/top_bar.dart'; +import 'package:qlevar_router/qlevar_router.dart'; class FlappyBirdTemplate extends HookConsumerWidget { final Widget child; @@ -8,6 +14,43 @@ class FlappyBirdTemplate extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - return Container(color: ColorConstants.background, child: child); + final leaderBoardNotifier = ref.watch(scoreListProvider.notifier); + final bestUserScoreNotifier = ref.watch(userScoreProvider.notifier); + return Container( + color: Colors.blue, + child: SafeArea( + child: Column( + children: [ + TopBar( + root: FlappyBirdRouter.root, + textStyle: GoogleFonts.silkscreen( + textStyle: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + rightIcon: QR.currentPath == FlappyBirdRouter.root + ? IconButton( + onPressed: () { + leaderBoardNotifier.getLeaderboard(); + bestUserScoreNotifier.getLeaderBoardPosition(); + QR.to( + FlappyBirdRouter.root + FlappyBirdRouter.leaderBoard, + ); + }, + icon: const HeroIcon( + HeroIcons.trophy, + color: Colors.white, + size: 40, + ), + ) + : null, + ), + Expanded(child: child), + ], + ), + ), + ); } } diff --git a/lib/home/ui/home.dart b/lib/home/ui/home.dart index f740b1b76b..7ab09de0a5 100644 --- a/lib/home/ui/home.dart +++ b/lib/home/ui/home.dart @@ -1,19 +1,26 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/event/providers/confirmed_event_list_provider.dart'; import 'package:titan/event/providers/sorted_event_list_provider.dart'; +import 'package:titan/home/router.dart'; import 'package:titan/home/ui/day_list.dart'; import 'package:titan/home/ui/days_event.dart'; import 'package:titan/home/ui/month_bar.dart'; import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/widgets/align_left_text.dart'; +import 'package:titan/tools/ui/layouts/refresher.dart'; +import 'package:titan/tools/ui/widgets/top_bar.dart'; class HomePage extends HookConsumerWidget { const HomePage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { + final confirmedEventListNotifier = ref.watch( + confirmedEventListProvider.notifier, + ); final sortedEventList = ref.watch(sortedEventListProvider); DateTime now = DateTime.now(); final ScrollController scrollController = useScrollController(); @@ -21,52 +28,61 @@ class HomePage extends HookConsumerWidget { return Container( color: ColorConstants.background, - child: Column( - children: [ - const SizedBox(height: 20), - MonthBar( - scrollController: scrollController, - width: MediaQuery.of(context).size.width, - ), - const SizedBox(height: 10), - DayList(scrollController, daysEventScrollController), - const SizedBox(height: 15), - AlignLeftText( - AppLocalizations.of(context)!.homeIncomingEvents, - padding: EdgeInsets.symmetric(horizontal: 30.0), - fontSize: 25, - ), - const SizedBox(height: 10), - SizedBox( - height: MediaQuery.of(context).size.height - 320, - child: SingleChildScrollView( - physics: const BouncingScrollPhysics(), - controller: daysEventScrollController, - child: sortedEventList.keys.isNotEmpty - ? Column( - children: sortedEventList - .map( - (key, value) => MapEntry( - key, - DaysEvent(day: key, now: now, events: value), + child: SafeArea( + child: Refresher( + onRefresh: () async { + await confirmedEventListNotifier.loadConfirmedEvent(); + now = DateTime.now(); + }, + child: Column( + children: [ + const TopBar(root: HomeRouter.root), + const SizedBox(height: 20), + MonthBar( + scrollController: scrollController, + width: MediaQuery.of(context).size.width, + ), + const SizedBox(height: 10), + DayList(scrollController, daysEventScrollController), + const SizedBox(height: 15), + AlignLeftText( + AppLocalizations.of(context)!.homeIncomingEvents, + padding: EdgeInsets.symmetric(horizontal: 30.0), + fontSize: 25, + ), + const SizedBox(height: 10), + SizedBox( + height: MediaQuery.of(context).size.height - 320, + child: SingleChildScrollView( + physics: const BouncingScrollPhysics(), + controller: daysEventScrollController, + child: sortedEventList.keys.isNotEmpty + ? Column( + children: sortedEventList + .map( + (key, value) => MapEntry( + key, + DaysEvent(day: key, now: now, events: value), + ), + ) + .values + .toList(), + ) + : Center( + child: Text( + AppLocalizations.of(context)!.homeNoEvents, + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.bold, + color: Colors.grey, ), - ) - .values - .toList(), - ) - : Center( - child: Text( - AppLocalizations.of(context)!.homeNoEvents, - style: TextStyle( - fontSize: 15, - fontWeight: FontWeight.bold, - color: Colors.grey, + ), ), - ), - ), - ), + ), + ), + ], ), - ], + ), ), ); } diff --git a/lib/loan/ui/loan.dart b/lib/loan/ui/loan.dart index 5338e613de..f9b70782b6 100644 --- a/lib/loan/ui/loan.dart +++ b/lib/loan/ui/loan.dart @@ -1,6 +1,12 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/loan/providers/item_list_provider.dart'; +import 'package:titan/loan/providers/loaner_provider.dart'; +import 'package:titan/loan/providers/loaners_items_provider.dart'; +import 'package:titan/loan/router.dart'; import 'package:titan/tools/constants.dart'; +import 'package:titan/tools/ui/widgets/top_bar.dart'; class LoanTemplate extends HookConsumerWidget { final Widget child; @@ -8,6 +14,27 @@ class LoanTemplate extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - return Container(color: ColorConstants.background, child: child); + return Container( + color: ColorConstants.background, + child: Column( + children: [ + TopBar( + root: LoanRouter.root, + onBack: () { + if (QR.currentPath == + LoanRouter.root + LoanRouter.admin + LoanRouter.addEditLoan) { + final loanersItemsNotifier = ref.watch( + loanersItemsProvider.notifier, + ); + final loaner = ref.watch(loanerProvider); + final itemList = ref.watch(itemListProvider); + loanersItemsNotifier.setTData(loaner, itemList); + } + }, + ), + Expanded(child: child), + ], + ), + ); } } diff --git a/lib/main.dart b/lib/main.dart index dae22f08b0..f168ae616e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -7,14 +7,12 @@ import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:titan/l10n/app_localizations.dart'; -import 'package:titan/login/providers/animation_provider.dart'; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:google_fonts/google_fonts.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/navigation/providers/navbar_animation.dart'; import 'package:titan/router.dart'; import 'package:titan/service/tools/setup.dart'; -import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/plausible/plausible_observer.dart'; import 'package:titan/tools/providers/path_forwarding_provider.dart'; @@ -49,19 +47,14 @@ class MyApp extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final appRouter = ref.watch(appRouterProvider); - final animationController = useAnimationController( - duration: const Duration(seconds: 2), - ); final navbarAnimationController = useAnimationController( duration: const Duration(milliseconds: 200), initialValue: 1.0, ); - final animationNotifier = ref.read(backgroundAnimationProvider.notifier); final navbarAnimationNotifier = ref.read(navbarAnimationProvider.notifier); final navigatorKey = GlobalKey(); final plausible = getPlausible(); final pathForwardingNotifier = ref.watch(pathForwardingProvider.notifier); - Future(() => animationNotifier.setController(animationController)); Future( () => navbarAnimationNotifier.setController(navbarAnimationController), ); @@ -110,10 +103,9 @@ class MyApp extends HookConsumerWidget { GlobalCupertinoLocalizations.delegate, ], theme: ThemeData( - primarySwatch: Colors.red, + primarySwatch: Colors.orange, textTheme: GoogleFonts.latoTextTheme(Theme.of(context).textTheme), brightness: Brightness.light, - scaffoldBackgroundColor: ColorConstants.background, ), routeInformationParser: const QRouteInformationParser(), builder: (context, child) { diff --git a/lib/navigation/providers/animation_provider.dart b/lib/navigation/providers/animation_provider.dart new file mode 100644 index 0000000000..41f766a28d --- /dev/null +++ b/lib/navigation/providers/animation_provider.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class AnimationNotifier extends StateNotifier { + AnimationNotifier() : super(null); + + void setController(AnimationController controller) { + state = controller; + } + + void toggle() { + if (state == null) { + return; + } + if (state!.isCompleted) { + state!.reverse(); + } else { + state!.forward(); + } + } + + double get value { + if (state == null) { + return 0; + } + return state!.value; + } + + AnimationController? get animation { + return state; + } +} + +final animationProvider = + StateNotifierProvider((ref) { + return AnimationNotifier(); + }); diff --git a/lib/navigation/tools/constants.dart b/lib/navigation/tools/constants.dart deleted file mode 100644 index 43bf740de3..0000000000 --- a/lib/navigation/tools/constants.dart +++ /dev/null @@ -1,10 +0,0 @@ -import 'package:flutter/material.dart'; - -class DrawerColorConstants { - static final Color lightText = Colors.grey.shade100.withValues(alpha: 0.6); - static final Color selectedText = Colors.grey.shade100; - static const Color lightBlue = Color.fromARGB(255, 86, 95, 95); - static const Color darkBlue = Color.fromARGB(255, 62, 62, 62); - static const Color fakePageBlue = Color.fromARGB(24, 161, 161, 161); - static const Color fakePageShadow = Color.fromARGB(14, 161, 161, 161); -} diff --git a/lib/navigation/ui/all_module_page.dart b/lib/navigation/ui/all_module_page.dart index 1623175b9d..503c400492 100644 --- a/lib/navigation/ui/all_module_page.dart +++ b/lib/navigation/ui/all_module_page.dart @@ -10,6 +10,7 @@ import 'package:titan/tools/constants.dart'; import 'package:titan/tools/providers/path_forwarding_provider.dart'; import 'package:titan/tools/ui/styleguide/list_item.dart'; import 'package:titan/tools/ui/styleguide/searchbar.dart'; +import 'package:titan/tools/ui/widgets/top_bar.dart'; class AllModulePage extends HookConsumerWidget { const AllModulePage({super.key}); @@ -25,7 +26,7 @@ class AllModulePage extends HookConsumerWidget { color: ColorConstants.background, child: Column( children: [ - TopBar(), + TopBar(root: AppRouter.allModules), Expanded( child: ScrollToHideNavbar( controller: scrollController, diff --git a/lib/navigation/ui/drawer_template.dart b/lib/navigation/ui/navigation_template.dart similarity index 98% rename from lib/navigation/ui/drawer_template.dart rename to lib/navigation/ui/navigation_template.dart index b72fe74040..cba00b2789 100644 --- a/lib/navigation/ui/drawer_template.dart +++ b/lib/navigation/ui/navigation_template.dart @@ -20,7 +20,7 @@ import 'package:titan/user/providers/user_provider.dart'; // Global navigator key that can be used to ensure bottom sheets appear above the navbar final GlobalKey rootNavigatorKey = GlobalKey(); -class DrawerTemplate extends HookConsumerWidget { +class NavigationTemplate extends HookConsumerWidget { static Duration duration = const Duration(milliseconds: 200); static const double maxSlide = 255; static const dragRightStartVal = 60; @@ -28,7 +28,7 @@ class DrawerTemplate extends HookConsumerWidget { static bool shouldDrag = false; final Widget child; - const DrawerTemplate({super.key, required this.child}); + const NavigationTemplate({super.key, required this.child}); @override Widget build(BuildContext context, WidgetRef ref) { diff --git a/lib/navigation/ui/top_bar.dart b/lib/navigation/ui/top_bar.dart deleted file mode 100644 index 810da3197e..0000000000 --- a/lib/navigation/ui/top_bar.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:flutter/material.dart'; - -class TopBar extends StatelessWidget { - const TopBar({super.key}); - - @override - Widget build(BuildContext context) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - padding: const EdgeInsets.all(15.0), - child: Center( - child: Text( - 'MyEMApp', - style: TextStyle(fontSize: 16, fontWeight: FontWeight.w900), - ), - ), - ), - SizedBox(height: 10), - ], - ); - } -} diff --git a/lib/paiement/ui/paiement.dart b/lib/paiement/ui/paiement.dart index ef7c6772e1..84d6803542 100644 --- a/lib/paiement/ui/paiement.dart +++ b/lib/paiement/ui/paiement.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:titan/paiement/router.dart'; +import 'package:titan/tools/ui/widgets/top_bar.dart'; import 'package:titan/tools/constants.dart'; class PaymentTemplate extends StatelessWidget { @@ -7,6 +9,19 @@ class PaymentTemplate extends StatelessWidget { @override Widget build(BuildContext context) { - return Container(color: ColorConstants.background, child: child); + return Scaffold( + body: Container( + decoration: const BoxDecoration(color: ColorConstants.background), + child: SafeArea( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + const TopBar(root: PaymentRouter.root), + Expanded(child: child), + ], + ), + ), + ), + ); } } diff --git a/lib/ph/ui/pages/ph.dart b/lib/ph/ui/pages/ph.dart index 3ca34f170f..8946f343f3 100644 --- a/lib/ph/ui/pages/ph.dart +++ b/lib/ph/ui/pages/ph.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/ph/router.dart'; +import 'package:titan/tools/ui/widgets/top_bar.dart'; import 'package:titan/tools/constants.dart'; class PhTemplate extends HookConsumerWidget { @@ -8,6 +10,19 @@ class PhTemplate extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - return Container(color: ColorConstants.background, child: child); + return Scaffold( + body: Container( + decoration: const BoxDecoration(color: ColorConstants.background), + child: SafeArea( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + const TopBar(root: PhRouter.root), + Expanded(child: child), + ], + ), + ), + ), + ); } } diff --git a/lib/phonebook/ui/phonebook.dart b/lib/phonebook/ui/phonebook.dart index 7312c930f6..2e96696afd 100644 --- a/lib/phonebook/ui/phonebook.dart +++ b/lib/phonebook/ui/phonebook.dart @@ -1,6 +1,10 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/phonebook/providers/association_kind_provider.dart'; +import 'package:titan/phonebook/router.dart'; import 'package:titan/tools/constants.dart'; +import 'package:titan/tools/ui/widgets/top_bar.dart'; class PhonebookTemplate extends HookConsumerWidget { final Widget child; @@ -8,6 +12,34 @@ class PhonebookTemplate extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - return Container(color: ColorConstants.background, child: child); + final kindNotifier = ref.watch(associationKindProvider.notifier); + return Container( + color: ColorConstants.background, + child: Column( + children: [ + TopBar( + root: PhonebookRouter.root, + onBack: () { + if (QR.currentPath != + PhonebookRouter.root + + PhonebookRouter.admin + + PhonebookRouter.editAssociation + + PhonebookRouter.addEditMember) { + kindNotifier.setKind(''); + } + if (QR.currentPath == + PhonebookRouter.root + + PhonebookRouter.admin + + PhonebookRouter.editAssociation) { + QR.to( + PhonebookRouter.root + PhonebookRouter.admin, + ); // Used on back after adding an association + } + }, + ), + Expanded(child: child), + ], + ), + ); } } diff --git a/lib/purchases/ui/purchases.dart b/lib/purchases/ui/purchases.dart index 545940681b..705ca3e84d 100644 --- a/lib/purchases/ui/purchases.dart +++ b/lib/purchases/ui/purchases.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/purchases/router.dart'; +import 'package:titan/tools/ui/widgets/top_bar.dart'; import 'package:titan/tools/constants.dart'; class PurchasesTemplate extends HookConsumerWidget { @@ -8,6 +10,19 @@ class PurchasesTemplate extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - return Container(color: ColorConstants.background, child: child); + return Scaffold( + body: Container( + decoration: const BoxDecoration(color: ColorConstants.background), + child: SafeArea( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + const TopBar(root: PurchasesRouter.root), + Expanded(child: child), + ], + ), + ), + ), + ); } } diff --git a/lib/raffle/ui/raffle.dart b/lib/raffle/ui/raffle.dart index b9838e0fe6..b6be955160 100644 --- a/lib/raffle/ui/raffle.dart +++ b/lib/raffle/ui/raffle.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/raffle/router.dart'; +import 'package:titan/tools/ui/widgets/top_bar.dart'; import 'package:titan/tools/constants.dart'; class RaffleTemplate extends HookConsumerWidget { @@ -8,6 +10,19 @@ class RaffleTemplate extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - return Container(color: ColorConstants.background, child: child); + return Scaffold( + body: Container( + decoration: const BoxDecoration(color: ColorConstants.background), + child: SafeArea( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + const TopBar(root: RaffleRouter.root), + Expanded(child: child), + ], + ), + ), + ), + ); } } diff --git a/lib/recommendation/ui/widgets/recommendation_template.dart b/lib/recommendation/ui/widgets/recommendation_template.dart index ba2ab7153c..3e22826904 100644 --- a/lib/recommendation/ui/widgets/recommendation_template.dart +++ b/lib/recommendation/ui/widgets/recommendation_template.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:titan/recommendation/router.dart'; +import 'package:titan/tools/ui/widgets/top_bar.dart'; import 'package:titan/tools/constants.dart'; class RecommendationTemplate extends StatelessWidget { @@ -7,6 +9,19 @@ class RecommendationTemplate extends StatelessWidget { @override Widget build(BuildContext context) { - return Container(color: ColorConstants.background, child: child); + return Scaffold( + body: Container( + decoration: const BoxDecoration(color: ColorConstants.background), + child: SafeArea( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + const TopBar(root: RecommendationRouter.root), + Expanded(child: child), + ], + ), + ), + ), + ); } } diff --git a/lib/router.dart b/lib/router.dart index 3195cf7df6..2d86c59176 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -12,8 +12,6 @@ import 'package:titan/home/router.dart'; import 'package:titan/home/ui/home.dart' deferred as home_page; import 'package:titan/loan/router.dart'; import 'package:titan/login/router.dart'; -import 'package:titan/navigation/ui/all_module_page.dart' - deferred as all_module_page; import 'package:titan/others/ui/loading_page.dart' deferred as loading_page; import 'package:titan/others/ui/no_internet_page.dart' deferred as no_internet_page; @@ -75,14 +73,6 @@ class AppRouter { builder: () => no_module_page.NoModulePage(), middleware: [DeferredLoadingMiddleware(no_module_page.loadLibrary)], ), - QRoute( - path: allModules, - builder: () => all_module_page.AllModulePage(), - middleware: [ - AuthenticatedMiddleware(ref), - DeferredLoadingMiddleware(all_module_page.loadLibrary), - ], - ), AdminRouter(ref).route(), AdvertRouter(ref).route(), AmapRouter(ref).route(), diff --git a/lib/seed-library/ui/seed_library.dart b/lib/seed-library/ui/seed_library.dart index 60dc8b102e..8a0d2bb51d 100644 --- a/lib/seed-library/ui/seed_library.dart +++ b/lib/seed-library/ui/seed_library.dart @@ -1,5 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:heroicons/heroicons.dart'; +import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/seed-library/router.dart'; import 'package:titan/tools/constants.dart'; +import 'package:titan/tools/ui/widgets/top_bar.dart'; class SeedLibraryTemplate extends StatelessWidget { final Widget child; @@ -7,6 +11,30 @@ class SeedLibraryTemplate extends StatelessWidget { @override Widget build(BuildContext context) { - return Container(color: ColorConstants.background, child: child); + return Container( + color: ColorConstants.background, + child: Column( + children: [ + TopBar( + root: SeedLibraryRouter.root, + rightIcon: QR.currentPath == SeedLibraryRouter.root + ? IconButton( + onPressed: () { + QR.to( + SeedLibraryRouter.root + SeedLibraryRouter.information, + ); + }, + icon: const HeroIcon( + HeroIcons.informationCircle, + color: Colors.black, + size: 40, + ), + ) + : null, + ), + Expanded(child: child), + ], + ), + ); } } diff --git a/lib/settings/ui/settings.dart b/lib/settings/ui/settings.dart index 8f61e9d218..783c27b631 100644 --- a/lib/settings/ui/settings.dart +++ b/lib/settings/ui/settings.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:titan/settings/router.dart'; +import 'package:titan/tools/ui/widgets/top_bar.dart'; import 'package:titan/tools/constants.dart'; class SettingsTemplate extends StatelessWidget { @@ -7,6 +9,17 @@ class SettingsTemplate extends StatelessWidget { @override Widget build(BuildContext context) { - return Container(color: ColorConstants.background, child: child); + return Container( + color: ColorConstants.background, + child: SafeArea( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + const TopBar(root: SettingsRouter.root), + Expanded(child: child), + ], + ), + ), + ); } } diff --git a/lib/tools/constants.dart b/lib/tools/constants.dart index 39473df2bc..05dc3eae01 100644 --- a/lib/tools/constants.dart +++ b/lib/tools/constants.dart @@ -10,7 +10,6 @@ class ColorConstants { static const Color background = Color(0xFFffffff); static const Color onBackground = Color(0xffb4b4b4); - static const Color searchBar = Color(0xfffafafa); static const Color secondary = Color(0xFFb1b2b5); static const Color tertiary = Color(0xFF424242); static const Color onTertiary = Color(0xFF212121); diff --git a/lib/tools/ui/layouts/app_template.dart b/lib/tools/ui/layouts/app_template.dart index 16936ac66f..d9dfa6094b 100644 --- a/lib/tools/ui/layouts/app_template.dart +++ b/lib/tools/ui/layouts/app_template.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/auth/providers/openid_provider.dart'; -import 'package:titan/navigation/ui/drawer_template.dart'; +import 'package:titan/navigation/ui/navigation_template.dart'; import 'package:titan/version/providers/titan_version_provider.dart'; import 'package:titan/version/providers/version_verifier_provider.dart'; @@ -26,7 +26,7 @@ class AppTemplate extends HookConsumerWidget { if (!isLoggedIn) { return child; } - return DrawerTemplate(child: child); + return NavigationTemplate(child: child); }, orElse: () => child, ); diff --git a/lib/tools/ui/styleguide/list_item_template.dart b/lib/tools/ui/styleguide/list_item_template.dart index 8517f17dfb..3998af8a3f 100644 --- a/lib/tools/ui/styleguide/list_item_template.dart +++ b/lib/tools/ui/styleguide/list_item_template.dart @@ -24,7 +24,7 @@ class ListItemTemplate extends StatelessWidget { onTap: onTap, behavior: HitTestBehavior.opaque, child: Container( - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 4), child: Row( children: [ if (icon != null) ...[icon!, const SizedBox(width: 10)], @@ -36,8 +36,8 @@ class ListItemTemplate extends StatelessWidget { title, style: TextStyle( fontSize: 16, - fontWeight: FontWeight.w900, - color: ColorConstants.onTertiary, + fontWeight: FontWeight.w500, + color: ColorConstants.tertiary, ), ), if (subtitle != null) diff --git a/lib/tools/ui/styleguide/searchbar.dart b/lib/tools/ui/styleguide/searchbar.dart index 87d2517670..0e5a402c9b 100644 --- a/lib/tools/ui/styleguide/searchbar.dart +++ b/lib/tools/ui/styleguide/searchbar.dart @@ -20,18 +20,17 @@ class CustomSearchBar extends HookWidget { Widget build(BuildContext context) { final textController = useTextEditingController(); - return Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(50), - color: ColorConstants.searchBar, - ), + return Material( + elevation: 4, + borderRadius: BorderRadius.circular(50), + color: ColorConstants.background, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 4.0), child: Row( children: [ HeroIcon( HeroIcons.magnifyingGlass, - color: ColorConstants.onBackground, + color: ColorConstants.tertiary, size: 24, ), const SizedBox(width: 8), @@ -42,10 +41,7 @@ class CustomSearchBar extends HookWidget { onChanged: (value) { onSearch(value); }, - style: TextStyle( - color: ColorConstants.onBackground, - fontSize: 16, - ), + style: TextStyle(color: ColorConstants.tertiary, fontSize: 16), decoration: InputDecoration( hintText: hintText, hintStyle: TextStyle( @@ -61,7 +57,7 @@ class CustomSearchBar extends HookWidget { IconButton( icon: HeroIcon( HeroIcons.xMark, - color: ColorConstants.onBackground, + color: ColorConstants.tertiary, size: 20, ), onPressed: () { @@ -73,7 +69,7 @@ class CustomSearchBar extends HookWidget { IconButton( icon: HeroIcon( HeroIcons.adjustmentsHorizontal, - color: ColorConstants.onBackground, + color: ColorConstants.tertiary, size: 20, ), onPressed: onFilter, diff --git a/lib/tools/ui/styleguide/styleguide_page.dart b/lib/tools/ui/styleguide/styleguide_page.dart index e88f94e88b..fb5bddc753 100644 --- a/lib/tools/ui/styleguide/styleguide_page.dart +++ b/lib/tools/ui/styleguide/styleguide_page.dart @@ -13,8 +13,10 @@ import 'package:titan/tools/ui/styleguide/list_item.dart'; import 'package:titan/tools/ui/styleguide/list_item_template.dart'; import 'package:titan/tools/ui/styleguide/list_item_toggle.dart'; import 'package:titan/tools/ui/styleguide/navbar.dart'; +import 'package:titan/tools/ui/styleguide/router.dart'; import 'package:titan/tools/ui/styleguide/searchbar.dart'; import 'package:titan/tools/ui/styleguide/text_entry.dart'; +import 'package:titan/tools/ui/widgets/top_bar.dart'; class StyleGuidePage extends HookConsumerWidget { const StyleGuidePage({super.key}); @@ -45,6 +47,7 @@ class StyleGuidePage extends HookConsumerWidget { return SafeArea( child: Column( children: [ + TopBar(root: StyleGuideRouter.root), Expanded( child: SingleChildScrollView( child: Padding( diff --git a/lib/tools/ui/widgets/top_bar.dart b/lib/tools/ui/widgets/top_bar.dart new file mode 100644 index 0000000000..8c7bc8472f --- /dev/null +++ b/lib/tools/ui/widgets/top_bar.dart @@ -0,0 +1,70 @@ +import 'package:auto_size_text/auto_size_text.dart'; +import 'package:flutter/material.dart'; +import 'package:heroicons/heroicons.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:qlevar_router/qlevar_router.dart'; + +class TopBar extends HookConsumerWidget { + final String root; + final VoidCallback? onBack; + final Widget? rightIcon; + final TextStyle? textStyle; + const TopBar({ + super.key, + required this.root, + this.onBack, + this.rightIcon, + this.textStyle, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Column( + children: [ + const SizedBox(height: 10), + Row( + children: [ + SizedBox( + width: 70, + height: 40, + child: Builder( + builder: (BuildContext appBarContext) { + if (QR.currentPath == root) { + return SizedBox.shrink(); + } + return IconButton( + onPressed: () { + QR.back(); + onBack?.call(); + }, + icon: HeroIcon( + HeroIcons.chevronLeft, + color: textStyle?.color ?? Colors.black, + size: 20, + ), + ); + }, + ), + ), + Expanded( + child: Center( + child: AutoSizeText( + "MyEMApp", + maxLines: 1, + style: + textStyle ?? + const TextStyle( + fontSize: 15, + fontWeight: FontWeight.w700, + color: Colors.black, + ), + ), + ), + ), + SizedBox(width: 70, child: rightIcon), + ], + ), + ], + ); + } +} diff --git a/lib/vote/ui/vote.dart b/lib/vote/ui/vote.dart index 107aff21ec..a6f5f113e9 100644 --- a/lib/vote/ui/vote.dart +++ b/lib/vote/ui/vote.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:titan/tools/ui/widgets/top_bar.dart'; +import 'package:titan/vote/router.dart'; import 'package:titan/tools/constants.dart'; class VoteTemplate extends StatelessWidget { @@ -7,6 +9,19 @@ class VoteTemplate extends StatelessWidget { @override Widget build(BuildContext context) { - return Container(color: ColorConstants.background, child: child); + return Scaffold( + body: Container( + decoration: const BoxDecoration(color: ColorConstants.background), + child: SafeArea( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + const TopBar(root: VoteRouter.root), + Expanded(child: child), + ], + ), + ), + ), + ); } } From 342bbbe108cee39bda3d9bdeaeb0daa3baf86dcb Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 26 Jul 2025 15:40:54 +0200 Subject: [PATCH 074/473] fix color --- lib/main.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/main.dart b/lib/main.dart index f168ae616e..c2cdc23c90 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -13,6 +13,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/navigation/providers/navbar_animation.dart'; import 'package:titan/router.dart'; import 'package:titan/service/tools/setup.dart'; +import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/plausible/plausible_observer.dart'; import 'package:titan/tools/providers/path_forwarding_provider.dart'; @@ -106,6 +107,7 @@ class MyApp extends HookConsumerWidget { primarySwatch: Colors.orange, textTheme: GoogleFonts.latoTextTheme(Theme.of(context).textTheme), brightness: Brightness.light, + scaffoldBackgroundColor: ColorConstants.background, ), routeInformationParser: const QRouteInformationParser(), builder: (context, child) { From bf7c60ca484ac9a9a8acf03026b92991025a0435 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Mon, 28 Jul 2025 20:48:07 +0200 Subject: [PATCH 075/473] fix : statusBar --- lib/feed/ui/feed.dart | 1 + lib/main.dart | 1 - lib/navigation/ui/navigation_template.dart | 151 +++++++++------------ 3 files changed, 64 insertions(+), 89 deletions(-) diff --git a/lib/feed/ui/feed.dart b/lib/feed/ui/feed.dart index 9447e836b0..4e38e33351 100644 --- a/lib/feed/ui/feed.dart +++ b/lib/feed/ui/feed.dart @@ -14,6 +14,7 @@ class FeedTemplate extends StatelessWidget { child: Column( mainAxisAlignment: MainAxisAlignment.start, children: [ + const SizedBox(height: 40), TopBar(root: FeedRouter.root), Expanded(child: child), ], diff --git a/lib/main.dart b/lib/main.dart index c2cdc23c90..fdc71d181b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -107,7 +107,6 @@ class MyApp extends HookConsumerWidget { primarySwatch: Colors.orange, textTheme: GoogleFonts.latoTextTheme(Theme.of(context).textTheme), brightness: Brightness.light, - scaffoldBackgroundColor: ColorConstants.background, ), routeInformationParser: const QRouteInformationParser(), builder: (context, child) { diff --git a/lib/navigation/ui/navigation_template.dart b/lib/navigation/ui/navigation_template.dart index cba00b2789..0fce93aa41 100644 --- a/lib/navigation/ui/navigation_template.dart +++ b/lib/navigation/ui/navigation_template.dart @@ -54,98 +54,73 @@ class NavigationTemplate extends HookConsumerWidget { return Builder( builder: (context) { return Scaffold( - body: SafeArea( - child: Stack( - children: [ - child, - if (pathForwarding.isLoggedIn) - Positioned( - left: 0, - bottom: 0, - right: 0, - child: Consumer( - builder: (context, ref, child) { - final navbarVisible = ref.watch( - navbarVisibilityProvider, - ); - return AnimatedSlide( - offset: Offset(0, navbarVisible ? 0 : 1), - duration: const Duration(milliseconds: 300), - curve: Curves.easeInOut, - child: AnimatedOpacity( - opacity: navbarVisible ? 1.0 : 0.0, - duration: const Duration(milliseconds: 300), - child: AnimatedBuilder( - animation: animation!, - builder: (context, child) => Visibility( - visible: - animation.isCompleted && - animation.value == 1.0, - child: Opacity( - opacity: animation.value, - child: FloatingNavbar( - items: [ - FloatingNavbarItem( - module: FeedRouter.module, - onTap: () { - pathForwardingNotifier.forward( - FeedRouter.root, - ); - WidgetsBinding.instance - .addPostFrameCallback((_) { - QR.to(FeedRouter.root); - }); - }, - ), - ...navbarListModule.map((module) { - return FloatingNavbarItem( - module: module, - onTap: () { - navbarListModuleNotifier.pushModule( - module, - ); - pathForwardingNotifier.forward( - module.root, - ); - WidgetsBinding.instance - .addPostFrameCallback((_) { - QR.to(module.root); - }); - }, - ); - }), - FloatingNavbarItem( - module: Module( - getName: (context) => - AppLocalizations.of( - context, - )!.moduleOthers, - description: '', - root: AppRouter.allModules, - ), - onTap: () { - pathForwardingNotifier.forward( - AppRouter.allModules, - ); - WidgetsBinding.instance - .addPostFrameCallback((_) { - QR.to(AppRouter.allModules); - }); - }, - ), - ], - ), - ), + body: Stack( + children: [ + child, + if (pathForwarding.isLoggedIn) + Positioned( + left: 0, + bottom: 20, + right: 0, + child: Visibility( + visible: animation!.isCompleted && animation.value == 1.0, + child: AnimatedBuilder( + animation: animation, + builder: (context, child) => Opacity( + opacity: animation.value, + child: FloatingNavbar( + items: [ + FloatingNavbarItem( + module: FeedRouter.module, + onTap: () { + pathForwardingNotifier.forward(FeedRouter.root); + WidgetsBinding.instance.addPostFrameCallback(( + _, + ) { + QR.to(FeedRouter.root); + }); + }, + ), + ...navbarListModule.map((module) { + return FloatingNavbarItem( + module: module, + onTap: () { + navbarListModuleNotifier.pushModule(module); + pathForwardingNotifier.forward(module.root); + WidgetsBinding.instance.addPostFrameCallback(( + _, + ) { + QR.to(module.root); + }); + }, + ); + }), + FloatingNavbarItem( + module: Module( + getName: (context) => + AppLocalizations.of(context)!.moduleOthers, + description: '', + root: AppRouter.allModules, ), + onTap: () { + pathForwardingNotifier.forward( + AppRouter.allModules, + ); + WidgetsBinding.instance.addPostFrameCallback(( + _, + ) { + QR.to(AppRouter.allModules); + }); + }, ), - ), - ); - }, + ], + ), + ), ), ), - if (displayQuit) const QuitDialog(), - ], - ), + ), + if (displayQuit) const QuitDialog(), + ], ), ); }, From 70b7302b1f5b633e8cf4af85bc0bd3ccaf4aabaf Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Mon, 28 Jul 2025 21:21:36 +0200 Subject: [PATCH 076/473] rebase fixes --- lib/main.dart | 1 - lib/navigation/ui/all_module_page.dart | 4 +++- lib/navigation/ui/navigation_template.dart | 1 - lib/router.dart | 10 ++++++++++ 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index fdc71d181b..f168ae616e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -13,7 +13,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/navigation/providers/navbar_animation.dart'; import 'package:titan/router.dart'; import 'package:titan/service/tools/setup.dart'; -import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/plausible/plausible_observer.dart'; import 'package:titan/tools/providers/path_forwarding_provider.dart'; diff --git a/lib/navigation/ui/all_module_page.dart b/lib/navigation/ui/all_module_page.dart index 503c400492..964647ee4e 100644 --- a/lib/navigation/ui/all_module_page.dart +++ b/lib/navigation/ui/all_module_page.dart @@ -4,7 +4,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/navigation/providers/navbar_module_list.dart'; import 'package:titan/navigation/ui/scroll_to_hide_navbar.dart'; -import 'package:titan/navigation/ui/top_bar.dart'; +import 'package:titan/router.dart'; import 'package:titan/settings/providers/module_list_provider.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/providers/path_forwarding_provider.dart'; @@ -25,7 +25,9 @@ class AllModulePage extends HookConsumerWidget { return Container( color: ColorConstants.background, child: Column( + mainAxisAlignment: MainAxisAlignment.start, children: [ + const SizedBox(height: 40), TopBar(root: AppRouter.allModules), Expanded( child: ScrollToHideNavbar( diff --git a/lib/navigation/ui/navigation_template.dart b/lib/navigation/ui/navigation_template.dart index 0fce93aa41..720369ab58 100644 --- a/lib/navigation/ui/navigation_template.dart +++ b/lib/navigation/ui/navigation_template.dart @@ -8,7 +8,6 @@ import 'package:titan/navigation/class/module.dart'; import 'package:titan/navigation/providers/display_quit_popup.dart'; import 'package:titan/navigation/providers/navbar_animation.dart'; import 'package:titan/navigation/providers/navbar_module_list.dart'; -import 'package:titan/navigation/providers/navbar_visibility_provider.dart'; import 'package:titan/navigation/providers/should_setup_provider.dart'; import 'package:titan/router.dart'; import 'package:titan/service/tools/setup.dart'; diff --git a/lib/router.dart b/lib/router.dart index 2d86c59176..353aa8bab5 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -15,6 +15,8 @@ import 'package:titan/login/router.dart'; import 'package:titan/others/ui/loading_page.dart' deferred as loading_page; import 'package:titan/others/ui/no_internet_page.dart' deferred as no_internet_page; +import 'package:titan/navigation/ui/all_module_page.dart' + deferred as all_module_page; import 'package:titan/others/ui/no_module.dart' deferred as no_module_page; import 'package:titan/others/ui/update_page.dart' deferred as update_page; import 'package:titan/paiement/router.dart'; @@ -73,6 +75,14 @@ class AppRouter { builder: () => no_module_page.NoModulePage(), middleware: [DeferredLoadingMiddleware(no_module_page.loadLibrary)], ), + QRoute( + path: allModules, + builder: () => all_module_page.AllModulePage(), + middleware: [ + AuthenticatedMiddleware(ref), + DeferredLoadingMiddleware(all_module_page.loadLibrary), + ], + ), AdminRouter(ref).route(), AdvertRouter(ref).route(), AmapRouter(ref).route(), From 25628c12a0f5af46e9ef2debe89598fce2183a49 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Mon, 28 Jul 2025 21:49:36 +0200 Subject: [PATCH 077/473] fix : navbar --- lib/feed/class/feed_item.dart | 10 ++ lib/navigation/ui/navigation_template.dart | 120 ++++++++++++--------- 2 files changed, 78 insertions(+), 52 deletions(-) diff --git a/lib/feed/class/feed_item.dart b/lib/feed/class/feed_item.dart index 9be1c12e33..46e08bec06 100644 --- a/lib/feed/class/feed_item.dart +++ b/lib/feed/class/feed_item.dart @@ -57,6 +57,16 @@ class FeedItem { participantsCount: 33, onRegister: () {}, ), + FeedItem( + type: FeedItemType.action, + title: 'Campagne', + subtitle: 'Jusqu\'à minuit', + date: DateTime(2025, 11, 8), + isOngoing: true, + needsRegistration: true, + participantsCount: 33, + onRegister: () {}, + ), ]; } } diff --git a/lib/navigation/ui/navigation_template.dart b/lib/navigation/ui/navigation_template.dart index 720369ab58..48e652968e 100644 --- a/lib/navigation/ui/navigation_template.dart +++ b/lib/navigation/ui/navigation_template.dart @@ -8,6 +8,7 @@ import 'package:titan/navigation/class/module.dart'; import 'package:titan/navigation/providers/display_quit_popup.dart'; import 'package:titan/navigation/providers/navbar_animation.dart'; import 'package:titan/navigation/providers/navbar_module_list.dart'; +import 'package:titan/navigation/providers/navbar_visibility_provider.dart'; import 'package:titan/navigation/providers/should_setup_provider.dart'; import 'package:titan/router.dart'; import 'package:titan/service/tools/setup.dart'; @@ -61,61 +62,76 @@ class NavigationTemplate extends HookConsumerWidget { left: 0, bottom: 20, right: 0, - child: Visibility( - visible: animation!.isCompleted && animation.value == 1.0, - child: AnimatedBuilder( - animation: animation, - builder: (context, child) => Opacity( - opacity: animation.value, - child: FloatingNavbar( - items: [ - FloatingNavbarItem( - module: FeedRouter.module, - onTap: () { - pathForwardingNotifier.forward(FeedRouter.root); - WidgetsBinding.instance.addPostFrameCallback(( - _, - ) { - QR.to(FeedRouter.root); - }); - }, - ), - ...navbarListModule.map((module) { - return FloatingNavbarItem( - module: module, - onTap: () { - navbarListModuleNotifier.pushModule(module); - pathForwardingNotifier.forward(module.root); - WidgetsBinding.instance.addPostFrameCallback(( - _, - ) { - QR.to(module.root); - }); - }, - ); - }), - FloatingNavbarItem( - module: Module( - getName: (context) => - AppLocalizations.of(context)!.moduleOthers, - description: '', - root: AppRouter.allModules, + child: Consumer( + builder: (context, ref, child) { + final navbarVisible = ref.watch(navbarVisibilityProvider); + return AnimatedSlide( + offset: Offset(0, navbarVisible ? 0 : 1), + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + child: AnimatedOpacity( + opacity: navbarVisible ? 1.0 : 0.0, + duration: const Duration(milliseconds: 300), + child: AnimatedBuilder( + animation: animation!, + builder: (context, child) => Opacity( + opacity: animation.value, + child: FloatingNavbar( + items: [ + FloatingNavbarItem( + module: FeedRouter.module, + onTap: () { + pathForwardingNotifier.forward( + FeedRouter.root, + ); + WidgetsBinding.instance + .addPostFrameCallback((_) { + QR.to(FeedRouter.root); + }); + }, + ), + ...navbarListModule.map((module) { + return FloatingNavbarItem( + module: module, + onTap: () { + navbarListModuleNotifier.pushModule( + module, + ); + pathForwardingNotifier.forward( + module.root, + ); + WidgetsBinding.instance + .addPostFrameCallback((_) { + QR.to(module.root); + }); + }, + ); + }), + FloatingNavbarItem( + module: Module( + getName: (context) => AppLocalizations.of( + context, + )!.moduleOthers, + description: '', + root: AppRouter.allModules, + ), + onTap: () { + pathForwardingNotifier.forward( + AppRouter.allModules, + ); + WidgetsBinding.instance + .addPostFrameCallback((_) { + QR.to(AppRouter.allModules); + }); + }, + ), + ], ), - onTap: () { - pathForwardingNotifier.forward( - AppRouter.allModules, - ); - WidgetsBinding.instance.addPostFrameCallback(( - _, - ) { - QR.to(AppRouter.allModules); - }); - }, ), - ], + ), ), - ), - ), + ); + }, ), ), if (displayQuit) const QuitDialog(), From c63983773f02f9088e066b076feec8e9217cdaef Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Mon, 28 Jul 2025 22:39:17 +0200 Subject: [PATCH 078/473] fix padding --- lib/amap/ui/amap.dart | 40 +++++++++++---------- lib/cinema/ui/cinema.dart | 16 ++++----- lib/feed/ui/feed.dart | 15 ++++---- lib/loan/ui/loan.dart | 40 +++++++++++---------- lib/phonebook/ui/phonebook.dart | 50 ++++++++++++++------------- lib/seed-library/ui/seed_library.dart | 45 +++++++++++++----------- 6 files changed, 109 insertions(+), 97 deletions(-) diff --git a/lib/amap/ui/amap.dart b/lib/amap/ui/amap.dart index 4c4023a180..dc52d09b93 100644 --- a/lib/amap/ui/amap.dart +++ b/lib/amap/ui/amap.dart @@ -13,25 +13,27 @@ class AmapTemplate extends StatelessWidget { Widget build(BuildContext context) { return Container( color: ColorConstants.background, - child: Column( - children: [ - TopBar( - root: AmapRouter.root, - rightIcon: QR.currentPath == AmapRouter.root - ? IconButton( - onPressed: () { - QR.to(AmapRouter.root + AmapRouter.presentation); - }, - icon: const HeroIcon( - HeroIcons.informationCircle, - color: Colors.black, - size: 40, - ), - ) - : null, - ), - Expanded(child: child), - ], + child: SafeArea( + child: Column( + children: [ + TopBar( + root: AmapRouter.root, + rightIcon: QR.currentPath == AmapRouter.root + ? IconButton( + onPressed: () { + QR.to(AmapRouter.root + AmapRouter.presentation); + }, + icon: const HeroIcon( + HeroIcons.informationCircle, + color: Colors.black, + size: 40, + ), + ) + : null, + ), + Expanded(child: child), + ], + ), ), ); } diff --git a/lib/cinema/ui/cinema.dart b/lib/cinema/ui/cinema.dart index 44c6e5ad9d..5d6c4f3f83 100644 --- a/lib/cinema/ui/cinema.dart +++ b/lib/cinema/ui/cinema.dart @@ -12,14 +12,14 @@ class CinemaTemplate extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { return Container( color: ColorConstants.background, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - TopBar( - root: CinemaRouter.root, - ), - Expanded(child: child), - ], + child: SafeArea( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + TopBar(root: CinemaRouter.root), + Expanded(child: child), + ], + ), ), ); } diff --git a/lib/feed/ui/feed.dart b/lib/feed/ui/feed.dart index 4e38e33351..239fb99aef 100644 --- a/lib/feed/ui/feed.dart +++ b/lib/feed/ui/feed.dart @@ -11,13 +11,14 @@ class FeedTemplate extends StatelessWidget { Widget build(BuildContext context) { return Container( color: ColorConstants.background, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - const SizedBox(height: 40), - TopBar(root: FeedRouter.root), - Expanded(child: child), - ], + child: SafeArea( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + TopBar(root: FeedRouter.root), + Expanded(child: child), + ], + ), ), ); } diff --git a/lib/loan/ui/loan.dart b/lib/loan/ui/loan.dart index f9b70782b6..03392d07f8 100644 --- a/lib/loan/ui/loan.dart +++ b/lib/loan/ui/loan.dart @@ -16,24 +16,28 @@ class LoanTemplate extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { return Container( color: ColorConstants.background, - child: Column( - children: [ - TopBar( - root: LoanRouter.root, - onBack: () { - if (QR.currentPath == - LoanRouter.root + LoanRouter.admin + LoanRouter.addEditLoan) { - final loanersItemsNotifier = ref.watch( - loanersItemsProvider.notifier, - ); - final loaner = ref.watch(loanerProvider); - final itemList = ref.watch(itemListProvider); - loanersItemsNotifier.setTData(loaner, itemList); - } - }, - ), - Expanded(child: child), - ], + child: SafeArea( + child: Column( + children: [ + TopBar( + root: LoanRouter.root, + onBack: () { + if (QR.currentPath == + LoanRouter.root + + LoanRouter.admin + + LoanRouter.addEditLoan) { + final loanersItemsNotifier = ref.watch( + loanersItemsProvider.notifier, + ); + final loaner = ref.watch(loanerProvider); + final itemList = ref.watch(itemListProvider); + loanersItemsNotifier.setTData(loaner, itemList); + } + }, + ), + Expanded(child: child), + ], + ), ), ); } diff --git a/lib/phonebook/ui/phonebook.dart b/lib/phonebook/ui/phonebook.dart index 2e96696afd..080215fdaf 100644 --- a/lib/phonebook/ui/phonebook.dart +++ b/lib/phonebook/ui/phonebook.dart @@ -15,30 +15,32 @@ class PhonebookTemplate extends HookConsumerWidget { final kindNotifier = ref.watch(associationKindProvider.notifier); return Container( color: ColorConstants.background, - child: Column( - children: [ - TopBar( - root: PhonebookRouter.root, - onBack: () { - if (QR.currentPath != - PhonebookRouter.root + - PhonebookRouter.admin + - PhonebookRouter.editAssociation + - PhonebookRouter.addEditMember) { - kindNotifier.setKind(''); - } - if (QR.currentPath == - PhonebookRouter.root + - PhonebookRouter.admin + - PhonebookRouter.editAssociation) { - QR.to( - PhonebookRouter.root + PhonebookRouter.admin, - ); // Used on back after adding an association - } - }, - ), - Expanded(child: child), - ], + child: SafeArea( + child: Column( + children: [ + TopBar( + root: PhonebookRouter.root, + onBack: () { + if (QR.currentPath != + PhonebookRouter.root + + PhonebookRouter.admin + + PhonebookRouter.editAssociation + + PhonebookRouter.addEditMember) { + kindNotifier.setKind(''); + } + if (QR.currentPath == + PhonebookRouter.root + + PhonebookRouter.admin + + PhonebookRouter.editAssociation) { + QR.to( + PhonebookRouter.root + PhonebookRouter.admin, + ); // Used on back after adding an association + } + }, + ), + Expanded(child: child), + ], + ), ), ); } diff --git a/lib/seed-library/ui/seed_library.dart b/lib/seed-library/ui/seed_library.dart index 8a0d2bb51d..b439d5932b 100644 --- a/lib/seed-library/ui/seed_library.dart +++ b/lib/seed-library/ui/seed_library.dart @@ -13,27 +13,30 @@ class SeedLibraryTemplate extends StatelessWidget { Widget build(BuildContext context) { return Container( color: ColorConstants.background, - child: Column( - children: [ - TopBar( - root: SeedLibraryRouter.root, - rightIcon: QR.currentPath == SeedLibraryRouter.root - ? IconButton( - onPressed: () { - QR.to( - SeedLibraryRouter.root + SeedLibraryRouter.information, - ); - }, - icon: const HeroIcon( - HeroIcons.informationCircle, - color: Colors.black, - size: 40, - ), - ) - : null, - ), - Expanded(child: child), - ], + child: SafeArea( + child: Column( + children: [ + TopBar( + root: SeedLibraryRouter.root, + rightIcon: QR.currentPath == SeedLibraryRouter.root + ? IconButton( + onPressed: () { + QR.to( + SeedLibraryRouter.root + + SeedLibraryRouter.information, + ); + }, + icon: const HeroIcon( + HeroIcons.informationCircle, + color: Colors.black, + size: 40, + ), + ) + : null, + ), + Expanded(child: child), + ], + ), ), ); } From e441b4110e59b70a5cc8f6dab19c053ab434a1d8 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Mon, 28 Jul 2025 22:39:56 +0200 Subject: [PATCH 079/473] Format --- lib/admin/ui/admin.dart | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/admin/ui/admin.dart b/lib/admin/ui/admin.dart index 1cd11404a8..2da81a1109 100644 --- a/lib/admin/ui/admin.dart +++ b/lib/admin/ui/admin.dart @@ -17,9 +17,7 @@ class AdminTemplate extends HookConsumerWidget { child: Column( mainAxisAlignment: MainAxisAlignment.start, children: [ - TopBar( - root: AdminRouter.root, - ), + TopBar(root: AdminRouter.root), Expanded(child: child), ], ), From 9d7744c74847d108594c0dfa4c19d5d9962f3e8b Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Tue, 29 Jul 2025 18:45:11 +0200 Subject: [PATCH 080/473] fade transition --- lib/admin/router.dart | 5 +++++ lib/advert/router.dart | 5 +++++ lib/amap/router.dart | 5 +++++ lib/booking/router.dart | 5 +++++ lib/centralisation/router.dart | 5 +++++ lib/cinema/router.dart | 5 +++++ lib/event/router.dart | 5 +++++ lib/feed/router.dart | 5 +++++ lib/flappybird/router.dart | 5 +++++ lib/home/router.dart | 5 +++++ lib/loan/router.dart | 5 +++++ lib/paiement/router.dart | 5 +++++ lib/ph/router.dart | 5 +++++ lib/phonebook/router.dart | 5 +++++ lib/purchases/router.dart | 5 +++++ lib/raffle/router.dart | 5 +++++ lib/recommendation/router.dart | 5 +++++ lib/router.dart | 5 +++++ lib/seed-library/router.dart | 5 +++++ lib/settings/router.dart | 5 +++++ lib/vote/router.dart | 5 +++++ 21 files changed, 105 insertions(+) diff --git a/lib/admin/router.dart b/lib/admin/router.dart index 6aa4df876d..4b99655793 100644 --- a/lib/admin/router.dart +++ b/lib/admin/router.dart @@ -1,3 +1,4 @@ +import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/admin/providers/is_admin_provider.dart'; import 'package:titan/admin/ui/pages/groups/add_group_page/add_group_page.dart' @@ -68,6 +69,10 @@ class AdminRouter { AdminMiddleware(ref, isAdminProvider), DeferredLoadingMiddleware(main_page.loadLibrary), ], + pageType: QCustomPage( + transitionsBuilder: (_, animation, _, child) => + FadeTransition(opacity: animation, child: child), + ), children: [ QRoute( path: groups, diff --git a/lib/advert/router.dart b/lib/advert/router.dart index 2bf5f1585b..4654b5ddbf 100644 --- a/lib/advert/router.dart +++ b/lib/advert/router.dart @@ -1,3 +1,4 @@ +import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/admin/providers/is_admin_provider.dart'; import 'package:titan/advert/providers/is_advert_admin_provider.dart'; @@ -40,6 +41,10 @@ class AdvertRouter { AuthenticatedMiddleware(ref), DeferredLoadingMiddleware(main_page.loadLibrary), ], + pageType: QCustomPage( + transitionsBuilder: (_, animation, _, child) => + FadeTransition(opacity: animation, child: child), + ), children: [ QRoute( path: admin, diff --git a/lib/amap/router.dart b/lib/amap/router.dart index 67d1623f8d..f0550f0ff4 100644 --- a/lib/amap/router.dart +++ b/lib/amap/router.dart @@ -1,3 +1,4 @@ +import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/amap/providers/is_amap_admin_provider.dart'; import 'package:titan/amap/ui/pages/admin_page/admin_page.dart' @@ -48,6 +49,10 @@ class AmapRouter { AuthenticatedMiddleware(ref), DeferredLoadingMiddleware(main_page.loadLibrary), ], + pageType: QCustomPage( + transitionsBuilder: (_, animation, _, child) => + FadeTransition(opacity: animation, child: child), + ), children: [ QRoute( path: admin, diff --git a/lib/booking/router.dart b/lib/booking/router.dart index 2ce4891101..8819f3ffb3 100644 --- a/lib/booking/router.dart +++ b/lib/booking/router.dart @@ -1,3 +1,4 @@ +import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/booking/providers/is_admin_provider.dart'; import 'package:titan/booking/providers/is_manager_provider.dart'; @@ -45,6 +46,10 @@ class BookingRouter { AuthenticatedMiddleware(ref), DeferredLoadingMiddleware(main_page.loadLibrary), ], + pageType: QCustomPage( + transitionsBuilder: (_, animation, _, child) => + FadeTransition(opacity: animation, child: child), + ), children: [ QRoute( path: admin, diff --git a/lib/centralisation/router.dart b/lib/centralisation/router.dart index 3076913cac..74e588317d 100644 --- a/lib/centralisation/router.dart +++ b/lib/centralisation/router.dart @@ -1,3 +1,4 @@ +import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/centralisation/ui/pages/main_page.dart' deferred as main_page; @@ -26,5 +27,9 @@ class CentralisationRouter { AuthenticatedMiddleware(ref), DeferredLoadingMiddleware(main_page.loadLibrary), ], + pageType: QCustomPage( + transitionsBuilder: (_, animation, _, child) => + FadeTransition(opacity: animation, child: child), + ), ); } diff --git a/lib/cinema/router.dart b/lib/cinema/router.dart index 78cdea516b..f488a03aa7 100644 --- a/lib/cinema/router.dart +++ b/lib/cinema/router.dart @@ -1,3 +1,4 @@ +import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/cinema/providers/is_cinema_admin.dart'; import 'package:titan/cinema/ui/pages/admin_page/admin_page.dart' @@ -38,6 +39,10 @@ class CinemaRouter { NotificationMiddleWare(ref), DeferredLoadingMiddleware(main_page.loadLibrary), ], + pageType: QCustomPage( + transitionsBuilder: (_, animation, _, child) => + FadeTransition(opacity: animation, child: child), + ), children: [ QRoute( path: detail, diff --git a/lib/event/router.dart b/lib/event/router.dart index 80e96cfe8b..a4ba1d6aee 100644 --- a/lib/event/router.dart +++ b/lib/event/router.dart @@ -1,3 +1,4 @@ +import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/navigation/class/module.dart'; @@ -36,6 +37,10 @@ class EventRouter { AuthenticatedMiddleware(ref), DeferredLoadingMiddleware(main_page.loadLibrary), ], + pageType: QCustomPage( + transitionsBuilder: (_, animation, _, child) => + FadeTransition(opacity: animation, child: child), + ), children: [ QRoute( path: admin, diff --git a/lib/feed/router.dart b/lib/feed/router.dart index 9f11abd64f..b5fece5628 100644 --- a/lib/feed/router.dart +++ b/lib/feed/router.dart @@ -1,3 +1,4 @@ +import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/admin/providers/is_admin_provider.dart'; import 'package:titan/l10n/app_localizations.dart'; @@ -32,6 +33,10 @@ class FeedRouter { AuthenticatedMiddleware(ref), DeferredLoadingMiddleware(main_page.loadLibrary), ], + pageType: QCustomPage( + transitionsBuilder: (_, animation, _, child) => + FadeTransition(opacity: animation, child: child), + ), children: [ QRoute( path: admin, diff --git a/lib/flappybird/router.dart b/lib/flappybird/router.dart index bcac822bb6..a26cdebd02 100644 --- a/lib/flappybird/router.dart +++ b/lib/flappybird/router.dart @@ -1,3 +1,4 @@ +import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/navigation/class/module.dart'; @@ -28,6 +29,10 @@ class FlappyBirdRouter { AuthenticatedMiddleware(ref), DeferredLoadingMiddleware(play_page.loadLibrary), ], + pageType: QCustomPage( + transitionsBuilder: (_, animation, _, child) => + FadeTransition(opacity: animation, child: child), + ), children: [ QRoute( path: leaderBoard, diff --git a/lib/home/router.dart b/lib/home/router.dart index 9582abb5b2..1ab0935059 100644 --- a/lib/home/router.dart +++ b/lib/home/router.dart @@ -1,3 +1,4 @@ +import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/navigation/class/module.dart'; @@ -27,6 +28,10 @@ class HomeRouter { AuthenticatedMiddleware(ref), DeferredLoadingMiddleware(home_page.loadLibrary), ], + pageType: QCustomPage( + transitionsBuilder: (_, animation, _, child) => + FadeTransition(opacity: animation, child: child), + ), children: [ QRoute( path: detail, diff --git a/lib/loan/router.dart b/lib/loan/router.dart index 3cdc71b67b..b9949400c6 100644 --- a/lib/loan/router.dart +++ b/lib/loan/router.dart @@ -1,3 +1,4 @@ +import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/navigation/class/module.dart'; @@ -39,6 +40,10 @@ class LoanRouter { AuthenticatedMiddleware(ref), DeferredLoadingMiddleware(main_page.loadLibrary), ], + pageType: QCustomPage( + transitionsBuilder: (_, animation, _, child) => + FadeTransition(opacity: animation, child: child), + ), children: [ QRoute( path: admin, diff --git a/lib/paiement/router.dart b/lib/paiement/router.dart index 3c40b6fae8..a262ac883e 100644 --- a/lib/paiement/router.dart +++ b/lib/paiement/router.dart @@ -1,3 +1,4 @@ +import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/navigation/class/module.dart'; @@ -51,6 +52,10 @@ class PaymentRouter { AuthenticatedMiddleware(ref), DeferredLoadingMiddleware(main_page.loadLibrary), ], + pageType: QCustomPage( + transitionsBuilder: (_, animation, _, child) => + FadeTransition(opacity: animation, child: child), + ), children: [ QRoute( path: PaymentRouter.stats, diff --git a/lib/ph/router.dart b/lib/ph/router.dart index bde6d29595..3390178e9d 100644 --- a/lib/ph/router.dart +++ b/lib/ph/router.dart @@ -1,5 +1,6 @@ // ignore_for_file: constant_identifier_names +import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/navigation/class/module.dart'; @@ -35,6 +36,10 @@ class PhRouter { path: PhRouter.root, builder: () => main_page.PhMainPage(), middleware: [DeferredLoadingMiddleware(main_page.loadLibrary)], + pageType: QCustomPage( + transitionsBuilder: (_, animation, _, child) => + FadeTransition(opacity: animation, child: child), + ), children: [ QRoute( path: past_ph_selection, diff --git a/lib/phonebook/router.dart b/lib/phonebook/router.dart index 4058a09cfa..7354fb42ce 100644 --- a/lib/phonebook/router.dart +++ b/lib/phonebook/router.dart @@ -1,3 +1,4 @@ +import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/navigation/class/module.dart'; @@ -34,6 +35,10 @@ class PhonebookRouter { path: PhonebookRouter.root, builder: () => const PhonebookMainPage(), middleware: [AuthenticatedMiddleware(ref)], + pageType: QCustomPage( + transitionsBuilder: (_, animation, _, child) => + FadeTransition(opacity: animation, child: child), + ), children: [ QRoute( path: admin, diff --git a/lib/purchases/router.dart b/lib/purchases/router.dart index 9d5be2c51a..586dbbd331 100644 --- a/lib/purchases/router.dart +++ b/lib/purchases/router.dart @@ -1,3 +1,4 @@ +import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/navigation/class/module.dart'; @@ -32,6 +33,10 @@ class PurchasesRouter { path: PurchasesRouter.root, builder: () => const PurchasesMainPage(), middleware: [AuthenticatedMiddleware(ref)], + pageType: QCustomPage( + transitionsBuilder: (_, animation, _, child) => + FadeTransition(opacity: animation, child: child), + ), children: [ QRoute( path: scan, diff --git a/lib/raffle/router.dart b/lib/raffle/router.dart index dc5a5fc68a..ed8a3bd61b 100644 --- a/lib/raffle/router.dart +++ b/lib/raffle/router.dart @@ -1,3 +1,4 @@ +import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/navigation/class/module.dart'; @@ -41,6 +42,10 @@ class RaffleRouter { AuthenticatedMiddleware(ref), DeferredLoadingMiddleware(main_page.loadLibrary), ], + pageType: QCustomPage( + transitionsBuilder: (_, animation, _, child) => + FadeTransition(opacity: animation, child: child), + ), children: [ QRoute( path: admin, diff --git a/lib/recommendation/router.dart b/lib/recommendation/router.dart index dfe45b41fe..f1d375acea 100644 --- a/lib/recommendation/router.dart +++ b/lib/recommendation/router.dart @@ -1,3 +1,4 @@ +import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/navigation/class/module.dart'; @@ -36,6 +37,10 @@ class RecommendationRouter { AuthenticatedMiddleware(ref), DeferredLoadingMiddleware(main_page.loadLibrary), ], + pageType: QCustomPage( + transitionsBuilder: (_, animation, _, child) => + FadeTransition(opacity: animation, child: child), + ), children: [ QRoute( path: information, diff --git a/lib/router.dart b/lib/router.dart index 353aa8bab5..68bb50ad65 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -1,3 +1,4 @@ +import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/admin/router.dart'; import 'package:titan/advert/router.dart'; @@ -82,6 +83,10 @@ class AppRouter { AuthenticatedMiddleware(ref), DeferredLoadingMiddleware(all_module_page.loadLibrary), ], + pageType: QCustomPage( + transitionsBuilder: (_, animation, _, child) => + FadeTransition(opacity: animation, child: child), + ), ), AdminRouter(ref).route(), AdvertRouter(ref).route(), diff --git a/lib/seed-library/router.dart b/lib/seed-library/router.dart index 3ca34d3f37..6b790884af 100644 --- a/lib/seed-library/router.dart +++ b/lib/seed-library/router.dart @@ -1,3 +1,4 @@ +import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/navigation/class/module.dart'; @@ -60,6 +61,10 @@ class SeedLibraryRouter { AuthenticatedMiddleware(ref), DeferredLoadingMiddleware(main_page.loadLibrary), ], + pageType: QCustomPage( + transitionsBuilder: (_, animation, _, child) => + FadeTransition(opacity: animation, child: child), + ), children: [ QRoute( path: SeedLibraryRouter.information, diff --git a/lib/settings/router.dart b/lib/settings/router.dart index bde36de735..104b81ec08 100644 --- a/lib/settings/router.dart +++ b/lib/settings/router.dart @@ -1,4 +1,5 @@ import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/admin/router.dart'; import 'package:titan/l10n/app_localizations.dart'; @@ -43,6 +44,10 @@ class SettingsRouter { AuthenticatedMiddleware(ref), DeferredLoadingMiddleware(main_page.loadLibrary), ], + pageType: QCustomPage( + transitionsBuilder: (_, animation, _, child) => + FadeTransition(opacity: animation, child: child), + ), children: [ QRoute( path: editAccount, diff --git a/lib/vote/router.dart b/lib/vote/router.dart index 01c6e312e6..ece95a48b8 100644 --- a/lib/vote/router.dart +++ b/lib/vote/router.dart @@ -1,3 +1,4 @@ +import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/navigation/class/module.dart'; @@ -39,6 +40,10 @@ class VoteRouter { AuthenticatedMiddleware(ref), DeferredLoadingMiddleware(main_page.loadLibrary), ], + pageType: QCustomPage( + transitionsBuilder: (_, animation, _, child) => + FadeTransition(opacity: animation, child: child), + ), children: [ QRoute( path: admin, From 0864152389ca5d32de811367a6e87a5277e4a79d Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 30 Jul 2025 00:04:32 +0200 Subject: [PATCH 081/473] fix : navbar issue --- lib/navigation/ui/navigation_template.dart | 92 ++++++++++++---------- 1 file changed, 49 insertions(+), 43 deletions(-) diff --git a/lib/navigation/ui/navigation_template.dart b/lib/navigation/ui/navigation_template.dart index 48e652968e..65b40fe4a9 100644 --- a/lib/navigation/ui/navigation_template.dart +++ b/lib/navigation/ui/navigation_template.dart @@ -74,58 +74,64 @@ class NavigationTemplate extends HookConsumerWidget { duration: const Duration(milliseconds: 300), child: AnimatedBuilder( animation: animation!, - builder: (context, child) => Opacity( - opacity: animation.value, - child: FloatingNavbar( - items: [ - FloatingNavbarItem( - module: FeedRouter.module, - onTap: () { - pathForwardingNotifier.forward( - FeedRouter.root, - ); - WidgetsBinding.instance - .addPostFrameCallback((_) { - QR.to(FeedRouter.root); - }); - }, - ), - ...navbarListModule.map((module) { - return FloatingNavbarItem( - module: module, + builder: (context, child) => Visibility( + visible: + animation.isCompleted && + animation.value == 1.0, + child: Opacity( + opacity: animation.value, + child: FloatingNavbar( + items: [ + FloatingNavbarItem( + module: FeedRouter.module, onTap: () { - navbarListModuleNotifier.pushModule( - module, - ); pathForwardingNotifier.forward( - module.root, + FeedRouter.root, ); WidgetsBinding.instance .addPostFrameCallback((_) { - QR.to(module.root); + QR.to(FeedRouter.root); }); }, - ); - }), - FloatingNavbarItem( - module: Module( - getName: (context) => AppLocalizations.of( - context, - )!.moduleOthers, - description: '', - root: AppRouter.allModules, ), - onTap: () { - pathForwardingNotifier.forward( - AppRouter.allModules, + ...navbarListModule.map((module) { + return FloatingNavbarItem( + module: module, + onTap: () { + navbarListModuleNotifier.pushModule( + module, + ); + pathForwardingNotifier.forward( + module.root, + ); + WidgetsBinding.instance + .addPostFrameCallback((_) { + QR.to(module.root); + }); + }, ); - WidgetsBinding.instance - .addPostFrameCallback((_) { - QR.to(AppRouter.allModules); - }); - }, - ), - ], + }), + FloatingNavbarItem( + module: Module( + getName: (context) => + AppLocalizations.of( + context, + )!.moduleOthers, + description: '', + root: AppRouter.allModules, + ), + onTap: () { + pathForwardingNotifier.forward( + AppRouter.allModules, + ); + WidgetsBinding.instance + .addPostFrameCallback((_) { + QR.to(AppRouter.allModules); + }); + }, + ), + ], + ), ), ), ), From 5984f65d2858b94ed2bea48bfc37e0f075e4e35e Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 30 Jul 2025 18:09:56 +0200 Subject: [PATCH 082/473] change scroll feature --- .../providers/navbar_visibility_provider.dart | 100 +++--------------- lib/navigation/ui/scroll_to_hide_navbar.dart | 74 ++----------- 2 files changed, 22 insertions(+), 152 deletions(-) diff --git a/lib/navigation/providers/navbar_visibility_provider.dart b/lib/navigation/providers/navbar_visibility_provider.dart index db54d8539c..c3a276bf11 100644 --- a/lib/navigation/providers/navbar_visibility_provider.dart +++ b/lib/navigation/providers/navbar_visibility_provider.dart @@ -1,58 +1,17 @@ -import 'dart:async'; import 'package:hooks_riverpod/hooks_riverpod.dart'; class NavbarVisibilityNotifier extends StateNotifier { NavbarVisibilityNotifier() : super(true); - Timer? _debounceTimer; - Timer? _hideTimer; - Timer? _autoShowTimer; - - bool _lastRequestedState = true; - - static const Duration _autoShowDelay = Duration(milliseconds: 800); - static const Duration _debounceDelay = Duration(milliseconds: 50); - static const Duration _hideDelay = Duration(milliseconds: 100); + bool lastRequestedState = true; void _updateState(bool visible) { - _lastRequestedState = visible; - - _debounceTimer?.cancel(); - _hideTimer?.cancel(); - - if (visible) { - _autoShowTimer?.cancel(); - } - + lastRequestedState = visible; if (state != visible) { - if (visible) { - _debounceTimer = Timer(_debounceDelay, () { - if (_lastRequestedState == true) { - state = true; - } - }); - } else { - _hideTimer = Timer(_hideDelay, () { - if (_lastRequestedState == false) { - state = false; - - _startAutoShowTimer(); - } - }); - } + state = visible; } } - void _startAutoShowTimer() { - _autoShowTimer?.cancel(); - _autoShowTimer = Timer(_autoShowDelay, () { - if (!state) { - _lastRequestedState = true; - state = true; - } - }); - } - void show() => _updateState(true); void hide() => _updateState(false); @@ -60,18 +19,12 @@ class NavbarVisibilityNotifier extends StateNotifier { void toggle() => _updateState(!state); void forceShow() { - _debounceTimer?.cancel(); - _hideTimer?.cancel(); - _autoShowTimer?.cancel(); - _lastRequestedState = true; + lastRequestedState = true; state = true; } void hideWithoutAutoShow() { - _debounceTimer?.cancel(); - _hideTimer?.cancel(); - _autoShowTimer?.cancel(); - _lastRequestedState = false; + lastRequestedState = false; state = false; } @@ -83,9 +36,6 @@ class NavbarVisibilityNotifier extends StateNotifier { @override void dispose() { - _debounceTimer?.cancel(); - _hideTimer?.cancel(); - _autoShowTimer?.cancel(); super.dispose(); } } @@ -99,43 +49,17 @@ class ScrollDirectionNotifier extends StateNotifier { ScrollDirectionNotifier() : super(ScrollDirection.idle); double _lastScrollOffset = 0; - DateTime _lastDirectionChange = DateTime.now(); - - final double _scrollThreshold = 5.0; - - final int _directionChangeThresholdMs = 50; void updateScrollDirection(double scrollOffset) { - final double scrollDelta = (scrollOffset - _lastScrollOffset).abs(); - - final now = DateTime.now(); - final timeSinceLastChange = now - .difference(_lastDirectionChange) - .inMilliseconds; - - bool isSignificantScrolling = - scrollDelta > _scrollThreshold && - timeSinceLastChange > _directionChangeThresholdMs; - - bool isSlowScrolling = scrollDelta > 1.0 && timeSinceLastChange > 200; + final double scrollDelta = scrollOffset - _lastScrollOffset; - if (isSignificantScrolling || isSlowScrolling) { - final previousState = state; - - if (scrollOffset > _lastScrollOffset) { - state = ScrollDirection.down; - } else if (scrollOffset < _lastScrollOffset) { - state = ScrollDirection.up; - } - - if (previousState != state) { - _lastDirectionChange = now; - } else { - _lastDirectionChange = now; - } - - _lastScrollOffset = scrollOffset; + if (scrollDelta > 0) { + state = ScrollDirection.down; + } else if (scrollDelta < 0) { + state = ScrollDirection.up; } + + _lastScrollOffset = scrollOffset; } void resetDirection() { diff --git a/lib/navigation/ui/scroll_to_hide_navbar.dart b/lib/navigation/ui/scroll_to_hide_navbar.dart index 1194889d04..b27bc14524 100644 --- a/lib/navigation/ui/scroll_to_hide_navbar.dart +++ b/lib/navigation/ui/scroll_to_hide_navbar.dart @@ -5,13 +5,11 @@ import 'package:titan/navigation/providers/navbar_visibility_provider.dart'; class ScrollToHideNavbar extends ConsumerStatefulWidget { final Widget child; final ScrollController controller; - final Duration duration; const ScrollToHideNavbar({ super.key, required this.child, required this.controller, - this.duration = const Duration(milliseconds: 200), }); @override @@ -19,6 +17,8 @@ class ScrollToHideNavbar extends ConsumerStatefulWidget { } class _ScrollToHideNavbarState extends ConsumerState { + double _previousOffset = 0; + @override void initState() { super.initState(); @@ -31,86 +31,32 @@ class _ScrollToHideNavbarState extends ConsumerState { super.dispose(); } - double _previousOffset = 0; - bool _isScrollingDown = false; - final _scrollThreshold = 2.0; - final _directionChangeThreshold = 3.0; - bool _isOverscrollInProgress = false; - DateTime _lastDirectionChange = DateTime.now(); - DateTime _lastSlowScrollCheck = DateTime.now(); - void _scrollListener() { final navbarVisibilityNotifier = ref.read( navbarVisibilityProvider.notifier, ); - final scrollDirectionNotifier = ref.read(scrollDirectionProvider.notifier); - final ScrollPosition position = widget.controller.position; + final double currentOffset = position.pixels; final double maxScrollExtent = position.maxScrollExtent; - final bool isAtTop = currentOffset <= 0; - final bool isAtBottom = currentOffset >= maxScrollExtent; - if (isAtTop) { + if (currentOffset <= 0) { navbarVisibilityNotifier.show(); _previousOffset = 0; - _isOverscrollInProgress = false; return; } - final double scrollDelta = (currentOffset - _previousOffset).abs(); - - bool isOverScrolling = position.outOfRange; - - if (isOverScrolling && !_isOverscrollInProgress) { - _isOverscrollInProgress = true; - } - - if (!isOverScrolling && - _isOverscrollInProgress && - !isAtTop && - !isAtBottom) { - _isOverscrollInProgress = false; - } - - if (scrollDelta < _scrollThreshold) { + if (currentOffset >= maxScrollExtent) { _previousOffset = currentOffset; return; } - if (!_isOverscrollInProgress) { - bool newIsScrollingDown = currentOffset > _previousOffset; - final currentTime = DateTime.now(); - final timeSinceLastChange = currentTime - .difference(_lastDirectionChange) - .inMilliseconds; - final timeSinceLastSlowCheck = currentTime - .difference(_lastSlowScrollCheck) - .inMilliseconds; + final double scrollDelta = currentOffset - _previousOffset; - if (_isScrollingDown != newIsScrollingDown && - scrollDelta >= _directionChangeThreshold && - timeSinceLastChange > 100) { - _isScrollingDown = newIsScrollingDown; - _lastDirectionChange = currentTime; - - scrollDirectionNotifier.updateScrollDirection(currentOffset); - - if (newIsScrollingDown) { - navbarVisibilityNotifier.hide(); - } else { - navbarVisibilityNotifier.show(); - } - } else if (scrollDelta >= _scrollThreshold && - timeSinceLastSlowCheck > 150) { - _lastSlowScrollCheck = currentTime; - - if (newIsScrollingDown) { - navbarVisibilityNotifier.hide(); - } else if (!newIsScrollingDown && !isAtTop) { - navbarVisibilityNotifier.show(); - } - } + if (scrollDelta > 0) { + navbarVisibilityNotifier.hide(); + } else if (scrollDelta < 0) { + navbarVisibilityNotifier.show(); } _previousOffset = currentOffset; From 87083601a64daed9f28db9804e679f48706b52d7 Mon Sep 17 00:00:00 2001 From: Thonyk Date: Sat, 5 Jul 2025 23:56:31 +0200 Subject: [PATCH 083/473] feat: new async child --- lib/tools/ui/builders/async_child.dart | 225 ++++++++++++++++++++++++- 1 file changed, 222 insertions(+), 3 deletions(-) diff --git a/lib/tools/ui/builders/async_child.dart b/lib/tools/ui/builders/async_child.dart index 10e59412d6..1114ddf28d 100644 --- a/lib/tools/ui/builders/async_child.dart +++ b/lib/tools/ui/builders/async_child.dart @@ -1,10 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/tools/ui/widgets/loader.dart'; +import 'package:tuple/tuple.dart'; -class AsyncChild extends StatelessWidget { - final AsyncValue value; - final Widget Function(BuildContext context, T value) builder; +class AsyncChild

extends StatelessWidget { + final AsyncValue

value; + final Widget Function(BuildContext context, P value) builder; final Widget Function(Object? error, StackTrace? stack)? errorBuilder; final Widget Function(BuildContext context)? loadingBuilder; final Widget Function(BuildContext context, Widget child)? orElseBuilder; @@ -41,3 +42,221 @@ class AsyncChild extends StatelessWidget { ); } } + +Widget handleLoadingAndError( + List values, + BuildContext context, { + Widget Function(BuildContext context)? loadingBuilder, + Widget Function(Object? error, StackTrace? stack)? errorBuilder, + Widget Function(BuildContext context, Widget child)? orElseBuilder, + Color? loaderColor, +}) { + final nonNullOrElseBuilder = orElseBuilder ?? (context, child) => child; + final nonNullLoadingBuilder = + loadingBuilder ?? (context) => Loader(color: loaderColor); + final nonNullErrorBuilder = + errorBuilder ?? + (error, stack) => Center( + child: Text( + "${TextConstants.error}:$error", + style: TextStyle(color: loaderColor), + ), + ); + if (values.any((test) => test.hasError)) { + final firstError = values.firstWhere((test) => test.hasError); + final error = firstError.error; + final stackTrace = firstError.stackTrace; + return nonNullOrElseBuilder( + context, + nonNullErrorBuilder(error, stackTrace), + ); + } + if (values.any((test) => test.isLoading)) { + return nonNullOrElseBuilder(context, nonNullLoadingBuilder(context)); + } + return nonNullOrElseBuilder(context, const SizedBox.shrink()); +} + +class Async2Child extends StatelessWidget { + final Tuple2, AsyncValue> values; + final Widget Function(BuildContext context, P values1, Q values2) builder; + final Widget Function(Object? error, StackTrace? stack)? errorBuilder; + final Widget Function(BuildContext context)? loadingBuilder; + final Widget Function(BuildContext context, Widget child)? orElseBuilder; + final Color? loaderColor; + const Async2Child({ + super.key, + required this.values, + required this.builder, + this.errorBuilder, + this.loaderColor, + this.orElseBuilder, + this.loadingBuilder, + }); + @override + Widget build(BuildContext context) { + List listValues = [values.item1, values.item2]; + if (listValues.any((test) => test.hasError) || + listValues.any((test) => test.isLoading)) { + return handleLoadingAndError( + listValues, + context, + loadingBuilder: loadingBuilder, + errorBuilder: errorBuilder, + orElseBuilder: orElseBuilder, + loaderColor: loaderColor, + ); + } + return builder(context, listValues[0].value as P, listValues[1].value as Q); + } +} + +class Async3Child extends StatelessWidget { + final Tuple3, AsyncValue, AsyncValue> values; + final Widget Function(BuildContext context, P values1, Q values2, R value3) + builder; + final Widget Function(Object? error, StackTrace? stack)? errorBuilder; + final Widget Function(BuildContext context)? loadingBuilder; + final Widget Function(BuildContext context, Widget child)? orElseBuilder; + final Color? loaderColor; + const Async3Child({ + super.key, + required this.values, + required this.builder, + this.errorBuilder, + this.loaderColor, + this.orElseBuilder, + this.loadingBuilder, + }); + @override + Widget build(BuildContext context) { + List listValues = [values.item1, values.item2, values.item3]; + if (listValues.any((test) => test.hasError) || + listValues.any((test) => test.isLoading)) { + return handleLoadingAndError( + listValues, + context, + loadingBuilder: loadingBuilder, + errorBuilder: errorBuilder, + orElseBuilder: orElseBuilder, + loaderColor: loaderColor, + ); + } + return builder( + context, + listValues[0].value as P, + listValues[1].value as Q, + listValues[2].value as R, + ); + } +} + +class Async4Child extends StatelessWidget { + final Tuple4, AsyncValue, AsyncValue, AsyncValue> + values; + final Widget Function( + BuildContext context, + P values1, + Q value2, + R values3, + S value4, + ) + builder; + final Widget Function(Object? error, StackTrace? stack)? errorBuilder; + final Widget Function(BuildContext context)? loadingBuilder; + final Widget Function(BuildContext context, Widget child)? orElseBuilder; + final Color? loaderColor; + const Async4Child({ + super.key, + required this.values, + required this.builder, + this.errorBuilder, + this.loaderColor, + this.orElseBuilder, + this.loadingBuilder, + }); + @override + Widget build(BuildContext context) { + List listValues = [values.item1, values.item2, values.item3]; + if (listValues.any((test) => test.hasError) || + listValues.any((test) => test.isLoading)) { + return handleLoadingAndError( + listValues, + context, + loadingBuilder: loadingBuilder, + errorBuilder: errorBuilder, + orElseBuilder: orElseBuilder, + loaderColor: loaderColor, + ); + } + return builder( + context, + listValues[0].value as P, + listValues[1].value as Q, + listValues[2].value as R, + listValues[3].value as S, + ); + } +} + +class Async5Child extends StatelessWidget { + final Tuple5< + AsyncValue

, + AsyncValue, + AsyncValue, + AsyncValue, + AsyncValue + > + values; + final Widget Function( + BuildContext context, + P values1, + Q value2, + R values3, + S value4, + T value5, + ) + builder; + final Widget Function(Object? error, StackTrace? stack)? errorBuilder; + final Widget Function(BuildContext context)? loadingBuilder; + final Widget Function(BuildContext context, Widget child)? orElseBuilder; + final Color? loaderColor; + const Async5Child({ + super.key, + required this.values, + required this.builder, + this.errorBuilder, + this.loaderColor, + this.orElseBuilder, + this.loadingBuilder, + }); + @override + Widget build(BuildContext context) { + List listValues = [ + values.item1, + values.item2, + values.item3, + values.item4, + values.item5, + ]; + if (listValues.any((test) => test.hasError) || + listValues.any((test) => test.isLoading)) { + return handleLoadingAndError( + listValues, + context, + loadingBuilder: loadingBuilder, + errorBuilder: errorBuilder, + orElseBuilder: orElseBuilder, + loaderColor: loaderColor, + ); + } + return builder( + context, + listValues[0].value as P, + listValues[1].value as Q, + listValues[2].value as R, + listValues[3].value as S, + listValues[4].value as T, + ); + } +} From 9504039c7e386718f6c2725a3ad0dfc0b384b830 Mon Sep 17 00:00:00 2001 From: Thonyk Date: Sat, 5 Jul 2025 23:56:31 +0200 Subject: [PATCH 084/473] feat: support new association groupements ^ Conflicts: ^ lib/phonebook/ui/phonebook.dart ^ Conflicts: ^ lib/phonebook/ui/pages/admin_page/admin_page.dart ^ lib/phonebook/ui/pages/association_creation_page/association_creation_page.dart ^ lib/phonebook/ui/pages/association_editor_page/association_information_editor.dart ^ lib/phonebook/ui/pages/main_page/main_page.dart --- lib/phonebook/class/association.dart | 16 ++-- .../class/association_groupement.dart | 26 +++++ lib/phonebook/class/association_kinds.dart | 23 ----- .../association_filtered_list_provider.dart | 17 ++-- .../association_groupement_list_provider.dart | 37 ++++++++ .../association_groupement_provider.dart | 22 +++++ .../providers/association_kind_provider.dart | 14 --- .../providers/association_kinds_provider.dart | 37 -------- .../association_groupement_repository.dart | 39 ++++++++ .../repositories/association_repository.dart | 5 - lib/phonebook/tools/function.dart | 20 ++-- .../ui/components/groupement_bar.dart | 70 ++++++++++++++ lib/phonebook/ui/components/kinds_bar.dart | 59 ------------ .../ui/pages/admin_page/admin_page.dart | 95 +++++++++++-------- .../admin_page/editable_association_card.dart | 5 +- .../association_creation_page.dart | 11 ++- .../association_editor_page.dart | 9 +- .../association_information_editor.dart | 11 ++- .../association_page/association_page.dart | 7 +- .../ui/pages/main_page/association_card.dart | 5 +- .../ui/pages/main_page/main_page.dart | 42 +++++--- 21 files changed, 335 insertions(+), 235 deletions(-) create mode 100644 lib/phonebook/class/association_groupement.dart delete mode 100644 lib/phonebook/class/association_kinds.dart create mode 100644 lib/phonebook/providers/association_groupement_list_provider.dart create mode 100644 lib/phonebook/providers/association_groupement_provider.dart delete mode 100644 lib/phonebook/providers/association_kind_provider.dart delete mode 100644 lib/phonebook/providers/association_kinds_provider.dart create mode 100644 lib/phonebook/repositories/association_groupement_repository.dart create mode 100644 lib/phonebook/ui/components/groupement_bar.dart delete mode 100644 lib/phonebook/ui/components/kinds_bar.dart diff --git a/lib/phonebook/class/association.dart b/lib/phonebook/class/association.dart index fca496dbdb..fe47c1b045 100644 --- a/lib/phonebook/class/association.dart +++ b/lib/phonebook/class/association.dart @@ -3,7 +3,7 @@ class Association { required this.id, required this.name, required this.description, - required this.kind, + required this.groupementId, required this.mandateYear, required this.deactivated, required this.associatedGroups, @@ -12,7 +12,7 @@ class Association { late final String id; late final String name; late final String description; - late final String kind; + late final String groupementId; late final int mandateYear; late final bool deactivated; late final List associatedGroups; @@ -21,7 +21,7 @@ class Association { id = json['id']; name = json['name']; description = json['description']; - kind = json['kind']; + groupementId = json['groupement_id']; mandateYear = json['mandate_year']; deactivated = json['deactivated']; associatedGroups = List.from(json['associated_groups']); @@ -32,7 +32,7 @@ class Association { 'id': id, 'name': name, 'description': description, - 'kind': kind, + 'groupement_id': groupementId, 'mandate_year': mandateYear, 'deactivated': deactivated, 'associated_groups': associatedGroups, @@ -44,7 +44,7 @@ class Association { String? id, String? name, String? description, - String? kind, + String? groupementId, int? mandateYear, bool? deactivated, List? associatedGroups, @@ -53,7 +53,7 @@ class Association { id: id ?? this.id, name: name ?? this.name, description: description ?? this.description, - kind: kind ?? this.kind, + groupementId: groupementId ?? this.groupementId, mandateYear: mandateYear ?? this.mandateYear, deactivated: deactivated ?? this.deactivated, associatedGroups: associatedGroups ?? this.associatedGroups, @@ -64,7 +64,7 @@ class Association { id = ""; name = ""; description = ""; - kind = ""; + groupementId = ""; mandateYear = 0; deactivated = false; associatedGroups = []; @@ -76,6 +76,6 @@ class Association { @override String toString() { - return "Association(Nom : $name, id : $id, description : $description, kind : $kind, mandate_year : $mandateYear, deactivated : $deactivated, associated_groups : $associatedGroups)"; + return "Association(Nom : $name, id : $id, description : $description, groupement_id : $groupementId, mandate_year : $mandateYear, deactivated : $deactivated, associated_groups : $associatedGroups)"; } } diff --git a/lib/phonebook/class/association_groupement.dart b/lib/phonebook/class/association_groupement.dart new file mode 100644 index 0000000000..67b84e765e --- /dev/null +++ b/lib/phonebook/class/association_groupement.dart @@ -0,0 +1,26 @@ +class AssociationGroupement { + AssociationGroupement({required this.id, required this.name}); + + late final String id; + late final String name; + + AssociationGroupement.fromJson(Map json) { + id = json['id']; + name = json['name']; + } + + Map toJson() { + final data = {'id': id, 'name': name}; + return data; + } + + AssociationGroupement.empty() { + id = ""; + name = ""; + } + + @override + String toString() { + return 'AssociationGroupement(kinds: $id, name: $name)'; + } +} diff --git a/lib/phonebook/class/association_kinds.dart b/lib/phonebook/class/association_kinds.dart deleted file mode 100644 index 0a7e60e8f2..0000000000 --- a/lib/phonebook/class/association_kinds.dart +++ /dev/null @@ -1,23 +0,0 @@ -class AssociationKinds { - AssociationKinds({required this.kinds}); - - late final List kinds; - - AssociationKinds.fromJson(Map json) { - kinds = json['kinds'].map((dynamic tag) => tag.toString()).toList(); - } - - Map toJson() { - final data = {'kinds': kinds}; - return data; - } - - AssociationKinds empty() { - return AssociationKinds(kinds: []); - } - - @override - String toString() { - return 'AssociationKinds(kinds: $kinds)'; - } -} diff --git a/lib/phonebook/providers/association_filtered_list_provider.dart b/lib/phonebook/providers/association_filtered_list_provider.dart index 1b5e931038..9d6ee198b3 100644 --- a/lib/phonebook/providers/association_filtered_list_provider.dart +++ b/lib/phonebook/providers/association_filtered_list_provider.dart @@ -1,7 +1,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/phonebook/class/association.dart'; -import 'package:titan/phonebook/providers/association_kind_provider.dart'; -import 'package:titan/phonebook/providers/association_kinds_provider.dart'; +import 'package:titan/phonebook/providers/association_groupement_provider.dart'; +import 'package:titan/phonebook/providers/association_groupement_list_provider.dart'; import 'package:titan/phonebook/providers/association_list_provider.dart'; import 'package:titan/phonebook/providers/research_filter_provider.dart'; import 'package:titan/phonebook/tools/function.dart'; @@ -9,8 +9,8 @@ import 'package:diacritic/diacritic.dart'; final associationFilteredListProvider = Provider>((ref) { final associationsProvider = ref.watch(associationListProvider); - final associationKinds = ref.watch(associationKindsProvider); - final kindFilter = ref.watch(associationKindProvider); + final associationGroupements = ref.watch(associationGroupementListProvider); + final associationGroupement = ref.watch(associationGroupementProvider); final searchFilter = ref.watch(filterProvider); return associationsProvider.maybeWhen( data: (associations) { @@ -21,12 +21,15 @@ final associationFilteredListProvider = Provider>((ref) { ).contains(removeDiacritics(searchFilter.toLowerCase())), ) .toList(); - if (kindFilter != "") { + if (associationGroupement.id != "") { filteredAssociations = filteredAssociations - .where((association) => association.kind == kindFilter) + .where( + (association) => + association.groupementId == associationGroupement.id, + ) .toList(); } - return associationKinds.maybeWhen( + return associationGroupements.maybeWhen( data: (kinds) => sortedAssociationByKind(filteredAssociations, kinds), orElse: () => filteredAssociations, ); diff --git a/lib/phonebook/providers/association_groupement_list_provider.dart b/lib/phonebook/providers/association_groupement_list_provider.dart new file mode 100644 index 0000000000..0ee38d32d2 --- /dev/null +++ b/lib/phonebook/providers/association_groupement_list_provider.dart @@ -0,0 +1,37 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/auth/providers/openid_provider.dart'; +import 'package:titan/phonebook/class/association_groupement.dart'; +import 'package:titan/phonebook/repositories/association_groupement_repository.dart'; +import 'package:titan/tools/providers/list_notifier.dart'; +import 'package:titan/tools/token_expire_wrapper.dart'; + +class AssociationGroupementListNotifier + extends ListNotifier { + final AssociationGroupementRepository associationGroupementRepository = + AssociationGroupementRepository(); + AssociationGroupementListNotifier({required String token}) + : super(const AsyncValue.loading()) { + associationGroupementRepository.setToken(token); + } + + Future>> + loadAssociationGroupement() async { + return await loadList( + associationGroupementRepository.getAssociationGroupements, + ); + } +} + +final associationGroupementListProvider = + StateNotifierProvider< + AssociationGroupementListNotifier, + AsyncValue> + >((ref) { + final token = ref.watch(tokenProvider); + AssociationGroupementListNotifier notifier = + AssociationGroupementListNotifier(token: token); + tokenExpireWrapperAuth(ref, () async { + await notifier.loadAssociationGroupement(); + }); + return notifier; + }); diff --git a/lib/phonebook/providers/association_groupement_provider.dart b/lib/phonebook/providers/association_groupement_provider.dart new file mode 100644 index 0000000000..8b2f1b88fc --- /dev/null +++ b/lib/phonebook/providers/association_groupement_provider.dart @@ -0,0 +1,22 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/phonebook/class/association_groupement.dart'; + +final associationGroupementProvider = + StateNotifierProvider( + (ref) { + return AssociationGroupementNotifier(); + }, + ); + +class AssociationGroupementNotifier + extends StateNotifier { + AssociationGroupementNotifier() : super(AssociationGroupement.empty()); + + void setAssociationGroupement(AssociationGroupement i) { + state = i; + } + + void resetAssociationGroupement() { + state = AssociationGroupement.empty(); + } +} diff --git a/lib/phonebook/providers/association_kind_provider.dart b/lib/phonebook/providers/association_kind_provider.dart deleted file mode 100644 index 26215b091f..0000000000 --- a/lib/phonebook/providers/association_kind_provider.dart +++ /dev/null @@ -1,14 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; - -final associationKindProvider = - StateNotifierProvider((ref) { - return AssociationKindNotifier(); - }); - -class AssociationKindNotifier extends StateNotifier { - AssociationKindNotifier() : super(""); - - void setKind(String i) { - state = i; - } -} diff --git a/lib/phonebook/providers/association_kinds_provider.dart b/lib/phonebook/providers/association_kinds_provider.dart deleted file mode 100644 index 78c233adb0..0000000000 --- a/lib/phonebook/providers/association_kinds_provider.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/auth/providers/openid_provider.dart'; -import 'package:titan/phonebook/class/association_kinds.dart'; -import 'package:titan/phonebook/repositories/association_repository.dart'; -import 'package:titan/tools/providers/single_notifier.dart'; -import 'package:titan/tools/token_expire_wrapper.dart'; - -class AssociationKindsNotifier extends SingleNotifier { - final AssociationRepository associationRepository = AssociationRepository(); - AssociationKindsNotifier({required String token}) - : super(const AsyncValue.loading()) { - associationRepository.setToken(token); - } - - void setKind(AssociationKinds i) { - state = AsyncValue.data(i); - } - - Future> loadAssociationKinds() async { - return await load(associationRepository.getAssociationKinds); - } -} - -final associationKindsProvider = - StateNotifierProvider< - AssociationKindsNotifier, - AsyncValue - >((ref) { - final token = ref.watch(tokenProvider); - AssociationKindsNotifier notifier = AssociationKindsNotifier( - token: token, - ); - tokenExpireWrapperAuth(ref, () async { - await notifier.loadAssociationKinds(); - }); - return notifier; - }); diff --git a/lib/phonebook/repositories/association_groupement_repository.dart b/lib/phonebook/repositories/association_groupement_repository.dart new file mode 100644 index 0000000000..e44e1c4546 --- /dev/null +++ b/lib/phonebook/repositories/association_groupement_repository.dart @@ -0,0 +1,39 @@ +import 'package:titan/phonebook/class/association_groupement.dart'; +import 'package:titan/tools/repository/repository.dart'; + +class AssociationGroupementRepository extends Repository { + @override + // ignore: overridden_fields + final ext = "phonebook/groupements/"; + + Future> getAssociationGroupements() async { + return List.from( + (await getList()).map((x) => AssociationGroupement.fromJson(x)), + ); + } + + Future getAssociationGroupementById(String id) async { + return AssociationGroupement.fromJson(await getOne(id)); + } + + Future updateAssociationGroupement( + AssociationGroupement associationGroupement, + ) async { + return await update( + associationGroupement.toJson(), + associationGroupement.id, + ); + } + + Future createAssociationGroupement( + AssociationGroupement associationGroupement, + ) async { + return AssociationGroupement.fromJson( + await create(associationGroupement.toJson()), + ); + } + + Future deleteAssociationGroupement(String id) async { + return await delete(id); + } +} diff --git a/lib/phonebook/repositories/association_repository.dart b/lib/phonebook/repositories/association_repository.dart index 349f817f25..bdfa337542 100644 --- a/lib/phonebook/repositories/association_repository.dart +++ b/lib/phonebook/repositories/association_repository.dart @@ -1,5 +1,4 @@ import 'package:titan/phonebook/class/association.dart'; -import 'package:titan/phonebook/class/association_kinds.dart'; import 'package:titan/tools/repository/repository.dart'; class AssociationRepository extends Repository { @@ -25,10 +24,6 @@ class AssociationRepository extends Repository { return Association.fromJson(await create(association.toJson())); } - Future getAssociationKinds() async { - return AssociationKinds.fromJson(await getOne("kinds")); - } - Future deactivateAssociation(Association association) async { return await update(null, association.id, suffix: "/deactivate"); } diff --git a/lib/phonebook/tools/function.dart b/lib/phonebook/tools/function.dart index 5c4653d02c..fed88cff5e 100644 --- a/lib/phonebook/tools/function.dart +++ b/lib/phonebook/tools/function.dart @@ -2,7 +2,7 @@ import 'package:diacritic/diacritic.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/phonebook/class/association.dart'; -import 'package:titan/phonebook/class/association_kinds.dart'; +import 'package:titan/phonebook/class/association_groupement.dart'; import 'package:titan/phonebook/class/complete_member.dart'; import 'package:titan/phonebook/class/membership.dart'; import 'package:titan/phonebook/providers/roles_tags_provider.dart'; @@ -26,25 +26,23 @@ List sortedMembers( List sortedAssociationByKind( List associations, - AssociationKinds kinds, + List groupements, ) { - List sorted = []; - List> sortedByKind = List.generate( - kinds.kinds.length, - (index) => [], - ); + Map> sortedByGroupement = { + for (var groupement in groupements) groupement.id: [], + }; for (Association association in associations) { - sortedByKind[kinds.kinds.indexOf(association.kind)].add(association); + sortedByGroupement[association.groupementId]!.add(association); } - for (List list in sortedByKind) { + for (List list in sortedByGroupement.values) { list.sort( (a, b) => removeDiacritics( a.name, ).toLowerCase().compareTo(removeDiacritics(b.name).toLowerCase()), ); - sorted.addAll(list); } - return sorted; + // Flatten the sorted map values into a single list + return sortedByGroupement.values.expand((list) => list).toList(); } Color getColorFromTagList(WidgetRef ref, List tags) { diff --git a/lib/phonebook/ui/components/groupement_bar.dart b/lib/phonebook/ui/components/groupement_bar.dart new file mode 100644 index 0000000000..db667b6606 --- /dev/null +++ b/lib/phonebook/ui/components/groupement_bar.dart @@ -0,0 +1,70 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/phonebook/providers/association_groupement_provider.dart'; +import 'package:titan/phonebook/providers/association_groupement_list_provider.dart'; +import 'package:titan/tools/ui/builders/async_child.dart'; +import 'package:titan/tools/ui/layouts/item_chip.dart'; + +class KindsBar extends HookConsumerWidget { + KindsBar({super.key}); + final dataKey = GlobalKey(); + @override + Widget build(BuildContext context, WidgetRef ref) { + final associationGroupement = ref.watch(associationGroupementProvider); + final associationGroupementNotifier = ref.watch( + associationGroupementProvider.notifier, + ); + final associationGroupementList = ref.watch( + associationGroupementListProvider, + ); + useEffect(() { + Future(() { + if (associationGroupement.id != "") { + Scrollable.ensureVisible( + dataKey.currentContext!, + duration: const Duration(milliseconds: 500), + alignment: 0.5, + ); + } + }); + return; + }, [dataKey]); + return AsyncChild( + value: associationGroupementList, + builder: (context, associationGroupements) => + associationGroupements.length > 1 + ? SizedBox( + width: MediaQuery.of(context).size.width, + height: 40, + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: associationGroupements.length, + itemBuilder: (context, index) { + final item = associationGroupements[index]; + final selected = associationGroupement == item; + return ItemChip( + key: selected ? dataKey : null, + onTap: () { + !selected + ? associationGroupementNotifier + .setAssociationGroupement(item) + : associationGroupementNotifier + .resetAssociationGroupement(); + }, + selected: selected, + child: Text( + item.name, + style: TextStyle( + color: selected ? Colors.white : Colors.black, + fontWeight: FontWeight.bold, + ), + ), + ); + }, + ), + ) + : SizedBox(height: 0), + ); + } +} diff --git a/lib/phonebook/ui/components/kinds_bar.dart b/lib/phonebook/ui/components/kinds_bar.dart deleted file mode 100644 index 7bdc8891c8..0000000000 --- a/lib/phonebook/ui/components/kinds_bar.dart +++ /dev/null @@ -1,59 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/phonebook/providers/association_kind_provider.dart'; -import 'package:titan/phonebook/providers/association_kinds_provider.dart'; -import 'package:titan/tools/ui/builders/async_child.dart'; -import 'package:titan/tools/ui/layouts/item_chip.dart'; - -class KindsBar extends HookConsumerWidget { - KindsBar({super.key}); - final dataKey = GlobalKey(); - @override - Widget build(BuildContext context, WidgetRef ref) { - final kind = ref.watch(associationKindProvider); - final kindNotifier = ref.watch(associationKindProvider.notifier); - final associationKinds = ref.watch(associationKindsProvider); - useEffect(() { - Future(() { - if (kind != "") { - Scrollable.ensureVisible( - dataKey.currentContext!, - duration: const Duration(milliseconds: 500), - alignment: 0.5, - ); - } - }); - return; - }, [dataKey]); - return AsyncChild( - value: associationKinds, - builder: (context, kinds) => SizedBox( - width: MediaQuery.of(context).size.width, - height: 40, - child: ListView.builder( - scrollDirection: Axis.horizontal, - itemCount: kinds.kinds.length, - itemBuilder: (context, index) { - final item = kinds.kinds[index]; - final selected = kind == item; - return ItemChip( - key: selected ? dataKey : null, - onTap: () { - kindNotifier.setKind(!selected ? item : ""); - }, - selected: selected, - child: Text( - item, - style: TextStyle( - color: selected ? Colors.white : Colors.black, - fontWeight: FontWeight.bold, - ), - ), - ); - }, - ), - ), - ); - } -} diff --git a/lib/phonebook/ui/pages/admin_page/admin_page.dart b/lib/phonebook/ui/pages/admin_page/admin_page.dart index e4637bc39a..79d5babf35 100644 --- a/lib/phonebook/ui/pages/admin_page/admin_page.dart +++ b/lib/phonebook/ui/pages/admin_page/admin_page.dart @@ -2,13 +2,15 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/phonebook/providers/association_filtered_list_provider.dart'; -import 'package:titan/phonebook/providers/association_kind_provider.dart'; +import 'package:titan/phonebook/providers/association_groupement_list_provider.dart'; +import 'package:titan/phonebook/providers/association_groupement_provider.dart'; import 'package:titan/phonebook/providers/association_list_provider.dart'; import 'package:titan/phonebook/providers/association_provider.dart'; import 'package:titan/phonebook/providers/phonebook_admin_provider.dart'; import 'package:titan/phonebook/providers/roles_tags_provider.dart'; import 'package:titan/phonebook/router.dart'; -import 'package:titan/phonebook/ui/components/kinds_bar.dart'; +import 'package:titan/phonebook/tools/constants.dart'; +import 'package:titan/phonebook/ui/components/groupement_bar.dart'; import 'package:titan/phonebook/ui/phonebook.dart'; import 'package:titan/phonebook/ui/pages/admin_page/association_research_bar.dart'; import 'package:titan/phonebook/ui/pages/admin_page/editable_association_card.dart'; @@ -19,6 +21,7 @@ import 'package:titan/tools/ui/layouts/card_layout.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:tuple/tuple.dart'; import 'package:titan/l10n/app_localizations.dart'; class AdminPage extends HookConsumerWidget { @@ -27,7 +30,12 @@ class AdminPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final associationNotifier = ref.watch(associationProvider.notifier); - final kindNotifier = ref.watch(associationKindProvider.notifier); + final associationGroupementsNotifier = ref.watch( + associationGroupementProvider.notifier, + ); + final associationGroupementList = ref.watch( + associationGroupementListProvider, + ); final associationListNotifier = ref.watch(associationListProvider.notifier); final associationList = ref.watch(associationListProvider); final associationFilteredList = ref.watch(associationFilteredListProvider); @@ -50,39 +58,46 @@ class AdminPage extends HookConsumerWidget { child: AssociationResearchBar(), ), const SizedBox(height: 10), - AsyncChild( - value: associationList, - builder: (context, associations) { - return Column( - children: [ - KindsBar(), - GestureDetector( - onTap: isPhonebookAdmin - ? () { - QR.to( - PhonebookRouter.root + - PhonebookRouter.admin + - PhonebookRouter.createAssociaiton, - ); - } - : null, - child: CardLayout( - margin: const EdgeInsets.only( - bottom: 10, - top: 20, - left: 40, - right: 40, - ), - width: double.infinity, - height: 100, - color: isPhonebookAdmin - ? Colors.white - : ColorConstants.deactivated2, - child: Center( - child: HeroIcon( - HeroIcons.plus, - size: 40, - color: Colors.grey.shade500, + Async2Child( + values: Tuple2(associationList, associationGroupementList), + builder: + ( + context, + syncAssociationList, + syncAssociationGroupementList, + ) { + return Column( + children: [ + KindsBar(), + GestureDetector( + onTap: isPhonebookAdmin + ? () { + QR.to( + PhonebookRouter.root + + PhonebookRouter.admin + + PhonebookRouter.createAssociaiton, + ); + } + : null, + child: CardLayout( + margin: const EdgeInsets.only( + bottom: 10, + top: 20, + left: 40, + right: 40, + ), + width: double.infinity, + height: 100, + color: isPhonebookAdmin + ? Colors.white + : ColorConstants.deactivated2, + child: Center( + child: HeroIcon( + HeroIcons.plus, + size: 40, + color: Colors.grey.shade500, + ), + ), ), ), ), @@ -194,11 +209,9 @@ class AdminPage extends HookConsumerWidget { ); }, ), - ), - ), - ], - ); - }, + ], + ); + }, ), ], ), diff --git a/lib/phonebook/ui/pages/admin_page/editable_association_card.dart b/lib/phonebook/ui/pages/admin_page/editable_association_card.dart index e2fe0d5b2a..82091c9714 100644 --- a/lib/phonebook/ui/pages/admin_page/editable_association_card.dart +++ b/lib/phonebook/ui/pages/admin_page/editable_association_card.dart @@ -1,17 +1,20 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/phonebook/class/association.dart'; +import 'package:titan/phonebook/class/association_groupement.dart'; import 'package:titan/phonebook/ui/pages/admin_page/delete_button.dart'; import 'package:titan/phonebook/ui/pages/admin_page/edition_button.dart'; class EditableAssociationCard extends HookConsumerWidget { final Association association; + final AssociationGroupement associationGroupement; final bool isPhonebookAdmin; final void Function() onEdit; final Future Function() onDelete; const EditableAssociationCard({ super.key, required this.association, + required this.associationGroupement, required this.isPhonebookAdmin, required this.onEdit, required this.onDelete, @@ -49,7 +52,7 @@ class EditableAssociationCard extends HookConsumerWidget { ), Expanded( child: Text( - association.kind, + associationGroupement.name, style: const TextStyle( color: Colors.black, fontSize: 16, diff --git a/lib/phonebook/ui/pages/association_creation_page/association_creation_page.dart b/lib/phonebook/ui/pages/association_creation_page/association_creation_page.dart index 722952ddae..4d301e1323 100644 --- a/lib/phonebook/ui/pages/association_creation_page/association_creation_page.dart +++ b/lib/phonebook/ui/pages/association_creation_page/association_creation_page.dart @@ -2,11 +2,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/phonebook/class/association.dart'; -import 'package:titan/phonebook/providers/association_kind_provider.dart'; +import 'package:titan/phonebook/providers/association_groupement_provider.dart'; import 'package:titan/phonebook/providers/association_list_provider.dart'; import 'package:titan/phonebook/providers/association_provider.dart'; import 'package:titan/phonebook/router.dart'; -import 'package:titan/phonebook/ui/components/kinds_bar.dart'; +import 'package:titan/phonebook/tools/constants.dart'; +import 'package:titan/phonebook/ui/components/groupement_bar.dart'; import 'package:titan/phonebook/ui/phonebook.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; @@ -29,7 +30,7 @@ class AssociationCreationPage extends HookConsumerWidget { final associationListNotifier = ref.watch(associationListProvider.notifier); final associations = ref.watch(associationListProvider); final associationNotifier = ref.watch(associationProvider.notifier); - final kind = ref.watch(associationKindProvider); + final associationGroupement = ref.watch(associationGroupementProvider); void displayToastWithContext(TypeMsg type, String msg) { displayToast(context, type, msg); } @@ -95,7 +96,7 @@ class AssociationCreationPage extends HookConsumerWidget { ); return; } - if (kind == '') { + if (associationGroupement == '') { displayToastWithContext( TypeMsg.error, AppLocalizations.of( @@ -116,7 +117,7 @@ class AssociationCreationPage extends HookConsumerWidget { Association.empty().copyWith( name: name.text, description: description.text, - kind: kind, + groupementId: associationGroupement.id, mandateYear: DateTime.now().year, ), ); diff --git a/lib/phonebook/ui/pages/association_editor_page/association_editor_page.dart b/lib/phonebook/ui/pages/association_editor_page/association_editor_page.dart index 641db46f21..20eb93a712 100644 --- a/lib/phonebook/ui/pages/association_editor_page/association_editor_page.dart +++ b/lib/phonebook/ui/pages/association_editor_page/association_editor_page.dart @@ -3,7 +3,7 @@ import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/phonebook/class/complete_member.dart'; import 'package:titan/phonebook/class/membership.dart'; -import 'package:titan/phonebook/providers/association_kind_provider.dart'; +import 'package:titan/phonebook/providers/association_groupement_provider.dart'; import 'package:titan/phonebook/providers/association_list_provider.dart'; import 'package:titan/phonebook/providers/association_member_list_provider.dart'; import 'package:titan/phonebook/providers/association_member_sorted_list_provider.dart'; @@ -53,7 +53,9 @@ class AssociationEditorPage extends HookConsumerWidget { final memberRoleTagsNotifier = ref.watch(memberRoleTagsProvider.notifier); final isPhonebookAdmin = ref.watch(isPhonebookAdminProvider); final isAssociationPresident = ref.watch(isAssociationPresidentProvider); - final kindNotifier = ref.watch(associationKindProvider.notifier); + final associationGroupementNotifier = ref.watch( + associationGroupementProvider.notifier, + ); void displayToastWithContext(TypeMsg type, String msg) { displayToast(context, type, msg); @@ -301,7 +303,8 @@ class AssociationEditorPage extends HookConsumerWidget { if (QR.currentPath.contains( PhonebookRouter.associationDetail, )) { - kindNotifier.setKind(""); + associationGroupementNotifier + .resetAssociationGroupement(); QR.to( PhonebookRouter.root + PhonebookRouter.associationDetail, diff --git a/lib/phonebook/ui/pages/association_editor_page/association_information_editor.dart b/lib/phonebook/ui/pages/association_editor_page/association_information_editor.dart index 941cf3b9d3..dfbba3ceca 100644 --- a/lib/phonebook/ui/pages/association_editor_page/association_information_editor.dart +++ b/lib/phonebook/ui/pages/association_editor_page/association_information_editor.dart @@ -5,11 +5,12 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/class/simple_group.dart'; import 'package:titan/admin/providers/group_list_provider.dart'; import 'package:titan/admin/providers/is_admin_provider.dart'; -import 'package:titan/phonebook/providers/association_kind_provider.dart'; +import 'package:titan/phonebook/providers/association_groupement_provider.dart'; import 'package:titan/phonebook/providers/association_list_provider.dart'; import 'package:titan/phonebook/providers/association_provider.dart'; import 'package:titan/phonebook/providers/phonebook_admin_provider.dart'; -import 'package:titan/phonebook/ui/components/kinds_bar.dart'; +import 'package:titan/phonebook/tools/constants.dart'; +import 'package:titan/phonebook/ui/components/groupement_bar.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; @@ -27,7 +28,7 @@ class AssociationInformationEditor extends HookConsumerWidget { displayToast(context, type, msg); } - final kind = ref.watch(associationKindProvider); + final kind = ref.watch(associationGroupementProvider); final association = ref.watch(associationProvider); final name = useTextEditingController(text: association.name); final description = useTextEditingController(text: association.description); @@ -156,7 +157,7 @@ class AssociationInformationEditor extends HookConsumerWidget { if (!key.currentState!.validate()) { return; } - if (kind == '') { + if (kind.id == '') { displayToastWithContext( TypeMsg.error, AppLocalizations.of( @@ -177,7 +178,7 @@ class AssociationInformationEditor extends HookConsumerWidget { association.copyWith( name: name.text, description: description.text, - kind: kind, + groupementId: kind.id, ), ); if (value) { diff --git a/lib/phonebook/ui/pages/association_page/association_page.dart b/lib/phonebook/ui/pages/association_page/association_page.dart index b2896ee89b..55b688db0b 100644 --- a/lib/phonebook/ui/pages/association_page/association_page.dart +++ b/lib/phonebook/ui/pages/association_page/association_page.dart @@ -2,7 +2,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/phonebook/providers/association_kind_provider.dart'; +import 'package:titan/phonebook/providers/association_groupement_provider.dart'; import 'package:titan/phonebook/providers/association_member_sorted_list_provider.dart'; import 'package:titan/phonebook/providers/association_picture_provider.dart'; import 'package:titan/phonebook/providers/association_provider.dart'; @@ -34,7 +34,7 @@ class AssociationPage extends HookConsumerWidget { associationPictureProvider.notifier, ); final isPresident = ref.watch(isAssociationPresidentProvider); - final kindNotifier = ref.watch(associationKindProvider.notifier); + final associationGroupement = ref.watch(associationGroupementProvider); return PhonebookTemplate( child: Refresher( @@ -60,7 +60,7 @@ class AssociationPage extends HookConsumerWidget { ), const SizedBox(height: 10), Text( - association.kind, + associationGroupement.name, style: const TextStyle(fontSize: 20, color: Colors.black), ), const SizedBox(height: 10), @@ -103,7 +103,6 @@ class AssociationPage extends HookConsumerWidget { right: 20, child: GestureDetector( onTap: () { - kindNotifier.setKind(association.kind); QR.to( PhonebookRouter.root + PhonebookRouter.associationDetail + diff --git a/lib/phonebook/ui/pages/main_page/association_card.dart b/lib/phonebook/ui/pages/main_page/association_card.dart index d263cb68ee..1ab44faecd 100644 --- a/lib/phonebook/ui/pages/main_page/association_card.dart +++ b/lib/phonebook/ui/pages/main_page/association_card.dart @@ -1,16 +1,19 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/phonebook/class/association.dart'; +import 'package:titan/phonebook/class/association_groupement.dart'; import 'package:titan/tools/ui/layouts/card_layout.dart'; class AssociationCard extends HookConsumerWidget { const AssociationCard({ super.key, required this.association, + required this.groupement, required this.onClicked, }); final Association association; + final AssociationGroupement groupement; final VoidCallback onClicked; @override @@ -33,7 +36,7 @@ class AssociationCard extends HookConsumerWidget { ), ), ), - Text(association.kind), + Text(groupement.name), ], ), ), diff --git a/lib/phonebook/ui/pages/main_page/main_page.dart b/lib/phonebook/ui/pages/main_page/main_page.dart index 00df2b5cf6..691c912298 100644 --- a/lib/phonebook/ui/pages/main_page/main_page.dart +++ b/lib/phonebook/ui/pages/main_page/main_page.dart @@ -2,13 +2,14 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/providers/is_admin_provider.dart'; import 'package:titan/phonebook/providers/association_filtered_list_provider.dart'; -import 'package:titan/phonebook/providers/association_kind_provider.dart'; -import 'package:titan/phonebook/providers/association_kinds_provider.dart'; +import 'package:titan/phonebook/providers/association_groupement_provider.dart'; +import 'package:titan/phonebook/providers/association_groupement_list_provider.dart'; import 'package:titan/phonebook/providers/association_list_provider.dart'; import 'package:titan/phonebook/providers/association_provider.dart'; import 'package:titan/phonebook/providers/phonebook_admin_provider.dart'; import 'package:titan/phonebook/router.dart'; -import 'package:titan/phonebook/ui/components/kinds_bar.dart'; +import 'package:titan/phonebook/tools/constants.dart'; +import 'package:titan/phonebook/ui/components/groupement_bar.dart'; import 'package:titan/phonebook/ui/pages/main_page/association_card.dart'; import 'package:titan/phonebook/ui/phonebook.dart'; import 'package:titan/phonebook/ui/pages/main_page/research_bar.dart'; @@ -17,6 +18,7 @@ import 'package:titan/tools/ui/layouts/refresher.dart'; import 'package:titan/tools/ui/widgets/admin_button.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/l10n/app_localizations.dart'; +import 'package:tuple/tuple.dart'; class PhonebookMainPage extends HookConsumerWidget { const PhonebookMainPage({super.key}); @@ -28,16 +30,21 @@ class PhonebookMainPage extends HookConsumerWidget { final associationNotifier = ref.watch(associationProvider.notifier); final associationListNotifier = ref.watch(associationListProvider.notifier); final associationList = ref.watch(associationListProvider); + final associationGroupementList = ref.watch( + associationGroupementListProvider, + ); final associationFilteredList = ref.watch(associationFilteredListProvider); - final associationKindsNotifier = ref.watch( - associationKindsProvider.notifier, + final associationGroupementListNotifier = ref.watch( + associationGroupementListProvider.notifier, + ); + final associationGroupementNotifier = ref.watch( + associationGroupementProvider.notifier, ); - final kindNotifier = ref.watch(associationKindProvider.notifier); return PhonebookTemplate( child: Refresher( onRefresh: () async { - await associationKindsNotifier.loadAssociationKinds(); + await associationGroupementListNotifier.loadAssociationGroupement(); await associationListNotifier.loadAssociations(); }, child: Column( @@ -52,7 +59,8 @@ class PhonebookMainPage extends HookConsumerWidget { padding: const EdgeInsets.only(left: 20), child: AdminButton( onTap: () { - kindNotifier.setKind(''); + associationGroupementNotifier + .resetAssociationGroupement(); QR.to(PhonebookRouter.root + PhonebookRouter.admin); }, ), @@ -61,9 +69,9 @@ class PhonebookMainPage extends HookConsumerWidget { ), ), const SizedBox(height: 10), - AsyncChild( - value: associationList, - builder: (context, associations) { + Async2Child( + values: Tuple2(associationList, associationGroupementList), + builder: (context, associations, associationGroupements) { return Column( children: [ KindsBar(), @@ -81,10 +89,22 @@ class PhonebookMainPage extends HookConsumerWidget { (association) => !association.deactivated ? AssociationCard( association: association, + groupement: associationGroupements.firstWhere( + (groupement) => + groupement.id == association.groupementId, + ), onClicked: () { associationNotifier.setAssociation( association, ); + associationGroupementNotifier + .setAssociationGroupement( + associationGroupements.firstWhere( + (groupement) => + groupement.id == + association.groupementId, + ), + ); QR.to( PhonebookRouter.root + PhonebookRouter.associationDetail, From 736f902cd98d33e11e875ca2a4be8e7f0201bc63 Mon Sep 17 00:00:00 2001 From: Thonyk Date: Sat, 5 Jul 2025 23:56:31 +0200 Subject: [PATCH 085/473] refacto: main and admin pages rework --- ...ation_membership_member_editable_card.dart | 4 +- .../delete_button.dart | 0 .../edition_button.dart | 0 .../association_picture_provider.dart | 45 ++-- .../associations_picture_map_provider.dart | 17 ++ .../associations_pictures_provider.dart | 40 ---- .../association_picture_repository.dart | 9 + .../ui/components/groupement_bar.dart | 6 +- lib/phonebook/ui/components/radio_chip.dart | 36 ---- lib/phonebook/ui/components/research_bar.dart | 43 ++++ .../ui/pages/admin_page/admin_page.dart | 199 ++++-------------- .../admin_page/association_research_bar.dart | 42 ---- .../admin_page/editable_association_card.dart | 106 +++++----- .../ui/pages/admin_page/edition_modal.dart | 168 +++++++++++++++ .../association_creation_page.dart | 2 +- .../association_information_editor.dart | 2 +- .../member_editable_card.dart | 4 +- .../ui/pages/main_page/association_card.dart | 71 ++++--- .../ui/pages/main_page/main_page.dart | 55 ++--- .../ui/pages/main_page/research_bar.dart | 44 ---- 20 files changed, 421 insertions(+), 472 deletions(-) rename lib/{phonebook/ui/pages/admin_page => admin/ui/pages/memberships/association_membership_detail_page}/delete_button.dart (100%) rename lib/{phonebook/ui/pages/admin_page => admin/ui/pages/memberships/association_membership_detail_page}/edition_button.dart (100%) create mode 100644 lib/phonebook/providers/associations_picture_map_provider.dart delete mode 100644 lib/phonebook/providers/associations_pictures_provider.dart delete mode 100644 lib/phonebook/ui/components/radio_chip.dart create mode 100644 lib/phonebook/ui/components/research_bar.dart delete mode 100644 lib/phonebook/ui/pages/admin_page/association_research_bar.dart create mode 100644 lib/phonebook/ui/pages/admin_page/edition_modal.dart delete mode 100644 lib/phonebook/ui/pages/main_page/research_bar.dart diff --git a/lib/admin/ui/pages/memberships/association_membership_detail_page/association_membership_member_editable_card.dart b/lib/admin/ui/pages/memberships/association_membership_detail_page/association_membership_member_editable_card.dart index 7693ee0e75..db0b5babe4 100644 --- a/lib/admin/ui/pages/memberships/association_membership_detail_page/association_membership_member_editable_card.dart +++ b/lib/admin/ui/pages/memberships/association_membership_detail_page/association_membership_member_editable_card.dart @@ -5,8 +5,8 @@ import 'package:titan/admin/class/user_association_membership.dart'; import 'package:titan/admin/providers/association_membership_members_list_provider.dart'; import 'package:titan/admin/providers/user_association_membership_provider.dart'; import 'package:titan/admin/router.dart'; -import 'package:titan/phonebook/ui/pages/admin_page/delete_button.dart'; -import 'package:titan/phonebook/ui/pages/admin_page/edition_button.dart'; +import 'package:titan/admin/ui/pages/memberships/association_membership_detail_page/delete_button.dart'; +import 'package:titan/admin/ui/pages/memberships/association_membership_detail_page/edition_button.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:qlevar_router/qlevar_router.dart'; diff --git a/lib/phonebook/ui/pages/admin_page/delete_button.dart b/lib/admin/ui/pages/memberships/association_membership_detail_page/delete_button.dart similarity index 100% rename from lib/phonebook/ui/pages/admin_page/delete_button.dart rename to lib/admin/ui/pages/memberships/association_membership_detail_page/delete_button.dart diff --git a/lib/phonebook/ui/pages/admin_page/edition_button.dart b/lib/admin/ui/pages/memberships/association_membership_detail_page/edition_button.dart similarity index 100% rename from lib/phonebook/ui/pages/admin_page/edition_button.dart rename to lib/admin/ui/pages/memberships/association_membership_detail_page/edition_button.dart diff --git a/lib/phonebook/providers/association_picture_provider.dart b/lib/phonebook/providers/association_picture_provider.dart index e875d88304..fbf4210735 100644 --- a/lib/phonebook/providers/association_picture_provider.dart +++ b/lib/phonebook/providers/association_picture_provider.dart @@ -2,40 +2,47 @@ import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/auth/providers/openid_provider.dart'; +import 'package:titan/phonebook/providers/associations_picture_map_provider.dart'; import 'package:titan/phonebook/repositories/association_picture_repository.dart'; import 'package:titan/tools/providers/single_notifier.dart'; -final associationPictureProvider = - StateNotifierProvider>((ref) { - final token = ref.watch(tokenProvider); - AssociationPictureNotifier notifier = AssociationPictureNotifier( - token: token, - ); - return notifier; - }); - -class AssociationPictureNotifier extends SingleNotifier { - final AssociationPictureRepository associationPictureRepository = - AssociationPictureRepository(); - AssociationPictureNotifier({required String token}) - : super(const AsyncLoading()) { - associationPictureRepository.setToken(token); - } +class AssociationPictureProvider extends SingleNotifier { + final AssociationPictureRepository associationPictureRepository; + final AssociationPictureMapNotifier associationPictureMapNotifier; + AssociationPictureProvider({ + required this.associationPictureRepository, + required this.associationPictureMapNotifier, + }) : super(const AsyncLoading()); Future getAssociationPicture(String associationId) async { - return await associationPictureRepository.getAssociationPicture( + final image = await associationPictureRepository.getAssociationPicture( associationId, ); + associationPictureMapNotifier.setTData(associationId, AsyncData([image])); + return image; } Future updateAssociationPicture( String associationId, Uint8List bytes, ) async { - return await associationPictureRepository.addAssociationPicture( + final image = await associationPictureRepository.addAssociationPicture( bytes, associationId, ); + associationPictureMapNotifier.setTData(associationId, AsyncData([image])); + return image; } } + +final associationPictureProvider = + StateNotifierProvider>((ref) { + final associationPicture = ref.watch(associationPictureRepository); + final sessionPosterMapNotifier = ref.watch( + associationPictureMapProvider.notifier, + ); + return AssociationPictureProvider( + associationPictureRepository: associationPicture, + associationPictureMapNotifier: sessionPosterMapNotifier, + ); + }); diff --git a/lib/phonebook/providers/associations_picture_map_provider.dart b/lib/phonebook/providers/associations_picture_map_provider.dart new file mode 100644 index 0000000000..ce98d6f641 --- /dev/null +++ b/lib/phonebook/providers/associations_picture_map_provider.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/tools/providers/map_provider.dart'; + +class AssociationPictureMapNotifier extends MapNotifier { + AssociationPictureMapNotifier() : super(); +} + +final associationPictureMapProvider = + StateNotifierProvider< + AssociationPictureMapNotifier, + Map>?> + >((ref) { + AssociationPictureMapNotifier associationPictureNotifier = + AssociationPictureMapNotifier(); + return associationPictureNotifier; + }); diff --git a/lib/phonebook/providers/associations_pictures_provider.dart b/lib/phonebook/providers/associations_pictures_provider.dart deleted file mode 100644 index e4bbf6c58d..0000000000 --- a/lib/phonebook/providers/associations_pictures_provider.dart +++ /dev/null @@ -1,40 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/phonebook/class/association.dart'; -import 'package:titan/phonebook/providers/association_list_provider.dart'; -import 'package:titan/tools/providers/map_provider.dart'; -import 'package:titan/tools/token_expire_wrapper.dart'; - -class AssociationPictureNotifier extends MapNotifier { - AssociationPictureNotifier() : super(); -} - -final associationPicturesProvider = - StateNotifierProvider< - AssociationPictureNotifier, - Map>?> - >((ref) { - AssociationPictureNotifier associationPictureNotifier = - AssociationPictureNotifier(); - tokenExpireWrapperAuth(ref, () async { - ref - .watch(associationListProvider) - .maybeWhen( - data: (association) { - associationPictureNotifier.loadTList(association); - for (final l in association) { - associationPictureNotifier.setTData( - l, - const AsyncValue.data([]), - ); - } - return associationPictureNotifier; - }, - orElse: () { - associationPictureNotifier.loadTList([]); - return associationPictureNotifier; - }, - ); - }); - return associationPictureNotifier; - }); diff --git a/lib/phonebook/repositories/association_picture_repository.dart b/lib/phonebook/repositories/association_picture_repository.dart index e882ddac28..490bf6b418 100644 --- a/lib/phonebook/repositories/association_picture_repository.dart +++ b/lib/phonebook/repositories/association_picture_repository.dart @@ -1,6 +1,8 @@ import 'dart:typed_data'; import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/auth/providers/openid_provider.dart'; import 'package:titan/tools/repository/logo_repository.dart'; class AssociationPictureRepository extends LogoRepository { @@ -24,3 +26,10 @@ class AssociationPictureRepository extends LogoRepository { return Image.memory(uint8List); } } + +final associationPictureRepository = Provider(( + ref, +) { + final token = ref.watch(tokenProvider); + return AssociationPictureRepository()..setToken(token); +}); diff --git a/lib/phonebook/ui/components/groupement_bar.dart b/lib/phonebook/ui/components/groupement_bar.dart index db667b6606..1603f8bc1d 100644 --- a/lib/phonebook/ui/components/groupement_bar.dart +++ b/lib/phonebook/ui/components/groupement_bar.dart @@ -6,8 +6,8 @@ import 'package:titan/phonebook/providers/association_groupement_list_provider.d import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/item_chip.dart'; -class KindsBar extends HookConsumerWidget { - KindsBar({super.key}); +class AssociationGroupementBar extends HookConsumerWidget { + AssociationGroupementBar({super.key}); final dataKey = GlobalKey(); @override Widget build(BuildContext context, WidgetRef ref) { @@ -64,7 +64,7 @@ class KindsBar extends HookConsumerWidget { }, ), ) - : SizedBox(height: 0), + : SizedBox.shrink(), ); } } diff --git a/lib/phonebook/ui/components/radio_chip.dart b/lib/phonebook/ui/components/radio_chip.dart deleted file mode 100644 index 5e8047274f..0000000000 --- a/lib/phonebook/ui/components/radio_chip.dart +++ /dev/null @@ -1,36 +0,0 @@ -import 'package:flutter/material.dart'; - -class RadioChip extends StatelessWidget { - final bool selected; - final String label; - final Function() onTap; - const RadioChip({ - super.key, - required this.label, - required this.selected, - required this.onTap, - }); - - @override - Widget build(BuildContext context) { - return GestureDetector( - onTap: onTap, - child: Container( - margin: const EdgeInsets.symmetric(horizontal: 10.0), - child: Chip( - label: Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - label, - style: TextStyle( - color: selected ? Colors.white : Colors.black, - fontWeight: FontWeight.bold, - ), - ), - ), - backgroundColor: selected ? Colors.black : Colors.grey.shade200, - ), - ), - ); - } -} diff --git a/lib/phonebook/ui/components/research_bar.dart b/lib/phonebook/ui/components/research_bar.dart new file mode 100644 index 0000000000..3f7a315cd5 --- /dev/null +++ b/lib/phonebook/ui/components/research_bar.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/phonebook/providers/research_filter_provider.dart'; +import 'package:titan/phonebook/ui/components/groupement_bar.dart'; +import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; +import 'package:titan/tools/ui/styleguide/button.dart'; +import 'package:titan/tools/ui/styleguide/searchbar.dart'; + +class AssociationResearchBar extends HookConsumerWidget { + const AssociationResearchBar({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final filterNotifier = ref.watch(filterProvider.notifier); + + return Expanded( + child: CustomSearchBar( + onSearch: (value) { + filterNotifier.setFilter(value); + }, + onFilter: () => showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (context) { + return BottomModalTemplate( + title: "Groupements d'associations", + description: + "Sélectionnez un ou plusieurs groupements pour filtrer les associations.", + actions: [ + Button(text: "Fermer", onPressed: () => Navigator.pop(context)), + ], + child: Padding( + padding: EdgeInsetsGeometry.symmetric(vertical: 10), + child: AssociationGroupementBar(), + ), + ); + }, + ), + ), + ); + } +} diff --git a/lib/phonebook/ui/pages/admin_page/admin_page.dart b/lib/phonebook/ui/pages/admin_page/admin_page.dart index 79d5babf35..a87469aa18 100644 --- a/lib/phonebook/ui/pages/admin_page/admin_page.dart +++ b/lib/phonebook/ui/pages/admin_page/admin_page.dart @@ -3,24 +3,18 @@ import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/phonebook/providers/association_filtered_list_provider.dart'; import 'package:titan/phonebook/providers/association_groupement_list_provider.dart'; -import 'package:titan/phonebook/providers/association_groupement_provider.dart'; import 'package:titan/phonebook/providers/association_list_provider.dart'; -import 'package:titan/phonebook/providers/association_provider.dart'; import 'package:titan/phonebook/providers/phonebook_admin_provider.dart'; import 'package:titan/phonebook/providers/roles_tags_provider.dart'; import 'package:titan/phonebook/router.dart'; import 'package:titan/phonebook/tools/constants.dart'; -import 'package:titan/phonebook/ui/components/groupement_bar.dart'; +import 'package:titan/phonebook/ui/components/research_bar.dart'; import 'package:titan/phonebook/ui/phonebook.dart'; -import 'package:titan/phonebook/ui/pages/admin_page/association_research_bar.dart'; import 'package:titan/phonebook/ui/pages/admin_page/editable_association_card.dart'; -import 'package:titan/tools/constants.dart'; -import 'package:titan/tools/functions.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; -import 'package:titan/tools/ui/layouts/card_layout.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; -import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/tools/ui/styleguide/list_item.dart'; import 'package:tuple/tuple.dart'; import 'package:titan/l10n/app_localizations.dart'; @@ -29,10 +23,6 @@ class AdminPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final associationNotifier = ref.watch(associationProvider.notifier); - final associationGroupementsNotifier = ref.watch( - associationGroupementProvider.notifier, - ); final associationGroupementList = ref.watch( associationGroupementListProvider, ); @@ -41,9 +31,6 @@ class AdminPage extends HookConsumerWidget { final associationFilteredList = ref.watch(associationFilteredListProvider); final roleNotifier = ref.watch(rolesTagsProvider.notifier); final isPhonebookAdmin = ref.watch(isPhonebookAdminProvider); - void displayToastWithContext(TypeMsg type, String msg) { - displayToast(context, type, msg); - } return PhonebookTemplate( child: Refresher( @@ -53,165 +40,51 @@ class AdminPage extends HookConsumerWidget { }, child: Column( children: [ - const Padding( - padding: EdgeInsets.all(30), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), child: AssociationResearchBar(), ), - const SizedBox(height: 10), Async2Child( values: Tuple2(associationList, associationGroupementList), - builder: - ( - context, - syncAssociationList, - syncAssociationGroupementList, - ) { - return Column( - children: [ - KindsBar(), - GestureDetector( - onTap: isPhonebookAdmin - ? () { - QR.to( - PhonebookRouter.root + - PhonebookRouter.admin + - PhonebookRouter.createAssociaiton, - ); - } - : null, - child: CardLayout( - margin: const EdgeInsets.only( - bottom: 10, - top: 20, - left: 40, - right: 40, - ), - width: double.infinity, - height: 100, - color: isPhonebookAdmin - ? Colors.white - : ColorConstants.deactivated2, - child: Center( - child: HeroIcon( - HeroIcons.plus, - size: 40, - color: Colors.grey.shade500, - ), - ), - ), - ), + builder: (context, associations, associationGroupements) { + return Column( + children: [ + ListItem( + title: "Ajouter une association", + icon: HeroIcon( + HeroIcons.plus, + size: 40, + color: Colors.grey.shade500, ), + onTap: isPhonebookAdmin + ? () { + QR.to( + PhonebookRouter.root + + PhonebookRouter.admin + + PhonebookRouter.createAssociaiton, + ); + } + : null, ), - const SizedBox(height: 30), + SizedBox(height: 5), if (associations.isEmpty) - Center( - child: Text( - AppLocalizations.of( - context, - )!.phonebookNoAssociationFound, - ), + const Center( + child: Text(PhonebookTextConstants.noAssociationFound), ) else ...associationFilteredList.map( - (association) => Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: EditableAssociationCard( - association: association, - isPhonebookAdmin: isPhonebookAdmin, - onEdit: () { - kindNotifier.setKind(association.kind); - associationNotifier.setAssociation(association); - QR.to( - PhonebookRouter.root + - PhonebookRouter.admin + - PhonebookRouter.editAssociation, - ); - }, - onDelete: () async { - association.deactivated - ? await showDialog( - context: context, - builder: (context) { - return CustomDialogBox( - title: AppLocalizations.of( - context, - )!.phonebookDeleting, - descriptions: AppLocalizations.of( - context, - )!.phonebookDeleteAssociation, - onYes: () async { - final deletedAssociationMsg = - AppLocalizations.of( - context, - )!.phonebookDeletedAssociation; - final deletingErrorMsg = - AppLocalizations.of( - context, - )!.phonebookDeletingError; - final result = - await associationListNotifier - .deleteAssociation( - association, - ); - if (result) { - displayToastWithContext( - TypeMsg.msg, - deletedAssociationMsg, - ); - } else { - displayToastWithContext( - TypeMsg.error, - deletingErrorMsg, - ); - } - }, - ); - }, - ) - : await showDialog( - context: context, - builder: (context) { - return CustomDialogBox( - title: AppLocalizations.of( - context, - )!.phonebookDeactivating, - descriptions: AppLocalizations.of( - context, - )!.phonebookDeactivateAssociation, - onYes: () async { - final deactivatedAssociationMsg = - AppLocalizations.of( - context, - )!.phonebookDeactivatedAssociation; - final deactivatingErrorMsg = - AppLocalizations.of( - context, - )!.phonebookDeactivatingError; - final result = - await associationListNotifier - .deactivateAssociation( - association, - ); - if (result) { - displayToastWithContext( - TypeMsg.msg, - deactivatedAssociationMsg, - ); - } else { - displayToastWithContext( - TypeMsg.error, - deactivatingErrorMsg, - ); - } - }, - ); - }, - ); - }, + (association) => EditableAssociationCard( + association: association, + groupement: associationGroupements.firstWhere( + (groupement) => + groupement.id == association.groupementId, ), - ], - ); - }, + isPhonebookAdmin: isPhonebookAdmin, + ), + ), + ], + ); + }, ), ], ), diff --git a/lib/phonebook/ui/pages/admin_page/association_research_bar.dart b/lib/phonebook/ui/pages/admin_page/association_research_bar.dart deleted file mode 100644 index 5fa79967d3..0000000000 --- a/lib/phonebook/ui/pages/admin_page/association_research_bar.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/phonebook/providers/research_filter_provider.dart'; -import 'package:titan/phonebook/tools/constants.dart'; -import 'package:titan/tools/constants.dart'; -import 'package:titan/l10n/app_localizations.dart'; - -class AssociationResearchBar extends HookConsumerWidget { - const AssociationResearchBar({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final focusNode = useFocusNode(); - final editingController = useTextEditingController(); - final filterNotifier = ref.watch(filterProvider.notifier); - - return TextField( - onChanged: (value) async { - filterNotifier.setFilter(value); - }, - focusNode: focusNode, - controller: editingController, - cursorColor: PhonebookColorConstants.textDark, - decoration: InputDecoration( - isDense: true, - suffixIcon: const Icon( - Icons.search, - color: PhonebookColorConstants.textDark, - size: 30, - ), - label: Text( - AppLocalizations.of(context)!.phonebookResearch, - style: const TextStyle(color: PhonebookColorConstants.textDark), - ), - focusedBorder: const UnderlineInputBorder( - borderSide: BorderSide(color: ColorConstants.gradient1), - ), - ), - ); - } -} diff --git a/lib/phonebook/ui/pages/admin_page/editable_association_card.dart b/lib/phonebook/ui/pages/admin_page/editable_association_card.dart index 82091c9714..7819188b49 100644 --- a/lib/phonebook/ui/pages/admin_page/editable_association_card.dart +++ b/lib/phonebook/ui/pages/admin_page/editable_association_card.dart @@ -1,78 +1,68 @@ import 'package:flutter/material.dart'; +import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/phonebook/class/association.dart'; import 'package:titan/phonebook/class/association_groupement.dart'; -import 'package:titan/phonebook/ui/pages/admin_page/delete_button.dart'; -import 'package:titan/phonebook/ui/pages/admin_page/edition_button.dart'; +import 'package:titan/phonebook/providers/association_picture_provider.dart'; +import 'package:titan/phonebook/providers/associations_picture_map_provider.dart'; +import 'package:titan/phonebook/ui/pages/admin_page/edition_modal.dart'; +import 'package:titan/tools/ui/builders/auto_loader_child.dart'; +import 'package:titan/tools/ui/styleguide/list_item.dart'; class EditableAssociationCard extends HookConsumerWidget { final Association association; - final AssociationGroupement associationGroupement; + final AssociationGroupement groupement; final bool isPhonebookAdmin; - final void Function() onEdit; - final Future Function() onDelete; const EditableAssociationCard({ super.key, required this.association, - required this.associationGroupement, + required this.groupement, required this.isPhonebookAdmin, - required this.onEdit, - required this.onDelete, }); @override Widget build(BuildContext context, WidgetRef ref) { - return Container( - margin: const EdgeInsets.symmetric(vertical: 5), - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - color: association.deactivated ? Colors.grey[500] : Colors.white, - borderRadius: BorderRadius.circular(15), - boxShadow: [ - BoxShadow( - color: Colors.grey.withValues(alpha: 0.2), - blurRadius: 5, - spreadRadius: 2, - ), - ], - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - const SizedBox(width: 10), - Expanded( - child: Text( - association.name, - style: const TextStyle( - color: Colors.black, - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - ), - Expanded( - child: Text( - associationGroupement.name, - style: const TextStyle( - color: Colors.black, - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - ), - Row( - children: [ - EditionButton(onEdition: onEdit, deactivated: false), - const SizedBox(width: 5), - DeleteButton( - onDelete: onDelete, - deactivated: !isPhonebookAdmin, - deletion: association.deactivated, - ), - ], + final associationPicture = ref.watch( + associationPictureMapProvider.select((value) => value[association.id]), + ); + final associationPictureMapNotifier = ref.watch( + associationPictureMapProvider.notifier, + ); + final associationPictureNotifier = ref.watch( + associationPictureProvider.notifier, + ); + return AutoLoaderChild( + group: associationPicture, + notifier: associationPictureMapNotifier, + mapKey: association.id, + loader: (associationId) => + associationPictureNotifier.getAssociationPicture(associationId), + dataBuilder: (context, data) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 5), + child: ListItem( + title: association.name, + subtitle: groupement.name, + icon: CircleAvatar(child: Image(image: data.first.image)), + onTap: () { + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (context) { + return AssociationEditionModal( + association: association, + groupement: groupement, + isPhonebookAdmin: isPhonebookAdmin, + ); + }, + ); + }, ), - ], - ), + ); + }, + errorBuilder: (error, stack) => + const Center(child: HeroIcon(HeroIcons.exclamationCircle)), ); } } diff --git a/lib/phonebook/ui/pages/admin_page/edition_modal.dart b/lib/phonebook/ui/pages/admin_page/edition_modal.dart new file mode 100644 index 0000000000..f43d851845 --- /dev/null +++ b/lib/phonebook/ui/pages/admin_page/edition_modal.dart @@ -0,0 +1,168 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/phonebook/class/association.dart'; +import 'package:titan/phonebook/class/association_groupement.dart'; +import 'package:titan/phonebook/providers/association_groupement_provider.dart'; +import 'package:titan/phonebook/providers/association_list_provider.dart'; +import 'package:titan/phonebook/providers/association_provider.dart'; +import 'package:titan/phonebook/router.dart'; +import 'package:titan/phonebook/tools/constants.dart'; +import 'package:titan/tools/functions.dart'; +import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; +import 'package:titan/tools/ui/styleguide/button.dart'; +import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; + +class AssociationEditionModal extends HookConsumerWidget { + final Association association; + final AssociationGroupement groupement; + final bool isPhonebookAdmin; + const AssociationEditionModal({ + super.key, + required this.association, + required this.groupement, + required this.isPhonebookAdmin, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final associationGroupementsNotifier = ref.watch( + associationGroupementProvider.notifier, + ); + final associationNotifier = ref.watch(associationProvider.notifier); + final associationListNotifier = ref.watch(associationListProvider.notifier); + + void displayToastWithContext(TypeMsg type, String msg) { + displayToast(context, type, msg); + } + + return BottomModalTemplate( + title: "title", + child: SingleChildScrollView( + child: Column( + children: [ + Button( + text: "Modifier les informations", + onPressed: () { + associationGroupementsNotifier.setAssociationGroupement( + groupement, + ); + associationNotifier.setAssociation(association); + QR.to( + PhonebookRouter.root + + PhonebookRouter.admin + + PhonebookRouter.editAssociation, + ); + }, + ), + SizedBox(height: 5), + Button( + text: "Gérer les groupes", + onPressed: () { + associationGroupementsNotifier.setAssociationGroupement( + groupement, + ); + associationNotifier.setAssociation(association); + QR.to( + PhonebookRouter.root + + PhonebookRouter.admin + + PhonebookRouter.editAssociation, + ); + }, + ), + SizedBox(height: 5), + Button( + text: "Gérer les membres", + onPressed: () { + associationGroupementsNotifier.setAssociationGroupement( + groupement, + ); + associationNotifier.setAssociation(association); + QR.to( + PhonebookRouter.root + + PhonebookRouter.admin + + PhonebookRouter.editAssociation, + ); + }, + ), + SizedBox(height: 15), + Button.danger( + text: "Passer au mandat ${association.mandateYear + 1}", + onPressed: () { + return null; + }, + ), + SizedBox(height: 5), + ...association.deactivated + ? [ + Button.danger( + text: "Réactiver l'association", + onPressed: () {}, + ), + SizedBox(height: 5), + ] + : [SizedBox.shrink()], + Button.danger( + text: association.deactivated + ? "Supprimer l'association" + : "Désactiver l'association", + onPressed: () async { + association.deactivated + ? await showDialog( + context: context, + builder: (context) { + return CustomDialogBox( + title: PhonebookTextConstants.deleting, + descriptions: + PhonebookTextConstants.deleteAssociation, + onYes: () async { + final result = await associationListNotifier + .deleteAssociation(association); + if (result) { + displayToastWithContext( + TypeMsg.msg, + PhonebookTextConstants.deletedAssociation, + ); + } else { + displayToastWithContext( + TypeMsg.error, + PhonebookTextConstants.deletingError, + ); + } + }, + ); + }, + ) + : await showDialog( + context: context, + builder: (context) { + return CustomDialogBox( + title: PhonebookTextConstants.deactivating, + descriptions: + PhonebookTextConstants.deactivateAssociation, + onYes: () async { + final result = await associationListNotifier + .deactivateAssociation(association); + if (result) { + displayToastWithContext( + TypeMsg.msg, + PhonebookTextConstants.deactivatedAssociation, + ); + } else { + displayToastWithContext( + TypeMsg.error, + PhonebookTextConstants.deactivatingError, + ); + } + }, + ); + }, + ); + }, + ), + ], + ), + ), + ); + } +} diff --git a/lib/phonebook/ui/pages/association_creation_page/association_creation_page.dart b/lib/phonebook/ui/pages/association_creation_page/association_creation_page.dart index 4d301e1323..5c083cf61c 100644 --- a/lib/phonebook/ui/pages/association_creation_page/association_creation_page.dart +++ b/lib/phonebook/ui/pages/association_creation_page/association_creation_page.dart @@ -60,7 +60,7 @@ class AssociationCreationPage extends HookConsumerWidget { ), ), const SizedBox(height: 30), - KindsBar(key: scrollKey), + AssociationGroupementBar(key: scrollKey), Padding( padding: const EdgeInsets.symmetric(horizontal: 30.0), child: Column( diff --git a/lib/phonebook/ui/pages/association_editor_page/association_information_editor.dart b/lib/phonebook/ui/pages/association_editor_page/association_information_editor.dart index dfbba3ceca..7a96f1625d 100644 --- a/lib/phonebook/ui/pages/association_editor_page/association_information_editor.dart +++ b/lib/phonebook/ui/pages/association_editor_page/association_information_editor.dart @@ -56,7 +56,7 @@ class AssociationInformationEditor extends HookConsumerWidget { key: key, child: Column( children: [ - KindsBar(key: scrollKey), + AssociationGroupementBar(key: scrollKey), Padding( padding: const EdgeInsets.symmetric(horizontal: 30), child: Column( diff --git a/lib/phonebook/ui/pages/association_editor_page/member_editable_card.dart b/lib/phonebook/ui/pages/association_editor_page/member_editable_card.dart index 4d59805ddc..4199fa0411 100644 --- a/lib/phonebook/ui/pages/association_editor_page/member_editable_card.dart +++ b/lib/phonebook/ui/pages/association_editor_page/member_editable_card.dart @@ -1,6 +1,8 @@ import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/admin/ui/pages/memberships/association_membership_detail_page/delete_button.dart'; +import 'package:titan/admin/ui/pages/memberships/association_membership_detail_page/edition_button.dart'; import 'package:titan/phonebook/class/association.dart'; import 'package:titan/phonebook/class/complete_member.dart'; import 'package:titan/phonebook/class/membership.dart'; @@ -13,8 +15,6 @@ import 'package:titan/phonebook/providers/profile_picture_provider.dart'; import 'package:titan/phonebook/providers/roles_tags_provider.dart'; import 'package:titan/phonebook/router.dart'; import 'package:titan/phonebook/tools/function.dart'; -import 'package:titan/phonebook/ui/pages/admin_page/delete_button.dart'; -import 'package:titan/phonebook/ui/pages/admin_page/edition_button.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/ui/builders/auto_loader_child.dart'; import 'package:qlevar_router/qlevar_router.dart'; diff --git a/lib/phonebook/ui/pages/main_page/association_card.dart b/lib/phonebook/ui/pages/main_page/association_card.dart index 1ab44faecd..a0ce8d9508 100644 --- a/lib/phonebook/ui/pages/main_page/association_card.dart +++ b/lib/phonebook/ui/pages/main_page/association_card.dart @@ -1,46 +1,67 @@ import 'package:flutter/material.dart'; +import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/phonebook/class/association.dart'; import 'package:titan/phonebook/class/association_groupement.dart'; -import 'package:titan/tools/ui/layouts/card_layout.dart'; +import 'package:titan/phonebook/providers/association_groupement_provider.dart'; +import 'package:titan/phonebook/providers/association_picture_provider.dart'; +import 'package:titan/phonebook/providers/association_provider.dart'; +import 'package:titan/phonebook/providers/associations_picture_map_provider.dart'; +import 'package:titan/phonebook/router.dart'; +import 'package:titan/tools/ui/builders/auto_loader_child.dart'; +import 'package:titan/tools/ui/styleguide/list_item.dart'; class AssociationCard extends HookConsumerWidget { const AssociationCard({ super.key, required this.association, required this.groupement, - required this.onClicked, }); final Association association; final AssociationGroupement groupement; - final VoidCallback onClicked; @override Widget build(BuildContext context, WidgetRef ref) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 5), - child: GestureDetector( - onTap: onClicked, - child: CardLayout( - margin: EdgeInsets.zero, - child: Row( - children: [ - const SizedBox(width: 10), - Expanded( - child: Text( - association.name, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - ), - Text(groupement.name), - ], + final associationPicture = ref.watch( + associationPictureMapProvider.select((value) => value[association.id]), + ); + final associationPictureMapNotifier = ref.watch( + associationPictureMapProvider.notifier, + ); + final associationPictureNotifier = ref.watch( + associationPictureProvider.notifier, + ); + final associationGroupementNotifier = ref.watch( + associationGroupementProvider.notifier, + ); + final associationNotifier = ref.watch(associationProvider.notifier); + return AutoLoaderChild( + group: associationPicture, + notifier: associationPictureMapNotifier, + mapKey: association.id, + loader: (associationId) => + associationPictureNotifier.getAssociationPicture(associationId), + dataBuilder: (context, data) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 5), + child: ListItem( + title: association.name, + subtitle: groupement.name, + icon: CircleAvatar(child: Image(image: data.first.image)), + onTap: () { + associationNotifier.setAssociation(association); + associationGroupementNotifier.setAssociationGroupement( + groupement, + ); + QR.to(PhonebookRouter.root + PhonebookRouter.associationDetail); + }, ), - ), - ), + ); + }, + errorBuilder: (error, stack) => + const Center(child: HeroIcon(HeroIcons.exclamationCircle)), ); } } diff --git a/lib/phonebook/ui/pages/main_page/main_page.dart b/lib/phonebook/ui/pages/main_page/main_page.dart index 691c912298..0c491de71a 100644 --- a/lib/phonebook/ui/pages/main_page/main_page.dart +++ b/lib/phonebook/ui/pages/main_page/main_page.dart @@ -1,21 +1,20 @@ import 'package:flutter/material.dart'; +import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/providers/is_admin_provider.dart'; import 'package:titan/phonebook/providers/association_filtered_list_provider.dart'; import 'package:titan/phonebook/providers/association_groupement_provider.dart'; import 'package:titan/phonebook/providers/association_groupement_list_provider.dart'; import 'package:titan/phonebook/providers/association_list_provider.dart'; -import 'package:titan/phonebook/providers/association_provider.dart'; import 'package:titan/phonebook/providers/phonebook_admin_provider.dart'; import 'package:titan/phonebook/router.dart'; import 'package:titan/phonebook/tools/constants.dart'; -import 'package:titan/phonebook/ui/components/groupement_bar.dart'; import 'package:titan/phonebook/ui/pages/main_page/association_card.dart'; import 'package:titan/phonebook/ui/phonebook.dart'; -import 'package:titan/phonebook/ui/pages/main_page/research_bar.dart'; +import 'package:titan/phonebook/ui/components/research_bar.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; -import 'package:titan/tools/ui/widgets/admin_button.dart'; +import 'package:titan/tools/ui/styleguide/icon_button.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/l10n/app_localizations.dart'; import 'package:tuple/tuple.dart'; @@ -27,7 +26,6 @@ class PhonebookMainPage extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final isPhonebookAdmin = ref.watch(isPhonebookAdminProvider); final isAdmin = ref.watch(isAdminProvider); - final associationNotifier = ref.watch(associationProvider.notifier); final associationListNotifier = ref.watch(associationListProvider.notifier); final associationList = ref.watch(associationListProvider); final associationGroupementList = ref.watch( @@ -50,32 +48,34 @@ class PhonebookMainPage extends HookConsumerWidget { child: Column( children: [ Padding( - padding: const EdgeInsets.all(30.0), + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), child: Row( children: [ - const ResearchBar(), - if (isPhonebookAdmin || isAdmin) - Padding( - padding: const EdgeInsets.only(left: 20), - child: AdminButton( - onTap: () { - associationGroupementNotifier - .resetAssociationGroupement(); - QR.to(PhonebookRouter.root + PhonebookRouter.admin); - }, + AssociationResearchBar(), + if (isPhonebookAdmin || isAdmin) ...[ + SizedBox(width: 10), + CustomIconButton( + icon: HeroIcon( + HeroIcons.cog6Tooth, + color: Colors.white, + size: 32, + style: HeroIconStyle.outline, ), + onPressed: () { + associationGroupementNotifier + .resetAssociationGroupement(); + QR.to(PhonebookRouter.root + PhonebookRouter.admin); + }, ), + ], ], ), ), - const SizedBox(height: 10), Async2Child( values: Tuple2(associationList, associationGroupementList), builder: (context, associations, associationGroupements) { return Column( children: [ - KindsBar(), - const SizedBox(height: 30), if (associations.isEmpty) Center( child: Text( @@ -93,23 +93,6 @@ class PhonebookMainPage extends HookConsumerWidget { (groupement) => groupement.id == association.groupementId, ), - onClicked: () { - associationNotifier.setAssociation( - association, - ); - associationGroupementNotifier - .setAssociationGroupement( - associationGroupements.firstWhere( - (groupement) => - groupement.id == - association.groupementId, - ), - ); - QR.to( - PhonebookRouter.root + - PhonebookRouter.associationDetail, - ); - }, ) : const SizedBox.shrink(), ), diff --git a/lib/phonebook/ui/pages/main_page/research_bar.dart b/lib/phonebook/ui/pages/main_page/research_bar.dart deleted file mode 100644 index a0d39e526d..0000000000 --- a/lib/phonebook/ui/pages/main_page/research_bar.dart +++ /dev/null @@ -1,44 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/phonebook/providers/research_filter_provider.dart'; -import 'package:titan/phonebook/tools/constants.dart'; -import 'package:titan/tools/constants.dart'; -import 'package:titan/l10n/app_localizations.dart'; - -class ResearchBar extends HookConsumerWidget { - const ResearchBar({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final focusNode = useFocusNode(); - final editingController = useTextEditingController(); - final filterNotifier = ref.watch(filterProvider.notifier); - - return Expanded( - child: TextField( - onChanged: (value) { - filterNotifier.setFilter(value); - }, - focusNode: focusNode, - controller: editingController, - cursorColor: PhonebookColorConstants.textDark, - decoration: InputDecoration( - isDense: true, - suffixIcon: const Icon( - Icons.search, - color: PhonebookColorConstants.textDark, - size: 30, - ), - label: Text( - AppLocalizations.of(context)!.phonebookResearch, - style: const TextStyle(color: PhonebookColorConstants.textDark), - ), - focusedBorder: const UnderlineInputBorder( - borderSide: BorderSide(color: ColorConstants.gradient1), - ), - ), - ), - ); - } -} From e308676b10759a7852c9e4ce1338a7ba27275173 Mon Sep 17 00:00:00 2001 From: Thonyk Date: Sat, 19 Jul 2025 00:58:43 +0200 Subject: [PATCH 086/473] feat: association groupements --- .../association_groupement_list_provider.dart | 36 +++ lib/phonebook/router.dart | 12 +- .../components/association_research_bar.dart | 52 ++++ .../ui/components/groupement_bar.dart | 141 ++++++++--- lib/phonebook/ui/components/research_bar.dart | 43 ---- .../groupement_add_edit_page.dart | 104 ++++++++ .../ui/pages/admin_page/admin_page.dart | 96 +++---- .../ui/pages/admin_page/edition_modal.dart | 234 +++++++++--------- .../association_add_edit_page.dart | 135 ++++++++++ .../association_creation_page.dart | 169 ------------- .../association_creation_page/text_entry.dart | 60 ----- .../ui/pages/main_page/main_page.dart | 14 +- lib/tools/ui/builders/async_child.dart | 66 +++-- .../styleguide/horizontal_multi_select.dart | 21 +- 14 files changed, 659 insertions(+), 524 deletions(-) create mode 100644 lib/phonebook/ui/components/association_research_bar.dart delete mode 100644 lib/phonebook/ui/components/research_bar.dart create mode 100644 lib/phonebook/ui/pages/add_edit_groupement_page/groupement_add_edit_page.dart create mode 100644 lib/phonebook/ui/pages/association_add_edit_page/association_add_edit_page.dart delete mode 100644 lib/phonebook/ui/pages/association_creation_page/association_creation_page.dart delete mode 100644 lib/phonebook/ui/pages/association_creation_page/text_entry.dart diff --git a/lib/phonebook/providers/association_groupement_list_provider.dart b/lib/phonebook/providers/association_groupement_list_provider.dart index 0ee38d32d2..e61666d9df 100644 --- a/lib/phonebook/providers/association_groupement_list_provider.dart +++ b/lib/phonebook/providers/association_groupement_list_provider.dart @@ -20,6 +20,42 @@ class AssociationGroupementListNotifier associationGroupementRepository.getAssociationGroupements, ); } + + Future createAssociationGroupement( + AssociationGroupement associationGroupement, + ) async { + return await add( + associationGroupementRepository.createAssociationGroupement, + associationGroupement, + ); + } + + Future updateAssociationGroupement( + AssociationGroupement associationGroupement, + ) async { + return await update( + associationGroupementRepository.updateAssociationGroupement, + (associationGroupements, associationGroupement) => associationGroupements + ..[associationGroupements.indexWhere( + (g) => g.id == associationGroupement.id, + )] = + associationGroupement, + associationGroupement, + ); + } + + Future deleteAssociationGroupement( + AssociationGroupement associationGroupement, + ) async { + return await delete( + associationGroupementRepository.deleteAssociationGroupement, + (associationGroupements, associationGroupement) => + associationGroupements + ..removeWhere((i) => i.id == associationGroupement.id), + associationGroupement.id, + associationGroupement, + ); + } } final associationGroupementListProvider = diff --git a/lib/phonebook/router.dart b/lib/phonebook/router.dart index 7354fb42ce..4585c005bd 100644 --- a/lib/phonebook/router.dart +++ b/lib/phonebook/router.dart @@ -3,8 +3,9 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/navigation/class/module.dart'; import 'package:titan/phonebook/providers/phonebook_admin_provider.dart'; +import 'package:titan/phonebook/ui/pages/add_edit_groupement_page/groupement_add_edit_page.dart'; import 'package:titan/phonebook/ui/pages/admin_page/admin_page.dart'; -import 'package:titan/phonebook/ui/pages/association_creation_page/association_creation_page.dart'; +import 'package:titan/phonebook/ui/pages/association_add_edit_page/association_add_edit_page.dart'; import 'package:titan/phonebook/ui/pages/association_editor_page/association_editor_page.dart'; import 'package:titan/phonebook/ui/pages/association_page/association_page.dart'; import 'package:titan/phonebook/ui/pages/main_page/main_page.dart'; @@ -19,6 +20,7 @@ class PhonebookRouter { static const String root = '/phonebook'; static const String admin = '/admin'; static const String createAssociaiton = '/create_association'; + static const String addEditGroupement = '/add_edit_groupement'; static const String editAssociation = '/edit_association'; static const String associationDetail = '/association_detail'; static const String memberDetail = '/member_detail'; @@ -57,7 +59,13 @@ class PhonebookRouter { ), QRoute( path: createAssociaiton, - builder: () => AssociationCreationPage(), + builder: () => AssociationAddEditPage(), + children: [ + QRoute( + path: addEditGroupement, + builder: () => const AssociationGroupementAddEditPage(), + ), + ], ), ], ), diff --git a/lib/phonebook/ui/components/association_research_bar.dart b/lib/phonebook/ui/components/association_research_bar.dart new file mode 100644 index 0000000000..12d739613f --- /dev/null +++ b/lib/phonebook/ui/components/association_research_bar.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/phonebook/providers/association_groupement_list_provider.dart'; +import 'package:titan/phonebook/providers/research_filter_provider.dart'; +import 'package:titan/phonebook/ui/components/groupement_bar.dart'; +import 'package:titan/tools/ui/builders/async_child.dart'; +import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; +import 'package:titan/tools/ui/styleguide/button.dart'; +import 'package:titan/tools/ui/styleguide/searchbar.dart'; + +class AssociationResearchBar extends HookConsumerWidget { + const AssociationResearchBar({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final filterNotifier = ref.watch(filterProvider.notifier); + final associaiontGroupementList = ref.watch( + associationGroupementListProvider, + ); + return AsyncChild( + value: associaiontGroupementList, + builder: (context, groupements) { + return CustomSearchBar( + onSearch: (value) { + filterNotifier.setFilter(value); + }, + onFilter: groupements.length <= 1 + ? null + : () => showCustomBottomModal( + ref: ref, + context: context, + modal: BottomModalTemplate( + title: "Groupements d'associations", + description: + "Sélectionnez un ou plusieurs groupements pour filtrer les associations.", + actions: [ + Button( + text: "Fermer", + onPressed: () => Navigator.pop(context), + ), + ], + child: Padding( + padding: EdgeInsetsGeometry.symmetric(vertical: 10), + child: AssociationGroupementBar(), + ), + ), + ), + ); + }, + ); + } +} diff --git a/lib/phonebook/ui/components/groupement_bar.dart b/lib/phonebook/ui/components/groupement_bar.dart index 1603f8bc1d..994cba35e8 100644 --- a/lib/phonebook/ui/components/groupement_bar.dart +++ b/lib/phonebook/ui/components/groupement_bar.dart @@ -1,14 +1,21 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/phonebook/class/association_groupement.dart'; import 'package:titan/phonebook/providers/association_groupement_provider.dart'; import 'package:titan/phonebook/providers/association_groupement_list_provider.dart'; +import 'package:titan/phonebook/router.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; -import 'package:titan/tools/ui/layouts/item_chip.dart'; +import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; +import 'package:titan/tools/ui/styleguide/button.dart'; +import 'package:titan/tools/ui/styleguide/item_chip.dart'; class AssociationGroupementBar extends HookConsumerWidget { - AssociationGroupementBar({super.key}); + AssociationGroupementBar({super.key, this.editable = false}); final dataKey = GlobalKey(); + final bool editable; + @override Widget build(BuildContext context, WidgetRef ref) { final associationGroupement = ref.watch(associationGroupementProvider); @@ -18,6 +25,60 @@ class AssociationGroupementBar extends HookConsumerWidget { final associationGroupementList = ref.watch( associationGroupementListProvider, ); + final associationGroupementListNotifier = ref.watch( + associationGroupementListProvider.notifier, + ); + + void showSnackBarWithContext(String message) { + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text(message))); + } + + void popWithContext() { + Navigator.of(context).pop(); + } + + void showEditDialog(AssociationGroupement item) => showCustomBottomModal( + ref: ref, + context: context, + modal: BottomModalTemplate( + title: item.name, + actions: [ + Button( + text: "Modifier", + onPressed: () { + associationGroupementNotifier.setAssociationGroupement(item); + QR.to( + PhonebookRouter.root + + PhonebookRouter.admin + + PhonebookRouter.createAssociaiton + + PhonebookRouter.addEditGroupement, + ); + }, + ), + SizedBox(height: 30), + Button.danger( + text: "Supprimer", + onPressed: () async { + final result = await associationGroupementListNotifier + .deleteAssociationGroupement(item); + if (result && context.mounted) { + popWithContext(); + showSnackBarWithContext("Groupe supprimé"); + } + if (!result && context.mounted) { + showSnackBarWithContext( + "Une erreur est survenue lors de la suppression du groupe", + ); + } + }, + ), + ], + child: SizedBox.shrink(), + ), + ); + useEffect(() { Future(() { if (associationGroupement.id != "") { @@ -32,39 +93,55 @@ class AssociationGroupementBar extends HookConsumerWidget { }, [dataKey]); return AsyncChild( value: associationGroupementList, - builder: (context, associationGroupements) => - associationGroupements.length > 1 - ? SizedBox( - width: MediaQuery.of(context).size.width, - height: 40, - child: ListView.builder( - scrollDirection: Axis.horizontal, - itemCount: associationGroupements.length, - itemBuilder: (context, index) { - final item = associationGroupements[index]; - final selected = associationGroupement == item; - return ItemChip( - key: selected ? dataKey : null, - onTap: () { - !selected - ? associationGroupementNotifier - .setAssociationGroupement(item) - : associationGroupementNotifier - .resetAssociationGroupement(); - }, - selected: selected, - child: Text( - item.name, - style: TextStyle( - color: selected ? Colors.white : Colors.black, - fontWeight: FontWeight.bold, - ), - ), + builder: (context, associationGroupements) => SizedBox( + width: MediaQuery.of(context).size.width, + height: 40, + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: editable + ? associationGroupements.length + 1 + : associationGroupements.length, + itemBuilder: (context, index) { + if (editable && index == 0) { + return ItemChip( + key: Key("add"), + onTap: () { + associationGroupementNotifier.resetAssociationGroupement(); + QR.to( + PhonebookRouter.root + + PhonebookRouter.admin + + PhonebookRouter.createAssociaiton + + PhonebookRouter.addEditGroupement, ); }, + child: Text("+", style: TextStyle(fontWeight: FontWeight.bold)), + ); + } + final item = associationGroupements[editable ? index - 1 : index]; + final selected = associationGroupement.id == item.id; + return ItemChip( + key: selected ? dataKey : null, + selected: selected, + onTap: () { + associationGroupement.id != item.id + ? associationGroupementNotifier.setAssociationGroupement( + item, + ) + : associationGroupementNotifier + .resetAssociationGroupement(); + }, + onLongPress: () => showEditDialog(item), + child: Text( + item.name, + style: TextStyle( + color: selected ? Colors.white : Colors.black, + fontWeight: FontWeight.bold, + ), ), - ) - : SizedBox.shrink(), + ); + }, + ), + ), ); } } diff --git a/lib/phonebook/ui/components/research_bar.dart b/lib/phonebook/ui/components/research_bar.dart deleted file mode 100644 index 3f7a315cd5..0000000000 --- a/lib/phonebook/ui/components/research_bar.dart +++ /dev/null @@ -1,43 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/phonebook/providers/research_filter_provider.dart'; -import 'package:titan/phonebook/ui/components/groupement_bar.dart'; -import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; -import 'package:titan/tools/ui/styleguide/button.dart'; -import 'package:titan/tools/ui/styleguide/searchbar.dart'; - -class AssociationResearchBar extends HookConsumerWidget { - const AssociationResearchBar({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final filterNotifier = ref.watch(filterProvider.notifier); - - return Expanded( - child: CustomSearchBar( - onSearch: (value) { - filterNotifier.setFilter(value); - }, - onFilter: () => showModalBottomSheet( - context: context, - isScrollControlled: true, - backgroundColor: Colors.transparent, - builder: (context) { - return BottomModalTemplate( - title: "Groupements d'associations", - description: - "Sélectionnez un ou plusieurs groupements pour filtrer les associations.", - actions: [ - Button(text: "Fermer", onPressed: () => Navigator.pop(context)), - ], - child: Padding( - padding: EdgeInsetsGeometry.symmetric(vertical: 10), - child: AssociationGroupementBar(), - ), - ); - }, - ), - ), - ); - } -} diff --git a/lib/phonebook/ui/pages/add_edit_groupement_page/groupement_add_edit_page.dart b/lib/phonebook/ui/pages/add_edit_groupement_page/groupement_add_edit_page.dart new file mode 100644 index 0000000000..995ceb624b --- /dev/null +++ b/lib/phonebook/ui/pages/add_edit_groupement_page/groupement_add_edit_page.dart @@ -0,0 +1,104 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/admin/tools/constants.dart'; +import 'package:titan/phonebook/class/association_groupement.dart'; +import 'package:titan/phonebook/providers/association_groupement_list_provider.dart'; +import 'package:titan/phonebook/providers/association_groupement_provider.dart'; +import 'package:titan/phonebook/tools/constants.dart'; +import 'package:titan/phonebook/ui/phonebook.dart'; +import 'package:titan/tools/constants.dart'; +import 'package:titan/tools/token_expire_wrapper.dart'; +import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/tools/ui/styleguide/button.dart'; +import 'package:titan/tools/ui/styleguide/text_entry.dart'; + +class AssociationGroupementAddEditPage extends HookConsumerWidget { + const AssociationGroupementAddEditPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final associationGroupement = ref.watch(associationGroupementProvider); + final associaitonGroupementListNotifier = ref.watch( + associationGroupementListProvider.notifier, + ); + final name = useTextEditingController(text: associationGroupement.name); + void showSnackBarWithContext(String message) { + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text(message))); + } + + return PhonebookTemplate( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 30.0), + child: Column( + children: [ + const SizedBox(height: 30), + Align( + alignment: Alignment.centerLeft, + child: Text( + associationGroupement.id.isNotEmpty + ? PhonebookTextConstants.editAssociationGroupement + : PhonebookTextConstants.addAssociationGroupement, + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w700, + color: ColorConstants.title, + ), + ), + ), + const SizedBox(height: 30), + TextEntry( + controller: name, + label: AdminTextConstants.name, + canBeEmpty: false, + ), + const SizedBox(height: 50), + Button( + text: AdminTextConstants.add, + onPressed: () async { + if (name.text.isEmpty) { + showSnackBarWithContext(AdminTextConstants.emptyFieldError); + return; + } + await tokenExpireWrapper(ref, () async { + if (associationGroupement.id.isNotEmpty) { + final value = await associaitonGroupementListNotifier + .updateAssociationGroupement( + AssociationGroupement( + id: associationGroupement.id, + name: name.text, + ), + ); + if (value) { + showSnackBarWithContext( + PhonebookTextConstants.addedAssociation, + ); + QR.back(); + } else { + showSnackBarWithContext(AdminTextConstants.updatingError); + } + return; + } + final value = await associaitonGroupementListNotifier + .createAssociationGroupement( + AssociationGroupement(id: "", name: name.text), + ); + if (value) { + showSnackBarWithContext( + PhonebookTextConstants.addedAssociation, + ); + QR.back(); + } else { + showSnackBarWithContext(AdminTextConstants.addingError); + } + }); + }, + ), + ], + ), + ), + ); + } +} diff --git a/lib/phonebook/ui/pages/admin_page/admin_page.dart b/lib/phonebook/ui/pages/admin_page/admin_page.dart index a87469aa18..7eb4bf54dd 100644 --- a/lib/phonebook/ui/pages/admin_page/admin_page.dart +++ b/lib/phonebook/ui/pages/admin_page/admin_page.dart @@ -8,7 +8,7 @@ import 'package:titan/phonebook/providers/phonebook_admin_provider.dart'; import 'package:titan/phonebook/providers/roles_tags_provider.dart'; import 'package:titan/phonebook/router.dart'; import 'package:titan/phonebook/tools/constants.dart'; -import 'package:titan/phonebook/ui/components/research_bar.dart'; +import 'package:titan/phonebook/ui/components/association_research_bar.dart'; import 'package:titan/phonebook/ui/phonebook.dart'; import 'package:titan/phonebook/ui/pages/admin_page/editable_association_card.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; @@ -38,55 +38,57 @@ class AdminPage extends HookConsumerWidget { await associationListNotifier.loadAssociations(); await roleNotifier.loadRolesTags(); }, - child: Column( - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), - child: AssociationResearchBar(), - ), - Async2Child( - values: Tuple2(associationList, associationGroupementList), - builder: (context, associations, associationGroupements) { - return Column( - children: [ - ListItem( - title: "Ajouter une association", - icon: HeroIcon( - HeroIcons.plus, - size: 40, - color: Colors.grey.shade500, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), + child: Column( + children: [ + AssociationResearchBar(), + Async2Children( + values: Tuple2(associationList, associationGroupementList), + builder: (context, associations, associationGroupements) { + return Column( + children: [ + ListItem( + title: "Ajouter une association", + icon: HeroIcon( + HeroIcons.plus, + size: 40, + color: Colors.grey.shade500, + ), + onTap: isPhonebookAdmin + ? () { + QR.to( + PhonebookRouter.root + + PhonebookRouter.admin + + PhonebookRouter.createAssociaiton, + ); + } + : null, ), - onTap: isPhonebookAdmin - ? () { - QR.to( - PhonebookRouter.root + - PhonebookRouter.admin + - PhonebookRouter.createAssociaiton, - ); - } - : null, - ), - SizedBox(height: 5), - if (associations.isEmpty) - const Center( - child: Text(PhonebookTextConstants.noAssociationFound), - ) - else - ...associationFilteredList.map( - (association) => EditableAssociationCard( - association: association, - groupement: associationGroupements.firstWhere( - (groupement) => - groupement.id == association.groupementId, + SizedBox(height: 5), + if (associations.isEmpty) + const Center( + child: Text( + PhonebookTextConstants.noAssociationFound, + ), + ) + else + ...associationFilteredList.map( + (association) => EditableAssociationCard( + association: association, + groupement: associationGroupements.firstWhere( + (groupement) => + groupement.id == association.groupementId, + ), + isPhonebookAdmin: isPhonebookAdmin, ), - isPhonebookAdmin: isPhonebookAdmin, ), - ), - ], - ); - }, - ), - ], + ], + ); + }, + ), + ], + ), ), ), ); diff --git a/lib/phonebook/ui/pages/admin_page/edition_modal.dart b/lib/phonebook/ui/pages/admin_page/edition_modal.dart index f43d851845..83d5a89834 100644 --- a/lib/phonebook/ui/pages/admin_page/edition_modal.dart +++ b/lib/phonebook/ui/pages/admin_page/edition_modal.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/phonebook/class/association.dart'; @@ -11,7 +12,6 @@ import 'package:titan/phonebook/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; import 'package:titan/tools/ui/styleguide/button.dart'; -import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; class AssociationEditionModal extends HookConsumerWidget { final Association association; @@ -26,6 +26,7 @@ class AssociationEditionModal extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + ValueNotifier isSuppresion = useState(false); final associationGroupementsNotifier = ref.watch( associationGroupementProvider.notifier, ); @@ -38,129 +39,120 @@ class AssociationEditionModal extends HookConsumerWidget { return BottomModalTemplate( title: "title", + type: isSuppresion.value ? BottomModalType.danger : BottomModalType.main, child: SingleChildScrollView( child: Column( - children: [ - Button( - text: "Modifier les informations", - onPressed: () { - associationGroupementsNotifier.setAssociationGroupement( - groupement, - ); - associationNotifier.setAssociation(association); - QR.to( - PhonebookRouter.root + - PhonebookRouter.admin + - PhonebookRouter.editAssociation, - ); - }, - ), - SizedBox(height: 5), - Button( - text: "Gérer les groupes", - onPressed: () { - associationGroupementsNotifier.setAssociationGroupement( - groupement, - ); - associationNotifier.setAssociation(association); - QR.to( - PhonebookRouter.root + - PhonebookRouter.admin + - PhonebookRouter.editAssociation, - ); - }, - ), - SizedBox(height: 5), - Button( - text: "Gérer les membres", - onPressed: () { - associationGroupementsNotifier.setAssociationGroupement( - groupement, - ); - associationNotifier.setAssociation(association); - QR.to( - PhonebookRouter.root + - PhonebookRouter.admin + - PhonebookRouter.editAssociation, - ); - }, - ), - SizedBox(height: 15), - Button.danger( - text: "Passer au mandat ${association.mandateYear + 1}", - onPressed: () { - return null; - }, - ), - SizedBox(height: 5), - ...association.deactivated - ? [ - Button.danger( - text: "Réactiver l'association", - onPressed: () {}, - ), - SizedBox(height: 5), - ] - : [SizedBox.shrink()], - Button.danger( - text: association.deactivated - ? "Supprimer l'association" - : "Désactiver l'association", - onPressed: () async { - association.deactivated - ? await showDialog( - context: context, - builder: (context) { - return CustomDialogBox( - title: PhonebookTextConstants.deleting, - descriptions: - PhonebookTextConstants.deleteAssociation, - onYes: () async { - final result = await associationListNotifier - .deleteAssociation(association); - if (result) { - displayToastWithContext( - TypeMsg.msg, - PhonebookTextConstants.deletedAssociation, - ); - } else { - displayToastWithContext( - TypeMsg.error, - PhonebookTextConstants.deletingError, - ); - } - }, - ); - }, - ) - : await showDialog( - context: context, - builder: (context) { - return CustomDialogBox( - title: PhonebookTextConstants.deactivating, - descriptions: - PhonebookTextConstants.deactivateAssociation, - onYes: () async { - final result = await associationListNotifier - .deactivateAssociation(association); - if (result) { - displayToastWithContext( - TypeMsg.msg, - PhonebookTextConstants.deactivatedAssociation, - ); - } else { - displayToastWithContext( - TypeMsg.error, - PhonebookTextConstants.deactivatingError, - ); - } - }, - ); - }, + children: !isSuppresion.value + ? [ + Button( + text: "Modifier les informations", + onPressed: () { + associationGroupementsNotifier.setAssociationGroupement( + groupement, ); - }, - ), - ], + associationNotifier.setAssociation(association); + QR.to( + PhonebookRouter.root + + PhonebookRouter.admin + + PhonebookRouter.editAssociation, + ); + }, + ), + SizedBox(height: 5), + Button( + text: "Gérer les groupes", + onPressed: () { + associationGroupementsNotifier.setAssociationGroupement( + groupement, + ); + associationNotifier.setAssociation(association); + QR.to( + PhonebookRouter.root + + PhonebookRouter.admin + + PhonebookRouter.editAssociation, + ); + }, + ), + SizedBox(height: 5), + Button( + text: "Gérer les membres", + onPressed: () { + associationGroupementsNotifier.setAssociationGroupement( + groupement, + ); + associationNotifier.setAssociation(association); + QR.to( + PhonebookRouter.root + + PhonebookRouter.admin + + PhonebookRouter.editAssociation, + ); + }, + ), + SizedBox(height: 15), + Button.danger( + text: "Passer au mandat ${association.mandateYear + 1}", + onPressed: () { + return null; + }, + ), + SizedBox(height: 5), + ...association.deactivated + ? [ + Button.danger( + text: "Réactiver l'association", + onPressed: () {}, + ), + SizedBox(height: 5), + ] + : [SizedBox.shrink()], + Button.danger( + text: association.deactivated + ? "Supprimer l'association" + : "Désactiver l'association", + onPressed: () => isSuppresion.value = true, + ), + ] + : [ + Button.danger( + text: PhonebookTextConstants.confirm, + onPressed: association.deactivated + ? () async { + final result = await associationListNotifier + .deleteAssociation(association); + if (result) { + displayToastWithContext( + TypeMsg.msg, + PhonebookTextConstants.deletedAssociation, + ); + } else { + displayToastWithContext( + TypeMsg.error, + PhonebookTextConstants.deletingError, + ); + } + } + : () async { + final result = await associationListNotifier + .deactivateAssociation(association); + if (result) { + displayToastWithContext( + TypeMsg.msg, + PhonebookTextConstants.deactivatedAssociation, + ); + } else { + displayToastWithContext( + TypeMsg.error, + PhonebookTextConstants.deactivatingError, + ); + } + }, + ), + SizedBox(height: 5), + Button( + text: PhonebookTextConstants.cancel, + onPressed: () => isSuppresion.value = false, + ), + ], ), ), ); diff --git a/lib/phonebook/ui/pages/association_add_edit_page/association_add_edit_page.dart b/lib/phonebook/ui/pages/association_add_edit_page/association_add_edit_page.dart new file mode 100644 index 0000000000..a4e3d2d520 --- /dev/null +++ b/lib/phonebook/ui/pages/association_add_edit_page/association_add_edit_page.dart @@ -0,0 +1,135 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/admin/tools/constants.dart'; +import 'package:titan/phonebook/providers/association_groupement_provider.dart'; +import 'package:titan/phonebook/providers/association_list_provider.dart'; +import 'package:titan/phonebook/providers/association_provider.dart'; +import 'package:titan/phonebook/router.dart'; +import 'package:titan/phonebook/tools/constants.dart'; +import 'package:titan/phonebook/ui/components/groupement_bar.dart'; +import 'package:titan/phonebook/ui/phonebook.dart'; +import 'package:titan/tools/constants.dart'; +import 'package:titan/tools/token_expire_wrapper.dart'; +import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/tools/ui/styleguide/button.dart'; +import 'package:titan/tools/ui/styleguide/text_entry.dart'; + +class AssociationAddEditPage extends HookConsumerWidget { + final scrollKey = GlobalKey(); + AssociationAddEditPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final key = GlobalKey(); + final associationListNotifier = ref.watch(associationListProvider.notifier); + final associations = ref.watch(associationListProvider); + final association = ref.watch(associationProvider); + final associationNotifier = ref.watch(associationProvider.notifier); + final associationGroupement = ref.watch(associationGroupementProvider); + final name = useTextEditingController(text: association.name); + final description = useTextEditingController(text: association.description); + + void showSnackBarWithContext(String message) { + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text(message))); + } + + return PhonebookTemplate( + child: SingleChildScrollView( + physics: const AlwaysScrollableScrollPhysics( + parent: BouncingScrollPhysics(), + ), + child: Form( + key: key, + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 30.0), + child: Column( + children: [ + const SizedBox(height: 30), + Align( + alignment: Alignment.centerLeft, + child: Text( + association.id == "" + ? PhonebookTextConstants.addAssociation + : PhonebookTextConstants.editAssociation, + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w700, + color: ColorConstants.title, + ), + ), + ), + const SizedBox(height: 30), + AssociationGroupementBar(editable: true), + Container(margin: const EdgeInsets.symmetric(vertical: 10)), + TextEntry( + controller: name, + label: AdminTextConstants.name, + canBeEmpty: false, + ), + const SizedBox(height: 30), + TextEntry( + controller: description, + label: AdminTextConstants.description, + canBeEmpty: true, + ), + const SizedBox(height: 50), + Button( + onPressed: () async { + if (!key.currentState!.validate()) { + showSnackBarWithContext( + PhonebookTextConstants.emptyFieldError, + ); + return; + } + if (associationGroupement.id == '') { + showSnackBarWithContext( + PhonebookTextConstants.emptyKindError, + ); + return; + } + await tokenExpireWrapper(ref, () async { + final value = await associationListNotifier + .createAssociation( + association.copyWith( + name: name.text, + description: description.text, + groupementId: associationGroupement.id, + mandateYear: DateTime.now().year, + ), + ); + if (value) { + showSnackBarWithContext( + PhonebookTextConstants.addedAssociation, + ); + associations.when( + data: (d) { + associationNotifier.setAssociation(d.last); + QR.to( + PhonebookRouter.root + + PhonebookRouter.admin + + PhonebookRouter.editAssociation, + ); + }, + error: (e, s) => showSnackBarWithContext( + PhonebookTextConstants.errorAssociationLoading, + ), + loading: () {}, + ); + } else { + showSnackBarWithContext(AdminTextConstants.addingError); + } + }); + }, + text: AdminTextConstants.add, + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/phonebook/ui/pages/association_creation_page/association_creation_page.dart b/lib/phonebook/ui/pages/association_creation_page/association_creation_page.dart deleted file mode 100644 index 5c083cf61c..0000000000 --- a/lib/phonebook/ui/pages/association_creation_page/association_creation_page.dart +++ /dev/null @@ -1,169 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/phonebook/class/association.dart'; -import 'package:titan/phonebook/providers/association_groupement_provider.dart'; -import 'package:titan/phonebook/providers/association_list_provider.dart'; -import 'package:titan/phonebook/providers/association_provider.dart'; -import 'package:titan/phonebook/router.dart'; -import 'package:titan/phonebook/tools/constants.dart'; -import 'package:titan/phonebook/ui/components/groupement_bar.dart'; -import 'package:titan/phonebook/ui/phonebook.dart'; -import 'package:titan/tools/constants.dart'; -import 'package:titan/tools/functions.dart'; -import 'package:titan/tools/token_expire_wrapper.dart'; -import 'package:titan/tools/ui/builders/waiting_button.dart'; -import 'package:titan/tools/ui/layouts/add_edit_button_layout.dart'; -import 'package:titan/tools/ui/widgets/text_entry.dart'; -import 'package:qlevar_router/qlevar_router.dart'; -import 'package:titan/l10n/app_localizations.dart'; - -class AssociationCreationPage extends HookConsumerWidget { - final scrollKey = GlobalKey(); - AssociationCreationPage({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final key = GlobalKey(); - final name = useTextEditingController(); - final description = useTextEditingController(); - final associationListNotifier = ref.watch(associationListProvider.notifier); - final associations = ref.watch(associationListProvider); - final associationNotifier = ref.watch(associationProvider.notifier); - final associationGroupement = ref.watch(associationGroupementProvider); - void displayToastWithContext(TypeMsg type, String msg) { - displayToast(context, type, msg); - } - - return PhonebookTemplate( - child: SingleChildScrollView( - physics: const AlwaysScrollableScrollPhysics( - parent: BouncingScrollPhysics(), - ), - child: Form( - key: key, - child: Column( - children: [ - const SizedBox(height: 30), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 30.0), - child: Align( - alignment: Alignment.centerLeft, - child: Text( - AppLocalizations.of(context)!.phonebookAddAssociation, - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.w700, - color: ColorConstants.gradient1, - ), - ), - ), - ), - const SizedBox(height: 30), - AssociationGroupementBar(key: scrollKey), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 30.0), - child: Column( - children: [ - Container(margin: const EdgeInsets.symmetric(vertical: 10)), - TextEntry( - controller: name, - label: AppLocalizations.of(context)!.adminName, - canBeEmpty: false, - ), - const SizedBox(height: 30), - TextEntry( - controller: description, - label: AppLocalizations.of(context)!.adminDescription, - canBeEmpty: true, - ), - const SizedBox(height: 50), - WaitingButton( - builder: (child) => AddEditButtonLayout( - colors: const [ - ColorConstants.gradient1, - ColorConstants.gradient2, - ], - child: child, - ), - onTap: () async { - if (!key.currentState!.validate()) { - displayToastWithContext( - TypeMsg.error, - AppLocalizations.of( - context, - )!.phonebookEmptyFieldError, - ); - return; - } - if (associationGroupement == '') { - displayToastWithContext( - TypeMsg.error, - AppLocalizations.of( - context, - )!.phonebookEmptyKindError, - ); - return; - } - await tokenExpireWrapper(ref, () async { - final addedMsg = AppLocalizations.of( - context, - )!.phonebookAddedAssociation; - final adminAddingErrorMsg = AppLocalizations.of( - context, - )!.adminAddingError; - final value = await associationListNotifier - .createAssociation( - Association.empty().copyWith( - name: name.text, - description: description.text, - groupementId: associationGroupement.id, - mandateYear: DateTime.now().year, - ), - ); - if (value) { - displayToastWithContext(TypeMsg.msg, addedMsg); - associations.when( - data: (d) { - associationNotifier.setAssociation(d.last); - QR.to( - PhonebookRouter.root + - PhonebookRouter.admin + - PhonebookRouter.editAssociation, - ); - }, - error: (e, s) => displayToastWithContext( - TypeMsg.error, - AppLocalizations.of( - context, - )!.phonebookErrorAssociationLoading, - ), - loading: () {}, - ); - } else { - displayToastWithContext( - TypeMsg.error, - adminAddingErrorMsg, - ); - } - }); - }, - child: Text( - AppLocalizations.of(context)!.adminAdd, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - color: Color.fromARGB(255, 255, 255, 255), - ), - ), - ), - ], - ), - ), - ], - ), - ), - ), - ); - } -} diff --git a/lib/phonebook/ui/pages/association_creation_page/text_entry.dart b/lib/phonebook/ui/pages/association_creation_page/text_entry.dart deleted file mode 100644 index de09a541aa..0000000000 --- a/lib/phonebook/ui/pages/association_creation_page/text_entry.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:titan/tools/constants.dart'; -import 'package:titan/l10n/app_localizations.dart'; - -class AddAssociationTextEntry extends StatelessWidget { - final TextEditingController controller; - final String title; - final bool canBeEmpty; - const AddAssociationTextEntry({ - super.key, - required this.controller, - required this.title, - required this.canBeEmpty, - }); - - @override - Widget build(BuildContext context) { - return Container( - margin: const EdgeInsets.symmetric(vertical: 20), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.centerLeft, - child: Text( - title, - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w400, - color: Color.fromARGB(255, 158, 158, 158), - ), - ), - ), - SizedBox( - child: TextFormField( - controller: controller, - decoration: const InputDecoration( - isDense: true, - focusedBorder: UnderlineInputBorder( - borderSide: BorderSide(color: ColorConstants.gradient1), - ), - ), - validator: canBeEmpty - ? null - : (value) { - if (value == null || value.isEmpty) { - return AppLocalizations.of( - context, - )!.adminEmptyFieldError; - } - return null; - }, - ), - ), - ], - ), - ); - } -} diff --git a/lib/phonebook/ui/pages/main_page/main_page.dart b/lib/phonebook/ui/pages/main_page/main_page.dart index 0c491de71a..57d767ad19 100644 --- a/lib/phonebook/ui/pages/main_page/main_page.dart +++ b/lib/phonebook/ui/pages/main_page/main_page.dart @@ -11,7 +11,7 @@ import 'package:titan/phonebook/router.dart'; import 'package:titan/phonebook/tools/constants.dart'; import 'package:titan/phonebook/ui/pages/main_page/association_card.dart'; import 'package:titan/phonebook/ui/phonebook.dart'; -import 'package:titan/phonebook/ui/components/research_bar.dart'; +import 'package:titan/phonebook/ui/components/association_research_bar.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; import 'package:titan/tools/ui/styleguide/icon_button.dart'; @@ -45,13 +45,13 @@ class PhonebookMainPage extends HookConsumerWidget { await associationGroupementListNotifier.loadAssociationGroupement(); await associationListNotifier.loadAssociations(); }, - child: Column( - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), - child: Row( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), + child: Column( + children: [ + Row( children: [ - AssociationResearchBar(), + Expanded(child: AssociationResearchBar()), if (isPhonebookAdmin || isAdmin) ...[ SizedBox(width: 10), CustomIconButton( diff --git a/lib/tools/ui/builders/async_child.dart b/lib/tools/ui/builders/async_child.dart index 1114ddf28d..0853b2ea83 100644 --- a/lib/tools/ui/builders/async_child.dart +++ b/lib/tools/ui/builders/async_child.dart @@ -52,18 +52,16 @@ Widget handleLoadingAndError( Color? loaderColor, }) { final nonNullOrElseBuilder = orElseBuilder ?? (context, child) => child; - final nonNullLoadingBuilder = - loadingBuilder ?? (context) => Loader(color: loaderColor); - final nonNullErrorBuilder = - errorBuilder ?? - (error, stack) => Center( - child: Text( - "${TextConstants.error}:$error", - style: TextStyle(color: loaderColor), - ), - ); - if (values.any((test) => test.hasError)) { - final firstError = values.firstWhere((test) => test.hasError); + if (values.any((value) => value.hasError)) { + final nonNullErrorBuilder = + errorBuilder ?? + (error, stack) => Center( + child: Text( + "${TextConstants.error}:$error", + style: TextStyle(color: loaderColor), + ), + ); + final firstError = values.firstWhere((value) => value.hasError); final error = firstError.error; final stackTrace = firstError.stackTrace; return nonNullOrElseBuilder( @@ -71,20 +69,22 @@ Widget handleLoadingAndError( nonNullErrorBuilder(error, stackTrace), ); } - if (values.any((test) => test.isLoading)) { + if (values.any((value) => value.isLoading)) { + final nonNullLoadingBuilder = + loadingBuilder ?? (context) => Loader(color: loaderColor); return nonNullOrElseBuilder(context, nonNullLoadingBuilder(context)); } return nonNullOrElseBuilder(context, const SizedBox.shrink()); } -class Async2Child extends StatelessWidget { +class Async2Children extends StatelessWidget { final Tuple2, AsyncValue> values; - final Widget Function(BuildContext context, P values1, Q values2) builder; + final Widget Function(BuildContext context, P value1, Q value2) builder; final Widget Function(Object? error, StackTrace? stack)? errorBuilder; final Widget Function(BuildContext context)? loadingBuilder; final Widget Function(BuildContext context, Widget child)? orElseBuilder; final Color? loaderColor; - const Async2Child({ + const Async2Children({ super.key, required this.values, required this.builder, @@ -96,8 +96,7 @@ class Async2Child extends StatelessWidget { @override Widget build(BuildContext context) { List listValues = [values.item1, values.item2]; - if (listValues.any((test) => test.hasError) || - listValues.any((test) => test.isLoading)) { + if (listValues.any((value) => value.hasError || value.isLoading)) { return handleLoadingAndError( listValues, context, @@ -111,15 +110,15 @@ class Async2Child extends StatelessWidget { } } -class Async3Child extends StatelessWidget { +class Async3Children extends StatelessWidget { final Tuple3, AsyncValue, AsyncValue> values; - final Widget Function(BuildContext context, P values1, Q values2, R value3) + final Widget Function(BuildContext context, P value1, Q value2, R value3) builder; final Widget Function(Object? error, StackTrace? stack)? errorBuilder; final Widget Function(BuildContext context)? loadingBuilder; final Widget Function(BuildContext context, Widget child)? orElseBuilder; final Color? loaderColor; - const Async3Child({ + const Async3Children({ super.key, required this.values, required this.builder, @@ -131,8 +130,7 @@ class Async3Child extends StatelessWidget { @override Widget build(BuildContext context) { List listValues = [values.item1, values.item2, values.item3]; - if (listValues.any((test) => test.hasError) || - listValues.any((test) => test.isLoading)) { + if (listValues.any((value) => value.hasError || value.isLoading)) { return handleLoadingAndError( listValues, context, @@ -151,14 +149,14 @@ class Async3Child extends StatelessWidget { } } -class Async4Child extends StatelessWidget { +class Async4Children extends StatelessWidget { final Tuple4, AsyncValue, AsyncValue, AsyncValue> values; final Widget Function( BuildContext context, - P values1, + P value1, Q value2, - R values3, + R value3, S value4, ) builder; @@ -166,7 +164,7 @@ class Async4Child extends StatelessWidget { final Widget Function(BuildContext context)? loadingBuilder; final Widget Function(BuildContext context, Widget child)? orElseBuilder; final Color? loaderColor; - const Async4Child({ + const Async4Children({ super.key, required this.values, required this.builder, @@ -178,8 +176,7 @@ class Async4Child extends StatelessWidget { @override Widget build(BuildContext context) { List listValues = [values.item1, values.item2, values.item3]; - if (listValues.any((test) => test.hasError) || - listValues.any((test) => test.isLoading)) { + if (listValues.any((value) => value.hasError || value.isLoading)) { return handleLoadingAndError( listValues, context, @@ -199,7 +196,7 @@ class Async4Child extends StatelessWidget { } } -class Async5Child extends StatelessWidget { +class Async5Children extends StatelessWidget { final Tuple5< AsyncValue

, AsyncValue, @@ -210,9 +207,9 @@ class Async5Child extends StatelessWidget { values; final Widget Function( BuildContext context, - P values1, + P value1, Q value2, - R values3, + R value3, S value4, T value5, ) @@ -221,7 +218,7 @@ class Async5Child extends StatelessWidget { final Widget Function(BuildContext context)? loadingBuilder; final Widget Function(BuildContext context, Widget child)? orElseBuilder; final Color? loaderColor; - const Async5Child({ + const Async5Children({ super.key, required this.values, required this.builder, @@ -239,8 +236,7 @@ class Async5Child extends StatelessWidget { values.item4, values.item5, ]; - if (listValues.any((test) => test.hasError) || - listValues.any((test) => test.isLoading)) { + if (listValues.any((value) => value.hasError || value.isLoading)) { return handleLoadingAndError( listValues, context, diff --git a/lib/tools/ui/styleguide/horizontal_multi_select.dart b/lib/tools/ui/styleguide/horizontal_multi_select.dart index b28e934218..fbd95ef1ba 100644 --- a/lib/tools/ui/styleguide/horizontal_multi_select.dart +++ b/lib/tools/ui/styleguide/horizontal_multi_select.dart @@ -4,25 +4,28 @@ import 'package:titan/tools/ui/styleguide/item_chip.dart'; class HorizontalMultiSelect extends HookWidget { final List items; + final T? selectedItem; final Widget Function(BuildContext context, T item, int index, bool selected) itemBuilder; final Widget? firstChild; final Function(T item)? onItemSelected; final Function(T item)? onLongPress; + final Function(T item1, T item2)? isEqual; final Widget? title; const HorizontalMultiSelect({ super.key, required this.items, + this.selectedItem, required this.itemBuilder, this.firstChild, this.onItemSelected, this.onLongPress, + this.isEqual, this.title, }); @override Widget build(BuildContext context) { - final selected = useState(null); return ListView.builder( scrollDirection: Axis.horizontal, clipBehavior: Clip.none, @@ -32,14 +35,16 @@ class HorizontalMultiSelect extends HookWidget { return firstChild!; } final item = items[index - (firstChild != null ? 1 : 0)]; + final selected = isEqual != null + ? selectedItem != null + ? isEqual!(item, selectedItem as T) + : false + : item == selectedItem; return ItemChip( - selected: selected.value == index, - onTap: () { - selected.value = index; - onItemSelected?.call(item); - }, - onLongPress: () => onLongPress?.call(item), - child: itemBuilder(context, item, index, selected.value == index), + selected: selected, + onTap: () => onItemSelected != null ? onItemSelected!(item) : null, + onLongPress: () => onLongPress != null ? onLongPress!(item) : null, + child: itemBuilder(context, item, index, selected), ); }, ); From 9121cafa66d790f187fed5cae4a02dbf9bec9d74 Mon Sep 17 00:00:00 2001 From: Thonyk Date: Tue, 29 Jul 2025 21:42:05 +0200 Subject: [PATCH 087/473] feat: rebase and misc --- lib/l10n/app_en.arb | 1409 +++++----- lib/l10n/app_fr.arb | 2499 +++++++++-------- lib/l10n/app_localizations.dart | 18 + lib/l10n/app_localizations_en.dart | 11 + lib/l10n/app_localizations_fr.dart | 11 + .../components/association_research_bar.dart | 4 +- .../ui/components/groupement_bar.dart | 17 +- .../groupement_add_edit_page.dart | 33 +- .../ui/pages/admin_page/admin_page.dart | 10 +- .../admin_page/editable_association_card.dart | 21 +- .../ui/pages/admin_page/edition_modal.dart | 56 +- .../association_add_edit_page.dart | 29 +- .../association_information_editor.dart | 1 - .../association_groups_page.dart | 185 ++ .../association_members_page.dart | 0 .../ui/pages/main_page/main_page.dart | 62 +- lib/tools/ui/builders/async_child.dart | 3 +- lib/tools/ui/styleguide/item_chip.dart | 6 +- 18 files changed, 2328 insertions(+), 2047 deletions(-) create mode 100644 lib/phonebook/ui/pages/association_groups_page/association_groups_page.dart create mode 100644 lib/phonebook/ui/pages/association_members_page/association_members_page.dart diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 4bbbec2d6d..3498729298 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1,705 +1,708 @@ - { - "@@locale": "en", - "adminAccountTypes": "Account types", - "adminAdd": "Add", - "adminAddGroup": "Add group", - "adminAddMember": "Add member", - "adminAddedGroup": "Group created", - "adminAddedLoaner": "Lender added", - "adminAddedMember": "Member added", - "adminAddingError": "Error while adding", - "adminAddingMember": "Adding a member", - "adminAddLoaningGroup": "Add loaning group", - "adminAddSchool": "Add school", - "adminAddStructure": "Add structure", - "adminAddedSchool": "School created", - "adminAddedStructure": "Structure added", - "adminEditedStructure": "Structure edited", - "adminAdministration": "Administration", - "adminAssociationMembership": "Membership", - "adminAssociationMembershipName": "Membership name", - "adminAssociationsMemberships": "Memberships", - "adminClearFilters": "Clear filters", - "adminCreateAssociationMembership": "Create membership", - "adminCreatedAssociationMembership": "Membership created", - "adminCreationError": "Error during creation", - "adminDateError": "Start date must be before end date", - "adminDelete": "Delete", - "adminDeleteAssociationMembership": "Delete membership?", - "adminDeletedAssociationMembership": "Membership deleted", - "adminDeleteGroup": "Delete group?", - "adminDeletedGroup": "Group deleted", - "adminDeleteSchool": "Delete school?", - "adminDeletedSchool": "School deleted", - "adminDeleting": "Deleting", - "adminDeletingError": "Error while deleting", - "adminDescription": "Description", - "adminEclSchool": "Centrale Lyon", - "adminEdit": "Edit", - "adminEditStructure": "Edit structure", - "adminEditMembership": "Edit membership", - "adminEmptyDate": "Empty date", - "adminEmptyFieldError": "Name cannot be empty", - "adminEmailRegex": "Email Regex", - "adminEmptyUser": "Empty user", - "adminEndDate": "End date", - "adminEndDateMaximal": "Maximum end date", - "adminEndDateMinimal": "Minimum end date", - "adminError": "Error", - "adminFilters": "Filters", - "adminGroup": "Group", - "adminGroups": "Groups", - "adminLoaningGroup": "Loaning group", - "adminLooking": "Searching", - "adminManager": "Structure administrator", - "adminMaximum": "Maximum", - "adminMembers": "Members", - "adminMembershipAddingError": "Error while adding (likely due to overlapping dates)", - "adminMemberships": "Memberships", - "adminMembershipUpdatingError": "Error while updating (likely due to overlapping dates)", - "adminMinimum": "Minimum", - "adminModifyModuleVisibility": "Module visibility", - "adminMyEclPay": "MyECLPay", - "adminName": "Name", - "adminNoManager": "No manager selected", - "adminNoMember": "No member", - "adminNoMoreLoaner": "No lender available", - "adminNoSchool": "No school", - "adminRemoveGroupMember": "Remove member from group?", - "adminResearch": "Search", - "adminSchools": "Schools", - "adminStructures": "Structures", - "adminStartDate": "Start date", - "adminStartDateMaximal": "Maximum start date", - "adminStartDateMinimal": "Minimum start date", - "adminUpdatedAssociationMembership": "Membership updated", - "adminUpdatedGroup": "Group updated", - "adminUpdatedMembership": "Membership updated", - "adminUpdatingError": "Error while updating", - "adminUser": "User", - "adminValidateFilters": "Apply filters", - "adminVisibilities": "Visibilities", - "advertAdd": "Add", - "advertAddedAdvert": "Advert published", - "advertAddedAnnouncer": "Announcer added", - "advertAddingError": "Error while adding", - "advertAdmin": "Admin", - "advertAdvert": "Advert", - "advertChoosingAnnouncer": "Please choose an announcer", - "advertChoosingPoster": "Please choose an image", - "advertContent": "Content", - "advertDeleteAdvert": "Delete ad?", - "advertDeleteAnnouncer": "Delete announcer?", - "advertDeleting": "Deleting", - "advertEdit": "Edit", - "advertEditedAdvert": "Advert edited", - "advertEditingError": "Error while editing", - "advertGroupAdvert": "Group", - "advertIncorrectOrMissingFields": "Incorrect or missing fields", - "advertInvalidNumber": "Please enter a number", - "advertManagement": "Management", - "advertModifyAnnouncingGroup": "Edit announcement group", - "advertNoMoreAnnouncer": "No more announcers available", - "advertNoValue": "Please enter a value", - "advertPositiveNumber": "Please enter a positive number", - "advertRemovedAnnouncer": "Announcer removed", - "advertRemovingError": "Error during removal", - "advertTags": "Tags", - "advertTitle": "Title", - "advertMonthJan": "Jan", - "advertMonthFeb": "Feb", - "advertMonthMar": "Mar", - "advertMonthApr": "Apr", - "advertMonthMay": "May", - "advertMonthJun": "Jun", - "advertMonthJul": "Jul", - "advertMonthAug": "Aug", - "advertMonthSep": "Sep", - "advertMonthOct": "Oct", - "advertMonthNov": "Nov", - "advertMonthDec": "Dec", - "amapAccounts": "Accounts", - "amapAdd": "Add", - "amapAddDelivery": "Add delivery", - "amapAddedCommand": "Order added", - "amapAddedOrder": "Order added", - "amapAddedProduct": "Product added", - "amapAddedUser": "User added", - "amapAddProduct": "Add product", - "amapAddUser": "Add user", - "amapAddingACommand": "Add an order", - "amapAddingCommand": "Add the order", - "amapAddingError": "Error while adding", - "amapAddingProduct": "Add a product", - "amapAddOrder": "Add an order", - "amapAdmin": "Admin", - "amapAlreadyExistCommand": "An order already exists for this date", - "amapAmap": "Amap", - "amapAmount": "Balance", - "amapArchive": "Archive", - "amapArchiveDelivery": "Archive", - "amapArchivingDelivery": "Archiving delivery", - "amapCategory": "Category", - "amapCloseDelivery": "Lock", - "amapCommandDate": "Order date", - "amapCommandProducts": "Order products", - "amapConfirm": "Confirm", - "amapContact": "Association contacts", - "amapCreateCategory": "Create category", - "amapDelete": "Delete", - "amapDeleteDelivery": "Delete delivery?", - "amapDeleteDeliveryDescription": "Are you sure you want to delete this delivery?", - "amapDeletedDelivery": "Delivery deleted", - "amapDeletedOrder": "Order deleted", - "amapDeletedProduct": "Product deleted", - "amapDeleteProduct": "Delete product?", - "amapDeleteProductDescription": "Are you sure you want to delete this product?", - "amapDeleting": "Deleting", - "amapDeletingDelivery": "Delete delivery?", - "amapDeletingError": "Error while deleting", - "amapDeletingOrder": "Delete order?", - "amapDeletingProduct": "Delete product?", - "amapDeliver": "Delivery completed?", - "amapDeliveries": "Deliveries", - "amapDeliveringDelivery": "Are all orders delivered?", - "amapDelivery": "Delivery", - "amapDeliveryArchived": "Delivery archived", - "amapDeliveryDate": "Delivery date", - "amapDeliveryDelivered": "Delivery completed", - "amapDeliveryHistory": "Delivery history", - "amapDeliveryList": "Delivery list", - "amapDeliveryLocked": "Delivery locked", - "amapDeliveryOn": "Delivery on", - "amapDeliveryOpened": "Delivery opened", - "amapDeliveryNotArchived": "Delivery not archived", - "amapDeliveryNotLocked": "Delivery not locked", - "amapDeliveryNotDelivered": "Delivery not completed", - "amapDeliveryNotOpened": "Delivery not opened", - "amapEditDelivery": "Edit delivery", - "amapEditedCommand": "Order edited", - "amapEditingError": "Error while editing", - "amapEditProduct": "Edit product", - "amapEndingDelivery": "End of delivery", - "amapError": "Error", - "amapErrorLink": "Error opening link", - "amapErrorLoadingUser": "Error loading users", - "amapEvening": "Evening", - "amapExpectingNumber": "Please enter a number", - "amapFillField": "Please fill out this field", - "amapHandlingAccount": "Manage accounts", - "amapLoading": "Loading...", - "amapLoadingError": "Loading error", - "amapLock": "Lock", - "amapLocked": "Locked", - "amapLockedDelivery": "Delivery locked", - "amapLockedOrder": "Order locked", - "amapLooking": "Search", - "amapLockingDelivery": "Lock delivery?", - "amapMidDay": "Midday", - "amapMyOrders": "My orders", - "amapName": "Name", - "amapNextStep": "Next step", - "amapNoProduct": "No product", - "amapNoCurrentOrder": "No current order", - "amapNoMoney": "Not enough money", - "amapNoOpennedDelivery": "No open delivery", - "amapNoOrder": "No order", - "amapNoSelectedDelivery": "No delivery selected", - "amapNotEnoughMoney": "Not enough money", - "amapNotPlannedDelivery": "No scheduled delivery", - "amapOneOrder": "order", - "amapOpenDelivery": "Open", - "amapOpened": "Opened", - "amapOpenningDelivery": "Open delivery?", - "amapOrder": "Order", - "amapOrders": "Orders", - "amapPickChooseCategory": "Please enter a value or choose an existing category", - "amapPickDeliveryMoment": "Choose a delivery time", - "amapPresentation": "Presentation", - "amapPresentation1": "The AMAP (association for the preservation of small-scale farming) is a service offered by the Planet&Co association of ECL. You can receive products (fruit and vegetable baskets, juices, jams...) directly on campus!\n\nOrders must be placed before Friday at 9 PM and are delivered on campus on Tuesday from 1:00 PM to 1:45 PM (or from 6:15 PM to 6:30 PM if you can't come at midday) in the M16 hall.\n\nYou can only order if your balance allows it. You can top up your balance via the Lydia collection or by cheque during office hours.\n\nLydia top-up link: ", - "amapPresentation2": "\n\nFeel free to contact us if you have any issues!", - "amapPrice": "Price", - "amapProduct": "product", - "amapProducts": "Products", - "amapProductInDelivery": "Product in an unfinished delivery", - "amapQuantity": "Quantity", - "amapRequiredDate": "Date is required", - "amapSeeMore": "See more", - "amapThe": "The", - "amapUnlock": "Unlock", - "amapUnlockedDelivery": "Delivery unlocked", - "amapUnlockingDelivery": "Unlock delivery?", - "amapUpdate": "Edit", - "amapUpdatedAmount": "Balance updated", - "amapUpdatedOrder": "Order updated", - "amapUpdatedProduct": "Product updated", - "amapUpdatingError": "Update failed", - "amapUsersNotFound": "No users found", - "amapWaiting": "Pending", - "bookingAdd": "Add", - "bookingAddBookingPage": "Request", - "bookingAddRoom": "Add room", - "bookingAddBooking": "Add booking", - "bookingAddedBooking": "Request added", - "bookingAddedRoom": "Room added", - "bookingAddedManager": "Manager added", - "bookingAddingError": "Error while adding", - "bookingAddManager": "Add manager", - "bookingAdminPage": "Admin", - "bookingAllDay": "All day", - "bookingBookedFor": "Booked for", - "bookingBooking": "Booking", - "bookingBookingCreated": "Booking created", - "bookingBookingDemand": "Booking request", - "bookingBookingNote": "Booking note", - "bookingBookingPage": "Booking", - "bookingBookingReason": "Booking reason", - "bookingBy": "by", - "bookingConfirm": "Confirm", - "bookingConfirmation": "Confirmation", - "bookingConfirmBooking": "Confirm the booking?", - "bookingConfirmed": "Confirmed", - "bookingDates": "Dates", - "bookingDecline": "Decline", - "bookingDeclineBooking": "Decline the booking?", - "bookingDeclined": "Declined", - "bookingDelete": "Delete", - "bookingDeleting": "Deleting", - "bookingDeleteBooking": "Deleting", - "bookingDeleteBookingConfirmation": "Are you sure you want to delete this booking?", - "bookingDeletedBooking": "Booking deleted", - "bookingDeletedRoom": "Room deleted", - "bookingDeletedManager": "Manager deleted", - "bookingDeleteRoomConfirmation": "Are you sure you want to delete this room?\n\nThe room must have no current or upcoming bookings to be deleted", - "bookingDeleteManagerConfirmation": "Are you sure you want to delete this manager?\n\nThe manager must not be associated with any room to be deleted", - "bookingDeletingBooking": "Delete the booking?", - "bookingDeletingError": "Error while deleting", - "bookingDeletingRoom": "Delete the room?", - "bookingEdit": "Edit", - "bookingEditBooking": "Edit a booking", - "bookingEditionError": "Error while editing", - "bookingEditedBooking": "Booking edited", - "bookingEditedRoom": "Room edited", - "bookingEditedManager": "Manager edited", - "bookingEditManager": "Edit or delete a manager", - "bookingEditRoom": "Edit or delete a room", - "bookingEndDate": "End date", - "bookingEndHour": "End hour", - "bookingEntity": "For whom?", - "bookingError": "Error", - "bookingEventEvery": "Every", - "bookingHistoryPage": "History", - "bookingIncorrectOrMissingFields": "Incorrect or missing fields", - "bookingInterval": "Interval", - "bookingInvalidIntervalError": "Invalid interval", - "bookingInvalidDates": "Invalid dates", - "bookingInvalidRoom": "Invalid room", - "bookingKeysRequested": "Keys requested", - "bookingManagement": "Management", - "bookingManager": "Manager", - "bookingManagerName": "Manager name", - "bookingMultipleDay": "Multiple days", - "bookingMyBookings": "My bookings", - "bookingNecessaryKey": "Key needed", - "bookingNext": "Next", - "bookingNo": "No", - "bookingNoCurrentBooking": "No current booking", - "bookingNoDateError": "Please choose a date", - "bookingNoAppointmentInReccurence": "No slot exists with these recurrence settings", - "bookingNoDaySelected": "No day selected", - "bookingNoDescriptionError": "Please enter a description", - "bookingNoKeys": "No keys", - "bookingNoNoteError": "Please enter a note", - "bookingNoPhoneRegistered": "Number not provided", - "bookingNoReasonError": "Please enter a reason", - "bookingNoRoomFoundError": "No room registered", - "bookingNoRoomFound": "No room found", - "bookingNote": "Note", - "bookingOther": "Other", - "bookingPending": "Pending", - "bookingPrevious": "Previous", - "bookingReason": "Reason", - "bookingRecurrence": "Recurrence", - "bookingRecurrenceDays": "Recurrence days", - "bookingRecurrenceEndDate": "Recurrence end date", - "bookingRecurrent": "Recurrent", - "bookingRegisteredRooms": "Registered rooms", - "bookingRoom": "Room", - "bookingRoomName": "Room name", - "bookingStartDate": "Start date", - "bookingStartHour": "Start hour", - "bookingWeeks": "Weeks", - "bookingYes": "Yes", - "bookingWeekDayMon": "Monday", - "bookingWeekDayTue": "Tuesday", - "bookingWeekDayWed": "Wednesday", - "bookingWeekDayThu": "Thursday", - "bookingWeekDayFri": "Friday", - "bookingWeekDaySat": "Saturday", - "bookingWeekDaySun": "Sunday", - "cinemaAdd": "Add", - "cinemaAddedSession": "Session added", - "cinemaAddingError": "Error while adding", - "cinemaAddSession": "Add a session", - "cinemaCinema": "Cinema", - "cinemaDeleteSession": "Delete the session?", - "cinemaDeleting": "Deleting", - "cinemaDuration": "Duration", - "cinemaEdit": "Edit", - "cinemaEditedSession": "Session edited", - "cinemaEditingError": "Error while editing", - "cinemaEditSession": "Edit the session", - "cinemaEmptyUrl": "Please enter a URL", - "cinemaImportFromTMDB": "Import from TMDB", - "cinemaIncomingSession": "Now showing", - "cinemaIncorrectOrMissingFields": "Incorrect or missing fields", - "cinemaInvalidUrl": "Invalid URL", - "cinemaGenre": "Genre", - "cinemaName": "Name", - "cinemaNoDateError": "Please enter a date", - "cinemaNoDuration": "Please enter a duration", - "cinemaNoOverview": "No synopsis", - "cinemaNoPoster": "No poster", - "cinemaNoSession": "No session", - "cinemaOverview": "Synopsis", - "cinemaPosterUrl": "Poster URL", - "cinemaSessionDate": "Session day", - "cinemaStartHour": "Start hour", - "cinemaTagline": "Tagline", - "cinemaThe": "The", - "drawerAdmin": "Administration", - "drawerAndroidAppLink": "https://play.google.com/store/apps/details?id=fr.myecl.titan", - "drawerCopied": "Copied!", - "drawerDownloadAppOnMobileDevice": "This site is the web version of the MyECL app. We invite you to download the app. Use this site only if you have problems with the app.\n", - "drawerIosAppLink": "https://apps.apple.com/fr/app/myecl/id6444443430", - "drawerLoginOut": "Do you want to log out?", - "drawerLogOut": "Log out", - "drawerOr": " or ", - "drawerSettings": "Settings", - "eventAdd": "Add", - "eventAddEvent": "Add an event", - "eventAddedEvent": "Event added", - "eventAddingError": "Error while adding", - "eventAllDay": "All day", - "eventConfirm": "Confirm", - "eventConfirmEvent": "Confirm the event?", - "eventConfirmation": "Confirmation", - "eventConfirmed": "Confirmed", - "eventDates": "Dates", - "eventDecline": "Decline", - "eventDeclineEvent": "Decline the event?", - "eventDeclined": "Declined", - "eventDelete": "Delete", - "eventDeletedEvent": "Event deleted", - "eventDeleting": "Deleting", - "eventDeletingError": "Error while deleting", - "eventDeletingEvent": "Delete the event?", - "eventDescription": "Description", - "eventEdit": "Edit", - "eventEditEvent": "Edit an event", - "eventEditedEvent": "Event edited", - "eventEditingError": "Error while editing", - "eventEndDate": "End date", - "eventEndHour": "End hour", - "eventError": "Error", - "eventEventList": "Event list", - "eventEventType": "Event type", - "eventEvery": "Every", - "eventHistory": "History", - "eventIncorrectOrMissingFields": "Some fields are incorrect or missing", - "eventInterval": "Interval", - "eventInvalidDates": "End date must be after start date", - "eventInvalidIntervalError": "Please enter a valid interval", - "eventLocation": "Location", - "eventMyEvents": "My events", - "eventName": "Name", - "eventNext": "Next", - "eventNo": "No", - "eventNoCurrentEvent": "No current event", - "eventNoDateError": "Please enter a date", - "eventNoDaySelected": "No day selected", - "eventNoDescriptionError": "Please enter a description", - "eventNoEvent": "No event", - "eventNoNameError": "Please enter a name", - "eventNoOrganizerError": "Please enter an organizer", - "eventNoPlaceError": "Please enter a location", - "eventNoPhoneRegistered": "Number not provided", - "eventNoRuleError": "Please enter a recurrence rule", - "eventOrganizer": "Organizer", - "eventOther": "Other", - "eventPending": "Pending", - "eventPrevious": "Previous", - "eventRecurrence": "Recurrence", - "eventRecurrenceDays": "Recurrence days", - "eventRecurrenceEndDate": "Recurrence end date", - "eventRecurrenceRule": "Recurrence rule", - "eventRoom": "Room", - "eventStartDate": "Start date", - "eventStartHour": "Start hour", - "eventTitle": "Events", - "eventYes": "Yes", - "eventEventEvery": "Every", - "eventWeeks": "weeks", - "eventDayMon": "Monday", - "eventDayTue": "Tuesday", - "eventDayWed": "Wednesday", - "eventDayThu": "Thursday", - "eventDayFri": "Friday", - "eventDaySat": "Saturday", - "eventDaySun": "Sunday", - "homeCalendar": "Calendar", - "homeEventOf": "Events of", - "homeIncomingEvents": "Upcoming events", - "homeLastInfos": "Latest announcements", - "homeNoEvents": "No events", - "homeTranslateDayShortMon": "Mon", - "homeTranslateDayShortTue": "Tue", - "homeTranslateDayShortWed": "Wed", - "homeTranslateDayShortThu": "Thu", - "homeTranslateDayShortFri": "Fri", - "homeTranslateDayShortSat": "Sat", - "homeTranslateDayShortSun": "Sun", - "loanAdd": "Add", - "loanAddLoan": "Add a loan", - "loanAddObject": "Add an object", - "loanAddedLoan": "Loan added", - "loanAddedObject": "Object added", - "loanAddedRoom": "Room added", - "loanAddingError": "Error while adding", - "loanAdmin": "Administrator", - "loanAvailable": "Available", - "loanAvailableMultiple": "Available", - "loanBorrowed": "Borrowed", - "loanBorrowedMultiple": "Borrowed", - "loanAnd": "and", - "loanAssociation": "Association", - "loanAvailableItems": "Available items", - "loanBeginDate": "Loan start date", - "loanBorrower": "Borrower", - "loanCaution": "Deposit", - "loanCancel": "Cancel", - "loanConfirm": "Confirm", - "loanConfirmation": "Confirmation", - "loanDates": "Dates", - "loanDays": "Days", - "loanDelay": "Extension delay", - "loanDelete": "Delete", - "loanDeletingLoan": "Delete the loan?", - "loanDeletedItem": "Object deleted", - "loanDeletedLoan": "Loan deleted", - "loanDeleting": "Deleting", - "loanDeletingError": "Error while deleting", - "loanDeletingItem": "Delete the object?", - "loanDuration": "Duration", - "loanEdit": "Edit", - "loanEditItem": "Edit the object", - "loanEditLoan": "Edit the loan", - "loanEditedRoom": "Room edited", - "loanEndDate": "Loan end date", - "loanEnded": "Ended", - "loanEnterDate": "Please enter a date", - "loanExtendedLoan": "Extended loan", - "loanExtendingError": "Error while extending", - "loanHistory": "History", - "loanIncorrectOrMissingFields": "Some fields are missing or incorrect", - "loanInvalidNumber": "Please enter a number", - "loanInvalidDates": "Dates are not valid", - "loanItem": "Item", - "loanItems": "Items", - "loanItemHandling": "Item management", - "loanItemSelected": "selected item", - "loanItemsSelected": "selected items", - "loanLendingDuration": "Possible loan duration", - "loanLoan": "Loan", - "loanLoanHandling": "Loan management", - "loanLooking": "Searching", - "loanName": "Name", - "loanNext": "Next", - "loanNo": "No", - "loanNoAssociationsFounded": "No associations found", - "loanNoAvailableItems": "No available items", - "loanNoBorrower": "No borrower", - "loanNoItems": "No items", - "loanNoItemSelected": "No item selected", - "loanNoLoan": "No loan", - "loanNoReturnedDate": "No return date", - "loanQuantity": "Quantity", - "loanNone": "None", - "loanNote": "Note", - "loanNoValue": "Please enter a value", - "loanOnGoing": "Ongoing", - "loanOnGoingLoan": "Ongoing loan", - "loanOthers": "others", - "loanPaidCaution": "Deposit paid", - "loanPositiveNumber": "Please enter a positive number", - "loanPrevious": "Previous", - "loanReturned": "Returned", - "loanReturnedLoan": "Returned loan", - "loanReturningError": "Error while returning", - "loanReturningLoan": "Return", - "loanReturnLoan": "Return the loan?", - "loanReturnLoanDescription": "Do you want to return this loan?", - "loanToReturn": "To return", - "loanUnavailable": "Unavailable", - "loanUpdate": "Edit", - "loanUpdatedItem": "Item updated", - "loanUpdatedLoan": "Loan updated", - "loanUpdatingError": "Error while updating", - "loanYes": "Yes", - "loginAccountActivated": "Account activated", - "loginAccountNotActivated": "Account not activated", - "loginActivationCode": "Activation code", - "loginBirthday": "Date of birth", - "loginCanBeEmpty": "This field can be empty", - "loginConfirmPassword": "Confirm password", - "loginCreate": "Create", - "loginCreateAccount": "Create an account", - "loginCreateAccountTitle": "Create an\naccount", - "loginEmail": "Email", - "loginEmailEmpty": "Please enter an email address", - "loginEmailInvalid": "Please enter a Centrale email address.\nIf you don't have one, please contact Éclair", - "loginEmptyFieldError": "This field cannot be empty", - "loginEndActivation": "Complete activation", - "loginEndResetPassword": "Complete\npassword reset", - "loginErrorResetPassword": "Error during reset", - "loginExpectingDate": "A date is expected", - "loginFillAllFields": "Please fill all fields", - "loginFirstname": "First name", - "loginFloor": "Floor", - "loginForgetPassword": "Forgot\npassword", - "loginForgotPassword": "Forgot password?", - "loginInvalidToken": "Invalid activation code", - "loginLoginFailed": "Login failed", - "loginMailSendingError": "Error during account creation", - "loginMustBeIntError": "This field must be an integer", - "loginName": "Last name", - "loginNewPassword": "New password", - "loginPassword": "Password", - "loginPasswordLengthError": "Password must be at least 6 characters", - "loginPasswordUppercaseError": "Password must contain at least one uppercase letter", - "loginPasswordLowercaseError": "Password must contain at least one lowercase letter", - "loginPasswordNumberError": "Password must contain at least one number", - "loginPasswordSpecialCaracterError": "Password must contain at least one special character", - "loginPasswordMustMatch": "Passwords must match", - "loginPasswordStrengthVeryWeak": "Very weak", - "loginPasswordStrengthWeak": "Weak", - "loginPasswordStrengthMedium": "Medium", - "loginPasswordStrengthStrong": "Strong", - "loginPasswordStrengthVeryStrong": "Very strong", - "loginPhone": "Phone", - "loginPromo": "Incoming class (e.g., 2023)", - "loginSendedMail": "Confirmation email sent", - "loginSendedResetMail": "Reset email sent", - "loginSignIn": "Sign in", - "loginRegister": "Register", - "loginRecievedMail": "I received the email", - "loginRecover": "Reset", - "loginResetedPassword": "Password reset", - "loginResetPasswordTitle": "Reset\npassword", - "loginNickname": "Nickname", - "loginWelcomeBack": "Welcome back", - "loginAppName": "MyECL", - "othersCheckInternetConnection": "Please check your internet connection", - "othersRetry": "Retry", - "othersTooOldVersion": "Your app version is too old.\n\nPlease update the app.", - "othersUnableToConnectToServer": "Unable to connect to the server", - "othersVersion": "Version", - "othersNoModule": "No modules available, please try again later 😢😢", - "othersAdmin": "Admin", - "othersError": "An error occurred", - "othersNoValue": "Please enter a value", - "othersInvalidNumber": "Please enter a number", - "othersNoDateError": "Please enter a date", - "othersImageSizeTooBig": "Image size must not exceed 4 MB", - "othersImageError": "Error adding the image", - "phAddNewJournal": "Add a new journal", - "phNameField": "Name: ", - "phDateField": "Date: ", - "phDelete": "Are you sure you want to delete this journal?", - "phIrreversibleAction": "This action is irreversible", - "phToHeavyFile": "File too large", - "phAddPdfFile": "Add a PDF file", - "phEditPdfFile": "Edit PDF file", - "phPhName": "PH name", - "phDate": "Date", - "phAdded": "Added", - "phEdited": "Edited", - "phAddingFileError": "Add error", - "phMissingInformatonsOrPdf": "Missing information or PDF file", - "phAdd": "Add", - "phEdit": "Edit", - "phSeePreviousJournal": "See previous journals", - "phNoJournalInDatabase": "No PH yet in database", - "phSuccesDowloading": "Successfully downloaded", - "phonebookActiveMandate": "Active mandate:", - "phonebookAdd": "Add", - "phonebookAddAssociation": "Add an association", - "phonebookAddedAssociation": "Association added", - "phonebookAddedMember": "Member added", - "phonebookAddingError": "Error adding", - "phonebookAddMember": "Add a member", - "phonebookAddRole": "Add a role", - "phonebookAdmin": "Admin", - "phonebookAdminPage": "Admin page", - "phonebookAll": "All", - "phonebookApparentName": "Public role name:", - "phonebookAssociation": "Association:", - "phonebookAssociationDetail": "Association details:", - "phonebookAssociationKind": "Type of association:", - "phonebookAssociationPure": "Association", - "phonebookAssociationPureSearch": " Association", - "phonebookAssociations": "Associations:", - "phonebookCancel": "Cancel", - "phonebookChangeMandate": "Switch to mandate ", - "phonebookChangeMandateConfirm": "Are you sure you want to change the entire mandate?\nThis action is irreversible!", - "phonebookCopied": "Copied to clipboard", - "phonebookDeactivateAssociation": "Are you sure you want to deactivate this association?\nThis action is irreversible!", - "phonebookDeactivatedAssociation": "Association deactivated", - "phonebookDeactivatedAssociationWarning": "Warning, this association is deactivated, you cannot modify it", - "phonebookDeactivating": "Deactivate the association?", - "phonebookDeactivatingError": "Error during deactivation", - "phonebookDetail": "Details:", - "phonebookDeleteAssociation": "Delete the association?\nThis will erase all association history", - "phonebookDeletedAssociation": "Association deleted", - "phonebookDeletedMember": "Member deleted", - "phonebookDeleting": "Deleting", - "phonebookDeletingError": "Error deleting", - "phonebookDescription": "Description", - "phonebookEdit": "Edit", - "phonebookEditMembership": "Edit role", - "phonebookEmail": "Email:", - "phonebookEmailCopied": "Email copied to clipboard", - "phonebookEmptyApparentName": "Please enter a role name", - "phonebookEmptyFieldError": "A field is not filled", - "phonebookEmptyKindError": "Please choose an association type", - "phonebookEmptyMember": "No member selected", - "phonebookErrorAssociationLoading": "Error loading association", - "phonebookErrorAssociationNameEmpty": "Please enter an association name", - "phonebookErrorAssociationPicture": "Error editing association picture", - "phonebookErrorKindsLoading": "Error loading association types", - "phonebookErrorLoadAssociationList": "Error loading association list", - "phonebookErrorLoadAssociationMember": "Error loading association members", - "phonebookErrorLoadAssociationPicture": "Error loading association picture", - "phonebookErrorLoadProfilePicture": "Error", - "phonebookErrorRoleTagsLoading": "Error loading role tags", - "phonebookExistingMembership": "This member is already in the current mandate", - "phonebookFirstname": "First name:", - "phonebookGroups": "Associated groups:", - "phonebookMandateChangingError": "Error changing mandate", - "phonebookMember": "Member", - "phonebookMemberReordered": "Member reordered", - "phonebookMembers": "Members", - "phonebookMembershipAssociationError": "Please choose an association", - "phonebookMembershipRole": "Role:", - "phonebookMembershipRoleError": "Please choose a role", - "phonebookName": "Last name:", - "phonebookNameCopied": "Name and first name copied to clipboard", - "phonebookNamePure": "Last name", - "phonebookNewMandate": "New mandate", - "phonebookNewMandateConfirmed": "Mandate changed", - "phonebookNickname": "Nickname:", - "phonebookNicknameCopied": "Nickname copied to clipboard", - "phonebookNoAssociationFound": "No association found", +{ + "@@locale": "en", + "adminAccountTypes": "Account types", + "adminAdd": "Add", + "adminAddGroup": "Add group", + "adminAddMember": "Add member", + "adminAddedGroup": "Group created", + "adminAddedLoaner": "Lender added", + "adminAddedMember": "Member added", + "adminAddingError": "Error while adding", + "adminAddingMember": "Adding a member", + "adminAddLoaningGroup": "Add loaning group", + "adminAddSchool": "Add school", + "adminAddStructure": "Add structure", + "adminAddedSchool": "School created", + "adminAddedStructure": "Structure added", + "adminEditedStructure": "Structure edited", + "adminAdministration": "Administration", + "adminAssociationMembership": "Membership", + "adminAssociationMembershipName": "Membership name", + "adminAssociationsMemberships": "Memberships", + "adminClearFilters": "Clear filters", + "adminCreateAssociationMembership": "Create membership", + "adminCreatedAssociationMembership": "Membership created", + "adminCreationError": "Error during creation", + "adminDateError": "Start date must be before end date", + "adminDelete": "Delete", + "adminDeleteAssociationMembership": "Delete membership?", + "adminDeletedAssociationMembership": "Membership deleted", + "adminDeleteGroup": "Delete group?", + "adminDeletedGroup": "Group deleted", + "adminDeleteSchool": "Delete school?", + "adminDeletedSchool": "School deleted", + "adminDeleting": "Deleting", + "adminDeletingError": "Error while deleting", + "adminDescription": "Description", + "adminEclSchool": "Centrale Lyon", + "adminEdit": "Edit", + "adminEditStructure": "Edit structure", + "adminEditMembership": "Edit membership", + "adminEmptyDate": "Empty date", + "adminEmptyFieldError": "Name cannot be empty", + "adminEmailRegex": "Email Regex", + "adminEmptyUser": "Empty user", + "adminEndDate": "End date", + "adminEndDateMaximal": "Maximum end date", + "adminEndDateMinimal": "Minimum end date", + "adminError": "Error", + "adminFilters": "Filters", + "adminGroup": "Group", + "adminGroups": "Groups", + "adminLoaningGroup": "Loaning group", + "adminLooking": "Searching", + "adminManager": "Structure administrator", + "adminMaximum": "Maximum", + "adminMembers": "Members", + "adminMembershipAddingError": "Error while adding (likely due to overlapping dates)", + "adminMemberships": "Memberships", + "adminMembershipUpdatingError": "Error while updating (likely due to overlapping dates)", + "adminMinimum": "Minimum", + "adminModifyModuleVisibility": "Module visibility", + "adminMyEclPay": "MyECLPay", + "adminName": "Name", + "adminNoManager": "No manager selected", + "adminNoMember": "No member", + "adminNoMoreLoaner": "No lender available", + "adminNoSchool": "No school", + "adminRemoveGroupMember": "Remove member from group?", + "adminResearch": "Search", + "adminSchools": "Schools", + "adminStructures": "Structures", + "adminStartDate": "Start date", + "adminStartDateMaximal": "Maximum start date", + "adminStartDateMinimal": "Minimum start date", + "adminUpdatedAssociationMembership": "Membership updated", + "adminUpdatedGroup": "Group updated", + "adminUpdatedMembership": "Membership updated", + "adminUpdatingError": "Error while updating", + "adminUser": "User", + "adminValidateFilters": "Apply filters", + "adminVisibilities": "Visibilities", + "advertAdd": "Add", + "advertAddedAdvert": "Advert published", + "advertAddedAnnouncer": "Announcer added", + "advertAddingError": "Error while adding", + "advertAdmin": "Admin", + "advertAdvert": "Advert", + "advertChoosingAnnouncer": "Please choose an announcer", + "advertChoosingPoster": "Please choose an image", + "advertContent": "Content", + "advertDeleteAdvert": "Delete ad?", + "advertDeleteAnnouncer": "Delete announcer?", + "advertDeleting": "Deleting", + "advertEdit": "Edit", + "advertEditedAdvert": "Advert edited", + "advertEditingError": "Error while editing", + "advertGroupAdvert": "Group", + "advertIncorrectOrMissingFields": "Incorrect or missing fields", + "advertInvalidNumber": "Please enter a number", + "advertManagement": "Management", + "advertModifyAnnouncingGroup": "Edit announcement group", + "advertNoMoreAnnouncer": "No more announcers available", + "advertNoValue": "Please enter a value", + "advertPositiveNumber": "Please enter a positive number", + "advertRemovedAnnouncer": "Announcer removed", + "advertRemovingError": "Error during removal", + "advertTags": "Tags", + "advertTitle": "Title", + "advertMonthJan": "Jan", + "advertMonthFeb": "Feb", + "advertMonthMar": "Mar", + "advertMonthApr": "Apr", + "advertMonthMay": "May", + "advertMonthJun": "Jun", + "advertMonthJul": "Jul", + "advertMonthAug": "Aug", + "advertMonthSep": "Sep", + "advertMonthOct": "Oct", + "advertMonthNov": "Nov", + "advertMonthDec": "Dec", + "amapAccounts": "Accounts", + "amapAdd": "Add", + "amapAddDelivery": "Add delivery", + "amapAddedCommand": "Order added", + "amapAddedOrder": "Order added", + "amapAddedProduct": "Product added", + "amapAddedUser": "User added", + "amapAddProduct": "Add product", + "amapAddUser": "Add user", + "amapAddingACommand": "Add an order", + "amapAddingCommand": "Add the order", + "amapAddingError": "Error while adding", + "amapAddingProduct": "Add a product", + "amapAddOrder": "Add an order", + "amapAdmin": "Admin", + "amapAlreadyExistCommand": "An order already exists for this date", + "amapAmap": "Amap", + "amapAmount": "Balance", + "amapArchive": "Archive", + "amapArchiveDelivery": "Archive", + "amapArchivingDelivery": "Archiving delivery", + "amapCategory": "Category", + "amapCloseDelivery": "Lock", + "amapCommandDate": "Order date", + "amapCommandProducts": "Order products", + "amapConfirm": "Confirm", + "amapContact": "Association contacts", + "amapCreateCategory": "Create category", + "amapDelete": "Delete", + "amapDeleteDelivery": "Delete delivery?", + "amapDeleteDeliveryDescription": "Are you sure you want to delete this delivery?", + "amapDeletedDelivery": "Delivery deleted", + "amapDeletedOrder": "Order deleted", + "amapDeletedProduct": "Product deleted", + "amapDeleteProduct": "Delete product?", + "amapDeleteProductDescription": "Are you sure you want to delete this product?", + "amapDeleting": "Deleting", + "amapDeletingDelivery": "Delete delivery?", + "amapDeletingError": "Error while deleting", + "amapDeletingOrder": "Delete order?", + "amapDeletingProduct": "Delete product?", + "amapDeliver": "Delivery completed?", + "amapDeliveries": "Deliveries", + "amapDeliveringDelivery": "Are all orders delivered?", + "amapDelivery": "Delivery", + "amapDeliveryArchived": "Delivery archived", + "amapDeliveryDate": "Delivery date", + "amapDeliveryDelivered": "Delivery completed", + "amapDeliveryHistory": "Delivery history", + "amapDeliveryList": "Delivery list", + "amapDeliveryLocked": "Delivery locked", + "amapDeliveryOn": "Delivery on", + "amapDeliveryOpened": "Delivery opened", + "amapDeliveryNotArchived": "Delivery not archived", + "amapDeliveryNotLocked": "Delivery not locked", + "amapDeliveryNotDelivered": "Delivery not completed", + "amapDeliveryNotOpened": "Delivery not opened", + "amapEditDelivery": "Edit delivery", + "amapEditedCommand": "Order edited", + "amapEditingError": "Error while editing", + "amapEditProduct": "Edit product", + "amapEndingDelivery": "End of delivery", + "amapError": "Error", + "amapErrorLink": "Error opening link", + "amapErrorLoadingUser": "Error loading users", + "amapEvening": "Evening", + "amapExpectingNumber": "Please enter a number", + "amapFillField": "Please fill out this field", + "amapHandlingAccount": "Manage accounts", + "amapLoading": "Loading...", + "amapLoadingError": "Loading error", + "amapLock": "Lock", + "amapLocked": "Locked", + "amapLockedDelivery": "Delivery locked", + "amapLockedOrder": "Order locked", + "amapLooking": "Search", + "amapLockingDelivery": "Lock delivery?", + "amapMidDay": "Midday", + "amapMyOrders": "My orders", + "amapName": "Name", + "amapNextStep": "Next step", + "amapNoProduct": "No product", + "amapNoCurrentOrder": "No current order", + "amapNoMoney": "Not enough money", + "amapNoOpennedDelivery": "No open delivery", + "amapNoOrder": "No order", + "amapNoSelectedDelivery": "No delivery selected", + "amapNotEnoughMoney": "Not enough money", + "amapNotPlannedDelivery": "No scheduled delivery", + "amapOneOrder": "order", + "amapOpenDelivery": "Open", + "amapOpened": "Opened", + "amapOpenningDelivery": "Open delivery?", + "amapOrder": "Order", + "amapOrders": "Orders", + "amapPickChooseCategory": "Please enter a value or choose an existing category", + "amapPickDeliveryMoment": "Choose a delivery time", + "amapPresentation": "Presentation", + "amapPresentation1": "The AMAP (association for the preservation of small-scale farming) is a service offered by the Planet&Co association of ECL. You can receive products (fruit and vegetable baskets, juices, jams...) directly on campus!\n\nOrders must be placed before Friday at 9 PM and are delivered on campus on Tuesday from 1:00 PM to 1:45 PM (or from 6:15 PM to 6:30 PM if you can't come at midday) in the M16 hall.\n\nYou can only order if your balance allows it. You can top up your balance via the Lydia collection or by cheque during office hours.\n\nLydia top-up link: ", + "amapPresentation2": "\n\nFeel free to contact us if you have any issues!", + "amapPrice": "Price", + "amapProduct": "product", + "amapProducts": "Products", + "amapProductInDelivery": "Product in an unfinished delivery", + "amapQuantity": "Quantity", + "amapRequiredDate": "Date is required", + "amapSeeMore": "See more", + "amapThe": "The", + "amapUnlock": "Unlock", + "amapUnlockedDelivery": "Delivery unlocked", + "amapUnlockingDelivery": "Unlock delivery?", + "amapUpdate": "Edit", + "amapUpdatedAmount": "Balance updated", + "amapUpdatedOrder": "Order updated", + "amapUpdatedProduct": "Product updated", + "amapUpdatingError": "Update failed", + "amapUsersNotFound": "No users found", + "amapWaiting": "Pending", + "bookingAdd": "Add", + "bookingAddBookingPage": "Request", + "bookingAddRoom": "Add room", + "bookingAddBooking": "Add booking", + "bookingAddedBooking": "Request added", + "bookingAddedRoom": "Room added", + "bookingAddedManager": "Manager added", + "bookingAddingError": "Error while adding", + "bookingAddManager": "Add manager", + "bookingAdminPage": "Admin", + "bookingAllDay": "All day", + "bookingBookedFor": "Booked for", + "bookingBooking": "Booking", + "bookingBookingCreated": "Booking created", + "bookingBookingDemand": "Booking request", + "bookingBookingNote": "Booking note", + "bookingBookingPage": "Booking", + "bookingBookingReason": "Booking reason", + "bookingBy": "by", + "bookingConfirm": "Confirm", + "bookingConfirmation": "Confirmation", + "bookingConfirmBooking": "Confirm the booking?", + "bookingConfirmed": "Confirmed", + "bookingDates": "Dates", + "bookingDecline": "Decline", + "bookingDeclineBooking": "Decline the booking?", + "bookingDeclined": "Declined", + "bookingDelete": "Delete", + "bookingDeleting": "Deleting", + "bookingDeleteBooking": "Deleting", + "bookingDeleteBookingConfirmation": "Are you sure you want to delete this booking?", + "bookingDeletedBooking": "Booking deleted", + "bookingDeletedRoom": "Room deleted", + "bookingDeletedManager": "Manager deleted", + "bookingDeleteRoomConfirmation": "Are you sure you want to delete this room?\n\nThe room must have no current or upcoming bookings to be deleted", + "bookingDeleteManagerConfirmation": "Are you sure you want to delete this manager?\n\nThe manager must not be associated with any room to be deleted", + "bookingDeletingBooking": "Delete the booking?", + "bookingDeletingError": "Error while deleting", + "bookingDeletingRoom": "Delete the room?", + "bookingEdit": "Edit", + "bookingEditBooking": "Edit a booking", + "bookingEditionError": "Error while editing", + "bookingEditedBooking": "Booking edited", + "bookingEditedRoom": "Room edited", + "bookingEditedManager": "Manager edited", + "bookingEditManager": "Edit or delete a manager", + "bookingEditRoom": "Edit or delete a room", + "bookingEndDate": "End date", + "bookingEndHour": "End hour", + "bookingEntity": "For whom?", + "bookingError": "Error", + "bookingEventEvery": "Every", + "bookingHistoryPage": "History", + "bookingIncorrectOrMissingFields": "Incorrect or missing fields", + "bookingInterval": "Interval", + "bookingInvalidIntervalError": "Invalid interval", + "bookingInvalidDates": "Invalid dates", + "bookingInvalidRoom": "Invalid room", + "bookingKeysRequested": "Keys requested", + "bookingManagement": "Management", + "bookingManager": "Manager", + "bookingManagerName": "Manager name", + "bookingMultipleDay": "Multiple days", + "bookingMyBookings": "My bookings", + "bookingNecessaryKey": "Key needed", + "bookingNext": "Next", + "bookingNo": "No", + "bookingNoCurrentBooking": "No current booking", + "bookingNoDateError": "Please choose a date", + "bookingNoAppointmentInReccurence": "No slot exists with these recurrence settings", + "bookingNoDaySelected": "No day selected", + "bookingNoDescriptionError": "Please enter a description", + "bookingNoKeys": "No keys", + "bookingNoNoteError": "Please enter a note", + "bookingNoPhoneRegistered": "Number not provided", + "bookingNoReasonError": "Please enter a reason", + "bookingNoRoomFoundError": "No room registered", + "bookingNoRoomFound": "No room found", + "bookingNote": "Note", + "bookingOther": "Other", + "bookingPending": "Pending", + "bookingPrevious": "Previous", + "bookingReason": "Reason", + "bookingRecurrence": "Recurrence", + "bookingRecurrenceDays": "Recurrence days", + "bookingRecurrenceEndDate": "Recurrence end date", + "bookingRecurrent": "Recurrent", + "bookingRegisteredRooms": "Registered rooms", + "bookingRoom": "Room", + "bookingRoomName": "Room name", + "bookingStartDate": "Start date", + "bookingStartHour": "Start hour", + "bookingWeeks": "Weeks", + "bookingYes": "Yes", + "bookingWeekDayMon": "Monday", + "bookingWeekDayTue": "Tuesday", + "bookingWeekDayWed": "Wednesday", + "bookingWeekDayThu": "Thursday", + "bookingWeekDayFri": "Friday", + "bookingWeekDaySat": "Saturday", + "bookingWeekDaySun": "Sunday", + "cinemaAdd": "Add", + "cinemaAddedSession": "Session added", + "cinemaAddingError": "Error while adding", + "cinemaAddSession": "Add a session", + "cinemaCinema": "Cinema", + "cinemaDeleteSession": "Delete the session?", + "cinemaDeleting": "Deleting", + "cinemaDuration": "Duration", + "cinemaEdit": "Edit", + "cinemaEditedSession": "Session edited", + "cinemaEditingError": "Error while editing", + "cinemaEditSession": "Edit the session", + "cinemaEmptyUrl": "Please enter a URL", + "cinemaImportFromTMDB": "Import from TMDB", + "cinemaIncomingSession": "Now showing", + "cinemaIncorrectOrMissingFields": "Incorrect or missing fields", + "cinemaInvalidUrl": "Invalid URL", + "cinemaGenre": "Genre", + "cinemaName": "Name", + "cinemaNoDateError": "Please enter a date", + "cinemaNoDuration": "Please enter a duration", + "cinemaNoOverview": "No synopsis", + "cinemaNoPoster": "No poster", + "cinemaNoSession": "No session", + "cinemaOverview": "Synopsis", + "cinemaPosterUrl": "Poster URL", + "cinemaSessionDate": "Session day", + "cinemaStartHour": "Start hour", + "cinemaTagline": "Tagline", + "cinemaThe": "The", + "drawerAdmin": "Administration", + "drawerAndroidAppLink": "https://play.google.com/store/apps/details?id=fr.myecl.titan", + "drawerCopied": "Copied!", + "drawerDownloadAppOnMobileDevice": "This site is the web version of the MyECL app. We invite you to download the app. Use this site only if you have problems with the app.\n", + "drawerIosAppLink": "https://apps.apple.com/fr/app/myecl/id6444443430", + "drawerLoginOut": "Do you want to log out?", + "drawerLogOut": "Log out", + "drawerOr": " or ", + "drawerSettings": "Settings", + "eventAdd": "Add", + "eventAddEvent": "Add an event", + "eventAddedEvent": "Event added", + "eventAddingError": "Error while adding", + "eventAllDay": "All day", + "eventConfirm": "Confirm", + "eventConfirmEvent": "Confirm the event?", + "eventConfirmation": "Confirmation", + "eventConfirmed": "Confirmed", + "eventDates": "Dates", + "eventDecline": "Decline", + "eventDeclineEvent": "Decline the event?", + "eventDeclined": "Declined", + "eventDelete": "Delete", + "eventDeletedEvent": "Event deleted", + "eventDeleting": "Deleting", + "eventDeletingError": "Error while deleting", + "eventDeletingEvent": "Delete the event?", + "eventDescription": "Description", + "eventEdit": "Edit", + "eventEditEvent": "Edit an event", + "eventEditedEvent": "Event edited", + "eventEditingError": "Error while editing", + "eventEndDate": "End date", + "eventEndHour": "End hour", + "eventError": "Error", + "eventEventList": "Event list", + "eventEventType": "Event type", + "eventEvery": "Every", + "eventHistory": "History", + "eventIncorrectOrMissingFields": "Some fields are incorrect or missing", + "eventInterval": "Interval", + "eventInvalidDates": "End date must be after start date", + "eventInvalidIntervalError": "Please enter a valid interval", + "eventLocation": "Location", + "eventMyEvents": "My events", + "eventName": "Name", + "eventNext": "Next", + "eventNo": "No", + "eventNoCurrentEvent": "No current event", + "eventNoDateError": "Please enter a date", + "eventNoDaySelected": "No day selected", + "eventNoDescriptionError": "Please enter a description", + "eventNoEvent": "No event", + "eventNoNameError": "Please enter a name", + "eventNoOrganizerError": "Please enter an organizer", + "eventNoPlaceError": "Please enter a location", + "eventNoPhoneRegistered": "Number not provided", + "eventNoRuleError": "Please enter a recurrence rule", + "eventOrganizer": "Organizer", + "eventOther": "Other", + "eventPending": "Pending", + "eventPrevious": "Previous", + "eventRecurrence": "Recurrence", + "eventRecurrenceDays": "Recurrence days", + "eventRecurrenceEndDate": "Recurrence end date", + "eventRecurrenceRule": "Recurrence rule", + "eventRoom": "Room", + "eventStartDate": "Start date", + "eventStartHour": "Start hour", + "eventTitle": "Events", + "eventYes": "Yes", + "eventEventEvery": "Every", + "eventWeeks": "weeks", + "eventDayMon": "Monday", + "eventDayTue": "Tuesday", + "eventDayWed": "Wednesday", + "eventDayThu": "Thursday", + "eventDayFri": "Friday", + "eventDaySat": "Saturday", + "eventDaySun": "Sunday", + "homeCalendar": "Calendar", + "homeEventOf": "Events of", + "homeIncomingEvents": "Upcoming events", + "homeLastInfos": "Latest announcements", + "homeNoEvents": "No events", + "homeTranslateDayShortMon": "Mon", + "homeTranslateDayShortTue": "Tue", + "homeTranslateDayShortWed": "Wed", + "homeTranslateDayShortThu": "Thu", + "homeTranslateDayShortFri": "Fri", + "homeTranslateDayShortSat": "Sat", + "homeTranslateDayShortSun": "Sun", + "loanAdd": "Add", + "loanAddLoan": "Add a loan", + "loanAddObject": "Add an object", + "loanAddedLoan": "Loan added", + "loanAddedObject": "Object added", + "loanAddedRoom": "Room added", + "loanAddingError": "Error while adding", + "loanAdmin": "Administrator", + "loanAvailable": "Available", + "loanAvailableMultiple": "Available", + "loanBorrowed": "Borrowed", + "loanBorrowedMultiple": "Borrowed", + "loanAnd": "and", + "loanAssociation": "Association", + "loanAvailableItems": "Available items", + "loanBeginDate": "Loan start date", + "loanBorrower": "Borrower", + "loanCaution": "Deposit", + "loanCancel": "Cancel", + "loanConfirm": "Confirm", + "loanConfirmation": "Confirmation", + "loanDates": "Dates", + "loanDays": "Days", + "loanDelay": "Extension delay", + "loanDelete": "Delete", + "loanDeletingLoan": "Delete the loan?", + "loanDeletedItem": "Object deleted", + "loanDeletedLoan": "Loan deleted", + "loanDeleting": "Deleting", + "loanDeletingError": "Error while deleting", + "loanDeletingItem": "Delete the object?", + "loanDuration": "Duration", + "loanEdit": "Edit", + "loanEditItem": "Edit the object", + "loanEditLoan": "Edit the loan", + "loanEditedRoom": "Room edited", + "loanEndDate": "Loan end date", + "loanEnded": "Ended", + "loanEnterDate": "Please enter a date", + "loanExtendedLoan": "Extended loan", + "loanExtendingError": "Error while extending", + "loanHistory": "History", + "loanIncorrectOrMissingFields": "Some fields are missing or incorrect", + "loanInvalidNumber": "Please enter a number", + "loanInvalidDates": "Dates are not valid", + "loanItem": "Item", + "loanItems": "Items", + "loanItemHandling": "Item management", + "loanItemSelected": "selected item", + "loanItemsSelected": "selected items", + "loanLendingDuration": "Possible loan duration", + "loanLoan": "Loan", + "loanLoanHandling": "Loan management", + "loanLooking": "Searching", + "loanName": "Name", + "loanNext": "Next", + "loanNo": "No", + "loanNoAssociationsFounded": "No associations found", + "loanNoAvailableItems": "No available items", + "loanNoBorrower": "No borrower", + "loanNoItems": "No items", + "loanNoItemSelected": "No item selected", + "loanNoLoan": "No loan", + "loanNoReturnedDate": "No return date", + "loanQuantity": "Quantity", + "loanNone": "None", + "loanNote": "Note", + "loanNoValue": "Please enter a value", + "loanOnGoing": "Ongoing", + "loanOnGoingLoan": "Ongoing loan", + "loanOthers": "others", + "loanPaidCaution": "Deposit paid", + "loanPositiveNumber": "Please enter a positive number", + "loanPrevious": "Previous", + "loanReturned": "Returned", + "loanReturnedLoan": "Returned loan", + "loanReturningError": "Error while returning", + "loanReturningLoan": "Return", + "loanReturnLoan": "Return the loan?", + "loanReturnLoanDescription": "Do you want to return this loan?", + "loanToReturn": "To return", + "loanUnavailable": "Unavailable", + "loanUpdate": "Edit", + "loanUpdatedItem": "Item updated", + "loanUpdatedLoan": "Loan updated", + "loanUpdatingError": "Error while updating", + "loanYes": "Yes", + "loginAccountActivated": "Account activated", + "loginAccountNotActivated": "Account not activated", + "loginActivationCode": "Activation code", + "loginBirthday": "Date of birth", + "loginCanBeEmpty": "This field can be empty", + "loginConfirmPassword": "Confirm password", + "loginCreate": "Create", + "loginCreateAccount": "Create an account", + "loginCreateAccountTitle": "Create an\naccount", + "loginEmail": "Email", + "loginEmailEmpty": "Please enter an email address", + "loginEmailInvalid": "Please enter a Centrale email address.\nIf you don't have one, please contact Éclair", + "loginEmptyFieldError": "This field cannot be empty", + "loginEndActivation": "Complete activation", + "loginEndResetPassword": "Complete\npassword reset", + "loginErrorResetPassword": "Error during reset", + "loginExpectingDate": "A date is expected", + "loginFillAllFields": "Please fill all fields", + "loginFirstname": "First name", + "loginFloor": "Floor", + "loginForgetPassword": "Forgot\npassword", + "loginForgotPassword": "Forgot password?", + "loginInvalidToken": "Invalid activation code", + "loginLoginFailed": "Login failed", + "loginMailSendingError": "Error during account creation", + "loginMustBeIntError": "This field must be an integer", + "loginName": "Last name", + "loginNewPassword": "New password", + "loginPassword": "Password", + "loginPasswordLengthError": "Password must be at least 6 characters", + "loginPasswordUppercaseError": "Password must contain at least one uppercase letter", + "loginPasswordLowercaseError": "Password must contain at least one lowercase letter", + "loginPasswordNumberError": "Password must contain at least one number", + "loginPasswordSpecialCaracterError": "Password must contain at least one special character", + "loginPasswordMustMatch": "Passwords must match", + "loginPasswordStrengthVeryWeak": "Very weak", + "loginPasswordStrengthWeak": "Weak", + "loginPasswordStrengthMedium": "Medium", + "loginPasswordStrengthStrong": "Strong", + "loginPasswordStrengthVeryStrong": "Very strong", + "loginPhone": "Phone", + "loginPromo": "Incoming class (e.g., 2023)", + "loginSendedMail": "Confirmation email sent", + "loginSendedResetMail": "Reset email sent", + "loginSignIn": "Sign in", + "loginRegister": "Register", + "loginRecievedMail": "I received the email", + "loginRecover": "Reset", + "loginResetedPassword": "Password reset", + "loginResetPasswordTitle": "Reset\npassword", + "loginNickname": "Nickname", + "loginWelcomeBack": "Welcome back", + "loginAppName": "MyECL", + "othersCheckInternetConnection": "Please check your internet connection", + "othersRetry": "Retry", + "othersTooOldVersion": "Your app version is too old.\n\nPlease update the app.", + "othersUnableToConnectToServer": "Unable to connect to the server", + "othersVersion": "Version", + "othersNoModule": "No modules available, please try again later 😢😢", + "othersAdmin": "Admin", + "othersError": "An error occurred", + "othersNoValue": "Please enter a value", + "othersInvalidNumber": "Please enter a number", + "othersNoDateError": "Please enter a date", + "othersImageSizeTooBig": "Image size must not exceed 4 MB", + "othersImageError": "Error adding the image", + "phAddNewJournal": "Add a new journal", + "phNameField": "Name: ", + "phDateField": "Date: ", + "phDelete": "Are you sure you want to delete this journal?", + "phIrreversibleAction": "This action is irreversible", + "phToHeavyFile": "File too large", + "phAddPdfFile": "Add a PDF file", + "phEditPdfFile": "Edit PDF file", + "phPhName": "PH name", + "phDate": "Date", + "phAdded": "Added", + "phEdited": "Edited", + "phAddingFileError": "Add error", + "phMissingInformatonsOrPdf": "Missing information or PDF file", + "phAdd": "Add", + "phEdit": "Edit", + "phSeePreviousJournal": "See previous journals", + "phNoJournalInDatabase": "No PH yet in database", + "phSuccesDowloading": "Successfully downloaded", + "phonebookActiveMandate": "Active mandate:", + "phonebookAdd": "Add", + "phonebookAddAssociation": "Add an association", + "phonebookAddAssociationGroupement": "Add an association groupement", + "phonebookAddedAssociation": "Association added", + "phonebookAddedMember": "Member added", + "phonebookAddingError": "Error adding", + "phonebookAddMember": "Add a member", + "phonebookAddRole": "Add a role", + "phonebookAdmin": "Admin", + "phonebookAdminPage": "Admin page", + "phonebookAll": "All", + "phonebookApparentName": "Public role name:", + "phonebookAssociation": "Association:", + "phonebookAssociationDetail": "Association details:", + "phonebookAssociationKind": "Type of association:", + "phonebookAssociationPure": "Association", + "phonebookAssociationPureSearch": " Association", + "phonebookAssociations": "Associations:", + "phonebookCancel": "Cancel", + "phonebookChangeMandate": "Switch to mandate ", + "phonebookChangeMandateConfirm": "Are you sure you want to change the entire mandate?\nThis action is irreversible!", + "phonebookConfirm": "Confirm", + "phonebookCopied": "Copied to clipboard", + "phonebookDeactivateAssociation": "Are you sure you want to deactivate this association?\nThis action is irreversible!", + "phonebookDeactivatedAssociation": "Association deactivated", + "phonebookDeactivatedAssociationWarning": "Warning, this association is deactivated, you cannot modify it", + "phonebookDeactivating": "Deactivate the association?", + "phonebookDeactivatingError": "Error during deactivation", + "phonebookDetail": "Details:", + "phonebookDeleteAssociation": "Delete the association?\nThis will erase all association history", + "phonebookDeletedAssociation": "Association deleted", + "phonebookDeletedMember": "Member deleted", + "phonebookDeleting": "Deleting", + "phonebookDeletingError": "Error deleting", + "phonebookDescription": "Description", + "phonebookEdit": "Edit", + "phonebookEditAssociationGroupement": "Edit association groupement", + "phonebookEditMembership": "Edit role", + "phonebookEmail": "Email:", + "phonebookEmailCopied": "Email copied to clipboard", + "phonebookEmptyApparentName": "Please enter a role name", + "phonebookEmptyFieldError": "A field is not filled", + "phonebookEmptyKindError": "Please choose an association type", + "phonebookEmptyMember": "No member selected", + "phonebookErrorAssociationLoading": "Error loading association", + "phonebookErrorAssociationNameEmpty": "Please enter an association name", + "phonebookErrorAssociationPicture": "Error editing association picture", + "phonebookErrorKindsLoading": "Error loading association types", + "phonebookErrorLoadAssociationList": "Error loading association list", + "phonebookErrorLoadAssociationMember": "Error loading association members", + "phonebookErrorLoadAssociationPicture": "Error loading association picture", + "phonebookErrorLoadProfilePicture": "Error", + "phonebookErrorRoleTagsLoading": "Error loading role tags", + "phonebookExistingMembership": "This member is already in the current mandate", + "phonebookFirstname": "First name:", + "phonebookGroups": "Associated groups:", + "phonebookMandateChangingError": "Error changing mandate", + "phonebookMember": "Member", + "phonebookMemberReordered": "Member reordered", + "phonebookMembers": "Members", + "phonebookMembershipAssociationError": "Please choose an association", + "phonebookMembershipRole": "Role:", + "phonebookMembershipRoleError": "Please choose a role", + "phonebookName": "Last name:", + "phonebookNameCopied": "Name and first name copied to clipboard", + "phonebookNamePure": "Last name", + "phonebookNewMandate": "New mandate", + "phonebookNewMandateConfirmed": "Mandate changed", + "phonebookNickname": "Nickname:", + "phonebookNicknameCopied": "Nickname copied to clipboard", + "phonebookNoAssociationFound": "No association found", "phonebookNoMember": "No member", "phonebookNoMemberRole": "No role found", "phonebookPhone": "Phone:", @@ -1246,4 +1249,4 @@ "paiementTransferStructureError": "Error while transferring structure", "paiementTransferStructureSuccess": "Structure transfer requested successfully", "paiementNextAccountable": "Next responsible" - } \ No newline at end of file +} diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 986e58e447..f09c082c0d 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -1,1249 +1,1252 @@ { - "@@locale": "fr", - "adminAccountTypes": "Types de compte", - "adminAdd": "Ajouter", - "adminAddGroup": "Ajouter un groupe", - "adminAddMember": "Ajouter un membre", - "adminAddedGroup": "Groupe créé", - "adminAddedLoaner": "Préteur ajouté", - "adminAddedMember": "Membre ajouté", - "adminAddingError": "Erreur lors de l'ajout", - "adminAddingMember": "Ajout d'un membre", - "adminAddLoaningGroup": "Ajouter un groupe de prêt", - "adminAddSchool": "Ajouter une école", - "adminAddStructure": "Ajouter une structure", - "adminAddedSchool": "École créée", - "adminAddedStructure": "Structure ajoutée", - "adminEditedStructure": "Structure modifiée", - "adminAdministration": "Administration", - "adminAssociationMembership": "Adhésion", - "adminAssociationMembershipName": "Nom de l'adhésion", - "adminAssociationsMemberships": "Adhésions", - "adminClearFilters": "Effacer les filtres", - "adminCreateAssociationMembership": "Créer une adhésion", - "adminCreatedAssociationMembership": "Adhésion créée", - "adminCreationError": "Erreur lors de la création", - "adminDateError": "La date de début doit être avant la date de fin", - "adminDelete": "Supprimer", - "adminDeleteAssociationMembership": "Supprimer l'adhésion ?", - "adminDeletedAssociationMembership": "Adhésion supprimée", - "adminDeleteGroup": "Supprimer le groupe ?", - "adminDeletedGroup": "Groupe supprimé", - "adminDeleteSchool": "Supprimer l'école ?", - "adminDeletedSchool": "École supprimée", - "adminDeleting": "Suppression", - "adminDeletingError": "Erreur lors de la suppression", - "adminDescription": "Description", - "adminEclSchool": "Centrale Lyon", - "adminEdit": "Modifier", - "adminEditStructure": "Modifier la structure", - "adminEditMembership": "Modifier l'adhésion", - "adminEmptyDate": "Date vide", - "adminEmptyFieldError": "Le nom ne peut pas être vide", - "adminEmailRegex": "Email Regex", - "adminEmptyUser": "Utilisateur vide", - "adminEndDate": "Date de fin", - "adminEndDateMaximal": "Date de fin maximale", - "adminEndDateMinimal": "Date de fin minimale", - "adminError": "Erreur", - "adminFilters": "Filtres", - "adminGroup": "Groupe", - "adminGroups": "Groupes", - "adminLoaningGroup": "Groupe de prêt", - "adminLooking": "Recherche", - "adminManager": "Administrateur de la structure", - "adminMaximum": "Maximum", - "adminMembers": "Membres", - "adminMembershipAddingError": "Erreur lors de l'ajout (surement dû à une superposition de dates)", - "adminMemberships": "Adhésions", - "adminMembershipUpdatingError": "Erreur lors de la modification (surement dû à une superposition de dates)", - "adminMinimum": "Minimum", - "adminModifyModuleVisibility": "Visibilité des modules", - "adminMyEclPay": "MyECLPay", - "adminName": "Nom", - "adminNoManager": "Aucun manager n'est sélectionné", - "adminNoMember": "Aucun membre", - "adminNoMoreLoaner": "Aucun prêteur n'est disponible", - "adminNoSchool": "Sans école", - "adminRemoveGroupMember": "Supprimer le membre du groupe ?", - "adminResearch": "Recherche", - "adminSchools": "Écoles", - "adminStructures": "Structures", - "adminStartDate": "Date de début", - "adminStartDateMaximal": "Date de début maximale", - "adminStartDateMinimal": "Date de début minimale", - "adminUpdatedAssociationMembership": "Adhésion modifiée", - "adminUpdatedGroup": "Groupe modifié", - "adminUpdatedMembership": "Adhésion modifiée", - "adminUpdatingError": "Erreur lors de la modification", - "adminUser": "Utilisateur", - "adminValidateFilters": "Valider les filtres", - "adminVisibilities": "Visibilités", - "advertAdd": "Ajouter", - "advertAddedAdvert": "Annonce publiée", - "advertAddedAnnouncer": "Annonceur ajouté", - "advertAddingError": "Erreur lors de l'ajout", - "advertAdmin": "Admin", - "advertAdvert": "Annonce", - "advertChoosingAnnouncer": "Veuillez choisir un annonceur", - "advertChoosingPoster": "Veuillez choisir une image", - "advertContent": "Contenu", - "advertDeleteAdvert": "Supprimer l'annonce ?", - "advertDeleteAnnouncer": "Supprimer l'annonceur ?", - "advertDeleting": "Suppression", - "advertEdit": "Modifier", - "advertEditedAdvert": "Annonce modifiée", - "advertEditingError": "Erreur lors de la modification", - "advertGroupAdvert": "Groupe", - "advertIncorrectOrMissingFields": "Champs incorrects ou manquants", - "advertInvalidNumber": "Veuillez entrer un nombre", - "advertManagement": "Gestion", - "advertModifyAnnouncingGroup": "Modifier un groupe d'annonce", - "advertNoMoreAnnouncer": "Aucun annonceur n'est disponible", - "advertNoValue": "Veuillez entrer une valeur", - "advertPositiveNumber": "Veuillez entrer un nombre positif", - "advertRemovedAnnouncer": "Annonceur supprimé", - "advertRemovingError": "Erreur lors de la suppression", - "advertTags": "Tags", - "advertTitle": "Titre", - "advertMonthJan": "Janv", - "advertMonthFeb": "Févr.", - "advertMonthMar": "Mars", - "advertMonthApr": "Avr.", - "advertMonthMay": "Mai", - "advertMonthJun": "Juin", - "advertMonthJul": "Juill.", - "advertMonthAug": "Août", - "advertMonthSep": "Sept.", - "advertMonthOct": "Oct.", - "advertMonthNov": "Nov.", - "advertMonthDec": "Déc.", - "amapAccounts": "Comptes", - "amapAdd": "Ajouter", - "amapAddDelivery": "Ajouter une livraison", - "amapAddedCommand": "Commande ajoutée", - "amapAddedOrder": "Commande ajoutée", - "amapAddedProduct": "Produit ajouté", - "amapAddedUser": "Utilisateur ajouté", - "amapAddProduct": "Ajouter un produit", - "amapAddUser": "Ajouter un utilisateur", - "amapAddingACommand": "Ajouter une commande", - "amapAddingCommand": "Ajouter la commande", - "amapAddingError": "Erreur lors de l'ajout", - "amapAddingProduct": "Ajouter un produit", - "amapAddOrder": "Ajouter une commande", - "amapAdmin": "Admin", - "amapAlreadyExistCommand": "Il existe déjà une commande à cette date", - "amapAmap": "Amap", - "amapAmount": "Solde", - "amapArchive": "Archiver", - "amapArchiveDelivery": "Archiver", - "amapArchivingDelivery": "Archivage de la livraison", - "amapCategory": "Catégorie", - "amapCloseDelivery": "Verrouiller", - "amapCommandDate": "Date de la commande", - "amapCommandProducts": "Produits de la commande", - "amapConfirm": "Confirmer", - "amapContact": "Contacts associatifs ", - "amapCreateCategory": "Créer une catégorie", - "amapDelete": "Supprimer", - "amapDeleteDelivery": "Supprimer la livraison ?", - "amapDeleteDeliveryDescription": "Voulez-vous vraiment supprimer cette livraison ?", - "amapDeletedDelivery": "Livraison supprimée", - "amapDeletedOrder": "Commande supprimée", - "amapDeletedProduct": "Produit supprimé", - "amapDeleteProduct": "Supprimer le produit ?", - "amapDeleteProductDescription": "Voulez-vous vraiment supprimer ce produit ?", - "amapDeleting": "Suppression", - "amapDeletingDelivery": "Supprimer la livraison ?", - "amapDeletingError": "Erreur lors de la suppression", - "amapDeletingOrder": "Supprimer la commande ?", - "amapDeletingProduct": "Supprimer le produit ?", - "amapDeliver": "Livraison teminée ?", - "amapDeliveries": "Livraisons", - "amapDeliveringDelivery": "Toutes les commandes sont livrées ?", - "amapDelivery": "Livraison", - "amapDeliveryArchived": "Livraison archivée", - "amapDeliveryDate": "Date de livraison", - "amapDeliveryDelivered": "Livraison effectuée", - "amapDeliveryHistory": "Historique des livraisons", - "amapDeliveryList": "Liste des livraisons", - "amapDeliveryLocked": "Livraison verrouillée", - "amapDeliveryOn": "Livraison le", - "amapDeliveryOpened": "Livraison ouverte", - "amapDeliveryNotArchived": "Livraison non archivée", - "amapDeliveryNotLocked": "Livraison non verrouillée", - "amapDeliveryNotDelivered": "Livraison non effectuée", - "amapDeliveryNotOpened": "Livraison non ouverte", - "amapEditDelivery": "Modifier la livraison", - "amapEditedCommand": "Commande modifiée", - "amapEditingError": "Erreur lors de la modification", - "amapEditProduct": "Modifier le produit", - "amapEndingDelivery": "Fin de la livraison", - "amapError": "Erreur", - "amapErrorLink": "Erreur lors de l'ouverture du lien", - "amapErrorLoadingUser": "Erreur lors du chargement des utilisateurs", - "amapEvening": "Soir", - "amapExpectingNumber": "Veuillez entrer un nombre", - "amapFillField": "Veuillez remplir ce champ", - "amapHandlingAccount": "Gérer les comptes", - "amapLoading": "Chargement...", - "amapLoadingError": "Erreur lors du chargement", - "amapLock": "Verrouiller", - "amapLocked": "Verrouillée", - "amapLockedDelivery": "Livraison verrouillée", - "amapLockedOrder": "Commande verrouillée", - "amapLooking": "Rechercher", - "amapLockingDelivery": "Verrouiller la livraison ?", - "amapMidDay": "Midi", - "amapMyOrders": "Mes commandes", - "amapName": "Nom", - "amapNextStep": "Étape suivante", - "amapNoProduct": "Pas de produit", - "amapNoCurrentOrder": "Pas de commande en cours", - "amapNoMoney": "Pas assez d'argent", - "amapNoOpennedDelivery": "Pas de livraison ouverte", - "amapNoOrder": "Pas de commande", - "amapNoSelectedDelivery": "Pas de livraison sélectionnée", - "amapNotEnoughMoney": "Pas assez d'argent", - "amapNotPlannedDelivery": "Pas de livraison planifiée", - "amapOneOrder": "commande", - "amapOpenDelivery": "Ouvrir", - "amapOpened": "Ouverte", - "amapOpenningDelivery": "Ouvrir la livraison ?", - "amapOrder": "Commander", - "amapOrders": "Commandes", - "amapPickChooseCategory": "Veuillez entrer une valeur ou choisir une catégorie existante", - "amapPickDeliveryMoment": "Choisissez un moment de livraison", - "amapPresentation": "Présentation", - "amapPresentation1": "L'AMAP (association pour le maintien d'une agriculture paysanne) est un service proposé par l'association Planet&Co de l'ECL. Vous pouvez ainsi recevoir des produits (paniers de fruits et légumes, jus, confitures...) directement sur le campus !\n\nLes commandes doivent être passées avant le vendredi 21h et sont livrées sur le campus le mardi de 13h à 13h45 (ou de 18h15 à 18h30 si vous ne pouvez pas passer le midi) dans le hall du M16.\n\nVous ne pouvez commander que si votre solde le permet. Vous pouvez recharger votre solde via la collecte Lydia ou bien avec un chèque que vous pouvez nous transmettre lors des permanences.\n\nLien vers la collecte Lydia pour le rechargement : ", - "amapPresentation2": "\n\nN'hésitez pas à nous contacter en cas de problème !", - "amapPrice": "Prix", - "amapProduct": "produit", - "amapProducts": "Produits", - "amapProductInDelivery": "Produit dans une livraison non terminée", - "amapQuantity": "Quantité", - "amapRequiredDate": "La date est requise", - "amapSeeMore": "Voir plus", - "amapThe": "Le", - "amapUnlock": "Dévérouiller", - "amapUnlockedDelivery": "Livraison dévérouillée", - "amapUnlockingDelivery": "Dévérouiller la livraison ?", - "amapUpdate": "Modifier", - "amapUpdatedAmount": "Solde modifié", - "amapUpdatedOrder": "Commande modifiée", - "amapUpdatedProduct": "Produit modifié", - "amapUpdatingError": "Echec de la modification", - "amapUsersNotFound": "Aucun utilisateur trouvé", - "amapWaiting": "En attente", - "bookingAdd": "Ajouter", - "bookingAddBookingPage": "Demande", - "bookingAddRoom": "Ajouter une salle", - "bookingAddBooking": "Ajouter une réservation", - "bookingAddedBooking": "Demande ajoutée", - "bookingAddedRoom": "Salle ajoutée", - "bookingAddedManager": "Gestionnaire ajouté", - "bookingAddingError": "Erreur lors de l'ajout", - "bookingAddManager": "Ajouter un gestionnaire", - "bookingAdminPage": "Administrateur", - "bookingAllDay": "Toute la journée", - "bookingBookedFor": "Réservé pour", - "bookingBooking": "Réservation", - "bookingBookingCreated": "Réservation créée", - "bookingBookingDemand": "Demande de réservation", - "bookingBookingNote": "Note de la réservation", - "bookingBookingPage": "Réservation", - "bookingBookingReason": "Motif de la réservation", - "bookingBy": "par", - "bookingConfirm": "Confirmer", - "bookingConfirmation": "Confirmation", - "bookingConfirmBooking": "Confirmer la réservation ?", - "bookingConfirmed": "Validée", - "bookingDates": "Dates", - "bookingDecline": "Refuser", - "bookingDeclineBooking": "Refuser la réservation ?", - "bookingDeclined": "Refusée", - "bookingDelete": "Supprimer", - "bookingDeleting": "Suppression", - "bookingDeleteBooking": "Suppression", - "bookingDeleteBookingConfirmation": "Êtes-vous sûr de vouloir supprimer cette réservation ?", - "bookingDeletedBooking": "Réservation supprimée", - "bookingDeletedRoom": "Salle supprimée", - "bookingDeletedManager": "Gestionnaire supprimé", - "bookingDeleteRoomConfirmation": "Êtes-vous sûr de vouloir supprimer cette salle ?\n\nLa salle ne doit avoir aucune réservation en cours ou à venir pour être supprimée", - "bookingDeleteManagerConfirmation": "Êtes-vous sûr de vouloir supprimer ce gestionnaire ?\n\nLe gestionnaire ne doit être associé à aucune salle pour pouvoir être supprimé", - "bookingDeletingBooking": "Supprimer la réservation ?", - "bookingDeletingError": "Erreur lors de la suppression", - "bookingDeletingRoom": "Supprimer la salle ?", - "bookingEdit": "Modifier", - "bookingEditBooking": "Modifier une réservation", - "bookingEditionError": "Erreur lors de la modification", - "bookingEditedBooking": "Réservation modifiée", - "bookingEditedRoom": "Salle modifiée", - "bookingEditedManager": "Gestionnaire modifié", - "bookingEditManager": "Modifier ou supprimer un gestionnaire", - "bookingEditRoom": "Modifier ou supprimer une salle", - "bookingEndDate": "Date de fin", - "bookingEndHour": "Heure de fin", - "bookingEntity": "Pour qui ?", - "bookingError": "Erreur", - "bookingEventEvery": "Tous les", - "bookingHistoryPage": "Historique", - "bookingIncorrectOrMissingFields": "Champs incorrects ou manquants", - "bookingInterval": "Intervalle", - "bookingInvalidIntervalError": "Intervalle invalide", - "bookingInvalidDates": "Dates invalides", - "bookingInvalidRoom": "Salle invalide", - "bookingKeysRequested": "Clés demandées", - "bookingManagement": "Gestion", - "bookingManager": "Gestionnaire", - "bookingManagerName": "Nom du gestionnaire", - "bookingMultipleDay": "Plusieurs jours", - "bookingMyBookings": "Mes réservations", - "bookingNecessaryKey": "Clé nécessaire", - "bookingNext": "Suivant", - "bookingNo": "Non", - "bookingNoCurrentBooking": "Pas de réservation en cours", - "bookingNoDateError": "Veuillez choisir une date", - "bookingNoAppointmentInReccurence": "Aucun créneau existe avec ces paramètres de récurrence", - "bookingNoDaySelected": "Aucun jour sélectionné", - "bookingNoDescriptionError": "Veuillez entrer une description", - "bookingNoKeys": "Aucune clé", - "bookingNoNoteError": "Veuillez entrer une note", - "bookingNoPhoneRegistered": "Numéro non renseigné", - "bookingNoReasonError": "Veuillez entrer un motif", - "bookingNoRoomFoundError": "Aucune salle enregistrée", - "bookingNoRoomFound": "Aucune salle trouvée", - "bookingNote": "Note", - "bookingOther": "Autre", - "bookingPending": "En attente", - "bookingPrevious": "Précédent", - "bookingReason": "Motif", - "bookingRecurrence": "Récurrence", - "bookingRecurrenceDays": "Jours de récurrence", - "bookingRecurrenceEndDate": "Date de fin de récurrence", - "bookingRecurrent": "Récurrent", - "bookingRegisteredRooms": "Salles enregistrées", - "bookingRoom": "Salle", - "bookingRoomName": "Nom de la salle", - "bookingStartDate": "Date de début", - "bookingStartHour": "Heure de début", - "bookingWeeks": "Semaines", - "bookingYes": "Oui", - "bookingWeekDayMon": "Lundi", - "bookingWeekDayTue": "Mardi", - "bookingWeekDayWed": "Mercredi", - "bookingWeekDayThu": "Jeudi", - "bookingWeekDayFri": "Vendredi", - "bookingWeekDaySat": "Samedi", - "bookingWeekDaySun": "Dimanche", - "cinemaAdd": "Ajouter", - "cinemaAddedSession": "Séance ajoutée", - "cinemaAddingError": "Erreur lors de l'ajout", - "cinemaAddSession": "Ajouter une séance", - "cinemaCinema": "Cinéma", - "cinemaDeleteSession": "Supprimer la séance ?", - "cinemaDeleting": "Suppression", - "cinemaDuration": "Durée", - "cinemaEdit": "Modifier", - "cinemaEditedSession": "Séance modifiée", - "cinemaEditingError": "Erreur lors de la modification", - "cinemaEditSession": "Modifier la séance", - "cinemaEmptyUrl": "Veuillez entrer une URL", - "cinemaImportFromTMDB": "Importer depuis TMDB", - "cinemaIncomingSession": "A l'affiche", - "cinemaIncorrectOrMissingFields": "Champs incorrects ou manquants", - "cinemaInvalidUrl": "URL invalide", - "cinemaGenre": "Genre", - "cinemaName": "Nom", - "cinemaNoDateError": "Veuillez entrer une date", - "cinemaNoDuration": "Veuillez entrer une durée", - "cinemaNoOverview": "Aucun synopsis", - "cinemaNoPoster": "Aucune affiche", - "cinemaNoSession": "Aucune séance", - "cinemaOverview": "Synopsis", - "cinemaPosterUrl": "URL de l'affiche", - "cinemaSessionDate": "Jour de la séance", - "cinemaStartHour": "Heure de début", - "cinemaTagline": "Slogan", - "cinemaThe": "Le", - "drawerAdmin": "Administration", - "drawerAndroidAppLink": "https://play.google.com/store/apps/details?id=fr.myecl.titan", - "drawerCopied": "Copié !", - "drawerDownloadAppOnMobileDevice": "Ce site est la version Web de l'application MyECL. Nous vous invitons à télécharger l'application. N'utilisez ce site qu'en cas de problème avec l'application.\n", - "drawerIosAppLink": "https://apps.apple.com/fr/app/myecl/id6444443430", - "drawerLoginOut": "Voulez-vous vous déconnecter ?", - "drawerLogOut": "Déconnexion", - "drawerOr": " ou ", - "drawerSettings": "Paramètres", - "eventAdd": "Ajouter", - "eventAddEvent": "Ajouter un événement", - "eventAddedEvent": "Événement ajouté", - "eventAddingError": "Erreur lors de l'ajout", - "eventAllDay": "Toute la journée", - "eventConfirm": "Confirmer", - "eventConfirmEvent": "Confirmer l'événement ?", - "eventConfirmation": "Confirmation", - "eventConfirmed": "Confirmé", - "eventDates": "Dates", - "eventDecline": "Refuser", - "eventDeclineEvent": "Refuser l'événement ?", - "eventDeclined": "Refusé", - "eventDelete": "Supprimer", - "eventDeletedEvent": "Événement supprimé", - "eventDeleting": "Suppression", - "eventDeletingError": "Erreur lors de la suppression", - "eventDeletingEvent": "Supprimer l'événement ?", - "eventDescription": "Description", - "eventEdit": "Modifier", - "eventEditEvent": "Modifier un événement", - "eventEditedEvent": "Événement modifié", - "eventEditingError": "Erreur lors de la modification", - "eventEndDate": "Date de fin", - "eventEndHour": "Heure de fin", - "eventError": "Erreur", - "eventEventList": "Liste des événements", - "eventEventType": "Type d'événement", - "eventEvery": "Tous les", - "eventHistory": "Historique", - "eventIncorrectOrMissingFields": "Certains champs sont incorrects ou manquants", - "eventInterval": "Intervalle", - "eventInvalidDates": "La date de fin doit être après la date de début", - "eventInvalidIntervalError": "Veuillez entrer un intervalle valide", - "eventLocation": "Lieu", - "eventMyEvents": "Mes événements", - "eventName": "Nom", - "eventNext": "Suivant", - "eventNo": "Non", - "eventNoCurrentEvent": "Aucun événement en cours", - "eventNoDateError": "Veuillez entrer une date", - "eventNoDaySelected": "Aucun jour sélectionné", - "eventNoDescriptionError": "Veuillez entrer une description", - "eventNoEvent": "Aucun événement", - "eventNoNameError": "Veuillez entrer un nom", - "eventNoOrganizerError": "Veuillez entrer un organisateur", - "eventNoPlaceError": "Veuillez entrer un lieu", - "eventNoPhoneRegistered": "Numéro non renseigné", - "eventNoRuleError": "Veuillez entrer une règle de récurrence", - "eventOrganizer": "Organisateur", - "eventOther": "Autre", - "eventPending": "En attente", - "eventPrevious": "Précédent", - "eventRecurrence": "Récurrence", - "eventRecurrenceDays": "Jours de récurrence", - "eventRecurrenceEndDate": "Date de fin de la récurrence", - "eventRecurrenceRule": "Règle de récurrence", - "eventRoom": "Salle", - "eventStartDate": "Date de début", - "eventStartHour": "Heure de début", - "eventTitle": "Événements", - "eventYes": "Oui", - "eventEventEvery": "Toutes les", - "eventWeeks": "semaines", - "eventDayMon": "Lundi", - "eventDayTue": "Mardi", - "eventDayWed": "Mercredi", - "eventDayThu": "Jeudi", - "eventDayFri": "Vendredi", - "eventDaySat": "Samedi", - "eventDaySun": "Dimanche", - "homeCalendar": "Calendrier", - "homeEventOf": "Évènements du", - "homeIncomingEvents": "Évènements à venir", - "homeLastInfos": "Dernières annonces", - "homeNoEvents": "Aucun évènement", - "homeTranslateDayShortMon": "Lun", - "homeTranslateDayShortTue": "Mar", - "homeTranslateDayShortWed": "Mer", - "homeTranslateDayShortThu": "Jeu", - "homeTranslateDayShortFri": "Ven", - "homeTranslateDayShortSat": "Sam", - "homeTranslateDayShortSun": "Dim", - "loanAdd": "Ajouter", - "loanAddLoan": "Ajouter un prêt", - "loanAddObject": "Ajouter un objet", - "loanAddedLoan": "Prêt ajouté", - "loanAddedObject": "Objet ajouté", - "loanAddedRoom": "Salle ajoutée", - "loanAddingError": "Erreur lors de l'ajout", - "loanAdmin": "Administrateur", - "loanAvailable": "Disponible", - "loanAvailableMultiple": "Disponibles", - "loanBorrowed": "Emprunté", - "loanBorrowedMultiple": "Empruntés", - "loanAnd": "et", - "loanAssociation": "Association", - "loanAvailableItems": "Objets disponibles", - "loanBeginDate": "Date du début du prêt", - "loanBorrower": "Emprunteur", - "loanCaution": "Caution", - "loanCancel": "Annuler", - "loanConfirm": "Confirmer", - "loanConfirmation": "Confirmation", - "loanDates": "Dates", - "loanDays": "Jours", - "loanDelay": "Délai de la prolongation", - "loanDelete": "Supprimer", - "loanDeletingLoan": "Supprimer le prêt ?", - "loanDeletedItem": "Objet supprimé", - "loanDeletedLoan": "Prêt supprimé", - "loanDeleting": "Suppression", - "loanDeletingError": "Erreur lors de la suppression", - "loanDeletingItem": "Supprimer l'objet ?", - "loanDuration": "Durée", - "loanEdit": "Modifier", - "loanEditItem": "Modifier l'objet", - "loanEditLoan": "Modifier le prêt", - "loanEditedRoom": "Salle modifiée", - "loanEndDate": "Date de fin du prêt", - "loanEnded": "Terminé", - "loanEnterDate": "Veuillez entrer une date", - "loanExtendedLoan": "Prêt prolongé", - "loanExtendingError": "Erreur lors de la prolongation", - "loanHistory": "Historique", - "loanIncorrectOrMissingFields": "Des champs sont manquants ou incorrects", - "loanInvalidNumber": "Veuillez entrer un nombre", - "loanInvalidDates": "Les dates ne sont pas valides", - "loanItem": "Objet", - "loanItems": "Objets", - "loanItemHandling": "Gestion des objets", - "loanItemSelected": "objet sélectionné", - "loanItemsSelected": "objets sélectionnés", - "loanLendingDuration": "Durée possible du prêt", - "loanLoan": "Prêt", - "loanLoanHandling": "Gestion des prêts", - "loanLooking": "Rechercher", - "loanName": "Nom", - "loanNext": "Suivant", - "loanNo": "Non", - "loanNoAssociationsFounded": "Aucune association trouvée", - "loanNoAvailableItems": "Aucun objet disponible", - "loanNoBorrower": "Aucun emprunteur", - "loanNoItems": "Aucun objet", - "loanNoItemSelected": "Aucun objet sélectionné", - "loanNoLoan": "Aucun prêt", - "loanNoReturnedDate": "Pas de date de retour", - "loanQuantity": "Quantité", - "loanNone": "Aucun", - "loanNote": "Note", - "loanNoValue": "Veuillez entrer une valeur", - "loanOnGoing": "En cours", - "loanOnGoingLoan": "Prêt en cours", - "loanOthers": "autres", - "loanPaidCaution": "Caution payée", - "loanPositiveNumber": "Veuillez entrer un nombre positif", - "loanPrevious": "Précédent", - "loanReturned": "Rendu", - "loanReturnedLoan": "Prêt rendu", - "loanReturningError": "Erreur lors du retour", - "loanReturningLoan": "Retour", - "loanReturnLoan": "Rendre le prêt ?", - "loanReturnLoanDescription": "Voulez-vous rendre ce prêt ?", - "loanToReturn": "A rendre", - "loanUnavailable": "Indisponible", - "loanUpdate": "Modifier", - "loanUpdatedItem": "Objet modifié", - "loanUpdatedLoan": "Prêt modifié", - "loanUpdatingError": "Erreur lors de la modification", - "loanYes": "Oui", - "loginAccountActivated": "Compte activé", - "loginAccountNotActivated": "Compte non activé", - "loginActivationCode": "Code d'activation", - "loginBirthday": "Date de naissance", - "loginCanBeEmpty": "Ce champ peut être vide", - "loginConfirmPassword": "Confirmer le mot de passe", - "loginCreate": "Créer", - "loginCreateAccount": "Créer un compte", - "loginCreateAccountTitle": "Créer un\ncompte", - "loginEmail": "Email", - "loginEmailEmpty": "Veuillez entrer une adresse mail", - "loginEmailInvalid": "Veuillez entrer une adresse mail de centrale.\nSi vous n'en possédez pas, veuillez contacter Éclair", - "loginEmptyFieldError": "Ce champ ne peut pas être vide", - "loginEndActivation": "Finaliser l'activation", - "loginEndResetPassword": "Finaliser la \nréinitialisation", - "loginErrorResetPassword": "Erreur lors de la réinitialisation", - "loginExpectingDate": "Une date est attendue", - "loginFillAllFields": "Veuillez remplir tous les champs", - "loginFirstname": "Prénom", - "loginFloor": "Étage", - "loginForgetPassword": "Mot de passe\noublié", - "loginForgotPassword": "Mot de passe oublié ?", - "loginInvalidToken": "Code d'activation invalide", - "loginLoginFailed": "Échec de la connexion", - "loginMailSendingError": "Erreur lors de la création du compte", - "loginMustBeIntError": "Ce champ doit être un entier", - "loginName": "Nom", - "loginNewPassword": "Nouveau mot de passe", - "loginPassword": "Mot de passe", - "loginPasswordLengthError": "Le mot de passe doit faire au moins 6 caractères", - "loginPasswordUppercaseError": "Le mot de passe doit contenir au moins une majuscule", - "loginPasswordLowercaseError": "Le mot de passe doit contenir au moins une minucule", - "loginPasswordNumberError": "Le mot de passe doit contenir au moins un chiffre", - "loginPasswordSpecialCaracterError": "Le mot de passe doit contenir au moins un caractère spécial", - "loginPasswordMustMatch": "Les mots de passe doivent correspondre", - "loginPasswordStrengthVeryWeak": "Très faible", - "loginPasswordStrengthWeak": "Faible", - "loginPasswordStrengthMedium": "Moyen", - "loginPasswordStrengthStrong": "Fort", - "loginPasswordStrengthVeryStrong": "Très fort", - "loginPhone": "Téléphone", - "loginPromo": "Promo entrante (ex : 2023)", - "loginSendedMail": "Mail de confirmation envoyé", - "loginSendedResetMail": "Mail de réinitialisation envoyé", - "loginSignIn": "Se connecter", - "loginRegister": "S'inscrire", - "loginRecievedMail": "J'ai reçu le mail", - "loginRecover": "Réinitialiser", - "loginResetedPassword": "Mot de passe réinitialisé", - "loginResetPasswordTitle": "Réinitialiser\nle mot de \npasse", - "loginNickname": "Surnom", - "loginWelcomeBack": "Bienvenue", - "loginAppName": "MyECL", - "othersCheckInternetConnection": "Veuillez vérifier votre connexion internet", - "othersRetry": "Réessayer", - "othersTooOldVersion": "Votre version de l'application est trop ancienne.\n\nVeuillez mettre à jour l'application.", - "othersUnableToConnectToServer": "Impossible de se connecter au serveur", - "othersVersion": "Version", - "othersNoModule": "Aucun module disponible, veuillez réessayer ultérieurement 😢😢", - "othersAdmin": "Admin", - "othersError": "Une erreur est survenue", - "othersNoValue": "Veuillez entrer une valeur", - "othersInvalidNumber": "Veuillez entrer un nombre", - "othersNoDateError": "Veuillez entrer une date", - "othersImageSizeTooBig": "La taille de l'image ne doit pas dépasser 4 Mio", - "othersImageError": "Erreur lors de l'ajout de l'image", - "phAddNewJournal": "Ajouter un nouveau journal", - "phNameField": "Nom : ", - "phDateField": "Date : ", - "phDelete": "Voulez-vous vraiment supprimer ce journal ?", - "phIrreversibleAction": "Cette action est irréversible", - "phToHeavyFile": "Fichier trop volumineux", - "phAddPdfFile": "Ajouter un fichier PDF", - "phEditPdfFile": "Modifier le fichier PDF", - "phPhName": "Nom du PH", - "phDate": "Date", - "phAdded": "Ajouté", - "phEdited": "Modifié", - "phAddingFileError": "Erreur d'ajout", - "phMissingInformatonsOrPdf": "Informations manquantes ou fichier PDF manquant", - "phAdd": "Ajouter", - "phEdit": "Modifier", - "phSeePreviousJournal": "Voir les anciens journaux", - "phNoJournalInDatabase": "Pas encore de PH dans la base de donnée", - "phSuccesDowloading": "Téléchargé avec succès", - "phonebookActiveMandate": "Mandat actif :", - "phonebookAdd": "Ajouter", - "phonebookAddAssociation": "Ajouter une association", - "phonebookAddedAssociation": "Association ajoutée", - "phonebookAddedMember": "Membre ajouté", - "phonebookAddingError": "Erreur lors de l'ajout", - "phonebookAddMember": "Ajouter un membre", - "phonebookAddRole": "Ajouter un rôle", - "phonebookAdmin": "Admin", - "phonebookAdminPage": "Page Administrateur", - "phonebookAll": "Toutes", - "phonebookApparentName": "Nom public du rôle :", - "phonebookAssociation": "Association :", - "phonebookAssociationDetail": "Détail de l'association :", - "phonebookAssociationKind": "Type d'association :", - "phonebookAssociationPure": "Association", - "phonebookAssociationPureSearch": " Association", - "phonebookAssociations": "Associations :", - "phonebookCancel": "Annuler", - "phonebookChangeMandate": "Passer au mandat ", - "phonebookChangeMandateConfirm": "Êtes-vous sûr de vouloir changer tout le mandat ?\nCette action est irréversible !", - "phonebookCopied": "Copié dans le presse-papier", - "phonebookDeactivateAssociation": "Êtes-vous sûr de vouloir désactiver cette association ?\nCette action est irréversible !", - "phonebookDeactivatedAssociation": "Association désactivée", - "phonebookDeactivatedAssociationWarning": "Attention, cette association est désactivée, vous ne pouvez pas la modifier", - "phonebookDeactivating": "Désactiver l'association ?", - "phonebookDeactivatingError": "Erreur lors de la désactivation", - "phonebookDetail": "Détail :", - "phonebookDeleteAssociation": "Supprimer l'association ?\nCela va effacer tout l'historique de l'association", - "phonebookDeletedAssociation": "Association supprimée", - "phonebookDeletedMember": "Membre supprimé", - "phonebookDeleting": "Suppression", - "phonebookDeletingError": "Erreur lors de la suppression", - "phonebookDescription": "Description", - "phonebookEdit": "Modifier", - "phonebookEditMembership": "Modifier le rôle", - "phonebookEmail": "Email :", - "phonebookEmailCopied": "Email copié dans le presse-papier", - "phonebookEmptyApparentName": "Veuillez entrer un nom de role", - "phonebookEmptyFieldError": "Un champ n'est pas rempli", - "phonebookEmptyKindError": "Veuillez choisir un type d'association", - "phonebookEmptyMember": "Aucun membre sélectionné", - "phonebookErrorAssociationLoading": "Erreur lors du chargement de l'association", - "phonebookErrorAssociationNameEmpty": "Veuillez entrer un nom d'association", - "phonebookErrorAssociationPicture": "Erreur lors de la modification de la photo d'association", - "phonebookErrorKindsLoading": "Erreur lors du chargement des types d'association", - "phonebookErrorLoadAssociationList": "Erreur lors du chargement de la liste des associations", - "phonebookErrorLoadAssociationMember": "Erreur lors du chargement des membres de l'association", - "phonebookErrorLoadAssociationPicture": "Erreur lors du chargement de la photo d'association", - "phonebookErrorLoadProfilePicture": "Erreur", - "phonebookErrorRoleTagsLoading": "Erreur lors du chargement des tags de rôle", - "phonebookExistingMembership": "Ce membre est déjà dans le mandat actuel", - "phonebookFirstname": "Prénom :", - "phonebookGroups": "Groupes associés :", - "phonebookMandateChangingError": "Erreur lors du changement de mandat", - "phonebookMember": "Membre", - "phonebookMemberReordered": "Membre réordonné", - "phonebookMembers": "Membres", - "phonebookMembershipAssociationError": "Veuillez choisir une association", - "phonebookMembershipRole": "Rôle :", - "phonebookMembershipRoleError": "Veuillez choisir un rôle", - "phonebookName": "Nom :", - "phonebookNameCopied": "Nom et prénom copié dans le presse-papier", - "phonebookNamePure": "Nom", - "phonebookNewMandate": "Nouveau mandat", - "phonebookNewMandateConfirmed": "Mandat changé", - "phonebookNickname": "Surnom :", - "phonebookNicknameCopied": "Surnom copié dans le presse-papier", - "phonebookNoAssociationFound": "Aucune association trouvée", - "phonebookNoMember": "Aucun membre", - "phonebookNoMemberRole": "Aucun role trouvé", - "phonebookPhone": "Téléphone :", - "phonebookPhonebook": "Annuaire", - "phonebookPhonebookSearch": "Rechercher", - "phonebookPhonebookSearchAssociation": "Association", - "phonebookPhonebookSearchField": "Rechercher :", - "phonebookPhonebookSearchName": "Nom/Prénom/Surnom", - "phonebookPhonebookSearchRole": "Poste", - "phonebookPresidentRoleTag": "Prez'", - "phonebookPromoNotGiven": "Promo non renseignée", - "phonebookPromotion": "Promotion :", - "phonebookReorderingError": "Erreur lors du réordonnement", - "phonebookResearch": "Rechercher", - "phonebookRolePure": "Rôle", - "phonebookTooHeavyAssociationPicture": "L'image est trop lourde (max 4Mo)", - "phonebookUpdateGroups": "Mettre à jour les groupes", - "phonebookUpdatedAssociation": "Association modifiée", - "phonebookUpdatedAssociationPicture": "La photo d'association a été changée", - "phonebookUpdatedGroups": "Groupes mis à jour", - "phonebookUpdatedMember": "Membre modifié", - "phonebookUpdatingError": "Erreur lors de la modification", - "phonebookValidation": "Valider", - "purchasesPurchases": "Achats", - "purchasesResearch": "Rechercher", - "purchasesNoPurchasesFound": "Aucun achat trouvé", - "purchasesNoTickets": "Aucun ticket", - "purchasesTicketsError": "Erreur lors du chargement des tickets", - "purchasesPurchasesError": "Erreur lors du chargement des achats", - "purchasesNoPurchases": "Aucun achat", - "purchasesTimes": "fois", - "purchasesAlreadyUsed": "Déjà utilisé", - "purchasesNotPaid": "Non validé", - "purchasesPleaseSelectProduct": "Veuillez sélectionner un produit", - "purchasesProducts": "Produits", - "purchasesCancel": "Annuler", - "purchasesValidate": "Valider", - "purchasesLeftScan": "Scans restants", - "purchasesTag": "Tag", - "purchasesHistory": "Historique", - "purchasesPleaseSelectSeller": "Veuillez sélectionner un vendeur", - "purchasesNoTagGiven": "Attention, aucun tag n'a été entré", - "purchasesTickets": "Tickets", - "purchasesNoScannableProducts": "Aucun produit scannable", - "purchasesLoading": "En attente de scan", - "purchasesScan": "Scanner", - "raffleRaffle": "Tombola", - "rafflePrize": "Lot", - "rafflePrizes": "Lots", - "raffleActualRaffles": "Tombola en cours", - "rafflePastRaffles": "Tombola passés", - "raffleYourTickets": "Tous vos tickets", - "raffleCreateMenu": "Menu de Création", - "raffleNextRaffles": "Prochaines tombolas", - "raffleNoTicket": "Vous n'avez pas de ticket", - "raffleSeeRaffleDetail": "Voir lots/tickets", - "raffleActualPrize": "Lots actuels", - "raffleMajorPrize": "Lot Majeurs", - "raffleTakeTickets": "Prendre vos tickets", - "raffleNoTicketBuyable": "Vous ne pouvez pas achetez de billets pour l'instant", - "raffleNoCurrentPrize": "Il n'y a aucun lots actuellement", - "raffleModifTombola": "Vous pouvez modifiez vos tombolas ou en créer de nouvelles, toute décision doit ensuite être prise par les admins", - "raffleCreateYourRaffle": "Votre menu de création de tombolas", - "rafflePossiblePrice": "Prix possible", - "raffleInformation": "Information et Statistiques", - "raffleAccounts": "Comptes", - "raffleAdd": "Ajouter", - "raffleUpdatedAmount": "Montant mis à jour", - "raffleUpdatingError": "Erreur lors de la mise à jour", - "raffleDeletedPrize": "Lot supprimé", - "raffleDeletingError": "Erreur lors de la suppression", - "raffleQuantity": "Quantité", - "raffleClose": "Fermer", - "raffleOpen": "Ouvrir", - "raffleAddTypeTicketSimple": "Ajouter", - "raffleAddingError": "Erreur lors de l'ajout", - "raffleEditTypeTicketSimple": "Modifier", - "raffleFillField": "Le champ ne peut pas être vide", - "raffleWaiting": "Chargement", - "raffleEditingError": "Erreur lors de la modification", - "raffleAddedTicket": "Ticket ajouté", - "raffleEditedTicket": "Ticket modifié", - "raffleAlreadyExistTicket": "Le ticket existe déjà", - "raffleNumberExpected": "Un entier est attendu", - "raffleDeletedTicket": "Ticket supprimé", - "raffleAddPrize": "Ajouter", - "raffleEditPrize": "Modifier", - "raffleOpenRaffle": "Ouvrir la tombola", - "raffleCloseRaffle": "Fermer la tombola", - "raffleOpenRaffleDescription": "Vous allez ouvrir la tombola, les utilisateurs pourront acheter des tickets. Vous ne pourrez plus modifier la tombola. Êtes-vous sûr de vouloir continuer ?", - "raffleCloseRaffleDescription": "Vous allez fermer la tombola, les utilisateurs ne pourront plus acheter de tickets. Êtes-vous sûr de vouloir continuer ?", - "raffleNoCurrentRaffle": "Il n'y a aucune tombola en cours", - "raffleBoughtTicket": "Ticket acheté", - "raffleDrawingError": "Erreur lors du tirage", - "raffleInvalidPrice": "Le prix doit être supérieur à 0", - "raffleMustBePositive": "Le nombre doit être strictement positif", - "raffleDraw": "Tirer", - "raffleDrawn": "Tiré", - "raffleError": "Erreur", - "raffleGathered": "Récolté", - "raffleTickets": "Tickets", - "raffleTicket": "ticket", - "raffleWinner": "Gagnant", - "raffleNoPrize": "Aucun lot", - "raffleDeletePrize": "Supprimer le lot", - "raffleDeletePrizeDescription": "Vous allez supprimer le lot, êtes-vous sûr de vouloir continuer ?", - "raffleDrawing": "Tirage", - "raffleDrawingDescription": "Tirer le gagnant du lot ?", - "raffleDeleteTicket": "Supprimer le ticket", - "raffleDeleteTicketDescription": "Vous allez supprimer le ticket, êtes-vous sûr de vouloir continuer ?", - "raffleWinningTickets": "Tickets gagnants", - "raffleNoWinningTicketYet": "Les tickets gagnants seront affichés ici", - "raffleName": "Nom", - "raffleDescription": "Description", - "raffleBuyThisTicket": "Acheter ce ticket", - "raffleLockedRaffle": "Tombola verrouillée", - "raffleUnavailableRaffle": "Tombola indisponible", - "raffleNotEnoughMoney": "Vous n'avez pas assez d'argent", - "raffleWinnable": "gagnable", - "raffleNoDescription": "Aucune description", - "raffleAmount": "Solde", - "raffleLoading": "Chargement", - "raffleTicketNumber": "Nombre de ticket", - "rafflePrice": "Prix", - "raffleEditRaffle": "Modifier la tombola", - "raffleEdit": "Modifier", - "raffleAddPackTicket": "Ajouter un pack de ticket", - "recommendationRecommendation": "Bons plans", - "recommendationTitle": "Titre", - "recommendationLogo": "Logo", - "recommendationCode": "Code", - "recommendationSummary": "Court résumé", - "recommendationDescription": "Description", - "recommendationAdd": "Ajouter", - "recommendationEdit": "Modifier", - "recommendationDelete": "Supprimer", - "recommendationAddImage": "Veuillez ajouter une image", - "recommendationAddedRecommendation": "Bon plan ajouté", - "recommendationEditedRecommendation": "Bon plan modifié", - "recommendationDeleteRecommendationConfirmation": "Êtes-vous sûr de vouloir supprimer ce bon plan ?", - "recommendationDeleteRecommendation": "Suppresion", - "recommendationDeletingRecommendationError": "Erreur lors de la suppression", - "recommendationDeletedRecommendation": "Bon plan supprimé", - "recommendationIncorrectOrMissingFields": "Champs incorrects ou manquants", - "recommendationEditingError": "Échec de la modification", - "recommendationAddingError": "Échec de l'ajout", - "recommendationCopiedCode": "Code de réduction copié", - "seedLibraryAdd": "Ajouter", - "seedLibraryAddedPlant": "Plante ajoutée", - "seedLibraryAddedSpecies": "Espèce ajoutée", - "seedLibraryAddingError": "Erreur lors de l'ajout", - "seedLibraryAddPlant": "Déposer une plante", - "seedLibraryAddSpecies": "Ajouter une espèce", - "seedLibraryAll": "Toutes", - "seedLibraryAncestor": "Ancêtre", - "seedLibraryAround": "environ", - "seedLibraryAutumn": "Automne", - "seedLibraryBorrowedPlant": "Plante empruntée", - "seedLibraryBorrowingDate": "Date d'emprunt :", - "seedLibraryBorrowPlant": "Emprunter la plante", - "seedLibraryCard": "Carte", - "seedLibraryChoosingAncestor": "Veuillez choisir un ancêtre", - "seedLibraryChoosingSpecies": "Veuillez choisir une espèce", - "seedLibraryChoosingSpeciesOrAncestor": "Veuillez choisir une espèce ou un ancêtre", - "seedLibraryContact": "Contact :", - "seedLibraryDays": "jours", - "seedLibraryDeadMsg": "Voulez-vous déclarer la plante morte ?", - "seedLibraryDeadPlant": "Plante morte", - "seedLibraryDeathDate": "Date de mort", - "seedLibraryDeletedSpecies": "Espèce supprimée", - "seedLibraryDeleteSpecies": "Supprimer l'espèce ?", - "seedLibraryDeleting": "Suppression", - "seedLibraryDeletingError": "Erreur lors de la suppression", - "seedLibraryDepositNotAvailable": "Le dépôt de plantes n'est pas possible sans emprunter une plante au préalable", - "seedLibraryDescription": "Description", - "seedLibraryDifficulty": "Difficulté :", - "seedLibraryEdit": "Modifier", - "seedLibraryEditedPlant": "Plante modifiée", - "seedLibraryEditInformation": "Modifier les informations", - "seedLibraryEditingError": "Erreur lors de la modification", - "seedLibraryEditSpecies": "Modifier l'espèce", - "seedLibraryEmptyDifficultyError": "Veuillez choisir une difficulté", - "seedLibraryEmptyFieldError": "Veuillez remplir tous les champs", - "seedLibraryEmptyTypeError": "Veuillez choisir un type de plante", - "seedLibraryEndMonth": "Mois de fin :", - "seedLibraryFacebookUrl": "Lien Facebook", - "seedLibraryFilters": "Filtres", - "seedLibraryForum": "Oskour maman j'ai tué ma plante - Forum d'aide", - "seedLibraryForumUrl": "Lien Forum", - "seedLibraryHelpSheets": "Fiches sur les plantes", - "seedLibraryInformation": "Informations :", - "seedLibraryMaturationTime": "Temps de maturation", - "seedLibraryMonthJan": "Janvier", - "seedLibraryMonthFeb": "Février", - "seedLibraryMonthMar": "Mars", - "seedLibraryMonthApr": "Avril", - "seedLibraryMonthMay": "Mai", - "seedLibraryMonthJun": "Juin", - "seedLibraryMonthJul": "Juillet", - "seedLibraryMonthAug": "Août", - "seedLibraryMonthSep": "Septembre", - "seedLibraryMonthOct": "Octobre", - "seedLibraryMonthNov": "Novembre", - "seedLibraryMonthDec": "Décembre", - "seedLibraryMyPlants": "Mes plantes", - "seedLibraryName": "Nom", - "seedLibraryNbSeedsRecommended": "Nombre de graines recommandées", - "seedLibraryNbSeedsRecommendedError": "Veuillez entrer un nombre de graines recommandé supérieur à 0", - "seedLibraryNoDateError": "Veuillez entrer une date", - "seedLibraryNoFilteredPlants": "Aucune plante ne correspond à votre recherche. Essayez d'autres filtres.", - "seedLibraryNoMorePlant": "Aucune plante n'est disponible", - "seedLibraryNoPersonalPlants": "Vous n'avez pas encore de plantes dans votre grainothèque. Vous pouvez en ajouter en allant dans les stocks.", - "seedLibraryNoSpecies": "Aucune espèce trouvée", - "seedLibraryNoStockPlants": "Aucune plante disponible dans le stock", - "seedLibraryNotes": "Notes", - "seedLibraryOk": "OK", - "seedLibraryPlantationPeriod": "Période de plantation :", - "seedLibraryPlantationType": "Type de plantation :", - "seedLibraryPlantDetail": "Détail de la plante", - "seedLibraryPlantingDate": "Date de plantation", - "seedLibraryPlantingNow": "Je la plante maintenant", - "seedLibraryPrefix": "Préfixe", - "seedLibraryPrefixError": "Prefixe déjà utilisé", - "seedLibraryPrefixLengthError": "Le préfixe doit faire 3 caractères", - "seedLibraryPropagationMethod": "Méthode de propagation :", - "seedLibraryReference": "Référence :", - "seedLibraryRemovedPlant": "Plante supprimée", - "seedLibraryRemovingError": "Erreur lors de la suppression", - "seedLibraryResearch": "Recherche", - "seedLibrarySaveChanges": "Sauvegarder les modifications", - "seedLibrarySeason": "Saison :", - "seedLibrarySeed": "Graine", - "seedLibrarySeeds": "graines", - "seedLibrarySeedDeposit": "Dépôt de plantes", - "seedLibrarySeedLibrary": "Grainothèque", - "seedLibrarySeedQuantitySimple": "Quantité de graines", - "seedLibrarySeedQuantity": "Quantité de graines :", - "seedLibraryShowDeadPlants": "Afficher les plantes mortes", - "seedLibrarySpecies": "Espèce :", - "seedLibrarySpeciesHelp": "Aide sur l'espèce", - "seedLibrarySpeciesPlural": "Espèces", - "seedLibrarySpeciesSimple": "Espèce", - "seedLibrarySpeciesType": "Type d'espèce :", - "seedLibrarySpring": "Printemps", - "seedLibraryStartMonth": "Mois de début :", - "seedLibraryStock": "Stock disponible", - "seedLibrarySummer": "Été", - "seedLibraryStocks": "Stocks", - "seedLibraryTimeUntilMaturation": "Temps avant maturation :", - "seedLibraryType": "Type :", - "seedLibraryUnableToOpen": "Impossible d'ouvrir le lien", - "seedLibraryUpdate": "Modifier", - "seedLibraryUpdatedInformation": "Informations modifiées", - "seedLibraryUpdatedSpecies": "Espèce modifiée", - "seedLibraryUpdatedPlant": "Plante modifiée", - "seedLibraryUpdatingError": "Erreur lors de la modification", - "seedLibraryWinter": "Hiver", - "seedLibraryWriteReference": "Veuillez écrire la référence suivante : ", - "settingsAccount": "Compte", - "settingsAddProfilePicture": "Ajouter une photo", - "settingsAdmin": "Administrateur", - "settingsAskHelp": "Demander de l'aide", - "settingsAssociation": "Association", - "settingsBirthday": "Date de naissance", - "settingsBugs": "Bugs", - "settingsChangePassword": "Changer de mot de passe", - "settingsChangingPassword": "Voulez-vous vraiment changer votre mot de passe ?", - "settingsConfirmPassword": "Confirmer le mot de passe", - "settingsCopied": "Copié !", - "settingsDarkMode": "Mode sombre", - "settingsDarkModeOff": "Désactivé", - "settingsDeleteLogs": "Supprimer les logs ?", - "settingsDeleteNotificationLogs": "Supprimer les logs des notifications ?", - "settingsDetelePersonalData": "Supprimer mes données personnelles", - "settingsDetelePersonalDataDesc": "Cette action notifie l'administrateur que vous souhaitez supprimer vos données personnelles.", - "settingsDeleting": "Suppresion", - "settingsEdit": "Modifier", - "settingsEditAccount": "Modifier le compte", - "settingsEditPassword": "Modifier le mot de passe", - "settingsEmail": "Email", - "settingsEmptyField": "Ce champ ne peut pas être vide", - "settingsErrorProfilePicture": "Erreur lors de la modification de la photo de profil", - "settingsErrorSendingDemand": "Erreur lors de l'envoi de la demande", - "settingsEventsIcal": "Lien Ical des événements", - "settingsExpectingDate": "Date de naissance attendue", - "settingsFirstname": "Prénom", - "settingsFloor": "Étage", - "settingsHelp": "Aide", - "settingsIcalCopied": "Lien Ical copié !", - "settingsLanguage": "Langue", - "settingsLanguageFr": "Français", - "settingsLogs": "Logs", - "settingsModules": "Modules", - "settingsMyIcs": "Mon lien Ical", - "settingsName": "Nom", - "settingsNewPassword": "Nouveau mot de passe", - "settingsNickname": "Surnom", - "settingsNotifications": "Notifications", - "settingsOldPassword": "Ancien mot de passe", - "settingsPasswordChanged": "Mot de passe changé", - "settingsPasswordsNotMatch": "Les mots de passe ne correspondent pas", - "settingsPersonalData": "Données personnelles", - "settingsPersonalisation": "Personnalisation", - "settingsPhone": "Téléphone", - "settingsProfilePicture": "Photo de profil", - "settingsPromo": "Promotion", - "settingsRepportBug": "Signaler un bug", - "settingsSave": "Enregistrer", - "settingsSecurity": "Sécurité", - "settingsSendedDemand": "Demande envoyée", - "settingsSettings": "Paramètres", - "settingsTooHeavyProfilePicture": "L'image est trop lourde (max 4Mo)", - "settingsUpdatedProfile": "Profil modifié", - "settingsUpdatedProfilePicture": "Photo de profil modifiée", - "settingsUpdateNotification": "Mettre à jour les notifications", - "settingsUpdatingError": "Erreur lors de la modification du profil", - "settingsVersion": "Version", - "settingsPasswordStrength": "Force du mot de passe", - "settingsPasswordStrengthVeryWeak": "Très faible", - "settingsPasswordStrengthWeak": "Faible", - "settingsPasswordStrengthMedium": "Moyen", - "settingsPasswordStrengthStrong": "Fort", - "settingsPasswordStrengthVeryStrong": "Très fort", - "voteAdd": "Ajouter", - "voteAddMember": "Ajouter un membre", - "voteAddedPretendance": "Liste ajoutée", - "voteAddedSection": "Section ajoutée", - "voteAddingError": "Erreur lors de l'ajout", - "voteAddPretendance": "Ajouter une liste", - "voteAddSection": "Ajouter une section", - "voteAll": "Tous", - "voteAlreadyAddedMember": "Membre déjà ajouté", - "voteAlreadyVoted": "Vote enregistré", - "voteChooseList": "Choisir une liste", - "voteClear": "Réinitialiser", - "voteClearVotes": "Réinitialiser les votes", - "voteClosedVote": "Votes clos", - "voteCloseVote": "Fermer les votes", - "voteConfirmVote": "Confirmer le vote", - "voteCountVote": "Dépouiller les votes", - "voteDeletedAll": "Tout supprimé", - "voteDeletedPipo": "Listes pipos supprimées", - "voteDeletedSection": "Section supprimée", - "voteDeleteAll": "Supprimer tout", - "voteDeleteAllDescription": "Voulez-vous vraiment supprimer tout ?", - "voteDeletePipo": "Supprimer les listes pipos", - "voteDeletePipoDescription": "Voulez-vous vraiment supprimer les listes pipos ?", - "voteDeletePretendance": "Supprimer la liste", - "voteDeletePretendanceDesc": "Voulez-vous vraiment supprimer cette liste ?", - "voteDeleteSection": "Supprimer la section", - "voteDeleteSectionDescription": "Voulez-vous vraiment supprimer cette section ?", - "voteDeletingError": "Erreur lors de la suppression", - "voteDescription": "Description", - "voteEdit": "Modifier", - "voteEditedPretendance": "Liste modifiée", - "voteEditedSection": "Section modifiée", - "voteEditingError": "Erreur lors de la modification", - "voteErrorClosingVotes": "Erreur lors de la fermeture des votes", - "voteErrorCountingVotes": "Erreur lors du dépouillement des votes", - "voteErrorResetingVotes": "Erreur lors de la réinitialisation des votes", - "voteErrorOpeningVotes": "Erreur lors de l'ouverture des votes", - "voteIncorrectOrMissingFields": "Champs incorrects ou manquants", - "voteMembers": "Membres", - "voteName": "Nom", - "voteNoPretendanceList": "Aucune liste de prétendance", - "voteNoSection": "Aucune section", - "voteCanNotVote": "Vous ne pouvez pas voter", - "voteNoSectionList": "Aucune section", - "voteNotOpenedVote": "Vote non ouvert", - "voteOnGoingCount": "Dépouillement en cours", - "voteOpenVote": "Ouvrir les votes", - "votePipo": "Pipo", - "votePretendance": "Listes", - "votePretendanceDeleted": "Prétendance supprimée", - "votePretendanceNotDeleted": "Erreur lors de la suppression", - "voteProgram": "Programme", - "votePublish": "Publier", - "votePublishVoteDescription": "Voulez-vous vraiment publier les votes ?", - "voteResetedVotes": "Votes réinitialisés", - "voteResetVote": "Réinitialiser les votes", - "voteResetVoteDescription": "Que voulez-vous faire ?", - "voteRole": "Rôle", - "voteSectionDescription": "Description de la section", - "voteSection": "Section", - "voteSectionName": "Nom de la section", - "voteSeeMore": "Voir plus", - "voteSelected": "Sélectionné", - "voteShowVotes": "Voir les votes", - "voteVote": "Vote", - "voteVoteError": "Erreur lors de l'enregistrement du vote", - "voteVoteFor": "Voter pour ", - "voteVoteNotStarted": "Vote non ouvert", - "voteVoters": "Groupes votants", - "voteVoteSuccess": "Vote enregistré", - "voteVotes": "Voix", - "voteVotesClosed": "Votes clos", - "voteVotesCounted": "Votes dépouillés", - "voteVotesOpened": "Votes ouverts", - "voteWarning": "Attention", - "voteWarningMessage": "La sélection ne sera pas sauvegardée.\nVoulez-vous continuer ?", - "moduleAdvert": "Annonce", - "moduleAmap": "AMAP", - "moduleBooking": "Réservation", - "moduleCalendar": "Calendrier", - "moduleCentralisation": "Centralisation", - "moduleCinema": "Cinéma", - "moduleEvent": "Événement", - "moduleFlappyBird": "Flappy Bird", - "moduleLoan": "Prêt", - "modulePhonebook": "Annuaire", - "modulePurchases": "Achats", - "moduleRaffle": "Tombola", - "moduleRecommendation": "Bons plans", - "moduleSeedLibrary": "Grainothèque", - "moduleVote": "Vote", - "modulePh": "PH", - "moduleSettings": "Paramètres", - "moduleFeed": "Feed", - "moduleStyleGuide": "StyleGuide", - "moduleAdmin": "Adminitration", - "moduleOthers": "Autres", - "modulePayment": "Paiement", - "paiementTopUp" : "Recharge", - "paiementStoreManagement" : "Gestion des associations", - "paiementDeleteStore": "Supprimer l'association", - "paiementDeleteStoreDescription": "Voulez-vous vraiment supprimer cette association ?", - "paiementDeleteStoreError": "Impossible de supprimer l'association", - "paiementStoreDeleted": "Association supprimée", - "paiementAddThisDevice": "Ajouter cet appareil", - "paiementThisDevice": "(cet appareil)", - "paiementCancelled": "Annulé", - "paiementThe" : "Le", - "paiementOf": "de", - "paiementRefundedThe": "Remboursé le", - "paiementAt": "à", - "paiementPleaseAcceptTOS" : "Veuillez accepter les Conditions Générales d'Utilisation.", - "paiementAskDeviceActivation" : "Demande d'activation de l'appareil", - "paiementDeviceActivationReceived" : "La demande d'activation est prise en compte, veuilliez consulter votre boite mail pour finaliser la démarche", - "paiementRevokeDevice" : "Révoquer l'appareil ?", - "paiementRevokeDeviceDescription" : "Vous ne pourrez plus utiliser cet appareil pour les paiements", - "paiementDeviceRevoked" : "Appareil révoqué", - "paiementDeviceRevokingError" : "Erreur lors de la révocation de l'appareil", - "paiementPleaseAcceptPopup": "Veuillez autoriser les popups", - "paiementProceedSuccessfully": "Paiement effectué avec succès", - "paiementCancelledTransaction": "Paiement annulé", - "paiementPleaseEnterMinAmount": "Veuillez entrer un montant supérieur à 1", - "paiementMaxAmount": "Le montant maximum de votre portefeuille est de", - "paiementPayWithHA" : "Payer avec HelloAsso", - "paiementBalanceAfterTopUp": "Solde après recharge :", - "paiementPersonalBalance": "Solde personnel", - "paiementDevices": "Appareils", - "paiementPay": "Payer", - "paiementDeviceNotRegistered": "Appareil non enregistré", - "paiementDeviceNotRegisteredDescription": "Votre appareil n'est pas encore enregistré. \nPour l'enregistrer, veuillez vous rendre sur la page des appareils.", - "paiementAccessPage": "Accéder à la page", - "paiementDeviceNotActivated": "Appareil non activé", - "paiementDeviceNotActivatedDescription": "Votre appareil n'est pas encore activé. \nPour l'activer, veuillez vous rendre sur la page des appareils.", - "paiementReactivateRevokedDeviceDescription": "Votre appareil a été révoqué. \nPour le réactiver, veuillez vous rendre sur la page des appareils.", - "paiementDeviceRecoveryError": "Erreur lors de la récupération de l'appareil", - "paiementStats": "Stats", - "paimentTopUpAction": "Recharger", - "paiementGetBalanceError": "Erreur lors de la récupération du solde : ", - "paiementLastTransactions": "Dernières transactions", - "paiementGetTransactionsError": "Erreur lors de la récupération des transactions : ", - "paiementStoreBalance" : "Solde associatif", - "paiementScan": "Scanner", - "paiementManagement": "Gestion", - "paiementHistory": "Historique", - "paiementHandOver": "Passation", - "paiementStores": "Associations", - "paiementAdmin": "Administrateur", - "paiementSuccededTransaction": "Paiement réussi", - "paiementNewCGU" : "Nouvelles Conditions Générales d'Utilisation", - "paiementDecline": "Refuser", - "paiementAccept": "Accepter", - "paiementAmount": "Montant", - "paiementValidUntil": "Valide jusqu'à", - "paiementClose": "Fermer", - "paiementPleaseEnterValidAmount": "Veuillez entrer un montant valide", - "paiementPleaseAuthenticate": "Veuillez vous authentifier", - "paiementAthenticationRequired": "Authentification requise pour payer", - "paiementNoThanks": "Non merci", - "paiementAuthentificationFailed": "Échec de l'authentification", - "paiementPleaseAddDevice": "Veuillez ajouter cet appareil pour payer", - "paiementPayment": "Paiement", - "paiementBalanceAfterTransaction": "Solde après paiement : ", - "paiementCancel": "Annuler", - "paiementLimitedTo": "Limité à", - "paiementScanCode": "Scanner un code", - "paiementNext": "Suivant", - "paiementCancelTransaction": "Annuler la transaction", - "paiementTransactionCancelled": "Transaction annulée", - "paiementTransactionCancelledDescription": "Voulez-vous vraiment annuler la transaction de", - "paiementTransactionCancelledError": "Erreur lors de l'annulation de la transaction", - "paiementNoMembership": "Aucune adhésion", - "paiementNoMembershipDescription": "Ce produit n'est pas disponnible pour les non-adhérents. Confirmer l'encaissement ?", - "paiementQRCodeAlreadyUsed": "QR Code déjà utilisé", - "paiementCameraPermissionRequired": "Permission d'accès à la caméra requise", - "paiementCameraPerssionRequiredDescription": "Pour scanner un QR Code, vous devez autoriser l'accès à la caméra.", - "paiementSettings": "Paramètres", - "paiementReceived": "Reçu", - "paiementSpent": "Déboursé", - "paiementNoTrasactionForThisMonth": "Aucune transaction pour ce mois", - "paiementNoTransactinon": "Aucune transaction", - "paiementSellerRigths": "Droits du vendeur", - "paiementCanBank": "Peut encaisser", - "paiementCanSeeHistory": "Peut voir l'historique", - "paiementCanCancelTransaction": "Peut annuler des transactions", - "paiementCanManageSellers": "Peut gérer les vendeurs", - "paiementAddedSeller": "Vendeur ajouté", - "paiementAddingSellerError": "Erreur lors de l'ajout du vendeur", - "paiementBank": "Encaisser", - "paiementSeeHistory": "Voir l'historique", - "paiementCancelTransactions": "Annuler les transactions", - "paiementManageSellers": "Gérer les vendeurs", - "paiementStructureAdmin": "Administrateur de la structure", - "paiementRightsOf": "Droits de", - "paiementRightsUpdated": "Droits mis à jour", - "paiementRightsUpdateError": "Erreur lors de la mise à jour des droits", - "paiementDeleteSellerDescription": "Voulez-vous vraiment supprimer ce vendeur ?", - "paiementDeletedSeller": "Vendeur supprimé", - "paiementDeletingSellerError": "Erreur lors de la suppression du vendeur", - "paiementDeleteSeller": "Supprimer le vendeur", - "paiementAdd" : "Ajouter", - "paiementAddSeller": "Ajouter un vendeur", - "paiementSellerError": "Vous n'êtes pas vendeur de cette association", - "paiementSellersOf": "Les vendeurs de", - "paiementModify": "Modifier", - "paiementAStore": "une association", - "paiementStoreName": "Nom de l'association", - "paiementSuccessfullyAddedStore": "Association ajoutée avec succès", - "paiementSuccessfullyModifiedStore": "Association modifiée avec succès", - "paiementAddingStoreError": "Erreur lors de l'ajout de l'association", - "paiementModifyingStoreError": "Erreur lors de la modification de l'association", - "paiementRefund": "Remboursement", - "paiementDoneTransaction": "Transaction effectuée", - "paiementRefundAction": "Rembourser", - "paiementTotalDuringPeriod": "Total sur la période", - "paiementMean" : "Moyenne : ", - "paiementTransaction": "ransaction", - "paiementTransferStructure": "Transfert de structure", - "paiementYouAreTransferingStructureTo": "Vous êtes sur le point de transférer la structure à ", - "paiementTransferStructureDescription": "Le nouveau responsable aura accès à toutes les fonctionnalités de gestion de la structure. Vous allez recevoir un email pour valider ce transfert. Le lien ne sera actif que pendant 20 minutes. Cette action est irréversible. Êtes-vous sûr de vouloir continuer ?", - "paiementTransferStructureError": "Erreur lors du transfert de la structure", - "paiementTransferStructureSuccess": "Transfert de structure demandé avec succès", - "paiementNextAccountable": "Prochain responsable" -} \ No newline at end of file + "@@locale": "fr", + "adminAccountTypes": "Types de compte", + "adminAdd": "Ajouter", + "adminAddGroup": "Ajouter un groupe", + "adminAddMember": "Ajouter un membre", + "adminAddedGroup": "Groupe créé", + "adminAddedLoaner": "Préteur ajouté", + "adminAddedMember": "Membre ajouté", + "adminAddingError": "Erreur lors de l'ajout", + "adminAddingMember": "Ajout d'un membre", + "adminAddLoaningGroup": "Ajouter un groupe de prêt", + "adminAddSchool": "Ajouter une école", + "adminAddStructure": "Ajouter une structure", + "adminAddedSchool": "École créée", + "adminAddedStructure": "Structure ajoutée", + "adminEditedStructure": "Structure modifiée", + "adminAdministration": "Administration", + "adminAssociationMembership": "Adhésion", + "adminAssociationMembershipName": "Nom de l'adhésion", + "adminAssociationsMemberships": "Adhésions", + "adminClearFilters": "Effacer les filtres", + "adminCreateAssociationMembership": "Créer une adhésion", + "adminCreatedAssociationMembership": "Adhésion créée", + "adminCreationError": "Erreur lors de la création", + "adminDateError": "La date de début doit être avant la date de fin", + "adminDelete": "Supprimer", + "adminDeleteAssociationMembership": "Supprimer l'adhésion ?", + "adminDeletedAssociationMembership": "Adhésion supprimée", + "adminDeleteGroup": "Supprimer le groupe ?", + "adminDeletedGroup": "Groupe supprimé", + "adminDeleteSchool": "Supprimer l'école ?", + "adminDeletedSchool": "École supprimée", + "adminDeleting": "Suppression", + "adminDeletingError": "Erreur lors de la suppression", + "adminDescription": "Description", + "adminEclSchool": "Centrale Lyon", + "adminEdit": "Modifier", + "adminEditStructure": "Modifier la structure", + "adminEditMembership": "Modifier l'adhésion", + "adminEmptyDate": "Date vide", + "adminEmptyFieldError": "Le nom ne peut pas être vide", + "adminEmailRegex": "Email Regex", + "adminEmptyUser": "Utilisateur vide", + "adminEndDate": "Date de fin", + "adminEndDateMaximal": "Date de fin maximale", + "adminEndDateMinimal": "Date de fin minimale", + "adminError": "Erreur", + "adminFilters": "Filtres", + "adminGroup": "Groupe", + "adminGroups": "Groupes", + "adminLoaningGroup": "Groupe de prêt", + "adminLooking": "Recherche", + "adminManager": "Administrateur de la structure", + "adminMaximum": "Maximum", + "adminMembers": "Membres", + "adminMembershipAddingError": "Erreur lors de l'ajout (surement dû à une superposition de dates)", + "adminMemberships": "Adhésions", + "adminMembershipUpdatingError": "Erreur lors de la modification (surement dû à une superposition de dates)", + "adminMinimum": "Minimum", + "adminModifyModuleVisibility": "Visibilité des modules", + "adminMyEclPay": "MyECLPay", + "adminName": "Nom", + "adminNoManager": "Aucun manager n'est sélectionné", + "adminNoMember": "Aucun membre", + "adminNoMoreLoaner": "Aucun prêteur n'est disponible", + "adminNoSchool": "Sans école", + "adminRemoveGroupMember": "Supprimer le membre du groupe ?", + "adminResearch": "Recherche", + "adminSchools": "Écoles", + "adminStructures": "Structures", + "adminStartDate": "Date de début", + "adminStartDateMaximal": "Date de début maximale", + "adminStartDateMinimal": "Date de début minimale", + "adminUpdatedAssociationMembership": "Adhésion modifiée", + "adminUpdatedGroup": "Groupe modifié", + "adminUpdatedMembership": "Adhésion modifiée", + "adminUpdatingError": "Erreur lors de la modification", + "adminUser": "Utilisateur", + "adminValidateFilters": "Valider les filtres", + "adminVisibilities": "Visibilités", + "advertAdd": "Ajouter", + "advertAddedAdvert": "Annonce publiée", + "advertAddedAnnouncer": "Annonceur ajouté", + "advertAddingError": "Erreur lors de l'ajout", + "advertAdmin": "Admin", + "advertAdvert": "Annonce", + "advertChoosingAnnouncer": "Veuillez choisir un annonceur", + "advertChoosingPoster": "Veuillez choisir une image", + "advertContent": "Contenu", + "advertDeleteAdvert": "Supprimer l'annonce ?", + "advertDeleteAnnouncer": "Supprimer l'annonceur ?", + "advertDeleting": "Suppression", + "advertEdit": "Modifier", + "advertEditedAdvert": "Annonce modifiée", + "advertEditingError": "Erreur lors de la modification", + "advertGroupAdvert": "Groupe", + "advertIncorrectOrMissingFields": "Champs incorrects ou manquants", + "advertInvalidNumber": "Veuillez entrer un nombre", + "advertManagement": "Gestion", + "advertModifyAnnouncingGroup": "Modifier un groupe d'annonce", + "advertNoMoreAnnouncer": "Aucun annonceur n'est disponible", + "advertNoValue": "Veuillez entrer une valeur", + "advertPositiveNumber": "Veuillez entrer un nombre positif", + "advertRemovedAnnouncer": "Annonceur supprimé", + "advertRemovingError": "Erreur lors de la suppression", + "advertTags": "Tags", + "advertTitle": "Titre", + "advertMonthJan": "Janv", + "advertMonthFeb": "Févr.", + "advertMonthMar": "Mars", + "advertMonthApr": "Avr.", + "advertMonthMay": "Mai", + "advertMonthJun": "Juin", + "advertMonthJul": "Juill.", + "advertMonthAug": "Août", + "advertMonthSep": "Sept.", + "advertMonthOct": "Oct.", + "advertMonthNov": "Nov.", + "advertMonthDec": "Déc.", + "amapAccounts": "Comptes", + "amapAdd": "Ajouter", + "amapAddDelivery": "Ajouter une livraison", + "amapAddedCommand": "Commande ajoutée", + "amapAddedOrder": "Commande ajoutée", + "amapAddedProduct": "Produit ajouté", + "amapAddedUser": "Utilisateur ajouté", + "amapAddProduct": "Ajouter un produit", + "amapAddUser": "Ajouter un utilisateur", + "amapAddingACommand": "Ajouter une commande", + "amapAddingCommand": "Ajouter la commande", + "amapAddingError": "Erreur lors de l'ajout", + "amapAddingProduct": "Ajouter un produit", + "amapAddOrder": "Ajouter une commande", + "amapAdmin": "Admin", + "amapAlreadyExistCommand": "Il existe déjà une commande à cette date", + "amapAmap": "Amap", + "amapAmount": "Solde", + "amapArchive": "Archiver", + "amapArchiveDelivery": "Archiver", + "amapArchivingDelivery": "Archivage de la livraison", + "amapCategory": "Catégorie", + "amapCloseDelivery": "Verrouiller", + "amapCommandDate": "Date de la commande", + "amapCommandProducts": "Produits de la commande", + "amapConfirm": "Confirmer", + "amapContact": "Contacts associatifs ", + "amapCreateCategory": "Créer une catégorie", + "amapDelete": "Supprimer", + "amapDeleteDelivery": "Supprimer la livraison ?", + "amapDeleteDeliveryDescription": "Voulez-vous vraiment supprimer cette livraison ?", + "amapDeletedDelivery": "Livraison supprimée", + "amapDeletedOrder": "Commande supprimée", + "amapDeletedProduct": "Produit supprimé", + "amapDeleteProduct": "Supprimer le produit ?", + "amapDeleteProductDescription": "Voulez-vous vraiment supprimer ce produit ?", + "amapDeleting": "Suppression", + "amapDeletingDelivery": "Supprimer la livraison ?", + "amapDeletingError": "Erreur lors de la suppression", + "amapDeletingOrder": "Supprimer la commande ?", + "amapDeletingProduct": "Supprimer le produit ?", + "amapDeliver": "Livraison teminée ?", + "amapDeliveries": "Livraisons", + "amapDeliveringDelivery": "Toutes les commandes sont livrées ?", + "amapDelivery": "Livraison", + "amapDeliveryArchived": "Livraison archivée", + "amapDeliveryDate": "Date de livraison", + "amapDeliveryDelivered": "Livraison effectuée", + "amapDeliveryHistory": "Historique des livraisons", + "amapDeliveryList": "Liste des livraisons", + "amapDeliveryLocked": "Livraison verrouillée", + "amapDeliveryOn": "Livraison le", + "amapDeliveryOpened": "Livraison ouverte", + "amapDeliveryNotArchived": "Livraison non archivée", + "amapDeliveryNotLocked": "Livraison non verrouillée", + "amapDeliveryNotDelivered": "Livraison non effectuée", + "amapDeliveryNotOpened": "Livraison non ouverte", + "amapEditDelivery": "Modifier la livraison", + "amapEditedCommand": "Commande modifiée", + "amapEditingError": "Erreur lors de la modification", + "amapEditProduct": "Modifier le produit", + "amapEndingDelivery": "Fin de la livraison", + "amapError": "Erreur", + "amapErrorLink": "Erreur lors de l'ouverture du lien", + "amapErrorLoadingUser": "Erreur lors du chargement des utilisateurs", + "amapEvening": "Soir", + "amapExpectingNumber": "Veuillez entrer un nombre", + "amapFillField": "Veuillez remplir ce champ", + "amapHandlingAccount": "Gérer les comptes", + "amapLoading": "Chargement...", + "amapLoadingError": "Erreur lors du chargement", + "amapLock": "Verrouiller", + "amapLocked": "Verrouillée", + "amapLockedDelivery": "Livraison verrouillée", + "amapLockedOrder": "Commande verrouillée", + "amapLooking": "Rechercher", + "amapLockingDelivery": "Verrouiller la livraison ?", + "amapMidDay": "Midi", + "amapMyOrders": "Mes commandes", + "amapName": "Nom", + "amapNextStep": "Étape suivante", + "amapNoProduct": "Pas de produit", + "amapNoCurrentOrder": "Pas de commande en cours", + "amapNoMoney": "Pas assez d'argent", + "amapNoOpennedDelivery": "Pas de livraison ouverte", + "amapNoOrder": "Pas de commande", + "amapNoSelectedDelivery": "Pas de livraison sélectionnée", + "amapNotEnoughMoney": "Pas assez d'argent", + "amapNotPlannedDelivery": "Pas de livraison planifiée", + "amapOneOrder": "commande", + "amapOpenDelivery": "Ouvrir", + "amapOpened": "Ouverte", + "amapOpenningDelivery": "Ouvrir la livraison ?", + "amapOrder": "Commander", + "amapOrders": "Commandes", + "amapPickChooseCategory": "Veuillez entrer une valeur ou choisir une catégorie existante", + "amapPickDeliveryMoment": "Choisissez un moment de livraison", + "amapPresentation": "Présentation", + "amapPresentation1": "L'AMAP (association pour le maintien d'une agriculture paysanne) est un service proposé par l'association Planet&Co de l'ECL. Vous pouvez ainsi recevoir des produits (paniers de fruits et légumes, jus, confitures...) directement sur le campus !\n\nLes commandes doivent être passées avant le vendredi 21h et sont livrées sur le campus le mardi de 13h à 13h45 (ou de 18h15 à 18h30 si vous ne pouvez pas passer le midi) dans le hall du M16.\n\nVous ne pouvez commander que si votre solde le permet. Vous pouvez recharger votre solde via la collecte Lydia ou bien avec un chèque que vous pouvez nous transmettre lors des permanences.\n\nLien vers la collecte Lydia pour le rechargement : ", + "amapPresentation2": "\n\nN'hésitez pas à nous contacter en cas de problème !", + "amapPrice": "Prix", + "amapProduct": "produit", + "amapProducts": "Produits", + "amapProductInDelivery": "Produit dans une livraison non terminée", + "amapQuantity": "Quantité", + "amapRequiredDate": "La date est requise", + "amapSeeMore": "Voir plus", + "amapThe": "Le", + "amapUnlock": "Dévérouiller", + "amapUnlockedDelivery": "Livraison dévérouillée", + "amapUnlockingDelivery": "Dévérouiller la livraison ?", + "amapUpdate": "Modifier", + "amapUpdatedAmount": "Solde modifié", + "amapUpdatedOrder": "Commande modifiée", + "amapUpdatedProduct": "Produit modifié", + "amapUpdatingError": "Echec de la modification", + "amapUsersNotFound": "Aucun utilisateur trouvé", + "amapWaiting": "En attente", + "bookingAdd": "Ajouter", + "bookingAddBookingPage": "Demande", + "bookingAddRoom": "Ajouter une salle", + "bookingAddBooking": "Ajouter une réservation", + "bookingAddedBooking": "Demande ajoutée", + "bookingAddedRoom": "Salle ajoutée", + "bookingAddedManager": "Gestionnaire ajouté", + "bookingAddingError": "Erreur lors de l'ajout", + "bookingAddManager": "Ajouter un gestionnaire", + "bookingAdminPage": "Administrateur", + "bookingAllDay": "Toute la journée", + "bookingBookedFor": "Réservé pour", + "bookingBooking": "Réservation", + "bookingBookingCreated": "Réservation créée", + "bookingBookingDemand": "Demande de réservation", + "bookingBookingNote": "Note de la réservation", + "bookingBookingPage": "Réservation", + "bookingBookingReason": "Motif de la réservation", + "bookingBy": "par", + "bookingConfirm": "Confirmer", + "bookingConfirmation": "Confirmation", + "bookingConfirmBooking": "Confirmer la réservation ?", + "bookingConfirmed": "Validée", + "bookingDates": "Dates", + "bookingDecline": "Refuser", + "bookingDeclineBooking": "Refuser la réservation ?", + "bookingDeclined": "Refusée", + "bookingDelete": "Supprimer", + "bookingDeleting": "Suppression", + "bookingDeleteBooking": "Suppression", + "bookingDeleteBookingConfirmation": "Êtes-vous sûr de vouloir supprimer cette réservation ?", + "bookingDeletedBooking": "Réservation supprimée", + "bookingDeletedRoom": "Salle supprimée", + "bookingDeletedManager": "Gestionnaire supprimé", + "bookingDeleteRoomConfirmation": "Êtes-vous sûr de vouloir supprimer cette salle ?\n\nLa salle ne doit avoir aucune réservation en cours ou à venir pour être supprimée", + "bookingDeleteManagerConfirmation": "Êtes-vous sûr de vouloir supprimer ce gestionnaire ?\n\nLe gestionnaire ne doit être associé à aucune salle pour pouvoir être supprimé", + "bookingDeletingBooking": "Supprimer la réservation ?", + "bookingDeletingError": "Erreur lors de la suppression", + "bookingDeletingRoom": "Supprimer la salle ?", + "bookingEdit": "Modifier", + "bookingEditBooking": "Modifier une réservation", + "bookingEditionError": "Erreur lors de la modification", + "bookingEditedBooking": "Réservation modifiée", + "bookingEditedRoom": "Salle modifiée", + "bookingEditedManager": "Gestionnaire modifié", + "bookingEditManager": "Modifier ou supprimer un gestionnaire", + "bookingEditRoom": "Modifier ou supprimer une salle", + "bookingEndDate": "Date de fin", + "bookingEndHour": "Heure de fin", + "bookingEntity": "Pour qui ?", + "bookingError": "Erreur", + "bookingEventEvery": "Tous les", + "bookingHistoryPage": "Historique", + "bookingIncorrectOrMissingFields": "Champs incorrects ou manquants", + "bookingInterval": "Intervalle", + "bookingInvalidIntervalError": "Intervalle invalide", + "bookingInvalidDates": "Dates invalides", + "bookingInvalidRoom": "Salle invalide", + "bookingKeysRequested": "Clés demandées", + "bookingManagement": "Gestion", + "bookingManager": "Gestionnaire", + "bookingManagerName": "Nom du gestionnaire", + "bookingMultipleDay": "Plusieurs jours", + "bookingMyBookings": "Mes réservations", + "bookingNecessaryKey": "Clé nécessaire", + "bookingNext": "Suivant", + "bookingNo": "Non", + "bookingNoCurrentBooking": "Pas de réservation en cours", + "bookingNoDateError": "Veuillez choisir une date", + "bookingNoAppointmentInReccurence": "Aucun créneau existe avec ces paramètres de récurrence", + "bookingNoDaySelected": "Aucun jour sélectionné", + "bookingNoDescriptionError": "Veuillez entrer une description", + "bookingNoKeys": "Aucune clé", + "bookingNoNoteError": "Veuillez entrer une note", + "bookingNoPhoneRegistered": "Numéro non renseigné", + "bookingNoReasonError": "Veuillez entrer un motif", + "bookingNoRoomFoundError": "Aucune salle enregistrée", + "bookingNoRoomFound": "Aucune salle trouvée", + "bookingNote": "Note", + "bookingOther": "Autre", + "bookingPending": "En attente", + "bookingPrevious": "Précédent", + "bookingReason": "Motif", + "bookingRecurrence": "Récurrence", + "bookingRecurrenceDays": "Jours de récurrence", + "bookingRecurrenceEndDate": "Date de fin de récurrence", + "bookingRecurrent": "Récurrent", + "bookingRegisteredRooms": "Salles enregistrées", + "bookingRoom": "Salle", + "bookingRoomName": "Nom de la salle", + "bookingStartDate": "Date de début", + "bookingStartHour": "Heure de début", + "bookingWeeks": "Semaines", + "bookingYes": "Oui", + "bookingWeekDayMon": "Lundi", + "bookingWeekDayTue": "Mardi", + "bookingWeekDayWed": "Mercredi", + "bookingWeekDayThu": "Jeudi", + "bookingWeekDayFri": "Vendredi", + "bookingWeekDaySat": "Samedi", + "bookingWeekDaySun": "Dimanche", + "cinemaAdd": "Ajouter", + "cinemaAddedSession": "Séance ajoutée", + "cinemaAddingError": "Erreur lors de l'ajout", + "cinemaAddSession": "Ajouter une séance", + "cinemaCinema": "Cinéma", + "cinemaDeleteSession": "Supprimer la séance ?", + "cinemaDeleting": "Suppression", + "cinemaDuration": "Durée", + "cinemaEdit": "Modifier", + "cinemaEditedSession": "Séance modifiée", + "cinemaEditingError": "Erreur lors de la modification", + "cinemaEditSession": "Modifier la séance", + "cinemaEmptyUrl": "Veuillez entrer une URL", + "cinemaImportFromTMDB": "Importer depuis TMDB", + "cinemaIncomingSession": "A l'affiche", + "cinemaIncorrectOrMissingFields": "Champs incorrects ou manquants", + "cinemaInvalidUrl": "URL invalide", + "cinemaGenre": "Genre", + "cinemaName": "Nom", + "cinemaNoDateError": "Veuillez entrer une date", + "cinemaNoDuration": "Veuillez entrer une durée", + "cinemaNoOverview": "Aucun synopsis", + "cinemaNoPoster": "Aucune affiche", + "cinemaNoSession": "Aucune séance", + "cinemaOverview": "Synopsis", + "cinemaPosterUrl": "URL de l'affiche", + "cinemaSessionDate": "Jour de la séance", + "cinemaStartHour": "Heure de début", + "cinemaTagline": "Slogan", + "cinemaThe": "Le", + "drawerAdmin": "Administration", + "drawerAndroidAppLink": "https://play.google.com/store/apps/details?id=fr.myecl.titan", + "drawerCopied": "Copié !", + "drawerDownloadAppOnMobileDevice": "Ce site est la version Web de l'application MyECL. Nous vous invitons à télécharger l'application. N'utilisez ce site qu'en cas de problème avec l'application.\n", + "drawerIosAppLink": "https://apps.apple.com/fr/app/myecl/id6444443430", + "drawerLoginOut": "Voulez-vous vous déconnecter ?", + "drawerLogOut": "Déconnexion", + "drawerOr": " ou ", + "drawerSettings": "Paramètres", + "eventAdd": "Ajouter", + "eventAddEvent": "Ajouter un événement", + "eventAddedEvent": "Événement ajouté", + "eventAddingError": "Erreur lors de l'ajout", + "eventAllDay": "Toute la journée", + "eventConfirm": "Confirmer", + "eventConfirmEvent": "Confirmer l'événement ?", + "eventConfirmation": "Confirmation", + "eventConfirmed": "Confirmé", + "eventDates": "Dates", + "eventDecline": "Refuser", + "eventDeclineEvent": "Refuser l'événement ?", + "eventDeclined": "Refusé", + "eventDelete": "Supprimer", + "eventDeletedEvent": "Événement supprimé", + "eventDeleting": "Suppression", + "eventDeletingError": "Erreur lors de la suppression", + "eventDeletingEvent": "Supprimer l'événement ?", + "eventDescription": "Description", + "eventEdit": "Modifier", + "eventEditEvent": "Modifier un événement", + "eventEditedEvent": "Événement modifié", + "eventEditingError": "Erreur lors de la modification", + "eventEndDate": "Date de fin", + "eventEndHour": "Heure de fin", + "eventError": "Erreur", + "eventEventList": "Liste des événements", + "eventEventType": "Type d'événement", + "eventEvery": "Tous les", + "eventHistory": "Historique", + "eventIncorrectOrMissingFields": "Certains champs sont incorrects ou manquants", + "eventInterval": "Intervalle", + "eventInvalidDates": "La date de fin doit être après la date de début", + "eventInvalidIntervalError": "Veuillez entrer un intervalle valide", + "eventLocation": "Lieu", + "eventMyEvents": "Mes événements", + "eventName": "Nom", + "eventNext": "Suivant", + "eventNo": "Non", + "eventNoCurrentEvent": "Aucun événement en cours", + "eventNoDateError": "Veuillez entrer une date", + "eventNoDaySelected": "Aucun jour sélectionné", + "eventNoDescriptionError": "Veuillez entrer une description", + "eventNoEvent": "Aucun événement", + "eventNoNameError": "Veuillez entrer un nom", + "eventNoOrganizerError": "Veuillez entrer un organisateur", + "eventNoPlaceError": "Veuillez entrer un lieu", + "eventNoPhoneRegistered": "Numéro non renseigné", + "eventNoRuleError": "Veuillez entrer une règle de récurrence", + "eventOrganizer": "Organisateur", + "eventOther": "Autre", + "eventPending": "En attente", + "eventPrevious": "Précédent", + "eventRecurrence": "Récurrence", + "eventRecurrenceDays": "Jours de récurrence", + "eventRecurrenceEndDate": "Date de fin de la récurrence", + "eventRecurrenceRule": "Règle de récurrence", + "eventRoom": "Salle", + "eventStartDate": "Date de début", + "eventStartHour": "Heure de début", + "eventTitle": "Événements", + "eventYes": "Oui", + "eventEventEvery": "Toutes les", + "eventWeeks": "semaines", + "eventDayMon": "Lundi", + "eventDayTue": "Mardi", + "eventDayWed": "Mercredi", + "eventDayThu": "Jeudi", + "eventDayFri": "Vendredi", + "eventDaySat": "Samedi", + "eventDaySun": "Dimanche", + "homeCalendar": "Calendrier", + "homeEventOf": "Évènements du", + "homeIncomingEvents": "Évènements à venir", + "homeLastInfos": "Dernières annonces", + "homeNoEvents": "Aucun évènement", + "homeTranslateDayShortMon": "Lun", + "homeTranslateDayShortTue": "Mar", + "homeTranslateDayShortWed": "Mer", + "homeTranslateDayShortThu": "Jeu", + "homeTranslateDayShortFri": "Ven", + "homeTranslateDayShortSat": "Sam", + "homeTranslateDayShortSun": "Dim", + "loanAdd": "Ajouter", + "loanAddLoan": "Ajouter un prêt", + "loanAddObject": "Ajouter un objet", + "loanAddedLoan": "Prêt ajouté", + "loanAddedObject": "Objet ajouté", + "loanAddedRoom": "Salle ajoutée", + "loanAddingError": "Erreur lors de l'ajout", + "loanAdmin": "Administrateur", + "loanAvailable": "Disponible", + "loanAvailableMultiple": "Disponibles", + "loanBorrowed": "Emprunté", + "loanBorrowedMultiple": "Empruntés", + "loanAnd": "et", + "loanAssociation": "Association", + "loanAvailableItems": "Objets disponibles", + "loanBeginDate": "Date du début du prêt", + "loanBorrower": "Emprunteur", + "loanCaution": "Caution", + "loanCancel": "Annuler", + "loanConfirm": "Confirmer", + "loanConfirmation": "Confirmation", + "loanDates": "Dates", + "loanDays": "Jours", + "loanDelay": "Délai de la prolongation", + "loanDelete": "Supprimer", + "loanDeletingLoan": "Supprimer le prêt ?", + "loanDeletedItem": "Objet supprimé", + "loanDeletedLoan": "Prêt supprimé", + "loanDeleting": "Suppression", + "loanDeletingError": "Erreur lors de la suppression", + "loanDeletingItem": "Supprimer l'objet ?", + "loanDuration": "Durée", + "loanEdit": "Modifier", + "loanEditItem": "Modifier l'objet", + "loanEditLoan": "Modifier le prêt", + "loanEditedRoom": "Salle modifiée", + "loanEndDate": "Date de fin du prêt", + "loanEnded": "Terminé", + "loanEnterDate": "Veuillez entrer une date", + "loanExtendedLoan": "Prêt prolongé", + "loanExtendingError": "Erreur lors de la prolongation", + "loanHistory": "Historique", + "loanIncorrectOrMissingFields": "Des champs sont manquants ou incorrects", + "loanInvalidNumber": "Veuillez entrer un nombre", + "loanInvalidDates": "Les dates ne sont pas valides", + "loanItem": "Objet", + "loanItems": "Objets", + "loanItemHandling": "Gestion des objets", + "loanItemSelected": "objet sélectionné", + "loanItemsSelected": "objets sélectionnés", + "loanLendingDuration": "Durée possible du prêt", + "loanLoan": "Prêt", + "loanLoanHandling": "Gestion des prêts", + "loanLooking": "Rechercher", + "loanName": "Nom", + "loanNext": "Suivant", + "loanNo": "Non", + "loanNoAssociationsFounded": "Aucune association trouvée", + "loanNoAvailableItems": "Aucun objet disponible", + "loanNoBorrower": "Aucun emprunteur", + "loanNoItems": "Aucun objet", + "loanNoItemSelected": "Aucun objet sélectionné", + "loanNoLoan": "Aucun prêt", + "loanNoReturnedDate": "Pas de date de retour", + "loanQuantity": "Quantité", + "loanNone": "Aucun", + "loanNote": "Note", + "loanNoValue": "Veuillez entrer une valeur", + "loanOnGoing": "En cours", + "loanOnGoingLoan": "Prêt en cours", + "loanOthers": "autres", + "loanPaidCaution": "Caution payée", + "loanPositiveNumber": "Veuillez entrer un nombre positif", + "loanPrevious": "Précédent", + "loanReturned": "Rendu", + "loanReturnedLoan": "Prêt rendu", + "loanReturningError": "Erreur lors du retour", + "loanReturningLoan": "Retour", + "loanReturnLoan": "Rendre le prêt ?", + "loanReturnLoanDescription": "Voulez-vous rendre ce prêt ?", + "loanToReturn": "A rendre", + "loanUnavailable": "Indisponible", + "loanUpdate": "Modifier", + "loanUpdatedItem": "Objet modifié", + "loanUpdatedLoan": "Prêt modifié", + "loanUpdatingError": "Erreur lors de la modification", + "loanYes": "Oui", + "loginAccountActivated": "Compte activé", + "loginAccountNotActivated": "Compte non activé", + "loginActivationCode": "Code d'activation", + "loginBirthday": "Date de naissance", + "loginCanBeEmpty": "Ce champ peut être vide", + "loginConfirmPassword": "Confirmer le mot de passe", + "loginCreate": "Créer", + "loginCreateAccount": "Créer un compte", + "loginCreateAccountTitle": "Créer un\ncompte", + "loginEmail": "Email", + "loginEmailEmpty": "Veuillez entrer une adresse mail", + "loginEmailInvalid": "Veuillez entrer une adresse mail de centrale.\nSi vous n'en possédez pas, veuillez contacter Éclair", + "loginEmptyFieldError": "Ce champ ne peut pas être vide", + "loginEndActivation": "Finaliser l'activation", + "loginEndResetPassword": "Finaliser la \nréinitialisation", + "loginErrorResetPassword": "Erreur lors de la réinitialisation", + "loginExpectingDate": "Une date est attendue", + "loginFillAllFields": "Veuillez remplir tous les champs", + "loginFirstname": "Prénom", + "loginFloor": "Étage", + "loginForgetPassword": "Mot de passe\noublié", + "loginForgotPassword": "Mot de passe oublié ?", + "loginInvalidToken": "Code d'activation invalide", + "loginLoginFailed": "Échec de la connexion", + "loginMailSendingError": "Erreur lors de la création du compte", + "loginMustBeIntError": "Ce champ doit être un entier", + "loginName": "Nom", + "loginNewPassword": "Nouveau mot de passe", + "loginPassword": "Mot de passe", + "loginPasswordLengthError": "Le mot de passe doit faire au moins 6 caractères", + "loginPasswordUppercaseError": "Le mot de passe doit contenir au moins une majuscule", + "loginPasswordLowercaseError": "Le mot de passe doit contenir au moins une minucule", + "loginPasswordNumberError": "Le mot de passe doit contenir au moins un chiffre", + "loginPasswordSpecialCaracterError": "Le mot de passe doit contenir au moins un caractère spécial", + "loginPasswordMustMatch": "Les mots de passe doivent correspondre", + "loginPasswordStrengthVeryWeak": "Très faible", + "loginPasswordStrengthWeak": "Faible", + "loginPasswordStrengthMedium": "Moyen", + "loginPasswordStrengthStrong": "Fort", + "loginPasswordStrengthVeryStrong": "Très fort", + "loginPhone": "Téléphone", + "loginPromo": "Promo entrante (ex : 2023)", + "loginSendedMail": "Mail de confirmation envoyé", + "loginSendedResetMail": "Mail de réinitialisation envoyé", + "loginSignIn": "Se connecter", + "loginRegister": "S'inscrire", + "loginRecievedMail": "J'ai reçu le mail", + "loginRecover": "Réinitialiser", + "loginResetedPassword": "Mot de passe réinitialisé", + "loginResetPasswordTitle": "Réinitialiser\nle mot de \npasse", + "loginNickname": "Surnom", + "loginWelcomeBack": "Bienvenue", + "loginAppName": "MyECL", + "othersCheckInternetConnection": "Veuillez vérifier votre connexion internet", + "othersRetry": "Réessayer", + "othersTooOldVersion": "Votre version de l'application est trop ancienne.\n\nVeuillez mettre à jour l'application.", + "othersUnableToConnectToServer": "Impossible de se connecter au serveur", + "othersVersion": "Version", + "othersNoModule": "Aucun module disponible, veuillez réessayer ultérieurement 😢😢", + "othersAdmin": "Admin", + "othersError": "Une erreur est survenue", + "othersNoValue": "Veuillez entrer une valeur", + "othersInvalidNumber": "Veuillez entrer un nombre", + "othersNoDateError": "Veuillez entrer une date", + "othersImageSizeTooBig": "La taille de l'image ne doit pas dépasser 4 Mio", + "othersImageError": "Erreur lors de l'ajout de l'image", + "phAddNewJournal": "Ajouter un nouveau journal", + "phNameField": "Nom : ", + "phDateField": "Date : ", + "phDelete": "Voulez-vous vraiment supprimer ce journal ?", + "phIrreversibleAction": "Cette action est irréversible", + "phToHeavyFile": "Fichier trop volumineux", + "phAddPdfFile": "Ajouter un fichier PDF", + "phEditPdfFile": "Modifier le fichier PDF", + "phPhName": "Nom du PH", + "phDate": "Date", + "phAdded": "Ajouté", + "phEdited": "Modifié", + "phAddingFileError": "Erreur d'ajout", + "phMissingInformatonsOrPdf": "Informations manquantes ou fichier PDF manquant", + "phAdd": "Ajouter", + "phEdit": "Modifier", + "phSeePreviousJournal": "Voir les anciens journaux", + "phNoJournalInDatabase": "Pas encore de PH dans la base de donnée", + "phSuccesDowloading": "Téléchargé avec succès", + "phonebookActiveMandate": "Mandat actif :", + "phonebookAdd": "Ajouter", + "phonebookAddAssociation": "Ajouter une association", + "phonebookAddAssociationGroupement": "Ajouter un groupement d'association", + "phonebookAddedAssociation": "Association ajoutée", + "phonebookAddedMember": "Membre ajouté", + "phonebookAddingError": "Erreur lors de l'ajout", + "phonebookAddMember": "Ajouter un membre", + "phonebookAddRole": "Ajouter un rôle", + "phonebookAdmin": "Admin", + "phonebookAdminPage": "Page Administrateur", + "phonebookAll": "Toutes", + "phonebookApparentName": "Nom public du rôle :", + "phonebookAssociation": "Association :", + "phonebookAssociationDetail": "Détail de l'association :", + "phonebookAssociationKind": "Type d'association :", + "phonebookAssociationPure": "Association", + "phonebookAssociationPureSearch": " Association", + "phonebookAssociations": "Associations :", + "phonebookCancel": "Annuler", + "phonebookChangeMandate": "Passer au mandat ", + "phonebookChangeMandateConfirm": "Êtes-vous sûr de vouloir changer tout le mandat ?\nCette action est irréversible !", + "phonebookConfirm": "Confirmer", + "phonebookCopied": "Copié dans le presse-papier", + "phonebookDeactivateAssociation": "Êtes-vous sûr de vouloir désactiver cette association ?\nCette action est irréversible !", + "phonebookDeactivatedAssociation": "Association désactivée", + "phonebookDeactivatedAssociationWarning": "Attention, cette association est désactivée, vous ne pouvez pas la modifier", + "phonebookDeactivating": "Désactiver l'association ?", + "phonebookDeactivatingError": "Erreur lors de la désactivation", + "phonebookDetail": "Détail :", + "phonebookDeleteAssociation": "Supprimer l'association ?\nCela va effacer tout l'historique de l'association", + "phonebookDeletedAssociation": "Association supprimée", + "phonebookDeletedMember": "Membre supprimé", + "phonebookDeleting": "Suppression", + "phonebookDeletingError": "Erreur lors de la suppression", + "phonebookDescription": "Description", + "phonebookEdit": "Modifier", + "phonebookEditAssociationGroupement": "Modifier le groupement d'association", + "phonebookEditMembership": "Modifier le rôle", + "phonebookEmail": "Email :", + "phonebookEmailCopied": "Email copié dans le presse-papier", + "phonebookEmptyApparentName": "Veuillez entrer un nom de role", + "phonebookEmptyFieldError": "Un champ n'est pas rempli", + "phonebookEmptyKindError": "Veuillez choisir un type d'association", + "phonebookEmptyMember": "Aucun membre sélectionné", + "phonebookErrorAssociationLoading": "Erreur lors du chargement de l'association", + "phonebookErrorAssociationNameEmpty": "Veuillez entrer un nom d'association", + "phonebookErrorAssociationPicture": "Erreur lors de la modification de la photo d'association", + "phonebookErrorKindsLoading": "Erreur lors du chargement des types d'association", + "phonebookErrorLoadAssociationList": "Erreur lors du chargement de la liste des associations", + "phonebookErrorLoadAssociationMember": "Erreur lors du chargement des membres de l'association", + "phonebookErrorLoadAssociationPicture": "Erreur lors du chargement de la photo d'association", + "phonebookErrorLoadProfilePicture": "Erreur", + "phonebookErrorRoleTagsLoading": "Erreur lors du chargement des tags de rôle", + "phonebookExistingMembership": "Ce membre est déjà dans le mandat actuel", + "phonebookFirstname": "Prénom :", + "phonebookGroups": "Groupes associés :", + "phonebookMandateChangingError": "Erreur lors du changement de mandat", + "phonebookMember": "Membre", + "phonebookMemberReordered": "Membre réordonné", + "phonebookMembers": "Membres", + "phonebookMembershipAssociationError": "Veuillez choisir une association", + "phonebookMembershipRole": "Rôle :", + "phonebookMembershipRoleError": "Veuillez choisir un rôle", + "phonebookName": "Nom :", + "phonebookNameCopied": "Nom et prénom copié dans le presse-papier", + "phonebookNamePure": "Nom", + "phonebookNewMandate": "Nouveau mandat", + "phonebookNewMandateConfirmed": "Mandat changé", + "phonebookNickname": "Surnom :", + "phonebookNicknameCopied": "Surnom copié dans le presse-papier", + "phonebookNoAssociationFound": "Aucune association trouvée", + "phonebookNoMember": "Aucun membre", + "phonebookNoMemberRole": "Aucun role trouvé", + "phonebookPhone": "Téléphone :", + "phonebookPhonebook": "Annuaire", + "phonebookPhonebookSearch": "Rechercher", + "phonebookPhonebookSearchAssociation": "Association", + "phonebookPhonebookSearchField": "Rechercher :", + "phonebookPhonebookSearchName": "Nom/Prénom/Surnom", + "phonebookPhonebookSearchRole": "Poste", + "phonebookPresidentRoleTag": "Prez'", + "phonebookPromoNotGiven": "Promo non renseignée", + "phonebookPromotion": "Promotion :", + "phonebookReorderingError": "Erreur lors du réordonnement", + "phonebookResearch": "Rechercher", + "phonebookRolePure": "Rôle", + "phonebookTooHeavyAssociationPicture": "L'image est trop lourde (max 4Mo)", + "phonebookUpdateGroups": "Mettre à jour les groupes", + "phonebookUpdatedAssociation": "Association modifiée", + "phonebookUpdatedAssociationPicture": "La photo d'association a été changée", + "phonebookUpdatedGroups": "Groupes mis à jour", + "phonebookUpdatedMember": "Membre modifié", + "phonebookUpdatingError": "Erreur lors de la modification", + "phonebookValidation": "Valider", + "purchasesPurchases": "Achats", + "purchasesResearch": "Rechercher", + "purchasesNoPurchasesFound": "Aucun achat trouvé", + "purchasesNoTickets": "Aucun ticket", + "purchasesTicketsError": "Erreur lors du chargement des tickets", + "purchasesPurchasesError": "Erreur lors du chargement des achats", + "purchasesNoPurchases": "Aucun achat", + "purchasesTimes": "fois", + "purchasesAlreadyUsed": "Déjà utilisé", + "purchasesNotPaid": "Non validé", + "purchasesPleaseSelectProduct": "Veuillez sélectionner un produit", + "purchasesProducts": "Produits", + "purchasesCancel": "Annuler", + "purchasesValidate": "Valider", + "purchasesLeftScan": "Scans restants", + "purchasesTag": "Tag", + "purchasesHistory": "Historique", + "purchasesPleaseSelectSeller": "Veuillez sélectionner un vendeur", + "purchasesNoTagGiven": "Attention, aucun tag n'a été entré", + "purchasesTickets": "Tickets", + "purchasesNoScannableProducts": "Aucun produit scannable", + "purchasesLoading": "En attente de scan", + "purchasesScan": "Scanner", + "raffleRaffle": "Tombola", + "rafflePrize": "Lot", + "rafflePrizes": "Lots", + "raffleActualRaffles": "Tombola en cours", + "rafflePastRaffles": "Tombola passés", + "raffleYourTickets": "Tous vos tickets", + "raffleCreateMenu": "Menu de Création", + "raffleNextRaffles": "Prochaines tombolas", + "raffleNoTicket": "Vous n'avez pas de ticket", + "raffleSeeRaffleDetail": "Voir lots/tickets", + "raffleActualPrize": "Lots actuels", + "raffleMajorPrize": "Lot Majeurs", + "raffleTakeTickets": "Prendre vos tickets", + "raffleNoTicketBuyable": "Vous ne pouvez pas achetez de billets pour l'instant", + "raffleNoCurrentPrize": "Il n'y a aucun lots actuellement", + "raffleModifTombola": "Vous pouvez modifiez vos tombolas ou en créer de nouvelles, toute décision doit ensuite être prise par les admins", + "raffleCreateYourRaffle": "Votre menu de création de tombolas", + "rafflePossiblePrice": "Prix possible", + "raffleInformation": "Information et Statistiques", + "raffleAccounts": "Comptes", + "raffleAdd": "Ajouter", + "raffleUpdatedAmount": "Montant mis à jour", + "raffleUpdatingError": "Erreur lors de la mise à jour", + "raffleDeletedPrize": "Lot supprimé", + "raffleDeletingError": "Erreur lors de la suppression", + "raffleQuantity": "Quantité", + "raffleClose": "Fermer", + "raffleOpen": "Ouvrir", + "raffleAddTypeTicketSimple": "Ajouter", + "raffleAddingError": "Erreur lors de l'ajout", + "raffleEditTypeTicketSimple": "Modifier", + "raffleFillField": "Le champ ne peut pas être vide", + "raffleWaiting": "Chargement", + "raffleEditingError": "Erreur lors de la modification", + "raffleAddedTicket": "Ticket ajouté", + "raffleEditedTicket": "Ticket modifié", + "raffleAlreadyExistTicket": "Le ticket existe déjà", + "raffleNumberExpected": "Un entier est attendu", + "raffleDeletedTicket": "Ticket supprimé", + "raffleAddPrize": "Ajouter", + "raffleEditPrize": "Modifier", + "raffleOpenRaffle": "Ouvrir la tombola", + "raffleCloseRaffle": "Fermer la tombola", + "raffleOpenRaffleDescription": "Vous allez ouvrir la tombola, les utilisateurs pourront acheter des tickets. Vous ne pourrez plus modifier la tombola. Êtes-vous sûr de vouloir continuer ?", + "raffleCloseRaffleDescription": "Vous allez fermer la tombola, les utilisateurs ne pourront plus acheter de tickets. Êtes-vous sûr de vouloir continuer ?", + "raffleNoCurrentRaffle": "Il n'y a aucune tombola en cours", + "raffleBoughtTicket": "Ticket acheté", + "raffleDrawingError": "Erreur lors du tirage", + "raffleInvalidPrice": "Le prix doit être supérieur à 0", + "raffleMustBePositive": "Le nombre doit être strictement positif", + "raffleDraw": "Tirer", + "raffleDrawn": "Tiré", + "raffleError": "Erreur", + "raffleGathered": "Récolté", + "raffleTickets": "Tickets", + "raffleTicket": "ticket", + "raffleWinner": "Gagnant", + "raffleNoPrize": "Aucun lot", + "raffleDeletePrize": "Supprimer le lot", + "raffleDeletePrizeDescription": "Vous allez supprimer le lot, êtes-vous sûr de vouloir continuer ?", + "raffleDrawing": "Tirage", + "raffleDrawingDescription": "Tirer le gagnant du lot ?", + "raffleDeleteTicket": "Supprimer le ticket", + "raffleDeleteTicketDescription": "Vous allez supprimer le ticket, êtes-vous sûr de vouloir continuer ?", + "raffleWinningTickets": "Tickets gagnants", + "raffleNoWinningTicketYet": "Les tickets gagnants seront affichés ici", + "raffleName": "Nom", + "raffleDescription": "Description", + "raffleBuyThisTicket": "Acheter ce ticket", + "raffleLockedRaffle": "Tombola verrouillée", + "raffleUnavailableRaffle": "Tombola indisponible", + "raffleNotEnoughMoney": "Vous n'avez pas assez d'argent", + "raffleWinnable": "gagnable", + "raffleNoDescription": "Aucune description", + "raffleAmount": "Solde", + "raffleLoading": "Chargement", + "raffleTicketNumber": "Nombre de ticket", + "rafflePrice": "Prix", + "raffleEditRaffle": "Modifier la tombola", + "raffleEdit": "Modifier", + "raffleAddPackTicket": "Ajouter un pack de ticket", + "recommendationRecommendation": "Bons plans", + "recommendationTitle": "Titre", + "recommendationLogo": "Logo", + "recommendationCode": "Code", + "recommendationSummary": "Court résumé", + "recommendationDescription": "Description", + "recommendationAdd": "Ajouter", + "recommendationEdit": "Modifier", + "recommendationDelete": "Supprimer", + "recommendationAddImage": "Veuillez ajouter une image", + "recommendationAddedRecommendation": "Bon plan ajouté", + "recommendationEditedRecommendation": "Bon plan modifié", + "recommendationDeleteRecommendationConfirmation": "Êtes-vous sûr de vouloir supprimer ce bon plan ?", + "recommendationDeleteRecommendation": "Suppresion", + "recommendationDeletingRecommendationError": "Erreur lors de la suppression", + "recommendationDeletedRecommendation": "Bon plan supprimé", + "recommendationIncorrectOrMissingFields": "Champs incorrects ou manquants", + "recommendationEditingError": "Échec de la modification", + "recommendationAddingError": "Échec de l'ajout", + "recommendationCopiedCode": "Code de réduction copié", + "seedLibraryAdd": "Ajouter", + "seedLibraryAddedPlant": "Plante ajoutée", + "seedLibraryAddedSpecies": "Espèce ajoutée", + "seedLibraryAddingError": "Erreur lors de l'ajout", + "seedLibraryAddPlant": "Déposer une plante", + "seedLibraryAddSpecies": "Ajouter une espèce", + "seedLibraryAll": "Toutes", + "seedLibraryAncestor": "Ancêtre", + "seedLibraryAround": "environ", + "seedLibraryAutumn": "Automne", + "seedLibraryBorrowedPlant": "Plante empruntée", + "seedLibraryBorrowingDate": "Date d'emprunt :", + "seedLibraryBorrowPlant": "Emprunter la plante", + "seedLibraryCard": "Carte", + "seedLibraryChoosingAncestor": "Veuillez choisir un ancêtre", + "seedLibraryChoosingSpecies": "Veuillez choisir une espèce", + "seedLibraryChoosingSpeciesOrAncestor": "Veuillez choisir une espèce ou un ancêtre", + "seedLibraryContact": "Contact :", + "seedLibraryDays": "jours", + "seedLibraryDeadMsg": "Voulez-vous déclarer la plante morte ?", + "seedLibraryDeadPlant": "Plante morte", + "seedLibraryDeathDate": "Date de mort", + "seedLibraryDeletedSpecies": "Espèce supprimée", + "seedLibraryDeleteSpecies": "Supprimer l'espèce ?", + "seedLibraryDeleting": "Suppression", + "seedLibraryDeletingError": "Erreur lors de la suppression", + "seedLibraryDepositNotAvailable": "Le dépôt de plantes n'est pas possible sans emprunter une plante au préalable", + "seedLibraryDescription": "Description", + "seedLibraryDifficulty": "Difficulté :", + "seedLibraryEdit": "Modifier", + "seedLibraryEditedPlant": "Plante modifiée", + "seedLibraryEditInformation": "Modifier les informations", + "seedLibraryEditingError": "Erreur lors de la modification", + "seedLibraryEditSpecies": "Modifier l'espèce", + "seedLibraryEmptyDifficultyError": "Veuillez choisir une difficulté", + "seedLibraryEmptyFieldError": "Veuillez remplir tous les champs", + "seedLibraryEmptyTypeError": "Veuillez choisir un type de plante", + "seedLibraryEndMonth": "Mois de fin :", + "seedLibraryFacebookUrl": "Lien Facebook", + "seedLibraryFilters": "Filtres", + "seedLibraryForum": "Oskour maman j'ai tué ma plante - Forum d'aide", + "seedLibraryForumUrl": "Lien Forum", + "seedLibraryHelpSheets": "Fiches sur les plantes", + "seedLibraryInformation": "Informations :", + "seedLibraryMaturationTime": "Temps de maturation", + "seedLibraryMonthJan": "Janvier", + "seedLibraryMonthFeb": "Février", + "seedLibraryMonthMar": "Mars", + "seedLibraryMonthApr": "Avril", + "seedLibraryMonthMay": "Mai", + "seedLibraryMonthJun": "Juin", + "seedLibraryMonthJul": "Juillet", + "seedLibraryMonthAug": "Août", + "seedLibraryMonthSep": "Septembre", + "seedLibraryMonthOct": "Octobre", + "seedLibraryMonthNov": "Novembre", + "seedLibraryMonthDec": "Décembre", + "seedLibraryMyPlants": "Mes plantes", + "seedLibraryName": "Nom", + "seedLibraryNbSeedsRecommended": "Nombre de graines recommandées", + "seedLibraryNbSeedsRecommendedError": "Veuillez entrer un nombre de graines recommandé supérieur à 0", + "seedLibraryNoDateError": "Veuillez entrer une date", + "seedLibraryNoFilteredPlants": "Aucune plante ne correspond à votre recherche. Essayez d'autres filtres.", + "seedLibraryNoMorePlant": "Aucune plante n'est disponible", + "seedLibraryNoPersonalPlants": "Vous n'avez pas encore de plantes dans votre grainothèque. Vous pouvez en ajouter en allant dans les stocks.", + "seedLibraryNoSpecies": "Aucune espèce trouvée", + "seedLibraryNoStockPlants": "Aucune plante disponible dans le stock", + "seedLibraryNotes": "Notes", + "seedLibraryOk": "OK", + "seedLibraryPlantationPeriod": "Période de plantation :", + "seedLibraryPlantationType": "Type de plantation :", + "seedLibraryPlantDetail": "Détail de la plante", + "seedLibraryPlantingDate": "Date de plantation", + "seedLibraryPlantingNow": "Je la plante maintenant", + "seedLibraryPrefix": "Préfixe", + "seedLibraryPrefixError": "Prefixe déjà utilisé", + "seedLibraryPrefixLengthError": "Le préfixe doit faire 3 caractères", + "seedLibraryPropagationMethod": "Méthode de propagation :", + "seedLibraryReference": "Référence :", + "seedLibraryRemovedPlant": "Plante supprimée", + "seedLibraryRemovingError": "Erreur lors de la suppression", + "seedLibraryResearch": "Recherche", + "seedLibrarySaveChanges": "Sauvegarder les modifications", + "seedLibrarySeason": "Saison :", + "seedLibrarySeed": "Graine", + "seedLibrarySeeds": "graines", + "seedLibrarySeedDeposit": "Dépôt de plantes", + "seedLibrarySeedLibrary": "Grainothèque", + "seedLibrarySeedQuantitySimple": "Quantité de graines", + "seedLibrarySeedQuantity": "Quantité de graines :", + "seedLibraryShowDeadPlants": "Afficher les plantes mortes", + "seedLibrarySpecies": "Espèce :", + "seedLibrarySpeciesHelp": "Aide sur l'espèce", + "seedLibrarySpeciesPlural": "Espèces", + "seedLibrarySpeciesSimple": "Espèce", + "seedLibrarySpeciesType": "Type d'espèce :", + "seedLibrarySpring": "Printemps", + "seedLibraryStartMonth": "Mois de début :", + "seedLibraryStock": "Stock disponible", + "seedLibrarySummer": "Été", + "seedLibraryStocks": "Stocks", + "seedLibraryTimeUntilMaturation": "Temps avant maturation :", + "seedLibraryType": "Type :", + "seedLibraryUnableToOpen": "Impossible d'ouvrir le lien", + "seedLibraryUpdate": "Modifier", + "seedLibraryUpdatedInformation": "Informations modifiées", + "seedLibraryUpdatedSpecies": "Espèce modifiée", + "seedLibraryUpdatedPlant": "Plante modifiée", + "seedLibraryUpdatingError": "Erreur lors de la modification", + "seedLibraryWinter": "Hiver", + "seedLibraryWriteReference": "Veuillez écrire la référence suivante : ", + "settingsAccount": "Compte", + "settingsAddProfilePicture": "Ajouter une photo", + "settingsAdmin": "Administrateur", + "settingsAskHelp": "Demander de l'aide", + "settingsAssociation": "Association", + "settingsBirthday": "Date de naissance", + "settingsBugs": "Bugs", + "settingsChangePassword": "Changer de mot de passe", + "settingsChangingPassword": "Voulez-vous vraiment changer votre mot de passe ?", + "settingsConfirmPassword": "Confirmer le mot de passe", + "settingsCopied": "Copié !", + "settingsDarkMode": "Mode sombre", + "settingsDarkModeOff": "Désactivé", + "settingsDeleteLogs": "Supprimer les logs ?", + "settingsDeleteNotificationLogs": "Supprimer les logs des notifications ?", + "settingsDetelePersonalData": "Supprimer mes données personnelles", + "settingsDetelePersonalDataDesc": "Cette action notifie l'administrateur que vous souhaitez supprimer vos données personnelles.", + "settingsDeleting": "Suppresion", + "settingsEdit": "Modifier", + "settingsEditAccount": "Modifier le compte", + "settingsEditPassword": "Modifier le mot de passe", + "settingsEmail": "Email", + "settingsEmptyField": "Ce champ ne peut pas être vide", + "settingsErrorProfilePicture": "Erreur lors de la modification de la photo de profil", + "settingsErrorSendingDemand": "Erreur lors de l'envoi de la demande", + "settingsEventsIcal": "Lien Ical des événements", + "settingsExpectingDate": "Date de naissance attendue", + "settingsFirstname": "Prénom", + "settingsFloor": "Étage", + "settingsHelp": "Aide", + "settingsIcalCopied": "Lien Ical copié !", + "settingsLanguage": "Langue", + "settingsLanguageFr": "Français", + "settingsLogs": "Logs", + "settingsModules": "Modules", + "settingsMyIcs": "Mon lien Ical", + "settingsName": "Nom", + "settingsNewPassword": "Nouveau mot de passe", + "settingsNickname": "Surnom", + "settingsNotifications": "Notifications", + "settingsOldPassword": "Ancien mot de passe", + "settingsPasswordChanged": "Mot de passe changé", + "settingsPasswordsNotMatch": "Les mots de passe ne correspondent pas", + "settingsPersonalData": "Données personnelles", + "settingsPersonalisation": "Personnalisation", + "settingsPhone": "Téléphone", + "settingsProfilePicture": "Photo de profil", + "settingsPromo": "Promotion", + "settingsRepportBug": "Signaler un bug", + "settingsSave": "Enregistrer", + "settingsSecurity": "Sécurité", + "settingsSendedDemand": "Demande envoyée", + "settingsSettings": "Paramètres", + "settingsTooHeavyProfilePicture": "L'image est trop lourde (max 4Mo)", + "settingsUpdatedProfile": "Profil modifié", + "settingsUpdatedProfilePicture": "Photo de profil modifiée", + "settingsUpdateNotification": "Mettre à jour les notifications", + "settingsUpdatingError": "Erreur lors de la modification du profil", + "settingsVersion": "Version", + "settingsPasswordStrength": "Force du mot de passe", + "settingsPasswordStrengthVeryWeak": "Très faible", + "settingsPasswordStrengthWeak": "Faible", + "settingsPasswordStrengthMedium": "Moyen", + "settingsPasswordStrengthStrong": "Fort", + "settingsPasswordStrengthVeryStrong": "Très fort", + "voteAdd": "Ajouter", + "voteAddMember": "Ajouter un membre", + "voteAddedPretendance": "Liste ajoutée", + "voteAddedSection": "Section ajoutée", + "voteAddingError": "Erreur lors de l'ajout", + "voteAddPretendance": "Ajouter une liste", + "voteAddSection": "Ajouter une section", + "voteAll": "Tous", + "voteAlreadyAddedMember": "Membre déjà ajouté", + "voteAlreadyVoted": "Vote enregistré", + "voteChooseList": "Choisir une liste", + "voteClear": "Réinitialiser", + "voteClearVotes": "Réinitialiser les votes", + "voteClosedVote": "Votes clos", + "voteCloseVote": "Fermer les votes", + "voteConfirmVote": "Confirmer le vote", + "voteCountVote": "Dépouiller les votes", + "voteDeletedAll": "Tout supprimé", + "voteDeletedPipo": "Listes pipos supprimées", + "voteDeletedSection": "Section supprimée", + "voteDeleteAll": "Supprimer tout", + "voteDeleteAllDescription": "Voulez-vous vraiment supprimer tout ?", + "voteDeletePipo": "Supprimer les listes pipos", + "voteDeletePipoDescription": "Voulez-vous vraiment supprimer les listes pipos ?", + "voteDeletePretendance": "Supprimer la liste", + "voteDeletePretendanceDesc": "Voulez-vous vraiment supprimer cette liste ?", + "voteDeleteSection": "Supprimer la section", + "voteDeleteSectionDescription": "Voulez-vous vraiment supprimer cette section ?", + "voteDeletingError": "Erreur lors de la suppression", + "voteDescription": "Description", + "voteEdit": "Modifier", + "voteEditedPretendance": "Liste modifiée", + "voteEditedSection": "Section modifiée", + "voteEditingError": "Erreur lors de la modification", + "voteErrorClosingVotes": "Erreur lors de la fermeture des votes", + "voteErrorCountingVotes": "Erreur lors du dépouillement des votes", + "voteErrorResetingVotes": "Erreur lors de la réinitialisation des votes", + "voteErrorOpeningVotes": "Erreur lors de l'ouverture des votes", + "voteIncorrectOrMissingFields": "Champs incorrects ou manquants", + "voteMembers": "Membres", + "voteName": "Nom", + "voteNoPretendanceList": "Aucune liste de prétendance", + "voteNoSection": "Aucune section", + "voteCanNotVote": "Vous ne pouvez pas voter", + "voteNoSectionList": "Aucune section", + "voteNotOpenedVote": "Vote non ouvert", + "voteOnGoingCount": "Dépouillement en cours", + "voteOpenVote": "Ouvrir les votes", + "votePipo": "Pipo", + "votePretendance": "Listes", + "votePretendanceDeleted": "Prétendance supprimée", + "votePretendanceNotDeleted": "Erreur lors de la suppression", + "voteProgram": "Programme", + "votePublish": "Publier", + "votePublishVoteDescription": "Voulez-vous vraiment publier les votes ?", + "voteResetedVotes": "Votes réinitialisés", + "voteResetVote": "Réinitialiser les votes", + "voteResetVoteDescription": "Que voulez-vous faire ?", + "voteRole": "Rôle", + "voteSectionDescription": "Description de la section", + "voteSection": "Section", + "voteSectionName": "Nom de la section", + "voteSeeMore": "Voir plus", + "voteSelected": "Sélectionné", + "voteShowVotes": "Voir les votes", + "voteVote": "Vote", + "voteVoteError": "Erreur lors de l'enregistrement du vote", + "voteVoteFor": "Voter pour ", + "voteVoteNotStarted": "Vote non ouvert", + "voteVoters": "Groupes votants", + "voteVoteSuccess": "Vote enregistré", + "voteVotes": "Voix", + "voteVotesClosed": "Votes clos", + "voteVotesCounted": "Votes dépouillés", + "voteVotesOpened": "Votes ouverts", + "voteWarning": "Attention", + "voteWarningMessage": "La sélection ne sera pas sauvegardée.\nVoulez-vous continuer ?", + "moduleAdvert": "Annonce", + "moduleAmap": "AMAP", + "moduleBooking": "Réservation", + "moduleCalendar": "Calendrier", + "moduleCentralisation": "Centralisation", + "moduleCinema": "Cinéma", + "moduleEvent": "Événement", + "moduleFlappyBird": "Flappy Bird", + "moduleLoan": "Prêt", + "modulePhonebook": "Annuaire", + "modulePurchases": "Achats", + "moduleRaffle": "Tombola", + "moduleRecommendation": "Bons plans", + "moduleSeedLibrary": "Grainothèque", + "moduleVote": "Vote", + "modulePh": "PH", + "moduleSettings": "Paramètres", + "moduleFeed": "Feed", + "moduleStyleGuide": "StyleGuide", + "moduleAdmin": "Adminitration", + "moduleOthers": "Autres", + "modulePayment": "Paiement", + "paiementTopUp": "Recharge", + "paiementStoreManagement": "Gestion des associations", + "paiementDeleteStore": "Supprimer l'association", + "paiementDeleteStoreDescription": "Voulez-vous vraiment supprimer cette association ?", + "paiementDeleteStoreError": "Impossible de supprimer l'association", + "paiementStoreDeleted": "Association supprimée", + "paiementAddThisDevice": "Ajouter cet appareil", + "paiementThisDevice": "(cet appareil)", + "paiementCancelled": "Annulé", + "paiementThe": "Le", + "paiementOf": "de", + "paiementRefundedThe": "Remboursé le", + "paiementAt": "à", + "paiementPleaseAcceptTOS": "Veuillez accepter les Conditions Générales d'Utilisation.", + "paiementAskDeviceActivation": "Demande d'activation de l'appareil", + "paiementDeviceActivationReceived": "La demande d'activation est prise en compte, veuilliez consulter votre boite mail pour finaliser la démarche", + "paiementRevokeDevice": "Révoquer l'appareil ?", + "paiementRevokeDeviceDescription": "Vous ne pourrez plus utiliser cet appareil pour les paiements", + "paiementDeviceRevoked": "Appareil révoqué", + "paiementDeviceRevokingError": "Erreur lors de la révocation de l'appareil", + "paiementPleaseAcceptPopup": "Veuillez autoriser les popups", + "paiementProceedSuccessfully": "Paiement effectué avec succès", + "paiementCancelledTransaction": "Paiement annulé", + "paiementPleaseEnterMinAmount": "Veuillez entrer un montant supérieur à 1", + "paiementMaxAmount": "Le montant maximum de votre portefeuille est de", + "paiementPayWithHA": "Payer avec HelloAsso", + "paiementBalanceAfterTopUp": "Solde après recharge :", + "paiementPersonalBalance": "Solde personnel", + "paiementDevices": "Appareils", + "paiementPay": "Payer", + "paiementDeviceNotRegistered": "Appareil non enregistré", + "paiementDeviceNotRegisteredDescription": "Votre appareil n'est pas encore enregistré. \nPour l'enregistrer, veuillez vous rendre sur la page des appareils.", + "paiementAccessPage": "Accéder à la page", + "paiementDeviceNotActivated": "Appareil non activé", + "paiementDeviceNotActivatedDescription": "Votre appareil n'est pas encore activé. \nPour l'activer, veuillez vous rendre sur la page des appareils.", + "paiementReactivateRevokedDeviceDescription": "Votre appareil a été révoqué. \nPour le réactiver, veuillez vous rendre sur la page des appareils.", + "paiementDeviceRecoveryError": "Erreur lors de la récupération de l'appareil", + "paiementStats": "Stats", + "paimentTopUpAction": "Recharger", + "paiementGetBalanceError": "Erreur lors de la récupération du solde : ", + "paiementLastTransactions": "Dernières transactions", + "paiementGetTransactionsError": "Erreur lors de la récupération des transactions : ", + "paiementStoreBalance": "Solde associatif", + "paiementScan": "Scanner", + "paiementManagement": "Gestion", + "paiementHistory": "Historique", + "paiementHandOver": "Passation", + "paiementStores": "Associations", + "paiementAdmin": "Administrateur", + "paiementSuccededTransaction": "Paiement réussi", + "paiementNewCGU": "Nouvelles Conditions Générales d'Utilisation", + "paiementDecline": "Refuser", + "paiementAccept": "Accepter", + "paiementAmount": "Montant", + "paiementValidUntil": "Valide jusqu'à", + "paiementClose": "Fermer", + "paiementPleaseEnterValidAmount": "Veuillez entrer un montant valide", + "paiementPleaseAuthenticate": "Veuillez vous authentifier", + "paiementAthenticationRequired": "Authentification requise pour payer", + "paiementNoThanks": "Non merci", + "paiementAuthentificationFailed": "Échec de l'authentification", + "paiementPleaseAddDevice": "Veuillez ajouter cet appareil pour payer", + "paiementPayment": "Paiement", + "paiementBalanceAfterTransaction": "Solde après paiement : ", + "paiementCancel": "Annuler", + "paiementLimitedTo": "Limité à", + "paiementScanCode": "Scanner un code", + "paiementNext": "Suivant", + "paiementCancelTransaction": "Annuler la transaction", + "paiementTransactionCancelled": "Transaction annulée", + "paiementTransactionCancelledDescription": "Voulez-vous vraiment annuler la transaction de", + "paiementTransactionCancelledError": "Erreur lors de l'annulation de la transaction", + "paiementNoMembership": "Aucune adhésion", + "paiementNoMembershipDescription": "Ce produit n'est pas disponnible pour les non-adhérents. Confirmer l'encaissement ?", + "paiementQRCodeAlreadyUsed": "QR Code déjà utilisé", + "paiementCameraPermissionRequired": "Permission d'accès à la caméra requise", + "paiementCameraPerssionRequiredDescription": "Pour scanner un QR Code, vous devez autoriser l'accès à la caméra.", + "paiementSettings": "Paramètres", + "paiementReceived": "Reçu", + "paiementSpent": "Déboursé", + "paiementNoTrasactionForThisMonth": "Aucune transaction pour ce mois", + "paiementNoTransactinon": "Aucune transaction", + "paiementSellerRigths": "Droits du vendeur", + "paiementCanBank": "Peut encaisser", + "paiementCanSeeHistory": "Peut voir l'historique", + "paiementCanCancelTransaction": "Peut annuler des transactions", + "paiementCanManageSellers": "Peut gérer les vendeurs", + "paiementAddedSeller": "Vendeur ajouté", + "paiementAddingSellerError": "Erreur lors de l'ajout du vendeur", + "paiementBank": "Encaisser", + "paiementSeeHistory": "Voir l'historique", + "paiementCancelTransactions": "Annuler les transactions", + "paiementManageSellers": "Gérer les vendeurs", + "paiementStructureAdmin": "Administrateur de la structure", + "paiementRightsOf": "Droits de", + "paiementRightsUpdated": "Droits mis à jour", + "paiementRightsUpdateError": "Erreur lors de la mise à jour des droits", + "paiementDeleteSellerDescription": "Voulez-vous vraiment supprimer ce vendeur ?", + "paiementDeletedSeller": "Vendeur supprimé", + "paiementDeletingSellerError": "Erreur lors de la suppression du vendeur", + "paiementDeleteSeller": "Supprimer le vendeur", + "paiementAdd": "Ajouter", + "paiementAddSeller": "Ajouter un vendeur", + "paiementSellerError": "Vous n'êtes pas vendeur de cette association", + "paiementSellersOf": "Les vendeurs de", + "paiementModify": "Modifier", + "paiementAStore": "une association", + "paiementStoreName": "Nom de l'association", + "paiementSuccessfullyAddedStore": "Association ajoutée avec succès", + "paiementSuccessfullyModifiedStore": "Association modifiée avec succès", + "paiementAddingStoreError": "Erreur lors de l'ajout de l'association", + "paiementModifyingStoreError": "Erreur lors de la modification de l'association", + "paiementRefund": "Remboursement", + "paiementDoneTransaction": "Transaction effectuée", + "paiementRefundAction": "Rembourser", + "paiementTotalDuringPeriod": "Total sur la période", + "paiementMean": "Moyenne : ", + "paiementTransaction": "ransaction", + "paiementTransferStructure": "Transfert de structure", + "paiementYouAreTransferingStructureTo": "Vous êtes sur le point de transférer la structure à ", + "paiementTransferStructureDescription": "Le nouveau responsable aura accès à toutes les fonctionnalités de gestion de la structure. Vous allez recevoir un email pour valider ce transfert. Le lien ne sera actif que pendant 20 minutes. Cette action est irréversible. Êtes-vous sûr de vouloir continuer ?", + "paiementTransferStructureError": "Erreur lors du transfert de la structure", + "paiementTransferStructureSuccess": "Transfert de structure demandé avec succès", + "paiementNextAccountable": "Prochain responsable" +} diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 8e3e7698bb..95225c22d0 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -3902,6 +3902,12 @@ abstract class AppLocalizations { /// **'Ajouter une association'** String get phonebookAddAssociation; + /// No description provided for @phonebookAddAssociationGroupement. + /// + /// In fr, this message translates to: + /// **'Ajouter un groupement d\'association'** + String get phonebookAddAssociationGroupement; + /// No description provided for @phonebookAddedAssociation. /// /// In fr, this message translates to: @@ -4010,6 +4016,12 @@ abstract class AppLocalizations { /// **'Êtes-vous sûr de vouloir changer tout le mandat ?\nCette action est irréversible !'** String get phonebookChangeMandateConfirm; + /// No description provided for @phonebookConfirm. + /// + /// In fr, this message translates to: + /// **'Confirmer'** + String get phonebookConfirm; + /// No description provided for @phonebookCopied. /// /// In fr, this message translates to: @@ -4094,6 +4106,12 @@ abstract class AppLocalizations { /// **'Modifier'** String get phonebookEdit; + /// No description provided for @phonebookEditAssociationGroupement. + /// + /// In fr, this message translates to: + /// **'Modifier le groupement d\'association'** + String get phonebookEditAssociationGroupement; + /// No description provided for @phonebookEditMembership. /// /// In fr, this message translates to: diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 2694e05cd2..23d042d9e3 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -1935,6 +1935,10 @@ class AppLocalizationsEn extends AppLocalizations { @override String get phonebookAddAssociation => 'Add an association'; + @override + String get phonebookAddAssociationGroupement => + 'Add an association groupement'; + @override String get phonebookAddedAssociation => 'Association added'; @@ -1990,6 +1994,9 @@ class AppLocalizationsEn extends AppLocalizations { String get phonebookChangeMandateConfirm => 'Are you sure you want to change the entire mandate?\nThis action is irreversible!'; + @override + String get phonebookConfirm => 'Confirm'; + @override String get phonebookCopied => 'Copied to clipboard'; @@ -2035,6 +2042,10 @@ class AppLocalizationsEn extends AppLocalizations { @override String get phonebookEdit => 'Edit'; + @override + String get phonebookEditAssociationGroupement => + 'Edit association groupement'; + @override String get phonebookEditMembership => 'Edit role'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index bf86111c65..044794ed38 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -1944,6 +1944,10 @@ class AppLocalizationsFr extends AppLocalizations { @override String get phonebookAddAssociation => 'Ajouter une association'; + @override + String get phonebookAddAssociationGroupement => + 'Ajouter un groupement d\'association'; + @override String get phonebookAddedAssociation => 'Association ajoutée'; @@ -1999,6 +2003,9 @@ class AppLocalizationsFr extends AppLocalizations { String get phonebookChangeMandateConfirm => 'Êtes-vous sûr de vouloir changer tout le mandat ?\nCette action est irréversible !'; + @override + String get phonebookConfirm => 'Confirmer'; + @override String get phonebookCopied => 'Copié dans le presse-papier'; @@ -2044,6 +2051,10 @@ class AppLocalizationsFr extends AppLocalizations { @override String get phonebookEdit => 'Modifier'; + @override + String get phonebookEditAssociationGroupement => + 'Modifier le groupement d\'association'; + @override String get phonebookEditMembership => 'Modifier le rôle'; diff --git a/lib/phonebook/ui/components/association_research_bar.dart b/lib/phonebook/ui/components/association_research_bar.dart index 12d739613f..8e96693f40 100644 --- a/lib/phonebook/ui/components/association_research_bar.dart +++ b/lib/phonebook/ui/components/association_research_bar.dart @@ -41,7 +41,9 @@ class AssociationResearchBar extends HookConsumerWidget { ], child: Padding( padding: EdgeInsetsGeometry.symmetric(vertical: 10), - child: AssociationGroupementBar(), + child: AssociationGroupementBar( + scrollDirection: Axis.vertical, + ), ), ), ), diff --git a/lib/phonebook/ui/components/groupement_bar.dart b/lib/phonebook/ui/components/groupement_bar.dart index 994cba35e8..39bda73718 100644 --- a/lib/phonebook/ui/components/groupement_bar.dart +++ b/lib/phonebook/ui/components/groupement_bar.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -12,9 +14,14 @@ import 'package:titan/tools/ui/styleguide/button.dart'; import 'package:titan/tools/ui/styleguide/item_chip.dart'; class AssociationGroupementBar extends HookConsumerWidget { - AssociationGroupementBar({super.key, this.editable = false}); + AssociationGroupementBar({ + super.key, + this.editable = false, + this.scrollDirection = Axis.horizontal, + }); final dataKey = GlobalKey(); final bool editable; + final Axis scrollDirection; @override Widget build(BuildContext context, WidgetRef ref) { @@ -95,9 +102,11 @@ class AssociationGroupementBar extends HookConsumerWidget { value: associationGroupementList, builder: (context, associationGroupements) => SizedBox( width: MediaQuery.of(context).size.width, - height: 40, + height: scrollDirection == Axis.horizontal + ? 40 + : min(associationGroupements.length * 70, 140), child: ListView.builder( - scrollDirection: Axis.horizontal, + scrollDirection: scrollDirection, itemCount: editable ? associationGroupements.length + 1 : associationGroupements.length, @@ -105,6 +114,7 @@ class AssociationGroupementBar extends HookConsumerWidget { if (editable && index == 0) { return ItemChip( key: Key("add"), + scrollDirection: scrollDirection, onTap: () { associationGroupementNotifier.resetAssociationGroupement(); QR.to( @@ -121,6 +131,7 @@ class AssociationGroupementBar extends HookConsumerWidget { final selected = associationGroupement.id == item.id; return ItemChip( key: selected ? dataKey : null, + scrollDirection: scrollDirection, selected: selected, onTap: () { associationGroupement.id != item.id diff --git a/lib/phonebook/ui/pages/add_edit_groupement_page/groupement_add_edit_page.dart b/lib/phonebook/ui/pages/add_edit_groupement_page/groupement_add_edit_page.dart index 995ceb624b..bb2eb4ec76 100644 --- a/lib/phonebook/ui/pages/add_edit_groupement_page/groupement_add_edit_page.dart +++ b/lib/phonebook/ui/pages/add_edit_groupement_page/groupement_add_edit_page.dart @@ -1,11 +1,12 @@ +// ignore_for_file: use_build_context_synchronously + import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/tools/constants.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/phonebook/class/association_groupement.dart'; import 'package:titan/phonebook/providers/association_groupement_list_provider.dart'; import 'package:titan/phonebook/providers/association_groupement_provider.dart'; -import 'package:titan/phonebook/tools/constants.dart'; import 'package:titan/phonebook/ui/phonebook.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; @@ -39,8 +40,12 @@ class AssociationGroupementAddEditPage extends HookConsumerWidget { alignment: Alignment.centerLeft, child: Text( associationGroupement.id.isNotEmpty - ? PhonebookTextConstants.editAssociationGroupement - : PhonebookTextConstants.addAssociationGroupement, + ? AppLocalizations.of( + context, + )!.phonebookEditAssociationGroupement + : AppLocalizations.of( + context, + )!.phonebookAddAssociationGroupement, style: TextStyle( fontSize: 20, fontWeight: FontWeight.w700, @@ -51,15 +56,17 @@ class AssociationGroupementAddEditPage extends HookConsumerWidget { const SizedBox(height: 30), TextEntry( controller: name, - label: AdminTextConstants.name, + label: AppLocalizations.of(context)!.phonebookName, canBeEmpty: false, ), const SizedBox(height: 50), Button( - text: AdminTextConstants.add, + text: AppLocalizations.of(context)!.phonebookAdd, onPressed: () async { if (name.text.isEmpty) { - showSnackBarWithContext(AdminTextConstants.emptyFieldError); + showSnackBarWithContext( + AppLocalizations.of(context)!.phonebookEmptyFieldError, + ); return; } await tokenExpireWrapper(ref, () async { @@ -73,11 +80,13 @@ class AssociationGroupementAddEditPage extends HookConsumerWidget { ); if (value) { showSnackBarWithContext( - PhonebookTextConstants.addedAssociation, + AppLocalizations.of(context)!.phonebookAddedAssociation, ); QR.back(); } else { - showSnackBarWithContext(AdminTextConstants.updatingError); + showSnackBarWithContext( + AppLocalizations.of(context)!.phonebookUpdatingError, + ); } return; } @@ -87,11 +96,13 @@ class AssociationGroupementAddEditPage extends HookConsumerWidget { ); if (value) { showSnackBarWithContext( - PhonebookTextConstants.addedAssociation, + AppLocalizations.of(context)!.phonebookAddedAssociation, ); QR.back(); } else { - showSnackBarWithContext(AdminTextConstants.addingError); + showSnackBarWithContext( + AppLocalizations.of(context)!.phonebookAddingError, + ); } }); }, diff --git a/lib/phonebook/ui/pages/admin_page/admin_page.dart b/lib/phonebook/ui/pages/admin_page/admin_page.dart index 7eb4bf54dd..ed7b6cc399 100644 --- a/lib/phonebook/ui/pages/admin_page/admin_page.dart +++ b/lib/phonebook/ui/pages/admin_page/admin_page.dart @@ -1,13 +1,13 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/admin/providers/is_admin_provider.dart'; import 'package:titan/phonebook/providers/association_filtered_list_provider.dart'; import 'package:titan/phonebook/providers/association_groupement_list_provider.dart'; import 'package:titan/phonebook/providers/association_list_provider.dart'; import 'package:titan/phonebook/providers/phonebook_admin_provider.dart'; import 'package:titan/phonebook/providers/roles_tags_provider.dart'; import 'package:titan/phonebook/router.dart'; -import 'package:titan/phonebook/tools/constants.dart'; import 'package:titan/phonebook/ui/components/association_research_bar.dart'; import 'package:titan/phonebook/ui/phonebook.dart'; import 'package:titan/phonebook/ui/pages/admin_page/editable_association_card.dart'; @@ -31,6 +31,7 @@ class AdminPage extends HookConsumerWidget { final associationFilteredList = ref.watch(associationFilteredListProvider); final roleNotifier = ref.watch(rolesTagsProvider.notifier); final isPhonebookAdmin = ref.watch(isPhonebookAdminProvider); + final isAdmin = ref.watch(isAdminProvider); return PhonebookTemplate( child: Refresher( @@ -67,9 +68,11 @@ class AdminPage extends HookConsumerWidget { ), SizedBox(height: 5), if (associations.isEmpty) - const Center( + Center( child: Text( - PhonebookTextConstants.noAssociationFound, + AppLocalizations.of( + context, + )!.phonebookNoAssociationFound, ), ) else @@ -81,6 +84,7 @@ class AdminPage extends HookConsumerWidget { groupement.id == association.groupementId, ), isPhonebookAdmin: isPhonebookAdmin, + isAdmin: isAdmin, ), ), ], diff --git a/lib/phonebook/ui/pages/admin_page/editable_association_card.dart b/lib/phonebook/ui/pages/admin_page/editable_association_card.dart index 7819188b49..22818c327e 100644 --- a/lib/phonebook/ui/pages/admin_page/editable_association_card.dart +++ b/lib/phonebook/ui/pages/admin_page/editable_association_card.dart @@ -7,17 +7,20 @@ import 'package:titan/phonebook/providers/association_picture_provider.dart'; import 'package:titan/phonebook/providers/associations_picture_map_provider.dart'; import 'package:titan/phonebook/ui/pages/admin_page/edition_modal.dart'; import 'package:titan/tools/ui/builders/auto_loader_child.dart'; +import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; import 'package:titan/tools/ui/styleguide/list_item.dart'; class EditableAssociationCard extends HookConsumerWidget { final Association association; final AssociationGroupement groupement; final bool isPhonebookAdmin; + final bool isAdmin; const EditableAssociationCard({ super.key, required this.association, required this.groupement, required this.isPhonebookAdmin, + required this.isAdmin, }); @override @@ -45,17 +48,15 @@ class EditableAssociationCard extends HookConsumerWidget { subtitle: groupement.name, icon: CircleAvatar(child: Image(image: data.first.image)), onTap: () { - showModalBottomSheet( + showCustomBottomModal( + ref: ref, context: context, - isScrollControlled: true, - backgroundColor: Colors.transparent, - builder: (context) { - return AssociationEditionModal( - association: association, - groupement: groupement, - isPhonebookAdmin: isPhonebookAdmin, - ); - }, + modal: AssociationEditionModal( + association: association, + groupement: groupement, + isPhonebookAdmin: isPhonebookAdmin, + isAdmin: isAdmin, + ), ); }, ), diff --git a/lib/phonebook/ui/pages/admin_page/edition_modal.dart b/lib/phonebook/ui/pages/admin_page/edition_modal.dart index 83d5a89834..496dee2d4f 100644 --- a/lib/phonebook/ui/pages/admin_page/edition_modal.dart +++ b/lib/phonebook/ui/pages/admin_page/edition_modal.dart @@ -2,13 +2,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/phonebook/class/association.dart'; import 'package:titan/phonebook/class/association_groupement.dart'; import 'package:titan/phonebook/providers/association_groupement_provider.dart'; import 'package:titan/phonebook/providers/association_list_provider.dart'; import 'package:titan/phonebook/providers/association_provider.dart'; import 'package:titan/phonebook/router.dart'; -import 'package:titan/phonebook/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; import 'package:titan/tools/ui/styleguide/button.dart'; @@ -17,11 +17,13 @@ class AssociationEditionModal extends HookConsumerWidget { final Association association; final AssociationGroupement groupement; final bool isPhonebookAdmin; + final bool isAdmin; const AssociationEditionModal({ super.key, required this.association, required this.groupement, required this.isPhonebookAdmin, + required this.isAdmin, }); @override @@ -58,21 +60,23 @@ class AssociationEditionModal extends HookConsumerWidget { ); }, ), - SizedBox(height: 5), - Button( - text: "Gérer les groupes", - onPressed: () { - associationGroupementsNotifier.setAssociationGroupement( - groupement, - ); - associationNotifier.setAssociation(association); - QR.to( - PhonebookRouter.root + - PhonebookRouter.admin + - PhonebookRouter.editAssociation, - ); - }, - ), + if (isAdmin) ...[ + SizedBox(height: 5), + Button( + text: "Gérer les groupes", + onPressed: () { + associationGroupementsNotifier.setAssociationGroupement( + groupement, + ); + associationNotifier.setAssociation(association); + QR.to( + PhonebookRouter.root + + PhonebookRouter.admin + + PhonebookRouter.editAssociation, + ); + }, + ), + ], SizedBox(height: 5), Button( text: "Gérer les membres", @@ -114,7 +118,7 @@ class AssociationEditionModal extends HookConsumerWidget { ] : [ Button.danger( - text: PhonebookTextConstants.confirm, + text: AppLocalizations.of(context)!.phonebookConfirm, onPressed: association.deactivated ? () async { final result = await associationListNotifier @@ -122,12 +126,16 @@ class AssociationEditionModal extends HookConsumerWidget { if (result) { displayToastWithContext( TypeMsg.msg, - PhonebookTextConstants.deletedAssociation, + AppLocalizations.of( + context, + )!.phonebookDeletedAssociation, ); } else { displayToastWithContext( TypeMsg.error, - PhonebookTextConstants.deletingError, + AppLocalizations.of( + context, + )!.phonebookDeletingError, ); } } @@ -137,19 +145,23 @@ class AssociationEditionModal extends HookConsumerWidget { if (result) { displayToastWithContext( TypeMsg.msg, - PhonebookTextConstants.deactivatedAssociation, + AppLocalizations.of( + context, + )!.phonebookDeactivatedAssociation, ); } else { displayToastWithContext( TypeMsg.error, - PhonebookTextConstants.deactivatingError, + AppLocalizations.of( + context, + )!.phonebookDeactivatingError, ); } }, ), SizedBox(height: 5), Button( - text: PhonebookTextConstants.cancel, + text: AppLocalizations.of(context)!.phonebookCancel, onPressed: () => isSuppresion.value = false, ), ], diff --git a/lib/phonebook/ui/pages/association_add_edit_page/association_add_edit_page.dart b/lib/phonebook/ui/pages/association_add_edit_page/association_add_edit_page.dart index a4e3d2d520..95a8559087 100644 --- a/lib/phonebook/ui/pages/association_add_edit_page/association_add_edit_page.dart +++ b/lib/phonebook/ui/pages/association_add_edit_page/association_add_edit_page.dart @@ -1,12 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/tools/constants.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/phonebook/providers/association_groupement_provider.dart'; import 'package:titan/phonebook/providers/association_list_provider.dart'; import 'package:titan/phonebook/providers/association_provider.dart'; import 'package:titan/phonebook/router.dart'; -import 'package:titan/phonebook/tools/constants.dart'; import 'package:titan/phonebook/ui/components/groupement_bar.dart'; import 'package:titan/phonebook/ui/phonebook.dart'; import 'package:titan/tools/constants.dart'; @@ -52,8 +51,8 @@ class AssociationAddEditPage extends HookConsumerWidget { alignment: Alignment.centerLeft, child: Text( association.id == "" - ? PhonebookTextConstants.addAssociation - : PhonebookTextConstants.editAssociation, + ? AppLocalizations.of(context)!.phonebookAddAssociation + : AppLocalizations.of(context)!.phonebookEdit, style: TextStyle( fontSize: 20, fontWeight: FontWeight.w700, @@ -66,13 +65,13 @@ class AssociationAddEditPage extends HookConsumerWidget { Container(margin: const EdgeInsets.symmetric(vertical: 10)), TextEntry( controller: name, - label: AdminTextConstants.name, + label: AppLocalizations.of(context)!.phonebookName, canBeEmpty: false, ), const SizedBox(height: 30), TextEntry( controller: description, - label: AdminTextConstants.description, + label: AppLocalizations.of(context)!.phonebookDescription, canBeEmpty: true, ), const SizedBox(height: 50), @@ -80,13 +79,13 @@ class AssociationAddEditPage extends HookConsumerWidget { onPressed: () async { if (!key.currentState!.validate()) { showSnackBarWithContext( - PhonebookTextConstants.emptyFieldError, + AppLocalizations.of(context)!.phonebookEmptyFieldError, ); return; } if (associationGroupement.id == '') { showSnackBarWithContext( - PhonebookTextConstants.emptyKindError, + AppLocalizations.of(context)!.phonebookEmptyKindError, ); return; } @@ -102,7 +101,9 @@ class AssociationAddEditPage extends HookConsumerWidget { ); if (value) { showSnackBarWithContext( - PhonebookTextConstants.addedAssociation, + AppLocalizations.of( + context, + )!.phonebookAddedAssociation, ); associations.when( data: (d) { @@ -114,16 +115,20 @@ class AssociationAddEditPage extends HookConsumerWidget { ); }, error: (e, s) => showSnackBarWithContext( - PhonebookTextConstants.errorAssociationLoading, + AppLocalizations.of( + context, + )!.phonebookErrorAssociationLoading, ), loading: () {}, ); } else { - showSnackBarWithContext(AdminTextConstants.addingError); + showSnackBarWithContext( + AppLocalizations.of(context)!.adminAddingError, + ); } }); }, - text: AdminTextConstants.add, + text: AppLocalizations.of(context)!.adminAdd, ), ], ), diff --git a/lib/phonebook/ui/pages/association_editor_page/association_information_editor.dart b/lib/phonebook/ui/pages/association_editor_page/association_information_editor.dart index 7a96f1625d..4b067c2151 100644 --- a/lib/phonebook/ui/pages/association_editor_page/association_information_editor.dart +++ b/lib/phonebook/ui/pages/association_editor_page/association_information_editor.dart @@ -9,7 +9,6 @@ import 'package:titan/phonebook/providers/association_groupement_provider.dart'; import 'package:titan/phonebook/providers/association_list_provider.dart'; import 'package:titan/phonebook/providers/association_provider.dart'; import 'package:titan/phonebook/providers/phonebook_admin_provider.dart'; -import 'package:titan/phonebook/tools/constants.dart'; import 'package:titan/phonebook/ui/components/groupement_bar.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; diff --git a/lib/phonebook/ui/pages/association_groups_page/association_groups_page.dart b/lib/phonebook/ui/pages/association_groups_page/association_groups_page.dart new file mode 100644 index 0000000000..acbe55911a --- /dev/null +++ b/lib/phonebook/ui/pages/association_groups_page/association_groups_page.dart @@ -0,0 +1,185 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/admin/class/simple_group.dart'; +import 'package:titan/admin/providers/group_list_provider.dart'; +import 'package:titan/phonebook/providers/association_list_provider.dart'; +import 'package:titan/phonebook/providers/association_member_list_provider.dart'; +import 'package:titan/phonebook/providers/association_picture_provider.dart'; +import 'package:titan/phonebook/providers/association_provider.dart'; +import 'package:titan/phonebook/ui/phonebook.dart'; +import 'package:titan/tools/constants.dart'; +import 'package:titan/tools/functions.dart'; +import 'package:titan/tools/token_expire_wrapper.dart'; +import 'package:titan/tools/ui/builders/waiting_button.dart'; +import 'package:titan/tools/ui/layouts/add_edit_button_layout.dart'; +import 'package:titan/tools/ui/layouts/refresher.dart'; +import 'package:titan/l10n/app_localizations.dart'; + +class AssociationGroupsPage extends HookConsumerWidget { + final scrollKey = GlobalKey(); + AssociationGroupsPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final association = ref.watch(associationProvider); + final associationMemberListNotifier = ref.watch( + associationMemberListProvider.notifier, + ); + final associationPictureNotifier = ref.watch( + associationPictureProvider.notifier, + ); + final associationListNotifier = ref.watch(associationListProvider.notifier); + + final groups = ref.watch(allGroupListProvider); + List selectedGroups = groups.maybeWhen( + data: (value) { + return value.where((element) { + return association.associatedGroups.contains(element.id); + }).toList(); + }, + orElse: () { + return []; + }, + ); + + void displayToastWithContext(TypeMsg type, String msg) { + displayToast(context, type, msg); + } + + return PhonebookTemplate( + child: Refresher( + onRefresh: () async { + await associationMemberListNotifier.loadMembers( + association.id, + association.mandateYear.toString(), + ); + await associationPictureNotifier.getAssociationPicture( + association.id, + ); + }, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 30), + child: Column( + children: [ + Container( + margin: const EdgeInsets.symmetric(vertical: 10), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + child: ExpansionTile( + title: Text( + AppLocalizations.of(context)!.phonebookGroups, + ), + children: groups.maybeWhen( + data: (data) { + return data.map((group) { + return Container( + padding: const EdgeInsets.symmetric( + vertical: 10, + horizontal: 20, + ), + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.black.withValues( + alpha: 0.1, + ), + offset: const Offset(0, 1), + blurRadius: 4, + spreadRadius: 2, + ), + ], + ), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + child: Text( + group.name, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + ), + StatefulBuilder( + builder: (context, setState) { + return Checkbox( + value: selectedGroups.contains(group), + onChanged: (value) { + if (value == true) { + selectedGroups.add(group); + } else { + selectedGroups.remove(group); + } + setState(() {}); + }, + ); + }, + ), + ], + ), + ); + }).toList(); + }, + orElse: () { + return []; + }, + ), + ), + ), + ], + ), + ), + WaitingButton( + builder: (child) => AddEditButtonLayout( + colors: const [ + ColorConstants.gradient1, + ColorConstants.gradient2, + ], + child: child, + ), + onTap: () async { + await tokenExpireWrapper(ref, () async { + final updatedGroupsMsg = AppLocalizations.of( + context, + )!.phonebookUpdatedGroups; + final updatingErrorMsg = AppLocalizations.of( + context, + )!.phonebookUpdatingError; + final value = await associationListNotifier + .updateAssociationGroups( + association.copyWith( + associatedGroups: selectedGroups + .map((e) => e.id) + .toList(), + ), + ); + if (value) { + displayToastWithContext(TypeMsg.msg, updatedGroupsMsg); + } else { + displayToastWithContext(TypeMsg.msg, updatingErrorMsg); + } + }); + }, + child: Text( + AppLocalizations.of(context)!.phonebookUpdateGroups, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + color: Color.fromARGB(255, 255, 255, 255), + ), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/phonebook/ui/pages/association_members_page/association_members_page.dart b/lib/phonebook/ui/pages/association_members_page/association_members_page.dart new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/phonebook/ui/pages/main_page/main_page.dart b/lib/phonebook/ui/pages/main_page/main_page.dart index 57d767ad19..b71b23f23c 100644 --- a/lib/phonebook/ui/pages/main_page/main_page.dart +++ b/lib/phonebook/ui/pages/main_page/main_page.dart @@ -8,7 +8,6 @@ import 'package:titan/phonebook/providers/association_groupement_list_provider.d import 'package:titan/phonebook/providers/association_list_provider.dart'; import 'package:titan/phonebook/providers/phonebook_admin_provider.dart'; import 'package:titan/phonebook/router.dart'; -import 'package:titan/phonebook/tools/constants.dart'; import 'package:titan/phonebook/ui/pages/main_page/association_card.dart'; import 'package:titan/phonebook/ui/phonebook.dart'; import 'package:titan/phonebook/ui/components/association_research_bar.dart'; @@ -70,37 +69,38 @@ class PhonebookMainPage extends HookConsumerWidget { ], ], ), - ), - Async2Child( - values: Tuple2(associationList, associationGroupementList), - builder: (context, associations, associationGroupements) { - return Column( - children: [ - if (associations.isEmpty) - Center( - child: Text( - AppLocalizations.of( - context, - )!.phonebookNoAssociationFound, + Async2Children( + values: Tuple2(associationList, associationGroupementList), + builder: (context, associations, associationGroupements) { + return Column( + children: [ + if (associations.isEmpty) + Center( + child: Text( + AppLocalizations.of( + context, + )!.phonebookNoAssociationFound, + ), + ) + else + ...associationFilteredList.map( + (association) => !association.deactivated + ? AssociationCard( + association: association, + groupement: associationGroupements.firstWhere( + (groupement) => + groupement.id == + association.groupementId, + ), + ) + : const SizedBox.shrink(), ), - ) - else - ...associationFilteredList.map( - (association) => !association.deactivated - ? AssociationCard( - association: association, - groupement: associationGroupements.firstWhere( - (groupement) => - groupement.id == association.groupementId, - ), - ) - : const SizedBox.shrink(), - ), - ], - ); - }, - ), - ], + ], + ); + }, + ), + ], + ), ), ), ); diff --git a/lib/tools/ui/builders/async_child.dart b/lib/tools/ui/builders/async_child.dart index 0853b2ea83..74c0b69ab2 100644 --- a/lib/tools/ui/builders/async_child.dart +++ b/lib/tools/ui/builders/async_child.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/tools/ui/widgets/loader.dart'; import 'package:tuple/tuple.dart'; @@ -57,7 +58,7 @@ Widget handleLoadingAndError( errorBuilder ?? (error, stack) => Center( child: Text( - "${TextConstants.error}:$error", + "${AppLocalizations.of(context)!.adminError}:$error", style: TextStyle(color: loaderColor), ), ); diff --git a/lib/tools/ui/styleguide/item_chip.dart b/lib/tools/ui/styleguide/item_chip.dart index e4cbe8ad87..602827ff67 100644 --- a/lib/tools/ui/styleguide/item_chip.dart +++ b/lib/tools/ui/styleguide/item_chip.dart @@ -5,11 +5,13 @@ class ItemChip extends StatelessWidget { final Function()? onTap; final Function()? onLongPress; final Widget child; + final Axis scrollDirection; const ItemChip({ super.key, this.selected = false, this.onTap, this.onLongPress, + this.scrollDirection = Axis.horizontal, required this.child, }); @@ -19,7 +21,9 @@ class ItemChip extends StatelessWidget { onTap: onTap, onLongPress: onLongPress, child: Container( - margin: const EdgeInsets.symmetric(horizontal: 10.0), + margin: scrollDirection == Axis.horizontal + ? EdgeInsets.symmetric(horizontal: 10.0) + : EdgeInsets.symmetric(vertical: 5.0), padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 20.0), decoration: BoxDecoration( borderRadius: BorderRadius.circular(30.0), From 47025eab73a36c78665dd8b1f963695fdadaed5e Mon Sep 17 00:00:00 2001 From: Thonyk Date: Wed, 30 Jul 2025 12:39:10 +0200 Subject: [PATCH 088/473] feat: association edition --- .../association_member_list_provider.dart | 8 +- ...sociation_member_sorted_list_provider.dart | 2 +- lib/phonebook/router.dart | 114 ++++-- lib/phonebook/tools/function.dart | 19 +- .../ui/components/groupement_bar.dart | 4 +- lib/phonebook/ui/components/member_card.dart | 80 ++++ .../ui/pages/admin_page/admin_page.dart | 2 +- ...al.dart => association_edition_modal.dart} | 31 +- .../admin_page/editable_association_card.dart | 2 +- .../association_add_edit_page.dart | 2 +- .../association_editor_page.dart | 347 ---------------- .../association_information_editor.dart | 383 ------------------ .../member_editable_card.dart | 183 --------- .../association_members_page.dart | 168 ++++++++ .../member_edition_modal.dart | 118 ++++++ .../association_page/association_page.dart | 5 +- .../pages/association_page/member_card.dart | 148 ------- .../association_page/web_member_card.dart | 20 +- lib/phonebook/ui/phonebook.dart | 18 +- 19 files changed, 503 insertions(+), 1151 deletions(-) create mode 100644 lib/phonebook/ui/components/member_card.dart rename lib/phonebook/ui/pages/admin_page/{edition_modal.dart => association_edition_modal.dart} (87%) delete mode 100644 lib/phonebook/ui/pages/association_editor_page/association_editor_page.dart delete mode 100644 lib/phonebook/ui/pages/association_editor_page/association_information_editor.dart delete mode 100644 lib/phonebook/ui/pages/association_editor_page/member_editable_card.dart create mode 100644 lib/phonebook/ui/pages/association_members_page/member_edition_modal.dart delete mode 100644 lib/phonebook/ui/pages/association_page/member_card.dart diff --git a/lib/phonebook/providers/association_member_list_provider.dart b/lib/phonebook/providers/association_member_list_provider.dart index e6495639df..43f5b47d7a 100644 --- a/lib/phonebook/providers/association_member_list_provider.dart +++ b/lib/phonebook/providers/association_member_list_provider.dart @@ -86,13 +86,7 @@ class AssociationMemberListNotifier extends ListNotifier { e.associationId == membership.associationId && e.mandateYear == membership.mandateYear, ); - memberships.remove( - memberships.firstWhere( - (e) => - e.associationId == membership.associationId && - e.mandateYear == membership.mandateYear, - ), - ); + memberships.remove(oldMembership); memberships.add(oldMembership.copyWith(order: i)); members[i].copyWith(membership: memberships); } diff --git a/lib/phonebook/providers/association_member_sorted_list_provider.dart b/lib/phonebook/providers/association_member_sorted_list_provider.dart index aece1237d5..b41782785e 100644 --- a/lib/phonebook/providers/association_member_sorted_list_provider.dart +++ b/lib/phonebook/providers/association_member_sorted_list_provider.dart @@ -11,7 +11,7 @@ final associationMemberSortedListProvider = Provider>(( final association = ref.watch(associationProvider); return memberListProvider.maybeWhen( data: (members) { - return sortedMembers(members, association.id); + return sortedMembers(members, association); }, orElse: () => List.empty(), ); diff --git a/lib/phonebook/router.dart b/lib/phonebook/router.dart index 4585c005bd..a5f8d8ecb9 100644 --- a/lib/phonebook/router.dart +++ b/lib/phonebook/router.dart @@ -3,25 +3,37 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/navigation/class/module.dart'; import 'package:titan/phonebook/providers/phonebook_admin_provider.dart'; -import 'package:titan/phonebook/ui/pages/add_edit_groupement_page/groupement_add_edit_page.dart'; -import 'package:titan/phonebook/ui/pages/admin_page/admin_page.dart'; -import 'package:titan/phonebook/ui/pages/association_add_edit_page/association_add_edit_page.dart'; -import 'package:titan/phonebook/ui/pages/association_editor_page/association_editor_page.dart'; -import 'package:titan/phonebook/ui/pages/association_page/association_page.dart'; -import 'package:titan/phonebook/ui/pages/main_page/main_page.dart'; -import 'package:titan/phonebook/ui/pages/member_detail_page/member_detail_page.dart'; -import 'package:titan/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart'; +import 'package:titan/phonebook/ui/pages/add_edit_groupement_page/groupement_add_edit_page.dart' + deferred as groupement_add_edit_page; +import 'package:titan/phonebook/ui/pages/admin_page/admin_page.dart' + deferred as admin_page; +import 'package:titan/phonebook/ui/pages/association_add_edit_page/association_add_edit_page.dart' + deferred as association_add_edit_page; +import 'package:titan/phonebook/ui/pages/association_groups_page/association_groups_page.dart' + deferred as association_groups_page; +import 'package:titan/phonebook/ui/pages/association_members_page/association_members_page.dart' + deferred as association_members_page; +import 'package:titan/phonebook/ui/pages/association_page/association_page.dart' + deferred as association_page; +import 'package:titan/phonebook/ui/pages/main_page/main_page.dart' + deferred as main_page; +import 'package:titan/phonebook/ui/pages/member_detail_page/member_detail_page.dart' + deferred as member_detail_page; +import 'package:titan/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart' + deferred as membership_editor_page; import 'package:titan/tools/middlewares/admin_middleware.dart'; import 'package:titan/tools/middlewares/authenticated_middleware.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/tools/middlewares/deferred_middleware.dart'; class PhonebookRouter { final Ref ref; static const String root = '/phonebook'; static const String admin = '/admin'; - static const String createAssociaiton = '/create_association'; static const String addEditGroupement = '/add_edit_groupement'; - static const String editAssociation = '/edit_association'; + static const String addEditAssociation = '/add_edit_association'; + static const String editAssociationMembers = '/edit_association_members'; + static const String editAssociationGroups = '/edit_association_groups'; static const String associationDetail = '/association_detail'; static const String memberDetail = '/member_detail'; static const String addEditMember = '/add_edit_member'; @@ -35,8 +47,11 @@ class PhonebookRouter { QRoute route() => QRoute( name: "phonebook", path: PhonebookRouter.root, - builder: () => const PhonebookMainPage(), - middleware: [AuthenticatedMiddleware(ref)], + builder: () => main_page.PhonebookMainPage(), + middleware: [ + AuthenticatedMiddleware(ref), + DeferredLoadingMiddleware(main_page.loadLibrary), + ], pageType: QCustomPage( transitionsBuilder: (_, animation, _, child) => FadeTransition(opacity: animation, child: child), @@ -44,26 +59,60 @@ class PhonebookRouter { children: [ QRoute( path: admin, - builder: () => const AdminPage(), - middleware: [AdminMiddleware(ref, hasPhonebookAdminAccessProvider)], + builder: () => admin_page.AdminPage(), + middleware: [ + AdminMiddleware(ref, hasPhonebookAdminAccessProvider), + DeferredLoadingMiddleware(admin_page.loadLibrary), + ], children: [ QRoute( - path: editAssociation, - builder: () => AssociationEditorPage(), + path: addEditAssociation, + builder: () => association_add_edit_page.AssociationAddEditPage(), + middleware: [ + DeferredLoadingMiddleware(association_add_edit_page.loadLibrary), + ], + children: [ + QRoute( + path: addEditGroupement, + builder: () => + groupement_add_edit_page.AssociationGroupementAddEditPage(), + middleware: [ + DeferredLoadingMiddleware( + groupement_add_edit_page.loadLibrary, + ), + ], + ), + ], + ), + QRoute( + path: editAssociationMembers, + builder: () => association_members_page.AssociationMembersPage(), + middleware: [ + DeferredLoadingMiddleware(association_members_page.loadLibrary), + ], children: [ QRoute( path: addEditMember, - builder: () => const MembershipEditorPage(), + builder: () => membership_editor_page.MembershipEditorPage(), + middleware: [ + DeferredLoadingMiddleware(membership_editor_page.loadLibrary), + ], ), ], ), QRoute( - path: createAssociaiton, - builder: () => AssociationAddEditPage(), + path: editAssociationGroups, + builder: () => association_groups_page.AssociationGroupsPage(), + middleware: [ + DeferredLoadingMiddleware(association_groups_page.loadLibrary), + ], children: [ QRoute( - path: addEditGroupement, - builder: () => const AssociationGroupementAddEditPage(), + path: addEditMember, + builder: () => membership_editor_page.MembershipEditorPage(), + middleware: [ + DeferredLoadingMiddleware(membership_editor_page.loadLibrary), + ], ), ], ), @@ -71,22 +120,33 @@ class PhonebookRouter { ), QRoute( path: associationDetail, - builder: () => const AssociationPage(), + builder: () => association_page.AssociationPage(), + middleware: [DeferredLoadingMiddleware(association_page.loadLibrary)], children: [ QRoute( - path: editAssociation, - builder: () => AssociationEditorPage(), - middleware: [AdminMiddleware(ref, isAssociationPresidentProvider)], + path: addEditAssociation, + builder: () => association_members_page.AssociationMembersPage(), + middleware: [ + AdminMiddleware(ref, isAssociationPresidentProvider), + DeferredLoadingMiddleware(association_members_page.loadLibrary), + ], children: [ QRoute( path: addEditMember, - builder: () => const MembershipEditorPage(), + builder: () => membership_editor_page.MembershipEditorPage(), + middleware: [ + DeferredLoadingMiddleware(membership_editor_page.loadLibrary), + ], ), ], ), ], ), - QRoute(path: memberDetail, builder: () => const MemberDetailPage()), + QRoute( + path: memberDetail, + builder: () => member_detail_page.MemberDetailPage(), + middleware: [DeferredLoadingMiddleware(member_detail_page.loadLibrary)], + ), ], ); } diff --git a/lib/phonebook/tools/function.dart b/lib/phonebook/tools/function.dart index fed88cff5e..8bdc5fc75f 100644 --- a/lib/phonebook/tools/function.dart +++ b/lib/phonebook/tools/function.dart @@ -7,20 +7,29 @@ import 'package:titan/phonebook/class/complete_member.dart'; import 'package:titan/phonebook/class/membership.dart'; import 'package:titan/phonebook/providers/roles_tags_provider.dart'; -int getPosition(CompleteMember member, String associationId) { - Membership membership = member.memberships.firstWhere( - (element) => element.associationId == associationId, +Membership getMembershipForAssociation( + CompleteMember member, + Association association, +) { + return member.memberships.firstWhere( + (element) => + element.associationId == association.id && + element.mandateYear == association.mandateYear, ); +} + +int getPosition(CompleteMember member, Association association) { + final membership = getMembershipForAssociation(member, association); return membership.order; } List sortedMembers( List members, - String associationId, + Association association, ) { return members..sort( (a, b) => - getPosition(a, associationId).compareTo(getPosition(b, associationId)), + getPosition(a, association).compareTo(getPosition(b, association)), ); } diff --git a/lib/phonebook/ui/components/groupement_bar.dart b/lib/phonebook/ui/components/groupement_bar.dart index 39bda73718..1b8b695e56 100644 --- a/lib/phonebook/ui/components/groupement_bar.dart +++ b/lib/phonebook/ui/components/groupement_bar.dart @@ -59,7 +59,7 @@ class AssociationGroupementBar extends HookConsumerWidget { QR.to( PhonebookRouter.root + PhonebookRouter.admin + - PhonebookRouter.createAssociaiton + + PhonebookRouter.addEditAssociation + PhonebookRouter.addEditGroupement, ); }, @@ -120,7 +120,7 @@ class AssociationGroupementBar extends HookConsumerWidget { QR.to( PhonebookRouter.root + PhonebookRouter.admin + - PhonebookRouter.createAssociaiton + + PhonebookRouter.addEditAssociation + PhonebookRouter.addEditGroupement, ); }, diff --git a/lib/phonebook/ui/components/member_card.dart b/lib/phonebook/ui/components/member_card.dart new file mode 100644 index 0000000000..63f9ef6138 --- /dev/null +++ b/lib/phonebook/ui/components/member_card.dart @@ -0,0 +1,80 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/phonebook/class/association.dart'; +import 'package:titan/phonebook/class/complete_member.dart'; +import 'package:titan/phonebook/class/membership.dart'; +import 'package:titan/phonebook/providers/complete_member_provider.dart'; +import 'package:titan/phonebook/providers/member_pictures_provider.dart'; +import 'package:titan/phonebook/providers/profile_picture_provider.dart'; +import 'package:titan/phonebook/router.dart'; +import 'package:titan/phonebook/tools/function.dart'; +import 'package:titan/phonebook/ui/pages/association_members_page/member_edition_modal.dart'; +import 'package:titan/tools/ui/builders/auto_loader_child.dart'; +import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; +import 'package:titan/tools/ui/styleguide/list_item.dart'; + +class MemberCard extends HookConsumerWidget { + const MemberCard({ + super.key, + required this.member, + required this.association, + required this.deactivated, + this.editable = false, + }); + + final CompleteMember member; + final Association association; + final bool deactivated; + final bool editable; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final profilePictureNotifier = ref.watch(profilePictureProvider.notifier); + final memberNotifier = ref.watch(completeMemberProvider.notifier); + + final memberPictures = ref.watch( + memberPicturesProvider.select((value) => value[member]), + ); + final memberPicturesNotifier = ref.watch(memberPicturesProvider.notifier); + + Membership assoMembership = getMembershipForAssociation( + member, + association, + ); + + return ListItem( + title: + "${(member.member.nickname ?? '${member.member.firstname} ${member.member.name}')} - ${assoMembership.apparentName}", + subtitle: member.member.nickname != null + ? "${member.member.firstname} ${member.member.name}" + : null, + icon: AutoLoaderChild( + group: memberPictures, + notifier: memberPicturesNotifier, + mapKey: member, + loader: (ref) => + profilePictureNotifier.getProfilePicture(member.member.id), + loadingBuilder: (context) => + const CircleAvatar(radius: 20, child: CircularProgressIndicator()), + dataBuilder: (context, data) => + CircleAvatar(child: Image(image: data.first.image)), + ), + onTap: editable + ? () { + showCustomBottomModal( + ref: ref, + context: context, + modal: MemberEditionModal( + member: member, + membership: assoMembership, + ), + ); + } + : () { + memberNotifier.setCompleteMember(member); + QR.to(PhonebookRouter.root + PhonebookRouter.memberDetail); + }, + ); + } +} diff --git a/lib/phonebook/ui/pages/admin_page/admin_page.dart b/lib/phonebook/ui/pages/admin_page/admin_page.dart index ed7b6cc399..e4329f5dc7 100644 --- a/lib/phonebook/ui/pages/admin_page/admin_page.dart +++ b/lib/phonebook/ui/pages/admin_page/admin_page.dart @@ -61,7 +61,7 @@ class AdminPage extends HookConsumerWidget { QR.to( PhonebookRouter.root + PhonebookRouter.admin + - PhonebookRouter.createAssociaiton, + PhonebookRouter.addEditAssociation, ); } : null, diff --git a/lib/phonebook/ui/pages/admin_page/edition_modal.dart b/lib/phonebook/ui/pages/admin_page/association_edition_modal.dart similarity index 87% rename from lib/phonebook/ui/pages/admin_page/edition_modal.dart rename to lib/phonebook/ui/pages/admin_page/association_edition_modal.dart index 496dee2d4f..395b2d8e0d 100644 --- a/lib/phonebook/ui/pages/admin_page/edition_modal.dart +++ b/lib/phonebook/ui/pages/admin_page/association_edition_modal.dart @@ -39,6 +39,10 @@ class AssociationEditionModal extends HookConsumerWidget { displayToast(context, type, msg); } + AppLocalizations localizeWithContext() { + return AppLocalizations.of(context)!; + } + return BottomModalTemplate( title: "title", type: isSuppresion.value ? BottomModalType.danger : BottomModalType.main, @@ -56,7 +60,7 @@ class AssociationEditionModal extends HookConsumerWidget { QR.to( PhonebookRouter.root + PhonebookRouter.admin + - PhonebookRouter.editAssociation, + PhonebookRouter.addEditAssociation, ); }, ), @@ -72,7 +76,7 @@ class AssociationEditionModal extends HookConsumerWidget { QR.to( PhonebookRouter.root + PhonebookRouter.admin + - PhonebookRouter.editAssociation, + PhonebookRouter.editAssociationGroups, ); }, ), @@ -88,7 +92,7 @@ class AssociationEditionModal extends HookConsumerWidget { QR.to( PhonebookRouter.root + PhonebookRouter.admin + - PhonebookRouter.editAssociation, + PhonebookRouter.editAssociationMembers, ); }, ), @@ -126,16 +130,13 @@ class AssociationEditionModal extends HookConsumerWidget { if (result) { displayToastWithContext( TypeMsg.msg, - AppLocalizations.of( - context, - )!.phonebookDeletedAssociation, + localizeWithContext() + .phonebookDeletedAssociation, ); } else { displayToastWithContext( TypeMsg.error, - AppLocalizations.of( - context, - )!.phonebookDeletingError, + localizeWithContext().phonebookDeletingError, ); } } @@ -145,23 +146,21 @@ class AssociationEditionModal extends HookConsumerWidget { if (result) { displayToastWithContext( TypeMsg.msg, - AppLocalizations.of( - context, - )!.phonebookDeactivatedAssociation, + localizeWithContext() + .phonebookDeactivatedAssociation, ); } else { displayToastWithContext( TypeMsg.error, - AppLocalizations.of( - context, - )!.phonebookDeactivatingError, + localizeWithContext() + .phonebookDeactivatingError, ); } }, ), SizedBox(height: 5), Button( - text: AppLocalizations.of(context)!.phonebookCancel, + text: localizeWithContext().phonebookCancel, onPressed: () => isSuppresion.value = false, ), ], diff --git a/lib/phonebook/ui/pages/admin_page/editable_association_card.dart b/lib/phonebook/ui/pages/admin_page/editable_association_card.dart index 22818c327e..0830692779 100644 --- a/lib/phonebook/ui/pages/admin_page/editable_association_card.dart +++ b/lib/phonebook/ui/pages/admin_page/editable_association_card.dart @@ -5,7 +5,7 @@ import 'package:titan/phonebook/class/association.dart'; import 'package:titan/phonebook/class/association_groupement.dart'; import 'package:titan/phonebook/providers/association_picture_provider.dart'; import 'package:titan/phonebook/providers/associations_picture_map_provider.dart'; -import 'package:titan/phonebook/ui/pages/admin_page/edition_modal.dart'; +import 'package:titan/phonebook/ui/pages/admin_page/association_edition_modal.dart'; import 'package:titan/tools/ui/builders/auto_loader_child.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; import 'package:titan/tools/ui/styleguide/list_item.dart'; diff --git a/lib/phonebook/ui/pages/association_add_edit_page/association_add_edit_page.dart b/lib/phonebook/ui/pages/association_add_edit_page/association_add_edit_page.dart index 95a8559087..81ddc36aab 100644 --- a/lib/phonebook/ui/pages/association_add_edit_page/association_add_edit_page.dart +++ b/lib/phonebook/ui/pages/association_add_edit_page/association_add_edit_page.dart @@ -111,7 +111,7 @@ class AssociationAddEditPage extends HookConsumerWidget { QR.to( PhonebookRouter.root + PhonebookRouter.admin + - PhonebookRouter.editAssociation, + PhonebookRouter.addEditAssociation, ); }, error: (e, s) => showSnackBarWithContext( diff --git a/lib/phonebook/ui/pages/association_editor_page/association_editor_page.dart b/lib/phonebook/ui/pages/association_editor_page/association_editor_page.dart deleted file mode 100644 index 20eb93a712..0000000000 --- a/lib/phonebook/ui/pages/association_editor_page/association_editor_page.dart +++ /dev/null @@ -1,347 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:heroicons/heroicons.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/phonebook/class/complete_member.dart'; -import 'package:titan/phonebook/class/membership.dart'; -import 'package:titan/phonebook/providers/association_groupement_provider.dart'; -import 'package:titan/phonebook/providers/association_list_provider.dart'; -import 'package:titan/phonebook/providers/association_member_list_provider.dart'; -import 'package:titan/phonebook/providers/association_member_sorted_list_provider.dart'; -import 'package:titan/phonebook/providers/association_picture_provider.dart'; -import 'package:titan/phonebook/providers/association_provider.dart'; -import 'package:titan/phonebook/providers/complete_member_provider.dart'; -import 'package:titan/phonebook/providers/member_role_tags_provider.dart'; -import 'package:titan/phonebook/providers/membership_provider.dart'; -import 'package:titan/phonebook/providers/phonebook_admin_provider.dart'; -import 'package:titan/phonebook/providers/roles_tags_provider.dart'; -import 'package:titan/phonebook/router.dart'; -import 'package:titan/phonebook/ui/pages/association_editor_page/association_information_editor.dart'; -import 'package:titan/phonebook/ui/phonebook.dart'; -import 'package:titan/phonebook/ui/pages/association_editor_page/member_editable_card.dart'; -import 'package:titan/tools/constants.dart'; -import 'package:titan/tools/functions.dart'; -import 'package:titan/tools/token_expire_wrapper.dart'; -import 'package:titan/tools/ui/builders/async_child.dart'; -import 'package:titan/tools/ui/builders/waiting_button.dart'; -import 'package:titan/tools/ui/layouts/add_edit_button_layout.dart'; -import 'package:titan/tools/ui/layouts/refresher.dart'; -import 'package:qlevar_router/qlevar_router.dart'; -import 'package:titan/l10n/app_localizations.dart'; - -class AssociationEditorPage extends HookConsumerWidget { - final scrollKey = GlobalKey(); - AssociationEditorPage({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final association = ref.watch(associationProvider); - final associationNotifier = ref.watch(associationProvider.notifier); - final associationMemberListNotifier = ref.watch( - associationMemberListProvider.notifier, - ); - final associationMemberList = ref.watch(associationMemberListProvider); - final associationMemberSortedList = ref.watch( - associationMemberSortedListProvider, - ); - final associationPictureNotifier = ref.watch( - associationPictureProvider.notifier, - ); - final associationListNotifier = ref.watch(associationListProvider.notifier); - final rolesTagsNotifier = ref.watch(rolesTagsProvider.notifier); - final membershipNotifier = ref.watch(membershipProvider.notifier); - final completeMemberNotifier = ref.watch(completeMemberProvider.notifier); - final memberRoleTagsNotifier = ref.watch(memberRoleTagsProvider.notifier); - final isPhonebookAdmin = ref.watch(isPhonebookAdminProvider); - final isAssociationPresident = ref.watch(isAssociationPresidentProvider); - final associationGroupementNotifier = ref.watch( - associationGroupementProvider.notifier, - ); - - void displayToastWithContext(TypeMsg type, String msg) { - displayToast(context, type, msg); - } - - return PhonebookTemplate( - child: Refresher( - onRefresh: () async { - await associationMemberListNotifier.loadMembers( - association.id, - association.mandateYear.toString(), - ); - await associationPictureNotifier.getAssociationPicture( - association.id, - ); - }, - child: Column( - children: [ - const SizedBox(height: 20), - Container( - padding: const EdgeInsets.symmetric(horizontal: 30), - alignment: Alignment.centerLeft, - child: Text( - AppLocalizations.of(context)!.phonebookEdit, - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.w700, - color: ColorConstants.gradient1, - ), - ), - ), - const SizedBox(height: 20), - AssociationInformationEditor(), - const SizedBox(height: 30), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 30), - child: Row( - children: [ - Text(AppLocalizations.of(context)!.phonebookMembers), - const Spacer(), - WaitingButton( - builder: (child) => Container( - width: 40, - height: 40, - decoration: BoxDecoration( - color: - (isPhonebookAdmin || isAssociationPresident) && - !association.deactivated - ? ColorConstants.gradient1 - : ColorConstants.deactivated1, - borderRadius: BorderRadius.circular(10), - ), - child: child, - ), - onTap: - (isPhonebookAdmin || isAssociationPresident) && - !association.deactivated - ? () async { - rolesTagsNotifier.resetChecked(); - memberRoleTagsNotifier.reset(); - completeMemberNotifier.setCompleteMember( - CompleteMember.empty(), - ); - membershipNotifier.setMembership( - Membership.empty().copyWith( - associationId: association.id, - ), - ); - if (QR.currentPath.contains( - PhonebookRouter.admin, - )) { - QR.to( - PhonebookRouter.root + - PhonebookRouter.admin + - PhonebookRouter.editAssociation + - PhonebookRouter.addEditMember, - ); - } else { - QR.to( - PhonebookRouter.root + - PhonebookRouter.associationDetail + - PhonebookRouter.editAssociation + - PhonebookRouter.addEditMember, - ); - } - } - : () async {}, - child: const HeroIcon( - HeroIcons.plus, - size: 30, - color: Colors.white, - ), - ), - ], - ), - ), - const SizedBox(height: 10), - AsyncChild( - value: associationMemberList, - builder: (context, associationMembers) => - associationMembers.isEmpty - ? Text(AppLocalizations.of(context)!.phonebookNoMember) - : (isPhonebookAdmin || isAssociationPresident) && - !association.deactivated - ? SizedBox( - height: 400, - child: ReorderableListView( - proxyDecorator: (child, index, animation) { - return Material( - child: FadeTransition( - opacity: animation, - child: child, - ), - ); - }, - onReorder: (int oldIndex, int newIndex) async { - final memberReorderedMsg = AppLocalizations.of( - context, - )!.phonebookMemberReordered; - final reorderingErrorMsg = AppLocalizations.of( - context, - )!.phonebookReorderingError; - await tokenExpireWrapper(ref, () async { - final result = await associationMemberListNotifier - .reorderMember( - associationMemberSortedList[oldIndex], - associationMemberSortedList[oldIndex] - .memberships - .firstWhere( - (element) => - element.associationId == - association.id && - element.mandateYear == - association.mandateYear, - ) - .copyWith(order: newIndex), - oldIndex, - newIndex, - ); - if (result) { - displayToastWithContext( - TypeMsg.msg, - memberReorderedMsg, - ); - } else { - displayToastWithContext( - TypeMsg.error, - reorderingErrorMsg, - ); - } - }); - }, - children: associationMemberSortedList - .map( - (member) => MemberEditableCard( - deactivated: false, - key: ValueKey(member.member.id), - member: member, - association: association, - ), - ) - .toList(), - ), - ) - : SizedBox( - height: 400, - child: ListView.builder( - itemCount: associationMembers.length, - itemBuilder: (context, index) { - return MemberEditableCard( - deactivated: true, - key: ValueKey(associationMembers[index].member.id), - member: associationMembers[index], - association: association, - ); - }, - ), - ), - ), - const SizedBox(height: 10), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 30), - child: WaitingButton( - builder: (child) => AddEditButtonLayout( - colors: isPhonebookAdmin && !association.deactivated - ? [ColorConstants.gradient1, ColorConstants.gradient2] - : [ - ColorConstants.deactivated1, - ColorConstants.deactivated2, - ], - child: child, - ), - onTap: isPhonebookAdmin && !association.deactivated - ? () async { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: Text( - AppLocalizations.of(context)!.phonebookNewMandate, - ), - content: Text( - AppLocalizations.of( - context, - )!.phonebookChangeMandateConfirm, - ), - actions: [ - TextButton( - onPressed: () { - Navigator.pop(context); - }, - child: Text( - AppLocalizations.of(context)!.phonebookCancel, - ), - ), - TextButton( - onPressed: () async { - final newMandateConfirmedMsg = - AppLocalizations.of( - context, - )!.phonebookNewMandateConfirmed; - final mandateChangingErrorMsg = - AppLocalizations.of( - context, - )!.phonebookMandateChangingError; - Navigator.pop(context); - await tokenExpireWrapper(ref, () async { - final value = await associationListNotifier - .updateAssociation( - association.copyWith( - mandateYear: - association.mandateYear + 1, - ), - ); - if (value) { - displayToastWithContext( - TypeMsg.msg, - newMandateConfirmedMsg, - ); - associationNotifier.setAssociation( - association.copyWith( - mandateYear: - association.mandateYear + 1, - ), - ); - if (QR.currentPath.contains( - PhonebookRouter.associationDetail, - )) { - associationGroupementNotifier - .resetAssociationGroupement(); - QR.to( - PhonebookRouter.root + - PhonebookRouter.associationDetail, - ); - } - } else { - displayToastWithContext( - TypeMsg.error, - mandateChangingErrorMsg, - ); - } - }); - }, - child: Text( - AppLocalizations.of( - context, - )!.phonebookValidation, - ), - ), - ], - ), - ); - } - : () async {}, - child: Text( - "${AppLocalizations.of(context)!.phonebookChangeMandate} ${association.mandateYear + 1}", - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - color: Color.fromARGB(255, 255, 255, 255), - ), - ), - ), - ), - ], - ), - ), - ); - } -} diff --git a/lib/phonebook/ui/pages/association_editor_page/association_information_editor.dart b/lib/phonebook/ui/pages/association_editor_page/association_information_editor.dart deleted file mode 100644 index 4b067c2151..0000000000 --- a/lib/phonebook/ui/pages/association_editor_page/association_information_editor.dart +++ /dev/null @@ -1,383 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:heroicons/heroicons.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/class/simple_group.dart'; -import 'package:titan/admin/providers/group_list_provider.dart'; -import 'package:titan/admin/providers/is_admin_provider.dart'; -import 'package:titan/phonebook/providers/association_groupement_provider.dart'; -import 'package:titan/phonebook/providers/association_list_provider.dart'; -import 'package:titan/phonebook/providers/association_provider.dart'; -import 'package:titan/phonebook/providers/phonebook_admin_provider.dart'; -import 'package:titan/phonebook/ui/components/groupement_bar.dart'; -import 'package:titan/tools/constants.dart'; -import 'package:titan/tools/functions.dart'; -import 'package:titan/tools/token_expire_wrapper.dart'; -import 'package:titan/tools/ui/builders/waiting_button.dart'; -import 'package:titan/tools/ui/layouts/add_edit_button_layout.dart'; -import 'package:titan/l10n/app_localizations.dart'; - -class AssociationInformationEditor extends HookConsumerWidget { - final scrollKey = GlobalKey(); - AssociationInformationEditor({super.key}); - - @override - Widget build(context, ref) { - void displayToastWithContext(TypeMsg type, String msg) { - displayToast(context, type, msg); - } - - final kind = ref.watch(associationGroupementProvider); - final association = ref.watch(associationProvider); - final name = useTextEditingController(text: association.name); - final description = useTextEditingController(text: association.description); - final associationListNotifier = ref.watch(associationListProvider.notifier); - final isAdmin = ref.watch(isAdminProvider); - final isPhonebookAdmin = ref.watch(isPhonebookAdminProvider); - - final groups = ref.watch(allGroupListProvider); - List selectedGroups = groups.maybeWhen( - data: (value) { - return value.where((element) { - return association.associatedGroups.contains(element.id); - }).toList(); - }, - orElse: () { - return []; - }, - ); - final key = GlobalKey(); - - return Column( - children: [ - isPhonebookAdmin && !association.deactivated - ? Form( - key: key, - child: Column( - children: [ - AssociationGroupementBar(key: scrollKey), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 30), - child: Column( - children: [ - Container( - margin: const EdgeInsets.symmetric(vertical: 10), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - child: TextFormField( - controller: name, - cursorColor: ColorConstants.gradient1, - decoration: InputDecoration( - labelText: AppLocalizations.of( - context, - )!.phonebookNamePure, - labelStyle: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - ), - suffixIcon: Container( - padding: const EdgeInsets.all(10), - child: const HeroIcon(HeroIcons.pencil), - ), - enabledBorder: const UnderlineInputBorder( - borderSide: BorderSide( - color: Colors.transparent, - ), - ), - focusedBorder: const UnderlineInputBorder( - borderSide: BorderSide( - color: ColorConstants.gradient1, - ), - ), - ), - validator: (value) { - if (value == null || value.isEmpty) { - return AppLocalizations.of( - context, - )!.phonebookEmptyFieldError; - } - return null; - }, - ), - ), - ], - ), - ), - Container( - margin: const EdgeInsets.symmetric(vertical: 10), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - child: TextFormField( - controller: description, - cursorColor: ColorConstants.gradient1, - decoration: InputDecoration( - labelText: AppLocalizations.of( - context, - )!.phonebookDescription, - labelStyle: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - ), - suffixIcon: Container( - padding: const EdgeInsets.all(10), - child: const HeroIcon(HeroIcons.pencil), - ), - enabledBorder: const UnderlineInputBorder( - borderSide: BorderSide( - color: Colors.transparent, - ), - ), - focusedBorder: const UnderlineInputBorder( - borderSide: BorderSide( - color: ColorConstants.gradient1, - ), - ), - ), - ), - ), - ], - ), - ), - WaitingButton( - builder: (child) => AddEditButtonLayout( - colors: const [ - ColorConstants.gradient1, - ColorConstants.gradient2, - ], - child: child, - ), - onTap: () async { - if (!key.currentState!.validate()) { - return; - } - if (kind.id == '') { - displayToastWithContext( - TypeMsg.error, - AppLocalizations.of( - context, - )!.phonebookEmptyKindError, - ); - return; - } - final updatedAssociationMsg = AppLocalizations.of( - context, - )!.phonebookUpdatedAssociation; - final updatingErrorMsg = AppLocalizations.of( - context, - )!.phonebookUpdatingError; - await tokenExpireWrapper(ref, () async { - final value = await associationListNotifier - .updateAssociation( - association.copyWith( - name: name.text, - description: description.text, - groupementId: kind.id, - ), - ); - if (value) { - displayToastWithContext( - TypeMsg.msg, - updatedAssociationMsg, - ); - } else { - displayToastWithContext( - TypeMsg.msg, - updatingErrorMsg, - ); - } - }); - }, - child: Text( - AppLocalizations.of(context)!.phonebookEdit, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - color: Color.fromARGB(255, 255, 255, 255), - ), - ), - ), - ], - ), - ), - ], - ), - ) - : Padding( - padding: const EdgeInsets.symmetric(horizontal: 30), - child: Column( - children: [ - if (association.deactivated) - Container( - margin: const EdgeInsets.symmetric(vertical: 10), - child: Text( - AppLocalizations.of( - context, - )!.phonebookDeactivatedAssociationWarning, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Colors.red, - ), - ), - ), - Container( - margin: const EdgeInsets.symmetric(vertical: 10), - child: Text( - association.name, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - ), - ), - ), - Container( - margin: const EdgeInsets.symmetric(vertical: 10), - child: SizedBox( - child: Text( - association.description, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - ), - ), - ), - ), - ], - ), - ), - if (isAdmin && !association.deactivated) - Padding( - padding: const EdgeInsets.symmetric(horizontal: 30), - child: Column( - children: [ - Container( - margin: const EdgeInsets.symmetric(vertical: 10), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - child: ExpansionTile( - title: Text( - AppLocalizations.of(context)!.phonebookGroups, - ), - children: groups.maybeWhen( - data: (data) { - return data.map((group) { - return Container( - padding: const EdgeInsets.symmetric( - vertical: 10, - horizontal: 20, - ), - decoration: BoxDecoration( - color: Colors.white, - boxShadow: [ - BoxShadow( - color: Colors.black.withValues( - alpha: 0.1, - ), - offset: const Offset(0, 1), - blurRadius: 4, - spreadRadius: 2, - ), - ], - ), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - SizedBox( - child: Text( - group.name, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - ), - ), - ), - StatefulBuilder( - builder: (context, setState) { - return Checkbox( - value: selectedGroups.contains( - group, - ), - onChanged: (value) { - if (value == true) { - selectedGroups.add(group); - } else { - selectedGroups.remove(group); - } - setState(() {}); - }, - ); - }, - ), - ], - ), - ); - }).toList(); - }, - orElse: () { - return []; - }, - ), - ), - ), - ], - ), - ), - WaitingButton( - builder: (child) => AddEditButtonLayout( - colors: const [ - ColorConstants.gradient1, - ColorConstants.gradient2, - ], - child: child, - ), - onTap: () async { - await tokenExpireWrapper(ref, () async { - final updatedGroupsMsg = AppLocalizations.of( - context, - )!.phonebookUpdatedGroups; - final updatingErrorMsg = AppLocalizations.of( - context, - )!.phonebookUpdatingError; - final value = await associationListNotifier - .updateAssociationGroups( - association.copyWith( - associatedGroups: selectedGroups - .map((e) => e.id) - .toList(), - ), - ); - if (value) { - displayToastWithContext(TypeMsg.msg, updatedGroupsMsg); - } else { - displayToastWithContext(TypeMsg.msg, updatingErrorMsg); - } - }); - }, - child: Text( - AppLocalizations.of(context)!.phonebookUpdateGroups, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - color: Color.fromARGB(255, 255, 255, 255), - ), - ), - ), - ], - ), - ), - ], - ); - } -} diff --git a/lib/phonebook/ui/pages/association_editor_page/member_editable_card.dart b/lib/phonebook/ui/pages/association_editor_page/member_editable_card.dart deleted file mode 100644 index 4199fa0411..0000000000 --- a/lib/phonebook/ui/pages/association_editor_page/member_editable_card.dart +++ /dev/null @@ -1,183 +0,0 @@ -import 'package:auto_size_text/auto_size_text.dart'; -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/ui/pages/memberships/association_membership_detail_page/delete_button.dart'; -import 'package:titan/admin/ui/pages/memberships/association_membership_detail_page/edition_button.dart'; -import 'package:titan/phonebook/class/association.dart'; -import 'package:titan/phonebook/class/complete_member.dart'; -import 'package:titan/phonebook/class/membership.dart'; -import 'package:titan/phonebook/providers/association_member_list_provider.dart'; -import 'package:titan/phonebook/providers/complete_member_provider.dart'; -import 'package:titan/phonebook/providers/member_pictures_provider.dart'; -import 'package:titan/phonebook/providers/member_role_tags_provider.dart'; -import 'package:titan/phonebook/providers/membership_provider.dart'; -import 'package:titan/phonebook/providers/profile_picture_provider.dart'; -import 'package:titan/phonebook/providers/roles_tags_provider.dart'; -import 'package:titan/phonebook/router.dart'; -import 'package:titan/phonebook/tools/function.dart'; -import 'package:titan/tools/functions.dart'; -import 'package:titan/tools/ui/builders/auto_loader_child.dart'; -import 'package:qlevar_router/qlevar_router.dart'; -import 'package:titan/l10n/app_localizations.dart'; - -class MemberEditableCard extends HookConsumerWidget { - const MemberEditableCard({ - super.key, - required this.member, - required this.association, - required this.deactivated, - }); - - final CompleteMember member; - final Association association; - final bool deactivated; - - @override - Widget build(BuildContext context, WidgetRef ref) { - final profilePictureNotifier = ref.watch(profilePictureProvider.notifier); - final associationMemberListNotifier = ref.watch( - associationMemberListProvider.notifier, - ); - final roleTagsNotifier = ref.watch(rolesTagsProvider.notifier); - final membershipNotifier = ref.watch(membershipProvider.notifier); - final completeMemberNotifier = ref.watch(completeMemberProvider.notifier); - final memberRoleTagsNotifier = ref.watch(memberRoleTagsProvider.notifier); - void displayToastWithContext(TypeMsg type, String msg) { - displayToast(context, type, msg); - } - - final memberPictures = ref.watch( - memberPicturesProvider.select((value) => value[member]), - ); - final memberPicturesNotifier = ref.watch(memberPicturesProvider.notifier); - - Membership assoMembership = member.memberships.firstWhere( - (memberships) => - memberships.associationId == association.id && - memberships.mandateYear == association.mandateYear, - orElse: () => Membership.empty(), - ); - - return Container( - padding: const EdgeInsets.all(5), - margin: const EdgeInsets.symmetric(horizontal: 30, vertical: 10), - decoration: BoxDecoration( - border: Border.all(), - color: getColorFromTagList( - ref, - member.memberships - .firstWhere( - (element) => - element.associationId == association.id && - element.mandateYear == association.mandateYear, - ) - .rolesTags, - ), - borderRadius: const BorderRadius.all(Radius.circular(20)), - ), - child: Row( - children: [ - Container( - decoration: BoxDecoration( - shape: BoxShape.circle, - boxShadow: [ - BoxShadow( - color: Colors.black.withValues(alpha: 0.1), - spreadRadius: 5, - blurRadius: 10, - offset: const Offset(2, 3), - ), - ], - ), - child: AutoLoaderChild( - group: memberPictures, - notifier: memberPicturesNotifier, - mapKey: member, - loader: (ref) => - profilePictureNotifier.getProfilePicture(member.member.id), - loadingBuilder: (context) => const CircleAvatar( - radius: 20, - child: CircularProgressIndicator(), - ), - dataBuilder: (context, data) => - CircleAvatar(radius: 20, backgroundImage: data.first.image), - ), - ), - const SizedBox(width: 10), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - AutoSizeText( - "${(member.member.nickname ?? member.member.firstname)} - ${member.memberships.firstWhere((element) => element.associationId == association.id && element.mandateYear == association.mandateYear).apparentName}", - style: const TextStyle(fontWeight: FontWeight.bold), - minFontSize: 10, - maxFontSize: 15, - ), - const SizedBox(height: 3), - AutoSizeText( - member.member.nickname != null - ? "${member.member.firstname} ${member.member.name}" - : member.member.name, - minFontSize: 10, - maxFontSize: 15, - ), - ], - ), - ), - EditionButton( - deactivated: deactivated, - onEdition: () async { - roleTagsNotifier.resetChecked(); - roleTagsNotifier.loadRoleTagsFromMember(member, association); - completeMemberNotifier.setCompleteMember(member); - membershipNotifier.setMembership(assoMembership); - memberRoleTagsNotifier.reset(); - if (QR.currentPath.contains(PhonebookRouter.admin)) { - QR.to( - PhonebookRouter.root + - PhonebookRouter.admin + - PhonebookRouter.editAssociation + - PhonebookRouter.addEditMember, - ); - } else { - QR.to( - PhonebookRouter.root + - PhonebookRouter.associationDetail + - PhonebookRouter.editAssociation + - PhonebookRouter.addEditMember, - ); - } - }, - ), - const SizedBox(width: 10), - DeleteButton( - deactivated: deactivated, - deletion: true, - onDelete: () async { - final deletedMemberMsg = AppLocalizations.of( - context, - )!.phonebookDeletedMember; - final deletingErrorMsg = AppLocalizations.of( - context, - )!.phonebookDeletingError; - final result = await associationMemberListNotifier.deleteMember( - member, - member.memberships.firstWhere( - (element) => - element.associationId == association.id && - element.mandateYear == association.mandateYear, - ), - ); - if (result) { - displayToastWithContext(TypeMsg.msg, deletedMemberMsg); - } else { - displayToastWithContext(TypeMsg.error, deletingErrorMsg); - } - }, - ), - ], - ), - ); - } -} diff --git a/lib/phonebook/ui/pages/association_members_page/association_members_page.dart b/lib/phonebook/ui/pages/association_members_page/association_members_page.dart index e69de29bb2..3af30ef537 100644 --- a/lib/phonebook/ui/pages/association_members_page/association_members_page.dart +++ b/lib/phonebook/ui/pages/association_members_page/association_members_page.dart @@ -0,0 +1,168 @@ +import 'package:flutter/material.dart'; +import 'package:heroicons/heroicons.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; +import 'package:titan/phonebook/class/complete_member.dart'; +import 'package:titan/phonebook/class/membership.dart'; +import 'package:titan/phonebook/providers/association_member_list_provider.dart'; +import 'package:titan/phonebook/providers/association_member_sorted_list_provider.dart'; +import 'package:titan/phonebook/providers/association_provider.dart'; +import 'package:titan/phonebook/providers/complete_member_provider.dart'; +import 'package:titan/phonebook/providers/member_role_tags_provider.dart'; +import 'package:titan/phonebook/providers/membership_provider.dart'; +import 'package:titan/phonebook/providers/roles_tags_provider.dart'; +import 'package:titan/phonebook/router.dart'; +import 'package:titan/phonebook/ui/components/member_card.dart'; +import 'package:titan/tools/functions.dart'; +import 'package:titan/tools/token_expire_wrapper.dart'; +import 'package:titan/tools/ui/builders/async_child.dart'; +import 'package:titan/tools/ui/styleguide/list_item.dart'; + +class AssociationMembersPage extends HookConsumerWidget { + const AssociationMembersPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final association = ref.watch(associationProvider); + final associationMemberList = ref.watch(associationMemberListProvider); + final associationMemberListNotifier = ref.watch( + associationMemberListProvider.notifier, + ); + final rolesTagsNotifier = ref.watch(rolesTagsProvider.notifier); + final memberRoleTagsNotifier = ref.watch(memberRoleTagsProvider.notifier); + final completeMemberNotifier = ref.watch(completeMemberProvider.notifier); + final membershipNotifier = ref.watch(membershipProvider.notifier); + final associationMemberSortedList = ref.watch( + associationMemberSortedListProvider, + ); + + void displayToastWithContext(TypeMsg type, String msg) { + displayToast(context, type, msg); + } + + AppLocalizations localizeWithContext() { + return AppLocalizations.of(context)!; + } + + return Padding( + padding: EdgeInsets.symmetric(horizontal: 20), + child: Column( + children: [ + Text( + AppLocalizations.of(context)!.phonebookMembers, + textAlign: TextAlign.start, + style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), + ), + if (!association.deactivated) ...[ + SizedBox(height: 10), + ListItem( + icon: const HeroIcon( + HeroIcons.plus, + size: 40, + color: Colors.black, + ), + title: "Ajouter", + onTap: () async { + rolesTagsNotifier.resetChecked(); + memberRoleTagsNotifier.reset(); + completeMemberNotifier.setCompleteMember( + CompleteMember.empty(), + ); + membershipNotifier.setMembership( + Membership.empty().copyWith(associationId: association.id), + ); + if (QR.currentPath.contains(PhonebookRouter.admin)) { + QR.to( + PhonebookRouter.root + + PhonebookRouter.admin + + PhonebookRouter.addEditAssociation + + PhonebookRouter.addEditMember, + ); + } else { + QR.to( + PhonebookRouter.root + + PhonebookRouter.associationDetail + + PhonebookRouter.addEditAssociation + + PhonebookRouter.addEditMember, + ); + } + }, + ), + ], + AsyncChild( + value: associationMemberList, + builder: (context, associationMembers) => associationMembers.isEmpty + ? Text(AppLocalizations.of(context)!.phonebookNoMember) + : !association.deactivated + ? SizedBox( + height: MediaQuery.of(context).size.height * 0.7, + child: ReorderableListView( + proxyDecorator: (child, index, animation) { + return Material( + child: FadeTransition( + opacity: animation, + child: child, + ), + ); + }, + onReorder: (int oldIndex, int newIndex) async { + await tokenExpireWrapper(ref, () async { + final result = await associationMemberListNotifier + .reorderMember( + associationMemberSortedList[oldIndex], + associationMemberSortedList[oldIndex] + .memberships + .firstWhere( + (element) => + element.associationId == + association.id && + element.mandateYear == + association.mandateYear, + ) + .copyWith(order: newIndex), + oldIndex, + newIndex, + ); + if (result) { + displayToastWithContext( + TypeMsg.msg, + localizeWithContext().phonebookMemberReordered, + ); + } else { + displayToastWithContext( + TypeMsg.error, + localizeWithContext().phonebookReorderingError, + ); + } + }); + }, + children: associationMemberSortedList + .map( + (member) => MemberCard( + deactivated: false, + key: ValueKey(member.member.id), + member: member, + association: association, + ), + ) + .toList(), + ), + ) + : ListView.builder( + itemCount: associationMembers.length, + itemBuilder: (context, index) { + return MemberCard( + deactivated: true, + key: ValueKey(associationMembers[index].member.id), + member: associationMembers[index], + association: association, + ); + }, + ), + ), + ], + ), + ); + } +} diff --git a/lib/phonebook/ui/pages/association_members_page/member_edition_modal.dart b/lib/phonebook/ui/pages/association_members_page/member_edition_modal.dart new file mode 100644 index 0000000000..8de3c0527a --- /dev/null +++ b/lib/phonebook/ui/pages/association_members_page/member_edition_modal.dart @@ -0,0 +1,118 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; +import 'package:titan/phonebook/class/complete_member.dart'; +import 'package:titan/phonebook/class/membership.dart'; +import 'package:titan/phonebook/providers/association_member_list_provider.dart'; +import 'package:titan/phonebook/providers/association_provider.dart'; +import 'package:titan/phonebook/providers/complete_member_provider.dart'; +import 'package:titan/phonebook/providers/member_role_tags_provider.dart'; +import 'package:titan/phonebook/providers/membership_provider.dart'; +import 'package:titan/phonebook/providers/roles_tags_provider.dart'; +import 'package:titan/phonebook/router.dart'; +import 'package:titan/phonebook/tools/function.dart'; +import 'package:titan/tools/functions.dart'; +import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; +import 'package:titan/tools/ui/styleguide/button.dart'; +import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; + +class MemberEditionModal extends HookConsumerWidget { + final CompleteMember member; + final Membership membership; + const MemberEditionModal({ + super.key, + required this.member, + required this.membership, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final completeMemberNotifier = ref.watch(completeMemberProvider.notifier); + final associationMemberListNotifier = ref.watch( + associationMemberListProvider.notifier, + ); + final association = ref.watch(associationProvider); + final membershipNotifier = ref.watch(membershipProvider.notifier); + final roleTagsNotifier = ref.watch(rolesTagsProvider.notifier); + final memberRoleTagsNotifier = ref.watch(memberRoleTagsProvider.notifier); + + void displayToastWithContext(TypeMsg type, String msg) { + displayToast(context, type, msg); + } + + AppLocalizations localizeWithContext() { + return AppLocalizations.of(context)!; + } + + return BottomModalTemplate( + title: "title", + type: BottomModalType.main, + child: SingleChildScrollView( + child: Column( + children: [ + Button( + text: "Modifier le rôle", + onPressed: () { + roleTagsNotifier.resetChecked(); + roleTagsNotifier.loadRoleTagsFromMember(member, association); + completeMemberNotifier.setCompleteMember(member); + membershipNotifier.setMembership(membership); + memberRoleTagsNotifier.reset(); + if (QR.currentPath.contains(PhonebookRouter.admin)) { + QR.to( + PhonebookRouter.root + + PhonebookRouter.admin + + PhonebookRouter.addEditAssociation + + PhonebookRouter.addEditMember, + ); + } else { + QR.to( + PhonebookRouter.root + + PhonebookRouter.associationDetail + + PhonebookRouter.addEditAssociation + + PhonebookRouter.addEditMember, + ); + } + }, + ), + SizedBox(height: 5), + Button.danger( + text: "Supprimer le rôle", + onPressed: () { + Navigator.of(context).pop(); + showDialog( + context: context, + builder: (context) => CustomDialogBox( + title: + "Supprimer le rôle de ${member.member.nickname ?? '${member.member.firstname} ${member.member.name}'}", + descriptions: "Cette action est irréversible", + onYes: () async { + final result = await associationMemberListNotifier + .deleteMember( + member, + getMembershipForAssociation(member, association), + ); + if (result) { + displayToastWithContext( + TypeMsg.msg, + localizeWithContext().phonebookDeletedMember, + ); + } else { + displayToastWithContext( + TypeMsg.error, + localizeWithContext().phonebookDeletingError, + ); + } + }, + onNo: () => Navigator.of(context).pop(), + ), + ); + }, + ), + ], + ), + ), + ); + } +} diff --git a/lib/phonebook/ui/pages/association_page/association_page.dart b/lib/phonebook/ui/pages/association_page/association_page.dart index 55b688db0b..8a0552580b 100644 --- a/lib/phonebook/ui/pages/association_page/association_page.dart +++ b/lib/phonebook/ui/pages/association_page/association_page.dart @@ -9,7 +9,7 @@ import 'package:titan/phonebook/providers/association_provider.dart'; import 'package:titan/phonebook/providers/association_member_list_provider.dart'; import 'package:titan/phonebook/providers/phonebook_admin_provider.dart'; import 'package:titan/phonebook/router.dart'; -import 'package:titan/phonebook/ui/pages/association_page/member_card.dart'; +import 'package:titan/phonebook/ui/components/member_card.dart'; import 'package:titan/phonebook/ui/pages/association_page/web_member_card.dart'; import 'package:titan/phonebook/ui/phonebook.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; @@ -90,6 +90,7 @@ class AssociationPage extends HookConsumerWidget { : MemberCard( member: member, association: association, + deactivated: false, ), ) .toList(), @@ -106,7 +107,7 @@ class AssociationPage extends HookConsumerWidget { QR.to( PhonebookRouter.root + PhonebookRouter.associationDetail + - PhonebookRouter.editAssociation, + PhonebookRouter.addEditAssociation, ); }, child: Container( diff --git a/lib/phonebook/ui/pages/association_page/member_card.dart b/lib/phonebook/ui/pages/association_page/member_card.dart deleted file mode 100644 index 2c82674d78..0000000000 --- a/lib/phonebook/ui/pages/association_page/member_card.dart +++ /dev/null @@ -1,148 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/phonebook/class/association.dart'; -import 'package:titan/phonebook/class/complete_member.dart'; -import 'package:titan/phonebook/class/membership.dart'; -import 'package:titan/phonebook/providers/member_pictures_provider.dart'; -import 'package:titan/phonebook/providers/profile_picture_provider.dart'; -import 'package:titan/phonebook/router.dart'; -import 'package:titan/phonebook/providers/complete_member_provider.dart'; -import 'package:titan/phonebook/tools/function.dart'; -import 'package:titan/tools/ui/builders/auto_loader_child.dart'; -import 'package:titan/tools/ui/layouts/card_layout.dart'; -import 'package:qlevar_router/qlevar_router.dart'; -import 'package:titan/l10n/app_localizations.dart'; - -class MemberCard extends HookConsumerWidget { - const MemberCard({ - super.key, - required this.member, - required this.association, - }); - - final CompleteMember member; - final Association association; - - @override - Widget build(BuildContext context, WidgetRef ref) { - final memberNotifier = ref.watch(completeMemberProvider.notifier); - - final memberPictures = ref.watch( - memberPicturesProvider.select((value) => value[member]), - ); - final memberPicturesNotifier = ref.watch(memberPicturesProvider.notifier); - final profilePictureNotifier = ref.watch(profilePictureProvider.notifier); - - Membership? assoMembership = member.memberships.firstWhereOrNull( - (memberships) => - memberships.associationId == association.id && - memberships.mandateYear == association.mandateYear, - ); - - return GestureDetector( - onTap: () { - memberNotifier.setCompleteMember(member); - QR.to(PhonebookRouter.root + PhonebookRouter.memberDetail); - }, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 5), - child: CardLayout( - color: getColorFromTagList( - ref, - member.memberships - .firstWhere( - (element) => - element.associationId == association.id && - element.mandateYear == association.mandateYear, - ) - .rolesTags, - ), - margin: EdgeInsets.zero, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Container( - decoration: BoxDecoration( - shape: BoxShape.circle, - boxShadow: [ - BoxShadow( - color: Colors.black.withValues(alpha: 0.1), - spreadRadius: 5, - blurRadius: 10, - offset: const Offset(2, 3), - ), - ], - ), - child: AutoLoaderChild( - group: memberPictures, - notifier: memberPicturesNotifier, - mapKey: member, - loader: (ref) => profilePictureNotifier.getProfilePicture( - member.member.id, - ), - loadingBuilder: (context) => const CircleAvatar( - radius: 20, - child: CircularProgressIndicator(), - ), - dataBuilder: (context, data) => CircleAvatar( - radius: 20, - backgroundImage: data.first.image, - ), - ), - ), - const SizedBox(width: 10), - if ((member.member.nickname != null) && - (member.member.nickname != "")) ...[ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - member.member.nickname!, - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 16, - ), - ), - Text( - "(${member.member.name} ${member.member.firstname})", - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 16, - color: Color.fromARGB(255, 115, 115, 115), - ), - ), - ], - ), - ] else - Text( - "${member.member.name} ${member.member.firstname}", - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 16, - ), - ), - ], - ), - Flexible( - child: Text( - textAlign: TextAlign.right, - assoMembership == null - ? AppLocalizations.of(context)!.phonebookNoMemberRole - : assoMembership.apparentName, - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 16, - ), - ), - ), - ], - ), - ), - ), - ); - } -} diff --git a/lib/phonebook/ui/pages/association_page/web_member_card.dart b/lib/phonebook/ui/pages/association_page/web_member_card.dart index dc63ef763f..a1a08d64a7 100644 --- a/lib/phonebook/ui/pages/association_page/web_member_card.dart +++ b/lib/phonebook/ui/pages/association_page/web_member_card.dart @@ -1,4 +1,3 @@ -import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/phonebook/class/association.dart'; @@ -35,10 +34,9 @@ class WebMemberCard extends HookConsumerWidget { final memberPicturesNotifier = ref.watch(memberPicturesProvider.notifier); final profilePictureNotifier = ref.watch(profilePictureProvider.notifier); - Membership? assoMembership = member.memberships.firstWhereOrNull( - (memberships) => - memberships.associationId == association.id && - memberships.mandateYear == association.mandateYear, + Membership assoMembership = getMembershipForAssociation( + member, + association, ); return Padding( @@ -171,12 +169,8 @@ class WebMemberCard extends HookConsumerWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ Text( + assoMembership.apparentName, textAlign: TextAlign.right, - assoMembership == null - ? AppLocalizations.of( - context, - )!.phonebookNoMemberRole - : assoMembership.apparentName, style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 20, @@ -281,12 +275,8 @@ class WebMemberCard extends HookConsumerWidget { Column( children: [ Text( + assoMembership.apparentName, textAlign: TextAlign.right, - assoMembership == null - ? AppLocalizations.of( - context, - )!.phonebookNoMemberRole - : assoMembership.apparentName, style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 20, diff --git a/lib/phonebook/ui/phonebook.dart b/lib/phonebook/ui/phonebook.dart index 080215fdaf..7afcab2cc4 100644 --- a/lib/phonebook/ui/phonebook.dart +++ b/lib/phonebook/ui/phonebook.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:qlevar_router/qlevar_router.dart'; -import 'package:titan/phonebook/providers/association_kind_provider.dart'; +import 'package:titan/phonebook/providers/association_groupement_provider.dart'; import 'package:titan/phonebook/router.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/widgets/top_bar.dart'; @@ -12,7 +12,9 @@ class PhonebookTemplate extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final kindNotifier = ref.watch(associationKindProvider.notifier); + final associationGroupementNotifer = ref.watch( + associationGroupementProvider.notifier, + ); return Container( color: ColorConstants.background, child: SafeArea( @@ -24,17 +26,9 @@ class PhonebookTemplate extends HookConsumerWidget { if (QR.currentPath != PhonebookRouter.root + PhonebookRouter.admin + - PhonebookRouter.editAssociation + + PhonebookRouter.editAssociationMembers + PhonebookRouter.addEditMember) { - kindNotifier.setKind(''); - } - if (QR.currentPath == - PhonebookRouter.root + - PhonebookRouter.admin + - PhonebookRouter.editAssociation) { - QR.to( - PhonebookRouter.root + PhonebookRouter.admin, - ); // Used on back after adding an association + associationGroupementNotifer.resetAssociationGroupement(); } }, ), From 7f58621e811c267f5d514df61e17ee97b017e81d Mon Sep 17 00:00:00 2001 From: Thonyk Date: Wed, 30 Jul 2025 16:59:08 +0200 Subject: [PATCH 089/473] feat: association picture --- .../association_picture_provider.dart | 36 ++- lib/phonebook/ui/components/member_card.dart | 12 +- .../ui/pages/admin_page/admin_page.dart | 5 +- .../admin_page/association_edition_modal.dart | 250 ++++++++++-------- .../association_add_edit_page.dart | 214 ++++++++++++--- .../association_groups_page.dart | 244 ++++++++--------- .../association_members_page.dart | 10 +- .../ui/pages/main_page/association_card.dart | 1 + 8 files changed, 475 insertions(+), 297 deletions(-) diff --git a/lib/phonebook/providers/association_picture_provider.dart b/lib/phonebook/providers/association_picture_provider.dart index fbf4210735..b675507aeb 100644 --- a/lib/phonebook/providers/association_picture_provider.dart +++ b/lib/phonebook/providers/association_picture_provider.dart @@ -1,7 +1,6 @@ -import 'dart:typed_data'; - import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:image_picker/image_picker.dart'; import 'package:titan/phonebook/providers/associations_picture_map_provider.dart'; import 'package:titan/phonebook/repositories/association_picture_repository.dart'; import 'package:titan/tools/providers/single_notifier.dart'; @@ -9,6 +8,8 @@ import 'package:titan/tools/providers/single_notifier.dart'; class AssociationPictureProvider extends SingleNotifier { final AssociationPictureRepository associationPictureRepository; final AssociationPictureMapNotifier associationPictureMapNotifier; + final ImagePicker _picker = ImagePicker(); + AssociationPictureProvider({ required this.associationPictureRepository, required this.associationPictureMapNotifier, @@ -19,19 +20,36 @@ class AssociationPictureProvider extends SingleNotifier { associationId, ); associationPictureMapNotifier.setTData(associationId, AsyncData([image])); + state = AsyncData(image); return image; } - Future updateAssociationPicture( + Future setProfilePicture( + ImageSource source, String associationId, - Uint8List bytes, ) async { - final image = await associationPictureRepository.addAssociationPicture( - bytes, - associationId, + final previousState = state; + state = const AsyncLoading(); + final XFile? image = await _picker.pickImage( + source: source, + imageQuality: 20, ); - associationPictureMapNotifier.setTData(associationId, AsyncData([image])); - return image; + if (image != null) { + try { + final i = await associationPictureRepository.addAssociationPicture( + await image.readAsBytes(), + associationId, + ); + state = AsyncValue.data(i); + associationPictureMapNotifier.setTData(associationId, AsyncData([i])); + return true; + } catch (e) { + state = previousState; + return false; + } + } + state = previousState; + return null; } } diff --git a/lib/phonebook/ui/components/member_card.dart b/lib/phonebook/ui/components/member_card.dart index 63f9ef6138..cfe7e9bbbe 100644 --- a/lib/phonebook/ui/components/member_card.dart +++ b/lib/phonebook/ui/components/member_card.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/phonebook/class/association.dart'; @@ -10,9 +11,10 @@ import 'package:titan/phonebook/providers/profile_picture_provider.dart'; import 'package:titan/phonebook/router.dart'; import 'package:titan/phonebook/tools/function.dart'; import 'package:titan/phonebook/ui/pages/association_members_page/member_edition_modal.dart'; +import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/auto_loader_child.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; -import 'package:titan/tools/ui/styleguide/list_item.dart'; +import 'package:titan/tools/ui/styleguide/list_item_template.dart'; class MemberCard extends HookConsumerWidget { const MemberCard({ @@ -43,7 +45,7 @@ class MemberCard extends HookConsumerWidget { association, ); - return ListItem( + return ListItemTemplate( title: "${(member.member.nickname ?? '${member.member.firstname} ${member.member.name}')} - ${assoMembership.apparentName}", subtitle: member.member.nickname != null @@ -75,6 +77,12 @@ class MemberCard extends HookConsumerWidget { memberNotifier.setCompleteMember(member); QR.to(PhonebookRouter.root + PhonebookRouter.memberDetail); }, + trailing: !editable + ? const HeroIcon( + HeroIcons.chevronRight, + color: ColorConstants.tertiary, + ) + : SizedBox.shrink(), ); } } diff --git a/lib/phonebook/ui/pages/admin_page/admin_page.dart b/lib/phonebook/ui/pages/admin_page/admin_page.dart index e4329f5dc7..ca61fddd6d 100644 --- a/lib/phonebook/ui/pages/admin_page/admin_page.dart +++ b/lib/phonebook/ui/pages/admin_page/admin_page.dart @@ -14,7 +14,7 @@ import 'package:titan/phonebook/ui/pages/admin_page/editable_association_card.da import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; import 'package:qlevar_router/qlevar_router.dart'; -import 'package:titan/tools/ui/styleguide/list_item.dart'; +import 'package:titan/tools/ui/styleguide/list_item_template.dart'; import 'package:tuple/tuple.dart'; import 'package:titan/l10n/app_localizations.dart'; @@ -49,13 +49,14 @@ class AdminPage extends HookConsumerWidget { builder: (context, associations, associationGroupements) { return Column( children: [ - ListItem( + ListItemTemplate( title: "Ajouter une association", icon: HeroIcon( HeroIcons.plus, size: 40, color: Colors.grey.shade500, ), + trailing: SizedBox.shrink(), onTap: isPhonebookAdmin ? () { QR.to( diff --git a/lib/phonebook/ui/pages/admin_page/association_edition_modal.dart b/lib/phonebook/ui/pages/admin_page/association_edition_modal.dart index 395b2d8e0d..d479b75898 100644 --- a/lib/phonebook/ui/pages/admin_page/association_edition_modal.dart +++ b/lib/phonebook/ui/pages/admin_page/association_edition_modal.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/l10n/app_localizations.dart'; @@ -7,11 +6,13 @@ import 'package:titan/phonebook/class/association.dart'; import 'package:titan/phonebook/class/association_groupement.dart'; import 'package:titan/phonebook/providers/association_groupement_provider.dart'; import 'package:titan/phonebook/providers/association_list_provider.dart'; +import 'package:titan/phonebook/providers/association_picture_provider.dart'; import 'package:titan/phonebook/providers/association_provider.dart'; import 'package:titan/phonebook/router.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; import 'package:titan/tools/ui/styleguide/button.dart'; +import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; class AssociationEditionModal extends HookConsumerWidget { final Association association; @@ -28,12 +29,14 @@ class AssociationEditionModal extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - ValueNotifier isSuppresion = useState(false); final associationGroupementsNotifier = ref.watch( associationGroupementProvider.notifier, ); final associationNotifier = ref.watch(associationProvider.notifier); final associationListNotifier = ref.watch(associationListProvider.notifier); + final associationPictureNotifier = ref.watch( + associationPictureProvider.notifier, + ); void displayToastWithContext(TypeMsg type, String msg) { displayToast(context, type, msg); @@ -45,125 +48,142 @@ class AssociationEditionModal extends HookConsumerWidget { return BottomModalTemplate( title: "title", - type: isSuppresion.value ? BottomModalType.danger : BottomModalType.main, child: SingleChildScrollView( child: Column( - children: !isSuppresion.value - ? [ - Button( - text: "Modifier les informations", - onPressed: () { - associationGroupementsNotifier.setAssociationGroupement( - groupement, - ); - associationNotifier.setAssociation(association); - QR.to( - PhonebookRouter.root + - PhonebookRouter.admin + - PhonebookRouter.addEditAssociation, - ); - }, - ), - if (isAdmin) ...[ - SizedBox(height: 5), - Button( - text: "Gérer les groupes", - onPressed: () { - associationGroupementsNotifier.setAssociationGroupement( - groupement, + children: [ + Button( + text: "Modifier les informations", + onPressed: () { + associationPictureNotifier.getAssociationPicture( + association.id, + ); + associationGroupementsNotifier.setAssociationGroupement( + groupement, + ); + associationNotifier.setAssociation(association); + Navigator.of(context).pop(); + QR.to( + PhonebookRouter.root + + PhonebookRouter.admin + + PhonebookRouter.addEditAssociation, + ); + }, + ), + if (isAdmin) ...[ + SizedBox(height: 5), + Button( + text: "Gérer les groupes", + onPressed: () { + associationGroupementsNotifier.setAssociationGroupement( + groupement, + ); + associationNotifier.setAssociation(association); + Navigator.of(context).pop(); + QR.to( + PhonebookRouter.root + + PhonebookRouter.admin + + PhonebookRouter.editAssociationGroups, + ); + }, + ), + ], + SizedBox(height: 5), + Button( + text: "Gérer les membres", + onPressed: () { + associationGroupementsNotifier.setAssociationGroupement( + groupement, + ); + associationNotifier.setAssociation(association); + Navigator.of(context).pop(); + QR.to( + PhonebookRouter.root + + PhonebookRouter.admin + + PhonebookRouter.editAssociationMembers, + ); + }, + ), + SizedBox(height: 15), + Button.danger( + text: "Passer au mandat ${association.mandateYear + 1}", + onPressed: () { + Navigator.of(context).pop(); + showDialog( + context: context, + builder: (context) => CustomDialogBox( + title: "title", + descriptions: "descriptions", + onYes: () async { + final result = await associationListNotifier + .updateAssociation( + association.copyWith( + mandateYear: association.mandateYear + 1, + ), + ); + if (result) { + displayToastWithContext( + TypeMsg.msg, + localizeWithContext().phonebookUpdatedAssociation, ); - associationNotifier.setAssociation(association); - QR.to( - PhonebookRouter.root + - PhonebookRouter.admin + - PhonebookRouter.editAssociationGroups, + } else { + displayToastWithContext( + TypeMsg.error, + localizeWithContext().phonebookUpdatingError, ); - }, - ), - ], - SizedBox(height: 5), - Button( - text: "Gérer les membres", - onPressed: () { - associationGroupementsNotifier.setAssociationGroupement( - groupement, - ); - associationNotifier.setAssociation(association); - QR.to( - PhonebookRouter.root + - PhonebookRouter.admin + - PhonebookRouter.editAssociationMembers, - ); - }, - ), - SizedBox(height: 15), - Button.danger( - text: "Passer au mandat ${association.mandateYear + 1}", - onPressed: () { - return null; + } }, ), - SizedBox(height: 5), - ...association.deactivated - ? [ - Button.danger( - text: "Réactiver l'association", - onPressed: () {}, - ), - SizedBox(height: 5), - ] - : [SizedBox.shrink()], - Button.danger( - text: association.deactivated - ? "Supprimer l'association" - : "Désactiver l'association", - onPressed: () => isSuppresion.value = true, - ), - ] - : [ - Button.danger( - text: AppLocalizations.of(context)!.phonebookConfirm, - onPressed: association.deactivated - ? () async { - final result = await associationListNotifier - .deleteAssociation(association); - if (result) { - displayToastWithContext( - TypeMsg.msg, - localizeWithContext() - .phonebookDeletedAssociation, - ); - } else { - displayToastWithContext( - TypeMsg.error, - localizeWithContext().phonebookDeletingError, - ); - } - } - : () async { - final result = await associationListNotifier - .deactivateAssociation(association); - if (result) { - displayToastWithContext( - TypeMsg.msg, - localizeWithContext() - .phonebookDeactivatedAssociation, - ); - } else { - displayToastWithContext( - TypeMsg.error, - localizeWithContext() - .phonebookDeactivatingError, - ); - } - }, - ), - SizedBox(height: 5), - Button( - text: localizeWithContext().phonebookCancel, - onPressed: () => isSuppresion.value = false, - ), - ], + ); + }, + ), + SizedBox(height: 5), + Button.danger( + text: association.deactivated + ? "Supprimer l'association" + : "Désactiver l'association", + onPressed: () async { + Navigator.of(context).pop(); + if (!association.deactivated) { + final result = await associationListNotifier + .deactivateAssociation(association); + if (result) { + displayToastWithContext( + TypeMsg.msg, + localizeWithContext().phonebookDeactivatedAssociation, + ); + } else { + displayToastWithContext( + TypeMsg.error, + localizeWithContext().phonebookDeactivatingError, + ); + } + } else { + showDialog( + context: context, + builder: (context) => CustomDialogBox( + title: "title", + descriptions: "descriptions", + onYes: () async { + final result = await associationListNotifier + .deleteAssociation(association); + if (result) { + displayToastWithContext( + TypeMsg.msg, + localizeWithContext().phonebookDeletedAssociation, + ); + } else { + displayToastWithContext( + TypeMsg.error, + localizeWithContext().phonebookDeletingError, + ); + } + }, + ), + ); + } + }, + ), + ], ), ), ); diff --git a/lib/phonebook/ui/pages/association_add_edit_page/association_add_edit_page.dart b/lib/phonebook/ui/pages/association_add_edit_page/association_add_edit_page.dart index 81ddc36aab..da8bab5e92 100644 --- a/lib/phonebook/ui/pages/association_add_edit_page/association_add_edit_page.dart +++ b/lib/phonebook/ui/pages/association_add_edit_page/association_add_edit_page.dart @@ -1,16 +1,21 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:image_picker/image_picker.dart'; import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/phonebook/providers/association_groupement_provider.dart'; import 'package:titan/phonebook/providers/association_list_provider.dart'; +import 'package:titan/phonebook/providers/association_picture_provider.dart'; import 'package:titan/phonebook/providers/association_provider.dart'; import 'package:titan/phonebook/router.dart'; import 'package:titan/phonebook/ui/components/groupement_bar.dart'; import 'package:titan/phonebook/ui/phonebook.dart'; +import 'package:titan/settings/ui/pages/edit_user_page/picture_button.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/styleguide/button.dart'; import 'package:titan/tools/ui/styleguide/text_entry.dart'; @@ -25,6 +30,10 @@ class AssociationAddEditPage extends HookConsumerWidget { final associations = ref.watch(associationListProvider); final association = ref.watch(associationProvider); final associationNotifier = ref.watch(associationProvider.notifier); + final associationPicture = ref.watch(associationPictureProvider); + final associationPictureNotifier = ref.watch( + associationPictureProvider.notifier, + ); final associationGroupement = ref.watch(associationGroupementProvider); final name = useTextEditingController(text: association.name); final description = useTextEditingController(text: association.description); @@ -35,6 +44,10 @@ class AssociationAddEditPage extends HookConsumerWidget { ).showSnackBar(SnackBar(content: Text(message))); } + AppLocalizations localizeWithContext() { + return AppLocalizations.of(context)!; + } + return PhonebookTemplate( child: SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics( @@ -60,6 +73,124 @@ class AssociationAddEditPage extends HookConsumerWidget { ), ), ), + if (association.id != "") ...[ + const SizedBox(height: 30), + AsyncChild( + value: associationPicture, + builder: (context, image) { + return Center( + child: Stack( + clipBehavior: Clip.none, + children: [ + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.1), + spreadRadius: 5, + blurRadius: 10, + offset: const Offset(2, 3), + ), + ], + ), + child: CircleAvatar( + radius: 80, + backgroundImage: image.image, + ), + ), + Positioned( + bottom: 0, + left: 0, + child: GestureDetector( + onTap: () async { + final updatedProfilePictureMsg = + AppLocalizations.of( + context, + )!.settingsUpdatedProfilePicture; + final tooHeavyProfilePictureMsg = + AppLocalizations.of( + context, + )!.settingsTooHeavyProfilePicture; + final profilePictureErrorMsg = + AppLocalizations.of( + context, + )!.settingsErrorProfilePicture; + final value = await associationPictureNotifier + .setProfilePicture( + ImageSource.gallery, + association.id, + ); + if (value != null) { + if (value) { + showSnackBarWithContext( + updatedProfilePictureMsg, + ); + } else { + showSnackBarWithContext( + tooHeavyProfilePictureMsg, + ); + } + } else { + showSnackBarWithContext( + profilePictureErrorMsg, + ); + } + }, + child: const PictureButton( + icon: HeroIcons.photo, + ), + ), + ), + Positioned( + bottom: 0, + right: 0, + child: GestureDetector( + onTap: () async { + final updatedProfilePictureMsg = + AppLocalizations.of( + context, + )!.settingsUpdatedProfilePicture; + final tooHeavyProfilePictureMsg = + AppLocalizations.of( + context, + )!.settingsTooHeavyProfilePicture; + final profilePictureErrorMsg = + AppLocalizations.of( + context, + )!.settingsErrorProfilePicture; + final value = await associationPictureNotifier + .setProfilePicture( + ImageSource.camera, + association.id, + ); + if (value != null) { + if (value) { + showSnackBarWithContext( + updatedProfilePictureMsg, + ); + } else { + showSnackBarWithContext( + tooHeavyProfilePictureMsg, + ); + } + } else { + showSnackBarWithContext( + profilePictureErrorMsg, + ); + } + }, + child: const PictureButton( + icon: HeroIcons.camera, + ), + ), + ), + ], + ), + ); + }, + ), + ], const SizedBox(height: 30), AssociationGroupementBar(editable: true), Container(margin: const EdgeInsets.symmetric(vertical: 10)), @@ -90,41 +221,60 @@ class AssociationAddEditPage extends HookConsumerWidget { return; } await tokenExpireWrapper(ref, () async { - final value = await associationListNotifier - .createAssociation( - association.copyWith( - name: name.text, - description: description.text, - groupementId: associationGroupement.id, - mandateYear: DateTime.now().year, + if (association.id == '') { + final value = await associationListNotifier + .createAssociation( + association.copyWith( + name: name.text, + description: description.text, + groupementId: associationGroupement.id, + mandateYear: DateTime.now().year, + ), + ); + if (value) { + showSnackBarWithContext( + localizeWithContext().phonebookAddedAssociation, + ); + associations.when( + data: (d) { + associationNotifier.setAssociation(d.last); + QR.to( + PhonebookRouter.root + + PhonebookRouter.admin + + PhonebookRouter.addEditAssociation, + ); + }, + error: (e, s) => showSnackBarWithContext( + AppLocalizations.of( + context, + )!.phonebookErrorAssociationLoading, ), + loading: () {}, ); - if (value) { - showSnackBarWithContext( - AppLocalizations.of( - context, - )!.phonebookAddedAssociation, - ); - associations.when( - data: (d) { - associationNotifier.setAssociation(d.last); - QR.to( - PhonebookRouter.root + - PhonebookRouter.admin + - PhonebookRouter.addEditAssociation, - ); - }, - error: (e, s) => showSnackBarWithContext( - AppLocalizations.of( - context, - )!.phonebookErrorAssociationLoading, - ), - loading: () {}, - ); + } else { + showSnackBarWithContext( + localizeWithContext().phonebookAddingError, + ); + } } else { - showSnackBarWithContext( - AppLocalizations.of(context)!.adminAddingError, - ); + final value = await associationListNotifier + .updateAssociation( + association.copyWith( + name: name.text, + description: description.text, + groupementId: associationGroupement.id, + ), + ); + if (value) { + showSnackBarWithContext( + localizeWithContext().phonebookUpdatedAssociation, + ); + QR.to(PhonebookRouter.root + PhonebookRouter.admin); + } else { + showSnackBarWithContext( + localizeWithContext().phonebookUpdatingError, + ); + } } }); }, diff --git a/lib/phonebook/ui/pages/association_groups_page/association_groups_page.dart b/lib/phonebook/ui/pages/association_groups_page/association_groups_page.dart index acbe55911a..9d4f5d8390 100644 --- a/lib/phonebook/ui/pages/association_groups_page/association_groups_page.dart +++ b/lib/phonebook/ui/pages/association_groups_page/association_groups_page.dart @@ -3,8 +3,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/class/simple_group.dart'; import 'package:titan/admin/providers/group_list_provider.dart'; import 'package:titan/phonebook/providers/association_list_provider.dart'; -import 'package:titan/phonebook/providers/association_member_list_provider.dart'; -import 'package:titan/phonebook/providers/association_picture_provider.dart'; import 'package:titan/phonebook/providers/association_provider.dart'; import 'package:titan/phonebook/ui/phonebook.dart'; import 'package:titan/tools/constants.dart'; @@ -12,7 +10,6 @@ import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; import 'package:titan/tools/ui/layouts/add_edit_button_layout.dart'; -import 'package:titan/tools/ui/layouts/refresher.dart'; import 'package:titan/l10n/app_localizations.dart'; class AssociationGroupsPage extends HookConsumerWidget { @@ -22,12 +19,6 @@ class AssociationGroupsPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final association = ref.watch(associationProvider); - final associationMemberListNotifier = ref.watch( - associationMemberListProvider.notifier, - ); - final associationPictureNotifier = ref.watch( - associationPictureProvider.notifier, - ); final associationListNotifier = ref.watch(associationListProvider.notifier); final groups = ref.watch(allGroupListProvider); @@ -47,137 +38,124 @@ class AssociationGroupsPage extends HookConsumerWidget { } return PhonebookTemplate( - child: Refresher( - onRefresh: () async { - await associationMemberListNotifier.loadMembers( - association.id, - association.mandateYear.toString(), - ); - await associationPictureNotifier.getAssociationPicture( - association.id, - ); - }, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 30), - child: Column( - children: [ - Container( - margin: const EdgeInsets.symmetric(vertical: 10), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - child: ExpansionTile( - title: Text( - AppLocalizations.of(context)!.phonebookGroups, - ), - children: groups.maybeWhen( - data: (data) { - return data.map((group) { - return Container( - padding: const EdgeInsets.symmetric( - vertical: 10, - horizontal: 20, - ), - decoration: BoxDecoration( - color: Colors.white, - boxShadow: [ - BoxShadow( - color: Colors.black.withValues( - alpha: 0.1, - ), - offset: const Offset(0, 1), - blurRadius: 4, - spreadRadius: 2, - ), - ], - ), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - child: Text( - group.name, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - ), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 30), + child: Column( + children: [ + Container( + margin: const EdgeInsets.symmetric(vertical: 10), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + child: ExpansionTile( + title: Text( + AppLocalizations.of(context)!.phonebookGroups, + ), + children: groups.maybeWhen( + data: (data) { + return data.map((group) { + return Container( + padding: const EdgeInsets.symmetric( + vertical: 10, + horizontal: 20, + ), + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.1), + offset: const Offset(0, 1), + blurRadius: 4, + spreadRadius: 2, + ), + ], + ), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + child: Text( + group.name, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, ), ), - StatefulBuilder( - builder: (context, setState) { - return Checkbox( - value: selectedGroups.contains(group), - onChanged: (value) { - if (value == true) { - selectedGroups.add(group); - } else { - selectedGroups.remove(group); - } - setState(() {}); - }, - ); - }, - ), - ], - ), - ); - }).toList(); - }, - orElse: () { - return []; - }, - ), + ), + StatefulBuilder( + builder: (context, setState) { + return Checkbox( + value: selectedGroups.contains(group), + onChanged: (value) { + if (value == true) { + selectedGroups.add(group); + } else { + selectedGroups.remove(group); + } + setState(() {}); + }, + ); + }, + ), + ], + ), + ); + }).toList(); + }, + orElse: () { + return []; + }, ), ), - ], - ), - ), - WaitingButton( - builder: (child) => AddEditButtonLayout( - colors: const [ - ColorConstants.gradient1, - ColorConstants.gradient2, - ], - child: child, - ), - onTap: () async { - await tokenExpireWrapper(ref, () async { - final updatedGroupsMsg = AppLocalizations.of( - context, - )!.phonebookUpdatedGroups; - final updatingErrorMsg = AppLocalizations.of( - context, - )!.phonebookUpdatingError; - final value = await associationListNotifier - .updateAssociationGroups( - association.copyWith( - associatedGroups: selectedGroups - .map((e) => e.id) - .toList(), - ), - ); - if (value) { - displayToastWithContext(TypeMsg.msg, updatedGroupsMsg); - } else { - displayToastWithContext(TypeMsg.msg, updatingErrorMsg); - } - }); - }, - child: Text( - AppLocalizations.of(context)!.phonebookUpdateGroups, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - color: Color.fromARGB(255, 255, 255, 255), ), + ], + ), + ), + WaitingButton( + builder: (child) => AddEditButtonLayout( + colors: const [ + ColorConstants.gradient1, + ColorConstants.gradient2, + ], + child: child, + ), + onTap: () async { + await tokenExpireWrapper(ref, () async { + final updatedGroupsMsg = AppLocalizations.of( + context, + )!.phonebookUpdatedGroups; + final updatingErrorMsg = AppLocalizations.of( + context, + )!.phonebookUpdatingError; + final value = await associationListNotifier + .updateAssociationGroups( + association.copyWith( + associatedGroups: selectedGroups + .map((e) => e.id) + .toList(), + ), + ); + if (value) { + displayToastWithContext(TypeMsg.msg, updatedGroupsMsg); + } else { + displayToastWithContext(TypeMsg.msg, updatingErrorMsg); + } + }); + }, + child: Text( + AppLocalizations.of(context)!.phonebookUpdateGroups, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + color: Color.fromARGB(255, 255, 255, 255), ), ), - ], - ), + ), + ], ), ), ); diff --git a/lib/phonebook/ui/pages/association_members_page/association_members_page.dart b/lib/phonebook/ui/pages/association_members_page/association_members_page.dart index 3af30ef537..7305ff8178 100644 --- a/lib/phonebook/ui/pages/association_members_page/association_members_page.dart +++ b/lib/phonebook/ui/pages/association_members_page/association_members_page.dart @@ -17,7 +17,7 @@ import 'package:titan/phonebook/ui/components/member_card.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; -import 'package:titan/tools/ui/styleguide/list_item.dart'; +import 'package:titan/tools/ui/styleguide/list_item_template.dart'; class AssociationMembersPage extends HookConsumerWidget { const AssociationMembersPage({super.key}); @@ -56,13 +56,14 @@ class AssociationMembersPage extends HookConsumerWidget { ), if (!association.deactivated) ...[ SizedBox(height: 10), - ListItem( + ListItemTemplate( icon: const HeroIcon( HeroIcons.plus, size: 40, color: Colors.black, ), title: "Ajouter", + trailing: SizedBox.shrink(), onTap: () async { rolesTagsNotifier.resetChecked(); memberRoleTagsNotifier.reset(); @@ -95,8 +96,7 @@ class AssociationMembersPage extends HookConsumerWidget { builder: (context, associationMembers) => associationMembers.isEmpty ? Text(AppLocalizations.of(context)!.phonebookNoMember) : !association.deactivated - ? SizedBox( - height: MediaQuery.of(context).size.height * 0.7, + ? Expanded( child: ReorderableListView( proxyDecorator: (child, index, animation) { return Material( @@ -144,6 +144,7 @@ class AssociationMembersPage extends HookConsumerWidget { key: ValueKey(member.member.id), member: member, association: association, + editable: true, ), ) .toList(), @@ -157,6 +158,7 @@ class AssociationMembersPage extends HookConsumerWidget { key: ValueKey(associationMembers[index].member.id), member: associationMembers[index], association: association, + editable: true, ); }, ), diff --git a/lib/phonebook/ui/pages/main_page/association_card.dart b/lib/phonebook/ui/pages/main_page/association_card.dart index a0ce8d9508..2552f8ca5d 100644 --- a/lib/phonebook/ui/pages/main_page/association_card.dart +++ b/lib/phonebook/ui/pages/main_page/association_card.dart @@ -52,6 +52,7 @@ class AssociationCard extends HookConsumerWidget { icon: CircleAvatar(child: Image(image: data.first.image)), onTap: () { associationNotifier.setAssociation(association); + associationPictureNotifier.getAssociationPicture(association.id); associationGroupementNotifier.setAssociationGroupement( groupement, ); From c7b48c79717cd7e165cc9f99b3a7a2c22548b15b Mon Sep 17 00:00:00 2001 From: Thonyk Date: Mon, 4 Aug 2025 11:31:36 +0200 Subject: [PATCH 090/473] feat: global UI refacto --- assets/images/vache.png | Bin 0 -> 25964 bytes lib/l10n/app_en.arb | 37 +- lib/l10n/app_fr.arb | 37 +- lib/l10n/app_localizations.dart | 66 +++- lib/l10n/app_localizations_en.dart | 37 +- lib/l10n/app_localizations_fr.dart | 37 +- .../association_filtered_list_provider.dart | 3 +- .../association_member_list_provider.dart | 7 +- .../providers/association_provider.dart | 4 + .../providers/member_role_tags_provider.dart | 26 -- .../providers/roles_tags_provider.dart | 35 +- .../association_member_repository.dart | 2 +- .../association_picture_repository.dart | 3 +- .../repositories/role_tags_repository.dart | 6 +- lib/phonebook/router.dart | 34 +- lib/phonebook/tools/function.dart | 34 +- lib/phonebook/ui/components/member_card.dart | 79 ++-- .../groupement_add_edit_page.dart | 28 +- .../ui/pages/admin_page/admin_page.dart | 17 +- ...t => association_admin_edition_modal.dart} | 42 +-- .../admin_page/editable_association_card.dart | 64 ++-- .../association_add_edit_page.dart | 83 ++--- .../association_groups_page.dart | 211 +++++------ .../association_members_page.dart | 248 ++++++------ .../member_edition_modal.dart | 28 +- .../association_edition_modal.dart | 79 ++++ .../association_page/association_page.dart | 90 ++--- .../association_page/web_member_card.dart | 52 +-- .../ui/pages/main_page/association_card.dart | 52 ++- .../ui/pages/main_page/main_page.dart | 43 +-- .../member_detail_page.dart | 185 +++++---- .../member_detail_page/membership_card.dart | 59 +-- .../membership_editor_page.dart | 352 ++++++++---------- .../membership_editor_page/search_result.dart | 38 +- .../user_search_modal.dart | 42 +++ lib/tools/ui/styleguide/button.dart | 7 +- .../ui/styleguide/custom_dialog_box.dart | 141 +++++++ pubspec.yaml | 1 + 38 files changed, 1318 insertions(+), 991 deletions(-) create mode 100644 assets/images/vache.png delete mode 100644 lib/phonebook/providers/member_role_tags_provider.dart rename lib/phonebook/ui/pages/admin_page/{association_edition_modal.dart => association_admin_edition_modal.dart} (83%) create mode 100644 lib/phonebook/ui/pages/association_page/association_edition_modal.dart create mode 100644 lib/phonebook/ui/pages/membership_editor_page/user_search_modal.dart create mode 100644 lib/tools/ui/styleguide/custom_dialog_box.dart diff --git a/assets/images/vache.png b/assets/images/vache.png new file mode 100644 index 0000000000000000000000000000000000000000..9c70f81a95be84f4f9235769a803125b35606678 GIT binary patch literal 25964 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 literal 0 HcmV?d00001 diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 3498729298..7536b9f05c 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -447,6 +447,8 @@ "eventDayFri": "Friday", "eventDaySat": "Saturday", "eventDaySun": "Sunday", + "globalConfirm": "Confirm", + "globalCancel": "Cancel", "homeCalendar": "Calendar", "homeEventOf": "Events of", "homeIncomingEvents": "Upcoming events", @@ -669,6 +671,9 @@ "phonebookDescription": "Description", "phonebookEdit": "Edit", "phonebookEditAssociationGroupement": "Edit association groupement", + "phonebookEditAssociationGroups": "Manage groups", + "phonebookEditAssociationInfo": "Edit", + "phonebookEditAssociationMembers": "Manage members", "phonebookEditMembership": "Edit role", "phonebookEmail": "Email:", "phonebookEmailCopied": "Email copied to clipboard", @@ -687,14 +692,40 @@ "phonebookErrorRoleTagsLoading": "Error loading role tags", "phonebookExistingMembership": "This member is already in the current mandate", "phonebookFirstname": "First name:", - "phonebookGroups": "Associated groups:", + "phonebookGroupementName": "Groupement name", + "phonebookGroups": "Manage {association} groups", + "@phonebookGroups": { + "description": "Manage the groups of an association", + "placeholders": { + "association": { + "type": "String" + } + } + }, "phonebookMandateChangingError": "Error changing mandate", "phonebookMember": "Member", "phonebookMemberReordered": "Member reordered", - "phonebookMembers": "Members", + "phonebookMembers": "Manage {association} members", + "@phonebookMembers": { + "description": "Manage the members of an association", + "placeholders": { + "association": { + "type": "String" + } + } + }, "phonebookMembershipAssociationError": "Please choose an association", "phonebookMembershipRole": "Role:", "phonebookMembershipRoleError": "Please choose a role", + "phonebookModifyMembership": "Modify {name}'s role", + "@phonebookModifyMembership": { + "description": "Modify the role of a member", + "placeholders": { + "name": { + "type": "String" + } + } + }, "phonebookName": "Last name:", "phonebookNameCopied": "Name and first name copied to clipboard", "phonebookNamePure": "Last name", @@ -705,6 +736,7 @@ "phonebookNoAssociationFound": "No association found", "phonebookNoMember": "No member", "phonebookNoMemberRole": "No role found", + "phonebookNoRoleTags": "No role tags found", "phonebookPhone": "Phone:", "phonebookPhonebook": "Phonebook", "phonebookPhonebookSearch": "Search", @@ -718,6 +750,7 @@ "phonebookReorderingError": "Error during reordering", "phonebookResearch": "Search", "phonebookRolePure": "Role", + "phonebookSearchMember": "Search a member", "phonebookTooHeavyAssociationPicture": "Image is too large (max 4MB)", "phonebookUpdateGroups": "Update groups", "phonebookUpdatedAssociation": "Association updated", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index f09c082c0d..551ea902bb 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -447,6 +447,8 @@ "eventDayFri": "Vendredi", "eventDaySat": "Samedi", "eventDaySun": "Dimanche", + "globalConfirm": "Confirmer", + "globalCancel": "Annuler", "homeCalendar": "Calendrier", "homeEventOf": "Évènements du", "homeIncomingEvents": "Évènements à venir", @@ -669,6 +671,9 @@ "phonebookDescription": "Description", "phonebookEdit": "Modifier", "phonebookEditAssociationGroupement": "Modifier le groupement d'association", + "phonebookEditAssociationGroups": "Gérer les groupes", + "phonebookEditAssociationInfo": "Modifier", + "phonebookEditAssociationMembers": "Gérer les membres", "phonebookEditMembership": "Modifier le rôle", "phonebookEmail": "Email :", "phonebookEmailCopied": "Email copié dans le presse-papier", @@ -687,14 +692,40 @@ "phonebookErrorRoleTagsLoading": "Erreur lors du chargement des tags de rôle", "phonebookExistingMembership": "Ce membre est déjà dans le mandat actuel", "phonebookFirstname": "Prénom :", - "phonebookGroups": "Groupes associés :", + "phonebookGroupementName": "Nom du groupement", + "phonebookGroups": "Gérer les groupes de {association}", + "@phonebookGroups": { + "description": "Permet de gérer les groupes d'une association", + "placeholders": { + "association": { + "type": "String" + } + } + }, "phonebookMandateChangingError": "Erreur lors du changement de mandat", "phonebookMember": "Membre", "phonebookMemberReordered": "Membre réordonné", - "phonebookMembers": "Membres", + "phonebookMembers": "Gérer les membres de {association}", + "@phonebookMembers": { + "description": "Permet de gérer les membres d'une association", + "placeholders": { + "association": { + "type": "String" + } + } + }, "phonebookMembershipAssociationError": "Veuillez choisir une association", "phonebookMembershipRole": "Rôle :", "phonebookMembershipRoleError": "Veuillez choisir un rôle", + "phonebookModifyMembership": "Modifier le rôle de {name}", + "@phonebookModifyMembership": { + "description": "Permet de modifier le rôle d'un membre dans une association", + "placeholders": { + "name": { + "type": "String" + } + } + }, "phonebookName": "Nom :", "phonebookNameCopied": "Nom et prénom copié dans le presse-papier", "phonebookNamePure": "Nom", @@ -705,6 +736,7 @@ "phonebookNoAssociationFound": "Aucune association trouvée", "phonebookNoMember": "Aucun membre", "phonebookNoMemberRole": "Aucun role trouvé", + "phonebookNoRoleTags": "Aucun tag de rôle trouvé", "phonebookPhone": "Téléphone :", "phonebookPhonebook": "Annuaire", "phonebookPhonebookSearch": "Rechercher", @@ -718,6 +750,7 @@ "phonebookReorderingError": "Erreur lors du réordonnement", "phonebookResearch": "Rechercher", "phonebookRolePure": "Rôle", + "phonebookSearchMember": "un membre", "phonebookTooHeavyAssociationPicture": "L'image est trop lourde (max 4Mo)", "phonebookUpdateGroups": "Mettre à jour les groupes", "phonebookUpdatedAssociation": "Association modifiée", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 95225c22d0..7a5396de1a 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -2780,6 +2780,18 @@ abstract class AppLocalizations { /// **'Dimanche'** String get eventDaySun; + /// No description provided for @globalConfirm. + /// + /// In fr, this message translates to: + /// **'Confirmer'** + String get globalConfirm; + + /// No description provided for @globalCancel. + /// + /// In fr, this message translates to: + /// **'Annuler'** + String get globalCancel; + /// No description provided for @homeCalendar. /// /// In fr, this message translates to: @@ -4112,6 +4124,24 @@ abstract class AppLocalizations { /// **'Modifier le groupement d\'association'** String get phonebookEditAssociationGroupement; + /// No description provided for @phonebookEditAssociationGroups. + /// + /// In fr, this message translates to: + /// **'Gérer les groupes'** + String get phonebookEditAssociationGroups; + + /// No description provided for @phonebookEditAssociationInfo. + /// + /// In fr, this message translates to: + /// **'Modifier'** + String get phonebookEditAssociationInfo; + + /// No description provided for @phonebookEditAssociationMembers. + /// + /// In fr, this message translates to: + /// **'Gérer les membres'** + String get phonebookEditAssociationMembers; + /// No description provided for @phonebookEditMembership. /// /// In fr, this message translates to: @@ -4220,11 +4250,17 @@ abstract class AppLocalizations { /// **'Prénom :'** String get phonebookFirstname; - /// No description provided for @phonebookGroups. + /// No description provided for @phonebookGroupementName. + /// + /// In fr, this message translates to: + /// **'Nom du groupement'** + String get phonebookGroupementName; + + /// Permet de gérer les groupes d'une association /// /// In fr, this message translates to: - /// **'Groupes associés :'** - String get phonebookGroups; + /// **'Gérer les groupes de {association}'** + String phonebookGroups(String association); /// No description provided for @phonebookMandateChangingError. /// @@ -4244,11 +4280,11 @@ abstract class AppLocalizations { /// **'Membre réordonné'** String get phonebookMemberReordered; - /// No description provided for @phonebookMembers. + /// Permet de gérer les membres d'une association /// /// In fr, this message translates to: - /// **'Membres'** - String get phonebookMembers; + /// **'Gérer les membres de {association}'** + String phonebookMembers(String association); /// No description provided for @phonebookMembershipAssociationError. /// @@ -4268,6 +4304,12 @@ abstract class AppLocalizations { /// **'Veuillez choisir un rôle'** String get phonebookMembershipRoleError; + /// Permet de modifier le rôle d'un membre dans une association + /// + /// In fr, this message translates to: + /// **'Modifier le rôle de {name}'** + String phonebookModifyMembership(String name); + /// No description provided for @phonebookName. /// /// In fr, this message translates to: @@ -4328,6 +4370,12 @@ abstract class AppLocalizations { /// **'Aucun role trouvé'** String get phonebookNoMemberRole; + /// No description provided for @phonebookNoRoleTags. + /// + /// In fr, this message translates to: + /// **'Aucun tag de rôle trouvé'** + String get phonebookNoRoleTags; + /// No description provided for @phonebookPhone. /// /// In fr, this message translates to: @@ -4406,6 +4454,12 @@ abstract class AppLocalizations { /// **'Rôle'** String get phonebookRolePure; + /// No description provided for @phonebookSearchMember. + /// + /// In fr, this message translates to: + /// **'un membre'** + String get phonebookSearchMember; + /// No description provided for @phonebookTooHeavyAssociationPicture. /// /// In fr, this message translates to: diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 23d042d9e3..2b51a5cd48 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -1364,6 +1364,12 @@ class AppLocalizationsEn extends AppLocalizations { @override String get eventDaySun => 'Sunday'; + @override + String get globalConfirm => 'Confirm'; + + @override + String get globalCancel => 'Cancel'; + @override String get homeCalendar => 'Calendar'; @@ -2046,6 +2052,15 @@ class AppLocalizationsEn extends AppLocalizations { String get phonebookEditAssociationGroupement => 'Edit association groupement'; + @override + String get phonebookEditAssociationGroups => 'Manage groups'; + + @override + String get phonebookEditAssociationInfo => 'Edit'; + + @override + String get phonebookEditAssociationMembers => 'Manage members'; + @override String get phonebookEditMembership => 'Edit role'; @@ -2107,7 +2122,12 @@ class AppLocalizationsEn extends AppLocalizations { String get phonebookFirstname => 'First name:'; @override - String get phonebookGroups => 'Associated groups:'; + String get phonebookGroupementName => 'Groupement name'; + + @override + String phonebookGroups(String association) { + return 'Manage $association groups'; + } @override String get phonebookMandateChangingError => 'Error changing mandate'; @@ -2119,7 +2139,9 @@ class AppLocalizationsEn extends AppLocalizations { String get phonebookMemberReordered => 'Member reordered'; @override - String get phonebookMembers => 'Members'; + String phonebookMembers(String association) { + return 'Manage $association members'; + } @override String get phonebookMembershipAssociationError => @@ -2131,6 +2153,11 @@ class AppLocalizationsEn extends AppLocalizations { @override String get phonebookMembershipRoleError => 'Please choose a role'; + @override + String phonebookModifyMembership(String name) { + return 'Modify $name\'s role'; + } + @override String get phonebookName => 'Last name:'; @@ -2161,6 +2188,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get phonebookNoMemberRole => 'No role found'; + @override + String get phonebookNoRoleTags => 'No role tags found'; + @override String get phonebookPhone => 'Phone:'; @@ -2200,6 +2230,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get phonebookRolePure => 'Role'; + @override + String get phonebookSearchMember => 'Search a member'; + @override String get phonebookTooHeavyAssociationPicture => 'Image is too large (max 4MB)'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 044794ed38..21b08a9fec 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -1370,6 +1370,12 @@ class AppLocalizationsFr extends AppLocalizations { @override String get eventDaySun => 'Dimanche'; + @override + String get globalConfirm => 'Confirmer'; + + @override + String get globalCancel => 'Annuler'; + @override String get homeCalendar => 'Calendrier'; @@ -2055,6 +2061,15 @@ class AppLocalizationsFr extends AppLocalizations { String get phonebookEditAssociationGroupement => 'Modifier le groupement d\'association'; + @override + String get phonebookEditAssociationGroups => 'Gérer les groupes'; + + @override + String get phonebookEditAssociationInfo => 'Modifier'; + + @override + String get phonebookEditAssociationMembers => 'Gérer les membres'; + @override String get phonebookEditMembership => 'Modifier le rôle'; @@ -2120,7 +2135,12 @@ class AppLocalizationsFr extends AppLocalizations { String get phonebookFirstname => 'Prénom :'; @override - String get phonebookGroups => 'Groupes associés :'; + String get phonebookGroupementName => 'Nom du groupement'; + + @override + String phonebookGroups(String association) { + return 'Gérer les groupes de $association'; + } @override String get phonebookMandateChangingError => @@ -2133,7 +2153,9 @@ class AppLocalizationsFr extends AppLocalizations { String get phonebookMemberReordered => 'Membre réordonné'; @override - String get phonebookMembers => 'Membres'; + String phonebookMembers(String association) { + return 'Gérer les membres de $association'; + } @override String get phonebookMembershipAssociationError => @@ -2145,6 +2167,11 @@ class AppLocalizationsFr extends AppLocalizations { @override String get phonebookMembershipRoleError => 'Veuillez choisir un rôle'; + @override + String phonebookModifyMembership(String name) { + return 'Modifier le rôle de $name'; + } + @override String get phonebookName => 'Nom :'; @@ -2175,6 +2202,9 @@ class AppLocalizationsFr extends AppLocalizations { @override String get phonebookNoMemberRole => 'Aucun role trouvé'; + @override + String get phonebookNoRoleTags => 'Aucun tag de rôle trouvé'; + @override String get phonebookPhone => 'Téléphone :'; @@ -2214,6 +2244,9 @@ class AppLocalizationsFr extends AppLocalizations { @override String get phonebookRolePure => 'Rôle'; + @override + String get phonebookSearchMember => 'un membre'; + @override String get phonebookTooHeavyAssociationPicture => 'L\'image est trop lourde (max 4Mo)'; diff --git a/lib/phonebook/providers/association_filtered_list_provider.dart b/lib/phonebook/providers/association_filtered_list_provider.dart index 9d6ee198b3..ae533612ba 100644 --- a/lib/phonebook/providers/association_filtered_list_provider.dart +++ b/lib/phonebook/providers/association_filtered_list_provider.dart @@ -30,7 +30,8 @@ final associationFilteredListProvider = Provider>((ref) { .toList(); } return associationGroupements.maybeWhen( - data: (kinds) => sortedAssociationByKind(filteredAssociations, kinds), + data: (groupements) => + sortedAssociationByKind(filteredAssociations, groupements), orElse: () => filteredAssociations, ); }, diff --git a/lib/phonebook/providers/association_member_list_provider.dart b/lib/phonebook/providers/association_member_list_provider.dart index 43f5b47d7a..78fdbc9843 100644 --- a/lib/phonebook/providers/association_member_list_provider.dart +++ b/lib/phonebook/providers/association_member_list_provider.dart @@ -17,7 +17,7 @@ class AssociationMemberListNotifier extends ListNotifier { Future>> loadMembers( String associationId, - String year, + int year, ) async { return await loadList( () async => associationMemberRepository.getAssociationMemberList( @@ -122,10 +122,7 @@ final associationMemberListProvider = tokenExpireWrapperAuth(ref, () async { final association = ref.watch(associationProvider); - await provider.loadMembers( - association.id, - association.mandateYear.toString(), - ); + await provider.loadMembers(association.id, association.mandateYear); }); return provider; }); diff --git a/lib/phonebook/providers/association_provider.dart b/lib/phonebook/providers/association_provider.dart index 29112c185f..85fb315d21 100644 --- a/lib/phonebook/providers/association_provider.dart +++ b/lib/phonebook/providers/association_provider.dart @@ -10,6 +10,10 @@ class AssociationNotifier extends Notifier { void setAssociation(Association association) { state = association; } + + void resetAssociation() { + state = Association.empty(); + } } final associationProvider = NotifierProvider( diff --git a/lib/phonebook/providers/member_role_tags_provider.dart b/lib/phonebook/providers/member_role_tags_provider.dart deleted file mode 100644 index 5b351c8d3f..0000000000 --- a/lib/phonebook/providers/member_role_tags_provider.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; - -final memberRoleTagsProvider = - StateNotifierProvider>((ref) { - return MemberRoleTagsProvider(); - }); - -class MemberRoleTagsProvider extends StateNotifier> { - MemberRoleTagsProvider() : super([]); - - void setRoleTagsWithFilter(Map>?> data) { - List newRoleTags = []; - data.forEach((key, value) { - value?.whenData((d) { - if (d[0]) { - newRoleTags.add(key); - } - }); - }); - state = newRoleTags; - } - - void reset() { - state = []; - } -} diff --git a/lib/phonebook/providers/roles_tags_provider.dart b/lib/phonebook/providers/roles_tags_provider.dart index 1e38843f3e..443a10fea4 100644 --- a/lib/phonebook/providers/roles_tags_provider.dart +++ b/lib/phonebook/providers/roles_tags_provider.dart @@ -1,44 +1,23 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/auth/providers/openid_provider.dart'; -import 'package:titan/phonebook/class/association.dart'; -import 'package:titan/phonebook/class/complete_member.dart'; import 'package:titan/phonebook/repositories/role_tags_repository.dart'; -import 'package:titan/tools/providers/map_provider.dart'; +import 'package:titan/tools/providers/list_notifier.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; -class RolesTagsNotifier extends MapNotifier { +class RolesTagsNotifier extends ListNotifier { final RolesTagsRepository rolesTagsRepository = RolesTagsRepository(); - RolesTagsNotifier({required String token}) { + RolesTagsNotifier({required String token}) + : super(const AsyncValue.loading()) { rolesTagsRepository.setToken(token); } - Future loadRolesTags() async { - loadTList([]); - final result = await rolesTagsRepository.getRolesTags(); - for (int i = 0; i < result.tags.length; i++) { - setTData(result.tags[i], const AsyncData([false])); - } - } - - void resetChecked() { - state.forEach((key, value) => state[key] = const AsyncData([false])); - state = Map.of(state); - } - - void loadRoleTagsFromMember(CompleteMember member, Association association) { - List roleTags = member.getRolesTags(association.id); - for (var value in roleTags) { - state[value] = const AsyncData([true]); - } - state = Map.of(state); + Future>> loadRolesTags() async { + return loadList(rolesTagsRepository.getRolesTags); } } final rolesTagsProvider = - StateNotifierProvider< - RolesTagsNotifier, - Map>?> - >((ref) { + StateNotifierProvider>>((ref) { final token = ref.watch(tokenProvider); RolesTagsNotifier notifier = RolesTagsNotifier(token: token); tokenExpireWrapperAuth(ref, () async { diff --git a/lib/phonebook/repositories/association_member_repository.dart b/lib/phonebook/repositories/association_member_repository.dart index 9f89f6c87d..935ee08dc6 100644 --- a/lib/phonebook/repositories/association_member_repository.dart +++ b/lib/phonebook/repositories/association_member_repository.dart @@ -9,7 +9,7 @@ class AssociationMemberRepository extends Repository { Future> getAssociationMemberList( String associationId, - String year, + int year, ) async { return List.from( (await getList( diff --git a/lib/phonebook/repositories/association_picture_repository.dart b/lib/phonebook/repositories/association_picture_repository.dart index 490bf6b418..21c4ab7eff 100644 --- a/lib/phonebook/repositories/association_picture_repository.dart +++ b/lib/phonebook/repositories/association_picture_repository.dart @@ -1,6 +1,7 @@ import 'dart:typed_data'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/auth/providers/openid_provider.dart'; import 'package:titan/tools/repository/logo_repository.dart'; @@ -13,7 +14,7 @@ class AssociationPictureRepository extends LogoRepository { Future getAssociationPicture(String associationId) async { final uint8List = await getLogo(associationId, suffix: "/picture"); if (uint8List.isEmpty) { - return Image.asset("assets/images/logo.png"); + return Image.asset('assets/images/vache.png'); } return Image.memory(uint8List); } diff --git a/lib/phonebook/repositories/role_tags_repository.dart b/lib/phonebook/repositories/role_tags_repository.dart index e8024368e8..4cd75649b6 100644 --- a/lib/phonebook/repositories/role_tags_repository.dart +++ b/lib/phonebook/repositories/role_tags_repository.dart @@ -1,4 +1,3 @@ -import 'package:titan/phonebook/class/roles_tags.dart'; import 'package:titan/tools/repository/repository.dart'; class RolesTagsRepository extends Repository { @@ -6,8 +5,7 @@ class RolesTagsRepository extends Repository { // ignore: overridden_fields final ext = "phonebook/"; - Future getRolesTags() async { - RolesTags rolesTags = RolesTags.fromJson(await getOne("roletags")); - return rolesTags; + Future> getRolesTags() async { + return List.from((await getOne("roletags"))["tags"]); } } diff --git a/lib/phonebook/router.dart b/lib/phonebook/router.dart index a5f8d8ecb9..8cd9f7a9e7 100644 --- a/lib/phonebook/router.dart +++ b/lib/phonebook/router.dart @@ -84,6 +84,7 @@ class PhonebookRouter { ), ], ), + QRoute( path: editAssociationMembers, builder: () => association_members_page.AssociationMembersPage(), @@ -106,15 +107,6 @@ class PhonebookRouter { middleware: [ DeferredLoadingMiddleware(association_groups_page.loadLibrary), ], - children: [ - QRoute( - path: addEditMember, - builder: () => membership_editor_page.MembershipEditorPage(), - middleware: [ - DeferredLoadingMiddleware(membership_editor_page.loadLibrary), - ], - ), - ], ), ], ), @@ -125,10 +117,31 @@ class PhonebookRouter { children: [ QRoute( path: addEditAssociation, - builder: () => association_members_page.AssociationMembersPage(), + builder: () => association_add_edit_page.AssociationAddEditPage(), middleware: [ + DeferredLoadingMiddleware(association_add_edit_page.loadLibrary), AdminMiddleware(ref, isAssociationPresidentProvider), + ], + children: [ + QRoute( + path: addEditGroupement, + builder: () => + groupement_add_edit_page.AssociationGroupementAddEditPage(), + middleware: [ + DeferredLoadingMiddleware( + groupement_add_edit_page.loadLibrary, + ), + AdminMiddleware(ref, isPhonebookAdminProvider), + ], + ), + ], + ), + QRoute( + path: editAssociationMembers, + builder: () => association_members_page.AssociationMembersPage(), + middleware: [ DeferredLoadingMiddleware(association_members_page.loadLibrary), + AdminMiddleware(ref, isAssociationPresidentProvider), ], children: [ QRoute( @@ -136,6 +149,7 @@ class PhonebookRouter { builder: () => membership_editor_page.MembershipEditorPage(), middleware: [ DeferredLoadingMiddleware(membership_editor_page.loadLibrary), + AdminMiddleware(ref, isAssociationPresidentProvider), ], ), ], diff --git a/lib/phonebook/tools/function.dart b/lib/phonebook/tools/function.dart index 8bdc5fc75f..c47576db1e 100644 --- a/lib/phonebook/tools/function.dart +++ b/lib/phonebook/tools/function.dart @@ -1,3 +1,4 @@ +import 'package:collection/collection.dart'; import 'package:diacritic/diacritic.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -55,20 +56,23 @@ List sortedAssociationByKind( } Color getColorFromTagList(WidgetRef ref, List tags) { - final rolesTags = ref.watch(rolesTagsProvider).keys.toList(); - int index = 3; - for (String tag in tags) { - if (rolesTags.indexOf(tag) < index) { - index = rolesTags.indexOf(tag); - } + if (tags.isEmpty) { + return Colors.white; } - switch (index) { - case 0: - return const Color.fromARGB(255, 251, 109, 16); - case 1: - return const Color.fromARGB(255, 252, 145, 74); - case 2: - return const Color.fromARGB(255, 253, 193, 153); - } - return Colors.white; + final rolesTags = ref.watch(rolesTagsProvider); + return rolesTags.maybeWhen( + data: (allTags) { + int index = tags.map((tag) => allTags.indexOf(tag)).toList().min; + switch (index) { + case 0: + return const Color.fromARGB(255, 251, 109, 16); + case 1: + return const Color.fromARGB(255, 252, 145, 74); + case 2: + return const Color.fromARGB(255, 253, 193, 153); + } + return Colors.white; + }, + orElse: () => Colors.white, + ); } diff --git a/lib/phonebook/ui/components/member_card.dart b/lib/phonebook/ui/components/member_card.dart index cfe7e9bbbe..0871c15572 100644 --- a/lib/phonebook/ui/components/member_card.dart +++ b/lib/phonebook/ui/components/member_card.dart @@ -45,44 +45,49 @@ class MemberCard extends HookConsumerWidget { association, ); - return ListItemTemplate( - title: - "${(member.member.nickname ?? '${member.member.firstname} ${member.member.name}')} - ${assoMembership.apparentName}", - subtitle: member.member.nickname != null - ? "${member.member.firstname} ${member.member.name}" - : null, - icon: AutoLoaderChild( - group: memberPictures, - notifier: memberPicturesNotifier, - mapKey: member, - loader: (ref) => - profilePictureNotifier.getProfilePicture(member.member.id), - loadingBuilder: (context) => - const CircleAvatar(radius: 20, child: CircularProgressIndicator()), - dataBuilder: (context, data) => - CircleAvatar(child: Image(image: data.first.image)), + return Padding( + padding: const EdgeInsets.symmetric(vertical: 5.0), + child: ListItemTemplate( + title: + "${(member.member.nickname ?? '${member.member.firstname} ${member.member.name}')} - ${assoMembership.apparentName}", + subtitle: member.member.nickname != null + ? "${member.member.firstname} ${member.member.name}" + : null, + icon: AutoLoaderChild( + group: memberPictures, + notifier: memberPicturesNotifier, + mapKey: member, + loader: (ref) => + profilePictureNotifier.getProfilePicture(member.member.id), + loadingBuilder: (context) => const CircleAvatar( + radius: 20, + child: CircularProgressIndicator(), + ), + dataBuilder: (context, data) => + CircleAvatar(child: Image(image: data.first.image)), + ), + onTap: editable + ? () { + showCustomBottomModal( + ref: ref, + context: context, + modal: MemberEditionModal( + member: member, + membership: assoMembership, + ), + ); + } + : () { + memberNotifier.setCompleteMember(member); + QR.to(PhonebookRouter.root + PhonebookRouter.memberDetail); + }, + trailing: !editable + ? const HeroIcon( + HeroIcons.chevronRight, + color: ColorConstants.tertiary, + ) + : SizedBox.shrink(), ), - onTap: editable - ? () { - showCustomBottomModal( - ref: ref, - context: context, - modal: MemberEditionModal( - member: member, - membership: assoMembership, - ), - ); - } - : () { - memberNotifier.setCompleteMember(member); - QR.to(PhonebookRouter.root + PhonebookRouter.memberDetail); - }, - trailing: !editable - ? const HeroIcon( - HeroIcons.chevronRight, - color: ColorConstants.tertiary, - ) - : SizedBox.shrink(), ); } } diff --git a/lib/phonebook/ui/pages/add_edit_groupement_page/groupement_add_edit_page.dart b/lib/phonebook/ui/pages/add_edit_groupement_page/groupement_add_edit_page.dart index bb2eb4ec76..0d2b960edc 100644 --- a/lib/phonebook/ui/pages/add_edit_groupement_page/groupement_add_edit_page.dart +++ b/lib/phonebook/ui/pages/add_edit_groupement_page/groupement_add_edit_page.dart @@ -30,6 +30,8 @@ class AssociationGroupementAddEditPage extends HookConsumerWidget { ).showSnackBar(SnackBar(content: Text(message))); } + AppLocalizations localizeWithContext = AppLocalizations.of(context)!; + return PhonebookTemplate( child: Padding( padding: EdgeInsets.symmetric(horizontal: 30.0), @@ -40,12 +42,8 @@ class AssociationGroupementAddEditPage extends HookConsumerWidget { alignment: Alignment.centerLeft, child: Text( associationGroupement.id.isNotEmpty - ? AppLocalizations.of( - context, - )!.phonebookEditAssociationGroupement - : AppLocalizations.of( - context, - )!.phonebookAddAssociationGroupement, + ? localizeWithContext.phonebookEditAssociationGroupement + : localizeWithContext.phonebookAddAssociationGroupement, style: TextStyle( fontSize: 20, fontWeight: FontWeight.w700, @@ -56,21 +54,23 @@ class AssociationGroupementAddEditPage extends HookConsumerWidget { const SizedBox(height: 30), TextEntry( controller: name, - label: AppLocalizations.of(context)!.phonebookName, + label: localizeWithContext.phonebookGroupementName, canBeEmpty: false, ), const SizedBox(height: 50), Button( - text: AppLocalizations.of(context)!.phonebookAdd, + text: associationGroupement.id != "" + ? localizeWithContext.phonebookEdit + : localizeWithContext.phonebookAdd, onPressed: () async { if (name.text.isEmpty) { showSnackBarWithContext( - AppLocalizations.of(context)!.phonebookEmptyFieldError, + localizeWithContext.phonebookEmptyFieldError, ); return; } await tokenExpireWrapper(ref, () async { - if (associationGroupement.id.isNotEmpty) { + if (associationGroupement.id != "") { final value = await associaitonGroupementListNotifier .updateAssociationGroupement( AssociationGroupement( @@ -80,12 +80,12 @@ class AssociationGroupementAddEditPage extends HookConsumerWidget { ); if (value) { showSnackBarWithContext( - AppLocalizations.of(context)!.phonebookAddedAssociation, + localizeWithContext.phonebookAddedAssociation, ); QR.back(); } else { showSnackBarWithContext( - AppLocalizations.of(context)!.phonebookUpdatingError, + localizeWithContext.phonebookUpdatingError, ); } return; @@ -96,12 +96,12 @@ class AssociationGroupementAddEditPage extends HookConsumerWidget { ); if (value) { showSnackBarWithContext( - AppLocalizations.of(context)!.phonebookAddedAssociation, + localizeWithContext.phonebookAddedAssociation, ); QR.back(); } else { showSnackBarWithContext( - AppLocalizations.of(context)!.phonebookAddingError, + localizeWithContext.phonebookAddingError, ); } }); diff --git a/lib/phonebook/ui/pages/admin_page/admin_page.dart b/lib/phonebook/ui/pages/admin_page/admin_page.dart index ca61fddd6d..7e4b930fc4 100644 --- a/lib/phonebook/ui/pages/admin_page/admin_page.dart +++ b/lib/phonebook/ui/pages/admin_page/admin_page.dart @@ -4,7 +4,9 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/providers/is_admin_provider.dart'; import 'package:titan/phonebook/providers/association_filtered_list_provider.dart'; import 'package:titan/phonebook/providers/association_groupement_list_provider.dart'; +import 'package:titan/phonebook/providers/association_groupement_provider.dart'; import 'package:titan/phonebook/providers/association_list_provider.dart'; +import 'package:titan/phonebook/providers/association_provider.dart'; import 'package:titan/phonebook/providers/phonebook_admin_provider.dart'; import 'package:titan/phonebook/providers/roles_tags_provider.dart'; import 'package:titan/phonebook/router.dart'; @@ -28,11 +30,17 @@ class AdminPage extends HookConsumerWidget { ); final associationListNotifier = ref.watch(associationListProvider.notifier); final associationList = ref.watch(associationListProvider); + final associationNotifier = ref.watch(associationProvider.notifier); + final associationGroupementNotifier = ref.watch( + associationGroupementProvider.notifier, + ); final associationFilteredList = ref.watch(associationFilteredListProvider); final roleNotifier = ref.watch(rolesTagsProvider.notifier); final isPhonebookAdmin = ref.watch(isPhonebookAdminProvider); final isAdmin = ref.watch(isAdminProvider); + final localizeWithContext = AppLocalizations.of(context)!; + return PhonebookTemplate( child: Refresher( onRefresh: () async { @@ -44,6 +52,7 @@ class AdminPage extends HookConsumerWidget { child: Column( children: [ AssociationResearchBar(), + const SizedBox(height: 10), Async2Children( values: Tuple2(associationList, associationGroupementList), builder: (context, associations, associationGroupements) { @@ -59,6 +68,9 @@ class AdminPage extends HookConsumerWidget { trailing: SizedBox.shrink(), onTap: isPhonebookAdmin ? () { + associationNotifier.resetAssociation(); + associationGroupementNotifier + .resetAssociationGroupement(); QR.to( PhonebookRouter.root + PhonebookRouter.admin + @@ -71,9 +83,7 @@ class AdminPage extends HookConsumerWidget { if (associations.isEmpty) Center( child: Text( - AppLocalizations.of( - context, - )!.phonebookNoAssociationFound, + localizeWithContext.phonebookNoAssociationFound, ), ) else @@ -92,6 +102,7 @@ class AdminPage extends HookConsumerWidget { ); }, ), + const SizedBox(height: 80), ], ), ), diff --git a/lib/phonebook/ui/pages/admin_page/association_edition_modal.dart b/lib/phonebook/ui/pages/admin_page/association_admin_edition_modal.dart similarity index 83% rename from lib/phonebook/ui/pages/admin_page/association_edition_modal.dart rename to lib/phonebook/ui/pages/admin_page/association_admin_edition_modal.dart index d479b75898..2916cd53bf 100644 --- a/lib/phonebook/ui/pages/admin_page/association_edition_modal.dart +++ b/lib/phonebook/ui/pages/admin_page/association_admin_edition_modal.dart @@ -12,14 +12,14 @@ import 'package:titan/phonebook/router.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; import 'package:titan/tools/ui/styleguide/button.dart'; -import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; +import 'package:titan/tools/ui/styleguide/custom_dialog_box.dart'; -class AssociationEditionModal extends HookConsumerWidget { +class AssociationAdminEditionModal extends HookConsumerWidget { final Association association; final AssociationGroupement groupement; final bool isPhonebookAdmin; final bool isAdmin; - const AssociationEditionModal({ + const AssociationAdminEditionModal({ super.key, required this.association, required this.groupement, @@ -42,12 +42,10 @@ class AssociationEditionModal extends HookConsumerWidget { displayToast(context, type, msg); } - AppLocalizations localizeWithContext() { - return AppLocalizations.of(context)!; - } + AppLocalizations localizeWithContext = AppLocalizations.of(context)!; return BottomModalTemplate( - title: "title", + title: association.name, child: SingleChildScrollView( child: Column( children: [ @@ -108,11 +106,12 @@ class AssociationEditionModal extends HookConsumerWidget { text: "Passer au mandat ${association.mandateYear + 1}", onPressed: () { Navigator.of(context).pop(); - showDialog( + showCustomDialog( context: context, - builder: (context) => CustomDialogBox( - title: "title", - descriptions: "descriptions", + ref: ref, + dialog: CustomDialogBox.danger( + title: "Passer au mandat ${association.mandateYear + 1}", + description: "Cette action est irréversible", onYes: () async { final result = await associationListNotifier .updateAssociation( @@ -123,12 +122,12 @@ class AssociationEditionModal extends HookConsumerWidget { if (result) { displayToastWithContext( TypeMsg.msg, - localizeWithContext().phonebookUpdatedAssociation, + localizeWithContext.phonebookUpdatedAssociation, ); } else { displayToastWithContext( TypeMsg.error, - localizeWithContext().phonebookUpdatingError, + localizeWithContext.phonebookUpdatingError, ); } }, @@ -149,32 +148,33 @@ class AssociationEditionModal extends HookConsumerWidget { if (result) { displayToastWithContext( TypeMsg.msg, - localizeWithContext().phonebookDeactivatedAssociation, + localizeWithContext.phonebookDeactivatedAssociation, ); } else { displayToastWithContext( TypeMsg.error, - localizeWithContext().phonebookDeactivatingError, + localizeWithContext.phonebookDeactivatingError, ); } } else { - showDialog( + showCustomDialog( context: context, - builder: (context) => CustomDialogBox( - title: "title", - descriptions: "descriptions", + ref: ref, + dialog: CustomDialogBox.danger( + title: "Supprimer l'association", + description: "Cette action est irréversible", onYes: () async { final result = await associationListNotifier .deleteAssociation(association); if (result) { displayToastWithContext( TypeMsg.msg, - localizeWithContext().phonebookDeletedAssociation, + localizeWithContext.phonebookDeletedAssociation, ); } else { displayToastWithContext( TypeMsg.error, - localizeWithContext().phonebookDeletingError, + localizeWithContext.phonebookDeletingError, ); } }, diff --git a/lib/phonebook/ui/pages/admin_page/editable_association_card.dart b/lib/phonebook/ui/pages/admin_page/editable_association_card.dart index 0830692779..a1af38f301 100644 --- a/lib/phonebook/ui/pages/admin_page/editable_association_card.dart +++ b/lib/phonebook/ui/pages/admin_page/editable_association_card.dart @@ -1,11 +1,10 @@ import 'package:flutter/material.dart'; -import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/phonebook/class/association.dart'; import 'package:titan/phonebook/class/association_groupement.dart'; import 'package:titan/phonebook/providers/association_picture_provider.dart'; import 'package:titan/phonebook/providers/associations_picture_map_provider.dart'; -import 'package:titan/phonebook/ui/pages/admin_page/association_edition_modal.dart'; +import 'package:titan/phonebook/ui/pages/admin_page/association_admin_edition_modal.dart'; import 'package:titan/tools/ui/builders/auto_loader_child.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; import 'package:titan/tools/ui/styleguide/list_item.dart'; @@ -34,36 +33,37 @@ class EditableAssociationCard extends HookConsumerWidget { final associationPictureNotifier = ref.watch( associationPictureProvider.notifier, ); - return AutoLoaderChild( - group: associationPicture, - notifier: associationPictureMapNotifier, - mapKey: association.id, - loader: (associationId) => - associationPictureNotifier.getAssociationPicture(associationId), - dataBuilder: (context, data) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 5), - child: ListItem( - title: association.name, - subtitle: groupement.name, - icon: CircleAvatar(child: Image(image: data.first.image)), - onTap: () { - showCustomBottomModal( - ref: ref, - context: context, - modal: AssociationEditionModal( - association: association, - groupement: groupement, - isPhonebookAdmin: isPhonebookAdmin, - isAdmin: isAdmin, - ), - ); - }, - ), - ); - }, - errorBuilder: (error, stack) => - const Center(child: HeroIcon(HeroIcons.exclamationCircle)), + return Padding( + padding: const EdgeInsets.symmetric(vertical: 5), + child: ListItem( + title: association.name, + subtitle: groupement.name, + icon: AutoLoaderChild( + group: associationPicture, + notifier: associationPictureMapNotifier, + mapKey: association.id, + loader: (associationId) => + associationPictureNotifier.getAssociationPicture(associationId), + dataBuilder: (context, data) { + return CircleAvatar( + backgroundColor: Colors.white, + child: Image(image: data.first.image), + ); + }, + ), + onTap: () { + showCustomBottomModal( + ref: ref, + context: context, + modal: AssociationAdminEditionModal( + association: association, + groupement: groupement, + isPhonebookAdmin: isPhonebookAdmin, + isAdmin: isAdmin, + ), + ); + }, + ), ); } } diff --git a/lib/phonebook/ui/pages/association_add_edit_page/association_add_edit_page.dart b/lib/phonebook/ui/pages/association_add_edit_page/association_add_edit_page.dart index da8bab5e92..31d1cef91f 100644 --- a/lib/phonebook/ui/pages/association_add_edit_page/association_add_edit_page.dart +++ b/lib/phonebook/ui/pages/association_add_edit_page/association_add_edit_page.dart @@ -35,6 +35,9 @@ class AssociationAddEditPage extends HookConsumerWidget { associationPictureProvider.notifier, ); final associationGroupement = ref.watch(associationGroupementProvider); + final associationGroupementNotifier = ref.watch( + associationGroupementProvider.notifier, + ); final name = useTextEditingController(text: association.name); final description = useTextEditingController(text: association.description); @@ -44,9 +47,7 @@ class AssociationAddEditPage extends HookConsumerWidget { ).showSnackBar(SnackBar(content: Text(message))); } - AppLocalizations localizeWithContext() { - return AppLocalizations.of(context)!; - } + AppLocalizations localizeWithContext = AppLocalizations.of(context)!; return PhonebookTemplate( child: SingleChildScrollView( @@ -64,8 +65,8 @@ class AssociationAddEditPage extends HookConsumerWidget { alignment: Alignment.centerLeft, child: Text( association.id == "" - ? AppLocalizations.of(context)!.phonebookAddAssociation - : AppLocalizations.of(context)!.phonebookEdit, + ? localizeWithContext.phonebookAddAssociation + : localizeWithContext.phonebookEdit, style: TextStyle( fontSize: 20, fontWeight: FontWeight.w700, @@ -96,6 +97,7 @@ class AssociationAddEditPage extends HookConsumerWidget { ), child: CircleAvatar( radius: 80, + backgroundColor: Colors.white, backgroundImage: image.image, ), ), @@ -104,18 +106,6 @@ class AssociationAddEditPage extends HookConsumerWidget { left: 0, child: GestureDetector( onTap: () async { - final updatedProfilePictureMsg = - AppLocalizations.of( - context, - )!.settingsUpdatedProfilePicture; - final tooHeavyProfilePictureMsg = - AppLocalizations.of( - context, - )!.settingsTooHeavyProfilePicture; - final profilePictureErrorMsg = - AppLocalizations.of( - context, - )!.settingsErrorProfilePicture; final value = await associationPictureNotifier .setProfilePicture( ImageSource.gallery, @@ -124,16 +114,19 @@ class AssociationAddEditPage extends HookConsumerWidget { if (value != null) { if (value) { showSnackBarWithContext( - updatedProfilePictureMsg, + localizeWithContext + .settingsUpdatedProfilePicture, ); } else { showSnackBarWithContext( - tooHeavyProfilePictureMsg, + localizeWithContext + .settingsTooHeavyProfilePicture, ); } } else { showSnackBarWithContext( - profilePictureErrorMsg, + localizeWithContext + .settingsErrorProfilePicture, ); } }, @@ -147,18 +140,6 @@ class AssociationAddEditPage extends HookConsumerWidget { right: 0, child: GestureDetector( onTap: () async { - final updatedProfilePictureMsg = - AppLocalizations.of( - context, - )!.settingsUpdatedProfilePicture; - final tooHeavyProfilePictureMsg = - AppLocalizations.of( - context, - )!.settingsTooHeavyProfilePicture; - final profilePictureErrorMsg = - AppLocalizations.of( - context, - )!.settingsErrorProfilePicture; final value = await associationPictureNotifier .setProfilePicture( ImageSource.camera, @@ -167,16 +148,19 @@ class AssociationAddEditPage extends HookConsumerWidget { if (value != null) { if (value) { showSnackBarWithContext( - updatedProfilePictureMsg, + localizeWithContext + .settingsUpdatedProfilePicture, ); } else { showSnackBarWithContext( - tooHeavyProfilePictureMsg, + localizeWithContext + .settingsTooHeavyProfilePicture, ); } } else { showSnackBarWithContext( - profilePictureErrorMsg, + localizeWithContext + .settingsErrorProfilePicture, ); } }, @@ -196,13 +180,13 @@ class AssociationAddEditPage extends HookConsumerWidget { Container(margin: const EdgeInsets.symmetric(vertical: 10)), TextEntry( controller: name, - label: AppLocalizations.of(context)!.phonebookName, + label: localizeWithContext.phonebookName, canBeEmpty: false, ), const SizedBox(height: 30), TextEntry( controller: description, - label: AppLocalizations.of(context)!.phonebookDescription, + label: localizeWithContext.phonebookDescription, canBeEmpty: true, ), const SizedBox(height: 50), @@ -210,13 +194,13 @@ class AssociationAddEditPage extends HookConsumerWidget { onPressed: () async { if (!key.currentState!.validate()) { showSnackBarWithContext( - AppLocalizations.of(context)!.phonebookEmptyFieldError, + localizeWithContext.phonebookEmptyFieldError, ); return; } if (associationGroupement.id == '') { showSnackBarWithContext( - AppLocalizations.of(context)!.phonebookEmptyKindError, + localizeWithContext.phonebookEmptyKindError, ); return; } @@ -233,7 +217,7 @@ class AssociationAddEditPage extends HookConsumerWidget { ); if (value) { showSnackBarWithContext( - localizeWithContext().phonebookAddedAssociation, + localizeWithContext.phonebookAddedAssociation, ); associations.when( data: (d) { @@ -245,15 +229,14 @@ class AssociationAddEditPage extends HookConsumerWidget { ); }, error: (e, s) => showSnackBarWithContext( - AppLocalizations.of( - context, - )!.phonebookErrorAssociationLoading, + localizeWithContext + .phonebookErrorAssociationLoading, ), loading: () {}, ); } else { showSnackBarWithContext( - localizeWithContext().phonebookAddingError, + localizeWithContext.phonebookAddingError, ); } } else { @@ -267,19 +250,23 @@ class AssociationAddEditPage extends HookConsumerWidget { ); if (value) { showSnackBarWithContext( - localizeWithContext().phonebookUpdatedAssociation, + localizeWithContext.phonebookUpdatedAssociation, ); - QR.to(PhonebookRouter.root + PhonebookRouter.admin); + + associationGroupementNotifier + .resetAssociationGroupement(); + QR.back(); } else { showSnackBarWithContext( - localizeWithContext().phonebookUpdatingError, + localizeWithContext.phonebookUpdatingError, ); } } }); }, - text: AppLocalizations.of(context)!.adminAdd, + text: localizeWithContext.adminAdd, ), + SizedBox(height: 80), ], ), ), diff --git a/lib/phonebook/ui/pages/association_groups_page/association_groups_page.dart b/lib/phonebook/ui/pages/association_groups_page/association_groups_page.dart index 9d4f5d8390..7e06c293c6 100644 --- a/lib/phonebook/ui/pages/association_groups_page/association_groups_page.dart +++ b/lib/phonebook/ui/pages/association_groups_page/association_groups_page.dart @@ -1,16 +1,21 @@ import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/admin/class/simple_group.dart'; import 'package:titan/admin/providers/group_list_provider.dart'; +import 'package:titan/phonebook/providers/association_groupement_provider.dart'; import 'package:titan/phonebook/providers/association_list_provider.dart'; import 'package:titan/phonebook/providers/association_provider.dart'; import 'package:titan/phonebook/ui/phonebook.dart'; -import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; -import 'package:titan/tools/ui/builders/waiting_button.dart'; -import 'package:titan/tools/ui/layouts/add_edit_button_layout.dart'; +import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/l10n/app_localizations.dart'; +import 'package:titan/tools/ui/layouts/refresher.dart'; +import 'package:titan/tools/ui/styleguide/button.dart'; +import 'package:titan/tools/ui/styleguide/list_item_toggle.dart'; +import 'package:titan/tools/ui/widgets/align_left_text.dart'; class AssociationGroupsPage extends HookConsumerWidget { final scrollKey = GlobalKey(); @@ -20,16 +25,26 @@ class AssociationGroupsPage extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final association = ref.watch(associationProvider); final associationListNotifier = ref.watch(associationListProvider.notifier); + final associationGroupementNotifier = ref.watch( + associationGroupementProvider.notifier, + ); final groups = ref.watch(allGroupListProvider); - List selectedGroups = groups.maybeWhen( + + AppLocalizations localizeWithContext = AppLocalizations.of(context)!; + + final selectedGroups = groups.maybeWhen( data: (value) { - return value.where((element) { - return association.associatedGroups.contains(element.id); - }).toList(); + return useState>( + List.from( + value.where((element) { + return association.associatedGroups.contains(element.id); + }).toList(), + ), + ); }, orElse: () { - return []; + return useState>([]); }, ); @@ -38,124 +53,76 @@ class AssociationGroupsPage extends HookConsumerWidget { } return PhonebookTemplate( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 30), - child: Column( - children: [ - Container( - margin: const EdgeInsets.symmetric(vertical: 10), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - child: ExpansionTile( - title: Text( - AppLocalizations.of(context)!.phonebookGroups, - ), - children: groups.maybeWhen( - data: (data) { - return data.map((group) { - return Container( - padding: const EdgeInsets.symmetric( - vertical: 10, - horizontal: 20, - ), - decoration: BoxDecoration( - color: Colors.white, - boxShadow: [ - BoxShadow( - color: Colors.black.withValues(alpha: 0.1), - offset: const Offset(0, 1), - blurRadius: 4, - spreadRadius: 2, - ), - ], - ), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - child: Text( - group.name, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - ), - ), - ), - StatefulBuilder( - builder: (context, setState) { - return Checkbox( - value: selectedGroups.contains(group), - onChanged: (value) { - if (value == true) { - selectedGroups.add(group); - } else { - selectedGroups.remove(group); - } - setState(() {}); - }, - ); - }, - ), - ], - ), - ); - }).toList(); - }, - orElse: () { - return []; - }, - ), - ), - ), - ], + child: Refresher( + onRefresh: () async { + await tokenExpireWrapper(ref, () async { + await associationListNotifier.loadAssociations(); + await ref.read(allGroupListProvider.notifier).loadGroups(); + }); + }, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 30), + child: Column( + children: [ + AlignLeftText( + localizeWithContext.phonebookGroups(association.name), ), - ), - WaitingButton( - builder: (child) => AddEditButtonLayout( - colors: const [ - ColorConstants.gradient1, - ColorConstants.gradient2, - ], - child: child, + AsyncChild( + value: groups, + builder: (context, groupList) { + return Column( + children: groupList + .map( + (group) => ToggleListItem( + title: group.name, + selected: selectedGroups.value.contains(group), + onTap: () { + final groups = [...selectedGroups.value]; + if (groups.contains(group)) { + groups.remove(group); + } else { + groups.add(group); + } + selectedGroups.value = groups; + }, + ), + ) + .toList(), + ); + }, ), - onTap: () async { - await tokenExpireWrapper(ref, () async { - final updatedGroupsMsg = AppLocalizations.of( - context, - )!.phonebookUpdatedGroups; - final updatingErrorMsg = AppLocalizations.of( - context, - )!.phonebookUpdatingError; - final value = await associationListNotifier - .updateAssociationGroups( - association.copyWith( - associatedGroups: selectedGroups - .map((e) => e.id) - .toList(), - ), + Button( + onPressed: () async { + await tokenExpireWrapper(ref, () async { + final value = await associationListNotifier + .updateAssociationGroups( + association.copyWith( + associatedGroups: selectedGroups.value + .map((e) => e.id) + .toList(), + ), + ); + if (value) { + displayToastWithContext( + TypeMsg.msg, + localizeWithContext.phonebookUpdatedGroups, + ); + associationGroupementNotifier + .resetAssociationGroupement(); + QR.back(); + } else { + displayToastWithContext( + TypeMsg.msg, + localizeWithContext.phonebookUpdatingError, ); - if (value) { - displayToastWithContext(TypeMsg.msg, updatedGroupsMsg); - } else { - displayToastWithContext(TypeMsg.msg, updatingErrorMsg); - } - }); - }, - child: Text( - AppLocalizations.of(context)!.phonebookUpdateGroups, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - color: Color.fromARGB(255, 255, 255, 255), - ), + } + }); + }, + text: localizeWithContext.phonebookUpdateGroups, ), - ), - ], + SizedBox(height: 80), + ], + ), ), ), ); diff --git a/lib/phonebook/ui/pages/association_members_page/association_members_page.dart b/lib/phonebook/ui/pages/association_members_page/association_members_page.dart index 7305ff8178..06639da755 100644 --- a/lib/phonebook/ui/pages/association_members_page/association_members_page.dart +++ b/lib/phonebook/ui/pages/association_members_page/association_members_page.dart @@ -9,15 +9,16 @@ import 'package:titan/phonebook/providers/association_member_list_provider.dart' import 'package:titan/phonebook/providers/association_member_sorted_list_provider.dart'; import 'package:titan/phonebook/providers/association_provider.dart'; import 'package:titan/phonebook/providers/complete_member_provider.dart'; -import 'package:titan/phonebook/providers/member_role_tags_provider.dart'; import 'package:titan/phonebook/providers/membership_provider.dart'; -import 'package:titan/phonebook/providers/roles_tags_provider.dart'; import 'package:titan/phonebook/router.dart'; import 'package:titan/phonebook/ui/components/member_card.dart'; +import 'package:titan/phonebook/ui/phonebook.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; +import 'package:titan/tools/ui/layouts/refresher.dart'; import 'package:titan/tools/ui/styleguide/list_item_template.dart'; +import 'package:titan/tools/ui/widgets/align_left_text.dart'; class AssociationMembersPage extends HookConsumerWidget { const AssociationMembersPage({super.key}); @@ -29,8 +30,6 @@ class AssociationMembersPage extends HookConsumerWidget { final associationMemberListNotifier = ref.watch( associationMemberListProvider.notifier, ); - final rolesTagsNotifier = ref.watch(rolesTagsProvider.notifier); - final memberRoleTagsNotifier = ref.watch(memberRoleTagsProvider.notifier); final completeMemberNotifier = ref.watch(completeMemberProvider.notifier); final membershipNotifier = ref.watch(membershipProvider.notifier); final associationMemberSortedList = ref.watch( @@ -41,129 +40,132 @@ class AssociationMembersPage extends HookConsumerWidget { displayToast(context, type, msg); } - AppLocalizations localizeWithContext() { - return AppLocalizations.of(context)!; - } + AppLocalizations localizeWithContext = AppLocalizations.of(context)!; - return Padding( - padding: EdgeInsets.symmetric(horizontal: 20), - child: Column( - children: [ - Text( - AppLocalizations.of(context)!.phonebookMembers, - textAlign: TextAlign.start, - style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), - ), - if (!association.deactivated) ...[ - SizedBox(height: 10), - ListItemTemplate( - icon: const HeroIcon( - HeroIcons.plus, - size: 40, - color: Colors.black, + return PhonebookTemplate( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 20), + child: Refresher( + onRefresh: () { + return tokenExpireWrapper(ref, () async { + await associationMemberListNotifier.loadMembers( + association.id, + association.mandateYear, + ); + }); + }, + child: Column( + children: [ + AlignLeftText( + localizeWithContext.phonebookMembers(association.name), ), - title: "Ajouter", - trailing: SizedBox.shrink(), - onTap: () async { - rolesTagsNotifier.resetChecked(); - memberRoleTagsNotifier.reset(); - completeMemberNotifier.setCompleteMember( - CompleteMember.empty(), - ); - membershipNotifier.setMembership( - Membership.empty().copyWith(associationId: association.id), - ); - if (QR.currentPath.contains(PhonebookRouter.admin)) { - QR.to( - PhonebookRouter.root + - PhonebookRouter.admin + - PhonebookRouter.addEditAssociation + - PhonebookRouter.addEditMember, - ); - } else { - QR.to( - PhonebookRouter.root + - PhonebookRouter.associationDetail + - PhonebookRouter.addEditAssociation + - PhonebookRouter.addEditMember, - ); - } - }, - ), - ], - AsyncChild( - value: associationMemberList, - builder: (context, associationMembers) => associationMembers.isEmpty - ? Text(AppLocalizations.of(context)!.phonebookNoMember) - : !association.deactivated - ? Expanded( - child: ReorderableListView( - proxyDecorator: (child, index, animation) { - return Material( - child: FadeTransition( - opacity: animation, - child: child, - ), - ); - }, - onReorder: (int oldIndex, int newIndex) async { - await tokenExpireWrapper(ref, () async { - final result = await associationMemberListNotifier - .reorderMember( - associationMemberSortedList[oldIndex], - associationMemberSortedList[oldIndex] - .memberships - .firstWhere( - (element) => - element.associationId == - association.id && - element.mandateYear == - association.mandateYear, - ) - .copyWith(order: newIndex), - oldIndex, - newIndex, - ); - if (result) { - displayToastWithContext( - TypeMsg.msg, - localizeWithContext().phonebookMemberReordered, - ); - } else { - displayToastWithContext( - TypeMsg.error, - localizeWithContext().phonebookReorderingError, - ); - } - }); - }, - children: associationMemberSortedList - .map( - (member) => MemberCard( - deactivated: false, - key: ValueKey(member.member.id), - member: member, - association: association, - editable: true, - ), - ) - .toList(), - ), - ) - : ListView.builder( - itemCount: associationMembers.length, - itemBuilder: (context, index) { - return MemberCard( - deactivated: true, - key: ValueKey(associationMembers[index].member.id), - member: associationMembers[index], - association: association, - editable: true, - ); - }, + if (!association.deactivated) ...[ + SizedBox(height: 10), + ListItemTemplate( + icon: const HeroIcon( + HeroIcons.plus, + size: 40, + color: Colors.black, ), + title: "Ajouter", + trailing: SizedBox.shrink(), + onTap: () async { + completeMemberNotifier.setCompleteMember( + CompleteMember.empty(), + ); + membershipNotifier.setMembership( + Membership.empty().copyWith( + associationId: association.id, + ), + ); + if (QR.currentPath.contains(PhonebookRouter.admin)) { + QR.to( + PhonebookRouter.root + + PhonebookRouter.admin + + PhonebookRouter.addEditAssociation + + PhonebookRouter.addEditMember, + ); + } else { + QR.to( + PhonebookRouter.root + + PhonebookRouter.associationDetail + + PhonebookRouter.addEditAssociation + + PhonebookRouter.addEditMember, + ); + } + }, + ), + ], + AsyncChild( + value: associationMemberList, + builder: (context, associationMembers) => + associationMembers.isEmpty + ? Text(localizeWithContext.phonebookNoMember) + : !association.deactivated + ? SizedBox( + height: MediaQuery.of(context).size.height * 0.7, + child: ReorderableListView( + onReorder: (int oldIndex, int newIndex) async { + await tokenExpireWrapper(ref, () async { + final result = await associationMemberListNotifier + .reorderMember( + associationMemberSortedList[oldIndex], + associationMemberSortedList[oldIndex] + .memberships + .firstWhere( + (element) => + element.associationId == + association.id && + element.mandateYear == + association.mandateYear, + ) + .copyWith(order: newIndex), + oldIndex, + newIndex, + ); + if (result) { + displayToastWithContext( + TypeMsg.msg, + localizeWithContext.phonebookMemberReordered, + ); + } else { + displayToastWithContext( + TypeMsg.error, + localizeWithContext.phonebookReorderingError, + ); + } + }); + }, + children: associationMemberSortedList + .map( + (member) => MemberCard( + deactivated: false, + key: ValueKey(member.member.id), + member: member, + association: association, + editable: true, + ), + ) + .toList(), + ), + ) + : ListView.builder( + itemCount: associationMembers.length, + itemBuilder: (context, index) { + return MemberCard( + deactivated: true, + key: ValueKey(associationMembers[index].member.id), + member: associationMembers[index], + association: association, + editable: true, + ); + }, + ), + ), + SizedBox(height: 80), + ], ), - ], + ), ), ); } diff --git a/lib/phonebook/ui/pages/association_members_page/member_edition_modal.dart b/lib/phonebook/ui/pages/association_members_page/member_edition_modal.dart index 8de3c0527a..0b9e6b862d 100644 --- a/lib/phonebook/ui/pages/association_members_page/member_edition_modal.dart +++ b/lib/phonebook/ui/pages/association_members_page/member_edition_modal.dart @@ -7,15 +7,13 @@ import 'package:titan/phonebook/class/membership.dart'; import 'package:titan/phonebook/providers/association_member_list_provider.dart'; import 'package:titan/phonebook/providers/association_provider.dart'; import 'package:titan/phonebook/providers/complete_member_provider.dart'; -import 'package:titan/phonebook/providers/member_role_tags_provider.dart'; import 'package:titan/phonebook/providers/membership_provider.dart'; -import 'package:titan/phonebook/providers/roles_tags_provider.dart'; import 'package:titan/phonebook/router.dart'; import 'package:titan/phonebook/tools/function.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; import 'package:titan/tools/ui/styleguide/button.dart'; -import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; +import 'package:titan/tools/ui/styleguide/custom_dialog_box.dart'; class MemberEditionModal extends HookConsumerWidget { final CompleteMember member; @@ -34,16 +32,12 @@ class MemberEditionModal extends HookConsumerWidget { ); final association = ref.watch(associationProvider); final membershipNotifier = ref.watch(membershipProvider.notifier); - final roleTagsNotifier = ref.watch(rolesTagsProvider.notifier); - final memberRoleTagsNotifier = ref.watch(memberRoleTagsProvider.notifier); void displayToastWithContext(TypeMsg type, String msg) { displayToast(context, type, msg); } - AppLocalizations localizeWithContext() { - return AppLocalizations.of(context)!; - } + AppLocalizations localizeWithContext = AppLocalizations.of(context)!; return BottomModalTemplate( title: "title", @@ -54,23 +48,20 @@ class MemberEditionModal extends HookConsumerWidget { Button( text: "Modifier le rôle", onPressed: () { - roleTagsNotifier.resetChecked(); - roleTagsNotifier.loadRoleTagsFromMember(member, association); completeMemberNotifier.setCompleteMember(member); membershipNotifier.setMembership(membership); - memberRoleTagsNotifier.reset(); if (QR.currentPath.contains(PhonebookRouter.admin)) { QR.to( PhonebookRouter.root + PhonebookRouter.admin + - PhonebookRouter.addEditAssociation + + PhonebookRouter.editAssociationMembers + PhonebookRouter.addEditMember, ); } else { QR.to( PhonebookRouter.root + PhonebookRouter.associationDetail + - PhonebookRouter.addEditAssociation + + PhonebookRouter.editAssociationMembers + PhonebookRouter.addEditMember, ); } @@ -81,12 +72,13 @@ class MemberEditionModal extends HookConsumerWidget { text: "Supprimer le rôle", onPressed: () { Navigator.of(context).pop(); - showDialog( + showCustomDialog( context: context, - builder: (context) => CustomDialogBox( + ref: ref, + dialog: CustomDialogBox.danger( title: "Supprimer le rôle de ${member.member.nickname ?? '${member.member.firstname} ${member.member.name}'}", - descriptions: "Cette action est irréversible", + description: "Cette action est irréversible", onYes: () async { final result = await associationMemberListNotifier .deleteMember( @@ -96,12 +88,12 @@ class MemberEditionModal extends HookConsumerWidget { if (result) { displayToastWithContext( TypeMsg.msg, - localizeWithContext().phonebookDeletedMember, + localizeWithContext.phonebookDeletedMember, ); } else { displayToastWithContext( TypeMsg.error, - localizeWithContext().phonebookDeletingError, + localizeWithContext.phonebookDeletingError, ); } }, diff --git a/lib/phonebook/ui/pages/association_page/association_edition_modal.dart b/lib/phonebook/ui/pages/association_page/association_edition_modal.dart new file mode 100644 index 0000000000..48891390d6 --- /dev/null +++ b/lib/phonebook/ui/pages/association_page/association_edition_modal.dart @@ -0,0 +1,79 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; +import 'package:titan/phonebook/class/association.dart'; +import 'package:titan/phonebook/class/association_groupement.dart'; +import 'package:titan/phonebook/providers/association_groupement_provider.dart'; +import 'package:titan/phonebook/providers/association_picture_provider.dart'; +import 'package:titan/phonebook/providers/association_provider.dart'; +import 'package:titan/phonebook/router.dart'; +import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; +import 'package:titan/tools/ui/styleguide/button.dart'; + +class AssociationEditionModal extends HookConsumerWidget { + final Association association; + final AssociationGroupement groupement; + const AssociationEditionModal({ + super.key, + required this.association, + required this.groupement, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final associationGroupementsNotifier = ref.watch( + associationGroupementProvider.notifier, + ); + final associationNotifier = ref.watch(associationProvider.notifier); + final associationPictureNotifier = ref.watch( + associationPictureProvider.notifier, + ); + + AppLocalizations localizeWithContext = AppLocalizations.of(context)!; + + return BottomModalTemplate( + title: association.name, + child: SingleChildScrollView( + child: Column( + children: [ + Button( + text: localizeWithContext.phonebookEditAssociationInfo, + onPressed: () { + associationPictureNotifier.getAssociationPicture( + association.id, + ); + associationGroupementsNotifier.setAssociationGroupement( + groupement, + ); + associationNotifier.setAssociation(association); + Navigator.of(context).pop(); + QR.to( + PhonebookRouter.root + + PhonebookRouter.associationDetail + + PhonebookRouter.addEditAssociation, + ); + }, + ), + SizedBox(height: 5), + Button( + text: localizeWithContext.phonebookEditAssociationMembers, + onPressed: () { + associationGroupementsNotifier.setAssociationGroupement( + groupement, + ); + associationNotifier.setAssociation(association); + Navigator.of(context).pop(); + QR.to( + PhonebookRouter.root + + PhonebookRouter.associationDetail + + PhonebookRouter.editAssociationMembers, + ); + }, + ), + ], + ), + ), + ); + } +} diff --git a/lib/phonebook/ui/pages/association_page/association_page.dart b/lib/phonebook/ui/pages/association_page/association_page.dart index 8a0552580b..35ebc2e95f 100644 --- a/lib/phonebook/ui/pages/association_page/association_page.dart +++ b/lib/phonebook/ui/pages/association_page/association_page.dart @@ -8,14 +8,15 @@ import 'package:titan/phonebook/providers/association_picture_provider.dart'; import 'package:titan/phonebook/providers/association_provider.dart'; import 'package:titan/phonebook/providers/association_member_list_provider.dart'; import 'package:titan/phonebook/providers/phonebook_admin_provider.dart'; -import 'package:titan/phonebook/router.dart'; import 'package:titan/phonebook/ui/components/member_card.dart'; +import 'package:titan/phonebook/ui/pages/association_page/association_edition_modal.dart'; import 'package:titan/phonebook/ui/pages/association_page/web_member_card.dart'; import 'package:titan/phonebook/ui/phonebook.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; -import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/l10n/app_localizations.dart'; +import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; +import 'package:titan/tools/ui/styleguide/icon_button.dart'; class AssociationPage extends HookConsumerWidget { const AssociationPage({super.key}); @@ -30,18 +31,21 @@ class AssociationPage extends HookConsumerWidget { final associationMemberListNotifier = ref.watch( associationMemberListProvider.notifier, ); + final associationPicture = ref.watch(associationPictureProvider); final associationPictureNotifier = ref.watch( associationPictureProvider.notifier, ); final isPresident = ref.watch(isAssociationPresidentProvider); final associationGroupement = ref.watch(associationGroupementProvider); + final localizeWithContext = AppLocalizations.of(context)!; + return PhonebookTemplate( child: Refresher( onRefresh: () async { await associationMemberListNotifier.loadMembers( association.id, - association.mandateYear.toString(), + association.mandateYear, ); await associationPictureNotifier.getAssociationPicture( association.id, @@ -53,6 +57,18 @@ class AssociationPage extends HookConsumerWidget { children: [ Column( children: [ + AsyncChild( + value: associationPicture, + builder: (context, image) { + return Center( + child: CircleAvatar( + radius: 80, + backgroundColor: Colors.white, + backgroundImage: image.image, + ), + ); + }, + ), const SizedBox(height: 20), Text( association.name, @@ -60,17 +76,17 @@ class AssociationPage extends HookConsumerWidget { ), const SizedBox(height: 10), Text( - associationGroupement.name, - style: const TextStyle(fontSize: 20, color: Colors.black), + "${localizeWithContext.phonebookActiveMandate} ${association.mandateYear}", + style: const TextStyle(fontSize: 15, color: Colors.black), ), const SizedBox(height: 10), Text( - association.description, - style: const TextStyle(fontSize: 15, color: Colors.black), + associationGroupement.name, + style: const TextStyle(fontSize: 20, color: Colors.black), ), const SizedBox(height: 10), Text( - "${AppLocalizations.of(context)!.phonebookActiveMandate} ${association.mandateYear}", + association.description, style: const TextStyle(fontSize: 15, color: Colors.black), ), const SizedBox(height: 20), @@ -78,7 +94,7 @@ class AssociationPage extends HookConsumerWidget { value: associationMemberList, builder: (context, associationMembers) => associationMembers.isEmpty - ? Text(AppLocalizations.of(context)!.phonebookNoMember) + ? Text(localizeWithContext.phonebookNoMember) : Column( children: associationMemberSortedList .map( @@ -96,56 +112,28 @@ class AssociationPage extends HookConsumerWidget { .toList(), ), ), + const SizedBox(height: 80), ], ), if (isPresident) Positioned( top: 20, right: 20, - child: GestureDetector( - onTap: () { - QR.to( - PhonebookRouter.root + - PhonebookRouter.associationDetail + - PhonebookRouter.addEditAssociation, + child: CustomIconButton( + onPressed: () { + showCustomBottomModal( + context: context, + modal: AssociationEditionModal( + association: association, + groupement: associationGroupement, + ), + ref: ref, ); }, - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 8, - ), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - gradient: const RadialGradient( - colors: [ - Color.fromARGB(255, 98, 98, 98), - Color.fromARGB(255, 27, 27, 27), - ], - center: Alignment.topLeft, - radius: 1.3, - ), - boxShadow: [ - BoxShadow( - color: const Color.fromARGB( - 255, - 27, - 27, - 27, - ).withValues(alpha: 0.3), - spreadRadius: 5, - blurRadius: 10, - offset: const Offset( - 3, - 3, - ), // changes position of shadow - ), - ], - ), - child: const HeroIcon( - HeroIcons.pencil, - color: Colors.white, - ), + icon: const HeroIcon( + HeroIcons.pencilSquare, + size: 30, + color: Colors.black, ), ), ), diff --git a/lib/phonebook/ui/pages/association_page/web_member_card.dart b/lib/phonebook/ui/pages/association_page/web_member_card.dart index a1a08d64a7..b2b2c3012d 100644 --- a/lib/phonebook/ui/pages/association_page/web_member_card.dart +++ b/lib/phonebook/ui/pages/association_page/web_member_card.dart @@ -39,6 +39,8 @@ class WebMemberCard extends HookConsumerWidget { association, ); + final localizeWithContext = AppLocalizations.of(context)!; + return Padding( padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 5), child: GestureDetector( @@ -47,16 +49,7 @@ class WebMemberCard extends HookConsumerWidget { QR.to(PhonebookRouter.root + PhonebookRouter.memberDetail); }, child: CardLayout( - color: getColorFromTagList( - ref, - member.memberships - .firstWhere( - (element) => - element.associationId == association.id && - element.mandateYear == association.mandateYear, - ) - .rolesTags, - ), + color: getColorFromTagList(ref, assoMembership.rolesTags), margin: EdgeInsets.zero, child: LayoutBuilder( builder: (context, constraints) { @@ -135,28 +128,21 @@ class WebMemberCard extends HookConsumerWidget { mainAxisAlignment: MainAxisAlignment.start, children: [ CardField( - label: AppLocalizations.of( - context, - )!.phonebookPromotion, + label: localizeWithContext.phonebookPromotion, value: member.member.promotion == 0 - ? AppLocalizations.of( - context, - )!.phonebookPromoNotGiven + ? localizeWithContext + .phonebookPromoNotGiven : member.member.promotion < 100 ? "20${member.member.promotion}" : member.member.promotion.toString(), ), CardField( - label: AppLocalizations.of( - context, - )!.phonebookEmail, + label: localizeWithContext.phonebookEmail, value: member.member.email, ), if (member.member.phone != null) CardField( - label: AppLocalizations.of( - context, - )!.phonebookPhone, + label: localizeWithContext.phonebookPhone, value: member.member.phone!, ), ], @@ -220,11 +206,9 @@ class WebMemberCard extends HookConsumerWidget { ), const SizedBox(height: 5), CardField( - label: AppLocalizations.of(context)!.phonebookPromotion, + label: localizeWithContext.phonebookPromotion, value: member.member.promotion == 0 - ? AppLocalizations.of( - context, - )!.phonebookPromoNotGiven + ? localizeWithContext.phonebookPromoNotGiven : member.member.promotion < 100 ? "20${member.member.promotion}" : member.member.promotion.toString(), @@ -235,16 +219,12 @@ class WebMemberCard extends HookConsumerWidget { child: Row( children: [ CardField( - label: AppLocalizations.of( - context, - )!.phonebookEmail, + label: localizeWithContext.phonebookEmail, value: member.member.email, ), if (member.member.phone != null) CardField( - label: AppLocalizations.of( - context, - )!.phonebookPhone, + label: localizeWithContext.phonebookPhone, value: member.member.phone!, ), ], @@ -254,17 +234,13 @@ class WebMemberCard extends HookConsumerWidget { Column( children: [ CardField( - label: AppLocalizations.of( - context, - )!.phonebookEmail, + label: localizeWithContext.phonebookEmail, value: member.member.email, showLabel: false, ), if (member.member.phone != null) CardField( - label: AppLocalizations.of( - context, - )!.phonebookPhone, + label: localizeWithContext.phonebookPhone, value: member.member.phone!, showLabel: false, ), diff --git a/lib/phonebook/ui/pages/main_page/association_card.dart b/lib/phonebook/ui/pages/main_page/association_card.dart index 2552f8ca5d..90c2749ac2 100644 --- a/lib/phonebook/ui/pages/main_page/association_card.dart +++ b/lib/phonebook/ui/pages/main_page/association_card.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/phonebook/class/association.dart'; @@ -37,32 +36,31 @@ class AssociationCard extends HookConsumerWidget { associationGroupementProvider.notifier, ); final associationNotifier = ref.watch(associationProvider.notifier); - return AutoLoaderChild( - group: associationPicture, - notifier: associationPictureMapNotifier, - mapKey: association.id, - loader: (associationId) => - associationPictureNotifier.getAssociationPicture(associationId), - dataBuilder: (context, data) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 5), - child: ListItem( - title: association.name, - subtitle: groupement.name, - icon: CircleAvatar(child: Image(image: data.first.image)), - onTap: () { - associationNotifier.setAssociation(association); - associationPictureNotifier.getAssociationPicture(association.id); - associationGroupementNotifier.setAssociationGroupement( - groupement, - ); - QR.to(PhonebookRouter.root + PhonebookRouter.associationDetail); - }, - ), - ); - }, - errorBuilder: (error, stack) => - const Center(child: HeroIcon(HeroIcons.exclamationCircle)), + return Padding( + padding: const EdgeInsets.symmetric(vertical: 5), + child: ListItem( + title: association.name, + subtitle: groupement.name, + icon: AutoLoaderChild( + group: associationPicture, + notifier: associationPictureMapNotifier, + mapKey: association.id, + loader: (associationId) => + associationPictureNotifier.getAssociationPicture(associationId), + dataBuilder: (context, data) { + return CircleAvatar( + backgroundColor: Colors.white, + child: Image(image: data.first.image), + ); + }, + ), + onTap: () { + associationNotifier.setAssociation(association); + associationPictureNotifier.getAssociationPicture(association.id); + associationGroupementNotifier.setAssociationGroupement(groupement); + QR.to(PhonebookRouter.root + PhonebookRouter.associationDetail); + }, + ), ); } } diff --git a/lib/phonebook/ui/pages/main_page/main_page.dart b/lib/phonebook/ui/pages/main_page/main_page.dart index b71b23f23c..b178b65fad 100644 --- a/lib/phonebook/ui/pages/main_page/main_page.dart +++ b/lib/phonebook/ui/pages/main_page/main_page.dart @@ -38,6 +38,8 @@ class PhonebookMainPage extends HookConsumerWidget { associationGroupementProvider.notifier, ); + final localizeWithContext = AppLocalizations.of(context)!; + return PhonebookTemplate( child: Refresher( onRefresh: () async { @@ -69,36 +71,35 @@ class PhonebookMainPage extends HookConsumerWidget { ], ], ), + const SizedBox(height: 10), Async2Children( values: Tuple2(associationList, associationGroupementList), builder: (context, associations, associationGroupements) { + if (associations.isEmpty) { + return Center( + child: Text( + localizeWithContext.phonebookNoAssociationFound, + ), + ); + } return Column( children: [ - if (associations.isEmpty) - Center( - child: Text( - AppLocalizations.of( - context, - )!.phonebookNoAssociationFound, - ), - ) - else - ...associationFilteredList.map( - (association) => !association.deactivated - ? AssociationCard( - association: association, - groupement: associationGroupements.firstWhere( - (groupement) => - groupement.id == - association.groupementId, - ), - ) - : const SizedBox.shrink(), - ), + ...associationFilteredList.map( + (association) => !association.deactivated + ? AssociationCard( + association: association, + groupement: associationGroupements.firstWhere( + (groupement) => + groupement.id == association.groupementId, + ), + ) + : const SizedBox.shrink(), + ), ], ); }, ), + SizedBox(height: 80), ], ), ), diff --git a/lib/phonebook/ui/pages/member_detail_page/member_detail_page.dart b/lib/phonebook/ui/pages/member_detail_page/member_detail_page.dart index 90a11bf422..2502fa4017 100644 --- a/lib/phonebook/ui/pages/member_detail_page/member_detail_page.dart +++ b/lib/phonebook/ui/pages/member_detail_page/member_detail_page.dart @@ -1,108 +1,137 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/phonebook/providers/association_list_provider.dart'; -import 'package:titan/phonebook/providers/association_provider.dart'; import 'package:titan/phonebook/providers/complete_member_provider.dart'; -import 'package:titan/phonebook/router.dart'; -import 'package:titan/phonebook/ui/pages/member_detail_page/element_field.dart'; +import 'package:titan/phonebook/providers/member_pictures_provider.dart'; +import 'package:titan/phonebook/providers/profile_picture_provider.dart'; import 'package:titan/phonebook/ui/pages/member_detail_page/membership_card.dart'; import 'package:titan/phonebook/ui/phonebook.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; -import 'package:titan/tools/ui/layouts/card_layout.dart'; -import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/l10n/app_localizations.dart'; +import 'package:titan/tools/ui/builders/auto_loader_child.dart'; class MemberDetailPage extends HookConsumerWidget { const MemberDetailPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - final memberProvider = ref.watch(completeMemberProvider); - final associationNotifier = ref.watch(associationProvider.notifier); + final member = ref.watch(completeMemberProvider); final associationList = ref.watch(associationListProvider); + final memberPictures = ref.watch( + memberPicturesProvider.select((value) => value[member]), + ); + final memberPicturesNotifier = ref.watch(memberPicturesProvider.notifier); + final profilePictureNotifier = ref.watch(profilePictureProvider.notifier); + + AppLocalizations localizeWithContext = AppLocalizations.of(context)!; + + final sortedMemberships = [...member.memberships]; + sortedMemberships.sort((a, b) => a.mandateYear.compareTo(b.mandateYear)); + return PhonebookTemplate( - child: SingleChildScrollView( - child: Column( - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 30), - child: CardLayout( - margin: EdgeInsets.zero, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 30), + child: SingleChildScrollView( + child: Column( + children: [ + Center( child: Column( children: [ - ElementField( - label: AppLocalizations.of(context)!.phonebookName, - value: memberProvider.member.name, - ), - ElementField( - label: AppLocalizations.of(context)!.phonebookFirstname, - value: memberProvider.member.firstname, - ), - if (memberProvider.member.nickname != null) - ElementField( - label: AppLocalizations.of(context)!.phonebookNickname, - value: memberProvider.member.nickname!, + AutoLoaderChild( + group: memberPictures, + notifier: memberPicturesNotifier, + mapKey: member, + loader: (ref) => profilePictureNotifier.getProfilePicture( + member.member.id, + ), + loadingBuilder: (context) => const CircleAvatar( + radius: 80, + child: CircularProgressIndicator(), + ), + dataBuilder: (context, data) => CircleAvatar( + radius: 80, + child: Image(image: data.first.image), ), - ElementField( - label: AppLocalizations.of(context)!.phonebookEmail, - value: memberProvider.member.email, ), - if (memberProvider.member.phone != null) - ElementField( - label: AppLocalizations.of(context)!.phonebookPhone, - value: memberProvider.member.phone!, + if (member.member.nickname != null) ...[ + Text( + member.member.nickname!, + style: const TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + ), ), - ElementField( - label: AppLocalizations.of(context)!.phonebookPromotion, - value: memberProvider.member.promotion == 0 - ? AppLocalizations.of(context)!.phonebookPromoNotGiven - : memberProvider.member.promotion < 100 - ? "20${memberProvider.member.promotion}" - : memberProvider.member.promotion.toString(), + const SizedBox(height: 10), + Text( + "${member.member.firstname} ${member.member.name}", + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + ] else + Text( + "${member.member.firstname} ${member.member.name}", + style: const TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 10), + if (member.member.promotion != 0) + Text( + "${localizeWithContext.phonebookPromotion} ${member.member.promotion < 100 ? '20${member.member.promotion}' : member.member.promotion.toString()}", + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 20), + Text( + member.member.email, + style: const TextStyle(fontSize: 16), ), + const SizedBox(height: 10), + if (member.member.phone != null) + Text( + member.member.phone!, + style: const TextStyle(fontSize: 16), + ), ], ), ), - ), - const SizedBox(height: 20), - if (memberProvider.memberships.isNotEmpty) - Text( - memberProvider.memberships.length == 1 - ? AppLocalizations.of(context)!.phonebookAssociation - : AppLocalizations.of(context)!.phonebookAssociations, - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 20, + const SizedBox(height: 20), + if (member.memberships.isNotEmpty) + Text( + member.memberships.length == 1 + ? localizeWithContext.phonebookAssociation + : localizeWithContext.phonebookAssociations, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 20, + ), + ), + const SizedBox(height: 20), + AsyncChild( + value: associationList, + builder: (context, associations) => Column( + children: [ + ...sortedMemberships.map((membership) { + final membershipAssociation = associations.firstWhere( + (association) => + association.id == membership.associationId, + ); + return MembershipCard( + association: membershipAssociation, + membership: membership, + ); + }), + ], ), ), - const SizedBox(height: 20), - AsyncChild( - value: associationList, - builder: (context, associations) => Column( - children: [ - ...memberProvider.memberships.map((membership) { - final associationMembership = associations.firstWhere( - (association) => - association.id == membership.associationId, - ); - return MembershipCard( - association: associationMembership, - onClicked: () { - associationNotifier.setAssociation( - associationMembership, - ); - QR.to( - PhonebookRouter.root + - PhonebookRouter.associationDetail, - ); - }, - membership: membership, - ); - }), - ], - ), - ), - ], + const SizedBox(height: 80), + ], + ), ), ), ); diff --git a/lib/phonebook/ui/pages/member_detail_page/membership_card.dart b/lib/phonebook/ui/pages/member_detail_page/membership_card.dart index 1775c1ccb8..d1a0d966a2 100644 --- a/lib/phonebook/ui/pages/member_detail_page/membership_card.dart +++ b/lib/phonebook/ui/pages/member_detail_page/membership_card.dart @@ -1,44 +1,59 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/phonebook/class/association.dart'; import 'package:titan/phonebook/class/membership.dart'; -import 'package:titan/tools/ui/layouts/card_layout.dart'; +import 'package:titan/phonebook/providers/association_picture_provider.dart'; +import 'package:titan/phonebook/providers/association_provider.dart'; +import 'package:titan/phonebook/providers/associations_picture_map_provider.dart'; +import 'package:titan/phonebook/router.dart'; +import 'package:titan/tools/ui/builders/auto_loader_child.dart'; +import 'package:titan/tools/ui/styleguide/list_item.dart'; class MembershipCard extends HookConsumerWidget { const MembershipCard({ super.key, required this.association, required this.membership, - required this.onClicked, }); final Association association; - final VoidCallback onClicked; final Membership membership; @override Widget build(BuildContext context, WidgetRef ref) { + final associationNotifier = ref.watch(associationProvider.notifier); + final associationPicture = ref.watch( + associationPictureMapProvider.select((value) => value[association.id]), + ); + final associationPictureMapNotifier = ref.watch( + associationPictureMapProvider.notifier, + ); + final associationPictureNotifier = ref.watch( + associationPictureProvider.notifier, + ); return Padding( - padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 5), - child: GestureDetector( - onTap: onClicked, - child: CardLayout( - margin: EdgeInsets.zero, - child: Row( - children: [ - const SizedBox(width: 10), - Text( - "${association.name} - ${membership.mandateYear}", - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - const Spacer(flex: 1), - Text(membership.apparentName), - ], - ), + padding: const EdgeInsets.symmetric(vertical: 5.0), + child: ListItem( + title: "${association.name} - ${membership.apparentName}", + subtitle: membership.mandateYear.toString(), + icon: AutoLoaderChild( + group: associationPicture, + notifier: associationPictureMapNotifier, + mapKey: association.id, + loader: (associationId) => + associationPictureNotifier.getAssociationPicture(associationId), + dataBuilder: (context, data) { + return CircleAvatar( + backgroundColor: Colors.white, + child: Image(image: data.first.image), + ); + }, ), + onTap: () { + associationNotifier.setAssociation(association); + QR.to(PhonebookRouter.root + PhonebookRouter.associationDetail); + }, ), ); } diff --git a/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart b/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart index f1004d81d1..e4d7aa484c 100644 --- a/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart +++ b/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart @@ -1,28 +1,24 @@ -import 'dart:math'; - import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/phonebook/class/membership.dart'; import 'package:titan/phonebook/providers/association_member_list_provider.dart'; import 'package:titan/phonebook/providers/association_provider.dart'; -import 'package:titan/phonebook/providers/member_role_tags_provider.dart'; import 'package:titan/phonebook/providers/membership_provider.dart'; import 'package:titan/phonebook/providers/phonebook_admin_provider.dart'; import 'package:titan/phonebook/providers/roles_tags_provider.dart'; +import 'package:titan/phonebook/ui/pages/membership_editor_page/user_search_modal.dart'; import 'package:titan/phonebook/ui/phonebook.dart'; -import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; -import 'package:titan/tools/ui/builders/waiting_button.dart'; -import 'package:titan/tools/ui/layouts/add_edit_button_layout.dart'; +import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; +import 'package:titan/tools/ui/styleguide/button.dart'; +import 'package:titan/tools/ui/styleguide/list_item_template.dart'; +import 'package:titan/tools/ui/styleguide/list_item_toggle.dart'; import 'package:titan/tools/ui/widgets/align_left_text.dart'; -import 'package:titan/tools/ui/widgets/styled_search_bar.dart'; import 'package:titan/tools/ui/widgets/text_entry.dart'; -import 'package:titan/user/providers/user_list_provider.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/phonebook/providers/complete_member_provider.dart'; -import 'package:titan/phonebook/ui/pages/membership_editor_page/search_result.dart'; import 'package:titan/l10n/app_localizations.dart'; class MembershipEditorPage extends HookConsumerWidget { @@ -31,9 +27,6 @@ class MembershipEditorPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final rolesTagList = ref.watch(rolesTagsProvider); - final queryController = useTextEditingController(text: ''); - final usersNotifier = ref.watch(userList.notifier); - final rolesTagsNotifier = ref.watch(rolesTagsProvider.notifier); final member = ref.watch(completeMemberProvider); final membership = ref.watch(membershipProvider); final association = ref.watch(associationProvider); @@ -41,8 +34,6 @@ class MembershipEditorPage extends HookConsumerWidget { final associationMemberListNotifier = ref.watch( associationMemberListProvider.notifier, ); - final memberRoleTagsNotifier = ref.watch(memberRoleTagsProvider.notifier); - final memberRoleTags = ref.watch(memberRoleTagsProvider); final apparentNameController = useTextEditingController( text: membership.apparentName, ); @@ -53,226 +44,197 @@ class MembershipEditorPage extends HookConsumerWidget { displayToast(context, type, msg); } + final selectedTags = useState>( + List.from(membership.rolesTags), + ); + + final localizeWithContext = AppLocalizations.of(context)!; + + Future addMember() async { + // Test if the membership already exists with (association_id,member_id,mandate_year) + final memberAssociationMemberships = member.memberships.where( + (membership) => membership.associationId == association.id, + ); + + if (memberAssociationMemberships + .where( + (membership) => membership.mandateYear == association.mandateYear, + ) + .isNotEmpty) { + displayToastWithContext( + TypeMsg.msg, + localizeWithContext.phonebookExistingMembership, + ); + return; + } + + final membershipAdd = Membership( + id: "", + memberId: member.member.id, + associationId: association.id, + rolesTags: selectedTags.value, + apparentName: apparentNameController.text, + mandateYear: association.mandateYear, + order: associationMembers.maybeWhen( + data: (members) => members.length, + orElse: () => 0, + ), + ); + final value = await associationMemberListNotifier.addMember( + member, + membershipAdd, + ); + if (value) { + displayToastWithContext( + TypeMsg.msg, + localizeWithContext.phonebookAddedMember, + ); + QR.back(); + } else { + displayToastWithContext( + TypeMsg.error, + localizeWithContext.phonebookAddingError, + ); + } + } + + Future updateMember() async { + final membershipEdit = Membership( + id: membership.id, + memberId: membership.memberId, + associationId: membership.associationId, + rolesTags: selectedTags.value, + apparentName: apparentNameController.text, + mandateYear: membership.mandateYear, + order: membership.order, + ); + member.memberships[member.memberships.indexWhere( + (membership) => membership.id == membershipEdit.id, + )] = + membershipEdit; + final value = await associationMemberListNotifier.updateMember( + member, + membershipEdit, + ); + if (value) { + associationMemberListNotifier.loadMembers( + association.id, + association.mandateYear, + ); + displayToastWithContext( + TypeMsg.msg, + localizeWithContext.phonebookUpdatedMember, + ); + QR.back(); + } else { + displayToastWithContext( + TypeMsg.error, + localizeWithContext.phonebookUpdatingError, + ); + } + } + return PhonebookTemplate( child: Padding( padding: const EdgeInsets.all(30.0), child: SingleChildScrollView( child: Column( children: [ - AlignLeftText( - isEdit - ? AppLocalizations.of(context)!.phonebookEditMembership - : AppLocalizations.of(context)!.phonebookAddMember, - ), if (!isEdit) ...[ - StyledSearchBar( - padding: EdgeInsets.zero, - label: AppLocalizations.of(context)!.phonebookMember, - editingController: queryController, - onChanged: (value) async { - tokenExpireWrapper(ref, () async { - if (value.isNotEmpty) { - await usersNotifier.filterUsers(value); - } else { - usersNotifier.clear(); - } - }); - }, + AlignLeftText(localizeWithContext.phonebookAddMember), + ListItemTemplate( + title: member.member.id == "" + ? localizeWithContext.phonebookSearchMember + : member.member.getName(), + subtitle: member.member.id == "" + ? "" + : localizeWithContext.phonebookSearchMember, + trailing: SizedBox.shrink(), + onTap: () => showCustomBottomModal( + context: context, + modal: UserSearchModal(), + ref: ref, + ), ), - SearchResult(queryController: queryController), ] else - member.member.nickname == null - ? Text( - "${member.member.firstname} ${member.member.name}", - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - ), - ) - : Text( - "${member.member.nickname} (${member.member.firstname} ${member.member.name})", - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - ), - ), + Text( + localizeWithContext.phonebookModifyMembership( + member.member.getName(), + ), + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + ), + ), const SizedBox(height: 10), - SizedBox( - width: min(MediaQuery.of(context).size.width, 300), - child: Column( - children: [ - ...rolesTagList.keys.map( - (tagKey) => Row( - children: [ - Text(tagKey), - const Spacer(), - Checkbox( - value: rolesTagList[tagKey]!.maybeWhen( - data: (rolesTag) => rolesTag[0], - orElse: () => false, - ), - fillColor: - rolesTagList.keys.first == tagKey && - !isPhonebookAdmin - ? WidgetStateProperty.all(Colors.black) - : WidgetStateProperty.all(Colors.grey), - onChanged: - rolesTagList.keys.first == tagKey && - !isPhonebookAdmin - ? null - : (value) { - rolesTagList[tagKey] = AsyncData([value!]); - memberRoleTagsNotifier - .setRoleTagsWithFilter(rolesTagList); - rolesTagsNotifier.setTData( - tagKey, - AsyncData([value]), - ); - if (value && - apparentNameController.text == "") { - apparentNameController.text = tagKey; - } else if (!value && - apparentNameController.text == tagKey) { - apparentNameController.text = ""; + rolesTagList.maybeWhen( + orElse: () => Text(localizeWithContext.phonebookNoRoleTags), + data: (tagList) { + return Column( + children: tagList + .map( + (tag) => ToggleListItem( + title: tag, + onTap: tagList.first == tag && !isPhonebookAdmin + ? () {} + : () { + final tags = [...selectedTags.value]; + final changeApparentName = + apparentNameController.text == + tags.join(", "); + tags.contains(tag) + ? tags.remove(tag) + : tags.add(tag); + if (changeApparentName) { + apparentNameController.text = tags.join( + ", ", + ); } + selectedTags.value = tags; }, + selected: selectedTags.value.contains(tag), ), - ], - ), - ), - ], - ), + ) + .toList(), + ); + }, ), const SizedBox(height: 30), TextEntry( controller: apparentNameController, - label: AppLocalizations.of(context)!.phonebookApparentName, + label: localizeWithContext.phonebookApparentName, ), const SizedBox(height: 50), - WaitingButton( - builder: (child) => AddEditButtonLayout( - colors: const [ - ColorConstants.gradient1, - ColorConstants.gradient2, - ], - child: child, - ), - child: Text( - !isEdit - ? AppLocalizations.of(context)!.phonebookAdd - : AppLocalizations.of(context)!.phonebookEdit, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - color: Color.fromARGB(255, 255, 255, 255), - ), - ), - onTap: () async { + Button( + text: isEdit + ? localizeWithContext.phonebookEdit + : localizeWithContext.phonebookAdd, + onPressed: () async { if (member.member.id == "") { displayToastWithContext( TypeMsg.msg, - AppLocalizations.of(context)!.phonebookEmptyMember, + localizeWithContext.phonebookEmptyMember, ); return; } if (apparentNameController.text == "") { displayToastWithContext( TypeMsg.msg, - AppLocalizations.of(context)!.phonebookEmptyApparentName, + localizeWithContext.phonebookEmptyApparentName, ); return; } tokenExpireWrapper(ref, () async { if (isEdit) { - final membershipEdit = Membership( - id: membership.id, - memberId: membership.memberId, - associationId: membership.associationId, - rolesTags: memberRoleTags, - apparentName: apparentNameController.text, - mandateYear: membership.mandateYear, - order: membership.order, - ); - member.memberships[member.memberships.indexWhere( - (membership) => membership.id == membershipEdit.id, - )] = - membershipEdit; - final updatedMemberMsg = AppLocalizations.of( - context, - )!.phonebookUpdatedMember; - final updatingErrorMsg = AppLocalizations.of( - context, - )!.phonebookUpdatingError; - final value = await associationMemberListNotifier - .updateMember(member, membershipEdit); - if (value) { - associationMemberListNotifier.loadMembers( - association.id, - association.mandateYear.toString(), - ); - displayToastWithContext(TypeMsg.msg, updatedMemberMsg); - QR.back(); - } else { - displayToastWithContext( - TypeMsg.error, - updatingErrorMsg, - ); - } + await updateMember(); } else { - // Test if the membership already exists with (association_id,member_id,mandate_year) - final memberAssociationMemberships = member.memberships - .where( - (membership) => - membership.associationId == association.id, - ); - - if (memberAssociationMemberships - .where( - (membership) => - membership.mandateYear == - association.mandateYear, - ) - .isNotEmpty) { - displayToastWithContext( - TypeMsg.msg, - AppLocalizations.of( - context, - )!.phonebookExistingMembership, - ); - return; - } - - final membershipAdd = Membership( - id: "", - memberId: member.member.id, - associationId: association.id, - rolesTags: memberRoleTags, - apparentName: apparentNameController.text, - mandateYear: association.mandateYear, - order: associationMembers.maybeWhen( - data: (members) => members.length, - orElse: () => 0, - ), - ); - final addedMemberMsg = AppLocalizations.of( - context, - )!.phonebookAddedMember; - final addingErrorMsg = AppLocalizations.of( - context, - )!.phonebookAddingError; - final value = await associationMemberListNotifier - .addMember(member, membershipAdd); - if (value) { - displayToastWithContext(TypeMsg.msg, addedMemberMsg); - QR.back(); - } else { - displayToastWithContext(TypeMsg.error, addingErrorMsg); - } + await addMember(); } }); }, ), + const SizedBox(height: 80), ], ), ), diff --git a/lib/phonebook/ui/pages/membership_editor_page/search_result.dart b/lib/phonebook/ui/pages/membership_editor_page/search_result.dart index d4b0244735..a8e29fd485 100644 --- a/lib/phonebook/ui/pages/membership_editor_page/search_result.dart +++ b/lib/phonebook/ui/pages/membership_editor_page/search_result.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/phonebook/class/member.dart'; import 'package:titan/phonebook/providers/complete_member_provider.dart'; +import 'package:titan/tools/ui/styleguide/list_item_template.dart'; import 'package:titan/user/providers/user_list_provider.dart'; class SearchResult extends HookConsumerWidget { @@ -19,41 +20,8 @@ class SearchResult extends HookConsumerWidget { return Column( children: usersData .map( - (user) => GestureDetector( - behavior: HitTestBehavior.opaque, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 4.0), - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - color: Colors.white, - boxShadow: [ - BoxShadow( - color: Colors.black.withValues(alpha: 0.1), - offset: const Offset(0, 1), - blurRadius: 4, - spreadRadius: 2, - ), - ], - ), - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 14), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container(width: 20), - Expanded( - child: Text( - user.getName(), - style: const TextStyle(fontSize: 13), - overflow: TextOverflow.ellipsis, - ), - ), - ], - ), - ), - ), - ), + (user) => ListItemTemplate( + title: user.getName(), onTap: () { memberNotifier.setMember(Member.fromUser(user)); queryController.text = user.getName(); diff --git a/lib/phonebook/ui/pages/membership_editor_page/user_search_modal.dart b/lib/phonebook/ui/pages/membership_editor_page/user_search_modal.dart new file mode 100644 index 0000000000..2f60d86e58 --- /dev/null +++ b/lib/phonebook/ui/pages/membership_editor_page/user_search_modal.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/phonebook/ui/pages/membership_editor_page/search_result.dart'; +import 'package:titan/tools/token_expire_wrapper.dart'; +import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; +import 'package:titan/tools/ui/styleguide/searchbar.dart'; +import 'package:titan/user/providers/user_list_provider.dart'; + +class UserSearchModal extends HookConsumerWidget { + const UserSearchModal({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final usersNotifier = ref.watch(userList.notifier); + final textController = useTextEditingController(); + + return BottomModalTemplate( + title: "title", + type: BottomModalType.main, + child: SingleChildScrollView( + child: Column( + children: [ + SearchResult(queryController: textController), + CustomSearchBar( + autofocus: true, + onSearch: (value) => tokenExpireWrapper(ref, () async { + if (value.isNotEmpty) { + await usersNotifier.filterUsers(value); + textController.text = value; + } else { + usersNotifier.clear(); + textController.clear(); + } + }), + ), + ], + ), + ), + ); + } +} diff --git a/lib/tools/ui/styleguide/button.dart b/lib/tools/ui/styleguide/button.dart index d8ca74c000..d9fd142337 100644 --- a/lib/tools/ui/styleguide/button.dart +++ b/lib/tools/ui/styleguide/button.dart @@ -7,6 +7,7 @@ class Button extends StatelessWidget { final ButtonType type; final String text; final bool? disabled; + final double? fontSize; final Function() onPressed; const Button({ @@ -15,6 +16,7 @@ class Button extends StatelessWidget { required this.text, required this.onPressed, this.disabled = false, + this.fontSize, }); const Button.danger({ @@ -22,6 +24,7 @@ class Button extends StatelessWidget { required this.text, required this.onPressed, this.disabled = false, + this.fontSize, }) : type = ButtonType.danger; const Button.onDanger({ @@ -29,6 +32,7 @@ class Button extends StatelessWidget { required this.text, required this.onPressed, this.disabled = false, + this.fontSize, }) : type = ButtonType.onDanger; const Button.secondary({ @@ -36,6 +40,7 @@ class Button extends StatelessWidget { required this.text, required this.onPressed, this.disabled = false, + this.fontSize, }) : type = ButtonType.secondary; Color get backgroundColor { @@ -99,7 +104,7 @@ class Button extends StatelessWidget { text, style: TextStyle( color: textColor, - fontSize: 18, + fontSize: fontSize ?? 18, fontWeight: FontWeight.w900, ), ), diff --git a/lib/tools/ui/styleguide/custom_dialog_box.dart b/lib/tools/ui/styleguide/custom_dialog_box.dart new file mode 100644 index 0000000000..f87ef7bf01 --- /dev/null +++ b/lib/tools/ui/styleguide/custom_dialog_box.dart @@ -0,0 +1,141 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/l10n/app_localizations.dart'; +import 'package:titan/navigation/providers/navbar_animation.dart'; +import 'package:titan/tools/constants.dart'; +import 'package:titan/tools/ui/styleguide/button.dart'; + +const double padding = 20.0; + +enum ModalType { main, danger } + +class CustomDialogBox extends StatelessWidget { + final String title, description; + final String? yesText, noText; + final ModalType type; + final Function() onYes; + final Function()? onNo; + + const CustomDialogBox({ + super.key, + required this.title, + required this.description, + required this.onYes, + this.type = ModalType.main, + this.onNo, + this.yesText, + this.noText, + }); + + const CustomDialogBox.danger({ + super.key, + required this.title, + required this.description, + required this.onYes, + this.onNo, + this.yesText, + this.noText, + }) : type = ModalType.danger; + + @override + Widget build(BuildContext context) { + AppLocalizations localizeWithContext = AppLocalizations.of(context)!; + return GestureDetector( + onTap: () { + Navigator.of(context).pop(); + }, + child: Dialog( + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(20.0)), + ), + elevation: 0.0, + insetPadding: const EdgeInsets.all(20.0), + backgroundColor: type == ModalType.main + ? ColorConstants.background + : ColorConstants.main, + child: Padding( + padding: const EdgeInsets.all(padding), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + title, + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w900, + color: type == ModalType.main + ? ColorConstants.tertiary + : ColorConstants.background, + ), + ), + SizedBox(height: 20), + Text( + description, + style: TextStyle( + fontSize: 15, + color: type == ModalType.main + ? ColorConstants.tertiary + : ColorConstants.background, + ), + ), + SizedBox(height: 20), + SizedBox( + width: 270, + child: Row( + children: [ + Expanded( + child: Button( + text: noText ?? localizeWithContext.globalCancel, + onPressed: () { + Navigator.of(context).pop(); + if (onNo != null) { + onNo!(); + } + }, + type: ButtonType.secondary, + fontSize: 18, + ), + ), + SizedBox(width: 10), + Expanded( + child: Button( + text: yesText ?? localizeWithContext.globalConfirm, + onPressed: () { + Navigator.of(context).pop(); + onYes(); + }, + type: type == ModalType.main + ? ButtonType.main + : ButtonType.onDanger, + fontSize: 18, + ), + ), + ], + ), + ), + ], + ), + ), + ), + ); + } +} + +Future showCustomDialog({ + required BuildContext context, + required Widget dialog, + required WidgetRef ref, + Function? onCloseCallback, +}) async { + final navbarAnimationNotifier = ref.watch(navbarAnimationProvider.notifier); + navbarAnimationNotifier.toggle(); + await showDialog( + useRootNavigator: true, + context: context, + builder: (_) => dialog, + ).then((value) { + navbarAnimationNotifier.toggle(); + onCloseCallback?.call(); + }); +} diff --git a/pubspec.yaml b/pubspec.yaml index 129a18faeb..41d3147e26 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -106,6 +106,7 @@ flutter: - assets/images/pipe.png - assets/images/logo_flappybird.svg - assets/images/helloasso.svg + - assets/images/vache.png uses-material-design: true generate: true From 777dbe4624ffcdfe17b9f0c5708b7cdd128059f0 Mon Sep 17 00:00:00 2001 From: Thonyk Date: Mon, 4 Aug 2025 19:06:57 +0200 Subject: [PATCH 091/473] fix: UI improvments --- lib/l10n/app_en.arb | 13 +- lib/l10n/app_fr.arb | 13 +- lib/l10n/app_localizations.dart | 42 ++--- lib/l10n/app_localizations_en.dart | 23 ++- lib/l10n/app_localizations_fr.dart | 23 ++- .../providers/phonebook_admin_provider.dart | 43 +++-- lib/phonebook/tools/constants.dart | 7 - lib/phonebook/tools/function.dart | 1 + .../components/association_research_bar.dart | 2 +- .../ui/pages/admin_page/admin_page.dart | 3 + .../association_add_edit_page.dart | 6 +- .../association_members_page.dart | 4 +- .../association_page/association_page.dart | 160 +++++++++--------- .../ui/pages/main_page/main_page.dart | 5 +- .../member_detail_page/element_field.dart | 40 ----- .../member_detail_page.dart | 9 +- .../membership_editor_page/search_result.dart | 1 + lib/phonebook/ui/phonebook.dart | 15 +- lib/tools/middlewares/admin_middleware.dart | 2 +- .../ui/styleguide/list_item_template.dart | 2 +- lib/tools/ui/styleguide/text_entry.dart | 7 +- 21 files changed, 191 insertions(+), 230 deletions(-) delete mode 100644 lib/phonebook/tools/constants.dart delete mode 100644 lib/phonebook/ui/pages/member_detail_page/element_field.dart diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 7536b9f05c..2b1b093035 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -449,6 +449,7 @@ "eventDaySun": "Sunday", "globalConfirm": "Confirm", "globalCancel": "Cancel", + "globalOptionnal": "Optional", "homeCalendar": "Calendar", "homeEventOf": "Events of", "homeIncomingEvents": "Upcoming events", @@ -633,7 +634,6 @@ "phSeePreviousJournal": "See previous journals", "phNoJournalInDatabase": "No PH yet in database", "phSuccesDowloading": "Successfully downloaded", - "phonebookActiveMandate": "Active mandate:", "phonebookAdd": "Add", "phonebookAddAssociation": "Add an association", "phonebookAddAssociationGroupement": "Add an association groupement", @@ -642,16 +642,14 @@ "phonebookAddingError": "Error adding", "phonebookAddMember": "Add a member", "phonebookAddRole": "Add a role", - "phonebookAdmin": "Admin", - "phonebookAdminPage": "Admin page", + "phonebookAdmin": "Administation", "phonebookAll": "All", "phonebookApparentName": "Public role name:", - "phonebookAssociation": "Association:", + "phonebookAssociation": "Association", "phonebookAssociationDetail": "Association details:", "phonebookAssociationKind": "Type of association:", - "phonebookAssociationPure": "Association", - "phonebookAssociationPureSearch": " Association", - "phonebookAssociations": "Associations:", + "phonebookAssociationName": "Association name", + "phonebookAssociations": "Associations", "phonebookCancel": "Cancel", "phonebookChangeMandate": "Switch to mandate ", "phonebookChangeMandateConfirm": "Are you sure you want to change the entire mandate?\nThis action is irreversible!", @@ -702,6 +700,7 @@ } } }, + "phonebookMandate": "Mandate", "phonebookMandateChangingError": "Error changing mandate", "phonebookMember": "Member", "phonebookMemberReordered": "Member reordered", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 551ea902bb..899de6639c 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -449,6 +449,7 @@ "eventDaySun": "Dimanche", "globalConfirm": "Confirmer", "globalCancel": "Annuler", + "globalOptionnal": "Optionnel", "homeCalendar": "Calendrier", "homeEventOf": "Évènements du", "homeIncomingEvents": "Évènements à venir", @@ -633,7 +634,6 @@ "phSeePreviousJournal": "Voir les anciens journaux", "phNoJournalInDatabase": "Pas encore de PH dans la base de donnée", "phSuccesDowloading": "Téléchargé avec succès", - "phonebookActiveMandate": "Mandat actif :", "phonebookAdd": "Ajouter", "phonebookAddAssociation": "Ajouter une association", "phonebookAddAssociationGroupement": "Ajouter un groupement d'association", @@ -642,16 +642,14 @@ "phonebookAddingError": "Erreur lors de l'ajout", "phonebookAddMember": "Ajouter un membre", "phonebookAddRole": "Ajouter un rôle", - "phonebookAdmin": "Admin", - "phonebookAdminPage": "Page Administrateur", + "phonebookAdmin": "Administration", "phonebookAll": "Toutes", "phonebookApparentName": "Nom public du rôle :", - "phonebookAssociation": "Association :", + "phonebookAssociation": "Association", "phonebookAssociationDetail": "Détail de l'association :", "phonebookAssociationKind": "Type d'association :", - "phonebookAssociationPure": "Association", - "phonebookAssociationPureSearch": " Association", - "phonebookAssociations": "Associations :", + "phonebookAssociationName": "Nom de l'association", + "phonebookAssociations": "Associations", "phonebookCancel": "Annuler", "phonebookChangeMandate": "Passer au mandat ", "phonebookChangeMandateConfirm": "Êtes-vous sûr de vouloir changer tout le mandat ?\nCette action est irréversible !", @@ -702,6 +700,7 @@ } } }, + "phonebookMandate": "Mandat", "phonebookMandateChangingError": "Erreur lors du changement de mandat", "phonebookMember": "Membre", "phonebookMemberReordered": "Membre réordonné", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 7a5396de1a..b8e497a3aa 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -2792,6 +2792,12 @@ abstract class AppLocalizations { /// **'Annuler'** String get globalCancel; + /// No description provided for @globalOptionnal. + /// + /// In fr, this message translates to: + /// **'Optionnel'** + String get globalOptionnal; + /// No description provided for @homeCalendar. /// /// In fr, this message translates to: @@ -3896,12 +3902,6 @@ abstract class AppLocalizations { /// **'Téléchargé avec succès'** String get phSuccesDowloading; - /// No description provided for @phonebookActiveMandate. - /// - /// In fr, this message translates to: - /// **'Mandat actif :'** - String get phonebookActiveMandate; - /// No description provided for @phonebookAdd. /// /// In fr, this message translates to: @@ -3953,15 +3953,9 @@ abstract class AppLocalizations { /// No description provided for @phonebookAdmin. /// /// In fr, this message translates to: - /// **'Admin'** + /// **'Administration'** String get phonebookAdmin; - /// No description provided for @phonebookAdminPage. - /// - /// In fr, this message translates to: - /// **'Page Administrateur'** - String get phonebookAdminPage; - /// No description provided for @phonebookAll. /// /// In fr, this message translates to: @@ -3977,7 +3971,7 @@ abstract class AppLocalizations { /// No description provided for @phonebookAssociation. /// /// In fr, this message translates to: - /// **'Association :'** + /// **'Association'** String get phonebookAssociation; /// No description provided for @phonebookAssociationDetail. @@ -3992,22 +3986,16 @@ abstract class AppLocalizations { /// **'Type d\'association :'** String get phonebookAssociationKind; - /// No description provided for @phonebookAssociationPure. - /// - /// In fr, this message translates to: - /// **'Association'** - String get phonebookAssociationPure; - - /// No description provided for @phonebookAssociationPureSearch. + /// No description provided for @phonebookAssociationName. /// /// In fr, this message translates to: - /// **' Association'** - String get phonebookAssociationPureSearch; + /// **'Nom de l\'association'** + String get phonebookAssociationName; /// No description provided for @phonebookAssociations. /// /// In fr, this message translates to: - /// **'Associations :'** + /// **'Associations'** String get phonebookAssociations; /// No description provided for @phonebookCancel. @@ -4262,6 +4250,12 @@ abstract class AppLocalizations { /// **'Gérer les groupes de {association}'** String phonebookGroups(String association); + /// No description provided for @phonebookMandate. + /// + /// In fr, this message translates to: + /// **'Mandat'** + String get phonebookMandate; + /// No description provided for @phonebookMandateChangingError. /// /// In fr, this message translates to: diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 2b51a5cd48..3f6df3a892 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -1370,6 +1370,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get globalCancel => 'Cancel'; + @override + String get globalOptionnal => 'Optional'; + @override String get homeCalendar => 'Calendar'; @@ -1932,9 +1935,6 @@ class AppLocalizationsEn extends AppLocalizations { @override String get phSuccesDowloading => 'Successfully downloaded'; - @override - String get phonebookActiveMandate => 'Active mandate:'; - @override String get phonebookAdd => 'Add'; @@ -1961,10 +1961,7 @@ class AppLocalizationsEn extends AppLocalizations { String get phonebookAddRole => 'Add a role'; @override - String get phonebookAdmin => 'Admin'; - - @override - String get phonebookAdminPage => 'Admin page'; + String get phonebookAdmin => 'Administation'; @override String get phonebookAll => 'All'; @@ -1973,7 +1970,7 @@ class AppLocalizationsEn extends AppLocalizations { String get phonebookApparentName => 'Public role name:'; @override - String get phonebookAssociation => 'Association:'; + String get phonebookAssociation => 'Association'; @override String get phonebookAssociationDetail => 'Association details:'; @@ -1982,13 +1979,10 @@ class AppLocalizationsEn extends AppLocalizations { String get phonebookAssociationKind => 'Type of association:'; @override - String get phonebookAssociationPure => 'Association'; - - @override - String get phonebookAssociationPureSearch => ' Association'; + String get phonebookAssociationName => 'Association name'; @override - String get phonebookAssociations => 'Associations:'; + String get phonebookAssociations => 'Associations'; @override String get phonebookCancel => 'Cancel'; @@ -2129,6 +2123,9 @@ class AppLocalizationsEn extends AppLocalizations { return 'Manage $association groups'; } + @override + String get phonebookMandate => 'Mandate'; + @override String get phonebookMandateChangingError => 'Error changing mandate'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 21b08a9fec..fca1fdd6a7 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -1376,6 +1376,9 @@ class AppLocalizationsFr extends AppLocalizations { @override String get globalCancel => 'Annuler'; + @override + String get globalOptionnal => 'Optionnel'; + @override String get homeCalendar => 'Calendrier'; @@ -1941,9 +1944,6 @@ class AppLocalizationsFr extends AppLocalizations { @override String get phSuccesDowloading => 'Téléchargé avec succès'; - @override - String get phonebookActiveMandate => 'Mandat actif :'; - @override String get phonebookAdd => 'Ajouter'; @@ -1970,10 +1970,7 @@ class AppLocalizationsFr extends AppLocalizations { String get phonebookAddRole => 'Ajouter un rôle'; @override - String get phonebookAdmin => 'Admin'; - - @override - String get phonebookAdminPage => 'Page Administrateur'; + String get phonebookAdmin => 'Administration'; @override String get phonebookAll => 'Toutes'; @@ -1982,7 +1979,7 @@ class AppLocalizationsFr extends AppLocalizations { String get phonebookApparentName => 'Nom public du rôle :'; @override - String get phonebookAssociation => 'Association :'; + String get phonebookAssociation => 'Association'; @override String get phonebookAssociationDetail => 'Détail de l\'association :'; @@ -1991,13 +1988,10 @@ class AppLocalizationsFr extends AppLocalizations { String get phonebookAssociationKind => 'Type d\'association :'; @override - String get phonebookAssociationPure => 'Association'; - - @override - String get phonebookAssociationPureSearch => ' Association'; + String get phonebookAssociationName => 'Nom de l\'association'; @override - String get phonebookAssociations => 'Associations :'; + String get phonebookAssociations => 'Associations'; @override String get phonebookCancel => 'Annuler'; @@ -2142,6 +2136,9 @@ class AppLocalizationsFr extends AppLocalizations { return 'Gérer les groupes de $association'; } + @override + String get phonebookMandate => 'Mandat'; + @override String get phonebookMandateChangingError => 'Erreur lors du changement de mandat'; diff --git a/lib/phonebook/providers/phonebook_admin_provider.dart b/lib/phonebook/providers/phonebook_admin_provider.dart index 0d6475dbc5..e358e45835 100644 --- a/lib/phonebook/providers/phonebook_admin_provider.dart +++ b/lib/phonebook/providers/phonebook_admin_provider.dart @@ -1,8 +1,10 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/admin/providers/is_admin_provider.dart'; +import 'package:titan/phonebook/class/complete_member.dart'; import 'package:titan/phonebook/providers/association_member_list_provider.dart'; import 'package:titan/phonebook/providers/association_provider.dart'; -import 'package:titan/phonebook/tools/constants.dart'; +import 'package:titan/phonebook/providers/roles_tags_provider.dart'; +import 'package:titan/phonebook/tools/function.dart'; import 'package:titan/user/providers/user_provider.dart'; final isPhonebookAdminProvider = StateProvider((ref) { @@ -24,24 +26,29 @@ final hasPhonebookAdminAccessProvider = StateProvider((ref) { return isPhonebookAdmin || isAdmin; }); -final isAssociationPresidentProvider = StateProvider((ref) { +final isAssociationPresidentProvider = Provider((ref) { + print("Provider is being evaluated"); + final association = ref.watch(associationProvider); + final rolesTags = ref.watch(rolesTagsProvider); final membersList = ref.watch(associationMemberListProvider); final me = ref.watch(userProvider); - bool isPresident = false; - membersList.whenData((members) { - if (members.map((e) => e.member.id).contains(me.id)) { - if (members - .firstWhere((completeMember) => completeMember.member.id == me.id) - .memberships - .firstWhere( - (membership) => membership.associationId == association.id, - ) - .rolesTags - .contains(presidentRoleTag)) { - isPresident = true; - } - } - }); - return isPresident; + + return membersList.maybeWhen( + data: (members) { + final member = members.firstWhere( + (m) => m.member.id == me.id, + orElse: () => CompleteMember.empty(), + ); + if (member.member.id == "") return false; + final membership = getMembershipForAssociation(member, association); + return rolesTags.maybeWhen( + data: (tags) { + return membership.rolesTags.contains(tags.first); + }, + orElse: () => false, + ); + }, + orElse: () => false, + ); }); diff --git a/lib/phonebook/tools/constants.dart b/lib/phonebook/tools/constants.dart deleted file mode 100644 index a60cf19c08..0000000000 --- a/lib/phonebook/tools/constants.dart +++ /dev/null @@ -1,7 +0,0 @@ -import 'package:flutter/material.dart'; - -const presidentRoleTag = 'president'; - -class PhonebookColorConstants { - static const Color textDark = Color(0xFF1D1D1D); -} diff --git a/lib/phonebook/tools/function.dart b/lib/phonebook/tools/function.dart index c47576db1e..99de7eade0 100644 --- a/lib/phonebook/tools/function.dart +++ b/lib/phonebook/tools/function.dart @@ -16,6 +16,7 @@ Membership getMembershipForAssociation( (element) => element.associationId == association.id && element.mandateYear == association.mandateYear, + orElse: () => Membership.empty(), ); } diff --git a/lib/phonebook/ui/components/association_research_bar.dart b/lib/phonebook/ui/components/association_research_bar.dart index 8e96693f40..83414c5c59 100644 --- a/lib/phonebook/ui/components/association_research_bar.dart +++ b/lib/phonebook/ui/components/association_research_bar.dart @@ -30,7 +30,7 @@ class AssociationResearchBar extends HookConsumerWidget { ref: ref, context: context, modal: BottomModalTemplate( - title: "Groupements d'associations", + title: "Filtrer", description: "Sélectionnez un ou plusieurs groupements pour filtrer les associations.", actions: [ diff --git a/lib/phonebook/ui/pages/admin_page/admin_page.dart b/lib/phonebook/ui/pages/admin_page/admin_page.dart index 7e4b930fc4..8abc0215fd 100644 --- a/lib/phonebook/ui/pages/admin_page/admin_page.dart +++ b/lib/phonebook/ui/pages/admin_page/admin_page.dart @@ -17,6 +17,7 @@ import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/tools/ui/styleguide/list_item_template.dart'; +import 'package:titan/tools/ui/widgets/align_left_text.dart'; import 'package:tuple/tuple.dart'; import 'package:titan/l10n/app_localizations.dart'; @@ -53,6 +54,8 @@ class AdminPage extends HookConsumerWidget { children: [ AssociationResearchBar(), const SizedBox(height: 10), + AlignLeftText(localizeWithContext.phonebookAdmin), + const SizedBox(height: 10), Async2Children( values: Tuple2(associationList, associationGroupementList), builder: (context, associations, associationGroupements) { diff --git a/lib/phonebook/ui/pages/association_add_edit_page/association_add_edit_page.dart b/lib/phonebook/ui/pages/association_add_edit_page/association_add_edit_page.dart index 31d1cef91f..f13c154bdc 100644 --- a/lib/phonebook/ui/pages/association_add_edit_page/association_add_edit_page.dart +++ b/lib/phonebook/ui/pages/association_add_edit_page/association_add_edit_page.dart @@ -180,7 +180,7 @@ class AssociationAddEditPage extends HookConsumerWidget { Container(margin: const EdgeInsets.symmetric(vertical: 10)), TextEntry( controller: name, - label: localizeWithContext.phonebookName, + label: localizeWithContext.phonebookAssociationName, canBeEmpty: false, ), const SizedBox(height: 30), @@ -264,7 +264,9 @@ class AssociationAddEditPage extends HookConsumerWidget { } }); }, - text: localizeWithContext.adminAdd, + text: association.id != "" + ? localizeWithContext.phonebookEdit + : localizeWithContext.phonebookAdd, ), SizedBox(height: 80), ], diff --git a/lib/phonebook/ui/pages/association_members_page/association_members_page.dart b/lib/phonebook/ui/pages/association_members_page/association_members_page.dart index 06639da755..7075ea6263 100644 --- a/lib/phonebook/ui/pages/association_members_page/association_members_page.dart +++ b/lib/phonebook/ui/pages/association_members_page/association_members_page.dart @@ -82,14 +82,14 @@ class AssociationMembersPage extends HookConsumerWidget { QR.to( PhonebookRouter.root + PhonebookRouter.admin + - PhonebookRouter.addEditAssociation + + PhonebookRouter.editAssociationMembers + PhonebookRouter.addEditMember, ); } else { QR.to( PhonebookRouter.root + PhonebookRouter.associationDetail + - PhonebookRouter.addEditAssociation + + PhonebookRouter.editAssociationMembers + PhonebookRouter.addEditMember, ); } diff --git a/lib/phonebook/ui/pages/association_page/association_page.dart b/lib/phonebook/ui/pages/association_page/association_page.dart index 35ebc2e95f..7c5b0d3f0a 100644 --- a/lib/phonebook/ui/pages/association_page/association_page.dart +++ b/lib/phonebook/ui/pages/association_page/association_page.dart @@ -39,7 +39,6 @@ class AssociationPage extends HookConsumerWidget { final associationGroupement = ref.watch(associationGroupementProvider); final localizeWithContext = AppLocalizations.of(context)!; - return PhonebookTemplate( child: Refresher( onRefresh: () async { @@ -51,92 +50,91 @@ class AssociationPage extends HookConsumerWidget { association.id, ); }, - child: Align( - alignment: Alignment.topCenter, - child: Stack( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + child: Column( children: [ - Column( + AsyncChild( + value: associationPicture, + builder: (context, image) { + return Center( + child: CircleAvatar( + radius: 80, + backgroundColor: Colors.white, + backgroundImage: image.image, + ), + ); + }, + ), + const SizedBox(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.center, children: [ - AsyncChild( - value: associationPicture, - builder: (context, image) { - return Center( - child: CircleAvatar( - radius: 80, - backgroundColor: Colors.white, - backgroundImage: image.image, - ), - ); - }, - ), - const SizedBox(height: 20), - Text( - association.name, - style: const TextStyle(fontSize: 40, color: Colors.black), - ), - const SizedBox(height: 10), - Text( - "${localizeWithContext.phonebookActiveMandate} ${association.mandateYear}", - style: const TextStyle(fontSize: 15, color: Colors.black), - ), - const SizedBox(height: 10), - Text( - associationGroupement.name, - style: const TextStyle(fontSize: 20, color: Colors.black), - ), - const SizedBox(height: 10), - Text( - association.description, - style: const TextStyle(fontSize: 15, color: Colors.black), + SizedBox( + width: 300, + child: Text( + association.name, + textAlign: TextAlign.center, + style: const TextStyle(fontSize: 40, color: Colors.black), + ), ), - const SizedBox(height: 20), - AsyncChild( - value: associationMemberList, - builder: (context, associationMembers) => - associationMembers.isEmpty - ? Text(localizeWithContext.phonebookNoMember) - : Column( - children: associationMemberSortedList - .map( - (member) => kIsWeb - ? WebMemberCard( - member: member, - association: association, - ) - : MemberCard( - member: member, - association: association, - deactivated: false, - ), - ) - .toList(), + if (isPresident) ...[ + const SizedBox(width: 10), + CustomIconButton.secondary( + onPressed: () { + showCustomBottomModal( + context: context, + modal: AssociationEditionModal( + association: association, + groupement: associationGroupement, ), - ), - const SizedBox(height: 80), + ref: ref, + ); + }, + icon: const HeroIcon(HeroIcons.pencilSquare, size: 30), + ), + ], ], ), - if (isPresident) - Positioned( - top: 20, - right: 20, - child: CustomIconButton( - onPressed: () { - showCustomBottomModal( - context: context, - modal: AssociationEditionModal( - association: association, - groupement: associationGroupement, - ), - ref: ref, - ); - }, - icon: const HeroIcon( - HeroIcons.pencilSquare, - size: 30, - color: Colors.black, - ), - ), - ), + const SizedBox(height: 10), + Text( + associationGroupement.name, + style: const TextStyle(fontSize: 20, color: Colors.black), + ), + const SizedBox(height: 10), + Text( + "${localizeWithContext.phonebookMandate} ${association.mandateYear}", + style: const TextStyle(fontSize: 15, color: Colors.black), + ), + const SizedBox(height: 10), + Text( + association.description, + style: const TextStyle(fontSize: 15, color: Colors.black), + ), + const SizedBox(height: 20), + AsyncChild( + value: associationMemberList, + builder: (context, associationMembers) => + associationMembers.isEmpty + ? Text(localizeWithContext.phonebookNoMember) + : Column( + children: associationMemberSortedList + .map( + (member) => kIsWeb + ? WebMemberCard( + member: member, + association: association, + ) + : MemberCard( + member: member, + association: association, + deactivated: false, + ), + ) + .toList(), + ), + ), + const SizedBox(height: 80), ], ), ), diff --git a/lib/phonebook/ui/pages/main_page/main_page.dart b/lib/phonebook/ui/pages/main_page/main_page.dart index b178b65fad..6150993b22 100644 --- a/lib/phonebook/ui/pages/main_page/main_page.dart +++ b/lib/phonebook/ui/pages/main_page/main_page.dart @@ -16,6 +16,7 @@ import 'package:titan/tools/ui/layouts/refresher.dart'; import 'package:titan/tools/ui/styleguide/icon_button.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/l10n/app_localizations.dart'; +import 'package:titan/tools/ui/widgets/align_left_text.dart'; import 'package:tuple/tuple.dart'; class PhonebookMainPage extends HookConsumerWidget { @@ -47,7 +48,7 @@ class PhonebookMainPage extends HookConsumerWidget { await associationListNotifier.loadAssociations(); }, child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), child: Column( children: [ Row( @@ -72,6 +73,8 @@ class PhonebookMainPage extends HookConsumerWidget { ], ), const SizedBox(height: 10), + AlignLeftText(localizeWithContext.phonebookPhonebook), + const SizedBox(height: 10), Async2Children( values: Tuple2(associationList, associationGroupementList), builder: (context, associations, associationGroupements) { diff --git a/lib/phonebook/ui/pages/member_detail_page/element_field.dart b/lib/phonebook/ui/pages/member_detail_page/element_field.dart deleted file mode 100644 index 87577b7e6c..0000000000 --- a/lib/phonebook/ui/pages/member_detail_page/element_field.dart +++ /dev/null @@ -1,40 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:titan/tools/functions.dart'; -import 'package:titan/l10n/app_localizations.dart'; - -class ElementField extends StatelessWidget { - final String label; - final String value; - const ElementField({super.key, required this.label, required this.value}); - - @override - Widget build(BuildContext context) { - void displayToastWithContext(TypeMsg type, String msg) { - displayToast(context, type, msg); - } - - return Container( - margin: const EdgeInsets.symmetric(vertical: 5.0), - child: Column( - children: [ - Center(child: Text(label, style: const TextStyle(fontSize: 16))), - Center( - child: SelectableText( - value, - maxLines: 1, - style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16), - onTap: () { - Clipboard.setData(ClipboardData(text: value)); - displayToastWithContext( - TypeMsg.msg, - AppLocalizations.of(context)!.phonebookCopied, - ); - }, - ), - ), - ], - ), - ); - } -} diff --git a/lib/phonebook/ui/pages/member_detail_page/member_detail_page.dart b/lib/phonebook/ui/pages/member_detail_page/member_detail_page.dart index 2502fa4017..28e04d41e3 100644 --- a/lib/phonebook/ui/pages/member_detail_page/member_detail_page.dart +++ b/lib/phonebook/ui/pages/member_detail_page/member_detail_page.dart @@ -9,6 +9,7 @@ import 'package:titan/phonebook/ui/phonebook.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/tools/ui/builders/auto_loader_child.dart'; +import 'package:titan/tools/ui/widgets/align_left_text.dart'; class MemberDetailPage extends HookConsumerWidget { const MemberDetailPage({super.key}); @@ -102,16 +103,12 @@ class MemberDetailPage extends HookConsumerWidget { ), const SizedBox(height: 20), if (member.memberships.isNotEmpty) - Text( + AlignLeftText( member.memberships.length == 1 ? localizeWithContext.phonebookAssociation : localizeWithContext.phonebookAssociations, - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 20, - ), ), - const SizedBox(height: 20), + const SizedBox(height: 10), AsyncChild( value: associationList, builder: (context, associations) => Column( diff --git a/lib/phonebook/ui/pages/membership_editor_page/search_result.dart b/lib/phonebook/ui/pages/membership_editor_page/search_result.dart index a8e29fd485..4fd44fbe3d 100644 --- a/lib/phonebook/ui/pages/membership_editor_page/search_result.dart +++ b/lib/phonebook/ui/pages/membership_editor_page/search_result.dart @@ -27,6 +27,7 @@ class SearchResult extends HookConsumerWidget { queryController.text = user.getName(); usersNotifier.clear(); memberNotifier.loadMemberComplete(); + Navigator.of(context).pop(); }, ), ) diff --git a/lib/phonebook/ui/phonebook.dart b/lib/phonebook/ui/phonebook.dart index 7afcab2cc4..4ca251ad0c 100644 --- a/lib/phonebook/ui/phonebook.dart +++ b/lib/phonebook/ui/phonebook.dart @@ -15,6 +15,15 @@ class PhonebookTemplate extends HookConsumerWidget { final associationGroupementNotifer = ref.watch( associationGroupementProvider.notifier, ); + + final pathGroupementClearing = [ + PhonebookRouter.root + PhonebookRouter.admin, + PhonebookRouter.root + PhonebookRouter.associationDetail, + PhonebookRouter.root + + PhonebookRouter.admin + + PhonebookRouter.addEditAssociation, + ]; + return Container( color: ColorConstants.background, child: SafeArea( @@ -23,11 +32,7 @@ class PhonebookTemplate extends HookConsumerWidget { TopBar( root: PhonebookRouter.root, onBack: () { - if (QR.currentPath != - PhonebookRouter.root + - PhonebookRouter.admin + - PhonebookRouter.editAssociationMembers + - PhonebookRouter.addEditMember) { + if (pathGroupementClearing.contains(QR.currentPath)) { associationGroupementNotifer.resetAssociationGroupement(); } }, diff --git a/lib/tools/middlewares/admin_middleware.dart b/lib/tools/middlewares/admin_middleware.dart index ee7262c440..95f279711a 100644 --- a/lib/tools/middlewares/admin_middleware.dart +++ b/lib/tools/middlewares/admin_middleware.dart @@ -3,7 +3,7 @@ import 'package:titan/router.dart'; import 'package:qlevar_router/qlevar_router.dart'; class AdminMiddleware extends QMiddleware { - final StateProvider isAdminProvider; + final ProviderBase isAdminProvider; final Ref ref; AdminMiddleware(this.ref, this.isAdminProvider); diff --git a/lib/tools/ui/styleguide/list_item_template.dart b/lib/tools/ui/styleguide/list_item_template.dart index 3998af8a3f..9b8f5079dc 100644 --- a/lib/tools/ui/styleguide/list_item_template.dart +++ b/lib/tools/ui/styleguide/list_item_template.dart @@ -24,7 +24,7 @@ class ListItemTemplate extends StatelessWidget { onTap: onTap, behavior: HitTestBehavior.opaque, child: Container( - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 4), + padding: const EdgeInsets.symmetric(vertical: 4), child: Row( children: [ if (icon != null) ...[icon!, const SizedBox(width: 10)], diff --git a/lib/tools/ui/styleguide/text_entry.dart b/lib/tools/ui/styleguide/text_entry.dart index 4214fab8d1..74209045a3 100644 --- a/lib/tools/ui/styleguide/text_entry.dart +++ b/lib/tools/ui/styleguide/text_entry.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/tools/constants.dart'; class TextEntry extends StatelessWidget { @@ -41,6 +42,8 @@ class TextEntry extends StatelessWidget { @override Widget build(BuildContext context) { + final localizeWithContext = AppLocalizations.of(context)!; + return TextFormField( minLines: minLines, maxLines: maxLines, @@ -54,7 +57,9 @@ class TextEntry extends StatelessWidget { enabled: enabled, decoration: InputDecoration( label: Text( - canBeEmpty ? '$label (optionnel)' : label, + canBeEmpty + ? '$label (${localizeWithContext.globalOptionnal})' + : label, style: TextStyle(color: color, height: 0.5), ), suffix: suffixIcon == null && suffix.isEmpty From 1864bfcf888ab6be3ce8f134138c575134bf27f7 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Tue, 8 Jul 2025 20:47:55 +0200 Subject: [PATCH 092/473] initial commit and password change # Conflicts: # lib/settings/router.dart --- .../create_account_page.dart | 2 +- .../recover_password_page.dart | 2 +- lib/settings/router.dart | 54 +- .../ui/pages/change_pass/change_pass.dart | 186 ------- .../pages/edit_user_page/edit_user_page.dart | 439 ---------------- .../pages/edit_user_page/picture_button.dart | 34 -- .../edit_user_page/user_field_modifier.dart | 43 -- lib/settings/ui/pages/log_page/log_card.dart | 85 ---- lib/settings/ui/pages/log_page/log_page.dart | 72 --- lib/settings/ui/pages/log_page/log_tab.dart | 82 --- .../ui/pages/log_page/notification_tab.dart | 81 --- .../ui/pages/main_page/change_pass.dart | 158 ++++++ .../ui/pages/main_page/main_page.dart | 473 +++++++----------- .../password_strength.dart | 2 +- .../secure_bar.dart | 0 .../ui/pages/main_page/settings_item.dart | 54 -- .../test_entry_style.dart | 0 .../ui/pages/modules_page/modules_page.dart | 75 --- .../notification_page/notification_page.dart | 128 ----- lib/tools/ui/styleguide/text_entry.dart | 15 +- 20 files changed, 360 insertions(+), 1625 deletions(-) delete mode 100644 lib/settings/ui/pages/change_pass/change_pass.dart delete mode 100644 lib/settings/ui/pages/edit_user_page/edit_user_page.dart delete mode 100644 lib/settings/ui/pages/edit_user_page/picture_button.dart delete mode 100644 lib/settings/ui/pages/edit_user_page/user_field_modifier.dart delete mode 100644 lib/settings/ui/pages/log_page/log_card.dart delete mode 100644 lib/settings/ui/pages/log_page/log_page.dart delete mode 100644 lib/settings/ui/pages/log_page/log_tab.dart delete mode 100644 lib/settings/ui/pages/log_page/notification_tab.dart create mode 100644 lib/settings/ui/pages/main_page/change_pass.dart rename lib/settings/ui/pages/{change_pass => main_page}/password_strength.dart (98%) rename lib/settings/ui/pages/{change_pass => main_page}/secure_bar.dart (100%) delete mode 100644 lib/settings/ui/pages/main_page/settings_item.dart rename lib/settings/ui/pages/{change_pass => main_page}/test_entry_style.dart (100%) delete mode 100644 lib/settings/ui/pages/modules_page/modules_page.dart delete mode 100644 lib/settings/ui/pages/notification_page/notification_page.dart diff --git a/lib/login/ui/pages/create_account_page/create_account_page.dart b/lib/login/ui/pages/create_account_page/create_account_page.dart index 9ce9dbd918..4c6328502c 100644 --- a/lib/login/ui/pages/create_account_page/create_account_page.dart +++ b/lib/login/ui/pages/create_account_page/create_account_page.dart @@ -10,7 +10,7 @@ import 'package:titan/login/router.dart'; import 'package:titan/login/ui/components/login_field.dart'; import 'package:titan/login/ui/auth_page.dart'; import 'package:titan/login/ui/components/sign_in_up_bar.dart'; -import 'package:titan/settings/ui/pages/change_pass/password_strength.dart'; +import 'package:titan/settings/ui/pages/main_page/password_strength.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/ui/widgets/align_left_text.dart'; diff --git a/lib/login/ui/pages/recover_password/recover_password_page.dart b/lib/login/ui/pages/recover_password/recover_password_page.dart index fa8a0a9fb2..61a7b8d585 100644 --- a/lib/login/ui/pages/recover_password/recover_password_page.dart +++ b/lib/login/ui/pages/recover_password/recover_password_page.dart @@ -10,7 +10,7 @@ import 'package:titan/login/router.dart'; import 'package:titan/login/ui/components/login_field.dart'; import 'package:titan/login/ui/auth_page.dart'; import 'package:titan/login/ui/components/sign_in_up_bar.dart'; -import 'package:titan/settings/ui/pages/change_pass/password_strength.dart'; +import 'package:titan/settings/ui/pages/main_page/password_strength.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:qlevar_router/qlevar_router.dart'; diff --git a/lib/settings/router.dart b/lib/settings/router.dart index 104b81ec08..6382fe72a4 100644 --- a/lib/settings/router.dart +++ b/lib/settings/router.dart @@ -1,21 +1,12 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/admin/router.dart'; import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/navigation/class/module.dart'; -import 'package:titan/settings/ui/pages/change_pass/change_pass.dart' - deferred as change_pass; -import 'package:titan/settings/ui/pages/edit_user_page/edit_user_page.dart' - deferred as edit_user_page; -import 'package:titan/settings/ui/pages/log_page/log_page.dart' - deferred as log_page; + import 'package:titan/settings/ui/pages/main_page/main_page.dart' deferred as main_page; -import 'package:titan/settings/ui/pages/modules_page/modules_page.dart' - deferred as modules_page; -import 'package:titan/settings/ui/pages/notification_page/notification_page.dart' - deferred as notification_page; + import 'package:titan/tools/middlewares/authenticated_middleware.dart'; import 'package:titan/tools/middlewares/deferred_middleware.dart'; import 'package:qlevar_router/qlevar_router.dart'; @@ -23,15 +14,10 @@ import 'package:qlevar_router/qlevar_router.dart'; class SettingsRouter { final Ref ref; static const String root = '/settings'; - static const String editAccount = '/edit_account'; - static const String changePassword = '/change_password'; - static const String logs = '/logs'; - static const String modules = '/modules'; - static const String notifications = '/notifications'; static final Module module = Module( getName: (context) => AppLocalizations.of(context)!.moduleSettings, description: "Gérer les paramètres de l'application", - root: AdminRouter.root, + root: SettingsRouter.root, ); SettingsRouter(this.ref); @@ -44,37 +30,5 @@ class SettingsRouter { AuthenticatedMiddleware(ref), DeferredLoadingMiddleware(main_page.loadLibrary), ], - pageType: QCustomPage( - transitionsBuilder: (_, animation, _, child) => - FadeTransition(opacity: animation, child: child), - ), - children: [ - QRoute( - path: editAccount, - builder: () => edit_user_page.EditUserPage(), - middleware: [DeferredLoadingMiddleware(edit_user_page.loadLibrary)], - ), - QRoute( - path: changePassword, - builder: () => change_pass.ChangePassPage(), - middleware: [DeferredLoadingMiddleware(change_pass.loadLibrary)], - ), - if (!kIsWeb) - QRoute( - path: logs, - builder: () => log_page.LogPage(), - middleware: [DeferredLoadingMiddleware(log_page.loadLibrary)], - ), - QRoute( - path: modules, - builder: () => modules_page.ModulesPage(), - middleware: [DeferredLoadingMiddleware(modules_page.loadLibrary)], - ), - QRoute( - path: notifications, - builder: () => notification_page.NotificationPage(), - middleware: [DeferredLoadingMiddleware(notification_page.loadLibrary)], - ), - ], ); } diff --git a/lib/settings/ui/pages/change_pass/change_pass.dart b/lib/settings/ui/pages/change_pass/change_pass.dart deleted file mode 100644 index 00c347993e..0000000000 --- a/lib/settings/ui/pages/change_pass/change_pass.dart +++ /dev/null @@ -1,186 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/settings/ui/pages/change_pass/password_strength.dart'; -import 'package:titan/settings/ui/pages/change_pass/test_entry_style.dart'; -import 'package:titan/settings/ui/settings.dart'; -import 'package:titan/tools/constants.dart'; -import 'package:titan/tools/ui/layouts/add_edit_button_layout.dart'; -import 'package:titan/tools/ui/widgets/align_left_text.dart'; -import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; -import 'package:titan/tools/functions.dart'; -import 'package:titan/tools/token_expire_wrapper.dart'; -import 'package:titan/tools/ui/builders/waiting_button.dart'; -import 'package:titan/user/providers/user_provider.dart'; -import 'package:qlevar_router/qlevar_router.dart'; -import 'package:titan/l10n/app_localizations.dart'; - -class ChangePassPage extends HookConsumerWidget { - const ChangePassPage({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final key = GlobalKey(); - final oldPassword = useTextEditingController(); - final newPassword = useTextEditingController(); - final confirmPassword = useTextEditingController(); - final hideOldPass = useState(true); - final hideNewPass = useState(true); - final hideConfirmPass = useState(true); - final userNotifier = ref.watch(asyncUserProvider.notifier); - final user = ref.watch(userProvider); - void displayToastWithContext(TypeMsg type, String msg) { - displayToast(context, type, msg); - } - - return SettingsTemplate( - child: SingleChildScrollView( - physics: const BouncingScrollPhysics(), - child: Form( - key: key, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 30.0), - child: Column( - children: [ - const SizedBox(height: 30), - AlignLeftText( - AppLocalizations.of(context)!.settingsChangePassword, - fontSize: 20, - ), - const SizedBox(height: 30), - Padding( - padding: const EdgeInsets.symmetric(vertical: 16), - child: TextFormField( - cursorColor: ColorConstants.gradient1, - decoration: changePassInputDecoration( - hintText: AppLocalizations.of( - context, - )!.settingsOldPassword, - notifier: hideOldPass, - ), - controller: oldPassword, - obscureText: hideOldPass.value, - validator: (value) { - if (value == null || value.isEmpty) { - return AppLocalizations.of(context)!.settingsEmptyField; - } - return null; - }, - ), - ), - const SizedBox(height: 30), - Padding( - padding: const EdgeInsets.symmetric(vertical: 16), - child: TextFormField( - cursorColor: ColorConstants.gradient1, - decoration: changePassInputDecoration( - hintText: AppLocalizations.of( - context, - )!.settingsNewPassword, - notifier: hideNewPass, - ), - controller: newPassword, - obscureText: hideNewPass.value, - validator: (value) { - if (value == null || value.isEmpty) { - return AppLocalizations.of(context)!.settingsEmptyField; - } - return null; - }, - ), - ), - const SizedBox(height: 30), - Padding( - padding: const EdgeInsets.symmetric(vertical: 16), - child: TextFormField( - cursorColor: ColorConstants.gradient1, - decoration: changePassInputDecoration( - hintText: AppLocalizations.of( - context, - )!.settingsConfirmPassword, - notifier: hideConfirmPass, - ), - controller: confirmPassword, - obscureText: hideConfirmPass.value, - validator: (value) { - if (value == null || value.isEmpty) { - return AppLocalizations.of(context)!.settingsEmptyField; - } else if (value != newPassword.text) { - return AppLocalizations.of( - context, - )!.settingsPasswordsNotMatch; - } - return null; - }, - ), - ), - const SizedBox(height: 40), - PasswordStrength(newPassword: newPassword), - const SizedBox(height: 60), - WaitingButton( - builder: (child) => AddEditButtonLayout( - colors: const [ - ColorConstants.gradient1, - ColorConstants.gradient2, - ], - child: child, - ), - onTap: () async { - if (key.currentState!.validate()) { - await showDialog( - context: context, - builder: (context) => CustomDialogBox( - descriptions: AppLocalizations.of( - context, - )!.settingsChangingPassword, - onYes: () async { - final passwordChangedMsg = AppLocalizations.of( - context, - )!.settingsPasswordChanged; - final passwordChangeErrorMsg = AppLocalizations.of( - context, - )!.settingsUpdatingError; - await tokenExpireWrapper(ref, () async { - final value = await userNotifier.changePassword( - oldPassword.text, - newPassword.text, - user, - ); - if (value) { - QR.back(); - displayToastWithContext( - TypeMsg.msg, - passwordChangedMsg, - ); - } else { - displayToastWithContext( - TypeMsg.error, - passwordChangeErrorMsg, - ); - } - }); - }, - title: AppLocalizations.of(context)!.settingsEdit, - ), - ); - } - }, - child: Center( - child: Text( - AppLocalizations.of(context)!.settingsSave, - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - color: Colors.white, - ), - ), - ), - ), - ], - ), - ), - ), - ), - ); - } -} diff --git a/lib/settings/ui/pages/edit_user_page/edit_user_page.dart b/lib/settings/ui/pages/edit_user_page/edit_user_page.dart deleted file mode 100644 index 61e83e4b8c..0000000000 --- a/lib/settings/ui/pages/edit_user_page/edit_user_page.dart +++ /dev/null @@ -1,439 +0,0 @@ -import 'package:auto_size_text/auto_size_text.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:heroicons/heroicons.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:image_picker/image_picker.dart'; -import 'package:titan/settings/router.dart'; -import 'package:titan/settings/ui/pages/edit_user_page/picture_button.dart'; -import 'package:titan/settings/ui/pages/edit_user_page/user_field_modifier.dart'; -import 'package:titan/settings/ui/settings.dart'; -import 'package:titan/tools/constants.dart'; -import 'package:titan/tools/functions.dart'; -import 'package:titan/tools/ui/layouts/add_edit_button_layout.dart'; -import 'package:titan/tools/ui/widgets/align_left_text.dart'; -import 'package:titan/tools/ui/builders/async_child.dart'; -import 'package:titan/tools/ui/layouts/refresher.dart'; -import 'package:titan/tools/token_expire_wrapper.dart'; -import 'package:titan/tools/ui/builders/waiting_button.dart'; -import 'package:titan/tools/ui/widgets/text_entry.dart'; -import 'package:titan/user/class/floors.dart'; -import 'package:titan/user/providers/user_provider.dart'; -import 'package:titan/user/providers/profile_picture_provider.dart'; -import 'package:qlevar_router/qlevar_router.dart'; -import 'package:titan/l10n/app_localizations.dart'; - -class EditUserPage extends HookConsumerWidget { - const EditUserPage({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final key = GlobalKey(); - final asyncUserNotifier = ref.watch(asyncUserProvider.notifier); - final user = ref.watch(userProvider); - final profilePicture = ref.watch(profilePictureProvider); - final profilePictureNotifier = ref.watch(profilePictureProvider.notifier); - final dateController = useTextEditingController( - text: user.birthday != null ? processDate(user.birthday!) : "", - ); - final nickNameController = useTextEditingController( - text: user.nickname ?? '', - ); - final phoneController = useTextEditingController(text: user.phone ?? ''); - final floorController = useTextEditingController( - text: user.floor.toString(), - ); - - void displayToastWithContext(TypeMsg type, String msg) { - displayToast(context, type, msg); - } - - List items = Floors.values - .map( - (e) => DropdownMenuItem( - value: capitalize(e.toString().split('.').last), - child: Text(capitalize(e.toString().split('.').last)), - ), - ) - .toList(); - - return SettingsTemplate( - child: Refresher( - onRefresh: () async { - await asyncUserNotifier.loadMe(); - }, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 30.0), - child: Form( - key: key, - child: Column( - children: [ - const SizedBox(height: 20), - AlignLeftText( - AppLocalizations.of(context)!.settingsEditAccount, - color: Colors.grey, - ), - const SizedBox(height: 40), - AsyncChild( - value: profilePicture, - builder: (context, profile) { - return Center( - child: Stack( - clipBehavior: Clip.none, - children: [ - Container( - decoration: BoxDecoration( - shape: BoxShape.circle, - boxShadow: [ - BoxShadow( - color: Colors.black.withValues(alpha: 0.1), - spreadRadius: 5, - blurRadius: 10, - offset: const Offset(2, 3), - ), - ], - ), - child: CircleAvatar( - radius: 80, - backgroundImage: profile.isEmpty - ? const AssetImage( - 'assets/images/profile.png', - ) - : Image.memory(profile).image, - ), - ), - Positioned( - bottom: 0, - left: 0, - child: GestureDetector( - onTap: () async { - final updatedProfilePictureMsg = - AppLocalizations.of( - context, - )!.settingsUpdatedProfilePicture; - final tooHeavyProfilePictureMsg = - AppLocalizations.of( - context, - )!.settingsTooHeavyProfilePicture; - final profilePictureErrorMsg = - AppLocalizations.of( - context, - )!.settingsErrorProfilePicture; - final value = await profilePictureNotifier - .setProfilePicture(ImageSource.gallery); - if (value != null) { - if (value) { - displayToastWithContext( - TypeMsg.msg, - updatedProfilePictureMsg, - ); - } else { - displayToastWithContext( - TypeMsg.error, - tooHeavyProfilePictureMsg, - ); - } - } else { - displayToastWithContext( - TypeMsg.error, - profilePictureErrorMsg, - ); - } - }, - child: const PictureButton(icon: HeroIcons.photo), - ), - ), - Positioned( - bottom: 0, - right: 0, - child: GestureDetector( - onTap: () async { - final updatedProfilePictureMsg = - AppLocalizations.of( - context, - )!.settingsUpdatedProfilePicture; - final tooHeavyProfilePictureMsg = - AppLocalizations.of( - context, - )!.settingsTooHeavyProfilePicture; - final profilePictureErrorMsg = - AppLocalizations.of( - context, - )!.settingsErrorProfilePicture; - final value = await profilePictureNotifier - .setProfilePicture(ImageSource.camera); - if (value != null) { - if (value) { - displayToastWithContext( - TypeMsg.msg, - updatedProfilePictureMsg, - ); - } else { - displayToastWithContext( - TypeMsg.error, - tooHeavyProfilePictureMsg, - ); - } - } else { - displayToastWithContext( - TypeMsg.error, - profilePictureErrorMsg, - ); - } - }, - child: const PictureButton( - icon: HeroIcons.camera, - ), - ), - ), - Positioned( - bottom: -20, - right: 60, - child: GestureDetector( - onTap: () async { - final updatedProfilePictureMsg = - AppLocalizations.of( - context, - )!.settingsUpdatedProfilePicture; - final profilePictureErrorMsg = - AppLocalizations.of( - context, - )!.settingsErrorProfilePicture; - final value = await profilePictureNotifier - .cropImage(); - if (value != null) { - if (value) { - displayToastWithContext( - TypeMsg.msg, - updatedProfilePictureMsg, - ); - } else { - displayToastWithContext( - TypeMsg.error, - profilePictureErrorMsg, - ); - } - } - }, - child: const PictureButton( - icon: HeroIcons.sparkles, - ), - ), - ), - ], - ), - ); - }, - ), - const SizedBox(height: 50), - if (user.promo != null) - Container( - margin: const EdgeInsets.only(bottom: 20), - child: Text( - '${AppLocalizations.of(context)!.settingsPromo} ${user.promo}', - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.w500, - color: Colors.black, - ), - ), - ), - AutoSizeText( - '${AppLocalizations.of(context)!.settingsEmail} : ${user.email}', - maxLines: 1, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.w500, - color: Colors.black, - ), - ), - const SizedBox(height: 50), - UserFieldModifier( - label: AppLocalizations.of(context)!.settingsNickname, - keyboardType: TextInputType.text, - controller: nickNameController, - ), - const SizedBox(height: 50), - UserFieldModifier( - label: AppLocalizations.of(context)!.settingsPhone, - keyboardType: TextInputType.text, - controller: phoneController, - ), - const SizedBox(height: 50), - Row( - children: [ - SizedBox( - width: 120, - child: Text( - AppLocalizations.of(context)!.settingsBirthday, - style: TextStyle( - fontSize: 15, - fontWeight: FontWeight.w400, - color: Colors.grey.shade500, - ), - ), - ), - Expanded( - child: AbsorbPointer( - child: TextEntry( - label: AppLocalizations.of(context)!.settingsBirthday, - controller: dateController, - ), - ), - ), - GestureDetector( - onTap: () => getOnlyDayDate( - context, - dateController, - initialDate: user.birthday, - firstDate: DateTime(1900), - lastDate: DateTime.now(), - ), - child: Container( - margin: const EdgeInsets.only(left: 30), - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - gradient: const LinearGradient( - colors: [ - ColorConstants.gradient1, - ColorConstants.gradient2, - ], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ), - boxShadow: [ - BoxShadow( - color: ColorConstants.gradient2.withValues( - alpha: 0.5, - ), - spreadRadius: 1, - blurRadius: 7, - offset: const Offset(0, 3), - ), - ], - borderRadius: BorderRadius.circular(10), - ), - child: const HeroIcon( - HeroIcons.calendar, - size: 25, - color: Colors.white, - ), - ), - ), - ], - ), - const SizedBox(height: 50), - Row( - children: [ - SizedBox( - width: 120, - child: Text( - AppLocalizations.of(context)!.settingsFloor, - style: TextStyle( - fontSize: 15, - fontWeight: FontWeight.w400, - color: Colors.grey.shade500, - ), - ), - ), - Expanded( - child: DropdownButtonFormField( - items: items, - value: floorController.text, - hint: Text( - AppLocalizations.of(context)!.settingsFloor, - style: TextStyle( - fontSize: 15, - fontWeight: FontWeight.w400, - color: Colors.grey.shade500, - ), - ), - onChanged: (value) { - floorController.text = value.toString(); - }, - style: TextStyle( - fontSize: 20, - color: Colors.grey.shade800, - ), - decoration: const InputDecoration( - contentPadding: EdgeInsets.all(10), - isDense: true, - focusedBorder: UnderlineInputBorder( - borderSide: BorderSide( - color: ColorConstants.gradient1, - ), - ), - ), - ), - ), - ], - ), - const SizedBox(height: 50), - WaitingButton( - builder: (child) => AddEditButtonLayout( - colors: const [ - ColorConstants.gradient1, - ColorConstants.gradient2, - ], - child: child, - ), - onTap: () async { - final settingsUpdatedProfileMsg = AppLocalizations.of( - context, - )!.settingsUpdatedProfile; - final settingsUpdatingErrorMsg = AppLocalizations.of( - context, - )!.settingsUpdatingError; - await tokenExpireWrapper(ref, () async { - final value = await asyncUserNotifier.updateMe( - user.copyWith( - birthday: dateController.value.text.isNotEmpty - ? DateTime.parse( - processDateBack(dateController.value.text), - ) - : null, - nickname: nickNameController.value.text.isEmpty - ? null - : nickNameController.value.text, - phone: phoneController.value.text.isEmpty - ? null - : phoneController.value.text, - floor: floorController.value.text, - ), - ); - if (value) { - displayToastWithContext( - TypeMsg.msg, - settingsUpdatedProfileMsg, - ); - QR.removeNavigator( - SettingsRouter.root + SettingsRouter.editAccount, - ); - } else { - displayToastWithContext( - TypeMsg.error, - settingsUpdatingErrorMsg, - ); - } - }); - }, - child: Center( - child: Text( - AppLocalizations.of(context)!.settingsSave, - style: const TextStyle( - fontSize: 25, - fontWeight: FontWeight.w400, - color: Colors.white, - ), - ), - ), - ), - const SizedBox(height: 30), - ], - ), - ), - ), - ), - ); - } -} diff --git a/lib/settings/ui/pages/edit_user_page/picture_button.dart b/lib/settings/ui/pages/edit_user_page/picture_button.dart deleted file mode 100644 index 905238fb15..0000000000 --- a/lib/settings/ui/pages/edit_user_page/picture_button.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:heroicons/heroicons.dart'; -import 'package:titan/tools/constants.dart'; - -class PictureButton extends StatelessWidget { - final HeroIcons icon; - const PictureButton({super.key, required this.icon}); - - @override - Widget build(BuildContext context) { - return Container( - height: 40, - width: 40, - padding: const EdgeInsets.all(7), - decoration: BoxDecoration( - shape: BoxShape.circle, - gradient: const LinearGradient( - colors: [ColorConstants.gradient1, ColorConstants.gradient2], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ), - boxShadow: [ - BoxShadow( - color: ColorConstants.gradient2.withValues(alpha: 0.3), - spreadRadius: 2, - blurRadius: 4, - offset: const Offset(2, 3), - ), - ], - ), - child: HeroIcon(icon, color: Colors.white), - ); - } -} diff --git a/lib/settings/ui/pages/edit_user_page/user_field_modifier.dart b/lib/settings/ui/pages/edit_user_page/user_field_modifier.dart deleted file mode 100644 index 43220bf684..0000000000 --- a/lib/settings/ui/pages/edit_user_page/user_field_modifier.dart +++ /dev/null @@ -1,43 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:titan/tools/constants.dart'; -import 'package:titan/tools/ui/widgets/text_entry.dart'; - -class UserFieldModifier extends StatelessWidget { - final String label; - final TextInputType keyboardType; - final TextEditingController controller; - const UserFieldModifier({ - super.key, - required this.label, - required this.keyboardType, - required this.controller, - }); - - @override - Widget build(BuildContext context) { - return Row( - children: [ - SizedBox( - width: 120, - child: Text( - label, - style: TextStyle( - fontSize: 15, - fontWeight: FontWeight.w400, - color: Colors.grey.shade500, - ), - ), - ), - Expanded( - child: TextEntry( - label: label, - keyboardType: keyboardType, - controller: controller, - color: ColorConstants.gradient1, - isInt: keyboardType == TextInputType.number, - ), - ), - ], - ); - } -} diff --git a/lib/settings/ui/pages/log_page/log_card.dart b/lib/settings/ui/pages/log_page/log_card.dart deleted file mode 100644 index 8a350a468a..0000000000 --- a/lib/settings/ui/pages/log_page/log_card.dart +++ /dev/null @@ -1,85 +0,0 @@ -import 'package:flutter/services.dart'; -import 'package:flutter/material.dart'; -import 'package:heroicons/heroicons.dart'; -import 'package:titan/tools/functions.dart'; -import 'package:titan/tools/logs/log.dart'; -import 'package:titan/l10n/app_localizations.dart'; - -class LogCard extends StatelessWidget { - final Log log; - const LogCard({super.key, required this.log}); - - @override - Widget build(BuildContext context) { - List colors = log.level == LogLevel.debug - ? [const Color(0xff00c3ff), const Color(0xff0077ff)] - : log.level == LogLevel.info - ? [const Color(0xff549227), const Color(0xFF3E721A)] - : log.level == LogLevel.warning - ? [const Color(0xfffc9a01), const Color(0xffee8300)] - : log.level == LogLevel.error - ? [const Color(0xffc72c41), const Color(0xff801336)] - : [ - const Color.fromARGB(255, 198, 190, 21), - const Color.fromARGB(255, 187, 178, 14), - ]; - - Color color = colors[0]; - - return Container( - width: double.infinity, - margin: const EdgeInsets.symmetric(vertical: 10), - padding: const EdgeInsets.symmetric(vertical: 18, horizontal: 20), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: colors, - ), - boxShadow: [ - BoxShadow( - color: color.withValues(alpha: 0.2), - spreadRadius: 5, - blurRadius: 10, - offset: const Offset(3, 3), // changes position of shadow - ), - ], - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - log.time.toString(), - style: const TextStyle( - fontSize: 16, - color: Colors.white, - fontWeight: FontWeight.bold, - ), - ), - GestureDetector( - onTap: () { - Clipboard.setData(ClipboardData(text: log.message)); - displayToast( - context, - TypeMsg.msg, - AppLocalizations.of(context)!.settingsCopied, - ); - }, - child: const HeroIcon(HeroIcons.clipboard, color: Colors.white), - ), - ], - ), - const SizedBox(height: 10), - Text( - log.message, - style: const TextStyle(fontSize: 12, color: Colors.white), - ), - ], - ), - ); - } -} diff --git a/lib/settings/ui/pages/log_page/log_page.dart b/lib/settings/ui/pages/log_page/log_page.dart deleted file mode 100644 index e13f9664ee..0000000000 --- a/lib/settings/ui/pages/log_page/log_page.dart +++ /dev/null @@ -1,72 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/settings/providers/logs_provider.dart'; -import 'package:titan/settings/providers/logs_tab_provider.dart'; -import 'package:titan/settings/ui/pages/log_page/log_tab.dart'; -import 'package:titan/settings/ui/pages/log_page/notification_tab.dart'; -import 'package:titan/settings/ui/settings.dart'; -import 'package:titan/tools/functions.dart'; -import 'package:titan/tools/ui/layouts/horizontal_list_view.dart'; -import 'package:titan/tools/ui/layouts/item_chip.dart'; -import 'package:titan/tools/ui/layouts/refresher.dart'; - -class LogPage extends HookConsumerWidget { - const LogPage({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final logsNotifier = ref.watch(logsProvider.notifier); - final notificationLogsNotifier = ref.watch( - notificationLogsProvider.notifier, - ); - final logTab = ref.watch(logTabProvider); - final logTabNotifier = ref.read(logTabProvider.notifier); - - Widget getTab(LogTabs tab) { - switch (tab) { - case LogTabs.log: - return const LogTab(); - case LogTabs.notification: - return const NotificationTab(); - } - } - - return SettingsTemplate( - child: Refresher( - onRefresh: () async { - await logsNotifier.getLogs(); - await notificationLogsNotifier.getLogs(); - }, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 30.0), - child: Column( - children: [ - const SizedBox(height: 20), - HorizontalListView.builder( - height: 40, - items: LogTabs.values, - itemBuilder: (context, item, i) => GestureDetector( - onTap: () { - logTabNotifier.setLogTabs(item); - }, - child: ItemChip( - selected: logTab == item, - child: Text( - capitalize(item.name), - style: TextStyle( - color: logTab == item ? Colors.white : Colors.black, - fontWeight: FontWeight.bold, - ), - ), - ), - ), - ), - const SizedBox(height: 10), - getTab(logTab), - ], - ), - ), - ), - ); - } -} diff --git a/lib/settings/ui/pages/log_page/log_tab.dart b/lib/settings/ui/pages/log_page/log_tab.dart deleted file mode 100644 index f8dcc8f9e1..0000000000 --- a/lib/settings/ui/pages/log_page/log_tab.dart +++ /dev/null @@ -1,82 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:heroicons/heroicons.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/settings/providers/logs_provider.dart'; - -import 'package:titan/settings/ui/pages/log_page/log_card.dart'; -import 'package:titan/tools/ui/builders/async_child.dart'; -import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; -import 'package:titan/l10n/app_localizations.dart'; - -class LogTab extends HookConsumerWidget { - const LogTab({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final logs = ref.watch(logsProvider); - final logsNotifier = ref.watch(logsProvider.notifier); - return Column( - children: [ - const SizedBox(height: 10), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - AppLocalizations.of(context)!.settingsLogs, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Colors.grey, - ), - ), - GestureDetector( - onTap: () { - showDialog( - context: context, - builder: ((context) => CustomDialogBox( - title: AppLocalizations.of(context)!.settingsDeleting, - descriptions: AppLocalizations.of( - context, - )!.settingsDeleteLogs, - onYes: (() async { - logsNotifier.deleteLogs(); - }), - )), - ); - }, - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 12, - ), - decoration: BoxDecoration( - color: Colors.black, - borderRadius: BorderRadius.circular(10), - boxShadow: [ - BoxShadow( - color: Colors.black.withValues(alpha: 0.2), - blurRadius: 10, - offset: const Offset(0, 5), - ), - ], - ), - child: const Row( - children: [ - HeroIcon(HeroIcons.trash, color: Colors.white, size: 20), - ], - ), - ), - ), - ], - ), - const SizedBox(height: 20), - AsyncChild( - value: logs, - builder: (context, data) => - Column(children: data.map((e) => LogCard(log: e)).toList()), - ), - const SizedBox(height: 20), - ], - ); - } -} diff --git a/lib/settings/ui/pages/log_page/notification_tab.dart b/lib/settings/ui/pages/log_page/notification_tab.dart deleted file mode 100644 index 5e70553521..0000000000 --- a/lib/settings/ui/pages/log_page/notification_tab.dart +++ /dev/null @@ -1,81 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:heroicons/heroicons.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/settings/providers/logs_provider.dart'; -import 'package:titan/settings/ui/pages/log_page/log_card.dart'; -import 'package:titan/tools/ui/builders/async_child.dart'; -import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; -import 'package:titan/l10n/app_localizations.dart'; - -class NotificationTab extends HookConsumerWidget { - const NotificationTab({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final logs = ref.watch(notificationLogsProvider); - final logsNotifier = ref.watch(notificationLogsProvider.notifier); - return Column( - children: [ - const SizedBox(height: 10), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - AppLocalizations.of(context)!.settingsNotifications, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Colors.grey, - ), - ), - GestureDetector( - onTap: () { - showDialog( - context: context, - builder: ((context) => CustomDialogBox( - title: AppLocalizations.of(context)!.settingsDeleting, - descriptions: AppLocalizations.of( - context, - )!.settingsDeleteNotificationLogs, - onYes: (() async { - logsNotifier.deleteLogs(); - }), - )), - ); - }, - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 12, - ), - decoration: BoxDecoration( - color: Colors.black, - borderRadius: BorderRadius.circular(10), - boxShadow: [ - BoxShadow( - color: Colors.black.withValues(alpha: 0.2), - blurRadius: 10, - offset: const Offset(0, 5), - ), - ], - ), - child: const Row( - children: [ - HeroIcon(HeroIcons.trash, color: Colors.white, size: 20), - ], - ), - ), - ), - ], - ), - const SizedBox(height: 20), - AsyncChild( - value: logs, - builder: (context, data) => - Column(children: data.map((e) => LogCard(log: e)).toList()), - ), - const SizedBox(height: 20), - ], - ); - } -} diff --git a/lib/settings/ui/pages/main_page/change_pass.dart b/lib/settings/ui/pages/main_page/change_pass.dart new file mode 100644 index 0000000000..0217920e56 --- /dev/null +++ b/lib/settings/ui/pages/main_page/change_pass.dart @@ -0,0 +1,158 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:heroicons/heroicons.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/settings/ui/pages/main_page/password_strength.dart'; +import 'package:titan/settings/ui/pages/main_page/test_entry_style.dart'; +import 'package:titan/tools/constants.dart'; +import 'package:titan/tools/ui/layouts/add_edit_button_layout.dart'; +import 'package:titan/tools/ui/styleguide/button.dart'; +import 'package:titan/tools/ui/styleguide/text_entry.dart'; +import 'package:titan/tools/ui/widgets/align_left_text.dart'; +import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; +import 'package:titan/tools/functions.dart'; +import 'package:titan/tools/token_expire_wrapper.dart'; +import 'package:titan/tools/ui/builders/waiting_button.dart'; +import 'package:titan/user/providers/user_provider.dart'; +import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; + +class ChangePassPage extends HookConsumerWidget { + const ChangePassPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final key = GlobalKey(); + final oldPassword = useTextEditingController(); + final newPassword = useTextEditingController(); + final confirmPassword = useTextEditingController(); + final hideOldPass = useState(true); + final hideNewPass = useState(true); + final hideConfirmPass = useState(true); + final userNotifier = ref.watch(asyncUserProvider.notifier); + final user = ref.watch(userProvider); + void displayToastWithContext(TypeMsg type, String msg) { + displayToast(context, type, msg); + } + + return SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: Form( + key: key, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 30.0), + child: Column( + children: [ + const SizedBox(height: 10), + TextEntry( + controller: oldPassword, + // obscureText: hideOldPass.value, + label: 'Ancien mot de passe', + maxLines: 1, + suffixIcon: IconButton( + icon: Icon( + hideOldPass.value ? Icons.visibility : Icons.visibility_off, + color: Colors.grey.shade600, + ), + onPressed: () { + hideOldPass.value = !hideOldPass.value; + }, + ), + ), + const SizedBox(height: 10), + TextEntry( + controller: newPassword, + obscureText: hideNewPass.value, + label: 'Nouveau mot de passe', + maxLines: 1, + suffixIcon: IconButton( + icon: Icon( + hideNewPass.value ? Icons.visibility : Icons.visibility_off, + color: Colors.grey.shade600, + ), + onPressed: () { + hideNewPass.value = !hideNewPass.value; + }, + ), + ), + const SizedBox(height: 10), + TextEntry( + controller: confirmPassword, + obscureText: hideConfirmPass.value, + label: 'Confirmer le mot de passe', + maxLines: 1, + suffixIcon: IconButton( + icon: Icon( + hideConfirmPass.value + ? Icons.visibility + : Icons.visibility_off, + color: Colors.grey.shade600, + ), + onPressed: () { + hideConfirmPass.value = !hideConfirmPass.value; + }, + ), + ), + const SizedBox(height: 20), + PasswordStrength(newPassword: newPassword), + const SizedBox(height: 20), + Button( + text: "Enregistrer", + onPressed: () async { + if (key.currentState!.validate()) { + await showDialog( + context: context, + builder: (context) => CustomDialogBox( + descriptions: AppLocalizations.of( + context, + )!.settingsChangingPassword, + onYes: () async { + final passwordChangedMsg = AppLocalizations.of( + context, + )!.settingsPasswordChanged; + final passwordChangeErrorMsg = AppLocalizations.of( + context, + )!.settingsUpdatingError; + await tokenExpireWrapper(ref, () async { + final value = await userNotifier.changePassword( + oldPassword.text, + newPassword.text, + user, + ); + if (value) { + QR.back(); + displayToastWithContext( + TypeMsg.msg, + passwordChangedMsg, + ); + } else { + displayToastWithContext( + TypeMsg.error, + passwordChangeErrorMsg, + ); + } + }); + }, + title: AppLocalizations.of(context)!.settingsEdit, + ), + ); + } + }, + ), + Center( + child: Text( + AppLocalizations.of(context)!.settingsSave, + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/settings/ui/pages/main_page/main_page.dart b/lib/settings/ui/pages/main_page/main_page.dart index e7f92e9484..16bfc540d6 100644 --- a/lib/settings/ui/pages/main_page/main_page.dart +++ b/lib/settings/ui/pages/main_page/main_page.dart @@ -1,323 +1,220 @@ -import 'package:auto_size_text/auto_size_text.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/flappybird/ui/flappybird_item_chip.dart'; -import 'package:titan/settings/providers/logs_provider.dart'; -import 'package:titan/settings/router.dart'; -import 'package:titan/settings/ui/pages/main_page/settings_item.dart'; +import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/settings/ui/pages/main_page/change_pass.dart'; + import 'package:titan/settings/ui/settings.dart'; -import 'package:titan/tools/ui/widgets/align_left_text.dart'; -import 'package:titan/tools/ui/builders/async_child.dart'; -import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; -import 'package:titan/tools/functions.dart'; +import 'package:titan/tools/constants.dart'; + import 'package:titan/tools/ui/layouts/horizontal_list_view.dart'; -import 'package:titan/tools/ui/layouts/item_chip.dart'; -import 'package:titan/tools/ui/layouts/refresher.dart'; -import 'package:titan/tools/repository/repository.dart'; -import 'package:titan/user/providers/user_provider.dart'; -import 'package:titan/user/providers/profile_picture_provider.dart'; -import 'package:titan/version/providers/titan_version_provider.dart'; -import 'package:qlevar_router/qlevar_router.dart'; -import 'package:titan/l10n/app_localizations.dart'; + +import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; +import 'package:titan/tools/ui/styleguide/button.dart'; +import 'package:titan/tools/ui/styleguide/item_chip.dart'; +import 'package:titan/tools/ui/styleguide/list_item.dart'; +import 'package:titan/tools/ui/styleguide/list_item_template.dart'; +import 'package:titan/tools/ui/styleguide/searchbar.dart'; class SettingsMainPage extends HookConsumerWidget { const SettingsMainPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - final me = ref.watch(userProvider); - final meNotifier = ref.watch(asyncUserProvider.notifier); - final titanVersion = ref.watch(titanVersionProvider); - final profilePicture = ref.watch(profilePictureProvider); - ref.watch(logsProvider.notifier).getLogs(); - - void displayToastWithContext(TypeMsg type, String msg) { - displayToast(context, type, msg); - } - return SettingsTemplate( - child: Refresher( - onRefresh: () async { - await meNotifier.loadMe(); - }, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 20), child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - const SizedBox(height: 25), - AsyncChild( - value: profilePicture, - builder: (context, profile) { - return Center( - child: Stack( - clipBehavior: Clip.none, - children: [ - Container( - decoration: BoxDecoration( - shape: BoxShape.circle, - boxShadow: [ - BoxShadow( - color: Colors.black.withValues(alpha: 0.3), - spreadRadius: 6, - blurRadius: 10, - offset: const Offset(0, 2), - ), + CustomSearchBar( + onFilter: () async { + await showCustomBottomModal( + modal: BottomModalTemplate( + title: 'Filtrer', + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Groupes d\'association'), + SizedBox(height: 10), + HorizontalListView( + height: 50, + children: [ + ItemChip(child: Text('Option 1')), + ItemChip(child: Text('Option 2')), + ItemChip(child: Text('Option 3')), ], ), - child: CircleAvatar( - radius: 70, - backgroundImage: profile.isEmpty - ? AssetImage(getTitanLogo()) - : Image.memory(profile).image, - ), - ), - Positioned( - top: 0, - left: -MediaQuery.of(context).size.width / 2 + 70, - child: Column( + SizedBox(height: 30), + Text('Associations'), + SizedBox(height: 10), + HorizontalListView( + height: 50, children: [ - const SizedBox(height: 125), - Container( - width: MediaQuery.of(context).size.width, - decoration: BoxDecoration( - color: Colors.white, - boxShadow: [ - BoxShadow( - color: Colors.white.withValues(alpha: 0.5), - spreadRadius: 5, - blurRadius: 10, - offset: const Offset(-2, -3), - ), - ], - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Column( - children: [ - const SizedBox(height: 8), - Text( - me.nickname ?? me.firstname, - style: const TextStyle( - fontSize: 25, - fontWeight: FontWeight.bold, - ), - ), - ], - ), - const SizedBox(height: 3), - Text( - me.nickname != null - ? "${me.firstname} ${me.name}" - : me.name, - style: const TextStyle(fontSize: 20), - ), - ], - ), - ), + ItemChip(child: Text('Association 1')), + ItemChip(child: Text('Association 2')), + ItemChip(child: Text('Association 3')), ], ), - ), - ], + SizedBox(height: 40), + Button( + text: 'Appliquer', + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ), ), + context: context, + ref: ref, ); }, - errorBuilder: (e, s) => - const HeroIcon(HeroIcons.userCircle, size: 140), + onSearch: (_) {}, ), - const SizedBox(height: 100), - HorizontalListView.builder( - height: 40, - items: me.groups, - itemBuilder: (context, item, i) => ItemChip( - selected: true, - child: Text( - capitalize(item.name), - style: const TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, - ), - ), + const SizedBox(height: 20), + const Text( + "Paramètres", + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: ColorConstants.title, ), - lastChild: const FlappyBirdItemChip(), ), - const SizedBox(height: 30), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 30.0), - child: Column( - children: [ - AlignLeftText( - AppLocalizations.of(context)!.settingsAccount, - fontSize: 25, - ), - const SizedBox(height: 30), - SettingsItem( - icon: HeroIcons.pencil, - onTap: () { - QR.to(SettingsRouter.root + SettingsRouter.editAccount); - }, - child: Text( - AppLocalizations.of(context)!.settingsEditAccount, - style: const TextStyle(fontSize: 16, color: Colors.black), - ), - ), - const SizedBox(height: 30), - SettingsItem( - icon: HeroIcons.calendarDays, - onTap: () { - final icalCopiedMsg = AppLocalizations.of( - context, - )!.settingsIcalCopied; - Clipboard.setData( - ClipboardData(text: "${Repository.host}calendar/ical"), - ).then((value) { - displayToastWithContext(TypeMsg.msg, icalCopiedMsg); - }); - }, - child: Text( - AppLocalizations.of(context)!.settingsEventsIcal, - style: const TextStyle(fontSize: 16, color: Colors.black), - ), - ), - const SizedBox(height: 50), - AlignLeftText( - AppLocalizations.of(context)!.settingsSecurity, - fontSize: 25, - ), - const SizedBox(height: 30), - SettingsItem( - icon: HeroIcons.lockClosed, - onTap: () { - QR.to( - SettingsRouter.root + SettingsRouter.changePassword, - ); - }, - child: Text( - AppLocalizations.of(context)!.settingsEditPassword, - style: const TextStyle(fontSize: 16, color: Colors.black), - ), - ), - const SizedBox(height: 50), - if (!kIsWeb) ...[ - AlignLeftText( - AppLocalizations.of(context)!.settingsHelp, - fontSize: 25, - ), - const SizedBox(height: 30), - SettingsItem( - icon: HeroIcons.clipboardDocumentList, - onTap: () { - QR.to(SettingsRouter.root + SettingsRouter.logs); - }, - child: Text( - AppLocalizations.of(context)!.settingsLogs, - style: const TextStyle( - fontSize: 16, - color: Colors.black, + const SizedBox(height: 20), + ListItem( + title: "Langue", + subtitle: "Français", + onTap: () async { + await showCustomBottomModal( + modal: BottomModalTemplate( + title: 'Choix de la langue', + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ListItemTemplate( + title: "🇫🇷 Français", + trailing: Container(), ), - ), - ), - const SizedBox(height: 50), - ], - AlignLeftText( - AppLocalizations.of(context)!.settingsPersonalisation, - fontSize: 25, - ), - const SizedBox(height: 30), - SettingsItem( - icon: HeroIcons.queueList, - onTap: () { - QR.to(SettingsRouter.root + SettingsRouter.modules); - }, - child: Text( - AppLocalizations.of(context)!.settingsModules, - style: const TextStyle(fontSize: 16, color: Colors.black), - ), - ), - const SizedBox(height: 30), - SettingsItem( - icon: HeroIcons.bellAlert, - onTap: () { - QR.to(SettingsRouter.root + SettingsRouter.notifications); - }, - child: Text( - AppLocalizations.of(context)!.settingsNotifications, - style: const TextStyle(fontSize: 16, color: Colors.black), - ), - ), - const SizedBox(height: 50), - AlignLeftText( - AppLocalizations.of(context)!.settingsPersonalData, - fontSize: 25, - ), - const SizedBox(height: 30), - SettingsItem( - icon: HeroIcons.circleStack, - onTap: () async { - showDialog( - context: context, - builder: (BuildContext context) { - return CustomDialogBox( - title: AppLocalizations.of( - context, - )!.settingsDetelePersonalData, - descriptions: AppLocalizations.of( - context, - )!.settingsDetelePersonalDataDesc, - onYes: () async { - final sendedDemandMsg = AppLocalizations.of( - context, - )!.settingsSendedDemand; - final errorSendingDemandMsg = AppLocalizations.of( - context, - )!.settingsErrorSendingDemand; - final value = await meNotifier.deletePersonal(); - if (value) { - displayToastWithContext( - TypeMsg.msg, - sendedDemandMsg, - ); - } else { - displayToastWithContext( - TypeMsg.error, - errorSendingDemandMsg, - ); - } - }, - ); - }, - ); - }, - child: Text( - AppLocalizations.of(context)!.settingsDetelePersonalData, - style: const TextStyle(fontSize: 16, color: Colors.black), - ), - ), - const SizedBox(height: 60), - Text( - "${AppLocalizations.of(context)!.settingsVersion} $titanVersion", - style: const TextStyle( - fontSize: 15, - fontWeight: FontWeight.w500, - color: Colors.black, + ListItemTemplate( + title: "🇬🇧 English", + trailing: const HeroIcon( + HeroIcons.check, + color: ColorConstants.tertiary, + ), + ), + ], ), ), - const SizedBox(height: 10), - AutoSizeText( - Repository.host, - maxLines: 1, - minFontSize: 10, - style: const TextStyle( - fontSize: 15, - fontWeight: FontWeight.w500, - color: Colors.black, + context: context, + ref: ref, + ); + }, + ), + ListItem( + title: "Notifications", + subtitle: "2/3 activées", + onTap: () async { + bool showAnnonceDetails = false; + await showCustomBottomModal( + modal: BottomModalTemplate( + title: 'Notifications', + child: StatefulBuilder( + builder: (context, setState) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ListItemTemplate( + title: "Feed", + trailing: Container(), + ), + ListItemTemplate( + title: "Campagnes", + trailing: const HeroIcon( + HeroIcons.check, + color: ColorConstants.tertiary, + ), + ), + ListItemTemplate( + title: "Annonces", + trailing: const HeroIcon( + HeroIcons.chevronDown, + color: ColorConstants.tertiary, + ), + onTap: () { + setState(() { + showAnnonceDetails = !showAnnonceDetails; + }); + }, + ), + if (showAnnonceDetails) ...[ + const SizedBox(height: 8), + Padding( + padding: const EdgeInsets.only(left: 20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ListItemTemplate( + title: "BDE", + trailing: Container(), + ), + ListItemTemplate( + title: "BDS", + trailing: const HeroIcon( + HeroIcons.check, + color: ColorConstants.tertiary, + ), + ), + ], + ), + ), + ], + ], + ); + }, ), ), - const SizedBox(height: 20), - ], + context: context, + ref: ref, + ); + }, + ), + const SizedBox(height: 20), + const Text( + "Événement", + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: ColorConstants.title, ), ), + const ListItem( + title: "Lien ical", + subtitle: "Synchroniser avec votre calendrier", + ), + const SizedBox(height: 20), + const Text( + "Profil", + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: ColorConstants.title, + ), + ), + ListItem( + title: "Mot de passe", + subtitle: "Changer mon mot de passe", + onTap: () async { + await showCustomBottomModal( + modal: BottomModalTemplate( + title: 'Mot de passe', + child: ChangePassPage(), + ), + context: context, + ref: ref, + ); + }, + ), ], ), ), diff --git a/lib/settings/ui/pages/change_pass/password_strength.dart b/lib/settings/ui/pages/main_page/password_strength.dart similarity index 98% rename from lib/settings/ui/pages/change_pass/password_strength.dart rename to lib/settings/ui/pages/main_page/password_strength.dart index abf13e2584..de6a6750cb 100644 --- a/lib/settings/ui/pages/change_pass/password_strength.dart +++ b/lib/settings/ui/pages/main_page/password_strength.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/settings/ui/pages/change_pass/secure_bar.dart'; +import 'package:titan/settings/ui/pages/main_page/secure_bar.dart'; import 'package:titan/tools/ui/widgets/align_left_text.dart'; import 'package:titan/l10n/app_localizations.dart'; diff --git a/lib/settings/ui/pages/change_pass/secure_bar.dart b/lib/settings/ui/pages/main_page/secure_bar.dart similarity index 100% rename from lib/settings/ui/pages/change_pass/secure_bar.dart rename to lib/settings/ui/pages/main_page/secure_bar.dart diff --git a/lib/settings/ui/pages/main_page/settings_item.dart b/lib/settings/ui/pages/main_page/settings_item.dart deleted file mode 100644 index 4b26ad2bba..0000000000 --- a/lib/settings/ui/pages/main_page/settings_item.dart +++ /dev/null @@ -1,54 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:heroicons/heroicons.dart'; - -class SettingsItem extends StatelessWidget { - final Widget child; - final HeroIcons icon; - final void Function() onTap; - - const SettingsItem({ - super.key, - required this.icon, - required this.onTap, - required this.child, - }); - - @override - Widget build(BuildContext context) { - return GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: onTap, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Padding( - padding: const EdgeInsets.only(right: 20), - child: HeroIcon(icon, size: 30, color: Colors.black), - ), - Expanded(child: child), - Container( - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - color: Colors.white, - border: Border.all(color: Colors.black), - boxShadow: [ - BoxShadow( - color: Colors.grey.shade400.withValues(alpha: 0.3), - spreadRadius: 2, - blurRadius: 5, - offset: const Offset(2, 3), - ), - ], - borderRadius: BorderRadius.circular(10), - ), - child: const HeroIcon( - HeroIcons.chevronRight, - size: 25, - color: Colors.black, - ), - ), - ], - ), - ); - } -} diff --git a/lib/settings/ui/pages/change_pass/test_entry_style.dart b/lib/settings/ui/pages/main_page/test_entry_style.dart similarity index 100% rename from lib/settings/ui/pages/change_pass/test_entry_style.dart rename to lib/settings/ui/pages/main_page/test_entry_style.dart diff --git a/lib/settings/ui/pages/modules_page/modules_page.dart b/lib/settings/ui/pages/modules_page/modules_page.dart deleted file mode 100644 index fbf6a1d336..0000000000 --- a/lib/settings/ui/pages/modules_page/modules_page.dart +++ /dev/null @@ -1,75 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:heroicons/heroicons.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/settings/providers/module_list_provider.dart'; -import 'package:titan/settings/ui/settings.dart'; - -class ModulesPage extends HookConsumerWidget { - const ModulesPage({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final modules = ref.watch(modulesProvider); - final modulesNotifier = ref.watch(modulesProvider.notifier); - return SettingsTemplate( - child: Container( - margin: const EdgeInsets.symmetric(horizontal: 20), - child: ReorderableListView( - physics: const BouncingScrollPhysics(), - proxyDecorator: (child, index, animation) { - return Material( - child: FadeTransition(opacity: animation, child: child), - ); - }, - onReorder: (int oldIndex, int newIndex) { - modulesNotifier.reorderModules(oldIndex, newIndex); - }, - children: modulesNotifier.allModules.map((module) { - return Container( - margin: const EdgeInsets.all(10), - padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 5), - key: Key(module.root.toString()), - decoration: BoxDecoration( - boxShadow: [ - BoxShadow( - color: Colors.grey.withValues(alpha: 0.2), - spreadRadius: 5, - blurRadius: 10, - offset: const Offset(0, 2), // changes position of shadow - ), - ], - color: Colors.white, - borderRadius: BorderRadius.circular(15), - ), - child: Container( - padding: const EdgeInsets.symmetric(vertical: 5), - child: Row( - children: [ - Text( - module.getName(context), - style: const TextStyle( - color: Colors.black, - fontSize: 20, - fontWeight: FontWeight.w500, - ), - textAlign: TextAlign.left, - ), - const Spacer(), - Checkbox( - value: modules.contains(module), - activeColor: Colors.grey.shade700, - onChanged: (bool? value) { - modulesNotifier.toggleModule(module); - }, - ), - const HeroIcon(HeroIcons.chevronUpDown, size: 30), - ], - ), - ), - ); - }).toList(), - ), - ), - ); - } -} diff --git a/lib/settings/ui/pages/notification_page/notification_page.dart b/lib/settings/ui/pages/notification_page/notification_page.dart deleted file mode 100644 index 1e6977b3a2..0000000000 --- a/lib/settings/ui/pages/notification_page/notification_page.dart +++ /dev/null @@ -1,128 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:load_switch/load_switch.dart'; -import 'package:titan/service/class/topic.dart'; -import 'package:titan/service/providers/topic_provider.dart'; -import 'package:titan/service/tools/functions.dart'; -import 'package:titan/settings/ui/settings.dart'; -import 'package:titan/tools/constants.dart'; -import 'package:titan/tools/ui/widgets/align_left_text.dart'; -import 'package:titan/tools/ui/builders/async_child.dart'; -import 'package:titan/tools/ui/layouts/refresher.dart'; -import 'package:titan/l10n/app_localizations.dart'; - -class NotificationPage extends HookConsumerWidget { - const NotificationPage({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final topics = ref.watch(topicsProvider); - final topicsNotifier = ref.read(topicsProvider.notifier); - return SettingsTemplate( - child: Refresher( - onRefresh: () async { - await topicsNotifier.getTopics(); - }, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 30.0), - child: Column( - children: [ - AlignLeftText( - AppLocalizations.of(context)!.settingsUpdateNotification, - padding: const EdgeInsets.symmetric(vertical: 30), - color: Colors.grey, - ), - AsyncChild( - value: topics, - builder: (context, topic) => Column( - children: Topic.values - .map( - (e) => Padding( - padding: const EdgeInsets.symmetric(vertical: 12), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - topicToFrenchString(e), - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.w700, - color: ColorConstants.background2, - ), - ), - LoadSwitch( - value: topic.contains(e), - future: () => - topicsNotifier.toggleSubscription(e), - height: 30, - width: 60, - curveIn: Curves.easeInBack, - curveOut: Curves.easeOutBack, - animationDuration: const Duration( - milliseconds: 500, - ), - switchDecoration: (value, _) => BoxDecoration( - color: value - ? ColorConstants.gradient1.withValues( - alpha: 0.3, - ) - : Colors.grey.shade200, - borderRadius: BorderRadius.circular(30), - shape: BoxShape.rectangle, - boxShadow: [ - BoxShadow( - color: value - ? ColorConstants.gradient1.withValues( - alpha: 0.2, - ) - : Colors.grey.withValues(alpha: 0.2), - spreadRadius: 1, - blurRadius: 3, - offset: const Offset(0, 1), - ), - ], - ), - spinColor: (value) => value - ? ColorConstants.gradient1 - : Colors.grey, - spinStrokeWidth: 2, - thumbDecoration: (value, _) => BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(30), - shape: BoxShape.rectangle, - boxShadow: [ - BoxShadow( - color: value - ? ColorConstants.gradient1.withValues( - alpha: 0.2, - ) - : Colors.grey.shade200.withValues( - alpha: 0.2, - ), - spreadRadius: 5, - blurRadius: 7, - offset: const Offset( - 0, - 3, - ), // changes position of shadow - ), - ], - ), - onChange: (v) {}, - onTap: (v) {}, - ), - ], - ), - ), - ) - .toList(), - ), - loaderColor: ColorConstants.gradient1, - ), - ], - ), - ), - ), - ); - } -} diff --git a/lib/tools/ui/styleguide/text_entry.dart b/lib/tools/ui/styleguide/text_entry.dart index 4214fab8d1..8d4d16a12c 100644 --- a/lib/tools/ui/styleguide/text_entry.dart +++ b/lib/tools/ui/styleguide/text_entry.dart @@ -14,6 +14,7 @@ class TextEntry extends StatelessWidget { final String? Function(String)? validator; final int? minLines, maxLines; final TextInputAction textInputAction; + final bool obscureText; const TextEntry({ super.key, @@ -37,6 +38,7 @@ class TextEntry extends StatelessWidget { this.suffixIcon, this.isNegative = false, this.textInputAction = TextInputAction.next, + this.obscureText = false, }); @override @@ -48,6 +50,7 @@ class TextEntry extends StatelessWidget { keyboardType: keyboardType, cursorColor: color, onChanged: onChanged, + obscureText: obscureText, textInputAction: (keyboardType == TextInputType.multiline) ? TextInputAction.newline : TextInputAction.next, @@ -57,13 +60,15 @@ class TextEntry extends StatelessWidget { canBeEmpty ? '$label (optionnel)' : label, style: TextStyle(color: color, height: 0.5), ), + suffixIcon: suffixIcon, suffix: suffixIcon == null && suffix.isEmpty ? null - : Container( - padding: const EdgeInsets.only(left: 10), - child: - suffixIcon ?? Text(suffix, style: TextStyle(color: color)), - ), + : (suffixIcon == null + ? Padding( + padding: const EdgeInsets.only(left: 10), + child: Text(suffix, style: TextStyle(color: color)), + ) + : null), prefix: prefix.isEmpty ? null : Container( From ed4ee420c2ee210f93092f2719e3274cf4e0be99 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Tue, 8 Jul 2025 21:05:37 +0200 Subject: [PATCH 093/473] Remove useless imports --- lib/settings/ui/pages/main_page/change_pass.dart | 6 ------ lib/settings/ui/pages/main_page/main_page.dart | 1 - 2 files changed, 7 deletions(-) diff --git a/lib/settings/ui/pages/main_page/change_pass.dart b/lib/settings/ui/pages/main_page/change_pass.dart index 0217920e56..145ba4f592 100644 --- a/lib/settings/ui/pages/main_page/change_pass.dart +++ b/lib/settings/ui/pages/main_page/change_pass.dart @@ -1,18 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/settings/ui/pages/main_page/password_strength.dart'; -import 'package:titan/settings/ui/pages/main_page/test_entry_style.dart'; -import 'package:titan/tools/constants.dart'; -import 'package:titan/tools/ui/layouts/add_edit_button_layout.dart'; import 'package:titan/tools/ui/styleguide/button.dart'; import 'package:titan/tools/ui/styleguide/text_entry.dart'; -import 'package:titan/tools/ui/widgets/align_left_text.dart'; import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; -import 'package:titan/tools/ui/builders/waiting_button.dart'; import 'package:titan/user/providers/user_provider.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/l10n/app_localizations.dart'; diff --git a/lib/settings/ui/pages/main_page/main_page.dart b/lib/settings/ui/pages/main_page/main_page.dart index 16bfc540d6..7679b33a66 100644 --- a/lib/settings/ui/pages/main_page/main_page.dart +++ b/lib/settings/ui/pages/main_page/main_page.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/settings/ui/pages/main_page/change_pass.dart'; import 'package:titan/settings/ui/settings.dart'; From 553eff247e68cef052f6981a5fb298212a2bd4a8 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Tue, 8 Jul 2025 21:09:24 +0200 Subject: [PATCH 094/473] ical copied --- .../ui/pages/main_page/main_page.dart | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/lib/settings/ui/pages/main_page/main_page.dart b/lib/settings/ui/pages/main_page/main_page.dart index 7679b33a66..281ca9c134 100644 --- a/lib/settings/ui/pages/main_page/main_page.dart +++ b/lib/settings/ui/pages/main_page/main_page.dart @@ -1,10 +1,13 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/settings/ui/pages/main_page/change_pass.dart'; import 'package:titan/settings/ui/settings.dart'; import 'package:titan/tools/constants.dart'; +import 'package:titan/tools/functions.dart'; +import 'package:titan/tools/repository/repository.dart'; import 'package:titan/tools/ui/layouts/horizontal_list_view.dart'; @@ -20,6 +23,10 @@ class SettingsMainPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + void displayToastWithContext(TypeMsg type, String msg) { + displayToast(context, type, msg); + } + return SettingsTemplate( child: Container( padding: const EdgeInsets.symmetric(horizontal: 20), @@ -187,9 +194,19 @@ class SettingsMainPage extends HookConsumerWidget { color: ColorConstants.title, ), ), - const ListItem( + ListItem( title: "Lien ical", subtitle: "Synchroniser avec votre calendrier", + onTap: () { + Clipboard.setData( + ClipboardData(text: "${Repository.host}calendar/ical"), + ).then((value) { + displayToastWithContext( + TypeMsg.msg, + "Lien ical copié dans le presse-papiers", + ); + }); + }, ), const SizedBox(height: 20), const Text( From fcd47d5430a8a08b3ca0e833ce36a5d9350509ed Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 16 Jul 2025 18:30:59 +0200 Subject: [PATCH 095/473] Enable language changing --- lib/main.dart | 4 ++- .../ui/pages/main_page/main_page.dart | 36 +++++++++++++++---- lib/tools/providers/locale_notifier.dart | 29 +++++++++++++++ 3 files changed, 61 insertions(+), 8 deletions(-) create mode 100644 lib/tools/providers/locale_notifier.dart diff --git a/lib/main.dart b/lib/main.dart index f168ae616e..416f6e0528 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -15,6 +15,7 @@ import 'package:titan/router.dart'; import 'package:titan/service/tools/setup.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/plausible/plausible_observer.dart'; +import 'package:titan/tools/providers/locale_notifier.dart'; import 'package:titan/tools/providers/path_forwarding_provider.dart'; import 'package:titan/tools/ui/layouts/app_template.dart'; import 'package:qlevar_router/qlevar_router.dart'; @@ -114,12 +115,13 @@ class MyApp extends HookConsumerWidget { } return AppTemplate(child: child); }, - routerDelegate: QRouterDelegate( + routerDelegate: QRouterDelegate( appRouter.routes, observers: [if (plausible != null) PlausibleObserver(plausible)], initPath: AppRouter.root, navKey: navigatorKey, ), + ); } } diff --git a/lib/settings/ui/pages/main_page/main_page.dart b/lib/settings/ui/pages/main_page/main_page.dart index 281ca9c134..5543cd1ef0 100644 --- a/lib/settings/ui/pages/main_page/main_page.dart +++ b/lib/settings/ui/pages/main_page/main_page.dart @@ -2,11 +2,13 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/settings/ui/pages/main_page/change_pass.dart'; import 'package:titan/settings/ui/settings.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; +import 'package:titan/tools/providers/locale_notifier.dart'; import 'package:titan/tools/repository/repository.dart'; import 'package:titan/tools/ui/layouts/horizontal_list_view.dart'; @@ -90,7 +92,7 @@ class SettingsMainPage extends HookConsumerWidget { const SizedBox(height: 20), ListItem( title: "Langue", - subtitle: "Français", + subtitle: ref.watch(localeProvider)?.languageCode, onTap: () async { await showCustomBottomModal( modal: BottomModalTemplate( @@ -100,15 +102,35 @@ class SettingsMainPage extends HookConsumerWidget { children: [ ListItemTemplate( title: "🇫🇷 Français", - trailing: Container(), + onTap: () async { + await ref + .read(localeProvider.notifier) + .setLocale(const Locale('fr')); + }, + trailing: + ref.watch(localeProvider)?.languageCode == 'fr' + ? const HeroIcon( + HeroIcons.check, + color: ColorConstants.tertiary, + ) + : Container(), ), ListItemTemplate( title: "🇬🇧 English", - trailing: const HeroIcon( - HeroIcons.check, - color: ColorConstants.tertiary, - ), + onTap: () async { + await ref + .read(localeProvider.notifier) + .setLocale(const Locale('en')); + }, + trailing: + ref.watch(localeProvider)?.languageCode == 'en' + ? const HeroIcon( + HeroIcons.check, + color: ColorConstants.tertiary, + ) + : Container(), ), + const SizedBox(height: 20), ], ), ), @@ -118,7 +140,7 @@ class SettingsMainPage extends HookConsumerWidget { }, ), ListItem( - title: "Notifications", + title: AppLocalizations.of(context)!.cinemaNoSession, subtitle: "2/3 activées", onTap: () async { bool showAnnonceDetails = false; diff --git a/lib/tools/providers/locale_notifier.dart b/lib/tools/providers/locale_notifier.dart new file mode 100644 index 0000000000..3a5c8b45f1 --- /dev/null +++ b/lib/tools/providers/locale_notifier.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +final localeProvider = StateNotifierProvider( + (ref) => LocaleNotifier(), +); + +class LocaleNotifier extends StateNotifier { + static const _localeKey = 'locale'; + + LocaleNotifier() : super(null) { + _loadLocale(); + } + + Future _loadLocale() async { + final prefs = await SharedPreferences.getInstance(); + final localeCode = prefs.getString(_localeKey); + if (localeCode != null) { + state = Locale(localeCode); + } + } + + Future setLocale(Locale locale) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setString(_localeKey, locale.languageCode); + state = locale; + } +} From fbbedcf3814b1f43ee118443955e993d582e1834 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 16 Jul 2025 18:33:00 +0200 Subject: [PATCH 096/473] typo --- lib/settings/ui/pages/main_page/main_page.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/settings/ui/pages/main_page/main_page.dart b/lib/settings/ui/pages/main_page/main_page.dart index 5543cd1ef0..e9ea4db4ad 100644 --- a/lib/settings/ui/pages/main_page/main_page.dart +++ b/lib/settings/ui/pages/main_page/main_page.dart @@ -140,7 +140,7 @@ class SettingsMainPage extends HookConsumerWidget { }, ), ListItem( - title: AppLocalizations.of(context)!.cinemaNoSession, + title: "Notifications", subtitle: "2/3 activées", onTap: () async { bool showAnnonceDetails = false; From 96821e96691095e3658b693a2ebe3856d77f08c7 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 16 Jul 2025 18:36:29 +0200 Subject: [PATCH 097/473] Changing icon --- lib/settings/ui/pages/main_page/main_page.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/settings/ui/pages/main_page/main_page.dart b/lib/settings/ui/pages/main_page/main_page.dart index e9ea4db4ad..d81473d50c 100644 --- a/lib/settings/ui/pages/main_page/main_page.dart +++ b/lib/settings/ui/pages/main_page/main_page.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/settings/ui/pages/main_page/change_pass.dart'; import 'package:titan/settings/ui/settings.dart'; @@ -165,8 +164,10 @@ class SettingsMainPage extends HookConsumerWidget { ), ListItemTemplate( title: "Annonces", - trailing: const HeroIcon( - HeroIcons.chevronDown, + trailing: HeroIcon( + showAnnonceDetails + ? HeroIcons.minus + : HeroIcons.plus, color: ColorConstants.tertiary, ), onTap: () { From b7141813f66f979246d35f9667b689e8b90f7eca Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 19 Jul 2025 19:30:17 +0200 Subject: [PATCH 098/473] Notif subscribe part 1 --- lib/settings/class/notification_topic.dart | 61 +++ lib/settings/providers/logs_provider.dart | 68 --- lib/settings/providers/logs_tab_provider.dart | 15 - .../notification_topic_provider.dart | 50 ++ .../notification_topic_repository.dart | 30 ++ .../ui/pages/main_page/main_page.dart | 449 +++++++++--------- 6 files changed, 366 insertions(+), 307 deletions(-) create mode 100644 lib/settings/class/notification_topic.dart delete mode 100644 lib/settings/providers/logs_provider.dart delete mode 100644 lib/settings/providers/logs_tab_provider.dart create mode 100644 lib/settings/providers/notification_topic_provider.dart create mode 100644 lib/settings/repositories/notification_topic_repository.dart diff --git a/lib/settings/class/notification_topic.dart b/lib/settings/class/notification_topic.dart new file mode 100644 index 0000000000..e0b70a7405 --- /dev/null +++ b/lib/settings/class/notification_topic.dart @@ -0,0 +1,61 @@ +class NotificationTopic { + NotificationTopic({ + required this.id, + required this.name, + required this.moduleRoot, + this.topicIdentifier, + required this.isUserSubscribed, + }); + late final String id; + late final String name; + late final String moduleRoot; + late final String? topicIdentifier; + late final bool isUserSubscribed; + + NotificationTopic.fromJson(Map json) { + id = json['id']; + name = json['name']; + moduleRoot = json['module_root']; + topicIdentifier = json['topic_identifier']; + isUserSubscribed = json['is_user_subscribed']; + } + + Map toJson() { + final data = {}; + data['id'] = id; + data['name'] = name; + data['module_root'] = moduleRoot; + data['topic_identifier'] = topicIdentifier; + data['is_user_subscribed'] = isUserSubscribed; + return data; + } + + NotificationTopic copyWith({ + String? id, + String? name, + String? moduleRoot, + String? topicIdentifier, + bool? isUserSubscribed, + }) { + return NotificationTopic( + id: id ?? this.id, + name: name ?? this.name, + moduleRoot: moduleRoot ?? this.moduleRoot, + topicIdentifier: topicIdentifier ?? this.topicIdentifier, + isUserSubscribed: isUserSubscribed ?? this.isUserSubscribed, + ); + } + + NotificationTopic.empty() { + id = ''; + name = ''; + moduleRoot = ''; + topicIdentifier = null; + isUserSubscribed = false; + } + + @override + String toString() { + return 'NotificationTopic{id : $id, name : $name, moduleRoot : $moduleRoot, topicIdentifier : $topicIdentifier, isUserSubscribed : $isUserSubscribed}'; + } +} diff --git a/lib/settings/providers/logs_provider.dart b/lib/settings/providers/logs_provider.dart deleted file mode 100644 index 61b4e03c42..0000000000 --- a/lib/settings/providers/logs_provider.dart +++ /dev/null @@ -1,68 +0,0 @@ -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/tools/logs/log.dart'; -import 'package:titan/tools/logs/logger.dart'; -import 'package:titan/tools/providers/list_notifier.dart'; -import 'package:titan/tools/repository/repository.dart'; - -class LogsProvider extends ListNotifier { - Logger logger = Repository.logger; - LogsProvider() : super(const AsyncValue.loading()); - - Future>> getLogs() async { - return await loadList(() async => logger.getLogs()); - } - - Future> getNotificationLogs() async { - return logger.getNotificationLogs(); - } - - Future deleteLogs() async { - return await delete( - (id) async => true, - (listT, t) { - logger.clearLogs(); - return []; - }, - "", - Log.empty(), - ); - } -} - -final logsProvider = StateNotifierProvider>>( - (ref) { - LogsProvider notifier = LogsProvider(); - notifier.getLogs(); - return notifier; - }, -); - -class NotificationLogsProvider extends ListNotifier { - Logger logger = Repository.logger; - NotificationLogsProvider() : super(const AsyncValue.loading()); - - Future>> getLogs() async { - return await loadList(() async => logger.getNotificationLogs()); - } - - Future deleteLogs() async { - return await delete( - (id) async => true, - (listT, t) { - logger.clearNotificationLogs(); - return []; - }, - "", - Log.empty(), - ); - } -} - -final notificationLogsProvider = - StateNotifierProvider>>(( - ref, - ) { - NotificationLogsProvider notifier = NotificationLogsProvider(); - notifier.getLogs(); - return notifier; - }); diff --git a/lib/settings/providers/logs_tab_provider.dart b/lib/settings/providers/logs_tab_provider.dart deleted file mode 100644 index bef4623762..0000000000 --- a/lib/settings/providers/logs_tab_provider.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; - -enum LogTabs { log, notification } - -class LogTabsNotifier extends StateNotifier { - LogTabsNotifier() : super(LogTabs.log); - - void setLogTabs(LogTabs i) { - state = i; - } -} - -final logTabProvider = StateNotifierProvider((ref) { - return LogTabsNotifier(); -}); diff --git a/lib/settings/providers/notification_topic_provider.dart b/lib/settings/providers/notification_topic_provider.dart new file mode 100644 index 0000000000..7a8f56fb8b --- /dev/null +++ b/lib/settings/providers/notification_topic_provider.dart @@ -0,0 +1,50 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/auth/providers/openid_provider.dart'; +import 'package:titan/settings/class/notification_topic.dart'; +import 'package:titan/settings/repositories/notification_topic_repository.dart'; +import 'package:titan/tools/providers/list_notifier.dart'; +import 'package:titan/tools/token_expire_wrapper.dart'; + +class NotificationTopicNotifier extends ListNotifier { + final NotificationTopicRepository notificationTopicRepository = + NotificationTopicRepository(); + NotificationTopicNotifier({required String token}) + : super(const AsyncValue.loading()) { + notificationTopicRepository.setToken(token); + } + + Future>> + loadNotificationTopicList() async { + return await loadList( + () async => notificationTopicRepository.getAllNotificationTopic(), + ); + } + + Future toggleSubscription(NotificationTopic topic) async { + return await update( + topic.isUserSubscribed + ? notificationTopicRepository.unsubscribeTopic + : notificationTopicRepository.subscribeTopic, + (topics, topic) { + topics[topics.indexWhere((t) => t.id == topic.id)] = topic.copyWith( + isUserSubscribed: !topic.isUserSubscribed, + ); + return topics; + }, + topic, + ); + } +} + +final notificationTopicListProvider = + StateNotifierProvider< + NotificationTopicNotifier, + AsyncValue> + >((ref) { + final token = ref.watch(tokenProvider); + final notifier = NotificationTopicNotifier(token: token); + tokenExpireWrapperAuth(ref, () async { + await notifier.loadNotificationTopicList(); + }); + return notifier; + }); diff --git a/lib/settings/repositories/notification_topic_repository.dart b/lib/settings/repositories/notification_topic_repository.dart new file mode 100644 index 0000000000..d1a597bc65 --- /dev/null +++ b/lib/settings/repositories/notification_topic_repository.dart @@ -0,0 +1,30 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/auth/providers/openid_provider.dart'; +import 'package:titan/settings/class/notification_topic.dart'; +import 'package:titan/tools/repository/repository.dart'; + +class NotificationTopicRepository extends Repository { + @override + // ignore: overridden_fields + final ext = "notification/"; + + Future> getAllNotificationTopic() async { + return (await getList( + suffix: 'topics', + )).map((e) => NotificationTopic.fromJson(e)).toList(); + } + + Future subscribeTopic(NotificationTopic topic) async { + return await create({}, suffix: "topics/${topic.id}/subscribe"); + } + + Future unsubscribeTopic(NotificationTopic topic) async { + return await create({}, suffix: "topics/${topic.id}/unsubscribe"); + } +} + +final notificationTopicRepositoryProvider = + Provider((ref) { + final token = ref.watch(tokenProvider); + return NotificationTopicRepository()..setToken(token); + }); diff --git a/lib/settings/ui/pages/main_page/main_page.dart b/lib/settings/ui/pages/main_page/main_page.dart index d81473d50c..d4dbf393fb 100644 --- a/lib/settings/ui/pages/main_page/main_page.dart +++ b/lib/settings/ui/pages/main_page/main_page.dart @@ -2,6 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:load_switch/load_switch.dart'; +import 'package:titan/settings/providers/notification_topic_provider.dart'; import 'package:titan/settings/ui/pages/main_page/change_pass.dart'; import 'package:titan/settings/ui/settings.dart'; @@ -9,15 +11,11 @@ import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/providers/locale_notifier.dart'; import 'package:titan/tools/repository/repository.dart'; - -import 'package:titan/tools/ui/layouts/horizontal_list_view.dart'; - +import 'package:titan/tools/ui/builders/async_child.dart'; +import 'package:titan/tools/ui/layouts/refresher.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; -import 'package:titan/tools/ui/styleguide/button.dart'; -import 'package:titan/tools/ui/styleguide/item_chip.dart'; import 'package:titan/tools/ui/styleguide/list_item.dart'; import 'package:titan/tools/ui/styleguide/list_item_template.dart'; -import 'package:titan/tools/ui/styleguide/searchbar.dart'; class SettingsMainPage extends HookConsumerWidget { const SettingsMainPage({super.key}); @@ -28,233 +26,236 @@ class SettingsMainPage extends HookConsumerWidget { displayToast(context, type, msg); } + final notificationTopicListNotifier = ref.watch( + notificationTopicListProvider.notifier, + ); + return SettingsTemplate( - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - CustomSearchBar( - onFilter: () async { - await showCustomBottomModal( - modal: BottomModalTemplate( - title: 'Filtrer', - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('Groupes d\'association'), - SizedBox(height: 10), - HorizontalListView( - height: 50, - children: [ - ItemChip(child: Text('Option 1')), - ItemChip(child: Text('Option 2')), - ItemChip(child: Text('Option 3')), - ], - ), - SizedBox(height: 30), - Text('Associations'), - SizedBox(height: 10), - HorizontalListView( - height: 50, - children: [ - ItemChip(child: Text('Association 1')), - ItemChip(child: Text('Association 2')), - ItemChip(child: Text('Association 3')), - ], - ), - SizedBox(height: 40), - Button( - text: 'Appliquer', - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - ), - ), - context: context, - ref: ref, - ); - }, - onSearch: (_) {}, - ), - const SizedBox(height: 20), - const Text( - "Paramètres", - style: TextStyle( - fontSize: 24, - fontWeight: FontWeight.bold, - color: ColorConstants.title, + child: Refresher( + onRefresh: () async { + await notificationTopicListNotifier.loadNotificationTopicList(); + }, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 20), + const Text( + "Paramètres", + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: ColorConstants.title, + ), ), - ), - const SizedBox(height: 20), - ListItem( - title: "Langue", - subtitle: ref.watch(localeProvider)?.languageCode, - onTap: () async { - await showCustomBottomModal( - modal: BottomModalTemplate( - title: 'Choix de la langue', - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ListItemTemplate( - title: "🇫🇷 Français", - onTap: () async { - await ref - .read(localeProvider.notifier) - .setLocale(const Locale('fr')); - }, - trailing: - ref.watch(localeProvider)?.languageCode == 'fr' - ? const HeroIcon( - HeroIcons.check, - color: ColorConstants.tertiary, - ) - : Container(), - ), - ListItemTemplate( - title: "🇬🇧 English", - onTap: () async { - await ref - .read(localeProvider.notifier) - .setLocale(const Locale('en')); - }, - trailing: - ref.watch(localeProvider)?.languageCode == 'en' - ? const HeroIcon( - HeroIcons.check, - color: ColorConstants.tertiary, - ) - : Container(), - ), - const SizedBox(height: 20), - ], + const SizedBox(height: 20), + ListItem( + title: "Langue", + subtitle: ref.watch(localeProvider)?.languageCode, + onTap: () async { + await showCustomBottomModal( + modal: BottomModalTemplate( + title: 'Choix de la langue', + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ListItemTemplate( + title: "🇫🇷 Français", + onTap: () async { + await ref + .read(localeProvider.notifier) + .setLocale(const Locale('fr')); + }, + trailing: + ref.watch(localeProvider)?.languageCode == 'fr' + ? const HeroIcon( + HeroIcons.check, + color: ColorConstants.tertiary, + ) + : Container(), + ), + ListItemTemplate( + title: "🇬🇧 English", + onTap: () async { + await ref + .read(localeProvider.notifier) + .setLocale(const Locale('en')); + }, + trailing: + ref.watch(localeProvider)?.languageCode == 'en' + ? const HeroIcon( + HeroIcons.check, + color: ColorConstants.tertiary, + ) + : Container(), + ), + const SizedBox(height: 20), + ], + ), ), - ), - context: context, - ref: ref, - ); - }, - ), - ListItem( - title: "Notifications", - subtitle: "2/3 activées", - onTap: () async { - bool showAnnonceDetails = false; - await showCustomBottomModal( - modal: BottomModalTemplate( - title: 'Notifications', - child: StatefulBuilder( - builder: (context, setState) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ListItemTemplate( - title: "Feed", - trailing: Container(), - ), - ListItemTemplate( - title: "Campagnes", - trailing: const HeroIcon( - HeroIcons.check, - color: ColorConstants.tertiary, - ), - ), - ListItemTemplate( - title: "Annonces", - trailing: HeroIcon( - showAnnonceDetails - ? HeroIcons.minus - : HeroIcons.plus, - color: ColorConstants.tertiary, - ), - onTap: () { - setState(() { - showAnnonceDetails = !showAnnonceDetails; - }); - }, - ), - if (showAnnonceDetails) ...[ - const SizedBox(height: 8), - Padding( - padding: const EdgeInsets.only(left: 20.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ListItemTemplate( - title: "BDE", - trailing: Container(), - ), - ListItemTemplate( - title: "BDS", - trailing: const HeroIcon( - HeroIcons.check, - color: ColorConstants.tertiary, + context: context, + ref: ref, + ); + }, + ), + ListItem( + title: "Notifications", + subtitle: "2/3 activées", + onTap: () async { + await showCustomBottomModal( + modal: BottomModalTemplate( + title: 'Notifications', + child: Consumer( + builder: (context, ref, child) { + final notificationTopicList = ref.watch( + notificationTopicListProvider, + ); + return AsyncChild( + value: notificationTopicList, + builder: (context, notificationTopicList) { + return Column( + children: [ + ...notificationTopicList.map( + (notificationTopic) => ListItemTemplate( + title: notificationTopic.name, + trailing: LoadSwitch( + value: + notificationTopic.isUserSubscribed, + future: () async { + await notificationTopicListNotifier + .toggleSubscription( + notificationTopic, + ); + return !notificationTopic + .isUserSubscribed; + }, + height: 30, + width: 60, + curveIn: Curves.easeInBack, + curveOut: Curves.easeOutBack, + animationDuration: const Duration( + milliseconds: 500, + ), + switchDecoration: (value, _) => + BoxDecoration( + color: value + ? Colors.red.withValues( + alpha: 0.3, + ) + : Colors.grey.shade200, + borderRadius: + BorderRadius.circular(30), + shape: BoxShape.rectangle, + boxShadow: [ + BoxShadow( + color: value + ? Colors.red.withValues( + alpha: 0.2, + ) + : Colors.grey.withValues( + alpha: 0.2, + ), + spreadRadius: 1, + blurRadius: 3, + offset: const Offset(0, 1), + ), + ], + ), + spinColor: (value) => + value ? Colors.red : Colors.grey, + spinStrokeWidth: 2, + thumbDecoration: (value, _) => + BoxDecoration( + color: Colors.white, + borderRadius: + BorderRadius.circular(30), + shape: BoxShape.rectangle, + boxShadow: [ + BoxShadow( + color: value + ? Colors.red.withValues( + alpha: 0.2, + ) + : Colors.grey.shade200 + .withValues( + alpha: 0.2, + ), + spreadRadius: 5, + blurRadius: 7, + offset: const Offset(0, 3), + ), + ], + ), + onChange: (v) {}, + onTap: (v) {}, ), ), - ], - ), - ), - ], - ], - ); - }, + ), + ], + ); + }, + ); + }, + ), ), - ), - context: context, - ref: ref, - ); - }, - ), - const SizedBox(height: 20), - const Text( - "Événement", - style: TextStyle( - fontSize: 24, - fontWeight: FontWeight.bold, - color: ColorConstants.title, + context: context, + ref: ref, + ); + }, + ), + const SizedBox(height: 20), + const Text( + "Événement", + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: ColorConstants.title, + ), ), - ), - ListItem( - title: "Lien ical", - subtitle: "Synchroniser avec votre calendrier", - onTap: () { - Clipboard.setData( - ClipboardData(text: "${Repository.host}calendar/ical"), - ).then((value) { - displayToastWithContext( - TypeMsg.msg, - "Lien ical copié dans le presse-papiers", + ListItemTemplate( + title: "Lien ical", + subtitle: "Synchroniser avec votre calendrier", + trailing: const HeroIcon( + HeroIcons.clipboardDocumentList, + color: ColorConstants.tertiary, + ), + onTap: () { + Clipboard.setData( + ClipboardData(text: "${Repository.host}calendar/ical"), + ).then((value) { + displayToastWithContext( + TypeMsg.msg, + "Lien ical copié dans le presse-papiers", + ); + }); + }, + ), + const SizedBox(height: 20), + const Text( + "Profil", + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: ColorConstants.title, + ), + ), + ListItem( + title: "Mot de passe", + subtitle: "Changer mon mot de passe", + onTap: () async { + await showCustomBottomModal( + modal: BottomModalTemplate( + title: 'Mot de passe', + child: ChangePassPage(), + ), + context: context, + ref: ref, ); - }); - }, - ), - const SizedBox(height: 20), - const Text( - "Profil", - style: TextStyle( - fontSize: 24, - fontWeight: FontWeight.bold, - color: ColorConstants.title, + }, ), - ), - ListItem( - title: "Mot de passe", - subtitle: "Changer mon mot de passe", - onTap: () async { - await showCustomBottomModal( - modal: BottomModalTemplate( - title: 'Mot de passe', - child: ChangePassPage(), - ), - context: context, - ref: ref, - ); - }, - ), - ], + ], + ), ), ), ); From 8536fc93bdb7dea9b152bb58caf223316a166326 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 19 Jul 2025 19:43:18 +0200 Subject: [PATCH 099/473] Fix switching language --- lib/settings/ui/pages/main_page/main_page.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/settings/ui/pages/main_page/main_page.dart b/lib/settings/ui/pages/main_page/main_page.dart index d4dbf393fb..7427298598 100644 --- a/lib/settings/ui/pages/main_page/main_page.dart +++ b/lib/settings/ui/pages/main_page/main_page.dart @@ -63,6 +63,7 @@ class SettingsMainPage extends HookConsumerWidget { ListItemTemplate( title: "🇫🇷 Français", onTap: () async { + Navigator.of(context).pop(); await ref .read(localeProvider.notifier) .setLocale(const Locale('fr')); @@ -78,6 +79,7 @@ class SettingsMainPage extends HookConsumerWidget { ListItemTemplate( title: "🇬🇧 English", onTap: () async { + Navigator.of(context).pop(); await ref .read(localeProvider.notifier) .setLocale(const Locale('en')); From 2f2072d8557219175ab6b9a38d7ec718e23152c8 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 19 Jul 2025 20:02:49 +0200 Subject: [PATCH 100/473] Notification topic part 2 --- lib/settings/ui/pages/main_page/main_page.dart | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/settings/ui/pages/main_page/main_page.dart b/lib/settings/ui/pages/main_page/main_page.dart index 7427298598..bbf51336df 100644 --- a/lib/settings/ui/pages/main_page/main_page.dart +++ b/lib/settings/ui/pages/main_page/main_page.dart @@ -29,6 +29,19 @@ class SettingsMainPage extends HookConsumerWidget { final notificationTopicListNotifier = ref.watch( notificationTopicListProvider.notifier, ); + final notificationTopicList = ref.watch(notificationTopicListProvider); + + final notificationAcivatedCounts = notificationTopicList.when( + data: (data) => data.where((topic) => topic.isUserSubscribed).length, + loading: () => 0, + error: (_, _) => 0, + ); + + final notificationTopicsLength = notificationTopicList.when( + data: (data) => data.length, + loading: () => 0, + error: (_, _) => 0, + ); return SettingsTemplate( child: Refresher( @@ -103,7 +116,8 @@ class SettingsMainPage extends HookConsumerWidget { ), ListItem( title: "Notifications", - subtitle: "2/3 activées", + subtitle: + "$notificationAcivatedCounts/$notificationTopicsLength activées", onTap: () async { await showCustomBottomModal( modal: BottomModalTemplate( From 83e5df477c5c6f48957257ddfe169556671de26f Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 19 Jul 2025 21:11:39 +0200 Subject: [PATCH 101/473] Notification topic part 3 --- lib/settings/tools/functions.dart | 27 ++++ .../ui/pages/main_page/load_switch_topic.dart | 65 +++++++++ .../ui/pages/main_page/main_page.dart | 129 ++++++++---------- 3 files changed, 149 insertions(+), 72 deletions(-) create mode 100644 lib/settings/tools/functions.dart create mode 100644 lib/settings/ui/pages/main_page/load_switch_topic.dart diff --git a/lib/settings/tools/functions.dart b/lib/settings/tools/functions.dart new file mode 100644 index 0000000000..6becd7e7e5 --- /dev/null +++ b/lib/settings/tools/functions.dart @@ -0,0 +1,27 @@ +import 'package:titan/settings/class/notification_topic.dart'; + +Map> groupNotificationTopicsByModuleRoot( + List topics, +) { + final Map> tempGroups = {}; + final Map> result = {}; + + for (final topic in topics) { + tempGroups.putIfAbsent(topic.moduleRoot, () => []).add(topic); + } + + final List singleTopics = []; + tempGroups.forEach((key, value) { + if (value.length == 1) { + singleTopics.addAll(value); + } else { + result[key] = value; + } + }); + + if (singleTopics.isNotEmpty) { + result[''] = singleTopics; + } + + return result; +} diff --git a/lib/settings/ui/pages/main_page/load_switch_topic.dart b/lib/settings/ui/pages/main_page/load_switch_topic.dart new file mode 100644 index 0000000000..b8acc586ad --- /dev/null +++ b/lib/settings/ui/pages/main_page/load_switch_topic.dart @@ -0,0 +1,65 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:load_switch/load_switch.dart'; +import 'package:titan/settings/class/notification_topic.dart'; +import 'package:titan/settings/providers/notification_topic_provider.dart'; + +class LoadSwitchTopic extends ConsumerWidget { + const LoadSwitchTopic({super.key, required this.notificationTopic}); + final NotificationTopic notificationTopic; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final notificationTopicListNotifier = ref.watch( + notificationTopicListProvider.notifier, + ); + return LoadSwitch( + value: notificationTopic.isUserSubscribed, + future: () async { + await notificationTopicListNotifier.toggleSubscription( + notificationTopic, + ); + return !notificationTopic.isUserSubscribed; + }, + height: 30, + width: 60, + curveIn: Curves.easeInBack, + curveOut: Curves.easeOutBack, + animationDuration: const Duration(milliseconds: 500), + switchDecoration: (value, _) => BoxDecoration( + color: value ? Colors.red.withValues(alpha: 0.3) : Colors.grey.shade200, + borderRadius: BorderRadius.circular(30), + shape: BoxShape.rectangle, + boxShadow: [ + BoxShadow( + color: value + ? Colors.red.withValues(alpha: 0.2) + : Colors.grey.withValues(alpha: 0.2), + spreadRadius: 1, + blurRadius: 3, + offset: const Offset(0, 1), + ), + ], + ), + spinColor: (value) => value ? Colors.red : Colors.grey, + spinStrokeWidth: 2, + thumbDecoration: (value, _) => BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(30), + shape: BoxShape.rectangle, + boxShadow: [ + BoxShadow( + color: value + ? Colors.red.withValues(alpha: 0.2) + : Colors.grey.shade200.withValues(alpha: 0.2), + spreadRadius: 5, + blurRadius: 7, + offset: const Offset(0, 3), + ), + ], + ), + onChange: (v) {}, + onTap: (v) {}, + ); + } +} diff --git a/lib/settings/ui/pages/main_page/main_page.dart b/lib/settings/ui/pages/main_page/main_page.dart index bbf51336df..1945688c03 100644 --- a/lib/settings/ui/pages/main_page/main_page.dart +++ b/lib/settings/ui/pages/main_page/main_page.dart @@ -2,9 +2,10 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:load_switch/load_switch.dart'; import 'package:titan/settings/providers/notification_topic_provider.dart'; +import 'package:titan/settings/tools/functions.dart'; import 'package:titan/settings/ui/pages/main_page/change_pass.dart'; +import 'package:titan/settings/ui/pages/main_page/load_switch_topic.dart'; import 'package:titan/settings/ui/settings.dart'; import 'package:titan/tools/constants.dart'; @@ -130,84 +131,68 @@ class SettingsMainPage extends HookConsumerWidget { return AsyncChild( value: notificationTopicList, builder: (context, notificationTopicList) { + final notificationTopicsByModuleRoot = + groupNotificationTopicsByModuleRoot( + notificationTopicList, + ); + final uniqueTopics = + notificationTopicsByModuleRoot[''] ?? []; + final groupedTopics = Map.from( + notificationTopicsByModuleRoot, + )..remove(''); return Column( children: [ - ...notificationTopicList.map( + ...uniqueTopics.map( (notificationTopic) => ListItemTemplate( title: notificationTopic.name, - trailing: LoadSwitch( - value: - notificationTopic.isUserSubscribed, - future: () async { - await notificationTopicListNotifier - .toggleSubscription( - notificationTopic, - ); - return !notificationTopic - .isUserSubscribed; - }, - height: 30, - width: 60, - curveIn: Curves.easeInBack, - curveOut: Curves.easeOutBack, - animationDuration: const Duration( - milliseconds: 500, - ), - switchDecoration: (value, _) => - BoxDecoration( - color: value - ? Colors.red.withValues( - alpha: 0.3, - ) - : Colors.grey.shade200, - borderRadius: - BorderRadius.circular(30), - shape: BoxShape.rectangle, - boxShadow: [ - BoxShadow( - color: value - ? Colors.red.withValues( - alpha: 0.2, - ) - : Colors.grey.withValues( - alpha: 0.2, - ), - spreadRadius: 1, - blurRadius: 3, - offset: const Offset(0, 1), - ), - ], - ), - spinColor: (value) => - value ? Colors.red : Colors.grey, - spinStrokeWidth: 2, - thumbDecoration: (value, _) => - BoxDecoration( - color: Colors.white, - borderRadius: - BorderRadius.circular(30), - shape: BoxShape.rectangle, - boxShadow: [ - BoxShadow( - color: value - ? Colors.red.withValues( - alpha: 0.2, - ) - : Colors.grey.shade200 - .withValues( - alpha: 0.2, - ), - spreadRadius: 5, - blurRadius: 7, - offset: const Offset(0, 3), - ), - ], - ), - onChange: (v) {}, - onTap: (v) {}, + trailing: LoadSwitchTopic( + notificationTopic: notificationTopic, ), ), ), + ...groupedTopics.entries.map((entry) { + final moduleRoot = entry.key; + final topics = entry.value; + bool expanded = false; + return StatefulBuilder( + builder: (context, setState) { + return Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + const SizedBox(height: 20), + ListItemTemplate( + title: moduleRoot, + trailing: HeroIcon( + expanded + ? HeroIcons.chevronDown + : HeroIcons.chevronRight, + color: ColorConstants.tertiary, + ), + onTap: () { + setState(() { + expanded = !expanded; + }); + }, + ), + const SizedBox(height: 10), + if (expanded) + ...topics.map( + ( + notificationTopic, + ) => ListItemTemplate( + title: notificationTopic.name, + trailing: LoadSwitchTopic( + notificationTopic: + notificationTopic, + ), + ), + ), + ], + ); + }, + ); + }), ], ); }, From d1b439b00fbc122ad5a908ad63d289f0e6399425 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 19 Jul 2025 21:22:39 +0200 Subject: [PATCH 102/473] Notification topic final part --- lib/settings/tools/functions.dart | 24 ++++++++++++++----- .../ui/pages/main_page/main_page.dart | 4 +++- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/lib/settings/tools/functions.dart b/lib/settings/tools/functions.dart index 6becd7e7e5..eebaa336d1 100644 --- a/lib/settings/tools/functions.dart +++ b/lib/settings/tools/functions.dart @@ -1,26 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/settings/class/notification_topic.dart'; +import 'package:titan/settings/providers/module_list_provider.dart'; Map> groupNotificationTopicsByModuleRoot( List topics, + WidgetRef ref, + BuildContext context, ) { final Map> tempGroups = {}; final Map> result = {}; - + final allModules = ref.read(modulesProvider.notifier).allModules; for (final topic in topics) { tempGroups.putIfAbsent(topic.moduleRoot, () => []).add(topic); } + final Map rootToName = { + for (final module in allModules) + module.root.replaceFirst('/', ''): module.getName(context), + }; + final List singleTopics = []; - tempGroups.forEach((key, value) { - if (value.length == 1) { - singleTopics.addAll(value); + + tempGroups.forEach((moduleRoot, topicList) { + if (topicList.length == 1) { + singleTopics.addAll(topicList); } else { - result[key] = value; + final moduleName = rootToName[moduleRoot] ?? moduleRoot; + result[moduleName] = topicList; } }); if (singleTopics.isNotEmpty) { - result[''] = singleTopics; + result[""] = singleTopics; } return result; diff --git a/lib/settings/ui/pages/main_page/main_page.dart b/lib/settings/ui/pages/main_page/main_page.dart index 1945688c03..fd742f299d 100644 --- a/lib/settings/ui/pages/main_page/main_page.dart +++ b/lib/settings/ui/pages/main_page/main_page.dart @@ -134,6 +134,8 @@ class SettingsMainPage extends HookConsumerWidget { final notificationTopicsByModuleRoot = groupNotificationTopicsByModuleRoot( notificationTopicList, + ref, + context, ); final uniqueTopics = notificationTopicsByModuleRoot[''] ?? []; @@ -153,7 +155,7 @@ class SettingsMainPage extends HookConsumerWidget { ...groupedTopics.entries.map((entry) { final moduleRoot = entry.key; final topics = entry.value; - bool expanded = false; + bool expanded = true; return StatefulBuilder( builder: (context, setState) { return Column( From 123b4df80954b430dfdcba8c17bd25c53807e4b5 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sun, 20 Jul 2025 17:15:20 +0200 Subject: [PATCH 103/473] Edit page --- .../ui/pages/main_page/edit_profile.dart | 155 ++++++++++++++++++ .../ui/pages/main_page/main_page.dart | 88 +++++++--- .../ui/pages/main_page/picture_button.dart | 34 ++++ 3 files changed, 250 insertions(+), 27 deletions(-) create mode 100644 lib/settings/ui/pages/main_page/edit_profile.dart create mode 100644 lib/settings/ui/pages/main_page/picture_button.dart diff --git a/lib/settings/ui/pages/main_page/edit_profile.dart b/lib/settings/ui/pages/main_page/edit_profile.dart new file mode 100644 index 0000000000..69486acee4 --- /dev/null +++ b/lib/settings/ui/pages/main_page/edit_profile.dart @@ -0,0 +1,155 @@ +import 'package:flutter/material.dart'; +import 'package:heroicons/heroicons.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:titan/l10n/app_localizations.dart'; +import 'package:titan/settings/ui/pages/main_page/picture_button.dart'; +import 'package:titan/tools/functions.dart'; +import 'package:titan/tools/ui/builders/async_child.dart'; +import 'package:titan/user/providers/profile_picture_provider.dart'; + +class EditProfile extends ConsumerWidget { + const EditProfile({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final profilePictureNotifier = ref.watch(profilePictureProvider.notifier); + final profilePicture = ref.watch(profilePictureProvider); + void displayToastWithContext(TypeMsg type, String msg) { + displayToast(context, type, msg); + } + + return Column( + children: [ + AsyncChild( + value: profilePicture, + builder: (context, profile) { + return Center( + child: Stack( + clipBehavior: Clip.none, + children: [ + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.1), + spreadRadius: 5, + blurRadius: 10, + offset: const Offset(2, 3), + ), + ], + ), + child: CircleAvatar( + radius: 80, + backgroundImage: profile.isEmpty + ? const AssetImage('assets/images/profile.png') + : Image.memory(profile).image, + ), + ), + Positioned( + bottom: 0, + left: 0, + child: GestureDetector( + onTap: () async { + final value = await profilePictureNotifier + .setProfilePicture(ImageSource.gallery); + if (value != null) { + if (value) { + displayToastWithContext( + TypeMsg.msg, + AppLocalizations.of( + context, + )!.settingsUpdatedProfilePicture, + ); + } else { + displayToastWithContext( + TypeMsg.error, + AppLocalizations.of( + context, + )!.settingsTooHeavyProfilePicture, + ); + } + } else { + displayToastWithContext( + TypeMsg.error, + AppLocalizations.of( + context, + )!.settingsErrorProfilePicture, + ); + } + }, + child: const PictureButton(icon: HeroIcons.photo), + ), + ), + Positioned( + bottom: 0, + right: 0, + child: GestureDetector( + onTap: () async { + final value = await profilePictureNotifier + .setProfilePicture(ImageSource.camera); + if (value != null) { + if (value) { + displayToastWithContext( + TypeMsg.msg, + AppLocalizations.of( + context, + )!.settingsUpdatedProfilePicture, + ); + } else { + displayToastWithContext( + TypeMsg.error, + AppLocalizations.of( + context, + )!.settingsTooHeavyProfilePicture, + ); + } + } else { + displayToastWithContext( + TypeMsg.error, + AppLocalizations.of( + context, + )!.settingsErrorProfilePicture, + ); + } + }, + child: const PictureButton(icon: HeroIcons.camera), + ), + ), + Positioned( + bottom: -20, + right: 60, + child: GestureDetector( + onTap: () async { + final value = await profilePictureNotifier.cropImage(); + if (value != null) { + if (value) { + displayToastWithContext( + TypeMsg.msg, + AppLocalizations.of( + context, + )!.settingsUpdatedProfilePicture, + ); + } else { + displayToastWithContext( + TypeMsg.error, + AppLocalizations.of( + context, + )!.settingsErrorProfilePicture, + ); + } + } + }, + child: const PictureButton(icon: HeroIcons.sparkles), + ), + ), + ], + ), + ); + }, + ), + ], + ); + } +} diff --git a/lib/settings/ui/pages/main_page/main_page.dart b/lib/settings/ui/pages/main_page/main_page.dart index fd742f299d..c91b98fe63 100644 --- a/lib/settings/ui/pages/main_page/main_page.dart +++ b/lib/settings/ui/pages/main_page/main_page.dart @@ -4,7 +4,7 @@ import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/settings/providers/notification_topic_provider.dart'; import 'package:titan/settings/tools/functions.dart'; -import 'package:titan/settings/ui/pages/main_page/change_pass.dart'; +import 'package:titan/settings/ui/pages/main_page/edit_profile.dart'; import 'package:titan/settings/ui/pages/main_page/load_switch_topic.dart'; import 'package:titan/settings/ui/settings.dart'; @@ -17,6 +17,8 @@ import 'package:titan/tools/ui/layouts/refresher.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; import 'package:titan/tools/ui/styleguide/list_item.dart'; import 'package:titan/tools/ui/styleguide/list_item_template.dart'; +import 'package:titan/user/providers/profile_picture_provider.dart'; +import 'package:titan/user/providers/user_provider.dart'; class SettingsMainPage extends HookConsumerWidget { const SettingsMainPage({super.key}); @@ -31,6 +33,8 @@ class SettingsMainPage extends HookConsumerWidget { notificationTopicListProvider.notifier, ); final notificationTopicList = ref.watch(notificationTopicListProvider); + final meNotifier = ref.watch(asyncUserProvider.notifier); + final profilePicture = ref.watch(profilePictureProvider); final notificationAcivatedCounts = notificationTopicList.when( data: (data) => data.where((topic) => topic.isUserSubscribed).length, @@ -44,29 +48,81 @@ class SettingsMainPage extends HookConsumerWidget { error: (_, _) => 0, ); + final selectedLanguage = ref.watch(localeProvider)?.languageCode == 'fr' + ? "Français" + : "English"; + return SettingsTemplate( child: Refresher( onRefresh: () async { await notificationTopicListNotifier.loadNotificationTopicList(); + await meNotifier.loadMe(); }, child: Container( padding: const EdgeInsets.symmetric(horizontal: 20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + const SizedBox(height: 20), + AsyncChild( + value: profilePicture, + builder: (context, profile) { + return Center( + child: Stack( + clipBehavior: Clip.none, + children: [ + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.3), + spreadRadius: 6, + blurRadius: 10, + offset: const Offset(0, 2), + ), + ], + ), + child: CircleAvatar( + radius: 70, + backgroundImage: profile.isEmpty + ? AssetImage(getTitanLogo()) + : Image.memory(profile).image, + ), + ), + ], + ), + ); + }, + errorBuilder: (e, s) => + const HeroIcon(HeroIcons.userCircle, size: 140), + ), const SizedBox(height: 20), const Text( - "Paramètres", + "Compte", style: TextStyle( fontSize: 24, fontWeight: FontWeight.bold, color: ColorConstants.title, ), ), - const SizedBox(height: 20), + ListItem( + title: "Profil", + subtitle: "Modifier mon profil", + onTap: () async { + await showCustomBottomModal( + modal: BottomModalTemplate( + title: 'Modifier mon profil', + child: EditProfile(), + ), + context: context, + ref: ref, + ); + }, + ), ListItem( title: "Langue", - subtitle: ref.watch(localeProvider)?.languageCode, + subtitle: selectedLanguage, onTap: () async { await showCustomBottomModal( modal: BottomModalTemplate( @@ -209,7 +265,7 @@ class SettingsMainPage extends HookConsumerWidget { ), const SizedBox(height: 20), const Text( - "Événement", + "Événements", style: TextStyle( fontSize: 24, fontWeight: FontWeight.bold, @@ -235,28 +291,6 @@ class SettingsMainPage extends HookConsumerWidget { }, ), const SizedBox(height: 20), - const Text( - "Profil", - style: TextStyle( - fontSize: 24, - fontWeight: FontWeight.bold, - color: ColorConstants.title, - ), - ), - ListItem( - title: "Mot de passe", - subtitle: "Changer mon mot de passe", - onTap: () async { - await showCustomBottomModal( - modal: BottomModalTemplate( - title: 'Mot de passe', - child: ChangePassPage(), - ), - context: context, - ref: ref, - ); - }, - ), ], ), ), diff --git a/lib/settings/ui/pages/main_page/picture_button.dart b/lib/settings/ui/pages/main_page/picture_button.dart new file mode 100644 index 0000000000..905238fb15 --- /dev/null +++ b/lib/settings/ui/pages/main_page/picture_button.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; +import 'package:heroicons/heroicons.dart'; +import 'package:titan/tools/constants.dart'; + +class PictureButton extends StatelessWidget { + final HeroIcons icon; + const PictureButton({super.key, required this.icon}); + + @override + Widget build(BuildContext context) { + return Container( + height: 40, + width: 40, + padding: const EdgeInsets.all(7), + decoration: BoxDecoration( + shape: BoxShape.circle, + gradient: const LinearGradient( + colors: [ColorConstants.gradient1, ColorConstants.gradient2], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + boxShadow: [ + BoxShadow( + color: ColorConstants.gradient2.withValues(alpha: 0.3), + spreadRadius: 2, + blurRadius: 4, + offset: const Offset(2, 3), + ), + ], + ), + child: HeroIcon(icon, color: Colors.white), + ); + } +} From 51e71db702c0f9230139dda9a7e4c5e191493d57 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Tue, 5 Aug 2025 21:43:26 +0200 Subject: [PATCH 104/473] rebase --- lib/settings/router.dart | 6 +++++- lib/settings/ui/settings.dart | 20 +++++++++++--------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/lib/settings/router.dart b/lib/settings/router.dart index 6382fe72a4..353f4be601 100644 --- a/lib/settings/router.dart +++ b/lib/settings/router.dart @@ -1,6 +1,6 @@ +import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/navigation/class/module.dart'; @@ -30,5 +30,9 @@ class SettingsRouter { AuthenticatedMiddleware(ref), DeferredLoadingMiddleware(main_page.loadLibrary), ], + pageType: QCustomPage( + transitionsBuilder: (_, animation, _, child) => + FadeTransition(opacity: animation, child: child), + ), ); } diff --git a/lib/settings/ui/settings.dart b/lib/settings/ui/settings.dart index 783c27b631..9f0ee0c89e 100644 --- a/lib/settings/ui/settings.dart +++ b/lib/settings/ui/settings.dart @@ -9,15 +9,17 @@ class SettingsTemplate extends StatelessWidget { @override Widget build(BuildContext context) { - return Container( - color: ColorConstants.background, - child: SafeArea( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - const TopBar(root: SettingsRouter.root), - Expanded(child: child), - ], + return Scaffold( + body: Container( + decoration: const BoxDecoration(color: ColorConstants.background), + child: SafeArea( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + const TopBar(root: SettingsRouter.root), + Expanded(child: child), + ], + ), ), ), ); From df35baab2f0c5aa968da4d673d40a57c54ffa571 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 9 Aug 2025 15:50:36 +0200 Subject: [PATCH 105/473] edit user --- .../providers/edit_user_provider.dart | 40 ++ .../ui/pages/main_page/edit_profile.dart | 348 +++++++++++------- lib/tools/ui/styleguide/text_entry.dart | 2 +- 3 files changed, 263 insertions(+), 127 deletions(-) create mode 100644 lib/settings/providers/edit_user_provider.dart diff --git a/lib/settings/providers/edit_user_provider.dart b/lib/settings/providers/edit_user_provider.dart new file mode 100644 index 0000000000..f85214baf9 --- /dev/null +++ b/lib/settings/providers/edit_user_provider.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/tools/functions.dart'; +import 'package:titan/user/providers/user_provider.dart'; + +class TextControllersNotifier + extends StateNotifier> { + final List list; + TextControllersNotifier(this.list) : super([]) { + state = list; + } + + void disposeAll() { + for (final controller in state) { + controller.dispose(); + } + state = []; + } + + void updateText(int index, String text) { + if (index < 0 || index >= state.length) return; + state[index].text = text; + state = [...state]; + } +} + +final textControllersProvider = + StateNotifierProvider>( + (ref) { + final me = ref.watch(userProvider); + final list = [ + TextEditingController(text: me.email), + TextEditingController(text: me.phone ?? ""), + TextEditingController( + text: me.birthday != null ? processDate(me.birthday!) : "", + ), + ]; + return TextControllersNotifier(list); + }, + ); diff --git a/lib/settings/ui/pages/main_page/edit_profile.dart b/lib/settings/ui/pages/main_page/edit_profile.dart index 69486acee4..7ce5d19c11 100644 --- a/lib/settings/ui/pages/main_page/edit_profile.dart +++ b/lib/settings/ui/pages/main_page/edit_profile.dart @@ -3,153 +3,249 @@ import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:image_picker/image_picker.dart'; import 'package:titan/l10n/app_localizations.dart'; +import 'package:titan/settings/providers/edit_user_provider.dart'; import 'package:titan/settings/ui/pages/main_page/picture_button.dart'; import 'package:titan/tools/functions.dart'; +import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; +import 'package:titan/tools/ui/styleguide/button.dart'; +import 'package:titan/tools/ui/styleguide/icon_button.dart'; +import 'package:titan/tools/ui/styleguide/text_entry.dart'; import 'package:titan/user/providers/profile_picture_provider.dart'; +import 'package:titan/user/providers/user_provider.dart'; class EditProfile extends ConsumerWidget { const EditProfile({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { + final me = ref.watch(userProvider); final profilePictureNotifier = ref.watch(profilePictureProvider.notifier); final profilePicture = ref.watch(profilePictureProvider); + final asyncUserNotifier = ref.watch(asyncUserProvider.notifier); void displayToastWithContext(TypeMsg type, String msg) { displayToast(context, type, msg); } - return Column( - children: [ - AsyncChild( - value: profilePicture, - builder: (context, profile) { - return Center( - child: Stack( - clipBehavior: Clip.none, - children: [ - Container( - decoration: BoxDecoration( - shape: BoxShape.circle, - boxShadow: [ - BoxShadow( - color: Colors.black.withValues(alpha: 0.1), - spreadRadius: 5, - blurRadius: 10, - offset: const Offset(2, 3), + final textControllers = ref.watch(textControllersProvider); + final textControllersNotifier = ref.watch(textControllersProvider.notifier); + + MediaQuery.of(context).viewInsets.bottom; + + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 40), + child: SingleChildScrollView( + child: Column( + children: [ + if (View.of(context).viewInsets.bottom == 0) + AsyncChild( + value: profilePicture, + builder: (context, profile) { + return Center( + child: Stack( + clipBehavior: Clip.none, + children: [ + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.1), + spreadRadius: 5, + blurRadius: 10, + offset: const Offset(2, 3), + ), + ], + ), + child: CircleAvatar( + radius: 80, + backgroundImage: profile.isEmpty + ? const AssetImage('assets/images/profile.png') + : Image.memory(profile).image, + ), + ), + Positioned( + bottom: 0, + left: 0, + child: GestureDetector( + onTap: () async { + final value = await profilePictureNotifier + .setProfilePicture(ImageSource.gallery); + if (value != null) { + if (value) { + displayToastWithContext( + TypeMsg.msg, + AppLocalizations.of( + context, + )!.settingsUpdatedProfilePicture, + ); + } else { + displayToastWithContext( + TypeMsg.error, + AppLocalizations.of( + context, + )!.settingsTooHeavyProfilePicture, + ); + } + } else { + displayToastWithContext( + TypeMsg.error, + AppLocalizations.of( + context, + )!.settingsErrorProfilePicture, + ); + } + }, + child: const PictureButton(icon: HeroIcons.photo), + ), + ), + Positioned( + bottom: 0, + right: 0, + child: GestureDetector( + onTap: () async { + final value = await profilePictureNotifier + .setProfilePicture(ImageSource.camera); + if (value != null) { + if (value) { + displayToastWithContext( + TypeMsg.msg, + AppLocalizations.of( + context, + )!.settingsUpdatedProfilePicture, + ); + } else { + displayToastWithContext( + TypeMsg.error, + AppLocalizations.of( + context, + )!.settingsTooHeavyProfilePicture, + ); + } + } else { + displayToastWithContext( + TypeMsg.error, + AppLocalizations.of( + context, + )!.settingsErrorProfilePicture, + ); + } + }, + child: const PictureButton(icon: HeroIcons.camera), + ), + ), + Positioned( + bottom: -20, + right: 60, + child: GestureDetector( + onTap: () async { + final value = await profilePictureNotifier + .cropImage(); + if (value != null) { + if (value) { + displayToastWithContext( + TypeMsg.msg, + AppLocalizations.of( + context, + )!.settingsUpdatedProfilePicture, + ); + } else { + displayToastWithContext( + TypeMsg.error, + AppLocalizations.of( + context, + )!.settingsErrorProfilePicture, + ); + } + } + }, + child: const PictureButton( + icon: HeroIcons.sparkles, + ), + ), ), ], ), - child: CircleAvatar( - radius: 80, - backgroundImage: profile.isEmpty - ? const AssetImage('assets/images/profile.png') - : Image.memory(profile).image, - ), - ), - Positioned( - bottom: 0, - left: 0, - child: GestureDetector( - onTap: () async { - final value = await profilePictureNotifier - .setProfilePicture(ImageSource.gallery); - if (value != null) { - if (value) { - displayToastWithContext( - TypeMsg.msg, - AppLocalizations.of( - context, - )!.settingsUpdatedProfilePicture, - ); - } else { - displayToastWithContext( - TypeMsg.error, - AppLocalizations.of( - context, - )!.settingsTooHeavyProfilePicture, - ); - } - } else { - displayToastWithContext( - TypeMsg.error, - AppLocalizations.of( - context, - )!.settingsErrorProfilePicture, - ); - } - }, - child: const PictureButton(icon: HeroIcons.photo), - ), - ), - Positioned( - bottom: 0, - right: 0, - child: GestureDetector( - onTap: () async { - final value = await profilePictureNotifier - .setProfilePicture(ImageSource.camera); - if (value != null) { - if (value) { - displayToastWithContext( - TypeMsg.msg, - AppLocalizations.of( - context, - )!.settingsUpdatedProfilePicture, - ); - } else { - displayToastWithContext( - TypeMsg.error, - AppLocalizations.of( - context, - )!.settingsTooHeavyProfilePicture, - ); - } - } else { - displayToastWithContext( - TypeMsg.error, - AppLocalizations.of( - context, - )!.settingsErrorProfilePicture, - ); - } - }, - child: const PictureButton(icon: HeroIcons.camera), - ), - ), - Positioned( - bottom: -20, - right: 60, - child: GestureDetector( - onTap: () async { - final value = await profilePictureNotifier.cropImage(); - if (value != null) { - if (value) { - displayToastWithContext( - TypeMsg.msg, - AppLocalizations.of( - context, - )!.settingsUpdatedProfilePicture, - ); - } else { - displayToastWithContext( - TypeMsg.error, - AppLocalizations.of( - context, - )!.settingsErrorProfilePicture, - ); - } - } - }, - child: const PictureButton(icon: HeroIcons.sparkles), - ), - ), - ], + ); + }, ), - ); - }, + SizedBox(height: View.of(context).viewInsets.bottom == 0 ? 30 : 10), + TextEntry( + label: "Email", + controller: textControllers[0], + enabled: false, + ), + SizedBox(height: 20), + TextEntry( + label: "Numéro de téléphone", + controller: textControllers[1], + textInputAction: TextInputAction.done, + onChanged: (value) { + textControllersNotifier.updateText(1, value); + }, + ), + SizedBox(height: 20), + Row( + children: [ + Expanded( + child: TextEntry( + label: "Date de naissance", + controller: textControllers[2], + enabled: false, + ), + ), + CustomIconButton( + icon: const Icon(Icons.calendar_today, color: Colors.white), + onPressed: () async { + final date = await showDatePicker( + context: context, + initialDate: DateTime(2004), + firstDate: DateTime(1900), + lastDate: DateTime.now(), + ); + if (date != null) { + final formattedDate = + "${date.day.toString().padLeft(2, '0')}/${date.month.toString().padLeft(2, '0')}/${date.year}"; + textControllersNotifier.updateText(2, formattedDate); + } + }, + ), + ], + ), + SizedBox(height: 30), + Button( + text: "Valider", + disabled: + !(textControllers[1].value.text != me.phone || + textControllers[2].value.text.isNotEmpty), + onPressed: () async { + if (textControllers[1].value.text != me.phone || + textControllers[2].value.text.isNotEmpty) { + await tokenExpireWrapper(ref, () async { + final newMe = me.copyWith( + birthday: textControllers[2].value.text.isNotEmpty + ? DateTime.parse( + processDateBack(textControllers[2].value.text), + ) + : null, + phone: textControllers[1].value.text.isEmpty + ? null + : textControllers[1].value.text, + ); + final value = await asyncUserNotifier.updateMe(newMe); + if (value) { + displayToastWithContext(TypeMsg.msg, "Succès"); + Navigator.of(context).pop(); + } else { + displayToastWithContext(TypeMsg.error, "Échec"); + } + }); + } + }, + ), + ], ), - ], + ), ); } } diff --git a/lib/tools/ui/styleguide/text_entry.dart b/lib/tools/ui/styleguide/text_entry.dart index 8d4d16a12c..dd0b1c02c4 100644 --- a/lib/tools/ui/styleguide/text_entry.dart +++ b/lib/tools/ui/styleguide/text_entry.dart @@ -53,7 +53,7 @@ class TextEntry extends StatelessWidget { obscureText: obscureText, textInputAction: (keyboardType == TextInputType.multiline) ? TextInputAction.newline - : TextInputAction.next, + : textInputAction, enabled: enabled, decoration: InputDecoration( label: Text( From dd6c9c6650e46917fdf284bd15467fccdb70c947 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 9 Aug 2025 16:12:25 +0200 Subject: [PATCH 106/473] lint and format --- lib/main.dart | 6 ++---- lib/navigation/providers/navbar_visibility_provider.dart | 5 ----- lib/settings/router.dart | 1 - 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 416f6e0528..55416fb42b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -15,7 +15,6 @@ import 'package:titan/router.dart'; import 'package:titan/service/tools/setup.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/plausible/plausible_observer.dart'; -import 'package:titan/tools/providers/locale_notifier.dart'; import 'package:titan/tools/providers/path_forwarding_provider.dart'; import 'package:titan/tools/ui/layouts/app_template.dart'; import 'package:qlevar_router/qlevar_router.dart'; @@ -104,7 +103,7 @@ class MyApp extends HookConsumerWidget { GlobalCupertinoLocalizations.delegate, ], theme: ThemeData( - primarySwatch: Colors.orange, + primarySwatch: Colors.red, textTheme: GoogleFonts.latoTextTheme(Theme.of(context).textTheme), brightness: Brightness.light, ), @@ -115,13 +114,12 @@ class MyApp extends HookConsumerWidget { } return AppTemplate(child: child); }, - routerDelegate: QRouterDelegate( + routerDelegate: QRouterDelegate( appRouter.routes, observers: [if (plausible != null) PlausibleObserver(plausible)], initPath: AppRouter.root, navKey: navigatorKey, ), - ); } } diff --git a/lib/navigation/providers/navbar_visibility_provider.dart b/lib/navigation/providers/navbar_visibility_provider.dart index c3a276bf11..ac5c4a675a 100644 --- a/lib/navigation/providers/navbar_visibility_provider.dart +++ b/lib/navigation/providers/navbar_visibility_provider.dart @@ -33,11 +33,6 @@ class NavbarVisibilityNotifier extends StateNotifier { forceShow(); } } - - @override - void dispose() { - super.dispose(); - } } final navbarVisibilityProvider = diff --git a/lib/settings/router.dart b/lib/settings/router.dart index 353f4be601..2c3f08784f 100644 --- a/lib/settings/router.dart +++ b/lib/settings/router.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter/foundation.dart'; import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/navigation/class/module.dart'; From 139821851b81856398bf0e5ffc5483e5b113e60c Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 9 Aug 2025 16:18:14 +0200 Subject: [PATCH 107/473] remove useless file --- .../ui/components}/password_strength.dart | 0 .../create_account_page.dart | 2 +- .../recover_password_page.dart | 2 +- .../ui/pages/main_page/change_pass.dart | 152 --------- .../ui/pages/main_page/secure_bar.dart | 312 ------------------ .../ui/pages/main_page/test_entry_style.dart | 35 -- 6 files changed, 2 insertions(+), 501 deletions(-) rename lib/{settings/ui/pages/main_page => login/ui/components}/password_strength.dart (100%) delete mode 100644 lib/settings/ui/pages/main_page/change_pass.dart delete mode 100644 lib/settings/ui/pages/main_page/secure_bar.dart delete mode 100644 lib/settings/ui/pages/main_page/test_entry_style.dart diff --git a/lib/settings/ui/pages/main_page/password_strength.dart b/lib/login/ui/components/password_strength.dart similarity index 100% rename from lib/settings/ui/pages/main_page/password_strength.dart rename to lib/login/ui/components/password_strength.dart diff --git a/lib/login/ui/pages/create_account_page/create_account_page.dart b/lib/login/ui/pages/create_account_page/create_account_page.dart index 4c6328502c..70b76569fb 100644 --- a/lib/login/ui/pages/create_account_page/create_account_page.dart +++ b/lib/login/ui/pages/create_account_page/create_account_page.dart @@ -10,7 +10,7 @@ import 'package:titan/login/router.dart'; import 'package:titan/login/ui/components/login_field.dart'; import 'package:titan/login/ui/auth_page.dart'; import 'package:titan/login/ui/components/sign_in_up_bar.dart'; -import 'package:titan/settings/ui/pages/main_page/password_strength.dart'; +import 'package:titan/login/ui/components/password_strength.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/ui/widgets/align_left_text.dart'; diff --git a/lib/login/ui/pages/recover_password/recover_password_page.dart b/lib/login/ui/pages/recover_password/recover_password_page.dart index 61a7b8d585..7c702f688c 100644 --- a/lib/login/ui/pages/recover_password/recover_password_page.dart +++ b/lib/login/ui/pages/recover_password/recover_password_page.dart @@ -10,7 +10,7 @@ import 'package:titan/login/router.dart'; import 'package:titan/login/ui/components/login_field.dart'; import 'package:titan/login/ui/auth_page.dart'; import 'package:titan/login/ui/components/sign_in_up_bar.dart'; -import 'package:titan/settings/ui/pages/main_page/password_strength.dart'; +import 'package:titan/login/ui/components/password_strength.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:qlevar_router/qlevar_router.dart'; diff --git a/lib/settings/ui/pages/main_page/change_pass.dart b/lib/settings/ui/pages/main_page/change_pass.dart deleted file mode 100644 index 145ba4f592..0000000000 --- a/lib/settings/ui/pages/main_page/change_pass.dart +++ /dev/null @@ -1,152 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/settings/ui/pages/main_page/password_strength.dart'; -import 'package:titan/tools/ui/styleguide/button.dart'; -import 'package:titan/tools/ui/styleguide/text_entry.dart'; -import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; -import 'package:titan/tools/functions.dart'; -import 'package:titan/tools/token_expire_wrapper.dart'; -import 'package:titan/user/providers/user_provider.dart'; -import 'package:qlevar_router/qlevar_router.dart'; -import 'package:titan/l10n/app_localizations.dart'; - -class ChangePassPage extends HookConsumerWidget { - const ChangePassPage({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final key = GlobalKey(); - final oldPassword = useTextEditingController(); - final newPassword = useTextEditingController(); - final confirmPassword = useTextEditingController(); - final hideOldPass = useState(true); - final hideNewPass = useState(true); - final hideConfirmPass = useState(true); - final userNotifier = ref.watch(asyncUserProvider.notifier); - final user = ref.watch(userProvider); - void displayToastWithContext(TypeMsg type, String msg) { - displayToast(context, type, msg); - } - - return SingleChildScrollView( - physics: const BouncingScrollPhysics(), - child: Form( - key: key, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 30.0), - child: Column( - children: [ - const SizedBox(height: 10), - TextEntry( - controller: oldPassword, - // obscureText: hideOldPass.value, - label: 'Ancien mot de passe', - maxLines: 1, - suffixIcon: IconButton( - icon: Icon( - hideOldPass.value ? Icons.visibility : Icons.visibility_off, - color: Colors.grey.shade600, - ), - onPressed: () { - hideOldPass.value = !hideOldPass.value; - }, - ), - ), - const SizedBox(height: 10), - TextEntry( - controller: newPassword, - obscureText: hideNewPass.value, - label: 'Nouveau mot de passe', - maxLines: 1, - suffixIcon: IconButton( - icon: Icon( - hideNewPass.value ? Icons.visibility : Icons.visibility_off, - color: Colors.grey.shade600, - ), - onPressed: () { - hideNewPass.value = !hideNewPass.value; - }, - ), - ), - const SizedBox(height: 10), - TextEntry( - controller: confirmPassword, - obscureText: hideConfirmPass.value, - label: 'Confirmer le mot de passe', - maxLines: 1, - suffixIcon: IconButton( - icon: Icon( - hideConfirmPass.value - ? Icons.visibility - : Icons.visibility_off, - color: Colors.grey.shade600, - ), - onPressed: () { - hideConfirmPass.value = !hideConfirmPass.value; - }, - ), - ), - const SizedBox(height: 20), - PasswordStrength(newPassword: newPassword), - const SizedBox(height: 20), - Button( - text: "Enregistrer", - onPressed: () async { - if (key.currentState!.validate()) { - await showDialog( - context: context, - builder: (context) => CustomDialogBox( - descriptions: AppLocalizations.of( - context, - )!.settingsChangingPassword, - onYes: () async { - final passwordChangedMsg = AppLocalizations.of( - context, - )!.settingsPasswordChanged; - final passwordChangeErrorMsg = AppLocalizations.of( - context, - )!.settingsUpdatingError; - await tokenExpireWrapper(ref, () async { - final value = await userNotifier.changePassword( - oldPassword.text, - newPassword.text, - user, - ); - if (value) { - QR.back(); - displayToastWithContext( - TypeMsg.msg, - passwordChangedMsg, - ); - } else { - displayToastWithContext( - TypeMsg.error, - passwordChangeErrorMsg, - ); - } - }); - }, - title: AppLocalizations.of(context)!.settingsEdit, - ), - ); - } - }, - ), - Center( - child: Text( - AppLocalizations.of(context)!.settingsSave, - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - color: Colors.white, - ), - ), - ), - ], - ), - ), - ), - ); - } -} diff --git a/lib/settings/ui/pages/main_page/secure_bar.dart b/lib/settings/ui/pages/main_page/secure_bar.dart deleted file mode 100644 index ab081792b9..0000000000 --- a/lib/settings/ui/pages/main_page/secure_bar.dart +++ /dev/null @@ -1,312 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:zxcvbn/zxcvbn.dart'; - -// Source : https://github.com/JinHoSo/flutter-password-strength/blob/master/lib/flutter_password_strength.dart - -class FlutterPasswordStrength extends StatefulWidget { - final String? password; - - //Strength bar width - final double? width; - - //Strength bar height - final double height; - - //Strength bar colors are changed depending on strength - final Animatable strengthColors; - - //Strength bar background color - final Color backgroundColor; - - //Strength bar radius - final double radius; - - //Strength bar animation duration - final Duration? duration; - - //Strength callback - final void Function(double strength)? strengthCallback; - - const FlutterPasswordStrength({ - super.key, - required this.password, - this.width, - this.height = 5, - required this.strengthColors, - this.backgroundColor = Colors.grey, - this.radius = 0, - this.duration, - this.strengthCallback, - }); - - /* - default strength bar colors - This is approximate values - 0.0 ~ 0.25 : red - 0.26 ~ 0.5 : yellow - 0.51 ~ 0.75 : blue - 0.76 ~ 1 : green - */ - Animatable get _strengthColors => strengthColors; - - //default duration is 300 milliseconds - Duration? get _duration => duration ?? const Duration(milliseconds: 300); - - @override - FlutterPasswordStrengthState createState() => FlutterPasswordStrengthState(); -} - -class FlutterPasswordStrengthState extends State - with SingleTickerProviderStateMixin { - //Animation controller for strength bar - late AnimationController _animationController; - - //Animation for strength bar sharp - late Animation _strengthBarAnimation; - - //Strength bar colors - late Animatable _strengthBarColors; - - //Strength bar color from the list of strength bar colors - late Color _strengthBarColor; - - //Strength bar color - late Color _backgroundColor; - - //Strength bar width - double? _width; - - //Strength bar height - late double _height; - - //Strength bar radius, default is 0 - double _radius = 0; - - //Strength callback - void Function(double strength)? _strengthCallback; - - //_begin is used in _strengthBarAnimation - double _begin = 0; - - //_end is used in _strengthBarAnimation - double _end = 0; - - //calculated strength from password - double _passwordStrength = 0; - - // zxcvbn password strength estimator - Zxcvbn zxcvbn = Zxcvbn(); - - @override - void initState() { - super.initState(); - - //initialize - _animationController = AnimationController( - duration: widget._duration, - vsync: this, - ); - _strengthBarAnimation = Tween( - begin: _begin, - end: _end, - ).animate(_animationController); - _strengthBarColors = widget._strengthColors; - _strengthBarColor = - _strengthBarColors.evaluate( - AlwaysStoppedAnimation(_passwordStrength), - ) ?? - Colors.transparent; - - _backgroundColor = widget.backgroundColor; - - _width = widget.width; - _height = widget.height; - _radius = widget.radius; - _strengthCallback = widget.strengthCallback; - - //start animation - _animationController.forward(); - } - - void animate() { - //calculate strength - if (widget.password == null || widget.password!.isEmpty) { - _passwordStrength = 0; - } else { - _passwordStrength = - (zxcvbn.evaluate(widget.password ?? "").score ?? 0) / 4; - } - - _begin = _end; - _end = _passwordStrength * 100; - - _strengthBarAnimation = Tween( - begin: _begin, - end: _end, - ).animate(_animationController); - _strengthBarColor = - _strengthBarColors.evaluate( - AlwaysStoppedAnimation(_passwordStrength), - ) ?? - Colors.transparent; - - _animationController.forward(from: 0.0); - - if (_strengthCallback != null) { - _strengthCallback!(_passwordStrength); - } - } - - @override - void dispose() { - super.dispose(); - _animationController.dispose(); - } - - @override - void didUpdateWidget(FlutterPasswordStrength oldWidget) { - super.didUpdateWidget(oldWidget); - - if (oldWidget.password != widget.password) { - animate(); - } - } - - @override - Widget build(BuildContext context) { - return StrengthBarContainer( - barColor: _strengthBarColor, - backgroundColor: _backgroundColor, - width: _width, - height: _height, - radius: _radius, - animation: _strengthBarAnimation, - ); - } -} - -class StrengthBarContainer extends AnimatedWidget { - final Color barColor; - final Color backgroundColor; - final double? width; - final double height; - final double radius; - - const StrengthBarContainer({ - super.key, - required this.barColor, - required this.backgroundColor, - this.width, - required this.height, - required this.radius, - required Animation animation, - }) : super(listenable: animation); - - Animation get _percent { - return listenable as Animation; - } - - @override - Widget build(BuildContext context) { - return LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - return CustomPaint( - size: Size(width ?? constraints.maxWidth, height), - painter: StrengthBarBackground( - backgroundColor: backgroundColor, - backgroundRadius: radius, - ), - foregroundPainter: StrengthBar( - barColor: barColor, - barRadius: radius, - percent: _percent.value, - ), - ); - }, - ); - } -} - -class StrengthBar extends CustomPainter { - Color barColor; - double barRadius; - double percent; - - StrengthBar({ - required this.barColor, - required this.barRadius, - required this.percent, - }); - - @override - void paint(Canvas canvas, Size size) { - drawBar(canvas, size); - } - - void drawBar(Canvas canvas, Size size) { - Paint paint = Paint() - ..color = barColor - ..style = PaintingStyle.fill - ..strokeCap = StrokeCap.round; - - double left = 0; - double top = 0; - double right = size.width / 100 * percent; - double bottom = size.height; - - //the bar width needs to be bigger than radius width - if (barRadius != 0 && right > 0 && barRadius * 2 > right) { - right = barRadius * 2; - } - - canvas.drawRRect( - RRect.fromRectAndRadius( - Rect.fromLTRB(left, top, right, bottom), - Radius.circular(barRadius), - ), - paint, - ); - } - - @override - bool shouldRepaint(StrengthBar oldDelegate) { - return oldDelegate.percent != percent; - } -} - -class StrengthBarBackground extends CustomPainter { - Color backgroundColor; - double? backgroundRadius; - - StrengthBarBackground({required this.backgroundColor, this.backgroundRadius}); - - @override - void paint(Canvas canvas, Size size) { - drawBarBackground(canvas, size); - } - - void drawBarBackground(Canvas canvas, Size size) { - Paint paint = Paint() - ..color = backgroundColor - ..style = PaintingStyle.fill - ..strokeCap = StrokeCap.round; - - double left = 0; - double top = 0; - double right = size.width; - double bottom = size.height; - - canvas.drawRRect( - RRect.fromRectAndRadius( - Rect.fromLTRB(left, top, right, bottom), - Radius.circular(backgroundRadius ?? 0), - ), - paint, - ); - } - - @override - bool shouldRepaint(StrengthBarBackground oldDelegate) { - return true; - } -} diff --git a/lib/settings/ui/pages/main_page/test_entry_style.dart b/lib/settings/ui/pages/main_page/test_entry_style.dart deleted file mode 100644 index 60773f2ecc..0000000000 --- a/lib/settings/ui/pages/main_page/test_entry_style.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:titan/tools/constants.dart'; - -InputDecoration changePassInputDecoration({ - required String hintText, - required ValueNotifier notifier, -}) { - return InputDecoration( - contentPadding: const EdgeInsets.symmetric(vertical: 18.0), - hintStyle: TextStyle(fontSize: 18, color: Colors.grey.shade400), - hintText: hintText, - focusedBorder: const UnderlineInputBorder( - borderSide: BorderSide(color: ColorConstants.gradient1), - ), - enabledBorder: UnderlineInputBorder( - borderSide: BorderSide(color: Colors.grey.shade600), - ), - errorBorder: const UnderlineInputBorder( - borderSide: BorderSide(color: ColorConstants.background2), - ), - focusedErrorBorder: const UnderlineInputBorder( - borderSide: BorderSide(width: 2.0, color: ColorConstants.gradient2), - ), - errorStyle: const TextStyle(color: ColorConstants.background2), - suffixIcon: IconButton( - icon: Icon( - notifier.value ? Icons.visibility : Icons.visibility_off, - color: Colors.grey.shade600, - ), - onPressed: () { - notifier.value = !notifier.value; - }, - ), - ); -} From a4b4e9567e7d2a3e186fb89f9f885fcc5d90d897 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 9 Aug 2025 17:13:26 +0200 Subject: [PATCH 108/473] translations --- lib/l10n/app_en.arb | 20 ++++- lib/l10n/app_fr.arb | 23 +++++- lib/l10n/app_localizations.dart | 80 ++++++++++++++++--- lib/l10n/app_localizations_en.dart | 47 ++++++++++- lib/l10n/app_localizations_fr.dart | 50 ++++++++++-- .../ui/pages/main_page/edit_profile.dart | 63 ++++++++------- .../ui/pages/main_page/main_page.dart | 41 +++++----- 7 files changed, 251 insertions(+), 73 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 4bbbec2d6d..cf5a1e9de0 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -990,7 +990,7 @@ "settingsHelp": "Help", "settingsIcalCopied": "Ical link copied!", "settingsLanguage": "Language", - "settingsLanguageFr": "French", + "settingsLanguageVar": "English", "settingsLogs": "Logs", "settingsModules": "Modules", "settingsMyIcs": "My Ical link", @@ -1023,6 +1023,24 @@ "settingsPasswordStrengthMedium": "Medium", "settingsPasswordStrengthStrong": "Strong", "settingsPasswordStrengthVeryStrong": "Very strong", + "settingsPhoneNumber": "Phone number", + "settingsValidate": "Confirm", + "settingsEditedAccount": "Account edited", + "settingsFailedToEditAccount": "Failed to edit account", + "settingsChooseLanguage": "Choose a language", + "settingsNotificationCounter": "{active}/{total} active {active, plural, zero {notification} one {notification} other {notifications}}", + "@settingsNotificationCounter": { + "description": "Affiche le nombre de notifications actives sur le total des notifications disponibles, avec gestion du pluriel", + "placeholders": { + "active": { "type": "int" }, + "total": { "type": "int" } + } + }, + "settingsEvent" : "Event", + "settingsIcal": "Ical link", + "settingsSynncWithCalendar": "Sync with calendar", + "settingsIcalLinkCopied": "Ical link copied", + "settingsProfile": "Profile", "voteAdd": "Add", "voteAddMember": "Add a member", "voteAddedPretendance": "List added", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 986e58e447..e42faa4d8d 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -977,8 +977,7 @@ "settingsDetelePersonalDataDesc": "Cette action notifie l'administrateur que vous souhaitez supprimer vos données personnelles.", "settingsDeleting": "Suppresion", "settingsEdit": "Modifier", - "settingsEditAccount": "Modifier le compte", - "settingsEditPassword": "Modifier le mot de passe", + "settingsEditAccount": "Modifier mon profil", "settingsEmail": "Email", "settingsEmptyField": "Ce champ ne peut pas être vide", "settingsErrorProfilePicture": "Erreur lors de la modification de la photo de profil", @@ -990,7 +989,7 @@ "settingsHelp": "Aide", "settingsIcalCopied": "Lien Ical copié !", "settingsLanguage": "Langue", - "settingsLanguageFr": "Français", + "settingsLanguageVar": "Français 🇫🇷", "settingsLogs": "Logs", "settingsModules": "Modules", "settingsMyIcs": "Mon lien Ical", @@ -1023,6 +1022,24 @@ "settingsPasswordStrengthMedium": "Moyen", "settingsPasswordStrengthStrong": "Fort", "settingsPasswordStrengthVeryStrong": "Très fort", + "settingsPhoneNumber": "Numéro de téléphone", + "settingsValidate": "Valider", + "settingsEditedAccount": "Compte modifié avec succès", + "settingsFailedToEditAccount": "Échec de la modification du compte", + "settingsChooseLanguage": "Choix de la langue", + "settingsNotificationCounter": "{active}/{total} {active, plural, zero {activée} one {activée} other {activées}}", + "@settingsNotificationCounter": { + "description": "Affiche le nombre de notifications actives sur le total des notifications disponibles, avec gestion du pluriel", + "placeholders": { + "active": { "type": "int" }, + "total": { "type": "int" } + } + }, + "settingsEvent" : "Événement", + "settingsIcal": "Lien Ical", + "settingsSynncWithCalendar": "Synchroniser avec votre calendrier", + "settingsIcalLinkCopied": "Lien Ical copié dans le presse-papier", + "settingsProfile": "Profil", "voteAdd": "Ajouter", "voteAddMember": "Ajouter un membre", "voteAddedPretendance": "Liste ajoutée", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 8e3e7698bb..3df8b0b8c7 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -5963,15 +5963,9 @@ abstract class AppLocalizations { /// No description provided for @settingsEditAccount. /// /// In fr, this message translates to: - /// **'Modifier le compte'** + /// **'Modifier mon profil'** String get settingsEditAccount; - /// No description provided for @settingsEditPassword. - /// - /// In fr, this message translates to: - /// **'Modifier le mot de passe'** - String get settingsEditPassword; - /// No description provided for @settingsEmail. /// /// In fr, this message translates to: @@ -6038,11 +6032,11 @@ abstract class AppLocalizations { /// **'Langue'** String get settingsLanguage; - /// No description provided for @settingsLanguageFr. + /// No description provided for @settingsLanguageVar. /// /// In fr, this message translates to: - /// **'Français'** - String get settingsLanguageFr; + /// **'Français 🇫🇷'** + String get settingsLanguageVar; /// No description provided for @settingsLogs. /// @@ -6236,6 +6230,72 @@ abstract class AppLocalizations { /// **'Très fort'** String get settingsPasswordStrengthVeryStrong; + /// No description provided for @settingsPhoneNumber. + /// + /// In fr, this message translates to: + /// **'Numéro de téléphone'** + String get settingsPhoneNumber; + + /// No description provided for @settingsValidate. + /// + /// In fr, this message translates to: + /// **'Valider'** + String get settingsValidate; + + /// No description provided for @settingsEditedAccount. + /// + /// In fr, this message translates to: + /// **'Compte modifié avec succès'** + String get settingsEditedAccount; + + /// No description provided for @settingsFailedToEditAccount. + /// + /// In fr, this message translates to: + /// **'Échec de la modification du compte'** + String get settingsFailedToEditAccount; + + /// No description provided for @settingsChooseLanguage. + /// + /// In fr, this message translates to: + /// **'Choix de la langue'** + String get settingsChooseLanguage; + + /// Affiche le nombre de notifications actives sur le total des notifications disponibles, avec gestion du pluriel + /// + /// In fr, this message translates to: + /// **'{active}/{total} {active, plural, zero {activée} one {activée} other {activées}}'** + String settingsNotificationCounter(int active, int total); + + /// No description provided for @settingsEvent. + /// + /// In fr, this message translates to: + /// **'Événement'** + String get settingsEvent; + + /// No description provided for @settingsIcal. + /// + /// In fr, this message translates to: + /// **'Lien Ical'** + String get settingsIcal; + + /// No description provided for @settingsSynncWithCalendar. + /// + /// In fr, this message translates to: + /// **'Synchroniser avec votre calendrier'** + String get settingsSynncWithCalendar; + + /// No description provided for @settingsIcalLinkCopied. + /// + /// In fr, this message translates to: + /// **'Lien Ical copié dans le presse-papier'** + String get settingsIcalLinkCopied; + + /// No description provided for @settingsProfile. + /// + /// In fr, this message translates to: + /// **'Profil'** + String get settingsProfile; + /// No description provided for @voteAdd. /// /// In fr, this message translates to: diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 2694e05cd2..ff5a904945 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -2997,9 +2997,6 @@ class AppLocalizationsEn extends AppLocalizations { @override String get settingsEditAccount => 'Edit account'; - @override - String get settingsEditPassword => 'Edit password'; - @override String get settingsEmail => 'Email'; @@ -3034,7 +3031,7 @@ class AppLocalizationsEn extends AppLocalizations { String get settingsLanguage => 'Language'; @override - String get settingsLanguageFr => 'French'; + String get settingsLanguageVar => 'English'; @override String get settingsLogs => 'Logs'; @@ -3132,6 +3129,48 @@ class AppLocalizationsEn extends AppLocalizations { @override String get settingsPasswordStrengthVeryStrong => 'Very strong'; + @override + String get settingsPhoneNumber => 'Phone number'; + + @override + String get settingsValidate => 'Confirm'; + + @override + String get settingsEditedAccount => 'Account edited'; + + @override + String get settingsFailedToEditAccount => 'Failed to edit account'; + + @override + String get settingsChooseLanguage => 'Choose a language'; + + @override + String settingsNotificationCounter(int active, int total) { + String _temp0 = intl.Intl.pluralLogic( + active, + locale: localeName, + other: 'notifications', + one: 'notification', + zero: 'notification', + ); + return '$active/$total active $_temp0'; + } + + @override + String get settingsEvent => 'Event'; + + @override + String get settingsIcal => 'Ical link'; + + @override + String get settingsSynncWithCalendar => 'Sync with calendar'; + + @override + String get settingsIcalLinkCopied => 'Ical link copied'; + + @override + String get settingsProfile => 'Profile'; + @override String get voteAdd => 'Add'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index bf86111c65..17129e7eac 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -3015,10 +3015,7 @@ class AppLocalizationsFr extends AppLocalizations { String get settingsEdit => 'Modifier'; @override - String get settingsEditAccount => 'Modifier le compte'; - - @override - String get settingsEditPassword => 'Modifier le mot de passe'; + String get settingsEditAccount => 'Modifier mon profil'; @override String get settingsEmail => 'Email'; @@ -3056,7 +3053,7 @@ class AppLocalizationsFr extends AppLocalizations { String get settingsLanguage => 'Langue'; @override - String get settingsLanguageFr => 'Français'; + String get settingsLanguageVar => 'Français 🇫🇷'; @override String get settingsLogs => 'Logs'; @@ -3157,6 +3154,49 @@ class AppLocalizationsFr extends AppLocalizations { @override String get settingsPasswordStrengthVeryStrong => 'Très fort'; + @override + String get settingsPhoneNumber => 'Numéro de téléphone'; + + @override + String get settingsValidate => 'Valider'; + + @override + String get settingsEditedAccount => 'Compte modifié avec succès'; + + @override + String get settingsFailedToEditAccount => + 'Échec de la modification du compte'; + + @override + String get settingsChooseLanguage => 'Choix de la langue'; + + @override + String settingsNotificationCounter(int active, int total) { + String _temp0 = intl.Intl.pluralLogic( + active, + locale: localeName, + other: 'activées', + one: 'activée', + zero: 'activée', + ); + return '$active/$total $_temp0'; + } + + @override + String get settingsEvent => 'Événement'; + + @override + String get settingsIcal => 'Lien Ical'; + + @override + String get settingsSynncWithCalendar => 'Synchroniser avec votre calendrier'; + + @override + String get settingsIcalLinkCopied => 'Lien Ical copié dans le presse-papier'; + + @override + String get settingsProfile => 'Profil'; + @override String get voteAdd => 'Ajouter'; diff --git a/lib/settings/ui/pages/main_page/edit_profile.dart b/lib/settings/ui/pages/main_page/edit_profile.dart index 7ce5d19c11..0f0776c795 100644 --- a/lib/settings/ui/pages/main_page/edit_profile.dart +++ b/lib/settings/ui/pages/main_page/edit_profile.dart @@ -32,6 +32,9 @@ class EditProfile extends ConsumerWidget { MediaQuery.of(context).viewInsets.bottom; + final localizeWithContext = AppLocalizations.of(context)!; + final navigatorWithContext = Navigator.of(context); + return Padding( padding: const EdgeInsets.symmetric(horizontal: 40), child: SingleChildScrollView( @@ -75,24 +78,21 @@ class EditProfile extends ConsumerWidget { if (value) { displayToastWithContext( TypeMsg.msg, - AppLocalizations.of( - context, - )!.settingsUpdatedProfilePicture, + localizeWithContext + .settingsUpdatedProfilePicture, ); } else { displayToastWithContext( TypeMsg.error, - AppLocalizations.of( - context, - )!.settingsTooHeavyProfilePicture, + localizeWithContext + .settingsTooHeavyProfilePicture, ); } } else { displayToastWithContext( TypeMsg.error, - AppLocalizations.of( - context, - )!.settingsErrorProfilePicture, + localizeWithContext + .settingsErrorProfilePicture, ); } }, @@ -110,24 +110,21 @@ class EditProfile extends ConsumerWidget { if (value) { displayToastWithContext( TypeMsg.msg, - AppLocalizations.of( - context, - )!.settingsUpdatedProfilePicture, + localizeWithContext + .settingsUpdatedProfilePicture, ); } else { displayToastWithContext( TypeMsg.error, - AppLocalizations.of( - context, - )!.settingsTooHeavyProfilePicture, + localizeWithContext + .settingsTooHeavyProfilePicture, ); } } else { displayToastWithContext( TypeMsg.error, - AppLocalizations.of( - context, - )!.settingsErrorProfilePicture, + localizeWithContext + .settingsErrorProfilePicture, ); } }, @@ -145,16 +142,14 @@ class EditProfile extends ConsumerWidget { if (value) { displayToastWithContext( TypeMsg.msg, - AppLocalizations.of( - context, - )!.settingsUpdatedProfilePicture, + localizeWithContext + .settingsUpdatedProfilePicture, ); } else { displayToastWithContext( TypeMsg.error, - AppLocalizations.of( - context, - )!.settingsErrorProfilePicture, + localizeWithContext + .settingsErrorProfilePicture, ); } } @@ -171,13 +166,13 @@ class EditProfile extends ConsumerWidget { ), SizedBox(height: View.of(context).viewInsets.bottom == 0 ? 30 : 10), TextEntry( - label: "Email", + label: localizeWithContext.settingsEmail, controller: textControllers[0], enabled: false, ), SizedBox(height: 20), TextEntry( - label: "Numéro de téléphone", + label: localizeWithContext.settingsPhoneNumber, controller: textControllers[1], textInputAction: TextInputAction.done, onChanged: (value) { @@ -189,7 +184,7 @@ class EditProfile extends ConsumerWidget { children: [ Expanded( child: TextEntry( - label: "Date de naissance", + label: localizeWithContext.settingsBirthday, controller: textControllers[2], enabled: false, ), @@ -214,7 +209,7 @@ class EditProfile extends ConsumerWidget { ), SizedBox(height: 30), Button( - text: "Valider", + text: localizeWithContext.settingsValidate, disabled: !(textControllers[1].value.text != me.phone || textControllers[2].value.text.isNotEmpty), @@ -234,10 +229,16 @@ class EditProfile extends ConsumerWidget { ); final value = await asyncUserNotifier.updateMe(newMe); if (value) { - displayToastWithContext(TypeMsg.msg, "Succès"); - Navigator.of(context).pop(); + displayToastWithContext( + TypeMsg.msg, + localizeWithContext.settingsEditedAccount, + ); + navigatorWithContext.pop(); } else { - displayToastWithContext(TypeMsg.error, "Échec"); + displayToastWithContext( + TypeMsg.error, + localizeWithContext.settingsFailedToEditAccount, + ); } }); } diff --git a/lib/settings/ui/pages/main_page/main_page.dart b/lib/settings/ui/pages/main_page/main_page.dart index c91b98fe63..d3c06c09c3 100644 --- a/lib/settings/ui/pages/main_page/main_page.dart +++ b/lib/settings/ui/pages/main_page/main_page.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/settings/providers/notification_topic_provider.dart'; import 'package:titan/settings/tools/functions.dart'; import 'package:titan/settings/ui/pages/main_page/edit_profile.dart'; @@ -48,9 +49,9 @@ class SettingsMainPage extends HookConsumerWidget { error: (_, _) => 0, ); - final selectedLanguage = ref.watch(localeProvider)?.languageCode == 'fr' - ? "Français" - : "English"; + final localizeWithContext = AppLocalizations.of(context)!; + + final selectedLanguage = localizeWithContext.settingsLanguageVar; return SettingsTemplate( child: Refresher( @@ -98,8 +99,8 @@ class SettingsMainPage extends HookConsumerWidget { const HeroIcon(HeroIcons.userCircle, size: 140), ), const SizedBox(height: 20), - const Text( - "Compte", + Text( + localizeWithContext.settingsAccount, style: TextStyle( fontSize: 24, fontWeight: FontWeight.bold, @@ -107,12 +108,12 @@ class SettingsMainPage extends HookConsumerWidget { ), ), ListItem( - title: "Profil", - subtitle: "Modifier mon profil", + title: localizeWithContext.settingsProfile, + subtitle: localizeWithContext.settingsEditAccount, onTap: () async { await showCustomBottomModal( modal: BottomModalTemplate( - title: 'Modifier mon profil', + title: localizeWithContext.settingsEditAccount, child: EditProfile(), ), context: context, @@ -121,12 +122,12 @@ class SettingsMainPage extends HookConsumerWidget { }, ), ListItem( - title: "Langue", + title: localizeWithContext.settingsLanguage, subtitle: selectedLanguage, onTap: () async { await showCustomBottomModal( modal: BottomModalTemplate( - title: 'Choix de la langue', + title: localizeWithContext.settingsChooseLanguage, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -172,13 +173,15 @@ class SettingsMainPage extends HookConsumerWidget { }, ), ListItem( - title: "Notifications", - subtitle: - "$notificationAcivatedCounts/$notificationTopicsLength activées", + title: localizeWithContext.settingsNotifications, + subtitle: localizeWithContext.settingsNotificationCounter( + notificationAcivatedCounts, + notificationTopicsLength, + ), onTap: () async { await showCustomBottomModal( modal: BottomModalTemplate( - title: 'Notifications', + title: localizeWithContext.settingsNotifications, child: Consumer( builder: (context, ref, child) { final notificationTopicList = ref.watch( @@ -264,8 +267,8 @@ class SettingsMainPage extends HookConsumerWidget { }, ), const SizedBox(height: 20), - const Text( - "Événements", + Text( + localizeWithContext.settingsEvent, style: TextStyle( fontSize: 24, fontWeight: FontWeight.bold, @@ -273,8 +276,8 @@ class SettingsMainPage extends HookConsumerWidget { ), ), ListItemTemplate( - title: "Lien ical", - subtitle: "Synchroniser avec votre calendrier", + title: localizeWithContext.settingsIcal, + subtitle: localizeWithContext.settingsSynncWithCalendar, trailing: const HeroIcon( HeroIcons.clipboardDocumentList, color: ColorConstants.tertiary, @@ -285,7 +288,7 @@ class SettingsMainPage extends HookConsumerWidget { ).then((value) { displayToastWithContext( TypeMsg.msg, - "Lien ical copié dans le presse-papiers", + localizeWithContext.settingsIcalCopied, ); }); }, From eaca960cd7ee408b759b6d6499a856193d25c2ba Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 9 Aug 2025 17:15:33 +0200 Subject: [PATCH 109/473] fix : import --- .../ui/components/password_strength.dart | 2 +- lib/login/ui/components/secure_bar.dart | 312 ++++++++++++++++++ 2 files changed, 313 insertions(+), 1 deletion(-) create mode 100644 lib/login/ui/components/secure_bar.dart diff --git a/lib/login/ui/components/password_strength.dart b/lib/login/ui/components/password_strength.dart index de6a6750cb..c5328c1188 100644 --- a/lib/login/ui/components/password_strength.dart +++ b/lib/login/ui/components/password_strength.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/settings/ui/pages/main_page/secure_bar.dart'; +import 'package:titan/login/ui/components/secure_bar.dart'; import 'package:titan/tools/ui/widgets/align_left_text.dart'; import 'package:titan/l10n/app_localizations.dart'; diff --git a/lib/login/ui/components/secure_bar.dart b/lib/login/ui/components/secure_bar.dart new file mode 100644 index 0000000000..ab081792b9 --- /dev/null +++ b/lib/login/ui/components/secure_bar.dart @@ -0,0 +1,312 @@ +import 'package:flutter/material.dart'; +import 'package:zxcvbn/zxcvbn.dart'; + +// Source : https://github.com/JinHoSo/flutter-password-strength/blob/master/lib/flutter_password_strength.dart + +class FlutterPasswordStrength extends StatefulWidget { + final String? password; + + //Strength bar width + final double? width; + + //Strength bar height + final double height; + + //Strength bar colors are changed depending on strength + final Animatable strengthColors; + + //Strength bar background color + final Color backgroundColor; + + //Strength bar radius + final double radius; + + //Strength bar animation duration + final Duration? duration; + + //Strength callback + final void Function(double strength)? strengthCallback; + + const FlutterPasswordStrength({ + super.key, + required this.password, + this.width, + this.height = 5, + required this.strengthColors, + this.backgroundColor = Colors.grey, + this.radius = 0, + this.duration, + this.strengthCallback, + }); + + /* + default strength bar colors + This is approximate values + 0.0 ~ 0.25 : red + 0.26 ~ 0.5 : yellow + 0.51 ~ 0.75 : blue + 0.76 ~ 1 : green + */ + Animatable get _strengthColors => strengthColors; + + //default duration is 300 milliseconds + Duration? get _duration => duration ?? const Duration(milliseconds: 300); + + @override + FlutterPasswordStrengthState createState() => FlutterPasswordStrengthState(); +} + +class FlutterPasswordStrengthState extends State + with SingleTickerProviderStateMixin { + //Animation controller for strength bar + late AnimationController _animationController; + + //Animation for strength bar sharp + late Animation _strengthBarAnimation; + + //Strength bar colors + late Animatable _strengthBarColors; + + //Strength bar color from the list of strength bar colors + late Color _strengthBarColor; + + //Strength bar color + late Color _backgroundColor; + + //Strength bar width + double? _width; + + //Strength bar height + late double _height; + + //Strength bar radius, default is 0 + double _radius = 0; + + //Strength callback + void Function(double strength)? _strengthCallback; + + //_begin is used in _strengthBarAnimation + double _begin = 0; + + //_end is used in _strengthBarAnimation + double _end = 0; + + //calculated strength from password + double _passwordStrength = 0; + + // zxcvbn password strength estimator + Zxcvbn zxcvbn = Zxcvbn(); + + @override + void initState() { + super.initState(); + + //initialize + _animationController = AnimationController( + duration: widget._duration, + vsync: this, + ); + _strengthBarAnimation = Tween( + begin: _begin, + end: _end, + ).animate(_animationController); + _strengthBarColors = widget._strengthColors; + _strengthBarColor = + _strengthBarColors.evaluate( + AlwaysStoppedAnimation(_passwordStrength), + ) ?? + Colors.transparent; + + _backgroundColor = widget.backgroundColor; + + _width = widget.width; + _height = widget.height; + _radius = widget.radius; + _strengthCallback = widget.strengthCallback; + + //start animation + _animationController.forward(); + } + + void animate() { + //calculate strength + if (widget.password == null || widget.password!.isEmpty) { + _passwordStrength = 0; + } else { + _passwordStrength = + (zxcvbn.evaluate(widget.password ?? "").score ?? 0) / 4; + } + + _begin = _end; + _end = _passwordStrength * 100; + + _strengthBarAnimation = Tween( + begin: _begin, + end: _end, + ).animate(_animationController); + _strengthBarColor = + _strengthBarColors.evaluate( + AlwaysStoppedAnimation(_passwordStrength), + ) ?? + Colors.transparent; + + _animationController.forward(from: 0.0); + + if (_strengthCallback != null) { + _strengthCallback!(_passwordStrength); + } + } + + @override + void dispose() { + super.dispose(); + _animationController.dispose(); + } + + @override + void didUpdateWidget(FlutterPasswordStrength oldWidget) { + super.didUpdateWidget(oldWidget); + + if (oldWidget.password != widget.password) { + animate(); + } + } + + @override + Widget build(BuildContext context) { + return StrengthBarContainer( + barColor: _strengthBarColor, + backgroundColor: _backgroundColor, + width: _width, + height: _height, + radius: _radius, + animation: _strengthBarAnimation, + ); + } +} + +class StrengthBarContainer extends AnimatedWidget { + final Color barColor; + final Color backgroundColor; + final double? width; + final double height; + final double radius; + + const StrengthBarContainer({ + super.key, + required this.barColor, + required this.backgroundColor, + this.width, + required this.height, + required this.radius, + required Animation animation, + }) : super(listenable: animation); + + Animation get _percent { + return listenable as Animation; + } + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + return CustomPaint( + size: Size(width ?? constraints.maxWidth, height), + painter: StrengthBarBackground( + backgroundColor: backgroundColor, + backgroundRadius: radius, + ), + foregroundPainter: StrengthBar( + barColor: barColor, + barRadius: radius, + percent: _percent.value, + ), + ); + }, + ); + } +} + +class StrengthBar extends CustomPainter { + Color barColor; + double barRadius; + double percent; + + StrengthBar({ + required this.barColor, + required this.barRadius, + required this.percent, + }); + + @override + void paint(Canvas canvas, Size size) { + drawBar(canvas, size); + } + + void drawBar(Canvas canvas, Size size) { + Paint paint = Paint() + ..color = barColor + ..style = PaintingStyle.fill + ..strokeCap = StrokeCap.round; + + double left = 0; + double top = 0; + double right = size.width / 100 * percent; + double bottom = size.height; + + //the bar width needs to be bigger than radius width + if (barRadius != 0 && right > 0 && barRadius * 2 > right) { + right = barRadius * 2; + } + + canvas.drawRRect( + RRect.fromRectAndRadius( + Rect.fromLTRB(left, top, right, bottom), + Radius.circular(barRadius), + ), + paint, + ); + } + + @override + bool shouldRepaint(StrengthBar oldDelegate) { + return oldDelegate.percent != percent; + } +} + +class StrengthBarBackground extends CustomPainter { + Color backgroundColor; + double? backgroundRadius; + + StrengthBarBackground({required this.backgroundColor, this.backgroundRadius}); + + @override + void paint(Canvas canvas, Size size) { + drawBarBackground(canvas, size); + } + + void drawBarBackground(Canvas canvas, Size size) { + Paint paint = Paint() + ..color = backgroundColor + ..style = PaintingStyle.fill + ..strokeCap = StrokeCap.round; + + double left = 0; + double top = 0; + double right = size.width; + double bottom = size.height; + + canvas.drawRRect( + RRect.fromRectAndRadius( + Rect.fromLTRB(left, top, right, bottom), + Radius.circular(backgroundRadius ?? 0), + ), + paint, + ); + } + + @override + bool shouldRepaint(StrengthBarBackground oldDelegate) { + return true; + } +} From 17d7a7358e8e17a0f744de88ba46a72c3ff065c9 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 9 Aug 2025 17:40:51 +0200 Subject: [PATCH 110/473] fix language --- lib/main.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/main.dart b/lib/main.dart index 55416fb42b..c25a4fce15 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -15,6 +15,7 @@ import 'package:titan/router.dart'; import 'package:titan/service/tools/setup.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/plausible/plausible_observer.dart'; +import 'package:titan/tools/providers/locale_notifier.dart'; import 'package:titan/tools/providers/path_forwarding_provider.dart'; import 'package:titan/tools/ui/layouts/app_template.dart'; import 'package:qlevar_router/qlevar_router.dart'; @@ -95,6 +96,7 @@ class MyApp extends HookConsumerWidget { debugShowCheckedModeBanner: false, title: 'MyECL', scrollBehavior: MyCustomScrollBehavior(), + locale: ref.watch(localeProvider), supportedLocales: const [Locale('en', 'US'), Locale('fr', 'FR')], localizationsDelegates: const [ AppLocalizations.delegate, From 761c2f58e86ddc0e18fe69306aeb2d744a03cd4e Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 9 Aug 2025 17:42:04 +0200 Subject: [PATCH 111/473] add flag --- lib/l10n/app_en.arb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index cf5a1e9de0..8a3082e195 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -990,7 +990,7 @@ "settingsHelp": "Help", "settingsIcalCopied": "Ical link copied!", "settingsLanguage": "Language", - "settingsLanguageVar": "English", + "settingsLanguageVar": "English 🇬🇧", "settingsLogs": "Logs", "settingsModules": "Modules", "settingsMyIcs": "My Ical link", From 9d96a0307bae3d1ee62fa286ae1eb0b34db0909b Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 9 Aug 2025 17:47:03 +0200 Subject: [PATCH 112/473] fix navbar when switching module --- lib/l10n/app_localizations_en.dart | 2 +- lib/navigation/ui/all_module_page.dart | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index ff5a904945..16efee4228 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -3031,7 +3031,7 @@ class AppLocalizationsEn extends AppLocalizations { String get settingsLanguage => 'Language'; @override - String get settingsLanguageVar => 'English'; + String get settingsLanguageVar => 'English 🇬🇧'; @override String get settingsLogs => 'Logs'; diff --git a/lib/navigation/ui/all_module_page.dart b/lib/navigation/ui/all_module_page.dart index 964647ee4e..e5ca095434 100644 --- a/lib/navigation/ui/all_module_page.dart +++ b/lib/navigation/ui/all_module_page.dart @@ -3,6 +3,7 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/navigation/providers/navbar_module_list.dart'; +import 'package:titan/navigation/providers/navbar_visibility_provider.dart'; import 'package:titan/navigation/ui/scroll_to_hide_navbar.dart'; import 'package:titan/router.dart'; import 'package:titan/settings/providers/module_list_provider.dart'; @@ -21,6 +22,9 @@ class AllModulePage extends HookConsumerWidget { final navbarListModuleNotifier = ref.watch( navbarListModuleProvider.notifier, ); + final navbarVisibilityNotifier = ref.read( + navbarVisibilityProvider.notifier, + ); final scrollController = useScrollController(); return Container( color: ColorConstants.background, @@ -55,6 +59,7 @@ class AllModulePage extends HookConsumerWidget { ); pathForwardingNotifier.forward(module.root); QR.to(module.root); + navbarVisibilityNotifier.show(); }, ), ), From 84164a418ae451cdaa744e84dffe55f91082e1e5 Mon Sep 17 00:00:00 2001 From: Thonyk Date: Sat, 9 Aug 2025 18:14:06 +0200 Subject: [PATCH 113/473] feat: finition --- lib/l10n/app_en.arb | 81 +++++- lib/l10n/app_fr.arb | 79 ++++- lib/l10n/app_localizations.dart | 96 ++++--- lib/l10n/app_localizations_en.dart | 64 +++-- lib/l10n/app_localizations_fr.dart | 60 ++-- lib/phonebook/class/complete_member.dart | 4 + .../ui/pages/admin_page/admin_page.dart | 4 +- .../association_admin_edition_modal.dart | 34 ++- .../association_add_edit_page.dart | 21 +- .../association_groups_page.dart | 3 + .../association_members_page.dart | 5 +- .../member_edition_modal.dart | 19 +- .../association_page/association_page.dart | 19 +- .../ui/pages/association_page/card_field.dart | 37 --- .../association_page/web_member_card.dart | 271 ------------------ .../member_detail_page.dart | 19 +- .../membership_editor_page.dart | 14 +- .../user_search_modal.dart | 5 +- lib/phonebook/ui/phonebook.dart | 6 + .../ui/styleguide/custom_dialog_box.dart | 111 +++---- lib/tools/ui/styleguide/text_entry.dart | 4 +- 21 files changed, 411 insertions(+), 545 deletions(-) delete mode 100644 lib/phonebook/ui/pages/association_page/card_field.dart delete mode 100644 lib/phonebook/ui/pages/association_page/web_member_card.dart diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 2b1b093035..56506b5043 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -449,7 +449,16 @@ "eventDaySun": "Sunday", "globalConfirm": "Confirm", "globalCancel": "Cancel", - "globalOptionnal": "Optional", + "globalIrreversibleAction": "This action is irreversible", + "globalOptionnal": "{text} (Optional)", + "@globalOptionnal": { + "description": "Text with optional complement", + "placeholders": { + "text": { + "type": "String" + } + } + }, "homeCalendar": "Calendar", "homeEventOf": "Events of", "homeIncomingEvents": "Upcoming events", @@ -647,23 +656,51 @@ "phonebookApparentName": "Public role name:", "phonebookAssociation": "Association", "phonebookAssociationDetail": "Association details:", + "phonebookAssociationGroupement": "Association groupement", "phonebookAssociationKind": "Type of association:", "phonebookAssociationName": "Association name", "phonebookAssociations": "Associations", "phonebookCancel": "Cancel", - "phonebookChangeMandate": "Switch to mandate ", - "phonebookChangeMandateConfirm": "Are you sure you want to change the entire mandate?\nThis action is irreversible!", + "phonebookChangeTermYear": "Switch to {year} term", + "@phonebookChangeTermYear": { + "description": "Change the term year of the association", + "placeholders": { + "year": { + "type": "int" + } + } + }, + "phonebookChangeTermConfirm": "Are you sure you want to change the entire term?\nThis action is irreversible!", "phonebookConfirm": "Confirm", "phonebookCopied": "Copied to clipboard", - "phonebookDeactivateAssociation": "Are you sure you want to deactivate this association?\nThis action is irreversible!", + "phonebookDeactivateAssociation": "Deactivate association", "phonebookDeactivatedAssociation": "Association deactivated", "phonebookDeactivatedAssociationWarning": "Warning, this association is deactivated, you cannot modify it", - "phonebookDeactivating": "Deactivate the association?", "phonebookDeactivatingError": "Error during deactivation", "phonebookDetail": "Details:", - "phonebookDeleteAssociation": "Delete the association?\nThis will erase all association history", + "phonebookDeleteAssociation": "Delete association", + "phonebookDeleteSelectedAssociation": "Delete the association {association}?", + "@phonebookDeleteSelectedAssociation": { + "description": "Delete an association", + "placeholders": { + "association": { + "type": "String" + } + } + }, + "phonebookDeleteAssociationDescription": "This will erase all association history", "phonebookDeletedAssociation": "Association deleted", "phonebookDeletedMember": "Member deleted", + "phonebookDeleteRole": "Delete role", + "phonebookDeleteUserRole": "Delete the role of {name}?", + "@phonebookDeleteUserRole": { + "description": "Delete the role of a user", + "placeholders": { + "name": { + "type": "String" + } + } + }, "phonebookDeleting": "Deleting", "phonebookDeletingError": "Error deleting", "phonebookDescription": "Description", @@ -672,7 +709,7 @@ "phonebookEditAssociationGroups": "Manage groups", "phonebookEditAssociationInfo": "Edit", "phonebookEditAssociationMembers": "Manage members", - "phonebookEditMembership": "Edit role", + "phonebookEditRole": "Edit role", "phonebookEmail": "Email:", "phonebookEmailCopied": "Email copied to clipboard", "phonebookEmptyApparentName": "Please enter a role name", @@ -688,7 +725,7 @@ "phonebookErrorLoadAssociationPicture": "Error loading association picture", "phonebookErrorLoadProfilePicture": "Error", "phonebookErrorRoleTagsLoading": "Error loading role tags", - "phonebookExistingMembership": "This member is already in the current mandate", + "phonebookExistingMembership": "This member is already in the current term", "phonebookFirstname": "First name:", "phonebookGroupementName": "Groupement name", "phonebookGroups": "Manage {association} groups", @@ -700,8 +737,16 @@ } } }, - "phonebookMandate": "Mandate", - "phonebookMandateChangingError": "Error changing mandate", + "phonebookTerm": "{year} term", + "@phonebookTerm": { + "description": "Term year of the association", + "placeholders": { + "year": { + "type": "int" + } + } + }, + "phonebookTermChangingError": "Error changing term", "phonebookMember": "Member", "phonebookMemberReordered": "Member reordered", "phonebookMembers": "Manage {association} members", @@ -728,8 +773,8 @@ "phonebookName": "Last name:", "phonebookNameCopied": "Name and first name copied to clipboard", "phonebookNamePure": "Last name", - "phonebookNewMandate": "New mandate", - "phonebookNewMandateConfirmed": "Mandate changed", + "phonebookNewTerm": "New term", + "phonebookNewTermConfirmed": "Term changed", "phonebookNickname": "Nickname:", "phonebookNicknameCopied": "Nickname copied to clipboard", "phonebookNoAssociationFound": "No association found", @@ -745,11 +790,19 @@ "phonebookPhonebookSearchRole": "Position", "phonebookPresidentRoleTag": "Prez'", "phonebookPromoNotGiven": "Promotion not provided", - "phonebookPromotion": "Promotion:", + "phonebookPromotion": "Promotion {year}", + "@phonebookPromotion": { + "description": "Promotion year of the member", + "placeholders": { + "year": { + "type": "int" + } + } + }, "phonebookReorderingError": "Error during reordering", "phonebookResearch": "Search", "phonebookRolePure": "Role", - "phonebookSearchMember": "Search a member", + "phonebookSearchUser": "Search a user", "phonebookTooHeavyAssociationPicture": "Image is too large (max 4MB)", "phonebookUpdateGroups": "Update groups", "phonebookUpdatedAssociation": "Association updated", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 899de6639c..4daa252489 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -449,7 +449,16 @@ "eventDaySun": "Dimanche", "globalConfirm": "Confirmer", "globalCancel": "Annuler", - "globalOptionnal": "Optionnel", + "globalIrreversibleAction": "Cette action est irréversible", + "globalOptionnal": "{text} (Optionnel)", + "@globalOptionnal": { + "description": "Texte avec complément optionnel", + "placeholders": { + "text": { + "type": "String" + } + } + }, "homeCalendar": "Calendrier", "homeEventOf": "Évènements du", "homeIncomingEvents": "Évènements à venir", @@ -647,23 +656,51 @@ "phonebookApparentName": "Nom public du rôle :", "phonebookAssociation": "Association", "phonebookAssociationDetail": "Détail de l'association :", + "phonebookAssociationGroupement": "Groupement d'association", "phonebookAssociationKind": "Type d'association :", "phonebookAssociationName": "Nom de l'association", "phonebookAssociations": "Associations", "phonebookCancel": "Annuler", - "phonebookChangeMandate": "Passer au mandat ", - "phonebookChangeMandateConfirm": "Êtes-vous sûr de vouloir changer tout le mandat ?\nCette action est irréversible !", + "phonebookChangeTermYear": "Passer au mandat {year}", + "@phonebookChangeTermYear": { + "description": "Permet de changer le mandat d'une association", + "placeholders": { + "year": { + "type": "int" + } + } + }, + "phonebookChangeTermConfirm": "Êtes-vous sûr de vouloir changer tout le mandat ?\nCette action est irréversible !", "phonebookConfirm": "Confirmer", "phonebookCopied": "Copié dans le presse-papier", - "phonebookDeactivateAssociation": "Êtes-vous sûr de vouloir désactiver cette association ?\nCette action est irréversible !", + "phonebookDeactivateAssociation": "Désactiver l'association", "phonebookDeactivatedAssociation": "Association désactivée", "phonebookDeactivatedAssociationWarning": "Attention, cette association est désactivée, vous ne pouvez pas la modifier", - "phonebookDeactivating": "Désactiver l'association ?", "phonebookDeactivatingError": "Erreur lors de la désactivation", "phonebookDetail": "Détail :", - "phonebookDeleteAssociation": "Supprimer l'association ?\nCela va effacer tout l'historique de l'association", + "phonebookDeleteAssociation": "Supprimer l'association", + "phonebookDeleteSelectedAssociation": "Supprimer l'association {association} ?", + "@phonebookDeleteSelectedAssociation": { + "description": "Permet de supprimer une association", + "placeholders": { + "association": { + "type": "String" + } + } + }, + "phonebookDeleteAssociationDescription": "Ceci va supprimer l'historique de l'association", "phonebookDeletedAssociation": "Association supprimée", "phonebookDeletedMember": "Membre supprimé", + "phonebookDeleteRole": "Supprimer le rôle", + "phonebookDeleteUserRole": "Supprimer le rôle de l'utilisateur {name} ?", + "@phonebookDeleteUserRole": { + "description": "Permet de supprimer le rôle d'un utilisateur dans une association", + "placeholders": { + "name": { + "type": "String" + } + } + }, "phonebookDeleting": "Suppression", "phonebookDeletingError": "Erreur lors de la suppression", "phonebookDescription": "Description", @@ -672,7 +709,7 @@ "phonebookEditAssociationGroups": "Gérer les groupes", "phonebookEditAssociationInfo": "Modifier", "phonebookEditAssociationMembers": "Gérer les membres", - "phonebookEditMembership": "Modifier le rôle", + "phonebookEditRole": "Modifier le rôle", "phonebookEmail": "Email :", "phonebookEmailCopied": "Email copié dans le presse-papier", "phonebookEmptyApparentName": "Veuillez entrer un nom de role", @@ -700,8 +737,16 @@ } } }, - "phonebookMandate": "Mandat", - "phonebookMandateChangingError": "Erreur lors du changement de mandat", + "phonebookTerm": "Mandat {year}", + "@phonebookTerm": { + "description": "Année de mandat d'une association", + "placeholders": { + "year": { + "type": "int" + } + } + }, + "phonebookTermChangingError": "Erreur lors du changement de mandat", "phonebookMember": "Membre", "phonebookMemberReordered": "Membre réordonné", "phonebookMembers": "Gérer les membres de {association}", @@ -728,8 +773,8 @@ "phonebookName": "Nom :", "phonebookNameCopied": "Nom et prénom copié dans le presse-papier", "phonebookNamePure": "Nom", - "phonebookNewMandate": "Nouveau mandat", - "phonebookNewMandateConfirmed": "Mandat changé", + "phonebookNewTerm": "Nouveau mandat", + "phonebookNewTermConfirmed": "Mandat changé", "phonebookNickname": "Surnom :", "phonebookNicknameCopied": "Surnom copié dans le presse-papier", "phonebookNoAssociationFound": "Aucune association trouvée", @@ -745,11 +790,19 @@ "phonebookPhonebookSearchRole": "Poste", "phonebookPresidentRoleTag": "Prez'", "phonebookPromoNotGiven": "Promo non renseignée", - "phonebookPromotion": "Promotion :", + "phonebookPromotion": "Promotion {year}", + "@phonebookPromotion": { + "description": "Année de promotion d'un membre", + "placeholders": { + "year": { + "type": "int" + } + } + }, "phonebookReorderingError": "Erreur lors du réordonnement", "phonebookResearch": "Rechercher", "phonebookRolePure": "Rôle", - "phonebookSearchMember": "un membre", + "phonebookSearchUser": "Rechercher un utilisateur", "phonebookTooHeavyAssociationPicture": "L'image est trop lourde (max 4Mo)", "phonebookUpdateGroups": "Mettre à jour les groupes", "phonebookUpdatedAssociation": "Association modifiée", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index b8e497a3aa..0e135d6298 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -2792,11 +2792,17 @@ abstract class AppLocalizations { /// **'Annuler'** String get globalCancel; - /// No description provided for @globalOptionnal. + /// No description provided for @globalIrreversibleAction. /// /// In fr, this message translates to: - /// **'Optionnel'** - String get globalOptionnal; + /// **'Cette action est irréversible'** + String get globalIrreversibleAction; + + /// Texte avec complément optionnel + /// + /// In fr, this message translates to: + /// **'{text} (Optionnel)'** + String globalOptionnal(String text); /// No description provided for @homeCalendar. /// @@ -3980,6 +3986,12 @@ abstract class AppLocalizations { /// **'Détail de l\'association :'** String get phonebookAssociationDetail; + /// No description provided for @phonebookAssociationGroupement. + /// + /// In fr, this message translates to: + /// **'Groupement d\'association'** + String get phonebookAssociationGroupement; + /// No description provided for @phonebookAssociationKind. /// /// In fr, this message translates to: @@ -4004,17 +4016,17 @@ abstract class AppLocalizations { /// **'Annuler'** String get phonebookCancel; - /// No description provided for @phonebookChangeMandate. + /// Permet de changer le mandat d'une association /// /// In fr, this message translates to: - /// **'Passer au mandat '** - String get phonebookChangeMandate; + /// **'Passer au mandat {year}'** + String phonebookChangeTermYear(int year); - /// No description provided for @phonebookChangeMandateConfirm. + /// No description provided for @phonebookChangeTermConfirm. /// /// In fr, this message translates to: /// **'Êtes-vous sûr de vouloir changer tout le mandat ?\nCette action est irréversible !'** - String get phonebookChangeMandateConfirm; + String get phonebookChangeTermConfirm; /// No description provided for @phonebookConfirm. /// @@ -4031,7 +4043,7 @@ abstract class AppLocalizations { /// No description provided for @phonebookDeactivateAssociation. /// /// In fr, this message translates to: - /// **'Êtes-vous sûr de vouloir désactiver cette association ?\nCette action est irréversible !'** + /// **'Désactiver l\'association'** String get phonebookDeactivateAssociation; /// No description provided for @phonebookDeactivatedAssociation. @@ -4046,12 +4058,6 @@ abstract class AppLocalizations { /// **'Attention, cette association est désactivée, vous ne pouvez pas la modifier'** String get phonebookDeactivatedAssociationWarning; - /// No description provided for @phonebookDeactivating. - /// - /// In fr, this message translates to: - /// **'Désactiver l\'association ?'** - String get phonebookDeactivating; - /// No description provided for @phonebookDeactivatingError. /// /// In fr, this message translates to: @@ -4067,9 +4073,21 @@ abstract class AppLocalizations { /// No description provided for @phonebookDeleteAssociation. /// /// In fr, this message translates to: - /// **'Supprimer l\'association ?\nCela va effacer tout l\'historique de l\'association'** + /// **'Supprimer l\'association'** String get phonebookDeleteAssociation; + /// Permet de supprimer une association + /// + /// In fr, this message translates to: + /// **'Supprimer l\'association {association} ?'** + String phonebookDeleteSelectedAssociation(String association); + + /// No description provided for @phonebookDeleteAssociationDescription. + /// + /// In fr, this message translates to: + /// **'Ceci va supprimer l\'historique de l\'association'** + String get phonebookDeleteAssociationDescription; + /// No description provided for @phonebookDeletedAssociation. /// /// In fr, this message translates to: @@ -4082,6 +4100,18 @@ abstract class AppLocalizations { /// **'Membre supprimé'** String get phonebookDeletedMember; + /// No description provided for @phonebookDeleteRole. + /// + /// In fr, this message translates to: + /// **'Supprimer le rôle'** + String get phonebookDeleteRole; + + /// Permet de supprimer le rôle d'un utilisateur dans une association + /// + /// In fr, this message translates to: + /// **'Supprimer le rôle de l\'utilisateur {name} ?'** + String phonebookDeleteUserRole(String name); + /// No description provided for @phonebookDeleting. /// /// In fr, this message translates to: @@ -4130,11 +4160,11 @@ abstract class AppLocalizations { /// **'Gérer les membres'** String get phonebookEditAssociationMembers; - /// No description provided for @phonebookEditMembership. + /// No description provided for @phonebookEditRole. /// /// In fr, this message translates to: /// **'Modifier le rôle'** - String get phonebookEditMembership; + String get phonebookEditRole; /// No description provided for @phonebookEmail. /// @@ -4250,17 +4280,17 @@ abstract class AppLocalizations { /// **'Gérer les groupes de {association}'** String phonebookGroups(String association); - /// No description provided for @phonebookMandate. + /// Année de mandat d'une association /// /// In fr, this message translates to: - /// **'Mandat'** - String get phonebookMandate; + /// **'Mandat {year}'** + String phonebookTerm(int year); - /// No description provided for @phonebookMandateChangingError. + /// No description provided for @phonebookTermChangingError. /// /// In fr, this message translates to: /// **'Erreur lors du changement de mandat'** - String get phonebookMandateChangingError; + String get phonebookTermChangingError; /// No description provided for @phonebookMember. /// @@ -4322,17 +4352,17 @@ abstract class AppLocalizations { /// **'Nom'** String get phonebookNamePure; - /// No description provided for @phonebookNewMandate. + /// No description provided for @phonebookNewTerm. /// /// In fr, this message translates to: /// **'Nouveau mandat'** - String get phonebookNewMandate; + String get phonebookNewTerm; - /// No description provided for @phonebookNewMandateConfirmed. + /// No description provided for @phonebookNewTermConfirmed. /// /// In fr, this message translates to: /// **'Mandat changé'** - String get phonebookNewMandateConfirmed; + String get phonebookNewTermConfirmed; /// No description provided for @phonebookNickname. /// @@ -4424,11 +4454,11 @@ abstract class AppLocalizations { /// **'Promo non renseignée'** String get phonebookPromoNotGiven; - /// No description provided for @phonebookPromotion. + /// Année de promotion d'un membre /// /// In fr, this message translates to: - /// **'Promotion :'** - String get phonebookPromotion; + /// **'Promotion {year}'** + String phonebookPromotion(int year); /// No description provided for @phonebookReorderingError. /// @@ -4448,11 +4478,11 @@ abstract class AppLocalizations { /// **'Rôle'** String get phonebookRolePure; - /// No description provided for @phonebookSearchMember. + /// No description provided for @phonebookSearchUser. /// /// In fr, this message translates to: - /// **'un membre'** - String get phonebookSearchMember; + /// **'Rechercher un utilisateur'** + String get phonebookSearchUser; /// No description provided for @phonebookTooHeavyAssociationPicture. /// diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 3f6df3a892..fe8cd3d15f 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -1371,7 +1371,12 @@ class AppLocalizationsEn extends AppLocalizations { String get globalCancel => 'Cancel'; @override - String get globalOptionnal => 'Optional'; + String get globalIrreversibleAction => 'This action is irreversible'; + + @override + String globalOptionnal(String text) { + return '$text (Optional)'; + } @override String get homeCalendar => 'Calendar'; @@ -1975,6 +1980,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get phonebookAssociationDetail => 'Association details:'; + @override + String get phonebookAssociationGroupement => 'Association groupement'; + @override String get phonebookAssociationKind => 'Type of association:'; @@ -1988,11 +1996,13 @@ class AppLocalizationsEn extends AppLocalizations { String get phonebookCancel => 'Cancel'; @override - String get phonebookChangeMandate => 'Switch to mandate '; + String phonebookChangeTermYear(int year) { + return 'Switch to $year term'; + } @override - String get phonebookChangeMandateConfirm => - 'Are you sure you want to change the entire mandate?\nThis action is irreversible!'; + String get phonebookChangeTermConfirm => + 'Are you sure you want to change the entire term?\nThis action is irreversible!'; @override String get phonebookConfirm => 'Confirm'; @@ -2001,8 +2011,7 @@ class AppLocalizationsEn extends AppLocalizations { String get phonebookCopied => 'Copied to clipboard'; @override - String get phonebookDeactivateAssociation => - 'Are you sure you want to deactivate this association?\nThis action is irreversible!'; + String get phonebookDeactivateAssociation => 'Deactivate association'; @override String get phonebookDeactivatedAssociation => 'Association deactivated'; @@ -2011,9 +2020,6 @@ class AppLocalizationsEn extends AppLocalizations { String get phonebookDeactivatedAssociationWarning => 'Warning, this association is deactivated, you cannot modify it'; - @override - String get phonebookDeactivating => 'Deactivate the association?'; - @override String get phonebookDeactivatingError => 'Error during deactivation'; @@ -2021,8 +2027,16 @@ class AppLocalizationsEn extends AppLocalizations { String get phonebookDetail => 'Details:'; @override - String get phonebookDeleteAssociation => - 'Delete the association?\nThis will erase all association history'; + String get phonebookDeleteAssociation => 'Delete association'; + + @override + String phonebookDeleteSelectedAssociation(String association) { + return 'Delete the association $association?'; + } + + @override + String get phonebookDeleteAssociationDescription => + 'This will erase all association history'; @override String get phonebookDeletedAssociation => 'Association deleted'; @@ -2030,6 +2044,14 @@ class AppLocalizationsEn extends AppLocalizations { @override String get phonebookDeletedMember => 'Member deleted'; + @override + String get phonebookDeleteRole => 'Delete role'; + + @override + String phonebookDeleteUserRole(String name) { + return 'Delete the role of $name?'; + } + @override String get phonebookDeleting => 'Deleting'; @@ -2056,7 +2078,7 @@ class AppLocalizationsEn extends AppLocalizations { String get phonebookEditAssociationMembers => 'Manage members'; @override - String get phonebookEditMembership => 'Edit role'; + String get phonebookEditRole => 'Edit role'; @override String get phonebookEmail => 'Email:'; @@ -2110,7 +2132,7 @@ class AppLocalizationsEn extends AppLocalizations { @override String get phonebookExistingMembership => - 'This member is already in the current mandate'; + 'This member is already in the current term'; @override String get phonebookFirstname => 'First name:'; @@ -2124,10 +2146,12 @@ class AppLocalizationsEn extends AppLocalizations { } @override - String get phonebookMandate => 'Mandate'; + String phonebookTerm(int year) { + return '$year term'; + } @override - String get phonebookMandateChangingError => 'Error changing mandate'; + String get phonebookTermChangingError => 'Error changing term'; @override String get phonebookMember => 'Member'; @@ -2165,10 +2189,10 @@ class AppLocalizationsEn extends AppLocalizations { String get phonebookNamePure => 'Last name'; @override - String get phonebookNewMandate => 'New mandate'; + String get phonebookNewTerm => 'New term'; @override - String get phonebookNewMandateConfirmed => 'Mandate changed'; + String get phonebookNewTermConfirmed => 'Term changed'; @override String get phonebookNickname => 'Nickname:'; @@ -2216,7 +2240,9 @@ class AppLocalizationsEn extends AppLocalizations { String get phonebookPromoNotGiven => 'Promotion not provided'; @override - String get phonebookPromotion => 'Promotion:'; + String phonebookPromotion(int year) { + return 'Promotion $year'; + } @override String get phonebookReorderingError => 'Error during reordering'; @@ -2228,7 +2254,7 @@ class AppLocalizationsEn extends AppLocalizations { String get phonebookRolePure => 'Role'; @override - String get phonebookSearchMember => 'Search a member'; + String get phonebookSearchUser => 'Search a user'; @override String get phonebookTooHeavyAssociationPicture => diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index fca1fdd6a7..54415b867e 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -1377,7 +1377,12 @@ class AppLocalizationsFr extends AppLocalizations { String get globalCancel => 'Annuler'; @override - String get globalOptionnal => 'Optionnel'; + String get globalIrreversibleAction => 'Cette action est irréversible'; + + @override + String globalOptionnal(String text) { + return '$text (Optionnel)'; + } @override String get homeCalendar => 'Calendrier'; @@ -1984,6 +1989,9 @@ class AppLocalizationsFr extends AppLocalizations { @override String get phonebookAssociationDetail => 'Détail de l\'association :'; + @override + String get phonebookAssociationGroupement => 'Groupement d\'association'; + @override String get phonebookAssociationKind => 'Type d\'association :'; @@ -1997,10 +2005,12 @@ class AppLocalizationsFr extends AppLocalizations { String get phonebookCancel => 'Annuler'; @override - String get phonebookChangeMandate => 'Passer au mandat '; + String phonebookChangeTermYear(int year) { + return 'Passer au mandat $year'; + } @override - String get phonebookChangeMandateConfirm => + String get phonebookChangeTermConfirm => 'Êtes-vous sûr de vouloir changer tout le mandat ?\nCette action est irréversible !'; @override @@ -2010,8 +2020,7 @@ class AppLocalizationsFr extends AppLocalizations { String get phonebookCopied => 'Copié dans le presse-papier'; @override - String get phonebookDeactivateAssociation => - 'Êtes-vous sûr de vouloir désactiver cette association ?\nCette action est irréversible !'; + String get phonebookDeactivateAssociation => 'Désactiver l\'association'; @override String get phonebookDeactivatedAssociation => 'Association désactivée'; @@ -2020,9 +2029,6 @@ class AppLocalizationsFr extends AppLocalizations { String get phonebookDeactivatedAssociationWarning => 'Attention, cette association est désactivée, vous ne pouvez pas la modifier'; - @override - String get phonebookDeactivating => 'Désactiver l\'association ?'; - @override String get phonebookDeactivatingError => 'Erreur lors de la désactivation'; @@ -2030,8 +2036,16 @@ class AppLocalizationsFr extends AppLocalizations { String get phonebookDetail => 'Détail :'; @override - String get phonebookDeleteAssociation => - 'Supprimer l\'association ?\nCela va effacer tout l\'historique de l\'association'; + String get phonebookDeleteAssociation => 'Supprimer l\'association'; + + @override + String phonebookDeleteSelectedAssociation(String association) { + return 'Supprimer l\'association $association ?'; + } + + @override + String get phonebookDeleteAssociationDescription => + 'Ceci va supprimer l\'historique de l\'association'; @override String get phonebookDeletedAssociation => 'Association supprimée'; @@ -2039,6 +2053,14 @@ class AppLocalizationsFr extends AppLocalizations { @override String get phonebookDeletedMember => 'Membre supprimé'; + @override + String get phonebookDeleteRole => 'Supprimer le rôle'; + + @override + String phonebookDeleteUserRole(String name) { + return 'Supprimer le rôle de l\'utilisateur $name ?'; + } + @override String get phonebookDeleting => 'Suppression'; @@ -2065,7 +2087,7 @@ class AppLocalizationsFr extends AppLocalizations { String get phonebookEditAssociationMembers => 'Gérer les membres'; @override - String get phonebookEditMembership => 'Modifier le rôle'; + String get phonebookEditRole => 'Modifier le rôle'; @override String get phonebookEmail => 'Email :'; @@ -2137,10 +2159,12 @@ class AppLocalizationsFr extends AppLocalizations { } @override - String get phonebookMandate => 'Mandat'; + String phonebookTerm(int year) { + return 'Mandat $year'; + } @override - String get phonebookMandateChangingError => + String get phonebookTermChangingError => 'Erreur lors du changement de mandat'; @override @@ -2179,10 +2203,10 @@ class AppLocalizationsFr extends AppLocalizations { String get phonebookNamePure => 'Nom'; @override - String get phonebookNewMandate => 'Nouveau mandat'; + String get phonebookNewTerm => 'Nouveau mandat'; @override - String get phonebookNewMandateConfirmed => 'Mandat changé'; + String get phonebookNewTermConfirmed => 'Mandat changé'; @override String get phonebookNickname => 'Surnom :'; @@ -2230,7 +2254,9 @@ class AppLocalizationsFr extends AppLocalizations { String get phonebookPromoNotGiven => 'Promo non renseignée'; @override - String get phonebookPromotion => 'Promotion :'; + String phonebookPromotion(int year) { + return 'Promotion $year'; + } @override String get phonebookReorderingError => 'Erreur lors du réordonnement'; @@ -2242,7 +2268,7 @@ class AppLocalizationsFr extends AppLocalizations { String get phonebookRolePure => 'Rôle'; @override - String get phonebookSearchMember => 'un membre'; + String get phonebookSearchUser => 'Rechercher un utilisateur'; @override String get phonebookTooHeavyAssociationPicture => diff --git a/lib/phonebook/class/complete_member.dart b/lib/phonebook/class/complete_member.dart index 666c3b0f88..ca48db888a 100644 --- a/lib/phonebook/class/complete_member.dart +++ b/lib/phonebook/class/complete_member.dart @@ -60,4 +60,8 @@ class CompleteMember { .firstWhere((element) => element.associationId == associationId) .rolesTags; } + + String getName() { + return "${member.firstname} ${member.name}"; + } } diff --git a/lib/phonebook/ui/pages/admin_page/admin_page.dart b/lib/phonebook/ui/pages/admin_page/admin_page.dart index 8abc0215fd..7c3bb63e09 100644 --- a/lib/phonebook/ui/pages/admin_page/admin_page.dart +++ b/lib/phonebook/ui/pages/admin_page/admin_page.dart @@ -49,7 +49,7 @@ class AdminPage extends HookConsumerWidget { await roleNotifier.loadRolesTags(); }, child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), child: Column( children: [ AssociationResearchBar(), @@ -62,7 +62,7 @@ class AdminPage extends HookConsumerWidget { return Column( children: [ ListItemTemplate( - title: "Ajouter une association", + title: localizeWithContext.phonebookAddAssociation, icon: HeroIcon( HeroIcons.plus, size: 40, diff --git a/lib/phonebook/ui/pages/admin_page/association_admin_edition_modal.dart b/lib/phonebook/ui/pages/admin_page/association_admin_edition_modal.dart index 2916cd53bf..113624c7c0 100644 --- a/lib/phonebook/ui/pages/admin_page/association_admin_edition_modal.dart +++ b/lib/phonebook/ui/pages/admin_page/association_admin_edition_modal.dart @@ -50,7 +50,7 @@ class AssociationAdminEditionModal extends HookConsumerWidget { child: Column( children: [ Button( - text: "Modifier les informations", + text: localizeWithContext.phonebookEditAssociationInfo, onPressed: () { associationPictureNotifier.getAssociationPicture( association.id, @@ -70,7 +70,7 @@ class AssociationAdminEditionModal extends HookConsumerWidget { if (isAdmin) ...[ SizedBox(height: 5), Button( - text: "Gérer les groupes", + text: localizeWithContext.phonebookEditAssociationGroups, onPressed: () { associationGroupementsNotifier.setAssociationGroupement( groupement, @@ -87,7 +87,7 @@ class AssociationAdminEditionModal extends HookConsumerWidget { ], SizedBox(height: 5), Button( - text: "Gérer les membres", + text: localizeWithContext.phonebookEditAssociationMembers, onPressed: () { associationGroupementsNotifier.setAssociationGroupement( groupement, @@ -103,15 +103,19 @@ class AssociationAdminEditionModal extends HookConsumerWidget { ), SizedBox(height: 15), Button.danger( - text: "Passer au mandat ${association.mandateYear + 1}", + text: localizeWithContext.phonebookChangeTermYear( + association.mandateYear + 1, + ), onPressed: () { Navigator.of(context).pop(); - showCustomDialog( + showCustomBottomModal( context: context, ref: ref, - dialog: CustomDialogBox.danger( - title: "Passer au mandat ${association.mandateYear + 1}", - description: "Cette action est irréversible", + modal: CustomDialogBox.danger( + title: localizeWithContext.phonebookChangeTermYear( + association.mandateYear + 1, + ), + description: localizeWithContext.globalIrreversibleAction, onYes: () async { final result = await associationListNotifier .updateAssociation( @@ -138,8 +142,8 @@ class AssociationAdminEditionModal extends HookConsumerWidget { SizedBox(height: 5), Button.danger( text: association.deactivated - ? "Supprimer l'association" - : "Désactiver l'association", + ? localizeWithContext.phonebookDeleteAssociation + : localizeWithContext.phonebookDeactivateAssociation, onPressed: () async { Navigator.of(context).pop(); if (!association.deactivated) { @@ -157,12 +161,14 @@ class AssociationAdminEditionModal extends HookConsumerWidget { ); } } else { - showCustomDialog( + showCustomBottomModal( context: context, ref: ref, - dialog: CustomDialogBox.danger( - title: "Supprimer l'association", - description: "Cette action est irréversible", + modal: CustomDialogBox.danger( + title: localizeWithContext + .phonebookDeleteSelectedAssociation(association.name), + description: localizeWithContext + .phonebookDeleteAssociationDescription, onYes: () async { final result = await associationListNotifier .deleteAssociation(association); diff --git a/lib/phonebook/ui/pages/association_add_edit_page/association_add_edit_page.dart b/lib/phonebook/ui/pages/association_add_edit_page/association_add_edit_page.dart index f13c154bdc..6061c6ac1d 100644 --- a/lib/phonebook/ui/pages/association_add_edit_page/association_add_edit_page.dart +++ b/lib/phonebook/ui/pages/association_add_edit_page/association_add_edit_page.dart @@ -18,6 +18,7 @@ import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/styleguide/button.dart'; import 'package:titan/tools/ui/styleguide/text_entry.dart'; +import 'package:titan/tools/ui/widgets/align_left_text.dart'; class AssociationAddEditPage extends HookConsumerWidget { final scrollKey = GlobalKey(); @@ -47,7 +48,7 @@ class AssociationAddEditPage extends HookConsumerWidget { ).showSnackBar(SnackBar(content: Text(message))); } - AppLocalizations localizeWithContext = AppLocalizations.of(context)!; + final localizeWithContext = AppLocalizations.of(context)!; return PhonebookTemplate( child: SingleChildScrollView( @@ -175,7 +176,13 @@ class AssociationAddEditPage extends HookConsumerWidget { }, ), ], - const SizedBox(height: 30), + const SizedBox(height: 20), + AlignLeftText( + localizeWithContext.phonebookAssociationGroupement, + fontWeight: FontWeight.normal, + fontSize: 16, + ), + const SizedBox(height: 5), AssociationGroupementBar(editable: true), Container(margin: const EdgeInsets.symmetric(vertical: 10)), TextEntry( @@ -183,7 +190,7 @@ class AssociationAddEditPage extends HookConsumerWidget { label: localizeWithContext.phonebookAssociationName, canBeEmpty: false, ), - const SizedBox(height: 30), + const SizedBox(height: 10), TextEntry( controller: description, label: localizeWithContext.phonebookDescription, @@ -252,7 +259,13 @@ class AssociationAddEditPage extends HookConsumerWidget { showSnackBarWithContext( localizeWithContext.phonebookUpdatedAssociation, ); - + associationNotifier.setAssociation( + association.copyWith( + name: name.text, + description: description.text, + groupementId: associationGroupement.id, + ), + ); associationGroupementNotifier .resetAssociationGroupement(); QR.back(); diff --git a/lib/phonebook/ui/pages/association_groups_page/association_groups_page.dart b/lib/phonebook/ui/pages/association_groups_page/association_groups_page.dart index 7e06c293c6..542db482c7 100644 --- a/lib/phonebook/ui/pages/association_groups_page/association_groups_page.dart +++ b/lib/phonebook/ui/pages/association_groups_page/association_groups_page.dart @@ -66,7 +66,9 @@ class AssociationGroupsPage extends HookConsumerWidget { children: [ AlignLeftText( localizeWithContext.phonebookGroups(association.name), + fontSize: 20, ), + const SizedBox(height: 20), AsyncChild( value: groups, builder: (context, groupList) { @@ -91,6 +93,7 @@ class AssociationGroupsPage extends HookConsumerWidget { ); }, ), + const SizedBox(height: 20), Button( onPressed: () async { await tokenExpireWrapper(ref, () async { diff --git a/lib/phonebook/ui/pages/association_members_page/association_members_page.dart b/lib/phonebook/ui/pages/association_members_page/association_members_page.dart index 7075ea6263..2521e19577 100644 --- a/lib/phonebook/ui/pages/association_members_page/association_members_page.dart +++ b/lib/phonebook/ui/pages/association_members_page/association_members_page.dart @@ -58,16 +58,17 @@ class AssociationMembersPage extends HookConsumerWidget { children: [ AlignLeftText( localizeWithContext.phonebookMembers(association.name), + fontSize: 20, ), if (!association.deactivated) ...[ - SizedBox(height: 10), + SizedBox(height: 20), ListItemTemplate( icon: const HeroIcon( HeroIcons.plus, size: 40, color: Colors.black, ), - title: "Ajouter", + title: localizeWithContext.phonebookAddMember, trailing: SizedBox.shrink(), onTap: () async { completeMemberNotifier.setCompleteMember( diff --git a/lib/phonebook/ui/pages/association_members_page/member_edition_modal.dart b/lib/phonebook/ui/pages/association_members_page/member_edition_modal.dart index 0b9e6b862d..d195d56d81 100644 --- a/lib/phonebook/ui/pages/association_members_page/member_edition_modal.dart +++ b/lib/phonebook/ui/pages/association_members_page/member_edition_modal.dart @@ -40,14 +40,16 @@ class MemberEditionModal extends HookConsumerWidget { AppLocalizations localizeWithContext = AppLocalizations.of(context)!; return BottomModalTemplate( - title: "title", + title: + "${member.member.nickname ?? '${member.member.firstname} ${member.member.name}'} - ${membership.apparentName}", type: BottomModalType.main, child: SingleChildScrollView( child: Column( children: [ Button( - text: "Modifier le rôle", + text: localizeWithContext.phonebookEditRole, onPressed: () { + Navigator.of(context).pop(); completeMemberNotifier.setCompleteMember(member); membershipNotifier.setMembership(membership); if (QR.currentPath.contains(PhonebookRouter.admin)) { @@ -69,16 +71,17 @@ class MemberEditionModal extends HookConsumerWidget { ), SizedBox(height: 5), Button.danger( - text: "Supprimer le rôle", + text: localizeWithContext.phonebookDeleteRole, onPressed: () { Navigator.of(context).pop(); - showCustomDialog( + showCustomBottomModal( context: context, ref: ref, - dialog: CustomDialogBox.danger( - title: - "Supprimer le rôle de ${member.member.nickname ?? '${member.member.firstname} ${member.member.name}'}", - description: "Cette action est irréversible", + modal: CustomDialogBox.danger( + title: localizeWithContext.phonebookDeleteUserRole( + member.member.nickname ?? member.getName(), + ), + description: localizeWithContext.globalIrreversibleAction, onYes: () async { final result = await associationMemberListNotifier .deleteMember( diff --git a/lib/phonebook/ui/pages/association_page/association_page.dart b/lib/phonebook/ui/pages/association_page/association_page.dart index 7c5b0d3f0a..038daf1c94 100644 --- a/lib/phonebook/ui/pages/association_page/association_page.dart +++ b/lib/phonebook/ui/pages/association_page/association_page.dart @@ -1,4 +1,3 @@ -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -10,7 +9,6 @@ import 'package:titan/phonebook/providers/association_member_list_provider.dart' import 'package:titan/phonebook/providers/phonebook_admin_provider.dart'; import 'package:titan/phonebook/ui/components/member_card.dart'; import 'package:titan/phonebook/ui/pages/association_page/association_edition_modal.dart'; -import 'package:titan/phonebook/ui/pages/association_page/web_member_card.dart'; import 'package:titan/phonebook/ui/phonebook.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; @@ -103,7 +101,7 @@ class AssociationPage extends HookConsumerWidget { ), const SizedBox(height: 10), Text( - "${localizeWithContext.phonebookMandate} ${association.mandateYear}", + localizeWithContext.phonebookTerm(association.mandateYear), style: const TextStyle(fontSize: 15, color: Colors.black), ), const SizedBox(height: 10), @@ -120,16 +118,11 @@ class AssociationPage extends HookConsumerWidget { : Column( children: associationMemberSortedList .map( - (member) => kIsWeb - ? WebMemberCard( - member: member, - association: association, - ) - : MemberCard( - member: member, - association: association, - deactivated: false, - ), + (member) => MemberCard( + member: member, + association: association, + deactivated: false, + ), ) .toList(), ), diff --git a/lib/phonebook/ui/pages/association_page/card_field.dart b/lib/phonebook/ui/pages/association_page/card_field.dart deleted file mode 100644 index 84f38b2b67..0000000000 --- a/lib/phonebook/ui/pages/association_page/card_field.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:titan/phonebook/ui/components/copiabled_text.dart'; - -class CardField extends StatelessWidget { - final String label; - final String value; - final bool showLabel; - const CardField({ - super.key, - required this.label, - required this.value, - this.showLabel = true, - }); - - @override - Widget build(BuildContext context) { - return Container( - margin: const EdgeInsets.symmetric(vertical: 5.0, horizontal: 10), - child: Center( - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - if (showLabel) ...[ - Text(label, style: const TextStyle(fontSize: 16)), - const SizedBox(width: 5), - ], - CopiabledText( - value, - maxLines: 1, - style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16), - ), - ], - ), - ), - ); - } -} diff --git a/lib/phonebook/ui/pages/association_page/web_member_card.dart b/lib/phonebook/ui/pages/association_page/web_member_card.dart deleted file mode 100644 index b2b2c3012d..0000000000 --- a/lib/phonebook/ui/pages/association_page/web_member_card.dart +++ /dev/null @@ -1,271 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/phonebook/class/association.dart'; -import 'package:titan/phonebook/class/complete_member.dart'; -import 'package:titan/phonebook/class/membership.dart'; -import 'package:titan/phonebook/providers/member_pictures_provider.dart'; -import 'package:titan/phonebook/providers/profile_picture_provider.dart'; -import 'package:titan/phonebook/providers/complete_member_provider.dart'; -import 'package:titan/phonebook/router.dart'; -import 'package:titan/phonebook/tools/function.dart'; -import 'package:titan/phonebook/ui/pages/association_page/card_field.dart'; -import 'package:titan/tools/ui/builders/auto_loader_child.dart'; -import 'package:titan/tools/ui/layouts/card_layout.dart'; -import 'package:qlevar_router/qlevar_router.dart'; -import 'package:titan/l10n/app_localizations.dart'; - -class WebMemberCard extends HookConsumerWidget { - const WebMemberCard({ - super.key, - required this.member, - required this.association, - }); - - final CompleteMember member; - final Association association; - - @override - Widget build(BuildContext context, WidgetRef ref) { - final memberNotifier = ref.watch(completeMemberProvider.notifier); - - final memberPictures = ref.watch( - memberPicturesProvider.select((value) => value[member]), - ); - final memberPicturesNotifier = ref.watch(memberPicturesProvider.notifier); - final profilePictureNotifier = ref.watch(profilePictureProvider.notifier); - - Membership assoMembership = getMembershipForAssociation( - member, - association, - ); - - final localizeWithContext = AppLocalizations.of(context)!; - - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 5), - child: GestureDetector( - onTap: () { - memberNotifier.setCompleteMember(member); - QR.to(PhonebookRouter.root + PhonebookRouter.memberDetail); - }, - child: CardLayout( - color: getColorFromTagList(ref, assoMembership.rolesTags), - margin: EdgeInsets.zero, - child: LayoutBuilder( - builder: (context, constraints) { - if (constraints.maxWidth > 700) { - return Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Container( - decoration: BoxDecoration( - shape: BoxShape.circle, - boxShadow: [ - BoxShadow( - color: Colors.black.withValues(alpha: 0.1), - spreadRadius: 5, - blurRadius: 10, - offset: const Offset(2, 3), - ), - ], - ), - child: AutoLoaderChild( - group: memberPictures, - notifier: memberPicturesNotifier, - mapKey: member, - loader: (ref) => profilePictureNotifier - .getProfilePicture(member.member.id), - loadingBuilder: (context) => const CircleAvatar( - radius: 40, - child: CircularProgressIndicator(), - ), - dataBuilder: (context, data) => CircleAvatar( - radius: 40, - backgroundImage: data.first.image, - ), - ), - ), - const SizedBox(width: 10), - Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceAround, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (member.member.nickname != null) ...[ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SelectableText( - member.member.nickname!, - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 20, - ), - ), - const SizedBox(width: 10), - SelectableText( - "(${member.member.name} ${member.member.firstname})", - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 20, - color: Color.fromARGB(255, 115, 115, 115), - ), - ), - ], - ), - ] else - SelectableText( - "${member.member.name} ${member.member.firstname}", - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 20, - ), - ), - const SizedBox(height: 5), - SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - CardField( - label: localizeWithContext.phonebookPromotion, - value: member.member.promotion == 0 - ? localizeWithContext - .phonebookPromoNotGiven - : member.member.promotion < 100 - ? "20${member.member.promotion}" - : member.member.promotion.toString(), - ), - CardField( - label: localizeWithContext.phonebookEmail, - value: member.member.email, - ), - if (member.member.phone != null) - CardField( - label: localizeWithContext.phonebookPhone, - value: member.member.phone!, - ), - ], - ), - ), - ], - ), - ), - Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - assoMembership.apparentName, - textAlign: TextAlign.right, - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 20, - ), - ), - ], - ), - ], - ); - } - return Column( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Column( - mainAxisAlignment: MainAxisAlignment.spaceAround, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - if (member.member.nickname != null) ...[ - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SelectableText( - member.member.nickname!, - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 20, - ), - ), - const SizedBox(width: 10), - SelectableText( - "(${member.member.name} ${member.member.firstname})", - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 20, - color: Color.fromARGB(255, 115, 115, 115), - ), - ), - ], - ), - ] else - SelectableText( - "${member.member.name} ${member.member.firstname}", - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 20, - ), - ), - const SizedBox(height: 5), - CardField( - label: localizeWithContext.phonebookPromotion, - value: member.member.promotion == 0 - ? localizeWithContext.phonebookPromoNotGiven - : member.member.promotion < 100 - ? "20${member.member.promotion}" - : member.member.promotion.toString(), - ), - if (constraints.maxWidth > 500) - SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Row( - children: [ - CardField( - label: localizeWithContext.phonebookEmail, - value: member.member.email, - ), - if (member.member.phone != null) - CardField( - label: localizeWithContext.phonebookPhone, - value: member.member.phone!, - ), - ], - ), - ) - else - Column( - children: [ - CardField( - label: localizeWithContext.phonebookEmail, - value: member.member.email, - showLabel: false, - ), - if (member.member.phone != null) - CardField( - label: localizeWithContext.phonebookPhone, - value: member.member.phone!, - showLabel: false, - ), - ], - ), - ], - ), - Column( - children: [ - Text( - assoMembership.apparentName, - textAlign: TextAlign.right, - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 20, - ), - ), - ], - ), - ], - ); - }, - ), - ), - ), - ); - } -} diff --git a/lib/phonebook/ui/pages/member_detail_page/member_detail_page.dart b/lib/phonebook/ui/pages/member_detail_page/member_detail_page.dart index 28e04d41e3..04cc82f505 100644 --- a/lib/phonebook/ui/pages/member_detail_page/member_detail_page.dart +++ b/lib/phonebook/ui/pages/member_detail_page/member_detail_page.dart @@ -62,9 +62,9 @@ class MemberDetailPage extends HookConsumerWidget { fontWeight: FontWeight.bold, ), ), - const SizedBox(height: 10), + const SizedBox(height: 5), Text( - "${member.member.firstname} ${member.member.name}", + member.getName(), style: const TextStyle( fontSize: 20, fontWeight: FontWeight.bold, @@ -72,27 +72,28 @@ class MemberDetailPage extends HookConsumerWidget { ), ] else Text( - "${member.member.firstname} ${member.member.name}", + member.getName(), style: const TextStyle( fontSize: 24, fontWeight: FontWeight.bold, ), ), - const SizedBox(height: 10), + const SizedBox(height: 5), if (member.member.promotion != 0) Text( - "${localizeWithContext.phonebookPromotion} ${member.member.promotion < 100 ? '20${member.member.promotion}' : member.member.promotion.toString()}", - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, + localizeWithContext.phonebookPromotion( + member.member.promotion < 100 + ? member.member.promotion + 2000 + : member.member.promotion, ), + style: const TextStyle(fontSize: 16), ), const SizedBox(height: 20), Text( member.member.email, style: const TextStyle(fontSize: 16), ), - const SizedBox(height: 10), + const SizedBox(height: 5), if (member.member.phone != null) Text( member.member.phone!, diff --git a/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart b/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart index e4d7aa484c..27aae51206 100644 --- a/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart +++ b/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart @@ -51,7 +51,6 @@ class MembershipEditorPage extends HookConsumerWidget { final localizeWithContext = AppLocalizations.of(context)!; Future addMember() async { - // Test if the membership already exists with (association_id,member_id,mandate_year) final memberAssociationMemberships = member.memberships.where( (membership) => membership.associationId == association.id, ); @@ -144,11 +143,11 @@ class MembershipEditorPage extends HookConsumerWidget { AlignLeftText(localizeWithContext.phonebookAddMember), ListItemTemplate( title: member.member.id == "" - ? localizeWithContext.phonebookSearchMember + ? localizeWithContext.phonebookSearchUser : member.member.getName(), subtitle: member.member.id == "" ? "" - : localizeWithContext.phonebookSearchMember, + : localizeWithContext.phonebookSearchUser, trailing: SizedBox.shrink(), onTap: () => showCustomBottomModal( context: context, @@ -157,14 +156,11 @@ class MembershipEditorPage extends HookConsumerWidget { ), ), ] else - Text( + AlignLeftText( localizeWithContext.phonebookModifyMembership( - member.member.getName(), - ), - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, + member.member.nickname ?? member.getName(), ), + fontSize: 18, ), const SizedBox(height: 10), rolesTagList.maybeWhen( diff --git a/lib/phonebook/ui/pages/membership_editor_page/user_search_modal.dart b/lib/phonebook/ui/pages/membership_editor_page/user_search_modal.dart index 2f60d86e58..621c6eaa23 100644 --- a/lib/phonebook/ui/pages/membership_editor_page/user_search_modal.dart +++ b/lib/phonebook/ui/pages/membership_editor_page/user_search_modal.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/phonebook/ui/pages/membership_editor_page/search_result.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; @@ -15,8 +16,10 @@ class UserSearchModal extends HookConsumerWidget { final usersNotifier = ref.watch(userList.notifier); final textController = useTextEditingController(); + final localizeWithContext = AppLocalizations.of(context)!; + return BottomModalTemplate( - title: "title", + title: localizeWithContext.phonebookSearchUser, type: BottomModalType.main, child: SingleChildScrollView( child: Column( diff --git a/lib/phonebook/ui/phonebook.dart b/lib/phonebook/ui/phonebook.dart index 4ca251ad0c..628d3c1a43 100644 --- a/lib/phonebook/ui/phonebook.dart +++ b/lib/phonebook/ui/phonebook.dart @@ -22,6 +22,12 @@ class PhonebookTemplate extends HookConsumerWidget { PhonebookRouter.root + PhonebookRouter.admin + PhonebookRouter.addEditAssociation, + PhonebookRouter.root + + PhonebookRouter.admin + + PhonebookRouter.editAssociationGroups, + PhonebookRouter.root + + PhonebookRouter.admin + + PhonebookRouter.editAssociationMembers, ]; return Container( diff --git a/lib/tools/ui/styleguide/custom_dialog_box.dart b/lib/tools/ui/styleguide/custom_dialog_box.dart index f87ef7bf01..272c9f5f88 100644 --- a/lib/tools/ui/styleguide/custom_dialog_box.dart +++ b/lib/tools/ui/styleguide/custom_dialog_box.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/navigation/providers/navbar_animation.dart'; -import 'package:titan/tools/constants.dart'; +import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; import 'package:titan/tools/ui/styleguide/button.dart'; const double padding = 20.0; @@ -40,84 +40,43 @@ class CustomDialogBox extends StatelessWidget { @override Widget build(BuildContext context) { AppLocalizations localizeWithContext = AppLocalizations.of(context)!; - return GestureDetector( - onTap: () { - Navigator.of(context).pop(); - }, - child: Dialog( - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(20.0)), - ), - elevation: 0.0, - insetPadding: const EdgeInsets.all(20.0), - backgroundColor: type == ModalType.main - ? ColorConstants.background - : ColorConstants.main, - child: Padding( - padding: const EdgeInsets.all(padding), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text( - title, - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.w900, - color: type == ModalType.main - ? ColorConstants.tertiary - : ColorConstants.background, - ), - ), - SizedBox(height: 20), - Text( - description, - style: TextStyle( - fontSize: 15, - color: type == ModalType.main - ? ColorConstants.tertiary - : ColorConstants.background, - ), + return BottomModalTemplate.danger( + title: title, + description: description, + actions: [ + Row( + children: [ + Expanded( + child: Button( + text: noText ?? localizeWithContext.globalCancel, + onPressed: () { + Navigator.of(context).pop(); + if (onNo != null) { + onNo!(); + } + }, + type: ButtonType.secondary, + fontSize: 18, ), - SizedBox(height: 20), - SizedBox( - width: 270, - child: Row( - children: [ - Expanded( - child: Button( - text: noText ?? localizeWithContext.globalCancel, - onPressed: () { - Navigator.of(context).pop(); - if (onNo != null) { - onNo!(); - } - }, - type: ButtonType.secondary, - fontSize: 18, - ), - ), - SizedBox(width: 10), - Expanded( - child: Button( - text: yesText ?? localizeWithContext.globalConfirm, - onPressed: () { - Navigator.of(context).pop(); - onYes(); - }, - type: type == ModalType.main - ? ButtonType.main - : ButtonType.onDanger, - fontSize: 18, - ), - ), - ], - ), + ), + SizedBox(width: 10), + Expanded( + child: Button( + text: yesText ?? localizeWithContext.globalConfirm, + onPressed: () { + Navigator.of(context).pop(); + onYes(); + }, + type: type == ModalType.main + ? ButtonType.main + : ButtonType.onDanger, + fontSize: 18, ), - ], - ), + ), + ], ), - ), + ], + child: SizedBox.shrink(), ); } } diff --git a/lib/tools/ui/styleguide/text_entry.dart b/lib/tools/ui/styleguide/text_entry.dart index 74209045a3..7749c00826 100644 --- a/lib/tools/ui/styleguide/text_entry.dart +++ b/lib/tools/ui/styleguide/text_entry.dart @@ -57,9 +57,7 @@ class TextEntry extends StatelessWidget { enabled: enabled, decoration: InputDecoration( label: Text( - canBeEmpty - ? '$label (${localizeWithContext.globalOptionnal})' - : label, + canBeEmpty ? localizeWithContext.globalOptionnal(label) : label, style: TextStyle(color: color, height: 0.5), ), suffix: suffixIcon == null && suffix.isEmpty From ac8f49c12116406a1c926b6cc61409f4c528c33c Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 9 Aug 2025 18:15:08 +0200 Subject: [PATCH 114/473] rename key --- lib/settings/tools/functions.dart | 2 +- lib/settings/ui/pages/main_page/main_page.dart | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/settings/tools/functions.dart b/lib/settings/tools/functions.dart index eebaa336d1..47bf664eed 100644 --- a/lib/settings/tools/functions.dart +++ b/lib/settings/tools/functions.dart @@ -32,7 +32,7 @@ Map> groupNotificationTopicsByModuleRoot( }); if (singleTopics.isNotEmpty) { - result[""] = singleTopics; + result["others"] = singleTopics; } return result; diff --git a/lib/settings/ui/pages/main_page/main_page.dart b/lib/settings/ui/pages/main_page/main_page.dart index d3c06c09c3..f6ad0ad70b 100644 --- a/lib/settings/ui/pages/main_page/main_page.dart +++ b/lib/settings/ui/pages/main_page/main_page.dart @@ -197,10 +197,11 @@ class SettingsMainPage extends HookConsumerWidget { context, ); final uniqueTopics = - notificationTopicsByModuleRoot[''] ?? []; + notificationTopicsByModuleRoot['others'] ?? + []; final groupedTopics = Map.from( notificationTopicsByModuleRoot, - )..remove(''); + )..remove('others'); return Column( children: [ ...uniqueTopics.map( From 241cb2f49d5025e17efd7ac7f785f530f70b5c75 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 9 Aug 2025 18:15:18 +0200 Subject: [PATCH 115/473] remove obscure --- lib/tools/ui/styleguide/text_entry.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/tools/ui/styleguide/text_entry.dart b/lib/tools/ui/styleguide/text_entry.dart index dd0b1c02c4..14101c80be 100644 --- a/lib/tools/ui/styleguide/text_entry.dart +++ b/lib/tools/ui/styleguide/text_entry.dart @@ -14,7 +14,6 @@ class TextEntry extends StatelessWidget { final String? Function(String)? validator; final int? minLines, maxLines; final TextInputAction textInputAction; - final bool obscureText; const TextEntry({ super.key, @@ -38,7 +37,6 @@ class TextEntry extends StatelessWidget { this.suffixIcon, this.isNegative = false, this.textInputAction = TextInputAction.next, - this.obscureText = false, }); @override @@ -50,7 +48,6 @@ class TextEntry extends StatelessWidget { keyboardType: keyboardType, cursorColor: color, onChanged: onChanged, - obscureText: obscureText, textInputAction: (keyboardType == TextInputType.multiline) ? TextInputAction.newline : textInputAction, From e48bfa1438db7704c701d3dd623f02e5cd2960e0 Mon Sep 17 00:00:00 2001 From: Thonyk Date: Sat, 9 Aug 2025 18:18:19 +0200 Subject: [PATCH 116/473] fix: clean up --- lib/phonebook/tools/function.dart | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/lib/phonebook/tools/function.dart b/lib/phonebook/tools/function.dart index 99de7eade0..0b0d535fcd 100644 --- a/lib/phonebook/tools/function.dart +++ b/lib/phonebook/tools/function.dart @@ -1,12 +1,8 @@ -import 'package:collection/collection.dart'; import 'package:diacritic/diacritic.dart'; -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/phonebook/class/association.dart'; import 'package:titan/phonebook/class/association_groupement.dart'; import 'package:titan/phonebook/class/complete_member.dart'; import 'package:titan/phonebook/class/membership.dart'; -import 'package:titan/phonebook/providers/roles_tags_provider.dart'; Membership getMembershipForAssociation( CompleteMember member, @@ -55,25 +51,3 @@ List sortedAssociationByKind( // Flatten the sorted map values into a single list return sortedByGroupement.values.expand((list) => list).toList(); } - -Color getColorFromTagList(WidgetRef ref, List tags) { - if (tags.isEmpty) { - return Colors.white; - } - final rolesTags = ref.watch(rolesTagsProvider); - return rolesTags.maybeWhen( - data: (allTags) { - int index = tags.map((tag) => allTags.indexOf(tag)).toList().min; - switch (index) { - case 0: - return const Color.fromARGB(255, 251, 109, 16); - case 1: - return const Color.fromARGB(255, 252, 145, 74); - case 2: - return const Color.fromARGB(255, 253, 193, 153); - } - return Colors.white; - }, - orElse: () => Colors.white, - ); -} From a32f4b6225d9a6ed56c8acfd07f4791c5ce33aae Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 9 Aug 2025 19:04:57 +0200 Subject: [PATCH 117/473] fix controller --- .../providers/edit_user_provider.dart | 40 ------------------ .../ui/pages/main_page/edit_profile.dart | 42 ++++++++++--------- 2 files changed, 22 insertions(+), 60 deletions(-) delete mode 100644 lib/settings/providers/edit_user_provider.dart diff --git a/lib/settings/providers/edit_user_provider.dart b/lib/settings/providers/edit_user_provider.dart deleted file mode 100644 index f85214baf9..0000000000 --- a/lib/settings/providers/edit_user_provider.dart +++ /dev/null @@ -1,40 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/tools/functions.dart'; -import 'package:titan/user/providers/user_provider.dart'; - -class TextControllersNotifier - extends StateNotifier> { - final List list; - TextControllersNotifier(this.list) : super([]) { - state = list; - } - - void disposeAll() { - for (final controller in state) { - controller.dispose(); - } - state = []; - } - - void updateText(int index, String text) { - if (index < 0 || index >= state.length) return; - state[index].text = text; - state = [...state]; - } -} - -final textControllersProvider = - StateNotifierProvider>( - (ref) { - final me = ref.watch(userProvider); - final list = [ - TextEditingController(text: me.email), - TextEditingController(text: me.phone ?? ""), - TextEditingController( - text: me.birthday != null ? processDate(me.birthday!) : "", - ), - ]; - return TextControllersNotifier(list); - }, - ); diff --git a/lib/settings/ui/pages/main_page/edit_profile.dart b/lib/settings/ui/pages/main_page/edit_profile.dart index 0f0776c795..18dfdd435f 100644 --- a/lib/settings/ui/pages/main_page/edit_profile.dart +++ b/lib/settings/ui/pages/main_page/edit_profile.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:image_picker/image_picker.dart'; import 'package:titan/l10n/app_localizations.dart'; -import 'package:titan/settings/providers/edit_user_provider.dart'; import 'package:titan/settings/ui/pages/main_page/picture_button.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; @@ -14,7 +14,7 @@ import 'package:titan/tools/ui/styleguide/text_entry.dart'; import 'package:titan/user/providers/profile_picture_provider.dart'; import 'package:titan/user/providers/user_provider.dart'; -class EditProfile extends ConsumerWidget { +class EditProfile extends HookConsumerWidget { const EditProfile({super.key}); @override @@ -27,8 +27,13 @@ class EditProfile extends ConsumerWidget { displayToast(context, type, msg); } - final textControllers = ref.watch(textControllersProvider); - final textControllersNotifier = ref.watch(textControllersProvider.notifier); + final emailController = useTextEditingController(text: me.email); + final phoneController = useTextEditingController(text: me.phone ?? ''); + final birthdayController = useTextEditingController( + text: me.birthday != null + ? "${me.birthday!.day.toString().padLeft(2, '0')}/${me.birthday!.month.toString().padLeft(2, '0')}/${me.birthday!.year}" + : '', + ); MediaQuery.of(context).viewInsets.bottom; @@ -167,17 +172,14 @@ class EditProfile extends ConsumerWidget { SizedBox(height: View.of(context).viewInsets.bottom == 0 ? 30 : 10), TextEntry( label: localizeWithContext.settingsEmail, - controller: textControllers[0], + controller: emailController, enabled: false, ), SizedBox(height: 20), TextEntry( label: localizeWithContext.settingsPhoneNumber, - controller: textControllers[1], + controller: phoneController, textInputAction: TextInputAction.done, - onChanged: (value) { - textControllersNotifier.updateText(1, value); - }, ), SizedBox(height: 20), Row( @@ -185,7 +187,7 @@ class EditProfile extends ConsumerWidget { Expanded( child: TextEntry( label: localizeWithContext.settingsBirthday, - controller: textControllers[2], + controller: birthdayController, enabled: false, ), ), @@ -199,9 +201,8 @@ class EditProfile extends ConsumerWidget { lastDate: DateTime.now(), ); if (date != null) { - final formattedDate = + birthdayController.text = "${date.day.toString().padLeft(2, '0')}/${date.month.toString().padLeft(2, '0')}/${date.year}"; - textControllersNotifier.updateText(2, formattedDate); } }, ), @@ -211,21 +212,22 @@ class EditProfile extends ConsumerWidget { Button( text: localizeWithContext.settingsValidate, disabled: - !(textControllers[1].value.text != me.phone || - textControllers[2].value.text.isNotEmpty), + !(phoneController.value.text != me.phone || + birthdayController.value.text != + "${me.birthday!.day.toString().padLeft(2, '0')}/${me.birthday!.month.toString().padLeft(2, '0')}/${me.birthday!.year}"), onPressed: () async { - if (textControllers[1].value.text != me.phone || - textControllers[2].value.text.isNotEmpty) { + if (phoneController.value.text != me.phone || + birthdayController.value.text.isNotEmpty) { await tokenExpireWrapper(ref, () async { final newMe = me.copyWith( - birthday: textControllers[2].value.text.isNotEmpty + birthday: birthdayController.value.text.isNotEmpty ? DateTime.parse( - processDateBack(textControllers[2].value.text), + processDateBack(birthdayController.value.text), ) : null, - phone: textControllers[1].value.text.isEmpty + phone: phoneController.value.text.isEmpty ? null - : textControllers[1].value.text, + : phoneController.value.text, ); final value = await asyncUserNotifier.updateMe(newMe); if (value) { From 51bbc5df70bca8fc96afe31b62061702b4b8678a Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 9 Aug 2025 19:13:10 +0200 Subject: [PATCH 118/473] one when only --- .../ui/pages/main_page/main_page.dart | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/lib/settings/ui/pages/main_page/main_page.dart b/lib/settings/ui/pages/main_page/main_page.dart index f6ad0ad70b..33bd4acab4 100644 --- a/lib/settings/ui/pages/main_page/main_page.dart +++ b/lib/settings/ui/pages/main_page/main_page.dart @@ -37,17 +37,20 @@ class SettingsMainPage extends HookConsumerWidget { final meNotifier = ref.watch(asyncUserProvider.notifier); final profilePicture = ref.watch(profilePictureProvider); - final notificationAcivatedCounts = notificationTopicList.when( - data: (data) => data.where((topic) => topic.isUserSubscribed).length, - loading: () => 0, - error: (_, _) => 0, + final results = notificationTopicList.when( + data: (data) { + final activatedCount = data + .where((topic) => topic.isUserSubscribed) + .length; + final totalCount = data.length; + return {'activatedCount': activatedCount, 'totalCount': totalCount}; + }, + loading: () => {'activatedCount': 0, 'totalCount': 0}, + error: (_, _) => {'activatedCount': 0, 'totalCount': 0}, ); - final notificationTopicsLength = notificationTopicList.when( - data: (data) => data.length, - loading: () => 0, - error: (_, _) => 0, - ); + final notificationActivatedCounts = results['activatedCount']!; + final notificationTopicsLength = results['totalCount']!; final localizeWithContext = AppLocalizations.of(context)!; @@ -175,7 +178,7 @@ class SettingsMainPage extends HookConsumerWidget { ListItem( title: localizeWithContext.settingsNotifications, subtitle: localizeWithContext.settingsNotificationCounter( - notificationAcivatedCounts, + notificationActivatedCounts, notificationTopicsLength, ), onTap: () async { From dc3c7d8aa251284e07f7183ceb80c796cd4d95b7 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 9 Aug 2025 19:16:49 +0200 Subject: [PATCH 119/473] picture color --- lib/settings/ui/pages/main_page/picture_button.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/settings/ui/pages/main_page/picture_button.dart b/lib/settings/ui/pages/main_page/picture_button.dart index 905238fb15..335a312b32 100644 --- a/lib/settings/ui/pages/main_page/picture_button.dart +++ b/lib/settings/ui/pages/main_page/picture_button.dart @@ -15,13 +15,13 @@ class PictureButton extends StatelessWidget { decoration: BoxDecoration( shape: BoxShape.circle, gradient: const LinearGradient( - colors: [ColorConstants.gradient1, ColorConstants.gradient2], + colors: [ColorConstants.main, ColorConstants.onMain], begin: Alignment.topLeft, end: Alignment.bottomRight, ), boxShadow: [ BoxShadow( - color: ColorConstants.gradient2.withValues(alpha: 0.3), + color: ColorConstants.main.withValues(alpha: 0.3), spreadRadius: 2, blurRadius: 4, offset: const Offset(2, 3), From a6a30dde8f504458c9cfb9fb8182082393d721d2 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 16 Jul 2025 19:23:56 +0200 Subject: [PATCH 120/473] Init # Conflicts: # lib/feed/ui/pages/main_page/main_page.dart # Conflicts: # lib/settings/router.dart --- lib/admin/admin.dart | 22 ++ lib/admin/router.dart | 183 +--------------- lib/admin/ui/pages/main_page/main_page.dart | 65 +----- .../users_management_page.dart | 15 ++ .../providers/is_advert_admin_provider.dart | 2 +- lib/advert/router.dart | 8 +- .../pages/admin_page/admin_advert_card.dart | 4 +- .../ui/pages/admin_page/admin_page.dart | 6 +- .../form_page/add_rem_announcer_page.dart | 2 +- .../ui/pages/form_page/announcer_card.dart | 2 +- lib/advert/ui/pages/main_page/main_page.dart | 14 +- .../delivery_order_list_provider.dart | 10 +- .../providers/is_amap_admin_provider.dart | 2 +- lib/amap/router.dart | 4 +- lib/amap/ui/pages/admin_page/admin_page.dart | 4 +- lib/amap/ui/pages/main_page/main_page.dart | 6 +- lib/booking/providers/is_admin_provider.dart | 2 +- lib/booking/router.dart | 20 +- lib/booking/ui/components/booking_card.dart | 22 +- .../admin_pages/add_edit_manager_page.dart | 14 +- .../pages/admin_pages/add_edit_room_page.dart | 8 +- .../ui/pages/admin_pages/admin_entry.dart | 4 +- .../ui/pages/admin_pages/admin_page.dart | 6 +- .../pages/admin_pages/admin_scroll_chips.dart | 4 +- .../admin_pages/admin_shrink_button.dart | 4 +- .../booking_pages/add_edit_booking_page.dart | 2 +- .../ui/pages/detail_pages/detail_booking.dart | 6 +- lib/booking/ui/pages/main_page/main_page.dart | 10 +- .../ui/pages/manager_page/list_booking.dart | 2 +- lib/cinema/providers/is_cinema_admin.dart | 2 +- lib/cinema/router.dart | 4 +- .../ui/pages/admin_page/admin_page.dart | 6 +- .../pages/admin_page/admin_session_card.dart | 4 +- lib/cinema/ui/pages/main_page/main_page.dart | 6 +- lib/event/providers/is_admin_provider.dart | 2 +- lib/event/router.dart | 8 +- lib/event/ui/components/event_ui.dart | 14 +- lib/event/ui/pages/admin_page/admin_page.dart | 4 +- lib/event/ui/pages/admin_page/list_event.dart | 2 +- .../ui/pages/detail_page/detail_page.dart | 6 +- lib/event/ui/pages/main_page/main_page.dart | 6 +- lib/feed/router.dart | 6 +- lib/feed/ui/pages/admin_page/admin_page.dart | 4 +- lib/feed/ui/pages/main_page/main_page.dart | 6 +- lib/home/router.dart | 2 +- lib/l10n/app_en.arb | 26 +-- lib/l10n/app_fr.arb | 28 +-- lib/l10n/app_localizations.dart | 80 +++---- lib/l10n/app_localizations_en.dart | 26 +-- lib/l10n/app_localizations_fr.dart | 29 +-- .../admin_history_loan_list_provider.dart | 10 +- .../providers/admin_loan_list_provider.dart | 9 +- .../providers/is_loan_admin_provider.dart | 2 +- lib/loan/router.dart | 4 +- lib/loan/ui/pages/admin_page/admin_page.dart | 17 +- lib/loan/ui/pages/admin_page/loan_card.dart | 14 +- .../ui/pages/admin_page/on_going_loan.dart | 2 +- lib/loan/ui/pages/main_page/main_page.dart | 6 +- lib/others/ui/no_module.dart | 2 +- lib/paiement/class/structure.dart | 2 +- lib/paiement/class/user_store.dart | 2 +- lib/paiement/providers/is_payment_admin.dart | 2 +- .../providers/new_admin_provider.dart | 19 +- .../repositories/funding_repository.dart | 2 +- lib/paiement/router.dart | 10 +- .../ui/pages/admin_page/admin_page.dart | 6 +- .../ui/pages/admin_page/admin_store_card.dart | 4 +- .../ui/pages/main_page/main_page.dart | 4 +- .../seller_card/store_admin_card.dart | 4 +- .../main_page/seller_card/store_card.dart | 4 +- .../main_page/seller_card/store_list.dart | 8 +- .../pages/store_admin_page/search_result.dart | 10 +- .../store_admin_page/seller_right_card.dart | 17 +- .../store_admin_page/store_admin_page.dart | 4 +- lib/ph/class/ph_admin.dart | 18 +- lib/ph/providers/is_ph_admin_provider.dart | 2 +- lib/ph/router.dart | 4 +- lib/ph/ui/pages/admin_page/admin_page.dart | 8 +- lib/ph/ui/pages/admin_page/admin_ph_card.dart | 4 +- lib/ph/ui/pages/admin_page/admin_ph_list.dart | 6 +- lib/ph/ui/pages/main_page/main_page.dart | 6 +- lib/phonebook/class/complete_member.dart | 2 +- lib/phonebook/class/member.dart | 2 +- .../providers/phonebook_admin_provider.dart | 12 +- lib/phonebook/router.dart | 10 +- .../ui/pages/admin_page/admin_page.dart | 12 +- .../admin_page/editable_association_card.dart | 6 +- .../association_editor_page.dart | 12 +- .../association_information_editor.dart | 14 +- .../ui/pages/main_page/main_page.dart | 10 +- .../membership_editor_page.dart | 6 +- .../providers/purchases_admin_provider.dart | 2 +- lib/purchases/router.dart | 2 +- .../ui/pages/main_page/custom_button.dart | 2 +- .../ui/pages/main_page/main_page.dart | 4 +- lib/raffle/class/raffle.dart | 2 +- lib/raffle/providers/is_raffle_admin.dart | 2 +- lib/raffle/router.dart | 6 +- .../admin_module_page/admin_module_page.dart | 4 +- .../admin_module_page/confirm_creation.dart | 2 +- .../admin_module_page/tombola_handler.dart | 4 +- lib/raffle/ui/pages/main_page/main_page.dart | 6 +- .../is_recommendation_admin_provider.dart | 2 +- lib/recommendation/router.dart | 2 +- lib/recommendation/ui/pages/main_page.dart | 6 +- .../ui/widgets/recommendation_card.dart | 6 +- lib/router.dart | 4 +- .../is_seed_library_admin_provider.dart | 2 +- .../repositories/plants_repository.dart | 2 +- lib/seed-library/router.dart | 4 +- .../ui/pages/information_page/text.dart | 4 +- .../ui/pages/main_page/main_page.dart | 4 +- .../plant_deposit_page.dart | 8 +- lib/service/provider_list.dart | 2 +- .../providers/module_list_provider.dart | 19 +- .../class/account_type.dart | 0 .../class/association_membership_simple.dart | 0 lib/{admin => super_admin}/class/group.dart | 2 +- .../class/module_visibility.dart | 2 +- lib/{admin => super_admin}/class/school.dart | 0 .../class/simple_group.dart | 0 .../class/user_association_membership.dart | 2 +- .../user_association_membership_base.dart | 0 .../notification_service.dart | 6 +- .../account_types_list_provider.dart | 4 +- .../all_account_types_list_provider.dart | 4 +- .../providers/all_groups_list_provider.dart | 4 +- .../all_my_module_roots_list_provider.dart | 2 +- ..._membership_filtered_members_provider.dart | 6 +- .../association_membership_list_provider.dart | 4 +- ...tion_membership_members_list_provider.dart | 8 +- .../association_membership_provider.dart | 2 +- .../providers/group_id_provider.dart | 0 .../providers/group_list_provider.dart | 4 +- .../providers/group_logo_provider.dart | 2 +- .../providers/group_provider.dart | 4 +- .../providers/is_admin_provider.dart | 2 +- .../providers/is_expanded_list_provider.dart | 4 +- .../providers/members_provider.dart | 0 .../providers/module_root_list_provider.dart | 2 +- .../module_visibility_list_provider.dart | 4 +- .../providers/research_filter_provider.dart | 0 .../providers/school_id_provider.dart | 0 .../providers/school_list_provider.dart | 4 +- .../providers/school_provider.dart | 4 +- .../providers/section_logo_provider.dart | 2 +- .../simple_groups_groups_provider.dart | 4 +- .../providers/structure_manager_provider.dart | 0 .../providers/structure_provider.dart | 0 ..._association_membership_list_provider.dart | 4 +- .../user_association_membership_provider.dart | 2 +- .../repositories/account_type_repository.dart | 2 +- .../association_membership_repository.dart | 4 +- ...ssociation_membership_user_repository.dart | 4 +- .../repositories/group_logo_repository.dart | 0 .../repositories/group_repository.dart | 4 +- .../module_visibility_repository.dart | 2 +- .../repositories/school_repository.dart | 2 +- lib/super_admin/router.dart | 203 ++++++++++++++++++ .../tools/constants.dart | 0 .../tools/function.dart | 2 +- lib/{admin => super_admin}/ui/admin.dart | 4 +- .../ui/components/admin_button.dart | 4 +- .../ui/components/item_card_ui.dart | 0 .../ui/components/text_editing.dart | 0 .../ui/components/user_ui.dart | 0 .../add_edit_structure_page.dart | 20 +- .../add_edit_structure_page/results.dart | 2 +- .../add_edit_structure_page/search_user.dart | 4 +- .../edit_module_visibility.dart | 12 +- .../modules_expansion_panel.dart | 12 +- .../groups/add_group_page/add_group_page.dart | 14 +- .../add_loaner_page/add_loaner_page.dart | 6 +- .../edit_group_page/edit_group_page.dart | 20 +- .../pages/groups/edit_group_page/results.dart | 6 +- .../groups/edit_group_page/search_user.dart | 12 +- .../pages/groups/group_page/group_button.dart | 0 .../pages/groups/group_page/group_page.dart | 32 +-- .../ui/pages/groups/group_page/group_ui.dart | 6 +- .../ui/pages/main_page/main_page.dart | 80 +++++++ .../ui/pages/main_page/menu_card_ui.dart | 0 .../add_edit_user_membership_page.dart | 14 +- .../search_result.dart | 2 +- .../association_membership_detail_page.dart | 32 +-- ...ciation_membership_information_editor.dart | 6 +- ...ation_membership_member_editable_card.dart | 16 +- .../research_bar.dart | 2 +- .../search_filters.dart | 4 +- .../association_membership_button.dart | 0 ...ssociation_membership_creation_dialog.dart | 2 +- .../association_membership_page.dart | 29 +-- .../association_membership_ui.dart | 6 +- .../add_school_page/add_school_page.dart | 14 +- .../edit_school_page/edit_school_page.dart | 16 +- .../schools/school_page/school_button.dart | 0 .../schools/school_page/school_page.dart | 26 +-- .../pages/schools/school_page/school_ui.dart | 10 +- .../structure_page/structure_button.dart | 0 .../pages/structure_page/structure_page.dart | 26 +-- .../ui/pages/structure_page/structure_ui.dart | 4 +- lib/tools/middlewares/admin_middleware.dart | 8 +- lib/tools/ui/styleguide/styleguide_page.dart | 4 +- lib/tools/ui/widgets/admin_button.dart | 6 +- lib/user/class/applicant.dart | 2 +- lib/user/class/simple_users.dart | 2 +- lib/user/class/user.dart | 4 +- lib/user/providers/user_list_provider.dart | 2 +- lib/vote/class/members.dart | 2 +- .../providers/is_vote_admin_provider.dart | 2 +- .../providers/voting_group_list_provider.dart | 4 +- lib/vote/router.dart | 4 +- lib/vote/ui/components/member_card.dart | 14 +- .../ui/pages/admin_page/admin_button.dart | 4 +- lib/vote/ui/pages/admin_page/admin_page.dart | 9 +- .../ui/pages/admin_page/contender_card.dart | 8 +- lib/vote/ui/pages/admin_page/section_bar.dart | 2 +- .../ui/pages/admin_page/section_chip.dart | 6 +- .../admin_page/section_contender_items.dart | 2 +- lib/vote/ui/pages/admin_page/voters_bar.dart | 2 +- .../contender_pages/add_edit_contender.dart | 2 +- lib/vote/ui/pages/main_page/main_page.dart | 16 +- test/admin/admin_test.dart | 8 +- test/admin/group_id_provider_test.dart | 2 +- test/admin/group_list_provider_test.dart | 8 +- test/admin/group_logo_provider_test.dart | 4 +- test/admin/group_provider_test.dart | 6 +- test/admin/is_admin_test.dart | 16 +- test/admin/members_provider_test.dart | 2 +- test/amap/is_amap_admin_provider_test.dart | 14 +- .../is_booking_admin_provider_test.dart | 10 +- test/cinema/is_cinema_admin_test.dart | 18 +- test/event/is_admin_provider_test.dart | 14 +- test/loan/loan_test.dart | 2 +- test/login/login_test.dart | 2 +- test/user/user_list_provider_test.dart | 2 +- test/user/user_test.dart | 2 +- test/vote/is_vote_admin_provider_test.dart | 12 +- test/vote/vote_test.dart | 2 +- 238 files changed, 1119 insertions(+), 987 deletions(-) create mode 100644 lib/admin/admin.dart create mode 100644 lib/admin/ui/pages/users_management_page/users_management_page.dart rename lib/{admin => super_admin}/class/account_type.dart (100%) rename lib/{admin => super_admin}/class/association_membership_simple.dart (100%) rename lib/{admin => super_admin}/class/group.dart (95%) rename lib/{admin => super_admin}/class/module_visibility.dart (95%) rename lib/{admin => super_admin}/class/school.dart (100%) rename lib/{admin => super_admin}/class/simple_group.dart (100%) rename lib/{admin => super_admin}/class/user_association_membership.dart (94%) rename lib/{admin => super_admin}/class/user_association_membership_base.dart (100%) rename lib/{admin => super_admin}/notification_service.dart (70%) rename lib/{admin => super_admin}/providers/account_types_list_provider.dart (87%) rename lib/{admin => super_admin}/providers/all_account_types_list_provider.dart (62%) rename lib/{admin => super_admin}/providers/all_groups_list_provider.dart (63%) rename lib/{admin => super_admin}/providers/all_my_module_roots_list_provider.dart (74%) rename lib/{admin => super_admin}/providers/association_membership_filtered_members_provider.dart (77%) rename lib/{admin => super_admin}/providers/association_membership_list_provider.dart (93%) rename lib/{admin => super_admin}/providers/association_membership_members_list_provider.dart (90%) rename lib/{admin => super_admin}/providers/association_membership_provider.dart (86%) rename lib/{admin => super_admin}/providers/group_id_provider.dart (100%) rename lib/{admin => super_admin}/providers/group_list_provider.dart (94%) rename lib/{admin => super_admin}/providers/group_logo_provider.dart (93%) rename lib/{admin => super_admin}/providers/group_provider.dart (90%) rename lib/{admin => super_admin}/providers/is_admin_provider.dart (81%) rename lib/{admin => super_admin}/providers/is_expanded_list_provider.dart (82%) rename lib/{admin => super_admin}/providers/members_provider.dart (100%) rename lib/{admin => super_admin}/providers/module_root_list_provider.dart (93%) rename lib/{admin => super_admin}/providers/module_visibility_list_provider.dart (95%) rename lib/{admin => super_admin}/providers/research_filter_provider.dart (100%) rename lib/{admin => super_admin}/providers/school_id_provider.dart (100%) rename lib/{admin => super_admin}/providers/school_list_provider.dart (93%) rename lib/{admin => super_admin}/providers/school_provider.dart (79%) rename lib/{admin => super_admin}/providers/section_logo_provider.dart (90%) rename lib/{admin => super_admin}/providers/simple_groups_groups_provider.dart (87%) rename lib/{admin => super_admin}/providers/structure_manager_provider.dart (100%) rename lib/{admin => super_admin}/providers/structure_provider.dart (100%) rename lib/{admin => super_admin}/providers/user_association_membership_list_provider.dart (92%) rename lib/{admin => super_admin}/providers/user_association_membership_provider.dart (88%) rename lib/{admin => super_admin}/repositories/account_type_repository.dart (91%) rename lib/{admin => super_admin}/repositories/association_membership_repository.dart (94%) rename lib/{admin => super_admin}/repositories/association_membership_user_repository.dart (91%) rename lib/{admin => super_admin}/repositories/group_logo_repository.dart (100%) rename lib/{admin => super_admin}/repositories/group_repository.dart (94%) rename lib/{admin => super_admin}/repositories/module_visibility_repository.dart (94%) rename lib/{admin => super_admin}/repositories/school_repository.dart (94%) create mode 100644 lib/super_admin/router.dart rename lib/{admin => super_admin}/tools/constants.dart (100%) rename lib/{admin => super_admin}/tools/function.dart (87%) rename lib/{admin => super_admin}/ui/admin.dart (86%) rename lib/{admin => super_admin}/ui/components/admin_button.dart (88%) rename lib/{admin => super_admin}/ui/components/item_card_ui.dart (100%) rename lib/{admin => super_admin}/ui/components/text_editing.dart (100%) rename lib/{admin => super_admin}/ui/components/user_ui.dart (100%) rename lib/{admin => super_admin}/ui/pages/add_edit_structure_page/add_edit_structure_page.dart (92%) rename lib/{admin => super_admin}/ui/pages/add_edit_structure_page/results.dart (96%) rename lib/{admin => super_admin}/ui/pages/add_edit_structure_page/search_user.dart (93%) rename lib/{admin => super_admin}/ui/pages/edit_module_visibility/edit_module_visibility.dart (83%) rename lib/{admin => super_admin}/ui/pages/edit_module_visibility/modules_expansion_panel.dart (95%) rename lib/{admin => super_admin}/ui/pages/groups/add_group_page/add_group_page.dart (88%) rename lib/{admin => super_admin}/ui/pages/groups/add_loaner_page/add_loaner_page.dart (97%) rename lib/{admin => super_admin}/ui/pages/groups/edit_group_page/edit_group_page.dart (90%) rename lib/{admin => super_admin}/ui/pages/groups/edit_group_page/results.dart (95%) rename lib/{admin => super_admin}/ui/pages/groups/edit_group_page/search_user.dart (93%) rename lib/{admin => super_admin}/ui/pages/groups/group_page/group_button.dart (100%) rename lib/{admin => super_admin}/ui/pages/groups/group_page/group_page.dart (88%) rename lib/{admin => super_admin}/ui/pages/groups/group_page/group_ui.dart (90%) create mode 100644 lib/super_admin/ui/pages/main_page/main_page.dart rename lib/{admin => super_admin}/ui/pages/main_page/menu_card_ui.dart (100%) rename lib/{admin => super_admin}/ui/pages/memberships/add_edit_user_membership_page/add_edit_user_membership_page.dart (94%) rename lib/{admin => super_admin}/ui/pages/memberships/add_edit_user_membership_page/search_result.dart (96%) rename lib/{admin => super_admin}/ui/pages/memberships/association_membership_detail_page/association_membership_detail_page.dart (79%) rename lib/{admin => super_admin}/ui/pages/memberships/association_membership_detail_page/association_membership_information_editor.dart (96%) rename lib/{admin => super_admin}/ui/pages/memberships/association_membership_detail_page/association_membership_member_editable_card.dart (87%) rename lib/{admin => super_admin}/ui/pages/memberships/association_membership_detail_page/research_bar.dart (94%) rename lib/{admin => super_admin}/ui/pages/memberships/association_membership_detail_page/search_filters.dart (97%) rename lib/{admin => super_admin}/ui/pages/memberships/association_membership_page/association_membership_button.dart (100%) rename lib/{admin => super_admin}/ui/pages/memberships/association_membership_page/association_membership_creation_dialog.dart (98%) rename lib/{admin => super_admin}/ui/pages/memberships/association_membership_page/association_membership_page.dart (89%) rename lib/{admin => super_admin}/ui/pages/memberships/association_membership_page/association_membership_ui.dart (87%) rename lib/{admin => super_admin}/ui/pages/schools/add_school_page/add_school_page.dart (88%) rename lib/{admin => super_admin}/ui/pages/schools/edit_school_page/edit_school_page.dart (90%) rename lib/{admin => super_admin}/ui/pages/schools/school_page/school_button.dart (100%) rename lib/{admin => super_admin}/ui/pages/schools/school_page/school_page.dart (87%) rename lib/{admin => super_admin}/ui/pages/schools/school_page/school_ui.dart (85%) rename lib/{admin => super_admin}/ui/pages/structure_page/structure_button.dart (100%) rename lib/{admin => super_admin}/ui/pages/structure_page/structure_page.dart (89%) rename lib/{admin => super_admin}/ui/pages/structure_page/structure_ui.dart (92%) diff --git a/lib/admin/admin.dart b/lib/admin/admin.dart new file mode 100644 index 0000000000..f43534394d --- /dev/null +++ b/lib/admin/admin.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; +import 'package:titan/navigation/ui/top_bar.dart'; +import 'package:titan/tools/constants.dart'; + +class AdminTemplate extends StatelessWidget { + final Widget child; + const AdminTemplate({super.key, required this.child}); + + @override + Widget build(BuildContext context) { + return Container( + color: ColorConstants.background, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + TopBar(), + Expanded(child: child), + ], + ), + ); + } +} diff --git a/lib/admin/router.dart b/lib/admin/router.dart index 4b99655793..7d5414448b 100644 --- a/lib/admin/router.dart +++ b/lib/admin/router.dart @@ -1,37 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/admin/providers/is_admin_provider.dart'; -import 'package:titan/admin/ui/pages/groups/add_group_page/add_group_page.dart' - deferred as add_group_page; -import 'package:titan/admin/ui/pages/groups/add_loaner_page/add_loaner_page.dart' - deferred as add_loaner_page; -import 'package:titan/admin/ui/pages/edit_module_visibility/edit_module_visibility.dart' - deferred as edit_module_visibility; -import 'package:titan/admin/ui/pages/groups/edit_group_page/edit_group_page.dart' - deferred as edit_group_page; -import 'package:titan/admin/ui/pages/groups/group_page/group_page.dart' - deferred as group_page; -import 'package:titan/admin/ui/pages/memberships/add_edit_user_membership_page/add_edit_user_membership_page.dart' - deferred as add_edit_user_membership_page; -import 'package:titan/admin/ui/pages/memberships/association_membership_detail_page/association_membership_detail_page.dart' - deferred as association_membership_detail_page; -import 'package:titan/admin/ui/pages/memberships/association_membership_page/association_membership_page.dart' - deferred as association_membership_page; -import 'package:titan/admin/ui/pages/schools/school_page/school_page.dart' - deferred as school_page; -import 'package:titan/admin/ui/pages/schools/add_school_page/add_school_page.dart' - deferred as add_school_page; -import 'package:titan/admin/ui/pages/schools/edit_school_page/edit_school_page.dart' - deferred as edit_school_page; -import 'package:titan/admin/ui/pages/structure_page/structure_page.dart' - deferred as structure_page; -import 'package:titan/admin/ui/pages/add_edit_structure_page/add_edit_structure_page.dart' - deferred as add_edit_structure_page; import 'package:titan/admin/ui/pages/main_page/main_page.dart' deferred as main_page; -import 'package:titan/l10n/app_localizations.dart'; +import 'package:titan/admin/ui/pages/users_management_page/users_management_page.dart' + deferred as users_managmement_page; import 'package:titan/navigation/class/module.dart'; -import 'package:titan/tools/middlewares/admin_middleware.dart'; import 'package:titan/tools/middlewares/authenticated_middleware.dart'; import 'package:titan/tools/middlewares/deferred_middleware.dart'; import 'package:qlevar_router/qlevar_router.dart'; @@ -39,25 +12,15 @@ import 'package:qlevar_router/qlevar_router.dart'; class AdminRouter { final Ref ref; static const String root = '/admin'; - static const String groups = '/groups'; - static const String addGroup = '/add_group'; - static const String editGroup = '/edit_group'; - static const String addLoaner = '/add_loaner'; - static const String schools = '/schools'; - static const String addSchool = '/add_school'; - static const String editSchool = '/edit_school'; - static const String structures = '/structures'; - static const String addEditStructure = '/add_edit_structure'; - static const String editModuleVisibility = '/edit_module_visibility'; - static const String associationMemberships = '/association_memberships'; - static const String detailAssociationMembership = - '/detail_association_membership'; - static const String addEditMember = '/add_edit_member'; + static const String usersManagement = '/users_management'; + static const String usersGroups = '/users_groups'; + static const String groupNotifications = '/group_notifications'; static final Module module = Module( - getName: (context) => AppLocalizations.of(context)!.moduleAdmin, - description: "Gérer les groupes, écoles et structures", + getName: (context) => "Admin", + description: "Gérer les paramètres de l'application", root: AdminRouter.root, ); + AdminRouter(this.ref); QRoute route() => QRoute( @@ -66,7 +29,6 @@ class AdminRouter { builder: () => main_page.AdminMainPage(), middleware: [ AuthenticatedMiddleware(ref), - AdminMiddleware(ref, isAdminProvider), DeferredLoadingMiddleware(main_page.loadLibrary), ], pageType: QCustomPage( @@ -75,133 +37,10 @@ class AdminRouter { ), children: [ QRoute( - path: groups, - builder: () => group_page.GroupsPage(), - middleware: [DeferredLoadingMiddleware(group_page.loadLibrary)], - children: [ - QRoute( - path: addGroup, - builder: () => add_group_page.AddGroupPage(), - middleware: [DeferredLoadingMiddleware(add_group_page.loadLibrary)], - ), - QRoute( - path: editGroup, - builder: () => edit_group_page.EditGroupPage(), - middleware: [ - DeferredLoadingMiddleware(edit_group_page.loadLibrary), - ], - ), - QRoute( - path: addLoaner, - builder: () => add_loaner_page.AddLoanerPage(), - middleware: [ - DeferredLoadingMiddleware(add_loaner_page.loadLibrary), - ], - ), - ], - ), - QRoute( - path: editModuleVisibility, - builder: () => edit_module_visibility.EditModulesVisibilityPage(), - middleware: [ - DeferredLoadingMiddleware(edit_module_visibility.loadLibrary), - ], - ), - QRoute( - path: schools, - builder: () => school_page.SchoolsPage(), - middleware: [DeferredLoadingMiddleware(school_page.loadLibrary)], - children: [ - QRoute( - path: addSchool, - builder: () => add_school_page.AddSchoolPage(), - middleware: [ - DeferredLoadingMiddleware(add_school_page.loadLibrary), - ], - ), - QRoute( - path: editSchool, - builder: () => edit_school_page.EditSchoolPage(), - middleware: [ - DeferredLoadingMiddleware(edit_school_page.loadLibrary), - ], - ), - ], - ), - QRoute( - path: associationMemberships, - builder: () => association_membership_page.AssociationMembershipsPage(), - middleware: [ - DeferredLoadingMiddleware(association_membership_page.loadLibrary), - ], - children: [ - QRoute( - path: detailAssociationMembership, - builder: () => - association_membership_detail_page.AssociationMembershipEditorPage(), - middleware: [ - DeferredLoadingMiddleware( - association_membership_detail_page.loadLibrary, - ), - ], - children: [ - QRoute( - path: addEditMember, - builder: () => - add_edit_user_membership_page.AddEditUserMembershipPage(), - middleware: [ - DeferredLoadingMiddleware( - add_edit_user_membership_page.loadLibrary, - ), - ], - ), - ], - ), - ], - ), - QRoute( - path: structures, - builder: () => structure_page.StructurePage(), - middleware: [DeferredLoadingMiddleware(structure_page.loadLibrary)], - children: [ - QRoute( - path: addEditStructure, - builder: () => add_edit_structure_page.AddEditStructurePage(), - middleware: [ - DeferredLoadingMiddleware(add_edit_structure_page.loadLibrary), - ], - ), - ], - ), - QRoute( - path: associationMemberships, - builder: () => association_membership_page.AssociationMembershipsPage(), + path: usersManagement, + builder: () => users_managmement_page.UsersManagementPage(), middleware: [ - DeferredLoadingMiddleware(association_membership_page.loadLibrary), - ], - children: [ - QRoute( - path: detailAssociationMembership, - builder: () => - association_membership_detail_page.AssociationMembershipEditorPage(), - middleware: [ - DeferredLoadingMiddleware( - association_membership_detail_page.loadLibrary, - ), - ], - children: [ - QRoute( - path: addEditMember, - builder: () => - add_edit_user_membership_page.AddEditUserMembershipPage(), - middleware: [ - DeferredLoadingMiddleware( - add_edit_user_membership_page.loadLibrary, - ), - ], - ), - ], - ), + DeferredLoadingMiddleware(users_managmement_page.loadLibrary), ], ), ], diff --git a/lib/admin/ui/pages/main_page/main_page.dart b/lib/admin/ui/pages/main_page/main_page.dart index 73bf14c928..77bde39184 100644 --- a/lib/admin/ui/pages/main_page/main_page.dart +++ b/lib/admin/ui/pages/main_page/main_page.dart @@ -1,13 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; -import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/router.dart'; -import 'package:titan/admin/ui/admin.dart'; -import 'package:titan/admin/ui/pages/main_page/menu_card_ui.dart'; +import 'package:titan/admin/admin.dart'; + import 'package:titan/user/providers/user_list_provider.dart'; -import 'package:qlevar_router/qlevar_router.dart'; -import 'package:titan/l10n/app_localizations.dart'; class AdminMainPage extends HookConsumerWidget { const AdminMainPage({super.key}); @@ -16,63 +12,8 @@ class AdminMainPage extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { ref.watch(userList); - final controller = ScrollController(); - return AdminTemplate( - child: Padding( - padding: const EdgeInsets.all(40), - child: GridView( - controller: controller, - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 2, - mainAxisSpacing: 20, - crossAxisSpacing: 20, - childAspectRatio: - MediaQuery.of(context).size.width < - MediaQuery.of(context).size.height - ? 0.75 - : 1.5, - ), - children: [ - GestureDetector( - onTap: () { - QR.to(AdminRouter.root + AdminRouter.editModuleVisibility); - }, - child: MenuCardUi( - text: AppLocalizations.of(context)!.adminVisibilities, - icon: HeroIcons.eye, - ), - ), - GestureDetector( - onTap: () { - QR.to(AdminRouter.root + AdminRouter.groups); - }, - child: MenuCardUi( - text: AppLocalizations.of(context)!.adminGroups, - icon: HeroIcons.users, - ), - ), - GestureDetector( - onTap: () { - QR.to(AdminRouter.root + AdminRouter.schools); - }, - child: MenuCardUi( - text: AppLocalizations.of(context)!.adminSchools, - icon: HeroIcons.academicCap, - ), - ), - GestureDetector( - onTap: () { - QR.to(AdminRouter.root + AdminRouter.structures); - }, - child: MenuCardUi( - text: AppLocalizations.of(context)!.adminMyEclPay, - icon: HeroIcons.creditCard, - ), - ), - ], - ), - ), + child: Padding(padding: const EdgeInsets.all(40), child: Text("yo")), ); } } diff --git a/lib/admin/ui/pages/users_management_page/users_management_page.dart b/lib/admin/ui/pages/users_management_page/users_management_page.dart new file mode 100644 index 0000000000..3e3d31cadb --- /dev/null +++ b/lib/admin/ui/pages/users_management_page/users_management_page.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/admin/admin.dart'; + +class UsersManagementPage extends HookConsumerWidget { + const UsersManagementPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return AdminTemplate( + child: Padding(padding: const EdgeInsets.all(40), child: Text("yo")), + ); + } +} diff --git a/lib/advert/providers/is_advert_admin_provider.dart b/lib/advert/providers/is_advert_admin_provider.dart index 3dec527d3a..bd3e1fd128 100644 --- a/lib/advert/providers/is_advert_admin_provider.dart +++ b/lib/advert/providers/is_advert_admin_provider.dart @@ -1,7 +1,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/advert/providers/announcer_list_provider.dart'; -final isAdvertAdminProvider = StateProvider((ref) { +final isAdvertSuperAdminProvider = StateProvider((ref) { final me = ref.watch(userAnnouncerListProvider); return me.maybeWhen(data: (data) => data.isNotEmpty, orElse: () => false); }); diff --git a/lib/advert/router.dart b/lib/advert/router.dart index 4654b5ddbf..a63686a659 100644 --- a/lib/advert/router.dart +++ b/lib/advert/router.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/admin/providers/is_admin_provider.dart'; +import 'package:titan/super_admin/providers/is_admin_provider.dart'; import 'package:titan/advert/providers/is_advert_admin_provider.dart'; import 'package:titan/advert/ui/pages/admin_page/admin_page.dart' deferred as admin_page; @@ -48,9 +48,9 @@ class AdvertRouter { children: [ QRoute( path: admin, - builder: () => admin_page.AdvertAdminPage(), + builder: () => admin_page.AdvertSuperAdminPage(), middleware: [ - AdminMiddleware(ref, isAdvertAdminProvider), + SuperAdminMiddleware(ref, isAdvertSuperAdminProvider), DeferredLoadingMiddleware(admin_page.loadLibrary), ], children: [ @@ -72,7 +72,7 @@ class AdvertRouter { path: addRemAnnouncer, builder: () => add_rem_announcer_page.AddRemAnnouncerPage(), middleware: [ - AdminMiddleware(ref, isAdminProvider), + SuperAdminMiddleware(ref, isSuperAdminProvider), DeferredLoadingMiddleware(add_rem_announcer_page.loadLibrary), ], ), diff --git a/lib/advert/ui/pages/admin_page/admin_advert_card.dart b/lib/advert/ui/pages/admin_page/admin_advert_card.dart index 7397504045..7c3b24dba5 100644 --- a/lib/advert/ui/pages/admin_page/admin_advert_card.dart +++ b/lib/advert/ui/pages/admin_page/admin_advert_card.dart @@ -7,12 +7,12 @@ import 'package:titan/advert/ui/components/advert_card.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; import 'package:titan/tools/ui/layouts/card_button.dart'; -class AdminAdvertCard extends HookConsumerWidget { +class SuperAdminAdvertCard extends HookConsumerWidget { final VoidCallback onTap, onEdit; final Future Function() onDelete; final Advert advert; - const AdminAdvertCard({ + const SuperAdminAdvertCard({ super.key, required this.advert, required this.onTap, diff --git a/lib/advert/ui/pages/admin_page/admin_page.dart b/lib/advert/ui/pages/admin_page/admin_page.dart index 3a7e21f47e..b443a3393e 100644 --- a/lib/advert/ui/pages/admin_page/admin_page.dart +++ b/lib/advert/ui/pages/admin_page/admin_page.dart @@ -19,8 +19,8 @@ import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/l10n/app_localizations.dart'; -class AdvertAdminPage extends HookConsumerWidget { - const AdvertAdminPage({super.key}); +class AdvertSuperAdminPage extends HookConsumerWidget { + const AdvertSuperAdminPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -100,7 +100,7 @@ class AdvertAdminPage extends HookConsumerWidget { ), ), ...filteredSortedUserAnnouncerAdverts.map( - (advert) => AdminAdvertCard( + (advert) => SuperAdminAdvertCard( onTap: () { advertNotifier.setAdvert(advert); QR.to(AdvertRouter.root + AdvertRouter.detail); diff --git a/lib/advert/ui/pages/form_page/add_rem_announcer_page.dart b/lib/advert/ui/pages/form_page/add_rem_announcer_page.dart index b79365ca47..2add07c9bd 100644 --- a/lib/advert/ui/pages/form_page/add_rem_announcer_page.dart +++ b/lib/advert/ui/pages/form_page/add_rem_announcer_page.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/providers/group_list_provider.dart'; +import 'package:titan/super_admin/providers/group_list_provider.dart'; import 'package:titan/advert/class/announcer.dart'; import 'package:titan/advert/providers/all_announcer_list_provider.dart'; import 'package:titan/advert/providers/announcer_list_provider.dart'; diff --git a/lib/advert/ui/pages/form_page/announcer_card.dart b/lib/advert/ui/pages/form_page/announcer_card.dart index 8a118d8d21..26f9d0c83b 100644 --- a/lib/advert/ui/pages/form_page/announcer_card.dart +++ b/lib/advert/ui/pages/form_page/announcer_card.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; -import 'package:titan/admin/class/simple_group.dart'; +import 'package:titan/super_admin/class/simple_group.dart'; class AnnouncerCard extends StatelessWidget { final SimpleGroup e; diff --git a/lib/advert/ui/pages/main_page/main_page.dart b/lib/advert/ui/pages/main_page/main_page.dart index f273e974b3..5b4dce50b5 100644 --- a/lib/advert/ui/pages/main_page/main_page.dart +++ b/lib/advert/ui/pages/main_page/main_page.dart @@ -1,7 +1,7 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/providers/is_admin_provider.dart'; +import 'package:titan/super_admin/providers/is_admin_provider.dart'; import 'package:titan/advert/providers/advert_list_provider.dart'; import 'package:titan/advert/providers/advert_posters_provider.dart'; import 'package:titan/advert/providers/advert_provider.dart'; @@ -28,8 +28,8 @@ class AdvertMainPage extends HookConsumerWidget { final advertPostersNotifier = ref.watch(advertPostersProvider.notifier); final selected = ref.watch(announcerProvider); final selectedNotifier = ref.watch(announcerProvider.notifier); - final isAdmin = ref.watch(isAdminProvider); - final isAdvertAdmin = ref.watch(isAdvertAdminProvider); + final isSuperAdmin = ref.watch(isSuperAdminProvider); + final isAdvertSuperAdmin = ref.watch(isAdvertSuperAdminProvider); return AdvertTemplate( child: Stack( children: [ @@ -57,15 +57,15 @@ class AdvertMainPage extends HookConsumerWidget { child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ - if (isAdvertAdmin) - AdminButton( + if (isAdvertSuperAdmin) + SuperAdminButton( onTap: () { selectedNotifier.clearAnnouncer(); QR.to(AdvertRouter.root + AdvertRouter.admin); }, ), - if (isAdmin) - AdminButton( + if (isSuperAdmin) + SuperAdminButton( onTap: () { QR.to( AdvertRouter.root + diff --git a/lib/amap/providers/delivery_order_list_provider.dart b/lib/amap/providers/delivery_order_list_provider.dart index dc77ba5396..999e3fd3ad 100644 --- a/lib/amap/providers/delivery_order_list_provider.dart +++ b/lib/amap/providers/delivery_order_list_provider.dart @@ -4,17 +4,17 @@ import 'package:titan/amap/providers/delivery_list_provider.dart'; import 'package:titan/tools/providers/map_provider.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; -class AdminDeliveryOrderListNotifier extends MapNotifier { - AdminDeliveryOrderListNotifier() : super(); +class SuperAdminDeliveryOrderListNotifier extends MapNotifier { + SuperAdminDeliveryOrderListNotifier() : super(); } final adminDeliveryOrderListProvider = StateNotifierProvider< - AdminDeliveryOrderListNotifier, + SuperAdminDeliveryOrderListNotifier, Map>?> >((ref) { - AdminDeliveryOrderListNotifier orderListNotifier = - AdminDeliveryOrderListNotifier(); + SuperAdminDeliveryOrderListNotifier orderListNotifier = + SuperAdminDeliveryOrderListNotifier(); tokenExpireWrapperAuth(ref, () async { final deliveries = ref.watch(deliveryList); orderListNotifier.loadTList(deliveries.map((e) => e.id).toList()); diff --git a/lib/amap/providers/is_amap_admin_provider.dart b/lib/amap/providers/is_amap_admin_provider.dart index ecc2ab30e8..eea62cb83a 100644 --- a/lib/amap/providers/is_amap_admin_provider.dart +++ b/lib/amap/providers/is_amap_admin_provider.dart @@ -1,7 +1,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/user/providers/user_provider.dart'; -final isAmapAdminProvider = StateProvider((ref) { +final isAmapSuperAdminProvider = StateProvider((ref) { final me = ref.watch(userProvider); return me.groups .map((e) => e.id) diff --git a/lib/amap/router.dart b/lib/amap/router.dart index f0550f0ff4..14b55d39d0 100644 --- a/lib/amap/router.dart +++ b/lib/amap/router.dart @@ -56,9 +56,9 @@ class AmapRouter { children: [ QRoute( path: admin, - builder: () => admin_page.AdminPage(), + builder: () => admin_page.SuperAdminPage(), middleware: [ - AdminMiddleware(ref, isAmapAdminProvider), + SuperAdminMiddleware(ref, isAmapSuperAdminProvider), DeferredLoadingMiddleware(admin_page.loadLibrary), ], children: [ diff --git a/lib/amap/ui/pages/admin_page/admin_page.dart b/lib/amap/ui/pages/admin_page/admin_page.dart index 328c3e652d..c9c11698f4 100644 --- a/lib/amap/ui/pages/admin_page/admin_page.dart +++ b/lib/amap/ui/pages/admin_page/admin_page.dart @@ -9,8 +9,8 @@ import 'package:titan/amap/ui/pages/admin_page/delivery_handler.dart'; import 'package:titan/amap/ui/pages/admin_page/product_handler.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; -class AdminPage extends HookConsumerWidget { - const AdminPage({super.key}); +class SuperAdminPage extends HookConsumerWidget { + const SuperAdminPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { diff --git a/lib/amap/ui/pages/main_page/main_page.dart b/lib/amap/ui/pages/main_page/main_page.dart index 91eb61c468..edf0b443a8 100644 --- a/lib/amap/ui/pages/main_page/main_page.dart +++ b/lib/amap/ui/pages/main_page/main_page.dart @@ -35,7 +35,7 @@ class AmapMainPage extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final order = ref.watch(orderProvider); final orderNotifier = ref.read(orderProvider.notifier); - final isAdmin = ref.watch(isAmapAdminProvider); + final isSuperAdmin = ref.watch(isAmapSuperAdminProvider); final delivery = ref.watch(deliveryProvider); final deliveriesNotifier = ref.read(deliveryListProvider.notifier); final ordersNotifier = ref.read(userOrderListProvider.notifier); @@ -94,8 +94,8 @@ class AmapMainPage extends HookConsumerWidget { loaderColor: AMAPColorConstants.greenGradient1, ), ), - if (isAdmin) - AdminButton( + if (isSuperAdmin) + SuperAdminButton( onTap: () { QR.to(AmapRouter.root + AmapRouter.admin); }, diff --git a/lib/booking/providers/is_admin_provider.dart b/lib/booking/providers/is_admin_provider.dart index 4cd58e778d..afcc5c9f65 100644 --- a/lib/booking/providers/is_admin_provider.dart +++ b/lib/booking/providers/is_admin_provider.dart @@ -1,7 +1,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/user/providers/user_provider.dart'; -final isAdminProvider = StateProvider((ref) { +final isSuperAdminProvider = StateProvider((ref) { final me = ref.watch(userProvider); return me.groups .map((e) => e.id) diff --git a/lib/booking/router.dart b/lib/booking/router.dart index 8819f3ffb3..bed681d6bc 100644 --- a/lib/booking/router.dart +++ b/lib/booking/router.dart @@ -53,9 +53,9 @@ class BookingRouter { children: [ QRoute( path: admin, - builder: () => admin_page.AdminPage(), + builder: () => admin_page.SuperAdminPage(), middleware: [ - AdminMiddleware(ref, isAdminProvider), + SuperAdminMiddleware(ref, isSuperAdminProvider), DeferredLoadingMiddleware(admin_page.loadLibrary), ], children: [ @@ -63,7 +63,7 @@ class BookingRouter { path: room, builder: () => add_edit_room_page.AddEditRoomPage(), middleware: [ - AdminMiddleware(ref, isAdminProvider), + SuperAdminMiddleware(ref, isSuperAdminProvider), DeferredLoadingMiddleware(add_edit_room_page.loadLibrary), ], ), @@ -71,7 +71,7 @@ class BookingRouter { path: manager, builder: () => add_edit_manager_page.AddEditManagerPage(), middleware: [ - AdminMiddleware(ref, isAdminProvider), + SuperAdminMiddleware(ref, isSuperAdminProvider), DeferredLoadingMiddleware(add_edit_manager_page.loadLibrary), ], ), @@ -81,15 +81,16 @@ class BookingRouter { path: manager, builder: () => manager_page.ManagerPage(), middleware: [ - AdminMiddleware(ref, isManagerProvider), + SuperAdminMiddleware(ref, isManagerProvider), DeferredLoadingMiddleware(manager_page.loadLibrary), ], children: [ QRoute( path: detail, - builder: () => detail_booking_page.DetailBookingPage(isAdmin: true), + builder: () => + detail_booking_page.DetailBookingPage(isSuperAdmin: true), middleware: [ - AdminMiddleware(ref, isManagerProvider), + SuperAdminMiddleware(ref, isManagerProvider), DeferredLoadingMiddleware(detail_booking_page.loadLibrary), ], ), @@ -98,7 +99,7 @@ class BookingRouter { builder: () => add_edit_booking_page.AddEditBookingPage(isManagerPage: true), middleware: [ - AdminMiddleware(ref, isManagerProvider), + SuperAdminMiddleware(ref, isManagerProvider), DeferredLoadingMiddleware(add_edit_booking_page.loadLibrary), ], ), @@ -114,7 +115,8 @@ class BookingRouter { ), QRoute( path: detail, - builder: () => detail_booking_page.DetailBookingPage(isAdmin: false), + builder: () => + detail_booking_page.DetailBookingPage(isSuperAdmin: false), middleware: [ DeferredLoadingMiddleware(detail_booking_page.loadLibrary), ], diff --git a/lib/booking/ui/components/booking_card.dart b/lib/booking/ui/components/booking_card.dart index 468bc18c9a..f02df7091a 100644 --- a/lib/booking/ui/components/booking_card.dart +++ b/lib/booking/ui/components/booking_card.dart @@ -13,7 +13,7 @@ class BookingCard extends StatelessWidget { final Booking booking; final Function()? onEdit, onConfirm, onDecline, onCopy, onInfo; final Future Function()? onDelete; - final bool isAdmin, isDetail; + final bool isSuperAdmin, isDetail; const BookingCard({ super.key, required this.booking, @@ -23,7 +23,7 @@ class BookingCard extends StatelessWidget { this.onInfo, this.onCopy, this.onDelete, - this.isAdmin = false, + this.isSuperAdmin = false, this.isDetail = false, }); @@ -36,7 +36,7 @@ class BookingCard extends StatelessWidget { ).endDate!.isAfter(DateTime.now()) : booking.end.isAfter(DateTime.now()); final showButton = - (isNotEnded && booking.decision == Decision.pending) || isAdmin; + (isNotEnded && booking.decision == Decision.pending) || isSuperAdmin; final List cardColor; final Color smallTextColor; final Color bigTextColor; @@ -199,13 +199,13 @@ class BookingCard extends StatelessWidget { ), ), ), - if (isAdmin) const Spacer(), - if (isAdmin) + if (isSuperAdmin) const Spacer(), + if (isSuperAdmin) GestureDetector( onTap: onConfirm, child: CardButton( color: lightIconBackgroundColor, - borderColor: isAdmin + borderColor: isSuperAdmin ? booking.decision == Decision.approved ? darkIconBackgroundColor : Colors.transparent @@ -217,13 +217,13 @@ class BookingCard extends StatelessWidget { ), ), ), - if (isAdmin) const Spacer(), - if (isAdmin) + if (isSuperAdmin) const Spacer(), + if (isSuperAdmin) GestureDetector( onTap: onDecline, child: CardButton( color: darkIconBackgroundColor, - borderColor: isAdmin + borderColor: isSuperAdmin ? booking.decision == Decision.declined ? Colors.white : Colors.transparent @@ -237,8 +237,8 @@ class BookingCard extends StatelessWidget { ), ), ), - if (!isAdmin) const Spacer(), - if (!isAdmin && booking.decision == Decision.pending) + if (!isSuperAdmin) const Spacer(), + if (!isSuperAdmin && booking.decision == Decision.pending) WaitingButton( onTap: onDelete, builder: (child) => CardButton( diff --git a/lib/booking/ui/pages/admin_pages/add_edit_manager_page.dart b/lib/booking/ui/pages/admin_pages/add_edit_manager_page.dart index c02ac990b2..eb6b445499 100644 --- a/lib/booking/ui/pages/admin_pages/add_edit_manager_page.dart +++ b/lib/booking/ui/pages/admin_pages/add_edit_manager_page.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:titan/admin/class/simple_group.dart'; -import 'package:titan/admin/providers/group_id_provider.dart'; +import 'package:titan/super_admin/class/simple_group.dart'; +import 'package:titan/super_admin/providers/group_id_provider.dart'; import 'package:titan/booking/class/manager.dart'; import 'package:titan/booking/providers/manager_list_provider.dart'; import 'package:titan/booking/providers/manager_provider.dart'; @@ -15,7 +15,7 @@ import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/layouts/item_chip.dart'; import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; import 'package:qlevar_router/qlevar_router.dart'; -import 'package:titan/admin/providers/group_list_provider.dart'; +import 'package:titan/super_admin/providers/group_list_provider.dart'; import 'package:titan/l10n/app_localizations.dart'; class AddEditManagerPage extends HookConsumerWidget { @@ -60,13 +60,13 @@ class AddEditManagerPage extends HookConsumerWidget { child: Column( children: [ const SizedBox(height: 50), - AdminEntry( + SuperAdminEntry( name: AppLocalizations.of(context)!.bookingManagerName, nameController: name, ), const SizedBox(height: 50), groupList.when( - data: (List data) => AdminScrollChips( + data: (List data) => SuperAdminScrollChips( isEdit: isEdit, data: data, dataKey: dataKey, @@ -97,7 +97,7 @@ class AddEditManagerPage extends HookConsumerWidget { }, ), const SizedBox(height: 50), - AdminShrinkButton( + SuperAdminShrinkButton( onTap: () async { await tokenExpireWrapper(ref, () async { Manager newManager = Manager( @@ -136,7 +136,7 @@ class AddEditManagerPage extends HookConsumerWidget { ), if (isEdit) ...[ const SizedBox(height: 30), - AdminShrinkButton( + SuperAdminShrinkButton( onTap: () async { await tokenExpireWrapper(ref, () async { await showDialog( diff --git a/lib/booking/ui/pages/admin_pages/add_edit_room_page.dart b/lib/booking/ui/pages/admin_pages/add_edit_room_page.dart index 17a2da96f0..c7f0f1a4c4 100644 --- a/lib/booking/ui/pages/admin_pages/add_edit_room_page.dart +++ b/lib/booking/ui/pages/admin_pages/add_edit_room_page.dart @@ -60,13 +60,13 @@ class AddEditRoomPage extends HookConsumerWidget { child: Column( children: [ const SizedBox(height: 50), - AdminEntry( + SuperAdminEntry( name: AppLocalizations.of(context)!.bookingRoomName, nameController: name, ), const SizedBox(height: 50), managerList.when( - data: (List data) => AdminScrollChips( + data: (List data) => SuperAdminScrollChips( data: data, isEdit: isEdit, dataKey: dataKey, @@ -97,7 +97,7 @@ class AddEditRoomPage extends HookConsumerWidget { }, ), const SizedBox(height: 50), - AdminShrinkButton( + SuperAdminShrinkButton( onTap: () async { await tokenExpireWrapper(ref, () async { Room newRoom = Room( @@ -131,7 +131,7 @@ class AddEditRoomPage extends HookConsumerWidget { ), if (isEdit) ...[ const SizedBox(height: 30), - AdminShrinkButton( + SuperAdminShrinkButton( onTap: () async { await tokenExpireWrapper(ref, () async { await showDialog( diff --git a/lib/booking/ui/pages/admin_pages/admin_entry.dart b/lib/booking/ui/pages/admin_pages/admin_entry.dart index 827422d3fa..2f0b0e7915 100644 --- a/lib/booking/ui/pages/admin_pages/admin_entry.dart +++ b/lib/booking/ui/pages/admin_pages/admin_entry.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import 'package:titan/tools/constants.dart'; -class AdminEntry extends StatelessWidget { +class SuperAdminEntry extends StatelessWidget { final String name; final TextEditingController nameController; - const AdminEntry({ + const SuperAdminEntry({ super.key, required this.name, required this.nameController, diff --git a/lib/booking/ui/pages/admin_pages/admin_page.dart b/lib/booking/ui/pages/admin_pages/admin_page.dart index 7c196517e2..547501228f 100644 --- a/lib/booking/ui/pages/admin_pages/admin_page.dart +++ b/lib/booking/ui/pages/admin_pages/admin_page.dart @@ -3,7 +3,7 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/providers/group_id_provider.dart'; +import 'package:titan/super_admin/providers/group_id_provider.dart'; import 'package:titan/booking/class/manager.dart'; import 'package:titan/service/class/room.dart'; import 'package:titan/booking/providers/confirmed_booking_list_provider.dart'; @@ -21,8 +21,8 @@ import 'package:titan/tools/ui/layouts/refresher.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/l10n/app_localizations.dart'; -class AdminPage extends HookConsumerWidget { - const AdminPage({super.key}); +class SuperAdminPage extends HookConsumerWidget { + const SuperAdminPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { diff --git a/lib/booking/ui/pages/admin_pages/admin_scroll_chips.dart b/lib/booking/ui/pages/admin_pages/admin_scroll_chips.dart index 0120862d73..79fc7b1c03 100644 --- a/lib/booking/ui/pages/admin_pages/admin_scroll_chips.dart +++ b/lib/booking/ui/pages/admin_pages/admin_scroll_chips.dart @@ -1,14 +1,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; -class AdminScrollChips extends HookWidget { +class SuperAdminScrollChips extends HookWidget { final bool isEdit; final List data; final GlobalKey dataKey; final String pageStorageKeyName; final Widget Function(T) builder; - const AdminScrollChips({ + const SuperAdminScrollChips({ super.key, required this.isEdit, required this.dataKey, diff --git a/lib/booking/ui/pages/admin_pages/admin_shrink_button.dart b/lib/booking/ui/pages/admin_pages/admin_shrink_button.dart index 8f163903bf..e549710d58 100644 --- a/lib/booking/ui/pages/admin_pages/admin_shrink_button.dart +++ b/lib/booking/ui/pages/admin_pages/admin_shrink_button.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; -class AdminShrinkButton extends StatelessWidget { +class SuperAdminShrinkButton extends StatelessWidget { final Future Function() onTap; final String buttonText; - const AdminShrinkButton({ + const SuperAdminShrinkButton({ super.key, required this.onTap, required this.buttonText, diff --git a/lib/booking/ui/pages/booking_pages/add_edit_booking_page.dart b/lib/booking/ui/pages/booking_pages/add_edit_booking_page.dart index 661cf5db3d..c0cf67a0af 100644 --- a/lib/booking/ui/pages/booking_pages/add_edit_booking_page.dart +++ b/lib/booking/ui/pages/booking_pages/add_edit_booking_page.dart @@ -115,7 +115,7 @@ class AddEditBookingPage extends HookConsumerWidget { const SizedBox(height: 20), AsyncChild( value: rooms, - builder: (context, data) => AdminScrollChips( + builder: (context, data) => SuperAdminScrollChips( key: scrollKey, isEdit: isEdit, dataKey: dataKey, diff --git a/lib/booking/ui/pages/detail_pages/detail_booking.dart b/lib/booking/ui/pages/detail_pages/detail_booking.dart index 094dcf7fcb..b146abe531 100644 --- a/lib/booking/ui/pages/detail_pages/detail_booking.dart +++ b/lib/booking/ui/pages/detail_pages/detail_booking.dart @@ -12,8 +12,8 @@ import 'package:url_launcher/url_launcher.dart'; import 'package:titan/l10n/app_localizations.dart'; class DetailBookingPage extends HookConsumerWidget { - final bool isAdmin; - const DetailBookingPage({super.key, required this.isAdmin}); + final bool isSuperAdmin; + const DetailBookingPage({super.key, required this.isSuperAdmin}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -76,7 +76,7 @@ class DetailBookingPage extends HookConsumerWidget { ), ), const SizedBox(height: 30), - if (isAdmin) + if (isSuperAdmin) Column( children: [ AutoSizeText( diff --git a/lib/booking/ui/pages/main_page/main_page.dart b/lib/booking/ui/pages/main_page/main_page.dart index 3a4a9387dd..b794158512 100644 --- a/lib/booking/ui/pages/main_page/main_page.dart +++ b/lib/booking/ui/pages/main_page/main_page.dart @@ -35,7 +35,7 @@ class BookingMainPage extends HookConsumerWidget { const double minCalendarHeight = 400; const double sumOfHeightOfOthersWidgets = 361; final isManager = ref.watch(isManagerProvider); - final isAdmin = ref.watch(isAdminProvider); + final isSuperAdmin = ref.watch(isSuperAdminProvider); final bookingsNotifier = ref.watch(userBookingListProvider.notifier); final confirmedBookingsNotifier = ref.watch( confirmedBookingListProvider.notifier, @@ -72,21 +72,21 @@ class BookingMainPage extends HookConsumerWidget { ), child: Column( children: [ - if (isAdmin | isManager) const SizedBox(height: 10), + if (isSuperAdmin | isManager) const SizedBox(height: 10), SizedBox( width: 300, child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ if (isManager) - AdminButton( + SuperAdminButton( text: AppLocalizations.of(context)!.bookingManagement, onTap: () { QR.to(BookingRouter.root + BookingRouter.manager); }, ), - if (isAdmin) - AdminButton( + if (isSuperAdmin) + SuperAdminButton( onTap: () { QR.to(BookingRouter.root + BookingRouter.admin); }, diff --git a/lib/booking/ui/pages/manager_page/list_booking.dart b/lib/booking/ui/pages/manager_page/list_booking.dart index c5a928de3f..1da087a858 100644 --- a/lib/booking/ui/pages/manager_page/list_booking.dart +++ b/lib/booking/ui/pages/manager_page/list_booking.dart @@ -101,7 +101,7 @@ class ListBooking extends HookConsumerWidget { items: bookings, itemBuilder: (context, e, i) => BookingCard( booking: e, - isAdmin: true, + isSuperAdmin: true, isDetail: false, onEdit: () { handleBooking(e); diff --git a/lib/cinema/providers/is_cinema_admin.dart b/lib/cinema/providers/is_cinema_admin.dart index 5a15b340c5..8665d693de 100644 --- a/lib/cinema/providers/is_cinema_admin.dart +++ b/lib/cinema/providers/is_cinema_admin.dart @@ -1,7 +1,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/user/providers/user_provider.dart'; -final isCinemaAdminProvider = StateProvider((ref) { +final isCinemaSuperAdminProvider = StateProvider((ref) { final me = ref.watch(userProvider); return me.groups .map((e) => e.id) diff --git a/lib/cinema/router.dart b/lib/cinema/router.dart index f488a03aa7..8e173c7373 100644 --- a/lib/cinema/router.dart +++ b/lib/cinema/router.dart @@ -51,9 +51,9 @@ class CinemaRouter { ), QRoute( path: admin, - builder: () => admin_page.AdminPage(), + builder: () => admin_page.SuperAdminPage(), middleware: [ - AdminMiddleware(ref, isCinemaAdminProvider), + SuperAdminMiddleware(ref, isCinemaSuperAdminProvider), DeferredLoadingMiddleware(admin_page.loadLibrary), ], children: [ diff --git a/lib/cinema/ui/pages/admin_page/admin_page.dart b/lib/cinema/ui/pages/admin_page/admin_page.dart index 46718e86ff..444abb3dfa 100644 --- a/lib/cinema/ui/pages/admin_page/admin_page.dart +++ b/lib/cinema/ui/pages/admin_page/admin_page.dart @@ -13,8 +13,8 @@ import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/l10n/app_localizations.dart'; -class AdminPage extends HookConsumerWidget { - const AdminPage({super.key}); +class SuperAdminPage extends HookConsumerWidget { + const SuperAdminPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -45,7 +45,7 @@ class AdminPage extends HookConsumerWidget { ), ), ...data.map( - (session) => AdminSessionCard( + (session) => SuperAdminSessionCard( session: session, onEdit: () { sessionNotifier.setSession(session); diff --git a/lib/cinema/ui/pages/admin_page/admin_session_card.dart b/lib/cinema/ui/pages/admin_page/admin_session_card.dart index 15ac4dfcb3..003da2d88b 100644 --- a/lib/cinema/ui/pages/admin_page/admin_session_card.dart +++ b/lib/cinema/ui/pages/admin_page/admin_session_card.dart @@ -9,11 +9,11 @@ import 'package:titan/tools/ui/builders/auto_loader_child.dart'; import 'package:titan/tools/ui/layouts/card_button.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; -class AdminSessionCard extends HookConsumerWidget { +class SuperAdminSessionCard extends HookConsumerWidget { final Session session; final VoidCallback onTap, onEdit; final Future Function() onDelete; - const AdminSessionCard({ + const SuperAdminSessionCard({ super.key, required this.session, required this.onTap, diff --git a/lib/cinema/ui/pages/main_page/main_page.dart b/lib/cinema/ui/pages/main_page/main_page.dart index f6fa825a1d..a02628fbd4 100644 --- a/lib/cinema/ui/pages/main_page/main_page.dart +++ b/lib/cinema/ui/pages/main_page/main_page.dart @@ -36,7 +36,7 @@ class CinemaMainPage extends HookConsumerWidget { sessionListPageControllerProvider(initialPage), ); final scrollNotifier = ref.watch(scrollProvider.notifier); - final isAdmin = ref.watch(isCinemaAdminProvider); + final isSuperAdmin = ref.watch(isCinemaSuperAdminProvider); final isWebFormat = ref.watch(isWebFormatProvider); pageController.addListener(() { scrollNotifier.setScroll(pageController.page!); @@ -71,8 +71,8 @@ class CinemaMainPage extends HookConsumerWidget { color: Colors.grey, ), ), - if (isAdmin) - AdminButton( + if (isSuperAdmin) + SuperAdminButton( onTap: () { QR.to(CinemaRouter.root + CinemaRouter.admin); initialPageNotifier.setMainPageIndex(currentPage); diff --git a/lib/event/providers/is_admin_provider.dart b/lib/event/providers/is_admin_provider.dart index 78f1998e95..aabefd9a5a 100644 --- a/lib/event/providers/is_admin_provider.dart +++ b/lib/event/providers/is_admin_provider.dart @@ -1,7 +1,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/user/providers/user_provider.dart'; -final isEventAdminProvider = StateProvider((ref) { +final isEventSuperAdminProvider = StateProvider((ref) { final me = ref.watch(userProvider); return me.groups .map((e) => e.id) diff --git a/lib/event/router.dart b/lib/event/router.dart index a4ba1d6aee..2f4a58686e 100644 --- a/lib/event/router.dart +++ b/lib/event/router.dart @@ -44,15 +44,15 @@ class EventRouter { children: [ QRoute( path: admin, - builder: () => admin_page.AdminPage(), + builder: () => admin_page.SuperAdminPage(), middleware: [ - AdminMiddleware(ref, isEventAdminProvider), + SuperAdminMiddleware(ref, isEventSuperAdminProvider), DeferredLoadingMiddleware(admin_page.loadLibrary), ], children: [ QRoute( path: detail, - builder: () => detail_page.DetailPage(isAdmin: true), + builder: () => detail_page.DetailPage(isSuperAdmin: true), middleware: [DeferredLoadingMiddleware(detail_page.loadLibrary)], ), QRoute( @@ -73,7 +73,7 @@ class EventRouter { ), QRoute( path: detail, - builder: () => detail_page.DetailPage(isAdmin: false), + builder: () => detail_page.DetailPage(isSuperAdmin: false), middleware: [DeferredLoadingMiddleware(detail_page.loadLibrary)], ), ], diff --git a/lib/event/ui/components/event_ui.dart b/lib/event/ui/components/event_ui.dart index 51f48f1c1b..c6e0966b01 100644 --- a/lib/event/ui/components/event_ui.dart +++ b/lib/event/ui/components/event_ui.dart @@ -18,13 +18,13 @@ import 'package:titan/l10n/app_localizations.dart'; class EventUi extends ConsumerWidget { final Event event; - final bool isDetailPage, isAdmin; + final bool isDetailPage, isSuperAdmin; final Function()? onEdit, onConfirm, onDecline, onCopy, onInfo; const EventUi({ super.key, required this.event, this.isDetailPage = false, - this.isAdmin = false, + this.isSuperAdmin = false, this.onEdit, this.onConfirm, this.onDecline, @@ -48,7 +48,7 @@ class EventUi extends ConsumerWidget { return GestureDetector( behavior: HitTestBehavior.opaque, onTap: () { - if (!isDetailPage || isAdmin) { + if (!isDetailPage || isSuperAdmin) { onInfo?.call(); } }, @@ -290,8 +290,8 @@ class EventUi extends ConsumerWidget { ), ], ), - if (isAdmin) const Spacer(), - if (isAdmin) + if (isSuperAdmin) const Spacer(), + if (isSuperAdmin) Row( children: [ GestureDetector( @@ -319,7 +319,7 @@ class EventUi extends ConsumerWidget { } }, child: CardButton( - borderColor: isAdmin + borderColor: isSuperAdmin ? event.decision == Decision.approved ? Colors.black : Colors.transparent @@ -339,7 +339,7 @@ class EventUi extends ConsumerWidget { }, child: CardButton( color: Colors.black, - borderColor: isAdmin + borderColor: isSuperAdmin ? event.decision == Decision.declined ? Colors.white : Colors.transparent diff --git a/lib/event/ui/pages/admin_page/admin_page.dart b/lib/event/ui/pages/admin_page/admin_page.dart index ba9eee208a..31de8fb4a6 100644 --- a/lib/event/ui/pages/admin_page/admin_page.dart +++ b/lib/event/ui/pages/admin_page/admin_page.dart @@ -10,8 +10,8 @@ import 'package:titan/tools/ui/widgets/calendar.dart'; import 'package:syncfusion_flutter_calendar/calendar.dart'; import 'package:titan/l10n/app_localizations.dart'; -class AdminPage extends HookConsumerWidget { - const AdminPage({super.key}); +class SuperAdminPage extends HookConsumerWidget { + const SuperAdminPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { diff --git a/lib/event/ui/pages/admin_page/list_event.dart b/lib/event/ui/pages/admin_page/list_event.dart index 7022e09598..5afe0a0af2 100644 --- a/lib/event/ui/pages/admin_page/list_event.dart +++ b/lib/event/ui/pages/admin_page/list_event.dart @@ -84,7 +84,7 @@ class ListEvent extends HookConsumerWidget { itemBuilder: (context, e, i) => EventUi( event: e, isDetailPage: true, - isAdmin: true, + isSuperAdmin: true, onEdit: () { eventNotifier.setEvent(e); QR.to( diff --git a/lib/event/ui/pages/detail_page/detail_page.dart b/lib/event/ui/pages/detail_page/detail_page.dart index b7f0dc08f0..7b271c895e 100644 --- a/lib/event/ui/pages/detail_page/detail_page.dart +++ b/lib/event/ui/pages/detail_page/detail_page.dart @@ -9,8 +9,8 @@ import 'package:url_launcher/url_launcher.dart'; import 'package:titan/l10n/app_localizations.dart'; class DetailPage extends HookConsumerWidget { - final bool isAdmin; - const DetailPage({super.key, required this.isAdmin}); + final bool isSuperAdmin; + const DetailPage({super.key, required this.isSuperAdmin}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -68,7 +68,7 @@ class DetailPage extends HookConsumerWidget { ), ), const SizedBox(height: 20), - if (isAdmin) + if (isSuperAdmin) Column( children: [ GestureDetector( diff --git a/lib/event/ui/pages/main_page/main_page.dart b/lib/event/ui/pages/main_page/main_page.dart index e99d9db759..88dd53fd5b 100644 --- a/lib/event/ui/pages/main_page/main_page.dart +++ b/lib/event/ui/pages/main_page/main_page.dart @@ -20,7 +20,7 @@ class EventMainPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final isAdmin = ref.watch(isEventAdminProvider); + final isSuperAdmin = ref.watch(isEventSuperAdminProvider); final eventNotifier = ref.watch(eventProvider.notifier); final eventListNotifier = ref.watch(eventEventListProvider.notifier); final events = ref.watch(eventEventListProvider); @@ -52,8 +52,8 @@ class EventMainPage extends HookConsumerWidget { color: Colors.grey, ), ), - if (isAdmin) - AdminButton( + if (isSuperAdmin) + SuperAdminButton( onTap: () { QR.to(EventRouter.root + EventRouter.admin); }, diff --git a/lib/feed/router.dart b/lib/feed/router.dart index b5fece5628..997ec5d9a4 100644 --- a/lib/feed/router.dart +++ b/lib/feed/router.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/admin/providers/is_admin_provider.dart'; +import 'package:titan/super_admin/providers/is_admin_provider.dart'; import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/navigation/class/module.dart'; import 'package:titan/feed/ui/pages/admin_page/admin_page.dart' @@ -40,10 +40,10 @@ class FeedRouter { children: [ QRoute( path: admin, - builder: () => admin_page.AdminPage(), + builder: () => admin_page.SuperAdminPage(), middleware: [ AuthenticatedMiddleware(ref), - AdminMiddleware(ref, isAdminProvider), + SuperAdminMiddleware(ref, isSuperAdminProvider), DeferredLoadingMiddleware(admin_page.loadLibrary), ], ), diff --git a/lib/feed/ui/pages/admin_page/admin_page.dart b/lib/feed/ui/pages/admin_page/admin_page.dart index 09908229cb..76f005b612 100644 --- a/lib/feed/ui/pages/admin_page/admin_page.dart +++ b/lib/feed/ui/pages/admin_page/admin_page.dart @@ -6,8 +6,8 @@ import 'package:titan/feed/ui/pages/admin_page/event_form.dart'; import 'package:titan/feed/ui/pages/admin_page/post_form.dart'; import 'package:titan/feed/ui/pages/admin_page/tab_navigation.dart'; -class AdminPage extends HookConsumerWidget { - const AdminPage({super.key}); +class SuperAdminPage extends HookConsumerWidget { + const SuperAdminPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { diff --git a/lib/feed/ui/pages/main_page/main_page.dart b/lib/feed/ui/pages/main_page/main_page.dart index ac2702a6bb..40947455b7 100644 --- a/lib/feed/ui/pages/main_page/main_page.dart +++ b/lib/feed/ui/pages/main_page/main_page.dart @@ -3,7 +3,7 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:qlevar_router/qlevar_router.dart'; -import 'package:titan/admin/providers/is_admin_provider.dart'; +import 'package:titan/super_admin/providers/is_admin_provider.dart'; import 'package:titan/feed/class/feed_item.dart'; import 'package:titan/feed/router.dart'; import 'package:titan/feed/ui/feed.dart'; @@ -24,7 +24,7 @@ class FeedMainPage extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final feedItems = useState>(FeedItem.getFakeItems()); final filteredItems = useState>(feedItems.value); - final isAdmin = ref.watch(isAdminProvider); + final isSuperAdmin = ref.watch(isSuperAdminProvider); final scrollController = useScrollController(); return FeedTemplate( @@ -93,7 +93,7 @@ class FeedMainPage extends HookConsumerWidget { color: ColorConstants.title, ), ), - if (isAdmin) + if (isSuperAdmin) CustomIconButton( icon: HeroIcon( HeroIcons.plus, diff --git a/lib/home/router.dart b/lib/home/router.dart index 1ab0935059..71bdd6f66d 100644 --- a/lib/home/router.dart +++ b/lib/home/router.dart @@ -35,7 +35,7 @@ class HomeRouter { children: [ QRoute( path: detail, - builder: () => detail_page.DetailPage(isAdmin: false), + builder: () => detail_page.DetailPage(isSuperAdmin: false), middleware: [DeferredLoadingMiddleware(detail_page.loadLibrary)], ), ], diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 8a3082e195..02fe67f2cc 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -15,7 +15,7 @@ "adminAddedSchool": "School created", "adminAddedStructure": "Structure added", "adminEditedStructure": "Structure edited", - "adminAdministration": "Administration", + "adminSuperAdministration": "SuperAdministration", "adminAssociationMembership": "Membership", "adminAssociationMembershipName": "Membership name", "adminAssociationsMemberships": "Memberships", @@ -83,7 +83,7 @@ "advertAddedAdvert": "Advert published", "advertAddedAnnouncer": "Announcer added", "advertAddingError": "Error while adding", - "advertAdmin": "Admin", + "advertSuperAdmin": "SuperAdmin", "advertAdvert": "Advert", "advertChoosingAnnouncer": "Please choose an announcer", "advertChoosingPoster": "Please choose an image", @@ -132,7 +132,7 @@ "amapAddingError": "Error while adding", "amapAddingProduct": "Add a product", "amapAddOrder": "Add an order", - "amapAdmin": "Admin", + "amapSuperAdmin": "SuperAdmin", "amapAlreadyExistCommand": "An order already exists for this date", "amapAmap": "Amap", "amapAmount": "Balance", @@ -245,7 +245,7 @@ "bookingAddedManager": "Manager added", "bookingAddingError": "Error while adding", "bookingAddManager": "Add manager", - "bookingAdminPage": "Admin", + "bookingSuperAdminPage": "SuperAdmin", "bookingAllDay": "All day", "bookingBookedFor": "Booked for", "bookingBooking": "Booking", @@ -367,7 +367,7 @@ "cinemaStartHour": "Start hour", "cinemaTagline": "Tagline", "cinemaThe": "The", - "drawerAdmin": "Administration", + "drawerSuperAdmin": "SuperAdministration", "drawerAndroidAppLink": "https://play.google.com/store/apps/details?id=fr.myecl.titan", "drawerCopied": "Copied!", "drawerDownloadAppOnMobileDevice": "This site is the web version of the MyECL app. We invite you to download the app. Use this site only if you have problems with the app.\n", @@ -466,7 +466,7 @@ "loanAddedObject": "Object added", "loanAddedRoom": "Room added", "loanAddingError": "Error while adding", - "loanAdmin": "Administrator", + "loanSuperAdmin": "SuperAdministrator", "loanAvailable": "Available", "loanAvailableMultiple": "Available", "loanBorrowed": "Borrowed", @@ -605,7 +605,7 @@ "othersUnableToConnectToServer": "Unable to connect to the server", "othersVersion": "Version", "othersNoModule": "No modules available, please try again later 😢😢", - "othersAdmin": "Admin", + "othersSuperAdmin": "SuperAdmin", "othersError": "An error occurred", "othersNoValue": "Please enter a value", "othersInvalidNumber": "Please enter a number", @@ -639,8 +639,8 @@ "phonebookAddingError": "Error adding", "phonebookAddMember": "Add a member", "phonebookAddRole": "Add a role", - "phonebookAdmin": "Admin", - "phonebookAdminPage": "Admin page", + "phonebookSuperAdmin": "SuperAdmin", + "phonebookSuperAdminPage": "SuperAdmin page", "phonebookAll": "All", "phonebookApparentName": "Public role name:", "phonebookAssociation": "Association:", @@ -960,7 +960,7 @@ "seedLibraryWriteReference": "Please write the following reference: ", "settingsAccount": "Account", "settingsAddProfilePicture": "Add a photo", - "settingsAdmin": "Administrator", + "settingsSuperAdmin": "SuperAdministrator", "settingsAskHelp": "Ask for help", "settingsAssociation": "Association", "settingsBirthday": "Birthday", @@ -1137,7 +1137,7 @@ "moduleSettings": "Settings", "moduleFeed": "Feed", "moduleStyleGuide": "StyleGuide", - "moduleAdmin": "Administration", + "moduleSuperAdmin": "SuperAdministration", "moduleOthers": "Others", "modulePayment": "Payment", "paiementTopUp": "Top-up", @@ -1188,7 +1188,7 @@ "paiementHistory": "History", "paiementHandOver": "Handover", "paiementStores": "Associations", - "paiementAdmin": "Administrator", + "paiementSuperAdmin": "SuperAdministrator", "paiementSuccededTransaction": "Successful payment", "paiementNewCGU": "New Terms of Service", "paiementDecline": "Decline", @@ -1233,7 +1233,7 @@ "paiementSeeHistory": "View history", "paiementCancelTransactions": "Cancel transactions", "paiementManageSellers": "Manage sellers", - "paiementStructureAdmin": "Structure administrator", + "paiementStructureSuperAdmin": "Structure administrator", "paiementRightsOf": "Rights of", "paiementRightsUpdated": "Rights updated", "paiementRightsUpdateError": "Error while updating rights", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index e42faa4d8d..74b757d375 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -15,7 +15,7 @@ "adminAddedSchool": "École créée", "adminAddedStructure": "Structure ajoutée", "adminEditedStructure": "Structure modifiée", - "adminAdministration": "Administration", + "adminSuperAdministration": "SuperAdministration", "adminAssociationMembership": "Adhésion", "adminAssociationMembershipName": "Nom de l'adhésion", "adminAssociationsMemberships": "Adhésions", @@ -51,7 +51,7 @@ "adminGroups": "Groupes", "adminLoaningGroup": "Groupe de prêt", "adminLooking": "Recherche", - "adminManager": "Administrateur de la structure", + "adminManager": "SuperAdministrateur de la structure", "adminMaximum": "Maximum", "adminMembers": "Membres", "adminMembershipAddingError": "Erreur lors de l'ajout (surement dû à une superposition de dates)", @@ -83,7 +83,7 @@ "advertAddedAdvert": "Annonce publiée", "advertAddedAnnouncer": "Annonceur ajouté", "advertAddingError": "Erreur lors de l'ajout", - "advertAdmin": "Admin", + "advertSuperAdmin": "SuperAdmin", "advertAdvert": "Annonce", "advertChoosingAnnouncer": "Veuillez choisir un annonceur", "advertChoosingPoster": "Veuillez choisir une image", @@ -132,7 +132,7 @@ "amapAddingError": "Erreur lors de l'ajout", "amapAddingProduct": "Ajouter un produit", "amapAddOrder": "Ajouter une commande", - "amapAdmin": "Admin", + "amapSuperAdmin": "SuperAdmin", "amapAlreadyExistCommand": "Il existe déjà une commande à cette date", "amapAmap": "Amap", "amapAmount": "Solde", @@ -245,7 +245,7 @@ "bookingAddedManager": "Gestionnaire ajouté", "bookingAddingError": "Erreur lors de l'ajout", "bookingAddManager": "Ajouter un gestionnaire", - "bookingAdminPage": "Administrateur", + "bookingSuperAdminPage": "SuperAdministrateur", "bookingAllDay": "Toute la journée", "bookingBookedFor": "Réservé pour", "bookingBooking": "Réservation", @@ -367,7 +367,7 @@ "cinemaStartHour": "Heure de début", "cinemaTagline": "Slogan", "cinemaThe": "Le", - "drawerAdmin": "Administration", + "drawerSuperAdmin": "SuperAdministration", "drawerAndroidAppLink": "https://play.google.com/store/apps/details?id=fr.myecl.titan", "drawerCopied": "Copié !", "drawerDownloadAppOnMobileDevice": "Ce site est la version Web de l'application MyECL. Nous vous invitons à télécharger l'application. N'utilisez ce site qu'en cas de problème avec l'application.\n", @@ -466,7 +466,7 @@ "loanAddedObject": "Objet ajouté", "loanAddedRoom": "Salle ajoutée", "loanAddingError": "Erreur lors de l'ajout", - "loanAdmin": "Administrateur", + "loanSuperAdmin": "SuperAdministrateur", "loanAvailable": "Disponible", "loanAvailableMultiple": "Disponibles", "loanBorrowed": "Emprunté", @@ -605,7 +605,7 @@ "othersUnableToConnectToServer": "Impossible de se connecter au serveur", "othersVersion": "Version", "othersNoModule": "Aucun module disponible, veuillez réessayer ultérieurement 😢😢", - "othersAdmin": "Admin", + "othersSuperAdmin": "SuperAdmin", "othersError": "Une erreur est survenue", "othersNoValue": "Veuillez entrer une valeur", "othersInvalidNumber": "Veuillez entrer un nombre", @@ -639,8 +639,8 @@ "phonebookAddingError": "Erreur lors de l'ajout", "phonebookAddMember": "Ajouter un membre", "phonebookAddRole": "Ajouter un rôle", - "phonebookAdmin": "Admin", - "phonebookAdminPage": "Page Administrateur", + "phonebookSuperAdmin": "SuperAdmin", + "phonebookSuperAdminPage": "Page SuperAdministrateur", "phonebookAll": "Toutes", "phonebookApparentName": "Nom public du rôle :", "phonebookAssociation": "Association :", @@ -960,7 +960,7 @@ "seedLibraryWriteReference": "Veuillez écrire la référence suivante : ", "settingsAccount": "Compte", "settingsAddProfilePicture": "Ajouter une photo", - "settingsAdmin": "Administrateur", + "settingsSuperAdmin": "SuperAdministrateur", "settingsAskHelp": "Demander de l'aide", "settingsAssociation": "Association", "settingsBirthday": "Date de naissance", @@ -1136,7 +1136,7 @@ "moduleSettings": "Paramètres", "moduleFeed": "Feed", "moduleStyleGuide": "StyleGuide", - "moduleAdmin": "Adminitration", + "moduleSuperAdmin": "SuperAdminitration", "moduleOthers": "Autres", "modulePayment": "Paiement", "paiementTopUp" : "Recharge", @@ -1187,7 +1187,7 @@ "paiementHistory": "Historique", "paiementHandOver": "Passation", "paiementStores": "Associations", - "paiementAdmin": "Administrateur", + "paiementSuperAdmin": "SuperAdministrateur", "paiementSuccededTransaction": "Paiement réussi", "paiementNewCGU" : "Nouvelles Conditions Générales d'Utilisation", "paiementDecline": "Refuser", @@ -1232,7 +1232,7 @@ "paiementSeeHistory": "Voir l'historique", "paiementCancelTransactions": "Annuler les transactions", "paiementManageSellers": "Gérer les vendeurs", - "paiementStructureAdmin": "Administrateur de la structure", + "paiementStructureSuperAdmin": "SuperAdministrateur de la structure", "paiementRightsOf": "Droits de", "paiementRightsUpdated": "Droits mis à jour", "paiementRightsUpdateError": "Erreur lors de la mise à jour des droits", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 3df8b0b8c7..e9d0de5aeb 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -188,11 +188,11 @@ abstract class AppLocalizations { /// **'Structure modifiée'** String get adminEditedStructure; - /// No description provided for @adminAdministration. + /// No description provided for @adminSuperAdministration. /// /// In fr, this message translates to: - /// **'Administration'** - String get adminAdministration; + /// **'SuperAdministration'** + String get adminSuperAdministration; /// No description provided for @adminAssociationMembership. /// @@ -407,7 +407,7 @@ abstract class AppLocalizations { /// No description provided for @adminManager. /// /// In fr, this message translates to: - /// **'Administrateur de la structure'** + /// **'SuperAdministrateur de la structure'** String get adminManager; /// No description provided for @adminMaximum. @@ -596,11 +596,11 @@ abstract class AppLocalizations { /// **'Erreur lors de l\'ajout'** String get advertAddingError; - /// No description provided for @advertAdmin. + /// No description provided for @advertSuperAdmin. /// /// In fr, this message translates to: - /// **'Admin'** - String get advertAdmin; + /// **'SuperAdmin'** + String get advertSuperAdmin; /// No description provided for @advertAdvert. /// @@ -890,11 +890,11 @@ abstract class AppLocalizations { /// **'Ajouter une commande'** String get amapAddOrder; - /// No description provided for @amapAdmin. + /// No description provided for @amapSuperAdmin. /// /// In fr, this message translates to: - /// **'Admin'** - String get amapAdmin; + /// **'SuperAdmin'** + String get amapSuperAdmin; /// No description provided for @amapAlreadyExistCommand. /// @@ -1568,11 +1568,11 @@ abstract class AppLocalizations { /// **'Ajouter un gestionnaire'** String get bookingAddManager; - /// No description provided for @bookingAdminPage. + /// No description provided for @bookingSuperAdminPage. /// /// In fr, this message translates to: - /// **'Administrateur'** - String get bookingAdminPage; + /// **'SuperAdministrateur'** + String get bookingSuperAdminPage; /// No description provided for @bookingAllDay. /// @@ -2300,11 +2300,11 @@ abstract class AppLocalizations { /// **'Le'** String get cinemaThe; - /// No description provided for @drawerAdmin. + /// No description provided for @drawerSuperAdmin. /// /// In fr, this message translates to: - /// **'Administration'** - String get drawerAdmin; + /// **'SuperAdministration'** + String get drawerSuperAdmin; /// No description provided for @drawerAndroidAppLink. /// @@ -2894,11 +2894,11 @@ abstract class AppLocalizations { /// **'Erreur lors de l\'ajout'** String get loanAddingError; - /// No description provided for @loanAdmin. + /// No description provided for @loanSuperAdmin. /// /// In fr, this message translates to: - /// **'Administrateur'** - String get loanAdmin; + /// **'SuperAdministrateur'** + String get loanSuperAdmin; /// No description provided for @loanAvailable. /// @@ -3728,11 +3728,11 @@ abstract class AppLocalizations { /// **'Aucun module disponible, veuillez réessayer ultérieurement 😢😢'** String get othersNoModule; - /// No description provided for @othersAdmin. + /// No description provided for @othersSuperAdmin. /// /// In fr, this message translates to: - /// **'Admin'** - String get othersAdmin; + /// **'SuperAdmin'** + String get othersSuperAdmin; /// No description provided for @othersError. /// @@ -3932,17 +3932,17 @@ abstract class AppLocalizations { /// **'Ajouter un rôle'** String get phonebookAddRole; - /// No description provided for @phonebookAdmin. + /// No description provided for @phonebookSuperAdmin. /// /// In fr, this message translates to: - /// **'Admin'** - String get phonebookAdmin; + /// **'SuperAdmin'** + String get phonebookSuperAdmin; - /// No description provided for @phonebookAdminPage. + /// No description provided for @phonebookSuperAdminPage. /// /// In fr, this message translates to: - /// **'Page Administrateur'** - String get phonebookAdminPage; + /// **'Page SuperAdministrateur'** + String get phonebookSuperAdminPage; /// No description provided for @phonebookAll. /// @@ -5858,11 +5858,11 @@ abstract class AppLocalizations { /// **'Ajouter une photo'** String get settingsAddProfilePicture; - /// No description provided for @settingsAdmin. + /// No description provided for @settingsSuperAdmin. /// /// In fr, this message translates to: - /// **'Administrateur'** - String get settingsAdmin; + /// **'SuperAdministrateur'** + String get settingsSuperAdmin; /// No description provided for @settingsAskHelp. /// @@ -6872,11 +6872,11 @@ abstract class AppLocalizations { /// **'StyleGuide'** String get moduleStyleGuide; - /// No description provided for @moduleAdmin. + /// No description provided for @moduleSuperAdmin. /// /// In fr, this message translates to: - /// **'Adminitration'** - String get moduleAdmin; + /// **'SuperAdminitration'** + String get moduleSuperAdmin; /// No description provided for @moduleOthers. /// @@ -7178,11 +7178,11 @@ abstract class AppLocalizations { /// **'Associations'** String get paiementStores; - /// No description provided for @paiementAdmin. + /// No description provided for @paiementSuperAdmin. /// /// In fr, this message translates to: - /// **'Administrateur'** - String get paiementAdmin; + /// **'SuperAdministrateur'** + String get paiementSuperAdmin; /// No description provided for @paiementSuccededTransaction. /// @@ -7448,11 +7448,11 @@ abstract class AppLocalizations { /// **'Gérer les vendeurs'** String get paiementManageSellers; - /// No description provided for @paiementStructureAdmin. + /// No description provided for @paiementStructureSuperAdmin. /// /// In fr, this message translates to: - /// **'Administrateur de la structure'** - String get paiementStructureAdmin; + /// **'SuperAdministrateur de la structure'** + String get paiementStructureSuperAdmin; /// No description provided for @paiementRightsOf. /// diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 16efee4228..1e02c2af5d 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -54,7 +54,7 @@ class AppLocalizationsEn extends AppLocalizations { String get adminEditedStructure => 'Structure edited'; @override - String get adminAdministration => 'Administration'; + String get adminSuperAdministration => 'SuperAdministration'; @override String get adminAssociationMembership => 'Membership'; @@ -260,7 +260,7 @@ class AppLocalizationsEn extends AppLocalizations { String get advertAddingError => 'Error while adding'; @override - String get advertAdmin => 'Admin'; + String get advertSuperAdmin => 'SuperAdmin'; @override String get advertAdvert => 'Advert'; @@ -407,7 +407,7 @@ class AppLocalizationsEn extends AppLocalizations { String get amapAddOrder => 'Add an order'; @override - String get amapAdmin => 'Admin'; + String get amapSuperAdmin => 'SuperAdmin'; @override String get amapAlreadyExistCommand => 'An order already exists for this date'; @@ -751,7 +751,7 @@ class AppLocalizationsEn extends AppLocalizations { String get bookingAddManager => 'Add manager'; @override - String get bookingAdminPage => 'Admin'; + String get bookingSuperAdminPage => 'SuperAdmin'; @override String get bookingAllDay => 'All day'; @@ -1121,7 +1121,7 @@ class AppLocalizationsEn extends AppLocalizations { String get cinemaThe => 'The'; @override - String get drawerAdmin => 'Administration'; + String get drawerSuperAdmin => 'SuperAdministration'; @override String get drawerAndroidAppLink => @@ -1422,7 +1422,7 @@ class AppLocalizationsEn extends AppLocalizations { String get loanAddingError => 'Error while adding'; @override - String get loanAdmin => 'Administrator'; + String get loanSuperAdmin => 'SuperAdministrator'; @override String get loanAvailable => 'Available'; @@ -1849,7 +1849,7 @@ class AppLocalizationsEn extends AppLocalizations { 'No modules available, please try again later 😢😢'; @override - String get othersAdmin => 'Admin'; + String get othersSuperAdmin => 'SuperAdmin'; @override String get othersError => 'An error occurred'; @@ -1951,10 +1951,10 @@ class AppLocalizationsEn extends AppLocalizations { String get phonebookAddRole => 'Add a role'; @override - String get phonebookAdmin => 'Admin'; + String get phonebookSuperAdmin => 'SuperAdmin'; @override - String get phonebookAdminPage => 'Admin page'; + String get phonebookSuperAdminPage => 'SuperAdmin page'; @override String get phonebookAll => 'All'; @@ -2942,7 +2942,7 @@ class AppLocalizationsEn extends AppLocalizations { String get settingsAddProfilePicture => 'Add a photo'; @override - String get settingsAdmin => 'Administrator'; + String get settingsSuperAdmin => 'SuperAdministrator'; @override String get settingsAskHelp => 'Ask for help'; @@ -3466,7 +3466,7 @@ class AppLocalizationsEn extends AppLocalizations { String get moduleStyleGuide => 'StyleGuide'; @override - String get moduleAdmin => 'Administration'; + String get moduleSuperAdmin => 'SuperAdministration'; @override String get moduleOthers => 'Others'; @@ -3627,7 +3627,7 @@ class AppLocalizationsEn extends AppLocalizations { String get paiementStores => 'Associations'; @override - String get paiementAdmin => 'Administrator'; + String get paiementSuperAdmin => 'SuperAdministrator'; @override String get paiementSuccededTransaction => 'Successful payment'; @@ -3767,7 +3767,7 @@ class AppLocalizationsEn extends AppLocalizations { String get paiementManageSellers => 'Manage sellers'; @override - String get paiementStructureAdmin => 'Structure administrator'; + String get paiementStructureSuperAdmin => 'Structure administrator'; @override String get paiementRightsOf => 'Rights of'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 17129e7eac..71581252cf 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -54,7 +54,7 @@ class AppLocalizationsFr extends AppLocalizations { String get adminEditedStructure => 'Structure modifiée'; @override - String get adminAdministration => 'Administration'; + String get adminSuperAdministration => 'SuperAdministration'; @override String get adminAssociationMembership => 'Adhésion'; @@ -163,7 +163,7 @@ class AppLocalizationsFr extends AppLocalizations { String get adminLooking => 'Recherche'; @override - String get adminManager => 'Administrateur de la structure'; + String get adminManager => 'SuperAdministrateur de la structure'; @override String get adminMaximum => 'Maximum'; @@ -261,7 +261,7 @@ class AppLocalizationsFr extends AppLocalizations { String get advertAddingError => 'Erreur lors de l\'ajout'; @override - String get advertAdmin => 'Admin'; + String get advertSuperAdmin => 'SuperAdmin'; @override String get advertAdvert => 'Annonce'; @@ -408,7 +408,7 @@ class AppLocalizationsFr extends AppLocalizations { String get amapAddOrder => 'Ajouter une commande'; @override - String get amapAdmin => 'Admin'; + String get amapSuperAdmin => 'SuperAdmin'; @override String get amapAlreadyExistCommand => @@ -754,7 +754,7 @@ class AppLocalizationsFr extends AppLocalizations { String get bookingAddManager => 'Ajouter un gestionnaire'; @override - String get bookingAdminPage => 'Administrateur'; + String get bookingSuperAdminPage => 'SuperAdministrateur'; @override String get bookingAllDay => 'Toute la journée'; @@ -1125,7 +1125,7 @@ class AppLocalizationsFr extends AppLocalizations { String get cinemaThe => 'Le'; @override - String get drawerAdmin => 'Administration'; + String get drawerSuperAdmin => 'SuperAdministration'; @override String get drawerAndroidAppLink => @@ -1428,7 +1428,7 @@ class AppLocalizationsFr extends AppLocalizations { String get loanAddingError => 'Erreur lors de l\'ajout'; @override - String get loanAdmin => 'Administrateur'; + String get loanSuperAdmin => 'SuperAdministrateur'; @override String get loanAvailable => 'Disponible'; @@ -1856,7 +1856,7 @@ class AppLocalizationsFr extends AppLocalizations { 'Aucun module disponible, veuillez réessayer ultérieurement 😢😢'; @override - String get othersAdmin => 'Admin'; + String get othersSuperAdmin => 'SuperAdmin'; @override String get othersError => 'Une erreur est survenue'; @@ -1960,10 +1960,10 @@ class AppLocalizationsFr extends AppLocalizations { String get phonebookAddRole => 'Ajouter un rôle'; @override - String get phonebookAdmin => 'Admin'; + String get phonebookSuperAdmin => 'SuperAdmin'; @override - String get phonebookAdminPage => 'Page Administrateur'; + String get phonebookSuperAdminPage => 'Page SuperAdministrateur'; @override String get phonebookAll => 'Toutes'; @@ -2961,7 +2961,7 @@ class AppLocalizationsFr extends AppLocalizations { String get settingsAddProfilePicture => 'Ajouter une photo'; @override - String get settingsAdmin => 'Administrateur'; + String get settingsSuperAdmin => 'SuperAdministrateur'; @override String get settingsAskHelp => 'Demander de l\'aide'; @@ -3493,7 +3493,7 @@ class AppLocalizationsFr extends AppLocalizations { String get moduleStyleGuide => 'StyleGuide'; @override - String get moduleAdmin => 'Adminitration'; + String get moduleSuperAdmin => 'SuperAdminitration'; @override String get moduleOthers => 'Autres'; @@ -3661,7 +3661,7 @@ class AppLocalizationsFr extends AppLocalizations { String get paiementStores => 'Associations'; @override - String get paiementAdmin => 'Administrateur'; + String get paiementSuperAdmin => 'SuperAdministrateur'; @override String get paiementSuccededTransaction => 'Paiement réussi'; @@ -3805,7 +3805,8 @@ class AppLocalizationsFr extends AppLocalizations { String get paiementManageSellers => 'Gérer les vendeurs'; @override - String get paiementStructureAdmin => 'Administrateur de la structure'; + String get paiementStructureSuperAdmin => + 'SuperAdministrateur de la structure'; @override String get paiementRightsOf => 'Droits de'; diff --git a/lib/loan/providers/admin_history_loan_list_provider.dart b/lib/loan/providers/admin_history_loan_list_provider.dart index 4cbc9a1787..273cbf73e7 100644 --- a/lib/loan/providers/admin_history_loan_list_provider.dart +++ b/lib/loan/providers/admin_history_loan_list_provider.dart @@ -7,17 +7,17 @@ import 'package:titan/loan/providers/user_loaner_list_provider.dart'; import 'package:titan/tools/providers/map_provider.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; -class AdminHistoryLoanListNotifier extends MapNotifier { - AdminHistoryLoanListNotifier() : super(); +class SuperAdminHistoryLoanListNotifier extends MapNotifier { + SuperAdminHistoryLoanListNotifier() : super(); } final adminHistoryLoanListProvider = StateNotifierProvider< - AdminHistoryLoanListNotifier, + SuperAdminHistoryLoanListNotifier, Map>?> >((ref) { - AdminHistoryLoanListNotifier adminLoanListNotifier = - AdminHistoryLoanListNotifier(); + SuperAdminHistoryLoanListNotifier adminLoanListNotifier = + SuperAdminHistoryLoanListNotifier(); tokenExpireWrapperAuth(ref, () async { final loaners = ref.watch(loanerList); final loaner = ref.watch(loanerProvider); diff --git a/lib/loan/providers/admin_loan_list_provider.dart b/lib/loan/providers/admin_loan_list_provider.dart index 68fd332dc5..70a6d7b9a3 100644 --- a/lib/loan/providers/admin_loan_list_provider.dart +++ b/lib/loan/providers/admin_loan_list_provider.dart @@ -7,16 +7,17 @@ import 'package:titan/loan/providers/user_loaner_list_provider.dart'; import 'package:titan/tools/providers/map_provider.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; -class AdminLoanListNotifier extends MapNotifier { - AdminLoanListNotifier() : super(); +class SuperAdminLoanListNotifier extends MapNotifier { + SuperAdminLoanListNotifier() : super(); } final adminLoanListProvider = StateNotifierProvider< - AdminLoanListNotifier, + SuperAdminLoanListNotifier, Map>?> >((ref) { - AdminLoanListNotifier adminLoanListNotifier = AdminLoanListNotifier(); + SuperAdminLoanListNotifier adminLoanListNotifier = + SuperAdminLoanListNotifier(); tokenExpireWrapperAuth(ref, () async { final loaners = ref.watch(loanerList); final loaner = ref.watch(loanerProvider); diff --git a/lib/loan/providers/is_loan_admin_provider.dart b/lib/loan/providers/is_loan_admin_provider.dart index 1069cac151..53f07dd115 100644 --- a/lib/loan/providers/is_loan_admin_provider.dart +++ b/lib/loan/providers/is_loan_admin_provider.dart @@ -1,7 +1,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/loan/providers/user_loaner_list_provider.dart'; -final isLoanAdminProvider = StateProvider((ref) { +final isLoanSuperAdminProvider = StateProvider((ref) { final loaners = ref.watch(userLoanerListProvider); final loanersName = loaners.maybeWhen( data: (loaners) => loaners.map((e) => e.name).toList(), diff --git a/lib/loan/router.dart b/lib/loan/router.dart index b9949400c6..0e989c3d77 100644 --- a/lib/loan/router.dart +++ b/lib/loan/router.dart @@ -47,9 +47,9 @@ class LoanRouter { children: [ QRoute( path: admin, - builder: () => admin_page.AdminPage(), + builder: () => admin_page.SuperAdminPage(), middleware: [ - AdminMiddleware(ref, isLoanAdminProvider), + SuperAdminMiddleware(ref, isLoanSuperAdminProvider), DeferredLoadingMiddleware(admin_page.loadLibrary), ], children: [ diff --git a/lib/loan/ui/pages/admin_page/admin_page.dart b/lib/loan/ui/pages/admin_page/admin_page.dart index 4dd4a31a9b..978ed55032 100644 --- a/lib/loan/ui/pages/admin_page/admin_page.dart +++ b/lib/loan/ui/pages/admin_page/admin_page.dart @@ -17,8 +17,8 @@ import 'package:titan/loan/ui/pages/admin_page/on_going_loan.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; -class AdminPage extends HookConsumerWidget { - const AdminPage({super.key}); +class SuperAdminPage extends HookConsumerWidget { + const SuperAdminPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -103,15 +103,15 @@ class AdminPage extends HookConsumerWidget { final adminLoanListNotifier = ref.read( adminLoanListProvider.notifier, ); - final listAdminItems = adminLoanList[key]; - if (listAdminItems == null) { + final listSuperAdminItems = adminLoanList[key]; + if (listSuperAdminItems == null) { adminLoanListNotifier.autoLoadList( ref, key, (key) => loanListNotifier.loadLoan(key.id), ); } else { - listAdminItems.whenData((adminLoanList) async { + listSuperAdminItems.whenData((adminLoanList) async { if (adminLoanList.isEmpty) { adminLoanListNotifier.autoLoadList( ref, @@ -128,15 +128,16 @@ class AdminPage extends HookConsumerWidget { final adminHistoryLoanListNotifier = ref.read( adminHistoryLoanListProvider.notifier, ); - final listAdminHistoryItems = adminHistoryLoanList[key]; - if (listAdminHistoryItems == null) { + final listSuperAdminHistoryItems = + adminHistoryLoanList[key]; + if (listSuperAdminHistoryItems == null) { adminHistoryLoanListNotifier.autoLoadList( ref, key, (key) => historyLoanListNotifier.loadLoan(key.id), ); } else { - listAdminHistoryItems.whenData(( + listSuperAdminHistoryItems.whenData(( adminHistoryLoanList, ) async { if (adminHistoryLoanList.isEmpty) { diff --git a/lib/loan/ui/pages/admin_page/loan_card.dart b/lib/loan/ui/pages/admin_page/loan_card.dart index c4dfcb8d5b..b23a42b94d 100644 --- a/lib/loan/ui/pages/admin_page/loan_card.dart +++ b/lib/loan/ui/pages/admin_page/loan_card.dart @@ -12,7 +12,7 @@ import 'package:titan/l10n/app_localizations.dart'; class LoanCard extends StatelessWidget { final Loan loan; - final bool isAdmin, isDetail, isHistory; + final bool isSuperAdmin, isDetail, isHistory; final Function()? onEdit, onInfo; final Future Function()? onCalendar, onReturn; const LoanCard({ @@ -22,7 +22,7 @@ class LoanCard extends StatelessWidget { this.onCalendar, this.onReturn, this.onInfo, - this.isAdmin = false, + this.isSuperAdmin = false, this.isDetail = false, this.isHistory = false, }); @@ -33,14 +33,14 @@ class LoanCard extends StatelessWidget { DateTime.now().compareTo(loan.end) > 0 && !loan.returned; return GestureDetector( onTap: () { - if (isAdmin || isHistory) { + if (isSuperAdmin || isHistory) { onInfo?.call(); } }, child: CardLayout( id: loan.id, width: 250, - height: (isAdmin && !isDetail) + height: (isSuperAdmin && !isDetail) ? 170 : isHistory ? 120 @@ -56,7 +56,7 @@ class LoanCard extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (!isAdmin && !isHistory) + if (!isSuperAdmin && !isHistory) Column( children: [ const SizedBox(height: 10), @@ -90,7 +90,7 @@ class LoanCard extends StatelessWidget { const SizedBox(height: 5), ], ), - SizedBox(height: !isAdmin && !isHistory ? 5 : 10), + SizedBox(height: !isSuperAdmin && !isHistory ? 5 : 10), AutoSizeText( loan.borrower.getName(), maxLines: 1, @@ -155,7 +155,7 @@ class LoanCard extends StatelessWidget { ], ), const Spacer(), - if (isAdmin) + if (isSuperAdmin) Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ diff --git a/lib/loan/ui/pages/admin_page/on_going_loan.dart b/lib/loan/ui/pages/admin_page/on_going_loan.dart index 582f7dbe6b..048fb3d32a 100644 --- a/lib/loan/ui/pages/admin_page/on_going_loan.dart +++ b/lib/loan/ui/pages/admin_page/on_going_loan.dart @@ -106,7 +106,7 @@ class OnGoingLoan extends HookConsumerWidget { items: data, itemBuilder: (context, e, i) => LoanCard( loan: e, - isAdmin: true, + isSuperAdmin: true, onEdit: () async { await loanNotifier.setLoan(e); startNotifier.setStart(processDate(e.start)); diff --git a/lib/loan/ui/pages/main_page/main_page.dart b/lib/loan/ui/pages/main_page/main_page.dart index 34b231bd45..efac1e53e7 100644 --- a/lib/loan/ui/pages/main_page/main_page.dart +++ b/lib/loan/ui/pages/main_page/main_page.dart @@ -25,7 +25,7 @@ class LoanMainPage extends HookConsumerWidget { final loanList = ref.watch(loanListProvider); final loanNotifier = ref.watch(loanProvider.notifier); final loanListNotifier = ref.watch(loanListProvider.notifier); - final isAdmin = ref.watch(isLoanAdminProvider); + final isSuperAdmin = ref.watch(isLoanSuperAdminProvider); ref.watch(adminLoanListProvider); ref.watch(itemListProvider); @@ -133,11 +133,11 @@ class LoanMainPage extends HookConsumerWidget { ], ), ), - if (isAdmin) + if (isSuperAdmin) Positioned( top: 30, right: 30, - child: AdminButton( + child: SuperAdminButton( onTap: () { QR.to(LoanRouter.root + LoanRouter.admin); }, diff --git a/lib/others/ui/no_module.dart b/lib/others/ui/no_module.dart index 38532bf636..d08943dc5d 100644 --- a/lib/others/ui/no_module.dart +++ b/lib/others/ui/no_module.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/providers/module_root_list_provider.dart'; +import 'package:titan/super_admin/providers/module_root_list_provider.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/providers/path_forwarding_provider.dart'; import 'package:qlevar_router/qlevar_router.dart'; diff --git a/lib/paiement/class/structure.dart b/lib/paiement/class/structure.dart index 9c1c3eefa2..718ed38e45 100644 --- a/lib/paiement/class/structure.dart +++ b/lib/paiement/class/structure.dart @@ -1,4 +1,4 @@ -import 'package:titan/admin/class/association_membership_simple.dart'; +import 'package:titan/super_admin/class/association_membership_simple.dart'; import 'package:titan/user/class/simple_users.dart'; class Structure { diff --git a/lib/paiement/class/user_store.dart b/lib/paiement/class/user_store.dart index f0d3db3c3c..f134525f9e 100644 --- a/lib/paiement/class/user_store.dart +++ b/lib/paiement/class/user_store.dart @@ -55,7 +55,7 @@ class UserStore extends Store { bool? canSeeHistory, bool? canCancel, bool? canManageSellers, - bool? storeAdmin, + bool? storeSuperAdmin, }) { return UserStore( id: id ?? this.id, diff --git a/lib/paiement/providers/is_payment_admin.dart b/lib/paiement/providers/is_payment_admin.dart index 4fdca3f91d..d402106331 100644 --- a/lib/paiement/providers/is_payment_admin.dart +++ b/lib/paiement/providers/is_payment_admin.dart @@ -1,7 +1,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/paiement/providers/my_structures_provider.dart'; -final isPaymentAdminProvider = StateProvider((ref) { +final isPaymentSuperAdminProvider = StateProvider((ref) { final myStructures = ref.watch(myStructuresProvider); return myStructures.isNotEmpty; }); diff --git a/lib/paiement/providers/new_admin_provider.dart b/lib/paiement/providers/new_admin_provider.dart index 50c98ec16a..ced78a0d4b 100644 --- a/lib/paiement/providers/new_admin_provider.dart +++ b/lib/paiement/providers/new_admin_provider.dart @@ -1,20 +1,19 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/user/class/simple_users.dart'; -class NewAdminNotifier extends StateNotifier { - NewAdminNotifier() : super(SimpleUser.empty()); +class NewSuperAdminNotifier extends StateNotifier { + NewSuperAdminNotifier() : super(SimpleUser.empty()); - void updateNewAdmin(SimpleUser newAdmin) { - state = newAdmin; + void updateNewSuperAdmin(SimpleUser newSuperAdmin) { + state = newSuperAdmin; } - void resetNewAdmin() { + void resetNewSuperAdmin() { state = SimpleUser.empty(); } } -final newAdminProvider = StateNotifierProvider(( - ref, -) { - return NewAdminNotifier(); -}); +final newSuperAdminProvider = + StateNotifierProvider((ref) { + return NewSuperAdminNotifier(); + }); diff --git a/lib/paiement/repositories/funding_repository.dart b/lib/paiement/repositories/funding_repository.dart index aabe1d8dca..7c82cc86ec 100644 --- a/lib/paiement/repositories/funding_repository.dart +++ b/lib/paiement/repositories/funding_repository.dart @@ -10,7 +10,7 @@ class FundingRepository extends Repository { // ignore: overridden_fields final ext = 'myeclpay/transfer/'; - Future getAdminPaymentUrl(Transfer transfer) async { + Future getSuperAdminPaymentUrl(Transfer transfer) async { return await create(transfer.toJson(), suffix: "admin"); } diff --git a/lib/paiement/router.dart b/lib/paiement/router.dart index a262ac883e..9896af8d5e 100644 --- a/lib/paiement/router.dart +++ b/lib/paiement/router.dart @@ -35,7 +35,7 @@ class PaymentRouter { static const String fund = '/fund'; static const String addEditStore = '/addEditStore'; static const String transferStructure = '/transferStructure'; - static const String storeAdmin = '/storeAdmin'; + static const String storeSuperAdmin = '/storeSuperAdmin'; static const String storeStats = '/storeStats'; static final Module module = Module( getName: (context) => AppLocalizations.of(context)!.modulePayment, @@ -68,16 +68,16 @@ class PaymentRouter { middleware: [DeferredLoadingMiddleware(devices_page.loadLibrary)], ), QRoute( - path: PaymentRouter.storeAdmin, - builder: () => store_admin_page.StoreAdminPage(), + path: PaymentRouter.storeSuperAdmin, + builder: () => store_admin_page.StoreSuperAdminPage(), middleware: [DeferredLoadingMiddleware(store_admin_page.loadLibrary)], ), QRoute( path: PaymentRouter.admin, - builder: () => admin_page.AdminPage(), + builder: () => admin_page.SuperAdminPage(), middleware: [ DeferredLoadingMiddleware(admin_page.loadLibrary), - AdminMiddleware(ref, isPaymentAdminProvider), + SuperAdminMiddleware(ref, isPaymentSuperAdminProvider), ], children: [ QRoute( diff --git a/lib/paiement/ui/pages/admin_page/admin_page.dart b/lib/paiement/ui/pages/admin_page/admin_page.dart index bdb566a73d..7a68cacb21 100644 --- a/lib/paiement/ui/pages/admin_page/admin_page.dart +++ b/lib/paiement/ui/pages/admin_page/admin_page.dart @@ -10,8 +10,8 @@ import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; import 'package:titan/tools/ui/widgets/align_left_text.dart'; -class AdminPage extends ConsumerWidget { - const AdminPage({super.key}); +class SuperAdminPage extends ConsumerWidget { + const SuperAdminPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -43,7 +43,7 @@ class AdminPage extends ConsumerWidget { ); return Column( children: storeFromStructures - .map((store) => AdminStoreCard(store: store)) + .map((store) => SuperAdminStoreCard(store: store)) .toList(), ); }, diff --git a/lib/paiement/ui/pages/admin_page/admin_store_card.dart b/lib/paiement/ui/pages/admin_page/admin_store_card.dart index b0bbe20e4f..47320e48ff 100644 --- a/lib/paiement/ui/pages/admin_page/admin_store_card.dart +++ b/lib/paiement/ui/pages/admin_page/admin_store_card.dart @@ -13,9 +13,9 @@ import 'package:titan/tools/ui/layouts/card_button.dart'; import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; import 'package:qlevar_router/qlevar_router.dart'; -class AdminStoreCard extends ConsumerWidget { +class SuperAdminStoreCard extends ConsumerWidget { final Store store; - const AdminStoreCard({super.key, required this.store}); + const SuperAdminStoreCard({super.key, required this.store}); @override Widget build(BuildContext context, WidgetRef ref) { diff --git a/lib/paiement/ui/pages/main_page/main_page.dart b/lib/paiement/ui/pages/main_page/main_page.dart index 41b705ffc2..1d1838a1f7 100644 --- a/lib/paiement/ui/pages/main_page/main_page.dart +++ b/lib/paiement/ui/pages/main_page/main_page.dart @@ -45,7 +45,7 @@ class PaymentMainPage extends HookConsumerWidget { final mySellersNotifier = ref.read(myStoresProvider.notifier); final myHistoryNotifier = ref.read(myHistoryProvider.notifier); final myWalletNotifier = ref.read(myWalletProvider.notifier); - final isAdmin = ref.watch(isPaymentAdminProvider); + final isSuperAdmin = ref.watch(isPaymentSuperAdminProvider); final flipped = useState(true); ref.listen(pathForwardingProvider, (previous, next) async { @@ -144,7 +144,7 @@ class PaymentMainPage extends HookConsumerWidget { AsyncChild( value: mySellers, builder: (context, mySellers) { - if (mySellers.isEmpty && !isAdmin) { + if (mySellers.isEmpty && !isSuperAdmin) { return SizedBox( height: 250, width: MediaQuery.of(context).size.width, diff --git a/lib/paiement/ui/pages/main_page/seller_card/store_admin_card.dart b/lib/paiement/ui/pages/main_page/seller_card/store_admin_card.dart index ca467b3dca..5f0bb0a0fb 100644 --- a/lib/paiement/ui/pages/main_page/seller_card/store_admin_card.dart +++ b/lib/paiement/ui/pages/main_page/seller_card/store_admin_card.dart @@ -8,8 +8,8 @@ import 'package:titan/paiement/providers/selected_structure_provider.dart'; import 'package:titan/paiement/router.dart'; import 'package:qlevar_router/qlevar_router.dart'; -class StoreAdminCard extends ConsumerWidget { - const StoreAdminCard({super.key}); +class StoreSuperAdminCard extends ConsumerWidget { + const StoreSuperAdminCard({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { diff --git a/lib/paiement/ui/pages/main_page/seller_card/store_card.dart b/lib/paiement/ui/pages/main_page/seller_card/store_card.dart index d3f93d3b63..4f4d440f74 100644 --- a/lib/paiement/ui/pages/main_page/seller_card/store_card.dart +++ b/lib/paiement/ui/pages/main_page/seller_card/store_card.dart @@ -63,8 +63,8 @@ class StoreCard extends HookConsumerWidget { colors: buttonGradient, icon: HeroIcons.userGroup, onPressed: () async { - // storeAdminListNotifier.getStoreAdminList(store.id); - QR.to(PaymentRouter.root + PaymentRouter.storeAdmin); + // storeSuperAdminListNotifier.getStoreSuperAdminList(store.id); + QR.to(PaymentRouter.root + PaymentRouter.storeSuperAdmin); }, title: AppLocalizations.of(context)!.paiementManagement, ), diff --git a/lib/paiement/ui/pages/main_page/seller_card/store_list.dart b/lib/paiement/ui/pages/main_page/seller_card/store_list.dart index f3dbb2ea4a..eda0bd1afd 100644 --- a/lib/paiement/ui/pages/main_page/seller_card/store_list.dart +++ b/lib/paiement/ui/pages/main_page/seller_card/store_list.dart @@ -16,7 +16,7 @@ class StoreList extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final stores = ref.watch(myStoresProvider); - final isAdmin = ref.watch(isPaymentAdminProvider); + final isSuperAdmin = ref.watch(isPaymentSuperAdminProvider); return SizedBox( height: maxHeight, child: SingleChildScrollView( @@ -49,11 +49,11 @@ class StoreList extends ConsumerWidget { } return Column( children: [ - if (isAdmin) ...[ + if (isSuperAdmin) ...[ StoreDivider( - name: AppLocalizations.of(context)!.paiementAdmin, + name: AppLocalizations.of(context)!.paiementSuperAdmin, ), - const StoreAdminCard(), + const StoreSuperAdminCard(), ], ...sortedByMembership.map((membership, stores) { final List alphabeticallyOrderedStores = stores diff --git a/lib/paiement/ui/pages/store_admin_page/search_result.dart b/lib/paiement/ui/pages/store_admin_page/search_result.dart index 8ffc540fb2..f6390aac40 100644 --- a/lib/paiement/ui/pages/store_admin_page/search_result.dart +++ b/lib/paiement/ui/pages/store_admin_page/search_result.dart @@ -32,8 +32,8 @@ class SearchResult extends HookConsumerWidget { final store = ref.watch(selectedStoreProvider); final users = ref.watch(userList); final usersNotifier = ref.watch(userList.notifier); - final newAdmin = ref.watch(newAdminProvider); - final newAdminNotifier = ref.watch(newAdminProvider.notifier); + final newSuperAdmin = ref.watch(newSuperAdminProvider); + final newSuperAdminNotifier = ref.watch(newSuperAdminProvider.notifier); final sellerStoreNotifier = ref.watch( sellerStoreProvider(store.id).notifier, ); @@ -83,7 +83,7 @@ class SearchResult extends HookConsumerWidget { ), onYes: () async { await tokenExpireWrapper(ref, () async { - newAdminNotifier.updateNewAdmin(simpleUser); + newSuperAdminNotifier.updateNewSuperAdmin(simpleUser); queryController.text = simpleUser.getName(); Seller seller = Seller( storeId: store.id, @@ -107,7 +107,7 @@ class SearchResult extends HookConsumerWidget { queryController.clear(); usersNotifier.clear(); sellerRightsListNotifier.clearRights(); - newAdminNotifier.resetNewAdmin(); + newSuperAdminNotifier.resetNewSuperAdmin(); displayToastWithContext(TypeMsg.msg, addedSellerMsg); if (context.mounted) { Navigator.of(context).pop(); @@ -151,7 +151,7 @@ class SearchResult extends HookConsumerWidget { simpleUser.getName(), style: TextStyle( fontSize: 18, - fontWeight: simpleUser.id == newAdmin.id + fontWeight: simpleUser.id == newSuperAdmin.id ? FontWeight.bold : FontWeight.normal, ), diff --git a/lib/paiement/ui/pages/store_admin_page/seller_right_card.dart b/lib/paiement/ui/pages/store_admin_page/seller_right_card.dart index 65272ec9dc..e7f0c4176f 100644 --- a/lib/paiement/ui/pages/store_admin_page/seller_right_card.dart +++ b/lib/paiement/ui/pages/store_admin_page/seller_right_card.dart @@ -33,9 +33,9 @@ class SellerRightCard extends ConsumerWidget { displayToast(context, type, msg); } - final amIAdmin = me.userId == store.structure.managerUser.id; + final amISuperAdmin = me.userId == store.structure.managerUser.id; - final isStructureAdmin = + final isStructureSuperAdmin = storeSeller.userId == store.structure.managerUser.id; final icons = @@ -72,7 +72,7 @@ class SellerRightCard extends ConsumerWidget { AppLocalizations.of(context)!.paiementSeeHistory, AppLocalizations.of(context)!.paiementCancelTransactions, AppLocalizations.of(context)!.paiementManageSellers, - AppLocalizations.of(context)!.paiementStructureAdmin, + AppLocalizations.of(context)!.paiementStructureSuperAdmin, ]; List sellerRights = [ @@ -89,7 +89,7 @@ class SellerRightCard extends ConsumerWidget { } } - if (isStructureAdmin) { + if (isStructureSuperAdmin) { rightsLabel.add(labels[4]); rightsIcons.add(icons[4]); } @@ -103,7 +103,7 @@ class SellerRightCard extends ConsumerWidget { context: context, backgroundColor: Colors.transparent, scrollControlDisabledMaxHeightRatio: - (((!amIAdmin || isStructureAdmin) ? 80 : 100) + + (((!amISuperAdmin || isStructureSuperAdmin) ? 80 : 100) + 45 * icons.length) / MediaQuery.of(context).size.height, builder: (context) { @@ -131,7 +131,7 @@ class SellerRightCard extends ConsumerWidget { ), const SizedBox(height: 10), for (var i = 0; i < icons.length; i++) - if (i < 4 || isStructureAdmin) + if (i < 4 || isStructureSuperAdmin) Padding( padding: const EdgeInsets.symmetric(vertical: 5), child: Row( @@ -146,7 +146,8 @@ class SellerRightCard extends ConsumerWidget { ), ), const Spacer(), - if (me.canManageSellers && !isStructureAdmin) + if (me.canManageSellers && + !isStructureSuperAdmin) Checkbox( value: sellerRights[i], activeColor: const Color(0xff204550), @@ -202,7 +203,7 @@ class SellerRightCard extends ConsumerWidget { ], ), ), - if (me.canManageSellers && !isStructureAdmin) + if (me.canManageSellers && !isStructureSuperAdmin) GestureDetector( onTap: () async { await showDialog( diff --git a/lib/paiement/ui/pages/store_admin_page/store_admin_page.dart b/lib/paiement/ui/pages/store_admin_page/store_admin_page.dart index e5dc41f531..65c3c2907b 100644 --- a/lib/paiement/ui/pages/store_admin_page/store_admin_page.dart +++ b/lib/paiement/ui/pages/store_admin_page/store_admin_page.dart @@ -16,8 +16,8 @@ import 'package:titan/tools/ui/widgets/text_entry.dart'; import 'package:titan/user/providers/user_list_provider.dart'; import 'package:titan/user/providers/user_provider.dart'; -class StoreAdminPage extends HookConsumerWidget { - const StoreAdminPage({super.key}); +class StoreSuperAdminPage extends HookConsumerWidget { + const StoreSuperAdminPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { diff --git a/lib/ph/class/ph_admin.dart b/lib/ph/class/ph_admin.dart index 1ca64bd695..491151613f 100644 --- a/lib/ph/class/ph_admin.dart +++ b/lib/ph/class/ph_admin.dart @@ -1,10 +1,14 @@ -class PhAdmin { - PhAdmin({required this.name, required this.groupManagerId, required this.id}); +class PhSuperAdmin { + PhSuperAdmin({ + required this.name, + required this.groupManagerId, + required this.id, + }); late final String name; late final String groupManagerId; late final String id; - PhAdmin.fromJson(Map json) { + PhSuperAdmin.fromJson(Map json) { name = json['name']; groupManagerId = json['group_manager_id']; id = json['id']; @@ -18,15 +22,15 @@ class PhAdmin { return data; } - PhAdmin copyWith({String? name, String? groupManagerId, String? id}) { - return PhAdmin( + PhSuperAdmin copyWith({String? name, String? groupManagerId, String? id}) { + return PhSuperAdmin( name: name ?? this.name, groupManagerId: groupManagerId ?? this.groupManagerId, id: id ?? this.id, ); } - PhAdmin.empty() { + PhSuperAdmin.empty() { name = ""; groupManagerId = ""; id = ""; @@ -34,6 +38,6 @@ class PhAdmin { @override String toString() { - return 'PhAdmin(name: $name, groupManagerId: $groupManagerId, id: $id)'; + return 'PhSuperAdmin(name: $name, groupManagerId: $groupManagerId, id: $id)'; } } diff --git a/lib/ph/providers/is_ph_admin_provider.dart b/lib/ph/providers/is_ph_admin_provider.dart index dd9d26b1ea..7459d621eb 100644 --- a/lib/ph/providers/is_ph_admin_provider.dart +++ b/lib/ph/providers/is_ph_admin_provider.dart @@ -1,7 +1,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/user/providers/user_provider.dart'; -final isPhAdminProvider = StateProvider((ref) { +final isPhSuperAdminProvider = StateProvider((ref) { final me = ref.watch(userProvider); for (final group in me.groups) { if (group.name == "ph") { diff --git a/lib/ph/router.dart b/lib/ph/router.dart index 3390178e9d..7391640e39 100644 --- a/lib/ph/router.dart +++ b/lib/ph/router.dart @@ -54,9 +54,9 @@ class PhRouter { ), QRoute( path: admin, - builder: () => admin_page.AdminPage(), + builder: () => admin_page.SuperAdminPage(), middleware: [ - AdminMiddleware(ref, isPhAdminProvider), + SuperAdminMiddleware(ref, isPhSuperAdminProvider), DeferredLoadingMiddleware(admin_page.loadLibrary), ], children: [ diff --git a/lib/ph/ui/pages/admin_page/admin_page.dart b/lib/ph/ui/pages/admin_page/admin_page.dart index f527a7229a..be14cfda85 100644 --- a/lib/ph/ui/pages/admin_page/admin_page.dart +++ b/lib/ph/ui/pages/admin_page/admin_page.dart @@ -14,8 +14,8 @@ import 'package:titan/ph/ui/pages/ph.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/l10n/app_localizations.dart'; -class AdminPage extends HookConsumerWidget { - const AdminPage({super.key}); +class SuperAdminPage extends HookConsumerWidget { + const SuperAdminPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -26,7 +26,9 @@ class AdminPage extends HookConsumerWidget { child: Column( children: [ const YearBar(), - const Expanded(child: SingleChildScrollView(child: AdminPhList())), + const Expanded( + child: SingleChildScrollView(child: SuperAdminPhList()), + ), const SizedBox(height: 20), GestureDetector( onTap: () { diff --git a/lib/ph/ui/pages/admin_page/admin_ph_card.dart b/lib/ph/ui/pages/admin_page/admin_ph_card.dart index 63eb319714..c6844ce692 100644 --- a/lib/ph/ui/pages/admin_page/admin_ph_card.dart +++ b/lib/ph/ui/pages/admin_page/admin_ph_card.dart @@ -6,10 +6,10 @@ import 'package:titan/tools/ui/layouts/card_button.dart'; import 'package:titan/tools/ui/layouts/card_layout.dart'; import 'package:titan/l10n/app_localizations.dart'; -class AdminPhCard extends StatelessWidget { +class SuperAdminPhCard extends StatelessWidget { final VoidCallback onEdit, onDelete; final Ph ph; - const AdminPhCard({ + const SuperAdminPhCard({ super.key, required this.ph, required this.onEdit, diff --git a/lib/ph/ui/pages/admin_page/admin_ph_list.dart b/lib/ph/ui/pages/admin_page/admin_ph_list.dart index fbdf19236f..153d7fd01d 100644 --- a/lib/ph/ui/pages/admin_page/admin_ph_list.dart +++ b/lib/ph/ui/pages/admin_page/admin_ph_list.dart @@ -10,8 +10,8 @@ import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/l10n/app_localizations.dart'; -class AdminPhList extends HookConsumerWidget { - const AdminPhList({super.key}); +class SuperAdminPhList extends HookConsumerWidget { + const SuperAdminPhList({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -30,7 +30,7 @@ class AdminPhList extends HookConsumerWidget { return Column( children: list .map( - (ph) => AdminPhCard( + (ph) => SuperAdminPhCard( ph: ph, onEdit: () { QR.to(PhRouter.root + PhRouter.admin + PhRouter.add_ph); diff --git a/lib/ph/ui/pages/main_page/main_page.dart b/lib/ph/ui/pages/main_page/main_page.dart index 7ef6ec2ba4..845f40d3d0 100644 --- a/lib/ph/ui/pages/main_page/main_page.dart +++ b/lib/ph/ui/pages/main_page/main_page.dart @@ -19,16 +19,16 @@ class PhMainPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final isAdmin = ref.watch(isPhAdminProvider); + final isSuperAdmin = ref.watch(isPhSuperAdminProvider); final phList = ref.watch(phListProvider); return PhTemplate( child: Column( children: [ - if (isAdmin) + if (isSuperAdmin) SizedBox( width: 116.7, - child: AdminButton( + child: SuperAdminButton( onTap: () { QR.to(PhRouter.root + PhRouter.admin); }, diff --git a/lib/phonebook/class/complete_member.dart b/lib/phonebook/class/complete_member.dart index 666c3b0f88..17cf263e07 100644 --- a/lib/phonebook/class/complete_member.dart +++ b/lib/phonebook/class/complete_member.dart @@ -1,4 +1,4 @@ -import 'package:titan/admin/class/account_type.dart'; +import 'package:titan/super_admin/class/account_type.dart'; import 'package:titan/phonebook/class/membership.dart'; import 'member.dart'; diff --git a/lib/phonebook/class/member.dart b/lib/phonebook/class/member.dart index 149466b0e3..adaa9ed72a 100644 --- a/lib/phonebook/class/member.dart +++ b/lib/phonebook/class/member.dart @@ -1,4 +1,4 @@ -import 'package:titan/admin/class/account_type.dart'; +import 'package:titan/super_admin/class/account_type.dart'; import 'package:titan/user/class/simple_users.dart'; class Member extends SimpleUser { diff --git a/lib/phonebook/providers/phonebook_admin_provider.dart b/lib/phonebook/providers/phonebook_admin_provider.dart index 0d6475dbc5..38d89bdb6c 100644 --- a/lib/phonebook/providers/phonebook_admin_provider.dart +++ b/lib/phonebook/providers/phonebook_admin_provider.dart @@ -1,11 +1,11 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/admin/providers/is_admin_provider.dart'; +import 'package:titan/super_admin/providers/is_admin_provider.dart'; import 'package:titan/phonebook/providers/association_member_list_provider.dart'; import 'package:titan/phonebook/providers/association_provider.dart'; import 'package:titan/phonebook/tools/constants.dart'; import 'package:titan/user/providers/user_provider.dart'; -final isPhonebookAdminProvider = StateProvider((ref) { +final isPhonebookSuperAdminProvider = StateProvider((ref) { final user = ref.watch(userProvider); if (user.groups .map((e) => e.id) @@ -18,10 +18,10 @@ final isPhonebookAdminProvider = StateProvider((ref) { return false; }); -final hasPhonebookAdminAccessProvider = StateProvider((ref) { - final isPhonebookAdmin = ref.watch(isPhonebookAdminProvider); - final isAdmin = ref.watch(isAdminProvider); - return isPhonebookAdmin || isAdmin; +final hasPhonebookSuperAdminAccessProvider = StateProvider((ref) { + final isPhonebookSuperAdmin = ref.watch(isPhonebookSuperAdminProvider); + final isSuperAdmin = ref.watch(isSuperAdminProvider); + return isPhonebookSuperAdmin || isSuperAdmin; }); final isAssociationPresidentProvider = StateProvider((ref) { diff --git a/lib/phonebook/router.dart b/lib/phonebook/router.dart index 7354fb42ce..c6d71d1e70 100644 --- a/lib/phonebook/router.dart +++ b/lib/phonebook/router.dart @@ -42,8 +42,10 @@ class PhonebookRouter { children: [ QRoute( path: admin, - builder: () => const AdminPage(), - middleware: [AdminMiddleware(ref, hasPhonebookAdminAccessProvider)], + builder: () => const SuperAdminPage(), + middleware: [ + SuperAdminMiddleware(ref, hasPhonebookSuperAdminAccessProvider), + ], children: [ QRoute( path: editAssociation, @@ -68,7 +70,9 @@ class PhonebookRouter { QRoute( path: editAssociation, builder: () => AssociationEditorPage(), - middleware: [AdminMiddleware(ref, isAssociationPresidentProvider)], + middleware: [ + SuperAdminMiddleware(ref, isAssociationPresidentProvider), + ], children: [ QRoute( path: addEditMember, diff --git a/lib/phonebook/ui/pages/admin_page/admin_page.dart b/lib/phonebook/ui/pages/admin_page/admin_page.dart index e4637bc39a..4f172e7a86 100644 --- a/lib/phonebook/ui/pages/admin_page/admin_page.dart +++ b/lib/phonebook/ui/pages/admin_page/admin_page.dart @@ -21,8 +21,8 @@ import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/l10n/app_localizations.dart'; -class AdminPage extends HookConsumerWidget { - const AdminPage({super.key}); +class SuperAdminPage extends HookConsumerWidget { + const SuperAdminPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -32,7 +32,7 @@ class AdminPage extends HookConsumerWidget { final associationList = ref.watch(associationListProvider); final associationFilteredList = ref.watch(associationFilteredListProvider); final roleNotifier = ref.watch(rolesTagsProvider.notifier); - final isPhonebookAdmin = ref.watch(isPhonebookAdminProvider); + final isPhonebookSuperAdmin = ref.watch(isPhonebookSuperAdminProvider); void displayToastWithContext(TypeMsg type, String msg) { displayToast(context, type, msg); } @@ -57,7 +57,7 @@ class AdminPage extends HookConsumerWidget { children: [ KindsBar(), GestureDetector( - onTap: isPhonebookAdmin + onTap: isPhonebookSuperAdmin ? () { QR.to( PhonebookRouter.root + @@ -75,7 +75,7 @@ class AdminPage extends HookConsumerWidget { ), width: double.infinity, height: 100, - color: isPhonebookAdmin + color: isPhonebookSuperAdmin ? Colors.white : ColorConstants.deactivated2, child: Center( @@ -102,7 +102,7 @@ class AdminPage extends HookConsumerWidget { padding: const EdgeInsets.symmetric(horizontal: 20), child: EditableAssociationCard( association: association, - isPhonebookAdmin: isPhonebookAdmin, + isPhonebookSuperAdmin: isPhonebookSuperAdmin, onEdit: () { kindNotifier.setKind(association.kind); associationNotifier.setAssociation(association); diff --git a/lib/phonebook/ui/pages/admin_page/editable_association_card.dart b/lib/phonebook/ui/pages/admin_page/editable_association_card.dart index e2fe0d5b2a..9fef943ac6 100644 --- a/lib/phonebook/ui/pages/admin_page/editable_association_card.dart +++ b/lib/phonebook/ui/pages/admin_page/editable_association_card.dart @@ -6,13 +6,13 @@ import 'package:titan/phonebook/ui/pages/admin_page/edition_button.dart'; class EditableAssociationCard extends HookConsumerWidget { final Association association; - final bool isPhonebookAdmin; + final bool isPhonebookSuperAdmin; final void Function() onEdit; final Future Function() onDelete; const EditableAssociationCard({ super.key, required this.association, - required this.isPhonebookAdmin, + required this.isPhonebookSuperAdmin, required this.onEdit, required this.onDelete, }); @@ -63,7 +63,7 @@ class EditableAssociationCard extends HookConsumerWidget { const SizedBox(width: 5), DeleteButton( onDelete: onDelete, - deactivated: !isPhonebookAdmin, + deactivated: !isPhonebookSuperAdmin, deletion: association.deactivated, ), ], diff --git a/lib/phonebook/ui/pages/association_editor_page/association_editor_page.dart b/lib/phonebook/ui/pages/association_editor_page/association_editor_page.dart index 641db46f21..7797e5996f 100644 --- a/lib/phonebook/ui/pages/association_editor_page/association_editor_page.dart +++ b/lib/phonebook/ui/pages/association_editor_page/association_editor_page.dart @@ -51,7 +51,7 @@ class AssociationEditorPage extends HookConsumerWidget { final membershipNotifier = ref.watch(membershipProvider.notifier); final completeMemberNotifier = ref.watch(completeMemberProvider.notifier); final memberRoleTagsNotifier = ref.watch(memberRoleTagsProvider.notifier); - final isPhonebookAdmin = ref.watch(isPhonebookAdminProvider); + final isPhonebookSuperAdmin = ref.watch(isPhonebookSuperAdminProvider); final isAssociationPresident = ref.watch(isAssociationPresidentProvider); final kindNotifier = ref.watch(associationKindProvider.notifier); @@ -100,7 +100,7 @@ class AssociationEditorPage extends HookConsumerWidget { height: 40, decoration: BoxDecoration( color: - (isPhonebookAdmin || isAssociationPresident) && + (isPhonebookSuperAdmin || isAssociationPresident) && !association.deactivated ? ColorConstants.gradient1 : ColorConstants.deactivated1, @@ -109,7 +109,7 @@ class AssociationEditorPage extends HookConsumerWidget { child: child, ), onTap: - (isPhonebookAdmin || isAssociationPresident) && + (isPhonebookSuperAdmin || isAssociationPresident) && !association.deactivated ? () async { rolesTagsNotifier.resetChecked(); @@ -156,7 +156,7 @@ class AssociationEditorPage extends HookConsumerWidget { builder: (context, associationMembers) => associationMembers.isEmpty ? Text(AppLocalizations.of(context)!.phonebookNoMember) - : (isPhonebookAdmin || isAssociationPresident) && + : (isPhonebookSuperAdmin || isAssociationPresident) && !association.deactivated ? SizedBox( height: 400, @@ -238,7 +238,7 @@ class AssociationEditorPage extends HookConsumerWidget { padding: const EdgeInsets.symmetric(horizontal: 30), child: WaitingButton( builder: (child) => AddEditButtonLayout( - colors: isPhonebookAdmin && !association.deactivated + colors: isPhonebookSuperAdmin && !association.deactivated ? [ColorConstants.gradient1, ColorConstants.gradient2] : [ ColorConstants.deactivated1, @@ -246,7 +246,7 @@ class AssociationEditorPage extends HookConsumerWidget { ], child: child, ), - onTap: isPhonebookAdmin && !association.deactivated + onTap: isPhonebookSuperAdmin && !association.deactivated ? () async { showDialog( context: context, diff --git a/lib/phonebook/ui/pages/association_editor_page/association_information_editor.dart b/lib/phonebook/ui/pages/association_editor_page/association_information_editor.dart index 941cf3b9d3..d39bb721e3 100644 --- a/lib/phonebook/ui/pages/association_editor_page/association_information_editor.dart +++ b/lib/phonebook/ui/pages/association_editor_page/association_information_editor.dart @@ -2,9 +2,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/class/simple_group.dart'; -import 'package:titan/admin/providers/group_list_provider.dart'; -import 'package:titan/admin/providers/is_admin_provider.dart'; +import 'package:titan/super_admin/class/simple_group.dart'; +import 'package:titan/super_admin/providers/group_list_provider.dart'; +import 'package:titan/super_admin/providers/is_admin_provider.dart'; import 'package:titan/phonebook/providers/association_kind_provider.dart'; import 'package:titan/phonebook/providers/association_list_provider.dart'; import 'package:titan/phonebook/providers/association_provider.dart'; @@ -32,8 +32,8 @@ class AssociationInformationEditor extends HookConsumerWidget { final name = useTextEditingController(text: association.name); final description = useTextEditingController(text: association.description); final associationListNotifier = ref.watch(associationListProvider.notifier); - final isAdmin = ref.watch(isAdminProvider); - final isPhonebookAdmin = ref.watch(isPhonebookAdminProvider); + final isSuperAdmin = ref.watch(isSuperAdminProvider); + final isPhonebookSuperAdmin = ref.watch(isPhonebookSuperAdminProvider); final groups = ref.watch(allGroupListProvider); List selectedGroups = groups.maybeWhen( @@ -50,7 +50,7 @@ class AssociationInformationEditor extends HookConsumerWidget { return Column( children: [ - isPhonebookAdmin && !association.deactivated + isPhonebookSuperAdmin && !association.deactivated ? Form( key: key, child: Column( @@ -251,7 +251,7 @@ class AssociationInformationEditor extends HookConsumerWidget { ], ), ), - if (isAdmin && !association.deactivated) + if (isSuperAdmin && !association.deactivated) Padding( padding: const EdgeInsets.symmetric(horizontal: 30), child: Column( diff --git a/lib/phonebook/ui/pages/main_page/main_page.dart b/lib/phonebook/ui/pages/main_page/main_page.dart index 00df2b5cf6..e5c8f74315 100644 --- a/lib/phonebook/ui/pages/main_page/main_page.dart +++ b/lib/phonebook/ui/pages/main_page/main_page.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/providers/is_admin_provider.dart'; +import 'package:titan/super_admin/providers/is_admin_provider.dart'; import 'package:titan/phonebook/providers/association_filtered_list_provider.dart'; import 'package:titan/phonebook/providers/association_kind_provider.dart'; import 'package:titan/phonebook/providers/association_kinds_provider.dart'; @@ -23,8 +23,8 @@ class PhonebookMainPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final isPhonebookAdmin = ref.watch(isPhonebookAdminProvider); - final isAdmin = ref.watch(isAdminProvider); + final isPhonebookSuperAdmin = ref.watch(isPhonebookSuperAdminProvider); + final isSuperAdmin = ref.watch(isSuperAdminProvider); final associationNotifier = ref.watch(associationProvider.notifier); final associationListNotifier = ref.watch(associationListProvider.notifier); final associationList = ref.watch(associationListProvider); @@ -47,10 +47,10 @@ class PhonebookMainPage extends HookConsumerWidget { child: Row( children: [ const ResearchBar(), - if (isPhonebookAdmin || isAdmin) + if (isPhonebookSuperAdmin || isSuperAdmin) Padding( padding: const EdgeInsets.only(left: 20), - child: AdminButton( + child: SuperAdminButton( onTap: () { kindNotifier.setKind(''); QR.to(PhonebookRouter.root + PhonebookRouter.admin); diff --git a/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart b/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart index f1004d81d1..8ed291f8a8 100644 --- a/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart +++ b/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart @@ -47,7 +47,7 @@ class MembershipEditorPage extends HookConsumerWidget { text: membership.apparentName, ); final associationMembers = ref.watch(associationMemberListProvider); - final isPhonebookAdmin = ref.watch(isPhonebookAdminProvider); + final isPhonebookSuperAdmin = ref.watch(isPhonebookSuperAdminProvider); void displayToastWithContext(TypeMsg type, String msg) { displayToast(context, type, msg); @@ -113,12 +113,12 @@ class MembershipEditorPage extends HookConsumerWidget { ), fillColor: rolesTagList.keys.first == tagKey && - !isPhonebookAdmin + !isPhonebookSuperAdmin ? WidgetStateProperty.all(Colors.black) : WidgetStateProperty.all(Colors.grey), onChanged: rolesTagList.keys.first == tagKey && - !isPhonebookAdmin + !isPhonebookSuperAdmin ? null : (value) { rolesTagList[tagKey] = AsyncData([value!]); diff --git a/lib/purchases/providers/purchases_admin_provider.dart b/lib/purchases/providers/purchases_admin_provider.dart index 121276b225..d87dd50ff8 100644 --- a/lib/purchases/providers/purchases_admin_provider.dart +++ b/lib/purchases/providers/purchases_admin_provider.dart @@ -2,7 +2,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/purchases/providers/seller_list_provider.dart'; import 'package:titan/user/providers/user_provider.dart'; -final isPurchasesAdminProvider = StateProvider((ref) { +final isPurchasesSuperAdminProvider = StateProvider((ref) { final user = ref.watch(userProvider); final sellers = ref.watch(sellerListProvider); if (user.groups diff --git a/lib/purchases/router.dart b/lib/purchases/router.dart index 586dbbd331..9587e8c80a 100644 --- a/lib/purchases/router.dart +++ b/lib/purchases/router.dart @@ -41,7 +41,7 @@ class PurchasesRouter { QRoute( path: scan, builder: () => const ScanPage(), - middleware: [AdminMiddleware(ref, isPurchasesAdminProvider)], + middleware: [SuperAdminMiddleware(ref, isPurchasesSuperAdminProvider)], ), QRoute( path: history, diff --git a/lib/purchases/ui/pages/main_page/custom_button.dart b/lib/purchases/ui/pages/main_page/custom_button.dart index b53f37d723..37f946e98c 100644 --- a/lib/purchases/ui/pages/main_page/custom_button.dart +++ b/lib/purchases/ui/pages/main_page/custom_button.dart @@ -13,7 +13,7 @@ class CustomButton extends StatelessWidget { required this.onTap, this.textColor = Colors.white, this.color = Colors.black, - this.text = 'Admin', + this.text = 'SuperAdmin', this.colors, required this.icon, }); diff --git a/lib/purchases/ui/pages/main_page/main_page.dart b/lib/purchases/ui/pages/main_page/main_page.dart index 3b55560b5e..18a3031ab9 100644 --- a/lib/purchases/ui/pages/main_page/main_page.dart +++ b/lib/purchases/ui/pages/main_page/main_page.dart @@ -20,7 +20,7 @@ class PurchasesMainPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final isAdmin = ref.watch(isPurchasesAdminProvider); + final isSuperAdmin = ref.watch(isPurchasesSuperAdminProvider); final ticketList = ref.watch(ticketListProvider); final ticketListNotifier = ref.watch(ticketListProvider.notifier); final ticketNotifier = ref.watch(ticketProvider.notifier); @@ -44,7 +44,7 @@ class PurchasesMainPage extends HookConsumerWidget { QR.to(PurchasesRouter.root + PurchasesRouter.history); }, ), - if (isAdmin) + if (isSuperAdmin) CustomButton( icon: HeroIcons.viewfinderCircle, text: AppLocalizations.of(context)!.purchasesScan, diff --git a/lib/raffle/class/raffle.dart b/lib/raffle/class/raffle.dart index 2b8ff7fa99..c934719a60 100644 --- a/lib/raffle/class/raffle.dart +++ b/lib/raffle/class/raffle.dart @@ -1,4 +1,4 @@ -import 'package:titan/admin/class/simple_group.dart'; +import 'package:titan/super_admin/class/simple_group.dart'; import 'package:titan/raffle/class/raffle_status_type.dart'; import 'package:titan/raffle/tools/functions.dart'; diff --git a/lib/raffle/providers/is_raffle_admin.dart b/lib/raffle/providers/is_raffle_admin.dart index 8aca22f6d8..25acc786ed 100644 --- a/lib/raffle/providers/is_raffle_admin.dart +++ b/lib/raffle/providers/is_raffle_admin.dart @@ -1,7 +1,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/user/providers/user_provider.dart'; -final isRaffleAdminProvider = StateProvider((ref) { +final isRaffleSuperAdminProvider = StateProvider((ref) { final me = ref.watch(userProvider); return me.groups .map((e) => e.id) diff --git a/lib/raffle/router.dart b/lib/raffle/router.dart index ed8a3bd61b..fcb513c96e 100644 --- a/lib/raffle/router.dart +++ b/lib/raffle/router.dart @@ -49,9 +49,9 @@ class RaffleRouter { children: [ QRoute( path: admin, - builder: () => admin_module_page.AdminModulePage(), + builder: () => admin_module_page.SuperAdminModulePage(), middleware: [ - AdminMiddleware(ref, isRaffleAdminProvider), + SuperAdminMiddleware(ref, isRaffleSuperAdminProvider), DeferredLoadingMiddleware(admin_module_page.loadLibrary), ], ), @@ -59,7 +59,7 @@ class RaffleRouter { path: detail, builder: () => raffle_page.RaffleInfoPage(), middleware: [ - AdminMiddleware(ref, isRaffleAdminProvider), + SuperAdminMiddleware(ref, isRaffleSuperAdminProvider), DeferredLoadingMiddleware(raffle_page.loadLibrary), ], children: [ diff --git a/lib/raffle/ui/pages/admin_module_page/admin_module_page.dart b/lib/raffle/ui/pages/admin_module_page/admin_module_page.dart index ffe4e75bcb..5b4793890c 100644 --- a/lib/raffle/ui/pages/admin_module_page/admin_module_page.dart +++ b/lib/raffle/ui/pages/admin_module_page/admin_module_page.dart @@ -6,8 +6,8 @@ import 'package:titan/raffle/ui/pages/admin_module_page/tombola_handler.dart'; import 'package:titan/raffle/ui/raffle.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; -class AdminModulePage extends HookConsumerWidget { - const AdminModulePage({super.key}); +class SuperAdminModulePage extends HookConsumerWidget { + const SuperAdminModulePage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { diff --git a/lib/raffle/ui/pages/admin_module_page/confirm_creation.dart b/lib/raffle/ui/pages/admin_module_page/confirm_creation.dart index e2c03ffb3f..b108650547 100644 --- a/lib/raffle/ui/pages/admin_module_page/confirm_creation.dart +++ b/lib/raffle/ui/pages/admin_module_page/confirm_creation.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/class/simple_group.dart'; +import 'package:titan/super_admin/class/simple_group.dart'; import 'package:titan/raffle/class/raffle.dart'; import 'package:titan/raffle/class/raffle_status_type.dart'; import 'package:titan/raffle/providers/raffle_list_provider.dart'; diff --git a/lib/raffle/ui/pages/admin_module_page/tombola_handler.dart b/lib/raffle/ui/pages/admin_module_page/tombola_handler.dart index 4af0d41b0e..ccdfd298db 100644 --- a/lib/raffle/ui/pages/admin_module_page/tombola_handler.dart +++ b/lib/raffle/ui/pages/admin_module_page/tombola_handler.dart @@ -2,8 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/class/simple_group.dart'; -import 'package:titan/admin/providers/group_list_provider.dart'; +import 'package:titan/super_admin/class/simple_group.dart'; +import 'package:titan/super_admin/providers/group_list_provider.dart'; import 'package:titan/raffle/providers/raffle_list_provider.dart'; import 'package:titan/raffle/tools/constants.dart'; import 'package:titan/raffle/ui/pages/admin_module_page/confirm_creation.dart'; diff --git a/lib/raffle/ui/pages/main_page/main_page.dart b/lib/raffle/ui/pages/main_page/main_page.dart index 1b884e584c..9a901a8c89 100644 --- a/lib/raffle/ui/pages/main_page/main_page.dart +++ b/lib/raffle/ui/pages/main_page/main_page.dart @@ -29,7 +29,7 @@ class RaffleMainPage extends HookConsumerWidget { final raffleListNotifier = ref.watch(raffleListProvider.notifier); final userTicketList = ref.watch(userTicketListProvider); final userTicketListNotifier = ref.watch(userTicketListProvider.notifier); - final isAdmin = ref.watch(isRaffleAdminProvider); + final isSuperAdmin = ref.watch(isRaffleSuperAdminProvider); final tombolaLogosNotifier = ref.watch(tombolaLogosProvider.notifier); final rafflesStatus = {}; @@ -57,8 +57,8 @@ class RaffleMainPage extends HookConsumerWidget { SectionTitle( text: AppLocalizations.of(context)!.raffleTickets, ), - if (isAdmin) - AdminButton( + if (isSuperAdmin) + SuperAdminButton( onTap: () { QR.to(RaffleRouter.root + RaffleRouter.admin); }, diff --git a/lib/recommendation/providers/is_recommendation_admin_provider.dart b/lib/recommendation/providers/is_recommendation_admin_provider.dart index 343a990199..fcf2a79d9f 100644 --- a/lib/recommendation/providers/is_recommendation_admin_provider.dart +++ b/lib/recommendation/providers/is_recommendation_admin_provider.dart @@ -1,7 +1,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/user/providers/user_provider.dart'; -final isRecommendationAdminProvider = StateProvider((ref) { +final isRecommendationSuperAdminProvider = StateProvider((ref) { final me = ref.watch(userProvider); return me.groups .map((e) => e.id) diff --git a/lib/recommendation/router.dart b/lib/recommendation/router.dart index f1d375acea..3c7149d3d6 100644 --- a/lib/recommendation/router.dart +++ b/lib/recommendation/router.dart @@ -51,7 +51,7 @@ class RecommendationRouter { path: addEdit, builder: () => add_edit_page.AddEditRecommendationPage(), middleware: [ - AdminMiddleware(ref, isRecommendationAdminProvider), + SuperAdminMiddleware(ref, isRecommendationSuperAdminProvider), DeferredLoadingMiddleware(add_edit_page.loadLibrary), ], ), diff --git a/lib/recommendation/ui/pages/main_page.dart b/lib/recommendation/ui/pages/main_page.dart index dd0f782c83..f1fbca7f84 100644 --- a/lib/recommendation/ui/pages/main_page.dart +++ b/lib/recommendation/ui/pages/main_page.dart @@ -18,7 +18,9 @@ class RecommendationMainPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final isRecommendationAdmin = ref.watch(isRecommendationAdminProvider); + final isRecommendationSuperAdmin = ref.watch( + isRecommendationSuperAdminProvider, + ); final recommendationNotifier = ref.watch(recommendationProvider.notifier); final recommendationList = ref.watch(recommendationListProvider); final recommendationListNotifier = ref.watch( @@ -35,7 +37,7 @@ class RecommendationMainPage extends HookConsumerWidget { builder: (context, data) => Column( children: [ const SizedBox(height: 30), - if (isRecommendationAdmin) + if (isRecommendationSuperAdmin) GestureDetector( onTap: () { recommendationNotifier.setRecommendation( diff --git a/lib/recommendation/ui/widgets/recommendation_card.dart b/lib/recommendation/ui/widgets/recommendation_card.dart index 66880d7610..f907b961c8 100644 --- a/lib/recommendation/ui/widgets/recommendation_card.dart +++ b/lib/recommendation/ui/widgets/recommendation_card.dart @@ -30,7 +30,9 @@ class RecommendationCard extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final isRecommendationAdmin = ref.watch(isRecommendationAdminProvider); + final isRecommendationSuperAdmin = ref.watch( + isRecommendationSuperAdminProvider, + ); final recommendationNotifier = ref.watch(recommendationProvider.notifier); final recommendationListNotifier = ref.watch( recommendationListProvider.notifier, @@ -137,7 +139,7 @@ class RecommendationCard extends HookConsumerWidget { ) : SizedBox( width: 50, - child: isRecommendationAdmin + child: isRecommendationSuperAdmin ? Column( children: [ GestureDetector( diff --git a/lib/router.dart b/lib/router.dart index 68bb50ad65..035371785d 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/admin/router.dart'; +import 'package:titan/super_admin/router.dart'; import 'package:titan/advert/router.dart'; import 'package:titan/amap/router.dart'; import 'package:titan/booking/router.dart'; @@ -88,7 +89,7 @@ class AppRouter { FadeTransition(opacity: animation, child: child), ), ), - AdminRouter(ref).route(), + SuperAdminRouter(ref).route(), AdvertRouter(ref).route(), AmapRouter(ref).route(), BookingRouter(ref).route(), @@ -112,6 +113,7 @@ class AppRouter { StyleGuideRouter(ref).route(), VoteRouter(ref).route(), SeedLibraryRouter(ref).route(), + AdminRouter(ref).route(), ]; } } diff --git a/lib/seed-library/providers/is_seed_library_admin_provider.dart b/lib/seed-library/providers/is_seed_library_admin_provider.dart index 2fbe5945f5..08422a7964 100644 --- a/lib/seed-library/providers/is_seed_library_admin_provider.dart +++ b/lib/seed-library/providers/is_seed_library_admin_provider.dart @@ -1,7 +1,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/user/providers/user_provider.dart'; -final isSeedLibraryAdminProvider = StateProvider((ref) { +final isSeedLibrarySuperAdminProvider = StateProvider((ref) { final me = ref.watch(userProvider); return me.groups .map((e) => e.id) diff --git a/lib/seed-library/repositories/plants_repository.dart b/lib/seed-library/repositories/plants_repository.dart index a9908c90e5..aaa14cfe85 100644 --- a/lib/seed-library/repositories/plants_repository.dart +++ b/lib/seed-library/repositories/plants_repository.dart @@ -24,7 +24,7 @@ class PlantsRepository extends Repository { return PlantSimple.fromJson(await getOne(plantsId)); } - Future> getListPlantSimpleAdmin(String userId) async { + Future> getListPlantSimpleSuperAdmin(String userId) async { return List.from( (await getList( suffix: "users/$userId", diff --git a/lib/seed-library/router.dart b/lib/seed-library/router.dart index 6b790884af..7f655e8351 100644 --- a/lib/seed-library/router.dart +++ b/lib/seed-library/router.dart @@ -75,7 +75,7 @@ class SeedLibraryRouter { path: SeedLibraryRouter.editInformation, builder: () => edit_information_page.EditInformationPage(), middleware: [ - AdminMiddleware(ref, isSeedLibraryAdminProvider), + SuperAdminMiddleware(ref, isSeedLibrarySuperAdminProvider), DeferredLoadingMiddleware(edit_information_page.loadLibrary), ], ), @@ -85,7 +85,7 @@ class SeedLibraryRouter { path: species, builder: () => species_page.SpeciesPage(), middleware: [ - AdminMiddleware(ref, isSeedLibraryAdminProvider), + SuperAdminMiddleware(ref, isSeedLibrarySuperAdminProvider), DeferredLoadingMiddleware(species_page.loadLibrary), ], children: [ diff --git a/lib/seed-library/ui/pages/information_page/text.dart b/lib/seed-library/ui/pages/information_page/text.dart index db68ad8f2f..41dc351994 100644 --- a/lib/seed-library/ui/pages/information_page/text.dart +++ b/lib/seed-library/ui/pages/information_page/text.dart @@ -14,7 +14,7 @@ class InformationPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final information = ref.watch(informationProvider); - final isSeedLibraryAdmin = ref.watch(isSeedLibraryAdminProvider); + final isSeedLibrarySuperAdmin = ref.watch(isSeedLibrarySuperAdminProvider); return SeedLibraryTemplate( child: SingleChildScrollView( @@ -25,7 +25,7 @@ class InformationPage extends HookConsumerWidget { value: information, builder: (context, info) => Column( children: [ - if (isSeedLibraryAdmin) + if (isSeedLibrarySuperAdmin) GestureDetector( onTap: () { QR.to( diff --git a/lib/seed-library/ui/pages/main_page/main_page.dart b/lib/seed-library/ui/pages/main_page/main_page.dart index 7a2906afbe..bfba111b30 100644 --- a/lib/seed-library/ui/pages/main_page/main_page.dart +++ b/lib/seed-library/ui/pages/main_page/main_page.dart @@ -22,7 +22,7 @@ class SeedLibraryMainPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final isSeedLibraryAdmin = ref.watch(isSeedLibraryAdminProvider); + final isSeedLibrarySuperAdmin = ref.watch(isSeedLibrarySuperAdminProvider); final information = ref.watch(syncInformationProvider); final speciesNotifier = ref.watch(speciesProvider.notifier); final seasonNotifier = ref.watch(seasonFilterProvider.notifier); @@ -56,7 +56,7 @@ class SeedLibraryMainPage extends HookConsumerWidget { : 1.5, ), children: [ - if (isSeedLibraryAdmin) + if (isSeedLibrarySuperAdmin) GestureDetector( onTap: () { resetNotifier(); diff --git a/lib/seed-library/ui/pages/plant_deposit_page/plant_deposit_page.dart b/lib/seed-library/ui/pages/plant_deposit_page/plant_deposit_page.dart index 749b2329fa..dbcf19c048 100644 --- a/lib/seed-library/ui/pages/plant_deposit_page/plant_deposit_page.dart +++ b/lib/seed-library/ui/pages/plant_deposit_page/plant_deposit_page.dart @@ -30,7 +30,7 @@ class PlantDepositPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final seedLibraryAdmin = ref.watch(isSeedLibraryAdminProvider); + final seedLibrarySuperAdmin = ref.watch(isSeedLibrarySuperAdminProvider); final key = GlobalKey(); final scrollController = useScrollController(); final species = ref.watch(syncSpeciesListProvider); @@ -66,7 +66,7 @@ class PlantDepositPage extends HookConsumerWidget { physics: const AlwaysScrollableScrollPhysics( parent: BouncingScrollPhysics(), ), - child: (myPlants.isEmpty && !seedLibraryAdmin) + child: (myPlants.isEmpty && !seedLibrarySuperAdmin) ? const Center( child: Text( SeedLibraryTextConstants.depositNotAvailable, @@ -121,7 +121,7 @@ class PlantDepositPage extends HookConsumerWidget { ], ), ), - if (selectedAncestor.id == '' && seedLibraryAdmin) ...[ + if (selectedAncestor.id == '' && seedLibrarySuperAdmin) ...[ Text( SeedLibraryTextConstants.speciesSimple, style: TextStyle( @@ -206,7 +206,7 @@ class PlantDepositPage extends HookConsumerWidget { } if (selectedAncestor.id == '' && selectedSpecies.id == '') { - if (seedLibraryAdmin) { + if (seedLibrarySuperAdmin) { displayToastWithContext( TypeMsg.error, SeedLibraryTextConstants diff --git a/lib/service/provider_list.dart b/lib/service/provider_list.dart index 1f2713c1ff..2fdd310e02 100644 --- a/lib/service/provider_list.dart +++ b/lib/service/provider_list.dart @@ -1,4 +1,4 @@ -import 'package:titan/admin/notification_service.dart'; +import 'package:titan/super_admin/notification_service.dart'; import 'package:titan/advert/notification_service.dart'; import 'package:titan/amap/notification_service.dart'; import 'package:titan/booking/notification_service.dart'; diff --git a/lib/settings/providers/module_list_provider.dart b/lib/settings/providers/module_list_provider.dart index d679c32d2e..0ddfb4372e 100644 --- a/lib/settings/providers/module_list_provider.dart +++ b/lib/settings/providers/module_list_provider.dart @@ -1,9 +1,10 @@ import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/admin/providers/is_admin_provider.dart'; import 'package:titan/admin/router.dart'; +import 'package:titan/super_admin/providers/is_admin_provider.dart'; +import 'package:titan/super_admin/router.dart'; import 'package:titan/advert/router.dart'; -import 'package:titan/admin/providers/all_my_module_roots_list_provider.dart'; +import 'package:titan/super_admin/providers/all_my_module_roots_list_provider.dart'; import 'package:titan/amap/router.dart'; import 'package:titan/booking/router.dart'; import 'package:titan/centralisation/router.dart'; @@ -32,9 +33,9 @@ final modulesProvider = StateNotifierProvider>(( .map((root) => '/$root') .toList(); - final isAdmin = ref.watch(isAdminProvider); + final isSuperAdmin = ref.watch(isSuperAdminProvider); - ModulesNotifier modulesNotifier = ModulesNotifier(isAdmin: isAdmin); + ModulesNotifier modulesNotifier = ModulesNotifier(isSuperAdmin: isSuperAdmin); modulesNotifier.loadModules(myModulesRoot); return modulesNotifier; }); @@ -42,7 +43,7 @@ final modulesProvider = StateNotifierProvider>(( class ModulesNotifier extends StateNotifier> { String dbModule = "modules"; String dbAllModules = "allModules"; - final bool isAdmin; + final bool isSuperAdmin; final eq = const DeepCollectionEquality.unordered(); List allModules = [ HomeRouter.module, @@ -61,8 +62,9 @@ class ModulesNotifier extends StateNotifier> { RecommendationRouter.module, VoteRouter.module, SeedLibraryRouter.module, + AdminRouter.module, ]; - ModulesNotifier({required this.isAdmin}) : super([]); + ModulesNotifier({required this.isSuperAdmin}) : super([]); void saveModules() { SharedPreferences.getInstance().then((prefs) { @@ -126,7 +128,10 @@ class ModulesNotifier extends StateNotifier> { for (Module module in toDelete) { allModules.remove(module); } - allModules.addAll([SettingsRouter.module, if (isAdmin) AdminRouter.module]); + allModules.addAll([ + SettingsRouter.module, + if (isSuperAdmin) SuperAdminRouter.module, + ]); state = allModules; } diff --git a/lib/admin/class/account_type.dart b/lib/super_admin/class/account_type.dart similarity index 100% rename from lib/admin/class/account_type.dart rename to lib/super_admin/class/account_type.dart diff --git a/lib/admin/class/association_membership_simple.dart b/lib/super_admin/class/association_membership_simple.dart similarity index 100% rename from lib/admin/class/association_membership_simple.dart rename to lib/super_admin/class/association_membership_simple.dart diff --git a/lib/admin/class/group.dart b/lib/super_admin/class/group.dart similarity index 95% rename from lib/admin/class/group.dart rename to lib/super_admin/class/group.dart index bf7eb29335..66c9c99c16 100644 --- a/lib/admin/class/group.dart +++ b/lib/super_admin/class/group.dart @@ -1,4 +1,4 @@ -import 'package:titan/admin/class/simple_group.dart'; +import 'package:titan/super_admin/class/simple_group.dart'; import 'package:titan/user/class/simple_users.dart'; class Group { diff --git a/lib/admin/class/module_visibility.dart b/lib/super_admin/class/module_visibility.dart similarity index 95% rename from lib/admin/class/module_visibility.dart rename to lib/super_admin/class/module_visibility.dart index 8e2ef1a441..b4f8452b8d 100644 --- a/lib/admin/class/module_visibility.dart +++ b/lib/super_admin/class/module_visibility.dart @@ -1,4 +1,4 @@ -import 'package:titan/admin/class/account_type.dart'; +import 'package:titan/super_admin/class/account_type.dart'; class ModuleVisibility { ModuleVisibility({ diff --git a/lib/admin/class/school.dart b/lib/super_admin/class/school.dart similarity index 100% rename from lib/admin/class/school.dart rename to lib/super_admin/class/school.dart diff --git a/lib/admin/class/simple_group.dart b/lib/super_admin/class/simple_group.dart similarity index 100% rename from lib/admin/class/simple_group.dart rename to lib/super_admin/class/simple_group.dart diff --git a/lib/admin/class/user_association_membership.dart b/lib/super_admin/class/user_association_membership.dart similarity index 94% rename from lib/admin/class/user_association_membership.dart rename to lib/super_admin/class/user_association_membership.dart index 1aab569848..5938b5673a 100644 --- a/lib/admin/class/user_association_membership.dart +++ b/lib/super_admin/class/user_association_membership.dart @@ -1,4 +1,4 @@ -import 'package:titan/admin/class/user_association_membership_base.dart'; +import 'package:titan/super_admin/class/user_association_membership_base.dart'; import 'package:titan/user/class/simple_users.dart'; class UserAssociationMembership extends UserAssociationMembershipBase { diff --git a/lib/admin/class/user_association_membership_base.dart b/lib/super_admin/class/user_association_membership_base.dart similarity index 100% rename from lib/admin/class/user_association_membership_base.dart rename to lib/super_admin/class/user_association_membership_base.dart diff --git a/lib/admin/notification_service.dart b/lib/super_admin/notification_service.dart similarity index 70% rename from lib/admin/notification_service.dart rename to lib/super_admin/notification_service.dart index 42346e87a7..a8ac035d5e 100644 --- a/lib/admin/notification_service.dart +++ b/lib/super_admin/notification_service.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/admin/providers/group_list_provider.dart'; -import 'package:titan/admin/router.dart'; +import 'package:titan/super_admin/providers/group_list_provider.dart'; +import 'package:titan/super_admin/router.dart'; import 'package:titan/router.dart'; import 'package:titan/user/providers/user_provider.dart'; import 'package:tuple/tuple.dart'; @@ -12,5 +12,5 @@ final Map>> adminProviders = allGroupListProvider, asyncUserProvider, ]), - "groups": Tuple2(AdminRouter.root, [allGroupListProvider]), + "groups": Tuple2(SuperAdminRouter.root, [allGroupListProvider]), }; diff --git a/lib/admin/providers/account_types_list_provider.dart b/lib/super_admin/providers/account_types_list_provider.dart similarity index 87% rename from lib/admin/providers/account_types_list_provider.dart rename to lib/super_admin/providers/account_types_list_provider.dart index c0b850faee..9f6c3e135b 100644 --- a/lib/admin/providers/account_types_list_provider.dart +++ b/lib/super_admin/providers/account_types_list_provider.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/admin/class/account_type.dart'; -import 'package:titan/admin/repositories/account_type_repository.dart'; +import 'package:titan/super_admin/class/account_type.dart'; +import 'package:titan/super_admin/repositories/account_type_repository.dart'; import 'package:titan/tools/providers/list_notifier.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; diff --git a/lib/admin/providers/all_account_types_list_provider.dart b/lib/super_admin/providers/all_account_types_list_provider.dart similarity index 62% rename from lib/admin/providers/all_account_types_list_provider.dart rename to lib/super_admin/providers/all_account_types_list_provider.dart index 51bd6ca7ff..2cc1986f90 100644 --- a/lib/admin/providers/all_account_types_list_provider.dart +++ b/lib/super_admin/providers/all_account_types_list_provider.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/admin/class/account_type.dart'; -import 'package:titan/admin/providers/account_types_list_provider.dart'; +import 'package:titan/super_admin/class/account_type.dart'; +import 'package:titan/super_admin/providers/account_types_list_provider.dart'; final allAccountTypes = Provider>((ref) { return ref diff --git a/lib/admin/providers/all_groups_list_provider.dart b/lib/super_admin/providers/all_groups_list_provider.dart similarity index 63% rename from lib/admin/providers/all_groups_list_provider.dart rename to lib/super_admin/providers/all_groups_list_provider.dart index b2e6d14d82..f1c48dd26d 100644 --- a/lib/admin/providers/all_groups_list_provider.dart +++ b/lib/super_admin/providers/all_groups_list_provider.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/admin/class/simple_group.dart'; -import 'package:titan/admin/providers/group_list_provider.dart'; +import 'package:titan/super_admin/class/simple_group.dart'; +import 'package:titan/super_admin/providers/group_list_provider.dart'; final allGroupList = Provider>((ref) { return ref diff --git a/lib/admin/providers/all_my_module_roots_list_provider.dart b/lib/super_admin/providers/all_my_module_roots_list_provider.dart similarity index 74% rename from lib/admin/providers/all_my_module_roots_list_provider.dart rename to lib/super_admin/providers/all_my_module_roots_list_provider.dart index 22aafc4a5b..f9a90bd7fd 100644 --- a/lib/admin/providers/all_my_module_roots_list_provider.dart +++ b/lib/super_admin/providers/all_my_module_roots_list_provider.dart @@ -1,5 +1,5 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/admin/providers/module_root_list_provider.dart'; +import 'package:titan/super_admin/providers/module_root_list_provider.dart'; final allMyModuleRootList = Provider>((ref) { return ref diff --git a/lib/admin/providers/association_membership_filtered_members_provider.dart b/lib/super_admin/providers/association_membership_filtered_members_provider.dart similarity index 77% rename from lib/admin/providers/association_membership_filtered_members_provider.dart rename to lib/super_admin/providers/association_membership_filtered_members_provider.dart index e74bccbaf7..0a3c0ae760 100644 --- a/lib/admin/providers/association_membership_filtered_members_provider.dart +++ b/lib/super_admin/providers/association_membership_filtered_members_provider.dart @@ -1,7 +1,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/admin/class/user_association_membership.dart'; -import 'package:titan/admin/providers/association_membership_members_list_provider.dart'; -import 'package:titan/admin/providers/research_filter_provider.dart'; +import 'package:titan/super_admin/class/user_association_membership.dart'; +import 'package:titan/super_admin/providers/association_membership_members_list_provider.dart'; +import 'package:titan/super_admin/providers/research_filter_provider.dart'; import 'package:diacritic/diacritic.dart'; final associationMembershipFilteredListProvider = diff --git a/lib/admin/providers/association_membership_list_provider.dart b/lib/super_admin/providers/association_membership_list_provider.dart similarity index 93% rename from lib/admin/providers/association_membership_list_provider.dart rename to lib/super_admin/providers/association_membership_list_provider.dart index b827c14a46..aaaa70f591 100644 --- a/lib/admin/providers/association_membership_list_provider.dart +++ b/lib/super_admin/providers/association_membership_list_provider.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/admin/class/association_membership_simple.dart'; -import 'package:titan/admin/repositories/association_membership_repository.dart'; +import 'package:titan/super_admin/class/association_membership_simple.dart'; +import 'package:titan/super_admin/repositories/association_membership_repository.dart'; import 'package:titan/tools/providers/list_notifier.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; diff --git a/lib/admin/providers/association_membership_members_list_provider.dart b/lib/super_admin/providers/association_membership_members_list_provider.dart similarity index 90% rename from lib/admin/providers/association_membership_members_list_provider.dart rename to lib/super_admin/providers/association_membership_members_list_provider.dart index 6964dbaec3..a8aa14f4f4 100644 --- a/lib/admin/providers/association_membership_members_list_provider.dart +++ b/lib/super_admin/providers/association_membership_members_list_provider.dart @@ -1,8 +1,8 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/class/user_association_membership.dart'; -import 'package:titan/admin/class/user_association_membership_base.dart'; -import 'package:titan/admin/repositories/association_membership_repository.dart'; -import 'package:titan/admin/repositories/association_membership_user_repository.dart'; +import 'package:titan/super_admin/class/user_association_membership.dart'; +import 'package:titan/super_admin/class/user_association_membership_base.dart'; +import 'package:titan/super_admin/repositories/association_membership_repository.dart'; +import 'package:titan/super_admin/repositories/association_membership_user_repository.dart'; import 'package:titan/tools/providers/list_notifier.dart'; import 'package:titan/user/class/simple_users.dart'; diff --git a/lib/admin/providers/association_membership_provider.dart b/lib/super_admin/providers/association_membership_provider.dart similarity index 86% rename from lib/admin/providers/association_membership_provider.dart rename to lib/super_admin/providers/association_membership_provider.dart index 5e44b3ed45..338f3e1e3c 100644 --- a/lib/admin/providers/association_membership_provider.dart +++ b/lib/super_admin/providers/association_membership_provider.dart @@ -1,5 +1,5 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/admin/class/association_membership_simple.dart'; +import 'package:titan/super_admin/class/association_membership_simple.dart'; class AssociationMembershipNotifier extends StateNotifier { diff --git a/lib/admin/providers/group_id_provider.dart b/lib/super_admin/providers/group_id_provider.dart similarity index 100% rename from lib/admin/providers/group_id_provider.dart rename to lib/super_admin/providers/group_id_provider.dart diff --git a/lib/admin/providers/group_list_provider.dart b/lib/super_admin/providers/group_list_provider.dart similarity index 94% rename from lib/admin/providers/group_list_provider.dart rename to lib/super_admin/providers/group_list_provider.dart index a6edbc8c57..8f5edbd102 100644 --- a/lib/admin/providers/group_list_provider.dart +++ b/lib/super_admin/providers/group_list_provider.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/admin/class/simple_group.dart'; -import 'package:titan/admin/repositories/group_repository.dart'; +import 'package:titan/super_admin/class/simple_group.dart'; +import 'package:titan/super_admin/repositories/group_repository.dart'; import 'package:titan/tools/providers/list_notifier.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/user/class/user.dart'; diff --git a/lib/admin/providers/group_logo_provider.dart b/lib/super_admin/providers/group_logo_provider.dart similarity index 93% rename from lib/admin/providers/group_logo_provider.dart rename to lib/super_admin/providers/group_logo_provider.dart index aef5ac86fa..5dfcca5e3f 100644 --- a/lib/admin/providers/group_logo_provider.dart +++ b/lib/super_admin/providers/group_logo_provider.dart @@ -2,7 +2,7 @@ import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/admin/repositories/group_logo_repository.dart'; +import 'package:titan/super_admin/repositories/group_logo_repository.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/providers/single_notifier.dart'; diff --git a/lib/admin/providers/group_provider.dart b/lib/super_admin/providers/group_provider.dart similarity index 90% rename from lib/admin/providers/group_provider.dart rename to lib/super_admin/providers/group_provider.dart index 5ba31f1d4c..123db1bd1f 100644 --- a/lib/admin/providers/group_provider.dart +++ b/lib/super_admin/providers/group_provider.dart @@ -1,6 +1,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/class/group.dart'; -import 'package:titan/admin/repositories/group_repository.dart'; +import 'package:titan/super_admin/class/group.dart'; +import 'package:titan/super_admin/repositories/group_repository.dart'; import 'package:titan/tools/providers/single_notifier.dart'; import 'package:titan/user/class/simple_users.dart'; diff --git a/lib/admin/providers/is_admin_provider.dart b/lib/super_admin/providers/is_admin_provider.dart similarity index 81% rename from lib/admin/providers/is_admin_provider.dart rename to lib/super_admin/providers/is_admin_provider.dart index a883e1bd04..5df7e1e55a 100644 --- a/lib/admin/providers/is_admin_provider.dart +++ b/lib/super_admin/providers/is_admin_provider.dart @@ -1,7 +1,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/user/providers/user_provider.dart'; -final isAdminProvider = StateProvider((ref) { +final isSuperAdminProvider = StateProvider((ref) { final me = ref.watch(userProvider); return me.groups .map((e) => e.id) diff --git a/lib/admin/providers/is_expanded_list_provider.dart b/lib/super_admin/providers/is_expanded_list_provider.dart similarity index 82% rename from lib/admin/providers/is_expanded_list_provider.dart rename to lib/super_admin/providers/is_expanded_list_provider.dart index f895def407..c2297526ac 100644 --- a/lib/admin/providers/is_expanded_list_provider.dart +++ b/lib/super_admin/providers/is_expanded_list_provider.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/admin/class/module_visibility.dart'; -import 'package:titan/admin/providers/module_visibility_list_provider.dart'; +import 'package:titan/super_admin/class/module_visibility.dart'; +import 'package:titan/super_admin/providers/module_visibility_list_provider.dart'; class IsExpandedListProvider extends StateNotifier> { IsExpandedListProvider(List modules) diff --git a/lib/admin/providers/members_provider.dart b/lib/super_admin/providers/members_provider.dart similarity index 100% rename from lib/admin/providers/members_provider.dart rename to lib/super_admin/providers/members_provider.dart diff --git a/lib/admin/providers/module_root_list_provider.dart b/lib/super_admin/providers/module_root_list_provider.dart similarity index 93% rename from lib/admin/providers/module_root_list_provider.dart rename to lib/super_admin/providers/module_root_list_provider.dart index fea7948193..e55fce6485 100644 --- a/lib/admin/providers/module_root_list_provider.dart +++ b/lib/super_admin/providers/module_root_list_provider.dart @@ -1,5 +1,5 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/admin/repositories/module_visibility_repository.dart'; +import 'package:titan/super_admin/repositories/module_visibility_repository.dart'; import 'package:titan/auth/providers/openid_provider.dart'; import 'package:titan/tools/providers/list_notifier.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; diff --git a/lib/admin/providers/module_visibility_list_provider.dart b/lib/super_admin/providers/module_visibility_list_provider.dart similarity index 95% rename from lib/admin/providers/module_visibility_list_provider.dart rename to lib/super_admin/providers/module_visibility_list_provider.dart index 0f7d4b9615..5917c76b7e 100644 --- a/lib/admin/providers/module_visibility_list_provider.dart +++ b/lib/super_admin/providers/module_visibility_list_provider.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/admin/class/module_visibility.dart'; -import 'package:titan/admin/repositories/module_visibility_repository.dart'; +import 'package:titan/super_admin/class/module_visibility.dart'; +import 'package:titan/super_admin/repositories/module_visibility_repository.dart'; import 'package:titan/auth/providers/openid_provider.dart'; import 'package:titan/tools/providers/list_notifier.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; diff --git a/lib/admin/providers/research_filter_provider.dart b/lib/super_admin/providers/research_filter_provider.dart similarity index 100% rename from lib/admin/providers/research_filter_provider.dart rename to lib/super_admin/providers/research_filter_provider.dart diff --git a/lib/admin/providers/school_id_provider.dart b/lib/super_admin/providers/school_id_provider.dart similarity index 100% rename from lib/admin/providers/school_id_provider.dart rename to lib/super_admin/providers/school_id_provider.dart diff --git a/lib/admin/providers/school_list_provider.dart b/lib/super_admin/providers/school_list_provider.dart similarity index 93% rename from lib/admin/providers/school_list_provider.dart rename to lib/super_admin/providers/school_list_provider.dart index 3490e330b9..812647533b 100644 --- a/lib/admin/providers/school_list_provider.dart +++ b/lib/super_admin/providers/school_list_provider.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/admin/class/school.dart'; -import 'package:titan/admin/repositories/school_repository.dart'; +import 'package:titan/super_admin/class/school.dart'; +import 'package:titan/super_admin/repositories/school_repository.dart'; import 'package:titan/tools/providers/list_notifier.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; diff --git a/lib/admin/providers/school_provider.dart b/lib/super_admin/providers/school_provider.dart similarity index 79% rename from lib/admin/providers/school_provider.dart rename to lib/super_admin/providers/school_provider.dart index 4c0b5f0f6c..93022d8a27 100644 --- a/lib/admin/providers/school_provider.dart +++ b/lib/super_admin/providers/school_provider.dart @@ -1,6 +1,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/class/school.dart'; -import 'package:titan/admin/repositories/school_repository.dart'; +import 'package:titan/super_admin/class/school.dart'; +import 'package:titan/super_admin/repositories/school_repository.dart'; class SchoolNotifier extends StateNotifier { final SchoolRepository schoolRepository; diff --git a/lib/admin/providers/section_logo_provider.dart b/lib/super_admin/providers/section_logo_provider.dart similarity index 90% rename from lib/admin/providers/section_logo_provider.dart rename to lib/super_admin/providers/section_logo_provider.dart index 61ca4e31f0..9a13547d56 100644 --- a/lib/admin/providers/section_logo_provider.dart +++ b/lib/super_admin/providers/section_logo_provider.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/admin/class/simple_group.dart'; +import 'package:titan/super_admin/class/simple_group.dart'; import 'package:titan/tools/providers/map_provider.dart'; class SimpleGroupLogoNotifier extends MapNotifier { diff --git a/lib/admin/providers/simple_groups_groups_provider.dart b/lib/super_admin/providers/simple_groups_groups_provider.dart similarity index 87% rename from lib/admin/providers/simple_groups_groups_provider.dart rename to lib/super_admin/providers/simple_groups_groups_provider.dart index a9732b8c8d..86a0e11ee9 100644 --- a/lib/admin/providers/simple_groups_groups_provider.dart +++ b/lib/super_admin/providers/simple_groups_groups_provider.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/admin/class/group.dart'; -import 'package:titan/admin/providers/group_list_provider.dart'; +import 'package:titan/super_admin/class/group.dart'; +import 'package:titan/super_admin/providers/group_list_provider.dart'; import 'package:titan/tools/providers/map_provider.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; diff --git a/lib/admin/providers/structure_manager_provider.dart b/lib/super_admin/providers/structure_manager_provider.dart similarity index 100% rename from lib/admin/providers/structure_manager_provider.dart rename to lib/super_admin/providers/structure_manager_provider.dart diff --git a/lib/admin/providers/structure_provider.dart b/lib/super_admin/providers/structure_provider.dart similarity index 100% rename from lib/admin/providers/structure_provider.dart rename to lib/super_admin/providers/structure_provider.dart diff --git a/lib/admin/providers/user_association_membership_list_provider.dart b/lib/super_admin/providers/user_association_membership_list_provider.dart similarity index 92% rename from lib/admin/providers/user_association_membership_list_provider.dart rename to lib/super_admin/providers/user_association_membership_list_provider.dart index 9a6ad7875a..19c3f405cf 100644 --- a/lib/admin/providers/user_association_membership_list_provider.dart +++ b/lib/super_admin/providers/user_association_membership_list_provider.dart @@ -1,6 +1,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/class/user_association_membership.dart'; -import 'package:titan/admin/repositories/association_membership_user_repository.dart'; +import 'package:titan/super_admin/class/user_association_membership.dart'; +import 'package:titan/super_admin/repositories/association_membership_user_repository.dart'; import 'package:titan/tools/providers/list_notifier.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; diff --git a/lib/admin/providers/user_association_membership_provider.dart b/lib/super_admin/providers/user_association_membership_provider.dart similarity index 88% rename from lib/admin/providers/user_association_membership_provider.dart rename to lib/super_admin/providers/user_association_membership_provider.dart index 859dcdbc35..0c4b73be7e 100644 --- a/lib/admin/providers/user_association_membership_provider.dart +++ b/lib/super_admin/providers/user_association_membership_provider.dart @@ -1,5 +1,5 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/admin/class/user_association_membership.dart'; +import 'package:titan/super_admin/class/user_association_membership.dart'; class UserAssociationMembershipNotifier extends StateNotifier { diff --git a/lib/admin/repositories/account_type_repository.dart b/lib/super_admin/repositories/account_type_repository.dart similarity index 91% rename from lib/admin/repositories/account_type_repository.dart rename to lib/super_admin/repositories/account_type_repository.dart index 9dff0fac15..c2fc1aa95c 100644 --- a/lib/admin/repositories/account_type_repository.dart +++ b/lib/super_admin/repositories/account_type_repository.dart @@ -1,5 +1,5 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/admin/class/account_type.dart'; +import 'package:titan/super_admin/class/account_type.dart'; import 'package:titan/auth/providers/openid_provider.dart'; import 'package:titan/tools/repository/repository.dart'; diff --git a/lib/admin/repositories/association_membership_repository.dart b/lib/super_admin/repositories/association_membership_repository.dart similarity index 94% rename from lib/admin/repositories/association_membership_repository.dart rename to lib/super_admin/repositories/association_membership_repository.dart index 4b20a78a46..bd2f33da3f 100644 --- a/lib/admin/repositories/association_membership_repository.dart +++ b/lib/super_admin/repositories/association_membership_repository.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/admin/class/association_membership_simple.dart'; -import 'package:titan/admin/class/user_association_membership.dart'; +import 'package:titan/super_admin/class/association_membership_simple.dart'; +import 'package:titan/super_admin/class/user_association_membership.dart'; import 'package:titan/auth/providers/openid_provider.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/repository/repository.dart'; diff --git a/lib/admin/repositories/association_membership_user_repository.dart b/lib/super_admin/repositories/association_membership_user_repository.dart similarity index 91% rename from lib/admin/repositories/association_membership_user_repository.dart rename to lib/super_admin/repositories/association_membership_user_repository.dart index ff22deb29a..13a6d6cc8a 100644 --- a/lib/admin/repositories/association_membership_user_repository.dart +++ b/lib/super_admin/repositories/association_membership_user_repository.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/admin/class/user_association_membership.dart'; -import 'package:titan/admin/class/user_association_membership_base.dart'; +import 'package:titan/super_admin/class/user_association_membership.dart'; +import 'package:titan/super_admin/class/user_association_membership_base.dart'; import 'package:titan/auth/providers/openid_provider.dart'; import 'package:titan/tools/repository/repository.dart'; diff --git a/lib/admin/repositories/group_logo_repository.dart b/lib/super_admin/repositories/group_logo_repository.dart similarity index 100% rename from lib/admin/repositories/group_logo_repository.dart rename to lib/super_admin/repositories/group_logo_repository.dart diff --git a/lib/admin/repositories/group_repository.dart b/lib/super_admin/repositories/group_repository.dart similarity index 94% rename from lib/admin/repositories/group_repository.dart rename to lib/super_admin/repositories/group_repository.dart index 869937d95d..1ee817dcc5 100644 --- a/lib/admin/repositories/group_repository.dart +++ b/lib/super_admin/repositories/group_repository.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/admin/class/group.dart'; -import 'package:titan/admin/class/simple_group.dart'; +import 'package:titan/super_admin/class/group.dart'; +import 'package:titan/super_admin/class/simple_group.dart'; import 'package:titan/auth/providers/openid_provider.dart'; import 'package:titan/tools/repository/repository.dart'; import 'package:titan/user/class/simple_users.dart'; diff --git a/lib/admin/repositories/module_visibility_repository.dart b/lib/super_admin/repositories/module_visibility_repository.dart similarity index 94% rename from lib/admin/repositories/module_visibility_repository.dart rename to lib/super_admin/repositories/module_visibility_repository.dart index 6d4e80b15f..08fcfb1770 100644 --- a/lib/admin/repositories/module_visibility_repository.dart +++ b/lib/super_admin/repositories/module_visibility_repository.dart @@ -1,4 +1,4 @@ -import 'package:titan/admin/class/module_visibility.dart'; +import 'package:titan/super_admin/class/module_visibility.dart'; import 'package:titan/tools/repository/repository.dart'; class ModuleVisibilityRepository extends Repository { diff --git a/lib/admin/repositories/school_repository.dart b/lib/super_admin/repositories/school_repository.dart similarity index 94% rename from lib/admin/repositories/school_repository.dart rename to lib/super_admin/repositories/school_repository.dart index ff0e55a6f1..814ccef216 100644 --- a/lib/admin/repositories/school_repository.dart +++ b/lib/super_admin/repositories/school_repository.dart @@ -1,5 +1,5 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/admin/class/school.dart'; +import 'package:titan/super_admin/class/school.dart'; import 'package:titan/auth/providers/openid_provider.dart'; import 'package:titan/tools/repository/repository.dart'; diff --git a/lib/super_admin/router.dart b/lib/super_admin/router.dart new file mode 100644 index 0000000000..8757986a0d --- /dev/null +++ b/lib/super_admin/router.dart @@ -0,0 +1,203 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/super_admin/providers/is_admin_provider.dart'; +import 'package:titan/super_admin/ui/pages/groups/add_group_page/add_group_page.dart' + deferred as add_group_page; +import 'package:titan/super_admin/ui/pages/groups/add_loaner_page/add_loaner_page.dart' + deferred as add_loaner_page; +import 'package:titan/super_admin/ui/pages/edit_module_visibility/edit_module_visibility.dart' + deferred as edit_module_visibility; +import 'package:titan/super_admin/ui/pages/groups/edit_group_page/edit_group_page.dart' + deferred as edit_group_page; +import 'package:titan/super_admin/ui/pages/groups/group_page/group_page.dart' + deferred as group_page; +import 'package:titan/super_admin/ui/pages/memberships/add_edit_user_membership_page/add_edit_user_membership_page.dart' + deferred as add_edit_user_membership_page; +import 'package:titan/super_admin/ui/pages/memberships/association_membership_detail_page/association_membership_detail_page.dart' + deferred as association_membership_detail_page; +import 'package:titan/super_admin/ui/pages/memberships/association_membership_page/association_membership_page.dart' + deferred as association_membership_page; +import 'package:titan/super_admin/ui/pages/schools/school_page/school_page.dart' + deferred as school_page; +import 'package:titan/super_admin/ui/pages/schools/add_school_page/add_school_page.dart' + deferred as add_school_page; +import 'package:titan/super_admin/ui/pages/schools/edit_school_page/edit_school_page.dart' + deferred as edit_school_page; +import 'package:titan/super_admin/ui/pages/structure_page/structure_page.dart' + deferred as structure_page; +import 'package:titan/super_admin/ui/pages/add_edit_structure_page/add_edit_structure_page.dart' + deferred as add_edit_structure_page; +import 'package:titan/super_admin/ui/pages/main_page/main_page.dart' + deferred as main_page; +import 'package:titan/navigation/class/module.dart'; +import 'package:titan/tools/middlewares/admin_middleware.dart'; +import 'package:titan/tools/middlewares/authenticated_middleware.dart'; +import 'package:titan/tools/middlewares/deferred_middleware.dart'; +import 'package:qlevar_router/qlevar_router.dart'; + +class SuperAdminRouter { + final Ref ref; + static const String root = '/super_admin'; + static const String groups = '/groups'; + static const String addGroup = '/add_group'; + static const String editGroup = '/edit_group'; + static const String addLoaner = '/add_loaner'; + static const String schools = '/schools'; + static const String addSchool = '/add_school'; + static const String editSchool = '/edit_school'; + static const String structures = '/structures'; + static const String addEditStructure = '/add_edit_structure'; + static const String editModuleVisibility = '/edit_module_visibility'; + static const String associationMemberships = '/association_memberships'; + static const String detailAssociationMembership = + '/detail_association_membership'; + static const String addEditMember = '/add_edit_member'; + static final Module module = Module( + getName: (context) => "SuperAdmin", + description: "Gérer les groupes, écoles et structures", + root: SuperAdminRouter.root, + ); + SuperAdminRouter(this.ref); + + QRoute route() => QRoute( + name: "super_admin", + path: SuperAdminRouter.root, + builder: () => main_page.SuperAdminMainPage(), + middleware: [ + AuthenticatedMiddleware(ref), + SuperAdminMiddleware(ref, isSuperAdminProvider), + DeferredLoadingMiddleware(main_page.loadLibrary), + ], + children: [ + QRoute( + path: groups, + builder: () => group_page.GroupsPage(), + middleware: [DeferredLoadingMiddleware(group_page.loadLibrary)], + children: [ + QRoute( + path: addGroup, + builder: () => add_group_page.AddGroupPage(), + middleware: [DeferredLoadingMiddleware(add_group_page.loadLibrary)], + ), + QRoute( + path: editGroup, + builder: () => edit_group_page.EditGroupPage(), + middleware: [ + DeferredLoadingMiddleware(edit_group_page.loadLibrary), + ], + ), + QRoute( + path: addLoaner, + builder: () => add_loaner_page.AddLoanerPage(), + middleware: [ + DeferredLoadingMiddleware(add_loaner_page.loadLibrary), + ], + ), + ], + ), + QRoute( + path: editModuleVisibility, + builder: () => edit_module_visibility.EditModulesVisibilityPage(), + middleware: [ + DeferredLoadingMiddleware(edit_module_visibility.loadLibrary), + ], + ), + QRoute( + path: schools, + builder: () => school_page.SchoolsPage(), + middleware: [DeferredLoadingMiddleware(school_page.loadLibrary)], + children: [ + QRoute( + path: addSchool, + builder: () => add_school_page.AddSchoolPage(), + middleware: [ + DeferredLoadingMiddleware(add_school_page.loadLibrary), + ], + ), + QRoute( + path: editSchool, + builder: () => edit_school_page.EditSchoolPage(), + middleware: [ + DeferredLoadingMiddleware(edit_school_page.loadLibrary), + ], + ), + ], + ), + QRoute( + path: associationMemberships, + builder: () => association_membership_page.AssociationMembershipsPage(), + middleware: [ + DeferredLoadingMiddleware(association_membership_page.loadLibrary), + ], + children: [ + QRoute( + path: detailAssociationMembership, + builder: () => + association_membership_detail_page.AssociationMembershipEditorPage(), + middleware: [ + DeferredLoadingMiddleware( + association_membership_detail_page.loadLibrary, + ), + ], + children: [ + QRoute( + path: addEditMember, + builder: () => + add_edit_user_membership_page.AddEditUserMembershipPage(), + middleware: [ + DeferredLoadingMiddleware( + add_edit_user_membership_page.loadLibrary, + ), + ], + ), + ], + ), + ], + ), + QRoute( + path: structures, + builder: () => structure_page.StructurePage(), + middleware: [DeferredLoadingMiddleware(structure_page.loadLibrary)], + children: [ + QRoute( + path: addEditStructure, + builder: () => add_edit_structure_page.AddEditStructurePage(), + middleware: [ + DeferredLoadingMiddleware(add_edit_structure_page.loadLibrary), + ], + ), + ], + ), + QRoute( + path: associationMemberships, + builder: () => association_membership_page.AssociationMembershipsPage(), + middleware: [ + DeferredLoadingMiddleware(association_membership_page.loadLibrary), + ], + children: [ + QRoute( + path: detailAssociationMembership, + builder: () => + association_membership_detail_page.AssociationMembershipEditorPage(), + middleware: [ + DeferredLoadingMiddleware( + association_membership_detail_page.loadLibrary, + ), + ], + children: [ + QRoute( + path: addEditMember, + builder: () => + add_edit_user_membership_page.AddEditUserMembershipPage(), + middleware: [ + DeferredLoadingMiddleware( + add_edit_user_membership_page.loadLibrary, + ), + ], + ), + ], + ), + ], + ), + ], + ); +} diff --git a/lib/admin/tools/constants.dart b/lib/super_admin/tools/constants.dart similarity index 100% rename from lib/admin/tools/constants.dart rename to lib/super_admin/tools/constants.dart diff --git a/lib/admin/tools/function.dart b/lib/super_admin/tools/function.dart similarity index 87% rename from lib/admin/tools/function.dart rename to lib/super_admin/tools/function.dart index 511d8d21c4..8de53f1490 100644 --- a/lib/admin/tools/function.dart +++ b/lib/super_admin/tools/function.dart @@ -1,5 +1,5 @@ import 'package:flutter/widgets.dart'; -import 'package:titan/admin/tools/constants.dart'; +import 'package:titan/super_admin/tools/constants.dart'; import 'package:titan/l10n/app_localizations.dart'; String getSchoolNameFromId(String id, String name, BuildContext context) { diff --git a/lib/admin/ui/admin.dart b/lib/super_admin/ui/admin.dart similarity index 86% rename from lib/admin/ui/admin.dart rename to lib/super_admin/ui/admin.dart index 2da81a1109..283ef7724d 100644 --- a/lib/admin/ui/admin.dart +++ b/lib/super_admin/ui/admin.dart @@ -4,9 +4,9 @@ import 'package:titan/admin/router.dart'; import 'package:titan/tools/ui/widgets/top_bar.dart'; import 'package:titan/tools/constants.dart'; -class AdminTemplate extends HookConsumerWidget { +class SuperAdminTemplate extends HookConsumerWidget { final Widget child; - const AdminTemplate({super.key, required this.child}); + const SuperAdminTemplate({super.key, required this.child}); @override Widget build(BuildContext context, WidgetRef ref) { diff --git a/lib/admin/ui/components/admin_button.dart b/lib/super_admin/ui/components/admin_button.dart similarity index 88% rename from lib/admin/ui/components/admin_button.dart rename to lib/super_admin/ui/components/admin_button.dart index 307c4887f7..72352229c5 100644 --- a/lib/admin/ui/components/admin_button.dart +++ b/lib/super_admin/ui/components/admin_button.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:titan/tools/constants.dart'; -class AdminButton extends StatelessWidget { +class SuperAdminButton extends StatelessWidget { final Widget child; - const AdminButton({super.key, required this.child}); + const SuperAdminButton({super.key, required this.child}); @override Widget build(BuildContext context) { diff --git a/lib/admin/ui/components/item_card_ui.dart b/lib/super_admin/ui/components/item_card_ui.dart similarity index 100% rename from lib/admin/ui/components/item_card_ui.dart rename to lib/super_admin/ui/components/item_card_ui.dart diff --git a/lib/admin/ui/components/text_editing.dart b/lib/super_admin/ui/components/text_editing.dart similarity index 100% rename from lib/admin/ui/components/text_editing.dart rename to lib/super_admin/ui/components/text_editing.dart diff --git a/lib/admin/ui/components/user_ui.dart b/lib/super_admin/ui/components/user_ui.dart similarity index 100% rename from lib/admin/ui/components/user_ui.dart rename to lib/super_admin/ui/components/user_ui.dart diff --git a/lib/admin/ui/pages/add_edit_structure_page/add_edit_structure_page.dart b/lib/super_admin/ui/pages/add_edit_structure_page/add_edit_structure_page.dart similarity index 92% rename from lib/admin/ui/pages/add_edit_structure_page/add_edit_structure_page.dart rename to lib/super_admin/ui/pages/add_edit_structure_page/add_edit_structure_page.dart index 05808104d3..481e2832f1 100644 --- a/lib/admin/ui/pages/add_edit_structure_page/add_edit_structure_page.dart +++ b/lib/super_admin/ui/pages/add_edit_structure_page/add_edit_structure_page.dart @@ -1,14 +1,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/class/association_membership_simple.dart'; -import 'package:titan/admin/providers/association_membership_list_provider.dart'; -import 'package:titan/admin/providers/structure_manager_provider.dart'; -import 'package:titan/admin/providers/structure_provider.dart'; -import 'package:titan/admin/ui/admin.dart'; -import 'package:titan/admin/ui/components/admin_button.dart'; -import 'package:titan/admin/ui/components/text_editing.dart'; -import 'package:titan/admin/ui/pages/add_edit_structure_page/search_user.dart'; +import 'package:titan/super_admin/class/association_membership_simple.dart'; +import 'package:titan/super_admin/providers/association_membership_list_provider.dart'; +import 'package:titan/super_admin/providers/structure_manager_provider.dart'; +import 'package:titan/super_admin/providers/structure_provider.dart'; +import 'package:titan/super_admin/ui/admin.dart'; +import 'package:titan/super_admin/ui/components/admin_button.dart'; +import 'package:titan/super_admin/ui/components/text_editing.dart'; +import 'package:titan/super_admin/ui/pages/add_edit_structure_page/search_user.dart'; import 'package:titan/paiement/class/structure.dart'; import 'package:titan/paiement/providers/structure_list_provider.dart'; import 'package:titan/tools/constants.dart'; @@ -49,7 +49,7 @@ class AddEditStructurePage extends HookConsumerWidget { displayToast(context, type, msg); } - return AdminTemplate( + return SuperAdminTemplate( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 30.0), child: SingleChildScrollView( @@ -182,7 +182,7 @@ class AddEditStructurePage extends HookConsumerWidget { }); } }, - builder: (child) => AdminButton(child: child), + builder: (child) => SuperAdminButton(child: child), child: Text( isEdit ? AppLocalizations.of(context)!.adminEdit diff --git a/lib/admin/ui/pages/add_edit_structure_page/results.dart b/lib/super_admin/ui/pages/add_edit_structure_page/results.dart similarity index 96% rename from lib/admin/ui/pages/add_edit_structure_page/results.dart rename to lib/super_admin/ui/pages/add_edit_structure_page/results.dart index 351f91eea7..386aa74eb7 100644 --- a/lib/admin/ui/pages/add_edit_structure_page/results.dart +++ b/lib/super_admin/ui/pages/add_edit_structure_page/results.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/providers/structure_manager_provider.dart'; +import 'package:titan/super_admin/providers/structure_manager_provider.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; diff --git a/lib/admin/ui/pages/add_edit_structure_page/search_user.dart b/lib/super_admin/ui/pages/add_edit_structure_page/search_user.dart similarity index 93% rename from lib/admin/ui/pages/add_edit_structure_page/search_user.dart rename to lib/super_admin/ui/pages/add_edit_structure_page/search_user.dart index d40c9515bd..38daf81547 100644 --- a/lib/admin/ui/pages/add_edit_structure_page/search_user.dart +++ b/lib/super_admin/ui/pages/add_edit_structure_page/search_user.dart @@ -2,8 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/providers/structure_manager_provider.dart'; -import 'package:titan/admin/ui/pages/add_edit_structure_page/results.dart'; +import 'package:titan/super_admin/providers/structure_manager_provider.dart'; +import 'package:titan/super_admin/ui/pages/add_edit_structure_page/results.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/widgets/styled_search_bar.dart'; import 'package:titan/user/class/simple_users.dart'; diff --git a/lib/admin/ui/pages/edit_module_visibility/edit_module_visibility.dart b/lib/super_admin/ui/pages/edit_module_visibility/edit_module_visibility.dart similarity index 83% rename from lib/admin/ui/pages/edit_module_visibility/edit_module_visibility.dart rename to lib/super_admin/ui/pages/edit_module_visibility/edit_module_visibility.dart index f9c0b0ac5f..a671bfc3ee 100644 --- a/lib/admin/ui/pages/edit_module_visibility/edit_module_visibility.dart +++ b/lib/super_admin/ui/pages/edit_module_visibility/edit_module_visibility.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/providers/all_account_types_list_provider.dart'; -import 'package:titan/admin/providers/all_groups_list_provider.dart'; -import 'package:titan/admin/providers/module_visibility_list_provider.dart'; -import 'package:titan/admin/ui/admin.dart'; -import 'package:titan/admin/ui/pages/edit_module_visibility/modules_expansion_panel.dart'; +import 'package:titan/super_admin/providers/all_account_types_list_provider.dart'; +import 'package:titan/super_admin/providers/all_groups_list_provider.dart'; +import 'package:titan/super_admin/providers/module_visibility_list_provider.dart'; +import 'package:titan/super_admin/ui/admin.dart'; +import 'package:titan/super_admin/ui/pages/edit_module_visibility/modules_expansion_panel.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/widgets/loader.dart'; @@ -18,7 +18,7 @@ class EditModulesVisibilityPage extends HookConsumerWidget { final modulesProvider = ref.watch(moduleVisibilityListProvider); final groups = ref.watch(allGroupList); final accountTypes = ref.watch(allAccountTypes); - return AdminTemplate( + return SuperAdminTemplate( child: Container( margin: const EdgeInsets.symmetric(horizontal: 20), child: SingleChildScrollView( diff --git a/lib/admin/ui/pages/edit_module_visibility/modules_expansion_panel.dart b/lib/super_admin/ui/pages/edit_module_visibility/modules_expansion_panel.dart similarity index 95% rename from lib/admin/ui/pages/edit_module_visibility/modules_expansion_panel.dart rename to lib/super_admin/ui/pages/edit_module_visibility/modules_expansion_panel.dart index 09e70358e4..479682828d 100644 --- a/lib/admin/ui/pages/edit_module_visibility/modules_expansion_panel.dart +++ b/lib/super_admin/ui/pages/edit_module_visibility/modules_expansion_panel.dart @@ -1,12 +1,12 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/class/account_type.dart'; -import 'package:titan/admin/class/module_visibility.dart'; -import 'package:titan/admin/providers/all_account_types_list_provider.dart'; -import 'package:titan/admin/providers/all_groups_list_provider.dart'; -import 'package:titan/admin/providers/is_expanded_list_provider.dart'; -import 'package:titan/admin/providers/module_visibility_list_provider.dart'; +import 'package:titan/super_admin/class/account_type.dart'; +import 'package:titan/super_admin/class/module_visibility.dart'; +import 'package:titan/super_admin/providers/all_account_types_list_provider.dart'; +import 'package:titan/super_admin/providers/all_groups_list_provider.dart'; +import 'package:titan/super_admin/providers/is_expanded_list_provider.dart'; +import 'package:titan/super_admin/providers/module_visibility_list_provider.dart'; import 'package:titan/l10n/app_localizations.dart'; class ModulesExpansionPanel extends HookConsumerWidget { diff --git a/lib/admin/ui/pages/groups/add_group_page/add_group_page.dart b/lib/super_admin/ui/pages/groups/add_group_page/add_group_page.dart similarity index 88% rename from lib/admin/ui/pages/groups/add_group_page/add_group_page.dart rename to lib/super_admin/ui/pages/groups/add_group_page/add_group_page.dart index 700d8ce409..ad27e07be3 100644 --- a/lib/admin/ui/pages/groups/add_group_page/add_group_page.dart +++ b/lib/super_admin/ui/pages/groups/add_group_page/add_group_page.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/class/simple_group.dart'; -import 'package:titan/admin/providers/group_list_provider.dart'; -import 'package:titan/admin/ui/admin.dart'; -import 'package:titan/admin/ui/components/admin_button.dart'; -import 'package:titan/admin/ui/components/text_editing.dart'; +import 'package:titan/super_admin/class/simple_group.dart'; +import 'package:titan/super_admin/providers/group_list_provider.dart'; +import 'package:titan/super_admin/ui/admin.dart'; +import 'package:titan/super_admin/ui/components/admin_button.dart'; +import 'package:titan/super_admin/ui/components/text_editing.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/widgets/align_left_text.dart'; @@ -26,7 +26,7 @@ class AddGroupPage extends HookConsumerWidget { displayToast(context, type, msg); } - return AdminTemplate( + return SuperAdminTemplate( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 30.0), child: SingleChildScrollView( @@ -71,7 +71,7 @@ class AddGroupPage extends HookConsumerWidget { } }); }, - builder: (child) => AdminButton(child: child), + builder: (child) => SuperAdminButton(child: child), child: Text( AppLocalizations.of(context)!.adminAdd, style: TextStyle( diff --git a/lib/admin/ui/pages/groups/add_loaner_page/add_loaner_page.dart b/lib/super_admin/ui/pages/groups/add_loaner_page/add_loaner_page.dart similarity index 97% rename from lib/admin/ui/pages/groups/add_loaner_page/add_loaner_page.dart rename to lib/super_admin/ui/pages/groups/add_loaner_page/add_loaner_page.dart index 364b129c5b..5ab333769b 100644 --- a/lib/admin/ui/pages/groups/add_loaner_page/add_loaner_page.dart +++ b/lib/super_admin/ui/pages/groups/add_loaner_page/add_loaner_page.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/providers/group_list_provider.dart'; -import 'package:titan/admin/ui/admin.dart'; +import 'package:titan/super_admin/providers/group_list_provider.dart'; +import 'package:titan/super_admin/ui/admin.dart'; import 'package:titan/loan/class/loaner.dart'; import 'package:titan/loan/providers/all_loaner_list_provider.dart'; import 'package:titan/loan/providers/loaner_list_provider.dart'; @@ -26,7 +26,7 @@ class AddLoanerPage extends HookConsumerWidget { displayToast(context, type, msg); } - return AdminTemplate( + return SuperAdminTemplate( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 30.0), child: SingleChildScrollView( diff --git a/lib/admin/ui/pages/groups/edit_group_page/edit_group_page.dart b/lib/super_admin/ui/pages/groups/edit_group_page/edit_group_page.dart similarity index 90% rename from lib/admin/ui/pages/groups/edit_group_page/edit_group_page.dart rename to lib/super_admin/ui/pages/groups/edit_group_page/edit_group_page.dart index e1812f0dc0..45c7279680 100644 --- a/lib/admin/ui/pages/groups/edit_group_page/edit_group_page.dart +++ b/lib/super_admin/ui/pages/groups/edit_group_page/edit_group_page.dart @@ -2,14 +2,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/class/group.dart'; -import 'package:titan/admin/providers/group_id_provider.dart'; -import 'package:titan/admin/providers/group_list_provider.dart'; -import 'package:titan/admin/providers/group_provider.dart'; -import 'package:titan/admin/providers/simple_groups_groups_provider.dart'; -import 'package:titan/admin/ui/admin.dart'; -import 'package:titan/admin/ui/components/admin_button.dart'; -import 'package:titan/admin/ui/pages/groups/edit_group_page/search_user.dart'; +import 'package:titan/super_admin/class/group.dart'; +import 'package:titan/super_admin/providers/group_id_provider.dart'; +import 'package:titan/super_admin/providers/group_list_provider.dart'; +import 'package:titan/super_admin/providers/group_provider.dart'; +import 'package:titan/super_admin/providers/simple_groups_groups_provider.dart'; +import 'package:titan/super_admin/ui/admin.dart'; +import 'package:titan/super_admin/ui/components/admin_button.dart'; +import 'package:titan/super_admin/ui/pages/groups/edit_group_page/search_user.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; @@ -42,7 +42,7 @@ class EditGroupPage extends HookConsumerWidget { displayToast(context, type, msg); } - return AdminTemplate( + return SuperAdminTemplate( child: SingleChildScrollView( physics: const BouncingScrollPhysics(), child: Padding( @@ -129,7 +129,7 @@ class EditGroupPage extends HookConsumerWidget { } }); }, - builder: (child) => AdminButton(child: child), + builder: (child) => SuperAdminButton(child: child), child: Text( AppLocalizations.of(context)!.adminEdit, style: const TextStyle( diff --git a/lib/admin/ui/pages/groups/edit_group_page/results.dart b/lib/super_admin/ui/pages/groups/edit_group_page/results.dart similarity index 95% rename from lib/admin/ui/pages/groups/edit_group_page/results.dart rename to lib/super_admin/ui/pages/groups/edit_group_page/results.dart index 8afeba63d6..a0d7185eba 100644 --- a/lib/admin/ui/pages/groups/edit_group_page/results.dart +++ b/lib/super_admin/ui/pages/groups/edit_group_page/results.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/class/group.dart'; -import 'package:titan/admin/providers/group_provider.dart'; -import 'package:titan/admin/providers/simple_groups_groups_provider.dart'; +import 'package:titan/super_admin/class/group.dart'; +import 'package:titan/super_admin/providers/group_provider.dart'; +import 'package:titan/super_admin/providers/simple_groups_groups_provider.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; diff --git a/lib/admin/ui/pages/groups/edit_group_page/search_user.dart b/lib/super_admin/ui/pages/groups/edit_group_page/search_user.dart similarity index 93% rename from lib/admin/ui/pages/groups/edit_group_page/search_user.dart rename to lib/super_admin/ui/pages/groups/edit_group_page/search_user.dart index f03120f218..9f0260f896 100644 --- a/lib/admin/ui/pages/groups/edit_group_page/search_user.dart +++ b/lib/super_admin/ui/pages/groups/edit_group_page/search_user.dart @@ -2,12 +2,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/class/group.dart'; -import 'package:titan/admin/providers/group_id_provider.dart'; -import 'package:titan/admin/providers/group_provider.dart'; -import 'package:titan/admin/providers/simple_groups_groups_provider.dart'; -import 'package:titan/admin/ui/pages/groups/edit_group_page/results.dart'; -import 'package:titan/admin/ui/components/user_ui.dart'; +import 'package:titan/super_admin/class/group.dart'; +import 'package:titan/super_admin/providers/group_id_provider.dart'; +import 'package:titan/super_admin/providers/group_provider.dart'; +import 'package:titan/super_admin/providers/simple_groups_groups_provider.dart'; +import 'package:titan/super_admin/ui/pages/groups/edit_group_page/results.dart'; +import 'package:titan/super_admin/ui/components/user_ui.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; diff --git a/lib/admin/ui/pages/groups/group_page/group_button.dart b/lib/super_admin/ui/pages/groups/group_page/group_button.dart similarity index 100% rename from lib/admin/ui/pages/groups/group_page/group_button.dart rename to lib/super_admin/ui/pages/groups/group_page/group_button.dart diff --git a/lib/admin/ui/pages/groups/group_page/group_page.dart b/lib/super_admin/ui/pages/groups/group_page/group_page.dart similarity index 88% rename from lib/admin/ui/pages/groups/group_page/group_page.dart rename to lib/super_admin/ui/pages/groups/group_page/group_page.dart index 18ed7f3c14..5cc294c52b 100644 --- a/lib/admin/ui/pages/groups/group_page/group_page.dart +++ b/lib/super_admin/ui/pages/groups/group_page/group_page.dart @@ -1,12 +1,12 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/providers/group_id_provider.dart'; -import 'package:titan/admin/providers/group_list_provider.dart'; -import 'package:titan/admin/router.dart'; -import 'package:titan/admin/ui/admin.dart'; -import 'package:titan/admin/ui/components/item_card_ui.dart'; -import 'package:titan/admin/ui/pages/groups/group_page/group_ui.dart'; +import 'package:titan/super_admin/providers/group_id_provider.dart'; +import 'package:titan/super_admin/providers/group_list_provider.dart'; +import 'package:titan/super_admin/router.dart'; +import 'package:titan/super_admin/ui/admin.dart'; +import 'package:titan/super_admin/ui/components/item_card_ui.dart'; +import 'package:titan/super_admin/ui/pages/groups/group_page/group_ui.dart'; import 'package:titan/loan/providers/loaner_list_provider.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; @@ -38,7 +38,7 @@ class GroupsPage extends HookConsumerWidget { orElse: () => [], ); - return AdminTemplate( + return SuperAdminTemplate( child: Refresher( onRefresh: () async { await groupsNotifier.loadGroups(); @@ -75,9 +75,9 @@ class GroupsPage extends HookConsumerWidget { GestureDetector( onTap: () { QR.to( - AdminRouter.root + - AdminRouter.groups + - AdminRouter.addGroup, + SuperAdminRouter.root + + SuperAdminRouter.groups + + SuperAdminRouter.addGroup, ); }, child: ItemCardUi( @@ -95,9 +95,9 @@ class GroupsPage extends HookConsumerWidget { GestureDetector( onTap: () { QR.to( - AdminRouter.root + - AdminRouter.groups + - AdminRouter.addLoaner, + SuperAdminRouter.root + + SuperAdminRouter.groups + + SuperAdminRouter.addLoaner, ); }, child: ItemCardUi( @@ -133,9 +133,9 @@ class GroupsPage extends HookConsumerWidget { onEdit: () { groupIdNotifier.setId(group.id); QR.to( - AdminRouter.root + - AdminRouter.groups + - AdminRouter.editGroup, + SuperAdminRouter.root + + SuperAdminRouter.groups + + SuperAdminRouter.editGroup, ); }, onDelete: () async { diff --git a/lib/admin/ui/pages/groups/group_page/group_ui.dart b/lib/super_admin/ui/pages/groups/group_page/group_ui.dart similarity index 90% rename from lib/admin/ui/pages/groups/group_page/group_ui.dart rename to lib/super_admin/ui/pages/groups/group_page/group_ui.dart index faad00dce6..b97a691a72 100644 --- a/lib/admin/ui/pages/groups/group_page/group_ui.dart +++ b/lib/super_admin/ui/pages/groups/group_page/group_ui.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/class/simple_group.dart'; -import 'package:titan/admin/ui/components/item_card_ui.dart'; -import 'package:titan/admin/ui/pages/groups/group_page/group_button.dart'; +import 'package:titan/super_admin/class/simple_group.dart'; +import 'package:titan/super_admin/ui/components/item_card_ui.dart'; +import 'package:titan/super_admin/ui/pages/groups/group_page/group_button.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; diff --git a/lib/super_admin/ui/pages/main_page/main_page.dart b/lib/super_admin/ui/pages/main_page/main_page.dart new file mode 100644 index 0000000000..eb81a9de07 --- /dev/null +++ b/lib/super_admin/ui/pages/main_page/main_page.dart @@ -0,0 +1,80 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:heroicons/heroicons.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/super_admin/router.dart'; +import 'package:titan/super_admin/ui/admin.dart'; +import 'package:titan/super_admin/ui/pages/main_page/menu_card_ui.dart'; +import 'package:titan/user/providers/user_list_provider.dart'; +import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; + +class SuperAdminMainPage extends HookConsumerWidget { + const SuperAdminMainPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + ref.watch(userList); + + final controller = ScrollController(); + + return SuperAdminTemplate( + child: Padding( + padding: const EdgeInsets.all(40), + child: GridView( + controller: controller, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + mainAxisSpacing: 20, + crossAxisSpacing: 20, + childAspectRatio: + MediaQuery.of(context).size.width < + MediaQuery.of(context).size.height + ? 0.75 + : 1.5, + ), + children: [ + GestureDetector( + onTap: () { + QR.to( + SuperAdminRouter.root + SuperAdminRouter.editModuleVisibility, + ); + }, + child: MenuCardUi( + text: AppLocalizations.of(context)!.adminVisibilities, + icon: HeroIcons.eye, + ), + ), + GestureDetector( + onTap: () { + QR.to(SuperAdminRouter.root + SuperAdminRouter.groups); + }, + child: MenuCardUi( + text: AppLocalizations.of(context)!.adminGroups, + icon: HeroIcons.users, + ), + ), + GestureDetector( + onTap: () { + QR.to(SuperAdminRouter.root + SuperAdminRouter.schools); + }, + child: MenuCardUi( + text: AppLocalizations.of(context)!.adminSchools, + icon: HeroIcons.academicCap, + ), + ), + GestureDetector( + onTap: () { + QR.to(SuperAdminRouter.root + SuperAdminRouter.structures); + }, + child: MenuCardUi( + text: AppLocalizations.of(context)!.adminMyEclPay, + icon: HeroIcons.creditCard, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/admin/ui/pages/main_page/menu_card_ui.dart b/lib/super_admin/ui/pages/main_page/menu_card_ui.dart similarity index 100% rename from lib/admin/ui/pages/main_page/menu_card_ui.dart rename to lib/super_admin/ui/pages/main_page/menu_card_ui.dart diff --git a/lib/admin/ui/pages/memberships/add_edit_user_membership_page/add_edit_user_membership_page.dart b/lib/super_admin/ui/pages/memberships/add_edit_user_membership_page/add_edit_user_membership_page.dart similarity index 94% rename from lib/admin/ui/pages/memberships/add_edit_user_membership_page/add_edit_user_membership_page.dart rename to lib/super_admin/ui/pages/memberships/add_edit_user_membership_page/add_edit_user_membership_page.dart index 5eb93b4c60..f43e526ef1 100644 --- a/lib/admin/ui/pages/memberships/add_edit_user_membership_page/add_edit_user_membership_page.dart +++ b/lib/super_admin/ui/pages/memberships/add_edit_user_membership_page/add_edit_user_membership_page.dart @@ -1,12 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/class/user_association_membership.dart'; -import 'package:titan/admin/class/user_association_membership_base.dart'; -import 'package:titan/admin/providers/association_membership_members_list_provider.dart'; -import 'package:titan/admin/providers/user_association_membership_provider.dart'; -import 'package:titan/admin/ui/admin.dart'; -import 'package:titan/admin/ui/pages/memberships/add_edit_user_membership_page/search_result.dart'; +import 'package:titan/super_admin/class/user_association_membership.dart'; +import 'package:titan/super_admin/class/user_association_membership_base.dart'; +import 'package:titan/super_admin/providers/association_membership_members_list_provider.dart'; +import 'package:titan/super_admin/providers/user_association_membership_provider.dart'; +import 'package:titan/super_admin/ui/admin.dart'; +import 'package:titan/super_admin/ui/pages/memberships/add_edit_user_membership_page/search_result.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; @@ -42,7 +42,7 @@ class AddEditUserMembershipPage extends HookConsumerWidget { displayToast(context, type, msg); } - return AdminTemplate( + return SuperAdminTemplate( child: Padding( padding: const EdgeInsets.all(30.0), child: SingleChildScrollView( diff --git a/lib/admin/ui/pages/memberships/add_edit_user_membership_page/search_result.dart b/lib/super_admin/ui/pages/memberships/add_edit_user_membership_page/search_result.dart similarity index 96% rename from lib/admin/ui/pages/memberships/add_edit_user_membership_page/search_result.dart rename to lib/super_admin/ui/pages/memberships/add_edit_user_membership_page/search_result.dart index e9d0994a6b..10fe6dfe26 100644 --- a/lib/admin/ui/pages/memberships/add_edit_user_membership_page/search_result.dart +++ b/lib/super_admin/ui/pages/memberships/add_edit_user_membership_page/search_result.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/providers/user_association_membership_provider.dart'; +import 'package:titan/super_admin/providers/user_association_membership_provider.dart'; import 'package:titan/user/providers/user_list_provider.dart'; class SearchResult extends HookConsumerWidget { diff --git a/lib/admin/ui/pages/memberships/association_membership_detail_page/association_membership_detail_page.dart b/lib/super_admin/ui/pages/memberships/association_membership_detail_page/association_membership_detail_page.dart similarity index 79% rename from lib/admin/ui/pages/memberships/association_membership_detail_page/association_membership_detail_page.dart rename to lib/super_admin/ui/pages/memberships/association_membership_detail_page/association_membership_detail_page.dart index de9d716c63..1a1b66c0ec 100644 --- a/lib/admin/ui/pages/memberships/association_membership_detail_page/association_membership_detail_page.dart +++ b/lib/super_admin/ui/pages/memberships/association_membership_detail_page/association_membership_detail_page.dart @@ -1,17 +1,17 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/class/user_association_membership.dart'; -import 'package:titan/admin/providers/association_membership_filtered_members_provider.dart'; -import 'package:titan/admin/providers/association_membership_members_list_provider.dart'; -import 'package:titan/admin/providers/association_membership_provider.dart'; -import 'package:titan/admin/providers/user_association_membership_provider.dart'; -import 'package:titan/admin/router.dart'; -import 'package:titan/admin/ui/admin.dart'; -import 'package:titan/admin/ui/pages/memberships/association_membership_detail_page/association_membership_information_editor.dart'; -import 'package:titan/admin/ui/pages/memberships/association_membership_detail_page/association_membership_member_editable_card.dart'; -import 'package:titan/admin/ui/pages/memberships/association_membership_detail_page/research_bar.dart'; -import 'package:titan/admin/ui/pages/memberships/association_membership_detail_page/search_filters.dart'; +import 'package:titan/super_admin/class/user_association_membership.dart'; +import 'package:titan/super_admin/providers/association_membership_filtered_members_provider.dart'; +import 'package:titan/super_admin/providers/association_membership_members_list_provider.dart'; +import 'package:titan/super_admin/providers/association_membership_provider.dart'; +import 'package:titan/super_admin/providers/user_association_membership_provider.dart'; +import 'package:titan/super_admin/router.dart'; +import 'package:titan/super_admin/ui/admin.dart'; +import 'package:titan/super_admin/ui/pages/memberships/association_membership_detail_page/association_membership_information_editor.dart'; +import 'package:titan/super_admin/ui/pages/memberships/association_membership_detail_page/association_membership_member_editable_card.dart'; +import 'package:titan/super_admin/ui/pages/memberships/association_membership_detail_page/research_bar.dart'; +import 'package:titan/super_admin/ui/pages/memberships/association_membership_detail_page/search_filters.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; @@ -35,7 +35,7 @@ class AssociationMembershipEditorPage extends HookConsumerWidget { associationMembershipFilteredListProvider, ); - return AdminTemplate( + return SuperAdminTemplate( child: Refresher( onRefresh: () async { await associationMembershipMemberListNotifier @@ -97,10 +97,10 @@ class AssociationMembershipEditorPage extends HookConsumerWidget { ), ); QR.to( - AdminRouter.root + - AdminRouter.associationMemberships + - AdminRouter.detailAssociationMembership + - AdminRouter.addEditMember, + SuperAdminRouter.root + + SuperAdminRouter.associationMemberships + + SuperAdminRouter.detailAssociationMembership + + SuperAdminRouter.addEditMember, ); }, child: const HeroIcon( diff --git a/lib/admin/ui/pages/memberships/association_membership_detail_page/association_membership_information_editor.dart b/lib/super_admin/ui/pages/memberships/association_membership_detail_page/association_membership_information_editor.dart similarity index 96% rename from lib/admin/ui/pages/memberships/association_membership_detail_page/association_membership_information_editor.dart rename to lib/super_admin/ui/pages/memberships/association_membership_detail_page/association_membership_information_editor.dart index 03ae463783..2b6c3cbea7 100644 --- a/lib/admin/ui/pages/memberships/association_membership_detail_page/association_membership_information_editor.dart +++ b/lib/super_admin/ui/pages/memberships/association_membership_detail_page/association_membership_information_editor.dart @@ -2,9 +2,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/providers/all_groups_list_provider.dart'; -import 'package:titan/admin/providers/association_membership_list_provider.dart'; -import 'package:titan/admin/providers/association_membership_provider.dart'; +import 'package:titan/super_admin/providers/all_groups_list_provider.dart'; +import 'package:titan/super_admin/providers/association_membership_list_provider.dart'; +import 'package:titan/super_admin/providers/association_membership_provider.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; diff --git a/lib/admin/ui/pages/memberships/association_membership_detail_page/association_membership_member_editable_card.dart b/lib/super_admin/ui/pages/memberships/association_membership_detail_page/association_membership_member_editable_card.dart similarity index 87% rename from lib/admin/ui/pages/memberships/association_membership_detail_page/association_membership_member_editable_card.dart rename to lib/super_admin/ui/pages/memberships/association_membership_detail_page/association_membership_member_editable_card.dart index 7693ee0e75..6be6073204 100644 --- a/lib/admin/ui/pages/memberships/association_membership_detail_page/association_membership_member_editable_card.dart +++ b/lib/super_admin/ui/pages/memberships/association_membership_detail_page/association_membership_member_editable_card.dart @@ -1,10 +1,10 @@ import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/class/user_association_membership.dart'; -import 'package:titan/admin/providers/association_membership_members_list_provider.dart'; -import 'package:titan/admin/providers/user_association_membership_provider.dart'; -import 'package:titan/admin/router.dart'; +import 'package:titan/super_admin/class/user_association_membership.dart'; +import 'package:titan/super_admin/providers/association_membership_members_list_provider.dart'; +import 'package:titan/super_admin/providers/user_association_membership_provider.dart'; +import 'package:titan/super_admin/router.dart'; import 'package:titan/phonebook/ui/pages/admin_page/delete_button.dart'; import 'package:titan/phonebook/ui/pages/admin_page/edition_button.dart'; import 'package:titan/tools/functions.dart'; @@ -76,10 +76,10 @@ class MemberEditableCard extends HookConsumerWidget { associationMembership, ); QR.to( - AdminRouter.root + - AdminRouter.associationMemberships + - AdminRouter.detailAssociationMembership + - AdminRouter.addEditMember, + SuperAdminRouter.root + + SuperAdminRouter.associationMemberships + + SuperAdminRouter.detailAssociationMembership + + SuperAdminRouter.addEditMember, ); }, ), diff --git a/lib/admin/ui/pages/memberships/association_membership_detail_page/research_bar.dart b/lib/super_admin/ui/pages/memberships/association_membership_detail_page/research_bar.dart similarity index 94% rename from lib/admin/ui/pages/memberships/association_membership_detail_page/research_bar.dart rename to lib/super_admin/ui/pages/memberships/association_membership_detail_page/research_bar.dart index 84b1f5accf..991bfe6234 100644 --- a/lib/admin/ui/pages/memberships/association_membership_detail_page/research_bar.dart +++ b/lib/super_admin/ui/pages/memberships/association_membership_detail_page/research_bar.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/providers/research_filter_provider.dart'; +import 'package:titan/super_admin/providers/research_filter_provider.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/l10n/app_localizations.dart'; diff --git a/lib/admin/ui/pages/memberships/association_membership_detail_page/search_filters.dart b/lib/super_admin/ui/pages/memberships/association_membership_detail_page/search_filters.dart similarity index 97% rename from lib/admin/ui/pages/memberships/association_membership_detail_page/search_filters.dart rename to lib/super_admin/ui/pages/memberships/association_membership_detail_page/search_filters.dart index 3fbea81199..7ca8d6c671 100644 --- a/lib/admin/ui/pages/memberships/association_membership_detail_page/search_filters.dart +++ b/lib/super_admin/ui/pages/memberships/association_membership_detail_page/search_filters.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/providers/association_membership_members_list_provider.dart'; -import 'package:titan/admin/providers/association_membership_provider.dart'; +import 'package:titan/super_admin/providers/association_membership_members_list_provider.dart'; +import 'package:titan/super_admin/providers/association_membership_provider.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; diff --git a/lib/admin/ui/pages/memberships/association_membership_page/association_membership_button.dart b/lib/super_admin/ui/pages/memberships/association_membership_page/association_membership_button.dart similarity index 100% rename from lib/admin/ui/pages/memberships/association_membership_page/association_membership_button.dart rename to lib/super_admin/ui/pages/memberships/association_membership_page/association_membership_button.dart diff --git a/lib/admin/ui/pages/memberships/association_membership_page/association_membership_creation_dialog.dart b/lib/super_admin/ui/pages/memberships/association_membership_page/association_membership_creation_dialog.dart similarity index 98% rename from lib/admin/ui/pages/memberships/association_membership_page/association_membership_creation_dialog.dart rename to lib/super_admin/ui/pages/memberships/association_membership_page/association_membership_creation_dialog.dart index 39d485c27d..3509ff9716 100644 --- a/lib/admin/ui/pages/memberships/association_membership_page/association_membership_creation_dialog.dart +++ b/lib/super_admin/ui/pages/memberships/association_membership_page/association_membership_creation_dialog.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:titan/admin/class/simple_group.dart'; +import 'package:titan/super_admin/class/simple_group.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/l10n/app_localizations.dart'; diff --git a/lib/admin/ui/pages/memberships/association_membership_page/association_membership_page.dart b/lib/super_admin/ui/pages/memberships/association_membership_page/association_membership_page.dart similarity index 89% rename from lib/admin/ui/pages/memberships/association_membership_page/association_membership_page.dart rename to lib/super_admin/ui/pages/memberships/association_membership_page/association_membership_page.dart index f6c59ca0c7..2a2c407767 100644 --- a/lib/admin/ui/pages/memberships/association_membership_page/association_membership_page.dart +++ b/lib/super_admin/ui/pages/memberships/association_membership_page/association_membership_page.dart @@ -1,16 +1,16 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/class/association_membership_simple.dart'; -import 'package:titan/admin/providers/all_groups_list_provider.dart'; -import 'package:titan/admin/providers/association_membership_list_provider.dart'; -import 'package:titan/admin/providers/association_membership_members_list_provider.dart'; -import 'package:titan/admin/providers/association_membership_provider.dart'; -import 'package:titan/admin/router.dart'; -import 'package:titan/admin/ui/admin.dart'; -import 'package:titan/admin/ui/components/item_card_ui.dart'; -import 'package:titan/admin/ui/pages/memberships/association_membership_page/association_membership_creation_dialog.dart'; -import 'package:titan/admin/ui/pages/memberships/association_membership_page/association_membership_ui.dart'; +import 'package:titan/super_admin/class/association_membership_simple.dart'; +import 'package:titan/super_admin/providers/all_groups_list_provider.dart'; +import 'package:titan/super_admin/providers/association_membership_list_provider.dart'; +import 'package:titan/super_admin/providers/association_membership_members_list_provider.dart'; +import 'package:titan/super_admin/providers/association_membership_provider.dart'; +import 'package:titan/super_admin/router.dart'; +import 'package:titan/super_admin/ui/admin.dart'; +import 'package:titan/super_admin/ui/components/item_card_ui.dart'; +import 'package:titan/super_admin/ui/pages/memberships/association_membership_page/association_membership_creation_dialog.dart'; +import 'package:titan/super_admin/ui/pages/memberships/association_membership_page/association_membership_ui.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; @@ -45,7 +45,7 @@ class AssociationMembershipsPage extends HookConsumerWidget { displayToast(context, type, msg); } - return AdminTemplate( + return SuperAdminTemplate( child: Refresher( onRefresh: () async { await associationMembershipsNotifier.loadAssociationMemberships(); @@ -151,9 +151,10 @@ class AssociationMembershipsPage extends HookConsumerWidget { associationMembership, ); QR.to( - AdminRouter.root + - AdminRouter.associationMemberships + - AdminRouter.detailAssociationMembership, + SuperAdminRouter.root + + SuperAdminRouter.associationMemberships + + SuperAdminRouter + .detailAssociationMembership, ); }, onDelete: () async { diff --git a/lib/admin/ui/pages/memberships/association_membership_page/association_membership_ui.dart b/lib/super_admin/ui/pages/memberships/association_membership_page/association_membership_ui.dart similarity index 87% rename from lib/admin/ui/pages/memberships/association_membership_page/association_membership_ui.dart rename to lib/super_admin/ui/pages/memberships/association_membership_page/association_membership_ui.dart index 694a576747..c7f8214d90 100644 --- a/lib/admin/ui/pages/memberships/association_membership_page/association_membership_ui.dart +++ b/lib/super_admin/ui/pages/memberships/association_membership_page/association_membership_ui.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/class/association_membership_simple.dart'; -import 'package:titan/admin/ui/components/item_card_ui.dart'; -import 'package:titan/admin/ui/pages/memberships/association_membership_page/association_membership_button.dart'; +import 'package:titan/super_admin/class/association_membership_simple.dart'; +import 'package:titan/super_admin/ui/components/item_card_ui.dart'; +import 'package:titan/super_admin/ui/pages/memberships/association_membership_page/association_membership_button.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; diff --git a/lib/admin/ui/pages/schools/add_school_page/add_school_page.dart b/lib/super_admin/ui/pages/schools/add_school_page/add_school_page.dart similarity index 88% rename from lib/admin/ui/pages/schools/add_school_page/add_school_page.dart rename to lib/super_admin/ui/pages/schools/add_school_page/add_school_page.dart index e8b8c38e6e..367fb6a3f1 100644 --- a/lib/admin/ui/pages/schools/add_school_page/add_school_page.dart +++ b/lib/super_admin/ui/pages/schools/add_school_page/add_school_page.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/class/school.dart'; -import 'package:titan/admin/providers/school_list_provider.dart'; -import 'package:titan/admin/ui/admin.dart'; -import 'package:titan/admin/ui/components/admin_button.dart'; -import 'package:titan/admin/ui/components/text_editing.dart'; +import 'package:titan/super_admin/class/school.dart'; +import 'package:titan/super_admin/providers/school_list_provider.dart'; +import 'package:titan/super_admin/ui/admin.dart'; +import 'package:titan/super_admin/ui/components/admin_button.dart'; +import 'package:titan/super_admin/ui/components/text_editing.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/widgets/align_left_text.dart'; @@ -26,7 +26,7 @@ class AddSchoolPage extends HookConsumerWidget { displayToast(context, type, msg); } - return AdminTemplate( + return SuperAdminTemplate( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 30.0), child: SingleChildScrollView( @@ -71,7 +71,7 @@ class AddSchoolPage extends HookConsumerWidget { } }); }, - builder: (child) => AdminButton(child: child), + builder: (child) => SuperAdminButton(child: child), child: Text( AppLocalizations.of(context)!.adminAdd, style: const TextStyle( diff --git a/lib/admin/ui/pages/schools/edit_school_page/edit_school_page.dart b/lib/super_admin/ui/pages/schools/edit_school_page/edit_school_page.dart similarity index 90% rename from lib/admin/ui/pages/schools/edit_school_page/edit_school_page.dart rename to lib/super_admin/ui/pages/schools/edit_school_page/edit_school_page.dart index 46d8e7ce6d..78688ab925 100644 --- a/lib/admin/ui/pages/schools/edit_school_page/edit_school_page.dart +++ b/lib/super_admin/ui/pages/schools/edit_school_page/edit_school_page.dart @@ -2,12 +2,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/class/school.dart'; -import 'package:titan/admin/providers/school_list_provider.dart'; -import 'package:titan/admin/providers/school_provider.dart'; -import 'package:titan/admin/tools/function.dart'; -import 'package:titan/admin/ui/admin.dart'; -import 'package:titan/admin/ui/components/admin_button.dart'; +import 'package:titan/super_admin/class/school.dart'; +import 'package:titan/super_admin/providers/school_list_provider.dart'; +import 'package:titan/super_admin/providers/school_provider.dart'; +import 'package:titan/super_admin/tools/function.dart'; +import 'package:titan/super_admin/ui/admin.dart'; +import 'package:titan/super_admin/ui/components/admin_button.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; @@ -36,7 +36,7 @@ class EditSchoolPage extends HookConsumerWidget { name.text = getSchoolNameFromId(school.id, school.name, context); emailRegex.text = school.emailRegex; - return AdminTemplate( + return SuperAdminTemplate( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 30.0), child: Column( @@ -105,7 +105,7 @@ class EditSchoolPage extends HookConsumerWidget { } }); }, - builder: (child) => AdminButton(child: child), + builder: (child) => SuperAdminButton(child: child), child: Text( AppLocalizations.of(context)!.adminEdit, style: const TextStyle( diff --git a/lib/admin/ui/pages/schools/school_page/school_button.dart b/lib/super_admin/ui/pages/schools/school_page/school_button.dart similarity index 100% rename from lib/admin/ui/pages/schools/school_page/school_button.dart rename to lib/super_admin/ui/pages/schools/school_page/school_button.dart diff --git a/lib/admin/ui/pages/schools/school_page/school_page.dart b/lib/super_admin/ui/pages/schools/school_page/school_page.dart similarity index 87% rename from lib/admin/ui/pages/schools/school_page/school_page.dart rename to lib/super_admin/ui/pages/schools/school_page/school_page.dart index 68c393b83c..df954b8823 100644 --- a/lib/admin/ui/pages/schools/school_page/school_page.dart +++ b/lib/super_admin/ui/pages/schools/school_page/school_page.dart @@ -1,12 +1,12 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/providers/school_list_provider.dart'; -import 'package:titan/admin/providers/school_provider.dart'; -import 'package:titan/admin/router.dart'; -import 'package:titan/admin/ui/admin.dart'; -import 'package:titan/admin/ui/components/item_card_ui.dart'; -import 'package:titan/admin/ui/pages/schools/school_page/school_ui.dart'; +import 'package:titan/super_admin/providers/school_list_provider.dart'; +import 'package:titan/super_admin/providers/school_provider.dart'; +import 'package:titan/super_admin/router.dart'; +import 'package:titan/super_admin/ui/admin.dart'; +import 'package:titan/super_admin/ui/components/item_card_ui.dart'; +import 'package:titan/super_admin/ui/pages/schools/school_page/school_ui.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; @@ -30,7 +30,7 @@ class SchoolsPage extends HookConsumerWidget { displayToast(context, type, msg); } - return AdminTemplate( + return SuperAdminTemplate( child: Refresher( onRefresh: () async { await schoolsNotifier.loadSchools(); @@ -66,9 +66,9 @@ class SchoolsPage extends HookConsumerWidget { GestureDetector( onTap: () { QR.to( - AdminRouter.root + - AdminRouter.schools + - AdminRouter.addSchool, + SuperAdminRouter.root + + SuperAdminRouter.schools + + SuperAdminRouter.addSchool, ); }, child: ItemCardUi( @@ -89,9 +89,9 @@ class SchoolsPage extends HookConsumerWidget { onEdit: () { schoolNotifier.setSchool(school); QR.to( - AdminRouter.root + - AdminRouter.schools + - AdminRouter.editSchool, + SuperAdminRouter.root + + SuperAdminRouter.schools + + SuperAdminRouter.editSchool, ); }, onDelete: () async { diff --git a/lib/admin/ui/pages/schools/school_page/school_ui.dart b/lib/super_admin/ui/pages/schools/school_page/school_ui.dart similarity index 85% rename from lib/admin/ui/pages/schools/school_page/school_ui.dart rename to lib/super_admin/ui/pages/schools/school_page/school_ui.dart index 669472ae17..7c3058f278 100644 --- a/lib/admin/ui/pages/schools/school_page/school_ui.dart +++ b/lib/super_admin/ui/pages/schools/school_page/school_ui.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/class/school.dart'; -import 'package:titan/admin/tools/constants.dart'; -import 'package:titan/admin/tools/function.dart'; -import 'package:titan/admin/ui/components/item_card_ui.dart'; -import 'package:titan/admin/ui/pages/schools/school_page/school_button.dart'; +import 'package:titan/super_admin/class/school.dart'; +import 'package:titan/super_admin/tools/constants.dart'; +import 'package:titan/super_admin/tools/function.dart'; +import 'package:titan/super_admin/ui/components/item_card_ui.dart'; +import 'package:titan/super_admin/ui/pages/schools/school_page/school_button.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; diff --git a/lib/admin/ui/pages/structure_page/structure_button.dart b/lib/super_admin/ui/pages/structure_page/structure_button.dart similarity index 100% rename from lib/admin/ui/pages/structure_page/structure_button.dart rename to lib/super_admin/ui/pages/structure_page/structure_button.dart diff --git a/lib/admin/ui/pages/structure_page/structure_page.dart b/lib/super_admin/ui/pages/structure_page/structure_page.dart similarity index 89% rename from lib/admin/ui/pages/structure_page/structure_page.dart rename to lib/super_admin/ui/pages/structure_page/structure_page.dart index e5610d278a..0b66e9bce2 100644 --- a/lib/admin/ui/pages/structure_page/structure_page.dart +++ b/lib/super_admin/ui/pages/structure_page/structure_page.dart @@ -1,12 +1,12 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/providers/structure_manager_provider.dart'; -import 'package:titan/admin/providers/structure_provider.dart'; -import 'package:titan/admin/router.dart'; -import 'package:titan/admin/ui/admin.dart'; -import 'package:titan/admin/ui/components/item_card_ui.dart'; -import 'package:titan/admin/ui/pages/structure_page/structure_ui.dart'; +import 'package:titan/super_admin/providers/structure_manager_provider.dart'; +import 'package:titan/super_admin/providers/structure_provider.dart'; +import 'package:titan/super_admin/router.dart'; +import 'package:titan/super_admin/ui/admin.dart'; +import 'package:titan/super_admin/ui/components/item_card_ui.dart'; +import 'package:titan/super_admin/ui/pages/structure_page/structure_ui.dart'; import 'package:titan/paiement/class/structure.dart'; import 'package:titan/paiement/providers/structure_list_provider.dart'; import 'package:titan/tools/constants.dart'; @@ -37,7 +37,7 @@ class StructurePage extends HookConsumerWidget { displayToast(context, type, msg); } - return AdminTemplate( + return SuperAdminTemplate( child: Refresher( onRefresh: () async { await structuresNotifier.getStructures(); @@ -77,9 +77,9 @@ class StructurePage extends HookConsumerWidget { SimpleUser.empty(), ); QR.to( - AdminRouter.root + - AdminRouter.structures + - AdminRouter.addEditStructure, + SuperAdminRouter.root + + SuperAdminRouter.structures + + SuperAdminRouter.addEditStructure, ); }, child: ItemCardUi( @@ -103,9 +103,9 @@ class StructurePage extends HookConsumerWidget { structure.managerUser, ); QR.to( - AdminRouter.root + - AdminRouter.structures + - AdminRouter.addEditStructure, + SuperAdminRouter.root + + SuperAdminRouter.structures + + SuperAdminRouter.addEditStructure, ); }, onDelete: () async { diff --git a/lib/admin/ui/pages/structure_page/structure_ui.dart b/lib/super_admin/ui/pages/structure_page/structure_ui.dart similarity index 92% rename from lib/admin/ui/pages/structure_page/structure_ui.dart rename to lib/super_admin/ui/pages/structure_page/structure_ui.dart index cd75918502..bd8f78664a 100644 --- a/lib/admin/ui/pages/structure_page/structure_ui.dart +++ b/lib/super_admin/ui/pages/structure_page/structure_ui.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/ui/components/item_card_ui.dart'; -import 'package:titan/admin/ui/pages/structure_page/structure_button.dart'; +import 'package:titan/super_admin/ui/components/item_card_ui.dart'; +import 'package:titan/super_admin/ui/pages/structure_page/structure_button.dart'; import 'package:titan/paiement/class/structure.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; diff --git a/lib/tools/middlewares/admin_middleware.dart b/lib/tools/middlewares/admin_middleware.dart index ee7262c440..fc67c06f23 100644 --- a/lib/tools/middlewares/admin_middleware.dart +++ b/lib/tools/middlewares/admin_middleware.dart @@ -2,14 +2,14 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/router.dart'; import 'package:qlevar_router/qlevar_router.dart'; -class AdminMiddleware extends QMiddleware { - final StateProvider isAdminProvider; +class SuperAdminMiddleware extends QMiddleware { + final StateProvider isSuperAdminProvider; final Ref ref; - AdminMiddleware(this.ref, this.isAdminProvider); + SuperAdminMiddleware(this.ref, this.isSuperAdminProvider); @override Future redirectGuard(String path) async { - return ref.watch(isAdminProvider) ? null : AppRouter.root; + return ref.watch(isSuperAdminProvider) ? null : AppRouter.root; } } diff --git a/lib/tools/ui/styleguide/styleguide_page.dart b/lib/tools/ui/styleguide/styleguide_page.dart index fb5bddc753..55e5270212 100644 --- a/lib/tools/ui/styleguide/styleguide_page.dart +++ b/lib/tools/ui/styleguide/styleguide_page.dart @@ -863,10 +863,10 @@ class StyleGuidePage extends HookConsumerWidget { height: 70, child: HorizontalMultiSelect>( items: const [ - {"name": "John", "role": "Admin"}, + {"name": "John", "role": "SuperAdmin"}, {"name": "Emma", "role": "Editor"}, {"name": "Michael", "role": "Viewer"}, - {"name": "Sarah", "role": "Admin"}, + {"name": "Sarah", "role": "SuperAdmin"}, {"name": "David", "role": "Editor"}, ], itemBuilder: (context, user, index, selected) { diff --git a/lib/tools/ui/widgets/admin_button.dart b/lib/tools/ui/widgets/admin_button.dart index 6db064ab3b..f735310f21 100644 --- a/lib/tools/ui/widgets/admin_button.dart +++ b/lib/tools/ui/widgets/admin_button.dart @@ -1,18 +1,18 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; -class AdminButton extends StatelessWidget { +class SuperAdminButton extends StatelessWidget { final VoidCallback onTap; final Color textColor; final Color? color; final List? colors; final String text; - const AdminButton({ + const SuperAdminButton({ super.key, required this.onTap, this.textColor = Colors.white, this.color = Colors.black, - this.text = "Admin", + this.text = "SuperAdmin", this.colors, }); diff --git a/lib/user/class/applicant.dart b/lib/user/class/applicant.dart index 01a12da170..4dfc31be24 100644 --- a/lib/user/class/applicant.dart +++ b/lib/user/class/applicant.dart @@ -1,4 +1,4 @@ -import 'package:titan/admin/class/account_type.dart'; +import 'package:titan/super_admin/class/account_type.dart'; import 'package:titan/user/class/simple_users.dart'; class Applicant extends SimpleUser { diff --git a/lib/user/class/simple_users.dart b/lib/user/class/simple_users.dart index 453adde035..0f7866abc2 100644 --- a/lib/user/class/simple_users.dart +++ b/lib/user/class/simple_users.dart @@ -1,4 +1,4 @@ -import 'package:titan/admin/class/account_type.dart'; +import 'package:titan/super_admin/class/account_type.dart'; import 'package:titan/tools/functions.dart'; class SimpleUser { diff --git a/lib/user/class/user.dart b/lib/user/class/user.dart index 6f297ce3e4..7e51705b5a 100644 --- a/lib/user/class/user.dart +++ b/lib/user/class/user.dart @@ -1,5 +1,5 @@ -import 'package:titan/admin/class/account_type.dart'; -import 'package:titan/admin/class/simple_group.dart'; +import 'package:titan/super_admin/class/account_type.dart'; +import 'package:titan/super_admin/class/simple_group.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/user/class/applicant.dart'; import 'package:titan/user/class/simple_users.dart'; diff --git a/lib/user/providers/user_list_provider.dart b/lib/user/providers/user_list_provider.dart index c24905f91a..443133bf0a 100644 --- a/lib/user/providers/user_list_provider.dart +++ b/lib/user/providers/user_list_provider.dart @@ -1,5 +1,5 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/admin/class/simple_group.dart'; +import 'package:titan/super_admin/class/simple_group.dart'; import 'package:titan/tools/providers/list_notifier.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/user/class/simple_users.dart'; diff --git a/lib/vote/class/members.dart b/lib/vote/class/members.dart index b3a7245037..666e4de08a 100644 --- a/lib/vote/class/members.dart +++ b/lib/vote/class/members.dart @@ -1,4 +1,4 @@ -import 'package:titan/admin/class/account_type.dart'; +import 'package:titan/super_admin/class/account_type.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/user/class/simple_users.dart'; diff --git a/lib/vote/providers/is_vote_admin_provider.dart b/lib/vote/providers/is_vote_admin_provider.dart index f7873020f9..ce3bd950f5 100644 --- a/lib/vote/providers/is_vote_admin_provider.dart +++ b/lib/vote/providers/is_vote_admin_provider.dart @@ -1,7 +1,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/user/providers/user_provider.dart'; -final isVoteAdminProvider = StateProvider((ref) { +final isVoteSuperAdminProvider = StateProvider((ref) { final me = ref.watch(userProvider); return me.groups .map((e) => e.id) diff --git a/lib/vote/providers/voting_group_list_provider.dart b/lib/vote/providers/voting_group_list_provider.dart index e79fb11de7..be2179fcc7 100644 --- a/lib/vote/providers/voting_group_list_provider.dart +++ b/lib/vote/providers/voting_group_list_provider.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/admin/class/simple_group.dart'; -import 'package:titan/admin/providers/group_list_provider.dart'; +import 'package:titan/super_admin/class/simple_group.dart'; +import 'package:titan/super_admin/providers/group_list_provider.dart'; import 'package:titan/vote/providers/voter_list_provider.dart'; final votingGroupListProvider = Provider>((ref) { diff --git a/lib/vote/router.dart b/lib/vote/router.dart index ece95a48b8..43b5347865 100644 --- a/lib/vote/router.dart +++ b/lib/vote/router.dart @@ -47,9 +47,9 @@ class VoteRouter { children: [ QRoute( path: admin, - builder: () => admin_page.AdminPage(), + builder: () => admin_page.SuperAdminPage(), middleware: [ - AdminMiddleware(ref, isVoteAdminProvider), + SuperAdminMiddleware(ref, isVoteSuperAdminProvider), DeferredLoadingMiddleware(admin_page.loadLibrary), ], children: [ diff --git a/lib/vote/ui/components/member_card.dart b/lib/vote/ui/components/member_card.dart index 0a9f7924fc..3e6f38b151 100644 --- a/lib/vote/ui/components/member_card.dart +++ b/lib/vote/ui/components/member_card.dart @@ -8,13 +8,13 @@ import 'package:titan/vote/class/members.dart'; class MemberCard extends StatelessWidget { final Member member; final Function()? onEdit, onDelete; - final bool isAdmin; + final bool isSuperAdmin; const MemberCard({ super.key, required this.member, this.onEdit, this.onDelete, - this.isAdmin = false, + this.isSuperAdmin = false, }); @override @@ -22,7 +22,7 @@ class MemberCard extends StatelessWidget { return CardLayout( id: member.id, width: 150, - height: isAdmin ? 145 : 110, + height: isSuperAdmin ? 145 : 110, margin: const EdgeInsets.all(10), padding: const EdgeInsets.symmetric(horizontal: 17.0), child: Column( @@ -54,7 +54,7 @@ class MemberCard extends StatelessWidget { ), ), const SizedBox(height: 2), - if (!isAdmin) const Spacer(), + if (!isSuperAdmin) const Spacer(), AutoSizeText( member.role, maxLines: 1, @@ -66,8 +66,8 @@ class MemberCard extends StatelessWidget { color: Colors.black, ), ), - if (isAdmin) const Spacer(), - if (isAdmin) + if (isSuperAdmin) const Spacer(), + if (isSuperAdmin) Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -91,7 +91,7 @@ class MemberCard extends StatelessWidget { ), ], ), - SizedBox(height: isAdmin ? 10 : 15), + SizedBox(height: isSuperAdmin ? 10 : 15), ], ), ); diff --git a/lib/vote/ui/pages/admin_page/admin_button.dart b/lib/vote/ui/pages/admin_page/admin_button.dart index 80af1215d2..d02cf58b1b 100644 --- a/lib/vote/ui/pages/admin_page/admin_button.dart +++ b/lib/vote/ui/pages/admin_page/admin_button.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; -class AdminButton extends StatelessWidget { +class SuperAdminButton extends StatelessWidget { final Widget child; - const AdminButton({super.key, required this.child}); + const SuperAdminButton({super.key, required this.child}); @override Widget build(BuildContext context) { diff --git a/lib/vote/ui/pages/admin_page/admin_page.dart b/lib/vote/ui/pages/admin_page/admin_page.dart index bd5ea02dac..393d0b63dc 100644 --- a/lib/vote/ui/pages/admin_page/admin_page.dart +++ b/lib/vote/ui/pages/admin_page/admin_page.dart @@ -28,8 +28,8 @@ import 'package:titan/vote/ui/pages/admin_page/voters_bar.dart'; import 'package:titan/vote/ui/vote.dart'; import 'package:titan/l10n/app_localizations.dart'; -class AdminPage extends HookConsumerWidget { - const AdminPage({super.key}); +class SuperAdminPage extends HookConsumerWidget { + const SuperAdminPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -132,7 +132,8 @@ class AdminPage extends HookConsumerWidget { ), ), WaitingButton( - builder: (child) => AdminButton(child: child), + builder: (child) => + SuperAdminButton(child: child), onTap: () async { await showDialog( context: context, @@ -167,7 +168,7 @@ class AdminPage extends HookConsumerWidget { if (status == Status.counting || status == Status.published) WaitingButton( - builder: (child) => AdminButton(child: child), + builder: (child) => SuperAdminButton(child: child), onTap: () async { await showDialog( context: context, diff --git a/lib/vote/ui/pages/admin_page/contender_card.dart b/lib/vote/ui/pages/admin_page/contender_card.dart index 4cf55d1e8e..48cba8da18 100644 --- a/lib/vote/ui/pages/admin_page/contender_card.dart +++ b/lib/vote/ui/pages/admin_page/contender_card.dart @@ -16,7 +16,7 @@ import 'package:qlevar_router/qlevar_router.dart'; class ContenderCard extends HookConsumerWidget { final Contender contender; - final bool isAdmin, isDetail; + final bool isSuperAdmin, isDetail; final Function()? onEdit; final Future Function()? onDelete; const ContenderCard({ @@ -24,7 +24,7 @@ class ContenderCard extends HookConsumerWidget { required this.contender, this.onEdit, this.onDelete, - this.isAdmin = false, + this.isSuperAdmin = false, this.isDetail = false, }); @@ -40,7 +40,7 @@ class ContenderCard extends HookConsumerWidget { height: (contender.listType != ListType.blank && status == Status.waiting && - isAdmin) + isSuperAdmin) ? 180 : 130, padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 15), @@ -109,7 +109,7 @@ class ContenderCard extends HookConsumerWidget { const Spacer(), if (contender.listType != ListType.blank && status == Status.waiting && - isAdmin) + isSuperAdmin) Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ diff --git a/lib/vote/ui/pages/admin_page/section_bar.dart b/lib/vote/ui/pages/admin_page/section_bar.dart index 11e1514f86..fa65f3863f 100644 --- a/lib/vote/ui/pages/admin_page/section_bar.dart +++ b/lib/vote/ui/pages/admin_page/section_bar.dart @@ -51,7 +51,7 @@ class SectionBar extends HookConsumerWidget { itemBuilder: (context, key, i) => SectionChip( label: key.name, selected: section.id == key.id, - isAdmin: status == Status.waiting, + isSuperAdmin: status == Status.waiting, onTap: () async { tokenExpireWrapper(ref, () async { sectionIdNotifier.setId(key.id); diff --git a/lib/vote/ui/pages/admin_page/section_chip.dart b/lib/vote/ui/pages/admin_page/section_chip.dart index bb2798e522..34f1872f27 100644 --- a/lib/vote/ui/pages/admin_page/section_chip.dart +++ b/lib/vote/ui/pages/admin_page/section_chip.dart @@ -1,13 +1,13 @@ import 'package:flutter/material.dart'; class SectionChip extends StatelessWidget { - final bool selected, isAdmin; + final bool selected, isSuperAdmin; final String label; final Function()? onTap, onDelete; const SectionChip({ super.key, required this.label, - this.isAdmin = false, + this.isSuperAdmin = false, this.selected = false, this.onTap, this.onDelete, @@ -34,7 +34,7 @@ class SectionChip extends StatelessWidget { fontSize: 18.0, ), ), - if (isAdmin && selected) + if (isSuperAdmin && selected) Container( margin: const EdgeInsets.only(left: 10.0), child: GestureDetector( diff --git a/lib/vote/ui/pages/admin_page/section_contender_items.dart b/lib/vote/ui/pages/admin_page/section_contender_items.dart index 46961b0eaa..cffa1d2a96 100644 --- a/lib/vote/ui/pages/admin_page/section_contender_items.dart +++ b/lib/vote/ui/pages/admin_page/section_contender_items.dart @@ -73,7 +73,7 @@ class SectionContenderItems extends HookConsumerWidget { items: data, itemBuilder: (context, e, i) => ContenderCard( contender: e, - isAdmin: true, + isSuperAdmin: true, onEdit: () { tokenExpireWrapper(ref, () async { contenderNotifier.setId(e); diff --git a/lib/vote/ui/pages/admin_page/voters_bar.dart b/lib/vote/ui/pages/admin_page/voters_bar.dart index 81dac4a033..a6ca4093b6 100644 --- a/lib/vote/ui/pages/admin_page/voters_bar.dart +++ b/lib/vote/ui/pages/admin_page/voters_bar.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/providers/group_list_provider.dart'; +import 'package:titan/super_admin/providers/group_list_provider.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/vote/class/voter.dart'; import 'package:titan/vote/providers/status_provider.dart'; diff --git a/lib/vote/ui/pages/contender_pages/add_edit_contender.dart b/lib/vote/ui/pages/contender_pages/add_edit_contender.dart index ce267a2ee3..ffc52fe054 100644 --- a/lib/vote/ui/pages/contender_pages/add_edit_contender.dart +++ b/lib/vote/ui/pages/contender_pages/add_edit_contender.dart @@ -189,7 +189,7 @@ class AddEditContenderPage extends HookConsumerWidget { items: members, itemBuilder: (context, e, i) => MemberCard( member: e, - isAdmin: true, + isSuperAdmin: true, onDelete: () async { membersNotifier.removeMember(e); }, diff --git a/lib/vote/ui/pages/main_page/main_page.dart b/lib/vote/ui/pages/main_page/main_page.dart index 488eeb081f..17a6947c21 100644 --- a/lib/vote/ui/pages/main_page/main_page.dart +++ b/lib/vote/ui/pages/main_page/main_page.dart @@ -30,7 +30,7 @@ class VoteMainPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final statusNotifier = ref.watch(statusProvider.notifier); - final isAdmin = ref.watch(isVoteAdminProvider); + final isSuperAdmin = ref.watch(isVoteSuperAdminProvider); final sections = ref.watch(sectionsProvider); final sectionsNotifier = ref.watch(sectionsProvider.notifier); final contenders = ref.watch(contenderListProvider); @@ -62,13 +62,13 @@ class VoteMainPage extends HookConsumerWidget { padding: const EdgeInsets.symmetric(horizontal: 30.0), child: Column( children: [ - if (isAdmin) + if (isSuperAdmin) Row( children: [ const Spacer(), Container( margin: const EdgeInsets.only(right: 20), - child: AdminButton( + child: SuperAdminButton( onTap: () { QR.to(VoteRouter.root + VoteRouter.admin); }, @@ -134,7 +134,7 @@ class VoteMainPage extends HookConsumerWidget { padding: const EdgeInsets.only(left: 30.0), child: Column( children: [ - SizedBox(height: isAdmin ? 10 : 15), + SizedBox(height: isSuperAdmin ? 10 : 15), AsyncChild( value: sections, builder: (context, sectionList) => Column( @@ -143,10 +143,10 @@ class VoteMainPage extends HookConsumerWidget { height: MediaQuery.of(context).size.height - (s == Status.open - ? isAdmin + ? isSuperAdmin ? 215 : 220 - : isAdmin + : isSuperAdmin ? 150 : 155), child: Row( @@ -166,12 +166,12 @@ class VoteMainPage extends HookConsumerWidget { MainAxisAlignment.spaceBetween, children: [ SectionTitle(sectionList: sectionList), - if (isAdmin) + if (isSuperAdmin) Container( margin: const EdgeInsets.only( right: 20, ), - child: AdminButton( + child: SuperAdminButton( onTap: () { QR.to( VoteRouter.root + diff --git a/test/admin/admin_test.dart b/test/admin/admin_test.dart index 3bc68a9b20..9ebcd978de 100644 --- a/test/admin/admin_test.dart +++ b/test/admin/admin_test.dart @@ -1,10 +1,10 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; -import 'package:titan/admin/class/account_type.dart'; -import 'package:titan/admin/class/group.dart'; -import 'package:titan/admin/class/simple_group.dart'; -import 'package:titan/admin/repositories/group_repository.dart'; +import 'package:titan/super_admin/class/account_type.dart'; +import 'package:titan/super_admin/class/group.dart'; +import 'package:titan/super_admin/class/simple_group.dart'; +import 'package:titan/super_admin/repositories/group_repository.dart'; import 'package:titan/user/class/simple_users.dart'; import 'package:titan/user/class/user.dart'; diff --git a/test/admin/group_id_provider_test.dart b/test/admin/group_id_provider_test.dart index 6055f403e2..d99205faa6 100644 --- a/test/admin/group_id_provider_test.dart +++ b/test/admin/group_id_provider_test.dart @@ -1,6 +1,6 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/providers/group_id_provider.dart'; +import 'package:titan/super_admin/providers/group_id_provider.dart'; void main() { group('GroupIdNotifier', () { diff --git a/test/admin/group_list_provider_test.dart b/test/admin/group_list_provider_test.dart index b352ecc733..1b1c80d46e 100644 --- a/test/admin/group_list_provider_test.dart +++ b/test/admin/group_list_provider_test.dart @@ -1,10 +1,10 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; -import 'package:titan/admin/class/account_type.dart'; -import 'package:titan/admin/class/simple_group.dart'; -import 'package:titan/admin/providers/group_list_provider.dart'; -import 'package:titan/admin/repositories/group_repository.dart'; +import 'package:titan/super_admin/class/account_type.dart'; +import 'package:titan/super_admin/class/simple_group.dart'; +import 'package:titan/super_admin/providers/group_list_provider.dart'; +import 'package:titan/super_admin/repositories/group_repository.dart'; import 'package:titan/user/class/user.dart'; class MockGroupRepository extends Mock implements GroupRepository {} diff --git a/test/admin/group_logo_provider_test.dart b/test/admin/group_logo_provider_test.dart index b3aa595508..2a8aa8ce77 100644 --- a/test/admin/group_logo_provider_test.dart +++ b/test/admin/group_logo_provider_test.dart @@ -3,8 +3,8 @@ import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; -import 'package:titan/admin/providers/group_logo_provider.dart'; -import 'package:titan/admin/repositories/group_logo_repository.dart'; +import 'package:titan/super_admin/providers/group_logo_provider.dart'; +import 'package:titan/super_admin/repositories/group_logo_repository.dart'; class MockGroupLogoRepository extends Mock implements GroupLogoRepository {} diff --git a/test/admin/group_provider_test.dart b/test/admin/group_provider_test.dart index a373824ec0..7d8d21d684 100644 --- a/test/admin/group_provider_test.dart +++ b/test/admin/group_provider_test.dart @@ -1,9 +1,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; -import 'package:titan/admin/class/group.dart'; -import 'package:titan/admin/providers/group_provider.dart'; -import 'package:titan/admin/repositories/group_repository.dart'; +import 'package:titan/super_admin/class/group.dart'; +import 'package:titan/super_admin/providers/group_provider.dart'; +import 'package:titan/super_admin/repositories/group_repository.dart'; import 'package:titan/user/class/simple_users.dart'; class MockGroupRepository extends Mock implements GroupRepository {} diff --git a/test/admin/is_admin_test.dart b/test/admin/is_admin_test.dart index 8d580e6a2e..2ff3fc3597 100644 --- a/test/admin/is_admin_test.dart +++ b/test/admin/is_admin_test.dart @@ -1,12 +1,12 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/class/simple_group.dart'; -import 'package:titan/admin/providers/is_admin_provider.dart'; +import 'package:titan/super_admin/class/simple_group.dart'; +import 'package:titan/super_admin/providers/is_admin_provider.dart'; import 'package:titan/user/class/user.dart'; import 'package:titan/user/providers/user_provider.dart'; void main() { - group('isAdminProvider', () { + group('isSuperAdminProvider', () { test('returns true if user is admin', () { final container = ProviderContainer( overrides: [ @@ -15,7 +15,7 @@ void main() { groups: [ SimpleGroup.empty().copyWith( id: '0a25cb76-4b63-4fd3-b939-da6d9feabf28', - name: 'Admin', + name: 'SuperAdmin', ), SimpleGroup.empty().copyWith(id: '123', name: 'User'), ], @@ -24,9 +24,9 @@ void main() { ], ); - final isAdmin = container.read(isAdminProvider); + final isSuperAdmin = container.read(isSuperAdminProvider); - expect(isAdmin, true); + expect(isSuperAdmin, true); }); test('returns false if user is not admin', () { @@ -40,9 +40,9 @@ void main() { ], ); - final isAdmin = container.read(isAdminProvider); + final isSuperAdmin = container.read(isSuperAdminProvider); - expect(isAdmin, false); + expect(isSuperAdmin, false); }); }); } diff --git a/test/admin/members_provider_test.dart b/test/admin/members_provider_test.dart index a3215c0ef8..8283376e3b 100644 --- a/test/admin/members_provider_test.dart +++ b/test/admin/members_provider_test.dart @@ -1,5 +1,5 @@ import 'package:flutter_test/flutter_test.dart'; -import 'package:titan/admin/providers/members_provider.dart'; +import 'package:titan/super_admin/providers/members_provider.dart'; import 'package:titan/user/class/simple_users.dart'; void main() { diff --git a/test/amap/is_amap_admin_provider_test.dart b/test/amap/is_amap_admin_provider_test.dart index 8ff091f076..ead09c83d1 100644 --- a/test/amap/is_amap_admin_provider_test.dart +++ b/test/amap/is_amap_admin_provider_test.dart @@ -1,12 +1,12 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:titan/admin/class/simple_group.dart'; +import 'package:titan/super_admin/class/simple_group.dart'; import 'package:titan/amap/providers/is_amap_admin_provider.dart'; import 'package:titan/user/class/user.dart'; import 'package:titan/user/providers/user_provider.dart'; void main() { - group('isAmapAdmin', () { + group('isAmapSuperAdmin', () { test('should return true if user is an Amap admin', () { final container = ProviderContainer( overrides: [ @@ -15,7 +15,7 @@ void main() { groups: [ SimpleGroup.empty().copyWith( id: '70db65ee-d533-4f6b-9ffa-a4d70a17b7ef', - name: 'Amap Admin', + name: 'Amap SuperAdmin', ), SimpleGroup.empty().copyWith(id: '123', name: 'Some Group'), ], @@ -24,9 +24,9 @@ void main() { ], ); - final isAmapAdminState = container.read(isAmapAdminProvider); + final isAmapSuperAdminState = container.read(isAmapSuperAdminProvider); - expect(isAmapAdminState, true); + expect(isAmapSuperAdminState, true); }); test('should return false if user is not an Amap admin', () { @@ -42,9 +42,9 @@ void main() { ], ); - final isAmapAdminState = container.read(isAmapAdminProvider); + final isAmapSuperAdminState = container.read(isAmapSuperAdminProvider); - expect(isAmapAdminState, false); + expect(isAmapSuperAdminState, false); }); }); } diff --git a/test/booking/is_booking_admin_provider_test.dart b/test/booking/is_booking_admin_provider_test.dart index 90906b759f..d3e102c2dc 100644 --- a/test/booking/is_booking_admin_provider_test.dart +++ b/test/booking/is_booking_admin_provider_test.dart @@ -1,12 +1,12 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:titan/admin/class/simple_group.dart'; +import 'package:titan/super_admin/class/simple_group.dart'; import 'package:titan/booking/providers/is_admin_provider.dart'; import 'package:titan/user/class/user.dart'; import 'package:titan/user/providers/user_provider.dart'; void main() { - group('isBookingAdminProvider', () { + group('isBookingSuperAdminProvider', () { test('should return true if user is a booking admin', () { final container = ProviderContainer( overrides: [ @@ -15,7 +15,7 @@ void main() { groups: [ SimpleGroup.empty().copyWith( id: '0a25cb76-4b63-4fd3-b939-da6d9feabf28', - name: 'Booking Admin', + name: 'Booking SuperAdmin', ), SimpleGroup.empty().copyWith(id: '123', name: 'Other Group'), ], @@ -24,7 +24,7 @@ void main() { ], ); - final result = container.read(isAdminProvider); + final result = container.read(isSuperAdminProvider); expect(result, true); }); @@ -42,7 +42,7 @@ void main() { ], ); - final result = container.read(isAdminProvider); + final result = container.read(isSuperAdminProvider); expect(result, false); }); diff --git a/test/cinema/is_cinema_admin_test.dart b/test/cinema/is_cinema_admin_test.dart index dc728268c0..508771e67e 100644 --- a/test/cinema/is_cinema_admin_test.dart +++ b/test/cinema/is_cinema_admin_test.dart @@ -1,12 +1,12 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/class/simple_group.dart'; +import 'package:titan/super_admin/class/simple_group.dart'; import 'package:titan/cinema/providers/is_cinema_admin.dart'; import 'package:titan/user/class/user.dart'; import 'package:titan/user/providers/user_provider.dart'; void main() { - group('isCinemaAdmin', () { + group('isCinemaSuperAdmin', () { test('should return true if user is a cinema admin', () { final container = ProviderContainer( overrides: [ @@ -15,7 +15,7 @@ void main() { groups: [ SimpleGroup.empty().copyWith( id: 'ce5f36e6-5377-489f-9696-de70e2477300', - name: 'Cinema Admin', + name: 'Cinema SuperAdmin', ), ], ), @@ -23,9 +23,11 @@ void main() { ], ); - final isCinemaAdminState = container.read(isCinemaAdminProvider); + final isCinemaSuperAdminState = container.read( + isCinemaSuperAdminProvider, + ); - expect(isCinemaAdminState, true); + expect(isCinemaSuperAdminState, true); }); test('should return false if user is not a cinema admin', () { @@ -42,9 +44,11 @@ void main() { ], ); - final isCinemaAdminState = container.read(isCinemaAdminProvider); + final isCinemaSuperAdminState = container.read( + isCinemaSuperAdminProvider, + ); - expect(isCinemaAdminState, false); + expect(isCinemaSuperAdminState, false); }); }); } diff --git a/test/event/is_admin_provider_test.dart b/test/event/is_admin_provider_test.dart index ad6b12a565..d4ae59ad64 100644 --- a/test/event/is_admin_provider_test.dart +++ b/test/event/is_admin_provider_test.dart @@ -1,12 +1,12 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/class/simple_group.dart'; +import 'package:titan/super_admin/class/simple_group.dart'; import 'package:titan/event/providers/is_admin_provider.dart'; import 'package:titan/user/class/user.dart'; import 'package:titan/user/providers/user_provider.dart'; void main() { - group('isEventAdmin', () { + group('isEventSuperAdmin', () { test('should return true if user is event admin', () { final container = ProviderContainer( overrides: [ @@ -15,7 +15,7 @@ void main() { groups: [ SimpleGroup.empty().copyWith( id: '53a669d6-84b1-4352-8d7c-421c1fbd9c6a', - name: 'Admin', + name: 'SuperAdmin', ), SimpleGroup.empty().copyWith(id: '123', name: 'User'), ], @@ -24,9 +24,9 @@ void main() { ], ); - final isEventAdminState = container.read(isEventAdminProvider); + final isEventSuperAdminState = container.read(isEventSuperAdminProvider); - expect(isEventAdminState, true); + expect(isEventSuperAdminState, true); }); test('should return false if user is not event admin', () { @@ -40,9 +40,9 @@ void main() { ], ); - final isEventAdminState = container.read(isEventAdminProvider); + final isEventSuperAdminState = container.read(isEventSuperAdminProvider); - expect(isEventAdminState, false); + expect(isEventSuperAdminState, false); }); }); } diff --git a/test/loan/loan_test.dart b/test/loan/loan_test.dart index 8491020c54..26afdfe949 100644 --- a/test/loan/loan_test.dart +++ b/test/loan/loan_test.dart @@ -1,5 +1,5 @@ import 'package:flutter_test/flutter_test.dart'; -import 'package:titan/admin/class/account_type.dart'; +import 'package:titan/super_admin/class/account_type.dart'; import 'package:titan/loan/class/item.dart'; import 'package:titan/loan/class/item_quantity.dart'; import 'package:titan/loan/class/item_simple.dart'; diff --git a/test/login/login_test.dart b/test/login/login_test.dart index 9464b1eece..5f42df52a4 100644 --- a/test/login/login_test.dart +++ b/test/login/login_test.dart @@ -225,7 +225,7 @@ void main() { ); }); - test('Account Type to ID - Admin', () { + test('Account Type to ID - SuperAdmin', () { expect( accountTypeToID(AccountType.admin), '0a25cb76-4b63-4fd3-b939-da6d9feabf28', diff --git a/test/user/user_list_provider_test.dart b/test/user/user_list_provider_test.dart index fd09ed7c28..400404dc6f 100644 --- a/test/user/user_list_provider_test.dart +++ b/test/user/user_list_provider_test.dart @@ -1,7 +1,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; -import 'package:titan/admin/class/simple_group.dart'; +import 'package:titan/super_admin/class/simple_group.dart'; import 'package:titan/user/class/simple_users.dart'; import 'package:titan/user/providers/user_list_provider.dart'; import 'package:titan/user/repositories/user_list_repository.dart'; diff --git a/test/user/user_test.dart b/test/user/user_test.dart index 600dc30937..8c6869ed35 100644 --- a/test/user/user_test.dart +++ b/test/user/user_test.dart @@ -1,7 +1,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:mocktail/mocktail.dart'; -import 'package:titan/admin/class/account_type.dart'; +import 'package:titan/super_admin/class/account_type.dart'; import 'package:titan/user/class/applicant.dart'; import 'package:titan/user/class/simple_users.dart'; import 'package:titan/user/class/user.dart'; diff --git a/test/vote/is_vote_admin_provider_test.dart b/test/vote/is_vote_admin_provider_test.dart index e878e98bb7..91742eea94 100644 --- a/test/vote/is_vote_admin_provider_test.dart +++ b/test/vote/is_vote_admin_provider_test.dart @@ -1,12 +1,12 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/admin/class/simple_group.dart'; +import 'package:titan/super_admin/class/simple_group.dart'; import 'package:titan/user/class/user.dart'; import 'package:titan/user/providers/user_provider.dart'; import 'package:titan/vote/providers/is_vote_admin_provider.dart'; void main() { - group('isVoteAdmin', () { + group('isVoteSuperAdmin', () { test('should return true if user is a vote admin', () { final container = ProviderContainer( overrides: [ @@ -22,9 +22,9 @@ void main() { ], ); - final isVoteAdminState = container.read(isVoteAdminProvider); + final isVoteSuperAdminState = container.read(isVoteSuperAdminProvider); - expect(isVoteAdminState, true); + expect(isVoteSuperAdminState, true); }); test('should return false if user is not a vote admin', () { @@ -42,9 +42,9 @@ void main() { ], ); - final isVoteAdminState = container.read(isVoteAdminProvider); + final isVoteSuperAdminState = container.read(isVoteSuperAdminProvider); - expect(isVoteAdminState, false); + expect(isVoteSuperAdminState, false); }); }); } diff --git a/test/vote/vote_test.dart b/test/vote/vote_test.dart index 5f2547427d..1163ea212e 100644 --- a/test/vote/vote_test.dart +++ b/test/vote/vote_test.dart @@ -1,5 +1,5 @@ import 'package:flutter_test/flutter_test.dart'; -import 'package:titan/admin/class/account_type.dart'; +import 'package:titan/super_admin/class/account_type.dart'; import 'package:titan/user/class/simple_users.dart'; import 'package:titan/vote/class/members.dart'; import 'package:titan/vote/class/contender.dart'; From d559087333a8edc6b02f59099fdc4681fc069f9a Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 16 Jul 2025 19:57:52 +0200 Subject: [PATCH 121/473] euh ... --- .../providers/is_advert_admin_provider.dart | 2 +- .../pages/admin_page/admin_advert_card.dart | 4 +- .../ui/pages/admin_page/admin_page.dart | 6 +- lib/advert/ui/pages/main_page/main_page.dart | 12 +-- .../delivery_order_list_provider.dart | 10 +-- .../providers/is_amap_admin_provider.dart | 2 +- lib/amap/ui/pages/admin_page/admin_page.dart | 4 +- lib/amap/ui/pages/main_page/main_page.dart | 6 +- lib/booking/providers/is_admin_provider.dart | 2 +- lib/booking/ui/components/booking_card.dart | 22 ++--- .../admin_pages/add_edit_manager_page.dart | 8 +- .../pages/admin_pages/add_edit_room_page.dart | 8 +- .../ui/pages/admin_pages/admin_entry.dart | 4 +- .../ui/pages/admin_pages/admin_page.dart | 4 +- .../pages/admin_pages/admin_scroll_chips.dart | 4 +- .../admin_pages/admin_shrink_button.dart | 4 +- .../booking_pages/add_edit_booking_page.dart | 2 +- .../ui/pages/detail_pages/detail_booking.dart | 6 +- lib/booking/ui/pages/main_page/main_page.dart | 10 +-- .../ui/pages/manager_page/list_booking.dart | 2 +- lib/cinema/providers/is_cinema_admin.dart | 2 +- .../ui/pages/admin_page/admin_page.dart | 6 +- .../pages/admin_page/admin_session_card.dart | 4 +- lib/cinema/ui/pages/main_page/main_page.dart | 6 +- lib/event/providers/is_admin_provider.dart | 2 +- lib/event/ui/components/event_ui.dart | 14 ++-- lib/event/ui/pages/admin_page/admin_page.dart | 4 +- lib/event/ui/pages/admin_page/list_event.dart | 2 +- .../ui/pages/detail_page/detail_page.dart | 6 +- lib/event/ui/pages/main_page/main_page.dart | 6 +- lib/feed/ui/pages/admin_page/admin_page.dart | 4 +- lib/feed/ui/pages/main_page/main_page.dart | 2 +- lib/l10n/app_en.arb | 26 +++--- lib/l10n/app_fr.arb | 28 +++---- lib/l10n/app_localizations.dart | 80 +++++++++---------- lib/l10n/app_localizations_en.dart | 26 +++--- lib/l10n/app_localizations_fr.dart | 29 ++++--- .../admin_history_loan_list_provider.dart | 10 +-- .../providers/admin_loan_list_provider.dart | 9 +-- .../providers/is_loan_admin_provider.dart | 2 +- lib/loan/ui/pages/admin_page/admin_page.dart | 17 ++-- lib/loan/ui/pages/admin_page/loan_card.dart | 14 ++-- .../ui/pages/admin_page/on_going_loan.dart | 2 +- lib/loan/ui/pages/main_page/main_page.dart | 6 +- lib/paiement/class/user_store.dart | 2 +- lib/paiement/providers/is_payment_admin.dart | 2 +- .../providers/new_admin_provider.dart | 19 ++--- .../repositories/funding_repository.dart | 2 +- .../ui/pages/admin_page/admin_page.dart | 6 +- .../ui/pages/admin_page/admin_store_card.dart | 4 +- .../ui/pages/main_page/main_page.dart | 4 +- .../seller_card/store_admin_card.dart | 4 +- .../main_page/seller_card/store_card.dart | 4 +- .../main_page/seller_card/store_list.dart | 8 +- .../pages/store_admin_page/search_result.dart | 10 +-- .../store_admin_page/seller_right_card.dart | 17 ++-- .../store_admin_page/store_admin_page.dart | 4 +- lib/ph/class/ph_admin.dart | 18 ++--- lib/ph/providers/is_ph_admin_provider.dart | 2 +- lib/ph/ui/pages/admin_page/admin_page.dart | 8 +- lib/ph/ui/pages/admin_page/admin_ph_card.dart | 4 +- lib/ph/ui/pages/admin_page/admin_ph_list.dart | 6 +- lib/ph/ui/pages/main_page/main_page.dart | 6 +- .../providers/phonebook_admin_provider.dart | 10 +-- .../ui/pages/admin_page/admin_page.dart | 12 +-- .../admin_page/editable_association_card.dart | 6 +- .../association_editor_page.dart | 12 +-- .../association_information_editor.dart | 8 +- .../ui/pages/main_page/main_page.dart | 8 +- .../membership_editor_page.dart | 6 +- .../providers/purchases_admin_provider.dart | 2 +- .../ui/pages/main_page/custom_button.dart | 2 +- .../ui/pages/main_page/main_page.dart | 4 +- lib/raffle/providers/is_raffle_admin.dart | 2 +- .../admin_module_page/admin_module_page.dart | 4 +- lib/raffle/ui/pages/main_page/main_page.dart | 6 +- .../is_recommendation_admin_provider.dart | 2 +- lib/recommendation/ui/pages/main_page.dart | 6 +- .../ui/widgets/recommendation_card.dart | 6 +- .../is_seed_library_admin_provider.dart | 2 +- .../repositories/plants_repository.dart | 2 +- .../ui/pages/information_page/text.dart | 4 +- .../ui/pages/main_page/main_page.dart | 4 +- .../plant_deposit_page.dart | 8 +- .../providers/module_list_provider.dart | 13 ++- lib/tools/middlewares/admin_middleware.dart | 8 +- lib/tools/ui/styleguide/styleguide_page.dart | 4 +- lib/tools/ui/widgets/admin_button.dart | 6 +- .../providers/is_vote_admin_provider.dart | 2 +- lib/vote/ui/components/member_card.dart | 14 ++-- .../ui/pages/admin_page/admin_button.dart | 4 +- lib/vote/ui/pages/admin_page/admin_page.dart | 9 +-- .../ui/pages/admin_page/contender_card.dart | 8 +- lib/vote/ui/pages/admin_page/section_bar.dart | 2 +- .../ui/pages/admin_page/section_chip.dart | 6 +- .../admin_page/section_contender_items.dart | 2 +- .../contender_pages/add_edit_contender.dart | 2 +- lib/vote/ui/pages/main_page/main_page.dart | 16 ++-- test/admin/is_admin_test.dart | 12 +-- test/amap/is_amap_admin_provider_test.dart | 12 +-- .../is_booking_admin_provider_test.dart | 8 +- test/cinema/is_cinema_admin_test.dart | 16 ++-- test/event/is_admin_provider_test.dart | 12 +-- test/login/login_test.dart | 2 +- test/vote/is_vote_admin_provider_test.dart | 10 +-- 105 files changed, 411 insertions(+), 432 deletions(-) diff --git a/lib/advert/providers/is_advert_admin_provider.dart b/lib/advert/providers/is_advert_admin_provider.dart index bd3e1fd128..3dec527d3a 100644 --- a/lib/advert/providers/is_advert_admin_provider.dart +++ b/lib/advert/providers/is_advert_admin_provider.dart @@ -1,7 +1,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/advert/providers/announcer_list_provider.dart'; -final isAdvertSuperAdminProvider = StateProvider((ref) { +final isAdvertAdminProvider = StateProvider((ref) { final me = ref.watch(userAnnouncerListProvider); return me.maybeWhen(data: (data) => data.isNotEmpty, orElse: () => false); }); diff --git a/lib/advert/ui/pages/admin_page/admin_advert_card.dart b/lib/advert/ui/pages/admin_page/admin_advert_card.dart index 7c3b24dba5..7397504045 100644 --- a/lib/advert/ui/pages/admin_page/admin_advert_card.dart +++ b/lib/advert/ui/pages/admin_page/admin_advert_card.dart @@ -7,12 +7,12 @@ import 'package:titan/advert/ui/components/advert_card.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; import 'package:titan/tools/ui/layouts/card_button.dart'; -class SuperAdminAdvertCard extends HookConsumerWidget { +class AdminAdvertCard extends HookConsumerWidget { final VoidCallback onTap, onEdit; final Future Function() onDelete; final Advert advert; - const SuperAdminAdvertCard({ + const AdminAdvertCard({ super.key, required this.advert, required this.onTap, diff --git a/lib/advert/ui/pages/admin_page/admin_page.dart b/lib/advert/ui/pages/admin_page/admin_page.dart index b443a3393e..3a7e21f47e 100644 --- a/lib/advert/ui/pages/admin_page/admin_page.dart +++ b/lib/advert/ui/pages/admin_page/admin_page.dart @@ -19,8 +19,8 @@ import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/l10n/app_localizations.dart'; -class AdvertSuperAdminPage extends HookConsumerWidget { - const AdvertSuperAdminPage({super.key}); +class AdvertAdminPage extends HookConsumerWidget { + const AdvertAdminPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -100,7 +100,7 @@ class AdvertSuperAdminPage extends HookConsumerWidget { ), ), ...filteredSortedUserAnnouncerAdverts.map( - (advert) => SuperAdminAdvertCard( + (advert) => AdminAdvertCard( onTap: () { advertNotifier.setAdvert(advert); QR.to(AdvertRouter.root + AdvertRouter.detail); diff --git a/lib/advert/ui/pages/main_page/main_page.dart b/lib/advert/ui/pages/main_page/main_page.dart index 5b4dce50b5..9c6882684d 100644 --- a/lib/advert/ui/pages/main_page/main_page.dart +++ b/lib/advert/ui/pages/main_page/main_page.dart @@ -28,8 +28,8 @@ class AdvertMainPage extends HookConsumerWidget { final advertPostersNotifier = ref.watch(advertPostersProvider.notifier); final selected = ref.watch(announcerProvider); final selectedNotifier = ref.watch(announcerProvider.notifier); - final isSuperAdmin = ref.watch(isSuperAdminProvider); - final isAdvertSuperAdmin = ref.watch(isAdvertSuperAdminProvider); + final isAdmin = ref.watch(isAdminProvider); + final isAdvertAdmin = ref.watch(isAdvertAdminProvider); return AdvertTemplate( child: Stack( children: [ @@ -57,15 +57,15 @@ class AdvertMainPage extends HookConsumerWidget { child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ - if (isAdvertSuperAdmin) - SuperAdminButton( + if (isAdvertAdmin) + AdminButton( onTap: () { selectedNotifier.clearAnnouncer(); QR.to(AdvertRouter.root + AdvertRouter.admin); }, ), - if (isSuperAdmin) - SuperAdminButton( + if (isAdmin) + AdminButton( onTap: () { QR.to( AdvertRouter.root + diff --git a/lib/amap/providers/delivery_order_list_provider.dart b/lib/amap/providers/delivery_order_list_provider.dart index 999e3fd3ad..dc77ba5396 100644 --- a/lib/amap/providers/delivery_order_list_provider.dart +++ b/lib/amap/providers/delivery_order_list_provider.dart @@ -4,17 +4,17 @@ import 'package:titan/amap/providers/delivery_list_provider.dart'; import 'package:titan/tools/providers/map_provider.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; -class SuperAdminDeliveryOrderListNotifier extends MapNotifier { - SuperAdminDeliveryOrderListNotifier() : super(); +class AdminDeliveryOrderListNotifier extends MapNotifier { + AdminDeliveryOrderListNotifier() : super(); } final adminDeliveryOrderListProvider = StateNotifierProvider< - SuperAdminDeliveryOrderListNotifier, + AdminDeliveryOrderListNotifier, Map>?> >((ref) { - SuperAdminDeliveryOrderListNotifier orderListNotifier = - SuperAdminDeliveryOrderListNotifier(); + AdminDeliveryOrderListNotifier orderListNotifier = + AdminDeliveryOrderListNotifier(); tokenExpireWrapperAuth(ref, () async { final deliveries = ref.watch(deliveryList); orderListNotifier.loadTList(deliveries.map((e) => e.id).toList()); diff --git a/lib/amap/providers/is_amap_admin_provider.dart b/lib/amap/providers/is_amap_admin_provider.dart index eea62cb83a..ecc2ab30e8 100644 --- a/lib/amap/providers/is_amap_admin_provider.dart +++ b/lib/amap/providers/is_amap_admin_provider.dart @@ -1,7 +1,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/user/providers/user_provider.dart'; -final isAmapSuperAdminProvider = StateProvider((ref) { +final isAmapAdminProvider = StateProvider((ref) { final me = ref.watch(userProvider); return me.groups .map((e) => e.id) diff --git a/lib/amap/ui/pages/admin_page/admin_page.dart b/lib/amap/ui/pages/admin_page/admin_page.dart index c9c11698f4..328c3e652d 100644 --- a/lib/amap/ui/pages/admin_page/admin_page.dart +++ b/lib/amap/ui/pages/admin_page/admin_page.dart @@ -9,8 +9,8 @@ import 'package:titan/amap/ui/pages/admin_page/delivery_handler.dart'; import 'package:titan/amap/ui/pages/admin_page/product_handler.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; -class SuperAdminPage extends HookConsumerWidget { - const SuperAdminPage({super.key}); +class AdminPage extends HookConsumerWidget { + const AdminPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { diff --git a/lib/amap/ui/pages/main_page/main_page.dart b/lib/amap/ui/pages/main_page/main_page.dart index edf0b443a8..91eb61c468 100644 --- a/lib/amap/ui/pages/main_page/main_page.dart +++ b/lib/amap/ui/pages/main_page/main_page.dart @@ -35,7 +35,7 @@ class AmapMainPage extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final order = ref.watch(orderProvider); final orderNotifier = ref.read(orderProvider.notifier); - final isSuperAdmin = ref.watch(isAmapSuperAdminProvider); + final isAdmin = ref.watch(isAmapAdminProvider); final delivery = ref.watch(deliveryProvider); final deliveriesNotifier = ref.read(deliveryListProvider.notifier); final ordersNotifier = ref.read(userOrderListProvider.notifier); @@ -94,8 +94,8 @@ class AmapMainPage extends HookConsumerWidget { loaderColor: AMAPColorConstants.greenGradient1, ), ), - if (isSuperAdmin) - SuperAdminButton( + if (isAdmin) + AdminButton( onTap: () { QR.to(AmapRouter.root + AmapRouter.admin); }, diff --git a/lib/booking/providers/is_admin_provider.dart b/lib/booking/providers/is_admin_provider.dart index afcc5c9f65..4cd58e778d 100644 --- a/lib/booking/providers/is_admin_provider.dart +++ b/lib/booking/providers/is_admin_provider.dart @@ -1,7 +1,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/user/providers/user_provider.dart'; -final isSuperAdminProvider = StateProvider((ref) { +final isAdminProvider = StateProvider((ref) { final me = ref.watch(userProvider); return me.groups .map((e) => e.id) diff --git a/lib/booking/ui/components/booking_card.dart b/lib/booking/ui/components/booking_card.dart index f02df7091a..468bc18c9a 100644 --- a/lib/booking/ui/components/booking_card.dart +++ b/lib/booking/ui/components/booking_card.dart @@ -13,7 +13,7 @@ class BookingCard extends StatelessWidget { final Booking booking; final Function()? onEdit, onConfirm, onDecline, onCopy, onInfo; final Future Function()? onDelete; - final bool isSuperAdmin, isDetail; + final bool isAdmin, isDetail; const BookingCard({ super.key, required this.booking, @@ -23,7 +23,7 @@ class BookingCard extends StatelessWidget { this.onInfo, this.onCopy, this.onDelete, - this.isSuperAdmin = false, + this.isAdmin = false, this.isDetail = false, }); @@ -36,7 +36,7 @@ class BookingCard extends StatelessWidget { ).endDate!.isAfter(DateTime.now()) : booking.end.isAfter(DateTime.now()); final showButton = - (isNotEnded && booking.decision == Decision.pending) || isSuperAdmin; + (isNotEnded && booking.decision == Decision.pending) || isAdmin; final List cardColor; final Color smallTextColor; final Color bigTextColor; @@ -199,13 +199,13 @@ class BookingCard extends StatelessWidget { ), ), ), - if (isSuperAdmin) const Spacer(), - if (isSuperAdmin) + if (isAdmin) const Spacer(), + if (isAdmin) GestureDetector( onTap: onConfirm, child: CardButton( color: lightIconBackgroundColor, - borderColor: isSuperAdmin + borderColor: isAdmin ? booking.decision == Decision.approved ? darkIconBackgroundColor : Colors.transparent @@ -217,13 +217,13 @@ class BookingCard extends StatelessWidget { ), ), ), - if (isSuperAdmin) const Spacer(), - if (isSuperAdmin) + if (isAdmin) const Spacer(), + if (isAdmin) GestureDetector( onTap: onDecline, child: CardButton( color: darkIconBackgroundColor, - borderColor: isSuperAdmin + borderColor: isAdmin ? booking.decision == Decision.declined ? Colors.white : Colors.transparent @@ -237,8 +237,8 @@ class BookingCard extends StatelessWidget { ), ), ), - if (!isSuperAdmin) const Spacer(), - if (!isSuperAdmin && booking.decision == Decision.pending) + if (!isAdmin) const Spacer(), + if (!isAdmin && booking.decision == Decision.pending) WaitingButton( onTap: onDelete, builder: (child) => CardButton( diff --git a/lib/booking/ui/pages/admin_pages/add_edit_manager_page.dart b/lib/booking/ui/pages/admin_pages/add_edit_manager_page.dart index eb6b445499..8cceac843e 100644 --- a/lib/booking/ui/pages/admin_pages/add_edit_manager_page.dart +++ b/lib/booking/ui/pages/admin_pages/add_edit_manager_page.dart @@ -60,13 +60,13 @@ class AddEditManagerPage extends HookConsumerWidget { child: Column( children: [ const SizedBox(height: 50), - SuperAdminEntry( + AdminEntry( name: AppLocalizations.of(context)!.bookingManagerName, nameController: name, ), const SizedBox(height: 50), groupList.when( - data: (List data) => SuperAdminScrollChips( + data: (List data) => AdminScrollChips( isEdit: isEdit, data: data, dataKey: dataKey, @@ -97,7 +97,7 @@ class AddEditManagerPage extends HookConsumerWidget { }, ), const SizedBox(height: 50), - SuperAdminShrinkButton( + AdminShrinkButton( onTap: () async { await tokenExpireWrapper(ref, () async { Manager newManager = Manager( @@ -136,7 +136,7 @@ class AddEditManagerPage extends HookConsumerWidget { ), if (isEdit) ...[ const SizedBox(height: 30), - SuperAdminShrinkButton( + AdminShrinkButton( onTap: () async { await tokenExpireWrapper(ref, () async { await showDialog( diff --git a/lib/booking/ui/pages/admin_pages/add_edit_room_page.dart b/lib/booking/ui/pages/admin_pages/add_edit_room_page.dart index c7f0f1a4c4..17a2da96f0 100644 --- a/lib/booking/ui/pages/admin_pages/add_edit_room_page.dart +++ b/lib/booking/ui/pages/admin_pages/add_edit_room_page.dart @@ -60,13 +60,13 @@ class AddEditRoomPage extends HookConsumerWidget { child: Column( children: [ const SizedBox(height: 50), - SuperAdminEntry( + AdminEntry( name: AppLocalizations.of(context)!.bookingRoomName, nameController: name, ), const SizedBox(height: 50), managerList.when( - data: (List data) => SuperAdminScrollChips( + data: (List data) => AdminScrollChips( data: data, isEdit: isEdit, dataKey: dataKey, @@ -97,7 +97,7 @@ class AddEditRoomPage extends HookConsumerWidget { }, ), const SizedBox(height: 50), - SuperAdminShrinkButton( + AdminShrinkButton( onTap: () async { await tokenExpireWrapper(ref, () async { Room newRoom = Room( @@ -131,7 +131,7 @@ class AddEditRoomPage extends HookConsumerWidget { ), if (isEdit) ...[ const SizedBox(height: 30), - SuperAdminShrinkButton( + AdminShrinkButton( onTap: () async { await tokenExpireWrapper(ref, () async { await showDialog( diff --git a/lib/booking/ui/pages/admin_pages/admin_entry.dart b/lib/booking/ui/pages/admin_pages/admin_entry.dart index 2f0b0e7915..827422d3fa 100644 --- a/lib/booking/ui/pages/admin_pages/admin_entry.dart +++ b/lib/booking/ui/pages/admin_pages/admin_entry.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import 'package:titan/tools/constants.dart'; -class SuperAdminEntry extends StatelessWidget { +class AdminEntry extends StatelessWidget { final String name; final TextEditingController nameController; - const SuperAdminEntry({ + const AdminEntry({ super.key, required this.name, required this.nameController, diff --git a/lib/booking/ui/pages/admin_pages/admin_page.dart b/lib/booking/ui/pages/admin_pages/admin_page.dart index 547501228f..a906b8f28a 100644 --- a/lib/booking/ui/pages/admin_pages/admin_page.dart +++ b/lib/booking/ui/pages/admin_pages/admin_page.dart @@ -21,8 +21,8 @@ import 'package:titan/tools/ui/layouts/refresher.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/l10n/app_localizations.dart'; -class SuperAdminPage extends HookConsumerWidget { - const SuperAdminPage({super.key}); +class AdminPage extends HookConsumerWidget { + const AdminPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { diff --git a/lib/booking/ui/pages/admin_pages/admin_scroll_chips.dart b/lib/booking/ui/pages/admin_pages/admin_scroll_chips.dart index 79fc7b1c03..0120862d73 100644 --- a/lib/booking/ui/pages/admin_pages/admin_scroll_chips.dart +++ b/lib/booking/ui/pages/admin_pages/admin_scroll_chips.dart @@ -1,14 +1,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; -class SuperAdminScrollChips extends HookWidget { +class AdminScrollChips extends HookWidget { final bool isEdit; final List data; final GlobalKey dataKey; final String pageStorageKeyName; final Widget Function(T) builder; - const SuperAdminScrollChips({ + const AdminScrollChips({ super.key, required this.isEdit, required this.dataKey, diff --git a/lib/booking/ui/pages/admin_pages/admin_shrink_button.dart b/lib/booking/ui/pages/admin_pages/admin_shrink_button.dart index e549710d58..8f163903bf 100644 --- a/lib/booking/ui/pages/admin_pages/admin_shrink_button.dart +++ b/lib/booking/ui/pages/admin_pages/admin_shrink_button.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; -class SuperAdminShrinkButton extends StatelessWidget { +class AdminShrinkButton extends StatelessWidget { final Future Function() onTap; final String buttonText; - const SuperAdminShrinkButton({ + const AdminShrinkButton({ super.key, required this.onTap, required this.buttonText, diff --git a/lib/booking/ui/pages/booking_pages/add_edit_booking_page.dart b/lib/booking/ui/pages/booking_pages/add_edit_booking_page.dart index c0cf67a0af..661cf5db3d 100644 --- a/lib/booking/ui/pages/booking_pages/add_edit_booking_page.dart +++ b/lib/booking/ui/pages/booking_pages/add_edit_booking_page.dart @@ -115,7 +115,7 @@ class AddEditBookingPage extends HookConsumerWidget { const SizedBox(height: 20), AsyncChild( value: rooms, - builder: (context, data) => SuperAdminScrollChips( + builder: (context, data) => AdminScrollChips( key: scrollKey, isEdit: isEdit, dataKey: dataKey, diff --git a/lib/booking/ui/pages/detail_pages/detail_booking.dart b/lib/booking/ui/pages/detail_pages/detail_booking.dart index b146abe531..094dcf7fcb 100644 --- a/lib/booking/ui/pages/detail_pages/detail_booking.dart +++ b/lib/booking/ui/pages/detail_pages/detail_booking.dart @@ -12,8 +12,8 @@ import 'package:url_launcher/url_launcher.dart'; import 'package:titan/l10n/app_localizations.dart'; class DetailBookingPage extends HookConsumerWidget { - final bool isSuperAdmin; - const DetailBookingPage({super.key, required this.isSuperAdmin}); + final bool isAdmin; + const DetailBookingPage({super.key, required this.isAdmin}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -76,7 +76,7 @@ class DetailBookingPage extends HookConsumerWidget { ), ), const SizedBox(height: 30), - if (isSuperAdmin) + if (isAdmin) Column( children: [ AutoSizeText( diff --git a/lib/booking/ui/pages/main_page/main_page.dart b/lib/booking/ui/pages/main_page/main_page.dart index b794158512..3a4a9387dd 100644 --- a/lib/booking/ui/pages/main_page/main_page.dart +++ b/lib/booking/ui/pages/main_page/main_page.dart @@ -35,7 +35,7 @@ class BookingMainPage extends HookConsumerWidget { const double minCalendarHeight = 400; const double sumOfHeightOfOthersWidgets = 361; final isManager = ref.watch(isManagerProvider); - final isSuperAdmin = ref.watch(isSuperAdminProvider); + final isAdmin = ref.watch(isAdminProvider); final bookingsNotifier = ref.watch(userBookingListProvider.notifier); final confirmedBookingsNotifier = ref.watch( confirmedBookingListProvider.notifier, @@ -72,21 +72,21 @@ class BookingMainPage extends HookConsumerWidget { ), child: Column( children: [ - if (isSuperAdmin | isManager) const SizedBox(height: 10), + if (isAdmin | isManager) const SizedBox(height: 10), SizedBox( width: 300, child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ if (isManager) - SuperAdminButton( + AdminButton( text: AppLocalizations.of(context)!.bookingManagement, onTap: () { QR.to(BookingRouter.root + BookingRouter.manager); }, ), - if (isSuperAdmin) - SuperAdminButton( + if (isAdmin) + AdminButton( onTap: () { QR.to(BookingRouter.root + BookingRouter.admin); }, diff --git a/lib/booking/ui/pages/manager_page/list_booking.dart b/lib/booking/ui/pages/manager_page/list_booking.dart index 1da087a858..c5a928de3f 100644 --- a/lib/booking/ui/pages/manager_page/list_booking.dart +++ b/lib/booking/ui/pages/manager_page/list_booking.dart @@ -101,7 +101,7 @@ class ListBooking extends HookConsumerWidget { items: bookings, itemBuilder: (context, e, i) => BookingCard( booking: e, - isSuperAdmin: true, + isAdmin: true, isDetail: false, onEdit: () { handleBooking(e); diff --git a/lib/cinema/providers/is_cinema_admin.dart b/lib/cinema/providers/is_cinema_admin.dart index 8665d693de..5a15b340c5 100644 --- a/lib/cinema/providers/is_cinema_admin.dart +++ b/lib/cinema/providers/is_cinema_admin.dart @@ -1,7 +1,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/user/providers/user_provider.dart'; -final isCinemaSuperAdminProvider = StateProvider((ref) { +final isCinemaAdminProvider = StateProvider((ref) { final me = ref.watch(userProvider); return me.groups .map((e) => e.id) diff --git a/lib/cinema/ui/pages/admin_page/admin_page.dart b/lib/cinema/ui/pages/admin_page/admin_page.dart index 444abb3dfa..46718e86ff 100644 --- a/lib/cinema/ui/pages/admin_page/admin_page.dart +++ b/lib/cinema/ui/pages/admin_page/admin_page.dart @@ -13,8 +13,8 @@ import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/l10n/app_localizations.dart'; -class SuperAdminPage extends HookConsumerWidget { - const SuperAdminPage({super.key}); +class AdminPage extends HookConsumerWidget { + const AdminPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -45,7 +45,7 @@ class SuperAdminPage extends HookConsumerWidget { ), ), ...data.map( - (session) => SuperAdminSessionCard( + (session) => AdminSessionCard( session: session, onEdit: () { sessionNotifier.setSession(session); diff --git a/lib/cinema/ui/pages/admin_page/admin_session_card.dart b/lib/cinema/ui/pages/admin_page/admin_session_card.dart index 003da2d88b..15ac4dfcb3 100644 --- a/lib/cinema/ui/pages/admin_page/admin_session_card.dart +++ b/lib/cinema/ui/pages/admin_page/admin_session_card.dart @@ -9,11 +9,11 @@ import 'package:titan/tools/ui/builders/auto_loader_child.dart'; import 'package:titan/tools/ui/layouts/card_button.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; -class SuperAdminSessionCard extends HookConsumerWidget { +class AdminSessionCard extends HookConsumerWidget { final Session session; final VoidCallback onTap, onEdit; final Future Function() onDelete; - const SuperAdminSessionCard({ + const AdminSessionCard({ super.key, required this.session, required this.onTap, diff --git a/lib/cinema/ui/pages/main_page/main_page.dart b/lib/cinema/ui/pages/main_page/main_page.dart index a02628fbd4..f6fa825a1d 100644 --- a/lib/cinema/ui/pages/main_page/main_page.dart +++ b/lib/cinema/ui/pages/main_page/main_page.dart @@ -36,7 +36,7 @@ class CinemaMainPage extends HookConsumerWidget { sessionListPageControllerProvider(initialPage), ); final scrollNotifier = ref.watch(scrollProvider.notifier); - final isSuperAdmin = ref.watch(isCinemaSuperAdminProvider); + final isAdmin = ref.watch(isCinemaAdminProvider); final isWebFormat = ref.watch(isWebFormatProvider); pageController.addListener(() { scrollNotifier.setScroll(pageController.page!); @@ -71,8 +71,8 @@ class CinemaMainPage extends HookConsumerWidget { color: Colors.grey, ), ), - if (isSuperAdmin) - SuperAdminButton( + if (isAdmin) + AdminButton( onTap: () { QR.to(CinemaRouter.root + CinemaRouter.admin); initialPageNotifier.setMainPageIndex(currentPage); diff --git a/lib/event/providers/is_admin_provider.dart b/lib/event/providers/is_admin_provider.dart index aabefd9a5a..78f1998e95 100644 --- a/lib/event/providers/is_admin_provider.dart +++ b/lib/event/providers/is_admin_provider.dart @@ -1,7 +1,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/user/providers/user_provider.dart'; -final isEventSuperAdminProvider = StateProvider((ref) { +final isEventAdminProvider = StateProvider((ref) { final me = ref.watch(userProvider); return me.groups .map((e) => e.id) diff --git a/lib/event/ui/components/event_ui.dart b/lib/event/ui/components/event_ui.dart index c6e0966b01..51f48f1c1b 100644 --- a/lib/event/ui/components/event_ui.dart +++ b/lib/event/ui/components/event_ui.dart @@ -18,13 +18,13 @@ import 'package:titan/l10n/app_localizations.dart'; class EventUi extends ConsumerWidget { final Event event; - final bool isDetailPage, isSuperAdmin; + final bool isDetailPage, isAdmin; final Function()? onEdit, onConfirm, onDecline, onCopy, onInfo; const EventUi({ super.key, required this.event, this.isDetailPage = false, - this.isSuperAdmin = false, + this.isAdmin = false, this.onEdit, this.onConfirm, this.onDecline, @@ -48,7 +48,7 @@ class EventUi extends ConsumerWidget { return GestureDetector( behavior: HitTestBehavior.opaque, onTap: () { - if (!isDetailPage || isSuperAdmin) { + if (!isDetailPage || isAdmin) { onInfo?.call(); } }, @@ -290,8 +290,8 @@ class EventUi extends ConsumerWidget { ), ], ), - if (isSuperAdmin) const Spacer(), - if (isSuperAdmin) + if (isAdmin) const Spacer(), + if (isAdmin) Row( children: [ GestureDetector( @@ -319,7 +319,7 @@ class EventUi extends ConsumerWidget { } }, child: CardButton( - borderColor: isSuperAdmin + borderColor: isAdmin ? event.decision == Decision.approved ? Colors.black : Colors.transparent @@ -339,7 +339,7 @@ class EventUi extends ConsumerWidget { }, child: CardButton( color: Colors.black, - borderColor: isSuperAdmin + borderColor: isAdmin ? event.decision == Decision.declined ? Colors.white : Colors.transparent diff --git a/lib/event/ui/pages/admin_page/admin_page.dart b/lib/event/ui/pages/admin_page/admin_page.dart index 31de8fb4a6..ba9eee208a 100644 --- a/lib/event/ui/pages/admin_page/admin_page.dart +++ b/lib/event/ui/pages/admin_page/admin_page.dart @@ -10,8 +10,8 @@ import 'package:titan/tools/ui/widgets/calendar.dart'; import 'package:syncfusion_flutter_calendar/calendar.dart'; import 'package:titan/l10n/app_localizations.dart'; -class SuperAdminPage extends HookConsumerWidget { - const SuperAdminPage({super.key}); +class AdminPage extends HookConsumerWidget { + const AdminPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { diff --git a/lib/event/ui/pages/admin_page/list_event.dart b/lib/event/ui/pages/admin_page/list_event.dart index 5afe0a0af2..7022e09598 100644 --- a/lib/event/ui/pages/admin_page/list_event.dart +++ b/lib/event/ui/pages/admin_page/list_event.dart @@ -84,7 +84,7 @@ class ListEvent extends HookConsumerWidget { itemBuilder: (context, e, i) => EventUi( event: e, isDetailPage: true, - isSuperAdmin: true, + isAdmin: true, onEdit: () { eventNotifier.setEvent(e); QR.to( diff --git a/lib/event/ui/pages/detail_page/detail_page.dart b/lib/event/ui/pages/detail_page/detail_page.dart index 7b271c895e..b7f0dc08f0 100644 --- a/lib/event/ui/pages/detail_page/detail_page.dart +++ b/lib/event/ui/pages/detail_page/detail_page.dart @@ -9,8 +9,8 @@ import 'package:url_launcher/url_launcher.dart'; import 'package:titan/l10n/app_localizations.dart'; class DetailPage extends HookConsumerWidget { - final bool isSuperAdmin; - const DetailPage({super.key, required this.isSuperAdmin}); + final bool isAdmin; + const DetailPage({super.key, required this.isAdmin}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -68,7 +68,7 @@ class DetailPage extends HookConsumerWidget { ), ), const SizedBox(height: 20), - if (isSuperAdmin) + if (isAdmin) Column( children: [ GestureDetector( diff --git a/lib/event/ui/pages/main_page/main_page.dart b/lib/event/ui/pages/main_page/main_page.dart index 88dd53fd5b..e99d9db759 100644 --- a/lib/event/ui/pages/main_page/main_page.dart +++ b/lib/event/ui/pages/main_page/main_page.dart @@ -20,7 +20,7 @@ class EventMainPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final isSuperAdmin = ref.watch(isEventSuperAdminProvider); + final isAdmin = ref.watch(isEventAdminProvider); final eventNotifier = ref.watch(eventProvider.notifier); final eventListNotifier = ref.watch(eventEventListProvider.notifier); final events = ref.watch(eventEventListProvider); @@ -52,8 +52,8 @@ class EventMainPage extends HookConsumerWidget { color: Colors.grey, ), ), - if (isSuperAdmin) - SuperAdminButton( + if (isAdmin) + AdminButton( onTap: () { QR.to(EventRouter.root + EventRouter.admin); }, diff --git a/lib/feed/ui/pages/admin_page/admin_page.dart b/lib/feed/ui/pages/admin_page/admin_page.dart index 76f005b612..09908229cb 100644 --- a/lib/feed/ui/pages/admin_page/admin_page.dart +++ b/lib/feed/ui/pages/admin_page/admin_page.dart @@ -6,8 +6,8 @@ import 'package:titan/feed/ui/pages/admin_page/event_form.dart'; import 'package:titan/feed/ui/pages/admin_page/post_form.dart'; import 'package:titan/feed/ui/pages/admin_page/tab_navigation.dart'; -class SuperAdminPage extends HookConsumerWidget { - const SuperAdminPage({super.key}); +class AdminPage extends HookConsumerWidget { + const AdminPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { diff --git a/lib/feed/ui/pages/main_page/main_page.dart b/lib/feed/ui/pages/main_page/main_page.dart index 40947455b7..698bd11f57 100644 --- a/lib/feed/ui/pages/main_page/main_page.dart +++ b/lib/feed/ui/pages/main_page/main_page.dart @@ -93,7 +93,7 @@ class FeedMainPage extends HookConsumerWidget { color: ColorConstants.title, ), ), - if (isSuperAdmin) + if (isAdmin) CustomIconButton( icon: HeroIcon( HeroIcons.plus, diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 02fe67f2cc..8a3082e195 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -15,7 +15,7 @@ "adminAddedSchool": "School created", "adminAddedStructure": "Structure added", "adminEditedStructure": "Structure edited", - "adminSuperAdministration": "SuperAdministration", + "adminAdministration": "Administration", "adminAssociationMembership": "Membership", "adminAssociationMembershipName": "Membership name", "adminAssociationsMemberships": "Memberships", @@ -83,7 +83,7 @@ "advertAddedAdvert": "Advert published", "advertAddedAnnouncer": "Announcer added", "advertAddingError": "Error while adding", - "advertSuperAdmin": "SuperAdmin", + "advertAdmin": "Admin", "advertAdvert": "Advert", "advertChoosingAnnouncer": "Please choose an announcer", "advertChoosingPoster": "Please choose an image", @@ -132,7 +132,7 @@ "amapAddingError": "Error while adding", "amapAddingProduct": "Add a product", "amapAddOrder": "Add an order", - "amapSuperAdmin": "SuperAdmin", + "amapAdmin": "Admin", "amapAlreadyExistCommand": "An order already exists for this date", "amapAmap": "Amap", "amapAmount": "Balance", @@ -245,7 +245,7 @@ "bookingAddedManager": "Manager added", "bookingAddingError": "Error while adding", "bookingAddManager": "Add manager", - "bookingSuperAdminPage": "SuperAdmin", + "bookingAdminPage": "Admin", "bookingAllDay": "All day", "bookingBookedFor": "Booked for", "bookingBooking": "Booking", @@ -367,7 +367,7 @@ "cinemaStartHour": "Start hour", "cinemaTagline": "Tagline", "cinemaThe": "The", - "drawerSuperAdmin": "SuperAdministration", + "drawerAdmin": "Administration", "drawerAndroidAppLink": "https://play.google.com/store/apps/details?id=fr.myecl.titan", "drawerCopied": "Copied!", "drawerDownloadAppOnMobileDevice": "This site is the web version of the MyECL app. We invite you to download the app. Use this site only if you have problems with the app.\n", @@ -466,7 +466,7 @@ "loanAddedObject": "Object added", "loanAddedRoom": "Room added", "loanAddingError": "Error while adding", - "loanSuperAdmin": "SuperAdministrator", + "loanAdmin": "Administrator", "loanAvailable": "Available", "loanAvailableMultiple": "Available", "loanBorrowed": "Borrowed", @@ -605,7 +605,7 @@ "othersUnableToConnectToServer": "Unable to connect to the server", "othersVersion": "Version", "othersNoModule": "No modules available, please try again later 😢😢", - "othersSuperAdmin": "SuperAdmin", + "othersAdmin": "Admin", "othersError": "An error occurred", "othersNoValue": "Please enter a value", "othersInvalidNumber": "Please enter a number", @@ -639,8 +639,8 @@ "phonebookAddingError": "Error adding", "phonebookAddMember": "Add a member", "phonebookAddRole": "Add a role", - "phonebookSuperAdmin": "SuperAdmin", - "phonebookSuperAdminPage": "SuperAdmin page", + "phonebookAdmin": "Admin", + "phonebookAdminPage": "Admin page", "phonebookAll": "All", "phonebookApparentName": "Public role name:", "phonebookAssociation": "Association:", @@ -960,7 +960,7 @@ "seedLibraryWriteReference": "Please write the following reference: ", "settingsAccount": "Account", "settingsAddProfilePicture": "Add a photo", - "settingsSuperAdmin": "SuperAdministrator", + "settingsAdmin": "Administrator", "settingsAskHelp": "Ask for help", "settingsAssociation": "Association", "settingsBirthday": "Birthday", @@ -1137,7 +1137,7 @@ "moduleSettings": "Settings", "moduleFeed": "Feed", "moduleStyleGuide": "StyleGuide", - "moduleSuperAdmin": "SuperAdministration", + "moduleAdmin": "Administration", "moduleOthers": "Others", "modulePayment": "Payment", "paiementTopUp": "Top-up", @@ -1188,7 +1188,7 @@ "paiementHistory": "History", "paiementHandOver": "Handover", "paiementStores": "Associations", - "paiementSuperAdmin": "SuperAdministrator", + "paiementAdmin": "Administrator", "paiementSuccededTransaction": "Successful payment", "paiementNewCGU": "New Terms of Service", "paiementDecline": "Decline", @@ -1233,7 +1233,7 @@ "paiementSeeHistory": "View history", "paiementCancelTransactions": "Cancel transactions", "paiementManageSellers": "Manage sellers", - "paiementStructureSuperAdmin": "Structure administrator", + "paiementStructureAdmin": "Structure administrator", "paiementRightsOf": "Rights of", "paiementRightsUpdated": "Rights updated", "paiementRightsUpdateError": "Error while updating rights", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 74b757d375..e42faa4d8d 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -15,7 +15,7 @@ "adminAddedSchool": "École créée", "adminAddedStructure": "Structure ajoutée", "adminEditedStructure": "Structure modifiée", - "adminSuperAdministration": "SuperAdministration", + "adminAdministration": "Administration", "adminAssociationMembership": "Adhésion", "adminAssociationMembershipName": "Nom de l'adhésion", "adminAssociationsMemberships": "Adhésions", @@ -51,7 +51,7 @@ "adminGroups": "Groupes", "adminLoaningGroup": "Groupe de prêt", "adminLooking": "Recherche", - "adminManager": "SuperAdministrateur de la structure", + "adminManager": "Administrateur de la structure", "adminMaximum": "Maximum", "adminMembers": "Membres", "adminMembershipAddingError": "Erreur lors de l'ajout (surement dû à une superposition de dates)", @@ -83,7 +83,7 @@ "advertAddedAdvert": "Annonce publiée", "advertAddedAnnouncer": "Annonceur ajouté", "advertAddingError": "Erreur lors de l'ajout", - "advertSuperAdmin": "SuperAdmin", + "advertAdmin": "Admin", "advertAdvert": "Annonce", "advertChoosingAnnouncer": "Veuillez choisir un annonceur", "advertChoosingPoster": "Veuillez choisir une image", @@ -132,7 +132,7 @@ "amapAddingError": "Erreur lors de l'ajout", "amapAddingProduct": "Ajouter un produit", "amapAddOrder": "Ajouter une commande", - "amapSuperAdmin": "SuperAdmin", + "amapAdmin": "Admin", "amapAlreadyExistCommand": "Il existe déjà une commande à cette date", "amapAmap": "Amap", "amapAmount": "Solde", @@ -245,7 +245,7 @@ "bookingAddedManager": "Gestionnaire ajouté", "bookingAddingError": "Erreur lors de l'ajout", "bookingAddManager": "Ajouter un gestionnaire", - "bookingSuperAdminPage": "SuperAdministrateur", + "bookingAdminPage": "Administrateur", "bookingAllDay": "Toute la journée", "bookingBookedFor": "Réservé pour", "bookingBooking": "Réservation", @@ -367,7 +367,7 @@ "cinemaStartHour": "Heure de début", "cinemaTagline": "Slogan", "cinemaThe": "Le", - "drawerSuperAdmin": "SuperAdministration", + "drawerAdmin": "Administration", "drawerAndroidAppLink": "https://play.google.com/store/apps/details?id=fr.myecl.titan", "drawerCopied": "Copié !", "drawerDownloadAppOnMobileDevice": "Ce site est la version Web de l'application MyECL. Nous vous invitons à télécharger l'application. N'utilisez ce site qu'en cas de problème avec l'application.\n", @@ -466,7 +466,7 @@ "loanAddedObject": "Objet ajouté", "loanAddedRoom": "Salle ajoutée", "loanAddingError": "Erreur lors de l'ajout", - "loanSuperAdmin": "SuperAdministrateur", + "loanAdmin": "Administrateur", "loanAvailable": "Disponible", "loanAvailableMultiple": "Disponibles", "loanBorrowed": "Emprunté", @@ -605,7 +605,7 @@ "othersUnableToConnectToServer": "Impossible de se connecter au serveur", "othersVersion": "Version", "othersNoModule": "Aucun module disponible, veuillez réessayer ultérieurement 😢😢", - "othersSuperAdmin": "SuperAdmin", + "othersAdmin": "Admin", "othersError": "Une erreur est survenue", "othersNoValue": "Veuillez entrer une valeur", "othersInvalidNumber": "Veuillez entrer un nombre", @@ -639,8 +639,8 @@ "phonebookAddingError": "Erreur lors de l'ajout", "phonebookAddMember": "Ajouter un membre", "phonebookAddRole": "Ajouter un rôle", - "phonebookSuperAdmin": "SuperAdmin", - "phonebookSuperAdminPage": "Page SuperAdministrateur", + "phonebookAdmin": "Admin", + "phonebookAdminPage": "Page Administrateur", "phonebookAll": "Toutes", "phonebookApparentName": "Nom public du rôle :", "phonebookAssociation": "Association :", @@ -960,7 +960,7 @@ "seedLibraryWriteReference": "Veuillez écrire la référence suivante : ", "settingsAccount": "Compte", "settingsAddProfilePicture": "Ajouter une photo", - "settingsSuperAdmin": "SuperAdministrateur", + "settingsAdmin": "Administrateur", "settingsAskHelp": "Demander de l'aide", "settingsAssociation": "Association", "settingsBirthday": "Date de naissance", @@ -1136,7 +1136,7 @@ "moduleSettings": "Paramètres", "moduleFeed": "Feed", "moduleStyleGuide": "StyleGuide", - "moduleSuperAdmin": "SuperAdminitration", + "moduleAdmin": "Adminitration", "moduleOthers": "Autres", "modulePayment": "Paiement", "paiementTopUp" : "Recharge", @@ -1187,7 +1187,7 @@ "paiementHistory": "Historique", "paiementHandOver": "Passation", "paiementStores": "Associations", - "paiementSuperAdmin": "SuperAdministrateur", + "paiementAdmin": "Administrateur", "paiementSuccededTransaction": "Paiement réussi", "paiementNewCGU" : "Nouvelles Conditions Générales d'Utilisation", "paiementDecline": "Refuser", @@ -1232,7 +1232,7 @@ "paiementSeeHistory": "Voir l'historique", "paiementCancelTransactions": "Annuler les transactions", "paiementManageSellers": "Gérer les vendeurs", - "paiementStructureSuperAdmin": "SuperAdministrateur de la structure", + "paiementStructureAdmin": "Administrateur de la structure", "paiementRightsOf": "Droits de", "paiementRightsUpdated": "Droits mis à jour", "paiementRightsUpdateError": "Erreur lors de la mise à jour des droits", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index e9d0de5aeb..3df8b0b8c7 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -188,11 +188,11 @@ abstract class AppLocalizations { /// **'Structure modifiée'** String get adminEditedStructure; - /// No description provided for @adminSuperAdministration. + /// No description provided for @adminAdministration. /// /// In fr, this message translates to: - /// **'SuperAdministration'** - String get adminSuperAdministration; + /// **'Administration'** + String get adminAdministration; /// No description provided for @adminAssociationMembership. /// @@ -407,7 +407,7 @@ abstract class AppLocalizations { /// No description provided for @adminManager. /// /// In fr, this message translates to: - /// **'SuperAdministrateur de la structure'** + /// **'Administrateur de la structure'** String get adminManager; /// No description provided for @adminMaximum. @@ -596,11 +596,11 @@ abstract class AppLocalizations { /// **'Erreur lors de l\'ajout'** String get advertAddingError; - /// No description provided for @advertSuperAdmin. + /// No description provided for @advertAdmin. /// /// In fr, this message translates to: - /// **'SuperAdmin'** - String get advertSuperAdmin; + /// **'Admin'** + String get advertAdmin; /// No description provided for @advertAdvert. /// @@ -890,11 +890,11 @@ abstract class AppLocalizations { /// **'Ajouter une commande'** String get amapAddOrder; - /// No description provided for @amapSuperAdmin. + /// No description provided for @amapAdmin. /// /// In fr, this message translates to: - /// **'SuperAdmin'** - String get amapSuperAdmin; + /// **'Admin'** + String get amapAdmin; /// No description provided for @amapAlreadyExistCommand. /// @@ -1568,11 +1568,11 @@ abstract class AppLocalizations { /// **'Ajouter un gestionnaire'** String get bookingAddManager; - /// No description provided for @bookingSuperAdminPage. + /// No description provided for @bookingAdminPage. /// /// In fr, this message translates to: - /// **'SuperAdministrateur'** - String get bookingSuperAdminPage; + /// **'Administrateur'** + String get bookingAdminPage; /// No description provided for @bookingAllDay. /// @@ -2300,11 +2300,11 @@ abstract class AppLocalizations { /// **'Le'** String get cinemaThe; - /// No description provided for @drawerSuperAdmin. + /// No description provided for @drawerAdmin. /// /// In fr, this message translates to: - /// **'SuperAdministration'** - String get drawerSuperAdmin; + /// **'Administration'** + String get drawerAdmin; /// No description provided for @drawerAndroidAppLink. /// @@ -2894,11 +2894,11 @@ abstract class AppLocalizations { /// **'Erreur lors de l\'ajout'** String get loanAddingError; - /// No description provided for @loanSuperAdmin. + /// No description provided for @loanAdmin. /// /// In fr, this message translates to: - /// **'SuperAdministrateur'** - String get loanSuperAdmin; + /// **'Administrateur'** + String get loanAdmin; /// No description provided for @loanAvailable. /// @@ -3728,11 +3728,11 @@ abstract class AppLocalizations { /// **'Aucun module disponible, veuillez réessayer ultérieurement 😢😢'** String get othersNoModule; - /// No description provided for @othersSuperAdmin. + /// No description provided for @othersAdmin. /// /// In fr, this message translates to: - /// **'SuperAdmin'** - String get othersSuperAdmin; + /// **'Admin'** + String get othersAdmin; /// No description provided for @othersError. /// @@ -3932,17 +3932,17 @@ abstract class AppLocalizations { /// **'Ajouter un rôle'** String get phonebookAddRole; - /// No description provided for @phonebookSuperAdmin. + /// No description provided for @phonebookAdmin. /// /// In fr, this message translates to: - /// **'SuperAdmin'** - String get phonebookSuperAdmin; + /// **'Admin'** + String get phonebookAdmin; - /// No description provided for @phonebookSuperAdminPage. + /// No description provided for @phonebookAdminPage. /// /// In fr, this message translates to: - /// **'Page SuperAdministrateur'** - String get phonebookSuperAdminPage; + /// **'Page Administrateur'** + String get phonebookAdminPage; /// No description provided for @phonebookAll. /// @@ -5858,11 +5858,11 @@ abstract class AppLocalizations { /// **'Ajouter une photo'** String get settingsAddProfilePicture; - /// No description provided for @settingsSuperAdmin. + /// No description provided for @settingsAdmin. /// /// In fr, this message translates to: - /// **'SuperAdministrateur'** - String get settingsSuperAdmin; + /// **'Administrateur'** + String get settingsAdmin; /// No description provided for @settingsAskHelp. /// @@ -6872,11 +6872,11 @@ abstract class AppLocalizations { /// **'StyleGuide'** String get moduleStyleGuide; - /// No description provided for @moduleSuperAdmin. + /// No description provided for @moduleAdmin. /// /// In fr, this message translates to: - /// **'SuperAdminitration'** - String get moduleSuperAdmin; + /// **'Adminitration'** + String get moduleAdmin; /// No description provided for @moduleOthers. /// @@ -7178,11 +7178,11 @@ abstract class AppLocalizations { /// **'Associations'** String get paiementStores; - /// No description provided for @paiementSuperAdmin. + /// No description provided for @paiementAdmin. /// /// In fr, this message translates to: - /// **'SuperAdministrateur'** - String get paiementSuperAdmin; + /// **'Administrateur'** + String get paiementAdmin; /// No description provided for @paiementSuccededTransaction. /// @@ -7448,11 +7448,11 @@ abstract class AppLocalizations { /// **'Gérer les vendeurs'** String get paiementManageSellers; - /// No description provided for @paiementStructureSuperAdmin. + /// No description provided for @paiementStructureAdmin. /// /// In fr, this message translates to: - /// **'SuperAdministrateur de la structure'** - String get paiementStructureSuperAdmin; + /// **'Administrateur de la structure'** + String get paiementStructureAdmin; /// No description provided for @paiementRightsOf. /// diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 1e02c2af5d..16efee4228 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -54,7 +54,7 @@ class AppLocalizationsEn extends AppLocalizations { String get adminEditedStructure => 'Structure edited'; @override - String get adminSuperAdministration => 'SuperAdministration'; + String get adminAdministration => 'Administration'; @override String get adminAssociationMembership => 'Membership'; @@ -260,7 +260,7 @@ class AppLocalizationsEn extends AppLocalizations { String get advertAddingError => 'Error while adding'; @override - String get advertSuperAdmin => 'SuperAdmin'; + String get advertAdmin => 'Admin'; @override String get advertAdvert => 'Advert'; @@ -407,7 +407,7 @@ class AppLocalizationsEn extends AppLocalizations { String get amapAddOrder => 'Add an order'; @override - String get amapSuperAdmin => 'SuperAdmin'; + String get amapAdmin => 'Admin'; @override String get amapAlreadyExistCommand => 'An order already exists for this date'; @@ -751,7 +751,7 @@ class AppLocalizationsEn extends AppLocalizations { String get bookingAddManager => 'Add manager'; @override - String get bookingSuperAdminPage => 'SuperAdmin'; + String get bookingAdminPage => 'Admin'; @override String get bookingAllDay => 'All day'; @@ -1121,7 +1121,7 @@ class AppLocalizationsEn extends AppLocalizations { String get cinemaThe => 'The'; @override - String get drawerSuperAdmin => 'SuperAdministration'; + String get drawerAdmin => 'Administration'; @override String get drawerAndroidAppLink => @@ -1422,7 +1422,7 @@ class AppLocalizationsEn extends AppLocalizations { String get loanAddingError => 'Error while adding'; @override - String get loanSuperAdmin => 'SuperAdministrator'; + String get loanAdmin => 'Administrator'; @override String get loanAvailable => 'Available'; @@ -1849,7 +1849,7 @@ class AppLocalizationsEn extends AppLocalizations { 'No modules available, please try again later 😢😢'; @override - String get othersSuperAdmin => 'SuperAdmin'; + String get othersAdmin => 'Admin'; @override String get othersError => 'An error occurred'; @@ -1951,10 +1951,10 @@ class AppLocalizationsEn extends AppLocalizations { String get phonebookAddRole => 'Add a role'; @override - String get phonebookSuperAdmin => 'SuperAdmin'; + String get phonebookAdmin => 'Admin'; @override - String get phonebookSuperAdminPage => 'SuperAdmin page'; + String get phonebookAdminPage => 'Admin page'; @override String get phonebookAll => 'All'; @@ -2942,7 +2942,7 @@ class AppLocalizationsEn extends AppLocalizations { String get settingsAddProfilePicture => 'Add a photo'; @override - String get settingsSuperAdmin => 'SuperAdministrator'; + String get settingsAdmin => 'Administrator'; @override String get settingsAskHelp => 'Ask for help'; @@ -3466,7 +3466,7 @@ class AppLocalizationsEn extends AppLocalizations { String get moduleStyleGuide => 'StyleGuide'; @override - String get moduleSuperAdmin => 'SuperAdministration'; + String get moduleAdmin => 'Administration'; @override String get moduleOthers => 'Others'; @@ -3627,7 +3627,7 @@ class AppLocalizationsEn extends AppLocalizations { String get paiementStores => 'Associations'; @override - String get paiementSuperAdmin => 'SuperAdministrator'; + String get paiementAdmin => 'Administrator'; @override String get paiementSuccededTransaction => 'Successful payment'; @@ -3767,7 +3767,7 @@ class AppLocalizationsEn extends AppLocalizations { String get paiementManageSellers => 'Manage sellers'; @override - String get paiementStructureSuperAdmin => 'Structure administrator'; + String get paiementStructureAdmin => 'Structure administrator'; @override String get paiementRightsOf => 'Rights of'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 71581252cf..17129e7eac 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -54,7 +54,7 @@ class AppLocalizationsFr extends AppLocalizations { String get adminEditedStructure => 'Structure modifiée'; @override - String get adminSuperAdministration => 'SuperAdministration'; + String get adminAdministration => 'Administration'; @override String get adminAssociationMembership => 'Adhésion'; @@ -163,7 +163,7 @@ class AppLocalizationsFr extends AppLocalizations { String get adminLooking => 'Recherche'; @override - String get adminManager => 'SuperAdministrateur de la structure'; + String get adminManager => 'Administrateur de la structure'; @override String get adminMaximum => 'Maximum'; @@ -261,7 +261,7 @@ class AppLocalizationsFr extends AppLocalizations { String get advertAddingError => 'Erreur lors de l\'ajout'; @override - String get advertSuperAdmin => 'SuperAdmin'; + String get advertAdmin => 'Admin'; @override String get advertAdvert => 'Annonce'; @@ -408,7 +408,7 @@ class AppLocalizationsFr extends AppLocalizations { String get amapAddOrder => 'Ajouter une commande'; @override - String get amapSuperAdmin => 'SuperAdmin'; + String get amapAdmin => 'Admin'; @override String get amapAlreadyExistCommand => @@ -754,7 +754,7 @@ class AppLocalizationsFr extends AppLocalizations { String get bookingAddManager => 'Ajouter un gestionnaire'; @override - String get bookingSuperAdminPage => 'SuperAdministrateur'; + String get bookingAdminPage => 'Administrateur'; @override String get bookingAllDay => 'Toute la journée'; @@ -1125,7 +1125,7 @@ class AppLocalizationsFr extends AppLocalizations { String get cinemaThe => 'Le'; @override - String get drawerSuperAdmin => 'SuperAdministration'; + String get drawerAdmin => 'Administration'; @override String get drawerAndroidAppLink => @@ -1428,7 +1428,7 @@ class AppLocalizationsFr extends AppLocalizations { String get loanAddingError => 'Erreur lors de l\'ajout'; @override - String get loanSuperAdmin => 'SuperAdministrateur'; + String get loanAdmin => 'Administrateur'; @override String get loanAvailable => 'Disponible'; @@ -1856,7 +1856,7 @@ class AppLocalizationsFr extends AppLocalizations { 'Aucun module disponible, veuillez réessayer ultérieurement 😢😢'; @override - String get othersSuperAdmin => 'SuperAdmin'; + String get othersAdmin => 'Admin'; @override String get othersError => 'Une erreur est survenue'; @@ -1960,10 +1960,10 @@ class AppLocalizationsFr extends AppLocalizations { String get phonebookAddRole => 'Ajouter un rôle'; @override - String get phonebookSuperAdmin => 'SuperAdmin'; + String get phonebookAdmin => 'Admin'; @override - String get phonebookSuperAdminPage => 'Page SuperAdministrateur'; + String get phonebookAdminPage => 'Page Administrateur'; @override String get phonebookAll => 'Toutes'; @@ -2961,7 +2961,7 @@ class AppLocalizationsFr extends AppLocalizations { String get settingsAddProfilePicture => 'Ajouter une photo'; @override - String get settingsSuperAdmin => 'SuperAdministrateur'; + String get settingsAdmin => 'Administrateur'; @override String get settingsAskHelp => 'Demander de l\'aide'; @@ -3493,7 +3493,7 @@ class AppLocalizationsFr extends AppLocalizations { String get moduleStyleGuide => 'StyleGuide'; @override - String get moduleSuperAdmin => 'SuperAdminitration'; + String get moduleAdmin => 'Adminitration'; @override String get moduleOthers => 'Autres'; @@ -3661,7 +3661,7 @@ class AppLocalizationsFr extends AppLocalizations { String get paiementStores => 'Associations'; @override - String get paiementSuperAdmin => 'SuperAdministrateur'; + String get paiementAdmin => 'Administrateur'; @override String get paiementSuccededTransaction => 'Paiement réussi'; @@ -3805,8 +3805,7 @@ class AppLocalizationsFr extends AppLocalizations { String get paiementManageSellers => 'Gérer les vendeurs'; @override - String get paiementStructureSuperAdmin => - 'SuperAdministrateur de la structure'; + String get paiementStructureAdmin => 'Administrateur de la structure'; @override String get paiementRightsOf => 'Droits de'; diff --git a/lib/loan/providers/admin_history_loan_list_provider.dart b/lib/loan/providers/admin_history_loan_list_provider.dart index 273cbf73e7..4cbc9a1787 100644 --- a/lib/loan/providers/admin_history_loan_list_provider.dart +++ b/lib/loan/providers/admin_history_loan_list_provider.dart @@ -7,17 +7,17 @@ import 'package:titan/loan/providers/user_loaner_list_provider.dart'; import 'package:titan/tools/providers/map_provider.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; -class SuperAdminHistoryLoanListNotifier extends MapNotifier { - SuperAdminHistoryLoanListNotifier() : super(); +class AdminHistoryLoanListNotifier extends MapNotifier { + AdminHistoryLoanListNotifier() : super(); } final adminHistoryLoanListProvider = StateNotifierProvider< - SuperAdminHistoryLoanListNotifier, + AdminHistoryLoanListNotifier, Map>?> >((ref) { - SuperAdminHistoryLoanListNotifier adminLoanListNotifier = - SuperAdminHistoryLoanListNotifier(); + AdminHistoryLoanListNotifier adminLoanListNotifier = + AdminHistoryLoanListNotifier(); tokenExpireWrapperAuth(ref, () async { final loaners = ref.watch(loanerList); final loaner = ref.watch(loanerProvider); diff --git a/lib/loan/providers/admin_loan_list_provider.dart b/lib/loan/providers/admin_loan_list_provider.dart index 70a6d7b9a3..68fd332dc5 100644 --- a/lib/loan/providers/admin_loan_list_provider.dart +++ b/lib/loan/providers/admin_loan_list_provider.dart @@ -7,17 +7,16 @@ import 'package:titan/loan/providers/user_loaner_list_provider.dart'; import 'package:titan/tools/providers/map_provider.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; -class SuperAdminLoanListNotifier extends MapNotifier { - SuperAdminLoanListNotifier() : super(); +class AdminLoanListNotifier extends MapNotifier { + AdminLoanListNotifier() : super(); } final adminLoanListProvider = StateNotifierProvider< - SuperAdminLoanListNotifier, + AdminLoanListNotifier, Map>?> >((ref) { - SuperAdminLoanListNotifier adminLoanListNotifier = - SuperAdminLoanListNotifier(); + AdminLoanListNotifier adminLoanListNotifier = AdminLoanListNotifier(); tokenExpireWrapperAuth(ref, () async { final loaners = ref.watch(loanerList); final loaner = ref.watch(loanerProvider); diff --git a/lib/loan/providers/is_loan_admin_provider.dart b/lib/loan/providers/is_loan_admin_provider.dart index 53f07dd115..1069cac151 100644 --- a/lib/loan/providers/is_loan_admin_provider.dart +++ b/lib/loan/providers/is_loan_admin_provider.dart @@ -1,7 +1,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/loan/providers/user_loaner_list_provider.dart'; -final isLoanSuperAdminProvider = StateProvider((ref) { +final isLoanAdminProvider = StateProvider((ref) { final loaners = ref.watch(userLoanerListProvider); final loanersName = loaners.maybeWhen( data: (loaners) => loaners.map((e) => e.name).toList(), diff --git a/lib/loan/ui/pages/admin_page/admin_page.dart b/lib/loan/ui/pages/admin_page/admin_page.dart index 978ed55032..4dd4a31a9b 100644 --- a/lib/loan/ui/pages/admin_page/admin_page.dart +++ b/lib/loan/ui/pages/admin_page/admin_page.dart @@ -17,8 +17,8 @@ import 'package:titan/loan/ui/pages/admin_page/on_going_loan.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; -class SuperAdminPage extends HookConsumerWidget { - const SuperAdminPage({super.key}); +class AdminPage extends HookConsumerWidget { + const AdminPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -103,15 +103,15 @@ class SuperAdminPage extends HookConsumerWidget { final adminLoanListNotifier = ref.read( adminLoanListProvider.notifier, ); - final listSuperAdminItems = adminLoanList[key]; - if (listSuperAdminItems == null) { + final listAdminItems = adminLoanList[key]; + if (listAdminItems == null) { adminLoanListNotifier.autoLoadList( ref, key, (key) => loanListNotifier.loadLoan(key.id), ); } else { - listSuperAdminItems.whenData((adminLoanList) async { + listAdminItems.whenData((adminLoanList) async { if (adminLoanList.isEmpty) { adminLoanListNotifier.autoLoadList( ref, @@ -128,16 +128,15 @@ class SuperAdminPage extends HookConsumerWidget { final adminHistoryLoanListNotifier = ref.read( adminHistoryLoanListProvider.notifier, ); - final listSuperAdminHistoryItems = - adminHistoryLoanList[key]; - if (listSuperAdminHistoryItems == null) { + final listAdminHistoryItems = adminHistoryLoanList[key]; + if (listAdminHistoryItems == null) { adminHistoryLoanListNotifier.autoLoadList( ref, key, (key) => historyLoanListNotifier.loadLoan(key.id), ); } else { - listSuperAdminHistoryItems.whenData(( + listAdminHistoryItems.whenData(( adminHistoryLoanList, ) async { if (adminHistoryLoanList.isEmpty) { diff --git a/lib/loan/ui/pages/admin_page/loan_card.dart b/lib/loan/ui/pages/admin_page/loan_card.dart index b23a42b94d..c4dfcb8d5b 100644 --- a/lib/loan/ui/pages/admin_page/loan_card.dart +++ b/lib/loan/ui/pages/admin_page/loan_card.dart @@ -12,7 +12,7 @@ import 'package:titan/l10n/app_localizations.dart'; class LoanCard extends StatelessWidget { final Loan loan; - final bool isSuperAdmin, isDetail, isHistory; + final bool isAdmin, isDetail, isHistory; final Function()? onEdit, onInfo; final Future Function()? onCalendar, onReturn; const LoanCard({ @@ -22,7 +22,7 @@ class LoanCard extends StatelessWidget { this.onCalendar, this.onReturn, this.onInfo, - this.isSuperAdmin = false, + this.isAdmin = false, this.isDetail = false, this.isHistory = false, }); @@ -33,14 +33,14 @@ class LoanCard extends StatelessWidget { DateTime.now().compareTo(loan.end) > 0 && !loan.returned; return GestureDetector( onTap: () { - if (isSuperAdmin || isHistory) { + if (isAdmin || isHistory) { onInfo?.call(); } }, child: CardLayout( id: loan.id, width: 250, - height: (isSuperAdmin && !isDetail) + height: (isAdmin && !isDetail) ? 170 : isHistory ? 120 @@ -56,7 +56,7 @@ class LoanCard extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (!isSuperAdmin && !isHistory) + if (!isAdmin && !isHistory) Column( children: [ const SizedBox(height: 10), @@ -90,7 +90,7 @@ class LoanCard extends StatelessWidget { const SizedBox(height: 5), ], ), - SizedBox(height: !isSuperAdmin && !isHistory ? 5 : 10), + SizedBox(height: !isAdmin && !isHistory ? 5 : 10), AutoSizeText( loan.borrower.getName(), maxLines: 1, @@ -155,7 +155,7 @@ class LoanCard extends StatelessWidget { ], ), const Spacer(), - if (isSuperAdmin) + if (isAdmin) Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ diff --git a/lib/loan/ui/pages/admin_page/on_going_loan.dart b/lib/loan/ui/pages/admin_page/on_going_loan.dart index 048fb3d32a..582f7dbe6b 100644 --- a/lib/loan/ui/pages/admin_page/on_going_loan.dart +++ b/lib/loan/ui/pages/admin_page/on_going_loan.dart @@ -106,7 +106,7 @@ class OnGoingLoan extends HookConsumerWidget { items: data, itemBuilder: (context, e, i) => LoanCard( loan: e, - isSuperAdmin: true, + isAdmin: true, onEdit: () async { await loanNotifier.setLoan(e); startNotifier.setStart(processDate(e.start)); diff --git a/lib/loan/ui/pages/main_page/main_page.dart b/lib/loan/ui/pages/main_page/main_page.dart index efac1e53e7..34b231bd45 100644 --- a/lib/loan/ui/pages/main_page/main_page.dart +++ b/lib/loan/ui/pages/main_page/main_page.dart @@ -25,7 +25,7 @@ class LoanMainPage extends HookConsumerWidget { final loanList = ref.watch(loanListProvider); final loanNotifier = ref.watch(loanProvider.notifier); final loanListNotifier = ref.watch(loanListProvider.notifier); - final isSuperAdmin = ref.watch(isLoanSuperAdminProvider); + final isAdmin = ref.watch(isLoanAdminProvider); ref.watch(adminLoanListProvider); ref.watch(itemListProvider); @@ -133,11 +133,11 @@ class LoanMainPage extends HookConsumerWidget { ], ), ), - if (isSuperAdmin) + if (isAdmin) Positioned( top: 30, right: 30, - child: SuperAdminButton( + child: AdminButton( onTap: () { QR.to(LoanRouter.root + LoanRouter.admin); }, diff --git a/lib/paiement/class/user_store.dart b/lib/paiement/class/user_store.dart index f134525f9e..f0d3db3c3c 100644 --- a/lib/paiement/class/user_store.dart +++ b/lib/paiement/class/user_store.dart @@ -55,7 +55,7 @@ class UserStore extends Store { bool? canSeeHistory, bool? canCancel, bool? canManageSellers, - bool? storeSuperAdmin, + bool? storeAdmin, }) { return UserStore( id: id ?? this.id, diff --git a/lib/paiement/providers/is_payment_admin.dart b/lib/paiement/providers/is_payment_admin.dart index d402106331..4fdca3f91d 100644 --- a/lib/paiement/providers/is_payment_admin.dart +++ b/lib/paiement/providers/is_payment_admin.dart @@ -1,7 +1,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/paiement/providers/my_structures_provider.dart'; -final isPaymentSuperAdminProvider = StateProvider((ref) { +final isPaymentAdminProvider = StateProvider((ref) { final myStructures = ref.watch(myStructuresProvider); return myStructures.isNotEmpty; }); diff --git a/lib/paiement/providers/new_admin_provider.dart b/lib/paiement/providers/new_admin_provider.dart index ced78a0d4b..50c98ec16a 100644 --- a/lib/paiement/providers/new_admin_provider.dart +++ b/lib/paiement/providers/new_admin_provider.dart @@ -1,19 +1,20 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/user/class/simple_users.dart'; -class NewSuperAdminNotifier extends StateNotifier { - NewSuperAdminNotifier() : super(SimpleUser.empty()); +class NewAdminNotifier extends StateNotifier { + NewAdminNotifier() : super(SimpleUser.empty()); - void updateNewSuperAdmin(SimpleUser newSuperAdmin) { - state = newSuperAdmin; + void updateNewAdmin(SimpleUser newAdmin) { + state = newAdmin; } - void resetNewSuperAdmin() { + void resetNewAdmin() { state = SimpleUser.empty(); } } -final newSuperAdminProvider = - StateNotifierProvider((ref) { - return NewSuperAdminNotifier(); - }); +final newAdminProvider = StateNotifierProvider(( + ref, +) { + return NewAdminNotifier(); +}); diff --git a/lib/paiement/repositories/funding_repository.dart b/lib/paiement/repositories/funding_repository.dart index 7c82cc86ec..aabe1d8dca 100644 --- a/lib/paiement/repositories/funding_repository.dart +++ b/lib/paiement/repositories/funding_repository.dart @@ -10,7 +10,7 @@ class FundingRepository extends Repository { // ignore: overridden_fields final ext = 'myeclpay/transfer/'; - Future getSuperAdminPaymentUrl(Transfer transfer) async { + Future getAdminPaymentUrl(Transfer transfer) async { return await create(transfer.toJson(), suffix: "admin"); } diff --git a/lib/paiement/ui/pages/admin_page/admin_page.dart b/lib/paiement/ui/pages/admin_page/admin_page.dart index 7a68cacb21..bdb566a73d 100644 --- a/lib/paiement/ui/pages/admin_page/admin_page.dart +++ b/lib/paiement/ui/pages/admin_page/admin_page.dart @@ -10,8 +10,8 @@ import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; import 'package:titan/tools/ui/widgets/align_left_text.dart'; -class SuperAdminPage extends ConsumerWidget { - const SuperAdminPage({super.key}); +class AdminPage extends ConsumerWidget { + const AdminPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -43,7 +43,7 @@ class SuperAdminPage extends ConsumerWidget { ); return Column( children: storeFromStructures - .map((store) => SuperAdminStoreCard(store: store)) + .map((store) => AdminStoreCard(store: store)) .toList(), ); }, diff --git a/lib/paiement/ui/pages/admin_page/admin_store_card.dart b/lib/paiement/ui/pages/admin_page/admin_store_card.dart index 47320e48ff..b0bbe20e4f 100644 --- a/lib/paiement/ui/pages/admin_page/admin_store_card.dart +++ b/lib/paiement/ui/pages/admin_page/admin_store_card.dart @@ -13,9 +13,9 @@ import 'package:titan/tools/ui/layouts/card_button.dart'; import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; import 'package:qlevar_router/qlevar_router.dart'; -class SuperAdminStoreCard extends ConsumerWidget { +class AdminStoreCard extends ConsumerWidget { final Store store; - const SuperAdminStoreCard({super.key, required this.store}); + const AdminStoreCard({super.key, required this.store}); @override Widget build(BuildContext context, WidgetRef ref) { diff --git a/lib/paiement/ui/pages/main_page/main_page.dart b/lib/paiement/ui/pages/main_page/main_page.dart index 1d1838a1f7..41b705ffc2 100644 --- a/lib/paiement/ui/pages/main_page/main_page.dart +++ b/lib/paiement/ui/pages/main_page/main_page.dart @@ -45,7 +45,7 @@ class PaymentMainPage extends HookConsumerWidget { final mySellersNotifier = ref.read(myStoresProvider.notifier); final myHistoryNotifier = ref.read(myHistoryProvider.notifier); final myWalletNotifier = ref.read(myWalletProvider.notifier); - final isSuperAdmin = ref.watch(isPaymentSuperAdminProvider); + final isAdmin = ref.watch(isPaymentAdminProvider); final flipped = useState(true); ref.listen(pathForwardingProvider, (previous, next) async { @@ -144,7 +144,7 @@ class PaymentMainPage extends HookConsumerWidget { AsyncChild( value: mySellers, builder: (context, mySellers) { - if (mySellers.isEmpty && !isSuperAdmin) { + if (mySellers.isEmpty && !isAdmin) { return SizedBox( height: 250, width: MediaQuery.of(context).size.width, diff --git a/lib/paiement/ui/pages/main_page/seller_card/store_admin_card.dart b/lib/paiement/ui/pages/main_page/seller_card/store_admin_card.dart index 5f0bb0a0fb..ca467b3dca 100644 --- a/lib/paiement/ui/pages/main_page/seller_card/store_admin_card.dart +++ b/lib/paiement/ui/pages/main_page/seller_card/store_admin_card.dart @@ -8,8 +8,8 @@ import 'package:titan/paiement/providers/selected_structure_provider.dart'; import 'package:titan/paiement/router.dart'; import 'package:qlevar_router/qlevar_router.dart'; -class StoreSuperAdminCard extends ConsumerWidget { - const StoreSuperAdminCard({super.key}); +class StoreAdminCard extends ConsumerWidget { + const StoreAdminCard({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { diff --git a/lib/paiement/ui/pages/main_page/seller_card/store_card.dart b/lib/paiement/ui/pages/main_page/seller_card/store_card.dart index 4f4d440f74..d3f93d3b63 100644 --- a/lib/paiement/ui/pages/main_page/seller_card/store_card.dart +++ b/lib/paiement/ui/pages/main_page/seller_card/store_card.dart @@ -63,8 +63,8 @@ class StoreCard extends HookConsumerWidget { colors: buttonGradient, icon: HeroIcons.userGroup, onPressed: () async { - // storeSuperAdminListNotifier.getStoreSuperAdminList(store.id); - QR.to(PaymentRouter.root + PaymentRouter.storeSuperAdmin); + // storeAdminListNotifier.getStoreAdminList(store.id); + QR.to(PaymentRouter.root + PaymentRouter.storeAdmin); }, title: AppLocalizations.of(context)!.paiementManagement, ), diff --git a/lib/paiement/ui/pages/main_page/seller_card/store_list.dart b/lib/paiement/ui/pages/main_page/seller_card/store_list.dart index eda0bd1afd..f3dbb2ea4a 100644 --- a/lib/paiement/ui/pages/main_page/seller_card/store_list.dart +++ b/lib/paiement/ui/pages/main_page/seller_card/store_list.dart @@ -16,7 +16,7 @@ class StoreList extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final stores = ref.watch(myStoresProvider); - final isSuperAdmin = ref.watch(isPaymentSuperAdminProvider); + final isAdmin = ref.watch(isPaymentAdminProvider); return SizedBox( height: maxHeight, child: SingleChildScrollView( @@ -49,11 +49,11 @@ class StoreList extends ConsumerWidget { } return Column( children: [ - if (isSuperAdmin) ...[ + if (isAdmin) ...[ StoreDivider( - name: AppLocalizations.of(context)!.paiementSuperAdmin, + name: AppLocalizations.of(context)!.paiementAdmin, ), - const StoreSuperAdminCard(), + const StoreAdminCard(), ], ...sortedByMembership.map((membership, stores) { final List alphabeticallyOrderedStores = stores diff --git a/lib/paiement/ui/pages/store_admin_page/search_result.dart b/lib/paiement/ui/pages/store_admin_page/search_result.dart index f6390aac40..8ffc540fb2 100644 --- a/lib/paiement/ui/pages/store_admin_page/search_result.dart +++ b/lib/paiement/ui/pages/store_admin_page/search_result.dart @@ -32,8 +32,8 @@ class SearchResult extends HookConsumerWidget { final store = ref.watch(selectedStoreProvider); final users = ref.watch(userList); final usersNotifier = ref.watch(userList.notifier); - final newSuperAdmin = ref.watch(newSuperAdminProvider); - final newSuperAdminNotifier = ref.watch(newSuperAdminProvider.notifier); + final newAdmin = ref.watch(newAdminProvider); + final newAdminNotifier = ref.watch(newAdminProvider.notifier); final sellerStoreNotifier = ref.watch( sellerStoreProvider(store.id).notifier, ); @@ -83,7 +83,7 @@ class SearchResult extends HookConsumerWidget { ), onYes: () async { await tokenExpireWrapper(ref, () async { - newSuperAdminNotifier.updateNewSuperAdmin(simpleUser); + newAdminNotifier.updateNewAdmin(simpleUser); queryController.text = simpleUser.getName(); Seller seller = Seller( storeId: store.id, @@ -107,7 +107,7 @@ class SearchResult extends HookConsumerWidget { queryController.clear(); usersNotifier.clear(); sellerRightsListNotifier.clearRights(); - newSuperAdminNotifier.resetNewSuperAdmin(); + newAdminNotifier.resetNewAdmin(); displayToastWithContext(TypeMsg.msg, addedSellerMsg); if (context.mounted) { Navigator.of(context).pop(); @@ -151,7 +151,7 @@ class SearchResult extends HookConsumerWidget { simpleUser.getName(), style: TextStyle( fontSize: 18, - fontWeight: simpleUser.id == newSuperAdmin.id + fontWeight: simpleUser.id == newAdmin.id ? FontWeight.bold : FontWeight.normal, ), diff --git a/lib/paiement/ui/pages/store_admin_page/seller_right_card.dart b/lib/paiement/ui/pages/store_admin_page/seller_right_card.dart index e7f0c4176f..65272ec9dc 100644 --- a/lib/paiement/ui/pages/store_admin_page/seller_right_card.dart +++ b/lib/paiement/ui/pages/store_admin_page/seller_right_card.dart @@ -33,9 +33,9 @@ class SellerRightCard extends ConsumerWidget { displayToast(context, type, msg); } - final amISuperAdmin = me.userId == store.structure.managerUser.id; + final amIAdmin = me.userId == store.structure.managerUser.id; - final isStructureSuperAdmin = + final isStructureAdmin = storeSeller.userId == store.structure.managerUser.id; final icons = @@ -72,7 +72,7 @@ class SellerRightCard extends ConsumerWidget { AppLocalizations.of(context)!.paiementSeeHistory, AppLocalizations.of(context)!.paiementCancelTransactions, AppLocalizations.of(context)!.paiementManageSellers, - AppLocalizations.of(context)!.paiementStructureSuperAdmin, + AppLocalizations.of(context)!.paiementStructureAdmin, ]; List sellerRights = [ @@ -89,7 +89,7 @@ class SellerRightCard extends ConsumerWidget { } } - if (isStructureSuperAdmin) { + if (isStructureAdmin) { rightsLabel.add(labels[4]); rightsIcons.add(icons[4]); } @@ -103,7 +103,7 @@ class SellerRightCard extends ConsumerWidget { context: context, backgroundColor: Colors.transparent, scrollControlDisabledMaxHeightRatio: - (((!amISuperAdmin || isStructureSuperAdmin) ? 80 : 100) + + (((!amIAdmin || isStructureAdmin) ? 80 : 100) + 45 * icons.length) / MediaQuery.of(context).size.height, builder: (context) { @@ -131,7 +131,7 @@ class SellerRightCard extends ConsumerWidget { ), const SizedBox(height: 10), for (var i = 0; i < icons.length; i++) - if (i < 4 || isStructureSuperAdmin) + if (i < 4 || isStructureAdmin) Padding( padding: const EdgeInsets.symmetric(vertical: 5), child: Row( @@ -146,8 +146,7 @@ class SellerRightCard extends ConsumerWidget { ), ), const Spacer(), - if (me.canManageSellers && - !isStructureSuperAdmin) + if (me.canManageSellers && !isStructureAdmin) Checkbox( value: sellerRights[i], activeColor: const Color(0xff204550), @@ -203,7 +202,7 @@ class SellerRightCard extends ConsumerWidget { ], ), ), - if (me.canManageSellers && !isStructureSuperAdmin) + if (me.canManageSellers && !isStructureAdmin) GestureDetector( onTap: () async { await showDialog( diff --git a/lib/paiement/ui/pages/store_admin_page/store_admin_page.dart b/lib/paiement/ui/pages/store_admin_page/store_admin_page.dart index 65c3c2907b..e5dc41f531 100644 --- a/lib/paiement/ui/pages/store_admin_page/store_admin_page.dart +++ b/lib/paiement/ui/pages/store_admin_page/store_admin_page.dart @@ -16,8 +16,8 @@ import 'package:titan/tools/ui/widgets/text_entry.dart'; import 'package:titan/user/providers/user_list_provider.dart'; import 'package:titan/user/providers/user_provider.dart'; -class StoreSuperAdminPage extends HookConsumerWidget { - const StoreSuperAdminPage({super.key}); +class StoreAdminPage extends HookConsumerWidget { + const StoreAdminPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { diff --git a/lib/ph/class/ph_admin.dart b/lib/ph/class/ph_admin.dart index 491151613f..1ca64bd695 100644 --- a/lib/ph/class/ph_admin.dart +++ b/lib/ph/class/ph_admin.dart @@ -1,14 +1,10 @@ -class PhSuperAdmin { - PhSuperAdmin({ - required this.name, - required this.groupManagerId, - required this.id, - }); +class PhAdmin { + PhAdmin({required this.name, required this.groupManagerId, required this.id}); late final String name; late final String groupManagerId; late final String id; - PhSuperAdmin.fromJson(Map json) { + PhAdmin.fromJson(Map json) { name = json['name']; groupManagerId = json['group_manager_id']; id = json['id']; @@ -22,15 +18,15 @@ class PhSuperAdmin { return data; } - PhSuperAdmin copyWith({String? name, String? groupManagerId, String? id}) { - return PhSuperAdmin( + PhAdmin copyWith({String? name, String? groupManagerId, String? id}) { + return PhAdmin( name: name ?? this.name, groupManagerId: groupManagerId ?? this.groupManagerId, id: id ?? this.id, ); } - PhSuperAdmin.empty() { + PhAdmin.empty() { name = ""; groupManagerId = ""; id = ""; @@ -38,6 +34,6 @@ class PhSuperAdmin { @override String toString() { - return 'PhSuperAdmin(name: $name, groupManagerId: $groupManagerId, id: $id)'; + return 'PhAdmin(name: $name, groupManagerId: $groupManagerId, id: $id)'; } } diff --git a/lib/ph/providers/is_ph_admin_provider.dart b/lib/ph/providers/is_ph_admin_provider.dart index 7459d621eb..dd9d26b1ea 100644 --- a/lib/ph/providers/is_ph_admin_provider.dart +++ b/lib/ph/providers/is_ph_admin_provider.dart @@ -1,7 +1,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/user/providers/user_provider.dart'; -final isPhSuperAdminProvider = StateProvider((ref) { +final isPhAdminProvider = StateProvider((ref) { final me = ref.watch(userProvider); for (final group in me.groups) { if (group.name == "ph") { diff --git a/lib/ph/ui/pages/admin_page/admin_page.dart b/lib/ph/ui/pages/admin_page/admin_page.dart index be14cfda85..f527a7229a 100644 --- a/lib/ph/ui/pages/admin_page/admin_page.dart +++ b/lib/ph/ui/pages/admin_page/admin_page.dart @@ -14,8 +14,8 @@ import 'package:titan/ph/ui/pages/ph.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/l10n/app_localizations.dart'; -class SuperAdminPage extends HookConsumerWidget { - const SuperAdminPage({super.key}); +class AdminPage extends HookConsumerWidget { + const AdminPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -26,9 +26,7 @@ class SuperAdminPage extends HookConsumerWidget { child: Column( children: [ const YearBar(), - const Expanded( - child: SingleChildScrollView(child: SuperAdminPhList()), - ), + const Expanded(child: SingleChildScrollView(child: AdminPhList())), const SizedBox(height: 20), GestureDetector( onTap: () { diff --git a/lib/ph/ui/pages/admin_page/admin_ph_card.dart b/lib/ph/ui/pages/admin_page/admin_ph_card.dart index c6844ce692..63eb319714 100644 --- a/lib/ph/ui/pages/admin_page/admin_ph_card.dart +++ b/lib/ph/ui/pages/admin_page/admin_ph_card.dart @@ -6,10 +6,10 @@ import 'package:titan/tools/ui/layouts/card_button.dart'; import 'package:titan/tools/ui/layouts/card_layout.dart'; import 'package:titan/l10n/app_localizations.dart'; -class SuperAdminPhCard extends StatelessWidget { +class AdminPhCard extends StatelessWidget { final VoidCallback onEdit, onDelete; final Ph ph; - const SuperAdminPhCard({ + const AdminPhCard({ super.key, required this.ph, required this.onEdit, diff --git a/lib/ph/ui/pages/admin_page/admin_ph_list.dart b/lib/ph/ui/pages/admin_page/admin_ph_list.dart index 153d7fd01d..fbdf19236f 100644 --- a/lib/ph/ui/pages/admin_page/admin_ph_list.dart +++ b/lib/ph/ui/pages/admin_page/admin_ph_list.dart @@ -10,8 +10,8 @@ import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/l10n/app_localizations.dart'; -class SuperAdminPhList extends HookConsumerWidget { - const SuperAdminPhList({super.key}); +class AdminPhList extends HookConsumerWidget { + const AdminPhList({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -30,7 +30,7 @@ class SuperAdminPhList extends HookConsumerWidget { return Column( children: list .map( - (ph) => SuperAdminPhCard( + (ph) => AdminPhCard( ph: ph, onEdit: () { QR.to(PhRouter.root + PhRouter.admin + PhRouter.add_ph); diff --git a/lib/ph/ui/pages/main_page/main_page.dart b/lib/ph/ui/pages/main_page/main_page.dart index 845f40d3d0..7ef6ec2ba4 100644 --- a/lib/ph/ui/pages/main_page/main_page.dart +++ b/lib/ph/ui/pages/main_page/main_page.dart @@ -19,16 +19,16 @@ class PhMainPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final isSuperAdmin = ref.watch(isPhSuperAdminProvider); + final isAdmin = ref.watch(isPhAdminProvider); final phList = ref.watch(phListProvider); return PhTemplate( child: Column( children: [ - if (isSuperAdmin) + if (isAdmin) SizedBox( width: 116.7, - child: SuperAdminButton( + child: AdminButton( onTap: () { QR.to(PhRouter.root + PhRouter.admin); }, diff --git a/lib/phonebook/providers/phonebook_admin_provider.dart b/lib/phonebook/providers/phonebook_admin_provider.dart index 38d89bdb6c..4c60f68254 100644 --- a/lib/phonebook/providers/phonebook_admin_provider.dart +++ b/lib/phonebook/providers/phonebook_admin_provider.dart @@ -5,7 +5,7 @@ import 'package:titan/phonebook/providers/association_provider.dart'; import 'package:titan/phonebook/tools/constants.dart'; import 'package:titan/user/providers/user_provider.dart'; -final isPhonebookSuperAdminProvider = StateProvider((ref) { +final isPhonebookAdminProvider = StateProvider((ref) { final user = ref.watch(userProvider); if (user.groups .map((e) => e.id) @@ -18,10 +18,10 @@ final isPhonebookSuperAdminProvider = StateProvider((ref) { return false; }); -final hasPhonebookSuperAdminAccessProvider = StateProvider((ref) { - final isPhonebookSuperAdmin = ref.watch(isPhonebookSuperAdminProvider); - final isSuperAdmin = ref.watch(isSuperAdminProvider); - return isPhonebookSuperAdmin || isSuperAdmin; +final hasPhonebookAdminAccessProvider = StateProvider((ref) { + final isPhonebookAdmin = ref.watch(isPhonebookAdminProvider); + final isAdmin = ref.watch(isAdminProvider); + return isPhonebookAdmin || isAdmin; }); final isAssociationPresidentProvider = StateProvider((ref) { diff --git a/lib/phonebook/ui/pages/admin_page/admin_page.dart b/lib/phonebook/ui/pages/admin_page/admin_page.dart index 4f172e7a86..e4637bc39a 100644 --- a/lib/phonebook/ui/pages/admin_page/admin_page.dart +++ b/lib/phonebook/ui/pages/admin_page/admin_page.dart @@ -21,8 +21,8 @@ import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/l10n/app_localizations.dart'; -class SuperAdminPage extends HookConsumerWidget { - const SuperAdminPage({super.key}); +class AdminPage extends HookConsumerWidget { + const AdminPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -32,7 +32,7 @@ class SuperAdminPage extends HookConsumerWidget { final associationList = ref.watch(associationListProvider); final associationFilteredList = ref.watch(associationFilteredListProvider); final roleNotifier = ref.watch(rolesTagsProvider.notifier); - final isPhonebookSuperAdmin = ref.watch(isPhonebookSuperAdminProvider); + final isPhonebookAdmin = ref.watch(isPhonebookAdminProvider); void displayToastWithContext(TypeMsg type, String msg) { displayToast(context, type, msg); } @@ -57,7 +57,7 @@ class SuperAdminPage extends HookConsumerWidget { children: [ KindsBar(), GestureDetector( - onTap: isPhonebookSuperAdmin + onTap: isPhonebookAdmin ? () { QR.to( PhonebookRouter.root + @@ -75,7 +75,7 @@ class SuperAdminPage extends HookConsumerWidget { ), width: double.infinity, height: 100, - color: isPhonebookSuperAdmin + color: isPhonebookAdmin ? Colors.white : ColorConstants.deactivated2, child: Center( @@ -102,7 +102,7 @@ class SuperAdminPage extends HookConsumerWidget { padding: const EdgeInsets.symmetric(horizontal: 20), child: EditableAssociationCard( association: association, - isPhonebookSuperAdmin: isPhonebookSuperAdmin, + isPhonebookAdmin: isPhonebookAdmin, onEdit: () { kindNotifier.setKind(association.kind); associationNotifier.setAssociation(association); diff --git a/lib/phonebook/ui/pages/admin_page/editable_association_card.dart b/lib/phonebook/ui/pages/admin_page/editable_association_card.dart index 9fef943ac6..e2fe0d5b2a 100644 --- a/lib/phonebook/ui/pages/admin_page/editable_association_card.dart +++ b/lib/phonebook/ui/pages/admin_page/editable_association_card.dart @@ -6,13 +6,13 @@ import 'package:titan/phonebook/ui/pages/admin_page/edition_button.dart'; class EditableAssociationCard extends HookConsumerWidget { final Association association; - final bool isPhonebookSuperAdmin; + final bool isPhonebookAdmin; final void Function() onEdit; final Future Function() onDelete; const EditableAssociationCard({ super.key, required this.association, - required this.isPhonebookSuperAdmin, + required this.isPhonebookAdmin, required this.onEdit, required this.onDelete, }); @@ -63,7 +63,7 @@ class EditableAssociationCard extends HookConsumerWidget { const SizedBox(width: 5), DeleteButton( onDelete: onDelete, - deactivated: !isPhonebookSuperAdmin, + deactivated: !isPhonebookAdmin, deletion: association.deactivated, ), ], diff --git a/lib/phonebook/ui/pages/association_editor_page/association_editor_page.dart b/lib/phonebook/ui/pages/association_editor_page/association_editor_page.dart index 7797e5996f..641db46f21 100644 --- a/lib/phonebook/ui/pages/association_editor_page/association_editor_page.dart +++ b/lib/phonebook/ui/pages/association_editor_page/association_editor_page.dart @@ -51,7 +51,7 @@ class AssociationEditorPage extends HookConsumerWidget { final membershipNotifier = ref.watch(membershipProvider.notifier); final completeMemberNotifier = ref.watch(completeMemberProvider.notifier); final memberRoleTagsNotifier = ref.watch(memberRoleTagsProvider.notifier); - final isPhonebookSuperAdmin = ref.watch(isPhonebookSuperAdminProvider); + final isPhonebookAdmin = ref.watch(isPhonebookAdminProvider); final isAssociationPresident = ref.watch(isAssociationPresidentProvider); final kindNotifier = ref.watch(associationKindProvider.notifier); @@ -100,7 +100,7 @@ class AssociationEditorPage extends HookConsumerWidget { height: 40, decoration: BoxDecoration( color: - (isPhonebookSuperAdmin || isAssociationPresident) && + (isPhonebookAdmin || isAssociationPresident) && !association.deactivated ? ColorConstants.gradient1 : ColorConstants.deactivated1, @@ -109,7 +109,7 @@ class AssociationEditorPage extends HookConsumerWidget { child: child, ), onTap: - (isPhonebookSuperAdmin || isAssociationPresident) && + (isPhonebookAdmin || isAssociationPresident) && !association.deactivated ? () async { rolesTagsNotifier.resetChecked(); @@ -156,7 +156,7 @@ class AssociationEditorPage extends HookConsumerWidget { builder: (context, associationMembers) => associationMembers.isEmpty ? Text(AppLocalizations.of(context)!.phonebookNoMember) - : (isPhonebookSuperAdmin || isAssociationPresident) && + : (isPhonebookAdmin || isAssociationPresident) && !association.deactivated ? SizedBox( height: 400, @@ -238,7 +238,7 @@ class AssociationEditorPage extends HookConsumerWidget { padding: const EdgeInsets.symmetric(horizontal: 30), child: WaitingButton( builder: (child) => AddEditButtonLayout( - colors: isPhonebookSuperAdmin && !association.deactivated + colors: isPhonebookAdmin && !association.deactivated ? [ColorConstants.gradient1, ColorConstants.gradient2] : [ ColorConstants.deactivated1, @@ -246,7 +246,7 @@ class AssociationEditorPage extends HookConsumerWidget { ], child: child, ), - onTap: isPhonebookSuperAdmin && !association.deactivated + onTap: isPhonebookAdmin && !association.deactivated ? () async { showDialog( context: context, diff --git a/lib/phonebook/ui/pages/association_editor_page/association_information_editor.dart b/lib/phonebook/ui/pages/association_editor_page/association_information_editor.dart index d39bb721e3..030f2bf7ab 100644 --- a/lib/phonebook/ui/pages/association_editor_page/association_information_editor.dart +++ b/lib/phonebook/ui/pages/association_editor_page/association_information_editor.dart @@ -32,8 +32,8 @@ class AssociationInformationEditor extends HookConsumerWidget { final name = useTextEditingController(text: association.name); final description = useTextEditingController(text: association.description); final associationListNotifier = ref.watch(associationListProvider.notifier); - final isSuperAdmin = ref.watch(isSuperAdminProvider); - final isPhonebookSuperAdmin = ref.watch(isPhonebookSuperAdminProvider); + final isAdmin = ref.watch(isAdminProvider); + final isPhonebookAdmin = ref.watch(isPhonebookAdminProvider); final groups = ref.watch(allGroupListProvider); List selectedGroups = groups.maybeWhen( @@ -50,7 +50,7 @@ class AssociationInformationEditor extends HookConsumerWidget { return Column( children: [ - isPhonebookSuperAdmin && !association.deactivated + isPhonebookAdmin && !association.deactivated ? Form( key: key, child: Column( @@ -251,7 +251,7 @@ class AssociationInformationEditor extends HookConsumerWidget { ], ), ), - if (isSuperAdmin && !association.deactivated) + if (isAdmin && !association.deactivated) Padding( padding: const EdgeInsets.symmetric(horizontal: 30), child: Column( diff --git a/lib/phonebook/ui/pages/main_page/main_page.dart b/lib/phonebook/ui/pages/main_page/main_page.dart index e5c8f74315..6cbe231b83 100644 --- a/lib/phonebook/ui/pages/main_page/main_page.dart +++ b/lib/phonebook/ui/pages/main_page/main_page.dart @@ -23,8 +23,8 @@ class PhonebookMainPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final isPhonebookSuperAdmin = ref.watch(isPhonebookSuperAdminProvider); - final isSuperAdmin = ref.watch(isSuperAdminProvider); + final isPhonebookAdmin = ref.watch(isPhonebookAdminProvider); + final isAdmin = ref.watch(isAdminProvider); final associationNotifier = ref.watch(associationProvider.notifier); final associationListNotifier = ref.watch(associationListProvider.notifier); final associationList = ref.watch(associationListProvider); @@ -47,10 +47,10 @@ class PhonebookMainPage extends HookConsumerWidget { child: Row( children: [ const ResearchBar(), - if (isPhonebookSuperAdmin || isSuperAdmin) + if (isPhonebookAdmin || isAdmin) Padding( padding: const EdgeInsets.only(left: 20), - child: SuperAdminButton( + child: AdminButton( onTap: () { kindNotifier.setKind(''); QR.to(PhonebookRouter.root + PhonebookRouter.admin); diff --git a/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart b/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart index 8ed291f8a8..f1004d81d1 100644 --- a/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart +++ b/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart @@ -47,7 +47,7 @@ class MembershipEditorPage extends HookConsumerWidget { text: membership.apparentName, ); final associationMembers = ref.watch(associationMemberListProvider); - final isPhonebookSuperAdmin = ref.watch(isPhonebookSuperAdminProvider); + final isPhonebookAdmin = ref.watch(isPhonebookAdminProvider); void displayToastWithContext(TypeMsg type, String msg) { displayToast(context, type, msg); @@ -113,12 +113,12 @@ class MembershipEditorPage extends HookConsumerWidget { ), fillColor: rolesTagList.keys.first == tagKey && - !isPhonebookSuperAdmin + !isPhonebookAdmin ? WidgetStateProperty.all(Colors.black) : WidgetStateProperty.all(Colors.grey), onChanged: rolesTagList.keys.first == tagKey && - !isPhonebookSuperAdmin + !isPhonebookAdmin ? null : (value) { rolesTagList[tagKey] = AsyncData([value!]); diff --git a/lib/purchases/providers/purchases_admin_provider.dart b/lib/purchases/providers/purchases_admin_provider.dart index d87dd50ff8..121276b225 100644 --- a/lib/purchases/providers/purchases_admin_provider.dart +++ b/lib/purchases/providers/purchases_admin_provider.dart @@ -2,7 +2,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/purchases/providers/seller_list_provider.dart'; import 'package:titan/user/providers/user_provider.dart'; -final isPurchasesSuperAdminProvider = StateProvider((ref) { +final isPurchasesAdminProvider = StateProvider((ref) { final user = ref.watch(userProvider); final sellers = ref.watch(sellerListProvider); if (user.groups diff --git a/lib/purchases/ui/pages/main_page/custom_button.dart b/lib/purchases/ui/pages/main_page/custom_button.dart index 37f946e98c..b53f37d723 100644 --- a/lib/purchases/ui/pages/main_page/custom_button.dart +++ b/lib/purchases/ui/pages/main_page/custom_button.dart @@ -13,7 +13,7 @@ class CustomButton extends StatelessWidget { required this.onTap, this.textColor = Colors.white, this.color = Colors.black, - this.text = 'SuperAdmin', + this.text = 'Admin', this.colors, required this.icon, }); diff --git a/lib/purchases/ui/pages/main_page/main_page.dart b/lib/purchases/ui/pages/main_page/main_page.dart index 18a3031ab9..3b55560b5e 100644 --- a/lib/purchases/ui/pages/main_page/main_page.dart +++ b/lib/purchases/ui/pages/main_page/main_page.dart @@ -20,7 +20,7 @@ class PurchasesMainPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final isSuperAdmin = ref.watch(isPurchasesSuperAdminProvider); + final isAdmin = ref.watch(isPurchasesAdminProvider); final ticketList = ref.watch(ticketListProvider); final ticketListNotifier = ref.watch(ticketListProvider.notifier); final ticketNotifier = ref.watch(ticketProvider.notifier); @@ -44,7 +44,7 @@ class PurchasesMainPage extends HookConsumerWidget { QR.to(PurchasesRouter.root + PurchasesRouter.history); }, ), - if (isSuperAdmin) + if (isAdmin) CustomButton( icon: HeroIcons.viewfinderCircle, text: AppLocalizations.of(context)!.purchasesScan, diff --git a/lib/raffle/providers/is_raffle_admin.dart b/lib/raffle/providers/is_raffle_admin.dart index 25acc786ed..8aca22f6d8 100644 --- a/lib/raffle/providers/is_raffle_admin.dart +++ b/lib/raffle/providers/is_raffle_admin.dart @@ -1,7 +1,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/user/providers/user_provider.dart'; -final isRaffleSuperAdminProvider = StateProvider((ref) { +final isRaffleAdminProvider = StateProvider((ref) { final me = ref.watch(userProvider); return me.groups .map((e) => e.id) diff --git a/lib/raffle/ui/pages/admin_module_page/admin_module_page.dart b/lib/raffle/ui/pages/admin_module_page/admin_module_page.dart index 5b4793890c..ffe4e75bcb 100644 --- a/lib/raffle/ui/pages/admin_module_page/admin_module_page.dart +++ b/lib/raffle/ui/pages/admin_module_page/admin_module_page.dart @@ -6,8 +6,8 @@ import 'package:titan/raffle/ui/pages/admin_module_page/tombola_handler.dart'; import 'package:titan/raffle/ui/raffle.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; -class SuperAdminModulePage extends HookConsumerWidget { - const SuperAdminModulePage({super.key}); +class AdminModulePage extends HookConsumerWidget { + const AdminModulePage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { diff --git a/lib/raffle/ui/pages/main_page/main_page.dart b/lib/raffle/ui/pages/main_page/main_page.dart index 9a901a8c89..1b884e584c 100644 --- a/lib/raffle/ui/pages/main_page/main_page.dart +++ b/lib/raffle/ui/pages/main_page/main_page.dart @@ -29,7 +29,7 @@ class RaffleMainPage extends HookConsumerWidget { final raffleListNotifier = ref.watch(raffleListProvider.notifier); final userTicketList = ref.watch(userTicketListProvider); final userTicketListNotifier = ref.watch(userTicketListProvider.notifier); - final isSuperAdmin = ref.watch(isRaffleSuperAdminProvider); + final isAdmin = ref.watch(isRaffleAdminProvider); final tombolaLogosNotifier = ref.watch(tombolaLogosProvider.notifier); final rafflesStatus = {}; @@ -57,8 +57,8 @@ class RaffleMainPage extends HookConsumerWidget { SectionTitle( text: AppLocalizations.of(context)!.raffleTickets, ), - if (isSuperAdmin) - SuperAdminButton( + if (isAdmin) + AdminButton( onTap: () { QR.to(RaffleRouter.root + RaffleRouter.admin); }, diff --git a/lib/recommendation/providers/is_recommendation_admin_provider.dart b/lib/recommendation/providers/is_recommendation_admin_provider.dart index fcf2a79d9f..343a990199 100644 --- a/lib/recommendation/providers/is_recommendation_admin_provider.dart +++ b/lib/recommendation/providers/is_recommendation_admin_provider.dart @@ -1,7 +1,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/user/providers/user_provider.dart'; -final isRecommendationSuperAdminProvider = StateProvider((ref) { +final isRecommendationAdminProvider = StateProvider((ref) { final me = ref.watch(userProvider); return me.groups .map((e) => e.id) diff --git a/lib/recommendation/ui/pages/main_page.dart b/lib/recommendation/ui/pages/main_page.dart index f1fbca7f84..dd0f782c83 100644 --- a/lib/recommendation/ui/pages/main_page.dart +++ b/lib/recommendation/ui/pages/main_page.dart @@ -18,9 +18,7 @@ class RecommendationMainPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final isRecommendationSuperAdmin = ref.watch( - isRecommendationSuperAdminProvider, - ); + final isRecommendationAdmin = ref.watch(isRecommendationAdminProvider); final recommendationNotifier = ref.watch(recommendationProvider.notifier); final recommendationList = ref.watch(recommendationListProvider); final recommendationListNotifier = ref.watch( @@ -37,7 +35,7 @@ class RecommendationMainPage extends HookConsumerWidget { builder: (context, data) => Column( children: [ const SizedBox(height: 30), - if (isRecommendationSuperAdmin) + if (isRecommendationAdmin) GestureDetector( onTap: () { recommendationNotifier.setRecommendation( diff --git a/lib/recommendation/ui/widgets/recommendation_card.dart b/lib/recommendation/ui/widgets/recommendation_card.dart index f907b961c8..66880d7610 100644 --- a/lib/recommendation/ui/widgets/recommendation_card.dart +++ b/lib/recommendation/ui/widgets/recommendation_card.dart @@ -30,9 +30,7 @@ class RecommendationCard extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final isRecommendationSuperAdmin = ref.watch( - isRecommendationSuperAdminProvider, - ); + final isRecommendationAdmin = ref.watch(isRecommendationAdminProvider); final recommendationNotifier = ref.watch(recommendationProvider.notifier); final recommendationListNotifier = ref.watch( recommendationListProvider.notifier, @@ -139,7 +137,7 @@ class RecommendationCard extends HookConsumerWidget { ) : SizedBox( width: 50, - child: isRecommendationSuperAdmin + child: isRecommendationAdmin ? Column( children: [ GestureDetector( diff --git a/lib/seed-library/providers/is_seed_library_admin_provider.dart b/lib/seed-library/providers/is_seed_library_admin_provider.dart index 08422a7964..2fbe5945f5 100644 --- a/lib/seed-library/providers/is_seed_library_admin_provider.dart +++ b/lib/seed-library/providers/is_seed_library_admin_provider.dart @@ -1,7 +1,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/user/providers/user_provider.dart'; -final isSeedLibrarySuperAdminProvider = StateProvider((ref) { +final isSeedLibraryAdminProvider = StateProvider((ref) { final me = ref.watch(userProvider); return me.groups .map((e) => e.id) diff --git a/lib/seed-library/repositories/plants_repository.dart b/lib/seed-library/repositories/plants_repository.dart index aaa14cfe85..a9908c90e5 100644 --- a/lib/seed-library/repositories/plants_repository.dart +++ b/lib/seed-library/repositories/plants_repository.dart @@ -24,7 +24,7 @@ class PlantsRepository extends Repository { return PlantSimple.fromJson(await getOne(plantsId)); } - Future> getListPlantSimpleSuperAdmin(String userId) async { + Future> getListPlantSimpleAdmin(String userId) async { return List.from( (await getList( suffix: "users/$userId", diff --git a/lib/seed-library/ui/pages/information_page/text.dart b/lib/seed-library/ui/pages/information_page/text.dart index 41dc351994..db68ad8f2f 100644 --- a/lib/seed-library/ui/pages/information_page/text.dart +++ b/lib/seed-library/ui/pages/information_page/text.dart @@ -14,7 +14,7 @@ class InformationPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final information = ref.watch(informationProvider); - final isSeedLibrarySuperAdmin = ref.watch(isSeedLibrarySuperAdminProvider); + final isSeedLibraryAdmin = ref.watch(isSeedLibraryAdminProvider); return SeedLibraryTemplate( child: SingleChildScrollView( @@ -25,7 +25,7 @@ class InformationPage extends HookConsumerWidget { value: information, builder: (context, info) => Column( children: [ - if (isSeedLibrarySuperAdmin) + if (isSeedLibraryAdmin) GestureDetector( onTap: () { QR.to( diff --git a/lib/seed-library/ui/pages/main_page/main_page.dart b/lib/seed-library/ui/pages/main_page/main_page.dart index bfba111b30..7a2906afbe 100644 --- a/lib/seed-library/ui/pages/main_page/main_page.dart +++ b/lib/seed-library/ui/pages/main_page/main_page.dart @@ -22,7 +22,7 @@ class SeedLibraryMainPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final isSeedLibrarySuperAdmin = ref.watch(isSeedLibrarySuperAdminProvider); + final isSeedLibraryAdmin = ref.watch(isSeedLibraryAdminProvider); final information = ref.watch(syncInformationProvider); final speciesNotifier = ref.watch(speciesProvider.notifier); final seasonNotifier = ref.watch(seasonFilterProvider.notifier); @@ -56,7 +56,7 @@ class SeedLibraryMainPage extends HookConsumerWidget { : 1.5, ), children: [ - if (isSeedLibrarySuperAdmin) + if (isSeedLibraryAdmin) GestureDetector( onTap: () { resetNotifier(); diff --git a/lib/seed-library/ui/pages/plant_deposit_page/plant_deposit_page.dart b/lib/seed-library/ui/pages/plant_deposit_page/plant_deposit_page.dart index dbcf19c048..749b2329fa 100644 --- a/lib/seed-library/ui/pages/plant_deposit_page/plant_deposit_page.dart +++ b/lib/seed-library/ui/pages/plant_deposit_page/plant_deposit_page.dart @@ -30,7 +30,7 @@ class PlantDepositPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final seedLibrarySuperAdmin = ref.watch(isSeedLibrarySuperAdminProvider); + final seedLibraryAdmin = ref.watch(isSeedLibraryAdminProvider); final key = GlobalKey(); final scrollController = useScrollController(); final species = ref.watch(syncSpeciesListProvider); @@ -66,7 +66,7 @@ class PlantDepositPage extends HookConsumerWidget { physics: const AlwaysScrollableScrollPhysics( parent: BouncingScrollPhysics(), ), - child: (myPlants.isEmpty && !seedLibrarySuperAdmin) + child: (myPlants.isEmpty && !seedLibraryAdmin) ? const Center( child: Text( SeedLibraryTextConstants.depositNotAvailable, @@ -121,7 +121,7 @@ class PlantDepositPage extends HookConsumerWidget { ], ), ), - if (selectedAncestor.id == '' && seedLibrarySuperAdmin) ...[ + if (selectedAncestor.id == '' && seedLibraryAdmin) ...[ Text( SeedLibraryTextConstants.speciesSimple, style: TextStyle( @@ -206,7 +206,7 @@ class PlantDepositPage extends HookConsumerWidget { } if (selectedAncestor.id == '' && selectedSpecies.id == '') { - if (seedLibrarySuperAdmin) { + if (seedLibraryAdmin) { displayToastWithContext( TypeMsg.error, SeedLibraryTextConstants diff --git a/lib/settings/providers/module_list_provider.dart b/lib/settings/providers/module_list_provider.dart index 0ddfb4372e..74a70e1c4b 100644 --- a/lib/settings/providers/module_list_provider.dart +++ b/lib/settings/providers/module_list_provider.dart @@ -33,9 +33,9 @@ final modulesProvider = StateNotifierProvider>(( .map((root) => '/$root') .toList(); - final isSuperAdmin = ref.watch(isSuperAdminProvider); + final isAdmin = ref.watch(isAdminProvider); - ModulesNotifier modulesNotifier = ModulesNotifier(isSuperAdmin: isSuperAdmin); + ModulesNotifier modulesNotifier = ModulesNotifier(isAdmin: isAdmin); modulesNotifier.loadModules(myModulesRoot); return modulesNotifier; }); @@ -43,7 +43,7 @@ final modulesProvider = StateNotifierProvider>(( class ModulesNotifier extends StateNotifier> { String dbModule = "modules"; String dbAllModules = "allModules"; - final bool isSuperAdmin; + final bool isAdmin; final eq = const DeepCollectionEquality.unordered(); List allModules = [ HomeRouter.module, @@ -64,7 +64,7 @@ class ModulesNotifier extends StateNotifier> { SeedLibraryRouter.module, AdminRouter.module, ]; - ModulesNotifier({required this.isSuperAdmin}) : super([]); + ModulesNotifier({required this.isAdmin}) : super([]); void saveModules() { SharedPreferences.getInstance().then((prefs) { @@ -128,10 +128,7 @@ class ModulesNotifier extends StateNotifier> { for (Module module in toDelete) { allModules.remove(module); } - allModules.addAll([ - SettingsRouter.module, - if (isSuperAdmin) SuperAdminRouter.module, - ]); + allModules.addAll([SettingsRouter.module, if (isAdmin) AdminRouter.module]); state = allModules; } diff --git a/lib/tools/middlewares/admin_middleware.dart b/lib/tools/middlewares/admin_middleware.dart index fc67c06f23..ee7262c440 100644 --- a/lib/tools/middlewares/admin_middleware.dart +++ b/lib/tools/middlewares/admin_middleware.dart @@ -2,14 +2,14 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/router.dart'; import 'package:qlevar_router/qlevar_router.dart'; -class SuperAdminMiddleware extends QMiddleware { - final StateProvider isSuperAdminProvider; +class AdminMiddleware extends QMiddleware { + final StateProvider isAdminProvider; final Ref ref; - SuperAdminMiddleware(this.ref, this.isSuperAdminProvider); + AdminMiddleware(this.ref, this.isAdminProvider); @override Future redirectGuard(String path) async { - return ref.watch(isSuperAdminProvider) ? null : AppRouter.root; + return ref.watch(isAdminProvider) ? null : AppRouter.root; } } diff --git a/lib/tools/ui/styleguide/styleguide_page.dart b/lib/tools/ui/styleguide/styleguide_page.dart index 55e5270212..fb5bddc753 100644 --- a/lib/tools/ui/styleguide/styleguide_page.dart +++ b/lib/tools/ui/styleguide/styleguide_page.dart @@ -863,10 +863,10 @@ class StyleGuidePage extends HookConsumerWidget { height: 70, child: HorizontalMultiSelect>( items: const [ - {"name": "John", "role": "SuperAdmin"}, + {"name": "John", "role": "Admin"}, {"name": "Emma", "role": "Editor"}, {"name": "Michael", "role": "Viewer"}, - {"name": "Sarah", "role": "SuperAdmin"}, + {"name": "Sarah", "role": "Admin"}, {"name": "David", "role": "Editor"}, ], itemBuilder: (context, user, index, selected) { diff --git a/lib/tools/ui/widgets/admin_button.dart b/lib/tools/ui/widgets/admin_button.dart index f735310f21..6db064ab3b 100644 --- a/lib/tools/ui/widgets/admin_button.dart +++ b/lib/tools/ui/widgets/admin_button.dart @@ -1,18 +1,18 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; -class SuperAdminButton extends StatelessWidget { +class AdminButton extends StatelessWidget { final VoidCallback onTap; final Color textColor; final Color? color; final List? colors; final String text; - const SuperAdminButton({ + const AdminButton({ super.key, required this.onTap, this.textColor = Colors.white, this.color = Colors.black, - this.text = "SuperAdmin", + this.text = "Admin", this.colors, }); diff --git a/lib/vote/providers/is_vote_admin_provider.dart b/lib/vote/providers/is_vote_admin_provider.dart index ce3bd950f5..f7873020f9 100644 --- a/lib/vote/providers/is_vote_admin_provider.dart +++ b/lib/vote/providers/is_vote_admin_provider.dart @@ -1,7 +1,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/user/providers/user_provider.dart'; -final isVoteSuperAdminProvider = StateProvider((ref) { +final isVoteAdminProvider = StateProvider((ref) { final me = ref.watch(userProvider); return me.groups .map((e) => e.id) diff --git a/lib/vote/ui/components/member_card.dart b/lib/vote/ui/components/member_card.dart index 3e6f38b151..0a9f7924fc 100644 --- a/lib/vote/ui/components/member_card.dart +++ b/lib/vote/ui/components/member_card.dart @@ -8,13 +8,13 @@ import 'package:titan/vote/class/members.dart'; class MemberCard extends StatelessWidget { final Member member; final Function()? onEdit, onDelete; - final bool isSuperAdmin; + final bool isAdmin; const MemberCard({ super.key, required this.member, this.onEdit, this.onDelete, - this.isSuperAdmin = false, + this.isAdmin = false, }); @override @@ -22,7 +22,7 @@ class MemberCard extends StatelessWidget { return CardLayout( id: member.id, width: 150, - height: isSuperAdmin ? 145 : 110, + height: isAdmin ? 145 : 110, margin: const EdgeInsets.all(10), padding: const EdgeInsets.symmetric(horizontal: 17.0), child: Column( @@ -54,7 +54,7 @@ class MemberCard extends StatelessWidget { ), ), const SizedBox(height: 2), - if (!isSuperAdmin) const Spacer(), + if (!isAdmin) const Spacer(), AutoSizeText( member.role, maxLines: 1, @@ -66,8 +66,8 @@ class MemberCard extends StatelessWidget { color: Colors.black, ), ), - if (isSuperAdmin) const Spacer(), - if (isSuperAdmin) + if (isAdmin) const Spacer(), + if (isAdmin) Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -91,7 +91,7 @@ class MemberCard extends StatelessWidget { ), ], ), - SizedBox(height: isSuperAdmin ? 10 : 15), + SizedBox(height: isAdmin ? 10 : 15), ], ), ); diff --git a/lib/vote/ui/pages/admin_page/admin_button.dart b/lib/vote/ui/pages/admin_page/admin_button.dart index d02cf58b1b..80af1215d2 100644 --- a/lib/vote/ui/pages/admin_page/admin_button.dart +++ b/lib/vote/ui/pages/admin_page/admin_button.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; -class SuperAdminButton extends StatelessWidget { +class AdminButton extends StatelessWidget { final Widget child; - const SuperAdminButton({super.key, required this.child}); + const AdminButton({super.key, required this.child}); @override Widget build(BuildContext context) { diff --git a/lib/vote/ui/pages/admin_page/admin_page.dart b/lib/vote/ui/pages/admin_page/admin_page.dart index 393d0b63dc..bd5ea02dac 100644 --- a/lib/vote/ui/pages/admin_page/admin_page.dart +++ b/lib/vote/ui/pages/admin_page/admin_page.dart @@ -28,8 +28,8 @@ import 'package:titan/vote/ui/pages/admin_page/voters_bar.dart'; import 'package:titan/vote/ui/vote.dart'; import 'package:titan/l10n/app_localizations.dart'; -class SuperAdminPage extends HookConsumerWidget { - const SuperAdminPage({super.key}); +class AdminPage extends HookConsumerWidget { + const AdminPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -132,8 +132,7 @@ class SuperAdminPage extends HookConsumerWidget { ), ), WaitingButton( - builder: (child) => - SuperAdminButton(child: child), + builder: (child) => AdminButton(child: child), onTap: () async { await showDialog( context: context, @@ -168,7 +167,7 @@ class SuperAdminPage extends HookConsumerWidget { if (status == Status.counting || status == Status.published) WaitingButton( - builder: (child) => SuperAdminButton(child: child), + builder: (child) => AdminButton(child: child), onTap: () async { await showDialog( context: context, diff --git a/lib/vote/ui/pages/admin_page/contender_card.dart b/lib/vote/ui/pages/admin_page/contender_card.dart index 48cba8da18..4cf55d1e8e 100644 --- a/lib/vote/ui/pages/admin_page/contender_card.dart +++ b/lib/vote/ui/pages/admin_page/contender_card.dart @@ -16,7 +16,7 @@ import 'package:qlevar_router/qlevar_router.dart'; class ContenderCard extends HookConsumerWidget { final Contender contender; - final bool isSuperAdmin, isDetail; + final bool isAdmin, isDetail; final Function()? onEdit; final Future Function()? onDelete; const ContenderCard({ @@ -24,7 +24,7 @@ class ContenderCard extends HookConsumerWidget { required this.contender, this.onEdit, this.onDelete, - this.isSuperAdmin = false, + this.isAdmin = false, this.isDetail = false, }); @@ -40,7 +40,7 @@ class ContenderCard extends HookConsumerWidget { height: (contender.listType != ListType.blank && status == Status.waiting && - isSuperAdmin) + isAdmin) ? 180 : 130, padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 15), @@ -109,7 +109,7 @@ class ContenderCard extends HookConsumerWidget { const Spacer(), if (contender.listType != ListType.blank && status == Status.waiting && - isSuperAdmin) + isAdmin) Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ diff --git a/lib/vote/ui/pages/admin_page/section_bar.dart b/lib/vote/ui/pages/admin_page/section_bar.dart index fa65f3863f..11e1514f86 100644 --- a/lib/vote/ui/pages/admin_page/section_bar.dart +++ b/lib/vote/ui/pages/admin_page/section_bar.dart @@ -51,7 +51,7 @@ class SectionBar extends HookConsumerWidget { itemBuilder: (context, key, i) => SectionChip( label: key.name, selected: section.id == key.id, - isSuperAdmin: status == Status.waiting, + isAdmin: status == Status.waiting, onTap: () async { tokenExpireWrapper(ref, () async { sectionIdNotifier.setId(key.id); diff --git a/lib/vote/ui/pages/admin_page/section_chip.dart b/lib/vote/ui/pages/admin_page/section_chip.dart index 34f1872f27..bb2798e522 100644 --- a/lib/vote/ui/pages/admin_page/section_chip.dart +++ b/lib/vote/ui/pages/admin_page/section_chip.dart @@ -1,13 +1,13 @@ import 'package:flutter/material.dart'; class SectionChip extends StatelessWidget { - final bool selected, isSuperAdmin; + final bool selected, isAdmin; final String label; final Function()? onTap, onDelete; const SectionChip({ super.key, required this.label, - this.isSuperAdmin = false, + this.isAdmin = false, this.selected = false, this.onTap, this.onDelete, @@ -34,7 +34,7 @@ class SectionChip extends StatelessWidget { fontSize: 18.0, ), ), - if (isSuperAdmin && selected) + if (isAdmin && selected) Container( margin: const EdgeInsets.only(left: 10.0), child: GestureDetector( diff --git a/lib/vote/ui/pages/admin_page/section_contender_items.dart b/lib/vote/ui/pages/admin_page/section_contender_items.dart index cffa1d2a96..46961b0eaa 100644 --- a/lib/vote/ui/pages/admin_page/section_contender_items.dart +++ b/lib/vote/ui/pages/admin_page/section_contender_items.dart @@ -73,7 +73,7 @@ class SectionContenderItems extends HookConsumerWidget { items: data, itemBuilder: (context, e, i) => ContenderCard( contender: e, - isSuperAdmin: true, + isAdmin: true, onEdit: () { tokenExpireWrapper(ref, () async { contenderNotifier.setId(e); diff --git a/lib/vote/ui/pages/contender_pages/add_edit_contender.dart b/lib/vote/ui/pages/contender_pages/add_edit_contender.dart index ffc52fe054..ce267a2ee3 100644 --- a/lib/vote/ui/pages/contender_pages/add_edit_contender.dart +++ b/lib/vote/ui/pages/contender_pages/add_edit_contender.dart @@ -189,7 +189,7 @@ class AddEditContenderPage extends HookConsumerWidget { items: members, itemBuilder: (context, e, i) => MemberCard( member: e, - isSuperAdmin: true, + isAdmin: true, onDelete: () async { membersNotifier.removeMember(e); }, diff --git a/lib/vote/ui/pages/main_page/main_page.dart b/lib/vote/ui/pages/main_page/main_page.dart index 17a6947c21..488eeb081f 100644 --- a/lib/vote/ui/pages/main_page/main_page.dart +++ b/lib/vote/ui/pages/main_page/main_page.dart @@ -30,7 +30,7 @@ class VoteMainPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final statusNotifier = ref.watch(statusProvider.notifier); - final isSuperAdmin = ref.watch(isVoteSuperAdminProvider); + final isAdmin = ref.watch(isVoteAdminProvider); final sections = ref.watch(sectionsProvider); final sectionsNotifier = ref.watch(sectionsProvider.notifier); final contenders = ref.watch(contenderListProvider); @@ -62,13 +62,13 @@ class VoteMainPage extends HookConsumerWidget { padding: const EdgeInsets.symmetric(horizontal: 30.0), child: Column( children: [ - if (isSuperAdmin) + if (isAdmin) Row( children: [ const Spacer(), Container( margin: const EdgeInsets.only(right: 20), - child: SuperAdminButton( + child: AdminButton( onTap: () { QR.to(VoteRouter.root + VoteRouter.admin); }, @@ -134,7 +134,7 @@ class VoteMainPage extends HookConsumerWidget { padding: const EdgeInsets.only(left: 30.0), child: Column( children: [ - SizedBox(height: isSuperAdmin ? 10 : 15), + SizedBox(height: isAdmin ? 10 : 15), AsyncChild( value: sections, builder: (context, sectionList) => Column( @@ -143,10 +143,10 @@ class VoteMainPage extends HookConsumerWidget { height: MediaQuery.of(context).size.height - (s == Status.open - ? isSuperAdmin + ? isAdmin ? 215 : 220 - : isSuperAdmin + : isAdmin ? 150 : 155), child: Row( @@ -166,12 +166,12 @@ class VoteMainPage extends HookConsumerWidget { MainAxisAlignment.spaceBetween, children: [ SectionTitle(sectionList: sectionList), - if (isSuperAdmin) + if (isAdmin) Container( margin: const EdgeInsets.only( right: 20, ), - child: SuperAdminButton( + child: AdminButton( onTap: () { QR.to( VoteRouter.root + diff --git a/test/admin/is_admin_test.dart b/test/admin/is_admin_test.dart index 2ff3fc3597..36bbbe4f52 100644 --- a/test/admin/is_admin_test.dart +++ b/test/admin/is_admin_test.dart @@ -6,7 +6,7 @@ import 'package:titan/user/class/user.dart'; import 'package:titan/user/providers/user_provider.dart'; void main() { - group('isSuperAdminProvider', () { + group('isAdminProvider', () { test('returns true if user is admin', () { final container = ProviderContainer( overrides: [ @@ -15,7 +15,7 @@ void main() { groups: [ SimpleGroup.empty().copyWith( id: '0a25cb76-4b63-4fd3-b939-da6d9feabf28', - name: 'SuperAdmin', + name: 'Admin', ), SimpleGroup.empty().copyWith(id: '123', name: 'User'), ], @@ -24,9 +24,9 @@ void main() { ], ); - final isSuperAdmin = container.read(isSuperAdminProvider); + final isAdmin = container.read(isAdminProvider); - expect(isSuperAdmin, true); + expect(isAdmin, true); }); test('returns false if user is not admin', () { @@ -40,9 +40,9 @@ void main() { ], ); - final isSuperAdmin = container.read(isSuperAdminProvider); + final isAdmin = container.read(isAdminProvider); - expect(isSuperAdmin, false); + expect(isAdmin, false); }); }); } diff --git a/test/amap/is_amap_admin_provider_test.dart b/test/amap/is_amap_admin_provider_test.dart index ead09c83d1..19f0b740e4 100644 --- a/test/amap/is_amap_admin_provider_test.dart +++ b/test/amap/is_amap_admin_provider_test.dart @@ -6,7 +6,7 @@ import 'package:titan/user/class/user.dart'; import 'package:titan/user/providers/user_provider.dart'; void main() { - group('isAmapSuperAdmin', () { + group('isAmapAdmin', () { test('should return true if user is an Amap admin', () { final container = ProviderContainer( overrides: [ @@ -15,7 +15,7 @@ void main() { groups: [ SimpleGroup.empty().copyWith( id: '70db65ee-d533-4f6b-9ffa-a4d70a17b7ef', - name: 'Amap SuperAdmin', + name: 'Amap Admin', ), SimpleGroup.empty().copyWith(id: '123', name: 'Some Group'), ], @@ -24,9 +24,9 @@ void main() { ], ); - final isAmapSuperAdminState = container.read(isAmapSuperAdminProvider); + final isAmapAdminState = container.read(isAmapAdminProvider); - expect(isAmapSuperAdminState, true); + expect(isAmapAdminState, true); }); test('should return false if user is not an Amap admin', () { @@ -42,9 +42,9 @@ void main() { ], ); - final isAmapSuperAdminState = container.read(isAmapSuperAdminProvider); + final isAmapAdminState = container.read(isAmapAdminProvider); - expect(isAmapSuperAdminState, false); + expect(isAmapAdminState, false); }); }); } diff --git a/test/booking/is_booking_admin_provider_test.dart b/test/booking/is_booking_admin_provider_test.dart index d3e102c2dc..03f5314658 100644 --- a/test/booking/is_booking_admin_provider_test.dart +++ b/test/booking/is_booking_admin_provider_test.dart @@ -6,7 +6,7 @@ import 'package:titan/user/class/user.dart'; import 'package:titan/user/providers/user_provider.dart'; void main() { - group('isBookingSuperAdminProvider', () { + group('isBookingAdminProvider', () { test('should return true if user is a booking admin', () { final container = ProviderContainer( overrides: [ @@ -15,7 +15,7 @@ void main() { groups: [ SimpleGroup.empty().copyWith( id: '0a25cb76-4b63-4fd3-b939-da6d9feabf28', - name: 'Booking SuperAdmin', + name: 'Booking Admin', ), SimpleGroup.empty().copyWith(id: '123', name: 'Other Group'), ], @@ -24,7 +24,7 @@ void main() { ], ); - final result = container.read(isSuperAdminProvider); + final result = container.read(isAdminProvider); expect(result, true); }); @@ -42,7 +42,7 @@ void main() { ], ); - final result = container.read(isSuperAdminProvider); + final result = container.read(isAdminProvider); expect(result, false); }); diff --git a/test/cinema/is_cinema_admin_test.dart b/test/cinema/is_cinema_admin_test.dart index 508771e67e..045a497c92 100644 --- a/test/cinema/is_cinema_admin_test.dart +++ b/test/cinema/is_cinema_admin_test.dart @@ -6,7 +6,7 @@ import 'package:titan/user/class/user.dart'; import 'package:titan/user/providers/user_provider.dart'; void main() { - group('isCinemaSuperAdmin', () { + group('isCinemaAdmin', () { test('should return true if user is a cinema admin', () { final container = ProviderContainer( overrides: [ @@ -15,7 +15,7 @@ void main() { groups: [ SimpleGroup.empty().copyWith( id: 'ce5f36e6-5377-489f-9696-de70e2477300', - name: 'Cinema SuperAdmin', + name: 'Cinema Admin', ), ], ), @@ -23,11 +23,9 @@ void main() { ], ); - final isCinemaSuperAdminState = container.read( - isCinemaSuperAdminProvider, - ); + final isCinemaAdminState = container.read(isCinemaAdminProvider); - expect(isCinemaSuperAdminState, true); + expect(isCinemaAdminState, true); }); test('should return false if user is not a cinema admin', () { @@ -44,11 +42,9 @@ void main() { ], ); - final isCinemaSuperAdminState = container.read( - isCinemaSuperAdminProvider, - ); + final isCinemaAdminState = container.read(isCinemaAdminProvider); - expect(isCinemaSuperAdminState, false); + expect(isCinemaAdminState, false); }); }); } diff --git a/test/event/is_admin_provider_test.dart b/test/event/is_admin_provider_test.dart index d4ae59ad64..6d6083a845 100644 --- a/test/event/is_admin_provider_test.dart +++ b/test/event/is_admin_provider_test.dart @@ -6,7 +6,7 @@ import 'package:titan/user/class/user.dart'; import 'package:titan/user/providers/user_provider.dart'; void main() { - group('isEventSuperAdmin', () { + group('isEventAdmin', () { test('should return true if user is event admin', () { final container = ProviderContainer( overrides: [ @@ -15,7 +15,7 @@ void main() { groups: [ SimpleGroup.empty().copyWith( id: '53a669d6-84b1-4352-8d7c-421c1fbd9c6a', - name: 'SuperAdmin', + name: 'Admin', ), SimpleGroup.empty().copyWith(id: '123', name: 'User'), ], @@ -24,9 +24,9 @@ void main() { ], ); - final isEventSuperAdminState = container.read(isEventSuperAdminProvider); + final isEventAdminState = container.read(isEventAdminProvider); - expect(isEventSuperAdminState, true); + expect(isEventAdminState, true); }); test('should return false if user is not event admin', () { @@ -40,9 +40,9 @@ void main() { ], ); - final isEventSuperAdminState = container.read(isEventSuperAdminProvider); + final isEventAdminState = container.read(isEventAdminProvider); - expect(isEventSuperAdminState, false); + expect(isEventAdminState, false); }); }); } diff --git a/test/login/login_test.dart b/test/login/login_test.dart index 5f42df52a4..9464b1eece 100644 --- a/test/login/login_test.dart +++ b/test/login/login_test.dart @@ -225,7 +225,7 @@ void main() { ); }); - test('Account Type to ID - SuperAdmin', () { + test('Account Type to ID - Admin', () { expect( accountTypeToID(AccountType.admin), '0a25cb76-4b63-4fd3-b939-da6d9feabf28', diff --git a/test/vote/is_vote_admin_provider_test.dart b/test/vote/is_vote_admin_provider_test.dart index 91742eea94..66cf2f2ee0 100644 --- a/test/vote/is_vote_admin_provider_test.dart +++ b/test/vote/is_vote_admin_provider_test.dart @@ -6,7 +6,7 @@ import 'package:titan/user/providers/user_provider.dart'; import 'package:titan/vote/providers/is_vote_admin_provider.dart'; void main() { - group('isVoteSuperAdmin', () { + group('isVoteAdmin', () { test('should return true if user is a vote admin', () { final container = ProviderContainer( overrides: [ @@ -22,9 +22,9 @@ void main() { ], ); - final isVoteSuperAdminState = container.read(isVoteSuperAdminProvider); + final isVoteAdminState = container.read(isVoteAdminProvider); - expect(isVoteSuperAdminState, true); + expect(isVoteAdminState, true); }); test('should return false if user is not a vote admin', () { @@ -42,9 +42,9 @@ void main() { ], ); - final isVoteSuperAdminState = container.read(isVoteSuperAdminProvider); + final isVoteAdminState = container.read(isVoteAdminProvider); - expect(isVoteSuperAdminState, false); + expect(isVoteAdminState, false); }); }); } From 5c1772f4865fb1947aa9caabcd057b13e4f7d869 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 16 Jul 2025 20:02:24 +0200 Subject: [PATCH 122/473] fix mistakes --- lib/advert/router.dart | 8 ++++---- lib/advert/ui/pages/main_page/main_page.dart | 4 ++-- lib/amap/router.dart | 4 ++-- lib/booking/router.dart | 20 +++++++++---------- lib/cinema/router.dart | 4 ++-- lib/event/router.dart | 8 ++++---- lib/feed/router.dart | 4 ++-- lib/home/router.dart | 2 +- lib/loan/router.dart | 4 ++-- lib/paiement/router.dart | 10 +++++----- lib/ph/router.dart | 4 ++-- .../providers/phonebook_admin_provider.dart | 2 +- lib/phonebook/router.dart | 10 +++------- .../association_information_editor.dart | 2 +- .../ui/pages/main_page/main_page.dart | 2 +- lib/purchases/router.dart | 2 +- lib/raffle/router.dart | 6 +++--- lib/recommendation/router.dart | 2 +- lib/seed-library/router.dart | 4 ++-- .../providers/module_list_provider.dart | 2 +- lib/super_admin/router.dart | 2 +- lib/vote/router.dart | 4 ++-- test/admin/is_admin_test.dart | 4 ++-- 23 files changed, 54 insertions(+), 60 deletions(-) diff --git a/lib/advert/router.dart b/lib/advert/router.dart index a63686a659..7c48218c5b 100644 --- a/lib/advert/router.dart +++ b/lib/advert/router.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/super_admin/providers/is_admin_provider.dart'; +import 'package:titan/booking/providers/is_admin_provider.dart'; import 'package:titan/advert/providers/is_advert_admin_provider.dart'; import 'package:titan/advert/ui/pages/admin_page/admin_page.dart' deferred as admin_page; @@ -48,9 +48,9 @@ class AdvertRouter { children: [ QRoute( path: admin, - builder: () => admin_page.AdvertSuperAdminPage(), + builder: () => admin_page.AdvertAdminPage(), middleware: [ - SuperAdminMiddleware(ref, isAdvertSuperAdminProvider), + AdminMiddleware(ref, isAdvertAdminProvider), DeferredLoadingMiddleware(admin_page.loadLibrary), ], children: [ @@ -72,7 +72,7 @@ class AdvertRouter { path: addRemAnnouncer, builder: () => add_rem_announcer_page.AddRemAnnouncerPage(), middleware: [ - SuperAdminMiddleware(ref, isSuperAdminProvider), + AdminMiddleware(ref, isAdminProvider), DeferredLoadingMiddleware(add_rem_announcer_page.loadLibrary), ], ), diff --git a/lib/advert/ui/pages/main_page/main_page.dart b/lib/advert/ui/pages/main_page/main_page.dart index 9c6882684d..011eefaa92 100644 --- a/lib/advert/ui/pages/main_page/main_page.dart +++ b/lib/advert/ui/pages/main_page/main_page.dart @@ -1,7 +1,6 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/super_admin/providers/is_admin_provider.dart'; import 'package:titan/advert/providers/advert_list_provider.dart'; import 'package:titan/advert/providers/advert_posters_provider.dart'; import 'package:titan/advert/providers/advert_provider.dart'; @@ -11,6 +10,7 @@ import 'package:titan/advert/ui/pages/advert.dart'; import 'package:titan/advert/router.dart'; import 'package:titan/advert/ui/components/announcer_bar.dart'; import 'package:titan/advert/ui/components/advert_card.dart'; +import 'package:titan/super_admin/providers/is_admin_provider.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/column_refresher.dart'; import 'package:titan/tools/ui/widgets/admin_button.dart'; @@ -28,7 +28,7 @@ class AdvertMainPage extends HookConsumerWidget { final advertPostersNotifier = ref.watch(advertPostersProvider.notifier); final selected = ref.watch(announcerProvider); final selectedNotifier = ref.watch(announcerProvider.notifier); - final isAdmin = ref.watch(isAdminProvider); + final isAdmin = ref.watch(isSuperAdminProvider); final isAdvertAdmin = ref.watch(isAdvertAdminProvider); return AdvertTemplate( child: Stack( diff --git a/lib/amap/router.dart b/lib/amap/router.dart index 14b55d39d0..f0550f0ff4 100644 --- a/lib/amap/router.dart +++ b/lib/amap/router.dart @@ -56,9 +56,9 @@ class AmapRouter { children: [ QRoute( path: admin, - builder: () => admin_page.SuperAdminPage(), + builder: () => admin_page.AdminPage(), middleware: [ - SuperAdminMiddleware(ref, isAmapSuperAdminProvider), + AdminMiddleware(ref, isAmapAdminProvider), DeferredLoadingMiddleware(admin_page.loadLibrary), ], children: [ diff --git a/lib/booking/router.dart b/lib/booking/router.dart index bed681d6bc..8819f3ffb3 100644 --- a/lib/booking/router.dart +++ b/lib/booking/router.dart @@ -53,9 +53,9 @@ class BookingRouter { children: [ QRoute( path: admin, - builder: () => admin_page.SuperAdminPage(), + builder: () => admin_page.AdminPage(), middleware: [ - SuperAdminMiddleware(ref, isSuperAdminProvider), + AdminMiddleware(ref, isAdminProvider), DeferredLoadingMiddleware(admin_page.loadLibrary), ], children: [ @@ -63,7 +63,7 @@ class BookingRouter { path: room, builder: () => add_edit_room_page.AddEditRoomPage(), middleware: [ - SuperAdminMiddleware(ref, isSuperAdminProvider), + AdminMiddleware(ref, isAdminProvider), DeferredLoadingMiddleware(add_edit_room_page.loadLibrary), ], ), @@ -71,7 +71,7 @@ class BookingRouter { path: manager, builder: () => add_edit_manager_page.AddEditManagerPage(), middleware: [ - SuperAdminMiddleware(ref, isSuperAdminProvider), + AdminMiddleware(ref, isAdminProvider), DeferredLoadingMiddleware(add_edit_manager_page.loadLibrary), ], ), @@ -81,16 +81,15 @@ class BookingRouter { path: manager, builder: () => manager_page.ManagerPage(), middleware: [ - SuperAdminMiddleware(ref, isManagerProvider), + AdminMiddleware(ref, isManagerProvider), DeferredLoadingMiddleware(manager_page.loadLibrary), ], children: [ QRoute( path: detail, - builder: () => - detail_booking_page.DetailBookingPage(isSuperAdmin: true), + builder: () => detail_booking_page.DetailBookingPage(isAdmin: true), middleware: [ - SuperAdminMiddleware(ref, isManagerProvider), + AdminMiddleware(ref, isManagerProvider), DeferredLoadingMiddleware(detail_booking_page.loadLibrary), ], ), @@ -99,7 +98,7 @@ class BookingRouter { builder: () => add_edit_booking_page.AddEditBookingPage(isManagerPage: true), middleware: [ - SuperAdminMiddleware(ref, isManagerProvider), + AdminMiddleware(ref, isManagerProvider), DeferredLoadingMiddleware(add_edit_booking_page.loadLibrary), ], ), @@ -115,8 +114,7 @@ class BookingRouter { ), QRoute( path: detail, - builder: () => - detail_booking_page.DetailBookingPage(isSuperAdmin: false), + builder: () => detail_booking_page.DetailBookingPage(isAdmin: false), middleware: [ DeferredLoadingMiddleware(detail_booking_page.loadLibrary), ], diff --git a/lib/cinema/router.dart b/lib/cinema/router.dart index 8e173c7373..f488a03aa7 100644 --- a/lib/cinema/router.dart +++ b/lib/cinema/router.dart @@ -51,9 +51,9 @@ class CinemaRouter { ), QRoute( path: admin, - builder: () => admin_page.SuperAdminPage(), + builder: () => admin_page.AdminPage(), middleware: [ - SuperAdminMiddleware(ref, isCinemaSuperAdminProvider), + AdminMiddleware(ref, isCinemaAdminProvider), DeferredLoadingMiddleware(admin_page.loadLibrary), ], children: [ diff --git a/lib/event/router.dart b/lib/event/router.dart index 2f4a58686e..a4ba1d6aee 100644 --- a/lib/event/router.dart +++ b/lib/event/router.dart @@ -44,15 +44,15 @@ class EventRouter { children: [ QRoute( path: admin, - builder: () => admin_page.SuperAdminPage(), + builder: () => admin_page.AdminPage(), middleware: [ - SuperAdminMiddleware(ref, isEventSuperAdminProvider), + AdminMiddleware(ref, isEventAdminProvider), DeferredLoadingMiddleware(admin_page.loadLibrary), ], children: [ QRoute( path: detail, - builder: () => detail_page.DetailPage(isSuperAdmin: true), + builder: () => detail_page.DetailPage(isAdmin: true), middleware: [DeferredLoadingMiddleware(detail_page.loadLibrary)], ), QRoute( @@ -73,7 +73,7 @@ class EventRouter { ), QRoute( path: detail, - builder: () => detail_page.DetailPage(isSuperAdmin: false), + builder: () => detail_page.DetailPage(isAdmin: false), middleware: [DeferredLoadingMiddleware(detail_page.loadLibrary)], ), ], diff --git a/lib/feed/router.dart b/lib/feed/router.dart index 997ec5d9a4..cb3ea99ab7 100644 --- a/lib/feed/router.dart +++ b/lib/feed/router.dart @@ -40,10 +40,10 @@ class FeedRouter { children: [ QRoute( path: admin, - builder: () => admin_page.SuperAdminPage(), + builder: () => admin_page.AdminPage(), middleware: [ AuthenticatedMiddleware(ref), - SuperAdminMiddleware(ref, isSuperAdminProvider), + AdminMiddleware(ref, isSuperAdminProvider), DeferredLoadingMiddleware(admin_page.loadLibrary), ], ), diff --git a/lib/home/router.dart b/lib/home/router.dart index 71bdd6f66d..1ab0935059 100644 --- a/lib/home/router.dart +++ b/lib/home/router.dart @@ -35,7 +35,7 @@ class HomeRouter { children: [ QRoute( path: detail, - builder: () => detail_page.DetailPage(isSuperAdmin: false), + builder: () => detail_page.DetailPage(isAdmin: false), middleware: [DeferredLoadingMiddleware(detail_page.loadLibrary)], ), ], diff --git a/lib/loan/router.dart b/lib/loan/router.dart index 0e989c3d77..b9949400c6 100644 --- a/lib/loan/router.dart +++ b/lib/loan/router.dart @@ -47,9 +47,9 @@ class LoanRouter { children: [ QRoute( path: admin, - builder: () => admin_page.SuperAdminPage(), + builder: () => admin_page.AdminPage(), middleware: [ - SuperAdminMiddleware(ref, isLoanSuperAdminProvider), + AdminMiddleware(ref, isLoanAdminProvider), DeferredLoadingMiddleware(admin_page.loadLibrary), ], children: [ diff --git a/lib/paiement/router.dart b/lib/paiement/router.dart index 9896af8d5e..a262ac883e 100644 --- a/lib/paiement/router.dart +++ b/lib/paiement/router.dart @@ -35,7 +35,7 @@ class PaymentRouter { static const String fund = '/fund'; static const String addEditStore = '/addEditStore'; static const String transferStructure = '/transferStructure'; - static const String storeSuperAdmin = '/storeSuperAdmin'; + static const String storeAdmin = '/storeAdmin'; static const String storeStats = '/storeStats'; static final Module module = Module( getName: (context) => AppLocalizations.of(context)!.modulePayment, @@ -68,16 +68,16 @@ class PaymentRouter { middleware: [DeferredLoadingMiddleware(devices_page.loadLibrary)], ), QRoute( - path: PaymentRouter.storeSuperAdmin, - builder: () => store_admin_page.StoreSuperAdminPage(), + path: PaymentRouter.storeAdmin, + builder: () => store_admin_page.StoreAdminPage(), middleware: [DeferredLoadingMiddleware(store_admin_page.loadLibrary)], ), QRoute( path: PaymentRouter.admin, - builder: () => admin_page.SuperAdminPage(), + builder: () => admin_page.AdminPage(), middleware: [ DeferredLoadingMiddleware(admin_page.loadLibrary), - SuperAdminMiddleware(ref, isPaymentSuperAdminProvider), + AdminMiddleware(ref, isPaymentAdminProvider), ], children: [ QRoute( diff --git a/lib/ph/router.dart b/lib/ph/router.dart index 7391640e39..3390178e9d 100644 --- a/lib/ph/router.dart +++ b/lib/ph/router.dart @@ -54,9 +54,9 @@ class PhRouter { ), QRoute( path: admin, - builder: () => admin_page.SuperAdminPage(), + builder: () => admin_page.AdminPage(), middleware: [ - SuperAdminMiddleware(ref, isPhSuperAdminProvider), + AdminMiddleware(ref, isPhAdminProvider), DeferredLoadingMiddleware(admin_page.loadLibrary), ], children: [ diff --git a/lib/phonebook/providers/phonebook_admin_provider.dart b/lib/phonebook/providers/phonebook_admin_provider.dart index 4c60f68254..681c939c2e 100644 --- a/lib/phonebook/providers/phonebook_admin_provider.dart +++ b/lib/phonebook/providers/phonebook_admin_provider.dart @@ -20,7 +20,7 @@ final isPhonebookAdminProvider = StateProvider((ref) { final hasPhonebookAdminAccessProvider = StateProvider((ref) { final isPhonebookAdmin = ref.watch(isPhonebookAdminProvider); - final isAdmin = ref.watch(isAdminProvider); + final isAdmin = ref.watch(isSuperAdminProvider); return isPhonebookAdmin || isAdmin; }); diff --git a/lib/phonebook/router.dart b/lib/phonebook/router.dart index c6d71d1e70..7354fb42ce 100644 --- a/lib/phonebook/router.dart +++ b/lib/phonebook/router.dart @@ -42,10 +42,8 @@ class PhonebookRouter { children: [ QRoute( path: admin, - builder: () => const SuperAdminPage(), - middleware: [ - SuperAdminMiddleware(ref, hasPhonebookSuperAdminAccessProvider), - ], + builder: () => const AdminPage(), + middleware: [AdminMiddleware(ref, hasPhonebookAdminAccessProvider)], children: [ QRoute( path: editAssociation, @@ -70,9 +68,7 @@ class PhonebookRouter { QRoute( path: editAssociation, builder: () => AssociationEditorPage(), - middleware: [ - SuperAdminMiddleware(ref, isAssociationPresidentProvider), - ], + middleware: [AdminMiddleware(ref, isAssociationPresidentProvider)], children: [ QRoute( path: addEditMember, diff --git a/lib/phonebook/ui/pages/association_editor_page/association_information_editor.dart b/lib/phonebook/ui/pages/association_editor_page/association_information_editor.dart index 030f2bf7ab..5f3bad11c1 100644 --- a/lib/phonebook/ui/pages/association_editor_page/association_information_editor.dart +++ b/lib/phonebook/ui/pages/association_editor_page/association_information_editor.dart @@ -32,7 +32,7 @@ class AssociationInformationEditor extends HookConsumerWidget { final name = useTextEditingController(text: association.name); final description = useTextEditingController(text: association.description); final associationListNotifier = ref.watch(associationListProvider.notifier); - final isAdmin = ref.watch(isAdminProvider); + final isAdmin = ref.watch(isSuperAdminProvider); final isPhonebookAdmin = ref.watch(isPhonebookAdminProvider); final groups = ref.watch(allGroupListProvider); diff --git a/lib/phonebook/ui/pages/main_page/main_page.dart b/lib/phonebook/ui/pages/main_page/main_page.dart index 6cbe231b83..186a0fef6c 100644 --- a/lib/phonebook/ui/pages/main_page/main_page.dart +++ b/lib/phonebook/ui/pages/main_page/main_page.dart @@ -24,7 +24,7 @@ class PhonebookMainPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final isPhonebookAdmin = ref.watch(isPhonebookAdminProvider); - final isAdmin = ref.watch(isAdminProvider); + final isAdmin = ref.watch(isSuperAdminProvider); final associationNotifier = ref.watch(associationProvider.notifier); final associationListNotifier = ref.watch(associationListProvider.notifier); final associationList = ref.watch(associationListProvider); diff --git a/lib/purchases/router.dart b/lib/purchases/router.dart index 9587e8c80a..586dbbd331 100644 --- a/lib/purchases/router.dart +++ b/lib/purchases/router.dart @@ -41,7 +41,7 @@ class PurchasesRouter { QRoute( path: scan, builder: () => const ScanPage(), - middleware: [SuperAdminMiddleware(ref, isPurchasesSuperAdminProvider)], + middleware: [AdminMiddleware(ref, isPurchasesAdminProvider)], ), QRoute( path: history, diff --git a/lib/raffle/router.dart b/lib/raffle/router.dart index fcb513c96e..ed8a3bd61b 100644 --- a/lib/raffle/router.dart +++ b/lib/raffle/router.dart @@ -49,9 +49,9 @@ class RaffleRouter { children: [ QRoute( path: admin, - builder: () => admin_module_page.SuperAdminModulePage(), + builder: () => admin_module_page.AdminModulePage(), middleware: [ - SuperAdminMiddleware(ref, isRaffleSuperAdminProvider), + AdminMiddleware(ref, isRaffleAdminProvider), DeferredLoadingMiddleware(admin_module_page.loadLibrary), ], ), @@ -59,7 +59,7 @@ class RaffleRouter { path: detail, builder: () => raffle_page.RaffleInfoPage(), middleware: [ - SuperAdminMiddleware(ref, isRaffleSuperAdminProvider), + AdminMiddleware(ref, isRaffleAdminProvider), DeferredLoadingMiddleware(raffle_page.loadLibrary), ], children: [ diff --git a/lib/recommendation/router.dart b/lib/recommendation/router.dart index 3c7149d3d6..f1d375acea 100644 --- a/lib/recommendation/router.dart +++ b/lib/recommendation/router.dart @@ -51,7 +51,7 @@ class RecommendationRouter { path: addEdit, builder: () => add_edit_page.AddEditRecommendationPage(), middleware: [ - SuperAdminMiddleware(ref, isRecommendationSuperAdminProvider), + AdminMiddleware(ref, isRecommendationAdminProvider), DeferredLoadingMiddleware(add_edit_page.loadLibrary), ], ), diff --git a/lib/seed-library/router.dart b/lib/seed-library/router.dart index 7f655e8351..6b790884af 100644 --- a/lib/seed-library/router.dart +++ b/lib/seed-library/router.dart @@ -75,7 +75,7 @@ class SeedLibraryRouter { path: SeedLibraryRouter.editInformation, builder: () => edit_information_page.EditInformationPage(), middleware: [ - SuperAdminMiddleware(ref, isSeedLibrarySuperAdminProvider), + AdminMiddleware(ref, isSeedLibraryAdminProvider), DeferredLoadingMiddleware(edit_information_page.loadLibrary), ], ), @@ -85,7 +85,7 @@ class SeedLibraryRouter { path: species, builder: () => species_page.SpeciesPage(), middleware: [ - SuperAdminMiddleware(ref, isSeedLibrarySuperAdminProvider), + AdminMiddleware(ref, isSeedLibraryAdminProvider), DeferredLoadingMiddleware(species_page.loadLibrary), ], children: [ diff --git a/lib/settings/providers/module_list_provider.dart b/lib/settings/providers/module_list_provider.dart index 74a70e1c4b..ee1a3faf30 100644 --- a/lib/settings/providers/module_list_provider.dart +++ b/lib/settings/providers/module_list_provider.dart @@ -33,7 +33,7 @@ final modulesProvider = StateNotifierProvider>(( .map((root) => '/$root') .toList(); - final isAdmin = ref.watch(isAdminProvider); + final isAdmin = ref.watch(isSuperAdminProvider); ModulesNotifier modulesNotifier = ModulesNotifier(isAdmin: isAdmin); modulesNotifier.loadModules(myModulesRoot); diff --git a/lib/super_admin/router.dart b/lib/super_admin/router.dart index 8757986a0d..7bec23143c 100644 --- a/lib/super_admin/router.dart +++ b/lib/super_admin/router.dart @@ -64,7 +64,7 @@ class SuperAdminRouter { builder: () => main_page.SuperAdminMainPage(), middleware: [ AuthenticatedMiddleware(ref), - SuperAdminMiddleware(ref, isSuperAdminProvider), + AdminMiddleware(ref, isSuperAdminProvider), DeferredLoadingMiddleware(main_page.loadLibrary), ], children: [ diff --git a/lib/vote/router.dart b/lib/vote/router.dart index 43b5347865..ece95a48b8 100644 --- a/lib/vote/router.dart +++ b/lib/vote/router.dart @@ -47,9 +47,9 @@ class VoteRouter { children: [ QRoute( path: admin, - builder: () => admin_page.SuperAdminPage(), + builder: () => admin_page.AdminPage(), middleware: [ - SuperAdminMiddleware(ref, isVoteSuperAdminProvider), + AdminMiddleware(ref, isVoteAdminProvider), DeferredLoadingMiddleware(admin_page.loadLibrary), ], children: [ diff --git a/test/admin/is_admin_test.dart b/test/admin/is_admin_test.dart index 36bbbe4f52..d791031f72 100644 --- a/test/admin/is_admin_test.dart +++ b/test/admin/is_admin_test.dart @@ -24,7 +24,7 @@ void main() { ], ); - final isAdmin = container.read(isAdminProvider); + final isAdmin = container.read(isSuperAdminProvider); expect(isAdmin, true); }); @@ -40,7 +40,7 @@ void main() { ], ); - final isAdmin = container.read(isAdminProvider); + final isAdmin = container.read(isSuperAdminProvider); expect(isAdmin, false); }); From 6ae84befc0d6f7735e0b45adab9bb05c6e7e2650 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 16 Jul 2025 21:07:02 +0200 Subject: [PATCH 123/473] basics --- lib/admin/router.dart | 2 +- lib/admin/ui/pages/main_page/main_page.dart | 20 +++++- .../users_management_page.dart | 70 ++++++++++++++++++- lib/router.dart | 4 +- .../providers/module_list_provider.dart | 7 +- lib/super_admin/router.dart | 2 +- 6 files changed, 95 insertions(+), 10 deletions(-) diff --git a/lib/admin/router.dart b/lib/admin/router.dart index 7d5414448b..74a67bb971 100644 --- a/lib/admin/router.dart +++ b/lib/admin/router.dart @@ -17,7 +17,7 @@ class AdminRouter { static const String groupNotifications = '/group_notifications'; static final Module module = Module( getName: (context) => "Admin", - description: "Gérer les paramètres de l'application", + description: "Gérer les utilisateurs de l'application", root: AdminRouter.root, ); diff --git a/lib/admin/ui/pages/main_page/main_page.dart b/lib/admin/ui/pages/main_page/main_page.dart index 77bde39184..66d975eb61 100644 --- a/lib/admin/ui/pages/main_page/main_page.dart +++ b/lib/admin/ui/pages/main_page/main_page.dart @@ -1,7 +1,9 @@ import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/admin/admin.dart'; +import 'package:titan/admin/router.dart'; +import 'package:titan/tools/ui/styleguide/list_item.dart'; import 'package:titan/user/providers/user_list_provider.dart'; @@ -13,7 +15,21 @@ class AdminMainPage extends HookConsumerWidget { ref.watch(userList); return AdminTemplate( - child: Padding(padding: const EdgeInsets.all(40), child: Text("yo")), + child: Padding( + padding: const EdgeInsets.all(40), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("Gestion", style: Theme.of(context).textTheme.headlineMedium), + ListItem( + title: "Gestion des utilisateurs", + subtitle: "Gérer les utilisateurs de l'application", + onTap: () => + QR.to(AdminRouter.root + AdminRouter.usersManagement), + ), + ], + ), + ), ); } } diff --git a/lib/admin/ui/pages/users_management_page/users_management_page.dart b/lib/admin/ui/pages/users_management_page/users_management_page.dart index 3e3d31cadb..d5cba7afb1 100644 --- a/lib/admin/ui/pages/users_management_page/users_management_page.dart +++ b/lib/admin/ui/pages/users_management_page/users_management_page.dart @@ -1,7 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/admin.dart'; +import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; +import 'package:titan/tools/ui/styleguide/button.dart'; class UsersManagementPage extends HookConsumerWidget { const UsersManagementPage({super.key}); @@ -9,7 +10,72 @@ class UsersManagementPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { return AdminTemplate( - child: Padding(padding: const EdgeInsets.all(40), child: Text("yo")), + child: Padding( + padding: const EdgeInsets.all(40), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Gestion des utilisateurs", + style: Theme.of(context).textTheme.headlineMedium, + ), + const SizedBox(height: 20), + Button( + text: 'Ajouter', + onPressed: () async { + await showCustomBottomModal( + context: context, + ref: ref, + modal: BottomModalTemplate( + title: "Ajouter des utilisateurs", + child: Column( + children: [ + Button(text: "Importer une liste", onPressed: () {}), + const SizedBox(height: 20), + Button( + text: "Ajouter", + onPressed: () {}, + disabled: true, + ), + ], + ), + ), + ); + }, + ), + const SizedBox(height: 20), + Button( + text: 'Supprimer', + onPressed: () async { + await showCustomBottomModal( + context: context, + ref: ref, + modal: BottomModalTemplate( + type: BottomModalType.danger, + title: "Supprimer des utilisateurs", + child: Column( + children: [ + Button( + text: "Importer une liste", + onPressed: () {}, + type: ButtonType.onDanger, + ), + const SizedBox(height: 20), + Button( + text: "Supprimer", + onPressed: () {}, + disabled: true, + ), + ], + ), + ), + ); + }, + type: ButtonType.danger, + ), + ], + ), + ), ); } } diff --git a/lib/router.dart b/lib/router.dart index 035371785d..6c3e800e28 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/admin/router.dart'; -import 'package:titan/super_admin/router.dart'; import 'package:titan/advert/router.dart'; import 'package:titan/amap/router.dart'; import 'package:titan/booking/router.dart'; @@ -29,6 +28,7 @@ import 'package:titan/recommendation/router.dart'; import 'package:titan/seed-library/router.dart'; import 'package:titan/settings/router.dart'; import 'package:titan/raffle/router.dart'; +import 'package:titan/super_admin/router.dart'; import 'package:titan/tools/middlewares/authenticated_middleware.dart'; import 'package:titan/tools/middlewares/deferred_middleware.dart'; import 'package:titan/tools/ui/styleguide/router.dart'; @@ -89,7 +89,6 @@ class AppRouter { FadeTransition(opacity: animation, child: child), ), ), - SuperAdminRouter(ref).route(), AdvertRouter(ref).route(), AmapRouter(ref).route(), BookingRouter(ref).route(), @@ -114,6 +113,7 @@ class AppRouter { VoteRouter(ref).route(), SeedLibraryRouter(ref).route(), AdminRouter(ref).route(), + SuperAdminRouter(ref).route(), ]; } } diff --git a/lib/settings/providers/module_list_provider.dart b/lib/settings/providers/module_list_provider.dart index ee1a3faf30..99e19ed3b6 100644 --- a/lib/settings/providers/module_list_provider.dart +++ b/lib/settings/providers/module_list_provider.dart @@ -2,7 +2,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/admin/router.dart'; import 'package:titan/super_admin/providers/is_admin_provider.dart'; -import 'package:titan/super_admin/router.dart'; import 'package:titan/advert/router.dart'; import 'package:titan/super_admin/providers/all_my_module_roots_list_provider.dart'; import 'package:titan/amap/router.dart'; @@ -23,6 +22,7 @@ import 'package:titan/recommendation/router.dart'; import 'package:titan/seed-library/router.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:titan/settings/router.dart'; +import 'package:titan/super_admin/router.dart'; import 'package:titan/vote/router.dart'; final modulesProvider = StateNotifierProvider>(( @@ -128,7 +128,10 @@ class ModulesNotifier extends StateNotifier> { for (Module module in toDelete) { allModules.remove(module); } - allModules.addAll([SettingsRouter.module, if (isAdmin) AdminRouter.module]); + allModules.addAll([ + SettingsRouter.module, + if (isAdmin) SuperAdminRouter.module, + ]); state = allModules; } diff --git a/lib/super_admin/router.dart b/lib/super_admin/router.dart index 7bec23143c..835b6f2517 100644 --- a/lib/super_admin/router.dart +++ b/lib/super_admin/router.dart @@ -52,7 +52,7 @@ class SuperAdminRouter { '/detail_association_membership'; static const String addEditMember = '/add_edit_member'; static final Module module = Module( - getName: (context) => "SuperAdmin", + getName: (context) => "Super Admin", description: "Gérer les groupes, écoles et structures", root: SuperAdminRouter.root, ); From 9bf35f656b8b19a14adea49deb68545dd3b144bb Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 16 Jul 2025 21:23:40 +0200 Subject: [PATCH 124/473] main pages --- lib/admin/router.dart | 20 ++++++++++++++- .../group_notification_page.dart | 25 +++++++++++++++++++ lib/admin/ui/pages/main_page/main_page.dart | 11 ++++++++ .../users_groups_management_page.dart | 25 +++++++++++++++++++ 4 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 lib/admin/ui/pages/group_notifification_page/group_notification_page.dart create mode 100644 lib/admin/ui/pages/users_groups_management_page/users_groups_management_page.dart diff --git a/lib/admin/router.dart b/lib/admin/router.dart index 74a67bb971..61b472b54f 100644 --- a/lib/admin/router.dart +++ b/lib/admin/router.dart @@ -2,8 +2,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/admin/ui/pages/main_page/main_page.dart' deferred as main_page; +import 'package:titan/admin/ui/pages/users_groups_management_page/users_groups_management_page.dart' + deferred as users_groups_management_page; import 'package:titan/admin/ui/pages/users_management_page/users_management_page.dart' deferred as users_managmement_page; +import 'package:titan/admin/ui/pages/group_notifification_page/group_notification_page.dart' + deferred as group_notification_page; import 'package:titan/navigation/class/module.dart'; import 'package:titan/tools/middlewares/authenticated_middleware.dart'; import 'package:titan/tools/middlewares/deferred_middleware.dart'; @@ -14,7 +18,7 @@ class AdminRouter { static const String root = '/admin'; static const String usersManagement = '/users_management'; static const String usersGroups = '/users_groups'; - static const String groupNotifications = '/group_notifications'; + static const String groupNotification = '/group_notification'; static final Module module = Module( getName: (context) => "Admin", description: "Gérer les utilisateurs de l'application", @@ -43,6 +47,20 @@ class AdminRouter { DeferredLoadingMiddleware(users_managmement_page.loadLibrary), ], ), + QRoute( + path: usersGroups, + builder: () => users_groups_management_page.UsersGroupsManagementPage(), + middleware: [ + DeferredLoadingMiddleware(users_groups_management_page.loadLibrary), + ], + ), + QRoute( + path: groupNotification, + builder: () => group_notification_page.GroupNotificationPage(), + middleware: [ + DeferredLoadingMiddleware(group_notification_page.loadLibrary), + ], + ), ], ); } diff --git a/lib/admin/ui/pages/group_notifification_page/group_notification_page.dart b/lib/admin/ui/pages/group_notifification_page/group_notification_page.dart new file mode 100644 index 0000000000..2c062a5100 --- /dev/null +++ b/lib/admin/ui/pages/group_notifification_page/group_notification_page.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/admin/admin.dart'; + +class GroupNotificationPage extends HookConsumerWidget { + const GroupNotificationPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return AdminTemplate( + child: Padding( + padding: const EdgeInsets.all(40), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Notification de groupe", + style: Theme.of(context).textTheme.headlineMedium, + ), + ], + ), + ), + ); + } +} diff --git a/lib/admin/ui/pages/main_page/main_page.dart b/lib/admin/ui/pages/main_page/main_page.dart index 66d975eb61..69e6cb04c6 100644 --- a/lib/admin/ui/pages/main_page/main_page.dart +++ b/lib/admin/ui/pages/main_page/main_page.dart @@ -27,6 +27,17 @@ class AdminMainPage extends HookConsumerWidget { onTap: () => QR.to(AdminRouter.root + AdminRouter.usersManagement), ), + ListItem( + title: "Gestion des groupes", + subtitle: "Gérer les groupes d'utilisateurs", + onTap: () => QR.to(AdminRouter.root + AdminRouter.usersGroups), + ), + ListItem( + title: "Notifications de groupe", + subtitle: "Gérer les notifications pour les groupes", + onTap: () => + QR.to(AdminRouter.root + AdminRouter.groupNotification), + ), ], ), ), diff --git a/lib/admin/ui/pages/users_groups_management_page/users_groups_management_page.dart b/lib/admin/ui/pages/users_groups_management_page/users_groups_management_page.dart new file mode 100644 index 0000000000..ee7a751769 --- /dev/null +++ b/lib/admin/ui/pages/users_groups_management_page/users_groups_management_page.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/admin/admin.dart'; + +class UsersGroupsManagementPage extends HookConsumerWidget { + const UsersGroupsManagementPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return AdminTemplate( + child: Padding( + padding: const EdgeInsets.all(40), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Gestion des utilisateurs", + style: Theme.of(context).textTheme.headlineMedium, + ), + ], + ), + ), + ); + } +} From 34be0813f0869a90ba882af175495a27c0a5fc9f Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Thu, 17 Jul 2025 22:07:55 +0200 Subject: [PATCH 125/473] groups --- lib/{super_admin => admin}/class/group.dart | 2 +- .../class/simple_group.dart | 0 .../providers/all_groups_list_provider.dart | 4 +- .../providers/group_id_provider.dart | 0 .../providers/group_list_provider.dart | 4 +- .../providers/group_logo_provider.dart | 2 +- .../providers/group_provider.dart | 4 +- .../providers/section_logo_provider.dart | 2 +- .../simple_groups_groups_provider.dart | 4 +- .../repositories/group_logo_repository.dart | 0 .../repositories/group_repository.dart | 4 +- lib/admin/router.dart | 29 +++- lib/admin/ui/components/admin_button.dart | 32 +++++ lib/admin/ui/components/item_card_ui.dart | 29 ++++ lib/admin/ui/components/user_ui.dart | 57 ++++++++ .../groups/add_group_page/add_group_page.dart | 4 +- .../edit_group_page/edit_group_page.dart | 19 +-- .../pages/groups/edit_group_page/results.dart | 6 +- .../groups/edit_group_page/search_user.dart | 13 +- .../groups/groups_page/groups_button.dart} | 0 .../groups/groups_page/groups_page.dart} | 71 ++-------- .../pages/groups/groups_page/groups_ui.dart} | 15 +- .../form_page/add_rem_announcer_page.dart | 2 +- .../ui/pages/form_page/announcer_card.dart | 2 +- .../admin_pages/add_edit_manager_page.dart | 6 +- .../ui/pages/admin_pages/admin_page.dart | 2 +- .../association_information_editor.dart | 4 +- lib/raffle/class/raffle.dart | 2 +- .../admin_module_page/confirm_creation.dart | 2 +- .../admin_module_page/tombola_handler.dart | 4 +- lib/super_admin/notification_service.dart | 2 +- lib/super_admin/router.dart | 36 +---- .../edit_module_visibility.dart | 2 +- .../modules_expansion_panel.dart | 2 +- .../add_loaner_page/add_loaner_page.dart | 133 ------------------ ...ciation_membership_information_editor.dart | 2 +- ...ssociation_membership_creation_dialog.dart | 2 +- .../association_membership_page.dart | 2 +- lib/user/class/user.dart | 2 +- lib/user/providers/user_list_provider.dart | 2 +- .../providers/voting_group_list_provider.dart | 4 +- lib/vote/ui/pages/admin_page/voters_bar.dart | 2 +- scripts/test.dart | 65 +++++++++ test/admin/admin_test.dart | 6 +- test/admin/group_id_provider_test.dart | 2 +- test/admin/group_list_provider_test.dart | 6 +- test/admin/group_logo_provider_test.dart | 4 +- test/admin/group_provider_test.dart | 6 +- test/admin/is_admin_test.dart | 2 +- test/amap/is_amap_admin_provider_test.dart | 2 +- .../is_booking_admin_provider_test.dart | 2 +- test/cinema/is_cinema_admin_test.dart | 2 +- test/event/is_admin_provider_test.dart | 2 +- test/user/user_list_provider_test.dart | 2 +- test/vote/is_vote_admin_provider_test.dart | 2 +- 55 files changed, 303 insertions(+), 316 deletions(-) rename lib/{super_admin => admin}/class/group.dart (95%) rename lib/{super_admin => admin}/class/simple_group.dart (100%) rename lib/{super_admin => admin}/providers/all_groups_list_provider.dart (63%) rename lib/{super_admin => admin}/providers/group_id_provider.dart (100%) rename lib/{super_admin => admin}/providers/group_list_provider.dart (94%) rename lib/{super_admin => admin}/providers/group_logo_provider.dart (93%) rename lib/{super_admin => admin}/providers/group_provider.dart (90%) rename lib/{super_admin => admin}/providers/section_logo_provider.dart (90%) rename lib/{super_admin => admin}/providers/simple_groups_groups_provider.dart (87%) rename lib/{super_admin => admin}/repositories/group_logo_repository.dart (100%) rename lib/{super_admin => admin}/repositories/group_repository.dart (94%) create mode 100644 lib/admin/ui/components/admin_button.dart create mode 100644 lib/admin/ui/components/item_card_ui.dart create mode 100644 lib/admin/ui/components/user_ui.dart rename lib/{super_admin => admin}/ui/pages/groups/add_group_page/add_group_page.dart (96%) rename lib/{super_admin => admin}/ui/pages/groups/edit_group_page/edit_group_page.dart (91%) rename lib/{super_admin => admin}/ui/pages/groups/edit_group_page/results.dart (95%) rename lib/{super_admin => admin}/ui/pages/groups/edit_group_page/search_user.dart (93%) rename lib/{super_admin/ui/pages/groups/group_page/group_button.dart => admin/ui/pages/groups/groups_page/groups_button.dart} (100%) rename lib/{super_admin/ui/pages/groups/group_page/group_page.dart => admin/ui/pages/groups/groups_page/groups_page.dart} (67%) rename lib/{super_admin/ui/pages/groups/group_page/group_ui.dart => admin/ui/pages/groups/groups_page/groups_ui.dart} (78%) delete mode 100644 lib/super_admin/ui/pages/groups/add_loaner_page/add_loaner_page.dart create mode 100644 scripts/test.dart diff --git a/lib/super_admin/class/group.dart b/lib/admin/class/group.dart similarity index 95% rename from lib/super_admin/class/group.dart rename to lib/admin/class/group.dart index 66c9c99c16..bf7eb29335 100644 --- a/lib/super_admin/class/group.dart +++ b/lib/admin/class/group.dart @@ -1,4 +1,4 @@ -import 'package:titan/super_admin/class/simple_group.dart'; +import 'package:titan/admin/class/simple_group.dart'; import 'package:titan/user/class/simple_users.dart'; class Group { diff --git a/lib/super_admin/class/simple_group.dart b/lib/admin/class/simple_group.dart similarity index 100% rename from lib/super_admin/class/simple_group.dart rename to lib/admin/class/simple_group.dart diff --git a/lib/super_admin/providers/all_groups_list_provider.dart b/lib/admin/providers/all_groups_list_provider.dart similarity index 63% rename from lib/super_admin/providers/all_groups_list_provider.dart rename to lib/admin/providers/all_groups_list_provider.dart index f1c48dd26d..b2e6d14d82 100644 --- a/lib/super_admin/providers/all_groups_list_provider.dart +++ b/lib/admin/providers/all_groups_list_provider.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/super_admin/class/simple_group.dart'; -import 'package:titan/super_admin/providers/group_list_provider.dart'; +import 'package:titan/admin/class/simple_group.dart'; +import 'package:titan/admin/providers/group_list_provider.dart'; final allGroupList = Provider>((ref) { return ref diff --git a/lib/super_admin/providers/group_id_provider.dart b/lib/admin/providers/group_id_provider.dart similarity index 100% rename from lib/super_admin/providers/group_id_provider.dart rename to lib/admin/providers/group_id_provider.dart diff --git a/lib/super_admin/providers/group_list_provider.dart b/lib/admin/providers/group_list_provider.dart similarity index 94% rename from lib/super_admin/providers/group_list_provider.dart rename to lib/admin/providers/group_list_provider.dart index 8f5edbd102..a6edbc8c57 100644 --- a/lib/super_admin/providers/group_list_provider.dart +++ b/lib/admin/providers/group_list_provider.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/super_admin/class/simple_group.dart'; -import 'package:titan/super_admin/repositories/group_repository.dart'; +import 'package:titan/admin/class/simple_group.dart'; +import 'package:titan/admin/repositories/group_repository.dart'; import 'package:titan/tools/providers/list_notifier.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/user/class/user.dart'; diff --git a/lib/super_admin/providers/group_logo_provider.dart b/lib/admin/providers/group_logo_provider.dart similarity index 93% rename from lib/super_admin/providers/group_logo_provider.dart rename to lib/admin/providers/group_logo_provider.dart index 5dfcca5e3f..aef5ac86fa 100644 --- a/lib/super_admin/providers/group_logo_provider.dart +++ b/lib/admin/providers/group_logo_provider.dart @@ -2,7 +2,7 @@ import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/super_admin/repositories/group_logo_repository.dart'; +import 'package:titan/admin/repositories/group_logo_repository.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/providers/single_notifier.dart'; diff --git a/lib/super_admin/providers/group_provider.dart b/lib/admin/providers/group_provider.dart similarity index 90% rename from lib/super_admin/providers/group_provider.dart rename to lib/admin/providers/group_provider.dart index 123db1bd1f..5ba31f1d4c 100644 --- a/lib/super_admin/providers/group_provider.dart +++ b/lib/admin/providers/group_provider.dart @@ -1,6 +1,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/super_admin/class/group.dart'; -import 'package:titan/super_admin/repositories/group_repository.dart'; +import 'package:titan/admin/class/group.dart'; +import 'package:titan/admin/repositories/group_repository.dart'; import 'package:titan/tools/providers/single_notifier.dart'; import 'package:titan/user/class/simple_users.dart'; diff --git a/lib/super_admin/providers/section_logo_provider.dart b/lib/admin/providers/section_logo_provider.dart similarity index 90% rename from lib/super_admin/providers/section_logo_provider.dart rename to lib/admin/providers/section_logo_provider.dart index 9a13547d56..61ca4e31f0 100644 --- a/lib/super_admin/providers/section_logo_provider.dart +++ b/lib/admin/providers/section_logo_provider.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/super_admin/class/simple_group.dart'; +import 'package:titan/admin/class/simple_group.dart'; import 'package:titan/tools/providers/map_provider.dart'; class SimpleGroupLogoNotifier extends MapNotifier { diff --git a/lib/super_admin/providers/simple_groups_groups_provider.dart b/lib/admin/providers/simple_groups_groups_provider.dart similarity index 87% rename from lib/super_admin/providers/simple_groups_groups_provider.dart rename to lib/admin/providers/simple_groups_groups_provider.dart index 86a0e11ee9..a9732b8c8d 100644 --- a/lib/super_admin/providers/simple_groups_groups_provider.dart +++ b/lib/admin/providers/simple_groups_groups_provider.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/super_admin/class/group.dart'; -import 'package:titan/super_admin/providers/group_list_provider.dart'; +import 'package:titan/admin/class/group.dart'; +import 'package:titan/admin/providers/group_list_provider.dart'; import 'package:titan/tools/providers/map_provider.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; diff --git a/lib/super_admin/repositories/group_logo_repository.dart b/lib/admin/repositories/group_logo_repository.dart similarity index 100% rename from lib/super_admin/repositories/group_logo_repository.dart rename to lib/admin/repositories/group_logo_repository.dart diff --git a/lib/super_admin/repositories/group_repository.dart b/lib/admin/repositories/group_repository.dart similarity index 94% rename from lib/super_admin/repositories/group_repository.dart rename to lib/admin/repositories/group_repository.dart index 1ee817dcc5..869937d95d 100644 --- a/lib/super_admin/repositories/group_repository.dart +++ b/lib/admin/repositories/group_repository.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/super_admin/class/group.dart'; -import 'package:titan/super_admin/class/simple_group.dart'; +import 'package:titan/admin/class/group.dart'; +import 'package:titan/admin/class/simple_group.dart'; import 'package:titan/auth/providers/openid_provider.dart'; import 'package:titan/tools/repository/repository.dart'; import 'package:titan/user/class/simple_users.dart'; diff --git a/lib/admin/router.dart b/lib/admin/router.dart index 61b472b54f..541835c336 100644 --- a/lib/admin/router.dart +++ b/lib/admin/router.dart @@ -1,13 +1,18 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/admin/ui/pages/groups/add_group_page/add_group_page.dart' + deferred as add_group_page; +import 'package:titan/admin/ui/pages/groups/edit_group_page/edit_group_page.dart' + deferred as edit_group_page; import 'package:titan/admin/ui/pages/main_page/main_page.dart' deferred as main_page; -import 'package:titan/admin/ui/pages/users_groups_management_page/users_groups_management_page.dart' - deferred as users_groups_management_page; +import 'package:titan/admin/ui/pages/groups/groups_page/groups_page.dart' + deferred as groups_page; import 'package:titan/admin/ui/pages/users_management_page/users_management_page.dart' deferred as users_managmement_page; import 'package:titan/admin/ui/pages/group_notifification_page/group_notification_page.dart' deferred as group_notification_page; + import 'package:titan/navigation/class/module.dart'; import 'package:titan/tools/middlewares/authenticated_middleware.dart'; import 'package:titan/tools/middlewares/deferred_middleware.dart'; @@ -19,6 +24,8 @@ class AdminRouter { static const String usersManagement = '/users_management'; static const String usersGroups = '/users_groups'; static const String groupNotification = '/group_notification'; + static const String addGroup = '/add_group'; + static const String editGroup = '/edit_group'; static final Module module = Module( getName: (context) => "Admin", description: "Gérer les utilisateurs de l'application", @@ -49,9 +56,21 @@ class AdminRouter { ), QRoute( path: usersGroups, - builder: () => users_groups_management_page.UsersGroupsManagementPage(), - middleware: [ - DeferredLoadingMiddleware(users_groups_management_page.loadLibrary), + builder: () => groups_page.GroupsPage(), + middleware: [DeferredLoadingMiddleware(groups_page.loadLibrary)], + children: [ + QRoute( + path: addGroup, + builder: () => add_group_page.AddGroupPage(), + middleware: [DeferredLoadingMiddleware(add_group_page.loadLibrary)], + ), + QRoute( + path: editGroup, + builder: () => edit_group_page.EditGroupPage(), + middleware: [ + DeferredLoadingMiddleware(edit_group_page.loadLibrary), + ], + ), ], ), QRoute( diff --git a/lib/admin/ui/components/admin_button.dart b/lib/admin/ui/components/admin_button.dart new file mode 100644 index 0000000000..72352229c5 --- /dev/null +++ b/lib/admin/ui/components/admin_button.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +import 'package:titan/tools/constants.dart'; + +class SuperAdminButton extends StatelessWidget { + final Widget child; + const SuperAdminButton({super.key, required this.child}); + + @override + Widget build(BuildContext context) { + return Container( + width: double.infinity, + margin: const EdgeInsets.symmetric(vertical: 20), + padding: const EdgeInsets.symmetric(vertical: 15), + alignment: Alignment.center, + decoration: BoxDecoration( + gradient: const LinearGradient( + colors: [ColorConstants.gradient1, ColorConstants.gradient2], + ), + boxShadow: [ + BoxShadow( + color: ColorConstants.gradient2.withValues(alpha: 0.5), + blurRadius: 5, + offset: const Offset(2, 2), + spreadRadius: 2, + ), + ], + borderRadius: BorderRadius.circular(15), + ), + child: child, + ); + } +} diff --git a/lib/admin/ui/components/item_card_ui.dart b/lib/admin/ui/components/item_card_ui.dart new file mode 100644 index 0000000000..9adf51fb06 --- /dev/null +++ b/lib/admin/ui/components/item_card_ui.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; + +class ItemCardUi extends StatelessWidget { + final List children; + const ItemCardUi({super.key, required this.children}); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.symmetric(vertical: 10), + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(10), + boxShadow: [ + BoxShadow( + color: Colors.grey.withValues(alpha: 0.2), + blurRadius: 5, + spreadRadius: 2, + ), + ], + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: children, + ), + ); + } +} diff --git a/lib/admin/ui/components/user_ui.dart b/lib/admin/ui/components/user_ui.dart new file mode 100644 index 0000000000..d7ca60174d --- /dev/null +++ b/lib/admin/ui/components/user_ui.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import 'package:heroicons/heroicons.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/tools/constants.dart'; +import 'package:titan/user/class/simple_users.dart'; + +class UserUi extends HookConsumerWidget { + final SimpleUser user; + final void Function() onDelete; + + const UserUi({super.key, required this.user, required this.onDelete}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return SizedBox( + height: 55, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + user.getName(), + style: const TextStyle(fontSize: 15), + overflow: TextOverflow.ellipsis, + ), + ), + GestureDetector( + onTap: onDelete, + child: Container( + padding: const EdgeInsets.all(7), + decoration: BoxDecoration( + gradient: const LinearGradient( + colors: [ColorConstants.background2, Colors.black], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + boxShadow: [ + BoxShadow( + color: ColorConstants.background2.withValues(alpha: 0.4), + offset: const Offset(2, 3), + blurRadius: 5, + ), + ], + borderRadius: const BorderRadius.all(Radius.circular(10)), + ), + child: const HeroIcon( + HeroIcons.trash, + size: 20, + color: Colors.white, + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/super_admin/ui/pages/groups/add_group_page/add_group_page.dart b/lib/admin/ui/pages/groups/add_group_page/add_group_page.dart similarity index 96% rename from lib/super_admin/ui/pages/groups/add_group_page/add_group_page.dart rename to lib/admin/ui/pages/groups/add_group_page/add_group_page.dart index ad27e07be3..3b00abb87a 100644 --- a/lib/super_admin/ui/pages/groups/add_group_page/add_group_page.dart +++ b/lib/admin/ui/pages/groups/add_group_page/add_group_page.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/super_admin/class/simple_group.dart'; -import 'package:titan/super_admin/providers/group_list_provider.dart'; +import 'package:titan/admin/class/simple_group.dart'; +import 'package:titan/admin/providers/group_list_provider.dart'; import 'package:titan/super_admin/ui/admin.dart'; import 'package:titan/super_admin/ui/components/admin_button.dart'; import 'package:titan/super_admin/ui/components/text_editing.dart'; diff --git a/lib/super_admin/ui/pages/groups/edit_group_page/edit_group_page.dart b/lib/admin/ui/pages/groups/edit_group_page/edit_group_page.dart similarity index 91% rename from lib/super_admin/ui/pages/groups/edit_group_page/edit_group_page.dart rename to lib/admin/ui/pages/groups/edit_group_page/edit_group_page.dart index 45c7279680..599e5e27b7 100644 --- a/lib/super_admin/ui/pages/groups/edit_group_page/edit_group_page.dart +++ b/lib/admin/ui/pages/groups/edit_group_page/edit_group_page.dart @@ -2,20 +2,21 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/super_admin/class/group.dart'; -import 'package:titan/super_admin/providers/group_id_provider.dart'; -import 'package:titan/super_admin/providers/group_list_provider.dart'; -import 'package:titan/super_admin/providers/group_provider.dart'; -import 'package:titan/super_admin/providers/simple_groups_groups_provider.dart'; -import 'package:titan/super_admin/ui/admin.dart'; -import 'package:titan/super_admin/ui/components/admin_button.dart'; -import 'package:titan/super_admin/ui/pages/groups/edit_group_page/search_user.dart'; +import 'package:titan/admin/admin.dart'; +import 'package:titan/admin/class/group.dart'; +import 'package:titan/admin/providers/group_id_provider.dart'; +import 'package:titan/admin/providers/group_list_provider.dart'; +import 'package:titan/admin/providers/group_provider.dart'; +import 'package:titan/admin/providers/simple_groups_groups_provider.dart'; +import 'package:titan/admin/ui/components/admin_button.dart'; +import 'package:titan/admin/ui/pages/groups/edit_group_page/search_user.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/builders/auto_loader_child.dart'; import 'package:titan/tools/ui/widgets/align_left_text.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; + import 'package:titan/tools/ui/widgets/text_entry.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/l10n/app_localizations.dart'; @@ -42,7 +43,7 @@ class EditGroupPage extends HookConsumerWidget { displayToast(context, type, msg); } - return SuperAdminTemplate( + return AdminTemplate( child: SingleChildScrollView( physics: const BouncingScrollPhysics(), child: Padding( diff --git a/lib/super_admin/ui/pages/groups/edit_group_page/results.dart b/lib/admin/ui/pages/groups/edit_group_page/results.dart similarity index 95% rename from lib/super_admin/ui/pages/groups/edit_group_page/results.dart rename to lib/admin/ui/pages/groups/edit_group_page/results.dart index a0d7185eba..8afeba63d6 100644 --- a/lib/super_admin/ui/pages/groups/edit_group_page/results.dart +++ b/lib/admin/ui/pages/groups/edit_group_page/results.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/super_admin/class/group.dart'; -import 'package:titan/super_admin/providers/group_provider.dart'; -import 'package:titan/super_admin/providers/simple_groups_groups_provider.dart'; +import 'package:titan/admin/class/group.dart'; +import 'package:titan/admin/providers/group_provider.dart'; +import 'package:titan/admin/providers/simple_groups_groups_provider.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; diff --git a/lib/super_admin/ui/pages/groups/edit_group_page/search_user.dart b/lib/admin/ui/pages/groups/edit_group_page/search_user.dart similarity index 93% rename from lib/super_admin/ui/pages/groups/edit_group_page/search_user.dart rename to lib/admin/ui/pages/groups/edit_group_page/search_user.dart index 9f0260f896..bef26cc727 100644 --- a/lib/super_admin/ui/pages/groups/edit_group_page/search_user.dart +++ b/lib/admin/ui/pages/groups/edit_group_page/search_user.dart @@ -2,12 +2,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/super_admin/class/group.dart'; -import 'package:titan/super_admin/providers/group_id_provider.dart'; -import 'package:titan/super_admin/providers/group_provider.dart'; -import 'package:titan/super_admin/providers/simple_groups_groups_provider.dart'; -import 'package:titan/super_admin/ui/pages/groups/edit_group_page/results.dart'; -import 'package:titan/super_admin/ui/components/user_ui.dart'; +import 'package:titan/admin/class/group.dart'; +import 'package:titan/admin/providers/group_id_provider.dart'; +import 'package:titan/admin/providers/group_provider.dart'; +import 'package:titan/admin/providers/simple_groups_groups_provider.dart'; +import 'package:titan/admin/ui/components/user_ui.dart'; +import 'package:titan/admin/ui/pages/groups/edit_group_page/results.dart'; + import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; diff --git a/lib/super_admin/ui/pages/groups/group_page/group_button.dart b/lib/admin/ui/pages/groups/groups_page/groups_button.dart similarity index 100% rename from lib/super_admin/ui/pages/groups/group_page/group_button.dart rename to lib/admin/ui/pages/groups/groups_page/groups_button.dart diff --git a/lib/super_admin/ui/pages/groups/group_page/group_page.dart b/lib/admin/ui/pages/groups/groups_page/groups_page.dart similarity index 67% rename from lib/super_admin/ui/pages/groups/group_page/group_page.dart rename to lib/admin/ui/pages/groups/groups_page/groups_page.dart index 5cc294c52b..c0ee0d0b12 100644 --- a/lib/super_admin/ui/pages/groups/group_page/group_page.dart +++ b/lib/admin/ui/pages/groups/groups_page/groups_page.dart @@ -1,13 +1,12 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/super_admin/providers/group_id_provider.dart'; -import 'package:titan/super_admin/providers/group_list_provider.dart'; -import 'package:titan/super_admin/router.dart'; -import 'package:titan/super_admin/ui/admin.dart'; -import 'package:titan/super_admin/ui/components/item_card_ui.dart'; -import 'package:titan/super_admin/ui/pages/groups/group_page/group_ui.dart'; -import 'package:titan/loan/providers/loaner_list_provider.dart'; +import 'package:titan/admin/admin.dart'; +import 'package:titan/admin/ui/pages/groups/groups_page/groups_ui.dart'; +import 'package:titan/admin/providers/group_id_provider.dart'; +import 'package:titan/admin/providers/group_list_provider.dart'; +import 'package:titan/admin/router.dart'; +import 'package:titan/admin/ui/components/item_card_ui.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; @@ -26,23 +25,15 @@ class GroupsPage extends HookConsumerWidget { final groups = ref.watch(allGroupListProvider); final groupsNotifier = ref.watch(allGroupListProvider.notifier); final groupIdNotifier = ref.watch(groupIdProvider.notifier); - final loans = ref.watch(loanerListProvider); - final loanListNotifier = ref.watch(loanerListProvider.notifier); ref.watch(userList); void displayToastWithContext(TypeMsg type, String msg) { displayToast(context, type, msg); } - final List loanersId = loans.maybeWhen( - data: (value) => value.map((e) => e.groupManagerId).toList(), - orElse: () => [], - ); - - return SuperAdminTemplate( + return AdminTemplate( child: Refresher( onRefresh: () async { await groupsNotifier.loadGroups(); - await loanListNotifier.loadLoanerList(); }, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 30.0), @@ -75,9 +66,9 @@ class GroupsPage extends HookConsumerWidget { GestureDetector( onTap: () { QR.to( - SuperAdminRouter.root + - SuperAdminRouter.groups + - SuperAdminRouter.addGroup, + AdminRouter.root + + AdminRouter.usersGroups + + AdminRouter.addGroup, ); }, child: ItemCardUi( @@ -92,50 +83,16 @@ class GroupsPage extends HookConsumerWidget { ], ), ), - GestureDetector( - onTap: () { - QR.to( - SuperAdminRouter.root + - SuperAdminRouter.groups + - SuperAdminRouter.addLoaner, - ); - }, - child: ItemCardUi( - children: [ - const Spacer(), - Stack( - clipBehavior: Clip.none, - children: [ - HeroIcon( - HeroIcons.buildingLibrary, - color: Colors.grey.shade700, - size: 40, - ), - Positioned( - right: -2, - top: -2, - child: HeroIcon( - HeroIcons.plus, - size: 15, - color: Colors.grey.shade700, - ), - ), - ], - ), - const Spacer(), - ], - ), - ), + ...g.map( (group) => GroupUi( group: group, - isLoaner: loanersId.contains(group.id), onEdit: () { groupIdNotifier.setId(group.id); QR.to( - SuperAdminRouter.root + - SuperAdminRouter.groups + - SuperAdminRouter.editGroup, + AdminRouter.root + + AdminRouter.usersGroups + + AdminRouter.editGroup, ); }, onDelete: () async { diff --git a/lib/super_admin/ui/pages/groups/group_page/group_ui.dart b/lib/admin/ui/pages/groups/groups_page/groups_ui.dart similarity index 78% rename from lib/super_admin/ui/pages/groups/group_page/group_ui.dart rename to lib/admin/ui/pages/groups/groups_page/groups_ui.dart index b97a691a72..438ff7d071 100644 --- a/lib/super_admin/ui/pages/groups/group_page/group_ui.dart +++ b/lib/admin/ui/pages/groups/groups_page/groups_ui.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/super_admin/class/simple_group.dart'; -import 'package:titan/super_admin/ui/components/item_card_ui.dart'; -import 'package:titan/super_admin/ui/pages/groups/group_page/group_button.dart'; +import 'package:titan/admin/class/simple_group.dart'; +import 'package:titan/admin/ui/components/item_card_ui.dart'; +import 'package:titan/admin/ui/pages/groups/groups_page/groups_button.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; @@ -11,13 +11,11 @@ class GroupUi extends HookConsumerWidget { final SimpleGroup group; final void Function() onEdit; final Future Function() onDelete; - final bool isLoaner; const GroupUi({ super.key, required this.group, required this.onEdit, required this.onDelete, - required this.isLoaner, }); @override @@ -25,13 +23,6 @@ class GroupUi extends HookConsumerWidget { return ItemCardUi( children: [ const SizedBox(width: 10), - if (isLoaner) - Row( - children: [ - HeroIcon(HeroIcons.buildingLibrary, color: Colors.grey.shade700), - const SizedBox(width: 15), - ], - ), Expanded( child: Text( group.name, diff --git a/lib/advert/ui/pages/form_page/add_rem_announcer_page.dart b/lib/advert/ui/pages/form_page/add_rem_announcer_page.dart index 2add07c9bd..b79365ca47 100644 --- a/lib/advert/ui/pages/form_page/add_rem_announcer_page.dart +++ b/lib/advert/ui/pages/form_page/add_rem_announcer_page.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/super_admin/providers/group_list_provider.dart'; +import 'package:titan/admin/providers/group_list_provider.dart'; import 'package:titan/advert/class/announcer.dart'; import 'package:titan/advert/providers/all_announcer_list_provider.dart'; import 'package:titan/advert/providers/announcer_list_provider.dart'; diff --git a/lib/advert/ui/pages/form_page/announcer_card.dart b/lib/advert/ui/pages/form_page/announcer_card.dart index 26f9d0c83b..8a118d8d21 100644 --- a/lib/advert/ui/pages/form_page/announcer_card.dart +++ b/lib/advert/ui/pages/form_page/announcer_card.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; -import 'package:titan/super_admin/class/simple_group.dart'; +import 'package:titan/admin/class/simple_group.dart'; class AnnouncerCard extends StatelessWidget { final SimpleGroup e; diff --git a/lib/booking/ui/pages/admin_pages/add_edit_manager_page.dart b/lib/booking/ui/pages/admin_pages/add_edit_manager_page.dart index 8cceac843e..c02ac990b2 100644 --- a/lib/booking/ui/pages/admin_pages/add_edit_manager_page.dart +++ b/lib/booking/ui/pages/admin_pages/add_edit_manager_page.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:titan/super_admin/class/simple_group.dart'; -import 'package:titan/super_admin/providers/group_id_provider.dart'; +import 'package:titan/admin/class/simple_group.dart'; +import 'package:titan/admin/providers/group_id_provider.dart'; import 'package:titan/booking/class/manager.dart'; import 'package:titan/booking/providers/manager_list_provider.dart'; import 'package:titan/booking/providers/manager_provider.dart'; @@ -15,7 +15,7 @@ import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/layouts/item_chip.dart'; import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; import 'package:qlevar_router/qlevar_router.dart'; -import 'package:titan/super_admin/providers/group_list_provider.dart'; +import 'package:titan/admin/providers/group_list_provider.dart'; import 'package:titan/l10n/app_localizations.dart'; class AddEditManagerPage extends HookConsumerWidget { diff --git a/lib/booking/ui/pages/admin_pages/admin_page.dart b/lib/booking/ui/pages/admin_pages/admin_page.dart index a906b8f28a..7c196517e2 100644 --- a/lib/booking/ui/pages/admin_pages/admin_page.dart +++ b/lib/booking/ui/pages/admin_pages/admin_page.dart @@ -3,7 +3,7 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/super_admin/providers/group_id_provider.dart'; +import 'package:titan/admin/providers/group_id_provider.dart'; import 'package:titan/booking/class/manager.dart'; import 'package:titan/service/class/room.dart'; import 'package:titan/booking/providers/confirmed_booking_list_provider.dart'; diff --git a/lib/phonebook/ui/pages/association_editor_page/association_information_editor.dart b/lib/phonebook/ui/pages/association_editor_page/association_information_editor.dart index 5f3bad11c1..061b5ca7e9 100644 --- a/lib/phonebook/ui/pages/association_editor_page/association_information_editor.dart +++ b/lib/phonebook/ui/pages/association_editor_page/association_information_editor.dart @@ -2,8 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/super_admin/class/simple_group.dart'; -import 'package:titan/super_admin/providers/group_list_provider.dart'; +import 'package:titan/admin/class/simple_group.dart'; +import 'package:titan/admin/providers/group_list_provider.dart'; import 'package:titan/super_admin/providers/is_admin_provider.dart'; import 'package:titan/phonebook/providers/association_kind_provider.dart'; import 'package:titan/phonebook/providers/association_list_provider.dart'; diff --git a/lib/raffle/class/raffle.dart b/lib/raffle/class/raffle.dart index c934719a60..2b8ff7fa99 100644 --- a/lib/raffle/class/raffle.dart +++ b/lib/raffle/class/raffle.dart @@ -1,4 +1,4 @@ -import 'package:titan/super_admin/class/simple_group.dart'; +import 'package:titan/admin/class/simple_group.dart'; import 'package:titan/raffle/class/raffle_status_type.dart'; import 'package:titan/raffle/tools/functions.dart'; diff --git a/lib/raffle/ui/pages/admin_module_page/confirm_creation.dart b/lib/raffle/ui/pages/admin_module_page/confirm_creation.dart index b108650547..e2c03ffb3f 100644 --- a/lib/raffle/ui/pages/admin_module_page/confirm_creation.dart +++ b/lib/raffle/ui/pages/admin_module_page/confirm_creation.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/super_admin/class/simple_group.dart'; +import 'package:titan/admin/class/simple_group.dart'; import 'package:titan/raffle/class/raffle.dart'; import 'package:titan/raffle/class/raffle_status_type.dart'; import 'package:titan/raffle/providers/raffle_list_provider.dart'; diff --git a/lib/raffle/ui/pages/admin_module_page/tombola_handler.dart b/lib/raffle/ui/pages/admin_module_page/tombola_handler.dart index ccdfd298db..4af0d41b0e 100644 --- a/lib/raffle/ui/pages/admin_module_page/tombola_handler.dart +++ b/lib/raffle/ui/pages/admin_module_page/tombola_handler.dart @@ -2,8 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/super_admin/class/simple_group.dart'; -import 'package:titan/super_admin/providers/group_list_provider.dart'; +import 'package:titan/admin/class/simple_group.dart'; +import 'package:titan/admin/providers/group_list_provider.dart'; import 'package:titan/raffle/providers/raffle_list_provider.dart'; import 'package:titan/raffle/tools/constants.dart'; import 'package:titan/raffle/ui/pages/admin_module_page/confirm_creation.dart'; diff --git a/lib/super_admin/notification_service.dart b/lib/super_admin/notification_service.dart index a8ac035d5e..c49cf574dc 100644 --- a/lib/super_admin/notification_service.dart +++ b/lib/super_admin/notification_service.dart @@ -1,5 +1,5 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/super_admin/providers/group_list_provider.dart'; +import 'package:titan/admin/providers/group_list_provider.dart'; import 'package:titan/super_admin/router.dart'; import 'package:titan/router.dart'; import 'package:titan/user/providers/user_provider.dart'; diff --git a/lib/super_admin/router.dart b/lib/super_admin/router.dart index 835b6f2517..24391e45d3 100644 --- a/lib/super_admin/router.dart +++ b/lib/super_admin/router.dart @@ -1,15 +1,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/super_admin/providers/is_admin_provider.dart'; -import 'package:titan/super_admin/ui/pages/groups/add_group_page/add_group_page.dart' - deferred as add_group_page; -import 'package:titan/super_admin/ui/pages/groups/add_loaner_page/add_loaner_page.dart' - deferred as add_loaner_page; + import 'package:titan/super_admin/ui/pages/edit_module_visibility/edit_module_visibility.dart' deferred as edit_module_visibility; -import 'package:titan/super_admin/ui/pages/groups/edit_group_page/edit_group_page.dart' - deferred as edit_group_page; -import 'package:titan/super_admin/ui/pages/groups/group_page/group_page.dart' - deferred as group_page; + import 'package:titan/super_admin/ui/pages/memberships/add_edit_user_membership_page/add_edit_user_membership_page.dart' deferred as add_edit_user_membership_page; import 'package:titan/super_admin/ui/pages/memberships/association_membership_detail_page/association_membership_detail_page.dart' @@ -68,32 +62,6 @@ class SuperAdminRouter { DeferredLoadingMiddleware(main_page.loadLibrary), ], children: [ - QRoute( - path: groups, - builder: () => group_page.GroupsPage(), - middleware: [DeferredLoadingMiddleware(group_page.loadLibrary)], - children: [ - QRoute( - path: addGroup, - builder: () => add_group_page.AddGroupPage(), - middleware: [DeferredLoadingMiddleware(add_group_page.loadLibrary)], - ), - QRoute( - path: editGroup, - builder: () => edit_group_page.EditGroupPage(), - middleware: [ - DeferredLoadingMiddleware(edit_group_page.loadLibrary), - ], - ), - QRoute( - path: addLoaner, - builder: () => add_loaner_page.AddLoanerPage(), - middleware: [ - DeferredLoadingMiddleware(add_loaner_page.loadLibrary), - ], - ), - ], - ), QRoute( path: editModuleVisibility, builder: () => edit_module_visibility.EditModulesVisibilityPage(), diff --git a/lib/super_admin/ui/pages/edit_module_visibility/edit_module_visibility.dart b/lib/super_admin/ui/pages/edit_module_visibility/edit_module_visibility.dart index a671bfc3ee..86809dbae9 100644 --- a/lib/super_admin/ui/pages/edit_module_visibility/edit_module_visibility.dart +++ b/lib/super_admin/ui/pages/edit_module_visibility/edit_module_visibility.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/super_admin/providers/all_account_types_list_provider.dart'; -import 'package:titan/super_admin/providers/all_groups_list_provider.dart'; +import 'package:titan/admin/providers/all_groups_list_provider.dart'; import 'package:titan/super_admin/providers/module_visibility_list_provider.dart'; import 'package:titan/super_admin/ui/admin.dart'; import 'package:titan/super_admin/ui/pages/edit_module_visibility/modules_expansion_panel.dart'; diff --git a/lib/super_admin/ui/pages/edit_module_visibility/modules_expansion_panel.dart b/lib/super_admin/ui/pages/edit_module_visibility/modules_expansion_panel.dart index 479682828d..7cee25b697 100644 --- a/lib/super_admin/ui/pages/edit_module_visibility/modules_expansion_panel.dart +++ b/lib/super_admin/ui/pages/edit_module_visibility/modules_expansion_panel.dart @@ -4,7 +4,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/super_admin/class/account_type.dart'; import 'package:titan/super_admin/class/module_visibility.dart'; import 'package:titan/super_admin/providers/all_account_types_list_provider.dart'; -import 'package:titan/super_admin/providers/all_groups_list_provider.dart'; +import 'package:titan/admin/providers/all_groups_list_provider.dart'; import 'package:titan/super_admin/providers/is_expanded_list_provider.dart'; import 'package:titan/super_admin/providers/module_visibility_list_provider.dart'; import 'package:titan/l10n/app_localizations.dart'; diff --git a/lib/super_admin/ui/pages/groups/add_loaner_page/add_loaner_page.dart b/lib/super_admin/ui/pages/groups/add_loaner_page/add_loaner_page.dart deleted file mode 100644 index 5ab333769b..0000000000 --- a/lib/super_admin/ui/pages/groups/add_loaner_page/add_loaner_page.dart +++ /dev/null @@ -1,133 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:heroicons/heroicons.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/super_admin/providers/group_list_provider.dart'; -import 'package:titan/super_admin/ui/admin.dart'; -import 'package:titan/loan/class/loaner.dart'; -import 'package:titan/loan/providers/all_loaner_list_provider.dart'; -import 'package:titan/loan/providers/loaner_list_provider.dart'; -import 'package:titan/tools/functions.dart'; -import 'package:titan/tools/token_expire_wrapper.dart'; -import 'package:titan/tools/ui/widgets/align_left_text.dart'; -import 'package:titan/tools/ui/builders/async_child.dart'; -import 'package:qlevar_router/qlevar_router.dart'; -import 'package:titan/l10n/app_localizations.dart'; - -class AddLoanerPage extends HookConsumerWidget { - const AddLoanerPage({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final loanerListNotifier = ref.watch(loanerListProvider.notifier); - final loaners = ref.watch(allLoanerList); - final associations = ref.watch(allGroupListProvider); - final loanersId = loaners.map((x) => x.groupManagerId).toList(); - void displayToastWithContext(TypeMsg type, String msg) { - displayToast(context, type, msg); - } - - return SuperAdminTemplate( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 30.0), - child: SingleChildScrollView( - physics: const AlwaysScrollableScrollPhysics( - parent: BouncingScrollPhysics(), - ), - child: Column( - children: [ - SizedBox( - child: Column( - children: [ - AlignLeftText( - AppLocalizations.of(context)!.adminAddLoaningGroup, - ), - const SizedBox(height: 30), - AsyncChild( - value: associations, - builder: (context, associationList) { - final canAdd = associationList - .where((x) => !loanersId.contains(x.id)) - .toList(); - return canAdd.isNotEmpty - ? Column( - children: canAdd - .map( - (e) => GestureDetector( - onTap: () { - Loaner newLoaner = Loaner( - groupManagerId: e.id, - id: '', - name: e.name, - ); - tokenExpireWrapper(ref, () async { - final addedLoanerMsg = - AppLocalizations.of( - context, - )!.adminAddedLoaner; - final addingErrorMsg = - AppLocalizations.of( - context, - )!.adminAddingError; - final value = - await loanerListNotifier - .addLoaner(newLoaner); - if (value) { - QR.back(); - displayToastWithContext( - TypeMsg.msg, - addedLoanerMsg, - ); - } else { - displayToastWithContext( - TypeMsg.error, - addingErrorMsg, - ); - } - }); - }, - child: Container( - padding: const EdgeInsets.symmetric( - vertical: 20, - ), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Text( - e.name, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.w500, - ), - ), - const HeroIcon( - HeroIcons.plus, - size: 25, - color: Colors.black, - ), - ], - ), - ), - ), - ) - .toList(), - ) - : Center( - child: Text( - AppLocalizations.of( - context, - )!.adminNoMoreLoaner, - ), - ); - }, - ), - ], - ), - ), - ], - ), - ), - ), - ); - } -} diff --git a/lib/super_admin/ui/pages/memberships/association_membership_detail_page/association_membership_information_editor.dart b/lib/super_admin/ui/pages/memberships/association_membership_detail_page/association_membership_information_editor.dart index 2b6c3cbea7..ad603d3afb 100644 --- a/lib/super_admin/ui/pages/memberships/association_membership_detail_page/association_membership_information_editor.dart +++ b/lib/super_admin/ui/pages/memberships/association_membership_detail_page/association_membership_information_editor.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/super_admin/providers/all_groups_list_provider.dart'; +import 'package:titan/admin/providers/all_groups_list_provider.dart'; import 'package:titan/super_admin/providers/association_membership_list_provider.dart'; import 'package:titan/super_admin/providers/association_membership_provider.dart'; import 'package:titan/tools/constants.dart'; diff --git a/lib/super_admin/ui/pages/memberships/association_membership_page/association_membership_creation_dialog.dart b/lib/super_admin/ui/pages/memberships/association_membership_page/association_membership_creation_dialog.dart index 3509ff9716..39d485c27d 100644 --- a/lib/super_admin/ui/pages/memberships/association_membership_page/association_membership_creation_dialog.dart +++ b/lib/super_admin/ui/pages/memberships/association_membership_page/association_membership_creation_dialog.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:titan/super_admin/class/simple_group.dart'; +import 'package:titan/admin/class/simple_group.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/l10n/app_localizations.dart'; diff --git a/lib/super_admin/ui/pages/memberships/association_membership_page/association_membership_page.dart b/lib/super_admin/ui/pages/memberships/association_membership_page/association_membership_page.dart index 2a2c407767..01ca138be7 100644 --- a/lib/super_admin/ui/pages/memberships/association_membership_page/association_membership_page.dart +++ b/lib/super_admin/ui/pages/memberships/association_membership_page/association_membership_page.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/super_admin/class/association_membership_simple.dart'; -import 'package:titan/super_admin/providers/all_groups_list_provider.dart'; +import 'package:titan/admin/providers/all_groups_list_provider.dart'; import 'package:titan/super_admin/providers/association_membership_list_provider.dart'; import 'package:titan/super_admin/providers/association_membership_members_list_provider.dart'; import 'package:titan/super_admin/providers/association_membership_provider.dart'; diff --git a/lib/user/class/user.dart b/lib/user/class/user.dart index 7e51705b5a..6a27978926 100644 --- a/lib/user/class/user.dart +++ b/lib/user/class/user.dart @@ -1,5 +1,5 @@ import 'package:titan/super_admin/class/account_type.dart'; -import 'package:titan/super_admin/class/simple_group.dart'; +import 'package:titan/admin/class/simple_group.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/user/class/applicant.dart'; import 'package:titan/user/class/simple_users.dart'; diff --git a/lib/user/providers/user_list_provider.dart b/lib/user/providers/user_list_provider.dart index 443133bf0a..c24905f91a 100644 --- a/lib/user/providers/user_list_provider.dart +++ b/lib/user/providers/user_list_provider.dart @@ -1,5 +1,5 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/super_admin/class/simple_group.dart'; +import 'package:titan/admin/class/simple_group.dart'; import 'package:titan/tools/providers/list_notifier.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/user/class/simple_users.dart'; diff --git a/lib/vote/providers/voting_group_list_provider.dart b/lib/vote/providers/voting_group_list_provider.dart index be2179fcc7..e79fb11de7 100644 --- a/lib/vote/providers/voting_group_list_provider.dart +++ b/lib/vote/providers/voting_group_list_provider.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/super_admin/class/simple_group.dart'; -import 'package:titan/super_admin/providers/group_list_provider.dart'; +import 'package:titan/admin/class/simple_group.dart'; +import 'package:titan/admin/providers/group_list_provider.dart'; import 'package:titan/vote/providers/voter_list_provider.dart'; final votingGroupListProvider = Provider>((ref) { diff --git a/lib/vote/ui/pages/admin_page/voters_bar.dart b/lib/vote/ui/pages/admin_page/voters_bar.dart index a6ca4093b6..81dac4a033 100644 --- a/lib/vote/ui/pages/admin_page/voters_bar.dart +++ b/lib/vote/ui/pages/admin_page/voters_bar.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/super_admin/providers/group_list_provider.dart'; +import 'package:titan/admin/providers/group_list_provider.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/vote/class/voter.dart'; import 'package:titan/vote/providers/status_provider.dart'; diff --git a/scripts/test.dart b/scripts/test.dart new file mode 100644 index 0000000000..1635d2d766 --- /dev/null +++ b/scripts/test.dart @@ -0,0 +1,65 @@ +import 'dart:io'; + +void main() { + final directoryPath = "."; + final searchStart = "VoteTextConstants."; + final replaceStart = "AppLocalizations.of(context)!.vote"; + final importStatement = "import 'package:titan/l10n/app_localizations.dart';"; + + final directory = Directory(directoryPath); + + if (!directory.existsSync()) { + print('Le dossier $directoryPath n\'existe pas.'); + exit(1); + } + + final files = directory + .listSync(recursive: true) + .whereType() + .where((file) => file.path.endsWith('.dart')) + .toList(); + + print('Fichiers à traiter : ${files.length}'); + for (var i = 'a'.codeUnitAt(0); i <= 'z'.codeUnitAt(0); i++) { + var letter = String.fromCharCode(i); + final search = '$searchStart$letter'; + final replace = '$replaceStart${letter.toUpperCase()}'; + for (final file in files) { + final content = file.readAsStringSync(); + + if (content.contains(search)) { + // Remplacer la chaîne + var newContent = content.replaceAll(search, replace); + + // Ajouter l'import si absent + if (!newContent.contains(importStatement)) { + // Trouver la fin des imports existants + final lines = newContent.split('\n'); + int insertIndex = 0; + + for (var j = 0; j < lines.length; j++) { + final line = lines[j].trim(); + // On suppose que les imports commencent par 'import' ou 'part' + if (line.startsWith('import ') || line.startsWith('part ')) { + insertIndex = j + 1; + } else if (line.isEmpty) { + // Ignorer les lignes vides, continuer la recherche + continue; + } else { + // On a atteint une ligne qui n'est ni import ni part ni vide -> stop + break; + } + } + + lines.insert(insertIndex, importStatement); + newContent = lines.join('\n'); + } + + file.writeAsStringSync(newContent); + print('Modifié : ${file.path}'); + } + } + } + + print('Remplacement terminé.'); +} diff --git a/test/admin/admin_test.dart b/test/admin/admin_test.dart index 9ebcd978de..8566a26536 100644 --- a/test/admin/admin_test.dart +++ b/test/admin/admin_test.dart @@ -2,9 +2,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:titan/super_admin/class/account_type.dart'; -import 'package:titan/super_admin/class/group.dart'; -import 'package:titan/super_admin/class/simple_group.dart'; -import 'package:titan/super_admin/repositories/group_repository.dart'; +import 'package:titan/admin/class/group.dart'; +import 'package:titan/admin/class/simple_group.dart'; +import 'package:titan/admin/repositories/group_repository.dart'; import 'package:titan/user/class/simple_users.dart'; import 'package:titan/user/class/user.dart'; diff --git a/test/admin/group_id_provider_test.dart b/test/admin/group_id_provider_test.dart index d99205faa6..6055f403e2 100644 --- a/test/admin/group_id_provider_test.dart +++ b/test/admin/group_id_provider_test.dart @@ -1,6 +1,6 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/super_admin/providers/group_id_provider.dart'; +import 'package:titan/admin/providers/group_id_provider.dart'; void main() { group('GroupIdNotifier', () { diff --git a/test/admin/group_list_provider_test.dart b/test/admin/group_list_provider_test.dart index 1b1c80d46e..5b43e85723 100644 --- a/test/admin/group_list_provider_test.dart +++ b/test/admin/group_list_provider_test.dart @@ -2,9 +2,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:titan/super_admin/class/account_type.dart'; -import 'package:titan/super_admin/class/simple_group.dart'; -import 'package:titan/super_admin/providers/group_list_provider.dart'; -import 'package:titan/super_admin/repositories/group_repository.dart'; +import 'package:titan/admin/class/simple_group.dart'; +import 'package:titan/admin/providers/group_list_provider.dart'; +import 'package:titan/admin/repositories/group_repository.dart'; import 'package:titan/user/class/user.dart'; class MockGroupRepository extends Mock implements GroupRepository {} diff --git a/test/admin/group_logo_provider_test.dart b/test/admin/group_logo_provider_test.dart index 2a8aa8ce77..b3aa595508 100644 --- a/test/admin/group_logo_provider_test.dart +++ b/test/admin/group_logo_provider_test.dart @@ -3,8 +3,8 @@ import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; -import 'package:titan/super_admin/providers/group_logo_provider.dart'; -import 'package:titan/super_admin/repositories/group_logo_repository.dart'; +import 'package:titan/admin/providers/group_logo_provider.dart'; +import 'package:titan/admin/repositories/group_logo_repository.dart'; class MockGroupLogoRepository extends Mock implements GroupLogoRepository {} diff --git a/test/admin/group_provider_test.dart b/test/admin/group_provider_test.dart index 7d8d21d684..a373824ec0 100644 --- a/test/admin/group_provider_test.dart +++ b/test/admin/group_provider_test.dart @@ -1,9 +1,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; -import 'package:titan/super_admin/class/group.dart'; -import 'package:titan/super_admin/providers/group_provider.dart'; -import 'package:titan/super_admin/repositories/group_repository.dart'; +import 'package:titan/admin/class/group.dart'; +import 'package:titan/admin/providers/group_provider.dart'; +import 'package:titan/admin/repositories/group_repository.dart'; import 'package:titan/user/class/simple_users.dart'; class MockGroupRepository extends Mock implements GroupRepository {} diff --git a/test/admin/is_admin_test.dart b/test/admin/is_admin_test.dart index d791031f72..1634dd70cb 100644 --- a/test/admin/is_admin_test.dart +++ b/test/admin/is_admin_test.dart @@ -1,6 +1,6 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/super_admin/class/simple_group.dart'; +import 'package:titan/admin/class/simple_group.dart'; import 'package:titan/super_admin/providers/is_admin_provider.dart'; import 'package:titan/user/class/user.dart'; import 'package:titan/user/providers/user_provider.dart'; diff --git a/test/amap/is_amap_admin_provider_test.dart b/test/amap/is_amap_admin_provider_test.dart index 19f0b740e4..8ff091f076 100644 --- a/test/amap/is_amap_admin_provider_test.dart +++ b/test/amap/is_amap_admin_provider_test.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:titan/super_admin/class/simple_group.dart'; +import 'package:titan/admin/class/simple_group.dart'; import 'package:titan/amap/providers/is_amap_admin_provider.dart'; import 'package:titan/user/class/user.dart'; import 'package:titan/user/providers/user_provider.dart'; diff --git a/test/booking/is_booking_admin_provider_test.dart b/test/booking/is_booking_admin_provider_test.dart index 03f5314658..90906b759f 100644 --- a/test/booking/is_booking_admin_provider_test.dart +++ b/test/booking/is_booking_admin_provider_test.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:titan/super_admin/class/simple_group.dart'; +import 'package:titan/admin/class/simple_group.dart'; import 'package:titan/booking/providers/is_admin_provider.dart'; import 'package:titan/user/class/user.dart'; import 'package:titan/user/providers/user_provider.dart'; diff --git a/test/cinema/is_cinema_admin_test.dart b/test/cinema/is_cinema_admin_test.dart index 045a497c92..dc728268c0 100644 --- a/test/cinema/is_cinema_admin_test.dart +++ b/test/cinema/is_cinema_admin_test.dart @@ -1,6 +1,6 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/super_admin/class/simple_group.dart'; +import 'package:titan/admin/class/simple_group.dart'; import 'package:titan/cinema/providers/is_cinema_admin.dart'; import 'package:titan/user/class/user.dart'; import 'package:titan/user/providers/user_provider.dart'; diff --git a/test/event/is_admin_provider_test.dart b/test/event/is_admin_provider_test.dart index 6d6083a845..ad6b12a565 100644 --- a/test/event/is_admin_provider_test.dart +++ b/test/event/is_admin_provider_test.dart @@ -1,6 +1,6 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/super_admin/class/simple_group.dart'; +import 'package:titan/admin/class/simple_group.dart'; import 'package:titan/event/providers/is_admin_provider.dart'; import 'package:titan/user/class/user.dart'; import 'package:titan/user/providers/user_provider.dart'; diff --git a/test/user/user_list_provider_test.dart b/test/user/user_list_provider_test.dart index 400404dc6f..fd09ed7c28 100644 --- a/test/user/user_list_provider_test.dart +++ b/test/user/user_list_provider_test.dart @@ -1,7 +1,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; -import 'package:titan/super_admin/class/simple_group.dart'; +import 'package:titan/admin/class/simple_group.dart'; import 'package:titan/user/class/simple_users.dart'; import 'package:titan/user/providers/user_list_provider.dart'; import 'package:titan/user/repositories/user_list_repository.dart'; diff --git a/test/vote/is_vote_admin_provider_test.dart b/test/vote/is_vote_admin_provider_test.dart index 66cf2f2ee0..e878e98bb7 100644 --- a/test/vote/is_vote_admin_provider_test.dart +++ b/test/vote/is_vote_admin_provider_test.dart @@ -1,6 +1,6 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/super_admin/class/simple_group.dart'; +import 'package:titan/admin/class/simple_group.dart'; import 'package:titan/user/class/user.dart'; import 'package:titan/user/providers/user_provider.dart'; import 'package:titan/vote/providers/is_vote_admin_provider.dart'; From 33def64b248f8c6c8c109e95c646a9fc4755add4 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Fri, 18 Jul 2025 13:01:22 +0200 Subject: [PATCH 126/473] send notification --- .../repositories/notification_repository.dart | 26 ++++ .../group_notification_page.dart | 132 ++++++++++++++++-- 2 files changed, 148 insertions(+), 10 deletions(-) create mode 100644 lib/admin/repositories/notification_repository.dart diff --git a/lib/admin/repositories/notification_repository.dart b/lib/admin/repositories/notification_repository.dart new file mode 100644 index 0000000000..e831bc5c51 --- /dev/null +++ b/lib/admin/repositories/notification_repository.dart @@ -0,0 +1,26 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/auth/providers/openid_provider.dart'; +import 'package:titan/tools/repository/repository.dart'; + +class NotificationRepository extends Repository { + @override + // ignore: overridden_fields + final ext = "notification/"; + + Future sendNotification( + String groupId, + String title, + String content, + ) async { + return await create({ + "group_id": groupId, + "title": title, + "content": content, + }, suffix: "send"); + } +} + +final notificationRepositoryProvider = Provider((ref) { + final token = ref.watch(tokenProvider); + return NotificationRepository()..setToken(token); +}); diff --git a/lib/admin/ui/pages/group_notifification_page/group_notification_page.dart b/lib/admin/ui/pages/group_notifification_page/group_notification_page.dart index 2c062a5100..820aaad99e 100644 --- a/lib/admin/ui/pages/group_notifification_page/group_notification_page.dart +++ b/lib/admin/ui/pages/group_notifification_page/group_notification_page.dart @@ -1,23 +1,135 @@ import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/admin.dart'; +import 'package:titan/admin/providers/group_list_provider.dart'; +import 'package:titan/admin/repositories/notification_repository.dart'; +import 'package:titan/l10n/app_localizations.dart'; +import 'package:titan/tools/constants.dart'; +import 'package:titan/tools/functions.dart'; +import 'package:titan/tools/ui/builders/async_child.dart'; +import 'package:titan/tools/ui/layouts/refresher.dart'; +import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; +import 'package:titan/tools/ui/styleguide/button.dart'; +import 'package:titan/tools/ui/styleguide/list_item.dart'; +import 'package:titan/tools/ui/widgets/text_entry.dart'; class GroupNotificationPage extends HookConsumerWidget { const GroupNotificationPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { + final groups = ref.watch(allGroupListProvider); + final groupsNotifier = ref.watch(allGroupListProvider.notifier); + final notificationRepository = ref.watch(notificationRepositoryProvider); + final titleController = useTextEditingController(); + final contentController = useTextEditingController(); + void displayToastWithContext(TypeMsg type, String msg) { + displayToast(context, type, msg); + } + return AdminTemplate( - child: Padding( - padding: const EdgeInsets.all(40), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Notification de groupe", - style: Theme.of(context).textTheme.headlineMedium, - ), - ], + child: Refresher( + onRefresh: () async { + await groupsNotifier.loadGroups(); + }, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 30.0), + child: Column( + children: [ + const SizedBox(height: 20), + Align( + alignment: Alignment.centerLeft, + child: Text( + AppLocalizations.of(context)!.adminGroups, + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.w700, + color: ColorConstants.gradient1, + ), + ), + ), + const SizedBox(height: 30), + AsyncChild( + value: groups, + builder: (context, g) { + g.sort( + (a, b) => + a.name.toLowerCase().compareTo(b.name.toLowerCase()), + ); + return Column( + children: [ + Column( + children: [ + ...g.map( + (group) => ListItem( + title: group.name, + onTap: () async { + await showCustomBottomModal( + context: context, + ref: ref, + modal: BottomModalTemplate( + title: "Notifier le groupe ${group.name}", + child: Column( + children: [ + TextEntry( + label: 'Titre', + controller: titleController, + ), + const SizedBox(height: 20), + TextEntry( + label: 'Contenu', + controller: contentController, + maxLines: 5, + ), + const SizedBox(height: 20), + Button( + text: "Envoyer", + onPressed: () { + notificationRepository + .sendNotification( + group.id, + titleController.text, + contentController.text, + ) + .then((value) { + if (value) { + displayToastWithContext( + TypeMsg.msg, + "Notification envoyée avec succès", + ); + } else { + displayToastWithContext( + TypeMsg.error, + "Échec de l'envoi de la notification", + ); + } + }) + .catchError((error) { + displayToastWithContext( + TypeMsg.error, + error.toString(), + ); + }); + }, + ), + ], + ), + ), + ); + }, + ), + ), + const SizedBox(height: 20), + ], + ), + ], + ); + }, + loaderColor: ColorConstants.gradient1, + ), + ], + ), ), ), ); From c87145200eca55fe5e9d46d9c381f209ffeaac05 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Fri, 18 Jul 2025 17:49:20 +0200 Subject: [PATCH 127/473] add user to group --- .../group_from_simple_group_provider.dart | 27 +++ .../group_notification_page.dart | 11 +- .../edit_group_page/edit_group_page.dart | 146 ++------------ .../groups/edit_group_page/search_user.dart | 35 ++-- .../pages/groups/groups_page/groups_page.dart | 188 +++++++++++------- lib/tools/providers/single_map_provider.dart | 51 +++++ .../ui/builders/single_auto_loader_child.dart | 49 +++++ lib/tools/ui/styleguide/text_entry.dart | 3 + 8 files changed, 281 insertions(+), 229 deletions(-) create mode 100644 lib/admin/providers/group_from_simple_group_provider.dart create mode 100644 lib/tools/providers/single_map_provider.dart create mode 100644 lib/tools/ui/builders/single_auto_loader_child.dart diff --git a/lib/admin/providers/group_from_simple_group_provider.dart b/lib/admin/providers/group_from_simple_group_provider.dart new file mode 100644 index 0000000000..2beac831dc --- /dev/null +++ b/lib/admin/providers/group_from_simple_group_provider.dart @@ -0,0 +1,27 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/admin/class/group.dart'; +import 'package:titan/admin/providers/group_list_provider.dart'; +import 'package:titan/tools/providers/single_map_provider.dart'; +import 'package:titan/tools/token_expire_wrapper.dart'; + +class GroupFromSimpleGroupNotifier extends SingleMapNotifier { + GroupFromSimpleGroupNotifier() : super(); +} + +final groupFromSimpleGroupProvider = + StateNotifierProvider< + GroupFromSimpleGroupNotifier, + Map?> + >((ref) { + GroupFromSimpleGroupNotifier groupFromSimpleGroupNotifier = + GroupFromSimpleGroupNotifier(); + tokenExpireWrapperAuth(ref, () async { + final simpleGroups = ref.watch(allGroupListProvider); + simpleGroups.whenData((value) { + groupFromSimpleGroupNotifier.loadTList( + value.map((e) => e.id).toList(), + ); + }); + }); + return groupFromSimpleGroupNotifier; + }); diff --git a/lib/admin/ui/pages/group_notifification_page/group_notification_page.dart b/lib/admin/ui/pages/group_notifification_page/group_notification_page.dart index 820aaad99e..0dc57e0d48 100644 --- a/lib/admin/ui/pages/group_notifification_page/group_notification_page.dart +++ b/lib/admin/ui/pages/group_notifification_page/group_notification_page.dart @@ -41,11 +41,11 @@ class GroupNotificationPage extends HookConsumerWidget { Align( alignment: Alignment.centerLeft, child: Text( - AppLocalizations.of(context)!.adminGroups, - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.w700, - color: ColorConstants.gradient1, + "Notifications de groupe", + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: ColorConstants.title, ), ), ), @@ -64,6 +64,7 @@ class GroupNotificationPage extends HookConsumerWidget { ...g.map( (group) => ListItem( title: group.name, + subtitle: group.description, onTap: () async { await showCustomBottomModal( context: context, diff --git a/lib/admin/ui/pages/groups/edit_group_page/edit_group_page.dart b/lib/admin/ui/pages/groups/edit_group_page/edit_group_page.dart index 599e5e27b7..ff71b30bc1 100644 --- a/lib/admin/ui/pages/groups/edit_group_page/edit_group_page.dart +++ b/lib/admin/ui/pages/groups/edit_group_page/edit_group_page.dart @@ -1,154 +1,38 @@ import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/admin.dart'; -import 'package:titan/admin/class/group.dart'; +import 'package:titan/admin/providers/group_from_simple_group_provider.dart'; import 'package:titan/admin/providers/group_id_provider.dart'; -import 'package:titan/admin/providers/group_list_provider.dart'; import 'package:titan/admin/providers/group_provider.dart'; -import 'package:titan/admin/providers/simple_groups_groups_provider.dart'; -import 'package:titan/admin/ui/components/admin_button.dart'; import 'package:titan/admin/ui/pages/groups/edit_group_page/search_user.dart'; -import 'package:titan/tools/constants.dart'; -import 'package:titan/tools/functions.dart'; -import 'package:titan/tools/token_expire_wrapper.dart'; -import 'package:titan/tools/ui/builders/auto_loader_child.dart'; -import 'package:titan/tools/ui/widgets/align_left_text.dart'; -import 'package:titan/tools/ui/builders/waiting_button.dart'; +import 'package:titan/tools/ui/builders/single_auto_loader_child.dart'; -import 'package:titan/tools/ui/widgets/text_entry.dart'; -import 'package:qlevar_router/qlevar_router.dart'; -import 'package:titan/l10n/app_localizations.dart'; - -class EditGroupPage extends HookConsumerWidget { +class EditGroupPage extends ConsumerWidget { const EditGroupPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final groupId = ref.watch(groupIdProvider); - final groupNotifier = ref.watch(groupProvider.notifier); - final groupListNotifier = ref.watch(allGroupListProvider.notifier); - final key = GlobalKey(); - final name = useTextEditingController(); - final description = useTextEditingController(); - final simpleGroupsGroupsNotifier = ref.watch( - simpleGroupsGroupsProvider.notifier, + final group = ref.watch( + groupFromSimpleGroupProvider.select((map) => map[groupId]), ); - final simpleGroupsGroups = ref.watch( - simpleGroupsGroupsProvider.select((value) => value[groupId]), + final groupNotifier = ref.watch(groupProvider.notifier); + final groupFromSimpleGroupNotifier = ref.watch( + groupFromSimpleGroupProvider.notifier, ); - - void displayToastWithContext(TypeMsg type, String msg) { - displayToast(context, type, msg); - } - return AdminTemplate( child: SingleChildScrollView( physics: const BouncingScrollPhysics(), child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 30.0), - child: AutoLoaderChild( - group: simpleGroupsGroups, - notifier: simpleGroupsGroupsNotifier, + padding: const EdgeInsets.all(8.0), + child: SingleAutoLoaderChild( + item: group, + notifier: groupFromSimpleGroupNotifier, mapKey: groupId, - loader: (groupId) async => (await groupNotifier.loadGroup( - groupId, - )).maybeWhen(data: (groups) => groups, orElse: () => Group.empty()), - dataBuilder: (context, groups) { - final group = groups.first; - name.text = group.name; - description.text = group.description; - return Column( - children: [ - AlignLeftText( - AppLocalizations.of(context)!.adminEdit, - fontSize: 20, - color: ColorConstants.gradient1, - ), - const SizedBox(height: 20), - Form( - key: key, - child: Column( - children: [ - Container( - margin: const EdgeInsets.symmetric(vertical: 10), - alignment: Alignment.centerLeft, - child: TextEntry( - controller: name, - color: ColorConstants.gradient1, - label: AppLocalizations.of(context)!.adminName, - suffixIcon: const HeroIcon(HeroIcons.pencil), - enabledColor: Colors.transparent, - ), - ), - Container( - margin: const EdgeInsets.symmetric(vertical: 10), - alignment: Alignment.centerLeft, - child: TextEntry( - controller: description, - color: ColorConstants.gradient1, - label: AppLocalizations.of( - context, - )!.adminDescription, - suffixIcon: const HeroIcon(HeroIcons.pencil), - enabledColor: Colors.transparent, - ), - ), - const SizedBox(height: 20), - WaitingButton( - onTap: () async { - if (!key.currentState!.validate()) { - return; - } - final updatedGroupMsg = AppLocalizations.of( - context, - )!.adminUpdatedGroup; - final updatingErrorMsg = AppLocalizations.of( - context, - )!.adminUpdatingError; - await tokenExpireWrapper(ref, () async { - Group newGroup = group.copyWith( - name: name.text, - description: description.text, - ); - groupNotifier.setGroup(newGroup); - final value = await groupListNotifier.updateGroup( - newGroup.toSimpleGroup(), - ); - if (value) { - QR.back(); - displayToastWithContext( - TypeMsg.msg, - updatedGroupMsg, - ); - } else { - displayToastWithContext( - TypeMsg.msg, - updatingErrorMsg, - ); - } - }); - }, - builder: (child) => SuperAdminButton(child: child), - child: Text( - AppLocalizations.of(context)!.adminEdit, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - color: Colors.white, - ), - ), - ), - const SizedBox(height: 20), - const SearchUser(), - ], - ), - ), - ], - ); + loader: groupNotifier.loadGroup, + dataBuilder: (BuildContext context, value) { + return SearchUser(); }, - loaderColor: Colors.white, ), ), ), diff --git a/lib/admin/ui/pages/groups/edit_group_page/search_user.dart b/lib/admin/ui/pages/groups/edit_group_page/search_user.dart index bef26cc727..fbd3c9c5b4 100644 --- a/lib/admin/ui/pages/groups/edit_group_page/search_user.dart +++ b/lib/admin/ui/pages/groups/edit_group_page/search_user.dart @@ -3,9 +3,9 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/class/group.dart'; +import 'package:titan/admin/providers/group_from_simple_group_provider.dart'; import 'package:titan/admin/providers/group_id_provider.dart'; import 'package:titan/admin/providers/group_provider.dart'; -import 'package:titan/admin/providers/simple_groups_groups_provider.dart'; import 'package:titan/admin/ui/components/user_ui.dart'; import 'package:titan/admin/ui/pages/groups/edit_group_page/results.dart'; @@ -14,7 +14,6 @@ import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; -import 'package:titan/tools/ui/widgets/loader.dart'; import 'package:titan/tools/ui/widgets/styled_search_bar.dart'; import 'package:titan/user/providers/user_list_provider.dart'; import 'package:titan/l10n/app_localizations.dart'; @@ -24,26 +23,27 @@ class SearchUser extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final group = ref.watch(groupProvider); final usersNotifier = ref.watch(userList.notifier); final groupId = ref.watch(groupIdProvider); final groupNotifier = ref.watch(groupProvider.notifier); - final simpleGroupsGroups = ref.watch(simpleGroupsGroupsProvider); - final simpleGroupGroupsNotifier = ref.watch( - simpleGroupsGroupsProvider.notifier, + final groupMap = ref.watch(groupFromSimpleGroupProvider); + final group = groupMap[groupId]; + final groupFromSimpleGroupNotifier = ref.watch( + groupFromSimpleGroupProvider.notifier, ); + final add = useState(false); void displayToastWithContext(TypeMsg type, String msg) { displayToast(context, type, msg); } - final simpleGroup = simpleGroupsGroups[groupId]; - if (simpleGroup == null) { - return const Loader(); + if (group == null) { + return const Center(child: CircularProgressIndicator()); } + return AsyncChild( - value: simpleGroup, + value: group, builder: (context, g) { return Column( children: [ @@ -53,10 +53,7 @@ class SearchUser extends HookConsumerWidget { padding: const EdgeInsets.all(0), onChanged: (value) async { if (value.isNotEmpty) { - await usersNotifier.filterUsers( - value, - excludeGroup: [group.value!.toSimpleGroup()], - ); + await usersNotifier.filterUsers(value, excludeGroup: []); } else { usersNotifier.clear(); } @@ -104,7 +101,7 @@ class SearchUser extends HookConsumerWidget { if (add.value) const SizedBox(height: 10), if (add.value) const MemberResults(), if (!add.value) - ...g[0].members.map( + ...g.members.map( (x) => UserUi( user: x, onDelete: () { @@ -123,8 +120,8 @@ class SearchUser extends HookConsumerWidget { context, )!.adminUpdatingError; await tokenExpireWrapper(ref, () async { - Group newGroup = g[0].copyWith( - members: g[0].members + Group newGroup = g.copyWith( + members: g.members .where((element) => element.id != x.id) .toList(), ); @@ -133,9 +130,9 @@ class SearchUser extends HookConsumerWidget { x, ); if (value) { - simpleGroupGroupsNotifier.setTData( + groupFromSimpleGroupNotifier.setTData( newGroup.id, - AsyncData([newGroup]), + AsyncData(newGroup), ); displayToastWithContext( TypeMsg.msg, diff --git a/lib/admin/ui/pages/groups/groups_page/groups_page.dart b/lib/admin/ui/pages/groups/groups_page/groups_page.dart index c0ee0d0b12..79056baaca 100644 --- a/lib/admin/ui/pages/groups/groups_page/groups_page.dart +++ b/lib/admin/ui/pages/groups/groups_page/groups_page.dart @@ -1,15 +1,18 @@ import 'package:flutter/material.dart'; -import 'package:heroicons/heroicons.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/admin.dart'; -import 'package:titan/admin/ui/pages/groups/groups_page/groups_ui.dart'; +import 'package:titan/admin/class/simple_group.dart'; import 'package:titan/admin/providers/group_id_provider.dart'; import 'package:titan/admin/providers/group_list_provider.dart'; +import 'package:titan/admin/providers/group_provider.dart'; import 'package:titan/admin/router.dart'; -import 'package:titan/admin/ui/components/item_card_ui.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; -import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; +import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; +import 'package:titan/tools/ui/styleguide/button.dart'; +import 'package:titan/tools/ui/styleguide/list_item.dart'; +import 'package:titan/tools/ui/styleguide/text_entry.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; @@ -24,7 +27,10 @@ class GroupsPage extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final groups = ref.watch(allGroupListProvider); final groupsNotifier = ref.watch(allGroupListProvider.notifier); + final nameController = useTextEditingController(); + final descController = useTextEditingController(); final groupIdNotifier = ref.watch(groupIdProvider.notifier); + final groupListNotifier = ref.watch(allGroupListProvider.notifier); ref.watch(userList); void displayToastWithContext(TypeMsg type, String msg) { displayToast(context, type, msg); @@ -43,11 +49,11 @@ class GroupsPage extends HookConsumerWidget { Align( alignment: Alignment.centerLeft, child: Text( - AppLocalizations.of(context)!.adminGroups, - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.w700, - color: ColorConstants.gradient1, + "Gestion des groupes", + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: ColorConstants.title, ), ), ), @@ -63,76 +69,111 @@ class GroupsPage extends HookConsumerWidget { children: [ Column( children: [ - GestureDetector( - onTap: () { - QR.to( - AdminRouter.root + - AdminRouter.usersGroups + - AdminRouter.addGroup, + Button( + text: 'Ajouter un groupe', + onPressed: () async { + await showCustomBottomModal( + context: context, + ref: ref, + modal: BottomModalTemplate( + title: "Ajouter un groupe", + child: Column( + children: [ + TextEntry( + label: 'Nom', + controller: nameController, + ), + const SizedBox(height: 20), + TextEntry( + label: 'Description', + controller: descController, + ), + const SizedBox(height: 20), + Button( + text: "Ajouter", + onPressed: () async { + final addedGroupMsg = + AppLocalizations.of( + context, + )!.adminAddedGroup; + final addingErrorMsg = + AppLocalizations.of( + context, + )!.adminAddingError; + await tokenExpireWrapper( + ref, + () async { + final value = + await groupListNotifier + .createGroup( + SimpleGroup( + name: nameController + .text, + description: + descController + .text, + id: '', + ), + ); + if (value) { + QR.back(); + displayToastWithContext( + TypeMsg.msg, + addedGroupMsg, + ); + } else { + displayToastWithContext( + TypeMsg.error, + addingErrorMsg, + ); + } + }, + ); + }, + ), + ], + ), + ), ); }, - child: ItemCardUi( - children: [ - const Spacer(), - HeroIcon( - HeroIcons.plus, - color: Colors.grey.shade700, - size: 40, - ), - const Spacer(), - ], - ), ), - ...g.map( - (group) => GroupUi( - group: group, - onEdit: () { - groupIdNotifier.setId(group.id); - QR.to( - AdminRouter.root + - AdminRouter.usersGroups + - AdminRouter.editGroup, - ); - }, - onDelete: () async { - await showDialog( + (group) => ListItem( + title: group.name, + subtitle: group.description, + onTap: () async { + await showCustomBottomModal( context: context, - builder: (context) { - return CustomDialogBox( - title: AppLocalizations.of( - context, - )!.adminDeleting, - descriptions: AppLocalizations.of( - context, - )!.adminDeleteGroup, - onYes: () async { - tokenExpireWrapper(ref, () async { - final deletedGroupMsg = - AppLocalizations.of( - context, - )!.adminDeletedGroup; - final deletingErrorMsg = - AppLocalizations.of( - context, - )!.adminDeletingError; - final value = await groupsNotifier - .deleteGroup(group); - if (value) { - displayToastWithContext( - TypeMsg.msg, - deletedGroupMsg, - ); - } else { - displayToastWithContext( - TypeMsg.error, - deletingErrorMsg, + ref: ref, + modal: BottomModalTemplate( + title: group.name, + child: Column( + children: [ + Button( + text: "Modifier", + onPressed: () async {}, + ), + const SizedBox(height: 20), + Button( + text: "Gérer les membres", + onPressed: () { + groupIdNotifier.setId(group.id); + QR.to( + AdminRouter.root + + AdminRouter.usersGroups + + AdminRouter.editGroup, ); - } - }); - }, - ); - }, + }, + ), + const SizedBox(height: 20), + Button( + text: "Supprimer le groupe", + type: ButtonType.danger, + onPressed: () async {}, + ), + ], + ), + ), ); }, ), @@ -143,7 +184,6 @@ class GroupsPage extends HookConsumerWidget { ], ); }, - loaderColor: ColorConstants.gradient1, ), ], ), diff --git a/lib/tools/providers/single_map_provider.dart b/lib/tools/providers/single_map_provider.dart new file mode 100644 index 0000000000..d7374d02f3 --- /dev/null +++ b/lib/tools/providers/single_map_provider.dart @@ -0,0 +1,51 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/tools/token_expire_wrapper.dart'; + +class SingleMapNotifier extends StateNotifier?>> { + SingleMapNotifier() : super(?>{}); + + void loadTList(List tList) async { + Map?> tMap = {}; + for (T l in tList) { + tMap[l] = null; + } + state = tMap; + } + + void addT(T t) { + if (!state.containsKey(t)) { + state = {...state, t: null}; + } + } + + void setTData(T t, AsyncValue value) { + state[t] = value; + state = Map.of(state); + } + + void deleteT(T t) { + if (state.containsKey(t)) { + final newState = Map.of(state)..remove(t); + state = newState; + } + } + + void resetAll() { + state = state.map((key, _) => MapEntry(key, null)); + } + + Future autoLoad( + WidgetRef ref, + T t, + Future> Function(T t) loader, + ) async { + setTData(t, const AsyncLoading()); + tokenExpireWrapper(ref, () async { + loader(t).then((value) { + if (mounted) { + setTData(t, value); + } + }); + }); + } +} diff --git a/lib/tools/ui/builders/single_auto_loader_child.dart b/lib/tools/ui/builders/single_auto_loader_child.dart new file mode 100644 index 0000000000..0a33fb6e49 --- /dev/null +++ b/lib/tools/ui/builders/single_auto_loader_child.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/tools/providers/single_map_provider.dart'; +import 'package:titan/tools/ui/builders/async_child.dart'; +import 'package:titan/tools/ui/widgets/loader.dart'; + +class SingleAutoLoaderChild extends ConsumerWidget { + final AsyncValue? item; + final SingleMapNotifier notifier; + final T mapKey; + final Future> Function(T t) loader; + final Widget Function(BuildContext context, E value) dataBuilder; + final Widget Function(Object? error, StackTrace? stack)? errorBuilder; + final Widget Function(BuildContext context)? loadingBuilder; + final Widget Function(BuildContext context, Widget child)? orElseBuilder; + final Color? loaderColor; + + const SingleAutoLoaderChild({ + super.key, + required this.item, + required this.notifier, + required this.mapKey, + required this.loader, + required this.dataBuilder, + this.errorBuilder, + this.loadingBuilder, + this.orElseBuilder, + this.loaderColor, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final nonNullLoadingBuilder = + loadingBuilder ?? (context) => Loader(color: loaderColor); + if (item == null) { + WidgetsBinding.instance.addPostFrameCallback((_) { + notifier.autoLoad(ref, mapKey, loader); + }); + return nonNullLoadingBuilder(context); + } + return AsyncChild( + value: item!, + builder: (context, value) { + return dataBuilder(context, value); + }, + loaderColor: loaderColor, + ); + } +} diff --git a/lib/tools/ui/styleguide/text_entry.dart b/lib/tools/ui/styleguide/text_entry.dart index 14101c80be..dbb53f30f1 100644 --- a/lib/tools/ui/styleguide/text_entry.dart +++ b/lib/tools/ui/styleguide/text_entry.dart @@ -8,6 +8,7 @@ class TextEntry extends StatelessWidget { final bool enabled; final TextEditingController controller; final TextInputType keyboardType; + final TextCapitalization textCapitalization; final Color color, enabledColor, errorColor; final Widget? suffixIcon; final Function(String)? onChanged; @@ -29,6 +30,7 @@ class TextEntry extends StatelessWidget { this.isInt = false, this.isDouble = false, this.keyboardType = TextInputType.text, + this.textCapitalization = TextCapitalization.sentences, this.canBeEmpty = false, this.color = ColorConstants.tertiary, this.enabledColor = ColorConstants.tertiary, @@ -46,6 +48,7 @@ class TextEntry extends StatelessWidget { maxLines: maxLines, controller: controller, keyboardType: keyboardType, + textCapitalization: textCapitalization, cursorColor: color, onChanged: onChanged, textInputAction: (keyboardType == TextInputType.multiline) From 24c9f0a56b0c8645b06fd646f97544a0af1e8a62 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Fri, 18 Jul 2025 17:49:47 +0200 Subject: [PATCH 128/473] Useless import --- .../pages/group_notifification_page/group_notification_page.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/admin/ui/pages/group_notifification_page/group_notification_page.dart b/lib/admin/ui/pages/group_notifification_page/group_notification_page.dart index 0dc57e0d48..521323ad4e 100644 --- a/lib/admin/ui/pages/group_notifification_page/group_notification_page.dart +++ b/lib/admin/ui/pages/group_notifification_page/group_notification_page.dart @@ -4,7 +4,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/admin.dart'; import 'package:titan/admin/providers/group_list_provider.dart'; import 'package:titan/admin/repositories/notification_repository.dart'; -import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; From 4930acf79b0d9f123d2c89e2c7a20984db9d0277 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Fri, 18 Jul 2025 17:50:06 +0200 Subject: [PATCH 129/473] same --- lib/admin/ui/pages/groups/groups_page/groups_page.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/admin/ui/pages/groups/groups_page/groups_page.dart b/lib/admin/ui/pages/groups/groups_page/groups_page.dart index 79056baaca..0eb4ffcb70 100644 --- a/lib/admin/ui/pages/groups/groups_page/groups_page.dart +++ b/lib/admin/ui/pages/groups/groups_page/groups_page.dart @@ -5,7 +5,6 @@ import 'package:titan/admin/admin.dart'; import 'package:titan/admin/class/simple_group.dart'; import 'package:titan/admin/providers/group_id_provider.dart'; import 'package:titan/admin/providers/group_list_provider.dart'; -import 'package:titan/admin/providers/group_provider.dart'; import 'package:titan/admin/router.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; From 838c877d96ea354b2bcdb948ee78d22a5de85718 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Fri, 18 Jul 2025 17:51:33 +0200 Subject: [PATCH 130/473] padding --- lib/admin/ui/pages/groups/edit_group_page/edit_group_page.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/admin/ui/pages/groups/edit_group_page/edit_group_page.dart b/lib/admin/ui/pages/groups/edit_group_page/edit_group_page.dart index ff71b30bc1..01a37a5f36 100644 --- a/lib/admin/ui/pages/groups/edit_group_page/edit_group_page.dart +++ b/lib/admin/ui/pages/groups/edit_group_page/edit_group_page.dart @@ -24,7 +24,7 @@ class EditGroupPage extends ConsumerWidget { child: SingleChildScrollView( physics: const BouncingScrollPhysics(), child: Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.symmetric(horizontal: 30.0), child: SingleAutoLoaderChild( item: group, notifier: groupFromSimpleGroupNotifier, From 70ad40708eabeb3020c35f6d20cf8cb850846137 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Fri, 18 Jul 2025 18:41:52 +0200 Subject: [PATCH 131/473] Search bar --- lib/admin/ui/components/user_ui.dart | 6 +- .../edit_group_page/edit_group_page.dart | 30 +++++---- .../groups/edit_group_page/search_user.dart | 64 ++++++------------- lib/tools/ui/styleguide/searchbar.dart | 3 + 4 files changed, 42 insertions(+), 61 deletions(-) diff --git a/lib/admin/ui/components/user_ui.dart b/lib/admin/ui/components/user_ui.dart index d7ca60174d..93ed169c64 100644 --- a/lib/admin/ui/components/user_ui.dart +++ b/lib/admin/ui/components/user_ui.dart @@ -29,11 +29,7 @@ class UserUi extends HookConsumerWidget { child: Container( padding: const EdgeInsets.all(7), decoration: BoxDecoration( - gradient: const LinearGradient( - colors: [ColorConstants.background2, Colors.black], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ), + color: ColorConstants.error, boxShadow: [ BoxShadow( color: ColorConstants.background2.withValues(alpha: 0.4), diff --git a/lib/admin/ui/pages/groups/edit_group_page/edit_group_page.dart b/lib/admin/ui/pages/groups/edit_group_page/edit_group_page.dart index 01a37a5f36..3dd42efe12 100644 --- a/lib/admin/ui/pages/groups/edit_group_page/edit_group_page.dart +++ b/lib/admin/ui/pages/groups/edit_group_page/edit_group_page.dart @@ -6,6 +6,7 @@ import 'package:titan/admin/providers/group_id_provider.dart'; import 'package:titan/admin/providers/group_provider.dart'; import 'package:titan/admin/ui/pages/groups/edit_group_page/search_user.dart'; import 'package:titan/tools/ui/builders/single_auto_loader_child.dart'; +import 'package:titan/tools/ui/layouts/refresher.dart'; class EditGroupPage extends ConsumerWidget { const EditGroupPage({super.key}); @@ -21,18 +22,23 @@ class EditGroupPage extends ConsumerWidget { groupFromSimpleGroupProvider.notifier, ); return AdminTemplate( - child: SingleChildScrollView( - physics: const BouncingScrollPhysics(), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 30.0), - child: SingleAutoLoaderChild( - item: group, - notifier: groupFromSimpleGroupNotifier, - mapKey: groupId, - loader: groupNotifier.loadGroup, - dataBuilder: (BuildContext context, value) { - return SearchUser(); - }, + child: Refresher( + onRefresh: () async { + await groupNotifier.loadGroup(groupId); + }, + child: SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 30.0), + child: SingleAutoLoaderChild( + item: group, + notifier: groupFromSimpleGroupNotifier, + mapKey: groupId, + loader: groupNotifier.loadGroup, + dataBuilder: (BuildContext context, value) { + return SearchUser(); + }, + ), ), ), ), diff --git a/lib/admin/ui/pages/groups/edit_group_page/search_user.dart b/lib/admin/ui/pages/groups/edit_group_page/search_user.dart index fbd3c9c5b4..26b363b981 100644 --- a/lib/admin/ui/pages/groups/edit_group_page/search_user.dart +++ b/lib/admin/ui/pages/groups/edit_group_page/search_user.dart @@ -8,13 +8,12 @@ import 'package:titan/admin/providers/group_id_provider.dart'; import 'package:titan/admin/providers/group_provider.dart'; import 'package:titan/admin/ui/components/user_ui.dart'; import 'package:titan/admin/ui/pages/groups/edit_group_page/results.dart'; - -import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; +import 'package:titan/tools/ui/styleguide/button.dart'; +import 'package:titan/tools/ui/styleguide/searchbar.dart'; import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; -import 'package:titan/tools/ui/widgets/styled_search_bar.dart'; import 'package:titan/user/providers/user_list_provider.dart'; import 'package:titan/l10n/app_localizations.dart'; @@ -33,6 +32,7 @@ class SearchUser extends HookConsumerWidget { ); final add = useState(false); + final searchFocusNode = useFocusNode(); void displayToastWithContext(TypeMsg type, String msg) { displayToast(context, type, msg); @@ -47,56 +47,32 @@ class SearchUser extends HookConsumerWidget { builder: (context, g) { return Column( children: [ - StyledSearchBar( - label: AppLocalizations.of(context)!.adminMembers, - color: ColorConstants.gradient1, - padding: const EdgeInsets.all(0), - onChanged: (value) async { + CustomSearchBar( + focusNode: searchFocusNode, + onSearch: (value) async { if (value.isNotEmpty) { - await usersNotifier.filterUsers(value, excludeGroup: []); + add.value + ? await usersNotifier.filterUsers(value, excludeGroup: []) + : await usersNotifier.filterUsers( + value, + excludeGroup: [], + ); } else { usersNotifier.clear(); } }, - onSuffixIconTap: (focusNode, editingController) { + ), + const SizedBox(height: 10), + Button( + text: 'Ajouter', + onPressed: () { add.value = !add.value; - if (!add.value) { - editingController.clear(); - usersNotifier.clear(); - focusNode.unfocus(); + if (add.value) { + searchFocusNode.requestFocus(); } else { - focusNode.requestFocus(); + searchFocusNode.unfocus(); } }, - suffixIcon: Padding( - padding: const EdgeInsets.all(7.0), - child: Container( - padding: const EdgeInsets.all(7), - decoration: BoxDecoration( - gradient: const LinearGradient( - colors: [ - ColorConstants.gradient1, - ColorConstants.gradient2, - ], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ), - boxShadow: [ - BoxShadow( - color: ColorConstants.gradient2.withValues(alpha: 0.4), - offset: const Offset(2, 3), - blurRadius: 5, - ), - ], - borderRadius: const BorderRadius.all(Radius.circular(10)), - ), - child: HeroIcon( - !add.value ? HeroIcons.plus : HeroIcons.xMark, - size: 20, - color: Colors.white, - ), - ), - ), ), if (add.value) const SizedBox(height: 10), if (add.value) const MemberResults(), diff --git a/lib/tools/ui/styleguide/searchbar.dart b/lib/tools/ui/styleguide/searchbar.dart index 0e5a402c9b..1f8ed456dc 100644 --- a/lib/tools/ui/styleguide/searchbar.dart +++ b/lib/tools/ui/styleguide/searchbar.dart @@ -8,12 +8,14 @@ class CustomSearchBar extends HookWidget { final Function()? onFilter; final Function(String) onSearch; final bool autofocus; + final FocusNode? focusNode; const CustomSearchBar({ super.key, this.hintText = 'Rechercher', this.onFilter, required this.onSearch, this.autofocus = false, + this.focusNode, }); @override @@ -37,6 +39,7 @@ class CustomSearchBar extends HookWidget { Expanded( child: TextField( controller: textController, + focusNode: focusNode, autofocus: autofocus, onChanged: (value) { onSearch(value); From 04ad84ff31fdaf9d281887ed18fc906b66e2ef1f Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Fri, 18 Jul 2025 18:42:08 +0200 Subject: [PATCH 132/473] Useless import --- lib/admin/ui/pages/groups/edit_group_page/search_user.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/admin/ui/pages/groups/edit_group_page/search_user.dart b/lib/admin/ui/pages/groups/edit_group_page/search_user.dart index 26b363b981..936c5a2a76 100644 --- a/lib/admin/ui/pages/groups/edit_group_page/search_user.dart +++ b/lib/admin/ui/pages/groups/edit_group_page/search_user.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/class/group.dart'; import 'package:titan/admin/providers/group_from_simple_group_provider.dart'; From da6014cbfc647dd980de19361e4829e9ab719fa5 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Fri, 18 Jul 2025 18:44:23 +0200 Subject: [PATCH 133/473] Text const --- lib/admin/ui/pages/groups/edit_group_page/search_user.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/admin/ui/pages/groups/edit_group_page/search_user.dart b/lib/admin/ui/pages/groups/edit_group_page/search_user.dart index 936c5a2a76..8b1e68f86c 100644 --- a/lib/admin/ui/pages/groups/edit_group_page/search_user.dart +++ b/lib/admin/ui/pages/groups/edit_group_page/search_user.dart @@ -63,7 +63,7 @@ class SearchUser extends HookConsumerWidget { ), const SizedBox(height: 10), Button( - text: 'Ajouter', + text: !add.value ? 'Ajouter' : "Terminer", onPressed: () { add.value = !add.value; if (add.value) { From 4287a65258eebfca7969f106349aa9f10ad45e45 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Fri, 18 Jul 2025 18:46:45 +0200 Subject: [PATCH 134/473] filter --- lib/admin/ui/pages/groups/edit_group_page/search_user.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/admin/ui/pages/groups/edit_group_page/search_user.dart b/lib/admin/ui/pages/groups/edit_group_page/search_user.dart index 8b1e68f86c..987ebc1eb6 100644 --- a/lib/admin/ui/pages/groups/edit_group_page/search_user.dart +++ b/lib/admin/ui/pages/groups/edit_group_page/search_user.dart @@ -51,10 +51,13 @@ class SearchUser extends HookConsumerWidget { onSearch: (value) async { if (value.isNotEmpty) { add.value - ? await usersNotifier.filterUsers(value, excludeGroup: []) + ? await usersNotifier.filterUsers( + value, + excludeGroup: [g.toSimpleGroup()], + ) : await usersNotifier.filterUsers( value, - excludeGroup: [], + includeGroup: [g.toSimpleGroup()], ); } else { usersNotifier.clear(); From f9cd7eacf65f27d7b2a881101123366a12d75806 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sun, 20 Jul 2025 17:44:10 +0200 Subject: [PATCH 135/473] Double modal --- lib/admin/ui/pages/main_page/main_page.dart | 15 ++- .../users_management_page.dart | 115 ++++++++---------- 2 files changed, 62 insertions(+), 68 deletions(-) diff --git a/lib/admin/ui/pages/main_page/main_page.dart b/lib/admin/ui/pages/main_page/main_page.dart index 69e6cb04c6..eb0c255205 100644 --- a/lib/admin/ui/pages/main_page/main_page.dart +++ b/lib/admin/ui/pages/main_page/main_page.dart @@ -3,6 +3,9 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/admin/admin.dart'; import 'package:titan/admin/router.dart'; +import 'package:titan/admin/ui/pages/users_management_page/users_management_page.dart'; +import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; +import 'package:titan/tools/ui/styleguide/button.dart'; import 'package:titan/tools/ui/styleguide/list_item.dart'; import 'package:titan/user/providers/user_list_provider.dart'; @@ -24,8 +27,16 @@ class AdminMainPage extends HookConsumerWidget { ListItem( title: "Gestion des utilisateurs", subtitle: "Gérer les utilisateurs de l'application", - onTap: () => - QR.to(AdminRouter.root + AdminRouter.usersManagement), + onTap: () async { + await showCustomBottomModal( + context: context, + ref: ref, + modal: BottomModalTemplate( + title: "Gestion des utilisateurs", + child: UsersManagementPage(), + ), + ); + }, ), ListItem( title: "Gestion des groupes", diff --git a/lib/admin/ui/pages/users_management_page/users_management_page.dart b/lib/admin/ui/pages/users_management_page/users_management_page.dart index d5cba7afb1..5c25fdfa4d 100644 --- a/lib/admin/ui/pages/users_management_page/users_management_page.dart +++ b/lib/admin/ui/pages/users_management_page/users_management_page.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/admin.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; import 'package:titan/tools/ui/styleguide/button.dart'; @@ -9,73 +8,57 @@ class UsersManagementPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - return AdminTemplate( - child: Padding( - padding: const EdgeInsets.all(40), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Gestion des utilisateurs", - style: Theme.of(context).textTheme.headlineMedium, - ), - const SizedBox(height: 20), - Button( - text: 'Ajouter', - onPressed: () async { - await showCustomBottomModal( - context: context, - ref: ref, - modal: BottomModalTemplate( - title: "Ajouter des utilisateurs", - child: Column( - children: [ - Button(text: "Importer une liste", onPressed: () {}), - const SizedBox(height: 20), - Button( - text: "Ajouter", - onPressed: () {}, - disabled: true, - ), - ], - ), - ), - ); - }, - ), - const SizedBox(height: 20), - Button( - text: 'Supprimer', - onPressed: () async { - await showCustomBottomModal( - context: context, - ref: ref, - modal: BottomModalTemplate( - type: BottomModalType.danger, - title: "Supprimer des utilisateurs", - child: Column( - children: [ - Button( - text: "Importer une liste", - onPressed: () {}, - type: ButtonType.onDanger, - ), - const SizedBox(height: 20), - Button( - text: "Supprimer", - onPressed: () {}, - disabled: true, - ), - ], + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Button( + text: 'Ajouter', + onPressed: () async { + Navigator.pop(context); + await showCustomBottomModal( + context: context, + ref: ref, + modal: BottomModalTemplate( + title: "Ajouter des utilisateurs", + child: Column( + children: [ + Button(text: "Importer une liste", onPressed: () {}), + const SizedBox(height: 20), + Button(text: "Ajouter", onPressed: () {}, disabled: true), + ], + ), + ), + ); + }, + ), + const SizedBox(height: 20), + Button( + text: 'Supprimer', + onPressed: () async { + Navigator.pop(context); + await showCustomBottomModal( + context: context, + ref: ref, + modal: BottomModalTemplate( + type: BottomModalType.danger, + title: "Supprimer des utilisateurs", + child: Column( + children: [ + Button( + text: "Importer une liste", + onPressed: () {}, + type: ButtonType.onDanger, ), - ), - ); - }, - type: ButtonType.danger, - ), - ], + const SizedBox(height: 20), + Button(text: "Supprimer", onPressed: () {}, disabled: true), + ], + ), + ), + ); + }, + type: ButtonType.danger, ), - ), + ], ); } } From 999d0e9b874fb11f7cc088a490fd0da36fb7be2a Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sun, 20 Jul 2025 17:49:54 +0200 Subject: [PATCH 136/473] useless import --- lib/admin/ui/pages/main_page/main_page.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/admin/ui/pages/main_page/main_page.dart b/lib/admin/ui/pages/main_page/main_page.dart index eb0c255205..78b7a2e5c7 100644 --- a/lib/admin/ui/pages/main_page/main_page.dart +++ b/lib/admin/ui/pages/main_page/main_page.dart @@ -5,7 +5,6 @@ import 'package:titan/admin/admin.dart'; import 'package:titan/admin/router.dart'; import 'package:titan/admin/ui/pages/users_management_page/users_management_page.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; -import 'package:titan/tools/ui/styleguide/button.dart'; import 'package:titan/tools/ui/styleguide/list_item.dart'; import 'package:titan/user/providers/user_list_provider.dart'; From 14015b611e9018776e8e08ccbcf1d5cb6dedbedc Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Tue, 29 Jul 2025 20:31:05 +0200 Subject: [PATCH 137/473] fix rebase --- lib/admin/admin.dart | 5 +++-- lib/feed/ui/pages/main_page/main_page.dart | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/admin/admin.dart b/lib/admin/admin.dart index f43534394d..7b7795a10c 100644 --- a/lib/admin/admin.dart +++ b/lib/admin/admin.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:titan/navigation/ui/top_bar.dart'; +import 'package:titan/admin/router.dart'; import 'package:titan/tools/constants.dart'; +import 'package:titan/tools/ui/widgets/top_bar.dart'; class AdminTemplate extends StatelessWidget { final Widget child; @@ -13,7 +14,7 @@ class AdminTemplate extends StatelessWidget { child: Column( mainAxisAlignment: MainAxisAlignment.start, children: [ - TopBar(), + TopBar(root: AdminRouter.root), Expanded(child: child), ], ), diff --git a/lib/feed/ui/pages/main_page/main_page.dart b/lib/feed/ui/pages/main_page/main_page.dart index 698bd11f57..40947455b7 100644 --- a/lib/feed/ui/pages/main_page/main_page.dart +++ b/lib/feed/ui/pages/main_page/main_page.dart @@ -93,7 +93,7 @@ class FeedMainPage extends HookConsumerWidget { color: ColorConstants.title, ), ), - if (isAdmin) + if (isSuperAdmin) CustomIconButton( icon: HeroIcon( HeroIcons.plus, From c0d23f5727293ec3e241778a9a308b3c92df6252 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Tue, 29 Jul 2025 20:33:09 +0200 Subject: [PATCH 138/473] Add safe area on admin --- lib/admin/admin.dart | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/admin/admin.dart b/lib/admin/admin.dart index 7b7795a10c..b5bbd01d33 100644 --- a/lib/admin/admin.dart +++ b/lib/admin/admin.dart @@ -11,12 +11,14 @@ class AdminTemplate extends StatelessWidget { Widget build(BuildContext context) { return Container( color: ColorConstants.background, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - TopBar(root: AdminRouter.root), - Expanded(child: child), - ], + child: SafeArea( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + TopBar(root: AdminRouter.root), + Expanded(child: child), + ], + ), ), ); } From ac82b1485d1ccb910a550f8d0c1206378e0634a8 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 30 Jul 2025 00:09:45 +0200 Subject: [PATCH 139/473] Improve UI --- .../pages/groups/edit_group_page/results.dart | 5 +- .../groups/edit_group_page/search_user.dart | 197 +++++++++------ .../pages/groups/groups_page/groups_page.dart | 238 +++++++++--------- 3 files changed, 234 insertions(+), 206 deletions(-) diff --git a/lib/admin/ui/pages/groups/edit_group_page/results.dart b/lib/admin/ui/pages/groups/edit_group_page/results.dart index 8afeba63d6..32b0b8b7b8 100644 --- a/lib/admin/ui/pages/groups/edit_group_page/results.dart +++ b/lib/admin/ui/pages/groups/edit_group_page/results.dart @@ -61,13 +61,14 @@ class MemberResults extends HookConsumerWidget { )!.adminAddingError; await tokenExpireWrapper(ref, () async { groupNotifier.addMember(newGroup, e).then(( - value, + result, ) { - if (value) { + if (result) { simpleGroupGroupsNotifier.setTData( newGroup.id, AsyncData([newGroup]), ); + value.remove(e); displayToastWithContext( TypeMsg.msg, addedMemberMsg, diff --git a/lib/admin/ui/pages/groups/edit_group_page/search_user.dart b/lib/admin/ui/pages/groups/edit_group_page/search_user.dart index 987ebc1eb6..ce0cafea1a 100644 --- a/lib/admin/ui/pages/groups/edit_group_page/search_user.dart +++ b/lib/admin/ui/pages/groups/edit_group_page/search_user.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/class/group.dart'; import 'package:titan/admin/providers/group_from_simple_group_provider.dart'; @@ -8,7 +9,8 @@ import 'package:titan/admin/providers/group_provider.dart'; import 'package:titan/admin/ui/components/user_ui.dart'; import 'package:titan/admin/ui/pages/groups/edit_group_page/results.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; -import 'package:titan/tools/ui/styleguide/button.dart'; +import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; +import 'package:titan/tools/ui/styleguide/icon_button.dart'; import 'package:titan/tools/ui/styleguide/searchbar.dart'; import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; import 'package:titan/tools/functions.dart'; @@ -22,113 +24,142 @@ class SearchUser extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final usersNotifier = ref.watch(userList.notifier); - final groupId = ref.watch(groupIdProvider); final groupNotifier = ref.watch(groupProvider.notifier); - final groupMap = ref.watch(groupFromSimpleGroupProvider); - final group = groupMap[groupId]; + final group = ref.watch(groupProvider); final groupFromSimpleGroupNotifier = ref.watch( groupFromSimpleGroupProvider.notifier, ); - final add = useState(false); final searchFocusNode = useFocusNode(); void displayToastWithContext(TypeMsg type, String msg) { displayToast(context, type, msg); } - if (group == null) { - return const Center(child: CircularProgressIndicator()); - } - return AsyncChild( value: group, builder: (context, g) { return Column( children: [ - CustomSearchBar( - focusNode: searchFocusNode, - onSearch: (value) async { - if (value.isNotEmpty) { - add.value - ? await usersNotifier.filterUsers( - value, - excludeGroup: [g.toSimpleGroup()], - ) - : await usersNotifier.filterUsers( + Row( + children: [ + Expanded( + child: CustomSearchBar( + focusNode: searchFocusNode, + onSearch: (value) async { + if (value.isNotEmpty) { + await usersNotifier.filterUsers( value, includeGroup: [g.toSimpleGroup()], ); - } else { - usersNotifier.clear(); - } - }, - ), - const SizedBox(height: 10), - Button( - text: !add.value ? 'Ajouter' : "Terminer", - onPressed: () { - add.value = !add.value; - if (add.value) { - searchFocusNode.requestFocus(); - } else { - searchFocusNode.unfocus(); - } - }, - ), - if (add.value) const SizedBox(height: 10), - if (add.value) const MemberResults(), - if (!add.value) - ...g.members.map( - (x) => UserUi( - user: x, - onDelete: () { - showDialog( + } else { + usersNotifier.clear(); + } + }, + ), + ), + const SizedBox(width: 10), + CustomIconButton( + icon: HeroIcon(HeroIcons.plus, size: 30, color: Colors.white), + onPressed: () async { + await showCustomBottomModal( context: context, - builder: (BuildContext context) => CustomDialogBox( - descriptions: AppLocalizations.of( - context, - )!.adminRemoveGroupMember, - title: AppLocalizations.of(context)!.adminDeleting, - onYes: () async { - final updatedGroupMsg = AppLocalizations.of( - context, - )!.adminUpdatedGroup; - final updatingErrorMsg = AppLocalizations.of( - context, - )!.adminUpdatingError; - await tokenExpireWrapper(ref, () async { - Group newGroup = g.copyWith( - members: g.members - .where((element) => element.id != x.id) - .toList(), - ); - final value = await groupNotifier.deleteMember( - newGroup, - x, - ); - if (value) { - groupFromSimpleGroupNotifier.setTData( - newGroup.id, - AsyncData(newGroup), - ); - displayToastWithContext( - TypeMsg.msg, - updatedGroupMsg, - ); - } else { - displayToastWithContext( - TypeMsg.msg, - updatingErrorMsg, - ); - } - }); - }, + ref: ref, + modal: BottomModalTemplate( + title: "Ajouter un membre", + child: ConstrainedBox( + constraints: const BoxConstraints(maxHeight: 300), + child: Column( + children: [ + CustomSearchBar( + onSearch: (value) async { + if (value.isNotEmpty) { + await usersNotifier.filterUsers( + value, + excludeGroup: [g.toSimpleGroup()], + ); + } else { + usersNotifier.clear(); + } + }, + ), + Expanded( + child: SingleChildScrollView( + child: const MemberResults(), + ), + ), + ], + ), + ), ), ); }, ), + ], + ), + const SizedBox(height: 10), + + // Button( + // text: !add.value ? 'Ajouter' : "Terminer", + // onPressed: () { + // add.value = !add.value; + // if (add.value) { + // searchFocusNode.requestFocus(); + // } else { + // searchFocusNode.unfocus(); + // } + // }, + // ), + ...g.members.map( + (x) => UserUi( + user: x, + onDelete: () { + showDialog( + context: context, + builder: (BuildContext context) => CustomDialogBox( + descriptions: AppLocalizations.of( + context, + )!.adminRemoveGroupMember, + title: AppLocalizations.of(context)!.adminDeleting, + onYes: () async { + final updatedGroupMsg = AppLocalizations.of( + context, + )!.adminUpdatedGroup; + final updatingErrorMsg = AppLocalizations.of( + context, + )!.adminUpdatingError; + await tokenExpireWrapper(ref, () async { + Group newGroup = g.copyWith( + members: g.members + .where((element) => element.id != x.id) + .toList(), + ); + final value = await groupNotifier.deleteMember( + newGroup, + x, + ); + if (value) { + groupFromSimpleGroupNotifier.setTData( + newGroup.id, + AsyncData(newGroup), + ); + displayToastWithContext( + TypeMsg.msg, + updatedGroupMsg, + ); + } else { + displayToastWithContext( + TypeMsg.msg, + updatingErrorMsg, + ); + } + }); + }, + ), + ); + }, ), + ), ], ); }, diff --git a/lib/admin/ui/pages/groups/groups_page/groups_page.dart b/lib/admin/ui/pages/groups/groups_page/groups_page.dart index 0eb4ffcb70..81aadec6dd 100644 --- a/lib/admin/ui/pages/groups/groups_page/groups_page.dart +++ b/lib/admin/ui/pages/groups/groups_page/groups_page.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/admin.dart'; import 'package:titan/admin/class/simple_group.dart'; @@ -10,6 +11,7 @@ import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; import 'package:titan/tools/ui/styleguide/button.dart'; +import 'package:titan/tools/ui/styleguide/icon_button.dart'; import 'package:titan/tools/ui/styleguide/list_item.dart'; import 'package:titan/tools/ui/styleguide/text_entry.dart'; import 'package:titan/tools/functions.dart'; @@ -45,16 +47,81 @@ class GroupsPage extends HookConsumerWidget { child: Column( children: [ const SizedBox(height: 20), - Align( - alignment: Alignment.centerLeft, - child: Text( - "Gestion des groupes", - style: TextStyle( - fontSize: 24, - fontWeight: FontWeight.bold, - color: ColorConstants.title, + Row( + children: [ + Text( + "Gestion des groupes", + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: ColorConstants.title, + ), ), - ), + Spacer(), + CustomIconButton( + icon: const HeroIcon( + HeroIcons.plus, + size: 30, + color: Colors.white, + ), + onPressed: () async { + await showCustomBottomModal( + context: context, + ref: ref, + modal: BottomModalTemplate( + title: "Ajouter un groupe", + child: Column( + children: [ + TextEntry( + label: 'Nom', + controller: nameController, + ), + const SizedBox(height: 20), + TextEntry( + label: 'Description', + controller: descController, + ), + const SizedBox(height: 20), + Button( + text: "Ajouter", + onPressed: () async { + final addedGroupMsg = AppLocalizations.of( + context, + )!.adminAddedGroup; + final addingErrorMsg = AppLocalizations.of( + context, + )!.adminAddingError; + await tokenExpireWrapper(ref, () async { + final value = await groupListNotifier + .createGroup( + SimpleGroup( + name: nameController.text, + description: descController.text, + id: '', + ), + ); + if (value) { + QR.back(); + displayToastWithContext( + TypeMsg.msg, + addedGroupMsg, + ); + } else { + displayToastWithContext( + TypeMsg.error, + addingErrorMsg, + ); + } + }); + }, + ), + ], + ), + ), + ); + }, + ), + ], ), const SizedBox(height: 30), AsyncChild( @@ -66,120 +133,49 @@ class GroupsPage extends HookConsumerWidget { ); return Column( children: [ - Column( - children: [ - Button( - text: 'Ajouter un groupe', - onPressed: () async { - await showCustomBottomModal( - context: context, - ref: ref, - modal: BottomModalTemplate( - title: "Ajouter un groupe", - child: Column( - children: [ - TextEntry( - label: 'Nom', - controller: nameController, - ), - const SizedBox(height: 20), - TextEntry( - label: 'Description', - controller: descController, - ), - const SizedBox(height: 20), - Button( - text: "Ajouter", - onPressed: () async { - final addedGroupMsg = - AppLocalizations.of( - context, - )!.adminAddedGroup; - final addingErrorMsg = - AppLocalizations.of( - context, - )!.adminAddingError; - await tokenExpireWrapper( - ref, - () async { - final value = - await groupListNotifier - .createGroup( - SimpleGroup( - name: nameController - .text, - description: - descController - .text, - id: '', - ), - ); - if (value) { - QR.back(); - displayToastWithContext( - TypeMsg.msg, - addedGroupMsg, - ); - } else { - displayToastWithContext( - TypeMsg.error, - addingErrorMsg, - ); - } - }, - ); - }, - ), - ], - ), - ), - ); - }, - ), - ...g.map( - (group) => ListItem( - title: group.name, - subtitle: group.description, - onTap: () async { - await showCustomBottomModal( - context: context, - ref: ref, - modal: BottomModalTemplate( - title: group.name, - child: Column( - children: [ - Button( - text: "Modifier", - onPressed: () async {}, - ), - const SizedBox(height: 20), - Button( - text: "Gérer les membres", - onPressed: () { - groupIdNotifier.setId(group.id); - QR.to( - AdminRouter.root + - AdminRouter.usersGroups + - AdminRouter.editGroup, - ); - }, - ), - const SizedBox(height: 20), - Button( - text: "Supprimer le groupe", - type: ButtonType.danger, - onPressed: () async {}, - ), - ], + ...g.map( + (group) => ListItem( + title: group.name, + subtitle: group.description, + onTap: () async { + await showCustomBottomModal( + context: context, + ref: ref, + modal: BottomModalTemplate( + title: group.name, + child: Column( + children: [ + Button( + text: "Modifier", + onPressed: () async {}, ), - ), - ); - }, - ), - ), - const SizedBox(height: 20), - ], + const SizedBox(height: 20), + Button( + text: "Gérer les membres", + onPressed: () { + Navigator.pop(context); + groupIdNotifier.setId(group.id); + QR.to( + AdminRouter.root + + AdminRouter.usersGroups + + AdminRouter.editGroup, + ); + }, + ), + const SizedBox(height: 20), + Button( + text: "Supprimer le groupe", + type: ButtonType.danger, + onPressed: () async {}, + ), + ], + ), + ), + ); + }, + ), ), + const SizedBox(height: 20), ], ); }, From 4bd177623a3bf1b6bf11fada8075884a8942e722 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 30 Jul 2025 00:09:54 +0200 Subject: [PATCH 140/473] Edit main.dart --- lib/main.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/main.dart b/lib/main.dart index c25a4fce15..24dee3c23e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -104,6 +104,7 @@ class MyApp extends HookConsumerWidget { GlobalWidgetsLocalizations.delegate, GlobalCupertinoLocalizations.delegate, ], + themeMode: ThemeMode.light, theme: ThemeData( primarySwatch: Colors.red, textTheme: GoogleFonts.latoTextTheme(Theme.of(context).textTheme), From 74ce8d21b7e070daeea291ee56f73d321b5c080d Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 30 Jul 2025 00:10:56 +0200 Subject: [PATCH 141/473] Remove useless variable --- lib/admin/ui/pages/groups/edit_group_page/search_user.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/admin/ui/pages/groups/edit_group_page/search_user.dart b/lib/admin/ui/pages/groups/edit_group_page/search_user.dart index ce0cafea1a..38d6184101 100644 --- a/lib/admin/ui/pages/groups/edit_group_page/search_user.dart +++ b/lib/admin/ui/pages/groups/edit_group_page/search_user.dart @@ -30,8 +30,6 @@ class SearchUser extends HookConsumerWidget { groupFromSimpleGroupProvider.notifier, ); - final searchFocusNode = useFocusNode(); - void displayToastWithContext(TypeMsg type, String msg) { displayToast(context, type, msg); } @@ -45,7 +43,6 @@ class SearchUser extends HookConsumerWidget { children: [ Expanded( child: CustomSearchBar( - focusNode: searchFocusNode, onSearch: (value) async { if (value.isNotEmpty) { await usersNotifier.filterUsers( From 750ae09d99a8992fb02d88c78403b624bb3be244 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 30 Jul 2025 00:11:22 +0200 Subject: [PATCH 142/473] Remove useless import --- lib/admin/ui/pages/groups/edit_group_page/search_user.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/admin/ui/pages/groups/edit_group_page/search_user.dart b/lib/admin/ui/pages/groups/edit_group_page/search_user.dart index 38d6184101..681633afa4 100644 --- a/lib/admin/ui/pages/groups/edit_group_page/search_user.dart +++ b/lib/admin/ui/pages/groups/edit_group_page/search_user.dart @@ -1,10 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/class/group.dart'; import 'package:titan/admin/providers/group_from_simple_group_provider.dart'; -import 'package:titan/admin/providers/group_id_provider.dart'; import 'package:titan/admin/providers/group_provider.dart'; import 'package:titan/admin/ui/components/user_ui.dart'; import 'package:titan/admin/ui/pages/groups/edit_group_page/results.dart'; From 36ca6bbd856bab072278f6e7e56d0bdb3b7e3566 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 30 Jul 2025 01:36:03 +0200 Subject: [PATCH 143/473] rm add_group_page --- lib/admin/router.dart | 7 -- .../groups/add_group_page/add_group_page.dart | 91 ------------------- 2 files changed, 98 deletions(-) delete mode 100644 lib/admin/ui/pages/groups/add_group_page/add_group_page.dart diff --git a/lib/admin/router.dart b/lib/admin/router.dart index 541835c336..eeb82bc07c 100644 --- a/lib/admin/router.dart +++ b/lib/admin/router.dart @@ -1,7 +1,5 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/admin/ui/pages/groups/add_group_page/add_group_page.dart' - deferred as add_group_page; import 'package:titan/admin/ui/pages/groups/edit_group_page/edit_group_page.dart' deferred as edit_group_page; import 'package:titan/admin/ui/pages/main_page/main_page.dart' @@ -59,11 +57,6 @@ class AdminRouter { builder: () => groups_page.GroupsPage(), middleware: [DeferredLoadingMiddleware(groups_page.loadLibrary)], children: [ - QRoute( - path: addGroup, - builder: () => add_group_page.AddGroupPage(), - middleware: [DeferredLoadingMiddleware(add_group_page.loadLibrary)], - ), QRoute( path: editGroup, builder: () => edit_group_page.EditGroupPage(), diff --git a/lib/admin/ui/pages/groups/add_group_page/add_group_page.dart b/lib/admin/ui/pages/groups/add_group_page/add_group_page.dart deleted file mode 100644 index 3b00abb87a..0000000000 --- a/lib/admin/ui/pages/groups/add_group_page/add_group_page.dart +++ /dev/null @@ -1,91 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/class/simple_group.dart'; -import 'package:titan/admin/providers/group_list_provider.dart'; -import 'package:titan/super_admin/ui/admin.dart'; -import 'package:titan/super_admin/ui/components/admin_button.dart'; -import 'package:titan/super_admin/ui/components/text_editing.dart'; -import 'package:titan/tools/functions.dart'; -import 'package:titan/tools/token_expire_wrapper.dart'; -import 'package:titan/tools/ui/widgets/align_left_text.dart'; -import 'package:titan/tools/ui/builders/waiting_button.dart'; -import 'package:qlevar_router/qlevar_router.dart'; -import 'package:titan/l10n/app_localizations.dart'; - -class AddGroupPage extends HookConsumerWidget { - const AddGroupPage({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final key = GlobalKey(); - final name = useTextEditingController(); - final description = useTextEditingController(); - final groupListNotifier = ref.watch(allGroupListProvider.notifier); - void displayToastWithContext(TypeMsg type, String msg) { - displayToast(context, type, msg); - } - - return SuperAdminTemplate( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 30.0), - child: SingleChildScrollView( - physics: const AlwaysScrollableScrollPhysics( - parent: BouncingScrollPhysics(), - ), - child: Form( - key: key, - child: Column( - children: [ - AlignLeftText(AppLocalizations.of(context)!.adminAddGroup), - const SizedBox(height: 30), - TextEditing( - controller: name, - label: AppLocalizations.of(context)!.adminName, - ), - TextEditing( - controller: description, - label: AppLocalizations.of(context)!.adminDescription, - ), - WaitingButton( - onTap: () async { - final addedGroupMsg = AppLocalizations.of( - context, - )!.adminAddedGroup; - final addingErrorMsg = AppLocalizations.of( - context, - )!.adminAddingError; - await tokenExpireWrapper(ref, () async { - final value = await groupListNotifier.createGroup( - SimpleGroup( - name: name.text, - description: description.text, - id: '', - ), - ); - if (value) { - QR.back(); - displayToastWithContext(TypeMsg.msg, addedGroupMsg); - } else { - displayToastWithContext(TypeMsg.error, addingErrorMsg); - } - }); - }, - builder: (child) => SuperAdminButton(child: child), - child: Text( - AppLocalizations.of(context)!.adminAdd, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - color: Colors.white, - ), - ), - ), - ], - ), - ), - ), - ), - ); - } -} From 831f107b72f208e9b1edae19ee6cba38cca11fef Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 30 Jul 2025 01:36:22 +0200 Subject: [PATCH 144/473] add modify page --- .../pages/groups/groups_page/groups_page.dart | 71 ++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/lib/admin/ui/pages/groups/groups_page/groups_page.dart b/lib/admin/ui/pages/groups/groups_page/groups_page.dart index 81aadec6dd..52e8720532 100644 --- a/lib/admin/ui/pages/groups/groups_page/groups_page.dart +++ b/lib/admin/ui/pages/groups/groups_page/groups_page.dart @@ -7,6 +7,8 @@ import 'package:titan/admin/class/simple_group.dart'; import 'package:titan/admin/providers/group_id_provider.dart'; import 'package:titan/admin/providers/group_list_provider.dart'; import 'package:titan/admin/router.dart'; +import 'package:titan/navigation/providers/modal_provider.dart'; +import 'package:titan/navigation/providers/navbar_animation.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; @@ -65,6 +67,8 @@ class GroupsPage extends HookConsumerWidget { color: Colors.white, ), onPressed: () async { + nameController.text = ''; + descController.text = ''; await showCustomBottomModal( context: context, ref: ref, @@ -147,7 +151,72 @@ class GroupsPage extends HookConsumerWidget { children: [ Button( text: "Modifier", - onPressed: () async {}, + onPressed: () async { + nameController.text = group.name; + descController.text = group.description; + Navigator.pop(context); + await showCustomBottomModal( + context: context, + ref: ref, + modal: BottomModalTemplate( + title: "Modifier le groupe", + child: Column( + children: [ + TextEntry( + label: "Nom", + controller: nameController, + ), + const SizedBox(height: 20), + TextEntry( + label: "Description", + controller: descController, + ), + const SizedBox(height: 20), + Button( + text: "Éditer", + onPressed: () async { + final addedGroupMsg = + AppLocalizations.of( + context, + )!.adminAddedGroup; + final addingErrorMsg = + AppLocalizations.of( + context, + )!.adminAddingError; + await tokenExpireWrapper(ref, () async { + final value = + await groupListNotifier + .updateGroup( + SimpleGroup( + name: + nameController + .text, + description: + descController + .text, + id: '', + ), + ); + if (value) { + QR.back(); + displayToastWithContext( + TypeMsg.msg, + addedGroupMsg, + ); + } else { + displayToastWithContext( + TypeMsg.error, + addingErrorMsg, + ); + } + }); + }, + ), + ], + ), + ), + ); + }, ), const SizedBox(height: 20), Button( From be9ca1145620f1f575b292a05597fe16d5df6ed9 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 30 Jul 2025 01:47:15 +0200 Subject: [PATCH 145/473] Fix multiple modal opening --- .../pages/groups/groups_page/groups_page.dart | 2 -- .../providers/navbar_animation.dart | 36 +++++++++++++++++++ .../ui/styleguide/bottom_modal_template.dart | 25 +++++++------ 3 files changed, 50 insertions(+), 13 deletions(-) diff --git a/lib/admin/ui/pages/groups/groups_page/groups_page.dart b/lib/admin/ui/pages/groups/groups_page/groups_page.dart index 52e8720532..265c3a4a55 100644 --- a/lib/admin/ui/pages/groups/groups_page/groups_page.dart +++ b/lib/admin/ui/pages/groups/groups_page/groups_page.dart @@ -7,8 +7,6 @@ import 'package:titan/admin/class/simple_group.dart'; import 'package:titan/admin/providers/group_id_provider.dart'; import 'package:titan/admin/providers/group_list_provider.dart'; import 'package:titan/admin/router.dart'; -import 'package:titan/navigation/providers/modal_provider.dart'; -import 'package:titan/navigation/providers/navbar_animation.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; diff --git a/lib/navigation/providers/navbar_animation.dart b/lib/navigation/providers/navbar_animation.dart index b489ea7736..4e15c62c50 100644 --- a/lib/navigation/providers/navbar_animation.dart +++ b/lib/navigation/providers/navbar_animation.dart @@ -4,6 +4,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; class NavbarAnimationProvider extends StateNotifier { NavbarAnimationProvider() : super(null); + int _modalCount = 0; + void setController(AnimationController controller) { state = controller; } @@ -19,6 +21,38 @@ class NavbarAnimationProvider extends StateNotifier { } } + void show() { + if (state == null) { + return; + } + if (state!.isDismissed) { + state!.forward(); + } + } + + void hide() { + if (state == null) { + return; + } + if (state!.isCompleted) { + state!.reverse(); + } + } + + void hideForModal() { + _modalCount++; + if (_modalCount == 1) { + hide(); + } + } + + void showForModal() { + _modalCount--; + if (_modalCount == 0) { + show(); + } + } + double get value { if (state == null) { return 0; @@ -29,6 +63,8 @@ class NavbarAnimationProvider extends StateNotifier { AnimationController? get animation { return state; } + + int get modalCount => _modalCount; } final navbarAnimationProvider = diff --git a/lib/tools/ui/styleguide/bottom_modal_template.dart b/lib/tools/ui/styleguide/bottom_modal_template.dart index 933dcf1c00..71eadb2de2 100644 --- a/lib/tools/ui/styleguide/bottom_modal_template.dart +++ b/lib/tools/ui/styleguide/bottom_modal_template.dart @@ -111,16 +111,19 @@ Future showCustomBottomModal({ Function? onCloseCallback, }) async { final navbarAnimationNotifier = ref.watch(navbarAnimationProvider.notifier); - navbarAnimationNotifier.toggle(); - await showModalBottomSheet( - elevation: 3, - backgroundColor: Colors.transparent, - isScrollControlled: true, - useRootNavigator: true, - context: context, - builder: (_) => modal, - ).then((value) { - navbarAnimationNotifier.toggle(); + navbarAnimationNotifier.hideForModal(); + + try { + await showModalBottomSheet( + elevation: 3, + backgroundColor: Colors.transparent, + isScrollControlled: true, + useRootNavigator: true, + context: context, + builder: (_) => modal, + ); + } finally { + navbarAnimationNotifier.showForModal(); onCloseCallback?.call(); - }); + } } From ff36e794c3c30b79619ad005d2d45c9ca5116416 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 30 Jul 2025 18:30:20 +0200 Subject: [PATCH 146/473] hotfix : navbar missing changing module --- lib/navigation/ui/all_module_page.dart | 1 + lib/router.dart | 1 + 2 files changed, 2 insertions(+) diff --git a/lib/navigation/ui/all_module_page.dart b/lib/navigation/ui/all_module_page.dart index e5ca095434..1c7a7f7eb5 100644 --- a/lib/navigation/ui/all_module_page.dart +++ b/lib/navigation/ui/all_module_page.dart @@ -58,6 +58,7 @@ class AllModulePage extends HookConsumerWidget { pathForwardingProvider.notifier, ); pathForwardingNotifier.forward(module.root); + QR.to(module.root); navbarVisibilityNotifier.show(); }, diff --git a/lib/router.dart b/lib/router.dart index 6c3e800e28..cc37e89323 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -32,6 +32,7 @@ import 'package:titan/super_admin/router.dart'; import 'package:titan/tools/middlewares/authenticated_middleware.dart'; import 'package:titan/tools/middlewares/deferred_middleware.dart'; import 'package:titan/tools/ui/styleguide/router.dart'; + import 'package:titan/vote/router.dart'; import 'package:qlevar_router/qlevar_router.dart'; From f7984a7798a7092435791e0cdf03672a791c7e5c Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 30 Jul 2025 18:33:45 +0200 Subject: [PATCH 147/473] fix : missing id in update --- lib/admin/ui/pages/groups/groups_page/groups_page.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/admin/ui/pages/groups/groups_page/groups_page.dart b/lib/admin/ui/pages/groups/groups_page/groups_page.dart index 265c3a4a55..653b4f3876 100644 --- a/lib/admin/ui/pages/groups/groups_page/groups_page.dart +++ b/lib/admin/ui/pages/groups/groups_page/groups_page.dart @@ -192,7 +192,7 @@ class GroupsPage extends HookConsumerWidget { description: descController .text, - id: '', + id: group.id, ), ); if (value) { From 8585dc9773f39accbdb36c754520395737866533 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 30 Jul 2025 19:11:16 +0200 Subject: [PATCH 148/473] Add scroll controller and delete on admin page --- .../pages/groups/groups_page/groups_page.dart | 426 ++++++++++-------- lib/tools/ui/layouts/refresher.dart | 11 +- 2 files changed, 243 insertions(+), 194 deletions(-) diff --git a/lib/admin/ui/pages/groups/groups_page/groups_page.dart b/lib/admin/ui/pages/groups/groups_page/groups_page.dart index 653b4f3876..24989b4a29 100644 --- a/lib/admin/ui/pages/groups/groups_page/groups_page.dart +++ b/lib/admin/ui/pages/groups/groups_page/groups_page.dart @@ -7,6 +7,7 @@ import 'package:titan/admin/class/simple_group.dart'; import 'package:titan/admin/providers/group_id_provider.dart'; import 'package:titan/admin/providers/group_list_provider.dart'; import 'package:titan/admin/router.dart'; +import 'package:titan/navigation/ui/scroll_to_hide_navbar.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; @@ -17,6 +18,7 @@ import 'package:titan/tools/ui/styleguide/text_entry.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; +import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; import 'package:titan/user/providers/user_list_provider.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/l10n/app_localizations.dart'; @@ -32,158 +34,164 @@ class GroupsPage extends HookConsumerWidget { final descController = useTextEditingController(); final groupIdNotifier = ref.watch(groupIdProvider.notifier); final groupListNotifier = ref.watch(allGroupListProvider.notifier); + final scrollController = useScrollController(); ref.watch(userList); void displayToastWithContext(TypeMsg type, String msg) { displayToast(context, type, msg); } return AdminTemplate( - child: Refresher( - onRefresh: () async { - await groupsNotifier.loadGroups(); - }, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 30.0), - child: Column( - children: [ - const SizedBox(height: 20), - Row( - children: [ - Text( - "Gestion des groupes", - style: TextStyle( - fontSize: 24, - fontWeight: FontWeight.bold, - color: ColorConstants.title, - ), - ), - Spacer(), - CustomIconButton( - icon: const HeroIcon( - HeroIcons.plus, - size: 30, - color: Colors.white, + child: ScrollToHideNavbar( + controller: scrollController, + child: Refresher( + controller: scrollController, + onRefresh: () async { + await groupsNotifier.loadGroups(); + }, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 30.0), + child: Column( + children: [ + const SizedBox(height: 20), + Row( + children: [ + Text( + "Gestion des groupes", + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: ColorConstants.title, + ), ), - onPressed: () async { - nameController.text = ''; - descController.text = ''; - await showCustomBottomModal( - context: context, - ref: ref, - modal: BottomModalTemplate( - title: "Ajouter un groupe", - child: Column( - children: [ - TextEntry( - label: 'Nom', - controller: nameController, - ), - const SizedBox(height: 20), - TextEntry( - label: 'Description', - controller: descController, - ), - const SizedBox(height: 20), - Button( - text: "Ajouter", - onPressed: () async { - final addedGroupMsg = AppLocalizations.of( - context, - )!.adminAddedGroup; - final addingErrorMsg = AppLocalizations.of( - context, - )!.adminAddingError; - await tokenExpireWrapper(ref, () async { - final value = await groupListNotifier - .createGroup( - SimpleGroup( - name: nameController.text, - description: descController.text, - id: '', - ), + Spacer(), + CustomIconButton( + icon: const HeroIcon( + HeroIcons.plus, + size: 30, + color: Colors.white, + ), + onPressed: () async { + nameController.text = ''; + descController.text = ''; + await showCustomBottomModal( + context: context, + ref: ref, + modal: BottomModalTemplate( + title: "Ajouter un groupe", + child: Column( + children: [ + TextEntry( + label: 'Nom', + controller: nameController, + ), + const SizedBox(height: 20), + TextEntry( + label: 'Description', + controller: descController, + ), + const SizedBox(height: 20), + Button( + text: "Ajouter", + onPressed: () async { + final addedGroupMsg = AppLocalizations.of( + context, + )!.adminAddedGroup; + final addingErrorMsg = AppLocalizations.of( + context, + )!.adminAddingError; + await tokenExpireWrapper(ref, () async { + final value = await groupListNotifier + .createGroup( + SimpleGroup( + name: nameController.text, + description: descController.text, + id: '', + ), + ); + if (value) { + QR.back(); + displayToastWithContext( + TypeMsg.msg, + addedGroupMsg, + ); + } else { + displayToastWithContext( + TypeMsg.error, + addingErrorMsg, ); - if (value) { - QR.back(); - displayToastWithContext( - TypeMsg.msg, - addedGroupMsg, - ); - } else { - displayToastWithContext( - TypeMsg.error, - addingErrorMsg, - ); - } - }); - }, - ), - ], + } + }); + }, + ), + ], + ), ), - ), - ); - }, - ), - ], - ), - const SizedBox(height: 30), - AsyncChild( - value: groups, - builder: (context, g) { - g.sort( - (a, b) => - a.name.toLowerCase().compareTo(b.name.toLowerCase()), - ); - return Column( - children: [ - ...g.map( - (group) => ListItem( - title: group.name, - subtitle: group.description, - onTap: () async { - await showCustomBottomModal( - context: context, - ref: ref, - modal: BottomModalTemplate( - title: group.name, - child: Column( - children: [ - Button( - text: "Modifier", - onPressed: () async { - nameController.text = group.name; - descController.text = group.description; - Navigator.pop(context); - await showCustomBottomModal( - context: context, - ref: ref, - modal: BottomModalTemplate( - title: "Modifier le groupe", - child: Column( - children: [ - TextEntry( - label: "Nom", - controller: nameController, - ), - const SizedBox(height: 20), - TextEntry( - label: "Description", - controller: descController, - ), - const SizedBox(height: 20), - Button( - text: "Éditer", - onPressed: () async { - final addedGroupMsg = - AppLocalizations.of( - context, - )!.adminAddedGroup; - final addingErrorMsg = - AppLocalizations.of( - context, - )!.adminAddingError; - await tokenExpireWrapper(ref, () async { - final value = - await groupListNotifier + ); + }, + ), + ], + ), + const SizedBox(height: 30), + AsyncChild( + value: groups, + builder: (context, g) { + g.sort( + (a, b) => + a.name.toLowerCase().compareTo(b.name.toLowerCase()), + ); + return Column( + children: [ + ...g.map( + (group) => ListItem( + title: group.name, + subtitle: group.description, + onTap: () async { + await showCustomBottomModal( + context: context, + ref: ref, + modal: BottomModalTemplate( + title: group.name, + child: Column( + children: [ + Button( + text: "Modifier", + onPressed: () async { + nameController.text = group.name; + descController.text = + group.description; + Navigator.pop(context); + await showCustomBottomModal( + context: context, + ref: ref, + modal: BottomModalTemplate( + title: "Modifier le groupe", + child: Column( + children: [ + TextEntry( + label: "Nom", + controller: nameController, + ), + const SizedBox(height: 20), + TextEntry( + label: "Description", + controller: descController, + ), + const SizedBox(height: 20), + Button( + text: "Éditer", + onPressed: () async { + final addedGroupMsg = + AppLocalizations.of( + context, + )!.adminAddedGroup; + final addingErrorMsg = + AppLocalizations.of( + context, + )!.adminAddingError; + await tokenExpireWrapper( + ref, + () async { + final value = await groupListNotifier .updateGroup( SimpleGroup( name: @@ -195,59 +203,91 @@ class GroupsPage extends HookConsumerWidget { id: group.id, ), ); - if (value) { - QR.back(); - displayToastWithContext( - TypeMsg.msg, - addedGroupMsg, - ); - } else { - displayToastWithContext( - TypeMsg.error, - addingErrorMsg, - ); - } - }); - }, - ), - ], + if (value) { + QR.back(); + displayToastWithContext( + TypeMsg.msg, + addedGroupMsg, + ); + } else { + displayToastWithContext( + TypeMsg.error, + addingErrorMsg, + ); + } + }, + ); + }, + ), + ], + ), ), - ), - ); - }, - ), - const SizedBox(height: 20), - Button( - text: "Gérer les membres", - onPressed: () { - Navigator.pop(context); - groupIdNotifier.setId(group.id); - QR.to( - AdminRouter.root + - AdminRouter.usersGroups + - AdminRouter.editGroup, - ); - }, - ), - const SizedBox(height: 20), - Button( - text: "Supprimer le groupe", - type: ButtonType.danger, - onPressed: () async {}, - ), - ], + ); + }, + ), + const SizedBox(height: 20), + Button( + text: "Gérer les membres", + onPressed: () { + Navigator.pop(context); + groupIdNotifier.setId(group.id); + QR.to( + AdminRouter.root + + AdminRouter.usersGroups + + AdminRouter.editGroup, + ); + }, + ), + const SizedBox(height: 20), + Button( + text: "Supprimer le groupe", + type: ButtonType.danger, + onPressed: () async { + await showDialog( + context: context, + builder: (context) { + return CustomDialogBox( + title: "Delete", + descriptions: + "Êtes-vous sûr de vouloir supprimer ce groupe ?", + onYes: () async { + tokenExpireWrapper(ref, () async { + final value = + await groupsNotifier + .deleteGroup(group); + if (value) { + displayToastWithContext( + TypeMsg.msg, + "Groupe supprimé avec succès", + ); + } else { + displayToastWithContext( + TypeMsg.error, + "Échec de la suppression du groupe", + ); + } + }); + }, + ); + }, + ); + Navigator.pop(context); + }, + ), + ], + ), ), - ), - ); - }, + ); + }, + ), ), - ), - const SizedBox(height: 20), - ], - ); - }, - ), - ], + const SizedBox(height: 20), + ], + ); + }, + ), + ], + ), ), ), ), diff --git a/lib/tools/ui/layouts/refresher.dart b/lib/tools/ui/layouts/refresher.dart index a6235f1b28..6cbeffc0a6 100644 --- a/lib/tools/ui/layouts/refresher.dart +++ b/lib/tools/ui/layouts/refresher.dart @@ -9,12 +9,19 @@ import 'package:titan/tools/token_expire_wrapper.dart'; class Refresher extends HookConsumerWidget { final Widget child; final Future Function() onRefresh; - const Refresher({super.key, required this.onRefresh, required this.child}); + final ScrollController? controller; + const Refresher({ + super.key, + required this.onRefresh, + required this.child, + this.controller, + }); @override Widget build(BuildContext context, WidgetRef ref) { if (kIsWeb) { return SingleChildScrollView( + controller: controller, physics: const AlwaysScrollableScrollPhysics( parent: BouncingScrollPhysics(), ), @@ -30,6 +37,7 @@ class Refresher extends HookConsumerWidget { tokenExpireWrapper(ref, onRefresh); }, child: SingleChildScrollView( + controller: controller, physics: const AlwaysScrollableScrollPhysics( parent: BouncingScrollPhysics(), ), @@ -42,6 +50,7 @@ class Refresher extends HookConsumerWidget { ); Widget buildIOSList(WidgetRef ref) => LayoutBuilder( builder: (context, constraints) => CustomScrollView( + controller: controller, shrinkWrap: false, physics: const BouncingScrollPhysics( parent: AlwaysScrollableScrollPhysics(), From 9b4a5c1338d120384fee100929d896edf2445024 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 30 Jul 2025 19:16:26 +0200 Subject: [PATCH 149/473] remove group from superadmin --- lib/super_admin/ui/pages/main_page/main_page.dart | 9 --------- 1 file changed, 9 deletions(-) diff --git a/lib/super_admin/ui/pages/main_page/main_page.dart b/lib/super_admin/ui/pages/main_page/main_page.dart index eb81a9de07..d34380b635 100644 --- a/lib/super_admin/ui/pages/main_page/main_page.dart +++ b/lib/super_admin/ui/pages/main_page/main_page.dart @@ -45,15 +45,6 @@ class SuperAdminMainPage extends HookConsumerWidget { icon: HeroIcons.eye, ), ), - GestureDetector( - onTap: () { - QR.to(SuperAdminRouter.root + SuperAdminRouter.groups); - }, - child: MenuCardUi( - text: AppLocalizations.of(context)!.adminGroups, - icon: HeroIcons.users, - ), - ), GestureDetector( onTap: () { QR.to(SuperAdminRouter.root + SuperAdminRouter.schools); From bd7ffc3dbb6d610ebc64978a1ffe831df1a61ebf Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 30 Jul 2025 19:23:07 +0200 Subject: [PATCH 150/473] router --- lib/super_admin/router.dart | 5 +++++ lib/super_admin/ui/admin.dart | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/super_admin/router.dart b/lib/super_admin/router.dart index 24391e45d3..4f7e5ec935 100644 --- a/lib/super_admin/router.dart +++ b/lib/super_admin/router.dart @@ -1,3 +1,4 @@ +import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/super_admin/providers/is_admin_provider.dart'; @@ -61,6 +62,10 @@ class SuperAdminRouter { AdminMiddleware(ref, isSuperAdminProvider), DeferredLoadingMiddleware(main_page.loadLibrary), ], + pageType: QCustomPage( + transitionsBuilder: (_, animation, _, child) => + FadeTransition(opacity: animation, child: child), + ), children: [ QRoute( path: editModuleVisibility, diff --git a/lib/super_admin/ui/admin.dart b/lib/super_admin/ui/admin.dart index 283ef7724d..1283fda4f5 100644 --- a/lib/super_admin/ui/admin.dart +++ b/lib/super_admin/ui/admin.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/router.dart'; +import 'package:titan/super_admin/router.dart'; import 'package:titan/tools/ui/widgets/top_bar.dart'; import 'package:titan/tools/constants.dart'; @@ -17,7 +17,7 @@ class SuperAdminTemplate extends HookConsumerWidget { child: Column( mainAxisAlignment: MainAxisAlignment.start, children: [ - TopBar(root: AdminRouter.root), + TopBar(root: SuperAdminRouter.root), Expanded(child: child), ], ), From 185bb99874361e65cdb4d23dc8dd15a4396807db Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 30 Jul 2025 19:43:44 +0200 Subject: [PATCH 151/473] move structure management from super admin to admin --- lib/admin/router.dart | 21 ++++++++++++++++++- lib/admin/ui/pages/main_page/main_page.dart | 14 +++++++++++++ .../structure_page/structure_button.dart | 0 .../pages/structure_page/structure_page.dart | 8 ++----- .../ui/pages/structure_page/structure_ui.dart | 2 +- lib/super_admin/router.dart | 2 +- .../ui/pages/main_page/main_page.dart | 9 -------- 7 files changed, 38 insertions(+), 18 deletions(-) rename lib/{super_admin => admin}/ui/pages/structure_page/structure_button.dart (100%) rename lib/{super_admin => admin}/ui/pages/structure_page/structure_page.dart (96%) rename lib/{super_admin => admin}/ui/pages/structure_page/structure_ui.dart (100%) diff --git a/lib/admin/router.dart b/lib/admin/router.dart index eeb82bc07c..35c063f611 100644 --- a/lib/admin/router.dart +++ b/lib/admin/router.dart @@ -10,14 +10,19 @@ import 'package:titan/admin/ui/pages/users_management_page/users_management_page deferred as users_managmement_page; import 'package:titan/admin/ui/pages/group_notifification_page/group_notification_page.dart' deferred as group_notification_page; - +import 'package:titan/super_admin/ui/pages/add_edit_structure_page/add_edit_structure_page.dart' + deferred as add_edit_structure_page; import 'package:titan/navigation/class/module.dart'; +import 'package:titan/admin/ui/pages/structure_page/structure_page.dart' + deferred as structure_page; import 'package:titan/tools/middlewares/authenticated_middleware.dart'; import 'package:titan/tools/middlewares/deferred_middleware.dart'; import 'package:qlevar_router/qlevar_router.dart'; class AdminRouter { final Ref ref; + static const String structures = '/structures'; + static const String addEditStructure = '/add_edit_structure'; static const String root = '/admin'; static const String usersManagement = '/users_management'; static const String usersGroups = '/users_groups'; @@ -73,6 +78,20 @@ class AdminRouter { DeferredLoadingMiddleware(group_notification_page.loadLibrary), ], ), + QRoute( + path: structures, + builder: () => structure_page.StructurePage(), + middleware: [DeferredLoadingMiddleware(structure_page.loadLibrary)], + children: [ + QRoute( + path: addEditStructure, + builder: () => add_edit_structure_page.AddEditStructurePage(), + middleware: [ + DeferredLoadingMiddleware(add_edit_structure_page.loadLibrary), + ], + ), + ], + ), ], ); } diff --git a/lib/admin/ui/pages/main_page/main_page.dart b/lib/admin/ui/pages/main_page/main_page.dart index 78b7a2e5c7..7a9ca21e32 100644 --- a/lib/admin/ui/pages/main_page/main_page.dart +++ b/lib/admin/ui/pages/main_page/main_page.dart @@ -23,6 +23,11 @@ class AdminMainPage extends HookConsumerWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Gestion", style: Theme.of(context).textTheme.headlineMedium), + const SizedBox(height: 20), + Text( + "Utilisateurs & Groupes", + style: Theme.of(context).textTheme.titleLarge, + ), ListItem( title: "Gestion des utilisateurs", subtitle: "Gérer les utilisateurs de l'application", @@ -48,6 +53,15 @@ class AdminMainPage extends HookConsumerWidget { onTap: () => QR.to(AdminRouter.root + AdminRouter.groupNotification), ), + Text( + "Module de paiement", + style: Theme.of(context).textTheme.titleLarge, + ), + ListItem( + title: "Paiement", + subtitle: "Gérer les structures du module de paiement", + onTap: () => QR.to(AdminRouter.root + AdminRouter.structures), + ), ], ), ), diff --git a/lib/super_admin/ui/pages/structure_page/structure_button.dart b/lib/admin/ui/pages/structure_page/structure_button.dart similarity index 100% rename from lib/super_admin/ui/pages/structure_page/structure_button.dart rename to lib/admin/ui/pages/structure_page/structure_button.dart diff --git a/lib/super_admin/ui/pages/structure_page/structure_page.dart b/lib/admin/ui/pages/structure_page/structure_page.dart similarity index 96% rename from lib/super_admin/ui/pages/structure_page/structure_page.dart rename to lib/admin/ui/pages/structure_page/structure_page.dart index 0b66e9bce2..2bae8624af 100644 --- a/lib/super_admin/ui/pages/structure_page/structure_page.dart +++ b/lib/admin/ui/pages/structure_page/structure_page.dart @@ -6,7 +6,7 @@ import 'package:titan/super_admin/providers/structure_provider.dart'; import 'package:titan/super_admin/router.dart'; import 'package:titan/super_admin/ui/admin.dart'; import 'package:titan/super_admin/ui/components/item_card_ui.dart'; -import 'package:titan/super_admin/ui/pages/structure_page/structure_ui.dart'; +import 'package:titan/admin/ui/pages/structure_page/structure_ui.dart'; import 'package:titan/paiement/class/structure.dart'; import 'package:titan/paiement/providers/structure_list_provider.dart'; import 'package:titan/tools/constants.dart'; @@ -51,11 +51,7 @@ class StructurePage extends HookConsumerWidget { alignment: Alignment.centerLeft, child: Text( AppLocalizations.of(context)!.adminStructures, - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.w700, - color: ColorConstants.gradient1, - ), + style: Theme.of(context).textTheme.headlineMedium, ), ), const SizedBox(height: 30), diff --git a/lib/super_admin/ui/pages/structure_page/structure_ui.dart b/lib/admin/ui/pages/structure_page/structure_ui.dart similarity index 100% rename from lib/super_admin/ui/pages/structure_page/structure_ui.dart rename to lib/admin/ui/pages/structure_page/structure_ui.dart index bd8f78664a..b3d72db6ec 100644 --- a/lib/super_admin/ui/pages/structure_page/structure_ui.dart +++ b/lib/admin/ui/pages/structure_page/structure_ui.dart @@ -2,8 +2,8 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/super_admin/ui/components/item_card_ui.dart'; -import 'package:titan/super_admin/ui/pages/structure_page/structure_button.dart'; import 'package:titan/paiement/class/structure.dart'; +import 'package:titan/super_admin/ui/pages/structure_page/structure_button.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; diff --git a/lib/super_admin/router.dart b/lib/super_admin/router.dart index 4f7e5ec935..4f55de2a4a 100644 --- a/lib/super_admin/router.dart +++ b/lib/super_admin/router.dart @@ -17,7 +17,7 @@ import 'package:titan/super_admin/ui/pages/schools/add_school_page/add_school_pa deferred as add_school_page; import 'package:titan/super_admin/ui/pages/schools/edit_school_page/edit_school_page.dart' deferred as edit_school_page; -import 'package:titan/super_admin/ui/pages/structure_page/structure_page.dart' +import 'package:titan/admin/ui/pages/structure_page/structure_page.dart' deferred as structure_page; import 'package:titan/super_admin/ui/pages/add_edit_structure_page/add_edit_structure_page.dart' deferred as add_edit_structure_page; diff --git a/lib/super_admin/ui/pages/main_page/main_page.dart b/lib/super_admin/ui/pages/main_page/main_page.dart index d34380b635..9f1cc07850 100644 --- a/lib/super_admin/ui/pages/main_page/main_page.dart +++ b/lib/super_admin/ui/pages/main_page/main_page.dart @@ -54,15 +54,6 @@ class SuperAdminMainPage extends HookConsumerWidget { icon: HeroIcons.academicCap, ), ), - GestureDetector( - onTap: () { - QR.to(SuperAdminRouter.root + SuperAdminRouter.structures); - }, - child: MenuCardUi( - text: AppLocalizations.of(context)!.adminMyEclPay, - icon: HeroIcons.creditCard, - ), - ), ], ), ), From d40a2a8f1b9e9e26ce336d438936694e4cd65149 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 30 Jul 2025 20:09:58 +0200 Subject: [PATCH 152/473] Change button --- .../pages/structure_page/structure_page.dart | 63 +++++++++---------- 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/lib/admin/ui/pages/structure_page/structure_page.dart b/lib/admin/ui/pages/structure_page/structure_page.dart index 2bae8624af..15c1ba4f1a 100644 --- a/lib/admin/ui/pages/structure_page/structure_page.dart +++ b/lib/admin/ui/pages/structure_page/structure_page.dart @@ -1,16 +1,20 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/admin/admin.dart'; +import 'package:titan/admin/router.dart'; import 'package:titan/super_admin/providers/structure_manager_provider.dart'; import 'package:titan/super_admin/providers/structure_provider.dart'; import 'package:titan/super_admin/router.dart'; -import 'package:titan/super_admin/ui/admin.dart'; import 'package:titan/super_admin/ui/components/item_card_ui.dart'; import 'package:titan/admin/ui/pages/structure_page/structure_ui.dart'; import 'package:titan/paiement/class/structure.dart'; import 'package:titan/paiement/providers/structure_list_provider.dart'; +import 'package:titan/super_admin/ui/pages/add_edit_structure_page/add_edit_structure_page.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; +import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; +import 'package:titan/tools/ui/styleguide/icon_button.dart'; import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; @@ -37,7 +41,7 @@ class StructurePage extends HookConsumerWidget { displayToast(context, type, msg); } - return SuperAdminTemplate( + return AdminTemplate( child: Refresher( onRefresh: () async { await structuresNotifier.getStructures(); @@ -45,14 +49,33 @@ class StructurePage extends HookConsumerWidget { child: Padding( padding: const EdgeInsets.symmetric(horizontal: 30.0), child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 20), - Align( - alignment: Alignment.centerLeft, - child: Text( - AppLocalizations.of(context)!.adminStructures, - style: Theme.of(context).textTheme.headlineMedium, - ), + Row( + children: [ + Text( + AppLocalizations.of(context)!.adminStructures, + style: Theme.of(context).textTheme.headlineMedium, + ), + const Spacer(), + CustomIconButton( + icon: HeroIcon( + HeroIcons.plus, + color: Colors.white, + size: 30, + ), + onPressed: () { + structureNotifier.setStructure(Structure.empty()); + structureManagerNotifier.setUser(SimpleUser.empty()); + QR.to( + AdminRouter.root + + AdminRouter.structures + + AdminRouter.addEditStructure, + ); + }, + ), + ], ), const SizedBox(height: 30), AsyncChild( @@ -66,30 +89,6 @@ class StructurePage extends HookConsumerWidget { children: [ Column( children: [ - GestureDetector( - onTap: () { - structureNotifier.setStructure(Structure.empty()); - structureManagerNotifier.setUser( - SimpleUser.empty(), - ); - QR.to( - SuperAdminRouter.root + - SuperAdminRouter.structures + - SuperAdminRouter.addEditStructure, - ); - }, - child: ItemCardUi( - children: [ - const Spacer(), - HeroIcon( - HeroIcons.plus, - color: Colors.grey.shade700, - size: 40, - ), - const Spacer(), - ], - ), - ), ...structures.map( (structure) => StructureUi( group: structure, From fecbd7e11556129a58b06baaec49ca9c9755452f Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 30 Jul 2025 20:10:31 +0200 Subject: [PATCH 153/473] Cleaning --- .../ui/pages/groups/edit_group_page/search_user.dart | 12 ------------ .../ui/pages/structure_page/structure_page.dart | 5 ----- 2 files changed, 17 deletions(-) diff --git a/lib/admin/ui/pages/groups/edit_group_page/search_user.dart b/lib/admin/ui/pages/groups/edit_group_page/search_user.dart index 681633afa4..73dfdaa008 100644 --- a/lib/admin/ui/pages/groups/edit_group_page/search_user.dart +++ b/lib/admin/ui/pages/groups/edit_group_page/search_user.dart @@ -93,18 +93,6 @@ class SearchUser extends HookConsumerWidget { ], ), const SizedBox(height: 10), - - // Button( - // text: !add.value ? 'Ajouter' : "Terminer", - // onPressed: () { - // add.value = !add.value; - // if (add.value) { - // searchFocusNode.requestFocus(); - // } else { - // searchFocusNode.unfocus(); - // } - // }, - // ), ...g.members.map( (x) => UserUi( user: x, diff --git a/lib/admin/ui/pages/structure_page/structure_page.dart b/lib/admin/ui/pages/structure_page/structure_page.dart index 15c1ba4f1a..5fada4973d 100644 --- a/lib/admin/ui/pages/structure_page/structure_page.dart +++ b/lib/admin/ui/pages/structure_page/structure_page.dart @@ -6,14 +6,10 @@ import 'package:titan/admin/router.dart'; import 'package:titan/super_admin/providers/structure_manager_provider.dart'; import 'package:titan/super_admin/providers/structure_provider.dart'; import 'package:titan/super_admin/router.dart'; -import 'package:titan/super_admin/ui/components/item_card_ui.dart'; import 'package:titan/admin/ui/pages/structure_page/structure_ui.dart'; import 'package:titan/paiement/class/structure.dart'; import 'package:titan/paiement/providers/structure_list_provider.dart'; -import 'package:titan/super_admin/ui/pages/add_edit_structure_page/add_edit_structure_page.dart'; -import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; -import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; import 'package:titan/tools/ui/styleguide/icon_button.dart'; import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; import 'package:titan/tools/functions.dart'; @@ -151,7 +147,6 @@ class StructurePage extends HookConsumerWidget { ], ); }, - loaderColor: ColorConstants.gradient1, ), ], ), From 6d9aa09f95d99a8e1dcc7826df2a98f0c8ed7cc7 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 30 Jul 2025 21:13:45 +0200 Subject: [PATCH 154/473] add_edit_structure beginning --- lib/admin/router.dart | 2 +- .../add_edit_structure_page.dart | 15 +++------------ .../add_edit_structure_page/results.dart | 4 ---- .../add_edit_structure_page/search_user.dart | 19 +------------------ .../ui/pages/structure_page/structure_ui.dart | 2 +- lib/super_admin/router.dart | 2 +- 6 files changed, 7 insertions(+), 37 deletions(-) rename lib/{super_admin => admin}/ui/pages/add_edit_structure_page/add_edit_structure_page.dart (93%) rename lib/{super_admin => admin}/ui/pages/add_edit_structure_page/results.dart (90%) rename lib/{super_admin => admin}/ui/pages/add_edit_structure_page/search_user.dart (68%) diff --git a/lib/admin/router.dart b/lib/admin/router.dart index 35c063f611..2df5812af7 100644 --- a/lib/admin/router.dart +++ b/lib/admin/router.dart @@ -10,7 +10,7 @@ import 'package:titan/admin/ui/pages/users_management_page/users_management_page deferred as users_managmement_page; import 'package:titan/admin/ui/pages/group_notifification_page/group_notification_page.dart' deferred as group_notification_page; -import 'package:titan/super_admin/ui/pages/add_edit_structure_page/add_edit_structure_page.dart' +import 'package:titan/admin/ui/pages/add_edit_structure_page/add_edit_structure_page.dart' deferred as add_edit_structure_page; import 'package:titan/navigation/class/module.dart'; import 'package:titan/admin/ui/pages/structure_page/structure_page.dart' diff --git a/lib/super_admin/ui/pages/add_edit_structure_page/add_edit_structure_page.dart b/lib/admin/ui/pages/add_edit_structure_page/add_edit_structure_page.dart similarity index 93% rename from lib/super_admin/ui/pages/add_edit_structure_page/add_edit_structure_page.dart rename to lib/admin/ui/pages/add_edit_structure_page/add_edit_structure_page.dart index 481e2832f1..e5c16af9a8 100644 --- a/lib/super_admin/ui/pages/add_edit_structure_page/add_edit_structure_page.dart +++ b/lib/admin/ui/pages/add_edit_structure_page/add_edit_structure_page.dart @@ -1,17 +1,16 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/admin/admin.dart'; import 'package:titan/super_admin/class/association_membership_simple.dart'; import 'package:titan/super_admin/providers/association_membership_list_provider.dart'; import 'package:titan/super_admin/providers/structure_manager_provider.dart'; import 'package:titan/super_admin/providers/structure_provider.dart'; -import 'package:titan/super_admin/ui/admin.dart'; import 'package:titan/super_admin/ui/components/admin_button.dart'; import 'package:titan/super_admin/ui/components/text_editing.dart'; -import 'package:titan/super_admin/ui/pages/add_edit_structure_page/search_user.dart'; +import 'package:titan/admin/ui/pages/add_edit_structure_page/search_user.dart'; import 'package:titan/paiement/class/structure.dart'; import 'package:titan/paiement/providers/structure_list_provider.dart'; -import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; @@ -49,7 +48,7 @@ class AddEditStructurePage extends HookConsumerWidget { displayToast(context, type, msg); } - return SuperAdminTemplate( + return AdminTemplate( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 30.0), child: SingleChildScrollView( @@ -60,12 +59,6 @@ class AddEditStructurePage extends HookConsumerWidget { key: key, child: Column( children: [ - const SizedBox(height: 20), - AlignLeftText( - isEdit - ? AppLocalizations.of(context)!.adminEditStructure - : AppLocalizations.of(context)!.adminAddStructure, - ), const SizedBox(height: 20), TextEditing( controller: name, @@ -108,7 +101,6 @@ class AddEditStructurePage extends HookConsumerWidget { Text( AppLocalizations.of(context)!.adminManager, style: TextStyle( - color: ColorConstants.gradient1, fontSize: 20, fontWeight: FontWeight.bold, ), @@ -117,7 +109,6 @@ class AddEditStructurePage extends HookConsumerWidget { Text( structureManager.getName(), style: TextStyle( - color: ColorConstants.gradient1, fontSize: 20, fontWeight: FontWeight.bold, ), diff --git a/lib/super_admin/ui/pages/add_edit_structure_page/results.dart b/lib/admin/ui/pages/add_edit_structure_page/results.dart similarity index 90% rename from lib/super_admin/ui/pages/add_edit_structure_page/results.dart rename to lib/admin/ui/pages/add_edit_structure_page/results.dart index 386aa74eb7..96a980bb8a 100644 --- a/lib/super_admin/ui/pages/add_edit_structure_page/results.dart +++ b/lib/admin/ui/pages/add_edit_structure_page/results.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/super_admin/providers/structure_manager_provider.dart'; -import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; import 'package:titan/user/providers/user_list_provider.dart'; @@ -41,9 +40,7 @@ class MemberResults extends HookConsumerWidget { onTap: () async { structureManagerNotifier.setUser(e); usersNotifier.clear(); - // TODO: Confirmation dialog }, - waitingColor: ColorConstants.gradient1, builder: (child) => child, child: const HeroIcon(HeroIcons.plus), ), @@ -55,7 +52,6 @@ class MemberResults extends HookConsumerWidget { ) .toList(), ), - loaderColor: ColorConstants.gradient1, ); } } diff --git a/lib/super_admin/ui/pages/add_edit_structure_page/search_user.dart b/lib/admin/ui/pages/add_edit_structure_page/search_user.dart similarity index 68% rename from lib/super_admin/ui/pages/add_edit_structure_page/search_user.dart rename to lib/admin/ui/pages/add_edit_structure_page/search_user.dart index 38daf81547..ae14ac602b 100644 --- a/lib/super_admin/ui/pages/add_edit_structure_page/search_user.dart +++ b/lib/admin/ui/pages/add_edit_structure_page/search_user.dart @@ -3,8 +3,7 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/super_admin/providers/structure_manager_provider.dart'; -import 'package:titan/super_admin/ui/pages/add_edit_structure_page/results.dart'; -import 'package:titan/tools/constants.dart'; +import 'package:titan/admin/ui/pages/add_edit_structure_page/results.dart'; import 'package:titan/tools/ui/widgets/styled_search_bar.dart'; import 'package:titan/user/class/simple_users.dart'; import 'package:titan/user/providers/user_list_provider.dart'; @@ -22,7 +21,6 @@ class SearchUser extends HookConsumerWidget { children: [ StyledSearchBar( label: AppLocalizations.of(context)!.adminManager, - color: ColorConstants.gradient1, padding: const EdgeInsets.all(0), editingController: useTextEditingController( text: structureManager.id == SimpleUser.empty().id @@ -40,21 +38,6 @@ class SearchUser extends HookConsumerWidget { padding: const EdgeInsets.all(7.0), child: Container( padding: const EdgeInsets.all(7), - decoration: BoxDecoration( - gradient: const LinearGradient( - colors: [ColorConstants.gradient1, ColorConstants.gradient2], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ), - boxShadow: [ - BoxShadow( - color: ColorConstants.gradient2.withValues(alpha: 0.4), - offset: const Offset(2, 3), - blurRadius: 5, - ), - ], - borderRadius: const BorderRadius.all(Radius.circular(10)), - ), child: HeroIcon(HeroIcons.plus, size: 20, color: Colors.white), ), ), diff --git a/lib/admin/ui/pages/structure_page/structure_ui.dart b/lib/admin/ui/pages/structure_page/structure_ui.dart index b3d72db6ec..df35a2e584 100644 --- a/lib/admin/ui/pages/structure_page/structure_ui.dart +++ b/lib/admin/ui/pages/structure_page/structure_ui.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/admin/ui/pages/groups/groups_page/groups_button.dart'; import 'package:titan/super_admin/ui/components/item_card_ui.dart'; import 'package:titan/paiement/class/structure.dart'; -import 'package:titan/super_admin/ui/pages/structure_page/structure_button.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; diff --git a/lib/super_admin/router.dart b/lib/super_admin/router.dart index 4f55de2a4a..20c6890847 100644 --- a/lib/super_admin/router.dart +++ b/lib/super_admin/router.dart @@ -19,7 +19,7 @@ import 'package:titan/super_admin/ui/pages/schools/edit_school_page/edit_school_ deferred as edit_school_page; import 'package:titan/admin/ui/pages/structure_page/structure_page.dart' deferred as structure_page; -import 'package:titan/super_admin/ui/pages/add_edit_structure_page/add_edit_structure_page.dart' +import 'package:titan/admin/ui/pages/add_edit_structure_page/add_edit_structure_page.dart' deferred as add_edit_structure_page; import 'package:titan/super_admin/ui/pages/main_page/main_page.dart' deferred as main_page; From 354fc5e4fb993237dc1abcf6a1d0ece1a31790a2 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Mon, 4 Aug 2025 18:24:14 +0200 Subject: [PATCH 155/473] wip : add edit structure page --- .../add_edit_structure_page/add_edit_structure_page.dart | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/admin/ui/pages/add_edit_structure_page/add_edit_structure_page.dart b/lib/admin/ui/pages/add_edit_structure_page/add_edit_structure_page.dart index e5c16af9a8..45e72fc57c 100644 --- a/lib/admin/ui/pages/add_edit_structure_page/add_edit_structure_page.dart +++ b/lib/admin/ui/pages/add_edit_structure_page/add_edit_structure_page.dart @@ -6,7 +6,6 @@ import 'package:titan/super_admin/class/association_membership_simple.dart'; import 'package:titan/super_admin/providers/association_membership_list_provider.dart'; import 'package:titan/super_admin/providers/structure_manager_provider.dart'; import 'package:titan/super_admin/providers/structure_provider.dart'; -import 'package:titan/super_admin/ui/components/admin_button.dart'; import 'package:titan/super_admin/ui/components/text_editing.dart'; import 'package:titan/admin/ui/pages/add_edit_structure_page/search_user.dart'; import 'package:titan/paiement/class/structure.dart'; @@ -16,11 +15,11 @@ import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/horizontal_list_view.dart'; import 'package:titan/tools/ui/layouts/item_chip.dart'; -import 'package:titan/tools/ui/widgets/align_left_text.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; import 'package:titan/user/class/simple_users.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/l10n/app_localizations.dart'; +import 'package:titan/vote/ui/pages/admin_page/admin_button.dart'; class AddEditStructurePage extends HookConsumerWidget { const AddEditStructurePage({super.key}); @@ -173,7 +172,7 @@ class AddEditStructurePage extends HookConsumerWidget { }); } }, - builder: (child) => SuperAdminButton(child: child), + builder: (child) => AdminButton(child: child), child: Text( isEdit ? AppLocalizations.of(context)!.adminEdit From 159406fbef24547e585bc3a8767d6df1cc174812 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Mon, 4 Aug 2025 20:00:25 +0200 Subject: [PATCH 156/473] add structure fix --- .../add_edit_structure_page.dart | 29 +++++++--- .../add_edit_structure_page/results.dart | 57 ------------------- .../search_result.dart | 40 +++++++++++++ .../add_edit_structure_page/search_user.dart | 50 ---------------- .../user_search_modal.dart | 45 +++++++++++++++ .../providers/structure_manager_provider.dart | 4 +- 6 files changed, 109 insertions(+), 116 deletions(-) delete mode 100644 lib/admin/ui/pages/add_edit_structure_page/results.dart create mode 100644 lib/admin/ui/pages/add_edit_structure_page/search_result.dart delete mode 100644 lib/admin/ui/pages/add_edit_structure_page/search_user.dart create mode 100644 lib/admin/ui/pages/add_edit_structure_page/user_search_modal.dart diff --git a/lib/admin/ui/pages/add_edit_structure_page/add_edit_structure_page.dart b/lib/admin/ui/pages/add_edit_structure_page/add_edit_structure_page.dart index 45e72fc57c..9c6f4079aa 100644 --- a/lib/admin/ui/pages/add_edit_structure_page/add_edit_structure_page.dart +++ b/lib/admin/ui/pages/add_edit_structure_page/add_edit_structure_page.dart @@ -2,12 +2,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/admin.dart'; +import 'package:titan/admin/ui/pages/add_edit_structure_page/user_search_modal.dart'; import 'package:titan/super_admin/class/association_membership_simple.dart'; import 'package:titan/super_admin/providers/association_membership_list_provider.dart'; import 'package:titan/super_admin/providers/structure_manager_provider.dart'; import 'package:titan/super_admin/providers/structure_provider.dart'; -import 'package:titan/super_admin/ui/components/text_editing.dart'; -import 'package:titan/admin/ui/pages/add_edit_structure_page/search_user.dart'; import 'package:titan/paiement/class/structure.dart'; import 'package:titan/paiement/providers/structure_list_provider.dart'; import 'package:titan/tools/functions.dart'; @@ -16,6 +15,9 @@ import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/horizontal_list_view.dart'; import 'package:titan/tools/ui/layouts/item_chip.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; +import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; +import 'package:titan/tools/ui/styleguide/list_item.dart'; +import 'package:titan/tools/ui/styleguide/text_entry.dart'; import 'package:titan/user/class/simple_users.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/l10n/app_localizations.dart'; @@ -59,10 +61,14 @@ class AddEditStructurePage extends HookConsumerWidget { child: Column( children: [ const SizedBox(height: 20), - TextEditing( - controller: name, - label: AppLocalizations.of(context)!.adminName, + Padding( + padding: const EdgeInsets.all(8.0), + child: TextEntry( + controller: name, + label: AppLocalizations.of(context)!.adminName, + ), ), + const SizedBox(height: 20), AsyncChild( value: allAssociationMembershipList, builder: (context, allAssociationMembershipList) { @@ -94,7 +100,7 @@ class AddEditStructurePage extends HookConsumerWidget { }, ), const SizedBox(height: 20), - isEdit + (isEdit | (structureManager.id != "")) ? Column( children: [ Text( @@ -115,7 +121,16 @@ class AddEditStructurePage extends HookConsumerWidget { const SizedBox(height: 10), ], ) - : const SearchUser(), + : ListItem( + title: "Selectioner un gestionnaire", + onTap: () async { + await showCustomBottomModal( + context: context, + ref: ref, + modal: UserSearchModal(), + ); + }, + ), const SizedBox(height: 20), WaitingButton( onTap: () async { diff --git a/lib/admin/ui/pages/add_edit_structure_page/results.dart b/lib/admin/ui/pages/add_edit_structure_page/results.dart deleted file mode 100644 index 96a980bb8a..0000000000 --- a/lib/admin/ui/pages/add_edit_structure_page/results.dart +++ /dev/null @@ -1,57 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:heroicons/heroicons.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/super_admin/providers/structure_manager_provider.dart'; -import 'package:titan/tools/ui/builders/async_child.dart'; -import 'package:titan/tools/ui/builders/waiting_button.dart'; -import 'package:titan/user/providers/user_list_provider.dart'; - -class MemberResults extends HookConsumerWidget { - const MemberResults({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final users = ref.watch(userList); - final usersNotifier = ref.watch(userList.notifier); - final structureManagerNotifier = ref.watch( - structureManagerProvider.notifier, - ); - - return AsyncChild( - value: users, - builder: (context, value) => Column( - children: value - .map( - (e) => Padding( - padding: const EdgeInsets.symmetric(vertical: 8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Text( - e.getName(), - style: const TextStyle(fontSize: 15), - overflow: TextOverflow.ellipsis, - ), - ), - Row( - children: [ - WaitingButton( - onTap: () async { - structureManagerNotifier.setUser(e); - usersNotifier.clear(); - }, - builder: (child) => child, - child: const HeroIcon(HeroIcons.plus), - ), - ], - ), - ], - ), - ), - ) - .toList(), - ), - ); - } -} diff --git a/lib/admin/ui/pages/add_edit_structure_page/search_result.dart b/lib/admin/ui/pages/add_edit_structure_page/search_result.dart new file mode 100644 index 0000000000..2a7dc74412 --- /dev/null +++ b/lib/admin/ui/pages/add_edit_structure_page/search_result.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/super_admin/providers/structure_manager_provider.dart'; +import 'package:titan/tools/ui/styleguide/list_item_template.dart'; +import 'package:titan/user/providers/user_list_provider.dart'; + +class SearchResult extends HookConsumerWidget { + final TextEditingController queryController; + const SearchResult({super.key, required this.queryController}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final users = ref.watch(userList); + final usersNotifier = ref.watch(userList.notifier); + final structureManagerNotifier = ref.watch( + structureManagerProvider.notifier, + ); + + return users.when( + data: (usersData) { + return Column( + children: usersData + .map( + (user) => ListItemTemplate( + title: user.getName(), + onTap: () { + structureManagerNotifier.setUser(user); + usersNotifier.clear(); + Navigator.of(context).pop(); + }, + ), + ) + .toList(), + ); + }, + loading: () => const Center(child: CircularProgressIndicator()), + error: (e, s) => Text(e.toString()), + ); + } +} diff --git a/lib/admin/ui/pages/add_edit_structure_page/search_user.dart b/lib/admin/ui/pages/add_edit_structure_page/search_user.dart deleted file mode 100644 index ae14ac602b..0000000000 --- a/lib/admin/ui/pages/add_edit_structure_page/search_user.dart +++ /dev/null @@ -1,50 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:heroicons/heroicons.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/super_admin/providers/structure_manager_provider.dart'; -import 'package:titan/admin/ui/pages/add_edit_structure_page/results.dart'; -import 'package:titan/tools/ui/widgets/styled_search_bar.dart'; -import 'package:titan/user/class/simple_users.dart'; -import 'package:titan/user/providers/user_list_provider.dart'; -import 'package:titan/l10n/app_localizations.dart'; - -class SearchUser extends HookConsumerWidget { - const SearchUser({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final usersNotifier = ref.watch(userList.notifier); - final structureManager = ref.watch(structureManagerProvider); - - return Column( - children: [ - StyledSearchBar( - label: AppLocalizations.of(context)!.adminManager, - padding: const EdgeInsets.all(0), - editingController: useTextEditingController( - text: structureManager.id == SimpleUser.empty().id - ? "" - : structureManager.getName(), - ), - onChanged: (value) async { - if (value.isNotEmpty) { - await usersNotifier.filterUsers(value); - } else { - usersNotifier.clear(); - } - }, - suffixIcon: Padding( - padding: const EdgeInsets.all(7.0), - child: Container( - padding: const EdgeInsets.all(7), - child: HeroIcon(HeroIcons.plus, size: 20, color: Colors.white), - ), - ), - ), - const SizedBox(height: 10), - const MemberResults(), - ], - ); - } -} diff --git a/lib/admin/ui/pages/add_edit_structure_page/user_search_modal.dart b/lib/admin/ui/pages/add_edit_structure_page/user_search_modal.dart new file mode 100644 index 0000000000..21145c3cc1 --- /dev/null +++ b/lib/admin/ui/pages/add_edit_structure_page/user_search_modal.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/admin/ui/pages/add_edit_structure_page/search_result.dart'; +import 'package:titan/tools/token_expire_wrapper.dart'; +import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; +import 'package:titan/tools/ui/styleguide/searchbar.dart'; +import 'package:titan/user/providers/user_list_provider.dart'; + +class UserSearchModal extends HookConsumerWidget { + const UserSearchModal({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final usersNotifier = ref.watch(userList.notifier); + final textController = useTextEditingController(); + + return BottomModalTemplate( + title: "title", + type: BottomModalType.main, + child: Column( + children: [ + ConstrainedBox( + constraints: const BoxConstraints(maxHeight: 280), + child: SingleChildScrollView( + child: SearchResult(queryController: textController), + ), + ), + CustomSearchBar( + autofocus: true, + onSearch: (value) => tokenExpireWrapper(ref, () async { + if (value.isNotEmpty) { + await usersNotifier.filterUsers(value); + textController.text = value; + } else { + usersNotifier.clear(); + textController.clear(); + } + }), + ), + ], + ), + ); + } +} diff --git a/lib/super_admin/providers/structure_manager_provider.dart b/lib/super_admin/providers/structure_manager_provider.dart index 7153b20c5b..95fa9ea654 100644 --- a/lib/super_admin/providers/structure_manager_provider.dart +++ b/lib/super_admin/providers/structure_manager_provider.dart @@ -4,8 +4,8 @@ import 'package:titan/user/class/simple_users.dart'; class StructureManagerProvider extends StateNotifier { StructureManagerProvider() : super(SimpleUser.empty()); - void setUser(SimpleUser id) { - state = id; + void setUser(SimpleUser user) { + state = user; } } From dd5d6ee8abded7bbec7c0a2ff3e8b14e2f6497f8 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Mon, 4 Aug 2025 20:22:02 +0200 Subject: [PATCH 157/473] admin structure --- .../pages/structure_page/structure_page.dart | 135 +++++++++++------- lib/tools/functions.dart | 17 +++ 2 files changed, 102 insertions(+), 50 deletions(-) diff --git a/lib/admin/ui/pages/structure_page/structure_page.dart b/lib/admin/ui/pages/structure_page/structure_page.dart index 5fada4973d..5f48501adc 100644 --- a/lib/admin/ui/pages/structure_page/structure_page.dart +++ b/lib/admin/ui/pages/structure_page/structure_page.dart @@ -3,14 +3,16 @@ import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/admin.dart'; import 'package:titan/admin/router.dart'; +import 'package:titan/navigation/providers/navbar_visibility_provider.dart'; import 'package:titan/super_admin/providers/structure_manager_provider.dart'; import 'package:titan/super_admin/providers/structure_provider.dart'; -import 'package:titan/super_admin/router.dart'; -import 'package:titan/admin/ui/pages/structure_page/structure_ui.dart'; import 'package:titan/paiement/class/structure.dart'; import 'package:titan/paiement/providers/structure_list_provider.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; +import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; +import 'package:titan/tools/ui/styleguide/button.dart'; import 'package:titan/tools/ui/styleguide/icon_button.dart'; +import 'package:titan/tools/ui/styleguide/list_item.dart'; import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; @@ -31,6 +33,9 @@ class StructurePage extends HookConsumerWidget { final structureManagerNotifier = ref.watch( structureManagerProvider.notifier, ); + final navbarVisibilityNotifier = ref.read( + navbarVisibilityProvider.notifier, + ); ref.watch(userList); void displayToastWithContext(TypeMsg type, String msg) { @@ -86,57 +91,87 @@ class StructurePage extends HookConsumerWidget { Column( children: [ ...structures.map( - (structure) => StructureUi( - group: structure, - onEdit: () { - structureNotifier.setStructure(structure); - structureManagerNotifier.setUser( - structure.managerUser, - ); - QR.to( - SuperAdminRouter.root + - SuperAdminRouter.structures + - SuperAdminRouter.addEditStructure, - ); - }, - onDelete: () async { - await showDialog( + (structure) => ListItem( + title: structure.name, + onTap: () async { + await showCustomBottomModal( context: context, - builder: (context) { - return CustomDialogBox( - title: AppLocalizations.of( - context, - )!.adminDeleting, - descriptions: AppLocalizations.of( - context, - )!.adminDeleteGroup, - onYes: () async { - final deletedGroupMsg = - AppLocalizations.of( - context, - )!.adminDeletedGroup; - final deletingErrorMsg = - AppLocalizations.of( - context, - )!.adminDeletingError; - tokenExpireWrapper(ref, () async { - final value = await structuresNotifier - .deleteStructure(structure); - if (value) { - displayToastWithContext( - TypeMsg.msg, - deletedGroupMsg, + ref: ref, + modal: BottomModalTemplate( + title: "Gestion des utilisateurs", + child: Column( + children: [ + Button( + text: "Modifier", + onPressed: () { + structureNotifier.setStructure( + structure, + ); + structureManagerNotifier.setUser( + structure.managerUser, + ); + + QR.to( + AdminRouter.root + + AdminRouter.structures + + AdminRouter.addEditStructure, ); - } else { - displayToastWithContext( - TypeMsg.error, - deletingErrorMsg, + Navigator.of(context).pop(); + }, + ), + const SizedBox(height: 10), + Button( + type: ButtonType.danger, + text: 'Supprimer', + onPressed: () async { + await showDialog( + context: context, + builder: (context) { + return CustomDialogBox( + title: AppLocalizations.of( + context, + )!.adminDeleting, + descriptions: + AppLocalizations.of( + context, + )!.adminDeleteGroup, + onYes: () async { + final deletedGroupMsg = + AppLocalizations.of( + context, + )!.adminDeletedGroup; + final deletingErrorMsg = + AppLocalizations.of( + context, + )!.adminDeletingError; + tokenExpireWrapper(ref, () async { + final value = + await structuresNotifier + .deleteStructure( + structure, + ); + if (value) { + displayToastWithContext( + TypeMsg.msg, + deletedGroupMsg, + ); + } else { + displayToastWithContext( + TypeMsg.error, + deletingErrorMsg, + ); + } + }); + Navigator.of(context).pop(); + }, + ); + }, ); - } - }); - }, - ); - }, + }, + ), + ], + ), + ), ); }, ), diff --git a/lib/tools/functions.dart b/lib/tools/functions.dart index dca0d7a05d..ff1ec3becc 100644 --- a/lib/tools/functions.dart +++ b/lib/tools/functions.dart @@ -4,7 +4,10 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:heroicons/heroicons.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:intl/intl.dart'; +import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/navigation/providers/navbar_visibility_provider.dart'; import 'package:titan/tools/constants.dart'; import 'package:auto_size_text/auto_size_text.dart'; import 'package:titan/tools/plausible/plausible.dart'; @@ -528,3 +531,17 @@ String getTitanPackageName() { String getTitanLogo() { return "assets/images/logo_${getAppFlavor()}.png"; } + +void navigateTo( + String path, { + bool ignoreSamePath = true, + PageAlreadyExistAction? pageAlreadyExistAction, + bool waitForResult = false, + required WidgetRef ref, +}) { + final navbarVisibilityNotifier = ref.read(navbarVisibilityProvider.notifier); + final navbarVisibility = ref.read(navbarVisibilityProvider); + print(navbarVisibility); + navbarVisibilityNotifier.show(); + QR.to(path); +} From a2db50493284fba720d7ba44de69be306d7c6ea5 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Mon, 4 Aug 2025 20:28:02 +0200 Subject: [PATCH 158/473] reorganize file --- .../class/association_membership_simple.dart | 0 .../association_membership_list_provider.dart | 2 +- .../providers/structure_manager_provider.dart | 0 .../providers/structure_provider.dart | 0 lib/admin/router.dart | 2 +- .../add_edit_structure_page.dart | 10 +++++----- .../add_edit_structure_page/search_result.dart | 2 +- .../add_edit_structure_page/user_search_modal.dart | 4 ++-- lib/admin/ui/pages/structure_page/structure_page.dart | 8 ++------ lib/paiement/class/structure.dart | 2 +- .../providers/association_membership_provider.dart | 2 +- .../association_membership_repository.dart | 2 +- lib/super_admin/router.dart | 2 +- .../association_membership_information_editor.dart | 2 +- .../association_membership_page.dart | 4 ++-- .../association_membership_ui.dart | 2 +- 16 files changed, 20 insertions(+), 24 deletions(-) rename lib/{super_admin => admin}/class/association_membership_simple.dart (100%) rename lib/{super_admin => admin}/providers/association_membership_list_provider.dart (97%) rename lib/{super_admin => admin}/providers/structure_manager_provider.dart (100%) rename lib/{super_admin => admin}/providers/structure_provider.dart (100%) rename lib/admin/ui/pages/{ => structure_page}/add_edit_structure_page/add_edit_structure_page.dart (95%) rename lib/admin/ui/pages/{ => structure_page}/add_edit_structure_page/search_result.dart (94%) rename lib/admin/ui/pages/{ => structure_page}/add_edit_structure_page/user_search_modal.dart (91%) diff --git a/lib/super_admin/class/association_membership_simple.dart b/lib/admin/class/association_membership_simple.dart similarity index 100% rename from lib/super_admin/class/association_membership_simple.dart rename to lib/admin/class/association_membership_simple.dart diff --git a/lib/super_admin/providers/association_membership_list_provider.dart b/lib/admin/providers/association_membership_list_provider.dart similarity index 97% rename from lib/super_admin/providers/association_membership_list_provider.dart rename to lib/admin/providers/association_membership_list_provider.dart index aaaa70f591..59161b0653 100644 --- a/lib/super_admin/providers/association_membership_list_provider.dart +++ b/lib/admin/providers/association_membership_list_provider.dart @@ -1,5 +1,5 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/super_admin/class/association_membership_simple.dart'; +import 'package:titan/admin/class/association_membership_simple.dart'; import 'package:titan/super_admin/repositories/association_membership_repository.dart'; import 'package:titan/tools/providers/list_notifier.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; diff --git a/lib/super_admin/providers/structure_manager_provider.dart b/lib/admin/providers/structure_manager_provider.dart similarity index 100% rename from lib/super_admin/providers/structure_manager_provider.dart rename to lib/admin/providers/structure_manager_provider.dart diff --git a/lib/super_admin/providers/structure_provider.dart b/lib/admin/providers/structure_provider.dart similarity index 100% rename from lib/super_admin/providers/structure_provider.dart rename to lib/admin/providers/structure_provider.dart diff --git a/lib/admin/router.dart b/lib/admin/router.dart index 2df5812af7..bd1bfde425 100644 --- a/lib/admin/router.dart +++ b/lib/admin/router.dart @@ -10,7 +10,7 @@ import 'package:titan/admin/ui/pages/users_management_page/users_management_page deferred as users_managmement_page; import 'package:titan/admin/ui/pages/group_notifification_page/group_notification_page.dart' deferred as group_notification_page; -import 'package:titan/admin/ui/pages/add_edit_structure_page/add_edit_structure_page.dart' +import 'package:titan/admin/ui/pages/structure_page/add_edit_structure_page/add_edit_structure_page.dart' deferred as add_edit_structure_page; import 'package:titan/navigation/class/module.dart'; import 'package:titan/admin/ui/pages/structure_page/structure_page.dart' diff --git a/lib/admin/ui/pages/add_edit_structure_page/add_edit_structure_page.dart b/lib/admin/ui/pages/structure_page/add_edit_structure_page/add_edit_structure_page.dart similarity index 95% rename from lib/admin/ui/pages/add_edit_structure_page/add_edit_structure_page.dart rename to lib/admin/ui/pages/structure_page/add_edit_structure_page/add_edit_structure_page.dart index 9c6f4079aa..19c1e1deb3 100644 --- a/lib/admin/ui/pages/add_edit_structure_page/add_edit_structure_page.dart +++ b/lib/admin/ui/pages/structure_page/add_edit_structure_page/add_edit_structure_page.dart @@ -2,11 +2,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/admin.dart'; -import 'package:titan/admin/ui/pages/add_edit_structure_page/user_search_modal.dart'; -import 'package:titan/super_admin/class/association_membership_simple.dart'; -import 'package:titan/super_admin/providers/association_membership_list_provider.dart'; -import 'package:titan/super_admin/providers/structure_manager_provider.dart'; -import 'package:titan/super_admin/providers/structure_provider.dart'; +import 'package:titan/admin/ui/pages/structure_page/add_edit_structure_page/user_search_modal.dart'; +import 'package:titan/admin/class/association_membership_simple.dart'; +import 'package:titan/admin/providers/association_membership_list_provider.dart'; +import 'package:titan/admin/providers/structure_manager_provider.dart'; +import 'package:titan/admin/providers/structure_provider.dart'; import 'package:titan/paiement/class/structure.dart'; import 'package:titan/paiement/providers/structure_list_provider.dart'; import 'package:titan/tools/functions.dart'; diff --git a/lib/admin/ui/pages/add_edit_structure_page/search_result.dart b/lib/admin/ui/pages/structure_page/add_edit_structure_page/search_result.dart similarity index 94% rename from lib/admin/ui/pages/add_edit_structure_page/search_result.dart rename to lib/admin/ui/pages/structure_page/add_edit_structure_page/search_result.dart index 2a7dc74412..db5142ba49 100644 --- a/lib/admin/ui/pages/add_edit_structure_page/search_result.dart +++ b/lib/admin/ui/pages/structure_page/add_edit_structure_page/search_result.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/super_admin/providers/structure_manager_provider.dart'; +import 'package:titan/admin/providers/structure_manager_provider.dart'; import 'package:titan/tools/ui/styleguide/list_item_template.dart'; import 'package:titan/user/providers/user_list_provider.dart'; diff --git a/lib/admin/ui/pages/add_edit_structure_page/user_search_modal.dart b/lib/admin/ui/pages/structure_page/add_edit_structure_page/user_search_modal.dart similarity index 91% rename from lib/admin/ui/pages/add_edit_structure_page/user_search_modal.dart rename to lib/admin/ui/pages/structure_page/add_edit_structure_page/user_search_modal.dart index 21145c3cc1..7603990f72 100644 --- a/lib/admin/ui/pages/add_edit_structure_page/user_search_modal.dart +++ b/lib/admin/ui/pages/structure_page/add_edit_structure_page/user_search_modal.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/ui/pages/add_edit_structure_page/search_result.dart'; +import 'package:titan/admin/ui/pages/structure_page/add_edit_structure_page/search_result.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; import 'package:titan/tools/ui/styleguide/searchbar.dart'; @@ -16,7 +16,7 @@ class UserSearchModal extends HookConsumerWidget { final textController = useTextEditingController(); return BottomModalTemplate( - title: "title", + title: "Choisir un gestionnaire", type: BottomModalType.main, child: Column( children: [ diff --git a/lib/admin/ui/pages/structure_page/structure_page.dart b/lib/admin/ui/pages/structure_page/structure_page.dart index 5f48501adc..f5357264fd 100644 --- a/lib/admin/ui/pages/structure_page/structure_page.dart +++ b/lib/admin/ui/pages/structure_page/structure_page.dart @@ -3,9 +3,8 @@ import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/admin.dart'; import 'package:titan/admin/router.dart'; -import 'package:titan/navigation/providers/navbar_visibility_provider.dart'; -import 'package:titan/super_admin/providers/structure_manager_provider.dart'; -import 'package:titan/super_admin/providers/structure_provider.dart'; +import 'package:titan/admin/providers/structure_manager_provider.dart'; +import 'package:titan/admin/providers/structure_provider.dart'; import 'package:titan/paiement/class/structure.dart'; import 'package:titan/paiement/providers/structure_list_provider.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; @@ -33,9 +32,6 @@ class StructurePage extends HookConsumerWidget { final structureManagerNotifier = ref.watch( structureManagerProvider.notifier, ); - final navbarVisibilityNotifier = ref.read( - navbarVisibilityProvider.notifier, - ); ref.watch(userList); void displayToastWithContext(TypeMsg type, String msg) { diff --git a/lib/paiement/class/structure.dart b/lib/paiement/class/structure.dart index 718ed38e45..9c1c3eefa2 100644 --- a/lib/paiement/class/structure.dart +++ b/lib/paiement/class/structure.dart @@ -1,4 +1,4 @@ -import 'package:titan/super_admin/class/association_membership_simple.dart'; +import 'package:titan/admin/class/association_membership_simple.dart'; import 'package:titan/user/class/simple_users.dart'; class Structure { diff --git a/lib/super_admin/providers/association_membership_provider.dart b/lib/super_admin/providers/association_membership_provider.dart index 338f3e1e3c..5e44b3ed45 100644 --- a/lib/super_admin/providers/association_membership_provider.dart +++ b/lib/super_admin/providers/association_membership_provider.dart @@ -1,5 +1,5 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/super_admin/class/association_membership_simple.dart'; +import 'package:titan/admin/class/association_membership_simple.dart'; class AssociationMembershipNotifier extends StateNotifier { diff --git a/lib/super_admin/repositories/association_membership_repository.dart b/lib/super_admin/repositories/association_membership_repository.dart index bd2f33da3f..c092ffd6ea 100644 --- a/lib/super_admin/repositories/association_membership_repository.dart +++ b/lib/super_admin/repositories/association_membership_repository.dart @@ -1,5 +1,5 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/super_admin/class/association_membership_simple.dart'; +import 'package:titan/admin/class/association_membership_simple.dart'; import 'package:titan/super_admin/class/user_association_membership.dart'; import 'package:titan/auth/providers/openid_provider.dart'; import 'package:titan/tools/functions.dart'; diff --git a/lib/super_admin/router.dart b/lib/super_admin/router.dart index 20c6890847..b7f88b6338 100644 --- a/lib/super_admin/router.dart +++ b/lib/super_admin/router.dart @@ -19,7 +19,7 @@ import 'package:titan/super_admin/ui/pages/schools/edit_school_page/edit_school_ deferred as edit_school_page; import 'package:titan/admin/ui/pages/structure_page/structure_page.dart' deferred as structure_page; -import 'package:titan/admin/ui/pages/add_edit_structure_page/add_edit_structure_page.dart' +import 'package:titan/admin/ui/pages/structure_page/add_edit_structure_page/add_edit_structure_page.dart' deferred as add_edit_structure_page; import 'package:titan/super_admin/ui/pages/main_page/main_page.dart' deferred as main_page; diff --git a/lib/super_admin/ui/pages/memberships/association_membership_detail_page/association_membership_information_editor.dart b/lib/super_admin/ui/pages/memberships/association_membership_detail_page/association_membership_information_editor.dart index ad603d3afb..e5fe6b8b97 100644 --- a/lib/super_admin/ui/pages/memberships/association_membership_detail_page/association_membership_information_editor.dart +++ b/lib/super_admin/ui/pages/memberships/association_membership_detail_page/association_membership_information_editor.dart @@ -3,7 +3,7 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/providers/all_groups_list_provider.dart'; -import 'package:titan/super_admin/providers/association_membership_list_provider.dart'; +import 'package:titan/admin/providers/association_membership_list_provider.dart'; import 'package:titan/super_admin/providers/association_membership_provider.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; diff --git a/lib/super_admin/ui/pages/memberships/association_membership_page/association_membership_page.dart b/lib/super_admin/ui/pages/memberships/association_membership_page/association_membership_page.dart index 01ca138be7..c003c1396f 100644 --- a/lib/super_admin/ui/pages/memberships/association_membership_page/association_membership_page.dart +++ b/lib/super_admin/ui/pages/memberships/association_membership_page/association_membership_page.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/super_admin/class/association_membership_simple.dart'; +import 'package:titan/admin/class/association_membership_simple.dart'; import 'package:titan/admin/providers/all_groups_list_provider.dart'; -import 'package:titan/super_admin/providers/association_membership_list_provider.dart'; +import 'package:titan/admin/providers/association_membership_list_provider.dart'; import 'package:titan/super_admin/providers/association_membership_members_list_provider.dart'; import 'package:titan/super_admin/providers/association_membership_provider.dart'; import 'package:titan/super_admin/router.dart'; diff --git a/lib/super_admin/ui/pages/memberships/association_membership_page/association_membership_ui.dart b/lib/super_admin/ui/pages/memberships/association_membership_page/association_membership_ui.dart index c7f8214d90..acfae951d1 100644 --- a/lib/super_admin/ui/pages/memberships/association_membership_page/association_membership_ui.dart +++ b/lib/super_admin/ui/pages/memberships/association_membership_page/association_membership_ui.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/super_admin/class/association_membership_simple.dart'; +import 'package:titan/admin/class/association_membership_simple.dart'; import 'package:titan/super_admin/ui/components/item_card_ui.dart'; import 'package:titan/super_admin/ui/pages/memberships/association_membership_page/association_membership_button.dart'; import 'package:titan/tools/constants.dart'; From 8eae7ab2b63c6505a6cc11471ee98688e6ff13bb Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Mon, 4 Aug 2025 20:29:28 +0200 Subject: [PATCH 159/473] wrong imports --- .../association_membership_list_provider.dart | 2 +- .../association_membership_repository.dart | 0 .../ui/pages/structure_page/structure_ui.dart | 62 ------------------- ...tion_membership_members_list_provider.dart | 2 +- 4 files changed, 2 insertions(+), 64 deletions(-) rename lib/{super_admin => admin}/repositories/association_membership_repository.dart (100%) delete mode 100644 lib/admin/ui/pages/structure_page/structure_ui.dart diff --git a/lib/admin/providers/association_membership_list_provider.dart b/lib/admin/providers/association_membership_list_provider.dart index 59161b0653..b827c14a46 100644 --- a/lib/admin/providers/association_membership_list_provider.dart +++ b/lib/admin/providers/association_membership_list_provider.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/admin/class/association_membership_simple.dart'; -import 'package:titan/super_admin/repositories/association_membership_repository.dart'; +import 'package:titan/admin/repositories/association_membership_repository.dart'; import 'package:titan/tools/providers/list_notifier.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; diff --git a/lib/super_admin/repositories/association_membership_repository.dart b/lib/admin/repositories/association_membership_repository.dart similarity index 100% rename from lib/super_admin/repositories/association_membership_repository.dart rename to lib/admin/repositories/association_membership_repository.dart diff --git a/lib/admin/ui/pages/structure_page/structure_ui.dart b/lib/admin/ui/pages/structure_page/structure_ui.dart deleted file mode 100644 index df35a2e584..0000000000 --- a/lib/admin/ui/pages/structure_page/structure_ui.dart +++ /dev/null @@ -1,62 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:heroicons/heroicons.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/ui/pages/groups/groups_page/groups_button.dart'; -import 'package:titan/super_admin/ui/components/item_card_ui.dart'; -import 'package:titan/paiement/class/structure.dart'; -import 'package:titan/tools/constants.dart'; -import 'package:titan/tools/ui/builders/waiting_button.dart'; - -class StructureUi extends HookConsumerWidget { - final Structure group; - final void Function() onEdit; - final Future Function() onDelete; - const StructureUi({ - super.key, - required this.group, - required this.onEdit, - required this.onDelete, - }); - - @override - Widget build(BuildContext context, WidgetRef ref) { - return ItemCardUi( - children: [ - const SizedBox(width: 10), - Expanded( - child: Text( - group.name, - style: const TextStyle( - color: Colors.black, - fontSize: 20, - fontWeight: FontWeight.bold, - ), - ), - ), - const SizedBox(width: 10), - Row( - children: [ - GestureDetector( - onTap: onEdit, - child: GroupButton( - gradient1: Colors.grey.shade800, - gradient2: Colors.grey.shade900, - child: const HeroIcon(HeroIcons.eye, color: Colors.white), - ), - ), - const SizedBox(width: 10), - WaitingButton( - onTap: onDelete, - builder: (child) => GroupButton( - gradient1: ColorConstants.gradient1, - gradient2: ColorConstants.gradient2, - child: child, - ), - child: const HeroIcon(HeroIcons.xMark, color: Colors.white), - ), - ], - ), - ], - ); - } -} diff --git a/lib/super_admin/providers/association_membership_members_list_provider.dart b/lib/super_admin/providers/association_membership_members_list_provider.dart index a8aa14f4f4..4b9894068d 100644 --- a/lib/super_admin/providers/association_membership_members_list_provider.dart +++ b/lib/super_admin/providers/association_membership_members_list_provider.dart @@ -1,7 +1,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/super_admin/class/user_association_membership.dart'; import 'package:titan/super_admin/class/user_association_membership_base.dart'; -import 'package:titan/super_admin/repositories/association_membership_repository.dart'; +import 'package:titan/admin/repositories/association_membership_repository.dart'; import 'package:titan/super_admin/repositories/association_membership_user_repository.dart'; import 'package:titan/tools/providers/list_notifier.dart'; import 'package:titan/user/class/simple_users.dart'; From af631f2a0d5257cef2df06a49812b2545d82682a Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Mon, 4 Aug 2025 20:33:39 +0200 Subject: [PATCH 160/473] wip association membership --- lib/admin/router.dart | 10 ++++++++++ .../association_membership_button.dart | 0 .../association_membership_creation_dialog.dart | 0 .../association_membership_page.dart | 4 ++-- .../association_membership_ui.dart | 2 +- lib/admin/ui/pages/main_page/main_page.dart | 7 +++++++ lib/super_admin/router.dart | 2 +- 7 files changed, 21 insertions(+), 4 deletions(-) rename lib/{super_admin/ui/pages/memberships => admin/ui/pages}/association_membership_page/association_membership_button.dart (100%) rename lib/{super_admin/ui/pages/memberships => admin/ui/pages}/association_membership_page/association_membership_creation_dialog.dart (100%) rename lib/{super_admin/ui/pages/memberships => admin/ui/pages}/association_membership_page/association_membership_page.dart (97%) rename lib/{super_admin/ui/pages/memberships => admin/ui/pages}/association_membership_page/association_membership_ui.dart (94%) diff --git a/lib/admin/router.dart b/lib/admin/router.dart index bd1bfde425..f8be4a7f63 100644 --- a/lib/admin/router.dart +++ b/lib/admin/router.dart @@ -15,6 +15,8 @@ import 'package:titan/admin/ui/pages/structure_page/add_edit_structure_page/add_ import 'package:titan/navigation/class/module.dart'; import 'package:titan/admin/ui/pages/structure_page/structure_page.dart' deferred as structure_page; +import 'package:titan/admin/ui/pages/association_membership_page/association_membership_page.dart' + deferred as association_membership_page; import 'package:titan/tools/middlewares/authenticated_middleware.dart'; import 'package:titan/tools/middlewares/deferred_middleware.dart'; import 'package:qlevar_router/qlevar_router.dart'; @@ -29,6 +31,7 @@ class AdminRouter { static const String groupNotification = '/group_notification'; static const String addGroup = '/add_group'; static const String editGroup = '/edit_group'; + static const String associationMemberships = '/association_memberships'; static final Module module = Module( getName: (context) => "Admin", description: "Gérer les utilisateurs de l'application", @@ -92,6 +95,13 @@ class AdminRouter { ), ], ), + QRoute( + path: associationMemberships, + builder: () => association_membership_page.AssociationMembershipsPage(), + middleware: [ + DeferredLoadingMiddleware(association_membership_page.loadLibrary), + ], + ), ], ); } diff --git a/lib/super_admin/ui/pages/memberships/association_membership_page/association_membership_button.dart b/lib/admin/ui/pages/association_membership_page/association_membership_button.dart similarity index 100% rename from lib/super_admin/ui/pages/memberships/association_membership_page/association_membership_button.dart rename to lib/admin/ui/pages/association_membership_page/association_membership_button.dart diff --git a/lib/super_admin/ui/pages/memberships/association_membership_page/association_membership_creation_dialog.dart b/lib/admin/ui/pages/association_membership_page/association_membership_creation_dialog.dart similarity index 100% rename from lib/super_admin/ui/pages/memberships/association_membership_page/association_membership_creation_dialog.dart rename to lib/admin/ui/pages/association_membership_page/association_membership_creation_dialog.dart diff --git a/lib/super_admin/ui/pages/memberships/association_membership_page/association_membership_page.dart b/lib/admin/ui/pages/association_membership_page/association_membership_page.dart similarity index 97% rename from lib/super_admin/ui/pages/memberships/association_membership_page/association_membership_page.dart rename to lib/admin/ui/pages/association_membership_page/association_membership_page.dart index c003c1396f..d510754433 100644 --- a/lib/super_admin/ui/pages/memberships/association_membership_page/association_membership_page.dart +++ b/lib/admin/ui/pages/association_membership_page/association_membership_page.dart @@ -9,8 +9,8 @@ import 'package:titan/super_admin/providers/association_membership_provider.dart import 'package:titan/super_admin/router.dart'; import 'package:titan/super_admin/ui/admin.dart'; import 'package:titan/super_admin/ui/components/item_card_ui.dart'; -import 'package:titan/super_admin/ui/pages/memberships/association_membership_page/association_membership_creation_dialog.dart'; -import 'package:titan/super_admin/ui/pages/memberships/association_membership_page/association_membership_ui.dart'; +import 'package:titan/admin/ui/pages/association_membership_page/association_membership_creation_dialog.dart'; +import 'package:titan/admin/ui/pages/association_membership_page/association_membership_ui.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; diff --git a/lib/super_admin/ui/pages/memberships/association_membership_page/association_membership_ui.dart b/lib/admin/ui/pages/association_membership_page/association_membership_ui.dart similarity index 94% rename from lib/super_admin/ui/pages/memberships/association_membership_page/association_membership_ui.dart rename to lib/admin/ui/pages/association_membership_page/association_membership_ui.dart index acfae951d1..fcead08391 100644 --- a/lib/super_admin/ui/pages/memberships/association_membership_page/association_membership_ui.dart +++ b/lib/admin/ui/pages/association_membership_page/association_membership_ui.dart @@ -3,7 +3,7 @@ import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/class/association_membership_simple.dart'; import 'package:titan/super_admin/ui/components/item_card_ui.dart'; -import 'package:titan/super_admin/ui/pages/memberships/association_membership_page/association_membership_button.dart'; +import 'package:titan/admin/ui/pages/association_membership_page/association_membership_button.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; diff --git a/lib/admin/ui/pages/main_page/main_page.dart b/lib/admin/ui/pages/main_page/main_page.dart index 7a9ca21e32..195218e3f6 100644 --- a/lib/admin/ui/pages/main_page/main_page.dart +++ b/lib/admin/ui/pages/main_page/main_page.dart @@ -62,6 +62,13 @@ class AdminMainPage extends HookConsumerWidget { subtitle: "Gérer les structures du module de paiement", onTap: () => QR.to(AdminRouter.root + AdminRouter.structures), ), + Text("Adhésion", style: Theme.of(context).textTheme.titleLarge), + ListItem( + title: "Adhésion", + subtitle: "Gérer les adhésions des utilisateurs", + onTap: () => + QR.to(AdminRouter.root + AdminRouter.associationMemberships), + ), ], ), ), diff --git a/lib/super_admin/router.dart b/lib/super_admin/router.dart index b7f88b6338..90809db30e 100644 --- a/lib/super_admin/router.dart +++ b/lib/super_admin/router.dart @@ -9,7 +9,7 @@ import 'package:titan/super_admin/ui/pages/memberships/add_edit_user_membership_ deferred as add_edit_user_membership_page; import 'package:titan/super_admin/ui/pages/memberships/association_membership_detail_page/association_membership_detail_page.dart' deferred as association_membership_detail_page; -import 'package:titan/super_admin/ui/pages/memberships/association_membership_page/association_membership_page.dart' +import 'package:titan/admin/ui/pages/association_membership_page/association_membership_page.dart' deferred as association_membership_page; import 'package:titan/super_admin/ui/pages/schools/school_page/school_page.dart' deferred as school_page; From ceb3426f4a4b9352150740579b3175ff7df501ae Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Mon, 4 Aug 2025 21:40:32 +0200 Subject: [PATCH 161/473] add membership --- .../add_membership_modal.dart | 85 ++++++++++++ .../association_membership_page.dart | 121 +++++++----------- 2 files changed, 133 insertions(+), 73 deletions(-) create mode 100644 lib/admin/ui/pages/association_membership_page/add_membership_modal.dart diff --git a/lib/admin/ui/pages/association_membership_page/add_membership_modal.dart b/lib/admin/ui/pages/association_membership_page/add_membership_modal.dart new file mode 100644 index 0000000000..ad5c27a839 --- /dev/null +++ b/lib/admin/ui/pages/association_membership_page/add_membership_modal.dart @@ -0,0 +1,85 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/admin/class/simple_group.dart'; +import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; +import 'package:titan/tools/ui/styleguide/button.dart'; +import 'package:titan/tools/ui/styleguide/list_item.dart'; +import 'package:titan/tools/ui/widgets/text_entry.dart'; + +class AddMembershipModal extends HookWidget { + final List groups; + final void Function(SimpleGroup group, String name) onSubmit; + final WidgetRef ref; + + const AddMembershipModal({ + super.key, + required this.groups, + required this.onSubmit, + required this.ref, + }); + + @override + Widget build(BuildContext context) { + final nameController = useTextEditingController(); + final chosenGroup = useState(null); + + return BottomModalTemplate( + title: "Gestion des adhésions", + child: SingleChildScrollView( + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: TextEntry(label: "Nom", controller: nameController), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: chosenGroup.value == null + ? ListItem( + title: "Choisir une association", + onTap: () async { + FocusScope.of(context).unfocus(); + final ctx = context; + await Future.delayed(Duration(milliseconds: 150)); + if (!ctx.mounted) return; + + await showCustomBottomModal( + context: ctx, + ref: ref, + modal: BottomModalTemplate( + title: "Choisir une association", + child: Column( + children: [ + ...groups.map( + (e) => ListItem( + title: e.name, + onTap: () { + chosenGroup.value = e; + Navigator.of(ctx).pop(); + }, + ), + ), + ], + ), + ), + ); + }, + ) + : Text(chosenGroup.value!.name), + ), + const SizedBox(height: 10), + Button( + text: 'Ajouter', + onPressed: () { + if (chosenGroup.value != null) { + onSubmit(chosenGroup.value!, nameController.text); + } + }, + ), + ], + ), + ), + ); + } +} diff --git a/lib/admin/ui/pages/association_membership_page/association_membership_page.dart b/lib/admin/ui/pages/association_membership_page/association_membership_page.dart index d510754433..7ca052d1fc 100644 --- a/lib/admin/ui/pages/association_membership_page/association_membership_page.dart +++ b/lib/admin/ui/pages/association_membership_page/association_membership_page.dart @@ -4,15 +4,16 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/class/association_membership_simple.dart'; import 'package:titan/admin/providers/all_groups_list_provider.dart'; import 'package:titan/admin/providers/association_membership_list_provider.dart'; +import 'package:titan/admin/ui/pages/association_membership_page/add_membership_modal.dart'; import 'package:titan/super_admin/providers/association_membership_members_list_provider.dart'; import 'package:titan/super_admin/providers/association_membership_provider.dart'; import 'package:titan/super_admin/router.dart'; import 'package:titan/super_admin/ui/admin.dart'; -import 'package:titan/super_admin/ui/components/item_card_ui.dart'; -import 'package:titan/admin/ui/pages/association_membership_page/association_membership_creation_dialog.dart'; import 'package:titan/admin/ui/pages/association_membership_page/association_membership_ui.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; +import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; +import 'package:titan/tools/ui/styleguide/icon_button.dart'; import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; @@ -39,12 +40,14 @@ class AssociationMembershipsPage extends HookConsumerWidget { ); final groups = ref.watch(allGroupList); - final nameController = TextEditingController(); - final groupIdController = TextEditingController(); void displayToastWithContext(TypeMsg type, String msg) { displayToast(context, type, msg); } + void popWithContext() { + Navigator.of(context).pop(); + } + return SuperAdminTemplate( child: Refresher( onRefresh: () async { @@ -55,16 +58,48 @@ class AssociationMembershipsPage extends HookConsumerWidget { child: Column( children: [ const SizedBox(height: 20), - Align( - alignment: Alignment.centerLeft, - child: Text( - AppLocalizations.of(context)!.adminAssociationsMemberships, - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.w700, - color: ColorConstants.gradient1, + Row( + children: [ + Text( + AppLocalizations.of(context)!.adminAssociationMembership, + style: Theme.of(context).textTheme.headlineMedium, + ), + const Spacer(), + CustomIconButton( + icon: HeroIcon( + HeroIcons.plus, + color: Colors.white, + size: 30, + ), + onPressed: () async { + await showCustomBottomModal( + context: context, + ref: ref, + modal: AddMembershipModal( + ref: ref, + groups: groups, + onSubmit: (group, name) { + tokenExpireWrapper(ref, () async { + final value = await associationMembershipsNotifier + .createAssociationMembership( + AssociationMembership.empty().copyWith( + managerGroupId: group.id, + name: name, + ), + ); + if (value) { + displayToastWithContext(TypeMsg.msg, "Succès"); + } else { + displayToastWithContext(TypeMsg.error, "Échec"); + } + popWithContext(); + }); + }, + ), + ); + }, ), - ), + ], ), SizedBox(height: 30), AsyncChild( @@ -78,66 +113,6 @@ class AssociationMembershipsPage extends HookConsumerWidget { children: [ Column( children: [ - GestureDetector( - onTap: () async { - await showDialog( - context: context, - builder: (context) { - return MembershipCreationDialogBox( - groups: groups, - nameController: nameController, - groupIdController: groupIdController, - onYes: () async { - final createdAssociationMembershipMsg = - AppLocalizations.of( - context, - )!.adminCreatedAssociationMembership; - final creationErrorMsg = - AppLocalizations.of( - context, - )!.adminCreationError; - tokenExpireWrapper(ref, () async { - final value = - await associationMembershipsNotifier - .createAssociationMembership( - AssociationMembership.empty() - .copyWith( - managerGroupId: - groupIdController - .text, - name: - nameController.text, - ), - ); - if (value) { - displayToastWithContext( - TypeMsg.msg, - createdAssociationMembershipMsg, - ); - } else { - displayToastWithContext( - TypeMsg.error, - creationErrorMsg, - ); - } - }); - }, - ); - }, - ); - }, - child: ItemCardUi( - children: [ - const Spacer(), - HeroIcon( - HeroIcons.plus, - color: Colors.grey.shade700, - size: 40, - ), - const Spacer(), - ], - ), - ), ...g.map( (associationMembership) => AssociationMembershipUi( associationMembership: associationMembership, From 65c8a6ca77aebef7328a6bae2babb1abf2e6cfff Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Tue, 5 Aug 2025 18:42:00 +0200 Subject: [PATCH 162/473] Association membership part 1 --- lib/admin/router.dart | 31 ++++ .../association_membership_detail_page.dart | 22 +-- ...ciation_membership_information_editor.dart | 0 ...ation_membership_member_editable_card.dart | 0 .../research_bar.dart | 0 .../search_filters.dart | 0 ...ssociation_membership_creation_dialog.dart | 153 ------------------ .../association_membership_page.dart | 139 +++++++++------- .../association_membership_ui.dart | 62 ------- lib/super_admin/router.dart | 2 +- 10 files changed, 124 insertions(+), 285 deletions(-) rename lib/{super_admin/ui/pages/memberships => admin/ui/pages}/association_membership_detail_page/association_membership_detail_page.dart (86%) rename lib/{super_admin/ui/pages/memberships => admin/ui/pages}/association_membership_detail_page/association_membership_information_editor.dart (100%) rename lib/{super_admin/ui/pages/memberships => admin/ui/pages}/association_membership_detail_page/association_membership_member_editable_card.dart (100%) rename lib/{super_admin/ui/pages/memberships => admin/ui/pages}/association_membership_detail_page/research_bar.dart (100%) rename lib/{super_admin/ui/pages/memberships => admin/ui/pages}/association_membership_detail_page/search_filters.dart (100%) delete mode 100644 lib/admin/ui/pages/association_membership_page/association_membership_creation_dialog.dart delete mode 100644 lib/admin/ui/pages/association_membership_page/association_membership_ui.dart diff --git a/lib/admin/router.dart b/lib/admin/router.dart index f8be4a7f63..44a49c2ecd 100644 --- a/lib/admin/router.dart +++ b/lib/admin/router.dart @@ -17,6 +17,10 @@ import 'package:titan/admin/ui/pages/structure_page/structure_page.dart' deferred as structure_page; import 'package:titan/admin/ui/pages/association_membership_page/association_membership_page.dart' deferred as association_membership_page; +import 'package:titan/admin/ui/pages/association_membership_detail_page/association_membership_detail_page.dart' + deferred as association_membership_detail_page; +import 'package:titan/super_admin/ui/pages/memberships/add_edit_user_membership_page/add_edit_user_membership_page.dart' + deferred as add_edit_user_membership_page; import 'package:titan/tools/middlewares/authenticated_middleware.dart'; import 'package:titan/tools/middlewares/deferred_middleware.dart'; import 'package:qlevar_router/qlevar_router.dart'; @@ -32,6 +36,9 @@ class AdminRouter { static const String addGroup = '/add_group'; static const String editGroup = '/edit_group'; static const String associationMemberships = '/association_memberships'; + static const String detailAssociationMembership = + '/detail_association_membership'; + static const String addEditMember = '/add_edit_member'; static final Module module = Module( getName: (context) => "Admin", description: "Gérer les utilisateurs de l'application", @@ -101,6 +108,30 @@ class AdminRouter { middleware: [ DeferredLoadingMiddleware(association_membership_page.loadLibrary), ], + children: [ + QRoute( + path: detailAssociationMembership, + builder: () => + association_membership_detail_page.AssociationMembershipEditorPage(), + middleware: [ + DeferredLoadingMiddleware( + association_membership_detail_page.loadLibrary, + ), + ], + children: [ + QRoute( + path: addEditMember, + builder: () => + add_edit_user_membership_page.AddEditUserMembershipPage(), + middleware: [ + DeferredLoadingMiddleware( + add_edit_user_membership_page.loadLibrary, + ), + ], + ), + ], + ), + ], ), ], ); diff --git a/lib/super_admin/ui/pages/memberships/association_membership_detail_page/association_membership_detail_page.dart b/lib/admin/ui/pages/association_membership_detail_page/association_membership_detail_page.dart similarity index 86% rename from lib/super_admin/ui/pages/memberships/association_membership_detail_page/association_membership_detail_page.dart rename to lib/admin/ui/pages/association_membership_detail_page/association_membership_detail_page.dart index 1a1b66c0ec..d37e4c6af8 100644 --- a/lib/super_admin/ui/pages/memberships/association_membership_detail_page/association_membership_detail_page.dart +++ b/lib/admin/ui/pages/association_membership_detail_page/association_membership_detail_page.dart @@ -1,17 +1,17 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/admin/admin.dart'; +import 'package:titan/admin/router.dart'; import 'package:titan/super_admin/class/user_association_membership.dart'; import 'package:titan/super_admin/providers/association_membership_filtered_members_provider.dart'; import 'package:titan/super_admin/providers/association_membership_members_list_provider.dart'; import 'package:titan/super_admin/providers/association_membership_provider.dart'; import 'package:titan/super_admin/providers/user_association_membership_provider.dart'; -import 'package:titan/super_admin/router.dart'; -import 'package:titan/super_admin/ui/admin.dart'; -import 'package:titan/super_admin/ui/pages/memberships/association_membership_detail_page/association_membership_information_editor.dart'; -import 'package:titan/super_admin/ui/pages/memberships/association_membership_detail_page/association_membership_member_editable_card.dart'; -import 'package:titan/super_admin/ui/pages/memberships/association_membership_detail_page/research_bar.dart'; -import 'package:titan/super_admin/ui/pages/memberships/association_membership_detail_page/search_filters.dart'; +import 'package:titan/admin/ui/pages/association_membership_detail_page/association_membership_information_editor.dart'; +import 'package:titan/admin/ui/pages/association_membership_detail_page/association_membership_member_editable_card.dart'; +import 'package:titan/admin/ui/pages/association_membership_detail_page/research_bar.dart'; +import 'package:titan/admin/ui/pages/association_membership_detail_page/search_filters.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; @@ -35,7 +35,7 @@ class AssociationMembershipEditorPage extends HookConsumerWidget { associationMembershipFilteredListProvider, ); - return SuperAdminTemplate( + return AdminTemplate( child: Refresher( onRefresh: () async { await associationMembershipMemberListNotifier @@ -97,10 +97,10 @@ class AssociationMembershipEditorPage extends HookConsumerWidget { ), ); QR.to( - SuperAdminRouter.root + - SuperAdminRouter.associationMemberships + - SuperAdminRouter.detailAssociationMembership + - SuperAdminRouter.addEditMember, + AdminRouter.root + + AdminRouter.associationMemberships + + AdminRouter.detailAssociationMembership + + AdminRouter.addEditMember, ); }, child: const HeroIcon( diff --git a/lib/super_admin/ui/pages/memberships/association_membership_detail_page/association_membership_information_editor.dart b/lib/admin/ui/pages/association_membership_detail_page/association_membership_information_editor.dart similarity index 100% rename from lib/super_admin/ui/pages/memberships/association_membership_detail_page/association_membership_information_editor.dart rename to lib/admin/ui/pages/association_membership_detail_page/association_membership_information_editor.dart diff --git a/lib/super_admin/ui/pages/memberships/association_membership_detail_page/association_membership_member_editable_card.dart b/lib/admin/ui/pages/association_membership_detail_page/association_membership_member_editable_card.dart similarity index 100% rename from lib/super_admin/ui/pages/memberships/association_membership_detail_page/association_membership_member_editable_card.dart rename to lib/admin/ui/pages/association_membership_detail_page/association_membership_member_editable_card.dart diff --git a/lib/super_admin/ui/pages/memberships/association_membership_detail_page/research_bar.dart b/lib/admin/ui/pages/association_membership_detail_page/research_bar.dart similarity index 100% rename from lib/super_admin/ui/pages/memberships/association_membership_detail_page/research_bar.dart rename to lib/admin/ui/pages/association_membership_detail_page/research_bar.dart diff --git a/lib/super_admin/ui/pages/memberships/association_membership_detail_page/search_filters.dart b/lib/admin/ui/pages/association_membership_detail_page/search_filters.dart similarity index 100% rename from lib/super_admin/ui/pages/memberships/association_membership_detail_page/search_filters.dart rename to lib/admin/ui/pages/association_membership_detail_page/search_filters.dart diff --git a/lib/admin/ui/pages/association_membership_page/association_membership_creation_dialog.dart b/lib/admin/ui/pages/association_membership_page/association_membership_creation_dialog.dart deleted file mode 100644 index 39d485c27d..0000000000 --- a/lib/admin/ui/pages/association_membership_page/association_membership_creation_dialog.dart +++ /dev/null @@ -1,153 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:titan/admin/class/simple_group.dart'; -import 'package:titan/tools/constants.dart'; -import 'package:titan/l10n/app_localizations.dart'; - -class MembershipCreationDialogBox extends StatelessWidget { - static const Color titleColor = ColorConstants.gradient1; - static const Color descriptionColor = Colors.black; - static const Color yesColor = ColorConstants.gradient2; - static const Color noColor = ColorConstants.background2; - - final Function() onYes; - final Function()? onNo; - final TextEditingController nameController; - final TextEditingController groupIdController; - final List groups; - - static const double _padding = 20; - static const double _avatarRadius = 45; - - static const Color background = Color(0xfffafafa); - const MembershipCreationDialogBox({ - super.key, - required this.nameController, - required this.groupIdController, - required this.onYes, - required this.groups, - this.onNo, - }); - - @override - Widget build(BuildContext context) { - groups.sort( - (SimpleGroup a, SimpleGroup b) => - a.name.toLowerCase().compareTo(b.name.toLowerCase()), - ); - groupIdController.text = groups.first.id; - return Dialog( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - MembershipCreationDialogBox._padding, - ), - ), - elevation: 0, - backgroundColor: Colors.transparent, - child: Stack( - children: [ - Container( - padding: const EdgeInsets.all(MembershipCreationDialogBox._padding), - margin: const EdgeInsets.only( - top: MembershipCreationDialogBox._avatarRadius, - ), - decoration: BoxDecoration( - shape: BoxShape.rectangle, - color: MembershipCreationDialogBox.background, - borderRadius: BorderRadius.circular( - MembershipCreationDialogBox._padding, - ), - boxShadow: [ - BoxShadow( - color: Colors.grey.shade700, - offset: const Offset(0, 5), - blurRadius: 5, - ), - ], - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - AppLocalizations.of( - context, - )!.adminCreateAssociationMembership, - style: const TextStyle( - fontSize: 25, - fontWeight: FontWeight.w800, - color: titleColor, - ), - ), - const SizedBox(height: 15), - TextField( - controller: nameController, - decoration: InputDecoration( - hintText: AppLocalizations.of( - context, - )!.adminAssociationMembershipName, - ), - ), - const SizedBox(height: 20), - DropdownButtonFormField( - value: groupIdController.text, - onChanged: (String? newValue) { - groupIdController.text = newValue!; - }, - items: groups - .map( - (SimpleGroup group) => DropdownMenuItem( - value: group.id, - child: Text(group.name), - ), - ) - .toList(), - decoration: InputDecoration( - hintText: AppLocalizations.of(context)!.adminGroup, - ), - ), - const SizedBox(height: 20), - Align( - alignment: Alignment.bottomCenter, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - TextButton( - onPressed: () async { - Navigator.of(context).pop(); - await onYes(); - }, - child: const Text( - "Créer", - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - color: noColor, - ), - ), - ), - TextButton( - onPressed: () async { - if (onNo == null) { - Navigator.of(context).pop(); - } - onNo?.call(); - }, - child: const Text( - "Annuler", - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - color: yesColor, - ), - ), - ), - ], - ), - ), - ], - ), - ), - ], - ), - ); - } -} diff --git a/lib/admin/ui/pages/association_membership_page/association_membership_page.dart b/lib/admin/ui/pages/association_membership_page/association_membership_page.dart index 7ca052d1fc..a887d665a1 100644 --- a/lib/admin/ui/pages/association_membership_page/association_membership_page.dart +++ b/lib/admin/ui/pages/association_membership_page/association_membership_page.dart @@ -4,16 +4,17 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/class/association_membership_simple.dart'; import 'package:titan/admin/providers/all_groups_list_provider.dart'; import 'package:titan/admin/providers/association_membership_list_provider.dart'; +import 'package:titan/admin/router.dart'; import 'package:titan/admin/ui/pages/association_membership_page/add_membership_modal.dart'; import 'package:titan/super_admin/providers/association_membership_members_list_provider.dart'; import 'package:titan/super_admin/providers/association_membership_provider.dart'; -import 'package:titan/super_admin/router.dart'; import 'package:titan/super_admin/ui/admin.dart'; -import 'package:titan/admin/ui/pages/association_membership_page/association_membership_ui.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; +import 'package:titan/tools/ui/styleguide/button.dart'; import 'package:titan/tools/ui/styleguide/icon_button.dart'; +import 'package:titan/tools/ui/styleguide/list_item.dart'; import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; @@ -114,65 +115,87 @@ class AssociationMembershipsPage extends HookConsumerWidget { Column( children: [ ...g.map( - (associationMembership) => AssociationMembershipUi( - associationMembership: associationMembership, - onEdit: () { - associationMembershipMembersNotifier - .loadAssociationMembershipMembers( - associationMembership.id, - ); - associationMembershipNotifier - .setAssociationMembership( - associationMembership, - ); - QR.to( - SuperAdminRouter.root + - SuperAdminRouter.associationMemberships + - SuperAdminRouter - .detailAssociationMembership, - ); - }, - onDelete: () async { - await showDialog( + (associationMembership) => ListItem( + title: associationMembership.name, + onTap: () async { + await showCustomBottomModal( context: context, - builder: (context) { - return CustomDialogBox( - title: AppLocalizations.of( - context, - )!.adminDeleting, - descriptions: AppLocalizations.of( - context, - )!.adminDeleteAssociationMembership, - onYes: () async { - tokenExpireWrapper(ref, () async { - final deletedAssociationMembershipMsg = - AppLocalizations.of( - context, - )!.adminDeletedAssociationMembership; - final deletingErrorMsg = - AppLocalizations.of( - context, - )!.adminDeletingError; - final value = - await associationMembershipsNotifier - .deleteAssociationMembership( - associationMembership, - ); - if (value) { - displayToastWithContext( - TypeMsg.msg, - deletedAssociationMembershipMsg, + ref: ref, + modal: BottomModalTemplate( + title: associationMembership.name, + child: Column( + children: [ + Button( + text: "Modifier", + onPressed: () { + associationMembershipMembersNotifier + .loadAssociationMembershipMembers( + associationMembership.id, + ); + associationMembershipNotifier + .setAssociationMembership( + associationMembership, + ); + QR.to( + AdminRouter.root + + AdminRouter + .associationMemberships + + AdminRouter + .detailAssociationMembership, ); - } else { - displayToastWithContext( - TypeMsg.error, - deletingErrorMsg, + }, + ), + const SizedBox(height: 20), + Button( + text: "Supprimer", + type: ButtonType.danger, + onPressed: () async { + await showDialog( + context: context, + builder: (context) { + return CustomDialogBox( + title: AppLocalizations.of( + context, + )!.adminDeleting, + descriptions: AppLocalizations.of( + context, + )!.adminDeleteAssociationMembership, + onYes: () async { + tokenExpireWrapper(ref, () async { + final deletedAssociationMembershipMsg = + AppLocalizations.of( + context, + )!.adminDeletedAssociationMembership; + final deletingErrorMsg = + AppLocalizations.of( + context, + )!.adminDeletingError; + final value = + await associationMembershipsNotifier + .deleteAssociationMembership( + associationMembership, + ); + if (value) { + displayToastWithContext( + TypeMsg.msg, + deletedAssociationMembershipMsg, + ); + } else { + displayToastWithContext( + TypeMsg.error, + deletingErrorMsg, + ); + } + }); + }, + ); + }, ); - } - }); - }, - ); - }, + }, + ), + ], + ), + ), ); }, ), diff --git a/lib/admin/ui/pages/association_membership_page/association_membership_ui.dart b/lib/admin/ui/pages/association_membership_page/association_membership_ui.dart deleted file mode 100644 index fcead08391..0000000000 --- a/lib/admin/ui/pages/association_membership_page/association_membership_ui.dart +++ /dev/null @@ -1,62 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:heroicons/heroicons.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/class/association_membership_simple.dart'; -import 'package:titan/super_admin/ui/components/item_card_ui.dart'; -import 'package:titan/admin/ui/pages/association_membership_page/association_membership_button.dart'; -import 'package:titan/tools/constants.dart'; -import 'package:titan/tools/ui/builders/waiting_button.dart'; - -class AssociationMembershipUi extends HookConsumerWidget { - final AssociationMembership associationMembership; - final void Function() onEdit; - final Future Function() onDelete; - const AssociationMembershipUi({ - super.key, - required this.associationMembership, - required this.onEdit, - required this.onDelete, - }); - - @override - Widget build(BuildContext context, WidgetRef ref) { - return ItemCardUi( - children: [ - const SizedBox(width: 10), - Expanded( - child: Text( - associationMembership.name, - style: const TextStyle( - color: Colors.black, - fontSize: 20, - fontWeight: FontWeight.bold, - ), - ), - ), - const SizedBox(width: 10), - Row( - children: [ - GestureDetector( - onTap: onEdit, - child: AssociationMembershipButton( - gradient1: Colors.grey.shade800, - gradient2: Colors.grey.shade900, - child: const HeroIcon(HeroIcons.eye, color: Colors.white), - ), - ), - const SizedBox(width: 10), - WaitingButton( - onTap: onDelete, - builder: (child) => AssociationMembershipButton( - gradient1: ColorConstants.gradient1, - gradient2: ColorConstants.gradient2, - child: child, - ), - child: const HeroIcon(HeroIcons.xMark, color: Colors.white), - ), - ], - ), - ], - ); - } -} diff --git a/lib/super_admin/router.dart b/lib/super_admin/router.dart index 90809db30e..7f53312b4b 100644 --- a/lib/super_admin/router.dart +++ b/lib/super_admin/router.dart @@ -7,7 +7,7 @@ import 'package:titan/super_admin/ui/pages/edit_module_visibility/edit_module_vi import 'package:titan/super_admin/ui/pages/memberships/add_edit_user_membership_page/add_edit_user_membership_page.dart' deferred as add_edit_user_membership_page; -import 'package:titan/super_admin/ui/pages/memberships/association_membership_detail_page/association_membership_detail_page.dart' +import 'package:titan/admin/ui/pages/association_membership_detail_page/association_membership_detail_page.dart' deferred as association_membership_detail_page; import 'package:titan/admin/ui/pages/association_membership_page/association_membership_page.dart' deferred as association_membership_page; From a9f674c2b098b90fe5929d061a5de40f52b75d55 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Tue, 5 Aug 2025 18:48:50 +0200 Subject: [PATCH 163/473] move files --- .../class/user_association_membership.dart | 2 +- .../user_association_membership_base.dart | 0 ..._membership_filtered_members_provider.dart | 6 ++--- ...tion_membership_members_list_provider.dart | 6 ++--- .../association_membership_provider.dart | 0 .../providers/research_filter_provider.dart | 0 ..._association_membership_list_provider.dart | 4 +-- .../user_association_membership_provider.dart | 2 +- .../association_membership_repository.dart | 2 +- ...ssociation_membership_user_repository.dart | 4 +-- lib/admin/router.dart | 6 ++--- .../add_edit_user_membership_page.dart | 10 ++++---- .../search_result.dart | 2 +- .../association_membership_detail_page.dart | 18 ++++++------- ...ciation_membership_information_editor.dart | 2 +- ...ation_membership_member_editable_card.dart | 16 ++++++------ .../research_bar.dart | 2 +- .../search_filters.dart | 4 +-- .../add_membership_modal.dart | 0 .../association_membership_button.dart | 0 .../association_membership_page.dart | 10 ++++---- .../users_groups_management_page.dart | 25 ------------------- lib/super_admin/router.dart | 6 ++--- 23 files changed, 51 insertions(+), 76 deletions(-) rename lib/{super_admin => admin}/class/user_association_membership.dart (94%) rename lib/{super_admin => admin}/class/user_association_membership_base.dart (100%) rename lib/{super_admin => admin}/providers/association_membership_filtered_members_provider.dart (77%) rename lib/{super_admin => admin}/providers/association_membership_members_list_provider.dart (93%) rename lib/{super_admin => admin}/providers/association_membership_provider.dart (100%) rename lib/{super_admin => admin}/providers/research_filter_provider.dart (100%) rename lib/{super_admin => admin}/providers/user_association_membership_list_provider.dart (92%) rename lib/{super_admin => admin}/providers/user_association_membership_provider.dart (88%) rename lib/{super_admin => admin}/repositories/association_membership_user_repository.dart (91%) rename lib/{super_admin/ui/pages/memberships => admin/ui/pages/membership}/add_edit_user_membership_page/add_edit_user_membership_page.dart (94%) rename lib/{super_admin/ui/pages/memberships => admin/ui/pages/membership}/add_edit_user_membership_page/search_result.dart (96%) rename lib/admin/ui/pages/{ => membership}/association_membership_detail_page/association_membership_detail_page.dart (85%) rename lib/admin/ui/pages/{ => membership}/association_membership_detail_page/association_membership_information_editor.dart (98%) rename lib/admin/ui/pages/{ => membership}/association_membership_detail_page/association_membership_member_editable_card.dart (87%) rename lib/admin/ui/pages/{ => membership}/association_membership_detail_page/research_bar.dart (94%) rename lib/admin/ui/pages/{ => membership}/association_membership_detail_page/search_filters.dart (97%) rename lib/admin/ui/pages/{ => membership}/association_membership_page/add_membership_modal.dart (100%) rename lib/admin/ui/pages/{ => membership}/association_membership_page/association_membership_button.dart (100%) rename lib/admin/ui/pages/{ => membership}/association_membership_page/association_membership_page.dart (96%) delete mode 100644 lib/admin/ui/pages/users_groups_management_page/users_groups_management_page.dart diff --git a/lib/super_admin/class/user_association_membership.dart b/lib/admin/class/user_association_membership.dart similarity index 94% rename from lib/super_admin/class/user_association_membership.dart rename to lib/admin/class/user_association_membership.dart index 5938b5673a..1aab569848 100644 --- a/lib/super_admin/class/user_association_membership.dart +++ b/lib/admin/class/user_association_membership.dart @@ -1,4 +1,4 @@ -import 'package:titan/super_admin/class/user_association_membership_base.dart'; +import 'package:titan/admin/class/user_association_membership_base.dart'; import 'package:titan/user/class/simple_users.dart'; class UserAssociationMembership extends UserAssociationMembershipBase { diff --git a/lib/super_admin/class/user_association_membership_base.dart b/lib/admin/class/user_association_membership_base.dart similarity index 100% rename from lib/super_admin/class/user_association_membership_base.dart rename to lib/admin/class/user_association_membership_base.dart diff --git a/lib/super_admin/providers/association_membership_filtered_members_provider.dart b/lib/admin/providers/association_membership_filtered_members_provider.dart similarity index 77% rename from lib/super_admin/providers/association_membership_filtered_members_provider.dart rename to lib/admin/providers/association_membership_filtered_members_provider.dart index 0a3c0ae760..e74bccbaf7 100644 --- a/lib/super_admin/providers/association_membership_filtered_members_provider.dart +++ b/lib/admin/providers/association_membership_filtered_members_provider.dart @@ -1,7 +1,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/super_admin/class/user_association_membership.dart'; -import 'package:titan/super_admin/providers/association_membership_members_list_provider.dart'; -import 'package:titan/super_admin/providers/research_filter_provider.dart'; +import 'package:titan/admin/class/user_association_membership.dart'; +import 'package:titan/admin/providers/association_membership_members_list_provider.dart'; +import 'package:titan/admin/providers/research_filter_provider.dart'; import 'package:diacritic/diacritic.dart'; final associationMembershipFilteredListProvider = diff --git a/lib/super_admin/providers/association_membership_members_list_provider.dart b/lib/admin/providers/association_membership_members_list_provider.dart similarity index 93% rename from lib/super_admin/providers/association_membership_members_list_provider.dart rename to lib/admin/providers/association_membership_members_list_provider.dart index 4b9894068d..6964dbaec3 100644 --- a/lib/super_admin/providers/association_membership_members_list_provider.dart +++ b/lib/admin/providers/association_membership_members_list_provider.dart @@ -1,8 +1,8 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/super_admin/class/user_association_membership.dart'; -import 'package:titan/super_admin/class/user_association_membership_base.dart'; +import 'package:titan/admin/class/user_association_membership.dart'; +import 'package:titan/admin/class/user_association_membership_base.dart'; import 'package:titan/admin/repositories/association_membership_repository.dart'; -import 'package:titan/super_admin/repositories/association_membership_user_repository.dart'; +import 'package:titan/admin/repositories/association_membership_user_repository.dart'; import 'package:titan/tools/providers/list_notifier.dart'; import 'package:titan/user/class/simple_users.dart'; diff --git a/lib/super_admin/providers/association_membership_provider.dart b/lib/admin/providers/association_membership_provider.dart similarity index 100% rename from lib/super_admin/providers/association_membership_provider.dart rename to lib/admin/providers/association_membership_provider.dart diff --git a/lib/super_admin/providers/research_filter_provider.dart b/lib/admin/providers/research_filter_provider.dart similarity index 100% rename from lib/super_admin/providers/research_filter_provider.dart rename to lib/admin/providers/research_filter_provider.dart diff --git a/lib/super_admin/providers/user_association_membership_list_provider.dart b/lib/admin/providers/user_association_membership_list_provider.dart similarity index 92% rename from lib/super_admin/providers/user_association_membership_list_provider.dart rename to lib/admin/providers/user_association_membership_list_provider.dart index 19c3f405cf..9a6ad7875a 100644 --- a/lib/super_admin/providers/user_association_membership_list_provider.dart +++ b/lib/admin/providers/user_association_membership_list_provider.dart @@ -1,6 +1,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/super_admin/class/user_association_membership.dart'; -import 'package:titan/super_admin/repositories/association_membership_user_repository.dart'; +import 'package:titan/admin/class/user_association_membership.dart'; +import 'package:titan/admin/repositories/association_membership_user_repository.dart'; import 'package:titan/tools/providers/list_notifier.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; diff --git a/lib/super_admin/providers/user_association_membership_provider.dart b/lib/admin/providers/user_association_membership_provider.dart similarity index 88% rename from lib/super_admin/providers/user_association_membership_provider.dart rename to lib/admin/providers/user_association_membership_provider.dart index 0c4b73be7e..859dcdbc35 100644 --- a/lib/super_admin/providers/user_association_membership_provider.dart +++ b/lib/admin/providers/user_association_membership_provider.dart @@ -1,5 +1,5 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/super_admin/class/user_association_membership.dart'; +import 'package:titan/admin/class/user_association_membership.dart'; class UserAssociationMembershipNotifier extends StateNotifier { diff --git a/lib/admin/repositories/association_membership_repository.dart b/lib/admin/repositories/association_membership_repository.dart index c092ffd6ea..4b20a78a46 100644 --- a/lib/admin/repositories/association_membership_repository.dart +++ b/lib/admin/repositories/association_membership_repository.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/admin/class/association_membership_simple.dart'; -import 'package:titan/super_admin/class/user_association_membership.dart'; +import 'package:titan/admin/class/user_association_membership.dart'; import 'package:titan/auth/providers/openid_provider.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/repository/repository.dart'; diff --git a/lib/super_admin/repositories/association_membership_user_repository.dart b/lib/admin/repositories/association_membership_user_repository.dart similarity index 91% rename from lib/super_admin/repositories/association_membership_user_repository.dart rename to lib/admin/repositories/association_membership_user_repository.dart index 13a6d6cc8a..ff22deb29a 100644 --- a/lib/super_admin/repositories/association_membership_user_repository.dart +++ b/lib/admin/repositories/association_membership_user_repository.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/super_admin/class/user_association_membership.dart'; -import 'package:titan/super_admin/class/user_association_membership_base.dart'; +import 'package:titan/admin/class/user_association_membership.dart'; +import 'package:titan/admin/class/user_association_membership_base.dart'; import 'package:titan/auth/providers/openid_provider.dart'; import 'package:titan/tools/repository/repository.dart'; diff --git a/lib/admin/router.dart b/lib/admin/router.dart index 44a49c2ecd..acbb0d2313 100644 --- a/lib/admin/router.dart +++ b/lib/admin/router.dart @@ -15,11 +15,11 @@ import 'package:titan/admin/ui/pages/structure_page/add_edit_structure_page/add_ import 'package:titan/navigation/class/module.dart'; import 'package:titan/admin/ui/pages/structure_page/structure_page.dart' deferred as structure_page; -import 'package:titan/admin/ui/pages/association_membership_page/association_membership_page.dart' +import 'package:titan/admin/ui/pages/membership/association_membership_page/association_membership_page.dart' deferred as association_membership_page; -import 'package:titan/admin/ui/pages/association_membership_detail_page/association_membership_detail_page.dart' +import 'package:titan/admin/ui/pages/membership/association_membership_detail_page/association_membership_detail_page.dart' deferred as association_membership_detail_page; -import 'package:titan/super_admin/ui/pages/memberships/add_edit_user_membership_page/add_edit_user_membership_page.dart' +import 'package:titan/admin/ui/pages/membership/add_edit_user_membership_page/add_edit_user_membership_page.dart' deferred as add_edit_user_membership_page; import 'package:titan/tools/middlewares/authenticated_middleware.dart'; import 'package:titan/tools/middlewares/deferred_middleware.dart'; diff --git a/lib/super_admin/ui/pages/memberships/add_edit_user_membership_page/add_edit_user_membership_page.dart b/lib/admin/ui/pages/membership/add_edit_user_membership_page/add_edit_user_membership_page.dart similarity index 94% rename from lib/super_admin/ui/pages/memberships/add_edit_user_membership_page/add_edit_user_membership_page.dart rename to lib/admin/ui/pages/membership/add_edit_user_membership_page/add_edit_user_membership_page.dart index f43e526ef1..58e283e1b0 100644 --- a/lib/super_admin/ui/pages/memberships/add_edit_user_membership_page/add_edit_user_membership_page.dart +++ b/lib/admin/ui/pages/membership/add_edit_user_membership_page/add_edit_user_membership_page.dart @@ -1,12 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/super_admin/class/user_association_membership.dart'; -import 'package:titan/super_admin/class/user_association_membership_base.dart'; -import 'package:titan/super_admin/providers/association_membership_members_list_provider.dart'; -import 'package:titan/super_admin/providers/user_association_membership_provider.dart'; +import 'package:titan/admin/class/user_association_membership.dart'; +import 'package:titan/admin/class/user_association_membership_base.dart'; +import 'package:titan/admin/providers/association_membership_members_list_provider.dart'; +import 'package:titan/admin/providers/user_association_membership_provider.dart'; import 'package:titan/super_admin/ui/admin.dart'; -import 'package:titan/super_admin/ui/pages/memberships/add_edit_user_membership_page/search_result.dart'; +import 'package:titan/admin/ui/pages/membership/add_edit_user_membership_page/search_result.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; diff --git a/lib/super_admin/ui/pages/memberships/add_edit_user_membership_page/search_result.dart b/lib/admin/ui/pages/membership/add_edit_user_membership_page/search_result.dart similarity index 96% rename from lib/super_admin/ui/pages/memberships/add_edit_user_membership_page/search_result.dart rename to lib/admin/ui/pages/membership/add_edit_user_membership_page/search_result.dart index 10fe6dfe26..e9d0994a6b 100644 --- a/lib/super_admin/ui/pages/memberships/add_edit_user_membership_page/search_result.dart +++ b/lib/admin/ui/pages/membership/add_edit_user_membership_page/search_result.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/super_admin/providers/user_association_membership_provider.dart'; +import 'package:titan/admin/providers/user_association_membership_provider.dart'; import 'package:titan/user/providers/user_list_provider.dart'; class SearchResult extends HookConsumerWidget { diff --git a/lib/admin/ui/pages/association_membership_detail_page/association_membership_detail_page.dart b/lib/admin/ui/pages/membership/association_membership_detail_page/association_membership_detail_page.dart similarity index 85% rename from lib/admin/ui/pages/association_membership_detail_page/association_membership_detail_page.dart rename to lib/admin/ui/pages/membership/association_membership_detail_page/association_membership_detail_page.dart index d37e4c6af8..b96ebbffc5 100644 --- a/lib/admin/ui/pages/association_membership_detail_page/association_membership_detail_page.dart +++ b/lib/admin/ui/pages/membership/association_membership_detail_page/association_membership_detail_page.dart @@ -3,15 +3,15 @@ import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/admin.dart'; import 'package:titan/admin/router.dart'; -import 'package:titan/super_admin/class/user_association_membership.dart'; -import 'package:titan/super_admin/providers/association_membership_filtered_members_provider.dart'; -import 'package:titan/super_admin/providers/association_membership_members_list_provider.dart'; -import 'package:titan/super_admin/providers/association_membership_provider.dart'; -import 'package:titan/super_admin/providers/user_association_membership_provider.dart'; -import 'package:titan/admin/ui/pages/association_membership_detail_page/association_membership_information_editor.dart'; -import 'package:titan/admin/ui/pages/association_membership_detail_page/association_membership_member_editable_card.dart'; -import 'package:titan/admin/ui/pages/association_membership_detail_page/research_bar.dart'; -import 'package:titan/admin/ui/pages/association_membership_detail_page/search_filters.dart'; +import 'package:titan/admin/class/user_association_membership.dart'; +import 'package:titan/admin/providers/association_membership_filtered_members_provider.dart'; +import 'package:titan/admin/providers/association_membership_members_list_provider.dart'; +import 'package:titan/admin/providers/association_membership_provider.dart'; +import 'package:titan/admin/providers/user_association_membership_provider.dart'; +import 'package:titan/admin/ui/pages/membership/association_membership_detail_page/association_membership_information_editor.dart'; +import 'package:titan/admin/ui/pages/membership/association_membership_detail_page/association_membership_member_editable_card.dart'; +import 'package:titan/admin/ui/pages/membership/association_membership_detail_page/research_bar.dart'; +import 'package:titan/admin/ui/pages/membership/association_membership_detail_page/search_filters.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; diff --git a/lib/admin/ui/pages/association_membership_detail_page/association_membership_information_editor.dart b/lib/admin/ui/pages/membership/association_membership_detail_page/association_membership_information_editor.dart similarity index 98% rename from lib/admin/ui/pages/association_membership_detail_page/association_membership_information_editor.dart rename to lib/admin/ui/pages/membership/association_membership_detail_page/association_membership_information_editor.dart index e5fe6b8b97..03ae463783 100644 --- a/lib/admin/ui/pages/association_membership_detail_page/association_membership_information_editor.dart +++ b/lib/admin/ui/pages/membership/association_membership_detail_page/association_membership_information_editor.dart @@ -4,7 +4,7 @@ import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/providers/all_groups_list_provider.dart'; import 'package:titan/admin/providers/association_membership_list_provider.dart'; -import 'package:titan/super_admin/providers/association_membership_provider.dart'; +import 'package:titan/admin/providers/association_membership_provider.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; diff --git a/lib/admin/ui/pages/association_membership_detail_page/association_membership_member_editable_card.dart b/lib/admin/ui/pages/membership/association_membership_detail_page/association_membership_member_editable_card.dart similarity index 87% rename from lib/admin/ui/pages/association_membership_detail_page/association_membership_member_editable_card.dart rename to lib/admin/ui/pages/membership/association_membership_detail_page/association_membership_member_editable_card.dart index 6be6073204..7693ee0e75 100644 --- a/lib/admin/ui/pages/association_membership_detail_page/association_membership_member_editable_card.dart +++ b/lib/admin/ui/pages/membership/association_membership_detail_page/association_membership_member_editable_card.dart @@ -1,10 +1,10 @@ import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/super_admin/class/user_association_membership.dart'; -import 'package:titan/super_admin/providers/association_membership_members_list_provider.dart'; -import 'package:titan/super_admin/providers/user_association_membership_provider.dart'; -import 'package:titan/super_admin/router.dart'; +import 'package:titan/admin/class/user_association_membership.dart'; +import 'package:titan/admin/providers/association_membership_members_list_provider.dart'; +import 'package:titan/admin/providers/user_association_membership_provider.dart'; +import 'package:titan/admin/router.dart'; import 'package:titan/phonebook/ui/pages/admin_page/delete_button.dart'; import 'package:titan/phonebook/ui/pages/admin_page/edition_button.dart'; import 'package:titan/tools/functions.dart'; @@ -76,10 +76,10 @@ class MemberEditableCard extends HookConsumerWidget { associationMembership, ); QR.to( - SuperAdminRouter.root + - SuperAdminRouter.associationMemberships + - SuperAdminRouter.detailAssociationMembership + - SuperAdminRouter.addEditMember, + AdminRouter.root + + AdminRouter.associationMemberships + + AdminRouter.detailAssociationMembership + + AdminRouter.addEditMember, ); }, ), diff --git a/lib/admin/ui/pages/association_membership_detail_page/research_bar.dart b/lib/admin/ui/pages/membership/association_membership_detail_page/research_bar.dart similarity index 94% rename from lib/admin/ui/pages/association_membership_detail_page/research_bar.dart rename to lib/admin/ui/pages/membership/association_membership_detail_page/research_bar.dart index 991bfe6234..84b1f5accf 100644 --- a/lib/admin/ui/pages/association_membership_detail_page/research_bar.dart +++ b/lib/admin/ui/pages/membership/association_membership_detail_page/research_bar.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/super_admin/providers/research_filter_provider.dart'; +import 'package:titan/admin/providers/research_filter_provider.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/l10n/app_localizations.dart'; diff --git a/lib/admin/ui/pages/association_membership_detail_page/search_filters.dart b/lib/admin/ui/pages/membership/association_membership_detail_page/search_filters.dart similarity index 97% rename from lib/admin/ui/pages/association_membership_detail_page/search_filters.dart rename to lib/admin/ui/pages/membership/association_membership_detail_page/search_filters.dart index 7ca8d6c671..3fbea81199 100644 --- a/lib/admin/ui/pages/association_membership_detail_page/search_filters.dart +++ b/lib/admin/ui/pages/membership/association_membership_detail_page/search_filters.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/super_admin/providers/association_membership_members_list_provider.dart'; -import 'package:titan/super_admin/providers/association_membership_provider.dart'; +import 'package:titan/admin/providers/association_membership_members_list_provider.dart'; +import 'package:titan/admin/providers/association_membership_provider.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; diff --git a/lib/admin/ui/pages/association_membership_page/add_membership_modal.dart b/lib/admin/ui/pages/membership/association_membership_page/add_membership_modal.dart similarity index 100% rename from lib/admin/ui/pages/association_membership_page/add_membership_modal.dart rename to lib/admin/ui/pages/membership/association_membership_page/add_membership_modal.dart diff --git a/lib/admin/ui/pages/association_membership_page/association_membership_button.dart b/lib/admin/ui/pages/membership/association_membership_page/association_membership_button.dart similarity index 100% rename from lib/admin/ui/pages/association_membership_page/association_membership_button.dart rename to lib/admin/ui/pages/membership/association_membership_page/association_membership_button.dart diff --git a/lib/admin/ui/pages/association_membership_page/association_membership_page.dart b/lib/admin/ui/pages/membership/association_membership_page/association_membership_page.dart similarity index 96% rename from lib/admin/ui/pages/association_membership_page/association_membership_page.dart rename to lib/admin/ui/pages/membership/association_membership_page/association_membership_page.dart index a887d665a1..25095781ef 100644 --- a/lib/admin/ui/pages/association_membership_page/association_membership_page.dart +++ b/lib/admin/ui/pages/membership/association_membership_page/association_membership_page.dart @@ -1,14 +1,14 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/admin/admin.dart'; import 'package:titan/admin/class/association_membership_simple.dart'; import 'package:titan/admin/providers/all_groups_list_provider.dart'; import 'package:titan/admin/providers/association_membership_list_provider.dart'; import 'package:titan/admin/router.dart'; -import 'package:titan/admin/ui/pages/association_membership_page/add_membership_modal.dart'; -import 'package:titan/super_admin/providers/association_membership_members_list_provider.dart'; -import 'package:titan/super_admin/providers/association_membership_provider.dart'; -import 'package:titan/super_admin/ui/admin.dart'; +import 'package:titan/admin/ui/pages/membership/association_membership_page/add_membership_modal.dart'; +import 'package:titan/admin/providers/association_membership_members_list_provider.dart'; +import 'package:titan/admin/providers/association_membership_provider.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; @@ -49,7 +49,7 @@ class AssociationMembershipsPage extends HookConsumerWidget { Navigator.of(context).pop(); } - return SuperAdminTemplate( + return AdminTemplate( child: Refresher( onRefresh: () async { await associationMembershipsNotifier.loadAssociationMemberships(); diff --git a/lib/admin/ui/pages/users_groups_management_page/users_groups_management_page.dart b/lib/admin/ui/pages/users_groups_management_page/users_groups_management_page.dart deleted file mode 100644 index ee7a751769..0000000000 --- a/lib/admin/ui/pages/users_groups_management_page/users_groups_management_page.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/admin.dart'; - -class UsersGroupsManagementPage extends HookConsumerWidget { - const UsersGroupsManagementPage({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - return AdminTemplate( - child: Padding( - padding: const EdgeInsets.all(40), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Gestion des utilisateurs", - style: Theme.of(context).textTheme.headlineMedium, - ), - ], - ), - ), - ); - } -} diff --git a/lib/super_admin/router.dart b/lib/super_admin/router.dart index 7f53312b4b..8c60590a78 100644 --- a/lib/super_admin/router.dart +++ b/lib/super_admin/router.dart @@ -5,11 +5,11 @@ import 'package:titan/super_admin/providers/is_admin_provider.dart'; import 'package:titan/super_admin/ui/pages/edit_module_visibility/edit_module_visibility.dart' deferred as edit_module_visibility; -import 'package:titan/super_admin/ui/pages/memberships/add_edit_user_membership_page/add_edit_user_membership_page.dart' +import 'package:titan/admin/ui/pages/membership/add_edit_user_membership_page/add_edit_user_membership_page.dart' deferred as add_edit_user_membership_page; -import 'package:titan/admin/ui/pages/association_membership_detail_page/association_membership_detail_page.dart' +import 'package:titan/admin/ui/pages/membership/association_membership_detail_page/association_membership_detail_page.dart' deferred as association_membership_detail_page; -import 'package:titan/admin/ui/pages/association_membership_page/association_membership_page.dart' +import 'package:titan/admin/ui/pages/membership/association_membership_page/association_membership_page.dart' deferred as association_membership_page; import 'package:titan/super_admin/ui/pages/schools/school_page/school_page.dart' deferred as school_page; From f9db6cfd6c0e1e4f4781e3af7b9c4c22edb8c86d Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Tue, 5 Aug 2025 21:20:52 +0200 Subject: [PATCH 164/473] user invitation --- .../providers/user_creation_provider.dart | 18 ++++ .../user_creation_repository.dart | 19 ++++ .../users_management_page/add_user_modal.dart | 91 +++++++++++++++++++ .../users_management_page.dart | 14 +-- 4 files changed, 132 insertions(+), 10 deletions(-) create mode 100644 lib/admin/providers/user_creation_provider.dart create mode 100644 lib/admin/repositories/user_creation_repository.dart create mode 100644 lib/admin/ui/pages/users_management_page/add_user_modal.dart diff --git a/lib/admin/providers/user_creation_provider.dart b/lib/admin/providers/user_creation_provider.dart new file mode 100644 index 0000000000..d9e7d88cf2 --- /dev/null +++ b/lib/admin/providers/user_creation_provider.dart @@ -0,0 +1,18 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/admin/repositories/user_creation_repository.dart'; + +class UserCreationNotifier extends StateNotifier { + final UserCreationRepository userCreationRepository; + UserCreationNotifier({required this.userCreationRepository}) : super(null); + + Future createUsers(List mailList) async { + return await userCreationRepository.createUsers(mailList); + } +} + +final userCreationProvider = StateNotifierProvider(( + ref, +) { + final userCreationRepository = ref.watch(userCreationRepositoryProvider); + return UserCreationNotifier(userCreationRepository: userCreationRepository); +}); diff --git a/lib/admin/repositories/user_creation_repository.dart b/lib/admin/repositories/user_creation_repository.dart new file mode 100644 index 0000000000..df16849dc7 --- /dev/null +++ b/lib/admin/repositories/user_creation_repository.dart @@ -0,0 +1,19 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/auth/providers/openid_provider.dart'; +import 'package:titan/tools/repository/repository.dart'; + +class UserCreationRepository extends Repository { + @override + // ignore: overridden_fields + final ext = "users/"; + + Future createUsers(List mailList) async { + final json = mailList.map((email) => {'email': email}).toList(); + return await create(json, suffix: "batch-invitation"); + } +} + +final userCreationRepositoryProvider = Provider((ref) { + final token = ref.watch(tokenProvider); + return UserCreationRepository()..setToken(token); +}); diff --git a/lib/admin/ui/pages/users_management_page/add_user_modal.dart b/lib/admin/ui/pages/users_management_page/add_user_modal.dart new file mode 100644 index 0000000000..4a46dbb441 --- /dev/null +++ b/lib/admin/ui/pages/users_management_page/add_user_modal.dart @@ -0,0 +1,91 @@ +import 'dart:io'; + +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:heroicons/heroicons.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/admin/providers/user_creation_provider.dart'; +import 'package:titan/tools/functions.dart'; +import 'package:titan/tools/token_expire_wrapper.dart'; +import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; +import 'package:titan/tools/ui/styleguide/button.dart'; +import 'package:titan/tools/ui/styleguide/icon_button.dart'; + +class AddUsersModalContent extends HookConsumerWidget { + const AddUsersModalContent({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final selectedFileName = useState(null); + final mailList = useState>([]); + + void displayToastWithContext(TypeMsg type, String msg) { + displayToast(context, type, msg); + } + + return BottomModalTemplate( + title: "Ajouter des utilisateurs", + child: Column( + children: [ + Button( + text: selectedFileName.value ?? "Importer une liste", + onPressed: () async { + final result = await FilePicker.platform.pickFiles( + type: FileType.custom, + allowedExtensions: ['csv'], + ); + if (result != null && result.files.isNotEmpty) { + final file = result.files.first; + + selectedFileName.value = file.name; + + if (file.path != null) { + final fileContent = await File(file.path!).readAsString(); + + final lines = fileContent.split('\n'); + final List emails = []; + + for (var i = 0; i < lines.length; i++) { + final line = lines[i].trim(); + if (line.isEmpty) continue; + + final columns = line.split(','); + + final email = columns[0].trim(); + + if (email.contains('@')) { + emails.add(email); + } + } + mailList.value = emails; + } + } + }, + ), + const SizedBox(height: 20), + Button( + text: "Ajouter", + onPressed: () { + tokenExpireWrapper(ref, () async { + final userCreationNotifier = ref.watch( + userCreationProvider.notifier, + ); + final value = await userCreationNotifier.createUsers( + mailList.value, + ); + if (value) { + displayToastWithContext(TypeMsg.msg, "Succès"); + } else { + displayToastWithContext(TypeMsg.error, "Échec"); + } + // popWithContext(); + }); + }, + disabled: selectedFileName.value == null, + ), + ], + ), + ); + } +} diff --git a/lib/admin/ui/pages/users_management_page/users_management_page.dart b/lib/admin/ui/pages/users_management_page/users_management_page.dart index 5c25fdfa4d..640c04e96a 100644 --- a/lib/admin/ui/pages/users_management_page/users_management_page.dart +++ b/lib/admin/ui/pages/users_management_page/users_management_page.dart @@ -1,5 +1,8 @@ +import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/admin/ui/pages/users_management_page/add_user_modal.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; import 'package:titan/tools/ui/styleguide/button.dart'; @@ -18,16 +21,7 @@ class UsersManagementPage extends HookConsumerWidget { await showCustomBottomModal( context: context, ref: ref, - modal: BottomModalTemplate( - title: "Ajouter des utilisateurs", - child: Column( - children: [ - Button(text: "Importer une liste", onPressed: () {}), - const SizedBox(height: 20), - Button(text: "Ajouter", onPressed: () {}, disabled: true), - ], - ), - ), + modal: AddUsersModalContent(), ); }, ), From 7d72e09483553b6726b958046fa0b580cf7aa22a Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Tue, 5 Aug 2025 21:21:18 +0200 Subject: [PATCH 165/473] useless imports --- lib/admin/ui/pages/users_management_page/add_user_modal.dart | 2 -- .../ui/pages/users_management_page/users_management_page.dart | 2 -- 2 files changed, 4 deletions(-) diff --git a/lib/admin/ui/pages/users_management_page/add_user_modal.dart b/lib/admin/ui/pages/users_management_page/add_user_modal.dart index 4a46dbb441..8f9da9e807 100644 --- a/lib/admin/ui/pages/users_management_page/add_user_modal.dart +++ b/lib/admin/ui/pages/users_management_page/add_user_modal.dart @@ -3,14 +3,12 @@ import 'dart:io'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/providers/user_creation_provider.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; import 'package:titan/tools/ui/styleguide/button.dart'; -import 'package:titan/tools/ui/styleguide/icon_button.dart'; class AddUsersModalContent extends HookConsumerWidget { const AddUsersModalContent({super.key}); diff --git a/lib/admin/ui/pages/users_management_page/users_management_page.dart b/lib/admin/ui/pages/users_management_page/users_management_page.dart index 640c04e96a..a58ef0385d 100644 --- a/lib/admin/ui/pages/users_management_page/users_management_page.dart +++ b/lib/admin/ui/pages/users_management_page/users_management_page.dart @@ -1,6 +1,4 @@ -import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/ui/pages/users_management_page/add_user_modal.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; From 6e98ae0b704176d7e559442c72f76edd527d4ca4 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Tue, 5 Aug 2025 21:26:56 +0200 Subject: [PATCH 166/473] isSuperAdmin -> isAdmin --- lib/{super_admin => admin}/providers/is_admin_provider.dart | 2 +- lib/advert/ui/pages/main_page/main_page.dart | 4 ++-- lib/feed/router.dart | 4 ++-- lib/feed/ui/pages/main_page/main_page.dart | 4 ++-- lib/phonebook/providers/phonebook_admin_provider.dart | 4 ++-- .../association_information_editor.dart | 4 ++-- lib/phonebook/ui/pages/main_page/main_page.dart | 4 ++-- lib/settings/providers/module_list_provider.dart | 4 ++-- lib/super_admin/router.dart | 4 ++-- test/admin/is_admin_test.dart | 6 +++--- 10 files changed, 20 insertions(+), 20 deletions(-) rename lib/{super_admin => admin}/providers/is_admin_provider.dart (81%) diff --git a/lib/super_admin/providers/is_admin_provider.dart b/lib/admin/providers/is_admin_provider.dart similarity index 81% rename from lib/super_admin/providers/is_admin_provider.dart rename to lib/admin/providers/is_admin_provider.dart index 5df7e1e55a..a883e1bd04 100644 --- a/lib/super_admin/providers/is_admin_provider.dart +++ b/lib/admin/providers/is_admin_provider.dart @@ -1,7 +1,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/user/providers/user_provider.dart'; -final isSuperAdminProvider = StateProvider((ref) { +final isAdminProvider = StateProvider((ref) { final me = ref.watch(userProvider); return me.groups .map((e) => e.id) diff --git a/lib/advert/ui/pages/main_page/main_page.dart b/lib/advert/ui/pages/main_page/main_page.dart index 011eefaa92..992321143b 100644 --- a/lib/advert/ui/pages/main_page/main_page.dart +++ b/lib/advert/ui/pages/main_page/main_page.dart @@ -10,7 +10,7 @@ import 'package:titan/advert/ui/pages/advert.dart'; import 'package:titan/advert/router.dart'; import 'package:titan/advert/ui/components/announcer_bar.dart'; import 'package:titan/advert/ui/components/advert_card.dart'; -import 'package:titan/super_admin/providers/is_admin_provider.dart'; +import 'package:titan/admin/providers/is_admin_provider.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/column_refresher.dart'; import 'package:titan/tools/ui/widgets/admin_button.dart'; @@ -28,7 +28,7 @@ class AdvertMainPage extends HookConsumerWidget { final advertPostersNotifier = ref.watch(advertPostersProvider.notifier); final selected = ref.watch(announcerProvider); final selectedNotifier = ref.watch(announcerProvider.notifier); - final isAdmin = ref.watch(isSuperAdminProvider); + final isAdmin = ref.watch(isAdminProvider); final isAdvertAdmin = ref.watch(isAdvertAdminProvider); return AdvertTemplate( child: Stack( diff --git a/lib/feed/router.dart b/lib/feed/router.dart index cb3ea99ab7..b5fece5628 100644 --- a/lib/feed/router.dart +++ b/lib/feed/router.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/super_admin/providers/is_admin_provider.dart'; +import 'package:titan/admin/providers/is_admin_provider.dart'; import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/navigation/class/module.dart'; import 'package:titan/feed/ui/pages/admin_page/admin_page.dart' @@ -43,7 +43,7 @@ class FeedRouter { builder: () => admin_page.AdminPage(), middleware: [ AuthenticatedMiddleware(ref), - AdminMiddleware(ref, isSuperAdminProvider), + AdminMiddleware(ref, isAdminProvider), DeferredLoadingMiddleware(admin_page.loadLibrary), ], ), diff --git a/lib/feed/ui/pages/main_page/main_page.dart b/lib/feed/ui/pages/main_page/main_page.dart index 40947455b7..dfbdfa6edd 100644 --- a/lib/feed/ui/pages/main_page/main_page.dart +++ b/lib/feed/ui/pages/main_page/main_page.dart @@ -3,7 +3,7 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:qlevar_router/qlevar_router.dart'; -import 'package:titan/super_admin/providers/is_admin_provider.dart'; +import 'package:titan/admin/providers/is_admin_provider.dart'; import 'package:titan/feed/class/feed_item.dart'; import 'package:titan/feed/router.dart'; import 'package:titan/feed/ui/feed.dart'; @@ -24,7 +24,7 @@ class FeedMainPage extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final feedItems = useState>(FeedItem.getFakeItems()); final filteredItems = useState>(feedItems.value); - final isSuperAdmin = ref.watch(isSuperAdminProvider); + final isSuperAdmin = ref.watch(isAdminProvider); final scrollController = useScrollController(); return FeedTemplate( diff --git a/lib/phonebook/providers/phonebook_admin_provider.dart b/lib/phonebook/providers/phonebook_admin_provider.dart index 681c939c2e..0d6475dbc5 100644 --- a/lib/phonebook/providers/phonebook_admin_provider.dart +++ b/lib/phonebook/providers/phonebook_admin_provider.dart @@ -1,5 +1,5 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/super_admin/providers/is_admin_provider.dart'; +import 'package:titan/admin/providers/is_admin_provider.dart'; import 'package:titan/phonebook/providers/association_member_list_provider.dart'; import 'package:titan/phonebook/providers/association_provider.dart'; import 'package:titan/phonebook/tools/constants.dart'; @@ -20,7 +20,7 @@ final isPhonebookAdminProvider = StateProvider((ref) { final hasPhonebookAdminAccessProvider = StateProvider((ref) { final isPhonebookAdmin = ref.watch(isPhonebookAdminProvider); - final isAdmin = ref.watch(isSuperAdminProvider); + final isAdmin = ref.watch(isAdminProvider); return isPhonebookAdmin || isAdmin; }); diff --git a/lib/phonebook/ui/pages/association_editor_page/association_information_editor.dart b/lib/phonebook/ui/pages/association_editor_page/association_information_editor.dart index 061b5ca7e9..941cf3b9d3 100644 --- a/lib/phonebook/ui/pages/association_editor_page/association_information_editor.dart +++ b/lib/phonebook/ui/pages/association_editor_page/association_information_editor.dart @@ -4,7 +4,7 @@ import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/class/simple_group.dart'; import 'package:titan/admin/providers/group_list_provider.dart'; -import 'package:titan/super_admin/providers/is_admin_provider.dart'; +import 'package:titan/admin/providers/is_admin_provider.dart'; import 'package:titan/phonebook/providers/association_kind_provider.dart'; import 'package:titan/phonebook/providers/association_list_provider.dart'; import 'package:titan/phonebook/providers/association_provider.dart'; @@ -32,7 +32,7 @@ class AssociationInformationEditor extends HookConsumerWidget { final name = useTextEditingController(text: association.name); final description = useTextEditingController(text: association.description); final associationListNotifier = ref.watch(associationListProvider.notifier); - final isAdmin = ref.watch(isSuperAdminProvider); + final isAdmin = ref.watch(isAdminProvider); final isPhonebookAdmin = ref.watch(isPhonebookAdminProvider); final groups = ref.watch(allGroupListProvider); diff --git a/lib/phonebook/ui/pages/main_page/main_page.dart b/lib/phonebook/ui/pages/main_page/main_page.dart index 186a0fef6c..00df2b5cf6 100644 --- a/lib/phonebook/ui/pages/main_page/main_page.dart +++ b/lib/phonebook/ui/pages/main_page/main_page.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/super_admin/providers/is_admin_provider.dart'; +import 'package:titan/admin/providers/is_admin_provider.dart'; import 'package:titan/phonebook/providers/association_filtered_list_provider.dart'; import 'package:titan/phonebook/providers/association_kind_provider.dart'; import 'package:titan/phonebook/providers/association_kinds_provider.dart'; @@ -24,7 +24,7 @@ class PhonebookMainPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final isPhonebookAdmin = ref.watch(isPhonebookAdminProvider); - final isAdmin = ref.watch(isSuperAdminProvider); + final isAdmin = ref.watch(isAdminProvider); final associationNotifier = ref.watch(associationProvider.notifier); final associationListNotifier = ref.watch(associationListProvider.notifier); final associationList = ref.watch(associationListProvider); diff --git a/lib/settings/providers/module_list_provider.dart b/lib/settings/providers/module_list_provider.dart index 99e19ed3b6..3763968e2f 100644 --- a/lib/settings/providers/module_list_provider.dart +++ b/lib/settings/providers/module_list_provider.dart @@ -1,7 +1,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/admin/router.dart'; -import 'package:titan/super_admin/providers/is_admin_provider.dart'; +import 'package:titan/admin/providers/is_admin_provider.dart'; import 'package:titan/advert/router.dart'; import 'package:titan/super_admin/providers/all_my_module_roots_list_provider.dart'; import 'package:titan/amap/router.dart'; @@ -33,7 +33,7 @@ final modulesProvider = StateNotifierProvider>(( .map((root) => '/$root') .toList(); - final isAdmin = ref.watch(isSuperAdminProvider); + final isAdmin = ref.watch(isAdminProvider); ModulesNotifier modulesNotifier = ModulesNotifier(isAdmin: isAdmin); modulesNotifier.loadModules(myModulesRoot); diff --git a/lib/super_admin/router.dart b/lib/super_admin/router.dart index 8c60590a78..478c0f8115 100644 --- a/lib/super_admin/router.dart +++ b/lib/super_admin/router.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/super_admin/providers/is_admin_provider.dart'; +import 'package:titan/admin/providers/is_admin_provider.dart'; import 'package:titan/super_admin/ui/pages/edit_module_visibility/edit_module_visibility.dart' deferred as edit_module_visibility; @@ -59,7 +59,7 @@ class SuperAdminRouter { builder: () => main_page.SuperAdminMainPage(), middleware: [ AuthenticatedMiddleware(ref), - AdminMiddleware(ref, isSuperAdminProvider), + AdminMiddleware(ref, isAdminProvider), DeferredLoadingMiddleware(main_page.loadLibrary), ], pageType: QCustomPage( diff --git a/test/admin/is_admin_test.dart b/test/admin/is_admin_test.dart index 1634dd70cb..8d580e6a2e 100644 --- a/test/admin/is_admin_test.dart +++ b/test/admin/is_admin_test.dart @@ -1,7 +1,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/class/simple_group.dart'; -import 'package:titan/super_admin/providers/is_admin_provider.dart'; +import 'package:titan/admin/providers/is_admin_provider.dart'; import 'package:titan/user/class/user.dart'; import 'package:titan/user/providers/user_provider.dart'; @@ -24,7 +24,7 @@ void main() { ], ); - final isAdmin = container.read(isSuperAdminProvider); + final isAdmin = container.read(isAdminProvider); expect(isAdmin, true); }); @@ -40,7 +40,7 @@ void main() { ], ); - final isAdmin = container.read(isSuperAdminProvider); + final isAdmin = container.read(isAdminProvider); expect(isAdmin, false); }); From 92ce97d352ccc9367361fd32b4cd07dd6d96ff39 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 9 Aug 2025 19:23:19 +0200 Subject: [PATCH 167/473] color --- lib/admin/ui/components/admin_button.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/admin/ui/components/admin_button.dart b/lib/admin/ui/components/admin_button.dart index 72352229c5..2d9671aca9 100644 --- a/lib/admin/ui/components/admin_button.dart +++ b/lib/admin/ui/components/admin_button.dart @@ -14,11 +14,11 @@ class SuperAdminButton extends StatelessWidget { alignment: Alignment.center, decoration: BoxDecoration( gradient: const LinearGradient( - colors: [ColorConstants.gradient1, ColorConstants.gradient2], + colors: [ColorConstants.main, ColorConstants.onMain], ), boxShadow: [ BoxShadow( - color: ColorConstants.gradient2.withValues(alpha: 0.5), + color: ColorConstants.main.withValues(alpha: 0.5), blurRadius: 5, offset: const Offset(2, 2), spreadRadius: 2, From 7221f3df2a6e8ebb351987d361fb25197a96ac28 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 9 Aug 2025 19:41:41 +0200 Subject: [PATCH 168/473] color --- lib/admin/ui/pages/groups/groups_page/groups_ui.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/admin/ui/pages/groups/groups_page/groups_ui.dart b/lib/admin/ui/pages/groups/groups_page/groups_ui.dart index 438ff7d071..ef7973025b 100644 --- a/lib/admin/ui/pages/groups/groups_page/groups_ui.dart +++ b/lib/admin/ui/pages/groups/groups_page/groups_ui.dart @@ -48,8 +48,8 @@ class GroupUi extends HookConsumerWidget { WaitingButton( onTap: onDelete, builder: (child) => GroupButton( - gradient1: ColorConstants.gradient1, - gradient2: ColorConstants.gradient2, + gradient1: ColorConstants.main, + gradient2: ColorConstants.onMain, child: child, ), child: const HeroIcon(HeroIcons.xMark, color: Colors.white), From 45f04a4ffed940f4cf39dadce2cefa5e976563bb Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 9 Aug 2025 19:41:55 +0200 Subject: [PATCH 169/473] translation part 1 --- .../group_notification_page.dart | 23 ++++-- .../pages/groups/edit_group_page/results.dart | 4 +- .../groups/edit_group_page/search_user.dart | 4 +- .../pages/groups/groups_page/groups_page.dart | 81 +++++++++++-------- lib/l10n/app_fr.arb | 20 +++++ lib/l10n/app_localizations.dart | 72 +++++++++++++++++ lib/l10n/app_localizations_en.dart | 40 +++++++++ lib/l10n/app_localizations_fr.dart | 40 +++++++++ 8 files changed, 241 insertions(+), 43 deletions(-) diff --git a/lib/admin/ui/pages/group_notifification_page/group_notification_page.dart b/lib/admin/ui/pages/group_notifification_page/group_notification_page.dart index 521323ad4e..5855a23cb1 100644 --- a/lib/admin/ui/pages/group_notifification_page/group_notification_page.dart +++ b/lib/admin/ui/pages/group_notifification_page/group_notification_page.dart @@ -4,6 +4,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/admin.dart'; import 'package:titan/admin/providers/group_list_provider.dart'; import 'package:titan/admin/repositories/notification_repository.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; @@ -27,6 +28,7 @@ class GroupNotificationPage extends HookConsumerWidget { displayToast(context, type, msg); } + final localizeWithContext = AppLocalizations.of(context)!; return AdminTemplate( child: Refresher( onRefresh: () async { @@ -40,7 +42,7 @@ class GroupNotificationPage extends HookConsumerWidget { Align( alignment: Alignment.centerLeft, child: Text( - "Notifications de groupe", + localizeWithContext.adminGroupNotification, style: TextStyle( fontSize: 24, fontWeight: FontWeight.bold, @@ -69,22 +71,25 @@ class GroupNotificationPage extends HookConsumerWidget { context: context, ref: ref, modal: BottomModalTemplate( - title: "Notifier le groupe ${group.name}", + title: localizeWithContext.adminNotifyGroup( + group.name, + ), child: Column( children: [ TextEntry( - label: 'Titre', + label: localizeWithContext.adminTitle, controller: titleController, ), const SizedBox(height: 20), TextEntry( - label: 'Contenu', + label: + localizeWithContext.adminContent, controller: contentController, maxLines: 5, ), const SizedBox(height: 20), Button( - text: "Envoyer", + text: localizeWithContext.adminSend, onPressed: () { notificationRepository .sendNotification( @@ -96,12 +101,14 @@ class GroupNotificationPage extends HookConsumerWidget { if (value) { displayToastWithContext( TypeMsg.msg, - "Notification envoyée avec succès", + localizeWithContext + .adminNotificationSended, ); } else { displayToastWithContext( TypeMsg.error, - "Échec de l'envoi de la notification", + localizeWithContext + .adminFailedToSendNotification, ); } }) @@ -126,7 +133,7 @@ class GroupNotificationPage extends HookConsumerWidget { ], ); }, - loaderColor: ColorConstants.gradient1, + loaderColor: ColorConstants.main, ), ], ), diff --git a/lib/admin/ui/pages/groups/edit_group_page/results.dart b/lib/admin/ui/pages/groups/edit_group_page/results.dart index 32b0b8b7b8..4a6351928d 100644 --- a/lib/admin/ui/pages/groups/edit_group_page/results.dart +++ b/lib/admin/ui/pages/groups/edit_group_page/results.dart @@ -83,7 +83,7 @@ class MemberResults extends HookConsumerWidget { }); } }, - waitingColor: ColorConstants.gradient1, + waitingColor: ColorConstants.main, builder: (child) => child, child: const HeroIcon(HeroIcons.plus), ), @@ -95,7 +95,7 @@ class MemberResults extends HookConsumerWidget { ) .toList(), ), - loaderColor: ColorConstants.gradient1, + loaderColor: ColorConstants.main, ); } } diff --git a/lib/admin/ui/pages/groups/edit_group_page/search_user.dart b/lib/admin/ui/pages/groups/edit_group_page/search_user.dart index 73dfdaa008..7ac8bfb2ea 100644 --- a/lib/admin/ui/pages/groups/edit_group_page/search_user.dart +++ b/lib/admin/ui/pages/groups/edit_group_page/search_user.dart @@ -32,6 +32,8 @@ class SearchUser extends HookConsumerWidget { displayToast(context, type, msg); } + final localizeWithContext = AppLocalizations.of(context)!; + return AsyncChild( value: group, builder: (context, g) { @@ -61,7 +63,7 @@ class SearchUser extends HookConsumerWidget { context: context, ref: ref, modal: BottomModalTemplate( - title: "Ajouter un membre", + title: localizeWithContext.adminAddMember, child: ConstrainedBox( constraints: const BoxConstraints(maxHeight: 300), child: Column( diff --git a/lib/admin/ui/pages/groups/groups_page/groups_page.dart b/lib/admin/ui/pages/groups/groups_page/groups_page.dart index 24989b4a29..cba0e36ac4 100644 --- a/lib/admin/ui/pages/groups/groups_page/groups_page.dart +++ b/lib/admin/ui/pages/groups/groups_page/groups_page.dart @@ -40,6 +40,9 @@ class GroupsPage extends HookConsumerWidget { displayToast(context, type, msg); } + final localizeWithContext = AppLocalizations.of(context)!; + final navigatorWithContext = Navigator.of(context); + return AdminTemplate( child: ScrollToHideNavbar( controller: scrollController, @@ -56,7 +59,7 @@ class GroupsPage extends HookConsumerWidget { Row( children: [ Text( - "Gestion des groupes", + localizeWithContext.adminGroupManagement, style: TextStyle( fontSize: 24, fontWeight: FontWeight.bold, @@ -77,21 +80,21 @@ class GroupsPage extends HookConsumerWidget { context: context, ref: ref, modal: BottomModalTemplate( - title: "Ajouter un groupe", + title: localizeWithContext.adminAddGroup, child: Column( children: [ TextEntry( - label: 'Nom', + label: localizeWithContext.adminName, controller: nameController, ), const SizedBox(height: 20), TextEntry( - label: 'Description', + label: localizeWithContext.adminDescription, controller: descController, ), const SizedBox(height: 20), Button( - text: "Ajouter", + text: localizeWithContext.adminAdd, onPressed: () async { final addedGroupMsg = AppLocalizations.of( context, @@ -154,7 +157,7 @@ class GroupsPage extends HookConsumerWidget { child: Column( children: [ Button( - text: "Modifier", + text: localizeWithContext.adminEdit, onPressed: () async { nameController.text = group.name; descController.text = @@ -164,21 +167,25 @@ class GroupsPage extends HookConsumerWidget { context: context, ref: ref, modal: BottomModalTemplate( - title: "Modifier le groupe", + title: localizeWithContext + .adminEditGroup, child: Column( children: [ TextEntry( - label: "Nom", + label: localizeWithContext + .adminName, controller: nameController, ), const SizedBox(height: 20), TextEntry( - label: "Description", + label: localizeWithContext + .adminDescription, controller: descController, ), const SizedBox(height: 20), Button( - text: "Éditer", + text: localizeWithContext + .adminEdit, onPressed: () async { final addedGroupMsg = AppLocalizations.of( @@ -227,7 +234,8 @@ class GroupsPage extends HookConsumerWidget { ), const SizedBox(height: 20), Button( - text: "Gérer les membres", + text: localizeWithContext + .adminManageMembers, onPressed: () { Navigator.pop(context); groupIdNotifier.setId(group.id); @@ -240,38 +248,47 @@ class GroupsPage extends HookConsumerWidget { ), const SizedBox(height: 20), Button( - text: "Supprimer le groupe", + text: localizeWithContext + .adminDeleteGroup, type: ButtonType.danger, onPressed: () async { await showDialog( context: context, builder: (context) { return CustomDialogBox( - title: "Delete", - descriptions: - "Êtes-vous sûr de vouloir supprimer ce groupe ?", + title: localizeWithContext + .adminDelete, + descriptions: localizeWithContext + .adminDeleteGroupConfirmation, onYes: () async { - tokenExpireWrapper(ref, () async { - final value = - await groupsNotifier - .deleteGroup(group); - if (value) { - displayToastWithContext( - TypeMsg.msg, - "Groupe supprimé avec succès", - ); - } else { - displayToastWithContext( - TypeMsg.error, - "Échec de la suppression du groupe", - ); - } - }); + tokenExpireWrapper( + ref, + () async { + final value = + await groupsNotifier + .deleteGroup( + group, + ); + if (value) { + displayToastWithContext( + TypeMsg.msg, + localizeWithContext + .adminDeletedGroup, + ); + } else { + displayToastWithContext( + TypeMsg.error, + localizeWithContext + .adminFailedToDeleteGroup, + ); + } + }, + ); }, ); }, ); - Navigator.pop(context); + navigatorWithContext.pop(); }, ), ], diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index e42faa4d8d..bd3ccb25f4 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -79,6 +79,26 @@ "adminUser": "Utilisateur", "adminValidateFilters": "Valider les filtres", "adminVisibilities": "Visibilités", + "adminGroupNotification": "Notification de groupe", + "adminNotifyGroup": "Notifier le groupe {groupName}", + "@adminNotifyGroup":{ + "description": "Notifie les membres du groupe sélectionné", + "placeholders": { + "groupName": { + "type": "String" + } + } + }, + "adminTitle": "Titre", + "adminContent": "Contenu", + "adminSend": "Envoyer", + "adminNotificationSended": "Notification envoyée", + "adminFailedToSendNotification": "Échec de l'envoi de la notification", + "adminGroupManagement": "Gestion des groupes", + "adminEditGroup": "Modifier le groupe", + "adminManageMembers": "Gérer les membres", + "adminDeleteGroupConfirmation": "Êtes-vous sûr de vouloir supprimer ce groupe ?", + "adminFailedToDeleteGroup": "Échec de la suppression du groupe", "advertAdd": "Ajouter", "advertAddedAdvert": "Annonce publiée", "advertAddedAnnouncer": "Annonceur ajouté", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 3df8b0b8c7..7f71cd14a0 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -572,6 +572,78 @@ abstract class AppLocalizations { /// **'Visibilités'** String get adminVisibilities; + /// No description provided for @adminGroupNotification. + /// + /// In fr, this message translates to: + /// **'Notification de groupe'** + String get adminGroupNotification; + + /// Notifie les membres du groupe sélectionné + /// + /// In fr, this message translates to: + /// **'Notifier le groupe {groupName}'** + String adminNotifyGroup(String groupName); + + /// No description provided for @adminTitle. + /// + /// In fr, this message translates to: + /// **'Titre'** + String get adminTitle; + + /// No description provided for @adminContent. + /// + /// In fr, this message translates to: + /// **'Contenu'** + String get adminContent; + + /// No description provided for @adminSend. + /// + /// In fr, this message translates to: + /// **'Envoyer'** + String get adminSend; + + /// No description provided for @adminNotificationSended. + /// + /// In fr, this message translates to: + /// **'Notification envoyée'** + String get adminNotificationSended; + + /// No description provided for @adminFailedToSendNotification. + /// + /// In fr, this message translates to: + /// **'Échec de l\'envoi de la notification'** + String get adminFailedToSendNotification; + + /// No description provided for @adminGroupManagement. + /// + /// In fr, this message translates to: + /// **'Gestion des groupes'** + String get adminGroupManagement; + + /// No description provided for @adminEditGroup. + /// + /// In fr, this message translates to: + /// **'Modifier le groupe'** + String get adminEditGroup; + + /// No description provided for @adminManageMembers. + /// + /// In fr, this message translates to: + /// **'Gérer les membres'** + String get adminManageMembers; + + /// No description provided for @adminDeleteGroupConfirmation. + /// + /// In fr, this message translates to: + /// **'Êtes-vous sûr de vouloir supprimer ce groupe ?'** + String get adminDeleteGroupConfirmation; + + /// No description provided for @adminFailedToDeleteGroup. + /// + /// In fr, this message translates to: + /// **'Échec de la suppression du groupe'** + String get adminFailedToDeleteGroup; + /// No description provided for @advertAdd. /// /// In fr, this message translates to: diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 16efee4228..adcc1e5571 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -247,6 +247,46 @@ class AppLocalizationsEn extends AppLocalizations { @override String get adminVisibilities => 'Visibilities'; + @override + String get adminGroupNotification => 'Notification de groupe'; + + @override + String adminNotifyGroup(String groupName) { + return 'Notifier le groupe $groupName'; + } + + @override + String get adminTitle => 'Titre'; + + @override + String get adminContent => 'Contenu'; + + @override + String get adminSend => 'Envoyer'; + + @override + String get adminNotificationSended => 'Notification envoyée'; + + @override + String get adminFailedToSendNotification => + 'Échec de l\'envoi de la notification'; + + @override + String get adminGroupManagement => 'Gestion des groupes'; + + @override + String get adminEditGroup => 'Modifier le groupe'; + + @override + String get adminManageMembers => 'Gérer les membres'; + + @override + String get adminDeleteGroupConfirmation => + 'Êtes-vous sûr de vouloir supprimer ce groupe ?'; + + @override + String get adminFailedToDeleteGroup => 'Échec de la suppression du groupe'; + @override String get advertAdd => 'Add'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 17129e7eac..ba36fb62ec 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -248,6 +248,46 @@ class AppLocalizationsFr extends AppLocalizations { @override String get adminVisibilities => 'Visibilités'; + @override + String get adminGroupNotification => 'Notification de groupe'; + + @override + String adminNotifyGroup(String groupName) { + return 'Notifier le groupe $groupName'; + } + + @override + String get adminTitle => 'Titre'; + + @override + String get adminContent => 'Contenu'; + + @override + String get adminSend => 'Envoyer'; + + @override + String get adminNotificationSended => 'Notification envoyée'; + + @override + String get adminFailedToSendNotification => + 'Échec de l\'envoi de la notification'; + + @override + String get adminGroupManagement => 'Gestion des groupes'; + + @override + String get adminEditGroup => 'Modifier le groupe'; + + @override + String get adminManageMembers => 'Gérer les membres'; + + @override + String get adminDeleteGroupConfirmation => + 'Êtes-vous sûr de vouloir supprimer ce groupe ?'; + + @override + String get adminFailedToDeleteGroup => 'Échec de la suppression du groupe'; + @override String get advertAdd => 'Ajouter'; From 3b614b5cf85c20551f6ff22ce2df5957b11d703b Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 9 Aug 2025 19:52:31 +0200 Subject: [PATCH 170/473] color --- .../add_edit_user_membership_page.dart | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/admin/ui/pages/membership/add_edit_user_membership_page/add_edit_user_membership_page.dart b/lib/admin/ui/pages/membership/add_edit_user_membership_page/add_edit_user_membership_page.dart index 58e283e1b0..2637b5f31c 100644 --- a/lib/admin/ui/pages/membership/add_edit_user_membership_page/add_edit_user_membership_page.dart +++ b/lib/admin/ui/pages/membership/add_edit_user_membership_page/add_edit_user_membership_page.dart @@ -103,10 +103,7 @@ class AddEditUserMembershipPage extends HookConsumerWidget { const SizedBox(height: 50), WaitingButton( builder: (child) => AddEditButtonLayout( - colors: const [ - ColorConstants.gradient1, - ColorConstants.gradient2, - ], + colors: const [ColorConstants.main, ColorConstants.onMain], child: child, ), child: Text( From 976fd448d183df1bb326a84b19ee5f7b99d1ff14 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 9 Aug 2025 19:54:12 +0200 Subject: [PATCH 171/473] color --- .../association_membership_detail_page.dart | 8 ++++---- .../association_membership_information_editor.dart | 11 +++-------- .../search_filters.dart | 10 ++-------- 3 files changed, 9 insertions(+), 20 deletions(-) diff --git a/lib/admin/ui/pages/membership/association_membership_detail_page/association_membership_detail_page.dart b/lib/admin/ui/pages/membership/association_membership_detail_page/association_membership_detail_page.dart index b96ebbffc5..24a05d8857 100644 --- a/lib/admin/ui/pages/membership/association_membership_detail_page/association_membership_detail_page.dart +++ b/lib/admin/ui/pages/membership/association_membership_detail_page/association_membership_detail_page.dart @@ -53,7 +53,7 @@ class AssociationMembershipEditorPage extends HookConsumerWidget { style: TextStyle( fontSize: 20, fontWeight: FontWeight.w700, - color: ColorConstants.gradient1, + color: ColorConstants.main, ), ), ), @@ -66,7 +66,7 @@ class AssociationMembershipEditorPage extends HookConsumerWidget { style: const TextStyle( fontSize: 20, fontWeight: FontWeight.w700, - color: ColorConstants.gradient1, + color: ColorConstants.main, ), ), const SizedBox(width: 10), @@ -75,7 +75,7 @@ class AssociationMembershipEditorPage extends HookConsumerWidget { style: const TextStyle( fontSize: 20, fontWeight: FontWeight.w700, - color: ColorConstants.gradient1, + color: ColorConstants.main, ), ), const Spacer(), @@ -85,7 +85,7 @@ class AssociationMembershipEditorPage extends HookConsumerWidget { height: 40, decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), - color: ColorConstants.gradient1, + color: ColorConstants.main, ), child: child, ), diff --git a/lib/admin/ui/pages/membership/association_membership_detail_page/association_membership_information_editor.dart b/lib/admin/ui/pages/membership/association_membership_detail_page/association_membership_information_editor.dart index 03ae463783..fe4af2d263 100644 --- a/lib/admin/ui/pages/membership/association_membership_detail_page/association_membership_information_editor.dart +++ b/lib/admin/ui/pages/membership/association_membership_detail_page/association_membership_information_editor.dart @@ -53,7 +53,7 @@ class AssociationMembershipInformationEditor extends HookConsumerWidget { SizedBox( child: TextFormField( controller: name, - cursorColor: ColorConstants.gradient1, + cursorColor: ColorConstants.main, decoration: InputDecoration( labelText: AppLocalizations.of(context)!.adminName, labelStyle: const TextStyle( @@ -68,9 +68,7 @@ class AssociationMembershipInformationEditor extends HookConsumerWidget { borderSide: BorderSide(color: Colors.transparent), ), focusedBorder: const UnderlineInputBorder( - borderSide: BorderSide( - color: ColorConstants.gradient1, - ), + borderSide: BorderSide(color: ColorConstants.main), ), ), validator: (value) { @@ -113,10 +111,7 @@ class AssociationMembershipInformationEditor extends HookConsumerWidget { const SizedBox(height: 20), WaitingButton( builder: (child) => AddEditButtonLayout( - colors: const [ - ColorConstants.gradient1, - ColorConstants.gradient2, - ], + colors: const [ColorConstants.main, ColorConstants.onMain], child: child, ), onTap: () async { diff --git a/lib/admin/ui/pages/membership/association_membership_detail_page/search_filters.dart b/lib/admin/ui/pages/membership/association_membership_detail_page/search_filters.dart index 3fbea81199..0fa5ee2bbe 100644 --- a/lib/admin/ui/pages/membership/association_membership_detail_page/search_filters.dart +++ b/lib/admin/ui/pages/membership/association_membership_detail_page/search_filters.dart @@ -132,10 +132,7 @@ class SearchFilters extends HookConsumerWidget { }); }, builder: (child) => AddEditButtonLayout( - colors: const [ - ColorConstants.gradient1, - ColorConstants.gradient2, - ], + colors: const [ColorConstants.main, ColorConstants.onMain], child: child, ), child: Text( @@ -166,10 +163,7 @@ class SearchFilters extends HookConsumerWidget { }); }, builder: (child) => AddEditButtonLayout( - colors: const [ - ColorConstants.gradient1, - ColorConstants.gradient2, - ], + colors: const [ColorConstants.main, ColorConstants.onMain], child: child, ), child: Text( From a75fea7d6f8a84f8f2a1102f92a8fcbb4ce92adc Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 9 Aug 2025 20:12:17 +0200 Subject: [PATCH 172/473] translations --- .../pages/groups/groups_page/groups_page.dart | 2 +- lib/admin/ui/pages/main_page/main_page.dart | 40 ++++--- .../add_membership_modal.dart | 16 ++- .../association_membership_page.dart | 19 +++- .../add_edit_structure_page.dart | 18 +-- .../user_search_modal.dart | 5 +- .../pages/structure_page/structure_page.dart | 21 ++-- .../users_management_page/add_user_modal.dart | 19 +++- .../users_management_page.dart | 16 ++- lib/l10n/app_fr.arb | 19 +++- lib/l10n/app_localizations.dart | 106 +++++++++++++++++- lib/l10n/app_localizations_en.dart | 58 +++++++++- lib/l10n/app_localizations_fr.dart | 58 +++++++++- 13 files changed, 337 insertions(+), 60 deletions(-) diff --git a/lib/admin/ui/pages/groups/groups_page/groups_page.dart b/lib/admin/ui/pages/groups/groups_page/groups_page.dart index cba0e36ac4..4a9f90eb4c 100644 --- a/lib/admin/ui/pages/groups/groups_page/groups_page.dart +++ b/lib/admin/ui/pages/groups/groups_page/groups_page.dart @@ -59,7 +59,7 @@ class GroupsPage extends HookConsumerWidget { Row( children: [ Text( - localizeWithContext.adminGroupManagement, + localizeWithContext.adminGroupsManagement, style: TextStyle( fontSize: 24, fontWeight: FontWeight.bold, diff --git a/lib/admin/ui/pages/main_page/main_page.dart b/lib/admin/ui/pages/main_page/main_page.dart index 195218e3f6..d0c86d104d 100644 --- a/lib/admin/ui/pages/main_page/main_page.dart +++ b/lib/admin/ui/pages/main_page/main_page.dart @@ -4,6 +4,7 @@ import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/admin/admin.dart'; import 'package:titan/admin/router.dart'; import 'package:titan/admin/ui/pages/users_management_page/users_management_page.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; import 'package:titan/tools/ui/styleguide/list_item.dart'; @@ -16,56 +17,65 @@ class AdminMainPage extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { ref.watch(userList); + final localizeWithContext = AppLocalizations.of(context)!; + return AdminTemplate( child: Padding( padding: const EdgeInsets.all(40), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text("Gestion", style: Theme.of(context).textTheme.headlineMedium), + Text( + localizeWithContext.adminAdministration, + style: Theme.of(context).textTheme.headlineMedium, + ), const SizedBox(height: 20), Text( - "Utilisateurs & Groupes", + localizeWithContext.adminUsersAndGroups, style: Theme.of(context).textTheme.titleLarge, ), ListItem( - title: "Gestion des utilisateurs", - subtitle: "Gérer les utilisateurs de l'application", + title: localizeWithContext.adminUsersManagement, + subtitle: localizeWithContext.adminUsersManagementDescription, onTap: () async { await showCustomBottomModal( context: context, ref: ref, modal: BottomModalTemplate( - title: "Gestion des utilisateurs", + title: localizeWithContext.adminUsersManagement, child: UsersManagementPage(), ), ); }, ), ListItem( - title: "Gestion des groupes", - subtitle: "Gérer les groupes d'utilisateurs", + title: localizeWithContext.adminGroupsManagement, + subtitle: localizeWithContext.adminManageUserGroups, onTap: () => QR.to(AdminRouter.root + AdminRouter.usersGroups), ), ListItem( - title: "Notifications de groupe", - subtitle: "Gérer les notifications pour les groupes", + title: localizeWithContext.adminGroupNotification, + subtitle: localizeWithContext.adminSendNotificationToGroup, onTap: () => QR.to(AdminRouter.root + AdminRouter.groupNotification), ), Text( - "Module de paiement", + localizeWithContext.adminPaiementModule, style: Theme.of(context).textTheme.titleLarge, ), ListItem( - title: "Paiement", - subtitle: "Gérer les structures du module de paiement", + title: localizeWithContext.adminPaiement, + subtitle: localizeWithContext.adminManagePaiementStructures, onTap: () => QR.to(AdminRouter.root + AdminRouter.structures), ), - Text("Adhésion", style: Theme.of(context).textTheme.titleLarge), + Text( + localizeWithContext.adminAssociationMembership, + style: Theme.of(context).textTheme.titleLarge, + ), ListItem( - title: "Adhésion", - subtitle: "Gérer les adhésions des utilisateurs", + title: localizeWithContext.adminAssociationMembership, + subtitle: + localizeWithContext.adminManageUsersAssociationMemberships, onTap: () => QR.to(AdminRouter.root + AdminRouter.associationMemberships), ), diff --git a/lib/admin/ui/pages/membership/association_membership_page/add_membership_modal.dart b/lib/admin/ui/pages/membership/association_membership_page/add_membership_modal.dart index ad5c27a839..f72e3eb644 100644 --- a/lib/admin/ui/pages/membership/association_membership_page/add_membership_modal.dart +++ b/lib/admin/ui/pages/membership/association_membership_page/add_membership_modal.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/admin/class/simple_group.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; import 'package:titan/tools/ui/styleguide/button.dart'; import 'package:titan/tools/ui/styleguide/list_item.dart'; @@ -24,20 +25,25 @@ class AddMembershipModal extends HookWidget { final nameController = useTextEditingController(); final chosenGroup = useState(null); + final localizeWithContext = AppLocalizations.of(context)!; + return BottomModalTemplate( - title: "Gestion des adhésions", + title: localizeWithContext.adminAssociationMembershipsManagement, child: SingleChildScrollView( child: Column( children: [ Padding( padding: const EdgeInsets.all(8.0), - child: TextEntry(label: "Nom", controller: nameController), + child: TextEntry( + label: localizeWithContext.adminName, + controller: nameController, + ), ), Padding( padding: const EdgeInsets.all(8.0), child: chosenGroup.value == null ? ListItem( - title: "Choisir une association", + title: localizeWithContext.adminChooseAssication, onTap: () async { FocusScope.of(context).unfocus(); final ctx = context; @@ -48,7 +54,7 @@ class AddMembershipModal extends HookWidget { context: ctx, ref: ref, modal: BottomModalTemplate( - title: "Choisir une association", + title: localizeWithContext.adminChooseAssication, child: Column( children: [ ...groups.map( @@ -70,7 +76,7 @@ class AddMembershipModal extends HookWidget { ), const SizedBox(height: 10), Button( - text: 'Ajouter', + text: localizeWithContext.adminAdd, onPressed: () { if (chosenGroup.value != null) { onSubmit(chosenGroup.value!, nameController.text); diff --git a/lib/admin/ui/pages/membership/association_membership_page/association_membership_page.dart b/lib/admin/ui/pages/membership/association_membership_page/association_membership_page.dart index 25095781ef..fd2c5f976e 100644 --- a/lib/admin/ui/pages/membership/association_membership_page/association_membership_page.dart +++ b/lib/admin/ui/pages/membership/association_membership_page/association_membership_page.dart @@ -49,6 +49,8 @@ class AssociationMembershipsPage extends HookConsumerWidget { Navigator.of(context).pop(); } + final localizeWithContext = AppLocalizations.of(context)!; + return AdminTemplate( child: Refresher( onRefresh: () async { @@ -89,9 +91,16 @@ class AssociationMembershipsPage extends HookConsumerWidget { ), ); if (value) { - displayToastWithContext(TypeMsg.msg, "Succès"); + displayToastWithContext( + TypeMsg.msg, + localizeWithContext + .adminCreatedAssociationMembership, + ); } else { - displayToastWithContext(TypeMsg.error, "Échec"); + displayToastWithContext( + TypeMsg.error, + localizeWithContext.adminCreationError, + ); } popWithContext(); }); @@ -126,7 +135,7 @@ class AssociationMembershipsPage extends HookConsumerWidget { child: Column( children: [ Button( - text: "Modifier", + text: localizeWithContext.adminEdit, onPressed: () { associationMembershipMembersNotifier .loadAssociationMembershipMembers( @@ -147,7 +156,7 @@ class AssociationMembershipsPage extends HookConsumerWidget { ), const SizedBox(height: 20), Button( - text: "Supprimer", + text: localizeWithContext.adminDelete, type: ButtonType.danger, onPressed: () async { await showDialog( @@ -206,7 +215,7 @@ class AssociationMembershipsPage extends HookConsumerWidget { ], ); }, - loaderColor: ColorConstants.gradient1, + loaderColor: ColorConstants.main, ), ], ), diff --git a/lib/admin/ui/pages/structure_page/add_edit_structure_page/add_edit_structure_page.dart b/lib/admin/ui/pages/structure_page/add_edit_structure_page/add_edit_structure_page.dart index 19c1e1deb3..091944b979 100644 --- a/lib/admin/ui/pages/structure_page/add_edit_structure_page/add_edit_structure_page.dart +++ b/lib/admin/ui/pages/structure_page/add_edit_structure_page/add_edit_structure_page.dart @@ -49,6 +49,8 @@ class AddEditStructurePage extends HookConsumerWidget { displayToast(context, type, msg); } + final localizeWithContext = AppLocalizations.of(context)!; + return AdminTemplate( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 30.0), @@ -65,7 +67,7 @@ class AddEditStructurePage extends HookConsumerWidget { padding: const EdgeInsets.all(8.0), child: TextEntry( controller: name, - label: AppLocalizations.of(context)!.adminName, + label: localizeWithContext.adminName, ), ), const SizedBox(height: 20), @@ -104,7 +106,7 @@ class AddEditStructurePage extends HookConsumerWidget { ? Column( children: [ Text( - AppLocalizations.of(context)!.adminManager, + localizeWithContext.adminManager, style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, @@ -122,7 +124,7 @@ class AddEditStructurePage extends HookConsumerWidget { ], ) : ListItem( - title: "Selectioner un gestionnaire", + title: localizeWithContext.adminSelectManager, onTap: () async { await showCustomBottomModal( context: context, @@ -140,15 +142,15 @@ class AddEditStructurePage extends HookConsumerWidget { if (structureManager.id.isEmpty && !isEdit) { displayToastWithContext( TypeMsg.error, - AppLocalizations.of(context)!.adminNoManager, + localizeWithContext.adminNoManager, ); return; } if (key.currentState!.validate()) { await tokenExpireWrapper(ref, () async { final editedStructureMsg = isEdit - ? AppLocalizations.of(context)!.adminEditedStructure - : AppLocalizations.of(context)!.adminAddedStructure; + ? localizeWithContext.adminEditedStructure + : localizeWithContext.adminAddedStructure; final addedStructureErrorMsg = AppLocalizations.of( context, )!.adminAddingError; @@ -190,8 +192,8 @@ class AddEditStructurePage extends HookConsumerWidget { builder: (child) => AdminButton(child: child), child: Text( isEdit - ? AppLocalizations.of(context)!.adminEdit - : AppLocalizations.of(context)!.adminAdd, + ? localizeWithContext.adminEdit + : localizeWithContext.adminAdd, style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, diff --git a/lib/admin/ui/pages/structure_page/add_edit_structure_page/user_search_modal.dart b/lib/admin/ui/pages/structure_page/add_edit_structure_page/user_search_modal.dart index 7603990f72..9b10f49bc5 100644 --- a/lib/admin/ui/pages/structure_page/add_edit_structure_page/user_search_modal.dart +++ b/lib/admin/ui/pages/structure_page/add_edit_structure_page/user_search_modal.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/ui/pages/structure_page/add_edit_structure_page/search_result.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; import 'package:titan/tools/ui/styleguide/searchbar.dart'; @@ -15,8 +16,10 @@ class UserSearchModal extends HookConsumerWidget { final usersNotifier = ref.watch(userList.notifier); final textController = useTextEditingController(); + final localizeWithContext = AppLocalizations.of(context)!; + return BottomModalTemplate( - title: "Choisir un gestionnaire", + title: localizeWithContext.adminSelectManager, type: BottomModalType.main, child: Column( children: [ diff --git a/lib/admin/ui/pages/structure_page/structure_page.dart b/lib/admin/ui/pages/structure_page/structure_page.dart index f5357264fd..9ebca03975 100644 --- a/lib/admin/ui/pages/structure_page/structure_page.dart +++ b/lib/admin/ui/pages/structure_page/structure_page.dart @@ -38,6 +38,8 @@ class StructurePage extends HookConsumerWidget { displayToast(context, type, msg); } + final localizeWithContext = AppLocalizations.of(context)!; + return AdminTemplate( child: Refresher( onRefresh: () async { @@ -52,7 +54,7 @@ class StructurePage extends HookConsumerWidget { Row( children: [ Text( - AppLocalizations.of(context)!.adminStructures, + localizeWithContext.adminStructures, style: Theme.of(context).textTheme.headlineMedium, ), const Spacer(), @@ -94,11 +96,12 @@ class StructurePage extends HookConsumerWidget { context: context, ref: ref, modal: BottomModalTemplate( - title: "Gestion des utilisateurs", + title: localizeWithContext + .adminUsersManagement, child: Column( children: [ Button( - text: "Modifier", + text: localizeWithContext.adminEdit, onPressed: () { structureNotifier.setStructure( structure, @@ -118,7 +121,7 @@ class StructurePage extends HookConsumerWidget { const SizedBox(height: 10), Button( type: ButtonType.danger, - text: 'Supprimer', + text: localizeWithContext.adminDelete, onPressed: () async { await showDialog( context: context, @@ -133,13 +136,11 @@ class StructurePage extends HookConsumerWidget { )!.adminDeleteGroup, onYes: () async { final deletedGroupMsg = - AppLocalizations.of( - context, - )!.adminDeletedGroup; + localizeWithContext + .adminDeletedGroup; final deletingErrorMsg = - AppLocalizations.of( - context, - )!.adminDeletingError; + localizeWithContext + .adminDeletingError; tokenExpireWrapper(ref, () async { final value = await structuresNotifier diff --git a/lib/admin/ui/pages/users_management_page/add_user_modal.dart b/lib/admin/ui/pages/users_management_page/add_user_modal.dart index 8f9da9e807..996999a2ed 100644 --- a/lib/admin/ui/pages/users_management_page/add_user_modal.dart +++ b/lib/admin/ui/pages/users_management_page/add_user_modal.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/providers/user_creation_provider.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; @@ -22,12 +23,14 @@ class AddUsersModalContent extends HookConsumerWidget { displayToast(context, type, msg); } + final localizeWithContext = AppLocalizations.of(context)!; + return BottomModalTemplate( - title: "Ajouter des utilisateurs", + title: localizeWithContext.adminInviteUsers, child: Column( children: [ Button( - text: selectedFileName.value ?? "Importer une liste", + text: selectedFileName.value ?? localizeWithContext.adminImportList, onPressed: () async { final result = await FilePicker.platform.pickFiles( type: FileType.custom, @@ -63,7 +66,7 @@ class AddUsersModalContent extends HookConsumerWidget { ), const SizedBox(height: 20), Button( - text: "Ajouter", + text: localizeWithContext.adminAdd, onPressed: () { tokenExpireWrapper(ref, () async { final userCreationNotifier = ref.watch( @@ -73,9 +76,15 @@ class AddUsersModalContent extends HookConsumerWidget { mailList.value, ); if (value) { - displayToastWithContext(TypeMsg.msg, "Succès"); + displayToastWithContext( + TypeMsg.msg, + localizeWithContext.adminInvitedUsers, + ); } else { - displayToastWithContext(TypeMsg.error, "Échec"); + displayToastWithContext( + TypeMsg.error, + localizeWithContext.adminFailedToInviteUsers, + ); } // popWithContext(); }); diff --git a/lib/admin/ui/pages/users_management_page/users_management_page.dart b/lib/admin/ui/pages/users_management_page/users_management_page.dart index a58ef0385d..f2a742f493 100644 --- a/lib/admin/ui/pages/users_management_page/users_management_page.dart +++ b/lib/admin/ui/pages/users_management_page/users_management_page.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/ui/pages/users_management_page/add_user_modal.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; import 'package:titan/tools/ui/styleguide/button.dart'; @@ -9,11 +10,12 @@ class UsersManagementPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final localizeWithContext = AppLocalizations.of(context)!; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Button( - text: 'Ajouter', + text: localizeWithContext.adminEdit, onPressed: () async { Navigator.pop(context); await showCustomBottomModal( @@ -25,7 +27,7 @@ class UsersManagementPage extends HookConsumerWidget { ), const SizedBox(height: 20), Button( - text: 'Supprimer', + text: localizeWithContext.adminDelete, onPressed: () async { Navigator.pop(context); await showCustomBottomModal( @@ -33,16 +35,20 @@ class UsersManagementPage extends HookConsumerWidget { ref: ref, modal: BottomModalTemplate( type: BottomModalType.danger, - title: "Supprimer des utilisateurs", + title: localizeWithContext.adminDeleteUsers, child: Column( children: [ Button( - text: "Importer une liste", + text: localizeWithContext.adminImportList, onPressed: () {}, type: ButtonType.onDanger, ), const SizedBox(height: 20), - Button(text: "Supprimer", onPressed: () {}, disabled: true), + Button( + text: localizeWithContext.adminDelete, + onPressed: () {}, + disabled: true, + ), ], ), ), diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index bd3ccb25f4..8dcd090406 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -94,11 +94,28 @@ "adminSend": "Envoyer", "adminNotificationSended": "Notification envoyée", "adminFailedToSendNotification": "Échec de l'envoi de la notification", - "adminGroupManagement": "Gestion des groupes", + "adminGroupsManagement": "Gestion des groupes", "adminEditGroup": "Modifier le groupe", "adminManageMembers": "Gérer les membres", "adminDeleteGroupConfirmation": "Êtes-vous sûr de vouloir supprimer ce groupe ?", "adminFailedToDeleteGroup": "Échec de la suppression du groupe", + "adminUsersAndGroups": "Utilisateurs et groupes", + "adminUsersManagement": "Gestion des utilisateurs", + "adminUsersManagementDescription": "Gérez les utilisateurs de l'application", + "adminManageUserGroups": "Gérer les groupes d'utilisateurs", + "adminSendNotificationToGroup": "Envoyer une notification à un groupe", + "adminPaiementModule": "Module de paiement", + "adminPaiement": "Paiement", + "adminManagePaiementStructures" : "Gérer les structures du module de paiement", + "adminManageUsersAssociationMemberships": "Gérer les adhésions des utilisateurs", + "adminAssociationMembershipsManagement": "Gestion des adhésions", + "adminChooseAssication" : "Choisir une association", + "adminSelectManager" : "Sélectionner un gestionnaire", + "adminInviteUsers" : "Inviter des utilisateurs", + "adminImportList": "Importer une liste", + "adminInvitedUsers": "Utilisateurs invités", + "adminFailedToInviteUsers": "Échec de l'invitation des utilisateurs", + "adminDeleteUsers": "Supprimer des utilisateurs", "advertAdd": "Ajouter", "advertAddedAdvert": "Annonce publiée", "advertAddedAnnouncer": "Annonceur ajouté", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 7f71cd14a0..f261bb41c5 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -614,11 +614,11 @@ abstract class AppLocalizations { /// **'Échec de l\'envoi de la notification'** String get adminFailedToSendNotification; - /// No description provided for @adminGroupManagement. + /// No description provided for @adminGroupsManagement. /// /// In fr, this message translates to: /// **'Gestion des groupes'** - String get adminGroupManagement; + String get adminGroupsManagement; /// No description provided for @adminEditGroup. /// @@ -644,6 +644,108 @@ abstract class AppLocalizations { /// **'Échec de la suppression du groupe'** String get adminFailedToDeleteGroup; + /// No description provided for @adminUsersAndGroups. + /// + /// In fr, this message translates to: + /// **'Utilisateurs et groupes'** + String get adminUsersAndGroups; + + /// No description provided for @adminUsersManagement. + /// + /// In fr, this message translates to: + /// **'Gestion des utilisateurs'** + String get adminUsersManagement; + + /// No description provided for @adminUsersManagementDescription. + /// + /// In fr, this message translates to: + /// **'Gérez les utilisateurs de l\'application'** + String get adminUsersManagementDescription; + + /// No description provided for @adminManageUserGroups. + /// + /// In fr, this message translates to: + /// **'Gérer les groupes d\'utilisateurs'** + String get adminManageUserGroups; + + /// No description provided for @adminSendNotificationToGroup. + /// + /// In fr, this message translates to: + /// **'Envoyer une notification à un groupe'** + String get adminSendNotificationToGroup; + + /// No description provided for @adminPaiementModule. + /// + /// In fr, this message translates to: + /// **'Module de paiement'** + String get adminPaiementModule; + + /// No description provided for @adminPaiement. + /// + /// In fr, this message translates to: + /// **'Paiement'** + String get adminPaiement; + + /// No description provided for @adminManagePaiementStructures. + /// + /// In fr, this message translates to: + /// **'Gérer les structures du module de paiement'** + String get adminManagePaiementStructures; + + /// No description provided for @adminManageUsersAssociationMemberships. + /// + /// In fr, this message translates to: + /// **'Gérer les adhésions des utilisateurs'** + String get adminManageUsersAssociationMemberships; + + /// No description provided for @adminAssociationMembershipsManagement. + /// + /// In fr, this message translates to: + /// **'Gestion des adhésions'** + String get adminAssociationMembershipsManagement; + + /// No description provided for @adminChooseAssication. + /// + /// In fr, this message translates to: + /// **'Choisir une association'** + String get adminChooseAssication; + + /// No description provided for @adminSelectManager. + /// + /// In fr, this message translates to: + /// **'Sélectionner un gestionnaire'** + String get adminSelectManager; + + /// No description provided for @adminInviteUsers. + /// + /// In fr, this message translates to: + /// **'Inviter des utilisateurs'** + String get adminInviteUsers; + + /// No description provided for @adminImportList. + /// + /// In fr, this message translates to: + /// **'Importer une liste'** + String get adminImportList; + + /// No description provided for @adminInvitedUsers. + /// + /// In fr, this message translates to: + /// **'Utilisateurs invités'** + String get adminInvitedUsers; + + /// No description provided for @adminFailedToInviteUsers. + /// + /// In fr, this message translates to: + /// **'Échec de l\'invitation des utilisateurs'** + String get adminFailedToInviteUsers; + + /// No description provided for @adminDeleteUsers. + /// + /// In fr, this message translates to: + /// **'Supprimer des utilisateurs'** + String get adminDeleteUsers; + /// No description provided for @advertAdd. /// /// In fr, this message translates to: diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index adcc1e5571..28755cedb6 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -272,7 +272,7 @@ class AppLocalizationsEn extends AppLocalizations { 'Échec de l\'envoi de la notification'; @override - String get adminGroupManagement => 'Gestion des groupes'; + String get adminGroupsManagement => 'Gestion des groupes'; @override String get adminEditGroup => 'Modifier le groupe'; @@ -287,6 +287,62 @@ class AppLocalizationsEn extends AppLocalizations { @override String get adminFailedToDeleteGroup => 'Échec de la suppression du groupe'; + @override + String get adminUsersAndGroups => 'Utilisateurs et groupes'; + + @override + String get adminUsersManagement => 'Gestion des utilisateurs'; + + @override + String get adminUsersManagementDescription => + 'Gérez les utilisateurs de l\'application'; + + @override + String get adminManageUserGroups => 'Gérer les groupes d\'utilisateurs'; + + @override + String get adminSendNotificationToGroup => + 'Envoyer une notification à un groupe'; + + @override + String get adminPaiementModule => 'Module de paiement'; + + @override + String get adminPaiement => 'Paiement'; + + @override + String get adminManagePaiementStructures => + 'Gérer les structures du module de paiement'; + + @override + String get adminManageUsersAssociationMemberships => + 'Gérer les adhésions des utilisateurs'; + + @override + String get adminAssociationMembershipsManagement => 'Gestion des adhésions'; + + @override + String get adminChooseAssication => 'Choisir une association'; + + @override + String get adminSelectManager => 'Sélectionner un gestionnaire'; + + @override + String get adminInviteUsers => 'Inviter des utilisateurs'; + + @override + String get adminImportList => 'Importer une liste'; + + @override + String get adminInvitedUsers => 'Utilisateurs invités'; + + @override + String get adminFailedToInviteUsers => + 'Échec de l\'invitation des utilisateurs'; + + @override + String get adminDeleteUsers => 'Supprimer des utilisateurs'; + @override String get advertAdd => 'Add'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index ba36fb62ec..a1d61cbd88 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -273,7 +273,7 @@ class AppLocalizationsFr extends AppLocalizations { 'Échec de l\'envoi de la notification'; @override - String get adminGroupManagement => 'Gestion des groupes'; + String get adminGroupsManagement => 'Gestion des groupes'; @override String get adminEditGroup => 'Modifier le groupe'; @@ -288,6 +288,62 @@ class AppLocalizationsFr extends AppLocalizations { @override String get adminFailedToDeleteGroup => 'Échec de la suppression du groupe'; + @override + String get adminUsersAndGroups => 'Utilisateurs et groupes'; + + @override + String get adminUsersManagement => 'Gestion des utilisateurs'; + + @override + String get adminUsersManagementDescription => + 'Gérez les utilisateurs de l\'application'; + + @override + String get adminManageUserGroups => 'Gérer les groupes d\'utilisateurs'; + + @override + String get adminSendNotificationToGroup => + 'Envoyer une notification à un groupe'; + + @override + String get adminPaiementModule => 'Module de paiement'; + + @override + String get adminPaiement => 'Paiement'; + + @override + String get adminManagePaiementStructures => + 'Gérer les structures du module de paiement'; + + @override + String get adminManageUsersAssociationMemberships => + 'Gérer les adhésions des utilisateurs'; + + @override + String get adminAssociationMembershipsManagement => 'Gestion des adhésions'; + + @override + String get adminChooseAssication => 'Choisir une association'; + + @override + String get adminSelectManager => 'Sélectionner un gestionnaire'; + + @override + String get adminInviteUsers => 'Inviter des utilisateurs'; + + @override + String get adminImportList => 'Importer une liste'; + + @override + String get adminInvitedUsers => 'Utilisateurs invités'; + + @override + String get adminFailedToInviteUsers => + 'Échec de l\'invitation des utilisateurs'; + + @override + String get adminDeleteUsers => 'Supprimer des utilisateurs'; + @override String get advertAdd => 'Ajouter'; From 3839863d9c1fed8313ceb13febe403904c844cb5 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 9 Aug 2025 21:24:50 +0200 Subject: [PATCH 173/473] Delete useless files --- lib/admin/ui/components/admin_button.dart | 32 ---------- .../pages/groups/groups_page/groups_ui.dart | 62 ------------------- 2 files changed, 94 deletions(-) delete mode 100644 lib/admin/ui/components/admin_button.dart delete mode 100644 lib/admin/ui/pages/groups/groups_page/groups_ui.dart diff --git a/lib/admin/ui/components/admin_button.dart b/lib/admin/ui/components/admin_button.dart deleted file mode 100644 index 2d9671aca9..0000000000 --- a/lib/admin/ui/components/admin_button.dart +++ /dev/null @@ -1,32 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:titan/tools/constants.dart'; - -class SuperAdminButton extends StatelessWidget { - final Widget child; - const SuperAdminButton({super.key, required this.child}); - - @override - Widget build(BuildContext context) { - return Container( - width: double.infinity, - margin: const EdgeInsets.symmetric(vertical: 20), - padding: const EdgeInsets.symmetric(vertical: 15), - alignment: Alignment.center, - decoration: BoxDecoration( - gradient: const LinearGradient( - colors: [ColorConstants.main, ColorConstants.onMain], - ), - boxShadow: [ - BoxShadow( - color: ColorConstants.main.withValues(alpha: 0.5), - blurRadius: 5, - offset: const Offset(2, 2), - spreadRadius: 2, - ), - ], - borderRadius: BorderRadius.circular(15), - ), - child: child, - ); - } -} diff --git a/lib/admin/ui/pages/groups/groups_page/groups_ui.dart b/lib/admin/ui/pages/groups/groups_page/groups_ui.dart deleted file mode 100644 index ef7973025b..0000000000 --- a/lib/admin/ui/pages/groups/groups_page/groups_ui.dart +++ /dev/null @@ -1,62 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:heroicons/heroicons.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/class/simple_group.dart'; -import 'package:titan/admin/ui/components/item_card_ui.dart'; -import 'package:titan/admin/ui/pages/groups/groups_page/groups_button.dart'; -import 'package:titan/tools/constants.dart'; -import 'package:titan/tools/ui/builders/waiting_button.dart'; - -class GroupUi extends HookConsumerWidget { - final SimpleGroup group; - final void Function() onEdit; - final Future Function() onDelete; - const GroupUi({ - super.key, - required this.group, - required this.onEdit, - required this.onDelete, - }); - - @override - Widget build(BuildContext context, WidgetRef ref) { - return ItemCardUi( - children: [ - const SizedBox(width: 10), - Expanded( - child: Text( - group.name, - style: const TextStyle( - color: Colors.black, - fontSize: 20, - fontWeight: FontWeight.bold, - ), - ), - ), - const SizedBox(width: 10), - Row( - children: [ - GestureDetector( - onTap: onEdit, - child: GroupButton( - gradient1: Colors.grey.shade800, - gradient2: Colors.grey.shade900, - child: const HeroIcon(HeroIcons.eye, color: Colors.white), - ), - ), - const SizedBox(width: 10), - WaitingButton( - onTap: onDelete, - builder: (child) => GroupButton( - gradient1: ColorConstants.main, - gradient2: ColorConstants.onMain, - child: child, - ), - child: const HeroIcon(HeroIcons.xMark, color: Colors.white), - ), - ], - ), - ], - ); - } -} From ad11625d7b22da3d205455ffe7697b9c975cbe20 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 9 Aug 2025 21:40:45 +0200 Subject: [PATCH 174/473] delete useless file --- .../add_edit_user_membership_page.dart | 4 +- .../association_membership_button.dart | 37 -------- lib/super_admin/router.dart | 87 ------------------- 3 files changed, 2 insertions(+), 126 deletions(-) delete mode 100644 lib/admin/ui/pages/membership/association_membership_page/association_membership_button.dart diff --git a/lib/admin/ui/pages/membership/add_edit_user_membership_page/add_edit_user_membership_page.dart b/lib/admin/ui/pages/membership/add_edit_user_membership_page/add_edit_user_membership_page.dart index 2637b5f31c..788f23e198 100644 --- a/lib/admin/ui/pages/membership/add_edit_user_membership_page/add_edit_user_membership_page.dart +++ b/lib/admin/ui/pages/membership/add_edit_user_membership_page/add_edit_user_membership_page.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/admin/admin.dart'; import 'package:titan/admin/class/user_association_membership.dart'; import 'package:titan/admin/class/user_association_membership_base.dart'; import 'package:titan/admin/providers/association_membership_members_list_provider.dart'; import 'package:titan/admin/providers/user_association_membership_provider.dart'; -import 'package:titan/super_admin/ui/admin.dart'; import 'package:titan/admin/ui/pages/membership/add_edit_user_membership_page/search_result.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; @@ -42,7 +42,7 @@ class AddEditUserMembershipPage extends HookConsumerWidget { displayToast(context, type, msg); } - return SuperAdminTemplate( + return AdminTemplate( child: Padding( padding: const EdgeInsets.all(30.0), child: SingleChildScrollView( diff --git a/lib/admin/ui/pages/membership/association_membership_page/association_membership_button.dart b/lib/admin/ui/pages/membership/association_membership_page/association_membership_button.dart deleted file mode 100644 index 9fd58fa128..0000000000 --- a/lib/admin/ui/pages/membership/association_membership_page/association_membership_button.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:flutter/material.dart'; - -class AssociationMembershipButton extends StatelessWidget { - final Widget child; - final Color gradient1; - final Color gradient2; - const AssociationMembershipButton({ - super.key, - required this.child, - required this.gradient1, - required this.gradient2, - }); - - @override - Widget build(BuildContext context) { - return Container( - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - gradient: LinearGradient( - colors: [gradient1, gradient2], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ), - boxShadow: [ - BoxShadow( - color: gradient2.withValues(alpha: 0.3), - spreadRadius: 2, - blurRadius: 4, - offset: const Offset(2, 3), - ), - ], - borderRadius: BorderRadius.circular(10), - ), - child: Center(child: child), - ); - } -} diff --git a/lib/super_admin/router.dart b/lib/super_admin/router.dart index 478c0f8115..f75978842d 100644 --- a/lib/super_admin/router.dart +++ b/lib/super_admin/router.dart @@ -4,23 +4,12 @@ import 'package:titan/admin/providers/is_admin_provider.dart'; import 'package:titan/super_admin/ui/pages/edit_module_visibility/edit_module_visibility.dart' deferred as edit_module_visibility; - -import 'package:titan/admin/ui/pages/membership/add_edit_user_membership_page/add_edit_user_membership_page.dart' - deferred as add_edit_user_membership_page; -import 'package:titan/admin/ui/pages/membership/association_membership_detail_page/association_membership_detail_page.dart' - deferred as association_membership_detail_page; -import 'package:titan/admin/ui/pages/membership/association_membership_page/association_membership_page.dart' - deferred as association_membership_page; import 'package:titan/super_admin/ui/pages/schools/school_page/school_page.dart' deferred as school_page; import 'package:titan/super_admin/ui/pages/schools/add_school_page/add_school_page.dart' deferred as add_school_page; import 'package:titan/super_admin/ui/pages/schools/edit_school_page/edit_school_page.dart' deferred as edit_school_page; -import 'package:titan/admin/ui/pages/structure_page/structure_page.dart' - deferred as structure_page; -import 'package:titan/admin/ui/pages/structure_page/add_edit_structure_page/add_edit_structure_page.dart' - deferred as add_edit_structure_page; import 'package:titan/super_admin/ui/pages/main_page/main_page.dart' deferred as main_page; import 'package:titan/navigation/class/module.dart'; @@ -95,82 +84,6 @@ class SuperAdminRouter { ), ], ), - QRoute( - path: associationMemberships, - builder: () => association_membership_page.AssociationMembershipsPage(), - middleware: [ - DeferredLoadingMiddleware(association_membership_page.loadLibrary), - ], - children: [ - QRoute( - path: detailAssociationMembership, - builder: () => - association_membership_detail_page.AssociationMembershipEditorPage(), - middleware: [ - DeferredLoadingMiddleware( - association_membership_detail_page.loadLibrary, - ), - ], - children: [ - QRoute( - path: addEditMember, - builder: () => - add_edit_user_membership_page.AddEditUserMembershipPage(), - middleware: [ - DeferredLoadingMiddleware( - add_edit_user_membership_page.loadLibrary, - ), - ], - ), - ], - ), - ], - ), - QRoute( - path: structures, - builder: () => structure_page.StructurePage(), - middleware: [DeferredLoadingMiddleware(structure_page.loadLibrary)], - children: [ - QRoute( - path: addEditStructure, - builder: () => add_edit_structure_page.AddEditStructurePage(), - middleware: [ - DeferredLoadingMiddleware(add_edit_structure_page.loadLibrary), - ], - ), - ], - ), - QRoute( - path: associationMemberships, - builder: () => association_membership_page.AssociationMembershipsPage(), - middleware: [ - DeferredLoadingMiddleware(association_membership_page.loadLibrary), - ], - children: [ - QRoute( - path: detailAssociationMembership, - builder: () => - association_membership_detail_page.AssociationMembershipEditorPage(), - middleware: [ - DeferredLoadingMiddleware( - association_membership_detail_page.loadLibrary, - ), - ], - children: [ - QRoute( - path: addEditMember, - builder: () => - add_edit_user_membership_page.AddEditUserMembershipPage(), - middleware: [ - DeferredLoadingMiddleware( - add_edit_user_membership_page.loadLibrary, - ), - ], - ), - ], - ), - ], - ), ], ); } From 17303c2cf17f1564eb12498062c0119ac1413058 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 9 Aug 2025 21:42:06 +0200 Subject: [PATCH 175/473] Delete useless file --- .../groups/groups_page/groups_button.dart | 37 ------------------- .../structure_page/structure_button.dart | 37 ------------------- 2 files changed, 74 deletions(-) delete mode 100644 lib/admin/ui/pages/groups/groups_page/groups_button.dart delete mode 100644 lib/admin/ui/pages/structure_page/structure_button.dart diff --git a/lib/admin/ui/pages/groups/groups_page/groups_button.dart b/lib/admin/ui/pages/groups/groups_page/groups_button.dart deleted file mode 100644 index 9c29cae36c..0000000000 --- a/lib/admin/ui/pages/groups/groups_page/groups_button.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:flutter/material.dart'; - -class GroupButton extends StatelessWidget { - final Widget child; - final Color gradient1; - final Color gradient2; - const GroupButton({ - super.key, - required this.child, - required this.gradient1, - required this.gradient2, - }); - - @override - Widget build(BuildContext context) { - return Container( - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - gradient: LinearGradient( - colors: [gradient1, gradient2], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ), - boxShadow: [ - BoxShadow( - color: gradient2.withValues(alpha: 0.3), - spreadRadius: 2, - blurRadius: 4, - offset: const Offset(2, 3), - ), - ], - borderRadius: BorderRadius.circular(10), - ), - child: Center(child: child), - ); - } -} diff --git a/lib/admin/ui/pages/structure_page/structure_button.dart b/lib/admin/ui/pages/structure_page/structure_button.dart deleted file mode 100644 index 9c29cae36c..0000000000 --- a/lib/admin/ui/pages/structure_page/structure_button.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:flutter/material.dart'; - -class GroupButton extends StatelessWidget { - final Widget child; - final Color gradient1; - final Color gradient2; - const GroupButton({ - super.key, - required this.child, - required this.gradient1, - required this.gradient2, - }); - - @override - Widget build(BuildContext context) { - return Container( - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - gradient: LinearGradient( - colors: [gradient1, gradient2], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ), - boxShadow: [ - BoxShadow( - color: gradient2.withValues(alpha: 0.3), - spreadRadius: 2, - blurRadius: 4, - offset: const Offset(2, 3), - ), - ], - borderRadius: BorderRadius.circular(10), - ), - child: Center(child: child), - ); - } -} From a0fccec681d7f7ecd729a8812632f3af56baac53 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 9 Aug 2025 21:56:11 +0200 Subject: [PATCH 176/473] useless file --- ..._association_membership_list_provider.dart | 61 ------------------- 1 file changed, 61 deletions(-) delete mode 100644 lib/admin/providers/user_association_membership_list_provider.dart diff --git a/lib/admin/providers/user_association_membership_list_provider.dart b/lib/admin/providers/user_association_membership_list_provider.dart deleted file mode 100644 index 9a6ad7875a..0000000000 --- a/lib/admin/providers/user_association_membership_list_provider.dart +++ /dev/null @@ -1,61 +0,0 @@ -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/class/user_association_membership.dart'; -import 'package:titan/admin/repositories/association_membership_user_repository.dart'; -import 'package:titan/tools/providers/list_notifier.dart'; -import 'package:titan/tools/token_expire_wrapper.dart'; - -class UserMembershiplistNotifier - extends ListNotifier { - final AssociationMembershipUserRepository associationMembershipUserRepository; - UserMembershiplistNotifier({ - required this.associationMembershipUserRepository, - }) : super(const AsyncValue.loading()); - - Future>> - loadPersonalAssociationMembershipsList() async { - return await loadList( - () async => associationMembershipUserRepository - .getPersonalAssociationMembershipList(), - ); - } - - Future>> - loadUserAssociationMembershipsList(String userId) async { - return await loadList( - () async => associationMembershipUserRepository - .getUserAssociationMembershipList(userId), - ); - } -} - -final userMembershipListProvider = - StateNotifierProvider< - UserMembershiplistNotifier, - AsyncValue> - >((ref) { - final associationMembershipUserRepository = ref.watch( - associationMembershipUserRepositoryProvider, - ); - return UserMembershiplistNotifier( - associationMembershipUserRepository: - associationMembershipUserRepository, - ); - }); - -final myMembershipListProvider = - StateNotifierProvider< - UserMembershiplistNotifier, - AsyncValue> - >((ref) { - final associationMembershipUserRepository = ref.watch( - associationMembershipUserRepositoryProvider, - ); - UserMembershiplistNotifier provider = UserMembershiplistNotifier( - associationMembershipUserRepository: - associationMembershipUserRepository, - ); - tokenExpireWrapperAuth(ref, () async { - await provider.loadPersonalAssociationMembershipsList(); - }); - return provider; - }); From e1eecdf85c6a71c91bbd4071cfadb31420d8a496 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 9 Aug 2025 21:57:29 +0200 Subject: [PATCH 177/473] Remove useless file --- lib/super_admin/providers/school_id_provider.dart | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 lib/super_admin/providers/school_id_provider.dart diff --git a/lib/super_admin/providers/school_id_provider.dart b/lib/super_admin/providers/school_id_provider.dart deleted file mode 100644 index 306cf6d71f..0000000000 --- a/lib/super_admin/providers/school_id_provider.dart +++ /dev/null @@ -1,13 +0,0 @@ -import 'package:hooks_riverpod/hooks_riverpod.dart'; - -class SchoolIdNotifier extends StateNotifier { - SchoolIdNotifier() : super(""); - - void setId(String id) { - state = id; - } -} - -final schoolIdProvider = StateNotifierProvider( - (ref) => SchoolIdNotifier(), -); From d4f1de3d0b1e0c3c4a1a87d78fe88eda68f3625f Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 9 Aug 2025 22:27:51 +0200 Subject: [PATCH 178/473] remove useless check --- .../add_edit_structure_page/add_edit_structure_page.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/admin/ui/pages/structure_page/add_edit_structure_page/add_edit_structure_page.dart b/lib/admin/ui/pages/structure_page/add_edit_structure_page/add_edit_structure_page.dart index 091944b979..8c8a2a78c0 100644 --- a/lib/admin/ui/pages/structure_page/add_edit_structure_page/add_edit_structure_page.dart +++ b/lib/admin/ui/pages/structure_page/add_edit_structure_page/add_edit_structure_page.dart @@ -102,7 +102,7 @@ class AddEditStructurePage extends HookConsumerWidget { }, ), const SizedBox(height: 20), - (isEdit | (structureManager.id != "")) + (isEdit) ? Column( children: [ Text( From 911388a7aff4df8572c69dc5885bfa602d6f90c0 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 9 Aug 2025 22:28:15 +0200 Subject: [PATCH 179/473] Advertiser init --- lib/admin/ui/pages/main_page/main_page.dart | 7 +++++++ lib/l10n/app_fr.arb | 6 +++--- lib/l10n/app_localizations.dart | 8 ++++---- lib/l10n/app_localizations_en.dart | 2 +- lib/l10n/app_localizations_fr.dart | 6 +++--- 5 files changed, 18 insertions(+), 11 deletions(-) diff --git a/lib/admin/ui/pages/main_page/main_page.dart b/lib/admin/ui/pages/main_page/main_page.dart index d0c86d104d..14e8e6a684 100644 --- a/lib/admin/ui/pages/main_page/main_page.dart +++ b/lib/admin/ui/pages/main_page/main_page.dart @@ -79,6 +79,13 @@ class AdminMainPage extends HookConsumerWidget { onTap: () => QR.to(AdminRouter.root + AdminRouter.associationMemberships), ), + Text("Annonces", style: Theme.of(context).textTheme.titleLarge), + ListItem( + title: "Annonceurs", + subtitle: "Gérer les annonceurs", + onTap: () => + QR.to(AdminRouter.root + AdminRouter.associationMemberships), + ), ], ), ), diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 8dcd090406..e9fec62f46 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -27,7 +27,7 @@ "adminDelete": "Supprimer", "adminDeleteAssociationMembership": "Supprimer l'adhésion ?", "adminDeletedAssociationMembership": "Adhésion supprimée", - "adminDeleteGroup": "Supprimer le groupe ?", + "adminDeleteGroup": "Supprimer le groupe", "adminDeletedGroup": "Groupe supprimé", "adminDeleteSchool": "Supprimer l'école ?", "adminDeletedSchool": "École supprimée", @@ -109,7 +109,7 @@ "adminManagePaiementStructures" : "Gérer les structures du module de paiement", "adminManageUsersAssociationMemberships": "Gérer les adhésions des utilisateurs", "adminAssociationMembershipsManagement": "Gestion des adhésions", - "adminChooseAssication" : "Choisir une association", + "adminChooseGroupManager" : "Choisir une association", "adminSelectManager" : "Sélectionner un gestionnaire", "adminInviteUsers" : "Inviter des utilisateurs", "adminImportList": "Importer une liste", @@ -125,7 +125,7 @@ "advertChoosingAnnouncer": "Veuillez choisir un annonceur", "advertChoosingPoster": "Veuillez choisir une image", "advertContent": "Contenu", - "advertDeleteAdvert": "Supprimer l'annonce ?", + "advertDeleteAdvert": "Supprimer l'annonce", "advertDeleteAnnouncer": "Supprimer l'annonceur ?", "advertDeleting": "Suppression", "advertEdit": "Modifier", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index f261bb41c5..553fe5f859 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -263,7 +263,7 @@ abstract class AppLocalizations { /// No description provided for @adminDeleteGroup. /// /// In fr, this message translates to: - /// **'Supprimer le groupe ?'** + /// **'Supprimer le groupe'** String get adminDeleteGroup; /// No description provided for @adminDeletedGroup. @@ -704,11 +704,11 @@ abstract class AppLocalizations { /// **'Gestion des adhésions'** String get adminAssociationMembershipsManagement; - /// No description provided for @adminChooseAssication. + /// No description provided for @adminChooseGroupManager. /// /// In fr, this message translates to: /// **'Choisir une association'** - String get adminChooseAssication; + String get adminChooseGroupManager; /// No description provided for @adminSelectManager. /// @@ -803,7 +803,7 @@ abstract class AppLocalizations { /// No description provided for @advertDeleteAdvert. /// /// In fr, this message translates to: - /// **'Supprimer l\'annonce ?'** + /// **'Supprimer l\'annonce'** String get advertDeleteAdvert; /// No description provided for @advertDeleteAnnouncer. diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 28755cedb6..090a788403 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -322,7 +322,7 @@ class AppLocalizationsEn extends AppLocalizations { String get adminAssociationMembershipsManagement => 'Gestion des adhésions'; @override - String get adminChooseAssication => 'Choisir une association'; + String get adminChooseGroupManager => 'Choisir une association'; @override String get adminSelectManager => 'Sélectionner un gestionnaire'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index a1d61cbd88..cec771ce59 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -91,7 +91,7 @@ class AppLocalizationsFr extends AppLocalizations { String get adminDeletedAssociationMembership => 'Adhésion supprimée'; @override - String get adminDeleteGroup => 'Supprimer le groupe ?'; + String get adminDeleteGroup => 'Supprimer le groupe'; @override String get adminDeletedGroup => 'Groupe supprimé'; @@ -323,7 +323,7 @@ class AppLocalizationsFr extends AppLocalizations { String get adminAssociationMembershipsManagement => 'Gestion des adhésions'; @override - String get adminChooseAssication => 'Choisir une association'; + String get adminChooseGroupManager => 'Choisir une association'; @override String get adminSelectManager => 'Sélectionner un gestionnaire'; @@ -372,7 +372,7 @@ class AppLocalizationsFr extends AppLocalizations { String get advertContent => 'Contenu'; @override - String get advertDeleteAdvert => 'Supprimer l\'annonce ?'; + String get advertDeleteAdvert => 'Supprimer l\'annonce'; @override String get advertDeleteAnnouncer => 'Supprimer l\'annonceur ?'; From 792db7f0877bbd240b02937b26499e6429343fd3 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 9 Aug 2025 22:32:17 +0200 Subject: [PATCH 180/473] Remove useless function --- lib/tools/functions.dart | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/lib/tools/functions.dart b/lib/tools/functions.dart index ff1ec3becc..5c2ee51519 100644 --- a/lib/tools/functions.dart +++ b/lib/tools/functions.dart @@ -531,17 +531,3 @@ String getTitanPackageName() { String getTitanLogo() { return "assets/images/logo_${getAppFlavor()}.png"; } - -void navigateTo( - String path, { - bool ignoreSamePath = true, - PageAlreadyExistAction? pageAlreadyExistAction, - bool waitForResult = false, - required WidgetRef ref, -}) { - final navbarVisibilityNotifier = ref.read(navbarVisibilityProvider.notifier); - final navbarVisibility = ref.read(navbarVisibilityProvider); - print(navbarVisibility); - navbarVisibilityNotifier.show(); - QR.to(path); -} From 5d21d91d4c97d23f79ccbab1ad03c08e7da8cb0b Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 9 Aug 2025 22:48:30 +0200 Subject: [PATCH 181/473] Language mistake --- .../association_membership_page/add_membership_modal.dart | 4 ++-- lib/l10n/app_fr.arb | 2 +- lib/l10n/app_localizations.dart | 2 +- lib/l10n/app_localizations_en.dart | 3 ++- lib/l10n/app_localizations_fr.dart | 3 ++- 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/admin/ui/pages/membership/association_membership_page/add_membership_modal.dart b/lib/admin/ui/pages/membership/association_membership_page/add_membership_modal.dart index f72e3eb644..749900f1b3 100644 --- a/lib/admin/ui/pages/membership/association_membership_page/add_membership_modal.dart +++ b/lib/admin/ui/pages/membership/association_membership_page/add_membership_modal.dart @@ -43,7 +43,7 @@ class AddMembershipModal extends HookWidget { padding: const EdgeInsets.all(8.0), child: chosenGroup.value == null ? ListItem( - title: localizeWithContext.adminChooseAssication, + title: localizeWithContext.adminChooseGroupManager, onTap: () async { FocusScope.of(context).unfocus(); final ctx = context; @@ -54,7 +54,7 @@ class AddMembershipModal extends HookWidget { context: ctx, ref: ref, modal: BottomModalTemplate( - title: localizeWithContext.adminChooseAssication, + title: localizeWithContext.adminChooseGroupManager, child: Column( children: [ ...groups.map( diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index e9fec62f46..543ff71a1d 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -109,7 +109,7 @@ "adminManagePaiementStructures" : "Gérer les structures du module de paiement", "adminManageUsersAssociationMemberships": "Gérer les adhésions des utilisateurs", "adminAssociationMembershipsManagement": "Gestion des adhésions", - "adminChooseGroupManager" : "Choisir une association", + "adminChooseGroupManager" : "Groupe gestionnaire de l'adhésion", "adminSelectManager" : "Sélectionner un gestionnaire", "adminInviteUsers" : "Inviter des utilisateurs", "adminImportList": "Importer une liste", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 553fe5f859..e042f95404 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -707,7 +707,7 @@ abstract class AppLocalizations { /// No description provided for @adminChooseGroupManager. /// /// In fr, this message translates to: - /// **'Choisir une association'** + /// **'Choisir un groupe pour gérer l\'adhésion'** String get adminChooseGroupManager; /// No description provided for @adminSelectManager. diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 090a788403..cb297bc699 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -322,7 +322,8 @@ class AppLocalizationsEn extends AppLocalizations { String get adminAssociationMembershipsManagement => 'Gestion des adhésions'; @override - String get adminChooseGroupManager => 'Choisir une association'; + String get adminChooseGroupManager => + 'Choisir un groupe pour gérer l\'adhésion'; @override String get adminSelectManager => 'Sélectionner un gestionnaire'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index cec771ce59..26f5e76b06 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -323,7 +323,8 @@ class AppLocalizationsFr extends AppLocalizations { String get adminAssociationMembershipsManagement => 'Gestion des adhésions'; @override - String get adminChooseGroupManager => 'Choisir une association'; + String get adminChooseGroupManager => + 'Choisir un groupe pour gérer l\'adhésion'; @override String get adminSelectManager => 'Sélectionner un gestionnaire'; From c67a014e6da3386d0fcc9be575e2363b928745af Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 9 Aug 2025 22:50:37 +0200 Subject: [PATCH 182/473] feat : renaming --- ...er_creation_provider.dart => user_invitation_provider.dart} | 2 +- ...reation_repository.dart => user_invitation_repository.dart} | 0 lib/admin/ui/pages/users_management_page/add_user_modal.dart | 2 +- lib/l10n/app_localizations.dart | 2 +- lib/l10n/app_localizations_en.dart | 3 +-- lib/l10n/app_localizations_fr.dart | 3 +-- 6 files changed, 5 insertions(+), 7 deletions(-) rename lib/admin/providers/{user_creation_provider.dart => user_invitation_provider.dart} (89%) rename lib/admin/repositories/{user_creation_repository.dart => user_invitation_repository.dart} (100%) diff --git a/lib/admin/providers/user_creation_provider.dart b/lib/admin/providers/user_invitation_provider.dart similarity index 89% rename from lib/admin/providers/user_creation_provider.dart rename to lib/admin/providers/user_invitation_provider.dart index d9e7d88cf2..d74eaa7063 100644 --- a/lib/admin/providers/user_creation_provider.dart +++ b/lib/admin/providers/user_invitation_provider.dart @@ -1,5 +1,5 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/repositories/user_creation_repository.dart'; +import 'package:titan/admin/repositories/user_invitation_repository.dart'; class UserCreationNotifier extends StateNotifier { final UserCreationRepository userCreationRepository; diff --git a/lib/admin/repositories/user_creation_repository.dart b/lib/admin/repositories/user_invitation_repository.dart similarity index 100% rename from lib/admin/repositories/user_creation_repository.dart rename to lib/admin/repositories/user_invitation_repository.dart diff --git a/lib/admin/ui/pages/users_management_page/add_user_modal.dart b/lib/admin/ui/pages/users_management_page/add_user_modal.dart index 996999a2ed..07ac428160 100644 --- a/lib/admin/ui/pages/users_management_page/add_user_modal.dart +++ b/lib/admin/ui/pages/users_management_page/add_user_modal.dart @@ -4,7 +4,7 @@ import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/providers/user_creation_provider.dart'; +import 'package:titan/admin/providers/user_invitation_provider.dart'; import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index e042f95404..2bc28e650f 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -707,7 +707,7 @@ abstract class AppLocalizations { /// No description provided for @adminChooseGroupManager. /// /// In fr, this message translates to: - /// **'Choisir un groupe pour gérer l\'adhésion'** + /// **'Groupe gestionnaire de l\'adhésion'** String get adminChooseGroupManager; /// No description provided for @adminSelectManager. diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index cb297bc699..fc5d6ce592 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -322,8 +322,7 @@ class AppLocalizationsEn extends AppLocalizations { String get adminAssociationMembershipsManagement => 'Gestion des adhésions'; @override - String get adminChooseGroupManager => - 'Choisir un groupe pour gérer l\'adhésion'; + String get adminChooseGroupManager => 'Groupe gestionnaire de l\'adhésion'; @override String get adminSelectManager => 'Sélectionner un gestionnaire'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 26f5e76b06..3066d263a1 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -323,8 +323,7 @@ class AppLocalizationsFr extends AppLocalizations { String get adminAssociationMembershipsManagement => 'Gestion des adhésions'; @override - String get adminChooseGroupManager => - 'Choisir un groupe pour gérer l\'adhésion'; + String get adminChooseGroupManager => 'Groupe gestionnaire de l\'adhésion'; @override String get adminSelectManager => 'Sélectionner un gestionnaire'; From 56826803913435cf43b8d692a9fc085156b9a083 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 9 Aug 2025 22:51:53 +0200 Subject: [PATCH 183/473] Remove mistake push file --- scripts/test.dart | 65 ----------------------------------------------- 1 file changed, 65 deletions(-) delete mode 100644 scripts/test.dart diff --git a/scripts/test.dart b/scripts/test.dart deleted file mode 100644 index 1635d2d766..0000000000 --- a/scripts/test.dart +++ /dev/null @@ -1,65 +0,0 @@ -import 'dart:io'; - -void main() { - final directoryPath = "."; - final searchStart = "VoteTextConstants."; - final replaceStart = "AppLocalizations.of(context)!.vote"; - final importStatement = "import 'package:titan/l10n/app_localizations.dart';"; - - final directory = Directory(directoryPath); - - if (!directory.existsSync()) { - print('Le dossier $directoryPath n\'existe pas.'); - exit(1); - } - - final files = directory - .listSync(recursive: true) - .whereType() - .where((file) => file.path.endsWith('.dart')) - .toList(); - - print('Fichiers à traiter : ${files.length}'); - for (var i = 'a'.codeUnitAt(0); i <= 'z'.codeUnitAt(0); i++) { - var letter = String.fromCharCode(i); - final search = '$searchStart$letter'; - final replace = '$replaceStart${letter.toUpperCase()}'; - for (final file in files) { - final content = file.readAsStringSync(); - - if (content.contains(search)) { - // Remplacer la chaîne - var newContent = content.replaceAll(search, replace); - - // Ajouter l'import si absent - if (!newContent.contains(importStatement)) { - // Trouver la fin des imports existants - final lines = newContent.split('\n'); - int insertIndex = 0; - - for (var j = 0; j < lines.length; j++) { - final line = lines[j].trim(); - // On suppose que les imports commencent par 'import' ou 'part' - if (line.startsWith('import ') || line.startsWith('part ')) { - insertIndex = j + 1; - } else if (line.isEmpty) { - // Ignorer les lignes vides, continuer la recherche - continue; - } else { - // On a atteint une ligne qui n'est ni import ni part ni vide -> stop - break; - } - } - - lines.insert(insertIndex, importStatement); - newContent = lines.join('\n'); - } - - file.writeAsStringSync(newContent); - print('Modifié : ${file.path}'); - } - } - } - - print('Remplacement terminé.'); -} From 2153838c1b8fbf586dc578ad15c7ab307a989c3e Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 9 Aug 2025 22:52:13 +0200 Subject: [PATCH 184/473] useless import --- lib/tools/functions.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/tools/functions.dart b/lib/tools/functions.dart index 5c2ee51519..dca0d7a05d 100644 --- a/lib/tools/functions.dart +++ b/lib/tools/functions.dart @@ -4,10 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:heroicons/heroicons.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:intl/intl.dart'; -import 'package:qlevar_router/qlevar_router.dart'; -import 'package:titan/navigation/providers/navbar_visibility_provider.dart'; import 'package:titan/tools/constants.dart'; import 'package:auto_size_text/auto_size_text.dart'; import 'package:titan/tools/plausible/plausible.dart'; From dc81b8546ccc56c8a4c8713c8971264590085bd8 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 9 Aug 2025 22:56:34 +0200 Subject: [PATCH 185/473] remove void --- lib/admin/providers/user_invitation_provider.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/admin/providers/user_invitation_provider.dart b/lib/admin/providers/user_invitation_provider.dart index d74eaa7063..49433f9fd2 100644 --- a/lib/admin/providers/user_invitation_provider.dart +++ b/lib/admin/providers/user_invitation_provider.dart @@ -1,7 +1,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/repositories/user_invitation_repository.dart'; -class UserCreationNotifier extends StateNotifier { +class UserCreationNotifier extends StateNotifier { final UserCreationRepository userCreationRepository; UserCreationNotifier({required this.userCreationRepository}) : super(null); From 73bccf2caf452cd3a8923ac793391760c006d130 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 9 Aug 2025 23:01:49 +0200 Subject: [PATCH 186/473] Renaming creation -> invitation --- .../providers/user_invitation_provider.dart | 24 +++++++++++-------- .../user_invitation_repository.dart | 6 ++--- .../users_management_page/add_user_modal.dart | 6 ++--- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/lib/admin/providers/user_invitation_provider.dart b/lib/admin/providers/user_invitation_provider.dart index 49433f9fd2..01d3761c8b 100644 --- a/lib/admin/providers/user_invitation_provider.dart +++ b/lib/admin/providers/user_invitation_provider.dart @@ -1,18 +1,22 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/repositories/user_invitation_repository.dart'; -class UserCreationNotifier extends StateNotifier { - final UserCreationRepository userCreationRepository; - UserCreationNotifier({required this.userCreationRepository}) : super(null); +class UserInvitationNotifier extends StateNotifier { + final UserInvitationRepository userInvitationRepository; + UserInvitationNotifier({required this.userInvitationRepository}) + : super(null); Future createUsers(List mailList) async { - return await userCreationRepository.createUsers(mailList); + return await userInvitationRepository.createUsers(mailList); } } -final userCreationProvider = StateNotifierProvider(( - ref, -) { - final userCreationRepository = ref.watch(userCreationRepositoryProvider); - return UserCreationNotifier(userCreationRepository: userCreationRepository); -}); +final userInvitationProvider = + StateNotifierProvider((ref) { + final userInvitationRepository = ref.watch( + userInvitationRepositoryProvider, + ); + return UserInvitationNotifier( + userInvitationRepository: userInvitationRepository, + ); + }); diff --git a/lib/admin/repositories/user_invitation_repository.dart b/lib/admin/repositories/user_invitation_repository.dart index df16849dc7..a78965b84b 100644 --- a/lib/admin/repositories/user_invitation_repository.dart +++ b/lib/admin/repositories/user_invitation_repository.dart @@ -2,7 +2,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/auth/providers/openid_provider.dart'; import 'package:titan/tools/repository/repository.dart'; -class UserCreationRepository extends Repository { +class UserInvitationRepository extends Repository { @override // ignore: overridden_fields final ext = "users/"; @@ -13,7 +13,7 @@ class UserCreationRepository extends Repository { } } -final userCreationRepositoryProvider = Provider((ref) { +final userInvitationRepositoryProvider = Provider((ref) { final token = ref.watch(tokenProvider); - return UserCreationRepository()..setToken(token); + return UserInvitationRepository()..setToken(token); }); diff --git a/lib/admin/ui/pages/users_management_page/add_user_modal.dart b/lib/admin/ui/pages/users_management_page/add_user_modal.dart index 07ac428160..0d0039042e 100644 --- a/lib/admin/ui/pages/users_management_page/add_user_modal.dart +++ b/lib/admin/ui/pages/users_management_page/add_user_modal.dart @@ -69,10 +69,10 @@ class AddUsersModalContent extends HookConsumerWidget { text: localizeWithContext.adminAdd, onPressed: () { tokenExpireWrapper(ref, () async { - final userCreationNotifier = ref.watch( - userCreationProvider.notifier, + final userInvitationNotifier = ref.watch( + userInvitationProvider.notifier, ); - final value = await userCreationNotifier.createUsers( + final value = await userInvitationNotifier.createUsers( mailList.value, ); if (value) { From 39d0825a1e86be35e50784309dc3db80f258cfaf Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 9 Aug 2025 23:18:06 +0200 Subject: [PATCH 187/473] Rename --- lib/admin/router.dart | 5 +++-- lib/l10n/app_fr.arb | 4 +++- lib/l10n/app_localizations.dart | 14 +++++++++++++- lib/l10n/app_localizations_en.dart | 8 +++++++- lib/l10n/app_localizations_fr.dart | 8 +++++++- 5 files changed, 33 insertions(+), 6 deletions(-) diff --git a/lib/admin/router.dart b/lib/admin/router.dart index acbb0d2313..0c18a20c7a 100644 --- a/lib/admin/router.dart +++ b/lib/admin/router.dart @@ -12,6 +12,7 @@ import 'package:titan/admin/ui/pages/group_notifification_page/group_notificatio deferred as group_notification_page; import 'package:titan/admin/ui/pages/structure_page/add_edit_structure_page/add_edit_structure_page.dart' deferred as add_edit_structure_page; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/navigation/class/module.dart'; import 'package:titan/admin/ui/pages/structure_page/structure_page.dart' deferred as structure_page; @@ -40,8 +41,8 @@ class AdminRouter { '/detail_association_membership'; static const String addEditMember = '/add_edit_member'; static final Module module = Module( - getName: (context) => "Admin", - description: "Gérer les utilisateurs de l'application", + getName: (context) => AppLocalizations.of(context)!.adminAdmin, + description: "Gérer les utilisateurs, groupes et structures", root: AdminRouter.root, ); diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 543ff71a1d..d6e8d1d415 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -101,7 +101,7 @@ "adminFailedToDeleteGroup": "Échec de la suppression du groupe", "adminUsersAndGroups": "Utilisateurs et groupes", "adminUsersManagement": "Gestion des utilisateurs", - "adminUsersManagementDescription": "Gérez les utilisateurs de l'application", + "adminUsersManagementDescription": "Gérer les utilisateurs de l'application", "adminManageUserGroups": "Gérer les groupes d'utilisateurs", "adminSendNotificationToGroup": "Envoyer une notification à un groupe", "adminPaiementModule": "Module de paiement", @@ -116,6 +116,7 @@ "adminInvitedUsers": "Utilisateurs invités", "adminFailedToInviteUsers": "Échec de l'invitation des utilisateurs", "adminDeleteUsers": "Supprimer des utilisateurs", + "adminAdmin": "Admin", "advertAdd": "Ajouter", "advertAddedAdvert": "Annonce publiée", "advertAddedAnnouncer": "Annonceur ajouté", @@ -155,6 +156,7 @@ "advertMonthOct": "Oct.", "advertMonthNov": "Nov.", "advertMonthDec": "Déc.", + "advertModuleDescription": "Gérer les annonces", "amapAccounts": "Comptes", "amapAdd": "Ajouter", "amapAddDelivery": "Ajouter une livraison", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 2bc28e650f..350c8a014f 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -659,7 +659,7 @@ abstract class AppLocalizations { /// No description provided for @adminUsersManagementDescription. /// /// In fr, this message translates to: - /// **'Gérez les utilisateurs de l\'application'** + /// **'Gérer les utilisateurs de l\'application'** String get adminUsersManagementDescription; /// No description provided for @adminManageUserGroups. @@ -746,6 +746,12 @@ abstract class AppLocalizations { /// **'Supprimer des utilisateurs'** String get adminDeleteUsers; + /// No description provided for @adminAdmin. + /// + /// In fr, this message translates to: + /// **'Admin'** + String get adminAdmin; + /// No description provided for @advertAdd. /// /// In fr, this message translates to: @@ -980,6 +986,12 @@ abstract class AppLocalizations { /// **'Déc.'** String get advertMonthDec; + /// No description provided for @advertModuleDescription. + /// + /// In fr, this message translates to: + /// **'Gérer les annonces'** + String get advertModuleDescription; + /// No description provided for @amapAccounts. /// /// In fr, this message translates to: diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index fc5d6ce592..effbba326a 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -295,7 +295,7 @@ class AppLocalizationsEn extends AppLocalizations { @override String get adminUsersManagementDescription => - 'Gérez les utilisateurs de l\'application'; + 'Gérer les utilisateurs de l\'application'; @override String get adminManageUserGroups => 'Gérer les groupes d\'utilisateurs'; @@ -343,6 +343,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get adminDeleteUsers => 'Supprimer des utilisateurs'; + @override + String get adminAdmin => 'Admin'; + @override String get advertAdd => 'Add'; @@ -460,6 +463,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get advertMonthDec => 'Dec'; + @override + String get advertModuleDescription => 'Gérer les annonces'; + @override String get amapAccounts => 'Accounts'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 3066d263a1..363ff7dd85 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -296,7 +296,7 @@ class AppLocalizationsFr extends AppLocalizations { @override String get adminUsersManagementDescription => - 'Gérez les utilisateurs de l\'application'; + 'Gérer les utilisateurs de l\'application'; @override String get adminManageUserGroups => 'Gérer les groupes d\'utilisateurs'; @@ -344,6 +344,9 @@ class AppLocalizationsFr extends AppLocalizations { @override String get adminDeleteUsers => 'Supprimer des utilisateurs'; + @override + String get adminAdmin => 'Admin'; + @override String get advertAdd => 'Ajouter'; @@ -461,6 +464,9 @@ class AppLocalizationsFr extends AppLocalizations { @override String get advertMonthDec => 'Déc.'; + @override + String get advertModuleDescription => 'Gérer les annonces'; + @override String get amapAccounts => 'Comptes'; From 7994c227fc3d0871fd5898fb1f89ff851591beeb Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 9 Aug 2025 23:19:52 +0200 Subject: [PATCH 188/473] change color --- lib/admin/ui/components/user_ui.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/admin/ui/components/user_ui.dart b/lib/admin/ui/components/user_ui.dart index 93ed169c64..681e426bb9 100644 --- a/lib/admin/ui/components/user_ui.dart +++ b/lib/admin/ui/components/user_ui.dart @@ -29,7 +29,7 @@ class UserUi extends HookConsumerWidget { child: Container( padding: const EdgeInsets.all(7), decoration: BoxDecoration( - color: ColorConstants.error, + color: ColorConstants.onMain, boxShadow: [ BoxShadow( color: ColorConstants.background2.withValues(alpha: 0.4), From 16b54e9e03e54345dd0c5d37c6c1fcdc0f2e523e Mon Sep 17 00:00:00 2001 From: Thonyk Date: Sat, 9 Aug 2025 23:26:35 +0200 Subject: [PATCH 189/473] fix: deactivated association and UI --- .../ui/pages/admin_page/admin_page.dart | 7 +++-- .../association_add_edit_page.dart | 7 ++--- .../association_groups_page.dart | 6 ++-- .../association_members_page.dart | 28 +++++++++---------- .../ui/pages/main_page/main_page.dart | 3 -- .../member_detail_page.dart | 5 ++-- .../membership_editor_page.dart | 10 ++++--- 7 files changed, 34 insertions(+), 32 deletions(-) diff --git a/lib/phonebook/ui/pages/admin_page/admin_page.dart b/lib/phonebook/ui/pages/admin_page/admin_page.dart index 7c3bb63e09..a40ff696cc 100644 --- a/lib/phonebook/ui/pages/admin_page/admin_page.dart +++ b/lib/phonebook/ui/pages/admin_page/admin_page.dart @@ -17,7 +17,6 @@ import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/tools/ui/styleguide/list_item_template.dart'; -import 'package:titan/tools/ui/widgets/align_left_text.dart'; import 'package:tuple/tuple.dart'; import 'package:titan/l10n/app_localizations.dart'; @@ -51,10 +50,14 @@ class AdminPage extends HookConsumerWidget { child: Padding( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ AssociationResearchBar(), const SizedBox(height: 10), - AlignLeftText(localizeWithContext.phonebookAdmin), + Text( + localizeWithContext.phonebookAdmin, + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), const SizedBox(height: 10), Async2Children( values: Tuple2(associationList, associationGroupementList), diff --git a/lib/phonebook/ui/pages/association_add_edit_page/association_add_edit_page.dart b/lib/phonebook/ui/pages/association_add_edit_page/association_add_edit_page.dart index 6061c6ac1d..e13d0d1ba2 100644 --- a/lib/phonebook/ui/pages/association_add_edit_page/association_add_edit_page.dart +++ b/lib/phonebook/ui/pages/association_add_edit_page/association_add_edit_page.dart @@ -18,7 +18,6 @@ import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/styleguide/button.dart'; import 'package:titan/tools/ui/styleguide/text_entry.dart'; -import 'package:titan/tools/ui/widgets/align_left_text.dart'; class AssociationAddEditPage extends HookConsumerWidget { final scrollKey = GlobalKey(); @@ -60,6 +59,7 @@ class AssociationAddEditPage extends HookConsumerWidget { child: Padding( padding: EdgeInsets.symmetric(horizontal: 30.0), child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 30), Align( @@ -177,10 +177,9 @@ class AssociationAddEditPage extends HookConsumerWidget { ), ], const SizedBox(height: 20), - AlignLeftText( + Text( localizeWithContext.phonebookAssociationGroupement, - fontWeight: FontWeight.normal, - fontSize: 16, + style: TextStyle(fontSize: 16, fontWeight: FontWeight.normal), ), const SizedBox(height: 5), AssociationGroupementBar(editable: true), diff --git a/lib/phonebook/ui/pages/association_groups_page/association_groups_page.dart b/lib/phonebook/ui/pages/association_groups_page/association_groups_page.dart index 542db482c7..52419505c7 100644 --- a/lib/phonebook/ui/pages/association_groups_page/association_groups_page.dart +++ b/lib/phonebook/ui/pages/association_groups_page/association_groups_page.dart @@ -15,7 +15,6 @@ import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; import 'package:titan/tools/ui/styleguide/button.dart'; import 'package:titan/tools/ui/styleguide/list_item_toggle.dart'; -import 'package:titan/tools/ui/widgets/align_left_text.dart'; class AssociationGroupsPage extends HookConsumerWidget { final scrollKey = GlobalKey(); @@ -63,10 +62,11 @@ class AssociationGroupsPage extends HookConsumerWidget { child: Padding( padding: const EdgeInsets.symmetric(horizontal: 30), child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - AlignLeftText( + Text( localizeWithContext.phonebookGroups(association.name), - fontSize: 20, + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), const SizedBox(height: 20), AsyncChild( diff --git a/lib/phonebook/ui/pages/association_members_page/association_members_page.dart b/lib/phonebook/ui/pages/association_members_page/association_members_page.dart index 2521e19577..d1d8a2626f 100644 --- a/lib/phonebook/ui/pages/association_members_page/association_members_page.dart +++ b/lib/phonebook/ui/pages/association_members_page/association_members_page.dart @@ -18,7 +18,6 @@ import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; import 'package:titan/tools/ui/styleguide/list_item_template.dart'; -import 'package:titan/tools/ui/widgets/align_left_text.dart'; class AssociationMembersPage extends HookConsumerWidget { const AssociationMembersPage({super.key}); @@ -55,10 +54,11 @@ class AssociationMembersPage extends HookConsumerWidget { }); }, child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - AlignLeftText( + Text( localizeWithContext.phonebookMembers(association.name), - fontSize: 20, + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), if (!association.deactivated) ...[ SizedBox(height: 20), @@ -150,17 +150,17 @@ class AssociationMembersPage extends HookConsumerWidget { .toList(), ), ) - : ListView.builder( - itemCount: associationMembers.length, - itemBuilder: (context, index) { - return MemberCard( - deactivated: true, - key: ValueKey(associationMembers[index].member.id), - member: associationMembers[index], - association: association, - editable: true, - ); - }, + : Column( + children: associationMemberSortedList + .map( + (e) => MemberCard( + deactivated: true, + member: e, + association: association, + editable: true, + ), + ) + .toList(), ), ), SizedBox(height: 80), diff --git a/lib/phonebook/ui/pages/main_page/main_page.dart b/lib/phonebook/ui/pages/main_page/main_page.dart index 6150993b22..031724abf2 100644 --- a/lib/phonebook/ui/pages/main_page/main_page.dart +++ b/lib/phonebook/ui/pages/main_page/main_page.dart @@ -16,7 +16,6 @@ import 'package:titan/tools/ui/layouts/refresher.dart'; import 'package:titan/tools/ui/styleguide/icon_button.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/l10n/app_localizations.dart'; -import 'package:titan/tools/ui/widgets/align_left_text.dart'; import 'package:tuple/tuple.dart'; class PhonebookMainPage extends HookConsumerWidget { @@ -73,8 +72,6 @@ class PhonebookMainPage extends HookConsumerWidget { ], ), const SizedBox(height: 10), - AlignLeftText(localizeWithContext.phonebookPhonebook), - const SizedBox(height: 10), Async2Children( values: Tuple2(associationList, associationGroupementList), builder: (context, associations, associationGroupements) { diff --git a/lib/phonebook/ui/pages/member_detail_page/member_detail_page.dart b/lib/phonebook/ui/pages/member_detail_page/member_detail_page.dart index 04cc82f505..fc825b38f8 100644 --- a/lib/phonebook/ui/pages/member_detail_page/member_detail_page.dart +++ b/lib/phonebook/ui/pages/member_detail_page/member_detail_page.dart @@ -9,7 +9,6 @@ import 'package:titan/phonebook/ui/phonebook.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/tools/ui/builders/auto_loader_child.dart'; -import 'package:titan/tools/ui/widgets/align_left_text.dart'; class MemberDetailPage extends HookConsumerWidget { const MemberDetailPage({super.key}); @@ -34,6 +33,7 @@ class MemberDetailPage extends HookConsumerWidget { padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 30), child: SingleChildScrollView( child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ Center( child: Column( @@ -104,10 +104,11 @@ class MemberDetailPage extends HookConsumerWidget { ), const SizedBox(height: 20), if (member.memberships.isNotEmpty) - AlignLeftText( + Text( member.memberships.length == 1 ? localizeWithContext.phonebookAssociation : localizeWithContext.phonebookAssociations, + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), const SizedBox(height: 10), AsyncChild( diff --git a/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart b/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart index 27aae51206..26837c04a6 100644 --- a/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart +++ b/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart @@ -15,7 +15,6 @@ import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; import 'package:titan/tools/ui/styleguide/button.dart'; import 'package:titan/tools/ui/styleguide/list_item_template.dart'; import 'package:titan/tools/ui/styleguide/list_item_toggle.dart'; -import 'package:titan/tools/ui/widgets/align_left_text.dart'; import 'package:titan/tools/ui/widgets/text_entry.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/phonebook/providers/complete_member_provider.dart'; @@ -140,7 +139,10 @@ class MembershipEditorPage extends HookConsumerWidget { child: Column( children: [ if (!isEdit) ...[ - AlignLeftText(localizeWithContext.phonebookAddMember), + Text( + localizeWithContext.phonebookAddMember, + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), ListItemTemplate( title: member.member.id == "" ? localizeWithContext.phonebookSearchUser @@ -156,11 +158,11 @@ class MembershipEditorPage extends HookConsumerWidget { ), ), ] else - AlignLeftText( + Text( localizeWithContext.phonebookModifyMembership( member.member.nickname ?? member.getName(), ), - fontSize: 18, + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), const SizedBox(height: 10), rolesTagList.maybeWhen( From 0b73ce84f2cc02f9f7f90f2077619e5c882c3119 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 9 Aug 2025 23:26:43 +0200 Subject: [PATCH 190/473] Move parsing to a function --- lib/admin/tools/functions.dart | 15 +++++++++++++++ .../users_management_page/add_user_modal.dart | 19 ++----------------- 2 files changed, 17 insertions(+), 17 deletions(-) create mode 100644 lib/admin/tools/functions.dart diff --git a/lib/admin/tools/functions.dart b/lib/admin/tools/functions.dart new file mode 100644 index 0000000000..c6714b5d88 --- /dev/null +++ b/lib/admin/tools/functions.dart @@ -0,0 +1,15 @@ +List getMailListFromCSV(String fileContent) { + final lines = fileContent.split('\n'); + final List emails = []; + + for (var i = 0; i < lines.length; i++) { + final line = lines[i].trim(); + if (line.isEmpty) continue; + + final columns = line.split(','); + + final email = columns[0].trim(); + emails.add(email); + } + return emails; +} diff --git a/lib/admin/ui/pages/users_management_page/add_user_modal.dart b/lib/admin/ui/pages/users_management_page/add_user_modal.dart index 0d0039042e..07437e84e6 100644 --- a/lib/admin/ui/pages/users_management_page/add_user_modal.dart +++ b/lib/admin/ui/pages/users_management_page/add_user_modal.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/providers/user_invitation_provider.dart'; +import 'package:titan/admin/tools/functions.dart'; import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; @@ -43,23 +44,7 @@ class AddUsersModalContent extends HookConsumerWidget { if (file.path != null) { final fileContent = await File(file.path!).readAsString(); - - final lines = fileContent.split('\n'); - final List emails = []; - - for (var i = 0; i < lines.length; i++) { - final line = lines[i].trim(); - if (line.isEmpty) continue; - - final columns = line.split(','); - - final email = columns[0].trim(); - - if (email.contains('@')) { - emails.add(email); - } - } - mailList.value = emails; + mailList.value = getMailListFromCSV(fileContent); } } }, From 7bc7ffb91106852424f0340824843bedd8492f75 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 9 Aug 2025 23:37:25 +0200 Subject: [PATCH 191/473] translation --- lib/l10n/app_en.arb | 30 +++++++++++++ lib/l10n/app_fr.arb | 3 +- lib/l10n/app_localizations.dart | 10 +---- lib/l10n/app_localizations_en.dart | 67 ++++++++++++++---------------- lib/l10n/app_localizations_fr.dart | 5 +-- 5 files changed, 65 insertions(+), 50 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 8a3082e195..5b2778aa25 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -79,6 +79,36 @@ "adminUser": "User", "adminValidateFilters": "Apply filters", "adminVisibilities": "Visibilities", + "adminGroupNotification": "Group notifications", + "adminNotifyGroup": "Send a notification", + "adminTitle": "Title", + "adminContent": "Content", + "adminSend": "Send", + "adminNotificationSent": "Notification sent", + "adminFailedToSendNotification": "Failed to send notification", + "adminGroupsManagement": "Groups management", + "adminEditGroup": "Edit group", + "adminManageMembers": "Manage members", + "adminDeleteGroupConfirmation": "Are you sure you want to delete this group?", + "adminFailedToDeleteGroup": "Failed to delete group", + "adminUsersAndGroups": "Users and groups", + "adminUsersManagement": "Users management", + "adminUsersManagementDescription": "Manage users, groups, and associations", + "adminManageUserGroups": "Manage user groups", + "adminSendNotificationToGroup": "Send notification to group", + "adminPaiementModule": "Payment module", + "adminPaiement": "Payment", + "adminManagePaiementStructures": "Manage payment structures", + "adminManageUsersAssociationMemberships": "Manage users' association memberships", + "adminAssociationMembershipsManagement": "Association memberships management", + "adminChooseGroupManager" : "Choose a group to manage this membership", + "adminSelectManager": "Select a manager", + "adminInviteUsers": "Invite users", + "adminImportList": "Import a list", + "adminInvitedUsers": "Invited users", + "adminFailedToInviteUsers": "Failed to invite users", + "adminDeleteUsers": "Delete users", + "adminAdmin" : "Admin", "advertAdd": "Add", "advertAddedAdvert": "Advert published", "advertAddedAnnouncer": "Announcer added", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index d6e8d1d415..484f07a553 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -92,7 +92,7 @@ "adminTitle": "Titre", "adminContent": "Contenu", "adminSend": "Envoyer", - "adminNotificationSended": "Notification envoyée", + "adminNotificationSent": "Notification envoyée", "adminFailedToSendNotification": "Échec de l'envoi de la notification", "adminGroupsManagement": "Gestion des groupes", "adminEditGroup": "Modifier le groupe", @@ -156,7 +156,6 @@ "advertMonthOct": "Oct.", "advertMonthNov": "Nov.", "advertMonthDec": "Déc.", - "advertModuleDescription": "Gérer les annonces", "amapAccounts": "Comptes", "amapAdd": "Ajouter", "amapAddDelivery": "Ajouter une livraison", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 350c8a014f..412755e607 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -602,11 +602,11 @@ abstract class AppLocalizations { /// **'Envoyer'** String get adminSend; - /// No description provided for @adminNotificationSended. + /// No description provided for @adminNotificationSent. /// /// In fr, this message translates to: /// **'Notification envoyée'** - String get adminNotificationSended; + String get adminNotificationSent; /// No description provided for @adminFailedToSendNotification. /// @@ -986,12 +986,6 @@ abstract class AppLocalizations { /// **'Déc.'** String get advertMonthDec; - /// No description provided for @advertModuleDescription. - /// - /// In fr, this message translates to: - /// **'Gérer les annonces'** - String get advertModuleDescription; - /// No description provided for @amapAccounts. /// /// In fr, this message translates to: diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index effbba326a..b51eb5d5cc 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -248,100 +248,98 @@ class AppLocalizationsEn extends AppLocalizations { String get adminVisibilities => 'Visibilities'; @override - String get adminGroupNotification => 'Notification de groupe'; + String get adminGroupNotification => 'Group notifications'; @override String adminNotifyGroup(String groupName) { - return 'Notifier le groupe $groupName'; + return 'Send a notification'; } @override - String get adminTitle => 'Titre'; + String get adminTitle => 'Title'; @override - String get adminContent => 'Contenu'; + String get adminContent => 'Content'; @override - String get adminSend => 'Envoyer'; + String get adminSend => 'Send'; @override - String get adminNotificationSended => 'Notification envoyée'; + String get adminNotificationSent => 'Notification sent'; @override - String get adminFailedToSendNotification => - 'Échec de l\'envoi de la notification'; + String get adminFailedToSendNotification => 'Failed to send notification'; @override - String get adminGroupsManagement => 'Gestion des groupes'; + String get adminGroupsManagement => 'Groups management'; @override - String get adminEditGroup => 'Modifier le groupe'; + String get adminEditGroup => 'Edit group'; @override - String get adminManageMembers => 'Gérer les membres'; + String get adminManageMembers => 'Manage members'; @override String get adminDeleteGroupConfirmation => - 'Êtes-vous sûr de vouloir supprimer ce groupe ?'; + 'Are you sure you want to delete this group?'; @override - String get adminFailedToDeleteGroup => 'Échec de la suppression du groupe'; + String get adminFailedToDeleteGroup => 'Failed to delete group'; @override - String get adminUsersAndGroups => 'Utilisateurs et groupes'; + String get adminUsersAndGroups => 'Users and groups'; @override - String get adminUsersManagement => 'Gestion des utilisateurs'; + String get adminUsersManagement => 'Users management'; @override String get adminUsersManagementDescription => - 'Gérer les utilisateurs de l\'application'; + 'Manage users, groups, and associations'; @override - String get adminManageUserGroups => 'Gérer les groupes d\'utilisateurs'; + String get adminManageUserGroups => 'Manage user groups'; @override - String get adminSendNotificationToGroup => - 'Envoyer une notification à un groupe'; + String get adminSendNotificationToGroup => 'Send notification to group'; @override - String get adminPaiementModule => 'Module de paiement'; + String get adminPaiementModule => 'Payment module'; @override - String get adminPaiement => 'Paiement'; + String get adminPaiement => 'Payment'; @override - String get adminManagePaiementStructures => - 'Gérer les structures du module de paiement'; + String get adminManagePaiementStructures => 'Manage payment structures'; @override String get adminManageUsersAssociationMemberships => - 'Gérer les adhésions des utilisateurs'; + 'Manage users\' association memberships'; @override - String get adminAssociationMembershipsManagement => 'Gestion des adhésions'; + String get adminAssociationMembershipsManagement => + 'Association memberships management'; @override - String get adminChooseGroupManager => 'Groupe gestionnaire de l\'adhésion'; + String get adminChooseGroupManager => + 'Choose a group to manage this membership'; @override - String get adminSelectManager => 'Sélectionner un gestionnaire'; + String get adminSelectManager => 'Select a manager'; @override - String get adminInviteUsers => 'Inviter des utilisateurs'; + String get adminInviteUsers => 'Invite users'; @override - String get adminImportList => 'Importer une liste'; + String get adminImportList => 'Import a list'; @override - String get adminInvitedUsers => 'Utilisateurs invités'; + String get adminInvitedUsers => 'Invited users'; @override - String get adminFailedToInviteUsers => - 'Échec de l\'invitation des utilisateurs'; + String get adminFailedToInviteUsers => 'Failed to invite users'; @override - String get adminDeleteUsers => 'Supprimer des utilisateurs'; + String get adminDeleteUsers => 'Delete users'; @override String get adminAdmin => 'Admin'; @@ -463,9 +461,6 @@ class AppLocalizationsEn extends AppLocalizations { @override String get advertMonthDec => 'Dec'; - @override - String get advertModuleDescription => 'Gérer les annonces'; - @override String get amapAccounts => 'Accounts'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 363ff7dd85..8a13d74910 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -266,7 +266,7 @@ class AppLocalizationsFr extends AppLocalizations { String get adminSend => 'Envoyer'; @override - String get adminNotificationSended => 'Notification envoyée'; + String get adminNotificationSent => 'Notification envoyée'; @override String get adminFailedToSendNotification => @@ -464,9 +464,6 @@ class AppLocalizationsFr extends AppLocalizations { @override String get advertMonthDec => 'Déc.'; - @override - String get advertModuleDescription => 'Gérer les annonces'; - @override String get amapAccounts => 'Comptes'; From aabeba8bed9b6825e65d5aa98f3e775a4c3f8fce Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 9 Aug 2025 23:38:01 +0200 Subject: [PATCH 192/473] typo --- .../group_notifification_page/group_notification_page.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/admin/ui/pages/group_notifification_page/group_notification_page.dart b/lib/admin/ui/pages/group_notifification_page/group_notification_page.dart index 5855a23cb1..587be764c2 100644 --- a/lib/admin/ui/pages/group_notifification_page/group_notification_page.dart +++ b/lib/admin/ui/pages/group_notifification_page/group_notification_page.dart @@ -102,7 +102,7 @@ class GroupNotificationPage extends HookConsumerWidget { displayToastWithContext( TypeMsg.msg, localizeWithContext - .adminNotificationSended, + .adminNotificationSent, ); } else { displayToastWithContext( From df86079467ceadbb54208bc07ec526d7344d1192 Mon Sep 17 00:00:00 2001 From: Thonyk Date: Sat, 9 Aug 2025 23:41:17 +0200 Subject: [PATCH 193/473] fix: groupement bar --- lib/phonebook/ui/components/groupement_bar.dart | 2 +- lib/tools/ui/styleguide/item_chip.dart | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/phonebook/ui/components/groupement_bar.dart b/lib/phonebook/ui/components/groupement_bar.dart index 1b8b695e56..4923417471 100644 --- a/lib/phonebook/ui/components/groupement_bar.dart +++ b/lib/phonebook/ui/components/groupement_bar.dart @@ -104,7 +104,7 @@ class AssociationGroupementBar extends HookConsumerWidget { width: MediaQuery.of(context).size.width, height: scrollDirection == Axis.horizontal ? 40 - : min(associationGroupements.length * 70, 140), + : min(associationGroupements.length * 50, 220), child: ListView.builder( scrollDirection: scrollDirection, itemCount: editable diff --git a/lib/tools/ui/styleguide/item_chip.dart b/lib/tools/ui/styleguide/item_chip.dart index 602827ff67..4713f1c7d9 100644 --- a/lib/tools/ui/styleguide/item_chip.dart +++ b/lib/tools/ui/styleguide/item_chip.dart @@ -22,14 +22,14 @@ class ItemChip extends StatelessWidget { onLongPress: onLongPress, child: Container( margin: scrollDirection == Axis.horizontal - ? EdgeInsets.symmetric(horizontal: 10.0) + ? EdgeInsets.symmetric(horizontal: 5.0) : EdgeInsets.symmetric(vertical: 5.0), padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 20.0), decoration: BoxDecoration( borderRadius: BorderRadius.circular(30.0), color: selected ? Colors.black : Colors.grey.shade200, ), - child: child, + child: Center(child: child), ), ); } From 49287b64dd15f187d6520959d0227b962d796ad4 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sun, 10 Aug 2025 00:06:55 +0200 Subject: [PATCH 194/473] New page --- lib/admin/router.dart | 10 ++++++++++ .../ui/pages/advertisers/advertisers_main_page.dart | 10 ++++++++++ lib/admin/ui/pages/main_page/main_page.dart | 3 +-- 3 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 lib/admin/ui/pages/advertisers/advertisers_main_page.dart diff --git a/lib/admin/router.dart b/lib/admin/router.dart index 0c18a20c7a..777b311605 100644 --- a/lib/admin/router.dart +++ b/lib/admin/router.dart @@ -22,6 +22,8 @@ import 'package:titan/admin/ui/pages/membership/association_membership_detail_pa deferred as association_membership_detail_page; import 'package:titan/admin/ui/pages/membership/add_edit_user_membership_page/add_edit_user_membership_page.dart' deferred as add_edit_user_membership_page; +import 'package:titan/admin/ui/pages/advertisers/advertisers_main_page.dart' + deferred as advertisers_main_page; import 'package:titan/tools/middlewares/authenticated_middleware.dart'; import 'package:titan/tools/middlewares/deferred_middleware.dart'; import 'package:qlevar_router/qlevar_router.dart'; @@ -40,6 +42,7 @@ class AdminRouter { static const String detailAssociationMembership = '/detail_association_membership'; static const String addEditMember = '/add_edit_member'; + static const String advertisers = '/advertisers'; static final Module module = Module( getName: (context) => AppLocalizations.of(context)!.adminAdmin, description: "Gérer les utilisateurs, groupes et structures", @@ -134,6 +137,13 @@ class AdminRouter { ), ], ), + QRoute( + path: advertisers, + builder: () => advertisers_main_page.AdvertisersMainPage(), + middleware: [ + DeferredLoadingMiddleware(advertisers_main_page.loadLibrary), + ], + ), ], ); } diff --git a/lib/admin/ui/pages/advertisers/advertisers_main_page.dart b/lib/admin/ui/pages/advertisers/advertisers_main_page.dart new file mode 100644 index 0000000000..ff5a0857c5 --- /dev/null +++ b/lib/admin/ui/pages/advertisers/advertisers_main_page.dart @@ -0,0 +1,10 @@ +import 'package:flutter/material.dart'; + +class AdvertisersMainPage extends StatelessWidget { + const AdvertisersMainPage({super.key}); + + @override + Widget build(BuildContext context) { + return Text("yo"); + } +} diff --git a/lib/admin/ui/pages/main_page/main_page.dart b/lib/admin/ui/pages/main_page/main_page.dart index 14e8e6a684..9306da3c29 100644 --- a/lib/admin/ui/pages/main_page/main_page.dart +++ b/lib/admin/ui/pages/main_page/main_page.dart @@ -83,8 +83,7 @@ class AdminMainPage extends HookConsumerWidget { ListItem( title: "Annonceurs", subtitle: "Gérer les annonceurs", - onTap: () => - QR.to(AdminRouter.root + AdminRouter.associationMemberships), + onTap: () => QR.to(AdminRouter.root + AdminRouter.advertisers), ), ], ), From f4b13fc1a81d9c8c167cffec34342f1b80df0709 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sun, 10 Aug 2025 00:40:26 +0200 Subject: [PATCH 195/473] remove useless parenthesis --- .../add_edit_structure_page/add_edit_structure_page.dart | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/admin/ui/pages/structure_page/add_edit_structure_page/add_edit_structure_page.dart b/lib/admin/ui/pages/structure_page/add_edit_structure_page/add_edit_structure_page.dart index 8c8a2a78c0..35c1d5b5fd 100644 --- a/lib/admin/ui/pages/structure_page/add_edit_structure_page/add_edit_structure_page.dart +++ b/lib/admin/ui/pages/structure_page/add_edit_structure_page/add_edit_structure_page.dart @@ -41,9 +41,7 @@ class AddEditStructurePage extends HookConsumerWidget { allAssociationMembershipListProvider, ); final currentMembership = useState( - (isEdit) - ? structure.associationMembership - : AssociationMembership.empty(), + isEdit ? structure.associationMembership : AssociationMembership.empty(), ); void displayToastWithContext(TypeMsg type, String msg) { displayToast(context, type, msg); @@ -102,7 +100,7 @@ class AddEditStructurePage extends HookConsumerWidget { }, ), const SizedBox(height: 20), - (isEdit) + isEdit ? Column( children: [ Text( From f08c03caaea855c22a0479325d1ab2efec74e430 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sun, 10 Aug 2025 02:00:54 +0200 Subject: [PATCH 196/473] Advertiser management --- lib/admin/router.dart | 9 -- ...main_page.dart => advertisers_modale.dart} | 4 +- .../advertisers/load_switch_advertisers.dart | 90 +++++++++++++++++++ lib/admin/ui/pages/main_page/main_page.dart | 38 +++++++- 4 files changed, 129 insertions(+), 12 deletions(-) rename lib/admin/ui/pages/advertisers/{advertisers_main_page.dart => advertisers_modale.dart} (56%) create mode 100644 lib/admin/ui/pages/advertisers/load_switch_advertisers.dart diff --git a/lib/admin/router.dart b/lib/admin/router.dart index 777b311605..13e0abd035 100644 --- a/lib/admin/router.dart +++ b/lib/admin/router.dart @@ -22,8 +22,6 @@ import 'package:titan/admin/ui/pages/membership/association_membership_detail_pa deferred as association_membership_detail_page; import 'package:titan/admin/ui/pages/membership/add_edit_user_membership_page/add_edit_user_membership_page.dart' deferred as add_edit_user_membership_page; -import 'package:titan/admin/ui/pages/advertisers/advertisers_main_page.dart' - deferred as advertisers_main_page; import 'package:titan/tools/middlewares/authenticated_middleware.dart'; import 'package:titan/tools/middlewares/deferred_middleware.dart'; import 'package:qlevar_router/qlevar_router.dart'; @@ -137,13 +135,6 @@ class AdminRouter { ), ], ), - QRoute( - path: advertisers, - builder: () => advertisers_main_page.AdvertisersMainPage(), - middleware: [ - DeferredLoadingMiddleware(advertisers_main_page.loadLibrary), - ], - ), ], ); } diff --git a/lib/admin/ui/pages/advertisers/advertisers_main_page.dart b/lib/admin/ui/pages/advertisers/advertisers_modale.dart similarity index 56% rename from lib/admin/ui/pages/advertisers/advertisers_main_page.dart rename to lib/admin/ui/pages/advertisers/advertisers_modale.dart index ff5a0857c5..a8131e4be1 100644 --- a/lib/admin/ui/pages/advertisers/advertisers_main_page.dart +++ b/lib/admin/ui/pages/advertisers/advertisers_modale.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; -class AdvertisersMainPage extends StatelessWidget { - const AdvertisersMainPage({super.key}); +class AdvertisersModale extends StatelessWidget { + const AdvertisersModale({super.key}); @override Widget build(BuildContext context) { diff --git a/lib/admin/ui/pages/advertisers/load_switch_advertisers.dart b/lib/admin/ui/pages/advertisers/load_switch_advertisers.dart new file mode 100644 index 0000000000..e72f6e589e --- /dev/null +++ b/lib/admin/ui/pages/advertisers/load_switch_advertisers.dart @@ -0,0 +1,90 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:load_switch/load_switch.dart'; +import 'package:titan/admin/class/simple_group.dart'; +import 'package:titan/advert/class/announcer.dart'; +import 'package:titan/advert/providers/announcer_list_provider.dart'; +import 'package:titan/tools/ui/builders/async_child.dart'; + +class LoadSwitchAdvertisers extends ConsumerWidget { + const LoadSwitchAdvertisers({super.key, required this.group}); + final SimpleGroup group; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final announcerListNotifier = ref.watch(announcerListProvider.notifier); + + final announcerList = ref.watch(announcerListProvider); + + return AsyncChild( + value: announcerList, + builder: (context, annoncerList) { + final annoncer = annoncerList + .where((a) => a.groupManagerId == group.id) + .firstOrNull; + final isAnnouncer = annoncerList.any( + (a) => a.groupManagerId == group.id, + ); + return LoadSwitch( + value: isAnnouncer, + future: isAnnouncer + ? () async { + await announcerListNotifier.deleteAnnouncer(annoncer!); + return false; + } + : () async { + await announcerListNotifier.addAnnouncer( + Announcer( + groupManagerId: group.id, + id: '', + name: group.name, + ), + ); + return true; + }, + height: 30, + width: 60, + curveIn: Curves.easeInBack, + curveOut: Curves.easeOutBack, + animationDuration: const Duration(milliseconds: 500), + switchDecoration: (value, _) => BoxDecoration( + color: value + ? Colors.red.withValues(alpha: 0.3) + : Colors.grey.shade200, + borderRadius: BorderRadius.circular(30), + shape: BoxShape.rectangle, + boxShadow: [ + BoxShadow( + color: value + ? Colors.red.withValues(alpha: 0.2) + : Colors.grey.withValues(alpha: 0.2), + spreadRadius: 1, + blurRadius: 3, + offset: const Offset(0, 1), + ), + ], + ), + spinColor: (value) => value ? Colors.red : Colors.grey, + spinStrokeWidth: 2, + thumbDecoration: (value, _) => BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(30), + shape: BoxShape.rectangle, + boxShadow: [ + BoxShadow( + color: value + ? Colors.red.withValues(alpha: 0.2) + : Colors.grey.shade200.withValues(alpha: 0.2), + spreadRadius: 5, + blurRadius: 7, + offset: const Offset(0, 3), + ), + ], + ), + onChange: (v) {}, + onTap: (v) {}, + ); + }, + ); + } +} diff --git a/lib/admin/ui/pages/main_page/main_page.dart b/lib/admin/ui/pages/main_page/main_page.dart index 9306da3c29..ce3fb2334d 100644 --- a/lib/admin/ui/pages/main_page/main_page.dart +++ b/lib/admin/ui/pages/main_page/main_page.dart @@ -1,12 +1,23 @@ import 'package:flutter/material.dart'; +import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/admin/admin.dart'; +import 'package:titan/admin/providers/all_groups_list_provider.dart'; import 'package:titan/admin/router.dart'; +import 'package:titan/admin/ui/pages/advertisers/load_switch_advertisers.dart'; import 'package:titan/admin/ui/pages/users_management_page/users_management_page.dart'; +import 'package:titan/advert/class/announcer.dart'; +import 'package:titan/advert/providers/all_announcer_list_provider.dart'; +import 'package:titan/advert/providers/announcer_list_provider.dart'; import 'package:titan/l10n/app_localizations.dart'; +import 'package:titan/tools/constants.dart'; +import 'package:titan/tools/functions.dart'; +import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; import 'package:titan/tools/ui/styleguide/list_item.dart'; +import 'package:titan/tools/ui/styleguide/list_item_template.dart'; +import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; import 'package:titan/user/providers/user_list_provider.dart'; @@ -18,6 +29,7 @@ class AdminMainPage extends HookConsumerWidget { ref.watch(userList); final localizeWithContext = AppLocalizations.of(context)!; + final groupList = ref.watch(allGroupList); return AdminTemplate( child: Padding( @@ -83,7 +95,31 @@ class AdminMainPage extends HookConsumerWidget { ListItem( title: "Annonceurs", subtitle: "Gérer les annonceurs", - onTap: () => QR.to(AdminRouter.root + AdminRouter.advertisers), + onTap: () async { + await showCustomBottomModal( + context: context, + ref: ref, + modal: BottomModalTemplate( + title: "Annonceurs", + child: ConstrainedBox( + constraints: const BoxConstraints(maxHeight: 500), + child: SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: Column( + children: [ + ...groupList.map((group) { + return ListItemTemplate( + title: group.name, + trailing: LoadSwitchAdvertisers(group: group), + ); + }), + ], + ), + ), + ), + ), + ); + }, ), ], ), From 5657caff229d6c0ddddd6f0f3c5e4f6bb80647fd Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sun, 10 Aug 2025 02:01:18 +0200 Subject: [PATCH 197/473] remove useless imports --- lib/admin/ui/pages/main_page/main_page.dart | 8 -------- 1 file changed, 8 deletions(-) diff --git a/lib/admin/ui/pages/main_page/main_page.dart b/lib/admin/ui/pages/main_page/main_page.dart index ce3fb2334d..3085997749 100644 --- a/lib/admin/ui/pages/main_page/main_page.dart +++ b/lib/admin/ui/pages/main_page/main_page.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/admin/admin.dart'; @@ -7,17 +6,10 @@ import 'package:titan/admin/providers/all_groups_list_provider.dart'; import 'package:titan/admin/router.dart'; import 'package:titan/admin/ui/pages/advertisers/load_switch_advertisers.dart'; import 'package:titan/admin/ui/pages/users_management_page/users_management_page.dart'; -import 'package:titan/advert/class/announcer.dart'; -import 'package:titan/advert/providers/all_announcer_list_provider.dart'; -import 'package:titan/advert/providers/announcer_list_provider.dart'; import 'package:titan/l10n/app_localizations.dart'; -import 'package:titan/tools/constants.dart'; -import 'package:titan/tools/functions.dart'; -import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; import 'package:titan/tools/ui/styleguide/list_item.dart'; import 'package:titan/tools/ui/styleguide/list_item_template.dart'; -import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; import 'package:titan/user/providers/user_list_provider.dart'; From b3855bbd814c1b0e0ab0519e5a973631c05dad74 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sun, 10 Aug 2025 02:05:06 +0200 Subject: [PATCH 198/473] rename --- lib/admin/ui/pages/advertisers/advertisers_modale.dart | 10 ---------- .../load_switch_announcers.dart} | 0 lib/admin/ui/pages/main_page/main_page.dart | 2 +- 3 files changed, 1 insertion(+), 11 deletions(-) delete mode 100644 lib/admin/ui/pages/advertisers/advertisers_modale.dart rename lib/admin/ui/pages/{advertisers/load_switch_advertisers.dart => announcers/load_switch_announcers.dart} (100%) diff --git a/lib/admin/ui/pages/advertisers/advertisers_modale.dart b/lib/admin/ui/pages/advertisers/advertisers_modale.dart deleted file mode 100644 index a8131e4be1..0000000000 --- a/lib/admin/ui/pages/advertisers/advertisers_modale.dart +++ /dev/null @@ -1,10 +0,0 @@ -import 'package:flutter/material.dart'; - -class AdvertisersModale extends StatelessWidget { - const AdvertisersModale({super.key}); - - @override - Widget build(BuildContext context) { - return Text("yo"); - } -} diff --git a/lib/admin/ui/pages/advertisers/load_switch_advertisers.dart b/lib/admin/ui/pages/announcers/load_switch_announcers.dart similarity index 100% rename from lib/admin/ui/pages/advertisers/load_switch_advertisers.dart rename to lib/admin/ui/pages/announcers/load_switch_announcers.dart diff --git a/lib/admin/ui/pages/main_page/main_page.dart b/lib/admin/ui/pages/main_page/main_page.dart index 3085997749..8e2e9cd8fe 100644 --- a/lib/admin/ui/pages/main_page/main_page.dart +++ b/lib/admin/ui/pages/main_page/main_page.dart @@ -4,7 +4,7 @@ import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/admin/admin.dart'; import 'package:titan/admin/providers/all_groups_list_provider.dart'; import 'package:titan/admin/router.dart'; -import 'package:titan/admin/ui/pages/advertisers/load_switch_advertisers.dart'; +import 'package:titan/admin/ui/pages/announcers/load_switch_announcers.dart'; import 'package:titan/admin/ui/pages/users_management_page/users_management_page.dart'; import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; From 8b0f026dc2f6c00d69904278eaf56ea8c4d8dd69 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sun, 10 Aug 2025 02:11:02 +0200 Subject: [PATCH 199/473] fix : translations --- lib/l10n/app_en.arb | 3 +++ lib/l10n/app_fr.arb | 3 +++ lib/l10n/app_localizations.dart | 18 ++++++++++++++++++ lib/l10n/app_localizations_en.dart | 9 +++++++++ lib/l10n/app_localizations_fr.dart | 9 +++++++++ 5 files changed, 42 insertions(+) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 5b2778aa25..2bc514d55d 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -109,6 +109,9 @@ "adminFailedToInviteUsers": "Failed to invite users", "adminDeleteUsers": "Delete users", "adminAdmin" : "Admin", + "adminAdverts": "Adverts", + "adminAnnouncers": "Announcers", + "adminManageAnnouncers" : "Manage announcers", "advertAdd": "Add", "advertAddedAdvert": "Advert published", "advertAddedAnnouncer": "Announcer added", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 484f07a553..72dc874031 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -117,6 +117,9 @@ "adminFailedToInviteUsers": "Échec de l'invitation des utilisateurs", "adminDeleteUsers": "Supprimer des utilisateurs", "adminAdmin": "Admin", + "adminAdverts": "Annonces", + "adminAnnouncers": "Annonceurs", + "adminManageAnnouncers" : "Gérer les annonceurs", "advertAdd": "Ajouter", "advertAddedAdvert": "Annonce publiée", "advertAddedAnnouncer": "Annonceur ajouté", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 412755e607..7a7434aa14 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -752,6 +752,24 @@ abstract class AppLocalizations { /// **'Admin'** String get adminAdmin; + /// No description provided for @adminAdverts. + /// + /// In fr, this message translates to: + /// **'Annonces'** + String get adminAdverts; + + /// No description provided for @adminAnnouncers. + /// + /// In fr, this message translates to: + /// **'Annonceurs'** + String get adminAnnouncers; + + /// No description provided for @adminManageAnnouncers. + /// + /// In fr, this message translates to: + /// **'Gérer les annonceurs'** + String get adminManageAnnouncers; + /// No description provided for @advertAdd. /// /// In fr, this message translates to: diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index b51eb5d5cc..a2a01e447d 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -344,6 +344,15 @@ class AppLocalizationsEn extends AppLocalizations { @override String get adminAdmin => 'Admin'; + @override + String get adminAdverts => 'Adverts'; + + @override + String get adminAnnouncers => 'Announcers'; + + @override + String get adminManageAnnouncers => 'Manage announcers'; + @override String get advertAdd => 'Add'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 8a13d74910..9dd945004c 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -347,6 +347,15 @@ class AppLocalizationsFr extends AppLocalizations { @override String get adminAdmin => 'Admin'; + @override + String get adminAdverts => 'Annonces'; + + @override + String get adminAnnouncers => 'Annonceurs'; + + @override + String get adminManageAnnouncers => 'Gérer les annonceurs'; + @override String get advertAdd => 'Ajouter'; From 8d743c5506b075f8d93c5385bbace3bc242a3fa7 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sun, 10 Aug 2025 02:16:52 +0200 Subject: [PATCH 200/473] Easier input --- lib/admin/tools/functions.dart | 15 --------------- .../users_management_page/add_user_modal.dart | 3 +-- 2 files changed, 1 insertion(+), 17 deletions(-) delete mode 100644 lib/admin/tools/functions.dart diff --git a/lib/admin/tools/functions.dart b/lib/admin/tools/functions.dart deleted file mode 100644 index c6714b5d88..0000000000 --- a/lib/admin/tools/functions.dart +++ /dev/null @@ -1,15 +0,0 @@ -List getMailListFromCSV(String fileContent) { - final lines = fileContent.split('\n'); - final List emails = []; - - for (var i = 0; i < lines.length; i++) { - final line = lines[i].trim(); - if (line.isEmpty) continue; - - final columns = line.split(','); - - final email = columns[0].trim(); - emails.add(email); - } - return emails; -} diff --git a/lib/admin/ui/pages/users_management_page/add_user_modal.dart b/lib/admin/ui/pages/users_management_page/add_user_modal.dart index 07437e84e6..8f3f6efaaf 100644 --- a/lib/admin/ui/pages/users_management_page/add_user_modal.dart +++ b/lib/admin/ui/pages/users_management_page/add_user_modal.dart @@ -5,7 +5,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/providers/user_invitation_provider.dart'; -import 'package:titan/admin/tools/functions.dart'; import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; @@ -44,7 +43,7 @@ class AddUsersModalContent extends HookConsumerWidget { if (file.path != null) { final fileContent = await File(file.path!).readAsString(); - mailList.value = getMailListFromCSV(fileContent); + mailList.value = fileContent.split('\n'); } } }, From a21d25b8e25d951854a735bf2abb9a859af886b1 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sun, 10 Aug 2025 02:20:39 +0200 Subject: [PATCH 201/473] fix context --- lib/admin/ui/pages/users_management_page/add_user_modal.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/admin/ui/pages/users_management_page/add_user_modal.dart b/lib/admin/ui/pages/users_management_page/add_user_modal.dart index 8f3f6efaaf..fffb5e481c 100644 --- a/lib/admin/ui/pages/users_management_page/add_user_modal.dart +++ b/lib/admin/ui/pages/users_management_page/add_user_modal.dart @@ -25,6 +25,8 @@ class AddUsersModalContent extends HookConsumerWidget { final localizeWithContext = AppLocalizations.of(context)!; + final navigatorWithContext = Navigator.of(context); + return BottomModalTemplate( title: localizeWithContext.adminInviteUsers, child: Column( @@ -70,7 +72,7 @@ class AddUsersModalContent extends HookConsumerWidget { localizeWithContext.adminFailedToInviteUsers, ); } - // popWithContext(); + navigatorWithContext.pop(); }); }, disabled: selectedFileName.value == null, From ee7b9406da7c28d23882e304537b3e45d68eb2a9 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sun, 10 Aug 2025 02:22:46 +0200 Subject: [PATCH 202/473] fix : wrong text constant --- .../ui/pages/users_management_page/users_management_page.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/admin/ui/pages/users_management_page/users_management_page.dart b/lib/admin/ui/pages/users_management_page/users_management_page.dart index f2a742f493..f02a7ee3cc 100644 --- a/lib/admin/ui/pages/users_management_page/users_management_page.dart +++ b/lib/admin/ui/pages/users_management_page/users_management_page.dart @@ -15,7 +15,7 @@ class UsersManagementPage extends HookConsumerWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Button( - text: localizeWithContext.adminEdit, + text: localizeWithContext.adminAdd, onPressed: () async { Navigator.pop(context); await showCustomBottomModal( From e950b0dea19f82ed543dc56c5f7bb0761df42bc2 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sun, 10 Aug 2025 13:55:08 +0200 Subject: [PATCH 203/473] feat : add prefered module feature --- .../providers/navbar_module_list.dart | 57 ++++++++++++----- lib/navigation/ui/all_module_page.dart | 61 +++++++++++++++---- .../prefered_module_root_list_provider.dart | 56 +++++++++++++++++ 3 files changed, 147 insertions(+), 27 deletions(-) create mode 100644 lib/tools/providers/prefered_module_root_list_provider.dart diff --git a/lib/navigation/providers/navbar_module_list.dart b/lib/navigation/providers/navbar_module_list.dart index 046ce728f1..33191f1dc7 100644 --- a/lib/navigation/providers/navbar_module_list.dart +++ b/lib/navigation/providers/navbar_module_list.dart @@ -1,34 +1,61 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/navigation/class/module.dart'; import 'package:titan/settings/providers/module_list_provider.dart'; +import 'package:titan/tools/providers/prefered_module_root_list_provider.dart'; class ModuleListNotifier extends StateNotifier> { - final int maxNumberOfModules = 2; - ModuleListNotifier(List modules) - : listModule = List.from(modules), - super(modules.take(2).toList()); + final int maxNumberOfModules; + final List allModules; - final List listModule; + ModuleListNotifier( + this.allModules, + List preferedRoots, { + this.maxNumberOfModules = 2, + }) : super(_initState(allModules, preferedRoots, maxNumberOfModules)); - void pushModule(Module module) { - final existingIndex = listModule.indexWhere((m) => m.root == module.root); + static List _initState( + List allModules, + List preferedRoots, + int max, + ) { + final preferredModules = allModules + .where((m) => preferedRoots.contains(m.root)) + .toList(); - if (existingIndex == -1) { - return; + final filled = List.from(preferredModules); + if (filled.length < max) { + for (final m in allModules) { + if (!filled.contains(m)) { + filled.add(m); + if (filled.length == max) break; + } + } } - if (existingIndex < maxNumberOfModules) { - return; + return filled.take(max).toList(); + } + + void pushModule(Module module) { + final updated = List.from(state); + + final idx = updated.indexWhere((m) => m.root == module.root); + if (idx != -1) { + updated.removeAt(idx); + updated.insert(0, module); + } else { + updated.insert(0, module); + if (updated.length > maxNumberOfModules) { + updated.removeLast(); + } } - listModule.removeAt(existingIndex); - listModule.insert(0, module); - state = listModule.take(maxNumberOfModules).toList(); + state = updated; } } final navbarListModuleProvider = StateNotifierProvider>((ref) { final modules = ref.watch(modulesProvider); - return ModuleListNotifier(modules); + final preferedRoots = ref.watch(preferedModuleListRootProvider); + return ModuleListNotifier(modules, preferedRoots); }); diff --git a/lib/navigation/ui/all_module_page.dart b/lib/navigation/ui/all_module_page.dart index 1c7a7f7eb5..b67f90d579 100644 --- a/lib/navigation/ui/all_module_page.dart +++ b/lib/navigation/ui/all_module_page.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/navigation/providers/navbar_module_list.dart'; @@ -9,6 +10,7 @@ import 'package:titan/router.dart'; import 'package:titan/settings/providers/module_list_provider.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/providers/path_forwarding_provider.dart'; +import 'package:titan/tools/providers/prefered_module_root_list_provider.dart'; import 'package:titan/tools/ui/styleguide/list_item.dart'; import 'package:titan/tools/ui/styleguide/searchbar.dart'; import 'package:titan/tools/ui/widgets/top_bar.dart'; @@ -26,6 +28,10 @@ class AllModulePage extends HookConsumerWidget { navbarVisibilityProvider.notifier, ); final scrollController = useScrollController(); + final preferedModuleRootList = ref.watch(preferedModuleListRootProvider); + final preferedModuleRootListNotifier = ref.watch( + preferedModuleListRootProvider.notifier, + ); return Container( color: ColorConstants.background, child: Column( @@ -49,19 +55,50 @@ class AllModulePage extends HookConsumerWidget { ), SizedBox(height: 30), ...modules.map( - (module) => ListItem( - title: module.getName(context), - subtitle: module.description, - onTap: () { - navbarListModuleNotifier.pushModule(module); - final pathForwardingNotifier = ref.watch( - pathForwardingProvider.notifier, - ); - pathForwardingNotifier.forward(module.root); + (module) => Row( + children: [ + GestureDetector( + onTap: () { + if (preferedModuleRootList.contains( + module.root, + )) { + preferedModuleRootListNotifier + .removePreferedModulesRoot(module.root); + } else if (preferedModuleRootList.length < 2) { + preferedModuleRootListNotifier + .addPreferedModulesRoot(module.root); + } + }, + child: HeroIcon( + HeroIcons.star, + style: + preferedModuleRootList.contains(module.root) + ? HeroIconStyle.solid + : HeroIconStyle.outline, + size: 20, + color: + preferedModuleRootList.contains(module.root) + ? Colors.yellow + : Colors.grey, + ), + ), + Expanded( + child: ListItem( + title: module.getName(context), + subtitle: module.description, + onTap: () { + navbarListModuleNotifier.pushModule(module); + final pathForwardingNotifier = ref.watch( + pathForwardingProvider.notifier, + ); + pathForwardingNotifier.forward(module.root); - QR.to(module.root); - navbarVisibilityNotifier.show(); - }, + QR.to(module.root); + navbarVisibilityNotifier.show(); + }, + ), + ), + ], ), ), SizedBox(height: 80), diff --git a/lib/tools/providers/prefered_module_root_list_provider.dart b/lib/tools/providers/prefered_module_root_list_provider.dart new file mode 100644 index 0000000000..5428657e6a --- /dev/null +++ b/lib/tools/providers/prefered_module_root_list_provider.dart @@ -0,0 +1,56 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class PreferedModuleRootListNotifier extends StateNotifier> { + static const preferedModuleRootListKey = 'prefered_modules'; + + PreferedModuleRootListNotifier() : super([]) { + loadPreferedModulesRootList(); + } + + Future loadPreferedModulesRootList() async { + final prefs = await SharedPreferences.getInstance(); + final preferedModuleRootList = prefs.getString(preferedModuleRootListKey); + if (preferedModuleRootList != null && preferedModuleRootList.isNotEmpty) { + state = preferedModuleRootList.split(','); + } else { + state = []; + } + } + + Future addPreferedModulesRoot(String preferedModuleRoot) async { + final prefs = await SharedPreferences.getInstance(); + final currentListStr = prefs.getString(preferedModuleRootListKey); + final currentList = currentListStr != null && currentListStr.isNotEmpty + ? currentListStr.split(',') + : []; + + if (currentList.length >= 2) return; + if (!currentList.contains(preferedModuleRoot)) { + final updatedList = [...currentList, preferedModuleRoot]; + prefs.setString(preferedModuleRootListKey, updatedList.join(',')); + state = updatedList; + } + } + + Future removePreferedModulesRoot(String preferedModuleRoot) async { + final prefs = await SharedPreferences.getInstance(); + final currentListStr = prefs.getString(preferedModuleRootListKey); + final currentList = currentListStr != null && currentListStr.isNotEmpty + ? currentListStr.split(',') + : []; + + if (currentList.contains(preferedModuleRoot)) { + final updatedList = currentList + .where((item) => item != preferedModuleRoot) + .toList(); + prefs.setString(preferedModuleRootListKey, updatedList.join(',')); + state = updatedList; + } + } +} + +final preferedModuleListRootProvider = + StateNotifierProvider>( + (ref) => PreferedModuleRootListNotifier(), + ); From 76ef53d3e05636e8969a6642e5c41c2a1d0adbb5 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sun, 10 Aug 2025 14:00:33 +0200 Subject: [PATCH 204/473] feat : new icon --- lib/navigation/ui/all_module_page.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/navigation/ui/all_module_page.dart b/lib/navigation/ui/all_module_page.dart index b67f90d579..de08e3f902 100644 --- a/lib/navigation/ui/all_module_page.dart +++ b/lib/navigation/ui/all_module_page.dart @@ -70,7 +70,7 @@ class AllModulePage extends HookConsumerWidget { } }, child: HeroIcon( - HeroIcons.star, + HeroIcons.bookmark, style: preferedModuleRootList.contains(module.root) ? HeroIconStyle.solid @@ -78,7 +78,7 @@ class AllModulePage extends HookConsumerWidget { size: 20, color: preferedModuleRootList.contains(module.root) - ? Colors.yellow + ? ColorConstants.main : Colors.grey, ), ), From 5d97709ebda773227752fccd718775300b1ba2a4 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sun, 10 Aug 2025 14:03:03 +0200 Subject: [PATCH 205/473] Color fix --- lib/navigation/ui/all_module_page.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/navigation/ui/all_module_page.dart b/lib/navigation/ui/all_module_page.dart index de08e3f902..ffbc739781 100644 --- a/lib/navigation/ui/all_module_page.dart +++ b/lib/navigation/ui/all_module_page.dart @@ -79,7 +79,7 @@ class AllModulePage extends HookConsumerWidget { color: preferedModuleRootList.contains(module.root) ? ColorConstants.main - : Colors.grey, + : ColorConstants.secondary, ), ), Expanded( From 165d58acd010806b6378eefbb440e6a106a83d94 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+foucblg@users.noreply.github.com> Date: Sun, 10 Aug 2025 14:25:25 +0200 Subject: [PATCH 206/473] navbar height (#20) * navbar height # Conflicts: # lib/navigation/ui/all_module_page.dart * fix : rebase * fix : rebase error --------- Co-authored-by: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> --- lib/navigation/ui/all_module_page.dart | 133 ++++++++++----------- lib/navigation/ui/navigation_template.dart | 2 +- 2 files changed, 67 insertions(+), 68 deletions(-) diff --git a/lib/navigation/ui/all_module_page.dart b/lib/navigation/ui/all_module_page.dart index ffbc739781..fb1e173fb4 100644 --- a/lib/navigation/ui/all_module_page.dart +++ b/lib/navigation/ui/all_module_page.dart @@ -12,7 +12,6 @@ import 'package:titan/tools/constants.dart'; import 'package:titan/tools/providers/path_forwarding_provider.dart'; import 'package:titan/tools/providers/prefered_module_root_list_provider.dart'; import 'package:titan/tools/ui/styleguide/list_item.dart'; -import 'package:titan/tools/ui/styleguide/searchbar.dart'; import 'package:titan/tools/ui/widgets/top_bar.dart'; class AllModulePage extends HookConsumerWidget { @@ -34,81 +33,81 @@ class AllModulePage extends HookConsumerWidget { ); return Container( color: ColorConstants.background, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - const SizedBox(height: 40), - TopBar(root: AppRouter.allModules), - Expanded( - child: ScrollToHideNavbar( - controller: scrollController, - child: SingleChildScrollView( + child: SafeArea( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + TopBar(root: AppRouter.allModules), + Expanded( + child: ScrollToHideNavbar( controller: scrollController, - physics: const BouncingScrollPhysics(), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 20.0), - child: Column( - children: [ - CustomSearchBar( - onSearch: (String query) {}, - onFilter: () {}, - ), - SizedBox(height: 30), - ...modules.map( - (module) => Row( - children: [ - GestureDetector( - onTap: () { - if (preferedModuleRootList.contains( - module.root, - )) { - preferedModuleRootListNotifier - .removePreferedModulesRoot(module.root); - } else if (preferedModuleRootList.length < 2) { - preferedModuleRootListNotifier - .addPreferedModulesRoot(module.root); - } - }, - child: HeroIcon( - HeroIcons.bookmark, - style: - preferedModuleRootList.contains(module.root) - ? HeroIconStyle.solid - : HeroIconStyle.outline, - size: 20, - color: - preferedModuleRootList.contains(module.root) - ? ColorConstants.main - : ColorConstants.secondary, - ), - ), - Expanded( - child: ListItem( - title: module.getName(context), - subtitle: module.description, + child: SingleChildScrollView( + physics: const BouncingScrollPhysics(), + controller: scrollController, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + child: Column( + children: [ + ...modules.map( + (module) => Row( + children: [ + GestureDetector( onTap: () { - navbarListModuleNotifier.pushModule(module); - final pathForwardingNotifier = ref.watch( - pathForwardingProvider.notifier, - ); - pathForwardingNotifier.forward(module.root); - - QR.to(module.root); - navbarVisibilityNotifier.show(); + if (preferedModuleRootList.contains( + module.root, + )) { + preferedModuleRootListNotifier + .removePreferedModulesRoot(module.root); + } else if (preferedModuleRootList.length < + 2) { + preferedModuleRootListNotifier + .addPreferedModulesRoot(module.root); + } }, + child: HeroIcon( + HeroIcons.bookmark, + style: + preferedModuleRootList.contains( + module.root, + ) + ? HeroIconStyle.solid + : HeroIconStyle.outline, + size: 20, + color: + preferedModuleRootList.contains( + module.root, + ) + ? ColorConstants.main + : ColorConstants.secondary, + ), + ), + Expanded( + child: ListItem( + title: module.getName(context), + subtitle: module.description, + onTap: () { + navbarListModuleNotifier.pushModule(module); + final pathForwardingNotifier = ref.watch( + pathForwardingProvider.notifier, + ); + pathForwardingNotifier.forward(module.root); + + QR.to(module.root); + navbarVisibilityNotifier.show(); + }, + ), ), - ), - ], + ], + ), ), - ), - SizedBox(height: 80), - ], + ], + ), ), ), ), ), - ), - ], + ], + ), ), ); } diff --git a/lib/navigation/ui/navigation_template.dart b/lib/navigation/ui/navigation_template.dart index 65b40fe4a9..fa08444fd7 100644 --- a/lib/navigation/ui/navigation_template.dart +++ b/lib/navigation/ui/navigation_template.dart @@ -60,7 +60,7 @@ class NavigationTemplate extends HookConsumerWidget { if (pathForwarding.isLoggedIn) Positioned( left: 0, - bottom: 20, + bottom: 0, right: 0, child: Consumer( builder: (context, ref, child) { From 8051427431a73a858edd7a7214022d104412bf02 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+foucblg@users.noreply.github.com> Date: Sun, 10 Aug 2025 15:05:22 +0200 Subject: [PATCH 207/473] Variables for module description (#21) * feat : variables for description * Translations * File with missing translations * file * gitignore missing traslation file * Delete wrong file --------- Co-authored-by: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> --- .gitignore | 3 + l10n.yaml | 1 + lib/admin/router.dart | 5 +- lib/advert/router.dart | 3 +- lib/amap/router.dart | 5 +- lib/booking/router.dart | 3 +- lib/centralisation/router.dart | 4 +- lib/cinema/router.dart | 3 +- lib/event/router.dart | 3 +- lib/feed/router.dart | 3 +- lib/flappybird/router.dart | 3 +- lib/home/router.dart | 3 +- lib/l10n/app_en.arb | 22 ++++ lib/l10n/app_fr.arb | 22 ++++ lib/l10n/app_localizations.dart | 132 +++++++++++++++++++++ lib/l10n/app_localizations_en.dart | 67 +++++++++++ lib/l10n/app_localizations_fr.dart | 83 +++++++++++++ lib/loan/router.dart | 3 +- lib/navigation/class/module.dart | 8 +- lib/navigation/ui/all_module_page.dart | 2 +- lib/navigation/ui/navigation_template.dart | 5 +- lib/paiement/router.dart | 3 +- lib/ph/router.dart | 3 +- lib/phonebook/router.dart | 3 +- lib/purchases/router.dart | 3 +- lib/raffle/router.dart | 3 +- lib/recommendation/router.dart | 4 +- lib/seed-library/router.dart | 3 +- lib/settings/router.dart | 3 +- lib/super_admin/router.dart | 2 +- lib/tools/ui/styleguide/router.dart | 3 +- lib/vote/router.dart | 3 +- 32 files changed, 384 insertions(+), 32 deletions(-) diff --git a/.gitignore b/.gitignore index 3a2448718e..4a49a3c9fb 100644 --- a/.gitignore +++ b/.gitignore @@ -63,3 +63,6 @@ coverage/ # Platforms not supported by this project /windows/ + +# Missing translations +missing.txt \ No newline at end of file diff --git a/l10n.yaml b/l10n.yaml index 20442b9d8a..c2f3c4ffd7 100644 --- a/l10n.yaml +++ b/l10n.yaml @@ -1,3 +1,4 @@ arb-dir: lib/l10n template-arb-file: app_fr.arb output-localization-file: app_localizations.dart +untranslated-messages-file: missing.txt diff --git a/lib/admin/router.dart b/lib/admin/router.dart index 13e0abd035..20f6db9aac 100644 --- a/lib/admin/router.dart +++ b/lib/admin/router.dart @@ -42,8 +42,9 @@ class AdminRouter { static const String addEditMember = '/add_edit_member'; static const String advertisers = '/advertisers'; static final Module module = Module( - getName: (context) => AppLocalizations.of(context)!.adminAdmin, - description: "Gérer les utilisateurs, groupes et structures", + getName: (context) => AppLocalizations.of(context)!.moduleAdmin, + getDescription: (context) => + AppLocalizations.of(context)!.moduleAdminDescription, root: AdminRouter.root, ); diff --git a/lib/advert/router.dart b/lib/advert/router.dart index 7c48218c5b..a86101a78f 100644 --- a/lib/advert/router.dart +++ b/lib/advert/router.dart @@ -28,7 +28,8 @@ class AdvertRouter { static const String detail = '/detail'; static final Module module = Module( getName: (context) => AppLocalizations.of(context)!.moduleAdvert, - description: "Gérer les annonces et les annonceurs", + getDescription: (context) => + AppLocalizations.of(context)!.moduleAdvertDescription, root: AdvertRouter.root, ); AdvertRouter(this.ref); diff --git a/lib/amap/router.dart b/lib/amap/router.dart index f0550f0ff4..80c43189ea 100644 --- a/lib/amap/router.dart +++ b/lib/amap/router.dart @@ -35,8 +35,9 @@ class AmapRouter { static const String presentation = '/presentation'; static const String addEditProduct = '/add_edit_product'; static final Module module = Module( - getName: (context) => AppLocalizations.of(context)!.amapAmap, - description: "Gérer les livraisons et les produits", + getName: (context) => AppLocalizations.of(context)!.moduleAmap, + getDescription: (context) => + AppLocalizations.of(context)!.moduleAmapDescription, root: AmapRouter.root, ); AmapRouter(this.ref); diff --git a/lib/booking/router.dart b/lib/booking/router.dart index 8819f3ffb3..7821bb328b 100644 --- a/lib/booking/router.dart +++ b/lib/booking/router.dart @@ -33,7 +33,8 @@ class BookingRouter { static const String room = '/room'; static final Module module = Module( getName: (context) => AppLocalizations.of(context)!.moduleBooking, - description: "Gérer les réservations, les salles et les managers", + getDescription: (context) => + AppLocalizations.of(context)!.moduleBookingDescription, root: BookingRouter.root, ); BookingRouter(this.ref); diff --git a/lib/centralisation/router.dart b/lib/centralisation/router.dart index 74e588317d..69d90b9d67 100644 --- a/lib/centralisation/router.dart +++ b/lib/centralisation/router.dart @@ -13,8 +13,8 @@ class CentralisationRouter { static const String root = '/centralisation'; static final Module module = Module( getName: (context) => AppLocalizations.of(context)!.moduleCentralisation, - description: "Gérer la centralisation des données", - + getDescription: (context) => + AppLocalizations.of(context)!.moduleCentralisationDescription, root: CentralisationRouter.root, ); CentralisationRouter(this.ref); diff --git a/lib/cinema/router.dart b/lib/cinema/router.dart index f488a03aa7..be9230bbd6 100644 --- a/lib/cinema/router.dart +++ b/lib/cinema/router.dart @@ -25,7 +25,8 @@ class CinemaRouter { static const String detail = '/detail'; static final Module module = Module( getName: (context) => AppLocalizations.of(context)!.moduleCinema, - description: "Gérer les séances de cinéma", + getDescription: (context) => + AppLocalizations.of(context)!.moduleCinemaDescription, root: CinemaRouter.root, ); CinemaRouter(this.ref); diff --git a/lib/event/router.dart b/lib/event/router.dart index a4ba1d6aee..fd655e1deb 100644 --- a/lib/event/router.dart +++ b/lib/event/router.dart @@ -24,7 +24,8 @@ class EventRouter { static const String detail = '/detail'; static final Module module = Module( getName: (context) => AppLocalizations.of(context)!.moduleEvent, - description: "Gérer les événements et les participants", + getDescription: (context) => + AppLocalizations.of(context)!.moduleEventDescription, root: EventRouter.root, ); EventRouter(this.ref); diff --git a/lib/feed/router.dart b/lib/feed/router.dart index b5fece5628..cf7a72bd57 100644 --- a/lib/feed/router.dart +++ b/lib/feed/router.dart @@ -19,7 +19,8 @@ class FeedRouter { static const String admin = '/admin'; static final Module module = Module( getName: (context) => AppLocalizations.of(context)!.moduleFeed, - description: "Consulter les actualités et mises à jour", + getDescription: (context) => + AppLocalizations.of(context)!.moduleFeedDescription, root: FeedRouter.root, ); diff --git a/lib/flappybird/router.dart b/lib/flappybird/router.dart index a26cdebd02..c3ffc9fa2c 100644 --- a/lib/flappybird/router.dart +++ b/lib/flappybird/router.dart @@ -16,7 +16,8 @@ class FlappyBirdRouter { static const String leaderBoard = '/leaderboard'; static final Module module = Module( getName: (context) => AppLocalizations.of(context)!.moduleFlappyBird, - description: "Jouer à Flappy Bird et consulter le classement", + getDescription: (context) => + AppLocalizations.of(context)!.moduleFlappyBirdDescription, root: FlappyBirdRouter.root, ); FlappyBirdRouter(this.ref); diff --git a/lib/home/router.dart b/lib/home/router.dart index 1ab0935059..f437f5f783 100644 --- a/lib/home/router.dart +++ b/lib/home/router.dart @@ -15,7 +15,8 @@ class HomeRouter { static const String detail = '/detail'; static final Module module = Module( getName: (context) => AppLocalizations.of(context)!.moduleCalendar, - description: "Consulter les événements et les activités", + getDescription: (context) => + AppLocalizations.of(context)!.moduleCalendarDescription, root: HomeRouter.root, ); HomeRouter(this.ref); diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 2bc514d55d..80a3ef892e 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1173,6 +1173,28 @@ "moduleAdmin": "Administration", "moduleOthers": "Others", "modulePayment": "Payment", + "moduleAdvertDescription": "View the latest adverts", + "moduleAmapDescription": "Order your AMAP basket", + "moduleBookingDescription": "Book a room", + "moduleCalendarDescription": "View the calendar of events", + "moduleCentralisationDescription": "Viw all links", + "moduleCinemaDescription": "View the cinema schedule", + "moduleEventDescription": "View events", + "moduleFlappyBirdDescription": "Play Flappy Bird", + "moduleLoanDescription": "See your loans", + "modulePhonebookDescription": "View the phonebook", + "modulePurchasesDescription": "View your purchases", + "moduleRaffleDescription": "View the raffle", + "moduleRecommendationDescription": "View the recommendations", + "moduleSeedLibraryDescription": "View the seed library", + "moduleVoteDescription": "Vote for the campaigns", + "modulePhDescription": "View the PH", + "moduleSettingsDescription": "Manage your settings", + "moduleFeedDescription": "View the latest news", + "moduleStyleGuideDescription": "Style guide for developers", + "moduleAdminDescription": "Administration module for administrators", + "moduleOthersDescription": "Other modules", + "modulePaymentDescription": "Pay and see your transactions", "paiementTopUp": "Top-up", "paiementStoreManagement": "Association management", "paiementDeleteStore": "Delete association", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 72dc874031..d0e8de078f 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -1159,27 +1159,49 @@ "voteWarning": "Attention", "voteWarningMessage": "La sélection ne sera pas sauvegardée.\nVoulez-vous continuer ?", "moduleAdvert": "Annonce", + "moduleAdvertDescription": "Gérer les annonces", "moduleAmap": "AMAP", + "moduleAmapDescription": "Gérer les livraisons et les produits", "moduleBooking": "Réservation", + "moduleBookingDescription": "Gérer les réservations, les salles et les managers", "moduleCalendar": "Calendrier", + "moduleCalendarDescription": "Consulter les événements et les activités", "moduleCentralisation": "Centralisation", + "moduleCentralisationDescription": "Gérer la centralisation des données", "moduleCinema": "Cinéma", + "moduleCinemaDescription": "Gérer les séances de cinéma", "moduleEvent": "Événement", + "moduleEventDescription": "Gérer les événements et les participants", "moduleFlappyBird": "Flappy Bird", + "moduleFlappyBirdDescription": "Jouer à Flappy Bird et consulter le classement", "moduleLoan": "Prêt", + "moduleLoanDescription": "Gérer les prêts et les articles", "modulePhonebook": "Annuaire", + "modulePhonebookDescription": "Gérer les associations, les membres et les administrateurs", "modulePurchases": "Achats", + "modulePurchasesDescription": "Gérer les achats, les tickets et l'historique", "moduleRaffle": "Tombola", + "moduleRaffleDescription": "Gérer les tombolas, les prix et les tickets", "moduleRecommendation": "Bons plans", + "moduleRecommendationDescription": "Gérer les recommandations, les informations et les administrateurs", "moduleSeedLibrary": "Grainothèque", + "moduleSeedLibraryDescription": "Gérer les graines, les espèces et les stocks", "moduleVote": "Vote", + "moduleVoteDescription": "Gérer les votes, les sections et les candidats", "modulePh": "PH", + "modulePhDescription": "Gérer les PH, les formulaires et les administrateurs", "moduleSettings": "Paramètres", + "moduleSettingsDescription": "Gérer les paramètres de l'application", "moduleFeed": "Feed", + "moduleFeedDescription": "Consulter les actualités et mises à jour", "moduleStyleGuide": "StyleGuide", + "moduleStyleGuideDescription": "Explore the UI components and styles used in Titan", "moduleAdmin": "Adminitration", + "moduleAdminDescription": "Gérer les utilisateurs, groupes et structures", "moduleOthers": "Autres", + "moduleOthersDescription": "Afficher les autres modules", "modulePayment": "Paiement", + "modulePaymentDescription": "Gérer les paiements, les statistiques et les appareils", "paiementTopUp" : "Recharge", "paiementStoreManagement" : "Gestion des associations", "paiementDeleteStore": "Supprimer l'association", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 7a7434aa14..bb0272a656 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -6962,132 +6962,264 @@ abstract class AppLocalizations { /// **'Annonce'** String get moduleAdvert; + /// No description provided for @moduleAdvertDescription. + /// + /// In fr, this message translates to: + /// **'Gérer les annonces'** + String get moduleAdvertDescription; + /// No description provided for @moduleAmap. /// /// In fr, this message translates to: /// **'AMAP'** String get moduleAmap; + /// No description provided for @moduleAmapDescription. + /// + /// In fr, this message translates to: + /// **'Gérer les livraisons et les produits'** + String get moduleAmapDescription; + /// No description provided for @moduleBooking. /// /// In fr, this message translates to: /// **'Réservation'** String get moduleBooking; + /// No description provided for @moduleBookingDescription. + /// + /// In fr, this message translates to: + /// **'Gérer les réservations, les salles et les managers'** + String get moduleBookingDescription; + /// No description provided for @moduleCalendar. /// /// In fr, this message translates to: /// **'Calendrier'** String get moduleCalendar; + /// No description provided for @moduleCalendarDescription. + /// + /// In fr, this message translates to: + /// **'Consulter les événements et les activités'** + String get moduleCalendarDescription; + /// No description provided for @moduleCentralisation. /// /// In fr, this message translates to: /// **'Centralisation'** String get moduleCentralisation; + /// No description provided for @moduleCentralisationDescription. + /// + /// In fr, this message translates to: + /// **'Gérer la centralisation des données'** + String get moduleCentralisationDescription; + /// No description provided for @moduleCinema. /// /// In fr, this message translates to: /// **'Cinéma'** String get moduleCinema; + /// No description provided for @moduleCinemaDescription. + /// + /// In fr, this message translates to: + /// **'Gérer les séances de cinéma'** + String get moduleCinemaDescription; + /// No description provided for @moduleEvent. /// /// In fr, this message translates to: /// **'Événement'** String get moduleEvent; + /// No description provided for @moduleEventDescription. + /// + /// In fr, this message translates to: + /// **'Gérer les événements et les participants'** + String get moduleEventDescription; + /// No description provided for @moduleFlappyBird. /// /// In fr, this message translates to: /// **'Flappy Bird'** String get moduleFlappyBird; + /// No description provided for @moduleFlappyBirdDescription. + /// + /// In fr, this message translates to: + /// **'Jouer à Flappy Bird et consulter le classement'** + String get moduleFlappyBirdDescription; + /// No description provided for @moduleLoan. /// /// In fr, this message translates to: /// **'Prêt'** String get moduleLoan; + /// No description provided for @moduleLoanDescription. + /// + /// In fr, this message translates to: + /// **'Gérer les prêts et les articles'** + String get moduleLoanDescription; + /// No description provided for @modulePhonebook. /// /// In fr, this message translates to: /// **'Annuaire'** String get modulePhonebook; + /// No description provided for @modulePhonebookDescription. + /// + /// In fr, this message translates to: + /// **'Gérer les associations, les membres et les administrateurs'** + String get modulePhonebookDescription; + /// No description provided for @modulePurchases. /// /// In fr, this message translates to: /// **'Achats'** String get modulePurchases; + /// No description provided for @modulePurchasesDescription. + /// + /// In fr, this message translates to: + /// **'Gérer les achats, les tickets et l\'historique'** + String get modulePurchasesDescription; + /// No description provided for @moduleRaffle. /// /// In fr, this message translates to: /// **'Tombola'** String get moduleRaffle; + /// No description provided for @moduleRaffleDescription. + /// + /// In fr, this message translates to: + /// **'Gérer les tombolas, les prix et les tickets'** + String get moduleRaffleDescription; + /// No description provided for @moduleRecommendation. /// /// In fr, this message translates to: /// **'Bons plans'** String get moduleRecommendation; + /// No description provided for @moduleRecommendationDescription. + /// + /// In fr, this message translates to: + /// **'Gérer les recommandations, les informations et les administrateurs'** + String get moduleRecommendationDescription; + /// No description provided for @moduleSeedLibrary. /// /// In fr, this message translates to: /// **'Grainothèque'** String get moduleSeedLibrary; + /// No description provided for @moduleSeedLibraryDescription. + /// + /// In fr, this message translates to: + /// **'Gérer les graines, les espèces et les stocks'** + String get moduleSeedLibraryDescription; + /// No description provided for @moduleVote. /// /// In fr, this message translates to: /// **'Vote'** String get moduleVote; + /// No description provided for @moduleVoteDescription. + /// + /// In fr, this message translates to: + /// **'Gérer les votes, les sections et les candidats'** + String get moduleVoteDescription; + /// No description provided for @modulePh. /// /// In fr, this message translates to: /// **'PH'** String get modulePh; + /// No description provided for @modulePhDescription. + /// + /// In fr, this message translates to: + /// **'Gérer les PH, les formulaires et les administrateurs'** + String get modulePhDescription; + /// No description provided for @moduleSettings. /// /// In fr, this message translates to: /// **'Paramètres'** String get moduleSettings; + /// No description provided for @moduleSettingsDescription. + /// + /// In fr, this message translates to: + /// **'Gérer les paramètres de l\'application'** + String get moduleSettingsDescription; + /// No description provided for @moduleFeed. /// /// In fr, this message translates to: /// **'Feed'** String get moduleFeed; + /// No description provided for @moduleFeedDescription. + /// + /// In fr, this message translates to: + /// **'Consulter les actualités et mises à jour'** + String get moduleFeedDescription; + /// No description provided for @moduleStyleGuide. /// /// In fr, this message translates to: /// **'StyleGuide'** String get moduleStyleGuide; + /// No description provided for @moduleStyleGuideDescription. + /// + /// In fr, this message translates to: + /// **'Explore the UI components and styles used in Titan'** + String get moduleStyleGuideDescription; + /// No description provided for @moduleAdmin. /// /// In fr, this message translates to: /// **'Adminitration'** String get moduleAdmin; + /// No description provided for @moduleAdminDescription. + /// + /// In fr, this message translates to: + /// **'Gérer les utilisateurs, groupes et structures'** + String get moduleAdminDescription; + /// No description provided for @moduleOthers. /// /// In fr, this message translates to: /// **'Autres'** String get moduleOthers; + /// No description provided for @moduleOthersDescription. + /// + /// In fr, this message translates to: + /// **'Afficher les autres modules'** + String get moduleOthersDescription; + /// No description provided for @modulePayment. /// /// In fr, this message translates to: /// **'Paiement'** String get modulePayment; + /// No description provided for @modulePaymentDescription. + /// + /// In fr, this message translates to: + /// **'Gérer les paiements, les statistiques et les appareils'** + String get modulePaymentDescription; + /// No description provided for @paiementTopUp. /// /// In fr, this message translates to: diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index a2a01e447d..f0e06e2fc3 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -3517,69 +3517,136 @@ class AppLocalizationsEn extends AppLocalizations { @override String get moduleAdvert => 'Advert'; + @override + String get moduleAdvertDescription => 'View the latest adverts'; + @override String get moduleAmap => 'AMAP'; + @override + String get moduleAmapDescription => 'Order your AMAP basket'; + @override String get moduleBooking => 'Booking'; + @override + String get moduleBookingDescription => 'Book a room'; + @override String get moduleCalendar => 'Calendar'; + @override + String get moduleCalendarDescription => 'View the calendar of events'; + @override String get moduleCentralisation => 'Centralisation'; + @override + String get moduleCentralisationDescription => 'Viw all links'; + @override String get moduleCinema => 'Cinema'; + @override + String get moduleCinemaDescription => 'View the cinema schedule'; + @override String get moduleEvent => 'Event'; + @override + String get moduleEventDescription => 'View events'; + @override String get moduleFlappyBird => 'Flappy Bird'; + @override + String get moduleFlappyBirdDescription => 'Play Flappy Bird'; + @override String get moduleLoan => 'Loan'; + @override + String get moduleLoanDescription => 'See your loans'; + @override String get modulePhonebook => 'Phonebook'; + @override + String get modulePhonebookDescription => 'View the phonebook'; + @override String get modulePurchases => 'Purchases'; + @override + String get modulePurchasesDescription => 'View your purchases'; + @override String get moduleRaffle => 'Raffle'; + @override + String get moduleRaffleDescription => 'View the raffle'; + @override String get moduleRecommendation => 'Recommendation'; + @override + String get moduleRecommendationDescription => 'View the recommendations'; + @override String get moduleSeedLibrary => 'Seed Library'; + @override + String get moduleSeedLibraryDescription => 'View the seed library'; + @override String get moduleVote => 'Vote'; + @override + String get moduleVoteDescription => 'Vote for the campaigns'; + @override String get modulePh => 'PH'; + @override + String get modulePhDescription => 'View the PH'; + @override String get moduleSettings => 'Settings'; + @override + String get moduleSettingsDescription => 'Manage your settings'; + @override String get moduleFeed => 'Feed'; + @override + String get moduleFeedDescription => 'View the latest news'; + @override String get moduleStyleGuide => 'StyleGuide'; + @override + String get moduleStyleGuideDescription => 'Style guide for developers'; + @override String get moduleAdmin => 'Administration'; + @override + String get moduleAdminDescription => + 'Administration module for administrators'; + @override String get moduleOthers => 'Others'; + @override + String get moduleOthersDescription => 'Other modules'; + @override String get modulePayment => 'Payment'; + @override + String get modulePaymentDescription => 'Pay and see your transactions'; + @override String get paiementTopUp => 'Top-up'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 9dd945004c..8f8c8cd2e9 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -3546,69 +3546,152 @@ class AppLocalizationsFr extends AppLocalizations { @override String get moduleAdvert => 'Annonce'; + @override + String get moduleAdvertDescription => 'Gérer les annonces'; + @override String get moduleAmap => 'AMAP'; + @override + String get moduleAmapDescription => 'Gérer les livraisons et les produits'; + @override String get moduleBooking => 'Réservation'; + @override + String get moduleBookingDescription => + 'Gérer les réservations, les salles et les managers'; + @override String get moduleCalendar => 'Calendrier'; + @override + String get moduleCalendarDescription => + 'Consulter les événements et les activités'; + @override String get moduleCentralisation => 'Centralisation'; + @override + String get moduleCentralisationDescription => + 'Gérer la centralisation des données'; + @override String get moduleCinema => 'Cinéma'; + @override + String get moduleCinemaDescription => 'Gérer les séances de cinéma'; + @override String get moduleEvent => 'Événement'; + @override + String get moduleEventDescription => + 'Gérer les événements et les participants'; + @override String get moduleFlappyBird => 'Flappy Bird'; + @override + String get moduleFlappyBirdDescription => + 'Jouer à Flappy Bird et consulter le classement'; + @override String get moduleLoan => 'Prêt'; + @override + String get moduleLoanDescription => 'Gérer les prêts et les articles'; + @override String get modulePhonebook => 'Annuaire'; + @override + String get modulePhonebookDescription => + 'Gérer les associations, les membres et les administrateurs'; + @override String get modulePurchases => 'Achats'; + @override + String get modulePurchasesDescription => + 'Gérer les achats, les tickets et l\'historique'; + @override String get moduleRaffle => 'Tombola'; + @override + String get moduleRaffleDescription => + 'Gérer les tombolas, les prix et les tickets'; + @override String get moduleRecommendation => 'Bons plans'; + @override + String get moduleRecommendationDescription => + 'Gérer les recommandations, les informations et les administrateurs'; + @override String get moduleSeedLibrary => 'Grainothèque'; + @override + String get moduleSeedLibraryDescription => + 'Gérer les graines, les espèces et les stocks'; + @override String get moduleVote => 'Vote'; + @override + String get moduleVoteDescription => + 'Gérer les votes, les sections et les candidats'; + @override String get modulePh => 'PH'; + @override + String get modulePhDescription => + 'Gérer les PH, les formulaires et les administrateurs'; + @override String get moduleSettings => 'Paramètres'; + @override + String get moduleSettingsDescription => + 'Gérer les paramètres de l\'application'; + @override String get moduleFeed => 'Feed'; + @override + String get moduleFeedDescription => + 'Consulter les actualités et mises à jour'; + @override String get moduleStyleGuide => 'StyleGuide'; + @override + String get moduleStyleGuideDescription => + 'Explore the UI components and styles used in Titan'; + @override String get moduleAdmin => 'Adminitration'; + @override + String get moduleAdminDescription => + 'Gérer les utilisateurs, groupes et structures'; + @override String get moduleOthers => 'Autres'; + @override + String get moduleOthersDescription => 'Afficher les autres modules'; + @override String get modulePayment => 'Paiement'; + @override + String get modulePaymentDescription => + 'Gérer les paiements, les statistiques et les appareils'; + @override String get paiementTopUp => 'Recharge'; diff --git a/lib/loan/router.dart b/lib/loan/router.dart index b9949400c6..f09fd99378 100644 --- a/lib/loan/router.dart +++ b/lib/loan/router.dart @@ -27,7 +27,8 @@ class LoanRouter { static const String detail = '/detail'; static final Module module = Module( getName: (context) => AppLocalizations.of(context)!.moduleLoan, - description: "Gérer les prêts et les articles", + getDescription: (context) => + AppLocalizations.of(context)!.moduleLoanDescription, root: LoanRouter.root, ); LoanRouter(this.ref); diff --git a/lib/navigation/class/module.dart b/lib/navigation/class/module.dart index d49d6db9dd..9d93c3c4a9 100644 --- a/lib/navigation/class/module.dart +++ b/lib/navigation/class/module.dart @@ -4,25 +4,25 @@ import 'package:heroicons/heroicons.dart'; class Module { final String Function(BuildContext) getName; - String description; + final String Function(BuildContext) getDescription; String root; Module({ required this.getName, - required this.description, + required this.getDescription, required this.root, }); Module copy({ String Function(BuildContext)? getName, - String? description, + String Function(BuildContext)? description, Either? icon, String? root, bool? selected, }) => Module( getName: getName ?? this.getName, - description: description ?? this.description, + getDescription: getDescription, root: root ?? this.root, ); } diff --git a/lib/navigation/ui/all_module_page.dart b/lib/navigation/ui/all_module_page.dart index fb1e173fb4..a9f68957be 100644 --- a/lib/navigation/ui/all_module_page.dart +++ b/lib/navigation/ui/all_module_page.dart @@ -84,7 +84,7 @@ class AllModulePage extends HookConsumerWidget { Expanded( child: ListItem( title: module.getName(context), - subtitle: module.description, + subtitle: module.getDescription(context), onTap: () { navbarListModuleNotifier.pushModule(module); final pathForwardingNotifier = ref.watch( diff --git a/lib/navigation/ui/navigation_template.dart b/lib/navigation/ui/navigation_template.dart index fa08444fd7..cc89f33d4b 100644 --- a/lib/navigation/ui/navigation_template.dart +++ b/lib/navigation/ui/navigation_template.dart @@ -117,7 +117,10 @@ class NavigationTemplate extends HookConsumerWidget { AppLocalizations.of( context, )!.moduleOthers, - description: '', + getDescription: (context) => + AppLocalizations.of( + context, + )!.moduleOthersDescription, root: AppRouter.allModules, ), onTap: () { diff --git a/lib/paiement/router.dart b/lib/paiement/router.dart index a262ac883e..a45be2fb2f 100644 --- a/lib/paiement/router.dart +++ b/lib/paiement/router.dart @@ -39,7 +39,8 @@ class PaymentRouter { static const String storeStats = '/storeStats'; static final Module module = Module( getName: (context) => AppLocalizations.of(context)!.modulePayment, - description: "Gérer les paiements, les statistiques et les appareils", + getDescription: (context) => + AppLocalizations.of(context)!.modulePaymentDescription, root: PaymentRouter.root, ); PaymentRouter(this.ref); diff --git a/lib/ph/router.dart b/lib/ph/router.dart index 3390178e9d..07c52b8819 100644 --- a/lib/ph/router.dart +++ b/lib/ph/router.dart @@ -27,7 +27,8 @@ class PhRouter { static const String add_ph = '/add_ph'; static final Module module = Module( getName: (context) => AppLocalizations.of(context)!.modulePh, - description: "Gérer les PH, les formulaires et les administrateurs", + getDescription: (context) => + AppLocalizations.of(context)!.modulePhDescription, root: PhRouter.root, ); PhRouter(this.ref); diff --git a/lib/phonebook/router.dart b/lib/phonebook/router.dart index 7354fb42ce..abd6d5d981 100644 --- a/lib/phonebook/router.dart +++ b/lib/phonebook/router.dart @@ -25,7 +25,8 @@ class PhonebookRouter { static const String addEditMember = '/add_edit_member'; static final Module module = Module( getName: (context) => AppLocalizations.of(context)!.modulePhonebook, - description: "Gérer les associations, les membres et les administrateurs", + getDescription: (context) => + AppLocalizations.of(context)!.modulePhonebookDescription, root: PhonebookRouter.root, ); PhonebookRouter(this.ref); diff --git a/lib/purchases/router.dart b/lib/purchases/router.dart index 586dbbd331..ac7588c5c6 100644 --- a/lib/purchases/router.dart +++ b/lib/purchases/router.dart @@ -23,7 +23,8 @@ class PurchasesRouter { static const String purchase = '/purchase'; static final Module module = Module( getName: (context) => AppLocalizations.of(context)!.modulePurchases, - description: "Gérer les achats, les tickets et l'historique", + getDescription: (context) => + AppLocalizations.of(context)!.modulePurchasesDescription, root: PurchasesRouter.root, ); PurchasesRouter(this.ref); diff --git a/lib/raffle/router.dart b/lib/raffle/router.dart index ed8a3bd61b..600bfcb165 100644 --- a/lib/raffle/router.dart +++ b/lib/raffle/router.dart @@ -30,7 +30,8 @@ class RaffleRouter { static const String creation = '/creation'; static final Module module = Module( getName: (context) => AppLocalizations.of(context)!.moduleRaffle, - description: "Gérer les tombolas, les prix et les tickets", + getDescription: (context) => + AppLocalizations.of(context)!.moduleRaffleDescription, root: RaffleRouter.root, ); RaffleRouter(this.ref); diff --git a/lib/recommendation/router.dart b/lib/recommendation/router.dart index f1d375acea..393ccd62d9 100644 --- a/lib/recommendation/router.dart +++ b/lib/recommendation/router.dart @@ -22,8 +22,8 @@ class RecommendationRouter { static const String addEdit = '/add_edit'; static final Module module = Module( getName: (context) => AppLocalizations.of(context)!.moduleRecommendation, - description: - "Gérer les recommandations, les informations et les administrateurs", + getDescription: (context) => + AppLocalizations.of(context)!.moduleRecommendationDescription, root: RecommendationRouter.root, ); diff --git a/lib/seed-library/router.dart b/lib/seed-library/router.dart index 6b790884af..2599555ad7 100644 --- a/lib/seed-library/router.dart +++ b/lib/seed-library/router.dart @@ -49,7 +49,8 @@ class SeedLibraryRouter { SeedLibraryRouter(this.ref); static final Module module = Module( getName: (context) => AppLocalizations.of(context)!.moduleSeedLibrary, - description: "Gérer les graines, les espèces et les stocks", + getDescription: (context) => + AppLocalizations.of(context)!.moduleSeedLibraryDescription, root: SeedLibraryRouter.root, ); diff --git a/lib/settings/router.dart b/lib/settings/router.dart index 2c3f08784f..3bf72e30ac 100644 --- a/lib/settings/router.dart +++ b/lib/settings/router.dart @@ -15,7 +15,8 @@ class SettingsRouter { static const String root = '/settings'; static final Module module = Module( getName: (context) => AppLocalizations.of(context)!.moduleSettings, - description: "Gérer les paramètres de l'application", + getDescription: (context) => + AppLocalizations.of(context)!.moduleSettingsDescription, root: SettingsRouter.root, ); diff --git a/lib/super_admin/router.dart b/lib/super_admin/router.dart index f75978842d..5a91468c77 100644 --- a/lib/super_admin/router.dart +++ b/lib/super_admin/router.dart @@ -37,7 +37,7 @@ class SuperAdminRouter { static const String addEditMember = '/add_edit_member'; static final Module module = Module( getName: (context) => "Super Admin", - description: "Gérer les groupes, écoles et structures", + getDescription: (context) => "Super Admin", root: SuperAdminRouter.root, ); SuperAdminRouter(this.ref); diff --git a/lib/tools/ui/styleguide/router.dart b/lib/tools/ui/styleguide/router.dart index 2eb4ce46e9..b0609624d7 100644 --- a/lib/tools/ui/styleguide/router.dart +++ b/lib/tools/ui/styleguide/router.dart @@ -9,7 +9,8 @@ class StyleGuideRouter { static const String root = '/styleguide'; static final Module module = Module( getName: (context) => AppLocalizations.of(context)!.moduleStyleGuide, - description: "Explore the UI components and styles used in Titan", + getDescription: (context) => + AppLocalizations.of(context)!.moduleStyleGuideDescription, root: StyleGuideRouter.root, ); StyleGuideRouter(this.ref); diff --git a/lib/vote/router.dart b/lib/vote/router.dart index ece95a48b8..d7c6b0e619 100644 --- a/lib/vote/router.dart +++ b/lib/vote/router.dart @@ -27,7 +27,8 @@ class VoteRouter { static const String detail = '/detail'; static final Module module = Module( getName: (context) => AppLocalizations.of(context)!.moduleVote, - description: "Gérer les votes, les sections et les candidats", + getDescription: (context) => + AppLocalizations.of(context)!.moduleVoteDescription, root: VoteRouter.root, ); VoteRouter(this.ref); From 03fb7230620e8bc54392d0734a9757abfcc67bb2 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 10 Aug 2025 15:08:28 +0200 Subject: [PATCH 208/473] fix: removing post handling --- lib/feed/ui/pages/admin_page/admin_page.dart | 94 ++------ lib/feed/ui/pages/admin_page/post_form.dart | 89 ------- .../ui/pages/admin_page/tab_navigation.dart | 220 ------------------ 3 files changed, 20 insertions(+), 383 deletions(-) delete mode 100644 lib/feed/ui/pages/admin_page/post_form.dart delete mode 100644 lib/feed/ui/pages/admin_page/tab_navigation.dart diff --git a/lib/feed/ui/pages/admin_page/admin_page.dart b/lib/feed/ui/pages/admin_page/admin_page.dart index 09908229cb..da64655a95 100644 --- a/lib/feed/ui/pages/admin_page/admin_page.dart +++ b/lib/feed/ui/pages/admin_page/admin_page.dart @@ -3,24 +3,12 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/feed/ui/feed.dart'; import 'package:titan/feed/ui/pages/admin_page/event_form.dart'; -import 'package:titan/feed/ui/pages/admin_page/post_form.dart'; -import 'package:titan/feed/ui/pages/admin_page/tab_navigation.dart'; class AdminPage extends HookConsumerWidget { const AdminPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - // Use hooks to manage tab selection state - final selectedTabIndex = useState(0); - final previousTabIndex = useRef(0); - final pageController = usePageController(); - - // Controllers for Post form - final postTitleController = useTextEditingController(); - final postDescriptionController = useTextEditingController(); - final postStartDateController = useTextEditingController(); - // Controllers for Event form final eventTitleController = useTextEditingController(); final eventDescriptionController = useTextEditingController(); @@ -32,71 +20,29 @@ class AdminPage extends HookConsumerWidget { final eventStartDateController = useTextEditingController(); final eventEndDateController = useTextEditingController(); - // Handle tab selection changes - useEffect(() { - if (pageController.hasClients) { - pageController.animateToPage( - selectedTabIndex.value, - duration: const Duration(milliseconds: 300), - curve: Curves.easeOutCubic, - ); - } - return null; - }, [selectedTabIndex.value]); - return FeedTemplate( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - // Tab Bar - TabNavigation( - selectedTabIndex: selectedTabIndex.value, - onTabChanged: (index) { - previousTabIndex.value = selectedTabIndex.value; - selectedTabIndex.value = index; - }, - tabLabels: const ['Post', 'Événement'], - ), - - const SizedBox(height: 30), - - // PageView for tab content with actual PageView widget - Expanded( - child: PageView( - controller: pageController, - physics: const BouncingScrollPhysics(), - onPageChanged: (index) { - previousTabIndex.value = selectedTabIndex.value; - selectedTabIndex.value = index; - }, - children: [ - // Post form - SingleChildScrollView( - physics: const BouncingScrollPhysics(), - child: PostForm( - titleController: postTitleController, - descriptionController: postDescriptionController, - startDateController: postStartDateController, - ), - ), - - // Event form - SingleChildScrollView( - physics: const BouncingScrollPhysics(), - child: EventForm( - titleController: eventTitleController, - descriptionController: eventDescriptionController, - startDateController: eventStartDateController, - endDateController: eventEndDateController, - locationController: eventLocationController, - shotgunDateController: shotgunDateController, - externalLinkController: eventExternalLinkController, + child: SafeArea( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + child: + // Event form + SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: EventForm( + titleController: eventTitleController, + descriptionController: eventDescriptionController, + startDateController: eventStartDateController, + endDateController: eventEndDateController, + locationController: eventLocationController, + shotgunDateController: shotgunDateController, + externalLinkController: eventExternalLinkController, + ), ), - ), - ], ), - ), - ], + ], + ), ), ); } diff --git a/lib/feed/ui/pages/admin_page/post_form.dart b/lib/feed/ui/pages/admin_page/post_form.dart deleted file mode 100644 index 5f96a89a67..0000000000 --- a/lib/feed/ui/pages/admin_page/post_form.dart +++ /dev/null @@ -1,89 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:titan/tools/ui/styleguide/button.dart'; -import 'package:titan/tools/ui/styleguide/date_entry.dart'; -import 'package:titan/tools/ui/styleguide/image_entry.dart'; -import 'package:titan/tools/ui/styleguide/text_entry.dart'; - -class PostForm extends StatelessWidget { - final TextEditingController titleController; - final TextEditingController descriptionController; - final TextEditingController startDateController; - - const PostForm({ - super.key, - required this.titleController, - required this.descriptionController, - required this.startDateController, - }); - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 16.0), - child: Column( - key: const ValueKey('post_form'), - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - TextEntry(label: "Titre", controller: titleController), - const SizedBox(height: 20), - TextEntry( - label: "Description", - controller: descriptionController, - maxLines: 5, - minLines: 3, - ), - const SizedBox(height: 20), - DateEntry( - onTap: () async { - final pickedDate = await showDatePicker( - context: context, - initialDate: DateTime.now(), - firstDate: DateTime.now(), - lastDate: DateTime.now().add(const Duration(days: 365)), - ); - if (pickedDate != null) { - final formattedDate = - "${pickedDate.day.toString().padLeft(2, '0')}/${pickedDate.month.toString().padLeft(2, '0')}/${pickedDate.year}"; - startDateController.text = formattedDate; - } - }, - title: "Date de début", - subtitle: "Sélectionnez une date", - ), - const SizedBox(height: 20), - ImageEntry( - title: "Image", - subtitle: "Sélectionnez une image", - onTap: () { - // Logic to add an image - ScaffoldMessenger.of( - context, - ).showSnackBar(const SnackBar(content: Text('Image ajoutée'))); - }, - ), - const SizedBox(height: 40), - Button( - text: "Publier", - onPressed: () { - if (titleController.text.isEmpty || - descriptionController.text.isEmpty) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text( - 'Veuillez remplir tous les champs obligatoires', - ), - ), - ); - return; - } - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Post créé avec succès')), - ); - }, - ), - const SizedBox(height: 80), - ], - ), - ); - } -} diff --git a/lib/feed/ui/pages/admin_page/tab_navigation.dart b/lib/feed/ui/pages/admin_page/tab_navigation.dart deleted file mode 100644 index 9b21d37245..0000000000 --- a/lib/feed/ui/pages/admin_page/tab_navigation.dart +++ /dev/null @@ -1,220 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:titan/tools/constants.dart'; - -class TabNavigation extends HookWidget { - final int selectedTabIndex; - final Function(int) onTabChanged; - final List tabLabels; - - const TabNavigation({ - super.key, - required this.selectedTabIndex, - required this.onTabChanged, - required this.tabLabels, - }); - - @override - Widget build(BuildContext context) { - // Track previous and current tab indices - final previousIndex = useRef(selectedTabIndex); - final currentState = useState(selectedTabIndex); - - // Update current state when selectedTabIndex changes externally - useEffect(() { - currentState.value = selectedTabIndex; - return null; - }, [selectedTabIndex]); - - // Main animation controller for sliding effect - final animationController = useAnimationController( - duration: const Duration(milliseconds: 300), - ); - - // Clean up animation controller if component is destroyed while animating - useEffect(() { - return () { - if (animationController.isAnimating) { - animationController.stop(); - } - }; - }, []); - - // Slide animation reference - final slideAnimation = useRef?>(null); - final itemWidthRef = useRef(0.0); - - // Update animation when tab changes - useEffect(() { - if (previousIndex.value != currentState.value && itemWidthRef.value > 0) { - slideAnimation.value = - Tween( - begin: previousIndex.value * itemWidthRef.value, - end: currentState.value * itemWidthRef.value, - ).animate( - CurvedAnimation( - parent: animationController, - curve: Curves.easeOutCubic, - ), - ); - animationController.reset(); - animationController.forward(); - previousIndex.value = currentState.value; - } - return null; - }, [currentState.value, itemWidthRef.value]); - - final borderRadius = 25.0; - - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), - child: Material( - elevation: 10, - shadowColor: ColorConstants.tertiary.withValues(alpha: 0.2), - borderRadius: BorderRadius.circular(borderRadius), - color: ColorConstants.tertiary, - child: Container( - height: borderRadius * 2, - padding: const EdgeInsets.symmetric(horizontal: 5), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(borderRadius), - ), - child: LayoutBuilder( - builder: (context, constraints) { - final availableWidth = constraints.maxWidth; - final itemWidth = availableWidth / tabLabels.length; - - itemWidthRef.value = itemWidth; - - return Stack( - children: [ - // Animated sliding indicator - AnimatedBuilder( - animation: animationController, - builder: (context, _) { - final leftPosition = slideAnimation.value != null - ? slideAnimation.value!.value - : itemWidth * currentState.value; - - return Positioned( - left: leftPosition, - top: 4, - bottom: 4, - width: itemWidth, - child: Container( - decoration: BoxDecoration( - color: ColorConstants.background, - borderRadius: BorderRadius.circular(borderRadius), - ), - ), - ); - }, - ), - - // Tab buttons row - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: tabLabels.asMap().entries.map((entry) { - final index = entry.key; - final label = entry.value; - final isSelected = index == currentState.value; - - return Expanded( - child: Material( - color: Colors.transparent, - borderRadius: BorderRadius.circular(borderRadius), - child: GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () { - if (index != currentState.value) { - if (animationController.isAnimating) { - animationController.stop(); - } - - previousIndex.value = currentState.value; - currentState.value = index; - - // Notify parent about the tab change - WidgetsBinding.instance.addPostFrameCallback(( - _, - ) { - onTabChanged(index); - }); - } - }, - child: Container( - padding: const EdgeInsets.all(8), - child: AnimatedBuilder( - animation: animationController, - builder: (context, child) { - Color textColor; - FontWeight textWeight; - - if (previousIndex.value == - currentState.value) { - textColor = isSelected - ? ColorConstants.tertiary - : ColorConstants.background; - textWeight = isSelected - ? FontWeight.w600 - : FontWeight.normal; - } else { - bool isInvolved = - index == previousIndex.value || - index == currentState.value; - - if (!isInvolved) { - textColor = ColorConstants.background; - textWeight = FontWeight.normal; - } else if (index == currentState.value) { - final progress = - animationController.value; - textColor = Color.lerp( - ColorConstants.background, - ColorConstants.tertiary, - progress, - )!; - textWeight = progress < 0.5 - ? FontWeight.normal - : FontWeight.w600; - } else { - final progress = - animationController.value; - textColor = Color.lerp( - ColorConstants.tertiary, - ColorConstants.background, - progress, - )!; - textWeight = progress < 0.5 - ? FontWeight.w600 - : FontWeight.normal; - } - } - - return Center( - child: Text( - label, - style: TextStyle( - color: textColor, - fontSize: 14, - fontWeight: textWeight, - ), - ), - ); - }, - ), - ), - ), - ), - ); - }).toList(), - ), - ], - ); - }, - ), - ), - ), - ); - } -} From ff518eee2bcdd51682788bfccf993a491f2f4345 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 10 Aug 2025 15:08:28 +0200 Subject: [PATCH 209/473] refacto: renaming pages --- lib/feed/router.dart | 24 ++- .../pages/add_event_page/add_event_page.dart | 152 +++++++++++++++++ lib/feed/ui/pages/admin_page/admin_page.dart | 49 ------ lib/feed/ui/pages/admin_page/event_form.dart | 154 ------------------ .../event_handling_page.dart | 12 ++ lib/feed/ui/pages/main_page/main_page.dart | 31 +++- 6 files changed, 210 insertions(+), 212 deletions(-) create mode 100644 lib/feed/ui/pages/add_event_page/add_event_page.dart delete mode 100644 lib/feed/ui/pages/admin_page/admin_page.dart delete mode 100644 lib/feed/ui/pages/admin_page/event_form.dart create mode 100644 lib/feed/ui/pages/event_handling_page/event_handling_page.dart diff --git a/lib/feed/router.dart b/lib/feed/router.dart index cf7a72bd57..4d6e807fc1 100644 --- a/lib/feed/router.dart +++ b/lib/feed/router.dart @@ -3,8 +3,10 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/admin/providers/is_admin_provider.dart'; import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/navigation/class/module.dart'; -import 'package:titan/feed/ui/pages/admin_page/admin_page.dart' - deferred as admin_page; +import 'package:titan/feed/ui/pages/add_event_page/add_event_page.dart' + deferred as add_event_page; +import 'package:titan/feed/ui/pages/event_handling_page/event_handling_page.dart' + deferred as event_handling_page; import 'package:titan/feed/ui/pages/main_page/main_page.dart' deferred as main_page; import 'package:titan/tools/middlewares/admin_middleware.dart'; @@ -16,7 +18,8 @@ class FeedRouter { final Ref ref; static const String root = '/feed'; - static const String admin = '/admin'; + static const String addEvent = '/add_event'; + static const String eventHandling = '/event_handling'; static final Module module = Module( getName: (context) => AppLocalizations.of(context)!.moduleFeed, getDescription: (context) => @@ -40,12 +43,21 @@ class FeedRouter { ), children: [ QRoute( - path: admin, - builder: () => admin_page.AdminPage(), + path: addEvent, + builder: () => add_event_page.AddEventPage(), middleware: [ AuthenticatedMiddleware(ref), AdminMiddleware(ref, isAdminProvider), - DeferredLoadingMiddleware(admin_page.loadLibrary), + DeferredLoadingMiddleware(add_event_page.loadLibrary), + ], + ), + QRoute( + path: eventHandling, + builder: () => event_handling_page.EventHandlingPage(), + middleware: [ + AuthenticatedMiddleware(ref), + AdminMiddleware(ref, isAdminProvider), + DeferredLoadingMiddleware(event_handling_page.loadLibrary), ], ), ], diff --git a/lib/feed/ui/pages/add_event_page/add_event_page.dart b/lib/feed/ui/pages/add_event_page/add_event_page.dart new file mode 100644 index 0000000000..2215b3c9be --- /dev/null +++ b/lib/feed/ui/pages/add_event_page/add_event_page.dart @@ -0,0 +1,152 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/feed/ui/feed.dart'; +import 'package:titan/tools/ui/styleguide/button.dart'; +import 'package:titan/tools/ui/styleguide/date_entry.dart'; +import 'package:titan/tools/ui/styleguide/image_entry.dart'; +import 'package:titan/tools/ui/styleguide/text_entry.dart'; + +class AddEventPage extends HookConsumerWidget { + const AddEventPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final eventTitleController = useTextEditingController(); + final eventDescriptionController = useTextEditingController(); + final eventLocationController = useTextEditingController(); + final shotgunDateController = useTextEditingController(); + final eventExternalLinkController = useTextEditingController(); + final eventStartDateController = useTextEditingController(); + final eventEndDateController = useTextEditingController(); + + return FeedTemplate( + child: Expanded( + child: SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 20.0, + vertical: 16.0, + ), + child: Column( + key: const ValueKey('event_form'), + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + TextEntry(label: "Titre", controller: eventTitleController), + TextEntry( + label: "Description", + controller: eventDescriptionController, + maxLines: 5, + minLines: 3, + ), + DateEntry( + onTap: () async { + final pickedDate = await showDatePicker( + context: context, + initialDate: DateTime.now(), + firstDate: DateTime.now(), + lastDate: DateTime.now().add(const Duration(days: 365)), + ); + if (pickedDate != null) { + final formattedDate = + "${pickedDate.day.toString().padLeft(2, '0')}/${pickedDate.month.toString().padLeft(2, '0')}/${pickedDate.year}"; + eventStartDateController.text = formattedDate; + } + }, + title: "Date de début", + subtitle: "Sélectionnez une date", + ), + DateEntry( + onTap: () async { + DateTime startDate = DateTime.now(); + if (eventStartDateController.text.isNotEmpty) { + final parts = eventStartDateController.text.split('/'); + startDate = DateTime( + int.parse(parts[2]), + int.parse(parts[1]), + int.parse(parts[0]), + ); + } + + final pickedDate = await showDatePicker( + context: context, + initialDate: startDate, + firstDate: startDate, + lastDate: startDate.add(const Duration(days: 365)), + ); + if (pickedDate != null) { + final formattedDate = + "${pickedDate.day.toString().padLeft(2, '0')}/${pickedDate.month.toString().padLeft(2, '0')}/${pickedDate.year}"; + eventEndDateController.text = formattedDate; + } + }, + title: "Date de fin", + subtitle: "Sélectionnez une date", + ), + TextEntry(label: "Lieu", controller: eventLocationController), + DateEntry( + onTap: () async { + final pickedDate = await showDatePicker( + context: context, + initialDate: DateTime.now(), + firstDate: DateTime.now(), + lastDate: DateTime.now().add(const Duration(days: 365)), + ); + if (pickedDate != null) { + final formattedDate = + "${pickedDate.day.toString().padLeft(2, '0')}/${pickedDate.month.toString().padLeft(2, '0')}/${pickedDate.year}"; + shotgunDateController.text = formattedDate; + } + }, + title: "Date et heure du SG", + subtitle: "Sélectionnez une date", + ), + TextEntry( + label: "Lien externe pour le SG", + controller: eventExternalLinkController, + canBeEmpty: true, + ), + ImageEntry( + title: "Image", + subtitle: "Sélectionnez une image", + onTap: () { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Image ajoutée')), + ); + }, + ), + const SizedBox(height: 40), + Button( + text: "Créer l'événement", + onPressed: () { + if (eventTitleController.text.isEmpty || + eventDescriptionController.text.isEmpty || + eventStartDateController.text.isEmpty || + eventEndDateController.text.isEmpty || + eventLocationController.text.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + 'Veuillez remplir tous les champs obligatoires', + ), + ), + ); + return; + } + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Événement créé avec succès'), + ), + ); + }, + ), + const SizedBox(height: 80), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/feed/ui/pages/admin_page/admin_page.dart b/lib/feed/ui/pages/admin_page/admin_page.dart deleted file mode 100644 index da64655a95..0000000000 --- a/lib/feed/ui/pages/admin_page/admin_page.dart +++ /dev/null @@ -1,49 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/feed/ui/feed.dart'; -import 'package:titan/feed/ui/pages/admin_page/event_form.dart'; - -class AdminPage extends HookConsumerWidget { - const AdminPage({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - // Controllers for Event form - final eventTitleController = useTextEditingController(); - final eventDescriptionController = useTextEditingController(); - final eventLocationController = useTextEditingController(); - final shotgunDateController = useTextEditingController(); - final eventExternalLinkController = useTextEditingController(); - - // Selected date state for event - final eventStartDateController = useTextEditingController(); - final eventEndDateController = useTextEditingController(); - - return FeedTemplate( - child: SafeArea( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Expanded( - child: - // Event form - SingleChildScrollView( - physics: const BouncingScrollPhysics(), - child: EventForm( - titleController: eventTitleController, - descriptionController: eventDescriptionController, - startDateController: eventStartDateController, - endDateController: eventEndDateController, - locationController: eventLocationController, - shotgunDateController: shotgunDateController, - externalLinkController: eventExternalLinkController, - ), - ), - ), - ], - ), - ), - ); - } -} diff --git a/lib/feed/ui/pages/admin_page/event_form.dart b/lib/feed/ui/pages/admin_page/event_form.dart deleted file mode 100644 index 0748e2512d..0000000000 --- a/lib/feed/ui/pages/admin_page/event_form.dart +++ /dev/null @@ -1,154 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:titan/tools/ui/styleguide/button.dart'; -import 'package:titan/tools/ui/styleguide/image_entry.dart'; -import 'package:titan/tools/ui/styleguide/text_entry.dart'; -import 'package:titan/tools/ui/styleguide/date_entry.dart'; - -class EventForm extends StatelessWidget { - final TextEditingController titleController; - final TextEditingController descriptionController; - final TextEditingController startDateController; - final TextEditingController endDateController; - final TextEditingController locationController; - final TextEditingController shotgunDateController; - final TextEditingController externalLinkController; - - const EventForm({ - super.key, - required this.titleController, - required this.descriptionController, - required this.startDateController, - required this.endDateController, - required this.locationController, - required this.shotgunDateController, - required this.externalLinkController, - }); - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 16.0), - child: Column( - key: const ValueKey('event_form'), - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - TextEntry(label: "Titre", controller: titleController), - const SizedBox(height: 20), - TextEntry( - label: "Description", - controller: descriptionController, - maxLines: 5, - minLines: 3, - ), - const SizedBox(height: 20), - DateEntry( - onTap: () async { - final pickedDate = await showDatePicker( - context: context, - initialDate: DateTime.now(), - firstDate: DateTime.now(), - lastDate: DateTime.now().add(const Duration(days: 365)), - ); - if (pickedDate != null) { - final formattedDate = - "${pickedDate.day.toString().padLeft(2, '0')}/${pickedDate.month.toString().padLeft(2, '0')}/${pickedDate.year}"; - startDateController.text = formattedDate; - } - }, - title: "Date de début", - subtitle: "Sélectionnez une date", - ), - const SizedBox(height: 20), - DateEntry( - onTap: () async { - DateTime startDate = DateTime.now(); - if (startDateController.text.isNotEmpty) { - final parts = startDateController.text.split('/'); - startDate = DateTime( - int.parse(parts[2]), - int.parse(parts[1]), - int.parse(parts[0]), - ); - } - - final pickedDate = await showDatePicker( - context: context, - initialDate: startDate, - firstDate: startDate, - lastDate: startDate.add(const Duration(days: 365)), - ); - if (pickedDate != null) { - final formattedDate = - "${pickedDate.day.toString().padLeft(2, '0')}/${pickedDate.month.toString().padLeft(2, '0')}/${pickedDate.year}"; - endDateController.text = formattedDate; - } - }, - title: "Date de fin", - subtitle: "Sélectionnez une date", - ), - const SizedBox(height: 20), - TextEntry(label: "Lieu", controller: locationController), - const SizedBox(height: 20), - DateEntry( - onTap: () async { - final pickedDate = await showDatePicker( - context: context, - initialDate: DateTime.now(), - firstDate: DateTime.now(), - lastDate: DateTime.now().add(const Duration(days: 365)), - ); - if (pickedDate != null) { - final formattedDate = - "${pickedDate.day.toString().padLeft(2, '0')}/${pickedDate.month.toString().padLeft(2, '0')}/${pickedDate.year}"; - shotgunDateController.text = formattedDate; - } - }, - title: "Date et heure du SG", - subtitle: "Sélectionnez une date", - ), - const SizedBox(height: 20), - TextEntry( - label: "Lien externe pour le SG", - controller: externalLinkController, - canBeEmpty: true, - ), - const SizedBox(height: 20), - ImageEntry( - title: "Image", - subtitle: "Sélectionnez une image", - onTap: () { - // Logic to add an image - ScaffoldMessenger.of( - context, - ).showSnackBar(const SnackBar(content: Text('Image ajoutée'))); - }, - ), - const SizedBox(height: 40), - Button( - text: "Créer l'événement", - onPressed: () { - if (titleController.text.isEmpty || - descriptionController.text.isEmpty || - startDateController.text.isEmpty || - endDateController.text.isEmpty || - locationController.text.isEmpty) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text( - 'Veuillez remplir tous les champs obligatoires', - ), - ), - ); - return; - } - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Événement créé avec succès')), - ); - }, - ), - const SizedBox(height: 80), - ], - ), - ); - } -} diff --git a/lib/feed/ui/pages/event_handling_page/event_handling_page.dart b/lib/feed/ui/pages/event_handling_page/event_handling_page.dart new file mode 100644 index 0000000000..a432965721 --- /dev/null +++ b/lib/feed/ui/pages/event_handling_page/event_handling_page.dart @@ -0,0 +1,12 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/feed/ui/feed.dart'; + +class EventHandlingPage extends HookConsumerWidget { + const EventHandlingPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return FeedTemplate(child: Container()); + } +} diff --git a/lib/feed/ui/pages/main_page/main_page.dart b/lib/feed/ui/pages/main_page/main_page.dart index dfbdfa6edd..669b8fa9e8 100644 --- a/lib/feed/ui/pages/main_page/main_page.dart +++ b/lib/feed/ui/pages/main_page/main_page.dart @@ -33,7 +33,6 @@ class FeedMainPage extends HookConsumerWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Search bar CustomSearchBar( onFilter: () async { await showCustomBottomModal( @@ -96,11 +95,37 @@ class FeedMainPage extends HookConsumerWidget { if (isSuperAdmin) CustomIconButton( icon: HeroIcon( - HeroIcons.plus, + HeroIcons.userGroup, color: ColorConstants.background, ), onPressed: () { - QR.to(FeedRouter.root + FeedRouter.admin); + showCustomBottomModal( + modal: BottomModalTemplate( + title: 'Administration', + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Button( + text: 'Créer un événement', + onPressed: () { + QR.to(FeedRouter.root + FeedRouter.addEvent); + }, + ), + const SizedBox(height: 20), + Button( + text: 'Demandes de publication', + onPressed: () { + QR.to( + FeedRouter.root + FeedRouter.eventHandling, + ); + }, + ), + ], + ), + ), + context: context, + ref: ref, + ); }, ), ], From 19bd8659b11e29754336b0d2c87148c7e3170739 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 10 Aug 2025 15:08:29 +0200 Subject: [PATCH 210/473] fix: input design --- lib/tools/ui/styleguide/image_entry.dart | 2 +- lib/tools/ui/styleguide/text_entry.dart | 35 ++++++++++++------------ 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/lib/tools/ui/styleguide/image_entry.dart b/lib/tools/ui/styleguide/image_entry.dart index 3f763c8863..4940431689 100644 --- a/lib/tools/ui/styleguide/image_entry.dart +++ b/lib/tools/ui/styleguide/image_entry.dart @@ -15,7 +15,7 @@ class ImageEntry extends StatelessWidget { onTap: onTap, title: title, subtitle: subtitle, - trailing: const HeroIcon(HeroIcons.phone, color: ColorConstants.tertiary), + trailing: const HeroIcon(HeroIcons.photo, color: ColorConstants.tertiary), ); } } diff --git a/lib/tools/ui/styleguide/text_entry.dart b/lib/tools/ui/styleguide/text_entry.dart index dbb53f30f1..f0b570110c 100644 --- a/lib/tools/ui/styleguide/text_entry.dart +++ b/lib/tools/ui/styleguide/text_entry.dart @@ -32,8 +32,8 @@ class TextEntry extends StatelessWidget { this.keyboardType = TextInputType.text, this.textCapitalization = TextCapitalization.sentences, this.canBeEmpty = false, - this.color = ColorConstants.tertiary, - this.enabledColor = ColorConstants.tertiary, + this.color = ColorConstants.onTertiary, + this.enabledColor = ColorConstants.onTertiary, this.errorColor = ColorConstants.main, this.noValueError = "No value", this.suffixIcon, @@ -97,25 +97,26 @@ class TextEntry extends StatelessWidget { return noValueError; } - if (isInt) { - final intValue = int.tryParse(value); - if (intValue == null || (intValue < 0 && !isNegative)) { - return "Invalid number"; + if (isInt) { + final intValue = int.tryParse(value); + if (intValue == null || (intValue < 0 && !isNegative)) { + return "Invalid number"; + } } - } - if (isDouble) { - final doubleValue = double.tryParse(value.replaceAll(',', '.')); - if (doubleValue == null || (doubleValue < 0 && !isNegative)) { - return "Invalid number"; + if (isDouble) { + final doubleValue = double.tryParse(value.replaceAll(',', '.')); + if (doubleValue == null || (doubleValue < 0 && !isNegative)) { + return "Invalid number"; + } } - } - if (validator == null) { - return null; - } - return validator!(value); - }, + if (validator == null) { + return null; + } + return validator!(value); + }, + ), ); } } From 9a0d23abdb6ae1ce66a49ac953c51c44e016b8c2 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 10 Aug 2025 15:08:29 +0200 Subject: [PATCH 211/473] feat: adding admin controls --- .../pages/main_page/event_action_admin.dart | 63 +++++++++++++++++++ .../ui/pages/main_page/feed_timeline.dart | 4 +- lib/feed/ui/pages/main_page/main_page.dart | 1 + .../ui/pages/main_page/time_line_item.dart | 47 +++++++++----- 4 files changed, 97 insertions(+), 18 deletions(-) create mode 100644 lib/feed/ui/pages/main_page/event_action_admin.dart diff --git a/lib/feed/ui/pages/main_page/event_action_admin.dart b/lib/feed/ui/pages/main_page/event_action_admin.dart new file mode 100644 index 0000000000..5481b5e553 --- /dev/null +++ b/lib/feed/ui/pages/main_page/event_action_admin.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; +import 'package:titan/tools/constants.dart'; + +class EventActionAdmin extends StatelessWidget { + const EventActionAdmin({super.key}); + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + GestureDetector( + onTap: () {}, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 5), + width: 100, + decoration: BoxDecoration( + color: ColorConstants.tertiary, + borderRadius: BorderRadius.circular(20), + border: Border.all(color: ColorConstants.onTertiary, width: 2), + ), + child: Center( + child: Text( + "Modifier", + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: ColorConstants.background, + ), + ), + ), + ), + ), + + const SizedBox(width: 10), + + // Action button + GestureDetector( + onTap: () {}, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 5), + width: 100, + decoration: BoxDecoration( + color: ColorConstants.main, + borderRadius: BorderRadius.circular(20), + border: Border.all(color: ColorConstants.onMain, width: 2), + ), + child: Center( + child: Text( + "Supprimer", + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: ColorConstants.background, + ), + ), + ), + ), + ), + ], + ); + } +} diff --git a/lib/feed/ui/pages/main_page/feed_timeline.dart b/lib/feed/ui/pages/main_page/feed_timeline.dart index bf7aca1439..6e293172af 100644 --- a/lib/feed/ui/pages/main_page/feed_timeline.dart +++ b/lib/feed/ui/pages/main_page/feed_timeline.dart @@ -5,8 +5,9 @@ import 'package:titan/feed/ui/pages/main_page/time_line_item.dart'; class FeedTimeline extends StatelessWidget { final List items; final Function(FeedItem item)? onItemTap; + final bool isAdmin; - const FeedTimeline({super.key, required this.items, this.onItemTap}); + const FeedTimeline({super.key, required this.items, this.onItemTap, required this.isAdmin}); @override Widget build(BuildContext context) { @@ -15,6 +16,7 @@ class FeedTimeline extends StatelessWidget { ...items.map( (item) => TimelineItem( item: item, + isAdmin: isAdmin, onTap: onItemTap != null ? () => onItemTap!(item) : null, ), ), diff --git a/lib/feed/ui/pages/main_page/main_page.dart b/lib/feed/ui/pages/main_page/main_page.dart index 669b8fa9e8..3fc8ca5255 100644 --- a/lib/feed/ui/pages/main_page/main_page.dart +++ b/lib/feed/ui/pages/main_page/main_page.dart @@ -140,6 +140,7 @@ class FeedMainPage extends HookConsumerWidget { controller: scrollController, physics: const BouncingScrollPhysics(), child: FeedTimeline( + isAdmin: isAdmin, items: filteredItems.value, onItemTap: (item) {}, ), diff --git a/lib/feed/ui/pages/main_page/time_line_item.dart b/lib/feed/ui/pages/main_page/time_line_item.dart index 6bb4b6bf9a..fcd93247d5 100644 --- a/lib/feed/ui/pages/main_page/time_line_item.dart +++ b/lib/feed/ui/pages/main_page/time_line_item.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:titan/feed/class/feed_item.dart'; import 'package:titan/feed/ui/pages/main_page/event_action.dart'; +import 'package:titan/feed/ui/pages/main_page/event_action_admin.dart'; import 'package:titan/feed/ui/pages/main_page/event_card.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/feed/ui/pages/main_page/dotted_vertical_line.dart'; @@ -9,13 +10,19 @@ import 'package:titan/feed/ui/pages/main_page/dotted_vertical_line.dart'; class TimelineItem extends StatelessWidget { final FeedItem item; final VoidCallback? onTap; + final bool isAdmin; - const TimelineItem({super.key, required this.item, this.onTap}); + const TimelineItem({ + super.key, + required this.item, + this.onTap, + required this.isAdmin, + }); @override Widget build(BuildContext context) { return SizedBox( - height: item.type == FeedItemType.announcement ? 160 : 200, + height: item.type == FeedItemType.announcement && !isAdmin ? 160 : 200, child: Stack( children: [ Padding( @@ -63,14 +70,17 @@ class TimelineItem extends StatelessWidget { ), ], ), - if (item.type != FeedItemType.announcement) + if (item.type != FeedItemType.announcement || isAdmin) Padding( padding: const EdgeInsets.only(top: 8), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( - padding: const EdgeInsets.only(left: 14, right: 45), + padding: EdgeInsets.only( + left: 14, + right: isAdmin ? 33 : 45, + ), child: Container( width: 20, height: 20, @@ -85,19 +95,22 @@ class TimelineItem extends StatelessWidget { ), ), Expanded( - child: EventAction( - title: item.type == FeedItemType.action - ? 'Tu peux voter' - : 'Tu es invité', - subtitle: item.type == FeedItemType.action - ? '254 votants' - : '75 participants', - onActionPressed: item.onRegister, - actionButtonText: item.type == FeedItemType.action - ? 'Participer' - : 'Voter', - isActionEnabled: true, - ), + child: isAdmin + ? EventActionAdmin() + : EventAction( + title: item.type == FeedItemType.action + ? 'Tu peux voter' + : 'Tu es invité', + subtitle: item.type == FeedItemType.action + ? '254 votants' + : '75 participants', + onActionPressed: item.onRegister, + actionButtonText: + item.type == FeedItemType.action + ? 'Participer' + : 'Voter', + isActionEnabled: true, + ), ), ], ), From e367ac9bd37e8f8444c26ab66e4b73ae42376b60 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 10 Aug 2025 15:08:29 +0200 Subject: [PATCH 212/473] feat: adding news class --- lib/feed/class/news.dart | 102 +++++++++++++++++++++++++++++++++++ lib/feed/tools/function.dart | 26 +++++++++ 2 files changed, 128 insertions(+) create mode 100644 lib/feed/class/news.dart create mode 100644 lib/feed/tools/function.dart diff --git a/lib/feed/class/news.dart b/lib/feed/class/news.dart new file mode 100644 index 0000000000..a7d8176007 --- /dev/null +++ b/lib/feed/class/news.dart @@ -0,0 +1,102 @@ +import 'package:titan/feed/tools/function.dart'; +import 'package:titan/tools/functions.dart'; + +class News { + final String id; + final String title; + final DateTime start; + final DateTime? end; + final String entity; + final String? location; + final DateTime? actionStart; + final String module; + final String moduleObjectId; + final NewsStatus status; + + const News({ + required this.id, + required this.title, + required this.start, + this.end, + required this.entity, + this.location, + this.actionStart, + required this.module, + required this.moduleObjectId, + required this.status, + }); + + News.fromJson(Map json) + : id = json['id'], + title = json['title'], + start = processDateFromAPI(json['start']), + end = json['end'] != null ? processDateFromAPI(json['end']) : null, + entity = json['entity'], + location = json['location'], + actionStart = json['action_start'] != null + ? processDateFromAPI(json['action_start']) + : null, + module = json['module'], + moduleObjectId = json['module_object_id'], + status = stringToNewsStatus(json['status']); + + Map toJson() { + return { + 'id': id, + 'title': title, + 'start': processDateToAPI(start), + 'end': end != null ? processDateToAPI(end!) : null, + 'entity': entity, + 'location': location, + 'action_start': actionStart != null + ? processDateToAPI(actionStart!) + : null, + 'module': module, + 'module_object_id': moduleObjectId, + 'status': status.toString().split('.').last, + }; + } + + News copyWith({ + String? id, + String? title, + DateTime? start, + DateTime? end, + String? entity, + String? location, + DateTime? actionStart, + String? module, + String? moduleObjectId, + NewsStatus? status, + }) { + return News( + id: id ?? this.id, + title: title ?? this.title, + start: start ?? this.start, + end: end ?? this.end, + entity: entity ?? this.entity, + location: location ?? this.location, + actionStart: actionStart ?? this.actionStart, + module: module ?? this.module, + moduleObjectId: moduleObjectId ?? this.moduleObjectId, + status: status ?? this.status, + ); + } + + @override + String toString() { + return 'News(id: $id, title: $title, start: $start, end: $end, entity: $entity, location: $location, actionStart: $actionStart, module: $module, moduleObjectId: $moduleObjectId, status: $status)'; + } + + News.empty() + : id = '', + title = '', + start = DateTime.now(), + end = null, + entity = '', + location = null, + actionStart = null, + module = '', + moduleObjectId = '', + status = NewsStatus.waitingApproval; +} diff --git a/lib/feed/tools/function.dart b/lib/feed/tools/function.dart new file mode 100644 index 0000000000..c6ec3426f1 --- /dev/null +++ b/lib/feed/tools/function.dart @@ -0,0 +1,26 @@ +enum NewsStatus { waitingApproval, rejected, published } + + +String newsStatusToString(NewsStatus status) { + switch (status) { + case NewsStatus.waitingApproval: + return 'waiting_approval'; + case NewsStatus.rejected: + return 'rejected'; + case NewsStatus.published: + return 'published'; + } +} + +NewsStatus stringToNewsStatus(String status) { + switch (status) { + case 'waiting_approval': + return NewsStatus.waitingApproval; + case 'rejected': + return NewsStatus.rejected; + case 'published': + return NewsStatus.published; + default: + return NewsStatus.waitingApproval; // Default case + } +} \ No newline at end of file From 4066b5a98717cfbcf1a5a3478e7c42f3c49675a0 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 10 Aug 2025 15:08:29 +0200 Subject: [PATCH 213/473] feat: adding news repositories --- .../repositories/news_image_repository.dart | 19 +++++++++++ lib/feed/repositories/news_repository.dart | 32 +++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 lib/feed/repositories/news_image_repository.dart create mode 100644 lib/feed/repositories/news_repository.dart diff --git a/lib/feed/repositories/news_image_repository.dart b/lib/feed/repositories/news_image_repository.dart new file mode 100644 index 0000000000..690e95c0cd --- /dev/null +++ b/lib/feed/repositories/news_image_repository.dart @@ -0,0 +1,19 @@ +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/auth/providers/openid_provider.dart'; +import 'package:titan/tools/repository/logo_repository.dart'; + +class NewsImageRepository extends LogoRepository { + @override + // ignore: overridden_fields + final ext = 'feed/news/'; + + Future getNewsImage(String id) async { + return await getLogo(id, suffix: "/image"); + } +} + +final newsImageRepositoryProvider = Provider((ref) { + final token = ref.watch(tokenProvider); + return NewsImageRepository()..setToken(token); +}); diff --git a/lib/feed/repositories/news_repository.dart b/lib/feed/repositories/news_repository.dart new file mode 100644 index 0000000000..6713073eca --- /dev/null +++ b/lib/feed/repositories/news_repository.dart @@ -0,0 +1,32 @@ +import 'package:titan/feed/class/news.dart'; +import 'package:titan/tools/repository/repository.dart'; + +class NewsRepository extends Repository { + @override + // ignore: overridden_fields + final ext = "feed/"; + + Future> getPublishedNews() async { + return List.from( + (await getList(suffix: "news")).map((e) => News.fromJson(e)), + ); + } + + Future createNews(News news) async { + return News.fromJson(await create(news.toJson(), suffix: "news")); + } + + Future> getAllNews() async { + return List.from( + (await getList(suffix: "admin/news")).map((e) => News.fromJson(e)), + ); + } + + Future approveNews(String id) async { + return await create({}, suffix: "admin/news/$id/approve"); + } + + Future rejectNews(String id) async { + return await create({}, suffix: "admin/news/$id/reject"); + } +} From f366b8231d89ad79b249e621992fd5ec9ffb65c0 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 10 Aug 2025 15:08:30 +0200 Subject: [PATCH 214/473] feat: adding news providers --- .../providers/admin_news_list_provider.dart | 47 +++++++++++++++++++ lib/feed/providers/news_image_provider.dart | 27 +++++++++++ lib/feed/providers/news_list_provider.dart | 25 ++++++++++ 3 files changed, 99 insertions(+) create mode 100644 lib/feed/providers/admin_news_list_provider.dart create mode 100644 lib/feed/providers/news_image_provider.dart create mode 100644 lib/feed/providers/news_list_provider.dart diff --git a/lib/feed/providers/admin_news_list_provider.dart b/lib/feed/providers/admin_news_list_provider.dart new file mode 100644 index 0000000000..015c4065af --- /dev/null +++ b/lib/feed/providers/admin_news_list_provider.dart @@ -0,0 +1,47 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/auth/providers/openid_provider.dart'; +import 'package:titan/feed/class/news.dart'; +import 'package:titan/feed/repositories/news_repository.dart'; +import 'package:titan/tools/providers/list_notifier.dart'; + +class AdminNewsListNotifier extends ListNotifier { + final NewsRepository newsRepository; + AdminNewsListNotifier({required this.newsRepository}) + : super(const AsyncValue.loading()); + + Future>> loadNewsList() async { + return await loadList(newsRepository.getAllNews); + } + + Future addNews(News news) async { + return await add(newsRepository.createNews, news); + } + + Future approveNews(News news) async { + return await update( + (news) => newsRepository.approveNews(news.id), + (newsList, news) => + newsList..[newsList.indexWhere((d) => d.id == news.id)] = news, + news, + ); + } + + Future rejectNews(News news) async { + return await update( + (news) => newsRepository.rejectNews(news.id), + (newsList, news) => + newsList..[newsList.indexWhere((d) => d.id == news.id)] = news, + news, + ); + } +} + +final newsListProvider = + StateNotifierProvider>>((ref) { + final token = ref.watch(tokenProvider); + final newsRepository = NewsRepository()..setToken(token); + AdminNewsListNotifier newsListNotifier = AdminNewsListNotifier( + newsRepository: newsRepository, + ); + return newsListNotifier; + }); diff --git a/lib/feed/providers/news_image_provider.dart b/lib/feed/providers/news_image_provider.dart new file mode 100644 index 0000000000..47f818096b --- /dev/null +++ b/lib/feed/providers/news_image_provider.dart @@ -0,0 +1,27 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/feed/repositories/news_image_repository.dart'; +import 'package:titan/tools/providers/single_notifier.dart'; + +class NewsImageNotifier extends SingleNotifier { + final NewsImageRepository newsImageRepository; + NewsImageNotifier({required this.newsImageRepository}) + : super(const AsyncLoading()); + + Future> getNewsImage(String userId) async { + return await load( + () async => newsImageRepository.getNewsImage(userId), + ); + } +} + +final newsImageProvider = + StateNotifierProvider>((ref) { + final newsImageRepository = ref.watch( + newsImageRepositoryProvider, + ); + NewsImageNotifier notifier = NewsImageNotifier( + newsImageRepository: newsImageRepository, + ); + return notifier; + }); diff --git a/lib/feed/providers/news_list_provider.dart b/lib/feed/providers/news_list_provider.dart new file mode 100644 index 0000000000..cd9c2bc297 --- /dev/null +++ b/lib/feed/providers/news_list_provider.dart @@ -0,0 +1,25 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/auth/providers/openid_provider.dart'; +import 'package:titan/feed/class/news.dart'; +import 'package:titan/feed/repositories/news_repository.dart'; +import 'package:titan/tools/providers/list_notifier.dart'; + +class NewsListNotifier extends ListNotifier { + final NewsRepository newsRepository; + NewsListNotifier({required this.newsRepository}) + : super(const AsyncValue.loading()); + + Future>> loadNewsList() async { + return await loadList(newsRepository.getPublishedNews); + } +} + +final newsListProvider = + StateNotifierProvider>>((ref) { + final token = ref.watch(tokenProvider); + final newsRepository = NewsRepository()..setToken(token); + NewsListNotifier newsListNotifier = NewsListNotifier( + newsRepository: newsRepository, + ); + return newsListNotifier; + }); From ade3dbf3483ad89710151b009249bd15533e67ed Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 10 Aug 2025 15:08:30 +0200 Subject: [PATCH 215/473] feat: adapting to news class --- lib/feed/class/feed_item.dart | 72 ------------------- lib/feed/tools/function.dart | 2 +- lib/feed/tools/news_status_helper.dart | 32 +++++++++ lib/feed/ui/pages/main_page/event_card.dart | 11 +-- .../ui/pages/main_page/feed_timeline.dart | 6 +- lib/feed/ui/pages/main_page/main_page.dart | 17 +++-- .../ui/pages/main_page/time_line_item.dart | 29 ++++---- 7 files changed, 64 insertions(+), 105 deletions(-) delete mode 100644 lib/feed/class/feed_item.dart create mode 100644 lib/feed/tools/news_status_helper.dart diff --git a/lib/feed/class/feed_item.dart b/lib/feed/class/feed_item.dart deleted file mode 100644 index 46e08bec06..0000000000 --- a/lib/feed/class/feed_item.dart +++ /dev/null @@ -1,72 +0,0 @@ -import 'package:flutter/material.dart'; - -enum FeedItemType { event, action, announcement } - -class FeedItem { - final FeedItemType type; - final String title; - final String subtitle; - final DateTime date; - final String? location; - final String? imageUrl; - final bool isTerminated; - final bool isOngoing; - final bool needsRegistration; - final int? participantsCount; - final VoidCallback? onRegister; - - const FeedItem({ - required this.type, - required this.title, - required this.subtitle, - required this.date, - this.location, - this.imageUrl, - this.isTerminated = false, - this.isOngoing = false, - this.needsRegistration = false, - this.participantsCount, - this.onRegister, - }); - - static List getFakeItems() { - return [ - FeedItem( - type: FeedItemType.announcement, - title: 'Weekly diplo', - subtitle: '55€', - date: DateTime(2025, 12, 18), - ), - FeedItem( - type: FeedItemType.event, - title: 'H11', - subtitle: '19:30 - 20:30 • Foyer', - date: DateTime(2025, 11, 8), - isTerminated: true, - needsRegistration: true, - participantsCount: 33, - onRegister: () {}, - ), - FeedItem( - type: FeedItemType.action, - title: 'Campagne', - subtitle: 'Jusqu\'à minuit', - date: DateTime(2025, 11, 8), - isOngoing: true, - needsRegistration: true, - participantsCount: 33, - onRegister: () {}, - ), - FeedItem( - type: FeedItemType.action, - title: 'Campagne', - subtitle: 'Jusqu\'à minuit', - date: DateTime(2025, 11, 8), - isOngoing: true, - needsRegistration: true, - participantsCount: 33, - onRegister: () {}, - ), - ]; - } -} diff --git a/lib/feed/tools/function.dart b/lib/feed/tools/function.dart index c6ec3426f1..9b9f2097fc 100644 --- a/lib/feed/tools/function.dart +++ b/lib/feed/tools/function.dart @@ -23,4 +23,4 @@ NewsStatus stringToNewsStatus(String status) { default: return NewsStatus.waitingApproval; // Default case } -} \ No newline at end of file +} diff --git a/lib/feed/tools/news_status_helper.dart b/lib/feed/tools/news_status_helper.dart new file mode 100644 index 0000000000..8daddee800 --- /dev/null +++ b/lib/feed/tools/news_status_helper.dart @@ -0,0 +1,32 @@ +import 'package:titan/feed/class/news.dart'; + +bool isNewsTerminated(News news) { + final now = DateTime.now(); + if (news.end != null && news.end!.isBefore(now)) { + return true; + } + return false; +} + +bool isNewsOngoing(News news) { + final now = DateTime.now(); + if (news.start.isBefore(now) && (news.end == null || news.end!.isAfter(now))) { + return true; + } + return false; +} + + +String getNewsSubtitle(News news) { + String subtitle = ''; + if (news.end != null) { + subtitle = '${news.start.toLocal().toIso8601String()} - ${news.end!.toLocal().toIso8601String()}'; + } + if (news.location != null && news.location!.isNotEmpty) { + subtitle += ' | ${news.location}'; + } + if (subtitle.isEmpty) { + subtitle = news.entity; + } + return subtitle; +} \ No newline at end of file diff --git a/lib/feed/ui/pages/main_page/event_card.dart b/lib/feed/ui/pages/main_page/event_card.dart index d00e085d9e..e5fc824f1d 100644 --- a/lib/feed/ui/pages/main_page/event_card.dart +++ b/lib/feed/ui/pages/main_page/event_card.dart @@ -1,9 +1,10 @@ import 'package:flutter/material.dart'; -import 'package:titan/feed/class/feed_item.dart'; +import 'package:titan/feed/class/news.dart'; +import 'package:titan/feed/tools/news_status_helper.dart'; import 'package:titan/tools/constants.dart'; class EventCard extends StatelessWidget { - final FeedItem item; + final News item; const EventCard({super.key, required this.item}); @@ -31,7 +32,7 @@ class EventCard extends StatelessWidget { ), ), Text( - item.subtitle, + getNewsSubtitle(item), style: const TextStyle( fontSize: 12, color: ColorConstants.background, @@ -40,7 +41,7 @@ class EventCard extends StatelessWidget { ], ), ), - if (item.isTerminated) + if (isNewsTerminated(item)) Positioned( bottom: 53, left: 15, @@ -59,7 +60,7 @@ class EventCard extends StatelessWidget { ), ), ), - if (item.isOngoing) + if (isNewsOngoing(item)) Positioned( bottom: 53, left: 15, diff --git a/lib/feed/ui/pages/main_page/feed_timeline.dart b/lib/feed/ui/pages/main_page/feed_timeline.dart index 6e293172af..03317f4efb 100644 --- a/lib/feed/ui/pages/main_page/feed_timeline.dart +++ b/lib/feed/ui/pages/main_page/feed_timeline.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; -import 'package:titan/feed/class/feed_item.dart'; +import 'package:titan/feed/class/news.dart'; import 'package:titan/feed/ui/pages/main_page/time_line_item.dart'; class FeedTimeline extends StatelessWidget { - final List items; - final Function(FeedItem item)? onItemTap; + final List items; + final Function(News item)? onItemTap; final bool isAdmin; const FeedTimeline({super.key, required this.items, this.onItemTap, required this.isAdmin}); diff --git a/lib/feed/ui/pages/main_page/main_page.dart b/lib/feed/ui/pages/main_page/main_page.dart index 3fc8ca5255..37275f174f 100644 --- a/lib/feed/ui/pages/main_page/main_page.dart +++ b/lib/feed/ui/pages/main_page/main_page.dart @@ -4,12 +4,13 @@ import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/admin/providers/is_admin_provider.dart'; -import 'package:titan/feed/class/feed_item.dart'; +import 'package:titan/feed/providers/news_list_provider.dart'; import 'package:titan/feed/router.dart'; import 'package:titan/feed/ui/feed.dart'; import 'package:titan/feed/ui/pages/main_page/feed_timeline.dart'; import 'package:titan/navigation/ui/scroll_to_hide_navbar.dart'; import 'package:titan/tools/constants.dart'; +import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/horizontal_list_view.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; import 'package:titan/tools/ui/styleguide/button.dart'; @@ -22,8 +23,7 @@ class FeedMainPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final feedItems = useState>(FeedItem.getFakeItems()); - final filteredItems = useState>(feedItems.value); + final news = ref.watch(newsListProvider); final isSuperAdmin = ref.watch(isAdminProvider); final scrollController = useScrollController(); @@ -139,10 +139,13 @@ class FeedMainPage extends HookConsumerWidget { child: SingleChildScrollView( controller: scrollController, physics: const BouncingScrollPhysics(), - child: FeedTimeline( - isAdmin: isAdmin, - items: filteredItems.value, - onItemTap: (item) {}, + child: AsyncChild( + value: news, + builder: (context, news) => FeedTimeline( + isAdmin: isAdmin, + items: news, + onItemTap: (item) {}, + ), ), ), ), diff --git a/lib/feed/ui/pages/main_page/time_line_item.dart b/lib/feed/ui/pages/main_page/time_line_item.dart index fcd93247d5..1bc9ad5fb0 100644 --- a/lib/feed/ui/pages/main_page/time_line_item.dart +++ b/lib/feed/ui/pages/main_page/time_line_item.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; -import 'package:titan/feed/class/feed_item.dart'; +import 'package:titan/feed/class/news.dart'; import 'package:titan/feed/ui/pages/main_page/event_action.dart'; import 'package:titan/feed/ui/pages/main_page/event_action_admin.dart'; import 'package:titan/feed/ui/pages/main_page/event_card.dart'; @@ -8,7 +8,7 @@ import 'package:titan/tools/constants.dart'; import 'package:titan/feed/ui/pages/main_page/dotted_vertical_line.dart'; class TimelineItem extends StatelessWidget { - final FeedItem item; + final News item; final VoidCallback? onTap; final bool isAdmin; @@ -22,7 +22,7 @@ class TimelineItem extends StatelessWidget { @override Widget build(BuildContext context) { return SizedBox( - height: item.type == FeedItemType.announcement && !isAdmin ? 160 : 200, + height: item.actionStart != null || isAdmin ? 200 : 160, child: Stack( children: [ Padding( @@ -44,7 +44,7 @@ class TimelineItem extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.center, children: [ Text( - DateFormat('d').format(item.date), + DateFormat('d').format(item.start), style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, @@ -52,7 +52,7 @@ class TimelineItem extends StatelessWidget { ), ), Text( - DateFormat('MMM').format(item.date).toUpperCase(), + DateFormat('MMM').format(item.start).toUpperCase(), style: const TextStyle( fontSize: 12, fontWeight: FontWeight.bold, @@ -70,7 +70,7 @@ class TimelineItem extends StatelessWidget { ), ], ), - if (item.type != FeedItemType.announcement || isAdmin) + if (item.actionStart != null || isAdmin) Padding( padding: const EdgeInsets.only(top: 8), child: Row( @@ -98,17 +98,12 @@ class TimelineItem extends StatelessWidget { child: isAdmin ? EventActionAdmin() : EventAction( - title: item.type == FeedItemType.action - ? 'Tu peux voter' - : 'Tu es invité', - subtitle: item.type == FeedItemType.action - ? '254 votants' - : '75 participants', - onActionPressed: item.onRegister, - actionButtonText: - item.type == FeedItemType.action - ? 'Participer' - : 'Voter', + title: 'Action', + subtitle: 'Indication', + onActionPressed: () { + // Handle action press + }, + actionButtonText: 'Ok', isActionEnabled: true, ), ), From 089f9cbfff79a7ae466ebfb5af5d6365dd5f8319 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 10 Aug 2025 15:08:30 +0200 Subject: [PATCH 216/473] feat: adding admin interaction --- .../providers/admin_news_list_provider.dart | 2 +- .../pages/main_page/event_action_admin.dart | 79 +++++++------------ lib/feed/ui/pages/main_page/main_page.dart | 20 +++-- .../ui/pages/main_page/time_line_item.dart | 4 +- 4 files changed, 48 insertions(+), 57 deletions(-) diff --git a/lib/feed/providers/admin_news_list_provider.dart b/lib/feed/providers/admin_news_list_provider.dart index 015c4065af..0e13f61374 100644 --- a/lib/feed/providers/admin_news_list_provider.dart +++ b/lib/feed/providers/admin_news_list_provider.dart @@ -36,7 +36,7 @@ class AdminNewsListNotifier extends ListNotifier { } } -final newsListProvider = +final adminNewsListProvider = StateNotifierProvider>>((ref) { final token = ref.watch(tokenProvider); final newsRepository = NewsRepository()..setToken(token); diff --git a/lib/feed/ui/pages/main_page/event_action_admin.dart b/lib/feed/ui/pages/main_page/event_action_admin.dart index 5481b5e553..b96c08aa09 100644 --- a/lib/feed/ui/pages/main_page/event_action_admin.dart +++ b/lib/feed/ui/pages/main_page/event_action_admin.dart @@ -1,63 +1,42 @@ import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/feed/class/news.dart'; +import 'package:titan/feed/providers/admin_news_list_provider.dart'; import 'package:titan/tools/constants.dart'; +import 'package:titan/tools/ui/builders/waiting_button.dart'; -class EventActionAdmin extends StatelessWidget { - const EventActionAdmin({super.key}); +class EventActionAdmin extends ConsumerWidget { + final News item; + const EventActionAdmin({super.key, required this.item}); @override - Widget build(BuildContext context) { - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - GestureDetector( - onTap: () {}, - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 5), - width: 100, - decoration: BoxDecoration( - color: ColorConstants.tertiary, - borderRadius: BorderRadius.circular(20), - border: Border.all(color: ColorConstants.onTertiary, width: 2), - ), - child: Center( - child: Text( - "Modifier", - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.bold, - color: ColorConstants.background, - ), - ), - ), + Widget build(BuildContext context, WidgetRef ref) { + final newsAdminNotifier = ref.watch(adminNewsListProvider.notifier); + return Align( + alignment: Alignment.centerRight, + child: WaitingButton( + onTap: () async => await newsAdminNotifier.rejectNews(item), + builder: (child) => Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 5), + width: 100, + decoration: BoxDecoration( + color: ColorConstants.main, + borderRadius: BorderRadius.circular(20), + border: Border.all(color: ColorConstants.onMain, width: 2), ), ), - - const SizedBox(width: 10), - - // Action button - GestureDetector( - onTap: () {}, - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 5), - width: 100, - decoration: BoxDecoration( - color: ColorConstants.main, - borderRadius: BorderRadius.circular(20), - border: Border.all(color: ColorConstants.onMain, width: 2), - ), - child: Center( - child: Text( - "Supprimer", - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.bold, - color: ColorConstants.background, - ), - ), + waitingColor: ColorConstants.background, + child: Center( + child: Text( + "Supprimer", + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: ColorConstants.background, ), ), ), - ], + ), ); } } diff --git a/lib/feed/ui/pages/main_page/main_page.dart b/lib/feed/ui/pages/main_page/main_page.dart index 37275f174f..3437363c54 100644 --- a/lib/feed/ui/pages/main_page/main_page.dart +++ b/lib/feed/ui/pages/main_page/main_page.dart @@ -141,11 +141,21 @@ class FeedMainPage extends HookConsumerWidget { physics: const BouncingScrollPhysics(), child: AsyncChild( value: news, - builder: (context, news) => FeedTimeline( - isAdmin: isAdmin, - items: news, - onItemTap: (item) {}, - ), + builder: (context, news) => news.isEmpty + ? const Center( + child: Text( + 'Aucune actualité disponible', + style: TextStyle( + fontSize: 16, + color: ColorConstants.tertiary, + ), + ), + ) + : FeedTimeline( + isAdmin: isAdmin, + items: news, + onItemTap: (item) {}, + ), ), ), ), diff --git a/lib/feed/ui/pages/main_page/time_line_item.dart b/lib/feed/ui/pages/main_page/time_line_item.dart index 1bc9ad5fb0..3a53cdb05a 100644 --- a/lib/feed/ui/pages/main_page/time_line_item.dart +++ b/lib/feed/ui/pages/main_page/time_line_item.dart @@ -96,7 +96,9 @@ class TimelineItem extends StatelessWidget { ), Expanded( child: isAdmin - ? EventActionAdmin() + ? EventActionAdmin( + item: item, + ) : EventAction( title: 'Action', subtitle: 'Indication', From b3f6fbf0f6c32a8e8c970e1069ee7a2df60bb12d Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 10 Aug 2025 15:08:31 +0200 Subject: [PATCH 217/473] fix: improving date display --- lib/feed/tools/news_status_helper.dart | 132 +++++++++++++++++++- lib/feed/ui/pages/main_page/event_card.dart | 6 +- lib/l10n/app_en.arb | 10 ++ lib/l10n/app_fr.arb | 10 ++ lib/l10n/app_localizations.dart | 60 +++++++++ lib/l10n/app_localizations_en.dart | 30 +++++ lib/l10n/app_localizations_fr.dart | 30 +++++ 7 files changed, 272 insertions(+), 6 deletions(-) diff --git a/lib/feed/tools/news_status_helper.dart b/lib/feed/tools/news_status_helper.dart index 8daddee800..70aa5c0ffc 100644 --- a/lib/feed/tools/news_status_helper.dart +++ b/lib/feed/tools/news_status_helper.dart @@ -1,4 +1,13 @@ +import 'package:flutter/widgets.dart'; import 'package:titan/feed/class/news.dart'; +import 'package:intl/intl.dart'; +import 'package:titan/l10n/app_localizations.dart'; + +/// Capitalizes the first letter of a string +String _capitalize(String text) { + if (text.isEmpty) return text; + return text[0].toUpperCase() + text.substring(1); +} bool isNewsTerminated(News news) { final now = DateTime.now(); @@ -10,23 +19,136 @@ bool isNewsTerminated(News news) { bool isNewsOngoing(News news) { final now = DateTime.now(); - if (news.start.isBefore(now) && (news.end == null || news.end!.isAfter(now))) { + if (news.start.isBefore(now) && + (news.end == null || news.end!.isAfter(now))) { return true; } return false; } +String formatUserFriendlyDate( + DateTime date, { + String locale = 'fr', + required BuildContext context, +}) { + final now = DateTime.now(); + final today = DateTime(now.year, now.month, now.day); + final dateDay = DateTime(date.year, date.month, date.day); + + final timeFormat = DateFormat('HH:mm'); + final time = timeFormat.format(date); + + final connector = AppLocalizations.of(context)?.dateAt ?? 'à'; + + final difference = dateDay.difference(today).inDays; + + if (difference == 0) { + return "${_capitalize(AppLocalizations.of(context)?.dateToday ?? 'Aujourd\'hui')} $connector $time"; + } else if (difference == -1) { + return "${_capitalize(AppLocalizations.of(context)?.dateYesterday ?? 'Hier')} $connector $time"; + } else if (difference == 1) { + return "${_capitalize(AppLocalizations.of(context)?.dateTomorrow ?? 'Demain')} $connector $time"; + } else if (difference > 1 && difference < 7) { + final dayName = _capitalize(DateFormat('EEEE', locale).format(date)); + return "$dayName $connector $time"; + } else if (difference < 0 && difference > -7) { + final dayName = _capitalize(DateFormat('EEEE', locale).format(date)); + final prefix = AppLocalizations.of(context)?.dateLast ?? ''; -String getNewsSubtitle(News news) { + final prefixWithSpace = prefix.isEmpty ? '' : _capitalize('$prefix '); + return "$prefixWithSpace$dayName $connector $time"; + } else { + if (date.year == now.year) { + final monthDay = _capitalize(DateFormat('d MMM', locale).format(date)); + return "$monthDay $connector $time"; + } else { + final dateFormat = locale == 'fr' ? 'd MMM yyyy' : 'MMM d, yyyy'; + final monthDayYear = _capitalize( + DateFormat(dateFormat, locale).format(date), + ); + return "$monthDayYear $connector $time"; + } + } +} + +String getNewsSubtitle( + News news, { + String locale = 'fr', + required BuildContext context, +}) { String subtitle = ''; - if (news.end != null) { - subtitle = '${news.start.toLocal().toIso8601String()} - ${news.end!.toLocal().toIso8601String()}'; + + final startDate = news.start.toLocal(); + + // For ongoing events, just display the end date if available + if (isNewsOngoing(news) && news.end != null) { + final untilText = _capitalize( + AppLocalizations.of(context)?.dateUntil ?? + (locale == 'fr' ? "Jusqu'au" : "Until"), + ); + subtitle = + "$untilText ${formatUserFriendlyDate(news.end!.toLocal(), locale: locale, context: context)}"; + } + // For events with no end date, just display the start date + else if (news.end == null) { + subtitle = formatUserFriendlyDate( + startDate, + locale: locale, + context: context, + ); } + // For events with both start and end dates that are not ongoing + else { + final endDate = news.end!.toLocal(); + bool sameDay = + startDate.year == endDate.year && + startDate.month == endDate.month && + startDate.day == endDate.day; + + if (sameDay) { + final connector = AppLocalizations.of(context)?.dateAt ?? 'à'; + + String dateStr = formatUserFriendlyDate( + startDate, + locale: locale, + context: context, + ).split(' $connector ')[0]; + + final startTime = DateFormat('HH:mm').format(startDate); + final endTime = DateFormat('HH:mm').format(endDate); + + final fromWord = AppLocalizations.of(context)?.dateFrom ?? 'de'; + final toWord = AppLocalizations.of(context)?.dateTo ?? 'à'; + + subtitle = '$dateStr $fromWord $startTime $toWord $endTime'; + } else { + final fromWord = _capitalize( + AppLocalizations.of(context)?.dateFrom ?? 'de', + ); + + // Determine if the end date is a special date (today, yesterday, tomorrow) + final now = DateTime.now(); + final today = DateTime(now.year, now.month, now.day); + final endDateTime = DateTime(endDate.year, endDate.month, endDate.day); + final difference = endDateTime.difference(today).inDays; + + // Use "à" (dateTo) instead of "au" (dateBetweenDays) for special dates + final toWord = (difference >= -1 && difference <= 1) + ? (AppLocalizations.of(context)?.dateTo ?? 'à') + : (AppLocalizations.of(context)?.dateBetweenDays ?? 'au'); + + subtitle = + '$fromWord ${formatUserFriendlyDate(startDate, locale: locale, context: context)} $toWord ${formatUserFriendlyDate(endDate, locale: locale, context: context)}'; + } + } + if (news.location != null && news.location!.isNotEmpty) { subtitle += ' | ${news.location}'; } + if (subtitle.isEmpty) { subtitle = news.entity; } + return subtitle; -} \ No newline at end of file +} diff --git a/lib/feed/ui/pages/main_page/event_card.dart b/lib/feed/ui/pages/main_page/event_card.dart index e5fc824f1d..70a516caa2 100644 --- a/lib/feed/ui/pages/main_page/event_card.dart +++ b/lib/feed/ui/pages/main_page/event_card.dart @@ -32,7 +32,11 @@ class EventCard extends StatelessWidget { ), ), Text( - getNewsSubtitle(item), + getNewsSubtitle( + item, + locale: Localizations.localeOf(context).languageCode, + context: context, + ), style: const TextStyle( fontSize: 12, color: ColorConstants.background, diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 80a3ef892e..e2de7b5815 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1,5 +1,15 @@ { "@@locale": "en", + "dateToday": "Today", + "dateYesterday": "Yesterday", + "dateTomorrow": "Tomorrow", + "dateAt": "at", + "dateFrom": "from", + "dateTo": "to", + "dateBetweenDays": "to", + "dateStarting": "Starting", + "dateLast": "Last", + "dateUntil": "Until", "adminAccountTypes": "Account types", "adminAdd": "Add", "adminAddGroup": "Add group", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index d0e8de078f..3103a3c970 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -1,5 +1,15 @@ { "@@locale": "fr", + "dateToday": "Aujourd'hui", + "dateYesterday": "Hier", + "dateTomorrow": "Demain", + "dateAt": "à", + "dateFrom": "de", + "dateTo": "à", + "dateBetweenDays": "au", + "dateStarting": "Commence", + "dateLast": "", + "dateUntil": "Jusqu'au", "adminAccountTypes": "Types de compte", "adminAdd": "Ajouter", "adminAddGroup": "Ajouter un groupe", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index bb0272a656..9c18a2f6d7 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -98,6 +98,66 @@ abstract class AppLocalizations { Locale('fr'), ]; + /// No description provided for @dateToday. + /// + /// In fr, this message translates to: + /// **'Aujourd\'hui'** + String get dateToday; + + /// No description provided for @dateYesterday. + /// + /// In fr, this message translates to: + /// **'Hier'** + String get dateYesterday; + + /// No description provided for @dateTomorrow. + /// + /// In fr, this message translates to: + /// **'Demain'** + String get dateTomorrow; + + /// No description provided for @dateAt. + /// + /// In fr, this message translates to: + /// **'à'** + String get dateAt; + + /// No description provided for @dateFrom. + /// + /// In fr, this message translates to: + /// **'de'** + String get dateFrom; + + /// No description provided for @dateTo. + /// + /// In fr, this message translates to: + /// **'à'** + String get dateTo; + + /// No description provided for @dateBetweenDays. + /// + /// In fr, this message translates to: + /// **'au'** + String get dateBetweenDays; + + /// No description provided for @dateStarting. + /// + /// In fr, this message translates to: + /// **'Commence'** + String get dateStarting; + + /// No description provided for @dateLast. + /// + /// In fr, this message translates to: + /// **''** + String get dateLast; + + /// No description provided for @dateUntil. + /// + /// In fr, this message translates to: + /// **'Jusqu\'au'** + String get dateUntil; + /// No description provided for @adminAccountTypes. /// /// In fr, this message translates to: diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index f0e06e2fc3..97392b1ee5 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -8,6 +8,36 @@ import 'app_localizations.dart'; class AppLocalizationsEn extends AppLocalizations { AppLocalizationsEn([String locale = 'en']) : super(locale); + @override + String get dateToday => 'Today'; + + @override + String get dateYesterday => 'Yesterday'; + + @override + String get dateTomorrow => 'Tomorrow'; + + @override + String get dateAt => 'at'; + + @override + String get dateFrom => 'from'; + + @override + String get dateTo => 'to'; + + @override + String get dateBetweenDays => 'to'; + + @override + String get dateStarting => 'Starting'; + + @override + String get dateLast => 'Last'; + + @override + String get dateUntil => 'Until'; + @override String get adminAccountTypes => 'Account types'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 8f8c8cd2e9..00e226bb7e 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -8,6 +8,36 @@ import 'app_localizations.dart'; class AppLocalizationsFr extends AppLocalizations { AppLocalizationsFr([String locale = 'fr']) : super(locale); + @override + String get dateToday => 'Aujourd\'hui'; + + @override + String get dateYesterday => 'Hier'; + + @override + String get dateTomorrow => 'Demain'; + + @override + String get dateAt => 'à'; + + @override + String get dateFrom => 'de'; + + @override + String get dateTo => 'à'; + + @override + String get dateBetweenDays => 'au'; + + @override + String get dateStarting => 'Commence'; + + @override + String get dateLast => ''; + + @override + String get dateUntil => 'Jusqu\'au'; + @override String get adminAccountTypes => 'Types de compte'; From 77d4f08c54161a5ba4d6d8bf97a257a0d0cf106e Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 10 Aug 2025 15:08:31 +0200 Subject: [PATCH 218/473] fix: displaying delete button child --- lib/feed/ui/pages/main_page/event_action_admin.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/feed/ui/pages/main_page/event_action_admin.dart b/lib/feed/ui/pages/main_page/event_action_admin.dart index b96c08aa09..57f8e26493 100644 --- a/lib/feed/ui/pages/main_page/event_action_admin.dart +++ b/lib/feed/ui/pages/main_page/event_action_admin.dart @@ -24,6 +24,7 @@ class EventActionAdmin extends ConsumerWidget { borderRadius: BorderRadius.circular(20), border: Border.all(color: ColorConstants.onMain, width: 2), ), + child: child, ), waitingColor: ColorConstants.background, child: Center( From 9ccf038eadc2d0e469cd3ff7dca6eabba5899bfa Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 10 Aug 2025 15:08:31 +0200 Subject: [PATCH 219/473] fix: loading news lists --- lib/feed/providers/admin_news_list_provider.dart | 2 +- lib/feed/providers/news_list_provider.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/feed/providers/admin_news_list_provider.dart b/lib/feed/providers/admin_news_list_provider.dart index 0e13f61374..33561aa404 100644 --- a/lib/feed/providers/admin_news_list_provider.dart +++ b/lib/feed/providers/admin_news_list_provider.dart @@ -42,6 +42,6 @@ final adminNewsListProvider = final newsRepository = NewsRepository()..setToken(token); AdminNewsListNotifier newsListNotifier = AdminNewsListNotifier( newsRepository: newsRepository, - ); + )..loadNewsList(); return newsListNotifier; }); diff --git a/lib/feed/providers/news_list_provider.dart b/lib/feed/providers/news_list_provider.dart index cd9c2bc297..b6a3f1eb53 100644 --- a/lib/feed/providers/news_list_provider.dart +++ b/lib/feed/providers/news_list_provider.dart @@ -20,6 +20,6 @@ final newsListProvider = final newsRepository = NewsRepository()..setToken(token); NewsListNotifier newsListNotifier = NewsListNotifier( newsRepository: newsRepository, - ); + )..loadNewsList(); return newsListNotifier; }); From be53fb2997b0f26af7f40801023f254ac890e400 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 10 Aug 2025 15:08:33 +0200 Subject: [PATCH 220/473] feat: admin page improvement --- lib/feed/repositories/news_repository.dart | 103 +++++++++- lib/feed/tools/news_filter_type.dart | 16 ++ .../event_handling_page/admin_event_card.dart | 181 ++++++++++++++++++ .../event_handling_page.dart | 148 +++++++++++++- lib/feed/ui/pages/main_page/main_page.dart | 2 + lib/l10n/app_en.arb | 9 + lib/l10n/app_fr.arb | 9 + lib/l10n/app_localizations.dart | 54 ++++++ lib/l10n/app_localizations_en.dart | 27 +++ lib/l10n/app_localizations_fr.dart | 27 +++ .../styleguide/horizontal_multi_select.dart | 1 + 11 files changed, 570 insertions(+), 7 deletions(-) create mode 100644 lib/feed/tools/news_filter_type.dart create mode 100644 lib/feed/ui/pages/event_handling_page/admin_event_card.dart diff --git a/lib/feed/repositories/news_repository.dart b/lib/feed/repositories/news_repository.dart index 6713073eca..dcff66668a 100644 --- a/lib/feed/repositories/news_repository.dart +++ b/lib/feed/repositories/news_repository.dart @@ -1,4 +1,5 @@ import 'package:titan/feed/class/news.dart'; +import 'package:titan/feed/tools/function.dart'; import 'package:titan/tools/repository/repository.dart'; class NewsRepository extends Repository { @@ -7,9 +8,54 @@ class NewsRepository extends Repository { final ext = "feed/"; Future> getPublishedNews() async { - return List.from( - (await getList(suffix: "news")).map((e) => News.fromJson(e)), - ); + // return List.from( + // (await getList(suffix: "news")).map((e) => News.fromJson(e)), + // ); + return Future.value([ + News( + id: '', + title: 'Test', + start: DateTime.now().subtract(const Duration(days: 1)), + entity: 'BDE', + module: 'post', + moduleObjectId: '', + status: NewsStatus.published, + ), + News( + id: '', + title: 'Vote', + start: DateTime.now().subtract(const Duration(days: 2)), + end: DateTime.now().add(const Duration(days: 2)), + actionStart: DateTime.now().subtract(const Duration(days: 2)), + entity: 'CAA', + module: 'campagne', + moduleObjectId: '', + status: NewsStatus.published, + ), + News( + id: '', + title: 'Rewass', + start: DateTime.now().add(const Duration(days: 3)), + end: DateTime.now().add(const Duration(days: 7)), + actionStart: DateTime.now().subtract(const Duration(days: 1)), + entity: 'DBS', + module: 'event', + moduleObjectId: '', + location: 'Foyer', + status: NewsStatus.published, + ), + News( + id: '', + title: 'Test 4', + start: DateTime.now().subtract(const Duration(days: 2)), + end: DateTime.now().subtract(const Duration(days: 1)), + actionStart: DateTime.now().subtract(const Duration(days: 3)), + entity: 'DBS', + module: 'event', + moduleObjectId: '', + status: NewsStatus.published, + ), + ]); } Future createNews(News news) async { @@ -17,9 +63,54 @@ class NewsRepository extends Repository { } Future> getAllNews() async { - return List.from( - (await getList(suffix: "admin/news")).map((e) => News.fromJson(e)), - ); + // return List.from( + // (await getList(suffix: "admin/news")).map((e) => News.fromJson(e)), + // ); + return Future.value([ + News( + id: '', + title: 'Test', + start: DateTime.now().subtract(const Duration(days: 1)), + entity: 'BDE', + module: 'post', + moduleObjectId: '', + status: NewsStatus.waitingApproval, + ), + News( + id: '', + title: 'Vote', + start: DateTime.now().subtract(const Duration(days: 2)), + end: DateTime.now().add(const Duration(days: 2)), + actionStart: DateTime.now().subtract(const Duration(days: 2)), + entity: 'CAA', + module: 'campagne', + moduleObjectId: '', + status: NewsStatus.waitingApproval, + ), + News( + id: '', + title: 'Rewass', + start: DateTime.now().add(const Duration(days: 3)), + end: DateTime.now().add(const Duration(days: 7)), + actionStart: DateTime.now().subtract(const Duration(days: 1)), + entity: 'DBS', + module: 'event', + moduleObjectId: '', + location: 'Foyer', + status: NewsStatus.waitingApproval, + ), + News( + id: '', + title: 'Test 4', + start: DateTime.now().subtract(const Duration(days: 2)), + end: DateTime.now().subtract(const Duration(days: 1)), + actionStart: DateTime.now().subtract(const Duration(days: 3)), + entity: 'DBS', + module: 'event', + moduleObjectId: '', + status: NewsStatus.waitingApproval, + ), + ]); } Future approveNews(String id) async { diff --git a/lib/feed/tools/news_filter_type.dart b/lib/feed/tools/news_filter_type.dart new file mode 100644 index 0000000000..91d369cb09 --- /dev/null +++ b/lib/feed/tools/news_filter_type.dart @@ -0,0 +1,16 @@ +enum NewsFilterType { all, pending, approved, rejected } + +extension NewsFilterTypeExtension on NewsFilterType { + String getKey() { + switch (this) { + case NewsFilterType.all: + return 'feedFilterAll'; + case NewsFilterType.pending: + return 'feedFilterPending'; + case NewsFilterType.approved: + return 'feedFilterApproved'; + case NewsFilterType.rejected: + return 'feedFilterRejected'; + } + } +} diff --git a/lib/feed/ui/pages/event_handling_page/admin_event_card.dart b/lib/feed/ui/pages/event_handling_page/admin_event_card.dart new file mode 100644 index 0000000000..ed296e6103 --- /dev/null +++ b/lib/feed/ui/pages/event_handling_page/admin_event_card.dart @@ -0,0 +1,181 @@ +import 'package:flutter/material.dart'; +import 'package:heroicons/heroicons.dart'; +import 'package:titan/feed/class/news.dart'; +import 'package:titan/feed/tools/news_status_helper.dart'; +import 'package:titan/tools/constants.dart'; +import 'package:titan/tools/ui/builders/waiting_button.dart'; + +class AdminEventCard extends StatelessWidget { + final News news; + const AdminEventCard({super.key, required this.news}); + + @override + Widget build(BuildContext context) { + final locale = Localizations.localeOf(context).languageCode; + + return Container( + decoration: BoxDecoration( + color: ColorConstants.background, + borderRadius: BorderRadius.circular(15), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.1), + blurRadius: 4, + offset: const Offset(0, 2), + ), + ], + ), + margin: const EdgeInsets.symmetric(vertical: 10), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Text( + news.title, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: ColorConstants.title, + ), + ), + ), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + decoration: BoxDecoration( + color: ColorConstants.secondary.withValues(alpha: 0.2), + borderRadius: BorderRadius.circular(8), + ), + child: Text( + news.entity, + style: const TextStyle( + fontSize: 12, + color: ColorConstants.secondary, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + + const SizedBox(height: 8), + + Row( + children: [ + const HeroIcon( + HeroIcons.calendar, + size: 16, + color: ColorConstants.tertiary, + ), + const SizedBox(width: 4), + Expanded( + child: Text( + getNewsSubtitle(news, locale: locale, context: context), + style: const TextStyle( + fontSize: 14, + color: ColorConstants.tertiary, + ), + ), + ), + ], + ), + + if (news.location != null && news.location!.isNotEmpty) ...[ + const SizedBox(height: 4), + Row( + children: [ + const HeroIcon( + HeroIcons.mapPin, + size: 16, + color: ColorConstants.tertiary, + ), + const SizedBox(width: 4), + Text( + news.location!, + style: const TextStyle( + fontSize: 14, + color: ColorConstants.tertiary, + ), + ), + ], + ), + ], + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + WaitingButton( + onTap: () async {}, + builder: (child) => Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 5, + ), + width: 100, + decoration: BoxDecoration( + color: ColorConstants.main, + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: ColorConstants.onMain, + width: 2, + ), + ), + child: child, + ), + waitingColor: ColorConstants.background, + child: Center( + child: Text( + "Rejeter", + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: ColorConstants.background, + ), + ), + ), + ), + SizedBox(width: 10), + WaitingButton( + onTap: () async {}, + builder: (child) => Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 5, + ), + width: 100, + decoration: BoxDecoration( + color: ColorConstants.tertiary, + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: ColorConstants.onTertiary, + width: 2, + ), + ), + child: child, + ), + waitingColor: ColorConstants.background, + child: Center( + child: Text( + "Approuver", + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: ColorConstants.background, + ), + ), + ), + ), + ], + ), + ], + ), + ), + ); + } +} diff --git a/lib/feed/ui/pages/event_handling_page/event_handling_page.dart b/lib/feed/ui/pages/event_handling_page/event_handling_page.dart index a432965721..b0edea3943 100644 --- a/lib/feed/ui/pages/event_handling_page/event_handling_page.dart +++ b/lib/feed/ui/pages/event_handling_page/event_handling_page.dart @@ -1,12 +1,158 @@ import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/feed/class/news.dart'; +import 'package:titan/feed/providers/admin_news_list_provider.dart'; +import 'package:titan/feed/tools/function.dart'; +import 'package:titan/feed/tools/news_filter_type.dart'; import 'package:titan/feed/ui/feed.dart'; +import 'package:titan/feed/ui/pages/event_handling_page/admin_event_card.dart'; +import 'package:titan/l10n/app_localizations.dart'; +import 'package:titan/tools/constants.dart'; +import 'package:titan/tools/ui/builders/async_child.dart'; +import 'package:titan/tools/ui/styleguide/horizontal_multi_select.dart'; class EventHandlingPage extends HookConsumerWidget { const EventHandlingPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - return FeedTemplate(child: Container()); + final newsListAsync = ref.watch(adminNewsListProvider); + final newsListNotifier = ref.watch(adminNewsListProvider.notifier); + final selectedFilter = useState(NewsFilterType.pending); + + return FeedTemplate( + child: RefreshIndicator( + onRefresh: () => newsListNotifier.loadNewsList(), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 16), + + Text( + AppLocalizations.of(context)?.feedEventManagement ?? + 'Event Management', + style: const TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: ColorConstants.title, + ), + ), + + const SizedBox(height: 16), + + SizedBox( + height: 40, + child: HorizontalMultiSelect( + items: NewsFilterType.values, + itemBuilder: (context, item, index, selected) { + final filterName = _getFilterName(context, item); + return Text( + filterName, + style: TextStyle( + color: selected + ? ColorConstants.background + : ColorConstants.tertiary, + fontWeight: selected + ? FontWeight.bold + : FontWeight.normal, + ), + ); + }, + onItemSelected: (item) { + selectedFilter.value = item; + }, + ), + ), + + const SizedBox(height: 16), + + Expanded( + child: AsyncChild( + value: newsListAsync, + builder: (context, newsList) { + final filteredNews = _getFilteredNews( + newsList, + selectedFilter.value, + ); + + if (filteredNews.isEmpty) { + return Center( + child: Text( + _getEmptyMessage(context, selectedFilter.value), + style: const TextStyle( + color: ColorConstants.tertiary, + ), + ), + ); + } + + return ListView.builder( + physics: const BouncingScrollPhysics(), + itemCount: filteredNews.length + 1, + itemBuilder: (context, index) { + if (index == filteredNews.length) { + return const SizedBox(height: 80); + } + return AdminEventCard(news: filteredNews[index]); + }, + ); + }, + ), + ), + ], + ), + ), + ), + ); + } + + String _getFilterName(BuildContext context, NewsFilterType filter) { + final localizations = AppLocalizations.of(context); + switch (filter) { + case NewsFilterType.all: + return localizations?.feedFilterAll ?? 'All'; + case NewsFilterType.pending: + return localizations?.feedFilterPending ?? 'Pending'; + case NewsFilterType.approved: + return localizations?.feedFilterApproved ?? 'Approved'; + case NewsFilterType.rejected: + return localizations?.feedFilterRejected ?? 'Rejected'; + } + } + + List _getFilteredNews(List allNews, NewsFilterType filter) { + switch (filter) { + case NewsFilterType.all: + return allNews; + case NewsFilterType.pending: + return allNews + .where((news) => news.status == NewsStatus.waitingApproval) + .toList(); + case NewsFilterType.approved: + return allNews + .where((news) => news.status == NewsStatus.published) + .toList(); + case NewsFilterType.rejected: + return allNews + .where((news) => news.status == NewsStatus.rejected) + .toList(); + } + } + + String _getEmptyMessage(BuildContext context, NewsFilterType filter) { + final localizations = AppLocalizations.of(context); + switch (filter) { + case NewsFilterType.all: + return localizations?.feedEmptyAll ?? 'No events available'; + case NewsFilterType.pending: + return localizations?.feedEmptyPending ?? 'No events pending approval'; + case NewsFilterType.approved: + return localizations?.feedEmptyApproved ?? 'No approved events'; + case NewsFilterType.rejected: + return localizations?.feedEmptyRejected ?? 'No rejected events'; + } } } diff --git a/lib/feed/ui/pages/main_page/main_page.dart b/lib/feed/ui/pages/main_page/main_page.dart index 3437363c54..abb694757f 100644 --- a/lib/feed/ui/pages/main_page/main_page.dart +++ b/lib/feed/ui/pages/main_page/main_page.dart @@ -108,6 +108,7 @@ class FeedMainPage extends HookConsumerWidget { Button( text: 'Créer un événement', onPressed: () { + Navigator.of(context).pop(); QR.to(FeedRouter.root + FeedRouter.addEvent); }, ), @@ -115,6 +116,7 @@ class FeedMainPage extends HookConsumerWidget { Button( text: 'Demandes de publication', onPressed: () { + Navigator.of(context).pop(); QR.to( FeedRouter.root + FeedRouter.eventHandling, ); diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index e2de7b5815..cb6e107f40 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -10,6 +10,15 @@ "dateStarting": "Starting", "dateLast": "Last", "dateUntil": "Until", + "feedFilterAll": "All", + "feedFilterPending": "Pending", + "feedFilterApproved": "Approved", + "feedFilterRejected": "Rejected", + "feedEmptyAll": "No events available", + "feedEmptyPending": "No events pending approval", + "feedEmptyApproved": "No approved events", + "feedEmptyRejected": "No rejected events", + "feedEventManagement": "Event Management", "adminAccountTypes": "Account types", "adminAdd": "Add", "adminAddGroup": "Add group", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 3103a3c970..95f40139a7 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -10,6 +10,15 @@ "dateStarting": "Commence", "dateLast": "", "dateUntil": "Jusqu'au", + "feedFilterAll": "Tous", + "feedFilterPending": "En attente", + "feedFilterApproved": "Approuvés", + "feedFilterRejected": "Rejetés", + "feedEmptyAll": "Aucun événement disponible", + "feedEmptyPending": "Aucun événement en attente de validation", + "feedEmptyApproved": "Aucun événement approuvé", + "feedEmptyRejected": "Aucun événement rejeté", + "feedEventManagement": "Gestion des événements", "adminAccountTypes": "Types de compte", "adminAdd": "Ajouter", "adminAddGroup": "Ajouter un groupe", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 9c18a2f6d7..609a3135d4 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -158,6 +158,60 @@ abstract class AppLocalizations { /// **'Jusqu\'au'** String get dateUntil; + /// No description provided for @feedFilterAll. + /// + /// In fr, this message translates to: + /// **'Tous'** + String get feedFilterAll; + + /// No description provided for @feedFilterPending. + /// + /// In fr, this message translates to: + /// **'En attente'** + String get feedFilterPending; + + /// No description provided for @feedFilterApproved. + /// + /// In fr, this message translates to: + /// **'Approuvés'** + String get feedFilterApproved; + + /// No description provided for @feedFilterRejected. + /// + /// In fr, this message translates to: + /// **'Rejetés'** + String get feedFilterRejected; + + /// No description provided for @feedEmptyAll. + /// + /// In fr, this message translates to: + /// **'Aucun événement disponible'** + String get feedEmptyAll; + + /// No description provided for @feedEmptyPending. + /// + /// In fr, this message translates to: + /// **'Aucun événement en attente de validation'** + String get feedEmptyPending; + + /// No description provided for @feedEmptyApproved. + /// + /// In fr, this message translates to: + /// **'Aucun événement approuvé'** + String get feedEmptyApproved; + + /// No description provided for @feedEmptyRejected. + /// + /// In fr, this message translates to: + /// **'Aucun événement rejeté'** + String get feedEmptyRejected; + + /// No description provided for @feedEventManagement. + /// + /// In fr, this message translates to: + /// **'Gestion des événements'** + String get feedEventManagement; + /// No description provided for @adminAccountTypes. /// /// In fr, this message translates to: diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 97392b1ee5..f51e1adab5 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -38,6 +38,33 @@ class AppLocalizationsEn extends AppLocalizations { @override String get dateUntil => 'Until'; + @override + String get feedFilterAll => 'All'; + + @override + String get feedFilterPending => 'Pending'; + + @override + String get feedFilterApproved => 'Approved'; + + @override + String get feedFilterRejected => 'Rejected'; + + @override + String get feedEmptyAll => 'No events available'; + + @override + String get feedEmptyPending => 'No events pending approval'; + + @override + String get feedEmptyApproved => 'No approved events'; + + @override + String get feedEmptyRejected => 'No rejected events'; + + @override + String get feedEventManagement => 'Event Management'; + @override String get adminAccountTypes => 'Account types'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 00e226bb7e..729424691f 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -38,6 +38,33 @@ class AppLocalizationsFr extends AppLocalizations { @override String get dateUntil => 'Jusqu\'au'; + @override + String get feedFilterAll => 'Tous'; + + @override + String get feedFilterPending => 'En attente'; + + @override + String get feedFilterApproved => 'Approuvés'; + + @override + String get feedFilterRejected => 'Rejetés'; + + @override + String get feedEmptyAll => 'Aucun événement disponible'; + + @override + String get feedEmptyPending => 'Aucun événement en attente de validation'; + + @override + String get feedEmptyApproved => 'Aucun événement approuvé'; + + @override + String get feedEmptyRejected => 'Aucun événement rejeté'; + + @override + String get feedEventManagement => 'Gestion des événements'; + @override String get adminAccountTypes => 'Types de compte'; diff --git a/lib/tools/ui/styleguide/horizontal_multi_select.dart b/lib/tools/ui/styleguide/horizontal_multi_select.dart index b28e934218..6878548c77 100644 --- a/lib/tools/ui/styleguide/horizontal_multi_select.dart +++ b/lib/tools/ui/styleguide/horizontal_multi_select.dart @@ -26,6 +26,7 @@ class HorizontalMultiSelect extends HookWidget { return ListView.builder( scrollDirection: Axis.horizontal, clipBehavior: Clip.none, + physics: const BouncingScrollPhysics(), itemCount: items.length + (firstChild != null ? 1 : 0), itemBuilder: (context, index) { if (index == 0 && firstChild != null) { From bbae8ab18181fc0baca6a1833d6b23fcd6f2243d Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 10 Aug 2025 15:08:33 +0200 Subject: [PATCH 221/473] feat: adding filter --- lib/feed/providers/news_list_provider.dart | 18 +++- lib/feed/ui/pages/main_page/filter_news.dart | 106 +++++++++++++++++++ lib/feed/ui/pages/main_page/main_page.dart | 71 ++++--------- 3 files changed, 144 insertions(+), 51 deletions(-) create mode 100644 lib/feed/ui/pages/main_page/filter_news.dart diff --git a/lib/feed/providers/news_list_provider.dart b/lib/feed/providers/news_list_provider.dart index b6a3f1eb53..8056cdbd32 100644 --- a/lib/feed/providers/news_list_provider.dart +++ b/lib/feed/providers/news_list_provider.dart @@ -6,11 +6,27 @@ import 'package:titan/tools/providers/list_notifier.dart'; class NewsListNotifier extends ListNotifier { final NewsRepository newsRepository; + AsyncValue> allNews = const AsyncValue.loading(); NewsListNotifier({required this.newsRepository}) : super(const AsyncValue.loading()); Future>> loadNewsList() async { - return await loadList(newsRepository.getPublishedNews); + return allNews = await loadList(newsRepository.getPublishedNews); + } + + void filterNews(List entities, List modules) { + state = AsyncValue.data( + (allNews.value ?? []).where((news) { + final matchesEntity = + entities.isEmpty || entities.contains(news.entity); + final matchesModule = modules.isEmpty || modules.contains(news.module); + return matchesEntity && matchesModule; + }).toList(), + ); + } + + void resetFilters() { + state = AsyncValue.data(allNews.value ?? []); } } diff --git a/lib/feed/ui/pages/main_page/filter_news.dart b/lib/feed/ui/pages/main_page/filter_news.dart new file mode 100644 index 0000000000..a2f0e5fc02 --- /dev/null +++ b/lib/feed/ui/pages/main_page/filter_news.dart @@ -0,0 +1,106 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/feed/providers/news_list_provider.dart'; +import 'package:titan/tools/ui/layouts/horizontal_list_view.dart'; +import 'package:titan/tools/ui/layouts/item_chip.dart'; +import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; +import 'package:titan/tools/ui/styleguide/button.dart'; + +class FilterNewsModal extends StatelessWidget { + final List entities, modules; + const FilterNewsModal({ + super.key, + required this.entities, + required this.modules, + }); + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, ref, child) { + final newsListNotifier = ref.watch(newsListProvider.notifier); + final selectedEntities = useState>([]); + final selectedModules = useState>([]); + return BottomModalTemplate( + title: 'Filtrer', + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Association'), + SizedBox(height: 10), + HorizontalListView( + height: 50, + children: entities + .map( + (entity) => ItemChip( + selected: selectedEntities.value.contains(entity), + onTap: () { + if (selectedEntities.value.contains(entity)) { + selectedEntities.value.remove(entity); + } else { + selectedEntities.value.add(entity); + } + if (selectedEntities.value.isEmpty && + selectedModules.value.isEmpty) { + newsListNotifier.resetFilters(); + } else { + newsListNotifier.filterNews( + selectedEntities.value, + selectedModules.value, + ); + } + newsListNotifier.filterNews( + selectedEntities.value, + selectedModules.value, + ); + }, + child: Text(entity), + ), + ) + .toList(), + ), + SizedBox(height: 30), + Text('Type d\'annonce'), + SizedBox(height: 10), + HorizontalListView( + height: 50, + children: modules + .map( + (module) => ItemChip( + selected: selectedModules.value.contains(module), + onTap: () { + if (selectedModules.value.contains(module)) { + selectedModules.value.remove(module); + } else { + selectedModules.value.add(module); + } + if (selectedEntities.value.isEmpty && + selectedModules.value.isEmpty) { + newsListNotifier.resetFilters(); + } else { + newsListNotifier.filterNews( + selectedEntities.value, + selectedModules.value, + ); + } + }, + child: Text(module), + ), + ) + .toList(), + ), + SizedBox(height: 40), + Button( + text: 'Appliquer', + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ), + ); + }, + ); + } +} diff --git a/lib/feed/ui/pages/main_page/main_page.dart b/lib/feed/ui/pages/main_page/main_page.dart index abb694757f..d542c20a41 100644 --- a/lib/feed/ui/pages/main_page/main_page.dart +++ b/lib/feed/ui/pages/main_page/main_page.dart @@ -4,18 +4,18 @@ import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/admin/providers/is_admin_provider.dart'; +import 'package:titan/feed/class/news.dart'; import 'package:titan/feed/providers/news_list_provider.dart'; import 'package:titan/feed/router.dart'; import 'package:titan/feed/ui/feed.dart'; import 'package:titan/feed/ui/pages/main_page/feed_timeline.dart'; +import 'package:titan/feed/ui/pages/main_page/filter_news.dart'; import 'package:titan/navigation/ui/scroll_to_hide_navbar.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; -import 'package:titan/tools/ui/layouts/horizontal_list_view.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; import 'package:titan/tools/ui/styleguide/button.dart'; import 'package:titan/tools/ui/styleguide/icon_button.dart'; -import 'package:titan/tools/ui/styleguide/item_chip.dart'; import 'package:titan/tools/ui/styleguide/searchbar.dart'; class FeedMainPage extends HookConsumerWidget { @@ -35,43 +35,14 @@ class FeedMainPage extends HookConsumerWidget { children: [ CustomSearchBar( onFilter: () async { + final syncNews = news.maybeWhen( + orElse: () => [], + data: (loaded) => loaded, + ); + final entities = syncNews.map((e) => e.entity).toList(); + final modules = syncNews.map((e) => e.module).toList(); await showCustomBottomModal( - modal: BottomModalTemplate( - title: 'Filtrer', - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('Groupes d\'association'), - SizedBox(height: 10), - HorizontalListView( - height: 50, - children: [ - ItemChip(child: Text('Option 1')), - ItemChip(child: Text('Option 2')), - ItemChip(child: Text('Option 3')), - ], - ), - SizedBox(height: 30), - Text('Associations'), - SizedBox(height: 10), - HorizontalListView( - height: 50, - children: [ - ItemChip(child: Text('Association 1')), - ItemChip(child: Text('Association 2')), - ItemChip(child: Text('Association 3')), - ], - ), - SizedBox(height: 40), - Button( - text: 'Appliquer', - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - ), - ), + modal: FilterNewsModal(entities: entities, modules: modules), context: context, ref: ref, ); @@ -142,22 +113,22 @@ class FeedMainPage extends HookConsumerWidget { controller: scrollController, physics: const BouncingScrollPhysics(), child: AsyncChild( - value: news, - builder: (context, news) => news.isEmpty - ? const Center( - child: Text( - 'Aucune actualité disponible', - style: TextStyle( - fontSize: 16, - color: ColorConstants.tertiary, + value: news, + builder: (context, news) => news.isEmpty + ? const Center( + child: Text( + 'Aucune actualité disponible', + style: TextStyle( + fontSize: 16, + color: ColorConstants.tertiary, + ), ), - ), - ) - : FeedTimeline( + ) + : FeedTimeline( isAdmin: isAdmin, items: news, onItemTap: (item) {}, - ), + ), ), ), ), From d170538bc6d4c79bc93ef78e65e4e2d3d4afaa21 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 10 Aug 2025 15:08:33 +0200 Subject: [PATCH 222/473] feat: removing duplicates --- lib/feed/ui/pages/main_page/main_page.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/feed/ui/pages/main_page/main_page.dart b/lib/feed/ui/pages/main_page/main_page.dart index d542c20a41..c7d1a47c6b 100644 --- a/lib/feed/ui/pages/main_page/main_page.dart +++ b/lib/feed/ui/pages/main_page/main_page.dart @@ -39,8 +39,8 @@ class FeedMainPage extends HookConsumerWidget { orElse: () => [], data: (loaded) => loaded, ); - final entities = syncNews.map((e) => e.entity).toList(); - final modules = syncNews.map((e) => e.module).toList(); + final entities = syncNews.map((e) => e.entity).toSet().toList(); + final modules = syncNews.map((e) => e.module).toSet().toList(); await showCustomBottomModal( modal: FilterNewsModal(entities: entities, modules: modules), context: context, From ddd64575ff42cfd88f6d3f92416e583e7318336f Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 10 Aug 2025 15:08:35 +0200 Subject: [PATCH 223/473] fix: filtering news by module and entity --- lib/feed/class/filter_state.dart | 26 ++++++ lib/feed/providers/filter_state_provider.dart | 14 +++ .../event_handling_page/admin_event_card.dart | 15 +++- lib/feed/ui/pages/main_page/filter_news.dart | 86 +++++++++++++------ lib/feed/ui/pages/main_page/main_page.dart | 5 +- lib/tools/ui/styleguide/item_chip.dart | 6 +- 6 files changed, 120 insertions(+), 32 deletions(-) create mode 100644 lib/feed/class/filter_state.dart create mode 100644 lib/feed/providers/filter_state_provider.dart diff --git a/lib/feed/class/filter_state.dart b/lib/feed/class/filter_state.dart new file mode 100644 index 0000000000..320f5f34e5 --- /dev/null +++ b/lib/feed/class/filter_state.dart @@ -0,0 +1,26 @@ +class FilterState { + final List selectedEntities; + final List selectedModules; + + FilterState({ + required this.selectedEntities, + required this.selectedModules, + }); + + FilterState copyWith({ + List? selectedEntities, + List? selectedModules, + }) { + return FilterState( + selectedEntities: selectedEntities ?? this.selectedEntities, + selectedModules: selectedModules ?? this.selectedModules, + ); + } + + factory FilterState.empty() { + return FilterState( + selectedEntities: [], + selectedModules: [], + ); + } +} diff --git a/lib/feed/providers/filter_state_provider.dart b/lib/feed/providers/filter_state_provider.dart new file mode 100644 index 0000000000..2165520255 --- /dev/null +++ b/lib/feed/providers/filter_state_provider.dart @@ -0,0 +1,14 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/feed/class/filter_state.dart'; + +class FilterStateNotifier extends StateNotifier { + FilterStateNotifier() : super(FilterState.empty()); + + void setFilterState(FilterState i) { + state = i; + } +} + +final filterStateProvider = StateNotifierProvider((ref) { + return FilterStateNotifier(); +}); diff --git a/lib/feed/ui/pages/event_handling_page/admin_event_card.dart b/lib/feed/ui/pages/event_handling_page/admin_event_card.dart index ed296e6103..defea71ae3 100644 --- a/lib/feed/ui/pages/event_handling_page/admin_event_card.dart +++ b/lib/feed/ui/pages/event_handling_page/admin_event_card.dart @@ -1,17 +1,20 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/feed/class/news.dart'; +import 'package:titan/feed/providers/admin_news_list_provider.dart'; import 'package:titan/feed/tools/news_status_helper.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; -class AdminEventCard extends StatelessWidget { +class AdminEventCard extends ConsumerWidget { final News news; const AdminEventCard({super.key, required this.news}); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { final locale = Localizations.localeOf(context).languageCode; + final newsAdminNotifier = ref.watch(adminNewsListProvider.notifier); return Container( decoration: BoxDecoration( @@ -111,7 +114,9 @@ class AdminEventCard extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ WaitingButton( - onTap: () async {}, + onTap: () async { + await newsAdminNotifier.rejectNews(news); + }, builder: (child) => Container( padding: const EdgeInsets.symmetric( horizontal: 12, @@ -142,7 +147,9 @@ class AdminEventCard extends StatelessWidget { ), SizedBox(width: 10), WaitingButton( - onTap: () async {}, + onTap: () async { + await newsAdminNotifier.approveNews(news); + }, builder: (child) => Container( padding: const EdgeInsets.symmetric( horizontal: 12, diff --git a/lib/feed/ui/pages/main_page/filter_news.dart b/lib/feed/ui/pages/main_page/filter_news.dart index a2f0e5fc02..5df555cbb3 100644 --- a/lib/feed/ui/pages/main_page/filter_news.dart +++ b/lib/feed/ui/pages/main_page/filter_news.dart @@ -1,13 +1,15 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/feed/providers/filter_state_provider.dart'; import 'package:titan/feed/providers/news_list_provider.dart'; +import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/layouts/horizontal_list_view.dart'; -import 'package:titan/tools/ui/layouts/item_chip.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; import 'package:titan/tools/ui/styleguide/button.dart'; +import 'package:titan/tools/ui/styleguide/item_chip.dart'; -class FilterNewsModal extends StatelessWidget { +class FilterNewsModal extends HookWidget { final List entities, modules; const FilterNewsModal({ super.key, @@ -17,11 +19,11 @@ class FilterNewsModal extends StatelessWidget { @override Widget build(BuildContext context) { - return Consumer( + return HookConsumer( builder: (context, ref, child) { final newsListNotifier = ref.watch(newsListProvider.notifier); - final selectedEntities = useState>([]); - final selectedModules = useState>([]); + final filterState = ref.watch(filterStateProvider); + final filterStateNotifier = ref.watch(filterStateProvider.notifier); return BottomModalTemplate( title: 'Filtrer', child: Column( @@ -34,28 +36,45 @@ class FilterNewsModal extends StatelessWidget { children: entities .map( (entity) => ItemChip( - selected: selectedEntities.value.contains(entity), + selected: filterState.selectedEntities.contains(entity), onTap: () { - if (selectedEntities.value.contains(entity)) { - selectedEntities.value.remove(entity); + if (filterState.selectedEntities.contains(entity)) { + filterStateNotifier.setFilterState( + filterState.copyWith( + selectedEntities: filterState.selectedEntities + ..remove(entity), + ), + ); } else { - selectedEntities.value.add(entity); + filterStateNotifier.setFilterState( + filterState.copyWith( + selectedEntities: filterState.selectedEntities + ..add(entity), + ), + ); } - if (selectedEntities.value.isEmpty && - selectedModules.value.isEmpty) { + if (filterState.selectedEntities.isEmpty && + filterState.selectedModules.isEmpty) { newsListNotifier.resetFilters(); } else { newsListNotifier.filterNews( - selectedEntities.value, - selectedModules.value, + filterState.selectedEntities, + filterState.selectedModules, ); } newsListNotifier.filterNews( - selectedEntities.value, - selectedModules.value, + filterState.selectedEntities, + filterState.selectedModules, ); }, - child: Text(entity), + child: Text( + entity, + style: TextStyle( + color: filterState.selectedEntities.contains(entity) + ? ColorConstants.background + : ColorConstants.onTertiary, + ), + ), ), ) .toList(), @@ -68,24 +87,41 @@ class FilterNewsModal extends StatelessWidget { children: modules .map( (module) => ItemChip( - selected: selectedModules.value.contains(module), + selected: filterState.selectedModules.contains(module), onTap: () { - if (selectedModules.value.contains(module)) { - selectedModules.value.remove(module); + if (filterState.selectedModules.contains(module)) { + filterStateNotifier.setFilterState( + filterState.copyWith( + selectedModules: filterState.selectedModules + ..remove(module), + ), + ); } else { - selectedModules.value.add(module); + filterStateNotifier.setFilterState( + filterState.copyWith( + selectedModules: filterState.selectedModules + ..add(module), + ), + ); } - if (selectedEntities.value.isEmpty && - selectedModules.value.isEmpty) { + if (filterState.selectedEntities.isEmpty && + filterState.selectedModules.isEmpty) { newsListNotifier.resetFilters(); } else { newsListNotifier.filterNews( - selectedEntities.value, - selectedModules.value, + filterState.selectedEntities, + filterState.selectedModules, ); } }, - child: Text(module), + child: Text( + module, + style: TextStyle( + color: filterState.selectedModules.contains(module) + ? ColorConstants.background + : ColorConstants.onTertiary, + ), + ), ), ) .toList(), diff --git a/lib/feed/ui/pages/main_page/main_page.dart b/lib/feed/ui/pages/main_page/main_page.dart index c7d1a47c6b..48308cbcc8 100644 --- a/lib/feed/ui/pages/main_page/main_page.dart +++ b/lib/feed/ui/pages/main_page/main_page.dart @@ -24,6 +24,7 @@ class FeedMainPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final news = ref.watch(newsListProvider); + final newsNotifier = ref.watch(newsListProvider.notifier); final isSuperAdmin = ref.watch(isAdminProvider); final scrollController = useScrollController(); @@ -35,7 +36,7 @@ class FeedMainPage extends HookConsumerWidget { children: [ CustomSearchBar( onFilter: () async { - final syncNews = news.maybeWhen( + final syncNews = newsNotifier.allNews.maybeWhen( orElse: () => [], data: (loaded) => loaded, ); @@ -125,7 +126,7 @@ class FeedMainPage extends HookConsumerWidget { ), ) : FeedTimeline( - isAdmin: isAdmin, + isAdmin: isSuperAdmin, items: news, onItemTap: (item) {}, ), diff --git a/lib/tools/ui/styleguide/item_chip.dart b/lib/tools/ui/styleguide/item_chip.dart index e4cbe8ad87..002ad88954 100644 --- a/lib/tools/ui/styleguide/item_chip.dart +++ b/lib/tools/ui/styleguide/item_chip.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:titan/tools/constants.dart'; class ItemChip extends StatelessWidget { final bool selected; @@ -23,7 +24,10 @@ class ItemChip extends StatelessWidget { padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 20.0), decoration: BoxDecoration( borderRadius: BorderRadius.circular(30.0), - color: selected ? Colors.black : Colors.grey.shade200, + border: Border.all(color: ColorConstants.onTertiary), + color: selected + ? ColorConstants.onTertiary + : ColorConstants.background, ), child: child, ), From e55276f169e9a5fe0e4995ec8bbf27c0153c2d2c Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 10 Aug 2025 15:08:36 +0200 Subject: [PATCH 224/473] feat: ordering news by dates --- lib/feed/ui/pages/main_page/feed_timeline.dart | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/feed/ui/pages/main_page/feed_timeline.dart b/lib/feed/ui/pages/main_page/feed_timeline.dart index 03317f4efb..8d4fedef3a 100644 --- a/lib/feed/ui/pages/main_page/feed_timeline.dart +++ b/lib/feed/ui/pages/main_page/feed_timeline.dart @@ -7,10 +7,24 @@ class FeedTimeline extends StatelessWidget { final Function(News item)? onItemTap; final bool isAdmin; - const FeedTimeline({super.key, required this.items, this.onItemTap, required this.isAdmin}); + const FeedTimeline({ + super.key, + required this.items, + this.onItemTap, + required this.isAdmin, + }); @override Widget build(BuildContext context) { + items.sort((a, b) { + if (a.start == b.start) { + if (a.end == null && b.end == null) return 0; + if (a.end == null) return -1; + if (b.end == null) return 1; + return a.end!.compareTo(b.end!); + } + return a.start.compareTo(b.start); + }); return Column( children: [ ...items.map( From 5491ff3dc56ca74a1e63596eb87874894298b30b Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 10 Aug 2025 15:08:37 +0200 Subject: [PATCH 225/473] feat: auto jump to first ongoing or incoming news --- lib/feed/ui/pages/main_page/main_page.dart | 44 ++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/lib/feed/ui/pages/main_page/main_page.dart b/lib/feed/ui/pages/main_page/main_page.dart index 48308cbcc8..a4acba0e3a 100644 --- a/lib/feed/ui/pages/main_page/main_page.dart +++ b/lib/feed/ui/pages/main_page/main_page.dart @@ -28,6 +28,50 @@ class FeedMainPage extends HookConsumerWidget { final isSuperAdmin = ref.watch(isAdminProvider); final scrollController = useScrollController(); + // Auto-scroll to first upcoming news when data loads + useEffect(() { + if (news.hasValue && news.value!.isNotEmpty) { + WidgetsBinding.instance.addPostFrameCallback((_) { + final now = DateTime.now(); + final newsList = news.value!; + + // Sort the news the same way as in FeedTimeline + newsList.sort((a, b) { + if (a.start == b.start) { + if (a.end == null && b.end == null) return 0; + if (a.end == null) return -1; + if (b.end == null) return 1; + return a.end!.compareTo(b.end!); + } + return a.start.compareTo(b.start); + }); + + // Find the first upcoming news item + final upcomingIndex = newsList.indexWhere( + (item) => + item.start.isAfter(now) || + (item.end != null && item.end!.isAfter(now)), + ); + + if (upcomingIndex != -1 && scrollController.hasClients) { + // Calculate the actual position based on real item heights + double scrollPosition = 0.0; + for (int i = 0; i < upcomingIndex; i++) { + final currentItem = newsList[i]; + // TimelineItem has fixed heights: 200px with actions, 160px without + final itemHeight = (currentItem.actionStart != null || isAdmin) + ? 200.0 + : 160.0; + scrollPosition += itemHeight; + } + + scrollController.jumpTo(scrollPosition); + } + }); + } + return null; + }, [news]); + return FeedTemplate( child: Container( padding: const EdgeInsets.symmetric(horizontal: 20), From 24b16970e6bf69b77e837663ecde7aa8844676df Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 10 Aug 2025 15:09:39 +0200 Subject: [PATCH 226/473] feat: action button text --- lib/feed/class/filter_state.dart | 10 +- lib/feed/providers/filter_state_provider.dart | 7 +- lib/feed/providers/news_image_provider.dart | 8 +- lib/feed/router.dart | 2 +- lib/feed/tools/function.dart | 1 - ...ws_status_helper.dart => news_helper.dart} | 48 + .../event_handling_page/admin_event_card.dart | 2 +- lib/feed/ui/pages/main_page/event_action.dart | 31 +- lib/feed/ui/pages/main_page/event_card.dart | 2 +- lib/feed/ui/pages/main_page/main_page.dart | 6 +- .../ui/pages/main_page/time_line_item.dart | 25 +- lib/l10n/app_en.arb | 2650 ++++++++-------- lib/l10n/app_fr.arb | 2662 +++++++++-------- lib/l10n/app_localizations.dart | 48 + lib/l10n/app_localizations_en.dart | 24 + lib/l10n/app_localizations_fr.dart | 24 + 16 files changed, 2862 insertions(+), 2688 deletions(-) rename lib/feed/tools/{news_status_helper.dart => news_helper.dart} (78%) diff --git a/lib/feed/class/filter_state.dart b/lib/feed/class/filter_state.dart index 320f5f34e5..c430e711da 100644 --- a/lib/feed/class/filter_state.dart +++ b/lib/feed/class/filter_state.dart @@ -2,10 +2,7 @@ class FilterState { final List selectedEntities; final List selectedModules; - FilterState({ - required this.selectedEntities, - required this.selectedModules, - }); + FilterState({required this.selectedEntities, required this.selectedModules}); FilterState copyWith({ List? selectedEntities, @@ -18,9 +15,6 @@ class FilterState { } factory FilterState.empty() { - return FilterState( - selectedEntities: [], - selectedModules: [], - ); + return FilterState(selectedEntities: [], selectedModules: []); } } diff --git a/lib/feed/providers/filter_state_provider.dart b/lib/feed/providers/filter_state_provider.dart index 2165520255..f0f479a701 100644 --- a/lib/feed/providers/filter_state_provider.dart +++ b/lib/feed/providers/filter_state_provider.dart @@ -9,6 +9,7 @@ class FilterStateNotifier extends StateNotifier { } } -final filterStateProvider = StateNotifierProvider((ref) { - return FilterStateNotifier(); -}); +final filterStateProvider = + StateNotifierProvider((ref) { + return FilterStateNotifier(); + }); diff --git a/lib/feed/providers/news_image_provider.dart b/lib/feed/providers/news_image_provider.dart index 47f818096b..7c3bfd0971 100644 --- a/lib/feed/providers/news_image_provider.dart +++ b/lib/feed/providers/news_image_provider.dart @@ -9,17 +9,13 @@ class NewsImageNotifier extends SingleNotifier { : super(const AsyncLoading()); Future> getNewsImage(String userId) async { - return await load( - () async => newsImageRepository.getNewsImage(userId), - ); + return await load(() async => newsImageRepository.getNewsImage(userId)); } } final newsImageProvider = StateNotifierProvider>((ref) { - final newsImageRepository = ref.watch( - newsImageRepositoryProvider, - ); + final newsImageRepository = ref.watch(newsImageRepositoryProvider); NewsImageNotifier notifier = NewsImageNotifier( newsImageRepository: newsImageRepository, ); diff --git a/lib/feed/router.dart b/lib/feed/router.dart index 4d6e807fc1..2c073449b0 100644 --- a/lib/feed/router.dart +++ b/lib/feed/router.dart @@ -52,7 +52,7 @@ class FeedRouter { ], ), QRoute( - path: eventHandling, + path: eventHandling, builder: () => event_handling_page.EventHandlingPage(), middleware: [ AuthenticatedMiddleware(ref), diff --git a/lib/feed/tools/function.dart b/lib/feed/tools/function.dart index 9b9f2097fc..b1c5b57df1 100644 --- a/lib/feed/tools/function.dart +++ b/lib/feed/tools/function.dart @@ -1,6 +1,5 @@ enum NewsStatus { waitingApproval, rejected, published } - String newsStatusToString(NewsStatus status) { switch (status) { case NewsStatus.waitingApproval: diff --git a/lib/feed/tools/news_status_helper.dart b/lib/feed/tools/news_helper.dart similarity index 78% rename from lib/feed/tools/news_status_helper.dart rename to lib/feed/tools/news_helper.dart index 70aa5c0ffc..54330af46f 100644 --- a/lib/feed/tools/news_status_helper.dart +++ b/lib/feed/tools/news_helper.dart @@ -152,3 +152,51 @@ String getNewsSubtitle( return subtitle; } + +String getActionTitle(News news, BuildContext context) { + final module = news.module; + + if (module == "campagne") { + return AppLocalizations.of(context)?.eventActionCampaign ?? 'Tu peux voter'; + } else if (module == "event") { + return AppLocalizations.of(context)?.eventActionEvent ?? 'Tu est invité'; + } + return ''; +} + +String getActionSubtitle(News news, BuildContext context) { + final module = news.module; + + if (module == "campagne") { + return AppLocalizations.of(context)?.eventActionCampaignSubtitle ?? + 'Votez maintenant'; + } else if (module == "event") { + return AppLocalizations.of(context)?.eventActionEventSubtitle ?? + 'Répondez à l\'invitation'; + } + return ''; +} + +String getActionEnableButtonText(News news, BuildContext context) { + final module = news.module; + + if (module == "campagne") { + return AppLocalizations.of(context)?.eventActionCampaignButton ?? 'Voter'; + } else if (module == "event") { + return AppLocalizations.of(context)?.eventActionEventButton ?? 'Participer'; + } + return ''; +} + +String getActionValidatedButtonText(News news, BuildContext context) { + final module = news.module; + + if (module == "campagne") { + return AppLocalizations.of(context)?.eventActionCampaignValidated ?? + 'J\'ai voté !'; + } else if (module == "event") { + return AppLocalizations.of(context)?.eventActionEventValidated ?? + 'Je viens !'; + } + return ''; +} diff --git a/lib/feed/ui/pages/event_handling_page/admin_event_card.dart b/lib/feed/ui/pages/event_handling_page/admin_event_card.dart index defea71ae3..d23757483e 100644 --- a/lib/feed/ui/pages/event_handling_page/admin_event_card.dart +++ b/lib/feed/ui/pages/event_handling_page/admin_event_card.dart @@ -3,7 +3,7 @@ import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/feed/class/news.dart'; import 'package:titan/feed/providers/admin_news_list_provider.dart'; -import 'package:titan/feed/tools/news_status_helper.dart'; +import 'package:titan/feed/tools/news_helper.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; diff --git a/lib/feed/ui/pages/main_page/event_action.dart b/lib/feed/ui/pages/main_page/event_action.dart index 4efb8d2fc2..19c2cad4ca 100644 --- a/lib/feed/ui/pages/main_page/event_action.dart +++ b/lib/feed/ui/pages/main_page/event_action.dart @@ -2,17 +2,22 @@ import 'package:flutter/material.dart'; import 'package:titan/tools/constants.dart'; class EventAction extends StatelessWidget { - final String title, subtitle, actionButtonText; + final String title, + subtitle, + actionEnableButtonText, + actionValidatedButtonText; final VoidCallback? onActionPressed; - final bool isActionEnabled; + final bool isActionEnabled, isActionValidated; const EventAction({ super.key, required this.title, required this.subtitle, this.onActionPressed, - required this.actionButtonText, + required this.actionEnableButtonText, + required this.actionValidatedButtonText, required this.isActionEnabled, + required this.isActionValidated, }); @override @@ -48,27 +53,31 @@ class EventAction extends StatelessWidget { // Action button GestureDetector( onTap: () { - if (isActionEnabled) onActionPressed!.call(); + if (isActionEnabled && !isActionValidated) onActionPressed!.call(); }, child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 5), width: 100, decoration: BoxDecoration( - color: isActionEnabled - ? ColorConstants.background - : ColorConstants.tertiary, + color: isActionValidated + ? ColorConstants.tertiary + : ColorConstants.background, borderRadius: BorderRadius.circular(20), border: Border.all(color: ColorConstants.tertiary, width: 2), ), child: Center( child: Text( - actionButtonText, + isActionValidated + ? actionValidatedButtonText + : actionEnableButtonText, style: TextStyle( fontSize: 12, fontWeight: FontWeight.bold, - color: isActionEnabled - ? ColorConstants.tertiary - : ColorConstants.background, + color: + (isActionValidated + ? ColorConstants.background + : ColorConstants.tertiary) + .withValues(alpha: isActionEnabled ? 255 : 100), ), ), ), diff --git a/lib/feed/ui/pages/main_page/event_card.dart b/lib/feed/ui/pages/main_page/event_card.dart index 70a516caa2..a9c5b4634f 100644 --- a/lib/feed/ui/pages/main_page/event_card.dart +++ b/lib/feed/ui/pages/main_page/event_card.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:titan/feed/class/news.dart'; -import 'package:titan/feed/tools/news_status_helper.dart'; +import 'package:titan/feed/tools/news_helper.dart'; import 'package:titan/tools/constants.dart'; class EventCard extends StatelessWidget { diff --git a/lib/feed/ui/pages/main_page/main_page.dart b/lib/feed/ui/pages/main_page/main_page.dart index a4acba0e3a..67932fbd26 100644 --- a/lib/feed/ui/pages/main_page/main_page.dart +++ b/lib/feed/ui/pages/main_page/main_page.dart @@ -28,14 +28,12 @@ class FeedMainPage extends HookConsumerWidget { final isSuperAdmin = ref.watch(isAdminProvider); final scrollController = useScrollController(); - // Auto-scroll to first upcoming news when data loads useEffect(() { if (news.hasValue && news.value!.isNotEmpty) { WidgetsBinding.instance.addPostFrameCallback((_) { final now = DateTime.now(); final newsList = news.value!; - // Sort the news the same way as in FeedTimeline newsList.sort((a, b) { if (a.start == b.start) { if (a.end == null && b.end == null) return 0; @@ -46,7 +44,6 @@ class FeedMainPage extends HookConsumerWidget { return a.start.compareTo(b.start); }); - // Find the first upcoming news item final upcomingIndex = newsList.indexWhere( (item) => item.start.isAfter(now) || @@ -54,11 +51,10 @@ class FeedMainPage extends HookConsumerWidget { ); if (upcomingIndex != -1 && scrollController.hasClients) { - // Calculate the actual position based on real item heights double scrollPosition = 0.0; for (int i = 0; i < upcomingIndex; i++) { final currentItem = newsList[i]; - // TimelineItem has fixed heights: 200px with actions, 160px without + final itemHeight = (currentItem.actionStart != null || isAdmin) ? 200.0 : 160.0; diff --git a/lib/feed/ui/pages/main_page/time_line_item.dart b/lib/feed/ui/pages/main_page/time_line_item.dart index 3a53cdb05a..2d2e91a6a0 100644 --- a/lib/feed/ui/pages/main_page/time_line_item.dart +++ b/lib/feed/ui/pages/main_page/time_line_item.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:titan/feed/class/news.dart'; +import 'package:titan/feed/tools/news_helper.dart'; import 'package:titan/feed/ui/pages/main_page/event_action.dart'; import 'package:titan/feed/ui/pages/main_page/event_action_admin.dart'; import 'package:titan/feed/ui/pages/main_page/event_card.dart'; @@ -96,17 +97,27 @@ class TimelineItem extends StatelessWidget { ), Expanded( child: isAdmin - ? EventActionAdmin( - item: item, - ) + ? EventActionAdmin(item: item) : EventAction( - title: 'Action', - subtitle: 'Indication', + title: getActionTitle(item, context), + subtitle: getActionSubtitle(item, context), onActionPressed: () { // Handle action press }, - actionButtonText: 'Ok', - isActionEnabled: true, + actionEnableButtonText: + getActionEnableButtonText(item, context), + actionValidatedButtonText: + getActionValidatedButtonText( + item, + context, + ), + isActionValidated: true, + isActionEnabled: + (item.actionStart ?? item.start).isAfter( + DateTime.now(), + ) && + item.end != null && + item.end!.isAfter(DateTime.now()), ), ), ], diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index cb6e107f40..729d8bfaca 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1,1198 +1,1210 @@ - { - "@@locale": "en", - "dateToday": "Today", - "dateYesterday": "Yesterday", - "dateTomorrow": "Tomorrow", - "dateAt": "at", - "dateFrom": "from", - "dateTo": "to", - "dateBetweenDays": "to", - "dateStarting": "Starting", - "dateLast": "Last", - "dateUntil": "Until", - "feedFilterAll": "All", - "feedFilterPending": "Pending", - "feedFilterApproved": "Approved", - "feedFilterRejected": "Rejected", - "feedEmptyAll": "No events available", - "feedEmptyPending": "No events pending approval", - "feedEmptyApproved": "No approved events", - "feedEmptyRejected": "No rejected events", - "feedEventManagement": "Event Management", - "adminAccountTypes": "Account types", - "adminAdd": "Add", - "adminAddGroup": "Add group", - "adminAddMember": "Add member", - "adminAddedGroup": "Group created", - "adminAddedLoaner": "Lender added", - "adminAddedMember": "Member added", - "adminAddingError": "Error while adding", - "adminAddingMember": "Adding a member", - "adminAddLoaningGroup": "Add loaning group", - "adminAddSchool": "Add school", - "adminAddStructure": "Add structure", - "adminAddedSchool": "School created", - "adminAddedStructure": "Structure added", - "adminEditedStructure": "Structure edited", - "adminAdministration": "Administration", - "adminAssociationMembership": "Membership", - "adminAssociationMembershipName": "Membership name", - "adminAssociationsMemberships": "Memberships", - "adminClearFilters": "Clear filters", - "adminCreateAssociationMembership": "Create membership", - "adminCreatedAssociationMembership": "Membership created", - "adminCreationError": "Error during creation", - "adminDateError": "Start date must be before end date", - "adminDelete": "Delete", - "adminDeleteAssociationMembership": "Delete membership?", - "adminDeletedAssociationMembership": "Membership deleted", - "adminDeleteGroup": "Delete group?", - "adminDeletedGroup": "Group deleted", - "adminDeleteSchool": "Delete school?", - "adminDeletedSchool": "School deleted", - "adminDeleting": "Deleting", - "adminDeletingError": "Error while deleting", - "adminDescription": "Description", - "adminEclSchool": "Centrale Lyon", - "adminEdit": "Edit", - "adminEditStructure": "Edit structure", - "adminEditMembership": "Edit membership", - "adminEmptyDate": "Empty date", - "adminEmptyFieldError": "Name cannot be empty", - "adminEmailRegex": "Email Regex", - "adminEmptyUser": "Empty user", - "adminEndDate": "End date", - "adminEndDateMaximal": "Maximum end date", - "adminEndDateMinimal": "Minimum end date", - "adminError": "Error", - "adminFilters": "Filters", - "adminGroup": "Group", - "adminGroups": "Groups", - "adminLoaningGroup": "Loaning group", - "adminLooking": "Searching", - "adminManager": "Structure administrator", - "adminMaximum": "Maximum", - "adminMembers": "Members", - "adminMembershipAddingError": "Error while adding (likely due to overlapping dates)", - "adminMemberships": "Memberships", - "adminMembershipUpdatingError": "Error while updating (likely due to overlapping dates)", - "adminMinimum": "Minimum", - "adminModifyModuleVisibility": "Module visibility", - "adminMyEclPay": "MyECLPay", - "adminName": "Name", - "adminNoManager": "No manager selected", - "adminNoMember": "No member", - "adminNoMoreLoaner": "No lender available", - "adminNoSchool": "No school", - "adminRemoveGroupMember": "Remove member from group?", - "adminResearch": "Search", - "adminSchools": "Schools", - "adminStructures": "Structures", - "adminStartDate": "Start date", - "adminStartDateMaximal": "Maximum start date", - "adminStartDateMinimal": "Minimum start date", - "adminUpdatedAssociationMembership": "Membership updated", - "adminUpdatedGroup": "Group updated", - "adminUpdatedMembership": "Membership updated", - "adminUpdatingError": "Error while updating", - "adminUser": "User", - "adminValidateFilters": "Apply filters", - "adminVisibilities": "Visibilities", - "adminGroupNotification": "Group notifications", - "adminNotifyGroup": "Send a notification", - "adminTitle": "Title", - "adminContent": "Content", - "adminSend": "Send", - "adminNotificationSent": "Notification sent", - "adminFailedToSendNotification": "Failed to send notification", - "adminGroupsManagement": "Groups management", - "adminEditGroup": "Edit group", - "adminManageMembers": "Manage members", - "adminDeleteGroupConfirmation": "Are you sure you want to delete this group?", - "adminFailedToDeleteGroup": "Failed to delete group", - "adminUsersAndGroups": "Users and groups", - "adminUsersManagement": "Users management", - "adminUsersManagementDescription": "Manage users, groups, and associations", - "adminManageUserGroups": "Manage user groups", - "adminSendNotificationToGroup": "Send notification to group", - "adminPaiementModule": "Payment module", - "adminPaiement": "Payment", - "adminManagePaiementStructures": "Manage payment structures", - "adminManageUsersAssociationMemberships": "Manage users' association memberships", - "adminAssociationMembershipsManagement": "Association memberships management", - "adminChooseGroupManager" : "Choose a group to manage this membership", - "adminSelectManager": "Select a manager", - "adminInviteUsers": "Invite users", - "adminImportList": "Import a list", - "adminInvitedUsers": "Invited users", - "adminFailedToInviteUsers": "Failed to invite users", - "adminDeleteUsers": "Delete users", - "adminAdmin" : "Admin", - "adminAdverts": "Adverts", - "adminAnnouncers": "Announcers", - "adminManageAnnouncers" : "Manage announcers", - "advertAdd": "Add", - "advertAddedAdvert": "Advert published", - "advertAddedAnnouncer": "Announcer added", - "advertAddingError": "Error while adding", - "advertAdmin": "Admin", - "advertAdvert": "Advert", - "advertChoosingAnnouncer": "Please choose an announcer", - "advertChoosingPoster": "Please choose an image", - "advertContent": "Content", - "advertDeleteAdvert": "Delete ad?", - "advertDeleteAnnouncer": "Delete announcer?", - "advertDeleting": "Deleting", - "advertEdit": "Edit", - "advertEditedAdvert": "Advert edited", - "advertEditingError": "Error while editing", - "advertGroupAdvert": "Group", - "advertIncorrectOrMissingFields": "Incorrect or missing fields", - "advertInvalidNumber": "Please enter a number", - "advertManagement": "Management", - "advertModifyAnnouncingGroup": "Edit announcement group", - "advertNoMoreAnnouncer": "No more announcers available", - "advertNoValue": "Please enter a value", - "advertPositiveNumber": "Please enter a positive number", - "advertRemovedAnnouncer": "Announcer removed", - "advertRemovingError": "Error during removal", - "advertTags": "Tags", - "advertTitle": "Title", - "advertMonthJan": "Jan", - "advertMonthFeb": "Feb", - "advertMonthMar": "Mar", - "advertMonthApr": "Apr", - "advertMonthMay": "May", - "advertMonthJun": "Jun", - "advertMonthJul": "Jul", - "advertMonthAug": "Aug", - "advertMonthSep": "Sep", - "advertMonthOct": "Oct", - "advertMonthNov": "Nov", - "advertMonthDec": "Dec", - "amapAccounts": "Accounts", - "amapAdd": "Add", - "amapAddDelivery": "Add delivery", - "amapAddedCommand": "Order added", - "amapAddedOrder": "Order added", - "amapAddedProduct": "Product added", - "amapAddedUser": "User added", - "amapAddProduct": "Add product", - "amapAddUser": "Add user", - "amapAddingACommand": "Add an order", - "amapAddingCommand": "Add the order", - "amapAddingError": "Error while adding", - "amapAddingProduct": "Add a product", - "amapAddOrder": "Add an order", - "amapAdmin": "Admin", - "amapAlreadyExistCommand": "An order already exists for this date", - "amapAmap": "Amap", - "amapAmount": "Balance", - "amapArchive": "Archive", - "amapArchiveDelivery": "Archive", - "amapArchivingDelivery": "Archiving delivery", - "amapCategory": "Category", - "amapCloseDelivery": "Lock", - "amapCommandDate": "Order date", - "amapCommandProducts": "Order products", - "amapConfirm": "Confirm", - "amapContact": "Association contacts", - "amapCreateCategory": "Create category", - "amapDelete": "Delete", - "amapDeleteDelivery": "Delete delivery?", - "amapDeleteDeliveryDescription": "Are you sure you want to delete this delivery?", - "amapDeletedDelivery": "Delivery deleted", - "amapDeletedOrder": "Order deleted", - "amapDeletedProduct": "Product deleted", - "amapDeleteProduct": "Delete product?", - "amapDeleteProductDescription": "Are you sure you want to delete this product?", - "amapDeleting": "Deleting", - "amapDeletingDelivery": "Delete delivery?", - "amapDeletingError": "Error while deleting", - "amapDeletingOrder": "Delete order?", - "amapDeletingProduct": "Delete product?", - "amapDeliver": "Delivery completed?", - "amapDeliveries": "Deliveries", - "amapDeliveringDelivery": "Are all orders delivered?", - "amapDelivery": "Delivery", - "amapDeliveryArchived": "Delivery archived", - "amapDeliveryDate": "Delivery date", - "amapDeliveryDelivered": "Delivery completed", - "amapDeliveryHistory": "Delivery history", - "amapDeliveryList": "Delivery list", - "amapDeliveryLocked": "Delivery locked", - "amapDeliveryOn": "Delivery on", - "amapDeliveryOpened": "Delivery opened", - "amapDeliveryNotArchived": "Delivery not archived", - "amapDeliveryNotLocked": "Delivery not locked", - "amapDeliveryNotDelivered": "Delivery not completed", - "amapDeliveryNotOpened": "Delivery not opened", - "amapEditDelivery": "Edit delivery", - "amapEditedCommand": "Order edited", - "amapEditingError": "Error while editing", - "amapEditProduct": "Edit product", - "amapEndingDelivery": "End of delivery", - "amapError": "Error", - "amapErrorLink": "Error opening link", - "amapErrorLoadingUser": "Error loading users", - "amapEvening": "Evening", - "amapExpectingNumber": "Please enter a number", - "amapFillField": "Please fill out this field", - "amapHandlingAccount": "Manage accounts", - "amapLoading": "Loading...", - "amapLoadingError": "Loading error", - "amapLock": "Lock", - "amapLocked": "Locked", - "amapLockedDelivery": "Delivery locked", - "amapLockedOrder": "Order locked", - "amapLooking": "Search", - "amapLockingDelivery": "Lock delivery?", - "amapMidDay": "Midday", - "amapMyOrders": "My orders", - "amapName": "Name", - "amapNextStep": "Next step", - "amapNoProduct": "No product", - "amapNoCurrentOrder": "No current order", - "amapNoMoney": "Not enough money", - "amapNoOpennedDelivery": "No open delivery", - "amapNoOrder": "No order", - "amapNoSelectedDelivery": "No delivery selected", - "amapNotEnoughMoney": "Not enough money", - "amapNotPlannedDelivery": "No scheduled delivery", - "amapOneOrder": "order", - "amapOpenDelivery": "Open", - "amapOpened": "Opened", - "amapOpenningDelivery": "Open delivery?", - "amapOrder": "Order", - "amapOrders": "Orders", - "amapPickChooseCategory": "Please enter a value or choose an existing category", - "amapPickDeliveryMoment": "Choose a delivery time", - "amapPresentation": "Presentation", - "amapPresentation1": "The AMAP (association for the preservation of small-scale farming) is a service offered by the Planet&Co association of ECL. You can receive products (fruit and vegetable baskets, juices, jams...) directly on campus!\n\nOrders must be placed before Friday at 9 PM and are delivered on campus on Tuesday from 1:00 PM to 1:45 PM (or from 6:15 PM to 6:30 PM if you can't come at midday) in the M16 hall.\n\nYou can only order if your balance allows it. You can top up your balance via the Lydia collection or by cheque during office hours.\n\nLydia top-up link: ", - "amapPresentation2": "\n\nFeel free to contact us if you have any issues!", - "amapPrice": "Price", - "amapProduct": "product", - "amapProducts": "Products", - "amapProductInDelivery": "Product in an unfinished delivery", - "amapQuantity": "Quantity", - "amapRequiredDate": "Date is required", - "amapSeeMore": "See more", - "amapThe": "The", - "amapUnlock": "Unlock", - "amapUnlockedDelivery": "Delivery unlocked", - "amapUnlockingDelivery": "Unlock delivery?", - "amapUpdate": "Edit", - "amapUpdatedAmount": "Balance updated", - "amapUpdatedOrder": "Order updated", - "amapUpdatedProduct": "Product updated", - "amapUpdatingError": "Update failed", - "amapUsersNotFound": "No users found", - "amapWaiting": "Pending", - "bookingAdd": "Add", - "bookingAddBookingPage": "Request", - "bookingAddRoom": "Add room", - "bookingAddBooking": "Add booking", - "bookingAddedBooking": "Request added", - "bookingAddedRoom": "Room added", - "bookingAddedManager": "Manager added", - "bookingAddingError": "Error while adding", - "bookingAddManager": "Add manager", - "bookingAdminPage": "Admin", - "bookingAllDay": "All day", - "bookingBookedFor": "Booked for", - "bookingBooking": "Booking", - "bookingBookingCreated": "Booking created", - "bookingBookingDemand": "Booking request", - "bookingBookingNote": "Booking note", - "bookingBookingPage": "Booking", - "bookingBookingReason": "Booking reason", - "bookingBy": "by", - "bookingConfirm": "Confirm", - "bookingConfirmation": "Confirmation", - "bookingConfirmBooking": "Confirm the booking?", - "bookingConfirmed": "Confirmed", - "bookingDates": "Dates", - "bookingDecline": "Decline", - "bookingDeclineBooking": "Decline the booking?", - "bookingDeclined": "Declined", - "bookingDelete": "Delete", - "bookingDeleting": "Deleting", - "bookingDeleteBooking": "Deleting", - "bookingDeleteBookingConfirmation": "Are you sure you want to delete this booking?", - "bookingDeletedBooking": "Booking deleted", - "bookingDeletedRoom": "Room deleted", - "bookingDeletedManager": "Manager deleted", - "bookingDeleteRoomConfirmation": "Are you sure you want to delete this room?\n\nThe room must have no current or upcoming bookings to be deleted", - "bookingDeleteManagerConfirmation": "Are you sure you want to delete this manager?\n\nThe manager must not be associated with any room to be deleted", - "bookingDeletingBooking": "Delete the booking?", - "bookingDeletingError": "Error while deleting", - "bookingDeletingRoom": "Delete the room?", - "bookingEdit": "Edit", - "bookingEditBooking": "Edit a booking", - "bookingEditionError": "Error while editing", - "bookingEditedBooking": "Booking edited", - "bookingEditedRoom": "Room edited", - "bookingEditedManager": "Manager edited", - "bookingEditManager": "Edit or delete a manager", - "bookingEditRoom": "Edit or delete a room", - "bookingEndDate": "End date", - "bookingEndHour": "End hour", - "bookingEntity": "For whom?", - "bookingError": "Error", - "bookingEventEvery": "Every", - "bookingHistoryPage": "History", - "bookingIncorrectOrMissingFields": "Incorrect or missing fields", - "bookingInterval": "Interval", - "bookingInvalidIntervalError": "Invalid interval", - "bookingInvalidDates": "Invalid dates", - "bookingInvalidRoom": "Invalid room", - "bookingKeysRequested": "Keys requested", - "bookingManagement": "Management", - "bookingManager": "Manager", - "bookingManagerName": "Manager name", - "bookingMultipleDay": "Multiple days", - "bookingMyBookings": "My bookings", - "bookingNecessaryKey": "Key needed", - "bookingNext": "Next", - "bookingNo": "No", - "bookingNoCurrentBooking": "No current booking", - "bookingNoDateError": "Please choose a date", - "bookingNoAppointmentInReccurence": "No slot exists with these recurrence settings", - "bookingNoDaySelected": "No day selected", - "bookingNoDescriptionError": "Please enter a description", - "bookingNoKeys": "No keys", - "bookingNoNoteError": "Please enter a note", - "bookingNoPhoneRegistered": "Number not provided", - "bookingNoReasonError": "Please enter a reason", - "bookingNoRoomFoundError": "No room registered", - "bookingNoRoomFound": "No room found", - "bookingNote": "Note", - "bookingOther": "Other", - "bookingPending": "Pending", - "bookingPrevious": "Previous", - "bookingReason": "Reason", - "bookingRecurrence": "Recurrence", - "bookingRecurrenceDays": "Recurrence days", - "bookingRecurrenceEndDate": "Recurrence end date", - "bookingRecurrent": "Recurrent", - "bookingRegisteredRooms": "Registered rooms", - "bookingRoom": "Room", - "bookingRoomName": "Room name", - "bookingStartDate": "Start date", - "bookingStartHour": "Start hour", - "bookingWeeks": "Weeks", - "bookingYes": "Yes", - "bookingWeekDayMon": "Monday", - "bookingWeekDayTue": "Tuesday", - "bookingWeekDayWed": "Wednesday", - "bookingWeekDayThu": "Thursday", - "bookingWeekDayFri": "Friday", - "bookingWeekDaySat": "Saturday", - "bookingWeekDaySun": "Sunday", - "cinemaAdd": "Add", - "cinemaAddedSession": "Session added", - "cinemaAddingError": "Error while adding", - "cinemaAddSession": "Add a session", - "cinemaCinema": "Cinema", - "cinemaDeleteSession": "Delete the session?", - "cinemaDeleting": "Deleting", - "cinemaDuration": "Duration", - "cinemaEdit": "Edit", - "cinemaEditedSession": "Session edited", - "cinemaEditingError": "Error while editing", - "cinemaEditSession": "Edit the session", - "cinemaEmptyUrl": "Please enter a URL", - "cinemaImportFromTMDB": "Import from TMDB", - "cinemaIncomingSession": "Now showing", - "cinemaIncorrectOrMissingFields": "Incorrect or missing fields", - "cinemaInvalidUrl": "Invalid URL", - "cinemaGenre": "Genre", - "cinemaName": "Name", - "cinemaNoDateError": "Please enter a date", - "cinemaNoDuration": "Please enter a duration", - "cinemaNoOverview": "No synopsis", - "cinemaNoPoster": "No poster", - "cinemaNoSession": "No session", - "cinemaOverview": "Synopsis", - "cinemaPosterUrl": "Poster URL", - "cinemaSessionDate": "Session day", - "cinemaStartHour": "Start hour", - "cinemaTagline": "Tagline", - "cinemaThe": "The", - "drawerAdmin": "Administration", - "drawerAndroidAppLink": "https://play.google.com/store/apps/details?id=fr.myecl.titan", - "drawerCopied": "Copied!", - "drawerDownloadAppOnMobileDevice": "This site is the web version of the MyECL app. We invite you to download the app. Use this site only if you have problems with the app.\n", - "drawerIosAppLink": "https://apps.apple.com/fr/app/myecl/id6444443430", - "drawerLoginOut": "Do you want to log out?", - "drawerLogOut": "Log out", - "drawerOr": " or ", - "drawerSettings": "Settings", - "eventAdd": "Add", - "eventAddEvent": "Add an event", - "eventAddedEvent": "Event added", - "eventAddingError": "Error while adding", - "eventAllDay": "All day", - "eventConfirm": "Confirm", - "eventConfirmEvent": "Confirm the event?", - "eventConfirmation": "Confirmation", - "eventConfirmed": "Confirmed", - "eventDates": "Dates", - "eventDecline": "Decline", - "eventDeclineEvent": "Decline the event?", - "eventDeclined": "Declined", - "eventDelete": "Delete", - "eventDeletedEvent": "Event deleted", - "eventDeleting": "Deleting", - "eventDeletingError": "Error while deleting", - "eventDeletingEvent": "Delete the event?", - "eventDescription": "Description", - "eventEdit": "Edit", - "eventEditEvent": "Edit an event", - "eventEditedEvent": "Event edited", - "eventEditingError": "Error while editing", - "eventEndDate": "End date", - "eventEndHour": "End hour", - "eventError": "Error", - "eventEventList": "Event list", - "eventEventType": "Event type", - "eventEvery": "Every", - "eventHistory": "History", - "eventIncorrectOrMissingFields": "Some fields are incorrect or missing", - "eventInterval": "Interval", - "eventInvalidDates": "End date must be after start date", - "eventInvalidIntervalError": "Please enter a valid interval", - "eventLocation": "Location", - "eventMyEvents": "My events", - "eventName": "Name", - "eventNext": "Next", - "eventNo": "No", - "eventNoCurrentEvent": "No current event", - "eventNoDateError": "Please enter a date", - "eventNoDaySelected": "No day selected", - "eventNoDescriptionError": "Please enter a description", - "eventNoEvent": "No event", - "eventNoNameError": "Please enter a name", - "eventNoOrganizerError": "Please enter an organizer", - "eventNoPlaceError": "Please enter a location", - "eventNoPhoneRegistered": "Number not provided", - "eventNoRuleError": "Please enter a recurrence rule", - "eventOrganizer": "Organizer", - "eventOther": "Other", - "eventPending": "Pending", - "eventPrevious": "Previous", - "eventRecurrence": "Recurrence", - "eventRecurrenceDays": "Recurrence days", - "eventRecurrenceEndDate": "Recurrence end date", - "eventRecurrenceRule": "Recurrence rule", - "eventRoom": "Room", - "eventStartDate": "Start date", - "eventStartHour": "Start hour", - "eventTitle": "Events", - "eventYes": "Yes", - "eventEventEvery": "Every", - "eventWeeks": "weeks", - "eventDayMon": "Monday", - "eventDayTue": "Tuesday", - "eventDayWed": "Wednesday", - "eventDayThu": "Thursday", - "eventDayFri": "Friday", - "eventDaySat": "Saturday", - "eventDaySun": "Sunday", - "homeCalendar": "Calendar", - "homeEventOf": "Events of", - "homeIncomingEvents": "Upcoming events", - "homeLastInfos": "Latest announcements", - "homeNoEvents": "No events", - "homeTranslateDayShortMon": "Mon", - "homeTranslateDayShortTue": "Tue", - "homeTranslateDayShortWed": "Wed", - "homeTranslateDayShortThu": "Thu", - "homeTranslateDayShortFri": "Fri", - "homeTranslateDayShortSat": "Sat", - "homeTranslateDayShortSun": "Sun", - "loanAdd": "Add", - "loanAddLoan": "Add a loan", - "loanAddObject": "Add an object", - "loanAddedLoan": "Loan added", - "loanAddedObject": "Object added", - "loanAddedRoom": "Room added", - "loanAddingError": "Error while adding", - "loanAdmin": "Administrator", - "loanAvailable": "Available", - "loanAvailableMultiple": "Available", - "loanBorrowed": "Borrowed", - "loanBorrowedMultiple": "Borrowed", - "loanAnd": "and", - "loanAssociation": "Association", - "loanAvailableItems": "Available items", - "loanBeginDate": "Loan start date", - "loanBorrower": "Borrower", - "loanCaution": "Deposit", - "loanCancel": "Cancel", - "loanConfirm": "Confirm", - "loanConfirmation": "Confirmation", - "loanDates": "Dates", - "loanDays": "Days", - "loanDelay": "Extension delay", - "loanDelete": "Delete", - "loanDeletingLoan": "Delete the loan?", - "loanDeletedItem": "Object deleted", - "loanDeletedLoan": "Loan deleted", - "loanDeleting": "Deleting", - "loanDeletingError": "Error while deleting", - "loanDeletingItem": "Delete the object?", - "loanDuration": "Duration", - "loanEdit": "Edit", - "loanEditItem": "Edit the object", - "loanEditLoan": "Edit the loan", - "loanEditedRoom": "Room edited", - "loanEndDate": "Loan end date", - "loanEnded": "Ended", - "loanEnterDate": "Please enter a date", - "loanExtendedLoan": "Extended loan", - "loanExtendingError": "Error while extending", - "loanHistory": "History", - "loanIncorrectOrMissingFields": "Some fields are missing or incorrect", - "loanInvalidNumber": "Please enter a number", - "loanInvalidDates": "Dates are not valid", - "loanItem": "Item", - "loanItems": "Items", - "loanItemHandling": "Item management", - "loanItemSelected": "selected item", - "loanItemsSelected": "selected items", - "loanLendingDuration": "Possible loan duration", - "loanLoan": "Loan", - "loanLoanHandling": "Loan management", - "loanLooking": "Searching", - "loanName": "Name", - "loanNext": "Next", - "loanNo": "No", - "loanNoAssociationsFounded": "No associations found", - "loanNoAvailableItems": "No available items", - "loanNoBorrower": "No borrower", - "loanNoItems": "No items", - "loanNoItemSelected": "No item selected", - "loanNoLoan": "No loan", - "loanNoReturnedDate": "No return date", - "loanQuantity": "Quantity", - "loanNone": "None", - "loanNote": "Note", - "loanNoValue": "Please enter a value", - "loanOnGoing": "Ongoing", - "loanOnGoingLoan": "Ongoing loan", - "loanOthers": "others", - "loanPaidCaution": "Deposit paid", - "loanPositiveNumber": "Please enter a positive number", - "loanPrevious": "Previous", - "loanReturned": "Returned", - "loanReturnedLoan": "Returned loan", - "loanReturningError": "Error while returning", - "loanReturningLoan": "Return", - "loanReturnLoan": "Return the loan?", - "loanReturnLoanDescription": "Do you want to return this loan?", - "loanToReturn": "To return", - "loanUnavailable": "Unavailable", - "loanUpdate": "Edit", - "loanUpdatedItem": "Item updated", - "loanUpdatedLoan": "Loan updated", - "loanUpdatingError": "Error while updating", - "loanYes": "Yes", - "loginAccountActivated": "Account activated", - "loginAccountNotActivated": "Account not activated", - "loginActivationCode": "Activation code", - "loginBirthday": "Date of birth", - "loginCanBeEmpty": "This field can be empty", - "loginConfirmPassword": "Confirm password", - "loginCreate": "Create", - "loginCreateAccount": "Create an account", - "loginCreateAccountTitle": "Create an\naccount", - "loginEmail": "Email", - "loginEmailEmpty": "Please enter an email address", - "loginEmailInvalid": "Please enter a Centrale email address.\nIf you don't have one, please contact Éclair", - "loginEmptyFieldError": "This field cannot be empty", - "loginEndActivation": "Complete activation", - "loginEndResetPassword": "Complete\npassword reset", - "loginErrorResetPassword": "Error during reset", - "loginExpectingDate": "A date is expected", - "loginFillAllFields": "Please fill all fields", - "loginFirstname": "First name", - "loginFloor": "Floor", - "loginForgetPassword": "Forgot\npassword", - "loginForgotPassword": "Forgot password?", - "loginInvalidToken": "Invalid activation code", - "loginLoginFailed": "Login failed", - "loginMailSendingError": "Error during account creation", - "loginMustBeIntError": "This field must be an integer", - "loginName": "Last name", - "loginNewPassword": "New password", - "loginPassword": "Password", - "loginPasswordLengthError": "Password must be at least 6 characters", - "loginPasswordUppercaseError": "Password must contain at least one uppercase letter", - "loginPasswordLowercaseError": "Password must contain at least one lowercase letter", - "loginPasswordNumberError": "Password must contain at least one number", - "loginPasswordSpecialCaracterError": "Password must contain at least one special character", - "loginPasswordMustMatch": "Passwords must match", - "loginPasswordStrengthVeryWeak": "Very weak", - "loginPasswordStrengthWeak": "Weak", - "loginPasswordStrengthMedium": "Medium", - "loginPasswordStrengthStrong": "Strong", - "loginPasswordStrengthVeryStrong": "Very strong", - "loginPhone": "Phone", - "loginPromo": "Incoming class (e.g., 2023)", - "loginSendedMail": "Confirmation email sent", - "loginSendedResetMail": "Reset email sent", - "loginSignIn": "Sign in", - "loginRegister": "Register", - "loginRecievedMail": "I received the email", - "loginRecover": "Reset", - "loginResetedPassword": "Password reset", - "loginResetPasswordTitle": "Reset\npassword", - "loginNickname": "Nickname", - "loginWelcomeBack": "Welcome back", - "loginAppName": "MyECL", - "othersCheckInternetConnection": "Please check your internet connection", - "othersRetry": "Retry", - "othersTooOldVersion": "Your app version is too old.\n\nPlease update the app.", - "othersUnableToConnectToServer": "Unable to connect to the server", - "othersVersion": "Version", - "othersNoModule": "No modules available, please try again later 😢😢", - "othersAdmin": "Admin", - "othersError": "An error occurred", - "othersNoValue": "Please enter a value", - "othersInvalidNumber": "Please enter a number", - "othersNoDateError": "Please enter a date", - "othersImageSizeTooBig": "Image size must not exceed 4 MB", - "othersImageError": "Error adding the image", - "phAddNewJournal": "Add a new journal", - "phNameField": "Name: ", - "phDateField": "Date: ", - "phDelete": "Are you sure you want to delete this journal?", - "phIrreversibleAction": "This action is irreversible", - "phToHeavyFile": "File too large", - "phAddPdfFile": "Add a PDF file", - "phEditPdfFile": "Edit PDF file", - "phPhName": "PH name", - "phDate": "Date", - "phAdded": "Added", - "phEdited": "Edited", - "phAddingFileError": "Add error", - "phMissingInformatonsOrPdf": "Missing information or PDF file", - "phAdd": "Add", - "phEdit": "Edit", - "phSeePreviousJournal": "See previous journals", - "phNoJournalInDatabase": "No PH yet in database", - "phSuccesDowloading": "Successfully downloaded", - "phonebookActiveMandate": "Active mandate:", - "phonebookAdd": "Add", - "phonebookAddAssociation": "Add an association", - "phonebookAddedAssociation": "Association added", - "phonebookAddedMember": "Member added", - "phonebookAddingError": "Error adding", - "phonebookAddMember": "Add a member", - "phonebookAddRole": "Add a role", - "phonebookAdmin": "Admin", - "phonebookAdminPage": "Admin page", - "phonebookAll": "All", - "phonebookApparentName": "Public role name:", - "phonebookAssociation": "Association:", - "phonebookAssociationDetail": "Association details:", - "phonebookAssociationKind": "Type of association:", - "phonebookAssociationPure": "Association", - "phonebookAssociationPureSearch": " Association", - "phonebookAssociations": "Associations:", - "phonebookCancel": "Cancel", - "phonebookChangeMandate": "Switch to mandate ", - "phonebookChangeMandateConfirm": "Are you sure you want to change the entire mandate?\nThis action is irreversible!", - "phonebookCopied": "Copied to clipboard", - "phonebookDeactivateAssociation": "Are you sure you want to deactivate this association?\nThis action is irreversible!", - "phonebookDeactivatedAssociation": "Association deactivated", - "phonebookDeactivatedAssociationWarning": "Warning, this association is deactivated, you cannot modify it", - "phonebookDeactivating": "Deactivate the association?", - "phonebookDeactivatingError": "Error during deactivation", - "phonebookDetail": "Details:", - "phonebookDeleteAssociation": "Delete the association?\nThis will erase all association history", - "phonebookDeletedAssociation": "Association deleted", - "phonebookDeletedMember": "Member deleted", - "phonebookDeleting": "Deleting", - "phonebookDeletingError": "Error deleting", - "phonebookDescription": "Description", - "phonebookEdit": "Edit", - "phonebookEditMembership": "Edit role", - "phonebookEmail": "Email:", - "phonebookEmailCopied": "Email copied to clipboard", - "phonebookEmptyApparentName": "Please enter a role name", - "phonebookEmptyFieldError": "A field is not filled", - "phonebookEmptyKindError": "Please choose an association type", - "phonebookEmptyMember": "No member selected", - "phonebookErrorAssociationLoading": "Error loading association", - "phonebookErrorAssociationNameEmpty": "Please enter an association name", - "phonebookErrorAssociationPicture": "Error editing association picture", - "phonebookErrorKindsLoading": "Error loading association types", - "phonebookErrorLoadAssociationList": "Error loading association list", - "phonebookErrorLoadAssociationMember": "Error loading association members", - "phonebookErrorLoadAssociationPicture": "Error loading association picture", - "phonebookErrorLoadProfilePicture": "Error", - "phonebookErrorRoleTagsLoading": "Error loading role tags", - "phonebookExistingMembership": "This member is already in the current mandate", - "phonebookFirstname": "First name:", - "phonebookGroups": "Associated groups:", - "phonebookMandateChangingError": "Error changing mandate", - "phonebookMember": "Member", - "phonebookMemberReordered": "Member reordered", - "phonebookMembers": "Members", - "phonebookMembershipAssociationError": "Please choose an association", - "phonebookMembershipRole": "Role:", - "phonebookMembershipRoleError": "Please choose a role", - "phonebookName": "Last name:", - "phonebookNameCopied": "Name and first name copied to clipboard", - "phonebookNamePure": "Last name", - "phonebookNewMandate": "New mandate", - "phonebookNewMandateConfirmed": "Mandate changed", - "phonebookNickname": "Nickname:", - "phonebookNicknameCopied": "Nickname copied to clipboard", - "phonebookNoAssociationFound": "No association found", - "phonebookNoMember": "No member", - "phonebookNoMemberRole": "No role found", - "phonebookPhone": "Phone:", - "phonebookPhonebook": "Phonebook", - "phonebookPhonebookSearch": "Search", - "phonebookPhonebookSearchAssociation": "Association", - "phonebookPhonebookSearchField": "Search:", - "phonebookPhonebookSearchName": "Last name/First name/Nickname", - "phonebookPhonebookSearchRole": "Position", - "phonebookPresidentRoleTag": "Prez'", - "phonebookPromoNotGiven": "Promotion not provided", - "phonebookPromotion": "Promotion:", - "phonebookReorderingError": "Error during reordering", - "phonebookResearch": "Search", - "phonebookRolePure": "Role", - "phonebookTooHeavyAssociationPicture": "Image is too large (max 4MB)", - "phonebookUpdateGroups": "Update groups", - "phonebookUpdatedAssociation": "Association updated", - "phonebookUpdatedAssociationPicture": "Association picture has been changed", - "phonebookUpdatedGroups": "Groups updated", - "phonebookUpdatedMember": "Member updated", - "phonebookUpdatingError": "Error during update", - "phonebookValidation": "Validate", - "purchasesPurchases": "Purchases", - "purchasesResearch": "Search", - "purchasesNoPurchasesFound": "No purchases found", - "purchasesNoTickets": "No tickets", - "purchasesTicketsError": "Error loading tickets", - "purchasesPurchasesError": "Error loading purchases", - "purchasesNoPurchases": "No purchase", - "purchasesTimes": "times", - "purchasesAlreadyUsed": "Already used", - "purchasesNotPaid": "Not validated", - "purchasesPleaseSelectProduct": "Please select a product", - "purchasesProducts": "Products", - "purchasesCancel": "Cancel", - "purchasesValidate": "Validate", - "purchasesLeftScan": "Scans remaining", - "purchasesTag": "Tag", - "purchasesHistory": "History", - "purchasesPleaseSelectSeller": "Please select a seller", - "purchasesNoTagGiven": "Warning, no tag entered", - "purchasesTickets": "Tickets", - "purchasesNoScannableProducts": "No scannable products", - "purchasesLoading": "Waiting for scan", - "purchasesScan": "Scan", - "raffleRaffle": "Raffle", - "rafflePrize": "Prize", - "rafflePrizes": "Prizes", - "raffleActualRaffles": "Current raffles", - "rafflePastRaffles": "Past raffles", - "raffleYourTickets": "All your tickets", - "raffleCreateMenu": "Creation menu", - "raffleNextRaffles": "Upcoming raffles", - "raffleNoTicket": "You have no ticket", - "raffleSeeRaffleDetail": "View prizes/tickets", - "raffleActualPrize": "Current prizes", - "raffleMajorPrize": "Major prizes", - "raffleTakeTickets": "Take your tickets", - "raffleNoTicketBuyable": "You cannot buy tickets right now", - "raffleNoCurrentPrize": "There are no prizes currently", - "raffleModifTombola": "You can modify your raffles or create new ones, all decisions must then be approved by admins", - "raffleCreateYourRaffle": "Your raffle creation menu", - "rafflePossiblePrice": "Possible prize", - "raffleInformation": "Information and statistics", - "raffleAccounts": "Accounts", - "raffleAdd": "Add", - "raffleUpdatedAmount": "Amount updated", - "raffleUpdatingError": "Error during update", - "raffleDeletedPrize": "Prize deleted", - "raffleDeletingError": "Error during deletion", - "raffleQuantity": "Quantity", - "raffleClose": "Close", - "raffleOpen": "Open", - "raffleAddTypeTicketSimple": "Add", - "raffleAddingError": "Error during addition", - "raffleEditTypeTicketSimple": "Edit", - "raffleFillField": "Field cannot be empty", - "raffleWaiting": "Loading", - "raffleEditingError": "Error during editing", - "raffleAddedTicket": "Ticket added", - "raffleEditedTicket": "Ticket edited", - "raffleAlreadyExistTicket": "Ticket already exists", - "raffleNumberExpected": "An integer is expected", - "raffleDeletedTicket": "Ticket deleted", - "raffleAddPrize": "Add", - "raffleEditPrize": "Edit", - "raffleOpenRaffle": "Open raffle", - "raffleCloseRaffle": "Close raffle", - "raffleOpenRaffleDescription": "You are going to open the raffle, users will be able to buy tickets. You will no longer be able to modify the raffle. Are you sure you want to continue?", - "raffleCloseRaffleDescription": "You are going to close the raffle, users will no longer be able to buy tickets. Are you sure you want to continue?", - "raffleNoCurrentRaffle": "There is no ongoing raffle", - "raffleBoughtTicket": "Ticket purchased", - "raffleDrawingError": "Error during drawing", - "raffleInvalidPrice": "Price must be greater than 0", - "raffleMustBePositive": "Number must be strictly positive", - "raffleDraw": "Draw", - "raffleDrawn": "Drawn", - "raffleError": "Error", - "raffleGathered": "Collected", - "raffleTickets": "Tickets", - "raffleTicket": "ticket", - "raffleWinner": "Winner", - "raffleNoPrize": "No prize", - "raffleDeletePrize": "Delete prize", - "raffleDeletePrizeDescription": "You are going to delete the prize, are you sure you want to continue?", - "raffleDrawing": "Drawing", - "raffleDrawingDescription": "Draw the prize winner?", - "raffleDeleteTicket": "Delete ticket", - "raffleDeleteTicketDescription": "You are going to delete the ticket, are you sure you want to continue?", - "raffleWinningTickets": "Winning tickets", - "raffleNoWinningTicketYet": "Winning tickets will be displayed here", - "raffleName": "Name", - "raffleDescription": "Description", - "raffleBuyThisTicket": "Buy this ticket", - "raffleLockedRaffle": "Locked raffle", - "raffleUnavailableRaffle": "Unavailable raffle", - "raffleNotEnoughMoney": "You don't have enough money", - "raffleWinnable": "winnable", - "raffleNoDescription": "No description", - "raffleAmount": "Balance", - "raffleLoading": "Loading", - "raffleTicketNumber": "Number of tickets", - "rafflePrice": "Price", - "raffleEditRaffle": "Edit raffle", - "raffleEdit": "Edit", - "raffleAddPackTicket": "Add ticket pack", - "recommendationRecommendation": "Recommendation", - "recommendationTitle": "Title", - "recommendationLogo": "Logo", - "recommendationCode": "Code", - "recommendationSummary": "Short summary", - "recommendationDescription": "Description", - "recommendationAdd": "Add", - "recommendationEdit": "Edit", - "recommendationDelete": "Delete", - "recommendationAddImage": "Please add an image", - "recommendationAddedRecommendation": "Deal added", - "recommendationEditedRecommendation": "Deal updated", - "recommendationDeleteRecommendationConfirmation": "Are you sure you want to delete this deal?", - "recommendationDeleteRecommendation": "Delete", - "recommendationDeletingRecommendationError": "Error during deletion", - "recommendationDeletedRecommendation": "Deal deleted", - "recommendationIncorrectOrMissingFields": "Incorrect or missing fields", - "recommendationEditingError": "Edit failed", - "recommendationAddingError": "Add failed", - "recommendationCopiedCode": "Discount code copied", - "seedLibraryAdd": "Add", - "seedLibraryAddedPlant": "Plant added", - "seedLibraryAddedSpecies": "Species added", - "seedLibraryAddingError": "Error during addition", - "seedLibraryAddPlant": "Deposit a plant", - "seedLibraryAddSpecies": "Add a species", - "seedLibraryAll": "All", - "seedLibraryAncestor": "Ancestor", - "seedLibraryAround": "around", - "seedLibraryAutumn": "Autumn", - "seedLibraryBorrowedPlant": "Borrowed plant", - "seedLibraryBorrowingDate": "Borrowing date:", - "seedLibraryBorrowPlant": "Borrow plant", - "seedLibraryCard": "Card", - "seedLibraryChoosingAncestor": "Please choose an ancestor", - "seedLibraryChoosingSpecies": "Please choose a species", - "seedLibraryChoosingSpeciesOrAncestor": "Please choose a species or an ancestor", - "seedLibraryContact": "Contact:", - "seedLibraryDays": "days", - "seedLibraryDeadMsg": "Do you want to declare the plant dead?", - "seedLibraryDeadPlant": "Dead plant", - "seedLibraryDeathDate": "Date of death", - "seedLibraryDeletedSpecies": "Species deleted", - "seedLibraryDeleteSpecies": "Delete species?", - "seedLibraryDeleting": "Deleting", - "seedLibraryDeletingError": "Error during deletion", - "seedLibraryDepositNotAvailable": "Plant deposit is not possible without borrowing a plant first", - "seedLibraryDescription": "Description", - "seedLibraryDifficulty": "Difficulty:", - "seedLibraryEdit": "Edit", - "seedLibraryEditedPlant": "Plant updated", - "seedLibraryEditInformation": "Edit information", - "seedLibraryEditingError": "Error during editing", - "seedLibraryEditSpecies": "Edit species", - "seedLibraryEmptyDifficultyError": "Please choose a difficulty", - "seedLibraryEmptyFieldError": "Please fill all fields", - "seedLibraryEmptyTypeError": "Please choose a plant type", - "seedLibraryEndMonth": "End month:", - "seedLibraryFacebookUrl": "Facebook link", - "seedLibraryFilters": "Filters", - "seedLibraryForum": "Oskour mom I killed my plant - Help forum", - "seedLibraryForumUrl": "Forum link", - "seedLibraryHelpSheets": "Plant sheets", - "seedLibraryInformation": "Information:", - "seedLibraryMaturationTime": "Maturation time", - "seedLibraryMonthJan": "January", - "seedLibraryMonthFeb": "February", - "seedLibraryMonthMar": "March", - "seedLibraryMonthApr": "April", - "seedLibraryMonthMay": "May", - "seedLibraryMonthJun": "June", - "seedLibraryMonthJul": "July", - "seedLibraryMonthAug": "August", - "seedLibraryMonthSep": "September", - "seedLibraryMonthOct": "October", - "seedLibraryMonthNov": "November", - "seedLibraryMonthDec": "December", - "seedLibraryMyPlants": "My plants", - "seedLibraryName": "Name", - "seedLibraryNbSeedsRecommended": "Number of seeds recommended", - "seedLibraryNbSeedsRecommendedError": "Please enter a recommended seed number greater than 0", - "seedLibraryNoDateError": "Please enter a date", - "seedLibraryNoFilteredPlants": "No plants match your search. Try other filters.", - "seedLibraryNoMorePlant": "No plants available", - "seedLibraryNoPersonalPlants": "You don't have any plants yet in your seed library. You can add some in the stocks.", - "seedLibraryNoSpecies": "No species found", - "seedLibraryNoStockPlants": "No plants available in stock", - "seedLibraryNotes": "Notes", - "seedLibraryOk": "OK", - "seedLibraryPlantationPeriod": "Planting period:", - "seedLibraryPlantationType": "Plantation type:", - "seedLibraryPlantDetail": "Plant details", - "seedLibraryPlantingDate": "Planting date", - "seedLibraryPlantingNow": "I'm planting it now", - "seedLibraryPrefix": "Prefix", - "seedLibraryPrefixError": "Prefix already used", - "seedLibraryPrefixLengthError": "The prefix must be 3 characters", - "seedLibraryPropagationMethod": "Propagation method:", - "seedLibraryReference": "Reference:", - "seedLibraryRemovedPlant": "Plant removed", - "seedLibraryRemovingError": "Error removing plant", - "seedLibraryResearch": "Search", - "seedLibrarySaveChanges": "Save changes", - "seedLibrarySeason": "Season:", - "seedLibrarySeed": "Seed", - "seedLibrarySeeds": "seeds", - "seedLibrarySeedDeposit": "Plant deposit", - "seedLibrarySeedLibrary": "Seed library", - "seedLibrarySeedQuantitySimple": "Seed quantity", - "seedLibrarySeedQuantity": "Seed quantity:", - "seedLibraryShowDeadPlants": "Show dead plants", - "seedLibrarySpecies": "Species:", - "seedLibrarySpeciesHelp": "Help on species", - "seedLibrarySpeciesPlural": "Species", - "seedLibrarySpeciesSimple": "Species", - "seedLibrarySpeciesType": "Species type:", - "seedLibrarySpring": "Spring", - "seedLibraryStartMonth": "Start month:", - "seedLibraryStock": "Available stock", - "seedLibrarySummer": "Summer", - "seedLibraryStocks": "Stocks", - "seedLibraryTimeUntilMaturation": "Time until maturation:", - "seedLibraryType": "Type:", - "seedLibraryUnableToOpen": "Unable to open link", - "seedLibraryUpdate": "Edit", - "seedLibraryUpdatedInformation": "Information updated", - "seedLibraryUpdatedSpecies": "Species updated", - "seedLibraryUpdatedPlant": "Plant updated", - "seedLibraryUpdatingError": "Error updating", - "seedLibraryWinter": "Winter", - "seedLibraryWriteReference": "Please write the following reference: ", - "settingsAccount": "Account", - "settingsAddProfilePicture": "Add a photo", - "settingsAdmin": "Administrator", - "settingsAskHelp": "Ask for help", - "settingsAssociation": "Association", - "settingsBirthday": "Birthday", - "settingsBugs": "Bugs", - "settingsChangePassword": "Change password", - "settingsChangingPassword": "Do you really want to change your password?", - "settingsConfirmPassword": "Confirm password", - "settingsCopied": "Copied!", - "settingsDarkMode": "Dark mode", - "settingsDarkModeOff": "Off", - "settingsDeleteLogs": "Delete logs?", - "settingsDeleteNotificationLogs": "Delete notification logs?", - "settingsDetelePersonalData": "Delete my personal data", - "settingsDetelePersonalDataDesc": "This action notifies the administrator that you want to delete your personal data.", - "settingsDeleting": "Deleting", - "settingsEdit": "Edit", - "settingsEditAccount": "Edit account", - "settingsEditPassword": "Edit password", - "settingsEmail": "Email", - "settingsEmptyField": "This field cannot be empty", - "settingsErrorProfilePicture": "Error editing profile picture", - "settingsErrorSendingDemand": "Error sending request", - "settingsEventsIcal": "Ical link for events", - "settingsExpectingDate": "Expected birth date", - "settingsFirstname": "First name", - "settingsFloor": "Floor", - "settingsHelp": "Help", - "settingsIcalCopied": "Ical link copied!", - "settingsLanguage": "Language", - "settingsLanguageVar": "English 🇬🇧", - "settingsLogs": "Logs", - "settingsModules": "Modules", - "settingsMyIcs": "My Ical link", - "settingsName": "Last name", - "settingsNewPassword": "New password", - "settingsNickname": "Nickname", - "settingsNotifications": "Notifications", - "settingsOldPassword": "Old password", - "settingsPasswordChanged": "Password changed", - "settingsPasswordsNotMatch": "Passwords do not match", - "settingsPersonalData": "Personal data", - "settingsPersonalisation": "Personalization", - "settingsPhone": "Phone", - "settingsProfilePicture": "Profile picture", - "settingsPromo": "Promotion", - "settingsRepportBug": "Report a bug", - "settingsSave": "Save", - "settingsSecurity": "Security", - "settingsSendedDemand": "Request sent", - "settingsSettings": "Settings", - "settingsTooHeavyProfilePicture": "Image is too large (max 4MB)", - "settingsUpdatedProfile": "Profile updated", - "settingsUpdatedProfilePicture": "Profile picture updated", - "settingsUpdateNotification": "Update notifications", - "settingsUpdatingError": "Error updating profile", - "settingsVersion": "Version", - "settingsPasswordStrength": "Password strength", - "settingsPasswordStrengthVeryWeak": "Very weak", - "settingsPasswordStrengthWeak": "Weak", - "settingsPasswordStrengthMedium": "Medium", - "settingsPasswordStrengthStrong": "Strong", - "settingsPasswordStrengthVeryStrong": "Very strong", - "settingsPhoneNumber": "Phone number", - "settingsValidate": "Confirm", - "settingsEditedAccount": "Account edited", - "settingsFailedToEditAccount": "Failed to edit account", - "settingsChooseLanguage": "Choose a language", - "settingsNotificationCounter": "{active}/{total} active {active, plural, zero {notification} one {notification} other {notifications}}", - "@settingsNotificationCounter": { - "description": "Affiche le nombre de notifications actives sur le total des notifications disponibles, avec gestion du pluriel", - "placeholders": { - "active": { "type": "int" }, - "total": { "type": "int" } - } - }, - "settingsEvent" : "Event", - "settingsIcal": "Ical link", - "settingsSynncWithCalendar": "Sync with calendar", - "settingsIcalLinkCopied": "Ical link copied", - "settingsProfile": "Profile", - "voteAdd": "Add", - "voteAddMember": "Add a member", - "voteAddedPretendance": "List added", - "voteAddedSection": "Section added", - "voteAddingError": "Error adding", - "voteAddPretendance": "Add a list", - "voteAddSection": "Add a section", - "voteAll": "All", - "voteAlreadyAddedMember": "Member already added", - "voteAlreadyVoted": "Vote recorded", - "voteChooseList": "Choose a list", - "voteClear": "Reset", - "voteClearVotes": "Reset votes", - "voteClosedVote": "Votes closed", - "voteCloseVote": "Close votes", - "voteConfirmVote": "Confirm vote", - "voteCountVote": "Count votes", - "voteDeletedAll": "All deleted", - "voteDeletedPipo": "Fake lists deleted", - "voteDeletedSection": "Section deleted", - "voteDeleteAll": "Delete all", - "voteDeleteAllDescription": "Do you really want to delete everything?", - "voteDeletePipo": "Delete fake lists", - "voteDeletePipoDescription": "Do you really want to delete the fake lists?", - "voteDeletePretendance": "Delete the list", - "voteDeletePretendanceDesc": "Do you really want to delete this list?", - "voteDeleteSection": "Delete the section", - "voteDeleteSectionDescription": "Do you really want to delete this section?", - "voteDeletingError": "Error deleting", - "voteDescription": "Description", - "voteEdit": "Edit", - "voteEditedPretendance": "List edited", - "voteEditedSection": "Section edited", - "voteEditingError": "Error editing", - "voteErrorClosingVotes": "Error closing votes", - "voteErrorCountingVotes": "Error counting votes", - "voteErrorResetingVotes": "Error resetting votes", - "voteErrorOpeningVotes": "Error opening votes", - "voteIncorrectOrMissingFields": "Incorrect or missing fields", - "voteMembers": "Members", - "voteName": "Name", - "voteNoPretendanceList": "No list of candidates", - "voteNoSection": "No section", - "voteCanNotVote": "You cannot vote", - "voteNoSectionList": "No section", - "voteNotOpenedVote": "Vote not opened", - "voteOnGoingCount": "Counting in progress", - "voteOpenVote": "Open votes", - "votePipo": "Fake", - "votePretendance": "Lists", - "votePretendanceDeleted": "Candidate list deleted", - "votePretendanceNotDeleted": "Error deleting", - "voteProgram": "Program", - "votePublish": "Publish", - "votePublishVoteDescription": "Do you really want to publish the votes?", - "voteResetedVotes": "Votes reset", - "voteResetVote": "Reset votes", - "voteResetVoteDescription": "What do you want to do?", - "voteRole": "Role", - "voteSectionDescription": "Section description", - "voteSection": "Section", - "voteSectionName": "Section name", - "voteSeeMore": "See more", - "voteSelected": "Selected", - "voteShowVotes": "Show votes", - "voteVote": "Vote", - "voteVoteError": "Error recording vote", - "voteVoteFor": "Vote for ", - "voteVoteNotStarted": "Vote not opened", - "voteVoters": "Voting groups", - "voteVoteSuccess": "Vote recorded", - "voteVotes": "Votes", - "voteVotesClosed": "Votes closed", - "voteVotesCounted": "Votes counted", - "voteVotesOpened": "Votes opened", - "voteWarning": "Warning", - "voteWarningMessage": "Selection will not be saved.\nDo you want to continue?", - "moduleAdvert": "Advert", - "moduleAmap": "AMAP", - "moduleBooking": "Booking", - "moduleCalendar": "Calendar", - "moduleCentralisation": "Centralisation", - "moduleCinema": "Cinema", - "moduleEvent": "Event", - "moduleFlappyBird": "Flappy Bird", - "moduleLoan": "Loan", - "modulePhonebook": "Phonebook", - "modulePurchases": "Purchases", - "moduleRaffle": "Raffle", - "moduleRecommendation": "Recommendation", - "moduleSeedLibrary": "Seed Library", - "moduleVote": "Vote", - "modulePh": "PH", - "moduleSettings": "Settings", - "moduleFeed": "Feed", - "moduleStyleGuide": "StyleGuide", - "moduleAdmin": "Administration", - "moduleOthers": "Others", - "modulePayment": "Payment", - "moduleAdvertDescription": "View the latest adverts", + { + "@@locale": "en", + "dateToday": "Today", + "dateYesterday": "Yesterday", + "dateTomorrow": "Tomorrow", + "dateAt": "at", + "dateFrom": "from", + "dateTo": "to", + "dateBetweenDays": "to", + "dateStarting": "Starting", + "dateLast": "Last", + "dateUntil": "Until", + "feedFilterAll": "All", + "feedFilterPending": "Pending", + "feedFilterApproved": "Approved", + "feedFilterRejected": "Rejected", + "feedEmptyAll": "No events available", + "feedEmptyPending": "No events pending approval", + "feedEmptyApproved": "No approved events", + "feedEmptyRejected": "No rejected events", + "feedEventManagement": "Event Management", + "eventActionCampaign": "You can vote", + "eventActionEvent": "You are invited", + "eventActionCampaignSubtitle": "Vote now", + "eventActionEventSubtitle": "Respond to the invitation", + "eventActionCampaignButton": "Vote", + "eventActionEventButton": "Participate", + "eventActionCampaignValidated": "I voted!", + "eventActionEventValidated": "I'm coming!", + "adminAccountTypes": "Account types", + "adminAdd": "Add", + "adminAddGroup": "Add group", + "adminAddMember": "Add member", + "adminAddedGroup": "Group created", + "adminAddedLoaner": "Lender added", + "adminAddedMember": "Member added", + "adminAddingError": "Error while adding", + "adminAddingMember": "Adding a member", + "adminAddLoaningGroup": "Add loaning group", + "adminAddSchool": "Add school", + "adminAddStructure": "Add structure", + "adminAddedSchool": "School created", + "adminAddedStructure": "Structure added", + "adminEditedStructure": "Structure edited", + "adminAdministration": "Administration", + "adminAssociationMembership": "Membership", + "adminAssociationMembershipName": "Membership name", + "adminAssociationsMemberships": "Memberships", + "adminClearFilters": "Clear filters", + "adminCreateAssociationMembership": "Create membership", + "adminCreatedAssociationMembership": "Membership created", + "adminCreationError": "Error during creation", + "adminDateError": "Start date must be before end date", + "adminDelete": "Delete", + "adminDeleteAssociationMembership": "Delete membership?", + "adminDeletedAssociationMembership": "Membership deleted", + "adminDeleteGroup": "Delete group?", + "adminDeletedGroup": "Group deleted", + "adminDeleteSchool": "Delete school?", + "adminDeletedSchool": "School deleted", + "adminDeleting": "Deleting", + "adminDeletingError": "Error while deleting", + "adminDescription": "Description", + "adminEclSchool": "Centrale Lyon", + "adminEdit": "Edit", + "adminEditStructure": "Edit structure", + "adminEditMembership": "Edit membership", + "adminEmptyDate": "Empty date", + "adminEmptyFieldError": "Name cannot be empty", + "adminEmailRegex": "Email Regex", + "adminEmptyUser": "Empty user", + "adminEndDate": "End date", + "adminEndDateMaximal": "Maximum end date", + "adminEndDateMinimal": "Minimum end date", + "adminError": "Error", + "adminFilters": "Filters", + "adminGroup": "Group", + "adminGroups": "Groups", + "adminLoaningGroup": "Loaning group", + "adminLooking": "Searching", + "adminManager": "Structure administrator", + "adminMaximum": "Maximum", + "adminMembers": "Members", + "adminMembershipAddingError": "Error while adding (likely due to overlapping dates)", + "adminMemberships": "Memberships", + "adminMembershipUpdatingError": "Error while updating (likely due to overlapping dates)", + "adminMinimum": "Minimum", + "adminModifyModuleVisibility": "Module visibility", + "adminMyEclPay": "MyECLPay", + "adminName": "Name", + "adminNoManager": "No manager selected", + "adminNoMember": "No member", + "adminNoMoreLoaner": "No lender available", + "adminNoSchool": "No school", + "adminRemoveGroupMember": "Remove member from group?", + "adminResearch": "Search", + "adminSchools": "Schools", + "adminStructures": "Structures", + "adminStartDate": "Start date", + "adminStartDateMaximal": "Maximum start date", + "adminStartDateMinimal": "Minimum start date", + "adminUpdatedAssociationMembership": "Membership updated", + "adminUpdatedGroup": "Group updated", + "adminUpdatedMembership": "Membership updated", + "adminUpdatingError": "Error while updating", + "adminUser": "User", + "adminValidateFilters": "Apply filters", + "adminVisibilities": "Visibilities", + "adminGroupNotification": "Group notifications", + "adminNotifyGroup": "Send a notification", + "adminTitle": "Title", + "adminContent": "Content", + "adminSend": "Send", + "adminNotificationSent": "Notification sent", + "adminFailedToSendNotification": "Failed to send notification", + "adminGroupsManagement": "Groups management", + "adminEditGroup": "Edit group", + "adminManageMembers": "Manage members", + "adminDeleteGroupConfirmation": "Are you sure you want to delete this group?", + "adminFailedToDeleteGroup": "Failed to delete group", + "adminUsersAndGroups": "Users and groups", + "adminUsersManagement": "Users management", + "adminUsersManagementDescription": "Manage users, groups, and associations", + "adminManageUserGroups": "Manage user groups", + "adminSendNotificationToGroup": "Send notification to group", + "adminPaiementModule": "Payment module", + "adminPaiement": "Payment", + "adminManagePaiementStructures": "Manage payment structures", + "adminManageUsersAssociationMemberships": "Manage users' association memberships", + "adminAssociationMembershipsManagement": "Association memberships management", + "adminChooseGroupManager": "Choose a group to manage this membership", + "adminSelectManager": "Select a manager", + "adminInviteUsers": "Invite users", + "adminImportList": "Import a list", + "adminInvitedUsers": "Invited users", + "adminFailedToInviteUsers": "Failed to invite users", + "adminDeleteUsers": "Delete users", + "adminAdmin": "Admin", + "adminAdverts": "Adverts", + "adminAnnouncers": "Announcers", + "adminManageAnnouncers": "Manage announcers", + "advertAdd": "Add", + "advertAddedAdvert": "Advert published", + "advertAddedAnnouncer": "Announcer added", + "advertAddingError": "Error while adding", + "advertAdmin": "Admin", + "advertAdvert": "Advert", + "advertChoosingAnnouncer": "Please choose an announcer", + "advertChoosingPoster": "Please choose an image", + "advertContent": "Content", + "advertDeleteAdvert": "Delete ad?", + "advertDeleteAnnouncer": "Delete announcer?", + "advertDeleting": "Deleting", + "advertEdit": "Edit", + "advertEditedAdvert": "Advert edited", + "advertEditingError": "Error while editing", + "advertGroupAdvert": "Group", + "advertIncorrectOrMissingFields": "Incorrect or missing fields", + "advertInvalidNumber": "Please enter a number", + "advertManagement": "Management", + "advertModifyAnnouncingGroup": "Edit announcement group", + "advertNoMoreAnnouncer": "No more announcers available", + "advertNoValue": "Please enter a value", + "advertPositiveNumber": "Please enter a positive number", + "advertRemovedAnnouncer": "Announcer removed", + "advertRemovingError": "Error during removal", + "advertTags": "Tags", + "advertTitle": "Title", + "advertMonthJan": "Jan", + "advertMonthFeb": "Feb", + "advertMonthMar": "Mar", + "advertMonthApr": "Apr", + "advertMonthMay": "May", + "advertMonthJun": "Jun", + "advertMonthJul": "Jul", + "advertMonthAug": "Aug", + "advertMonthSep": "Sep", + "advertMonthOct": "Oct", + "advertMonthNov": "Nov", + "advertMonthDec": "Dec", + "amapAccounts": "Accounts", + "amapAdd": "Add", + "amapAddDelivery": "Add delivery", + "amapAddedCommand": "Order added", + "amapAddedOrder": "Order added", + "amapAddedProduct": "Product added", + "amapAddedUser": "User added", + "amapAddProduct": "Add product", + "amapAddUser": "Add user", + "amapAddingACommand": "Add an order", + "amapAddingCommand": "Add the order", + "amapAddingError": "Error while adding", + "amapAddingProduct": "Add a product", + "amapAddOrder": "Add an order", + "amapAdmin": "Admin", + "amapAlreadyExistCommand": "An order already exists for this date", + "amapAmap": "Amap", + "amapAmount": "Balance", + "amapArchive": "Archive", + "amapArchiveDelivery": "Archive", + "amapArchivingDelivery": "Archiving delivery", + "amapCategory": "Category", + "amapCloseDelivery": "Lock", + "amapCommandDate": "Order date", + "amapCommandProducts": "Order products", + "amapConfirm": "Confirm", + "amapContact": "Association contacts", + "amapCreateCategory": "Create category", + "amapDelete": "Delete", + "amapDeleteDelivery": "Delete delivery?", + "amapDeleteDeliveryDescription": "Are you sure you want to delete this delivery?", + "amapDeletedDelivery": "Delivery deleted", + "amapDeletedOrder": "Order deleted", + "amapDeletedProduct": "Product deleted", + "amapDeleteProduct": "Delete product?", + "amapDeleteProductDescription": "Are you sure you want to delete this product?", + "amapDeleting": "Deleting", + "amapDeletingDelivery": "Delete delivery?", + "amapDeletingError": "Error while deleting", + "amapDeletingOrder": "Delete order?", + "amapDeletingProduct": "Delete product?", + "amapDeliver": "Delivery completed?", + "amapDeliveries": "Deliveries", + "amapDeliveringDelivery": "Are all orders delivered?", + "amapDelivery": "Delivery", + "amapDeliveryArchived": "Delivery archived", + "amapDeliveryDate": "Delivery date", + "amapDeliveryDelivered": "Delivery completed", + "amapDeliveryHistory": "Delivery history", + "amapDeliveryList": "Delivery list", + "amapDeliveryLocked": "Delivery locked", + "amapDeliveryOn": "Delivery on", + "amapDeliveryOpened": "Delivery opened", + "amapDeliveryNotArchived": "Delivery not archived", + "amapDeliveryNotLocked": "Delivery not locked", + "amapDeliveryNotDelivered": "Delivery not completed", + "amapDeliveryNotOpened": "Delivery not opened", + "amapEditDelivery": "Edit delivery", + "amapEditedCommand": "Order edited", + "amapEditingError": "Error while editing", + "amapEditProduct": "Edit product", + "amapEndingDelivery": "End of delivery", + "amapError": "Error", + "amapErrorLink": "Error opening link", + "amapErrorLoadingUser": "Error loading users", + "amapEvening": "Evening", + "amapExpectingNumber": "Please enter a number", + "amapFillField": "Please fill out this field", + "amapHandlingAccount": "Manage accounts", + "amapLoading": "Loading...", + "amapLoadingError": "Loading error", + "amapLock": "Lock", + "amapLocked": "Locked", + "amapLockedDelivery": "Delivery locked", + "amapLockedOrder": "Order locked", + "amapLooking": "Search", + "amapLockingDelivery": "Lock delivery?", + "amapMidDay": "Midday", + "amapMyOrders": "My orders", + "amapName": "Name", + "amapNextStep": "Next step", + "amapNoProduct": "No product", + "amapNoCurrentOrder": "No current order", + "amapNoMoney": "Not enough money", + "amapNoOpennedDelivery": "No open delivery", + "amapNoOrder": "No order", + "amapNoSelectedDelivery": "No delivery selected", + "amapNotEnoughMoney": "Not enough money", + "amapNotPlannedDelivery": "No scheduled delivery", + "amapOneOrder": "order", + "amapOpenDelivery": "Open", + "amapOpened": "Opened", + "amapOpenningDelivery": "Open delivery?", + "amapOrder": "Order", + "amapOrders": "Orders", + "amapPickChooseCategory": "Please enter a value or choose an existing category", + "amapPickDeliveryMoment": "Choose a delivery time", + "amapPresentation": "Presentation", + "amapPresentation1": "The AMAP (association for the preservation of small-scale farming) is a service offered by the Planet&Co association of ECL. You can receive products (fruit and vegetable baskets, juices, jams...) directly on campus!\n\nOrders must be placed before Friday at 9 PM and are delivered on campus on Tuesday from 1:00 PM to 1:45 PM (or from 6:15 PM to 6:30 PM if you can't come at midday) in the M16 hall.\n\nYou can only order if your balance allows it. You can top up your balance via the Lydia collection or by cheque during office hours.\n\nLydia top-up link: ", + "amapPresentation2": "\n\nFeel free to contact us if you have any issues!", + "amapPrice": "Price", + "amapProduct": "product", + "amapProducts": "Products", + "amapProductInDelivery": "Product in an unfinished delivery", + "amapQuantity": "Quantity", + "amapRequiredDate": "Date is required", + "amapSeeMore": "See more", + "amapThe": "The", + "amapUnlock": "Unlock", + "amapUnlockedDelivery": "Delivery unlocked", + "amapUnlockingDelivery": "Unlock delivery?", + "amapUpdate": "Edit", + "amapUpdatedAmount": "Balance updated", + "amapUpdatedOrder": "Order updated", + "amapUpdatedProduct": "Product updated", + "amapUpdatingError": "Update failed", + "amapUsersNotFound": "No users found", + "amapWaiting": "Pending", + "bookingAdd": "Add", + "bookingAddBookingPage": "Request", + "bookingAddRoom": "Add room", + "bookingAddBooking": "Add booking", + "bookingAddedBooking": "Request added", + "bookingAddedRoom": "Room added", + "bookingAddedManager": "Manager added", + "bookingAddingError": "Error while adding", + "bookingAddManager": "Add manager", + "bookingAdminPage": "Admin", + "bookingAllDay": "All day", + "bookingBookedFor": "Booked for", + "bookingBooking": "Booking", + "bookingBookingCreated": "Booking created", + "bookingBookingDemand": "Booking request", + "bookingBookingNote": "Booking note", + "bookingBookingPage": "Booking", + "bookingBookingReason": "Booking reason", + "bookingBy": "by", + "bookingConfirm": "Confirm", + "bookingConfirmation": "Confirmation", + "bookingConfirmBooking": "Confirm the booking?", + "bookingConfirmed": "Confirmed", + "bookingDates": "Dates", + "bookingDecline": "Decline", + "bookingDeclineBooking": "Decline the booking?", + "bookingDeclined": "Declined", + "bookingDelete": "Delete", + "bookingDeleting": "Deleting", + "bookingDeleteBooking": "Deleting", + "bookingDeleteBookingConfirmation": "Are you sure you want to delete this booking?", + "bookingDeletedBooking": "Booking deleted", + "bookingDeletedRoom": "Room deleted", + "bookingDeletedManager": "Manager deleted", + "bookingDeleteRoomConfirmation": "Are you sure you want to delete this room?\n\nThe room must have no current or upcoming bookings to be deleted", + "bookingDeleteManagerConfirmation": "Are you sure you want to delete this manager?\n\nThe manager must not be associated with any room to be deleted", + "bookingDeletingBooking": "Delete the booking?", + "bookingDeletingError": "Error while deleting", + "bookingDeletingRoom": "Delete the room?", + "bookingEdit": "Edit", + "bookingEditBooking": "Edit a booking", + "bookingEditionError": "Error while editing", + "bookingEditedBooking": "Booking edited", + "bookingEditedRoom": "Room edited", + "bookingEditedManager": "Manager edited", + "bookingEditManager": "Edit or delete a manager", + "bookingEditRoom": "Edit or delete a room", + "bookingEndDate": "End date", + "bookingEndHour": "End hour", + "bookingEntity": "For whom?", + "bookingError": "Error", + "bookingEventEvery": "Every", + "bookingHistoryPage": "History", + "bookingIncorrectOrMissingFields": "Incorrect or missing fields", + "bookingInterval": "Interval", + "bookingInvalidIntervalError": "Invalid interval", + "bookingInvalidDates": "Invalid dates", + "bookingInvalidRoom": "Invalid room", + "bookingKeysRequested": "Keys requested", + "bookingManagement": "Management", + "bookingManager": "Manager", + "bookingManagerName": "Manager name", + "bookingMultipleDay": "Multiple days", + "bookingMyBookings": "My bookings", + "bookingNecessaryKey": "Key needed", + "bookingNext": "Next", + "bookingNo": "No", + "bookingNoCurrentBooking": "No current booking", + "bookingNoDateError": "Please choose a date", + "bookingNoAppointmentInReccurence": "No slot exists with these recurrence settings", + "bookingNoDaySelected": "No day selected", + "bookingNoDescriptionError": "Please enter a description", + "bookingNoKeys": "No keys", + "bookingNoNoteError": "Please enter a note", + "bookingNoPhoneRegistered": "Number not provided", + "bookingNoReasonError": "Please enter a reason", + "bookingNoRoomFoundError": "No room registered", + "bookingNoRoomFound": "No room found", + "bookingNote": "Note", + "bookingOther": "Other", + "bookingPending": "Pending", + "bookingPrevious": "Previous", + "bookingReason": "Reason", + "bookingRecurrence": "Recurrence", + "bookingRecurrenceDays": "Recurrence days", + "bookingRecurrenceEndDate": "Recurrence end date", + "bookingRecurrent": "Recurrent", + "bookingRegisteredRooms": "Registered rooms", + "bookingRoom": "Room", + "bookingRoomName": "Room name", + "bookingStartDate": "Start date", + "bookingStartHour": "Start hour", + "bookingWeeks": "Weeks", + "bookingYes": "Yes", + "bookingWeekDayMon": "Monday", + "bookingWeekDayTue": "Tuesday", + "bookingWeekDayWed": "Wednesday", + "bookingWeekDayThu": "Thursday", + "bookingWeekDayFri": "Friday", + "bookingWeekDaySat": "Saturday", + "bookingWeekDaySun": "Sunday", + "cinemaAdd": "Add", + "cinemaAddedSession": "Session added", + "cinemaAddingError": "Error while adding", + "cinemaAddSession": "Add a session", + "cinemaCinema": "Cinema", + "cinemaDeleteSession": "Delete the session?", + "cinemaDeleting": "Deleting", + "cinemaDuration": "Duration", + "cinemaEdit": "Edit", + "cinemaEditedSession": "Session edited", + "cinemaEditingError": "Error while editing", + "cinemaEditSession": "Edit the session", + "cinemaEmptyUrl": "Please enter a URL", + "cinemaImportFromTMDB": "Import from TMDB", + "cinemaIncomingSession": "Now showing", + "cinemaIncorrectOrMissingFields": "Incorrect or missing fields", + "cinemaInvalidUrl": "Invalid URL", + "cinemaGenre": "Genre", + "cinemaName": "Name", + "cinemaNoDateError": "Please enter a date", + "cinemaNoDuration": "Please enter a duration", + "cinemaNoOverview": "No synopsis", + "cinemaNoPoster": "No poster", + "cinemaNoSession": "No session", + "cinemaOverview": "Synopsis", + "cinemaPosterUrl": "Poster URL", + "cinemaSessionDate": "Session day", + "cinemaStartHour": "Start hour", + "cinemaTagline": "Tagline", + "cinemaThe": "The", + "drawerAdmin": "Administration", + "drawerAndroidAppLink": "https://play.google.com/store/apps/details?id=fr.myecl.titan", + "drawerCopied": "Copied!", + "drawerDownloadAppOnMobileDevice": "This site is the web version of the MyECL app. We invite you to download the app. Use this site only if you have problems with the app.\n", + "drawerIosAppLink": "https://apps.apple.com/fr/app/myecl/id6444443430", + "drawerLoginOut": "Do you want to log out?", + "drawerLogOut": "Log out", + "drawerOr": " or ", + "drawerSettings": "Settings", + "eventAdd": "Add", + "eventAddEvent": "Add an event", + "eventAddedEvent": "Event added", + "eventAddingError": "Error while adding", + "eventAllDay": "All day", + "eventConfirm": "Confirm", + "eventConfirmEvent": "Confirm the event?", + "eventConfirmation": "Confirmation", + "eventConfirmed": "Confirmed", + "eventDates": "Dates", + "eventDecline": "Decline", + "eventDeclineEvent": "Decline the event?", + "eventDeclined": "Declined", + "eventDelete": "Delete", + "eventDeletedEvent": "Event deleted", + "eventDeleting": "Deleting", + "eventDeletingError": "Error while deleting", + "eventDeletingEvent": "Delete the event?", + "eventDescription": "Description", + "eventEdit": "Edit", + "eventEditEvent": "Edit an event", + "eventEditedEvent": "Event edited", + "eventEditingError": "Error while editing", + "eventEndDate": "End date", + "eventEndHour": "End hour", + "eventError": "Error", + "eventEventList": "Event list", + "eventEventType": "Event type", + "eventEvery": "Every", + "eventHistory": "History", + "eventIncorrectOrMissingFields": "Some fields are incorrect or missing", + "eventInterval": "Interval", + "eventInvalidDates": "End date must be after start date", + "eventInvalidIntervalError": "Please enter a valid interval", + "eventLocation": "Location", + "eventMyEvents": "My events", + "eventName": "Name", + "eventNext": "Next", + "eventNo": "No", + "eventNoCurrentEvent": "No current event", + "eventNoDateError": "Please enter a date", + "eventNoDaySelected": "No day selected", + "eventNoDescriptionError": "Please enter a description", + "eventNoEvent": "No event", + "eventNoNameError": "Please enter a name", + "eventNoOrganizerError": "Please enter an organizer", + "eventNoPlaceError": "Please enter a location", + "eventNoPhoneRegistered": "Number not provided", + "eventNoRuleError": "Please enter a recurrence rule", + "eventOrganizer": "Organizer", + "eventOther": "Other", + "eventPending": "Pending", + "eventPrevious": "Previous", + "eventRecurrence": "Recurrence", + "eventRecurrenceDays": "Recurrence days", + "eventRecurrenceEndDate": "Recurrence end date", + "eventRecurrenceRule": "Recurrence rule", + "eventRoom": "Room", + "eventStartDate": "Start date", + "eventStartHour": "Start hour", + "eventTitle": "Events", + "eventYes": "Yes", + "eventEventEvery": "Every", + "eventWeeks": "weeks", + "eventDayMon": "Monday", + "eventDayTue": "Tuesday", + "eventDayWed": "Wednesday", + "eventDayThu": "Thursday", + "eventDayFri": "Friday", + "eventDaySat": "Saturday", + "eventDaySun": "Sunday", + "homeCalendar": "Calendar", + "homeEventOf": "Events of", + "homeIncomingEvents": "Upcoming events", + "homeLastInfos": "Latest announcements", + "homeNoEvents": "No events", + "homeTranslateDayShortMon": "Mon", + "homeTranslateDayShortTue": "Tue", + "homeTranslateDayShortWed": "Wed", + "homeTranslateDayShortThu": "Thu", + "homeTranslateDayShortFri": "Fri", + "homeTranslateDayShortSat": "Sat", + "homeTranslateDayShortSun": "Sun", + "loanAdd": "Add", + "loanAddLoan": "Add a loan", + "loanAddObject": "Add an object", + "loanAddedLoan": "Loan added", + "loanAddedObject": "Object added", + "loanAddedRoom": "Room added", + "loanAddingError": "Error while adding", + "loanAdmin": "Administrator", + "loanAvailable": "Available", + "loanAvailableMultiple": "Available", + "loanBorrowed": "Borrowed", + "loanBorrowedMultiple": "Borrowed", + "loanAnd": "and", + "loanAssociation": "Association", + "loanAvailableItems": "Available items", + "loanBeginDate": "Loan start date", + "loanBorrower": "Borrower", + "loanCaution": "Deposit", + "loanCancel": "Cancel", + "loanConfirm": "Confirm", + "loanConfirmation": "Confirmation", + "loanDates": "Dates", + "loanDays": "Days", + "loanDelay": "Extension delay", + "loanDelete": "Delete", + "loanDeletingLoan": "Delete the loan?", + "loanDeletedItem": "Object deleted", + "loanDeletedLoan": "Loan deleted", + "loanDeleting": "Deleting", + "loanDeletingError": "Error while deleting", + "loanDeletingItem": "Delete the object?", + "loanDuration": "Duration", + "loanEdit": "Edit", + "loanEditItem": "Edit the object", + "loanEditLoan": "Edit the loan", + "loanEditedRoom": "Room edited", + "loanEndDate": "Loan end date", + "loanEnded": "Ended", + "loanEnterDate": "Please enter a date", + "loanExtendedLoan": "Extended loan", + "loanExtendingError": "Error while extending", + "loanHistory": "History", + "loanIncorrectOrMissingFields": "Some fields are missing or incorrect", + "loanInvalidNumber": "Please enter a number", + "loanInvalidDates": "Dates are not valid", + "loanItem": "Item", + "loanItems": "Items", + "loanItemHandling": "Item management", + "loanItemSelected": "selected item", + "loanItemsSelected": "selected items", + "loanLendingDuration": "Possible loan duration", + "loanLoan": "Loan", + "loanLoanHandling": "Loan management", + "loanLooking": "Searching", + "loanName": "Name", + "loanNext": "Next", + "loanNo": "No", + "loanNoAssociationsFounded": "No associations found", + "loanNoAvailableItems": "No available items", + "loanNoBorrower": "No borrower", + "loanNoItems": "No items", + "loanNoItemSelected": "No item selected", + "loanNoLoan": "No loan", + "loanNoReturnedDate": "No return date", + "loanQuantity": "Quantity", + "loanNone": "None", + "loanNote": "Note", + "loanNoValue": "Please enter a value", + "loanOnGoing": "Ongoing", + "loanOnGoingLoan": "Ongoing loan", + "loanOthers": "others", + "loanPaidCaution": "Deposit paid", + "loanPositiveNumber": "Please enter a positive number", + "loanPrevious": "Previous", + "loanReturned": "Returned", + "loanReturnedLoan": "Returned loan", + "loanReturningError": "Error while returning", + "loanReturningLoan": "Return", + "loanReturnLoan": "Return the loan?", + "loanReturnLoanDescription": "Do you want to return this loan?", + "loanToReturn": "To return", + "loanUnavailable": "Unavailable", + "loanUpdate": "Edit", + "loanUpdatedItem": "Item updated", + "loanUpdatedLoan": "Loan updated", + "loanUpdatingError": "Error while updating", + "loanYes": "Yes", + "loginAccountActivated": "Account activated", + "loginAccountNotActivated": "Account not activated", + "loginActivationCode": "Activation code", + "loginBirthday": "Date of birth", + "loginCanBeEmpty": "This field can be empty", + "loginConfirmPassword": "Confirm password", + "loginCreate": "Create", + "loginCreateAccount": "Create an account", + "loginCreateAccountTitle": "Create an\naccount", + "loginEmail": "Email", + "loginEmailEmpty": "Please enter an email address", + "loginEmailInvalid": "Please enter a Centrale email address.\nIf you don't have one, please contact Éclair", + "loginEmptyFieldError": "This field cannot be empty", + "loginEndActivation": "Complete activation", + "loginEndResetPassword": "Complete\npassword reset", + "loginErrorResetPassword": "Error during reset", + "loginExpectingDate": "A date is expected", + "loginFillAllFields": "Please fill all fields", + "loginFirstname": "First name", + "loginFloor": "Floor", + "loginForgetPassword": "Forgot\npassword", + "loginForgotPassword": "Forgot password?", + "loginInvalidToken": "Invalid activation code", + "loginLoginFailed": "Login failed", + "loginMailSendingError": "Error during account creation", + "loginMustBeIntError": "This field must be an integer", + "loginName": "Last name", + "loginNewPassword": "New password", + "loginPassword": "Password", + "loginPasswordLengthError": "Password must be at least 6 characters", + "loginPasswordUppercaseError": "Password must contain at least one uppercase letter", + "loginPasswordLowercaseError": "Password must contain at least one lowercase letter", + "loginPasswordNumberError": "Password must contain at least one number", + "loginPasswordSpecialCaracterError": "Password must contain at least one special character", + "loginPasswordMustMatch": "Passwords must match", + "loginPasswordStrengthVeryWeak": "Very weak", + "loginPasswordStrengthWeak": "Weak", + "loginPasswordStrengthMedium": "Medium", + "loginPasswordStrengthStrong": "Strong", + "loginPasswordStrengthVeryStrong": "Very strong", + "loginPhone": "Phone", + "loginPromo": "Incoming class (e.g., 2023)", + "loginSendedMail": "Confirmation email sent", + "loginSendedResetMail": "Reset email sent", + "loginSignIn": "Sign in", + "loginRegister": "Register", + "loginRecievedMail": "I received the email", + "loginRecover": "Reset", + "loginResetedPassword": "Password reset", + "loginResetPasswordTitle": "Reset\npassword", + "loginNickname": "Nickname", + "loginWelcomeBack": "Welcome back", + "loginAppName": "MyECL", + "othersCheckInternetConnection": "Please check your internet connection", + "othersRetry": "Retry", + "othersTooOldVersion": "Your app version is too old.\n\nPlease update the app.", + "othersUnableToConnectToServer": "Unable to connect to the server", + "othersVersion": "Version", + "othersNoModule": "No modules available, please try again later 😢😢", + "othersAdmin": "Admin", + "othersError": "An error occurred", + "othersNoValue": "Please enter a value", + "othersInvalidNumber": "Please enter a number", + "othersNoDateError": "Please enter a date", + "othersImageSizeTooBig": "Image size must not exceed 4 MB", + "othersImageError": "Error adding the image", + "phAddNewJournal": "Add a new journal", + "phNameField": "Name: ", + "phDateField": "Date: ", + "phDelete": "Are you sure you want to delete this journal?", + "phIrreversibleAction": "This action is irreversible", + "phToHeavyFile": "File too large", + "phAddPdfFile": "Add a PDF file", + "phEditPdfFile": "Edit PDF file", + "phPhName": "PH name", + "phDate": "Date", + "phAdded": "Added", + "phEdited": "Edited", + "phAddingFileError": "Add error", + "phMissingInformatonsOrPdf": "Missing information or PDF file", + "phAdd": "Add", + "phEdit": "Edit", + "phSeePreviousJournal": "See previous journals", + "phNoJournalInDatabase": "No PH yet in database", + "phSuccesDowloading": "Successfully downloaded", + "phonebookActiveMandate": "Active mandate:", + "phonebookAdd": "Add", + "phonebookAddAssociation": "Add an association", + "phonebookAddedAssociation": "Association added", + "phonebookAddedMember": "Member added", + "phonebookAddingError": "Error adding", + "phonebookAddMember": "Add a member", + "phonebookAddRole": "Add a role", + "phonebookAdmin": "Admin", + "phonebookAdminPage": "Admin page", + "phonebookAll": "All", + "phonebookApparentName": "Public role name:", + "phonebookAssociation": "Association:", + "phonebookAssociationDetail": "Association details:", + "phonebookAssociationKind": "Type of association:", + "phonebookAssociationPure": "Association", + "phonebookAssociationPureSearch": " Association", + "phonebookAssociations": "Associations:", + "phonebookCancel": "Cancel", + "phonebookChangeMandate": "Switch to mandate ", + "phonebookChangeMandateConfirm": "Are you sure you want to change the entire mandate?\nThis action is irreversible!", + "phonebookCopied": "Copied to clipboard", + "phonebookDeactivateAssociation": "Are you sure you want to deactivate this association?\nThis action is irreversible!", + "phonebookDeactivatedAssociation": "Association deactivated", + "phonebookDeactivatedAssociationWarning": "Warning, this association is deactivated, you cannot modify it", + "phonebookDeactivating": "Deactivate the association?", + "phonebookDeactivatingError": "Error during deactivation", + "phonebookDetail": "Details:", + "phonebookDeleteAssociation": "Delete the association?\nThis will erase all association history", + "phonebookDeletedAssociation": "Association deleted", + "phonebookDeletedMember": "Member deleted", + "phonebookDeleting": "Deleting", + "phonebookDeletingError": "Error deleting", + "phonebookDescription": "Description", + "phonebookEdit": "Edit", + "phonebookEditMembership": "Edit role", + "phonebookEmail": "Email:", + "phonebookEmailCopied": "Email copied to clipboard", + "phonebookEmptyApparentName": "Please enter a role name", + "phonebookEmptyFieldError": "A field is not filled", + "phonebookEmptyKindError": "Please choose an association type", + "phonebookEmptyMember": "No member selected", + "phonebookErrorAssociationLoading": "Error loading association", + "phonebookErrorAssociationNameEmpty": "Please enter an association name", + "phonebookErrorAssociationPicture": "Error editing association picture", + "phonebookErrorKindsLoading": "Error loading association types", + "phonebookErrorLoadAssociationList": "Error loading association list", + "phonebookErrorLoadAssociationMember": "Error loading association members", + "phonebookErrorLoadAssociationPicture": "Error loading association picture", + "phonebookErrorLoadProfilePicture": "Error", + "phonebookErrorRoleTagsLoading": "Error loading role tags", + "phonebookExistingMembership": "This member is already in the current mandate", + "phonebookFirstname": "First name:", + "phonebookGroups": "Associated groups:", + "phonebookMandateChangingError": "Error changing mandate", + "phonebookMember": "Member", + "phonebookMemberReordered": "Member reordered", + "phonebookMembers": "Members", + "phonebookMembershipAssociationError": "Please choose an association", + "phonebookMembershipRole": "Role:", + "phonebookMembershipRoleError": "Please choose a role", + "phonebookName": "Last name:", + "phonebookNameCopied": "Name and first name copied to clipboard", + "phonebookNamePure": "Last name", + "phonebookNewMandate": "New mandate", + "phonebookNewMandateConfirmed": "Mandate changed", + "phonebookNickname": "Nickname:", + "phonebookNicknameCopied": "Nickname copied to clipboard", + "phonebookNoAssociationFound": "No association found", + "phonebookNoMember": "No member", + "phonebookNoMemberRole": "No role found", + "phonebookPhone": "Phone:", + "phonebookPhonebook": "Phonebook", + "phonebookPhonebookSearch": "Search", + "phonebookPhonebookSearchAssociation": "Association", + "phonebookPhonebookSearchField": "Search:", + "phonebookPhonebookSearchName": "Last name/First name/Nickname", + "phonebookPhonebookSearchRole": "Position", + "phonebookPresidentRoleTag": "Prez'", + "phonebookPromoNotGiven": "Promotion not provided", + "phonebookPromotion": "Promotion:", + "phonebookReorderingError": "Error during reordering", + "phonebookResearch": "Search", + "phonebookRolePure": "Role", + "phonebookTooHeavyAssociationPicture": "Image is too large (max 4MB)", + "phonebookUpdateGroups": "Update groups", + "phonebookUpdatedAssociation": "Association updated", + "phonebookUpdatedAssociationPicture": "Association picture has been changed", + "phonebookUpdatedGroups": "Groups updated", + "phonebookUpdatedMember": "Member updated", + "phonebookUpdatingError": "Error during update", + "phonebookValidation": "Validate", + "purchasesPurchases": "Purchases", + "purchasesResearch": "Search", + "purchasesNoPurchasesFound": "No purchases found", + "purchasesNoTickets": "No tickets", + "purchasesTicketsError": "Error loading tickets", + "purchasesPurchasesError": "Error loading purchases", + "purchasesNoPurchases": "No purchase", + "purchasesTimes": "times", + "purchasesAlreadyUsed": "Already used", + "purchasesNotPaid": "Not validated", + "purchasesPleaseSelectProduct": "Please select a product", + "purchasesProducts": "Products", + "purchasesCancel": "Cancel", + "purchasesValidate": "Validate", + "purchasesLeftScan": "Scans remaining", + "purchasesTag": "Tag", + "purchasesHistory": "History", + "purchasesPleaseSelectSeller": "Please select a seller", + "purchasesNoTagGiven": "Warning, no tag entered", + "purchasesTickets": "Tickets", + "purchasesNoScannableProducts": "No scannable products", + "purchasesLoading": "Waiting for scan", + "purchasesScan": "Scan", + "raffleRaffle": "Raffle", + "rafflePrize": "Prize", + "rafflePrizes": "Prizes", + "raffleActualRaffles": "Current raffles", + "rafflePastRaffles": "Past raffles", + "raffleYourTickets": "All your tickets", + "raffleCreateMenu": "Creation menu", + "raffleNextRaffles": "Upcoming raffles", + "raffleNoTicket": "You have no ticket", + "raffleSeeRaffleDetail": "View prizes/tickets", + "raffleActualPrize": "Current prizes", + "raffleMajorPrize": "Major prizes", + "raffleTakeTickets": "Take your tickets", + "raffleNoTicketBuyable": "You cannot buy tickets right now", + "raffleNoCurrentPrize": "There are no prizes currently", + "raffleModifTombola": "You can modify your raffles or create new ones, all decisions must then be approved by admins", + "raffleCreateYourRaffle": "Your raffle creation menu", + "rafflePossiblePrice": "Possible prize", + "raffleInformation": "Information and statistics", + "raffleAccounts": "Accounts", + "raffleAdd": "Add", + "raffleUpdatedAmount": "Amount updated", + "raffleUpdatingError": "Error during update", + "raffleDeletedPrize": "Prize deleted", + "raffleDeletingError": "Error during deletion", + "raffleQuantity": "Quantity", + "raffleClose": "Close", + "raffleOpen": "Open", + "raffleAddTypeTicketSimple": "Add", + "raffleAddingError": "Error during addition", + "raffleEditTypeTicketSimple": "Edit", + "raffleFillField": "Field cannot be empty", + "raffleWaiting": "Loading", + "raffleEditingError": "Error during editing", + "raffleAddedTicket": "Ticket added", + "raffleEditedTicket": "Ticket edited", + "raffleAlreadyExistTicket": "Ticket already exists", + "raffleNumberExpected": "An integer is expected", + "raffleDeletedTicket": "Ticket deleted", + "raffleAddPrize": "Add", + "raffleEditPrize": "Edit", + "raffleOpenRaffle": "Open raffle", + "raffleCloseRaffle": "Close raffle", + "raffleOpenRaffleDescription": "You are going to open the raffle, users will be able to buy tickets. You will no longer be able to modify the raffle. Are you sure you want to continue?", + "raffleCloseRaffleDescription": "You are going to close the raffle, users will no longer be able to buy tickets. Are you sure you want to continue?", + "raffleNoCurrentRaffle": "There is no ongoing raffle", + "raffleBoughtTicket": "Ticket purchased", + "raffleDrawingError": "Error during drawing", + "raffleInvalidPrice": "Price must be greater than 0", + "raffleMustBePositive": "Number must be strictly positive", + "raffleDraw": "Draw", + "raffleDrawn": "Drawn", + "raffleError": "Error", + "raffleGathered": "Collected", + "raffleTickets": "Tickets", + "raffleTicket": "ticket", + "raffleWinner": "Winner", + "raffleNoPrize": "No prize", + "raffleDeletePrize": "Delete prize", + "raffleDeletePrizeDescription": "You are going to delete the prize, are you sure you want to continue?", + "raffleDrawing": "Drawing", + "raffleDrawingDescription": "Draw the prize winner?", + "raffleDeleteTicket": "Delete ticket", + "raffleDeleteTicketDescription": "You are going to delete the ticket, are you sure you want to continue?", + "raffleWinningTickets": "Winning tickets", + "raffleNoWinningTicketYet": "Winning tickets will be displayed here", + "raffleName": "Name", + "raffleDescription": "Description", + "raffleBuyThisTicket": "Buy this ticket", + "raffleLockedRaffle": "Locked raffle", + "raffleUnavailableRaffle": "Unavailable raffle", + "raffleNotEnoughMoney": "You don't have enough money", + "raffleWinnable": "winnable", + "raffleNoDescription": "No description", + "raffleAmount": "Balance", + "raffleLoading": "Loading", + "raffleTicketNumber": "Number of tickets", + "rafflePrice": "Price", + "raffleEditRaffle": "Edit raffle", + "raffleEdit": "Edit", + "raffleAddPackTicket": "Add ticket pack", + "recommendationRecommendation": "Recommendation", + "recommendationTitle": "Title", + "recommendationLogo": "Logo", + "recommendationCode": "Code", + "recommendationSummary": "Short summary", + "recommendationDescription": "Description", + "recommendationAdd": "Add", + "recommendationEdit": "Edit", + "recommendationDelete": "Delete", + "recommendationAddImage": "Please add an image", + "recommendationAddedRecommendation": "Deal added", + "recommendationEditedRecommendation": "Deal updated", + "recommendationDeleteRecommendationConfirmation": "Are you sure you want to delete this deal?", + "recommendationDeleteRecommendation": "Delete", + "recommendationDeletingRecommendationError": "Error during deletion", + "recommendationDeletedRecommendation": "Deal deleted", + "recommendationIncorrectOrMissingFields": "Incorrect or missing fields", + "recommendationEditingError": "Edit failed", + "recommendationAddingError": "Add failed", + "recommendationCopiedCode": "Discount code copied", + "seedLibraryAdd": "Add", + "seedLibraryAddedPlant": "Plant added", + "seedLibraryAddedSpecies": "Species added", + "seedLibraryAddingError": "Error during addition", + "seedLibraryAddPlant": "Deposit a plant", + "seedLibraryAddSpecies": "Add a species", + "seedLibraryAll": "All", + "seedLibraryAncestor": "Ancestor", + "seedLibraryAround": "around", + "seedLibraryAutumn": "Autumn", + "seedLibraryBorrowedPlant": "Borrowed plant", + "seedLibraryBorrowingDate": "Borrowing date:", + "seedLibraryBorrowPlant": "Borrow plant", + "seedLibraryCard": "Card", + "seedLibraryChoosingAncestor": "Please choose an ancestor", + "seedLibraryChoosingSpecies": "Please choose a species", + "seedLibraryChoosingSpeciesOrAncestor": "Please choose a species or an ancestor", + "seedLibraryContact": "Contact:", + "seedLibraryDays": "days", + "seedLibraryDeadMsg": "Do you want to declare the plant dead?", + "seedLibraryDeadPlant": "Dead plant", + "seedLibraryDeathDate": "Date of death", + "seedLibraryDeletedSpecies": "Species deleted", + "seedLibraryDeleteSpecies": "Delete species?", + "seedLibraryDeleting": "Deleting", + "seedLibraryDeletingError": "Error during deletion", + "seedLibraryDepositNotAvailable": "Plant deposit is not possible without borrowing a plant first", + "seedLibraryDescription": "Description", + "seedLibraryDifficulty": "Difficulty:", + "seedLibraryEdit": "Edit", + "seedLibraryEditedPlant": "Plant updated", + "seedLibraryEditInformation": "Edit information", + "seedLibraryEditingError": "Error during editing", + "seedLibraryEditSpecies": "Edit species", + "seedLibraryEmptyDifficultyError": "Please choose a difficulty", + "seedLibraryEmptyFieldError": "Please fill all fields", + "seedLibraryEmptyTypeError": "Please choose a plant type", + "seedLibraryEndMonth": "End month:", + "seedLibraryFacebookUrl": "Facebook link", + "seedLibraryFilters": "Filters", + "seedLibraryForum": "Oskour mom I killed my plant - Help forum", + "seedLibraryForumUrl": "Forum link", + "seedLibraryHelpSheets": "Plant sheets", + "seedLibraryInformation": "Information:", + "seedLibraryMaturationTime": "Maturation time", + "seedLibraryMonthJan": "January", + "seedLibraryMonthFeb": "February", + "seedLibraryMonthMar": "March", + "seedLibraryMonthApr": "April", + "seedLibraryMonthMay": "May", + "seedLibraryMonthJun": "June", + "seedLibraryMonthJul": "July", + "seedLibraryMonthAug": "August", + "seedLibraryMonthSep": "September", + "seedLibraryMonthOct": "October", + "seedLibraryMonthNov": "November", + "seedLibraryMonthDec": "December", + "seedLibraryMyPlants": "My plants", + "seedLibraryName": "Name", + "seedLibraryNbSeedsRecommended": "Number of seeds recommended", + "seedLibraryNbSeedsRecommendedError": "Please enter a recommended seed number greater than 0", + "seedLibraryNoDateError": "Please enter a date", + "seedLibraryNoFilteredPlants": "No plants match your search. Try other filters.", + "seedLibraryNoMorePlant": "No plants available", + "seedLibraryNoPersonalPlants": "You don't have any plants yet in your seed library. You can add some in the stocks.", + "seedLibraryNoSpecies": "No species found", + "seedLibraryNoStockPlants": "No plants available in stock", + "seedLibraryNotes": "Notes", + "seedLibraryOk": "OK", + "seedLibraryPlantationPeriod": "Planting period:", + "seedLibraryPlantationType": "Plantation type:", + "seedLibraryPlantDetail": "Plant details", + "seedLibraryPlantingDate": "Planting date", + "seedLibraryPlantingNow": "I'm planting it now", + "seedLibraryPrefix": "Prefix", + "seedLibraryPrefixError": "Prefix already used", + "seedLibraryPrefixLengthError": "The prefix must be 3 characters", + "seedLibraryPropagationMethod": "Propagation method:", + "seedLibraryReference": "Reference:", + "seedLibraryRemovedPlant": "Plant removed", + "seedLibraryRemovingError": "Error removing plant", + "seedLibraryResearch": "Search", + "seedLibrarySaveChanges": "Save changes", + "seedLibrarySeason": "Season:", + "seedLibrarySeed": "Seed", + "seedLibrarySeeds": "seeds", + "seedLibrarySeedDeposit": "Plant deposit", + "seedLibrarySeedLibrary": "Seed library", + "seedLibrarySeedQuantitySimple": "Seed quantity", + "seedLibrarySeedQuantity": "Seed quantity:", + "seedLibraryShowDeadPlants": "Show dead plants", + "seedLibrarySpecies": "Species:", + "seedLibrarySpeciesHelp": "Help on species", + "seedLibrarySpeciesPlural": "Species", + "seedLibrarySpeciesSimple": "Species", + "seedLibrarySpeciesType": "Species type:", + "seedLibrarySpring": "Spring", + "seedLibraryStartMonth": "Start month:", + "seedLibraryStock": "Available stock", + "seedLibrarySummer": "Summer", + "seedLibraryStocks": "Stocks", + "seedLibraryTimeUntilMaturation": "Time until maturation:", + "seedLibraryType": "Type:", + "seedLibraryUnableToOpen": "Unable to open link", + "seedLibraryUpdate": "Edit", + "seedLibraryUpdatedInformation": "Information updated", + "seedLibraryUpdatedSpecies": "Species updated", + "seedLibraryUpdatedPlant": "Plant updated", + "seedLibraryUpdatingError": "Error updating", + "seedLibraryWinter": "Winter", + "seedLibraryWriteReference": "Please write the following reference: ", + "settingsAccount": "Account", + "settingsAddProfilePicture": "Add a photo", + "settingsAdmin": "Administrator", + "settingsAskHelp": "Ask for help", + "settingsAssociation": "Association", + "settingsBirthday": "Birthday", + "settingsBugs": "Bugs", + "settingsChangePassword": "Change password", + "settingsChangingPassword": "Do you really want to change your password?", + "settingsConfirmPassword": "Confirm password", + "settingsCopied": "Copied!", + "settingsDarkMode": "Dark mode", + "settingsDarkModeOff": "Off", + "settingsDeleteLogs": "Delete logs?", + "settingsDeleteNotificationLogs": "Delete notification logs?", + "settingsDetelePersonalData": "Delete my personal data", + "settingsDetelePersonalDataDesc": "This action notifies the administrator that you want to delete your personal data.", + "settingsDeleting": "Deleting", + "settingsEdit": "Edit", + "settingsEditAccount": "Edit account", + "settingsEditPassword": "Edit password", + "settingsEmail": "Email", + "settingsEmptyField": "This field cannot be empty", + "settingsErrorProfilePicture": "Error editing profile picture", + "settingsErrorSendingDemand": "Error sending request", + "settingsEventsIcal": "Ical link for events", + "settingsExpectingDate": "Expected birth date", + "settingsFirstname": "First name", + "settingsFloor": "Floor", + "settingsHelp": "Help", + "settingsIcalCopied": "Ical link copied!", + "settingsLanguage": "Language", + "settingsLanguageVar": "English 🇬🇧", + "settingsLogs": "Logs", + "settingsModules": "Modules", + "settingsMyIcs": "My Ical link", + "settingsName": "Last name", + "settingsNewPassword": "New password", + "settingsNickname": "Nickname", + "settingsNotifications": "Notifications", + "settingsOldPassword": "Old password", + "settingsPasswordChanged": "Password changed", + "settingsPasswordsNotMatch": "Passwords do not match", + "settingsPersonalData": "Personal data", + "settingsPersonalisation": "Personalization", + "settingsPhone": "Phone", + "settingsProfilePicture": "Profile picture", + "settingsPromo": "Promotion", + "settingsRepportBug": "Report a bug", + "settingsSave": "Save", + "settingsSecurity": "Security", + "settingsSendedDemand": "Request sent", + "settingsSettings": "Settings", + "settingsTooHeavyProfilePicture": "Image is too large (max 4MB)", + "settingsUpdatedProfile": "Profile updated", + "settingsUpdatedProfilePicture": "Profile picture updated", + "settingsUpdateNotification": "Update notifications", + "settingsUpdatingError": "Error updating profile", + "settingsVersion": "Version", + "settingsPasswordStrength": "Password strength", + "settingsPasswordStrengthVeryWeak": "Very weak", + "settingsPasswordStrengthWeak": "Weak", + "settingsPasswordStrengthMedium": "Medium", + "settingsPasswordStrengthStrong": "Strong", + "settingsPasswordStrengthVeryStrong": "Very strong", + "settingsPhoneNumber": "Phone number", + "settingsValidate": "Confirm", + "settingsEditedAccount": "Account edited", + "settingsFailedToEditAccount": "Failed to edit account", + "settingsChooseLanguage": "Choose a language", + "settingsNotificationCounter": "{active}/{total} active {active, plural, zero {notification} one {notification} other {notifications}}", + "@settingsNotificationCounter": { + "description": "Affiche le nombre de notifications actives sur le total des notifications disponibles, avec gestion du pluriel", + "placeholders": { + "active": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "settingsEvent": "Event", + "settingsIcal": "Ical link", + "settingsSynncWithCalendar": "Sync with calendar", + "settingsIcalLinkCopied": "Ical link copied", + "settingsProfile": "Profile", + "voteAdd": "Add", + "voteAddMember": "Add a member", + "voteAddedPretendance": "List added", + "voteAddedSection": "Section added", + "voteAddingError": "Error adding", + "voteAddPretendance": "Add a list", + "voteAddSection": "Add a section", + "voteAll": "All", + "voteAlreadyAddedMember": "Member already added", + "voteAlreadyVoted": "Vote recorded", + "voteChooseList": "Choose a list", + "voteClear": "Reset", + "voteClearVotes": "Reset votes", + "voteClosedVote": "Votes closed", + "voteCloseVote": "Close votes", + "voteConfirmVote": "Confirm vote", + "voteCountVote": "Count votes", + "voteDeletedAll": "All deleted", + "voteDeletedPipo": "Fake lists deleted", + "voteDeletedSection": "Section deleted", + "voteDeleteAll": "Delete all", + "voteDeleteAllDescription": "Do you really want to delete everything?", + "voteDeletePipo": "Delete fake lists", + "voteDeletePipoDescription": "Do you really want to delete the fake lists?", + "voteDeletePretendance": "Delete the list", + "voteDeletePretendanceDesc": "Do you really want to delete this list?", + "voteDeleteSection": "Delete the section", + "voteDeleteSectionDescription": "Do you really want to delete this section?", + "voteDeletingError": "Error deleting", + "voteDescription": "Description", + "voteEdit": "Edit", + "voteEditedPretendance": "List edited", + "voteEditedSection": "Section edited", + "voteEditingError": "Error editing", + "voteErrorClosingVotes": "Error closing votes", + "voteErrorCountingVotes": "Error counting votes", + "voteErrorResetingVotes": "Error resetting votes", + "voteErrorOpeningVotes": "Error opening votes", + "voteIncorrectOrMissingFields": "Incorrect or missing fields", + "voteMembers": "Members", + "voteName": "Name", + "voteNoPretendanceList": "No list of candidates", + "voteNoSection": "No section", + "voteCanNotVote": "You cannot vote", + "voteNoSectionList": "No section", + "voteNotOpenedVote": "Vote not opened", + "voteOnGoingCount": "Counting in progress", + "voteOpenVote": "Open votes", + "votePipo": "Fake", + "votePretendance": "Lists", + "votePretendanceDeleted": "Candidate list deleted", + "votePretendanceNotDeleted": "Error deleting", + "voteProgram": "Program", + "votePublish": "Publish", + "votePublishVoteDescription": "Do you really want to publish the votes?", + "voteResetedVotes": "Votes reset", + "voteResetVote": "Reset votes", + "voteResetVoteDescription": "What do you want to do?", + "voteRole": "Role", + "voteSectionDescription": "Section description", + "voteSection": "Section", + "voteSectionName": "Section name", + "voteSeeMore": "See more", + "voteSelected": "Selected", + "voteShowVotes": "Show votes", + "voteVote": "Vote", + "voteVoteError": "Error recording vote", + "voteVoteFor": "Vote for ", + "voteVoteNotStarted": "Vote not opened", + "voteVoters": "Voting groups", + "voteVoteSuccess": "Vote recorded", + "voteVotes": "Votes", + "voteVotesClosed": "Votes closed", + "voteVotesCounted": "Votes counted", + "voteVotesOpened": "Votes opened", + "voteWarning": "Warning", + "voteWarningMessage": "Selection will not be saved.\nDo you want to continue?", + "moduleAdvert": "Advert", + "moduleAmap": "AMAP", + "moduleBooking": "Booking", + "moduleCalendar": "Calendar", + "moduleCentralisation": "Centralisation", + "moduleCinema": "Cinema", + "moduleEvent": "Event", + "moduleFlappyBird": "Flappy Bird", + "moduleLoan": "Loan", + "modulePhonebook": "Phonebook", + "modulePurchases": "Purchases", + "moduleRaffle": "Raffle", + "moduleRecommendation": "Recommendation", + "moduleSeedLibrary": "Seed Library", + "moduleVote": "Vote", + "modulePh": "PH", + "moduleSettings": "Settings", + "moduleFeed": "Feed", + "moduleStyleGuide": "StyleGuide", + "moduleAdmin": "Administration", + "moduleOthers": "Others", + "modulePayment": "Payment", + "moduleAdvertDescription": "View the latest adverts", "moduleAmapDescription": "Order your AMAP basket", "moduleBookingDescription": "Book a room", "moduleCalendarDescription": "View the calendar of events", @@ -1215,127 +1227,127 @@ "moduleOthersDescription": "Other modules", "modulePaymentDescription": "Pay and see your transactions", "paiementTopUp": "Top-up", - "paiementStoreManagement": "Association management", - "paiementDeleteStore": "Delete association", - "paiementDeleteStoreDescription": "Are you sure you want to delete this association?", - "paiementDeleteStoreError": "Unable to delete the association", - "paiementStoreDeleted": "Association deleted", - "paiementAddThisDevice": "Add this device", - "paiementThisDevice": "(this device)", - "paiementCancelled": "Cancelled", - "paiementThe": "The", - "paiementOf": "of", - "paiementRefundedThe": "Refunded on", - "paiementAt": "at", - "paiementPleaseAcceptTOS": "Please accept the Terms of Service.", - "paiementAskDeviceActivation": "Device activation request", - "paiementDeviceActivationReceived": "The activation request has been received, please check your email to finalize the process", - "paiementRevokeDevice": "Revoke device?", - "paiementRevokeDeviceDescription": "You will no longer be able to use this device for payments", - "paiementDeviceRevoked": "Device revoked", - "paiementDeviceRevokingError": "Error while revoking device", - "paiementPleaseAcceptPopup": "Please allow popups", - "paiementProceedSuccessfully": "Payment completed successfully", - "paiementCancelledTransaction": "Payment cancelled", - "paiementPleaseEnterMinAmount": "Please enter an amount greater than 1", - "paiementMaxAmount": "The maximum wallet amount is", - "paiementPayWithHA": "Pay with HelloAsso", - "paiementBalanceAfterTopUp": "Balance after top-up:", - "paiementPersonalBalance": "Personal balance", - "paiementDevices": "Devices", - "paiementPay": "Pay", - "paiementDeviceNotRegistered": "Device not registered", - "paiementDeviceNotRegisteredDescription": "Your device is not registered yet. \nTo register it, please go to the devices page.", - "paiementAccessPage": "Access the page", - "paiementDeviceNotActivated": "Device not activated", - "paiementDeviceNotActivatedDescription": "Your device is not yet activated. \nTo activate it, please go to the devices page.", - "paiementReactivateRevokedDeviceDescription": "Your device has been revoked. \nTo reactivate it, please go to the devices page.", - "paiementDeviceRecoveryError": "Error while retrieving device", - "paiementStats": "Stats", - "paimentTopUpAction": "Top-up", - "paiementGetBalanceError": "Error while retrieving balance: ", - "paiementLastTransactions": "Latest transactions", - "paiementGetTransactionsError": "Error while retrieving transactions: ", - "paiementStoreBalance": "Association balance", - "paiementScan": "Scan", - "paiementManagement": "Management", - "paiementHistory": "History", - "paiementHandOver": "Handover", - "paiementStores": "Associations", - "paiementAdmin": "Administrator", - "paiementSuccededTransaction": "Successful payment", - "paiementNewCGU": "New Terms of Service", - "paiementDecline": "Decline", - "paiementAccept": "Accept", - "paiementAmount": "Amount", - "paiementValidUntil": "Valid until", - "paiementClose": "Close", - "paiementPleaseEnterValidAmount": "Please enter a valid amount", - "paiementPleaseAuthenticate": "Please authenticate", - "paiementAthenticationRequired": "Authentication required to pay", - "paiementNoThanks": "No thanks", - "paiementAuthentificationFailed": "Authentication failed", - "paiementPleaseAddDevice": "Please add this device to pay", - "paiementPayment": "Payment", - "paiementBalanceAfterTransaction": "Balance after payment: ", - "paiementCancel": "Cancel", - "paiementLimitedTo": "Limited to", - "paiementScanCode": "Scan a code", - "paiementNext": "Next", - "paiementCancelTransaction": "Cancel transaction", - "paiementTransactionCancelled": "Transaction cancelled", - "paiementTransactionCancelledDescription": "Are you sure you want to cancel the transaction of", - "paiementTransactionCancelledError": "Error while cancelling the transaction", - "paiementNoMembership": "No membership", - "paiementNoMembershipDescription": "This product is not available to non-members. Confirm the payment?", - "paiementQRCodeAlreadyUsed": "QR Code already used", - "paiementCameraPermissionRequired": "Camera permission required", - "paiementCameraPerssionRequiredDescription": "To scan a QR Code, you must allow camera access.", - "paiementSettings": "Settings", - "paiementReceived": "Received", - "paiementSpent": "Spent", - "paiementNoTrasactionForThisMonth": "No transactions for this month", - "paiementNoTransactinon": "No transaction", - "paiementSellerRigths": "Seller rights", - "paiementCanBank": "Can collect payments", - "paiementCanSeeHistory": "Can view history", - "paiementCanCancelTransaction": "Can cancel transactions", - "paiementCanManageSellers": "Can manage sellers", - "paiementAddedSeller": "Seller added", - "paiementAddingSellerError": "Error while adding seller", - "paiementBank": "Collect", - "paiementSeeHistory": "View history", - "paiementCancelTransactions": "Cancel transactions", - "paiementManageSellers": "Manage sellers", - "paiementStructureAdmin": "Structure administrator", - "paiementRightsOf": "Rights of", - "paiementRightsUpdated": "Rights updated", - "paiementRightsUpdateError": "Error while updating rights", - "paiementDeleteSellerDescription": "Are you sure you want to delete this seller?", - "paiementDeletedSeller": "Seller deleted", - "paiementDeletingSellerError": "Error while deleting seller", - "paiementDeleteSeller": "Delete seller", - "paiementAdd": "Add", - "paiementAddSeller": "Add seller", - "paiementSellerError": "You are not a seller of this association", - "paiementSellersOf": "Sellers of", - "paiementModify": "Edit", - "paiementAStore": "an association", - "paiementStoreName": "Association name", - "paiementSuccessfullyAddedStore": "Association successfully added", - "paiementSuccessfullyModifiedStore": "Association successfully updated", - "paiementAddingStoreError": "Error while adding the association", - "paiementModifyingStoreError": "Error while updating the association", - "paiementRefund": "Refund", - "paiementDoneTransaction": "Transaction completed", - "paiementRefundAction": "Refund", - "paiementTotalDuringPeriod": "Total during the period", - "paiementMean": "Average: ", - "paiementTransaction": "Transaction", - "paiementTransferStructure": "Structure transfer", - "paiementYouAreTransferingStructureTo": "You are about to transfer the structure to ", - "paiementTransferStructureDescription": "The new manager will have access to all structure management features. You will receive an email to confirm this transfer. The link will only be active for 20 minutes. This action is irreversible. Are you sure you want to continue?", - "paiementTransferStructureError": "Error while transferring structure", - "paiementTransferStructureSuccess": "Structure transfer requested successfully", - "paiementNextAccountable": "Next responsible" - } \ No newline at end of file + "paiementStoreManagement": "Association management", + "paiementDeleteStore": "Delete association", + "paiementDeleteStoreDescription": "Are you sure you want to delete this association?", + "paiementDeleteStoreError": "Unable to delete the association", + "paiementStoreDeleted": "Association deleted", + "paiementAddThisDevice": "Add this device", + "paiementThisDevice": "(this device)", + "paiementCancelled": "Cancelled", + "paiementThe": "The", + "paiementOf": "of", + "paiementRefundedThe": "Refunded on", + "paiementAt": "at", + "paiementPleaseAcceptTOS": "Please accept the Terms of Service.", + "paiementAskDeviceActivation": "Device activation request", + "paiementDeviceActivationReceived": "The activation request has been received, please check your email to finalize the process", + "paiementRevokeDevice": "Revoke device?", + "paiementRevokeDeviceDescription": "You will no longer be able to use this device for payments", + "paiementDeviceRevoked": "Device revoked", + "paiementDeviceRevokingError": "Error while revoking device", + "paiementPleaseAcceptPopup": "Please allow popups", + "paiementProceedSuccessfully": "Payment completed successfully", + "paiementCancelledTransaction": "Payment cancelled", + "paiementPleaseEnterMinAmount": "Please enter an amount greater than 1", + "paiementMaxAmount": "The maximum wallet amount is", + "paiementPayWithHA": "Pay with HelloAsso", + "paiementBalanceAfterTopUp": "Balance after top-up:", + "paiementPersonalBalance": "Personal balance", + "paiementDevices": "Devices", + "paiementPay": "Pay", + "paiementDeviceNotRegistered": "Device not registered", + "paiementDeviceNotRegisteredDescription": "Your device is not registered yet. \nTo register it, please go to the devices page.", + "paiementAccessPage": "Access the page", + "paiementDeviceNotActivated": "Device not activated", + "paiementDeviceNotActivatedDescription": "Your device is not yet activated. \nTo activate it, please go to the devices page.", + "paiementReactivateRevokedDeviceDescription": "Your device has been revoked. \nTo reactivate it, please go to the devices page.", + "paiementDeviceRecoveryError": "Error while retrieving device", + "paiementStats": "Stats", + "paimentTopUpAction": "Top-up", + "paiementGetBalanceError": "Error while retrieving balance: ", + "paiementLastTransactions": "Latest transactions", + "paiementGetTransactionsError": "Error while retrieving transactions: ", + "paiementStoreBalance": "Association balance", + "paiementScan": "Scan", + "paiementManagement": "Management", + "paiementHistory": "History", + "paiementHandOver": "Handover", + "paiementStores": "Associations", + "paiementAdmin": "Administrator", + "paiementSuccededTransaction": "Successful payment", + "paiementNewCGU": "New Terms of Service", + "paiementDecline": "Decline", + "paiementAccept": "Accept", + "paiementAmount": "Amount", + "paiementValidUntil": "Valid until", + "paiementClose": "Close", + "paiementPleaseEnterValidAmount": "Please enter a valid amount", + "paiementPleaseAuthenticate": "Please authenticate", + "paiementAthenticationRequired": "Authentication required to pay", + "paiementNoThanks": "No thanks", + "paiementAuthentificationFailed": "Authentication failed", + "paiementPleaseAddDevice": "Please add this device to pay", + "paiementPayment": "Payment", + "paiementBalanceAfterTransaction": "Balance after payment: ", + "paiementCancel": "Cancel", + "paiementLimitedTo": "Limited to", + "paiementScanCode": "Scan a code", + "paiementNext": "Next", + "paiementCancelTransaction": "Cancel transaction", + "paiementTransactionCancelled": "Transaction cancelled", + "paiementTransactionCancelledDescription": "Are you sure you want to cancel the transaction of", + "paiementTransactionCancelledError": "Error while cancelling the transaction", + "paiementNoMembership": "No membership", + "paiementNoMembershipDescription": "This product is not available to non-members. Confirm the payment?", + "paiementQRCodeAlreadyUsed": "QR Code already used", + "paiementCameraPermissionRequired": "Camera permission required", + "paiementCameraPerssionRequiredDescription": "To scan a QR Code, you must allow camera access.", + "paiementSettings": "Settings", + "paiementReceived": "Received", + "paiementSpent": "Spent", + "paiementNoTrasactionForThisMonth": "No transactions for this month", + "paiementNoTransactinon": "No transaction", + "paiementSellerRigths": "Seller rights", + "paiementCanBank": "Can collect payments", + "paiementCanSeeHistory": "Can view history", + "paiementCanCancelTransaction": "Can cancel transactions", + "paiementCanManageSellers": "Can manage sellers", + "paiementAddedSeller": "Seller added", + "paiementAddingSellerError": "Error while adding seller", + "paiementBank": "Collect", + "paiementSeeHistory": "View history", + "paiementCancelTransactions": "Cancel transactions", + "paiementManageSellers": "Manage sellers", + "paiementStructureAdmin": "Structure administrator", + "paiementRightsOf": "Rights of", + "paiementRightsUpdated": "Rights updated", + "paiementRightsUpdateError": "Error while updating rights", + "paiementDeleteSellerDescription": "Are you sure you want to delete this seller?", + "paiementDeletedSeller": "Seller deleted", + "paiementDeletingSellerError": "Error while deleting seller", + "paiementDeleteSeller": "Delete seller", + "paiementAdd": "Add", + "paiementAddSeller": "Add seller", + "paiementSellerError": "You are not a seller of this association", + "paiementSellersOf": "Sellers of", + "paiementModify": "Edit", + "paiementAStore": "an association", + "paiementStoreName": "Association name", + "paiementSuccessfullyAddedStore": "Association successfully added", + "paiementSuccessfullyModifiedStore": "Association successfully updated", + "paiementAddingStoreError": "Error while adding the association", + "paiementModifyingStoreError": "Error while updating the association", + "paiementRefund": "Refund", + "paiementDoneTransaction": "Transaction completed", + "paiementRefundAction": "Refund", + "paiementTotalDuringPeriod": "Total during the period", + "paiementMean": "Average: ", + "paiementTransaction": "Transaction", + "paiementTransferStructure": "Structure transfer", + "paiementYouAreTransferingStructureTo": "You are about to transfer the structure to ", + "paiementTransferStructureDescription": "The new manager will have access to all structure management features. You will receive an email to confirm this transfer. The link will only be active for 20 minutes. This action is irreversible. Are you sure you want to continue?", + "paiementTransferStructureError": "Error while transferring structure", + "paiementTransferStructureSuccess": "Structure transfer requested successfully", + "paiementNextAccountable": "Next responsible" + } diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 95f40139a7..1fbfa85161 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -1,1348 +1,1360 @@ { - "@@locale": "fr", - "dateToday": "Aujourd'hui", - "dateYesterday": "Hier", - "dateTomorrow": "Demain", - "dateAt": "à", - "dateFrom": "de", - "dateTo": "à", - "dateBetweenDays": "au", - "dateStarting": "Commence", - "dateLast": "", - "dateUntil": "Jusqu'au", - "feedFilterAll": "Tous", - "feedFilterPending": "En attente", - "feedFilterApproved": "Approuvés", - "feedFilterRejected": "Rejetés", - "feedEmptyAll": "Aucun événement disponible", - "feedEmptyPending": "Aucun événement en attente de validation", - "feedEmptyApproved": "Aucun événement approuvé", - "feedEmptyRejected": "Aucun événement rejeté", - "feedEventManagement": "Gestion des événements", - "adminAccountTypes": "Types de compte", - "adminAdd": "Ajouter", - "adminAddGroup": "Ajouter un groupe", - "adminAddMember": "Ajouter un membre", - "adminAddedGroup": "Groupe créé", - "adminAddedLoaner": "Préteur ajouté", - "adminAddedMember": "Membre ajouté", - "adminAddingError": "Erreur lors de l'ajout", - "adminAddingMember": "Ajout d'un membre", - "adminAddLoaningGroup": "Ajouter un groupe de prêt", - "adminAddSchool": "Ajouter une école", - "adminAddStructure": "Ajouter une structure", - "adminAddedSchool": "École créée", - "adminAddedStructure": "Structure ajoutée", - "adminEditedStructure": "Structure modifiée", - "adminAdministration": "Administration", - "adminAssociationMembership": "Adhésion", - "adminAssociationMembershipName": "Nom de l'adhésion", - "adminAssociationsMemberships": "Adhésions", - "adminClearFilters": "Effacer les filtres", - "adminCreateAssociationMembership": "Créer une adhésion", - "adminCreatedAssociationMembership": "Adhésion créée", - "adminCreationError": "Erreur lors de la création", - "adminDateError": "La date de début doit être avant la date de fin", - "adminDelete": "Supprimer", - "adminDeleteAssociationMembership": "Supprimer l'adhésion ?", - "adminDeletedAssociationMembership": "Adhésion supprimée", - "adminDeleteGroup": "Supprimer le groupe", - "adminDeletedGroup": "Groupe supprimé", - "adminDeleteSchool": "Supprimer l'école ?", - "adminDeletedSchool": "École supprimée", - "adminDeleting": "Suppression", - "adminDeletingError": "Erreur lors de la suppression", - "adminDescription": "Description", - "adminEclSchool": "Centrale Lyon", - "adminEdit": "Modifier", - "adminEditStructure": "Modifier la structure", - "adminEditMembership": "Modifier l'adhésion", - "adminEmptyDate": "Date vide", - "adminEmptyFieldError": "Le nom ne peut pas être vide", - "adminEmailRegex": "Email Regex", - "adminEmptyUser": "Utilisateur vide", - "adminEndDate": "Date de fin", - "adminEndDateMaximal": "Date de fin maximale", - "adminEndDateMinimal": "Date de fin minimale", - "adminError": "Erreur", - "adminFilters": "Filtres", - "adminGroup": "Groupe", - "adminGroups": "Groupes", - "adminLoaningGroup": "Groupe de prêt", - "adminLooking": "Recherche", - "adminManager": "Administrateur de la structure", - "adminMaximum": "Maximum", - "adminMembers": "Membres", - "adminMembershipAddingError": "Erreur lors de l'ajout (surement dû à une superposition de dates)", - "adminMemberships": "Adhésions", - "adminMembershipUpdatingError": "Erreur lors de la modification (surement dû à une superposition de dates)", - "adminMinimum": "Minimum", - "adminModifyModuleVisibility": "Visibilité des modules", - "adminMyEclPay": "MyECLPay", - "adminName": "Nom", - "adminNoManager": "Aucun manager n'est sélectionné", - "adminNoMember": "Aucun membre", - "adminNoMoreLoaner": "Aucun prêteur n'est disponible", - "adminNoSchool": "Sans école", - "adminRemoveGroupMember": "Supprimer le membre du groupe ?", - "adminResearch": "Recherche", - "adminSchools": "Écoles", - "adminStructures": "Structures", - "adminStartDate": "Date de début", - "adminStartDateMaximal": "Date de début maximale", - "adminStartDateMinimal": "Date de début minimale", - "adminUpdatedAssociationMembership": "Adhésion modifiée", - "adminUpdatedGroup": "Groupe modifié", - "adminUpdatedMembership": "Adhésion modifiée", - "adminUpdatingError": "Erreur lors de la modification", - "adminUser": "Utilisateur", - "adminValidateFilters": "Valider les filtres", - "adminVisibilities": "Visibilités", - "adminGroupNotification": "Notification de groupe", - "adminNotifyGroup": "Notifier le groupe {groupName}", - "@adminNotifyGroup":{ - "description": "Notifie les membres du groupe sélectionné", - "placeholders": { - "groupName": { - "type": "String" - } - } - }, - "adminTitle": "Titre", - "adminContent": "Contenu", - "adminSend": "Envoyer", - "adminNotificationSent": "Notification envoyée", - "adminFailedToSendNotification": "Échec de l'envoi de la notification", - "adminGroupsManagement": "Gestion des groupes", - "adminEditGroup": "Modifier le groupe", - "adminManageMembers": "Gérer les membres", - "adminDeleteGroupConfirmation": "Êtes-vous sûr de vouloir supprimer ce groupe ?", - "adminFailedToDeleteGroup": "Échec de la suppression du groupe", - "adminUsersAndGroups": "Utilisateurs et groupes", - "adminUsersManagement": "Gestion des utilisateurs", - "adminUsersManagementDescription": "Gérer les utilisateurs de l'application", - "adminManageUserGroups": "Gérer les groupes d'utilisateurs", - "adminSendNotificationToGroup": "Envoyer une notification à un groupe", - "adminPaiementModule": "Module de paiement", - "adminPaiement": "Paiement", - "adminManagePaiementStructures" : "Gérer les structures du module de paiement", - "adminManageUsersAssociationMemberships": "Gérer les adhésions des utilisateurs", - "adminAssociationMembershipsManagement": "Gestion des adhésions", - "adminChooseGroupManager" : "Groupe gestionnaire de l'adhésion", - "adminSelectManager" : "Sélectionner un gestionnaire", - "adminInviteUsers" : "Inviter des utilisateurs", - "adminImportList": "Importer une liste", - "adminInvitedUsers": "Utilisateurs invités", - "adminFailedToInviteUsers": "Échec de l'invitation des utilisateurs", - "adminDeleteUsers": "Supprimer des utilisateurs", - "adminAdmin": "Admin", - "adminAdverts": "Annonces", - "adminAnnouncers": "Annonceurs", - "adminManageAnnouncers" : "Gérer les annonceurs", - "advertAdd": "Ajouter", - "advertAddedAdvert": "Annonce publiée", - "advertAddedAnnouncer": "Annonceur ajouté", - "advertAddingError": "Erreur lors de l'ajout", - "advertAdmin": "Admin", - "advertAdvert": "Annonce", - "advertChoosingAnnouncer": "Veuillez choisir un annonceur", - "advertChoosingPoster": "Veuillez choisir une image", - "advertContent": "Contenu", - "advertDeleteAdvert": "Supprimer l'annonce", - "advertDeleteAnnouncer": "Supprimer l'annonceur ?", - "advertDeleting": "Suppression", - "advertEdit": "Modifier", - "advertEditedAdvert": "Annonce modifiée", - "advertEditingError": "Erreur lors de la modification", - "advertGroupAdvert": "Groupe", - "advertIncorrectOrMissingFields": "Champs incorrects ou manquants", - "advertInvalidNumber": "Veuillez entrer un nombre", - "advertManagement": "Gestion", - "advertModifyAnnouncingGroup": "Modifier un groupe d'annonce", - "advertNoMoreAnnouncer": "Aucun annonceur n'est disponible", - "advertNoValue": "Veuillez entrer une valeur", - "advertPositiveNumber": "Veuillez entrer un nombre positif", - "advertRemovedAnnouncer": "Annonceur supprimé", - "advertRemovingError": "Erreur lors de la suppression", - "advertTags": "Tags", - "advertTitle": "Titre", - "advertMonthJan": "Janv", - "advertMonthFeb": "Févr.", - "advertMonthMar": "Mars", - "advertMonthApr": "Avr.", - "advertMonthMay": "Mai", - "advertMonthJun": "Juin", - "advertMonthJul": "Juill.", - "advertMonthAug": "Août", - "advertMonthSep": "Sept.", - "advertMonthOct": "Oct.", - "advertMonthNov": "Nov.", - "advertMonthDec": "Déc.", - "amapAccounts": "Comptes", - "amapAdd": "Ajouter", - "amapAddDelivery": "Ajouter une livraison", - "amapAddedCommand": "Commande ajoutée", - "amapAddedOrder": "Commande ajoutée", - "amapAddedProduct": "Produit ajouté", - "amapAddedUser": "Utilisateur ajouté", - "amapAddProduct": "Ajouter un produit", - "amapAddUser": "Ajouter un utilisateur", - "amapAddingACommand": "Ajouter une commande", - "amapAddingCommand": "Ajouter la commande", - "amapAddingError": "Erreur lors de l'ajout", - "amapAddingProduct": "Ajouter un produit", - "amapAddOrder": "Ajouter une commande", - "amapAdmin": "Admin", - "amapAlreadyExistCommand": "Il existe déjà une commande à cette date", - "amapAmap": "Amap", - "amapAmount": "Solde", - "amapArchive": "Archiver", - "amapArchiveDelivery": "Archiver", - "amapArchivingDelivery": "Archivage de la livraison", - "amapCategory": "Catégorie", - "amapCloseDelivery": "Verrouiller", - "amapCommandDate": "Date de la commande", - "amapCommandProducts": "Produits de la commande", - "amapConfirm": "Confirmer", - "amapContact": "Contacts associatifs ", - "amapCreateCategory": "Créer une catégorie", - "amapDelete": "Supprimer", - "amapDeleteDelivery": "Supprimer la livraison ?", - "amapDeleteDeliveryDescription": "Voulez-vous vraiment supprimer cette livraison ?", - "amapDeletedDelivery": "Livraison supprimée", - "amapDeletedOrder": "Commande supprimée", - "amapDeletedProduct": "Produit supprimé", - "amapDeleteProduct": "Supprimer le produit ?", - "amapDeleteProductDescription": "Voulez-vous vraiment supprimer ce produit ?", - "amapDeleting": "Suppression", - "amapDeletingDelivery": "Supprimer la livraison ?", - "amapDeletingError": "Erreur lors de la suppression", - "amapDeletingOrder": "Supprimer la commande ?", - "amapDeletingProduct": "Supprimer le produit ?", - "amapDeliver": "Livraison teminée ?", - "amapDeliveries": "Livraisons", - "amapDeliveringDelivery": "Toutes les commandes sont livrées ?", - "amapDelivery": "Livraison", - "amapDeliveryArchived": "Livraison archivée", - "amapDeliveryDate": "Date de livraison", - "amapDeliveryDelivered": "Livraison effectuée", - "amapDeliveryHistory": "Historique des livraisons", - "amapDeliveryList": "Liste des livraisons", - "amapDeliveryLocked": "Livraison verrouillée", - "amapDeliveryOn": "Livraison le", - "amapDeliveryOpened": "Livraison ouverte", - "amapDeliveryNotArchived": "Livraison non archivée", - "amapDeliveryNotLocked": "Livraison non verrouillée", - "amapDeliveryNotDelivered": "Livraison non effectuée", - "amapDeliveryNotOpened": "Livraison non ouverte", - "amapEditDelivery": "Modifier la livraison", - "amapEditedCommand": "Commande modifiée", - "amapEditingError": "Erreur lors de la modification", - "amapEditProduct": "Modifier le produit", - "amapEndingDelivery": "Fin de la livraison", - "amapError": "Erreur", - "amapErrorLink": "Erreur lors de l'ouverture du lien", - "amapErrorLoadingUser": "Erreur lors du chargement des utilisateurs", - "amapEvening": "Soir", - "amapExpectingNumber": "Veuillez entrer un nombre", - "amapFillField": "Veuillez remplir ce champ", - "amapHandlingAccount": "Gérer les comptes", - "amapLoading": "Chargement...", - "amapLoadingError": "Erreur lors du chargement", - "amapLock": "Verrouiller", - "amapLocked": "Verrouillée", - "amapLockedDelivery": "Livraison verrouillée", - "amapLockedOrder": "Commande verrouillée", - "amapLooking": "Rechercher", - "amapLockingDelivery": "Verrouiller la livraison ?", - "amapMidDay": "Midi", - "amapMyOrders": "Mes commandes", - "amapName": "Nom", - "amapNextStep": "Étape suivante", - "amapNoProduct": "Pas de produit", - "amapNoCurrentOrder": "Pas de commande en cours", - "amapNoMoney": "Pas assez d'argent", - "amapNoOpennedDelivery": "Pas de livraison ouverte", - "amapNoOrder": "Pas de commande", - "amapNoSelectedDelivery": "Pas de livraison sélectionnée", - "amapNotEnoughMoney": "Pas assez d'argent", - "amapNotPlannedDelivery": "Pas de livraison planifiée", - "amapOneOrder": "commande", - "amapOpenDelivery": "Ouvrir", - "amapOpened": "Ouverte", - "amapOpenningDelivery": "Ouvrir la livraison ?", - "amapOrder": "Commander", - "amapOrders": "Commandes", - "amapPickChooseCategory": "Veuillez entrer une valeur ou choisir une catégorie existante", - "amapPickDeliveryMoment": "Choisissez un moment de livraison", - "amapPresentation": "Présentation", - "amapPresentation1": "L'AMAP (association pour le maintien d'une agriculture paysanne) est un service proposé par l'association Planet&Co de l'ECL. Vous pouvez ainsi recevoir des produits (paniers de fruits et légumes, jus, confitures...) directement sur le campus !\n\nLes commandes doivent être passées avant le vendredi 21h et sont livrées sur le campus le mardi de 13h à 13h45 (ou de 18h15 à 18h30 si vous ne pouvez pas passer le midi) dans le hall du M16.\n\nVous ne pouvez commander que si votre solde le permet. Vous pouvez recharger votre solde via la collecte Lydia ou bien avec un chèque que vous pouvez nous transmettre lors des permanences.\n\nLien vers la collecte Lydia pour le rechargement : ", - "amapPresentation2": "\n\nN'hésitez pas à nous contacter en cas de problème !", - "amapPrice": "Prix", - "amapProduct": "produit", - "amapProducts": "Produits", - "amapProductInDelivery": "Produit dans une livraison non terminée", - "amapQuantity": "Quantité", - "amapRequiredDate": "La date est requise", - "amapSeeMore": "Voir plus", - "amapThe": "Le", - "amapUnlock": "Dévérouiller", - "amapUnlockedDelivery": "Livraison dévérouillée", - "amapUnlockingDelivery": "Dévérouiller la livraison ?", - "amapUpdate": "Modifier", - "amapUpdatedAmount": "Solde modifié", - "amapUpdatedOrder": "Commande modifiée", - "amapUpdatedProduct": "Produit modifié", - "amapUpdatingError": "Echec de la modification", - "amapUsersNotFound": "Aucun utilisateur trouvé", - "amapWaiting": "En attente", - "bookingAdd": "Ajouter", - "bookingAddBookingPage": "Demande", - "bookingAddRoom": "Ajouter une salle", - "bookingAddBooking": "Ajouter une réservation", - "bookingAddedBooking": "Demande ajoutée", - "bookingAddedRoom": "Salle ajoutée", - "bookingAddedManager": "Gestionnaire ajouté", - "bookingAddingError": "Erreur lors de l'ajout", - "bookingAddManager": "Ajouter un gestionnaire", - "bookingAdminPage": "Administrateur", - "bookingAllDay": "Toute la journée", - "bookingBookedFor": "Réservé pour", - "bookingBooking": "Réservation", - "bookingBookingCreated": "Réservation créée", - "bookingBookingDemand": "Demande de réservation", - "bookingBookingNote": "Note de la réservation", - "bookingBookingPage": "Réservation", - "bookingBookingReason": "Motif de la réservation", - "bookingBy": "par", - "bookingConfirm": "Confirmer", - "bookingConfirmation": "Confirmation", - "bookingConfirmBooking": "Confirmer la réservation ?", - "bookingConfirmed": "Validée", - "bookingDates": "Dates", - "bookingDecline": "Refuser", - "bookingDeclineBooking": "Refuser la réservation ?", - "bookingDeclined": "Refusée", - "bookingDelete": "Supprimer", - "bookingDeleting": "Suppression", - "bookingDeleteBooking": "Suppression", - "bookingDeleteBookingConfirmation": "Êtes-vous sûr de vouloir supprimer cette réservation ?", - "bookingDeletedBooking": "Réservation supprimée", - "bookingDeletedRoom": "Salle supprimée", - "bookingDeletedManager": "Gestionnaire supprimé", - "bookingDeleteRoomConfirmation": "Êtes-vous sûr de vouloir supprimer cette salle ?\n\nLa salle ne doit avoir aucune réservation en cours ou à venir pour être supprimée", - "bookingDeleteManagerConfirmation": "Êtes-vous sûr de vouloir supprimer ce gestionnaire ?\n\nLe gestionnaire ne doit être associé à aucune salle pour pouvoir être supprimé", - "bookingDeletingBooking": "Supprimer la réservation ?", - "bookingDeletingError": "Erreur lors de la suppression", - "bookingDeletingRoom": "Supprimer la salle ?", - "bookingEdit": "Modifier", - "bookingEditBooking": "Modifier une réservation", - "bookingEditionError": "Erreur lors de la modification", - "bookingEditedBooking": "Réservation modifiée", - "bookingEditedRoom": "Salle modifiée", - "bookingEditedManager": "Gestionnaire modifié", - "bookingEditManager": "Modifier ou supprimer un gestionnaire", - "bookingEditRoom": "Modifier ou supprimer une salle", - "bookingEndDate": "Date de fin", - "bookingEndHour": "Heure de fin", - "bookingEntity": "Pour qui ?", - "bookingError": "Erreur", - "bookingEventEvery": "Tous les", - "bookingHistoryPage": "Historique", - "bookingIncorrectOrMissingFields": "Champs incorrects ou manquants", - "bookingInterval": "Intervalle", - "bookingInvalidIntervalError": "Intervalle invalide", - "bookingInvalidDates": "Dates invalides", - "bookingInvalidRoom": "Salle invalide", - "bookingKeysRequested": "Clés demandées", - "bookingManagement": "Gestion", - "bookingManager": "Gestionnaire", - "bookingManagerName": "Nom du gestionnaire", - "bookingMultipleDay": "Plusieurs jours", - "bookingMyBookings": "Mes réservations", - "bookingNecessaryKey": "Clé nécessaire", - "bookingNext": "Suivant", - "bookingNo": "Non", - "bookingNoCurrentBooking": "Pas de réservation en cours", - "bookingNoDateError": "Veuillez choisir une date", - "bookingNoAppointmentInReccurence": "Aucun créneau existe avec ces paramètres de récurrence", - "bookingNoDaySelected": "Aucun jour sélectionné", - "bookingNoDescriptionError": "Veuillez entrer une description", - "bookingNoKeys": "Aucune clé", - "bookingNoNoteError": "Veuillez entrer une note", - "bookingNoPhoneRegistered": "Numéro non renseigné", - "bookingNoReasonError": "Veuillez entrer un motif", - "bookingNoRoomFoundError": "Aucune salle enregistrée", - "bookingNoRoomFound": "Aucune salle trouvée", - "bookingNote": "Note", - "bookingOther": "Autre", - "bookingPending": "En attente", - "bookingPrevious": "Précédent", - "bookingReason": "Motif", - "bookingRecurrence": "Récurrence", - "bookingRecurrenceDays": "Jours de récurrence", - "bookingRecurrenceEndDate": "Date de fin de récurrence", - "bookingRecurrent": "Récurrent", - "bookingRegisteredRooms": "Salles enregistrées", - "bookingRoom": "Salle", - "bookingRoomName": "Nom de la salle", - "bookingStartDate": "Date de début", - "bookingStartHour": "Heure de début", - "bookingWeeks": "Semaines", - "bookingYes": "Oui", - "bookingWeekDayMon": "Lundi", - "bookingWeekDayTue": "Mardi", - "bookingWeekDayWed": "Mercredi", - "bookingWeekDayThu": "Jeudi", - "bookingWeekDayFri": "Vendredi", - "bookingWeekDaySat": "Samedi", - "bookingWeekDaySun": "Dimanche", - "cinemaAdd": "Ajouter", - "cinemaAddedSession": "Séance ajoutée", - "cinemaAddingError": "Erreur lors de l'ajout", - "cinemaAddSession": "Ajouter une séance", - "cinemaCinema": "Cinéma", - "cinemaDeleteSession": "Supprimer la séance ?", - "cinemaDeleting": "Suppression", - "cinemaDuration": "Durée", - "cinemaEdit": "Modifier", - "cinemaEditedSession": "Séance modifiée", - "cinemaEditingError": "Erreur lors de la modification", - "cinemaEditSession": "Modifier la séance", - "cinemaEmptyUrl": "Veuillez entrer une URL", - "cinemaImportFromTMDB": "Importer depuis TMDB", - "cinemaIncomingSession": "A l'affiche", - "cinemaIncorrectOrMissingFields": "Champs incorrects ou manquants", - "cinemaInvalidUrl": "URL invalide", - "cinemaGenre": "Genre", - "cinemaName": "Nom", - "cinemaNoDateError": "Veuillez entrer une date", - "cinemaNoDuration": "Veuillez entrer une durée", - "cinemaNoOverview": "Aucun synopsis", - "cinemaNoPoster": "Aucune affiche", - "cinemaNoSession": "Aucune séance", - "cinemaOverview": "Synopsis", - "cinemaPosterUrl": "URL de l'affiche", - "cinemaSessionDate": "Jour de la séance", - "cinemaStartHour": "Heure de début", - "cinemaTagline": "Slogan", - "cinemaThe": "Le", - "drawerAdmin": "Administration", - "drawerAndroidAppLink": "https://play.google.com/store/apps/details?id=fr.myecl.titan", - "drawerCopied": "Copié !", - "drawerDownloadAppOnMobileDevice": "Ce site est la version Web de l'application MyECL. Nous vous invitons à télécharger l'application. N'utilisez ce site qu'en cas de problème avec l'application.\n", - "drawerIosAppLink": "https://apps.apple.com/fr/app/myecl/id6444443430", - "drawerLoginOut": "Voulez-vous vous déconnecter ?", - "drawerLogOut": "Déconnexion", - "drawerOr": " ou ", - "drawerSettings": "Paramètres", - "eventAdd": "Ajouter", - "eventAddEvent": "Ajouter un événement", - "eventAddedEvent": "Événement ajouté", - "eventAddingError": "Erreur lors de l'ajout", - "eventAllDay": "Toute la journée", - "eventConfirm": "Confirmer", - "eventConfirmEvent": "Confirmer l'événement ?", - "eventConfirmation": "Confirmation", - "eventConfirmed": "Confirmé", - "eventDates": "Dates", - "eventDecline": "Refuser", - "eventDeclineEvent": "Refuser l'événement ?", - "eventDeclined": "Refusé", - "eventDelete": "Supprimer", - "eventDeletedEvent": "Événement supprimé", - "eventDeleting": "Suppression", - "eventDeletingError": "Erreur lors de la suppression", - "eventDeletingEvent": "Supprimer l'événement ?", - "eventDescription": "Description", - "eventEdit": "Modifier", - "eventEditEvent": "Modifier un événement", - "eventEditedEvent": "Événement modifié", - "eventEditingError": "Erreur lors de la modification", - "eventEndDate": "Date de fin", - "eventEndHour": "Heure de fin", - "eventError": "Erreur", - "eventEventList": "Liste des événements", - "eventEventType": "Type d'événement", - "eventEvery": "Tous les", - "eventHistory": "Historique", - "eventIncorrectOrMissingFields": "Certains champs sont incorrects ou manquants", - "eventInterval": "Intervalle", - "eventInvalidDates": "La date de fin doit être après la date de début", - "eventInvalidIntervalError": "Veuillez entrer un intervalle valide", - "eventLocation": "Lieu", - "eventMyEvents": "Mes événements", - "eventName": "Nom", - "eventNext": "Suivant", - "eventNo": "Non", - "eventNoCurrentEvent": "Aucun événement en cours", - "eventNoDateError": "Veuillez entrer une date", - "eventNoDaySelected": "Aucun jour sélectionné", - "eventNoDescriptionError": "Veuillez entrer une description", - "eventNoEvent": "Aucun événement", - "eventNoNameError": "Veuillez entrer un nom", - "eventNoOrganizerError": "Veuillez entrer un organisateur", - "eventNoPlaceError": "Veuillez entrer un lieu", - "eventNoPhoneRegistered": "Numéro non renseigné", - "eventNoRuleError": "Veuillez entrer une règle de récurrence", - "eventOrganizer": "Organisateur", - "eventOther": "Autre", - "eventPending": "En attente", - "eventPrevious": "Précédent", - "eventRecurrence": "Récurrence", - "eventRecurrenceDays": "Jours de récurrence", - "eventRecurrenceEndDate": "Date de fin de la récurrence", - "eventRecurrenceRule": "Règle de récurrence", - "eventRoom": "Salle", - "eventStartDate": "Date de début", - "eventStartHour": "Heure de début", - "eventTitle": "Événements", - "eventYes": "Oui", - "eventEventEvery": "Toutes les", - "eventWeeks": "semaines", - "eventDayMon": "Lundi", - "eventDayTue": "Mardi", - "eventDayWed": "Mercredi", - "eventDayThu": "Jeudi", - "eventDayFri": "Vendredi", - "eventDaySat": "Samedi", - "eventDaySun": "Dimanche", - "homeCalendar": "Calendrier", - "homeEventOf": "Évènements du", - "homeIncomingEvents": "Évènements à venir", - "homeLastInfos": "Dernières annonces", - "homeNoEvents": "Aucun évènement", - "homeTranslateDayShortMon": "Lun", - "homeTranslateDayShortTue": "Mar", - "homeTranslateDayShortWed": "Mer", - "homeTranslateDayShortThu": "Jeu", - "homeTranslateDayShortFri": "Ven", - "homeTranslateDayShortSat": "Sam", - "homeTranslateDayShortSun": "Dim", - "loanAdd": "Ajouter", - "loanAddLoan": "Ajouter un prêt", - "loanAddObject": "Ajouter un objet", - "loanAddedLoan": "Prêt ajouté", - "loanAddedObject": "Objet ajouté", - "loanAddedRoom": "Salle ajoutée", - "loanAddingError": "Erreur lors de l'ajout", - "loanAdmin": "Administrateur", - "loanAvailable": "Disponible", - "loanAvailableMultiple": "Disponibles", - "loanBorrowed": "Emprunté", - "loanBorrowedMultiple": "Empruntés", - "loanAnd": "et", - "loanAssociation": "Association", - "loanAvailableItems": "Objets disponibles", - "loanBeginDate": "Date du début du prêt", - "loanBorrower": "Emprunteur", - "loanCaution": "Caution", - "loanCancel": "Annuler", - "loanConfirm": "Confirmer", - "loanConfirmation": "Confirmation", - "loanDates": "Dates", - "loanDays": "Jours", - "loanDelay": "Délai de la prolongation", - "loanDelete": "Supprimer", - "loanDeletingLoan": "Supprimer le prêt ?", - "loanDeletedItem": "Objet supprimé", - "loanDeletedLoan": "Prêt supprimé", - "loanDeleting": "Suppression", - "loanDeletingError": "Erreur lors de la suppression", - "loanDeletingItem": "Supprimer l'objet ?", - "loanDuration": "Durée", - "loanEdit": "Modifier", - "loanEditItem": "Modifier l'objet", - "loanEditLoan": "Modifier le prêt", - "loanEditedRoom": "Salle modifiée", - "loanEndDate": "Date de fin du prêt", - "loanEnded": "Terminé", - "loanEnterDate": "Veuillez entrer une date", - "loanExtendedLoan": "Prêt prolongé", - "loanExtendingError": "Erreur lors de la prolongation", - "loanHistory": "Historique", - "loanIncorrectOrMissingFields": "Des champs sont manquants ou incorrects", - "loanInvalidNumber": "Veuillez entrer un nombre", - "loanInvalidDates": "Les dates ne sont pas valides", - "loanItem": "Objet", - "loanItems": "Objets", - "loanItemHandling": "Gestion des objets", - "loanItemSelected": "objet sélectionné", - "loanItemsSelected": "objets sélectionnés", - "loanLendingDuration": "Durée possible du prêt", - "loanLoan": "Prêt", - "loanLoanHandling": "Gestion des prêts", - "loanLooking": "Rechercher", - "loanName": "Nom", - "loanNext": "Suivant", - "loanNo": "Non", - "loanNoAssociationsFounded": "Aucune association trouvée", - "loanNoAvailableItems": "Aucun objet disponible", - "loanNoBorrower": "Aucun emprunteur", - "loanNoItems": "Aucun objet", - "loanNoItemSelected": "Aucun objet sélectionné", - "loanNoLoan": "Aucun prêt", - "loanNoReturnedDate": "Pas de date de retour", - "loanQuantity": "Quantité", - "loanNone": "Aucun", - "loanNote": "Note", - "loanNoValue": "Veuillez entrer une valeur", - "loanOnGoing": "En cours", - "loanOnGoingLoan": "Prêt en cours", - "loanOthers": "autres", - "loanPaidCaution": "Caution payée", - "loanPositiveNumber": "Veuillez entrer un nombre positif", - "loanPrevious": "Précédent", - "loanReturned": "Rendu", - "loanReturnedLoan": "Prêt rendu", - "loanReturningError": "Erreur lors du retour", - "loanReturningLoan": "Retour", - "loanReturnLoan": "Rendre le prêt ?", - "loanReturnLoanDescription": "Voulez-vous rendre ce prêt ?", - "loanToReturn": "A rendre", - "loanUnavailable": "Indisponible", - "loanUpdate": "Modifier", - "loanUpdatedItem": "Objet modifié", - "loanUpdatedLoan": "Prêt modifié", - "loanUpdatingError": "Erreur lors de la modification", - "loanYes": "Oui", - "loginAccountActivated": "Compte activé", - "loginAccountNotActivated": "Compte non activé", - "loginActivationCode": "Code d'activation", - "loginBirthday": "Date de naissance", - "loginCanBeEmpty": "Ce champ peut être vide", - "loginConfirmPassword": "Confirmer le mot de passe", - "loginCreate": "Créer", - "loginCreateAccount": "Créer un compte", - "loginCreateAccountTitle": "Créer un\ncompte", - "loginEmail": "Email", - "loginEmailEmpty": "Veuillez entrer une adresse mail", - "loginEmailInvalid": "Veuillez entrer une adresse mail de centrale.\nSi vous n'en possédez pas, veuillez contacter Éclair", - "loginEmptyFieldError": "Ce champ ne peut pas être vide", - "loginEndActivation": "Finaliser l'activation", - "loginEndResetPassword": "Finaliser la \nréinitialisation", - "loginErrorResetPassword": "Erreur lors de la réinitialisation", - "loginExpectingDate": "Une date est attendue", - "loginFillAllFields": "Veuillez remplir tous les champs", - "loginFirstname": "Prénom", - "loginFloor": "Étage", - "loginForgetPassword": "Mot de passe\noublié", - "loginForgotPassword": "Mot de passe oublié ?", - "loginInvalidToken": "Code d'activation invalide", - "loginLoginFailed": "Échec de la connexion", - "loginMailSendingError": "Erreur lors de la création du compte", - "loginMustBeIntError": "Ce champ doit être un entier", - "loginName": "Nom", - "loginNewPassword": "Nouveau mot de passe", - "loginPassword": "Mot de passe", - "loginPasswordLengthError": "Le mot de passe doit faire au moins 6 caractères", - "loginPasswordUppercaseError": "Le mot de passe doit contenir au moins une majuscule", - "loginPasswordLowercaseError": "Le mot de passe doit contenir au moins une minucule", - "loginPasswordNumberError": "Le mot de passe doit contenir au moins un chiffre", - "loginPasswordSpecialCaracterError": "Le mot de passe doit contenir au moins un caractère spécial", - "loginPasswordMustMatch": "Les mots de passe doivent correspondre", - "loginPasswordStrengthVeryWeak": "Très faible", - "loginPasswordStrengthWeak": "Faible", - "loginPasswordStrengthMedium": "Moyen", - "loginPasswordStrengthStrong": "Fort", - "loginPasswordStrengthVeryStrong": "Très fort", - "loginPhone": "Téléphone", - "loginPromo": "Promo entrante (ex : 2023)", - "loginSendedMail": "Mail de confirmation envoyé", - "loginSendedResetMail": "Mail de réinitialisation envoyé", - "loginSignIn": "Se connecter", - "loginRegister": "S'inscrire", - "loginRecievedMail": "J'ai reçu le mail", - "loginRecover": "Réinitialiser", - "loginResetedPassword": "Mot de passe réinitialisé", - "loginResetPasswordTitle": "Réinitialiser\nle mot de \npasse", - "loginNickname": "Surnom", - "loginWelcomeBack": "Bienvenue", - "loginAppName": "MyECL", - "othersCheckInternetConnection": "Veuillez vérifier votre connexion internet", - "othersRetry": "Réessayer", - "othersTooOldVersion": "Votre version de l'application est trop ancienne.\n\nVeuillez mettre à jour l'application.", - "othersUnableToConnectToServer": "Impossible de se connecter au serveur", - "othersVersion": "Version", - "othersNoModule": "Aucun module disponible, veuillez réessayer ultérieurement 😢😢", - "othersAdmin": "Admin", - "othersError": "Une erreur est survenue", - "othersNoValue": "Veuillez entrer une valeur", - "othersInvalidNumber": "Veuillez entrer un nombre", - "othersNoDateError": "Veuillez entrer une date", - "othersImageSizeTooBig": "La taille de l'image ne doit pas dépasser 4 Mio", - "othersImageError": "Erreur lors de l'ajout de l'image", - "phAddNewJournal": "Ajouter un nouveau journal", - "phNameField": "Nom : ", - "phDateField": "Date : ", - "phDelete": "Voulez-vous vraiment supprimer ce journal ?", - "phIrreversibleAction": "Cette action est irréversible", - "phToHeavyFile": "Fichier trop volumineux", - "phAddPdfFile": "Ajouter un fichier PDF", - "phEditPdfFile": "Modifier le fichier PDF", - "phPhName": "Nom du PH", - "phDate": "Date", - "phAdded": "Ajouté", - "phEdited": "Modifié", - "phAddingFileError": "Erreur d'ajout", - "phMissingInformatonsOrPdf": "Informations manquantes ou fichier PDF manquant", - "phAdd": "Ajouter", - "phEdit": "Modifier", - "phSeePreviousJournal": "Voir les anciens journaux", - "phNoJournalInDatabase": "Pas encore de PH dans la base de donnée", - "phSuccesDowloading": "Téléchargé avec succès", - "phonebookActiveMandate": "Mandat actif :", - "phonebookAdd": "Ajouter", - "phonebookAddAssociation": "Ajouter une association", - "phonebookAddedAssociation": "Association ajoutée", - "phonebookAddedMember": "Membre ajouté", - "phonebookAddingError": "Erreur lors de l'ajout", - "phonebookAddMember": "Ajouter un membre", - "phonebookAddRole": "Ajouter un rôle", - "phonebookAdmin": "Admin", - "phonebookAdminPage": "Page Administrateur", - "phonebookAll": "Toutes", - "phonebookApparentName": "Nom public du rôle :", - "phonebookAssociation": "Association :", - "phonebookAssociationDetail": "Détail de l'association :", - "phonebookAssociationKind": "Type d'association :", - "phonebookAssociationPure": "Association", - "phonebookAssociationPureSearch": " Association", - "phonebookAssociations": "Associations :", - "phonebookCancel": "Annuler", - "phonebookChangeMandate": "Passer au mandat ", - "phonebookChangeMandateConfirm": "Êtes-vous sûr de vouloir changer tout le mandat ?\nCette action est irréversible !", - "phonebookCopied": "Copié dans le presse-papier", - "phonebookDeactivateAssociation": "Êtes-vous sûr de vouloir désactiver cette association ?\nCette action est irréversible !", - "phonebookDeactivatedAssociation": "Association désactivée", - "phonebookDeactivatedAssociationWarning": "Attention, cette association est désactivée, vous ne pouvez pas la modifier", - "phonebookDeactivating": "Désactiver l'association ?", - "phonebookDeactivatingError": "Erreur lors de la désactivation", - "phonebookDetail": "Détail :", - "phonebookDeleteAssociation": "Supprimer l'association ?\nCela va effacer tout l'historique de l'association", - "phonebookDeletedAssociation": "Association supprimée", - "phonebookDeletedMember": "Membre supprimé", - "phonebookDeleting": "Suppression", - "phonebookDeletingError": "Erreur lors de la suppression", - "phonebookDescription": "Description", - "phonebookEdit": "Modifier", - "phonebookEditMembership": "Modifier le rôle", - "phonebookEmail": "Email :", - "phonebookEmailCopied": "Email copié dans le presse-papier", - "phonebookEmptyApparentName": "Veuillez entrer un nom de role", - "phonebookEmptyFieldError": "Un champ n'est pas rempli", - "phonebookEmptyKindError": "Veuillez choisir un type d'association", - "phonebookEmptyMember": "Aucun membre sélectionné", - "phonebookErrorAssociationLoading": "Erreur lors du chargement de l'association", - "phonebookErrorAssociationNameEmpty": "Veuillez entrer un nom d'association", - "phonebookErrorAssociationPicture": "Erreur lors de la modification de la photo d'association", - "phonebookErrorKindsLoading": "Erreur lors du chargement des types d'association", - "phonebookErrorLoadAssociationList": "Erreur lors du chargement de la liste des associations", - "phonebookErrorLoadAssociationMember": "Erreur lors du chargement des membres de l'association", - "phonebookErrorLoadAssociationPicture": "Erreur lors du chargement de la photo d'association", - "phonebookErrorLoadProfilePicture": "Erreur", - "phonebookErrorRoleTagsLoading": "Erreur lors du chargement des tags de rôle", - "phonebookExistingMembership": "Ce membre est déjà dans le mandat actuel", - "phonebookFirstname": "Prénom :", - "phonebookGroups": "Groupes associés :", - "phonebookMandateChangingError": "Erreur lors du changement de mandat", - "phonebookMember": "Membre", - "phonebookMemberReordered": "Membre réordonné", - "phonebookMembers": "Membres", - "phonebookMembershipAssociationError": "Veuillez choisir une association", - "phonebookMembershipRole": "Rôle :", - "phonebookMembershipRoleError": "Veuillez choisir un rôle", - "phonebookName": "Nom :", - "phonebookNameCopied": "Nom et prénom copié dans le presse-papier", - "phonebookNamePure": "Nom", - "phonebookNewMandate": "Nouveau mandat", - "phonebookNewMandateConfirmed": "Mandat changé", - "phonebookNickname": "Surnom :", - "phonebookNicknameCopied": "Surnom copié dans le presse-papier", - "phonebookNoAssociationFound": "Aucune association trouvée", - "phonebookNoMember": "Aucun membre", - "phonebookNoMemberRole": "Aucun role trouvé", - "phonebookPhone": "Téléphone :", - "phonebookPhonebook": "Annuaire", - "phonebookPhonebookSearch": "Rechercher", - "phonebookPhonebookSearchAssociation": "Association", - "phonebookPhonebookSearchField": "Rechercher :", - "phonebookPhonebookSearchName": "Nom/Prénom/Surnom", - "phonebookPhonebookSearchRole": "Poste", - "phonebookPresidentRoleTag": "Prez'", - "phonebookPromoNotGiven": "Promo non renseignée", - "phonebookPromotion": "Promotion :", - "phonebookReorderingError": "Erreur lors du réordonnement", - "phonebookResearch": "Rechercher", - "phonebookRolePure": "Rôle", - "phonebookTooHeavyAssociationPicture": "L'image est trop lourde (max 4Mo)", - "phonebookUpdateGroups": "Mettre à jour les groupes", - "phonebookUpdatedAssociation": "Association modifiée", - "phonebookUpdatedAssociationPicture": "La photo d'association a été changée", - "phonebookUpdatedGroups": "Groupes mis à jour", - "phonebookUpdatedMember": "Membre modifié", - "phonebookUpdatingError": "Erreur lors de la modification", - "phonebookValidation": "Valider", - "purchasesPurchases": "Achats", - "purchasesResearch": "Rechercher", - "purchasesNoPurchasesFound": "Aucun achat trouvé", - "purchasesNoTickets": "Aucun ticket", - "purchasesTicketsError": "Erreur lors du chargement des tickets", - "purchasesPurchasesError": "Erreur lors du chargement des achats", - "purchasesNoPurchases": "Aucun achat", - "purchasesTimes": "fois", - "purchasesAlreadyUsed": "Déjà utilisé", - "purchasesNotPaid": "Non validé", - "purchasesPleaseSelectProduct": "Veuillez sélectionner un produit", - "purchasesProducts": "Produits", - "purchasesCancel": "Annuler", - "purchasesValidate": "Valider", - "purchasesLeftScan": "Scans restants", - "purchasesTag": "Tag", - "purchasesHistory": "Historique", - "purchasesPleaseSelectSeller": "Veuillez sélectionner un vendeur", - "purchasesNoTagGiven": "Attention, aucun tag n'a été entré", - "purchasesTickets": "Tickets", - "purchasesNoScannableProducts": "Aucun produit scannable", - "purchasesLoading": "En attente de scan", - "purchasesScan": "Scanner", - "raffleRaffle": "Tombola", - "rafflePrize": "Lot", - "rafflePrizes": "Lots", - "raffleActualRaffles": "Tombola en cours", - "rafflePastRaffles": "Tombola passés", - "raffleYourTickets": "Tous vos tickets", - "raffleCreateMenu": "Menu de Création", - "raffleNextRaffles": "Prochaines tombolas", - "raffleNoTicket": "Vous n'avez pas de ticket", - "raffleSeeRaffleDetail": "Voir lots/tickets", - "raffleActualPrize": "Lots actuels", - "raffleMajorPrize": "Lot Majeurs", - "raffleTakeTickets": "Prendre vos tickets", - "raffleNoTicketBuyable": "Vous ne pouvez pas achetez de billets pour l'instant", - "raffleNoCurrentPrize": "Il n'y a aucun lots actuellement", - "raffleModifTombola": "Vous pouvez modifiez vos tombolas ou en créer de nouvelles, toute décision doit ensuite être prise par les admins", - "raffleCreateYourRaffle": "Votre menu de création de tombolas", - "rafflePossiblePrice": "Prix possible", - "raffleInformation": "Information et Statistiques", - "raffleAccounts": "Comptes", - "raffleAdd": "Ajouter", - "raffleUpdatedAmount": "Montant mis à jour", - "raffleUpdatingError": "Erreur lors de la mise à jour", - "raffleDeletedPrize": "Lot supprimé", - "raffleDeletingError": "Erreur lors de la suppression", - "raffleQuantity": "Quantité", - "raffleClose": "Fermer", - "raffleOpen": "Ouvrir", - "raffleAddTypeTicketSimple": "Ajouter", - "raffleAddingError": "Erreur lors de l'ajout", - "raffleEditTypeTicketSimple": "Modifier", - "raffleFillField": "Le champ ne peut pas être vide", - "raffleWaiting": "Chargement", - "raffleEditingError": "Erreur lors de la modification", - "raffleAddedTicket": "Ticket ajouté", - "raffleEditedTicket": "Ticket modifié", - "raffleAlreadyExistTicket": "Le ticket existe déjà", - "raffleNumberExpected": "Un entier est attendu", - "raffleDeletedTicket": "Ticket supprimé", - "raffleAddPrize": "Ajouter", - "raffleEditPrize": "Modifier", - "raffleOpenRaffle": "Ouvrir la tombola", - "raffleCloseRaffle": "Fermer la tombola", - "raffleOpenRaffleDescription": "Vous allez ouvrir la tombola, les utilisateurs pourront acheter des tickets. Vous ne pourrez plus modifier la tombola. Êtes-vous sûr de vouloir continuer ?", - "raffleCloseRaffleDescription": "Vous allez fermer la tombola, les utilisateurs ne pourront plus acheter de tickets. Êtes-vous sûr de vouloir continuer ?", - "raffleNoCurrentRaffle": "Il n'y a aucune tombola en cours", - "raffleBoughtTicket": "Ticket acheté", - "raffleDrawingError": "Erreur lors du tirage", - "raffleInvalidPrice": "Le prix doit être supérieur à 0", - "raffleMustBePositive": "Le nombre doit être strictement positif", - "raffleDraw": "Tirer", - "raffleDrawn": "Tiré", - "raffleError": "Erreur", - "raffleGathered": "Récolté", - "raffleTickets": "Tickets", - "raffleTicket": "ticket", - "raffleWinner": "Gagnant", - "raffleNoPrize": "Aucun lot", - "raffleDeletePrize": "Supprimer le lot", - "raffleDeletePrizeDescription": "Vous allez supprimer le lot, êtes-vous sûr de vouloir continuer ?", - "raffleDrawing": "Tirage", - "raffleDrawingDescription": "Tirer le gagnant du lot ?", - "raffleDeleteTicket": "Supprimer le ticket", - "raffleDeleteTicketDescription": "Vous allez supprimer le ticket, êtes-vous sûr de vouloir continuer ?", - "raffleWinningTickets": "Tickets gagnants", - "raffleNoWinningTicketYet": "Les tickets gagnants seront affichés ici", - "raffleName": "Nom", - "raffleDescription": "Description", - "raffleBuyThisTicket": "Acheter ce ticket", - "raffleLockedRaffle": "Tombola verrouillée", - "raffleUnavailableRaffle": "Tombola indisponible", - "raffleNotEnoughMoney": "Vous n'avez pas assez d'argent", - "raffleWinnable": "gagnable", - "raffleNoDescription": "Aucune description", - "raffleAmount": "Solde", - "raffleLoading": "Chargement", - "raffleTicketNumber": "Nombre de ticket", - "rafflePrice": "Prix", - "raffleEditRaffle": "Modifier la tombola", - "raffleEdit": "Modifier", - "raffleAddPackTicket": "Ajouter un pack de ticket", - "recommendationRecommendation": "Bons plans", - "recommendationTitle": "Titre", - "recommendationLogo": "Logo", - "recommendationCode": "Code", - "recommendationSummary": "Court résumé", - "recommendationDescription": "Description", - "recommendationAdd": "Ajouter", - "recommendationEdit": "Modifier", - "recommendationDelete": "Supprimer", - "recommendationAddImage": "Veuillez ajouter une image", - "recommendationAddedRecommendation": "Bon plan ajouté", - "recommendationEditedRecommendation": "Bon plan modifié", - "recommendationDeleteRecommendationConfirmation": "Êtes-vous sûr de vouloir supprimer ce bon plan ?", - "recommendationDeleteRecommendation": "Suppresion", - "recommendationDeletingRecommendationError": "Erreur lors de la suppression", - "recommendationDeletedRecommendation": "Bon plan supprimé", - "recommendationIncorrectOrMissingFields": "Champs incorrects ou manquants", - "recommendationEditingError": "Échec de la modification", - "recommendationAddingError": "Échec de l'ajout", - "recommendationCopiedCode": "Code de réduction copié", - "seedLibraryAdd": "Ajouter", - "seedLibraryAddedPlant": "Plante ajoutée", - "seedLibraryAddedSpecies": "Espèce ajoutée", - "seedLibraryAddingError": "Erreur lors de l'ajout", - "seedLibraryAddPlant": "Déposer une plante", - "seedLibraryAddSpecies": "Ajouter une espèce", - "seedLibraryAll": "Toutes", - "seedLibraryAncestor": "Ancêtre", - "seedLibraryAround": "environ", - "seedLibraryAutumn": "Automne", - "seedLibraryBorrowedPlant": "Plante empruntée", - "seedLibraryBorrowingDate": "Date d'emprunt :", - "seedLibraryBorrowPlant": "Emprunter la plante", - "seedLibraryCard": "Carte", - "seedLibraryChoosingAncestor": "Veuillez choisir un ancêtre", - "seedLibraryChoosingSpecies": "Veuillez choisir une espèce", - "seedLibraryChoosingSpeciesOrAncestor": "Veuillez choisir une espèce ou un ancêtre", - "seedLibraryContact": "Contact :", - "seedLibraryDays": "jours", - "seedLibraryDeadMsg": "Voulez-vous déclarer la plante morte ?", - "seedLibraryDeadPlant": "Plante morte", - "seedLibraryDeathDate": "Date de mort", - "seedLibraryDeletedSpecies": "Espèce supprimée", - "seedLibraryDeleteSpecies": "Supprimer l'espèce ?", - "seedLibraryDeleting": "Suppression", - "seedLibraryDeletingError": "Erreur lors de la suppression", - "seedLibraryDepositNotAvailable": "Le dépôt de plantes n'est pas possible sans emprunter une plante au préalable", - "seedLibraryDescription": "Description", - "seedLibraryDifficulty": "Difficulté :", - "seedLibraryEdit": "Modifier", - "seedLibraryEditedPlant": "Plante modifiée", - "seedLibraryEditInformation": "Modifier les informations", - "seedLibraryEditingError": "Erreur lors de la modification", - "seedLibraryEditSpecies": "Modifier l'espèce", - "seedLibraryEmptyDifficultyError": "Veuillez choisir une difficulté", - "seedLibraryEmptyFieldError": "Veuillez remplir tous les champs", - "seedLibraryEmptyTypeError": "Veuillez choisir un type de plante", - "seedLibraryEndMonth": "Mois de fin :", - "seedLibraryFacebookUrl": "Lien Facebook", - "seedLibraryFilters": "Filtres", - "seedLibraryForum": "Oskour maman j'ai tué ma plante - Forum d'aide", - "seedLibraryForumUrl": "Lien Forum", - "seedLibraryHelpSheets": "Fiches sur les plantes", - "seedLibraryInformation": "Informations :", - "seedLibraryMaturationTime": "Temps de maturation", - "seedLibraryMonthJan": "Janvier", - "seedLibraryMonthFeb": "Février", - "seedLibraryMonthMar": "Mars", - "seedLibraryMonthApr": "Avril", - "seedLibraryMonthMay": "Mai", - "seedLibraryMonthJun": "Juin", - "seedLibraryMonthJul": "Juillet", - "seedLibraryMonthAug": "Août", - "seedLibraryMonthSep": "Septembre", - "seedLibraryMonthOct": "Octobre", - "seedLibraryMonthNov": "Novembre", - "seedLibraryMonthDec": "Décembre", - "seedLibraryMyPlants": "Mes plantes", - "seedLibraryName": "Nom", - "seedLibraryNbSeedsRecommended": "Nombre de graines recommandées", - "seedLibraryNbSeedsRecommendedError": "Veuillez entrer un nombre de graines recommandé supérieur à 0", - "seedLibraryNoDateError": "Veuillez entrer une date", - "seedLibraryNoFilteredPlants": "Aucune plante ne correspond à votre recherche. Essayez d'autres filtres.", - "seedLibraryNoMorePlant": "Aucune plante n'est disponible", - "seedLibraryNoPersonalPlants": "Vous n'avez pas encore de plantes dans votre grainothèque. Vous pouvez en ajouter en allant dans les stocks.", - "seedLibraryNoSpecies": "Aucune espèce trouvée", - "seedLibraryNoStockPlants": "Aucune plante disponible dans le stock", - "seedLibraryNotes": "Notes", - "seedLibraryOk": "OK", - "seedLibraryPlantationPeriod": "Période de plantation :", - "seedLibraryPlantationType": "Type de plantation :", - "seedLibraryPlantDetail": "Détail de la plante", - "seedLibraryPlantingDate": "Date de plantation", - "seedLibraryPlantingNow": "Je la plante maintenant", - "seedLibraryPrefix": "Préfixe", - "seedLibraryPrefixError": "Prefixe déjà utilisé", - "seedLibraryPrefixLengthError": "Le préfixe doit faire 3 caractères", - "seedLibraryPropagationMethod": "Méthode de propagation :", - "seedLibraryReference": "Référence :", - "seedLibraryRemovedPlant": "Plante supprimée", - "seedLibraryRemovingError": "Erreur lors de la suppression", - "seedLibraryResearch": "Recherche", - "seedLibrarySaveChanges": "Sauvegarder les modifications", - "seedLibrarySeason": "Saison :", - "seedLibrarySeed": "Graine", - "seedLibrarySeeds": "graines", - "seedLibrarySeedDeposit": "Dépôt de plantes", - "seedLibrarySeedLibrary": "Grainothèque", - "seedLibrarySeedQuantitySimple": "Quantité de graines", - "seedLibrarySeedQuantity": "Quantité de graines :", - "seedLibraryShowDeadPlants": "Afficher les plantes mortes", - "seedLibrarySpecies": "Espèce :", - "seedLibrarySpeciesHelp": "Aide sur l'espèce", - "seedLibrarySpeciesPlural": "Espèces", - "seedLibrarySpeciesSimple": "Espèce", - "seedLibrarySpeciesType": "Type d'espèce :", - "seedLibrarySpring": "Printemps", - "seedLibraryStartMonth": "Mois de début :", - "seedLibraryStock": "Stock disponible", - "seedLibrarySummer": "Été", - "seedLibraryStocks": "Stocks", - "seedLibraryTimeUntilMaturation": "Temps avant maturation :", - "seedLibraryType": "Type :", - "seedLibraryUnableToOpen": "Impossible d'ouvrir le lien", - "seedLibraryUpdate": "Modifier", - "seedLibraryUpdatedInformation": "Informations modifiées", - "seedLibraryUpdatedSpecies": "Espèce modifiée", - "seedLibraryUpdatedPlant": "Plante modifiée", - "seedLibraryUpdatingError": "Erreur lors de la modification", - "seedLibraryWinter": "Hiver", - "seedLibraryWriteReference": "Veuillez écrire la référence suivante : ", - "settingsAccount": "Compte", - "settingsAddProfilePicture": "Ajouter une photo", - "settingsAdmin": "Administrateur", - "settingsAskHelp": "Demander de l'aide", - "settingsAssociation": "Association", - "settingsBirthday": "Date de naissance", - "settingsBugs": "Bugs", - "settingsChangePassword": "Changer de mot de passe", - "settingsChangingPassword": "Voulez-vous vraiment changer votre mot de passe ?", - "settingsConfirmPassword": "Confirmer le mot de passe", - "settingsCopied": "Copié !", - "settingsDarkMode": "Mode sombre", - "settingsDarkModeOff": "Désactivé", - "settingsDeleteLogs": "Supprimer les logs ?", - "settingsDeleteNotificationLogs": "Supprimer les logs des notifications ?", - "settingsDetelePersonalData": "Supprimer mes données personnelles", - "settingsDetelePersonalDataDesc": "Cette action notifie l'administrateur que vous souhaitez supprimer vos données personnelles.", - "settingsDeleting": "Suppresion", - "settingsEdit": "Modifier", - "settingsEditAccount": "Modifier mon profil", - "settingsEmail": "Email", - "settingsEmptyField": "Ce champ ne peut pas être vide", - "settingsErrorProfilePicture": "Erreur lors de la modification de la photo de profil", - "settingsErrorSendingDemand": "Erreur lors de l'envoi de la demande", - "settingsEventsIcal": "Lien Ical des événements", - "settingsExpectingDate": "Date de naissance attendue", - "settingsFirstname": "Prénom", - "settingsFloor": "Étage", - "settingsHelp": "Aide", - "settingsIcalCopied": "Lien Ical copié !", - "settingsLanguage": "Langue", - "settingsLanguageVar": "Français 🇫🇷", - "settingsLogs": "Logs", - "settingsModules": "Modules", - "settingsMyIcs": "Mon lien Ical", - "settingsName": "Nom", - "settingsNewPassword": "Nouveau mot de passe", - "settingsNickname": "Surnom", - "settingsNotifications": "Notifications", - "settingsOldPassword": "Ancien mot de passe", - "settingsPasswordChanged": "Mot de passe changé", - "settingsPasswordsNotMatch": "Les mots de passe ne correspondent pas", - "settingsPersonalData": "Données personnelles", - "settingsPersonalisation": "Personnalisation", - "settingsPhone": "Téléphone", - "settingsProfilePicture": "Photo de profil", - "settingsPromo": "Promotion", - "settingsRepportBug": "Signaler un bug", - "settingsSave": "Enregistrer", - "settingsSecurity": "Sécurité", - "settingsSendedDemand": "Demande envoyée", - "settingsSettings": "Paramètres", - "settingsTooHeavyProfilePicture": "L'image est trop lourde (max 4Mo)", - "settingsUpdatedProfile": "Profil modifié", - "settingsUpdatedProfilePicture": "Photo de profil modifiée", - "settingsUpdateNotification": "Mettre à jour les notifications", - "settingsUpdatingError": "Erreur lors de la modification du profil", - "settingsVersion": "Version", - "settingsPasswordStrength": "Force du mot de passe", - "settingsPasswordStrengthVeryWeak": "Très faible", - "settingsPasswordStrengthWeak": "Faible", - "settingsPasswordStrengthMedium": "Moyen", - "settingsPasswordStrengthStrong": "Fort", - "settingsPasswordStrengthVeryStrong": "Très fort", - "settingsPhoneNumber": "Numéro de téléphone", - "settingsValidate": "Valider", - "settingsEditedAccount": "Compte modifié avec succès", - "settingsFailedToEditAccount": "Échec de la modification du compte", - "settingsChooseLanguage": "Choix de la langue", - "settingsNotificationCounter": "{active}/{total} {active, plural, zero {activée} one {activée} other {activées}}", - "@settingsNotificationCounter": { - "description": "Affiche le nombre de notifications actives sur le total des notifications disponibles, avec gestion du pluriel", - "placeholders": { - "active": { "type": "int" }, - "total": { "type": "int" } - } - }, - "settingsEvent" : "Événement", - "settingsIcal": "Lien Ical", - "settingsSynncWithCalendar": "Synchroniser avec votre calendrier", - "settingsIcalLinkCopied": "Lien Ical copié dans le presse-papier", - "settingsProfile": "Profil", - "voteAdd": "Ajouter", - "voteAddMember": "Ajouter un membre", - "voteAddedPretendance": "Liste ajoutée", - "voteAddedSection": "Section ajoutée", - "voteAddingError": "Erreur lors de l'ajout", - "voteAddPretendance": "Ajouter une liste", - "voteAddSection": "Ajouter une section", - "voteAll": "Tous", - "voteAlreadyAddedMember": "Membre déjà ajouté", - "voteAlreadyVoted": "Vote enregistré", - "voteChooseList": "Choisir une liste", - "voteClear": "Réinitialiser", - "voteClearVotes": "Réinitialiser les votes", - "voteClosedVote": "Votes clos", - "voteCloseVote": "Fermer les votes", - "voteConfirmVote": "Confirmer le vote", - "voteCountVote": "Dépouiller les votes", - "voteDeletedAll": "Tout supprimé", - "voteDeletedPipo": "Listes pipos supprimées", - "voteDeletedSection": "Section supprimée", - "voteDeleteAll": "Supprimer tout", - "voteDeleteAllDescription": "Voulez-vous vraiment supprimer tout ?", - "voteDeletePipo": "Supprimer les listes pipos", - "voteDeletePipoDescription": "Voulez-vous vraiment supprimer les listes pipos ?", - "voteDeletePretendance": "Supprimer la liste", - "voteDeletePretendanceDesc": "Voulez-vous vraiment supprimer cette liste ?", - "voteDeleteSection": "Supprimer la section", - "voteDeleteSectionDescription": "Voulez-vous vraiment supprimer cette section ?", - "voteDeletingError": "Erreur lors de la suppression", - "voteDescription": "Description", - "voteEdit": "Modifier", - "voteEditedPretendance": "Liste modifiée", - "voteEditedSection": "Section modifiée", - "voteEditingError": "Erreur lors de la modification", - "voteErrorClosingVotes": "Erreur lors de la fermeture des votes", - "voteErrorCountingVotes": "Erreur lors du dépouillement des votes", - "voteErrorResetingVotes": "Erreur lors de la réinitialisation des votes", - "voteErrorOpeningVotes": "Erreur lors de l'ouverture des votes", - "voteIncorrectOrMissingFields": "Champs incorrects ou manquants", - "voteMembers": "Membres", - "voteName": "Nom", - "voteNoPretendanceList": "Aucune liste de prétendance", - "voteNoSection": "Aucune section", - "voteCanNotVote": "Vous ne pouvez pas voter", - "voteNoSectionList": "Aucune section", - "voteNotOpenedVote": "Vote non ouvert", - "voteOnGoingCount": "Dépouillement en cours", - "voteOpenVote": "Ouvrir les votes", - "votePipo": "Pipo", - "votePretendance": "Listes", - "votePretendanceDeleted": "Prétendance supprimée", - "votePretendanceNotDeleted": "Erreur lors de la suppression", - "voteProgram": "Programme", - "votePublish": "Publier", - "votePublishVoteDescription": "Voulez-vous vraiment publier les votes ?", - "voteResetedVotes": "Votes réinitialisés", - "voteResetVote": "Réinitialiser les votes", - "voteResetVoteDescription": "Que voulez-vous faire ?", - "voteRole": "Rôle", - "voteSectionDescription": "Description de la section", - "voteSection": "Section", - "voteSectionName": "Nom de la section", - "voteSeeMore": "Voir plus", - "voteSelected": "Sélectionné", - "voteShowVotes": "Voir les votes", - "voteVote": "Vote", - "voteVoteError": "Erreur lors de l'enregistrement du vote", - "voteVoteFor": "Voter pour ", - "voteVoteNotStarted": "Vote non ouvert", - "voteVoters": "Groupes votants", - "voteVoteSuccess": "Vote enregistré", - "voteVotes": "Voix", - "voteVotesClosed": "Votes clos", - "voteVotesCounted": "Votes dépouillés", - "voteVotesOpened": "Votes ouverts", - "voteWarning": "Attention", - "voteWarningMessage": "La sélection ne sera pas sauvegardée.\nVoulez-vous continuer ?", - "moduleAdvert": "Annonce", + "@@locale": "fr", + "dateToday": "Aujourd'hui", + "dateYesterday": "Hier", + "dateTomorrow": "Demain", + "dateAt": "à", + "dateFrom": "de", + "dateTo": "à", + "dateBetweenDays": "au", + "dateStarting": "Commence", + "dateLast": "", + "dateUntil": "Jusqu'au", + "feedFilterAll": "Tous", + "feedFilterPending": "En attente", + "feedFilterApproved": "Approuvés", + "feedFilterRejected": "Rejetés", + "feedEmptyAll": "Aucun événement disponible", + "feedEmptyPending": "Aucun événement en attente de validation", + "feedEmptyApproved": "Aucun événement approuvé", + "feedEmptyRejected": "Aucun événement rejeté", + "feedEventManagement": "Gestion des événements", + "eventActionCampaign": "Tu peux voter", + "eventActionEvent": "Tu es invité", + "eventActionCampaignSubtitle": "Votez maintenant", + "eventActionEventSubtitle": "Répondez à l'invitation", + "eventActionCampaignButton": "Voter", + "eventActionEventButton": "Participer", + "eventActionCampaignValidated": "J'ai voté !", + "eventActionEventValidated": "Je viens !", + "adminAccountTypes": "Types de compte", + "adminAdd": "Ajouter", + "adminAddGroup": "Ajouter un groupe", + "adminAddMember": "Ajouter un membre", + "adminAddedGroup": "Groupe créé", + "adminAddedLoaner": "Préteur ajouté", + "adminAddedMember": "Membre ajouté", + "adminAddingError": "Erreur lors de l'ajout", + "adminAddingMember": "Ajout d'un membre", + "adminAddLoaningGroup": "Ajouter un groupe de prêt", + "adminAddSchool": "Ajouter une école", + "adminAddStructure": "Ajouter une structure", + "adminAddedSchool": "École créée", + "adminAddedStructure": "Structure ajoutée", + "adminEditedStructure": "Structure modifiée", + "adminAdministration": "Administration", + "adminAssociationMembership": "Adhésion", + "adminAssociationMembershipName": "Nom de l'adhésion", + "adminAssociationsMemberships": "Adhésions", + "adminClearFilters": "Effacer les filtres", + "adminCreateAssociationMembership": "Créer une adhésion", + "adminCreatedAssociationMembership": "Adhésion créée", + "adminCreationError": "Erreur lors de la création", + "adminDateError": "La date de début doit être avant la date de fin", + "adminDelete": "Supprimer", + "adminDeleteAssociationMembership": "Supprimer l'adhésion ?", + "adminDeletedAssociationMembership": "Adhésion supprimée", + "adminDeleteGroup": "Supprimer le groupe", + "adminDeletedGroup": "Groupe supprimé", + "adminDeleteSchool": "Supprimer l'école ?", + "adminDeletedSchool": "École supprimée", + "adminDeleting": "Suppression", + "adminDeletingError": "Erreur lors de la suppression", + "adminDescription": "Description", + "adminEclSchool": "Centrale Lyon", + "adminEdit": "Modifier", + "adminEditStructure": "Modifier la structure", + "adminEditMembership": "Modifier l'adhésion", + "adminEmptyDate": "Date vide", + "adminEmptyFieldError": "Le nom ne peut pas être vide", + "adminEmailRegex": "Email Regex", + "adminEmptyUser": "Utilisateur vide", + "adminEndDate": "Date de fin", + "adminEndDateMaximal": "Date de fin maximale", + "adminEndDateMinimal": "Date de fin minimale", + "adminError": "Erreur", + "adminFilters": "Filtres", + "adminGroup": "Groupe", + "adminGroups": "Groupes", + "adminLoaningGroup": "Groupe de prêt", + "adminLooking": "Recherche", + "adminManager": "Administrateur de la structure", + "adminMaximum": "Maximum", + "adminMembers": "Membres", + "adminMembershipAddingError": "Erreur lors de l'ajout (surement dû à une superposition de dates)", + "adminMemberships": "Adhésions", + "adminMembershipUpdatingError": "Erreur lors de la modification (surement dû à une superposition de dates)", + "adminMinimum": "Minimum", + "adminModifyModuleVisibility": "Visibilité des modules", + "adminMyEclPay": "MyECLPay", + "adminName": "Nom", + "adminNoManager": "Aucun manager n'est sélectionné", + "adminNoMember": "Aucun membre", + "adminNoMoreLoaner": "Aucun prêteur n'est disponible", + "adminNoSchool": "Sans école", + "adminRemoveGroupMember": "Supprimer le membre du groupe ?", + "adminResearch": "Recherche", + "adminSchools": "Écoles", + "adminStructures": "Structures", + "adminStartDate": "Date de début", + "adminStartDateMaximal": "Date de début maximale", + "adminStartDateMinimal": "Date de début minimale", + "adminUpdatedAssociationMembership": "Adhésion modifiée", + "adminUpdatedGroup": "Groupe modifié", + "adminUpdatedMembership": "Adhésion modifiée", + "adminUpdatingError": "Erreur lors de la modification", + "adminUser": "Utilisateur", + "adminValidateFilters": "Valider les filtres", + "adminVisibilities": "Visibilités", + "adminGroupNotification": "Notification de groupe", + "adminNotifyGroup": "Notifier le groupe {groupName}", + "@adminNotifyGroup": { + "description": "Notifie les membres du groupe sélectionné", + "placeholders": { + "groupName": { + "type": "String" + } + } + }, + "adminTitle": "Titre", + "adminContent": "Contenu", + "adminSend": "Envoyer", + "adminNotificationSent": "Notification envoyée", + "adminFailedToSendNotification": "Échec de l'envoi de la notification", + "adminGroupsManagement": "Gestion des groupes", + "adminEditGroup": "Modifier le groupe", + "adminManageMembers": "Gérer les membres", + "adminDeleteGroupConfirmation": "Êtes-vous sûr de vouloir supprimer ce groupe ?", + "adminFailedToDeleteGroup": "Échec de la suppression du groupe", + "adminUsersAndGroups": "Utilisateurs et groupes", + "adminUsersManagement": "Gestion des utilisateurs", + "adminUsersManagementDescription": "Gérer les utilisateurs de l'application", + "adminManageUserGroups": "Gérer les groupes d'utilisateurs", + "adminSendNotificationToGroup": "Envoyer une notification à un groupe", + "adminPaiementModule": "Module de paiement", + "adminPaiement": "Paiement", + "adminManagePaiementStructures": "Gérer les structures du module de paiement", + "adminManageUsersAssociationMemberships": "Gérer les adhésions des utilisateurs", + "adminAssociationMembershipsManagement": "Gestion des adhésions", + "adminChooseGroupManager": "Groupe gestionnaire de l'adhésion", + "adminSelectManager": "Sélectionner un gestionnaire", + "adminInviteUsers": "Inviter des utilisateurs", + "adminImportList": "Importer une liste", + "adminInvitedUsers": "Utilisateurs invités", + "adminFailedToInviteUsers": "Échec de l'invitation des utilisateurs", + "adminDeleteUsers": "Supprimer des utilisateurs", + "adminAdmin": "Admin", + "adminAdverts": "Annonces", + "adminAnnouncers": "Annonceurs", + "adminManageAnnouncers": "Gérer les annonceurs", + "advertAdd": "Ajouter", + "advertAddedAdvert": "Annonce publiée", + "advertAddedAnnouncer": "Annonceur ajouté", + "advertAddingError": "Erreur lors de l'ajout", + "advertAdmin": "Admin", + "advertAdvert": "Annonce", + "advertChoosingAnnouncer": "Veuillez choisir un annonceur", + "advertChoosingPoster": "Veuillez choisir une image", + "advertContent": "Contenu", + "advertDeleteAdvert": "Supprimer l'annonce", + "advertDeleteAnnouncer": "Supprimer l'annonceur ?", + "advertDeleting": "Suppression", + "advertEdit": "Modifier", + "advertEditedAdvert": "Annonce modifiée", + "advertEditingError": "Erreur lors de la modification", + "advertGroupAdvert": "Groupe", + "advertIncorrectOrMissingFields": "Champs incorrects ou manquants", + "advertInvalidNumber": "Veuillez entrer un nombre", + "advertManagement": "Gestion", + "advertModifyAnnouncingGroup": "Modifier un groupe d'annonce", + "advertNoMoreAnnouncer": "Aucun annonceur n'est disponible", + "advertNoValue": "Veuillez entrer une valeur", + "advertPositiveNumber": "Veuillez entrer un nombre positif", + "advertRemovedAnnouncer": "Annonceur supprimé", + "advertRemovingError": "Erreur lors de la suppression", + "advertTags": "Tags", + "advertTitle": "Titre", + "advertMonthJan": "Janv", + "advertMonthFeb": "Févr.", + "advertMonthMar": "Mars", + "advertMonthApr": "Avr.", + "advertMonthMay": "Mai", + "advertMonthJun": "Juin", + "advertMonthJul": "Juill.", + "advertMonthAug": "Août", + "advertMonthSep": "Sept.", + "advertMonthOct": "Oct.", + "advertMonthNov": "Nov.", + "advertMonthDec": "Déc.", + "amapAccounts": "Comptes", + "amapAdd": "Ajouter", + "amapAddDelivery": "Ajouter une livraison", + "amapAddedCommand": "Commande ajoutée", + "amapAddedOrder": "Commande ajoutée", + "amapAddedProduct": "Produit ajouté", + "amapAddedUser": "Utilisateur ajouté", + "amapAddProduct": "Ajouter un produit", + "amapAddUser": "Ajouter un utilisateur", + "amapAddingACommand": "Ajouter une commande", + "amapAddingCommand": "Ajouter la commande", + "amapAddingError": "Erreur lors de l'ajout", + "amapAddingProduct": "Ajouter un produit", + "amapAddOrder": "Ajouter une commande", + "amapAdmin": "Admin", + "amapAlreadyExistCommand": "Il existe déjà une commande à cette date", + "amapAmap": "Amap", + "amapAmount": "Solde", + "amapArchive": "Archiver", + "amapArchiveDelivery": "Archiver", + "amapArchivingDelivery": "Archivage de la livraison", + "amapCategory": "Catégorie", + "amapCloseDelivery": "Verrouiller", + "amapCommandDate": "Date de la commande", + "amapCommandProducts": "Produits de la commande", + "amapConfirm": "Confirmer", + "amapContact": "Contacts associatifs ", + "amapCreateCategory": "Créer une catégorie", + "amapDelete": "Supprimer", + "amapDeleteDelivery": "Supprimer la livraison ?", + "amapDeleteDeliveryDescription": "Voulez-vous vraiment supprimer cette livraison ?", + "amapDeletedDelivery": "Livraison supprimée", + "amapDeletedOrder": "Commande supprimée", + "amapDeletedProduct": "Produit supprimé", + "amapDeleteProduct": "Supprimer le produit ?", + "amapDeleteProductDescription": "Voulez-vous vraiment supprimer ce produit ?", + "amapDeleting": "Suppression", + "amapDeletingDelivery": "Supprimer la livraison ?", + "amapDeletingError": "Erreur lors de la suppression", + "amapDeletingOrder": "Supprimer la commande ?", + "amapDeletingProduct": "Supprimer le produit ?", + "amapDeliver": "Livraison teminée ?", + "amapDeliveries": "Livraisons", + "amapDeliveringDelivery": "Toutes les commandes sont livrées ?", + "amapDelivery": "Livraison", + "amapDeliveryArchived": "Livraison archivée", + "amapDeliveryDate": "Date de livraison", + "amapDeliveryDelivered": "Livraison effectuée", + "amapDeliveryHistory": "Historique des livraisons", + "amapDeliveryList": "Liste des livraisons", + "amapDeliveryLocked": "Livraison verrouillée", + "amapDeliveryOn": "Livraison le", + "amapDeliveryOpened": "Livraison ouverte", + "amapDeliveryNotArchived": "Livraison non archivée", + "amapDeliveryNotLocked": "Livraison non verrouillée", + "amapDeliveryNotDelivered": "Livraison non effectuée", + "amapDeliveryNotOpened": "Livraison non ouverte", + "amapEditDelivery": "Modifier la livraison", + "amapEditedCommand": "Commande modifiée", + "amapEditingError": "Erreur lors de la modification", + "amapEditProduct": "Modifier le produit", + "amapEndingDelivery": "Fin de la livraison", + "amapError": "Erreur", + "amapErrorLink": "Erreur lors de l'ouverture du lien", + "amapErrorLoadingUser": "Erreur lors du chargement des utilisateurs", + "amapEvening": "Soir", + "amapExpectingNumber": "Veuillez entrer un nombre", + "amapFillField": "Veuillez remplir ce champ", + "amapHandlingAccount": "Gérer les comptes", + "amapLoading": "Chargement...", + "amapLoadingError": "Erreur lors du chargement", + "amapLock": "Verrouiller", + "amapLocked": "Verrouillée", + "amapLockedDelivery": "Livraison verrouillée", + "amapLockedOrder": "Commande verrouillée", + "amapLooking": "Rechercher", + "amapLockingDelivery": "Verrouiller la livraison ?", + "amapMidDay": "Midi", + "amapMyOrders": "Mes commandes", + "amapName": "Nom", + "amapNextStep": "Étape suivante", + "amapNoProduct": "Pas de produit", + "amapNoCurrentOrder": "Pas de commande en cours", + "amapNoMoney": "Pas assez d'argent", + "amapNoOpennedDelivery": "Pas de livraison ouverte", + "amapNoOrder": "Pas de commande", + "amapNoSelectedDelivery": "Pas de livraison sélectionnée", + "amapNotEnoughMoney": "Pas assez d'argent", + "amapNotPlannedDelivery": "Pas de livraison planifiée", + "amapOneOrder": "commande", + "amapOpenDelivery": "Ouvrir", + "amapOpened": "Ouverte", + "amapOpenningDelivery": "Ouvrir la livraison ?", + "amapOrder": "Commander", + "amapOrders": "Commandes", + "amapPickChooseCategory": "Veuillez entrer une valeur ou choisir une catégorie existante", + "amapPickDeliveryMoment": "Choisissez un moment de livraison", + "amapPresentation": "Présentation", + "amapPresentation1": "L'AMAP (association pour le maintien d'une agriculture paysanne) est un service proposé par l'association Planet&Co de l'ECL. Vous pouvez ainsi recevoir des produits (paniers de fruits et légumes, jus, confitures...) directement sur le campus !\n\nLes commandes doivent être passées avant le vendredi 21h et sont livrées sur le campus le mardi de 13h à 13h45 (ou de 18h15 à 18h30 si vous ne pouvez pas passer le midi) dans le hall du M16.\n\nVous ne pouvez commander que si votre solde le permet. Vous pouvez recharger votre solde via la collecte Lydia ou bien avec un chèque que vous pouvez nous transmettre lors des permanences.\n\nLien vers la collecte Lydia pour le rechargement : ", + "amapPresentation2": "\n\nN'hésitez pas à nous contacter en cas de problème !", + "amapPrice": "Prix", + "amapProduct": "produit", + "amapProducts": "Produits", + "amapProductInDelivery": "Produit dans une livraison non terminée", + "amapQuantity": "Quantité", + "amapRequiredDate": "La date est requise", + "amapSeeMore": "Voir plus", + "amapThe": "Le", + "amapUnlock": "Dévérouiller", + "amapUnlockedDelivery": "Livraison dévérouillée", + "amapUnlockingDelivery": "Dévérouiller la livraison ?", + "amapUpdate": "Modifier", + "amapUpdatedAmount": "Solde modifié", + "amapUpdatedOrder": "Commande modifiée", + "amapUpdatedProduct": "Produit modifié", + "amapUpdatingError": "Echec de la modification", + "amapUsersNotFound": "Aucun utilisateur trouvé", + "amapWaiting": "En attente", + "bookingAdd": "Ajouter", + "bookingAddBookingPage": "Demande", + "bookingAddRoom": "Ajouter une salle", + "bookingAddBooking": "Ajouter une réservation", + "bookingAddedBooking": "Demande ajoutée", + "bookingAddedRoom": "Salle ajoutée", + "bookingAddedManager": "Gestionnaire ajouté", + "bookingAddingError": "Erreur lors de l'ajout", + "bookingAddManager": "Ajouter un gestionnaire", + "bookingAdminPage": "Administrateur", + "bookingAllDay": "Toute la journée", + "bookingBookedFor": "Réservé pour", + "bookingBooking": "Réservation", + "bookingBookingCreated": "Réservation créée", + "bookingBookingDemand": "Demande de réservation", + "bookingBookingNote": "Note de la réservation", + "bookingBookingPage": "Réservation", + "bookingBookingReason": "Motif de la réservation", + "bookingBy": "par", + "bookingConfirm": "Confirmer", + "bookingConfirmation": "Confirmation", + "bookingConfirmBooking": "Confirmer la réservation ?", + "bookingConfirmed": "Validée", + "bookingDates": "Dates", + "bookingDecline": "Refuser", + "bookingDeclineBooking": "Refuser la réservation ?", + "bookingDeclined": "Refusée", + "bookingDelete": "Supprimer", + "bookingDeleting": "Suppression", + "bookingDeleteBooking": "Suppression", + "bookingDeleteBookingConfirmation": "Êtes-vous sûr de vouloir supprimer cette réservation ?", + "bookingDeletedBooking": "Réservation supprimée", + "bookingDeletedRoom": "Salle supprimée", + "bookingDeletedManager": "Gestionnaire supprimé", + "bookingDeleteRoomConfirmation": "Êtes-vous sûr de vouloir supprimer cette salle ?\n\nLa salle ne doit avoir aucune réservation en cours ou à venir pour être supprimée", + "bookingDeleteManagerConfirmation": "Êtes-vous sûr de vouloir supprimer ce gestionnaire ?\n\nLe gestionnaire ne doit être associé à aucune salle pour pouvoir être supprimé", + "bookingDeletingBooking": "Supprimer la réservation ?", + "bookingDeletingError": "Erreur lors de la suppression", + "bookingDeletingRoom": "Supprimer la salle ?", + "bookingEdit": "Modifier", + "bookingEditBooking": "Modifier une réservation", + "bookingEditionError": "Erreur lors de la modification", + "bookingEditedBooking": "Réservation modifiée", + "bookingEditedRoom": "Salle modifiée", + "bookingEditedManager": "Gestionnaire modifié", + "bookingEditManager": "Modifier ou supprimer un gestionnaire", + "bookingEditRoom": "Modifier ou supprimer une salle", + "bookingEndDate": "Date de fin", + "bookingEndHour": "Heure de fin", + "bookingEntity": "Pour qui ?", + "bookingError": "Erreur", + "bookingEventEvery": "Tous les", + "bookingHistoryPage": "Historique", + "bookingIncorrectOrMissingFields": "Champs incorrects ou manquants", + "bookingInterval": "Intervalle", + "bookingInvalidIntervalError": "Intervalle invalide", + "bookingInvalidDates": "Dates invalides", + "bookingInvalidRoom": "Salle invalide", + "bookingKeysRequested": "Clés demandées", + "bookingManagement": "Gestion", + "bookingManager": "Gestionnaire", + "bookingManagerName": "Nom du gestionnaire", + "bookingMultipleDay": "Plusieurs jours", + "bookingMyBookings": "Mes réservations", + "bookingNecessaryKey": "Clé nécessaire", + "bookingNext": "Suivant", + "bookingNo": "Non", + "bookingNoCurrentBooking": "Pas de réservation en cours", + "bookingNoDateError": "Veuillez choisir une date", + "bookingNoAppointmentInReccurence": "Aucun créneau existe avec ces paramètres de récurrence", + "bookingNoDaySelected": "Aucun jour sélectionné", + "bookingNoDescriptionError": "Veuillez entrer une description", + "bookingNoKeys": "Aucune clé", + "bookingNoNoteError": "Veuillez entrer une note", + "bookingNoPhoneRegistered": "Numéro non renseigné", + "bookingNoReasonError": "Veuillez entrer un motif", + "bookingNoRoomFoundError": "Aucune salle enregistrée", + "bookingNoRoomFound": "Aucune salle trouvée", + "bookingNote": "Note", + "bookingOther": "Autre", + "bookingPending": "En attente", + "bookingPrevious": "Précédent", + "bookingReason": "Motif", + "bookingRecurrence": "Récurrence", + "bookingRecurrenceDays": "Jours de récurrence", + "bookingRecurrenceEndDate": "Date de fin de récurrence", + "bookingRecurrent": "Récurrent", + "bookingRegisteredRooms": "Salles enregistrées", + "bookingRoom": "Salle", + "bookingRoomName": "Nom de la salle", + "bookingStartDate": "Date de début", + "bookingStartHour": "Heure de début", + "bookingWeeks": "Semaines", + "bookingYes": "Oui", + "bookingWeekDayMon": "Lundi", + "bookingWeekDayTue": "Mardi", + "bookingWeekDayWed": "Mercredi", + "bookingWeekDayThu": "Jeudi", + "bookingWeekDayFri": "Vendredi", + "bookingWeekDaySat": "Samedi", + "bookingWeekDaySun": "Dimanche", + "cinemaAdd": "Ajouter", + "cinemaAddedSession": "Séance ajoutée", + "cinemaAddingError": "Erreur lors de l'ajout", + "cinemaAddSession": "Ajouter une séance", + "cinemaCinema": "Cinéma", + "cinemaDeleteSession": "Supprimer la séance ?", + "cinemaDeleting": "Suppression", + "cinemaDuration": "Durée", + "cinemaEdit": "Modifier", + "cinemaEditedSession": "Séance modifiée", + "cinemaEditingError": "Erreur lors de la modification", + "cinemaEditSession": "Modifier la séance", + "cinemaEmptyUrl": "Veuillez entrer une URL", + "cinemaImportFromTMDB": "Importer depuis TMDB", + "cinemaIncomingSession": "A l'affiche", + "cinemaIncorrectOrMissingFields": "Champs incorrects ou manquants", + "cinemaInvalidUrl": "URL invalide", + "cinemaGenre": "Genre", + "cinemaName": "Nom", + "cinemaNoDateError": "Veuillez entrer une date", + "cinemaNoDuration": "Veuillez entrer une durée", + "cinemaNoOverview": "Aucun synopsis", + "cinemaNoPoster": "Aucune affiche", + "cinemaNoSession": "Aucune séance", + "cinemaOverview": "Synopsis", + "cinemaPosterUrl": "URL de l'affiche", + "cinemaSessionDate": "Jour de la séance", + "cinemaStartHour": "Heure de début", + "cinemaTagline": "Slogan", + "cinemaThe": "Le", + "drawerAdmin": "Administration", + "drawerAndroidAppLink": "https://play.google.com/store/apps/details?id=fr.myecl.titan", + "drawerCopied": "Copié !", + "drawerDownloadAppOnMobileDevice": "Ce site est la version Web de l'application MyECL. Nous vous invitons à télécharger l'application. N'utilisez ce site qu'en cas de problème avec l'application.\n", + "drawerIosAppLink": "https://apps.apple.com/fr/app/myecl/id6444443430", + "drawerLoginOut": "Voulez-vous vous déconnecter ?", + "drawerLogOut": "Déconnexion", + "drawerOr": " ou ", + "drawerSettings": "Paramètres", + "eventAdd": "Ajouter", + "eventAddEvent": "Ajouter un événement", + "eventAddedEvent": "Événement ajouté", + "eventAddingError": "Erreur lors de l'ajout", + "eventAllDay": "Toute la journée", + "eventConfirm": "Confirmer", + "eventConfirmEvent": "Confirmer l'événement ?", + "eventConfirmation": "Confirmation", + "eventConfirmed": "Confirmé", + "eventDates": "Dates", + "eventDecline": "Refuser", + "eventDeclineEvent": "Refuser l'événement ?", + "eventDeclined": "Refusé", + "eventDelete": "Supprimer", + "eventDeletedEvent": "Événement supprimé", + "eventDeleting": "Suppression", + "eventDeletingError": "Erreur lors de la suppression", + "eventDeletingEvent": "Supprimer l'événement ?", + "eventDescription": "Description", + "eventEdit": "Modifier", + "eventEditEvent": "Modifier un événement", + "eventEditedEvent": "Événement modifié", + "eventEditingError": "Erreur lors de la modification", + "eventEndDate": "Date de fin", + "eventEndHour": "Heure de fin", + "eventError": "Erreur", + "eventEventList": "Liste des événements", + "eventEventType": "Type d'événement", + "eventEvery": "Tous les", + "eventHistory": "Historique", + "eventIncorrectOrMissingFields": "Certains champs sont incorrects ou manquants", + "eventInterval": "Intervalle", + "eventInvalidDates": "La date de fin doit être après la date de début", + "eventInvalidIntervalError": "Veuillez entrer un intervalle valide", + "eventLocation": "Lieu", + "eventMyEvents": "Mes événements", + "eventName": "Nom", + "eventNext": "Suivant", + "eventNo": "Non", + "eventNoCurrentEvent": "Aucun événement en cours", + "eventNoDateError": "Veuillez entrer une date", + "eventNoDaySelected": "Aucun jour sélectionné", + "eventNoDescriptionError": "Veuillez entrer une description", + "eventNoEvent": "Aucun événement", + "eventNoNameError": "Veuillez entrer un nom", + "eventNoOrganizerError": "Veuillez entrer un organisateur", + "eventNoPlaceError": "Veuillez entrer un lieu", + "eventNoPhoneRegistered": "Numéro non renseigné", + "eventNoRuleError": "Veuillez entrer une règle de récurrence", + "eventOrganizer": "Organisateur", + "eventOther": "Autre", + "eventPending": "En attente", + "eventPrevious": "Précédent", + "eventRecurrence": "Récurrence", + "eventRecurrenceDays": "Jours de récurrence", + "eventRecurrenceEndDate": "Date de fin de la récurrence", + "eventRecurrenceRule": "Règle de récurrence", + "eventRoom": "Salle", + "eventStartDate": "Date de début", + "eventStartHour": "Heure de début", + "eventTitle": "Événements", + "eventYes": "Oui", + "eventEventEvery": "Toutes les", + "eventWeeks": "semaines", + "eventDayMon": "Lundi", + "eventDayTue": "Mardi", + "eventDayWed": "Mercredi", + "eventDayThu": "Jeudi", + "eventDayFri": "Vendredi", + "eventDaySat": "Samedi", + "eventDaySun": "Dimanche", + "homeCalendar": "Calendrier", + "homeEventOf": "Évènements du", + "homeIncomingEvents": "Évènements à venir", + "homeLastInfos": "Dernières annonces", + "homeNoEvents": "Aucun évènement", + "homeTranslateDayShortMon": "Lun", + "homeTranslateDayShortTue": "Mar", + "homeTranslateDayShortWed": "Mer", + "homeTranslateDayShortThu": "Jeu", + "homeTranslateDayShortFri": "Ven", + "homeTranslateDayShortSat": "Sam", + "homeTranslateDayShortSun": "Dim", + "loanAdd": "Ajouter", + "loanAddLoan": "Ajouter un prêt", + "loanAddObject": "Ajouter un objet", + "loanAddedLoan": "Prêt ajouté", + "loanAddedObject": "Objet ajouté", + "loanAddedRoom": "Salle ajoutée", + "loanAddingError": "Erreur lors de l'ajout", + "loanAdmin": "Administrateur", + "loanAvailable": "Disponible", + "loanAvailableMultiple": "Disponibles", + "loanBorrowed": "Emprunté", + "loanBorrowedMultiple": "Empruntés", + "loanAnd": "et", + "loanAssociation": "Association", + "loanAvailableItems": "Objets disponibles", + "loanBeginDate": "Date du début du prêt", + "loanBorrower": "Emprunteur", + "loanCaution": "Caution", + "loanCancel": "Annuler", + "loanConfirm": "Confirmer", + "loanConfirmation": "Confirmation", + "loanDates": "Dates", + "loanDays": "Jours", + "loanDelay": "Délai de la prolongation", + "loanDelete": "Supprimer", + "loanDeletingLoan": "Supprimer le prêt ?", + "loanDeletedItem": "Objet supprimé", + "loanDeletedLoan": "Prêt supprimé", + "loanDeleting": "Suppression", + "loanDeletingError": "Erreur lors de la suppression", + "loanDeletingItem": "Supprimer l'objet ?", + "loanDuration": "Durée", + "loanEdit": "Modifier", + "loanEditItem": "Modifier l'objet", + "loanEditLoan": "Modifier le prêt", + "loanEditedRoom": "Salle modifiée", + "loanEndDate": "Date de fin du prêt", + "loanEnded": "Terminé", + "loanEnterDate": "Veuillez entrer une date", + "loanExtendedLoan": "Prêt prolongé", + "loanExtendingError": "Erreur lors de la prolongation", + "loanHistory": "Historique", + "loanIncorrectOrMissingFields": "Des champs sont manquants ou incorrects", + "loanInvalidNumber": "Veuillez entrer un nombre", + "loanInvalidDates": "Les dates ne sont pas valides", + "loanItem": "Objet", + "loanItems": "Objets", + "loanItemHandling": "Gestion des objets", + "loanItemSelected": "objet sélectionné", + "loanItemsSelected": "objets sélectionnés", + "loanLendingDuration": "Durée possible du prêt", + "loanLoan": "Prêt", + "loanLoanHandling": "Gestion des prêts", + "loanLooking": "Rechercher", + "loanName": "Nom", + "loanNext": "Suivant", + "loanNo": "Non", + "loanNoAssociationsFounded": "Aucune association trouvée", + "loanNoAvailableItems": "Aucun objet disponible", + "loanNoBorrower": "Aucun emprunteur", + "loanNoItems": "Aucun objet", + "loanNoItemSelected": "Aucun objet sélectionné", + "loanNoLoan": "Aucun prêt", + "loanNoReturnedDate": "Pas de date de retour", + "loanQuantity": "Quantité", + "loanNone": "Aucun", + "loanNote": "Note", + "loanNoValue": "Veuillez entrer une valeur", + "loanOnGoing": "En cours", + "loanOnGoingLoan": "Prêt en cours", + "loanOthers": "autres", + "loanPaidCaution": "Caution payée", + "loanPositiveNumber": "Veuillez entrer un nombre positif", + "loanPrevious": "Précédent", + "loanReturned": "Rendu", + "loanReturnedLoan": "Prêt rendu", + "loanReturningError": "Erreur lors du retour", + "loanReturningLoan": "Retour", + "loanReturnLoan": "Rendre le prêt ?", + "loanReturnLoanDescription": "Voulez-vous rendre ce prêt ?", + "loanToReturn": "A rendre", + "loanUnavailable": "Indisponible", + "loanUpdate": "Modifier", + "loanUpdatedItem": "Objet modifié", + "loanUpdatedLoan": "Prêt modifié", + "loanUpdatingError": "Erreur lors de la modification", + "loanYes": "Oui", + "loginAccountActivated": "Compte activé", + "loginAccountNotActivated": "Compte non activé", + "loginActivationCode": "Code d'activation", + "loginBirthday": "Date de naissance", + "loginCanBeEmpty": "Ce champ peut être vide", + "loginConfirmPassword": "Confirmer le mot de passe", + "loginCreate": "Créer", + "loginCreateAccount": "Créer un compte", + "loginCreateAccountTitle": "Créer un\ncompte", + "loginEmail": "Email", + "loginEmailEmpty": "Veuillez entrer une adresse mail", + "loginEmailInvalid": "Veuillez entrer une adresse mail de centrale.\nSi vous n'en possédez pas, veuillez contacter Éclair", + "loginEmptyFieldError": "Ce champ ne peut pas être vide", + "loginEndActivation": "Finaliser l'activation", + "loginEndResetPassword": "Finaliser la \nréinitialisation", + "loginErrorResetPassword": "Erreur lors de la réinitialisation", + "loginExpectingDate": "Une date est attendue", + "loginFillAllFields": "Veuillez remplir tous les champs", + "loginFirstname": "Prénom", + "loginFloor": "Étage", + "loginForgetPassword": "Mot de passe\noublié", + "loginForgotPassword": "Mot de passe oublié ?", + "loginInvalidToken": "Code d'activation invalide", + "loginLoginFailed": "Échec de la connexion", + "loginMailSendingError": "Erreur lors de la création du compte", + "loginMustBeIntError": "Ce champ doit être un entier", + "loginName": "Nom", + "loginNewPassword": "Nouveau mot de passe", + "loginPassword": "Mot de passe", + "loginPasswordLengthError": "Le mot de passe doit faire au moins 6 caractères", + "loginPasswordUppercaseError": "Le mot de passe doit contenir au moins une majuscule", + "loginPasswordLowercaseError": "Le mot de passe doit contenir au moins une minucule", + "loginPasswordNumberError": "Le mot de passe doit contenir au moins un chiffre", + "loginPasswordSpecialCaracterError": "Le mot de passe doit contenir au moins un caractère spécial", + "loginPasswordMustMatch": "Les mots de passe doivent correspondre", + "loginPasswordStrengthVeryWeak": "Très faible", + "loginPasswordStrengthWeak": "Faible", + "loginPasswordStrengthMedium": "Moyen", + "loginPasswordStrengthStrong": "Fort", + "loginPasswordStrengthVeryStrong": "Très fort", + "loginPhone": "Téléphone", + "loginPromo": "Promo entrante (ex : 2023)", + "loginSendedMail": "Mail de confirmation envoyé", + "loginSendedResetMail": "Mail de réinitialisation envoyé", + "loginSignIn": "Se connecter", + "loginRegister": "S'inscrire", + "loginRecievedMail": "J'ai reçu le mail", + "loginRecover": "Réinitialiser", + "loginResetedPassword": "Mot de passe réinitialisé", + "loginResetPasswordTitle": "Réinitialiser\nle mot de \npasse", + "loginNickname": "Surnom", + "loginWelcomeBack": "Bienvenue", + "loginAppName": "MyECL", + "othersCheckInternetConnection": "Veuillez vérifier votre connexion internet", + "othersRetry": "Réessayer", + "othersTooOldVersion": "Votre version de l'application est trop ancienne.\n\nVeuillez mettre à jour l'application.", + "othersUnableToConnectToServer": "Impossible de se connecter au serveur", + "othersVersion": "Version", + "othersNoModule": "Aucun module disponible, veuillez réessayer ultérieurement 😢😢", + "othersAdmin": "Admin", + "othersError": "Une erreur est survenue", + "othersNoValue": "Veuillez entrer une valeur", + "othersInvalidNumber": "Veuillez entrer un nombre", + "othersNoDateError": "Veuillez entrer une date", + "othersImageSizeTooBig": "La taille de l'image ne doit pas dépasser 4 Mio", + "othersImageError": "Erreur lors de l'ajout de l'image", + "phAddNewJournal": "Ajouter un nouveau journal", + "phNameField": "Nom : ", + "phDateField": "Date : ", + "phDelete": "Voulez-vous vraiment supprimer ce journal ?", + "phIrreversibleAction": "Cette action est irréversible", + "phToHeavyFile": "Fichier trop volumineux", + "phAddPdfFile": "Ajouter un fichier PDF", + "phEditPdfFile": "Modifier le fichier PDF", + "phPhName": "Nom du PH", + "phDate": "Date", + "phAdded": "Ajouté", + "phEdited": "Modifié", + "phAddingFileError": "Erreur d'ajout", + "phMissingInformatonsOrPdf": "Informations manquantes ou fichier PDF manquant", + "phAdd": "Ajouter", + "phEdit": "Modifier", + "phSeePreviousJournal": "Voir les anciens journaux", + "phNoJournalInDatabase": "Pas encore de PH dans la base de donnée", + "phSuccesDowloading": "Téléchargé avec succès", + "phonebookActiveMandate": "Mandat actif :", + "phonebookAdd": "Ajouter", + "phonebookAddAssociation": "Ajouter une association", + "phonebookAddedAssociation": "Association ajoutée", + "phonebookAddedMember": "Membre ajouté", + "phonebookAddingError": "Erreur lors de l'ajout", + "phonebookAddMember": "Ajouter un membre", + "phonebookAddRole": "Ajouter un rôle", + "phonebookAdmin": "Admin", + "phonebookAdminPage": "Page Administrateur", + "phonebookAll": "Toutes", + "phonebookApparentName": "Nom public du rôle :", + "phonebookAssociation": "Association :", + "phonebookAssociationDetail": "Détail de l'association :", + "phonebookAssociationKind": "Type d'association :", + "phonebookAssociationPure": "Association", + "phonebookAssociationPureSearch": " Association", + "phonebookAssociations": "Associations :", + "phonebookCancel": "Annuler", + "phonebookChangeMandate": "Passer au mandat ", + "phonebookChangeMandateConfirm": "Êtes-vous sûr de vouloir changer tout le mandat ?\nCette action est irréversible !", + "phonebookCopied": "Copié dans le presse-papier", + "phonebookDeactivateAssociation": "Êtes-vous sûr de vouloir désactiver cette association ?\nCette action est irréversible !", + "phonebookDeactivatedAssociation": "Association désactivée", + "phonebookDeactivatedAssociationWarning": "Attention, cette association est désactivée, vous ne pouvez pas la modifier", + "phonebookDeactivating": "Désactiver l'association ?", + "phonebookDeactivatingError": "Erreur lors de la désactivation", + "phonebookDetail": "Détail :", + "phonebookDeleteAssociation": "Supprimer l'association ?\nCela va effacer tout l'historique de l'association", + "phonebookDeletedAssociation": "Association supprimée", + "phonebookDeletedMember": "Membre supprimé", + "phonebookDeleting": "Suppression", + "phonebookDeletingError": "Erreur lors de la suppression", + "phonebookDescription": "Description", + "phonebookEdit": "Modifier", + "phonebookEditMembership": "Modifier le rôle", + "phonebookEmail": "Email :", + "phonebookEmailCopied": "Email copié dans le presse-papier", + "phonebookEmptyApparentName": "Veuillez entrer un nom de role", + "phonebookEmptyFieldError": "Un champ n'est pas rempli", + "phonebookEmptyKindError": "Veuillez choisir un type d'association", + "phonebookEmptyMember": "Aucun membre sélectionné", + "phonebookErrorAssociationLoading": "Erreur lors du chargement de l'association", + "phonebookErrorAssociationNameEmpty": "Veuillez entrer un nom d'association", + "phonebookErrorAssociationPicture": "Erreur lors de la modification de la photo d'association", + "phonebookErrorKindsLoading": "Erreur lors du chargement des types d'association", + "phonebookErrorLoadAssociationList": "Erreur lors du chargement de la liste des associations", + "phonebookErrorLoadAssociationMember": "Erreur lors du chargement des membres de l'association", + "phonebookErrorLoadAssociationPicture": "Erreur lors du chargement de la photo d'association", + "phonebookErrorLoadProfilePicture": "Erreur", + "phonebookErrorRoleTagsLoading": "Erreur lors du chargement des tags de rôle", + "phonebookExistingMembership": "Ce membre est déjà dans le mandat actuel", + "phonebookFirstname": "Prénom :", + "phonebookGroups": "Groupes associés :", + "phonebookMandateChangingError": "Erreur lors du changement de mandat", + "phonebookMember": "Membre", + "phonebookMemberReordered": "Membre réordonné", + "phonebookMembers": "Membres", + "phonebookMembershipAssociationError": "Veuillez choisir une association", + "phonebookMembershipRole": "Rôle :", + "phonebookMembershipRoleError": "Veuillez choisir un rôle", + "phonebookName": "Nom :", + "phonebookNameCopied": "Nom et prénom copié dans le presse-papier", + "phonebookNamePure": "Nom", + "phonebookNewMandate": "Nouveau mandat", + "phonebookNewMandateConfirmed": "Mandat changé", + "phonebookNickname": "Surnom :", + "phonebookNicknameCopied": "Surnom copié dans le presse-papier", + "phonebookNoAssociationFound": "Aucune association trouvée", + "phonebookNoMember": "Aucun membre", + "phonebookNoMemberRole": "Aucun role trouvé", + "phonebookPhone": "Téléphone :", + "phonebookPhonebook": "Annuaire", + "phonebookPhonebookSearch": "Rechercher", + "phonebookPhonebookSearchAssociation": "Association", + "phonebookPhonebookSearchField": "Rechercher :", + "phonebookPhonebookSearchName": "Nom/Prénom/Surnom", + "phonebookPhonebookSearchRole": "Poste", + "phonebookPresidentRoleTag": "Prez'", + "phonebookPromoNotGiven": "Promo non renseignée", + "phonebookPromotion": "Promotion :", + "phonebookReorderingError": "Erreur lors du réordonnement", + "phonebookResearch": "Rechercher", + "phonebookRolePure": "Rôle", + "phonebookTooHeavyAssociationPicture": "L'image est trop lourde (max 4Mo)", + "phonebookUpdateGroups": "Mettre à jour les groupes", + "phonebookUpdatedAssociation": "Association modifiée", + "phonebookUpdatedAssociationPicture": "La photo d'association a été changée", + "phonebookUpdatedGroups": "Groupes mis à jour", + "phonebookUpdatedMember": "Membre modifié", + "phonebookUpdatingError": "Erreur lors de la modification", + "phonebookValidation": "Valider", + "purchasesPurchases": "Achats", + "purchasesResearch": "Rechercher", + "purchasesNoPurchasesFound": "Aucun achat trouvé", + "purchasesNoTickets": "Aucun ticket", + "purchasesTicketsError": "Erreur lors du chargement des tickets", + "purchasesPurchasesError": "Erreur lors du chargement des achats", + "purchasesNoPurchases": "Aucun achat", + "purchasesTimes": "fois", + "purchasesAlreadyUsed": "Déjà utilisé", + "purchasesNotPaid": "Non validé", + "purchasesPleaseSelectProduct": "Veuillez sélectionner un produit", + "purchasesProducts": "Produits", + "purchasesCancel": "Annuler", + "purchasesValidate": "Valider", + "purchasesLeftScan": "Scans restants", + "purchasesTag": "Tag", + "purchasesHistory": "Historique", + "purchasesPleaseSelectSeller": "Veuillez sélectionner un vendeur", + "purchasesNoTagGiven": "Attention, aucun tag n'a été entré", + "purchasesTickets": "Tickets", + "purchasesNoScannableProducts": "Aucun produit scannable", + "purchasesLoading": "En attente de scan", + "purchasesScan": "Scanner", + "raffleRaffle": "Tombola", + "rafflePrize": "Lot", + "rafflePrizes": "Lots", + "raffleActualRaffles": "Tombola en cours", + "rafflePastRaffles": "Tombola passés", + "raffleYourTickets": "Tous vos tickets", + "raffleCreateMenu": "Menu de Création", + "raffleNextRaffles": "Prochaines tombolas", + "raffleNoTicket": "Vous n'avez pas de ticket", + "raffleSeeRaffleDetail": "Voir lots/tickets", + "raffleActualPrize": "Lots actuels", + "raffleMajorPrize": "Lot Majeurs", + "raffleTakeTickets": "Prendre vos tickets", + "raffleNoTicketBuyable": "Vous ne pouvez pas achetez de billets pour l'instant", + "raffleNoCurrentPrize": "Il n'y a aucun lots actuellement", + "raffleModifTombola": "Vous pouvez modifiez vos tombolas ou en créer de nouvelles, toute décision doit ensuite être prise par les admins", + "raffleCreateYourRaffle": "Votre menu de création de tombolas", + "rafflePossiblePrice": "Prix possible", + "raffleInformation": "Information et Statistiques", + "raffleAccounts": "Comptes", + "raffleAdd": "Ajouter", + "raffleUpdatedAmount": "Montant mis à jour", + "raffleUpdatingError": "Erreur lors de la mise à jour", + "raffleDeletedPrize": "Lot supprimé", + "raffleDeletingError": "Erreur lors de la suppression", + "raffleQuantity": "Quantité", + "raffleClose": "Fermer", + "raffleOpen": "Ouvrir", + "raffleAddTypeTicketSimple": "Ajouter", + "raffleAddingError": "Erreur lors de l'ajout", + "raffleEditTypeTicketSimple": "Modifier", + "raffleFillField": "Le champ ne peut pas être vide", + "raffleWaiting": "Chargement", + "raffleEditingError": "Erreur lors de la modification", + "raffleAddedTicket": "Ticket ajouté", + "raffleEditedTicket": "Ticket modifié", + "raffleAlreadyExistTicket": "Le ticket existe déjà", + "raffleNumberExpected": "Un entier est attendu", + "raffleDeletedTicket": "Ticket supprimé", + "raffleAddPrize": "Ajouter", + "raffleEditPrize": "Modifier", + "raffleOpenRaffle": "Ouvrir la tombola", + "raffleCloseRaffle": "Fermer la tombola", + "raffleOpenRaffleDescription": "Vous allez ouvrir la tombola, les utilisateurs pourront acheter des tickets. Vous ne pourrez plus modifier la tombola. Êtes-vous sûr de vouloir continuer ?", + "raffleCloseRaffleDescription": "Vous allez fermer la tombola, les utilisateurs ne pourront plus acheter de tickets. Êtes-vous sûr de vouloir continuer ?", + "raffleNoCurrentRaffle": "Il n'y a aucune tombola en cours", + "raffleBoughtTicket": "Ticket acheté", + "raffleDrawingError": "Erreur lors du tirage", + "raffleInvalidPrice": "Le prix doit être supérieur à 0", + "raffleMustBePositive": "Le nombre doit être strictement positif", + "raffleDraw": "Tirer", + "raffleDrawn": "Tiré", + "raffleError": "Erreur", + "raffleGathered": "Récolté", + "raffleTickets": "Tickets", + "raffleTicket": "ticket", + "raffleWinner": "Gagnant", + "raffleNoPrize": "Aucun lot", + "raffleDeletePrize": "Supprimer le lot", + "raffleDeletePrizeDescription": "Vous allez supprimer le lot, êtes-vous sûr de vouloir continuer ?", + "raffleDrawing": "Tirage", + "raffleDrawingDescription": "Tirer le gagnant du lot ?", + "raffleDeleteTicket": "Supprimer le ticket", + "raffleDeleteTicketDescription": "Vous allez supprimer le ticket, êtes-vous sûr de vouloir continuer ?", + "raffleWinningTickets": "Tickets gagnants", + "raffleNoWinningTicketYet": "Les tickets gagnants seront affichés ici", + "raffleName": "Nom", + "raffleDescription": "Description", + "raffleBuyThisTicket": "Acheter ce ticket", + "raffleLockedRaffle": "Tombola verrouillée", + "raffleUnavailableRaffle": "Tombola indisponible", + "raffleNotEnoughMoney": "Vous n'avez pas assez d'argent", + "raffleWinnable": "gagnable", + "raffleNoDescription": "Aucune description", + "raffleAmount": "Solde", + "raffleLoading": "Chargement", + "raffleTicketNumber": "Nombre de ticket", + "rafflePrice": "Prix", + "raffleEditRaffle": "Modifier la tombola", + "raffleEdit": "Modifier", + "raffleAddPackTicket": "Ajouter un pack de ticket", + "recommendationRecommendation": "Bons plans", + "recommendationTitle": "Titre", + "recommendationLogo": "Logo", + "recommendationCode": "Code", + "recommendationSummary": "Court résumé", + "recommendationDescription": "Description", + "recommendationAdd": "Ajouter", + "recommendationEdit": "Modifier", + "recommendationDelete": "Supprimer", + "recommendationAddImage": "Veuillez ajouter une image", + "recommendationAddedRecommendation": "Bon plan ajouté", + "recommendationEditedRecommendation": "Bon plan modifié", + "recommendationDeleteRecommendationConfirmation": "Êtes-vous sûr de vouloir supprimer ce bon plan ?", + "recommendationDeleteRecommendation": "Suppresion", + "recommendationDeletingRecommendationError": "Erreur lors de la suppression", + "recommendationDeletedRecommendation": "Bon plan supprimé", + "recommendationIncorrectOrMissingFields": "Champs incorrects ou manquants", + "recommendationEditingError": "Échec de la modification", + "recommendationAddingError": "Échec de l'ajout", + "recommendationCopiedCode": "Code de réduction copié", + "seedLibraryAdd": "Ajouter", + "seedLibraryAddedPlant": "Plante ajoutée", + "seedLibraryAddedSpecies": "Espèce ajoutée", + "seedLibraryAddingError": "Erreur lors de l'ajout", + "seedLibraryAddPlant": "Déposer une plante", + "seedLibraryAddSpecies": "Ajouter une espèce", + "seedLibraryAll": "Toutes", + "seedLibraryAncestor": "Ancêtre", + "seedLibraryAround": "environ", + "seedLibraryAutumn": "Automne", + "seedLibraryBorrowedPlant": "Plante empruntée", + "seedLibraryBorrowingDate": "Date d'emprunt :", + "seedLibraryBorrowPlant": "Emprunter la plante", + "seedLibraryCard": "Carte", + "seedLibraryChoosingAncestor": "Veuillez choisir un ancêtre", + "seedLibraryChoosingSpecies": "Veuillez choisir une espèce", + "seedLibraryChoosingSpeciesOrAncestor": "Veuillez choisir une espèce ou un ancêtre", + "seedLibraryContact": "Contact :", + "seedLibraryDays": "jours", + "seedLibraryDeadMsg": "Voulez-vous déclarer la plante morte ?", + "seedLibraryDeadPlant": "Plante morte", + "seedLibraryDeathDate": "Date de mort", + "seedLibraryDeletedSpecies": "Espèce supprimée", + "seedLibraryDeleteSpecies": "Supprimer l'espèce ?", + "seedLibraryDeleting": "Suppression", + "seedLibraryDeletingError": "Erreur lors de la suppression", + "seedLibraryDepositNotAvailable": "Le dépôt de plantes n'est pas possible sans emprunter une plante au préalable", + "seedLibraryDescription": "Description", + "seedLibraryDifficulty": "Difficulté :", + "seedLibraryEdit": "Modifier", + "seedLibraryEditedPlant": "Plante modifiée", + "seedLibraryEditInformation": "Modifier les informations", + "seedLibraryEditingError": "Erreur lors de la modification", + "seedLibraryEditSpecies": "Modifier l'espèce", + "seedLibraryEmptyDifficultyError": "Veuillez choisir une difficulté", + "seedLibraryEmptyFieldError": "Veuillez remplir tous les champs", + "seedLibraryEmptyTypeError": "Veuillez choisir un type de plante", + "seedLibraryEndMonth": "Mois de fin :", + "seedLibraryFacebookUrl": "Lien Facebook", + "seedLibraryFilters": "Filtres", + "seedLibraryForum": "Oskour maman j'ai tué ma plante - Forum d'aide", + "seedLibraryForumUrl": "Lien Forum", + "seedLibraryHelpSheets": "Fiches sur les plantes", + "seedLibraryInformation": "Informations :", + "seedLibraryMaturationTime": "Temps de maturation", + "seedLibraryMonthJan": "Janvier", + "seedLibraryMonthFeb": "Février", + "seedLibraryMonthMar": "Mars", + "seedLibraryMonthApr": "Avril", + "seedLibraryMonthMay": "Mai", + "seedLibraryMonthJun": "Juin", + "seedLibraryMonthJul": "Juillet", + "seedLibraryMonthAug": "Août", + "seedLibraryMonthSep": "Septembre", + "seedLibraryMonthOct": "Octobre", + "seedLibraryMonthNov": "Novembre", + "seedLibraryMonthDec": "Décembre", + "seedLibraryMyPlants": "Mes plantes", + "seedLibraryName": "Nom", + "seedLibraryNbSeedsRecommended": "Nombre de graines recommandées", + "seedLibraryNbSeedsRecommendedError": "Veuillez entrer un nombre de graines recommandé supérieur à 0", + "seedLibraryNoDateError": "Veuillez entrer une date", + "seedLibraryNoFilteredPlants": "Aucune plante ne correspond à votre recherche. Essayez d'autres filtres.", + "seedLibraryNoMorePlant": "Aucune plante n'est disponible", + "seedLibraryNoPersonalPlants": "Vous n'avez pas encore de plantes dans votre grainothèque. Vous pouvez en ajouter en allant dans les stocks.", + "seedLibraryNoSpecies": "Aucune espèce trouvée", + "seedLibraryNoStockPlants": "Aucune plante disponible dans le stock", + "seedLibraryNotes": "Notes", + "seedLibraryOk": "OK", + "seedLibraryPlantationPeriod": "Période de plantation :", + "seedLibraryPlantationType": "Type de plantation :", + "seedLibraryPlantDetail": "Détail de la plante", + "seedLibraryPlantingDate": "Date de plantation", + "seedLibraryPlantingNow": "Je la plante maintenant", + "seedLibraryPrefix": "Préfixe", + "seedLibraryPrefixError": "Prefixe déjà utilisé", + "seedLibraryPrefixLengthError": "Le préfixe doit faire 3 caractères", + "seedLibraryPropagationMethod": "Méthode de propagation :", + "seedLibraryReference": "Référence :", + "seedLibraryRemovedPlant": "Plante supprimée", + "seedLibraryRemovingError": "Erreur lors de la suppression", + "seedLibraryResearch": "Recherche", + "seedLibrarySaveChanges": "Sauvegarder les modifications", + "seedLibrarySeason": "Saison :", + "seedLibrarySeed": "Graine", + "seedLibrarySeeds": "graines", + "seedLibrarySeedDeposit": "Dépôt de plantes", + "seedLibrarySeedLibrary": "Grainothèque", + "seedLibrarySeedQuantitySimple": "Quantité de graines", + "seedLibrarySeedQuantity": "Quantité de graines :", + "seedLibraryShowDeadPlants": "Afficher les plantes mortes", + "seedLibrarySpecies": "Espèce :", + "seedLibrarySpeciesHelp": "Aide sur l'espèce", + "seedLibrarySpeciesPlural": "Espèces", + "seedLibrarySpeciesSimple": "Espèce", + "seedLibrarySpeciesType": "Type d'espèce :", + "seedLibrarySpring": "Printemps", + "seedLibraryStartMonth": "Mois de début :", + "seedLibraryStock": "Stock disponible", + "seedLibrarySummer": "Été", + "seedLibraryStocks": "Stocks", + "seedLibraryTimeUntilMaturation": "Temps avant maturation :", + "seedLibraryType": "Type :", + "seedLibraryUnableToOpen": "Impossible d'ouvrir le lien", + "seedLibraryUpdate": "Modifier", + "seedLibraryUpdatedInformation": "Informations modifiées", + "seedLibraryUpdatedSpecies": "Espèce modifiée", + "seedLibraryUpdatedPlant": "Plante modifiée", + "seedLibraryUpdatingError": "Erreur lors de la modification", + "seedLibraryWinter": "Hiver", + "seedLibraryWriteReference": "Veuillez écrire la référence suivante : ", + "settingsAccount": "Compte", + "settingsAddProfilePicture": "Ajouter une photo", + "settingsAdmin": "Administrateur", + "settingsAskHelp": "Demander de l'aide", + "settingsAssociation": "Association", + "settingsBirthday": "Date de naissance", + "settingsBugs": "Bugs", + "settingsChangePassword": "Changer de mot de passe", + "settingsChangingPassword": "Voulez-vous vraiment changer votre mot de passe ?", + "settingsConfirmPassword": "Confirmer le mot de passe", + "settingsCopied": "Copié !", + "settingsDarkMode": "Mode sombre", + "settingsDarkModeOff": "Désactivé", + "settingsDeleteLogs": "Supprimer les logs ?", + "settingsDeleteNotificationLogs": "Supprimer les logs des notifications ?", + "settingsDetelePersonalData": "Supprimer mes données personnelles", + "settingsDetelePersonalDataDesc": "Cette action notifie l'administrateur que vous souhaitez supprimer vos données personnelles.", + "settingsDeleting": "Suppresion", + "settingsEdit": "Modifier", + "settingsEditAccount": "Modifier mon profil", + "settingsEmail": "Email", + "settingsEmptyField": "Ce champ ne peut pas être vide", + "settingsErrorProfilePicture": "Erreur lors de la modification de la photo de profil", + "settingsErrorSendingDemand": "Erreur lors de l'envoi de la demande", + "settingsEventsIcal": "Lien Ical des événements", + "settingsExpectingDate": "Date de naissance attendue", + "settingsFirstname": "Prénom", + "settingsFloor": "Étage", + "settingsHelp": "Aide", + "settingsIcalCopied": "Lien Ical copié !", + "settingsLanguage": "Langue", + "settingsLanguageVar": "Français 🇫🇷", + "settingsLogs": "Logs", + "settingsModules": "Modules", + "settingsMyIcs": "Mon lien Ical", + "settingsName": "Nom", + "settingsNewPassword": "Nouveau mot de passe", + "settingsNickname": "Surnom", + "settingsNotifications": "Notifications", + "settingsOldPassword": "Ancien mot de passe", + "settingsPasswordChanged": "Mot de passe changé", + "settingsPasswordsNotMatch": "Les mots de passe ne correspondent pas", + "settingsPersonalData": "Données personnelles", + "settingsPersonalisation": "Personnalisation", + "settingsPhone": "Téléphone", + "settingsProfilePicture": "Photo de profil", + "settingsPromo": "Promotion", + "settingsRepportBug": "Signaler un bug", + "settingsSave": "Enregistrer", + "settingsSecurity": "Sécurité", + "settingsSendedDemand": "Demande envoyée", + "settingsSettings": "Paramètres", + "settingsTooHeavyProfilePicture": "L'image est trop lourde (max 4Mo)", + "settingsUpdatedProfile": "Profil modifié", + "settingsUpdatedProfilePicture": "Photo de profil modifiée", + "settingsUpdateNotification": "Mettre à jour les notifications", + "settingsUpdatingError": "Erreur lors de la modification du profil", + "settingsVersion": "Version", + "settingsPasswordStrength": "Force du mot de passe", + "settingsPasswordStrengthVeryWeak": "Très faible", + "settingsPasswordStrengthWeak": "Faible", + "settingsPasswordStrengthMedium": "Moyen", + "settingsPasswordStrengthStrong": "Fort", + "settingsPasswordStrengthVeryStrong": "Très fort", + "settingsPhoneNumber": "Numéro de téléphone", + "settingsValidate": "Valider", + "settingsEditedAccount": "Compte modifié avec succès", + "settingsFailedToEditAccount": "Échec de la modification du compte", + "settingsChooseLanguage": "Choix de la langue", + "settingsNotificationCounter": "{active}/{total} {active, plural, zero {activée} one {activée} other {activées}}", + "@settingsNotificationCounter": { + "description": "Affiche le nombre de notifications actives sur le total des notifications disponibles, avec gestion du pluriel", + "placeholders": { + "active": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "settingsEvent": "Événement", + "settingsIcal": "Lien Ical", + "settingsSynncWithCalendar": "Synchroniser avec votre calendrier", + "settingsIcalLinkCopied": "Lien Ical copié dans le presse-papier", + "settingsProfile": "Profil", + "voteAdd": "Ajouter", + "voteAddMember": "Ajouter un membre", + "voteAddedPretendance": "Liste ajoutée", + "voteAddedSection": "Section ajoutée", + "voteAddingError": "Erreur lors de l'ajout", + "voteAddPretendance": "Ajouter une liste", + "voteAddSection": "Ajouter une section", + "voteAll": "Tous", + "voteAlreadyAddedMember": "Membre déjà ajouté", + "voteAlreadyVoted": "Vote enregistré", + "voteChooseList": "Choisir une liste", + "voteClear": "Réinitialiser", + "voteClearVotes": "Réinitialiser les votes", + "voteClosedVote": "Votes clos", + "voteCloseVote": "Fermer les votes", + "voteConfirmVote": "Confirmer le vote", + "voteCountVote": "Dépouiller les votes", + "voteDeletedAll": "Tout supprimé", + "voteDeletedPipo": "Listes pipos supprimées", + "voteDeletedSection": "Section supprimée", + "voteDeleteAll": "Supprimer tout", + "voteDeleteAllDescription": "Voulez-vous vraiment supprimer tout ?", + "voteDeletePipo": "Supprimer les listes pipos", + "voteDeletePipoDescription": "Voulez-vous vraiment supprimer les listes pipos ?", + "voteDeletePretendance": "Supprimer la liste", + "voteDeletePretendanceDesc": "Voulez-vous vraiment supprimer cette liste ?", + "voteDeleteSection": "Supprimer la section", + "voteDeleteSectionDescription": "Voulez-vous vraiment supprimer cette section ?", + "voteDeletingError": "Erreur lors de la suppression", + "voteDescription": "Description", + "voteEdit": "Modifier", + "voteEditedPretendance": "Liste modifiée", + "voteEditedSection": "Section modifiée", + "voteEditingError": "Erreur lors de la modification", + "voteErrorClosingVotes": "Erreur lors de la fermeture des votes", + "voteErrorCountingVotes": "Erreur lors du dépouillement des votes", + "voteErrorResetingVotes": "Erreur lors de la réinitialisation des votes", + "voteErrorOpeningVotes": "Erreur lors de l'ouverture des votes", + "voteIncorrectOrMissingFields": "Champs incorrects ou manquants", + "voteMembers": "Membres", + "voteName": "Nom", + "voteNoPretendanceList": "Aucune liste de prétendance", + "voteNoSection": "Aucune section", + "voteCanNotVote": "Vous ne pouvez pas voter", + "voteNoSectionList": "Aucune section", + "voteNotOpenedVote": "Vote non ouvert", + "voteOnGoingCount": "Dépouillement en cours", + "voteOpenVote": "Ouvrir les votes", + "votePipo": "Pipo", + "votePretendance": "Listes", + "votePretendanceDeleted": "Prétendance supprimée", + "votePretendanceNotDeleted": "Erreur lors de la suppression", + "voteProgram": "Programme", + "votePublish": "Publier", + "votePublishVoteDescription": "Voulez-vous vraiment publier les votes ?", + "voteResetedVotes": "Votes réinitialisés", + "voteResetVote": "Réinitialiser les votes", + "voteResetVoteDescription": "Que voulez-vous faire ?", + "voteRole": "Rôle", + "voteSectionDescription": "Description de la section", + "voteSection": "Section", + "voteSectionName": "Nom de la section", + "voteSeeMore": "Voir plus", + "voteSelected": "Sélectionné", + "voteShowVotes": "Voir les votes", + "voteVote": "Vote", + "voteVoteError": "Erreur lors de l'enregistrement du vote", + "voteVoteFor": "Voter pour ", + "voteVoteNotStarted": "Vote non ouvert", + "voteVoters": "Groupes votants", + "voteVoteSuccess": "Vote enregistré", + "voteVotes": "Voix", + "voteVotesClosed": "Votes clos", + "voteVotesCounted": "Votes dépouillés", + "voteVotesOpened": "Votes ouverts", + "voteWarning": "Attention", + "voteWarningMessage": "La sélection ne sera pas sauvegardée.\nVoulez-vous continuer ?", + "moduleAdvert": "Annonce", "moduleAdvertDescription": "Gérer les annonces", - "moduleAmap": "AMAP", + "moduleAmap": "AMAP", "moduleAmapDescription": "Gérer les livraisons et les produits", - "moduleBooking": "Réservation", + "moduleBooking": "Réservation", "moduleBookingDescription": "Gérer les réservations, les salles et les managers", - "moduleCalendar": "Calendrier", + "moduleCalendar": "Calendrier", "moduleCalendarDescription": "Consulter les événements et les activités", - "moduleCentralisation": "Centralisation", + "moduleCentralisation": "Centralisation", "moduleCentralisationDescription": "Gérer la centralisation des données", - "moduleCinema": "Cinéma", + "moduleCinema": "Cinéma", "moduleCinemaDescription": "Gérer les séances de cinéma", - "moduleEvent": "Événement", + "moduleEvent": "Événement", "moduleEventDescription": "Gérer les événements et les participants", - "moduleFlappyBird": "Flappy Bird", + "moduleFlappyBird": "Flappy Bird", "moduleFlappyBirdDescription": "Jouer à Flappy Bird et consulter le classement", - "moduleLoan": "Prêt", + "moduleLoan": "Prêt", "moduleLoanDescription": "Gérer les prêts et les articles", - "modulePhonebook": "Annuaire", + "modulePhonebook": "Annuaire", "modulePhonebookDescription": "Gérer les associations, les membres et les administrateurs", - "modulePurchases": "Achats", + "modulePurchases": "Achats", "modulePurchasesDescription": "Gérer les achats, les tickets et l'historique", - "moduleRaffle": "Tombola", + "moduleRaffle": "Tombola", "moduleRaffleDescription": "Gérer les tombolas, les prix et les tickets", - "moduleRecommendation": "Bons plans", + "moduleRecommendation": "Bons plans", "moduleRecommendationDescription": "Gérer les recommandations, les informations et les administrateurs", - "moduleSeedLibrary": "Grainothèque", + "moduleSeedLibrary": "Grainothèque", "moduleSeedLibraryDescription": "Gérer les graines, les espèces et les stocks", - "moduleVote": "Vote", + "moduleVote": "Vote", "moduleVoteDescription": "Gérer les votes, les sections et les candidats", - "modulePh": "PH", + "modulePh": "PH", "modulePhDescription": "Gérer les PH, les formulaires et les administrateurs", - "moduleSettings": "Paramètres", + "moduleSettings": "Paramètres", "moduleSettingsDescription": "Gérer les paramètres de l'application", - "moduleFeed": "Feed", + "moduleFeed": "Feed", "moduleFeedDescription": "Consulter les actualités et mises à jour", - "moduleStyleGuide": "StyleGuide", + "moduleStyleGuide": "StyleGuide", "moduleStyleGuideDescription": "Explore the UI components and styles used in Titan", - "moduleAdmin": "Adminitration", + "moduleAdmin": "Adminitration", "moduleAdminDescription": "Gérer les utilisateurs, groupes et structures", - "moduleOthers": "Autres", + "moduleOthers": "Autres", "moduleOthersDescription": "Afficher les autres modules", - "modulePayment": "Paiement", + "modulePayment": "Paiement", "modulePaymentDescription": "Gérer les paiements, les statistiques et les appareils", - "paiementTopUp" : "Recharge", - "paiementStoreManagement" : "Gestion des associations", - "paiementDeleteStore": "Supprimer l'association", - "paiementDeleteStoreDescription": "Voulez-vous vraiment supprimer cette association ?", - "paiementDeleteStoreError": "Impossible de supprimer l'association", - "paiementStoreDeleted": "Association supprimée", - "paiementAddThisDevice": "Ajouter cet appareil", - "paiementThisDevice": "(cet appareil)", - "paiementCancelled": "Annulé", - "paiementThe" : "Le", - "paiementOf": "de", - "paiementRefundedThe": "Remboursé le", - "paiementAt": "à", - "paiementPleaseAcceptTOS" : "Veuillez accepter les Conditions Générales d'Utilisation.", - "paiementAskDeviceActivation" : "Demande d'activation de l'appareil", - "paiementDeviceActivationReceived" : "La demande d'activation est prise en compte, veuilliez consulter votre boite mail pour finaliser la démarche", - "paiementRevokeDevice" : "Révoquer l'appareil ?", - "paiementRevokeDeviceDescription" : "Vous ne pourrez plus utiliser cet appareil pour les paiements", - "paiementDeviceRevoked" : "Appareil révoqué", - "paiementDeviceRevokingError" : "Erreur lors de la révocation de l'appareil", - "paiementPleaseAcceptPopup": "Veuillez autoriser les popups", - "paiementProceedSuccessfully": "Paiement effectué avec succès", - "paiementCancelledTransaction": "Paiement annulé", - "paiementPleaseEnterMinAmount": "Veuillez entrer un montant supérieur à 1", - "paiementMaxAmount": "Le montant maximum de votre portefeuille est de", - "paiementPayWithHA" : "Payer avec HelloAsso", - "paiementBalanceAfterTopUp": "Solde après recharge :", - "paiementPersonalBalance": "Solde personnel", - "paiementDevices": "Appareils", - "paiementPay": "Payer", - "paiementDeviceNotRegistered": "Appareil non enregistré", - "paiementDeviceNotRegisteredDescription": "Votre appareil n'est pas encore enregistré. \nPour l'enregistrer, veuillez vous rendre sur la page des appareils.", - "paiementAccessPage": "Accéder à la page", - "paiementDeviceNotActivated": "Appareil non activé", - "paiementDeviceNotActivatedDescription": "Votre appareil n'est pas encore activé. \nPour l'activer, veuillez vous rendre sur la page des appareils.", - "paiementReactivateRevokedDeviceDescription": "Votre appareil a été révoqué. \nPour le réactiver, veuillez vous rendre sur la page des appareils.", - "paiementDeviceRecoveryError": "Erreur lors de la récupération de l'appareil", - "paiementStats": "Stats", - "paimentTopUpAction": "Recharger", - "paiementGetBalanceError": "Erreur lors de la récupération du solde : ", - "paiementLastTransactions": "Dernières transactions", - "paiementGetTransactionsError": "Erreur lors de la récupération des transactions : ", - "paiementStoreBalance" : "Solde associatif", - "paiementScan": "Scanner", - "paiementManagement": "Gestion", - "paiementHistory": "Historique", - "paiementHandOver": "Passation", - "paiementStores": "Associations", - "paiementAdmin": "Administrateur", - "paiementSuccededTransaction": "Paiement réussi", - "paiementNewCGU" : "Nouvelles Conditions Générales d'Utilisation", - "paiementDecline": "Refuser", - "paiementAccept": "Accepter", - "paiementAmount": "Montant", - "paiementValidUntil": "Valide jusqu'à", - "paiementClose": "Fermer", - "paiementPleaseEnterValidAmount": "Veuillez entrer un montant valide", - "paiementPleaseAuthenticate": "Veuillez vous authentifier", - "paiementAthenticationRequired": "Authentification requise pour payer", - "paiementNoThanks": "Non merci", - "paiementAuthentificationFailed": "Échec de l'authentification", - "paiementPleaseAddDevice": "Veuillez ajouter cet appareil pour payer", - "paiementPayment": "Paiement", - "paiementBalanceAfterTransaction": "Solde après paiement : ", - "paiementCancel": "Annuler", - "paiementLimitedTo": "Limité à", - "paiementScanCode": "Scanner un code", - "paiementNext": "Suivant", - "paiementCancelTransaction": "Annuler la transaction", - "paiementTransactionCancelled": "Transaction annulée", - "paiementTransactionCancelledDescription": "Voulez-vous vraiment annuler la transaction de", - "paiementTransactionCancelledError": "Erreur lors de l'annulation de la transaction", - "paiementNoMembership": "Aucune adhésion", - "paiementNoMembershipDescription": "Ce produit n'est pas disponnible pour les non-adhérents. Confirmer l'encaissement ?", - "paiementQRCodeAlreadyUsed": "QR Code déjà utilisé", - "paiementCameraPermissionRequired": "Permission d'accès à la caméra requise", - "paiementCameraPerssionRequiredDescription": "Pour scanner un QR Code, vous devez autoriser l'accès à la caméra.", - "paiementSettings": "Paramètres", - "paiementReceived": "Reçu", - "paiementSpent": "Déboursé", - "paiementNoTrasactionForThisMonth": "Aucune transaction pour ce mois", - "paiementNoTransactinon": "Aucune transaction", - "paiementSellerRigths": "Droits du vendeur", - "paiementCanBank": "Peut encaisser", - "paiementCanSeeHistory": "Peut voir l'historique", - "paiementCanCancelTransaction": "Peut annuler des transactions", - "paiementCanManageSellers": "Peut gérer les vendeurs", - "paiementAddedSeller": "Vendeur ajouté", - "paiementAddingSellerError": "Erreur lors de l'ajout du vendeur", - "paiementBank": "Encaisser", - "paiementSeeHistory": "Voir l'historique", - "paiementCancelTransactions": "Annuler les transactions", - "paiementManageSellers": "Gérer les vendeurs", - "paiementStructureAdmin": "Administrateur de la structure", - "paiementRightsOf": "Droits de", - "paiementRightsUpdated": "Droits mis à jour", - "paiementRightsUpdateError": "Erreur lors de la mise à jour des droits", - "paiementDeleteSellerDescription": "Voulez-vous vraiment supprimer ce vendeur ?", - "paiementDeletedSeller": "Vendeur supprimé", - "paiementDeletingSellerError": "Erreur lors de la suppression du vendeur", - "paiementDeleteSeller": "Supprimer le vendeur", - "paiementAdd" : "Ajouter", - "paiementAddSeller": "Ajouter un vendeur", - "paiementSellerError": "Vous n'êtes pas vendeur de cette association", - "paiementSellersOf": "Les vendeurs de", - "paiementModify": "Modifier", - "paiementAStore": "une association", - "paiementStoreName": "Nom de l'association", - "paiementSuccessfullyAddedStore": "Association ajoutée avec succès", - "paiementSuccessfullyModifiedStore": "Association modifiée avec succès", - "paiementAddingStoreError": "Erreur lors de l'ajout de l'association", - "paiementModifyingStoreError": "Erreur lors de la modification de l'association", - "paiementRefund": "Remboursement", - "paiementDoneTransaction": "Transaction effectuée", - "paiementRefundAction": "Rembourser", - "paiementTotalDuringPeriod": "Total sur la période", - "paiementMean" : "Moyenne : ", - "paiementTransaction": "ransaction", - "paiementTransferStructure": "Transfert de structure", - "paiementYouAreTransferingStructureTo": "Vous êtes sur le point de transférer la structure à ", - "paiementTransferStructureDescription": "Le nouveau responsable aura accès à toutes les fonctionnalités de gestion de la structure. Vous allez recevoir un email pour valider ce transfert. Le lien ne sera actif que pendant 20 minutes. Cette action est irréversible. Êtes-vous sûr de vouloir continuer ?", - "paiementTransferStructureError": "Erreur lors du transfert de la structure", - "paiementTransferStructureSuccess": "Transfert de structure demandé avec succès", - "paiementNextAccountable": "Prochain responsable" -} \ No newline at end of file + "paiementTopUp": "Recharge", + "paiementStoreManagement": "Gestion des associations", + "paiementDeleteStore": "Supprimer l'association", + "paiementDeleteStoreDescription": "Voulez-vous vraiment supprimer cette association ?", + "paiementDeleteStoreError": "Impossible de supprimer l'association", + "paiementStoreDeleted": "Association supprimée", + "paiementAddThisDevice": "Ajouter cet appareil", + "paiementThisDevice": "(cet appareil)", + "paiementCancelled": "Annulé", + "paiementThe": "Le", + "paiementOf": "de", + "paiementRefundedThe": "Remboursé le", + "paiementAt": "à", + "paiementPleaseAcceptTOS": "Veuillez accepter les Conditions Générales d'Utilisation.", + "paiementAskDeviceActivation": "Demande d'activation de l'appareil", + "paiementDeviceActivationReceived": "La demande d'activation est prise en compte, veuilliez consulter votre boite mail pour finaliser la démarche", + "paiementRevokeDevice": "Révoquer l'appareil ?", + "paiementRevokeDeviceDescription": "Vous ne pourrez plus utiliser cet appareil pour les paiements", + "paiementDeviceRevoked": "Appareil révoqué", + "paiementDeviceRevokingError": "Erreur lors de la révocation de l'appareil", + "paiementPleaseAcceptPopup": "Veuillez autoriser les popups", + "paiementProceedSuccessfully": "Paiement effectué avec succès", + "paiementCancelledTransaction": "Paiement annulé", + "paiementPleaseEnterMinAmount": "Veuillez entrer un montant supérieur à 1", + "paiementMaxAmount": "Le montant maximum de votre portefeuille est de", + "paiementPayWithHA": "Payer avec HelloAsso", + "paiementBalanceAfterTopUp": "Solde après recharge :", + "paiementPersonalBalance": "Solde personnel", + "paiementDevices": "Appareils", + "paiementPay": "Payer", + "paiementDeviceNotRegistered": "Appareil non enregistré", + "paiementDeviceNotRegisteredDescription": "Votre appareil n'est pas encore enregistré. \nPour l'enregistrer, veuillez vous rendre sur la page des appareils.", + "paiementAccessPage": "Accéder à la page", + "paiementDeviceNotActivated": "Appareil non activé", + "paiementDeviceNotActivatedDescription": "Votre appareil n'est pas encore activé. \nPour l'activer, veuillez vous rendre sur la page des appareils.", + "paiementReactivateRevokedDeviceDescription": "Votre appareil a été révoqué. \nPour le réactiver, veuillez vous rendre sur la page des appareils.", + "paiementDeviceRecoveryError": "Erreur lors de la récupération de l'appareil", + "paiementStats": "Stats", + "paimentTopUpAction": "Recharger", + "paiementGetBalanceError": "Erreur lors de la récupération du solde : ", + "paiementLastTransactions": "Dernières transactions", + "paiementGetTransactionsError": "Erreur lors de la récupération des transactions : ", + "paiementStoreBalance": "Solde associatif", + "paiementScan": "Scanner", + "paiementManagement": "Gestion", + "paiementHistory": "Historique", + "paiementHandOver": "Passation", + "paiementStores": "Associations", + "paiementAdmin": "Administrateur", + "paiementSuccededTransaction": "Paiement réussi", + "paiementNewCGU": "Nouvelles Conditions Générales d'Utilisation", + "paiementDecline": "Refuser", + "paiementAccept": "Accepter", + "paiementAmount": "Montant", + "paiementValidUntil": "Valide jusqu'à", + "paiementClose": "Fermer", + "paiementPleaseEnterValidAmount": "Veuillez entrer un montant valide", + "paiementPleaseAuthenticate": "Veuillez vous authentifier", + "paiementAthenticationRequired": "Authentification requise pour payer", + "paiementNoThanks": "Non merci", + "paiementAuthentificationFailed": "Échec de l'authentification", + "paiementPleaseAddDevice": "Veuillez ajouter cet appareil pour payer", + "paiementPayment": "Paiement", + "paiementBalanceAfterTransaction": "Solde après paiement : ", + "paiementCancel": "Annuler", + "paiementLimitedTo": "Limité à", + "paiementScanCode": "Scanner un code", + "paiementNext": "Suivant", + "paiementCancelTransaction": "Annuler la transaction", + "paiementTransactionCancelled": "Transaction annulée", + "paiementTransactionCancelledDescription": "Voulez-vous vraiment annuler la transaction de", + "paiementTransactionCancelledError": "Erreur lors de l'annulation de la transaction", + "paiementNoMembership": "Aucune adhésion", + "paiementNoMembershipDescription": "Ce produit n'est pas disponnible pour les non-adhérents. Confirmer l'encaissement ?", + "paiementQRCodeAlreadyUsed": "QR Code déjà utilisé", + "paiementCameraPermissionRequired": "Permission d'accès à la caméra requise", + "paiementCameraPerssionRequiredDescription": "Pour scanner un QR Code, vous devez autoriser l'accès à la caméra.", + "paiementSettings": "Paramètres", + "paiementReceived": "Reçu", + "paiementSpent": "Déboursé", + "paiementNoTrasactionForThisMonth": "Aucune transaction pour ce mois", + "paiementNoTransactinon": "Aucune transaction", + "paiementSellerRigths": "Droits du vendeur", + "paiementCanBank": "Peut encaisser", + "paiementCanSeeHistory": "Peut voir l'historique", + "paiementCanCancelTransaction": "Peut annuler des transactions", + "paiementCanManageSellers": "Peut gérer les vendeurs", + "paiementAddedSeller": "Vendeur ajouté", + "paiementAddingSellerError": "Erreur lors de l'ajout du vendeur", + "paiementBank": "Encaisser", + "paiementSeeHistory": "Voir l'historique", + "paiementCancelTransactions": "Annuler les transactions", + "paiementManageSellers": "Gérer les vendeurs", + "paiementStructureAdmin": "Administrateur de la structure", + "paiementRightsOf": "Droits de", + "paiementRightsUpdated": "Droits mis à jour", + "paiementRightsUpdateError": "Erreur lors de la mise à jour des droits", + "paiementDeleteSellerDescription": "Voulez-vous vraiment supprimer ce vendeur ?", + "paiementDeletedSeller": "Vendeur supprimé", + "paiementDeletingSellerError": "Erreur lors de la suppression du vendeur", + "paiementDeleteSeller": "Supprimer le vendeur", + "paiementAdd": "Ajouter", + "paiementAddSeller": "Ajouter un vendeur", + "paiementSellerError": "Vous n'êtes pas vendeur de cette association", + "paiementSellersOf": "Les vendeurs de", + "paiementModify": "Modifier", + "paiementAStore": "une association", + "paiementStoreName": "Nom de l'association", + "paiementSuccessfullyAddedStore": "Association ajoutée avec succès", + "paiementSuccessfullyModifiedStore": "Association modifiée avec succès", + "paiementAddingStoreError": "Erreur lors de l'ajout de l'association", + "paiementModifyingStoreError": "Erreur lors de la modification de l'association", + "paiementRefund": "Remboursement", + "paiementDoneTransaction": "Transaction effectuée", + "paiementRefundAction": "Rembourser", + "paiementTotalDuringPeriod": "Total sur la période", + "paiementMean": "Moyenne : ", + "paiementTransaction": "ransaction", + "paiementTransferStructure": "Transfert de structure", + "paiementYouAreTransferingStructureTo": "Vous êtes sur le point de transférer la structure à ", + "paiementTransferStructureDescription": "Le nouveau responsable aura accès à toutes les fonctionnalités de gestion de la structure. Vous allez recevoir un email pour valider ce transfert. Le lien ne sera actif que pendant 20 minutes. Cette action est irréversible. Êtes-vous sûr de vouloir continuer ?", + "paiementTransferStructureError": "Erreur lors du transfert de la structure", + "paiementTransferStructureSuccess": "Transfert de structure demandé avec succès", + "paiementNextAccountable": "Prochain responsable" +} diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 609a3135d4..88f6a35cfa 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -212,6 +212,54 @@ abstract class AppLocalizations { /// **'Gestion des événements'** String get feedEventManagement; + /// No description provided for @eventActionCampaign. + /// + /// In fr, this message translates to: + /// **'Tu peux voter'** + String get eventActionCampaign; + + /// No description provided for @eventActionEvent. + /// + /// In fr, this message translates to: + /// **'Tu es invité'** + String get eventActionEvent; + + /// No description provided for @eventActionCampaignSubtitle. + /// + /// In fr, this message translates to: + /// **'Votez maintenant'** + String get eventActionCampaignSubtitle; + + /// No description provided for @eventActionEventSubtitle. + /// + /// In fr, this message translates to: + /// **'Répondez à l\'invitation'** + String get eventActionEventSubtitle; + + /// No description provided for @eventActionCampaignButton. + /// + /// In fr, this message translates to: + /// **'Voter'** + String get eventActionCampaignButton; + + /// No description provided for @eventActionEventButton. + /// + /// In fr, this message translates to: + /// **'Participer'** + String get eventActionEventButton; + + /// No description provided for @eventActionCampaignValidated. + /// + /// In fr, this message translates to: + /// **'J\'ai voté !'** + String get eventActionCampaignValidated; + + /// No description provided for @eventActionEventValidated. + /// + /// In fr, this message translates to: + /// **'Je viens !'** + String get eventActionEventValidated; + /// No description provided for @adminAccountTypes. /// /// In fr, this message translates to: diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index f51e1adab5..23664041e7 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -65,6 +65,30 @@ class AppLocalizationsEn extends AppLocalizations { @override String get feedEventManagement => 'Event Management'; + @override + String get eventActionCampaign => 'You can vote'; + + @override + String get eventActionEvent => 'You are invited'; + + @override + String get eventActionCampaignSubtitle => 'Vote now'; + + @override + String get eventActionEventSubtitle => 'Respond to the invitation'; + + @override + String get eventActionCampaignButton => 'Vote'; + + @override + String get eventActionEventButton => 'Participate'; + + @override + String get eventActionCampaignValidated => 'I voted!'; + + @override + String get eventActionEventValidated => 'I\'m coming!'; + @override String get adminAccountTypes => 'Account types'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 729424691f..841ba4555b 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -65,6 +65,30 @@ class AppLocalizationsFr extends AppLocalizations { @override String get feedEventManagement => 'Gestion des événements'; + @override + String get eventActionCampaign => 'Tu peux voter'; + + @override + String get eventActionEvent => 'Tu es invité'; + + @override + String get eventActionCampaignSubtitle => 'Votez maintenant'; + + @override + String get eventActionEventSubtitle => 'Répondez à l\'invitation'; + + @override + String get eventActionCampaignButton => 'Voter'; + + @override + String get eventActionEventButton => 'Participer'; + + @override + String get eventActionCampaignValidated => 'J\'ai voté !'; + + @override + String get eventActionEventValidated => 'Je viens !'; + @override String get adminAccountTypes => 'Types de compte'; From 7533aa4cd9de6c71d811a6b712e2238c7d220600 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 10 Aug 2025 15:09:39 +0200 Subject: [PATCH 227/473] fix: action button text color --- lib/feed/tools/news_helper.dart | 12 ++---------- lib/feed/ui/pages/main_page/event_action.dart | 2 +- lib/feed/ui/pages/main_page/time_line_item.dart | 4 ++-- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/lib/feed/tools/news_helper.dart b/lib/feed/tools/news_helper.dart index 54330af46f..53fa2125c4 100644 --- a/lib/feed/tools/news_helper.dart +++ b/lib/feed/tools/news_helper.dart @@ -3,7 +3,6 @@ import 'package:titan/feed/class/news.dart'; import 'package:intl/intl.dart'; import 'package:titan/l10n/app_localizations.dart'; -/// Capitalizes the first letter of a string String _capitalize(String text) { if (text.isEmpty) return text; return text[0].toUpperCase() + text.substring(1); @@ -80,7 +79,6 @@ String getNewsSubtitle( final startDate = news.start.toLocal(); - // For ongoing events, just display the end date if available if (isNewsOngoing(news) && news.end != null) { final untilText = _capitalize( AppLocalizations.of(context)?.dateUntil ?? @@ -88,17 +86,13 @@ String getNewsSubtitle( ); subtitle = "$untilText ${formatUserFriendlyDate(news.end!.toLocal(), locale: locale, context: context)}"; - } - // For events with no end date, just display the start date - else if (news.end == null) { + } else if (news.end == null) { subtitle = formatUserFriendlyDate( startDate, locale: locale, context: context, ); - } - // For events with both start and end dates that are not ongoing - else { + } else { final endDate = news.end!.toLocal(); bool sameDay = startDate.year == endDate.year && @@ -126,13 +120,11 @@ String getNewsSubtitle( AppLocalizations.of(context)?.dateFrom ?? 'de', ); - // Determine if the end date is a special date (today, yesterday, tomorrow) final now = DateTime.now(); final today = DateTime(now.year, now.month, now.day); final endDateTime = DateTime(endDate.year, endDate.month, endDate.day); final difference = endDateTime.difference(today).inDays; - // Use "à" (dateTo) instead of "au" (dateBetweenDays) for special dates final toWord = (difference >= -1 && difference <= 1) ? (AppLocalizations.of(context)?.dateTo ?? 'à') : (AppLocalizations.of(context)?.dateBetweenDays ?? 'au'); diff --git a/lib/feed/ui/pages/main_page/event_action.dart b/lib/feed/ui/pages/main_page/event_action.dart index 19c2cad4ca..9aaa94771e 100644 --- a/lib/feed/ui/pages/main_page/event_action.dart +++ b/lib/feed/ui/pages/main_page/event_action.dart @@ -77,7 +77,7 @@ class EventAction extends StatelessWidget { (isActionValidated ? ColorConstants.background : ColorConstants.tertiary) - .withValues(alpha: isActionEnabled ? 255 : 100), + .withValues(alpha: isActionEnabled ? 1 : 0.5), ), ), ), diff --git a/lib/feed/ui/pages/main_page/time_line_item.dart b/lib/feed/ui/pages/main_page/time_line_item.dart index 2d2e91a6a0..92edfcf500 100644 --- a/lib/feed/ui/pages/main_page/time_line_item.dart +++ b/lib/feed/ui/pages/main_page/time_line_item.dart @@ -96,7 +96,7 @@ class TimelineItem extends StatelessWidget { ), ), Expanded( - child: isAdmin + child: !isAdmin ? EventActionAdmin(item: item) : EventAction( title: getActionTitle(item, context), @@ -113,7 +113,7 @@ class TimelineItem extends StatelessWidget { ), isActionValidated: true, isActionEnabled: - (item.actionStart ?? item.start).isAfter( + (item.actionStart ?? item.start).isBefore( DateTime.now(), ) && item.end != null && From 0de8819e55252f54d7f6125af8e52b8f318042a6 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 10 Aug 2025 15:09:40 +0200 Subject: [PATCH 228/473] feat: adding action callback --- lib/feed/tools/news_helper.dart | 19 +++++++++++++++++++ lib/feed/ui/pages/main_page/event_action.dart | 6 +++++- .../ui/pages/main_page/time_line_item.dart | 5 ++--- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/lib/feed/tools/news_helper.dart b/lib/feed/tools/news_helper.dart index 53fa2125c4..86af092293 100644 --- a/lib/feed/tools/news_helper.dart +++ b/lib/feed/tools/news_helper.dart @@ -1,7 +1,11 @@ import 'package:flutter/widgets.dart'; +import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/advert/router.dart'; +import 'package:titan/event/router.dart'; import 'package:titan/feed/class/news.dart'; import 'package:intl/intl.dart'; import 'package:titan/l10n/app_localizations.dart'; +import 'package:titan/vote/router.dart'; String _capitalize(String text) { if (text.isEmpty) return text; @@ -192,3 +196,18 @@ String getActionValidatedButtonText(News news, BuildContext context) { } return ''; } + +void getActionButtonAction(News news) { + final module = news.module; + + if (module == "campagne") { + QR.to(VoteRouter.root); + } else if (module == "event") { + // TODO : query event + QR.to(EventRouter.root); + } else if (module == "post") { + // TODO : set id + QR.to(AdvertRouter.root); + } + return; +} diff --git a/lib/feed/ui/pages/main_page/event_action.dart b/lib/feed/ui/pages/main_page/event_action.dart index 9aaa94771e..1c84d21ad9 100644 --- a/lib/feed/ui/pages/main_page/event_action.dart +++ b/lib/feed/ui/pages/main_page/event_action.dart @@ -77,7 +77,11 @@ class EventAction extends StatelessWidget { (isActionValidated ? ColorConstants.background : ColorConstants.tertiary) - .withValues(alpha: isActionEnabled ? 1 : 0.5), + .withValues( + alpha: isActionEnabled && !isActionValidated + ? 1 + : 0.5, + ), ), ), ), diff --git a/lib/feed/ui/pages/main_page/time_line_item.dart b/lib/feed/ui/pages/main_page/time_line_item.dart index 92edfcf500..8063c36775 100644 --- a/lib/feed/ui/pages/main_page/time_line_item.dart +++ b/lib/feed/ui/pages/main_page/time_line_item.dart @@ -101,9 +101,8 @@ class TimelineItem extends StatelessWidget { : EventAction( title: getActionTitle(item, context), subtitle: getActionSubtitle(item, context), - onActionPressed: () { - // Handle action press - }, + onActionPressed: () => + getActionButtonAction(item), actionEnableButtonText: getActionEnableButtonText(item, context), actionValidatedButtonText: From d8538623cb8ea1f1c3bd54134543bb0f0650b84b Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 10 Aug 2025 15:09:40 +0200 Subject: [PATCH 229/473] fix: typo --- lib/tools/ui/styleguide/text_entry.dart | 31 ++++++++++++------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/lib/tools/ui/styleguide/text_entry.dart b/lib/tools/ui/styleguide/text_entry.dart index f0b570110c..f79d76a16b 100644 --- a/lib/tools/ui/styleguide/text_entry.dart +++ b/lib/tools/ui/styleguide/text_entry.dart @@ -97,26 +97,25 @@ class TextEntry extends StatelessWidget { return noValueError; } - if (isInt) { - final intValue = int.tryParse(value); - if (intValue == null || (intValue < 0 && !isNegative)) { - return "Invalid number"; - } + if (isInt) { + final intValue = int.tryParse(value); + if (intValue == null || (intValue < 0 && !isNegative)) { + return "Invalid number"; } + } - if (isDouble) { - final doubleValue = double.tryParse(value.replaceAll(',', '.')); - if (doubleValue == null || (doubleValue < 0 && !isNegative)) { - return "Invalid number"; - } + if (isDouble) { + final doubleValue = double.tryParse(value.replaceAll(',', '.')); + if (doubleValue == null || (doubleValue < 0 && !isNegative)) { + return "Invalid number"; } + } - if (validator == null) { - return null; - } - return validator!(value); - }, - ), + if (validator == null) { + return null; + } + return validator!(value); + }, ); } } From 8bafdeca6a6b1dea84af7a48c75b9ea8526264d9 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 10 Aug 2025 15:09:40 +0200 Subject: [PATCH 230/473] fix: typo --- lib/feed/ui/pages/main_page/main_page.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/feed/ui/pages/main_page/main_page.dart b/lib/feed/ui/pages/main_page/main_page.dart index 67932fbd26..327bded763 100644 --- a/lib/feed/ui/pages/main_page/main_page.dart +++ b/lib/feed/ui/pages/main_page/main_page.dart @@ -55,7 +55,7 @@ class FeedMainPage extends HookConsumerWidget { for (int i = 0; i < upcomingIndex; i++) { final currentItem = newsList[i]; - final itemHeight = (currentItem.actionStart != null || isAdmin) + final itemHeight = (currentItem.actionStart != null || isSuperAdmin) ? 200.0 : 160.0; scrollPosition += itemHeight; From e6b7a86bdcbb65b48d8ce8be1216357c2c9c677a Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 10 Aug 2025 15:13:57 +0200 Subject: [PATCH 231/473] lint: applying linter --- lib/feed/ui/pages/main_page/main_page.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/feed/ui/pages/main_page/main_page.dart b/lib/feed/ui/pages/main_page/main_page.dart index 327bded763..667328679b 100644 --- a/lib/feed/ui/pages/main_page/main_page.dart +++ b/lib/feed/ui/pages/main_page/main_page.dart @@ -55,7 +55,8 @@ class FeedMainPage extends HookConsumerWidget { for (int i = 0; i < upcomingIndex; i++) { final currentItem = newsList[i]; - final itemHeight = (currentItem.actionStart != null || isSuperAdmin) + final itemHeight = + (currentItem.actionStart != null || isSuperAdmin) ? 200.0 : 160.0; scrollPosition += itemHeight; From aeb94f38c7df08f86ee67ce65f5bf844418577ac Mon Sep 17 00:00:00 2001 From: Thonyk Date: Sun, 10 Aug 2025 15:39:02 +0200 Subject: [PATCH 232/473] fix: circle avatar --- lib/phonebook/ui/components/member_card.dart | 7 +++++-- .../ui/pages/admin_page/editable_association_card.dart | 3 ++- lib/phonebook/ui/pages/main_page/association_card.dart | 3 ++- .../ui/pages/member_detail_page/member_detail_page.dart | 3 ++- .../ui/pages/member_detail_page/membership_card.dart | 3 ++- 5 files changed, 13 insertions(+), 6 deletions(-) diff --git a/lib/phonebook/ui/components/member_card.dart b/lib/phonebook/ui/components/member_card.dart index 0871c15572..bbc6561f32 100644 --- a/lib/phonebook/ui/components/member_card.dart +++ b/lib/phonebook/ui/components/member_card.dart @@ -63,8 +63,11 @@ class MemberCard extends HookConsumerWidget { radius: 20, child: CircularProgressIndicator(), ), - dataBuilder: (context, data) => - CircleAvatar(child: Image(image: data.first.image)), + dataBuilder: (context, data) => CircleAvatar( + radius: 20, + backgroundColor: Colors.white, + backgroundImage: Image(image: data.first.image).image, + ), ), onTap: editable ? () { diff --git a/lib/phonebook/ui/pages/admin_page/editable_association_card.dart b/lib/phonebook/ui/pages/admin_page/editable_association_card.dart index a1af38f301..e8faf226ff 100644 --- a/lib/phonebook/ui/pages/admin_page/editable_association_card.dart +++ b/lib/phonebook/ui/pages/admin_page/editable_association_card.dart @@ -46,8 +46,9 @@ class EditableAssociationCard extends HookConsumerWidget { associationPictureNotifier.getAssociationPicture(associationId), dataBuilder: (context, data) { return CircleAvatar( + radius: 20, backgroundColor: Colors.white, - child: Image(image: data.first.image), + backgroundImage: Image(image: data.first.image).image, ); }, ), diff --git a/lib/phonebook/ui/pages/main_page/association_card.dart b/lib/phonebook/ui/pages/main_page/association_card.dart index 90c2749ac2..1eecc5b65c 100644 --- a/lib/phonebook/ui/pages/main_page/association_card.dart +++ b/lib/phonebook/ui/pages/main_page/association_card.dart @@ -49,8 +49,9 @@ class AssociationCard extends HookConsumerWidget { associationPictureNotifier.getAssociationPicture(associationId), dataBuilder: (context, data) { return CircleAvatar( + radius: 20, backgroundColor: Colors.white, - child: Image(image: data.first.image), + backgroundImage: Image(image: data.first.image).image, ); }, ), diff --git a/lib/phonebook/ui/pages/member_detail_page/member_detail_page.dart b/lib/phonebook/ui/pages/member_detail_page/member_detail_page.dart index fc825b38f8..ea76ce576f 100644 --- a/lib/phonebook/ui/pages/member_detail_page/member_detail_page.dart +++ b/lib/phonebook/ui/pages/member_detail_page/member_detail_page.dart @@ -51,7 +51,8 @@ class MemberDetailPage extends HookConsumerWidget { ), dataBuilder: (context, data) => CircleAvatar( radius: 80, - child: Image(image: data.first.image), + backgroundColor: Colors.white, + backgroundImage: Image(image: data.first.image).image, ), ), if (member.member.nickname != null) ...[ diff --git a/lib/phonebook/ui/pages/member_detail_page/membership_card.dart b/lib/phonebook/ui/pages/member_detail_page/membership_card.dart index d1a0d966a2..5cc71caa65 100644 --- a/lib/phonebook/ui/pages/member_detail_page/membership_card.dart +++ b/lib/phonebook/ui/pages/member_detail_page/membership_card.dart @@ -45,8 +45,9 @@ class MembershipCard extends HookConsumerWidget { associationPictureNotifier.getAssociationPicture(associationId), dataBuilder: (context, data) { return CircleAvatar( + radius: 20, backgroundColor: Colors.white, - child: Image(image: data.first.image), + backgroundImage: Image(image: data.first.image).image, ); }, ), From 3312fe0e51c8dca5c1112986dc621e0dfa0d82c5 Mon Sep 17 00:00:00 2001 From: Thonyk Date: Sun, 10 Aug 2025 15:47:08 +0200 Subject: [PATCH 233/473] clean up --- lib/phonebook/providers/phonebook_admin_provider.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/phonebook/providers/phonebook_admin_provider.dart b/lib/phonebook/providers/phonebook_admin_provider.dart index e358e45835..9fb982a555 100644 --- a/lib/phonebook/providers/phonebook_admin_provider.dart +++ b/lib/phonebook/providers/phonebook_admin_provider.dart @@ -27,8 +27,6 @@ final hasPhonebookAdminAccessProvider = StateProvider((ref) { }); final isAssociationPresidentProvider = Provider((ref) { - print("Provider is being evaluated"); - final association = ref.watch(associationProvider); final rolesTags = ref.watch(rolesTagsProvider); final membersList = ref.watch(associationMemberListProvider); From b8e9476d4f8d0869cd41b9d858daec2f33382eb0 Mon Sep 17 00:00:00 2001 From: Thonyk Date: Sun, 10 Aug 2025 16:08:42 +0200 Subject: [PATCH 234/473] fix: missing translations and clean up --- lib/l10n/app_en.arb | 15 +++ lib/l10n/app_fr.arb | 15 +++ lib/l10n/app_localizations.dart | 42 +++++++++ lib/l10n/app_localizations_en.dart | 25 +++++ lib/l10n/app_localizations_fr.dart | 25 +++++ .../components/association_research_bar.dart | 12 ++- .../ui/components/groupement_bar.dart | 13 ++- .../association_admin_edition_modal.dart | 94 ++++++++++--------- .../association_add_edit_page.dart | 3 +- .../association_groups_page.dart | 3 +- .../member_edition_modal.dart | 2 +- .../membership_editor_page.dart | 4 +- .../ui/styleguide/custom_dialog_box.dart | 26 +---- 13 files changed, 199 insertions(+), 80 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 56506b5043..727e2d4fb8 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -671,13 +671,24 @@ } }, "phonebookChangeTermConfirm": "Are you sure you want to change the entire term?\nThis action is irreversible!", + "phonebookClose": "Close", "phonebookConfirm": "Confirm", "phonebookCopied": "Copied to clipboard", "phonebookDeactivateAssociation": "Deactivate association", "phonebookDeactivatedAssociation": "Association deactivated", "phonebookDeactivatedAssociationWarning": "Warning, this association is deactivated, you cannot modify it", + "phonebookDeactivateSelectedAssociation": "Désactiver l'association {association} ?", + "@phonebookDeactivateSelectedAssociation": { + "description": "Permet de désactiver une association", + "placeholders": { + "association": { + "type": "String" + } + } + }, "phonebookDeactivatingError": "Error during deactivation", "phonebookDetail": "Details:", + "phonebookDelete": "Delete", "phonebookDeleteAssociation": "Delete association", "phonebookDeleteSelectedAssociation": "Delete the association {association}?", "@phonebookDeleteSelectedAssociation": { @@ -726,7 +737,11 @@ "phonebookErrorLoadProfilePicture": "Error", "phonebookErrorRoleTagsLoading": "Error loading role tags", "phonebookExistingMembership": "This member is already in the current term", + "phonebookFilter": "Filter", + "phonebookFilterDescription": "Filter the associations by their groupement", "phonebookFirstname": "First name:", + "phonebookGroupementDeleted": "Association groupement deleted", + "phonebookGroupementDeleteError": "Error deleting association groupement", "phonebookGroupementName": "Groupement name", "phonebookGroups": "Manage {association} groups", "@phonebookGroups": { diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 4daa252489..3a7d44ff46 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -671,13 +671,24 @@ } }, "phonebookChangeTermConfirm": "Êtes-vous sûr de vouloir changer tout le mandat ?\nCette action est irréversible !", + "phonebookClose": "Fermer", "phonebookConfirm": "Confirmer", "phonebookCopied": "Copié dans le presse-papier", "phonebookDeactivateAssociation": "Désactiver l'association", "phonebookDeactivatedAssociation": "Association désactivée", "phonebookDeactivatedAssociationWarning": "Attention, cette association est désactivée, vous ne pouvez pas la modifier", + "phonebookDeactivateSelectedAssociation": "Désactiver l'association {association} ?", + "@phonebookDeactivateSelectedAssociation": { + "description": "Permet de désactiver une association", + "placeholders": { + "association": { + "type": "String" + } + } + }, "phonebookDeactivatingError": "Erreur lors de la désactivation", "phonebookDetail": "Détail :", + "phonebookDelete": "Supprimer", "phonebookDeleteAssociation": "Supprimer l'association", "phonebookDeleteSelectedAssociation": "Supprimer l'association {association} ?", "@phonebookDeleteSelectedAssociation": { @@ -726,7 +737,11 @@ "phonebookErrorLoadProfilePicture": "Erreur", "phonebookErrorRoleTagsLoading": "Erreur lors du chargement des tags de rôle", "phonebookExistingMembership": "Ce membre est déjà dans le mandat actuel", + "phonebookFilter": "Filtrer", + "phonebookFilterDescription": "Sélectionnez un ou plusieurs groupements pour filtrer les associations.", "phonebookFirstname": "Prénom :", + "phonebookGroupementDeleted": "Groupement d'association supprimé", + "phonebookGroupementDeleteError": "Erreur lors de la suppression du groupement d'association", "phonebookGroupementName": "Nom du groupement", "phonebookGroups": "Gérer les groupes de {association}", "@phonebookGroups": { diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 0e135d6298..6367f0bd07 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -4028,6 +4028,12 @@ abstract class AppLocalizations { /// **'Êtes-vous sûr de vouloir changer tout le mandat ?\nCette action est irréversible !'** String get phonebookChangeTermConfirm; + /// No description provided for @phonebookClose. + /// + /// In fr, this message translates to: + /// **'Fermer'** + String get phonebookClose; + /// No description provided for @phonebookConfirm. /// /// In fr, this message translates to: @@ -4058,6 +4064,12 @@ abstract class AppLocalizations { /// **'Attention, cette association est désactivée, vous ne pouvez pas la modifier'** String get phonebookDeactivatedAssociationWarning; + /// Permet de désactiver une association + /// + /// In fr, this message translates to: + /// **'Désactiver l\'association {association} ?'** + String phonebookDeactivateSelectedAssociation(String association); + /// No description provided for @phonebookDeactivatingError. /// /// In fr, this message translates to: @@ -4070,6 +4082,12 @@ abstract class AppLocalizations { /// **'Détail :'** String get phonebookDetail; + /// No description provided for @phonebookDelete. + /// + /// In fr, this message translates to: + /// **'Supprimer'** + String get phonebookDelete; + /// No description provided for @phonebookDeleteAssociation. /// /// In fr, this message translates to: @@ -4262,12 +4280,36 @@ abstract class AppLocalizations { /// **'Ce membre est déjà dans le mandat actuel'** String get phonebookExistingMembership; + /// No description provided for @phonebookFilter. + /// + /// In fr, this message translates to: + /// **'Filtrer'** + String get phonebookFilter; + + /// No description provided for @phonebookFilterDescription. + /// + /// In fr, this message translates to: + /// **'Sélectionnez un ou plusieurs groupements pour filtrer les associations.'** + String get phonebookFilterDescription; + /// No description provided for @phonebookFirstname. /// /// In fr, this message translates to: /// **'Prénom :'** String get phonebookFirstname; + /// No description provided for @phonebookGroupementDeleted. + /// + /// In fr, this message translates to: + /// **'Groupement d\'association supprimé'** + String get phonebookGroupementDeleted; + + /// No description provided for @phonebookGroupementDeleteError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de la suppression du groupement d\'association'** + String get phonebookGroupementDeleteError; + /// No description provided for @phonebookGroupementName. /// /// In fr, this message translates to: diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index fe8cd3d15f..f3cba94652 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -2004,6 +2004,9 @@ class AppLocalizationsEn extends AppLocalizations { String get phonebookChangeTermConfirm => 'Are you sure you want to change the entire term?\nThis action is irreversible!'; + @override + String get phonebookClose => 'Close'; + @override String get phonebookConfirm => 'Confirm'; @@ -2020,12 +2023,20 @@ class AppLocalizationsEn extends AppLocalizations { String get phonebookDeactivatedAssociationWarning => 'Warning, this association is deactivated, you cannot modify it'; + @override + String phonebookDeactivateSelectedAssociation(String association) { + return 'Désactiver l\'association $association ?'; + } + @override String get phonebookDeactivatingError => 'Error during deactivation'; @override String get phonebookDetail => 'Details:'; + @override + String get phonebookDelete => 'Delete'; + @override String get phonebookDeleteAssociation => 'Delete association'; @@ -2134,9 +2145,23 @@ class AppLocalizationsEn extends AppLocalizations { String get phonebookExistingMembership => 'This member is already in the current term'; + @override + String get phonebookFilter => 'Filter'; + + @override + String get phonebookFilterDescription => + 'Filter the associations by their groupement'; + @override String get phonebookFirstname => 'First name:'; + @override + String get phonebookGroupementDeleted => 'Association groupement deleted'; + + @override + String get phonebookGroupementDeleteError => + 'Error deleting association groupement'; + @override String get phonebookGroupementName => 'Groupement name'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 54415b867e..b11d4e06f0 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -2013,6 +2013,9 @@ class AppLocalizationsFr extends AppLocalizations { String get phonebookChangeTermConfirm => 'Êtes-vous sûr de vouloir changer tout le mandat ?\nCette action est irréversible !'; + @override + String get phonebookClose => 'Fermer'; + @override String get phonebookConfirm => 'Confirmer'; @@ -2029,12 +2032,20 @@ class AppLocalizationsFr extends AppLocalizations { String get phonebookDeactivatedAssociationWarning => 'Attention, cette association est désactivée, vous ne pouvez pas la modifier'; + @override + String phonebookDeactivateSelectedAssociation(String association) { + return 'Désactiver l\'association $association ?'; + } + @override String get phonebookDeactivatingError => 'Erreur lors de la désactivation'; @override String get phonebookDetail => 'Détail :'; + @override + String get phonebookDelete => 'Supprimer'; + @override String get phonebookDeleteAssociation => 'Supprimer l\'association'; @@ -2147,9 +2158,23 @@ class AppLocalizationsFr extends AppLocalizations { String get phonebookExistingMembership => 'Ce membre est déjà dans le mandat actuel'; + @override + String get phonebookFilter => 'Filtrer'; + + @override + String get phonebookFilterDescription => + 'Sélectionnez un ou plusieurs groupements pour filtrer les associations.'; + @override String get phonebookFirstname => 'Prénom :'; + @override + String get phonebookGroupementDeleted => 'Groupement d\'association supprimé'; + + @override + String get phonebookGroupementDeleteError => + 'Erreur lors de la suppression du groupement d\'association'; + @override String get phonebookGroupementName => 'Nom du groupement'; diff --git a/lib/phonebook/ui/components/association_research_bar.dart b/lib/phonebook/ui/components/association_research_bar.dart index 83414c5c59..e89f234ed4 100644 --- a/lib/phonebook/ui/components/association_research_bar.dart +++ b/lib/phonebook/ui/components/association_research_bar.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/phonebook/providers/association_groupement_list_provider.dart'; import 'package:titan/phonebook/providers/research_filter_provider.dart'; import 'package:titan/phonebook/ui/components/groupement_bar.dart'; @@ -17,6 +18,9 @@ class AssociationResearchBar extends HookConsumerWidget { final associaiontGroupementList = ref.watch( associationGroupementListProvider, ); + + final localizeWithContext = AppLocalizations.of(context)!; + return AsyncChild( value: associaiontGroupementList, builder: (context, groupements) { @@ -30,12 +34,12 @@ class AssociationResearchBar extends HookConsumerWidget { ref: ref, context: context, modal: BottomModalTemplate( - title: "Filtrer", - description: - "Sélectionnez un ou plusieurs groupements pour filtrer les associations.", + title: localizeWithContext.phonebookFilter, + description: localizeWithContext.phonebookFilterDescription, + // "Sélectionnez un ou plusieurs groupements pour filtrer les associations.", actions: [ Button( - text: "Fermer", + text: localizeWithContext.phonebookClose, onPressed: () => Navigator.pop(context), ), ], diff --git a/lib/phonebook/ui/components/groupement_bar.dart b/lib/phonebook/ui/components/groupement_bar.dart index 4923417471..c44b4899cd 100644 --- a/lib/phonebook/ui/components/groupement_bar.dart +++ b/lib/phonebook/ui/components/groupement_bar.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/phonebook/class/association_groupement.dart'; import 'package:titan/phonebook/providers/association_groupement_provider.dart'; import 'package:titan/phonebook/providers/association_groupement_list_provider.dart'; @@ -46,6 +47,8 @@ class AssociationGroupementBar extends HookConsumerWidget { Navigator.of(context).pop(); } + final localizeWithContext = AppLocalizations.of(context)!; + void showEditDialog(AssociationGroupement item) => showCustomBottomModal( ref: ref, context: context, @@ -53,7 +56,7 @@ class AssociationGroupementBar extends HookConsumerWidget { title: item.name, actions: [ Button( - text: "Modifier", + text: localizeWithContext.phonebookEdit, onPressed: () { associationGroupementNotifier.setAssociationGroupement(item); QR.to( @@ -66,17 +69,19 @@ class AssociationGroupementBar extends HookConsumerWidget { ), SizedBox(height: 30), Button.danger( - text: "Supprimer", + text: localizeWithContext.phonebookDelete, onPressed: () async { final result = await associationGroupementListNotifier .deleteAssociationGroupement(item); if (result && context.mounted) { popWithContext(); - showSnackBarWithContext("Groupe supprimé"); + showSnackBarWithContext( + localizeWithContext.phonebookGroupementDeleted, + ); } if (!result && context.mounted) { showSnackBarWithContext( - "Une erreur est survenue lors de la suppression du groupe", + localizeWithContext.phonebookGroupementDeleteError, ); } }, diff --git a/lib/phonebook/ui/pages/admin_page/association_admin_edition_modal.dart b/lib/phonebook/ui/pages/admin_page/association_admin_edition_modal.dart index 113624c7c0..c0c4e48710 100644 --- a/lib/phonebook/ui/pages/admin_page/association_admin_edition_modal.dart +++ b/lib/phonebook/ui/pages/admin_page/association_admin_edition_modal.dart @@ -111,7 +111,7 @@ class AssociationAdminEditionModal extends HookConsumerWidget { showCustomBottomModal( context: context, ref: ref, - modal: CustomDialogBox.danger( + modal: ConfirmModal.danger( title: localizeWithContext.phonebookChangeTermYear( association.mandateYear + 1, ), @@ -146,47 +146,57 @@ class AssociationAdminEditionModal extends HookConsumerWidget { : localizeWithContext.phonebookDeactivateAssociation, onPressed: () async { Navigator.of(context).pop(); - if (!association.deactivated) { - final result = await associationListNotifier - .deactivateAssociation(association); - if (result) { - displayToastWithContext( - TypeMsg.msg, - localizeWithContext.phonebookDeactivatedAssociation, - ); - } else { - displayToastWithContext( - TypeMsg.error, - localizeWithContext.phonebookDeactivatingError, - ); - } - } else { - showCustomBottomModal( - context: context, - ref: ref, - modal: CustomDialogBox.danger( - title: localizeWithContext - .phonebookDeleteSelectedAssociation(association.name), - description: localizeWithContext - .phonebookDeleteAssociationDescription, - onYes: () async { - final result = await associationListNotifier - .deleteAssociation(association); - if (result) { - displayToastWithContext( - TypeMsg.msg, - localizeWithContext.phonebookDeletedAssociation, - ); - } else { - displayToastWithContext( - TypeMsg.error, - localizeWithContext.phonebookDeletingError, - ); - } - }, - ), - ); - } + showCustomBottomModal( + context: context, + ref: ref, + modal: ConfirmModal.danger( + title: association.deactivated + ? localizeWithContext + .phonebookDeleteSelectedAssociation( + association.name, + ) + : localizeWithContext + .phonebookDeactivateSelectedAssociation( + association.name, + ), + description: association.deactivated + ? localizeWithContext + .phonebookDeleteAssociationDescription + : localizeWithContext.globalIrreversibleAction, + onYes: association.deactivated + ? () async { + final result = await associationListNotifier + .deactivateAssociation(association); + if (result) { + displayToastWithContext( + TypeMsg.msg, + localizeWithContext + .phonebookDeactivatedAssociation, + ); + } else { + displayToastWithContext( + TypeMsg.error, + localizeWithContext.phonebookDeactivatingError, + ); + } + } + : () async { + final result = await associationListNotifier + .deleteAssociation(association); + if (result) { + displayToastWithContext( + TypeMsg.msg, + localizeWithContext.phonebookDeletedAssociation, + ); + } else { + displayToastWithContext( + TypeMsg.error, + localizeWithContext.phonebookDeletingError, + ); + } + }, + ), + ); }, ), ], diff --git a/lib/phonebook/ui/pages/association_add_edit_page/association_add_edit_page.dart b/lib/phonebook/ui/pages/association_add_edit_page/association_add_edit_page.dart index e13d0d1ba2..d8b674c67e 100644 --- a/lib/phonebook/ui/pages/association_add_edit_page/association_add_edit_page.dart +++ b/lib/phonebook/ui/pages/association_add_edit_page/association_add_edit_page.dart @@ -20,8 +20,7 @@ import 'package:titan/tools/ui/styleguide/button.dart'; import 'package:titan/tools/ui/styleguide/text_entry.dart'; class AssociationAddEditPage extends HookConsumerWidget { - final scrollKey = GlobalKey(); - AssociationAddEditPage({super.key}); + const AssociationAddEditPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { diff --git a/lib/phonebook/ui/pages/association_groups_page/association_groups_page.dart b/lib/phonebook/ui/pages/association_groups_page/association_groups_page.dart index 52419505c7..16f986107f 100644 --- a/lib/phonebook/ui/pages/association_groups_page/association_groups_page.dart +++ b/lib/phonebook/ui/pages/association_groups_page/association_groups_page.dart @@ -17,8 +17,7 @@ import 'package:titan/tools/ui/styleguide/button.dart'; import 'package:titan/tools/ui/styleguide/list_item_toggle.dart'; class AssociationGroupsPage extends HookConsumerWidget { - final scrollKey = GlobalKey(); - AssociationGroupsPage({super.key}); + const AssociationGroupsPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { diff --git a/lib/phonebook/ui/pages/association_members_page/member_edition_modal.dart b/lib/phonebook/ui/pages/association_members_page/member_edition_modal.dart index d195d56d81..e25d11ab0d 100644 --- a/lib/phonebook/ui/pages/association_members_page/member_edition_modal.dart +++ b/lib/phonebook/ui/pages/association_members_page/member_edition_modal.dart @@ -77,7 +77,7 @@ class MemberEditionModal extends HookConsumerWidget { showCustomBottomModal( context: context, ref: ref, - modal: CustomDialogBox.danger( + modal: ConfirmModal.danger( title: localizeWithContext.phonebookDeleteUserRole( member.member.nickname ?? member.getName(), ), diff --git a/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart b/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart index 26837c04a6..b2a700decb 100644 --- a/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart +++ b/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart @@ -49,7 +49,7 @@ class MembershipEditorPage extends HookConsumerWidget { final localizeWithContext = AppLocalizations.of(context)!; - Future addMember() async { + Future addMember() async { final memberAssociationMemberships = member.memberships.where( (membership) => membership.associationId == association.id, ); @@ -96,7 +96,7 @@ class MembershipEditorPage extends HookConsumerWidget { } } - Future updateMember() async { + Future updateMember() async { final membershipEdit = Membership( id: membership.id, memberId: membership.memberId, diff --git a/lib/tools/ui/styleguide/custom_dialog_box.dart b/lib/tools/ui/styleguide/custom_dialog_box.dart index 272c9f5f88..4a2195e5e4 100644 --- a/lib/tools/ui/styleguide/custom_dialog_box.dart +++ b/lib/tools/ui/styleguide/custom_dialog_box.dart @@ -1,7 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/l10n/app_localizations.dart'; -import 'package:titan/navigation/providers/navbar_animation.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; import 'package:titan/tools/ui/styleguide/button.dart'; @@ -9,14 +7,14 @@ const double padding = 20.0; enum ModalType { main, danger } -class CustomDialogBox extends StatelessWidget { +class ConfirmModal extends StatelessWidget { final String title, description; final String? yesText, noText; final ModalType type; final Function() onYes; final Function()? onNo; - const CustomDialogBox({ + const ConfirmModal({ super.key, required this.title, required this.description, @@ -27,7 +25,7 @@ class CustomDialogBox extends StatelessWidget { this.noText, }); - const CustomDialogBox.danger({ + const ConfirmModal.danger({ super.key, required this.title, required this.description, @@ -80,21 +78,3 @@ class CustomDialogBox extends StatelessWidget { ); } } - -Future showCustomDialog({ - required BuildContext context, - required Widget dialog, - required WidgetRef ref, - Function? onCloseCallback, -}) async { - final navbarAnimationNotifier = ref.watch(navbarAnimationProvider.notifier); - navbarAnimationNotifier.toggle(); - await showDialog( - useRootNavigator: true, - context: context, - builder: (_) => dialog, - ).then((value) { - navbarAnimationNotifier.toggle(); - onCloseCallback?.call(); - }); -} From 099408b5296f7920f8dd517dda2ec6952a2af603 Mon Sep 17 00:00:00 2001 From: Thonyk Date: Sun, 10 Aug 2025 16:24:01 +0200 Subject: [PATCH 235/473] fix: indent --- lib/l10n/app_en.arb | 2700 +++++++++++++++++++++---------------------- lib/l10n/app_fr.arb | 2700 +++++++++++++++++++++---------------------- 2 files changed, 2700 insertions(+), 2700 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 727e2d4fb8..8a82b16fd3 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1,1352 +1,1352 @@ { - "@@locale": "en", - "adminAccountTypes": "Account types", - "adminAdd": "Add", - "adminAddGroup": "Add group", - "adminAddMember": "Add member", - "adminAddedGroup": "Group created", - "adminAddedLoaner": "Lender added", - "adminAddedMember": "Member added", - "adminAddingError": "Error while adding", - "adminAddingMember": "Adding a member", - "adminAddLoaningGroup": "Add loaning group", - "adminAddSchool": "Add school", - "adminAddStructure": "Add structure", - "adminAddedSchool": "School created", - "adminAddedStructure": "Structure added", - "adminEditedStructure": "Structure edited", - "adminAdministration": "Administration", - "adminAssociationMembership": "Membership", - "adminAssociationMembershipName": "Membership name", - "adminAssociationsMemberships": "Memberships", - "adminClearFilters": "Clear filters", - "adminCreateAssociationMembership": "Create membership", - "adminCreatedAssociationMembership": "Membership created", - "adminCreationError": "Error during creation", - "adminDateError": "Start date must be before end date", - "adminDelete": "Delete", - "adminDeleteAssociationMembership": "Delete membership?", - "adminDeletedAssociationMembership": "Membership deleted", - "adminDeleteGroup": "Delete group?", - "adminDeletedGroup": "Group deleted", - "adminDeleteSchool": "Delete school?", - "adminDeletedSchool": "School deleted", - "adminDeleting": "Deleting", - "adminDeletingError": "Error while deleting", - "adminDescription": "Description", - "adminEclSchool": "Centrale Lyon", - "adminEdit": "Edit", - "adminEditStructure": "Edit structure", - "adminEditMembership": "Edit membership", - "adminEmptyDate": "Empty date", - "adminEmptyFieldError": "Name cannot be empty", - "adminEmailRegex": "Email Regex", - "adminEmptyUser": "Empty user", - "adminEndDate": "End date", - "adminEndDateMaximal": "Maximum end date", - "adminEndDateMinimal": "Minimum end date", - "adminError": "Error", - "adminFilters": "Filters", - "adminGroup": "Group", - "adminGroups": "Groups", - "adminLoaningGroup": "Loaning group", - "adminLooking": "Searching", - "adminManager": "Structure administrator", - "adminMaximum": "Maximum", - "adminMembers": "Members", - "adminMembershipAddingError": "Error while adding (likely due to overlapping dates)", - "adminMemberships": "Memberships", - "adminMembershipUpdatingError": "Error while updating (likely due to overlapping dates)", - "adminMinimum": "Minimum", - "adminModifyModuleVisibility": "Module visibility", - "adminMyEclPay": "MyECLPay", - "adminName": "Name", - "adminNoManager": "No manager selected", - "adminNoMember": "No member", - "adminNoMoreLoaner": "No lender available", - "adminNoSchool": "No school", - "adminRemoveGroupMember": "Remove member from group?", - "adminResearch": "Search", - "adminSchools": "Schools", - "adminStructures": "Structures", - "adminStartDate": "Start date", - "adminStartDateMaximal": "Maximum start date", - "adminStartDateMinimal": "Minimum start date", - "adminUpdatedAssociationMembership": "Membership updated", - "adminUpdatedGroup": "Group updated", - "adminUpdatedMembership": "Membership updated", - "adminUpdatingError": "Error while updating", - "adminUser": "User", - "adminValidateFilters": "Apply filters", - "adminVisibilities": "Visibilities", - "advertAdd": "Add", - "advertAddedAdvert": "Advert published", - "advertAddedAnnouncer": "Announcer added", - "advertAddingError": "Error while adding", - "advertAdmin": "Admin", - "advertAdvert": "Advert", - "advertChoosingAnnouncer": "Please choose an announcer", - "advertChoosingPoster": "Please choose an image", - "advertContent": "Content", - "advertDeleteAdvert": "Delete ad?", - "advertDeleteAnnouncer": "Delete announcer?", - "advertDeleting": "Deleting", - "advertEdit": "Edit", - "advertEditedAdvert": "Advert edited", - "advertEditingError": "Error while editing", - "advertGroupAdvert": "Group", - "advertIncorrectOrMissingFields": "Incorrect or missing fields", - "advertInvalidNumber": "Please enter a number", - "advertManagement": "Management", - "advertModifyAnnouncingGroup": "Edit announcement group", - "advertNoMoreAnnouncer": "No more announcers available", - "advertNoValue": "Please enter a value", - "advertPositiveNumber": "Please enter a positive number", - "advertRemovedAnnouncer": "Announcer removed", - "advertRemovingError": "Error during removal", - "advertTags": "Tags", - "advertTitle": "Title", - "advertMonthJan": "Jan", - "advertMonthFeb": "Feb", - "advertMonthMar": "Mar", - "advertMonthApr": "Apr", - "advertMonthMay": "May", - "advertMonthJun": "Jun", - "advertMonthJul": "Jul", - "advertMonthAug": "Aug", - "advertMonthSep": "Sep", - "advertMonthOct": "Oct", - "advertMonthNov": "Nov", - "advertMonthDec": "Dec", - "amapAccounts": "Accounts", - "amapAdd": "Add", - "amapAddDelivery": "Add delivery", - "amapAddedCommand": "Order added", - "amapAddedOrder": "Order added", - "amapAddedProduct": "Product added", - "amapAddedUser": "User added", - "amapAddProduct": "Add product", - "amapAddUser": "Add user", - "amapAddingACommand": "Add an order", - "amapAddingCommand": "Add the order", - "amapAddingError": "Error while adding", - "amapAddingProduct": "Add a product", - "amapAddOrder": "Add an order", - "amapAdmin": "Admin", - "amapAlreadyExistCommand": "An order already exists for this date", - "amapAmap": "Amap", - "amapAmount": "Balance", - "amapArchive": "Archive", - "amapArchiveDelivery": "Archive", - "amapArchivingDelivery": "Archiving delivery", - "amapCategory": "Category", - "amapCloseDelivery": "Lock", - "amapCommandDate": "Order date", - "amapCommandProducts": "Order products", - "amapConfirm": "Confirm", - "amapContact": "Association contacts", - "amapCreateCategory": "Create category", - "amapDelete": "Delete", - "amapDeleteDelivery": "Delete delivery?", - "amapDeleteDeliveryDescription": "Are you sure you want to delete this delivery?", - "amapDeletedDelivery": "Delivery deleted", - "amapDeletedOrder": "Order deleted", - "amapDeletedProduct": "Product deleted", - "amapDeleteProduct": "Delete product?", - "amapDeleteProductDescription": "Are you sure you want to delete this product?", - "amapDeleting": "Deleting", - "amapDeletingDelivery": "Delete delivery?", - "amapDeletingError": "Error while deleting", - "amapDeletingOrder": "Delete order?", - "amapDeletingProduct": "Delete product?", - "amapDeliver": "Delivery completed?", - "amapDeliveries": "Deliveries", - "amapDeliveringDelivery": "Are all orders delivered?", - "amapDelivery": "Delivery", - "amapDeliveryArchived": "Delivery archived", - "amapDeliveryDate": "Delivery date", - "amapDeliveryDelivered": "Delivery completed", - "amapDeliveryHistory": "Delivery history", - "amapDeliveryList": "Delivery list", - "amapDeliveryLocked": "Delivery locked", - "amapDeliveryOn": "Delivery on", - "amapDeliveryOpened": "Delivery opened", - "amapDeliveryNotArchived": "Delivery not archived", - "amapDeliveryNotLocked": "Delivery not locked", - "amapDeliveryNotDelivered": "Delivery not completed", - "amapDeliveryNotOpened": "Delivery not opened", - "amapEditDelivery": "Edit delivery", - "amapEditedCommand": "Order edited", - "amapEditingError": "Error while editing", - "amapEditProduct": "Edit product", - "amapEndingDelivery": "End of delivery", - "amapError": "Error", - "amapErrorLink": "Error opening link", - "amapErrorLoadingUser": "Error loading users", - "amapEvening": "Evening", - "amapExpectingNumber": "Please enter a number", - "amapFillField": "Please fill out this field", - "amapHandlingAccount": "Manage accounts", - "amapLoading": "Loading...", - "amapLoadingError": "Loading error", - "amapLock": "Lock", - "amapLocked": "Locked", - "amapLockedDelivery": "Delivery locked", - "amapLockedOrder": "Order locked", - "amapLooking": "Search", - "amapLockingDelivery": "Lock delivery?", - "amapMidDay": "Midday", - "amapMyOrders": "My orders", - "amapName": "Name", - "amapNextStep": "Next step", - "amapNoProduct": "No product", - "amapNoCurrentOrder": "No current order", - "amapNoMoney": "Not enough money", - "amapNoOpennedDelivery": "No open delivery", - "amapNoOrder": "No order", - "amapNoSelectedDelivery": "No delivery selected", - "amapNotEnoughMoney": "Not enough money", - "amapNotPlannedDelivery": "No scheduled delivery", - "amapOneOrder": "order", - "amapOpenDelivery": "Open", - "amapOpened": "Opened", - "amapOpenningDelivery": "Open delivery?", - "amapOrder": "Order", - "amapOrders": "Orders", - "amapPickChooseCategory": "Please enter a value or choose an existing category", - "amapPickDeliveryMoment": "Choose a delivery time", - "amapPresentation": "Presentation", - "amapPresentation1": "The AMAP (association for the preservation of small-scale farming) is a service offered by the Planet&Co association of ECL. You can receive products (fruit and vegetable baskets, juices, jams...) directly on campus!\n\nOrders must be placed before Friday at 9 PM and are delivered on campus on Tuesday from 1:00 PM to 1:45 PM (or from 6:15 PM to 6:30 PM if you can't come at midday) in the M16 hall.\n\nYou can only order if your balance allows it. You can top up your balance via the Lydia collection or by cheque during office hours.\n\nLydia top-up link: ", - "amapPresentation2": "\n\nFeel free to contact us if you have any issues!", - "amapPrice": "Price", - "amapProduct": "product", - "amapProducts": "Products", - "amapProductInDelivery": "Product in an unfinished delivery", - "amapQuantity": "Quantity", - "amapRequiredDate": "Date is required", - "amapSeeMore": "See more", - "amapThe": "The", - "amapUnlock": "Unlock", - "amapUnlockedDelivery": "Delivery unlocked", - "amapUnlockingDelivery": "Unlock delivery?", - "amapUpdate": "Edit", - "amapUpdatedAmount": "Balance updated", - "amapUpdatedOrder": "Order updated", - "amapUpdatedProduct": "Product updated", - "amapUpdatingError": "Update failed", - "amapUsersNotFound": "No users found", - "amapWaiting": "Pending", - "bookingAdd": "Add", - "bookingAddBookingPage": "Request", - "bookingAddRoom": "Add room", - "bookingAddBooking": "Add booking", - "bookingAddedBooking": "Request added", - "bookingAddedRoom": "Room added", - "bookingAddedManager": "Manager added", - "bookingAddingError": "Error while adding", - "bookingAddManager": "Add manager", - "bookingAdminPage": "Admin", - "bookingAllDay": "All day", - "bookingBookedFor": "Booked for", - "bookingBooking": "Booking", - "bookingBookingCreated": "Booking created", - "bookingBookingDemand": "Booking request", - "bookingBookingNote": "Booking note", - "bookingBookingPage": "Booking", - "bookingBookingReason": "Booking reason", - "bookingBy": "by", - "bookingConfirm": "Confirm", - "bookingConfirmation": "Confirmation", - "bookingConfirmBooking": "Confirm the booking?", - "bookingConfirmed": "Confirmed", - "bookingDates": "Dates", - "bookingDecline": "Decline", - "bookingDeclineBooking": "Decline the booking?", - "bookingDeclined": "Declined", - "bookingDelete": "Delete", - "bookingDeleting": "Deleting", - "bookingDeleteBooking": "Deleting", - "bookingDeleteBookingConfirmation": "Are you sure you want to delete this booking?", - "bookingDeletedBooking": "Booking deleted", - "bookingDeletedRoom": "Room deleted", - "bookingDeletedManager": "Manager deleted", - "bookingDeleteRoomConfirmation": "Are you sure you want to delete this room?\n\nThe room must have no current or upcoming bookings to be deleted", - "bookingDeleteManagerConfirmation": "Are you sure you want to delete this manager?\n\nThe manager must not be associated with any room to be deleted", - "bookingDeletingBooking": "Delete the booking?", - "bookingDeletingError": "Error while deleting", - "bookingDeletingRoom": "Delete the room?", - "bookingEdit": "Edit", - "bookingEditBooking": "Edit a booking", - "bookingEditionError": "Error while editing", - "bookingEditedBooking": "Booking edited", - "bookingEditedRoom": "Room edited", - "bookingEditedManager": "Manager edited", - "bookingEditManager": "Edit or delete a manager", - "bookingEditRoom": "Edit or delete a room", - "bookingEndDate": "End date", - "bookingEndHour": "End hour", - "bookingEntity": "For whom?", - "bookingError": "Error", - "bookingEventEvery": "Every", - "bookingHistoryPage": "History", - "bookingIncorrectOrMissingFields": "Incorrect or missing fields", - "bookingInterval": "Interval", - "bookingInvalidIntervalError": "Invalid interval", - "bookingInvalidDates": "Invalid dates", - "bookingInvalidRoom": "Invalid room", - "bookingKeysRequested": "Keys requested", - "bookingManagement": "Management", - "bookingManager": "Manager", - "bookingManagerName": "Manager name", - "bookingMultipleDay": "Multiple days", - "bookingMyBookings": "My bookings", - "bookingNecessaryKey": "Key needed", - "bookingNext": "Next", - "bookingNo": "No", - "bookingNoCurrentBooking": "No current booking", - "bookingNoDateError": "Please choose a date", - "bookingNoAppointmentInReccurence": "No slot exists with these recurrence settings", - "bookingNoDaySelected": "No day selected", - "bookingNoDescriptionError": "Please enter a description", - "bookingNoKeys": "No keys", - "bookingNoNoteError": "Please enter a note", - "bookingNoPhoneRegistered": "Number not provided", - "bookingNoReasonError": "Please enter a reason", - "bookingNoRoomFoundError": "No room registered", - "bookingNoRoomFound": "No room found", - "bookingNote": "Note", - "bookingOther": "Other", - "bookingPending": "Pending", - "bookingPrevious": "Previous", - "bookingReason": "Reason", - "bookingRecurrence": "Recurrence", - "bookingRecurrenceDays": "Recurrence days", - "bookingRecurrenceEndDate": "Recurrence end date", - "bookingRecurrent": "Recurrent", - "bookingRegisteredRooms": "Registered rooms", - "bookingRoom": "Room", - "bookingRoomName": "Room name", - "bookingStartDate": "Start date", - "bookingStartHour": "Start hour", - "bookingWeeks": "Weeks", - "bookingYes": "Yes", - "bookingWeekDayMon": "Monday", - "bookingWeekDayTue": "Tuesday", - "bookingWeekDayWed": "Wednesday", - "bookingWeekDayThu": "Thursday", - "bookingWeekDayFri": "Friday", - "bookingWeekDaySat": "Saturday", - "bookingWeekDaySun": "Sunday", - "cinemaAdd": "Add", - "cinemaAddedSession": "Session added", - "cinemaAddingError": "Error while adding", - "cinemaAddSession": "Add a session", - "cinemaCinema": "Cinema", - "cinemaDeleteSession": "Delete the session?", - "cinemaDeleting": "Deleting", - "cinemaDuration": "Duration", - "cinemaEdit": "Edit", - "cinemaEditedSession": "Session edited", - "cinemaEditingError": "Error while editing", - "cinemaEditSession": "Edit the session", - "cinemaEmptyUrl": "Please enter a URL", - "cinemaImportFromTMDB": "Import from TMDB", - "cinemaIncomingSession": "Now showing", - "cinemaIncorrectOrMissingFields": "Incorrect or missing fields", - "cinemaInvalidUrl": "Invalid URL", - "cinemaGenre": "Genre", - "cinemaName": "Name", - "cinemaNoDateError": "Please enter a date", - "cinemaNoDuration": "Please enter a duration", - "cinemaNoOverview": "No synopsis", - "cinemaNoPoster": "No poster", - "cinemaNoSession": "No session", - "cinemaOverview": "Synopsis", - "cinemaPosterUrl": "Poster URL", - "cinemaSessionDate": "Session day", - "cinemaStartHour": "Start hour", - "cinemaTagline": "Tagline", - "cinemaThe": "The", - "drawerAdmin": "Administration", - "drawerAndroidAppLink": "https://play.google.com/store/apps/details?id=fr.myecl.titan", - "drawerCopied": "Copied!", - "drawerDownloadAppOnMobileDevice": "This site is the web version of the MyECL app. We invite you to download the app. Use this site only if you have problems with the app.\n", - "drawerIosAppLink": "https://apps.apple.com/fr/app/myecl/id6444443430", - "drawerLoginOut": "Do you want to log out?", - "drawerLogOut": "Log out", - "drawerOr": " or ", - "drawerSettings": "Settings", - "eventAdd": "Add", - "eventAddEvent": "Add an event", - "eventAddedEvent": "Event added", - "eventAddingError": "Error while adding", - "eventAllDay": "All day", - "eventConfirm": "Confirm", - "eventConfirmEvent": "Confirm the event?", - "eventConfirmation": "Confirmation", - "eventConfirmed": "Confirmed", - "eventDates": "Dates", - "eventDecline": "Decline", - "eventDeclineEvent": "Decline the event?", - "eventDeclined": "Declined", - "eventDelete": "Delete", - "eventDeletedEvent": "Event deleted", - "eventDeleting": "Deleting", - "eventDeletingError": "Error while deleting", - "eventDeletingEvent": "Delete the event?", - "eventDescription": "Description", - "eventEdit": "Edit", - "eventEditEvent": "Edit an event", - "eventEditedEvent": "Event edited", - "eventEditingError": "Error while editing", - "eventEndDate": "End date", - "eventEndHour": "End hour", - "eventError": "Error", - "eventEventList": "Event list", - "eventEventType": "Event type", - "eventEvery": "Every", - "eventHistory": "History", - "eventIncorrectOrMissingFields": "Some fields are incorrect or missing", - "eventInterval": "Interval", - "eventInvalidDates": "End date must be after start date", - "eventInvalidIntervalError": "Please enter a valid interval", - "eventLocation": "Location", - "eventMyEvents": "My events", - "eventName": "Name", - "eventNext": "Next", - "eventNo": "No", - "eventNoCurrentEvent": "No current event", - "eventNoDateError": "Please enter a date", - "eventNoDaySelected": "No day selected", - "eventNoDescriptionError": "Please enter a description", - "eventNoEvent": "No event", - "eventNoNameError": "Please enter a name", - "eventNoOrganizerError": "Please enter an organizer", - "eventNoPlaceError": "Please enter a location", - "eventNoPhoneRegistered": "Number not provided", - "eventNoRuleError": "Please enter a recurrence rule", - "eventOrganizer": "Organizer", - "eventOther": "Other", - "eventPending": "Pending", - "eventPrevious": "Previous", - "eventRecurrence": "Recurrence", - "eventRecurrenceDays": "Recurrence days", - "eventRecurrenceEndDate": "Recurrence end date", - "eventRecurrenceRule": "Recurrence rule", - "eventRoom": "Room", - "eventStartDate": "Start date", - "eventStartHour": "Start hour", - "eventTitle": "Events", - "eventYes": "Yes", - "eventEventEvery": "Every", - "eventWeeks": "weeks", - "eventDayMon": "Monday", - "eventDayTue": "Tuesday", - "eventDayWed": "Wednesday", - "eventDayThu": "Thursday", - "eventDayFri": "Friday", - "eventDaySat": "Saturday", - "eventDaySun": "Sunday", - "globalConfirm": "Confirm", - "globalCancel": "Cancel", - "globalIrreversibleAction": "This action is irreversible", - "globalOptionnal": "{text} (Optional)", - "@globalOptionnal": { - "description": "Text with optional complement", - "placeholders": { - "text": { - "type": "String" - } - } - }, - "homeCalendar": "Calendar", - "homeEventOf": "Events of", - "homeIncomingEvents": "Upcoming events", - "homeLastInfos": "Latest announcements", - "homeNoEvents": "No events", - "homeTranslateDayShortMon": "Mon", - "homeTranslateDayShortTue": "Tue", - "homeTranslateDayShortWed": "Wed", - "homeTranslateDayShortThu": "Thu", - "homeTranslateDayShortFri": "Fri", - "homeTranslateDayShortSat": "Sat", - "homeTranslateDayShortSun": "Sun", - "loanAdd": "Add", - "loanAddLoan": "Add a loan", - "loanAddObject": "Add an object", - "loanAddedLoan": "Loan added", - "loanAddedObject": "Object added", - "loanAddedRoom": "Room added", - "loanAddingError": "Error while adding", - "loanAdmin": "Administrator", - "loanAvailable": "Available", - "loanAvailableMultiple": "Available", - "loanBorrowed": "Borrowed", - "loanBorrowedMultiple": "Borrowed", - "loanAnd": "and", - "loanAssociation": "Association", - "loanAvailableItems": "Available items", - "loanBeginDate": "Loan start date", - "loanBorrower": "Borrower", - "loanCaution": "Deposit", - "loanCancel": "Cancel", - "loanConfirm": "Confirm", - "loanConfirmation": "Confirmation", - "loanDates": "Dates", - "loanDays": "Days", - "loanDelay": "Extension delay", - "loanDelete": "Delete", - "loanDeletingLoan": "Delete the loan?", - "loanDeletedItem": "Object deleted", - "loanDeletedLoan": "Loan deleted", - "loanDeleting": "Deleting", - "loanDeletingError": "Error while deleting", - "loanDeletingItem": "Delete the object?", - "loanDuration": "Duration", - "loanEdit": "Edit", - "loanEditItem": "Edit the object", - "loanEditLoan": "Edit the loan", - "loanEditedRoom": "Room edited", - "loanEndDate": "Loan end date", - "loanEnded": "Ended", - "loanEnterDate": "Please enter a date", - "loanExtendedLoan": "Extended loan", - "loanExtendingError": "Error while extending", - "loanHistory": "History", - "loanIncorrectOrMissingFields": "Some fields are missing or incorrect", - "loanInvalidNumber": "Please enter a number", - "loanInvalidDates": "Dates are not valid", - "loanItem": "Item", - "loanItems": "Items", - "loanItemHandling": "Item management", - "loanItemSelected": "selected item", - "loanItemsSelected": "selected items", - "loanLendingDuration": "Possible loan duration", - "loanLoan": "Loan", - "loanLoanHandling": "Loan management", - "loanLooking": "Searching", - "loanName": "Name", - "loanNext": "Next", - "loanNo": "No", - "loanNoAssociationsFounded": "No associations found", - "loanNoAvailableItems": "No available items", - "loanNoBorrower": "No borrower", - "loanNoItems": "No items", - "loanNoItemSelected": "No item selected", - "loanNoLoan": "No loan", - "loanNoReturnedDate": "No return date", - "loanQuantity": "Quantity", - "loanNone": "None", - "loanNote": "Note", - "loanNoValue": "Please enter a value", - "loanOnGoing": "Ongoing", - "loanOnGoingLoan": "Ongoing loan", - "loanOthers": "others", - "loanPaidCaution": "Deposit paid", - "loanPositiveNumber": "Please enter a positive number", - "loanPrevious": "Previous", - "loanReturned": "Returned", - "loanReturnedLoan": "Returned loan", - "loanReturningError": "Error while returning", - "loanReturningLoan": "Return", - "loanReturnLoan": "Return the loan?", - "loanReturnLoanDescription": "Do you want to return this loan?", - "loanToReturn": "To return", - "loanUnavailable": "Unavailable", - "loanUpdate": "Edit", - "loanUpdatedItem": "Item updated", - "loanUpdatedLoan": "Loan updated", - "loanUpdatingError": "Error while updating", - "loanYes": "Yes", - "loginAccountActivated": "Account activated", - "loginAccountNotActivated": "Account not activated", - "loginActivationCode": "Activation code", - "loginBirthday": "Date of birth", - "loginCanBeEmpty": "This field can be empty", - "loginConfirmPassword": "Confirm password", - "loginCreate": "Create", - "loginCreateAccount": "Create an account", - "loginCreateAccountTitle": "Create an\naccount", - "loginEmail": "Email", - "loginEmailEmpty": "Please enter an email address", - "loginEmailInvalid": "Please enter a Centrale email address.\nIf you don't have one, please contact Éclair", - "loginEmptyFieldError": "This field cannot be empty", - "loginEndActivation": "Complete activation", - "loginEndResetPassword": "Complete\npassword reset", - "loginErrorResetPassword": "Error during reset", - "loginExpectingDate": "A date is expected", - "loginFillAllFields": "Please fill all fields", - "loginFirstname": "First name", - "loginFloor": "Floor", - "loginForgetPassword": "Forgot\npassword", - "loginForgotPassword": "Forgot password?", - "loginInvalidToken": "Invalid activation code", - "loginLoginFailed": "Login failed", - "loginMailSendingError": "Error during account creation", - "loginMustBeIntError": "This field must be an integer", - "loginName": "Last name", - "loginNewPassword": "New password", - "loginPassword": "Password", - "loginPasswordLengthError": "Password must be at least 6 characters", - "loginPasswordUppercaseError": "Password must contain at least one uppercase letter", - "loginPasswordLowercaseError": "Password must contain at least one lowercase letter", - "loginPasswordNumberError": "Password must contain at least one number", - "loginPasswordSpecialCaracterError": "Password must contain at least one special character", - "loginPasswordMustMatch": "Passwords must match", - "loginPasswordStrengthVeryWeak": "Very weak", - "loginPasswordStrengthWeak": "Weak", - "loginPasswordStrengthMedium": "Medium", - "loginPasswordStrengthStrong": "Strong", - "loginPasswordStrengthVeryStrong": "Very strong", - "loginPhone": "Phone", - "loginPromo": "Incoming class (e.g., 2023)", - "loginSendedMail": "Confirmation email sent", - "loginSendedResetMail": "Reset email sent", - "loginSignIn": "Sign in", - "loginRegister": "Register", - "loginRecievedMail": "I received the email", - "loginRecover": "Reset", - "loginResetedPassword": "Password reset", - "loginResetPasswordTitle": "Reset\npassword", - "loginNickname": "Nickname", - "loginWelcomeBack": "Welcome back", - "loginAppName": "MyECL", - "othersCheckInternetConnection": "Please check your internet connection", - "othersRetry": "Retry", - "othersTooOldVersion": "Your app version is too old.\n\nPlease update the app.", - "othersUnableToConnectToServer": "Unable to connect to the server", - "othersVersion": "Version", - "othersNoModule": "No modules available, please try again later 😢😢", - "othersAdmin": "Admin", - "othersError": "An error occurred", - "othersNoValue": "Please enter a value", - "othersInvalidNumber": "Please enter a number", - "othersNoDateError": "Please enter a date", - "othersImageSizeTooBig": "Image size must not exceed 4 MB", - "othersImageError": "Error adding the image", - "phAddNewJournal": "Add a new journal", - "phNameField": "Name: ", - "phDateField": "Date: ", - "phDelete": "Are you sure you want to delete this journal?", - "phIrreversibleAction": "This action is irreversible", - "phToHeavyFile": "File too large", - "phAddPdfFile": "Add a PDF file", - "phEditPdfFile": "Edit PDF file", - "phPhName": "PH name", - "phDate": "Date", - "phAdded": "Added", - "phEdited": "Edited", - "phAddingFileError": "Add error", - "phMissingInformatonsOrPdf": "Missing information or PDF file", - "phAdd": "Add", - "phEdit": "Edit", - "phSeePreviousJournal": "See previous journals", - "phNoJournalInDatabase": "No PH yet in database", - "phSuccesDowloading": "Successfully downloaded", - "phonebookAdd": "Add", - "phonebookAddAssociation": "Add an association", - "phonebookAddAssociationGroupement": "Add an association groupement", - "phonebookAddedAssociation": "Association added", - "phonebookAddedMember": "Member added", - "phonebookAddingError": "Error adding", - "phonebookAddMember": "Add a member", - "phonebookAddRole": "Add a role", - "phonebookAdmin": "Administation", - "phonebookAll": "All", - "phonebookApparentName": "Public role name:", - "phonebookAssociation": "Association", - "phonebookAssociationDetail": "Association details:", - "phonebookAssociationGroupement": "Association groupement", - "phonebookAssociationKind": "Type of association:", - "phonebookAssociationName": "Association name", - "phonebookAssociations": "Associations", - "phonebookCancel": "Cancel", - "phonebookChangeTermYear": "Switch to {year} term", - "@phonebookChangeTermYear": { - "description": "Change the term year of the association", - "placeholders": { - "year": { - "type": "int" - } - } - }, - "phonebookChangeTermConfirm": "Are you sure you want to change the entire term?\nThis action is irreversible!", - "phonebookClose": "Close", - "phonebookConfirm": "Confirm", - "phonebookCopied": "Copied to clipboard", - "phonebookDeactivateAssociation": "Deactivate association", - "phonebookDeactivatedAssociation": "Association deactivated", - "phonebookDeactivatedAssociationWarning": "Warning, this association is deactivated, you cannot modify it", - "phonebookDeactivateSelectedAssociation": "Désactiver l'association {association} ?", - "@phonebookDeactivateSelectedAssociation": { - "description": "Permet de désactiver une association", - "placeholders": { - "association": { - "type": "String" - } - } - }, - "phonebookDeactivatingError": "Error during deactivation", - "phonebookDetail": "Details:", - "phonebookDelete": "Delete", - "phonebookDeleteAssociation": "Delete association", - "phonebookDeleteSelectedAssociation": "Delete the association {association}?", - "@phonebookDeleteSelectedAssociation": { - "description": "Delete an association", - "placeholders": { - "association": { - "type": "String" - } - } - }, - "phonebookDeleteAssociationDescription": "This will erase all association history", - "phonebookDeletedAssociation": "Association deleted", - "phonebookDeletedMember": "Member deleted", - "phonebookDeleteRole": "Delete role", - "phonebookDeleteUserRole": "Delete the role of {name}?", - "@phonebookDeleteUserRole": { - "description": "Delete the role of a user", - "placeholders": { - "name": { - "type": "String" - } - } - }, - "phonebookDeleting": "Deleting", - "phonebookDeletingError": "Error deleting", - "phonebookDescription": "Description", - "phonebookEdit": "Edit", - "phonebookEditAssociationGroupement": "Edit association groupement", - "phonebookEditAssociationGroups": "Manage groups", - "phonebookEditAssociationInfo": "Edit", - "phonebookEditAssociationMembers": "Manage members", - "phonebookEditRole": "Edit role", - "phonebookEmail": "Email:", - "phonebookEmailCopied": "Email copied to clipboard", - "phonebookEmptyApparentName": "Please enter a role name", - "phonebookEmptyFieldError": "A field is not filled", - "phonebookEmptyKindError": "Please choose an association type", - "phonebookEmptyMember": "No member selected", - "phonebookErrorAssociationLoading": "Error loading association", - "phonebookErrorAssociationNameEmpty": "Please enter an association name", - "phonebookErrorAssociationPicture": "Error editing association picture", - "phonebookErrorKindsLoading": "Error loading association types", - "phonebookErrorLoadAssociationList": "Error loading association list", - "phonebookErrorLoadAssociationMember": "Error loading association members", - "phonebookErrorLoadAssociationPicture": "Error loading association picture", - "phonebookErrorLoadProfilePicture": "Error", - "phonebookErrorRoleTagsLoading": "Error loading role tags", - "phonebookExistingMembership": "This member is already in the current term", - "phonebookFilter": "Filter", - "phonebookFilterDescription": "Filter the associations by their groupement", - "phonebookFirstname": "First name:", - "phonebookGroupementDeleted": "Association groupement deleted", - "phonebookGroupementDeleteError": "Error deleting association groupement", - "phonebookGroupementName": "Groupement name", - "phonebookGroups": "Manage {association} groups", - "@phonebookGroups": { - "description": "Manage the groups of an association", - "placeholders": { - "association": { - "type": "String" - } - } - }, - "phonebookTerm": "{year} term", - "@phonebookTerm": { - "description": "Term year of the association", - "placeholders": { - "year": { - "type": "int" - } - } - }, - "phonebookTermChangingError": "Error changing term", - "phonebookMember": "Member", - "phonebookMemberReordered": "Member reordered", - "phonebookMembers": "Manage {association} members", - "@phonebookMembers": { - "description": "Manage the members of an association", - "placeholders": { - "association": { - "type": "String" - } - } - }, - "phonebookMembershipAssociationError": "Please choose an association", - "phonebookMembershipRole": "Role:", - "phonebookMembershipRoleError": "Please choose a role", - "phonebookModifyMembership": "Modify {name}'s role", - "@phonebookModifyMembership": { - "description": "Modify the role of a member", - "placeholders": { - "name": { - "type": "String" - } - } - }, - "phonebookName": "Last name:", - "phonebookNameCopied": "Name and first name copied to clipboard", - "phonebookNamePure": "Last name", - "phonebookNewTerm": "New term", - "phonebookNewTermConfirmed": "Term changed", - "phonebookNickname": "Nickname:", - "phonebookNicknameCopied": "Nickname copied to clipboard", - "phonebookNoAssociationFound": "No association found", - "phonebookNoMember": "No member", - "phonebookNoMemberRole": "No role found", - "phonebookNoRoleTags": "No role tags found", - "phonebookPhone": "Phone:", - "phonebookPhonebook": "Phonebook", - "phonebookPhonebookSearch": "Search", - "phonebookPhonebookSearchAssociation": "Association", - "phonebookPhonebookSearchField": "Search:", - "phonebookPhonebookSearchName": "Last name/First name/Nickname", - "phonebookPhonebookSearchRole": "Position", - "phonebookPresidentRoleTag": "Prez'", - "phonebookPromoNotGiven": "Promotion not provided", - "phonebookPromotion": "Promotion {year}", - "@phonebookPromotion": { - "description": "Promotion year of the member", - "placeholders": { - "year": { - "type": "int" - } - } - }, - "phonebookReorderingError": "Error during reordering", - "phonebookResearch": "Search", - "phonebookRolePure": "Role", - "phonebookSearchUser": "Search a user", - "phonebookTooHeavyAssociationPicture": "Image is too large (max 4MB)", - "phonebookUpdateGroups": "Update groups", - "phonebookUpdatedAssociation": "Association updated", - "phonebookUpdatedAssociationPicture": "Association picture has been changed", - "phonebookUpdatedGroups": "Groups updated", - "phonebookUpdatedMember": "Member updated", - "phonebookUpdatingError": "Error during update", - "phonebookValidation": "Validate", - "purchasesPurchases": "Purchases", - "purchasesResearch": "Search", - "purchasesNoPurchasesFound": "No purchases found", - "purchasesNoTickets": "No tickets", - "purchasesTicketsError": "Error loading tickets", - "purchasesPurchasesError": "Error loading purchases", - "purchasesNoPurchases": "No purchase", - "purchasesTimes": "times", - "purchasesAlreadyUsed": "Already used", - "purchasesNotPaid": "Not validated", - "purchasesPleaseSelectProduct": "Please select a product", - "purchasesProducts": "Products", - "purchasesCancel": "Cancel", - "purchasesValidate": "Validate", - "purchasesLeftScan": "Scans remaining", - "purchasesTag": "Tag", - "purchasesHistory": "History", - "purchasesPleaseSelectSeller": "Please select a seller", - "purchasesNoTagGiven": "Warning, no tag entered", - "purchasesTickets": "Tickets", - "purchasesNoScannableProducts": "No scannable products", - "purchasesLoading": "Waiting for scan", - "purchasesScan": "Scan", - "raffleRaffle": "Raffle", - "rafflePrize": "Prize", - "rafflePrizes": "Prizes", - "raffleActualRaffles": "Current raffles", - "rafflePastRaffles": "Past raffles", - "raffleYourTickets": "All your tickets", - "raffleCreateMenu": "Creation menu", - "raffleNextRaffles": "Upcoming raffles", - "raffleNoTicket": "You have no ticket", - "raffleSeeRaffleDetail": "View prizes/tickets", - "raffleActualPrize": "Current prizes", - "raffleMajorPrize": "Major prizes", - "raffleTakeTickets": "Take your tickets", - "raffleNoTicketBuyable": "You cannot buy tickets right now", - "raffleNoCurrentPrize": "There are no prizes currently", - "raffleModifTombola": "You can modify your raffles or create new ones, all decisions must then be approved by admins", - "raffleCreateYourRaffle": "Your raffle creation menu", - "rafflePossiblePrice": "Possible prize", - "raffleInformation": "Information and statistics", - "raffleAccounts": "Accounts", - "raffleAdd": "Add", - "raffleUpdatedAmount": "Amount updated", - "raffleUpdatingError": "Error during update", - "raffleDeletedPrize": "Prize deleted", - "raffleDeletingError": "Error during deletion", - "raffleQuantity": "Quantity", - "raffleClose": "Close", - "raffleOpen": "Open", - "raffleAddTypeTicketSimple": "Add", - "raffleAddingError": "Error during addition", - "raffleEditTypeTicketSimple": "Edit", - "raffleFillField": "Field cannot be empty", - "raffleWaiting": "Loading", - "raffleEditingError": "Error during editing", - "raffleAddedTicket": "Ticket added", - "raffleEditedTicket": "Ticket edited", - "raffleAlreadyExistTicket": "Ticket already exists", - "raffleNumberExpected": "An integer is expected", - "raffleDeletedTicket": "Ticket deleted", - "raffleAddPrize": "Add", - "raffleEditPrize": "Edit", - "raffleOpenRaffle": "Open raffle", - "raffleCloseRaffle": "Close raffle", - "raffleOpenRaffleDescription": "You are going to open the raffle, users will be able to buy tickets. You will no longer be able to modify the raffle. Are you sure you want to continue?", - "raffleCloseRaffleDescription": "You are going to close the raffle, users will no longer be able to buy tickets. Are you sure you want to continue?", - "raffleNoCurrentRaffle": "There is no ongoing raffle", - "raffleBoughtTicket": "Ticket purchased", - "raffleDrawingError": "Error during drawing", - "raffleInvalidPrice": "Price must be greater than 0", - "raffleMustBePositive": "Number must be strictly positive", - "raffleDraw": "Draw", - "raffleDrawn": "Drawn", - "raffleError": "Error", - "raffleGathered": "Collected", - "raffleTickets": "Tickets", - "raffleTicket": "ticket", - "raffleWinner": "Winner", - "raffleNoPrize": "No prize", - "raffleDeletePrize": "Delete prize", - "raffleDeletePrizeDescription": "You are going to delete the prize, are you sure you want to continue?", - "raffleDrawing": "Drawing", - "raffleDrawingDescription": "Draw the prize winner?", - "raffleDeleteTicket": "Delete ticket", - "raffleDeleteTicketDescription": "You are going to delete the ticket, are you sure you want to continue?", - "raffleWinningTickets": "Winning tickets", - "raffleNoWinningTicketYet": "Winning tickets will be displayed here", - "raffleName": "Name", - "raffleDescription": "Description", - "raffleBuyThisTicket": "Buy this ticket", - "raffleLockedRaffle": "Locked raffle", - "raffleUnavailableRaffle": "Unavailable raffle", - "raffleNotEnoughMoney": "You don't have enough money", - "raffleWinnable": "winnable", - "raffleNoDescription": "No description", - "raffleAmount": "Balance", - "raffleLoading": "Loading", - "raffleTicketNumber": "Number of tickets", - "rafflePrice": "Price", - "raffleEditRaffle": "Edit raffle", - "raffleEdit": "Edit", - "raffleAddPackTicket": "Add ticket pack", - "recommendationRecommendation": "Recommendation", - "recommendationTitle": "Title", - "recommendationLogo": "Logo", - "recommendationCode": "Code", - "recommendationSummary": "Short summary", - "recommendationDescription": "Description", - "recommendationAdd": "Add", - "recommendationEdit": "Edit", - "recommendationDelete": "Delete", - "recommendationAddImage": "Please add an image", - "recommendationAddedRecommendation": "Deal added", - "recommendationEditedRecommendation": "Deal updated", - "recommendationDeleteRecommendationConfirmation": "Are you sure you want to delete this deal?", - "recommendationDeleteRecommendation": "Delete", - "recommendationDeletingRecommendationError": "Error during deletion", - "recommendationDeletedRecommendation": "Deal deleted", - "recommendationIncorrectOrMissingFields": "Incorrect or missing fields", - "recommendationEditingError": "Edit failed", - "recommendationAddingError": "Add failed", - "recommendationCopiedCode": "Discount code copied", - "seedLibraryAdd": "Add", - "seedLibraryAddedPlant": "Plant added", - "seedLibraryAddedSpecies": "Species added", - "seedLibraryAddingError": "Error during addition", - "seedLibraryAddPlant": "Deposit a plant", - "seedLibraryAddSpecies": "Add a species", - "seedLibraryAll": "All", - "seedLibraryAncestor": "Ancestor", - "seedLibraryAround": "around", - "seedLibraryAutumn": "Autumn", - "seedLibraryBorrowedPlant": "Borrowed plant", - "seedLibraryBorrowingDate": "Borrowing date:", - "seedLibraryBorrowPlant": "Borrow plant", - "seedLibraryCard": "Card", - "seedLibraryChoosingAncestor": "Please choose an ancestor", - "seedLibraryChoosingSpecies": "Please choose a species", - "seedLibraryChoosingSpeciesOrAncestor": "Please choose a species or an ancestor", - "seedLibraryContact": "Contact:", - "seedLibraryDays": "days", - "seedLibraryDeadMsg": "Do you want to declare the plant dead?", - "seedLibraryDeadPlant": "Dead plant", - "seedLibraryDeathDate": "Date of death", - "seedLibraryDeletedSpecies": "Species deleted", - "seedLibraryDeleteSpecies": "Delete species?", - "seedLibraryDeleting": "Deleting", - "seedLibraryDeletingError": "Error during deletion", - "seedLibraryDepositNotAvailable": "Plant deposit is not possible without borrowing a plant first", - "seedLibraryDescription": "Description", - "seedLibraryDifficulty": "Difficulty:", - "seedLibraryEdit": "Edit", - "seedLibraryEditedPlant": "Plant updated", - "seedLibraryEditInformation": "Edit information", - "seedLibraryEditingError": "Error during editing", - "seedLibraryEditSpecies": "Edit species", - "seedLibraryEmptyDifficultyError": "Please choose a difficulty", - "seedLibraryEmptyFieldError": "Please fill all fields", - "seedLibraryEmptyTypeError": "Please choose a plant type", - "seedLibraryEndMonth": "End month:", - "seedLibraryFacebookUrl": "Facebook link", - "seedLibraryFilters": "Filters", - "seedLibraryForum": "Oskour mom I killed my plant - Help forum", - "seedLibraryForumUrl": "Forum link", - "seedLibraryHelpSheets": "Plant sheets", - "seedLibraryInformation": "Information:", - "seedLibraryMaturationTime": "Maturation time", - "seedLibraryMonthJan": "January", - "seedLibraryMonthFeb": "February", - "seedLibraryMonthMar": "March", - "seedLibraryMonthApr": "April", - "seedLibraryMonthMay": "May", - "seedLibraryMonthJun": "June", - "seedLibraryMonthJul": "July", - "seedLibraryMonthAug": "August", - "seedLibraryMonthSep": "September", - "seedLibraryMonthOct": "October", - "seedLibraryMonthNov": "November", - "seedLibraryMonthDec": "December", - "seedLibraryMyPlants": "My plants", - "seedLibraryName": "Name", - "seedLibraryNbSeedsRecommended": "Number of seeds recommended", - "seedLibraryNbSeedsRecommendedError": "Please enter a recommended seed number greater than 0", - "seedLibraryNoDateError": "Please enter a date", - "seedLibraryNoFilteredPlants": "No plants match your search. Try other filters.", - "seedLibraryNoMorePlant": "No plants available", - "seedLibraryNoPersonalPlants": "You don't have any plants yet in your seed library. You can add some in the stocks.", - "seedLibraryNoSpecies": "No species found", - "seedLibraryNoStockPlants": "No plants available in stock", - "seedLibraryNotes": "Notes", - "seedLibraryOk": "OK", - "seedLibraryPlantationPeriod": "Planting period:", - "seedLibraryPlantationType": "Plantation type:", - "seedLibraryPlantDetail": "Plant details", - "seedLibraryPlantingDate": "Planting date", - "seedLibraryPlantingNow": "I'm planting it now", - "seedLibraryPrefix": "Prefix", - "seedLibraryPrefixError": "Prefix already used", - "seedLibraryPrefixLengthError": "The prefix must be 3 characters", - "seedLibraryPropagationMethod": "Propagation method:", - "seedLibraryReference": "Reference:", - "seedLibraryRemovedPlant": "Plant removed", - "seedLibraryRemovingError": "Error removing plant", - "seedLibraryResearch": "Search", - "seedLibrarySaveChanges": "Save changes", - "seedLibrarySeason": "Season:", - "seedLibrarySeed": "Seed", - "seedLibrarySeeds": "seeds", - "seedLibrarySeedDeposit": "Plant deposit", - "seedLibrarySeedLibrary": "Seed library", - "seedLibrarySeedQuantitySimple": "Seed quantity", - "seedLibrarySeedQuantity": "Seed quantity:", - "seedLibraryShowDeadPlants": "Show dead plants", - "seedLibrarySpecies": "Species:", - "seedLibrarySpeciesHelp": "Help on species", - "seedLibrarySpeciesPlural": "Species", - "seedLibrarySpeciesSimple": "Species", - "seedLibrarySpeciesType": "Species type:", - "seedLibrarySpring": "Spring", - "seedLibraryStartMonth": "Start month:", - "seedLibraryStock": "Available stock", - "seedLibrarySummer": "Summer", - "seedLibraryStocks": "Stocks", - "seedLibraryTimeUntilMaturation": "Time until maturation:", - "seedLibraryType": "Type:", - "seedLibraryUnableToOpen": "Unable to open link", - "seedLibraryUpdate": "Edit", - "seedLibraryUpdatedInformation": "Information updated", - "seedLibraryUpdatedSpecies": "Species updated", - "seedLibraryUpdatedPlant": "Plant updated", - "seedLibraryUpdatingError": "Error updating", - "seedLibraryWinter": "Winter", - "seedLibraryWriteReference": "Please write the following reference: ", - "settingsAccount": "Account", - "settingsAddProfilePicture": "Add a photo", - "settingsAdmin": "Administrator", - "settingsAskHelp": "Ask for help", - "settingsAssociation": "Association", - "settingsBirthday": "Birthday", - "settingsBugs": "Bugs", - "settingsChangePassword": "Change password", - "settingsChangingPassword": "Do you really want to change your password?", - "settingsConfirmPassword": "Confirm password", - "settingsCopied": "Copied!", - "settingsDarkMode": "Dark mode", - "settingsDarkModeOff": "Off", - "settingsDeleteLogs": "Delete logs?", - "settingsDeleteNotificationLogs": "Delete notification logs?", - "settingsDetelePersonalData": "Delete my personal data", - "settingsDetelePersonalDataDesc": "This action notifies the administrator that you want to delete your personal data.", - "settingsDeleting": "Deleting", - "settingsEdit": "Edit", - "settingsEditAccount": "Edit account", - "settingsEditPassword": "Edit password", - "settingsEmail": "Email", - "settingsEmptyField": "This field cannot be empty", - "settingsErrorProfilePicture": "Error editing profile picture", - "settingsErrorSendingDemand": "Error sending request", - "settingsEventsIcal": "Ical link for events", - "settingsExpectingDate": "Expected birth date", - "settingsFirstname": "First name", - "settingsFloor": "Floor", - "settingsHelp": "Help", - "settingsIcalCopied": "Ical link copied!", - "settingsLanguage": "Language", - "settingsLanguageFr": "French", - "settingsLogs": "Logs", - "settingsModules": "Modules", - "settingsMyIcs": "My Ical link", - "settingsName": "Last name", - "settingsNewPassword": "New password", - "settingsNickname": "Nickname", - "settingsNotifications": "Notifications", - "settingsOldPassword": "Old password", - "settingsPasswordChanged": "Password changed", - "settingsPasswordsNotMatch": "Passwords do not match", - "settingsPersonalData": "Personal data", - "settingsPersonalisation": "Personalization", - "settingsPhone": "Phone", - "settingsProfilePicture": "Profile picture", - "settingsPromo": "Promotion", - "settingsRepportBug": "Report a bug", - "settingsSave": "Save", - "settingsSecurity": "Security", - "settingsSendedDemand": "Request sent", - "settingsSettings": "Settings", - "settingsTooHeavyProfilePicture": "Image is too large (max 4MB)", - "settingsUpdatedProfile": "Profile updated", - "settingsUpdatedProfilePicture": "Profile picture updated", - "settingsUpdateNotification": "Update notifications", - "settingsUpdatingError": "Error updating profile", - "settingsVersion": "Version", - "settingsPasswordStrength": "Password strength", - "settingsPasswordStrengthVeryWeak": "Very weak", - "settingsPasswordStrengthWeak": "Weak", - "settingsPasswordStrengthMedium": "Medium", - "settingsPasswordStrengthStrong": "Strong", - "settingsPasswordStrengthVeryStrong": "Very strong", - "voteAdd": "Add", - "voteAddMember": "Add a member", - "voteAddedPretendance": "List added", - "voteAddedSection": "Section added", - "voteAddingError": "Error adding", - "voteAddPretendance": "Add a list", - "voteAddSection": "Add a section", - "voteAll": "All", - "voteAlreadyAddedMember": "Member already added", - "voteAlreadyVoted": "Vote recorded", - "voteChooseList": "Choose a list", - "voteClear": "Reset", - "voteClearVotes": "Reset votes", - "voteClosedVote": "Votes closed", - "voteCloseVote": "Close votes", - "voteConfirmVote": "Confirm vote", - "voteCountVote": "Count votes", - "voteDeletedAll": "All deleted", - "voteDeletedPipo": "Fake lists deleted", - "voteDeletedSection": "Section deleted", - "voteDeleteAll": "Delete all", - "voteDeleteAllDescription": "Do you really want to delete everything?", - "voteDeletePipo": "Delete fake lists", - "voteDeletePipoDescription": "Do you really want to delete the fake lists?", - "voteDeletePretendance": "Delete the list", - "voteDeletePretendanceDesc": "Do you really want to delete this list?", - "voteDeleteSection": "Delete the section", - "voteDeleteSectionDescription": "Do you really want to delete this section?", - "voteDeletingError": "Error deleting", - "voteDescription": "Description", - "voteEdit": "Edit", - "voteEditedPretendance": "List edited", - "voteEditedSection": "Section edited", - "voteEditingError": "Error editing", - "voteErrorClosingVotes": "Error closing votes", - "voteErrorCountingVotes": "Error counting votes", - "voteErrorResetingVotes": "Error resetting votes", - "voteErrorOpeningVotes": "Error opening votes", - "voteIncorrectOrMissingFields": "Incorrect or missing fields", - "voteMembers": "Members", - "voteName": "Name", - "voteNoPretendanceList": "No list of candidates", - "voteNoSection": "No section", - "voteCanNotVote": "You cannot vote", - "voteNoSectionList": "No section", - "voteNotOpenedVote": "Vote not opened", - "voteOnGoingCount": "Counting in progress", - "voteOpenVote": "Open votes", - "votePipo": "Fake", - "votePretendance": "Lists", - "votePretendanceDeleted": "Candidate list deleted", - "votePretendanceNotDeleted": "Error deleting", - "voteProgram": "Program", - "votePublish": "Publish", - "votePublishVoteDescription": "Do you really want to publish the votes?", - "voteResetedVotes": "Votes reset", - "voteResetVote": "Reset votes", - "voteResetVoteDescription": "What do you want to do?", - "voteRole": "Role", - "voteSectionDescription": "Section description", - "voteSection": "Section", - "voteSectionName": "Section name", - "voteSeeMore": "See more", - "voteSelected": "Selected", - "voteShowVotes": "Show votes", - "voteVote": "Vote", - "voteVoteError": "Error recording vote", - "voteVoteFor": "Vote for ", - "voteVoteNotStarted": "Vote not opened", - "voteVoters": "Voting groups", - "voteVoteSuccess": "Vote recorded", - "voteVotes": "Votes", - "voteVotesClosed": "Votes closed", - "voteVotesCounted": "Votes counted", - "voteVotesOpened": "Votes opened", - "voteWarning": "Warning", - "voteWarningMessage": "Selection will not be saved.\nDo you want to continue?", - "moduleAdvert": "Advert", - "moduleAmap": "AMAP", - "moduleBooking": "Booking", - "moduleCalendar": "Calendar", - "moduleCentralisation": "Centralisation", - "moduleCinema": "Cinema", - "moduleEvent": "Event", - "moduleFlappyBird": "Flappy Bird", - "moduleLoan": "Loan", - "modulePhonebook": "Phonebook", - "modulePurchases": "Purchases", - "moduleRaffle": "Raffle", - "moduleRecommendation": "Recommendation", - "moduleSeedLibrary": "Seed Library", - "moduleVote": "Vote", - "modulePh": "PH", - "moduleSettings": "Settings", - "moduleFeed": "Feed", - "moduleStyleGuide": "StyleGuide", - "moduleAdmin": "Administration", - "moduleOthers": "Others", - "modulePayment": "Payment", - "paiementTopUp": "Top-up", - "paiementStoreManagement": "Association management", - "paiementDeleteStore": "Delete association", - "paiementDeleteStoreDescription": "Are you sure you want to delete this association?", - "paiementDeleteStoreError": "Unable to delete the association", - "paiementStoreDeleted": "Association deleted", - "paiementAddThisDevice": "Add this device", - "paiementThisDevice": "(this device)", - "paiementCancelled": "Cancelled", - "paiementThe": "The", - "paiementOf": "of", - "paiementRefundedThe": "Refunded on", - "paiementAt": "at", - "paiementPleaseAcceptTOS": "Please accept the Terms of Service.", - "paiementAskDeviceActivation": "Device activation request", - "paiementDeviceActivationReceived": "The activation request has been received, please check your email to finalize the process", - "paiementRevokeDevice": "Revoke device?", - "paiementRevokeDeviceDescription": "You will no longer be able to use this device for payments", - "paiementDeviceRevoked": "Device revoked", - "paiementDeviceRevokingError": "Error while revoking device", - "paiementPleaseAcceptPopup": "Please allow popups", - "paiementProceedSuccessfully": "Payment completed successfully", - "paiementCancelledTransaction": "Payment cancelled", - "paiementPleaseEnterMinAmount": "Please enter an amount greater than 1", - "paiementMaxAmount": "The maximum wallet amount is", - "paiementPayWithHA": "Pay with HelloAsso", - "paiementBalanceAfterTopUp": "Balance after top-up:", - "paiementPersonalBalance": "Personal balance", - "paiementDevices": "Devices", - "paiementPay": "Pay", - "paiementDeviceNotRegistered": "Device not registered", - "paiementDeviceNotRegisteredDescription": "Your device is not registered yet. \nTo register it, please go to the devices page.", - "paiementAccessPage": "Access the page", - "paiementDeviceNotActivated": "Device not activated", - "paiementDeviceNotActivatedDescription": "Your device is not yet activated. \nTo activate it, please go to the devices page.", - "paiementReactivateRevokedDeviceDescription": "Your device has been revoked. \nTo reactivate it, please go to the devices page.", - "paiementDeviceRecoveryError": "Error while retrieving device", - "paiementStats": "Stats", - "paimentTopUpAction": "Top-up", - "paiementGetBalanceError": "Error while retrieving balance: ", - "paiementLastTransactions": "Latest transactions", - "paiementGetTransactionsError": "Error while retrieving transactions: ", - "paiementStoreBalance": "Association balance", - "paiementScan": "Scan", - "paiementManagement": "Management", - "paiementHistory": "History", - "paiementHandOver": "Handover", - "paiementStores": "Associations", - "paiementAdmin": "Administrator", - "paiementSuccededTransaction": "Successful payment", - "paiementNewCGU": "New Terms of Service", - "paiementDecline": "Decline", - "paiementAccept": "Accept", - "paiementAmount": "Amount", - "paiementValidUntil": "Valid until", - "paiementClose": "Close", - "paiementPleaseEnterValidAmount": "Please enter a valid amount", - "paiementPleaseAuthenticate": "Please authenticate", - "paiementAthenticationRequired": "Authentication required to pay", - "paiementNoThanks": "No thanks", - "paiementAuthentificationFailed": "Authentication failed", - "paiementPleaseAddDevice": "Please add this device to pay", - "paiementPayment": "Payment", - "paiementBalanceAfterTransaction": "Balance after payment: ", - "paiementCancel": "Cancel", - "paiementLimitedTo": "Limited to", - "paiementScanCode": "Scan a code", - "paiementNext": "Next", - "paiementCancelTransaction": "Cancel transaction", - "paiementTransactionCancelled": "Transaction cancelled", - "paiementTransactionCancelledDescription": "Are you sure you want to cancel the transaction of", - "paiementTransactionCancelledError": "Error while cancelling the transaction", - "paiementNoMembership": "No membership", - "paiementNoMembershipDescription": "This product is not available to non-members. Confirm the payment?", - "paiementQRCodeAlreadyUsed": "QR Code already used", - "paiementCameraPermissionRequired": "Camera permission required", - "paiementCameraPerssionRequiredDescription": "To scan a QR Code, you must allow camera access.", - "paiementSettings": "Settings", - "paiementReceived": "Received", - "paiementSpent": "Spent", - "paiementNoTrasactionForThisMonth": "No transactions for this month", - "paiementNoTransactinon": "No transaction", - "paiementSellerRigths": "Seller rights", - "paiementCanBank": "Can collect payments", - "paiementCanSeeHistory": "Can view history", - "paiementCanCancelTransaction": "Can cancel transactions", - "paiementCanManageSellers": "Can manage sellers", - "paiementAddedSeller": "Seller added", - "paiementAddingSellerError": "Error while adding seller", - "paiementBank": "Collect", - "paiementSeeHistory": "View history", - "paiementCancelTransactions": "Cancel transactions", - "paiementManageSellers": "Manage sellers", - "paiementStructureAdmin": "Structure administrator", - "paiementRightsOf": "Rights of", - "paiementRightsUpdated": "Rights updated", - "paiementRightsUpdateError": "Error while updating rights", - "paiementDeleteSellerDescription": "Are you sure you want to delete this seller?", - "paiementDeletedSeller": "Seller deleted", - "paiementDeletingSellerError": "Error while deleting seller", - "paiementDeleteSeller": "Delete seller", - "paiementAdd": "Add", - "paiementAddSeller": "Add seller", - "paiementSellerError": "You are not a seller of this association", - "paiementSellersOf": "Sellers of", - "paiementModify": "Edit", - "paiementAStore": "an association", - "paiementStoreName": "Association name", - "paiementSuccessfullyAddedStore": "Association successfully added", - "paiementSuccessfullyModifiedStore": "Association successfully updated", - "paiementAddingStoreError": "Error while adding the association", - "paiementModifyingStoreError": "Error while updating the association", - "paiementRefund": "Refund", - "paiementDoneTransaction": "Transaction completed", - "paiementRefundAction": "Refund", - "paiementTotalDuringPeriod": "Total during the period", - "paiementMean": "Average: ", - "paiementTransaction": "Transaction", - "paiementTransferStructure": "Structure transfer", - "paiementYouAreTransferingStructureTo": "You are about to transfer the structure to ", - "paiementTransferStructureDescription": "The new manager will have access to all structure management features. You will receive an email to confirm this transfer. The link will only be active for 20 minutes. This action is irreversible. Are you sure you want to continue?", - "paiementTransferStructureError": "Error while transferring structure", - "paiementTransferStructureSuccess": "Structure transfer requested successfully", - "paiementNextAccountable": "Next responsible" + "@@locale": "en", + "adminAccountTypes": "Account types", + "adminAdd": "Add", + "adminAddGroup": "Add group", + "adminAddMember": "Add member", + "adminAddedGroup": "Group created", + "adminAddedLoaner": "Lender added", + "adminAddedMember": "Member added", + "adminAddingError": "Error while adding", + "adminAddingMember": "Adding a member", + "adminAddLoaningGroup": "Add loaning group", + "adminAddSchool": "Add school", + "adminAddStructure": "Add structure", + "adminAddedSchool": "School created", + "adminAddedStructure": "Structure added", + "adminEditedStructure": "Structure edited", + "adminAdministration": "Administration", + "adminAssociationMembership": "Membership", + "adminAssociationMembershipName": "Membership name", + "adminAssociationsMemberships": "Memberships", + "adminClearFilters": "Clear filters", + "adminCreateAssociationMembership": "Create membership", + "adminCreatedAssociationMembership": "Membership created", + "adminCreationError": "Error during creation", + "adminDateError": "Start date must be before end date", + "adminDelete": "Delete", + "adminDeleteAssociationMembership": "Delete membership?", + "adminDeletedAssociationMembership": "Membership deleted", + "adminDeleteGroup": "Delete group?", + "adminDeletedGroup": "Group deleted", + "adminDeleteSchool": "Delete school?", + "adminDeletedSchool": "School deleted", + "adminDeleting": "Deleting", + "adminDeletingError": "Error while deleting", + "adminDescription": "Description", + "adminEclSchool": "Centrale Lyon", + "adminEdit": "Edit", + "adminEditStructure": "Edit structure", + "adminEditMembership": "Edit membership", + "adminEmptyDate": "Empty date", + "adminEmptyFieldError": "Name cannot be empty", + "adminEmailRegex": "Email Regex", + "adminEmptyUser": "Empty user", + "adminEndDate": "End date", + "adminEndDateMaximal": "Maximum end date", + "adminEndDateMinimal": "Minimum end date", + "adminError": "Error", + "adminFilters": "Filters", + "adminGroup": "Group", + "adminGroups": "Groups", + "adminLoaningGroup": "Loaning group", + "adminLooking": "Searching", + "adminManager": "Structure administrator", + "adminMaximum": "Maximum", + "adminMembers": "Members", + "adminMembershipAddingError": "Error while adding (likely due to overlapping dates)", + "adminMemberships": "Memberships", + "adminMembershipUpdatingError": "Error while updating (likely due to overlapping dates)", + "adminMinimum": "Minimum", + "adminModifyModuleVisibility": "Module visibility", + "adminMyEclPay": "MyECLPay", + "adminName": "Name", + "adminNoManager": "No manager selected", + "adminNoMember": "No member", + "adminNoMoreLoaner": "No lender available", + "adminNoSchool": "No school", + "adminRemoveGroupMember": "Remove member from group?", + "adminResearch": "Search", + "adminSchools": "Schools", + "adminStructures": "Structures", + "adminStartDate": "Start date", + "adminStartDateMaximal": "Maximum start date", + "adminStartDateMinimal": "Minimum start date", + "adminUpdatedAssociationMembership": "Membership updated", + "adminUpdatedGroup": "Group updated", + "adminUpdatedMembership": "Membership updated", + "adminUpdatingError": "Error while updating", + "adminUser": "User", + "adminValidateFilters": "Apply filters", + "adminVisibilities": "Visibilities", + "advertAdd": "Add", + "advertAddedAdvert": "Advert published", + "advertAddedAnnouncer": "Announcer added", + "advertAddingError": "Error while adding", + "advertAdmin": "Admin", + "advertAdvert": "Advert", + "advertChoosingAnnouncer": "Please choose an announcer", + "advertChoosingPoster": "Please choose an image", + "advertContent": "Content", + "advertDeleteAdvert": "Delete ad?", + "advertDeleteAnnouncer": "Delete announcer?", + "advertDeleting": "Deleting", + "advertEdit": "Edit", + "advertEditedAdvert": "Advert edited", + "advertEditingError": "Error while editing", + "advertGroupAdvert": "Group", + "advertIncorrectOrMissingFields": "Incorrect or missing fields", + "advertInvalidNumber": "Please enter a number", + "advertManagement": "Management", + "advertModifyAnnouncingGroup": "Edit announcement group", + "advertNoMoreAnnouncer": "No more announcers available", + "advertNoValue": "Please enter a value", + "advertPositiveNumber": "Please enter a positive number", + "advertRemovedAnnouncer": "Announcer removed", + "advertRemovingError": "Error during removal", + "advertTags": "Tags", + "advertTitle": "Title", + "advertMonthJan": "Jan", + "advertMonthFeb": "Feb", + "advertMonthMar": "Mar", + "advertMonthApr": "Apr", + "advertMonthMay": "May", + "advertMonthJun": "Jun", + "advertMonthJul": "Jul", + "advertMonthAug": "Aug", + "advertMonthSep": "Sep", + "advertMonthOct": "Oct", + "advertMonthNov": "Nov", + "advertMonthDec": "Dec", + "amapAccounts": "Accounts", + "amapAdd": "Add", + "amapAddDelivery": "Add delivery", + "amapAddedCommand": "Order added", + "amapAddedOrder": "Order added", + "amapAddedProduct": "Product added", + "amapAddedUser": "User added", + "amapAddProduct": "Add product", + "amapAddUser": "Add user", + "amapAddingACommand": "Add an order", + "amapAddingCommand": "Add the order", + "amapAddingError": "Error while adding", + "amapAddingProduct": "Add a product", + "amapAddOrder": "Add an order", + "amapAdmin": "Admin", + "amapAlreadyExistCommand": "An order already exists for this date", + "amapAmap": "Amap", + "amapAmount": "Balance", + "amapArchive": "Archive", + "amapArchiveDelivery": "Archive", + "amapArchivingDelivery": "Archiving delivery", + "amapCategory": "Category", + "amapCloseDelivery": "Lock", + "amapCommandDate": "Order date", + "amapCommandProducts": "Order products", + "amapConfirm": "Confirm", + "amapContact": "Association contacts", + "amapCreateCategory": "Create category", + "amapDelete": "Delete", + "amapDeleteDelivery": "Delete delivery?", + "amapDeleteDeliveryDescription": "Are you sure you want to delete this delivery?", + "amapDeletedDelivery": "Delivery deleted", + "amapDeletedOrder": "Order deleted", + "amapDeletedProduct": "Product deleted", + "amapDeleteProduct": "Delete product?", + "amapDeleteProductDescription": "Are you sure you want to delete this product?", + "amapDeleting": "Deleting", + "amapDeletingDelivery": "Delete delivery?", + "amapDeletingError": "Error while deleting", + "amapDeletingOrder": "Delete order?", + "amapDeletingProduct": "Delete product?", + "amapDeliver": "Delivery completed?", + "amapDeliveries": "Deliveries", + "amapDeliveringDelivery": "Are all orders delivered?", + "amapDelivery": "Delivery", + "amapDeliveryArchived": "Delivery archived", + "amapDeliveryDate": "Delivery date", + "amapDeliveryDelivered": "Delivery completed", + "amapDeliveryHistory": "Delivery history", + "amapDeliveryList": "Delivery list", + "amapDeliveryLocked": "Delivery locked", + "amapDeliveryOn": "Delivery on", + "amapDeliveryOpened": "Delivery opened", + "amapDeliveryNotArchived": "Delivery not archived", + "amapDeliveryNotLocked": "Delivery not locked", + "amapDeliveryNotDelivered": "Delivery not completed", + "amapDeliveryNotOpened": "Delivery not opened", + "amapEditDelivery": "Edit delivery", + "amapEditedCommand": "Order edited", + "amapEditingError": "Error while editing", + "amapEditProduct": "Edit product", + "amapEndingDelivery": "End of delivery", + "amapError": "Error", + "amapErrorLink": "Error opening link", + "amapErrorLoadingUser": "Error loading users", + "amapEvening": "Evening", + "amapExpectingNumber": "Please enter a number", + "amapFillField": "Please fill out this field", + "amapHandlingAccount": "Manage accounts", + "amapLoading": "Loading...", + "amapLoadingError": "Loading error", + "amapLock": "Lock", + "amapLocked": "Locked", + "amapLockedDelivery": "Delivery locked", + "amapLockedOrder": "Order locked", + "amapLooking": "Search", + "amapLockingDelivery": "Lock delivery?", + "amapMidDay": "Midday", + "amapMyOrders": "My orders", + "amapName": "Name", + "amapNextStep": "Next step", + "amapNoProduct": "No product", + "amapNoCurrentOrder": "No current order", + "amapNoMoney": "Not enough money", + "amapNoOpennedDelivery": "No open delivery", + "amapNoOrder": "No order", + "amapNoSelectedDelivery": "No delivery selected", + "amapNotEnoughMoney": "Not enough money", + "amapNotPlannedDelivery": "No scheduled delivery", + "amapOneOrder": "order", + "amapOpenDelivery": "Open", + "amapOpened": "Opened", + "amapOpenningDelivery": "Open delivery?", + "amapOrder": "Order", + "amapOrders": "Orders", + "amapPickChooseCategory": "Please enter a value or choose an existing category", + "amapPickDeliveryMoment": "Choose a delivery time", + "amapPresentation": "Presentation", + "amapPresentation1": "The AMAP (association for the preservation of small-scale farming) is a service offered by the Planet&Co association of ECL. You can receive products (fruit and vegetable baskets, juices, jams...) directly on campus!\n\nOrders must be placed before Friday at 9 PM and are delivered on campus on Tuesday from 1:00 PM to 1:45 PM (or from 6:15 PM to 6:30 PM if you can't come at midday) in the M16 hall.\n\nYou can only order if your balance allows it. You can top up your balance via the Lydia collection or by cheque during office hours.\n\nLydia top-up link: ", + "amapPresentation2": "\n\nFeel free to contact us if you have any issues!", + "amapPrice": "Price", + "amapProduct": "product", + "amapProducts": "Products", + "amapProductInDelivery": "Product in an unfinished delivery", + "amapQuantity": "Quantity", + "amapRequiredDate": "Date is required", + "amapSeeMore": "See more", + "amapThe": "The", + "amapUnlock": "Unlock", + "amapUnlockedDelivery": "Delivery unlocked", + "amapUnlockingDelivery": "Unlock delivery?", + "amapUpdate": "Edit", + "amapUpdatedAmount": "Balance updated", + "amapUpdatedOrder": "Order updated", + "amapUpdatedProduct": "Product updated", + "amapUpdatingError": "Update failed", + "amapUsersNotFound": "No users found", + "amapWaiting": "Pending", + "bookingAdd": "Add", + "bookingAddBookingPage": "Request", + "bookingAddRoom": "Add room", + "bookingAddBooking": "Add booking", + "bookingAddedBooking": "Request added", + "bookingAddedRoom": "Room added", + "bookingAddedManager": "Manager added", + "bookingAddingError": "Error while adding", + "bookingAddManager": "Add manager", + "bookingAdminPage": "Admin", + "bookingAllDay": "All day", + "bookingBookedFor": "Booked for", + "bookingBooking": "Booking", + "bookingBookingCreated": "Booking created", + "bookingBookingDemand": "Booking request", + "bookingBookingNote": "Booking note", + "bookingBookingPage": "Booking", + "bookingBookingReason": "Booking reason", + "bookingBy": "by", + "bookingConfirm": "Confirm", + "bookingConfirmation": "Confirmation", + "bookingConfirmBooking": "Confirm the booking?", + "bookingConfirmed": "Confirmed", + "bookingDates": "Dates", + "bookingDecline": "Decline", + "bookingDeclineBooking": "Decline the booking?", + "bookingDeclined": "Declined", + "bookingDelete": "Delete", + "bookingDeleting": "Deleting", + "bookingDeleteBooking": "Deleting", + "bookingDeleteBookingConfirmation": "Are you sure you want to delete this booking?", + "bookingDeletedBooking": "Booking deleted", + "bookingDeletedRoom": "Room deleted", + "bookingDeletedManager": "Manager deleted", + "bookingDeleteRoomConfirmation": "Are you sure you want to delete this room?\n\nThe room must have no current or upcoming bookings to be deleted", + "bookingDeleteManagerConfirmation": "Are you sure you want to delete this manager?\n\nThe manager must not be associated with any room to be deleted", + "bookingDeletingBooking": "Delete the booking?", + "bookingDeletingError": "Error while deleting", + "bookingDeletingRoom": "Delete the room?", + "bookingEdit": "Edit", + "bookingEditBooking": "Edit a booking", + "bookingEditionError": "Error while editing", + "bookingEditedBooking": "Booking edited", + "bookingEditedRoom": "Room edited", + "bookingEditedManager": "Manager edited", + "bookingEditManager": "Edit or delete a manager", + "bookingEditRoom": "Edit or delete a room", + "bookingEndDate": "End date", + "bookingEndHour": "End hour", + "bookingEntity": "For whom?", + "bookingError": "Error", + "bookingEventEvery": "Every", + "bookingHistoryPage": "History", + "bookingIncorrectOrMissingFields": "Incorrect or missing fields", + "bookingInterval": "Interval", + "bookingInvalidIntervalError": "Invalid interval", + "bookingInvalidDates": "Invalid dates", + "bookingInvalidRoom": "Invalid room", + "bookingKeysRequested": "Keys requested", + "bookingManagement": "Management", + "bookingManager": "Manager", + "bookingManagerName": "Manager name", + "bookingMultipleDay": "Multiple days", + "bookingMyBookings": "My bookings", + "bookingNecessaryKey": "Key needed", + "bookingNext": "Next", + "bookingNo": "No", + "bookingNoCurrentBooking": "No current booking", + "bookingNoDateError": "Please choose a date", + "bookingNoAppointmentInReccurence": "No slot exists with these recurrence settings", + "bookingNoDaySelected": "No day selected", + "bookingNoDescriptionError": "Please enter a description", + "bookingNoKeys": "No keys", + "bookingNoNoteError": "Please enter a note", + "bookingNoPhoneRegistered": "Number not provided", + "bookingNoReasonError": "Please enter a reason", + "bookingNoRoomFoundError": "No room registered", + "bookingNoRoomFound": "No room found", + "bookingNote": "Note", + "bookingOther": "Other", + "bookingPending": "Pending", + "bookingPrevious": "Previous", + "bookingReason": "Reason", + "bookingRecurrence": "Recurrence", + "bookingRecurrenceDays": "Recurrence days", + "bookingRecurrenceEndDate": "Recurrence end date", + "bookingRecurrent": "Recurrent", + "bookingRegisteredRooms": "Registered rooms", + "bookingRoom": "Room", + "bookingRoomName": "Room name", + "bookingStartDate": "Start date", + "bookingStartHour": "Start hour", + "bookingWeeks": "Weeks", + "bookingYes": "Yes", + "bookingWeekDayMon": "Monday", + "bookingWeekDayTue": "Tuesday", + "bookingWeekDayWed": "Wednesday", + "bookingWeekDayThu": "Thursday", + "bookingWeekDayFri": "Friday", + "bookingWeekDaySat": "Saturday", + "bookingWeekDaySun": "Sunday", + "cinemaAdd": "Add", + "cinemaAddedSession": "Session added", + "cinemaAddingError": "Error while adding", + "cinemaAddSession": "Add a session", + "cinemaCinema": "Cinema", + "cinemaDeleteSession": "Delete the session?", + "cinemaDeleting": "Deleting", + "cinemaDuration": "Duration", + "cinemaEdit": "Edit", + "cinemaEditedSession": "Session edited", + "cinemaEditingError": "Error while editing", + "cinemaEditSession": "Edit the session", + "cinemaEmptyUrl": "Please enter a URL", + "cinemaImportFromTMDB": "Import from TMDB", + "cinemaIncomingSession": "Now showing", + "cinemaIncorrectOrMissingFields": "Incorrect or missing fields", + "cinemaInvalidUrl": "Invalid URL", + "cinemaGenre": "Genre", + "cinemaName": "Name", + "cinemaNoDateError": "Please enter a date", + "cinemaNoDuration": "Please enter a duration", + "cinemaNoOverview": "No synopsis", + "cinemaNoPoster": "No poster", + "cinemaNoSession": "No session", + "cinemaOverview": "Synopsis", + "cinemaPosterUrl": "Poster URL", + "cinemaSessionDate": "Session day", + "cinemaStartHour": "Start hour", + "cinemaTagline": "Tagline", + "cinemaThe": "The", + "drawerAdmin": "Administration", + "drawerAndroidAppLink": "https://play.google.com/store/apps/details?id=fr.myecl.titan", + "drawerCopied": "Copied!", + "drawerDownloadAppOnMobileDevice": "This site is the web version of the MyECL app. We invite you to download the app. Use this site only if you have problems with the app.\n", + "drawerIosAppLink": "https://apps.apple.com/fr/app/myecl/id6444443430", + "drawerLoginOut": "Do you want to log out?", + "drawerLogOut": "Log out", + "drawerOr": " or ", + "drawerSettings": "Settings", + "eventAdd": "Add", + "eventAddEvent": "Add an event", + "eventAddedEvent": "Event added", + "eventAddingError": "Error while adding", + "eventAllDay": "All day", + "eventConfirm": "Confirm", + "eventConfirmEvent": "Confirm the event?", + "eventConfirmation": "Confirmation", + "eventConfirmed": "Confirmed", + "eventDates": "Dates", + "eventDecline": "Decline", + "eventDeclineEvent": "Decline the event?", + "eventDeclined": "Declined", + "eventDelete": "Delete", + "eventDeletedEvent": "Event deleted", + "eventDeleting": "Deleting", + "eventDeletingError": "Error while deleting", + "eventDeletingEvent": "Delete the event?", + "eventDescription": "Description", + "eventEdit": "Edit", + "eventEditEvent": "Edit an event", + "eventEditedEvent": "Event edited", + "eventEditingError": "Error while editing", + "eventEndDate": "End date", + "eventEndHour": "End hour", + "eventError": "Error", + "eventEventList": "Event list", + "eventEventType": "Event type", + "eventEvery": "Every", + "eventHistory": "History", + "eventIncorrectOrMissingFields": "Some fields are incorrect or missing", + "eventInterval": "Interval", + "eventInvalidDates": "End date must be after start date", + "eventInvalidIntervalError": "Please enter a valid interval", + "eventLocation": "Location", + "eventMyEvents": "My events", + "eventName": "Name", + "eventNext": "Next", + "eventNo": "No", + "eventNoCurrentEvent": "No current event", + "eventNoDateError": "Please enter a date", + "eventNoDaySelected": "No day selected", + "eventNoDescriptionError": "Please enter a description", + "eventNoEvent": "No event", + "eventNoNameError": "Please enter a name", + "eventNoOrganizerError": "Please enter an organizer", + "eventNoPlaceError": "Please enter a location", + "eventNoPhoneRegistered": "Number not provided", + "eventNoRuleError": "Please enter a recurrence rule", + "eventOrganizer": "Organizer", + "eventOther": "Other", + "eventPending": "Pending", + "eventPrevious": "Previous", + "eventRecurrence": "Recurrence", + "eventRecurrenceDays": "Recurrence days", + "eventRecurrenceEndDate": "Recurrence end date", + "eventRecurrenceRule": "Recurrence rule", + "eventRoom": "Room", + "eventStartDate": "Start date", + "eventStartHour": "Start hour", + "eventTitle": "Events", + "eventYes": "Yes", + "eventEventEvery": "Every", + "eventWeeks": "weeks", + "eventDayMon": "Monday", + "eventDayTue": "Tuesday", + "eventDayWed": "Wednesday", + "eventDayThu": "Thursday", + "eventDayFri": "Friday", + "eventDaySat": "Saturday", + "eventDaySun": "Sunday", + "globalConfirm": "Confirm", + "globalCancel": "Cancel", + "globalIrreversibleAction": "This action is irreversible", + "globalOptionnal": "{text} (Optional)", + "@globalOptionnal": { + "description": "Text with optional complement", + "placeholders": { + "text": { + "type": "String" + } + } + }, + "homeCalendar": "Calendar", + "homeEventOf": "Events of", + "homeIncomingEvents": "Upcoming events", + "homeLastInfos": "Latest announcements", + "homeNoEvents": "No events", + "homeTranslateDayShortMon": "Mon", + "homeTranslateDayShortTue": "Tue", + "homeTranslateDayShortWed": "Wed", + "homeTranslateDayShortThu": "Thu", + "homeTranslateDayShortFri": "Fri", + "homeTranslateDayShortSat": "Sat", + "homeTranslateDayShortSun": "Sun", + "loanAdd": "Add", + "loanAddLoan": "Add a loan", + "loanAddObject": "Add an object", + "loanAddedLoan": "Loan added", + "loanAddedObject": "Object added", + "loanAddedRoom": "Room added", + "loanAddingError": "Error while adding", + "loanAdmin": "Administrator", + "loanAvailable": "Available", + "loanAvailableMultiple": "Available", + "loanBorrowed": "Borrowed", + "loanBorrowedMultiple": "Borrowed", + "loanAnd": "and", + "loanAssociation": "Association", + "loanAvailableItems": "Available items", + "loanBeginDate": "Loan start date", + "loanBorrower": "Borrower", + "loanCaution": "Deposit", + "loanCancel": "Cancel", + "loanConfirm": "Confirm", + "loanConfirmation": "Confirmation", + "loanDates": "Dates", + "loanDays": "Days", + "loanDelay": "Extension delay", + "loanDelete": "Delete", + "loanDeletingLoan": "Delete the loan?", + "loanDeletedItem": "Object deleted", + "loanDeletedLoan": "Loan deleted", + "loanDeleting": "Deleting", + "loanDeletingError": "Error while deleting", + "loanDeletingItem": "Delete the object?", + "loanDuration": "Duration", + "loanEdit": "Edit", + "loanEditItem": "Edit the object", + "loanEditLoan": "Edit the loan", + "loanEditedRoom": "Room edited", + "loanEndDate": "Loan end date", + "loanEnded": "Ended", + "loanEnterDate": "Please enter a date", + "loanExtendedLoan": "Extended loan", + "loanExtendingError": "Error while extending", + "loanHistory": "History", + "loanIncorrectOrMissingFields": "Some fields are missing or incorrect", + "loanInvalidNumber": "Please enter a number", + "loanInvalidDates": "Dates are not valid", + "loanItem": "Item", + "loanItems": "Items", + "loanItemHandling": "Item management", + "loanItemSelected": "selected item", + "loanItemsSelected": "selected items", + "loanLendingDuration": "Possible loan duration", + "loanLoan": "Loan", + "loanLoanHandling": "Loan management", + "loanLooking": "Searching", + "loanName": "Name", + "loanNext": "Next", + "loanNo": "No", + "loanNoAssociationsFounded": "No associations found", + "loanNoAvailableItems": "No available items", + "loanNoBorrower": "No borrower", + "loanNoItems": "No items", + "loanNoItemSelected": "No item selected", + "loanNoLoan": "No loan", + "loanNoReturnedDate": "No return date", + "loanQuantity": "Quantity", + "loanNone": "None", + "loanNote": "Note", + "loanNoValue": "Please enter a value", + "loanOnGoing": "Ongoing", + "loanOnGoingLoan": "Ongoing loan", + "loanOthers": "others", + "loanPaidCaution": "Deposit paid", + "loanPositiveNumber": "Please enter a positive number", + "loanPrevious": "Previous", + "loanReturned": "Returned", + "loanReturnedLoan": "Returned loan", + "loanReturningError": "Error while returning", + "loanReturningLoan": "Return", + "loanReturnLoan": "Return the loan?", + "loanReturnLoanDescription": "Do you want to return this loan?", + "loanToReturn": "To return", + "loanUnavailable": "Unavailable", + "loanUpdate": "Edit", + "loanUpdatedItem": "Item updated", + "loanUpdatedLoan": "Loan updated", + "loanUpdatingError": "Error while updating", + "loanYes": "Yes", + "loginAccountActivated": "Account activated", + "loginAccountNotActivated": "Account not activated", + "loginActivationCode": "Activation code", + "loginBirthday": "Date of birth", + "loginCanBeEmpty": "This field can be empty", + "loginConfirmPassword": "Confirm password", + "loginCreate": "Create", + "loginCreateAccount": "Create an account", + "loginCreateAccountTitle": "Create an\naccount", + "loginEmail": "Email", + "loginEmailEmpty": "Please enter an email address", + "loginEmailInvalid": "Please enter a Centrale email address.\nIf you don't have one, please contact Éclair", + "loginEmptyFieldError": "This field cannot be empty", + "loginEndActivation": "Complete activation", + "loginEndResetPassword": "Complete\npassword reset", + "loginErrorResetPassword": "Error during reset", + "loginExpectingDate": "A date is expected", + "loginFillAllFields": "Please fill all fields", + "loginFirstname": "First name", + "loginFloor": "Floor", + "loginForgetPassword": "Forgot\npassword", + "loginForgotPassword": "Forgot password?", + "loginInvalidToken": "Invalid activation code", + "loginLoginFailed": "Login failed", + "loginMailSendingError": "Error during account creation", + "loginMustBeIntError": "This field must be an integer", + "loginName": "Last name", + "loginNewPassword": "New password", + "loginPassword": "Password", + "loginPasswordLengthError": "Password must be at least 6 characters", + "loginPasswordUppercaseError": "Password must contain at least one uppercase letter", + "loginPasswordLowercaseError": "Password must contain at least one lowercase letter", + "loginPasswordNumberError": "Password must contain at least one number", + "loginPasswordSpecialCaracterError": "Password must contain at least one special character", + "loginPasswordMustMatch": "Passwords must match", + "loginPasswordStrengthVeryWeak": "Very weak", + "loginPasswordStrengthWeak": "Weak", + "loginPasswordStrengthMedium": "Medium", + "loginPasswordStrengthStrong": "Strong", + "loginPasswordStrengthVeryStrong": "Very strong", + "loginPhone": "Phone", + "loginPromo": "Incoming class (e.g., 2023)", + "loginSendedMail": "Confirmation email sent", + "loginSendedResetMail": "Reset email sent", + "loginSignIn": "Sign in", + "loginRegister": "Register", + "loginRecievedMail": "I received the email", + "loginRecover": "Reset", + "loginResetedPassword": "Password reset", + "loginResetPasswordTitle": "Reset\npassword", + "loginNickname": "Nickname", + "loginWelcomeBack": "Welcome back", + "loginAppName": "MyECL", + "othersCheckInternetConnection": "Please check your internet connection", + "othersRetry": "Retry", + "othersTooOldVersion": "Your app version is too old.\n\nPlease update the app.", + "othersUnableToConnectToServer": "Unable to connect to the server", + "othersVersion": "Version", + "othersNoModule": "No modules available, please try again later 😢😢", + "othersAdmin": "Admin", + "othersError": "An error occurred", + "othersNoValue": "Please enter a value", + "othersInvalidNumber": "Please enter a number", + "othersNoDateError": "Please enter a date", + "othersImageSizeTooBig": "Image size must not exceed 4 MB", + "othersImageError": "Error adding the image", + "phAddNewJournal": "Add a new journal", + "phNameField": "Name: ", + "phDateField": "Date: ", + "phDelete": "Are you sure you want to delete this journal?", + "phIrreversibleAction": "This action is irreversible", + "phToHeavyFile": "File too large", + "phAddPdfFile": "Add a PDF file", + "phEditPdfFile": "Edit PDF file", + "phPhName": "PH name", + "phDate": "Date", + "phAdded": "Added", + "phEdited": "Edited", + "phAddingFileError": "Add error", + "phMissingInformatonsOrPdf": "Missing information or PDF file", + "phAdd": "Add", + "phEdit": "Edit", + "phSeePreviousJournal": "See previous journals", + "phNoJournalInDatabase": "No PH yet in database", + "phSuccesDowloading": "Successfully downloaded", + "phonebookAdd": "Add", + "phonebookAddAssociation": "Add an association", + "phonebookAddAssociationGroupement": "Add an association groupement", + "phonebookAddedAssociation": "Association added", + "phonebookAddedMember": "Member added", + "phonebookAddingError": "Error adding", + "phonebookAddMember": "Add a member", + "phonebookAddRole": "Add a role", + "phonebookAdmin": "Administation", + "phonebookAll": "All", + "phonebookApparentName": "Public role name:", + "phonebookAssociation": "Association", + "phonebookAssociationDetail": "Association details:", + "phonebookAssociationGroupement": "Association groupement", + "phonebookAssociationKind": "Type of association:", + "phonebookAssociationName": "Association name", + "phonebookAssociations": "Associations", + "phonebookCancel": "Cancel", + "phonebookChangeTermYear": "Switch to {year} term", + "@phonebookChangeTermYear": { + "description": "Change the term year of the association", + "placeholders": { + "year": { + "type": "int" + } + } + }, + "phonebookChangeTermConfirm": "Are you sure you want to change the entire term?\nThis action is irreversible!", + "phonebookClose": "Close", + "phonebookConfirm": "Confirm", + "phonebookCopied": "Copied to clipboard", + "phonebookDeactivateAssociation": "Deactivate association", + "phonebookDeactivatedAssociation": "Association deactivated", + "phonebookDeactivatedAssociationWarning": "Warning, this association is deactivated, you cannot modify it", + "phonebookDeactivateSelectedAssociation": "Désactiver l'association {association} ?", + "@phonebookDeactivateSelectedAssociation": { + "description": "Permet de désactiver une association", + "placeholders": { + "association": { + "type": "String" + } + } + }, + "phonebookDeactivatingError": "Error during deactivation", + "phonebookDetail": "Details:", + "phonebookDelete": "Delete", + "phonebookDeleteAssociation": "Delete association", + "phonebookDeleteSelectedAssociation": "Delete the association {association}?", + "@phonebookDeleteSelectedAssociation": { + "description": "Delete an association", + "placeholders": { + "association": { + "type": "String" + } + } + }, + "phonebookDeleteAssociationDescription": "This will erase all association history", + "phonebookDeletedAssociation": "Association deleted", + "phonebookDeletedMember": "Member deleted", + "phonebookDeleteRole": "Delete role", + "phonebookDeleteUserRole": "Delete the role of {name}?", + "@phonebookDeleteUserRole": { + "description": "Delete the role of a user", + "placeholders": { + "name": { + "type": "String" + } + } + }, + "phonebookDeleting": "Deleting", + "phonebookDeletingError": "Error deleting", + "phonebookDescription": "Description", + "phonebookEdit": "Edit", + "phonebookEditAssociationGroupement": "Edit association groupement", + "phonebookEditAssociationGroups": "Manage groups", + "phonebookEditAssociationInfo": "Edit", + "phonebookEditAssociationMembers": "Manage members", + "phonebookEditRole": "Edit role", + "phonebookEmail": "Email:", + "phonebookEmailCopied": "Email copied to clipboard", + "phonebookEmptyApparentName": "Please enter a role name", + "phonebookEmptyFieldError": "A field is not filled", + "phonebookEmptyKindError": "Please choose an association type", + "phonebookEmptyMember": "No member selected", + "phonebookErrorAssociationLoading": "Error loading association", + "phonebookErrorAssociationNameEmpty": "Please enter an association name", + "phonebookErrorAssociationPicture": "Error editing association picture", + "phonebookErrorKindsLoading": "Error loading association types", + "phonebookErrorLoadAssociationList": "Error loading association list", + "phonebookErrorLoadAssociationMember": "Error loading association members", + "phonebookErrorLoadAssociationPicture": "Error loading association picture", + "phonebookErrorLoadProfilePicture": "Error", + "phonebookErrorRoleTagsLoading": "Error loading role tags", + "phonebookExistingMembership": "This member is already in the current term", + "phonebookFilter": "Filter", + "phonebookFilterDescription": "Filter the associations by their groupement", + "phonebookFirstname": "First name:", + "phonebookGroupementDeleted": "Association groupement deleted", + "phonebookGroupementDeleteError": "Error deleting association groupement", + "phonebookGroupementName": "Groupement name", + "phonebookGroups": "Manage {association} groups", + "@phonebookGroups": { + "description": "Manage the groups of an association", + "placeholders": { + "association": { + "type": "String" + } + } + }, + "phonebookTerm": "{year} term", + "@phonebookTerm": { + "description": "Term year of the association", + "placeholders": { + "year": { + "type": "int" + } + } + }, + "phonebookTermChangingError": "Error changing term", + "phonebookMember": "Member", + "phonebookMemberReordered": "Member reordered", + "phonebookMembers": "Manage {association} members", + "@phonebookMembers": { + "description": "Manage the members of an association", + "placeholders": { + "association": { + "type": "String" + } + } + }, + "phonebookMembershipAssociationError": "Please choose an association", + "phonebookMembershipRole": "Role:", + "phonebookMembershipRoleError": "Please choose a role", + "phonebookModifyMembership": "Modify {name}'s role", + "@phonebookModifyMembership": { + "description": "Modify the role of a member", + "placeholders": { + "name": { + "type": "String" + } + } + }, + "phonebookName": "Last name:", + "phonebookNameCopied": "Name and first name copied to clipboard", + "phonebookNamePure": "Last name", + "phonebookNewTerm": "New term", + "phonebookNewTermConfirmed": "Term changed", + "phonebookNickname": "Nickname:", + "phonebookNicknameCopied": "Nickname copied to clipboard", + "phonebookNoAssociationFound": "No association found", + "phonebookNoMember": "No member", + "phonebookNoMemberRole": "No role found", + "phonebookNoRoleTags": "No role tags found", + "phonebookPhone": "Phone:", + "phonebookPhonebook": "Phonebook", + "phonebookPhonebookSearch": "Search", + "phonebookPhonebookSearchAssociation": "Association", + "phonebookPhonebookSearchField": "Search:", + "phonebookPhonebookSearchName": "Last name/First name/Nickname", + "phonebookPhonebookSearchRole": "Position", + "phonebookPresidentRoleTag": "Prez'", + "phonebookPromoNotGiven": "Promotion not provided", + "phonebookPromotion": "Promotion {year}", + "@phonebookPromotion": { + "description": "Promotion year of the member", + "placeholders": { + "year": { + "type": "int" + } + } + }, + "phonebookReorderingError": "Error during reordering", + "phonebookResearch": "Search", + "phonebookRolePure": "Role", + "phonebookSearchUser": "Search a user", + "phonebookTooHeavyAssociationPicture": "Image is too large (max 4MB)", + "phonebookUpdateGroups": "Update groups", + "phonebookUpdatedAssociation": "Association updated", + "phonebookUpdatedAssociationPicture": "Association picture has been changed", + "phonebookUpdatedGroups": "Groups updated", + "phonebookUpdatedMember": "Member updated", + "phonebookUpdatingError": "Error during update", + "phonebookValidation": "Validate", + "purchasesPurchases": "Purchases", + "purchasesResearch": "Search", + "purchasesNoPurchasesFound": "No purchases found", + "purchasesNoTickets": "No tickets", + "purchasesTicketsError": "Error loading tickets", + "purchasesPurchasesError": "Error loading purchases", + "purchasesNoPurchases": "No purchase", + "purchasesTimes": "times", + "purchasesAlreadyUsed": "Already used", + "purchasesNotPaid": "Not validated", + "purchasesPleaseSelectProduct": "Please select a product", + "purchasesProducts": "Products", + "purchasesCancel": "Cancel", + "purchasesValidate": "Validate", + "purchasesLeftScan": "Scans remaining", + "purchasesTag": "Tag", + "purchasesHistory": "History", + "purchasesPleaseSelectSeller": "Please select a seller", + "purchasesNoTagGiven": "Warning, no tag entered", + "purchasesTickets": "Tickets", + "purchasesNoScannableProducts": "No scannable products", + "purchasesLoading": "Waiting for scan", + "purchasesScan": "Scan", + "raffleRaffle": "Raffle", + "rafflePrize": "Prize", + "rafflePrizes": "Prizes", + "raffleActualRaffles": "Current raffles", + "rafflePastRaffles": "Past raffles", + "raffleYourTickets": "All your tickets", + "raffleCreateMenu": "Creation menu", + "raffleNextRaffles": "Upcoming raffles", + "raffleNoTicket": "You have no ticket", + "raffleSeeRaffleDetail": "View prizes/tickets", + "raffleActualPrize": "Current prizes", + "raffleMajorPrize": "Major prizes", + "raffleTakeTickets": "Take your tickets", + "raffleNoTicketBuyable": "You cannot buy tickets right now", + "raffleNoCurrentPrize": "There are no prizes currently", + "raffleModifTombola": "You can modify your raffles or create new ones, all decisions must then be approved by admins", + "raffleCreateYourRaffle": "Your raffle creation menu", + "rafflePossiblePrice": "Possible prize", + "raffleInformation": "Information and statistics", + "raffleAccounts": "Accounts", + "raffleAdd": "Add", + "raffleUpdatedAmount": "Amount updated", + "raffleUpdatingError": "Error during update", + "raffleDeletedPrize": "Prize deleted", + "raffleDeletingError": "Error during deletion", + "raffleQuantity": "Quantity", + "raffleClose": "Close", + "raffleOpen": "Open", + "raffleAddTypeTicketSimple": "Add", + "raffleAddingError": "Error during addition", + "raffleEditTypeTicketSimple": "Edit", + "raffleFillField": "Field cannot be empty", + "raffleWaiting": "Loading", + "raffleEditingError": "Error during editing", + "raffleAddedTicket": "Ticket added", + "raffleEditedTicket": "Ticket edited", + "raffleAlreadyExistTicket": "Ticket already exists", + "raffleNumberExpected": "An integer is expected", + "raffleDeletedTicket": "Ticket deleted", + "raffleAddPrize": "Add", + "raffleEditPrize": "Edit", + "raffleOpenRaffle": "Open raffle", + "raffleCloseRaffle": "Close raffle", + "raffleOpenRaffleDescription": "You are going to open the raffle, users will be able to buy tickets. You will no longer be able to modify the raffle. Are you sure you want to continue?", + "raffleCloseRaffleDescription": "You are going to close the raffle, users will no longer be able to buy tickets. Are you sure you want to continue?", + "raffleNoCurrentRaffle": "There is no ongoing raffle", + "raffleBoughtTicket": "Ticket purchased", + "raffleDrawingError": "Error during drawing", + "raffleInvalidPrice": "Price must be greater than 0", + "raffleMustBePositive": "Number must be strictly positive", + "raffleDraw": "Draw", + "raffleDrawn": "Drawn", + "raffleError": "Error", + "raffleGathered": "Collected", + "raffleTickets": "Tickets", + "raffleTicket": "ticket", + "raffleWinner": "Winner", + "raffleNoPrize": "No prize", + "raffleDeletePrize": "Delete prize", + "raffleDeletePrizeDescription": "You are going to delete the prize, are you sure you want to continue?", + "raffleDrawing": "Drawing", + "raffleDrawingDescription": "Draw the prize winner?", + "raffleDeleteTicket": "Delete ticket", + "raffleDeleteTicketDescription": "You are going to delete the ticket, are you sure you want to continue?", + "raffleWinningTickets": "Winning tickets", + "raffleNoWinningTicketYet": "Winning tickets will be displayed here", + "raffleName": "Name", + "raffleDescription": "Description", + "raffleBuyThisTicket": "Buy this ticket", + "raffleLockedRaffle": "Locked raffle", + "raffleUnavailableRaffle": "Unavailable raffle", + "raffleNotEnoughMoney": "You don't have enough money", + "raffleWinnable": "winnable", + "raffleNoDescription": "No description", + "raffleAmount": "Balance", + "raffleLoading": "Loading", + "raffleTicketNumber": "Number of tickets", + "rafflePrice": "Price", + "raffleEditRaffle": "Edit raffle", + "raffleEdit": "Edit", + "raffleAddPackTicket": "Add ticket pack", + "recommendationRecommendation": "Recommendation", + "recommendationTitle": "Title", + "recommendationLogo": "Logo", + "recommendationCode": "Code", + "recommendationSummary": "Short summary", + "recommendationDescription": "Description", + "recommendationAdd": "Add", + "recommendationEdit": "Edit", + "recommendationDelete": "Delete", + "recommendationAddImage": "Please add an image", + "recommendationAddedRecommendation": "Deal added", + "recommendationEditedRecommendation": "Deal updated", + "recommendationDeleteRecommendationConfirmation": "Are you sure you want to delete this deal?", + "recommendationDeleteRecommendation": "Delete", + "recommendationDeletingRecommendationError": "Error during deletion", + "recommendationDeletedRecommendation": "Deal deleted", + "recommendationIncorrectOrMissingFields": "Incorrect or missing fields", + "recommendationEditingError": "Edit failed", + "recommendationAddingError": "Add failed", + "recommendationCopiedCode": "Discount code copied", + "seedLibraryAdd": "Add", + "seedLibraryAddedPlant": "Plant added", + "seedLibraryAddedSpecies": "Species added", + "seedLibraryAddingError": "Error during addition", + "seedLibraryAddPlant": "Deposit a plant", + "seedLibraryAddSpecies": "Add a species", + "seedLibraryAll": "All", + "seedLibraryAncestor": "Ancestor", + "seedLibraryAround": "around", + "seedLibraryAutumn": "Autumn", + "seedLibraryBorrowedPlant": "Borrowed plant", + "seedLibraryBorrowingDate": "Borrowing date:", + "seedLibraryBorrowPlant": "Borrow plant", + "seedLibraryCard": "Card", + "seedLibraryChoosingAncestor": "Please choose an ancestor", + "seedLibraryChoosingSpecies": "Please choose a species", + "seedLibraryChoosingSpeciesOrAncestor": "Please choose a species or an ancestor", + "seedLibraryContact": "Contact:", + "seedLibraryDays": "days", + "seedLibraryDeadMsg": "Do you want to declare the plant dead?", + "seedLibraryDeadPlant": "Dead plant", + "seedLibraryDeathDate": "Date of death", + "seedLibraryDeletedSpecies": "Species deleted", + "seedLibraryDeleteSpecies": "Delete species?", + "seedLibraryDeleting": "Deleting", + "seedLibraryDeletingError": "Error during deletion", + "seedLibraryDepositNotAvailable": "Plant deposit is not possible without borrowing a plant first", + "seedLibraryDescription": "Description", + "seedLibraryDifficulty": "Difficulty:", + "seedLibraryEdit": "Edit", + "seedLibraryEditedPlant": "Plant updated", + "seedLibraryEditInformation": "Edit information", + "seedLibraryEditingError": "Error during editing", + "seedLibraryEditSpecies": "Edit species", + "seedLibraryEmptyDifficultyError": "Please choose a difficulty", + "seedLibraryEmptyFieldError": "Please fill all fields", + "seedLibraryEmptyTypeError": "Please choose a plant type", + "seedLibraryEndMonth": "End month:", + "seedLibraryFacebookUrl": "Facebook link", + "seedLibraryFilters": "Filters", + "seedLibraryForum": "Oskour mom I killed my plant - Help forum", + "seedLibraryForumUrl": "Forum link", + "seedLibraryHelpSheets": "Plant sheets", + "seedLibraryInformation": "Information:", + "seedLibraryMaturationTime": "Maturation time", + "seedLibraryMonthJan": "January", + "seedLibraryMonthFeb": "February", + "seedLibraryMonthMar": "March", + "seedLibraryMonthApr": "April", + "seedLibraryMonthMay": "May", + "seedLibraryMonthJun": "June", + "seedLibraryMonthJul": "July", + "seedLibraryMonthAug": "August", + "seedLibraryMonthSep": "September", + "seedLibraryMonthOct": "October", + "seedLibraryMonthNov": "November", + "seedLibraryMonthDec": "December", + "seedLibraryMyPlants": "My plants", + "seedLibraryName": "Name", + "seedLibraryNbSeedsRecommended": "Number of seeds recommended", + "seedLibraryNbSeedsRecommendedError": "Please enter a recommended seed number greater than 0", + "seedLibraryNoDateError": "Please enter a date", + "seedLibraryNoFilteredPlants": "No plants match your search. Try other filters.", + "seedLibraryNoMorePlant": "No plants available", + "seedLibraryNoPersonalPlants": "You don't have any plants yet in your seed library. You can add some in the stocks.", + "seedLibraryNoSpecies": "No species found", + "seedLibraryNoStockPlants": "No plants available in stock", + "seedLibraryNotes": "Notes", + "seedLibraryOk": "OK", + "seedLibraryPlantationPeriod": "Planting period:", + "seedLibraryPlantationType": "Plantation type:", + "seedLibraryPlantDetail": "Plant details", + "seedLibraryPlantingDate": "Planting date", + "seedLibraryPlantingNow": "I'm planting it now", + "seedLibraryPrefix": "Prefix", + "seedLibraryPrefixError": "Prefix already used", + "seedLibraryPrefixLengthError": "The prefix must be 3 characters", + "seedLibraryPropagationMethod": "Propagation method:", + "seedLibraryReference": "Reference:", + "seedLibraryRemovedPlant": "Plant removed", + "seedLibraryRemovingError": "Error removing plant", + "seedLibraryResearch": "Search", + "seedLibrarySaveChanges": "Save changes", + "seedLibrarySeason": "Season:", + "seedLibrarySeed": "Seed", + "seedLibrarySeeds": "seeds", + "seedLibrarySeedDeposit": "Plant deposit", + "seedLibrarySeedLibrary": "Seed library", + "seedLibrarySeedQuantitySimple": "Seed quantity", + "seedLibrarySeedQuantity": "Seed quantity:", + "seedLibraryShowDeadPlants": "Show dead plants", + "seedLibrarySpecies": "Species:", + "seedLibrarySpeciesHelp": "Help on species", + "seedLibrarySpeciesPlural": "Species", + "seedLibrarySpeciesSimple": "Species", + "seedLibrarySpeciesType": "Species type:", + "seedLibrarySpring": "Spring", + "seedLibraryStartMonth": "Start month:", + "seedLibraryStock": "Available stock", + "seedLibrarySummer": "Summer", + "seedLibraryStocks": "Stocks", + "seedLibraryTimeUntilMaturation": "Time until maturation:", + "seedLibraryType": "Type:", + "seedLibraryUnableToOpen": "Unable to open link", + "seedLibraryUpdate": "Edit", + "seedLibraryUpdatedInformation": "Information updated", + "seedLibraryUpdatedSpecies": "Species updated", + "seedLibraryUpdatedPlant": "Plant updated", + "seedLibraryUpdatingError": "Error updating", + "seedLibraryWinter": "Winter", + "seedLibraryWriteReference": "Please write the following reference: ", + "settingsAccount": "Account", + "settingsAddProfilePicture": "Add a photo", + "settingsAdmin": "Administrator", + "settingsAskHelp": "Ask for help", + "settingsAssociation": "Association", + "settingsBirthday": "Birthday", + "settingsBugs": "Bugs", + "settingsChangePassword": "Change password", + "settingsChangingPassword": "Do you really want to change your password?", + "settingsConfirmPassword": "Confirm password", + "settingsCopied": "Copied!", + "settingsDarkMode": "Dark mode", + "settingsDarkModeOff": "Off", + "settingsDeleteLogs": "Delete logs?", + "settingsDeleteNotificationLogs": "Delete notification logs?", + "settingsDetelePersonalData": "Delete my personal data", + "settingsDetelePersonalDataDesc": "This action notifies the administrator that you want to delete your personal data.", + "settingsDeleting": "Deleting", + "settingsEdit": "Edit", + "settingsEditAccount": "Edit account", + "settingsEditPassword": "Edit password", + "settingsEmail": "Email", + "settingsEmptyField": "This field cannot be empty", + "settingsErrorProfilePicture": "Error editing profile picture", + "settingsErrorSendingDemand": "Error sending request", + "settingsEventsIcal": "Ical link for events", + "settingsExpectingDate": "Expected birth date", + "settingsFirstname": "First name", + "settingsFloor": "Floor", + "settingsHelp": "Help", + "settingsIcalCopied": "Ical link copied!", + "settingsLanguage": "Language", + "settingsLanguageFr": "French", + "settingsLogs": "Logs", + "settingsModules": "Modules", + "settingsMyIcs": "My Ical link", + "settingsName": "Last name", + "settingsNewPassword": "New password", + "settingsNickname": "Nickname", + "settingsNotifications": "Notifications", + "settingsOldPassword": "Old password", + "settingsPasswordChanged": "Password changed", + "settingsPasswordsNotMatch": "Passwords do not match", + "settingsPersonalData": "Personal data", + "settingsPersonalisation": "Personalization", + "settingsPhone": "Phone", + "settingsProfilePicture": "Profile picture", + "settingsPromo": "Promotion", + "settingsRepportBug": "Report a bug", + "settingsSave": "Save", + "settingsSecurity": "Security", + "settingsSendedDemand": "Request sent", + "settingsSettings": "Settings", + "settingsTooHeavyProfilePicture": "Image is too large (max 4MB)", + "settingsUpdatedProfile": "Profile updated", + "settingsUpdatedProfilePicture": "Profile picture updated", + "settingsUpdateNotification": "Update notifications", + "settingsUpdatingError": "Error updating profile", + "settingsVersion": "Version", + "settingsPasswordStrength": "Password strength", + "settingsPasswordStrengthVeryWeak": "Very weak", + "settingsPasswordStrengthWeak": "Weak", + "settingsPasswordStrengthMedium": "Medium", + "settingsPasswordStrengthStrong": "Strong", + "settingsPasswordStrengthVeryStrong": "Very strong", + "voteAdd": "Add", + "voteAddMember": "Add a member", + "voteAddedPretendance": "List added", + "voteAddedSection": "Section added", + "voteAddingError": "Error adding", + "voteAddPretendance": "Add a list", + "voteAddSection": "Add a section", + "voteAll": "All", + "voteAlreadyAddedMember": "Member already added", + "voteAlreadyVoted": "Vote recorded", + "voteChooseList": "Choose a list", + "voteClear": "Reset", + "voteClearVotes": "Reset votes", + "voteClosedVote": "Votes closed", + "voteCloseVote": "Close votes", + "voteConfirmVote": "Confirm vote", + "voteCountVote": "Count votes", + "voteDeletedAll": "All deleted", + "voteDeletedPipo": "Fake lists deleted", + "voteDeletedSection": "Section deleted", + "voteDeleteAll": "Delete all", + "voteDeleteAllDescription": "Do you really want to delete everything?", + "voteDeletePipo": "Delete fake lists", + "voteDeletePipoDescription": "Do you really want to delete the fake lists?", + "voteDeletePretendance": "Delete the list", + "voteDeletePretendanceDesc": "Do you really want to delete this list?", + "voteDeleteSection": "Delete the section", + "voteDeleteSectionDescription": "Do you really want to delete this section?", + "voteDeletingError": "Error deleting", + "voteDescription": "Description", + "voteEdit": "Edit", + "voteEditedPretendance": "List edited", + "voteEditedSection": "Section edited", + "voteEditingError": "Error editing", + "voteErrorClosingVotes": "Error closing votes", + "voteErrorCountingVotes": "Error counting votes", + "voteErrorResetingVotes": "Error resetting votes", + "voteErrorOpeningVotes": "Error opening votes", + "voteIncorrectOrMissingFields": "Incorrect or missing fields", + "voteMembers": "Members", + "voteName": "Name", + "voteNoPretendanceList": "No list of candidates", + "voteNoSection": "No section", + "voteCanNotVote": "You cannot vote", + "voteNoSectionList": "No section", + "voteNotOpenedVote": "Vote not opened", + "voteOnGoingCount": "Counting in progress", + "voteOpenVote": "Open votes", + "votePipo": "Fake", + "votePretendance": "Lists", + "votePretendanceDeleted": "Candidate list deleted", + "votePretendanceNotDeleted": "Error deleting", + "voteProgram": "Program", + "votePublish": "Publish", + "votePublishVoteDescription": "Do you really want to publish the votes?", + "voteResetedVotes": "Votes reset", + "voteResetVote": "Reset votes", + "voteResetVoteDescription": "What do you want to do?", + "voteRole": "Role", + "voteSectionDescription": "Section description", + "voteSection": "Section", + "voteSectionName": "Section name", + "voteSeeMore": "See more", + "voteSelected": "Selected", + "voteShowVotes": "Show votes", + "voteVote": "Vote", + "voteVoteError": "Error recording vote", + "voteVoteFor": "Vote for ", + "voteVoteNotStarted": "Vote not opened", + "voteVoters": "Voting groups", + "voteVoteSuccess": "Vote recorded", + "voteVotes": "Votes", + "voteVotesClosed": "Votes closed", + "voteVotesCounted": "Votes counted", + "voteVotesOpened": "Votes opened", + "voteWarning": "Warning", + "voteWarningMessage": "Selection will not be saved.\nDo you want to continue?", + "moduleAdvert": "Advert", + "moduleAmap": "AMAP", + "moduleBooking": "Booking", + "moduleCalendar": "Calendar", + "moduleCentralisation": "Centralisation", + "moduleCinema": "Cinema", + "moduleEvent": "Event", + "moduleFlappyBird": "Flappy Bird", + "moduleLoan": "Loan", + "modulePhonebook": "Phonebook", + "modulePurchases": "Purchases", + "moduleRaffle": "Raffle", + "moduleRecommendation": "Recommendation", + "moduleSeedLibrary": "Seed Library", + "moduleVote": "Vote", + "modulePh": "PH", + "moduleSettings": "Settings", + "moduleFeed": "Feed", + "moduleStyleGuide": "StyleGuide", + "moduleAdmin": "Administration", + "moduleOthers": "Others", + "modulePayment": "Payment", + "paiementTopUp": "Top-up", + "paiementStoreManagement": "Association management", + "paiementDeleteStore": "Delete association", + "paiementDeleteStoreDescription": "Are you sure you want to delete this association?", + "paiementDeleteStoreError": "Unable to delete the association", + "paiementStoreDeleted": "Association deleted", + "paiementAddThisDevice": "Add this device", + "paiementThisDevice": "(this device)", + "paiementCancelled": "Cancelled", + "paiementThe": "The", + "paiementOf": "of", + "paiementRefundedThe": "Refunded on", + "paiementAt": "at", + "paiementPleaseAcceptTOS": "Please accept the Terms of Service.", + "paiementAskDeviceActivation": "Device activation request", + "paiementDeviceActivationReceived": "The activation request has been received, please check your email to finalize the process", + "paiementRevokeDevice": "Revoke device?", + "paiementRevokeDeviceDescription": "You will no longer be able to use this device for payments", + "paiementDeviceRevoked": "Device revoked", + "paiementDeviceRevokingError": "Error while revoking device", + "paiementPleaseAcceptPopup": "Please allow popups", + "paiementProceedSuccessfully": "Payment completed successfully", + "paiementCancelledTransaction": "Payment cancelled", + "paiementPleaseEnterMinAmount": "Please enter an amount greater than 1", + "paiementMaxAmount": "The maximum wallet amount is", + "paiementPayWithHA": "Pay with HelloAsso", + "paiementBalanceAfterTopUp": "Balance after top-up:", + "paiementPersonalBalance": "Personal balance", + "paiementDevices": "Devices", + "paiementPay": "Pay", + "paiementDeviceNotRegistered": "Device not registered", + "paiementDeviceNotRegisteredDescription": "Your device is not registered yet. \nTo register it, please go to the devices page.", + "paiementAccessPage": "Access the page", + "paiementDeviceNotActivated": "Device not activated", + "paiementDeviceNotActivatedDescription": "Your device is not yet activated. \nTo activate it, please go to the devices page.", + "paiementReactivateRevokedDeviceDescription": "Your device has been revoked. \nTo reactivate it, please go to the devices page.", + "paiementDeviceRecoveryError": "Error while retrieving device", + "paiementStats": "Stats", + "paimentTopUpAction": "Top-up", + "paiementGetBalanceError": "Error while retrieving balance: ", + "paiementLastTransactions": "Latest transactions", + "paiementGetTransactionsError": "Error while retrieving transactions: ", + "paiementStoreBalance": "Association balance", + "paiementScan": "Scan", + "paiementManagement": "Management", + "paiementHistory": "History", + "paiementHandOver": "Handover", + "paiementStores": "Associations", + "paiementAdmin": "Administrator", + "paiementSuccededTransaction": "Successful payment", + "paiementNewCGU": "New Terms of Service", + "paiementDecline": "Decline", + "paiementAccept": "Accept", + "paiementAmount": "Amount", + "paiementValidUntil": "Valid until", + "paiementClose": "Close", + "paiementPleaseEnterValidAmount": "Please enter a valid amount", + "paiementPleaseAuthenticate": "Please authenticate", + "paiementAthenticationRequired": "Authentication required to pay", + "paiementNoThanks": "No thanks", + "paiementAuthentificationFailed": "Authentication failed", + "paiementPleaseAddDevice": "Please add this device to pay", + "paiementPayment": "Payment", + "paiementBalanceAfterTransaction": "Balance after payment: ", + "paiementCancel": "Cancel", + "paiementLimitedTo": "Limited to", + "paiementScanCode": "Scan a code", + "paiementNext": "Next", + "paiementCancelTransaction": "Cancel transaction", + "paiementTransactionCancelled": "Transaction cancelled", + "paiementTransactionCancelledDescription": "Are you sure you want to cancel the transaction of", + "paiementTransactionCancelledError": "Error while cancelling the transaction", + "paiementNoMembership": "No membership", + "paiementNoMembershipDescription": "This product is not available to non-members. Confirm the payment?", + "paiementQRCodeAlreadyUsed": "QR Code already used", + "paiementCameraPermissionRequired": "Camera permission required", + "paiementCameraPerssionRequiredDescription": "To scan a QR Code, you must allow camera access.", + "paiementSettings": "Settings", + "paiementReceived": "Received", + "paiementSpent": "Spent", + "paiementNoTrasactionForThisMonth": "No transactions for this month", + "paiementNoTransactinon": "No transaction", + "paiementSellerRigths": "Seller rights", + "paiementCanBank": "Can collect payments", + "paiementCanSeeHistory": "Can view history", + "paiementCanCancelTransaction": "Can cancel transactions", + "paiementCanManageSellers": "Can manage sellers", + "paiementAddedSeller": "Seller added", + "paiementAddingSellerError": "Error while adding seller", + "paiementBank": "Collect", + "paiementSeeHistory": "View history", + "paiementCancelTransactions": "Cancel transactions", + "paiementManageSellers": "Manage sellers", + "paiementStructureAdmin": "Structure administrator", + "paiementRightsOf": "Rights of", + "paiementRightsUpdated": "Rights updated", + "paiementRightsUpdateError": "Error while updating rights", + "paiementDeleteSellerDescription": "Are you sure you want to delete this seller?", + "paiementDeletedSeller": "Seller deleted", + "paiementDeletingSellerError": "Error while deleting seller", + "paiementDeleteSeller": "Delete seller", + "paiementAdd": "Add", + "paiementAddSeller": "Add seller", + "paiementSellerError": "You are not a seller of this association", + "paiementSellersOf": "Sellers of", + "paiementModify": "Edit", + "paiementAStore": "an association", + "paiementStoreName": "Association name", + "paiementSuccessfullyAddedStore": "Association successfully added", + "paiementSuccessfullyModifiedStore": "Association successfully updated", + "paiementAddingStoreError": "Error while adding the association", + "paiementModifyingStoreError": "Error while updating the association", + "paiementRefund": "Refund", + "paiementDoneTransaction": "Transaction completed", + "paiementRefundAction": "Refund", + "paiementTotalDuringPeriod": "Total during the period", + "paiementMean": "Average: ", + "paiementTransaction": "Transaction", + "paiementTransferStructure": "Structure transfer", + "paiementYouAreTransferingStructureTo": "You are about to transfer the structure to ", + "paiementTransferStructureDescription": "The new manager will have access to all structure management features. You will receive an email to confirm this transfer. The link will only be active for 20 minutes. This action is irreversible. Are you sure you want to continue?", + "paiementTransferStructureError": "Error while transferring structure", + "paiementTransferStructureSuccess": "Structure transfer requested successfully", + "paiementNextAccountable": "Next responsible" } diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 3a7d44ff46..18bba44dcc 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -1,1352 +1,1352 @@ { - "@@locale": "fr", - "adminAccountTypes": "Types de compte", - "adminAdd": "Ajouter", - "adminAddGroup": "Ajouter un groupe", - "adminAddMember": "Ajouter un membre", - "adminAddedGroup": "Groupe créé", - "adminAddedLoaner": "Préteur ajouté", - "adminAddedMember": "Membre ajouté", - "adminAddingError": "Erreur lors de l'ajout", - "adminAddingMember": "Ajout d'un membre", - "adminAddLoaningGroup": "Ajouter un groupe de prêt", - "adminAddSchool": "Ajouter une école", - "adminAddStructure": "Ajouter une structure", - "adminAddedSchool": "École créée", - "adminAddedStructure": "Structure ajoutée", - "adminEditedStructure": "Structure modifiée", - "adminAdministration": "Administration", - "adminAssociationMembership": "Adhésion", - "adminAssociationMembershipName": "Nom de l'adhésion", - "adminAssociationsMemberships": "Adhésions", - "adminClearFilters": "Effacer les filtres", - "adminCreateAssociationMembership": "Créer une adhésion", - "adminCreatedAssociationMembership": "Adhésion créée", - "adminCreationError": "Erreur lors de la création", - "adminDateError": "La date de début doit être avant la date de fin", - "adminDelete": "Supprimer", - "adminDeleteAssociationMembership": "Supprimer l'adhésion ?", - "adminDeletedAssociationMembership": "Adhésion supprimée", - "adminDeleteGroup": "Supprimer le groupe ?", - "adminDeletedGroup": "Groupe supprimé", - "adminDeleteSchool": "Supprimer l'école ?", - "adminDeletedSchool": "École supprimée", - "adminDeleting": "Suppression", - "adminDeletingError": "Erreur lors de la suppression", - "adminDescription": "Description", - "adminEclSchool": "Centrale Lyon", - "adminEdit": "Modifier", - "adminEditStructure": "Modifier la structure", - "adminEditMembership": "Modifier l'adhésion", - "adminEmptyDate": "Date vide", - "adminEmptyFieldError": "Le nom ne peut pas être vide", - "adminEmailRegex": "Email Regex", - "adminEmptyUser": "Utilisateur vide", - "adminEndDate": "Date de fin", - "adminEndDateMaximal": "Date de fin maximale", - "adminEndDateMinimal": "Date de fin minimale", - "adminError": "Erreur", - "adminFilters": "Filtres", - "adminGroup": "Groupe", - "adminGroups": "Groupes", - "adminLoaningGroup": "Groupe de prêt", - "adminLooking": "Recherche", - "adminManager": "Administrateur de la structure", - "adminMaximum": "Maximum", - "adminMembers": "Membres", - "adminMembershipAddingError": "Erreur lors de l'ajout (surement dû à une superposition de dates)", - "adminMemberships": "Adhésions", - "adminMembershipUpdatingError": "Erreur lors de la modification (surement dû à une superposition de dates)", - "adminMinimum": "Minimum", - "adminModifyModuleVisibility": "Visibilité des modules", - "adminMyEclPay": "MyECLPay", - "adminName": "Nom", - "adminNoManager": "Aucun manager n'est sélectionné", - "adminNoMember": "Aucun membre", - "adminNoMoreLoaner": "Aucun prêteur n'est disponible", - "adminNoSchool": "Sans école", - "adminRemoveGroupMember": "Supprimer le membre du groupe ?", - "adminResearch": "Recherche", - "adminSchools": "Écoles", - "adminStructures": "Structures", - "adminStartDate": "Date de début", - "adminStartDateMaximal": "Date de début maximale", - "adminStartDateMinimal": "Date de début minimale", - "adminUpdatedAssociationMembership": "Adhésion modifiée", - "adminUpdatedGroup": "Groupe modifié", - "adminUpdatedMembership": "Adhésion modifiée", - "adminUpdatingError": "Erreur lors de la modification", - "adminUser": "Utilisateur", - "adminValidateFilters": "Valider les filtres", - "adminVisibilities": "Visibilités", - "advertAdd": "Ajouter", - "advertAddedAdvert": "Annonce publiée", - "advertAddedAnnouncer": "Annonceur ajouté", - "advertAddingError": "Erreur lors de l'ajout", - "advertAdmin": "Admin", - "advertAdvert": "Annonce", - "advertChoosingAnnouncer": "Veuillez choisir un annonceur", - "advertChoosingPoster": "Veuillez choisir une image", - "advertContent": "Contenu", - "advertDeleteAdvert": "Supprimer l'annonce ?", - "advertDeleteAnnouncer": "Supprimer l'annonceur ?", - "advertDeleting": "Suppression", - "advertEdit": "Modifier", - "advertEditedAdvert": "Annonce modifiée", - "advertEditingError": "Erreur lors de la modification", - "advertGroupAdvert": "Groupe", - "advertIncorrectOrMissingFields": "Champs incorrects ou manquants", - "advertInvalidNumber": "Veuillez entrer un nombre", - "advertManagement": "Gestion", - "advertModifyAnnouncingGroup": "Modifier un groupe d'annonce", - "advertNoMoreAnnouncer": "Aucun annonceur n'est disponible", - "advertNoValue": "Veuillez entrer une valeur", - "advertPositiveNumber": "Veuillez entrer un nombre positif", - "advertRemovedAnnouncer": "Annonceur supprimé", - "advertRemovingError": "Erreur lors de la suppression", - "advertTags": "Tags", - "advertTitle": "Titre", - "advertMonthJan": "Janv", - "advertMonthFeb": "Févr.", - "advertMonthMar": "Mars", - "advertMonthApr": "Avr.", - "advertMonthMay": "Mai", - "advertMonthJun": "Juin", - "advertMonthJul": "Juill.", - "advertMonthAug": "Août", - "advertMonthSep": "Sept.", - "advertMonthOct": "Oct.", - "advertMonthNov": "Nov.", - "advertMonthDec": "Déc.", - "amapAccounts": "Comptes", - "amapAdd": "Ajouter", - "amapAddDelivery": "Ajouter une livraison", - "amapAddedCommand": "Commande ajoutée", - "amapAddedOrder": "Commande ajoutée", - "amapAddedProduct": "Produit ajouté", - "amapAddedUser": "Utilisateur ajouté", - "amapAddProduct": "Ajouter un produit", - "amapAddUser": "Ajouter un utilisateur", - "amapAddingACommand": "Ajouter une commande", - "amapAddingCommand": "Ajouter la commande", - "amapAddingError": "Erreur lors de l'ajout", - "amapAddingProduct": "Ajouter un produit", - "amapAddOrder": "Ajouter une commande", - "amapAdmin": "Admin", - "amapAlreadyExistCommand": "Il existe déjà une commande à cette date", - "amapAmap": "Amap", - "amapAmount": "Solde", - "amapArchive": "Archiver", - "amapArchiveDelivery": "Archiver", - "amapArchivingDelivery": "Archivage de la livraison", - "amapCategory": "Catégorie", - "amapCloseDelivery": "Verrouiller", - "amapCommandDate": "Date de la commande", - "amapCommandProducts": "Produits de la commande", - "amapConfirm": "Confirmer", - "amapContact": "Contacts associatifs ", - "amapCreateCategory": "Créer une catégorie", - "amapDelete": "Supprimer", - "amapDeleteDelivery": "Supprimer la livraison ?", - "amapDeleteDeliveryDescription": "Voulez-vous vraiment supprimer cette livraison ?", - "amapDeletedDelivery": "Livraison supprimée", - "amapDeletedOrder": "Commande supprimée", - "amapDeletedProduct": "Produit supprimé", - "amapDeleteProduct": "Supprimer le produit ?", - "amapDeleteProductDescription": "Voulez-vous vraiment supprimer ce produit ?", - "amapDeleting": "Suppression", - "amapDeletingDelivery": "Supprimer la livraison ?", - "amapDeletingError": "Erreur lors de la suppression", - "amapDeletingOrder": "Supprimer la commande ?", - "amapDeletingProduct": "Supprimer le produit ?", - "amapDeliver": "Livraison teminée ?", - "amapDeliveries": "Livraisons", - "amapDeliveringDelivery": "Toutes les commandes sont livrées ?", - "amapDelivery": "Livraison", - "amapDeliveryArchived": "Livraison archivée", - "amapDeliveryDate": "Date de livraison", - "amapDeliveryDelivered": "Livraison effectuée", - "amapDeliveryHistory": "Historique des livraisons", - "amapDeliveryList": "Liste des livraisons", - "amapDeliveryLocked": "Livraison verrouillée", - "amapDeliveryOn": "Livraison le", - "amapDeliveryOpened": "Livraison ouverte", - "amapDeliveryNotArchived": "Livraison non archivée", - "amapDeliveryNotLocked": "Livraison non verrouillée", - "amapDeliveryNotDelivered": "Livraison non effectuée", - "amapDeliveryNotOpened": "Livraison non ouverte", - "amapEditDelivery": "Modifier la livraison", - "amapEditedCommand": "Commande modifiée", - "amapEditingError": "Erreur lors de la modification", - "amapEditProduct": "Modifier le produit", - "amapEndingDelivery": "Fin de la livraison", - "amapError": "Erreur", - "amapErrorLink": "Erreur lors de l'ouverture du lien", - "amapErrorLoadingUser": "Erreur lors du chargement des utilisateurs", - "amapEvening": "Soir", - "amapExpectingNumber": "Veuillez entrer un nombre", - "amapFillField": "Veuillez remplir ce champ", - "amapHandlingAccount": "Gérer les comptes", - "amapLoading": "Chargement...", - "amapLoadingError": "Erreur lors du chargement", - "amapLock": "Verrouiller", - "amapLocked": "Verrouillée", - "amapLockedDelivery": "Livraison verrouillée", - "amapLockedOrder": "Commande verrouillée", - "amapLooking": "Rechercher", - "amapLockingDelivery": "Verrouiller la livraison ?", - "amapMidDay": "Midi", - "amapMyOrders": "Mes commandes", - "amapName": "Nom", - "amapNextStep": "Étape suivante", - "amapNoProduct": "Pas de produit", - "amapNoCurrentOrder": "Pas de commande en cours", - "amapNoMoney": "Pas assez d'argent", - "amapNoOpennedDelivery": "Pas de livraison ouverte", - "amapNoOrder": "Pas de commande", - "amapNoSelectedDelivery": "Pas de livraison sélectionnée", - "amapNotEnoughMoney": "Pas assez d'argent", - "amapNotPlannedDelivery": "Pas de livraison planifiée", - "amapOneOrder": "commande", - "amapOpenDelivery": "Ouvrir", - "amapOpened": "Ouverte", - "amapOpenningDelivery": "Ouvrir la livraison ?", - "amapOrder": "Commander", - "amapOrders": "Commandes", - "amapPickChooseCategory": "Veuillez entrer une valeur ou choisir une catégorie existante", - "amapPickDeliveryMoment": "Choisissez un moment de livraison", - "amapPresentation": "Présentation", - "amapPresentation1": "L'AMAP (association pour le maintien d'une agriculture paysanne) est un service proposé par l'association Planet&Co de l'ECL. Vous pouvez ainsi recevoir des produits (paniers de fruits et légumes, jus, confitures...) directement sur le campus !\n\nLes commandes doivent être passées avant le vendredi 21h et sont livrées sur le campus le mardi de 13h à 13h45 (ou de 18h15 à 18h30 si vous ne pouvez pas passer le midi) dans le hall du M16.\n\nVous ne pouvez commander que si votre solde le permet. Vous pouvez recharger votre solde via la collecte Lydia ou bien avec un chèque que vous pouvez nous transmettre lors des permanences.\n\nLien vers la collecte Lydia pour le rechargement : ", - "amapPresentation2": "\n\nN'hésitez pas à nous contacter en cas de problème !", - "amapPrice": "Prix", - "amapProduct": "produit", - "amapProducts": "Produits", - "amapProductInDelivery": "Produit dans une livraison non terminée", - "amapQuantity": "Quantité", - "amapRequiredDate": "La date est requise", - "amapSeeMore": "Voir plus", - "amapThe": "Le", - "amapUnlock": "Dévérouiller", - "amapUnlockedDelivery": "Livraison dévérouillée", - "amapUnlockingDelivery": "Dévérouiller la livraison ?", - "amapUpdate": "Modifier", - "amapUpdatedAmount": "Solde modifié", - "amapUpdatedOrder": "Commande modifiée", - "amapUpdatedProduct": "Produit modifié", - "amapUpdatingError": "Echec de la modification", - "amapUsersNotFound": "Aucun utilisateur trouvé", - "amapWaiting": "En attente", - "bookingAdd": "Ajouter", - "bookingAddBookingPage": "Demande", - "bookingAddRoom": "Ajouter une salle", - "bookingAddBooking": "Ajouter une réservation", - "bookingAddedBooking": "Demande ajoutée", - "bookingAddedRoom": "Salle ajoutée", - "bookingAddedManager": "Gestionnaire ajouté", - "bookingAddingError": "Erreur lors de l'ajout", - "bookingAddManager": "Ajouter un gestionnaire", - "bookingAdminPage": "Administrateur", - "bookingAllDay": "Toute la journée", - "bookingBookedFor": "Réservé pour", - "bookingBooking": "Réservation", - "bookingBookingCreated": "Réservation créée", - "bookingBookingDemand": "Demande de réservation", - "bookingBookingNote": "Note de la réservation", - "bookingBookingPage": "Réservation", - "bookingBookingReason": "Motif de la réservation", - "bookingBy": "par", - "bookingConfirm": "Confirmer", - "bookingConfirmation": "Confirmation", - "bookingConfirmBooking": "Confirmer la réservation ?", - "bookingConfirmed": "Validée", - "bookingDates": "Dates", - "bookingDecline": "Refuser", - "bookingDeclineBooking": "Refuser la réservation ?", - "bookingDeclined": "Refusée", - "bookingDelete": "Supprimer", - "bookingDeleting": "Suppression", - "bookingDeleteBooking": "Suppression", - "bookingDeleteBookingConfirmation": "Êtes-vous sûr de vouloir supprimer cette réservation ?", - "bookingDeletedBooking": "Réservation supprimée", - "bookingDeletedRoom": "Salle supprimée", - "bookingDeletedManager": "Gestionnaire supprimé", - "bookingDeleteRoomConfirmation": "Êtes-vous sûr de vouloir supprimer cette salle ?\n\nLa salle ne doit avoir aucune réservation en cours ou à venir pour être supprimée", - "bookingDeleteManagerConfirmation": "Êtes-vous sûr de vouloir supprimer ce gestionnaire ?\n\nLe gestionnaire ne doit être associé à aucune salle pour pouvoir être supprimé", - "bookingDeletingBooking": "Supprimer la réservation ?", - "bookingDeletingError": "Erreur lors de la suppression", - "bookingDeletingRoom": "Supprimer la salle ?", - "bookingEdit": "Modifier", - "bookingEditBooking": "Modifier une réservation", - "bookingEditionError": "Erreur lors de la modification", - "bookingEditedBooking": "Réservation modifiée", - "bookingEditedRoom": "Salle modifiée", - "bookingEditedManager": "Gestionnaire modifié", - "bookingEditManager": "Modifier ou supprimer un gestionnaire", - "bookingEditRoom": "Modifier ou supprimer une salle", - "bookingEndDate": "Date de fin", - "bookingEndHour": "Heure de fin", - "bookingEntity": "Pour qui ?", - "bookingError": "Erreur", - "bookingEventEvery": "Tous les", - "bookingHistoryPage": "Historique", - "bookingIncorrectOrMissingFields": "Champs incorrects ou manquants", - "bookingInterval": "Intervalle", - "bookingInvalidIntervalError": "Intervalle invalide", - "bookingInvalidDates": "Dates invalides", - "bookingInvalidRoom": "Salle invalide", - "bookingKeysRequested": "Clés demandées", - "bookingManagement": "Gestion", - "bookingManager": "Gestionnaire", - "bookingManagerName": "Nom du gestionnaire", - "bookingMultipleDay": "Plusieurs jours", - "bookingMyBookings": "Mes réservations", - "bookingNecessaryKey": "Clé nécessaire", - "bookingNext": "Suivant", - "bookingNo": "Non", - "bookingNoCurrentBooking": "Pas de réservation en cours", - "bookingNoDateError": "Veuillez choisir une date", - "bookingNoAppointmentInReccurence": "Aucun créneau existe avec ces paramètres de récurrence", - "bookingNoDaySelected": "Aucun jour sélectionné", - "bookingNoDescriptionError": "Veuillez entrer une description", - "bookingNoKeys": "Aucune clé", - "bookingNoNoteError": "Veuillez entrer une note", - "bookingNoPhoneRegistered": "Numéro non renseigné", - "bookingNoReasonError": "Veuillez entrer un motif", - "bookingNoRoomFoundError": "Aucune salle enregistrée", - "bookingNoRoomFound": "Aucune salle trouvée", - "bookingNote": "Note", - "bookingOther": "Autre", - "bookingPending": "En attente", - "bookingPrevious": "Précédent", - "bookingReason": "Motif", - "bookingRecurrence": "Récurrence", - "bookingRecurrenceDays": "Jours de récurrence", - "bookingRecurrenceEndDate": "Date de fin de récurrence", - "bookingRecurrent": "Récurrent", - "bookingRegisteredRooms": "Salles enregistrées", - "bookingRoom": "Salle", - "bookingRoomName": "Nom de la salle", - "bookingStartDate": "Date de début", - "bookingStartHour": "Heure de début", - "bookingWeeks": "Semaines", - "bookingYes": "Oui", - "bookingWeekDayMon": "Lundi", - "bookingWeekDayTue": "Mardi", - "bookingWeekDayWed": "Mercredi", - "bookingWeekDayThu": "Jeudi", - "bookingWeekDayFri": "Vendredi", - "bookingWeekDaySat": "Samedi", - "bookingWeekDaySun": "Dimanche", - "cinemaAdd": "Ajouter", - "cinemaAddedSession": "Séance ajoutée", - "cinemaAddingError": "Erreur lors de l'ajout", - "cinemaAddSession": "Ajouter une séance", - "cinemaCinema": "Cinéma", - "cinemaDeleteSession": "Supprimer la séance ?", - "cinemaDeleting": "Suppression", - "cinemaDuration": "Durée", - "cinemaEdit": "Modifier", - "cinemaEditedSession": "Séance modifiée", - "cinemaEditingError": "Erreur lors de la modification", - "cinemaEditSession": "Modifier la séance", - "cinemaEmptyUrl": "Veuillez entrer une URL", - "cinemaImportFromTMDB": "Importer depuis TMDB", - "cinemaIncomingSession": "A l'affiche", - "cinemaIncorrectOrMissingFields": "Champs incorrects ou manquants", - "cinemaInvalidUrl": "URL invalide", - "cinemaGenre": "Genre", - "cinemaName": "Nom", - "cinemaNoDateError": "Veuillez entrer une date", - "cinemaNoDuration": "Veuillez entrer une durée", - "cinemaNoOverview": "Aucun synopsis", - "cinemaNoPoster": "Aucune affiche", - "cinemaNoSession": "Aucune séance", - "cinemaOverview": "Synopsis", - "cinemaPosterUrl": "URL de l'affiche", - "cinemaSessionDate": "Jour de la séance", - "cinemaStartHour": "Heure de début", - "cinemaTagline": "Slogan", - "cinemaThe": "Le", - "drawerAdmin": "Administration", - "drawerAndroidAppLink": "https://play.google.com/store/apps/details?id=fr.myecl.titan", - "drawerCopied": "Copié !", - "drawerDownloadAppOnMobileDevice": "Ce site est la version Web de l'application MyECL. Nous vous invitons à télécharger l'application. N'utilisez ce site qu'en cas de problème avec l'application.\n", - "drawerIosAppLink": "https://apps.apple.com/fr/app/myecl/id6444443430", - "drawerLoginOut": "Voulez-vous vous déconnecter ?", - "drawerLogOut": "Déconnexion", - "drawerOr": " ou ", - "drawerSettings": "Paramètres", - "eventAdd": "Ajouter", - "eventAddEvent": "Ajouter un événement", - "eventAddedEvent": "Événement ajouté", - "eventAddingError": "Erreur lors de l'ajout", - "eventAllDay": "Toute la journée", - "eventConfirm": "Confirmer", - "eventConfirmEvent": "Confirmer l'événement ?", - "eventConfirmation": "Confirmation", - "eventConfirmed": "Confirmé", - "eventDates": "Dates", - "eventDecline": "Refuser", - "eventDeclineEvent": "Refuser l'événement ?", - "eventDeclined": "Refusé", - "eventDelete": "Supprimer", - "eventDeletedEvent": "Événement supprimé", - "eventDeleting": "Suppression", - "eventDeletingError": "Erreur lors de la suppression", - "eventDeletingEvent": "Supprimer l'événement ?", - "eventDescription": "Description", - "eventEdit": "Modifier", - "eventEditEvent": "Modifier un événement", - "eventEditedEvent": "Événement modifié", - "eventEditingError": "Erreur lors de la modification", - "eventEndDate": "Date de fin", - "eventEndHour": "Heure de fin", - "eventError": "Erreur", - "eventEventList": "Liste des événements", - "eventEventType": "Type d'événement", - "eventEvery": "Tous les", - "eventHistory": "Historique", - "eventIncorrectOrMissingFields": "Certains champs sont incorrects ou manquants", - "eventInterval": "Intervalle", - "eventInvalidDates": "La date de fin doit être après la date de début", - "eventInvalidIntervalError": "Veuillez entrer un intervalle valide", - "eventLocation": "Lieu", - "eventMyEvents": "Mes événements", - "eventName": "Nom", - "eventNext": "Suivant", - "eventNo": "Non", - "eventNoCurrentEvent": "Aucun événement en cours", - "eventNoDateError": "Veuillez entrer une date", - "eventNoDaySelected": "Aucun jour sélectionné", - "eventNoDescriptionError": "Veuillez entrer une description", - "eventNoEvent": "Aucun événement", - "eventNoNameError": "Veuillez entrer un nom", - "eventNoOrganizerError": "Veuillez entrer un organisateur", - "eventNoPlaceError": "Veuillez entrer un lieu", - "eventNoPhoneRegistered": "Numéro non renseigné", - "eventNoRuleError": "Veuillez entrer une règle de récurrence", - "eventOrganizer": "Organisateur", - "eventOther": "Autre", - "eventPending": "En attente", - "eventPrevious": "Précédent", - "eventRecurrence": "Récurrence", - "eventRecurrenceDays": "Jours de récurrence", - "eventRecurrenceEndDate": "Date de fin de la récurrence", - "eventRecurrenceRule": "Règle de récurrence", - "eventRoom": "Salle", - "eventStartDate": "Date de début", - "eventStartHour": "Heure de début", - "eventTitle": "Événements", - "eventYes": "Oui", - "eventEventEvery": "Toutes les", - "eventWeeks": "semaines", - "eventDayMon": "Lundi", - "eventDayTue": "Mardi", - "eventDayWed": "Mercredi", - "eventDayThu": "Jeudi", - "eventDayFri": "Vendredi", - "eventDaySat": "Samedi", - "eventDaySun": "Dimanche", - "globalConfirm": "Confirmer", - "globalCancel": "Annuler", - "globalIrreversibleAction": "Cette action est irréversible", - "globalOptionnal": "{text} (Optionnel)", - "@globalOptionnal": { - "description": "Texte avec complément optionnel", - "placeholders": { - "text": { - "type": "String" - } - } - }, - "homeCalendar": "Calendrier", - "homeEventOf": "Évènements du", - "homeIncomingEvents": "Évènements à venir", - "homeLastInfos": "Dernières annonces", - "homeNoEvents": "Aucun évènement", - "homeTranslateDayShortMon": "Lun", - "homeTranslateDayShortTue": "Mar", - "homeTranslateDayShortWed": "Mer", - "homeTranslateDayShortThu": "Jeu", - "homeTranslateDayShortFri": "Ven", - "homeTranslateDayShortSat": "Sam", - "homeTranslateDayShortSun": "Dim", - "loanAdd": "Ajouter", - "loanAddLoan": "Ajouter un prêt", - "loanAddObject": "Ajouter un objet", - "loanAddedLoan": "Prêt ajouté", - "loanAddedObject": "Objet ajouté", - "loanAddedRoom": "Salle ajoutée", - "loanAddingError": "Erreur lors de l'ajout", - "loanAdmin": "Administrateur", - "loanAvailable": "Disponible", - "loanAvailableMultiple": "Disponibles", - "loanBorrowed": "Emprunté", - "loanBorrowedMultiple": "Empruntés", - "loanAnd": "et", - "loanAssociation": "Association", - "loanAvailableItems": "Objets disponibles", - "loanBeginDate": "Date du début du prêt", - "loanBorrower": "Emprunteur", - "loanCaution": "Caution", - "loanCancel": "Annuler", - "loanConfirm": "Confirmer", - "loanConfirmation": "Confirmation", - "loanDates": "Dates", - "loanDays": "Jours", - "loanDelay": "Délai de la prolongation", - "loanDelete": "Supprimer", - "loanDeletingLoan": "Supprimer le prêt ?", - "loanDeletedItem": "Objet supprimé", - "loanDeletedLoan": "Prêt supprimé", - "loanDeleting": "Suppression", - "loanDeletingError": "Erreur lors de la suppression", - "loanDeletingItem": "Supprimer l'objet ?", - "loanDuration": "Durée", - "loanEdit": "Modifier", - "loanEditItem": "Modifier l'objet", - "loanEditLoan": "Modifier le prêt", - "loanEditedRoom": "Salle modifiée", - "loanEndDate": "Date de fin du prêt", - "loanEnded": "Terminé", - "loanEnterDate": "Veuillez entrer une date", - "loanExtendedLoan": "Prêt prolongé", - "loanExtendingError": "Erreur lors de la prolongation", - "loanHistory": "Historique", - "loanIncorrectOrMissingFields": "Des champs sont manquants ou incorrects", - "loanInvalidNumber": "Veuillez entrer un nombre", - "loanInvalidDates": "Les dates ne sont pas valides", - "loanItem": "Objet", - "loanItems": "Objets", - "loanItemHandling": "Gestion des objets", - "loanItemSelected": "objet sélectionné", - "loanItemsSelected": "objets sélectionnés", - "loanLendingDuration": "Durée possible du prêt", - "loanLoan": "Prêt", - "loanLoanHandling": "Gestion des prêts", - "loanLooking": "Rechercher", - "loanName": "Nom", - "loanNext": "Suivant", - "loanNo": "Non", - "loanNoAssociationsFounded": "Aucune association trouvée", - "loanNoAvailableItems": "Aucun objet disponible", - "loanNoBorrower": "Aucun emprunteur", - "loanNoItems": "Aucun objet", - "loanNoItemSelected": "Aucun objet sélectionné", - "loanNoLoan": "Aucun prêt", - "loanNoReturnedDate": "Pas de date de retour", - "loanQuantity": "Quantité", - "loanNone": "Aucun", - "loanNote": "Note", - "loanNoValue": "Veuillez entrer une valeur", - "loanOnGoing": "En cours", - "loanOnGoingLoan": "Prêt en cours", - "loanOthers": "autres", - "loanPaidCaution": "Caution payée", - "loanPositiveNumber": "Veuillez entrer un nombre positif", - "loanPrevious": "Précédent", - "loanReturned": "Rendu", - "loanReturnedLoan": "Prêt rendu", - "loanReturningError": "Erreur lors du retour", - "loanReturningLoan": "Retour", - "loanReturnLoan": "Rendre le prêt ?", - "loanReturnLoanDescription": "Voulez-vous rendre ce prêt ?", - "loanToReturn": "A rendre", - "loanUnavailable": "Indisponible", - "loanUpdate": "Modifier", - "loanUpdatedItem": "Objet modifié", - "loanUpdatedLoan": "Prêt modifié", - "loanUpdatingError": "Erreur lors de la modification", - "loanYes": "Oui", - "loginAccountActivated": "Compte activé", - "loginAccountNotActivated": "Compte non activé", - "loginActivationCode": "Code d'activation", - "loginBirthday": "Date de naissance", - "loginCanBeEmpty": "Ce champ peut être vide", - "loginConfirmPassword": "Confirmer le mot de passe", - "loginCreate": "Créer", - "loginCreateAccount": "Créer un compte", - "loginCreateAccountTitle": "Créer un\ncompte", - "loginEmail": "Email", - "loginEmailEmpty": "Veuillez entrer une adresse mail", - "loginEmailInvalid": "Veuillez entrer une adresse mail de centrale.\nSi vous n'en possédez pas, veuillez contacter Éclair", - "loginEmptyFieldError": "Ce champ ne peut pas être vide", - "loginEndActivation": "Finaliser l'activation", - "loginEndResetPassword": "Finaliser la \nréinitialisation", - "loginErrorResetPassword": "Erreur lors de la réinitialisation", - "loginExpectingDate": "Une date est attendue", - "loginFillAllFields": "Veuillez remplir tous les champs", - "loginFirstname": "Prénom", - "loginFloor": "Étage", - "loginForgetPassword": "Mot de passe\noublié", - "loginForgotPassword": "Mot de passe oublié ?", - "loginInvalidToken": "Code d'activation invalide", - "loginLoginFailed": "Échec de la connexion", - "loginMailSendingError": "Erreur lors de la création du compte", - "loginMustBeIntError": "Ce champ doit être un entier", - "loginName": "Nom", - "loginNewPassword": "Nouveau mot de passe", - "loginPassword": "Mot de passe", - "loginPasswordLengthError": "Le mot de passe doit faire au moins 6 caractères", - "loginPasswordUppercaseError": "Le mot de passe doit contenir au moins une majuscule", - "loginPasswordLowercaseError": "Le mot de passe doit contenir au moins une minucule", - "loginPasswordNumberError": "Le mot de passe doit contenir au moins un chiffre", - "loginPasswordSpecialCaracterError": "Le mot de passe doit contenir au moins un caractère spécial", - "loginPasswordMustMatch": "Les mots de passe doivent correspondre", - "loginPasswordStrengthVeryWeak": "Très faible", - "loginPasswordStrengthWeak": "Faible", - "loginPasswordStrengthMedium": "Moyen", - "loginPasswordStrengthStrong": "Fort", - "loginPasswordStrengthVeryStrong": "Très fort", - "loginPhone": "Téléphone", - "loginPromo": "Promo entrante (ex : 2023)", - "loginSendedMail": "Mail de confirmation envoyé", - "loginSendedResetMail": "Mail de réinitialisation envoyé", - "loginSignIn": "Se connecter", - "loginRegister": "S'inscrire", - "loginRecievedMail": "J'ai reçu le mail", - "loginRecover": "Réinitialiser", - "loginResetedPassword": "Mot de passe réinitialisé", - "loginResetPasswordTitle": "Réinitialiser\nle mot de \npasse", - "loginNickname": "Surnom", - "loginWelcomeBack": "Bienvenue", - "loginAppName": "MyECL", - "othersCheckInternetConnection": "Veuillez vérifier votre connexion internet", - "othersRetry": "Réessayer", - "othersTooOldVersion": "Votre version de l'application est trop ancienne.\n\nVeuillez mettre à jour l'application.", - "othersUnableToConnectToServer": "Impossible de se connecter au serveur", - "othersVersion": "Version", - "othersNoModule": "Aucun module disponible, veuillez réessayer ultérieurement 😢😢", - "othersAdmin": "Admin", - "othersError": "Une erreur est survenue", - "othersNoValue": "Veuillez entrer une valeur", - "othersInvalidNumber": "Veuillez entrer un nombre", - "othersNoDateError": "Veuillez entrer une date", - "othersImageSizeTooBig": "La taille de l'image ne doit pas dépasser 4 Mio", - "othersImageError": "Erreur lors de l'ajout de l'image", - "phAddNewJournal": "Ajouter un nouveau journal", - "phNameField": "Nom : ", - "phDateField": "Date : ", - "phDelete": "Voulez-vous vraiment supprimer ce journal ?", - "phIrreversibleAction": "Cette action est irréversible", - "phToHeavyFile": "Fichier trop volumineux", - "phAddPdfFile": "Ajouter un fichier PDF", - "phEditPdfFile": "Modifier le fichier PDF", - "phPhName": "Nom du PH", - "phDate": "Date", - "phAdded": "Ajouté", - "phEdited": "Modifié", - "phAddingFileError": "Erreur d'ajout", - "phMissingInformatonsOrPdf": "Informations manquantes ou fichier PDF manquant", - "phAdd": "Ajouter", - "phEdit": "Modifier", - "phSeePreviousJournal": "Voir les anciens journaux", - "phNoJournalInDatabase": "Pas encore de PH dans la base de donnée", - "phSuccesDowloading": "Téléchargé avec succès", - "phonebookAdd": "Ajouter", - "phonebookAddAssociation": "Ajouter une association", - "phonebookAddAssociationGroupement": "Ajouter un groupement d'association", - "phonebookAddedAssociation": "Association ajoutée", - "phonebookAddedMember": "Membre ajouté", - "phonebookAddingError": "Erreur lors de l'ajout", - "phonebookAddMember": "Ajouter un membre", - "phonebookAddRole": "Ajouter un rôle", - "phonebookAdmin": "Administration", - "phonebookAll": "Toutes", - "phonebookApparentName": "Nom public du rôle :", - "phonebookAssociation": "Association", - "phonebookAssociationDetail": "Détail de l'association :", - "phonebookAssociationGroupement": "Groupement d'association", - "phonebookAssociationKind": "Type d'association :", - "phonebookAssociationName": "Nom de l'association", - "phonebookAssociations": "Associations", - "phonebookCancel": "Annuler", - "phonebookChangeTermYear": "Passer au mandat {year}", - "@phonebookChangeTermYear": { - "description": "Permet de changer le mandat d'une association", - "placeholders": { - "year": { - "type": "int" - } - } - }, - "phonebookChangeTermConfirm": "Êtes-vous sûr de vouloir changer tout le mandat ?\nCette action est irréversible !", - "phonebookClose": "Fermer", - "phonebookConfirm": "Confirmer", - "phonebookCopied": "Copié dans le presse-papier", - "phonebookDeactivateAssociation": "Désactiver l'association", - "phonebookDeactivatedAssociation": "Association désactivée", - "phonebookDeactivatedAssociationWarning": "Attention, cette association est désactivée, vous ne pouvez pas la modifier", - "phonebookDeactivateSelectedAssociation": "Désactiver l'association {association} ?", - "@phonebookDeactivateSelectedAssociation": { - "description": "Permet de désactiver une association", - "placeholders": { - "association": { - "type": "String" - } - } - }, - "phonebookDeactivatingError": "Erreur lors de la désactivation", - "phonebookDetail": "Détail :", - "phonebookDelete": "Supprimer", - "phonebookDeleteAssociation": "Supprimer l'association", - "phonebookDeleteSelectedAssociation": "Supprimer l'association {association} ?", - "@phonebookDeleteSelectedAssociation": { - "description": "Permet de supprimer une association", - "placeholders": { - "association": { - "type": "String" - } - } - }, - "phonebookDeleteAssociationDescription": "Ceci va supprimer l'historique de l'association", - "phonebookDeletedAssociation": "Association supprimée", - "phonebookDeletedMember": "Membre supprimé", - "phonebookDeleteRole": "Supprimer le rôle", - "phonebookDeleteUserRole": "Supprimer le rôle de l'utilisateur {name} ?", - "@phonebookDeleteUserRole": { - "description": "Permet de supprimer le rôle d'un utilisateur dans une association", - "placeholders": { - "name": { - "type": "String" - } - } - }, - "phonebookDeleting": "Suppression", - "phonebookDeletingError": "Erreur lors de la suppression", - "phonebookDescription": "Description", - "phonebookEdit": "Modifier", - "phonebookEditAssociationGroupement": "Modifier le groupement d'association", - "phonebookEditAssociationGroups": "Gérer les groupes", - "phonebookEditAssociationInfo": "Modifier", - "phonebookEditAssociationMembers": "Gérer les membres", - "phonebookEditRole": "Modifier le rôle", - "phonebookEmail": "Email :", - "phonebookEmailCopied": "Email copié dans le presse-papier", - "phonebookEmptyApparentName": "Veuillez entrer un nom de role", - "phonebookEmptyFieldError": "Un champ n'est pas rempli", - "phonebookEmptyKindError": "Veuillez choisir un type d'association", - "phonebookEmptyMember": "Aucun membre sélectionné", - "phonebookErrorAssociationLoading": "Erreur lors du chargement de l'association", - "phonebookErrorAssociationNameEmpty": "Veuillez entrer un nom d'association", - "phonebookErrorAssociationPicture": "Erreur lors de la modification de la photo d'association", - "phonebookErrorKindsLoading": "Erreur lors du chargement des types d'association", - "phonebookErrorLoadAssociationList": "Erreur lors du chargement de la liste des associations", - "phonebookErrorLoadAssociationMember": "Erreur lors du chargement des membres de l'association", - "phonebookErrorLoadAssociationPicture": "Erreur lors du chargement de la photo d'association", - "phonebookErrorLoadProfilePicture": "Erreur", - "phonebookErrorRoleTagsLoading": "Erreur lors du chargement des tags de rôle", - "phonebookExistingMembership": "Ce membre est déjà dans le mandat actuel", - "phonebookFilter": "Filtrer", - "phonebookFilterDescription": "Sélectionnez un ou plusieurs groupements pour filtrer les associations.", - "phonebookFirstname": "Prénom :", - "phonebookGroupementDeleted": "Groupement d'association supprimé", - "phonebookGroupementDeleteError": "Erreur lors de la suppression du groupement d'association", - "phonebookGroupementName": "Nom du groupement", - "phonebookGroups": "Gérer les groupes de {association}", - "@phonebookGroups": { - "description": "Permet de gérer les groupes d'une association", - "placeholders": { - "association": { - "type": "String" - } - } - }, - "phonebookTerm": "Mandat {year}", - "@phonebookTerm": { - "description": "Année de mandat d'une association", - "placeholders": { - "year": { - "type": "int" - } - } - }, - "phonebookTermChangingError": "Erreur lors du changement de mandat", - "phonebookMember": "Membre", - "phonebookMemberReordered": "Membre réordonné", - "phonebookMembers": "Gérer les membres de {association}", - "@phonebookMembers": { - "description": "Permet de gérer les membres d'une association", - "placeholders": { - "association": { - "type": "String" - } - } - }, - "phonebookMembershipAssociationError": "Veuillez choisir une association", - "phonebookMembershipRole": "Rôle :", - "phonebookMembershipRoleError": "Veuillez choisir un rôle", - "phonebookModifyMembership": "Modifier le rôle de {name}", - "@phonebookModifyMembership": { - "description": "Permet de modifier le rôle d'un membre dans une association", - "placeholders": { - "name": { - "type": "String" - } - } - }, - "phonebookName": "Nom :", - "phonebookNameCopied": "Nom et prénom copié dans le presse-papier", - "phonebookNamePure": "Nom", - "phonebookNewTerm": "Nouveau mandat", - "phonebookNewTermConfirmed": "Mandat changé", - "phonebookNickname": "Surnom :", - "phonebookNicknameCopied": "Surnom copié dans le presse-papier", - "phonebookNoAssociationFound": "Aucune association trouvée", - "phonebookNoMember": "Aucun membre", - "phonebookNoMemberRole": "Aucun role trouvé", - "phonebookNoRoleTags": "Aucun tag de rôle trouvé", - "phonebookPhone": "Téléphone :", - "phonebookPhonebook": "Annuaire", - "phonebookPhonebookSearch": "Rechercher", - "phonebookPhonebookSearchAssociation": "Association", - "phonebookPhonebookSearchField": "Rechercher :", - "phonebookPhonebookSearchName": "Nom/Prénom/Surnom", - "phonebookPhonebookSearchRole": "Poste", - "phonebookPresidentRoleTag": "Prez'", - "phonebookPromoNotGiven": "Promo non renseignée", - "phonebookPromotion": "Promotion {year}", - "@phonebookPromotion": { - "description": "Année de promotion d'un membre", - "placeholders": { - "year": { - "type": "int" - } - } - }, - "phonebookReorderingError": "Erreur lors du réordonnement", - "phonebookResearch": "Rechercher", - "phonebookRolePure": "Rôle", - "phonebookSearchUser": "Rechercher un utilisateur", - "phonebookTooHeavyAssociationPicture": "L'image est trop lourde (max 4Mo)", - "phonebookUpdateGroups": "Mettre à jour les groupes", - "phonebookUpdatedAssociation": "Association modifiée", - "phonebookUpdatedAssociationPicture": "La photo d'association a été changée", - "phonebookUpdatedGroups": "Groupes mis à jour", - "phonebookUpdatedMember": "Membre modifié", - "phonebookUpdatingError": "Erreur lors de la modification", - "phonebookValidation": "Valider", - "purchasesPurchases": "Achats", - "purchasesResearch": "Rechercher", - "purchasesNoPurchasesFound": "Aucun achat trouvé", - "purchasesNoTickets": "Aucun ticket", - "purchasesTicketsError": "Erreur lors du chargement des tickets", - "purchasesPurchasesError": "Erreur lors du chargement des achats", - "purchasesNoPurchases": "Aucun achat", - "purchasesTimes": "fois", - "purchasesAlreadyUsed": "Déjà utilisé", - "purchasesNotPaid": "Non validé", - "purchasesPleaseSelectProduct": "Veuillez sélectionner un produit", - "purchasesProducts": "Produits", - "purchasesCancel": "Annuler", - "purchasesValidate": "Valider", - "purchasesLeftScan": "Scans restants", - "purchasesTag": "Tag", - "purchasesHistory": "Historique", - "purchasesPleaseSelectSeller": "Veuillez sélectionner un vendeur", - "purchasesNoTagGiven": "Attention, aucun tag n'a été entré", - "purchasesTickets": "Tickets", - "purchasesNoScannableProducts": "Aucun produit scannable", - "purchasesLoading": "En attente de scan", - "purchasesScan": "Scanner", - "raffleRaffle": "Tombola", - "rafflePrize": "Lot", - "rafflePrizes": "Lots", - "raffleActualRaffles": "Tombola en cours", - "rafflePastRaffles": "Tombola passés", - "raffleYourTickets": "Tous vos tickets", - "raffleCreateMenu": "Menu de Création", - "raffleNextRaffles": "Prochaines tombolas", - "raffleNoTicket": "Vous n'avez pas de ticket", - "raffleSeeRaffleDetail": "Voir lots/tickets", - "raffleActualPrize": "Lots actuels", - "raffleMajorPrize": "Lot Majeurs", - "raffleTakeTickets": "Prendre vos tickets", - "raffleNoTicketBuyable": "Vous ne pouvez pas achetez de billets pour l'instant", - "raffleNoCurrentPrize": "Il n'y a aucun lots actuellement", - "raffleModifTombola": "Vous pouvez modifiez vos tombolas ou en créer de nouvelles, toute décision doit ensuite être prise par les admins", - "raffleCreateYourRaffle": "Votre menu de création de tombolas", - "rafflePossiblePrice": "Prix possible", - "raffleInformation": "Information et Statistiques", - "raffleAccounts": "Comptes", - "raffleAdd": "Ajouter", - "raffleUpdatedAmount": "Montant mis à jour", - "raffleUpdatingError": "Erreur lors de la mise à jour", - "raffleDeletedPrize": "Lot supprimé", - "raffleDeletingError": "Erreur lors de la suppression", - "raffleQuantity": "Quantité", - "raffleClose": "Fermer", - "raffleOpen": "Ouvrir", - "raffleAddTypeTicketSimple": "Ajouter", - "raffleAddingError": "Erreur lors de l'ajout", - "raffleEditTypeTicketSimple": "Modifier", - "raffleFillField": "Le champ ne peut pas être vide", - "raffleWaiting": "Chargement", - "raffleEditingError": "Erreur lors de la modification", - "raffleAddedTicket": "Ticket ajouté", - "raffleEditedTicket": "Ticket modifié", - "raffleAlreadyExistTicket": "Le ticket existe déjà", - "raffleNumberExpected": "Un entier est attendu", - "raffleDeletedTicket": "Ticket supprimé", - "raffleAddPrize": "Ajouter", - "raffleEditPrize": "Modifier", - "raffleOpenRaffle": "Ouvrir la tombola", - "raffleCloseRaffle": "Fermer la tombola", - "raffleOpenRaffleDescription": "Vous allez ouvrir la tombola, les utilisateurs pourront acheter des tickets. Vous ne pourrez plus modifier la tombola. Êtes-vous sûr de vouloir continuer ?", - "raffleCloseRaffleDescription": "Vous allez fermer la tombola, les utilisateurs ne pourront plus acheter de tickets. Êtes-vous sûr de vouloir continuer ?", - "raffleNoCurrentRaffle": "Il n'y a aucune tombola en cours", - "raffleBoughtTicket": "Ticket acheté", - "raffleDrawingError": "Erreur lors du tirage", - "raffleInvalidPrice": "Le prix doit être supérieur à 0", - "raffleMustBePositive": "Le nombre doit être strictement positif", - "raffleDraw": "Tirer", - "raffleDrawn": "Tiré", - "raffleError": "Erreur", - "raffleGathered": "Récolté", - "raffleTickets": "Tickets", - "raffleTicket": "ticket", - "raffleWinner": "Gagnant", - "raffleNoPrize": "Aucun lot", - "raffleDeletePrize": "Supprimer le lot", - "raffleDeletePrizeDescription": "Vous allez supprimer le lot, êtes-vous sûr de vouloir continuer ?", - "raffleDrawing": "Tirage", - "raffleDrawingDescription": "Tirer le gagnant du lot ?", - "raffleDeleteTicket": "Supprimer le ticket", - "raffleDeleteTicketDescription": "Vous allez supprimer le ticket, êtes-vous sûr de vouloir continuer ?", - "raffleWinningTickets": "Tickets gagnants", - "raffleNoWinningTicketYet": "Les tickets gagnants seront affichés ici", - "raffleName": "Nom", - "raffleDescription": "Description", - "raffleBuyThisTicket": "Acheter ce ticket", - "raffleLockedRaffle": "Tombola verrouillée", - "raffleUnavailableRaffle": "Tombola indisponible", - "raffleNotEnoughMoney": "Vous n'avez pas assez d'argent", - "raffleWinnable": "gagnable", - "raffleNoDescription": "Aucune description", - "raffleAmount": "Solde", - "raffleLoading": "Chargement", - "raffleTicketNumber": "Nombre de ticket", - "rafflePrice": "Prix", - "raffleEditRaffle": "Modifier la tombola", - "raffleEdit": "Modifier", - "raffleAddPackTicket": "Ajouter un pack de ticket", - "recommendationRecommendation": "Bons plans", - "recommendationTitle": "Titre", - "recommendationLogo": "Logo", - "recommendationCode": "Code", - "recommendationSummary": "Court résumé", - "recommendationDescription": "Description", - "recommendationAdd": "Ajouter", - "recommendationEdit": "Modifier", - "recommendationDelete": "Supprimer", - "recommendationAddImage": "Veuillez ajouter une image", - "recommendationAddedRecommendation": "Bon plan ajouté", - "recommendationEditedRecommendation": "Bon plan modifié", - "recommendationDeleteRecommendationConfirmation": "Êtes-vous sûr de vouloir supprimer ce bon plan ?", - "recommendationDeleteRecommendation": "Suppresion", - "recommendationDeletingRecommendationError": "Erreur lors de la suppression", - "recommendationDeletedRecommendation": "Bon plan supprimé", - "recommendationIncorrectOrMissingFields": "Champs incorrects ou manquants", - "recommendationEditingError": "Échec de la modification", - "recommendationAddingError": "Échec de l'ajout", - "recommendationCopiedCode": "Code de réduction copié", - "seedLibraryAdd": "Ajouter", - "seedLibraryAddedPlant": "Plante ajoutée", - "seedLibraryAddedSpecies": "Espèce ajoutée", - "seedLibraryAddingError": "Erreur lors de l'ajout", - "seedLibraryAddPlant": "Déposer une plante", - "seedLibraryAddSpecies": "Ajouter une espèce", - "seedLibraryAll": "Toutes", - "seedLibraryAncestor": "Ancêtre", - "seedLibraryAround": "environ", - "seedLibraryAutumn": "Automne", - "seedLibraryBorrowedPlant": "Plante empruntée", - "seedLibraryBorrowingDate": "Date d'emprunt :", - "seedLibraryBorrowPlant": "Emprunter la plante", - "seedLibraryCard": "Carte", - "seedLibraryChoosingAncestor": "Veuillez choisir un ancêtre", - "seedLibraryChoosingSpecies": "Veuillez choisir une espèce", - "seedLibraryChoosingSpeciesOrAncestor": "Veuillez choisir une espèce ou un ancêtre", - "seedLibraryContact": "Contact :", - "seedLibraryDays": "jours", - "seedLibraryDeadMsg": "Voulez-vous déclarer la plante morte ?", - "seedLibraryDeadPlant": "Plante morte", - "seedLibraryDeathDate": "Date de mort", - "seedLibraryDeletedSpecies": "Espèce supprimée", - "seedLibraryDeleteSpecies": "Supprimer l'espèce ?", - "seedLibraryDeleting": "Suppression", - "seedLibraryDeletingError": "Erreur lors de la suppression", - "seedLibraryDepositNotAvailable": "Le dépôt de plantes n'est pas possible sans emprunter une plante au préalable", - "seedLibraryDescription": "Description", - "seedLibraryDifficulty": "Difficulté :", - "seedLibraryEdit": "Modifier", - "seedLibraryEditedPlant": "Plante modifiée", - "seedLibraryEditInformation": "Modifier les informations", - "seedLibraryEditingError": "Erreur lors de la modification", - "seedLibraryEditSpecies": "Modifier l'espèce", - "seedLibraryEmptyDifficultyError": "Veuillez choisir une difficulté", - "seedLibraryEmptyFieldError": "Veuillez remplir tous les champs", - "seedLibraryEmptyTypeError": "Veuillez choisir un type de plante", - "seedLibraryEndMonth": "Mois de fin :", - "seedLibraryFacebookUrl": "Lien Facebook", - "seedLibraryFilters": "Filtres", - "seedLibraryForum": "Oskour maman j'ai tué ma plante - Forum d'aide", - "seedLibraryForumUrl": "Lien Forum", - "seedLibraryHelpSheets": "Fiches sur les plantes", - "seedLibraryInformation": "Informations :", - "seedLibraryMaturationTime": "Temps de maturation", - "seedLibraryMonthJan": "Janvier", - "seedLibraryMonthFeb": "Février", - "seedLibraryMonthMar": "Mars", - "seedLibraryMonthApr": "Avril", - "seedLibraryMonthMay": "Mai", - "seedLibraryMonthJun": "Juin", - "seedLibraryMonthJul": "Juillet", - "seedLibraryMonthAug": "Août", - "seedLibraryMonthSep": "Septembre", - "seedLibraryMonthOct": "Octobre", - "seedLibraryMonthNov": "Novembre", - "seedLibraryMonthDec": "Décembre", - "seedLibraryMyPlants": "Mes plantes", - "seedLibraryName": "Nom", - "seedLibraryNbSeedsRecommended": "Nombre de graines recommandées", - "seedLibraryNbSeedsRecommendedError": "Veuillez entrer un nombre de graines recommandé supérieur à 0", - "seedLibraryNoDateError": "Veuillez entrer une date", - "seedLibraryNoFilteredPlants": "Aucune plante ne correspond à votre recherche. Essayez d'autres filtres.", - "seedLibraryNoMorePlant": "Aucune plante n'est disponible", - "seedLibraryNoPersonalPlants": "Vous n'avez pas encore de plantes dans votre grainothèque. Vous pouvez en ajouter en allant dans les stocks.", - "seedLibraryNoSpecies": "Aucune espèce trouvée", - "seedLibraryNoStockPlants": "Aucune plante disponible dans le stock", - "seedLibraryNotes": "Notes", - "seedLibraryOk": "OK", - "seedLibraryPlantationPeriod": "Période de plantation :", - "seedLibraryPlantationType": "Type de plantation :", - "seedLibraryPlantDetail": "Détail de la plante", - "seedLibraryPlantingDate": "Date de plantation", - "seedLibraryPlantingNow": "Je la plante maintenant", - "seedLibraryPrefix": "Préfixe", - "seedLibraryPrefixError": "Prefixe déjà utilisé", - "seedLibraryPrefixLengthError": "Le préfixe doit faire 3 caractères", - "seedLibraryPropagationMethod": "Méthode de propagation :", - "seedLibraryReference": "Référence :", - "seedLibraryRemovedPlant": "Plante supprimée", - "seedLibraryRemovingError": "Erreur lors de la suppression", - "seedLibraryResearch": "Recherche", - "seedLibrarySaveChanges": "Sauvegarder les modifications", - "seedLibrarySeason": "Saison :", - "seedLibrarySeed": "Graine", - "seedLibrarySeeds": "graines", - "seedLibrarySeedDeposit": "Dépôt de plantes", - "seedLibrarySeedLibrary": "Grainothèque", - "seedLibrarySeedQuantitySimple": "Quantité de graines", - "seedLibrarySeedQuantity": "Quantité de graines :", - "seedLibraryShowDeadPlants": "Afficher les plantes mortes", - "seedLibrarySpecies": "Espèce :", - "seedLibrarySpeciesHelp": "Aide sur l'espèce", - "seedLibrarySpeciesPlural": "Espèces", - "seedLibrarySpeciesSimple": "Espèce", - "seedLibrarySpeciesType": "Type d'espèce :", - "seedLibrarySpring": "Printemps", - "seedLibraryStartMonth": "Mois de début :", - "seedLibraryStock": "Stock disponible", - "seedLibrarySummer": "Été", - "seedLibraryStocks": "Stocks", - "seedLibraryTimeUntilMaturation": "Temps avant maturation :", - "seedLibraryType": "Type :", - "seedLibraryUnableToOpen": "Impossible d'ouvrir le lien", - "seedLibraryUpdate": "Modifier", - "seedLibraryUpdatedInformation": "Informations modifiées", - "seedLibraryUpdatedSpecies": "Espèce modifiée", - "seedLibraryUpdatedPlant": "Plante modifiée", - "seedLibraryUpdatingError": "Erreur lors de la modification", - "seedLibraryWinter": "Hiver", - "seedLibraryWriteReference": "Veuillez écrire la référence suivante : ", - "settingsAccount": "Compte", - "settingsAddProfilePicture": "Ajouter une photo", - "settingsAdmin": "Administrateur", - "settingsAskHelp": "Demander de l'aide", - "settingsAssociation": "Association", - "settingsBirthday": "Date de naissance", - "settingsBugs": "Bugs", - "settingsChangePassword": "Changer de mot de passe", - "settingsChangingPassword": "Voulez-vous vraiment changer votre mot de passe ?", - "settingsConfirmPassword": "Confirmer le mot de passe", - "settingsCopied": "Copié !", - "settingsDarkMode": "Mode sombre", - "settingsDarkModeOff": "Désactivé", - "settingsDeleteLogs": "Supprimer les logs ?", - "settingsDeleteNotificationLogs": "Supprimer les logs des notifications ?", - "settingsDetelePersonalData": "Supprimer mes données personnelles", - "settingsDetelePersonalDataDesc": "Cette action notifie l'administrateur que vous souhaitez supprimer vos données personnelles.", - "settingsDeleting": "Suppresion", - "settingsEdit": "Modifier", - "settingsEditAccount": "Modifier le compte", - "settingsEditPassword": "Modifier le mot de passe", - "settingsEmail": "Email", - "settingsEmptyField": "Ce champ ne peut pas être vide", - "settingsErrorProfilePicture": "Erreur lors de la modification de la photo de profil", - "settingsErrorSendingDemand": "Erreur lors de l'envoi de la demande", - "settingsEventsIcal": "Lien Ical des événements", - "settingsExpectingDate": "Date de naissance attendue", - "settingsFirstname": "Prénom", - "settingsFloor": "Étage", - "settingsHelp": "Aide", - "settingsIcalCopied": "Lien Ical copié !", - "settingsLanguage": "Langue", - "settingsLanguageFr": "Français", - "settingsLogs": "Logs", - "settingsModules": "Modules", - "settingsMyIcs": "Mon lien Ical", - "settingsName": "Nom", - "settingsNewPassword": "Nouveau mot de passe", - "settingsNickname": "Surnom", - "settingsNotifications": "Notifications", - "settingsOldPassword": "Ancien mot de passe", - "settingsPasswordChanged": "Mot de passe changé", - "settingsPasswordsNotMatch": "Les mots de passe ne correspondent pas", - "settingsPersonalData": "Données personnelles", - "settingsPersonalisation": "Personnalisation", - "settingsPhone": "Téléphone", - "settingsProfilePicture": "Photo de profil", - "settingsPromo": "Promotion", - "settingsRepportBug": "Signaler un bug", - "settingsSave": "Enregistrer", - "settingsSecurity": "Sécurité", - "settingsSendedDemand": "Demande envoyée", - "settingsSettings": "Paramètres", - "settingsTooHeavyProfilePicture": "L'image est trop lourde (max 4Mo)", - "settingsUpdatedProfile": "Profil modifié", - "settingsUpdatedProfilePicture": "Photo de profil modifiée", - "settingsUpdateNotification": "Mettre à jour les notifications", - "settingsUpdatingError": "Erreur lors de la modification du profil", - "settingsVersion": "Version", - "settingsPasswordStrength": "Force du mot de passe", - "settingsPasswordStrengthVeryWeak": "Très faible", - "settingsPasswordStrengthWeak": "Faible", - "settingsPasswordStrengthMedium": "Moyen", - "settingsPasswordStrengthStrong": "Fort", - "settingsPasswordStrengthVeryStrong": "Très fort", - "voteAdd": "Ajouter", - "voteAddMember": "Ajouter un membre", - "voteAddedPretendance": "Liste ajoutée", - "voteAddedSection": "Section ajoutée", - "voteAddingError": "Erreur lors de l'ajout", - "voteAddPretendance": "Ajouter une liste", - "voteAddSection": "Ajouter une section", - "voteAll": "Tous", - "voteAlreadyAddedMember": "Membre déjà ajouté", - "voteAlreadyVoted": "Vote enregistré", - "voteChooseList": "Choisir une liste", - "voteClear": "Réinitialiser", - "voteClearVotes": "Réinitialiser les votes", - "voteClosedVote": "Votes clos", - "voteCloseVote": "Fermer les votes", - "voteConfirmVote": "Confirmer le vote", - "voteCountVote": "Dépouiller les votes", - "voteDeletedAll": "Tout supprimé", - "voteDeletedPipo": "Listes pipos supprimées", - "voteDeletedSection": "Section supprimée", - "voteDeleteAll": "Supprimer tout", - "voteDeleteAllDescription": "Voulez-vous vraiment supprimer tout ?", - "voteDeletePipo": "Supprimer les listes pipos", - "voteDeletePipoDescription": "Voulez-vous vraiment supprimer les listes pipos ?", - "voteDeletePretendance": "Supprimer la liste", - "voteDeletePretendanceDesc": "Voulez-vous vraiment supprimer cette liste ?", - "voteDeleteSection": "Supprimer la section", - "voteDeleteSectionDescription": "Voulez-vous vraiment supprimer cette section ?", - "voteDeletingError": "Erreur lors de la suppression", - "voteDescription": "Description", - "voteEdit": "Modifier", - "voteEditedPretendance": "Liste modifiée", - "voteEditedSection": "Section modifiée", - "voteEditingError": "Erreur lors de la modification", - "voteErrorClosingVotes": "Erreur lors de la fermeture des votes", - "voteErrorCountingVotes": "Erreur lors du dépouillement des votes", - "voteErrorResetingVotes": "Erreur lors de la réinitialisation des votes", - "voteErrorOpeningVotes": "Erreur lors de l'ouverture des votes", - "voteIncorrectOrMissingFields": "Champs incorrects ou manquants", - "voteMembers": "Membres", - "voteName": "Nom", - "voteNoPretendanceList": "Aucune liste de prétendance", - "voteNoSection": "Aucune section", - "voteCanNotVote": "Vous ne pouvez pas voter", - "voteNoSectionList": "Aucune section", - "voteNotOpenedVote": "Vote non ouvert", - "voteOnGoingCount": "Dépouillement en cours", - "voteOpenVote": "Ouvrir les votes", - "votePipo": "Pipo", - "votePretendance": "Listes", - "votePretendanceDeleted": "Prétendance supprimée", - "votePretendanceNotDeleted": "Erreur lors de la suppression", - "voteProgram": "Programme", - "votePublish": "Publier", - "votePublishVoteDescription": "Voulez-vous vraiment publier les votes ?", - "voteResetedVotes": "Votes réinitialisés", - "voteResetVote": "Réinitialiser les votes", - "voteResetVoteDescription": "Que voulez-vous faire ?", - "voteRole": "Rôle", - "voteSectionDescription": "Description de la section", - "voteSection": "Section", - "voteSectionName": "Nom de la section", - "voteSeeMore": "Voir plus", - "voteSelected": "Sélectionné", - "voteShowVotes": "Voir les votes", - "voteVote": "Vote", - "voteVoteError": "Erreur lors de l'enregistrement du vote", - "voteVoteFor": "Voter pour ", - "voteVoteNotStarted": "Vote non ouvert", - "voteVoters": "Groupes votants", - "voteVoteSuccess": "Vote enregistré", - "voteVotes": "Voix", - "voteVotesClosed": "Votes clos", - "voteVotesCounted": "Votes dépouillés", - "voteVotesOpened": "Votes ouverts", - "voteWarning": "Attention", - "voteWarningMessage": "La sélection ne sera pas sauvegardée.\nVoulez-vous continuer ?", - "moduleAdvert": "Annonce", - "moduleAmap": "AMAP", - "moduleBooking": "Réservation", - "moduleCalendar": "Calendrier", - "moduleCentralisation": "Centralisation", - "moduleCinema": "Cinéma", - "moduleEvent": "Événement", - "moduleFlappyBird": "Flappy Bird", - "moduleLoan": "Prêt", - "modulePhonebook": "Annuaire", - "modulePurchases": "Achats", - "moduleRaffle": "Tombola", - "moduleRecommendation": "Bons plans", - "moduleSeedLibrary": "Grainothèque", - "moduleVote": "Vote", - "modulePh": "PH", - "moduleSettings": "Paramètres", - "moduleFeed": "Feed", - "moduleStyleGuide": "StyleGuide", - "moduleAdmin": "Adminitration", - "moduleOthers": "Autres", - "modulePayment": "Paiement", - "paiementTopUp": "Recharge", - "paiementStoreManagement": "Gestion des associations", - "paiementDeleteStore": "Supprimer l'association", - "paiementDeleteStoreDescription": "Voulez-vous vraiment supprimer cette association ?", - "paiementDeleteStoreError": "Impossible de supprimer l'association", - "paiementStoreDeleted": "Association supprimée", - "paiementAddThisDevice": "Ajouter cet appareil", - "paiementThisDevice": "(cet appareil)", - "paiementCancelled": "Annulé", - "paiementThe": "Le", - "paiementOf": "de", - "paiementRefundedThe": "Remboursé le", - "paiementAt": "à", - "paiementPleaseAcceptTOS": "Veuillez accepter les Conditions Générales d'Utilisation.", - "paiementAskDeviceActivation": "Demande d'activation de l'appareil", - "paiementDeviceActivationReceived": "La demande d'activation est prise en compte, veuilliez consulter votre boite mail pour finaliser la démarche", - "paiementRevokeDevice": "Révoquer l'appareil ?", - "paiementRevokeDeviceDescription": "Vous ne pourrez plus utiliser cet appareil pour les paiements", - "paiementDeviceRevoked": "Appareil révoqué", - "paiementDeviceRevokingError": "Erreur lors de la révocation de l'appareil", - "paiementPleaseAcceptPopup": "Veuillez autoriser les popups", - "paiementProceedSuccessfully": "Paiement effectué avec succès", - "paiementCancelledTransaction": "Paiement annulé", - "paiementPleaseEnterMinAmount": "Veuillez entrer un montant supérieur à 1", - "paiementMaxAmount": "Le montant maximum de votre portefeuille est de", - "paiementPayWithHA": "Payer avec HelloAsso", - "paiementBalanceAfterTopUp": "Solde après recharge :", - "paiementPersonalBalance": "Solde personnel", - "paiementDevices": "Appareils", - "paiementPay": "Payer", - "paiementDeviceNotRegistered": "Appareil non enregistré", - "paiementDeviceNotRegisteredDescription": "Votre appareil n'est pas encore enregistré. \nPour l'enregistrer, veuillez vous rendre sur la page des appareils.", - "paiementAccessPage": "Accéder à la page", - "paiementDeviceNotActivated": "Appareil non activé", - "paiementDeviceNotActivatedDescription": "Votre appareil n'est pas encore activé. \nPour l'activer, veuillez vous rendre sur la page des appareils.", - "paiementReactivateRevokedDeviceDescription": "Votre appareil a été révoqué. \nPour le réactiver, veuillez vous rendre sur la page des appareils.", - "paiementDeviceRecoveryError": "Erreur lors de la récupération de l'appareil", - "paiementStats": "Stats", - "paimentTopUpAction": "Recharger", - "paiementGetBalanceError": "Erreur lors de la récupération du solde : ", - "paiementLastTransactions": "Dernières transactions", - "paiementGetTransactionsError": "Erreur lors de la récupération des transactions : ", - "paiementStoreBalance": "Solde associatif", - "paiementScan": "Scanner", - "paiementManagement": "Gestion", - "paiementHistory": "Historique", - "paiementHandOver": "Passation", - "paiementStores": "Associations", - "paiementAdmin": "Administrateur", - "paiementSuccededTransaction": "Paiement réussi", - "paiementNewCGU": "Nouvelles Conditions Générales d'Utilisation", - "paiementDecline": "Refuser", - "paiementAccept": "Accepter", - "paiementAmount": "Montant", - "paiementValidUntil": "Valide jusqu'à", - "paiementClose": "Fermer", - "paiementPleaseEnterValidAmount": "Veuillez entrer un montant valide", - "paiementPleaseAuthenticate": "Veuillez vous authentifier", - "paiementAthenticationRequired": "Authentification requise pour payer", - "paiementNoThanks": "Non merci", - "paiementAuthentificationFailed": "Échec de l'authentification", - "paiementPleaseAddDevice": "Veuillez ajouter cet appareil pour payer", - "paiementPayment": "Paiement", - "paiementBalanceAfterTransaction": "Solde après paiement : ", - "paiementCancel": "Annuler", - "paiementLimitedTo": "Limité à", - "paiementScanCode": "Scanner un code", - "paiementNext": "Suivant", - "paiementCancelTransaction": "Annuler la transaction", - "paiementTransactionCancelled": "Transaction annulée", - "paiementTransactionCancelledDescription": "Voulez-vous vraiment annuler la transaction de", - "paiementTransactionCancelledError": "Erreur lors de l'annulation de la transaction", - "paiementNoMembership": "Aucune adhésion", - "paiementNoMembershipDescription": "Ce produit n'est pas disponnible pour les non-adhérents. Confirmer l'encaissement ?", - "paiementQRCodeAlreadyUsed": "QR Code déjà utilisé", - "paiementCameraPermissionRequired": "Permission d'accès à la caméra requise", - "paiementCameraPerssionRequiredDescription": "Pour scanner un QR Code, vous devez autoriser l'accès à la caméra.", - "paiementSettings": "Paramètres", - "paiementReceived": "Reçu", - "paiementSpent": "Déboursé", - "paiementNoTrasactionForThisMonth": "Aucune transaction pour ce mois", - "paiementNoTransactinon": "Aucune transaction", - "paiementSellerRigths": "Droits du vendeur", - "paiementCanBank": "Peut encaisser", - "paiementCanSeeHistory": "Peut voir l'historique", - "paiementCanCancelTransaction": "Peut annuler des transactions", - "paiementCanManageSellers": "Peut gérer les vendeurs", - "paiementAddedSeller": "Vendeur ajouté", - "paiementAddingSellerError": "Erreur lors de l'ajout du vendeur", - "paiementBank": "Encaisser", - "paiementSeeHistory": "Voir l'historique", - "paiementCancelTransactions": "Annuler les transactions", - "paiementManageSellers": "Gérer les vendeurs", - "paiementStructureAdmin": "Administrateur de la structure", - "paiementRightsOf": "Droits de", - "paiementRightsUpdated": "Droits mis à jour", - "paiementRightsUpdateError": "Erreur lors de la mise à jour des droits", - "paiementDeleteSellerDescription": "Voulez-vous vraiment supprimer ce vendeur ?", - "paiementDeletedSeller": "Vendeur supprimé", - "paiementDeletingSellerError": "Erreur lors de la suppression du vendeur", - "paiementDeleteSeller": "Supprimer le vendeur", - "paiementAdd": "Ajouter", - "paiementAddSeller": "Ajouter un vendeur", - "paiementSellerError": "Vous n'êtes pas vendeur de cette association", - "paiementSellersOf": "Les vendeurs de", - "paiementModify": "Modifier", - "paiementAStore": "une association", - "paiementStoreName": "Nom de l'association", - "paiementSuccessfullyAddedStore": "Association ajoutée avec succès", - "paiementSuccessfullyModifiedStore": "Association modifiée avec succès", - "paiementAddingStoreError": "Erreur lors de l'ajout de l'association", - "paiementModifyingStoreError": "Erreur lors de la modification de l'association", - "paiementRefund": "Remboursement", - "paiementDoneTransaction": "Transaction effectuée", - "paiementRefundAction": "Rembourser", - "paiementTotalDuringPeriod": "Total sur la période", - "paiementMean": "Moyenne : ", - "paiementTransaction": "ransaction", - "paiementTransferStructure": "Transfert de structure", - "paiementYouAreTransferingStructureTo": "Vous êtes sur le point de transférer la structure à ", - "paiementTransferStructureDescription": "Le nouveau responsable aura accès à toutes les fonctionnalités de gestion de la structure. Vous allez recevoir un email pour valider ce transfert. Le lien ne sera actif que pendant 20 minutes. Cette action est irréversible. Êtes-vous sûr de vouloir continuer ?", - "paiementTransferStructureError": "Erreur lors du transfert de la structure", - "paiementTransferStructureSuccess": "Transfert de structure demandé avec succès", - "paiementNextAccountable": "Prochain responsable" + "@@locale": "fr", + "adminAccountTypes": "Types de compte", + "adminAdd": "Ajouter", + "adminAddGroup": "Ajouter un groupe", + "adminAddMember": "Ajouter un membre", + "adminAddedGroup": "Groupe créé", + "adminAddedLoaner": "Préteur ajouté", + "adminAddedMember": "Membre ajouté", + "adminAddingError": "Erreur lors de l'ajout", + "adminAddingMember": "Ajout d'un membre", + "adminAddLoaningGroup": "Ajouter un groupe de prêt", + "adminAddSchool": "Ajouter une école", + "adminAddStructure": "Ajouter une structure", + "adminAddedSchool": "École créée", + "adminAddedStructure": "Structure ajoutée", + "adminEditedStructure": "Structure modifiée", + "adminAdministration": "Administration", + "adminAssociationMembership": "Adhésion", + "adminAssociationMembershipName": "Nom de l'adhésion", + "adminAssociationsMemberships": "Adhésions", + "adminClearFilters": "Effacer les filtres", + "adminCreateAssociationMembership": "Créer une adhésion", + "adminCreatedAssociationMembership": "Adhésion créée", + "adminCreationError": "Erreur lors de la création", + "adminDateError": "La date de début doit être avant la date de fin", + "adminDelete": "Supprimer", + "adminDeleteAssociationMembership": "Supprimer l'adhésion ?", + "adminDeletedAssociationMembership": "Adhésion supprimée", + "adminDeleteGroup": "Supprimer le groupe ?", + "adminDeletedGroup": "Groupe supprimé", + "adminDeleteSchool": "Supprimer l'école ?", + "adminDeletedSchool": "École supprimée", + "adminDeleting": "Suppression", + "adminDeletingError": "Erreur lors de la suppression", + "adminDescription": "Description", + "adminEclSchool": "Centrale Lyon", + "adminEdit": "Modifier", + "adminEditStructure": "Modifier la structure", + "adminEditMembership": "Modifier l'adhésion", + "adminEmptyDate": "Date vide", + "adminEmptyFieldError": "Le nom ne peut pas être vide", + "adminEmailRegex": "Email Regex", + "adminEmptyUser": "Utilisateur vide", + "adminEndDate": "Date de fin", + "adminEndDateMaximal": "Date de fin maximale", + "adminEndDateMinimal": "Date de fin minimale", + "adminError": "Erreur", + "adminFilters": "Filtres", + "adminGroup": "Groupe", + "adminGroups": "Groupes", + "adminLoaningGroup": "Groupe de prêt", + "adminLooking": "Recherche", + "adminManager": "Administrateur de la structure", + "adminMaximum": "Maximum", + "adminMembers": "Membres", + "adminMembershipAddingError": "Erreur lors de l'ajout (surement dû à une superposition de dates)", + "adminMemberships": "Adhésions", + "adminMembershipUpdatingError": "Erreur lors de la modification (surement dû à une superposition de dates)", + "adminMinimum": "Minimum", + "adminModifyModuleVisibility": "Visibilité des modules", + "adminMyEclPay": "MyECLPay", + "adminName": "Nom", + "adminNoManager": "Aucun manager n'est sélectionné", + "adminNoMember": "Aucun membre", + "adminNoMoreLoaner": "Aucun prêteur n'est disponible", + "adminNoSchool": "Sans école", + "adminRemoveGroupMember": "Supprimer le membre du groupe ?", + "adminResearch": "Recherche", + "adminSchools": "Écoles", + "adminStructures": "Structures", + "adminStartDate": "Date de début", + "adminStartDateMaximal": "Date de début maximale", + "adminStartDateMinimal": "Date de début minimale", + "adminUpdatedAssociationMembership": "Adhésion modifiée", + "adminUpdatedGroup": "Groupe modifié", + "adminUpdatedMembership": "Adhésion modifiée", + "adminUpdatingError": "Erreur lors de la modification", + "adminUser": "Utilisateur", + "adminValidateFilters": "Valider les filtres", + "adminVisibilities": "Visibilités", + "advertAdd": "Ajouter", + "advertAddedAdvert": "Annonce publiée", + "advertAddedAnnouncer": "Annonceur ajouté", + "advertAddingError": "Erreur lors de l'ajout", + "advertAdmin": "Admin", + "advertAdvert": "Annonce", + "advertChoosingAnnouncer": "Veuillez choisir un annonceur", + "advertChoosingPoster": "Veuillez choisir une image", + "advertContent": "Contenu", + "advertDeleteAdvert": "Supprimer l'annonce ?", + "advertDeleteAnnouncer": "Supprimer l'annonceur ?", + "advertDeleting": "Suppression", + "advertEdit": "Modifier", + "advertEditedAdvert": "Annonce modifiée", + "advertEditingError": "Erreur lors de la modification", + "advertGroupAdvert": "Groupe", + "advertIncorrectOrMissingFields": "Champs incorrects ou manquants", + "advertInvalidNumber": "Veuillez entrer un nombre", + "advertManagement": "Gestion", + "advertModifyAnnouncingGroup": "Modifier un groupe d'annonce", + "advertNoMoreAnnouncer": "Aucun annonceur n'est disponible", + "advertNoValue": "Veuillez entrer une valeur", + "advertPositiveNumber": "Veuillez entrer un nombre positif", + "advertRemovedAnnouncer": "Annonceur supprimé", + "advertRemovingError": "Erreur lors de la suppression", + "advertTags": "Tags", + "advertTitle": "Titre", + "advertMonthJan": "Janv", + "advertMonthFeb": "Févr.", + "advertMonthMar": "Mars", + "advertMonthApr": "Avr.", + "advertMonthMay": "Mai", + "advertMonthJun": "Juin", + "advertMonthJul": "Juill.", + "advertMonthAug": "Août", + "advertMonthSep": "Sept.", + "advertMonthOct": "Oct.", + "advertMonthNov": "Nov.", + "advertMonthDec": "Déc.", + "amapAccounts": "Comptes", + "amapAdd": "Ajouter", + "amapAddDelivery": "Ajouter une livraison", + "amapAddedCommand": "Commande ajoutée", + "amapAddedOrder": "Commande ajoutée", + "amapAddedProduct": "Produit ajouté", + "amapAddedUser": "Utilisateur ajouté", + "amapAddProduct": "Ajouter un produit", + "amapAddUser": "Ajouter un utilisateur", + "amapAddingACommand": "Ajouter une commande", + "amapAddingCommand": "Ajouter la commande", + "amapAddingError": "Erreur lors de l'ajout", + "amapAddingProduct": "Ajouter un produit", + "amapAddOrder": "Ajouter une commande", + "amapAdmin": "Admin", + "amapAlreadyExistCommand": "Il existe déjà une commande à cette date", + "amapAmap": "Amap", + "amapAmount": "Solde", + "amapArchive": "Archiver", + "amapArchiveDelivery": "Archiver", + "amapArchivingDelivery": "Archivage de la livraison", + "amapCategory": "Catégorie", + "amapCloseDelivery": "Verrouiller", + "amapCommandDate": "Date de la commande", + "amapCommandProducts": "Produits de la commande", + "amapConfirm": "Confirmer", + "amapContact": "Contacts associatifs ", + "amapCreateCategory": "Créer une catégorie", + "amapDelete": "Supprimer", + "amapDeleteDelivery": "Supprimer la livraison ?", + "amapDeleteDeliveryDescription": "Voulez-vous vraiment supprimer cette livraison ?", + "amapDeletedDelivery": "Livraison supprimée", + "amapDeletedOrder": "Commande supprimée", + "amapDeletedProduct": "Produit supprimé", + "amapDeleteProduct": "Supprimer le produit ?", + "amapDeleteProductDescription": "Voulez-vous vraiment supprimer ce produit ?", + "amapDeleting": "Suppression", + "amapDeletingDelivery": "Supprimer la livraison ?", + "amapDeletingError": "Erreur lors de la suppression", + "amapDeletingOrder": "Supprimer la commande ?", + "amapDeletingProduct": "Supprimer le produit ?", + "amapDeliver": "Livraison teminée ?", + "amapDeliveries": "Livraisons", + "amapDeliveringDelivery": "Toutes les commandes sont livrées ?", + "amapDelivery": "Livraison", + "amapDeliveryArchived": "Livraison archivée", + "amapDeliveryDate": "Date de livraison", + "amapDeliveryDelivered": "Livraison effectuée", + "amapDeliveryHistory": "Historique des livraisons", + "amapDeliveryList": "Liste des livraisons", + "amapDeliveryLocked": "Livraison verrouillée", + "amapDeliveryOn": "Livraison le", + "amapDeliveryOpened": "Livraison ouverte", + "amapDeliveryNotArchived": "Livraison non archivée", + "amapDeliveryNotLocked": "Livraison non verrouillée", + "amapDeliveryNotDelivered": "Livraison non effectuée", + "amapDeliveryNotOpened": "Livraison non ouverte", + "amapEditDelivery": "Modifier la livraison", + "amapEditedCommand": "Commande modifiée", + "amapEditingError": "Erreur lors de la modification", + "amapEditProduct": "Modifier le produit", + "amapEndingDelivery": "Fin de la livraison", + "amapError": "Erreur", + "amapErrorLink": "Erreur lors de l'ouverture du lien", + "amapErrorLoadingUser": "Erreur lors du chargement des utilisateurs", + "amapEvening": "Soir", + "amapExpectingNumber": "Veuillez entrer un nombre", + "amapFillField": "Veuillez remplir ce champ", + "amapHandlingAccount": "Gérer les comptes", + "amapLoading": "Chargement...", + "amapLoadingError": "Erreur lors du chargement", + "amapLock": "Verrouiller", + "amapLocked": "Verrouillée", + "amapLockedDelivery": "Livraison verrouillée", + "amapLockedOrder": "Commande verrouillée", + "amapLooking": "Rechercher", + "amapLockingDelivery": "Verrouiller la livraison ?", + "amapMidDay": "Midi", + "amapMyOrders": "Mes commandes", + "amapName": "Nom", + "amapNextStep": "Étape suivante", + "amapNoProduct": "Pas de produit", + "amapNoCurrentOrder": "Pas de commande en cours", + "amapNoMoney": "Pas assez d'argent", + "amapNoOpennedDelivery": "Pas de livraison ouverte", + "amapNoOrder": "Pas de commande", + "amapNoSelectedDelivery": "Pas de livraison sélectionnée", + "amapNotEnoughMoney": "Pas assez d'argent", + "amapNotPlannedDelivery": "Pas de livraison planifiée", + "amapOneOrder": "commande", + "amapOpenDelivery": "Ouvrir", + "amapOpened": "Ouverte", + "amapOpenningDelivery": "Ouvrir la livraison ?", + "amapOrder": "Commander", + "amapOrders": "Commandes", + "amapPickChooseCategory": "Veuillez entrer une valeur ou choisir une catégorie existante", + "amapPickDeliveryMoment": "Choisissez un moment de livraison", + "amapPresentation": "Présentation", + "amapPresentation1": "L'AMAP (association pour le maintien d'une agriculture paysanne) est un service proposé par l'association Planet&Co de l'ECL. Vous pouvez ainsi recevoir des produits (paniers de fruits et légumes, jus, confitures...) directement sur le campus !\n\nLes commandes doivent être passées avant le vendredi 21h et sont livrées sur le campus le mardi de 13h à 13h45 (ou de 18h15 à 18h30 si vous ne pouvez pas passer le midi) dans le hall du M16.\n\nVous ne pouvez commander que si votre solde le permet. Vous pouvez recharger votre solde via la collecte Lydia ou bien avec un chèque que vous pouvez nous transmettre lors des permanences.\n\nLien vers la collecte Lydia pour le rechargement : ", + "amapPresentation2": "\n\nN'hésitez pas à nous contacter en cas de problème !", + "amapPrice": "Prix", + "amapProduct": "produit", + "amapProducts": "Produits", + "amapProductInDelivery": "Produit dans une livraison non terminée", + "amapQuantity": "Quantité", + "amapRequiredDate": "La date est requise", + "amapSeeMore": "Voir plus", + "amapThe": "Le", + "amapUnlock": "Dévérouiller", + "amapUnlockedDelivery": "Livraison dévérouillée", + "amapUnlockingDelivery": "Dévérouiller la livraison ?", + "amapUpdate": "Modifier", + "amapUpdatedAmount": "Solde modifié", + "amapUpdatedOrder": "Commande modifiée", + "amapUpdatedProduct": "Produit modifié", + "amapUpdatingError": "Echec de la modification", + "amapUsersNotFound": "Aucun utilisateur trouvé", + "amapWaiting": "En attente", + "bookingAdd": "Ajouter", + "bookingAddBookingPage": "Demande", + "bookingAddRoom": "Ajouter une salle", + "bookingAddBooking": "Ajouter une réservation", + "bookingAddedBooking": "Demande ajoutée", + "bookingAddedRoom": "Salle ajoutée", + "bookingAddedManager": "Gestionnaire ajouté", + "bookingAddingError": "Erreur lors de l'ajout", + "bookingAddManager": "Ajouter un gestionnaire", + "bookingAdminPage": "Administrateur", + "bookingAllDay": "Toute la journée", + "bookingBookedFor": "Réservé pour", + "bookingBooking": "Réservation", + "bookingBookingCreated": "Réservation créée", + "bookingBookingDemand": "Demande de réservation", + "bookingBookingNote": "Note de la réservation", + "bookingBookingPage": "Réservation", + "bookingBookingReason": "Motif de la réservation", + "bookingBy": "par", + "bookingConfirm": "Confirmer", + "bookingConfirmation": "Confirmation", + "bookingConfirmBooking": "Confirmer la réservation ?", + "bookingConfirmed": "Validée", + "bookingDates": "Dates", + "bookingDecline": "Refuser", + "bookingDeclineBooking": "Refuser la réservation ?", + "bookingDeclined": "Refusée", + "bookingDelete": "Supprimer", + "bookingDeleting": "Suppression", + "bookingDeleteBooking": "Suppression", + "bookingDeleteBookingConfirmation": "Êtes-vous sûr de vouloir supprimer cette réservation ?", + "bookingDeletedBooking": "Réservation supprimée", + "bookingDeletedRoom": "Salle supprimée", + "bookingDeletedManager": "Gestionnaire supprimé", + "bookingDeleteRoomConfirmation": "Êtes-vous sûr de vouloir supprimer cette salle ?\n\nLa salle ne doit avoir aucune réservation en cours ou à venir pour être supprimée", + "bookingDeleteManagerConfirmation": "Êtes-vous sûr de vouloir supprimer ce gestionnaire ?\n\nLe gestionnaire ne doit être associé à aucune salle pour pouvoir être supprimé", + "bookingDeletingBooking": "Supprimer la réservation ?", + "bookingDeletingError": "Erreur lors de la suppression", + "bookingDeletingRoom": "Supprimer la salle ?", + "bookingEdit": "Modifier", + "bookingEditBooking": "Modifier une réservation", + "bookingEditionError": "Erreur lors de la modification", + "bookingEditedBooking": "Réservation modifiée", + "bookingEditedRoom": "Salle modifiée", + "bookingEditedManager": "Gestionnaire modifié", + "bookingEditManager": "Modifier ou supprimer un gestionnaire", + "bookingEditRoom": "Modifier ou supprimer une salle", + "bookingEndDate": "Date de fin", + "bookingEndHour": "Heure de fin", + "bookingEntity": "Pour qui ?", + "bookingError": "Erreur", + "bookingEventEvery": "Tous les", + "bookingHistoryPage": "Historique", + "bookingIncorrectOrMissingFields": "Champs incorrects ou manquants", + "bookingInterval": "Intervalle", + "bookingInvalidIntervalError": "Intervalle invalide", + "bookingInvalidDates": "Dates invalides", + "bookingInvalidRoom": "Salle invalide", + "bookingKeysRequested": "Clés demandées", + "bookingManagement": "Gestion", + "bookingManager": "Gestionnaire", + "bookingManagerName": "Nom du gestionnaire", + "bookingMultipleDay": "Plusieurs jours", + "bookingMyBookings": "Mes réservations", + "bookingNecessaryKey": "Clé nécessaire", + "bookingNext": "Suivant", + "bookingNo": "Non", + "bookingNoCurrentBooking": "Pas de réservation en cours", + "bookingNoDateError": "Veuillez choisir une date", + "bookingNoAppointmentInReccurence": "Aucun créneau existe avec ces paramètres de récurrence", + "bookingNoDaySelected": "Aucun jour sélectionné", + "bookingNoDescriptionError": "Veuillez entrer une description", + "bookingNoKeys": "Aucune clé", + "bookingNoNoteError": "Veuillez entrer une note", + "bookingNoPhoneRegistered": "Numéro non renseigné", + "bookingNoReasonError": "Veuillez entrer un motif", + "bookingNoRoomFoundError": "Aucune salle enregistrée", + "bookingNoRoomFound": "Aucune salle trouvée", + "bookingNote": "Note", + "bookingOther": "Autre", + "bookingPending": "En attente", + "bookingPrevious": "Précédent", + "bookingReason": "Motif", + "bookingRecurrence": "Récurrence", + "bookingRecurrenceDays": "Jours de récurrence", + "bookingRecurrenceEndDate": "Date de fin de récurrence", + "bookingRecurrent": "Récurrent", + "bookingRegisteredRooms": "Salles enregistrées", + "bookingRoom": "Salle", + "bookingRoomName": "Nom de la salle", + "bookingStartDate": "Date de début", + "bookingStartHour": "Heure de début", + "bookingWeeks": "Semaines", + "bookingYes": "Oui", + "bookingWeekDayMon": "Lundi", + "bookingWeekDayTue": "Mardi", + "bookingWeekDayWed": "Mercredi", + "bookingWeekDayThu": "Jeudi", + "bookingWeekDayFri": "Vendredi", + "bookingWeekDaySat": "Samedi", + "bookingWeekDaySun": "Dimanche", + "cinemaAdd": "Ajouter", + "cinemaAddedSession": "Séance ajoutée", + "cinemaAddingError": "Erreur lors de l'ajout", + "cinemaAddSession": "Ajouter une séance", + "cinemaCinema": "Cinéma", + "cinemaDeleteSession": "Supprimer la séance ?", + "cinemaDeleting": "Suppression", + "cinemaDuration": "Durée", + "cinemaEdit": "Modifier", + "cinemaEditedSession": "Séance modifiée", + "cinemaEditingError": "Erreur lors de la modification", + "cinemaEditSession": "Modifier la séance", + "cinemaEmptyUrl": "Veuillez entrer une URL", + "cinemaImportFromTMDB": "Importer depuis TMDB", + "cinemaIncomingSession": "A l'affiche", + "cinemaIncorrectOrMissingFields": "Champs incorrects ou manquants", + "cinemaInvalidUrl": "URL invalide", + "cinemaGenre": "Genre", + "cinemaName": "Nom", + "cinemaNoDateError": "Veuillez entrer une date", + "cinemaNoDuration": "Veuillez entrer une durée", + "cinemaNoOverview": "Aucun synopsis", + "cinemaNoPoster": "Aucune affiche", + "cinemaNoSession": "Aucune séance", + "cinemaOverview": "Synopsis", + "cinemaPosterUrl": "URL de l'affiche", + "cinemaSessionDate": "Jour de la séance", + "cinemaStartHour": "Heure de début", + "cinemaTagline": "Slogan", + "cinemaThe": "Le", + "drawerAdmin": "Administration", + "drawerAndroidAppLink": "https://play.google.com/store/apps/details?id=fr.myecl.titan", + "drawerCopied": "Copié !", + "drawerDownloadAppOnMobileDevice": "Ce site est la version Web de l'application MyECL. Nous vous invitons à télécharger l'application. N'utilisez ce site qu'en cas de problème avec l'application.\n", + "drawerIosAppLink": "https://apps.apple.com/fr/app/myecl/id6444443430", + "drawerLoginOut": "Voulez-vous vous déconnecter ?", + "drawerLogOut": "Déconnexion", + "drawerOr": " ou ", + "drawerSettings": "Paramètres", + "eventAdd": "Ajouter", + "eventAddEvent": "Ajouter un événement", + "eventAddedEvent": "Événement ajouté", + "eventAddingError": "Erreur lors de l'ajout", + "eventAllDay": "Toute la journée", + "eventConfirm": "Confirmer", + "eventConfirmEvent": "Confirmer l'événement ?", + "eventConfirmation": "Confirmation", + "eventConfirmed": "Confirmé", + "eventDates": "Dates", + "eventDecline": "Refuser", + "eventDeclineEvent": "Refuser l'événement ?", + "eventDeclined": "Refusé", + "eventDelete": "Supprimer", + "eventDeletedEvent": "Événement supprimé", + "eventDeleting": "Suppression", + "eventDeletingError": "Erreur lors de la suppression", + "eventDeletingEvent": "Supprimer l'événement ?", + "eventDescription": "Description", + "eventEdit": "Modifier", + "eventEditEvent": "Modifier un événement", + "eventEditedEvent": "Événement modifié", + "eventEditingError": "Erreur lors de la modification", + "eventEndDate": "Date de fin", + "eventEndHour": "Heure de fin", + "eventError": "Erreur", + "eventEventList": "Liste des événements", + "eventEventType": "Type d'événement", + "eventEvery": "Tous les", + "eventHistory": "Historique", + "eventIncorrectOrMissingFields": "Certains champs sont incorrects ou manquants", + "eventInterval": "Intervalle", + "eventInvalidDates": "La date de fin doit être après la date de début", + "eventInvalidIntervalError": "Veuillez entrer un intervalle valide", + "eventLocation": "Lieu", + "eventMyEvents": "Mes événements", + "eventName": "Nom", + "eventNext": "Suivant", + "eventNo": "Non", + "eventNoCurrentEvent": "Aucun événement en cours", + "eventNoDateError": "Veuillez entrer une date", + "eventNoDaySelected": "Aucun jour sélectionné", + "eventNoDescriptionError": "Veuillez entrer une description", + "eventNoEvent": "Aucun événement", + "eventNoNameError": "Veuillez entrer un nom", + "eventNoOrganizerError": "Veuillez entrer un organisateur", + "eventNoPlaceError": "Veuillez entrer un lieu", + "eventNoPhoneRegistered": "Numéro non renseigné", + "eventNoRuleError": "Veuillez entrer une règle de récurrence", + "eventOrganizer": "Organisateur", + "eventOther": "Autre", + "eventPending": "En attente", + "eventPrevious": "Précédent", + "eventRecurrence": "Récurrence", + "eventRecurrenceDays": "Jours de récurrence", + "eventRecurrenceEndDate": "Date de fin de la récurrence", + "eventRecurrenceRule": "Règle de récurrence", + "eventRoom": "Salle", + "eventStartDate": "Date de début", + "eventStartHour": "Heure de début", + "eventTitle": "Événements", + "eventYes": "Oui", + "eventEventEvery": "Toutes les", + "eventWeeks": "semaines", + "eventDayMon": "Lundi", + "eventDayTue": "Mardi", + "eventDayWed": "Mercredi", + "eventDayThu": "Jeudi", + "eventDayFri": "Vendredi", + "eventDaySat": "Samedi", + "eventDaySun": "Dimanche", + "globalConfirm": "Confirmer", + "globalCancel": "Annuler", + "globalIrreversibleAction": "Cette action est irréversible", + "globalOptionnal": "{text} (Optionnel)", + "@globalOptionnal": { + "description": "Texte avec complément optionnel", + "placeholders": { + "text": { + "type": "String" + } + } + }, + "homeCalendar": "Calendrier", + "homeEventOf": "Évènements du", + "homeIncomingEvents": "Évènements à venir", + "homeLastInfos": "Dernières annonces", + "homeNoEvents": "Aucun évènement", + "homeTranslateDayShortMon": "Lun", + "homeTranslateDayShortTue": "Mar", + "homeTranslateDayShortWed": "Mer", + "homeTranslateDayShortThu": "Jeu", + "homeTranslateDayShortFri": "Ven", + "homeTranslateDayShortSat": "Sam", + "homeTranslateDayShortSun": "Dim", + "loanAdd": "Ajouter", + "loanAddLoan": "Ajouter un prêt", + "loanAddObject": "Ajouter un objet", + "loanAddedLoan": "Prêt ajouté", + "loanAddedObject": "Objet ajouté", + "loanAddedRoom": "Salle ajoutée", + "loanAddingError": "Erreur lors de l'ajout", + "loanAdmin": "Administrateur", + "loanAvailable": "Disponible", + "loanAvailableMultiple": "Disponibles", + "loanBorrowed": "Emprunté", + "loanBorrowedMultiple": "Empruntés", + "loanAnd": "et", + "loanAssociation": "Association", + "loanAvailableItems": "Objets disponibles", + "loanBeginDate": "Date du début du prêt", + "loanBorrower": "Emprunteur", + "loanCaution": "Caution", + "loanCancel": "Annuler", + "loanConfirm": "Confirmer", + "loanConfirmation": "Confirmation", + "loanDates": "Dates", + "loanDays": "Jours", + "loanDelay": "Délai de la prolongation", + "loanDelete": "Supprimer", + "loanDeletingLoan": "Supprimer le prêt ?", + "loanDeletedItem": "Objet supprimé", + "loanDeletedLoan": "Prêt supprimé", + "loanDeleting": "Suppression", + "loanDeletingError": "Erreur lors de la suppression", + "loanDeletingItem": "Supprimer l'objet ?", + "loanDuration": "Durée", + "loanEdit": "Modifier", + "loanEditItem": "Modifier l'objet", + "loanEditLoan": "Modifier le prêt", + "loanEditedRoom": "Salle modifiée", + "loanEndDate": "Date de fin du prêt", + "loanEnded": "Terminé", + "loanEnterDate": "Veuillez entrer une date", + "loanExtendedLoan": "Prêt prolongé", + "loanExtendingError": "Erreur lors de la prolongation", + "loanHistory": "Historique", + "loanIncorrectOrMissingFields": "Des champs sont manquants ou incorrects", + "loanInvalidNumber": "Veuillez entrer un nombre", + "loanInvalidDates": "Les dates ne sont pas valides", + "loanItem": "Objet", + "loanItems": "Objets", + "loanItemHandling": "Gestion des objets", + "loanItemSelected": "objet sélectionné", + "loanItemsSelected": "objets sélectionnés", + "loanLendingDuration": "Durée possible du prêt", + "loanLoan": "Prêt", + "loanLoanHandling": "Gestion des prêts", + "loanLooking": "Rechercher", + "loanName": "Nom", + "loanNext": "Suivant", + "loanNo": "Non", + "loanNoAssociationsFounded": "Aucune association trouvée", + "loanNoAvailableItems": "Aucun objet disponible", + "loanNoBorrower": "Aucun emprunteur", + "loanNoItems": "Aucun objet", + "loanNoItemSelected": "Aucun objet sélectionné", + "loanNoLoan": "Aucun prêt", + "loanNoReturnedDate": "Pas de date de retour", + "loanQuantity": "Quantité", + "loanNone": "Aucun", + "loanNote": "Note", + "loanNoValue": "Veuillez entrer une valeur", + "loanOnGoing": "En cours", + "loanOnGoingLoan": "Prêt en cours", + "loanOthers": "autres", + "loanPaidCaution": "Caution payée", + "loanPositiveNumber": "Veuillez entrer un nombre positif", + "loanPrevious": "Précédent", + "loanReturned": "Rendu", + "loanReturnedLoan": "Prêt rendu", + "loanReturningError": "Erreur lors du retour", + "loanReturningLoan": "Retour", + "loanReturnLoan": "Rendre le prêt ?", + "loanReturnLoanDescription": "Voulez-vous rendre ce prêt ?", + "loanToReturn": "A rendre", + "loanUnavailable": "Indisponible", + "loanUpdate": "Modifier", + "loanUpdatedItem": "Objet modifié", + "loanUpdatedLoan": "Prêt modifié", + "loanUpdatingError": "Erreur lors de la modification", + "loanYes": "Oui", + "loginAccountActivated": "Compte activé", + "loginAccountNotActivated": "Compte non activé", + "loginActivationCode": "Code d'activation", + "loginBirthday": "Date de naissance", + "loginCanBeEmpty": "Ce champ peut être vide", + "loginConfirmPassword": "Confirmer le mot de passe", + "loginCreate": "Créer", + "loginCreateAccount": "Créer un compte", + "loginCreateAccountTitle": "Créer un\ncompte", + "loginEmail": "Email", + "loginEmailEmpty": "Veuillez entrer une adresse mail", + "loginEmailInvalid": "Veuillez entrer une adresse mail de centrale.\nSi vous n'en possédez pas, veuillez contacter Éclair", + "loginEmptyFieldError": "Ce champ ne peut pas être vide", + "loginEndActivation": "Finaliser l'activation", + "loginEndResetPassword": "Finaliser la \nréinitialisation", + "loginErrorResetPassword": "Erreur lors de la réinitialisation", + "loginExpectingDate": "Une date est attendue", + "loginFillAllFields": "Veuillez remplir tous les champs", + "loginFirstname": "Prénom", + "loginFloor": "Étage", + "loginForgetPassword": "Mot de passe\noublié", + "loginForgotPassword": "Mot de passe oublié ?", + "loginInvalidToken": "Code d'activation invalide", + "loginLoginFailed": "Échec de la connexion", + "loginMailSendingError": "Erreur lors de la création du compte", + "loginMustBeIntError": "Ce champ doit être un entier", + "loginName": "Nom", + "loginNewPassword": "Nouveau mot de passe", + "loginPassword": "Mot de passe", + "loginPasswordLengthError": "Le mot de passe doit faire au moins 6 caractères", + "loginPasswordUppercaseError": "Le mot de passe doit contenir au moins une majuscule", + "loginPasswordLowercaseError": "Le mot de passe doit contenir au moins une minucule", + "loginPasswordNumberError": "Le mot de passe doit contenir au moins un chiffre", + "loginPasswordSpecialCaracterError": "Le mot de passe doit contenir au moins un caractère spécial", + "loginPasswordMustMatch": "Les mots de passe doivent correspondre", + "loginPasswordStrengthVeryWeak": "Très faible", + "loginPasswordStrengthWeak": "Faible", + "loginPasswordStrengthMedium": "Moyen", + "loginPasswordStrengthStrong": "Fort", + "loginPasswordStrengthVeryStrong": "Très fort", + "loginPhone": "Téléphone", + "loginPromo": "Promo entrante (ex : 2023)", + "loginSendedMail": "Mail de confirmation envoyé", + "loginSendedResetMail": "Mail de réinitialisation envoyé", + "loginSignIn": "Se connecter", + "loginRegister": "S'inscrire", + "loginRecievedMail": "J'ai reçu le mail", + "loginRecover": "Réinitialiser", + "loginResetedPassword": "Mot de passe réinitialisé", + "loginResetPasswordTitle": "Réinitialiser\nle mot de \npasse", + "loginNickname": "Surnom", + "loginWelcomeBack": "Bienvenue", + "loginAppName": "MyECL", + "othersCheckInternetConnection": "Veuillez vérifier votre connexion internet", + "othersRetry": "Réessayer", + "othersTooOldVersion": "Votre version de l'application est trop ancienne.\n\nVeuillez mettre à jour l'application.", + "othersUnableToConnectToServer": "Impossible de se connecter au serveur", + "othersVersion": "Version", + "othersNoModule": "Aucun module disponible, veuillez réessayer ultérieurement 😢😢", + "othersAdmin": "Admin", + "othersError": "Une erreur est survenue", + "othersNoValue": "Veuillez entrer une valeur", + "othersInvalidNumber": "Veuillez entrer un nombre", + "othersNoDateError": "Veuillez entrer une date", + "othersImageSizeTooBig": "La taille de l'image ne doit pas dépasser 4 Mio", + "othersImageError": "Erreur lors de l'ajout de l'image", + "phAddNewJournal": "Ajouter un nouveau journal", + "phNameField": "Nom : ", + "phDateField": "Date : ", + "phDelete": "Voulez-vous vraiment supprimer ce journal ?", + "phIrreversibleAction": "Cette action est irréversible", + "phToHeavyFile": "Fichier trop volumineux", + "phAddPdfFile": "Ajouter un fichier PDF", + "phEditPdfFile": "Modifier le fichier PDF", + "phPhName": "Nom du PH", + "phDate": "Date", + "phAdded": "Ajouté", + "phEdited": "Modifié", + "phAddingFileError": "Erreur d'ajout", + "phMissingInformatonsOrPdf": "Informations manquantes ou fichier PDF manquant", + "phAdd": "Ajouter", + "phEdit": "Modifier", + "phSeePreviousJournal": "Voir les anciens journaux", + "phNoJournalInDatabase": "Pas encore de PH dans la base de donnée", + "phSuccesDowloading": "Téléchargé avec succès", + "phonebookAdd": "Ajouter", + "phonebookAddAssociation": "Ajouter une association", + "phonebookAddAssociationGroupement": "Ajouter un groupement d'association", + "phonebookAddedAssociation": "Association ajoutée", + "phonebookAddedMember": "Membre ajouté", + "phonebookAddingError": "Erreur lors de l'ajout", + "phonebookAddMember": "Ajouter un membre", + "phonebookAddRole": "Ajouter un rôle", + "phonebookAdmin": "Administration", + "phonebookAll": "Toutes", + "phonebookApparentName": "Nom public du rôle :", + "phonebookAssociation": "Association", + "phonebookAssociationDetail": "Détail de l'association :", + "phonebookAssociationGroupement": "Groupement d'association", + "phonebookAssociationKind": "Type d'association :", + "phonebookAssociationName": "Nom de l'association", + "phonebookAssociations": "Associations", + "phonebookCancel": "Annuler", + "phonebookChangeTermYear": "Passer au mandat {year}", + "@phonebookChangeTermYear": { + "description": "Permet de changer le mandat d'une association", + "placeholders": { + "year": { + "type": "int" + } + } + }, + "phonebookChangeTermConfirm": "Êtes-vous sûr de vouloir changer tout le mandat ?\nCette action est irréversible !", + "phonebookClose": "Fermer", + "phonebookConfirm": "Confirmer", + "phonebookCopied": "Copié dans le presse-papier", + "phonebookDeactivateAssociation": "Désactiver l'association", + "phonebookDeactivatedAssociation": "Association désactivée", + "phonebookDeactivatedAssociationWarning": "Attention, cette association est désactivée, vous ne pouvez pas la modifier", + "phonebookDeactivateSelectedAssociation": "Désactiver l'association {association} ?", + "@phonebookDeactivateSelectedAssociation": { + "description": "Permet de désactiver une association", + "placeholders": { + "association": { + "type": "String" + } + } + }, + "phonebookDeactivatingError": "Erreur lors de la désactivation", + "phonebookDetail": "Détail :", + "phonebookDelete": "Supprimer", + "phonebookDeleteAssociation": "Supprimer l'association", + "phonebookDeleteSelectedAssociation": "Supprimer l'association {association} ?", + "@phonebookDeleteSelectedAssociation": { + "description": "Permet de supprimer une association", + "placeholders": { + "association": { + "type": "String" + } + } + }, + "phonebookDeleteAssociationDescription": "Ceci va supprimer l'historique de l'association", + "phonebookDeletedAssociation": "Association supprimée", + "phonebookDeletedMember": "Membre supprimé", + "phonebookDeleteRole": "Supprimer le rôle", + "phonebookDeleteUserRole": "Supprimer le rôle de l'utilisateur {name} ?", + "@phonebookDeleteUserRole": { + "description": "Permet de supprimer le rôle d'un utilisateur dans une association", + "placeholders": { + "name": { + "type": "String" + } + } + }, + "phonebookDeleting": "Suppression", + "phonebookDeletingError": "Erreur lors de la suppression", + "phonebookDescription": "Description", + "phonebookEdit": "Modifier", + "phonebookEditAssociationGroupement": "Modifier le groupement d'association", + "phonebookEditAssociationGroups": "Gérer les groupes", + "phonebookEditAssociationInfo": "Modifier", + "phonebookEditAssociationMembers": "Gérer les membres", + "phonebookEditRole": "Modifier le rôle", + "phonebookEmail": "Email :", + "phonebookEmailCopied": "Email copié dans le presse-papier", + "phonebookEmptyApparentName": "Veuillez entrer un nom de role", + "phonebookEmptyFieldError": "Un champ n'est pas rempli", + "phonebookEmptyKindError": "Veuillez choisir un type d'association", + "phonebookEmptyMember": "Aucun membre sélectionné", + "phonebookErrorAssociationLoading": "Erreur lors du chargement de l'association", + "phonebookErrorAssociationNameEmpty": "Veuillez entrer un nom d'association", + "phonebookErrorAssociationPicture": "Erreur lors de la modification de la photo d'association", + "phonebookErrorKindsLoading": "Erreur lors du chargement des types d'association", + "phonebookErrorLoadAssociationList": "Erreur lors du chargement de la liste des associations", + "phonebookErrorLoadAssociationMember": "Erreur lors du chargement des membres de l'association", + "phonebookErrorLoadAssociationPicture": "Erreur lors du chargement de la photo d'association", + "phonebookErrorLoadProfilePicture": "Erreur", + "phonebookErrorRoleTagsLoading": "Erreur lors du chargement des tags de rôle", + "phonebookExistingMembership": "Ce membre est déjà dans le mandat actuel", + "phonebookFilter": "Filtrer", + "phonebookFilterDescription": "Sélectionnez un ou plusieurs groupements pour filtrer les associations.", + "phonebookFirstname": "Prénom :", + "phonebookGroupementDeleted": "Groupement d'association supprimé", + "phonebookGroupementDeleteError": "Erreur lors de la suppression du groupement d'association", + "phonebookGroupementName": "Nom du groupement", + "phonebookGroups": "Gérer les groupes de {association}", + "@phonebookGroups": { + "description": "Permet de gérer les groupes d'une association", + "placeholders": { + "association": { + "type": "String" + } + } + }, + "phonebookTerm": "Mandat {year}", + "@phonebookTerm": { + "description": "Année de mandat d'une association", + "placeholders": { + "year": { + "type": "int" + } + } + }, + "phonebookTermChangingError": "Erreur lors du changement de mandat", + "phonebookMember": "Membre", + "phonebookMemberReordered": "Membre réordonné", + "phonebookMembers": "Gérer les membres de {association}", + "@phonebookMembers": { + "description": "Permet de gérer les membres d'une association", + "placeholders": { + "association": { + "type": "String" + } + } + }, + "phonebookMembershipAssociationError": "Veuillez choisir une association", + "phonebookMembershipRole": "Rôle :", + "phonebookMembershipRoleError": "Veuillez choisir un rôle", + "phonebookModifyMembership": "Modifier le rôle de {name}", + "@phonebookModifyMembership": { + "description": "Permet de modifier le rôle d'un membre dans une association", + "placeholders": { + "name": { + "type": "String" + } + } + }, + "phonebookName": "Nom :", + "phonebookNameCopied": "Nom et prénom copié dans le presse-papier", + "phonebookNamePure": "Nom", + "phonebookNewTerm": "Nouveau mandat", + "phonebookNewTermConfirmed": "Mandat changé", + "phonebookNickname": "Surnom :", + "phonebookNicknameCopied": "Surnom copié dans le presse-papier", + "phonebookNoAssociationFound": "Aucune association trouvée", + "phonebookNoMember": "Aucun membre", + "phonebookNoMemberRole": "Aucun role trouvé", + "phonebookNoRoleTags": "Aucun tag de rôle trouvé", + "phonebookPhone": "Téléphone :", + "phonebookPhonebook": "Annuaire", + "phonebookPhonebookSearch": "Rechercher", + "phonebookPhonebookSearchAssociation": "Association", + "phonebookPhonebookSearchField": "Rechercher :", + "phonebookPhonebookSearchName": "Nom/Prénom/Surnom", + "phonebookPhonebookSearchRole": "Poste", + "phonebookPresidentRoleTag": "Prez'", + "phonebookPromoNotGiven": "Promo non renseignée", + "phonebookPromotion": "Promotion {year}", + "@phonebookPromotion": { + "description": "Année de promotion d'un membre", + "placeholders": { + "year": { + "type": "int" + } + } + }, + "phonebookReorderingError": "Erreur lors du réordonnement", + "phonebookResearch": "Rechercher", + "phonebookRolePure": "Rôle", + "phonebookSearchUser": "Rechercher un utilisateur", + "phonebookTooHeavyAssociationPicture": "L'image est trop lourde (max 4Mo)", + "phonebookUpdateGroups": "Mettre à jour les groupes", + "phonebookUpdatedAssociation": "Association modifiée", + "phonebookUpdatedAssociationPicture": "La photo d'association a été changée", + "phonebookUpdatedGroups": "Groupes mis à jour", + "phonebookUpdatedMember": "Membre modifié", + "phonebookUpdatingError": "Erreur lors de la modification", + "phonebookValidation": "Valider", + "purchasesPurchases": "Achats", + "purchasesResearch": "Rechercher", + "purchasesNoPurchasesFound": "Aucun achat trouvé", + "purchasesNoTickets": "Aucun ticket", + "purchasesTicketsError": "Erreur lors du chargement des tickets", + "purchasesPurchasesError": "Erreur lors du chargement des achats", + "purchasesNoPurchases": "Aucun achat", + "purchasesTimes": "fois", + "purchasesAlreadyUsed": "Déjà utilisé", + "purchasesNotPaid": "Non validé", + "purchasesPleaseSelectProduct": "Veuillez sélectionner un produit", + "purchasesProducts": "Produits", + "purchasesCancel": "Annuler", + "purchasesValidate": "Valider", + "purchasesLeftScan": "Scans restants", + "purchasesTag": "Tag", + "purchasesHistory": "Historique", + "purchasesPleaseSelectSeller": "Veuillez sélectionner un vendeur", + "purchasesNoTagGiven": "Attention, aucun tag n'a été entré", + "purchasesTickets": "Tickets", + "purchasesNoScannableProducts": "Aucun produit scannable", + "purchasesLoading": "En attente de scan", + "purchasesScan": "Scanner", + "raffleRaffle": "Tombola", + "rafflePrize": "Lot", + "rafflePrizes": "Lots", + "raffleActualRaffles": "Tombola en cours", + "rafflePastRaffles": "Tombola passés", + "raffleYourTickets": "Tous vos tickets", + "raffleCreateMenu": "Menu de Création", + "raffleNextRaffles": "Prochaines tombolas", + "raffleNoTicket": "Vous n'avez pas de ticket", + "raffleSeeRaffleDetail": "Voir lots/tickets", + "raffleActualPrize": "Lots actuels", + "raffleMajorPrize": "Lot Majeurs", + "raffleTakeTickets": "Prendre vos tickets", + "raffleNoTicketBuyable": "Vous ne pouvez pas achetez de billets pour l'instant", + "raffleNoCurrentPrize": "Il n'y a aucun lots actuellement", + "raffleModifTombola": "Vous pouvez modifiez vos tombolas ou en créer de nouvelles, toute décision doit ensuite être prise par les admins", + "raffleCreateYourRaffle": "Votre menu de création de tombolas", + "rafflePossiblePrice": "Prix possible", + "raffleInformation": "Information et Statistiques", + "raffleAccounts": "Comptes", + "raffleAdd": "Ajouter", + "raffleUpdatedAmount": "Montant mis à jour", + "raffleUpdatingError": "Erreur lors de la mise à jour", + "raffleDeletedPrize": "Lot supprimé", + "raffleDeletingError": "Erreur lors de la suppression", + "raffleQuantity": "Quantité", + "raffleClose": "Fermer", + "raffleOpen": "Ouvrir", + "raffleAddTypeTicketSimple": "Ajouter", + "raffleAddingError": "Erreur lors de l'ajout", + "raffleEditTypeTicketSimple": "Modifier", + "raffleFillField": "Le champ ne peut pas être vide", + "raffleWaiting": "Chargement", + "raffleEditingError": "Erreur lors de la modification", + "raffleAddedTicket": "Ticket ajouté", + "raffleEditedTicket": "Ticket modifié", + "raffleAlreadyExistTicket": "Le ticket existe déjà", + "raffleNumberExpected": "Un entier est attendu", + "raffleDeletedTicket": "Ticket supprimé", + "raffleAddPrize": "Ajouter", + "raffleEditPrize": "Modifier", + "raffleOpenRaffle": "Ouvrir la tombola", + "raffleCloseRaffle": "Fermer la tombola", + "raffleOpenRaffleDescription": "Vous allez ouvrir la tombola, les utilisateurs pourront acheter des tickets. Vous ne pourrez plus modifier la tombola. Êtes-vous sûr de vouloir continuer ?", + "raffleCloseRaffleDescription": "Vous allez fermer la tombola, les utilisateurs ne pourront plus acheter de tickets. Êtes-vous sûr de vouloir continuer ?", + "raffleNoCurrentRaffle": "Il n'y a aucune tombola en cours", + "raffleBoughtTicket": "Ticket acheté", + "raffleDrawingError": "Erreur lors du tirage", + "raffleInvalidPrice": "Le prix doit être supérieur à 0", + "raffleMustBePositive": "Le nombre doit être strictement positif", + "raffleDraw": "Tirer", + "raffleDrawn": "Tiré", + "raffleError": "Erreur", + "raffleGathered": "Récolté", + "raffleTickets": "Tickets", + "raffleTicket": "ticket", + "raffleWinner": "Gagnant", + "raffleNoPrize": "Aucun lot", + "raffleDeletePrize": "Supprimer le lot", + "raffleDeletePrizeDescription": "Vous allez supprimer le lot, êtes-vous sûr de vouloir continuer ?", + "raffleDrawing": "Tirage", + "raffleDrawingDescription": "Tirer le gagnant du lot ?", + "raffleDeleteTicket": "Supprimer le ticket", + "raffleDeleteTicketDescription": "Vous allez supprimer le ticket, êtes-vous sûr de vouloir continuer ?", + "raffleWinningTickets": "Tickets gagnants", + "raffleNoWinningTicketYet": "Les tickets gagnants seront affichés ici", + "raffleName": "Nom", + "raffleDescription": "Description", + "raffleBuyThisTicket": "Acheter ce ticket", + "raffleLockedRaffle": "Tombola verrouillée", + "raffleUnavailableRaffle": "Tombola indisponible", + "raffleNotEnoughMoney": "Vous n'avez pas assez d'argent", + "raffleWinnable": "gagnable", + "raffleNoDescription": "Aucune description", + "raffleAmount": "Solde", + "raffleLoading": "Chargement", + "raffleTicketNumber": "Nombre de ticket", + "rafflePrice": "Prix", + "raffleEditRaffle": "Modifier la tombola", + "raffleEdit": "Modifier", + "raffleAddPackTicket": "Ajouter un pack de ticket", + "recommendationRecommendation": "Bons plans", + "recommendationTitle": "Titre", + "recommendationLogo": "Logo", + "recommendationCode": "Code", + "recommendationSummary": "Court résumé", + "recommendationDescription": "Description", + "recommendationAdd": "Ajouter", + "recommendationEdit": "Modifier", + "recommendationDelete": "Supprimer", + "recommendationAddImage": "Veuillez ajouter une image", + "recommendationAddedRecommendation": "Bon plan ajouté", + "recommendationEditedRecommendation": "Bon plan modifié", + "recommendationDeleteRecommendationConfirmation": "Êtes-vous sûr de vouloir supprimer ce bon plan ?", + "recommendationDeleteRecommendation": "Suppresion", + "recommendationDeletingRecommendationError": "Erreur lors de la suppression", + "recommendationDeletedRecommendation": "Bon plan supprimé", + "recommendationIncorrectOrMissingFields": "Champs incorrects ou manquants", + "recommendationEditingError": "Échec de la modification", + "recommendationAddingError": "Échec de l'ajout", + "recommendationCopiedCode": "Code de réduction copié", + "seedLibraryAdd": "Ajouter", + "seedLibraryAddedPlant": "Plante ajoutée", + "seedLibraryAddedSpecies": "Espèce ajoutée", + "seedLibraryAddingError": "Erreur lors de l'ajout", + "seedLibraryAddPlant": "Déposer une plante", + "seedLibraryAddSpecies": "Ajouter une espèce", + "seedLibraryAll": "Toutes", + "seedLibraryAncestor": "Ancêtre", + "seedLibraryAround": "environ", + "seedLibraryAutumn": "Automne", + "seedLibraryBorrowedPlant": "Plante empruntée", + "seedLibraryBorrowingDate": "Date d'emprunt :", + "seedLibraryBorrowPlant": "Emprunter la plante", + "seedLibraryCard": "Carte", + "seedLibraryChoosingAncestor": "Veuillez choisir un ancêtre", + "seedLibraryChoosingSpecies": "Veuillez choisir une espèce", + "seedLibraryChoosingSpeciesOrAncestor": "Veuillez choisir une espèce ou un ancêtre", + "seedLibraryContact": "Contact :", + "seedLibraryDays": "jours", + "seedLibraryDeadMsg": "Voulez-vous déclarer la plante morte ?", + "seedLibraryDeadPlant": "Plante morte", + "seedLibraryDeathDate": "Date de mort", + "seedLibraryDeletedSpecies": "Espèce supprimée", + "seedLibraryDeleteSpecies": "Supprimer l'espèce ?", + "seedLibraryDeleting": "Suppression", + "seedLibraryDeletingError": "Erreur lors de la suppression", + "seedLibraryDepositNotAvailable": "Le dépôt de plantes n'est pas possible sans emprunter une plante au préalable", + "seedLibraryDescription": "Description", + "seedLibraryDifficulty": "Difficulté :", + "seedLibraryEdit": "Modifier", + "seedLibraryEditedPlant": "Plante modifiée", + "seedLibraryEditInformation": "Modifier les informations", + "seedLibraryEditingError": "Erreur lors de la modification", + "seedLibraryEditSpecies": "Modifier l'espèce", + "seedLibraryEmptyDifficultyError": "Veuillez choisir une difficulté", + "seedLibraryEmptyFieldError": "Veuillez remplir tous les champs", + "seedLibraryEmptyTypeError": "Veuillez choisir un type de plante", + "seedLibraryEndMonth": "Mois de fin :", + "seedLibraryFacebookUrl": "Lien Facebook", + "seedLibraryFilters": "Filtres", + "seedLibraryForum": "Oskour maman j'ai tué ma plante - Forum d'aide", + "seedLibraryForumUrl": "Lien Forum", + "seedLibraryHelpSheets": "Fiches sur les plantes", + "seedLibraryInformation": "Informations :", + "seedLibraryMaturationTime": "Temps de maturation", + "seedLibraryMonthJan": "Janvier", + "seedLibraryMonthFeb": "Février", + "seedLibraryMonthMar": "Mars", + "seedLibraryMonthApr": "Avril", + "seedLibraryMonthMay": "Mai", + "seedLibraryMonthJun": "Juin", + "seedLibraryMonthJul": "Juillet", + "seedLibraryMonthAug": "Août", + "seedLibraryMonthSep": "Septembre", + "seedLibraryMonthOct": "Octobre", + "seedLibraryMonthNov": "Novembre", + "seedLibraryMonthDec": "Décembre", + "seedLibraryMyPlants": "Mes plantes", + "seedLibraryName": "Nom", + "seedLibraryNbSeedsRecommended": "Nombre de graines recommandées", + "seedLibraryNbSeedsRecommendedError": "Veuillez entrer un nombre de graines recommandé supérieur à 0", + "seedLibraryNoDateError": "Veuillez entrer une date", + "seedLibraryNoFilteredPlants": "Aucune plante ne correspond à votre recherche. Essayez d'autres filtres.", + "seedLibraryNoMorePlant": "Aucune plante n'est disponible", + "seedLibraryNoPersonalPlants": "Vous n'avez pas encore de plantes dans votre grainothèque. Vous pouvez en ajouter en allant dans les stocks.", + "seedLibraryNoSpecies": "Aucune espèce trouvée", + "seedLibraryNoStockPlants": "Aucune plante disponible dans le stock", + "seedLibraryNotes": "Notes", + "seedLibraryOk": "OK", + "seedLibraryPlantationPeriod": "Période de plantation :", + "seedLibraryPlantationType": "Type de plantation :", + "seedLibraryPlantDetail": "Détail de la plante", + "seedLibraryPlantingDate": "Date de plantation", + "seedLibraryPlantingNow": "Je la plante maintenant", + "seedLibraryPrefix": "Préfixe", + "seedLibraryPrefixError": "Prefixe déjà utilisé", + "seedLibraryPrefixLengthError": "Le préfixe doit faire 3 caractères", + "seedLibraryPropagationMethod": "Méthode de propagation :", + "seedLibraryReference": "Référence :", + "seedLibraryRemovedPlant": "Plante supprimée", + "seedLibraryRemovingError": "Erreur lors de la suppression", + "seedLibraryResearch": "Recherche", + "seedLibrarySaveChanges": "Sauvegarder les modifications", + "seedLibrarySeason": "Saison :", + "seedLibrarySeed": "Graine", + "seedLibrarySeeds": "graines", + "seedLibrarySeedDeposit": "Dépôt de plantes", + "seedLibrarySeedLibrary": "Grainothèque", + "seedLibrarySeedQuantitySimple": "Quantité de graines", + "seedLibrarySeedQuantity": "Quantité de graines :", + "seedLibraryShowDeadPlants": "Afficher les plantes mortes", + "seedLibrarySpecies": "Espèce :", + "seedLibrarySpeciesHelp": "Aide sur l'espèce", + "seedLibrarySpeciesPlural": "Espèces", + "seedLibrarySpeciesSimple": "Espèce", + "seedLibrarySpeciesType": "Type d'espèce :", + "seedLibrarySpring": "Printemps", + "seedLibraryStartMonth": "Mois de début :", + "seedLibraryStock": "Stock disponible", + "seedLibrarySummer": "Été", + "seedLibraryStocks": "Stocks", + "seedLibraryTimeUntilMaturation": "Temps avant maturation :", + "seedLibraryType": "Type :", + "seedLibraryUnableToOpen": "Impossible d'ouvrir le lien", + "seedLibraryUpdate": "Modifier", + "seedLibraryUpdatedInformation": "Informations modifiées", + "seedLibraryUpdatedSpecies": "Espèce modifiée", + "seedLibraryUpdatedPlant": "Plante modifiée", + "seedLibraryUpdatingError": "Erreur lors de la modification", + "seedLibraryWinter": "Hiver", + "seedLibraryWriteReference": "Veuillez écrire la référence suivante : ", + "settingsAccount": "Compte", + "settingsAddProfilePicture": "Ajouter une photo", + "settingsAdmin": "Administrateur", + "settingsAskHelp": "Demander de l'aide", + "settingsAssociation": "Association", + "settingsBirthday": "Date de naissance", + "settingsBugs": "Bugs", + "settingsChangePassword": "Changer de mot de passe", + "settingsChangingPassword": "Voulez-vous vraiment changer votre mot de passe ?", + "settingsConfirmPassword": "Confirmer le mot de passe", + "settingsCopied": "Copié !", + "settingsDarkMode": "Mode sombre", + "settingsDarkModeOff": "Désactivé", + "settingsDeleteLogs": "Supprimer les logs ?", + "settingsDeleteNotificationLogs": "Supprimer les logs des notifications ?", + "settingsDetelePersonalData": "Supprimer mes données personnelles", + "settingsDetelePersonalDataDesc": "Cette action notifie l'administrateur que vous souhaitez supprimer vos données personnelles.", + "settingsDeleting": "Suppresion", + "settingsEdit": "Modifier", + "settingsEditAccount": "Modifier le compte", + "settingsEditPassword": "Modifier le mot de passe", + "settingsEmail": "Email", + "settingsEmptyField": "Ce champ ne peut pas être vide", + "settingsErrorProfilePicture": "Erreur lors de la modification de la photo de profil", + "settingsErrorSendingDemand": "Erreur lors de l'envoi de la demande", + "settingsEventsIcal": "Lien Ical des événements", + "settingsExpectingDate": "Date de naissance attendue", + "settingsFirstname": "Prénom", + "settingsFloor": "Étage", + "settingsHelp": "Aide", + "settingsIcalCopied": "Lien Ical copié !", + "settingsLanguage": "Langue", + "settingsLanguageFr": "Français", + "settingsLogs": "Logs", + "settingsModules": "Modules", + "settingsMyIcs": "Mon lien Ical", + "settingsName": "Nom", + "settingsNewPassword": "Nouveau mot de passe", + "settingsNickname": "Surnom", + "settingsNotifications": "Notifications", + "settingsOldPassword": "Ancien mot de passe", + "settingsPasswordChanged": "Mot de passe changé", + "settingsPasswordsNotMatch": "Les mots de passe ne correspondent pas", + "settingsPersonalData": "Données personnelles", + "settingsPersonalisation": "Personnalisation", + "settingsPhone": "Téléphone", + "settingsProfilePicture": "Photo de profil", + "settingsPromo": "Promotion", + "settingsRepportBug": "Signaler un bug", + "settingsSave": "Enregistrer", + "settingsSecurity": "Sécurité", + "settingsSendedDemand": "Demande envoyée", + "settingsSettings": "Paramètres", + "settingsTooHeavyProfilePicture": "L'image est trop lourde (max 4Mo)", + "settingsUpdatedProfile": "Profil modifié", + "settingsUpdatedProfilePicture": "Photo de profil modifiée", + "settingsUpdateNotification": "Mettre à jour les notifications", + "settingsUpdatingError": "Erreur lors de la modification du profil", + "settingsVersion": "Version", + "settingsPasswordStrength": "Force du mot de passe", + "settingsPasswordStrengthVeryWeak": "Très faible", + "settingsPasswordStrengthWeak": "Faible", + "settingsPasswordStrengthMedium": "Moyen", + "settingsPasswordStrengthStrong": "Fort", + "settingsPasswordStrengthVeryStrong": "Très fort", + "voteAdd": "Ajouter", + "voteAddMember": "Ajouter un membre", + "voteAddedPretendance": "Liste ajoutée", + "voteAddedSection": "Section ajoutée", + "voteAddingError": "Erreur lors de l'ajout", + "voteAddPretendance": "Ajouter une liste", + "voteAddSection": "Ajouter une section", + "voteAll": "Tous", + "voteAlreadyAddedMember": "Membre déjà ajouté", + "voteAlreadyVoted": "Vote enregistré", + "voteChooseList": "Choisir une liste", + "voteClear": "Réinitialiser", + "voteClearVotes": "Réinitialiser les votes", + "voteClosedVote": "Votes clos", + "voteCloseVote": "Fermer les votes", + "voteConfirmVote": "Confirmer le vote", + "voteCountVote": "Dépouiller les votes", + "voteDeletedAll": "Tout supprimé", + "voteDeletedPipo": "Listes pipos supprimées", + "voteDeletedSection": "Section supprimée", + "voteDeleteAll": "Supprimer tout", + "voteDeleteAllDescription": "Voulez-vous vraiment supprimer tout ?", + "voteDeletePipo": "Supprimer les listes pipos", + "voteDeletePipoDescription": "Voulez-vous vraiment supprimer les listes pipos ?", + "voteDeletePretendance": "Supprimer la liste", + "voteDeletePretendanceDesc": "Voulez-vous vraiment supprimer cette liste ?", + "voteDeleteSection": "Supprimer la section", + "voteDeleteSectionDescription": "Voulez-vous vraiment supprimer cette section ?", + "voteDeletingError": "Erreur lors de la suppression", + "voteDescription": "Description", + "voteEdit": "Modifier", + "voteEditedPretendance": "Liste modifiée", + "voteEditedSection": "Section modifiée", + "voteEditingError": "Erreur lors de la modification", + "voteErrorClosingVotes": "Erreur lors de la fermeture des votes", + "voteErrorCountingVotes": "Erreur lors du dépouillement des votes", + "voteErrorResetingVotes": "Erreur lors de la réinitialisation des votes", + "voteErrorOpeningVotes": "Erreur lors de l'ouverture des votes", + "voteIncorrectOrMissingFields": "Champs incorrects ou manquants", + "voteMembers": "Membres", + "voteName": "Nom", + "voteNoPretendanceList": "Aucune liste de prétendance", + "voteNoSection": "Aucune section", + "voteCanNotVote": "Vous ne pouvez pas voter", + "voteNoSectionList": "Aucune section", + "voteNotOpenedVote": "Vote non ouvert", + "voteOnGoingCount": "Dépouillement en cours", + "voteOpenVote": "Ouvrir les votes", + "votePipo": "Pipo", + "votePretendance": "Listes", + "votePretendanceDeleted": "Prétendance supprimée", + "votePretendanceNotDeleted": "Erreur lors de la suppression", + "voteProgram": "Programme", + "votePublish": "Publier", + "votePublishVoteDescription": "Voulez-vous vraiment publier les votes ?", + "voteResetedVotes": "Votes réinitialisés", + "voteResetVote": "Réinitialiser les votes", + "voteResetVoteDescription": "Que voulez-vous faire ?", + "voteRole": "Rôle", + "voteSectionDescription": "Description de la section", + "voteSection": "Section", + "voteSectionName": "Nom de la section", + "voteSeeMore": "Voir plus", + "voteSelected": "Sélectionné", + "voteShowVotes": "Voir les votes", + "voteVote": "Vote", + "voteVoteError": "Erreur lors de l'enregistrement du vote", + "voteVoteFor": "Voter pour ", + "voteVoteNotStarted": "Vote non ouvert", + "voteVoters": "Groupes votants", + "voteVoteSuccess": "Vote enregistré", + "voteVotes": "Voix", + "voteVotesClosed": "Votes clos", + "voteVotesCounted": "Votes dépouillés", + "voteVotesOpened": "Votes ouverts", + "voteWarning": "Attention", + "voteWarningMessage": "La sélection ne sera pas sauvegardée.\nVoulez-vous continuer ?", + "moduleAdvert": "Annonce", + "moduleAmap": "AMAP", + "moduleBooking": "Réservation", + "moduleCalendar": "Calendrier", + "moduleCentralisation": "Centralisation", + "moduleCinema": "Cinéma", + "moduleEvent": "Événement", + "moduleFlappyBird": "Flappy Bird", + "moduleLoan": "Prêt", + "modulePhonebook": "Annuaire", + "modulePurchases": "Achats", + "moduleRaffle": "Tombola", + "moduleRecommendation": "Bons plans", + "moduleSeedLibrary": "Grainothèque", + "moduleVote": "Vote", + "modulePh": "PH", + "moduleSettings": "Paramètres", + "moduleFeed": "Feed", + "moduleStyleGuide": "StyleGuide", + "moduleAdmin": "Adminitration", + "moduleOthers": "Autres", + "modulePayment": "Paiement", + "paiementTopUp": "Recharge", + "paiementStoreManagement": "Gestion des associations", + "paiementDeleteStore": "Supprimer l'association", + "paiementDeleteStoreDescription": "Voulez-vous vraiment supprimer cette association ?", + "paiementDeleteStoreError": "Impossible de supprimer l'association", + "paiementStoreDeleted": "Association supprimée", + "paiementAddThisDevice": "Ajouter cet appareil", + "paiementThisDevice": "(cet appareil)", + "paiementCancelled": "Annulé", + "paiementThe": "Le", + "paiementOf": "de", + "paiementRefundedThe": "Remboursé le", + "paiementAt": "à", + "paiementPleaseAcceptTOS": "Veuillez accepter les Conditions Générales d'Utilisation.", + "paiementAskDeviceActivation": "Demande d'activation de l'appareil", + "paiementDeviceActivationReceived": "La demande d'activation est prise en compte, veuilliez consulter votre boite mail pour finaliser la démarche", + "paiementRevokeDevice": "Révoquer l'appareil ?", + "paiementRevokeDeviceDescription": "Vous ne pourrez plus utiliser cet appareil pour les paiements", + "paiementDeviceRevoked": "Appareil révoqué", + "paiementDeviceRevokingError": "Erreur lors de la révocation de l'appareil", + "paiementPleaseAcceptPopup": "Veuillez autoriser les popups", + "paiementProceedSuccessfully": "Paiement effectué avec succès", + "paiementCancelledTransaction": "Paiement annulé", + "paiementPleaseEnterMinAmount": "Veuillez entrer un montant supérieur à 1", + "paiementMaxAmount": "Le montant maximum de votre portefeuille est de", + "paiementPayWithHA": "Payer avec HelloAsso", + "paiementBalanceAfterTopUp": "Solde après recharge :", + "paiementPersonalBalance": "Solde personnel", + "paiementDevices": "Appareils", + "paiementPay": "Payer", + "paiementDeviceNotRegistered": "Appareil non enregistré", + "paiementDeviceNotRegisteredDescription": "Votre appareil n'est pas encore enregistré. \nPour l'enregistrer, veuillez vous rendre sur la page des appareils.", + "paiementAccessPage": "Accéder à la page", + "paiementDeviceNotActivated": "Appareil non activé", + "paiementDeviceNotActivatedDescription": "Votre appareil n'est pas encore activé. \nPour l'activer, veuillez vous rendre sur la page des appareils.", + "paiementReactivateRevokedDeviceDescription": "Votre appareil a été révoqué. \nPour le réactiver, veuillez vous rendre sur la page des appareils.", + "paiementDeviceRecoveryError": "Erreur lors de la récupération de l'appareil", + "paiementStats": "Stats", + "paimentTopUpAction": "Recharger", + "paiementGetBalanceError": "Erreur lors de la récupération du solde : ", + "paiementLastTransactions": "Dernières transactions", + "paiementGetTransactionsError": "Erreur lors de la récupération des transactions : ", + "paiementStoreBalance": "Solde associatif", + "paiementScan": "Scanner", + "paiementManagement": "Gestion", + "paiementHistory": "Historique", + "paiementHandOver": "Passation", + "paiementStores": "Associations", + "paiementAdmin": "Administrateur", + "paiementSuccededTransaction": "Paiement réussi", + "paiementNewCGU": "Nouvelles Conditions Générales d'Utilisation", + "paiementDecline": "Refuser", + "paiementAccept": "Accepter", + "paiementAmount": "Montant", + "paiementValidUntil": "Valide jusqu'à", + "paiementClose": "Fermer", + "paiementPleaseEnterValidAmount": "Veuillez entrer un montant valide", + "paiementPleaseAuthenticate": "Veuillez vous authentifier", + "paiementAthenticationRequired": "Authentification requise pour payer", + "paiementNoThanks": "Non merci", + "paiementAuthentificationFailed": "Échec de l'authentification", + "paiementPleaseAddDevice": "Veuillez ajouter cet appareil pour payer", + "paiementPayment": "Paiement", + "paiementBalanceAfterTransaction": "Solde après paiement : ", + "paiementCancel": "Annuler", + "paiementLimitedTo": "Limité à", + "paiementScanCode": "Scanner un code", + "paiementNext": "Suivant", + "paiementCancelTransaction": "Annuler la transaction", + "paiementTransactionCancelled": "Transaction annulée", + "paiementTransactionCancelledDescription": "Voulez-vous vraiment annuler la transaction de", + "paiementTransactionCancelledError": "Erreur lors de l'annulation de la transaction", + "paiementNoMembership": "Aucune adhésion", + "paiementNoMembershipDescription": "Ce produit n'est pas disponnible pour les non-adhérents. Confirmer l'encaissement ?", + "paiementQRCodeAlreadyUsed": "QR Code déjà utilisé", + "paiementCameraPermissionRequired": "Permission d'accès à la caméra requise", + "paiementCameraPerssionRequiredDescription": "Pour scanner un QR Code, vous devez autoriser l'accès à la caméra.", + "paiementSettings": "Paramètres", + "paiementReceived": "Reçu", + "paiementSpent": "Déboursé", + "paiementNoTrasactionForThisMonth": "Aucune transaction pour ce mois", + "paiementNoTransactinon": "Aucune transaction", + "paiementSellerRigths": "Droits du vendeur", + "paiementCanBank": "Peut encaisser", + "paiementCanSeeHistory": "Peut voir l'historique", + "paiementCanCancelTransaction": "Peut annuler des transactions", + "paiementCanManageSellers": "Peut gérer les vendeurs", + "paiementAddedSeller": "Vendeur ajouté", + "paiementAddingSellerError": "Erreur lors de l'ajout du vendeur", + "paiementBank": "Encaisser", + "paiementSeeHistory": "Voir l'historique", + "paiementCancelTransactions": "Annuler les transactions", + "paiementManageSellers": "Gérer les vendeurs", + "paiementStructureAdmin": "Administrateur de la structure", + "paiementRightsOf": "Droits de", + "paiementRightsUpdated": "Droits mis à jour", + "paiementRightsUpdateError": "Erreur lors de la mise à jour des droits", + "paiementDeleteSellerDescription": "Voulez-vous vraiment supprimer ce vendeur ?", + "paiementDeletedSeller": "Vendeur supprimé", + "paiementDeletingSellerError": "Erreur lors de la suppression du vendeur", + "paiementDeleteSeller": "Supprimer le vendeur", + "paiementAdd": "Ajouter", + "paiementAddSeller": "Ajouter un vendeur", + "paiementSellerError": "Vous n'êtes pas vendeur de cette association", + "paiementSellersOf": "Les vendeurs de", + "paiementModify": "Modifier", + "paiementAStore": "une association", + "paiementStoreName": "Nom de l'association", + "paiementSuccessfullyAddedStore": "Association ajoutée avec succès", + "paiementSuccessfullyModifiedStore": "Association modifiée avec succès", + "paiementAddingStoreError": "Erreur lors de l'ajout de l'association", + "paiementModifyingStoreError": "Erreur lors de la modification de l'association", + "paiementRefund": "Remboursement", + "paiementDoneTransaction": "Transaction effectuée", + "paiementRefundAction": "Rembourser", + "paiementTotalDuringPeriod": "Total sur la période", + "paiementMean": "Moyenne : ", + "paiementTransaction": "ransaction", + "paiementTransferStructure": "Transfert de structure", + "paiementYouAreTransferingStructureTo": "Vous êtes sur le point de transférer la structure à ", + "paiementTransferStructureDescription": "Le nouveau responsable aura accès à toutes les fonctionnalités de gestion de la structure. Vous allez recevoir un email pour valider ce transfert. Le lien ne sera actif que pendant 20 minutes. Cette action est irréversible. Êtes-vous sûr de vouloir continuer ?", + "paiementTransferStructureError": "Erreur lors du transfert de la structure", + "paiementTransferStructureSuccess": "Transfert de structure demandé avec succès", + "paiementNextAccountable": "Prochain responsable" } From 37b2b4fe91d5a551272422252c5c0e61ff2f7636 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+foucblg@users.noreply.github.com> Date: Sun, 10 Aug 2025 16:25:24 +0200 Subject: [PATCH 236/473] Delete announcers confirmation (#24) * feat : delete announcer confirmation * Rename module * better logic * translations indent --------- Co-authored-by: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> --- .../announcers/load_switch_announcers.dart | 28 +- lib/admin/ui/pages/main_page/main_page.dart | 11 +- lib/l10n/app_en.arb | 1178 +++++++++-------- lib/l10n/app_fr.arb | 4 +- lib/l10n/app_localizations.dart | 14 +- lib/l10n/app_localizations_en.dart | 9 +- lib/l10n/app_localizations_fr.dart | 9 +- 7 files changed, 654 insertions(+), 599 deletions(-) diff --git a/lib/admin/ui/pages/announcers/load_switch_announcers.dart b/lib/admin/ui/pages/announcers/load_switch_announcers.dart index e72f6e589e..17a6ff2560 100644 --- a/lib/admin/ui/pages/announcers/load_switch_announcers.dart +++ b/lib/admin/ui/pages/announcers/load_switch_announcers.dart @@ -4,7 +4,10 @@ import 'package:load_switch/load_switch.dart'; import 'package:titan/admin/class/simple_group.dart'; import 'package:titan/advert/class/announcer.dart'; import 'package:titan/advert/providers/announcer_list_provider.dart'; +import 'package:titan/l10n/app_localizations.dart'; +import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; +import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; class LoadSwitchAdvertisers extends ConsumerWidget { const LoadSwitchAdvertisers({super.key, required this.group}); @@ -13,8 +16,8 @@ class LoadSwitchAdvertisers extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final announcerListNotifier = ref.watch(announcerListProvider.notifier); - final announcerList = ref.watch(announcerListProvider); + final localizeWithContext = AppLocalizations.of(context)!; return AsyncChild( value: announcerList, @@ -29,8 +32,27 @@ class LoadSwitchAdvertisers extends ConsumerWidget { value: isAnnouncer, future: isAnnouncer ? () async { - await announcerListNotifier.deleteAnnouncer(annoncer!); - return false; + return await showDialog( + context: context, + builder: (context) { + return CustomDialogBox( + title: localizeWithContext.adminDeleteAnnouncer, + descriptions: + localizeWithContext.adminDeleteAnnouncerDescription, + onYes: () { + tokenExpireWrapper(ref, () async { + final value = await announcerListNotifier + .deleteAnnouncer(annoncer!); + if (value) { + announcerListNotifier.loadAllAnnouncerList(); + return false; + } + return true; + }); + }, + ); + }, + ); } : () async { await announcerListNotifier.addAnnouncer( diff --git a/lib/admin/ui/pages/main_page/main_page.dart b/lib/admin/ui/pages/main_page/main_page.dart index 8e2e9cd8fe..24e5e9affb 100644 --- a/lib/admin/ui/pages/main_page/main_page.dart +++ b/lib/admin/ui/pages/main_page/main_page.dart @@ -83,16 +83,19 @@ class AdminMainPage extends HookConsumerWidget { onTap: () => QR.to(AdminRouter.root + AdminRouter.associationMemberships), ), - Text("Annonces", style: Theme.of(context).textTheme.titleLarge), + Text( + localizeWithContext.adminAdverts, + style: Theme.of(context).textTheme.titleLarge, + ), ListItem( - title: "Annonceurs", - subtitle: "Gérer les annonceurs", + title: localizeWithContext.adminAnnouncers, + subtitle: localizeWithContext.adminManageAnnouncers, onTap: () async { await showCustomBottomModal( context: context, ref: ref, modal: BottomModalTemplate( - title: "Annonceurs", + title: localizeWithContext.adminAnnouncers, child: ConstrainedBox( constraints: const BoxConstraints(maxHeight: 500), child: SingleChildScrollView( diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 80a3ef892e..82d83911b5 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1,4 +1,4 @@ - { +{ "@@locale": "en", "adminAccountTypes": "Account types", "adminAdd": "Add", @@ -112,6 +112,8 @@ "adminAdverts": "Adverts", "adminAnnouncers": "Announcers", "adminManageAnnouncers" : "Manage announcers", + "adminDeleteAnnouncer" : "Delete announcer?", + "adminDeleteAnnouncerDescription": "Are you sure you want to delete this announcer? All their adverts will be deleted.", "advertAdd": "Add", "advertAddedAdvert": "Advert published", "advertAddedAnnouncer": "Announcer added", @@ -733,590 +735,590 @@ "phonebookNickname": "Nickname:", "phonebookNicknameCopied": "Nickname copied to clipboard", "phonebookNoAssociationFound": "No association found", - "phonebookNoMember": "No member", - "phonebookNoMemberRole": "No role found", - "phonebookPhone": "Phone:", - "phonebookPhonebook": "Phonebook", - "phonebookPhonebookSearch": "Search", - "phonebookPhonebookSearchAssociation": "Association", - "phonebookPhonebookSearchField": "Search:", - "phonebookPhonebookSearchName": "Last name/First name/Nickname", - "phonebookPhonebookSearchRole": "Position", - "phonebookPresidentRoleTag": "Prez'", - "phonebookPromoNotGiven": "Promotion not provided", - "phonebookPromotion": "Promotion:", - "phonebookReorderingError": "Error during reordering", - "phonebookResearch": "Search", - "phonebookRolePure": "Role", - "phonebookTooHeavyAssociationPicture": "Image is too large (max 4MB)", - "phonebookUpdateGroups": "Update groups", - "phonebookUpdatedAssociation": "Association updated", - "phonebookUpdatedAssociationPicture": "Association picture has been changed", - "phonebookUpdatedGroups": "Groups updated", - "phonebookUpdatedMember": "Member updated", - "phonebookUpdatingError": "Error during update", - "phonebookValidation": "Validate", - "purchasesPurchases": "Purchases", - "purchasesResearch": "Search", - "purchasesNoPurchasesFound": "No purchases found", - "purchasesNoTickets": "No tickets", - "purchasesTicketsError": "Error loading tickets", - "purchasesPurchasesError": "Error loading purchases", - "purchasesNoPurchases": "No purchase", - "purchasesTimes": "times", - "purchasesAlreadyUsed": "Already used", - "purchasesNotPaid": "Not validated", - "purchasesPleaseSelectProduct": "Please select a product", - "purchasesProducts": "Products", - "purchasesCancel": "Cancel", - "purchasesValidate": "Validate", - "purchasesLeftScan": "Scans remaining", - "purchasesTag": "Tag", - "purchasesHistory": "History", - "purchasesPleaseSelectSeller": "Please select a seller", - "purchasesNoTagGiven": "Warning, no tag entered", - "purchasesTickets": "Tickets", - "purchasesNoScannableProducts": "No scannable products", - "purchasesLoading": "Waiting for scan", - "purchasesScan": "Scan", - "raffleRaffle": "Raffle", - "rafflePrize": "Prize", - "rafflePrizes": "Prizes", - "raffleActualRaffles": "Current raffles", - "rafflePastRaffles": "Past raffles", - "raffleYourTickets": "All your tickets", - "raffleCreateMenu": "Creation menu", - "raffleNextRaffles": "Upcoming raffles", - "raffleNoTicket": "You have no ticket", - "raffleSeeRaffleDetail": "View prizes/tickets", - "raffleActualPrize": "Current prizes", - "raffleMajorPrize": "Major prizes", - "raffleTakeTickets": "Take your tickets", - "raffleNoTicketBuyable": "You cannot buy tickets right now", - "raffleNoCurrentPrize": "There are no prizes currently", - "raffleModifTombola": "You can modify your raffles or create new ones, all decisions must then be approved by admins", - "raffleCreateYourRaffle": "Your raffle creation menu", - "rafflePossiblePrice": "Possible prize", - "raffleInformation": "Information and statistics", - "raffleAccounts": "Accounts", - "raffleAdd": "Add", - "raffleUpdatedAmount": "Amount updated", - "raffleUpdatingError": "Error during update", - "raffleDeletedPrize": "Prize deleted", - "raffleDeletingError": "Error during deletion", - "raffleQuantity": "Quantity", - "raffleClose": "Close", - "raffleOpen": "Open", - "raffleAddTypeTicketSimple": "Add", - "raffleAddingError": "Error during addition", - "raffleEditTypeTicketSimple": "Edit", - "raffleFillField": "Field cannot be empty", - "raffleWaiting": "Loading", - "raffleEditingError": "Error during editing", - "raffleAddedTicket": "Ticket added", - "raffleEditedTicket": "Ticket edited", - "raffleAlreadyExistTicket": "Ticket already exists", - "raffleNumberExpected": "An integer is expected", - "raffleDeletedTicket": "Ticket deleted", - "raffleAddPrize": "Add", - "raffleEditPrize": "Edit", - "raffleOpenRaffle": "Open raffle", - "raffleCloseRaffle": "Close raffle", - "raffleOpenRaffleDescription": "You are going to open the raffle, users will be able to buy tickets. You will no longer be able to modify the raffle. Are you sure you want to continue?", - "raffleCloseRaffleDescription": "You are going to close the raffle, users will no longer be able to buy tickets. Are you sure you want to continue?", - "raffleNoCurrentRaffle": "There is no ongoing raffle", - "raffleBoughtTicket": "Ticket purchased", - "raffleDrawingError": "Error during drawing", - "raffleInvalidPrice": "Price must be greater than 0", - "raffleMustBePositive": "Number must be strictly positive", - "raffleDraw": "Draw", - "raffleDrawn": "Drawn", - "raffleError": "Error", - "raffleGathered": "Collected", - "raffleTickets": "Tickets", - "raffleTicket": "ticket", - "raffleWinner": "Winner", - "raffleNoPrize": "No prize", - "raffleDeletePrize": "Delete prize", - "raffleDeletePrizeDescription": "You are going to delete the prize, are you sure you want to continue?", - "raffleDrawing": "Drawing", - "raffleDrawingDescription": "Draw the prize winner?", - "raffleDeleteTicket": "Delete ticket", - "raffleDeleteTicketDescription": "You are going to delete the ticket, are you sure you want to continue?", - "raffleWinningTickets": "Winning tickets", - "raffleNoWinningTicketYet": "Winning tickets will be displayed here", - "raffleName": "Name", - "raffleDescription": "Description", - "raffleBuyThisTicket": "Buy this ticket", - "raffleLockedRaffle": "Locked raffle", - "raffleUnavailableRaffle": "Unavailable raffle", - "raffleNotEnoughMoney": "You don't have enough money", - "raffleWinnable": "winnable", - "raffleNoDescription": "No description", - "raffleAmount": "Balance", - "raffleLoading": "Loading", - "raffleTicketNumber": "Number of tickets", - "rafflePrice": "Price", - "raffleEditRaffle": "Edit raffle", - "raffleEdit": "Edit", - "raffleAddPackTicket": "Add ticket pack", - "recommendationRecommendation": "Recommendation", - "recommendationTitle": "Title", - "recommendationLogo": "Logo", - "recommendationCode": "Code", - "recommendationSummary": "Short summary", - "recommendationDescription": "Description", - "recommendationAdd": "Add", - "recommendationEdit": "Edit", - "recommendationDelete": "Delete", - "recommendationAddImage": "Please add an image", - "recommendationAddedRecommendation": "Deal added", - "recommendationEditedRecommendation": "Deal updated", - "recommendationDeleteRecommendationConfirmation": "Are you sure you want to delete this deal?", - "recommendationDeleteRecommendation": "Delete", - "recommendationDeletingRecommendationError": "Error during deletion", - "recommendationDeletedRecommendation": "Deal deleted", - "recommendationIncorrectOrMissingFields": "Incorrect or missing fields", - "recommendationEditingError": "Edit failed", - "recommendationAddingError": "Add failed", - "recommendationCopiedCode": "Discount code copied", - "seedLibraryAdd": "Add", - "seedLibraryAddedPlant": "Plant added", - "seedLibraryAddedSpecies": "Species added", - "seedLibraryAddingError": "Error during addition", - "seedLibraryAddPlant": "Deposit a plant", - "seedLibraryAddSpecies": "Add a species", - "seedLibraryAll": "All", - "seedLibraryAncestor": "Ancestor", - "seedLibraryAround": "around", - "seedLibraryAutumn": "Autumn", - "seedLibraryBorrowedPlant": "Borrowed plant", - "seedLibraryBorrowingDate": "Borrowing date:", - "seedLibraryBorrowPlant": "Borrow plant", - "seedLibraryCard": "Card", - "seedLibraryChoosingAncestor": "Please choose an ancestor", - "seedLibraryChoosingSpecies": "Please choose a species", - "seedLibraryChoosingSpeciesOrAncestor": "Please choose a species or an ancestor", - "seedLibraryContact": "Contact:", - "seedLibraryDays": "days", - "seedLibraryDeadMsg": "Do you want to declare the plant dead?", - "seedLibraryDeadPlant": "Dead plant", - "seedLibraryDeathDate": "Date of death", - "seedLibraryDeletedSpecies": "Species deleted", - "seedLibraryDeleteSpecies": "Delete species?", - "seedLibraryDeleting": "Deleting", - "seedLibraryDeletingError": "Error during deletion", - "seedLibraryDepositNotAvailable": "Plant deposit is not possible without borrowing a plant first", - "seedLibraryDescription": "Description", - "seedLibraryDifficulty": "Difficulty:", - "seedLibraryEdit": "Edit", - "seedLibraryEditedPlant": "Plant updated", - "seedLibraryEditInformation": "Edit information", - "seedLibraryEditingError": "Error during editing", - "seedLibraryEditSpecies": "Edit species", - "seedLibraryEmptyDifficultyError": "Please choose a difficulty", - "seedLibraryEmptyFieldError": "Please fill all fields", - "seedLibraryEmptyTypeError": "Please choose a plant type", - "seedLibraryEndMonth": "End month:", - "seedLibraryFacebookUrl": "Facebook link", - "seedLibraryFilters": "Filters", - "seedLibraryForum": "Oskour mom I killed my plant - Help forum", - "seedLibraryForumUrl": "Forum link", - "seedLibraryHelpSheets": "Plant sheets", - "seedLibraryInformation": "Information:", - "seedLibraryMaturationTime": "Maturation time", - "seedLibraryMonthJan": "January", - "seedLibraryMonthFeb": "February", - "seedLibraryMonthMar": "March", - "seedLibraryMonthApr": "April", - "seedLibraryMonthMay": "May", - "seedLibraryMonthJun": "June", - "seedLibraryMonthJul": "July", - "seedLibraryMonthAug": "August", - "seedLibraryMonthSep": "September", - "seedLibraryMonthOct": "October", - "seedLibraryMonthNov": "November", - "seedLibraryMonthDec": "December", - "seedLibraryMyPlants": "My plants", - "seedLibraryName": "Name", - "seedLibraryNbSeedsRecommended": "Number of seeds recommended", - "seedLibraryNbSeedsRecommendedError": "Please enter a recommended seed number greater than 0", - "seedLibraryNoDateError": "Please enter a date", - "seedLibraryNoFilteredPlants": "No plants match your search. Try other filters.", - "seedLibraryNoMorePlant": "No plants available", - "seedLibraryNoPersonalPlants": "You don't have any plants yet in your seed library. You can add some in the stocks.", - "seedLibraryNoSpecies": "No species found", - "seedLibraryNoStockPlants": "No plants available in stock", - "seedLibraryNotes": "Notes", - "seedLibraryOk": "OK", - "seedLibraryPlantationPeriod": "Planting period:", - "seedLibraryPlantationType": "Plantation type:", - "seedLibraryPlantDetail": "Plant details", - "seedLibraryPlantingDate": "Planting date", - "seedLibraryPlantingNow": "I'm planting it now", - "seedLibraryPrefix": "Prefix", - "seedLibraryPrefixError": "Prefix already used", - "seedLibraryPrefixLengthError": "The prefix must be 3 characters", - "seedLibraryPropagationMethod": "Propagation method:", - "seedLibraryReference": "Reference:", - "seedLibraryRemovedPlant": "Plant removed", - "seedLibraryRemovingError": "Error removing plant", - "seedLibraryResearch": "Search", - "seedLibrarySaveChanges": "Save changes", - "seedLibrarySeason": "Season:", - "seedLibrarySeed": "Seed", - "seedLibrarySeeds": "seeds", - "seedLibrarySeedDeposit": "Plant deposit", - "seedLibrarySeedLibrary": "Seed library", - "seedLibrarySeedQuantitySimple": "Seed quantity", - "seedLibrarySeedQuantity": "Seed quantity:", - "seedLibraryShowDeadPlants": "Show dead plants", - "seedLibrarySpecies": "Species:", - "seedLibrarySpeciesHelp": "Help on species", - "seedLibrarySpeciesPlural": "Species", - "seedLibrarySpeciesSimple": "Species", - "seedLibrarySpeciesType": "Species type:", - "seedLibrarySpring": "Spring", - "seedLibraryStartMonth": "Start month:", - "seedLibraryStock": "Available stock", - "seedLibrarySummer": "Summer", - "seedLibraryStocks": "Stocks", - "seedLibraryTimeUntilMaturation": "Time until maturation:", - "seedLibraryType": "Type:", - "seedLibraryUnableToOpen": "Unable to open link", - "seedLibraryUpdate": "Edit", - "seedLibraryUpdatedInformation": "Information updated", - "seedLibraryUpdatedSpecies": "Species updated", - "seedLibraryUpdatedPlant": "Plant updated", - "seedLibraryUpdatingError": "Error updating", - "seedLibraryWinter": "Winter", - "seedLibraryWriteReference": "Please write the following reference: ", - "settingsAccount": "Account", - "settingsAddProfilePicture": "Add a photo", - "settingsAdmin": "Administrator", - "settingsAskHelp": "Ask for help", - "settingsAssociation": "Association", - "settingsBirthday": "Birthday", - "settingsBugs": "Bugs", - "settingsChangePassword": "Change password", - "settingsChangingPassword": "Do you really want to change your password?", - "settingsConfirmPassword": "Confirm password", - "settingsCopied": "Copied!", - "settingsDarkMode": "Dark mode", - "settingsDarkModeOff": "Off", - "settingsDeleteLogs": "Delete logs?", - "settingsDeleteNotificationLogs": "Delete notification logs?", - "settingsDetelePersonalData": "Delete my personal data", - "settingsDetelePersonalDataDesc": "This action notifies the administrator that you want to delete your personal data.", - "settingsDeleting": "Deleting", - "settingsEdit": "Edit", - "settingsEditAccount": "Edit account", - "settingsEditPassword": "Edit password", - "settingsEmail": "Email", - "settingsEmptyField": "This field cannot be empty", - "settingsErrorProfilePicture": "Error editing profile picture", - "settingsErrorSendingDemand": "Error sending request", - "settingsEventsIcal": "Ical link for events", - "settingsExpectingDate": "Expected birth date", - "settingsFirstname": "First name", - "settingsFloor": "Floor", - "settingsHelp": "Help", - "settingsIcalCopied": "Ical link copied!", - "settingsLanguage": "Language", - "settingsLanguageVar": "English 🇬🇧", - "settingsLogs": "Logs", - "settingsModules": "Modules", - "settingsMyIcs": "My Ical link", - "settingsName": "Last name", - "settingsNewPassword": "New password", - "settingsNickname": "Nickname", - "settingsNotifications": "Notifications", - "settingsOldPassword": "Old password", - "settingsPasswordChanged": "Password changed", - "settingsPasswordsNotMatch": "Passwords do not match", - "settingsPersonalData": "Personal data", - "settingsPersonalisation": "Personalization", - "settingsPhone": "Phone", - "settingsProfilePicture": "Profile picture", - "settingsPromo": "Promotion", - "settingsRepportBug": "Report a bug", - "settingsSave": "Save", - "settingsSecurity": "Security", - "settingsSendedDemand": "Request sent", - "settingsSettings": "Settings", - "settingsTooHeavyProfilePicture": "Image is too large (max 4MB)", - "settingsUpdatedProfile": "Profile updated", - "settingsUpdatedProfilePicture": "Profile picture updated", - "settingsUpdateNotification": "Update notifications", - "settingsUpdatingError": "Error updating profile", - "settingsVersion": "Version", - "settingsPasswordStrength": "Password strength", - "settingsPasswordStrengthVeryWeak": "Very weak", - "settingsPasswordStrengthWeak": "Weak", - "settingsPasswordStrengthMedium": "Medium", - "settingsPasswordStrengthStrong": "Strong", - "settingsPasswordStrengthVeryStrong": "Very strong", - "settingsPhoneNumber": "Phone number", - "settingsValidate": "Confirm", - "settingsEditedAccount": "Account edited", - "settingsFailedToEditAccount": "Failed to edit account", - "settingsChooseLanguage": "Choose a language", - "settingsNotificationCounter": "{active}/{total} active {active, plural, zero {notification} one {notification} other {notifications}}", - "@settingsNotificationCounter": { - "description": "Affiche le nombre de notifications actives sur le total des notifications disponibles, avec gestion du pluriel", - "placeholders": { - "active": { "type": "int" }, - "total": { "type": "int" } - } - }, - "settingsEvent" : "Event", - "settingsIcal": "Ical link", - "settingsSynncWithCalendar": "Sync with calendar", - "settingsIcalLinkCopied": "Ical link copied", - "settingsProfile": "Profile", - "voteAdd": "Add", - "voteAddMember": "Add a member", - "voteAddedPretendance": "List added", - "voteAddedSection": "Section added", - "voteAddingError": "Error adding", - "voteAddPretendance": "Add a list", - "voteAddSection": "Add a section", - "voteAll": "All", - "voteAlreadyAddedMember": "Member already added", - "voteAlreadyVoted": "Vote recorded", - "voteChooseList": "Choose a list", - "voteClear": "Reset", - "voteClearVotes": "Reset votes", - "voteClosedVote": "Votes closed", - "voteCloseVote": "Close votes", - "voteConfirmVote": "Confirm vote", - "voteCountVote": "Count votes", - "voteDeletedAll": "All deleted", - "voteDeletedPipo": "Fake lists deleted", - "voteDeletedSection": "Section deleted", - "voteDeleteAll": "Delete all", - "voteDeleteAllDescription": "Do you really want to delete everything?", - "voteDeletePipo": "Delete fake lists", - "voteDeletePipoDescription": "Do you really want to delete the fake lists?", - "voteDeletePretendance": "Delete the list", - "voteDeletePretendanceDesc": "Do you really want to delete this list?", - "voteDeleteSection": "Delete the section", - "voteDeleteSectionDescription": "Do you really want to delete this section?", - "voteDeletingError": "Error deleting", - "voteDescription": "Description", - "voteEdit": "Edit", - "voteEditedPretendance": "List edited", - "voteEditedSection": "Section edited", - "voteEditingError": "Error editing", - "voteErrorClosingVotes": "Error closing votes", - "voteErrorCountingVotes": "Error counting votes", - "voteErrorResetingVotes": "Error resetting votes", - "voteErrorOpeningVotes": "Error opening votes", - "voteIncorrectOrMissingFields": "Incorrect or missing fields", - "voteMembers": "Members", - "voteName": "Name", - "voteNoPretendanceList": "No list of candidates", - "voteNoSection": "No section", - "voteCanNotVote": "You cannot vote", - "voteNoSectionList": "No section", - "voteNotOpenedVote": "Vote not opened", - "voteOnGoingCount": "Counting in progress", - "voteOpenVote": "Open votes", - "votePipo": "Fake", - "votePretendance": "Lists", - "votePretendanceDeleted": "Candidate list deleted", - "votePretendanceNotDeleted": "Error deleting", - "voteProgram": "Program", - "votePublish": "Publish", - "votePublishVoteDescription": "Do you really want to publish the votes?", - "voteResetedVotes": "Votes reset", - "voteResetVote": "Reset votes", - "voteResetVoteDescription": "What do you want to do?", - "voteRole": "Role", - "voteSectionDescription": "Section description", - "voteSection": "Section", - "voteSectionName": "Section name", - "voteSeeMore": "See more", - "voteSelected": "Selected", - "voteShowVotes": "Show votes", - "voteVote": "Vote", - "voteVoteError": "Error recording vote", - "voteVoteFor": "Vote for ", - "voteVoteNotStarted": "Vote not opened", - "voteVoters": "Voting groups", - "voteVoteSuccess": "Vote recorded", - "voteVotes": "Votes", - "voteVotesClosed": "Votes closed", - "voteVotesCounted": "Votes counted", - "voteVotesOpened": "Votes opened", - "voteWarning": "Warning", - "voteWarningMessage": "Selection will not be saved.\nDo you want to continue?", - "moduleAdvert": "Advert", - "moduleAmap": "AMAP", - "moduleBooking": "Booking", - "moduleCalendar": "Calendar", - "moduleCentralisation": "Centralisation", - "moduleCinema": "Cinema", - "moduleEvent": "Event", - "moduleFlappyBird": "Flappy Bird", - "moduleLoan": "Loan", - "modulePhonebook": "Phonebook", - "modulePurchases": "Purchases", - "moduleRaffle": "Raffle", - "moduleRecommendation": "Recommendation", - "moduleSeedLibrary": "Seed Library", - "moduleVote": "Vote", - "modulePh": "PH", - "moduleSettings": "Settings", - "moduleFeed": "Feed", - "moduleStyleGuide": "StyleGuide", - "moduleAdmin": "Administration", - "moduleOthers": "Others", - "modulePayment": "Payment", - "moduleAdvertDescription": "View the latest adverts", - "moduleAmapDescription": "Order your AMAP basket", - "moduleBookingDescription": "Book a room", - "moduleCalendarDescription": "View the calendar of events", - "moduleCentralisationDescription": "Viw all links", - "moduleCinemaDescription": "View the cinema schedule", - "moduleEventDescription": "View events", - "moduleFlappyBirdDescription": "Play Flappy Bird", - "moduleLoanDescription": "See your loans", - "modulePhonebookDescription": "View the phonebook", - "modulePurchasesDescription": "View your purchases", - "moduleRaffleDescription": "View the raffle", - "moduleRecommendationDescription": "View the recommendations", - "moduleSeedLibraryDescription": "View the seed library", - "moduleVoteDescription": "Vote for the campaigns", - "modulePhDescription": "View the PH", - "moduleSettingsDescription": "Manage your settings", - "moduleFeedDescription": "View the latest news", - "moduleStyleGuideDescription": "Style guide for developers", - "moduleAdminDescription": "Administration module for administrators", - "moduleOthersDescription": "Other modules", - "modulePaymentDescription": "Pay and see your transactions", - "paiementTopUp": "Top-up", - "paiementStoreManagement": "Association management", - "paiementDeleteStore": "Delete association", - "paiementDeleteStoreDescription": "Are you sure you want to delete this association?", - "paiementDeleteStoreError": "Unable to delete the association", - "paiementStoreDeleted": "Association deleted", - "paiementAddThisDevice": "Add this device", - "paiementThisDevice": "(this device)", - "paiementCancelled": "Cancelled", - "paiementThe": "The", - "paiementOf": "of", - "paiementRefundedThe": "Refunded on", - "paiementAt": "at", - "paiementPleaseAcceptTOS": "Please accept the Terms of Service.", - "paiementAskDeviceActivation": "Device activation request", - "paiementDeviceActivationReceived": "The activation request has been received, please check your email to finalize the process", - "paiementRevokeDevice": "Revoke device?", - "paiementRevokeDeviceDescription": "You will no longer be able to use this device for payments", - "paiementDeviceRevoked": "Device revoked", - "paiementDeviceRevokingError": "Error while revoking device", - "paiementPleaseAcceptPopup": "Please allow popups", - "paiementProceedSuccessfully": "Payment completed successfully", - "paiementCancelledTransaction": "Payment cancelled", - "paiementPleaseEnterMinAmount": "Please enter an amount greater than 1", - "paiementMaxAmount": "The maximum wallet amount is", - "paiementPayWithHA": "Pay with HelloAsso", - "paiementBalanceAfterTopUp": "Balance after top-up:", - "paiementPersonalBalance": "Personal balance", - "paiementDevices": "Devices", - "paiementPay": "Pay", - "paiementDeviceNotRegistered": "Device not registered", - "paiementDeviceNotRegisteredDescription": "Your device is not registered yet. \nTo register it, please go to the devices page.", - "paiementAccessPage": "Access the page", - "paiementDeviceNotActivated": "Device not activated", - "paiementDeviceNotActivatedDescription": "Your device is not yet activated. \nTo activate it, please go to the devices page.", - "paiementReactivateRevokedDeviceDescription": "Your device has been revoked. \nTo reactivate it, please go to the devices page.", - "paiementDeviceRecoveryError": "Error while retrieving device", - "paiementStats": "Stats", - "paimentTopUpAction": "Top-up", - "paiementGetBalanceError": "Error while retrieving balance: ", - "paiementLastTransactions": "Latest transactions", - "paiementGetTransactionsError": "Error while retrieving transactions: ", - "paiementStoreBalance": "Association balance", - "paiementScan": "Scan", - "paiementManagement": "Management", - "paiementHistory": "History", - "paiementHandOver": "Handover", - "paiementStores": "Associations", - "paiementAdmin": "Administrator", - "paiementSuccededTransaction": "Successful payment", - "paiementNewCGU": "New Terms of Service", - "paiementDecline": "Decline", - "paiementAccept": "Accept", - "paiementAmount": "Amount", - "paiementValidUntil": "Valid until", - "paiementClose": "Close", - "paiementPleaseEnterValidAmount": "Please enter a valid amount", - "paiementPleaseAuthenticate": "Please authenticate", - "paiementAthenticationRequired": "Authentication required to pay", - "paiementNoThanks": "No thanks", - "paiementAuthentificationFailed": "Authentication failed", - "paiementPleaseAddDevice": "Please add this device to pay", - "paiementPayment": "Payment", - "paiementBalanceAfterTransaction": "Balance after payment: ", - "paiementCancel": "Cancel", - "paiementLimitedTo": "Limited to", - "paiementScanCode": "Scan a code", - "paiementNext": "Next", - "paiementCancelTransaction": "Cancel transaction", - "paiementTransactionCancelled": "Transaction cancelled", - "paiementTransactionCancelledDescription": "Are you sure you want to cancel the transaction of", - "paiementTransactionCancelledError": "Error while cancelling the transaction", - "paiementNoMembership": "No membership", - "paiementNoMembershipDescription": "This product is not available to non-members. Confirm the payment?", - "paiementQRCodeAlreadyUsed": "QR Code already used", - "paiementCameraPermissionRequired": "Camera permission required", - "paiementCameraPerssionRequiredDescription": "To scan a QR Code, you must allow camera access.", - "paiementSettings": "Settings", - "paiementReceived": "Received", - "paiementSpent": "Spent", - "paiementNoTrasactionForThisMonth": "No transactions for this month", - "paiementNoTransactinon": "No transaction", - "paiementSellerRigths": "Seller rights", - "paiementCanBank": "Can collect payments", - "paiementCanSeeHistory": "Can view history", - "paiementCanCancelTransaction": "Can cancel transactions", - "paiementCanManageSellers": "Can manage sellers", - "paiementAddedSeller": "Seller added", - "paiementAddingSellerError": "Error while adding seller", - "paiementBank": "Collect", - "paiementSeeHistory": "View history", - "paiementCancelTransactions": "Cancel transactions", - "paiementManageSellers": "Manage sellers", - "paiementStructureAdmin": "Structure administrator", - "paiementRightsOf": "Rights of", - "paiementRightsUpdated": "Rights updated", - "paiementRightsUpdateError": "Error while updating rights", - "paiementDeleteSellerDescription": "Are you sure you want to delete this seller?", - "paiementDeletedSeller": "Seller deleted", - "paiementDeletingSellerError": "Error while deleting seller", - "paiementDeleteSeller": "Delete seller", - "paiementAdd": "Add", - "paiementAddSeller": "Add seller", - "paiementSellerError": "You are not a seller of this association", - "paiementSellersOf": "Sellers of", - "paiementModify": "Edit", - "paiementAStore": "an association", - "paiementStoreName": "Association name", - "paiementSuccessfullyAddedStore": "Association successfully added", - "paiementSuccessfullyModifiedStore": "Association successfully updated", - "paiementAddingStoreError": "Error while adding the association", - "paiementModifyingStoreError": "Error while updating the association", - "paiementRefund": "Refund", - "paiementDoneTransaction": "Transaction completed", - "paiementRefundAction": "Refund", - "paiementTotalDuringPeriod": "Total during the period", - "paiementMean": "Average: ", - "paiementTransaction": "Transaction", - "paiementTransferStructure": "Structure transfer", - "paiementYouAreTransferingStructureTo": "You are about to transfer the structure to ", - "paiementTransferStructureDescription": "The new manager will have access to all structure management features. You will receive an email to confirm this transfer. The link will only be active for 20 minutes. This action is irreversible. Are you sure you want to continue?", - "paiementTransferStructureError": "Error while transferring structure", - "paiementTransferStructureSuccess": "Structure transfer requested successfully", - "paiementNextAccountable": "Next responsible" - } \ No newline at end of file + "phonebookNoMember": "No member", + "phonebookNoMemberRole": "No role found", + "phonebookPhone": "Phone:", + "phonebookPhonebook": "Phonebook", + "phonebookPhonebookSearch": "Search", + "phonebookPhonebookSearchAssociation": "Association", + "phonebookPhonebookSearchField": "Search:", + "phonebookPhonebookSearchName": "Last name/First name/Nickname", + "phonebookPhonebookSearchRole": "Position", + "phonebookPresidentRoleTag": "Prez'", + "phonebookPromoNotGiven": "Promotion not provided", + "phonebookPromotion": "Promotion:", + "phonebookReorderingError": "Error during reordering", + "phonebookResearch": "Search", + "phonebookRolePure": "Role", + "phonebookTooHeavyAssociationPicture": "Image is too large (max 4MB)", + "phonebookUpdateGroups": "Update groups", + "phonebookUpdatedAssociation": "Association updated", + "phonebookUpdatedAssociationPicture": "Association picture has been changed", + "phonebookUpdatedGroups": "Groups updated", + "phonebookUpdatedMember": "Member updated", + "phonebookUpdatingError": "Error during update", + "phonebookValidation": "Validate", + "purchasesPurchases": "Purchases", + "purchasesResearch": "Search", + "purchasesNoPurchasesFound": "No purchases found", + "purchasesNoTickets": "No tickets", + "purchasesTicketsError": "Error loading tickets", + "purchasesPurchasesError": "Error loading purchases", + "purchasesNoPurchases": "No purchase", + "purchasesTimes": "times", + "purchasesAlreadyUsed": "Already used", + "purchasesNotPaid": "Not validated", + "purchasesPleaseSelectProduct": "Please select a product", + "purchasesProducts": "Products", + "purchasesCancel": "Cancel", + "purchasesValidate": "Validate", + "purchasesLeftScan": "Scans remaining", + "purchasesTag": "Tag", + "purchasesHistory": "History", + "purchasesPleaseSelectSeller": "Please select a seller", + "purchasesNoTagGiven": "Warning, no tag entered", + "purchasesTickets": "Tickets", + "purchasesNoScannableProducts": "No scannable products", + "purchasesLoading": "Waiting for scan", + "purchasesScan": "Scan", + "raffleRaffle": "Raffle", + "rafflePrize": "Prize", + "rafflePrizes": "Prizes", + "raffleActualRaffles": "Current raffles", + "rafflePastRaffles": "Past raffles", + "raffleYourTickets": "All your tickets", + "raffleCreateMenu": "Creation menu", + "raffleNextRaffles": "Upcoming raffles", + "raffleNoTicket": "You have no ticket", + "raffleSeeRaffleDetail": "View prizes/tickets", + "raffleActualPrize": "Current prizes", + "raffleMajorPrize": "Major prizes", + "raffleTakeTickets": "Take your tickets", + "raffleNoTicketBuyable": "You cannot buy tickets right now", + "raffleNoCurrentPrize": "There are no prizes currently", + "raffleModifTombola": "You can modify your raffles or create new ones, all decisions must then be approved by admins", + "raffleCreateYourRaffle": "Your raffle creation menu", + "rafflePossiblePrice": "Possible prize", + "raffleInformation": "Information and statistics", + "raffleAccounts": "Accounts", + "raffleAdd": "Add", + "raffleUpdatedAmount": "Amount updated", + "raffleUpdatingError": "Error during update", + "raffleDeletedPrize": "Prize deleted", + "raffleDeletingError": "Error during deletion", + "raffleQuantity": "Quantity", + "raffleClose": "Close", + "raffleOpen": "Open", + "raffleAddTypeTicketSimple": "Add", + "raffleAddingError": "Error during addition", + "raffleEditTypeTicketSimple": "Edit", + "raffleFillField": "Field cannot be empty", + "raffleWaiting": "Loading", + "raffleEditingError": "Error during editing", + "raffleAddedTicket": "Ticket added", + "raffleEditedTicket": "Ticket edited", + "raffleAlreadyExistTicket": "Ticket already exists", + "raffleNumberExpected": "An integer is expected", + "raffleDeletedTicket": "Ticket deleted", + "raffleAddPrize": "Add", + "raffleEditPrize": "Edit", + "raffleOpenRaffle": "Open raffle", + "raffleCloseRaffle": "Close raffle", + "raffleOpenRaffleDescription": "You are going to open the raffle, users will be able to buy tickets. You will no longer be able to modify the raffle. Are you sure you want to continue?", + "raffleCloseRaffleDescription": "You are going to close the raffle, users will no longer be able to buy tickets. Are you sure you want to continue?", + "raffleNoCurrentRaffle": "There is no ongoing raffle", + "raffleBoughtTicket": "Ticket purchased", + "raffleDrawingError": "Error during drawing", + "raffleInvalidPrice": "Price must be greater than 0", + "raffleMustBePositive": "Number must be strictly positive", + "raffleDraw": "Draw", + "raffleDrawn": "Drawn", + "raffleError": "Error", + "raffleGathered": "Collected", + "raffleTickets": "Tickets", + "raffleTicket": "ticket", + "raffleWinner": "Winner", + "raffleNoPrize": "No prize", + "raffleDeletePrize": "Delete prize", + "raffleDeletePrizeDescription": "You are going to delete the prize, are you sure you want to continue?", + "raffleDrawing": "Drawing", + "raffleDrawingDescription": "Draw the prize winner?", + "raffleDeleteTicket": "Delete ticket", + "raffleDeleteTicketDescription": "You are going to delete the ticket, are you sure you want to continue?", + "raffleWinningTickets": "Winning tickets", + "raffleNoWinningTicketYet": "Winning tickets will be displayed here", + "raffleName": "Name", + "raffleDescription": "Description", + "raffleBuyThisTicket": "Buy this ticket", + "raffleLockedRaffle": "Locked raffle", + "raffleUnavailableRaffle": "Unavailable raffle", + "raffleNotEnoughMoney": "You don't have enough money", + "raffleWinnable": "winnable", + "raffleNoDescription": "No description", + "raffleAmount": "Balance", + "raffleLoading": "Loading", + "raffleTicketNumber": "Number of tickets", + "rafflePrice": "Price", + "raffleEditRaffle": "Edit raffle", + "raffleEdit": "Edit", + "raffleAddPackTicket": "Add ticket pack", + "recommendationRecommendation": "Recommendation", + "recommendationTitle": "Title", + "recommendationLogo": "Logo", + "recommendationCode": "Code", + "recommendationSummary": "Short summary", + "recommendationDescription": "Description", + "recommendationAdd": "Add", + "recommendationEdit": "Edit", + "recommendationDelete": "Delete", + "recommendationAddImage": "Please add an image", + "recommendationAddedRecommendation": "Deal added", + "recommendationEditedRecommendation": "Deal updated", + "recommendationDeleteRecommendationConfirmation": "Are you sure you want to delete this deal?", + "recommendationDeleteRecommendation": "Delete", + "recommendationDeletingRecommendationError": "Error during deletion", + "recommendationDeletedRecommendation": "Deal deleted", + "recommendationIncorrectOrMissingFields": "Incorrect or missing fields", + "recommendationEditingError": "Edit failed", + "recommendationAddingError": "Add failed", + "recommendationCopiedCode": "Discount code copied", + "seedLibraryAdd": "Add", + "seedLibraryAddedPlant": "Plant added", + "seedLibraryAddedSpecies": "Species added", + "seedLibraryAddingError": "Error during addition", + "seedLibraryAddPlant": "Deposit a plant", + "seedLibraryAddSpecies": "Add a species", + "seedLibraryAll": "All", + "seedLibraryAncestor": "Ancestor", + "seedLibraryAround": "around", + "seedLibraryAutumn": "Autumn", + "seedLibraryBorrowedPlant": "Borrowed plant", + "seedLibraryBorrowingDate": "Borrowing date:", + "seedLibraryBorrowPlant": "Borrow plant", + "seedLibraryCard": "Card", + "seedLibraryChoosingAncestor": "Please choose an ancestor", + "seedLibraryChoosingSpecies": "Please choose a species", + "seedLibraryChoosingSpeciesOrAncestor": "Please choose a species or an ancestor", + "seedLibraryContact": "Contact:", + "seedLibraryDays": "days", + "seedLibraryDeadMsg": "Do you want to declare the plant dead?", + "seedLibraryDeadPlant": "Dead plant", + "seedLibraryDeathDate": "Date of death", + "seedLibraryDeletedSpecies": "Species deleted", + "seedLibraryDeleteSpecies": "Delete species?", + "seedLibraryDeleting": "Deleting", + "seedLibraryDeletingError": "Error during deletion", + "seedLibraryDepositNotAvailable": "Plant deposit is not possible without borrowing a plant first", + "seedLibraryDescription": "Description", + "seedLibraryDifficulty": "Difficulty:", + "seedLibraryEdit": "Edit", + "seedLibraryEditedPlant": "Plant updated", + "seedLibraryEditInformation": "Edit information", + "seedLibraryEditingError": "Error during editing", + "seedLibraryEditSpecies": "Edit species", + "seedLibraryEmptyDifficultyError": "Please choose a difficulty", + "seedLibraryEmptyFieldError": "Please fill all fields", + "seedLibraryEmptyTypeError": "Please choose a plant type", + "seedLibraryEndMonth": "End month:", + "seedLibraryFacebookUrl": "Facebook link", + "seedLibraryFilters": "Filters", + "seedLibraryForum": "Oskour mom I killed my plant - Help forum", + "seedLibraryForumUrl": "Forum link", + "seedLibraryHelpSheets": "Plant sheets", + "seedLibraryInformation": "Information:", + "seedLibraryMaturationTime": "Maturation time", + "seedLibraryMonthJan": "January", + "seedLibraryMonthFeb": "February", + "seedLibraryMonthMar": "March", + "seedLibraryMonthApr": "April", + "seedLibraryMonthMay": "May", + "seedLibraryMonthJun": "June", + "seedLibraryMonthJul": "July", + "seedLibraryMonthAug": "August", + "seedLibraryMonthSep": "September", + "seedLibraryMonthOct": "October", + "seedLibraryMonthNov": "November", + "seedLibraryMonthDec": "December", + "seedLibraryMyPlants": "My plants", + "seedLibraryName": "Name", + "seedLibraryNbSeedsRecommended": "Number of seeds recommended", + "seedLibraryNbSeedsRecommendedError": "Please enter a recommended seed number greater than 0", + "seedLibraryNoDateError": "Please enter a date", + "seedLibraryNoFilteredPlants": "No plants match your search. Try other filters.", + "seedLibraryNoMorePlant": "No plants available", + "seedLibraryNoPersonalPlants": "You don't have any plants yet in your seed library. You can add some in the stocks.", + "seedLibraryNoSpecies": "No species found", + "seedLibraryNoStockPlants": "No plants available in stock", + "seedLibraryNotes": "Notes", + "seedLibraryOk": "OK", + "seedLibraryPlantationPeriod": "Planting period:", + "seedLibraryPlantationType": "Plantation type:", + "seedLibraryPlantDetail": "Plant details", + "seedLibraryPlantingDate": "Planting date", + "seedLibraryPlantingNow": "I'm planting it now", + "seedLibraryPrefix": "Prefix", + "seedLibraryPrefixError": "Prefix already used", + "seedLibraryPrefixLengthError": "The prefix must be 3 characters", + "seedLibraryPropagationMethod": "Propagation method:", + "seedLibraryReference": "Reference:", + "seedLibraryRemovedPlant": "Plant removed", + "seedLibraryRemovingError": "Error removing plant", + "seedLibraryResearch": "Search", + "seedLibrarySaveChanges": "Save changes", + "seedLibrarySeason": "Season:", + "seedLibrarySeed": "Seed", + "seedLibrarySeeds": "seeds", + "seedLibrarySeedDeposit": "Plant deposit", + "seedLibrarySeedLibrary": "Seed library", + "seedLibrarySeedQuantitySimple": "Seed quantity", + "seedLibrarySeedQuantity": "Seed quantity:", + "seedLibraryShowDeadPlants": "Show dead plants", + "seedLibrarySpecies": "Species:", + "seedLibrarySpeciesHelp": "Help on species", + "seedLibrarySpeciesPlural": "Species", + "seedLibrarySpeciesSimple": "Species", + "seedLibrarySpeciesType": "Species type:", + "seedLibrarySpring": "Spring", + "seedLibraryStartMonth": "Start month:", + "seedLibraryStock": "Available stock", + "seedLibrarySummer": "Summer", + "seedLibraryStocks": "Stocks", + "seedLibraryTimeUntilMaturation": "Time until maturation:", + "seedLibraryType": "Type:", + "seedLibraryUnableToOpen": "Unable to open link", + "seedLibraryUpdate": "Edit", + "seedLibraryUpdatedInformation": "Information updated", + "seedLibraryUpdatedSpecies": "Species updated", + "seedLibraryUpdatedPlant": "Plant updated", + "seedLibraryUpdatingError": "Error updating", + "seedLibraryWinter": "Winter", + "seedLibraryWriteReference": "Please write the following reference: ", + "settingsAccount": "Account", + "settingsAddProfilePicture": "Add a photo", + "settingsAdmin": "Administrator", + "settingsAskHelp": "Ask for help", + "settingsAssociation": "Association", + "settingsBirthday": "Birthday", + "settingsBugs": "Bugs", + "settingsChangePassword": "Change password", + "settingsChangingPassword": "Do you really want to change your password?", + "settingsConfirmPassword": "Confirm password", + "settingsCopied": "Copied!", + "settingsDarkMode": "Dark mode", + "settingsDarkModeOff": "Off", + "settingsDeleteLogs": "Delete logs?", + "settingsDeleteNotificationLogs": "Delete notification logs?", + "settingsDetelePersonalData": "Delete my personal data", + "settingsDetelePersonalDataDesc": "This action notifies the administrator that you want to delete your personal data.", + "settingsDeleting": "Deleting", + "settingsEdit": "Edit", + "settingsEditAccount": "Edit account", + "settingsEditPassword": "Edit password", + "settingsEmail": "Email", + "settingsEmptyField": "This field cannot be empty", + "settingsErrorProfilePicture": "Error editing profile picture", + "settingsErrorSendingDemand": "Error sending request", + "settingsEventsIcal": "Ical link for events", + "settingsExpectingDate": "Expected birth date", + "settingsFirstname": "First name", + "settingsFloor": "Floor", + "settingsHelp": "Help", + "settingsIcalCopied": "Ical link copied!", + "settingsLanguage": "Language", + "settingsLanguageVar": "English 🇬🇧", + "settingsLogs": "Logs", + "settingsModules": "Modules", + "settingsMyIcs": "My Ical link", + "settingsName": "Last name", + "settingsNewPassword": "New password", + "settingsNickname": "Nickname", + "settingsNotifications": "Notifications", + "settingsOldPassword": "Old password", + "settingsPasswordChanged": "Password changed", + "settingsPasswordsNotMatch": "Passwords do not match", + "settingsPersonalData": "Personal data", + "settingsPersonalisation": "Personalization", + "settingsPhone": "Phone", + "settingsProfilePicture": "Profile picture", + "settingsPromo": "Promotion", + "settingsRepportBug": "Report a bug", + "settingsSave": "Save", + "settingsSecurity": "Security", + "settingsSendedDemand": "Request sent", + "settingsSettings": "Settings", + "settingsTooHeavyProfilePicture": "Image is too large (max 4MB)", + "settingsUpdatedProfile": "Profile updated", + "settingsUpdatedProfilePicture": "Profile picture updated", + "settingsUpdateNotification": "Update notifications", + "settingsUpdatingError": "Error updating profile", + "settingsVersion": "Version", + "settingsPasswordStrength": "Password strength", + "settingsPasswordStrengthVeryWeak": "Very weak", + "settingsPasswordStrengthWeak": "Weak", + "settingsPasswordStrengthMedium": "Medium", + "settingsPasswordStrengthStrong": "Strong", + "settingsPasswordStrengthVeryStrong": "Very strong", + "settingsPhoneNumber": "Phone number", + "settingsValidate": "Confirm", + "settingsEditedAccount": "Account edited", + "settingsFailedToEditAccount": "Failed to edit account", + "settingsChooseLanguage": "Choose a language", + "settingsNotificationCounter": "{active}/{total} active {active, plural, zero {notification} one {notification} other {notifications}}", + "@settingsNotificationCounter": { + "description": "Affiche le nombre de notifications actives sur le total des notifications disponibles, avec gestion du pluriel", + "placeholders": { + "active": { "type": "int" }, + "total": { "type": "int" } + } + }, + "settingsEvent" : "Event", + "settingsIcal": "Ical link", + "settingsSynncWithCalendar": "Sync with calendar", + "settingsIcalLinkCopied": "Ical link copied", + "settingsProfile": "Profile", + "voteAdd": "Add", + "voteAddMember": "Add a member", + "voteAddedPretendance": "List added", + "voteAddedSection": "Section added", + "voteAddingError": "Error adding", + "voteAddPretendance": "Add a list", + "voteAddSection": "Add a section", + "voteAll": "All", + "voteAlreadyAddedMember": "Member already added", + "voteAlreadyVoted": "Vote recorded", + "voteChooseList": "Choose a list", + "voteClear": "Reset", + "voteClearVotes": "Reset votes", + "voteClosedVote": "Votes closed", + "voteCloseVote": "Close votes", + "voteConfirmVote": "Confirm vote", + "voteCountVote": "Count votes", + "voteDeletedAll": "All deleted", + "voteDeletedPipo": "Fake lists deleted", + "voteDeletedSection": "Section deleted", + "voteDeleteAll": "Delete all", + "voteDeleteAllDescription": "Do you really want to delete everything?", + "voteDeletePipo": "Delete fake lists", + "voteDeletePipoDescription": "Do you really want to delete the fake lists?", + "voteDeletePretendance": "Delete the list", + "voteDeletePretendanceDesc": "Do you really want to delete this list?", + "voteDeleteSection": "Delete the section", + "voteDeleteSectionDescription": "Do you really want to delete this section?", + "voteDeletingError": "Error deleting", + "voteDescription": "Description", + "voteEdit": "Edit", + "voteEditedPretendance": "List edited", + "voteEditedSection": "Section edited", + "voteEditingError": "Error editing", + "voteErrorClosingVotes": "Error closing votes", + "voteErrorCountingVotes": "Error counting votes", + "voteErrorResetingVotes": "Error resetting votes", + "voteErrorOpeningVotes": "Error opening votes", + "voteIncorrectOrMissingFields": "Incorrect or missing fields", + "voteMembers": "Members", + "voteName": "Name", + "voteNoPretendanceList": "No list of candidates", + "voteNoSection": "No section", + "voteCanNotVote": "You cannot vote", + "voteNoSectionList": "No section", + "voteNotOpenedVote": "Vote not opened", + "voteOnGoingCount": "Counting in progress", + "voteOpenVote": "Open votes", + "votePipo": "Fake", + "votePretendance": "Lists", + "votePretendanceDeleted": "Candidate list deleted", + "votePretendanceNotDeleted": "Error deleting", + "voteProgram": "Program", + "votePublish": "Publish", + "votePublishVoteDescription": "Do you really want to publish the votes?", + "voteResetedVotes": "Votes reset", + "voteResetVote": "Reset votes", + "voteResetVoteDescription": "What do you want to do?", + "voteRole": "Role", + "voteSectionDescription": "Section description", + "voteSection": "Section", + "voteSectionName": "Section name", + "voteSeeMore": "See more", + "voteSelected": "Selected", + "voteShowVotes": "Show votes", + "voteVote": "Vote", + "voteVoteError": "Error recording vote", + "voteVoteFor": "Vote for ", + "voteVoteNotStarted": "Vote not opened", + "voteVoters": "Voting groups", + "voteVoteSuccess": "Vote recorded", + "voteVotes": "Votes", + "voteVotesClosed": "Votes closed", + "voteVotesCounted": "Votes counted", + "voteVotesOpened": "Votes opened", + "voteWarning": "Warning", + "voteWarningMessage": "Selection will not be saved.\nDo you want to continue?", + "moduleAdvert": "Advert", + "moduleAmap": "AMAP", + "moduleBooking": "Booking", + "moduleCalendar": "Calendar", + "moduleCentralisation": "Centralisation", + "moduleCinema": "Cinema", + "moduleEvent": "Event", + "moduleFlappyBird": "Flappy Bird", + "moduleLoan": "Loan", + "modulePhonebook": "Phonebook", + "modulePurchases": "Purchases", + "moduleRaffle": "Raffle", + "moduleRecommendation": "Recommendation", + "moduleSeedLibrary": "Seed Library", + "moduleVote": "Vote", + "modulePh": "PH", + "moduleSettings": "Settings", + "moduleFeed": "Feed", + "moduleStyleGuide": "StyleGuide", + "moduleAdmin": "Admin", + "moduleOthers": "Others", + "modulePayment": "Payment", + "moduleAdvertDescription": "View the latest adverts", + "moduleAmapDescription": "Order your AMAP basket", + "moduleBookingDescription": "Book a room", + "moduleCalendarDescription": "View the calendar of events", + "moduleCentralisationDescription": "Viw all links", + "moduleCinemaDescription": "View the cinema schedule", + "moduleEventDescription": "View events", + "moduleFlappyBirdDescription": "Play Flappy Bird", + "moduleLoanDescription": "See your loans", + "modulePhonebookDescription": "View the phonebook", + "modulePurchasesDescription": "View your purchases", + "moduleRaffleDescription": "View the raffle", + "moduleRecommendationDescription": "View the recommendations", + "moduleSeedLibraryDescription": "View the seed library", + "moduleVoteDescription": "Vote for the campaigns", + "modulePhDescription": "View the PH", + "moduleSettingsDescription": "Manage your settings", + "moduleFeedDescription": "View the latest news", + "moduleStyleGuideDescription": "Style guide for developers", + "moduleAdminDescription": "Administration module for administrators", + "moduleOthersDescription": "Other modules", + "modulePaymentDescription": "Pay and see your transactions", + "paiementTopUp": "Top-up", + "paiementStoreManagement": "Association management", + "paiementDeleteStore": "Delete association", + "paiementDeleteStoreDescription": "Are you sure you want to delete this association?", + "paiementDeleteStoreError": "Unable to delete the association", + "paiementStoreDeleted": "Association deleted", + "paiementAddThisDevice": "Add this device", + "paiementThisDevice": "(this device)", + "paiementCancelled": "Cancelled", + "paiementThe": "The", + "paiementOf": "of", + "paiementRefundedThe": "Refunded on", + "paiementAt": "at", + "paiementPleaseAcceptTOS": "Please accept the Terms of Service.", + "paiementAskDeviceActivation": "Device activation request", + "paiementDeviceActivationReceived": "The activation request has been received, please check your email to finalize the process", + "paiementRevokeDevice": "Revoke device?", + "paiementRevokeDeviceDescription": "You will no longer be able to use this device for payments", + "paiementDeviceRevoked": "Device revoked", + "paiementDeviceRevokingError": "Error while revoking device", + "paiementPleaseAcceptPopup": "Please allow popups", + "paiementProceedSuccessfully": "Payment completed successfully", + "paiementCancelledTransaction": "Payment cancelled", + "paiementPleaseEnterMinAmount": "Please enter an amount greater than 1", + "paiementMaxAmount": "The maximum wallet amount is", + "paiementPayWithHA": "Pay with HelloAsso", + "paiementBalanceAfterTopUp": "Balance after top-up:", + "paiementPersonalBalance": "Personal balance", + "paiementDevices": "Devices", + "paiementPay": "Pay", + "paiementDeviceNotRegistered": "Device not registered", + "paiementDeviceNotRegisteredDescription": "Your device is not registered yet. \nTo register it, please go to the devices page.", + "paiementAccessPage": "Access the page", + "paiementDeviceNotActivated": "Device not activated", + "paiementDeviceNotActivatedDescription": "Your device is not yet activated. \nTo activate it, please go to the devices page.", + "paiementReactivateRevokedDeviceDescription": "Your device has been revoked. \nTo reactivate it, please go to the devices page.", + "paiementDeviceRecoveryError": "Error while retrieving device", + "paiementStats": "Stats", + "paimentTopUpAction": "Top-up", + "paiementGetBalanceError": "Error while retrieving balance: ", + "paiementLastTransactions": "Latest transactions", + "paiementGetTransactionsError": "Error while retrieving transactions: ", + "paiementStoreBalance": "Association balance", + "paiementScan": "Scan", + "paiementManagement": "Management", + "paiementHistory": "History", + "paiementHandOver": "Handover", + "paiementStores": "Associations", + "paiementAdmin": "Administrator", + "paiementSuccededTransaction": "Successful payment", + "paiementNewCGU": "New Terms of Service", + "paiementDecline": "Decline", + "paiementAccept": "Accept", + "paiementAmount": "Amount", + "paiementValidUntil": "Valid until", + "paiementClose": "Close", + "paiementPleaseEnterValidAmount": "Please enter a valid amount", + "paiementPleaseAuthenticate": "Please authenticate", + "paiementAthenticationRequired": "Authentication required to pay", + "paiementNoThanks": "No thanks", + "paiementAuthentificationFailed": "Authentication failed", + "paiementPleaseAddDevice": "Please add this device to pay", + "paiementPayment": "Payment", + "paiementBalanceAfterTransaction": "Balance after payment: ", + "paiementCancel": "Cancel", + "paiementLimitedTo": "Limited to", + "paiementScanCode": "Scan a code", + "paiementNext": "Next", + "paiementCancelTransaction": "Cancel transaction", + "paiementTransactionCancelled": "Transaction cancelled", + "paiementTransactionCancelledDescription": "Are you sure you want to cancel the transaction of", + "paiementTransactionCancelledError": "Error while cancelling the transaction", + "paiementNoMembership": "No membership", + "paiementNoMembershipDescription": "This product is not available to non-members. Confirm the payment?", + "paiementQRCodeAlreadyUsed": "QR Code already used", + "paiementCameraPermissionRequired": "Camera permission required", + "paiementCameraPerssionRequiredDescription": "To scan a QR Code, you must allow camera access.", + "paiementSettings": "Settings", + "paiementReceived": "Received", + "paiementSpent": "Spent", + "paiementNoTrasactionForThisMonth": "No transactions for this month", + "paiementNoTransactinon": "No transaction", + "paiementSellerRigths": "Seller rights", + "paiementCanBank": "Can collect payments", + "paiementCanSeeHistory": "Can view history", + "paiementCanCancelTransaction": "Can cancel transactions", + "paiementCanManageSellers": "Can manage sellers", + "paiementAddedSeller": "Seller added", + "paiementAddingSellerError": "Error while adding seller", + "paiementBank": "Collect", + "paiementSeeHistory": "View history", + "paiementCancelTransactions": "Cancel transactions", + "paiementManageSellers": "Manage sellers", + "paiementStructureAdmin": "Structure administrator", + "paiementRightsOf": "Rights of", + "paiementRightsUpdated": "Rights updated", + "paiementRightsUpdateError": "Error while updating rights", + "paiementDeleteSellerDescription": "Are you sure you want to delete this seller?", + "paiementDeletedSeller": "Seller deleted", + "paiementDeletingSellerError": "Error while deleting seller", + "paiementDeleteSeller": "Delete seller", + "paiementAdd": "Add", + "paiementAddSeller": "Add seller", + "paiementSellerError": "You are not a seller of this association", + "paiementSellersOf": "Sellers of", + "paiementModify": "Edit", + "paiementAStore": "an association", + "paiementStoreName": "Association name", + "paiementSuccessfullyAddedStore": "Association successfully added", + "paiementSuccessfullyModifiedStore": "Association successfully updated", + "paiementAddingStoreError": "Error while adding the association", + "paiementModifyingStoreError": "Error while updating the association", + "paiementRefund": "Refund", + "paiementDoneTransaction": "Transaction completed", + "paiementRefundAction": "Refund", + "paiementTotalDuringPeriod": "Total during the period", + "paiementMean": "Average: ", + "paiementTransaction": "Transaction", + "paiementTransferStructure": "Structure transfer", + "paiementYouAreTransferingStructureTo": "You are about to transfer the structure to ", + "paiementTransferStructureDescription": "The new manager will have access to all structure management features. You will receive an email to confirm this transfer. The link will only be active for 20 minutes. This action is irreversible. Are you sure you want to continue?", + "paiementTransferStructureError": "Error while transferring structure", + "paiementTransferStructureSuccess": "Structure transfer requested successfully", + "paiementNextAccountable": "Next responsible" +} \ No newline at end of file diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index d0e8de078f..dd66558a9f 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -120,6 +120,8 @@ "adminAdverts": "Annonces", "adminAnnouncers": "Annonceurs", "adminManageAnnouncers" : "Gérer les annonceurs", + "adminDeleteAnnouncer": "Supprimer cet annonceur ?", + "adminDeleteAnnouncerDescription": "Supprimer cet annonceurs supprimera toutes ses annonces.", "advertAdd": "Ajouter", "advertAddedAdvert": "Annonce publiée", "advertAddedAnnouncer": "Annonceur ajouté", @@ -1196,7 +1198,7 @@ "moduleFeedDescription": "Consulter les actualités et mises à jour", "moduleStyleGuide": "StyleGuide", "moduleStyleGuideDescription": "Explore the UI components and styles used in Titan", - "moduleAdmin": "Adminitration", + "moduleAdmin": "Admin", "moduleAdminDescription": "Gérer les utilisateurs, groupes et structures", "moduleOthers": "Autres", "moduleOthersDescription": "Afficher les autres modules", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index bb0272a656..ce12eeaddc 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -770,6 +770,18 @@ abstract class AppLocalizations { /// **'Gérer les annonceurs'** String get adminManageAnnouncers; + /// No description provided for @adminDeleteAnnouncer. + /// + /// In fr, this message translates to: + /// **'Supprimer cet annonceur ?'** + String get adminDeleteAnnouncer; + + /// No description provided for @adminDeleteAnnouncerDescription. + /// + /// In fr, this message translates to: + /// **'Supprimer cet annonceurs supprimera toutes ses annonces.'** + String get adminDeleteAnnouncerDescription; + /// No description provided for @advertAdd. /// /// In fr, this message translates to: @@ -7187,7 +7199,7 @@ abstract class AppLocalizations { /// No description provided for @moduleAdmin. /// /// In fr, this message translates to: - /// **'Adminitration'** + /// **'Admin'** String get moduleAdmin; /// No description provided for @moduleAdminDescription. diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index f0e06e2fc3..7107d5b701 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -353,6 +353,13 @@ class AppLocalizationsEn extends AppLocalizations { @override String get adminManageAnnouncers => 'Manage announcers'; + @override + String get adminDeleteAnnouncer => 'Delete announcer?'; + + @override + String get adminDeleteAnnouncerDescription => + 'Are you sure you want to delete this announcer? All their adverts will be deleted.'; + @override String get advertAdd => 'Add'; @@ -3629,7 +3636,7 @@ class AppLocalizationsEn extends AppLocalizations { String get moduleStyleGuideDescription => 'Style guide for developers'; @override - String get moduleAdmin => 'Administration'; + String get moduleAdmin => 'Admin'; @override String get moduleAdminDescription => diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 8f8c8cd2e9..4cbc9eeed1 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -356,6 +356,13 @@ class AppLocalizationsFr extends AppLocalizations { @override String get adminManageAnnouncers => 'Gérer les annonceurs'; + @override + String get adminDeleteAnnouncer => 'Supprimer cet annonceur ?'; + + @override + String get adminDeleteAnnouncerDescription => + 'Supprimer cet annonceurs supprimera toutes ses annonces.'; + @override String get advertAdd => 'Ajouter'; @@ -3673,7 +3680,7 @@ class AppLocalizationsFr extends AppLocalizations { 'Explore the UI components and styles used in Titan'; @override - String get moduleAdmin => 'Adminitration'; + String get moduleAdmin => 'Admin'; @override String get moduleAdminDescription => From 8f537edc94c5aad0711d3521e203c6fe1cfc9c17 Mon Sep 17 00:00:00 2001 From: Thonyk Date: Sun, 10 Aug 2025 16:32:09 +0200 Subject: [PATCH 237/473] fix: merge --- .../association_membership_member_editable_card.dart | 4 ++-- .../association_add_edit_page/association_add_edit_page.dart | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/admin/ui/pages/membership/association_membership_detail_page/association_membership_member_editable_card.dart b/lib/admin/ui/pages/membership/association_membership_detail_page/association_membership_member_editable_card.dart index db0b5babe4..317773b5e6 100644 --- a/lib/admin/ui/pages/membership/association_membership_detail_page/association_membership_member_editable_card.dart +++ b/lib/admin/ui/pages/membership/association_membership_detail_page/association_membership_member_editable_card.dart @@ -5,8 +5,8 @@ import 'package:titan/admin/class/user_association_membership.dart'; import 'package:titan/admin/providers/association_membership_members_list_provider.dart'; import 'package:titan/admin/providers/user_association_membership_provider.dart'; import 'package:titan/admin/router.dart'; -import 'package:titan/admin/ui/pages/memberships/association_membership_detail_page/delete_button.dart'; -import 'package:titan/admin/ui/pages/memberships/association_membership_detail_page/edition_button.dart'; +import 'package:titan/admin/ui/pages/membership/association_membership_detail_page/delete_button.dart'; +import 'package:titan/admin/ui/pages/membership/association_membership_detail_page/edition_button.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:qlevar_router/qlevar_router.dart'; diff --git a/lib/phonebook/ui/pages/association_add_edit_page/association_add_edit_page.dart b/lib/phonebook/ui/pages/association_add_edit_page/association_add_edit_page.dart index d8b674c67e..6717342f53 100644 --- a/lib/phonebook/ui/pages/association_add_edit_page/association_add_edit_page.dart +++ b/lib/phonebook/ui/pages/association_add_edit_page/association_add_edit_page.dart @@ -11,7 +11,7 @@ import 'package:titan/phonebook/providers/association_provider.dart'; import 'package:titan/phonebook/router.dart'; import 'package:titan/phonebook/ui/components/groupement_bar.dart'; import 'package:titan/phonebook/ui/phonebook.dart'; -import 'package:titan/settings/ui/pages/edit_user_page/picture_button.dart'; +import 'package:titan/settings/ui/pages/main_page/picture_button.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:qlevar_router/qlevar_router.dart'; From 6d31f91ce4c7c31d4118997b2e571ffd468be1ea Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 10 Aug 2025 18:17:35 +0200 Subject: [PATCH 238/473] fix: merge --- lib/l10n/app_fr.arb | 2883 ++++++++++++++++++++----------------------- 1 file changed, 1319 insertions(+), 1564 deletions(-) diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 5ab21cbf2c..06bd0bf12b 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -1,1589 +1,1344 @@ { - "@@locale": "fr", - "dateToday": "Aujourd'hui", - "dateYesterday": "Hier", - "dateTomorrow": "Demain", - "dateAt": "à", - "dateFrom": "de", - "dateTo": "à", - "dateBetweenDays": "au", - "dateStarting": "Commence", - "dateLast": "", - "dateUntil": "Jusqu'au", - "feedFilterAll": "Tous", - "feedFilterPending": "En attente", - "feedFilterApproved": "Approuvés", - "feedFilterRejected": "Rejetés", - "feedEmptyAll": "Aucun événement disponible", - "feedEmptyPending": "Aucun événement en attente de validation", - "feedEmptyApproved": "Aucun événement approuvé", - "feedEmptyRejected": "Aucun événement rejeté", - "feedEventManagement": "Gestion des événements", - "eventActionCampaign": "Tu peux voter", - "eventActionEvent": "Tu es invité", - "eventActionCampaignSubtitle": "Votez maintenant", - "eventActionEventSubtitle": "Répondez à l'invitation", - "eventActionCampaignButton": "Voter", - "eventActionEventButton": "Participer", - "eventActionCampaignValidated": "J'ai voté !", - "eventActionEventValidated": "Je viens !", - "adminAccountTypes": "Types de compte", - "adminAdd": "Ajouter", - "adminAddGroup": "Ajouter un groupe", - "adminAddMember": "Ajouter un membre", - "adminAddedGroup": "Groupe créé", - "adminAddedLoaner": "Préteur ajouté", - "adminAddedMember": "Membre ajouté", - "adminAddingError": "Erreur lors de l'ajout", - "adminAddingMember": "Ajout d'un membre", - "adminAddLoaningGroup": "Ajouter un groupe de prêt", - "adminAddSchool": "Ajouter une école", - "adminAddStructure": "Ajouter une structure", - "adminAddedSchool": "École créée", - "adminAddedStructure": "Structure ajoutée", - "adminEditedStructure": "Structure modifiée", - "adminAdministration": "Administration", - "adminAssociationMembership": "Adhésion", - "adminAssociationMembershipName": "Nom de l'adhésion", - "adminAssociationsMemberships": "Adhésions", - "adminClearFilters": "Effacer les filtres", - "adminCreateAssociationMembership": "Créer une adhésion", - "adminCreatedAssociationMembership": "Adhésion créée", - "adminCreationError": "Erreur lors de la création", - "adminDateError": "La date de début doit être avant la date de fin", - "adminDelete": "Supprimer", - "adminDeleteAssociationMembership": "Supprimer l'adhésion ?", - "adminDeletedAssociationMembership": "Adhésion supprimée", - "adminDeleteGroup": "Supprimer le groupe", - "adminDeletedGroup": "Groupe supprimé", - "adminDeleteSchool": "Supprimer l'école ?", - "adminDeletedSchool": "École supprimée", - "adminDeleting": "Suppression", - "adminDeletingError": "Erreur lors de la suppression", - "adminDescription": "Description", - "adminEclSchool": "Centrale Lyon", - "adminEdit": "Modifier", - "adminEditStructure": "Modifier la structure", - "adminEditMembership": "Modifier l'adhésion", - "adminEmptyDate": "Date vide", - "adminEmptyFieldError": "Le nom ne peut pas être vide", - "adminEmailRegex": "Email Regex", - "adminEmptyUser": "Utilisateur vide", - "adminEndDate": "Date de fin", - "adminEndDateMaximal": "Date de fin maximale", - "adminEndDateMinimal": "Date de fin minimale", - "adminError": "Erreur", - "adminFilters": "Filtres", - "adminGroup": "Groupe", - "adminGroups": "Groupes", - "adminLoaningGroup": "Groupe de prêt", - "adminLooking": "Recherche", - "adminManager": "Administrateur de la structure", - "adminMaximum": "Maximum", - "adminMembers": "Membres", - "adminMembershipAddingError": "Erreur lors de l'ajout (surement dû à une superposition de dates)", - "adminMemberships": "Adhésions", - "adminMembershipUpdatingError": "Erreur lors de la modification (surement dû à une superposition de dates)", - "adminMinimum": "Minimum", - "adminModifyModuleVisibility": "Visibilité des modules", - "adminMyEclPay": "MyECLPay", - "adminName": "Nom", - "adminNoManager": "Aucun manager n'est sélectionné", - "adminNoMember": "Aucun membre", - "adminNoMoreLoaner": "Aucun prêteur n'est disponible", - "adminNoSchool": "Sans école", - "adminRemoveGroupMember": "Supprimer le membre du groupe ?", - "adminResearch": "Recherche", - "adminSchools": "Écoles", - "adminStructures": "Structures", - "adminStartDate": "Date de début", - "adminStartDateMaximal": "Date de début maximale", - "adminStartDateMinimal": "Date de début minimale", - "adminUpdatedAssociationMembership": "Adhésion modifiée", - "adminUpdatedGroup": "Groupe modifié", - "adminUpdatedMembership": "Adhésion modifiée", - "adminUpdatingError": "Erreur lors de la modification", - "adminUser": "Utilisateur", - "adminValidateFilters": "Valider les filtres", - "adminVisibilities": "Visibilités", - "adminGroupNotification": "Notification de groupe", - "adminNotifyGroup": "Notifier le groupe {groupName}", - "@adminNotifyGroup": { - "description": "Notifie les membres du groupe sélectionné", - "placeholders": { - "groupName": { - "type": "String" - } - } - }, - "adminTitle": "Titre", - "adminContent": "Contenu", - "adminSend": "Envoyer", - "adminNotificationSent": "Notification envoyée", - "adminFailedToSendNotification": "Échec de l'envoi de la notification", - "adminGroupsManagement": "Gestion des groupes", - "adminEditGroup": "Modifier le groupe", - "adminManageMembers": "Gérer les membres", - "adminDeleteGroupConfirmation": "Êtes-vous sûr de vouloir supprimer ce groupe ?", - "adminFailedToDeleteGroup": "Échec de la suppression du groupe", - "adminUsersAndGroups": "Utilisateurs et groupes", - "adminUsersManagement": "Gestion des utilisateurs", - "adminUsersManagementDescription": "Gérer les utilisateurs de l'application", - "adminManageUserGroups": "Gérer les groupes d'utilisateurs", - "adminSendNotificationToGroup": "Envoyer une notification à un groupe", - "adminPaiementModule": "Module de paiement", - "adminPaiement": "Paiement", - "adminManagePaiementStructures": "Gérer les structures du module de paiement", - "adminManageUsersAssociationMemberships": "Gérer les adhésions des utilisateurs", - "adminAssociationMembershipsManagement": "Gestion des adhésions", - "adminChooseGroupManager": "Groupe gestionnaire de l'adhésion", - "adminSelectManager": "Sélectionner un gestionnaire", - "adminInviteUsers": "Inviter des utilisateurs", - "adminImportList": "Importer une liste", - "adminInvitedUsers": "Utilisateurs invités", - "adminFailedToInviteUsers": "Échec de l'invitation des utilisateurs", - "adminDeleteUsers": "Supprimer des utilisateurs", - "adminAdmin": "Admin", - "adminAdverts": "Annonces", - "adminAnnouncers": "Annonceurs", - "adminManageAnnouncers": "Gérer les annonceurs", - "adminDeleteAnnouncer": "Supprimer cet annonceur ?", - "adminDeleteAnnouncerDescription": "Supprimer cet annonceurs supprimera toutes ses annonces.", - "advertAdd": "Ajouter", - "advertAddedAdvert": "Annonce publiée", - "advertAddedAnnouncer": "Annonceur ajouté", - "advertAddingError": "Erreur lors de l'ajout", - "advertAdmin": "Admin", - "advertAdvert": "Annonce", - "advertChoosingAnnouncer": "Veuillez choisir un annonceur", - "advertChoosingPoster": "Veuillez choisir une image", - "advertContent": "Contenu", - "advertDeleteAdvert": "Supprimer l'annonce", - "advertDeleteAnnouncer": "Supprimer l'annonceur ?", - "advertDeleting": "Suppression", - "advertEdit": "Modifier", - "advertEditedAdvert": "Annonce modifiée", - "advertEditingError": "Erreur lors de la modification", - "advertGroupAdvert": "Groupe", - "advertIncorrectOrMissingFields": "Champs incorrects ou manquants", - "advertInvalidNumber": "Veuillez entrer un nombre", - "advertManagement": "Gestion", - "advertModifyAnnouncingGroup": "Modifier un groupe d'annonce", - "advertNoMoreAnnouncer": "Aucun annonceur n'est disponible", - "advertNoValue": "Veuillez entrer une valeur", - "advertPositiveNumber": "Veuillez entrer un nombre positif", - "advertRemovedAnnouncer": "Annonceur supprimé", - "advertRemovingError": "Erreur lors de la suppression", - "advertTags": "Tags", - "advertTitle": "Titre", - "advertMonthJan": "Janv", - "advertMonthFeb": "Févr.", - "advertMonthMar": "Mars", - "advertMonthApr": "Avr.", - "advertMonthMay": "Mai", - "advertMonthJun": "Juin", - "advertMonthJul": "Juill.", - "advertMonthAug": "Août", - "advertMonthSep": "Sept.", - "advertMonthOct": "Oct.", - "advertMonthNov": "Nov.", - "advertMonthDec": "Déc.", - "amapAccounts": "Comptes", - "amapAdd": "Ajouter", - "amapAddDelivery": "Ajouter une livraison", - "amapAddedCommand": "Commande ajoutée", - "amapAddedOrder": "Commande ajoutée", - "amapAddedProduct": "Produit ajouté", - "amapAddedUser": "Utilisateur ajouté", - "amapAddProduct": "Ajouter un produit", - "amapAddUser": "Ajouter un utilisateur", - "amapAddingACommand": "Ajouter une commande", - "amapAddingCommand": "Ajouter la commande", - "amapAddingError": "Erreur lors de l'ajout", - "amapAddingProduct": "Ajouter un produit", - "amapAddOrder": "Ajouter une commande", - "amapAdmin": "Admin", - "amapAlreadyExistCommand": "Il existe déjà une commande à cette date", - "amapAmap": "Amap", - "amapAmount": "Solde", - "amapArchive": "Archiver", - "amapArchiveDelivery": "Archiver", - "amapArchivingDelivery": "Archivage de la livraison", - "amapCategory": "Catégorie", - "amapCloseDelivery": "Verrouiller", - "amapCommandDate": "Date de la commande", - "amapCommandProducts": "Produits de la commande", - "amapConfirm": "Confirmer", - "amapContact": "Contacts associatifs ", - "amapCreateCategory": "Créer une catégorie", - "amapDelete": "Supprimer", - "amapDeleteDelivery": "Supprimer la livraison ?", - "amapDeleteDeliveryDescription": "Voulez-vous vraiment supprimer cette livraison ?", - "amapDeletedDelivery": "Livraison supprimée", - "amapDeletedOrder": "Commande supprimée", - "amapDeletedProduct": "Produit supprimé", - "amapDeleteProduct": "Supprimer le produit ?", - "amapDeleteProductDescription": "Voulez-vous vraiment supprimer ce produit ?", - "amapDeleting": "Suppression", - "amapDeletingDelivery": "Supprimer la livraison ?", - "amapDeletingError": "Erreur lors de la suppression", - "amapDeletingOrder": "Supprimer la commande ?", - "amapDeletingProduct": "Supprimer le produit ?", - "amapDeliver": "Livraison teminée ?", - "amapDeliveries": "Livraisons", - "amapDeliveringDelivery": "Toutes les commandes sont livrées ?", - "amapDelivery": "Livraison", - "amapDeliveryArchived": "Livraison archivée", - "amapDeliveryDate": "Date de livraison", - "amapDeliveryDelivered": "Livraison effectuée", - "amapDeliveryHistory": "Historique des livraisons", - "amapDeliveryList": "Liste des livraisons", - "amapDeliveryLocked": "Livraison verrouillée", - "amapDeliveryOn": "Livraison le", - "amapDeliveryOpened": "Livraison ouverte", - "amapDeliveryNotArchived": "Livraison non archivée", - "amapDeliveryNotLocked": "Livraison non verrouillée", - "amapDeliveryNotDelivered": "Livraison non effectuée", - "amapDeliveryNotOpened": "Livraison non ouverte", - "amapEditDelivery": "Modifier la livraison", - "amapEditedCommand": "Commande modifiée", - "amapEditingError": "Erreur lors de la modification", - "amapEditProduct": "Modifier le produit", - "amapEndingDelivery": "Fin de la livraison", - "amapError": "Erreur", - "amapErrorLink": "Erreur lors de l'ouverture du lien", - "amapErrorLoadingUser": "Erreur lors du chargement des utilisateurs", - "amapEvening": "Soir", - "amapExpectingNumber": "Veuillez entrer un nombre", - "amapFillField": "Veuillez remplir ce champ", - "amapHandlingAccount": "Gérer les comptes", - "amapLoading": "Chargement...", - "amapLoadingError": "Erreur lors du chargement", - "amapLock": "Verrouiller", - "amapLocked": "Verrouillée", - "amapLockedDelivery": "Livraison verrouillée", - "amapLockedOrder": "Commande verrouillée", - "amapLooking": "Rechercher", - "amapLockingDelivery": "Verrouiller la livraison ?", - "amapMidDay": "Midi", - "amapMyOrders": "Mes commandes", - "amapName": "Nom", - "amapNextStep": "Étape suivante", - "amapNoProduct": "Pas de produit", - "amapNoCurrentOrder": "Pas de commande en cours", - "amapNoMoney": "Pas assez d'argent", - "amapNoOpennedDelivery": "Pas de livraison ouverte", - "amapNoOrder": "Pas de commande", - "amapNoSelectedDelivery": "Pas de livraison sélectionnée", - "amapNotEnoughMoney": "Pas assez d'argent", - "amapNotPlannedDelivery": "Pas de livraison planifiée", - "amapOneOrder": "commande", - "amapOpenDelivery": "Ouvrir", - "amapOpened": "Ouverte", - "amapOpenningDelivery": "Ouvrir la livraison ?", - "amapOrder": "Commander", - "amapOrders": "Commandes", - "amapPickChooseCategory": "Veuillez entrer une valeur ou choisir une catégorie existante", - "amapPickDeliveryMoment": "Choisissez un moment de livraison", - "amapPresentation": "Présentation", - "amapPresentation1": "L'AMAP (association pour le maintien d'une agriculture paysanne) est un service proposé par l'association Planet&Co de l'ECL. Vous pouvez ainsi recevoir des produits (paniers de fruits et légumes, jus, confitures...) directement sur le campus !\n\nLes commandes doivent être passées avant le vendredi 21h et sont livrées sur le campus le mardi de 13h à 13h45 (ou de 18h15 à 18h30 si vous ne pouvez pas passer le midi) dans le hall du M16.\n\nVous ne pouvez commander que si votre solde le permet. Vous pouvez recharger votre solde via la collecte Lydia ou bien avec un chèque que vous pouvez nous transmettre lors des permanences.\n\nLien vers la collecte Lydia pour le rechargement : ", - "amapPresentation2": "\n\nN'hésitez pas à nous contacter en cas de problème !", - "amapPrice": "Prix", - "amapProduct": "produit", - "amapProducts": "Produits", - "amapProductInDelivery": "Produit dans une livraison non terminée", - "amapQuantity": "Quantité", - "amapRequiredDate": "La date est requise", - "amapSeeMore": "Voir plus", - "amapThe": "Le", - "amapUnlock": "Dévérouiller", - "amapUnlockedDelivery": "Livraison dévérouillée", - "amapUnlockingDelivery": "Dévérouiller la livraison ?", - "amapUpdate": "Modifier", - "amapUpdatedAmount": "Solde modifié", - "amapUpdatedOrder": "Commande modifiée", - "amapUpdatedProduct": "Produit modifié", - "amapUpdatingError": "Echec de la modification", - "amapUsersNotFound": "Aucun utilisateur trouvé", - "amapWaiting": "En attente", - "bookingAdd": "Ajouter", - "bookingAddBookingPage": "Demande", - "bookingAddRoom": "Ajouter une salle", - "bookingAddBooking": "Ajouter une réservation", - "bookingAddedBooking": "Demande ajoutée", - "bookingAddedRoom": "Salle ajoutée", - "bookingAddedManager": "Gestionnaire ajouté", - "bookingAddingError": "Erreur lors de l'ajout", - "bookingAddManager": "Ajouter un gestionnaire", - "bookingAdminPage": "Administrateur", - "bookingAllDay": "Toute la journée", - "bookingBookedFor": "Réservé pour", - "bookingBooking": "Réservation", - "bookingBookingCreated": "Réservation créée", - "bookingBookingDemand": "Demande de réservation", - "bookingBookingNote": "Note de la réservation", - "bookingBookingPage": "Réservation", - "bookingBookingReason": "Motif de la réservation", - "bookingBy": "par", - "bookingConfirm": "Confirmer", - "bookingConfirmation": "Confirmation", - "bookingConfirmBooking": "Confirmer la réservation ?", - "bookingConfirmed": "Validée", - "bookingDates": "Dates", - "bookingDecline": "Refuser", - "bookingDeclineBooking": "Refuser la réservation ?", - "bookingDeclined": "Refusée", - "bookingDelete": "Supprimer", - "bookingDeleting": "Suppression", - "bookingDeleteBooking": "Suppression", - "bookingDeleteBookingConfirmation": "Êtes-vous sûr de vouloir supprimer cette réservation ?", - "bookingDeletedBooking": "Réservation supprimée", - "bookingDeletedRoom": "Salle supprimée", - "bookingDeletedManager": "Gestionnaire supprimé", - "bookingDeleteRoomConfirmation": "Êtes-vous sûr de vouloir supprimer cette salle ?\n\nLa salle ne doit avoir aucune réservation en cours ou à venir pour être supprimée", - "bookingDeleteManagerConfirmation": "Êtes-vous sûr de vouloir supprimer ce gestionnaire ?\n\nLe gestionnaire ne doit être associé à aucune salle pour pouvoir être supprimé", - "bookingDeletingBooking": "Supprimer la réservation ?", - "bookingDeletingError": "Erreur lors de la suppression", - "bookingDeletingRoom": "Supprimer la salle ?", - "bookingEdit": "Modifier", - "bookingEditBooking": "Modifier une réservation", - "bookingEditionError": "Erreur lors de la modification", - "bookingEditedBooking": "Réservation modifiée", - "bookingEditedRoom": "Salle modifiée", - "bookingEditedManager": "Gestionnaire modifié", - "bookingEditManager": "Modifier ou supprimer un gestionnaire", - "bookingEditRoom": "Modifier ou supprimer une salle", - "bookingEndDate": "Date de fin", - "bookingEndHour": "Heure de fin", - "bookingEntity": "Pour qui ?", - "bookingError": "Erreur", - "bookingEventEvery": "Tous les", - "bookingHistoryPage": "Historique", - "bookingIncorrectOrMissingFields": "Champs incorrects ou manquants", - "bookingInterval": "Intervalle", - "bookingInvalidIntervalError": "Intervalle invalide", - "bookingInvalidDates": "Dates invalides", - "bookingInvalidRoom": "Salle invalide", - "bookingKeysRequested": "Clés demandées", - "bookingManagement": "Gestion", - "bookingManager": "Gestionnaire", - "bookingManagerName": "Nom du gestionnaire", - "bookingMultipleDay": "Plusieurs jours", - "bookingMyBookings": "Mes réservations", - "bookingNecessaryKey": "Clé nécessaire", - "bookingNext": "Suivant", - "bookingNo": "Non", - "bookingNoCurrentBooking": "Pas de réservation en cours", - "bookingNoDateError": "Veuillez choisir une date", - "bookingNoAppointmentInReccurence": "Aucun créneau existe avec ces paramètres de récurrence", - "bookingNoDaySelected": "Aucun jour sélectionné", - "bookingNoDescriptionError": "Veuillez entrer une description", - "bookingNoKeys": "Aucune clé", - "bookingNoNoteError": "Veuillez entrer une note", - "bookingNoPhoneRegistered": "Numéro non renseigné", - "bookingNoReasonError": "Veuillez entrer un motif", - "bookingNoRoomFoundError": "Aucune salle enregistrée", - "bookingNoRoomFound": "Aucune salle trouvée", - "bookingNote": "Note", - "bookingOther": "Autre", - "bookingPending": "En attente", - "bookingPrevious": "Précédent", - "bookingReason": "Motif", - "bookingRecurrence": "Récurrence", - "bookingRecurrenceDays": "Jours de récurrence", - "bookingRecurrenceEndDate": "Date de fin de récurrence", - "bookingRecurrent": "Récurrent", - "bookingRegisteredRooms": "Salles enregistrées", - "bookingRoom": "Salle", - "bookingRoomName": "Nom de la salle", - "bookingStartDate": "Date de début", - "bookingStartHour": "Heure de début", - "bookingWeeks": "Semaines", - "bookingYes": "Oui", - "bookingWeekDayMon": "Lundi", - "bookingWeekDayTue": "Mardi", - "bookingWeekDayWed": "Mercredi", - "bookingWeekDayThu": "Jeudi", - "bookingWeekDayFri": "Vendredi", - "bookingWeekDaySat": "Samedi", - "bookingWeekDaySun": "Dimanche", - "cinemaAdd": "Ajouter", - "cinemaAddedSession": "Séance ajoutée", - "cinemaAddingError": "Erreur lors de l'ajout", - "cinemaAddSession": "Ajouter une séance", - "cinemaCinema": "Cinéma", - "cinemaDeleteSession": "Supprimer la séance ?", - "cinemaDeleting": "Suppression", - "cinemaDuration": "Durée", - "cinemaEdit": "Modifier", - "cinemaEditedSession": "Séance modifiée", - "cinemaEditingError": "Erreur lors de la modification", - "cinemaEditSession": "Modifier la séance", - "cinemaEmptyUrl": "Veuillez entrer une URL", - "cinemaImportFromTMDB": "Importer depuis TMDB", - "cinemaIncomingSession": "A l'affiche", - "cinemaIncorrectOrMissingFields": "Champs incorrects ou manquants", - "cinemaInvalidUrl": "URL invalide", - "cinemaGenre": "Genre", - "cinemaName": "Nom", - "cinemaNoDateError": "Veuillez entrer une date", - "cinemaNoDuration": "Veuillez entrer une durée", - "cinemaNoOverview": "Aucun synopsis", - "cinemaNoPoster": "Aucune affiche", - "cinemaNoSession": "Aucune séance", - "cinemaOverview": "Synopsis", - "cinemaPosterUrl": "URL de l'affiche", - "cinemaSessionDate": "Jour de la séance", - "cinemaStartHour": "Heure de début", - "cinemaTagline": "Slogan", - "cinemaThe": "Le", - "drawerAdmin": "Administration", - "drawerAndroidAppLink": "https://play.google.com/store/apps/details?id=fr.myecl.titan", - "drawerCopied": "Copié !", - "drawerDownloadAppOnMobileDevice": "Ce site est la version Web de l'application MyECL. Nous vous invitons à télécharger l'application. N'utilisez ce site qu'en cas de problème avec l'application.\n", - "drawerIosAppLink": "https://apps.apple.com/fr/app/myecl/id6444443430", - "drawerLoginOut": "Voulez-vous vous déconnecter ?", - "drawerLogOut": "Déconnexion", - "drawerOr": " ou ", - "drawerSettings": "Paramètres", - "eventAdd": "Ajouter", - "eventAddEvent": "Ajouter un événement", - "eventAddedEvent": "Événement ajouté", - "eventAddingError": "Erreur lors de l'ajout", - "eventAllDay": "Toute la journée", - "eventConfirm": "Confirmer", - "eventConfirmEvent": "Confirmer l'événement ?", - "eventConfirmation": "Confirmation", - "eventConfirmed": "Confirmé", - "eventDates": "Dates", - "eventDecline": "Refuser", - "eventDeclineEvent": "Refuser l'événement ?", - "eventDeclined": "Refusé", - "eventDelete": "Supprimer", - "eventDeletedEvent": "Événement supprimé", - "eventDeleting": "Suppression", - "eventDeletingError": "Erreur lors de la suppression", - "eventDeletingEvent": "Supprimer l'événement ?", - "eventDescription": "Description", - "eventEdit": "Modifier", - "eventEditEvent": "Modifier un événement", - "eventEditedEvent": "Événement modifié", - "eventEditingError": "Erreur lors de la modification", - "eventEndDate": "Date de fin", - "eventEndHour": "Heure de fin", - "eventError": "Erreur", - "eventEventList": "Liste des événements", - "eventEventType": "Type d'événement", - "eventEvery": "Tous les", - "eventHistory": "Historique", - "eventIncorrectOrMissingFields": "Certains champs sont incorrects ou manquants", - "eventInterval": "Intervalle", - "eventInvalidDates": "La date de fin doit être après la date de début", - "eventInvalidIntervalError": "Veuillez entrer un intervalle valide", - "eventLocation": "Lieu", - "eventMyEvents": "Mes événements", - "eventName": "Nom", - "eventNext": "Suivant", - "eventNo": "Non", - "eventNoCurrentEvent": "Aucun événement en cours", - "eventNoDateError": "Veuillez entrer une date", - "eventNoDaySelected": "Aucun jour sélectionné", - "eventNoDescriptionError": "Veuillez entrer une description", - "eventNoEvent": "Aucun événement", - "eventNoNameError": "Veuillez entrer un nom", - "eventNoOrganizerError": "Veuillez entrer un organisateur", - "eventNoPlaceError": "Veuillez entrer un lieu", - "eventNoPhoneRegistered": "Numéro non renseigné", - "eventNoRuleError": "Veuillez entrer une règle de récurrence", - "eventOrganizer": "Organisateur", - "eventOther": "Autre", - "eventPending": "En attente", - "eventPrevious": "Précédent", - "eventRecurrence": "Récurrence", - "eventRecurrenceDays": "Jours de récurrence", - "eventRecurrenceEndDate": "Date de fin de la récurrence", - "eventRecurrenceRule": "Règle de récurrence", - "eventRoom": "Salle", - "eventStartDate": "Date de début", - "eventStartHour": "Heure de début", - "eventTitle": "Événements", - "eventYes": "Oui", - "eventEventEvery": "Toutes les", - "eventWeeks": "semaines", - "eventDayMon": "Lundi", - "eventDayTue": "Mardi", - "eventDayWed": "Mercredi", - "eventDayThu": "Jeudi", - "eventDayFri": "Vendredi", - "eventDaySat": "Samedi", - "eventDaySun": "Dimanche", - "globalConfirm": "Confirmer", - "globalCancel": "Annuler", - "globalIrreversibleAction": "Cette action est irréversible", - "globalOptionnal": "{text} (Optionnel)", - "@globalOptionnal": { - "description": "Texte avec complément optionnel", - "placeholders": { - "text": { - "type": "String" - } - } - }, - "homeCalendar": "Calendrier", - "homeEventOf": "Évènements du", - "homeIncomingEvents": "Évènements à venir", - "homeLastInfos": "Dernières annonces", - "homeNoEvents": "Aucun évènement", - "homeTranslateDayShortMon": "Lun", - "homeTranslateDayShortTue": "Mar", - "homeTranslateDayShortWed": "Mer", - "homeTranslateDayShortThu": "Jeu", - "homeTranslateDayShortFri": "Ven", - "homeTranslateDayShortSat": "Sam", - "homeTranslateDayShortSun": "Dim", - "loanAdd": "Ajouter", - "loanAddLoan": "Ajouter un prêt", - "loanAddObject": "Ajouter un objet", - "loanAddedLoan": "Prêt ajouté", - "loanAddedObject": "Objet ajouté", - "loanAddedRoom": "Salle ajoutée", - "loanAddingError": "Erreur lors de l'ajout", - "loanAdmin": "Administrateur", - "loanAvailable": "Disponible", - "loanAvailableMultiple": "Disponibles", - "loanBorrowed": "Emprunté", - "loanBorrowedMultiple": "Empruntés", - "loanAnd": "et", - "loanAssociation": "Association", - "loanAvailableItems": "Objets disponibles", - "loanBeginDate": "Date du début du prêt", - "loanBorrower": "Emprunteur", - "loanCaution": "Caution", - "loanCancel": "Annuler", - "loanConfirm": "Confirmer", - "loanConfirmation": "Confirmation", - "loanDates": "Dates", - "loanDays": "Jours", - "loanDelay": "Délai de la prolongation", - "loanDelete": "Supprimer", - "loanDeletingLoan": "Supprimer le prêt ?", - "loanDeletedItem": "Objet supprimé", - "loanDeletedLoan": "Prêt supprimé", - "loanDeleting": "Suppression", - "loanDeletingError": "Erreur lors de la suppression", - "loanDeletingItem": "Supprimer l'objet ?", - "loanDuration": "Durée", - "loanEdit": "Modifier", - "loanEditItem": "Modifier l'objet", - "loanEditLoan": "Modifier le prêt", - "loanEditedRoom": "Salle modifiée", - "loanEndDate": "Date de fin du prêt", - "loanEnded": "Terminé", - "loanEnterDate": "Veuillez entrer une date", - "loanExtendedLoan": "Prêt prolongé", - "loanExtendingError": "Erreur lors de la prolongation", - "loanHistory": "Historique", - "loanIncorrectOrMissingFields": "Des champs sont manquants ou incorrects", - "loanInvalidNumber": "Veuillez entrer un nombre", - "loanInvalidDates": "Les dates ne sont pas valides", - "loanItem": "Objet", - "loanItems": "Objets", - "loanItemHandling": "Gestion des objets", - "loanItemSelected": "objet sélectionné", - "loanItemsSelected": "objets sélectionnés", - "loanLendingDuration": "Durée possible du prêt", - "loanLoan": "Prêt", - "loanLoanHandling": "Gestion des prêts", - "loanLooking": "Rechercher", - "loanName": "Nom", - "loanNext": "Suivant", - "loanNo": "Non", - "loanNoAssociationsFounded": "Aucune association trouvée", - "loanNoAvailableItems": "Aucun objet disponible", - "loanNoBorrower": "Aucun emprunteur", - "loanNoItems": "Aucun objet", - "loanNoItemSelected": "Aucun objet sélectionné", - "loanNoLoan": "Aucun prêt", - "loanNoReturnedDate": "Pas de date de retour", - "loanQuantity": "Quantité", - "loanNone": "Aucun", - "loanNote": "Note", - "loanNoValue": "Veuillez entrer une valeur", - "loanOnGoing": "En cours", - "loanOnGoingLoan": "Prêt en cours", - "loanOthers": "autres", - "loanPaidCaution": "Caution payée", - "loanPositiveNumber": "Veuillez entrer un nombre positif", - "loanPrevious": "Précédent", - "loanReturned": "Rendu", - "loanReturnedLoan": "Prêt rendu", - "loanReturningError": "Erreur lors du retour", - "loanReturningLoan": "Retour", - "loanReturnLoan": "Rendre le prêt ?", - "loanReturnLoanDescription": "Voulez-vous rendre ce prêt ?", - "loanToReturn": "A rendre", - "loanUnavailable": "Indisponible", - "loanUpdate": "Modifier", - "loanUpdatedItem": "Objet modifié", - "loanUpdatedLoan": "Prêt modifié", - "loanUpdatingError": "Erreur lors de la modification", - "loanYes": "Oui", - "loginAccountActivated": "Compte activé", - "loginAccountNotActivated": "Compte non activé", - "loginActivationCode": "Code d'activation", - "loginBirthday": "Date de naissance", - "loginCanBeEmpty": "Ce champ peut être vide", - "loginConfirmPassword": "Confirmer le mot de passe", - "loginCreate": "Créer", - "loginCreateAccount": "Créer un compte", - "loginCreateAccountTitle": "Créer un\ncompte", - "loginEmail": "Email", - "loginEmailEmpty": "Veuillez entrer une adresse mail", - "loginEmailInvalid": "Veuillez entrer une adresse mail de centrale.\nSi vous n'en possédez pas, veuillez contacter Éclair", - "loginEmptyFieldError": "Ce champ ne peut pas être vide", - "loginEndActivation": "Finaliser l'activation", - "loginEndResetPassword": "Finaliser la \nréinitialisation", - "loginErrorResetPassword": "Erreur lors de la réinitialisation", - "loginExpectingDate": "Une date est attendue", - "loginFillAllFields": "Veuillez remplir tous les champs", - "loginFirstname": "Prénom", - "loginFloor": "Étage", - "loginForgetPassword": "Mot de passe\noublié", - "loginForgotPassword": "Mot de passe oublié ?", - "loginInvalidToken": "Code d'activation invalide", - "loginLoginFailed": "Échec de la connexion", - "loginMailSendingError": "Erreur lors de la création du compte", - "loginMustBeIntError": "Ce champ doit être un entier", - "loginName": "Nom", - "loginNewPassword": "Nouveau mot de passe", - "loginPassword": "Mot de passe", - "loginPasswordLengthError": "Le mot de passe doit faire au moins 6 caractères", - "loginPasswordUppercaseError": "Le mot de passe doit contenir au moins une majuscule", - "loginPasswordLowercaseError": "Le mot de passe doit contenir au moins une minucule", - "loginPasswordNumberError": "Le mot de passe doit contenir au moins un chiffre", - "loginPasswordSpecialCaracterError": "Le mot de passe doit contenir au moins un caractère spécial", - "loginPasswordMustMatch": "Les mots de passe doivent correspondre", - "loginPasswordStrengthVeryWeak": "Très faible", - "loginPasswordStrengthWeak": "Faible", - "loginPasswordStrengthMedium": "Moyen", - "loginPasswordStrengthStrong": "Fort", - "loginPasswordStrengthVeryStrong": "Très fort", - "loginPhone": "Téléphone", - "loginPromo": "Promo entrante (ex : 2023)", - "loginSendedMail": "Mail de confirmation envoyé", - "loginSendedResetMail": "Mail de réinitialisation envoyé", - "loginSignIn": "Se connecter", - "loginRegister": "S'inscrire", - "loginRecievedMail": "J'ai reçu le mail", - "loginRecover": "Réinitialiser", - "loginResetedPassword": "Mot de passe réinitialisé", - "loginResetPasswordTitle": "Réinitialiser\nle mot de \npasse", - "loginNickname": "Surnom", - "loginWelcomeBack": "Bienvenue", - "loginAppName": "MyECL", - "othersCheckInternetConnection": "Veuillez vérifier votre connexion internet", - "othersRetry": "Réessayer", - "othersTooOldVersion": "Votre version de l'application est trop ancienne.\n\nVeuillez mettre à jour l'application.", - "othersUnableToConnectToServer": "Impossible de se connecter au serveur", - "othersVersion": "Version", - "othersNoModule": "Aucun module disponible, veuillez réessayer ultérieurement 😢😢", - "othersAdmin": "Admin", - "othersError": "Une erreur est survenue", - "othersNoValue": "Veuillez entrer une valeur", - "othersInvalidNumber": "Veuillez entrer un nombre", - "othersNoDateError": "Veuillez entrer une date", - "othersImageSizeTooBig": "La taille de l'image ne doit pas dépasser 4 Mio", - "othersImageError": "Erreur lors de l'ajout de l'image", - "phAddNewJournal": "Ajouter un nouveau journal", - "phNameField": "Nom : ", - "phDateField": "Date : ", - "phDelete": "Voulez-vous vraiment supprimer ce journal ?", - "phIrreversibleAction": "Cette action est irréversible", - "phToHeavyFile": "Fichier trop volumineux", - "phAddPdfFile": "Ajouter un fichier PDF", - "phEditPdfFile": "Modifier le fichier PDF", - "phPhName": "Nom du PH", - "phDate": "Date", - "phAdded": "Ajouté", - "phEdited": "Modifié", - "phAddingFileError": "Erreur d'ajout", - "phMissingInformatonsOrPdf": "Informations manquantes ou fichier PDF manquant", - "phAdd": "Ajouter", - "phEdit": "Modifier", - "phSeePreviousJournal": "Voir les anciens journaux", - "phNoJournalInDatabase": "Pas encore de PH dans la base de donnée", - "phSuccesDowloading": "Téléchargé avec succès", - "phonebookAdd": "Ajouter", - "phonebookAddAssociation": "Ajouter une association", - "phonebookAddAssociationGroupement": "Ajouter un groupement d'association", - "phonebookAddedAssociation": "Association ajoutée", - "phonebookAddedMember": "Membre ajouté", - "phonebookAddingError": "Erreur lors de l'ajout", - "phonebookAddMember": "Ajouter un membre", - "phonebookAddRole": "Ajouter un rôle", - "phonebookAdmin": "Administration", - "phonebookAll": "Toutes", - "phonebookApparentName": "Nom public du rôle :", - "phonebookAssociation": "Association", - "phonebookAssociationDetail": "Détail de l'association :", - "phonebookAssociationGroupement": "Groupement d'association", - "phonebookAssociationKind": "Type d'association :", - "phonebookAssociationName": "Nom de l'association", - "phonebookAssociations": "Associations", - "phonebookCancel": "Annuler", - "phonebookChangeTermYear": "Passer au mandat {year}", - "@phonebookChangeTermYear": { - "description": "Permet de changer le mandat d'une association", - "placeholders": { - "year": { - "type": "int" - } - } - }, - "phonebookChangeTermConfirm": "Êtes-vous sûr de vouloir changer tout le mandat ?\nCette action est irréversible !", - "phonebookClose": "Fermer", - "phonebookConfirm": "Confirmer", - "phonebookCopied": "Copié dans le presse-papier", - "phonebookDeactivateAssociation": "Désactiver l'association", - "phonebookDeactivatedAssociation": "Association désactivée", - "phonebookDeactivatedAssociationWarning": "Attention, cette association est désactivée, vous ne pouvez pas la modifier", - "phonebookDeactivateSelectedAssociation": "Désactiver l'association {association} ?", - "@phonebookDeactivateSelectedAssociation": { - "description": "Permet de désactiver une association", - "placeholders": { - "association": { - "type": "String" - } - } - }, - "phonebookDeactivatingError": "Erreur lors de la désactivation", - "phonebookDetail": "Détail :", - "phonebookDelete": "Supprimer", - "phonebookDeleteAssociation": "Supprimer l'association", - "phonebookDeleteSelectedAssociation": "Supprimer l'association {association} ?", - "@phonebookDeleteSelectedAssociation": { - "description": "Permet de supprimer une association", - "placeholders": { - "association": { - "type": "String" - } - } - }, - "phonebookDeleteAssociationDescription": "Ceci va supprimer l'historique de l'association", - "phonebookDeletedAssociation": "Association supprimée", - "phonebookDeletedMember": "Membre supprimé", - "phonebookDeleteRole": "Supprimer le rôle", - "phonebookDeleteUserRole": "Supprimer le rôle de l'utilisateur {name} ?", - "@phonebookDeleteUserRole": { - "description": "Permet de supprimer le rôle d'un utilisateur dans une association", - "placeholders": { - "name": { - "type": "String" - } - } - }, - "phonebookDeleting": "Suppression", - "phonebookDeletingError": "Erreur lors de la suppression", - "phonebookDescription": "Description", - "phonebookEdit": "Modifier", - "phonebookEditAssociationGroupement": "Modifier le groupement d'association", - "phonebookEditAssociationGroups": "Gérer les groupes", - "phonebookEditAssociationInfo": "Modifier", - "phonebookEditAssociationMembers": "Gérer les membres", - "phonebookEditRole": "Modifier le rôle", - "phonebookEmail": "Email :", - "phonebookEmailCopied": "Email copié dans le presse-papier", - "phonebookEmptyApparentName": "Veuillez entrer un nom de role", - "phonebookEmptyFieldError": "Un champ n'est pas rempli", - "phonebookEmptyKindError": "Veuillez choisir un type d'association", - "phonebookEmptyMember": "Aucun membre sélectionné", - "phonebookErrorAssociationLoading": "Erreur lors du chargement de l'association", - "phonebookErrorAssociationNameEmpty": "Veuillez entrer un nom d'association", - "phonebookErrorAssociationPicture": "Erreur lors de la modification de la photo d'association", - "phonebookErrorKindsLoading": "Erreur lors du chargement des types d'association", - "phonebookErrorLoadAssociationList": "Erreur lors du chargement de la liste des associations", - "phonebookErrorLoadAssociationMember": "Erreur lors du chargement des membres de l'association", - "phonebookErrorLoadAssociationPicture": "Erreur lors du chargement de la photo d'association", - "phonebookErrorLoadProfilePicture": "Erreur", - "phonebookErrorRoleTagsLoading": "Erreur lors du chargement des tags de rôle", - "phonebookExistingMembership": "Ce membre est déjà dans le mandat actuel", - "phonebookFilter": "Filtrer", - "phonebookFilterDescription": "Sélectionnez un ou plusieurs groupements pour filtrer les associations.", - "phonebookFirstname": "Prénom :", - "phonebookGroupementDeleted": "Groupement d'association supprimé", - "phonebookGroupementDeleteError": "Erreur lors de la suppression du groupement d'association", - "phonebookGroupementName": "Nom du groupement", - "phonebookGroups": "Gérer les groupes de {association}", - "@phonebookGroups": { - "description": "Permet de gérer les groupes d'une association", - "placeholders": { - "association": { - "type": "String" - } - } - }, - "phonebookTerm": "Mandat {year}", - "@phonebookTerm": { - "description": "Année de mandat d'une association", - "placeholders": { - "year": { - "type": "int" - } - } - }, - "phonebookTermChangingError": "Erreur lors du changement de mandat", - "phonebookMember": "Membre", - "phonebookMemberReordered": "Membre réordonné", - "phonebookMembers": "Gérer les membres de {association}", - "@phonebookMembers": { - "description": "Permet de gérer les membres d'une association", - "placeholders": { - "association": { - "type": "String" - } - } - }, - "phonebookMembershipAssociationError": "Veuillez choisir une association", - "phonebookMembershipRole": "Rôle :", - "phonebookMembershipRoleError": "Veuillez choisir un rôle", - "phonebookModifyMembership": "Modifier le rôle de {name}", - "@phonebookModifyMembership": { - "description": "Permet de modifier le rôle d'un membre dans une association", - "placeholders": { - "name": { - "type": "String" - } - } - }, - "phonebookName": "Nom :", - "phonebookNameCopied": "Nom et prénom copié dans le presse-papier", - "phonebookNamePure": "Nom", - "phonebookNewTerm": "Nouveau mandat", - "phonebookNewTermConfirmed": "Mandat changé", - "phonebookNickname": "Surnom :", - "phonebookNicknameCopied": "Surnom copié dans le presse-papier", - "phonebookNoAssociationFound": "Aucune association trouvée", - "phonebookNoMember": "Aucun membre", - "phonebookNoMemberRole": "Aucun role trouvé", - "phonebookNoRoleTags": "Aucun tag de rôle trouvé", - "phonebookPhone": "Téléphone :", - "phonebookPhonebook": "Annuaire", - "phonebookPhonebookSearch": "Rechercher", - "phonebookPhonebookSearchAssociation": "Association", - "phonebookPhonebookSearchField": "Rechercher :", - "phonebookPhonebookSearchName": "Nom/Prénom/Surnom", - "phonebookPhonebookSearchRole": "Poste", - "phonebookPresidentRoleTag": "Prez'", - "phonebookPromoNotGiven": "Promo non renseignée", - "phonebookPromotion": "Promotion {year}", - "@phonebookPromotion": { - "description": "Année de promotion d'un membre", - "placeholders": { - "year": { - "type": "int" - } - } - }, - "phonebookReorderingError": "Erreur lors du réordonnement", - "phonebookResearch": "Rechercher", - "phonebookRolePure": "Rôle", - "phonebookSearchUser": "Rechercher un utilisateur", - "phonebookTooHeavyAssociationPicture": "L'image est trop lourde (max 4Mo)", - "phonebookUpdateGroups": "Mettre à jour les groupes", - "phonebookUpdatedAssociation": "Association modifiée", - "phonebookUpdatedAssociationPicture": "La photo d'association a été changée", - "phonebookUpdatedGroups": "Groupes mis à jour", - "phonebookUpdatedMember": "Membre modifié", - "phonebookUpdatingError": "Erreur lors de la modification", - "phonebookValidation": "Valider", - "purchasesPurchases": "Achats", - "purchasesResearch": "Rechercher", - "purchasesNoPurchasesFound": "Aucun achat trouvé", - "purchasesNoTickets": "Aucun ticket", - "purchasesTicketsError": "Erreur lors du chargement des tickets", - "purchasesPurchasesError": "Erreur lors du chargement des achats", - "purchasesNoPurchases": "Aucun achat", - "purchasesTimes": "fois", - "purchasesAlreadyUsed": "Déjà utilisé", - "purchasesNotPaid": "Non validé", - "purchasesPleaseSelectProduct": "Veuillez sélectionner un produit", - "purchasesProducts": "Produits", - "purchasesCancel": "Annuler", - "purchasesValidate": "Valider", - "purchasesLeftScan": "Scans restants", - "purchasesTag": "Tag", - "purchasesHistory": "Historique", - "purchasesPleaseSelectSeller": "Veuillez sélectionner un vendeur", - "purchasesNoTagGiven": "Attention, aucun tag n'a été entré", - "purchasesTickets": "Tickets", - "purchasesNoScannableProducts": "Aucun produit scannable", - "purchasesLoading": "En attente de scan", - "purchasesScan": "Scanner", - "raffleRaffle": "Tombola", - "rafflePrize": "Lot", - "rafflePrizes": "Lots", - "raffleActualRaffles": "Tombola en cours", - "rafflePastRaffles": "Tombola passés", - "raffleYourTickets": "Tous vos tickets", - "raffleCreateMenu": "Menu de Création", - "raffleNextRaffles": "Prochaines tombolas", - "raffleNoTicket": "Vous n'avez pas de ticket", - "raffleSeeRaffleDetail": "Voir lots/tickets", - "raffleActualPrize": "Lots actuels", - "raffleMajorPrize": "Lot Majeurs", - "raffleTakeTickets": "Prendre vos tickets", - "raffleNoTicketBuyable": "Vous ne pouvez pas achetez de billets pour l'instant", - "raffleNoCurrentPrize": "Il n'y a aucun lots actuellement", - "raffleModifTombola": "Vous pouvez modifiez vos tombolas ou en créer de nouvelles, toute décision doit ensuite être prise par les admins", - "raffleCreateYourRaffle": "Votre menu de création de tombolas", - "rafflePossiblePrice": "Prix possible", - "raffleInformation": "Information et Statistiques", - "raffleAccounts": "Comptes", - "raffleAdd": "Ajouter", - "raffleUpdatedAmount": "Montant mis à jour", - "raffleUpdatingError": "Erreur lors de la mise à jour", - "raffleDeletedPrize": "Lot supprimé", - "raffleDeletingError": "Erreur lors de la suppression", - "raffleQuantity": "Quantité", - "raffleClose": "Fermer", - "raffleOpen": "Ouvrir", - "raffleAddTypeTicketSimple": "Ajouter", - "raffleAddingError": "Erreur lors de l'ajout", - "raffleEditTypeTicketSimple": "Modifier", - "raffleFillField": "Le champ ne peut pas être vide", - "raffleWaiting": "Chargement", - "raffleEditingError": "Erreur lors de la modification", - "raffleAddedTicket": "Ticket ajouté", - "raffleEditedTicket": "Ticket modifié", - "raffleAlreadyExistTicket": "Le ticket existe déjà", - "raffleNumberExpected": "Un entier est attendu", - "raffleDeletedTicket": "Ticket supprimé", - "raffleAddPrize": "Ajouter", - "raffleEditPrize": "Modifier", - "raffleOpenRaffle": "Ouvrir la tombola", - "raffleCloseRaffle": "Fermer la tombola", - "raffleOpenRaffleDescription": "Vous allez ouvrir la tombola, les utilisateurs pourront acheter des tickets. Vous ne pourrez plus modifier la tombola. Êtes-vous sûr de vouloir continuer ?", - "raffleCloseRaffleDescription": "Vous allez fermer la tombola, les utilisateurs ne pourront plus acheter de tickets. Êtes-vous sûr de vouloir continuer ?", - "raffleNoCurrentRaffle": "Il n'y a aucune tombola en cours", - "raffleBoughtTicket": "Ticket acheté", - "raffleDrawingError": "Erreur lors du tirage", - "raffleInvalidPrice": "Le prix doit être supérieur à 0", - "raffleMustBePositive": "Le nombre doit être strictement positif", - "raffleDraw": "Tirer", - "raffleDrawn": "Tiré", - "raffleError": "Erreur", - "raffleGathered": "Récolté", - "raffleTickets": "Tickets", - "raffleTicket": "ticket", - "raffleWinner": "Gagnant", - "raffleNoPrize": "Aucun lot", - "raffleDeletePrize": "Supprimer le lot", - "raffleDeletePrizeDescription": "Vous allez supprimer le lot, êtes-vous sûr de vouloir continuer ?", - "raffleDrawing": "Tirage", - "raffleDrawingDescription": "Tirer le gagnant du lot ?", - "raffleDeleteTicket": "Supprimer le ticket", - "raffleDeleteTicketDescription": "Vous allez supprimer le ticket, êtes-vous sûr de vouloir continuer ?", - "raffleWinningTickets": "Tickets gagnants", - "raffleNoWinningTicketYet": "Les tickets gagnants seront affichés ici", - "raffleName": "Nom", - "raffleDescription": "Description", - "raffleBuyThisTicket": "Acheter ce ticket", - "raffleLockedRaffle": "Tombola verrouillée", - "raffleUnavailableRaffle": "Tombola indisponible", - "raffleNotEnoughMoney": "Vous n'avez pas assez d'argent", - "raffleWinnable": "gagnable", - "raffleNoDescription": "Aucune description", - "raffleAmount": "Solde", - "raffleLoading": "Chargement", - "raffleTicketNumber": "Nombre de ticket", - "rafflePrice": "Prix", - "raffleEditRaffle": "Modifier la tombola", - "raffleEdit": "Modifier", - "raffleAddPackTicket": "Ajouter un pack de ticket", - "recommendationRecommendation": "Bons plans", - "recommendationTitle": "Titre", - "recommendationLogo": "Logo", - "recommendationCode": "Code", - "recommendationSummary": "Court résumé", - "recommendationDescription": "Description", - "recommendationAdd": "Ajouter", - "recommendationEdit": "Modifier", - "recommendationDelete": "Supprimer", - "recommendationAddImage": "Veuillez ajouter une image", - "recommendationAddedRecommendation": "Bon plan ajouté", - "recommendationEditedRecommendation": "Bon plan modifié", - "recommendationDeleteRecommendationConfirmation": "Êtes-vous sûr de vouloir supprimer ce bon plan ?", - "recommendationDeleteRecommendation": "Suppresion", - "recommendationDeletingRecommendationError": "Erreur lors de la suppression", - "recommendationDeletedRecommendation": "Bon plan supprimé", - "recommendationIncorrectOrMissingFields": "Champs incorrects ou manquants", - "recommendationEditingError": "Échec de la modification", - "recommendationAddingError": "Échec de l'ajout", - "recommendationCopiedCode": "Code de réduction copié", - "seedLibraryAdd": "Ajouter", - "seedLibraryAddedPlant": "Plante ajoutée", - "seedLibraryAddedSpecies": "Espèce ajoutée", - "seedLibraryAddingError": "Erreur lors de l'ajout", - "seedLibraryAddPlant": "Déposer une plante", - "seedLibraryAddSpecies": "Ajouter une espèce", - "seedLibraryAll": "Toutes", - "seedLibraryAncestor": "Ancêtre", - "seedLibraryAround": "environ", - "seedLibraryAutumn": "Automne", - "seedLibraryBorrowedPlant": "Plante empruntée", - "seedLibraryBorrowingDate": "Date d'emprunt :", - "seedLibraryBorrowPlant": "Emprunter la plante", - "seedLibraryCard": "Carte", - "seedLibraryChoosingAncestor": "Veuillez choisir un ancêtre", - "seedLibraryChoosingSpecies": "Veuillez choisir une espèce", - "seedLibraryChoosingSpeciesOrAncestor": "Veuillez choisir une espèce ou un ancêtre", - "seedLibraryContact": "Contact :", - "seedLibraryDays": "jours", - "seedLibraryDeadMsg": "Voulez-vous déclarer la plante morte ?", - "seedLibraryDeadPlant": "Plante morte", - "seedLibraryDeathDate": "Date de mort", - "seedLibraryDeletedSpecies": "Espèce supprimée", - "seedLibraryDeleteSpecies": "Supprimer l'espèce ?", - "seedLibraryDeleting": "Suppression", - "seedLibraryDeletingError": "Erreur lors de la suppression", - "seedLibraryDepositNotAvailable": "Le dépôt de plantes n'est pas possible sans emprunter une plante au préalable", - "seedLibraryDescription": "Description", - "seedLibraryDifficulty": "Difficulté :", - "seedLibraryEdit": "Modifier", - "seedLibraryEditedPlant": "Plante modifiée", - "seedLibraryEditInformation": "Modifier les informations", - "seedLibraryEditingError": "Erreur lors de la modification", - "seedLibraryEditSpecies": "Modifier l'espèce", - "seedLibraryEmptyDifficultyError": "Veuillez choisir une difficulté", - "seedLibraryEmptyFieldError": "Veuillez remplir tous les champs", - "seedLibraryEmptyTypeError": "Veuillez choisir un type de plante", - "seedLibraryEndMonth": "Mois de fin :", - "seedLibraryFacebookUrl": "Lien Facebook", - "seedLibraryFilters": "Filtres", - "seedLibraryForum": "Oskour maman j'ai tué ma plante - Forum d'aide", - "seedLibraryForumUrl": "Lien Forum", - "seedLibraryHelpSheets": "Fiches sur les plantes", - "seedLibraryInformation": "Informations :", - "seedLibraryMaturationTime": "Temps de maturation", - "seedLibraryMonthJan": "Janvier", - "seedLibraryMonthFeb": "Février", - "seedLibraryMonthMar": "Mars", - "seedLibraryMonthApr": "Avril", - "seedLibraryMonthMay": "Mai", - "seedLibraryMonthJun": "Juin", - "seedLibraryMonthJul": "Juillet", - "seedLibraryMonthAug": "Août", - "seedLibraryMonthSep": "Septembre", - "seedLibraryMonthOct": "Octobre", - "seedLibraryMonthNov": "Novembre", - "seedLibraryMonthDec": "Décembre", - "seedLibraryMyPlants": "Mes plantes", - "seedLibraryName": "Nom", - "seedLibraryNbSeedsRecommended": "Nombre de graines recommandées", - "seedLibraryNbSeedsRecommendedError": "Veuillez entrer un nombre de graines recommandé supérieur à 0", - "seedLibraryNoDateError": "Veuillez entrer une date", - "seedLibraryNoFilteredPlants": "Aucune plante ne correspond à votre recherche. Essayez d'autres filtres.", - "seedLibraryNoMorePlant": "Aucune plante n'est disponible", - "seedLibraryNoPersonalPlants": "Vous n'avez pas encore de plantes dans votre grainothèque. Vous pouvez en ajouter en allant dans les stocks.", - "seedLibraryNoSpecies": "Aucune espèce trouvée", - "seedLibraryNoStockPlants": "Aucune plante disponible dans le stock", - "seedLibraryNotes": "Notes", - "seedLibraryOk": "OK", - "seedLibraryPlantationPeriod": "Période de plantation :", - "seedLibraryPlantationType": "Type de plantation :", - "seedLibraryPlantDetail": "Détail de la plante", - "seedLibraryPlantingDate": "Date de plantation", - "seedLibraryPlantingNow": "Je la plante maintenant", - "seedLibraryPrefix": "Préfixe", - "seedLibraryPrefixError": "Prefixe déjà utilisé", - "seedLibraryPrefixLengthError": "Le préfixe doit faire 3 caractères", - "seedLibraryPropagationMethod": "Méthode de propagation :", - "seedLibraryReference": "Référence :", - "seedLibraryRemovedPlant": "Plante supprimée", - "seedLibraryRemovingError": "Erreur lors de la suppression", - "seedLibraryResearch": "Recherche", - "seedLibrarySaveChanges": "Sauvegarder les modifications", - "seedLibrarySeason": "Saison :", - "seedLibrarySeed": "Graine", - "seedLibrarySeeds": "graines", - "seedLibrarySeedDeposit": "Dépôt de plantes", - "seedLibrarySeedLibrary": "Grainothèque", - "seedLibrarySeedQuantitySimple": "Quantité de graines", - "seedLibrarySeedQuantity": "Quantité de graines :", - "seedLibraryShowDeadPlants": "Afficher les plantes mortes", - "seedLibrarySpecies": "Espèce :", - "seedLibrarySpeciesHelp": "Aide sur l'espèce", - "seedLibrarySpeciesPlural": "Espèces", - "seedLibrarySpeciesSimple": "Espèce", - "seedLibrarySpeciesType": "Type d'espèce :", - "seedLibrarySpring": "Printemps", - "seedLibraryStartMonth": "Mois de début :", - "seedLibraryStock": "Stock disponible", - "seedLibrarySummer": "Été", - "seedLibraryStocks": "Stocks", - "seedLibraryTimeUntilMaturation": "Temps avant maturation :", - "seedLibraryType": "Type :", - "seedLibraryUnableToOpen": "Impossible d'ouvrir le lien", - "seedLibraryUpdate": "Modifier", - "seedLibraryUpdatedInformation": "Informations modifiées", - "seedLibraryUpdatedSpecies": "Espèce modifiée", - "seedLibraryUpdatedPlant": "Plante modifiée", - "seedLibraryUpdatingError": "Erreur lors de la modification", - "seedLibraryWinter": "Hiver", - "seedLibraryWriteReference": "Veuillez écrire la référence suivante : ", - "settingsAccount": "Compte", - "settingsAddProfilePicture": "Ajouter une photo", - "settingsAdmin": "Administrateur", - "settingsAskHelp": "Demander de l'aide", - "settingsAssociation": "Association", - "settingsBirthday": "Date de naissance", - "settingsBugs": "Bugs", - "settingsChangePassword": "Changer de mot de passe", - "settingsChangingPassword": "Voulez-vous vraiment changer votre mot de passe ?", - "settingsConfirmPassword": "Confirmer le mot de passe", - "settingsCopied": "Copié !", - "settingsDarkMode": "Mode sombre", - "settingsDarkModeOff": "Désactivé", - "settingsDeleteLogs": "Supprimer les logs ?", - "settingsDeleteNotificationLogs": "Supprimer les logs des notifications ?", - "settingsDetelePersonalData": "Supprimer mes données personnelles", - "settingsDetelePersonalDataDesc": "Cette action notifie l'administrateur que vous souhaitez supprimer vos données personnelles.", - "settingsDeleting": "Suppresion", - "settingsEdit": "Modifier", - "settingsEditAccount": "Modifier mon profil", - "settingsEmail": "Email", - "settingsEmptyField": "Ce champ ne peut pas être vide", - "settingsErrorProfilePicture": "Erreur lors de la modification de la photo de profil", - "settingsErrorSendingDemand": "Erreur lors de l'envoi de la demande", - "settingsEventsIcal": "Lien Ical des événements", - "settingsExpectingDate": "Date de naissance attendue", - "settingsFirstname": "Prénom", - "settingsFloor": "Étage", - "settingsHelp": "Aide", - "settingsIcalCopied": "Lien Ical copié !", - "settingsLanguage": "Langue", - "settingsLanguageVar": "Français 🇫🇷", - "settingsLogs": "Logs", - "settingsModules": "Modules", - "settingsMyIcs": "Mon lien Ical", - "settingsName": "Nom", - "settingsNewPassword": "Nouveau mot de passe", - "settingsNickname": "Surnom", - "settingsNotifications": "Notifications", - "settingsOldPassword": "Ancien mot de passe", - "settingsPasswordChanged": "Mot de passe changé", - "settingsPasswordsNotMatch": "Les mots de passe ne correspondent pas", - "settingsPersonalData": "Données personnelles", - "settingsPersonalisation": "Personnalisation", - "settingsPhone": "Téléphone", - "settingsProfilePicture": "Photo de profil", - "settingsPromo": "Promotion", - "settingsRepportBug": "Signaler un bug", - "settingsSave": "Enregistrer", - "settingsSecurity": "Sécurité", - "settingsSendedDemand": "Demande envoyée", - "settingsSettings": "Paramètres", - "settingsTooHeavyProfilePicture": "L'image est trop lourde (max 4Mo)", - "settingsUpdatedProfile": "Profil modifié", - "settingsUpdatedProfilePicture": "Photo de profil modifiée", - "settingsUpdateNotification": "Mettre à jour les notifications", - "settingsUpdatingError": "Erreur lors de la modification du profil", - "settingsVersion": "Version", - "settingsPasswordStrength": "Force du mot de passe", - "settingsPasswordStrengthVeryWeak": "Très faible", - "settingsPasswordStrengthWeak": "Faible", - "settingsPasswordStrengthMedium": "Moyen", - "settingsPasswordStrengthStrong": "Fort", - "settingsPasswordStrengthVeryStrong": "Très fort", - "settingsPhoneNumber": "Numéro de téléphone", - "settingsValidate": "Valider", - "settingsEditedAccount": "Compte modifié avec succès", - "settingsFailedToEditAccount": "Échec de la modification du compte", - "settingsChooseLanguage": "Choix de la langue", - "settingsNotificationCounter": "{active}/{total} {active, plural, zero {activée} one {activée} other {activées}}", - "@settingsNotificationCounter": { - "description": "Affiche le nombre de notifications actives sur le total des notifications disponibles, avec gestion du pluriel", - "placeholders": { - "active": { - "type": "int" - }, - "total": { - "type": "int" - } - } - }, - "settingsEvent": "Événement", - "settingsIcal": "Lien Ical", - "settingsSynncWithCalendar": "Synchroniser avec votre calendrier", - "settingsIcalLinkCopied": "Lien Ical copié dans le presse-papier", - "settingsProfile": "Profil", - "voteAdd": "Ajouter", - "voteAddMember": "Ajouter un membre", - "voteAddedPretendance": "Liste ajoutée", - "voteAddedSection": "Section ajoutée", - "voteAddingError": "Erreur lors de l'ajout", - "voteAddPretendance": "Ajouter une liste", - "voteAddSection": "Ajouter une section", - "voteAll": "Tous", - "voteAlreadyAddedMember": "Membre déjà ajouté", - "voteAlreadyVoted": "Vote enregistré", - "voteChooseList": "Choisir une liste", - "voteClear": "Réinitialiser", - "voteClearVotes": "Réinitialiser les votes", - "voteClosedVote": "Votes clos", - "voteCloseVote": "Fermer les votes", - "voteConfirmVote": "Confirmer le vote", - "voteCountVote": "Dépouiller les votes", - "voteDeletedAll": "Tout supprimé", - "voteDeletedPipo": "Listes pipos supprimées", - "voteDeletedSection": "Section supprimée", - "voteDeleteAll": "Supprimer tout", - "voteDeleteAllDescription": "Voulez-vous vraiment supprimer tout ?", - "voteDeletePipo": "Supprimer les listes pipos", - "voteDeletePipoDescription": "Voulez-vous vraiment supprimer les listes pipos ?", - "voteDeletePretendance": "Supprimer la liste", - "voteDeletePretendanceDesc": "Voulez-vous vraiment supprimer cette liste ?", - "voteDeleteSection": "Supprimer la section", - "voteDeleteSectionDescription": "Voulez-vous vraiment supprimer cette section ?", - "voteDeletingError": "Erreur lors de la suppression", - "voteDescription": "Description", - "voteEdit": "Modifier", - "voteEditedPretendance": "Liste modifiée", - "voteEditedSection": "Section modifiée", - "voteEditingError": "Erreur lors de la modification", - "voteErrorClosingVotes": "Erreur lors de la fermeture des votes", - "voteErrorCountingVotes": "Erreur lors du dépouillement des votes", - "voteErrorResetingVotes": "Erreur lors de la réinitialisation des votes", - "voteErrorOpeningVotes": "Erreur lors de l'ouverture des votes", - "voteIncorrectOrMissingFields": "Champs incorrects ou manquants", - "voteMembers": "Membres", - "voteName": "Nom", - "voteNoPretendanceList": "Aucune liste de prétendance", - "voteNoSection": "Aucune section", - "voteCanNotVote": "Vous ne pouvez pas voter", - "voteNoSectionList": "Aucune section", - "voteNotOpenedVote": "Vote non ouvert", - "voteOnGoingCount": "Dépouillement en cours", - "voteOpenVote": "Ouvrir les votes", - "votePipo": "Pipo", - "votePretendance": "Listes", - "votePretendanceDeleted": "Prétendance supprimée", - "votePretendanceNotDeleted": "Erreur lors de la suppression", - "voteProgram": "Programme", - "votePublish": "Publier", - "votePublishVoteDescription": "Voulez-vous vraiment publier les votes ?", - "voteResetedVotes": "Votes réinitialisés", - "voteResetVote": "Réinitialiser les votes", - "voteResetVoteDescription": "Que voulez-vous faire ?", - "voteRole": "Rôle", - "voteSectionDescription": "Description de la section", - "voteSection": "Section", - "voteSectionName": "Nom de la section", - "voteSeeMore": "Voir plus", - "voteSelected": "Sélectionné", - "voteShowVotes": "Voir les votes", - "voteVote": "Vote", - "voteVoteError": "Erreur lors de l'enregistrement du vote", - "voteVoteFor": "Voter pour ", - "voteVoteNotStarted": "Vote non ouvert", - "voteVoters": "Groupes votants", - "voteVoteSuccess": "Vote enregistré", - "voteVotes": "Voix", - "voteVotesClosed": "Votes clos", - "voteVotesCounted": "Votes dépouillés", - "voteVotesOpened": "Votes ouverts", - "voteWarning": "Attention", - "voteWarningMessage": "La sélection ne sera pas sauvegardée.\nVoulez-vous continuer ?", - "moduleAdvert": "Annonce", - "moduleAdvertDescription": "Gérer les annonces", + "@@locale": "fr", + "dateToday": "Aujourd'hui", + "dateYesterday": "Hier", + "dateTomorrow": "Demain", + "dateAt": "à", + "dateFrom": "de", + "dateTo": "à", + "dateBetweenDays": "au", + "dateStarting": "Commence", + "dateLast": "", + "dateUntil": "Jusqu'au", + "feedFilterAll": "Tous", + "feedFilterPending": "En attente", + "feedFilterApproved": "Approuvés", + "feedFilterRejected": "Rejetés", + "feedEmptyAll": "Aucun événement disponible", + "feedEmptyPending": "Aucun événement en attente de validation", + "feedEmptyApproved": "Aucun événement approuvé", + "feedEmptyRejected": "Aucun événement rejeté", + "feedEventManagement": "Gestion des événements", + "eventActionCampaign": "Tu peux voter", + "eventActionEvent": "Tu es invité", + "eventActionCampaignSubtitle": "Votez maintenant", + "eventActionEventSubtitle": "Répondez à l'invitation", + "eventActionCampaignButton": "Voter", + "eventActionEventButton": "Participer", + "eventActionCampaignValidated": "J'ai voté !", + "eventActionEventValidated": "Je viens !", + "adminAccountTypes": "Types de compte", + "adminAdd": "Ajouter", + "adminAddGroup": "Ajouter un groupe", + "adminAddMember": "Ajouter un membre", + "adminAddedGroup": "Groupe créé", + "adminAddedLoaner": "Préteur ajouté", + "adminAddedMember": "Membre ajouté", + "adminAddingError": "Erreur lors de l'ajout", + "adminAddingMember": "Ajout d'un membre", + "adminAddLoaningGroup": "Ajouter un groupe de prêt", + "adminAddSchool": "Ajouter une école", + "adminAddStructure": "Ajouter une structure", + "adminAddedSchool": "École créée", + "adminAddedStructure": "Structure ajoutée", + "adminEditedStructure": "Structure modifiée", + "adminAdministration": "Administration", + "adminAssociationMembership": "Adhésion", + "adminAssociationMembershipName": "Nom de l'adhésion", + "adminAssociationsMemberships": "Adhésions", + "adminClearFilters": "Effacer les filtres", + "adminCreateAssociationMembership": "Créer une adhésion", + "adminCreatedAssociationMembership": "Adhésion créée", + "adminCreationError": "Erreur lors de la création", + "adminDateError": "La date de début doit être avant la date de fin", + "adminDelete": "Supprimer", + "adminDeleteAssociationMembership": "Supprimer l'adhésion ?", + "adminDeletedAssociationMembership": "Adhésion supprimée", + "adminDeleteGroup": "Supprimer le groupe", + "adminDeletedGroup": "Groupe supprimé", + "adminDeleteSchool": "Supprimer l'école ?", + "adminDeletedSchool": "École supprimée", + "adminDeleting": "Suppression", + "adminDeletingError": "Erreur lors de la suppression", + "adminDescription": "Description", + "adminEclSchool": "Centrale Lyon", + "adminEdit": "Modifier", + "adminEditStructure": "Modifier la structure", + "adminEditMembership": "Modifier l'adhésion", + "adminEmptyDate": "Date vide", + "adminEmptyFieldError": "Le nom ne peut pas être vide", + "adminEmailRegex": "Email Regex", + "adminEmptyUser": "Utilisateur vide", + "adminEndDate": "Date de fin", + "adminEndDateMaximal": "Date de fin maximale", + "adminEndDateMinimal": "Date de fin minimale", + "adminError": "Erreur", + "adminFilters": "Filtres", + "adminGroup": "Groupe", + "adminGroups": "Groupes", + "adminLoaningGroup": "Groupe de prêt", + "adminLooking": "Recherche", + "adminManager": "Administrateur de la structure", + "adminMaximum": "Maximum", + "adminMembers": "Membres", + "adminMembershipAddingError": "Erreur lors de l'ajout (surement dû à une superposition de dates)", + "adminMemberships": "Adhésions", + "adminMembershipUpdatingError": "Erreur lors de la modification (surement dû à une superposition de dates)", + "adminMinimum": "Minimum", + "adminModifyModuleVisibility": "Visibilité des modules", + "adminMyEclPay": "MyECLPay", + "adminName": "Nom", + "adminNoManager": "Aucun manager n'est sélectionné", + "adminNoMember": "Aucun membre", + "adminNoMoreLoaner": "Aucun prêteur n'est disponible", + "adminNoSchool": "Sans école", + "adminRemoveGroupMember": "Supprimer le membre du groupe ?", + "adminResearch": "Recherche", + "adminSchools": "Écoles", + "adminStructures": "Structures", + "adminStartDate": "Date de début", + "adminStartDateMaximal": "Date de début maximale", + "adminStartDateMinimal": "Date de début minimale", + "adminUpdatedAssociationMembership": "Adhésion modifiée", + "adminUpdatedGroup": "Groupe modifié", + "adminUpdatedMembership": "Adhésion modifiée", + "adminUpdatingError": "Erreur lors de la modification", + "adminUser": "Utilisateur", + "adminValidateFilters": "Valider les filtres", + "adminVisibilities": "Visibilités", + "adminGroupNotification": "Notification de groupe", + "adminNotifyGroup": "Notifier le groupe {groupName}", + "@adminNotifyGroup": { + "description": "Notifie les membres du groupe sélectionné", + "placeholders": { + "groupName": { + "type": "String" + } + } + }, + "adminTitle": "Titre", + "adminContent": "Contenu", + "adminSend": "Envoyer", + "adminNotificationSent": "Notification envoyée", + "adminFailedToSendNotification": "Échec de l'envoi de la notification", + "adminGroupsManagement": "Gestion des groupes", + "adminEditGroup": "Modifier le groupe", + "adminManageMembers": "Gérer les membres", + "adminDeleteGroupConfirmation": "Êtes-vous sûr de vouloir supprimer ce groupe ?", + "adminFailedToDeleteGroup": "Échec de la suppression du groupe", + "adminUsersAndGroups": "Utilisateurs et groupes", + "adminUsersManagement": "Gestion des utilisateurs", + "adminUsersManagementDescription": "Gérer les utilisateurs de l'application", + "adminManageUserGroups": "Gérer les groupes d'utilisateurs", + "adminSendNotificationToGroup": "Envoyer une notification à un groupe", + "adminPaiementModule": "Module de paiement", + "adminPaiement": "Paiement", + "adminManagePaiementStructures": "Gérer les structures du module de paiement", + "adminManageUsersAssociationMemberships": "Gérer les adhésions des utilisateurs", + "adminAssociationMembershipsManagement": "Gestion des adhésions", + "adminChooseGroupManager": "Groupe gestionnaire de l'adhésion", + "adminSelectManager": "Sélectionner un gestionnaire", + "adminInviteUsers": "Inviter des utilisateurs", + "adminImportList": "Importer une liste", + "adminInvitedUsers": "Utilisateurs invités", + "adminFailedToInviteUsers": "Échec de l'invitation des utilisateurs", + "adminDeleteUsers": "Supprimer des utilisateurs", + "adminAdmin": "Admin", + "adminAdverts": "Annonces", + "adminAnnouncers": "Annonceurs", + "adminManageAnnouncers": "Gérer les annonceurs", + "adminDeleteAnnouncer": "Supprimer cet annonceur ?", + "adminDeleteAnnouncerDescription": "Supprimer cet annonceurs supprimera toutes ses annonces.", + "advertAdd": "Ajouter", + "advertAddedAdvert": "Annonce publiée", + "advertAddedAnnouncer": "Annonceur ajouté", + "advertAddingError": "Erreur lors de l'ajout", + "advertAdmin": "Admin", + "advertAdvert": "Annonce", + "advertChoosingAnnouncer": "Veuillez choisir un annonceur", + "advertChoosingPoster": "Veuillez choisir une image", + "advertContent": "Contenu", + "advertDeleteAdvert": "Supprimer l'annonce", + "advertDeleteAnnouncer": "Supprimer l'annonceur ?", + "advertDeleting": "Suppression", + "advertEdit": "Modifier", + "advertEditedAdvert": "Annonce modifiée", + "advertEditingError": "Erreur lors de la modification", + "advertGroupAdvert": "Groupe", + "advertIncorrectOrMissingFields": "Champs incorrects ou manquants", + "advertInvalidNumber": "Veuillez entrer un nombre", + "advertManagement": "Gestion", + "advertModifyAnnouncingGroup": "Modifier un groupe d'annonce", + "advertNoMoreAnnouncer": "Aucun annonceur n'est disponible", + "advertNoValue": "Veuillez entrer une valeur", + "advertPositiveNumber": "Veuillez entrer un nombre positif", + "advertRemovedAnnouncer": "Annonceur supprimé", + "advertRemovingError": "Erreur lors de la suppression", + "advertTags": "Tags", + "advertTitle": "Titre", + "advertMonthJan": "Janv", + "advertMonthFeb": "Févr.", + "advertMonthMar": "Mars", + "advertMonthApr": "Avr.", + "advertMonthMay": "Mai", + "advertMonthJun": "Juin", + "advertMonthJul": "Juill.", + "advertMonthAug": "Août", + "advertMonthSep": "Sept.", + "advertMonthOct": "Oct.", + "advertMonthNov": "Nov.", + "advertMonthDec": "Déc.", + "amapAccounts": "Comptes", + "amapAdd": "Ajouter", + "amapAddDelivery": "Ajouter une livraison", + "amapAddedCommand": "Commande ajoutée", + "amapAddedOrder": "Commande ajoutée", + "amapAddedProduct": "Produit ajouté", + "amapAddedUser": "Utilisateur ajouté", + "amapAddProduct": "Ajouter un produit", + "amapAddUser": "Ajouter un utilisateur", + "amapAddingACommand": "Ajouter une commande", + "amapAddingCommand": "Ajouter la commande", + "amapAddingError": "Erreur lors de l'ajout", + "amapAddingProduct": "Ajouter un produit", + "amapAddOrder": "Ajouter une commande", + "amapAdmin": "Admin", + "amapAlreadyExistCommand": "Il existe déjà une commande à cette date", + "amapAmap": "Amap", + "amapAmount": "Solde", + "amapArchive": "Archiver", + "amapArchiveDelivery": "Archiver", + "amapArchivingDelivery": "Archivage de la livraison", + "amapCategory": "Catégorie", + "amapCloseDelivery": "Verrouiller", + "amapCommandDate": "Date de la commande", + "amapCommandProducts": "Produits de la commande", + "amapConfirm": "Confirmer", + "amapContact": "Contacts associatifs ", + "amapCreateCategory": "Créer une catégorie", + "amapDelete": "Supprimer", + "amapDeleteDelivery": "Supprimer la livraison ?", + "amapDeleteDeliveryDescription": "Voulez-vous vraiment supprimer cette livraison ?", + "amapDeletedDelivery": "Livraison supprimée", + "amapDeletedOrder": "Commande supprimée", + "amapDeletedProduct": "Produit supprimé", + "amapDeleteProduct": "Supprimer le produit ?", + "amapDeleteProductDescription": "Voulez-vous vraiment supprimer ce produit ?", + "amapDeleting": "Suppression", + "amapDeletingDelivery": "Supprimer la livraison ?", + "amapDeletingError": "Erreur lors de la suppression", + "amapDeletingOrder": "Supprimer la commande ?", + "amapDeletingProduct": "Supprimer le produit ?", + "amapDeliver": "Livraison teminée ?", + "amapDeliveries": "Livraisons", + "amapDeliveringDelivery": "Toutes les commandes sont livrées ?", + "amapDelivery": "Livraison", + "amapDeliveryArchived": "Livraison archivée", + "amapDeliveryDate": "Date de livraison", + "amapDeliveryDelivered": "Livraison effectuée", + "amapDeliveryHistory": "Historique des livraisons", + "amapDeliveryList": "Liste des livraisons", + "amapDeliveryLocked": "Livraison verrouillée", + "amapDeliveryOn": "Livraison le", + "amapDeliveryOpened": "Livraison ouverte", + "amapDeliveryNotArchived": "Livraison non archivée", + "amapDeliveryNotLocked": "Livraison non verrouillée", + "amapDeliveryNotDelivered": "Livraison non effectuée", + "amapDeliveryNotOpened": "Livraison non ouverte", + "amapEditDelivery": "Modifier la livraison", + "amapEditedCommand": "Commande modifiée", + "amapEditingError": "Erreur lors de la modification", + "amapEditProduct": "Modifier le produit", + "amapEndingDelivery": "Fin de la livraison", + "amapError": "Erreur", + "amapErrorLink": "Erreur lors de l'ouverture du lien", + "amapErrorLoadingUser": "Erreur lors du chargement des utilisateurs", + "amapEvening": "Soir", + "amapExpectingNumber": "Veuillez entrer un nombre", + "amapFillField": "Veuillez remplir ce champ", + "amapHandlingAccount": "Gérer les comptes", + "amapLoading": "Chargement...", + "amapLoadingError": "Erreur lors du chargement", + "amapLock": "Verrouiller", + "amapLocked": "Verrouillée", + "amapLockedDelivery": "Livraison verrouillée", + "amapLockedOrder": "Commande verrouillée", + "amapLooking": "Rechercher", + "amapLockingDelivery": "Verrouiller la livraison ?", + "amapMidDay": "Midi", + "amapMyOrders": "Mes commandes", + "amapName": "Nom", + "amapNextStep": "Étape suivante", + "amapNoProduct": "Pas de produit", + "amapNoCurrentOrder": "Pas de commande en cours", + "amapNoMoney": "Pas assez d'argent", + "amapNoOpennedDelivery": "Pas de livraison ouverte", + "amapNoOrder": "Pas de commande", + "amapNoSelectedDelivery": "Pas de livraison sélectionnée", + "amapNotEnoughMoney": "Pas assez d'argent", + "amapNotPlannedDelivery": "Pas de livraison planifiée", + "amapOneOrder": "commande", + "amapOpenDelivery": "Ouvrir", + "amapOpened": "Ouverte", + "amapOpenningDelivery": "Ouvrir la livraison ?", + "amapOrder": "Commander", + "amapOrders": "Commandes", + "amapPickChooseCategory": "Veuillez entrer une valeur ou choisir une catégorie existante", + "amapPickDeliveryMoment": "Choisissez un moment de livraison", + "amapPresentation": "Présentation", + "amapPresentation1": "L'AMAP (association pour le maintien d'une agriculture paysanne) est un service proposé par l'association Planet&Co de l'ECL. Vous pouvez ainsi recevoir des produits (paniers de fruits et légumes, jus, confitures...) directement sur le campus !\n\nLes commandes doivent être passées avant le vendredi 21h et sont livrées sur le campus le mardi de 13h à 13h45 (ou de 18h15 à 18h30 si vous ne pouvez pas passer le midi) dans le hall du M16.\n\nVous ne pouvez commander que si votre solde le permet. Vous pouvez recharger votre solde via la collecte Lydia ou bien avec un chèque que vous pouvez nous transmettre lors des permanences.\n\nLien vers la collecte Lydia pour le rechargement : ", + "amapPresentation2": "\n\nN'hésitez pas à nous contacter en cas de problème !", + "amapPrice": "Prix", + "amapProduct": "produit", + "amapProducts": "Produits", + "amapProductInDelivery": "Produit dans une livraison non terminée", + "amapQuantity": "Quantité", + "amapRequiredDate": "La date est requise", + "amapSeeMore": "Voir plus", + "amapThe": "Le", + "amapUnlock": "Dévérouiller", + "amapUnlockedDelivery": "Livraison dévérouillée", + "amapUnlockingDelivery": "Dévérouiller la livraison ?", + "amapUpdate": "Modifier", + "amapUpdatedAmount": "Solde modifié", + "amapUpdatedOrder": "Commande modifiée", + "amapUpdatedProduct": "Produit modifié", + "amapUpdatingError": "Echec de la modification", + "amapUsersNotFound": "Aucun utilisateur trouvé", + "amapWaiting": "En attente", + "bookingAdd": "Ajouter", + "bookingAddBookingPage": "Demande", + "bookingAddRoom": "Ajouter une salle", + "bookingAddBooking": "Ajouter une réservation", + "bookingAddedBooking": "Demande ajoutée", + "bookingAddedRoom": "Salle ajoutée", + "bookingAddedManager": "Gestionnaire ajouté", + "bookingAddingError": "Erreur lors de l'ajout", + "bookingAddManager": "Ajouter un gestionnaire", + "bookingAdminPage": "Administrateur", + "bookingAllDay": "Toute la journée", + "bookingBookedFor": "Réservé pour", + "bookingBooking": "Réservation", + "bookingBookingCreated": "Réservation créée", + "bookingBookingDemand": "Demande de réservation", + "bookingBookingNote": "Note de la réservation", + "bookingBookingPage": "Réservation", + "bookingBookingReason": "Motif de la réservation", + "bookingBy": "par", + "bookingConfirm": "Confirmer", + "bookingConfirmation": "Confirmation", + "bookingConfirmBooking": "Confirmer la réservation ?", + "bookingConfirmed": "Validée", + "bookingDates": "Dates", + "bookingDecline": "Refuser", + "bookingDeclineBooking": "Refuser la réservation ?", + "bookingDeclined": "Refusée", + "bookingDelete": "Supprimer", + "bookingDeleting": "Suppression", + "bookingDeleteBooking": "Suppression", + "bookingDeleteBookingConfirmation": "Êtes-vous sûr de vouloir supprimer cette réservation ?", + "bookingDeletedBooking": "Réservation supprimée", + "bookingDeletedRoom": "Salle supprimée", + "bookingDeletedManager": "Gestionnaire supprimé", + "bookingDeleteRoomConfirmation": "Êtes-vous sûr de vouloir supprimer cette salle ?\n\nLa salle ne doit avoir aucune réservation en cours ou à venir pour être supprimée", + "bookingDeleteManagerConfirmation": "Êtes-vous sûr de vouloir supprimer ce gestionnaire ?\n\nLe gestionnaire ne doit être associé à aucune salle pour pouvoir être supprimé", + "bookingDeletingBooking": "Supprimer la réservation ?", + "bookingDeletingError": "Erreur lors de la suppression", + "bookingDeletingRoom": "Supprimer la salle ?", + "bookingEdit": "Modifier", + "bookingEditBooking": "Modifier une réservation", + "bookingEditionError": "Erreur lors de la modification", + "bookingEditedBooking": "Réservation modifiée", + "bookingEditedRoom": "Salle modifiée", + "bookingEditedManager": "Gestionnaire modifié", + "bookingEditManager": "Modifier ou supprimer un gestionnaire", + "bookingEditRoom": "Modifier ou supprimer une salle", + "bookingEndDate": "Date de fin", + "bookingEndHour": "Heure de fin", + "bookingEntity": "Pour qui ?", + "bookingError": "Erreur", + "bookingEventEvery": "Tous les", + "bookingHistoryPage": "Historique", + "bookingIncorrectOrMissingFields": "Champs incorrects ou manquants", + "bookingInterval": "Intervalle", + "bookingInvalidIntervalError": "Intervalle invalide", + "bookingInvalidDates": "Dates invalides", + "bookingInvalidRoom": "Salle invalide", + "bookingKeysRequested": "Clés demandées", + "bookingManagement": "Gestion", + "bookingManager": "Gestionnaire", + "bookingManagerName": "Nom du gestionnaire", + "bookingMultipleDay": "Plusieurs jours", + "bookingMyBookings": "Mes réservations", + "bookingNecessaryKey": "Clé nécessaire", + "bookingNext": "Suivant", + "bookingNo": "Non", + "bookingNoCurrentBooking": "Pas de réservation en cours", + "bookingNoDateError": "Veuillez choisir une date", + "bookingNoAppointmentInReccurence": "Aucun créneau existe avec ces paramètres de récurrence", + "bookingNoDaySelected": "Aucun jour sélectionné", + "bookingNoDescriptionError": "Veuillez entrer une description", + "bookingNoKeys": "Aucune clé", + "bookingNoNoteError": "Veuillez entrer une note", + "bookingNoPhoneRegistered": "Numéro non renseigné", + "bookingNoReasonError": "Veuillez entrer un motif", + "bookingNoRoomFoundError": "Aucune salle enregistrée", + "bookingNoRoomFound": "Aucune salle trouvée", + "bookingNote": "Note", + "bookingOther": "Autre", + "bookingPending": "En attente", + "bookingPrevious": "Précédent", + "bookingReason": "Motif", + "bookingRecurrence": "Récurrence", + "bookingRecurrenceDays": "Jours de récurrence", + "bookingRecurrenceEndDate": "Date de fin de récurrence", + "bookingRecurrent": "Récurrent", + "bookingRegisteredRooms": "Salles enregistrées", + "bookingRoom": "Salle", + "bookingRoomName": "Nom de la salle", + "bookingStartDate": "Date de début", + "bookingStartHour": "Heure de début", + "bookingWeeks": "Semaines", + "bookingYes": "Oui", + "bookingWeekDayMon": "Lundi", + "bookingWeekDayTue": "Mardi", + "bookingWeekDayWed": "Mercredi", + "bookingWeekDayThu": "Jeudi", + "bookingWeekDayFri": "Vendredi", + "bookingWeekDaySat": "Samedi", + "bookingWeekDaySun": "Dimanche", + "cinemaAdd": "Ajouter", + "cinemaAddedSession": "Séance ajoutée", + "cinemaAddingError": "Erreur lors de l'ajout", + "cinemaAddSession": "Ajouter une séance", + "cinemaCinema": "Cinéma", + "cinemaDeleteSession": "Supprimer la séance ?", + "cinemaDeleting": "Suppression", + "cinemaDuration": "Durée", + "cinemaEdit": "Modifier", + "cinemaEditedSession": "Séance modifiée", + "cinemaEditingError": "Erreur lors de la modification", + "cinemaEditSession": "Modifier la séance", + "cinemaEmptyUrl": "Veuillez entrer une URL", + "cinemaImportFromTMDB": "Importer depuis TMDB", + "cinemaIncomingSession": "A l'affiche", + "cinemaIncorrectOrMissingFields": "Champs incorrects ou manquants", + "cinemaInvalidUrl": "URL invalide", + "cinemaGenre": "Genre", + "cinemaName": "Nom", + "cinemaNoDateError": "Veuillez entrer une date", + "cinemaNoDuration": "Veuillez entrer une durée", + "cinemaNoOverview": "Aucun synopsis", + "cinemaNoPoster": "Aucune affiche", + "cinemaNoSession": "Aucune séance", + "cinemaOverview": "Synopsis", + "cinemaPosterUrl": "URL de l'affiche", + "cinemaSessionDate": "Jour de la séance", + "cinemaStartHour": "Heure de début", + "cinemaTagline": "Slogan", + "cinemaThe": "Le", + "drawerAdmin": "Administration", + "drawerAndroidAppLink": "https://play.google.com/store/apps/details?id=fr.myecl.titan", + "drawerCopied": "Copié !", + "drawerDownloadAppOnMobileDevice": "Ce site est la version Web de l'application MyECL. Nous vous invitons à télécharger l'application. N'utilisez ce site qu'en cas de problème avec l'application.\n", + "drawerIosAppLink": "https://apps.apple.com/fr/app/myecl/id6444443430", + "drawerLoginOut": "Voulez-vous vous déconnecter ?", + "drawerLogOut": "Déconnexion", + "drawerOr": " ou ", + "drawerSettings": "Paramètres", + "eventAdd": "Ajouter", + "eventAddEvent": "Ajouter un événement", + "eventAddedEvent": "Événement ajouté", + "eventAddingError": "Erreur lors de l'ajout", + "eventAllDay": "Toute la journée", + "eventConfirm": "Confirmer", + "eventConfirmEvent": "Confirmer l'événement ?", + "eventConfirmation": "Confirmation", + "eventConfirmed": "Confirmé", + "eventDates": "Dates", + "eventDecline": "Refuser", + "eventDeclineEvent": "Refuser l'événement ?", + "eventDeclined": "Refusé", + "eventDelete": "Supprimer", + "eventDeletedEvent": "Événement supprimé", + "eventDeleting": "Suppression", + "eventDeletingError": "Erreur lors de la suppression", + "eventDeletingEvent": "Supprimer l'événement ?", + "eventDescription": "Description", + "eventEdit": "Modifier", + "eventEditEvent": "Modifier un événement", + "eventEditedEvent": "Événement modifié", + "eventEditingError": "Erreur lors de la modification", + "eventEndDate": "Date de fin", + "eventEndHour": "Heure de fin", + "eventError": "Erreur", + "eventEventList": "Liste des événements", + "eventEventType": "Type d'événement", + "eventEvery": "Tous les", + "eventHistory": "Historique", + "eventIncorrectOrMissingFields": "Certains champs sont incorrects ou manquants", + "eventInterval": "Intervalle", + "eventInvalidDates": "La date de fin doit être après la date de début", + "eventInvalidIntervalError": "Veuillez entrer un intervalle valide", + "eventLocation": "Lieu", + "eventMyEvents": "Mes événements", + "eventName": "Nom", + "eventNext": "Suivant", + "eventNo": "Non", + "eventNoCurrentEvent": "Aucun événement en cours", + "eventNoDateError": "Veuillez entrer une date", + "eventNoDaySelected": "Aucun jour sélectionné", + "eventNoDescriptionError": "Veuillez entrer une description", + "eventNoEvent": "Aucun événement", + "eventNoNameError": "Veuillez entrer un nom", + "eventNoOrganizerError": "Veuillez entrer un organisateur", + "eventNoPlaceError": "Veuillez entrer un lieu", + "eventNoPhoneRegistered": "Numéro non renseigné", + "eventNoRuleError": "Veuillez entrer une règle de récurrence", + "eventOrganizer": "Organisateur", + "eventOther": "Autre", + "eventPending": "En attente", + "eventPrevious": "Précédent", + "eventRecurrence": "Récurrence", + "eventRecurrenceDays": "Jours de récurrence", + "eventRecurrenceEndDate": "Date de fin de la récurrence", + "eventRecurrenceRule": "Règle de récurrence", + "eventRoom": "Salle", + "eventStartDate": "Date de début", + "eventStartHour": "Heure de début", + "eventTitle": "Événements", + "eventYes": "Oui", + "eventEventEvery": "Toutes les", + "eventWeeks": "semaines", + "eventDayMon": "Lundi", + "eventDayTue": "Mardi", + "eventDayWed": "Mercredi", + "eventDayThu": "Jeudi", + "eventDayFri": "Vendredi", + "eventDaySat": "Samedi", + "eventDaySun": "Dimanche", + "globalConfirm": "Confirmer", + "globalCancel": "Annuler", + "globalIrreversibleAction": "Cette action est irréversible", + "globalOptionnal": "{text} (Optionnel)", + "@globalOptionnal": { + "description": "Texte avec complément optionnel", + "placeholders": { + "text": { + "type": "String" + } + } + }, + "homeCalendar": "Calendrier", + "homeEventOf": "Évènements du", + "homeIncomingEvents": "Évènements à venir", + "homeLastInfos": "Dernières annonces", + "homeNoEvents": "Aucun évènement", + "homeTranslateDayShortMon": "Lun", + "homeTranslateDayShortTue": "Mar", + "homeTranslateDayShortWed": "Mer", + "homeTranslateDayShortThu": "Jeu", + "homeTranslateDayShortFri": "Ven", + "homeTranslateDayShortSat": "Sam", + "homeTranslateDayShortSun": "Dim", + "loanAdd": "Ajouter", + "loanAddLoan": "Ajouter un prêt", + "loanAddObject": "Ajouter un objet", + "loanAddedLoan": "Prêt ajouté", + "loanAddedObject": "Objet ajouté", + "loanAddedRoom": "Salle ajoutée", + "loanAddingError": "Erreur lors de l'ajout", + "loanAdmin": "Administrateur", + "loanAvailable": "Disponible", + "loanAvailableMultiple": "Disponibles", + "loanBorrowed": "Emprunté", + "loanBorrowedMultiple": "Empruntés", + "loanAnd": "et", + "loanAssociation": "Association", + "loanAvailableItems": "Objets disponibles", + "loanBeginDate": "Date du début du prêt", + "loanBorrower": "Emprunteur", + "loanCaution": "Caution", + "loanCancel": "Annuler", + "loanConfirm": "Confirmer", + "loanConfirmation": "Confirmation", + "loanDates": "Dates", + "loanDays": "Jours", + "loanDelay": "Délai de la prolongation", + "loanDelete": "Supprimer", + "loanDeletingLoan": "Supprimer le prêt ?", + "loanDeletedItem": "Objet supprimé", + "loanDeletedLoan": "Prêt supprimé", + "loanDeleting": "Suppression", + "loanDeletingError": "Erreur lors de la suppression", + "loanDeletingItem": "Supprimer l'objet ?", + "loanDuration": "Durée", + "loanEdit": "Modifier", + "loanEditItem": "Modifier l'objet", + "loanEditLoan": "Modifier le prêt", + "loanEditedRoom": "Salle modifiée", + "loanEndDate": "Date de fin du prêt", + "loanEnded": "Terminé", + "loanEnterDate": "Veuillez entrer une date", + "loanExtendedLoan": "Prêt prolongé", + "loanExtendingError": "Erreur lors de la prolongation", + "loanHistory": "Historique", + "loanIncorrectOrMissingFields": "Des champs sont manquants ou incorrects", + "loanInvalidNumber": "Veuillez entrer un nombre", + "loanInvalidDates": "Les dates ne sont pas valides", + "loanItem": "Objet", + "loanItems": "Objets", + "loanItemHandling": "Gestion des objets", + "loanItemSelected": "objet sélectionné", + "loanItemsSelected": "objets sélectionnés", + "loanLendingDuration": "Durée possible du prêt", + "loanLoan": "Prêt", + "loanLoanHandling": "Gestion des prêts", + "loanLooking": "Rechercher", + "loanName": "Nom", + "loanNext": "Suivant", + "loanNo": "Non", + "loanNoAssociationsFounded": "Aucune association trouvée", + "loanNoAvailableItems": "Aucun objet disponible", + "loanNoBorrower": "Aucun emprunteur", + "loanNoItems": "Aucun objet", + "loanNoItemSelected": "Aucun objet sélectionné", + "loanNoLoan": "Aucun prêt", + "loanNoReturnedDate": "Pas de date de retour", + "loanQuantity": "Quantité", + "loanNone": "Aucun", + "loanNote": "Note", + "loanNoValue": "Veuillez entrer une valeur", + "loanOnGoing": "En cours", + "loanOnGoingLoan": "Prêt en cours", + "loanOthers": "autres", + "loanPaidCaution": "Caution payée", + "loanPositiveNumber": "Veuillez entrer un nombre positif", + "loanPrevious": "Précédent", + "loanReturned": "Rendu", + "loanReturnedLoan": "Prêt rendu", + "loanReturningError": "Erreur lors du retour", + "loanReturningLoan": "Retour", + "loanReturnLoan": "Rendre le prêt ?", + "loanReturnLoanDescription": "Voulez-vous rendre ce prêt ?", + "loanToReturn": "A rendre", + "loanUnavailable": "Indisponible", + "loanUpdate": "Modifier", + "loanUpdatedItem": "Objet modifié", + "loanUpdatedLoan": "Prêt modifié", + "loanUpdatingError": "Erreur lors de la modification", + "loanYes": "Oui", + "loginAccountActivated": "Compte activé", + "loginAccountNotActivated": "Compte non activé", + "loginActivationCode": "Code d'activation", + "loginBirthday": "Date de naissance", + "loginCanBeEmpty": "Ce champ peut être vide", + "loginConfirmPassword": "Confirmer le mot de passe", + "loginCreate": "Créer", + "loginCreateAccount": "Créer un compte", + "loginCreateAccountTitle": "Créer un\ncompte", + "loginEmail": "Email", + "loginEmailEmpty": "Veuillez entrer une adresse mail", + "loginEmailInvalid": "Veuillez entrer une adresse mail de centrale.\nSi vous n'en possédez pas, veuillez contacter Éclair", + "loginEmptyFieldError": "Ce champ ne peut pas être vide", + "loginEndActivation": "Finaliser l'activation", + "loginEndResetPassword": "Finaliser la \nréinitialisation", + "loginErrorResetPassword": "Erreur lors de la réinitialisation", + "loginExpectingDate": "Une date est attendue", + "loginFillAllFields": "Veuillez remplir tous les champs", + "loginFirstname": "Prénom", + "loginFloor": "Étage", + "loginForgetPassword": "Mot de passe\noublié", + "loginForgotPassword": "Mot de passe oublié ?", + "loginInvalidToken": "Code d'activation invalide", + "loginLoginFailed": "Échec de la connexion", + "loginMailSendingError": "Erreur lors de la création du compte", + "loginMustBeIntError": "Ce champ doit être un entier", + "loginName": "Nom", + "loginNewPassword": "Nouveau mot de passe", + "loginPassword": "Mot de passe", + "loginPasswordLengthError": "Le mot de passe doit faire au moins 6 caractères", + "loginPasswordUppercaseError": "Le mot de passe doit contenir au moins une majuscule", + "loginPasswordLowercaseError": "Le mot de passe doit contenir au moins une minucule", + "loginPasswordNumberError": "Le mot de passe doit contenir au moins un chiffre", + "loginPasswordSpecialCaracterError": "Le mot de passe doit contenir au moins un caractère spécial", + "loginPasswordMustMatch": "Les mots de passe doivent correspondre", + "loginPasswordStrengthVeryWeak": "Très faible", + "loginPasswordStrengthWeak": "Faible", + "loginPasswordStrengthMedium": "Moyen", + "loginPasswordStrengthStrong": "Fort", + "loginPasswordStrengthVeryStrong": "Très fort", + "loginPhone": "Téléphone", + "loginPromo": "Promo entrante (ex : 2023)", + "loginSendedMail": "Mail de confirmation envoyé", + "loginSendedResetMail": "Mail de réinitialisation envoyé", + "loginSignIn": "Se connecter", + "loginRegister": "S'inscrire", + "loginRecievedMail": "J'ai reçu le mail", + "loginRecover": "Réinitialiser", + "loginResetedPassword": "Mot de passe réinitialisé", + "loginResetPasswordTitle": "Réinitialiser\nle mot de \npasse", + "loginNickname": "Surnom", + "loginWelcomeBack": "Bienvenue", + "loginAppName": "MyECL", + "othersCheckInternetConnection": "Veuillez vérifier votre connexion internet", + "othersRetry": "Réessayer", + "othersTooOldVersion": "Votre version de l'application est trop ancienne.\n\nVeuillez mettre à jour l'application.", + "othersUnableToConnectToServer": "Impossible de se connecter au serveur", + "othersVersion": "Version", + "othersNoModule": "Aucun module disponible, veuillez réessayer ultérieurement 😢😢", + "othersAdmin": "Admin", + "othersError": "Une erreur est survenue", + "othersNoValue": "Veuillez entrer une valeur", + "othersInvalidNumber": "Veuillez entrer un nombre", + "othersNoDateError": "Veuillez entrer une date", + "othersImageSizeTooBig": "La taille de l'image ne doit pas dépasser 4 Mio", + "othersImageError": "Erreur lors de l'ajout de l'image", + "phAddNewJournal": "Ajouter un nouveau journal", + "phNameField": "Nom : ", + "phDateField": "Date : ", + "phDelete": "Voulez-vous vraiment supprimer ce journal ?", + "phIrreversibleAction": "Cette action est irréversible", + "phToHeavyFile": "Fichier trop volumineux", + "phAddPdfFile": "Ajouter un fichier PDF", + "phEditPdfFile": "Modifier le fichier PDF", + "phPhName": "Nom du PH", + "phDate": "Date", + "phAdded": "Ajouté", + "phEdited": "Modifié", + "phAddingFileError": "Erreur d'ajout", + "phMissingInformatonsOrPdf": "Informations manquantes ou fichier PDF manquant", + "phAdd": "Ajouter", + "phEdit": "Modifier", + "phSeePreviousJournal": "Voir les anciens journaux", + "phNoJournalInDatabase": "Pas encore de PH dans la base de donnée", + "phSuccesDowloading": "Téléchargé avec succès", + "phonebookAdd": "Ajouter", + "phonebookAddAssociation": "Ajouter une association", + "phonebookAddAssociationGroupement": "Ajouter un groupement d'association", + "phonebookAddedAssociation": "Association ajoutée", + "phonebookAddedMember": "Membre ajouté", + "phonebookAddingError": "Erreur lors de l'ajout", + "phonebookAddMember": "Ajouter un membre", + "phonebookAddRole": "Ajouter un rôle", + "phonebookAdmin": "Administration", + "phonebookAll": "Toutes", + "phonebookApparentName": "Nom public du rôle :", + "phonebookAssociation": "Association", + "phonebookAssociationDetail": "Détail de l'association :", + "phonebookAssociationGroupement": "Groupement d'association", + "phonebookAssociationKind": "Type d'association :", + "phonebookAssociationName": "Nom de l'association", + "phonebookAssociations": "Associations", + "phonebookCancel": "Annuler", + "phonebookChangeTermYear": "Passer au mandat {year}", + "@phonebookChangeTermYear": { + "description": "Permet de changer le mandat d'une association", + "placeholders": { + "year": { + "type": "int" + } + } + }, + "phonebookChangeTermConfirm": "Êtes-vous sûr de vouloir changer tout le mandat ?\nCette action est irréversible !", + "phonebookClose": "Fermer", + "phonebookConfirm": "Confirmer", + "phonebookCopied": "Copié dans le presse-papier", + "phonebookDeactivateAssociation": "Désactiver l'association", + "phonebookDeactivatedAssociation": "Association désactivée", + "phonebookDeactivatedAssociationWarning": "Attention, cette association est désactivée, vous ne pouvez pas la modifier", + "phonebookDeactivateSelectedAssociation": "Désactiver l'association {association} ?", + "@phonebookDeactivateSelectedAssociation": { + "description": "Permet de désactiver une association", + "placeholders": { + "association": { + "type": "String" + } + } + }, + "phonebookDeactivatingError": "Erreur lors de la désactivation", + "phonebookDetail": "Détail :", + "phonebookDelete": "Supprimer", + "phonebookDeleteAssociation": "Supprimer l'association", + "phonebookDeleteSelectedAssociation": "Supprimer l'association {association} ?", + "@phonebookDeleteSelectedAssociation": { + "description": "Permet de supprimer une association", + "placeholders": { + "association": { + "type": "String" + } + } + }, + "phonebookDeleteAssociationDescription": "Ceci va supprimer l'historique de l'association", + "phonebookDeletedAssociation": "Association supprimée", + "phonebookDeletedMember": "Membre supprimé", + "phonebookDeleteRole": "Supprimer le rôle", + "phonebookDeleteUserRole": "Supprimer le rôle de l'utilisateur {name} ?", + "@phonebookDeleteUserRole": { + "description": "Permet de supprimer le rôle d'un utilisateur dans une association", + "placeholders": { + "name": { + "type": "String" + } + } + }, + "phonebookDeleting": "Suppression", + "phonebookDeletingError": "Erreur lors de la suppression", + "phonebookDescription": "Description", + "phonebookEdit": "Modifier", + "phonebookEditAssociationGroupement": "Modifier le groupement d'association", + "phonebookEditAssociationGroups": "Gérer les groupes", + "phonebookEditAssociationInfo": "Modifier", + "phonebookEditAssociationMembers": "Gérer les membres", + "phonebookEditRole": "Modifier le rôle", + "phonebookEmail": "Email :", + "phonebookEmailCopied": "Email copié dans le presse-papier", + "phonebookEmptyApparentName": "Veuillez entrer un nom de role", + "phonebookEmptyFieldError": "Un champ n'est pas rempli", + "phonebookEmptyKindError": "Veuillez choisir un type d'association", + "phonebookEmptyMember": "Aucun membre sélectionné", + "phonebookErrorAssociationLoading": "Erreur lors du chargement de l'association", + "phonebookErrorAssociationNameEmpty": "Veuillez entrer un nom d'association", + "phonebookErrorAssociationPicture": "Erreur lors de la modification de la photo d'association", + "phonebookErrorKindsLoading": "Erreur lors du chargement des types d'association", + "phonebookErrorLoadAssociationList": "Erreur lors du chargement de la liste des associations", + "phonebookErrorLoadAssociationMember": "Erreur lors du chargement des membres de l'association", + "phonebookErrorLoadAssociationPicture": "Erreur lors du chargement de la photo d'association", + "phonebookErrorLoadProfilePicture": "Erreur", + "phonebookErrorRoleTagsLoading": "Erreur lors du chargement des tags de rôle", + "phonebookExistingMembership": "Ce membre est déjà dans le mandat actuel", + "phonebookFilter": "Filtrer", + "phonebookFilterDescription": "Sélectionnez un ou plusieurs groupements pour filtrer les associations.", + "phonebookFirstname": "Prénom :", + "phonebookGroupementDeleted": "Groupement d'association supprimé", + "phonebookGroupementDeleteError": "Erreur lors de la suppression du groupement d'association", + "phonebookGroupementName": "Nom du groupement", + "phonebookGroups": "Gérer les groupes de {association}", + "@phonebookGroups": { + "description": "Permet de gérer les groupes d'une association", + "placeholders": { + "association": { + "type": "String" + } + } + }, + "phonebookTerm": "Mandat {year}", + "@phonebookTerm": { + "description": "Année de mandat d'une association", + "placeholders": { + "year": { + "type": "int" + } + } + }, + "phonebookTermChangingError": "Erreur lors du changement de mandat", + "phonebookMember": "Membre", + "phonebookMemberReordered": "Membre réordonné", + "phonebookMembers": "Gérer les membres de {association}", + "@phonebookMembers": { + "description": "Permet de gérer les membres d'une association", + "placeholders": { + "association": { + "type": "String" + } + } + }, + "phonebookMembershipAssociationError": "Veuillez choisir une association", + "phonebookMembershipRole": "Rôle :", + "phonebookMembershipRoleError": "Veuillez choisir un rôle", + "phonebookModifyMembership": "Modifier le rôle de {name}", + "@phonebookModifyMembership": { + "description": "Permet de modifier le rôle d'un membre dans une association", + "placeholders": { + "name": { + "type": "String" + } + } + }, + "phonebookName": "Nom :", + "phonebookNameCopied": "Nom et prénom copié dans le presse-papier", + "phonebookNamePure": "Nom", + "phonebookNewTerm": "Nouveau mandat", + "phonebookNewTermConfirmed": "Mandat changé", + "phonebookNickname": "Surnom :", + "phonebookNicknameCopied": "Surnom copié dans le presse-papier", + "phonebookNoAssociationFound": "Aucune association trouvée", + "phonebookNoMember": "Aucun membre", + "phonebookNoMemberRole": "Aucun role trouvé", + "phonebookNoRoleTags": "Aucun tag de rôle trouvé", + "phonebookPhone": "Téléphone :", + "phonebookPhonebook": "Annuaire", + "phonebookPhonebookSearch": "Rechercher", + "phonebookPhonebookSearchAssociation": "Association", + "phonebookPhonebookSearchField": "Rechercher :", + "phonebookPhonebookSearchName": "Nom/Prénom/Surnom", + "phonebookPhonebookSearchRole": "Poste", + "phonebookPresidentRoleTag": "Prez'", + "phonebookPromoNotGiven": "Promo non renseignée", + "phonebookPromotion": "Promotion {year}", + "@phonebookPromotion": { + "description": "Année de promotion d'un membre", + "placeholders": { + "year": { + "type": "int" + } + } + }, + "phonebookReorderingError": "Erreur lors du réordonnement", + "phonebookResearch": "Rechercher", + "phonebookRolePure": "Rôle", + "phonebookSearchUser": "Rechercher un utilisateur", + "phonebookTooHeavyAssociationPicture": "L'image est trop lourde (max 4Mo)", + "phonebookUpdateGroups": "Mettre à jour les groupes", + "phonebookUpdatedAssociation": "Association modifiée", + "phonebookUpdatedAssociationPicture": "La photo d'association a été changée", + "phonebookUpdatedGroups": "Groupes mis à jour", + "phonebookUpdatedMember": "Membre modifié", + "phonebookUpdatingError": "Erreur lors de la modification", + "phonebookValidation": "Valider", + "purchasesPurchases": "Achats", + "purchasesResearch": "Rechercher", + "purchasesNoPurchasesFound": "Aucun achat trouvé", + "purchasesNoTickets": "Aucun ticket", + "purchasesTicketsError": "Erreur lors du chargement des tickets", + "purchasesPurchasesError": "Erreur lors du chargement des achats", + "purchasesNoPurchases": "Aucun achat", + "purchasesTimes": "fois", + "purchasesAlreadyUsed": "Déjà utilisé", + "purchasesNotPaid": "Non validé", + "purchasesPleaseSelectProduct": "Veuillez sélectionner un produit", + "purchasesProducts": "Produits", + "purchasesCancel": "Annuler", + "purchasesValidate": "Valider", + "purchasesLeftScan": "Scans restants", + "purchasesTag": "Tag", + "purchasesHistory": "Historique", + "purchasesPleaseSelectSeller": "Veuillez sélectionner un vendeur", + "purchasesNoTagGiven": "Attention, aucun tag n'a été entré", + "purchasesTickets": "Tickets", + "purchasesNoScannableProducts": "Aucun produit scannable", + "purchasesLoading": "En attente de scan", + "purchasesScan": "Scanner", + "raffleRaffle": "Tombola", + "rafflePrize": "Lot", + "rafflePrizes": "Lots", + "raffleActualRaffles": "Tombola en cours", + "rafflePastRaffles": "Tombola passés", + "raffleYourTickets": "Tous vos tickets", + "raffleCreateMenu": "Menu de Création", + "raffleNextRaffles": "Prochaines tombolas", + "raffleNoTicket": "Vous n'avez pas de ticket", + "raffleSeeRaffleDetail": "Voir lots/tickets", + "raffleActualPrize": "Lots actuels", + "raffleMajorPrize": "Lot Majeurs", + "raffleTakeTickets": "Prendre vos tickets", + "raffleNoTicketBuyable": "Vous ne pouvez pas achetez de billets pour l'instant", + "raffleNoCurrentPrize": "Il n'y a aucun lots actuellement", + "raffleModifTombola": "Vous pouvez modifiez vos tombolas ou en créer de nouvelles, toute décision doit ensuite être prise par les admins", + "raffleCreateYourRaffle": "Votre menu de création de tombolas", + "rafflePossiblePrice": "Prix possible", + "raffleInformation": "Information et Statistiques", + "raffleAccounts": "Comptes", + "raffleAdd": "Ajouter", + "raffleUpdatedAmount": "Montant mis à jour", + "raffleUpdatingError": "Erreur lors de la mise à jour", + "raffleDeletedPrize": "Lot supprimé", + "raffleDeletingError": "Erreur lors de la suppression", + "raffleQuantity": "Quantité", + "raffleClose": "Fermer", + "raffleOpen": "Ouvrir", + "raffleAddTypeTicketSimple": "Ajouter", + "raffleAddingError": "Erreur lors de l'ajout", + "raffleEditTypeTicketSimple": "Modifier", + "raffleFillField": "Le champ ne peut pas être vide", + "raffleWaiting": "Chargement", + "raffleEditingError": "Erreur lors de la modification", + "raffleAddedTicket": "Ticket ajouté", + "raffleEditedTicket": "Ticket modifié", + "raffleAlreadyExistTicket": "Le ticket existe déjà", + "raffleNumberExpected": "Un entier est attendu", + "raffleDeletedTicket": "Ticket supprimé", + "raffleAddPrize": "Ajouter", + "raffleEditPrize": "Modifier", + "raffleOpenRaffle": "Ouvrir la tombola", + "raffleCloseRaffle": "Fermer la tombola", + "raffleOpenRaffleDescription": "Vous allez ouvrir la tombola, les utilisateurs pourront acheter des tickets. Vous ne pourrez plus modifier la tombola. Êtes-vous sûr de vouloir continuer ?", + "raffleCloseRaffleDescription": "Vous allez fermer la tombola, les utilisateurs ne pourront plus acheter de tickets. Êtes-vous sûr de vouloir continuer ?", + "raffleNoCurrentRaffle": "Il n'y a aucune tombola en cours", + "raffleBoughtTicket": "Ticket acheté", + "raffleDrawingError": "Erreur lors du tirage", + "raffleInvalidPrice": "Le prix doit être supérieur à 0", + "raffleMustBePositive": "Le nombre doit être strictement positif", + "raffleDraw": "Tirer", + "raffleDrawn": "Tiré", + "raffleError": "Erreur", + "raffleGathered": "Récolté", + "raffleTickets": "Tickets", + "raffleTicket": "ticket", + "raffleWinner": "Gagnant", + "raffleNoPrize": "Aucun lot", + "raffleDeletePrize": "Supprimer le lot", + "raffleDeletePrizeDescription": "Vous allez supprimer le lot, êtes-vous sûr de vouloir continuer ?", + "raffleDrawing": "Tirage", + "raffleDrawingDescription": "Tirer le gagnant du lot ?", + "raffleDeleteTicket": "Supprimer le ticket", + "raffleDeleteTicketDescription": "Vous allez supprimer le ticket, êtes-vous sûr de vouloir continuer ?", + "raffleWinningTickets": "Tickets gagnants", + "raffleNoWinningTicketYet": "Les tickets gagnants seront affichés ici", + "raffleName": "Nom", + "raffleDescription": "Description", + "raffleBuyThisTicket": "Acheter ce ticket", + "raffleLockedRaffle": "Tombola verrouillée", + "raffleUnavailableRaffle": "Tombola indisponible", + "raffleNotEnoughMoney": "Vous n'avez pas assez d'argent", + "raffleWinnable": "gagnable", + "raffleNoDescription": "Aucune description", + "raffleAmount": "Solde", + "raffleLoading": "Chargement", + "raffleTicketNumber": "Nombre de ticket", + "rafflePrice": "Prix", + "raffleEditRaffle": "Modifier la tombola", + "raffleEdit": "Modifier", + "raffleAddPackTicket": "Ajouter un pack de ticket", + "recommendationRecommendation": "Bons plans", + "recommendationTitle": "Titre", + "recommendationLogo": "Logo", + "recommendationCode": "Code", + "recommendationSummary": "Court résumé", + "recommendationDescription": "Description", + "recommendationAdd": "Ajouter", + "recommendationEdit": "Modifier", + "recommendationDelete": "Supprimer", + "recommendationAddImage": "Veuillez ajouter une image", + "recommendationAddedRecommendation": "Bon plan ajouté", + "recommendationEditedRecommendation": "Bon plan modifié", + "recommendationDeleteRecommendationConfirmation": "Êtes-vous sûr de vouloir supprimer ce bon plan ?", + "recommendationDeleteRecommendation": "Suppresion", + "recommendationDeletingRecommendationError": "Erreur lors de la suppression", + "recommendationDeletedRecommendation": "Bon plan supprimé", + "recommendationIncorrectOrMissingFields": "Champs incorrects ou manquants", + "recommendationEditingError": "Échec de la modification", + "recommendationAddingError": "Échec de l'ajout", + "recommendationCopiedCode": "Code de réduction copié", + "seedLibraryAdd": "Ajouter", + "seedLibraryAddedPlant": "Plante ajoutée", + "seedLibraryAddedSpecies": "Espèce ajoutée", + "seedLibraryAddingError": "Erreur lors de l'ajout", + "seedLibraryAddPlant": "Déposer une plante", + "seedLibraryAddSpecies": "Ajouter une espèce", + "seedLibraryAll": "Toutes", + "seedLibraryAncestor": "Ancêtre", + "seedLibraryAround": "environ", + "seedLibraryAutumn": "Automne", + "seedLibraryBorrowedPlant": "Plante empruntée", + "seedLibraryBorrowingDate": "Date d'emprunt :", + "seedLibraryBorrowPlant": "Emprunter la plante", + "seedLibraryCard": "Carte", + "seedLibraryChoosingAncestor": "Veuillez choisir un ancêtre", + "seedLibraryChoosingSpecies": "Veuillez choisir une espèce", + "seedLibraryChoosingSpeciesOrAncestor": "Veuillez choisir une espèce ou un ancêtre", + "seedLibraryContact": "Contact :", + "seedLibraryDays": "jours", + "seedLibraryDeadMsg": "Voulez-vous déclarer la plante morte ?", + "seedLibraryDeadPlant": "Plante morte", + "seedLibraryDeathDate": "Date de mort", + "seedLibraryDeletedSpecies": "Espèce supprimée", + "seedLibraryDeleteSpecies": "Supprimer l'espèce ?", + "seedLibraryDeleting": "Suppression", + "seedLibraryDeletingError": "Erreur lors de la suppression", + "seedLibraryDepositNotAvailable": "Le dépôt de plantes n'est pas possible sans emprunter une plante au préalable", + "seedLibraryDescription": "Description", + "seedLibraryDifficulty": "Difficulté :", + "seedLibraryEdit": "Modifier", + "seedLibraryEditedPlant": "Plante modifiée", + "seedLibraryEditInformation": "Modifier les informations", + "seedLibraryEditingError": "Erreur lors de la modification", + "seedLibraryEditSpecies": "Modifier l'espèce", + "seedLibraryEmptyDifficultyError": "Veuillez choisir une difficulté", + "seedLibraryEmptyFieldError": "Veuillez remplir tous les champs", + "seedLibraryEmptyTypeError": "Veuillez choisir un type de plante", + "seedLibraryEndMonth": "Mois de fin :", + "seedLibraryFacebookUrl": "Lien Facebook", + "seedLibraryFilters": "Filtres", + "seedLibraryForum": "Oskour maman j'ai tué ma plante - Forum d'aide", + "seedLibraryForumUrl": "Lien Forum", + "seedLibraryHelpSheets": "Fiches sur les plantes", + "seedLibraryInformation": "Informations :", + "seedLibraryMaturationTime": "Temps de maturation", + "seedLibraryMonthJan": "Janvier", + "seedLibraryMonthFeb": "Février", + "seedLibraryMonthMar": "Mars", + "seedLibraryMonthApr": "Avril", + "seedLibraryMonthMay": "Mai", + "seedLibraryMonthJun": "Juin", + "seedLibraryMonthJul": "Juillet", + "seedLibraryMonthAug": "Août", + "seedLibraryMonthSep": "Septembre", + "seedLibraryMonthOct": "Octobre", + "seedLibraryMonthNov": "Novembre", + "seedLibraryMonthDec": "Décembre", + "seedLibraryMyPlants": "Mes plantes", + "seedLibraryName": "Nom", + "seedLibraryNbSeedsRecommended": "Nombre de graines recommandées", + "seedLibraryNbSeedsRecommendedError": "Veuillez entrer un nombre de graines recommandé supérieur à 0", + "seedLibraryNoDateError": "Veuillez entrer une date", + "seedLibraryNoFilteredPlants": "Aucune plante ne correspond à votre recherche. Essayez d'autres filtres.", + "seedLibraryNoMorePlant": "Aucune plante n'est disponible", + "seedLibraryNoPersonalPlants": "Vous n'avez pas encore de plantes dans votre grainothèque. Vous pouvez en ajouter en allant dans les stocks.", + "seedLibraryNoSpecies": "Aucune espèce trouvée", + "seedLibraryNoStockPlants": "Aucune plante disponible dans le stock", + "seedLibraryNotes": "Notes", + "seedLibraryOk": "OK", + "seedLibraryPlantationPeriod": "Période de plantation :", + "seedLibraryPlantationType": "Type de plantation :", + "seedLibraryPlantDetail": "Détail de la plante", + "seedLibraryPlantingDate": "Date de plantation", + "seedLibraryPlantingNow": "Je la plante maintenant", + "seedLibraryPrefix": "Préfixe", + "seedLibraryPrefixError": "Prefixe déjà utilisé", + "seedLibraryPrefixLengthError": "Le préfixe doit faire 3 caractères", + "seedLibraryPropagationMethod": "Méthode de propagation :", + "seedLibraryReference": "Référence :", + "seedLibraryRemovedPlant": "Plante supprimée", + "seedLibraryRemovingError": "Erreur lors de la suppression", + "seedLibraryResearch": "Recherche", + "seedLibrarySaveChanges": "Sauvegarder les modifications", + "seedLibrarySeason": "Saison :", + "seedLibrarySeed": "Graine", + "seedLibrarySeeds": "graines", + "seedLibrarySeedDeposit": "Dépôt de plantes", + "seedLibrarySeedLibrary": "Grainothèque", + "seedLibrarySeedQuantitySimple": "Quantité de graines", + "seedLibrarySeedQuantity": "Quantité de graines :", + "seedLibraryShowDeadPlants": "Afficher les plantes mortes", + "seedLibrarySpecies": "Espèce :", + "seedLibrarySpeciesHelp": "Aide sur l'espèce", + "seedLibrarySpeciesPlural": "Espèces", + "seedLibrarySpeciesSimple": "Espèce", + "seedLibrarySpeciesType": "Type d'espèce :", + "seedLibrarySpring": "Printemps", + "seedLibraryStartMonth": "Mois de début :", + "seedLibraryStock": "Stock disponible", + "seedLibrarySummer": "Été", + "seedLibraryStocks": "Stocks", + "seedLibraryTimeUntilMaturation": "Temps avant maturation :", + "seedLibraryType": "Type :", + "seedLibraryUnableToOpen": "Impossible d'ouvrir le lien", + "seedLibraryUpdate": "Modifier", + "seedLibraryUpdatedInformation": "Informations modifiées", + "seedLibraryUpdatedSpecies": "Espèce modifiée", + "seedLibraryUpdatedPlant": "Plante modifiée", + "seedLibraryUpdatingError": "Erreur lors de la modification", + "seedLibraryWinter": "Hiver", + "seedLibraryWriteReference": "Veuillez écrire la référence suivante : ", + "settingsAccount": "Compte", + "settingsAddProfilePicture": "Ajouter une photo", + "settingsAdmin": "Administrateur", + "settingsAskHelp": "Demander de l'aide", + "settingsAssociation": "Association", + "settingsBirthday": "Date de naissance", + "settingsBugs": "Bugs", + "settingsChangePassword": "Changer de mot de passe", + "settingsChangingPassword": "Voulez-vous vraiment changer votre mot de passe ?", + "settingsConfirmPassword": "Confirmer le mot de passe", + "settingsCopied": "Copié !", + "settingsDarkMode": "Mode sombre", + "settingsDarkModeOff": "Désactivé", + "settingsDeleteLogs": "Supprimer les logs ?", + "settingsDeleteNotificationLogs": "Supprimer les logs des notifications ?", + "settingsDetelePersonalData": "Supprimer mes données personnelles", + "settingsDetelePersonalDataDesc": "Cette action notifie l'administrateur que vous souhaitez supprimer vos données personnelles.", + "settingsDeleting": "Suppresion", + "settingsEdit": "Modifier", + "settingsEditAccount": "Modifier mon profil", + "settingsEmail": "Email", + "settingsEmptyField": "Ce champ ne peut pas être vide", + "settingsErrorProfilePicture": "Erreur lors de la modification de la photo de profil", + "settingsErrorSendingDemand": "Erreur lors de l'envoi de la demande", + "settingsEventsIcal": "Lien Ical des événements", + "settingsExpectingDate": "Date de naissance attendue", + "settingsFirstname": "Prénom", + "settingsFloor": "Étage", + "settingsHelp": "Aide", + "settingsIcalCopied": "Lien Ical copié !", + "settingsLanguage": "Langue", + "settingsLanguageVar": "Français 🇫🇷", + "settingsLogs": "Logs", + "settingsModules": "Modules", + "settingsMyIcs": "Mon lien Ical", + "settingsName": "Nom", + "settingsNewPassword": "Nouveau mot de passe", + "settingsNickname": "Surnom", + "settingsNotifications": "Notifications", + "settingsOldPassword": "Ancien mot de passe", + "settingsPasswordChanged": "Mot de passe changé", + "settingsPasswordsNotMatch": "Les mots de passe ne correspondent pas", + "settingsPersonalData": "Données personnelles", + "settingsPersonalisation": "Personnalisation", + "settingsPhone": "Téléphone", + "settingsProfilePicture": "Photo de profil", + "settingsPromo": "Promotion", + "settingsRepportBug": "Signaler un bug", + "settingsSave": "Enregistrer", + "settingsSecurity": "Sécurité", + "settingsSendedDemand": "Demande envoyée", + "settingsSettings": "Paramètres", + "settingsTooHeavyProfilePicture": "L'image est trop lourde (max 4Mo)", + "settingsUpdatedProfile": "Profil modifié", + "settingsUpdatedProfilePicture": "Photo de profil modifiée", + "settingsUpdateNotification": "Mettre à jour les notifications", + "settingsUpdatingError": "Erreur lors de la modification du profil", + "settingsVersion": "Version", + "settingsPasswordStrength": "Force du mot de passe", + "settingsPasswordStrengthVeryWeak": "Très faible", + "settingsPasswordStrengthWeak": "Faible", + "settingsPasswordStrengthMedium": "Moyen", + "settingsPasswordStrengthStrong": "Fort", + "settingsPasswordStrengthVeryStrong": "Très fort", + "settingsPhoneNumber": "Numéro de téléphone", + "settingsValidate": "Valider", + "settingsEditedAccount": "Compte modifié avec succès", + "settingsFailedToEditAccount": "Échec de la modification du compte", + "settingsChooseLanguage": "Choix de la langue", + "settingsNotificationCounter": "{active}/{total} {active, plural, zero {activée} one {activée} other {activées}}", + "@settingsNotificationCounter": { + "description": "Affiche le nombre de notifications actives sur le total des notifications disponibles, avec gestion du pluriel", + "placeholders": { + "active": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "settingsEvent": "Événement", + "settingsIcal": "Lien Ical", + "settingsSynncWithCalendar": "Synchroniser avec votre calendrier", + "settingsIcalLinkCopied": "Lien Ical copié dans le presse-papier", + "settingsProfile": "Profil", + "voteAdd": "Ajouter", + "voteAddMember": "Ajouter un membre", + "voteAddedPretendance": "Liste ajoutée", + "voteAddedSection": "Section ajoutée", + "voteAddingError": "Erreur lors de l'ajout", + "voteAddPretendance": "Ajouter une liste", + "voteAddSection": "Ajouter une section", + "voteAll": "Tous", + "voteAlreadyAddedMember": "Membre déjà ajouté", + "voteAlreadyVoted": "Vote enregistré", + "voteChooseList": "Choisir une liste", + "voteClear": "Réinitialiser", + "voteClearVotes": "Réinitialiser les votes", + "voteClosedVote": "Votes clos", + "voteCloseVote": "Fermer les votes", + "voteConfirmVote": "Confirmer le vote", + "voteCountVote": "Dépouiller les votes", + "voteDeletedAll": "Tout supprimé", + "voteDeletedPipo": "Listes pipos supprimées", + "voteDeletedSection": "Section supprimée", + "voteDeleteAll": "Supprimer tout", + "voteDeleteAllDescription": "Voulez-vous vraiment supprimer tout ?", + "voteDeletePipo": "Supprimer les listes pipos", + "voteDeletePipoDescription": "Voulez-vous vraiment supprimer les listes pipos ?", + "voteDeletePretendance": "Supprimer la liste", + "voteDeletePretendanceDesc": "Voulez-vous vraiment supprimer cette liste ?", + "voteDeleteSection": "Supprimer la section", + "voteDeleteSectionDescription": "Voulez-vous vraiment supprimer cette section ?", + "voteDeletingError": "Erreur lors de la suppression", + "voteDescription": "Description", + "voteEdit": "Modifier", + "voteEditedPretendance": "Liste modifiée", + "voteEditedSection": "Section modifiée", + "voteEditingError": "Erreur lors de la modification", + "voteErrorClosingVotes": "Erreur lors de la fermeture des votes", + "voteErrorCountingVotes": "Erreur lors du dépouillement des votes", + "voteErrorResetingVotes": "Erreur lors de la réinitialisation des votes", + "voteErrorOpeningVotes": "Erreur lors de l'ouverture des votes", + "voteIncorrectOrMissingFields": "Champs incorrects ou manquants", + "voteMembers": "Membres", + "voteName": "Nom", + "voteNoPretendanceList": "Aucune liste de prétendance", + "voteNoSection": "Aucune section", + "voteCanNotVote": "Vous ne pouvez pas voter", + "voteNoSectionList": "Aucune section", + "voteNotOpenedVote": "Vote non ouvert", + "voteOnGoingCount": "Dépouillement en cours", + "voteOpenVote": "Ouvrir les votes", + "votePipo": "Pipo", + "votePretendance": "Listes", + "votePretendanceDeleted": "Prétendance supprimée", + "votePretendanceNotDeleted": "Erreur lors de la suppression", + "voteProgram": "Programme", + "votePublish": "Publier", + "votePublishVoteDescription": "Voulez-vous vraiment publier les votes ?", + "voteResetedVotes": "Votes réinitialisés", + "voteResetVote": "Réinitialiser les votes", + "voteResetVoteDescription": "Que voulez-vous faire ?", + "voteRole": "Rôle", + "voteSectionDescription": "Description de la section", + "voteSection": "Section", + "voteSectionName": "Nom de la section", + "voteSeeMore": "Voir plus", + "voteSelected": "Sélectionné", + "voteShowVotes": "Voir les votes", + "voteVote": "Vote", + "voteVoteError": "Erreur lors de l'enregistrement du vote", + "voteVoteFor": "Voter pour ", + "voteVoteNotStarted": "Vote non ouvert", + "voteVoters": "Groupes votants", + "voteVoteSuccess": "Vote enregistré", + "voteVotes": "Voix", + "voteVotesClosed": "Votes clos", + "voteVotesCounted": "Votes dépouillés", + "voteVotesOpened": "Votes ouverts", + "voteWarning": "Attention", + "voteWarningMessage": "La sélection ne sera pas sauvegardée.\nVoulez-vous continuer ?", + "moduleAdvert": "Annonce", + "moduleAdvertDescription": "Gérer les annonces", "moduleAmap": "AMAP", - "moduleAmapDescription": "Gérer les livraisons et les produits", + "moduleAmapDescription": "Gérer les livraisons et les produits", "moduleBooking": "Réservation", - "moduleBookingDescription": "Gérer les réservations, les salles et les managers", + "moduleBookingDescription": "Gérer les réservations, les salles et les managers", "moduleCalendar": "Calendrier", - "moduleCalendarDescription": "Consulter les événements et les activités", + "moduleCalendarDescription": "Consulter les événements et les activités", "moduleCentralisation": "Centralisation", - "moduleCentralisationDescription": "Gérer la centralisation des données", + "moduleCentralisationDescription": "Gérer la centralisation des données", "moduleCinema": "Cinéma", - "moduleCinemaDescription": "Gérer les séances de cinéma", + "moduleCinemaDescription": "Gérer les séances de cinéma", "moduleEvent": "Événement", - "moduleEventDescription": "Gérer les événements et les participants", + "moduleEventDescription": "Gérer les événements et les participants", "moduleFlappyBird": "Flappy Bird", - "moduleFlappyBirdDescription": "Jouer à Flappy Bird et consulter le classement", + "moduleFlappyBirdDescription": "Jouer à Flappy Bird et consulter le classement", "moduleLoan": "Prêt", - "moduleLoanDescription": "Gérer les prêts et les articles", + "moduleLoanDescription": "Gérer les prêts et les articles", "modulePhonebook": "Annuaire", - "modulePhonebookDescription": "Gérer les associations, les membres et les administrateurs", + "modulePhonebookDescription": "Gérer les associations, les membres et les administrateurs", "modulePurchases": "Achats", - "modulePurchasesDescription": "Gérer les achats, les tickets et l'historique", + "modulePurchasesDescription": "Gérer les achats, les tickets et l'historique", "moduleRaffle": "Tombola", - "moduleRaffleDescription": "Gérer les tombolas, les prix et les tickets", + "moduleRaffleDescription": "Gérer les tombolas, les prix et les tickets", "moduleRecommendation": "Bons plans", - "moduleRecommendationDescription": "Gérer les recommandations, les informations et les administrateurs", + "moduleRecommendationDescription": "Gérer les recommandations, les informations et les administrateurs", "moduleSeedLibrary": "Grainothèque", - "moduleSeedLibraryDescription": "Gérer les graines, les espèces et les stocks", + "moduleSeedLibraryDescription": "Gérer les graines, les espèces et les stocks", "moduleVote": "Vote", - "moduleVoteDescription": "Gérer les votes, les sections et les candidats", + "moduleVoteDescription": "Gérer les votes, les sections et les candidats", "modulePh": "PH", - "modulePhDescription": "Gérer les PH, les formulaires et les administrateurs", + "modulePhDescription": "Gérer les PH, les formulaires et les administrateurs", "moduleSettings": "Paramètres", - "moduleSettingsDescription": "Gérer les paramètres de l'application", + "moduleSettingsDescription": "Gérer les paramètres de l'application", "moduleFeed": "Feed", - "moduleFeedDescription": "Consulter les actualités et mises à jour", + "moduleFeedDescription": "Consulter les actualités et mises à jour", "moduleStyleGuide": "StyleGuide", - "moduleStyleGuideDescription": "Explore the UI components and styles used in Titan", + "moduleStyleGuideDescription": "Explore the UI components and styles used in Titan", "moduleAdmin": "Adminitration", - "moduleAdminDescription": "Gérer les utilisateurs, groupes et structures", + "moduleAdminDescription": "Gérer les utilisateurs, groupes et structures", "moduleOthers": "Autres", - "moduleOthersDescription": "Afficher les autres modules", + "moduleOthersDescription": "Afficher les autres modules", "modulePayment": "Paiement", - "modulePaymentDescription": "Gérer les paiements, les statistiques et les appareils", - "paiementTopUp": "Recharge", - "paiementStoreManagement": "Gestion des associations", - "paiementDeleteStore": "Supprimer l'association", - "paiementDeleteStoreDescription": "Voulez-vous vraiment supprimer cette association ?", - "paiementDeleteStoreError": "Impossible de supprimer l'association", - "paiementStoreDeleted": "Association supprimée", - "paiementAddThisDevice": "Ajouter cet appareil", - "paiementThisDevice": "(cet appareil)", - "paiementCancelled": "Annulé", - "paiementThe": "Le", - "paiementOf": "de", - "paiementRefundedThe": "Remboursé le", - "paiementAt": "à", - "paiementPleaseAcceptTOS": "Veuillez accepter les Conditions Générales d'Utilisation.", - "paiementAskDeviceActivation": "Demande d'activation de l'appareil", - "paiementDeviceActivationReceived": "La demande d'activation est prise en compte, veuilliez consulter votre boite mail pour finaliser la démarche", - "paiementRevokeDevice": "Révoquer l'appareil ?", - "paiementRevokeDeviceDescription": "Vous ne pourrez plus utiliser cet appareil pour les paiements", - "paiementDeviceRevoked": "Appareil révoqué", - "paiementDeviceRevokingError": "Erreur lors de la révocation de l'appareil", - "paiementPleaseAcceptPopup": "Veuillez autoriser les popups", - "paiementProceedSuccessfully": "Paiement effectué avec succès", - "paiementCancelledTransaction": "Paiement annulé", - "paiementPleaseEnterMinAmount": "Veuillez entrer un montant supérieur à 1", - "paiementMaxAmount": "Le montant maximum de votre portefeuille est de", - "paiementPayWithHA": "Payer avec HelloAsso", - "paiementBalanceAfterTopUp": "Solde après recharge :", - "paiementPersonalBalance": "Solde personnel", - "paiementDevices": "Appareils", - "paiementPay": "Payer", - "paiementDeviceNotRegistered": "Appareil non enregistré", - "paiementDeviceNotRegisteredDescription": "Votre appareil n'est pas encore enregistré. \nPour l'enregistrer, veuillez vous rendre sur la page des appareils.", - "paiementAccessPage": "Accéder à la page", - "paiementDeviceNotActivated": "Appareil non activé", - "paiementDeviceNotActivatedDescription": "Votre appareil n'est pas encore activé. \nPour l'activer, veuillez vous rendre sur la page des appareils.", - "paiementReactivateRevokedDeviceDescription": "Votre appareil a été révoqué. \nPour le réactiver, veuillez vous rendre sur la page des appareils.", - "paiementDeviceRecoveryError": "Erreur lors de la récupération de l'appareil", - "paiementStats": "Stats", - "paimentTopUpAction": "Recharger", - "paiementGetBalanceError": "Erreur lors de la récupération du solde : ", - "paiementLastTransactions": "Dernières transactions", - "paiementGetTransactionsError": "Erreur lors de la récupération des transactions : ", - "paiementStoreBalance": "Solde associatif", - "paiementScan": "Scanner", - "paiementManagement": "Gestion", - "paiementHistory": "Historique", - "paiementHandOver": "Passation", - "paiementStores": "Associations", - "paiementAdmin": "Administrateur", - "paiementSuccededTransaction": "Paiement réussi", - "paiementNewCGU": "Nouvelles Conditions Générales d'Utilisation", - "paiementDecline": "Refuser", - "paiementAccept": "Accepter", - "paiementAmount": "Montant", - "paiementValidUntil": "Valide jusqu'à", - "paiementClose": "Fermer", - "paiementPleaseEnterValidAmount": "Veuillez entrer un montant valide", - "paiementPleaseAuthenticate": "Veuillez vous authentifier", - "paiementAthenticationRequired": "Authentification requise pour payer", - "paiementNoThanks": "Non merci", - "paiementAuthentificationFailed": "Échec de l'authentification", - "paiementPleaseAddDevice": "Veuillez ajouter cet appareil pour payer", - "paiementPayment": "Paiement", - "paiementBalanceAfterTransaction": "Solde après paiement : ", - "paiementCancel": "Annuler", - "paiementLimitedTo": "Limité à", - "paiementScanCode": "Scanner un code", - "paiementNext": "Suivant", - "paiementCancelTransaction": "Annuler la transaction", - "paiementTransactionCancelled": "Transaction annulée", - "paiementTransactionCancelledDescription": "Voulez-vous vraiment annuler la transaction de", - "paiementTransactionCancelledError": "Erreur lors de l'annulation de la transaction", - "paiementNoMembership": "Aucune adhésion", - "paiementNoMembershipDescription": "Ce produit n'est pas disponnible pour les non-adhérents. Confirmer l'encaissement ?", - "paiementQRCodeAlreadyUsed": "QR Code déjà utilisé", - "paiementCameraPermissionRequired": "Permission d'accès à la caméra requise", - "paiementCameraPerssionRequiredDescription": "Pour scanner un QR Code, vous devez autoriser l'accès à la caméra.", - "paiementSettings": "Paramètres", - "paiementReceived": "Reçu", - "paiementSpent": "Déboursé", - "paiementNoTrasactionForThisMonth": "Aucune transaction pour ce mois", - "paiementNoTransactinon": "Aucune transaction", - "paiementSellerRigths": "Droits du vendeur", - "paiementCanBank": "Peut encaisser", - "paiementCanSeeHistory": "Peut voir l'historique", - "paiementCanCancelTransaction": "Peut annuler des transactions", - "paiementCanManageSellers": "Peut gérer les vendeurs", - "paiementAddedSeller": "Vendeur ajouté", - "paiementAddingSellerError": "Erreur lors de l'ajout du vendeur", - "paiementBank": "Encaisser", - "paiementSeeHistory": "Voir l'historique", - "paiementCancelTransactions": "Annuler les transactions", - "paiementManageSellers": "Gérer les vendeurs", - "paiementStructureAdmin": "Administrateur de la structure", - "paiementRightsOf": "Droits de", - "paiementRightsUpdated": "Droits mis à jour", - "paiementRightsUpdateError": "Erreur lors de la mise à jour des droits", - "paiementDeleteSellerDescription": "Voulez-vous vraiment supprimer ce vendeur ?", - "paiementDeletedSeller": "Vendeur supprimé", - "paiementDeletingSellerError": "Erreur lors de la suppression du vendeur", - "paiementDeleteSeller": "Supprimer le vendeur", - "paiementAdd": "Ajouter", - "paiementAddSeller": "Ajouter un vendeur", - "paiementSellerError": "Vous n'êtes pas vendeur de cette association", - "paiementSellersOf": "Les vendeurs de", - "paiementModify": "Modifier", - "paiementAStore": "une association", - "paiementStoreName": "Nom de l'association", - "paiementSuccessfullyAddedStore": "Association ajoutée avec succès", - "paiementSuccessfullyModifiedStore": "Association modifiée avec succès", - "paiementAddingStoreError": "Erreur lors de l'ajout de l'association", - "paiementModifyingStoreError": "Erreur lors de la modification de l'association", - "paiementRefund": "Remboursement", - "paiementDoneTransaction": "Transaction effectuée", - "paiementRefundAction": "Rembourser", - "paiementTotalDuringPeriod": "Total sur la période", - "paiementMean": "Moyenne : ", - "paiementTransaction": "ransaction", - "paiementTransferStructure": "Transfert de structure", - "paiementYouAreTransferingStructureTo": "Vous êtes sur le point de transférer la structure à ", - "paiementTransferStructureDescription": "Le nouveau responsable aura accès à toutes les fonctionnalités de gestion de la structure. Vous allez recevoir un email pour valider ce transfert. Le lien ne sera actif que pendant 20 minutes. Cette action est irréversible. Êtes-vous sûr de vouloir continuer ?", - "paiementTransferStructureError": "Erreur lors du transfert de la structure", - "paiementTransferStructureSuccess": "Transfert de structure demandé avec succès", - "paiementNextAccountable": "Prochain responsable" + "modulePaymentDescription": "Gérer les paiements, les statistiques et les appareils", "paiementTopUp": "Recharge", "paiementStoreManagement": "Gestion des associations", - "paiementDeleteStore": "Supprimer l'association", - "paiementDeleteStoreDescription": "Voulez-vous vraiment supprimer cette association ?", - "paiementDeleteStoreError": "Impossible de supprimer l'association", - "paiementStoreDeleted": "Association supprimée", - "paiementAddThisDevice": "Ajouter cet appareil", - "paiementThisDevice": "(cet appareil)", - "paiementCancelled": "Annulé", - "paiementThe": "Le", - "paiementOf": "de", - "paiementRefundedThe": "Remboursé le", - "paiementAt": "à", - "paiementPleaseAcceptTOS": "Veuillez accepter les Conditions Générales d'Utilisation.", - "paiementAskDeviceActivation": "Demande d'activation de l'appareil", - "paiementDeviceActivationReceived": "La demande d'activation est prise en compte, veuilliez consulter votre boite mail pour finaliser la démarche", - "paiementRevokeDevice": "Révoquer l'appareil ?", - "paiementRevokeDeviceDescription": "Vous ne pourrez plus utiliser cet appareil pour les paiements", - "paiementDeviceRevoked": "Appareil révoqué", - "paiementDeviceRevokingError": "Erreur lors de la révocation de l'appareil", - "paiementPleaseAcceptPopup": "Veuillez autoriser les popups", - "paiementProceedSuccessfully": "Paiement effectué avec succès", - "paiementCancelledTransaction": "Paiement annulé", - "paiementPleaseEnterMinAmount": "Veuillez entrer un montant supérieur à 1", - "paiementMaxAmount": "Le montant maximum de votre portefeuille est de", - "paiementPayWithHA": "Payer avec HelloAsso", - "paiementBalanceAfterTopUp": "Solde après recharge :", - "paiementPersonalBalance": "Solde personnel", - "paiementDevices": "Appareils", - "paiementPay": "Payer", - "paiementDeviceNotRegistered": "Appareil non enregistré", - "paiementDeviceNotRegisteredDescription": "Votre appareil n'est pas encore enregistré. \nPour l'enregistrer, veuillez vous rendre sur la page des appareils.", - "paiementAccessPage": "Accéder à la page", - "paiementDeviceNotActivated": "Appareil non activé", - "paiementDeviceNotActivatedDescription": "Votre appareil n'est pas encore activé. \nPour l'activer, veuillez vous rendre sur la page des appareils.", - "paiementReactivateRevokedDeviceDescription": "Votre appareil a été révoqué. \nPour le réactiver, veuillez vous rendre sur la page des appareils.", - "paiementDeviceRecoveryError": "Erreur lors de la récupération de l'appareil", - "paiementStats": "Stats", - "paimentTopUpAction": "Recharger", - "paiementGetBalanceError": "Erreur lors de la récupération du solde : ", - "paiementLastTransactions": "Dernières transactions", - "paiementGetTransactionsError": "Erreur lors de la récupération des transactions : ", - "paiementStoreBalance": "Solde associatif", - "paiementScan": "Scanner", - "paiementManagement": "Gestion", - "paiementHistory": "Historique", - "paiementHandOver": "Passation", - "paiementStores": "Associations", - "paiementAdmin": "Administrateur", - "paiementSuccededTransaction": "Paiement réussi", - "paiementNewCGU": "Nouvelles Conditions Générales d'Utilisation", - "paiementDecline": "Refuser", - "paiementAccept": "Accepter", - "paiementAmount": "Montant", - "paiementValidUntil": "Valide jusqu'à", - "paiementClose": "Fermer", - "paiementPleaseEnterValidAmount": "Veuillez entrer un montant valide", - "paiementPleaseAuthenticate": "Veuillez vous authentifier", - "paiementAthenticationRequired": "Authentification requise pour payer", - "paiementNoThanks": "Non merci", - "paiementAuthentificationFailed": "Échec de l'authentification", - "paiementPleaseAddDevice": "Veuillez ajouter cet appareil pour payer", - "paiementPayment": "Paiement", - "paiementBalanceAfterTransaction": "Solde après paiement : ", - "paiementCancel": "Annuler", - "paiementLimitedTo": "Limité à", - "paiementScanCode": "Scanner un code", - "paiementNext": "Suivant", - "paiementCancelTransaction": "Annuler la transaction", - "paiementTransactionCancelled": "Transaction annulée", - "paiementTransactionCancelledDescription": "Voulez-vous vraiment annuler la transaction de", - "paiementTransactionCancelledError": "Erreur lors de l'annulation de la transaction", - "paiementNoMembership": "Aucune adhésion", - "paiementNoMembershipDescription": "Ce produit n'est pas disponnible pour les non-adhérents. Confirmer l'encaissement ?", - "paiementQRCodeAlreadyUsed": "QR Code déjà utilisé", - "paiementCameraPermissionRequired": "Permission d'accès à la caméra requise", - "paiementCameraPerssionRequiredDescription": "Pour scanner un QR Code, vous devez autoriser l'accès à la caméra.", - "paiementSettings": "Paramètres", - "paiementReceived": "Reçu", - "paiementSpent": "Déboursé", - "paiementNoTrasactionForThisMonth": "Aucune transaction pour ce mois", - "paiementNoTransactinon": "Aucune transaction", - "paiementSellerRigths": "Droits du vendeur", - "paiementCanBank": "Peut encaisser", - "paiementCanSeeHistory": "Peut voir l'historique", - "paiementCanCancelTransaction": "Peut annuler des transactions", - "paiementCanManageSellers": "Peut gérer les vendeurs", - "paiementAddedSeller": "Vendeur ajouté", - "paiementAddingSellerError": "Erreur lors de l'ajout du vendeur", - "paiementBank": "Encaisser", - "paiementSeeHistory": "Voir l'historique", - "paiementCancelTransactions": "Annuler les transactions", - "paiementManageSellers": "Gérer les vendeurs", - "paiementStructureAdmin": "Administrateur de la structure", - "paiementRightsOf": "Droits de", - "paiementRightsUpdated": "Droits mis à jour", - "paiementRightsUpdateError": "Erreur lors de la mise à jour des droits", - "paiementDeleteSellerDescription": "Voulez-vous vraiment supprimer ce vendeur ?", - "paiementDeletedSeller": "Vendeur supprimé", - "paiementDeletingSellerError": "Erreur lors de la suppression du vendeur", - "paiementDeleteSeller": "Supprimer le vendeur", - "paiementAdd": "Ajouter", - "paiementAddSeller": "Ajouter un vendeur", - "paiementSellerError": "Vous n'êtes pas vendeur de cette association", - "paiementSellersOf": "Les vendeurs de", - "paiementModify": "Modifier", - "paiementAStore": "une association", - "paiementStoreName": "Nom de l'association", - "paiementSuccessfullyAddedStore": "Association ajoutée avec succès", - "paiementSuccessfullyModifiedStore": "Association modifiée avec succès", - "paiementAddingStoreError": "Erreur lors de l'ajout de l'association", - "paiementModifyingStoreError": "Erreur lors de la modification de l'association", - "paiementRefund": "Remboursement", - "paiementDoneTransaction": "Transaction effectuée", - "paiementRefundAction": "Rembourser", - "paiementTotalDuringPeriod": "Total sur la période", - "paiementMean": "Moyenne : ", - "paiementTransaction": "ransaction", - "paiementTransferStructure": "Transfert de structure", - "paiementYouAreTransferingStructureTo": "Vous êtes sur le point de transférer la structure à ", - "paiementTransferStructureDescription": "Le nouveau responsable aura accès à toutes les fonctionnalités de gestion de la structure. Vous allez recevoir un email pour valider ce transfert. Le lien ne sera actif que pendant 20 minutes. Cette action est irréversible. Êtes-vous sûr de vouloir continuer ?", - "paiementTransferStructureError": "Erreur lors du transfert de la structure", - "paiementTransferStructureSuccess": "Transfert de structure demandé avec succès", - "paiementNextAccountable": "Prochain responsable" + "paiementDeleteStore": "Supprimer l'association" } From ad766b5d5327f2d311f6c91875c40c49a46fa3a6 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 10 Aug 2025 18:20:37 +0200 Subject: [PATCH 239/473] feat: removing tags --- lib/advert/class/advert.dart | 11 ++--- lib/advert/class/tag.dart | 31 ------------ lib/advert/repositories/tag_repository.dart | 24 --------- lib/advert/ui/components/tag_chip.dart | 49 ------------------- lib/advert/ui/pages/detail_page/detail.dart | 15 ------ .../pages/form_page/add_edit_advert_page.dart | 11 ----- 6 files changed, 3 insertions(+), 138 deletions(-) delete mode 100644 lib/advert/class/tag.dart delete mode 100644 lib/advert/repositories/tag_repository.dart delete mode 100644 lib/advert/ui/components/tag_chip.dart diff --git a/lib/advert/class/advert.dart b/lib/advert/class/advert.dart index 1fe15b5355..a089a22511 100644 --- a/lib/advert/class/advert.dart +++ b/lib/advert/class/advert.dart @@ -7,7 +7,6 @@ class Advert { late final String content; late final DateTime date; late final Announcer announcer; - late final List tags; Advert({ required this.id, @@ -15,7 +14,6 @@ class Advert { required this.content, required this.date, required this.announcer, - required this.tags, }); Advert.fromJson(Map json) { @@ -24,7 +22,6 @@ class Advert { content = json["content"]; date = processDateFromAPI(json["date"]); announcer = Announcer.fromJson(json["advertiser"]); - tags = json["tags"].split(', '); } Map toJson() { @@ -34,7 +31,8 @@ class Advert { data["content"] = content; data["date"] = processDateToAPI(date); data["advertiser_id"] = announcer.id; - data["tags"] = tags.join(', '); + // TODO: waiting backend migration + data["tags"] = ""; return data; } @@ -44,7 +42,6 @@ class Advert { String? content, DateTime? date, Announcer? announcer, - List? tags, }) { return Advert( id: id ?? this.id, @@ -52,7 +49,6 @@ class Advert { content: content ?? this.content, date: date ?? this.date, announcer: announcer ?? this.announcer, - tags: tags ?? this.tags, ); } @@ -63,12 +59,11 @@ class Advert { content: "", date: DateTime.now(), announcer: Announcer.empty(), - tags: [], ); } @override String toString() { - return 'Advert{id: $id, title: $title, content: $content, date: $date, announcer: $announcer, tags: $tags}'; + return 'Advert{id: $id, title: $title, content: $content, date: $date, announcer: $announcer}'; } } diff --git a/lib/advert/class/tag.dart b/lib/advert/class/tag.dart deleted file mode 100644 index 6b19c33d12..0000000000 --- a/lib/advert/class/tag.dart +++ /dev/null @@ -1,31 +0,0 @@ -class Tag { - late final String id; - late final String name; - - Tag({required this.id, required this.name}); - - Tag.fromJson(Map json) { - id = json["id"]; - name = json["name"]; - } - - Map toJson() { - final data = {}; - data["id"] = id; - data["name"] = name; - return data; - } - - Tag copyWith({String? id, String? name}) { - return Tag(id: id ?? this.id, name: name ?? this.name); - } - - static Tag empty() { - return Tag(id: "", name: ""); - } - - @override - String toString() { - return 'Tag{id: $id, name: $name}'; - } -} diff --git a/lib/advert/repositories/tag_repository.dart b/lib/advert/repositories/tag_repository.dart deleted file mode 100644 index 01f25b0d3e..0000000000 --- a/lib/advert/repositories/tag_repository.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:titan/advert/class/tag.dart'; -import 'package:titan/tools/repository/repository.dart'; - -class TagRepository extends Repository { - @override - // ignore: overridden_fields - final ext = "advert/tag/"; - - Future> getAllTag() async { - return (await getList()).map((e) => Tag.fromJson(e)).toList(); - } - - Future getTag(String id) async { - return Tag.fromJson(await getOne(id)); - } - - Future addTag(Tag tag) async { - return Tag.fromJson(await create(tag.toJson())); - } - - Future deleteTag(String id) async { - return await delete("/$id"); - } -} diff --git a/lib/advert/ui/components/tag_chip.dart b/lib/advert/ui/components/tag_chip.dart deleted file mode 100644 index fba084f3c5..0000000000 --- a/lib/advert/ui/components/tag_chip.dart +++ /dev/null @@ -1,49 +0,0 @@ -import 'dart:math'; - -import 'package:flutter/material.dart'; -import 'package:titan/advert/tools/functions.dart'; - -class TagChip extends StatelessWidget { - final String tagName; - - const TagChip({super.key, required this.tagName}); - - @override - Widget build(BuildContext context) { - Color bgColor = generateColor(tagName); - Color borderColor = bgColor.computeLuminance() > 0.1 - ? bgColor - : Colors.white; - Color darkerBgColor = Color.from( - alpha: bgColor.a, - red: max(bgColor.r - 0.12, 0), // 0.12 = 30/255 - green: max(bgColor.g - 0.12, 0), - blue: max(bgColor.b - 0.12, 0), - ); - - return Container( - margin: const EdgeInsets.symmetric(horizontal: 5), - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 5), - height: 30, - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomLeft, - colors: [bgColor, darkerBgColor], - stops: const [0.7, 1.0], - ), - borderRadius: BorderRadius.circular(20), - border: Border.all(color: borderColor), - ), - child: Align( - alignment: Alignment.center, - child: Text( - tagName, - textAlign: TextAlign.center, - maxLines: 1, - style: const TextStyle(color: Colors.white, fontSize: 13), - ), - ), - ); - } -} diff --git a/lib/advert/ui/pages/detail_page/detail.dart b/lib/advert/ui/pages/detail_page/detail.dart index 643d28fb42..168b6b67c2 100644 --- a/lib/advert/ui/pages/detail_page/detail.dart +++ b/lib/advert/ui/pages/detail_page/detail.dart @@ -6,10 +6,8 @@ import 'package:intl/intl.dart'; import 'package:titan/advert/providers/advert_poster_provider.dart'; import 'package:titan/advert/providers/advert_posters_provider.dart'; import 'package:titan/advert/providers/advert_provider.dart'; -import 'package:titan/advert/ui/components/tag_chip.dart'; import 'package:titan/cinema/tools/functions.dart'; import 'package:titan/tools/ui/builders/auto_loader_child.dart'; -import 'package:titan/tools/ui/layouts/horizontal_list_view.dart'; import 'package:titan/tools/ui/widgets/text_with_hyper_link.dart'; import 'package:qlevar_router/qlevar_router.dart'; @@ -24,10 +22,6 @@ class AdvertDetailPage extends HookConsumerWidget { ); final advertPostersNotifier = ref.watch(advertPostersProvider.notifier); final logoNotifier = ref.watch(advertPosterProvider.notifier); - final filteredTagList = advert.tags - .where((element) => element != "") - .toList(); - final inTagChipsList = [advert.announcer.name] + filteredTagList; return Stack( children: [ @@ -101,15 +95,6 @@ class AdvertDetailPage extends HookConsumerWidget { ), ), const SizedBox(height: 20), - HorizontalListView.builder( - height: 35, - horizontalSpace: 30, - items: inTagChipsList, - itemBuilder: - (BuildContext context, String item, int index) => - TagChip(tagName: item), - ), - const SizedBox(height: 15), Container( padding: const EdgeInsets.symmetric(horizontal: 30.0), child: TextWithHyperLink( diff --git a/lib/advert/ui/pages/form_page/add_edit_advert_page.dart b/lib/advert/ui/pages/form_page/add_edit_advert_page.dart index 324c1251b7..7253f4ff60 100644 --- a/lib/advert/ui/pages/form_page/add_edit_advert_page.dart +++ b/lib/advert/ui/pages/form_page/add_edit_advert_page.dart @@ -36,9 +36,6 @@ class AdvertAddEditAdvertPage extends HookConsumerWidget { final content = useTextEditingController(text: advert.content); final selectedAnnouncers = ref.watch(announcerProvider); - final tags = advert.tags; - var textTags = tags.join(', '); - final textTagsController = useTextEditingController(text: textTags); final advertPosters = ref.watch(advertPostersProvider); final advertListNotifier = ref.watch(advertListProvider.notifier); final posterNotifier = ref.watch(advertPosterProvider.notifier); @@ -210,13 +207,6 @@ class AdvertAddEditAdvertPage extends HookConsumerWidget { padding: const EdgeInsets.symmetric(horizontal: 30), child: Column( children: [ - TextEntry( - maxLines: 1, - label: AppLocalizations.of(context)!.advertTags, - canBeEmpty: true, - controller: textTagsController, - ), - const SizedBox(height: 50), WaitingButton( onTap: () async { if (key.currentState == null) { @@ -232,7 +222,6 @@ class AdvertAddEditAdvertPage extends HookConsumerWidget { announcer: selectedAnnouncers[0], content: content.text, date: isEdit ? advert.date : DateTime.now(), - tags: textTagsController.text.split(', '), title: title.text, ); final editedAdvertMsg = AppLocalizations.of( From 172b8e40970a134f7f697f949aa15fe30ee754ba Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 10 Aug 2025 18:20:37 +0200 Subject: [PATCH 240/473] feat: new advert card ui --- lib/advert/ui/components/advert_card.dart | 415 +++++++------------ lib/advert/ui/components/announcer_bar.dart | 102 +++-- lib/advert/ui/pages/advert.dart | 1 - lib/advert/ui/pages/main_page/main_page.dart | 139 +++---- lib/tools/ui/builders/auto_loader_child.dart | 8 +- 5 files changed, 293 insertions(+), 372 deletions(-) diff --git a/lib/advert/ui/components/advert_card.dart b/lib/advert/ui/components/advert_card.dart index 43d06906d9..2fdc8e5557 100644 --- a/lib/advert/ui/components/advert_card.dart +++ b/lib/advert/ui/components/advert_card.dart @@ -1,16 +1,14 @@ -import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:intl/intl.dart'; import 'package:titan/advert/class/advert.dart'; import 'package:titan/advert/providers/advert_poster_provider.dart'; import 'package:titan/advert/providers/advert_posters_provider.dart'; -import 'package:titan/advert/tools/functions.dart'; -import 'package:titan/cinema/tools/functions.dart'; import 'package:titan/navigation/providers/is_web_format_provider.dart'; +import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/auto_loader_child.dart'; -import 'package:titan/tools/ui/widgets/text_with_hyper_link.dart'; +import 'package:timeago/timeago.dart' as timeago; class AdvertCard extends HookConsumerWidget { final VoidCallback onTap; @@ -20,10 +18,7 @@ class AdvertCard extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - double width = 300; - double height = 300; - double imageHeight = 175; - double maxHeight = MediaQuery.of(context).size.height - 344; + final isExpanded = useState(false); final posters = ref.watch( advertPostersProvider.select((advertPosters) => advertPosters[advert.id]), ); @@ -38,280 +33,162 @@ class AdvertCard extends HookConsumerWidget { }, child: Container( margin: const EdgeInsets.all(10), - padding: EdgeInsets.all(isWebFormat ? 50 : 0), - child: isWebFormat - ? Container( - height: maxHeight, - width: double.infinity, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(30), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - ClipRRect( - borderRadius: BorderRadius.circular(30), - child: AspectRatio( - aspectRatio: 2 / 3, - child: AutoLoaderChild( - group: posters, - notifier: advertPostersNotifier, - mapKey: advert.id, - loader: (advertId) => - posterNotifier.getAdvertPoster(advertId), - loadingBuilder: (context) => - HeroIcon(HeroIcons.photo, size: width), - dataBuilder: (context, value) => Image( - image: value.first.image, - fit: BoxFit.cover, // use this - ), - ), - ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 10.0), + child: Row( + children: [ + Container( + width: 40, + height: 40, + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: Colors.black, ), - const SizedBox(width: 50), - Expanded( - child: Column( - children: [ - AutoSizeText( - advert.title, - maxLines: 2, - overflow: TextOverflow.ellipsis, - textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 10), - AutoSizeText( - formatDate(advert.date), - maxLines: 1, - textAlign: TextAlign.center, - style: const TextStyle(fontSize: 16), - ), - const SizedBox(height: 10), - Expanded( - child: SingleChildScrollView( - child: TextWithHyperLink( - advert.content, - textAlign: TextAlign.center, - style: const TextStyle(fontSize: 16), - ), - ), - ), - ], + child: Center( + child: Text( + advert.announcer.name.isNotEmpty + ? advert.announcer.name + .split(' ') + .take(2) + .map((s) => s[0].toUpperCase()) + .join() + : '?', + style: const TextStyle( + color: Colors.white, + fontSize: 18, + fontWeight: FontWeight.bold, + ), ), ), - const SizedBox(width: 50), - ], - ), - ) - : Container( - margin: const EdgeInsets.symmetric(vertical: 10), - width: width, - height: height, - decoration: BoxDecoration( - color: Colors.white, - boxShadow: const [ - BoxShadow( - blurRadius: 5, - color: Color(0x33000000), - offset: Offset(2, 2), - spreadRadius: 3, - ), - ], - borderRadius: BorderRadius.circular(20), - ), - child: Stack( - children: [ - Column( + ), + const SizedBox(width: 12), + + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - AutoLoaderChild( - group: posters, - notifier: advertPostersNotifier, - mapKey: advert.id, - loader: (advertId) => - posterNotifier.getAdvertPoster(advertId), - loadingBuilder: (context) => Container( - width: width, - height: imageHeight, - decoration: const BoxDecoration( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(20), - topRight: Radius.circular(20), - ), - ), - child: HeroIcon(HeroIcons.photo, size: width), - ), - dataBuilder: (context, value) => Container( - width: width, - height: imageHeight, - decoration: BoxDecoration( - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(20), - topRight: Radius.circular(20), - ), - image: DecorationImage( - image: value.first.image, - fit: BoxFit.cover, - ), - ), + Text( + advert.announcer.name, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black, ), ), - Container( - padding: const EdgeInsets.only( - top: 20, - left: 10, - right: 10, + Text( + _capitalizeFirst( + timeago.format(advert.date, locale: 'fr_short'), ), - width: width, - height: height - imageHeight, - child: Column( - children: [ - Column( - children: [ - Container( - width: width, - margin: const EdgeInsets.only(bottom: 5), - child: AutoSizeText( - advert.title.trim(), - textAlign: TextAlign.left, - overflow: TextOverflow.ellipsis, - maxLines: 1, - minFontSize: 15, - style: const TextStyle( - color: Colors.black, - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - ), - ], - ), - TextWithHyperLink( - advert.content.trim(), - overflow: TextOverflow.ellipsis, - textAlign: TextAlign.justify, - maxLines: 3, - minFontSize: 13, - maxFontSize: 15, - style: const TextStyle( - color: Colors.black, - fontSize: 15, - ), - ), - ], + style: const TextStyle( + fontSize: 12, + color: Colors.grey, ), ), ], ), - Positioned( - top: imageHeight - 40, - left: 15, - child: Container( - decoration: BoxDecoration( - borderRadius: const BorderRadius.all( - Radius.circular(8), - ), - boxShadow: [ - BoxShadow( - blurRadius: 5, - color: Colors.black.withValues(alpha: 0.3), - offset: const Offset(2, 2), - spreadRadius: 3, - ), - ], - ), - child: ClipRRect( - borderRadius: const BorderRadius.all( - Radius.circular(8), - ), - child: Container( - color: Colors.white, - height: 50, - width: 50, - padding: const EdgeInsets.symmetric( - horizontal: 5, - vertical: 5, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - AutoSizeText( - DateFormat('dd').format(advert.date), - textAlign: TextAlign.center, - style: const TextStyle( - color: Colors.black, - fontSize: 22, - fontWeight: FontWeight.bold, - height: 1.0, - ), - ), - AutoSizeText( - getLocalizedMonths(context)[int.parse( - DateFormat('MM').format(advert.date), - ) - - 1], - textAlign: TextAlign.center, - style: const TextStyle( - color: Colors.black, - fontSize: 11, - fontWeight: FontWeight.bold, - height: 1.0, - ), - ), - ], - ), - ), - ), - ), - ), - Positioned( - top: imageHeight - 20, - right: 15, - child: Container( - decoration: BoxDecoration( - borderRadius: const BorderRadius.all( - Radius.circular(8), - ), - boxShadow: [ - BoxShadow( - blurRadius: 5, - color: Colors.black.withValues(alpha: 0.3), - offset: const Offset(2, 2), - spreadRadius: 3, - ), - ], - ), - child: ClipRRect( - borderRadius: const BorderRadius.all( - Radius.circular(8), - ), - child: Container( - color: Colors.white, - height: 30, - padding: const EdgeInsets.symmetric( - vertical: 5, - horizontal: 10, - ), - alignment: Alignment.center, - child: AutoSizeText( - advert.announcer.name, - textAlign: TextAlign.center, - style: const TextStyle( - color: Colors.black, - fontSize: 14, - fontWeight: FontWeight.bold, - ), - ), - ), - ), - ), + ), + ], + ), + ), + const SizedBox(height: 15), + + AutoLoaderChild( + group: posters, + notifier: advertPostersNotifier, + mapKey: advert.id, + loader: (advertId) => posterNotifier.getAdvertPoster(advertId), + loadingBuilder: (context) => AspectRatio( + aspectRatio: 1, + child: Container( + decoration: BoxDecoration( + color: ColorConstants.onBackground, + borderRadius: BorderRadius.circular(20), + ), + child: const Center( + child: HeroIcon(HeroIcons.photo, size: 50), + ), + ), + ), + dataBuilder: (context, value) => AspectRatio( + aspectRatio: 1, + child: Container( + decoration: BoxDecoration( + color: ColorConstants.onBackground, + borderRadius: BorderRadius.circular(20), + image: DecorationImage( + image: value.first.image, + fit: BoxFit.cover, ), - ], + ), + ), + ), + ), + + Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [_buildExpandableText(isExpanded)], + ), + ), + ], + ), + ), + ); + } + + String _capitalizeFirst(String text) { + if (text.isEmpty) return text; + return text[0].toUpperCase() + text.substring(1); + } + + Widget _buildExpandableText(ValueNotifier isExpanded) { + final title = advert.title.trim(); + final content = advert.content.trim(); + + final fullText = title.isNotEmpty ? '$title $content' : content; + + final isLong = fullText.length > 40; + + return RichText( + text: TextSpan( + style: const TextStyle(color: Colors.black, fontSize: 14, height: 1.4), + children: [ + if (title.isNotEmpty) ...[ + TextSpan( + text: title, + style: const TextStyle(fontWeight: FontWeight.bold), + ), + const TextSpan(text: ' '), + ], + TextSpan( + text: isLong && !isExpanded.value + ? '${content.substring(0, 40)}...' + : content, + ), + if (isLong) ...[ + const TextSpan(text: ' '), + WidgetSpan( + alignment: PlaceholderAlignment.middle, + child: GestureDetector( + onTap: () { + isExpanded.value = !isExpanded.value; + }, + child: Text( + isExpanded.value ? 'voir moins' : 'voir plus', + style: const TextStyle( + color: Colors.grey, + fontSize: 14, + fontWeight: FontWeight.w500, + ), ), ), + ), + ], + ], ), ); } diff --git a/lib/advert/ui/components/announcer_bar.dart b/lib/advert/ui/components/announcer_bar.dart index ce1a331382..0b3cd9d083 100644 --- a/lib/advert/ui/components/announcer_bar.dart +++ b/lib/advert/ui/components/announcer_bar.dart @@ -1,10 +1,11 @@ +import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/advert/providers/announcer_provider.dart'; import 'package:titan/advert/providers/announcer_list_provider.dart'; +import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/horizontal_list_view.dart'; -import 'package:titan/tools/ui/layouts/item_chip.dart'; class AnnouncerBar extends HookConsumerWidget { final bool useUserAnnouncers; @@ -25,41 +26,86 @@ class AnnouncerBar extends HookConsumerWidget { final announcerList = useUserAnnouncers ? ref.watch(userAnnouncerListProvider) : ref.watch(announcerListProvider); - final darkerColor = (isNotClickable) ? Colors.grey[800] : Colors.black; return AsyncChild( value: announcerList, builder: (context, userAnnouncers) => HorizontalListView.builder( - height: 40, + height: 66, items: userAnnouncers, - itemBuilder: (context, e, i) => GestureDetector( - onTap: () { - if (isNotClickable) { - return; - } - if (multipleSelect) { - selectedId.contains(e.id) - ? selectedNotifier.removeAnnouncer(e) - : selectedNotifier.addAnnouncer(e); - } else { - bool contain = selectedId.contains(e.id); - selectedNotifier.clearAnnouncer(); - if (!contain) { - selectedNotifier.addAnnouncer(e); + itemBuilder: (context, e, i) { + final selected = selectedId.contains(e.id); + return GestureDetector( + onTap: () { + if (isNotClickable) { + return; } - } - }, - child: ItemChip( - selected: selectedId.contains(e.id), - child: Text( - e.name, - style: TextStyle( - color: selectedId.contains(e.id) ? Colors.white : darkerColor, - fontWeight: FontWeight.bold, + if (multipleSelect) { + selected + ? selectedNotifier.removeAnnouncer(e) + : selectedNotifier.addAnnouncer(e); + } else { + selectedNotifier.clearAnnouncer(); + if (!selected) { + selectedNotifier.addAnnouncer(e); + } + } + }, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 5.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 45, + height: 45, + decoration: BoxDecoration( + shape: BoxShape.circle, + border: selected + ? Border.all(color: ColorConstants.tertiary, width: 3) + : null, + color: Colors.grey.shade100, + ), + child: Center( + child: Text( + e.name + .split(' ') + .take(2) + .map((s) => s[0].toUpperCase()) + .join(), + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.bold, + color: selected + ? ColorConstants.onTertiary + : ColorConstants.tertiary, + ), + ), + ), + ), + const SizedBox(height: 4), + SizedBox( + width: 55, + child: AutoSizeText( + e.name, + textAlign: TextAlign.center, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 12, + color: selected + ? ColorConstants.onTertiary + : ColorConstants.tertiary, + fontWeight: selected + ? FontWeight.bold + : FontWeight.normal, + ), + ), + ), + ], ), ), - ), - ), + ); + }, ), ); } diff --git a/lib/advert/ui/pages/advert.dart b/lib/advert/ui/pages/advert.dart index bf862afe1b..f1712cbf33 100644 --- a/lib/advert/ui/pages/advert.dart +++ b/lib/advert/ui/pages/advert.dart @@ -24,7 +24,6 @@ class AdvertTemplate extends HookConsumerWidget { selectedAnnouncersNotifier.clearAnnouncer(); }, ), - const SizedBox(height: 30), Expanded(child: child), ], ), diff --git a/lib/advert/ui/pages/main_page/main_page.dart b/lib/advert/ui/pages/main_page/main_page.dart index 992321143b..c725a98a55 100644 --- a/lib/advert/ui/pages/main_page/main_page.dart +++ b/lib/advert/ui/pages/main_page/main_page.dart @@ -1,5 +1,6 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; +import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/advert/providers/advert_list_provider.dart'; import 'package:titan/advert/providers/advert_posters_provider.dart'; @@ -10,12 +11,11 @@ import 'package:titan/advert/ui/pages/advert.dart'; import 'package:titan/advert/router.dart'; import 'package:titan/advert/ui/components/announcer_bar.dart'; import 'package:titan/advert/ui/components/advert_card.dart'; -import 'package:titan/admin/providers/is_admin_provider.dart'; +import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; -import 'package:titan/tools/ui/layouts/column_refresher.dart'; -import 'package:titan/tools/ui/widgets/admin_button.dart'; +import 'package:titan/tools/ui/layouts/refresher.dart'; +import 'package:titan/tools/ui/styleguide/icon_button.dart'; import 'package:qlevar_router/qlevar_router.dart'; -import 'package:titan/l10n/app_localizations.dart'; class AdvertMainPage extends HookConsumerWidget { const AdvertMainPage({super.key}); @@ -28,80 +28,77 @@ class AdvertMainPage extends HookConsumerWidget { final advertPostersNotifier = ref.watch(advertPostersProvider.notifier); final selected = ref.watch(announcerProvider); final selectedNotifier = ref.watch(announcerProvider.notifier); - final isAdmin = ref.watch(isAdminProvider); - final isAdvertAdmin = ref.watch(isAdvertAdminProvider); + final isAdmin = ref.watch(isAdvertAdminProvider); return AdvertTemplate( - child: Stack( + child: Column( children: [ - AsyncChild( - value: advertList, - builder: (context, advertData) { - final sortedAdvertData = advertData - .sortedBy((element) => element.date) - .reversed; - final filteredSortedAdvertData = sortedAdvertData.where( - (advert) => - selected - .where((e) => advert.announcer.name == e.name) - .isNotEmpty || - selected.isEmpty, - ); - return ColumnRefresher( - onRefresh: () async { - await advertListNotifier.loadAdverts(); - advertPostersNotifier.resetTData(); - }, - children: [ - SizedBox( - width: 300, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - if (isAdvertAdmin) - AdminButton( - onTap: () { - selectedNotifier.clearAnnouncer(); - QR.to(AdvertRouter.root + AdvertRouter.admin); - }, - ), - if (isAdmin) - AdminButton( + const AnnouncerBar(useUserAnnouncers: false, multipleSelect: true), + const SizedBox(height: 20), + + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + "Annonces", + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: ColorConstants.title, + ), + ), + if (isAdmin) + CustomIconButton( + icon: HeroIcon( + HeroIcons.userGroup, + color: ColorConstants.background, + ), + onPressed: () { + selectedNotifier.clearAnnouncer(); + QR.to(AdvertRouter.root + AdvertRouter.admin); + }, + ), + ], + ), + ), + + Expanded( + child: AsyncChild( + value: advertList, + builder: (context, advertData) { + final sortedAdvertData = advertData + .sortedBy((element) => element.date) + .reversed; + final filteredSortedAdvertData = sortedAdvertData.where( + (advert) => + selected + .where((e) => advert.announcer.name == e.name) + .isNotEmpty || + selected.isEmpty, + ); + return Refresher( + onRefresh: () async { + await advertListNotifier.loadAdverts(); + advertPostersNotifier.resetTData(); + }, + child: Column( + children: filteredSortedAdvertData + .map( + (advert) => AdvertCard( onTap: () { - QR.to( - AdvertRouter.root + - AdvertRouter.addRemAnnouncer, - ); + advertNotifier.setAdvert(advert); + QR.to(AdvertRouter.root + AdvertRouter.detail); }, - text: AppLocalizations.of( - context, - )!.advertManagement, + advert: advert, ), - ], - ), - ), - const SizedBox(height: 20), - const AnnouncerBar( - useUserAnnouncers: false, - multipleSelect: true, + ) + .toList(), ), - const SizedBox(height: 20), - ...filteredSortedAdvertData.map( - (advert) => Padding( - padding: const EdgeInsets.symmetric(horizontal: 30), - child: AdvertCard( - onTap: () { - advertNotifier.setAdvert(advert); - QR.to(AdvertRouter.root + AdvertRouter.detail); - }, - advert: advert, - ), - ), - ), - ], - ); - }, + ); + }, + ), ), - const SizedBox(height: 20), ], ), ); diff --git a/lib/tools/ui/builders/auto_loader_child.dart b/lib/tools/ui/builders/auto_loader_child.dart index f057860c2a..f6aaddc23e 100644 --- a/lib/tools/ui/builders/auto_loader_child.dart +++ b/lib/tools/ui/builders/auto_loader_child.dart @@ -35,9 +35,11 @@ class AutoLoaderChild extends ConsumerWidget { final nonNullLoadingBuilder = loadingBuilder ?? (context) => Loader(color: loaderColor); if (group == null) { - loader == null - ? notifier.autoLoadList(ref, mapKey, listLoader!) - : notifier.autoLoad(ref, mapKey, loader!); + Future.microtask(() { + loader == null + ? notifier.autoLoadList(ref, mapKey, listLoader!) + : notifier.autoLoad(ref, mapKey, loader!); + }); return nonNullLoadingBuilder(context); } return AsyncChild( From d0fff00e5695abb3ddba049d6f80429c7bdaf881 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 10 Aug 2025 18:20:37 +0200 Subject: [PATCH 241/473] feat: missing bottom padding --- lib/advert/ui/pages/main_page/main_page.dart | 23 ++++++++++---------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/lib/advert/ui/pages/main_page/main_page.dart b/lib/advert/ui/pages/main_page/main_page.dart index c725a98a55..00c2241222 100644 --- a/lib/advert/ui/pages/main_page/main_page.dart +++ b/lib/advert/ui/pages/main_page/main_page.dart @@ -83,17 +83,18 @@ class AdvertMainPage extends HookConsumerWidget { advertPostersNotifier.resetTData(); }, child: Column( - children: filteredSortedAdvertData - .map( - (advert) => AdvertCard( - onTap: () { - advertNotifier.setAdvert(advert); - QR.to(AdvertRouter.root + AdvertRouter.detail); - }, - advert: advert, - ), - ) - .toList(), + children: [ + ...filteredSortedAdvertData.map( + (advert) => AdvertCard( + onTap: () { + advertNotifier.setAdvert(advert); + QR.to(AdvertRouter.root + AdvertRouter.detail); + }, + advert: advert, + ), + ), + SizedBox(height: 80), + ], ), ); }, From db9d9a262aa3cb0535c141a505feaef2e596c90c Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 10 Aug 2025 18:20:38 +0200 Subject: [PATCH 242/473] feat: new admin page ui --- lib/advert/ui/components/announcer_bar.dart | 69 +----- lib/advert/ui/components/announcer_item.dart | 71 ++++++ .../pages/admin_page/admin_advert_card.dart | 91 ++++---- .../ui/pages/admin_page/admin_page.dart | 208 +++++++++--------- .../main_page}/advert_card.dart | 0 lib/advert/ui/pages/main_page/main_page.dart | 2 +- lib/tools/ui/styleguide/icon_button.dart | 10 +- 7 files changed, 245 insertions(+), 206 deletions(-) create mode 100644 lib/advert/ui/components/announcer_item.dart rename lib/advert/ui/{components => pages/main_page}/advert_card.dart (100%) diff --git a/lib/advert/ui/components/announcer_bar.dart b/lib/advert/ui/components/announcer_bar.dart index 0b3cd9d083..ac45c4fc8a 100644 --- a/lib/advert/ui/components/announcer_bar.dart +++ b/lib/advert/ui/components/announcer_bar.dart @@ -1,9 +1,8 @@ -import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/advert/providers/announcer_provider.dart'; import 'package:titan/advert/providers/announcer_list_provider.dart'; -import 'package:titan/tools/constants.dart'; +import 'package:titan/advert/ui/components/announcer_item.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/horizontal_list_view.dart'; @@ -11,11 +10,13 @@ class AnnouncerBar extends HookConsumerWidget { final bool useUserAnnouncers; final bool multipleSelect; final bool isNotClickable; + final Widget? addButton; const AnnouncerBar({ super.key, required this.multipleSelect, required this.useUserAnnouncers, this.isNotClickable = false, + this.addButton, }); @override @@ -30,11 +31,12 @@ class AnnouncerBar extends HookConsumerWidget { return AsyncChild( value: announcerList, builder: (context, userAnnouncers) => HorizontalListView.builder( + firstChild: addButton, height: 66, items: userAnnouncers, itemBuilder: (context, e, i) { final selected = selectedId.contains(e.id); - return GestureDetector( + return AnnouncerItem( onTap: () { if (isNotClickable) { return; @@ -50,60 +52,13 @@ class AnnouncerBar extends HookConsumerWidget { } } }, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 5.0), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - width: 45, - height: 45, - decoration: BoxDecoration( - shape: BoxShape.circle, - border: selected - ? Border.all(color: ColorConstants.tertiary, width: 3) - : null, - color: Colors.grey.shade100, - ), - child: Center( - child: Text( - e.name - .split(' ') - .take(2) - .map((s) => s[0].toUpperCase()) - .join(), - style: TextStyle( - fontSize: 15, - fontWeight: FontWeight.bold, - color: selected - ? ColorConstants.onTertiary - : ColorConstants.tertiary, - ), - ), - ), - ), - const SizedBox(height: 4), - SizedBox( - width: 55, - child: AutoSizeText( - e.name, - textAlign: TextAlign.center, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: 12, - color: selected - ? ColorConstants.onTertiary - : ColorConstants.tertiary, - fontWeight: selected - ? FontWeight.bold - : FontWeight.normal, - ), - ), - ), - ], - ), - ), + name: e.name, + avatarName: e.name + .split(' ') + .take(2) + .map((s) => s[0].toUpperCase()) + .join(), + selected: selected, ); }, ), diff --git a/lib/advert/ui/components/announcer_item.dart b/lib/advert/ui/components/announcer_item.dart new file mode 100644 index 0000000000..9bfde9e86d --- /dev/null +++ b/lib/advert/ui/components/announcer_item.dart @@ -0,0 +1,71 @@ +import 'package:auto_size_text/auto_size_text.dart'; +import 'package:flutter/material.dart'; +import 'package:titan/tools/constants.dart'; + +class AnnouncerItem extends StatelessWidget { + final String name, avatarName; + final bool selected; + final VoidCallback onTap; + const AnnouncerItem({ + super.key, + required this.name, + required this.onTap, + required this.selected, + required this.avatarName, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 5.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 45, + height: 45, + decoration: BoxDecoration( + shape: BoxShape.circle, + border: selected + ? Border.all(color: ColorConstants.tertiary, width: 3) + : null, + color: Colors.grey.shade100, + ), + child: Center( + child: Text( + avatarName, + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.bold, + color: selected + ? ColorConstants.onTertiary + : ColorConstants.tertiary, + ), + ), + ), + ), + const SizedBox(height: 4), + SizedBox( + width: 55, + child: AutoSizeText( + name, + textAlign: TextAlign.center, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 12, + color: selected + ? ColorConstants.onTertiary + : ColorConstants.tertiary, + fontWeight: selected ? FontWeight.bold : FontWeight.normal, + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/advert/ui/pages/admin_page/admin_advert_card.dart b/lib/advert/ui/pages/admin_page/admin_advert_card.dart index 7397504045..1e6af89a27 100644 --- a/lib/advert/ui/pages/admin_page/admin_advert_card.dart +++ b/lib/advert/ui/pages/admin_page/admin_advert_card.dart @@ -2,10 +2,9 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/advert/class/advert.dart'; -import 'package:titan/advert/tools/constants.dart'; -import 'package:titan/advert/ui/components/advert_card.dart'; -import 'package:titan/tools/ui/builders/waiting_button.dart'; -import 'package:titan/tools/ui/layouts/card_button.dart'; +import 'package:timeago/timeago.dart' as timeago; +import 'package:titan/tools/constants.dart'; +import 'package:titan/tools/ui/styleguide/icon_button.dart'; class AdminAdvertCard extends HookConsumerWidget { final VoidCallback onTap, onEdit; @@ -23,50 +22,62 @@ class AdminAdvertCard extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { return Padding( - padding: const EdgeInsets.symmetric(horizontal: 30), - child: Stack( - children: [ - AdvertCard(onTap: onTap, advert: advert), - Positioned( - top: 10, - right: 15, - child: Container( - margin: const EdgeInsets.only(top: 30), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - GestureDetector( - onTap: onEdit, - child: CardButton( - colors: [Colors.grey.shade100, Colors.grey.shade400], - shadowColor: Colors.grey.shade300.withValues(alpha: 0.2), - child: const HeroIcon( - HeroIcons.pencil, + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Container( + margin: const EdgeInsets.all(10), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + advert.title, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, color: Colors.black, ), ), - ), - const SizedBox(width: 20), - WaitingButton( - onTap: onDelete, - builder: (child) => CardButton( - colors: const [ - AdvertColorConstants.redGradient1, - AdvertColorConstants.redGradient2, - ], - shadowColor: AdvertColorConstants.redGradient2.withValues( - alpha: 0.2, + Text( + _capitalizeFirst( + timeago.format(advert.date, locale: 'fr_short'), + ), + style: const TextStyle( + fontSize: 12, + color: ColorConstants.tertiary, ), - child: child, ), - child: const HeroIcon(HeroIcons.trash, color: Colors.white), + ], + ), + const Spacer(), + CustomIconButton.secondary( + onPressed: onEdit, + icon: const HeroIcon( + HeroIcons.pencil, + color: ColorConstants.tertiary, + ), + ), + const SizedBox(width: 20), + CustomIconButton.danger( + onPressed: onDelete, + icon: const HeroIcon( + HeroIcons.trash, + color: ColorConstants.background, ), - ], - ), + ), + ], ), - ), - ], + ], + ), ), ); } + + String _capitalizeFirst(String text) { + if (text.isEmpty) return text; + return text[0].toUpperCase() + text.substring(1); + } } diff --git a/lib/advert/ui/pages/admin_page/admin_page.dart b/lib/advert/ui/pages/admin_page/admin_page.dart index 3a7e21f47e..332829a045 100644 --- a/lib/advert/ui/pages/admin_page/admin_page.dart +++ b/lib/advert/ui/pages/admin_page/admin_page.dart @@ -1,6 +1,5 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; -import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/advert/class/advert.dart'; import 'package:titan/advert/providers/advert_list_provider.dart'; @@ -8,13 +7,13 @@ import 'package:titan/advert/providers/advert_posters_provider.dart'; import 'package:titan/advert/providers/advert_provider.dart'; import 'package:titan/advert/providers/announcer_list_provider.dart'; import 'package:titan/advert/providers/announcer_provider.dart'; +import 'package:titan/advert/ui/components/announcer_item.dart'; import 'package:titan/advert/ui/pages/admin_page/admin_advert_card.dart'; import 'package:titan/advert/ui/pages/advert.dart'; import 'package:titan/advert/router.dart'; import 'package:titan/advert/ui/components/announcer_bar.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; -import 'package:titan/tools/ui/layouts/card_layout.dart'; -import 'package:titan/tools/ui/layouts/column_refresher.dart'; +import 'package:titan/tools/ui/layouts/refresher.dart'; import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/l10n/app_localizations.dart'; @@ -35,110 +34,111 @@ class AdvertAdminPage extends HookConsumerWidget { final selectedAnnouncers = ref.watch(announcerProvider); final selectedAnnouncersNotifier = ref.read(announcerProvider.notifier); return AdvertTemplate( - child: AsyncChild( - value: advertList, - builder: (context, advertData) => AsyncChild( - value: userAnnouncerList, - builder: (context, userAnnouncerData) { - final userAnnouncerAdvert = advertData.where( - (advert) => userAnnouncerData - .where((element) => advert.announcer.id == element.id) - .isNotEmpty, - ); - final sortedUserAnnouncerAdverts = userAnnouncerAdvert - .toList() - .sortedBy((element) => element.date) - .reversed; - final filteredSortedUserAnnouncerAdverts = - sortedUserAnnouncerAdverts - .where( - (advert) => - selectedAnnouncers - .where((e) => advert.announcer.id == e.id) - .isNotEmpty || - selectedAnnouncers.isEmpty, - ) - .toList(); - return ColumnRefresher( - onRefresh: () async { - await advertListNotifier.loadAdverts(); - await userAnnouncerListNotifier.loadMyAnnouncerList(); - advertPostersNotifier.resetTData(); + child: Column( + children: [ + AnnouncerBar( + useUserAnnouncers: true, + multipleSelect: true, + addButton: AnnouncerItem( + name: 'Post', + avatarName: '+', + selected: false, + onTap: () { + advertNotifier.setAdvert(Advert.empty()); + QR.to( + AdvertRouter.root + + AdvertRouter.admin + + AdvertRouter.addEditAdvert, + ); }, - children: [ - const AnnouncerBar( - useUserAnnouncers: true, - multipleSelect: true, - ), - GestureDetector( - onTap: () { - advertNotifier.setAdvert(Advert.empty()); - QR.to( - AdvertRouter.root + - AdvertRouter.admin + - AdvertRouter.addEditAdvert, - ); - }, - child: CardLayout( - margin: const EdgeInsets.only( - bottom: 10, - top: 20, - left: 30, - right: 30, - ), - width: 300, - height: 100, - colors: [Colors.white, Colors.grey.shade100], - shadowColor: Colors.grey.withValues(alpha: 0.2), - child: Center( - child: HeroIcon( - HeroIcons.plus, - size: 40, - color: Colors.grey.shade500, - ), - ), - ), - ), - ...filteredSortedUserAnnouncerAdverts.map( - (advert) => AdminAdvertCard( - onTap: () { - advertNotifier.setAdvert(advert); - QR.to(AdvertRouter.root + AdvertRouter.detail); + ), + ), + const SizedBox(height: 20), + Expanded( + child: AsyncChild( + value: advertList, + builder: (context, advertData) => AsyncChild( + value: userAnnouncerList, + builder: (context, userAnnouncerData) { + final userAnnouncerAdvert = advertData.where( + (advert) => userAnnouncerData + .where((element) => advert.announcer.id == element.id) + .isNotEmpty, + ); + final sortedUserAnnouncerAdverts = userAnnouncerAdvert + .toList() + .sortedBy((element) => element.date) + .reversed; + final filteredSortedUserAnnouncerAdverts = + sortedUserAnnouncerAdverts + .where( + (advert) => + selectedAnnouncers + .where((e) => advert.announcer.id == e.id) + .isNotEmpty || + selectedAnnouncers.isEmpty, + ) + .toList(); + return Refresher( + onRefresh: () async { + await advertListNotifier.loadAdverts(); + await userAnnouncerListNotifier.loadMyAnnouncerList(); + advertPostersNotifier.resetTData(); }, - onEdit: () { - QR.to( - AdvertRouter.root + - AdvertRouter.admin + - AdvertRouter.addEditAdvert, - ); - advertNotifier.setAdvert(advert); - selectedAnnouncersNotifier.clearAnnouncer(); - selectedAnnouncersNotifier.addAnnouncer(advert.announcer); - }, - onDelete: () async { - await showDialog( - context: context, - builder: (context) { - return CustomDialogBox( - title: AppLocalizations.of(context)!.advertDeleting, - descriptions: AppLocalizations.of( - context, - )!.advertDeleteAdvert, - onYes: () { - advertListNotifier.deleteAdvert(advert); - advertPostersNotifier.deleteE(advert.id, 0); + child: Column( + children: [ + ...filteredSortedUserAnnouncerAdverts.map( + (advert) => AdminAdvertCard( + onTap: () { + advertNotifier.setAdvert(advert); + QR.to(AdvertRouter.root + AdvertRouter.detail); }, - ); - }, - ); - }, - advert: advert, - ), - ), - ], - ); - }, - ), + onEdit: () { + QR.to( + AdvertRouter.root + + AdvertRouter.admin + + AdvertRouter.addEditAdvert, + ); + advertNotifier.setAdvert(advert); + selectedAnnouncersNotifier.clearAnnouncer(); + selectedAnnouncersNotifier.addAnnouncer( + advert.announcer, + ); + }, + onDelete: () async { + await showDialog( + context: context, + builder: (context) { + return CustomDialogBox( + title: AppLocalizations.of( + context, + )!.advertDeleting, + descriptions: AppLocalizations.of( + context, + )!.advertDeleteAdvert, + onYes: () { + advertListNotifier.deleteAdvert(advert); + advertPostersNotifier.deleteE( + advert.id, + 0, + ); + }, + ); + }, + ); + }, + advert: advert, + ), + ), + SizedBox(height: 80), + ], + ), + ); + }, + ), + ), + ), + ], ), ); } diff --git a/lib/advert/ui/components/advert_card.dart b/lib/advert/ui/pages/main_page/advert_card.dart similarity index 100% rename from lib/advert/ui/components/advert_card.dart rename to lib/advert/ui/pages/main_page/advert_card.dart diff --git a/lib/advert/ui/pages/main_page/main_page.dart b/lib/advert/ui/pages/main_page/main_page.dart index 00c2241222..13d25da697 100644 --- a/lib/advert/ui/pages/main_page/main_page.dart +++ b/lib/advert/ui/pages/main_page/main_page.dart @@ -10,7 +10,7 @@ import 'package:titan/advert/providers/is_advert_admin_provider.dart'; import 'package:titan/advert/ui/pages/advert.dart'; import 'package:titan/advert/router.dart'; import 'package:titan/advert/ui/components/announcer_bar.dart'; -import 'package:titan/advert/ui/components/advert_card.dart'; +import 'package:titan/advert/ui/pages/main_page/advert_card.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; diff --git a/lib/tools/ui/styleguide/icon_button.dart b/lib/tools/ui/styleguide/icon_button.dart index 616fd13660..f40e7129e1 100644 --- a/lib/tools/ui/styleguide/icon_button.dart +++ b/lib/tools/ui/styleguide/icon_button.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:titan/tools/constants.dart'; +import 'package:titan/tools/ui/builders/waiting_button.dart'; enum CustomIconButtonType { main, danger, secondary } @@ -71,17 +72,18 @@ class CustomIconButton extends StatelessWidget { @override Widget build(BuildContext context) { - return GestureDetector( - onTap: disabled == true ? null : onPressed, - child: Container( + return WaitingButton( + onTap: disabled == true ? null : () async => onPressed(), + builder: (child) => Container( padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 3), decoration: BoxDecoration( color: backgroundColor, borderRadius: BorderRadius.circular(10), border: Border.all(color: borderColor, width: 2), ), - child: Center(child: icon), + child: child, ), + child: Center(child: icon), ); } } From f2a10b382bea9cb0be7eb0c69be086f6ecdc567e Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 10 Aug 2025 18:20:38 +0200 Subject: [PATCH 243/473] fix: icon button size --- lib/tools/ui/styleguide/icon_button.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/tools/ui/styleguide/icon_button.dart b/lib/tools/ui/styleguide/icon_button.dart index f40e7129e1..de3d693af9 100644 --- a/lib/tools/ui/styleguide/icon_button.dart +++ b/lib/tools/ui/styleguide/icon_button.dart @@ -75,7 +75,9 @@ class CustomIconButton extends StatelessWidget { return WaitingButton( onTap: disabled == true ? null : () async => onPressed(), builder: (child) => Container( - padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 3), + height: 32, + width: 32, + padding: const EdgeInsets.all(5), decoration: BoxDecoration( color: backgroundColor, borderRadius: BorderRadius.circular(10), From 3c2a2bf1502fbf970a968108a43ec72ddac02a57 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 10 Aug 2025 18:20:39 +0200 Subject: [PATCH 244/473] fix: add edit advert page ui --- .../pages/form_page/add_edit_advert_page.dart | 88 +++++++++++-------- 1 file changed, 51 insertions(+), 37 deletions(-) diff --git a/lib/advert/ui/pages/form_page/add_edit_advert_page.dart b/lib/advert/ui/pages/form_page/add_edit_advert_page.dart index 7253f4ff60..e3ce19d2c0 100644 --- a/lib/advert/ui/pages/form_page/add_edit_advert_page.dart +++ b/lib/advert/ui/pages/form_page/add_edit_advert_page.dart @@ -19,8 +19,8 @@ import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; import 'package:titan/tools/ui/layouts/add_edit_button_layout.dart'; +import 'package:titan/tools/ui/styleguide/text_entry.dart'; import 'package:titan/tools/ui/widgets/image_picker_on_tap.dart'; -import 'package:titan/tools/ui/widgets/text_entry.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/l10n/app_localizations.dart'; @@ -63,10 +63,42 @@ class AdvertAddEditAdvertPage extends HookConsumerWidget { key: key, child: Column( children: [ + FormField>( + validator: (e) { + if (selectedAnnouncers.isEmpty) { + return AppLocalizations.of( + context, + )!.advertChoosingAnnouncer; + } + return null; + }, + builder: (formFieldState) => Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: const BorderRadius.all(Radius.circular(20)), + boxShadow: formFieldState.hasError + ? [ + const BoxShadow( + color: Colors.red, + spreadRadius: 3, + blurRadius: 3, + offset: Offset(2, 2), + ), + ] + : [], + ), + child: AnnouncerBar( + useUserAnnouncers: true, + multipleSelect: false, + isNotClickable: isEdit, + ), + ), + ), Padding( padding: const EdgeInsets.symmetric(horizontal: 30), child: Column( children: [ + const SizedBox(height: 20), TextEntry( maxLines: 1, label: AppLocalizations.of(context)!.advertTitle, @@ -94,6 +126,9 @@ class AdvertAddEditAdvertPage extends HookConsumerWidget { displayAdvertToastWithContext, child: Container( decoration: BoxDecoration( + borderRadius: const BorderRadius.all( + Radius.circular(5), + ), color: Colors.white, boxShadow: [ BoxShadow( @@ -150,10 +185,19 @@ class AdvertAddEditAdvertPage extends HookConsumerWidget { ), ], ) - : const HeroIcon( - HeroIcons.photo, - size: 160, - color: Colors.grey, + : Container( + width: 285, + height: 160, + decoration: BoxDecoration( + borderRadius: const BorderRadius.all( + Radius.circular(5), + ), + ), + child: const HeroIcon( + HeroIcons.photo, + size: 160, + color: Colors.grey, + ), ), ), ), @@ -161,6 +205,7 @@ class AdvertAddEditAdvertPage extends HookConsumerWidget { ), ), ), + const SizedBox(height: 20), TextEntry( minLines: 5, maxLines: 50, @@ -172,37 +217,6 @@ class AdvertAddEditAdvertPage extends HookConsumerWidget { ), ), const SizedBox(height: 50), - FormField>( - validator: (e) { - if (selectedAnnouncers.isEmpty) { - return AppLocalizations.of( - context, - )!.advertChoosingAnnouncer; - } - return null; - }, - builder: (formFieldState) => Container( - decoration: BoxDecoration( - color: Colors.white, - borderRadius: const BorderRadius.all(Radius.circular(20)), - boxShadow: formFieldState.hasError - ? [ - const BoxShadow( - color: Colors.red, - spreadRadius: 3, - blurRadius: 3, - offset: Offset(2, 2), - ), - ] - : [], - ), - child: AnnouncerBar( - useUserAnnouncers: true, - multipleSelect: false, - isNotClickable: isEdit, - ), - ), - ), Padding( padding: const EdgeInsets.symmetric(horizontal: 30), child: Column( @@ -304,7 +318,7 @@ class AdvertAddEditAdvertPage extends HookConsumerWidget { ], ), ), - const SizedBox(height: 20), + const SizedBox(height: 100), ], ), ), From 3f18bdcee16ec90511d6f328256d0e69b1241619 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 10 Aug 2025 18:20:39 +0200 Subject: [PATCH 245/473] fix: max length --- lib/advert/ui/pages/main_page/advert_card.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/advert/ui/pages/main_page/advert_card.dart b/lib/advert/ui/pages/main_page/advert_card.dart index 2fdc8e5557..592c0cb86c 100644 --- a/lib/advert/ui/pages/main_page/advert_card.dart +++ b/lib/advert/ui/pages/main_page/advert_card.dart @@ -151,7 +151,9 @@ class AdvertCard extends HookConsumerWidget { final fullText = title.isNotEmpty ? '$title $content' : content; - final isLong = fullText.length > 40; + const maxLength = 100; + + final isLong = fullText.length > maxLength; return RichText( text: TextSpan( @@ -166,7 +168,7 @@ class AdvertCard extends HookConsumerWidget { ], TextSpan( text: isLong && !isExpanded.value - ? '${content.substring(0, 40)}...' + ? '${content.substring(0, maxLength)}...' : content, ), if (isLong) ...[ From 4597eab0dc7f15cf48fa80e193a43f7f6d40c67d Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 10 Aug 2025 18:20:39 +0200 Subject: [PATCH 246/473] feat: removing detail page --- lib/advert/router.dart | 8 - .../pages/admin_page/admin_advert_card.dart | 3 +- .../ui/pages/admin_page/admin_page.dart | 4 - lib/advert/ui/pages/detail_page/detail.dart | 176 ---------------- .../ui/pages/main_page/advert_card.dart | 188 +++++++++--------- lib/advert/ui/pages/main_page/main_page.dart | 6 - 6 files changed, 90 insertions(+), 295 deletions(-) delete mode 100644 lib/advert/ui/pages/detail_page/detail.dart diff --git a/lib/advert/router.dart b/lib/advert/router.dart index a86101a78f..c0d27ab15b 100644 --- a/lib/advert/router.dart +++ b/lib/advert/router.dart @@ -4,8 +4,6 @@ import 'package:titan/booking/providers/is_admin_provider.dart'; import 'package:titan/advert/providers/is_advert_admin_provider.dart'; import 'package:titan/advert/ui/pages/admin_page/admin_page.dart' deferred as admin_page; -import 'package:titan/advert/ui/pages/detail_page/detail.dart' - deferred as detail_page; import 'package:titan/advert/ui/pages/form_page/add_edit_advert_page.dart' deferred as add_edit_advert_page; import 'package:titan/advert/ui/pages/form_page/add_rem_announcer_page.dart' @@ -25,7 +23,6 @@ class AdvertRouter { static const String admin = '/admin'; static const String addEditAdvert = '/add_edit_advert'; static const String addRemAnnouncer = '/add_remove_announcer'; - static const String detail = '/detail'; static final Module module = Module( getName: (context) => AppLocalizations.of(context)!.moduleAdvert, getDescription: (context) => @@ -64,11 +61,6 @@ class AdvertRouter { ), ], ), - QRoute( - path: detail, - builder: () => detail_page.AdvertDetailPage(), - middleware: [DeferredLoadingMiddleware(detail_page.loadLibrary)], - ), QRoute( path: addRemAnnouncer, builder: () => add_rem_announcer_page.AddRemAnnouncerPage(), diff --git a/lib/advert/ui/pages/admin_page/admin_advert_card.dart b/lib/advert/ui/pages/admin_page/admin_advert_card.dart index 1e6af89a27..72ac479d76 100644 --- a/lib/advert/ui/pages/admin_page/admin_advert_card.dart +++ b/lib/advert/ui/pages/admin_page/admin_advert_card.dart @@ -7,14 +7,13 @@ import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/styleguide/icon_button.dart'; class AdminAdvertCard extends HookConsumerWidget { - final VoidCallback onTap, onEdit; + final VoidCallback onEdit; final Future Function() onDelete; final Advert advert; const AdminAdvertCard({ super.key, required this.advert, - required this.onTap, required this.onEdit, required this.onDelete, }); diff --git a/lib/advert/ui/pages/admin_page/admin_page.dart b/lib/advert/ui/pages/admin_page/admin_page.dart index 332829a045..065e8376c9 100644 --- a/lib/advert/ui/pages/admin_page/admin_page.dart +++ b/lib/advert/ui/pages/admin_page/admin_page.dart @@ -89,10 +89,6 @@ class AdvertAdminPage extends HookConsumerWidget { children: [ ...filteredSortedUserAnnouncerAdverts.map( (advert) => AdminAdvertCard( - onTap: () { - advertNotifier.setAdvert(advert); - QR.to(AdvertRouter.root + AdvertRouter.detail); - }, onEdit: () { QR.to( AdvertRouter.root + diff --git a/lib/advert/ui/pages/detail_page/detail.dart b/lib/advert/ui/pages/detail_page/detail.dart deleted file mode 100644 index 168b6b67c2..0000000000 --- a/lib/advert/ui/pages/detail_page/detail.dart +++ /dev/null @@ -1,176 +0,0 @@ -import 'package:auto_size_text/auto_size_text.dart'; -import 'package:flutter/material.dart'; -import 'package:heroicons/heroicons.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:intl/intl.dart'; -import 'package:titan/advert/providers/advert_poster_provider.dart'; -import 'package:titan/advert/providers/advert_posters_provider.dart'; -import 'package:titan/advert/providers/advert_provider.dart'; -import 'package:titan/cinema/tools/functions.dart'; -import 'package:titan/tools/ui/builders/auto_loader_child.dart'; -import 'package:titan/tools/ui/widgets/text_with_hyper_link.dart'; -import 'package:qlevar_router/qlevar_router.dart'; - -class AdvertDetailPage extends HookConsumerWidget { - const AdvertDetailPage({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final advert = ref.watch(advertProvider); - final posters = ref.watch( - advertPostersProvider.select((advertPosters) => advertPosters[advert.id]), - ); - final advertPostersNotifier = ref.watch(advertPostersProvider.notifier); - final logoNotifier = ref.watch(advertPosterProvider.notifier); - - return Stack( - children: [ - Container( - width: double.infinity, - decoration: const BoxDecoration( - boxShadow: [ - BoxShadow( - color: Colors.black26, - blurRadius: 10, - spreadRadius: 7, - offset: Offset(0, 5), - ), - ], - ), - child: AutoLoaderChild( - group: posters, - notifier: advertPostersNotifier, - mapKey: advert, - loader: (ref) => logoNotifier.getAdvertPoster(advert.id), - dataBuilder: (context, value) => - Image(image: value.first.image, fit: BoxFit.fill), - ), - ), - SingleChildScrollView( - physics: const BouncingScrollPhysics(), - child: Column( - children: [ - const SizedBox(height: 220), - Container( - width: double.infinity, - height: 50, - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - const Color.fromARGB(0, 255, 255, 255), - Colors.grey.shade50.withValues(alpha: 0.85), - Colors.grey.shade50, - ], - stops: const [0.0, 0.65, 1.0], - ), - ), - ), - Container( - color: Colors.grey.shade50, - child: Column( - children: [ - const SizedBox(height: 15), - Container( - padding: const EdgeInsets.symmetric(horizontal: 30.0), - alignment: Alignment.center, - child: AutoSizeText( - advert.title, - maxLines: 2, - textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 30, - fontWeight: FontWeight.bold, - ), - ), - ), - const SizedBox(height: 15), - Container( - padding: const EdgeInsets.symmetric(horizontal: 30.0), - alignment: Alignment.center, - child: Text( - formatDate(advert.date), - style: const TextStyle(fontSize: 18), - ), - ), - const SizedBox(height: 20), - Container( - padding: const EdgeInsets.symmetric(horizontal: 30.0), - child: TextWithHyperLink( - advert.content, - textAlign: TextAlign.left, - style: const TextStyle(fontSize: 15), - ), - ), - const SizedBox(height: 140), - ], - ), - ), - ], - ), - ), - Column( - children: [ - const SizedBox(height: 45), - Row( - children: [ - const SizedBox(width: 20), - GestureDetector( - onTap: QR.back, - child: Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: Colors.white.withValues(alpha: 0.9), - borderRadius: BorderRadius.circular(18), - boxShadow: [ - BoxShadow( - color: Colors.white.withValues(alpha: 0.3), - blurRadius: 7, - spreadRadius: 2, - offset: const Offset(2, 3), - ), - ], - ), - child: const Icon(Icons.arrow_back, color: Colors.black), - ), - ), - const Spacer(), - Container( - padding: const EdgeInsets.symmetric( - vertical: 8, - horizontal: 12, - ), - decoration: BoxDecoration( - color: Colors.white.withValues(alpha: 0.9), - borderRadius: BorderRadius.circular(18), - boxShadow: [ - BoxShadow( - color: Colors.white.withValues(alpha: 0.3), - blurRadius: 7, - spreadRadius: 2, - offset: const Offset(2, 3), - ), - ], - ), - child: Row( - children: [ - const HeroIcon(HeroIcons.calendar, size: 20), - const SizedBox(width: 7), - Text( - DateFormat('dd/MM/yyyy - HH:mm').format(advert.date), - style: const TextStyle(fontSize: 16), - textAlign: TextAlign.center, - ), - ], - ), - ), - const SizedBox(width: 20), - ], - ), - ], - ), - ], - ); - } -} diff --git a/lib/advert/ui/pages/main_page/advert_card.dart b/lib/advert/ui/pages/main_page/advert_card.dart index 592c0cb86c..a020da0f2d 100644 --- a/lib/advert/ui/pages/main_page/advert_card.dart +++ b/lib/advert/ui/pages/main_page/advert_card.dart @@ -5,16 +5,14 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/advert/class/advert.dart'; import 'package:titan/advert/providers/advert_poster_provider.dart'; import 'package:titan/advert/providers/advert_posters_provider.dart'; -import 'package:titan/navigation/providers/is_web_format_provider.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/auto_loader_child.dart'; import 'package:timeago/timeago.dart' as timeago; class AdvertCard extends HookConsumerWidget { - final VoidCallback onTap; final Advert advert; - const AdvertCard({super.key, required this.onTap, required this.advert}); + const AdvertCard({super.key, required this.advert}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -24,118 +22,110 @@ class AdvertCard extends HookConsumerWidget { ); final advertPostersNotifier = ref.watch(advertPostersProvider.notifier); final posterNotifier = ref.watch(advertPosterProvider.notifier); - final isWebFormat = ref.watch(isWebFormatProvider); - return GestureDetector( - onTap: () { - if (!isWebFormat) { - onTap(); - } - }, - child: Container( - margin: const EdgeInsets.all(10), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 10.0), - child: Row( - children: [ - Container( - width: 40, - height: 40, - decoration: const BoxDecoration( - shape: BoxShape.circle, - color: Colors.black, + return Container( + margin: const EdgeInsets.all(10), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 10.0), + child: Row( + children: [ + Container( + width: 40, + height: 40, + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: Colors.black, + ), + child: Center( + child: Text( + advert.announcer.name.isNotEmpty + ? advert.announcer.name + .split(' ') + .take(2) + .map((s) => s[0].toUpperCase()) + .join() + : '?', + style: const TextStyle( + color: Colors.white, + fontSize: 18, + fontWeight: FontWeight.bold, + ), ), - child: Center( - child: Text( - advert.announcer.name.isNotEmpty - ? advert.announcer.name - .split(' ') - .take(2) - .map((s) => s[0].toUpperCase()) - .join() - : '?', + ), + ), + const SizedBox(width: 12), + + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + advert.announcer.name, style: const TextStyle( - color: Colors.white, - fontSize: 18, + fontSize: 16, fontWeight: FontWeight.bold, + color: Colors.black, ), ), - ), - ), - const SizedBox(width: 12), - - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - advert.announcer.name, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.black, - ), + Text( + _capitalizeFirst( + timeago.format(advert.date, locale: 'fr_short'), ), - Text( - _capitalizeFirst( - timeago.format(advert.date, locale: 'fr_short'), - ), - style: const TextStyle( - fontSize: 12, - color: Colors.grey, - ), + style: const TextStyle( + fontSize: 12, + color: Colors.grey, ), - ], - ), + ), + ], ), - ], - ), + ), + ], ), - const SizedBox(height: 15), - - AutoLoaderChild( - group: posters, - notifier: advertPostersNotifier, - mapKey: advert.id, - loader: (advertId) => posterNotifier.getAdvertPoster(advertId), - loadingBuilder: (context) => AspectRatio( - aspectRatio: 1, - child: Container( - decoration: BoxDecoration( - color: ColorConstants.onBackground, - borderRadius: BorderRadius.circular(20), - ), - child: const Center( - child: HeroIcon(HeroIcons.photo, size: 50), - ), + ), + const SizedBox(height: 15), + + AutoLoaderChild( + group: posters, + notifier: advertPostersNotifier, + mapKey: advert.id, + loader: (advertId) => posterNotifier.getAdvertPoster(advertId), + loadingBuilder: (context) => AspectRatio( + aspectRatio: 1, + child: Container( + decoration: BoxDecoration( + color: ColorConstants.onBackground, + borderRadius: BorderRadius.circular(20), + ), + child: const Center( + child: HeroIcon(HeroIcons.photo, size: 50), ), ), - dataBuilder: (context, value) => AspectRatio( - aspectRatio: 1, - child: Container( - decoration: BoxDecoration( - color: ColorConstants.onBackground, - borderRadius: BorderRadius.circular(20), - image: DecorationImage( - image: value.first.image, - fit: BoxFit.cover, - ), + ), + dataBuilder: (context, value) => AspectRatio( + aspectRatio: 1, + child: Container( + decoration: BoxDecoration( + color: ColorConstants.onBackground, + borderRadius: BorderRadius.circular(20), + image: DecorationImage( + image: value.first.image, + fit: BoxFit.cover, ), ), ), ), - - Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [_buildExpandableText(isExpanded)], - ), + ), + + Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [_buildExpandableText(isExpanded)], ), - ], - ), + ), + ], ), ); } diff --git a/lib/advert/ui/pages/main_page/main_page.dart b/lib/advert/ui/pages/main_page/main_page.dart index 13d25da697..534b4bd8bd 100644 --- a/lib/advert/ui/pages/main_page/main_page.dart +++ b/lib/advert/ui/pages/main_page/main_page.dart @@ -4,7 +4,6 @@ import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/advert/providers/advert_list_provider.dart'; import 'package:titan/advert/providers/advert_posters_provider.dart'; -import 'package:titan/advert/providers/advert_provider.dart'; import 'package:titan/advert/providers/announcer_provider.dart'; import 'package:titan/advert/providers/is_advert_admin_provider.dart'; import 'package:titan/advert/ui/pages/advert.dart'; @@ -22,7 +21,6 @@ class AdvertMainPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final advertNotifier = ref.watch(advertProvider.notifier); final advertList = ref.watch(advertListProvider); final advertListNotifier = ref.watch(advertListProvider.notifier); final advertPostersNotifier = ref.watch(advertPostersProvider.notifier); @@ -86,10 +84,6 @@ class AdvertMainPage extends HookConsumerWidget { children: [ ...filteredSortedAdvertData.map( (advert) => AdvertCard( - onTap: () { - advertNotifier.setAdvert(advert); - QR.to(AdvertRouter.root + AdvertRouter.detail); - }, advert: advert, ), ), From 08fcb9b2ef1700b62143fa9ecdf05dde45498c70 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 10 Aug 2025 18:20:40 +0200 Subject: [PATCH 247/473] feat: removing add remove announcer --- lib/advert/router.dart | 12 -- .../form_page/add_rem_announcer_page.dart | 195 ------------------ 2 files changed, 207 deletions(-) delete mode 100644 lib/advert/ui/pages/form_page/add_rem_announcer_page.dart diff --git a/lib/advert/router.dart b/lib/advert/router.dart index c0d27ab15b..673582c391 100644 --- a/lib/advert/router.dart +++ b/lib/advert/router.dart @@ -1,13 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/booking/providers/is_admin_provider.dart'; import 'package:titan/advert/providers/is_advert_admin_provider.dart'; import 'package:titan/advert/ui/pages/admin_page/admin_page.dart' deferred as admin_page; import 'package:titan/advert/ui/pages/form_page/add_edit_advert_page.dart' deferred as add_edit_advert_page; -import 'package:titan/advert/ui/pages/form_page/add_rem_announcer_page.dart' - deferred as add_rem_announcer_page; import 'package:titan/advert/ui/pages/main_page/main_page.dart' deferred as main_page; import 'package:titan/l10n/app_localizations.dart'; @@ -22,7 +19,6 @@ class AdvertRouter { static const String root = '/advert'; static const String admin = '/admin'; static const String addEditAdvert = '/add_edit_advert'; - static const String addRemAnnouncer = '/add_remove_announcer'; static final Module module = Module( getName: (context) => AppLocalizations.of(context)!.moduleAdvert, getDescription: (context) => @@ -61,14 +57,6 @@ class AdvertRouter { ), ], ), - QRoute( - path: addRemAnnouncer, - builder: () => add_rem_announcer_page.AddRemAnnouncerPage(), - middleware: [ - AdminMiddleware(ref, isAdminProvider), - DeferredLoadingMiddleware(add_rem_announcer_page.loadLibrary), - ], - ), ], ); } diff --git a/lib/advert/ui/pages/form_page/add_rem_announcer_page.dart b/lib/advert/ui/pages/form_page/add_rem_announcer_page.dart deleted file mode 100644 index b79365ca47..0000000000 --- a/lib/advert/ui/pages/form_page/add_rem_announcer_page.dart +++ /dev/null @@ -1,195 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:heroicons/heroicons.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/providers/group_list_provider.dart'; -import 'package:titan/advert/class/announcer.dart'; -import 'package:titan/advert/providers/all_announcer_list_provider.dart'; -import 'package:titan/advert/providers/announcer_list_provider.dart'; -import 'package:titan/advert/ui/pages/advert.dart'; -import 'package:titan/advert/ui/pages/form_page/announcer_card.dart'; -import 'package:titan/tools/constants.dart'; -import 'package:titan/tools/functions.dart'; -import 'package:titan/tools/token_expire_wrapper.dart'; -import 'package:titan/tools/ui/builders/async_child.dart'; -import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; -import 'package:titan/l10n/app_localizations.dart'; - -class AddRemAnnouncerPage extends HookConsumerWidget { - const AddRemAnnouncerPage({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final announcerListNotifier = ref.watch(announcerListProvider.notifier); - final announcers = ref.watch(allAnnouncerList); - final groups = ref.watch(allGroupListProvider); - final announcerIds = announcers.map((x) => x.groupManagerId).toList(); - - void displayToastWithContext(TypeMsg type, String msg) { - displayToast(context, type, msg); - } - - return AdvertTemplate( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 30.0), - child: SingleChildScrollView( - physics: const AlwaysScrollableScrollPhysics( - parent: BouncingScrollPhysics(), - ), - child: Column( - children: [ - SizedBox( - child: Column( - children: [ - Align( - alignment: Alignment.centerLeft, - child: Text( - AppLocalizations.of( - context, - )!.advertModifyAnnouncingGroup, - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.w700, - color: ColorConstants.gradient1, - ), - ), - ), - const SizedBox(height: 30), - AsyncChild( - value: groups, - builder: (context, groupList) { - final canAdd = groupList - .where((x) => !announcerIds.contains(x.id)) - .toList(); - final canRemove = groupList - .where((x) => announcerIds.contains(x.id)) - .toList(); - return (canAdd + canRemove).isNotEmpty - ? Column( - children: - canAdd - .map( - (e) => GestureDetector( - onTap: () { - Announcer newAnnouncer = - Announcer( - groupManagerId: e.id, - id: '', - name: e.name, - ); - tokenExpireWrapper(ref, () async { - final addedMessage = - AppLocalizations.of( - context, - )!.advertAddedAnnouncer; - final errorMessage = - AppLocalizations.of( - context, - )!.advertAddingError; - final value = - await announcerListNotifier - .addAnnouncer( - newAnnouncer, - ); - if (value) { - displayToastWithContext( - TypeMsg.msg, - addedMessage, - ); - } else { - displayToastWithContext( - TypeMsg.error, - errorMessage, - ); - } - announcerListNotifier - .loadAllAnnouncerList(); - }); - }, - child: AnnouncerCard( - e: e, - icon: HeroIcons.plus, - ), - ), - ) - .toList() + - canRemove - .map( - (e) => GestureDetector( - onTap: () async { - await showDialog( - context: context, - builder: (context) { - return CustomDialogBox( - title: AppLocalizations.of( - context, - )!.advertDeleting, - descriptions: - AppLocalizations.of( - context, - )!.advertDeleteAnnouncer, - onYes: () { - final removedAnnouncerMsg = - AppLocalizations.of( - context, - )!.advertRemovedAnnouncer; - final removingErrorMsg = - AppLocalizations.of( - context, - )!.advertRemovingError; - tokenExpireWrapper(ref, () async { - final value = await announcerListNotifier - .deleteAnnouncer( - announcers - .where( - (element) => - e.id == - e.id, - ) - .toList()[0], - ); - if (value) { - displayToastWithContext( - TypeMsg.msg, - removedAnnouncerMsg, - ); - } else { - displayToastWithContext( - TypeMsg.error, - removingErrorMsg, - ); - } - announcerListNotifier - .loadAllAnnouncerList(); - }); - }, - ); - }, - ); - }, - child: AnnouncerCard( - e: e, - icon: HeroIcons.minus, - ), - ), - ) - .toList(), - ) - : Center( - child: Text( - AppLocalizations.of( - context, - )!.advertNoMoreAnnouncer, - ), - ); - }, - ), - ], - ), - ), - ], - ), - ), - ), - ); - } -} From 6f1abae174357cfc2c4e6271297d3307ba8c8283 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 10 Aug 2025 18:20:53 +0200 Subject: [PATCH 248/473] fix: scroll to hide navbar on refresher --- .../association_membership_detail_page.dart | 1 + .../pages/structure_page/structure_page.dart | 1 + .../ui/pages/admin_page/admin_page.dart | 1 + lib/advert/ui/pages/main_page/main_page.dart | 8 +-- lib/amap/ui/pages/admin_page/admin_page.dart | 1 + .../detail_delivery_page/detail_page.dart | 1 + lib/amap/ui/pages/main_page/main_page.dart | 1 + .../ui/pages/admin_pages/admin_page.dart | 1 + lib/booking/ui/pages/main_page/main_page.dart | 1 + .../ui/pages/manager_page/manager_page.dart | 1 + lib/cinema/ui/pages/main_page/main_page.dart | 1 + lib/event/ui/pages/admin_page/admin_page.dart | 1 + lib/event/ui/pages/main_page/main_page.dart | 1 + lib/home/ui/home.dart | 1 + lib/loan/ui/pages/admin_page/admin_page.dart | 1 + lib/loan/ui/pages/main_page/main_page.dart | 1 + .../ui/pages/admin_page/admin_page.dart | 1 + .../ui/pages/devices_page/devices_page.dart | 1 + .../ui/pages/main_page/main_page.dart | 1 + .../ui/pages/stats_page/stats_page.dart | 1 + .../store_admin_page/store_admin_page.dart | 1 + .../store_stats_page/store_stats_page.dart | 1 + .../ui/pages/admin_page/admin_page.dart | 1 + .../association_page/association_page.dart | 1 + .../ui/pages/main_page/main_page.dart | 1 + .../ui/pages/history_page/history_page.dart | 1 + .../ui/pages/main_page/main_page.dart | 1 + .../ui/pages/purchase_page/purchase_page.dart | 1 + .../ui/pages/scan_page/scan_page.dart | 1 + .../ui/pages/ticket_page/ticket_page.dart | 1 + .../pages/user_list_page/user_list_page.dart | 1 + .../admin_module_page/admin_module_page.dart | 1 + .../creation_edit_page.dart | 1 + lib/raffle/ui/pages/main_page/main_page.dart | 1 + .../ui/pages/raffle_page/raffle_page.dart | 1 + lib/recommendation/ui/pages/main_page.dart | 1 + .../ui/pages/plants_page/plants_page.dart | 1 + .../ui/pages/species_page/species_page.dart | 1 + .../ui/pages/stock_page/stocks_page.dart | 1 + .../ui/pages/main_page/main_page.dart | 1 + .../schools/school_page/school_page.dart | 1 + lib/tools/ui/layouts/column_refresher.dart | 62 ++++++++++------- lib/tools/ui/layouts/refresher.dart | 68 +++++++++++-------- lib/vote/ui/pages/admin_page/admin_page.dart | 1 + lib/vote/ui/pages/main_page/main_page.dart | 1 + 45 files changed, 122 insertions(+), 58 deletions(-) diff --git a/lib/admin/ui/pages/membership/association_membership_detail_page/association_membership_detail_page.dart b/lib/admin/ui/pages/membership/association_membership_detail_page/association_membership_detail_page.dart index 24a05d8857..4197ea29b1 100644 --- a/lib/admin/ui/pages/membership/association_membership_detail_page/association_membership_detail_page.dart +++ b/lib/admin/ui/pages/membership/association_membership_detail_page/association_membership_detail_page.dart @@ -37,6 +37,7 @@ class AssociationMembershipEditorPage extends HookConsumerWidget { return AdminTemplate( child: Refresher( + controller: ScrollController(), onRefresh: () async { await associationMembershipMemberListNotifier .loadAssociationMembershipMembers(associationMembership.id); diff --git a/lib/admin/ui/pages/structure_page/structure_page.dart b/lib/admin/ui/pages/structure_page/structure_page.dart index 9ebca03975..acd4273bc8 100644 --- a/lib/admin/ui/pages/structure_page/structure_page.dart +++ b/lib/admin/ui/pages/structure_page/structure_page.dart @@ -42,6 +42,7 @@ class StructurePage extends HookConsumerWidget { return AdminTemplate( child: Refresher( + controller: ScrollController(), onRefresh: () async { await structuresNotifier.getStructures(); }, diff --git a/lib/advert/ui/pages/admin_page/admin_page.dart b/lib/advert/ui/pages/admin_page/admin_page.dart index 065e8376c9..adf40b1d15 100644 --- a/lib/advert/ui/pages/admin_page/admin_page.dart +++ b/lib/advert/ui/pages/admin_page/admin_page.dart @@ -80,6 +80,7 @@ class AdvertAdminPage extends HookConsumerWidget { ) .toList(); return Refresher( + controller: ScrollController(), onRefresh: () async { await advertListNotifier.loadAdverts(); await userAnnouncerListNotifier.loadMyAnnouncerList(); diff --git a/lib/advert/ui/pages/main_page/main_page.dart b/lib/advert/ui/pages/main_page/main_page.dart index 534b4bd8bd..0671b2ee91 100644 --- a/lib/advert/ui/pages/main_page/main_page.dart +++ b/lib/advert/ui/pages/main_page/main_page.dart @@ -31,7 +31,7 @@ class AdvertMainPage extends HookConsumerWidget { child: Column( children: [ const AnnouncerBar(useUserAnnouncers: false, multipleSelect: true), - const SizedBox(height: 20), + const SizedBox(height: 15), Padding( padding: const EdgeInsets.symmetric(horizontal: 20), @@ -60,6 +60,7 @@ class AdvertMainPage extends HookConsumerWidget { ], ), ), + const SizedBox(height: 5), Expanded( child: AsyncChild( @@ -76,6 +77,7 @@ class AdvertMainPage extends HookConsumerWidget { selected.isEmpty, ); return Refresher( + controller: ScrollController(), onRefresh: () async { await advertListNotifier.loadAdverts(); advertPostersNotifier.resetTData(); @@ -83,9 +85,7 @@ class AdvertMainPage extends HookConsumerWidget { child: Column( children: [ ...filteredSortedAdvertData.map( - (advert) => AdvertCard( - advert: advert, - ), + (advert) => AdvertCard(advert: advert), ), SizedBox(height: 80), ], diff --git a/lib/amap/ui/pages/admin_page/admin_page.dart b/lib/amap/ui/pages/admin_page/admin_page.dart index 328c3e652d..a7910e6fef 100644 --- a/lib/amap/ui/pages/admin_page/admin_page.dart +++ b/lib/amap/ui/pages/admin_page/admin_page.dart @@ -19,6 +19,7 @@ class AdminPage extends HookConsumerWidget { final productListNotifier = ref.read(productListProvider.notifier); return AmapTemplate( child: Refresher( + controller: ScrollController(), onRefresh: () async { await cashNotifier.loadCashList(); await deliveryListNotifier.loadDeliveriesList(); diff --git a/lib/amap/ui/pages/detail_delivery_page/detail_page.dart b/lib/amap/ui/pages/detail_delivery_page/detail_page.dart index 31337385fe..f6778ad13f 100644 --- a/lib/amap/ui/pages/detail_delivery_page/detail_page.dart +++ b/lib/amap/ui/pages/detail_delivery_page/detail_page.dart @@ -37,6 +37,7 @@ class DetailDeliveryPage extends HookConsumerWidget { final cash = ref.watch(cashListProvider); return AmapTemplate( child: Refresher( + controller: ScrollController(), onRefresh: () async { await deliveryProductListNotifier.loadProductList(delivery.products); await deliveryListNotifier.loadDeliveriesList(); diff --git a/lib/amap/ui/pages/main_page/main_page.dart b/lib/amap/ui/pages/main_page/main_page.dart index 91eb61c468..3f38eb0bee 100644 --- a/lib/amap/ui/pages/main_page/main_page.dart +++ b/lib/amap/ui/pages/main_page/main_page.dart @@ -66,6 +66,7 @@ class AmapMainPage extends HookConsumerWidget { return AmapTemplate( child: Refresher( + controller: ScrollController(), onRefresh: () async { await ordersNotifier.loadOrderList(me.id); await balanceNotifier.loadCashByUser(me.id); diff --git a/lib/booking/ui/pages/admin_pages/admin_page.dart b/lib/booking/ui/pages/admin_pages/admin_page.dart index 7c196517e2..3cba6f3e47 100644 --- a/lib/booking/ui/pages/admin_pages/admin_page.dart +++ b/lib/booking/ui/pages/admin_pages/admin_page.dart @@ -37,6 +37,7 @@ class AdminPage extends HookConsumerWidget { return BookingTemplate( child: LayoutBuilder( builder: (context, constraints) => Refresher( + controller: ScrollController(), onRefresh: () async { await ref.watch(roomListProvider.notifier).loadRooms(); await ref diff --git a/lib/booking/ui/pages/main_page/main_page.dart b/lib/booking/ui/pages/main_page/main_page.dart index 3a4a9387dd..98ae22f341 100644 --- a/lib/booking/ui/pages/main_page/main_page.dart +++ b/lib/booking/ui/pages/main_page/main_page.dart @@ -61,6 +61,7 @@ class BookingMainPage extends HookConsumerWidget { return BookingTemplate( child: LayoutBuilder( builder: (context, constraints) => Refresher( + controller: ScrollController(), onRefresh: () async { await confirmedBookingsNotifier.loadConfirmedBooking(); await bookingsNotifier.loadUserBookings(); diff --git a/lib/booking/ui/pages/manager_page/manager_page.dart b/lib/booking/ui/pages/manager_page/manager_page.dart index 4f7c0546a0..b272f143ec 100644 --- a/lib/booking/ui/pages/manager_page/manager_page.dart +++ b/lib/booking/ui/pages/manager_page/manager_page.dart @@ -43,6 +43,7 @@ class ManagerPage extends HookConsumerWidget { ); return BookingTemplate( child: Refresher( + controller: ScrollController(), onRefresh: () async { await ref .watch(managerBookingListProvider.notifier) diff --git a/lib/cinema/ui/pages/main_page/main_page.dart b/lib/cinema/ui/pages/main_page/main_page.dart index f6fa825a1d..5f345e4551 100644 --- a/lib/cinema/ui/pages/main_page/main_page.dart +++ b/lib/cinema/ui/pages/main_page/main_page.dart @@ -45,6 +45,7 @@ class CinemaMainPage extends HookConsumerWidget { return CinemaTemplate( child: Refresher( + controller: ScrollController(), onRefresh: () async { await sessionListNotifier.loadSessions(); ref.watch(mainPageIndexProvider.notifier).reset(); diff --git a/lib/event/ui/pages/admin_page/admin_page.dart b/lib/event/ui/pages/admin_page/admin_page.dart index ba9eee208a..1f5682029e 100644 --- a/lib/event/ui/pages/admin_page/admin_page.dart +++ b/lib/event/ui/pages/admin_page/admin_page.dart @@ -73,6 +73,7 @@ class AdminPage extends HookConsumerWidget { }).toList(); return EventTemplate( child: Refresher( + controller: ScrollController(), onRefresh: () async { await ref.watch(eventListProvider.notifier).loadEventList(); }, diff --git a/lib/event/ui/pages/main_page/main_page.dart b/lib/event/ui/pages/main_page/main_page.dart index e99d9db759..7b6a6898ad 100644 --- a/lib/event/ui/pages/main_page/main_page.dart +++ b/lib/event/ui/pages/main_page/main_page.dart @@ -30,6 +30,7 @@ class EventMainPage extends HookConsumerWidget { builder: (context, eventList) { eventList.sort((a, b) => b.start.compareTo(a.start)); return ColumnRefresher( + controller: ScrollController(), onRefresh: () async { await eventListNotifier.loadConfirmedEvent(); }, diff --git a/lib/home/ui/home.dart b/lib/home/ui/home.dart index 7ab09de0a5..e81bebe4d7 100644 --- a/lib/home/ui/home.dart +++ b/lib/home/ui/home.dart @@ -30,6 +30,7 @@ class HomePage extends HookConsumerWidget { color: ColorConstants.background, child: SafeArea( child: Refresher( + controller: ScrollController(), onRefresh: () async { await confirmedEventListNotifier.loadConfirmedEvent(); now = DateTime.now(); diff --git a/lib/loan/ui/pages/admin_page/admin_page.dart b/lib/loan/ui/pages/admin_page/admin_page.dart index 4dd4a31a9b..d0f468a7e0 100644 --- a/lib/loan/ui/pages/admin_page/admin_page.dart +++ b/lib/loan/ui/pages/admin_page/admin_page.dart @@ -41,6 +41,7 @@ class AdminPage extends HookConsumerWidget { return LoanTemplate( child: Refresher( + controller: ScrollController(), onRefresh: () async { final itemListNotifier = ref.read(itemListProvider.notifier); final loanersItemsNotifier = ref.read(loanersItemsProvider.notifier); diff --git a/lib/loan/ui/pages/main_page/main_page.dart b/lib/loan/ui/pages/main_page/main_page.dart index 34b231bd45..739c97f865 100644 --- a/lib/loan/ui/pages/main_page/main_page.dart +++ b/lib/loan/ui/pages/main_page/main_page.dart @@ -54,6 +54,7 @@ class LoanMainPage extends HookConsumerWidget { child: Stack( children: [ Refresher( + controller: ScrollController(), onRefresh: () async { await loanListNotifier.loadLoanList(); }, diff --git a/lib/paiement/ui/pages/admin_page/admin_page.dart b/lib/paiement/ui/pages/admin_page/admin_page.dart index bdb566a73d..a020394d20 100644 --- a/lib/paiement/ui/pages/admin_page/admin_page.dart +++ b/lib/paiement/ui/pages/admin_page/admin_page.dart @@ -20,6 +20,7 @@ class AdminPage extends ConsumerWidget { final structure = ref.watch(selectedStructureProvider); return PaymentTemplate( child: Refresher( + controller: ScrollController(), onRefresh: () async { await storeListNotifier.getStores(); }, diff --git a/lib/paiement/ui/pages/devices_page/devices_page.dart b/lib/paiement/ui/pages/devices_page/devices_page.dart index a3b26c0bc2..c430d8f82f 100644 --- a/lib/paiement/ui/pages/devices_page/devices_page.dart +++ b/lib/paiement/ui/pages/devices_page/devices_page.dart @@ -53,6 +53,7 @@ class DevicesPage extends HookConsumerWidget { return PaymentTemplate( child: Refresher( + controller: ScrollController(), onRefresh: () async { await devicesNotifier.getDeviceList(); }, diff --git a/lib/paiement/ui/pages/main_page/main_page.dart b/lib/paiement/ui/pages/main_page/main_page.dart index 41b705ffc2..623c722b20 100644 --- a/lib/paiement/ui/pages/main_page/main_page.dart +++ b/lib/paiement/ui/pages/main_page/main_page.dart @@ -132,6 +132,7 @@ class PaymentMainPage extends HookConsumerWidget { : LayoutBuilder( builder: (context, constraints) { return Refresher( + controller: ScrollController(), onRefresh: () async { await mySellersNotifier.getMyStores(); await myHistoryNotifier.getHistory(); diff --git a/lib/paiement/ui/pages/stats_page/stats_page.dart b/lib/paiement/ui/pages/stats_page/stats_page.dart index f041e21efe..6bf7cb9db7 100644 --- a/lib/paiement/ui/pages/stats_page/stats_page.dart +++ b/lib/paiement/ui/pages/stats_page/stats_page.dart @@ -19,6 +19,7 @@ class StatsPage extends HookConsumerWidget { return PaymentTemplate( child: LayoutBuilder( builder: (context, constraints) => Refresher( + controller: ScrollController(), onRefresh: () async { await myHistoryNotifier.getHistory(); }, diff --git a/lib/paiement/ui/pages/store_admin_page/store_admin_page.dart b/lib/paiement/ui/pages/store_admin_page/store_admin_page.dart index e5dc41f531..67e3ea4b97 100644 --- a/lib/paiement/ui/pages/store_admin_page/store_admin_page.dart +++ b/lib/paiement/ui/pages/store_admin_page/store_admin_page.dart @@ -34,6 +34,7 @@ class StoreAdminPage extends HookConsumerWidget { return PaymentTemplate( child: Refresher( + controller: ScrollController(), onRefresh: () async { await storeSellersNotifier.getStoreSellerList(store.id); }, diff --git a/lib/paiement/ui/pages/store_stats_page/store_stats_page.dart b/lib/paiement/ui/pages/store_stats_page/store_stats_page.dart index 0edab7025e..c3323ed507 100644 --- a/lib/paiement/ui/pages/store_stats_page/store_stats_page.dart +++ b/lib/paiement/ui/pages/store_stats_page/store_stats_page.dart @@ -21,6 +21,7 @@ class StoreStatsPage extends ConsumerWidget { final selectedInterval = ref.watch(selectedIntervalProvider); return PaymentTemplate( child: Refresher( + controller: ScrollController(), onRefresh: () async { await selectedHistoryNotifier.getHistory( selectedStore.id, diff --git a/lib/phonebook/ui/pages/admin_page/admin_page.dart b/lib/phonebook/ui/pages/admin_page/admin_page.dart index a40ff696cc..e52ecaefb4 100644 --- a/lib/phonebook/ui/pages/admin_page/admin_page.dart +++ b/lib/phonebook/ui/pages/admin_page/admin_page.dart @@ -43,6 +43,7 @@ class AdminPage extends HookConsumerWidget { return PhonebookTemplate( child: Refresher( + controller: ScrollController(), onRefresh: () async { await associationListNotifier.loadAssociations(); await roleNotifier.loadRolesTags(); diff --git a/lib/phonebook/ui/pages/association_page/association_page.dart b/lib/phonebook/ui/pages/association_page/association_page.dart index 038daf1c94..a83f54a097 100644 --- a/lib/phonebook/ui/pages/association_page/association_page.dart +++ b/lib/phonebook/ui/pages/association_page/association_page.dart @@ -39,6 +39,7 @@ class AssociationPage extends HookConsumerWidget { final localizeWithContext = AppLocalizations.of(context)!; return PhonebookTemplate( child: Refresher( + controller: ScrollController(), onRefresh: () async { await associationMemberListNotifier.loadMembers( association.id, diff --git a/lib/phonebook/ui/pages/main_page/main_page.dart b/lib/phonebook/ui/pages/main_page/main_page.dart index 031724abf2..605184f50e 100644 --- a/lib/phonebook/ui/pages/main_page/main_page.dart +++ b/lib/phonebook/ui/pages/main_page/main_page.dart @@ -42,6 +42,7 @@ class PhonebookMainPage extends HookConsumerWidget { return PhonebookTemplate( child: Refresher( + controller: ScrollController(), onRefresh: () async { await associationGroupementListNotifier.loadAssociationGroupement(); await associationListNotifier.loadAssociations(); diff --git a/lib/purchases/ui/pages/history_page/history_page.dart b/lib/purchases/ui/pages/history_page/history_page.dart index 2117e90f32..a75b81d0ff 100644 --- a/lib/purchases/ui/pages/history_page/history_page.dart +++ b/lib/purchases/ui/pages/history_page/history_page.dart @@ -26,6 +26,7 @@ class HistoryPage extends HookConsumerWidget { return PurchasesTemplate( child: Refresher( + controller: ScrollController(), onRefresh: () async { await purchasesListNotifier.loadPurchases(); }, diff --git a/lib/purchases/ui/pages/main_page/main_page.dart b/lib/purchases/ui/pages/main_page/main_page.dart index 3b55560b5e..0eac73f0d2 100644 --- a/lib/purchases/ui/pages/main_page/main_page.dart +++ b/lib/purchases/ui/pages/main_page/main_page.dart @@ -27,6 +27,7 @@ class PurchasesMainPage extends HookConsumerWidget { return PurchasesTemplate( child: Refresher( + controller: ScrollController(), onRefresh: () async { await ticketListNotifier.loadTickets(); }, diff --git a/lib/purchases/ui/pages/purchase_page/purchase_page.dart b/lib/purchases/ui/pages/purchase_page/purchase_page.dart index b9f6e83cef..55cf8ced1d 100644 --- a/lib/purchases/ui/pages/purchase_page/purchase_page.dart +++ b/lib/purchases/ui/pages/purchase_page/purchase_page.dart @@ -15,6 +15,7 @@ class PurchasePage extends HookConsumerWidget { return PurchasesTemplate( child: Refresher( + controller: ScrollController(), onRefresh: () async {}, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 30), diff --git a/lib/purchases/ui/pages/scan_page/scan_page.dart b/lib/purchases/ui/pages/scan_page/scan_page.dart index bd5de73120..375af4fa0a 100644 --- a/lib/purchases/ui/pages/scan_page/scan_page.dart +++ b/lib/purchases/ui/pages/scan_page/scan_page.dart @@ -32,6 +32,7 @@ class ScanPage extends HookConsumerWidget { return PurchasesTemplate( child: Refresher( + controller: ScrollController(), onRefresh: () async { await sellersNotifier.loadSellers(); if (seller != Seller.empty()) { diff --git a/lib/purchases/ui/pages/ticket_page/ticket_page.dart b/lib/purchases/ui/pages/ticket_page/ticket_page.dart index cbb984d4f3..1e0b2d4e64 100644 --- a/lib/purchases/ui/pages/ticket_page/ticket_page.dart +++ b/lib/purchases/ui/pages/ticket_page/ticket_page.dart @@ -20,6 +20,7 @@ class TicketPage extends HookConsumerWidget { return PurchasesTemplate( child: Refresher( + controller: ScrollController(), onRefresh: () async { await ticketNotifier.loadTicketSecret(); }, diff --git a/lib/purchases/ui/pages/user_list_page/user_list_page.dart b/lib/purchases/ui/pages/user_list_page/user_list_page.dart index e8402d99ed..a105c05405 100644 --- a/lib/purchases/ui/pages/user_list_page/user_list_page.dart +++ b/lib/purchases/ui/pages/user_list_page/user_list_page.dart @@ -30,6 +30,7 @@ class UserListPage extends HookConsumerWidget { final selectedTag = useState(null); return PurchasesTemplate( child: Refresher( + controller: ScrollController(), onRefresh: () async { productId.maybeWhen( orElse: () {}, diff --git a/lib/raffle/ui/pages/admin_module_page/admin_module_page.dart b/lib/raffle/ui/pages/admin_module_page/admin_module_page.dart index ffe4e75bcb..acad16d56d 100644 --- a/lib/raffle/ui/pages/admin_module_page/admin_module_page.dart +++ b/lib/raffle/ui/pages/admin_module_page/admin_module_page.dart @@ -14,6 +14,7 @@ class AdminModulePage extends HookConsumerWidget { final tombolaLogosNotifier = ref.watch(tombolaLogosProvider.notifier); return RaffleTemplate( child: Refresher( + controller: ScrollController(), onRefresh: () async { tombolaLogosNotifier.resetTData(); }, diff --git a/lib/raffle/ui/pages/creation_edit_page/creation_edit_page.dart b/lib/raffle/ui/pages/creation_edit_page/creation_edit_page.dart index 6453de95f4..2bd4de4fd1 100644 --- a/lib/raffle/ui/pages/creation_edit_page/creation_edit_page.dart +++ b/lib/raffle/ui/pages/creation_edit_page/creation_edit_page.dart @@ -73,6 +73,7 @@ class CreationPage extends HookConsumerWidget { return RaffleTemplate( child: Refresher( + controller: ScrollController(), onRefresh: () async { await cashNotifier.loadCashList(); await packTicketListNotifier.loadPackTicketList(); diff --git a/lib/raffle/ui/pages/main_page/main_page.dart b/lib/raffle/ui/pages/main_page/main_page.dart index 1b884e584c..da1d6f768a 100644 --- a/lib/raffle/ui/pages/main_page/main_page.dart +++ b/lib/raffle/ui/pages/main_page/main_page.dart @@ -41,6 +41,7 @@ class RaffleMainPage extends HookConsumerWidget { return RaffleTemplate( child: Refresher( + controller: ScrollController(), onRefresh: () async { await userTicketListNotifier.loadTicketList(); await raffleListNotifier.loadRaffleList(); diff --git a/lib/raffle/ui/pages/raffle_page/raffle_page.dart b/lib/raffle/ui/pages/raffle_page/raffle_page.dart index 5023c0f4a8..71c7dde85b 100644 --- a/lib/raffle/ui/pages/raffle_page/raffle_page.dart +++ b/lib/raffle/ui/pages/raffle_page/raffle_page.dart @@ -31,6 +31,7 @@ class RaffleInfoPage extends HookConsumerWidget { return RaffleTemplate( child: Refresher( + controller: ScrollController(), onRefresh: () async { userId.whenData( (value) async => await balanceNotifier.loadCashByUser(value), diff --git a/lib/recommendation/ui/pages/main_page.dart b/lib/recommendation/ui/pages/main_page.dart index dd0f782c83..d62cc8942c 100644 --- a/lib/recommendation/ui/pages/main_page.dart +++ b/lib/recommendation/ui/pages/main_page.dart @@ -27,6 +27,7 @@ class RecommendationMainPage extends HookConsumerWidget { return RecommendationTemplate( child: Refresher( + controller: ScrollController(), onRefresh: () async { await recommendationListNotifier.loadRecommendation(); }, diff --git a/lib/seed-library/ui/pages/plants_page/plants_page.dart b/lib/seed-library/ui/pages/plants_page/plants_page.dart index 0000a6f950..50956bab7d 100644 --- a/lib/seed-library/ui/pages/plants_page/plants_page.dart +++ b/lib/seed-library/ui/pages/plants_page/plants_page.dart @@ -27,6 +27,7 @@ class PlantsPage extends HookConsumerWidget { return SeedLibraryTemplate( child: Refresher( + controller: ScrollController(), onRefresh: () async { await plantListNotifier.loadPlants(); }, diff --git a/lib/seed-library/ui/pages/species_page/species_page.dart b/lib/seed-library/ui/pages/species_page/species_page.dart index f8e0914694..007c362a78 100644 --- a/lib/seed-library/ui/pages/species_page/species_page.dart +++ b/lib/seed-library/ui/pages/species_page/species_page.dart @@ -39,6 +39,7 @@ class SpeciesPage extends HookConsumerWidget { return SeedLibraryTemplate( child: Refresher( + controller: ScrollController(), onRefresh: () async { await speciesListNotifier.loadSpecies(); }, diff --git a/lib/seed-library/ui/pages/stock_page/stocks_page.dart b/lib/seed-library/ui/pages/stock_page/stocks_page.dart index e291b8bd1f..a67b0eda82 100644 --- a/lib/seed-library/ui/pages/stock_page/stocks_page.dart +++ b/lib/seed-library/ui/pages/stock_page/stocks_page.dart @@ -23,6 +23,7 @@ class StockPage extends HookConsumerWidget { return SeedLibraryTemplate( child: Refresher( + controller: ScrollController(), onRefresh: () async { await plantListNotifier.loadPlants(); }, diff --git a/lib/settings/ui/pages/main_page/main_page.dart b/lib/settings/ui/pages/main_page/main_page.dart index 33bd4acab4..475be8a038 100644 --- a/lib/settings/ui/pages/main_page/main_page.dart +++ b/lib/settings/ui/pages/main_page/main_page.dart @@ -58,6 +58,7 @@ class SettingsMainPage extends HookConsumerWidget { return SettingsTemplate( child: Refresher( + controller: ScrollController(), onRefresh: () async { await notificationTopicListNotifier.loadNotificationTopicList(); await meNotifier.loadMe(); diff --git a/lib/super_admin/ui/pages/schools/school_page/school_page.dart b/lib/super_admin/ui/pages/schools/school_page/school_page.dart index df954b8823..8fe594aa0f 100644 --- a/lib/super_admin/ui/pages/schools/school_page/school_page.dart +++ b/lib/super_admin/ui/pages/schools/school_page/school_page.dart @@ -32,6 +32,7 @@ class SchoolsPage extends HookConsumerWidget { return SuperAdminTemplate( child: Refresher( + controller: ScrollController(), onRefresh: () async { await schoolsNotifier.loadSchools(); }, diff --git a/lib/tools/ui/layouts/column_refresher.dart b/lib/tools/ui/layouts/column_refresher.dart index 4906fda47c..edc87ab4da 100644 --- a/lib/tools/ui/layouts/column_refresher.dart +++ b/lib/tools/ui/layouts/column_refresher.dart @@ -4,25 +4,31 @@ import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/navigation/ui/scroll_to_hide_navbar.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; class ColumnRefresher extends ConsumerWidget { final List children; final Future Function() onRefresh; + final ScrollController controller; const ColumnRefresher({ super.key, required this.onRefresh, required this.children, + required this.controller, }); @override Widget build(BuildContext context, WidgetRef ref) { if (kIsWeb) { - return ListView.builder( - itemCount: children.length, - itemBuilder: (BuildContext context, int index) => children[index], - physics: const AlwaysScrollableScrollPhysics( - parent: BouncingScrollPhysics(), + return ScrollToHideNavbar( + controller: controller, + child: ListView.builder( + itemCount: children.length, + itemBuilder: (BuildContext context, int index) => children[index], + physics: const AlwaysScrollableScrollPhysics( + parent: BouncingScrollPhysics(), + ), ), ); } @@ -33,29 +39,35 @@ class ColumnRefresher extends ConsumerWidget { onRefresh: () async { tokenExpireWrapper(ref, onRefresh); }, - child: ListView.builder( - itemCount: children.length, - itemBuilder: (BuildContext context, int index) => children[index], - physics: const AlwaysScrollableScrollPhysics( - parent: BouncingScrollPhysics(), + child: ScrollToHideNavbar( + controller: controller, + child: ListView.builder( + itemCount: children.length, + itemBuilder: (BuildContext context, int index) => children[index], + physics: const AlwaysScrollableScrollPhysics( + parent: BouncingScrollPhysics(), + ), ), ), ); - Widget buildIOSList(WidgetRef ref) => CustomScrollView( - shrinkWrap: true, - physics: const BouncingScrollPhysics(), - slivers: [ - CupertinoSliverRefreshControl( - onRefresh: () async { - tokenExpireWrapper(ref, onRefresh); - }, - ), - SliverList( - delegate: SliverChildBuilderDelegate( - (context, index) => children[index], - childCount: children.length, + Widget buildIOSList(WidgetRef ref) => ScrollToHideNavbar( + controller: controller, + child: CustomScrollView( + shrinkWrap: true, + physics: const BouncingScrollPhysics(), + slivers: [ + CupertinoSliverRefreshControl( + onRefresh: () async { + tokenExpireWrapper(ref, onRefresh); + }, ), - ), - ], + SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) => children[index], + childCount: children.length, + ), + ), + ], + ), ); } diff --git a/lib/tools/ui/layouts/refresher.dart b/lib/tools/ui/layouts/refresher.dart index 6cbeffc0a6..76db521a99 100644 --- a/lib/tools/ui/layouts/refresher.dart +++ b/lib/tools/ui/layouts/refresher.dart @@ -4,28 +4,32 @@ import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/navigation/ui/scroll_to_hide_navbar.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; class Refresher extends HookConsumerWidget { final Widget child; final Future Function() onRefresh; - final ScrollController? controller; + final ScrollController controller; const Refresher({ super.key, required this.onRefresh, required this.child, - this.controller, + required this.controller, }); @override Widget build(BuildContext context, WidgetRef ref) { if (kIsWeb) { - return SingleChildScrollView( + return ScrollToHideNavbar( controller: controller, - physics: const AlwaysScrollableScrollPhysics( - parent: BouncingScrollPhysics(), + child: SingleChildScrollView( + controller: controller, + physics: const AlwaysScrollableScrollPhysics( + parent: BouncingScrollPhysics(), + ), + child: child, ), - child: child, ); } return Platform.isAndroid ? buildAndroidList(ref) : buildIOSList(ref); @@ -36,38 +40,44 @@ class Refresher extends HookConsumerWidget { onRefresh: () async { tokenExpireWrapper(ref, onRefresh); }, - child: SingleChildScrollView( + child: ScrollToHideNavbar( controller: controller, - physics: const AlwaysScrollableScrollPhysics( - parent: BouncingScrollPhysics(), - ), - child: ConstrainedBox( - constraints: BoxConstraints(minHeight: constraints.maxHeight), - child: child, + child: SingleChildScrollView( + controller: controller, + physics: const AlwaysScrollableScrollPhysics( + parent: BouncingScrollPhysics(), + ), + child: ConstrainedBox( + constraints: BoxConstraints(minHeight: constraints.maxHeight), + child: child, + ), ), ), ), ); Widget buildIOSList(WidgetRef ref) => LayoutBuilder( - builder: (context, constraints) => CustomScrollView( + builder: (context, constraints) => ScrollToHideNavbar( controller: controller, - shrinkWrap: false, - physics: const BouncingScrollPhysics( - parent: AlwaysScrollableScrollPhysics(), - ), - slivers: [ - CupertinoSliverRefreshControl( - onRefresh: () async { - tokenExpireWrapper(ref, onRefresh); - }, + child: CustomScrollView( + controller: controller, + shrinkWrap: false, + physics: const BouncingScrollPhysics( + parent: AlwaysScrollableScrollPhysics(), ), - SliverToBoxAdapter( - child: ConstrainedBox( - constraints: BoxConstraints(minHeight: constraints.maxHeight), - child: child, + slivers: [ + CupertinoSliverRefreshControl( + onRefresh: () async { + tokenExpireWrapper(ref, onRefresh); + }, ), - ), - ], + SliverToBoxAdapter( + child: ConstrainedBox( + constraints: BoxConstraints(minHeight: constraints.maxHeight), + child: child, + ), + ), + ], + ), ), ); } diff --git a/lib/vote/ui/pages/admin_page/admin_page.dart b/lib/vote/ui/pages/admin_page/admin_page.dart index bd5ea02dac..d66cd3862b 100644 --- a/lib/vote/ui/pages/admin_page/admin_page.dart +++ b/lib/vote/ui/pages/admin_page/admin_page.dart @@ -51,6 +51,7 @@ class AdminPage extends HookConsumerWidget { return VoteTemplate( child: Refresher( + controller: ScrollController(), onRefresh: () async { await statusNotifier.loadStatus(); if (status == Status.counting || status == Status.published) { diff --git a/lib/vote/ui/pages/main_page/main_page.dart b/lib/vote/ui/pages/main_page/main_page.dart index 488eeb081f..8699731ee2 100644 --- a/lib/vote/ui/pages/main_page/main_page.dart +++ b/lib/vote/ui/pages/main_page/main_page.dart @@ -92,6 +92,7 @@ class VoteMainPage extends HookConsumerWidget { } return VoteTemplate( child: Refresher( + controller: ScrollController(), onRefresh: () async { await statusNotifier.loadStatus(); if (s == Status.open) { From c97180fd48aa236ddea32937c9d5f74cb4efdf23 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 10 Aug 2025 18:20:54 +0200 Subject: [PATCH 249/473] feat: adding post button --- lib/advert/ui/components/announcer_bar.dart | 3 - .../ui/components/special_action_button.dart | 54 ++++++++++++++++++ .../ui/pages/admin_page/admin_page.dart | 47 +++++++++------ lib/advert/ui/pages/main_page/main_page.dart | 57 ++++++++++--------- 4 files changed, 115 insertions(+), 46 deletions(-) create mode 100644 lib/advert/ui/components/special_action_button.dart diff --git a/lib/advert/ui/components/announcer_bar.dart b/lib/advert/ui/components/announcer_bar.dart index ac45c4fc8a..0a09a6996e 100644 --- a/lib/advert/ui/components/announcer_bar.dart +++ b/lib/advert/ui/components/announcer_bar.dart @@ -10,13 +10,11 @@ class AnnouncerBar extends HookConsumerWidget { final bool useUserAnnouncers; final bool multipleSelect; final bool isNotClickable; - final Widget? addButton; const AnnouncerBar({ super.key, required this.multipleSelect, required this.useUserAnnouncers, this.isNotClickable = false, - this.addButton, }); @override @@ -31,7 +29,6 @@ class AnnouncerBar extends HookConsumerWidget { return AsyncChild( value: announcerList, builder: (context, userAnnouncers) => HorizontalListView.builder( - firstChild: addButton, height: 66, items: userAnnouncers, itemBuilder: (context, e, i) { diff --git a/lib/advert/ui/components/special_action_button.dart b/lib/advert/ui/components/special_action_button.dart new file mode 100644 index 0000000000..b490804672 --- /dev/null +++ b/lib/advert/ui/components/special_action_button.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; +import 'package:titan/tools/constants.dart'; + +class SpecialActionButton extends StatelessWidget { + final String name; + final VoidCallback onTap; + final Widget icon; + const SpecialActionButton({ + super.key, + required this.name, + required this.onTap, + required this.icon, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 5.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 45, + height: 45, + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all(color: ColorConstants.onTertiary, width: 2), + color: ColorConstants.tertiary, + ), + child: Center(child: icon), + ), + const SizedBox(height: 4), + SizedBox( + width: 55, + child: Text( + name, + textAlign: TextAlign.center, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 12, + color: ColorConstants.onTertiary, + fontWeight: FontWeight.normal, + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/advert/ui/pages/admin_page/admin_page.dart b/lib/advert/ui/pages/admin_page/admin_page.dart index adf40b1d15..a5895472d6 100644 --- a/lib/advert/ui/pages/admin_page/admin_page.dart +++ b/lib/advert/ui/pages/admin_page/admin_page.dart @@ -1,5 +1,6 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; +import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/advert/class/advert.dart'; import 'package:titan/advert/providers/advert_list_provider.dart'; @@ -8,10 +9,12 @@ import 'package:titan/advert/providers/advert_provider.dart'; import 'package:titan/advert/providers/announcer_list_provider.dart'; import 'package:titan/advert/providers/announcer_provider.dart'; import 'package:titan/advert/ui/components/announcer_item.dart'; +import 'package:titan/advert/ui/components/special_action_button.dart'; import 'package:titan/advert/ui/pages/admin_page/admin_advert_card.dart'; import 'package:titan/advert/ui/pages/advert.dart'; import 'package:titan/advert/router.dart'; import 'package:titan/advert/ui/components/announcer_bar.dart'; +import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; @@ -36,22 +39,34 @@ class AdvertAdminPage extends HookConsumerWidget { return AdvertTemplate( child: Column( children: [ - AnnouncerBar( - useUserAnnouncers: true, - multipleSelect: true, - addButton: AnnouncerItem( - name: 'Post', - avatarName: '+', - selected: false, - onTap: () { - advertNotifier.setAdvert(Advert.empty()); - QR.to( - AdvertRouter.root + - AdvertRouter.admin + - AdvertRouter.addEditAdvert, - ); - }, - ), + Row( + children: [ + Expanded( + child: AnnouncerBar( + useUserAnnouncers: true, + multipleSelect: true, + ), + ), + SizedBox(width: 5), + Container(width: 2, height: 60, color: ColorConstants.secondary), + SizedBox(width: 5), + SpecialActionButton( + onTap: () { + advertNotifier.setAdvert(Advert.empty()); + QR.to( + AdvertRouter.root + + AdvertRouter.admin + + AdvertRouter.addEditAdvert, + ); + }, + icon: HeroIcon( + HeroIcons.plus, + color: ColorConstants.background, + ), + name: "Post", + ), + SizedBox(width: 10), + ], ), const SizedBox(height: 20), Expanded( diff --git a/lib/advert/ui/pages/main_page/main_page.dart b/lib/advert/ui/pages/main_page/main_page.dart index 0671b2ee91..e92a38808c 100644 --- a/lib/advert/ui/pages/main_page/main_page.dart +++ b/lib/advert/ui/pages/main_page/main_page.dart @@ -6,6 +6,7 @@ import 'package:titan/advert/providers/advert_list_provider.dart'; import 'package:titan/advert/providers/advert_posters_provider.dart'; import 'package:titan/advert/providers/announcer_provider.dart'; import 'package:titan/advert/providers/is_advert_admin_provider.dart'; +import 'package:titan/advert/ui/components/special_action_button.dart'; import 'package:titan/advert/ui/pages/advert.dart'; import 'package:titan/advert/router.dart'; import 'package:titan/advert/ui/components/announcer_bar.dart'; @@ -13,7 +14,6 @@ import 'package:titan/advert/ui/pages/main_page/advert_card.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; -import 'package:titan/tools/ui/styleguide/icon_button.dart'; import 'package:qlevar_router/qlevar_router.dart'; class AdvertMainPage extends HookConsumerWidget { @@ -30,37 +30,40 @@ class AdvertMainPage extends HookConsumerWidget { return AdvertTemplate( child: Column( children: [ - const AnnouncerBar(useUserAnnouncers: false, multipleSelect: true), - const SizedBox(height: 15), + Row( + children: [ + Expanded( + child: const AnnouncerBar( + useUserAnnouncers: false, + multipleSelect: true, + ), + ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text( - "Annonces", - style: TextStyle( - fontSize: 24, - fontWeight: FontWeight.bold, - color: ColorConstants.title, - ), + if (isAdmin) ...[ + SizedBox(width: 5), + Container( + width: 2, + height: 60, + color: ColorConstants.secondary, ), - if (isAdmin) - CustomIconButton( - icon: HeroIcon( - HeroIcons.userGroup, - color: ColorConstants.background, - ), - onPressed: () { - selectedNotifier.clearAnnouncer(); - QR.to(AdvertRouter.root + AdvertRouter.admin); - }, + SizedBox(width: 5), + SpecialActionButton( + onTap: () { + selectedNotifier.clearAnnouncer(); + QR.to(AdvertRouter.root + AdvertRouter.admin); + }, + icon: HeroIcon( + HeroIcons.userGroup, + color: ColorConstants.background, ), + name: "Admin", + ), + SizedBox(width: 10), ], - ), + ], ), - const SizedBox(height: 5), + + const SizedBox(height: 20), Expanded( child: AsyncChild( From ef730e2685778aee934e3c07873f80dab1cf07fe Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 10 Aug 2025 18:20:54 +0200 Subject: [PATCH 250/473] fix: setting announcer when only one available --- .../ui/pages/admin_page/admin_page.dart | 10 ++- .../pages/form_page/add_edit_advert_page.dart | 66 +++++++++++-------- 2 files changed, 46 insertions(+), 30 deletions(-) diff --git a/lib/advert/ui/pages/admin_page/admin_page.dart b/lib/advert/ui/pages/admin_page/admin_page.dart index a5895472d6..74c9474de9 100644 --- a/lib/advert/ui/pages/admin_page/admin_page.dart +++ b/lib/advert/ui/pages/admin_page/admin_page.dart @@ -8,7 +8,6 @@ import 'package:titan/advert/providers/advert_posters_provider.dart'; import 'package:titan/advert/providers/advert_provider.dart'; import 'package:titan/advert/providers/announcer_list_provider.dart'; import 'package:titan/advert/providers/announcer_provider.dart'; -import 'package:titan/advert/ui/components/announcer_item.dart'; import 'package:titan/advert/ui/components/special_action_button.dart'; import 'package:titan/advert/ui/pages/admin_page/admin_advert_card.dart'; import 'package:titan/advert/ui/pages/advert.dart'; @@ -36,6 +35,11 @@ class AdvertAdminPage extends HookConsumerWidget { final advertListNotifier = ref.watch(advertListProvider.notifier); final selectedAnnouncers = ref.watch(announcerProvider); final selectedAnnouncersNotifier = ref.read(announcerProvider.notifier); + final selectedNotifier = ref.read(announcerProvider.notifier); + final userAnnouncersSync = userAnnouncerList.maybeWhen( + orElse: () => [], + data: (data) => data, + ); return AdvertTemplate( child: Column( children: [ @@ -53,6 +57,10 @@ class AdvertAdminPage extends HookConsumerWidget { SpecialActionButton( onTap: () { advertNotifier.setAdvert(Advert.empty()); + if (userAnnouncersSync.length == 1 && + selectedAnnouncers.isEmpty) { + selectedNotifier.addAnnouncer(userAnnouncersSync[0]); + } QR.to( AdvertRouter.root + AdvertRouter.admin + diff --git a/lib/advert/ui/pages/form_page/add_edit_advert_page.dart b/lib/advert/ui/pages/form_page/add_edit_advert_page.dart index e3ce19d2c0..4168ecc7a0 100644 --- a/lib/advert/ui/pages/form_page/add_edit_advert_page.dart +++ b/lib/advert/ui/pages/form_page/add_edit_advert_page.dart @@ -12,6 +12,7 @@ import 'package:titan/advert/providers/advert_list_provider.dart'; import 'package:titan/advert/providers/advert_poster_provider.dart'; import 'package:titan/advert/providers/advert_posters_provider.dart'; import 'package:titan/advert/providers/advert_provider.dart'; +import 'package:titan/advert/providers/announcer_list_provider.dart'; import 'package:titan/advert/providers/announcer_provider.dart'; import 'package:titan/advert/ui/pages/advert.dart'; import 'package:titan/advert/ui/components/announcer_bar.dart'; @@ -35,6 +36,7 @@ class AdvertAddEditAdvertPage extends HookConsumerWidget { final title = useTextEditingController(text: advert.title); final content = useTextEditingController(text: advert.content); final selectedAnnouncers = ref.watch(announcerProvider); + final userAnnouncerList = ref.watch(userAnnouncerListProvider); final advertPosters = ref.watch(advertPostersProvider); final advertListNotifier = ref.watch(advertListProvider.notifier); @@ -50,6 +52,11 @@ class AdvertAddEditAdvertPage extends HookConsumerWidget { }); } + final userAnnouncersSync = userAnnouncerList.maybeWhen( + orElse: () => [], + data: (data) => data, + ); + final ImagePicker picker = ImagePicker(); void displayAdvertToastWithContext(TypeMsg type, String msg) { @@ -63,37 +70,38 @@ class AdvertAddEditAdvertPage extends HookConsumerWidget { key: key, child: Column( children: [ - FormField>( - validator: (e) { - if (selectedAnnouncers.isEmpty) { - return AppLocalizations.of( - context, - )!.advertChoosingAnnouncer; - } - return null; - }, - builder: (formFieldState) => Container( - decoration: BoxDecoration( - color: Colors.white, - borderRadius: const BorderRadius.all(Radius.circular(20)), - boxShadow: formFieldState.hasError - ? [ - const BoxShadow( - color: Colors.red, - spreadRadius: 3, - blurRadius: 3, - offset: Offset(2, 2), - ), - ] - : [], - ), - child: AnnouncerBar( - useUserAnnouncers: true, - multipleSelect: false, - isNotClickable: isEdit, + if (userAnnouncersSync.length > 1) + FormField>( + validator: (e) { + if (selectedAnnouncers.isEmpty) { + return AppLocalizations.of( + context, + )!.advertChoosingAnnouncer; + } + return null; + }, + builder: (formFieldState) => Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: const BorderRadius.all(Radius.circular(20)), + boxShadow: formFieldState.hasError + ? [ + const BoxShadow( + color: Colors.red, + spreadRadius: 3, + blurRadius: 3, + offset: Offset(2, 2), + ), + ] + : [], + ), + child: AnnouncerBar( + useUserAnnouncers: true, + multipleSelect: false, + isNotClickable: isEdit, + ), ), ), - ), Padding( padding: const EdgeInsets.symmetric(horizontal: 30), child: Column( From 3347b04c48cbf1d7c11054d1290634b088cf2bb8 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 10 Aug 2025 18:20:54 +0200 Subject: [PATCH 251/473] feat: allowing admin to delete any advert --- .../providers/admin_advert_list_provider.dart | 37 ++++++++ .../repositories/advert_repository.dart | 6 ++ .../ui/pages/admin_page/admin_page.dart | 85 +++++++++++++------ lib/advert/ui/pages/main_page/main_page.dart | 6 +- 4 files changed, 104 insertions(+), 30 deletions(-) create mode 100644 lib/advert/providers/admin_advert_list_provider.dart diff --git a/lib/advert/providers/admin_advert_list_provider.dart b/lib/advert/providers/admin_advert_list_provider.dart new file mode 100644 index 0000000000..65d3cdba9c --- /dev/null +++ b/lib/advert/providers/admin_advert_list_provider.dart @@ -0,0 +1,37 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/auth/providers/openid_provider.dart'; +import 'package:titan/advert/class/advert.dart'; +import 'package:titan/advert/repositories/advert_repository.dart'; +import 'package:titan/tools/providers/list_notifier.dart'; +import 'package:titan/tools/token_expire_wrapper.dart'; + +class AdminAdvertListNotifier extends ListNotifier { + AdvertRepository repository = AdvertRepository(); + AdminAdvertListNotifier({required String token}) + : super(const AsyncValue.loading()) { + repository.setToken(token); + } + + Future>> loadAdverts() async { + return await loadList(repository.getAllAdminAdvert); + } + + Future deleteAdvert(Advert advert) async { + return await delete( + repository.deleteAdvert, + (adverts, advert) => adverts..removeWhere((b) => b.id == advert.id), + advert.id, + advert, + ); + } +} + +final adminAdvertListProvider = + StateNotifierProvider>>((ref) { + final token = ref.watch(tokenProvider); + AdminAdvertListNotifier notifier = AdminAdvertListNotifier(token: token); + tokenExpireWrapperAuth(ref, () async { + await notifier.loadAdverts(); + }); + return notifier; + }); diff --git a/lib/advert/repositories/advert_repository.dart b/lib/advert/repositories/advert_repository.dart index befbc7da72..26b452d9e9 100644 --- a/lib/advert/repositories/advert_repository.dart +++ b/lib/advert/repositories/advert_repository.dart @@ -12,6 +12,12 @@ class AdvertRepository extends Repository { )).map((e) => Advert.fromJson(e)).toList(); } + Future> getAllAdminAdvert() async { + return (await getList( + suffix: 'adverts/admin', + )).map((e) => Advert.fromJson(e)).toList(); + } + Future getAdvert(String id) async { return Advert.fromJson(await getOne(id)); } diff --git a/lib/advert/ui/pages/admin_page/admin_page.dart b/lib/advert/ui/pages/admin_page/admin_page.dart index 74c9474de9..4b32d96e5a 100644 --- a/lib/advert/ui/pages/admin_page/admin_page.dart +++ b/lib/advert/ui/pages/admin_page/admin_page.dart @@ -2,12 +2,15 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/admin/providers/is_admin_provider.dart'; import 'package:titan/advert/class/advert.dart'; +import 'package:titan/advert/providers/admin_advert_list_provider.dart'; import 'package:titan/advert/providers/advert_list_provider.dart'; import 'package:titan/advert/providers/advert_posters_provider.dart'; import 'package:titan/advert/providers/advert_provider.dart'; import 'package:titan/advert/providers/announcer_list_provider.dart'; import 'package:titan/advert/providers/announcer_provider.dart'; +import 'package:titan/advert/providers/is_advert_admin_provider.dart'; import 'package:titan/advert/ui/components/special_action_button.dart'; import 'package:titan/advert/ui/pages/admin_page/admin_advert_card.dart'; import 'package:titan/advert/ui/pages/advert.dart'; @@ -26,13 +29,16 @@ class AdvertAdminPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final advertNotifier = ref.watch(advertProvider.notifier); - final advertList = ref.watch(advertListProvider); + final isAdmin = ref.watch(isAdminProvider); + final isAdvertAdmin = ref.watch(isAdvertAdminProvider); + final advertList = isAdmin + ? ref.watch(adminAdvertListProvider) + : ref.watch(advertListProvider); final userAnnouncerListNotifier = ref.watch( userAnnouncerListProvider.notifier, ); final userAnnouncerList = ref.watch(userAnnouncerListProvider); final advertPostersNotifier = ref.watch(advertPostersProvider.notifier); - final advertListNotifier = ref.watch(advertListProvider.notifier); final selectedAnnouncers = ref.watch(announcerProvider); final selectedAnnouncersNotifier = ref.read(announcerProvider.notifier); final selectedNotifier = ref.read(announcerProvider.notifier); @@ -47,33 +53,39 @@ class AdvertAdminPage extends HookConsumerWidget { children: [ Expanded( child: AnnouncerBar( - useUserAnnouncers: true, + useUserAnnouncers: !isAdmin, multipleSelect: true, ), ), - SizedBox(width: 5), - Container(width: 2, height: 60, color: ColorConstants.secondary), - SizedBox(width: 5), - SpecialActionButton( - onTap: () { - advertNotifier.setAdvert(Advert.empty()); - if (userAnnouncersSync.length == 1 && - selectedAnnouncers.isEmpty) { - selectedNotifier.addAnnouncer(userAnnouncersSync[0]); - } - QR.to( - AdvertRouter.root + - AdvertRouter.admin + - AdvertRouter.addEditAdvert, - ); - }, - icon: HeroIcon( - HeroIcons.plus, - color: ColorConstants.background, + if (!isAdmin || isAdvertAdmin) ...[ + SizedBox(width: 5), + Container( + width: 2, + height: 60, + color: ColorConstants.secondary, ), - name: "Post", - ), - SizedBox(width: 10), + SizedBox(width: 5), + SpecialActionButton( + onTap: () { + advertNotifier.setAdvert(Advert.empty()); + if (userAnnouncersSync.length == 1 && + selectedAnnouncers.isEmpty) { + selectedNotifier.addAnnouncer(userAnnouncersSync[0]); + } + QR.to( + AdvertRouter.root + + AdvertRouter.admin + + AdvertRouter.addEditAdvert, + ); + }, + icon: HeroIcon( + HeroIcons.plus, + color: ColorConstants.background, + ), + name: "Post", + ), + SizedBox(width: 10), + ], ], ), const SizedBox(height: 20), @@ -105,7 +117,14 @@ class AdvertAdminPage extends HookConsumerWidget { return Refresher( controller: ScrollController(), onRefresh: () async { - await advertListNotifier.loadAdverts(); + if (isAdmin) { + await ref + .watch(adminAdvertListProvider.notifier) + .loadAdverts(); + } + await ref + .watch(advertListProvider.notifier) + .loadAdverts(); await userAnnouncerListNotifier.loadMyAnnouncerList(); advertPostersNotifier.resetTData(); }, @@ -136,8 +155,18 @@ class AdvertAdminPage extends HookConsumerWidget { descriptions: AppLocalizations.of( context, )!.advertDeleteAdvert, - onYes: () { - advertListNotifier.deleteAdvert(advert); + onYes: () async { + if (isAdmin) { + await ref + .watch( + adminAdvertListProvider.notifier, + ) + .deleteAdvert(advert); + } else { + await ref + .watch(advertListProvider.notifier) + .deleteAdvert(advert); + } advertPostersNotifier.deleteE( advert.id, 0, diff --git a/lib/advert/ui/pages/main_page/main_page.dart b/lib/advert/ui/pages/main_page/main_page.dart index e92a38808c..1538feda5d 100644 --- a/lib/advert/ui/pages/main_page/main_page.dart +++ b/lib/advert/ui/pages/main_page/main_page.dart @@ -2,6 +2,7 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/admin/providers/is_admin_provider.dart'; import 'package:titan/advert/providers/advert_list_provider.dart'; import 'package:titan/advert/providers/advert_posters_provider.dart'; import 'package:titan/advert/providers/announcer_provider.dart'; @@ -26,7 +27,8 @@ class AdvertMainPage extends HookConsumerWidget { final advertPostersNotifier = ref.watch(advertPostersProvider.notifier); final selected = ref.watch(announcerProvider); final selectedNotifier = ref.watch(announcerProvider.notifier); - final isAdmin = ref.watch(isAdvertAdminProvider); + final isAdvertAdmin = ref.watch(isAdvertAdminProvider); + final isAdmin = ref.watch(isAdminProvider); return AdvertTemplate( child: Column( children: [ @@ -39,7 +41,7 @@ class AdvertMainPage extends HookConsumerWidget { ), ), - if (isAdmin) ...[ + if (isAdmin || isAdvertAdmin) ...[ SizedBox(width: 5), Container( width: 2, From 4c7749f1610b29cc6169f94d821c34e7eb44f0ec Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 10 Aug 2025 18:20:54 +0200 Subject: [PATCH 252/473] fix: missing controller --- .../pages/group_notifification_page/group_notification_page.dart | 1 + lib/admin/ui/pages/groups/edit_group_page/edit_group_page.dart | 1 + .../association_membership_page/association_membership_page.dart | 1 + 3 files changed, 3 insertions(+) diff --git a/lib/admin/ui/pages/group_notifification_page/group_notification_page.dart b/lib/admin/ui/pages/group_notifification_page/group_notification_page.dart index 587be764c2..cfb4a63cad 100644 --- a/lib/admin/ui/pages/group_notifification_page/group_notification_page.dart +++ b/lib/admin/ui/pages/group_notifification_page/group_notification_page.dart @@ -31,6 +31,7 @@ class GroupNotificationPage extends HookConsumerWidget { final localizeWithContext = AppLocalizations.of(context)!; return AdminTemplate( child: Refresher( + controller: ScrollController(), onRefresh: () async { await groupsNotifier.loadGroups(); }, diff --git a/lib/admin/ui/pages/groups/edit_group_page/edit_group_page.dart b/lib/admin/ui/pages/groups/edit_group_page/edit_group_page.dart index 3dd42efe12..8261493875 100644 --- a/lib/admin/ui/pages/groups/edit_group_page/edit_group_page.dart +++ b/lib/admin/ui/pages/groups/edit_group_page/edit_group_page.dart @@ -23,6 +23,7 @@ class EditGroupPage extends ConsumerWidget { ); return AdminTemplate( child: Refresher( + controller: ScrollController(), onRefresh: () async { await groupNotifier.loadGroup(groupId); }, diff --git a/lib/admin/ui/pages/membership/association_membership_page/association_membership_page.dart b/lib/admin/ui/pages/membership/association_membership_page/association_membership_page.dart index fd2c5f976e..5941407b4f 100644 --- a/lib/admin/ui/pages/membership/association_membership_page/association_membership_page.dart +++ b/lib/admin/ui/pages/membership/association_membership_page/association_membership_page.dart @@ -53,6 +53,7 @@ class AssociationMembershipsPage extends HookConsumerWidget { return AdminTemplate( child: Refresher( + controller: ScrollController(), onRefresh: () async { await associationMembershipsNotifier.loadAssociationMemberships(); }, From 3f1c9d2a2430ec999291ca912b5ddb98a2d3d42e Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 10 Aug 2025 18:20:54 +0200 Subject: [PATCH 253/473] lint: applying linter --- lib/advert/providers/admin_advert_list_provider.dart | 4 +++- lib/advert/ui/pages/main_page/advert_card.dart | 10 ++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/advert/providers/admin_advert_list_provider.dart b/lib/advert/providers/admin_advert_list_provider.dart index 65d3cdba9c..40ac1c94f9 100644 --- a/lib/advert/providers/admin_advert_list_provider.dart +++ b/lib/advert/providers/admin_advert_list_provider.dart @@ -27,7 +27,9 @@ class AdminAdvertListNotifier extends ListNotifier { } final adminAdvertListProvider = - StateNotifierProvider>>((ref) { + StateNotifierProvider>>(( + ref, + ) { final token = ref.watch(tokenProvider); AdminAdvertListNotifier notifier = AdminAdvertListNotifier(token: token); tokenExpireWrapperAuth(ref, () async { diff --git a/lib/advert/ui/pages/main_page/advert_card.dart b/lib/advert/ui/pages/main_page/advert_card.dart index a020da0f2d..78d19b8943 100644 --- a/lib/advert/ui/pages/main_page/advert_card.dart +++ b/lib/advert/ui/pages/main_page/advert_card.dart @@ -56,7 +56,7 @@ class AdvertCard extends HookConsumerWidget { ), ), const SizedBox(width: 12), - + Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -85,7 +85,7 @@ class AdvertCard extends HookConsumerWidget { ), ), const SizedBox(height: 15), - + AutoLoaderChild( group: posters, notifier: advertPostersNotifier, @@ -98,9 +98,7 @@ class AdvertCard extends HookConsumerWidget { color: ColorConstants.onBackground, borderRadius: BorderRadius.circular(20), ), - child: const Center( - child: HeroIcon(HeroIcons.photo, size: 50), - ), + child: const Center(child: HeroIcon(HeroIcons.photo, size: 50)), ), ), dataBuilder: (context, value) => AspectRatio( @@ -117,7 +115,7 @@ class AdvertCard extends HookConsumerWidget { ), ), ), - + Padding( padding: const EdgeInsets.all(16.0), child: Column( From 334e7c71401f59933c16c0d10bf75f3730d37761 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 10 Aug 2025 19:59:12 +0200 Subject: [PATCH 254/473] fix: missing controllers --- .../pages/association_groups_page/association_groups_page.dart | 1 + .../pages/association_members_page/association_members_page.dart | 1 + 2 files changed, 2 insertions(+) diff --git a/lib/phonebook/ui/pages/association_groups_page/association_groups_page.dart b/lib/phonebook/ui/pages/association_groups_page/association_groups_page.dart index 16f986107f..43942ea55e 100644 --- a/lib/phonebook/ui/pages/association_groups_page/association_groups_page.dart +++ b/lib/phonebook/ui/pages/association_groups_page/association_groups_page.dart @@ -52,6 +52,7 @@ class AssociationGroupsPage extends HookConsumerWidget { return PhonebookTemplate( child: Refresher( + controller: ScrollController(), onRefresh: () async { await tokenExpireWrapper(ref, () async { await associationListNotifier.loadAssociations(); diff --git a/lib/phonebook/ui/pages/association_members_page/association_members_page.dart b/lib/phonebook/ui/pages/association_members_page/association_members_page.dart index d1d8a2626f..597c90d581 100644 --- a/lib/phonebook/ui/pages/association_members_page/association_members_page.dart +++ b/lib/phonebook/ui/pages/association_members_page/association_members_page.dart @@ -45,6 +45,7 @@ class AssociationMembersPage extends HookConsumerWidget { child: Padding( padding: EdgeInsets.symmetric(horizontal: 20), child: Refresher( + controller: ScrollController(), onRefresh: () { return tokenExpireWrapper(ref, () async { await associationMemberListNotifier.loadMembers( From 1253e7f6168afd46f84188bba7534c878c72ffe5 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 10 Aug 2025 20:02:29 +0200 Subject: [PATCH 255/473] fix: missing controller --- .../pages/association_groups_page/association_groups_page.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/phonebook/ui/pages/association_groups_page/association_groups_page.dart b/lib/phonebook/ui/pages/association_groups_page/association_groups_page.dart index 16f986107f..43942ea55e 100644 --- a/lib/phonebook/ui/pages/association_groups_page/association_groups_page.dart +++ b/lib/phonebook/ui/pages/association_groups_page/association_groups_page.dart @@ -52,6 +52,7 @@ class AssociationGroupsPage extends HookConsumerWidget { return PhonebookTemplate( child: Refresher( + controller: ScrollController(), onRefresh: () async { await tokenExpireWrapper(ref, () async { await associationListNotifier.loadAssociations(); From fa9d10719def6599c87601ea2782b1754097841f Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 10 Aug 2025 20:03:35 +0200 Subject: [PATCH 256/473] fix: linter --- .../association_members_page/association_members_page.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/phonebook/ui/pages/association_members_page/association_members_page.dart b/lib/phonebook/ui/pages/association_members_page/association_members_page.dart index 597c90d581..72410765cb 100644 --- a/lib/phonebook/ui/pages/association_members_page/association_members_page.dart +++ b/lib/phonebook/ui/pages/association_members_page/association_members_page.dart @@ -45,7 +45,7 @@ class AssociationMembersPage extends HookConsumerWidget { child: Padding( padding: EdgeInsets.symmetric(horizontal: 20), child: Refresher( - controller: ScrollController(), + controller: ScrollController(), onRefresh: () { return tokenExpireWrapper(ref, () async { await associationMemberListNotifier.loadMembers( From d44d89088e41d656a2a58c822157668db3bc5b58 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 10 Aug 2025 20:19:55 +0200 Subject: [PATCH 257/473] fix: missing translation --- lib/l10n/app_fr.arb | 123 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 122 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 06bd0bf12b..3fea825738 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -1340,5 +1340,126 @@ "modulePaymentDescription": "Gérer les paiements, les statistiques et les appareils", "paiementTopUp": "Recharge", "paiementStoreManagement": "Gestion des associations", - "paiementDeleteStore": "Supprimer l'association" + "paiementDeleteStore": "Supprimer l'association", + "paiementDeleteStoreDescription": "Voulez-vous vraiment supprimer cette association ?", + "paiementDeleteStoreError": "Impossible de supprimer l'association", + "paiementStoreDeleted": "Association supprimée", + "paiementAddThisDevice": "Ajouter cet appareil", + "paiementThisDevice": "(cet appareil)", + "paiementCancelled": "Annulé", + "paiementThe": "Le", + "paiementOf": "de", + "paiementRefundedThe": "Remboursé le", + "paiementAt": "à", + "paiementPleaseAcceptTOS": "Veuillez accepter les Conditions Générales d'Utilisation.", + "paiementAskDeviceActivation": "Demande d'activation de l'appareil", + "paiementDeviceActivationReceived": "La demande d'activation est prise en compte, veuilliez consulter votre boite mail pour finaliser la démarche", + "paiementRevokeDevice": "Révoquer l'appareil ?", + "paiementRevokeDeviceDescription": "Vous ne pourrez plus utiliser cet appareil pour les paiements", + "paiementDeviceRevoked": "Appareil révoqué", + "paiementDeviceRevokingError": "Erreur lors de la révocation de l'appareil", + "paiementPleaseAcceptPopup": "Veuillez autoriser les popups", + "paiementProceedSuccessfully": "Paiement effectué avec succès", + "paiementCancelledTransaction": "Paiement annulé", + "paiementPleaseEnterMinAmount": "Veuillez entrer un montant supérieur à 1", + "paiementMaxAmount": "Le montant maximum de votre portefeuille est de", + "paiementPayWithHA": "Payer avec HelloAsso", + "paiementBalanceAfterTopUp": "Solde après recharge :", + "paiementPersonalBalance": "Solde personnel", + "paiementDevices": "Appareils", + "paiementPay": "Payer", + "paiementDeviceNotRegistered": "Appareil non enregistré", + "paiementDeviceNotRegisteredDescription": "Votre appareil n'est pas encore enregistré. \nPour l'enregistrer, veuillez vous rendre sur la page des appareils.", + "paiementAccessPage": "Accéder à la page", + "paiementDeviceNotActivated": "Appareil non activé", + "paiementDeviceNotActivatedDescription": "Votre appareil n'est pas encore activé. \nPour l'activer, veuillez vous rendre sur la page des appareils.", + "paiementReactivateRevokedDeviceDescription": "Votre appareil a été révoqué. \nPour le réactiver, veuillez vous rendre sur la page des appareils.", + "paiementDeviceRecoveryError": "Erreur lors de la récupération de l'appareil", + "paiementStats": "Stats", + "paimentTopUpAction": "Recharger", + "paiementGetBalanceError": "Erreur lors de la récupération du solde : ", + "paiementLastTransactions": "Dernières transactions", + "paiementGetTransactionsError": "Erreur lors de la récupération des transactions : ", + "paiementStoreBalance": "Solde associatif", + "paiementScan": "Scanner", + "paiementManagement": "Gestion", + "paiementHistory": "Historique", + "paiementHandOver": "Passation", + "paiementStores": "Associations", + "paiementAdmin": "Administrateur", + "paiementSuccededTransaction": "Paiement réussi", + "paiementNewCGU": "Nouvelles Conditions Générales d'Utilisation", + "paiementDecline": "Refuser", + "paiementAccept": "Accepter", + "paiementAmount": "Montant", + "paiementValidUntil": "Valide jusqu'à", + "paiementClose": "Fermer", + "paiementPleaseEnterValidAmount": "Veuillez entrer un montant valide", + "paiementPleaseAuthenticate": "Veuillez vous authentifier", + "paiementAthenticationRequired": "Authentification requise pour payer", + "paiementNoThanks": "Non merci", + "paiementAuthentificationFailed": "Échec de l'authentification", + "paiementPleaseAddDevice": "Veuillez ajouter cet appareil pour payer", + "paiementPayment": "Paiement", + "paiementBalanceAfterTransaction": "Solde après paiement : ", + "paiementCancel": "Annuler", + "paiementLimitedTo": "Limité à", + "paiementScanCode": "Scanner un code", + "paiementNext": "Suivant", + "paiementCancelTransaction": "Annuler la transaction", + "paiementTransactionCancelled": "Transaction annulée", + "paiementTransactionCancelledDescription": "Voulez-vous vraiment annuler la transaction de", + "paiementTransactionCancelledError": "Erreur lors de l'annulation de la transaction", + "paiementNoMembership": "Aucune adhésion", + "paiementNoMembershipDescription": "Ce produit n'est pas disponnible pour les non-adhérents. Confirmer l'encaissement ?", + "paiementQRCodeAlreadyUsed": "QR Code déjà utilisé", + "paiementCameraPermissionRequired": "Permission d'accès à la caméra requise", + "paiementCameraPerssionRequiredDescription": "Pour scanner un QR Code, vous devez autoriser l'accès à la caméra.", + "paiementSettings": "Paramètres", + "paiementReceived": "Reçu", + "paiementSpent": "Déboursé", + "paiementNoTrasactionForThisMonth": "Aucune transaction pour ce mois", + "paiementNoTransactinon": "Aucune transaction", + "paiementSellerRigths": "Droits du vendeur", + "paiementCanBank": "Peut encaisser", + "paiementCanSeeHistory": "Peut voir l'historique", + "paiementCanCancelTransaction": "Peut annuler des transactions", + "paiementCanManageSellers": "Peut gérer les vendeurs", + "paiementAddedSeller": "Vendeur ajouté", + "paiementAddingSellerError": "Erreur lors de l'ajout du vendeur", + "paiementBank": "Encaisser", + "paiementSeeHistory": "Voir l'historique", + "paiementCancelTransactions": "Annuler les transactions", + "paiementManageSellers": "Gérer les vendeurs", + "paiementStructureAdmin": "Administrateur de la structure", + "paiementRightsOf": "Droits de", + "paiementRightsUpdated": "Droits mis à jour", + "paiementRightsUpdateError": "Erreur lors de la mise à jour des droits", + "paiementDeleteSellerDescription": "Voulez-vous vraiment supprimer ce vendeur ?", + "paiementDeletedSeller": "Vendeur supprimé", + "paiementDeletingSellerError": "Erreur lors de la suppression du vendeur", + "paiementDeleteSeller": "Supprimer le vendeur", + "paiementAdd": "Ajouter", + "paiementAddSeller": "Ajouter un vendeur", + "paiementSellerError": "Vous n'êtes pas vendeur de cette association", + "paiementSellersOf": "Les vendeurs de", + "paiementModify": "Modifier", + "paiementAStore": "une association", + "paiementStoreName": "Nom de l'association", + "paiementSuccessfullyAddedStore": "Association ajoutée avec succès", + "paiementSuccessfullyModifiedStore": "Association modifiée avec succès", + "paiementAddingStoreError": "Erreur lors de l'ajout de l'association", + "paiementModifyingStoreError": "Erreur lors de la modification de l'association", + "paiementRefund": "Remboursement", + "paiementDoneTransaction": "Transaction effectuée", + "paiementRefundAction": "Rembourser", + "paiementTotalDuringPeriod": "Total sur la période", + "paiementMean": "Moyenne : ", + "paiementTransaction": "ransaction", + "paiementTransferStructure": "Transfert de structure", + "paiementYouAreTransferingStructureTo": "Vous êtes sur le point de transférer la structure à ", + "paiementTransferStructureDescription": "Le nouveau responsable aura accès à toutes les fonctionnalités de gestion de la structure. Vous allez recevoir un email pour valider ce transfert. Le lien ne sera actif que pendant 20 minutes. Cette action est irréversible. Êtes-vous sûr de vouloir continuer ?", + "paiementTransferStructureError": "Erreur lors du transfert de la structure", + "paiementTransferStructureSuccess": "Transfert de structure demandé avec succès", + "paiementNextAccountable": "Prochain responsable" } From c8469ecff1782f1715f33a8999434ecb79800a10 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Tue, 12 Aug 2025 22:22:03 +0200 Subject: [PATCH 258/473] fix: feed admin action --- lib/feed/ui/pages/main_page/time_line_item.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/feed/ui/pages/main_page/time_line_item.dart b/lib/feed/ui/pages/main_page/time_line_item.dart index 8063c36775..58b4c10769 100644 --- a/lib/feed/ui/pages/main_page/time_line_item.dart +++ b/lib/feed/ui/pages/main_page/time_line_item.dart @@ -96,7 +96,7 @@ class TimelineItem extends StatelessWidget { ), ), Expanded( - child: !isAdmin + child: isAdmin ? EventActionAdmin(item: item) : EventAction( title: getActionTitle(item, context), From 2152c94489b563c7400de64b4f1da1ba0c3ef0d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Robert?= <114694873+Rotheem@users.noreply.github.com> Date: Wed, 13 Aug 2025 13:42:14 +0200 Subject: [PATCH 259/473] feat: use new mypayment url (#25) --- lib/l10n/app_fr.arb | 2 +- lib/paiement/repositories/devices_repository.dart | 2 +- lib/paiement/repositories/funding_repository.dart | 2 +- lib/paiement/repositories/store_sellers_repository.dart | 2 +- lib/paiement/repositories/stores_repository.dart | 2 +- lib/paiement/repositories/structures_repository.dart | 2 +- lib/paiement/repositories/tos_repository.dart | 2 +- lib/paiement/repositories/transaction_repository.dart | 2 +- lib/paiement/repositories/users_me_repository.dart | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 3fea825738..54f3f6415f 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -1332,7 +1332,7 @@ "moduleFeedDescription": "Consulter les actualités et mises à jour", "moduleStyleGuide": "StyleGuide", "moduleStyleGuideDescription": "Explore the UI components and styles used in Titan", - "moduleAdmin": "Adminitration", + "moduleAdmin": "Admin", "moduleAdminDescription": "Gérer les utilisateurs, groupes et structures", "moduleOthers": "Autres", "moduleOthersDescription": "Afficher les autres modules", diff --git a/lib/paiement/repositories/devices_repository.dart b/lib/paiement/repositories/devices_repository.dart index a92ee5598d..977879a41f 100644 --- a/lib/paiement/repositories/devices_repository.dart +++ b/lib/paiement/repositories/devices_repository.dart @@ -7,7 +7,7 @@ import 'package:titan/tools/repository/repository.dart'; class DevicesRepository extends Repository { @override // ignore: overridden_fields - final ext = 'myeclpay/users/me/wallet/devices'; + final ext = 'mypayment/users/me/wallet/devices'; Future registerDevice(CreateDevice body) async { return WalletDevice.fromJson(await create(body.toJson())); diff --git a/lib/paiement/repositories/funding_repository.dart b/lib/paiement/repositories/funding_repository.dart index aabe1d8dca..94011224a5 100644 --- a/lib/paiement/repositories/funding_repository.dart +++ b/lib/paiement/repositories/funding_repository.dart @@ -8,7 +8,7 @@ import 'package:titan/tools/repository/repository.dart'; class FundingRepository extends Repository { @override // ignore: overridden_fields - final ext = 'myeclpay/transfer/'; + final ext = 'mypayment/transfer/'; Future getAdminPaymentUrl(Transfer transfer) async { return await create(transfer.toJson(), suffix: "admin"); diff --git a/lib/paiement/repositories/store_sellers_repository.dart b/lib/paiement/repositories/store_sellers_repository.dart index 0e8ba4bf44..810cf139e5 100644 --- a/lib/paiement/repositories/store_sellers_repository.dart +++ b/lib/paiement/repositories/store_sellers_repository.dart @@ -6,7 +6,7 @@ import 'package:titan/tools/repository/repository.dart'; class SellerStoreRepository extends Repository { @override // ignore: overridden_fields - final ext = 'myeclpay/stores'; + final ext = 'mypayment/stores'; Future createSeller(String storeId, Seller seller) async { return Seller.fromJson( diff --git a/lib/paiement/repositories/stores_repository.dart b/lib/paiement/repositories/stores_repository.dart index ec1a48b57f..7fa3f50b58 100644 --- a/lib/paiement/repositories/stores_repository.dart +++ b/lib/paiement/repositories/stores_repository.dart @@ -10,7 +10,7 @@ import 'package:titan/tools/repository/repository.dart'; class StoresRepository extends Repository { @override // ignore: overridden_fields - final ext = 'myeclpay/stores'; + final ext = 'mypayment/stores'; Future updateStore(Store store) async { return await update(store.toJson(), "/${store.id}"); diff --git a/lib/paiement/repositories/structures_repository.dart b/lib/paiement/repositories/structures_repository.dart index 4fec46b3e0..45ff5fc36c 100644 --- a/lib/paiement/repositories/structures_repository.dart +++ b/lib/paiement/repositories/structures_repository.dart @@ -7,7 +7,7 @@ import 'package:titan/tools/repository/repository.dart'; class StructuresRepository extends Repository { @override // ignore: overridden_fields - final ext = 'myeclpay/structures'; + final ext = 'mypayment/structures'; Future createStructure(Structure structure) async { return Structure.fromJson(await create(structure.toJson())); diff --git a/lib/paiement/repositories/tos_repository.dart b/lib/paiement/repositories/tos_repository.dart index b90379c14c..64f3185d07 100644 --- a/lib/paiement/repositories/tos_repository.dart +++ b/lib/paiement/repositories/tos_repository.dart @@ -6,7 +6,7 @@ import 'package:titan/tools/repository/repository.dart'; class TosRepository extends Repository { @override // ignore: overridden_fields - final ext = 'myeclpay/users/me/'; + final ext = 'mypayment/users/me/'; Future getTOS() async { return TOS.fromJson(await getOne("tos")); diff --git a/lib/paiement/repositories/transaction_repository.dart b/lib/paiement/repositories/transaction_repository.dart index b1c8dd82c0..f6d96906fd 100644 --- a/lib/paiement/repositories/transaction_repository.dart +++ b/lib/paiement/repositories/transaction_repository.dart @@ -6,7 +6,7 @@ import 'package:titan/tools/repository/repository.dart'; class TransactionsRepository extends Repository { @override // ignore: overridden_fields - final ext = 'myeclpay/transactions'; + final ext = 'mypayment/transactions'; Future refundTransaction(String transactionId, Refund refund) async { return await create(refund.toJson(), suffix: '/$transactionId/refund'); diff --git a/lib/paiement/repositories/users_me_repository.dart b/lib/paiement/repositories/users_me_repository.dart index 9c6d82e95b..e33b64b005 100644 --- a/lib/paiement/repositories/users_me_repository.dart +++ b/lib/paiement/repositories/users_me_repository.dart @@ -8,7 +8,7 @@ import 'package:titan/tools/repository/repository.dart'; class UsersMeRepository extends Repository { @override // ignore: overridden_fields - final ext = 'myeclpay/users/me/'; + final ext = 'mypayment/users/me/'; Future register() async { return await create({}, suffix: 'register'); From 15a565ed153d4eea2d8fdcff3377ddb2bca75669 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+foucblg@users.noreply.github.com> Date: Fri, 15 Aug 2025 18:33:40 +0200 Subject: [PATCH 260/473] Implement vertical clip scroll (#26) Co-authored-by: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> --- lib/admin/ui/pages/main_page/main_page.dart | 4 +- .../ui/pages/main_page/main_page.dart | 158 ++++++++++-------- .../ui/widgets/vertical_clip_scroll.dart | 31 ++++ 3 files changed, 117 insertions(+), 76 deletions(-) create mode 100644 lib/tools/ui/widgets/vertical_clip_scroll.dart diff --git a/lib/admin/ui/pages/main_page/main_page.dart b/lib/admin/ui/pages/main_page/main_page.dart index 24e5e9affb..de0b862922 100644 --- a/lib/admin/ui/pages/main_page/main_page.dart +++ b/lib/admin/ui/pages/main_page/main_page.dart @@ -4,6 +4,7 @@ import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/admin/admin.dart'; import 'package:titan/admin/providers/all_groups_list_provider.dart'; import 'package:titan/admin/router.dart'; +import 'package:titan/tools/ui/widgets/vertical_clip_scroll.dart'; import 'package:titan/admin/ui/pages/announcers/load_switch_announcers.dart'; import 'package:titan/admin/ui/pages/users_management_page/users_management_page.dart'; import 'package:titan/l10n/app_localizations.dart'; @@ -98,8 +99,7 @@ class AdminMainPage extends HookConsumerWidget { title: localizeWithContext.adminAnnouncers, child: ConstrainedBox( constraints: const BoxConstraints(maxHeight: 500), - child: SingleChildScrollView( - physics: const BouncingScrollPhysics(), + child: VerticalClipScroll( child: Column( children: [ ...groupList.map((group) { diff --git a/lib/settings/ui/pages/main_page/main_page.dart b/lib/settings/ui/pages/main_page/main_page.dart index 475be8a038..0b45d23f94 100644 --- a/lib/settings/ui/pages/main_page/main_page.dart +++ b/lib/settings/ui/pages/main_page/main_page.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/tools/ui/widgets/vertical_clip_scroll.dart'; import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/settings/providers/notification_topic_provider.dart'; import 'package:titan/settings/tools/functions.dart'; @@ -186,84 +187,93 @@ class SettingsMainPage extends HookConsumerWidget { await showCustomBottomModal( modal: BottomModalTemplate( title: localizeWithContext.settingsNotifications, - child: Consumer( - builder: (context, ref, child) { - final notificationTopicList = ref.watch( - notificationTopicListProvider, - ); - return AsyncChild( - value: notificationTopicList, - builder: (context, notificationTopicList) { - final notificationTopicsByModuleRoot = - groupNotificationTopicsByModuleRoot( - notificationTopicList, - ref, - context, - ); - final uniqueTopics = - notificationTopicsByModuleRoot['others'] ?? - []; - final groupedTopics = Map.from( - notificationTopicsByModuleRoot, - )..remove('others'); - return Column( - children: [ - ...uniqueTopics.map( - (notificationTopic) => ListItemTemplate( - title: notificationTopic.name, - trailing: LoadSwitchTopic( - notificationTopic: notificationTopic, + child: ConstrainedBox( + constraints: const BoxConstraints(maxHeight: 600), + child: Consumer( + builder: (context, ref, child) { + final notificationTopicList = ref.watch( + notificationTopicListProvider, + ); + return AsyncChild( + value: notificationTopicList, + builder: (context, notificationTopicList) { + final notificationTopicsByModuleRoot = + groupNotificationTopicsByModuleRoot( + notificationTopicList, + ref, + context, + ); + final uniqueTopics = + notificationTopicsByModuleRoot['others'] ?? + []; + final groupedTopics = Map.from( + notificationTopicsByModuleRoot, + )..remove('others'); + return VerticalClipScroll( + child: Column( + children: [ + ...uniqueTopics.map( + (notificationTopic) => ListItemTemplate( + title: notificationTopic.name, + trailing: LoadSwitchTopic( + notificationTopic: + notificationTopic, + ), + ), ), - ), - ), - ...groupedTopics.entries.map((entry) { - final moduleRoot = entry.key; - final topics = entry.value; - bool expanded = true; - return StatefulBuilder( - builder: (context, setState) { - return Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - const SizedBox(height: 20), - ListItemTemplate( - title: moduleRoot, - trailing: HeroIcon( - expanded - ? HeroIcons.chevronDown - : HeroIcons.chevronRight, - color: ColorConstants.tertiary, - ), - onTap: () { - setState(() { - expanded = !expanded; - }); - }, - ), - const SizedBox(height: 10), - if (expanded) - ...topics.map( - ( - notificationTopic, - ) => ListItemTemplate( - title: notificationTopic.name, - trailing: LoadSwitchTopic( - notificationTopic: - notificationTopic, + ...groupedTopics.entries.map((entry) { + final moduleRoot = entry.key; + final topics = entry.value; + bool expanded = true; + return StatefulBuilder( + builder: (context, setState) { + return Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + const SizedBox(height: 20), + ListItemTemplate( + title: moduleRoot, + trailing: HeroIcon( + expanded + ? HeroIcons.chevronDown + : HeroIcons + .chevronRight, + color: + ColorConstants.tertiary, ), + onTap: () { + setState(() { + expanded = !expanded; + }); + }, ), - ), - ], + const SizedBox(height: 10), + if (expanded) + ...topics.map( + ( + notificationTopic, + ) => ListItemTemplate( + title: notificationTopic + .name, + trailing: LoadSwitchTopic( + notificationTopic: + notificationTopic, + ), + ), + ), + ], + ); + }, ); - }, - ); - }), - ], - ); - }, - ); - }, + }), + ], + ), + ); + }, + ); + }, + ), ), ), context: context, diff --git a/lib/tools/ui/widgets/vertical_clip_scroll.dart b/lib/tools/ui/widgets/vertical_clip_scroll.dart new file mode 100644 index 0000000000..ec7f029fb4 --- /dev/null +++ b/lib/tools/ui/widgets/vertical_clip_scroll.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; + +class VerticalClipScroll extends StatelessWidget { + final Widget child; + + const VerticalClipScroll({super.key, required this.child}); + + @override + Widget build(BuildContext context) { + return ClipPath( + clipper: _VerticalOnlyClipper(), + child: SingleChildScrollView( + scrollDirection: Axis.vertical, + clipBehavior: Clip.none, + child: Align(alignment: Alignment.topCenter, child: child), + ), + ); + } +} + +class _VerticalOnlyClipper extends CustomClipper { + @override + Path getClip(Size size) { + const big = 1000000.0; + return Path() + ..addRect(Rect.fromLTRB(-big, 0.0, size.width + big, size.height)); + } + + @override + bool shouldReclip(covariant _VerticalOnlyClipper oldClipper) => false; +} From f129ce5ceb4fc6c319b4eeca9aa7d2d61fa681dc Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 17 Aug 2025 11:15:30 +0200 Subject: [PATCH 261/473] fix: announcer bar overflow (#28) * fix: announcer bar overflow * fix: removing clip none behavior overflow --------- Co-authored-by: Foucauld Bellanger <63885990+foucblg@users.noreply.github.com> --- lib/tools/ui/layouts/horizontal_list_view.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/tools/ui/layouts/horizontal_list_view.dart b/lib/tools/ui/layouts/horizontal_list_view.dart index b4ef4a40d9..366b2b18ec 100644 --- a/lib/tools/ui/layouts/horizontal_list_view.dart +++ b/lib/tools/ui/layouts/horizontal_list_view.dart @@ -26,7 +26,6 @@ class HorizontalListView extends StatelessWidget { lastChild = null, childDelegate = SingleChildScrollView( scrollDirection: Axis.horizontal, - clipBehavior: Clip.none, controller: scrollController, physics: const BouncingScrollPhysics(), child: Row(children: children!), @@ -47,7 +46,6 @@ class HorizontalListView extends StatelessWidget { children = null, childDelegate = ListView.builder( scrollDirection: Axis.horizontal, - clipBehavior: Clip.none, controller: scrollController, physics: const BouncingScrollPhysics(), itemCount: From cf207da4cdad3bcbcf3cb8c9d4e5ac99c151eba5 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+foucblg@users.noreply.github.com> Date: Sun, 17 Aug 2025 16:30:05 +0200 Subject: [PATCH 262/473] Account deletion and disconnect (#32) * adding delete and disconnect option * Translations --------- Co-authored-by: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> --- lib/l10n/app_en.arb | 8 ++ lib/l10n/app_fr.arb | 8 ++ lib/l10n/app_localizations.dart | 48 ++++++++++++ lib/l10n/app_localizations_en.dart | 28 +++++++ lib/l10n/app_localizations_fr.dart | 28 +++++++ .../ui/pages/main_page/main_page.dart | 74 +++++++++++++++++++ 6 files changed, 194 insertions(+) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 0a2bb3f516..dcc6ea19f7 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1210,6 +1210,14 @@ "settingsSynncWithCalendar": "Sync with calendar", "settingsIcalLinkCopied": "Ical link copied", "settingsProfile": "Profile", + "settingsConnexion": "Connection", + "settingsDisconnect": "Disconnect", + "settingsDisconnectDescription": "Do you really want to disconnect?", + "settingsDisconnectionSuccess": "Disconnected successfully", + "settingsDeleteMyAccount": "Delete my account", + "settingsDeleteMyAccountDescription": "This action will send a request to the administrator to delete your account.", + "settingsDeletionAsked" : "Your account deletion request has been sent to the administrator.", + "settingsDeleteMyAccountError": "Error sending account deletion request", "voteAdd": "Add", "voteAddMember": "Add a member", "voteAddedPretendance": "List added", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 54f3f6415f..28727051f4 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -1217,6 +1217,14 @@ "settingsSynncWithCalendar": "Synchroniser avec votre calendrier", "settingsIcalLinkCopied": "Lien Ical copié dans le presse-papier", "settingsProfile": "Profil", + "settingsConnexion": "Connexion", + "settingsDisconnect": "Se déconnecter", + "settingsDisconnectDescription": "Êtes-vous sûr de vouloir vous déconnecter ?", + "settingsDisconnectionSuccess": "Déconnexion réussie", + "settingsDeleteMyAccount": "Supprimer mon compte", + "settingsDeleteMyAccountDescription": "Cette action notifie l'administrateur que vous souhaitez supprimer votre compte.", + "settingsDeletionAsked" : "Demande de suppression de compte envoyée", + "settingsDeleteMyAccountError": "Erreur lors de la demande de suppression de compte", "voteAdd": "Ajouter", "voteAddMember": "Ajouter un membre", "voteAddedPretendance": "Liste ajoutée", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 3f4d27fa5e..2ea194cf65 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -6806,6 +6806,54 @@ abstract class AppLocalizations { /// **'Profil'** String get settingsProfile; + /// No description provided for @settingsConnexion. + /// + /// In fr, this message translates to: + /// **'Connexion'** + String get settingsConnexion; + + /// No description provided for @settingsDisconnect. + /// + /// In fr, this message translates to: + /// **'Se déconnecter'** + String get settingsDisconnect; + + /// No description provided for @settingsDisconnectDescription. + /// + /// In fr, this message translates to: + /// **'Êtes-vous sûr de vouloir vous déconnecter ?'** + String get settingsDisconnectDescription; + + /// No description provided for @settingsDisconnectionSuccess. + /// + /// In fr, this message translates to: + /// **'Déconnexion réussie'** + String get settingsDisconnectionSuccess; + + /// No description provided for @settingsDeleteMyAccount. + /// + /// In fr, this message translates to: + /// **'Supprimer mon compte'** + String get settingsDeleteMyAccount; + + /// No description provided for @settingsDeleteMyAccountDescription. + /// + /// In fr, this message translates to: + /// **'Cette action notifie l\'administrateur que vous souhaitez supprimer votre compte.'** + String get settingsDeleteMyAccountDescription; + + /// No description provided for @settingsDeletionAsked. + /// + /// In fr, this message translates to: + /// **'Demande de suppression de compte envoyée'** + String get settingsDeletionAsked; + + /// No description provided for @settingsDeleteMyAccountError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de la demande de suppression de compte'** + String get settingsDeleteMyAccountError; + /// No description provided for @voteAdd. /// /// In fr, this message translates to: diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 20e8be8f08..1308b82bcb 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -3457,6 +3457,34 @@ class AppLocalizationsEn extends AppLocalizations { @override String get settingsProfile => 'Profile'; + @override + String get settingsConnexion => 'Connection'; + + @override + String get settingsDisconnect => 'Disconnect'; + + @override + String get settingsDisconnectDescription => + 'Do you really want to disconnect?'; + + @override + String get settingsDisconnectionSuccess => 'Disconnected successfully'; + + @override + String get settingsDeleteMyAccount => 'Delete my account'; + + @override + String get settingsDeleteMyAccountDescription => + 'This action will send a request to the administrator to delete your account.'; + + @override + String get settingsDeletionAsked => + 'Your account deletion request has been sent to the administrator.'; + + @override + String get settingsDeleteMyAccountError => + 'Error sending account deletion request'; + @override String get voteAdd => 'Add'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index e9107dbbff..431c44ad08 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -3485,6 +3485,34 @@ class AppLocalizationsFr extends AppLocalizations { @override String get settingsProfile => 'Profil'; + @override + String get settingsConnexion => 'Connexion'; + + @override + String get settingsDisconnect => 'Se déconnecter'; + + @override + String get settingsDisconnectDescription => + 'Êtes-vous sûr de vouloir vous déconnecter ?'; + + @override + String get settingsDisconnectionSuccess => 'Déconnexion réussie'; + + @override + String get settingsDeleteMyAccount => 'Supprimer mon compte'; + + @override + String get settingsDeleteMyAccountDescription => + 'Cette action notifie l\'administrateur que vous souhaitez supprimer votre compte.'; + + @override + String get settingsDeletionAsked => + 'Demande de suppression de compte envoyée'; + + @override + String get settingsDeleteMyAccountError => + 'Erreur lors de la demande de suppression de compte'; + @override String get voteAdd => 'Ajouter'; diff --git a/lib/settings/ui/pages/main_page/main_page.dart b/lib/settings/ui/pages/main_page/main_page.dart index 0b45d23f94..dd1e68e0c4 100644 --- a/lib/settings/ui/pages/main_page/main_page.dart +++ b/lib/settings/ui/pages/main_page/main_page.dart @@ -1,7 +1,12 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/auth/providers/openid_provider.dart'; +import 'package:titan/service/providers/firebase_token_expiration_provider.dart'; +import 'package:titan/service/providers/messages_provider.dart'; +import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; import 'package:titan/tools/ui/widgets/vertical_clip_scroll.dart'; import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/settings/providers/notification_topic_provider.dart'; @@ -37,6 +42,8 @@ class SettingsMainPage extends HookConsumerWidget { final notificationTopicList = ref.watch(notificationTopicListProvider); final meNotifier = ref.watch(asyncUserProvider.notifier); final profilePicture = ref.watch(profilePictureProvider); + final auth = ref.watch(authTokenProvider.notifier); + final isCachingNotifier = ref.watch(isCachingProvider.notifier); final results = notificationTopicList.when( data: (data) { @@ -309,6 +316,73 @@ class SettingsMainPage extends HookConsumerWidget { }, ), const SizedBox(height: 20), + Text( + localizeWithContext.settingsConnexion, + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: ColorConstants.title, + ), + ), + ListItem( + title: localizeWithContext.settingsDisconnect, + onTap: () async { + await showDialog( + context: context, + builder: (context) { + return CustomDialogBox( + descriptions: + localizeWithContext.settingsDisconnectDescription, + title: localizeWithContext.settingsDisconnect, + onYes: () { + auth.deleteToken(); + if (!kIsWeb) { + ref.watch(messagesProvider.notifier).forgetDevice(); + ref + .watch(firebaseTokenExpirationProvider.notifier) + .reset(); + } + isCachingNotifier.set(false); + displayToast( + context, + TypeMsg.msg, + localizeWithContext.settingsDisconnectionSuccess, + ); + }, + ); + }, + ); + }, + ), + ListItem( + title: localizeWithContext.settingsDeleteMyAccount, + onTap: () async { + await showDialog( + context: context, + builder: (context) { + return CustomDialogBox( + descriptions: localizeWithContext + .settingsDeleteMyAccountDescription, + title: localizeWithContext.settingsDeleteMyAccount, + onYes: () async { + final value = await meNotifier.deletePersonal(); + if (value) { + displayToastWithContext( + TypeMsg.msg, + localizeWithContext.settingsDeletionAsked, + ); + } else { + displayToastWithContext( + TypeMsg.error, + localizeWithContext.settingsDeleteMyAccountError, + ); + } + }, + ); + }, + ); + }, + ), ], ), ), From daa08451464bb289d9e4a369e33baaece178713a Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Fri, 15 Aug 2025 16:48:04 +0200 Subject: [PATCH 263/473] remove navbar on keyboard opening --- lib/navigation/ui/navigation_template.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/navigation/ui/navigation_template.dart b/lib/navigation/ui/navigation_template.dart index cc89f33d4b..a868bd161d 100644 --- a/lib/navigation/ui/navigation_template.dart +++ b/lib/navigation/ui/navigation_template.dart @@ -51,6 +51,8 @@ class NavigationTemplate extends HookConsumerWidget { } }); + MediaQuery.of(context).viewInsets.bottom; + return Builder( builder: (context) { return Scaffold( @@ -77,7 +79,8 @@ class NavigationTemplate extends HookConsumerWidget { builder: (context, child) => Visibility( visible: animation.isCompleted && - animation.value == 1.0, + animation.value == 1.0 && + View.of(context).viewInsets.bottom == 0, child: Opacity( opacity: animation.value, child: FloatingNavbar( From 10abc938523a2bdbc15ccad7933cba28cb61bcf0 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Fri, 15 Aug 2025 18:32:27 +0200 Subject: [PATCH 264/473] Remove useless provider --- .../providers/admin_advert_list_provider.dart | 39 ------------------- .../ui/pages/admin_page/admin_page.dart | 24 ++++++------ 2 files changed, 11 insertions(+), 52 deletions(-) delete mode 100644 lib/advert/providers/admin_advert_list_provider.dart diff --git a/lib/advert/providers/admin_advert_list_provider.dart b/lib/advert/providers/admin_advert_list_provider.dart deleted file mode 100644 index 40ac1c94f9..0000000000 --- a/lib/advert/providers/admin_advert_list_provider.dart +++ /dev/null @@ -1,39 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/auth/providers/openid_provider.dart'; -import 'package:titan/advert/class/advert.dart'; -import 'package:titan/advert/repositories/advert_repository.dart'; -import 'package:titan/tools/providers/list_notifier.dart'; -import 'package:titan/tools/token_expire_wrapper.dart'; - -class AdminAdvertListNotifier extends ListNotifier { - AdvertRepository repository = AdvertRepository(); - AdminAdvertListNotifier({required String token}) - : super(const AsyncValue.loading()) { - repository.setToken(token); - } - - Future>> loadAdverts() async { - return await loadList(repository.getAllAdminAdvert); - } - - Future deleteAdvert(Advert advert) async { - return await delete( - repository.deleteAdvert, - (adverts, advert) => adverts..removeWhere((b) => b.id == advert.id), - advert.id, - advert, - ); - } -} - -final adminAdvertListProvider = - StateNotifierProvider>>(( - ref, - ) { - final token = ref.watch(tokenProvider); - AdminAdvertListNotifier notifier = AdminAdvertListNotifier(token: token); - tokenExpireWrapperAuth(ref, () async { - await notifier.loadAdverts(); - }); - return notifier; - }); diff --git a/lib/advert/ui/pages/admin_page/admin_page.dart b/lib/advert/ui/pages/admin_page/admin_page.dart index 4b32d96e5a..6fde598098 100644 --- a/lib/advert/ui/pages/admin_page/admin_page.dart +++ b/lib/advert/ui/pages/admin_page/admin_page.dart @@ -4,7 +4,6 @@ import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/providers/is_admin_provider.dart'; import 'package:titan/advert/class/advert.dart'; -import 'package:titan/advert/providers/admin_advert_list_provider.dart'; import 'package:titan/advert/providers/advert_list_provider.dart'; import 'package:titan/advert/providers/advert_posters_provider.dart'; import 'package:titan/advert/providers/advert_provider.dart'; @@ -31,9 +30,7 @@ class AdvertAdminPage extends HookConsumerWidget { final advertNotifier = ref.watch(advertProvider.notifier); final isAdmin = ref.watch(isAdminProvider); final isAdvertAdmin = ref.watch(isAdvertAdminProvider); - final advertList = isAdmin - ? ref.watch(adminAdvertListProvider) - : ref.watch(advertListProvider); + final advertList = ref.watch(advertListProvider); final userAnnouncerListNotifier = ref.watch( userAnnouncerListProvider.notifier, ); @@ -41,7 +38,6 @@ class AdvertAdminPage extends HookConsumerWidget { final advertPostersNotifier = ref.watch(advertPostersProvider.notifier); final selectedAnnouncers = ref.watch(announcerProvider); final selectedAnnouncersNotifier = ref.read(announcerProvider.notifier); - final selectedNotifier = ref.read(announcerProvider.notifier); final userAnnouncersSync = userAnnouncerList.maybeWhen( orElse: () => [], data: (data) => data, @@ -70,7 +66,9 @@ class AdvertAdminPage extends HookConsumerWidget { advertNotifier.setAdvert(Advert.empty()); if (userAnnouncersSync.length == 1 && selectedAnnouncers.isEmpty) { - selectedNotifier.addAnnouncer(userAnnouncersSync[0]); + selectedAnnouncersNotifier.addAnnouncer( + userAnnouncersSync[0], + ); } QR.to( AdvertRouter.root + @@ -96,9 +94,11 @@ class AdvertAdminPage extends HookConsumerWidget { value: userAnnouncerList, builder: (context, userAnnouncerData) { final userAnnouncerAdvert = advertData.where( - (advert) => userAnnouncerData - .where((element) => advert.announcer.id == element.id) - .isNotEmpty, + (advert) => !isAdmin + ? userAnnouncerData.any( + (element) => advert.announcer.id == element.id, + ) + : true, ); final sortedUserAnnouncerAdverts = userAnnouncerAdvert .toList() @@ -119,7 +119,7 @@ class AdvertAdminPage extends HookConsumerWidget { onRefresh: () async { if (isAdmin) { await ref - .watch(adminAdvertListProvider.notifier) + .watch(advertListProvider.notifier) .loadAdverts(); } await ref @@ -158,9 +158,7 @@ class AdvertAdminPage extends HookConsumerWidget { onYes: () async { if (isAdmin) { await ref - .watch( - adminAdvertListProvider.notifier, - ) + .watch(advertListProvider.notifier) .deleteAdvert(advert); } else { await ref From 064d11e4f691b08a3e511f7d35801f84ad93f070 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Fri, 15 Aug 2025 18:45:31 +0200 Subject: [PATCH 265/473] authorize edition of my adverts --- .../pages/admin_page/admin_advert_card.dart | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/lib/advert/ui/pages/admin_page/admin_advert_card.dart b/lib/advert/ui/pages/admin_page/admin_advert_card.dart index 72ac479d76..19b0820cd9 100644 --- a/lib/advert/ui/pages/admin_page/admin_advert_card.dart +++ b/lib/advert/ui/pages/admin_page/admin_advert_card.dart @@ -3,6 +3,7 @@ import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/advert/class/advert.dart'; import 'package:timeago/timeago.dart' as timeago; +import 'package:titan/advert/providers/announcer_list_provider.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/styleguide/icon_button.dart'; @@ -20,6 +21,11 @@ class AdminAdvertCard extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final userAnnouncerList = ref.watch(userAnnouncerListProvider); + final userAnnouncersIdListSync = userAnnouncerList.maybeWhen( + orElse: () => [], + data: (data) => data.map((e) => e.id).toList(), + ); return Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: Container( @@ -52,13 +58,14 @@ class AdminAdvertCard extends HookConsumerWidget { ], ), const Spacer(), - CustomIconButton.secondary( - onPressed: onEdit, - icon: const HeroIcon( - HeroIcons.pencil, - color: ColorConstants.tertiary, + if (userAnnouncersIdListSync.contains(advert.announcer.id)) + CustomIconButton.secondary( + onPressed: onEdit, + icon: const HeroIcon( + HeroIcons.pencil, + color: ColorConstants.tertiary, + ), ), - ), const SizedBox(width: 20), CustomIconButton.danger( onPressed: onDelete, From 792c2725129b362c49b1b59b4e540654cd229532 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Fri, 15 Aug 2025 19:14:45 +0200 Subject: [PATCH 266/473] remove tags --- lib/advert/class/advert.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/advert/class/advert.dart b/lib/advert/class/advert.dart index a089a22511..1ce090ffc4 100644 --- a/lib/advert/class/advert.dart +++ b/lib/advert/class/advert.dart @@ -31,8 +31,6 @@ class Advert { data["content"] = content; data["date"] = processDateToAPI(date); data["advertiser_id"] = announcer.id; - // TODO: waiting backend migration - data["tags"] = ""; return data; } From 7f9d6b2d1b1b053defa52331831fcce10517d965 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Fri, 15 Aug 2025 16:29:15 +0200 Subject: [PATCH 267/473] Association # Conflicts: # lib/admin/ui/pages/main_page/main_page.dart --- lib/admin/class/assocation.dart | 38 ++++ .../providers/assocation_list_provider.dart | 63 ++++++ .../providers/assocation_logo_provider.dart | 45 ++++ .../providers/association_logo_list.dart | 17 ++ .../association_logo_repository.dart | 25 +++ .../repositories/association_repository.dart | 37 ++++ lib/admin/router.dart | 9 +- .../announcers/load_switch_announcers.dart | 112 ---------- .../association_page/association_page.dart | 11 + lib/admin/ui/pages/main_page/main_page.dart | 39 +--- lib/feed/repositories/news_repository.dart | 192 +++++++++--------- 11 files changed, 345 insertions(+), 243 deletions(-) create mode 100644 lib/admin/class/assocation.dart create mode 100644 lib/admin/providers/assocation_list_provider.dart create mode 100644 lib/admin/providers/assocation_logo_provider.dart create mode 100644 lib/admin/providers/association_logo_list.dart create mode 100644 lib/admin/repositories/association_logo_repository.dart create mode 100644 lib/admin/repositories/association_repository.dart delete mode 100644 lib/admin/ui/pages/announcers/load_switch_announcers.dart create mode 100644 lib/admin/ui/pages/association_page/association_page.dart diff --git a/lib/admin/class/assocation.dart b/lib/admin/class/assocation.dart new file mode 100644 index 0000000000..5e6151dcdb --- /dev/null +++ b/lib/admin/class/assocation.dart @@ -0,0 +1,38 @@ +class Association { + Association({required this.name, required this.groupId, required this.id}); + late final String name; + late final String groupId; + late final String id; + + Association.fromJson(Map json) { + name = json['name']; + groupId = json['groupId']; + id = json['id']; + } + + Map toJson() { + final data = {}; + data['name'] = name; + data['groupId'] = groupId; + data['id'] = id; + return data; + } + + Association copyWith({String? name, String? groupId, String? id}) => + Association( + name: name ?? this.name, + groupId: groupId ?? this.groupId, + id: id ?? this.id, + ); + + Association.empty() { + name = 'Nom'; + groupId = 'Description'; + id = ''; + } + + @override + String toString() { + return 'Association(name: $name, groupId: $groupId, id: $id)'; + } +} diff --git a/lib/admin/providers/assocation_list_provider.dart b/lib/admin/providers/assocation_list_provider.dart new file mode 100644 index 0000000000..6702b5abd0 --- /dev/null +++ b/lib/admin/providers/assocation_list_provider.dart @@ -0,0 +1,63 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/admin/class/assocation.dart'; +import 'package:titan/admin/repositories/association_repository.dart'; +import 'package:titan/tools/providers/list_notifier.dart'; +import 'package:titan/tools/token_expire_wrapper.dart'; + +class AssociationListNotifier extends ListNotifier { + final AssociationRepository associationRepository; + AssociationListNotifier({required this.associationRepository}) + : super(const AsyncValue.loading()); + + Future>> loadAssociations() async { + return await loadList(associationRepository.getAssociationList); + } + + Future createAssociation(Association association) async { + return await add(associationRepository.createAssociation, association); + } + + Future updateAssociation(Association association) async { + return await update( + associationRepository.updateAssociation, + (associations, association) => associations + ..[associations.indexWhere((g) => g.id == association.id)] = + association, + association, + ); + } + + Future deleteAssociation(Association association) async { + return await delete( + associationRepository.deleteAssociation, + (associations, association) => + associations..removeWhere((i) => i.id == association.id), + association.id, + association, + ); + } + + void setAssociation(Association association) { + state.whenData((d) { + if (d.indexWhere((g) => g.id == association.id) == -1) return; + state = AsyncValue.data( + d..[d.indexWhere((g) => g.id == association.id)] = association, + ); + }); + } +} + +final associationListProvider = + StateNotifierProvider< + AssociationListNotifier, + AsyncValue> + >((ref) { + final associationRepository = ref.watch(associationRepositoryProvider); + AssociationListNotifier provider = AssociationListNotifier( + associationRepository: associationRepository, + ); + tokenExpireWrapperAuth(ref, () async { + await provider.loadAssociations(); + }); + return provider; + }); diff --git a/lib/admin/providers/assocation_logo_provider.dart b/lib/admin/providers/assocation_logo_provider.dart new file mode 100644 index 0000000000..1a0d895cd8 --- /dev/null +++ b/lib/admin/providers/assocation_logo_provider.dart @@ -0,0 +1,45 @@ +import 'dart:typed_data'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/admin/providers/association_logo_list.dart'; +import 'package:titan/admin/repositories/association_logo_repository.dart'; +import 'package:titan/auth/providers/openid_provider.dart'; +import 'package:titan/tools/providers/single_notifier.dart'; + +class AssociationLogoNotifier extends SingleNotifier { + final associationLogoRepository = AssociationLogoRepository(); + final AssociationLogoListNotifier associationLogoListNotifier; + AssociationLogoNotifier({ + required String token, + required this.associationLogoListNotifier, + }) : super(const AsyncValue.loading()) { + associationLogoRepository.setToken(token); + } + + Future getAssociationLogo(String id) async { + final image = await associationLogoRepository.getAssociationLogo(id); + associationLogoListNotifier.setTData(id, AsyncData([image])); + return image; + } + + Future updateAssociationLogo(String id, Uint8List bytes) async { + associationLogoListNotifier.setTData(id, const AsyncLoading()); + final image = await associationLogoRepository.addAssociationLogo(bytes, id); + associationLogoListNotifier.setTData(id, AsyncData([image])); + return image; + } +} + +final associationLogoProvider = + StateNotifierProvider>((ref) { + final token = ref.watch(tokenProvider); + final associationLogoListNotifier = ref.watch( + associationLogoListProvider.notifier, + ); + return AssociationLogoNotifier( + token: token, + associationLogoListNotifier: associationLogoListNotifier, + ); + }); diff --git a/lib/admin/providers/association_logo_list.dart b/lib/admin/providers/association_logo_list.dart new file mode 100644 index 0000000000..0ac30face6 --- /dev/null +++ b/lib/admin/providers/association_logo_list.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/tools/providers/map_provider.dart'; + +class AssociationLogoListNotifier extends MapNotifier { + AssociationLogoListNotifier() : super(); +} + +final associationLogoListProvider = + StateNotifierProvider< + AssociationLogoListNotifier, + Map>?> + >((ref) { + AssociationLogoListNotifier associationLogoNotifier = + AssociationLogoListNotifier(); + return associationLogoNotifier; + }); diff --git a/lib/admin/repositories/association_logo_repository.dart b/lib/admin/repositories/association_logo_repository.dart new file mode 100644 index 0000000000..70e740758e --- /dev/null +++ b/lib/admin/repositories/association_logo_repository.dart @@ -0,0 +1,25 @@ +import 'dart:async'; +import 'dart:typed_data'; + +import 'package:flutter/material.dart'; +import 'package:titan/tools/functions.dart'; +import 'package:titan/tools/repository/logo_repository.dart'; + +class AssociationLogoRepository extends LogoRepository { + @override + // ignore: overridden_fields + final ext = "association/"; + + Future getAssociationLogo(String id) async { + final uint8List = await getLogo("", suffix: "associations/$id/logo"); + if (uint8List.isEmpty) { + return Image.asset(getTitanLogo()); + } + return Image.memory(uint8List); + } + + Future addAssociationLogo(Uint8List bytes, String id) async { + final uint8List = await addLogo(bytes, "", suffix: "associations/$id/logo"); + return Image.memory(uint8List); + } +} diff --git a/lib/admin/repositories/association_repository.dart b/lib/admin/repositories/association_repository.dart new file mode 100644 index 0000000000..ade3cd5577 --- /dev/null +++ b/lib/admin/repositories/association_repository.dart @@ -0,0 +1,37 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/admin/class/assocation.dart'; +import 'package:titan/auth/providers/openid_provider.dart'; +import 'package:titan/tools/repository/repository.dart'; + +class AssociationRepository extends Repository { + @override + // ignore: overridden_fields + final ext = "associations/"; + + Future> getAssociationList() async { + return List.from( + (await getList()).map((x) => Association.fromJson(x)), + ); + } + + Future getMyAssociation() async { + return Association.fromJson(await getOne("", suffix: "me")); + } + + Future deleteAssociation(String associationId) async { + return await delete(associationId); + } + + Future updateAssociation(Association association) async { + return await update(association.toJson(), association.id); + } + + Future createAssociation(Association association) async { + return Association.fromJson(await create(association.toJson())); + } +} + +final associationRepositoryProvider = Provider((ref) { + final token = ref.watch(tokenProvider); + return AssociationRepository()..setToken(token); +}); diff --git a/lib/admin/router.dart b/lib/admin/router.dart index 20f6db9aac..d3af7a4b0e 100644 --- a/lib/admin/router.dart +++ b/lib/admin/router.dart @@ -22,6 +22,8 @@ import 'package:titan/admin/ui/pages/membership/association_membership_detail_pa deferred as association_membership_detail_page; import 'package:titan/admin/ui/pages/membership/add_edit_user_membership_page/add_edit_user_membership_page.dart' deferred as add_edit_user_membership_page; +import 'package:titan/admin/ui/pages/association_page/association_page.dart' + deferred as association_page; import 'package:titan/tools/middlewares/authenticated_middleware.dart'; import 'package:titan/tools/middlewares/deferred_middleware.dart'; import 'package:qlevar_router/qlevar_router.dart'; @@ -40,7 +42,7 @@ class AdminRouter { static const String detailAssociationMembership = '/detail_association_membership'; static const String addEditMember = '/add_edit_member'; - static const String advertisers = '/advertisers'; + static const String association = '/association'; static final Module module = Module( getName: (context) => AppLocalizations.of(context)!.moduleAdmin, getDescription: (context) => @@ -136,6 +138,11 @@ class AdminRouter { ), ], ), + QRoute( + path: association, + builder: () => association_page.AssociationPage(), + middleware: [DeferredLoadingMiddleware(association_page.loadLibrary)], + ), ], ); } diff --git a/lib/admin/ui/pages/announcers/load_switch_announcers.dart b/lib/admin/ui/pages/announcers/load_switch_announcers.dart deleted file mode 100644 index 17a6ff2560..0000000000 --- a/lib/admin/ui/pages/announcers/load_switch_announcers.dart +++ /dev/null @@ -1,112 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:load_switch/load_switch.dart'; -import 'package:titan/admin/class/simple_group.dart'; -import 'package:titan/advert/class/announcer.dart'; -import 'package:titan/advert/providers/announcer_list_provider.dart'; -import 'package:titan/l10n/app_localizations.dart'; -import 'package:titan/tools/token_expire_wrapper.dart'; -import 'package:titan/tools/ui/builders/async_child.dart'; -import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; - -class LoadSwitchAdvertisers extends ConsumerWidget { - const LoadSwitchAdvertisers({super.key, required this.group}); - final SimpleGroup group; - - @override - Widget build(BuildContext context, WidgetRef ref) { - final announcerListNotifier = ref.watch(announcerListProvider.notifier); - final announcerList = ref.watch(announcerListProvider); - final localizeWithContext = AppLocalizations.of(context)!; - - return AsyncChild( - value: announcerList, - builder: (context, annoncerList) { - final annoncer = annoncerList - .where((a) => a.groupManagerId == group.id) - .firstOrNull; - final isAnnouncer = annoncerList.any( - (a) => a.groupManagerId == group.id, - ); - return LoadSwitch( - value: isAnnouncer, - future: isAnnouncer - ? () async { - return await showDialog( - context: context, - builder: (context) { - return CustomDialogBox( - title: localizeWithContext.adminDeleteAnnouncer, - descriptions: - localizeWithContext.adminDeleteAnnouncerDescription, - onYes: () { - tokenExpireWrapper(ref, () async { - final value = await announcerListNotifier - .deleteAnnouncer(annoncer!); - if (value) { - announcerListNotifier.loadAllAnnouncerList(); - return false; - } - return true; - }); - }, - ); - }, - ); - } - : () async { - await announcerListNotifier.addAnnouncer( - Announcer( - groupManagerId: group.id, - id: '', - name: group.name, - ), - ); - return true; - }, - height: 30, - width: 60, - curveIn: Curves.easeInBack, - curveOut: Curves.easeOutBack, - animationDuration: const Duration(milliseconds: 500), - switchDecoration: (value, _) => BoxDecoration( - color: value - ? Colors.red.withValues(alpha: 0.3) - : Colors.grey.shade200, - borderRadius: BorderRadius.circular(30), - shape: BoxShape.rectangle, - boxShadow: [ - BoxShadow( - color: value - ? Colors.red.withValues(alpha: 0.2) - : Colors.grey.withValues(alpha: 0.2), - spreadRadius: 1, - blurRadius: 3, - offset: const Offset(0, 1), - ), - ], - ), - spinColor: (value) => value ? Colors.red : Colors.grey, - spinStrokeWidth: 2, - thumbDecoration: (value, _) => BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(30), - shape: BoxShape.rectangle, - boxShadow: [ - BoxShadow( - color: value - ? Colors.red.withValues(alpha: 0.2) - : Colors.grey.shade200.withValues(alpha: 0.2), - spreadRadius: 5, - blurRadius: 7, - offset: const Offset(0, 3), - ), - ], - ), - onChange: (v) {}, - onTap: (v) {}, - ); - }, - ); - } -} diff --git a/lib/admin/ui/pages/association_page/association_page.dart b/lib/admin/ui/pages/association_page/association_page.dart new file mode 100644 index 0000000000..b7c4ad4505 --- /dev/null +++ b/lib/admin/ui/pages/association_page/association_page.dart @@ -0,0 +1,11 @@ +import 'package:flutter/widgets.dart'; +import 'package:titan/admin/admin.dart'; + +class AssociationPage extends StatelessWidget { + const AssociationPage({super.key}); + + @override + Widget build(BuildContext context) { + return AdminTemplate(child: Text("yo")); + } +} diff --git a/lib/admin/ui/pages/main_page/main_page.dart b/lib/admin/ui/pages/main_page/main_page.dart index de0b862922..c46e24f0e0 100644 --- a/lib/admin/ui/pages/main_page/main_page.dart +++ b/lib/admin/ui/pages/main_page/main_page.dart @@ -2,15 +2,11 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/admin/admin.dart'; -import 'package:titan/admin/providers/all_groups_list_provider.dart'; import 'package:titan/admin/router.dart'; -import 'package:titan/tools/ui/widgets/vertical_clip_scroll.dart'; -import 'package:titan/admin/ui/pages/announcers/load_switch_announcers.dart'; import 'package:titan/admin/ui/pages/users_management_page/users_management_page.dart'; import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; import 'package:titan/tools/ui/styleguide/list_item.dart'; -import 'package:titan/tools/ui/styleguide/list_item_template.dart'; import 'package:titan/user/providers/user_list_provider.dart'; @@ -22,7 +18,6 @@ class AdminMainPage extends HookConsumerWidget { ref.watch(userList); final localizeWithContext = AppLocalizations.of(context)!; - final groupList = ref.watch(allGroupList); return AdminTemplate( child: Padding( @@ -84,36 +79,12 @@ class AdminMainPage extends HookConsumerWidget { onTap: () => QR.to(AdminRouter.root + AdminRouter.associationMemberships), ), - Text( - localizeWithContext.adminAdverts, - style: Theme.of(context).textTheme.titleLarge, - ), + Text("Associations", style: Theme.of(context).textTheme.titleLarge), ListItem( - title: localizeWithContext.adminAnnouncers, - subtitle: localizeWithContext.adminManageAnnouncers, - onTap: () async { - await showCustomBottomModal( - context: context, - ref: ref, - modal: BottomModalTemplate( - title: localizeWithContext.adminAnnouncers, - child: ConstrainedBox( - constraints: const BoxConstraints(maxHeight: 500), - child: VerticalClipScroll( - child: Column( - children: [ - ...groupList.map((group) { - return ListItemTemplate( - title: group.name, - trailing: LoadSwitchAdvertisers(group: group), - ); - }), - ], - ), - ), - ), - ), - ); + title: "Associations", + subtitle: "Gérer les associations", + onTap: () { + QR.to(AdminRouter.root + AdminRouter.association); }, ), ], diff --git a/lib/feed/repositories/news_repository.dart b/lib/feed/repositories/news_repository.dart index dcff66668a..2724a3b44a 100644 --- a/lib/feed/repositories/news_repository.dart +++ b/lib/feed/repositories/news_repository.dart @@ -8,54 +8,54 @@ class NewsRepository extends Repository { final ext = "feed/"; Future> getPublishedNews() async { - // return List.from( - // (await getList(suffix: "news")).map((e) => News.fromJson(e)), - // ); - return Future.value([ - News( - id: '', - title: 'Test', - start: DateTime.now().subtract(const Duration(days: 1)), - entity: 'BDE', - module: 'post', - moduleObjectId: '', - status: NewsStatus.published, - ), - News( - id: '', - title: 'Vote', - start: DateTime.now().subtract(const Duration(days: 2)), - end: DateTime.now().add(const Duration(days: 2)), - actionStart: DateTime.now().subtract(const Duration(days: 2)), - entity: 'CAA', - module: 'campagne', - moduleObjectId: '', - status: NewsStatus.published, - ), - News( - id: '', - title: 'Rewass', - start: DateTime.now().add(const Duration(days: 3)), - end: DateTime.now().add(const Duration(days: 7)), - actionStart: DateTime.now().subtract(const Duration(days: 1)), - entity: 'DBS', - module: 'event', - moduleObjectId: '', - location: 'Foyer', - status: NewsStatus.published, - ), - News( - id: '', - title: 'Test 4', - start: DateTime.now().subtract(const Duration(days: 2)), - end: DateTime.now().subtract(const Duration(days: 1)), - actionStart: DateTime.now().subtract(const Duration(days: 3)), - entity: 'DBS', - module: 'event', - moduleObjectId: '', - status: NewsStatus.published, - ), - ]); + return List.from( + (await getList(suffix: "news")).map((e) => News.fromJson(e)), + ); + // return Future.value([ + // News( + // id: '', + // title: 'Test', + // start: DateTime.now().subtract(const Duration(days: 1)), + // entity: 'BDE', + // module: 'post', + // moduleObjectId: '', + // status: NewsStatus.published, + // ), + // News( + // id: '', + // title: 'Vote', + // start: DateTime.now().subtract(const Duration(days: 2)), + // end: DateTime.now().add(const Duration(days: 2)), + // actionStart: DateTime.now().subtract(const Duration(days: 2)), + // entity: 'CAA', + // module: 'campagne', + // moduleObjectId: '', + // status: NewsStatus.published, + // ), + // News( + // id: '', + // title: 'Rewass', + // start: DateTime.now().add(const Duration(days: 3)), + // end: DateTime.now().add(const Duration(days: 7)), + // actionStart: DateTime.now().subtract(const Duration(days: 1)), + // entity: 'DBS', + // module: 'event', + // moduleObjectId: '', + // location: 'Foyer', + // status: NewsStatus.published, + // ), + // News( + // id: '', + // title: 'Test 4', + // start: DateTime.now().subtract(const Duration(days: 2)), + // end: DateTime.now().subtract(const Duration(days: 1)), + // actionStart: DateTime.now().subtract(const Duration(days: 3)), + // entity: 'DBS', + // module: 'event', + // moduleObjectId: '', + // status: NewsStatus.published, + // ), + // ]); } Future createNews(News news) async { @@ -63,54 +63,54 @@ class NewsRepository extends Repository { } Future> getAllNews() async { - // return List.from( - // (await getList(suffix: "admin/news")).map((e) => News.fromJson(e)), - // ); - return Future.value([ - News( - id: '', - title: 'Test', - start: DateTime.now().subtract(const Duration(days: 1)), - entity: 'BDE', - module: 'post', - moduleObjectId: '', - status: NewsStatus.waitingApproval, - ), - News( - id: '', - title: 'Vote', - start: DateTime.now().subtract(const Duration(days: 2)), - end: DateTime.now().add(const Duration(days: 2)), - actionStart: DateTime.now().subtract(const Duration(days: 2)), - entity: 'CAA', - module: 'campagne', - moduleObjectId: '', - status: NewsStatus.waitingApproval, - ), - News( - id: '', - title: 'Rewass', - start: DateTime.now().add(const Duration(days: 3)), - end: DateTime.now().add(const Duration(days: 7)), - actionStart: DateTime.now().subtract(const Duration(days: 1)), - entity: 'DBS', - module: 'event', - moduleObjectId: '', - location: 'Foyer', - status: NewsStatus.waitingApproval, - ), - News( - id: '', - title: 'Test 4', - start: DateTime.now().subtract(const Duration(days: 2)), - end: DateTime.now().subtract(const Duration(days: 1)), - actionStart: DateTime.now().subtract(const Duration(days: 3)), - entity: 'DBS', - module: 'event', - moduleObjectId: '', - status: NewsStatus.waitingApproval, - ), - ]); + return List.from( + (await getList(suffix: "admin/news")).map((e) => News.fromJson(e)), + ); + // return Future.value([ + // News( + // id: '', + // title: 'Test', + // start: DateTime.now().subtract(const Duration(days: 1)), + // entity: 'BDE', + // module: 'post', + // moduleObjectId: '', + // status: NewsStatus.waitingApproval, + // ), + // News( + // id: '', + // title: 'Vote', + // start: DateTime.now().subtract(const Duration(days: 2)), + // end: DateTime.now().add(const Duration(days: 2)), + // actionStart: DateTime.now().subtract(const Duration(days: 2)), + // entity: 'CAA', + // module: 'campagne', + // moduleObjectId: '', + // status: NewsStatus.waitingApproval, + // ), + // News( + // id: '', + // title: 'Rewass', + // start: DateTime.now().add(const Duration(days: 3)), + // end: DateTime.now().add(const Duration(days: 7)), + // actionStart: DateTime.now().subtract(const Duration(days: 1)), + // entity: 'DBS', + // module: 'event', + // moduleObjectId: '', + // location: 'Foyer', + // status: NewsStatus.waitingApproval, + // ), + // News( + // id: '', + // title: 'Test 4', + // start: DateTime.now().subtract(const Duration(days: 2)), + // end: DateTime.now().subtract(const Duration(days: 1)), + // actionStart: DateTime.now().subtract(const Duration(days: 3)), + // entity: 'DBS', + // module: 'event', + // moduleObjectId: '', + // status: NewsStatus.waitingApproval, + // ), + // ]); } Future approveNews(String id) async { From 7b766d4163c0406bbf236d84784289e7c15495f2 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Fri, 15 Aug 2025 19:14:31 +0200 Subject: [PATCH 268/473] wrong branch --- lib/feed/repositories/news_repository.dart | 192 ++++++++++----------- 1 file changed, 96 insertions(+), 96 deletions(-) diff --git a/lib/feed/repositories/news_repository.dart b/lib/feed/repositories/news_repository.dart index 2724a3b44a..dcff66668a 100644 --- a/lib/feed/repositories/news_repository.dart +++ b/lib/feed/repositories/news_repository.dart @@ -8,54 +8,54 @@ class NewsRepository extends Repository { final ext = "feed/"; Future> getPublishedNews() async { - return List.from( - (await getList(suffix: "news")).map((e) => News.fromJson(e)), - ); - // return Future.value([ - // News( - // id: '', - // title: 'Test', - // start: DateTime.now().subtract(const Duration(days: 1)), - // entity: 'BDE', - // module: 'post', - // moduleObjectId: '', - // status: NewsStatus.published, - // ), - // News( - // id: '', - // title: 'Vote', - // start: DateTime.now().subtract(const Duration(days: 2)), - // end: DateTime.now().add(const Duration(days: 2)), - // actionStart: DateTime.now().subtract(const Duration(days: 2)), - // entity: 'CAA', - // module: 'campagne', - // moduleObjectId: '', - // status: NewsStatus.published, - // ), - // News( - // id: '', - // title: 'Rewass', - // start: DateTime.now().add(const Duration(days: 3)), - // end: DateTime.now().add(const Duration(days: 7)), - // actionStart: DateTime.now().subtract(const Duration(days: 1)), - // entity: 'DBS', - // module: 'event', - // moduleObjectId: '', - // location: 'Foyer', - // status: NewsStatus.published, - // ), - // News( - // id: '', - // title: 'Test 4', - // start: DateTime.now().subtract(const Duration(days: 2)), - // end: DateTime.now().subtract(const Duration(days: 1)), - // actionStart: DateTime.now().subtract(const Duration(days: 3)), - // entity: 'DBS', - // module: 'event', - // moduleObjectId: '', - // status: NewsStatus.published, - // ), - // ]); + // return List.from( + // (await getList(suffix: "news")).map((e) => News.fromJson(e)), + // ); + return Future.value([ + News( + id: '', + title: 'Test', + start: DateTime.now().subtract(const Duration(days: 1)), + entity: 'BDE', + module: 'post', + moduleObjectId: '', + status: NewsStatus.published, + ), + News( + id: '', + title: 'Vote', + start: DateTime.now().subtract(const Duration(days: 2)), + end: DateTime.now().add(const Duration(days: 2)), + actionStart: DateTime.now().subtract(const Duration(days: 2)), + entity: 'CAA', + module: 'campagne', + moduleObjectId: '', + status: NewsStatus.published, + ), + News( + id: '', + title: 'Rewass', + start: DateTime.now().add(const Duration(days: 3)), + end: DateTime.now().add(const Duration(days: 7)), + actionStart: DateTime.now().subtract(const Duration(days: 1)), + entity: 'DBS', + module: 'event', + moduleObjectId: '', + location: 'Foyer', + status: NewsStatus.published, + ), + News( + id: '', + title: 'Test 4', + start: DateTime.now().subtract(const Duration(days: 2)), + end: DateTime.now().subtract(const Duration(days: 1)), + actionStart: DateTime.now().subtract(const Duration(days: 3)), + entity: 'DBS', + module: 'event', + moduleObjectId: '', + status: NewsStatus.published, + ), + ]); } Future createNews(News news) async { @@ -63,54 +63,54 @@ class NewsRepository extends Repository { } Future> getAllNews() async { - return List.from( - (await getList(suffix: "admin/news")).map((e) => News.fromJson(e)), - ); - // return Future.value([ - // News( - // id: '', - // title: 'Test', - // start: DateTime.now().subtract(const Duration(days: 1)), - // entity: 'BDE', - // module: 'post', - // moduleObjectId: '', - // status: NewsStatus.waitingApproval, - // ), - // News( - // id: '', - // title: 'Vote', - // start: DateTime.now().subtract(const Duration(days: 2)), - // end: DateTime.now().add(const Duration(days: 2)), - // actionStart: DateTime.now().subtract(const Duration(days: 2)), - // entity: 'CAA', - // module: 'campagne', - // moduleObjectId: '', - // status: NewsStatus.waitingApproval, - // ), - // News( - // id: '', - // title: 'Rewass', - // start: DateTime.now().add(const Duration(days: 3)), - // end: DateTime.now().add(const Duration(days: 7)), - // actionStart: DateTime.now().subtract(const Duration(days: 1)), - // entity: 'DBS', - // module: 'event', - // moduleObjectId: '', - // location: 'Foyer', - // status: NewsStatus.waitingApproval, - // ), - // News( - // id: '', - // title: 'Test 4', - // start: DateTime.now().subtract(const Duration(days: 2)), - // end: DateTime.now().subtract(const Duration(days: 1)), - // actionStart: DateTime.now().subtract(const Duration(days: 3)), - // entity: 'DBS', - // module: 'event', - // moduleObjectId: '', - // status: NewsStatus.waitingApproval, - // ), - // ]); + // return List.from( + // (await getList(suffix: "admin/news")).map((e) => News.fromJson(e)), + // ); + return Future.value([ + News( + id: '', + title: 'Test', + start: DateTime.now().subtract(const Duration(days: 1)), + entity: 'BDE', + module: 'post', + moduleObjectId: '', + status: NewsStatus.waitingApproval, + ), + News( + id: '', + title: 'Vote', + start: DateTime.now().subtract(const Duration(days: 2)), + end: DateTime.now().add(const Duration(days: 2)), + actionStart: DateTime.now().subtract(const Duration(days: 2)), + entity: 'CAA', + module: 'campagne', + moduleObjectId: '', + status: NewsStatus.waitingApproval, + ), + News( + id: '', + title: 'Rewass', + start: DateTime.now().add(const Duration(days: 3)), + end: DateTime.now().add(const Duration(days: 7)), + actionStart: DateTime.now().subtract(const Duration(days: 1)), + entity: 'DBS', + module: 'event', + moduleObjectId: '', + location: 'Foyer', + status: NewsStatus.waitingApproval, + ), + News( + id: '', + title: 'Test 4', + start: DateTime.now().subtract(const Duration(days: 2)), + end: DateTime.now().subtract(const Duration(days: 1)), + actionStart: DateTime.now().subtract(const Duration(days: 3)), + entity: 'DBS', + module: 'event', + moduleObjectId: '', + status: NewsStatus.waitingApproval, + ), + ]); } Future approveNews(String id) async { From df098c305d08e5e375b626fca8c5d4f1662e50d6 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sun, 17 Aug 2025 11:53:43 +0200 Subject: [PATCH 269/473] Association adding part.1 --- .../add_association_modal.dart | 88 +++++++++++++++++++ .../association_page/association_page.dart | 58 +++++++++++- 2 files changed, 143 insertions(+), 3 deletions(-) create mode 100644 lib/admin/ui/pages/association_page/add_association_modal.dart diff --git a/lib/admin/ui/pages/association_page/add_association_modal.dart b/lib/admin/ui/pages/association_page/add_association_modal.dart new file mode 100644 index 0000000000..44dfc73f94 --- /dev/null +++ b/lib/admin/ui/pages/association_page/add_association_modal.dart @@ -0,0 +1,88 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/admin/class/simple_group.dart'; +import 'package:titan/l10n/app_localizations.dart'; +import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; +import 'package:titan/tools/ui/styleguide/button.dart'; +import 'package:titan/tools/ui/styleguide/list_item.dart'; +import 'package:titan/tools/ui/widgets/text_entry.dart'; + +class AddAssociationModal extends HookWidget { + final List groups; + final void Function(SimpleGroup group, String name) onSubmit; + final WidgetRef ref; + + const AddAssociationModal({ + super.key, + required this.groups, + required this.onSubmit, + required this.ref, + }); + + @override + Widget build(BuildContext context) { + final nameController = useTextEditingController(); + final chosenGroup = useState(null); + + final localizeWithContext = AppLocalizations.of(context)!; + + return BottomModalTemplate( + title: "Association Management", + child: SingleChildScrollView( + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: TextEntry(label: "Admin", controller: nameController), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: chosenGroup.value == null + ? ListItem( + title: "Choisir le groupe gestionnaire de l'association", + onTap: () async { + FocusScope.of(context).unfocus(); + final ctx = context; + await Future.delayed(Duration(milliseconds: 150)); + if (!ctx.mounted) return; + + await showCustomBottomModal( + context: ctx, + ref: ref, + modal: BottomModalTemplate( + title: "Choisir un groupe", + child: Column( + children: [ + ...groups.map( + (e) => ListItem( + title: e.name, + onTap: () { + chosenGroup.value = e; + Navigator.of(ctx).pop(); + }, + ), + ), + ], + ), + ), + ); + }, + ) + : Text(chosenGroup.value!.name), + ), + const SizedBox(height: 10), + Button( + text: localizeWithContext.adminAdd, + onPressed: () { + if (chosenGroup.value != null) { + onSubmit(chosenGroup.value!, nameController.text); + } + }, + ), + ], + ), + ), + ); + } +} diff --git a/lib/admin/ui/pages/association_page/association_page.dart b/lib/admin/ui/pages/association_page/association_page.dart index b7c4ad4505..74b2c45abe 100644 --- a/lib/admin/ui/pages/association_page/association_page.dart +++ b/lib/admin/ui/pages/association_page/association_page.dart @@ -1,11 +1,63 @@ +import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:heroicons/heroicons.dart'; import 'package:titan/admin/admin.dart'; +import 'package:titan/admin/providers/all_groups_list_provider.dart'; +import 'package:titan/admin/providers/assocation_list_provider.dart'; +import 'package:titan/admin/ui/pages/association_page/add_association_modal.dart'; +import 'package:titan/tools/ui/builders/async_child.dart'; +import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; +import 'package:titan/tools/ui/styleguide/icon_button.dart'; -class AssociationPage extends StatelessWidget { +class AssociationPage extends ConsumerWidget { const AssociationPage({super.key}); @override - Widget build(BuildContext context) { - return AdminTemplate(child: Text("yo")); + Widget build(BuildContext context, WidgetRef ref) { + final associationList = ref.watch(associationListProvider); + final groups = ref.watch(allGroupList); + return AdminTemplate( + child: AsyncChild( + value: associationList, + builder: (BuildContext context, associationList) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 30), + child: Column( + children: [ + Row( + children: [ + Text( + "Association", + style: Theme.of(context).textTheme.headlineMedium, + ), + const Spacer(), + CustomIconButton( + icon: HeroIcon( + HeroIcons.plus, + color: Colors.white, + size: 30, + ), + onPressed: () async { + await showCustomBottomModal( + context: context, + ref: ref, + modal: AddAssociationModal( + groups: groups, + onSubmit: (group, name) {}, + ref: ref, + ), + ); + }, + ), + ], + ), + ...associationList.map((association) => Text(association.name)), + ], + ), + ); + }, + ), + ); } } From 8ce6383f27fd5222254f81a5b13876a16367286f Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sun, 17 Aug 2025 12:12:09 +0200 Subject: [PATCH 270/473] create association part.2 --- lib/admin/class/assocation.dart | 4 +- .../add_association_modal.dart | 5 +- .../association_page/association_page.dart | 54 +++++++++++++++++-- 3 files changed, 57 insertions(+), 6 deletions(-) diff --git a/lib/admin/class/assocation.dart b/lib/admin/class/assocation.dart index 5e6151dcdb..c95f9b4337 100644 --- a/lib/admin/class/assocation.dart +++ b/lib/admin/class/assocation.dart @@ -6,14 +6,14 @@ class Association { Association.fromJson(Map json) { name = json['name']; - groupId = json['groupId']; + groupId = json['group_id']; id = json['id']; } Map toJson() { final data = {}; data['name'] = name; - data['groupId'] = groupId; + data['group_id'] = groupId; data['id'] = id; return data; } diff --git a/lib/admin/ui/pages/association_page/add_association_modal.dart b/lib/admin/ui/pages/association_page/add_association_modal.dart index 44dfc73f94..dcc28086cc 100644 --- a/lib/admin/ui/pages/association_page/add_association_modal.dart +++ b/lib/admin/ui/pages/association_page/add_association_modal.dart @@ -34,7 +34,10 @@ class AddAssociationModal extends HookWidget { children: [ Padding( padding: const EdgeInsets.all(8.0), - child: TextEntry(label: "Admin", controller: nameController), + child: TextEntry( + label: "Nom de l'association", + controller: nameController, + ), ), Padding( padding: const EdgeInsets.all(8.0), diff --git a/lib/admin/ui/pages/association_page/association_page.dart b/lib/admin/ui/pages/association_page/association_page.dart index 74b2c45abe..9a9cd605f2 100644 --- a/lib/admin/ui/pages/association_page/association_page.dart +++ b/lib/admin/ui/pages/association_page/association_page.dart @@ -1,12 +1,15 @@ import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:heroicons/heroicons.dart'; import 'package:titan/admin/admin.dart'; +import 'package:titan/admin/class/assocation.dart'; import 'package:titan/admin/providers/all_groups_list_provider.dart'; import 'package:titan/admin/providers/assocation_list_provider.dart'; import 'package:titan/admin/ui/pages/association_page/add_association_modal.dart'; +import 'package:titan/tools/functions.dart'; +import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; +import 'package:titan/tools/ui/layouts/refresher.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; import 'package:titan/tools/ui/styleguide/icon_button.dart'; @@ -16,7 +19,16 @@ class AssociationPage extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final associationList = ref.watch(associationListProvider); + final associationNotifier = ref.watch(associationListProvider.notifier); final groups = ref.watch(allGroupList); + void displayToastWithContext(TypeMsg type, String msg) { + displayToast(context, type, msg); + } + + void popWithContext() { + Navigator.of(context).pop(); + } + return AdminTemplate( child: AsyncChild( value: associationList, @@ -44,7 +56,29 @@ class AssociationPage extends ConsumerWidget { ref: ref, modal: AddAssociationModal( groups: groups, - onSubmit: (group, name) {}, + onSubmit: (group, name) { + tokenExpireWrapper(ref, () async { + final value = await associationNotifier + .createAssociation( + Association.empty().copyWith( + groupId: group.id, + name: name, + ), + ); + if (value) { + displayToastWithContext( + TypeMsg.msg, + "Association created successfully", + ); + } else { + displayToastWithContext( + TypeMsg.error, + "Failed to create association", + ); + } + popWithContext(); + }); + }, ref: ref, ), ); @@ -52,7 +86,21 @@ class AssociationPage extends ConsumerWidget { ), ], ), - ...associationList.map((association) => Text(association.name)), + Expanded( + child: Refresher( + controller: ScrollController(), + onRefresh: () async { + await associationNotifier.loadAssociations(); + }, + child: Column( + children: [ + ...associationList.map( + (association) => Text(association.name), + ), + ], + ), + ), + ), ], ), ); From db79bfbe4862c167ece675d8b2f34a1e32d5c67e Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sun, 17 Aug 2025 13:23:54 +0200 Subject: [PATCH 271/473] association logo --- .../providers/association_logo_provider.dart | 63 +++++++++++++++++++ .../associations_logo_map_provider.dart | 17 +++++ .../association_logo_repository.dart | 16 +++-- .../association_page/association_item.dart | 50 +++++++++++++++ .../association_page/association_page.dart | 5 +- lib/tools/repository/logo_repository.dart | 2 + 6 files changed, 147 insertions(+), 6 deletions(-) create mode 100644 lib/admin/providers/association_logo_provider.dart create mode 100644 lib/admin/providers/associations_logo_map_provider.dart create mode 100644 lib/admin/ui/pages/association_page/association_item.dart diff --git a/lib/admin/providers/association_logo_provider.dart b/lib/admin/providers/association_logo_provider.dart new file mode 100644 index 0000000000..9ea817d216 --- /dev/null +++ b/lib/admin/providers/association_logo_provider.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:titan/admin/providers/associations_logo_map_provider.dart'; +import 'package:titan/admin/repositories/association_logo_repository.dart'; +import 'package:titan/tools/providers/single_notifier.dart'; + +class AssociationLogoProvider extends SingleNotifier { + final AssociationLogoRepository associationLogoRepository; + final AssociationLogoMapNotifier associationLogoMapNotifier; + final ImagePicker _picker = ImagePicker(); + + AssociationLogoProvider({ + required this.associationLogoRepository, + required this.associationLogoMapNotifier, + }) : super(const AsyncLoading()); + + Future getAssociationLogo(String associationId) async { + final image = await associationLogoRepository.getAssociationLogo( + associationId, + ); + associationLogoMapNotifier.setTData(associationId, AsyncData([image])); + state = AsyncData(image); + return image; + } + + Future setProfileLogo(ImageSource source, String associationId) async { + final previousState = state; + state = const AsyncLoading(); + final XFile? image = await _picker.pickImage( + source: source, + imageQuality: 20, + ); + if (image != null) { + try { + final i = await associationLogoRepository.addAssociationLogo( + await image.readAsBytes(), + associationId, + ); + state = AsyncValue.data(i); + associationLogoMapNotifier.setTData(associationId, AsyncData([i])); + return true; + } catch (e) { + state = previousState; + return false; + } + } + state = previousState; + return null; + } +} + +final associationLogoProvider = + StateNotifierProvider>((ref) { + final associationLogo = ref.watch(associationLogoRepository); + final sessionPosterMapNotifier = ref.watch( + associationLogoMapProvider.notifier, + ); + return AssociationLogoProvider( + associationLogoRepository: associationLogo, + associationLogoMapNotifier: sessionPosterMapNotifier, + ); + }); diff --git a/lib/admin/providers/associations_logo_map_provider.dart b/lib/admin/providers/associations_logo_map_provider.dart new file mode 100644 index 0000000000..504052649c --- /dev/null +++ b/lib/admin/providers/associations_logo_map_provider.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/tools/providers/map_provider.dart'; + +class AssociationLogoMapNotifier extends MapNotifier { + AssociationLogoMapNotifier() : super(); +} + +final associationLogoMapProvider = + StateNotifierProvider< + AssociationLogoMapNotifier, + Map>?> + >((ref) { + AssociationLogoMapNotifier associationLogoNotifier = + AssociationLogoMapNotifier(); + return associationLogoNotifier; + }); diff --git a/lib/admin/repositories/association_logo_repository.dart b/lib/admin/repositories/association_logo_repository.dart index 70e740758e..dd3111aa49 100644 --- a/lib/admin/repositories/association_logo_repository.dart +++ b/lib/admin/repositories/association_logo_repository.dart @@ -2,24 +2,30 @@ import 'dart:async'; import 'dart:typed_data'; import 'package:flutter/material.dart'; -import 'package:titan/tools/functions.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/auth/providers/openid_provider.dart'; import 'package:titan/tools/repository/logo_repository.dart'; class AssociationLogoRepository extends LogoRepository { @override // ignore: overridden_fields - final ext = "association/"; + final ext = "associations/"; Future getAssociationLogo(String id) async { - final uint8List = await getLogo("", suffix: "associations/$id/logo"); + final uint8List = await getLogo("", suffix: "$id/logo"); if (uint8List.isEmpty) { - return Image.asset(getTitanLogo()); + return Image.asset("assets/images/vache.png", fit: BoxFit.cover); } return Image.memory(uint8List); } Future addAssociationLogo(Uint8List bytes, String id) async { - final uint8List = await addLogo(bytes, "", suffix: "associations/$id/logo"); + final uint8List = await addLogo(bytes, "", suffix: "$id/logo"); return Image.memory(uint8List); } } + +final associationLogoRepository = Provider((ref) { + final token = ref.watch(tokenProvider); + return AssociationLogoRepository()..setToken(token); +}); diff --git a/lib/admin/ui/pages/association_page/association_item.dart b/lib/admin/ui/pages/association_page/association_item.dart new file mode 100644 index 0000000000..83e25393ef --- /dev/null +++ b/lib/admin/ui/pages/association_page/association_item.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/admin/class/assocation.dart'; +import 'package:titan/admin/providers/assocation_list_provider.dart'; +import 'package:titan/admin/providers/association_logo_provider.dart'; +import 'package:titan/admin/providers/associations_logo_map_provider.dart'; +import 'package:titan/tools/ui/builders/auto_loader_child.dart'; +import 'package:titan/tools/ui/styleguide/list_item.dart'; + +class AssociationItem extends HookConsumerWidget { + const AssociationItem({super.key, required this.association}); + + final Association association; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final associationLogo = ref.watch( + associationLogoMapProvider.select((value) => value[association.id]), + ); + final associationLogoMapNotifier = ref.watch( + associationLogoMapProvider.notifier, + ); + final associationLogoNotifier = ref.watch(associationLogoProvider.notifier); + final associationNotifier = ref.watch(associationListProvider.notifier); + return Padding( + padding: const EdgeInsets.symmetric(vertical: 5), + child: ListItem( + title: association.name, + icon: AutoLoaderChild( + group: associationLogo, + notifier: associationLogoMapNotifier, + mapKey: association.id, + loader: (associationId) => + associationLogoNotifier.getAssociationLogo(associationId), + dataBuilder: (context, data) { + return CircleAvatar( + radius: 20, + backgroundColor: Colors.white, + backgroundImage: Image(image: data.first.image).image, + ); + }, + ), + onTap: () { + associationNotifier.setAssociation(association); + associationLogoNotifier.getAssociationLogo(association.id); + }, + ), + ); + } +} diff --git a/lib/admin/ui/pages/association_page/association_page.dart b/lib/admin/ui/pages/association_page/association_page.dart index 9a9cd605f2..446aec7042 100644 --- a/lib/admin/ui/pages/association_page/association_page.dart +++ b/lib/admin/ui/pages/association_page/association_page.dart @@ -6,6 +6,7 @@ import 'package:titan/admin/class/assocation.dart'; import 'package:titan/admin/providers/all_groups_list_provider.dart'; import 'package:titan/admin/providers/assocation_list_provider.dart'; import 'package:titan/admin/ui/pages/association_page/add_association_modal.dart'; +import 'package:titan/admin/ui/pages/association_page/association_item.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; @@ -86,6 +87,7 @@ class AssociationPage extends ConsumerWidget { ), ], ), + SizedBox(height: 20), Expanded( child: Refresher( controller: ScrollController(), @@ -95,7 +97,8 @@ class AssociationPage extends ConsumerWidget { child: Column( children: [ ...associationList.map( - (association) => Text(association.name), + (association) => + AssociationItem(association: association), ), ], ), diff --git a/lib/tools/repository/logo_repository.dart b/lib/tools/repository/logo_repository.dart index 7c4cacfd9f..5294be83ca 100644 --- a/lib/tools/repository/logo_repository.dart +++ b/lib/tools/repository/logo_repository.dart @@ -38,6 +38,8 @@ abstract class LogoRepository extends Repository { } else { throw AppException(ErrorType.notFound, decoded["detail"]); } + } else if (response.statusCode == 404) { + return Uint8List(0); } else { Repository.logger.error( "GET $ext$id$suffix\n${response.statusCode} ${response.body}", From 3accb1167a6d3a7ba4c5f70948fd38568110963d Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sun, 17 Aug 2025 14:39:56 +0200 Subject: [PATCH 272/473] association edition --- lib/admin/class/assocation.dart | 2 +- .../providers/association_logo_provider.dart | 2 +- .../association_page/association_item.dart | 78 ++++++- .../association_page/edit_association.dart | 216 ++++++++++++++++++ 4 files changed, 293 insertions(+), 5 deletions(-) create mode 100644 lib/admin/ui/pages/association_page/edit_association.dart diff --git a/lib/admin/class/assocation.dart b/lib/admin/class/assocation.dart index c95f9b4337..22efd6b521 100644 --- a/lib/admin/class/assocation.dart +++ b/lib/admin/class/assocation.dart @@ -27,7 +27,7 @@ class Association { Association.empty() { name = 'Nom'; - groupId = 'Description'; + groupId = ''; id = ''; } diff --git a/lib/admin/providers/association_logo_provider.dart b/lib/admin/providers/association_logo_provider.dart index 9ea817d216..5dd5038e2b 100644 --- a/lib/admin/providers/association_logo_provider.dart +++ b/lib/admin/providers/association_logo_provider.dart @@ -24,7 +24,7 @@ class AssociationLogoProvider extends SingleNotifier { return image; } - Future setProfileLogo(ImageSource source, String associationId) async { + Future setLogo(ImageSource source, String associationId) async { final previousState = state; state = const AsyncLoading(); final XFile? image = await _picker.pickImage( diff --git a/lib/admin/ui/pages/association_page/association_item.dart b/lib/admin/ui/pages/association_page/association_item.dart index 83e25393ef..fb0b50a28c 100644 --- a/lib/admin/ui/pages/association_page/association_item.dart +++ b/lib/admin/ui/pages/association_page/association_item.dart @@ -4,8 +4,15 @@ import 'package:titan/admin/class/assocation.dart'; import 'package:titan/admin/providers/assocation_list_provider.dart'; import 'package:titan/admin/providers/association_logo_provider.dart'; import 'package:titan/admin/providers/associations_logo_map_provider.dart'; +import 'package:titan/admin/ui/pages/association_page/edit_association.dart'; +import 'package:titan/l10n/app_localizations.dart'; +import 'package:titan/tools/functions.dart'; +import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/builders/auto_loader_child.dart'; +import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; +import 'package:titan/tools/ui/styleguide/button.dart'; import 'package:titan/tools/ui/styleguide/list_item.dart'; +import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; class AssociationItem extends HookConsumerWidget { const AssociationItem({super.key, required this.association}); @@ -22,6 +29,11 @@ class AssociationItem extends HookConsumerWidget { ); final associationLogoNotifier = ref.watch(associationLogoProvider.notifier); final associationNotifier = ref.watch(associationListProvider.notifier); + + void displayToastWithContext(TypeMsg type, String msg) { + displayToast(context, type, msg); + } + return Padding( padding: const EdgeInsets.symmetric(vertical: 5), child: ListItem( @@ -40,9 +52,69 @@ class AssociationItem extends HookConsumerWidget { ); }, ), - onTap: () { - associationNotifier.setAssociation(association); - associationLogoNotifier.getAssociationLogo(association.id); + onTap: () async { + await showCustomBottomModal( + context: context, + ref: ref, + modal: BottomModalTemplate( + title: association.name, + child: Column( + children: [ + Button( + text: "Modifier", + onPressed: () async { + associationLogoNotifier.getAssociationLogo( + association.id, + ); + await showCustomBottomModal( + context: context, + ref: ref, + modal: BottomModalTemplate( + title: "Modifier l'association", + child: EditAssociation(association: association), + ), + ); + }, + ), + SizedBox(height: 10), + Button( + text: "Supprimer", + onPressed: () async { + await showDialog( + context: context, + builder: (context) { + return CustomDialogBox( + title: AppLocalizations.of(context)!.adminDeleting, + descriptions: AppLocalizations.of( + context, + )!.adminDeleteAssociationMembership, + onYes: () async { + tokenExpireWrapper(ref, () async { + final value = await associationNotifier + .deleteAssociation(association); + if (value) { + displayToastWithContext( + TypeMsg.msg, + "Suppression réussie", + ); + } else { + displayToastWithContext( + TypeMsg.error, + "Échec de la suppression", + ); + } + }); + }, + ); + }, + ); + }, + type: ButtonType.danger, + ), + ], + ), + ), + ); }, ), ); diff --git a/lib/admin/ui/pages/association_page/edit_association.dart b/lib/admin/ui/pages/association_page/edit_association.dart new file mode 100644 index 0000000000..2e9223946b --- /dev/null +++ b/lib/admin/ui/pages/association_page/edit_association.dart @@ -0,0 +1,216 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:heroicons/heroicons.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:titan/admin/class/assocation.dart'; +import 'package:titan/admin/class/simple_group.dart'; +import 'package:titan/admin/providers/all_groups_list_provider.dart'; +import 'package:titan/admin/providers/assocation_list_provider.dart'; +import 'package:titan/admin/providers/association_logo_provider.dart'; +import 'package:titan/l10n/app_localizations.dart'; +import 'package:titan/settings/ui/pages/main_page/picture_button.dart'; +import 'package:titan/tools/functions.dart'; +import 'package:titan/tools/token_expire_wrapper.dart'; +import 'package:titan/tools/ui/builders/async_child.dart'; +import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; +import 'package:titan/tools/ui/styleguide/button.dart'; +import 'package:titan/tools/ui/styleguide/list_item.dart'; +import 'package:titan/tools/ui/styleguide/text_entry.dart'; + +class EditAssociation extends HookConsumerWidget { + final Association association; + const EditAssociation({super.key, required this.association}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final associationListNotifier = ref.watch(associationListProvider.notifier); + final nameController = useTextEditingController(text: association.name); + final groups = ref.watch(allGroupList); + final group = groups.firstWhere((group) => group.id == association.groupId); + final chosenGroup = useState(group); + final associationLogo = ref.watch(associationLogoProvider); + final associationLogoNotifier = ref.watch(associationLogoProvider.notifier); + + MediaQuery.of(context).viewInsets.bottom; + + void displayToastWithContext(TypeMsg type, String msg) { + displayToast(context, type, msg); + } + + final localizeWithContext = AppLocalizations.of(context)!; + final navigatorWithContext = Navigator.of(context); + + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 40), + child: SingleChildScrollView( + child: Column( + children: [ + if (View.of(context).viewInsets.bottom == 0) + AsyncChild( + value: associationLogo, + builder: (context, associationLogo) { + return Center( + child: Stack( + clipBehavior: Clip.none, + children: [ + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.1), + spreadRadius: 5, + blurRadius: 10, + offset: const Offset(2, 3), + ), + ], + ), + child: CircleAvatar( + radius: 80, + backgroundImage: Image( + image: associationLogo.image, + ).image, + ), + ), + Positioned( + bottom: 0, + left: 0, + child: GestureDetector( + onTap: () async { + final value = await associationLogoNotifier + .setLogo(ImageSource.gallery, association.id); + if (value != null) { + if (value) { + displayToastWithContext( + TypeMsg.msg, + localizeWithContext + .settingsUpdatedProfilePicture, + ); + } else { + displayToastWithContext( + TypeMsg.error, + localizeWithContext + .settingsTooHeavyProfilePicture, + ); + } + } else { + displayToastWithContext( + TypeMsg.error, + localizeWithContext + .settingsErrorProfilePicture, + ); + } + }, + child: const PictureButton(icon: HeroIcons.photo), + ), + ), + Positioned( + bottom: 0, + right: 0, + child: GestureDetector( + onTap: () async { + final value = await associationLogoNotifier + .setLogo(ImageSource.camera, association.id); + if (value != null) { + if (value) { + displayToastWithContext( + TypeMsg.msg, + localizeWithContext + .settingsUpdatedProfilePicture, + ); + } else { + displayToastWithContext( + TypeMsg.error, + localizeWithContext + .settingsTooHeavyProfilePicture, + ); + } + } else { + displayToastWithContext( + TypeMsg.error, + localizeWithContext + .settingsErrorProfilePicture, + ); + } + }, + child: const PictureButton(icon: HeroIcons.camera), + ), + ), + ], + ), + ); + }, + ), + SizedBox(height: View.of(context).viewInsets.bottom == 0 ? 30 : 10), + TextEntry( + label: "Nom de l'association", + controller: nameController, + ), + SizedBox(height: 20), + ListItem( + title: "Groupe gestionnaire : ${chosenGroup.value!.name}", + onTap: () async { + FocusScope.of(context).unfocus(); + final ctx = context; + await Future.delayed(Duration(milliseconds: 150)); + if (!ctx.mounted) return; + + await showCustomBottomModal( + context: ctx, + ref: ref, + modal: BottomModalTemplate( + title: "Choisir un groupe", + child: Column( + children: [ + ...groups.map( + (e) => ListItem( + title: e.name, + onTap: () { + chosenGroup.value = e; + Navigator.of(ctx).pop(); + }, + ), + ), + ], + ), + ), + ); + }, + ), + SizedBox(height: 20), + Button( + text: "Valider", + disabled: + !(nameController.value.text != association.name || + chosenGroup.value!.id != association.groupId), + onPressed: () async { + await tokenExpireWrapper(ref, () async { + final newAssociation = association.copyWith( + name: nameController.value.text, + groupId: chosenGroup.value!.id, + ); + final value = await associationListNotifier.updateAssociation( + newAssociation, + ); + if (value) { + displayToastWithContext( + TypeMsg.msg, + localizeWithContext.settingsEditedAccount, + ); + navigatorWithContext.pop(); + } else { + displayToastWithContext( + TypeMsg.error, + localizeWithContext.settingsFailedToEditAccount, + ); + } + }); + }, + ), + ], + ), + ), + ); + } +} From f0de2c3000d4977d79bf9acb97713ad39a2cec73 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sun, 17 Aug 2025 14:45:31 +0200 Subject: [PATCH 273/473] optimization --- .../pages/association_page/association_item.dart | 14 +++++++++++++- .../pages/association_page/edit_association.dart | 8 ++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/lib/admin/ui/pages/association_page/association_item.dart b/lib/admin/ui/pages/association_page/association_item.dart index fb0b50a28c..09c7d08df5 100644 --- a/lib/admin/ui/pages/association_page/association_item.dart +++ b/lib/admin/ui/pages/association_page/association_item.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/class/assocation.dart'; +import 'package:titan/admin/providers/all_groups_list_provider.dart'; import 'package:titan/admin/providers/assocation_list_provider.dart'; import 'package:titan/admin/providers/association_logo_provider.dart'; import 'package:titan/admin/providers/associations_logo_map_provider.dart'; @@ -21,6 +22,8 @@ class AssociationItem extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final groups = ref.watch(allGroupList); + final group = groups.firstWhere((group) => group.id == association.groupId); final associationLogo = ref.watch( associationLogoMapProvider.select((value) => value[association.id]), ); @@ -34,10 +37,15 @@ class AssociationItem extends HookConsumerWidget { displayToast(context, type, msg); } + void popWithContext() { + Navigator.of(context).pop(); + } + return Padding( padding: const EdgeInsets.symmetric(vertical: 5), child: ListItem( title: association.name, + subtitle: "Géré par : ${group.name}", icon: AutoLoaderChild( group: associationLogo, notifier: associationLogoMapNotifier, @@ -71,9 +79,13 @@ class AssociationItem extends HookConsumerWidget { ref: ref, modal: BottomModalTemplate( title: "Modifier l'association", - child: EditAssociation(association: association), + child: EditAssociation( + association: association, + group: group, + ), ), ); + popWithContext(); }, ), SizedBox(height: 10), diff --git a/lib/admin/ui/pages/association_page/edit_association.dart b/lib/admin/ui/pages/association_page/edit_association.dart index 2e9223946b..437e41b5f2 100644 --- a/lib/admin/ui/pages/association_page/edit_association.dart +++ b/lib/admin/ui/pages/association_page/edit_association.dart @@ -20,14 +20,18 @@ import 'package:titan/tools/ui/styleguide/text_entry.dart'; class EditAssociation extends HookConsumerWidget { final Association association; - const EditAssociation({super.key, required this.association}); + final SimpleGroup group; + const EditAssociation({ + super.key, + required this.association, + required this.group, + }); @override Widget build(BuildContext context, WidgetRef ref) { final associationListNotifier = ref.watch(associationListProvider.notifier); final nameController = useTextEditingController(text: association.name); final groups = ref.watch(allGroupList); - final group = groups.firstWhere((group) => group.id == association.groupId); final chosenGroup = useState(group); final associationLogo = ref.watch(associationLogoProvider); final associationLogoNotifier = ref.watch(associationLogoProvider.notifier); From 7447b59ddd549829eaa5109ac7726311556bb178 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sun, 17 Aug 2025 14:50:47 +0200 Subject: [PATCH 274/473] comment delete button --- .../association_page/association_item.dart | 68 +++++++++---------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/lib/admin/ui/pages/association_page/association_item.dart b/lib/admin/ui/pages/association_page/association_item.dart index 09c7d08df5..5057e47f1e 100644 --- a/lib/admin/ui/pages/association_page/association_item.dart +++ b/lib/admin/ui/pages/association_page/association_item.dart @@ -89,40 +89,40 @@ class AssociationItem extends HookConsumerWidget { }, ), SizedBox(height: 10), - Button( - text: "Supprimer", - onPressed: () async { - await showDialog( - context: context, - builder: (context) { - return CustomDialogBox( - title: AppLocalizations.of(context)!.adminDeleting, - descriptions: AppLocalizations.of( - context, - )!.adminDeleteAssociationMembership, - onYes: () async { - tokenExpireWrapper(ref, () async { - final value = await associationNotifier - .deleteAssociation(association); - if (value) { - displayToastWithContext( - TypeMsg.msg, - "Suppression réussie", - ); - } else { - displayToastWithContext( - TypeMsg.error, - "Échec de la suppression", - ); - } - }); - }, - ); - }, - ); - }, - type: ButtonType.danger, - ), + // Button( + // text: "Supprimer", + // onPressed: () async { + // await showDialog( + // context: context, + // builder: (context) { + // return CustomDialogBox( + // title: AppLocalizations.of(context)!.adminDeleting, + // descriptions: AppLocalizations.of( + // context, + // )!.adminDeleteAssociationMembership, + // onYes: () async { + // tokenExpireWrapper(ref, () async { + // final value = await associationNotifier + // .deleteAssociation(association); + // if (value) { + // displayToastWithContext( + // TypeMsg.msg, + // "Suppression réussie", + // ); + // } else { + // displayToastWithContext( + // TypeMsg.error, + // "Échec de la suppression", + // ); + // } + // }); + // }, + // ); + // }, + // ); + // }, + // type: ButtonType.danger, + // ), ], ), ), From 0eb756cbf822f05b98476ac3fe3586e080583943 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sun, 17 Aug 2025 14:53:21 +0200 Subject: [PATCH 275/473] rm association deletion --- .../association_page/association_item.dart | 78 +------------------ 1 file changed, 3 insertions(+), 75 deletions(-) diff --git a/lib/admin/ui/pages/association_page/association_item.dart b/lib/admin/ui/pages/association_page/association_item.dart index 5057e47f1e..df420c0cf9 100644 --- a/lib/admin/ui/pages/association_page/association_item.dart +++ b/lib/admin/ui/pages/association_page/association_item.dart @@ -2,18 +2,12 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/class/assocation.dart'; import 'package:titan/admin/providers/all_groups_list_provider.dart'; -import 'package:titan/admin/providers/assocation_list_provider.dart'; import 'package:titan/admin/providers/association_logo_provider.dart'; import 'package:titan/admin/providers/associations_logo_map_provider.dart'; import 'package:titan/admin/ui/pages/association_page/edit_association.dart'; -import 'package:titan/l10n/app_localizations.dart'; -import 'package:titan/tools/functions.dart'; -import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/builders/auto_loader_child.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; -import 'package:titan/tools/ui/styleguide/button.dart'; import 'package:titan/tools/ui/styleguide/list_item.dart'; -import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; class AssociationItem extends HookConsumerWidget { const AssociationItem({super.key, required this.association}); @@ -31,15 +25,6 @@ class AssociationItem extends HookConsumerWidget { associationLogoMapProvider.notifier, ); final associationLogoNotifier = ref.watch(associationLogoProvider.notifier); - final associationNotifier = ref.watch(associationListProvider.notifier); - - void displayToastWithContext(TypeMsg type, String msg) { - displayToast(context, type, msg); - } - - void popWithContext() { - Navigator.of(context).pop(); - } return Padding( padding: const EdgeInsets.symmetric(vertical: 5), @@ -61,70 +46,13 @@ class AssociationItem extends HookConsumerWidget { }, ), onTap: () async { + associationLogoNotifier.getAssociationLogo(association.id); await showCustomBottomModal( context: context, ref: ref, modal: BottomModalTemplate( - title: association.name, - child: Column( - children: [ - Button( - text: "Modifier", - onPressed: () async { - associationLogoNotifier.getAssociationLogo( - association.id, - ); - await showCustomBottomModal( - context: context, - ref: ref, - modal: BottomModalTemplate( - title: "Modifier l'association", - child: EditAssociation( - association: association, - group: group, - ), - ), - ); - popWithContext(); - }, - ), - SizedBox(height: 10), - // Button( - // text: "Supprimer", - // onPressed: () async { - // await showDialog( - // context: context, - // builder: (context) { - // return CustomDialogBox( - // title: AppLocalizations.of(context)!.adminDeleting, - // descriptions: AppLocalizations.of( - // context, - // )!.adminDeleteAssociationMembership, - // onYes: () async { - // tokenExpireWrapper(ref, () async { - // final value = await associationNotifier - // .deleteAssociation(association); - // if (value) { - // displayToastWithContext( - // TypeMsg.msg, - // "Suppression réussie", - // ); - // } else { - // displayToastWithContext( - // TypeMsg.error, - // "Échec de la suppression", - // ); - // } - // }); - // }, - // ); - // }, - // ); - // }, - // type: ButtonType.danger, - // ), - ], - ), + title: "Modifier l'association : ${association.name}", + child: EditAssociation(association: association, group: group), ), ); }, From 699390c1e8cde9943cd0c4d64b89364b479eafee Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sun, 17 Aug 2025 15:14:12 +0200 Subject: [PATCH 276/473] translations --- .../add_association_modal.dart | 42 +++++++- .../association_page/association_item.dart | 7 +- .../association_page/association_page.dart | 10 +- .../association_page/edit_association.dart | 28 ++--- lib/l10n/app_en.arb | 38 ++++++- lib/l10n/app_fr.arb | 38 ++++++- lib/l10n/app_localizations.dart | 102 +++++++++++++++--- lib/l10n/app_localizations_en.dart | 55 ++++++++-- lib/l10n/app_localizations_fr.dart | 57 ++++++++-- 9 files changed, 316 insertions(+), 61 deletions(-) diff --git a/lib/admin/ui/pages/association_page/add_association_modal.dart b/lib/admin/ui/pages/association_page/add_association_modal.dart index dcc28086cc..0f9a70fd88 100644 --- a/lib/admin/ui/pages/association_page/add_association_modal.dart +++ b/lib/admin/ui/pages/association_page/add_association_modal.dart @@ -28,14 +28,14 @@ class AddAssociationModal extends HookWidget { final localizeWithContext = AppLocalizations.of(context)!; return BottomModalTemplate( - title: "Association Management", + title: localizeWithContext.adminAddAssociation, child: SingleChildScrollView( child: Column( children: [ Padding( padding: const EdgeInsets.all(8.0), child: TextEntry( - label: "Nom de l'association", + label: localizeWithContext.adminAssociationName, controller: nameController, ), ), @@ -43,7 +43,8 @@ class AddAssociationModal extends HookWidget { padding: const EdgeInsets.all(8.0), child: chosenGroup.value == null ? ListItem( - title: "Choisir le groupe gestionnaire de l'association", + title: localizeWithContext + .adminChooseAssociationManagerGroup, onTap: () async { FocusScope.of(context).unfocus(); final ctx = context; @@ -54,7 +55,7 @@ class AddAssociationModal extends HookWidget { context: ctx, ref: ref, modal: BottomModalTemplate( - title: "Choisir un groupe", + title: localizeWithContext.adminChooseGroup, child: Column( children: [ ...groups.map( @@ -72,7 +73,38 @@ class AddAssociationModal extends HookWidget { ); }, ) - : Text(chosenGroup.value!.name), + : ListItem( + title: localizeWithContext.adminManagerGroup( + chosenGroup.value!.name, + ), + onTap: () async { + FocusScope.of(context).unfocus(); + final ctx = context; + await Future.delayed(Duration(milliseconds: 150)); + if (!ctx.mounted) return; + + await showCustomBottomModal( + context: ctx, + ref: ref, + modal: BottomModalTemplate( + title: localizeWithContext.adminChooseGroup, + child: Column( + children: [ + ...groups.map( + (e) => ListItem( + title: e.name, + onTap: () { + chosenGroup.value = e; + Navigator.of(ctx).pop(); + }, + ), + ), + ], + ), + ), + ); + }, + ), ), const SizedBox(height: 10), Button( diff --git a/lib/admin/ui/pages/association_page/association_item.dart b/lib/admin/ui/pages/association_page/association_item.dart index df420c0cf9..08b0a8267c 100644 --- a/lib/admin/ui/pages/association_page/association_item.dart +++ b/lib/admin/ui/pages/association_page/association_item.dart @@ -5,6 +5,7 @@ import 'package:titan/admin/providers/all_groups_list_provider.dart'; import 'package:titan/admin/providers/association_logo_provider.dart'; import 'package:titan/admin/providers/associations_logo_map_provider.dart'; import 'package:titan/admin/ui/pages/association_page/edit_association.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/tools/ui/builders/auto_loader_child.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; import 'package:titan/tools/ui/styleguide/list_item.dart'; @@ -26,11 +27,13 @@ class AssociationItem extends HookConsumerWidget { ); final associationLogoNotifier = ref.watch(associationLogoProvider.notifier); + final localizeWithContext = AppLocalizations.of(context)!; + return Padding( padding: const EdgeInsets.symmetric(vertical: 5), child: ListItem( title: association.name, - subtitle: "Géré par : ${group.name}", + subtitle: localizeWithContext.adminManagerGroup(group.name), icon: AutoLoaderChild( group: associationLogo, notifier: associationLogoMapNotifier, @@ -51,7 +54,7 @@ class AssociationItem extends HookConsumerWidget { context: context, ref: ref, modal: BottomModalTemplate( - title: "Modifier l'association : ${association.name}", + title: localizeWithContext.adminEditAssociation(association.name), child: EditAssociation(association: association, group: group), ), ); diff --git a/lib/admin/ui/pages/association_page/association_page.dart b/lib/admin/ui/pages/association_page/association_page.dart index 446aec7042..d4e11fa34d 100644 --- a/lib/admin/ui/pages/association_page/association_page.dart +++ b/lib/admin/ui/pages/association_page/association_page.dart @@ -7,6 +7,7 @@ import 'package:titan/admin/providers/all_groups_list_provider.dart'; import 'package:titan/admin/providers/assocation_list_provider.dart'; import 'package:titan/admin/ui/pages/association_page/add_association_modal.dart'; import 'package:titan/admin/ui/pages/association_page/association_item.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; @@ -30,6 +31,8 @@ class AssociationPage extends ConsumerWidget { Navigator.of(context).pop(); } + final localizeWithContext = AppLocalizations.of(context)!; + return AdminTemplate( child: AsyncChild( value: associationList, @@ -41,7 +44,7 @@ class AssociationPage extends ConsumerWidget { Row( children: [ Text( - "Association", + localizeWithContext.adminAssociations, style: Theme.of(context).textTheme.headlineMedium, ), const Spacer(), @@ -69,12 +72,13 @@ class AssociationPage extends ConsumerWidget { if (value) { displayToastWithContext( TypeMsg.msg, - "Association created successfully", + localizeWithContext.adminAssociationCreated, ); } else { displayToastWithContext( TypeMsg.error, - "Failed to create association", + localizeWithContext + .adminAssociationCreationError, ); } popWithContext(); diff --git a/lib/admin/ui/pages/association_page/edit_association.dart b/lib/admin/ui/pages/association_page/edit_association.dart index 437e41b5f2..85f72b51bb 100644 --- a/lib/admin/ui/pages/association_page/edit_association.dart +++ b/lib/admin/ui/pages/association_page/edit_association.dart @@ -89,20 +89,19 @@ class EditAssociation extends HookConsumerWidget { displayToastWithContext( TypeMsg.msg, localizeWithContext - .settingsUpdatedProfilePicture, + .adminUpdatedAssociationLogo, ); } else { displayToastWithContext( TypeMsg.error, - localizeWithContext - .settingsTooHeavyProfilePicture, + localizeWithContext.adminTooHeavyLogo, ); } } else { displayToastWithContext( TypeMsg.error, localizeWithContext - .settingsErrorProfilePicture, + .adminFailedToUpdateAssociationLogo, ); } }, @@ -121,20 +120,19 @@ class EditAssociation extends HookConsumerWidget { displayToastWithContext( TypeMsg.msg, localizeWithContext - .settingsUpdatedProfilePicture, + .adminUpdatedAssociationLogo, ); } else { displayToastWithContext( TypeMsg.error, - localizeWithContext - .settingsTooHeavyProfilePicture, + localizeWithContext.adminTooHeavyLogo, ); } } else { displayToastWithContext( TypeMsg.error, localizeWithContext - .settingsErrorProfilePicture, + .adminFailedToUpdateAssociationLogo, ); } }, @@ -148,12 +146,14 @@ class EditAssociation extends HookConsumerWidget { ), SizedBox(height: View.of(context).viewInsets.bottom == 0 ? 30 : 10), TextEntry( - label: "Nom de l'association", + label: localizeWithContext.adminAssociationName, controller: nameController, ), SizedBox(height: 20), ListItem( - title: "Groupe gestionnaire : ${chosenGroup.value!.name}", + title: localizeWithContext.adminManagerGroup( + chosenGroup.value!.name, + ), onTap: () async { FocusScope.of(context).unfocus(); final ctx = context; @@ -164,7 +164,7 @@ class EditAssociation extends HookConsumerWidget { context: ctx, ref: ref, modal: BottomModalTemplate( - title: "Choisir un groupe", + title: localizeWithContext.adminChooseGroup, child: Column( children: [ ...groups.map( @@ -184,7 +184,7 @@ class EditAssociation extends HookConsumerWidget { ), SizedBox(height: 20), Button( - text: "Valider", + text: localizeWithContext.adminConfirm, disabled: !(nameController.value.text != association.name || chosenGroup.value!.id != association.groupId), @@ -200,13 +200,13 @@ class EditAssociation extends HookConsumerWidget { if (value) { displayToastWithContext( TypeMsg.msg, - localizeWithContext.settingsEditedAccount, + localizeWithContext.adminAssociationUpdated, ); navigatorWithContext.pop(); } else { displayToastWithContext( TypeMsg.error, - localizeWithContext.settingsFailedToEditAccount, + localizeWithContext.adminAssociationUpdateError, ); } }); diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index dcc6ea19f7..9c5ddbcec4 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -136,11 +136,39 @@ "adminFailedToInviteUsers": "Failed to invite users", "adminDeleteUsers": "Delete users", "adminAdmin": "Admin", - "adminAdverts": "Adverts", - "adminAnnouncers": "Announcers", - "adminManageAnnouncers": "Manage announcers", - "adminDeleteAnnouncer": "Delete announcer?", - "adminDeleteAnnouncerDescription": "Are you sure you want to delete this announcer? All their adverts will be deleted.", + "adminAssociations" : "Associations", + "adminManageAssociations" : "Manage associations", + "adminAddAssociation" : "Add association", + "adminAssociationName" : "Association name", + "adminSelectGroupAssociationManager" : "Select a group to manage this association", + "adminEditAssociation" : "Edit association : {associationName}", + "@adminEditAssociation" : { + "description": "Edit association", + "placeholders": { + "associationName": { + "type": "String" + } + } + }, + "adminManagerGroup" : "Manager group : {groupName}", + "@adminManagerGroup" : { + "description": "Manager group", + "placeholders": { + "groupName": { + "type": "String" + } + } + }, + "adminAssociationCreated" : "Association created", + "adminAssociationUpdated" : "Association updated", + "adminAssociationCreationError" : "Error while creating association", + "adminAssociationUpdateError" : "Error while updating association", + "adminUpdatedAssociationLogo" : "Association logo updated", + "adminTooHeavyLogo" : "Logo too heavy, maximum size is 4MB", + "adminFailedToUpdateAssociationLogo": "Failed to update association logo", + "adminChooseGroup": "Choose a group", + "adminChooseAssociationManagerGroup": "Choose a group to manage this association", + "adminConfirm" : "Confirm", "advertAdd": "Add", "advertAddedAdvert": "Advert published", "advertAddedAnnouncer": "Announcer added", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 28727051f4..fb39385040 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -144,11 +144,39 @@ "adminFailedToInviteUsers": "Échec de l'invitation des utilisateurs", "adminDeleteUsers": "Supprimer des utilisateurs", "adminAdmin": "Admin", - "adminAdverts": "Annonces", - "adminAnnouncers": "Annonceurs", - "adminManageAnnouncers": "Gérer les annonceurs", - "adminDeleteAnnouncer": "Supprimer cet annonceur ?", - "adminDeleteAnnouncerDescription": "Supprimer cet annonceurs supprimera toutes ses annonces.", + "adminAssociations": "Associations", + "adminManageAssociations": "Gérer les associations", + "adminAddAssociation": "Ajouter une association", + "adminAssociationName": "Nom de l'association", + "adminSelectGroupAssociationManager": "Séléctionner roupe gestionnaire de l'association", + "adminEditAssociation": "Modifier l'association : {associationName}", + "@adminEditAssociation": { + "description": "Modifier les informations de l'association", + "placeholders": { + "associationName": { + "type": "String" + } + } + }, + "adminManagerGroup" : "Groupe gestionnaire : {groupName}", + "@adminManagerGroup": { + "description": "Groupe qui gère l'association", + "placeholders": { + "groupName": { + "type": "String" + } + } + }, + "adminAssociationCreated": "Association créée", + "adminAssociationUpdated": "Association mise à jour", + "adminAssociationCreationError": "Échec de la création de l'association", + "adminAssociationUpdateError": "Échec de la mise à jour de l'association", + "adminUpdatedAssociationLogo" : "Logo de l'association mis à jour", + "adminTooHeavyLogo": "Le logo de l'association est trop lourd, il doit faire moins de 4 Mo", + "adminFailedToUpdateAssociationLogo": "Échec de la mise à jour du logo de l'association", + "adminChooseGroup": "Choisir un groupe", + "adminChooseAssociationManagerGroup": "Choisir un groupe gestionnaire pour l'association", + "adminConfirm" : "Valider", "advertAdd": "Ajouter", "advertAddedAdvert": "Annonce publiée", "advertAddedAnnouncer": "Annonceur ajouté", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 2ea194cf65..443fcf77dd 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -914,35 +914,107 @@ abstract class AppLocalizations { /// **'Admin'** String get adminAdmin; - /// No description provided for @adminAdverts. + /// No description provided for @adminAssociations. /// /// In fr, this message translates to: - /// **'Annonces'** - String get adminAdverts; + /// **'Associations'** + String get adminAssociations; + + /// No description provided for @adminManageAssociations. + /// + /// In fr, this message translates to: + /// **'Gérer les associations'** + String get adminManageAssociations; + + /// No description provided for @adminAddAssociation. + /// + /// In fr, this message translates to: + /// **'Ajouter une association'** + String get adminAddAssociation; + + /// No description provided for @adminAssociationName. + /// + /// In fr, this message translates to: + /// **'Nom de l\'association'** + String get adminAssociationName; + + /// No description provided for @adminSelectGroupAssociationManager. + /// + /// In fr, this message translates to: + /// **'Séléctionner roupe gestionnaire de l\'association'** + String get adminSelectGroupAssociationManager; + + /// Modifier les informations de l'association + /// + /// In fr, this message translates to: + /// **'Modifier l\'association : {associationName}'** + String adminEditAssociation(String associationName); + + /// Groupe qui gère l'association + /// + /// In fr, this message translates to: + /// **'Groupe gestionnaire : {groupName}'** + String adminManagerGroup(String groupName); + + /// No description provided for @adminAssociationCreated. + /// + /// In fr, this message translates to: + /// **'Association créée'** + String get adminAssociationCreated; + + /// No description provided for @adminAssociationUpdated. + /// + /// In fr, this message translates to: + /// **'Association mise à jour'** + String get adminAssociationUpdated; + + /// No description provided for @adminAssociationCreationError. + /// + /// In fr, this message translates to: + /// **'Échec de la création de l\'association'** + String get adminAssociationCreationError; - /// No description provided for @adminAnnouncers. + /// No description provided for @adminAssociationUpdateError. /// /// In fr, this message translates to: - /// **'Annonceurs'** - String get adminAnnouncers; + /// **'Échec de la mise à jour de l\'association'** + String get adminAssociationUpdateError; - /// No description provided for @adminManageAnnouncers. + /// No description provided for @adminUpdatedAssociationLogo. /// /// In fr, this message translates to: - /// **'Gérer les annonceurs'** - String get adminManageAnnouncers; + /// **'Logo de l\'association mis à jour'** + String get adminUpdatedAssociationLogo; - /// No description provided for @adminDeleteAnnouncer. + /// No description provided for @adminTooHeavyLogo. /// /// In fr, this message translates to: - /// **'Supprimer cet annonceur ?'** - String get adminDeleteAnnouncer; + /// **'Le logo de l\'association est trop lourd, il doit faire moins de 4 Mo'** + String get adminTooHeavyLogo; - /// No description provided for @adminDeleteAnnouncerDescription. + /// No description provided for @adminFailedToUpdateAssociationLogo. /// /// In fr, this message translates to: - /// **'Supprimer cet annonceurs supprimera toutes ses annonces.'** - String get adminDeleteAnnouncerDescription; + /// **'Échec de la mise à jour du logo de l\'association'** + String get adminFailedToUpdateAssociationLogo; + + /// No description provided for @adminChooseGroup. + /// + /// In fr, this message translates to: + /// **'Choisir un groupe'** + String get adminChooseGroup; + + /// No description provided for @adminChooseAssociationManagerGroup. + /// + /// In fr, this message translates to: + /// **'Choisir un groupe gestionnaire pour l\'association'** + String get adminChooseAssociationManagerGroup; + + /// No description provided for @adminConfirm. + /// + /// In fr, this message translates to: + /// **'Valider'** + String get adminConfirm; /// No description provided for @advertAdd. /// diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 1308b82bcb..16c4dbf289 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -426,20 +426,63 @@ class AppLocalizationsEn extends AppLocalizations { String get adminAdmin => 'Admin'; @override - String get adminAdverts => 'Adverts'; + String get adminAssociations => 'Associations'; @override - String get adminAnnouncers => 'Announcers'; + String get adminManageAssociations => 'Manage associations'; @override - String get adminManageAnnouncers => 'Manage announcers'; + String get adminAddAssociation => 'Add association'; @override - String get adminDeleteAnnouncer => 'Delete announcer?'; + String get adminAssociationName => 'Association name'; @override - String get adminDeleteAnnouncerDescription => - 'Are you sure you want to delete this announcer? All their adverts will be deleted.'; + String get adminSelectGroupAssociationManager => + 'Select a group to manage this association'; + + @override + String adminEditAssociation(String associationName) { + return 'Edit association : $associationName'; + } + + @override + String adminManagerGroup(String groupName) { + return 'Manager group : $groupName'; + } + + @override + String get adminAssociationCreated => 'Association created'; + + @override + String get adminAssociationUpdated => 'Association updated'; + + @override + String get adminAssociationCreationError => + 'Error while creating association'; + + @override + String get adminAssociationUpdateError => 'Error while updating association'; + + @override + String get adminUpdatedAssociationLogo => 'Association logo updated'; + + @override + String get adminTooHeavyLogo => 'Logo too heavy, maximum size is 4MB'; + + @override + String get adminFailedToUpdateAssociationLogo => + 'Failed to update association logo'; + + @override + String get adminChooseGroup => 'Choose a group'; + + @override + String get adminChooseAssociationManagerGroup => + 'Choose a group to manage this association'; + + @override + String get adminConfirm => 'Confirm'; @override String get advertAdd => 'Add'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 431c44ad08..9a326184f0 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -429,20 +429,65 @@ class AppLocalizationsFr extends AppLocalizations { String get adminAdmin => 'Admin'; @override - String get adminAdverts => 'Annonces'; + String get adminAssociations => 'Associations'; @override - String get adminAnnouncers => 'Annonceurs'; + String get adminManageAssociations => 'Gérer les associations'; @override - String get adminManageAnnouncers => 'Gérer les annonceurs'; + String get adminAddAssociation => 'Ajouter une association'; @override - String get adminDeleteAnnouncer => 'Supprimer cet annonceur ?'; + String get adminAssociationName => 'Nom de l\'association'; @override - String get adminDeleteAnnouncerDescription => - 'Supprimer cet annonceurs supprimera toutes ses annonces.'; + String get adminSelectGroupAssociationManager => + 'Séléctionner roupe gestionnaire de l\'association'; + + @override + String adminEditAssociation(String associationName) { + return 'Modifier l\'association : $associationName'; + } + + @override + String adminManagerGroup(String groupName) { + return 'Groupe gestionnaire : $groupName'; + } + + @override + String get adminAssociationCreated => 'Association créée'; + + @override + String get adminAssociationUpdated => 'Association mise à jour'; + + @override + String get adminAssociationCreationError => + 'Échec de la création de l\'association'; + + @override + String get adminAssociationUpdateError => + 'Échec de la mise à jour de l\'association'; + + @override + String get adminUpdatedAssociationLogo => 'Logo de l\'association mis à jour'; + + @override + String get adminTooHeavyLogo => + 'Le logo de l\'association est trop lourd, il doit faire moins de 4 Mo'; + + @override + String get adminFailedToUpdateAssociationLogo => + 'Échec de la mise à jour du logo de l\'association'; + + @override + String get adminChooseGroup => 'Choisir un groupe'; + + @override + String get adminChooseAssociationManagerGroup => + 'Choisir un groupe gestionnaire pour l\'association'; + + @override + String get adminConfirm => 'Valider'; @override String get advertAdd => 'Ajouter'; From 65a5246a4611cc1529b63a5237a4757726c00198 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sun, 17 Aug 2025 15:15:51 +0200 Subject: [PATCH 277/473] missing translations --- lib/admin/ui/pages/main_page/main_page.dart | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/admin/ui/pages/main_page/main_page.dart b/lib/admin/ui/pages/main_page/main_page.dart index c46e24f0e0..1fdbe0613d 100644 --- a/lib/admin/ui/pages/main_page/main_page.dart +++ b/lib/admin/ui/pages/main_page/main_page.dart @@ -79,10 +79,13 @@ class AdminMainPage extends HookConsumerWidget { onTap: () => QR.to(AdminRouter.root + AdminRouter.associationMemberships), ), - Text("Associations", style: Theme.of(context).textTheme.titleLarge), + Text( + localizeWithContext.adminAssociations, + style: Theme.of(context).textTheme.titleLarge, + ), ListItem( - title: "Associations", - subtitle: "Gérer les associations", + title: localizeWithContext.adminAssociations, + subtitle: localizeWithContext.adminManageAssociations, onTap: () { QR.to(AdminRouter.root + AdminRouter.association); }, From 102d41781755e22c629000295791b243b00d449b Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+foucblg@users.noreply.github.com> Date: Tue, 19 Aug 2025 21:39:24 +0200 Subject: [PATCH 278/473] add super admin in module list (#35) * add super admin in module list * add superadmin * fix : test * fix test * Changes * remove comment --------- Co-authored-by: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> --- lib/admin/router.dart | 3 +++ lib/settings/providers/module_list_provider.dart | 15 +++++++++++---- .../providers/is_super_admin_provider.dart | 7 +++++++ lib/super_admin/router.dart | 4 ++-- lib/user/class/user.dart | 7 +++++++ test/admin/group_list_provider_test.dart | 1 + test/user/user_test.dart | 2 ++ 7 files changed, 33 insertions(+), 6 deletions(-) create mode 100644 lib/super_admin/providers/is_super_admin_provider.dart diff --git a/lib/admin/router.dart b/lib/admin/router.dart index d3af7a4b0e..95fa2cbafb 100644 --- a/lib/admin/router.dart +++ b/lib/admin/router.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/admin/providers/is_admin_provider.dart'; import 'package:titan/admin/ui/pages/groups/edit_group_page/edit_group_page.dart' deferred as edit_group_page; import 'package:titan/admin/ui/pages/main_page/main_page.dart' @@ -24,6 +25,7 @@ import 'package:titan/admin/ui/pages/membership/add_edit_user_membership_page/ad deferred as add_edit_user_membership_page; import 'package:titan/admin/ui/pages/association_page/association_page.dart' deferred as association_page; +import 'package:titan/tools/middlewares/admin_middleware.dart'; import 'package:titan/tools/middlewares/authenticated_middleware.dart'; import 'package:titan/tools/middlewares/deferred_middleware.dart'; import 'package:qlevar_router/qlevar_router.dart'; @@ -58,6 +60,7 @@ class AdminRouter { builder: () => main_page.AdminMainPage(), middleware: [ AuthenticatedMiddleware(ref), + AdminMiddleware(ref, isAdminProvider), DeferredLoadingMiddleware(main_page.loadLibrary), ], pageType: QCustomPage( diff --git a/lib/settings/providers/module_list_provider.dart b/lib/settings/providers/module_list_provider.dart index 3763968e2f..669eb1c780 100644 --- a/lib/settings/providers/module_list_provider.dart +++ b/lib/settings/providers/module_list_provider.dart @@ -22,6 +22,7 @@ import 'package:titan/recommendation/router.dart'; import 'package:titan/seed-library/router.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:titan/settings/router.dart'; +import 'package:titan/super_admin/providers/is_super_admin_provider.dart'; import 'package:titan/super_admin/router.dart'; import 'package:titan/vote/router.dart'; @@ -34,8 +35,12 @@ final modulesProvider = StateNotifierProvider>(( .toList(); final isAdmin = ref.watch(isAdminProvider); + final isSuperAdmin = ref.watch(isSuperAdminProvider); - ModulesNotifier modulesNotifier = ModulesNotifier(isAdmin: isAdmin); + ModulesNotifier modulesNotifier = ModulesNotifier( + isAdmin: isAdmin, + isSuperAdmin: isSuperAdmin, + ); modulesNotifier.loadModules(myModulesRoot); return modulesNotifier; }); @@ -44,6 +49,7 @@ class ModulesNotifier extends StateNotifier> { String dbModule = "modules"; String dbAllModules = "allModules"; final bool isAdmin; + final bool isSuperAdmin; final eq = const DeepCollectionEquality.unordered(); List allModules = [ HomeRouter.module, @@ -62,9 +68,9 @@ class ModulesNotifier extends StateNotifier> { RecommendationRouter.module, VoteRouter.module, SeedLibraryRouter.module, - AdminRouter.module, ]; - ModulesNotifier({required this.isAdmin}) : super([]); + ModulesNotifier({required this.isAdmin, required this.isSuperAdmin}) + : super([]); void saveModules() { SharedPreferences.getInstance().then((prefs) { @@ -130,7 +136,8 @@ class ModulesNotifier extends StateNotifier> { } allModules.addAll([ SettingsRouter.module, - if (isAdmin) SuperAdminRouter.module, + if (isAdmin) AdminRouter.module, + if (isSuperAdmin) SuperAdminRouter.module, ]); state = allModules; } diff --git a/lib/super_admin/providers/is_super_admin_provider.dart b/lib/super_admin/providers/is_super_admin_provider.dart new file mode 100644 index 0000000000..91235065a4 --- /dev/null +++ b/lib/super_admin/providers/is_super_admin_provider.dart @@ -0,0 +1,7 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/user/providers/user_provider.dart'; + +final isSuperAdminProvider = StateProvider((ref) { + final me = ref.watch(userProvider); + return me.isSuperAdmin; +}); diff --git a/lib/super_admin/router.dart b/lib/super_admin/router.dart index 5a91468c77..1ebec9c866 100644 --- a/lib/super_admin/router.dart +++ b/lib/super_admin/router.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/admin/providers/is_admin_provider.dart'; +import 'package:titan/super_admin/providers/is_super_admin_provider.dart'; import 'package:titan/super_admin/ui/pages/edit_module_visibility/edit_module_visibility.dart' deferred as edit_module_visibility; @@ -48,7 +48,7 @@ class SuperAdminRouter { builder: () => main_page.SuperAdminMainPage(), middleware: [ AuthenticatedMiddleware(ref), - AdminMiddleware(ref, isAdminProvider), + AdminMiddleware(ref, isSuperAdminProvider), DeferredLoadingMiddleware(main_page.loadLibrary), ], pageType: QCustomPage( diff --git a/lib/user/class/user.dart b/lib/user/class/user.dart index 6a27978926..60b64ca486 100644 --- a/lib/user/class/user.dart +++ b/lib/user/class/user.dart @@ -19,6 +19,7 @@ class User { required this.phone, required this.createdOn, required this.groups, + required this.isSuperAdmin, }); late final String name; late final String firstname; @@ -32,6 +33,7 @@ class User { late final String? phone; late final DateTime createdOn; late final List groups; + late final bool isSuperAdmin; User.fromJson(Map json) { name = capitaliseAll(json['name']); @@ -56,6 +58,7 @@ class User { groups = List.from( json['groups'], ).map((e) => SimpleGroup.fromJson(e)).toList(); + isSuperAdmin = json['is_super_admin'] ?? false; } Map toJson() { @@ -74,6 +77,7 @@ class User { data['phone'] = phone; data['created_on'] = processDateToAPI(createdOn); data['groups'] = groups.map((e) => e.toJson()).toList(); + data['is_super_admin'] = isSuperAdmin; return data; } @@ -90,6 +94,7 @@ class User { phone = null; createdOn = DateTime.now(); groups = []; + isSuperAdmin = false; } User copyWith({ @@ -105,6 +110,7 @@ class User { String? phone, DateTime? createdOn, List? groups, + bool? isSuperAdmin, }) { return User( name: name ?? this.name, @@ -119,6 +125,7 @@ class User { phone: phone, createdOn: createdOn ?? this.createdOn, groups: groups ?? this.groups, + isSuperAdmin: isSuperAdmin ?? this.isSuperAdmin, ); } diff --git a/test/admin/group_list_provider_test.dart b/test/admin/group_list_provider_test.dart index 5b43e85723..974a4cf558 100644 --- a/test/admin/group_list_provider_test.dart +++ b/test/admin/group_list_provider_test.dart @@ -61,6 +61,7 @@ void main() { floor: '', phone: '', promo: null, + isSuperAdmin: false, ); final GroupListNotifier groupNotifier = GroupListNotifier( groupRepository: mockGroup, diff --git a/test/user/user_test.dart b/test/user/user_test.dart index 8c6869ed35..bb17818ced 100644 --- a/test/user/user_test.dart +++ b/test/user/user_test.dart @@ -122,6 +122,7 @@ void main() { groups: [], phone: 'phone', promo: null, + isSuperAdmin: false, ); expect( user.toString(), @@ -158,6 +159,7 @@ void main() { "groups": [], "phone": "phone", "promo": null, + "is_super_admin": false, }); }); }); From 605580f377e07b4bc4cb1fddf7288f5211d16524 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Tue, 19 Aug 2025 21:45:09 +0200 Subject: [PATCH 279/473] Add navbar on feed scroll --- lib/feed/ui/pages/main_page/main_page.dart | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/feed/ui/pages/main_page/main_page.dart b/lib/feed/ui/pages/main_page/main_page.dart index 667328679b..b5526f33cb 100644 --- a/lib/feed/ui/pages/main_page/main_page.dart +++ b/lib/feed/ui/pages/main_page/main_page.dart @@ -10,6 +10,7 @@ import 'package:titan/feed/router.dart'; import 'package:titan/feed/ui/feed.dart'; import 'package:titan/feed/ui/pages/main_page/feed_timeline.dart'; import 'package:titan/feed/ui/pages/main_page/filter_news.dart'; +import 'package:titan/navigation/providers/navbar_visibility_provider.dart'; import 'package:titan/navigation/ui/scroll_to_hide_navbar.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; @@ -27,6 +28,9 @@ class FeedMainPage extends HookConsumerWidget { final newsNotifier = ref.watch(newsListProvider.notifier); final isSuperAdmin = ref.watch(isAdminProvider); final scrollController = useScrollController(); + final navbarVisibilityNotifier = ref.watch( + navbarVisibilityProvider.notifier, + ); useEffect(() { if (news.hasValue && news.value!.isNotEmpty) { @@ -63,6 +67,7 @@ class FeedMainPage extends HookConsumerWidget { } scrollController.jumpTo(scrollPosition); + navbarVisibilityNotifier.show(); } }); } From 840e7d93ef8bf828db8d1577bb3630ec75906dcd Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Tue, 19 Aug 2025 23:09:18 +0200 Subject: [PATCH 280/473] fix : settings refresher --- lib/settings/ui/pages/main_page/main_page.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/settings/ui/pages/main_page/main_page.dart b/lib/settings/ui/pages/main_page/main_page.dart index dd1e68e0c4..9c638d47eb 100644 --- a/lib/settings/ui/pages/main_page/main_page.dart +++ b/lib/settings/ui/pages/main_page/main_page.dart @@ -69,7 +69,6 @@ class SettingsMainPage extends HookConsumerWidget { controller: ScrollController(), onRefresh: () async { await notificationTopicListNotifier.loadNotificationTopicList(); - await meNotifier.loadMe(); }, child: Container( padding: const EdgeInsets.symmetric(horizontal: 20), From dd562d65275670a3d05a7ef988313162eda12b41 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Tue, 19 Aug 2025 21:54:43 +0200 Subject: [PATCH 281/473] fix : don't switch module in navbar --- lib/navigation/ui/navigation_template.dart | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/lib/navigation/ui/navigation_template.dart b/lib/navigation/ui/navigation_template.dart index a868bd161d..f7667fc620 100644 --- a/lib/navigation/ui/navigation_template.dart +++ b/lib/navigation/ui/navigation_template.dart @@ -101,16 +101,7 @@ class NavigationTemplate extends HookConsumerWidget { return FloatingNavbarItem( module: module, onTap: () { - navbarListModuleNotifier.pushModule( - module, - ); - pathForwardingNotifier.forward( - module.root, - ); - WidgetsBinding.instance - .addPostFrameCallback((_) { - QR.to(module.root); - }); + QR.to(module.root); }, ); }), From bce3af0e890a1a686a7d047c8b170ea13b04d52c Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Tue, 19 Aug 2025 23:12:03 +0200 Subject: [PATCH 282/473] lint --- lib/navigation/ui/navigation_template.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/navigation/ui/navigation_template.dart b/lib/navigation/ui/navigation_template.dart index f7667fc620..f5d023ceb5 100644 --- a/lib/navigation/ui/navigation_template.dart +++ b/lib/navigation/ui/navigation_template.dart @@ -34,9 +34,6 @@ class NavigationTemplate extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final user = ref.watch(userProvider); final navbarListModule = ref.watch(navbarListModuleProvider); - final navbarListModuleNotifier = ref.watch( - navbarListModuleProvider.notifier, - ); final displayQuit = ref.watch(displayQuitProvider); final shouldSetup = ref.watch(shouldSetupProvider); final shouldSetupNotifier = ref.read(shouldSetupProvider.notifier); From eb819ca0e6bd1fcabe0f4d4316d43ecc7815be48 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Mon, 18 Aug 2025 19:00:40 +0200 Subject: [PATCH 283/473] wip --- .../providers/is_feed_admin_provider.dart | 12 + lib/feed/repositories/news_repository.dart | 104 +-------- .../pages/add_event_page/add_event_page.dart | 210 +++++++++++------- .../event_handling_page.dart | 71 +++--- lib/feed/ui/pages/main_page/main_page.dart | 10 +- lib/tools/ui/styleguide/icon_button.dart | 1 - 6 files changed, 194 insertions(+), 214 deletions(-) create mode 100644 lib/feed/providers/is_feed_admin_provider.dart diff --git a/lib/feed/providers/is_feed_admin_provider.dart b/lib/feed/providers/is_feed_admin_provider.dart new file mode 100644 index 0000000000..15f2780884 --- /dev/null +++ b/lib/feed/providers/is_feed_admin_provider.dart @@ -0,0 +1,12 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/user/providers/user_provider.dart'; + +final isFeedAdminProvider = StateProvider((ref) { + final me = ref.watch(userProvider); + for (final group in me.groups) { + if (group.name == "feed_admin") { + return true; + } + } + return false; +}); diff --git a/lib/feed/repositories/news_repository.dart b/lib/feed/repositories/news_repository.dart index dcff66668a..78934f74b8 100644 --- a/lib/feed/repositories/news_repository.dart +++ b/lib/feed/repositories/news_repository.dart @@ -1,5 +1,4 @@ import 'package:titan/feed/class/news.dart'; -import 'package:titan/feed/tools/function.dart'; import 'package:titan/tools/repository/repository.dart'; class NewsRepository extends Repository { @@ -8,109 +7,20 @@ class NewsRepository extends Repository { final ext = "feed/"; Future> getPublishedNews() async { - // return List.from( - // (await getList(suffix: "news")).map((e) => News.fromJson(e)), - // ); - return Future.value([ - News( - id: '', - title: 'Test', - start: DateTime.now().subtract(const Duration(days: 1)), - entity: 'BDE', - module: 'post', - moduleObjectId: '', - status: NewsStatus.published, - ), - News( - id: '', - title: 'Vote', - start: DateTime.now().subtract(const Duration(days: 2)), - end: DateTime.now().add(const Duration(days: 2)), - actionStart: DateTime.now().subtract(const Duration(days: 2)), - entity: 'CAA', - module: 'campagne', - moduleObjectId: '', - status: NewsStatus.published, - ), - News( - id: '', - title: 'Rewass', - start: DateTime.now().add(const Duration(days: 3)), - end: DateTime.now().add(const Duration(days: 7)), - actionStart: DateTime.now().subtract(const Duration(days: 1)), - entity: 'DBS', - module: 'event', - moduleObjectId: '', - location: 'Foyer', - status: NewsStatus.published, - ), - News( - id: '', - title: 'Test 4', - start: DateTime.now().subtract(const Duration(days: 2)), - end: DateTime.now().subtract(const Duration(days: 1)), - actionStart: DateTime.now().subtract(const Duration(days: 3)), - entity: 'DBS', - module: 'event', - moduleObjectId: '', - status: NewsStatus.published, - ), - ]); + return List.from( + (await getList(suffix: "news")).map((e) => News.fromJson(e)), + ); } Future createNews(News news) async { + print(news.toJson()); return News.fromJson(await create(news.toJson(), suffix: "news")); } Future> getAllNews() async { - // return List.from( - // (await getList(suffix: "admin/news")).map((e) => News.fromJson(e)), - // ); - return Future.value([ - News( - id: '', - title: 'Test', - start: DateTime.now().subtract(const Duration(days: 1)), - entity: 'BDE', - module: 'post', - moduleObjectId: '', - status: NewsStatus.waitingApproval, - ), - News( - id: '', - title: 'Vote', - start: DateTime.now().subtract(const Duration(days: 2)), - end: DateTime.now().add(const Duration(days: 2)), - actionStart: DateTime.now().subtract(const Duration(days: 2)), - entity: 'CAA', - module: 'campagne', - moduleObjectId: '', - status: NewsStatus.waitingApproval, - ), - News( - id: '', - title: 'Rewass', - start: DateTime.now().add(const Duration(days: 3)), - end: DateTime.now().add(const Duration(days: 7)), - actionStart: DateTime.now().subtract(const Duration(days: 1)), - entity: 'DBS', - module: 'event', - moduleObjectId: '', - location: 'Foyer', - status: NewsStatus.waitingApproval, - ), - News( - id: '', - title: 'Test 4', - start: DateTime.now().subtract(const Duration(days: 2)), - end: DateTime.now().subtract(const Duration(days: 1)), - actionStart: DateTime.now().subtract(const Duration(days: 3)), - entity: 'DBS', - module: 'event', - moduleObjectId: '', - status: NewsStatus.waitingApproval, - ), - ]); + return List.from( + (await getList(suffix: "admin/news")).map((e) => News.fromJson(e)), + ); } Future approveNews(String id) async { diff --git a/lib/feed/ui/pages/add_event_page/add_event_page.dart b/lib/feed/ui/pages/add_event_page/add_event_page.dart index 2215b3c9be..b6d7f266e8 100644 --- a/lib/feed/ui/pages/add_event_page/add_event_page.dart +++ b/lib/feed/ui/pages/add_event_page/add_event_page.dart @@ -1,9 +1,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/feed/class/news.dart'; +import 'package:titan/feed/providers/admin_news_list_provider.dart'; import 'package:titan/feed/ui/feed.dart'; +import 'package:titan/tools/functions.dart'; import 'package:titan/tools/ui/styleguide/button.dart'; import 'package:titan/tools/ui/styleguide/date_entry.dart'; +import 'package:titan/tools/ui/styleguide/icon_button.dart'; import 'package:titan/tools/ui/styleguide/image_entry.dart'; import 'package:titan/tools/ui/styleguide/text_entry.dart'; @@ -13,13 +17,18 @@ class AddEventPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final eventTitleController = useTextEditingController(); - final eventDescriptionController = useTextEditingController(); final eventLocationController = useTextEditingController(); final shotgunDateController = useTextEditingController(); final eventExternalLinkController = useTextEditingController(); final eventStartDateController = useTextEditingController(); final eventEndDateController = useTextEditingController(); + final adminNewsListNotifier = ref.watch(adminNewsListProvider.notifier); + + void displayToastWithContext(TypeMsg type, String msg) { + displayToast(context, type, msg); + } + return FeedTemplate( child: Expanded( child: SingleChildScrollView( @@ -34,79 +43,111 @@ class AddEventPage extends HookConsumerWidget { crossAxisAlignment: CrossAxisAlignment.stretch, children: [ TextEntry(label: "Titre", controller: eventTitleController), - TextEntry( - label: "Description", - controller: eventDescriptionController, - maxLines: 5, - minLines: 3, - ), - DateEntry( - onTap: () async { - final pickedDate = await showDatePicker( - context: context, - initialDate: DateTime.now(), - firstDate: DateTime.now(), - lastDate: DateTime.now().add(const Duration(days: 365)), - ); - if (pickedDate != null) { - final formattedDate = - "${pickedDate.day.toString().padLeft(2, '0')}/${pickedDate.month.toString().padLeft(2, '0')}/${pickedDate.year}"; - eventStartDateController.text = formattedDate; - } - }, - title: "Date de début", - subtitle: "Sélectionnez une date", + SizedBox(height: 10), + Row( + children: [ + Expanded( + child: TextEntry( + label: "Date de début ", + controller: eventStartDateController, + enabled: false, + ), + ), + CustomIconButton( + icon: const Icon( + Icons.calendar_today, + color: Colors.white, + ), + onPressed: () async { + final date = await showDatePicker( + context: context, + initialDate: DateTime.now(), + firstDate: DateTime.now(), + lastDate: DateTime.now().add( + const Duration(days: 365), + ), + ); + if (date != null) { + eventStartDateController.text = + "${date.day.toString().padLeft(2, '0')}/${date.month.toString().padLeft(2, '0')}/${date.year}"; + } + }, + ), + ], ), - DateEntry( - onTap: () async { - DateTime startDate = DateTime.now(); - if (eventStartDateController.text.isNotEmpty) { - final parts = eventStartDateController.text.split('/'); - startDate = DateTime( - int.parse(parts[2]), - int.parse(parts[1]), - int.parse(parts[0]), - ); - } - - final pickedDate = await showDatePicker( - context: context, - initialDate: startDate, - firstDate: startDate, - lastDate: startDate.add(const Duration(days: 365)), - ); - if (pickedDate != null) { - final formattedDate = - "${pickedDate.day.toString().padLeft(2, '0')}/${pickedDate.month.toString().padLeft(2, '0')}/${pickedDate.year}"; - eventEndDateController.text = formattedDate; - } - }, - title: "Date de fin", - subtitle: "Sélectionnez une date", + SizedBox(height: 10), + Row( + children: [ + Expanded( + child: TextEntry( + label: "Date de fin ", + controller: eventEndDateController, + enabled: false, + ), + ), + CustomIconButton( + icon: const Icon( + Icons.calendar_today, + color: Colors.white, + ), + onPressed: () async { + final date = await showDatePicker( + context: context, + initialDate: DateTime.now(), + firstDate: DateTime.now(), + lastDate: DateTime.now().add( + const Duration(days: 365), + ), + ); + if (date != null) { + eventEndDateController.text = + "${date.day.toString().padLeft(2, '0')}/${date.month.toString().padLeft(2, '0')}/${date.year}"; + } + }, + ), + ], ), + SizedBox(height: 10), TextEntry(label: "Lieu", controller: eventLocationController), - DateEntry( - onTap: () async { - final pickedDate = await showDatePicker( - context: context, - initialDate: DateTime.now(), - firstDate: DateTime.now(), - lastDate: DateTime.now().add(const Duration(days: 365)), - ); - if (pickedDate != null) { - final formattedDate = - "${pickedDate.day.toString().padLeft(2, '0')}/${pickedDate.month.toString().padLeft(2, '0')}/${pickedDate.year}"; - shotgunDateController.text = formattedDate; - } - }, - title: "Date et heure du SG", - subtitle: "Sélectionnez une date", + SizedBox(height: 10), + Row( + children: [ + Expanded( + child: TextEntry( + label: "Date du SG ", + controller: shotgunDateController, + enabled: false, + ), + ), + CustomIconButton( + icon: const Icon( + Icons.calendar_today, + color: Colors.white, + ), + onPressed: () async { + final date = await showDatePicker( + context: context, + initialDate: DateTime.now(), + firstDate: DateTime.now(), + lastDate: DateTime.now().add( + const Duration(days: 365), + ), + ); + if (date != null) { + shotgunDateController.text = + "${date.day.toString().padLeft(2, '0')}/${date.month.toString().padLeft(2, '0')}/${date.year}"; + } + }, + ), + ], ), + SizedBox(height: 10), TextEntry( label: "Lien externe pour le SG", controller: eventExternalLinkController, canBeEmpty: true, ), + const SizedBox(height: 10), ImageEntry( title: "Image", subtitle: "Sélectionnez une image", @@ -119,26 +160,43 @@ class AddEventPage extends HookConsumerWidget { const SizedBox(height: 40), Button( text: "Créer l'événement", - onPressed: () { + onPressed: () async { if (eventTitleController.text.isEmpty || - eventDescriptionController.text.isEmpty || eventStartDateController.text.isEmpty || eventEndDateController.text.isEmpty || eventLocationController.text.isEmpty) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text( - 'Veuillez remplir tous les champs obligatoires', - ), - ), + displayToastWithContext( + TypeMsg.error, + "Veuillez remplir tous les champs obligatoires.", ); return; } - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Événement créé avec succès'), + final newNews = News.empty().copyWith( + title: eventTitleController.text, + start: DateTime.parse( + processDateBack(eventStartDateController.text), + ), + end: DateTime.parse( + processDateBack(eventEndDateController.text), ), + location: eventLocationController.text, + actionStart: DateTime.parse( + processDateBack(shotgunDateController.text), + ), + // externalLink: eventExternalLinkController.text, ); + final value = await adminNewsListNotifier.addNews(newNews); + if (value) { + displayToastWithContext( + TypeMsg.msg, + "Événement créé avec succès !", + ); + } else { + displayToastWithContext( + TypeMsg.error, + "Échec de la création de l'événement.", + ); + } }, ), const SizedBox(height: 80), diff --git a/lib/feed/ui/pages/event_handling_page/event_handling_page.dart b/lib/feed/ui/pages/event_handling_page/event_handling_page.dart index b0edea3943..99c2dfc575 100644 --- a/lib/feed/ui/pages/event_handling_page/event_handling_page.dart +++ b/lib/feed/ui/pages/event_handling_page/event_handling_page.dart @@ -10,6 +10,7 @@ import 'package:titan/feed/ui/pages/event_handling_page/admin_event_card.dart'; import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; +import 'package:titan/tools/ui/layouts/refresher.dart'; import 'package:titan/tools/ui/styleguide/horizontal_multi_select.dart'; class EventHandlingPage extends HookConsumerWidget { @@ -22,10 +23,13 @@ class EventHandlingPage extends HookConsumerWidget { final selectedFilter = useState(NewsFilterType.pending); return FeedTemplate( - child: RefreshIndicator( - onRefresh: () => newsListNotifier.loadNewsList(), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 20.0), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + child: Refresher( + onRefresh: () { + return newsListNotifier.loadNewsList(); + }, + controller: ScrollController(), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -47,7 +51,8 @@ class EventHandlingPage extends HookConsumerWidget { height: 40, child: HorizontalMultiSelect( items: NewsFilterType.values, - itemBuilder: (context, item, index, selected) { + selectedItem: selectedFilter.value, + itemBuilder: (context, item, _, selected) { final filterName = _getFilterName(context, item); return Text( filterName, @@ -69,38 +74,34 @@ class EventHandlingPage extends HookConsumerWidget { const SizedBox(height: 16), - Expanded( - child: AsyncChild( - value: newsListAsync, - builder: (context, newsList) { - final filteredNews = _getFilteredNews( - newsList, - selectedFilter.value, - ); - - if (filteredNews.isEmpty) { - return Center( - child: Text( - _getEmptyMessage(context, selectedFilter.value), - style: const TextStyle( - color: ColorConstants.tertiary, - ), - ), - ); - } + AsyncChild( + value: newsListAsync, + builder: (context, newsList) { + final filteredNews = _getFilteredNews( + newsList, + selectedFilter.value, + ); - return ListView.builder( - physics: const BouncingScrollPhysics(), - itemCount: filteredNews.length + 1, - itemBuilder: (context, index) { - if (index == filteredNews.length) { - return const SizedBox(height: 80); - } - return AdminEventCard(news: filteredNews[index]); - }, + if (filteredNews.isEmpty) { + return Center( + child: Text( + _getEmptyMessage(context, selectedFilter.value), + style: const TextStyle(color: ColorConstants.tertiary), + ), ); - }, - ), + } + + return ListView.builder( + physics: const BouncingScrollPhysics(), + itemCount: filteredNews.length + 1, + itemBuilder: (context, index) { + if (index == filteredNews.length) { + return const SizedBox(height: 80); + } + return AdminEventCard(news: filteredNews[index]); + }, + ); + }, ), ], ), diff --git a/lib/feed/ui/pages/main_page/main_page.dart b/lib/feed/ui/pages/main_page/main_page.dart index b5526f33cb..c5070420ed 100644 --- a/lib/feed/ui/pages/main_page/main_page.dart +++ b/lib/feed/ui/pages/main_page/main_page.dart @@ -3,8 +3,8 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:qlevar_router/qlevar_router.dart'; -import 'package:titan/admin/providers/is_admin_provider.dart'; import 'package:titan/feed/class/news.dart'; +import 'package:titan/feed/providers/is_feed_admin_provider.dart'; import 'package:titan/feed/providers/news_list_provider.dart'; import 'package:titan/feed/router.dart'; import 'package:titan/feed/ui/feed.dart'; @@ -26,7 +26,7 @@ class FeedMainPage extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final news = ref.watch(newsListProvider); final newsNotifier = ref.watch(newsListProvider.notifier); - final isSuperAdmin = ref.watch(isAdminProvider); + final isFeedAdmin = ref.watch(isFeedAdminProvider); final scrollController = useScrollController(); final navbarVisibilityNotifier = ref.watch( navbarVisibilityProvider.notifier, @@ -60,7 +60,7 @@ class FeedMainPage extends HookConsumerWidget { final currentItem = newsList[i]; final itemHeight = - (currentItem.actionStart != null || isSuperAdmin) + (currentItem.actionStart != null || isFeedAdmin) ? 200.0 : 160.0; scrollPosition += itemHeight; @@ -110,7 +110,7 @@ class FeedMainPage extends HookConsumerWidget { color: ColorConstants.title, ), ), - if (isSuperAdmin) + if (isFeedAdmin) CustomIconButton( icon: HeroIcon( HeroIcons.userGroup, @@ -172,7 +172,7 @@ class FeedMainPage extends HookConsumerWidget { ), ) : FeedTimeline( - isAdmin: isSuperAdmin, + isAdmin: isFeedAdmin, items: news, onItemTap: (item) {}, ), diff --git a/lib/tools/ui/styleguide/icon_button.dart b/lib/tools/ui/styleguide/icon_button.dart index de3d693af9..b359e181b2 100644 --- a/lib/tools/ui/styleguide/icon_button.dart +++ b/lib/tools/ui/styleguide/icon_button.dart @@ -77,7 +77,6 @@ class CustomIconButton extends StatelessWidget { builder: (child) => Container( height: 32, width: 32, - padding: const EdgeInsets.all(5), decoration: BoxDecoration( color: backgroundColor, borderRadius: BorderRadius.circular(10), From 84f5a2c9dfcc900298db7b843e7f396048032fc7 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Mon, 18 Aug 2025 19:00:40 +0200 Subject: [PATCH 284/473] wip --- .../providers/is_feed_admin_provider.dart | 12 ---- .../is_user_a_member_of_an_association.dart | 61 +++++++++++++++++++ .../pages/add_event_page/add_event_page.dart | 1 - lib/feed/ui/pages/main_page/main_page.dart | 2 +- 4 files changed, 62 insertions(+), 14 deletions(-) delete mode 100644 lib/feed/providers/is_feed_admin_provider.dart create mode 100644 lib/feed/providers/is_user_a_member_of_an_association.dart diff --git a/lib/feed/providers/is_feed_admin_provider.dart b/lib/feed/providers/is_feed_admin_provider.dart deleted file mode 100644 index 15f2780884..0000000000 --- a/lib/feed/providers/is_feed_admin_provider.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/user/providers/user_provider.dart'; - -final isFeedAdminProvider = StateProvider((ref) { - final me = ref.watch(userProvider); - for (final group in me.groups) { - if (group.name == "feed_admin") { - return true; - } - } - return false; -}); diff --git a/lib/feed/providers/is_user_a_member_of_an_association.dart b/lib/feed/providers/is_user_a_member_of_an_association.dart new file mode 100644 index 0000000000..e8256b5478 --- /dev/null +++ b/lib/feed/providers/is_user_a_member_of_an_association.dart @@ -0,0 +1,61 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/auth/providers/openid_provider.dart'; +import 'package:titan/tools/providers/single_notifier.dart'; +import 'package:titan/tools/token_expire_wrapper.dart'; +import 'package:titan/user/class/user.dart'; +import 'package:titan/user/repositories/user_repository.dart'; + +class IsUserAMemberOfAnAssociationNotifier extends SingleNotifier { + final IsUserAMemberOfAnAssociationRepository + isUserAMemberOfAnAssociationRepository; + IsUserAMemberOfAnAssociationNotifier({ + required this.isUserAMemberOfAnAssociationRepository, + }) : super(const AsyncValue.loading()); + Future> loadIsUserAMemberOfAnAssociation() async { + List associationList = + await isUserAMemberOfAnAssociationRepository.getAssociations(); + await load( + isUserAMemberOfAnAssociationRepository.isUserAMemberOfAnAssociation, + ); + } +} + +final asyncIsUserAMemberOfAnAssociationProvider = + StateNotifierProvider< + IsUserAMemberOfAnAssociationNotifier, + AsyncValue + >((ref) { + final UserRepository isUserAMemberOfAnAssociationRepository = ref.watch( + isUserAMemberOfAnAssociationRepository, + ); + IsUserAMemberOfAnAssociationNotifier + asyncIsUserAMemberOfAnAssociationNotifier = + IsUserAMemberOfAnAssociationNotifier( + isUserAMemberOfAnAssociationRepository: + isUserAMemberOfAnAssociationRepository, + ); + final token = ref.watch(tokenProvider); + tokenExpireWrapperAuth(ref, () async { + final isLoggedIn = ref.watch(isLoggedInProvider); + final id = ref + .watch(idProvider) + .maybeWhen(data: (value) => value, orElse: () => ""); + if (isLoggedIn && id != "" && token != "") { + return asyncIsUserAMemberOfAnAssociationNotifier + .loadIsUserAMemberOfAnAssociation(); + } + }); + return asyncIsUserAMemberOfAnAssociationNotifier; + }); + +final isUserAMemberOfAnAssociationProvider = Provider((ref) { + return ref + .watch(asyncIsUserAMemberOfAnAssociationProvider) + .maybeWhen( + data: (b) => b, + orElse: () { + return false; + }, + ); +}); diff --git a/lib/feed/ui/pages/add_event_page/add_event_page.dart b/lib/feed/ui/pages/add_event_page/add_event_page.dart index b6d7f266e8..f805a34595 100644 --- a/lib/feed/ui/pages/add_event_page/add_event_page.dart +++ b/lib/feed/ui/pages/add_event_page/add_event_page.dart @@ -6,7 +6,6 @@ import 'package:titan/feed/providers/admin_news_list_provider.dart'; import 'package:titan/feed/ui/feed.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/ui/styleguide/button.dart'; -import 'package:titan/tools/ui/styleguide/date_entry.dart'; import 'package:titan/tools/ui/styleguide/icon_button.dart'; import 'package:titan/tools/ui/styleguide/image_entry.dart'; import 'package:titan/tools/ui/styleguide/text_entry.dart'; diff --git a/lib/feed/ui/pages/main_page/main_page.dart b/lib/feed/ui/pages/main_page/main_page.dart index c5070420ed..409722867f 100644 --- a/lib/feed/ui/pages/main_page/main_page.dart +++ b/lib/feed/ui/pages/main_page/main_page.dart @@ -4,7 +4,7 @@ import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/feed/class/news.dart'; -import 'package:titan/feed/providers/is_feed_admin_provider.dart'; +import 'package:titan/feed/providers/is_user_a_member_of_an_association.dart'; import 'package:titan/feed/providers/news_list_provider.dart'; import 'package:titan/feed/router.dart'; import 'package:titan/feed/ui/feed.dart'; From 20a9b8c3ed3617136b0b74c73aed0ff428ce8748 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Mon, 18 Aug 2025 19:00:40 +0200 Subject: [PATCH 285/473] change permissions --- .../repositories/association_repository.dart | 6 +- .../providers/is_feed_admin_provider.dart | 12 ++++ .../is_user_a_member_of_an_association.dart | 63 ++----------------- lib/feed/ui/pages/main_page/main_page.dart | 11 +++- 4 files changed, 30 insertions(+), 62 deletions(-) create mode 100644 lib/feed/providers/is_feed_admin_provider.dart diff --git a/lib/admin/repositories/association_repository.dart b/lib/admin/repositories/association_repository.dart index ade3cd5577..aa24f9fcca 100644 --- a/lib/admin/repositories/association_repository.dart +++ b/lib/admin/repositories/association_repository.dart @@ -14,8 +14,10 @@ class AssociationRepository extends Repository { ); } - Future getMyAssociation() async { - return Association.fromJson(await getOne("", suffix: "me")); + Future> getMyAssociations() async { + return List.from( + (await getList(suffix: "me")).map((x) => Association.fromJson(x)), + ); } Future deleteAssociation(String associationId) async { diff --git a/lib/feed/providers/is_feed_admin_provider.dart b/lib/feed/providers/is_feed_admin_provider.dart new file mode 100644 index 0000000000..15f2780884 --- /dev/null +++ b/lib/feed/providers/is_feed_admin_provider.dart @@ -0,0 +1,12 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/user/providers/user_provider.dart'; + +final isFeedAdminProvider = StateProvider((ref) { + final me = ref.watch(userProvider); + for (final group in me.groups) { + if (group.name == "feed_admin") { + return true; + } + } + return false; +}); diff --git a/lib/feed/providers/is_user_a_member_of_an_association.dart b/lib/feed/providers/is_user_a_member_of_an_association.dart index e8256b5478..ce7160a48b 100644 --- a/lib/feed/providers/is_user_a_member_of_an_association.dart +++ b/lib/feed/providers/is_user_a_member_of_an_association.dart @@ -1,61 +1,8 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/auth/providers/openid_provider.dart'; -import 'package:titan/tools/providers/single_notifier.dart'; -import 'package:titan/tools/token_expire_wrapper.dart'; -import 'package:titan/user/class/user.dart'; -import 'package:titan/user/repositories/user_repository.dart'; +import 'package:titan/admin/repositories/association_repository.dart'; -class IsUserAMemberOfAnAssociationNotifier extends SingleNotifier { - final IsUserAMemberOfAnAssociationRepository - isUserAMemberOfAnAssociationRepository; - IsUserAMemberOfAnAssociationNotifier({ - required this.isUserAMemberOfAnAssociationRepository, - }) : super(const AsyncValue.loading()); - Future> loadIsUserAMemberOfAnAssociation() async { - List associationList = - await isUserAMemberOfAnAssociationRepository.getAssociations(); - await load( - isUserAMemberOfAnAssociationRepository.isUserAMemberOfAnAssociation, - ); - } -} - -final asyncIsUserAMemberOfAnAssociationProvider = - StateNotifierProvider< - IsUserAMemberOfAnAssociationNotifier, - AsyncValue - >((ref) { - final UserRepository isUserAMemberOfAnAssociationRepository = ref.watch( - isUserAMemberOfAnAssociationRepository, - ); - IsUserAMemberOfAnAssociationNotifier - asyncIsUserAMemberOfAnAssociationNotifier = - IsUserAMemberOfAnAssociationNotifier( - isUserAMemberOfAnAssociationRepository: - isUserAMemberOfAnAssociationRepository, - ); - final token = ref.watch(tokenProvider); - tokenExpireWrapperAuth(ref, () async { - final isLoggedIn = ref.watch(isLoggedInProvider); - final id = ref - .watch(idProvider) - .maybeWhen(data: (value) => value, orElse: () => ""); - if (isLoggedIn && id != "" && token != "") { - return asyncIsUserAMemberOfAnAssociationNotifier - .loadIsUserAMemberOfAnAssociation(); - } - }); - return asyncIsUserAMemberOfAnAssociationNotifier; - }); - -final isUserAMemberOfAnAssociationProvider = Provider((ref) { - return ref - .watch(asyncIsUserAMemberOfAnAssociationProvider) - .maybeWhen( - data: (b) => b, - orElse: () { - return false; - }, - ); +final isUserAMemberOfAnAssociationProvider = FutureProvider((ref) async { + final associationRepository = ref.watch(associationRepositoryProvider); + final associations = await associationRepository.getMyAssociations(); + return associations.isNotEmpty; }); diff --git a/lib/feed/ui/pages/main_page/main_page.dart b/lib/feed/ui/pages/main_page/main_page.dart index 409722867f..ba1b5c9c9a 100644 --- a/lib/feed/ui/pages/main_page/main_page.dart +++ b/lib/feed/ui/pages/main_page/main_page.dart @@ -4,6 +4,7 @@ import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/feed/class/news.dart'; +import 'package:titan/feed/providers/is_feed_admin_provider.dart'; import 'package:titan/feed/providers/is_user_a_member_of_an_association.dart'; import 'package:titan/feed/providers/news_list_provider.dart'; import 'package:titan/feed/router.dart'; @@ -26,6 +27,11 @@ class FeedMainPage extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final news = ref.watch(newsListProvider); final newsNotifier = ref.watch(newsListProvider.notifier); + final asyncIsUserAMemberOfAnAssociation = ref.watch( + isUserAMemberOfAnAssociationProvider, + ); + final isUserAMemberOfAnAssociation = asyncIsUserAMemberOfAnAssociation + .maybeWhen(data: (isMember) => isMember, orElse: () => false); final isFeedAdmin = ref.watch(isFeedAdminProvider); final scrollController = useScrollController(); final navbarVisibilityNotifier = ref.watch( @@ -60,7 +66,8 @@ class FeedMainPage extends HookConsumerWidget { final currentItem = newsList[i]; final itemHeight = - (currentItem.actionStart != null || isFeedAdmin) + (currentItem.actionStart != null || + isUserAMemberOfAnAssociation) ? 200.0 : 160.0; scrollPosition += itemHeight; @@ -110,7 +117,7 @@ class FeedMainPage extends HookConsumerWidget { color: ColorConstants.title, ), ), - if (isFeedAdmin) + if (isUserAMemberOfAnAssociation) CustomIconButton( icon: HeroIcon( HeroIcons.userGroup, From b8d571ccf71cef753116ad2c0fe05f71991ea31d Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Tue, 19 Aug 2025 21:28:13 +0200 Subject: [PATCH 286/473] feat: adding event to feed --- .../my_association_list_provider.dart | 30 + lib/feed/class/event_creation.dart | 101 ++++ .../providers/event_creation_provider.dart | 29 + .../is_user_a_member_of_an_association.dart | 10 +- .../event_creation_repository.dart | 19 + .../pages/add_event_page/add_event_page.dart | 530 ++++++++++++------ 6 files changed, 553 insertions(+), 166 deletions(-) create mode 100644 lib/admin/providers/my_association_list_provider.dart create mode 100644 lib/feed/class/event_creation.dart create mode 100644 lib/feed/providers/event_creation_provider.dart create mode 100644 lib/feed/repositories/event_creation_repository.dart diff --git a/lib/admin/providers/my_association_list_provider.dart b/lib/admin/providers/my_association_list_provider.dart new file mode 100644 index 0000000000..5535175ebe --- /dev/null +++ b/lib/admin/providers/my_association_list_provider.dart @@ -0,0 +1,30 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/admin/class/assocation.dart'; +import 'package:titan/admin/repositories/association_repository.dart'; +import 'package:titan/tools/providers/list_notifier.dart'; +import 'package:titan/tools/token_expire_wrapper.dart'; + +class MyAssociationListNotifier extends ListNotifier { + final AssociationRepository associationRepository; + MyAssociationListNotifier({required this.associationRepository}) + : super(const AsyncValue.loading()); + + Future>> loadAssociations() async { + return await loadList(associationRepository.getMyAssociations); + } +} + +final myAssociationListProvider = + StateNotifierProvider< + MyAssociationListNotifier, + AsyncValue> + >((ref) { + final associationRepository = ref.watch(associationRepositoryProvider); + MyAssociationListNotifier provider = MyAssociationListNotifier( + associationRepository: associationRepository, + ); + tokenExpireWrapperAuth(ref, () async { + await provider.loadAssociations(); + }); + return provider; + }); diff --git a/lib/feed/class/event_creation.dart b/lib/feed/class/event_creation.dart new file mode 100644 index 0000000000..d18a94030b --- /dev/null +++ b/lib/feed/class/event_creation.dart @@ -0,0 +1,101 @@ +import 'package:titan/tools/functions.dart'; + +class EventCreation { + late final String name; + late final DateTime start; + late final DateTime end; + late final bool allDay; + late final String location; + late final String recurrenceRule; + late final DateTime? ticketUrlOpening; + late final String associationId; + late final String? ticketUrl; + + EventCreation({ + required this.name, + required this.start, + required this.end, + required this.allDay, + required this.location, + required this.recurrenceRule, + this.ticketUrlOpening, + required this.associationId, + this.ticketUrl, + }); + + EventCreation.fromJson(Map json) { + name = json['name']; + start = processDateFromAPI(json['start']); + end = processDateFromAPI(json['end']); + allDay = json['all_day']; + location = json['location']; + recurrenceRule = json['recurrence_rule'] ?? ""; + ticketUrlOpening = json['ticket_url_opening'] != null + ? processDateFromAPI(json['ticket_url_opening']) + : null; + associationId = json['association_id']; + ticketUrl = json['ticket_url']; + } + + Map toJson() { + final Map data = {}; + + data['name'] = name; + data['start'] = processDateToAPI(start); + data['end'] = processDateToAPI(end); + data['all_day'] = allDay; + data['location'] = location; + data['recurrence_rule'] = recurrenceRule; + if (ticketUrlOpening != null) { + data['ticket_url_opening'] = processDateToAPI(ticketUrlOpening!); + } + data['association_id'] = associationId; + if (ticketUrl != null) { + data['ticket_url'] = ticketUrl; + } + data['description'] = ""; // TODO: remove + return data; + } + + EventCreation copyWith({ + String? name, + DateTime? start, + DateTime? end, + String? location, + bool? allDay, + String? recurrenceRule, + DateTime? ticketUrlOpening, + String? associationId, + String? ticketUrl, + bool? hasRoom, + }) { + return EventCreation( + name: name ?? this.name, + start: start ?? this.start, + end: end ?? this.end, + location: location ?? this.location, + recurrenceRule: recurrenceRule ?? this.recurrenceRule, + allDay: allDay ?? this.allDay, + ticketUrlOpening: ticketUrlOpening ?? this.ticketUrlOpening, + associationId: associationId ?? this.associationId, + ticketUrl: ticketUrl ?? this.ticketUrl, + ); + } + + EventCreation.empty() { + name = ''; + start = DateTime.now(); + end = DateTime.now(); + allDay = false; + location = ''; + recurrenceRule = ''; + ticketUrlOpening = null; + associationId = ''; + ticketUrl = null; + } + + @override + String toString() { + return 'EventCreation{name: $name, start: $start, end: $end, allDay: $allDay, location: $location, recurrenceRule: $recurrenceRule, ticketUrlOpening: $ticketUrlOpening, associationId: $associationId, ticketUrl: $ticketUrl}'; + } +} diff --git a/lib/feed/providers/event_creation_provider.dart b/lib/feed/providers/event_creation_provider.dart new file mode 100644 index 0000000000..7b4a6eb0fd --- /dev/null +++ b/lib/feed/providers/event_creation_provider.dart @@ -0,0 +1,29 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/feed/class/event_creation.dart'; +import 'package:titan/feed/repositories/event_creation_repository.dart'; +import 'package:titan/tools/providers/single_notifier.dart'; + +class EventCreationNotifier extends SingleNotifier { + final EventCreationRepository eventRepository; + EventCreationNotifier({required this.eventRepository}) + : super(const AsyncValue.loading()); + + void fakeLoad() { + state = AsyncValue.data(EventCreation.empty()); + } + + Future addEvent(EventCreation event) async { + return await add(eventRepository.createEvent, event); + } +} + +final eventCreationProvider = + StateNotifierProvider>(( + ref, + ) { + final eventRepository = ref.watch(eventCreationRepositoryProvider); + EventCreationNotifier notifier = EventCreationNotifier( + eventRepository: eventRepository, + )..fakeLoad(); + return notifier; + }); diff --git a/lib/feed/providers/is_user_a_member_of_an_association.dart b/lib/feed/providers/is_user_a_member_of_an_association.dart index ce7160a48b..9557a0814c 100644 --- a/lib/feed/providers/is_user_a_member_of_an_association.dart +++ b/lib/feed/providers/is_user_a_member_of_an_association.dart @@ -1,8 +1,10 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/repositories/association_repository.dart'; +import 'package:titan/admin/providers/my_association_list_provider.dart'; final isUserAMemberOfAnAssociationProvider = FutureProvider((ref) async { - final associationRepository = ref.watch(associationRepositoryProvider); - final associations = await associationRepository.getMyAssociations(); - return associations.isNotEmpty; + final myAssociation = ref.watch(myAssociationListProvider); + return myAssociation.maybeWhen( + data: (associations) => associations.isNotEmpty, + orElse: () => false, + ); }); diff --git a/lib/feed/repositories/event_creation_repository.dart b/lib/feed/repositories/event_creation_repository.dart new file mode 100644 index 0000000000..b88a6ea4a2 --- /dev/null +++ b/lib/feed/repositories/event_creation_repository.dart @@ -0,0 +1,19 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/auth/providers/openid_provider.dart'; +import 'package:titan/feed/class/event_creation.dart'; +import 'package:titan/tools/repository/repository.dart'; + +class EventCreationRepository extends Repository { + @override + // ignore: overridden_fields + final ext = "calendar/events/"; + + Future createEvent(EventCreation event) async { + return EventCreation.fromJson(await create(event.toJson())); + } +} + +final eventCreationRepositoryProvider = Provider((ref) { + final token = ref.watch(tokenProvider); + return EventCreationRepository()..setToken(token); +}); diff --git a/lib/feed/ui/pages/add_event_page/add_event_page.dart b/lib/feed/ui/pages/add_event_page/add_event_page.dart index f805a34595..9168d373d8 100644 --- a/lib/feed/ui/pages/add_event_page/add_event_page.dart +++ b/lib/feed/ui/pages/add_event_page/add_event_page.dart @@ -1,28 +1,55 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/feed/class/news.dart'; +// import 'package:syncfusion_flutter_calendar/calendar.dart'; +import 'package:titan/admin/class/assocation.dart'; +import 'package:titan/admin/providers/my_association_list_provider.dart'; +// import 'package:titan/event/providers/selected_days_provider.dart'; +// import 'package:titan/event/tools/constants.dart'; +// import 'package:titan/event/tools/functions.dart'; +import 'package:titan/event/ui/pages/event_pages/checkbox_entry.dart'; +import 'package:titan/feed/class/event_creation.dart'; import 'package:titan/feed/providers/admin_news_list_provider.dart'; +import 'package:titan/feed/providers/event_creation_provider.dart'; import 'package:titan/feed/ui/feed.dart'; +import 'package:titan/l10n/app_localizations.dart'; +import 'package:titan/navigation/ui/scroll_to_hide_navbar.dart'; +import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; +import 'package:titan/tools/token_expire_wrapper.dart'; +import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/styleguide/button.dart'; -import 'package:titan/tools/ui/styleguide/icon_button.dart'; +import 'package:titan/tools/ui/styleguide/horizontal_multi_select.dart'; import 'package:titan/tools/ui/styleguide/image_entry.dart'; import 'package:titan/tools/ui/styleguide/text_entry.dart'; +import 'package:titan/tools/ui/widgets/date_entry.dart'; class AddEventPage extends HookConsumerWidget { const AddEventPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - final eventTitleController = useTextEditingController(); - final eventLocationController = useTextEditingController(); + final key = GlobalKey(); + final myAssociations = ref.watch(myAssociationListProvider); + final titleController = useTextEditingController(); + final locationController = useTextEditingController(); final shotgunDateController = useTextEditingController(); - final eventExternalLinkController = useTextEditingController(); - final eventStartDateController = useTextEditingController(); - final eventEndDateController = useTextEditingController(); + final externalLinkController = useTextEditingController(); + final startDateController = useTextEditingController(); + final endDateController = useTextEditingController(); + final allDay = useState(false); + // final recurrentController = useState(false); + // final recurrenceEndDateController = useTextEditingController(); + final eventCreationNotifier = ref.watch(eventCreationProvider.notifier); final adminNewsListNotifier = ref.watch(adminNewsListProvider.notifier); + // final interval = useTextEditingController(); + // final recurrenceEndDate = useTextEditingController(); + // final selectedDays = ref.watch(selectedDaysProvider); + // final selectedDaysNotifier = ref.watch(selectedDaysProvider.notifier); + // final now = DateTime.now(); + final selectedAssociation = useState(null); + void displayToastWithContext(TypeMsg type, String msg) { displayToast(context, type, msg); @@ -30,176 +57,355 @@ class AddEventPage extends HookConsumerWidget { return FeedTemplate( child: Expanded( - child: SingleChildScrollView( - physics: const BouncingScrollPhysics(), - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 20.0, - vertical: 16.0, - ), - child: Column( - key: const ValueKey('event_form'), - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - TextEntry(label: "Titre", controller: eventTitleController), - SizedBox(height: 10), - Row( + child: Form( + key: key, + child: ScrollToHideNavbar( + controller: ScrollController(), + child: SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 20.0, + vertical: 16.0, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - Expanded( - child: TextEntry( - label: "Date de début ", - controller: eventStartDateController, - enabled: false, + AsyncChild( + value: myAssociations, + builder: (context, associations) => SizedBox( + height: 50, + child: HorizontalMultiSelect( + items: associations, + selectedItem: selectedAssociation.value, + onItemSelected: (association) { + selectedAssociation.value = association; + }, + itemBuilder: (context, association, index, selected) => + Text( + association.name, + style: TextStyle( + color: selected + ? ColorConstants.background + : ColorConstants.tertiary, + fontSize: 16, + ), + ), + ), ), ), - CustomIconButton( - icon: const Icon( - Icons.calendar_today, - color: Colors.white, - ), - onPressed: () async { - final date = await showDatePicker( - context: context, - initialDate: DateTime.now(), - firstDate: DateTime.now(), - lastDate: DateTime.now().add( - const Duration(days: 365), - ), - ); - if (date != null) { - eventStartDateController.text = - "${date.day.toString().padLeft(2, '0')}/${date.month.toString().padLeft(2, '0')}/${date.year}"; - } + const SizedBox(height: 10), + TextEntry(label: "Titre", controller: titleController), + const SizedBox(height: 10), + CheckBoxEntry( + title: AppLocalizations.of(context)!.eventAllDay, + valueNotifier: allDay, + onChanged: () { + startDateController.text = ""; + endDateController.text = ""; + // recurrenceEndDateController.text = ""; }, ), - ], - ), - SizedBox(height: 10), - Row( - children: [ - Expanded( - child: TextEntry( - label: "Date de fin ", - controller: eventEndDateController, - enabled: false, - ), - ), - CustomIconButton( - icon: const Icon( - Icons.calendar_today, - color: Colors.white, - ), - onPressed: () async { - final date = await showDatePicker( - context: context, - initialDate: DateTime.now(), - firstDate: DateTime.now(), - lastDate: DateTime.now().add( - const Duration(days: 365), + // const SizedBox(height: 10), + // CheckBoxEntry( + // title: AppLocalizations.of(context)!.eventRecurrence, + // valueNotifier: recurrentController, + // onChanged: () { + // startDateController.text = ""; + // endDateController.text = ""; + // recurrenceEndDateController.text = ""; + // }, + // ), + + const SizedBox(height: 30), + // recurrentController.value + // ? Column( + // children: [ + // Column( + // children: [ + // Text( + // AppLocalizations.of( + // context, + // )!.eventRecurrenceDays, + // style: const TextStyle(color: Colors.black), + // ), + // const SizedBox(height: 10), + // Column( + // children: eventDayKeys.asMap().entries.map(( + // entry, + // ) { + // final index = entry.key; + // final key = entry.value; + // final localizedLabel = getLocalizedEventDay( + // context, + // key, + // ); + + // return GestureDetector( + // onTap: () => + // selectedDaysNotifier.toggle(index), + // behavior: HitTestBehavior.opaque, + // child: Row( + // mainAxisAlignment: + // MainAxisAlignment.spaceBetween, + // children: [ + // Text( + // localizedLabel, + // style: TextStyle( + // color: Colors.grey.shade700, + // fontSize: 16, + // ), + // ), + // Checkbox( + // checkColor: Colors.white, + // activeColor: Colors.black, + // value: selectedDays[index], + // onChanged: (_) => + // selectedDaysNotifier.toggle( + // index, + // ), + // ), + // ], + // ), + // ); + // }).toList(), + // ), + // const SizedBox(height: 20), + // Text( + // AppLocalizations.of(context)!.eventInterval, + // style: const TextStyle(color: Colors.black), + // ), + // const SizedBox(height: 10), + // TextEntry( + // label: AppLocalizations.of( + // context, + // )!.eventInterval, + // controller: interval, + // prefix: AppLocalizations.of( + // context, + // )!.eventEventEvery, + // suffix: AppLocalizations.of( + // context, + // )!.eventWeeks, + // isInt: true, + // keyboardType: TextInputType.number, + // ), + // const SizedBox(height: 30), + // if (!allDay.value) + // Column( + // children: [ + // DateEntry( + // onTap: () => getOnlyHourDate( + // context, + // startDateController, + // ), + // controller: startDateController, + // label: AppLocalizations.of( + // context, + // )!.eventStartHour, + // ), + // const SizedBox(height: 30), + // DateEntry( + // onTap: () => getOnlyHourDate( + // context, + // endDateController, + // ), + // controller: endDateController, + // label: AppLocalizations.of( + // context, + // )!.eventEndHour, + // ), + // const SizedBox(height: 30), + // ], + // ), + // DateEntry( + // onTap: () => getOnlyDayDate( + // context, + // recurrenceEndDate, + // ), + // controller: recurrenceEndDate, + // label: AppLocalizations.of( + // context, + // )!.eventRecurrenceEndDate, + // ), + // ], + // ), + // ], + // ) + // : + Column( + children: [ + DateEntry( + onTap: () => allDay.value + ? getOnlyDayDate(context, startDateController) + : getFullDate(context, startDateController), + controller: startDateController, + label: AppLocalizations.of( + context, + )!.eventStartDate, + ), + const SizedBox(height: 30), + DateEntry( + onTap: () => allDay.value + ? getOnlyDayDate(context, endDateController) + : getFullDate(context, endDateController), + controller: endDateController, + label: AppLocalizations.of(context)!.eventEndDate, + ), + ], ), + SizedBox(height: 10), + TextEntry(label: "Lieu", controller: locationController), + SizedBox(height: 10), + DateEntry( + onTap: () => getFullDate(context, shotgunDateController), + controller: shotgunDateController, + label: "Date du SG ", + ), + SizedBox(height: 10), + TextEntry( + label: "Lien externe pour le SG", + controller: externalLinkController, + canBeEmpty: true, + ), + const SizedBox(height: 10), + ImageEntry( + title: "Image", + subtitle: "Sélectionnez une image", + onTap: () { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Image ajoutée')), ); - if (date != null) { - eventEndDateController.text = - "${date.day.toString().padLeft(2, '0')}/${date.month.toString().padLeft(2, '0')}/${date.year}"; - } }, ), - ], - ), - SizedBox(height: 10), - TextEntry(label: "Lieu", controller: eventLocationController), - SizedBox(height: 10), - Row( - children: [ - Expanded( - child: TextEntry( - label: "Date du SG ", - controller: shotgunDateController, - enabled: false, - ), - ), - CustomIconButton( - icon: const Icon( - Icons.calendar_today, - color: Colors.white, - ), + const SizedBox(height: 40), + Button( + text: "Créer l'événement", onPressed: () async { - final date = await showDatePicker( - context: context, - initialDate: DateTime.now(), - firstDate: DateTime.now(), - lastDate: DateTime.now().add( - const Duration(days: 365), - ), - ); - if (date != null) { - shotgunDateController.text = - "${date.day.toString().padLeft(2, '0')}/${date.month.toString().padLeft(2, '0')}/${date.year}"; + if (key.currentState == null) { + return; + } + if (selectedAssociation.value == null) { + displayToastWithContext( + TypeMsg.error, + "Veuillez sélectionner une association" + ); + return; + } + final addedEventMsg = AppLocalizations.of( + context, + )!.eventAddedEvent; + final addingErrorMsg = AppLocalizations.of( + context, + )!.eventAddingError; + if (key.currentState!.validate()) { + // if (allDay.value) { + // startDateController.text = + // "${!recurrentController.value ? "${startDateController.text} " : ""}00:00"; + // endDateController.text = + // "${!recurrentController.value ? "${endDateController.text} " : ""}23:59"; + // } + if (endDateController.text.contains("/") && + isDateBefore( + processDateBack(endDateController.text), + processDateBack(startDateController.text), + )) { + displayToast( + context, + TypeMsg.error, + AppLocalizations.of(context)!.eventInvalidDates, + ); + // } else if (recurrentController.value && + // selectedDays.where((element) => element).isEmpty) { + // displayToast( + // context, + // TypeMsg.error, + // AppLocalizations.of(context)!.eventNoDaySelected, + // ); + } else { + await tokenExpireWrapper(ref, () async { + // String recurrenceRule = ""; + // String startString = startDateController.text; + // if (!startString.contains("/")) { + // startString = "${processDate(now)} $startString"; + // } + // String endString = endDateController.text; + // if (!endString.contains("/")) { + // endString = "${processDate(now)} $endString"; + // } + // if (recurrentController.value) { + // RecurrenceProperties recurrence = + // RecurrenceProperties(startDate: now); + // recurrence.recurrenceType = RecurrenceType.weekly; + // recurrence.recurrenceRange = + // RecurrenceRange.endDate; + // recurrence.endDate = DateTime.parse( + // processDateBack(recurrenceEndDate.text), + // ); + // recurrence.weekDays = WeekDays.values + // .where( + // (element) => + // selectedDays[(WeekDays.values.indexOf( + // element, + // ) - + // 1) % + // 7], + // ) + // .toList(); + // recurrence.interval = int.parse(interval.text); + // recurrenceRule = SfCalendar.generateRRule( + // recurrence, + // DateTime.parse( + // processDateBackWithHour(startString), + // ), + // DateTime.parse( + // processDateBackWithHour(endString), + // ), + // ); + // } + final newEvent = EventCreation( + start: DateTime.parse( + processDateBack(startDateController.text), + ), + end: DateTime.parse( + processDateBack(endDateController.text), + ), + location: locationController.text, + ticketUrlOpening: DateTime.parse( + processDateBack(shotgunDateController.text), + ), + name: titleController.text, + allDay: allDay.value, + // recurrenceRule: recurrenceRule, + recurrenceRule: "", + associationId: selectedAssociation.value!.id, + ticketUrl: externalLinkController.text, + ); + final value = await eventCreationNotifier.addEvent( + newEvent, + ); + if (value) { + adminNewsListNotifier.loadNewsList(); + Navigator.of(context).pop(); + displayToastWithContext( + TypeMsg.msg, + addedEventMsg, + ); + } else { + displayToastWithContext( + TypeMsg.error, + addingErrorMsg, + ); + } + }); + } } }, ), + const SizedBox(height: 80), ], ), - SizedBox(height: 10), - TextEntry( - label: "Lien externe pour le SG", - controller: eventExternalLinkController, - canBeEmpty: true, - ), - const SizedBox(height: 10), - ImageEntry( - title: "Image", - subtitle: "Sélectionnez une image", - onTap: () { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Image ajoutée')), - ); - }, - ), - const SizedBox(height: 40), - Button( - text: "Créer l'événement", - onPressed: () async { - if (eventTitleController.text.isEmpty || - eventStartDateController.text.isEmpty || - eventEndDateController.text.isEmpty || - eventLocationController.text.isEmpty) { - displayToastWithContext( - TypeMsg.error, - "Veuillez remplir tous les champs obligatoires.", - ); - return; - } - final newNews = News.empty().copyWith( - title: eventTitleController.text, - start: DateTime.parse( - processDateBack(eventStartDateController.text), - ), - end: DateTime.parse( - processDateBack(eventEndDateController.text), - ), - location: eventLocationController.text, - actionStart: DateTime.parse( - processDateBack(shotgunDateController.text), - ), - // externalLink: eventExternalLinkController.text, - ); - final value = await adminNewsListNotifier.addNews(newNews); - if (value) { - displayToastWithContext( - TypeMsg.msg, - "Événement créé avec succès !", - ); - } else { - displayToastWithContext( - TypeMsg.error, - "Échec de la création de l'événement.", - ); - } - }, - ), - const SizedBox(height: 80), - ], + ), ), ), ), From 68db8215c81fb86ff935c7479084dc307e20a210 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Tue, 19 Aug 2025 21:28:43 +0200 Subject: [PATCH 287/473] feat: handling feed admin action based on right --- lib/feed/ui/pages/main_page/main_page.dart | 67 ++++++++++++---------- 1 file changed, 38 insertions(+), 29 deletions(-) diff --git a/lib/feed/ui/pages/main_page/main_page.dart b/lib/feed/ui/pages/main_page/main_page.dart index ba1b5c9c9a..9112283efd 100644 --- a/lib/feed/ui/pages/main_page/main_page.dart +++ b/lib/feed/ui/pages/main_page/main_page.dart @@ -117,42 +117,51 @@ class FeedMainPage extends HookConsumerWidget { color: ColorConstants.title, ), ), - if (isUserAMemberOfAnAssociation) + if (isUserAMemberOfAnAssociation || isFeedAdmin) CustomIconButton( icon: HeroIcon( HeroIcons.userGroup, color: ColorConstants.background, ), onPressed: () { - showCustomBottomModal( - modal: BottomModalTemplate( - title: 'Administration', - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Button( - text: 'Créer un événement', - onPressed: () { - Navigator.of(context).pop(); - QR.to(FeedRouter.root + FeedRouter.addEvent); - }, - ), - const SizedBox(height: 20), - Button( - text: 'Demandes de publication', - onPressed: () { - Navigator.of(context).pop(); - QR.to( - FeedRouter.root + FeedRouter.eventHandling, - ); - }, - ), - ], + if (isFeedAdmin && !isUserAMemberOfAnAssociation) { + QR.to(FeedRouter.root + FeedRouter.eventHandling); + } else if (!isFeedAdmin && isUserAMemberOfAnAssociation) { + QR.to(FeedRouter.root + FeedRouter.addEvent); + } else { + showCustomBottomModal( + modal: BottomModalTemplate( + title: 'Administration', + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Button( + text: 'Créer un événement', + onPressed: () { + Navigator.of(context).pop(); + QR.to( + FeedRouter.root + FeedRouter.addEvent, + ); + }, + ), + const SizedBox(height: 20), + Button( + text: 'Demandes de publication', + onPressed: () { + Navigator.of(context).pop(); + QR.to( + FeedRouter.root + + FeedRouter.eventHandling, + ); + }, + ), + ], + ), ), - ), - context: context, - ref: ref, - ); + context: context, + ref: ref, + ); + } }, ), ], From f24fd35752e820271752718eadbe15fba9e9e73b Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Tue, 19 Aug 2025 21:29:08 +0200 Subject: [PATCH 288/473] feat: default feed background --- lib/feed/ui/pages/main_page/event_card.dart | 172 ++++++++++++-------- 1 file changed, 107 insertions(+), 65 deletions(-) diff --git a/lib/feed/ui/pages/main_page/event_card.dart b/lib/feed/ui/pages/main_page/event_card.dart index a9c5b4634f..b3f4a6475c 100644 --- a/lib/feed/ui/pages/main_page/event_card.dart +++ b/lib/feed/ui/pages/main_page/event_card.dart @@ -1,87 +1,129 @@ import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/feed/class/news.dart'; +import 'package:titan/feed/providers/news_image_provider.dart'; +import 'package:titan/feed/providers/news_images_provider.dart'; import 'package:titan/feed/tools/news_helper.dart'; import 'package:titan/tools/constants.dart'; +import 'package:titan/tools/ui/builders/auto_loader_child.dart'; -class EventCard extends StatelessWidget { +class EventCard extends ConsumerWidget { final News item; const EventCard({super.key, required this.item}); @override - Widget build(BuildContext context) { - return Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(15), - color: ColorConstants.secondary, - ), - child: Stack( - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox(height: 70), - Text( - item.title, - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w900, - color: ColorConstants.background, - ), - ), - Text( - getNewsSubtitle( - item, - locale: Localizations.localeOf(context).languageCode, - context: context, - ), - style: const TextStyle( - fontSize: 12, - color: ColorConstants.background, - ), - ), - ], + Widget build(BuildContext context, WidgetRef ref) { + final images = ref.watch( + newsImagesProvider.select((newsImages) => newsImages[item.id]), + ); + final newsImagesNotifier = ref.watch(newsImagesProvider.notifier); + final imageNotifier = ref.watch(newsImageProvider.notifier); + return Stack( + children: [ + AutoLoaderChild( + group: images, + notifier: newsImagesNotifier, + mapKey: item.id, + loader: (itemId) => imageNotifier.getNewsImage(itemId), + orElseBuilder: (context, stack) => Container( + width: double.infinity, + height: 130, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(15), + gradient: const LinearGradient( + colors: [ColorConstants.onMain, ColorConstants.main], + begin: Alignment.bottomLeft, + end: Alignment.topRight, + ), ), ), - if (isNewsTerminated(item)) - Positioned( - bottom: 53, - left: 15, - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 3), - decoration: const BoxDecoration( - color: ColorConstants.main, - borderRadius: BorderRadius.all(Radius.circular(5)), - ), - child: const Text( - 'Terminé', - style: TextStyle( - color: ColorConstants.background, - fontSize: 10, - ), + dataBuilder: (context, value) => Container( + width: double.infinity, + height: 130, + decoration: BoxDecoration( + color: ColorConstants.onBackground, + borderRadius: BorderRadius.circular(15), + image: value.isEmpty + ? null + : DecorationImage( + image: value.first.image, + fit: BoxFit.cover, + ), + gradient: value.isNotEmpty + ? null + : const LinearGradient( + colors: [ColorConstants.onMain, ColorConstants.main], + begin: Alignment.bottomLeft, + end: Alignment.topRight, + ), + ), + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 70), + Text( + item.title, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w900, + color: ColorConstants.background, ), ), - ), - if (isNewsOngoing(item)) - Positioned( - bottom: 53, - left: 15, - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 3), - decoration: const BoxDecoration( + Text( + getNewsSubtitle( + item, + locale: Localizations.localeOf(context).languageCode, + context: context, + ), + style: const TextStyle( + fontSize: 12, color: ColorConstants.background, - borderRadius: BorderRadius.all(Radius.circular(5)), ), - child: const Text( - 'En cours', - style: TextStyle(color: ColorConstants.main, fontSize: 10), + ), + ], + ), + ), + if (isNewsTerminated(item)) + Positioned( + bottom: 53, + left: 15, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 3), + decoration: const BoxDecoration( + color: ColorConstants.main, + borderRadius: BorderRadius.all(Radius.circular(5)), + ), + child: const Text( + 'Terminé', + style: TextStyle( + color: ColorConstants.background, + fontSize: 10, ), ), ), - ], - ), + ), + if (isNewsOngoing(item)) + Positioned( + bottom: 53, + left: 15, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 3), + decoration: const BoxDecoration( + color: ColorConstants.background, + borderRadius: BorderRadius.all(Radius.circular(5)), + ), + child: const Text( + 'En cours', + style: TextStyle(color: ColorConstants.main, fontSize: 10), + ), + ), + ), + ], ); } } From a4fa96a31c2a8b87d3c8b08932df2dd4428afb22 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Tue, 19 Aug 2025 22:06:12 +0200 Subject: [PATCH 289/473] feat: news image handling --- lib/feed/providers/news_image_provider.dart | 44 +++- lib/feed/providers/news_images_provider.dart | 16 ++ .../repositories/news_image_repository.dart | 18 +- .../pages/add_event_page/add_event_page.dart | 206 ++++++++++++++---- 4 files changed, 223 insertions(+), 61 deletions(-) create mode 100644 lib/feed/providers/news_images_provider.dart diff --git a/lib/feed/providers/news_image_provider.dart b/lib/feed/providers/news_image_provider.dart index 7c3bfd0971..c3155a3a87 100644 --- a/lib/feed/providers/news_image_provider.dart +++ b/lib/feed/providers/news_image_provider.dart @@ -1,23 +1,43 @@ -import 'package:flutter/foundation.dart'; +import 'dart:typed_data'; + +import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/auth/providers/openid_provider.dart'; +import 'package:titan/feed/providers/news_images_provider.dart'; import 'package:titan/feed/repositories/news_image_repository.dart'; import 'package:titan/tools/providers/single_notifier.dart'; -class NewsImageNotifier extends SingleNotifier { - final NewsImageRepository newsImageRepository; - NewsImageNotifier({required this.newsImageRepository}) - : super(const AsyncLoading()); +class NewsImageNotifier extends SingleNotifier { + final newsImageRepository = NewsImageRepository(); + final NewsImagesNotifier newsImagesNotifier; + NewsImageNotifier({ + required String token, + required this.newsImagesNotifier, + }) : super(const AsyncValue.loading()) { + newsImageRepository.setToken(token); + } + + Future getNewsImage(String id) async { + final image = await newsImageRepository.getNewsImage(id); + newsImagesNotifier.setTData(id, AsyncData([image])); + return image; + } - Future> getNewsImage(String userId) async { - return await load(() async => newsImageRepository.getNewsImage(userId)); + Future updateNewsImage(String id, Uint8List bytes) async { + newsImagesNotifier.setTData(id, const AsyncLoading()); + final image = await newsImageRepository.addNewsImage(bytes, id); + newsImagesNotifier.setTData(id, AsyncData([image])); + return image; } } final newsImageProvider = - StateNotifierProvider>((ref) { - final newsImageRepository = ref.watch(newsImageRepositoryProvider); - NewsImageNotifier notifier = NewsImageNotifier( - newsImageRepository: newsImageRepository, + StateNotifierProvider>((ref) { + final token = ref.watch(tokenProvider); + final newsImagesNotifier = ref.watch(newsImagesProvider.notifier); + return NewsImageNotifier( + token: token, + newsImagesNotifier: newsImagesNotifier, ); - return notifier; }); diff --git a/lib/feed/providers/news_images_provider.dart b/lib/feed/providers/news_images_provider.dart new file mode 100644 index 0000000000..7db05cd5d7 --- /dev/null +++ b/lib/feed/providers/news_images_provider.dart @@ -0,0 +1,16 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/tools/providers/map_provider.dart'; + +class NewsImagesNotifier extends MapNotifier { + NewsImagesNotifier() : super(); +} + +final newsImagesProvider = + StateNotifierProvider< + NewsImagesNotifier, + Map>?> + >((ref) { + NewsImagesNotifier advertPosterNotifier = NewsImagesNotifier(); + return advertPosterNotifier; + }); diff --git a/lib/feed/repositories/news_image_repository.dart b/lib/feed/repositories/news_image_repository.dart index 690e95c0cd..58be969449 100644 --- a/lib/feed/repositories/news_image_repository.dart +++ b/lib/feed/repositories/news_image_repository.dart @@ -1,6 +1,9 @@ -import 'package:flutter/services.dart'; +import 'dart:typed_data'; + +import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/auth/providers/openid_provider.dart'; +import 'package:titan/tools/functions.dart'; import 'package:titan/tools/repository/logo_repository.dart'; class NewsImageRepository extends LogoRepository { @@ -8,8 +11,17 @@ class NewsImageRepository extends LogoRepository { // ignore: overridden_fields final ext = 'feed/news/'; - Future getNewsImage(String id) async { - return await getLogo(id, suffix: "/image"); + Future getNewsImage(String id) async { + final uint8List = await getLogo(id, suffix: "/image"); + if (uint8List.isEmpty) { + return Image.asset(getTitanLogo()); + } + return Image.memory(uint8List); + } + + Future addNewsImage(Uint8List bytes, String id) async { + final uint8List = await addLogo(bytes, id, suffix: "/image"); + return Image.memory(uint8List); } } diff --git a/lib/feed/ui/pages/add_event_page/add_event_page.dart b/lib/feed/ui/pages/add_event_page/add_event_page.dart index 9168d373d8..1bbc84a064 100644 --- a/lib/feed/ui/pages/add_event_page/add_event_page.dart +++ b/lib/feed/ui/pages/add_event_page/add_event_page.dart @@ -1,6 +1,11 @@ +import 'dart:io'; +import 'dart:typed_data'; + import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:image_picker/image_picker.dart'; // import 'package:syncfusion_flutter_calendar/calendar.dart'; import 'package:titan/admin/class/assocation.dart'; import 'package:titan/admin/providers/my_association_list_provider.dart'; @@ -11,6 +16,7 @@ import 'package:titan/event/ui/pages/event_pages/checkbox_entry.dart'; import 'package:titan/feed/class/event_creation.dart'; import 'package:titan/feed/providers/admin_news_list_provider.dart'; import 'package:titan/feed/providers/event_creation_provider.dart'; +import 'package:titan/feed/providers/news_image_provider.dart'; import 'package:titan/feed/ui/feed.dart'; import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/navigation/ui/scroll_to_hide_navbar.dart'; @@ -20,9 +26,9 @@ import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/styleguide/button.dart'; import 'package:titan/tools/ui/styleguide/horizontal_multi_select.dart'; -import 'package:titan/tools/ui/styleguide/image_entry.dart'; import 'package:titan/tools/ui/styleguide/text_entry.dart'; import 'package:titan/tools/ui/widgets/date_entry.dart'; +import 'package:titan/tools/ui/widgets/image_picker_on_tap.dart'; class AddEventPage extends HookConsumerWidget { const AddEventPage({super.key}); @@ -43,6 +49,7 @@ class AddEventPage extends HookConsumerWidget { final eventCreationNotifier = ref.watch(eventCreationProvider.notifier); final adminNewsListNotifier = ref.watch(adminNewsListProvider.notifier); + final newsListNotifier = ref.watch(newsListProvider.notifier); // final interval = useTextEditingController(); // final recurrenceEndDate = useTextEditingController(); // final selectedDays = ref.watch(selectedDaysProvider); @@ -50,6 +57,11 @@ class AddEventPage extends HookConsumerWidget { // final now = DateTime.now(); final selectedAssociation = useState(null); + final imageNotifier = ref.watch(newsImageProvider.notifier); + final poster = useState(null); + final posterFile = useState(null); + + final ImagePicker picker = ImagePicker(); void displayToastWithContext(TypeMsg type, String msg) { displayToast(context, type, msg); @@ -81,8 +93,8 @@ class AddEventPage extends HookConsumerWidget { onItemSelected: (association) { selectedAssociation.value = association; }, - itemBuilder: (context, association, index, selected) => - Text( + itemBuilder: + (context, association, index, selected) => Text( association.name, style: TextStyle( color: selected @@ -106,6 +118,7 @@ class AddEventPage extends HookConsumerWidget { // recurrenceEndDateController.text = ""; }, ), + // const SizedBox(height: 10), // CheckBoxEntry( // title: AppLocalizations.of(context)!.eventRecurrence, @@ -116,7 +129,6 @@ class AddEventPage extends HookConsumerWidget { // recurrenceEndDateController.text = ""; // }, // ), - const SizedBox(height: 30), // recurrentController.value // ? Column( @@ -140,7 +152,7 @@ class AddEventPage extends HookConsumerWidget { // context, // key, // ); - + // return GestureDetector( // onTap: () => // selectedDaysNotifier.toggle(index), @@ -232,28 +244,26 @@ class AddEventPage extends HookConsumerWidget { // ), // ], // ) - // : - Column( - children: [ - DateEntry( - onTap: () => allDay.value - ? getOnlyDayDate(context, startDateController) - : getFullDate(context, startDateController), - controller: startDateController, - label: AppLocalizations.of( - context, - )!.eventStartDate, - ), - const SizedBox(height: 30), - DateEntry( - onTap: () => allDay.value - ? getOnlyDayDate(context, endDateController) - : getFullDate(context, endDateController), - controller: endDateController, - label: AppLocalizations.of(context)!.eventEndDate, - ), - ], - ), + // : + Column( + children: [ + DateEntry( + onTap: () => allDay.value + ? getOnlyDayDate(context, startDateController) + : getFullDate(context, startDateController), + controller: startDateController, + label: AppLocalizations.of(context)!.eventStartDate, + ), + const SizedBox(height: 30), + DateEntry( + onTap: () => allDay.value + ? getOnlyDayDate(context, endDateController) + : getFullDate(context, endDateController), + controller: endDateController, + label: AppLocalizations.of(context)!.eventEndDate, + ), + ], + ), SizedBox(height: 10), TextEntry(label: "Lieu", controller: locationController), SizedBox(height: 10), @@ -269,14 +279,105 @@ class AddEventPage extends HookConsumerWidget { canBeEmpty: true, ), const SizedBox(height: 10), - ImageEntry( - title: "Image", - subtitle: "Sélectionnez une image", - onTap: () { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Image ajoutée')), - ); + + FormField( + validator: (e) { + if (poster.value == null) { + return AppLocalizations.of( + context, + )!.advertChoosingPoster; + } + return null; }, + builder: (formFieldState) => Center( + child: Stack( + clipBehavior: Clip.none, + children: [ + ImagePickerOnTap( + picker: picker, + imageBytesNotifier: poster, + imageNotifier: posterFile, + displayToastWithContext: displayToastWithContext, + child: Container( + decoration: BoxDecoration( + borderRadius: const BorderRadius.all( + Radius.circular(5), + ), + color: Colors.white, + boxShadow: [ + BoxShadow( + color: formFieldState.hasError + ? Colors.red + : Colors.black.withValues(alpha: 0.1), + spreadRadius: 5, + blurRadius: 10, + offset: const Offset(2, 3), + ), + ], + ), + child: posterFile.value != null + ? Stack( + children: [ + Container( + width: 285, + height: 160, + decoration: BoxDecoration( + borderRadius: + const BorderRadius.all( + Radius.circular(5), + ), + image: DecorationImage( + image: poster.value != null + ? Image.memory( + poster.value!, + fit: BoxFit.cover, + ).image + : posterFile.value!.image, + fit: BoxFit.cover, + ), + ), + child: Center( + child: Container( + width: 50, + height: 50, + decoration: BoxDecoration( + borderRadius: + const BorderRadius.all( + Radius.circular(5), + ), + color: Colors.white + .withValues(alpha: 0.4), + ), + child: HeroIcon( + HeroIcons.photo, + size: 40, + color: Colors.black + .withValues(alpha: 0.5), + ), + ), + ), + ), + ], + ) + : Container( + width: 285, + height: 160, + decoration: BoxDecoration( + borderRadius: const BorderRadius.all( + Radius.circular(5), + ), + ), + child: const HeroIcon( + HeroIcons.photo, + size: 160, + color: Colors.grey, + ), + ), + ), + ), + ], + ), + ), ), const SizedBox(height: 40), Button( @@ -288,7 +389,7 @@ class AddEventPage extends HookConsumerWidget { if (selectedAssociation.value == null) { displayToastWithContext( TypeMsg.error, - "Veuillez sélectionner une association" + "Veuillez sélectionner une association", ); return; } @@ -315,13 +416,13 @@ class AddEventPage extends HookConsumerWidget { TypeMsg.error, AppLocalizations.of(context)!.eventInvalidDates, ); - // } else if (recurrentController.value && - // selectedDays.where((element) => element).isEmpty) { - // displayToast( - // context, - // TypeMsg.error, - // AppLocalizations.of(context)!.eventNoDaySelected, - // ); + // } else if (recurrentController.value && + // selectedDays.where((element) => element).isEmpty) { + // displayToast( + // context, + // TypeMsg.error, + // AppLocalizations.of(context)!.eventNoDaySelected, + // ); } else { await tokenExpireWrapper(ref, () async { // String recurrenceRule = ""; @@ -381,16 +482,29 @@ class AddEventPage extends HookConsumerWidget { associationId: selectedAssociation.value!.id, ticketUrl: externalLinkController.text, ); - final value = await eventCreationNotifier.addEvent( - newEvent, - ); + final value = await eventCreationNotifier + .addEvent(newEvent); if (value) { - adminNewsListNotifier.loadNewsList(); Navigator.of(context).pop(); displayToastWithContext( TypeMsg.msg, addedEventMsg, ); + newsListNotifier.loadNewsList(); + adminNewsListNotifier.loadNewsList().then(( + news, + ) { + news.maybeWhen( + data: (list) { + final newNews = list.last; + imageNotifier.updateNewsImage( + newNews.id, + poster.value!, + ); + }, + orElse: () {}, + ); + }); } else { displayToastWithContext( TypeMsg.error, From e17176b5e6edfb548bcd24e907f0cd9251ae2da0 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Tue, 19 Aug 2025 22:15:48 +0200 Subject: [PATCH 290/473] fix: missing import --- lib/feed/ui/pages/add_event_page/add_event_page.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/feed/ui/pages/add_event_page/add_event_page.dart b/lib/feed/ui/pages/add_event_page/add_event_page.dart index 1bbc84a064..6a7dcceb9b 100644 --- a/lib/feed/ui/pages/add_event_page/add_event_page.dart +++ b/lib/feed/ui/pages/add_event_page/add_event_page.dart @@ -17,6 +17,7 @@ import 'package:titan/feed/class/event_creation.dart'; import 'package:titan/feed/providers/admin_news_list_provider.dart'; import 'package:titan/feed/providers/event_creation_provider.dart'; import 'package:titan/feed/providers/news_image_provider.dart'; +import 'package:titan/feed/providers/news_list_provider.dart'; import 'package:titan/feed/ui/feed.dart'; import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/navigation/ui/scroll_to_hide_navbar.dart'; From 18265a6d456c95bbe7886de1d4db3e5e04484de8 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Tue, 19 Aug 2025 22:39:55 +0200 Subject: [PATCH 291/473] fix: event card overflow --- lib/feed/ui/pages/main_page/event_card.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/feed/ui/pages/main_page/event_card.dart b/lib/feed/ui/pages/main_page/event_card.dart index b3f4a6475c..b5dccc8ab3 100644 --- a/lib/feed/ui/pages/main_page/event_card.dart +++ b/lib/feed/ui/pages/main_page/event_card.dart @@ -28,7 +28,7 @@ class EventCard extends ConsumerWidget { loader: (itemId) => imageNotifier.getNewsImage(itemId), orElseBuilder: (context, stack) => Container( width: double.infinity, - height: 130, + height: 125, decoration: BoxDecoration( borderRadius: BorderRadius.circular(15), gradient: const LinearGradient( @@ -40,7 +40,7 @@ class EventCard extends ConsumerWidget { ), dataBuilder: (context, value) => Container( width: double.infinity, - height: 130, + height: 125, decoration: BoxDecoration( color: ColorConstants.onBackground, borderRadius: BorderRadius.circular(15), From 6179df7e69e4ef5d35571d2892fbc14a10d71e53 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Tue, 19 Aug 2025 23:18:07 +0200 Subject: [PATCH 292/473] fix: nullable fields --- lib/feed/ui/pages/add_event_page/add_event_page.dart | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/lib/feed/ui/pages/add_event_page/add_event_page.dart b/lib/feed/ui/pages/add_event_page/add_event_page.dart index 6a7dcceb9b..1ad6f08102 100644 --- a/lib/feed/ui/pages/add_event_page/add_event_page.dart +++ b/lib/feed/ui/pages/add_event_page/add_event_page.dart @@ -272,6 +272,7 @@ class AddEventPage extends HookConsumerWidget { onTap: () => getFullDate(context, shotgunDateController), controller: shotgunDateController, label: "Date du SG ", + canBeEmpty: false, ), SizedBox(height: 10), TextEntry( @@ -280,16 +281,7 @@ class AddEventPage extends HookConsumerWidget { canBeEmpty: true, ), const SizedBox(height: 10), - FormField( - validator: (e) { - if (poster.value == null) { - return AppLocalizations.of( - context, - )!.advertChoosingPoster; - } - return null; - }, builder: (formFieldState) => Center( child: Stack( clipBehavior: Clip.none, From a0831914702a24eb3f3777aedfedf360d3d041ce Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Tue, 19 Aug 2025 23:18:23 +0200 Subject: [PATCH 293/473] fix: nullable date entry --- lib/tools/ui/widgets/date_entry.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/tools/ui/widgets/date_entry.dart b/lib/tools/ui/widgets/date_entry.dart index 2c27630806..2eb4eb6fd9 100644 --- a/lib/tools/ui/widgets/date_entry.dart +++ b/lib/tools/ui/widgets/date_entry.dart @@ -4,7 +4,7 @@ import 'package:titan/tools/ui/widgets/text_entry.dart'; class DateEntry extends StatelessWidget { final VoidCallback onTap; final String label; - final bool enabled; + final bool enabled, canBeEmpty; final TextEditingController controller; final Color color, enabledColor, errorColor; final Widget? suffixIcon; @@ -15,6 +15,7 @@ class DateEntry extends StatelessWidget { required this.controller, required this.onTap, this.enabled = true, + this.canBeEmpty = false, this.color = Colors.black, this.enabledColor = Colors.black, this.errorColor = Colors.red, @@ -34,6 +35,7 @@ class DateEntry extends StatelessWidget { enabledColor: enabledColor, errorColor: errorColor, suffixIcon: suffixIcon, + canBeEmpty: canBeEmpty, ), ), ); From baaae0b422d4eb5c240ea8448274a438527f78b3 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Tue, 19 Aug 2025 23:19:58 +0200 Subject: [PATCH 294/473] feat: ticket url --- lib/feed/class/ticket_url.dart | 33 +++++++++++++++++ .../providers/event_ticket_url_provider.dart | 23 ++++++++++++ .../event_creation_repository.dart | 5 +++ lib/feed/repositories/news_repository.dart | 1 - lib/feed/tools/news_helper.dart | 35 +++++++++++++++---- .../ui/pages/main_page/time_line_item.dart | 11 +++--- 6 files changed, 96 insertions(+), 12 deletions(-) create mode 100644 lib/feed/class/ticket_url.dart create mode 100644 lib/feed/providers/event_ticket_url_provider.dart diff --git a/lib/feed/class/ticket_url.dart b/lib/feed/class/ticket_url.dart new file mode 100644 index 0000000000..2212a3e1a0 --- /dev/null +++ b/lib/feed/class/ticket_url.dart @@ -0,0 +1,33 @@ +class TicketUrl { + late final String ticketUrl; + TicketUrl({ + required this.ticketUrl, + }); + + TicketUrl.fromJson(Map json) { + ticketUrl = json['ticket_url']; + } + + Map toJson() { + final Map data = {}; + data['ticket_url'] = ticketUrl; + return data; + } + + TicketUrl copyWith({ + String? ticketUrl, + }) { + return TicketUrl( + ticketUrl: ticketUrl ?? this.ticketUrl, + ); + } + + TicketUrl.empty() { + ticketUrl = ''; + } + + @override + String toString() { + return 'TicketUrl{ticketUrl: $ticketUrl}'; + } +} diff --git a/lib/feed/providers/event_ticket_url_provider.dart b/lib/feed/providers/event_ticket_url_provider.dart new file mode 100644 index 0000000000..caa13955ee --- /dev/null +++ b/lib/feed/providers/event_ticket_url_provider.dart @@ -0,0 +1,23 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/feed/class/ticket_url.dart'; +import 'package:titan/feed/repositories/event_creation_repository.dart'; +import 'package:titan/tools/providers/single_notifier.dart'; + +class TicketUrlNotifier extends SingleNotifier { + final EventCreationRepository eventRepository; + TicketUrlNotifier({required this.eventRepository}) + : super(const AsyncValue.loading()); + + Future> getTicketUrl(String eventId) async { + return await load(() => eventRepository.getTicketUrl(eventId)); + } +} + +final ticketUrlProvider = + StateNotifierProvider>((ref) { + final eventRepository = ref.watch(eventCreationRepositoryProvider); + TicketUrlNotifier notifier = TicketUrlNotifier( + eventRepository: eventRepository, + ); + return notifier; + }); diff --git a/lib/feed/repositories/event_creation_repository.dart b/lib/feed/repositories/event_creation_repository.dart index b88a6ea4a2..c374f291cb 100644 --- a/lib/feed/repositories/event_creation_repository.dart +++ b/lib/feed/repositories/event_creation_repository.dart @@ -1,6 +1,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/auth/providers/openid_provider.dart'; import 'package:titan/feed/class/event_creation.dart'; +import 'package:titan/feed/class/ticket_url.dart'; import 'package:titan/tools/repository/repository.dart'; class EventCreationRepository extends Repository { @@ -11,6 +12,10 @@ class EventCreationRepository extends Repository { Future createEvent(EventCreation event) async { return EventCreation.fromJson(await create(event.toJson())); } + + Future getTicketUrl(String id) async { + return TicketUrl.fromJson(await getOne(id, suffix: "/ticket-url")); + } } final eventCreationRepositoryProvider = Provider((ref) { diff --git a/lib/feed/repositories/news_repository.dart b/lib/feed/repositories/news_repository.dart index 78934f74b8..6713073eca 100644 --- a/lib/feed/repositories/news_repository.dart +++ b/lib/feed/repositories/news_repository.dart @@ -13,7 +13,6 @@ class NewsRepository extends Repository { } Future createNews(News news) async { - print(news.toJson()); return News.fromJson(await create(news.toJson(), suffix: "news")); } diff --git a/lib/feed/tools/news_helper.dart b/lib/feed/tools/news_helper.dart index 86af092293..4071947150 100644 --- a/lib/feed/tools/news_helper.dart +++ b/lib/feed/tools/news_helper.dart @@ -1,11 +1,14 @@ import 'package:flutter/widgets.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/advert/router.dart'; -import 'package:titan/event/router.dart'; import 'package:titan/feed/class/news.dart'; import 'package:intl/intl.dart'; +import 'package:titan/feed/providers/event_ticket_url_provider.dart'; import 'package:titan/l10n/app_localizations.dart'; +import 'package:titan/tools/functions.dart'; import 'package:titan/vote/router.dart'; +import 'package:url_launcher/url_launcher.dart'; String _capitalize(String text) { if (text.isEmpty) return text; @@ -165,10 +168,10 @@ String getActionSubtitle(News news, BuildContext context) { if (module == "campagne") { return AppLocalizations.of(context)?.eventActionCampaignSubtitle ?? - 'Votez maintenant'; + 'Votes maintenant'; } else if (module == "event") { return AppLocalizations.of(context)?.eventActionEventSubtitle ?? - 'Répondez à l\'invitation'; + 'Réponds à l\'invitation'; } return ''; } @@ -197,14 +200,34 @@ String getActionValidatedButtonText(News news, BuildContext context) { return ''; } -void getActionButtonAction(News news) { +void getActionButtonAction( + News news, + BuildContext context, + WidgetRef ref, +) async { final module = news.module; if (module == "campagne") { QR.to(VoteRouter.root); } else if (module == "event") { - // TODO : query event - QR.to(EventRouter.root); + final ticketUrlNotifier = ref.watch(ticketUrlProvider.notifier); + final ticketUrl = await ticketUrlNotifier.getTicketUrl(news.moduleObjectId); + ticketUrl.when( + data: (ticketUrl) async { + if (await canLaunchUrl(Uri.parse(ticketUrl.ticketUrl))) { + await launchUrl( + Uri.parse(ticketUrl.ticketUrl), + mode: LaunchMode.externalApplication, + ); + } else { + displayToast(context, TypeMsg.error, 'Impossible d\'ouvrir le lien'); + } + }, + error: (e, stackTrace) { + displayToast(context, TypeMsg.error, e.toString()); + }, + loading: () {}, + ); } else if (module == "post") { // TODO : set id QR.to(AdvertRouter.root); diff --git a/lib/feed/ui/pages/main_page/time_line_item.dart b/lib/feed/ui/pages/main_page/time_line_item.dart index 58b4c10769..74832ae9f2 100644 --- a/lib/feed/ui/pages/main_page/time_line_item.dart +++ b/lib/feed/ui/pages/main_page/time_line_item.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:intl/intl.dart'; import 'package:titan/feed/class/news.dart'; import 'package:titan/feed/tools/news_helper.dart'; @@ -8,7 +9,7 @@ import 'package:titan/feed/ui/pages/main_page/event_card.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/feed/ui/pages/main_page/dotted_vertical_line.dart'; -class TimelineItem extends StatelessWidget { +class TimelineItem extends ConsumerWidget { final News item; final VoidCallback? onTap; final bool isAdmin; @@ -21,7 +22,7 @@ class TimelineItem extends StatelessWidget { }); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { return SizedBox( height: item.actionStart != null || isAdmin ? 200 : 160, child: Stack( @@ -96,13 +97,13 @@ class TimelineItem extends StatelessWidget { ), ), Expanded( - child: isAdmin + child: !isAdmin ? EventActionAdmin(item: item) : EventAction( title: getActionTitle(item, context), subtitle: getActionSubtitle(item, context), onActionPressed: () => - getActionButtonAction(item), + getActionButtonAction(item, context, ref), actionEnableButtonText: getActionEnableButtonText(item, context), actionValidatedButtonText: @@ -110,7 +111,7 @@ class TimelineItem extends StatelessWidget { item, context, ), - isActionValidated: true, + isActionValidated: false, isActionEnabled: (item.actionStart ?? item.start).isBefore( DateTime.now(), From 5bb84e92ace1e893c80dcf9d31354e06d4b2343b Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Tue, 19 Aug 2025 23:25:07 +0200 Subject: [PATCH 295/473] fix: gradient fallback --- lib/feed/ui/pages/main_page/event_card.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/feed/ui/pages/main_page/event_card.dart b/lib/feed/ui/pages/main_page/event_card.dart index b5dccc8ab3..30a3fc01b2 100644 --- a/lib/feed/ui/pages/main_page/event_card.dart +++ b/lib/feed/ui/pages/main_page/event_card.dart @@ -42,7 +42,6 @@ class EventCard extends ConsumerWidget { width: double.infinity, height: 125, decoration: BoxDecoration( - color: ColorConstants.onBackground, borderRadius: BorderRadius.circular(15), image: value.isEmpty ? null From da4f82a9a0fe6752ada691aef0850cca087bbc58 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Tue, 19 Aug 2025 23:32:27 +0200 Subject: [PATCH 296/473] lint: applying linter --- lib/feed/class/ticket_url.dart | 12 +++--------- lib/feed/providers/event_creation_provider.dart | 6 +++--- lib/feed/providers/news_image_provider.dart | 6 ++---- lib/feed/repositories/event_creation_repository.dart | 4 +++- 4 files changed, 11 insertions(+), 17 deletions(-) diff --git a/lib/feed/class/ticket_url.dart b/lib/feed/class/ticket_url.dart index 2212a3e1a0..f9c7842736 100644 --- a/lib/feed/class/ticket_url.dart +++ b/lib/feed/class/ticket_url.dart @@ -1,8 +1,6 @@ class TicketUrl { late final String ticketUrl; - TicketUrl({ - required this.ticketUrl, - }); + TicketUrl({required this.ticketUrl}); TicketUrl.fromJson(Map json) { ticketUrl = json['ticket_url']; @@ -14,12 +12,8 @@ class TicketUrl { return data; } - TicketUrl copyWith({ - String? ticketUrl, - }) { - return TicketUrl( - ticketUrl: ticketUrl ?? this.ticketUrl, - ); + TicketUrl copyWith({String? ticketUrl}) { + return TicketUrl(ticketUrl: ticketUrl ?? this.ticketUrl); } TicketUrl.empty() { diff --git a/lib/feed/providers/event_creation_provider.dart b/lib/feed/providers/event_creation_provider.dart index 7b4a6eb0fd..2d15ecef3b 100644 --- a/lib/feed/providers/event_creation_provider.dart +++ b/lib/feed/providers/event_creation_provider.dart @@ -8,9 +8,9 @@ class EventCreationNotifier extends SingleNotifier { EventCreationNotifier({required this.eventRepository}) : super(const AsyncValue.loading()); - void fakeLoad() { - state = AsyncValue.data(EventCreation.empty()); - } + void fakeLoad() { + state = AsyncValue.data(EventCreation.empty()); + } Future addEvent(EventCreation event) async { return await add(eventRepository.createEvent, event); diff --git a/lib/feed/providers/news_image_provider.dart b/lib/feed/providers/news_image_provider.dart index c3155a3a87..92b4e1b877 100644 --- a/lib/feed/providers/news_image_provider.dart +++ b/lib/feed/providers/news_image_provider.dart @@ -11,10 +11,8 @@ import 'package:titan/tools/providers/single_notifier.dart'; class NewsImageNotifier extends SingleNotifier { final newsImageRepository = NewsImageRepository(); final NewsImagesNotifier newsImagesNotifier; - NewsImageNotifier({ - required String token, - required this.newsImagesNotifier, - }) : super(const AsyncValue.loading()) { + NewsImageNotifier({required String token, required this.newsImagesNotifier}) + : super(const AsyncValue.loading()) { newsImageRepository.setToken(token); } diff --git a/lib/feed/repositories/event_creation_repository.dart b/lib/feed/repositories/event_creation_repository.dart index c374f291cb..1031fcc73a 100644 --- a/lib/feed/repositories/event_creation_repository.dart +++ b/lib/feed/repositories/event_creation_repository.dart @@ -18,7 +18,9 @@ class EventCreationRepository extends Repository { } } -final eventCreationRepositoryProvider = Provider((ref) { +final eventCreationRepositoryProvider = Provider(( + ref, +) { final token = ref.watch(tokenProvider); return EventCreationRepository()..setToken(token); }); From 9be962d88ce7ecc34d966c14575e6bcb2fd87084 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Robert?= <114694873+Rotheem@users.noreply.github.com> Date: Wed, 20 Aug 2025 16:58:58 +0200 Subject: [PATCH 297/473] feat: modular base_school_name (#22) * feat: modular base_school_name * fix: clean up * fix: typo --- .github/workflows/build.yml | 7 +++- .github/workflows/release-mobile.yml | 8 ++++ .github/workflows/release-web.yml | 4 ++ .../association_page/edit_association.dart | 2 +- lib/admin/ui/pages/main_page/main_page.dart | 3 +- lib/l10n/app_en.arb | 39 +++++++++---------- lib/l10n/app_fr.arb | 13 +++---- lib/l10n/app_localizations.dart | 32 +++++++-------- lib/l10n/app_localizations_en.dart | 17 ++++---- lib/l10n/app_localizations_fr.dart | 17 ++++---- lib/paiement/router.dart | 3 +- lib/super_admin/tools/function.dart | 3 +- lib/tools/functions.dart | 16 ++++++++ 13 files changed, 93 insertions(+), 71 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 16a7c37c3d..c50280d1e1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -61,9 +61,14 @@ jobs: run: flutter pub get - name: Configure Dev env - run: echo DEV_HOST=$DEV_HOST >> .env + run: | + echo DEV_HOST=$DEV_HOST >> .env + echo PAYMENT_NAME=$PAYMENT_NAME >> .env + echo SCHOOL_NAME=$SCHOOL_NAME >> .env env: DEV_HOST: ${{ vars.DEV_HOST }} + PAYMENT_NAME: ${{ vars.PAYMENT_NAME }} + SCHOOL_NAME: ${{ vars.SCHOOL_NAME }} - name: Add Firebase configuration for Dev run: | diff --git a/.github/workflows/release-mobile.yml b/.github/workflows/release-mobile.yml index a490d11bb1..1e6b1a62a4 100644 --- a/.github/workflows/release-mobile.yml +++ b/.github/workflows/release-mobile.yml @@ -71,6 +71,14 @@ jobs: - name: Get Packages run: flutter pub get + - name: Configure general env + run: | + echo PAYMENT_NAME=$PAYMENT_NAME >> .env + echo SCHOOL_NAME=$SCHOOL_NAME >> .env + env: + PAYMENT_NAME: ${{ vars.PAYMENT_NAME }} + SCHOOL_NAME: ${{ vars.SCHOOL_NAME }} + - name: Configure Alpha env if: needs.extract-version.outputs.isAlpha == 'true' run: | diff --git a/.github/workflows/release-web.yml b/.github/workflows/release-web.yml index 9259e18547..e8b06105c0 100644 --- a/.github/workflows/release-web.yml +++ b/.github/workflows/release-web.yml @@ -58,9 +58,13 @@ jobs: run: | echo PROD_HOST=$PROD_HOST >> .env echo ALPHA_HOST=$ALPHA_HOST >> .env + echo PAYMENT_NAME=$PAYMENT_NAME >> .env + echo SCHOOL_NAME=$SCHOOL_NAME >> .env env: PROD_HOST: ${{ vars.PROD_HOST }} ALPHA_HOST: ${{ vars.ALPHA_HOST }} + PAYMENT_NAME: ${{ vars.PAYMENT_NAME }} + SCHOOL_NAME: ${{ vars.SCHOOL_NAME }} - name: Configure Alpha env if: needs.extract-version.outputs.isAlpha == 'true' diff --git a/lib/admin/ui/pages/association_page/edit_association.dart b/lib/admin/ui/pages/association_page/edit_association.dart index 85f72b51bb..2d34818584 100644 --- a/lib/admin/ui/pages/association_page/edit_association.dart +++ b/lib/admin/ui/pages/association_page/edit_association.dart @@ -184,7 +184,7 @@ class EditAssociation extends HookConsumerWidget { ), SizedBox(height: 20), Button( - text: localizeWithContext.adminConfirm, + text: localizeWithContext.globalConfirm, disabled: !(nameController.value.text != association.name || chosenGroup.value!.id != association.groupId), diff --git a/lib/admin/ui/pages/main_page/main_page.dart b/lib/admin/ui/pages/main_page/main_page.dart index 1fdbe0613d..e418b2034c 100644 --- a/lib/admin/ui/pages/main_page/main_page.dart +++ b/lib/admin/ui/pages/main_page/main_page.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/tools/functions.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/admin/admin.dart'; import 'package:titan/admin/router.dart'; @@ -64,7 +65,7 @@ class AdminMainPage extends HookConsumerWidget { style: Theme.of(context).textTheme.titleLarge, ), ListItem( - title: localizeWithContext.adminPaiement, + title: getPaymentName(), subtitle: localizeWithContext.adminManagePaiementStructures, onTap: () => QR.to(AdminRouter.root + AdminRouter.structures), ), diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 9c5ddbcec4..274c060d62 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -61,7 +61,6 @@ "adminDeleting": "Deleting", "adminDeletingError": "Error while deleting", "adminDescription": "Description", - "adminEclSchool": "Centrale Lyon", "adminEdit": "Edit", "adminEditStructure": "Edit structure", "adminEditMembership": "Edit membership", @@ -86,7 +85,6 @@ "adminMembershipUpdatingError": "Error while updating (likely due to overlapping dates)", "adminMinimum": "Minimum", "adminModifyModuleVisibility": "Module visibility", - "adminMyEclPay": "MyECLPay", "adminName": "Name", "adminNoManager": "No manager selected", "adminNoMember": "No member", @@ -136,13 +134,13 @@ "adminFailedToInviteUsers": "Failed to invite users", "adminDeleteUsers": "Delete users", "adminAdmin": "Admin", - "adminAssociations" : "Associations", - "adminManageAssociations" : "Manage associations", - "adminAddAssociation" : "Add association", - "adminAssociationName" : "Association name", - "adminSelectGroupAssociationManager" : "Select a group to manage this association", - "adminEditAssociation" : "Edit association : {associationName}", - "@adminEditAssociation" : { + "adminAssociations": "Associations", + "adminManageAssociations": "Manage associations", + "adminAddAssociation": "Add association", + "adminAssociationName": "Association name", + "adminSelectGroupAssociationManager": "Select a group to manage this association", + "adminEditAssociation": "Edit association : {associationName}", + "@adminEditAssociation": { "description": "Edit association", "placeholders": { "associationName": { @@ -150,8 +148,8 @@ } } }, - "adminManagerGroup" : "Manager group : {groupName}", - "@adminManagerGroup" : { + "adminManagerGroup": "Manager group : {groupName}", + "@adminManagerGroup": { "description": "Manager group", "placeholders": { "groupName": { @@ -159,16 +157,15 @@ } } }, - "adminAssociationCreated" : "Association created", - "adminAssociationUpdated" : "Association updated", - "adminAssociationCreationError" : "Error while creating association", - "adminAssociationUpdateError" : "Error while updating association", - "adminUpdatedAssociationLogo" : "Association logo updated", - "adminTooHeavyLogo" : "Logo too heavy, maximum size is 4MB", + "adminAssociationCreated": "Association created", + "adminAssociationUpdated": "Association updated", + "adminAssociationCreationError": "Error while creating association", + "adminAssociationUpdateError": "Error while updating association", + "adminUpdatedAssociationLogo": "Association logo updated", + "adminTooHeavyLogo": "Logo too heavy, maximum size is 4MB", "adminFailedToUpdateAssociationLogo": "Failed to update association logo", "adminChooseGroup": "Choose a group", "adminChooseAssociationManagerGroup": "Choose a group to manage this association", - "adminConfirm" : "Confirm", "advertAdd": "Add", "advertAddedAdvert": "Advert published", "advertAddedAnnouncer": "Announcer added", @@ -741,7 +738,7 @@ "phonebookAddingError": "Error adding", "phonebookAddMember": "Add a member", "phonebookAddRole": "Add a role", - "phonebookAdmin": "Administation", + "phonebookAdmin": "Admin", "phonebookAll": "All", "phonebookApparentName": "Public role name:", "phonebookAssociation": "Association", @@ -802,6 +799,7 @@ } } }, + "phonebookDeactivating": "Deactivate the association?", "phonebookDeleting": "Deleting", "phonebookDeletingError": "Error deleting", "phonebookDescription": "Description", @@ -811,6 +809,7 @@ "phonebookEditAssociationInfo": "Edit", "phonebookEditAssociationMembers": "Manage members", "phonebookEditRole": "Edit role", + "phonebookEditMembership": "Edit role", "phonebookEmail": "Email:", "phonebookEmailCopied": "Email copied to clipboard", "phonebookEmptyApparentName": "Please enter a role name", @@ -1244,7 +1243,7 @@ "settingsDisconnectionSuccess": "Disconnected successfully", "settingsDeleteMyAccount": "Delete my account", "settingsDeleteMyAccountDescription": "This action will send a request to the administrator to delete your account.", - "settingsDeletionAsked" : "Your account deletion request has been sent to the administrator.", + "settingsDeletionAsked": "Your account deletion request has been sent to the administrator.", "settingsDeleteMyAccountError": "Error sending account deletion request", "voteAdd": "Add", "voteAddMember": "Add a member", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index fb39385040..ea8925ed95 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -61,7 +61,6 @@ "adminDeleting": "Suppression", "adminDeletingError": "Erreur lors de la suppression", "adminDescription": "Description", - "adminEclSchool": "Centrale Lyon", "adminEdit": "Modifier", "adminEditStructure": "Modifier la structure", "adminEditMembership": "Modifier l'adhésion", @@ -86,7 +85,6 @@ "adminMembershipUpdatingError": "Erreur lors de la modification (surement dû à une superposition de dates)", "adminMinimum": "Minimum", "adminModifyModuleVisibility": "Visibilité des modules", - "adminMyEclPay": "MyECLPay", "adminName": "Nom", "adminNoManager": "Aucun manager n'est sélectionné", "adminNoMember": "Aucun membre", @@ -158,7 +156,7 @@ } } }, - "adminManagerGroup" : "Groupe gestionnaire : {groupName}", + "adminManagerGroup": "Groupe gestionnaire : {groupName}", "@adminManagerGroup": { "description": "Groupe qui gère l'association", "placeholders": { @@ -171,12 +169,11 @@ "adminAssociationUpdated": "Association mise à jour", "adminAssociationCreationError": "Échec de la création de l'association", "adminAssociationUpdateError": "Échec de la mise à jour de l'association", - "adminUpdatedAssociationLogo" : "Logo de l'association mis à jour", + "adminUpdatedAssociationLogo": "Logo de l'association mis à jour", "adminTooHeavyLogo": "Le logo de l'association est trop lourd, il doit faire moins de 4 Mo", "adminFailedToUpdateAssociationLogo": "Échec de la mise à jour du logo de l'association", "adminChooseGroup": "Choisir un groupe", "adminChooseAssociationManagerGroup": "Choisir un groupe gestionnaire pour l'association", - "adminConfirm" : "Valider", "advertAdd": "Ajouter", "advertAddedAdvert": "Annonce publiée", "advertAddedAnnouncer": "Annonceur ajouté", @@ -749,7 +746,7 @@ "phonebookAddingError": "Erreur lors de l'ajout", "phonebookAddMember": "Ajouter un membre", "phonebookAddRole": "Ajouter un rôle", - "phonebookAdmin": "Administration", + "phonebookAdmin": "Admin", "phonebookAll": "Toutes", "phonebookApparentName": "Nom public du rôle :", "phonebookAssociation": "Association", @@ -810,6 +807,7 @@ } } }, + "phonebookDeactivating": "Désactiver l'association ?", "phonebookDeleting": "Suppression", "phonebookDeletingError": "Erreur lors de la suppression", "phonebookDescription": "Description", @@ -819,6 +817,7 @@ "phonebookEditAssociationInfo": "Modifier", "phonebookEditAssociationMembers": "Gérer les membres", "phonebookEditRole": "Modifier le rôle", + "phonebookEditMembership": "Modifier le rôle", "phonebookEmail": "Email :", "phonebookEmailCopied": "Email copié dans le presse-papier", "phonebookEmptyApparentName": "Veuillez entrer un nom de role", @@ -1251,7 +1250,7 @@ "settingsDisconnectionSuccess": "Déconnexion réussie", "settingsDeleteMyAccount": "Supprimer mon compte", "settingsDeleteMyAccountDescription": "Cette action notifie l'administrateur que vous souhaitez supprimer votre compte.", - "settingsDeletionAsked" : "Demande de suppression de compte envoyée", + "settingsDeletionAsked": "Demande de suppression de compte envoyée", "settingsDeleteMyAccountError": "Erreur lors de la demande de suppression de compte", "voteAdd": "Ajouter", "voteAddMember": "Ajouter un membre", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 443fcf77dd..e26205b687 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -464,12 +464,6 @@ abstract class AppLocalizations { /// **'Description'** String get adminDescription; - /// No description provided for @adminEclSchool. - /// - /// In fr, this message translates to: - /// **'Centrale Lyon'** - String get adminEclSchool; - /// No description provided for @adminEdit. /// /// In fr, this message translates to: @@ -614,12 +608,6 @@ abstract class AppLocalizations { /// **'Visibilité des modules'** String get adminModifyModuleVisibility; - /// No description provided for @adminMyEclPay. - /// - /// In fr, this message translates to: - /// **'MyECLPay'** - String get adminMyEclPay; - /// No description provided for @adminName. /// /// In fr, this message translates to: @@ -1010,12 +998,6 @@ abstract class AppLocalizations { /// **'Choisir un groupe gestionnaire pour l\'association'** String get adminChooseAssociationManagerGroup; - /// No description provided for @adminConfirm. - /// - /// In fr, this message translates to: - /// **'Valider'** - String get adminConfirm; - /// No description provided for @advertAdd. /// /// In fr, this message translates to: @@ -4403,7 +4385,7 @@ abstract class AppLocalizations { /// No description provided for @phonebookAdmin. /// /// In fr, this message translates to: - /// **'Administration'** + /// **'Admin'** String get phonebookAdmin; /// No description provided for @phonebookAll. @@ -4574,6 +4556,12 @@ abstract class AppLocalizations { /// **'Supprimer le rôle de l\'utilisateur {name} ?'** String phonebookDeleteUserRole(String name); + /// No description provided for @phonebookDeactivating. + /// + /// In fr, this message translates to: + /// **'Désactiver l\'association ?'** + String get phonebookDeactivating; + /// No description provided for @phonebookDeleting. /// /// In fr, this message translates to: @@ -4628,6 +4616,12 @@ abstract class AppLocalizations { /// **'Modifier le rôle'** String get phonebookEditRole; + /// No description provided for @phonebookEditMembership. + /// + /// In fr, this message translates to: + /// **'Modifier le rôle'** + String get phonebookEditMembership; + /// No description provided for @phonebookEmail. /// /// In fr, this message translates to: diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 16c4dbf289..0dd4b08c2a 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -191,9 +191,6 @@ class AppLocalizationsEn extends AppLocalizations { @override String get adminDescription => 'Description'; - @override - String get adminEclSchool => 'Centrale Lyon'; - @override String get adminEdit => 'Edit'; @@ -268,9 +265,6 @@ class AppLocalizationsEn extends AppLocalizations { @override String get adminModifyModuleVisibility => 'Module visibility'; - @override - String get adminMyEclPay => 'MyECLPay'; - @override String get adminName => 'Name'; @@ -481,9 +475,6 @@ class AppLocalizationsEn extends AppLocalizations { String get adminChooseAssociationManagerGroup => 'Choose a group to manage this association'; - @override - String get adminConfirm => 'Confirm'; - @override String get advertAdd => 'Add'; @@ -2203,7 +2194,7 @@ class AppLocalizationsEn extends AppLocalizations { String get phonebookAddRole => 'Add a role'; @override - String get phonebookAdmin => 'Administation'; + String get phonebookAdmin => 'Admin'; @override String get phonebookAll => 'All'; @@ -2300,6 +2291,9 @@ class AppLocalizationsEn extends AppLocalizations { return 'Delete the role of $name?'; } + @override + String get phonebookDeactivating => 'Deactivate the association?'; + @override String get phonebookDeleting => 'Deleting'; @@ -2328,6 +2322,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get phonebookEditRole => 'Edit role'; + @override + String get phonebookEditMembership => 'Edit role'; + @override String get phonebookEmail => 'Email:'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 9a326184f0..f46fdc91d5 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -192,9 +192,6 @@ class AppLocalizationsFr extends AppLocalizations { @override String get adminDescription => 'Description'; - @override - String get adminEclSchool => 'Centrale Lyon'; - @override String get adminEdit => 'Modifier'; @@ -269,9 +266,6 @@ class AppLocalizationsFr extends AppLocalizations { @override String get adminModifyModuleVisibility => 'Visibilité des modules'; - @override - String get adminMyEclPay => 'MyECLPay'; - @override String get adminName => 'Nom'; @@ -486,9 +480,6 @@ class AppLocalizationsFr extends AppLocalizations { String get adminChooseAssociationManagerGroup => 'Choisir un groupe gestionnaire pour l\'association'; - @override - String get adminConfirm => 'Valider'; - @override String get advertAdd => 'Ajouter'; @@ -2216,7 +2207,7 @@ class AppLocalizationsFr extends AppLocalizations { String get phonebookAddRole => 'Ajouter un rôle'; @override - String get phonebookAdmin => 'Administration'; + String get phonebookAdmin => 'Admin'; @override String get phonebookAll => 'Toutes'; @@ -2313,6 +2304,9 @@ class AppLocalizationsFr extends AppLocalizations { return 'Supprimer le rôle de l\'utilisateur $name ?'; } + @override + String get phonebookDeactivating => 'Désactiver l\'association ?'; + @override String get phonebookDeleting => 'Suppression'; @@ -2341,6 +2335,9 @@ class AppLocalizationsFr extends AppLocalizations { @override String get phonebookEditRole => 'Modifier le rôle'; + @override + String get phonebookEditMembership => 'Modifier le rôle'; + @override String get phonebookEmail => 'Email :'; diff --git a/lib/paiement/router.dart b/lib/paiement/router.dart index a45be2fb2f..fd36380079 100644 --- a/lib/paiement/router.dart +++ b/lib/paiement/router.dart @@ -21,6 +21,7 @@ import 'package:titan/paiement/ui/pages/store_stats_page/store_stats_page.dart' deferred as store_stats_page; import 'package:titan/paiement/ui/pages/transfer_structure_page/transfer_structure_page.dart' deferred as transfer_structure_page; +import 'package:titan/tools/functions.dart'; import 'package:titan/tools/middlewares/admin_middleware.dart'; import 'package:titan/tools/middlewares/authenticated_middleware.dart'; import 'package:titan/tools/middlewares/deferred_middleware.dart'; @@ -38,7 +39,7 @@ class PaymentRouter { static const String storeAdmin = '/storeAdmin'; static const String storeStats = '/storeStats'; static final Module module = Module( - getName: (context) => AppLocalizations.of(context)!.modulePayment, + getName: (context) => getPaymentName(), getDescription: (context) => AppLocalizations.of(context)!.modulePaymentDescription, root: PaymentRouter.root, diff --git a/lib/super_admin/tools/function.dart b/lib/super_admin/tools/function.dart index 8de53f1490..76d39810a7 100644 --- a/lib/super_admin/tools/function.dart +++ b/lib/super_admin/tools/function.dart @@ -1,13 +1,14 @@ import 'package:flutter/widgets.dart'; import 'package:titan/super_admin/tools/constants.dart'; import 'package:titan/l10n/app_localizations.dart'; +import 'package:titan/tools/functions.dart'; String getSchoolNameFromId(String id, String name, BuildContext context) { if (id == SchoolIdConstant.noSchool.value) { return AppLocalizations.of(context)!.adminNoSchool; } if (id == SchoolIdConstant.eclSchool.value) { - return AppLocalizations.of(context)!.adminEclSchool; + return getBaseSchoolName(); } return name; } diff --git a/lib/tools/functions.dart b/lib/tools/functions.dart index dca0d7a05d..fa1280a3fe 100644 --- a/lib/tools/functions.dart +++ b/lib/tools/functions.dart @@ -495,6 +495,22 @@ String getTitanHost() { return host; } +String getPaymentName() { + var paymentName = dotenv.env["PAYMENT_NAME"]; + if (paymentName == null || paymentName.isEmpty) { + paymentName = "Payment"; + } + return paymentName; +} + +String getBaseSchoolName() { + var schoolName = dotenv.env["SCHOOL_NAME"]; + if (schoolName == null || schoolName.isEmpty) { + throw StateError("Could not find school name in environment variables"); + } + return schoolName; +} + String getTitanURL() { switch (getAppFlavor()) { case "dev": From 446e7ce4ee84b836001075a03b64b88f121ee5bd Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 20 Aug 2025 17:43:38 +0200 Subject: [PATCH 298/473] Remove pages --- lib/login/class/create_account.dart | 91 ---- lib/login/providers/sign_up_provider.dart | 31 -- .../repositories/sign_up_repository.dart | 74 --- lib/login/router.dart | 42 -- lib/login/ui/app_sign_in.dart | 17 +- .../create_account_page.dart | 446 ------------------ .../ui/pages/forget_page/forget_page.dart | 179 ------- .../recover_password_page.dart | 245 ---------- .../ui/pages/register_page/register_page.dart | 206 -------- lib/router.dart | 2 - test/login/login_test.dart | 242 ---------- test/login/sign_up_provider_test.dart | 131 ----- 12 files changed, 11 insertions(+), 1695 deletions(-) delete mode 100644 lib/login/class/create_account.dart delete mode 100644 lib/login/providers/sign_up_provider.dart delete mode 100644 lib/login/repositories/sign_up_repository.dart delete mode 100644 lib/login/ui/pages/create_account_page/create_account_page.dart delete mode 100644 lib/login/ui/pages/forget_page/forget_page.dart delete mode 100644 lib/login/ui/pages/recover_password/recover_password_page.dart delete mode 100644 lib/login/ui/pages/register_page/register_page.dart delete mode 100644 test/login/login_test.dart delete mode 100644 test/login/sign_up_provider_test.dart diff --git a/lib/login/class/create_account.dart b/lib/login/class/create_account.dart deleted file mode 100644 index 83d5391365..0000000000 --- a/lib/login/class/create_account.dart +++ /dev/null @@ -1,91 +0,0 @@ -import 'package:titan/tools/functions.dart'; - -class CreateAccount { - late String name; - late String firstname; - late String? nickname; - late String password; - late DateTime birthday; - late String? phone; - late String floor; - late int? promo; - late String activationToken; - - CreateAccount({ - required this.name, - required this.firstname, - required this.nickname, - required this.password, - required this.birthday, - required this.phone, - required this.floor, - required this.promo, - required this.activationToken, - }); - - CreateAccount.fromJson(Map json) { - name = json['name']; - firstname = json['firstname']; - nickname = json['nickname']; - password = json['password']; - birthday = processDateFromAPIWithoutHour(json["birthday"]); - phone = json['phone'] != "" ? json['phone'] : null; - floor = json['floor']; - promo = json['promo']; - activationToken = json['activation_token']; - } - - Map toJson() { - final Map data = {}; - data['name'] = name; - data['firstname'] = firstname; - data['nickname'] = nickname; - data['password'] = password; - data['birthday'] = processDateToAPIWithoutHour(birthday); - data['phone'] = phone; - data['floor'] = floor; - data['promo'] = promo; - data['activation_token'] = activationToken; - return data; - } - - CreateAccount.empty() { - name = ""; - firstname = ""; - nickname = ""; - password = ""; - birthday = DateTime.now(); - phone = ""; - floor = ""; - activationToken = ""; - } - - CreateAccount copyWith({ - String? name, - String? firstname, - String? nickname, - String? password, - DateTime? birthday, - String? phone, - int? promo, - String? floor, - String? activationToken, - }) { - return CreateAccount( - name: name ?? this.name, - firstname: firstname ?? this.firstname, - nickname: nickname ?? this.nickname, - password: password ?? this.password, - birthday: birthday ?? this.birthday, - phone: phone ?? this.phone, - floor: floor ?? this.floor, - promo: promo, - activationToken: activationToken ?? this.activationToken, - ); - } - - @override - String toString() { - return "CreateAccount {name: $name, firstname: $firstname, nickname: $nickname, password: $password, birthday: $birthday, phone: $phone, promo: $promo, floor: $floor, activationToken: $activationToken}"; - } -} diff --git a/lib/login/providers/sign_up_provider.dart b/lib/login/providers/sign_up_provider.dart deleted file mode 100644 index 8c0d642b27..0000000000 --- a/lib/login/providers/sign_up_provider.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/login/class/account_type.dart'; -import 'package:titan/login/class/create_account.dart'; -import 'package:titan/login/class/recover_request.dart'; -import 'package:titan/login/repositories/sign_up_repository.dart'; - -class SignUpProvider extends StateNotifier { - final SignUpRepository repository; - SignUpProvider({required this.repository}) : super(null); - - Future createUser(String email, AccountType accountType) async { - return await repository.createUser(email, accountType); - } - - Future recoverUser(String email) async { - return await repository.recoverUser(email); - } - - Future activateUser(CreateAccount createAccount) async { - return await repository.activateUser(createAccount); - } - - Future resetPassword(RecoverRequest recoverRequest) async { - return await repository.resetPassword(recoverRequest); - } -} - -final signUpProvider = StateNotifierProvider((ref) { - final signUpRepository = ref.watch(signUpRepositoryProvider); - return SignUpProvider(repository: signUpRepository); -}); diff --git a/lib/login/repositories/sign_up_repository.dart b/lib/login/repositories/sign_up_repository.dart deleted file mode 100644 index 19679ff0f2..0000000000 --- a/lib/login/repositories/sign_up_repository.dart +++ /dev/null @@ -1,74 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/auth/providers/openid_provider.dart'; -import 'package:titan/login/class/account_type.dart'; -import 'package:titan/login/class/create_account.dart'; -import 'package:titan/login/class/recover_request.dart'; -import 'package:titan/login/tools/functions.dart'; -import 'package:titan/tools/repository/repository.dart'; - -class SignUpRepository extends Repository { - @override - // ignore: overridden_fields - final ext = "users/"; - - Future createUser(String email, AccountType accountType) async { - try { - final value = await create({ - "email": email, - "account_type": accountTypeToID(accountType), - }, suffix: "create"); - return value["success"]; - } catch (e) { - return false; - } - } - - Future recoverUser(String email) async { - return (await create({"email": email}, suffix: "recover"))["success"]; - } - - Future resetPasswordUser(String token, String password) async { - return await create({ - "reset_token": token, - "new_password": password, - }, suffix: "reset-password"); - } - - Future changePasswordUser( - String userId, - String oldPassword, - String newPassword, - ) async { - return await create({ - "user_id": userId, - "old_password": oldPassword, - "new_password": newPassword, - }, suffix: "change-password"); - } - - Future activateUser(CreateAccount createAccount) async { - try { - final value = await create(createAccount.toJson(), suffix: "activate"); - return value["success"]; - } catch (e) { - return false; - } - } - - Future resetPassword(RecoverRequest recoverRequest) async { - try { - final value = await create( - recoverRequest.toJson(), - suffix: "reset-password", - ); - return value["success"]; - } catch (e) { - return false; - } - } -} - -final signUpRepositoryProvider = Provider((ref) { - final token = ref.watch(tokenProvider); - return SignUpRepository()..setToken(token); -}); diff --git a/lib/login/router.dart b/lib/login/router.dart index 026ce96f22..e83b4d375d 100644 --- a/lib/login/router.dart +++ b/lib/login/router.dart @@ -2,14 +2,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/navigation/providers/is_web_format_provider.dart'; import 'package:titan/login/ui/app_sign_in.dart' deferred as app_sign_in; -import 'package:titan/login/ui/pages/create_account_page/create_account_page.dart' - deferred as create_account_page; -import 'package:titan/login/ui/pages/forget_page/forget_page.dart' - deferred as forget_page; -import 'package:titan/login/ui/pages/recover_password/recover_password_page.dart' - deferred as recover_password_page; -import 'package:titan/login/ui/pages/register_page/register_page.dart' - deferred as register_page; import 'package:titan/login/ui/web/web_sign_in.dart' deferred as web_sign_in; import 'package:titan/tools/middlewares/authenticated_middleware.dart'; import 'package:titan/tools/middlewares/deferred_middleware.dart'; @@ -23,40 +15,6 @@ class LoginRouter { static const String mailReceived = '/mail_received'; LoginRouter(this.ref); - QRoute accountRoute() => QRoute( - path: createAccount, - builder: () => register_page.Register(), - pageType: const QMaterialPage(), - middleware: [DeferredLoadingMiddleware(register_page.loadLibrary)], - children: [ - QRoute( - path: mailReceived, - pageType: const QMaterialPage(), - builder: () => create_account_page.CreateAccountPage(), - middleware: [ - DeferredLoadingMiddleware(create_account_page.loadLibrary), - ], - ), - ], - ); - - QRoute passwordRoute() => QRoute( - path: forgotPassword, - builder: () => forget_page.ForgetPassword(), - pageType: const QMaterialPage(), - middleware: [DeferredLoadingMiddleware(forget_page.loadLibrary)], - children: [ - QRoute( - path: mailReceived, - pageType: const QMaterialPage(), - builder: () => recover_password_page.RecoverPasswordPage(), - middleware: [ - DeferredLoadingMiddleware(recover_password_page.loadLibrary), - ], - ), - ], - ); - QRoute route() => QRoute( path: LoginRouter.root, builder: () => (kIsWeb && ref.watch(isWebFormatProvider)) diff --git a/lib/login/ui/app_sign_in.dart b/lib/login/ui/app_sign_in.dart index 75d1de6293..a1bd4027f0 100644 --- a/lib/login/ui/app_sign_in.dart +++ b/lib/login/ui/app_sign_in.dart @@ -4,7 +4,6 @@ import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/auth/providers/openid_provider.dart'; import 'package:titan/login/providers/animation_provider.dart'; -import 'package:titan/login/router.dart'; import 'package:titan/login/ui/auth_page.dart'; import 'package:titan/login/ui/components/sign_in_up_bar.dart'; import 'package:titan/tools/constants.dart'; @@ -12,6 +11,7 @@ import 'package:titan/tools/functions.dart'; import 'package:titan/tools/providers/path_forwarding_provider.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/l10n/app_localizations.dart'; +import 'package:url_launcher/url_launcher.dart'; class AppSignIn extends HookConsumerWidget { const AppSignIn({super.key}); @@ -106,9 +106,10 @@ class AppSignIn extends HookConsumerWidget { alignment: Alignment.centerLeft, child: InkWell( splashColor: const Color.fromRGBO(255, 255, 255, 1), - onTap: () { - QR.to(LoginRouter.createAccount); - controller?.forward(); + onTap: () async { + await launchUrl( + Uri.parse("${getTitanHost()}calypsso/register"), + ); }, child: Text( AppLocalizations.of(context)!.loginCreateAccount, @@ -126,8 +127,12 @@ class AppSignIn extends HookConsumerWidget { alignment: Alignment.centerLeft, child: InkWell( splashColor: const Color.fromRGBO(255, 255, 255, 1), - onTap: () { - QR.to(LoginRouter.forgotPassword); + onTap: () async { + await launchUrl( + Uri.parse( + "${getTitanHost()}calypsso/change-password", + ), + ); controller?.forward(); }, child: Text( diff --git a/lib/login/ui/pages/create_account_page/create_account_page.dart b/lib/login/ui/pages/create_account_page/create_account_page.dart deleted file mode 100644 index 70b76569fb..0000000000 --- a/lib/login/ui/pages/create_account_page/create_account_page.dart +++ /dev/null @@ -1,446 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:google_fonts/google_fonts.dart'; -import 'package:heroicons/heroicons.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/auth/providers/openid_provider.dart'; -import 'package:titan/login/class/create_account.dart'; -import 'package:titan/login/providers/sign_up_provider.dart'; -import 'package:titan/login/router.dart'; -import 'package:titan/login/ui/components/login_field.dart'; -import 'package:titan/login/ui/auth_page.dart'; -import 'package:titan/login/ui/components/sign_in_up_bar.dart'; -import 'package:titan/login/ui/components/password_strength.dart'; -import 'package:titan/tools/constants.dart'; -import 'package:titan/tools/functions.dart'; -import 'package:titan/tools/ui/widgets/align_left_text.dart'; -import 'package:titan/tools/ui/widgets/date_entry.dart'; -import 'package:titan/user/class/floors.dart'; -import 'package:qlevar_router/qlevar_router.dart'; -import 'package:smooth_page_indicator/smooth_page_indicator.dart'; -import 'package:titan/l10n/app_localizations.dart'; - -class CreateAccountPage extends HookConsumerWidget { - const CreateAccountPage({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final authTokenNotifier = ref.watch(authTokenProvider.notifier); - final signUpNotifier = ref.watch(signUpProvider.notifier); - final code = QR.params['code'] ?? ''; - final isCodeGiven = code != ''; - final activationCode = useTextEditingController(text: code.toString()); - final name = useTextEditingController(); - final password = useTextEditingController(); - final passwordConfirmation = useTextEditingController(); - final firstname = useTextEditingController(); - final nickname = useTextEditingController(); - final birthday = useTextEditingController(); - final phone = useTextEditingController(); - final promo = useTextEditingController(); - final lastIndex = useState(isCodeGiven ? 1 : 0); - List items = Floors.values - .map( - (e) => DropdownMenuItem( - value: capitalize(e.toString().split('.').last), - child: Text(capitalize(e.toString().split('.').last)), - ), - ) - .toList(); - - final floor = useTextEditingController(text: items[0].value.toString()); - final currentPage = useState(isCodeGiven ? 1 : 0); - final pageController = usePageController(initialPage: currentPage.value); - void displayToastWithContext(TypeMsg type, String msg) { - displayToast(context, type, msg); - } - - List> formKeys = [ - GlobalKey(), - GlobalKey(), - GlobalKey(), - GlobalKey(), - GlobalKey(), - GlobalKey(), - GlobalKey(), - GlobalKey(), - GlobalKey(), - GlobalKey(), - ]; - - List steps = [ - CreateAccountField( - controller: activationCode, - label: AppLocalizations.of(context)!.loginActivationCode, - index: 1, - pageController: pageController, - currentPage: currentPage, - formKey: formKeys[0], - ), - Column( - children: [ - CreateAccountField( - controller: password, - label: AppLocalizations.of(context)!.loginPassword, - index: 2, - pageController: pageController, - currentPage: currentPage, - formKey: formKeys[1], - keyboardType: TextInputType.visiblePassword, - ), - const Spacer(), - PasswordStrength( - newPassword: password, - textColor: ColorConstants.background2, - ), - const Spacer(), - ], - ), - Column( - children: [ - CreateAccountField( - controller: passwordConfirmation, - label: AppLocalizations.of(context)!.loginConfirmPassword, - index: 3, - pageController: pageController, - currentPage: currentPage, - formKey: formKeys[2], - keyboardType: TextInputType.visiblePassword, - validator: (value) { - if (value != password.text) { - return AppLocalizations.of(context)!.loginPasswordMustMatch; - } - return null; - }, - ), - const Spacer(), - PasswordStrength( - newPassword: password, - textColor: ColorConstants.background2, - ), - const Spacer(), - ], - ), - CreateAccountField( - controller: name, - label: AppLocalizations.of(context)!.loginName, - index: 4, - pageController: pageController, - currentPage: currentPage, - formKey: formKeys[3], - keyboardType: TextInputType.name, - autofillHints: const [AutofillHints.familyName], - ), - CreateAccountField( - controller: firstname, - label: AppLocalizations.of(context)!.loginFirstname, - index: 5, - pageController: pageController, - currentPage: currentPage, - formKey: formKeys[4], - keyboardType: TextInputType.name, - autofillHints: const [AutofillHints.givenName], - ), - CreateAccountField( - controller: nickname, - label: AppLocalizations.of(context)!.loginNickname, - index: 6, - pageController: pageController, - currentPage: currentPage, - formKey: formKeys[5], - keyboardType: TextInputType.name, - canBeEmpty: true, - hint: AppLocalizations.of(context)!.loginCanBeEmpty, - ), - Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - const SizedBox(height: 9), - AlignLeftText( - AppLocalizations.of(context)!.loginBirthday, - fontSize: 20, - color: ColorConstants.background2, - ), - const SizedBox(height: 1), - Form( - key: formKeys[6], - autovalidateMode: AutovalidateMode.onUserInteraction, - child: DateEntry( - onTap: () { - DateTime now = DateTime.now(); - getOnlyDayDate( - context, - birthday, - firstDate: DateTime(now.year - 110, now.month, now.day), - initialDate: DateTime(now.year - 21, now.month, now.day), - lastDate: DateTime.now(), - ); - }, - label: AppLocalizations.of(context)!.loginBirthday, - controller: birthday, - color: Colors.white, - enabledColor: ColorConstants.background2, - errorColor: Colors.white, - ), - ), - ], - ), - CreateAccountField( - controller: phone, - label: AppLocalizations.of(context)!.loginPhone, - index: 8, - pageController: pageController, - currentPage: currentPage, - formKey: formKeys[7], - keyboardType: TextInputType.phone, - autofillHints: const [AutofillHints.telephoneNumber], - canBeEmpty: true, - hint: AppLocalizations.of(context)!.loginCanBeEmpty, - ), - CreateAccountField( - controller: promo, - label: AppLocalizations.of(context)!.loginPromo, - index: 9, - pageController: pageController, - currentPage: currentPage, - formKey: formKeys[8], - keyboardType: TextInputType.number, - canBeEmpty: true, - mustBeInt: true, - hint: AppLocalizations.of(context)!.loginCanBeEmpty, - ), - Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - const SizedBox(height: 8), - AlignLeftText( - AppLocalizations.of(context)!.loginFloor, - fontSize: 20, - color: ColorConstants.background2, - ), - const SizedBox(height: 8), - AutofillGroup( - child: DropdownButtonFormField( - items: items, - value: floor.text, - onChanged: (value) { - floor.text = value.toString(); - }, - dropdownColor: ColorConstants.background2, - iconEnabledColor: Colors.grey.shade100.withValues(alpha: 0.8), - style: const TextStyle(fontSize: 20, color: Colors.white), - decoration: const InputDecoration( - contentPadding: EdgeInsets.symmetric(vertical: 10), - isDense: true, - enabledBorder: UnderlineInputBorder( - borderSide: BorderSide(color: ColorConstants.background2), - ), - focusedBorder: UnderlineInputBorder( - borderSide: BorderSide(color: Colors.white), - ), - errorBorder: UnderlineInputBorder( - borderSide: BorderSide(color: Colors.white), - ), - errorStyle: TextStyle(color: Colors.white), - ), - ), - ), - ], - ), - SignInUpBar( - label: AppLocalizations.of(context)!.loginEndActivation, - isLoading: false, - onPressed: () async { - if (name.text.isNotEmpty && - firstname.text.isNotEmpty && - birthday.text.isNotEmpty && - floor.text.isNotEmpty && - password.text.isNotEmpty && - activationCode.text.isNotEmpty && - passwordConfirmation.text.isNotEmpty && - password.text == passwordConfirmation.text) { - CreateAccount finalCreateAccount = CreateAccount( - name: name.text, - firstname: firstname.text, - nickname: nickname.text.isEmpty ? null : nickname.text, - birthday: DateTime.parse(processDateBack(birthday.text)), - phone: phone.text.isEmpty ? null : phone.text, - promo: promo.text.isEmpty ? null : int.parse(promo.text), - floor: floor.text, - activationToken: activationCode.text.trim(), - password: password.text, - ); - final accountActivatedMsg = AppLocalizations.of( - context, - )!.loginAccountActivated; - final accountNotActivatedMsg = AppLocalizations.of( - context, - )!.loginAccountNotActivated; - try { - final value = await signUpNotifier.activateUser( - finalCreateAccount, - ); - if (value) { - displayToastWithContext(TypeMsg.msg, accountActivatedMsg); - authTokenNotifier.deleteToken(); - QR.to(LoginRouter.root); - } else { - displayToastWithContext(TypeMsg.error, accountNotActivatedMsg); - } - } catch (e) { - displayToastWithContext(TypeMsg.error, e.toString()); - } - } else { - displayToastWithContext( - TypeMsg.error, - AppLocalizations.of(context)!.loginFillAllFields, - ); - } - }, - ), - ]; - final len = steps.length; - - return LoginTemplate( - callback: (AnimationController controller) { - if (!controller.isCompleted) { - controller.forward(); - } - }, - child: Padding( - padding: const EdgeInsets.all(30.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Align( - alignment: Alignment.centerLeft, - child: GestureDetector( - onTap: () { - QR.to(LoginRouter.createAccount); - }, - child: const HeroIcon( - HeroIcons.chevronLeft, - color: Colors.white, - size: 30, - ), - ), - ), - Expanded( - flex: 3, - child: Align( - alignment: Alignment.centerLeft, - child: Text( - AppLocalizations.of(context)!.loginCreateAccountTitle, - style: GoogleFonts.elMessiri( - textStyle: const TextStyle( - fontSize: 30, - fontWeight: FontWeight.bold, - color: Colors.white, - ), - ), - ), - ), - ), - Expanded( - flex: 5, - child: Column( - children: [ - const Spacer(), - Expanded( - flex: 6, - child: PageView( - physics: const NeverScrollableScrollPhysics(), - scrollDirection: Axis.horizontal, - controller: pageController, - onPageChanged: (value) { - lastIndex.value = currentPage.value; - currentPage.value = value; - }, - children: steps, - ), - ), - const SizedBox(height: 20), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - currentPage.value != (isCodeGiven ? 1 : 0) - ? GestureDetector( - onTap: (() { - FocusScope.of( - context, - ).requestFocus(FocusNode()); - currentPage.value--; - lastIndex.value = currentPage.value; - pageController.previousPage( - duration: const Duration(milliseconds: 500), - curve: Curves.decelerate, - ); - }), - child: const HeroIcon( - HeroIcons.arrowLeft, - color: Colors.white, - size: 30, - ), - ) - : Container(), - currentPage.value != len - 1 - ? GestureDetector( - onTap: (() { - if (currentPage.value >= steps.length - 2 || - formKeys[lastIndex.value].currentState! - .validate()) { - FocusScope.of( - context, - ).requestFocus(FocusNode()); - pageController.nextPage( - duration: const Duration(milliseconds: 500), - curve: Curves.decelerate, - ); - currentPage.value++; - lastIndex.value = currentPage.value; - } - }), - child: const HeroIcon( - HeroIcons.arrowRight, - color: Colors.white, - size: 30, - ), - ) - : Container(), - ], - ), - const Spacer(), - SmoothPageIndicator( - controller: pageController, - count: len, - effect: const WormEffect( - dotColor: ColorConstants.background2, - activeDotColor: Colors.white, - dotWidth: 12, - dotHeight: 12, - ), - onDotClicked: (index) { - if (index < lastIndex.value || - currentPage.value >= steps.length - 2 || - formKeys[lastIndex.value].currentState!.validate()) { - FocusScope.of(context).requestFocus(FocusNode()); - currentPage.value = index; - lastIndex.value = index; - pageController.animateToPage( - index, - duration: const Duration(milliseconds: 500), - curve: Curves.decelerate, - ); - } - }, - ), - const SizedBox(height: 12), - ], - ), - ), - ], - ), - ), - ); - } -} diff --git a/lib/login/ui/pages/forget_page/forget_page.dart b/lib/login/ui/pages/forget_page/forget_page.dart deleted file mode 100644 index aec493ba0f..0000000000 --- a/lib/login/ui/pages/forget_page/forget_page.dart +++ /dev/null @@ -1,179 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:google_fonts/google_fonts.dart'; -import 'package:heroicons/heroicons.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/auth/providers/openid_provider.dart'; -import 'package:titan/login/providers/sign_up_provider.dart'; -import 'package:titan/login/router.dart'; -import 'package:titan/login/ui/auth_page.dart'; -import 'package:titan/login/ui/components/sign_in_up_bar.dart'; -import 'package:titan/login/ui/components/text_from_decoration.dart'; -import 'package:titan/tools/functions.dart'; -import 'package:qlevar_router/qlevar_router.dart'; -import 'package:titan/l10n/app_localizations.dart'; - -class ForgetPassword extends HookConsumerWidget { - const ForgetPassword({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final signUpNotifier = ref.watch(signUpProvider.notifier); - final email = useTextEditingController(); - void displayToastWithContext(TypeMsg type, String msg) { - displayToast(context, type, msg); - } - - return LoginTemplate( - callback: (AnimationController controller) { - if (!controller.isCompleted) { - controller.forward(); - } - }, - child: Form( - autovalidateMode: AutovalidateMode.onUserInteraction, - child: Padding( - padding: const EdgeInsets.all(30.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Align( - alignment: Alignment.centerLeft, - child: GestureDetector( - onTap: () { - QR.to(LoginRouter.root); - }, - child: const HeroIcon( - HeroIcons.chevronLeft, - color: Colors.white, - size: 30, - ), - ), - ), - Expanded( - flex: 3, - child: Align( - alignment: Alignment.centerLeft, - child: Text( - AppLocalizations.of(context)!.loginForgetPassword, - style: GoogleFonts.elMessiri( - textStyle: const TextStyle( - fontSize: 30, - fontWeight: FontWeight.bold, - color: Colors.white, - ), - ), - ), - ), - ), - Expanded( - flex: 5, - child: Column( - children: [ - const SizedBox(height: 30), - Padding( - padding: const EdgeInsets.symmetric(vertical: 16), - child: AutofillGroup( - child: TextFormField( - controller: email, - style: const TextStyle( - fontSize: 18, - color: Colors.white, - ), - decoration: signInRegisterInputDecoration( - isSignIn: false, - hintText: AppLocalizations.of(context)!.loginEmail, - ), - keyboardType: TextInputType.emailAddress, - autofillHints: const [AutofillHints.email], - ), - ), - ), - const SizedBox(height: 30), - SignInUpBar( - label: AppLocalizations.of(context)!.loginRecover, - isLoading: ref - .watch(loadingProvider) - .maybeWhen(data: (data) => data, orElse: () => false), - onPressed: () async { - final sendedResetMail = AppLocalizations.of( - context, - )!.loginSendedResetMail; - final mailSendingError = AppLocalizations.of( - context, - )!.loginMailSendingError; - final value = await signUpNotifier.recoverUser( - email.text, - ); - if (value) { - displayToastWithContext(TypeMsg.msg, sendedResetMail); - email.clear(); - QR.to( - LoginRouter.forgotPassword + - LoginRouter.mailReceived, - ); - } else { - displayToastWithContext( - TypeMsg.error, - mailSendingError, - ); - } - }, - ), - const Spacer(), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - height: 40, - alignment: Alignment.centerLeft, - child: InkWell( - splashColor: const Color.fromRGBO(255, 255, 255, 1), - onTap: () { - QR.to(LoginRouter.root); - }, - child: Text( - AppLocalizations.of(context)!.loginSignIn, - style: const TextStyle( - color: Colors.white, - fontWeight: FontWeight.w800, - decoration: TextDecoration.underline, - fontSize: 14, - ), - ), - ), - ), - Container( - height: 40, - alignment: Alignment.centerLeft, - child: InkWell( - splashColor: const Color.fromRGBO(255, 255, 255, 1), - onTap: () { - QR.to( - LoginRouter.forgotPassword + - LoginRouter.mailReceived, - ); - }, - child: Text( - AppLocalizations.of(context)!.loginRecievedMail, - style: const TextStyle( - color: Colors.white, - fontWeight: FontWeight.w800, - decoration: TextDecoration.underline, - fontSize: 14, - ), - ), - ), - ), - ], - ), - ], - ), - ), - ], - ), - ), - ), - ); - } -} diff --git a/lib/login/ui/pages/recover_password/recover_password_page.dart b/lib/login/ui/pages/recover_password/recover_password_page.dart deleted file mode 100644 index 7c702f688c..0000000000 --- a/lib/login/ui/pages/recover_password/recover_password_page.dart +++ /dev/null @@ -1,245 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:google_fonts/google_fonts.dart'; -import 'package:heroicons/heroicons.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/auth/providers/openid_provider.dart'; -import 'package:titan/login/class/recover_request.dart'; -import 'package:titan/login/providers/sign_up_provider.dart'; -import 'package:titan/login/router.dart'; -import 'package:titan/login/ui/components/login_field.dart'; -import 'package:titan/login/ui/auth_page.dart'; -import 'package:titan/login/ui/components/sign_in_up_bar.dart'; -import 'package:titan/login/ui/components/password_strength.dart'; -import 'package:titan/tools/constants.dart'; -import 'package:titan/tools/functions.dart'; -import 'package:qlevar_router/qlevar_router.dart'; -import 'package:smooth_page_indicator/smooth_page_indicator.dart'; -import 'package:titan/l10n/app_localizations.dart'; - -class RecoverPasswordPage extends HookConsumerWidget { - const RecoverPasswordPage({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final authTokenNotifier = ref.watch(authTokenProvider.notifier); - final signUpNotifier = ref.watch(signUpProvider.notifier); - final activationCode = useTextEditingController(); - final password = useTextEditingController(); - final currentPage = useState(0); - final lastIndex = useState(0); - final pageController = usePageController(); - void displayToastWithContext(TypeMsg type, String msg) { - displayToast(context, type, msg); - } - - List> formKeys = [ - GlobalKey(), - GlobalKey(), - ]; - - List steps = [ - CreateAccountField( - controller: activationCode, - label: AppLocalizations.of(context)!.loginActivationCode, - index: 1, - pageController: pageController, - currentPage: currentPage, - formKey: formKeys[0], - ), - Column( - children: [ - CreateAccountField( - controller: password, - label: AppLocalizations.of(context)!.loginNewPassword, - index: 2, - pageController: pageController, - currentPage: currentPage, - formKey: formKeys[1], - keyboardType: TextInputType.visiblePassword, - ), - const Spacer(), - PasswordStrength( - newPassword: password, - textColor: ColorConstants.background2, - ), - const Spacer(), - ], - ), - SignInUpBar( - label: AppLocalizations.of(context)!.loginEndResetPassword, - isLoading: false, - onPressed: () async { - final resetedPasswordMsg = AppLocalizations.of( - context, - )!.loginResetedPassword; - final invalidTokenMsg = AppLocalizations.of( - context, - )!.loginInvalidToken; - if (password.text.isNotEmpty && activationCode.text.isNotEmpty) { - RecoverRequest recoverRequest = RecoverRequest( - resetToken: activationCode.text.trim(), - newPassword: password.text, - ); - final value = await signUpNotifier.resetPassword(recoverRequest); - if (value) { - displayToastWithContext(TypeMsg.msg, resetedPasswordMsg); - authTokenNotifier.deleteToken(); - QR.to(LoginRouter.root); - } else { - displayToastWithContext(TypeMsg.error, invalidTokenMsg); - } - } else { - displayToastWithContext( - TypeMsg.error, - AppLocalizations.of(context)!.loginFillAllFields, - ); - } - }, - ), - ]; - final len = steps.length; - - return LoginTemplate( - callback: (AnimationController controller) { - if (!controller.isCompleted) { - controller.forward(); - } - }, - child: Padding( - padding: const EdgeInsets.all(30.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Align( - alignment: Alignment.centerLeft, - child: GestureDetector( - onTap: () { - QR.to(LoginRouter.forgotPassword); - }, - child: const HeroIcon( - HeroIcons.chevronLeft, - color: Colors.white, - size: 30, - ), - ), - ), - Expanded( - flex: 3, - child: Align( - alignment: Alignment.centerLeft, - child: Text( - AppLocalizations.of(context)!.loginResetPasswordTitle, - style: GoogleFonts.elMessiri( - textStyle: const TextStyle( - fontSize: 30, - fontWeight: FontWeight.bold, - color: Colors.white, - ), - ), - ), - ), - ), - Expanded( - flex: 5, - child: Column( - children: [ - const Spacer(), - Expanded( - flex: 4, - child: PageView( - scrollDirection: Axis.horizontal, - controller: pageController, - onPageChanged: (index) { - lastIndex.value = currentPage.value; - currentPage.value = index; - }, - physics: const BouncingScrollPhysics(), - children: steps, - ), - ), - const SizedBox(height: 20), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - currentPage.value != 0 - ? GestureDetector( - onTap: (() { - FocusScope.of( - context, - ).requestFocus(FocusNode()); - currentPage.value--; - lastIndex.value = currentPage.value; - pageController.previousPage( - duration: const Duration(milliseconds: 500), - curve: Curves.decelerate, - ); - }), - child: const HeroIcon( - HeroIcons.arrowLeft, - color: Colors.white, - size: 30, - ), - ) - : Container(), - currentPage.value != len - 1 - ? GestureDetector( - onTap: (() { - if (currentPage.value == steps.length - 1 || - formKeys[lastIndex.value].currentState! - .validate()) { - FocusScope.of( - context, - ).requestFocus(FocusNode()); - pageController.nextPage( - duration: const Duration(milliseconds: 500), - curve: Curves.decelerate, - ); - currentPage.value++; - lastIndex.value = currentPage.value; - } - }), - child: const HeroIcon( - HeroIcons.arrowRight, - color: Colors.white, - size: 30, - ), - ) - : Container(), - ], - ), - const Spacer(), - SmoothPageIndicator( - controller: pageController, - count: len, - effect: const WormEffect( - dotColor: ColorConstants.background2, - activeDotColor: Colors.white, - dotWidth: 12, - dotHeight: 12, - ), - onDotClicked: (index) { - if (index < lastIndex.value || - index == steps.length - 1 || - formKeys[lastIndex.value].currentState!.validate()) { - FocusScope.of(context).requestFocus(FocusNode()); - currentPage.value = index; - lastIndex.value = index; - pageController.animateToPage( - index, - duration: const Duration(milliseconds: 500), - curve: Curves.decelerate, - ); - } - }, - ), - const SizedBox(height: 10), - ], - ), - ), - ], - ), - ), - ); - } -} diff --git a/lib/login/ui/pages/register_page/register_page.dart b/lib/login/ui/pages/register_page/register_page.dart deleted file mode 100644 index 250f0e4ee8..0000000000 --- a/lib/login/ui/pages/register_page/register_page.dart +++ /dev/null @@ -1,206 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:google_fonts/google_fonts.dart'; -import 'package:heroicons/heroicons.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/auth/providers/openid_provider.dart'; -import 'package:titan/login/class/account_type.dart'; -import 'package:titan/login/providers/sign_up_provider.dart'; -import 'package:titan/login/router.dart'; -import 'package:titan/login/tools/constants.dart'; -import 'package:titan/login/ui/auth_page.dart'; -import 'package:titan/login/ui/components/sign_in_up_bar.dart'; -import 'package:titan/login/ui/components/text_from_decoration.dart'; -import 'package:titan/tools/functions.dart'; -import 'package:qlevar_router/qlevar_router.dart'; -import 'package:titan/l10n/app_localizations.dart'; - -class Register extends HookConsumerWidget { - const Register({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final signUpNotifier = ref.watch(signUpProvider.notifier); - final mail = useTextEditingController(); - final hidePass = useState(true); - final key = GlobalKey(); - void displayToastWithContext(TypeMsg type, String msg) { - displayToast(context, type, msg); - } - - return LoginTemplate( - callback: (AnimationController controller) { - if (!controller.isCompleted) { - controller.forward(); - } - }, - child: Form( - key: key, - child: Padding( - padding: const EdgeInsets.all(30.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Align( - alignment: Alignment.centerLeft, - child: GestureDetector( - onTap: () { - QR.to(LoginRouter.root); - }, - child: const HeroIcon( - HeroIcons.chevronLeft, - color: Colors.white, - size: 30, - ), - ), - ), - Expanded( - flex: 3, - child: Align( - alignment: Alignment.centerLeft, - child: Text( - AppLocalizations.of(context)!.loginCreateAccountTitle, - style: GoogleFonts.elMessiri( - textStyle: const TextStyle( - fontSize: 30, - fontWeight: FontWeight.bold, - color: Colors.white, - ), - ), - ), - ), - ), - Expanded( - flex: 5, - child: Column( - children: [ - const Spacer(), - Padding( - padding: const EdgeInsets.symmetric(vertical: 16), - child: AutofillGroup( - child: TextFormField( - controller: mail, - style: const TextStyle( - fontSize: 18, - color: Colors.white, - ), - decoration: signInRegisterInputDecoration( - isSignIn: false, - hintText: AppLocalizations.of(context)!.loginEmail, - ), - keyboardType: TextInputType.emailAddress, - autofillHints: const [AutofillHints.email], - validator: (value) { - if (value == null || value.isEmpty) { - return AppLocalizations.of( - context, - )!.loginEmailEmpty; - } - RegExp regExp = RegExp(emailRegExp); - if (!regExp.hasMatch(value)) { - return AppLocalizations.of( - context, - )!.loginEmailInvalid; - } - return null; - }, - ), - ), - ), - const SizedBox(height: 30), - SignInUpBar( - label: AppLocalizations.of(context)!.loginCreate, - isLoading: ref - .watch(loadingProvider) - .maybeWhen(data: (data) => data, orElse: () => false), - onPressed: () async { - final sendedMailMsg = AppLocalizations.of( - context, - )!.loginSendedMail; - final mailSendingErrorMsg = AppLocalizations.of( - context, - )!.loginMailSendingError; - if (key.currentState!.validate()) { - final value = await signUpNotifier.createUser( - mail.text, - AccountType.student, - ); - if (value) { - hidePass.value = true; - mail.clear(); - QR.to( - LoginRouter.createAccount + - LoginRouter.mailReceived, - ); - displayToastWithContext(TypeMsg.msg, sendedMailMsg); - } else { - displayToastWithContext( - TypeMsg.error, - mailSendingErrorMsg, - ); - } - } else { - displayToastWithContext( - TypeMsg.error, - AppLocalizations.of(context)!.loginEmailInvalid, - ); - } - }, - ), - const Spacer(), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - height: 40, - alignment: Alignment.centerLeft, - child: InkWell( - splashColor: const Color.fromRGBO(255, 255, 255, 1), - onTap: () { - QR.to(LoginRouter.root); - }, - child: Text( - AppLocalizations.of(context)!.loginSignIn, - style: const TextStyle( - color: Colors.white, - fontWeight: FontWeight.w800, - decoration: TextDecoration.underline, - fontSize: 14, - ), - ), - ), - ), - Container( - height: 40, - alignment: Alignment.centerLeft, - child: InkWell( - splashColor: const Color.fromRGBO(255, 255, 255, 1), - onTap: () { - QR.to( - LoginRouter.createAccount + - LoginRouter.mailReceived, - ); - }, - child: Text( - AppLocalizations.of(context)!.loginRecievedMail, - style: const TextStyle( - color: Colors.white, - fontWeight: FontWeight.w800, - decoration: TextDecoration.underline, - fontSize: 14, - ), - ), - ), - ), - ], - ), - ], - ), - ), - ], - ), - ), - ), - ); - } -} diff --git a/lib/router.dart b/lib/router.dart index cc37e89323..93e04bed84 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -100,9 +100,7 @@ class AppRouter { FeedRouter(ref).route(), HomeRouter(ref).route(), LoanRouter(ref).route(), - LoginRouter(ref).accountRoute(), LoginRouter(ref).route(), - LoginRouter(ref).passwordRoute(), PaymentRouter(ref).route(), PhonebookRouter(ref).route(), PhRouter(ref).route(), diff --git a/test/login/login_test.dart b/test/login/login_test.dart deleted file mode 100644 index 9464b1eece..0000000000 --- a/test/login/login_test.dart +++ /dev/null @@ -1,242 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:titan/login/class/account_type.dart'; -import 'package:titan/login/class/create_account.dart'; -import 'package:titan/login/class/recover_request.dart'; -import 'package:titan/login/tools/functions.dart'; - -void main() { - group('Testing RecoverRequest class', () { - test('Should return an empty RecoverResquest', () { - final recoverRequest = RecoverRequest.empty(); - expect(recoverRequest, isA()); - expect(recoverRequest.resetToken, ''); - expect(recoverRequest.newPassword, ''); - }); - - test('Should return a RecoverRequest', () { - final recoverRequest = RecoverRequest( - resetToken: 'token', - newPassword: 'password', - ); - expect(recoverRequest, isA()); - expect(recoverRequest.resetToken, 'token'); - expect(recoverRequest.newPassword, 'password'); - }); - - test('Should update with new values', () { - final recoverRequest = RecoverRequest( - resetToken: 'token', - newPassword: 'password', - ); - RecoverRequest newRecoverRequest = recoverRequest.copyWith( - resetToken: 'newToken', - ); - expect(newRecoverRequest.resetToken, 'newToken'); - newRecoverRequest = recoverRequest.copyWith(newPassword: 'newPassword'); - expect(newRecoverRequest.newPassword, 'newPassword'); - }); - - test('Should print a recoverRequest', () { - final recoverRequest = RecoverRequest( - resetToken: 'token', - newPassword: 'password', - ); - expect( - recoverRequest.toString(), - 'RecoverRequest{resetToken: token, newPassword: password}', - ); - }); - - test('Should parse a recoverRequest', () { - final recoverRequest = RecoverRequest.fromJson({ - "reset_token": "token", - "new_password": "password", - }); - expect(recoverRequest, isA()); - expect(recoverRequest.resetToken, 'token'); - expect(recoverRequest.newPassword, 'password'); - }); - - test('Should return a correct json', () { - final recoverRequest = RecoverRequest.fromJson({ - "reset_token": "token", - "new_password": "password", - }); - expect(recoverRequest.toJson(), { - "reset_token": "token", - "new_password": "password", - }); - }); - }); - - group('Testing CreateAccount class', () { - test('Should return an empty CreateAccount', () { - final createAccount = CreateAccount.empty(); - expect(createAccount, isA()); - expect(createAccount.password, ''); - expect(createAccount.phone, ''); - expect(createAccount.activationToken, ''); - expect(createAccount.birthday, isA()); - expect(createAccount.firstname, ''); - expect(createAccount.floor, ''); - expect(createAccount.name, ''); - expect(createAccount.nickname, ''); - }); - - test('Should return a CreateAccount', () { - final createAccount = CreateAccount( - password: 'password', - phone: 'phone', - activationToken: '', - birthday: DateTime.parse('2021-01-01'), - firstname: '', - floor: '', - name: '', - nickname: '', - promo: 1, - ); - expect(createAccount, isA()); - expect(createAccount.password, 'password'); - expect(createAccount.phone, 'phone'); - expect(createAccount.activationToken, ''); - expect(createAccount.birthday, DateTime.parse('2021-01-01')); - expect(createAccount.firstname, ''); - expect(createAccount.floor, ''); - expect(createAccount.name, ''); - expect(createAccount.nickname, ''); - expect(createAccount.promo, 1); - }); - - test('Should update with new values', () { - final createAccount = CreateAccount( - password: 'password', - phone: 'phone', - activationToken: '', - birthday: DateTime.parse('2021-01-01'), - firstname: '', - floor: '', - name: '', - nickname: '', - promo: 1, - ); - CreateAccount newCreateAccount = createAccount.copyWith( - password: 'newPassword', - ); - expect(newCreateAccount.password, 'newPassword'); - newCreateAccount = createAccount.copyWith(phone: 'newPhone'); - expect(newCreateAccount.phone, 'newPhone'); - newCreateAccount = newCreateAccount.copyWith( - activationToken: 'newActivationToken', - ); - expect(newCreateAccount.activationToken, 'newActivationToken'); - newCreateAccount = newCreateAccount.copyWith( - birthday: DateTime.parse('2021-02-02'), - ); - expect(newCreateAccount.birthday, DateTime.parse('2021-02-02')); - newCreateAccount = newCreateAccount.copyWith(firstname: 'newFirstname'); - expect(newCreateAccount.firstname, 'newFirstname'); - newCreateAccount = newCreateAccount.copyWith(floor: 'newFloor'); - expect(newCreateAccount.floor, 'newFloor'); - newCreateAccount = newCreateAccount.copyWith(name: 'newName'); - expect(newCreateAccount.name, 'newName'); - newCreateAccount = newCreateAccount.copyWith(nickname: 'newNickname'); - expect(newCreateAccount.nickname, 'newNickname'); - newCreateAccount = newCreateAccount.copyWith(promo: 2); - expect(newCreateAccount.promo, 2); - }); - - test('Should print a createAccount', () { - final createAccount = CreateAccount( - password: 'password', - phone: 'phone', - activationToken: '', - birthday: DateTime.parse('2021-01-01'), - firstname: '', - floor: '', - name: '', - nickname: '', - promo: 1, - ); - expect( - createAccount.toString(), - 'CreateAccount {name: , firstname: , nickname: , password: password, birthday: 2021-01-01 00:00:00.000, phone: phone, promo: 1, floor: , activationToken: }', - ); - }); - - test('Should parse a createAccount', () { - final createAccount = CreateAccount.fromJson({ - "name": "", - "nickname": "", - "firstname": "", - "password": "password", - "birthday": "2021-01-01", - "phone": "phone", - "floor": "", - "activation_token": "", - }); - expect(createAccount, isA()); - expect(createAccount.password, 'password'); - expect(createAccount.phone, 'phone'); - expect(createAccount.activationToken, ''); - expect(createAccount.birthday, DateTime.parse('2021-01-01')); - expect(createAccount.firstname, ''); - expect(createAccount.floor, ''); - expect(createAccount.name, ''); - expect(createAccount.nickname, ''); - }); - - test('Should return a correct json', () { - final createAccount = CreateAccount.fromJson({ - "password": "password", - "phone": "phone", - "activation_token": "", - "birthday": "2021-01-01", - "firstname": "", - "floor": "", - "name": "", - "nickname": "", - }); - expect(createAccount.toJson(), { - 'name': '', - 'firstname': '', - 'nickname': '', - 'password': 'password', - 'birthday': '2021-01-01', - 'phone': 'phone', - 'floor': '', - 'promo': null, - 'activation_token': '', - }); - }); - }); - - group('Account Type Utils', () { - test('Account Type to ID - Student', () { - expect( - accountTypeToID(AccountType.student), - '39691052-2ae5-4e12-99d0-7a9f5f2b0136', - ); - }); - - test('Account Type to ID - Staff', () { - expect( - accountTypeToID(AccountType.staff), - '703056c4-be9d-475c-aa51-b7fc62a96aaa', - ); - }); - - test('Account Type to ID - Admin', () { - expect( - accountTypeToID(AccountType.admin), - '0a25cb76-4b63-4fd3-b939-da6d9feabf28', - ); - }); - - test('Account Type to ID - Association', () { - expect( - accountTypeToID(AccountType.association), - '29751438-103c-42f2-b09b-33fbb20758a7', - ); - }); - }); -} diff --git a/test/login/sign_up_provider_test.dart b/test/login/sign_up_provider_test.dart deleted file mode 100644 index f9b957fa79..0000000000 --- a/test/login/sign_up_provider_test.dart +++ /dev/null @@ -1,131 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:titan/login/class/account_type.dart'; -import 'package:titan/login/class/create_account.dart'; -import 'package:titan/login/class/recover_request.dart'; -import 'package:titan/login/providers/sign_up_provider.dart'; -import 'package:titan/login/repositories/sign_up_repository.dart'; - -class MockSignUpRepository extends Mock implements SignUpRepository {} - -void main() { - late SignUpProvider signUpProvider; - late MockSignUpRepository mockSignUpRepository; - - setUp(() { - mockSignUpRepository = MockSignUpRepository(); - signUpProvider = SignUpProvider(repository: mockSignUpRepository); - }); - - group('createUser', () { - test('returns true when repository returns true', () async { - when( - () => mockSignUpRepository.createUser( - 'test@test.com', - AccountType.student, - ), - ).thenAnswer((_) async => true); - - final result = await signUpProvider.createUser( - 'test@test.com', - AccountType.student, - ); - - expect(result, true); - }); - - test('returns false when repository returns false', () async { - when( - () => mockSignUpRepository.createUser( - 'test@test.com', - AccountType.student, - ), - ).thenAnswer((_) async => false); - - final result = await signUpProvider.createUser( - 'test@test.com', - AccountType.student, - ); - - expect(result, false); - }); - }); - - group('recoverUser', () { - test('returns true when repository returns true', () async { - when( - () => mockSignUpRepository.recoverUser('test@test.com'), - ).thenAnswer((_) async => true); - - final result = await signUpProvider.recoverUser('test@test.com'); - - expect(result, true); - }); - - test('returns false when repository returns false', () async { - when( - () => mockSignUpRepository.recoverUser('test@test.com'), - ).thenAnswer((_) async => false); - - final result = await signUpProvider.recoverUser('test@test.com'); - - expect(result, false); - }); - }); - - group('activateUser', () { - test('returns true when repository returns true', () async { - final createAccount = CreateAccount.empty().copyWith( - password: 'password', - ); - when( - () => mockSignUpRepository.activateUser(createAccount), - ).thenAnswer((_) async => true); - - final result = await signUpProvider.activateUser(createAccount); - - expect(result, true); - }); - - test('returns false when repository returns false', () async { - final createAccount = CreateAccount.empty().copyWith( - password: 'password', - ); - when( - () => mockSignUpRepository.activateUser(createAccount), - ).thenAnswer((_) async => false); - - final result = await signUpProvider.activateUser(createAccount); - - expect(result, false); - }); - }); - - group('resetPassword', () { - test('returns true when repository returns true', () async { - final recoverRequest = RecoverRequest.empty().copyWith( - newPassword: 'password', - ); - when( - () => mockSignUpRepository.resetPassword(recoverRequest), - ).thenAnswer((_) async => true); - - final result = await signUpProvider.resetPassword(recoverRequest); - - expect(result, true); - }); - - test('returns false when repository returns false', () async { - final recoverRequest = RecoverRequest.empty().copyWith( - newPassword: 'password', - ); - when( - () => mockSignUpRepository.resetPassword(recoverRequest), - ).thenAnswer((_) async => false); - - final result = await signUpProvider.resetPassword(recoverRequest); - - expect(result, false); - }); - }); -} From 42636b25cba1ff77b5ed11226c500d6312298a4a Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 20 Aug 2025 17:45:45 +0200 Subject: [PATCH 299/473] Remove useless pages --- lib/login/class/recover_request.dart | 35 -- .../ui/components/animation_provider.dart | 1 - .../ui/components/password_strength.dart | 103 ------ lib/login/ui/components/secure_bar.dart | 312 ------------------ 4 files changed, 451 deletions(-) delete mode 100644 lib/login/class/recover_request.dart delete mode 100644 lib/login/ui/components/animation_provider.dart delete mode 100644 lib/login/ui/components/password_strength.dart delete mode 100644 lib/login/ui/components/secure_bar.dart diff --git a/lib/login/class/recover_request.dart b/lib/login/class/recover_request.dart deleted file mode 100644 index c4918019d3..0000000000 --- a/lib/login/class/recover_request.dart +++ /dev/null @@ -1,35 +0,0 @@ -class RecoverRequest { - late String resetToken; - late String newPassword; - - RecoverRequest({required this.resetToken, required this.newPassword}); - - RecoverRequest.fromJson(Map json) { - resetToken = json['reset_token']; - newPassword = json['new_password']; - } - - Map toJson() { - final Map data = {}; - data['reset_token'] = resetToken; - data['new_password'] = newPassword; - return data; - } - - RecoverRequest.empty() { - resetToken = ""; - newPassword = ""; - } - - RecoverRequest copyWith({String? resetToken, String? newPassword}) { - return RecoverRequest( - resetToken: resetToken ?? this.resetToken, - newPassword: newPassword ?? this.newPassword, - ); - } - - @override - String toString() { - return 'RecoverRequest{resetToken: $resetToken, newPassword: $newPassword}'; - } -} diff --git a/lib/login/ui/components/animation_provider.dart b/lib/login/ui/components/animation_provider.dart deleted file mode 100644 index 8b13789179..0000000000 --- a/lib/login/ui/components/animation_provider.dart +++ /dev/null @@ -1 +0,0 @@ - diff --git a/lib/login/ui/components/password_strength.dart b/lib/login/ui/components/password_strength.dart deleted file mode 100644 index c5328c1188..0000000000 --- a/lib/login/ui/components/password_strength.dart +++ /dev/null @@ -1,103 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/login/ui/components/secure_bar.dart'; -import 'package:titan/tools/ui/widgets/align_left_text.dart'; -import 'package:titan/l10n/app_localizations.dart'; - -class PasswordStrength extends HookConsumerWidget { - final TextEditingController newPassword; - final Color textColor; - final Color color0 = const Color(0xffd31336); - final Color color1 = const Color(0xff880e65); - final Color color2 = const Color(0xff1c1840); - final Color color3 = const Color(0xff3a5a81); - final Color color4 = const Color(0xff1791b1); - - const PasswordStrength({ - super.key, - required this.newPassword, - this.textColor = Colors.black, - }); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final currentStrength = useState( - AppLocalizations.of(context)!.settingsPasswordStrengthVeryWeak, - ); - final useColor = textColor == Colors.black; - return ValueListenableBuilder( - valueListenable: newPassword, - builder: (context, value, child) { - return Column( - children: [ - const SizedBox(height: 10), - AlignLeftText( - "${AppLocalizations.of(context)!.settingsPasswordStrength} : ${currentStrength.value}", - color: textColor, - ), - const SizedBox(height: 10), - FlutterPasswordStrength( - password: newPassword.text, - backgroundColor: Colors.transparent, - radius: 10, - strengthColors: TweenSequence([ - TweenSequenceItem( - weight: 1.0, - tween: Tween( - begin: useColor ? color0 : Colors.white, - end: useColor ? color1 : Colors.white, - ), - ), - TweenSequenceItem( - weight: 1.0, - tween: Tween( - begin: useColor ? color1 : Colors.white, - end: useColor ? color2 : Colors.white, - ), - ), - TweenSequenceItem( - weight: 1.0, - tween: Tween( - begin: useColor ? color2 : Colors.white, - end: useColor ? color3 : Colors.white, - ), - ), - TweenSequenceItem( - weight: 1.0, - tween: Tween( - begin: useColor ? color3 : Colors.white, - end: useColor ? color4 : Colors.white, - ), - ), - ]), - strengthCallback: (strength) { - if (strength < 0.2) { - currentStrength.value = AppLocalizations.of( - context, - )!.settingsPasswordStrengthVeryWeak; - } else if (strength < 0.4) { - currentStrength.value = AppLocalizations.of( - context, - )!.settingsPasswordStrengthWeak; - } else if (strength < 0.6) { - currentStrength.value = AppLocalizations.of( - context, - )!.settingsPasswordStrengthMedium; - } else if (strength < 0.8) { - currentStrength.value = AppLocalizations.of( - context, - )!.settingsPasswordStrengthStrong; - } else { - currentStrength.value = AppLocalizations.of( - context, - )!.settingsPasswordStrengthVeryStrong; - } - }, - ), - ], - ); - }, - ); - } -} diff --git a/lib/login/ui/components/secure_bar.dart b/lib/login/ui/components/secure_bar.dart deleted file mode 100644 index ab081792b9..0000000000 --- a/lib/login/ui/components/secure_bar.dart +++ /dev/null @@ -1,312 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:zxcvbn/zxcvbn.dart'; - -// Source : https://github.com/JinHoSo/flutter-password-strength/blob/master/lib/flutter_password_strength.dart - -class FlutterPasswordStrength extends StatefulWidget { - final String? password; - - //Strength bar width - final double? width; - - //Strength bar height - final double height; - - //Strength bar colors are changed depending on strength - final Animatable strengthColors; - - //Strength bar background color - final Color backgroundColor; - - //Strength bar radius - final double radius; - - //Strength bar animation duration - final Duration? duration; - - //Strength callback - final void Function(double strength)? strengthCallback; - - const FlutterPasswordStrength({ - super.key, - required this.password, - this.width, - this.height = 5, - required this.strengthColors, - this.backgroundColor = Colors.grey, - this.radius = 0, - this.duration, - this.strengthCallback, - }); - - /* - default strength bar colors - This is approximate values - 0.0 ~ 0.25 : red - 0.26 ~ 0.5 : yellow - 0.51 ~ 0.75 : blue - 0.76 ~ 1 : green - */ - Animatable get _strengthColors => strengthColors; - - //default duration is 300 milliseconds - Duration? get _duration => duration ?? const Duration(milliseconds: 300); - - @override - FlutterPasswordStrengthState createState() => FlutterPasswordStrengthState(); -} - -class FlutterPasswordStrengthState extends State - with SingleTickerProviderStateMixin { - //Animation controller for strength bar - late AnimationController _animationController; - - //Animation for strength bar sharp - late Animation _strengthBarAnimation; - - //Strength bar colors - late Animatable _strengthBarColors; - - //Strength bar color from the list of strength bar colors - late Color _strengthBarColor; - - //Strength bar color - late Color _backgroundColor; - - //Strength bar width - double? _width; - - //Strength bar height - late double _height; - - //Strength bar radius, default is 0 - double _radius = 0; - - //Strength callback - void Function(double strength)? _strengthCallback; - - //_begin is used in _strengthBarAnimation - double _begin = 0; - - //_end is used in _strengthBarAnimation - double _end = 0; - - //calculated strength from password - double _passwordStrength = 0; - - // zxcvbn password strength estimator - Zxcvbn zxcvbn = Zxcvbn(); - - @override - void initState() { - super.initState(); - - //initialize - _animationController = AnimationController( - duration: widget._duration, - vsync: this, - ); - _strengthBarAnimation = Tween( - begin: _begin, - end: _end, - ).animate(_animationController); - _strengthBarColors = widget._strengthColors; - _strengthBarColor = - _strengthBarColors.evaluate( - AlwaysStoppedAnimation(_passwordStrength), - ) ?? - Colors.transparent; - - _backgroundColor = widget.backgroundColor; - - _width = widget.width; - _height = widget.height; - _radius = widget.radius; - _strengthCallback = widget.strengthCallback; - - //start animation - _animationController.forward(); - } - - void animate() { - //calculate strength - if (widget.password == null || widget.password!.isEmpty) { - _passwordStrength = 0; - } else { - _passwordStrength = - (zxcvbn.evaluate(widget.password ?? "").score ?? 0) / 4; - } - - _begin = _end; - _end = _passwordStrength * 100; - - _strengthBarAnimation = Tween( - begin: _begin, - end: _end, - ).animate(_animationController); - _strengthBarColor = - _strengthBarColors.evaluate( - AlwaysStoppedAnimation(_passwordStrength), - ) ?? - Colors.transparent; - - _animationController.forward(from: 0.0); - - if (_strengthCallback != null) { - _strengthCallback!(_passwordStrength); - } - } - - @override - void dispose() { - super.dispose(); - _animationController.dispose(); - } - - @override - void didUpdateWidget(FlutterPasswordStrength oldWidget) { - super.didUpdateWidget(oldWidget); - - if (oldWidget.password != widget.password) { - animate(); - } - } - - @override - Widget build(BuildContext context) { - return StrengthBarContainer( - barColor: _strengthBarColor, - backgroundColor: _backgroundColor, - width: _width, - height: _height, - radius: _radius, - animation: _strengthBarAnimation, - ); - } -} - -class StrengthBarContainer extends AnimatedWidget { - final Color barColor; - final Color backgroundColor; - final double? width; - final double height; - final double radius; - - const StrengthBarContainer({ - super.key, - required this.barColor, - required this.backgroundColor, - this.width, - required this.height, - required this.radius, - required Animation animation, - }) : super(listenable: animation); - - Animation get _percent { - return listenable as Animation; - } - - @override - Widget build(BuildContext context) { - return LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - return CustomPaint( - size: Size(width ?? constraints.maxWidth, height), - painter: StrengthBarBackground( - backgroundColor: backgroundColor, - backgroundRadius: radius, - ), - foregroundPainter: StrengthBar( - barColor: barColor, - barRadius: radius, - percent: _percent.value, - ), - ); - }, - ); - } -} - -class StrengthBar extends CustomPainter { - Color barColor; - double barRadius; - double percent; - - StrengthBar({ - required this.barColor, - required this.barRadius, - required this.percent, - }); - - @override - void paint(Canvas canvas, Size size) { - drawBar(canvas, size); - } - - void drawBar(Canvas canvas, Size size) { - Paint paint = Paint() - ..color = barColor - ..style = PaintingStyle.fill - ..strokeCap = StrokeCap.round; - - double left = 0; - double top = 0; - double right = size.width / 100 * percent; - double bottom = size.height; - - //the bar width needs to be bigger than radius width - if (barRadius != 0 && right > 0 && barRadius * 2 > right) { - right = barRadius * 2; - } - - canvas.drawRRect( - RRect.fromRectAndRadius( - Rect.fromLTRB(left, top, right, bottom), - Radius.circular(barRadius), - ), - paint, - ); - } - - @override - bool shouldRepaint(StrengthBar oldDelegate) { - return oldDelegate.percent != percent; - } -} - -class StrengthBarBackground extends CustomPainter { - Color backgroundColor; - double? backgroundRadius; - - StrengthBarBackground({required this.backgroundColor, this.backgroundRadius}); - - @override - void paint(Canvas canvas, Size size) { - drawBarBackground(canvas, size); - } - - void drawBarBackground(Canvas canvas, Size size) { - Paint paint = Paint() - ..color = backgroundColor - ..style = PaintingStyle.fill - ..strokeCap = StrokeCap.round; - - double left = 0; - double top = 0; - double right = size.width; - double bottom = size.height; - - canvas.drawRRect( - RRect.fromRectAndRadius( - Rect.fromLTRB(left, top, right, bottom), - Radius.circular(backgroundRadius ?? 0), - ), - paint, - ); - } - - @override - bool shouldRepaint(StrengthBarBackground oldDelegate) { - return true; - } -} From 778818caccf1f7e3f9c98111be138e5b1cf48303 Mon Sep 17 00:00:00 2001 From: Thonyk Date: Wed, 20 Aug 2025 23:49:09 +0200 Subject: [PATCH 300/473] feat: update web login page and add change password in settings --- lib/login/ui/app_sign_in.dart | 7 +- lib/login/ui/web/left_panel.dart | 18 +-- .../ui/pages/main_page/main_page.dart | 103 ++++++++++-------- .../ui/styleguide/custom_dialog_box.dart | 8 +- 4 files changed, 72 insertions(+), 64 deletions(-) diff --git a/lib/login/ui/app_sign_in.dart b/lib/login/ui/app_sign_in.dart index a1bd4027f0..505bf94cc0 100644 --- a/lib/login/ui/app_sign_in.dart +++ b/lib/login/ui/app_sign_in.dart @@ -3,7 +3,6 @@ import 'package:google_fonts/google_fonts.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/auth/providers/openid_provider.dart'; -import 'package:titan/login/providers/animation_provider.dart'; import 'package:titan/login/ui/auth_page.dart'; import 'package:titan/login/ui/components/sign_in_up_bar.dart'; import 'package:titan/tools/constants.dart'; @@ -20,7 +19,6 @@ class AppSignIn extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final authNotifier = ref.watch(authTokenProvider.notifier); final pathForwarding = ref.read(pathForwardingProvider); - final controller = ref.watch(backgroundAnimationProvider); return LoginTemplate( callback: (AnimationController controller) { @@ -129,11 +127,8 @@ class AppSignIn extends HookConsumerWidget { splashColor: const Color.fromRGBO(255, 255, 255, 1), onTap: () async { await launchUrl( - Uri.parse( - "${getTitanHost()}calypsso/change-password", - ), + Uri.parse("${getTitanHost()}calypsso/recover"), ); - controller?.forward(); }, child: Text( AppLocalizations.of(context)!.loginForgotPassword, diff --git a/lib/login/ui/web/left_panel.dart b/lib/login/ui/web/left_panel.dart index 2178876335..d1290b4548 100644 --- a/lib/login/ui/web/left_panel.dart +++ b/lib/login/ui/web/left_panel.dart @@ -3,13 +3,12 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/auth/providers/openid_provider.dart'; -import 'package:titan/login/providers/animation_provider.dart'; -import 'package:titan/login/router.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/providers/path_forwarding_provider.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/l10n/app_localizations.dart'; +import 'package:url_launcher/url_launcher.dart'; class LeftPanel extends HookConsumerWidget { const LeftPanel({super.key}); @@ -18,7 +17,6 @@ class LeftPanel extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final authNotifier = ref.watch(authTokenProvider.notifier); final pathForwarding = ref.read(pathForwardingProvider); - final controller = ref.watch(backgroundAnimationProvider); final isLoading = ref .watch(loadingProvider) .maybeWhen(data: (data) => data, orElse: () => false); @@ -164,9 +162,10 @@ class LeftPanel extends HookConsumerWidget { children: [ const Spacer(), GestureDetector( - onTap: () { - QR.to(LoginRouter.createAccount); - controller?.forward(); + onTap: () async { + await launchUrl( + Uri.parse("${getTitanHost()}calypsso/register"), + ); }, child: Text( AppLocalizations.of(context)!.loginCreateAccount, @@ -180,9 +179,10 @@ class LeftPanel extends HookConsumerWidget { ), const Spacer(flex: 4), GestureDetector( - onTap: () { - QR.to(LoginRouter.forgotPassword); - controller?.forward(); + onTap: () async { + await launchUrl( + Uri.parse("${getTitanHost()}calypsso/recover"), + ); }, child: Text( AppLocalizations.of(context)!.loginForgotPassword, diff --git a/lib/settings/ui/pages/main_page/main_page.dart b/lib/settings/ui/pages/main_page/main_page.dart index 9c638d47eb..1608e2aede 100644 --- a/lib/settings/ui/pages/main_page/main_page.dart +++ b/lib/settings/ui/pages/main_page/main_page.dart @@ -6,7 +6,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/auth/providers/openid_provider.dart'; import 'package:titan/service/providers/firebase_token_expiration_provider.dart'; import 'package:titan/service/providers/messages_provider.dart'; -import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; +import 'package:titan/tools/ui/styleguide/custom_dialog_box.dart'; import 'package:titan/tools/ui/widgets/vertical_clip_scroll.dart'; import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/settings/providers/notification_topic_provider.dart'; @@ -26,6 +26,7 @@ import 'package:titan/tools/ui/styleguide/list_item.dart'; import 'package:titan/tools/ui/styleguide/list_item_template.dart'; import 'package:titan/user/providers/profile_picture_provider.dart'; import 'package:titan/user/providers/user_provider.dart'; +import 'package:url_launcher/url_launcher.dart'; class SettingsMainPage extends HookConsumerWidget { const SettingsMainPage({super.key}); @@ -323,65 +324,75 @@ class SettingsMainPage extends HookConsumerWidget { color: ColorConstants.title, ), ), + const SizedBox(height: 10), + ListItem( + title: localizeWithContext.settingsChangePassword, + onTap: () async { + await launchUrl( + Uri.parse("${getTitanHost()}calypsso/change-password"), + ); + }, + ), + const SizedBox(height: 10), ListItem( title: localizeWithContext.settingsDisconnect, onTap: () async { - await showDialog( + await showCustomBottomModal( + ref: ref, context: context, - builder: (context) { - return CustomDialogBox( - descriptions: - localizeWithContext.settingsDisconnectDescription, - title: localizeWithContext.settingsDisconnect, - onYes: () { - auth.deleteToken(); - if (!kIsWeb) { - ref.watch(messagesProvider.notifier).forgetDevice(); - ref - .watch(firebaseTokenExpirationProvider.notifier) - .reset(); - } - isCachingNotifier.set(false); - displayToast( - context, - TypeMsg.msg, - localizeWithContext.settingsDisconnectionSuccess, - ); - }, - ); - }, + modal: ConfirmModal( + description: + localizeWithContext.settingsDisconnectDescription, + title: localizeWithContext.settingsDisconnect, + onYes: () { + auth.deleteToken(); + if (!kIsWeb) { + ref.watch(messagesProvider.notifier).forgetDevice(); + ref + .watch(firebaseTokenExpirationProvider.notifier) + .reset(); + } + isCachingNotifier.set(false); + displayToast( + context, + TypeMsg.msg, + localizeWithContext.settingsDisconnectionSuccess, + ); + }, + ), ); }, ), + const SizedBox(height: 10), ListItem( title: localizeWithContext.settingsDeleteMyAccount, onTap: () async { - await showDialog( + await showCustomBottomModal( context: context, - builder: (context) { - return CustomDialogBox( - descriptions: localizeWithContext - .settingsDeleteMyAccountDescription, - title: localizeWithContext.settingsDeleteMyAccount, - onYes: () async { - final value = await meNotifier.deletePersonal(); - if (value) { - displayToastWithContext( - TypeMsg.msg, - localizeWithContext.settingsDeletionAsked, - ); - } else { - displayToastWithContext( - TypeMsg.error, - localizeWithContext.settingsDeleteMyAccountError, - ); - } - }, - ); - }, + ref: ref, + modal: ConfirmModal.danger( + description: localizeWithContext + .settingsDeleteMyAccountDescription, + title: localizeWithContext.settingsDeleteMyAccount, + onYes: () async { + final value = await meNotifier.deletePersonal(); + if (value) { + displayToastWithContext( + TypeMsg.msg, + localizeWithContext.settingsDeletionAsked, + ); + } else { + displayToastWithContext( + TypeMsg.error, + localizeWithContext.settingsDeleteMyAccountError, + ); + } + }, + ), ); }, ), + const SizedBox(height: 80), ], ), ), diff --git a/lib/tools/ui/styleguide/custom_dialog_box.dart b/lib/tools/ui/styleguide/custom_dialog_box.dart index 4a2195e5e4..63e3ca67e4 100644 --- a/lib/tools/ui/styleguide/custom_dialog_box.dart +++ b/lib/tools/ui/styleguide/custom_dialog_box.dart @@ -3,8 +3,6 @@ import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; import 'package:titan/tools/ui/styleguide/button.dart'; -const double padding = 20.0; - enum ModalType { main, danger } class ConfirmModal extends StatelessWidget { @@ -38,10 +36,14 @@ class ConfirmModal extends StatelessWidget { @override Widget build(BuildContext context) { AppLocalizations localizeWithContext = AppLocalizations.of(context)!; - return BottomModalTemplate.danger( + return BottomModalTemplate( title: title, description: description, + type: type == ModalType.main + ? BottomModalType.main + : BottomModalType.danger, actions: [ + const SizedBox(height: 20), Row( children: [ Expanded( From 090bc4cecf159a21ecc1d782e17491d506285fd1 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Wed, 20 Aug 2025 23:54:10 +0200 Subject: [PATCH 301/473] feat: new background color --- lib/login/ui/components/background_painter.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/login/ui/components/background_painter.dart b/lib/login/ui/components/background_painter.dart index 73f8902c15..d2c06ed1d9 100644 --- a/lib/login/ui/components/background_painter.dart +++ b/lib/login/ui/components/background_painter.dart @@ -119,7 +119,7 @@ class BackgroundPainter extends CustomPainter { Point(w, lerpDouble(0, h / 10.2, blueAnim.value)!), ]); - var colors = [ColorConstants.gradient1, ColorConstants.gradient2]; + var colors = [ColorConstants.main, ColorConstants.onMain]; Rect rectShape = Rect.fromLTWH(0, 0, w, h); final Gradient gradient = LinearGradient( @@ -128,7 +128,7 @@ class BackgroundPainter extends CustomPainter { end: Alignment.topRight, ); - paint = Paint()..color = ColorConstants.background2; + paint = Paint()..color = ColorConstants.onTertiary; paint2 = Paint()..shader = gradient.createShader(rectShape); paint3 = Paint()..shader = gradient.createShader(rectShape); @@ -138,7 +138,7 @@ class BackgroundPainter extends CustomPainter { canvas.drawShadow( path, - ColorConstants.background2.withAlpha(125), + ColorConstants.onTertiary.withAlpha(125), 10.0, false, ); From 6fcee938e88f177cb3819ec9607b3e254abd0bb5 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Wed, 20 Aug 2025 23:56:51 +0200 Subject: [PATCH 302/473] feat: new color for web login --- lib/login/ui/web/left_panel.dart | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/lib/login/ui/web/left_panel.dart b/lib/login/ui/web/left_panel.dart index 2178876335..3da26a201d 100644 --- a/lib/login/ui/web/left_panel.dart +++ b/lib/login/ui/web/left_panel.dart @@ -5,6 +5,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/auth/providers/openid_provider.dart'; import 'package:titan/login/providers/animation_provider.dart'; import 'package:titan/login/router.dart'; +import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/providers/path_forwarding_provider.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; @@ -47,7 +48,10 @@ class LeftPanel extends HookConsumerWidget { const SizedBox(width: 15), const Text( "-", - style: TextStyle(fontSize: 25, color: Colors.black), + style: TextStyle( + fontSize: 25, + color: ColorConstants.onTertiary, + ), ), const SizedBox(width: 15), const Text( @@ -55,7 +59,7 @@ class LeftPanel extends HookConsumerWidget { style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, - color: Colors.black, + color: ColorConstants.onTertiary, ), ), ], @@ -104,22 +108,14 @@ class LeftPanel extends HookConsumerWidget { height: 60, decoration: BoxDecoration( gradient: const LinearGradient( - colors: [ - Color(0xFFFF8A14), - Color.fromARGB(255, 255, 114, 0), - ], + colors: [ColorConstants.main, ColorConstants.onMain], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( - color: const Color.fromARGB( - 255, - 255, - 114, - 0, - ).withValues(alpha: 0.2), + color: ColorConstants.onMain.withValues(alpha: 0.2), spreadRadius: 3, blurRadius: 7, offset: const Offset(0, 3), @@ -136,7 +132,7 @@ class LeftPanel extends HookConsumerWidget { style: const TextStyle( fontSize: 30, fontWeight: FontWeight.bold, - color: Colors.white, + color: ColorConstants.background, ), ), Container( @@ -145,12 +141,12 @@ class LeftPanel extends HookConsumerWidget { ? const Padding( padding: EdgeInsets.all(12.0), child: CircularProgressIndicator( - color: Colors.white, + color: ColorConstants.background, ), ) : const HeroIcon( HeroIcons.arrowRight, - color: Colors.white, + color: ColorConstants.background, size: 35.0, ), ), @@ -174,7 +170,7 @@ class LeftPanel extends HookConsumerWidget { fontSize: 15, fontWeight: FontWeight.bold, decoration: TextDecoration.underline, - color: Color.fromARGB(255, 48, 48, 48), + color: ColorConstants.onTertiary, ), ), ), @@ -190,7 +186,7 @@ class LeftPanel extends HookConsumerWidget { fontSize: 15, fontWeight: FontWeight.bold, decoration: TextDecoration.underline, - color: Color.fromARGB(255, 48, 48, 48), + color: ColorConstants.onTertiary, ), ), ), From bb8f163fbf69eece1f71e0226e8c40117da7c6dc Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Thu, 21 Aug 2025 00:02:15 +0200 Subject: [PATCH 303/473] feat: removing unused page --- lib/login/ui/app_sign_in.dart | 2 +- .../ui/pages/sign_in_page/sign_in_page.dart | 169 ------------------ 2 files changed, 1 insertion(+), 170 deletions(-) delete mode 100644 lib/login/ui/pages/sign_in_page/sign_in_page.dart diff --git a/lib/login/ui/app_sign_in.dart b/lib/login/ui/app_sign_in.dart index 505bf94cc0..c341251d49 100644 --- a/lib/login/ui/app_sign_in.dart +++ b/lib/login/ui/app_sign_in.dart @@ -35,7 +35,7 @@ class AppSignIn extends HookConsumerWidget { child: Align( alignment: Alignment.centerLeft, child: Text( - "MyECL", + AppLocalizations.of(context)!.loginAppName, style: GoogleFonts.elMessiri( textStyle: const TextStyle( fontSize: 40, diff --git a/lib/login/ui/pages/sign_in_page/sign_in_page.dart b/lib/login/ui/pages/sign_in_page/sign_in_page.dart deleted file mode 100644 index bf3572dec6..0000000000 --- a/lib/login/ui/pages/sign_in_page/sign_in_page.dart +++ /dev/null @@ -1,169 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:google_fonts/google_fonts.dart'; -import 'package:heroicons/heroicons.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/auth/providers/openid_provider.dart'; -import 'package:titan/login/router.dart'; -import 'package:titan/login/ui/auth_page.dart'; -import 'package:titan/login/ui/components/sign_in_up_bar.dart'; -import 'package:titan/tools/constants.dart'; -import 'package:titan/tools/functions.dart'; -import 'package:titan/tools/providers/path_forwarding_provider.dart'; -import 'package:qlevar_router/qlevar_router.dart'; -import 'package:titan/l10n/app_localizations.dart'; - -class SignIn extends HookConsumerWidget { - const SignIn({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final authNotifier = ref.watch(authTokenProvider.notifier); - final pathForwarding = ref.read(pathForwardingProvider); - - return LoginTemplate( - callback: (AnimationController controller) { - if (controller.isCompleted) { - controller.reverse(); - } - }, - child: AutofillGroup( - child: Form( - autovalidateMode: AutovalidateMode.onUserInteraction, - child: Padding( - padding: const EdgeInsets.all(20.0), - child: Column( - children: [ - Expanded( - flex: 3, - child: Align( - alignment: Alignment.centerLeft, - child: Text( - AppLocalizations.of(context)!.loginAppName, - style: GoogleFonts.elMessiri( - textStyle: const TextStyle( - fontSize: 40, - fontWeight: FontWeight.bold, - color: Colors.white, - ), - ), - ), - ), - ), - Expanded( - flex: 5, - child: Column( - children: [ - Expanded( - flex: 2, - child: Column( - children: [ - Expanded( - child: Image(image: AssetImage(getTitanLogo())), - ), - SignInUpBar( - isLoading: ref - .watch(loadingProvider) - .maybeWhen( - data: (data) => data, - orElse: () => false, - ), - label: AppLocalizations.of(context)!.loginSignIn, - onPressed: () async { - await authNotifier.getTokenFromRequest(); - ref - .watch(authTokenProvider) - .when( - data: (token) { - QR.to(pathForwarding.path); - }, - error: (e, s) { - displayToast( - context, - TypeMsg.error, - AppLocalizations.of( - context, - )!.loginLoginFailed, - ); - }, - loading: () {}, - ); - }, - color: ColorConstants.background2, - icon: const HeroIcon( - HeroIcons.arrowRight, - color: ColorConstants.background2, - size: 35.0, - ), - ), - ], - ), - ), - const Spacer(flex: 1), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - height: 40, - alignment: Alignment.centerLeft, - child: InkWell( - splashColor: const Color.fromRGBO( - 255, - 255, - 255, - 1, - ), - onTap: () { - QR.to(LoginRouter.createAccount); - }, - child: Text( - AppLocalizations.of( - context, - )!.loginCreateAccount, - style: const TextStyle( - color: Colors.white, - fontWeight: FontWeight.w800, - decoration: TextDecoration.underline, - fontSize: 14, - ), - ), - ), - ), - Container( - height: 40, - alignment: Alignment.centerLeft, - child: InkWell( - splashColor: const Color.fromRGBO( - 255, - 255, - 255, - 1, - ), - onTap: () { - QR.to(LoginRouter.forgotPassword); - }, - child: Text( - AppLocalizations.of( - context, - )!.loginForgotPassword, - style: const TextStyle( - color: Colors.white, - fontWeight: FontWeight.w800, - decoration: TextDecoration.underline, - fontSize: 14, - ), - ), - ), - ), - ], - ), - ], - ), - ), - ], - ), - ), - ), - ), - ); - } -} From c9a8d763ccd9f39123634435c1928219bfc4e86d Mon Sep 17 00:00:00 2001 From: Thonyk Date: Thu, 21 Aug 2025 00:05:26 +0200 Subject: [PATCH 304/473] fix: remove unused routes --- lib/login/router.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/login/router.dart b/lib/login/router.dart index e83b4d375d..2ac01f366f 100644 --- a/lib/login/router.dart +++ b/lib/login/router.dart @@ -10,9 +10,6 @@ import 'package:qlevar_router/qlevar_router.dart'; class LoginRouter { final Ref ref; static const String root = '/login'; - static const String createAccount = '/create_account'; - static const String forgotPassword = '/forgot_password'; - static const String mailReceived = '/mail_received'; LoginRouter(this.ref); QRoute route() => QRoute( From b92408c4010a64fe0d2fd6bc4afc7140862f311b Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 20 Aug 2025 17:58:01 +0200 Subject: [PATCH 305/473] fix event handling page --- .../event_handling_page/event_handling_page.dart | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/lib/feed/ui/pages/event_handling_page/event_handling_page.dart b/lib/feed/ui/pages/event_handling_page/event_handling_page.dart index 99c2dfc575..c5aac1f50f 100644 --- a/lib/feed/ui/pages/event_handling_page/event_handling_page.dart +++ b/lib/feed/ui/pages/event_handling_page/event_handling_page.dart @@ -90,16 +90,12 @@ class EventHandlingPage extends HookConsumerWidget { ), ); } - - return ListView.builder( - physics: const BouncingScrollPhysics(), - itemCount: filteredNews.length + 1, - itemBuilder: (context, index) { - if (index == filteredNews.length) { - return const SizedBox(height: 80); - } - return AdminEventCard(news: filteredNews[index]); - }, + return SingleChildScrollView( + child: Column( + children: filteredNews + .map((news) => AdminEventCard(news: news)) + .toList(), + ), ); }, ), From 05ff191fffef69840b82d081bbef6d6d4fd01190 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 20 Aug 2025 18:59:38 +0200 Subject: [PATCH 306/473] manage event image --- .../class/{event_creation.dart => event.dart} | 20 ++++++----- .../providers/event_creation_provider.dart | 29 ---------------- lib/feed/providers/event_image_provider.dart | 31 +++++++++++++++++ lib/feed/providers/event_provider.dart | 31 +++++++++++++++++ .../providers/event_ticket_url_provider.dart | 6 ++-- lib/feed/providers/news_image_provider.dart | 7 ---- .../repositories/event_image_repository.dart | 22 ++++++++++++ ..._repository.dart => event_repository.dart} | 20 ++++++----- .../pages/add_event_page/add_event_page.dart | 34 ++++++++----------- 9 files changed, 125 insertions(+), 75 deletions(-) rename lib/feed/class/{event_creation.dart => event.dart} (84%) delete mode 100644 lib/feed/providers/event_creation_provider.dart create mode 100644 lib/feed/providers/event_image_provider.dart create mode 100644 lib/feed/providers/event_provider.dart create mode 100644 lib/feed/repositories/event_image_repository.dart rename lib/feed/repositories/{event_creation_repository.dart => event_repository.dart} (51%) diff --git a/lib/feed/class/event_creation.dart b/lib/feed/class/event.dart similarity index 84% rename from lib/feed/class/event_creation.dart rename to lib/feed/class/event.dart index d18a94030b..7b1caa2334 100644 --- a/lib/feed/class/event_creation.dart +++ b/lib/feed/class/event.dart @@ -1,6 +1,7 @@ import 'package:titan/tools/functions.dart'; -class EventCreation { +class Event { + late final String id; late final String name; late final DateTime start; late final DateTime end; @@ -11,7 +12,8 @@ class EventCreation { late final String associationId; late final String? ticketUrl; - EventCreation({ + Event({ + required this.id, required this.name, required this.start, required this.end, @@ -23,7 +25,8 @@ class EventCreation { this.ticketUrl, }); - EventCreation.fromJson(Map json) { + Event.fromJson(Map json) { + id = json['id']; name = json['name']; start = processDateFromAPI(json['start']); end = processDateFromAPI(json['end']); @@ -53,11 +56,10 @@ class EventCreation { if (ticketUrl != null) { data['ticket_url'] = ticketUrl; } - data['description'] = ""; // TODO: remove return data; } - EventCreation copyWith({ + Event copyWith({ String? name, DateTime? start, DateTime? end, @@ -69,7 +71,8 @@ class EventCreation { String? ticketUrl, bool? hasRoom, }) { - return EventCreation( + return Event( + id: id, name: name ?? this.name, start: start ?? this.start, end: end ?? this.end, @@ -82,7 +85,8 @@ class EventCreation { ); } - EventCreation.empty() { + Event.empty() { + id = ''; name = ''; start = DateTime.now(); end = DateTime.now(); @@ -96,6 +100,6 @@ class EventCreation { @override String toString() { - return 'EventCreation{name: $name, start: $start, end: $end, allDay: $allDay, location: $location, recurrenceRule: $recurrenceRule, ticketUrlOpening: $ticketUrlOpening, associationId: $associationId, ticketUrl: $ticketUrl}'; + return 'Event{name: $name, start: $start, end: $end, allDay: $allDay, location: $location, recurrenceRule: $recurrenceRule, ticketUrlOpening: $ticketUrlOpening, associationId: $associationId, ticketUrl: $ticketUrl}'; } } diff --git a/lib/feed/providers/event_creation_provider.dart b/lib/feed/providers/event_creation_provider.dart deleted file mode 100644 index 2d15ecef3b..0000000000 --- a/lib/feed/providers/event_creation_provider.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/feed/class/event_creation.dart'; -import 'package:titan/feed/repositories/event_creation_repository.dart'; -import 'package:titan/tools/providers/single_notifier.dart'; - -class EventCreationNotifier extends SingleNotifier { - final EventCreationRepository eventRepository; - EventCreationNotifier({required this.eventRepository}) - : super(const AsyncValue.loading()); - - void fakeLoad() { - state = AsyncValue.data(EventCreation.empty()); - } - - Future addEvent(EventCreation event) async { - return await add(eventRepository.createEvent, event); - } -} - -final eventCreationProvider = - StateNotifierProvider>(( - ref, - ) { - final eventRepository = ref.watch(eventCreationRepositoryProvider); - EventCreationNotifier notifier = EventCreationNotifier( - eventRepository: eventRepository, - )..fakeLoad(); - return notifier; - }); diff --git a/lib/feed/providers/event_image_provider.dart b/lib/feed/providers/event_image_provider.dart new file mode 100644 index 0000000000..7af288f984 --- /dev/null +++ b/lib/feed/providers/event_image_provider.dart @@ -0,0 +1,31 @@ +import 'dart:typed_data'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/auth/providers/openid_provider.dart'; +import 'package:titan/feed/repositories/event_image_repository.dart'; +import 'package:titan/tools/providers/single_notifier.dart'; + +class EventImageNotifier extends SingleNotifier { + final eventImageRepository = EventImageRepository(); + EventImageNotifier({required String token}) + : super(const AsyncValue.loading()) { + eventImageRepository.setToken(token); + } + + Future addEventImage(String id, Uint8List bytes) async { + final image = await eventImageRepository.addEventImage(bytes, id); + if (image.toString() != "") { + state = AsyncData(image); + return true; + } + return false; + } +} + +final eventImageProvider = + StateNotifierProvider>((ref) { + final token = ref.watch(tokenProvider); + return EventImageNotifier(token: token); + }); diff --git a/lib/feed/providers/event_provider.dart b/lib/feed/providers/event_provider.dart new file mode 100644 index 0000000000..bf9dcc48bb --- /dev/null +++ b/lib/feed/providers/event_provider.dart @@ -0,0 +1,31 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/auth/providers/openid_provider.dart'; +import 'package:titan/feed/class/event.dart'; +import 'package:titan/feed/repositories/event_repository.dart'; +import 'package:titan/tools/providers/single_notifier.dart'; + +class EventNotifier extends SingleNotifier { + final EventRepository eventRepository; + AsyncValue> allEvent = const AsyncValue.loading(); + EventNotifier({required this.eventRepository}) + : super(const AsyncValue.loading()); + + Future addEvent(Event event) async { + return eventRepository.createEvent(event); + } + + void fakeLoad() { + state = AsyncValue.data(Event.empty()); + } +} + +final eventProvider = StateNotifierProvider>(( + ref, +) { + final token = ref.watch(tokenProvider); + final eventRepository = EventRepository()..setToken(token); + EventNotifier eventListNotifier = EventNotifier( + eventRepository: eventRepository, + )..fakeLoad(); + return eventListNotifier; +}); diff --git a/lib/feed/providers/event_ticket_url_provider.dart b/lib/feed/providers/event_ticket_url_provider.dart index caa13955ee..1603e5e6a4 100644 --- a/lib/feed/providers/event_ticket_url_provider.dart +++ b/lib/feed/providers/event_ticket_url_provider.dart @@ -1,10 +1,10 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/feed/class/ticket_url.dart'; -import 'package:titan/feed/repositories/event_creation_repository.dart'; +import 'package:titan/feed/repositories/event_repository.dart'; import 'package:titan/tools/providers/single_notifier.dart'; class TicketUrlNotifier extends SingleNotifier { - final EventCreationRepository eventRepository; + final EventRepository eventRepository; TicketUrlNotifier({required this.eventRepository}) : super(const AsyncValue.loading()); @@ -15,7 +15,7 @@ class TicketUrlNotifier extends SingleNotifier { final ticketUrlProvider = StateNotifierProvider>((ref) { - final eventRepository = ref.watch(eventCreationRepositoryProvider); + final eventRepository = ref.watch(eventRepositoryProvider); TicketUrlNotifier notifier = TicketUrlNotifier( eventRepository: eventRepository, ); diff --git a/lib/feed/providers/news_image_provider.dart b/lib/feed/providers/news_image_provider.dart index 92b4e1b877..b98eb2d6b2 100644 --- a/lib/feed/providers/news_image_provider.dart +++ b/lib/feed/providers/news_image_provider.dart @@ -21,13 +21,6 @@ class NewsImageNotifier extends SingleNotifier { newsImagesNotifier.setTData(id, AsyncData([image])); return image; } - - Future updateNewsImage(String id, Uint8List bytes) async { - newsImagesNotifier.setTData(id, const AsyncLoading()); - final image = await newsImageRepository.addNewsImage(bytes, id); - newsImagesNotifier.setTData(id, AsyncData([image])); - return image; - } } final newsImageProvider = diff --git a/lib/feed/repositories/event_image_repository.dart b/lib/feed/repositories/event_image_repository.dart new file mode 100644 index 0000000000..3ec377277f --- /dev/null +++ b/lib/feed/repositories/event_image_repository.dart @@ -0,0 +1,22 @@ +import 'dart:typed_data'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/auth/providers/openid_provider.dart'; +import 'package:titan/tools/repository/logo_repository.dart'; + +class EventImageRepository extends LogoRepository { + @override + // ignore: overridden_fields + final ext = 'calendar/events/'; + + Future addEventImage(Uint8List bytes, String id) async { + final uint8List = await addLogo(bytes, id, suffix: "/image"); + return Image.memory(uint8List); + } +} + +final eventImageRepositoryProvider = Provider((ref) { + final token = ref.watch(tokenProvider); + return EventImageRepository()..setToken(token); +}); diff --git a/lib/feed/repositories/event_creation_repository.dart b/lib/feed/repositories/event_repository.dart similarity index 51% rename from lib/feed/repositories/event_creation_repository.dart rename to lib/feed/repositories/event_repository.dart index 1031fcc73a..1e1cb398e2 100644 --- a/lib/feed/repositories/event_creation_repository.dart +++ b/lib/feed/repositories/event_repository.dart @@ -1,16 +1,22 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/auth/providers/openid_provider.dart'; -import 'package:titan/feed/class/event_creation.dart'; +import 'package:titan/feed/class/event.dart'; import 'package:titan/feed/class/ticket_url.dart'; import 'package:titan/tools/repository/repository.dart'; -class EventCreationRepository extends Repository { +class EventRepository extends Repository { @override // ignore: overridden_fields final ext = "calendar/events/"; - Future createEvent(EventCreation event) async { - return EventCreation.fromJson(await create(event.toJson())); + Future createEvent(Event event) async { + return Event.fromJson(await create(event.toJson())); + } + + Future> getEventList() async { + return List.from( + (await getList(suffix: "")).map((e) => Event.fromJson(e)), + ); } Future getTicketUrl(String id) async { @@ -18,9 +24,7 @@ class EventCreationRepository extends Repository { } } -final eventCreationRepositoryProvider = Provider(( - ref, -) { +final eventRepositoryProvider = Provider((ref) { final token = ref.watch(tokenProvider); - return EventCreationRepository()..setToken(token); + return EventRepository()..setToken(token); }); diff --git a/lib/feed/ui/pages/add_event_page/add_event_page.dart b/lib/feed/ui/pages/add_event_page/add_event_page.dart index 1ad6f08102..a8a612cd05 100644 --- a/lib/feed/ui/pages/add_event_page/add_event_page.dart +++ b/lib/feed/ui/pages/add_event_page/add_event_page.dart @@ -13,9 +13,10 @@ import 'package:titan/admin/providers/my_association_list_provider.dart'; // import 'package:titan/event/tools/constants.dart'; // import 'package:titan/event/tools/functions.dart'; import 'package:titan/event/ui/pages/event_pages/checkbox_entry.dart'; -import 'package:titan/feed/class/event_creation.dart'; +import 'package:titan/feed/class/event.dart'; import 'package:titan/feed/providers/admin_news_list_provider.dart'; -import 'package:titan/feed/providers/event_creation_provider.dart'; +import 'package:titan/feed/providers/event_image_provider.dart'; +import 'package:titan/feed/providers/event_provider.dart'; import 'package:titan/feed/providers/news_image_provider.dart'; import 'package:titan/feed/providers/news_list_provider.dart'; import 'package:titan/feed/ui/feed.dart'; @@ -48,7 +49,9 @@ class AddEventPage extends HookConsumerWidget { // final recurrentController = useState(false); // final recurrenceEndDateController = useTextEditingController(); - final eventCreationNotifier = ref.watch(eventCreationProvider.notifier); + final eventCreationNotifier = ref.watch(eventProvider.notifier); + final event = ref.watch(eventProvider); + final eventImageNotifier = ref.watch(eventImageProvider.notifier); final adminNewsListNotifier = ref.watch(adminNewsListProvider.notifier); final newsListNotifier = ref.watch(newsListProvider.notifier); // final interval = useTextEditingController(); @@ -58,7 +61,6 @@ class AddEventPage extends HookConsumerWidget { // final now = DateTime.now(); final selectedAssociation = useState(null); - final imageNotifier = ref.watch(newsImageProvider.notifier); final poster = useState(null); final posterFile = useState(null); @@ -457,7 +459,8 @@ class AddEventPage extends HookConsumerWidget { // ), // ); // } - final newEvent = EventCreation( + final newEvent = Event( + id: "", start: DateTime.parse( processDateBack(startDateController.text), ), @@ -475,8 +478,13 @@ class AddEventPage extends HookConsumerWidget { associationId: selectedAssociation.value!.id, ticketUrl: externalLinkController.text, ); - final value = await eventCreationNotifier + final eventCreated = await eventCreationNotifier .addEvent(newEvent); + final value = await eventImageNotifier + .addEventImage( + eventCreated.id, + poster.value!, + ); if (value) { Navigator.of(context).pop(); displayToastWithContext( @@ -484,20 +492,6 @@ class AddEventPage extends HookConsumerWidget { addedEventMsg, ); newsListNotifier.loadNewsList(); - adminNewsListNotifier.loadNewsList().then(( - news, - ) { - news.maybeWhen( - data: (list) { - final newNews = list.last; - imageNotifier.updateNewsImage( - newNews.id, - poster.value!, - ); - }, - orElse: () {}, - ); - }); } else { displayToastWithContext( TypeMsg.error, From d246a7fa84ddde0cd7e10331a7db62fc898f2a83 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 20 Aug 2025 19:00:34 +0200 Subject: [PATCH 307/473] remove useless import --- lib/feed/providers/news_image_provider.dart | 2 -- lib/feed/ui/pages/add_event_page/add_event_page.dart | 4 ---- 2 files changed, 6 deletions(-) diff --git a/lib/feed/providers/news_image_provider.dart b/lib/feed/providers/news_image_provider.dart index b98eb2d6b2..d3152546b4 100644 --- a/lib/feed/providers/news_image_provider.dart +++ b/lib/feed/providers/news_image_provider.dart @@ -1,5 +1,3 @@ -import 'dart:typed_data'; - import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; diff --git a/lib/feed/ui/pages/add_event_page/add_event_page.dart b/lib/feed/ui/pages/add_event_page/add_event_page.dart index a8a612cd05..679d0c1297 100644 --- a/lib/feed/ui/pages/add_event_page/add_event_page.dart +++ b/lib/feed/ui/pages/add_event_page/add_event_page.dart @@ -14,10 +14,8 @@ import 'package:titan/admin/providers/my_association_list_provider.dart'; // import 'package:titan/event/tools/functions.dart'; import 'package:titan/event/ui/pages/event_pages/checkbox_entry.dart'; import 'package:titan/feed/class/event.dart'; -import 'package:titan/feed/providers/admin_news_list_provider.dart'; import 'package:titan/feed/providers/event_image_provider.dart'; import 'package:titan/feed/providers/event_provider.dart'; -import 'package:titan/feed/providers/news_image_provider.dart'; import 'package:titan/feed/providers/news_list_provider.dart'; import 'package:titan/feed/ui/feed.dart'; import 'package:titan/l10n/app_localizations.dart'; @@ -50,9 +48,7 @@ class AddEventPage extends HookConsumerWidget { // final recurrenceEndDateController = useTextEditingController(); final eventCreationNotifier = ref.watch(eventProvider.notifier); - final event = ref.watch(eventProvider); final eventImageNotifier = ref.watch(eventImageProvider.notifier); - final adminNewsListNotifier = ref.watch(adminNewsListProvider.notifier); final newsListNotifier = ref.watch(newsListProvider.notifier); // final interval = useTextEditingController(); // final recurrenceEndDate = useTextEditingController(); From 831493f721b12d0b2fcd7e633347a467e290c7b8 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 20 Aug 2025 19:06:07 +0200 Subject: [PATCH 308/473] sg date can be empty --- .../pages/add_event_page/add_event_page.dart | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/lib/feed/ui/pages/add_event_page/add_event_page.dart b/lib/feed/ui/pages/add_event_page/add_event_page.dart index 679d0c1297..8d55b52003 100644 --- a/lib/feed/ui/pages/add_event_page/add_event_page.dart +++ b/lib/feed/ui/pages/add_event_page/add_event_page.dart @@ -270,7 +270,7 @@ class AddEventPage extends HookConsumerWidget { onTap: () => getFullDate(context, shotgunDateController), controller: shotgunDateController, label: "Date du SG ", - canBeEmpty: false, + canBeEmpty: true, ), SizedBox(height: 10), TextEntry( @@ -464,9 +464,14 @@ class AddEventPage extends HookConsumerWidget { processDateBack(endDateController.text), ), location: locationController.text, - ticketUrlOpening: DateTime.parse( - processDateBack(shotgunDateController.text), - ), + ticketUrlOpening: + shotgunDateController.text != "" + ? DateTime.parse( + processDateBack( + shotgunDateController.text, + ), + ) + : null, name: titleController.text, allDay: allDay.value, // recurrenceRule: recurrenceRule, @@ -476,6 +481,16 @@ class AddEventPage extends HookConsumerWidget { ); final eventCreated = await eventCreationNotifier .addEvent(newEvent); + if (poster.value == null || + posterFile.value == null) { + Navigator.of(context).pop(); + displayToastWithContext( + TypeMsg.msg, + addedEventMsg, + ); + newsListNotifier.loadNewsList(); + return; + } final value = await eventImageNotifier .addEventImage( eventCreated.id, From a8dd07bc12b5558d4fc20b78d190fdeaa070dc5a Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 20 Aug 2025 20:23:17 +0200 Subject: [PATCH 309/473] adapt advert with association --- .../my_association_list_provider.dart | 10 +- lib/advert/class/advert.dart | 17 +- lib/advert/class/announcer.dart | 43 ---- .../providers/advert_list_provider.dart | 4 +- .../all_announcer_list_provider.dart | 11 - .../providers/announcer_list_provider.dart | 73 ------ lib/advert/providers/announcer_provider.dart | 24 -- .../providers/is_advert_admin_provider.dart | 7 - .../selected_association_provider.dart | 24 ++ .../user_association_list_provider.dart | 28 +++ .../repositories/advert_repository.dart | 5 +- .../repositories/announcer_repository.dart | 40 ---- lib/advert/router.dart | 5 +- ...nnouncer_bar.dart => association_bar.dart} | 42 ++-- ...ouncer_item.dart => association_item.dart} | 4 +- .../pages/admin_page/admin_advert_card.dart | 11 +- .../ui/pages/admin_page/admin_page.dart | 211 +++++++++--------- lib/advert/ui/pages/advert.dart | 8 +- .../pages/form_page/add_edit_advert_page.dart | 33 ++- .../ui/pages/main_page/advert_card.dart | 23 +- lib/advert/ui/pages/main_page/main_page.dart | 20 +- .../is_user_a_member_of_an_association.dart | 7 +- .../pages/add_event_page/add_event_page.dart | 37 ++- lib/feed/ui/pages/main_page/main_page.dart | 4 +- 24 files changed, 273 insertions(+), 418 deletions(-) delete mode 100644 lib/advert/class/announcer.dart delete mode 100644 lib/advert/providers/all_announcer_list_provider.dart delete mode 100644 lib/advert/providers/announcer_list_provider.dart delete mode 100644 lib/advert/providers/announcer_provider.dart delete mode 100644 lib/advert/providers/is_advert_admin_provider.dart create mode 100644 lib/advert/providers/selected_association_provider.dart create mode 100644 lib/advert/providers/user_association_list_provider.dart delete mode 100644 lib/advert/repositories/announcer_repository.dart rename lib/advert/ui/components/{announcer_bar.dart => association_bar.dart} (51%) rename lib/advert/ui/components/{announcer_item.dart => association_item.dart} (96%) diff --git a/lib/admin/providers/my_association_list_provider.dart b/lib/admin/providers/my_association_list_provider.dart index 5535175ebe..1c269ff4d3 100644 --- a/lib/admin/providers/my_association_list_provider.dart +++ b/lib/admin/providers/my_association_list_provider.dart @@ -14,7 +14,7 @@ class MyAssociationListNotifier extends ListNotifier { } } -final myAssociationListProvider = +final asyncMyAssociationListProvider = StateNotifierProvider< MyAssociationListNotifier, AsyncValue> @@ -28,3 +28,11 @@ final myAssociationListProvider = }); return provider; }); + +final myAssociationListProvider = Provider>((ref) { + final asyncMyAssociationList = ref.watch(asyncMyAssociationListProvider); + return asyncMyAssociationList.maybeWhen( + data: (associations) => associations, + orElse: () => [], + ); +}); diff --git a/lib/advert/class/advert.dart b/lib/advert/class/advert.dart index 1ce090ffc4..1e9bd8d0fc 100644 --- a/lib/advert/class/advert.dart +++ b/lib/advert/class/advert.dart @@ -1,4 +1,3 @@ -import 'package:titan/advert/class/announcer.dart'; import 'package:titan/tools/functions.dart'; class Advert { @@ -6,14 +5,14 @@ class Advert { late final String title; late final String content; late final DateTime date; - late final Announcer announcer; + late final String associationId; Advert({ required this.id, required this.title, required this.content, required this.date, - required this.announcer, + required this.associationId, }); Advert.fromJson(Map json) { @@ -21,7 +20,7 @@ class Advert { title = json["title"]; content = json["content"]; date = processDateFromAPI(json["date"]); - announcer = Announcer.fromJson(json["advertiser"]); + associationId = json["advertiser_id"]; } Map toJson() { @@ -30,7 +29,7 @@ class Advert { data["title"] = title; data["content"] = content; data["date"] = processDateToAPI(date); - data["advertiser_id"] = announcer.id; + data["advertiser_id"] = associationId; return data; } @@ -39,14 +38,14 @@ class Advert { String? title, String? content, DateTime? date, - Announcer? announcer, + String? associationId, }) { return Advert( id: id ?? this.id, title: title ?? this.title, content: content ?? this.content, date: date ?? this.date, - announcer: announcer ?? this.announcer, + associationId: associationId ?? this.associationId, ); } @@ -56,12 +55,12 @@ class Advert { title: "", content: "", date: DateTime.now(), - announcer: Announcer.empty(), + associationId: "", ); } @override String toString() { - return 'Advert{id: $id, title: $title, content: $content, date: $date, announcer: $announcer}'; + return 'Advert{id: $id, title: $title, content: $content, date: $date, association_id: $associationId}'; } } diff --git a/lib/advert/class/announcer.dart b/lib/advert/class/announcer.dart deleted file mode 100644 index e76d7c7c5e..0000000000 --- a/lib/advert/class/announcer.dart +++ /dev/null @@ -1,43 +0,0 @@ -class Announcer { - Announcer({ - required this.name, - required this.groupManagerId, - required this.id, - }); - late final String name; - late final String groupManagerId; - late final String id; - - Announcer.fromJson(Map json) { - name = json['name']; - groupManagerId = json['group_manager_id']; - id = json['id']; - } - - Map toJson() { - final data = {}; - data['name'] = name; - data['group_manager_id'] = groupManagerId; - data['id'] = id; - return data; - } - - Announcer copyWith({String? name, String? groupManagerId, String? id}) { - return Announcer( - name: name ?? this.name, - groupManagerId: groupManagerId ?? this.groupManagerId, - id: id ?? this.id, - ); - } - - Announcer.empty() { - name = ""; - groupManagerId = ""; - id = ""; - } - - @override - String toString() { - return 'Announcer(name: $name, groupManagerId: $groupManagerId, id: $id)'; - } -} diff --git a/lib/advert/providers/advert_list_provider.dart b/lib/advert/providers/advert_list_provider.dart index 9480e5a228..7d6febeb63 100644 --- a/lib/advert/providers/advert_list_provider.dart +++ b/lib/advert/providers/advert_list_provider.dart @@ -13,7 +13,9 @@ class AdvertListNotifier extends ListNotifier { } Future>> loadAdverts() async { - return await loadList(repository.getAllAdvert); + final list = await loadList(repository.getAllAdvert); + print(list); + return list; } Future addAdvert(Advert advert) async { diff --git a/lib/advert/providers/all_announcer_list_provider.dart b/lib/advert/providers/all_announcer_list_provider.dart deleted file mode 100644 index 95d0fb63a6..0000000000 --- a/lib/advert/providers/all_announcer_list_provider.dart +++ /dev/null @@ -1,11 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/advert/class/announcer.dart'; -import 'package:titan/advert/providers/announcer_list_provider.dart'; - -final allAnnouncerList = Provider>((ref) { - final announcersProvider = ref.watch(announcerListProvider); - return announcersProvider.maybeWhen( - data: (announcers) => announcers, - orElse: () => [], - ); -}); diff --git a/lib/advert/providers/announcer_list_provider.dart b/lib/advert/providers/announcer_list_provider.dart deleted file mode 100644 index 52a223c9df..0000000000 --- a/lib/advert/providers/announcer_list_provider.dart +++ /dev/null @@ -1,73 +0,0 @@ -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/auth/providers/openid_provider.dart'; -import 'package:titan/advert/class/announcer.dart'; -import 'package:titan/advert/repositories/announcer_repository.dart'; -import 'package:titan/tools/providers/list_notifier.dart'; -import 'package:titan/tools/token_expire_wrapper.dart'; - -class AnnouncerListNotifier extends ListNotifier { - final AnnouncerRepository _announcerRepository = AnnouncerRepository(); - AnnouncerListNotifier({required String token}) - : super(const AsyncValue.loading()) { - _announcerRepository.setToken(token); - } - - Future>> loadAllAnnouncerList() async { - return await loadList(_announcerRepository.getAllAnnouncer); - } - - Future>> loadMyAnnouncerList() async { - return await loadList(_announcerRepository.getMyAnnouncer); - } - - Future addAnnouncer(Announcer announcer) async { - return await add(_announcerRepository.createAnnouncer, announcer); - } - - Future updateAnnouncer(Announcer announcer) async { - return await update( - _announcerRepository.updateAnnouncer, - (announcers, announcer) => - announcers - ..[announcers.indexWhere((i) => i.id == announcer.id)] = announcer, - announcer, - ); - } - - Future deleteAnnouncer(Announcer announcer) async { - return await delete( - _announcerRepository.deleteAnnouncer, - (adverts, advert) => adverts..removeWhere((i) => i.id == advert.id), - announcer.id, - announcer, - ); - } -} - -final announcerListProvider = - StateNotifierProvider>>(( - ref, - ) { - final token = ref.watch(tokenProvider); - AnnouncerListNotifier announcerListNotifier = AnnouncerListNotifier( - token: token, - ); - tokenExpireWrapperAuth(ref, () async { - await announcerListNotifier.loadAllAnnouncerList(); - }); - return announcerListNotifier; - }); - -final userAnnouncerListProvider = - StateNotifierProvider>>(( - ref, - ) { - final token = ref.watch(tokenProvider); - AnnouncerListNotifier announcerListNotifier = AnnouncerListNotifier( - token: token, - ); - tokenExpireWrapperAuth(ref, () async { - await announcerListNotifier.loadMyAnnouncerList(); - }); - return announcerListNotifier; - }); diff --git a/lib/advert/providers/announcer_provider.dart b/lib/advert/providers/announcer_provider.dart deleted file mode 100644 index df895c9ca5..0000000000 --- a/lib/advert/providers/announcer_provider.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/advert/class/announcer.dart'; - -final announcerProvider = - StateNotifierProvider>((ref) { - return AnnouncerNotifier(); - }); - -class AnnouncerNotifier extends StateNotifier> { - AnnouncerNotifier() : super([]); - - void addAnnouncer(Announcer i) { - state.add(i); - state = state.sublist(0); - } - - void removeAnnouncer(Announcer i) { - state = state.where((element) => element.id != i.id).toList(); - } - - void clearAnnouncer() { - state = []; - } -} diff --git a/lib/advert/providers/is_advert_admin_provider.dart b/lib/advert/providers/is_advert_admin_provider.dart deleted file mode 100644 index 3dec527d3a..0000000000 --- a/lib/advert/providers/is_advert_admin_provider.dart +++ /dev/null @@ -1,7 +0,0 @@ -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/advert/providers/announcer_list_provider.dart'; - -final isAdvertAdminProvider = StateProvider((ref) { - final me = ref.watch(userAnnouncerListProvider); - return me.maybeWhen(data: (data) => data.isNotEmpty, orElse: () => false); -}); diff --git a/lib/advert/providers/selected_association_provider.dart b/lib/advert/providers/selected_association_provider.dart new file mode 100644 index 0000000000..5a84bf07ad --- /dev/null +++ b/lib/advert/providers/selected_association_provider.dart @@ -0,0 +1,24 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/admin/class/assocation.dart'; + +final selectedAssociationProvider = + StateNotifierProvider>((ref) { + return AssociationNotifier(); + }); + +class AssociationNotifier extends StateNotifier> { + AssociationNotifier() : super([]); + + void addAssociation(Association i) { + state.add(i); + state = state.sublist(0); + } + + void removeAssociation(Association i) { + state = state.where((element) => element.id != i.id).toList(); + } + + void clearAssociation() { + state = []; + } +} diff --git a/lib/advert/providers/user_association_list_provider.dart b/lib/advert/providers/user_association_list_provider.dart new file mode 100644 index 0000000000..a078544d04 --- /dev/null +++ b/lib/advert/providers/user_association_list_provider.dart @@ -0,0 +1,28 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/auth/providers/openid_provider.dart'; +import 'package:titan/advert/class/advert.dart'; +import 'package:titan/advert/repositories/advert_repository.dart'; +import 'package:titan/tools/providers/list_notifier.dart'; +import 'package:titan/tools/token_expire_wrapper.dart'; + +class AdvertListNotifier extends ListNotifier { + AdvertRepository repository = AdvertRepository(); + AdvertListNotifier({required String token}) + : super(const AsyncValue.loading()) { + repository.setToken(token); + } + + Future>> loadUserAssmicationList() async { + return await loadList(repository.getAllAdvert); + } +} + +final advertListProvider = + StateNotifierProvider>>((ref) { + final token = ref.watch(tokenProvider); + AdvertListNotifier notifier = AdvertListNotifier(token: token); + tokenExpireWrapperAuth(ref, () async { + await notifier.loadUserAssmicationList(); + }); + return notifier; + }); diff --git a/lib/advert/repositories/advert_repository.dart b/lib/advert/repositories/advert_repository.dart index 26b452d9e9..64e1cf5a60 100644 --- a/lib/advert/repositories/advert_repository.dart +++ b/lib/advert/repositories/advert_repository.dart @@ -7,9 +7,12 @@ class AdvertRepository extends Repository { final ext = 'advert/'; Future> getAllAdvert() async { - return (await getList( + print("got herre"); + final list = (await getList( suffix: 'adverts', )).map((e) => Advert.fromJson(e)).toList(); + print(list); + return list; } Future> getAllAdminAdvert() async { diff --git a/lib/advert/repositories/announcer_repository.dart b/lib/advert/repositories/announcer_repository.dart deleted file mode 100644 index 40b611a357..0000000000 --- a/lib/advert/repositories/announcer_repository.dart +++ /dev/null @@ -1,40 +0,0 @@ -import 'package:titan/advert/class/announcer.dart'; -import 'package:titan/tools/repository/repository.dart'; - -class AnnouncerRepository extends Repository { - @override - // ignore: overridden_fields - final ext = "advert/"; - - Future> getAllAnnouncer() async { - return List.from( - (await getList(suffix: "advertisers")).map((x) => Announcer.fromJson(x)), - ); - } - - Future> getMyAnnouncer() async { - return List.from( - (await getList( - suffix: "me/advertisers", - )).map((x) => Announcer.fromJson(x)), - ); - } - - Future getAnnouncer(String id) async { - return Announcer.fromJson(await getOne("advertisers/$id")); - } - - Future createAnnouncer(Announcer announcer) async { - return Announcer.fromJson( - await create(announcer.toJson(), suffix: "advertisers"), - ); - } - - Future updateAnnouncer(Announcer announcer) async { - return await update(announcer.toJson(), "advertisers/${announcer.id}"); - } - - Future deleteAnnouncer(String announcerId) async { - return await delete("advertisers/$announcerId"); - } -} diff --git a/lib/advert/router.dart b/lib/advert/router.dart index 673582c391..48b95dde3b 100644 --- a/lib/advert/router.dart +++ b/lib/advert/router.dart @@ -1,12 +1,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/advert/providers/is_advert_admin_provider.dart'; + import 'package:titan/advert/ui/pages/admin_page/admin_page.dart' deferred as admin_page; import 'package:titan/advert/ui/pages/form_page/add_edit_advert_page.dart' deferred as add_edit_advert_page; import 'package:titan/advert/ui/pages/main_page/main_page.dart' deferred as main_page; +import 'package:titan/feed/providers/is_user_a_member_of_an_association.dart'; import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/navigation/class/module.dart'; import 'package:titan/tools/middlewares/admin_middleware.dart'; @@ -44,7 +45,7 @@ class AdvertRouter { path: admin, builder: () => admin_page.AdvertAdminPage(), middleware: [ - AdminMiddleware(ref, isAdvertAdminProvider), + AdminMiddleware(ref, isUserAMemberOfAnAssociationProvider), DeferredLoadingMiddleware(admin_page.loadLibrary), ], children: [ diff --git a/lib/advert/ui/components/announcer_bar.dart b/lib/advert/ui/components/association_bar.dart similarity index 51% rename from lib/advert/ui/components/announcer_bar.dart rename to lib/advert/ui/components/association_bar.dart index 0a09a6996e..478095941d 100644 --- a/lib/advert/ui/components/announcer_bar.dart +++ b/lib/advert/ui/components/association_bar.dart @@ -1,51 +1,51 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/advert/providers/announcer_provider.dart'; -import 'package:titan/advert/providers/announcer_list_provider.dart'; -import 'package:titan/advert/ui/components/announcer_item.dart'; +import 'package:titan/admin/providers/assocation_list_provider.dart'; +import 'package:titan/admin/providers/my_association_list_provider.dart'; +import 'package:titan/advert/providers/selected_association_provider.dart'; +import 'package:titan/advert/ui/components/association_item.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/horizontal_list_view.dart'; -class AnnouncerBar extends HookConsumerWidget { - final bool useUserAnnouncers; +class AssociationBar extends HookConsumerWidget { + final bool useUserAssociations; final bool multipleSelect; final bool isNotClickable; - const AnnouncerBar({ + const AssociationBar({ super.key, required this.multipleSelect, - required this.useUserAnnouncers, + required this.useUserAssociations, this.isNotClickable = false, }); @override Widget build(BuildContext context, WidgetRef ref) { - final selected = ref.watch(announcerProvider); + final selected = ref.watch(selectedAssociationProvider); final selectedId = selected.map((e) => e.id).toList(); - final selectedNotifier = ref.read(announcerProvider.notifier); - final announcerList = useUserAnnouncers - ? ref.watch(userAnnouncerListProvider) - : ref.watch(announcerListProvider); - + final selectedNotifier = ref.read(selectedAssociationProvider.notifier); + final associationList = useUserAssociations + ? ref.watch(asyncMyAssociationListProvider) + : ref.watch(associationListProvider); return AsyncChild( - value: announcerList, - builder: (context, userAnnouncers) => HorizontalListView.builder( + value: associationList, + builder: (context, userAssociations) => HorizontalListView.builder( height: 66, - items: userAnnouncers, + items: userAssociations, itemBuilder: (context, e, i) { final selected = selectedId.contains(e.id); - return AnnouncerItem( + return AssociationItem( onTap: () { if (isNotClickable) { return; } if (multipleSelect) { selected - ? selectedNotifier.removeAnnouncer(e) - : selectedNotifier.addAnnouncer(e); + ? selectedNotifier.removeAssociation(e) + : selectedNotifier.addAssociation(e); } else { - selectedNotifier.clearAnnouncer(); + selectedNotifier.clearAssociation(); if (!selected) { - selectedNotifier.addAnnouncer(e); + selectedNotifier.addAssociation(e); } } }, diff --git a/lib/advert/ui/components/announcer_item.dart b/lib/advert/ui/components/association_item.dart similarity index 96% rename from lib/advert/ui/components/announcer_item.dart rename to lib/advert/ui/components/association_item.dart index 9bfde9e86d..41fc6a0516 100644 --- a/lib/advert/ui/components/announcer_item.dart +++ b/lib/advert/ui/components/association_item.dart @@ -2,11 +2,11 @@ import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter/material.dart'; import 'package:titan/tools/constants.dart'; -class AnnouncerItem extends StatelessWidget { +class AssociationItem extends StatelessWidget { final String name, avatarName; final bool selected; final VoidCallback onTap; - const AnnouncerItem({ + const AssociationItem({ super.key, required this.name, required this.onTap, diff --git a/lib/advert/ui/pages/admin_page/admin_advert_card.dart b/lib/advert/ui/pages/admin_page/admin_advert_card.dart index 19b0820cd9..4f4560a659 100644 --- a/lib/advert/ui/pages/admin_page/admin_advert_card.dart +++ b/lib/advert/ui/pages/admin_page/admin_advert_card.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/admin/providers/my_association_list_provider.dart'; import 'package:titan/advert/class/advert.dart'; import 'package:timeago/timeago.dart' as timeago; -import 'package:titan/advert/providers/announcer_list_provider.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/styleguide/icon_button.dart'; @@ -21,11 +21,8 @@ class AdminAdvertCard extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final userAnnouncerList = ref.watch(userAnnouncerListProvider); - final userAnnouncersIdListSync = userAnnouncerList.maybeWhen( - orElse: () => [], - data: (data) => data.map((e) => e.id).toList(), - ); + final myAssociations = ref.watch(myAssociationListProvider); + final myAssociationIdList = myAssociations.map((e) => e.id).toList(); return Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: Container( @@ -58,7 +55,7 @@ class AdminAdvertCard extends HookConsumerWidget { ], ), const Spacer(), - if (userAnnouncersIdListSync.contains(advert.announcer.id)) + if (myAssociationIdList.contains(advert.associationId)) CustomIconButton.secondary( onPressed: onEdit, icon: const HeroIcon( diff --git a/lib/advert/ui/pages/admin_page/admin_page.dart b/lib/advert/ui/pages/admin_page/admin_page.dart index 6fde598098..31c746c1df 100644 --- a/lib/advert/ui/pages/admin_page/admin_page.dart +++ b/lib/advert/ui/pages/admin_page/admin_page.dart @@ -3,18 +3,18 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/providers/is_admin_provider.dart'; +import 'package:titan/admin/providers/my_association_list_provider.dart'; import 'package:titan/advert/class/advert.dart'; import 'package:titan/advert/providers/advert_list_provider.dart'; import 'package:titan/advert/providers/advert_posters_provider.dart'; import 'package:titan/advert/providers/advert_provider.dart'; -import 'package:titan/advert/providers/announcer_list_provider.dart'; -import 'package:titan/advert/providers/announcer_provider.dart'; -import 'package:titan/advert/providers/is_advert_admin_provider.dart'; +import 'package:titan/advert/providers/selected_association_provider.dart'; import 'package:titan/advert/ui/components/special_action_button.dart'; import 'package:titan/advert/ui/pages/admin_page/admin_advert_card.dart'; import 'package:titan/advert/ui/pages/advert.dart'; import 'package:titan/advert/router.dart'; -import 'package:titan/advert/ui/components/announcer_bar.dart'; +import 'package:titan/advert/ui/components/association_bar.dart'; +import 'package:titan/feed/providers/is_user_a_member_of_an_association.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; @@ -29,27 +29,26 @@ class AdvertAdminPage extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final advertNotifier = ref.watch(advertProvider.notifier); final isAdmin = ref.watch(isAdminProvider); - final isAdvertAdmin = ref.watch(isAdvertAdminProvider); final advertList = ref.watch(advertListProvider); - final userAnnouncerListNotifier = ref.watch( - userAnnouncerListProvider.notifier, - ); - final userAnnouncerList = ref.watch(userAnnouncerListProvider); final advertPostersNotifier = ref.watch(advertPostersProvider.notifier); - final selectedAnnouncers = ref.watch(announcerProvider); - final selectedAnnouncersNotifier = ref.read(announcerProvider.notifier); - final userAnnouncersSync = userAnnouncerList.maybeWhen( - orElse: () => [], - data: (data) => data, + final selectedAssociations = ref.watch(selectedAssociationProvider); + final selectedAssociationsNotifier = ref.read( + selectedAssociationProvider.notifier, + ); + final myAssociationList = ref.watch(myAssociationListProvider); + final myAssociationListNotifier = ref.watch( + asyncMyAssociationListProvider.notifier, ); + final isAdvertAdmin = ref.watch(isUserAMemberOfAnAssociationProvider); + return AdvertTemplate( child: Column( children: [ Row( children: [ Expanded( - child: AnnouncerBar( - useUserAnnouncers: !isAdmin, + child: AssociationBar( + useUserAssociations: !isAdmin, multipleSelect: true, ), ), @@ -64,10 +63,10 @@ class AdvertAdminPage extends HookConsumerWidget { SpecialActionButton( onTap: () { advertNotifier.setAdvert(Advert.empty()); - if (userAnnouncersSync.length == 1 && - selectedAnnouncers.isEmpty) { - selectedAnnouncersNotifier.addAnnouncer( - userAnnouncersSync[0], + if (myAssociationList.length == 1 && + selectedAssociations.isEmpty) { + selectedAssociationsNotifier.addAssociation( + myAssociationList[0], ); } QR.to( @@ -90,99 +89,93 @@ class AdvertAdminPage extends HookConsumerWidget { Expanded( child: AsyncChild( value: advertList, - builder: (context, advertData) => AsyncChild( - value: userAnnouncerList, - builder: (context, userAnnouncerData) { - final userAnnouncerAdvert = advertData.where( - (advert) => !isAdmin - ? userAnnouncerData.any( - (element) => advert.announcer.id == element.id, - ) - : true, - ); - final sortedUserAnnouncerAdverts = userAnnouncerAdvert - .toList() - .sortedBy((element) => element.date) - .reversed; - final filteredSortedUserAnnouncerAdverts = - sortedUserAnnouncerAdverts - .where( - (advert) => - selectedAnnouncers - .where((e) => advert.announcer.id == e.id) - .isNotEmpty || - selectedAnnouncers.isEmpty, - ) - .toList(); - return Refresher( - controller: ScrollController(), - onRefresh: () async { - if (isAdmin) { - await ref - .watch(advertListProvider.notifier) - .loadAdverts(); - } + builder: (context, advertData) { + final userAssociationAdvert = advertData.where( + (advert) => !isAdmin + ? myAssociationList.any( + (element) => advert.associationId == element.id, + ) + : true, + ); + final sortedUserAssociationAdverts = userAssociationAdvert + .toList() + .sortedBy((element) => element.date) + .reversed; + final filteredSortedUserAssociationAdverts = + sortedUserAssociationAdverts + .where( + (advert) => + selectedAssociations + .where((e) => advert.associationId == e.id) + .isNotEmpty || + selectedAssociations.isEmpty, + ) + .toList(); + return Refresher( + controller: ScrollController(), + onRefresh: () async { + if (isAdmin) { await ref .watch(advertListProvider.notifier) .loadAdverts(); - await userAnnouncerListNotifier.loadMyAnnouncerList(); - advertPostersNotifier.resetTData(); - }, - child: Column( - children: [ - ...filteredSortedUserAnnouncerAdverts.map( - (advert) => AdminAdvertCard( - onEdit: () { - QR.to( - AdvertRouter.root + - AdvertRouter.admin + - AdvertRouter.addEditAdvert, - ); - advertNotifier.setAdvert(advert); - selectedAnnouncersNotifier.clearAnnouncer(); - selectedAnnouncersNotifier.addAnnouncer( - advert.announcer, - ); - }, - onDelete: () async { - await showDialog( - context: context, - builder: (context) { - return CustomDialogBox( - title: AppLocalizations.of( - context, - )!.advertDeleting, - descriptions: AppLocalizations.of( - context, - )!.advertDeleteAdvert, - onYes: () async { - if (isAdmin) { - await ref - .watch(advertListProvider.notifier) - .deleteAdvert(advert); - } else { - await ref - .watch(advertListProvider.notifier) - .deleteAdvert(advert); - } - advertPostersNotifier.deleteE( - advert.id, - 0, - ); - }, - ); - }, - ); - }, - advert: advert, - ), + } + await ref.watch(advertListProvider.notifier).loadAdverts(); + await myAssociationListNotifier.loadAssociations(); + advertPostersNotifier.resetTData(); + }, + child: Column( + children: [ + ...filteredSortedUserAssociationAdverts.map( + (advert) => AdminAdvertCard( + onEdit: () { + QR.to( + AdvertRouter.root + + AdvertRouter.admin + + AdvertRouter.addEditAdvert, + ); + advertNotifier.setAdvert(advert); + selectedAssociationsNotifier.clearAssociation(); + selectedAssociationsNotifier.addAssociation( + myAssociationList.firstWhere( + (element) => element.id == advert.associationId, + ), + ); + }, + onDelete: () async { + await showDialog( + context: context, + builder: (context) { + return CustomDialogBox( + title: AppLocalizations.of( + context, + )!.advertDeleting, + descriptions: AppLocalizations.of( + context, + )!.advertDeleteAdvert, + onYes: () async { + if (isAdmin) { + await ref + .watch(advertListProvider.notifier) + .deleteAdvert(advert); + } else { + await ref + .watch(advertListProvider.notifier) + .deleteAdvert(advert); + } + advertPostersNotifier.deleteE(advert.id, 0); + }, + ); + }, + ); + }, + advert: advert, ), - SizedBox(height: 80), - ], - ), - ); - }, - ), + ), + SizedBox(height: 80), + ], + ), + ); + }, ), ), ], diff --git a/lib/advert/ui/pages/advert.dart b/lib/advert/ui/pages/advert.dart index f1712cbf33..966d6777d0 100644 --- a/lib/advert/ui/pages/advert.dart +++ b/lib/advert/ui/pages/advert.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/advert/providers/selected_association_provider.dart'; import 'package:titan/tools/constants.dart'; -import 'package:titan/advert/providers/announcer_provider.dart'; import 'package:titan/advert/router.dart'; import 'package:titan/tools/ui/widgets/top_bar.dart'; @@ -11,7 +11,9 @@ class AdvertTemplate extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final selectedAnnouncersNotifier = ref.read(announcerProvider.notifier); + final selectedAssociationsNotifier = ref.read( + selectedAssociationProvider.notifier, + ); return Scaffold( body: Container( color: ColorConstants.background, @@ -21,7 +23,7 @@ class AdvertTemplate extends HookConsumerWidget { TopBar( root: AdvertRouter.root, onBack: () { - selectedAnnouncersNotifier.clearAnnouncer(); + selectedAssociationsNotifier.clearAssociation(); }, ), Expanded(child: child), diff --git a/lib/advert/ui/pages/form_page/add_edit_advert_page.dart b/lib/advert/ui/pages/form_page/add_edit_advert_page.dart index 4168ecc7a0..781a53eaaa 100644 --- a/lib/advert/ui/pages/form_page/add_edit_advert_page.dart +++ b/lib/advert/ui/pages/form_page/add_edit_advert_page.dart @@ -6,16 +6,16 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:image_picker/image_picker.dart'; +import 'package:titan/admin/class/assocation.dart'; +import 'package:titan/admin/providers/my_association_list_provider.dart'; import 'package:titan/advert/class/advert.dart'; -import 'package:titan/advert/class/announcer.dart'; import 'package:titan/advert/providers/advert_list_provider.dart'; import 'package:titan/advert/providers/advert_poster_provider.dart'; import 'package:titan/advert/providers/advert_posters_provider.dart'; import 'package:titan/advert/providers/advert_provider.dart'; -import 'package:titan/advert/providers/announcer_list_provider.dart'; -import 'package:titan/advert/providers/announcer_provider.dart'; +import 'package:titan/advert/providers/selected_association_provider.dart'; import 'package:titan/advert/ui/pages/advert.dart'; -import 'package:titan/advert/ui/components/announcer_bar.dart'; +import 'package:titan/advert/ui/components/association_bar.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; @@ -35,8 +35,6 @@ class AdvertAddEditAdvertPage extends HookConsumerWidget { final isEdit = advert.id != Advert.empty().id; final title = useTextEditingController(text: advert.title); final content = useTextEditingController(text: advert.content); - final selectedAnnouncers = ref.watch(announcerProvider); - final userAnnouncerList = ref.watch(userAnnouncerListProvider); final advertPosters = ref.watch(advertPostersProvider); final advertListNotifier = ref.watch(advertListProvider.notifier); @@ -44,6 +42,10 @@ class AdvertAddEditAdvertPage extends HookConsumerWidget { final poster = useState(null); final posterFile = useState(null); + final userAssociations = ref.watch(myAssociationListProvider); + + final selectedAssociation = ref.watch(selectedAssociationProvider); + if (advertPosters[advert.id] != null) { advertPosters[advert.id]!.whenData((data) { if (data.isNotEmpty) { @@ -52,11 +54,6 @@ class AdvertAddEditAdvertPage extends HookConsumerWidget { }); } - final userAnnouncersSync = userAnnouncerList.maybeWhen( - orElse: () => [], - data: (data) => data, - ); - final ImagePicker picker = ImagePicker(); void displayAdvertToastWithContext(TypeMsg type, String msg) { @@ -70,10 +67,10 @@ class AdvertAddEditAdvertPage extends HookConsumerWidget { key: key, child: Column( children: [ - if (userAnnouncersSync.length > 1) - FormField>( + if (userAssociations.length > 1) + FormField>( validator: (e) { - if (selectedAnnouncers.isEmpty) { + if (selectedAssociation.isEmpty) { return AppLocalizations.of( context, )!.advertChoosingAnnouncer; @@ -95,8 +92,8 @@ class AdvertAddEditAdvertPage extends HookConsumerWidget { ] : [], ), - child: AnnouncerBar( - useUserAnnouncers: true, + child: AssociationBar( + useUserAssociations: true, multipleSelect: false, isNotClickable: isEdit, ), @@ -235,13 +232,13 @@ class AdvertAddEditAdvertPage extends HookConsumerWidget { return; } if (key.currentState!.validate() && - selectedAnnouncers.isNotEmpty && + selectedAssociation.isNotEmpty && (poster.value != null || isEdit)) { await tokenExpireWrapper(ref, () async { final advertList = ref.watch(advertListProvider); Advert newAdvert = Advert( id: isEdit ? advert.id : '', - announcer: selectedAnnouncers[0], + associationId: selectedAssociation[0].id, content: content.text, date: isEdit ? advert.date : DateTime.now(), title: title.text, diff --git a/lib/advert/ui/pages/main_page/advert_card.dart b/lib/advert/ui/pages/main_page/advert_card.dart index 78d19b8943..811fcc7fa4 100644 --- a/lib/advert/ui/pages/main_page/advert_card.dart +++ b/lib/advert/ui/pages/main_page/advert_card.dart @@ -1,7 +1,9 @@ +import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/admin/providers/assocation_list_provider.dart'; import 'package:titan/advert/class/advert.dart'; import 'package:titan/advert/providers/advert_poster_provider.dart'; import 'package:titan/advert/providers/advert_posters_provider.dart'; @@ -22,6 +24,17 @@ class AdvertCard extends HookConsumerWidget { ); final advertPostersNotifier = ref.watch(advertPostersProvider.notifier); final posterNotifier = ref.watch(advertPosterProvider.notifier); + final asyncAssociationList = ref.watch(associationListProvider); + final associationList = asyncAssociationList.when( + data: (data) => data, + loading: () => [], + error: (_, _) => [], + ); + final associationName = + associationList + .firstWhereOrNull((e) => e.id == advert.associationId) + ?.name ?? + ''; return Container( margin: const EdgeInsets.all(10), child: Column( @@ -40,13 +53,7 @@ class AdvertCard extends HookConsumerWidget { ), child: Center( child: Text( - advert.announcer.name.isNotEmpty - ? advert.announcer.name - .split(' ') - .take(2) - .map((s) => s[0].toUpperCase()) - .join() - : '?', + associationName, style: const TextStyle( color: Colors.white, fontSize: 18, @@ -62,7 +69,7 @@ class AdvertCard extends HookConsumerWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - advert.announcer.name, + associationName, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, diff --git a/lib/advert/ui/pages/main_page/main_page.dart b/lib/advert/ui/pages/main_page/main_page.dart index 1538feda5d..6901e97154 100644 --- a/lib/advert/ui/pages/main_page/main_page.dart +++ b/lib/advert/ui/pages/main_page/main_page.dart @@ -5,13 +5,13 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/providers/is_admin_provider.dart'; import 'package:titan/advert/providers/advert_list_provider.dart'; import 'package:titan/advert/providers/advert_posters_provider.dart'; -import 'package:titan/advert/providers/announcer_provider.dart'; -import 'package:titan/advert/providers/is_advert_admin_provider.dart'; +import 'package:titan/advert/providers/selected_association_provider.dart'; import 'package:titan/advert/ui/components/special_action_button.dart'; import 'package:titan/advert/ui/pages/advert.dart'; import 'package:titan/advert/router.dart'; -import 'package:titan/advert/ui/components/announcer_bar.dart'; +import 'package:titan/advert/ui/components/association_bar.dart'; import 'package:titan/advert/ui/pages/main_page/advert_card.dart'; +import 'package:titan/feed/providers/is_user_a_member_of_an_association.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; @@ -25,9 +25,9 @@ class AdvertMainPage extends HookConsumerWidget { final advertList = ref.watch(advertListProvider); final advertListNotifier = ref.watch(advertListProvider.notifier); final advertPostersNotifier = ref.watch(advertPostersProvider.notifier); - final selected = ref.watch(announcerProvider); - final selectedNotifier = ref.watch(announcerProvider.notifier); - final isAdvertAdmin = ref.watch(isAdvertAdminProvider); + final selected = ref.watch(selectedAssociationProvider); + final selectedNotifier = ref.watch(selectedAssociationProvider.notifier); + final isAdvertAdmin = ref.watch(isUserAMemberOfAnAssociationProvider); final isAdmin = ref.watch(isAdminProvider); return AdvertTemplate( child: Column( @@ -35,8 +35,8 @@ class AdvertMainPage extends HookConsumerWidget { Row( children: [ Expanded( - child: const AnnouncerBar( - useUserAnnouncers: false, + child: const AssociationBar( + useUserAssociations: false, multipleSelect: true, ), ), @@ -51,7 +51,7 @@ class AdvertMainPage extends HookConsumerWidget { SizedBox(width: 5), SpecialActionButton( onTap: () { - selectedNotifier.clearAnnouncer(); + selectedNotifier.clearAssociation(); QR.to(AdvertRouter.root + AdvertRouter.admin); }, icon: HeroIcon( @@ -77,7 +77,7 @@ class AdvertMainPage extends HookConsumerWidget { final filteredSortedAdvertData = sortedAdvertData.where( (advert) => selected - .where((e) => advert.announcer.name == e.name) + .where((e) => advert.associationId == e.id) .isNotEmpty || selected.isEmpty, ); diff --git a/lib/feed/providers/is_user_a_member_of_an_association.dart b/lib/feed/providers/is_user_a_member_of_an_association.dart index 9557a0814c..7c7a6f2121 100644 --- a/lib/feed/providers/is_user_a_member_of_an_association.dart +++ b/lib/feed/providers/is_user_a_member_of_an_association.dart @@ -1,10 +1,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/providers/my_association_list_provider.dart'; -final isUserAMemberOfAnAssociationProvider = FutureProvider((ref) async { +final isUserAMemberOfAnAssociationProvider = StateProvider((ref) { final myAssociation = ref.watch(myAssociationListProvider); - return myAssociation.maybeWhen( - data: (associations) => associations.isNotEmpty, - orElse: () => false, - ); + return myAssociation.isNotEmpty; }); diff --git a/lib/feed/ui/pages/add_event_page/add_event_page.dart b/lib/feed/ui/pages/add_event_page/add_event_page.dart index 8d55b52003..1d7244b626 100644 --- a/lib/feed/ui/pages/add_event_page/add_event_page.dart +++ b/lib/feed/ui/pages/add_event_page/add_event_page.dart @@ -82,27 +82,24 @@ class AddEventPage extends HookConsumerWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - AsyncChild( - value: myAssociations, - builder: (context, associations) => SizedBox( - height: 50, - child: HorizontalMultiSelect( - items: associations, - selectedItem: selectedAssociation.value, - onItemSelected: (association) { - selectedAssociation.value = association; - }, - itemBuilder: - (context, association, index, selected) => Text( - association.name, - style: TextStyle( - color: selected - ? ColorConstants.background - : ColorConstants.tertiary, - fontSize: 16, - ), + SizedBox( + height: 50, + child: HorizontalMultiSelect( + items: myAssociations, + selectedItem: selectedAssociation.value, + onItemSelected: (association) { + selectedAssociation.value = association; + }, + itemBuilder: (context, association, index, selected) => + Text( + association.name, + style: TextStyle( + color: selected + ? ColorConstants.background + : ColorConstants.tertiary, + fontSize: 16, ), - ), + ), ), ), const SizedBox(height: 10), diff --git a/lib/feed/ui/pages/main_page/main_page.dart b/lib/feed/ui/pages/main_page/main_page.dart index 9112283efd..b955ed3c0b 100644 --- a/lib/feed/ui/pages/main_page/main_page.dart +++ b/lib/feed/ui/pages/main_page/main_page.dart @@ -27,11 +27,9 @@ class FeedMainPage extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final news = ref.watch(newsListProvider); final newsNotifier = ref.watch(newsListProvider.notifier); - final asyncIsUserAMemberOfAnAssociation = ref.watch( + final isUserAMemberOfAnAssociation = ref.watch( isUserAMemberOfAnAssociationProvider, ); - final isUserAMemberOfAnAssociation = asyncIsUserAMemberOfAnAssociation - .maybeWhen(data: (isMember) => isMember, orElse: () => false); final isFeedAdmin = ref.watch(isFeedAdminProvider); final scrollController = useScrollController(); final navbarVisibilityNotifier = ref.watch( From 6e81d0bded9a5f384c6216df5fd96ddb28b7a254 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 20 Aug 2025 20:31:51 +0200 Subject: [PATCH 310/473] add bool postToFeed --- lib/advert/class/advert.dart | 6 ++++++ .../ui/pages/form_page/add_edit_advert_page.dart | 11 +++++++++++ lib/feed/ui/pages/add_event_page/add_event_page.dart | 1 - 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/advert/class/advert.dart b/lib/advert/class/advert.dart index 1e9bd8d0fc..0e522ca428 100644 --- a/lib/advert/class/advert.dart +++ b/lib/advert/class/advert.dart @@ -6,6 +6,7 @@ class Advert { late final String content; late final DateTime date; late final String associationId; + late final bool postToFeed; Advert({ required this.id, @@ -13,6 +14,7 @@ class Advert { required this.content, required this.date, required this.associationId, + required this.postToFeed, }); Advert.fromJson(Map json) { @@ -30,6 +32,7 @@ class Advert { data["content"] = content; data["date"] = processDateToAPI(date); data["advertiser_id"] = associationId; + data["post_to_feed"] = postToFeed; return data; } @@ -39,6 +42,7 @@ class Advert { String? content, DateTime? date, String? associationId, + bool? postToFeed, }) { return Advert( id: id ?? this.id, @@ -46,6 +50,7 @@ class Advert { content: content ?? this.content, date: date ?? this.date, associationId: associationId ?? this.associationId, + postToFeed: postToFeed ?? this.postToFeed, ); } @@ -56,6 +61,7 @@ class Advert { content: "", date: DateTime.now(), associationId: "", + postToFeed: false, ); } diff --git a/lib/advert/ui/pages/form_page/add_edit_advert_page.dart b/lib/advert/ui/pages/form_page/add_edit_advert_page.dart index 781a53eaaa..436ed43f0f 100644 --- a/lib/advert/ui/pages/form_page/add_edit_advert_page.dart +++ b/lib/advert/ui/pages/form_page/add_edit_advert_page.dart @@ -16,6 +16,7 @@ import 'package:titan/advert/providers/advert_provider.dart'; import 'package:titan/advert/providers/selected_association_provider.dart'; import 'package:titan/advert/ui/pages/advert.dart'; import 'package:titan/advert/ui/components/association_bar.dart'; +import 'package:titan/event/ui/pages/event_pages/checkbox_entry.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; @@ -54,6 +55,8 @@ class AdvertAddEditAdvertPage extends HookConsumerWidget { }); } + final postToFeed = useState(false); + final ImagePicker picker = ImagePicker(); void displayAdvertToastWithContext(TypeMsg type, String msg) { @@ -221,6 +224,13 @@ class AdvertAddEditAdvertPage extends HookConsumerWidget { ], ), ), + CheckBoxEntry( + title: AppLocalizations.of(context)!.eventAllDay, + valueNotifier: postToFeed, + onChanged: () { + postToFeed.value = !postToFeed.value; + }, + ), const SizedBox(height: 50), Padding( padding: const EdgeInsets.symmetric(horizontal: 30), @@ -242,6 +252,7 @@ class AdvertAddEditAdvertPage extends HookConsumerWidget { content: content.text, date: isEdit ? advert.date : DateTime.now(), title: title.text, + postToFeed: postToFeed.value, ); final editedAdvertMsg = AppLocalizations.of( context, diff --git a/lib/feed/ui/pages/add_event_page/add_event_page.dart b/lib/feed/ui/pages/add_event_page/add_event_page.dart index 1d7244b626..dffe0600e2 100644 --- a/lib/feed/ui/pages/add_event_page/add_event_page.dart +++ b/lib/feed/ui/pages/add_event_page/add_event_page.dart @@ -23,7 +23,6 @@ import 'package:titan/navigation/ui/scroll_to_hide_navbar.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; -import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/styleguide/button.dart'; import 'package:titan/tools/ui/styleguide/horizontal_multi_select.dart'; import 'package:titan/tools/ui/styleguide/text_entry.dart'; From 8a691150a2b3fbc3f59a5dbfcf6321ccb219e21f Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 20 Aug 2025 21:07:39 +0200 Subject: [PATCH 311/473] Add update list on clic --- .../ui/pages/event_handling_page/admin_event_card.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/feed/ui/pages/event_handling_page/admin_event_card.dart b/lib/feed/ui/pages/event_handling_page/admin_event_card.dart index d23757483e..bb119b8b5f 100644 --- a/lib/feed/ui/pages/event_handling_page/admin_event_card.dart +++ b/lib/feed/ui/pages/event_handling_page/admin_event_card.dart @@ -3,6 +3,7 @@ import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/feed/class/news.dart'; import 'package:titan/feed/providers/admin_news_list_provider.dart'; +import 'package:titan/feed/tools/function.dart'; import 'package:titan/feed/tools/news_helper.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; @@ -115,7 +116,8 @@ class AdminEventCard extends ConsumerWidget { children: [ WaitingButton( onTap: () async { - await newsAdminNotifier.rejectNews(news); + final newNews = news.copyWith(status: NewsStatus.rejected); + await newsAdminNotifier.rejectNews(newNews); }, builder: (child) => Container( padding: const EdgeInsets.symmetric( @@ -148,7 +150,8 @@ class AdminEventCard extends ConsumerWidget { SizedBox(width: 10), WaitingButton( onTap: () async { - await newsAdminNotifier.approveNews(news); + final newNews = news.copyWith(status: NewsStatus.published); + await newsAdminNotifier.approveNews(newNews); }, builder: (child) => Container( padding: const EdgeInsets.symmetric( From 79ec76f94eb068dced52ccf0717c28ccd0c0f559 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 20 Aug 2025 21:09:54 +0200 Subject: [PATCH 312/473] remove print --- lib/advert/providers/advert_list_provider.dart | 4 +--- lib/advert/repositories/advert_repository.dart | 5 +---- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/lib/advert/providers/advert_list_provider.dart b/lib/advert/providers/advert_list_provider.dart index 7d6febeb63..9480e5a228 100644 --- a/lib/advert/providers/advert_list_provider.dart +++ b/lib/advert/providers/advert_list_provider.dart @@ -13,9 +13,7 @@ class AdvertListNotifier extends ListNotifier { } Future>> loadAdverts() async { - final list = await loadList(repository.getAllAdvert); - print(list); - return list; + return await loadList(repository.getAllAdvert); } Future addAdvert(Advert advert) async { diff --git a/lib/advert/repositories/advert_repository.dart b/lib/advert/repositories/advert_repository.dart index 64e1cf5a60..26b452d9e9 100644 --- a/lib/advert/repositories/advert_repository.dart +++ b/lib/advert/repositories/advert_repository.dart @@ -7,12 +7,9 @@ class AdvertRepository extends Repository { final ext = 'advert/'; Future> getAllAdvert() async { - print("got herre"); - final list = (await getList( + return (await getList( suffix: 'adverts', )).map((e) => Advert.fromJson(e)).toList(); - print(list); - return list; } Future> getAllAdminAdvert() async { From 55bd42f6a851299e06fe81a5f8efa3698e010581 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 20 Aug 2025 21:47:13 +0200 Subject: [PATCH 313/473] Scroll to hide navbar --- .../event_handling_page/event_handling_page.dart | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/feed/ui/pages/event_handling_page/event_handling_page.dart b/lib/feed/ui/pages/event_handling_page/event_handling_page.dart index c5aac1f50f..08b1fd3a94 100644 --- a/lib/feed/ui/pages/event_handling_page/event_handling_page.dart +++ b/lib/feed/ui/pages/event_handling_page/event_handling_page.dart @@ -8,6 +8,7 @@ import 'package:titan/feed/tools/news_filter_type.dart'; import 'package:titan/feed/ui/feed.dart'; import 'package:titan/feed/ui/pages/event_handling_page/admin_event_card.dart'; import 'package:titan/l10n/app_localizations.dart'; +import 'package:titan/navigation/ui/scroll_to_hide_navbar.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; @@ -90,11 +91,14 @@ class EventHandlingPage extends HookConsumerWidget { ), ); } - return SingleChildScrollView( - child: Column( - children: filteredNews - .map((news) => AdminEventCard(news: news)) - .toList(), + return ScrollToHideNavbar( + controller: ScrollController(), + child: SingleChildScrollView( + child: Column( + children: filteredNews + .map((news) => AdminEventCard(news: news)) + .toList(), + ), ), ); }, From 90ae81ba0dbb70e4903d43ae1c7855e65136d0d8 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 20 Aug 2025 21:58:23 +0200 Subject: [PATCH 314/473] ui check box --- .../ui/pages/form_page/add_edit_advert_page.dart | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/advert/ui/pages/form_page/add_edit_advert_page.dart b/lib/advert/ui/pages/form_page/add_edit_advert_page.dart index 436ed43f0f..033632ddbb 100644 --- a/lib/advert/ui/pages/form_page/add_edit_advert_page.dart +++ b/lib/advert/ui/pages/form_page/add_edit_advert_page.dart @@ -224,12 +224,15 @@ class AdvertAddEditAdvertPage extends HookConsumerWidget { ], ), ), - CheckBoxEntry( - title: AppLocalizations.of(context)!.eventAllDay, - valueNotifier: postToFeed, - onChanged: () { - postToFeed.value = !postToFeed.value; - }, + Padding( + padding: const EdgeInsets.symmetric(horizontal: 30.0), + child: CheckBoxEntry( + title: "Poster dans le feed ?", + valueNotifier: postToFeed, + onChanged: () { + postToFeed.value = !postToFeed.value; + }, + ), ), const SizedBox(height: 50), Padding( From 99be51c0088157c024b279eda85715cda9d18bee Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Wed, 20 Aug 2025 22:25:37 +0200 Subject: [PATCH 315/473] feat: adding association logo --- lib/advert/ui/components/association_bar.dart | 63 ++++++++------- .../ui/components/association_item.dart | 78 +++++++++++++------ .../ui/pages/main_page/advert_card.dart | 55 +++++++++---- 3 files changed, 130 insertions(+), 66 deletions(-) diff --git a/lib/advert/ui/components/association_bar.dart b/lib/advert/ui/components/association_bar.dart index 478095941d..4c1aef6d7d 100644 --- a/lib/advert/ui/components/association_bar.dart +++ b/lib/advert/ui/components/association_bar.dart @@ -28,37 +28,40 @@ class AssociationBar extends HookConsumerWidget { : ref.watch(associationListProvider); return AsyncChild( value: associationList, - builder: (context, userAssociations) => HorizontalListView.builder( - height: 66, - items: userAssociations, - itemBuilder: (context, e, i) { - final selected = selectedId.contains(e.id); - return AssociationItem( - onTap: () { - if (isNotClickable) { - return; - } - if (multipleSelect) { - selected - ? selectedNotifier.removeAssociation(e) - : selectedNotifier.addAssociation(e); - } else { - selectedNotifier.clearAssociation(); - if (!selected) { - selectedNotifier.addAssociation(e); + builder: (context, userAssociations) { + return HorizontalListView.builder( + height: 66, + items: userAssociations, + itemBuilder: (context, e, i) { + final selected = selectedId.contains(e.id); + return AssociationItem( + onTap: () { + if (isNotClickable) { + return; } - } - }, - name: e.name, - avatarName: e.name - .split(' ') - .take(2) - .map((s) => s[0].toUpperCase()) - .join(), - selected: selected, - ); - }, - ), + if (multipleSelect) { + selected + ? selectedNotifier.removeAssociation(e) + : selectedNotifier.addAssociation(e); + } else { + selectedNotifier.clearAssociation(); + if (!selected) { + selectedNotifier.addAssociation(e); + } + } + }, + associationId: e.id, + name: e.name, + avatarName: e.name + .split(' ') + .take(2) + .map((s) => s[0].toUpperCase()) + .join(), + selected: selected, + ); + }, + ); + }, ); } } diff --git a/lib/advert/ui/components/association_item.dart b/lib/advert/ui/components/association_item.dart index 41fc6a0516..2bb6efa3ba 100644 --- a/lib/advert/ui/components/association_item.dart +++ b/lib/advert/ui/components/association_item.dart @@ -1,21 +1,34 @@ import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/admin/providers/association_logo_provider.dart'; +import 'package:titan/admin/providers/associations_logo_map_provider.dart'; import 'package:titan/tools/constants.dart'; +import 'package:titan/tools/ui/builders/auto_loader_child.dart'; -class AssociationItem extends StatelessWidget { - final String name, avatarName; +class AssociationItem extends ConsumerWidget { + final String name, avatarName, associationId; final bool selected; final VoidCallback onTap; const AssociationItem({ super.key, + required this.name, required this.onTap, required this.selected, required this.avatarName, + required this.associationId, }); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { + final associationLogo = ref.watch( + associationLogoMapProvider.select((value) => value[associationId]), + ); + final associationLogoMapNotifier = ref.watch( + associationLogoMapProvider.notifier, + ); + final associationLogoNotifier = ref.watch(associationLogoProvider.notifier); return GestureDetector( onTap: onTap, child: Padding( @@ -23,25 +36,46 @@ class AssociationItem extends StatelessWidget { child: Column( mainAxisSize: MainAxisSize.min, children: [ - Container( - width: 45, - height: 45, - decoration: BoxDecoration( - shape: BoxShape.circle, - border: selected - ? Border.all(color: ColorConstants.tertiary, width: 3) - : null, - color: Colors.grey.shade100, - ), - child: Center( - child: Text( - avatarName, - style: TextStyle( - fontSize: 15, - fontWeight: FontWeight.bold, - color: selected - ? ColorConstants.onTertiary - : ColorConstants.tertiary, + Center( + child: AutoLoaderChild( + group: associationLogo, + notifier: associationLogoMapNotifier, + mapKey: associationId, + loader: (associationId) => + associationLogoNotifier.getAssociationLogo(associationId), + dataBuilder: (context, data) => Container( + width: 44, + height: 44, + decoration: BoxDecoration( + shape: BoxShape.circle, + border: selected + ? Border.all(color: ColorConstants.tertiary, width: 3) + : null, + image: DecorationImage( + image: data.first.image, + fit: BoxFit.cover, + ), + ), + ), + orElseBuilder: (context, stack) => Container( + width: 44, + height: 44, + decoration: BoxDecoration( + shape: BoxShape.circle, + border: selected + ? Border.all(color: ColorConstants.tertiary, width: 3) + : null, + color: Colors.grey.shade100, + ), + child: Text( + avatarName, + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.bold, + color: selected + ? ColorConstants.onTertiary + : ColorConstants.tertiary, + ), ), ), ), diff --git a/lib/advert/ui/pages/main_page/advert_card.dart b/lib/advert/ui/pages/main_page/advert_card.dart index 811fcc7fa4..754d7813f0 100644 --- a/lib/advert/ui/pages/main_page/advert_card.dart +++ b/lib/advert/ui/pages/main_page/advert_card.dart @@ -4,6 +4,8 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/providers/assocation_list_provider.dart'; +import 'package:titan/admin/providers/association_logo_provider.dart'; +import 'package:titan/admin/providers/associations_logo_map_provider.dart'; import 'package:titan/advert/class/advert.dart'; import 'package:titan/advert/providers/advert_poster_provider.dart'; import 'package:titan/advert/providers/advert_posters_provider.dart'; @@ -35,6 +37,13 @@ class AdvertCard extends HookConsumerWidget { .firstWhereOrNull((e) => e.id == advert.associationId) ?.name ?? ''; + final associationLogo = ref.watch( + associationLogoMapProvider.select((value) => value[advert.associationId]), + ); + final associationLogoMapNotifier = ref.watch( + associationLogoMapProvider.notifier, + ); + final associationLogoNotifier = ref.watch(associationLogoProvider.notifier); return Container( margin: const EdgeInsets.all(10), child: Column( @@ -44,20 +53,38 @@ class AdvertCard extends HookConsumerWidget { padding: const EdgeInsets.symmetric(horizontal: 10.0), child: Row( children: [ - Container( - width: 40, - height: 40, - decoration: const BoxDecoration( - shape: BoxShape.circle, - color: Colors.black, - ), - child: Center( - child: Text( - associationName, - style: const TextStyle( - color: Colors.white, - fontSize: 18, - fontWeight: FontWeight.bold, + Center( + child: AutoLoaderChild( + group: associationLogo, + notifier: associationLogoMapNotifier, + mapKey: advert.associationId, + loader: (associationId) => associationLogoNotifier + .getAssociationLogo(associationId), + dataBuilder: (context, data) { + return CircleAvatar( + radius: 20, + backgroundColor: Colors.white, + backgroundImage: Image(image: data.first.image).image, + ); + }, + orElseBuilder: (context, stack) => Container( + width: 40, + height: 40, + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: Colors.black, + ), + child: Text( + associationName + .split(' ') + .take(2) + .map((s) => s[0].toUpperCase()) + .join(), + style: const TextStyle( + color: Colors.white, + fontSize: 18, + fontWeight: FontWeight.bold, + ), ), ), ), From 6a6d45ed96a2ce44c00b8b5f03e2bf9a576eb92e Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Wed, 20 Aug 2025 22:30:03 +0200 Subject: [PATCH 316/473] fix: checkbox --- lib/advert/ui/pages/form_page/add_edit_advert_page.dart | 3 ++- lib/event/ui/pages/event_pages/checkbox_entry.dart | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/advert/ui/pages/form_page/add_edit_advert_page.dart b/lib/advert/ui/pages/form_page/add_edit_advert_page.dart index 033632ddbb..b05299cd51 100644 --- a/lib/advert/ui/pages/form_page/add_edit_advert_page.dart +++ b/lib/advert/ui/pages/form_page/add_edit_advert_page.dart @@ -224,6 +224,7 @@ class AdvertAddEditAdvertPage extends HookConsumerWidget { ], ), ), + const SizedBox(height: 20), Padding( padding: const EdgeInsets.symmetric(horizontal: 30.0), child: CheckBoxEntry( @@ -234,7 +235,7 @@ class AdvertAddEditAdvertPage extends HookConsumerWidget { }, ), ), - const SizedBox(height: 50), + const SizedBox(height: 30), Padding( padding: const EdgeInsets.symmetric(horizontal: 30), child: Column( diff --git a/lib/event/ui/pages/event_pages/checkbox_entry.dart b/lib/event/ui/pages/event_pages/checkbox_entry.dart index 88d70206ca..5ede882684 100644 --- a/lib/event/ui/pages/event_pages/checkbox_entry.dart +++ b/lib/event/ui/pages/event_pages/checkbox_entry.dart @@ -30,7 +30,7 @@ class CheckBoxEntry extends StatelessWidget { activeColor: Colors.black, value: valueNotifier.value, onChanged: (value) { - valueNotifier.value = value!; + // valueNotifier.value = value!; onChanged(); }, ), From bf074ba50d11f6a48247d17f0d3d17422b401397 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Wed, 20 Aug 2025 22:30:13 +0200 Subject: [PATCH 317/473] fix: removing comment --- lib/event/ui/pages/event_pages/checkbox_entry.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/event/ui/pages/event_pages/checkbox_entry.dart b/lib/event/ui/pages/event_pages/checkbox_entry.dart index 5ede882684..118b51c6c1 100644 --- a/lib/event/ui/pages/event_pages/checkbox_entry.dart +++ b/lib/event/ui/pages/event_pages/checkbox_entry.dart @@ -30,7 +30,6 @@ class CheckBoxEntry extends StatelessWidget { activeColor: Colors.black, value: valueNotifier.value, onChanged: (value) { - // valueNotifier.value = value!; onChanged(); }, ), From 33f1f93da8edac771b9ee1a5169ba98ed864d37c Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Wed, 20 Aug 2025 23:33:24 +0200 Subject: [PATCH 318/473] fix: linter and comment --- .../ui/pages/main_page/advert_card.dart | 28 +++++++++---------- lib/feed/repositories/event_repository.dart | 4 +-- .../pages/add_event_page/add_event_page.dart | 3 +- 3 files changed, 16 insertions(+), 19 deletions(-) diff --git a/lib/advert/ui/pages/main_page/advert_card.dart b/lib/advert/ui/pages/main_page/advert_card.dart index 754d7813f0..5ba464b4f7 100644 --- a/lib/advert/ui/pages/main_page/advert_card.dart +++ b/lib/advert/ui/pages/main_page/advert_card.dart @@ -54,20 +54,20 @@ class AdvertCard extends HookConsumerWidget { child: Row( children: [ Center( - child: AutoLoaderChild( - group: associationLogo, - notifier: associationLogoMapNotifier, - mapKey: advert.associationId, - loader: (associationId) => associationLogoNotifier - .getAssociationLogo(associationId), - dataBuilder: (context, data) { - return CircleAvatar( - radius: 20, - backgroundColor: Colors.white, - backgroundImage: Image(image: data.first.image).image, - ); - }, - orElseBuilder: (context, stack) => Container( + child: AutoLoaderChild( + group: associationLogo, + notifier: associationLogoMapNotifier, + mapKey: advert.associationId, + loader: (associationId) => associationLogoNotifier + .getAssociationLogo(associationId), + dataBuilder: (context, data) { + return CircleAvatar( + radius: 20, + backgroundColor: Colors.white, + backgroundImage: Image(image: data.first.image).image, + ); + }, + orElseBuilder: (context, stack) => Container( width: 40, height: 40, decoration: const BoxDecoration( diff --git a/lib/feed/repositories/event_repository.dart b/lib/feed/repositories/event_repository.dart index 1e1cb398e2..45c3d7eafb 100644 --- a/lib/feed/repositories/event_repository.dart +++ b/lib/feed/repositories/event_repository.dart @@ -14,9 +14,7 @@ class EventRepository extends Repository { } Future> getEventList() async { - return List.from( - (await getList(suffix: "")).map((e) => Event.fromJson(e)), - ); + return List.from((await getList()).map((e) => Event.fromJson(e))); } Future getTicketUrl(String id) async { diff --git a/lib/feed/ui/pages/add_event_page/add_event_page.dart b/lib/feed/ui/pages/add_event_page/add_event_page.dart index dffe0600e2..af9bead4da 100644 --- a/lib/feed/ui/pages/add_event_page/add_event_page.dart +++ b/lib/feed/ui/pages/add_event_page/add_event_page.dart @@ -477,8 +477,7 @@ class AddEventPage extends HookConsumerWidget { ); final eventCreated = await eventCreationNotifier .addEvent(newEvent); - if (poster.value == null || - posterFile.value == null) { + if (poster.value == null) { Navigator.of(context).pop(); displayToastWithContext( TypeMsg.msg, From 179319fa57b45b56000e28172023638132678153 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Wed, 20 Aug 2025 23:37:51 +0200 Subject: [PATCH 319/473] feat: hiding button based on status --- .../event_handling_page/admin_event_card.dart | 121 +++++++++--------- 1 file changed, 64 insertions(+), 57 deletions(-) diff --git a/lib/feed/ui/pages/event_handling_page/admin_event_card.dart b/lib/feed/ui/pages/event_handling_page/admin_event_card.dart index bb119b8b5f..e877ab2344 100644 --- a/lib/feed/ui/pages/event_handling_page/admin_event_card.dart +++ b/lib/feed/ui/pages/event_handling_page/admin_event_card.dart @@ -114,73 +114,80 @@ class AdminEventCard extends ConsumerWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - WaitingButton( - onTap: () async { - final newNews = news.copyWith(status: NewsStatus.rejected); - await newsAdminNotifier.rejectNews(newNews); - }, - builder: (child) => Container( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 5, - ), - width: 100, - decoration: BoxDecoration( - color: ColorConstants.main, - borderRadius: BorderRadius.circular(20), - border: Border.all( - color: ColorConstants.onMain, - width: 2, + if (news.status != NewsStatus.rejected) + WaitingButton( + onTap: () async { + final newNews = news.copyWith( + status: NewsStatus.rejected, + ); + await newsAdminNotifier.rejectNews(newNews); + }, + builder: (child) => Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 5, + ), + width: 100, + decoration: BoxDecoration( + color: ColorConstants.main, + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: ColorConstants.onMain, + width: 2, + ), ), + child: child, ), - child: child, - ), - waitingColor: ColorConstants.background, - child: Center( - child: Text( - "Rejeter", - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.bold, - color: ColorConstants.background, + waitingColor: ColorConstants.background, + child: Center( + child: Text( + "Rejeter", + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: ColorConstants.background, + ), ), ), ), - ), - SizedBox(width: 10), - WaitingButton( - onTap: () async { - final newNews = news.copyWith(status: NewsStatus.published); - await newsAdminNotifier.approveNews(newNews); - }, - builder: (child) => Container( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 5, - ), - width: 100, - decoration: BoxDecoration( - color: ColorConstants.tertiary, - borderRadius: BorderRadius.circular(20), - border: Border.all( - color: ColorConstants.onTertiary, - width: 2, + if (news.status == NewsStatus.waitingApproval) + SizedBox(width: 10), + if (news.status != NewsStatus.published) + WaitingButton( + onTap: () async { + final newNews = news.copyWith( + status: NewsStatus.published, + ); + await newsAdminNotifier.approveNews(newNews); + }, + builder: (child) => Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 5, + ), + width: 100, + decoration: BoxDecoration( + color: ColorConstants.tertiary, + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: ColorConstants.onTertiary, + width: 2, + ), ), + child: child, ), - child: child, - ), - waitingColor: ColorConstants.background, - child: Center( - child: Text( - "Approuver", - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.bold, - color: ColorConstants.background, + waitingColor: ColorConstants.background, + child: Center( + child: Text( + "Approuver", + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: ColorConstants.background, + ), ), ), ), - ), ], ), ], From 3632141eb58c64c0a5113418a84075b4e597ca11 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Wed, 20 Aug 2025 23:41:31 +0200 Subject: [PATCH 320/473] fix: admin actions --- lib/feed/tools/news_helper.dart | 2 +- lib/feed/ui/pages/main_page/time_line_item.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/feed/tools/news_helper.dart b/lib/feed/tools/news_helper.dart index 4071947150..4f7bc8394e 100644 --- a/lib/feed/tools/news_helper.dart +++ b/lib/feed/tools/news_helper.dart @@ -228,7 +228,7 @@ void getActionButtonAction( }, loading: () {}, ); - } else if (module == "post") { + } else if (module == "advert") { // TODO : set id QR.to(AdvertRouter.root); } diff --git a/lib/feed/ui/pages/main_page/time_line_item.dart b/lib/feed/ui/pages/main_page/time_line_item.dart index 74832ae9f2..4f30b8a2bc 100644 --- a/lib/feed/ui/pages/main_page/time_line_item.dart +++ b/lib/feed/ui/pages/main_page/time_line_item.dart @@ -97,7 +97,7 @@ class TimelineItem extends ConsumerWidget { ), ), Expanded( - child: !isAdmin + child: isAdmin ? EventActionAdmin(item: item) : EventAction( title: getActionTitle(item, context), From 3af0522403b0594076be4177355440cc84735098 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+foucblg@users.noreply.github.com> Date: Thu, 21 Aug 2025 18:18:07 +0200 Subject: [PATCH 321/473] Various fixes (#46) * reduce spaces * bookmark sapce * feed fixes --------- Co-authored-by: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> --- .../event_handling_page/admin_event_card.dart | 4 ++ .../event_handling_page.dart | 1 + lib/feed/ui/pages/main_page/event_card.dart | 4 +- .../ui/pages/main_page/feed_timeline.dart | 1 - lib/feed/ui/pages/main_page/main_page.dart | 55 ++++++++++------- .../ui/pages/main_page/time_line_item.dart | 59 ++++++++----------- lib/navigation/ui/all_module_page.dart | 1 + lib/tools/ui/widgets/top_bar.dart | 5 +- 8 files changed, 66 insertions(+), 64 deletions(-) diff --git a/lib/feed/ui/pages/event_handling_page/admin_event_card.dart b/lib/feed/ui/pages/event_handling_page/admin_event_card.dart index e877ab2344..c88054e05c 100644 --- a/lib/feed/ui/pages/event_handling_page/admin_event_card.dart +++ b/lib/feed/ui/pages/event_handling_page/admin_event_card.dart @@ -3,6 +3,7 @@ import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/feed/class/news.dart'; import 'package:titan/feed/providers/admin_news_list_provider.dart'; +import 'package:titan/feed/providers/news_list_provider.dart'; import 'package:titan/feed/tools/function.dart'; import 'package:titan/feed/tools/news_helper.dart'; import 'package:titan/tools/constants.dart'; @@ -16,6 +17,7 @@ class AdminEventCard extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final locale = Localizations.localeOf(context).languageCode; final newsAdminNotifier = ref.watch(adminNewsListProvider.notifier); + final newsNotifier = ref.watch(newsListProvider.notifier); return Container( decoration: BoxDecoration( @@ -121,6 +123,7 @@ class AdminEventCard extends ConsumerWidget { status: NewsStatus.rejected, ); await newsAdminNotifier.rejectNews(newNews); + await newsNotifier.loadNewsList(); }, builder: (child) => Container( padding: const EdgeInsets.symmetric( @@ -159,6 +162,7 @@ class AdminEventCard extends ConsumerWidget { status: NewsStatus.published, ); await newsAdminNotifier.approveNews(newNews); + await newsNotifier.loadNewsList(); }, builder: (child) => Container( padding: const EdgeInsets.symmetric( diff --git a/lib/feed/ui/pages/event_handling_page/event_handling_page.dart b/lib/feed/ui/pages/event_handling_page/event_handling_page.dart index 08b1fd3a94..1e2aacf002 100644 --- a/lib/feed/ui/pages/event_handling_page/event_handling_page.dart +++ b/lib/feed/ui/pages/event_handling_page/event_handling_page.dart @@ -22,6 +22,7 @@ class EventHandlingPage extends HookConsumerWidget { final newsListAsync = ref.watch(adminNewsListProvider); final newsListNotifier = ref.watch(adminNewsListProvider.notifier); final selectedFilter = useState(NewsFilterType.pending); + newsListNotifier.loadNewsList(); return FeedTemplate( child: Padding( diff --git a/lib/feed/ui/pages/main_page/event_card.dart b/lib/feed/ui/pages/main_page/event_card.dart index 30a3fc01b2..d6c23fdd9b 100644 --- a/lib/feed/ui/pages/main_page/event_card.dart +++ b/lib/feed/ui/pages/main_page/event_card.dart @@ -87,7 +87,7 @@ class EventCard extends ConsumerWidget { ], ), ), - if (isNewsTerminated(item)) + if (isNewsTerminated(item) && item.module != "advert") Positioned( bottom: 53, left: 15, @@ -106,7 +106,7 @@ class EventCard extends ConsumerWidget { ), ), ), - if (isNewsOngoing(item)) + if (isNewsOngoing(item) && item.module != "advert") Positioned( bottom: 53, left: 15, diff --git a/lib/feed/ui/pages/main_page/feed_timeline.dart b/lib/feed/ui/pages/main_page/feed_timeline.dart index 8d4fedef3a..2db88064d5 100644 --- a/lib/feed/ui/pages/main_page/feed_timeline.dart +++ b/lib/feed/ui/pages/main_page/feed_timeline.dart @@ -30,7 +30,6 @@ class FeedTimeline extends StatelessWidget { ...items.map( (item) => TimelineItem( item: item, - isAdmin: isAdmin, onTap: onItemTap != null ? () => onItemTap!(item) : null, ), ), diff --git a/lib/feed/ui/pages/main_page/main_page.dart b/lib/feed/ui/pages/main_page/main_page.dart index b955ed3c0b..5c5cea49a7 100644 --- a/lib/feed/ui/pages/main_page/main_page.dart +++ b/lib/feed/ui/pages/main_page/main_page.dart @@ -18,7 +18,6 @@ import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; import 'package:titan/tools/ui/styleguide/button.dart'; import 'package:titan/tools/ui/styleguide/icon_button.dart'; -import 'package:titan/tools/ui/styleguide/searchbar.dart'; class FeedMainPage extends HookConsumerWidget { const FeedMainPage({super.key}); @@ -85,27 +84,8 @@ class FeedMainPage extends HookConsumerWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - CustomSearchBar( - onFilter: () async { - final syncNews = newsNotifier.allNews.maybeWhen( - orElse: () => [], - data: (loaded) => loaded, - ); - final entities = syncNews.map((e) => e.entity).toSet().toList(); - final modules = syncNews.map((e) => e.module).toSet().toList(); - await showCustomBottomModal( - modal: FilterNewsModal(entities: entities, modules: modules), - context: context, - ref: ref, - ); - }, - onSearch: (_) {}, - ), - - const SizedBox(height: 20), - + const SizedBox(height: 10), Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( "Actualité", @@ -115,6 +95,37 @@ class FeedMainPage extends HookConsumerWidget { color: ColorConstants.title, ), ), + Spacer(), + IconButton( + icon: HeroIcon( + HeroIcons.adjustmentsHorizontal, + color: ColorConstants.tertiary, + size: 20, + ), + onPressed: () async { + final syncNews = newsNotifier.allNews.maybeWhen( + orElse: () => [], + data: (loaded) => loaded, + ); + final entities = syncNews + .map((e) => e.entity) + .toSet() + .toList(); + final modules = syncNews + .map((e) => e.module) + .toSet() + .toList(); + await showCustomBottomModal( + modal: FilterNewsModal( + entities: entities, + modules: modules, + ), + context: context, + ref: ref, + ); + }, + splashRadius: 20, + ), if (isUserAMemberOfAnAssociation || isFeedAdmin) CustomIconButton( icon: HeroIcon( @@ -165,7 +176,7 @@ class FeedMainPage extends HookConsumerWidget { ], ), - const SizedBox(height: 20), + const SizedBox(height: 10), Expanded( child: ScrollToHideNavbar( diff --git a/lib/feed/ui/pages/main_page/time_line_item.dart b/lib/feed/ui/pages/main_page/time_line_item.dart index 4f30b8a2bc..db30b3641a 100644 --- a/lib/feed/ui/pages/main_page/time_line_item.dart +++ b/lib/feed/ui/pages/main_page/time_line_item.dart @@ -4,7 +4,6 @@ import 'package:intl/intl.dart'; import 'package:titan/feed/class/news.dart'; import 'package:titan/feed/tools/news_helper.dart'; import 'package:titan/feed/ui/pages/main_page/event_action.dart'; -import 'package:titan/feed/ui/pages/main_page/event_action_admin.dart'; import 'package:titan/feed/ui/pages/main_page/event_card.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/feed/ui/pages/main_page/dotted_vertical_line.dart'; @@ -12,19 +11,13 @@ import 'package:titan/feed/ui/pages/main_page/dotted_vertical_line.dart'; class TimelineItem extends ConsumerWidget { final News item; final VoidCallback? onTap; - final bool isAdmin; - const TimelineItem({ - super.key, - required this.item, - this.onTap, - required this.isAdmin, - }); + const TimelineItem({super.key, required this.item, this.onTap}); @override Widget build(BuildContext context, WidgetRef ref) { return SizedBox( - height: item.actionStart != null || isAdmin ? 200 : 160, + height: item.actionStart != null ? 200 : 160, child: Stack( children: [ Padding( @@ -72,17 +65,14 @@ class TimelineItem extends ConsumerWidget { ), ], ), - if (item.actionStart != null || isAdmin) + if (item.actionStart != null) Padding( padding: const EdgeInsets.only(top: 8), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( - padding: EdgeInsets.only( - left: 14, - right: isAdmin ? 33 : 45, - ), + padding: EdgeInsets.only(left: 14, right: 45), child: Container( width: 20, height: 20, @@ -97,28 +87,25 @@ class TimelineItem extends ConsumerWidget { ), ), Expanded( - child: isAdmin - ? EventActionAdmin(item: item) - : EventAction( - title: getActionTitle(item, context), - subtitle: getActionSubtitle(item, context), - onActionPressed: () => - getActionButtonAction(item, context, ref), - actionEnableButtonText: - getActionEnableButtonText(item, context), - actionValidatedButtonText: - getActionValidatedButtonText( - item, - context, - ), - isActionValidated: false, - isActionEnabled: - (item.actionStart ?? item.start).isBefore( - DateTime.now(), - ) && - item.end != null && - item.end!.isAfter(DateTime.now()), - ), + child: EventAction( + title: getActionTitle(item, context), + subtitle: getActionSubtitle(item, context), + onActionPressed: () => + getActionButtonAction(item, context, ref), + actionEnableButtonText: getActionEnableButtonText( + item, + context, + ), + actionValidatedButtonText: + getActionValidatedButtonText(item, context), + isActionValidated: false, + isActionEnabled: + (item.actionStart ?? item.start).isBefore( + DateTime.now(), + ) && + item.end != null && + item.end!.isAfter(DateTime.now()), + ), ), ], ), diff --git a/lib/navigation/ui/all_module_page.dart b/lib/navigation/ui/all_module_page.dart index a9f68957be..9d8d78b706 100644 --- a/lib/navigation/ui/all_module_page.dart +++ b/lib/navigation/ui/all_module_page.dart @@ -81,6 +81,7 @@ class AllModulePage extends HookConsumerWidget { : ColorConstants.secondary, ), ), + SizedBox(width: 10), Expanded( child: ListItem( title: module.getName(context), diff --git a/lib/tools/ui/widgets/top_bar.dart b/lib/tools/ui/widgets/top_bar.dart index 8c7bc8472f..91c913c381 100644 --- a/lib/tools/ui/widgets/top_bar.dart +++ b/lib/tools/ui/widgets/top_bar.dart @@ -21,12 +21,11 @@ class TopBar extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { return Column( children: [ - const SizedBox(height: 10), Row( children: [ SizedBox( width: 70, - height: 40, + height: 30, child: Builder( builder: (BuildContext appBarContext) { if (QR.currentPath == root) { @@ -49,7 +48,7 @@ class TopBar extends HookConsumerWidget { Expanded( child: Center( child: AutoSizeText( - "MyEMApp", + "myemapp", maxLines: 1, style: textStyle ?? From 55025c6f5cdcb2c341b36091d4ed0924e4865570 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Wed, 20 Aug 2025 23:25:51 +0200 Subject: [PATCH 322/473] feat: adding refresh button # Conflicts: # lib/feed/ui/pages/main_page/main_page.dart --- lib/feed/ui/pages/main_page/main_page.dart | 325 +++++++++++++++------ 1 file changed, 237 insertions(+), 88 deletions(-) diff --git a/lib/feed/ui/pages/main_page/main_page.dart b/lib/feed/ui/pages/main_page/main_page.dart index 5c5cea49a7..58a91187b5 100644 --- a/lib/feed/ui/pages/main_page/main_page.dart +++ b/lib/feed/ui/pages/main_page/main_page.dart @@ -11,7 +11,6 @@ import 'package:titan/feed/router.dart'; import 'package:titan/feed/ui/feed.dart'; import 'package:titan/feed/ui/pages/main_page/feed_timeline.dart'; import 'package:titan/feed/ui/pages/main_page/filter_news.dart'; -import 'package:titan/navigation/providers/navbar_visibility_provider.dart'; import 'package:titan/navigation/ui/scroll_to_hide_navbar.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; @@ -31,9 +30,68 @@ class FeedMainPage extends HookConsumerWidget { ); final isFeedAdmin = ref.watch(isFeedAdminProvider); final scrollController = useScrollController(); - final navbarVisibilityNotifier = ref.watch( - navbarVisibilityProvider.notifier, - ); + + final showRefreshButton = useState(false); + final lastScrollPosition = useState(0.0); + final hasScrolledEnough = useState(false); + final lastUserScrollTime = useState(DateTime.now()); + final consecutiveUpwardScrolls = useState(0); + + Future onRefresh() async { + showRefreshButton.value = false; + await newsNotifier.loadNewsList(); + } + + useEffect(() { + void scrollListener() { + if (!scrollController.hasClients) return; + + final position = scrollController.position; + final currentScrollPosition = position.pixels; + final scrollDirection = + currentScrollPosition - lastScrollPosition.value; + final now = DateTime.now(); + + if (scrollDirection.abs() < 3) return; + + final isAtTop = currentScrollPosition <= position.minScrollExtent; + final isAtBottom = currentScrollPosition >= position.maxScrollExtent; + final isInBounceZone = isAtTop || isAtBottom; + + if (currentScrollPosition > 200 && !hasScrolledEnough.value) { + hasScrolledEnough.value = true; + } + + if (scrollDirection < -15) { + final timeSinceLastScroll = now + .difference(lastUserScrollTime.value) + .inMilliseconds; + + if (!isInBounceZone && timeSinceLastScroll > 50) { + consecutiveUpwardScrolls.value++; + lastUserScrollTime.value = now; + + if (hasScrolledEnough.value && + consecutiveUpwardScrolls.value >= 2 && + !showRefreshButton.value) { + showRefreshButton.value = true; + } + } + } else if (scrollDirection > 5) { + consecutiveUpwardScrolls.value = 0; + lastUserScrollTime.value = now; + + if (showRefreshButton.value && currentScrollPosition > 50) { + showRefreshButton.value = false; + } + } + + lastScrollPosition.value = currentScrollPosition; + } + + scrollController.addListener(scrollListener); + return () => scrollController.removeListener(scrollListener); + }, []); useEffect(() { if (news.hasValue && news.value!.isNotEmpty) { @@ -71,7 +129,6 @@ class FeedMainPage extends HookConsumerWidget { } scrollController.jumpTo(scrollPosition); - navbarVisibilityNotifier.show(); } }); } @@ -79,23 +136,52 @@ class FeedMainPage extends HookConsumerWidget { }, [news]); return FeedTemplate( - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 10), - Row( + child: Stack( + children: [ + Container( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text( - "Actualité", - style: TextStyle( - fontSize: 24, - fontWeight: FontWeight.bold, - color: ColorConstants.title, - ), + CustomSearchBar( + onFilter: () async { + final syncNews = newsNotifier.allNews.maybeWhen( + orElse: () => [], + data: (loaded) => loaded, + ); + final entities = syncNews + .map((e) => e.entity) + .toSet() + .toList(); + final modules = syncNews + .map((e) => e.module) + .toSet() + .toList(); + await showCustomBottomModal( + modal: FilterNewsModal( + entities: entities, + modules: modules, + ), + context: context, + ref: ref, + ); + }, + onSearch: (_) {}, ), - Spacer(), + + const SizedBox(height: 20), + + Row( + children: [ + const Text( + "Actualité", + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: ColorConstants.title, + ), + ), + Spacer(), IconButton( icon: HeroIcon( HeroIcons.adjustmentsHorizontal, @@ -126,87 +212,150 @@ class FeedMainPage extends HookConsumerWidget { }, splashRadius: 20, ), - if (isUserAMemberOfAnAssociation || isFeedAdmin) - CustomIconButton( - icon: HeroIcon( - HeroIcons.userGroup, - color: ColorConstants.background, - ), - onPressed: () { - if (isFeedAdmin && !isUserAMemberOfAnAssociation) { - QR.to(FeedRouter.root + FeedRouter.eventHandling); - } else if (!isFeedAdmin && isUserAMemberOfAnAssociation) { - QR.to(FeedRouter.root + FeedRouter.addEvent); - } else { - showCustomBottomModal( - modal: BottomModalTemplate( - title: 'Administration', - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Button( - text: 'Créer un événement', - onPressed: () { - Navigator.of(context).pop(); - QR.to( - FeedRouter.root + FeedRouter.addEvent, - ); - }, + if (isUserAMemberOfAnAssociation || isFeedAdmin) + CustomIconButton( + icon: HeroIcon( + HeroIcons.userGroup, + color: ColorConstants.background, + ), + onPressed: () { + if (isFeedAdmin && !isUserAMemberOfAnAssociation) { + QR.to(FeedRouter.root + FeedRouter.eventHandling); + } else if (!isFeedAdmin && + isUserAMemberOfAnAssociation) { + QR.to(FeedRouter.root + FeedRouter.addEvent); + } else { + showCustomBottomModal( + modal: BottomModalTemplate( + title: 'Administration', + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Button( + text: 'Créer un événement', + onPressed: () { + Navigator.of(context).pop(); + QR.to( + FeedRouter.root + FeedRouter.addEvent, + ); + }, + ), + const SizedBox(height: 20), + Button( + text: 'Demandes de publication', + onPressed: () { + Navigator.of(context).pop(); + QR.to( + FeedRouter.root + + FeedRouter.eventHandling, + ); + }, + ), + ], ), - const SizedBox(height: 20), - Button( - text: 'Demandes de publication', - onPressed: () { - Navigator.of(context).pop(); - QR.to( - FeedRouter.root + - FeedRouter.eventHandling, - ); - }, + ), + context: context, + ref: ref, + ); + } + }, + ), + ], + ), + + const SizedBox(height: 20), + + Expanded( + child: ScrollToHideNavbar( + controller: scrollController, + child: SingleChildScrollView( + controller: scrollController, + physics: const BouncingScrollPhysics(), + child: AsyncChild( + value: news, + builder: (context, news) => news.isEmpty + ? const Center( + child: Text( + 'Aucune actualité disponible', + style: TextStyle( + fontSize: 16, + color: ColorConstants.tertiary, + ), ), - ], - ), - ), - context: context, - ref: ref, - ); - } - }, + ) + : FeedTimeline( + isAdmin: isFeedAdmin, + items: news, + onItemTap: (item) {}, + ), + ), + ), ), + ), ], ), + ), - const SizedBox(height: 10), - - Expanded( - child: ScrollToHideNavbar( - controller: scrollController, - child: SingleChildScrollView( - controller: scrollController, - physics: const BouncingScrollPhysics(), - child: AsyncChild( - value: news, - builder: (context, news) => news.isEmpty - ? const Center( - child: Text( - 'Aucune actualité disponible', + AnimatedPositioned( + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + top: showRefreshButton.value ? 75 : 40, + left: 0, + right: 0, + child: AnimatedOpacity( + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + opacity: showRefreshButton.value ? 1.0 : 0.0, + child: Center( + child: Container( + decoration: BoxDecoration( + color: ColorConstants.main, + borderRadius: BorderRadius.circular(25), + boxShadow: [ + BoxShadow( + color: ColorConstants.onMain.withValues(alpha: 0.4), + blurRadius: 8, + offset: const Offset(0, 3), + ), + ], + ), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: onRefresh, + borderRadius: BorderRadius.circular(25), + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + HeroIcon( + HeroIcons.arrowPath, + size: 16, + color: ColorConstants.background, + ), + const SizedBox(width: 8), + Text( + 'Actualiser', style: TextStyle( - fontSize: 16, - color: ColorConstants.tertiary, + color: ColorConstants.background, + fontSize: 14, + fontWeight: FontWeight.w600, ), ), - ) - : FeedTimeline( - isAdmin: isFeedAdmin, - items: news, - onItemTap: (item) {}, - ), + ], + ), + ), + ), ), ), ), ), - ], - ), + ), + ], ), ); } From 6ed025cc7c9eb2cc6dfb9865e51cbef18026f47e Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Thu, 21 Aug 2025 18:32:15 +0200 Subject: [PATCH 323/473] fix: laggy behavior --- .../ui/pages/main_page/feed_timeline.dart | 2 +- lib/feed/ui/pages/main_page/main_page.dart | 123 +------------ .../main_page/scroll_with_refresh_button.dart | 165 ++++++++++++++++++ 3 files changed, 169 insertions(+), 121 deletions(-) create mode 100644 lib/feed/ui/pages/main_page/scroll_with_refresh_button.dart diff --git a/lib/feed/ui/pages/main_page/feed_timeline.dart b/lib/feed/ui/pages/main_page/feed_timeline.dart index 2db88064d5..9cf52dd75d 100644 --- a/lib/feed/ui/pages/main_page/feed_timeline.dart +++ b/lib/feed/ui/pages/main_page/feed_timeline.dart @@ -33,7 +33,7 @@ class FeedTimeline extends StatelessWidget { onTap: onItemTap != null ? () => onItemTap!(item) : null, ), ), - SizedBox(height: 80), + SizedBox(height: 20), ], ); } diff --git a/lib/feed/ui/pages/main_page/main_page.dart b/lib/feed/ui/pages/main_page/main_page.dart index 58a91187b5..1ea1fdd970 100644 --- a/lib/feed/ui/pages/main_page/main_page.dart +++ b/lib/feed/ui/pages/main_page/main_page.dart @@ -11,7 +11,7 @@ import 'package:titan/feed/router.dart'; import 'package:titan/feed/ui/feed.dart'; import 'package:titan/feed/ui/pages/main_page/feed_timeline.dart'; import 'package:titan/feed/ui/pages/main_page/filter_news.dart'; -import 'package:titan/navigation/ui/scroll_to_hide_navbar.dart'; +import 'package:titan/feed/ui/pages/main_page/scroll_with_refresh_button.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; @@ -31,68 +31,10 @@ class FeedMainPage extends HookConsumerWidget { final isFeedAdmin = ref.watch(isFeedAdminProvider); final scrollController = useScrollController(); - final showRefreshButton = useState(false); - final lastScrollPosition = useState(0.0); - final hasScrolledEnough = useState(false); - final lastUserScrollTime = useState(DateTime.now()); - final consecutiveUpwardScrolls = useState(0); - Future onRefresh() async { - showRefreshButton.value = false; await newsNotifier.loadNewsList(); } - useEffect(() { - void scrollListener() { - if (!scrollController.hasClients) return; - - final position = scrollController.position; - final currentScrollPosition = position.pixels; - final scrollDirection = - currentScrollPosition - lastScrollPosition.value; - final now = DateTime.now(); - - if (scrollDirection.abs() < 3) return; - - final isAtTop = currentScrollPosition <= position.minScrollExtent; - final isAtBottom = currentScrollPosition >= position.maxScrollExtent; - final isInBounceZone = isAtTop || isAtBottom; - - if (currentScrollPosition > 200 && !hasScrolledEnough.value) { - hasScrolledEnough.value = true; - } - - if (scrollDirection < -15) { - final timeSinceLastScroll = now - .difference(lastUserScrollTime.value) - .inMilliseconds; - - if (!isInBounceZone && timeSinceLastScroll > 50) { - consecutiveUpwardScrolls.value++; - lastUserScrollTime.value = now; - - if (hasScrolledEnough.value && - consecutiveUpwardScrolls.value >= 2 && - !showRefreshButton.value) { - showRefreshButton.value = true; - } - } - } else if (scrollDirection > 5) { - consecutiveUpwardScrolls.value = 0; - lastUserScrollTime.value = now; - - if (showRefreshButton.value && currentScrollPosition > 50) { - showRefreshButton.value = false; - } - } - - lastScrollPosition.value = currentScrollPosition; - } - - scrollController.addListener(scrollListener); - return () => scrollController.removeListener(scrollListener); - }, []); - useEffect(() { if (news.hasValue && news.value!.isNotEmpty) { WidgetsBinding.instance.addPostFrameCallback((_) { @@ -266,8 +208,9 @@ class FeedMainPage extends HookConsumerWidget { const SizedBox(height: 20), Expanded( - child: ScrollToHideNavbar( + child: ScrollWithRefreshButton( controller: scrollController, + onRefresh: onRefresh, child: SingleChildScrollView( controller: scrollController, physics: const BouncingScrollPhysics(), @@ -295,66 +238,6 @@ class FeedMainPage extends HookConsumerWidget { ], ), ), - - AnimatedPositioned( - duration: const Duration(milliseconds: 300), - curve: Curves.easeInOut, - top: showRefreshButton.value ? 75 : 40, - left: 0, - right: 0, - child: AnimatedOpacity( - duration: const Duration(milliseconds: 300), - curve: Curves.easeInOut, - opacity: showRefreshButton.value ? 1.0 : 0.0, - child: Center( - child: Container( - decoration: BoxDecoration( - color: ColorConstants.main, - borderRadius: BorderRadius.circular(25), - boxShadow: [ - BoxShadow( - color: ColorConstants.onMain.withValues(alpha: 0.4), - blurRadius: 8, - offset: const Offset(0, 3), - ), - ], - ), - child: Material( - color: Colors.transparent, - child: InkWell( - onTap: onRefresh, - borderRadius: BorderRadius.circular(25), - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 8, - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - HeroIcon( - HeroIcons.arrowPath, - size: 16, - color: ColorConstants.background, - ), - const SizedBox(width: 8), - Text( - 'Actualiser', - style: TextStyle( - color: ColorConstants.background, - fontSize: 14, - fontWeight: FontWeight.w600, - ), - ), - ], - ), - ), - ), - ), - ), - ), - ), - ), ], ), ); diff --git a/lib/feed/ui/pages/main_page/scroll_with_refresh_button.dart b/lib/feed/ui/pages/main_page/scroll_with_refresh_button.dart new file mode 100644 index 0000000000..ac9adc6698 --- /dev/null +++ b/lib/feed/ui/pages/main_page/scroll_with_refresh_button.dart @@ -0,0 +1,165 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:heroicons/heroicons.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/navigation/providers/navbar_visibility_provider.dart'; +import 'package:titan/tools/constants.dart'; + +class ScrollWithRefreshButton extends HookConsumerWidget { + final Widget child; + final ScrollController controller; + final Future Function() onRefresh; + + const ScrollWithRefreshButton({ + super.key, + required this.child, + required this.controller, + required this.onRefresh, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final showRefreshButton = useState(false); + final lastScrollPosition = useState(0.0); + final hasScrolledEnough = useState(false); + + final lastUserScrollTime = useState(DateTime.now()); + final consecutiveUpwardScrolls = useState(0); + + useEffect(() { + void scrollListener() { + if (!controller.hasClients) return; + + final navbarVisibilityNotifier = ref.read( + navbarVisibilityProvider.notifier, + ); + final position = controller.position; + final currentScrollPosition = position.pixels; + final scrollDirection = + currentScrollPosition - lastScrollPosition.value; + final maxScrollExtent = position.maxScrollExtent; + + if (currentScrollPosition <= 0) { + navbarVisibilityNotifier.show(); + } else if (currentScrollPosition >= maxScrollExtent) { + } else if (scrollDirection > 0) { + navbarVisibilityNotifier.hide(); + } else if (scrollDirection < 0) { + navbarVisibilityNotifier.show(); + } + + final now = DateTime.now(); + + if (scrollDirection.abs() < 3) return; + + final isAtTop = currentScrollPosition <= position.minScrollExtent; + final isAtBottom = currentScrollPosition >= position.maxScrollExtent; + final isInBounceZone = isAtTop || isAtBottom; + + if (currentScrollPosition > 200 && !hasScrolledEnough.value) { + hasScrolledEnough.value = true; + } + + if (scrollDirection < -15) { + final timeSinceLastScroll = now + .difference(lastUserScrollTime.value) + .inMilliseconds; + + if (!isInBounceZone && timeSinceLastScroll > 50) { + consecutiveUpwardScrolls.value++; + lastUserScrollTime.value = now; + + if (hasScrolledEnough.value && + consecutiveUpwardScrolls.value >= 2 && + !showRefreshButton.value) { + showRefreshButton.value = true; + } + } + } else if (scrollDirection > 5) { + consecutiveUpwardScrolls.value = 0; + lastUserScrollTime.value = now; + + if (showRefreshButton.value && currentScrollPosition > 50) { + showRefreshButton.value = false; + } + } + + lastScrollPosition.value = currentScrollPosition; + } + + controller.addListener(scrollListener); + return () => controller.removeListener(scrollListener); + }, []); + + Future handleRefresh() async { + showRefreshButton.value = false; + await onRefresh(); + } + + return Stack( + clipBehavior: Clip.none, + children: [ + child, + AnimatedPositioned( + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + top: showRefreshButton.value ? -55 : -90, + left: 0, + right: 0, + child: AnimatedOpacity( + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + opacity: showRefreshButton.value ? 1.0 : 0.0, + child: Center( + child: Container( + decoration: BoxDecoration( + color: ColorConstants.main, + borderRadius: BorderRadius.circular(25), + boxShadow: [ + BoxShadow( + color: ColorConstants.onMain.withValues(alpha: 0.4), + blurRadius: 8, + offset: const Offset(0, 3), + ), + ], + ), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: handleRefresh, + borderRadius: BorderRadius.circular(25), + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + HeroIcon( + HeroIcons.arrowPath, + size: 16, + color: ColorConstants.background, + ), + const SizedBox(width: 8), + Text( + 'Actualiser', + style: TextStyle( + color: ColorConstants.background, + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + ), + ), + ), + ), + ), + ), + ], + ); + } +} From 24d433abe2bdc6404731b652d007161ae0383757 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Thu, 21 Aug 2025 18:47:16 +0200 Subject: [PATCH 324/473] fix rebase --- lib/feed/ui/pages/main_page/main_page.dart | 90 ++++++++-------------- 1 file changed, 32 insertions(+), 58 deletions(-) diff --git a/lib/feed/ui/pages/main_page/main_page.dart b/lib/feed/ui/pages/main_page/main_page.dart index 1ea1fdd970..ce423ea2d8 100644 --- a/lib/feed/ui/pages/main_page/main_page.dart +++ b/lib/feed/ui/pages/main_page/main_page.dart @@ -85,33 +85,7 @@ class FeedMainPage extends HookConsumerWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - CustomSearchBar( - onFilter: () async { - final syncNews = newsNotifier.allNews.maybeWhen( - orElse: () => [], - data: (loaded) => loaded, - ); - final entities = syncNews - .map((e) => e.entity) - .toSet() - .toList(); - final modules = syncNews - .map((e) => e.module) - .toSet() - .toList(); - await showCustomBottomModal( - modal: FilterNewsModal( - entities: entities, - modules: modules, - ), - context: context, - ref: ref, - ); - }, - onSearch: (_) {}, - ), - - const SizedBox(height: 20), + const SizedBox(height: 5), Row( children: [ @@ -123,37 +97,37 @@ class FeedMainPage extends HookConsumerWidget { color: ColorConstants.title, ), ), - Spacer(), - IconButton( - icon: HeroIcon( - HeroIcons.adjustmentsHorizontal, - color: ColorConstants.tertiary, - size: 20, - ), - onPressed: () async { - final syncNews = newsNotifier.allNews.maybeWhen( - orElse: () => [], - data: (loaded) => loaded, - ); - final entities = syncNews - .map((e) => e.entity) - .toSet() - .toList(); - final modules = syncNews - .map((e) => e.module) - .toSet() - .toList(); - await showCustomBottomModal( - modal: FilterNewsModal( - entities: entities, - modules: modules, + Spacer(), + IconButton( + icon: HeroIcon( + HeroIcons.adjustmentsHorizontal, + color: ColorConstants.tertiary, + size: 20, ), - context: context, - ref: ref, - ); - }, - splashRadius: 20, - ), + onPressed: () async { + final syncNews = newsNotifier.allNews.maybeWhen( + orElse: () => [], + data: (loaded) => loaded, + ); + final entities = syncNews + .map((e) => e.entity) + .toSet() + .toList(); + final modules = syncNews + .map((e) => e.module) + .toSet() + .toList(); + await showCustomBottomModal( + modal: FilterNewsModal( + entities: entities, + modules: modules, + ), + context: context, + ref: ref, + ); + }, + splashRadius: 20, + ), if (isUserAMemberOfAnAssociation || isFeedAdmin) CustomIconButton( icon: HeroIcon( @@ -205,7 +179,7 @@ class FeedMainPage extends HookConsumerWidget { ], ), - const SizedBox(height: 20), + const SizedBox(height: 10), Expanded( child: ScrollWithRefreshButton( From 6d0cc80b2e2de22dfea88c8ac51f1a426dc91a37 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Thu, 21 Aug 2025 20:10:35 +0200 Subject: [PATCH 325/473] Feed fix (#47) * fix: refresh button position * fix: not displaying action when action not available * feat: adding redirection to advert * fix: checkbox * fix: feed refresh button * fix: putting location next to event name * fix: condition for action to be displayed --- lib/feed/tools/news_helper.dart | 4 - .../pages/add_event_page/add_event_page.dart | 35 ++- lib/feed/ui/pages/main_page/event_card.dart | 203 ++++++++++-------- lib/feed/ui/pages/main_page/main_page.dart | 48 ++--- .../main_page/scroll_with_refresh_button.dart | 103 ++++----- .../ui/pages/main_page/time_line_item.dart | 16 +- 6 files changed, 203 insertions(+), 206 deletions(-) diff --git a/lib/feed/tools/news_helper.dart b/lib/feed/tools/news_helper.dart index 4f7bc8394e..f6f9f1559e 100644 --- a/lib/feed/tools/news_helper.dart +++ b/lib/feed/tools/news_helper.dart @@ -141,10 +141,6 @@ String getNewsSubtitle( } } - if (news.location != null && news.location!.isNotEmpty) { - subtitle += ' | ${news.location}'; - } - if (subtitle.isEmpty) { subtitle = news.entity; } diff --git a/lib/feed/ui/pages/add_event_page/add_event_page.dart b/lib/feed/ui/pages/add_event_page/add_event_page.dart index af9bead4da..a9695235d5 100644 --- a/lib/feed/ui/pages/add_event_page/add_event_page.dart +++ b/lib/feed/ui/pages/add_event_page/add_event_page.dart @@ -108,6 +108,7 @@ class AddEventPage extends HookConsumerWidget { title: AppLocalizations.of(context)!.eventAllDay, valueNotifier: allDay, onChanged: () { + allDay.value = !allDay.value; startDateController.text = ""; endDateController.text = ""; // recurrenceEndDateController.text = ""; @@ -124,7 +125,7 @@ class AddEventPage extends HookConsumerWidget { // recurrenceEndDateController.text = ""; // }, // ), - const SizedBox(height: 30), + // const SizedBox(height: 0), // recurrentController.value // ? Column( // children: [ @@ -240,24 +241,20 @@ class AddEventPage extends HookConsumerWidget { // ], // ) // : - Column( - children: [ - DateEntry( - onTap: () => allDay.value - ? getOnlyDayDate(context, startDateController) - : getFullDate(context, startDateController), - controller: startDateController, - label: AppLocalizations.of(context)!.eventStartDate, - ), - const SizedBox(height: 30), - DateEntry( - onTap: () => allDay.value - ? getOnlyDayDate(context, endDateController) - : getFullDate(context, endDateController), - controller: endDateController, - label: AppLocalizations.of(context)!.eventEndDate, - ), - ], + DateEntry( + onTap: () => allDay.value + ? getOnlyDayDate(context, startDateController) + : getFullDate(context, startDateController), + controller: startDateController, + label: AppLocalizations.of(context)!.eventStartDate, + ), + const SizedBox(height: 10), + DateEntry( + onTap: () => allDay.value + ? getOnlyDayDate(context, endDateController) + : getFullDate(context, endDateController), + controller: endDateController, + label: AppLocalizations.of(context)!.eventEndDate, ), SizedBox(height: 10), TextEntry(label: "Lieu", controller: locationController), diff --git a/lib/feed/ui/pages/main_page/event_card.dart b/lib/feed/ui/pages/main_page/event_card.dart index d6c23fdd9b..a2db3a0679 100644 --- a/lib/feed/ui/pages/main_page/event_card.dart +++ b/lib/feed/ui/pages/main_page/event_card.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/advert/router.dart'; import 'package:titan/feed/class/news.dart'; import 'package:titan/feed/providers/news_image_provider.dart'; import 'package:titan/feed/providers/news_images_provider.dart'; @@ -19,110 +21,129 @@ class EventCard extends ConsumerWidget { ); final newsImagesNotifier = ref.watch(newsImagesProvider.notifier); final imageNotifier = ref.watch(newsImageProvider.notifier); - return Stack( - children: [ - AutoLoaderChild( - group: images, - notifier: newsImagesNotifier, - mapKey: item.id, - loader: (itemId) => imageNotifier.getNewsImage(itemId), - orElseBuilder: (context, stack) => Container( - width: double.infinity, - height: 125, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(15), - gradient: const LinearGradient( - colors: [ColorConstants.onMain, ColorConstants.main], - begin: Alignment.bottomLeft, - end: Alignment.topRight, + return GestureDetector( + onTap: () { + if (item.module == "advert") { + QR.to(AdvertRouter.root); + } + }, + child: Stack( + children: [ + AutoLoaderChild( + group: images, + notifier: newsImagesNotifier, + mapKey: item.id, + loader: (itemId) => imageNotifier.getNewsImage(itemId), + orElseBuilder: (context, stack) => Container( + width: double.infinity, + height: 125, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(15), + gradient: const LinearGradient( + colors: [ColorConstants.onMain, ColorConstants.main], + begin: Alignment.bottomLeft, + end: Alignment.topRight, + ), + ), + ), + dataBuilder: (context, value) => Container( + width: double.infinity, + height: 125, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(15), + image: value.isEmpty + ? null + : DecorationImage( + image: value.first.image, + fit: BoxFit.cover, + ), + gradient: value.isNotEmpty + ? null + : const LinearGradient( + colors: [ColorConstants.onMain, ColorConstants.main], + begin: Alignment.bottomLeft, + end: Alignment.topRight, + ), ), ), ), - dataBuilder: (context, value) => Container( - width: double.infinity, - height: 125, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(15), - image: value.isEmpty - ? null - : DecorationImage( - image: value.first.image, - fit: BoxFit.cover, - ), - gradient: value.isNotEmpty - ? null - : const LinearGradient( - colors: [ColorConstants.onMain, ColorConstants.main], - begin: Alignment.bottomLeft, - end: Alignment.topRight, + Padding( + padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 70), + Row( + children: [ + Text( + item.title, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w900, + color: ColorConstants.background, + ), ), + if (item.location != null && item.location!.isNotEmpty) + Text( + ' | ${item.location}', + style: const TextStyle( + fontSize: 14, + color: ColorConstants.background, + ), + ), + ], + ), + Text( + getNewsSubtitle( + item, + locale: Localizations.localeOf(context).languageCode, + context: context, + ), + style: const TextStyle( + fontSize: 12, + color: ColorConstants.background, + ), + ), + ], ), ), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox(height: 70), - Text( - item.title, - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w900, - color: ColorConstants.background, + if (isNewsTerminated(item) && item.module != "advert") + Positioned( + bottom: 53, + left: 15, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 3), + decoration: const BoxDecoration( + color: ColorConstants.main, + borderRadius: BorderRadius.all(Radius.circular(5)), ), - ), - Text( - getNewsSubtitle( - item, - locale: Localizations.localeOf(context).languageCode, - context: context, - ), - style: const TextStyle( - fontSize: 12, - color: ColorConstants.background, + child: const Text( + 'Terminé', + style: TextStyle( + color: ColorConstants.background, + fontSize: 10, + ), ), ), - ], - ), - ), - if (isNewsTerminated(item) && item.module != "advert") - Positioned( - bottom: 53, - left: 15, - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 3), - decoration: const BoxDecoration( - color: ColorConstants.main, - borderRadius: BorderRadius.all(Radius.circular(5)), - ), - child: const Text( - 'Terminé', - style: TextStyle( + ), + if (isNewsOngoing(item) && item.module != "advert") + Positioned( + bottom: 53, + left: 15, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 3), + decoration: const BoxDecoration( color: ColorConstants.background, - fontSize: 10, + borderRadius: BorderRadius.all(Radius.circular(5)), + ), + child: const Text( + 'En cours', + style: TextStyle(color: ColorConstants.main, fontSize: 10), ), ), ), - ), - if (isNewsOngoing(item) && item.module != "advert") - Positioned( - bottom: 53, - left: 15, - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 3), - decoration: const BoxDecoration( - color: ColorConstants.background, - borderRadius: BorderRadius.all(Radius.circular(5)), - ), - child: const Text( - 'En cours', - style: TextStyle(color: ColorConstants.main, fontSize: 10), - ), - ), - ), - ], + ], + ), ); } } diff --git a/lib/feed/ui/pages/main_page/main_page.dart b/lib/feed/ui/pages/main_page/main_page.dart index ce423ea2d8..e2b87fdc83 100644 --- a/lib/feed/ui/pages/main_page/main_page.dart +++ b/lib/feed/ui/pages/main_page/main_page.dart @@ -24,7 +24,7 @@ class FeedMainPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final news = ref.watch(newsListProvider); - final newsNotifier = ref.watch(newsListProvider.notifier); + final newsListNotifier = ref.watch(newsListProvider.notifier); final isUserAMemberOfAnAssociation = ref.watch( isUserAMemberOfAnAssociationProvider, ); @@ -32,7 +32,7 @@ class FeedMainPage extends HookConsumerWidget { final scrollController = useScrollController(); Future onRefresh() async { - await newsNotifier.loadNewsList(); + await newsListNotifier.loadNewsList(); } useEffect(() { @@ -105,7 +105,7 @@ class FeedMainPage extends HookConsumerWidget { size: 20, ), onPressed: () async { - final syncNews = newsNotifier.allNews.maybeWhen( + final syncNews = newsListNotifier.allNews.maybeWhen( orElse: () => [], data: (loaded) => loaded, ); @@ -182,36 +182,36 @@ class FeedMainPage extends HookConsumerWidget { const SizedBox(height: 10), Expanded( - child: ScrollWithRefreshButton( + child: SingleChildScrollView( controller: scrollController, - onRefresh: onRefresh, - child: SingleChildScrollView( - controller: scrollController, - physics: const BouncingScrollPhysics(), - child: AsyncChild( - value: news, - builder: (context, news) => news.isEmpty - ? const Center( - child: Text( - 'Aucune actualité disponible', - style: TextStyle( - fontSize: 16, - color: ColorConstants.tertiary, - ), + physics: const BouncingScrollPhysics(), + child: AsyncChild( + value: news, + builder: (context, news) => news.isEmpty + ? const Center( + child: Text( + 'Aucune actualité disponible', + style: TextStyle( + fontSize: 16, + color: ColorConstants.tertiary, ), - ) - : FeedTimeline( - isAdmin: isFeedAdmin, - items: news, - onItemTap: (item) {}, ), - ), + ) + : FeedTimeline( + isAdmin: isFeedAdmin, + items: news, + onItemTap: (item) {}, + ), ), ), ), ], ), ), + ScrollWithRefreshButton( + controller: scrollController, + onRefresh: onRefresh, + ), ], ), ); diff --git a/lib/feed/ui/pages/main_page/scroll_with_refresh_button.dart b/lib/feed/ui/pages/main_page/scroll_with_refresh_button.dart index ac9adc6698..569ce2ecfd 100644 --- a/lib/feed/ui/pages/main_page/scroll_with_refresh_button.dart +++ b/lib/feed/ui/pages/main_page/scroll_with_refresh_button.dart @@ -6,13 +6,11 @@ import 'package:titan/navigation/providers/navbar_visibility_provider.dart'; import 'package:titan/tools/constants.dart'; class ScrollWithRefreshButton extends HookConsumerWidget { - final Widget child; final ScrollController controller; final Future Function() onRefresh; const ScrollWithRefreshButton({ super.key, - required this.child, required this.controller, required this.onRefresh, }); @@ -96,70 +94,55 @@ class ScrollWithRefreshButton extends HookConsumerWidget { await onRefresh(); } - return Stack( - clipBehavior: Clip.none, - children: [ - child, - AnimatedPositioned( - duration: const Duration(milliseconds: 300), - curve: Curves.easeInOut, - top: showRefreshButton.value ? -55 : -90, - left: 0, - right: 0, - child: AnimatedOpacity( - duration: const Duration(milliseconds: 300), - curve: Curves.easeInOut, - opacity: showRefreshButton.value ? 1.0 : 0.0, - child: Center( - child: Container( - decoration: BoxDecoration( - color: ColorConstants.main, - borderRadius: BorderRadius.circular(25), - boxShadow: [ - BoxShadow( - color: ColorConstants.onMain.withValues(alpha: 0.4), - blurRadius: 8, - offset: const Offset(0, 3), - ), - ], - ), - child: Material( - color: Colors.transparent, - child: InkWell( - onTap: handleRefresh, - borderRadius: BorderRadius.circular(25), - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 8, - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - HeroIcon( - HeroIcons.arrowPath, - size: 16, - color: ColorConstants.background, - ), - const SizedBox(width: 8), - Text( - 'Actualiser', - style: TextStyle( - color: ColorConstants.background, - fontSize: 14, - fontWeight: FontWeight.w600, - ), - ), - ], - ), + return AnimatedPositioned( + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + top: showRefreshButton.value ? 10 : -10, + left: 0, + right: 0, + child: AnimatedOpacity( + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + opacity: showRefreshButton.value ? 1.0 : 0.0, + child: Center( + child: GestureDetector( + onTap: handleRefresh, + child: Container( + decoration: BoxDecoration( + color: ColorConstants.main, + borderRadius: BorderRadius.circular(25), + boxShadow: [ + BoxShadow( + color: ColorConstants.onMain.withValues(alpha: 0.4), + blurRadius: 8, + offset: const Offset(0, 3), + ), + ], + ), + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + HeroIcon( + HeroIcons.arrowPath, + size: 16, + color: ColorConstants.background, + ), + const SizedBox(width: 8), + Text( + 'Actualiser', + style: TextStyle( + color: ColorConstants.background, + fontSize: 14, + fontWeight: FontWeight.w600, ), ), - ), + ], ), ), ), ), - ], + ), ); } } diff --git a/lib/feed/ui/pages/main_page/time_line_item.dart b/lib/feed/ui/pages/main_page/time_line_item.dart index db30b3641a..bf06ed37ae 100644 --- a/lib/feed/ui/pages/main_page/time_line_item.dart +++ b/lib/feed/ui/pages/main_page/time_line_item.dart @@ -16,8 +16,13 @@ class TimelineItem extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final showAction = + item.actionStart != null && + item.actionStart!.isBefore(DateTime.now()) && + item.end != null && + item.end!.isAfter(DateTime.now()); return SizedBox( - height: item.actionStart != null ? 200 : 160, + height: showAction ? 200 : 160, child: Stack( children: [ Padding( @@ -65,7 +70,7 @@ class TimelineItem extends ConsumerWidget { ), ], ), - if (item.actionStart != null) + if (showAction) Padding( padding: const EdgeInsets.only(top: 8), child: Row( @@ -99,12 +104,7 @@ class TimelineItem extends ConsumerWidget { actionValidatedButtonText: getActionValidatedButtonText(item, context), isActionValidated: false, - isActionEnabled: - (item.actionStart ?? item.start).isBefore( - DateTime.now(), - ) && - item.end != null && - item.end!.isAfter(DateTime.now()), + isActionEnabled: true, ), ), ], From 259500f801c33d09a8523e4ed41e8da99092c9d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Robert?= <114694873+Rotheem@users.noreply.github.com> Date: Thu, 21 Aug 2025 22:54:03 +0200 Subject: [PATCH 326/473] Fix phonebook (#48) * fix: apply suggested fixes * feat: change admin button * fix: alignement --------- Co-authored-by: Foucauld Bellanger <63885990+foucblg@users.noreply.github.com> --- lib/l10n/app_en.arb | 2 +- lib/l10n/app_fr.arb | 2 +- lib/l10n/app_localizations.dart | 2 +- lib/l10n/app_localizations_en.dart | 2 +- lib/l10n/app_localizations_fr.dart | 3 +-- lib/phonebook/class/complete_member.dart | 12 +----------- lib/phonebook/class/member.dart | 2 +- .../association_member_repository.dart | 7 ++++--- lib/phonebook/ui/components/member_card.dart | 3 +-- lib/phonebook/ui/pages/main_page/main_page.dart | 14 +++++--------- .../membership_editor_page.dart | 6 ++++-- .../membership_editor_page/user_search_modal.dart | 2 +- lib/tools/ui/styleguide/list_item_template.dart | 1 + 13 files changed, 23 insertions(+), 35 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 274c060d62..8ddc0d9cf8 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -827,7 +827,7 @@ "phonebookErrorRoleTagsLoading": "Error loading role tags", "phonebookExistingMembership": "This member is already in the current term", "phonebookFilter": "Filter", - "phonebookFilterDescription": "Filter the associations by their groupement", + "phonebookFilterDescription": "Filter the associations by their type", "phonebookFirstname": "First name:", "phonebookGroupementDeleted": "Association groupement deleted", "phonebookGroupementDeleteError": "Error deleting association groupement", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index ea8925ed95..68c10ef647 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -835,7 +835,7 @@ "phonebookErrorRoleTagsLoading": "Erreur lors du chargement des tags de rôle", "phonebookExistingMembership": "Ce membre est déjà dans le mandat actuel", "phonebookFilter": "Filtrer", - "phonebookFilterDescription": "Sélectionnez un ou plusieurs groupements pour filtrer les associations.", + "phonebookFilterDescription": "Filtrer les associations par type", "phonebookFirstname": "Prénom :", "phonebookGroupementDeleted": "Groupement d'association supprimé", "phonebookGroupementDeleteError": "Erreur lors de la suppression du groupement d'association", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index e26205b687..d3437ed222 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -4727,7 +4727,7 @@ abstract class AppLocalizations { /// No description provided for @phonebookFilterDescription. /// /// In fr, this message translates to: - /// **'Sélectionnez un ou plusieurs groupements pour filtrer les associations.'** + /// **'Filtrer les associations par type'** String get phonebookFilterDescription; /// No description provided for @phonebookFirstname. diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 0dd4b08c2a..b3d5028b09 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -2384,7 +2384,7 @@ class AppLocalizationsEn extends AppLocalizations { @override String get phonebookFilterDescription => - 'Filter the associations by their groupement'; + 'Filter the associations by their type'; @override String get phonebookFirstname => 'First name:'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index f46fdc91d5..e9b7d0481a 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -2400,8 +2400,7 @@ class AppLocalizationsFr extends AppLocalizations { String get phonebookFilter => 'Filtrer'; @override - String get phonebookFilterDescription => - 'Sélectionnez un ou plusieurs groupements pour filtrer les associations.'; + String get phonebookFilterDescription => 'Filtrer les associations par type'; @override String get phonebookFirstname => 'Prénom :'; diff --git a/lib/phonebook/class/complete_member.dart b/lib/phonebook/class/complete_member.dart index 6639807653..58323c66ed 100644 --- a/lib/phonebook/class/complete_member.dart +++ b/lib/phonebook/class/complete_member.dart @@ -1,4 +1,3 @@ -import 'package:titan/super_admin/class/account_type.dart'; import 'package:titan/phonebook/class/membership.dart'; import 'member.dart'; @@ -9,16 +8,7 @@ class CompleteMember { late final List memberships; CompleteMember.fromJson(Map json) { - member = Member( - name: json['name'], - firstname: json['firstname'], - nickname: json['nickname'] ?? "", - id: json['id'], - accountType: AccountType(type: json['account_type']), - email: json['email'], - phone: json['phone'], - promotion: json['promo'] ?? 0, - ); + member = Member.fromJson(json); memberships = List.from( json['memberships'].map((membership) { return Membership.fromJson(membership); diff --git a/lib/phonebook/class/member.dart b/lib/phonebook/class/member.dart index adaa9ed72a..6e01c5e8a0 100644 --- a/lib/phonebook/class/member.dart +++ b/lib/phonebook/class/member.dart @@ -5,7 +5,7 @@ class Member extends SimpleUser { Member({ required super.name, required super.firstname, - required super.nickname, + super.nickname, required super.id, required super.accountType, required this.email, diff --git a/lib/phonebook/repositories/association_member_repository.dart b/lib/phonebook/repositories/association_member_repository.dart index 935ee08dc6..cb92c87ad1 100644 --- a/lib/phonebook/repositories/association_member_repository.dart +++ b/lib/phonebook/repositories/association_member_repository.dart @@ -12,9 +12,10 @@ class AssociationMemberRepository extends Repository { int year, ) async { return List.from( - (await getList( - suffix: "$associationId/members/$year", - )).map((x) => CompleteMember.fromJson(x)), + (await getList(suffix: "$associationId/members/$year")).map((x) { + print("AssociationMemberRepository.getAssociationMemberList: $x"); + return CompleteMember.fromJson(x); + }), ); } diff --git a/lib/phonebook/ui/components/member_card.dart b/lib/phonebook/ui/components/member_card.dart index bbc6561f32..d2e6e61102 100644 --- a/lib/phonebook/ui/components/member_card.dart +++ b/lib/phonebook/ui/components/member_card.dart @@ -44,12 +44,11 @@ class MemberCard extends HookConsumerWidget { member, association, ); - return Padding( padding: const EdgeInsets.symmetric(vertical: 5.0), child: ListItemTemplate( title: - "${(member.member.nickname ?? '${member.member.firstname} ${member.member.name}')} - ${assoMembership.apparentName}", + "${(member.member.nickname ?? member.getName())} - ${assoMembership.apparentName}", subtitle: member.member.nickname != null ? "${member.member.firstname} ${member.member.name}" : null, diff --git a/lib/phonebook/ui/pages/main_page/main_page.dart b/lib/phonebook/ui/pages/main_page/main_page.dart index 605184f50e..2006a98437 100644 --- a/lib/phonebook/ui/pages/main_page/main_page.dart +++ b/lib/phonebook/ui/pages/main_page/main_page.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/providers/is_admin_provider.dart'; +import 'package:titan/advert/ui/components/special_action_button.dart'; import 'package:titan/phonebook/providers/association_filtered_list_provider.dart'; import 'package:titan/phonebook/providers/association_groupement_provider.dart'; import 'package:titan/phonebook/providers/association_groupement_list_provider.dart'; @@ -13,7 +14,6 @@ import 'package:titan/phonebook/ui/phonebook.dart'; import 'package:titan/phonebook/ui/components/association_research_bar.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; -import 'package:titan/tools/ui/styleguide/icon_button.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/l10n/app_localizations.dart'; import 'package:tuple/tuple.dart'; @@ -56,14 +56,10 @@ class PhonebookMainPage extends HookConsumerWidget { Expanded(child: AssociationResearchBar()), if (isPhonebookAdmin || isAdmin) ...[ SizedBox(width: 10), - CustomIconButton( - icon: HeroIcon( - HeroIcons.cog6Tooth, - color: Colors.white, - size: 32, - style: HeroIconStyle.outline, - ), - onPressed: () { + SpecialActionButton( + icon: HeroIcon(HeroIcons.userGroup, color: Colors.white), + name: localizeWithContext.phonebookAdmin, + onTap: () { associationGroupementNotifier .resetAssociationGroupement(); QR.to(PhonebookRouter.root + PhonebookRouter.admin); diff --git a/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart b/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart index b2a700decb..7e61409a35 100644 --- a/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart +++ b/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/phonebook/class/membership.dart'; import 'package:titan/phonebook/providers/association_member_list_provider.dart'; @@ -144,13 +145,14 @@ class MembershipEditorPage extends HookConsumerWidget { style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), ListItemTemplate( + icon: const HeroIcon(HeroIcons.magnifyingGlass), title: member.member.id == "" ? localizeWithContext.phonebookSearchUser : member.member.getName(), subtitle: member.member.id == "" - ? "" + ? null : localizeWithContext.phonebookSearchUser, - trailing: SizedBox.shrink(), + trailing: const HeroIcon(HeroIcons.plus), onTap: () => showCustomBottomModal( context: context, modal: UserSearchModal(), diff --git a/lib/phonebook/ui/pages/membership_editor_page/user_search_modal.dart b/lib/phonebook/ui/pages/membership_editor_page/user_search_modal.dart index 621c6eaa23..d5c8a69092 100644 --- a/lib/phonebook/ui/pages/membership_editor_page/user_search_modal.dart +++ b/lib/phonebook/ui/pages/membership_editor_page/user_search_modal.dart @@ -24,7 +24,6 @@ class UserSearchModal extends HookConsumerWidget { child: SingleChildScrollView( child: Column( children: [ - SearchResult(queryController: textController), CustomSearchBar( autofocus: true, onSearch: (value) => tokenExpireWrapper(ref, () async { @@ -37,6 +36,7 @@ class UserSearchModal extends HookConsumerWidget { } }), ), + SearchResult(queryController: textController), ], ), ), diff --git a/lib/tools/ui/styleguide/list_item_template.dart b/lib/tools/ui/styleguide/list_item_template.dart index 9b8f5079dc..d4f92ac086 100644 --- a/lib/tools/ui/styleguide/list_item_template.dart +++ b/lib/tools/ui/styleguide/list_item_template.dart @@ -26,6 +26,7 @@ class ListItemTemplate extends StatelessWidget { child: Container( padding: const EdgeInsets.symmetric(vertical: 4), child: Row( + crossAxisAlignment: CrossAxisAlignment.center, children: [ if (icon != null) ...[icon!, const SizedBox(width: 10)], Expanded( From fa5ca7623964250d5762509c85aca9d5e289f270 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Thu, 21 Aug 2025 23:39:29 +0200 Subject: [PATCH 327/473] fix shotgun (#50) * fix: event with url and date or none of both * feat: time aware event action * fix: custom messages --------- Co-authored-by: Foucauld Bellanger <63885990+foucblg@users.noreply.github.com> --- lib/feed/tools/news_helper.dart | 15 ++++ .../pages/add_event_page/add_event_page.dart | 16 ++++ lib/feed/ui/pages/main_page/event_action.dart | 77 +++++++++++++++---- .../ui/pages/main_page/time_line_item.dart | 17 ++-- lib/main.dart | 8 +- lib/tools/trads/en_timeago.dart | 73 ++++++++++++++++++ lib/tools/trads/fr_timeago.dart | 73 ++++++++++++++++++ pubspec.lock | 28 ++++--- pubspec.yaml | 1 + 9 files changed, 272 insertions(+), 36 deletions(-) create mode 100644 lib/tools/trads/en_timeago.dart create mode 100644 lib/tools/trads/fr_timeago.dart diff --git a/lib/feed/tools/news_helper.dart b/lib/feed/tools/news_helper.dart index f6f9f1559e..b934b640e7 100644 --- a/lib/feed/tools/news_helper.dart +++ b/lib/feed/tools/news_helper.dart @@ -159,6 +159,21 @@ String getActionTitle(News news, BuildContext context) { return ''; } +String getWaitingTitle( + News news, + BuildContext context, { + required String timeToGo, +}) { + final module = news.module; + + if (module == "campagne") { + return 'Vote $timeToGo'; + } else if (module == "event") { + return 'Shotgun $timeToGo'; + } + return ''; +} + String getActionSubtitle(News news, BuildContext context) { final module = news.module; diff --git a/lib/feed/ui/pages/add_event_page/add_event_page.dart b/lib/feed/ui/pages/add_event_page/add_event_page.dart index a9695235d5..a47ff79563 100644 --- a/lib/feed/ui/pages/add_event_page/add_event_page.dart +++ b/lib/feed/ui/pages/add_event_page/add_event_page.dart @@ -377,6 +377,22 @@ class AddEventPage extends HookConsumerWidget { ); return; } + if (externalLinkController.text.isEmpty && + shotgunDateController.text.isNotEmpty) { + displayToastWithContext( + TypeMsg.error, + "Veuillez fournir un lien externe", + ); + return; + } + if (externalLinkController.text.isNotEmpty && + shotgunDateController.text.isEmpty) { + displayToastWithContext( + TypeMsg.error, + "Veuillez fournir une date", + ); + return; + } final addedEventMsg = AppLocalizations.of( context, )!.eventAddedEvent; diff --git a/lib/feed/ui/pages/main_page/event_action.dart b/lib/feed/ui/pages/main_page/event_action.dart index 1c84d21ad9..5a9eb8c1a2 100644 --- a/lib/feed/ui/pages/main_page/event_action.dart +++ b/lib/feed/ui/pages/main_page/event_action.dart @@ -1,13 +1,19 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:timeago_flutter/timeago_flutter.dart'; import 'package:titan/tools/constants.dart'; -class EventAction extends StatelessWidget { +class EventAction extends HookWidget { final String title, subtitle, actionEnableButtonText, actionValidatedButtonText; + final String Function(String timeToGo) waitingTitle; + final DateTime? timeOpening, eventEnd; final VoidCallback? onActionPressed; - final bool isActionEnabled, isActionValidated; + final bool isActionValidated; const EventAction({ super.key, @@ -16,12 +22,31 @@ class EventAction extends StatelessWidget { this.onActionPressed, required this.actionEnableButtonText, required this.actionValidatedButtonText, - required this.isActionEnabled, required this.isActionValidated, + required this.timeOpening, + required this.eventEnd, + required this.waitingTitle, }); @override Widget build(BuildContext context) { + final now = useState(DateTime.now()); + + useEffect(() { + final timer = Timer.periodic(const Duration(seconds: 1), (_) { + now.value = DateTime.now(); + }); + + return timer.cancel; + }, []); + + final isActionEnabled = + timeOpening != null && + timeOpening!.isBefore(now.value) && + eventEnd != null && + eventEnd!.isAfter(now.value); + + final isWaiting = timeOpening != null && timeOpening!.isAfter(now.value); return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -29,7 +54,7 @@ class EventAction extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - title, + isWaiting ? 'Prépares-toi' : title, style: const TextStyle( fontSize: 13, fontWeight: FontWeight.bold, @@ -37,20 +62,35 @@ class EventAction extends StatelessWidget { ), overflow: TextOverflow.ellipsis, ), - Text( - subtitle, - style: const TextStyle( - fontSize: 11, - color: ColorConstants.secondary, - ), - overflow: TextOverflow.ellipsis, - ), + timeOpening != null && + eventEnd != null && + eventEnd!.isAfter(now.value) && + timeOpening!.isAfter(now.value) + ? Timeago( + date: timeOpening!, + locale: 'fr_short', + allowFromNow: true, + refreshRate: const Duration(seconds: 1), + builder: (context, str) => Text( + waitingTitle(str), + style: const TextStyle( + fontSize: 11, + color: ColorConstants.secondary, + ), + overflow: TextOverflow.ellipsis, + ), + ) + : Text( + subtitle, + style: const TextStyle( + fontSize: 11, + color: ColorConstants.secondary, + ), + overflow: TextOverflow.ellipsis, + ), ], ), - const SizedBox(width: 10), - - // Action button GestureDetector( onTap: () { if (isActionEnabled && !isActionValidated) onActionPressed!.call(); @@ -63,7 +103,12 @@ class EventAction extends StatelessWidget { ? ColorConstants.tertiary : ColorConstants.background, borderRadius: BorderRadius.circular(20), - border: Border.all(color: ColorConstants.tertiary, width: 2), + border: Border.all( + color: ColorConstants.tertiary.withValues( + alpha: isActionEnabled && !isActionValidated ? 1 : 0.5, + ), + width: 2, + ), ), child: Center( child: Text( diff --git a/lib/feed/ui/pages/main_page/time_line_item.dart b/lib/feed/ui/pages/main_page/time_line_item.dart index bf06ed37ae..afe8cadb2c 100644 --- a/lib/feed/ui/pages/main_page/time_line_item.dart +++ b/lib/feed/ui/pages/main_page/time_line_item.dart @@ -16,13 +16,8 @@ class TimelineItem extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final showAction = - item.actionStart != null && - item.actionStart!.isBefore(DateTime.now()) && - item.end != null && - item.end!.isAfter(DateTime.now()); return SizedBox( - height: showAction ? 200 : 160, + height: item.actionStart != null ? 200 : 160, child: Stack( children: [ Padding( @@ -70,7 +65,7 @@ class TimelineItem extends ConsumerWidget { ), ], ), - if (showAction) + if (item.actionStart != null) Padding( padding: const EdgeInsets.only(top: 8), child: Row( @@ -94,6 +89,11 @@ class TimelineItem extends ConsumerWidget { Expanded( child: EventAction( title: getActionTitle(item, context), + waitingTitle: (timeToGo) => getWaitingTitle( + item, + context, + timeToGo: timeToGo, + ), subtitle: getActionSubtitle(item, context), onActionPressed: () => getActionButtonAction(item, context, ref), @@ -104,7 +104,8 @@ class TimelineItem extends ConsumerWidget { actionValidatedButtonText: getActionValidatedButtonText(item, context), isActionValidated: false, - isActionEnabled: true, + eventEnd: item.end, + timeOpening: item.actionStart, ), ), ], diff --git a/lib/main.dart b/lib/main.dart index 24dee3c23e..85a6d1e322 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -17,6 +17,8 @@ import 'package:titan/tools/functions.dart'; import 'package:titan/tools/plausible/plausible_observer.dart'; import 'package:titan/tools/providers/locale_notifier.dart'; import 'package:titan/tools/providers/path_forwarding_provider.dart'; +import 'package:titan/tools/trads/en_timeago.dart'; +import 'package:titan/tools/trads/fr_timeago.dart'; import 'package:titan/tools/ui/layouts/app_template.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:qlevar_router/qlevar_router.dart' as qqr; @@ -37,8 +39,10 @@ void main() async { FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler); } await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); - timeago.setLocaleMessages('fr', timeago.FrMessages()); - timeago.setLocaleMessages('fr_short', timeago.FrShortMessages()); + timeago.setLocaleMessages('fr', CustomFrMessages()); + timeago.setLocaleMessages('fr_short', CustomFrShortMessages()); + timeago.setLocaleMessages('en', CustomEnMessages()); + timeago.setLocaleMessages('en_short', CustomEnShortMessages()); runApp(ProviderScope(child: MyApp())); } diff --git a/lib/tools/trads/en_timeago.dart b/lib/tools/trads/en_timeago.dart new file mode 100644 index 0000000000..2bd77cf300 --- /dev/null +++ b/lib/tools/trads/en_timeago.dart @@ -0,0 +1,73 @@ +import 'package:timeago/timeago.dart'; + +/// English Messages +class CustomEnMessages implements LookupMessages { + @override + String prefixAgo() => ''; + @override + String prefixFromNow() => ''; + @override + String suffixAgo() => 'ago'; + @override + String suffixFromNow() => 'from now'; + @override + String lessThanOneMinute(int seconds) => '$seconds seconds'; + @override + String aboutAMinute(int minutes) => 'a minute'; + @override + String minutes(int minutes) => '$minutes minutes'; + @override + String aboutAnHour(int minutes) => 'about an hour'; + @override + String hours(int hours) => '$hours hours'; + @override + String aDay(int hours) => 'a day'; + @override + String days(int days) => '$days days'; + @override + String aboutAMonth(int days) => 'about a month'; + @override + String months(int months) => '$months months'; + @override + String aboutAYear(int year) => 'about a year'; + @override + String years(int years) => '$years years'; + @override + String wordSeparator() => ' '; +} + +/// English short Messages +class CustomEnShortMessages implements LookupMessages { + @override + String prefixAgo() => ''; + @override + String prefixFromNow() => ''; + @override + String suffixAgo() => ''; + @override + String suffixFromNow() => ''; + @override + String lessThanOneMinute(int seconds) => '${seconds}s'; + @override + String aboutAMinute(int minutes) => '1m'; + @override + String minutes(int minutes) => '${minutes}m'; + @override + String aboutAnHour(int minutes) => '~1h'; + @override + String hours(int hours) => '${hours}h'; + @override + String aDay(int hours) => '~1d'; + @override + String days(int days) => '${days}d'; + @override + String aboutAMonth(int days) => '~1mo'; + @override + String months(int months) => '${months}mo'; + @override + String aboutAYear(int year) => '~1y'; + @override + String years(int years) => '${years}y'; + @override + String wordSeparator() => ' '; +} diff --git a/lib/tools/trads/fr_timeago.dart b/lib/tools/trads/fr_timeago.dart new file mode 100644 index 0000000000..b7e389bc21 --- /dev/null +++ b/lib/tools/trads/fr_timeago.dart @@ -0,0 +1,73 @@ +import 'package:timeago/timeago.dart'; + +/// English Messages +class CustomFrMessages implements LookupMessages { + @override + String prefixAgo() => 'il y a'; + @override + String prefixFromNow() => "d'ici"; + @override + String suffixAgo() => ''; + @override + String suffixFromNow() => ''; + @override + String lessThanOneMinute(int seconds) => "$seconds secondes"; + @override + String aboutAMinute(int minutes) => 'environ une minute'; + @override + String minutes(int minutes) => 'environ $minutes minutes'; + @override + String aboutAnHour(int minutes) => 'environ une heure'; + @override + String hours(int hours) => '$hours heures'; + @override + String aDay(int hours) => 'environ un jour'; + @override + String days(int days) => 'environ $days jours'; + @override + String aboutAMonth(int days) => 'environ un mois'; + @override + String months(int months) => 'environ $months mois'; + @override + String aboutAYear(int year) => 'un an'; + @override + String years(int years) => '$years ans'; + @override + String wordSeparator() => ' '; +} + +/// English short Messages +class CustomFrShortMessages implements LookupMessages { + @override + String prefixAgo() => 'il y a'; + @override + String prefixFromNow() => "d'ici"; + @override + String suffixAgo() => ''; + @override + String suffixFromNow() => ''; + @override + String lessThanOneMinute(int seconds) => "$seconds secondes"; + @override + String aboutAMinute(int minutes) => 'une minute'; + @override + String minutes(int minutes) => '$minutes minutes'; + @override + String aboutAnHour(int minutes) => 'une heure'; + @override + String hours(int hours) => '$hours heures'; + @override + String aDay(int hours) => 'un jour'; + @override + String days(int days) => '$days jours'; + @override + String aboutAMonth(int days) => 'un mois'; + @override + String months(int months) => '$months mois'; + @override + String aboutAYear(int year) => 'un an'; + @override + String years(int years) => '$years ans'; + @override + String wordSeparator() => ' '; +} diff --git a/pubspec.lock b/pubspec.lock index 489f1b2d7d..062fe329e5 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -841,26 +841,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + sha256: "8dcda04c3fc16c14f48a7bb586d4be1f0d1572731b6d81d51772ef47c02081e0" url: "https://pub.dev" source: hosted - version: "10.0.9" + version: "11.0.1" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" url: "https://pub.dev" source: hosted - version: "3.0.9" + version: "3.0.10" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" lints: dependency: transitive description: @@ -1398,10 +1398,10 @@ packages: dependency: transitive description: name: test_api - sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" url: "https://pub.dev" source: hosted - version: "0.7.4" + version: "0.7.6" timeago: dependency: "direct main" description: @@ -1410,6 +1410,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.7.1" + timeago_flutter: + dependency: "direct main" + description: + name: timeago_flutter + sha256: "0fd70e79f35f5ea6507f04b3852ba3df3de595901cc1a916e4abc452115b09ac" + url: "https://pub.dev" + source: hosted + version: "3.7.0" timezone: dependency: "direct main" description: @@ -1558,10 +1566,10 @@ packages: dependency: "direct dev" description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" vm_service: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 41d3147e26..66a53bb1b7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -66,6 +66,7 @@ dependencies: smooth_page_indicator: ^1.0.0+2 syncfusion_flutter_calendar: ^29.1.38 timeago: ^3.7.0 + timeago_flutter: ^3.7.0 timezone: ^0.10.0 tuple: ^2.0.0 universal_html: ^2.0.8 From 06d3b5f07ffb34b6a28b534b5a5059d179e9c886 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Robert?= <114694873+Rotheem@users.noreply.github.com> Date: Sat, 23 Aug 2025 19:46:01 +0200 Subject: [PATCH 328/473] PROXI-79: mypayment invoices (#39) * feat: reorder paiement traductions * feat: add invoices * feat: update translations * fix: clean up * feat: UI improvement * fix: missing structure selector * fix: translation --- lib/admin/providers/structure_provider.dart | 4 + .../add_edit_structure_page.dart | 165 +- .../pages/structure_page/structure_page.dart | 215 +- lib/l10n/app_en.arb | 336 +- lib/l10n/app_fr.arb | 336 +- lib/l10n/app_localizations.dart | 4412 +++++++++-------- lib/l10n/app_localizations_en.dart | 1705 ++++--- lib/l10n/app_localizations_fr.dart | 1805 +++---- lib/paiement/class/bank_account_holder.dart | 32 + lib/paiement/class/invoice.dart | 133 + lib/paiement/class/store.dart | 31 +- lib/paiement/class/structure.dart | 83 +- .../bank_account_holder_provider.dart | 34 + lib/paiement/providers/device_provider.dart | 8 +- .../providers/invoice_list_provider.dart | 90 + .../providers/invoice_pdf_provider.dart | 19 + lib/paiement/providers/invoice_provider.dart | 14 + lib/paiement/providers/is_payment_admin.dart | 16 +- .../providers/my_structures_provider.dart | 5 +- .../bank_account_holder_repository.dart | 34 + .../repositories/invoice_pdf_repository.dart | 20 + .../repositories/invoices_repository.dart | 87 + lib/paiement/router.dart | 36 +- lib/paiement/tools/constants.dart | 3 - lib/paiement/tools/functions.dart | 18 - .../ui/pages/devices_page/devices_page.dart | 1 + .../ui/pages/fund_page/fund_page.dart | 2 + .../invoices_admin_page/invoice_card.dart | 224 + .../invoices_admin_page.dart | 210 + .../invoices_structure_page.dart | 106 + .../main_page/account_card/account_card.dart | 79 +- .../ui/pages/main_page/main_page.dart | 2 +- .../seller_card/admin_invoice_card.dart | 60 + .../seller_card/store_admin_card.dart | 62 - .../main_page/seller_card/store_card.dart | 24 +- .../main_page/seller_card/store_list.dart | 13 +- .../seller_card/structure_admin_card.dart | 107 + .../ui/pages/main_page/tos_dialog.dart | 1 + .../ui/pages/pay_page/confirm_button.dart | 2 +- lib/paiement/ui/pages/pay_page/pay_page.dart | 1 + .../ui/pages/scan_page/scan_page.dart | 601 +-- .../ui/pages/stats_page/sum_up_chart.dart | 2 +- .../pages/stats_page/transaction_chart.dart | 2 +- .../ui/pages/store_pages/add_edit_store.dart | 6 +- .../add_store_card.dart | 4 +- .../admin_store_card.dart | 2 +- .../structure_admin_page.dart} | 12 +- lib/paiement/ui/paiement.dart | 7 +- .../association_admin_edition_modal.dart | 2 +- .../member_edition_modal.dart | 2 +- lib/tools/ui/styleguide/button.dart | 1 + ...tom_dialog_box.dart => confirm_modal.dart} | 0 52 files changed, 6640 insertions(+), 4536 deletions(-) create mode 100644 lib/paiement/class/bank_account_holder.dart create mode 100644 lib/paiement/class/invoice.dart create mode 100644 lib/paiement/providers/bank_account_holder_provider.dart create mode 100644 lib/paiement/providers/invoice_list_provider.dart create mode 100644 lib/paiement/providers/invoice_pdf_provider.dart create mode 100644 lib/paiement/providers/invoice_provider.dart create mode 100644 lib/paiement/repositories/bank_account_holder_repository.dart create mode 100644 lib/paiement/repositories/invoice_pdf_repository.dart create mode 100644 lib/paiement/repositories/invoices_repository.dart delete mode 100644 lib/paiement/tools/constants.dart create mode 100644 lib/paiement/ui/pages/invoices_admin_page/invoice_card.dart create mode 100644 lib/paiement/ui/pages/invoices_admin_page/invoices_admin_page.dart create mode 100644 lib/paiement/ui/pages/invoices_structure_page/invoices_structure_page.dart create mode 100644 lib/paiement/ui/pages/main_page/seller_card/admin_invoice_card.dart delete mode 100644 lib/paiement/ui/pages/main_page/seller_card/store_admin_card.dart create mode 100644 lib/paiement/ui/pages/main_page/seller_card/structure_admin_card.dart rename lib/paiement/ui/pages/{admin_page => structure_admin_page}/add_store_card.dart (92%) rename lib/paiement/ui/pages/{admin_page => structure_admin_page}/admin_store_card.dart (98%) rename lib/paiement/ui/pages/{admin_page/admin_page.dart => structure_admin_page/structure_admin_page.dart} (82%) rename lib/tools/ui/styleguide/{custom_dialog_box.dart => confirm_modal.dart} (100%) diff --git a/lib/admin/providers/structure_provider.dart b/lib/admin/providers/structure_provider.dart index d678a7bc75..1cd9308633 100644 --- a/lib/admin/providers/structure_provider.dart +++ b/lib/admin/providers/structure_provider.dart @@ -7,6 +7,10 @@ class StructureNotifier extends StateNotifier { void setStructure(Structure structure) { state = structure; } + + void resetStructure() { + state = Structure.empty(); + } } final structureProvider = StateNotifierProvider( diff --git a/lib/admin/ui/pages/structure_page/add_edit_structure_page/add_edit_structure_page.dart b/lib/admin/ui/pages/structure_page/add_edit_structure_page/add_edit_structure_page.dart index 35c1d5b5fd..5ffa4dc2e1 100644 --- a/lib/admin/ui/pages/structure_page/add_edit_structure_page/add_edit_structure_page.dart +++ b/lib/admin/ui/pages/structure_page/add_edit_structure_page/add_edit_structure_page.dart @@ -14,14 +14,13 @@ import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/horizontal_list_view.dart'; import 'package:titan/tools/ui/layouts/item_chip.dart'; -import 'package:titan/tools/ui/builders/waiting_button.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; +import 'package:titan/tools/ui/styleguide/button.dart'; import 'package:titan/tools/ui/styleguide/list_item.dart'; import 'package:titan/tools/ui/styleguide/text_entry.dart'; import 'package:titan/user/class/simple_users.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/l10n/app_localizations.dart'; -import 'package:titan/vote/ui/pages/admin_page/admin_button.dart'; class AddEditStructurePage extends HookConsumerWidget { const AddEditStructurePage({super.key}); @@ -37,6 +36,26 @@ class AddEditStructurePage extends HookConsumerWidget { final structureListNotifier = ref.watch(structureListProvider.notifier); final isEdit = structure.id != ''; final name = useTextEditingController(text: isEdit ? structure.name : null); + final shortId = useTextEditingController( + text: isEdit ? structure.shortId : null, + ); + final siegeAddressStreet = useTextEditingController( + text: isEdit ? structure.siegeAddressStreet : null, + ); + final siegeAddressCity = useTextEditingController( + text: isEdit ? structure.siegeAddressCity : null, + ); + final siegeAddressZipcode = useTextEditingController( + text: isEdit ? structure.siegeAddressZipcode : null, + ); + final siegeAddressCountry = useTextEditingController( + text: isEdit ? structure.siegeAddressCountry : null, + ); + final siret = useTextEditingController( + text: isEdit ? structure.siret : null, + ); + final iban = useTextEditingController(text: isEdit ? structure.iban : null); + final bic = useTextEditingController(text: isEdit ? structure.bic : null); final allAssociationMembershipList = ref.watch( allAssociationMembershipListProvider, ); @@ -60,13 +79,103 @@ class AddEditStructurePage extends HookConsumerWidget { key: key, child: Column( children: [ + Text( + isEdit + ? localizeWithContext.adminEditStructure + : localizeWithContext.adminAddStructure, + style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 20), + TextEntry( + controller: name, + label: localizeWithContext.adminName, + ), + const SizedBox(height: 20), + TextEntry( + controller: shortId, + label: localizeWithContext.adminShortId, + validator: (value) { + if (value.isNotEmpty && value.length != 3) { + return localizeWithContext.adminShortIdError; + } + return null; + }, + ), + const SizedBox(height: 30), + Text( + localizeWithContext.adminSiegeAddress, + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 10), + TextEntry( + controller: siegeAddressStreet, + label: localizeWithContext.adminStreet, + ), + const SizedBox(height: 20), + Row( + children: [ + Expanded( + flex: 2, + child: TextEntry( + controller: siegeAddressCity, + label: localizeWithContext.adminCity, + ), + ), + const SizedBox(width: 20), + Expanded( + child: TextEntry( + controller: siegeAddressZipcode, + label: localizeWithContext.adminZipcode, + ), + ), + ], + ), + const SizedBox(height: 20), + TextEntry( + controller: siegeAddressCountry, + label: localizeWithContext.adminCountry, + ), + const SizedBox(height: 20), + TextEntry( + controller: siret, + label: localizeWithContext.adminSiret, + validator: (value) { + if (value.isNotEmpty && + value.replaceAll(" ", "").length != 14) { + return localizeWithContext.adminSiretError; + } + return null; + }, + canBeEmpty: true, + ), + const SizedBox(height: 20), + Text( + localizeWithContext.adminBankDetails, + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 20), + TextEntry( + controller: iban, + label: localizeWithContext.adminIban, + validator: (value) { + if (value.isNotEmpty && + value.replaceAll(" ", "").length != 27) { + return localizeWithContext.adminIbanError; + } + return null; + }, + ), const SizedBox(height: 20), - Padding( - padding: const EdgeInsets.all(8.0), - child: TextEntry( - controller: name, - label: localizeWithContext.adminName, - ), + TextEntry( + controller: bic, + label: localizeWithContext.adminBic, + validator: (value) { + if (value.isNotEmpty && + value.replaceAll(" ", "").length != 11) { + return localizeWithContext.adminBicError; + } + return null; + }, ), const SizedBox(height: 20), AsyncChild( @@ -123,6 +232,7 @@ class AddEditStructurePage extends HookConsumerWidget { ) : ListItem( title: localizeWithContext.adminSelectManager, + subtitle: structureManager.getName(), onTap: () async { await showCustomBottomModal( context: context, @@ -132,8 +242,8 @@ class AddEditStructurePage extends HookConsumerWidget { }, ), const SizedBox(height: 20), - WaitingButton( - onTap: () async { + Button( + onPressed: () async { if (key.currentState == null) { return; } @@ -155,20 +265,36 @@ class AddEditStructurePage extends HookConsumerWidget { final value = isEdit ? await structureListNotifier.updateStructure( Structure( + id: structure.id, + shortId: shortId.text, name: name.text, + siegeAddressStreet: siegeAddressStreet.text, + siegeAddressCity: siegeAddressCity.text, + siegeAddressZipcode: siegeAddressZipcode.text, + siegeAddressCountry: siegeAddressCountry.text, + siret: siret.text, + iban: iban.text, + bic: bic.text, associationMembership: currentMembership.value, managerUser: structureManager, - id: structure.id, ), ) : await structureListNotifier.createStructure( Structure( + id: '', + shortId: shortId.text, name: name.text, + siegeAddressStreet: siegeAddressStreet.text, + siegeAddressCity: siegeAddressCity.text, + siegeAddressZipcode: siegeAddressZipcode.text, + siegeAddressCountry: siegeAddressCountry.text, + siret: siret.text, + iban: iban.text, + bic: bic.text, associationMembership: currentMembership.value, managerUser: structureManager, - id: '', ), ); if (value) { @@ -187,18 +313,11 @@ class AddEditStructurePage extends HookConsumerWidget { }); } }, - builder: (child) => AdminButton(child: child), - child: Text( - isEdit - ? localizeWithContext.adminEdit - : localizeWithContext.adminAdd, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - color: Colors.white, - ), - ), + text: isEdit + ? localizeWithContext.adminEdit + : localizeWithContext.adminAdd, ), + SizedBox(height: 80), ], ), ), diff --git a/lib/admin/ui/pages/structure_page/structure_page.dart b/lib/admin/ui/pages/structure_page/structure_page.dart index acd4273bc8..e0e4c13bfd 100644 --- a/lib/admin/ui/pages/structure_page/structure_page.dart +++ b/lib/admin/ui/pages/structure_page/structure_page.dart @@ -6,6 +6,7 @@ import 'package:titan/admin/router.dart'; import 'package:titan/admin/providers/structure_manager_provider.dart'; import 'package:titan/admin/providers/structure_provider.dart'; import 'package:titan/paiement/class/structure.dart'; +import 'package:titan/paiement/providers/bank_account_holder_provider.dart'; import 'package:titan/paiement/providers/structure_list_provider.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; @@ -20,12 +21,17 @@ import 'package:titan/user/class/simple_users.dart'; import 'package:titan/user/providers/user_list_provider.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/l10n/app_localizations.dart'; +import 'package:tuple/tuple.dart'; class StructurePage extends HookConsumerWidget { const StructurePage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { + final bankAccountHolder = ref.watch(bankAccountHolderProvider); + final bankAccountHolderNotifier = ref.watch( + bankAccountHolderProvider.notifier, + ); final structures = ref.watch(structureListProvider); final structuresNotifier = ref.watch(structureListProvider.notifier); final structureNotifier = ref.watch(structureProvider.notifier); @@ -78,9 +84,9 @@ class StructurePage extends HookConsumerWidget { ], ), const SizedBox(height: 30), - AsyncChild( - value: structures, - builder: (context, structures) { + Async2Children( + values: Tuple2(structures, bankAccountHolder), + builder: (context, structures, bankAccountHolder) { structures.sort( (a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase()), @@ -89,89 +95,134 @@ class StructurePage extends HookConsumerWidget { children: [ Column( children: [ + Text( + bankAccountHolder.holderStructureId == "" + ? localizeWithContext + .adminUndefinedBankAccountHolder + : localizeWithContext.adminBankAccountHolder( + bankAccountHolder.holderStructure.name, + ), + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 10), ...structures.map( - (structure) => ListItem( - title: structure.name, - onTap: () async { - await showCustomBottomModal( - context: context, - ref: ref, - modal: BottomModalTemplate( - title: localizeWithContext - .adminUsersManagement, - child: Column( - children: [ - Button( - text: localizeWithContext.adminEdit, - onPressed: () { - structureNotifier.setStructure( - structure, - ); - structureManagerNotifier.setUser( - structure.managerUser, - ); + (structure) => Padding( + padding: const EdgeInsets.symmetric(vertical: 2), + child: ListItem( + title: structure.name, + subtitle: structure.managerUser.getName(), + onTap: () async { + await showCustomBottomModal( + context: context, + ref: ref, + modal: BottomModalTemplate( + title: structure.name, + child: Column( + children: [ + Button( + text: localizeWithContext.adminEdit, + onPressed: () { + structureNotifier.setStructure( + structure, + ); + structureManagerNotifier.setUser( + structure.managerUser, + ); - QR.to( - AdminRouter.root + - AdminRouter.structures + - AdminRouter.addEditStructure, - ); - Navigator.of(context).pop(); - }, - ), - const SizedBox(height: 10), - Button( - type: ButtonType.danger, - text: localizeWithContext.adminDelete, - onPressed: () async { - await showDialog( - context: context, - builder: (context) { - return CustomDialogBox( - title: AppLocalizations.of( - context, - )!.adminDeleting, - descriptions: - AppLocalizations.of( - context, - )!.adminDeleteGroup, - onYes: () async { - final deletedGroupMsg = - localizeWithContext - .adminDeletedGroup; - final deletingErrorMsg = - localizeWithContext - .adminDeletingError; - tokenExpireWrapper(ref, () async { - final value = - await structuresNotifier - .deleteStructure( - structure, - ); - if (value) { - displayToastWithContext( - TypeMsg.msg, - deletedGroupMsg, - ); - } else { - displayToastWithContext( - TypeMsg.error, - deletingErrorMsg, - ); - } - }); - Navigator.of(context).pop(); - }, + QR.to( + AdminRouter.root + + AdminRouter.structures + + AdminRouter + .addEditStructure, + ); + Navigator.of(context).pop(); + }, + ), + const SizedBox(height: 10), + Button( + text: localizeWithContext + .adminDefineAsBankAccountHolder, + onPressed: () async { + final value = + await bankAccountHolderNotifier + .updateBankAccountHolder( + structure.id, + ); + if (value) { + displayToastWithContext( + TypeMsg.msg, + localizeWithContext + .adminBankAccountHolderModified, + ); + } else { + displayToastWithContext( + TypeMsg.error, + localizeWithContext + .adminError, ); - }, - ); - }, - ), - ], + } + }, + ), + const SizedBox(height: 10), + Button( + type: ButtonType.danger, + text: + localizeWithContext.adminDelete, + onPressed: () async { + await showDialog( + context: context, + builder: (context) { + return CustomDialogBox( + title: AppLocalizations.of( + context, + )!.adminDeleting, + descriptions: + AppLocalizations.of( + context, + )!.adminDeleteGroup, + onYes: () async { + final deletedGroupMsg = + localizeWithContext + .adminDeletedGroup; + final deletingErrorMsg = + localizeWithContext + .adminDeletingError; + tokenExpireWrapper(ref, () async { + final value = + await structuresNotifier + .deleteStructure( + structure, + ); + if (value) { + displayToastWithContext( + TypeMsg.msg, + deletedGroupMsg, + ); + } else { + displayToastWithContext( + TypeMsg.error, + deletingErrorMsg, + ); + } + }); + Navigator.of( + context, + ).pop(); + }, + ); + }, + ); + }, + ), + ], + ), ), - ), - ); - }, + ); + }, + ), ), ), const SizedBox(height: 20), diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 8ddc0d9cf8..e32df92b73 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -46,11 +46,27 @@ "adminAssociationMembership": "Membership", "adminAssociationMembershipName": "Membership name", "adminAssociationsMemberships": "Memberships", + "adminBankAccountHolder": "Bank account holder: {bankAccountHolder}", + "@adminBankAccountHolder": { + "description": "Displays the bank account holder's name", + "placeholders": { + "bankAccountHolder": { + "type": "String" + } + } + }, + "adminBankAccountHolderModified": "Bank account holder modified", + "adminBankDetails": "Bank details", + "adminBic": "BIC", + "adminBicError": "BIC must be 11 characters", + "adminCity": "City", "adminClearFilters": "Clear filters", + "adminCountry": "Country", "adminCreateAssociationMembership": "Create membership", "adminCreatedAssociationMembership": "Membership created", "adminCreationError": "Error during creation", "adminDateError": "Start date must be before end date", + "adminDefineAsBankAccountHolder": "Define as bank account holder", "adminDelete": "Delete", "adminDeleteAssociationMembership": "Delete membership?", "adminDeletedAssociationMembership": "Membership deleted", @@ -75,6 +91,8 @@ "adminFilters": "Filters", "adminGroup": "Group", "adminGroups": "Groups", + "adminIban": "IBAN", + "adminIbanError": "IBAN must be 27 characters", "adminLoaningGroup": "Loaning group", "adminLooking": "Searching", "adminManager": "Structure administrator", @@ -93,10 +111,17 @@ "adminRemoveGroupMember": "Remove member from group?", "adminResearch": "Search", "adminSchools": "Schools", + "adminShortId": "Short ID (3 letters)", + "adminShortIdError": "Short ID must be 3 characters", + "adminSiegeAddress": "Head office address", + "adminSiret": "SIRET", + "adminSiretError": "SIRET must be 14 digits", + "adminStreet": "Street and number", "adminStructures": "Structures", "adminStartDate": "Start date", "adminStartDateMaximal": "Maximum start date", "adminStartDateMinimal": "Minimum start date", + "adminUndefinedBankAccountHolder": "Bank account holder not defined", "adminUpdatedAssociationMembership": "Membership updated", "adminUpdatedGroup": "Group updated", "adminUpdatedMembership": "Membership updated", @@ -104,6 +129,7 @@ "adminUser": "User", "adminValidateFilters": "Apply filters", "adminVisibilities": "Visibilities", + "adminZipcode": "Zip code", "adminGroupNotification": "Group notifications", "adminNotifyGroup": "Send a notification", "adminTitle": "Title", @@ -166,6 +192,7 @@ "adminFailedToUpdateAssociationLogo": "Failed to update association logo", "adminChooseGroup": "Choose a group", "adminChooseAssociationManagerGroup": "Choose a group to manage this association", + "adminConfirm": "Confirm", "advertAdd": "Add", "advertAddedAdvert": "Advert published", "advertAddedAnnouncer": "Announcer added", @@ -711,6 +738,189 @@ "othersNoDateError": "Please enter a date", "othersImageSizeTooBig": "Image size must not exceed 4 MB", "othersImageError": "Error adding the image", + "paiementAccept": "Accept", + "paiementAccessPage": "Access the page", + "paiementAdd": "Add", + "paiementAddedSeller": "Seller added", + "paiementAddingSellerError": "Error while adding seller", + "paiementAddingStoreError": "Error while adding the store", + "paiementAddSeller": "Add seller", + "paiementAddStore": "Add store", + "paiementAddThisDevice": "Add this device", + "paiementAdmin": "Administrator", + "paiementAmount": "Amount", + "paiementAskDeviceActivation": "Device activation request", + "paiementAStore": "a store", + "paiementAt": "at", + "paiementAuthenticationRequired": "Authentication required to pay", + "paiementAuthentificationFailed": "Authentication failed", + "paiementBalanceAfterTopUp": "Balance after top-up:", + "paiementBalanceAfterTransaction": "Balance after payment: ", + "paiementBank": "Collect", + "paiementBillingSpace": "Billing page", + "paiementCameraPermissionRequired": "Camera permission required", + "paiementCameraPerssionRequiredDescription": "To scan a QR Code, you must allow camera access.", + "paiementCanBank": "Can collect payments", + "paiementCanCancelTransaction": "Can cancel transactions", + "paiementCancel": "Cancel", + "paiementCancelled": "Cancelled", + "paiementCancelledTransaction": "Payment cancelled", + "paiementCancelTransaction": "Cancel transaction", + "paiementCancelTransactions": "Cancel transactions", + "paiementCanManageSellers": "Can manage sellers", + "paiementCanSeeHistory": "Can view history", + "paiementClose": "Close", + "paiementCreate": "Create", + "paiementCreateInvoice": "Create new invoice", + "paiementDecline": "Decline", + "paiementDeletedSeller": "Seller deleted", + "paiementDeleteInvoice": "Delete invoice", + "paiementDeleteSeller": "Delete seller", + "paiementDeleteSellerDescription": "Are you sure you want to delete this seller?", + "paiementDeleteSuccessfully": "Successfully deleted", + "paiementDeleteStore": "Delete store", + "paiementDeleteStoreDescription": "Are you sure you want to delete this store?", + "paiementDeleteStoreError": "Unable to delete the store", + "paiementDeletingSellerError": "Error while deleting seller", + "paiementDeviceActivationReceived": "The activation request has been received, please check your email to finalize the process", + "paiementDeviceNotActivated": "Device not activated", + "paiementDeviceNotActivatedDescription": "Your device is not yet activated. \nTo activate it, please go to the devices page.", + "paiementDeviceNotRegistered": "Device not registered", + "paiementDeviceNotRegisteredDescription": "Your device is not registered yet. \nTo register it, please go to the devices page.", + "paiementDeviceRecoveryError": "Error while retrieving device", + "paiementDeviceRevoked": "Device revoked", + "paiementDeviceRevokingError": "Error while revoking device", + "paiementDevices": "Devices", + "paiementDoneTransaction": "Transaction completed", + "paiementDownload": "Download", + "paiementEditStore": "Edit store {store}", + "@paiementEditStore": { + "description": "Text to edit a store", + "placeholders": { + "store": { + "type": "String" + } + } + }, + "paiementErrorDeleting": "Error while deleting", + "paiementErrorUpdatingStatus": "Error while updating the status", + "paiementFromTo": "From {from} to {to}", + "@paiementFromTo": { + "description": "Text with a date range", + "placeholders": { + "from": { + "type": "DateTime", + "format": "yMd" + }, + "to": { + "type": "DateTime", + "format": "yMd" + } + } + }, + "paiementGetBalanceError": "Error while retrieving balance: ", + "paiementGetTransactionsError": "Error while retrieving transactions: ", + "paiementHandOver": "Handover", + "paiementHistory": "History", + "paiementInvoiceCreatedSuccessfully": "Invoice created successfully", + "paiementInvoices": "Invoices", + "paiementInvoicesPerPage": "{quantity} invoices/page", + "@paiementInvoicesPerPage": { + "description": "Text with the number of invoices per page", + "placeholders": { + "quantity": { + "type": "int" + } + } + }, + "paiementLastTransactions": "Latest transactions", + "paiementLimitedTo": "Limited to", + "paiementManagement": "Management", + "paiementManageSellers": "Manage sellers", + "paiementMarkPaid": "Mark as paid", + "paiementMarkReceived": "Mark as received", + "paiementMarkUnpaid": "Mark as unpaid", + "paiementMaxAmount": "The maximum wallet amount is", + "paiementMean": "Average: ", + "paiementModify": "Edit", + "paiementModifyingStoreError": "Error while updating the store", + "paiementModifySuccessfully": "Successfully modified", + "paiementNewCGU": "New Terms of Service", + "paiementNext": "Next", + "paiementNextAccountable": "Next responsible", + "paiementNoInvoiceToCreate": "No invoice to create", + "paiementNoMembership": "No membership", + "paiementNoMembershipDescription": "This product is not available to non-members. Confirm the payment?", + "paiementNoThanks": "No thanks", + "paiementNoTransaction": "No transaction", + "paiementNoTransactionForThisMonth": "No transactions for this month", + "paiementOf": "of", + "paiementPaid": "Paid", + "paiementPay": "Pay", + "paiementPayment": "Payment", + "paiementPayWithHA": "Pay with HelloAsso", + "paiementPending": "Pending", + "paiementPersonalBalance": "Personal balance", + "paiementPleaseAcceptPopup": "Please allow popups", + "paiementPleaseAcceptTOS": "Please accept the Terms of Service.", + "paiementPleaseAddDevice": "Please add this device to pay", + "paiementPleaseAuthenticate": "Please authenticate", + "paiementPleaseEnterMinAmount": "Please enter an amount greater than 1", + "paiementPleaseEnterValidAmount": "Please enter a valid amount", + "paiementProceedSuccessfully": "Payment completed successfully", + "paiementQRCodeAlreadyUsed": "QR Code already used", + "paiementReactivateRevokedDeviceDescription": "Your device has been revoked. \nTo reactivate it, please go to the devices page.", + "paiementReceived": "Received", + "paiementRefund": "Refund", + "paiementRefundAction": "Refund", + "paiementRefundedThe": "Refunded on", + "paiementRevokeDevice": "Revoke device?", + "paiementRevokeDeviceDescription": "You will no longer be able to use this device for payments", + "paiementRightsOf": "Rights of", + "paiementRightsUpdated": "Rights updated", + "paiementRightsUpdateError": "Error while updating rights", + "paiementScan": "Scan", + "paiementScanCode": "Scan a code", + "paiementSeeHistory": "View history", + "paiementSelectStructure": "Select a structure", + "paiementSellerError": "You are not a seller of this store", + "paiementSellerRigths": "Seller rights", + "paiementSellersOf": "Sellers of", + "paiementSettings": "Settings", + "paiementSpent": "Spent", + "paiementStats": "Stats", + "paiementStoreBalance": "Store balance", + "paiementStoreDeleted": "Store deleted", + "paiementStructureManagement": "{structure} management", + "@paiementStructureManagement": { + "description": "Gestion de la structure", + "placeholders": { + "structure": { + "type": "String" + } + } + }, + "paiementStoreName": "Store name", + "paiementStores": "Stores", + "paiementStructureAdmin": "Structure administrator", + "paiementSuccededTransaction": "Successful payment", + "paiementSuccessfullyAddedStore": "Store successfully added", + "paiementSuccessfullyModifiedStore": "Store successfully updated", + "paiementThe": "The", + "paiementThisDevice": "(this device)", + "paiementTopUp": "Top-up", + "paiementTopUpAction": "Top-up", + "paiementTotalDuringPeriod": "Total during the period", + "paiementTransaction": "Transaction", + "paiementTransactionCancelled": "Transaction cancelled", + "paiementTransactionCancelledDescription": "Are you sure you want to cancel the transaction of", + "paiementTransactionCancelledError": "Error while cancelling the transaction", + "paiementTransferStructure": "Structure transfer", + "paiementTransferStructureDescription": "The new manager will have access to all structure management features. You will receive an email to confirm this transfer. The link will only be active for 20 minutes. This action is irreversible. Are you sure you want to continue?", + "paiementTransferStructureError": "Error while transferring structure", + "paiementTransferStructureSuccess": "Structure transfer requested successfully", + "paiementValidUntil": "Valid until", + "paiementYouAreTransferingStructureTo": "You are about to transfer the structure to ", "phAddNewJournal": "Add a new journal", "phNameField": "Name: ", "phDateField": "Date: ", @@ -1365,129 +1575,5 @@ "moduleStyleGuideDescription": "Style guide for developers", "moduleAdminDescription": "Administration module for administrators", "moduleOthersDescription": "Other modules", - "modulePaymentDescription": "Pay and see your transactions", - "paiementTopUp": "Top-up", - "paiementStoreManagement": "Association management", - "paiementDeleteStore": "Delete association", - "paiementDeleteStoreDescription": "Are you sure you want to delete this association?", - "paiementDeleteStoreError": "Unable to delete the association", - "paiementStoreDeleted": "Association deleted", - "paiementAddThisDevice": "Add this device", - "paiementThisDevice": "(this device)", - "paiementCancelled": "Cancelled", - "paiementThe": "The", - "paiementOf": "of", - "paiementRefundedThe": "Refunded on", - "paiementAt": "at", - "paiementPleaseAcceptTOS": "Please accept the Terms of Service.", - "paiementAskDeviceActivation": "Device activation request", - "paiementDeviceActivationReceived": "The activation request has been received, please check your email to finalize the process", - "paiementRevokeDevice": "Revoke device?", - "paiementRevokeDeviceDescription": "You will no longer be able to use this device for payments", - "paiementDeviceRevoked": "Device revoked", - "paiementDeviceRevokingError": "Error while revoking device", - "paiementPleaseAcceptPopup": "Please allow popups", - "paiementProceedSuccessfully": "Payment completed successfully", - "paiementCancelledTransaction": "Payment cancelled", - "paiementPleaseEnterMinAmount": "Please enter an amount greater than 1", - "paiementMaxAmount": "The maximum wallet amount is", - "paiementPayWithHA": "Pay with HelloAsso", - "paiementBalanceAfterTopUp": "Balance after top-up:", - "paiementPersonalBalance": "Personal balance", - "paiementDevices": "Devices", - "paiementPay": "Pay", - "paiementDeviceNotRegistered": "Device not registered", - "paiementDeviceNotRegisteredDescription": "Your device is not registered yet. \nTo register it, please go to the devices page.", - "paiementAccessPage": "Access the page", - "paiementDeviceNotActivated": "Device not activated", - "paiementDeviceNotActivatedDescription": "Your device is not yet activated. \nTo activate it, please go to the devices page.", - "paiementReactivateRevokedDeviceDescription": "Your device has been revoked. \nTo reactivate it, please go to the devices page.", - "paiementDeviceRecoveryError": "Error while retrieving device", - "paiementStats": "Stats", - "paimentTopUpAction": "Top-up", - "paiementGetBalanceError": "Error while retrieving balance: ", - "paiementLastTransactions": "Latest transactions", - "paiementGetTransactionsError": "Error while retrieving transactions: ", - "paiementStoreBalance": "Association balance", - "paiementScan": "Scan", - "paiementManagement": "Management", - "paiementHistory": "History", - "paiementHandOver": "Handover", - "paiementStores": "Associations", - "paiementAdmin": "Administrator", - "paiementSuccededTransaction": "Successful payment", - "paiementNewCGU": "New Terms of Service", - "paiementDecline": "Decline", - "paiementAccept": "Accept", - "paiementAmount": "Amount", - "paiementValidUntil": "Valid until", - "paiementClose": "Close", - "paiementPleaseEnterValidAmount": "Please enter a valid amount", - "paiementPleaseAuthenticate": "Please authenticate", - "paiementAthenticationRequired": "Authentication required to pay", - "paiementNoThanks": "No thanks", - "paiementAuthentificationFailed": "Authentication failed", - "paiementPleaseAddDevice": "Please add this device to pay", - "paiementPayment": "Payment", - "paiementBalanceAfterTransaction": "Balance after payment: ", - "paiementCancel": "Cancel", - "paiementLimitedTo": "Limited to", - "paiementScanCode": "Scan a code", - "paiementNext": "Next", - "paiementCancelTransaction": "Cancel transaction", - "paiementTransactionCancelled": "Transaction cancelled", - "paiementTransactionCancelledDescription": "Are you sure you want to cancel the transaction of", - "paiementTransactionCancelledError": "Error while cancelling the transaction", - "paiementNoMembership": "No membership", - "paiementNoMembershipDescription": "This product is not available to non-members. Confirm the payment?", - "paiementQRCodeAlreadyUsed": "QR Code already used", - "paiementCameraPermissionRequired": "Camera permission required", - "paiementCameraPerssionRequiredDescription": "To scan a QR Code, you must allow camera access.", - "paiementSettings": "Settings", - "paiementReceived": "Received", - "paiementSpent": "Spent", - "paiementNoTrasactionForThisMonth": "No transactions for this month", - "paiementNoTransactinon": "No transaction", - "paiementSellerRigths": "Seller rights", - "paiementCanBank": "Can collect payments", - "paiementCanSeeHistory": "Can view history", - "paiementCanCancelTransaction": "Can cancel transactions", - "paiementCanManageSellers": "Can manage sellers", - "paiementAddedSeller": "Seller added", - "paiementAddingSellerError": "Error while adding seller", - "paiementBank": "Collect", - "paiementSeeHistory": "View history", - "paiementCancelTransactions": "Cancel transactions", - "paiementManageSellers": "Manage sellers", - "paiementStructureAdmin": "Structure administrator", - "paiementRightsOf": "Rights of", - "paiementRightsUpdated": "Rights updated", - "paiementRightsUpdateError": "Error while updating rights", - "paiementDeleteSellerDescription": "Are you sure you want to delete this seller?", - "paiementDeletedSeller": "Seller deleted", - "paiementDeletingSellerError": "Error while deleting seller", - "paiementDeleteSeller": "Delete seller", - "paiementAdd": "Add", - "paiementAddSeller": "Add seller", - "paiementSellerError": "You are not a seller of this association", - "paiementSellersOf": "Sellers of", - "paiementModify": "Edit", - "paiementAStore": "an association", - "paiementStoreName": "Association name", - "paiementSuccessfullyAddedStore": "Association successfully added", - "paiementSuccessfullyModifiedStore": "Association successfully updated", - "paiementAddingStoreError": "Error while adding the association", - "paiementModifyingStoreError": "Error while updating the association", - "paiementRefund": "Refund", - "paiementDoneTransaction": "Transaction completed", - "paiementRefundAction": "Refund", - "paiementTotalDuringPeriod": "Total during the period", - "paiementMean": "Average: ", - "paiementTransaction": "Transaction", - "paiementTransferStructure": "Structure transfer", - "paiementYouAreTransferingStructureTo": "You are about to transfer the structure to ", - "paiementTransferStructureDescription": "The new manager will have access to all structure management features. You will receive an email to confirm this transfer. The link will only be active for 20 minutes. This action is irreversible. Are you sure you want to continue?", - "paiementTransferStructureError": "Error while transferring structure", - "paiementTransferStructureSuccess": "Structure transfer requested successfully", - "paiementNextAccountable": "Next responsible" + "modulePaymentDescription": "Pay and see your transactions" } diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 68c10ef647..2591ab2bf6 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -46,11 +46,27 @@ "adminAssociationMembership": "Adhésion", "adminAssociationMembershipName": "Nom de l'adhésion", "adminAssociationsMemberships": "Adhésions", + "adminBankAccountHolder": "Titulaire du compte bancaire : {bankAccountHolder}", + "@adminBankAccountHolder": { + "description": "Displays the bank account holder's name", + "placeholders": { + "bankAccountHolder": { + "type": "String" + } + } + }, + "adminBankAccountHolderModified": "Titulaire du compte bancaire modifié", + "adminBankDetails": "Coordonnées bancaires", + "adminBic": "BIC", + "adminBicError": "Le BIC doit faire 11 caractères", + "adminCity": "Ville", "adminClearFilters": "Effacer les filtres", + "adminCountry": "Pays", "adminCreateAssociationMembership": "Créer une adhésion", "adminCreatedAssociationMembership": "Adhésion créée", "adminCreationError": "Erreur lors de la création", "adminDateError": "La date de début doit être avant la date de fin", + "adminDefineAsBankAccountHolder": "Définir comme titulaire du compte bancaire", "adminDelete": "Supprimer", "adminDeleteAssociationMembership": "Supprimer l'adhésion ?", "adminDeletedAssociationMembership": "Adhésion supprimée", @@ -75,6 +91,8 @@ "adminFilters": "Filtres", "adminGroup": "Groupe", "adminGroups": "Groupes", + "adminIban": "IBAN", + "adminIbanError": "L'IBAN doit faire 27 caractères", "adminLoaningGroup": "Groupe de prêt", "adminLooking": "Recherche", "adminManager": "Administrateur de la structure", @@ -93,10 +111,17 @@ "adminRemoveGroupMember": "Supprimer le membre du groupe ?", "adminResearch": "Recherche", "adminSchools": "Écoles", + "adminShortId": "Short ID (3 lettres)", + "adminShortIdError": "Le short ID doit faire 3 caractères", + "adminSiegeAddress": "Adresse du siège", + "adminSiret": "SIRET", + "adminSiretError": "SIRET must be 14 digits", + "adminStreet": "Numéro et rue", "adminStructures": "Structures", "adminStartDate": "Date de début", "adminStartDateMaximal": "Date de début maximale", "adminStartDateMinimal": "Date de début minimale", + "adminUndefinedBankAccountHolder": "Titulaire du compte bancaire non défini", "adminUpdatedAssociationMembership": "Adhésion modifiée", "adminUpdatedGroup": "Groupe modifié", "adminUpdatedMembership": "Adhésion modifiée", @@ -104,6 +129,7 @@ "adminUser": "Utilisateur", "adminValidateFilters": "Valider les filtres", "adminVisibilities": "Visibilités", + "adminZipcode": "Code postal", "adminGroupNotification": "Notification de groupe", "adminNotifyGroup": "Notifier le groupe {groupName}", "@adminNotifyGroup": { @@ -174,6 +200,7 @@ "adminFailedToUpdateAssociationLogo": "Échec de la mise à jour du logo de l'association", "adminChooseGroup": "Choisir un groupe", "adminChooseAssociationManagerGroup": "Choisir un groupe gestionnaire pour l'association", + "adminConfirm": "Valider", "advertAdd": "Ajouter", "advertAddedAdvert": "Annonce publiée", "advertAddedAnnouncer": "Annonceur ajouté", @@ -719,6 +746,189 @@ "othersNoDateError": "Veuillez entrer une date", "othersImageSizeTooBig": "La taille de l'image ne doit pas dépasser 4 Mio", "othersImageError": "Erreur lors de l'ajout de l'image", + "paiementAccept": "Accepter", + "paiementAccessPage": "Accéder à la page", + "paiementAdd": "Ajouter", + "paiementAddedSeller": "Vendeur ajouté", + "paiementAddingSellerError": "Erreur lors de l'ajout du vendeur", + "paiementAddingStoreError": "Erreur lors de l'ajout du magasin", + "paiementAddSeller": "Ajouter un vendeur", + "paiementAddStore": "Ajouter un magasin", + "paiementAddThisDevice": "Ajouter cet appareil", + "paiementAdmin": "Administrateur", + "paiementAmount": "Montant", + "paiementAskDeviceActivation": "Demande d'activation de l'appareil", + "paiementAStore": "un magasin", + "paiementAt": "à", + "paiementAuthenticationRequired": "Authentification requise pour payer", + "paiementAuthentificationFailed": "Échec de l'authentification", + "paiementBalanceAfterTopUp": "Solde après recharge :", + "paiementBalanceAfterTransaction": "Solde après paiement : ", + "paiementBank": "Encaisser", + "paiementBillingSpace": "Espace facturation", + "paiementCameraPermissionRequired": "Permission d'accès à la caméra requise", + "paiementCameraPerssionRequiredDescription": "Pour scanner un QR Code, vous devez autoriser l'accès à la caméra.", + "paiementCanBank": "Peut encaisser", + "paiementCanCancelTransaction": "Peut annuler des transactions", + "paiementCancel": "Annuler", + "paiementCancelled": "Annulé", + "paiementCancelledTransaction": "Paiement annulé", + "paiementCancelTransaction": "Annuler la transaction", + "paiementCancelTransactions": "Annuler les transactions", + "paiementCanManageSellers": "Peut gérer les vendeurs", + "paiementCanSeeHistory": "Peut voir l'historique", + "paiementClose": "Fermer", + "paiementCreate": "Créer", + "paiementCreateInvoice": "Créer une facture", + "paiementDecline": "Refuser", + "paiementDeletedSeller": "Vendeur supprimé", + "paiementDeleteInvoice": "Supprimer la facture", + "paiementDeleteSeller": "Supprimer le vendeur", + "paiementDeleteSellerDescription": "Voulez-vous vraiment supprimer ce vendeur ?", + "paiementDeleteSuccessfully": "Supprimé avec succès", + "paiementDeleteStore": "Supprimer le magasin", + "paiementDeleteStoreDescription": "Voulez-vous vraiment supprimer ce magasin ?", + "paiementDeleteStoreError": "Impossible de supprimer le magasin", + "paiementDeletingSellerError": "Erreur lors de la suppression du vendeur", + "paiementDeviceActivationReceived": "La demande d'activation est prise en compte, veuilliez consulter votre boite mail pour finaliser la démarche", + "paiementDeviceNotActivated": "Appareil non activé", + "paiementDeviceNotActivatedDescription": "Votre appareil n'est pas encore activé. \nPour l'activer, veuillez vous rendre sur la page des appareils.", + "paiementDeviceNotRegistered": "Appareil non enregistré", + "paiementDeviceNotRegisteredDescription": "Votre appareil n'est pas encore enregistré. \nPour l'enregistrer, veuillez vous rendre sur la page des appareils.", + "paiementDeviceRecoveryError": "Erreur lors de la récupération de l'appareil", + "paiementDeviceRevoked": "Appareil révoqué", + "paiementDeviceRevokingError": "Erreur lors de la révocation de l'appareil", + "paiementDevices": "Appareils", + "paiementDoneTransaction": "Transaction effectuée", + "paiementDownload": "Télécharger", + "paiementEditStore": "Modifier le magasin {store}", + "@paiementEditStore": { + "description": "Modifier le magasin", + "placeholders": { + "store": { + "type": "String" + } + } + }, + "paiementErrorDeleting": "Erreur lors de la suppression", + "paiementErrorUpdatingStatus": "Erreur lors de la mise à jour du statut", + "paiementFromTo": "Du {from} au {to}", + "@paiementFromTo": { + "description": "Text with a date range", + "placeholders": { + "from": { + "type": "DateTime", + "format": "yMd" + }, + "to": { + "type": "DateTime", + "format": "yMd" + } + } + }, + "paiementGetBalanceError": "Erreur lors de la récupération du solde : ", + "paiementGetTransactionsError": "Erreur lors de la récupération des transactions : ", + "paiementHandOver": "Passation", + "paiementHistory": "Historique", + "paiementInvoiceCreatedSuccessfully": "Facture créée avec succès", + "paiementInvoices": "Factures", + "paiementInvoicesPerPage": "{quantity} factures/page", + "@paiementInvoicesPerPage": { + "description": "Text with the number of invoices per page", + "placeholders": { + "quantity": { + "type": "int" + } + } + }, + "paiementLastTransactions": "Dernières transactions", + "paiementLimitedTo": "Limité à", + "paiementManagement": "Gestion", + "paiementManageSellers": "Gérer les vendeurs", + "paiementMarkPaid": "Marquer comme payé", + "paiementMarkReceived": "Marquer comme reçu", + "paiementMarkUnpaid": "Marquer comme non payé", + "paiementMaxAmount": "Le montant maximum de votre portefeuille est de", + "paiementMean": "Moyenne : ", + "paiementModify": "Modifier", + "paiementModifyingStoreError": "Erreur lors de la modification du magasin", + "paiementModifySuccessfully": "Modifié avec succès", + "paiementNewCGU": "Nouvelles Conditions Générales d'Utilisation", + "paiementNext": "Suivant", + "paiementNextAccountable": "Prochain responsable", + "paiementNoInvoiceToCreate": "Aucune facture à créer", + "paiementNoMembership": "Aucune adhésion", + "paiementNoMembershipDescription": "Ce produit n'est pas disponnible pour les non-adhérents. Confirmer l'encaissement ?", + "paiementNoThanks": "Non merci", + "paiementNoTransaction": "Aucune transaction", + "paiementNoTransactionForThisMonth": "Aucune transaction pour ce mois", + "paiementOf": "de", + "paiementPaid": "Payé", + "paiementPay": "Payer", + "paiementPayment": "Paiement", + "paiementPayWithHA": "Payer avec HelloAsso", + "paiementPending": "En attente", + "paiementPersonalBalance": "Solde personnel", + "paiementPleaseAcceptPopup": "Veuillez autoriser les popups", + "paiementPleaseAcceptTOS": "Veuillez accepter les Conditions Générales d'Utilisation.", + "paiementPleaseAddDevice": "Veuillez ajouter cet appareil pour payer", + "paiementPleaseAuthenticate": "Veuillez vous authentifier", + "paiementPleaseEnterMinAmount": "Veuillez entrer un montant supérieur à 1", + "paiementPleaseEnterValidAmount": "Veuillez entrer un montant valide", + "paiementProceedSuccessfully": "Paiement effectué avec succès", + "paiementQRCodeAlreadyUsed": "QR Code déjà utilisé", + "paiementReactivateRevokedDeviceDescription": "Votre appareil a été révoqué. \nPour le réactiver, veuillez vous rendre sur la page des appareils.", + "paiementReceived": "Reçu", + "paiementRefund": "Remboursement", + "paiementRefundAction": "Rembourser", + "paiementRefundedThe": "Remboursé le", + "paiementRevokeDevice": "Révoquer l'appareil ?", + "paiementRevokeDeviceDescription": "Vous ne pourrez plus utiliser cet appareil pour les paiements", + "paiementRightsOf": "Droits de", + "paiementRightsUpdated": "Droits mis à jour", + "paiementRightsUpdateError": "Erreur lors de la mise à jour des droits", + "paiementScan": "Scanner", + "paiementScanCode": "Scanner un code", + "paiementSeeHistory": "Voir l'historique", + "paiementSelectStructure": "Choisir une structure", + "paiementSellerError": "Vous n'êtes pas vendeur de ce magasin", + "paiementSellerRigths": "Droits du vendeur", + "paiementSellersOf": "Les vendeurs de", + "paiementSettings": "Paramètres", + "paiementSpent": "Déboursé", + "paiementStats": "Stats", + "paiementStoreBalance": "Solde du magasin", + "paiementStoreDeleted": "Magasin supprimée", + "paiementStructureManagement": "Gestion de {structure}", + "@paiementStructureManagement": { + "description": "Gestion de la structure", + "placeholders": { + "structure": { + "type": "String" + } + } + }, + "paiementStoreName": "Nom du magasin", + "paiementStores": "Magasins", + "paiementStructureAdmin": "Administrateur de la structure", + "paiementSuccededTransaction": "Paiement réussi", + "paiementSuccessfullyAddedStore": "Magasin ajoutée avec succès", + "paiementSuccessfullyModifiedStore": "Magasin modifiée avec succès", + "paiementThe": "Le", + "paiementThisDevice": "(cet appareil)", + "paiementTopUp": "Recharge", + "paiementTopUpAction": "Recharger", + "paiementTotalDuringPeriod": "Total sur la période", + "paiementTransaction": "ransaction", + "paiementTransactionCancelled": "Transaction annulée", + "paiementTransactionCancelledDescription": "Voulez-vous vraiment annuler la transaction de", + "paiementTransactionCancelledError": "Erreur lors de l'annulation de la transaction", + "paiementTransferStructure": "Transfert de structure", + "paiementTransferStructureDescription": "Le nouveau responsable aura accès à toutes les fonctionnalités de gestion de la structure. Vous allez recevoir un email pour valider ce transfert. Le lien ne sera actif que pendant 20 minutes. Cette action est irréversible. Êtes-vous sûr de vouloir continuer ?", + "paiementTransferStructureError": "Erreur lors du transfert de la structure", + "paiementTransferStructureSuccess": "Transfert de structure demandé avec succès", + "paiementValidUntil": "Valide jusqu'à", + "paiementYouAreTransferingStructureTo": "Vous êtes sur le point de transférer la structure à ", "phAddNewJournal": "Ajouter un nouveau journal", "phNameField": "Nom : ", "phDateField": "Date : ", @@ -1372,129 +1582,5 @@ "moduleOthers": "Autres", "moduleOthersDescription": "Afficher les autres modules", "modulePayment": "Paiement", - "modulePaymentDescription": "Gérer les paiements, les statistiques et les appareils", - "paiementTopUp": "Recharge", - "paiementStoreManagement": "Gestion des associations", - "paiementDeleteStore": "Supprimer l'association", - "paiementDeleteStoreDescription": "Voulez-vous vraiment supprimer cette association ?", - "paiementDeleteStoreError": "Impossible de supprimer l'association", - "paiementStoreDeleted": "Association supprimée", - "paiementAddThisDevice": "Ajouter cet appareil", - "paiementThisDevice": "(cet appareil)", - "paiementCancelled": "Annulé", - "paiementThe": "Le", - "paiementOf": "de", - "paiementRefundedThe": "Remboursé le", - "paiementAt": "à", - "paiementPleaseAcceptTOS": "Veuillez accepter les Conditions Générales d'Utilisation.", - "paiementAskDeviceActivation": "Demande d'activation de l'appareil", - "paiementDeviceActivationReceived": "La demande d'activation est prise en compte, veuilliez consulter votre boite mail pour finaliser la démarche", - "paiementRevokeDevice": "Révoquer l'appareil ?", - "paiementRevokeDeviceDescription": "Vous ne pourrez plus utiliser cet appareil pour les paiements", - "paiementDeviceRevoked": "Appareil révoqué", - "paiementDeviceRevokingError": "Erreur lors de la révocation de l'appareil", - "paiementPleaseAcceptPopup": "Veuillez autoriser les popups", - "paiementProceedSuccessfully": "Paiement effectué avec succès", - "paiementCancelledTransaction": "Paiement annulé", - "paiementPleaseEnterMinAmount": "Veuillez entrer un montant supérieur à 1", - "paiementMaxAmount": "Le montant maximum de votre portefeuille est de", - "paiementPayWithHA": "Payer avec HelloAsso", - "paiementBalanceAfterTopUp": "Solde après recharge :", - "paiementPersonalBalance": "Solde personnel", - "paiementDevices": "Appareils", - "paiementPay": "Payer", - "paiementDeviceNotRegistered": "Appareil non enregistré", - "paiementDeviceNotRegisteredDescription": "Votre appareil n'est pas encore enregistré. \nPour l'enregistrer, veuillez vous rendre sur la page des appareils.", - "paiementAccessPage": "Accéder à la page", - "paiementDeviceNotActivated": "Appareil non activé", - "paiementDeviceNotActivatedDescription": "Votre appareil n'est pas encore activé. \nPour l'activer, veuillez vous rendre sur la page des appareils.", - "paiementReactivateRevokedDeviceDescription": "Votre appareil a été révoqué. \nPour le réactiver, veuillez vous rendre sur la page des appareils.", - "paiementDeviceRecoveryError": "Erreur lors de la récupération de l'appareil", - "paiementStats": "Stats", - "paimentTopUpAction": "Recharger", - "paiementGetBalanceError": "Erreur lors de la récupération du solde : ", - "paiementLastTransactions": "Dernières transactions", - "paiementGetTransactionsError": "Erreur lors de la récupération des transactions : ", - "paiementStoreBalance": "Solde associatif", - "paiementScan": "Scanner", - "paiementManagement": "Gestion", - "paiementHistory": "Historique", - "paiementHandOver": "Passation", - "paiementStores": "Associations", - "paiementAdmin": "Administrateur", - "paiementSuccededTransaction": "Paiement réussi", - "paiementNewCGU": "Nouvelles Conditions Générales d'Utilisation", - "paiementDecline": "Refuser", - "paiementAccept": "Accepter", - "paiementAmount": "Montant", - "paiementValidUntil": "Valide jusqu'à", - "paiementClose": "Fermer", - "paiementPleaseEnterValidAmount": "Veuillez entrer un montant valide", - "paiementPleaseAuthenticate": "Veuillez vous authentifier", - "paiementAthenticationRequired": "Authentification requise pour payer", - "paiementNoThanks": "Non merci", - "paiementAuthentificationFailed": "Échec de l'authentification", - "paiementPleaseAddDevice": "Veuillez ajouter cet appareil pour payer", - "paiementPayment": "Paiement", - "paiementBalanceAfterTransaction": "Solde après paiement : ", - "paiementCancel": "Annuler", - "paiementLimitedTo": "Limité à", - "paiementScanCode": "Scanner un code", - "paiementNext": "Suivant", - "paiementCancelTransaction": "Annuler la transaction", - "paiementTransactionCancelled": "Transaction annulée", - "paiementTransactionCancelledDescription": "Voulez-vous vraiment annuler la transaction de", - "paiementTransactionCancelledError": "Erreur lors de l'annulation de la transaction", - "paiementNoMembership": "Aucune adhésion", - "paiementNoMembershipDescription": "Ce produit n'est pas disponnible pour les non-adhérents. Confirmer l'encaissement ?", - "paiementQRCodeAlreadyUsed": "QR Code déjà utilisé", - "paiementCameraPermissionRequired": "Permission d'accès à la caméra requise", - "paiementCameraPerssionRequiredDescription": "Pour scanner un QR Code, vous devez autoriser l'accès à la caméra.", - "paiementSettings": "Paramètres", - "paiementReceived": "Reçu", - "paiementSpent": "Déboursé", - "paiementNoTrasactionForThisMonth": "Aucune transaction pour ce mois", - "paiementNoTransactinon": "Aucune transaction", - "paiementSellerRigths": "Droits du vendeur", - "paiementCanBank": "Peut encaisser", - "paiementCanSeeHistory": "Peut voir l'historique", - "paiementCanCancelTransaction": "Peut annuler des transactions", - "paiementCanManageSellers": "Peut gérer les vendeurs", - "paiementAddedSeller": "Vendeur ajouté", - "paiementAddingSellerError": "Erreur lors de l'ajout du vendeur", - "paiementBank": "Encaisser", - "paiementSeeHistory": "Voir l'historique", - "paiementCancelTransactions": "Annuler les transactions", - "paiementManageSellers": "Gérer les vendeurs", - "paiementStructureAdmin": "Administrateur de la structure", - "paiementRightsOf": "Droits de", - "paiementRightsUpdated": "Droits mis à jour", - "paiementRightsUpdateError": "Erreur lors de la mise à jour des droits", - "paiementDeleteSellerDescription": "Voulez-vous vraiment supprimer ce vendeur ?", - "paiementDeletedSeller": "Vendeur supprimé", - "paiementDeletingSellerError": "Erreur lors de la suppression du vendeur", - "paiementDeleteSeller": "Supprimer le vendeur", - "paiementAdd": "Ajouter", - "paiementAddSeller": "Ajouter un vendeur", - "paiementSellerError": "Vous n'êtes pas vendeur de cette association", - "paiementSellersOf": "Les vendeurs de", - "paiementModify": "Modifier", - "paiementAStore": "une association", - "paiementStoreName": "Nom de l'association", - "paiementSuccessfullyAddedStore": "Association ajoutée avec succès", - "paiementSuccessfullyModifiedStore": "Association modifiée avec succès", - "paiementAddingStoreError": "Erreur lors de l'ajout de l'association", - "paiementModifyingStoreError": "Erreur lors de la modification de l'association", - "paiementRefund": "Remboursement", - "paiementDoneTransaction": "Transaction effectuée", - "paiementRefundAction": "Rembourser", - "paiementTotalDuringPeriod": "Total sur la période", - "paiementMean": "Moyenne : ", - "paiementTransaction": "ransaction", - "paiementTransferStructure": "Transfert de structure", - "paiementYouAreTransferingStructureTo": "Vous êtes sur le point de transférer la structure à ", - "paiementTransferStructureDescription": "Le nouveau responsable aura accès à toutes les fonctionnalités de gestion de la structure. Vous allez recevoir un email pour valider ce transfert. Le lien ne sera actif que pendant 20 minutes. Cette action est irréversible. Êtes-vous sûr de vouloir continuer ?", - "paiementTransferStructureError": "Erreur lors du transfert de la structure", - "paiementTransferStructureSuccess": "Transfert de structure demandé avec succès", - "paiementNextAccountable": "Prochain responsable" + "modulePaymentDescription": "Gérer les paiements, les statistiques et les appareils" } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index d3437ed222..3d98877afa 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -374,12 +374,54 @@ abstract class AppLocalizations { /// **'Adhésions'** String get adminAssociationsMemberships; + /// Displays the bank account holder's name + /// + /// In fr, this message translates to: + /// **'Titulaire du compte bancaire : {bankAccountHolder}'** + String adminBankAccountHolder(String bankAccountHolder); + + /// No description provided for @adminBankAccountHolderModified. + /// + /// In fr, this message translates to: + /// **'Titulaire du compte bancaire modifié'** + String get adminBankAccountHolderModified; + + /// No description provided for @adminBankDetails. + /// + /// In fr, this message translates to: + /// **'Coordonnées bancaires'** + String get adminBankDetails; + + /// No description provided for @adminBic. + /// + /// In fr, this message translates to: + /// **'BIC'** + String get adminBic; + + /// No description provided for @adminBicError. + /// + /// In fr, this message translates to: + /// **'Le BIC doit faire 11 caractères'** + String get adminBicError; + + /// No description provided for @adminCity. + /// + /// In fr, this message translates to: + /// **'Ville'** + String get adminCity; + /// No description provided for @adminClearFilters. /// /// In fr, this message translates to: /// **'Effacer les filtres'** String get adminClearFilters; + /// No description provided for @adminCountry. + /// + /// In fr, this message translates to: + /// **'Pays'** + String get adminCountry; + /// No description provided for @adminCreateAssociationMembership. /// /// In fr, this message translates to: @@ -404,6 +446,12 @@ abstract class AppLocalizations { /// **'La date de début doit être avant la date de fin'** String get adminDateError; + /// No description provided for @adminDefineAsBankAccountHolder. + /// + /// In fr, this message translates to: + /// **'Définir comme titulaire du compte bancaire'** + String get adminDefineAsBankAccountHolder; + /// No description provided for @adminDelete. /// /// In fr, this message translates to: @@ -548,6 +596,18 @@ abstract class AppLocalizations { /// **'Groupes'** String get adminGroups; + /// No description provided for @adminIban. + /// + /// In fr, this message translates to: + /// **'IBAN'** + String get adminIban; + + /// No description provided for @adminIbanError. + /// + /// In fr, this message translates to: + /// **'L\'IBAN doit faire 27 caractères'** + String get adminIbanError; + /// No description provided for @adminLoaningGroup. /// /// In fr, this message translates to: @@ -656,6 +716,42 @@ abstract class AppLocalizations { /// **'Écoles'** String get adminSchools; + /// No description provided for @adminShortId. + /// + /// In fr, this message translates to: + /// **'Short ID (3 lettres)'** + String get adminShortId; + + /// No description provided for @adminShortIdError. + /// + /// In fr, this message translates to: + /// **'Le short ID doit faire 3 caractères'** + String get adminShortIdError; + + /// No description provided for @adminSiegeAddress. + /// + /// In fr, this message translates to: + /// **'Adresse du siège'** + String get adminSiegeAddress; + + /// No description provided for @adminSiret. + /// + /// In fr, this message translates to: + /// **'SIRET'** + String get adminSiret; + + /// No description provided for @adminSiretError. + /// + /// In fr, this message translates to: + /// **'SIRET must be 14 digits'** + String get adminSiretError; + + /// No description provided for @adminStreet. + /// + /// In fr, this message translates to: + /// **'Numéro et rue'** + String get adminStreet; + /// No description provided for @adminStructures. /// /// In fr, this message translates to: @@ -680,6 +776,12 @@ abstract class AppLocalizations { /// **'Date de début minimale'** String get adminStartDateMinimal; + /// No description provided for @adminUndefinedBankAccountHolder. + /// + /// In fr, this message translates to: + /// **'Titulaire du compte bancaire non défini'** + String get adminUndefinedBankAccountHolder; + /// No description provided for @adminUpdatedAssociationMembership. /// /// In fr, this message translates to: @@ -722,6 +824,12 @@ abstract class AppLocalizations { /// **'Visibilités'** String get adminVisibilities; + /// No description provided for @adminZipcode. + /// + /// In fr, this message translates to: + /// **'Code postal'** + String get adminZipcode; + /// No description provided for @adminGroupNotification. /// /// In fr, this message translates to: @@ -4220,4175 +4328,4307 @@ abstract class AppLocalizations { /// **'Erreur lors de l\'ajout de l\'image'** String get othersImageError; - /// No description provided for @phAddNewJournal. + /// No description provided for @paiementAccept. /// /// In fr, this message translates to: - /// **'Ajouter un nouveau journal'** - String get phAddNewJournal; + /// **'Accepter'** + String get paiementAccept; - /// No description provided for @phNameField. + /// No description provided for @paiementAccessPage. /// /// In fr, this message translates to: - /// **'Nom : '** - String get phNameField; + /// **'Accéder à la page'** + String get paiementAccessPage; - /// No description provided for @phDateField. + /// No description provided for @paiementAdd. /// /// In fr, this message translates to: - /// **'Date : '** - String get phDateField; + /// **'Ajouter'** + String get paiementAdd; - /// No description provided for @phDelete. + /// No description provided for @paiementAddedSeller. /// /// In fr, this message translates to: - /// **'Voulez-vous vraiment supprimer ce journal ?'** - String get phDelete; + /// **'Vendeur ajouté'** + String get paiementAddedSeller; - /// No description provided for @phIrreversibleAction. + /// No description provided for @paiementAddingSellerError. /// /// In fr, this message translates to: - /// **'Cette action est irréversible'** - String get phIrreversibleAction; + /// **'Erreur lors de l\'ajout du vendeur'** + String get paiementAddingSellerError; - /// No description provided for @phToHeavyFile. + /// No description provided for @paiementAddingStoreError. /// /// In fr, this message translates to: - /// **'Fichier trop volumineux'** - String get phToHeavyFile; + /// **'Erreur lors de l\'ajout du magasin'** + String get paiementAddingStoreError; - /// No description provided for @phAddPdfFile. + /// No description provided for @paiementAddSeller. /// /// In fr, this message translates to: - /// **'Ajouter un fichier PDF'** - String get phAddPdfFile; + /// **'Ajouter un vendeur'** + String get paiementAddSeller; - /// No description provided for @phEditPdfFile. + /// No description provided for @paiementAddStore. /// /// In fr, this message translates to: - /// **'Modifier le fichier PDF'** - String get phEditPdfFile; + /// **'Ajouter un magasin'** + String get paiementAddStore; - /// No description provided for @phPhName. + /// No description provided for @paiementAddThisDevice. /// /// In fr, this message translates to: - /// **'Nom du PH'** - String get phPhName; + /// **'Ajouter cet appareil'** + String get paiementAddThisDevice; - /// No description provided for @phDate. + /// No description provided for @paiementAdmin. /// /// In fr, this message translates to: - /// **'Date'** - String get phDate; + /// **'Administrateur'** + String get paiementAdmin; - /// No description provided for @phAdded. + /// No description provided for @paiementAmount. /// /// In fr, this message translates to: - /// **'Ajouté'** - String get phAdded; + /// **'Montant'** + String get paiementAmount; - /// No description provided for @phEdited. + /// No description provided for @paiementAskDeviceActivation. /// /// In fr, this message translates to: - /// **'Modifié'** - String get phEdited; + /// **'Demande d\'activation de l\'appareil'** + String get paiementAskDeviceActivation; - /// No description provided for @phAddingFileError. + /// No description provided for @paiementAStore. /// /// In fr, this message translates to: - /// **'Erreur d\'ajout'** - String get phAddingFileError; + /// **'un magasin'** + String get paiementAStore; - /// No description provided for @phMissingInformatonsOrPdf. + /// No description provided for @paiementAt. /// /// In fr, this message translates to: - /// **'Informations manquantes ou fichier PDF manquant'** - String get phMissingInformatonsOrPdf; + /// **'à'** + String get paiementAt; - /// No description provided for @phAdd. + /// No description provided for @paiementAuthenticationRequired. /// /// In fr, this message translates to: - /// **'Ajouter'** - String get phAdd; + /// **'Authentification requise pour payer'** + String get paiementAuthenticationRequired; - /// No description provided for @phEdit. + /// No description provided for @paiementAuthentificationFailed. /// /// In fr, this message translates to: - /// **'Modifier'** - String get phEdit; + /// **'Échec de l\'authentification'** + String get paiementAuthentificationFailed; - /// No description provided for @phSeePreviousJournal. + /// No description provided for @paiementBalanceAfterTopUp. /// /// In fr, this message translates to: - /// **'Voir les anciens journaux'** - String get phSeePreviousJournal; + /// **'Solde après recharge :'** + String get paiementBalanceAfterTopUp; - /// No description provided for @phNoJournalInDatabase. + /// No description provided for @paiementBalanceAfterTransaction. /// /// In fr, this message translates to: - /// **'Pas encore de PH dans la base de donnée'** - String get phNoJournalInDatabase; + /// **'Solde après paiement : '** + String get paiementBalanceAfterTransaction; - /// No description provided for @phSuccesDowloading. + /// No description provided for @paiementBank. /// /// In fr, this message translates to: - /// **'Téléchargé avec succès'** - String get phSuccesDowloading; + /// **'Encaisser'** + String get paiementBank; - /// No description provided for @phonebookAdd. + /// No description provided for @paiementBillingSpace. /// /// In fr, this message translates to: - /// **'Ajouter'** - String get phonebookAdd; + /// **'Espace facturation'** + String get paiementBillingSpace; - /// No description provided for @phonebookAddAssociation. + /// No description provided for @paiementCameraPermissionRequired. /// /// In fr, this message translates to: - /// **'Ajouter une association'** - String get phonebookAddAssociation; + /// **'Permission d\'accès à la caméra requise'** + String get paiementCameraPermissionRequired; - /// No description provided for @phonebookAddAssociationGroupement. + /// No description provided for @paiementCameraPerssionRequiredDescription. /// /// In fr, this message translates to: - /// **'Ajouter un groupement d\'association'** - String get phonebookAddAssociationGroupement; + /// **'Pour scanner un QR Code, vous devez autoriser l\'accès à la caméra.'** + String get paiementCameraPerssionRequiredDescription; - /// No description provided for @phonebookAddedAssociation. + /// No description provided for @paiementCanBank. /// /// In fr, this message translates to: - /// **'Association ajoutée'** - String get phonebookAddedAssociation; + /// **'Peut encaisser'** + String get paiementCanBank; - /// No description provided for @phonebookAddedMember. + /// No description provided for @paiementCanCancelTransaction. /// /// In fr, this message translates to: - /// **'Membre ajouté'** - String get phonebookAddedMember; + /// **'Peut annuler des transactions'** + String get paiementCanCancelTransaction; - /// No description provided for @phonebookAddingError. + /// No description provided for @paiementCancel. /// /// In fr, this message translates to: - /// **'Erreur lors de l\'ajout'** - String get phonebookAddingError; + /// **'Annuler'** + String get paiementCancel; - /// No description provided for @phonebookAddMember. + /// No description provided for @paiementCancelled. /// /// In fr, this message translates to: - /// **'Ajouter un membre'** - String get phonebookAddMember; + /// **'Annulé'** + String get paiementCancelled; - /// No description provided for @phonebookAddRole. + /// No description provided for @paiementCancelledTransaction. /// /// In fr, this message translates to: - /// **'Ajouter un rôle'** - String get phonebookAddRole; + /// **'Paiement annulé'** + String get paiementCancelledTransaction; - /// No description provided for @phonebookAdmin. + /// No description provided for @paiementCancelTransaction. /// /// In fr, this message translates to: - /// **'Admin'** - String get phonebookAdmin; + /// **'Annuler la transaction'** + String get paiementCancelTransaction; - /// No description provided for @phonebookAll. + /// No description provided for @paiementCancelTransactions. /// /// In fr, this message translates to: - /// **'Toutes'** - String get phonebookAll; + /// **'Annuler les transactions'** + String get paiementCancelTransactions; - /// No description provided for @phonebookApparentName. + /// No description provided for @paiementCanManageSellers. /// /// In fr, this message translates to: - /// **'Nom public du rôle :'** - String get phonebookApparentName; + /// **'Peut gérer les vendeurs'** + String get paiementCanManageSellers; - /// No description provided for @phonebookAssociation. + /// No description provided for @paiementCanSeeHistory. /// /// In fr, this message translates to: - /// **'Association'** - String get phonebookAssociation; + /// **'Peut voir l\'historique'** + String get paiementCanSeeHistory; - /// No description provided for @phonebookAssociationDetail. + /// No description provided for @paiementClose. /// /// In fr, this message translates to: - /// **'Détail de l\'association :'** - String get phonebookAssociationDetail; + /// **'Fermer'** + String get paiementClose; - /// No description provided for @phonebookAssociationGroupement. + /// No description provided for @paiementCreate. /// /// In fr, this message translates to: - /// **'Groupement d\'association'** - String get phonebookAssociationGroupement; + /// **'Créer'** + String get paiementCreate; - /// No description provided for @phonebookAssociationKind. + /// No description provided for @paiementCreateInvoice. /// /// In fr, this message translates to: - /// **'Type d\'association :'** - String get phonebookAssociationKind; + /// **'Créer une facture'** + String get paiementCreateInvoice; - /// No description provided for @phonebookAssociationName. + /// No description provided for @paiementDecline. /// /// In fr, this message translates to: - /// **'Nom de l\'association'** - String get phonebookAssociationName; + /// **'Refuser'** + String get paiementDecline; - /// No description provided for @phonebookAssociations. + /// No description provided for @paiementDeletedSeller. /// /// In fr, this message translates to: - /// **'Associations'** - String get phonebookAssociations; + /// **'Vendeur supprimé'** + String get paiementDeletedSeller; - /// No description provided for @phonebookCancel. + /// No description provided for @paiementDeleteInvoice. /// /// In fr, this message translates to: - /// **'Annuler'** - String get phonebookCancel; + /// **'Supprimer la facture'** + String get paiementDeleteInvoice; - /// Permet de changer le mandat d'une association + /// No description provided for @paiementDeleteSeller. /// /// In fr, this message translates to: - /// **'Passer au mandat {year}'** - String phonebookChangeTermYear(int year); + /// **'Supprimer le vendeur'** + String get paiementDeleteSeller; - /// No description provided for @phonebookChangeTermConfirm. + /// No description provided for @paiementDeleteSellerDescription. /// /// In fr, this message translates to: - /// **'Êtes-vous sûr de vouloir changer tout le mandat ?\nCette action est irréversible !'** - String get phonebookChangeTermConfirm; + /// **'Voulez-vous vraiment supprimer ce vendeur ?'** + String get paiementDeleteSellerDescription; - /// No description provided for @phonebookClose. + /// No description provided for @paiementDeleteSuccessfully. /// /// In fr, this message translates to: - /// **'Fermer'** - String get phonebookClose; + /// **'Supprimé avec succès'** + String get paiementDeleteSuccessfully; - /// No description provided for @phonebookConfirm. + /// No description provided for @paiementDeleteStore. /// /// In fr, this message translates to: - /// **'Confirmer'** - String get phonebookConfirm; + /// **'Supprimer le magasin'** + String get paiementDeleteStore; - /// No description provided for @phonebookCopied. + /// No description provided for @paiementDeleteStoreDescription. /// /// In fr, this message translates to: - /// **'Copié dans le presse-papier'** - String get phonebookCopied; + /// **'Voulez-vous vraiment supprimer ce magasin ?'** + String get paiementDeleteStoreDescription; - /// No description provided for @phonebookDeactivateAssociation. + /// No description provided for @paiementDeleteStoreError. /// /// In fr, this message translates to: - /// **'Désactiver l\'association'** - String get phonebookDeactivateAssociation; + /// **'Impossible de supprimer le magasin'** + String get paiementDeleteStoreError; - /// No description provided for @phonebookDeactivatedAssociation. + /// No description provided for @paiementDeletingSellerError. /// /// In fr, this message translates to: - /// **'Association désactivée'** - String get phonebookDeactivatedAssociation; + /// **'Erreur lors de la suppression du vendeur'** + String get paiementDeletingSellerError; - /// No description provided for @phonebookDeactivatedAssociationWarning. + /// No description provided for @paiementDeviceActivationReceived. /// /// In fr, this message translates to: - /// **'Attention, cette association est désactivée, vous ne pouvez pas la modifier'** - String get phonebookDeactivatedAssociationWarning; + /// **'La demande d\'activation est prise en compte, veuilliez consulter votre boite mail pour finaliser la démarche'** + String get paiementDeviceActivationReceived; - /// Permet de désactiver une association + /// No description provided for @paiementDeviceNotActivated. /// /// In fr, this message translates to: - /// **'Désactiver l\'association {association} ?'** - String phonebookDeactivateSelectedAssociation(String association); + /// **'Appareil non activé'** + String get paiementDeviceNotActivated; - /// No description provided for @phonebookDeactivatingError. + /// No description provided for @paiementDeviceNotActivatedDescription. /// /// In fr, this message translates to: - /// **'Erreur lors de la désactivation'** - String get phonebookDeactivatingError; + /// **'Votre appareil n\'est pas encore activé. \nPour l\'activer, veuillez vous rendre sur la page des appareils.'** + String get paiementDeviceNotActivatedDescription; - /// No description provided for @phonebookDetail. + /// No description provided for @paiementDeviceNotRegistered. /// /// In fr, this message translates to: - /// **'Détail :'** - String get phonebookDetail; + /// **'Appareil non enregistré'** + String get paiementDeviceNotRegistered; - /// No description provided for @phonebookDelete. + /// No description provided for @paiementDeviceNotRegisteredDescription. /// /// In fr, this message translates to: - /// **'Supprimer'** - String get phonebookDelete; + /// **'Votre appareil n\'est pas encore enregistré. \nPour l\'enregistrer, veuillez vous rendre sur la page des appareils.'** + String get paiementDeviceNotRegisteredDescription; - /// No description provided for @phonebookDeleteAssociation. + /// No description provided for @paiementDeviceRecoveryError. /// /// In fr, this message translates to: - /// **'Supprimer l\'association'** - String get phonebookDeleteAssociation; + /// **'Erreur lors de la récupération de l\'appareil'** + String get paiementDeviceRecoveryError; - /// Permet de supprimer une association + /// No description provided for @paiementDeviceRevoked. /// /// In fr, this message translates to: - /// **'Supprimer l\'association {association} ?'** - String phonebookDeleteSelectedAssociation(String association); + /// **'Appareil révoqué'** + String get paiementDeviceRevoked; - /// No description provided for @phonebookDeleteAssociationDescription. + /// No description provided for @paiementDeviceRevokingError. /// /// In fr, this message translates to: - /// **'Ceci va supprimer l\'historique de l\'association'** - String get phonebookDeleteAssociationDescription; + /// **'Erreur lors de la révocation de l\'appareil'** + String get paiementDeviceRevokingError; - /// No description provided for @phonebookDeletedAssociation. + /// No description provided for @paiementDevices. /// /// In fr, this message translates to: - /// **'Association supprimée'** - String get phonebookDeletedAssociation; + /// **'Appareils'** + String get paiementDevices; - /// No description provided for @phonebookDeletedMember. + /// No description provided for @paiementDoneTransaction. /// /// In fr, this message translates to: - /// **'Membre supprimé'** - String get phonebookDeletedMember; + /// **'Transaction effectuée'** + String get paiementDoneTransaction; - /// No description provided for @phonebookDeleteRole. + /// No description provided for @paiementDownload. /// /// In fr, this message translates to: - /// **'Supprimer le rôle'** - String get phonebookDeleteRole; + /// **'Télécharger'** + String get paiementDownload; - /// Permet de supprimer le rôle d'un utilisateur dans une association + /// Modifier le magasin /// /// In fr, this message translates to: - /// **'Supprimer le rôle de l\'utilisateur {name} ?'** - String phonebookDeleteUserRole(String name); + /// **'Modifier le magasin {store}'** + String paiementEditStore(String store); - /// No description provided for @phonebookDeactivating. + /// No description provided for @paiementErrorDeleting. /// /// In fr, this message translates to: - /// **'Désactiver l\'association ?'** - String get phonebookDeactivating; + /// **'Erreur lors de la suppression'** + String get paiementErrorDeleting; - /// No description provided for @phonebookDeleting. + /// No description provided for @paiementErrorUpdatingStatus. /// /// In fr, this message translates to: - /// **'Suppression'** - String get phonebookDeleting; + /// **'Erreur lors de la mise à jour du statut'** + String get paiementErrorUpdatingStatus; - /// No description provided for @phonebookDeletingError. + /// Text with a date range /// /// In fr, this message translates to: - /// **'Erreur lors de la suppression'** - String get phonebookDeletingError; + /// **'Du {from} au {to}'** + String paiementFromTo(DateTime from, DateTime to); - /// No description provided for @phonebookDescription. + /// No description provided for @paiementGetBalanceError. /// /// In fr, this message translates to: - /// **'Description'** - String get phonebookDescription; + /// **'Erreur lors de la récupération du solde : '** + String get paiementGetBalanceError; - /// No description provided for @phonebookEdit. + /// No description provided for @paiementGetTransactionsError. /// /// In fr, this message translates to: - /// **'Modifier'** - String get phonebookEdit; + /// **'Erreur lors de la récupération des transactions : '** + String get paiementGetTransactionsError; - /// No description provided for @phonebookEditAssociationGroupement. + /// No description provided for @paiementHandOver. /// /// In fr, this message translates to: - /// **'Modifier le groupement d\'association'** - String get phonebookEditAssociationGroupement; + /// **'Passation'** + String get paiementHandOver; - /// No description provided for @phonebookEditAssociationGroups. + /// No description provided for @paiementHistory. /// /// In fr, this message translates to: - /// **'Gérer les groupes'** - String get phonebookEditAssociationGroups; + /// **'Historique'** + String get paiementHistory; - /// No description provided for @phonebookEditAssociationInfo. + /// No description provided for @paiementInvoiceCreatedSuccessfully. /// /// In fr, this message translates to: - /// **'Modifier'** - String get phonebookEditAssociationInfo; + /// **'Facture créée avec succès'** + String get paiementInvoiceCreatedSuccessfully; - /// No description provided for @phonebookEditAssociationMembers. + /// No description provided for @paiementInvoices. /// /// In fr, this message translates to: - /// **'Gérer les membres'** - String get phonebookEditAssociationMembers; + /// **'Factures'** + String get paiementInvoices; - /// No description provided for @phonebookEditRole. + /// Text with the number of invoices per page /// /// In fr, this message translates to: - /// **'Modifier le rôle'** - String get phonebookEditRole; + /// **'{quantity} factures/page'** + String paiementInvoicesPerPage(int quantity); - /// No description provided for @phonebookEditMembership. + /// No description provided for @paiementLastTransactions. /// /// In fr, this message translates to: - /// **'Modifier le rôle'** - String get phonebookEditMembership; + /// **'Dernières transactions'** + String get paiementLastTransactions; - /// No description provided for @phonebookEmail. + /// No description provided for @paiementLimitedTo. /// /// In fr, this message translates to: - /// **'Email :'** - String get phonebookEmail; + /// **'Limité à'** + String get paiementLimitedTo; - /// No description provided for @phonebookEmailCopied. + /// No description provided for @paiementManagement. /// /// In fr, this message translates to: - /// **'Email copié dans le presse-papier'** - String get phonebookEmailCopied; + /// **'Gestion'** + String get paiementManagement; - /// No description provided for @phonebookEmptyApparentName. + /// No description provided for @paiementManageSellers. /// /// In fr, this message translates to: - /// **'Veuillez entrer un nom de role'** - String get phonebookEmptyApparentName; + /// **'Gérer les vendeurs'** + String get paiementManageSellers; - /// No description provided for @phonebookEmptyFieldError. + /// No description provided for @paiementMarkPaid. /// /// In fr, this message translates to: - /// **'Un champ n\'est pas rempli'** - String get phonebookEmptyFieldError; + /// **'Marquer comme payé'** + String get paiementMarkPaid; - /// No description provided for @phonebookEmptyKindError. + /// No description provided for @paiementMarkReceived. /// /// In fr, this message translates to: - /// **'Veuillez choisir un type d\'association'** - String get phonebookEmptyKindError; + /// **'Marquer comme reçu'** + String get paiementMarkReceived; - /// No description provided for @phonebookEmptyMember. + /// No description provided for @paiementMarkUnpaid. /// /// In fr, this message translates to: - /// **'Aucun membre sélectionné'** - String get phonebookEmptyMember; + /// **'Marquer comme non payé'** + String get paiementMarkUnpaid; - /// No description provided for @phonebookErrorAssociationLoading. + /// No description provided for @paiementMaxAmount. /// /// In fr, this message translates to: - /// **'Erreur lors du chargement de l\'association'** - String get phonebookErrorAssociationLoading; + /// **'Le montant maximum de votre portefeuille est de'** + String get paiementMaxAmount; - /// No description provided for @phonebookErrorAssociationNameEmpty. + /// No description provided for @paiementMean. /// /// In fr, this message translates to: - /// **'Veuillez entrer un nom d\'association'** - String get phonebookErrorAssociationNameEmpty; + /// **'Moyenne : '** + String get paiementMean; - /// No description provided for @phonebookErrorAssociationPicture. + /// No description provided for @paiementModify. /// /// In fr, this message translates to: - /// **'Erreur lors de la modification de la photo d\'association'** - String get phonebookErrorAssociationPicture; + /// **'Modifier'** + String get paiementModify; - /// No description provided for @phonebookErrorKindsLoading. + /// No description provided for @paiementModifyingStoreError. /// /// In fr, this message translates to: - /// **'Erreur lors du chargement des types d\'association'** - String get phonebookErrorKindsLoading; + /// **'Erreur lors de la modification du magasin'** + String get paiementModifyingStoreError; - /// No description provided for @phonebookErrorLoadAssociationList. + /// No description provided for @paiementModifySuccessfully. /// /// In fr, this message translates to: - /// **'Erreur lors du chargement de la liste des associations'** - String get phonebookErrorLoadAssociationList; + /// **'Modifié avec succès'** + String get paiementModifySuccessfully; - /// No description provided for @phonebookErrorLoadAssociationMember. + /// No description provided for @paiementNewCGU. /// /// In fr, this message translates to: - /// **'Erreur lors du chargement des membres de l\'association'** - String get phonebookErrorLoadAssociationMember; + /// **'Nouvelles Conditions Générales d\'Utilisation'** + String get paiementNewCGU; - /// No description provided for @phonebookErrorLoadAssociationPicture. + /// No description provided for @paiementNext. /// /// In fr, this message translates to: - /// **'Erreur lors du chargement de la photo d\'association'** - String get phonebookErrorLoadAssociationPicture; + /// **'Suivant'** + String get paiementNext; - /// No description provided for @phonebookErrorLoadProfilePicture. + /// No description provided for @paiementNextAccountable. /// /// In fr, this message translates to: - /// **'Erreur'** - String get phonebookErrorLoadProfilePicture; + /// **'Prochain responsable'** + String get paiementNextAccountable; - /// No description provided for @phonebookErrorRoleTagsLoading. + /// No description provided for @paiementNoInvoiceToCreate. /// /// In fr, this message translates to: - /// **'Erreur lors du chargement des tags de rôle'** - String get phonebookErrorRoleTagsLoading; + /// **'Aucune facture à créer'** + String get paiementNoInvoiceToCreate; - /// No description provided for @phonebookExistingMembership. + /// No description provided for @paiementNoMembership. /// /// In fr, this message translates to: - /// **'Ce membre est déjà dans le mandat actuel'** - String get phonebookExistingMembership; + /// **'Aucune adhésion'** + String get paiementNoMembership; - /// No description provided for @phonebookFilter. + /// No description provided for @paiementNoMembershipDescription. /// /// In fr, this message translates to: - /// **'Filtrer'** - String get phonebookFilter; + /// **'Ce produit n\'est pas disponnible pour les non-adhérents. Confirmer l\'encaissement ?'** + String get paiementNoMembershipDescription; - /// No description provided for @phonebookFilterDescription. + /// No description provided for @paiementNoThanks. /// /// In fr, this message translates to: - /// **'Filtrer les associations par type'** - String get phonebookFilterDescription; + /// **'Non merci'** + String get paiementNoThanks; - /// No description provided for @phonebookFirstname. + /// No description provided for @paiementNoTransaction. /// /// In fr, this message translates to: - /// **'Prénom :'** - String get phonebookFirstname; + /// **'Aucune transaction'** + String get paiementNoTransaction; - /// No description provided for @phonebookGroupementDeleted. + /// No description provided for @paiementNoTransactionForThisMonth. /// /// In fr, this message translates to: - /// **'Groupement d\'association supprimé'** - String get phonebookGroupementDeleted; + /// **'Aucune transaction pour ce mois'** + String get paiementNoTransactionForThisMonth; - /// No description provided for @phonebookGroupementDeleteError. + /// No description provided for @paiementOf. /// /// In fr, this message translates to: - /// **'Erreur lors de la suppression du groupement d\'association'** - String get phonebookGroupementDeleteError; + /// **'de'** + String get paiementOf; - /// No description provided for @phonebookGroupementName. + /// No description provided for @paiementPaid. /// /// In fr, this message translates to: - /// **'Nom du groupement'** - String get phonebookGroupementName; + /// **'Payé'** + String get paiementPaid; - /// Permet de gérer les groupes d'une association + /// No description provided for @paiementPay. /// /// In fr, this message translates to: - /// **'Gérer les groupes de {association}'** - String phonebookGroups(String association); + /// **'Payer'** + String get paiementPay; - /// Année de mandat d'une association + /// No description provided for @paiementPayment. /// /// In fr, this message translates to: - /// **'Mandat {year}'** - String phonebookTerm(int year); + /// **'Paiement'** + String get paiementPayment; - /// No description provided for @phonebookTermChangingError. + /// No description provided for @paiementPayWithHA. /// /// In fr, this message translates to: - /// **'Erreur lors du changement de mandat'** - String get phonebookTermChangingError; + /// **'Payer avec HelloAsso'** + String get paiementPayWithHA; - /// No description provided for @phonebookMember. + /// No description provided for @paiementPending. /// /// In fr, this message translates to: - /// **'Membre'** - String get phonebookMember; + /// **'En attente'** + String get paiementPending; - /// No description provided for @phonebookMemberReordered. + /// No description provided for @paiementPersonalBalance. /// /// In fr, this message translates to: - /// **'Membre réordonné'** - String get phonebookMemberReordered; + /// **'Solde personnel'** + String get paiementPersonalBalance; - /// Permet de gérer les membres d'une association + /// No description provided for @paiementPleaseAcceptPopup. /// /// In fr, this message translates to: - /// **'Gérer les membres de {association}'** - String phonebookMembers(String association); + /// **'Veuillez autoriser les popups'** + String get paiementPleaseAcceptPopup; - /// No description provided for @phonebookMembershipAssociationError. + /// No description provided for @paiementPleaseAcceptTOS. /// /// In fr, this message translates to: - /// **'Veuillez choisir une association'** - String get phonebookMembershipAssociationError; + /// **'Veuillez accepter les Conditions Générales d\'Utilisation.'** + String get paiementPleaseAcceptTOS; - /// No description provided for @phonebookMembershipRole. + /// No description provided for @paiementPleaseAddDevice. /// /// In fr, this message translates to: - /// **'Rôle :'** - String get phonebookMembershipRole; + /// **'Veuillez ajouter cet appareil pour payer'** + String get paiementPleaseAddDevice; - /// No description provided for @phonebookMembershipRoleError. + /// No description provided for @paiementPleaseAuthenticate. /// /// In fr, this message translates to: - /// **'Veuillez choisir un rôle'** - String get phonebookMembershipRoleError; + /// **'Veuillez vous authentifier'** + String get paiementPleaseAuthenticate; - /// Permet de modifier le rôle d'un membre dans une association + /// No description provided for @paiementPleaseEnterMinAmount. /// /// In fr, this message translates to: - /// **'Modifier le rôle de {name}'** - String phonebookModifyMembership(String name); + /// **'Veuillez entrer un montant supérieur à 1'** + String get paiementPleaseEnterMinAmount; - /// No description provided for @phonebookName. + /// No description provided for @paiementPleaseEnterValidAmount. /// /// In fr, this message translates to: - /// **'Nom :'** - String get phonebookName; + /// **'Veuillez entrer un montant valide'** + String get paiementPleaseEnterValidAmount; - /// No description provided for @phonebookNameCopied. + /// No description provided for @paiementProceedSuccessfully. /// /// In fr, this message translates to: - /// **'Nom et prénom copié dans le presse-papier'** - String get phonebookNameCopied; + /// **'Paiement effectué avec succès'** + String get paiementProceedSuccessfully; - /// No description provided for @phonebookNamePure. + /// No description provided for @paiementQRCodeAlreadyUsed. /// /// In fr, this message translates to: - /// **'Nom'** - String get phonebookNamePure; + /// **'QR Code déjà utilisé'** + String get paiementQRCodeAlreadyUsed; - /// No description provided for @phonebookNewTerm. + /// No description provided for @paiementReactivateRevokedDeviceDescription. /// /// In fr, this message translates to: - /// **'Nouveau mandat'** - String get phonebookNewTerm; + /// **'Votre appareil a été révoqué. \nPour le réactiver, veuillez vous rendre sur la page des appareils.'** + String get paiementReactivateRevokedDeviceDescription; - /// No description provided for @phonebookNewTermConfirmed. + /// No description provided for @paiementReceived. /// /// In fr, this message translates to: - /// **'Mandat changé'** - String get phonebookNewTermConfirmed; + /// **'Reçu'** + String get paiementReceived; - /// No description provided for @phonebookNickname. + /// No description provided for @paiementRefund. /// /// In fr, this message translates to: - /// **'Surnom :'** - String get phonebookNickname; + /// **'Remboursement'** + String get paiementRefund; - /// No description provided for @phonebookNicknameCopied. + /// No description provided for @paiementRefundAction. /// /// In fr, this message translates to: - /// **'Surnom copié dans le presse-papier'** - String get phonebookNicknameCopied; + /// **'Rembourser'** + String get paiementRefundAction; - /// No description provided for @phonebookNoAssociationFound. + /// No description provided for @paiementRefundedThe. /// /// In fr, this message translates to: - /// **'Aucune association trouvée'** - String get phonebookNoAssociationFound; + /// **'Remboursé le'** + String get paiementRefundedThe; - /// No description provided for @phonebookNoMember. + /// No description provided for @paiementRevokeDevice. /// /// In fr, this message translates to: - /// **'Aucun membre'** - String get phonebookNoMember; + /// **'Révoquer l\'appareil ?'** + String get paiementRevokeDevice; - /// No description provided for @phonebookNoMemberRole. + /// No description provided for @paiementRevokeDeviceDescription. /// /// In fr, this message translates to: - /// **'Aucun role trouvé'** - String get phonebookNoMemberRole; + /// **'Vous ne pourrez plus utiliser cet appareil pour les paiements'** + String get paiementRevokeDeviceDescription; - /// No description provided for @phonebookNoRoleTags. + /// No description provided for @paiementRightsOf. /// /// In fr, this message translates to: - /// **'Aucun tag de rôle trouvé'** - String get phonebookNoRoleTags; + /// **'Droits de'** + String get paiementRightsOf; - /// No description provided for @phonebookPhone. + /// No description provided for @paiementRightsUpdated. /// /// In fr, this message translates to: - /// **'Téléphone :'** - String get phonebookPhone; + /// **'Droits mis à jour'** + String get paiementRightsUpdated; - /// No description provided for @phonebookPhonebook. + /// No description provided for @paiementRightsUpdateError. /// /// In fr, this message translates to: - /// **'Annuaire'** - String get phonebookPhonebook; + /// **'Erreur lors de la mise à jour des droits'** + String get paiementRightsUpdateError; - /// No description provided for @phonebookPhonebookSearch. + /// No description provided for @paiementScan. /// /// In fr, this message translates to: - /// **'Rechercher'** - String get phonebookPhonebookSearch; + /// **'Scanner'** + String get paiementScan; - /// No description provided for @phonebookPhonebookSearchAssociation. + /// No description provided for @paiementScanCode. /// /// In fr, this message translates to: - /// **'Association'** - String get phonebookPhonebookSearchAssociation; + /// **'Scanner un code'** + String get paiementScanCode; - /// No description provided for @phonebookPhonebookSearchField. + /// No description provided for @paiementSeeHistory. /// /// In fr, this message translates to: - /// **'Rechercher :'** - String get phonebookPhonebookSearchField; + /// **'Voir l\'historique'** + String get paiementSeeHistory; - /// No description provided for @phonebookPhonebookSearchName. + /// No description provided for @paiementSelectStructure. /// /// In fr, this message translates to: - /// **'Nom/Prénom/Surnom'** - String get phonebookPhonebookSearchName; + /// **'Choisir une structure'** + String get paiementSelectStructure; - /// No description provided for @phonebookPhonebookSearchRole. + /// No description provided for @paiementSellerError. /// /// In fr, this message translates to: - /// **'Poste'** - String get phonebookPhonebookSearchRole; + /// **'Vous n\'êtes pas vendeur de ce magasin'** + String get paiementSellerError; - /// No description provided for @phonebookPresidentRoleTag. + /// No description provided for @paiementSellerRigths. /// /// In fr, this message translates to: - /// **'Prez\''** - String get phonebookPresidentRoleTag; + /// **'Droits du vendeur'** + String get paiementSellerRigths; - /// No description provided for @phonebookPromoNotGiven. + /// No description provided for @paiementSellersOf. /// /// In fr, this message translates to: - /// **'Promo non renseignée'** - String get phonebookPromoNotGiven; + /// **'Les vendeurs de'** + String get paiementSellersOf; - /// Année de promotion d'un membre + /// No description provided for @paiementSettings. /// /// In fr, this message translates to: - /// **'Promotion {year}'** - String phonebookPromotion(int year); + /// **'Paramètres'** + String get paiementSettings; - /// No description provided for @phonebookReorderingError. + /// No description provided for @paiementSpent. /// /// In fr, this message translates to: - /// **'Erreur lors du réordonnement'** - String get phonebookReorderingError; + /// **'Déboursé'** + String get paiementSpent; - /// No description provided for @phonebookResearch. + /// No description provided for @paiementStats. /// /// In fr, this message translates to: - /// **'Rechercher'** - String get phonebookResearch; + /// **'Stats'** + String get paiementStats; - /// No description provided for @phonebookRolePure. + /// No description provided for @paiementStoreBalance. /// /// In fr, this message translates to: - /// **'Rôle'** - String get phonebookRolePure; + /// **'Solde du magasin'** + String get paiementStoreBalance; - /// No description provided for @phonebookSearchUser. + /// No description provided for @paiementStoreDeleted. /// /// In fr, this message translates to: - /// **'Rechercher un utilisateur'** - String get phonebookSearchUser; + /// **'Magasin supprimée'** + String get paiementStoreDeleted; - /// No description provided for @phonebookTooHeavyAssociationPicture. + /// Gestion de la structure /// /// In fr, this message translates to: - /// **'L\'image est trop lourde (max 4Mo)'** - String get phonebookTooHeavyAssociationPicture; + /// **'Gestion de {structure}'** + String paiementStructureManagement(String structure); - /// No description provided for @phonebookUpdateGroups. + /// No description provided for @paiementStoreName. /// /// In fr, this message translates to: - /// **'Mettre à jour les groupes'** - String get phonebookUpdateGroups; + /// **'Nom du magasin'** + String get paiementStoreName; - /// No description provided for @phonebookUpdatedAssociation. + /// No description provided for @paiementStores. /// /// In fr, this message translates to: - /// **'Association modifiée'** - String get phonebookUpdatedAssociation; + /// **'Magasins'** + String get paiementStores; - /// No description provided for @phonebookUpdatedAssociationPicture. + /// No description provided for @paiementStructureAdmin. /// /// In fr, this message translates to: - /// **'La photo d\'association a été changée'** - String get phonebookUpdatedAssociationPicture; + /// **'Administrateur de la structure'** + String get paiementStructureAdmin; - /// No description provided for @phonebookUpdatedGroups. + /// No description provided for @paiementSuccededTransaction. /// /// In fr, this message translates to: - /// **'Groupes mis à jour'** - String get phonebookUpdatedGroups; + /// **'Paiement réussi'** + String get paiementSuccededTransaction; - /// No description provided for @phonebookUpdatedMember. + /// No description provided for @paiementSuccessfullyAddedStore. /// /// In fr, this message translates to: - /// **'Membre modifié'** - String get phonebookUpdatedMember; + /// **'Magasin ajoutée avec succès'** + String get paiementSuccessfullyAddedStore; - /// No description provided for @phonebookUpdatingError. + /// No description provided for @paiementSuccessfullyModifiedStore. /// /// In fr, this message translates to: - /// **'Erreur lors de la modification'** - String get phonebookUpdatingError; + /// **'Magasin modifiée avec succès'** + String get paiementSuccessfullyModifiedStore; - /// No description provided for @phonebookValidation. + /// No description provided for @paiementThe. /// /// In fr, this message translates to: - /// **'Valider'** - String get phonebookValidation; + /// **'Le'** + String get paiementThe; - /// No description provided for @purchasesPurchases. + /// No description provided for @paiementThisDevice. /// /// In fr, this message translates to: - /// **'Achats'** - String get purchasesPurchases; + /// **'(cet appareil)'** + String get paiementThisDevice; - /// No description provided for @purchasesResearch. + /// No description provided for @paiementTopUp. /// /// In fr, this message translates to: - /// **'Rechercher'** - String get purchasesResearch; + /// **'Recharge'** + String get paiementTopUp; - /// No description provided for @purchasesNoPurchasesFound. + /// No description provided for @paiementTopUpAction. /// /// In fr, this message translates to: - /// **'Aucun achat trouvé'** - String get purchasesNoPurchasesFound; + /// **'Recharger'** + String get paiementTopUpAction; - /// No description provided for @purchasesNoTickets. + /// No description provided for @paiementTotalDuringPeriod. /// /// In fr, this message translates to: - /// **'Aucun ticket'** - String get purchasesNoTickets; + /// **'Total sur la période'** + String get paiementTotalDuringPeriod; - /// No description provided for @purchasesTicketsError. + /// No description provided for @paiementTransaction. /// /// In fr, this message translates to: - /// **'Erreur lors du chargement des tickets'** - String get purchasesTicketsError; + /// **'ransaction'** + String get paiementTransaction; - /// No description provided for @purchasesPurchasesError. + /// No description provided for @paiementTransactionCancelled. /// /// In fr, this message translates to: - /// **'Erreur lors du chargement des achats'** - String get purchasesPurchasesError; + /// **'Transaction annulée'** + String get paiementTransactionCancelled; - /// No description provided for @purchasesNoPurchases. + /// No description provided for @paiementTransactionCancelledDescription. /// /// In fr, this message translates to: - /// **'Aucun achat'** - String get purchasesNoPurchases; + /// **'Voulez-vous vraiment annuler la transaction de'** + String get paiementTransactionCancelledDescription; - /// No description provided for @purchasesTimes. + /// No description provided for @paiementTransactionCancelledError. /// /// In fr, this message translates to: - /// **'fois'** - String get purchasesTimes; + /// **'Erreur lors de l\'annulation de la transaction'** + String get paiementTransactionCancelledError; - /// No description provided for @purchasesAlreadyUsed. + /// No description provided for @paiementTransferStructure. /// /// In fr, this message translates to: - /// **'Déjà utilisé'** - String get purchasesAlreadyUsed; + /// **'Transfert de structure'** + String get paiementTransferStructure; - /// No description provided for @purchasesNotPaid. + /// No description provided for @paiementTransferStructureDescription. /// /// In fr, this message translates to: - /// **'Non validé'** - String get purchasesNotPaid; + /// **'Le nouveau responsable aura accès à toutes les fonctionnalités de gestion de la structure. Vous allez recevoir un email pour valider ce transfert. Le lien ne sera actif que pendant 20 minutes. Cette action est irréversible. Êtes-vous sûr de vouloir continuer ?'** + String get paiementTransferStructureDescription; - /// No description provided for @purchasesPleaseSelectProduct. + /// No description provided for @paiementTransferStructureError. /// /// In fr, this message translates to: - /// **'Veuillez sélectionner un produit'** - String get purchasesPleaseSelectProduct; + /// **'Erreur lors du transfert de la structure'** + String get paiementTransferStructureError; - /// No description provided for @purchasesProducts. + /// No description provided for @paiementTransferStructureSuccess. /// /// In fr, this message translates to: - /// **'Produits'** - String get purchasesProducts; + /// **'Transfert de structure demandé avec succès'** + String get paiementTransferStructureSuccess; - /// No description provided for @purchasesCancel. + /// No description provided for @paiementValidUntil. /// /// In fr, this message translates to: - /// **'Annuler'** - String get purchasesCancel; + /// **'Valide jusqu\'à'** + String get paiementValidUntil; - /// No description provided for @purchasesValidate. + /// No description provided for @paiementYouAreTransferingStructureTo. /// /// In fr, this message translates to: - /// **'Valider'** - String get purchasesValidate; + /// **'Vous êtes sur le point de transférer la structure à '** + String get paiementYouAreTransferingStructureTo; - /// No description provided for @purchasesLeftScan. + /// No description provided for @phAddNewJournal. /// /// In fr, this message translates to: - /// **'Scans restants'** - String get purchasesLeftScan; + /// **'Ajouter un nouveau journal'** + String get phAddNewJournal; - /// No description provided for @purchasesTag. + /// No description provided for @phNameField. /// /// In fr, this message translates to: - /// **'Tag'** - String get purchasesTag; + /// **'Nom : '** + String get phNameField; - /// No description provided for @purchasesHistory. + /// No description provided for @phDateField. /// /// In fr, this message translates to: - /// **'Historique'** - String get purchasesHistory; + /// **'Date : '** + String get phDateField; - /// No description provided for @purchasesPleaseSelectSeller. + /// No description provided for @phDelete. /// /// In fr, this message translates to: - /// **'Veuillez sélectionner un vendeur'** - String get purchasesPleaseSelectSeller; + /// **'Voulez-vous vraiment supprimer ce journal ?'** + String get phDelete; - /// No description provided for @purchasesNoTagGiven. + /// No description provided for @phIrreversibleAction. /// /// In fr, this message translates to: - /// **'Attention, aucun tag n\'a été entré'** - String get purchasesNoTagGiven; + /// **'Cette action est irréversible'** + String get phIrreversibleAction; - /// No description provided for @purchasesTickets. + /// No description provided for @phToHeavyFile. /// /// In fr, this message translates to: - /// **'Tickets'** - String get purchasesTickets; + /// **'Fichier trop volumineux'** + String get phToHeavyFile; - /// No description provided for @purchasesNoScannableProducts. + /// No description provided for @phAddPdfFile. /// /// In fr, this message translates to: - /// **'Aucun produit scannable'** - String get purchasesNoScannableProducts; + /// **'Ajouter un fichier PDF'** + String get phAddPdfFile; - /// No description provided for @purchasesLoading. + /// No description provided for @phEditPdfFile. /// /// In fr, this message translates to: - /// **'En attente de scan'** - String get purchasesLoading; + /// **'Modifier le fichier PDF'** + String get phEditPdfFile; - /// No description provided for @purchasesScan. + /// No description provided for @phPhName. /// /// In fr, this message translates to: - /// **'Scanner'** - String get purchasesScan; + /// **'Nom du PH'** + String get phPhName; - /// No description provided for @raffleRaffle. + /// No description provided for @phDate. /// /// In fr, this message translates to: - /// **'Tombola'** - String get raffleRaffle; + /// **'Date'** + String get phDate; - /// No description provided for @rafflePrize. + /// No description provided for @phAdded. /// /// In fr, this message translates to: - /// **'Lot'** - String get rafflePrize; + /// **'Ajouté'** + String get phAdded; - /// No description provided for @rafflePrizes. + /// No description provided for @phEdited. /// /// In fr, this message translates to: - /// **'Lots'** - String get rafflePrizes; + /// **'Modifié'** + String get phEdited; - /// No description provided for @raffleActualRaffles. + /// No description provided for @phAddingFileError. /// /// In fr, this message translates to: - /// **'Tombola en cours'** - String get raffleActualRaffles; + /// **'Erreur d\'ajout'** + String get phAddingFileError; - /// No description provided for @rafflePastRaffles. + /// No description provided for @phMissingInformatonsOrPdf. /// /// In fr, this message translates to: - /// **'Tombola passés'** - String get rafflePastRaffles; + /// **'Informations manquantes ou fichier PDF manquant'** + String get phMissingInformatonsOrPdf; - /// No description provided for @raffleYourTickets. + /// No description provided for @phAdd. /// /// In fr, this message translates to: - /// **'Tous vos tickets'** - String get raffleYourTickets; + /// **'Ajouter'** + String get phAdd; - /// No description provided for @raffleCreateMenu. + /// No description provided for @phEdit. /// /// In fr, this message translates to: - /// **'Menu de Création'** - String get raffleCreateMenu; + /// **'Modifier'** + String get phEdit; - /// No description provided for @raffleNextRaffles. + /// No description provided for @phSeePreviousJournal. /// /// In fr, this message translates to: - /// **'Prochaines tombolas'** - String get raffleNextRaffles; + /// **'Voir les anciens journaux'** + String get phSeePreviousJournal; - /// No description provided for @raffleNoTicket. + /// No description provided for @phNoJournalInDatabase. /// /// In fr, this message translates to: - /// **'Vous n\'avez pas de ticket'** - String get raffleNoTicket; + /// **'Pas encore de PH dans la base de donnée'** + String get phNoJournalInDatabase; - /// No description provided for @raffleSeeRaffleDetail. + /// No description provided for @phSuccesDowloading. /// /// In fr, this message translates to: - /// **'Voir lots/tickets'** - String get raffleSeeRaffleDetail; + /// **'Téléchargé avec succès'** + String get phSuccesDowloading; - /// No description provided for @raffleActualPrize. + /// No description provided for @phonebookAdd. /// /// In fr, this message translates to: - /// **'Lots actuels'** - String get raffleActualPrize; + /// **'Ajouter'** + String get phonebookAdd; - /// No description provided for @raffleMajorPrize. + /// No description provided for @phonebookAddAssociation. /// /// In fr, this message translates to: - /// **'Lot Majeurs'** - String get raffleMajorPrize; + /// **'Ajouter une association'** + String get phonebookAddAssociation; - /// No description provided for @raffleTakeTickets. + /// No description provided for @phonebookAddAssociationGroupement. /// /// In fr, this message translates to: - /// **'Prendre vos tickets'** - String get raffleTakeTickets; + /// **'Ajouter un groupement d\'association'** + String get phonebookAddAssociationGroupement; - /// No description provided for @raffleNoTicketBuyable. + /// No description provided for @phonebookAddedAssociation. /// /// In fr, this message translates to: - /// **'Vous ne pouvez pas achetez de billets pour l\'instant'** - String get raffleNoTicketBuyable; + /// **'Association ajoutée'** + String get phonebookAddedAssociation; - /// No description provided for @raffleNoCurrentPrize. + /// No description provided for @phonebookAddedMember. /// /// In fr, this message translates to: - /// **'Il n\'y a aucun lots actuellement'** - String get raffleNoCurrentPrize; + /// **'Membre ajouté'** + String get phonebookAddedMember; - /// No description provided for @raffleModifTombola. + /// No description provided for @phonebookAddingError. /// /// In fr, this message translates to: - /// **'Vous pouvez modifiez vos tombolas ou en créer de nouvelles, toute décision doit ensuite être prise par les admins'** - String get raffleModifTombola; + /// **'Erreur lors de l\'ajout'** + String get phonebookAddingError; - /// No description provided for @raffleCreateYourRaffle. + /// No description provided for @phonebookAddMember. /// /// In fr, this message translates to: - /// **'Votre menu de création de tombolas'** - String get raffleCreateYourRaffle; + /// **'Ajouter un membre'** + String get phonebookAddMember; - /// No description provided for @rafflePossiblePrice. + /// No description provided for @phonebookAddRole. /// /// In fr, this message translates to: - /// **'Prix possible'** - String get rafflePossiblePrice; + /// **'Ajouter un rôle'** + String get phonebookAddRole; - /// No description provided for @raffleInformation. + /// No description provided for @phonebookAdmin. /// /// In fr, this message translates to: - /// **'Information et Statistiques'** - String get raffleInformation; + /// **'Admin'** + String get phonebookAdmin; - /// No description provided for @raffleAccounts. + /// No description provided for @phonebookAll. /// /// In fr, this message translates to: - /// **'Comptes'** - String get raffleAccounts; + /// **'Toutes'** + String get phonebookAll; - /// No description provided for @raffleAdd. + /// No description provided for @phonebookApparentName. /// /// In fr, this message translates to: - /// **'Ajouter'** - String get raffleAdd; + /// **'Nom public du rôle :'** + String get phonebookApparentName; - /// No description provided for @raffleUpdatedAmount. + /// No description provided for @phonebookAssociation. /// /// In fr, this message translates to: - /// **'Montant mis à jour'** - String get raffleUpdatedAmount; + /// **'Association'** + String get phonebookAssociation; - /// No description provided for @raffleUpdatingError. + /// No description provided for @phonebookAssociationDetail. /// /// In fr, this message translates to: - /// **'Erreur lors de la mise à jour'** - String get raffleUpdatingError; + /// **'Détail de l\'association :'** + String get phonebookAssociationDetail; - /// No description provided for @raffleDeletedPrize. + /// No description provided for @phonebookAssociationGroupement. /// /// In fr, this message translates to: - /// **'Lot supprimé'** - String get raffleDeletedPrize; + /// **'Groupement d\'association'** + String get phonebookAssociationGroupement; - /// No description provided for @raffleDeletingError. + /// No description provided for @phonebookAssociationKind. /// /// In fr, this message translates to: - /// **'Erreur lors de la suppression'** - String get raffleDeletingError; + /// **'Type d\'association :'** + String get phonebookAssociationKind; - /// No description provided for @raffleQuantity. + /// No description provided for @phonebookAssociationName. /// /// In fr, this message translates to: - /// **'Quantité'** - String get raffleQuantity; + /// **'Nom de l\'association'** + String get phonebookAssociationName; - /// No description provided for @raffleClose. + /// No description provided for @phonebookAssociations. /// /// In fr, this message translates to: - /// **'Fermer'** - String get raffleClose; + /// **'Associations'** + String get phonebookAssociations; - /// No description provided for @raffleOpen. + /// No description provided for @phonebookCancel. /// /// In fr, this message translates to: - /// **'Ouvrir'** - String get raffleOpen; + /// **'Annuler'** + String get phonebookCancel; - /// No description provided for @raffleAddTypeTicketSimple. + /// Permet de changer le mandat d'une association /// /// In fr, this message translates to: - /// **'Ajouter'** - String get raffleAddTypeTicketSimple; + /// **'Passer au mandat {year}'** + String phonebookChangeTermYear(int year); - /// No description provided for @raffleAddingError. + /// No description provided for @phonebookChangeTermConfirm. /// /// In fr, this message translates to: - /// **'Erreur lors de l\'ajout'** - String get raffleAddingError; + /// **'Êtes-vous sûr de vouloir changer tout le mandat ?\nCette action est irréversible !'** + String get phonebookChangeTermConfirm; - /// No description provided for @raffleEditTypeTicketSimple. + /// No description provided for @phonebookClose. /// /// In fr, this message translates to: - /// **'Modifier'** - String get raffleEditTypeTicketSimple; + /// **'Fermer'** + String get phonebookClose; - /// No description provided for @raffleFillField. + /// No description provided for @phonebookConfirm. /// /// In fr, this message translates to: - /// **'Le champ ne peut pas être vide'** - String get raffleFillField; + /// **'Confirmer'** + String get phonebookConfirm; - /// No description provided for @raffleWaiting. + /// No description provided for @phonebookCopied. /// /// In fr, this message translates to: - /// **'Chargement'** - String get raffleWaiting; + /// **'Copié dans le presse-papier'** + String get phonebookCopied; - /// No description provided for @raffleEditingError. + /// No description provided for @phonebookDeactivateAssociation. /// /// In fr, this message translates to: - /// **'Erreur lors de la modification'** - String get raffleEditingError; + /// **'Désactiver l\'association'** + String get phonebookDeactivateAssociation; - /// No description provided for @raffleAddedTicket. + /// No description provided for @phonebookDeactivatedAssociation. /// /// In fr, this message translates to: - /// **'Ticket ajouté'** - String get raffleAddedTicket; + /// **'Association désactivée'** + String get phonebookDeactivatedAssociation; - /// No description provided for @raffleEditedTicket. + /// No description provided for @phonebookDeactivatedAssociationWarning. /// /// In fr, this message translates to: - /// **'Ticket modifié'** - String get raffleEditedTicket; + /// **'Attention, cette association est désactivée, vous ne pouvez pas la modifier'** + String get phonebookDeactivatedAssociationWarning; - /// No description provided for @raffleAlreadyExistTicket. + /// Permet de désactiver une association /// /// In fr, this message translates to: - /// **'Le ticket existe déjà'** - String get raffleAlreadyExistTicket; + /// **'Désactiver l\'association {association} ?'** + String phonebookDeactivateSelectedAssociation(String association); - /// No description provided for @raffleNumberExpected. + /// No description provided for @phonebookDeactivatingError. /// /// In fr, this message translates to: - /// **'Un entier est attendu'** - String get raffleNumberExpected; + /// **'Erreur lors de la désactivation'** + String get phonebookDeactivatingError; - /// No description provided for @raffleDeletedTicket. + /// No description provided for @phonebookDetail. /// /// In fr, this message translates to: - /// **'Ticket supprimé'** - String get raffleDeletedTicket; + /// **'Détail :'** + String get phonebookDetail; - /// No description provided for @raffleAddPrize. + /// No description provided for @phonebookDelete. /// /// In fr, this message translates to: - /// **'Ajouter'** - String get raffleAddPrize; + /// **'Supprimer'** + String get phonebookDelete; - /// No description provided for @raffleEditPrize. + /// No description provided for @phonebookDeleteAssociation. /// /// In fr, this message translates to: - /// **'Modifier'** - String get raffleEditPrize; + /// **'Supprimer l\'association'** + String get phonebookDeleteAssociation; - /// No description provided for @raffleOpenRaffle. + /// Permet de supprimer une association /// /// In fr, this message translates to: - /// **'Ouvrir la tombola'** - String get raffleOpenRaffle; + /// **'Supprimer l\'association {association} ?'** + String phonebookDeleteSelectedAssociation(String association); - /// No description provided for @raffleCloseRaffle. + /// No description provided for @phonebookDeleteAssociationDescription. /// /// In fr, this message translates to: - /// **'Fermer la tombola'** - String get raffleCloseRaffle; + /// **'Ceci va supprimer l\'historique de l\'association'** + String get phonebookDeleteAssociationDescription; - /// No description provided for @raffleOpenRaffleDescription. + /// No description provided for @phonebookDeletedAssociation. /// /// In fr, this message translates to: - /// **'Vous allez ouvrir la tombola, les utilisateurs pourront acheter des tickets. Vous ne pourrez plus modifier la tombola. Êtes-vous sûr de vouloir continuer ?'** - String get raffleOpenRaffleDescription; + /// **'Association supprimée'** + String get phonebookDeletedAssociation; - /// No description provided for @raffleCloseRaffleDescription. + /// No description provided for @phonebookDeletedMember. /// /// In fr, this message translates to: - /// **'Vous allez fermer la tombola, les utilisateurs ne pourront plus acheter de tickets. Êtes-vous sûr de vouloir continuer ?'** - String get raffleCloseRaffleDescription; + /// **'Membre supprimé'** + String get phonebookDeletedMember; - /// No description provided for @raffleNoCurrentRaffle. + /// No description provided for @phonebookDeleteRole. /// /// In fr, this message translates to: - /// **'Il n\'y a aucune tombola en cours'** - String get raffleNoCurrentRaffle; + /// **'Supprimer le rôle'** + String get phonebookDeleteRole; - /// No description provided for @raffleBoughtTicket. + /// Permet de supprimer le rôle d'un utilisateur dans une association /// /// In fr, this message translates to: - /// **'Ticket acheté'** - String get raffleBoughtTicket; + /// **'Supprimer le rôle de l\'utilisateur {name} ?'** + String phonebookDeleteUserRole(String name); - /// No description provided for @raffleDrawingError. + /// No description provided for @phonebookDeactivating. /// /// In fr, this message translates to: - /// **'Erreur lors du tirage'** - String get raffleDrawingError; + /// **'Désactiver l\'association ?'** + String get phonebookDeactivating; - /// No description provided for @raffleInvalidPrice. + /// No description provided for @phonebookDeleting. /// /// In fr, this message translates to: - /// **'Le prix doit être supérieur à 0'** - String get raffleInvalidPrice; + /// **'Suppression'** + String get phonebookDeleting; - /// No description provided for @raffleMustBePositive. + /// No description provided for @phonebookDeletingError. /// /// In fr, this message translates to: - /// **'Le nombre doit être strictement positif'** - String get raffleMustBePositive; + /// **'Erreur lors de la suppression'** + String get phonebookDeletingError; - /// No description provided for @raffleDraw. + /// No description provided for @phonebookDescription. /// /// In fr, this message translates to: - /// **'Tirer'** - String get raffleDraw; + /// **'Description'** + String get phonebookDescription; - /// No description provided for @raffleDrawn. + /// No description provided for @phonebookEdit. /// /// In fr, this message translates to: - /// **'Tiré'** - String get raffleDrawn; + /// **'Modifier'** + String get phonebookEdit; - /// No description provided for @raffleError. + /// No description provided for @phonebookEditAssociationGroupement. /// /// In fr, this message translates to: - /// **'Erreur'** - String get raffleError; + /// **'Modifier le groupement d\'association'** + String get phonebookEditAssociationGroupement; - /// No description provided for @raffleGathered. + /// No description provided for @phonebookEditAssociationGroups. /// /// In fr, this message translates to: - /// **'Récolté'** - String get raffleGathered; + /// **'Gérer les groupes'** + String get phonebookEditAssociationGroups; - /// No description provided for @raffleTickets. + /// No description provided for @phonebookEditAssociationInfo. /// /// In fr, this message translates to: - /// **'Tickets'** - String get raffleTickets; + /// **'Modifier'** + String get phonebookEditAssociationInfo; - /// No description provided for @raffleTicket. + /// No description provided for @phonebookEditAssociationMembers. /// /// In fr, this message translates to: - /// **'ticket'** - String get raffleTicket; + /// **'Gérer les membres'** + String get phonebookEditAssociationMembers; - /// No description provided for @raffleWinner. + /// No description provided for @phonebookEditRole. /// /// In fr, this message translates to: - /// **'Gagnant'** - String get raffleWinner; + /// **'Modifier le rôle'** + String get phonebookEditRole; - /// No description provided for @raffleNoPrize. + /// No description provided for @phonebookEditMembership. /// /// In fr, this message translates to: - /// **'Aucun lot'** - String get raffleNoPrize; + /// **'Modifier le rôle'** + String get phonebookEditMembership; - /// No description provided for @raffleDeletePrize. + /// No description provided for @phonebookEmail. /// /// In fr, this message translates to: - /// **'Supprimer le lot'** - String get raffleDeletePrize; + /// **'Email :'** + String get phonebookEmail; - /// No description provided for @raffleDeletePrizeDescription. + /// No description provided for @phonebookEmailCopied. /// /// In fr, this message translates to: - /// **'Vous allez supprimer le lot, êtes-vous sûr de vouloir continuer ?'** - String get raffleDeletePrizeDescription; + /// **'Email copié dans le presse-papier'** + String get phonebookEmailCopied; - /// No description provided for @raffleDrawing. + /// No description provided for @phonebookEmptyApparentName. /// /// In fr, this message translates to: - /// **'Tirage'** - String get raffleDrawing; + /// **'Veuillez entrer un nom de role'** + String get phonebookEmptyApparentName; - /// No description provided for @raffleDrawingDescription. + /// No description provided for @phonebookEmptyFieldError. /// /// In fr, this message translates to: - /// **'Tirer le gagnant du lot ?'** - String get raffleDrawingDescription; + /// **'Un champ n\'est pas rempli'** + String get phonebookEmptyFieldError; - /// No description provided for @raffleDeleteTicket. + /// No description provided for @phonebookEmptyKindError. /// /// In fr, this message translates to: - /// **'Supprimer le ticket'** - String get raffleDeleteTicket; + /// **'Veuillez choisir un type d\'association'** + String get phonebookEmptyKindError; - /// No description provided for @raffleDeleteTicketDescription. + /// No description provided for @phonebookEmptyMember. /// /// In fr, this message translates to: - /// **'Vous allez supprimer le ticket, êtes-vous sûr de vouloir continuer ?'** - String get raffleDeleteTicketDescription; - - /// No description provided for @raffleWinningTickets. + /// **'Aucun membre sélectionné'** + String get phonebookEmptyMember; + + /// No description provided for @phonebookErrorAssociationLoading. /// /// In fr, this message translates to: - /// **'Tickets gagnants'** - String get raffleWinningTickets; + /// **'Erreur lors du chargement de l\'association'** + String get phonebookErrorAssociationLoading; - /// No description provided for @raffleNoWinningTicketYet. + /// No description provided for @phonebookErrorAssociationNameEmpty. /// /// In fr, this message translates to: - /// **'Les tickets gagnants seront affichés ici'** - String get raffleNoWinningTicketYet; + /// **'Veuillez entrer un nom d\'association'** + String get phonebookErrorAssociationNameEmpty; - /// No description provided for @raffleName. + /// No description provided for @phonebookErrorAssociationPicture. /// /// In fr, this message translates to: - /// **'Nom'** - String get raffleName; + /// **'Erreur lors de la modification de la photo d\'association'** + String get phonebookErrorAssociationPicture; - /// No description provided for @raffleDescription. + /// No description provided for @phonebookErrorKindsLoading. /// /// In fr, this message translates to: - /// **'Description'** - String get raffleDescription; + /// **'Erreur lors du chargement des types d\'association'** + String get phonebookErrorKindsLoading; - /// No description provided for @raffleBuyThisTicket. + /// No description provided for @phonebookErrorLoadAssociationList. /// /// In fr, this message translates to: - /// **'Acheter ce ticket'** - String get raffleBuyThisTicket; + /// **'Erreur lors du chargement de la liste des associations'** + String get phonebookErrorLoadAssociationList; - /// No description provided for @raffleLockedRaffle. + /// No description provided for @phonebookErrorLoadAssociationMember. /// /// In fr, this message translates to: - /// **'Tombola verrouillée'** - String get raffleLockedRaffle; + /// **'Erreur lors du chargement des membres de l\'association'** + String get phonebookErrorLoadAssociationMember; - /// No description provided for @raffleUnavailableRaffle. + /// No description provided for @phonebookErrorLoadAssociationPicture. /// /// In fr, this message translates to: - /// **'Tombola indisponible'** - String get raffleUnavailableRaffle; + /// **'Erreur lors du chargement de la photo d\'association'** + String get phonebookErrorLoadAssociationPicture; - /// No description provided for @raffleNotEnoughMoney. + /// No description provided for @phonebookErrorLoadProfilePicture. /// /// In fr, this message translates to: - /// **'Vous n\'avez pas assez d\'argent'** - String get raffleNotEnoughMoney; + /// **'Erreur'** + String get phonebookErrorLoadProfilePicture; - /// No description provided for @raffleWinnable. + /// No description provided for @phonebookErrorRoleTagsLoading. /// /// In fr, this message translates to: - /// **'gagnable'** - String get raffleWinnable; + /// **'Erreur lors du chargement des tags de rôle'** + String get phonebookErrorRoleTagsLoading; - /// No description provided for @raffleNoDescription. + /// No description provided for @phonebookExistingMembership. /// /// In fr, this message translates to: - /// **'Aucune description'** - String get raffleNoDescription; + /// **'Ce membre est déjà dans le mandat actuel'** + String get phonebookExistingMembership; - /// No description provided for @raffleAmount. + /// No description provided for @phonebookFilter. /// /// In fr, this message translates to: - /// **'Solde'** - String get raffleAmount; + /// **'Filtrer'** + String get phonebookFilter; - /// No description provided for @raffleLoading. + /// No description provided for @phonebookFilterDescription. /// /// In fr, this message translates to: - /// **'Chargement'** - String get raffleLoading; + /// **'Filtrer les associations par type'** + String get phonebookFilterDescription; - /// No description provided for @raffleTicketNumber. + /// No description provided for @phonebookFirstname. /// /// In fr, this message translates to: - /// **'Nombre de ticket'** - String get raffleTicketNumber; + /// **'Prénom :'** + String get phonebookFirstname; - /// No description provided for @rafflePrice. + /// No description provided for @phonebookGroupementDeleted. /// /// In fr, this message translates to: - /// **'Prix'** - String get rafflePrice; + /// **'Groupement d\'association supprimé'** + String get phonebookGroupementDeleted; - /// No description provided for @raffleEditRaffle. + /// No description provided for @phonebookGroupementDeleteError. /// /// In fr, this message translates to: - /// **'Modifier la tombola'** - String get raffleEditRaffle; + /// **'Erreur lors de la suppression du groupement d\'association'** + String get phonebookGroupementDeleteError; - /// No description provided for @raffleEdit. + /// No description provided for @phonebookGroupementName. /// /// In fr, this message translates to: - /// **'Modifier'** - String get raffleEdit; + /// **'Nom du groupement'** + String get phonebookGroupementName; - /// No description provided for @raffleAddPackTicket. + /// Permet de gérer les groupes d'une association /// /// In fr, this message translates to: - /// **'Ajouter un pack de ticket'** - String get raffleAddPackTicket; + /// **'Gérer les groupes de {association}'** + String phonebookGroups(String association); - /// No description provided for @recommendationRecommendation. + /// Année de mandat d'une association /// /// In fr, this message translates to: - /// **'Bons plans'** - String get recommendationRecommendation; + /// **'Mandat {year}'** + String phonebookTerm(int year); - /// No description provided for @recommendationTitle. + /// No description provided for @phonebookTermChangingError. /// /// In fr, this message translates to: - /// **'Titre'** - String get recommendationTitle; + /// **'Erreur lors du changement de mandat'** + String get phonebookTermChangingError; - /// No description provided for @recommendationLogo. + /// No description provided for @phonebookMember. /// /// In fr, this message translates to: - /// **'Logo'** - String get recommendationLogo; + /// **'Membre'** + String get phonebookMember; - /// No description provided for @recommendationCode. + /// No description provided for @phonebookMemberReordered. /// /// In fr, this message translates to: - /// **'Code'** - String get recommendationCode; + /// **'Membre réordonné'** + String get phonebookMemberReordered; - /// No description provided for @recommendationSummary. + /// Permet de gérer les membres d'une association /// /// In fr, this message translates to: - /// **'Court résumé'** - String get recommendationSummary; + /// **'Gérer les membres de {association}'** + String phonebookMembers(String association); - /// No description provided for @recommendationDescription. + /// No description provided for @phonebookMembershipAssociationError. /// /// In fr, this message translates to: - /// **'Description'** - String get recommendationDescription; + /// **'Veuillez choisir une association'** + String get phonebookMembershipAssociationError; - /// No description provided for @recommendationAdd. + /// No description provided for @phonebookMembershipRole. /// /// In fr, this message translates to: - /// **'Ajouter'** - String get recommendationAdd; + /// **'Rôle :'** + String get phonebookMembershipRole; - /// No description provided for @recommendationEdit. + /// No description provided for @phonebookMembershipRoleError. /// /// In fr, this message translates to: - /// **'Modifier'** - String get recommendationEdit; + /// **'Veuillez choisir un rôle'** + String get phonebookMembershipRoleError; - /// No description provided for @recommendationDelete. + /// Permet de modifier le rôle d'un membre dans une association /// /// In fr, this message translates to: - /// **'Supprimer'** - String get recommendationDelete; + /// **'Modifier le rôle de {name}'** + String phonebookModifyMembership(String name); - /// No description provided for @recommendationAddImage. + /// No description provided for @phonebookName. /// /// In fr, this message translates to: - /// **'Veuillez ajouter une image'** - String get recommendationAddImage; + /// **'Nom :'** + String get phonebookName; - /// No description provided for @recommendationAddedRecommendation. + /// No description provided for @phonebookNameCopied. /// /// In fr, this message translates to: - /// **'Bon plan ajouté'** - String get recommendationAddedRecommendation; + /// **'Nom et prénom copié dans le presse-papier'** + String get phonebookNameCopied; - /// No description provided for @recommendationEditedRecommendation. + /// No description provided for @phonebookNamePure. /// /// In fr, this message translates to: - /// **'Bon plan modifié'** - String get recommendationEditedRecommendation; + /// **'Nom'** + String get phonebookNamePure; - /// No description provided for @recommendationDeleteRecommendationConfirmation. + /// No description provided for @phonebookNewTerm. /// /// In fr, this message translates to: - /// **'Êtes-vous sûr de vouloir supprimer ce bon plan ?'** - String get recommendationDeleteRecommendationConfirmation; + /// **'Nouveau mandat'** + String get phonebookNewTerm; - /// No description provided for @recommendationDeleteRecommendation. + /// No description provided for @phonebookNewTermConfirmed. /// /// In fr, this message translates to: - /// **'Suppresion'** - String get recommendationDeleteRecommendation; + /// **'Mandat changé'** + String get phonebookNewTermConfirmed; - /// No description provided for @recommendationDeletingRecommendationError. + /// No description provided for @phonebookNickname. /// /// In fr, this message translates to: - /// **'Erreur lors de la suppression'** - String get recommendationDeletingRecommendationError; + /// **'Surnom :'** + String get phonebookNickname; - /// No description provided for @recommendationDeletedRecommendation. + /// No description provided for @phonebookNicknameCopied. /// /// In fr, this message translates to: - /// **'Bon plan supprimé'** - String get recommendationDeletedRecommendation; + /// **'Surnom copié dans le presse-papier'** + String get phonebookNicknameCopied; - /// No description provided for @recommendationIncorrectOrMissingFields. + /// No description provided for @phonebookNoAssociationFound. /// /// In fr, this message translates to: - /// **'Champs incorrects ou manquants'** - String get recommendationIncorrectOrMissingFields; + /// **'Aucune association trouvée'** + String get phonebookNoAssociationFound; - /// No description provided for @recommendationEditingError. + /// No description provided for @phonebookNoMember. /// /// In fr, this message translates to: - /// **'Échec de la modification'** - String get recommendationEditingError; + /// **'Aucun membre'** + String get phonebookNoMember; - /// No description provided for @recommendationAddingError. + /// No description provided for @phonebookNoMemberRole. /// /// In fr, this message translates to: - /// **'Échec de l\'ajout'** - String get recommendationAddingError; + /// **'Aucun role trouvé'** + String get phonebookNoMemberRole; - /// No description provided for @recommendationCopiedCode. + /// No description provided for @phonebookNoRoleTags. /// /// In fr, this message translates to: - /// **'Code de réduction copié'** - String get recommendationCopiedCode; + /// **'Aucun tag de rôle trouvé'** + String get phonebookNoRoleTags; - /// No description provided for @seedLibraryAdd. + /// No description provided for @phonebookPhone. /// /// In fr, this message translates to: - /// **'Ajouter'** - String get seedLibraryAdd; + /// **'Téléphone :'** + String get phonebookPhone; - /// No description provided for @seedLibraryAddedPlant. + /// No description provided for @phonebookPhonebook. /// /// In fr, this message translates to: - /// **'Plante ajoutée'** - String get seedLibraryAddedPlant; + /// **'Annuaire'** + String get phonebookPhonebook; - /// No description provided for @seedLibraryAddedSpecies. + /// No description provided for @phonebookPhonebookSearch. /// /// In fr, this message translates to: - /// **'Espèce ajoutée'** - String get seedLibraryAddedSpecies; + /// **'Rechercher'** + String get phonebookPhonebookSearch; - /// No description provided for @seedLibraryAddingError. + /// No description provided for @phonebookPhonebookSearchAssociation. /// /// In fr, this message translates to: - /// **'Erreur lors de l\'ajout'** - String get seedLibraryAddingError; + /// **'Association'** + String get phonebookPhonebookSearchAssociation; - /// No description provided for @seedLibraryAddPlant. + /// No description provided for @phonebookPhonebookSearchField. /// /// In fr, this message translates to: - /// **'Déposer une plante'** - String get seedLibraryAddPlant; + /// **'Rechercher :'** + String get phonebookPhonebookSearchField; - /// No description provided for @seedLibraryAddSpecies. + /// No description provided for @phonebookPhonebookSearchName. /// /// In fr, this message translates to: - /// **'Ajouter une espèce'** - String get seedLibraryAddSpecies; + /// **'Nom/Prénom/Surnom'** + String get phonebookPhonebookSearchName; - /// No description provided for @seedLibraryAll. + /// No description provided for @phonebookPhonebookSearchRole. /// /// In fr, this message translates to: - /// **'Toutes'** - String get seedLibraryAll; + /// **'Poste'** + String get phonebookPhonebookSearchRole; - /// No description provided for @seedLibraryAncestor. + /// No description provided for @phonebookPresidentRoleTag. /// /// In fr, this message translates to: - /// **'Ancêtre'** - String get seedLibraryAncestor; + /// **'Prez\''** + String get phonebookPresidentRoleTag; - /// No description provided for @seedLibraryAround. + /// No description provided for @phonebookPromoNotGiven. /// /// In fr, this message translates to: - /// **'environ'** - String get seedLibraryAround; + /// **'Promo non renseignée'** + String get phonebookPromoNotGiven; + + /// Année de promotion d'un membre + /// + /// In fr, this message translates to: + /// **'Promotion {year}'** + String phonebookPromotion(int year); + + /// No description provided for @phonebookReorderingError. + /// + /// In fr, this message translates to: + /// **'Erreur lors du réordonnement'** + String get phonebookReorderingError; + + /// No description provided for @phonebookResearch. + /// + /// In fr, this message translates to: + /// **'Rechercher'** + String get phonebookResearch; + + /// No description provided for @phonebookRolePure. + /// + /// In fr, this message translates to: + /// **'Rôle'** + String get phonebookRolePure; + + /// No description provided for @phonebookSearchUser. + /// + /// In fr, this message translates to: + /// **'Rechercher un utilisateur'** + String get phonebookSearchUser; + + /// No description provided for @phonebookTooHeavyAssociationPicture. + /// + /// In fr, this message translates to: + /// **'L\'image est trop lourde (max 4Mo)'** + String get phonebookTooHeavyAssociationPicture; + + /// No description provided for @phonebookUpdateGroups. + /// + /// In fr, this message translates to: + /// **'Mettre à jour les groupes'** + String get phonebookUpdateGroups; + + /// No description provided for @phonebookUpdatedAssociation. + /// + /// In fr, this message translates to: + /// **'Association modifiée'** + String get phonebookUpdatedAssociation; + + /// No description provided for @phonebookUpdatedAssociationPicture. + /// + /// In fr, this message translates to: + /// **'La photo d\'association a été changée'** + String get phonebookUpdatedAssociationPicture; + + /// No description provided for @phonebookUpdatedGroups. + /// + /// In fr, this message translates to: + /// **'Groupes mis à jour'** + String get phonebookUpdatedGroups; + + /// No description provided for @phonebookUpdatedMember. + /// + /// In fr, this message translates to: + /// **'Membre modifié'** + String get phonebookUpdatedMember; + + /// No description provided for @phonebookUpdatingError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de la modification'** + String get phonebookUpdatingError; + + /// No description provided for @phonebookValidation. + /// + /// In fr, this message translates to: + /// **'Valider'** + String get phonebookValidation; + + /// No description provided for @purchasesPurchases. + /// + /// In fr, this message translates to: + /// **'Achats'** + String get purchasesPurchases; + + /// No description provided for @purchasesResearch. + /// + /// In fr, this message translates to: + /// **'Rechercher'** + String get purchasesResearch; + + /// No description provided for @purchasesNoPurchasesFound. + /// + /// In fr, this message translates to: + /// **'Aucun achat trouvé'** + String get purchasesNoPurchasesFound; + + /// No description provided for @purchasesNoTickets. + /// + /// In fr, this message translates to: + /// **'Aucun ticket'** + String get purchasesNoTickets; + + /// No description provided for @purchasesTicketsError. + /// + /// In fr, this message translates to: + /// **'Erreur lors du chargement des tickets'** + String get purchasesTicketsError; + + /// No description provided for @purchasesPurchasesError. + /// + /// In fr, this message translates to: + /// **'Erreur lors du chargement des achats'** + String get purchasesPurchasesError; + + /// No description provided for @purchasesNoPurchases. + /// + /// In fr, this message translates to: + /// **'Aucun achat'** + String get purchasesNoPurchases; + + /// No description provided for @purchasesTimes. + /// + /// In fr, this message translates to: + /// **'fois'** + String get purchasesTimes; + + /// No description provided for @purchasesAlreadyUsed. + /// + /// In fr, this message translates to: + /// **'Déjà utilisé'** + String get purchasesAlreadyUsed; - /// No description provided for @seedLibraryAutumn. + /// No description provided for @purchasesNotPaid. /// /// In fr, this message translates to: - /// **'Automne'** - String get seedLibraryAutumn; + /// **'Non validé'** + String get purchasesNotPaid; - /// No description provided for @seedLibraryBorrowedPlant. + /// No description provided for @purchasesPleaseSelectProduct. /// /// In fr, this message translates to: - /// **'Plante empruntée'** - String get seedLibraryBorrowedPlant; + /// **'Veuillez sélectionner un produit'** + String get purchasesPleaseSelectProduct; - /// No description provided for @seedLibraryBorrowingDate. + /// No description provided for @purchasesProducts. /// /// In fr, this message translates to: - /// **'Date d\'emprunt :'** - String get seedLibraryBorrowingDate; + /// **'Produits'** + String get purchasesProducts; - /// No description provided for @seedLibraryBorrowPlant. + /// No description provided for @purchasesCancel. /// /// In fr, this message translates to: - /// **'Emprunter la plante'** - String get seedLibraryBorrowPlant; + /// **'Annuler'** + String get purchasesCancel; - /// No description provided for @seedLibraryCard. + /// No description provided for @purchasesValidate. /// /// In fr, this message translates to: - /// **'Carte'** - String get seedLibraryCard; + /// **'Valider'** + String get purchasesValidate; - /// No description provided for @seedLibraryChoosingAncestor. + /// No description provided for @purchasesLeftScan. /// /// In fr, this message translates to: - /// **'Veuillez choisir un ancêtre'** - String get seedLibraryChoosingAncestor; + /// **'Scans restants'** + String get purchasesLeftScan; - /// No description provided for @seedLibraryChoosingSpecies. + /// No description provided for @purchasesTag. /// /// In fr, this message translates to: - /// **'Veuillez choisir une espèce'** - String get seedLibraryChoosingSpecies; + /// **'Tag'** + String get purchasesTag; - /// No description provided for @seedLibraryChoosingSpeciesOrAncestor. + /// No description provided for @purchasesHistory. /// /// In fr, this message translates to: - /// **'Veuillez choisir une espèce ou un ancêtre'** - String get seedLibraryChoosingSpeciesOrAncestor; + /// **'Historique'** + String get purchasesHistory; - /// No description provided for @seedLibraryContact. + /// No description provided for @purchasesPleaseSelectSeller. /// /// In fr, this message translates to: - /// **'Contact :'** - String get seedLibraryContact; + /// **'Veuillez sélectionner un vendeur'** + String get purchasesPleaseSelectSeller; - /// No description provided for @seedLibraryDays. + /// No description provided for @purchasesNoTagGiven. /// /// In fr, this message translates to: - /// **'jours'** - String get seedLibraryDays; + /// **'Attention, aucun tag n\'a été entré'** + String get purchasesNoTagGiven; - /// No description provided for @seedLibraryDeadMsg. + /// No description provided for @purchasesTickets. /// /// In fr, this message translates to: - /// **'Voulez-vous déclarer la plante morte ?'** - String get seedLibraryDeadMsg; + /// **'Tickets'** + String get purchasesTickets; - /// No description provided for @seedLibraryDeadPlant. + /// No description provided for @purchasesNoScannableProducts. /// /// In fr, this message translates to: - /// **'Plante morte'** - String get seedLibraryDeadPlant; + /// **'Aucun produit scannable'** + String get purchasesNoScannableProducts; - /// No description provided for @seedLibraryDeathDate. + /// No description provided for @purchasesLoading. /// /// In fr, this message translates to: - /// **'Date de mort'** - String get seedLibraryDeathDate; + /// **'En attente de scan'** + String get purchasesLoading; - /// No description provided for @seedLibraryDeletedSpecies. + /// No description provided for @purchasesScan. /// /// In fr, this message translates to: - /// **'Espèce supprimée'** - String get seedLibraryDeletedSpecies; + /// **'Scanner'** + String get purchasesScan; - /// No description provided for @seedLibraryDeleteSpecies. + /// No description provided for @raffleRaffle. /// /// In fr, this message translates to: - /// **'Supprimer l\'espèce ?'** - String get seedLibraryDeleteSpecies; + /// **'Tombola'** + String get raffleRaffle; - /// No description provided for @seedLibraryDeleting. + /// No description provided for @rafflePrize. /// /// In fr, this message translates to: - /// **'Suppression'** - String get seedLibraryDeleting; + /// **'Lot'** + String get rafflePrize; - /// No description provided for @seedLibraryDeletingError. + /// No description provided for @rafflePrizes. /// /// In fr, this message translates to: - /// **'Erreur lors de la suppression'** - String get seedLibraryDeletingError; + /// **'Lots'** + String get rafflePrizes; - /// No description provided for @seedLibraryDepositNotAvailable. + /// No description provided for @raffleActualRaffles. /// /// In fr, this message translates to: - /// **'Le dépôt de plantes n\'est pas possible sans emprunter une plante au préalable'** - String get seedLibraryDepositNotAvailable; + /// **'Tombola en cours'** + String get raffleActualRaffles; - /// No description provided for @seedLibraryDescription. + /// No description provided for @rafflePastRaffles. /// /// In fr, this message translates to: - /// **'Description'** - String get seedLibraryDescription; + /// **'Tombola passés'** + String get rafflePastRaffles; - /// No description provided for @seedLibraryDifficulty. + /// No description provided for @raffleYourTickets. /// /// In fr, this message translates to: - /// **'Difficulté :'** - String get seedLibraryDifficulty; + /// **'Tous vos tickets'** + String get raffleYourTickets; - /// No description provided for @seedLibraryEdit. + /// No description provided for @raffleCreateMenu. /// /// In fr, this message translates to: - /// **'Modifier'** - String get seedLibraryEdit; + /// **'Menu de Création'** + String get raffleCreateMenu; - /// No description provided for @seedLibraryEditedPlant. + /// No description provided for @raffleNextRaffles. /// /// In fr, this message translates to: - /// **'Plante modifiée'** - String get seedLibraryEditedPlant; + /// **'Prochaines tombolas'** + String get raffleNextRaffles; - /// No description provided for @seedLibraryEditInformation. + /// No description provided for @raffleNoTicket. /// /// In fr, this message translates to: - /// **'Modifier les informations'** - String get seedLibraryEditInformation; + /// **'Vous n\'avez pas de ticket'** + String get raffleNoTicket; - /// No description provided for @seedLibraryEditingError. + /// No description provided for @raffleSeeRaffleDetail. /// /// In fr, this message translates to: - /// **'Erreur lors de la modification'** - String get seedLibraryEditingError; + /// **'Voir lots/tickets'** + String get raffleSeeRaffleDetail; - /// No description provided for @seedLibraryEditSpecies. + /// No description provided for @raffleActualPrize. /// /// In fr, this message translates to: - /// **'Modifier l\'espèce'** - String get seedLibraryEditSpecies; + /// **'Lots actuels'** + String get raffleActualPrize; - /// No description provided for @seedLibraryEmptyDifficultyError. + /// No description provided for @raffleMajorPrize. /// /// In fr, this message translates to: - /// **'Veuillez choisir une difficulté'** - String get seedLibraryEmptyDifficultyError; + /// **'Lot Majeurs'** + String get raffleMajorPrize; - /// No description provided for @seedLibraryEmptyFieldError. + /// No description provided for @raffleTakeTickets. /// /// In fr, this message translates to: - /// **'Veuillez remplir tous les champs'** - String get seedLibraryEmptyFieldError; + /// **'Prendre vos tickets'** + String get raffleTakeTickets; - /// No description provided for @seedLibraryEmptyTypeError. + /// No description provided for @raffleNoTicketBuyable. /// /// In fr, this message translates to: - /// **'Veuillez choisir un type de plante'** - String get seedLibraryEmptyTypeError; + /// **'Vous ne pouvez pas achetez de billets pour l\'instant'** + String get raffleNoTicketBuyable; - /// No description provided for @seedLibraryEndMonth. + /// No description provided for @raffleNoCurrentPrize. /// /// In fr, this message translates to: - /// **'Mois de fin :'** - String get seedLibraryEndMonth; + /// **'Il n\'y a aucun lots actuellement'** + String get raffleNoCurrentPrize; - /// No description provided for @seedLibraryFacebookUrl. + /// No description provided for @raffleModifTombola. /// /// In fr, this message translates to: - /// **'Lien Facebook'** - String get seedLibraryFacebookUrl; + /// **'Vous pouvez modifiez vos tombolas ou en créer de nouvelles, toute décision doit ensuite être prise par les admins'** + String get raffleModifTombola; - /// No description provided for @seedLibraryFilters. + /// No description provided for @raffleCreateYourRaffle. /// /// In fr, this message translates to: - /// **'Filtres'** - String get seedLibraryFilters; + /// **'Votre menu de création de tombolas'** + String get raffleCreateYourRaffle; - /// No description provided for @seedLibraryForum. + /// No description provided for @rafflePossiblePrice. /// /// In fr, this message translates to: - /// **'Oskour maman j\'ai tué ma plante - Forum d\'aide'** - String get seedLibraryForum; + /// **'Prix possible'** + String get rafflePossiblePrice; - /// No description provided for @seedLibraryForumUrl. + /// No description provided for @raffleInformation. /// /// In fr, this message translates to: - /// **'Lien Forum'** - String get seedLibraryForumUrl; + /// **'Information et Statistiques'** + String get raffleInformation; - /// No description provided for @seedLibraryHelpSheets. + /// No description provided for @raffleAccounts. /// /// In fr, this message translates to: - /// **'Fiches sur les plantes'** - String get seedLibraryHelpSheets; + /// **'Comptes'** + String get raffleAccounts; - /// No description provided for @seedLibraryInformation. + /// No description provided for @raffleAdd. /// /// In fr, this message translates to: - /// **'Informations :'** - String get seedLibraryInformation; + /// **'Ajouter'** + String get raffleAdd; - /// No description provided for @seedLibraryMaturationTime. + /// No description provided for @raffleUpdatedAmount. /// /// In fr, this message translates to: - /// **'Temps de maturation'** - String get seedLibraryMaturationTime; + /// **'Montant mis à jour'** + String get raffleUpdatedAmount; - /// No description provided for @seedLibraryMonthJan. + /// No description provided for @raffleUpdatingError. /// /// In fr, this message translates to: - /// **'Janvier'** - String get seedLibraryMonthJan; + /// **'Erreur lors de la mise à jour'** + String get raffleUpdatingError; - /// No description provided for @seedLibraryMonthFeb. + /// No description provided for @raffleDeletedPrize. /// /// In fr, this message translates to: - /// **'Février'** - String get seedLibraryMonthFeb; + /// **'Lot supprimé'** + String get raffleDeletedPrize; - /// No description provided for @seedLibraryMonthMar. + /// No description provided for @raffleDeletingError. /// /// In fr, this message translates to: - /// **'Mars'** - String get seedLibraryMonthMar; + /// **'Erreur lors de la suppression'** + String get raffleDeletingError; - /// No description provided for @seedLibraryMonthApr. + /// No description provided for @raffleQuantity. /// /// In fr, this message translates to: - /// **'Avril'** - String get seedLibraryMonthApr; + /// **'Quantité'** + String get raffleQuantity; - /// No description provided for @seedLibraryMonthMay. + /// No description provided for @raffleClose. /// /// In fr, this message translates to: - /// **'Mai'** - String get seedLibraryMonthMay; + /// **'Fermer'** + String get raffleClose; - /// No description provided for @seedLibraryMonthJun. + /// No description provided for @raffleOpen. /// /// In fr, this message translates to: - /// **'Juin'** - String get seedLibraryMonthJun; + /// **'Ouvrir'** + String get raffleOpen; - /// No description provided for @seedLibraryMonthJul. + /// No description provided for @raffleAddTypeTicketSimple. /// /// In fr, this message translates to: - /// **'Juillet'** - String get seedLibraryMonthJul; + /// **'Ajouter'** + String get raffleAddTypeTicketSimple; - /// No description provided for @seedLibraryMonthAug. + /// No description provided for @raffleAddingError. /// /// In fr, this message translates to: - /// **'Août'** - String get seedLibraryMonthAug; + /// **'Erreur lors de l\'ajout'** + String get raffleAddingError; - /// No description provided for @seedLibraryMonthSep. + /// No description provided for @raffleEditTypeTicketSimple. /// /// In fr, this message translates to: - /// **'Septembre'** - String get seedLibraryMonthSep; + /// **'Modifier'** + String get raffleEditTypeTicketSimple; - /// No description provided for @seedLibraryMonthOct. + /// No description provided for @raffleFillField. /// /// In fr, this message translates to: - /// **'Octobre'** - String get seedLibraryMonthOct; + /// **'Le champ ne peut pas être vide'** + String get raffleFillField; - /// No description provided for @seedLibraryMonthNov. + /// No description provided for @raffleWaiting. /// /// In fr, this message translates to: - /// **'Novembre'** - String get seedLibraryMonthNov; + /// **'Chargement'** + String get raffleWaiting; - /// No description provided for @seedLibraryMonthDec. + /// No description provided for @raffleEditingError. /// /// In fr, this message translates to: - /// **'Décembre'** - String get seedLibraryMonthDec; + /// **'Erreur lors de la modification'** + String get raffleEditingError; - /// No description provided for @seedLibraryMyPlants. + /// No description provided for @raffleAddedTicket. /// /// In fr, this message translates to: - /// **'Mes plantes'** - String get seedLibraryMyPlants; + /// **'Ticket ajouté'** + String get raffleAddedTicket; - /// No description provided for @seedLibraryName. + /// No description provided for @raffleEditedTicket. /// /// In fr, this message translates to: - /// **'Nom'** - String get seedLibraryName; + /// **'Ticket modifié'** + String get raffleEditedTicket; - /// No description provided for @seedLibraryNbSeedsRecommended. + /// No description provided for @raffleAlreadyExistTicket. /// /// In fr, this message translates to: - /// **'Nombre de graines recommandées'** - String get seedLibraryNbSeedsRecommended; + /// **'Le ticket existe déjà'** + String get raffleAlreadyExistTicket; - /// No description provided for @seedLibraryNbSeedsRecommendedError. + /// No description provided for @raffleNumberExpected. /// /// In fr, this message translates to: - /// **'Veuillez entrer un nombre de graines recommandé supérieur à 0'** - String get seedLibraryNbSeedsRecommendedError; + /// **'Un entier est attendu'** + String get raffleNumberExpected; - /// No description provided for @seedLibraryNoDateError. + /// No description provided for @raffleDeletedTicket. /// /// In fr, this message translates to: - /// **'Veuillez entrer une date'** - String get seedLibraryNoDateError; + /// **'Ticket supprimé'** + String get raffleDeletedTicket; - /// No description provided for @seedLibraryNoFilteredPlants. + /// No description provided for @raffleAddPrize. /// /// In fr, this message translates to: - /// **'Aucune plante ne correspond à votre recherche. Essayez d\'autres filtres.'** - String get seedLibraryNoFilteredPlants; + /// **'Ajouter'** + String get raffleAddPrize; - /// No description provided for @seedLibraryNoMorePlant. + /// No description provided for @raffleEditPrize. /// /// In fr, this message translates to: - /// **'Aucune plante n\'est disponible'** - String get seedLibraryNoMorePlant; + /// **'Modifier'** + String get raffleEditPrize; - /// No description provided for @seedLibraryNoPersonalPlants. + /// No description provided for @raffleOpenRaffle. /// /// In fr, this message translates to: - /// **'Vous n\'avez pas encore de plantes dans votre grainothèque. Vous pouvez en ajouter en allant dans les stocks.'** - String get seedLibraryNoPersonalPlants; + /// **'Ouvrir la tombola'** + String get raffleOpenRaffle; - /// No description provided for @seedLibraryNoSpecies. + /// No description provided for @raffleCloseRaffle. /// /// In fr, this message translates to: - /// **'Aucune espèce trouvée'** - String get seedLibraryNoSpecies; + /// **'Fermer la tombola'** + String get raffleCloseRaffle; - /// No description provided for @seedLibraryNoStockPlants. + /// No description provided for @raffleOpenRaffleDescription. /// /// In fr, this message translates to: - /// **'Aucune plante disponible dans le stock'** - String get seedLibraryNoStockPlants; + /// **'Vous allez ouvrir la tombola, les utilisateurs pourront acheter des tickets. Vous ne pourrez plus modifier la tombola. Êtes-vous sûr de vouloir continuer ?'** + String get raffleOpenRaffleDescription; - /// No description provided for @seedLibraryNotes. + /// No description provided for @raffleCloseRaffleDescription. /// /// In fr, this message translates to: - /// **'Notes'** - String get seedLibraryNotes; + /// **'Vous allez fermer la tombola, les utilisateurs ne pourront plus acheter de tickets. Êtes-vous sûr de vouloir continuer ?'** + String get raffleCloseRaffleDescription; - /// No description provided for @seedLibraryOk. + /// No description provided for @raffleNoCurrentRaffle. /// /// In fr, this message translates to: - /// **'OK'** - String get seedLibraryOk; + /// **'Il n\'y a aucune tombola en cours'** + String get raffleNoCurrentRaffle; - /// No description provided for @seedLibraryPlantationPeriod. + /// No description provided for @raffleBoughtTicket. /// /// In fr, this message translates to: - /// **'Période de plantation :'** - String get seedLibraryPlantationPeriod; + /// **'Ticket acheté'** + String get raffleBoughtTicket; - /// No description provided for @seedLibraryPlantationType. + /// No description provided for @raffleDrawingError. /// /// In fr, this message translates to: - /// **'Type de plantation :'** - String get seedLibraryPlantationType; + /// **'Erreur lors du tirage'** + String get raffleDrawingError; - /// No description provided for @seedLibraryPlantDetail. + /// No description provided for @raffleInvalidPrice. /// /// In fr, this message translates to: - /// **'Détail de la plante'** - String get seedLibraryPlantDetail; + /// **'Le prix doit être supérieur à 0'** + String get raffleInvalidPrice; - /// No description provided for @seedLibraryPlantingDate. + /// No description provided for @raffleMustBePositive. /// /// In fr, this message translates to: - /// **'Date de plantation'** - String get seedLibraryPlantingDate; + /// **'Le nombre doit être strictement positif'** + String get raffleMustBePositive; - /// No description provided for @seedLibraryPlantingNow. + /// No description provided for @raffleDraw. /// /// In fr, this message translates to: - /// **'Je la plante maintenant'** - String get seedLibraryPlantingNow; + /// **'Tirer'** + String get raffleDraw; - /// No description provided for @seedLibraryPrefix. + /// No description provided for @raffleDrawn. /// /// In fr, this message translates to: - /// **'Préfixe'** - String get seedLibraryPrefix; + /// **'Tiré'** + String get raffleDrawn; - /// No description provided for @seedLibraryPrefixError. + /// No description provided for @raffleError. /// /// In fr, this message translates to: - /// **'Prefixe déjà utilisé'** - String get seedLibraryPrefixError; + /// **'Erreur'** + String get raffleError; - /// No description provided for @seedLibraryPrefixLengthError. + /// No description provided for @raffleGathered. /// /// In fr, this message translates to: - /// **'Le préfixe doit faire 3 caractères'** - String get seedLibraryPrefixLengthError; + /// **'Récolté'** + String get raffleGathered; - /// No description provided for @seedLibraryPropagationMethod. + /// No description provided for @raffleTickets. /// /// In fr, this message translates to: - /// **'Méthode de propagation :'** - String get seedLibraryPropagationMethod; + /// **'Tickets'** + String get raffleTickets; - /// No description provided for @seedLibraryReference. + /// No description provided for @raffleTicket. /// /// In fr, this message translates to: - /// **'Référence :'** - String get seedLibraryReference; + /// **'ticket'** + String get raffleTicket; - /// No description provided for @seedLibraryRemovedPlant. + /// No description provided for @raffleWinner. /// /// In fr, this message translates to: - /// **'Plante supprimée'** - String get seedLibraryRemovedPlant; + /// **'Gagnant'** + String get raffleWinner; - /// No description provided for @seedLibraryRemovingError. + /// No description provided for @raffleNoPrize. /// /// In fr, this message translates to: - /// **'Erreur lors de la suppression'** - String get seedLibraryRemovingError; + /// **'Aucun lot'** + String get raffleNoPrize; - /// No description provided for @seedLibraryResearch. + /// No description provided for @raffleDeletePrize. /// /// In fr, this message translates to: - /// **'Recherche'** - String get seedLibraryResearch; + /// **'Supprimer le lot'** + String get raffleDeletePrize; - /// No description provided for @seedLibrarySaveChanges. + /// No description provided for @raffleDeletePrizeDescription. /// /// In fr, this message translates to: - /// **'Sauvegarder les modifications'** - String get seedLibrarySaveChanges; + /// **'Vous allez supprimer le lot, êtes-vous sûr de vouloir continuer ?'** + String get raffleDeletePrizeDescription; - /// No description provided for @seedLibrarySeason. + /// No description provided for @raffleDrawing. /// /// In fr, this message translates to: - /// **'Saison :'** - String get seedLibrarySeason; + /// **'Tirage'** + String get raffleDrawing; - /// No description provided for @seedLibrarySeed. + /// No description provided for @raffleDrawingDescription. /// /// In fr, this message translates to: - /// **'Graine'** - String get seedLibrarySeed; + /// **'Tirer le gagnant du lot ?'** + String get raffleDrawingDescription; - /// No description provided for @seedLibrarySeeds. + /// No description provided for @raffleDeleteTicket. /// /// In fr, this message translates to: - /// **'graines'** - String get seedLibrarySeeds; + /// **'Supprimer le ticket'** + String get raffleDeleteTicket; - /// No description provided for @seedLibrarySeedDeposit. + /// No description provided for @raffleDeleteTicketDescription. /// /// In fr, this message translates to: - /// **'Dépôt de plantes'** - String get seedLibrarySeedDeposit; + /// **'Vous allez supprimer le ticket, êtes-vous sûr de vouloir continuer ?'** + String get raffleDeleteTicketDescription; - /// No description provided for @seedLibrarySeedLibrary. + /// No description provided for @raffleWinningTickets. /// /// In fr, this message translates to: - /// **'Grainothèque'** - String get seedLibrarySeedLibrary; + /// **'Tickets gagnants'** + String get raffleWinningTickets; - /// No description provided for @seedLibrarySeedQuantitySimple. + /// No description provided for @raffleNoWinningTicketYet. /// /// In fr, this message translates to: - /// **'Quantité de graines'** - String get seedLibrarySeedQuantitySimple; + /// **'Les tickets gagnants seront affichés ici'** + String get raffleNoWinningTicketYet; - /// No description provided for @seedLibrarySeedQuantity. + /// No description provided for @raffleName. /// /// In fr, this message translates to: - /// **'Quantité de graines :'** - String get seedLibrarySeedQuantity; + /// **'Nom'** + String get raffleName; - /// No description provided for @seedLibraryShowDeadPlants. + /// No description provided for @raffleDescription. /// /// In fr, this message translates to: - /// **'Afficher les plantes mortes'** - String get seedLibraryShowDeadPlants; + /// **'Description'** + String get raffleDescription; - /// No description provided for @seedLibrarySpecies. + /// No description provided for @raffleBuyThisTicket. /// /// In fr, this message translates to: - /// **'Espèce :'** - String get seedLibrarySpecies; + /// **'Acheter ce ticket'** + String get raffleBuyThisTicket; - /// No description provided for @seedLibrarySpeciesHelp. + /// No description provided for @raffleLockedRaffle. /// /// In fr, this message translates to: - /// **'Aide sur l\'espèce'** - String get seedLibrarySpeciesHelp; + /// **'Tombola verrouillée'** + String get raffleLockedRaffle; - /// No description provided for @seedLibrarySpeciesPlural. + /// No description provided for @raffleUnavailableRaffle. /// /// In fr, this message translates to: - /// **'Espèces'** - String get seedLibrarySpeciesPlural; + /// **'Tombola indisponible'** + String get raffleUnavailableRaffle; - /// No description provided for @seedLibrarySpeciesSimple. + /// No description provided for @raffleNotEnoughMoney. /// /// In fr, this message translates to: - /// **'Espèce'** - String get seedLibrarySpeciesSimple; + /// **'Vous n\'avez pas assez d\'argent'** + String get raffleNotEnoughMoney; - /// No description provided for @seedLibrarySpeciesType. + /// No description provided for @raffleWinnable. /// /// In fr, this message translates to: - /// **'Type d\'espèce :'** - String get seedLibrarySpeciesType; + /// **'gagnable'** + String get raffleWinnable; - /// No description provided for @seedLibrarySpring. + /// No description provided for @raffleNoDescription. /// /// In fr, this message translates to: - /// **'Printemps'** - String get seedLibrarySpring; + /// **'Aucune description'** + String get raffleNoDescription; - /// No description provided for @seedLibraryStartMonth. + /// No description provided for @raffleAmount. /// /// In fr, this message translates to: - /// **'Mois de début :'** - String get seedLibraryStartMonth; + /// **'Solde'** + String get raffleAmount; - /// No description provided for @seedLibraryStock. + /// No description provided for @raffleLoading. /// /// In fr, this message translates to: - /// **'Stock disponible'** - String get seedLibraryStock; + /// **'Chargement'** + String get raffleLoading; - /// No description provided for @seedLibrarySummer. + /// No description provided for @raffleTicketNumber. /// /// In fr, this message translates to: - /// **'Été'** - String get seedLibrarySummer; + /// **'Nombre de ticket'** + String get raffleTicketNumber; - /// No description provided for @seedLibraryStocks. + /// No description provided for @rafflePrice. /// /// In fr, this message translates to: - /// **'Stocks'** - String get seedLibraryStocks; + /// **'Prix'** + String get rafflePrice; - /// No description provided for @seedLibraryTimeUntilMaturation. + /// No description provided for @raffleEditRaffle. /// /// In fr, this message translates to: - /// **'Temps avant maturation :'** - String get seedLibraryTimeUntilMaturation; + /// **'Modifier la tombola'** + String get raffleEditRaffle; - /// No description provided for @seedLibraryType. + /// No description provided for @raffleEdit. /// /// In fr, this message translates to: - /// **'Type :'** - String get seedLibraryType; + /// **'Modifier'** + String get raffleEdit; - /// No description provided for @seedLibraryUnableToOpen. + /// No description provided for @raffleAddPackTicket. /// /// In fr, this message translates to: - /// **'Impossible d\'ouvrir le lien'** - String get seedLibraryUnableToOpen; + /// **'Ajouter un pack de ticket'** + String get raffleAddPackTicket; - /// No description provided for @seedLibraryUpdate. + /// No description provided for @recommendationRecommendation. /// /// In fr, this message translates to: - /// **'Modifier'** - String get seedLibraryUpdate; + /// **'Bons plans'** + String get recommendationRecommendation; - /// No description provided for @seedLibraryUpdatedInformation. + /// No description provided for @recommendationTitle. /// /// In fr, this message translates to: - /// **'Informations modifiées'** - String get seedLibraryUpdatedInformation; + /// **'Titre'** + String get recommendationTitle; - /// No description provided for @seedLibraryUpdatedSpecies. + /// No description provided for @recommendationLogo. /// /// In fr, this message translates to: - /// **'Espèce modifiée'** - String get seedLibraryUpdatedSpecies; + /// **'Logo'** + String get recommendationLogo; - /// No description provided for @seedLibraryUpdatedPlant. + /// No description provided for @recommendationCode. /// /// In fr, this message translates to: - /// **'Plante modifiée'** - String get seedLibraryUpdatedPlant; + /// **'Code'** + String get recommendationCode; - /// No description provided for @seedLibraryUpdatingError. + /// No description provided for @recommendationSummary. /// /// In fr, this message translates to: - /// **'Erreur lors de la modification'** - String get seedLibraryUpdatingError; + /// **'Court résumé'** + String get recommendationSummary; - /// No description provided for @seedLibraryWinter. + /// No description provided for @recommendationDescription. /// /// In fr, this message translates to: - /// **'Hiver'** - String get seedLibraryWinter; + /// **'Description'** + String get recommendationDescription; - /// No description provided for @seedLibraryWriteReference. + /// No description provided for @recommendationAdd. /// /// In fr, this message translates to: - /// **'Veuillez écrire la référence suivante : '** - String get seedLibraryWriteReference; + /// **'Ajouter'** + String get recommendationAdd; - /// No description provided for @settingsAccount. + /// No description provided for @recommendationEdit. /// /// In fr, this message translates to: - /// **'Compte'** - String get settingsAccount; + /// **'Modifier'** + String get recommendationEdit; - /// No description provided for @settingsAddProfilePicture. + /// No description provided for @recommendationDelete. /// /// In fr, this message translates to: - /// **'Ajouter une photo'** - String get settingsAddProfilePicture; + /// **'Supprimer'** + String get recommendationDelete; - /// No description provided for @settingsAdmin. + /// No description provided for @recommendationAddImage. /// /// In fr, this message translates to: - /// **'Administrateur'** - String get settingsAdmin; + /// **'Veuillez ajouter une image'** + String get recommendationAddImage; - /// No description provided for @settingsAskHelp. + /// No description provided for @recommendationAddedRecommendation. /// /// In fr, this message translates to: - /// **'Demander de l\'aide'** - String get settingsAskHelp; + /// **'Bon plan ajouté'** + String get recommendationAddedRecommendation; - /// No description provided for @settingsAssociation. + /// No description provided for @recommendationEditedRecommendation. /// /// In fr, this message translates to: - /// **'Association'** - String get settingsAssociation; + /// **'Bon plan modifié'** + String get recommendationEditedRecommendation; - /// No description provided for @settingsBirthday. + /// No description provided for @recommendationDeleteRecommendationConfirmation. /// /// In fr, this message translates to: - /// **'Date de naissance'** - String get settingsBirthday; + /// **'Êtes-vous sûr de vouloir supprimer ce bon plan ?'** + String get recommendationDeleteRecommendationConfirmation; - /// No description provided for @settingsBugs. + /// No description provided for @recommendationDeleteRecommendation. /// /// In fr, this message translates to: - /// **'Bugs'** - String get settingsBugs; + /// **'Suppresion'** + String get recommendationDeleteRecommendation; - /// No description provided for @settingsChangePassword. + /// No description provided for @recommendationDeletingRecommendationError. /// /// In fr, this message translates to: - /// **'Changer de mot de passe'** - String get settingsChangePassword; + /// **'Erreur lors de la suppression'** + String get recommendationDeletingRecommendationError; - /// No description provided for @settingsChangingPassword. + /// No description provided for @recommendationDeletedRecommendation. /// /// In fr, this message translates to: - /// **'Voulez-vous vraiment changer votre mot de passe ?'** - String get settingsChangingPassword; + /// **'Bon plan supprimé'** + String get recommendationDeletedRecommendation; - /// No description provided for @settingsConfirmPassword. + /// No description provided for @recommendationIncorrectOrMissingFields. /// /// In fr, this message translates to: - /// **'Confirmer le mot de passe'** - String get settingsConfirmPassword; + /// **'Champs incorrects ou manquants'** + String get recommendationIncorrectOrMissingFields; - /// No description provided for @settingsCopied. + /// No description provided for @recommendationEditingError. /// /// In fr, this message translates to: - /// **'Copié !'** - String get settingsCopied; + /// **'Échec de la modification'** + String get recommendationEditingError; - /// No description provided for @settingsDarkMode. + /// No description provided for @recommendationAddingError. /// /// In fr, this message translates to: - /// **'Mode sombre'** - String get settingsDarkMode; + /// **'Échec de l\'ajout'** + String get recommendationAddingError; - /// No description provided for @settingsDarkModeOff. + /// No description provided for @recommendationCopiedCode. /// /// In fr, this message translates to: - /// **'Désactivé'** - String get settingsDarkModeOff; + /// **'Code de réduction copié'** + String get recommendationCopiedCode; - /// No description provided for @settingsDeleteLogs. + /// No description provided for @seedLibraryAdd. /// /// In fr, this message translates to: - /// **'Supprimer les logs ?'** - String get settingsDeleteLogs; + /// **'Ajouter'** + String get seedLibraryAdd; - /// No description provided for @settingsDeleteNotificationLogs. + /// No description provided for @seedLibraryAddedPlant. /// /// In fr, this message translates to: - /// **'Supprimer les logs des notifications ?'** - String get settingsDeleteNotificationLogs; + /// **'Plante ajoutée'** + String get seedLibraryAddedPlant; - /// No description provided for @settingsDetelePersonalData. + /// No description provided for @seedLibraryAddedSpecies. /// /// In fr, this message translates to: - /// **'Supprimer mes données personnelles'** - String get settingsDetelePersonalData; + /// **'Espèce ajoutée'** + String get seedLibraryAddedSpecies; - /// No description provided for @settingsDetelePersonalDataDesc. + /// No description provided for @seedLibraryAddingError. /// /// In fr, this message translates to: - /// **'Cette action notifie l\'administrateur que vous souhaitez supprimer vos données personnelles.'** - String get settingsDetelePersonalDataDesc; + /// **'Erreur lors de l\'ajout'** + String get seedLibraryAddingError; - /// No description provided for @settingsDeleting. + /// No description provided for @seedLibraryAddPlant. /// /// In fr, this message translates to: - /// **'Suppresion'** - String get settingsDeleting; + /// **'Déposer une plante'** + String get seedLibraryAddPlant; - /// No description provided for @settingsEdit. + /// No description provided for @seedLibraryAddSpecies. /// /// In fr, this message translates to: - /// **'Modifier'** - String get settingsEdit; + /// **'Ajouter une espèce'** + String get seedLibraryAddSpecies; - /// No description provided for @settingsEditAccount. + /// No description provided for @seedLibraryAll. /// /// In fr, this message translates to: - /// **'Modifier mon profil'** - String get settingsEditAccount; + /// **'Toutes'** + String get seedLibraryAll; - /// No description provided for @settingsEmail. + /// No description provided for @seedLibraryAncestor. /// /// In fr, this message translates to: - /// **'Email'** - String get settingsEmail; + /// **'Ancêtre'** + String get seedLibraryAncestor; - /// No description provided for @settingsEmptyField. + /// No description provided for @seedLibraryAround. /// /// In fr, this message translates to: - /// **'Ce champ ne peut pas être vide'** - String get settingsEmptyField; + /// **'environ'** + String get seedLibraryAround; - /// No description provided for @settingsErrorProfilePicture. + /// No description provided for @seedLibraryAutumn. /// /// In fr, this message translates to: - /// **'Erreur lors de la modification de la photo de profil'** - String get settingsErrorProfilePicture; + /// **'Automne'** + String get seedLibraryAutumn; - /// No description provided for @settingsErrorSendingDemand. + /// No description provided for @seedLibraryBorrowedPlant. /// /// In fr, this message translates to: - /// **'Erreur lors de l\'envoi de la demande'** - String get settingsErrorSendingDemand; + /// **'Plante empruntée'** + String get seedLibraryBorrowedPlant; - /// No description provided for @settingsEventsIcal. + /// No description provided for @seedLibraryBorrowingDate. /// /// In fr, this message translates to: - /// **'Lien Ical des événements'** - String get settingsEventsIcal; + /// **'Date d\'emprunt :'** + String get seedLibraryBorrowingDate; - /// No description provided for @settingsExpectingDate. + /// No description provided for @seedLibraryBorrowPlant. /// /// In fr, this message translates to: - /// **'Date de naissance attendue'** - String get settingsExpectingDate; + /// **'Emprunter la plante'** + String get seedLibraryBorrowPlant; - /// No description provided for @settingsFirstname. + /// No description provided for @seedLibraryCard. /// /// In fr, this message translates to: - /// **'Prénom'** - String get settingsFirstname; + /// **'Carte'** + String get seedLibraryCard; - /// No description provided for @settingsFloor. + /// No description provided for @seedLibraryChoosingAncestor. /// /// In fr, this message translates to: - /// **'Étage'** - String get settingsFloor; + /// **'Veuillez choisir un ancêtre'** + String get seedLibraryChoosingAncestor; - /// No description provided for @settingsHelp. + /// No description provided for @seedLibraryChoosingSpecies. /// /// In fr, this message translates to: - /// **'Aide'** - String get settingsHelp; + /// **'Veuillez choisir une espèce'** + String get seedLibraryChoosingSpecies; - /// No description provided for @settingsIcalCopied. + /// No description provided for @seedLibraryChoosingSpeciesOrAncestor. /// /// In fr, this message translates to: - /// **'Lien Ical copié !'** - String get settingsIcalCopied; + /// **'Veuillez choisir une espèce ou un ancêtre'** + String get seedLibraryChoosingSpeciesOrAncestor; - /// No description provided for @settingsLanguage. + /// No description provided for @seedLibraryContact. /// /// In fr, this message translates to: - /// **'Langue'** - String get settingsLanguage; + /// **'Contact :'** + String get seedLibraryContact; - /// No description provided for @settingsLanguageVar. + /// No description provided for @seedLibraryDays. /// /// In fr, this message translates to: - /// **'Français 🇫🇷'** - String get settingsLanguageVar; + /// **'jours'** + String get seedLibraryDays; - /// No description provided for @settingsLogs. + /// No description provided for @seedLibraryDeadMsg. /// /// In fr, this message translates to: - /// **'Logs'** - String get settingsLogs; + /// **'Voulez-vous déclarer la plante morte ?'** + String get seedLibraryDeadMsg; - /// No description provided for @settingsModules. + /// No description provided for @seedLibraryDeadPlant. /// /// In fr, this message translates to: - /// **'Modules'** - String get settingsModules; + /// **'Plante morte'** + String get seedLibraryDeadPlant; - /// No description provided for @settingsMyIcs. + /// No description provided for @seedLibraryDeathDate. /// /// In fr, this message translates to: - /// **'Mon lien Ical'** - String get settingsMyIcs; + /// **'Date de mort'** + String get seedLibraryDeathDate; - /// No description provided for @settingsName. + /// No description provided for @seedLibraryDeletedSpecies. /// /// In fr, this message translates to: - /// **'Nom'** - String get settingsName; + /// **'Espèce supprimée'** + String get seedLibraryDeletedSpecies; - /// No description provided for @settingsNewPassword. + /// No description provided for @seedLibraryDeleteSpecies. /// /// In fr, this message translates to: - /// **'Nouveau mot de passe'** - String get settingsNewPassword; + /// **'Supprimer l\'espèce ?'** + String get seedLibraryDeleteSpecies; - /// No description provided for @settingsNickname. + /// No description provided for @seedLibraryDeleting. /// /// In fr, this message translates to: - /// **'Surnom'** - String get settingsNickname; + /// **'Suppression'** + String get seedLibraryDeleting; - /// No description provided for @settingsNotifications. + /// No description provided for @seedLibraryDeletingError. /// /// In fr, this message translates to: - /// **'Notifications'** - String get settingsNotifications; + /// **'Erreur lors de la suppression'** + String get seedLibraryDeletingError; - /// No description provided for @settingsOldPassword. + /// No description provided for @seedLibraryDepositNotAvailable. /// /// In fr, this message translates to: - /// **'Ancien mot de passe'** - String get settingsOldPassword; + /// **'Le dépôt de plantes n\'est pas possible sans emprunter une plante au préalable'** + String get seedLibraryDepositNotAvailable; - /// No description provided for @settingsPasswordChanged. + /// No description provided for @seedLibraryDescription. /// /// In fr, this message translates to: - /// **'Mot de passe changé'** - String get settingsPasswordChanged; + /// **'Description'** + String get seedLibraryDescription; - /// No description provided for @settingsPasswordsNotMatch. + /// No description provided for @seedLibraryDifficulty. /// /// In fr, this message translates to: - /// **'Les mots de passe ne correspondent pas'** - String get settingsPasswordsNotMatch; + /// **'Difficulté :'** + String get seedLibraryDifficulty; - /// No description provided for @settingsPersonalData. + /// No description provided for @seedLibraryEdit. /// /// In fr, this message translates to: - /// **'Données personnelles'** - String get settingsPersonalData; + /// **'Modifier'** + String get seedLibraryEdit; - /// No description provided for @settingsPersonalisation. + /// No description provided for @seedLibraryEditedPlant. /// /// In fr, this message translates to: - /// **'Personnalisation'** - String get settingsPersonalisation; + /// **'Plante modifiée'** + String get seedLibraryEditedPlant; - /// No description provided for @settingsPhone. + /// No description provided for @seedLibraryEditInformation. /// /// In fr, this message translates to: - /// **'Téléphone'** - String get settingsPhone; + /// **'Modifier les informations'** + String get seedLibraryEditInformation; - /// No description provided for @settingsProfilePicture. + /// No description provided for @seedLibraryEditingError. /// /// In fr, this message translates to: - /// **'Photo de profil'** - String get settingsProfilePicture; + /// **'Erreur lors de la modification'** + String get seedLibraryEditingError; - /// No description provided for @settingsPromo. + /// No description provided for @seedLibraryEditSpecies. /// /// In fr, this message translates to: - /// **'Promotion'** - String get settingsPromo; + /// **'Modifier l\'espèce'** + String get seedLibraryEditSpecies; - /// No description provided for @settingsRepportBug. + /// No description provided for @seedLibraryEmptyDifficultyError. /// /// In fr, this message translates to: - /// **'Signaler un bug'** - String get settingsRepportBug; + /// **'Veuillez choisir une difficulté'** + String get seedLibraryEmptyDifficultyError; - /// No description provided for @settingsSave. + /// No description provided for @seedLibraryEmptyFieldError. /// /// In fr, this message translates to: - /// **'Enregistrer'** - String get settingsSave; + /// **'Veuillez remplir tous les champs'** + String get seedLibraryEmptyFieldError; - /// No description provided for @settingsSecurity. + /// No description provided for @seedLibraryEmptyTypeError. /// /// In fr, this message translates to: - /// **'Sécurité'** - String get settingsSecurity; + /// **'Veuillez choisir un type de plante'** + String get seedLibraryEmptyTypeError; - /// No description provided for @settingsSendedDemand. + /// No description provided for @seedLibraryEndMonth. /// /// In fr, this message translates to: - /// **'Demande envoyée'** - String get settingsSendedDemand; + /// **'Mois de fin :'** + String get seedLibraryEndMonth; - /// No description provided for @settingsSettings. + /// No description provided for @seedLibraryFacebookUrl. /// /// In fr, this message translates to: - /// **'Paramètres'** - String get settingsSettings; + /// **'Lien Facebook'** + String get seedLibraryFacebookUrl; - /// No description provided for @settingsTooHeavyProfilePicture. + /// No description provided for @seedLibraryFilters. /// /// In fr, this message translates to: - /// **'L\'image est trop lourde (max 4Mo)'** - String get settingsTooHeavyProfilePicture; + /// **'Filtres'** + String get seedLibraryFilters; - /// No description provided for @settingsUpdatedProfile. + /// No description provided for @seedLibraryForum. /// /// In fr, this message translates to: - /// **'Profil modifié'** - String get settingsUpdatedProfile; + /// **'Oskour maman j\'ai tué ma plante - Forum d\'aide'** + String get seedLibraryForum; - /// No description provided for @settingsUpdatedProfilePicture. + /// No description provided for @seedLibraryForumUrl. /// /// In fr, this message translates to: - /// **'Photo de profil modifiée'** - String get settingsUpdatedProfilePicture; + /// **'Lien Forum'** + String get seedLibraryForumUrl; - /// No description provided for @settingsUpdateNotification. + /// No description provided for @seedLibraryHelpSheets. /// /// In fr, this message translates to: - /// **'Mettre à jour les notifications'** - String get settingsUpdateNotification; + /// **'Fiches sur les plantes'** + String get seedLibraryHelpSheets; - /// No description provided for @settingsUpdatingError. + /// No description provided for @seedLibraryInformation. /// /// In fr, this message translates to: - /// **'Erreur lors de la modification du profil'** - String get settingsUpdatingError; + /// **'Informations :'** + String get seedLibraryInformation; - /// No description provided for @settingsVersion. + /// No description provided for @seedLibraryMaturationTime. /// /// In fr, this message translates to: - /// **'Version'** - String get settingsVersion; + /// **'Temps de maturation'** + String get seedLibraryMaturationTime; - /// No description provided for @settingsPasswordStrength. + /// No description provided for @seedLibraryMonthJan. /// /// In fr, this message translates to: - /// **'Force du mot de passe'** - String get settingsPasswordStrength; + /// **'Janvier'** + String get seedLibraryMonthJan; - /// No description provided for @settingsPasswordStrengthVeryWeak. + /// No description provided for @seedLibraryMonthFeb. /// /// In fr, this message translates to: - /// **'Très faible'** - String get settingsPasswordStrengthVeryWeak; + /// **'Février'** + String get seedLibraryMonthFeb; - /// No description provided for @settingsPasswordStrengthWeak. + /// No description provided for @seedLibraryMonthMar. /// /// In fr, this message translates to: - /// **'Faible'** - String get settingsPasswordStrengthWeak; + /// **'Mars'** + String get seedLibraryMonthMar; - /// No description provided for @settingsPasswordStrengthMedium. + /// No description provided for @seedLibraryMonthApr. /// /// In fr, this message translates to: - /// **'Moyen'** - String get settingsPasswordStrengthMedium; + /// **'Avril'** + String get seedLibraryMonthApr; - /// No description provided for @settingsPasswordStrengthStrong. + /// No description provided for @seedLibraryMonthMay. /// /// In fr, this message translates to: - /// **'Fort'** - String get settingsPasswordStrengthStrong; + /// **'Mai'** + String get seedLibraryMonthMay; - /// No description provided for @settingsPasswordStrengthVeryStrong. + /// No description provided for @seedLibraryMonthJun. /// /// In fr, this message translates to: - /// **'Très fort'** - String get settingsPasswordStrengthVeryStrong; + /// **'Juin'** + String get seedLibraryMonthJun; - /// No description provided for @settingsPhoneNumber. + /// No description provided for @seedLibraryMonthJul. /// /// In fr, this message translates to: - /// **'Numéro de téléphone'** - String get settingsPhoneNumber; + /// **'Juillet'** + String get seedLibraryMonthJul; - /// No description provided for @settingsValidate. + /// No description provided for @seedLibraryMonthAug. /// /// In fr, this message translates to: - /// **'Valider'** - String get settingsValidate; + /// **'Août'** + String get seedLibraryMonthAug; - /// No description provided for @settingsEditedAccount. + /// No description provided for @seedLibraryMonthSep. /// /// In fr, this message translates to: - /// **'Compte modifié avec succès'** - String get settingsEditedAccount; + /// **'Septembre'** + String get seedLibraryMonthSep; - /// No description provided for @settingsFailedToEditAccount. + /// No description provided for @seedLibraryMonthOct. /// /// In fr, this message translates to: - /// **'Échec de la modification du compte'** - String get settingsFailedToEditAccount; + /// **'Octobre'** + String get seedLibraryMonthOct; - /// No description provided for @settingsChooseLanguage. + /// No description provided for @seedLibraryMonthNov. /// /// In fr, this message translates to: - /// **'Choix de la langue'** - String get settingsChooseLanguage; + /// **'Novembre'** + String get seedLibraryMonthNov; - /// Affiche le nombre de notifications actives sur le total des notifications disponibles, avec gestion du pluriel + /// No description provided for @seedLibraryMonthDec. /// /// In fr, this message translates to: - /// **'{active}/{total} {active, plural, zero {activée} one {activée} other {activées}}'** - String settingsNotificationCounter(int active, int total); + /// **'Décembre'** + String get seedLibraryMonthDec; - /// No description provided for @settingsEvent. + /// No description provided for @seedLibraryMyPlants. /// /// In fr, this message translates to: - /// **'Événement'** - String get settingsEvent; + /// **'Mes plantes'** + String get seedLibraryMyPlants; - /// No description provided for @settingsIcal. + /// No description provided for @seedLibraryName. /// /// In fr, this message translates to: - /// **'Lien Ical'** - String get settingsIcal; + /// **'Nom'** + String get seedLibraryName; - /// No description provided for @settingsSynncWithCalendar. + /// No description provided for @seedLibraryNbSeedsRecommended. /// /// In fr, this message translates to: - /// **'Synchroniser avec votre calendrier'** - String get settingsSynncWithCalendar; + /// **'Nombre de graines recommandées'** + String get seedLibraryNbSeedsRecommended; - /// No description provided for @settingsIcalLinkCopied. + /// No description provided for @seedLibraryNbSeedsRecommendedError. /// /// In fr, this message translates to: - /// **'Lien Ical copié dans le presse-papier'** - String get settingsIcalLinkCopied; + /// **'Veuillez entrer un nombre de graines recommandé supérieur à 0'** + String get seedLibraryNbSeedsRecommendedError; - /// No description provided for @settingsProfile. + /// No description provided for @seedLibraryNoDateError. /// /// In fr, this message translates to: - /// **'Profil'** - String get settingsProfile; + /// **'Veuillez entrer une date'** + String get seedLibraryNoDateError; - /// No description provided for @settingsConnexion. + /// No description provided for @seedLibraryNoFilteredPlants. /// /// In fr, this message translates to: - /// **'Connexion'** - String get settingsConnexion; + /// **'Aucune plante ne correspond à votre recherche. Essayez d\'autres filtres.'** + String get seedLibraryNoFilteredPlants; - /// No description provided for @settingsDisconnect. + /// No description provided for @seedLibraryNoMorePlant. /// /// In fr, this message translates to: - /// **'Se déconnecter'** - String get settingsDisconnect; + /// **'Aucune plante n\'est disponible'** + String get seedLibraryNoMorePlant; - /// No description provided for @settingsDisconnectDescription. + /// No description provided for @seedLibraryNoPersonalPlants. /// /// In fr, this message translates to: - /// **'Êtes-vous sûr de vouloir vous déconnecter ?'** - String get settingsDisconnectDescription; + /// **'Vous n\'avez pas encore de plantes dans votre grainothèque. Vous pouvez en ajouter en allant dans les stocks.'** + String get seedLibraryNoPersonalPlants; - /// No description provided for @settingsDisconnectionSuccess. + /// No description provided for @seedLibraryNoSpecies. /// /// In fr, this message translates to: - /// **'Déconnexion réussie'** - String get settingsDisconnectionSuccess; + /// **'Aucune espèce trouvée'** + String get seedLibraryNoSpecies; - /// No description provided for @settingsDeleteMyAccount. + /// No description provided for @seedLibraryNoStockPlants. /// /// In fr, this message translates to: - /// **'Supprimer mon compte'** - String get settingsDeleteMyAccount; + /// **'Aucune plante disponible dans le stock'** + String get seedLibraryNoStockPlants; - /// No description provided for @settingsDeleteMyAccountDescription. + /// No description provided for @seedLibraryNotes. /// /// In fr, this message translates to: - /// **'Cette action notifie l\'administrateur que vous souhaitez supprimer votre compte.'** - String get settingsDeleteMyAccountDescription; + /// **'Notes'** + String get seedLibraryNotes; - /// No description provided for @settingsDeletionAsked. + /// No description provided for @seedLibraryOk. /// /// In fr, this message translates to: - /// **'Demande de suppression de compte envoyée'** - String get settingsDeletionAsked; + /// **'OK'** + String get seedLibraryOk; - /// No description provided for @settingsDeleteMyAccountError. + /// No description provided for @seedLibraryPlantationPeriod. /// /// In fr, this message translates to: - /// **'Erreur lors de la demande de suppression de compte'** - String get settingsDeleteMyAccountError; + /// **'Période de plantation :'** + String get seedLibraryPlantationPeriod; - /// No description provided for @voteAdd. + /// No description provided for @seedLibraryPlantationType. /// /// In fr, this message translates to: - /// **'Ajouter'** - String get voteAdd; + /// **'Type de plantation :'** + String get seedLibraryPlantationType; - /// No description provided for @voteAddMember. + /// No description provided for @seedLibraryPlantDetail. /// /// In fr, this message translates to: - /// **'Ajouter un membre'** - String get voteAddMember; + /// **'Détail de la plante'** + String get seedLibraryPlantDetail; - /// No description provided for @voteAddedPretendance. + /// No description provided for @seedLibraryPlantingDate. /// /// In fr, this message translates to: - /// **'Liste ajoutée'** - String get voteAddedPretendance; + /// **'Date de plantation'** + String get seedLibraryPlantingDate; - /// No description provided for @voteAddedSection. + /// No description provided for @seedLibraryPlantingNow. /// /// In fr, this message translates to: - /// **'Section ajoutée'** - String get voteAddedSection; + /// **'Je la plante maintenant'** + String get seedLibraryPlantingNow; - /// No description provided for @voteAddingError. + /// No description provided for @seedLibraryPrefix. /// /// In fr, this message translates to: - /// **'Erreur lors de l\'ajout'** - String get voteAddingError; + /// **'Préfixe'** + String get seedLibraryPrefix; - /// No description provided for @voteAddPretendance. + /// No description provided for @seedLibraryPrefixError. /// /// In fr, this message translates to: - /// **'Ajouter une liste'** - String get voteAddPretendance; + /// **'Prefixe déjà utilisé'** + String get seedLibraryPrefixError; - /// No description provided for @voteAddSection. + /// No description provided for @seedLibraryPrefixLengthError. /// /// In fr, this message translates to: - /// **'Ajouter une section'** - String get voteAddSection; + /// **'Le préfixe doit faire 3 caractères'** + String get seedLibraryPrefixLengthError; - /// No description provided for @voteAll. + /// No description provided for @seedLibraryPropagationMethod. /// /// In fr, this message translates to: - /// **'Tous'** - String get voteAll; + /// **'Méthode de propagation :'** + String get seedLibraryPropagationMethod; - /// No description provided for @voteAlreadyAddedMember. + /// No description provided for @seedLibraryReference. /// /// In fr, this message translates to: - /// **'Membre déjà ajouté'** - String get voteAlreadyAddedMember; + /// **'Référence :'** + String get seedLibraryReference; - /// No description provided for @voteAlreadyVoted. + /// No description provided for @seedLibraryRemovedPlant. /// /// In fr, this message translates to: - /// **'Vote enregistré'** - String get voteAlreadyVoted; + /// **'Plante supprimée'** + String get seedLibraryRemovedPlant; - /// No description provided for @voteChooseList. + /// No description provided for @seedLibraryRemovingError. /// /// In fr, this message translates to: - /// **'Choisir une liste'** - String get voteChooseList; + /// **'Erreur lors de la suppression'** + String get seedLibraryRemovingError; - /// No description provided for @voteClear. + /// No description provided for @seedLibraryResearch. /// /// In fr, this message translates to: - /// **'Réinitialiser'** - String get voteClear; + /// **'Recherche'** + String get seedLibraryResearch; - /// No description provided for @voteClearVotes. + /// No description provided for @seedLibrarySaveChanges. /// /// In fr, this message translates to: - /// **'Réinitialiser les votes'** - String get voteClearVotes; + /// **'Sauvegarder les modifications'** + String get seedLibrarySaveChanges; - /// No description provided for @voteClosedVote. + /// No description provided for @seedLibrarySeason. /// /// In fr, this message translates to: - /// **'Votes clos'** - String get voteClosedVote; + /// **'Saison :'** + String get seedLibrarySeason; - /// No description provided for @voteCloseVote. + /// No description provided for @seedLibrarySeed. /// /// In fr, this message translates to: - /// **'Fermer les votes'** - String get voteCloseVote; + /// **'Graine'** + String get seedLibrarySeed; - /// No description provided for @voteConfirmVote. + /// No description provided for @seedLibrarySeeds. /// /// In fr, this message translates to: - /// **'Confirmer le vote'** - String get voteConfirmVote; + /// **'graines'** + String get seedLibrarySeeds; - /// No description provided for @voteCountVote. + /// No description provided for @seedLibrarySeedDeposit. /// /// In fr, this message translates to: - /// **'Dépouiller les votes'** - String get voteCountVote; + /// **'Dépôt de plantes'** + String get seedLibrarySeedDeposit; - /// No description provided for @voteDeletedAll. + /// No description provided for @seedLibrarySeedLibrary. /// /// In fr, this message translates to: - /// **'Tout supprimé'** - String get voteDeletedAll; + /// **'Grainothèque'** + String get seedLibrarySeedLibrary; - /// No description provided for @voteDeletedPipo. + /// No description provided for @seedLibrarySeedQuantitySimple. /// /// In fr, this message translates to: - /// **'Listes pipos supprimées'** - String get voteDeletedPipo; + /// **'Quantité de graines'** + String get seedLibrarySeedQuantitySimple; - /// No description provided for @voteDeletedSection. + /// No description provided for @seedLibrarySeedQuantity. /// /// In fr, this message translates to: - /// **'Section supprimée'** - String get voteDeletedSection; + /// **'Quantité de graines :'** + String get seedLibrarySeedQuantity; - /// No description provided for @voteDeleteAll. + /// No description provided for @seedLibraryShowDeadPlants. /// /// In fr, this message translates to: - /// **'Supprimer tout'** - String get voteDeleteAll; + /// **'Afficher les plantes mortes'** + String get seedLibraryShowDeadPlants; - /// No description provided for @voteDeleteAllDescription. + /// No description provided for @seedLibrarySpecies. /// /// In fr, this message translates to: - /// **'Voulez-vous vraiment supprimer tout ?'** - String get voteDeleteAllDescription; + /// **'Espèce :'** + String get seedLibrarySpecies; - /// No description provided for @voteDeletePipo. + /// No description provided for @seedLibrarySpeciesHelp. /// /// In fr, this message translates to: - /// **'Supprimer les listes pipos'** - String get voteDeletePipo; + /// **'Aide sur l\'espèce'** + String get seedLibrarySpeciesHelp; - /// No description provided for @voteDeletePipoDescription. + /// No description provided for @seedLibrarySpeciesPlural. /// /// In fr, this message translates to: - /// **'Voulez-vous vraiment supprimer les listes pipos ?'** - String get voteDeletePipoDescription; + /// **'Espèces'** + String get seedLibrarySpeciesPlural; - /// No description provided for @voteDeletePretendance. + /// No description provided for @seedLibrarySpeciesSimple. /// /// In fr, this message translates to: - /// **'Supprimer la liste'** - String get voteDeletePretendance; + /// **'Espèce'** + String get seedLibrarySpeciesSimple; - /// No description provided for @voteDeletePretendanceDesc. + /// No description provided for @seedLibrarySpeciesType. /// /// In fr, this message translates to: - /// **'Voulez-vous vraiment supprimer cette liste ?'** - String get voteDeletePretendanceDesc; + /// **'Type d\'espèce :'** + String get seedLibrarySpeciesType; - /// No description provided for @voteDeleteSection. + /// No description provided for @seedLibrarySpring. /// /// In fr, this message translates to: - /// **'Supprimer la section'** - String get voteDeleteSection; + /// **'Printemps'** + String get seedLibrarySpring; - /// No description provided for @voteDeleteSectionDescription. + /// No description provided for @seedLibraryStartMonth. /// /// In fr, this message translates to: - /// **'Voulez-vous vraiment supprimer cette section ?'** - String get voteDeleteSectionDescription; + /// **'Mois de début :'** + String get seedLibraryStartMonth; - /// No description provided for @voteDeletingError. + /// No description provided for @seedLibraryStock. /// /// In fr, this message translates to: - /// **'Erreur lors de la suppression'** - String get voteDeletingError; + /// **'Stock disponible'** + String get seedLibraryStock; - /// No description provided for @voteDescription. + /// No description provided for @seedLibrarySummer. /// /// In fr, this message translates to: - /// **'Description'** - String get voteDescription; + /// **'Été'** + String get seedLibrarySummer; - /// No description provided for @voteEdit. + /// No description provided for @seedLibraryStocks. /// /// In fr, this message translates to: - /// **'Modifier'** - String get voteEdit; + /// **'Stocks'** + String get seedLibraryStocks; - /// No description provided for @voteEditedPretendance. + /// No description provided for @seedLibraryTimeUntilMaturation. /// /// In fr, this message translates to: - /// **'Liste modifiée'** - String get voteEditedPretendance; + /// **'Temps avant maturation :'** + String get seedLibraryTimeUntilMaturation; - /// No description provided for @voteEditedSection. + /// No description provided for @seedLibraryType. /// /// In fr, this message translates to: - /// **'Section modifiée'** - String get voteEditedSection; + /// **'Type :'** + String get seedLibraryType; - /// No description provided for @voteEditingError. + /// No description provided for @seedLibraryUnableToOpen. /// /// In fr, this message translates to: - /// **'Erreur lors de la modification'** - String get voteEditingError; + /// **'Impossible d\'ouvrir le lien'** + String get seedLibraryUnableToOpen; - /// No description provided for @voteErrorClosingVotes. + /// No description provided for @seedLibraryUpdate. /// /// In fr, this message translates to: - /// **'Erreur lors de la fermeture des votes'** - String get voteErrorClosingVotes; + /// **'Modifier'** + String get seedLibraryUpdate; - /// No description provided for @voteErrorCountingVotes. + /// No description provided for @seedLibraryUpdatedInformation. /// /// In fr, this message translates to: - /// **'Erreur lors du dépouillement des votes'** - String get voteErrorCountingVotes; + /// **'Informations modifiées'** + String get seedLibraryUpdatedInformation; - /// No description provided for @voteErrorResetingVotes. + /// No description provided for @seedLibraryUpdatedSpecies. /// /// In fr, this message translates to: - /// **'Erreur lors de la réinitialisation des votes'** - String get voteErrorResetingVotes; + /// **'Espèce modifiée'** + String get seedLibraryUpdatedSpecies; - /// No description provided for @voteErrorOpeningVotes. + /// No description provided for @seedLibraryUpdatedPlant. /// /// In fr, this message translates to: - /// **'Erreur lors de l\'ouverture des votes'** - String get voteErrorOpeningVotes; + /// **'Plante modifiée'** + String get seedLibraryUpdatedPlant; - /// No description provided for @voteIncorrectOrMissingFields. + /// No description provided for @seedLibraryUpdatingError. /// /// In fr, this message translates to: - /// **'Champs incorrects ou manquants'** - String get voteIncorrectOrMissingFields; + /// **'Erreur lors de la modification'** + String get seedLibraryUpdatingError; - /// No description provided for @voteMembers. + /// No description provided for @seedLibraryWinter. /// /// In fr, this message translates to: - /// **'Membres'** - String get voteMembers; + /// **'Hiver'** + String get seedLibraryWinter; - /// No description provided for @voteName. + /// No description provided for @seedLibraryWriteReference. /// /// In fr, this message translates to: - /// **'Nom'** - String get voteName; + /// **'Veuillez écrire la référence suivante : '** + String get seedLibraryWriteReference; - /// No description provided for @voteNoPretendanceList. + /// No description provided for @settingsAccount. /// /// In fr, this message translates to: - /// **'Aucune liste de prétendance'** - String get voteNoPretendanceList; + /// **'Compte'** + String get settingsAccount; - /// No description provided for @voteNoSection. + /// No description provided for @settingsAddProfilePicture. /// /// In fr, this message translates to: - /// **'Aucune section'** - String get voteNoSection; + /// **'Ajouter une photo'** + String get settingsAddProfilePicture; - /// No description provided for @voteCanNotVote. + /// No description provided for @settingsAdmin. /// /// In fr, this message translates to: - /// **'Vous ne pouvez pas voter'** - String get voteCanNotVote; + /// **'Administrateur'** + String get settingsAdmin; - /// No description provided for @voteNoSectionList. + /// No description provided for @settingsAskHelp. /// /// In fr, this message translates to: - /// **'Aucune section'** - String get voteNoSectionList; + /// **'Demander de l\'aide'** + String get settingsAskHelp; - /// No description provided for @voteNotOpenedVote. + /// No description provided for @settingsAssociation. /// /// In fr, this message translates to: - /// **'Vote non ouvert'** - String get voteNotOpenedVote; + /// **'Association'** + String get settingsAssociation; - /// No description provided for @voteOnGoingCount. + /// No description provided for @settingsBirthday. /// /// In fr, this message translates to: - /// **'Dépouillement en cours'** - String get voteOnGoingCount; + /// **'Date de naissance'** + String get settingsBirthday; - /// No description provided for @voteOpenVote. + /// No description provided for @settingsBugs. /// /// In fr, this message translates to: - /// **'Ouvrir les votes'** - String get voteOpenVote; + /// **'Bugs'** + String get settingsBugs; - /// No description provided for @votePipo. + /// No description provided for @settingsChangePassword. /// /// In fr, this message translates to: - /// **'Pipo'** - String get votePipo; + /// **'Changer de mot de passe'** + String get settingsChangePassword; - /// No description provided for @votePretendance. + /// No description provided for @settingsChangingPassword. /// /// In fr, this message translates to: - /// **'Listes'** - String get votePretendance; + /// **'Voulez-vous vraiment changer votre mot de passe ?'** + String get settingsChangingPassword; - /// No description provided for @votePretendanceDeleted. + /// No description provided for @settingsConfirmPassword. /// /// In fr, this message translates to: - /// **'Prétendance supprimée'** - String get votePretendanceDeleted; + /// **'Confirmer le mot de passe'** + String get settingsConfirmPassword; - /// No description provided for @votePretendanceNotDeleted. + /// No description provided for @settingsCopied. /// /// In fr, this message translates to: - /// **'Erreur lors de la suppression'** - String get votePretendanceNotDeleted; + /// **'Copié !'** + String get settingsCopied; - /// No description provided for @voteProgram. + /// No description provided for @settingsDarkMode. /// /// In fr, this message translates to: - /// **'Programme'** - String get voteProgram; + /// **'Mode sombre'** + String get settingsDarkMode; - /// No description provided for @votePublish. + /// No description provided for @settingsDarkModeOff. /// /// In fr, this message translates to: - /// **'Publier'** - String get votePublish; + /// **'Désactivé'** + String get settingsDarkModeOff; - /// No description provided for @votePublishVoteDescription. + /// No description provided for @settingsDeleteLogs. /// /// In fr, this message translates to: - /// **'Voulez-vous vraiment publier les votes ?'** - String get votePublishVoteDescription; + /// **'Supprimer les logs ?'** + String get settingsDeleteLogs; - /// No description provided for @voteResetedVotes. + /// No description provided for @settingsDeleteNotificationLogs. /// /// In fr, this message translates to: - /// **'Votes réinitialisés'** - String get voteResetedVotes; + /// **'Supprimer les logs des notifications ?'** + String get settingsDeleteNotificationLogs; - /// No description provided for @voteResetVote. + /// No description provided for @settingsDetelePersonalData. /// /// In fr, this message translates to: - /// **'Réinitialiser les votes'** - String get voteResetVote; + /// **'Supprimer mes données personnelles'** + String get settingsDetelePersonalData; - /// No description provided for @voteResetVoteDescription. + /// No description provided for @settingsDetelePersonalDataDesc. /// /// In fr, this message translates to: - /// **'Que voulez-vous faire ?'** - String get voteResetVoteDescription; + /// **'Cette action notifie l\'administrateur que vous souhaitez supprimer vos données personnelles.'** + String get settingsDetelePersonalDataDesc; - /// No description provided for @voteRole. + /// No description provided for @settingsDeleting. /// /// In fr, this message translates to: - /// **'Rôle'** - String get voteRole; + /// **'Suppresion'** + String get settingsDeleting; - /// No description provided for @voteSectionDescription. + /// No description provided for @settingsEdit. /// /// In fr, this message translates to: - /// **'Description de la section'** - String get voteSectionDescription; + /// **'Modifier'** + String get settingsEdit; - /// No description provided for @voteSection. + /// No description provided for @settingsEditAccount. /// /// In fr, this message translates to: - /// **'Section'** - String get voteSection; + /// **'Modifier mon profil'** + String get settingsEditAccount; - /// No description provided for @voteSectionName. + /// No description provided for @settingsEmail. /// /// In fr, this message translates to: - /// **'Nom de la section'** - String get voteSectionName; + /// **'Email'** + String get settingsEmail; - /// No description provided for @voteSeeMore. + /// No description provided for @settingsEmptyField. /// /// In fr, this message translates to: - /// **'Voir plus'** - String get voteSeeMore; + /// **'Ce champ ne peut pas être vide'** + String get settingsEmptyField; - /// No description provided for @voteSelected. + /// No description provided for @settingsErrorProfilePicture. /// /// In fr, this message translates to: - /// **'Sélectionné'** - String get voteSelected; + /// **'Erreur lors de la modification de la photo de profil'** + String get settingsErrorProfilePicture; - /// No description provided for @voteShowVotes. + /// No description provided for @settingsErrorSendingDemand. /// /// In fr, this message translates to: - /// **'Voir les votes'** - String get voteShowVotes; + /// **'Erreur lors de l\'envoi de la demande'** + String get settingsErrorSendingDemand; - /// No description provided for @voteVote. + /// No description provided for @settingsEventsIcal. /// /// In fr, this message translates to: - /// **'Vote'** - String get voteVote; + /// **'Lien Ical des événements'** + String get settingsEventsIcal; - /// No description provided for @voteVoteError. + /// No description provided for @settingsExpectingDate. /// /// In fr, this message translates to: - /// **'Erreur lors de l\'enregistrement du vote'** - String get voteVoteError; + /// **'Date de naissance attendue'** + String get settingsExpectingDate; - /// No description provided for @voteVoteFor. + /// No description provided for @settingsFirstname. /// /// In fr, this message translates to: - /// **'Voter pour '** - String get voteVoteFor; + /// **'Prénom'** + String get settingsFirstname; - /// No description provided for @voteVoteNotStarted. + /// No description provided for @settingsFloor. /// /// In fr, this message translates to: - /// **'Vote non ouvert'** - String get voteVoteNotStarted; + /// **'Étage'** + String get settingsFloor; - /// No description provided for @voteVoters. + /// No description provided for @settingsHelp. /// /// In fr, this message translates to: - /// **'Groupes votants'** - String get voteVoters; + /// **'Aide'** + String get settingsHelp; - /// No description provided for @voteVoteSuccess. + /// No description provided for @settingsIcalCopied. /// /// In fr, this message translates to: - /// **'Vote enregistré'** - String get voteVoteSuccess; + /// **'Lien Ical copié !'** + String get settingsIcalCopied; - /// No description provided for @voteVotes. + /// No description provided for @settingsLanguage. /// /// In fr, this message translates to: - /// **'Voix'** - String get voteVotes; + /// **'Langue'** + String get settingsLanguage; - /// No description provided for @voteVotesClosed. + /// No description provided for @settingsLanguageVar. /// /// In fr, this message translates to: - /// **'Votes clos'** - String get voteVotesClosed; + /// **'Français 🇫🇷'** + String get settingsLanguageVar; - /// No description provided for @voteVotesCounted. + /// No description provided for @settingsLogs. /// /// In fr, this message translates to: - /// **'Votes dépouillés'** - String get voteVotesCounted; + /// **'Logs'** + String get settingsLogs; - /// No description provided for @voteVotesOpened. + /// No description provided for @settingsModules. /// /// In fr, this message translates to: - /// **'Votes ouverts'** - String get voteVotesOpened; + /// **'Modules'** + String get settingsModules; - /// No description provided for @voteWarning. + /// No description provided for @settingsMyIcs. /// /// In fr, this message translates to: - /// **'Attention'** - String get voteWarning; + /// **'Mon lien Ical'** + String get settingsMyIcs; - /// No description provided for @voteWarningMessage. + /// No description provided for @settingsName. /// /// In fr, this message translates to: - /// **'La sélection ne sera pas sauvegardée.\nVoulez-vous continuer ?'** - String get voteWarningMessage; + /// **'Nom'** + String get settingsName; - /// No description provided for @moduleAdvert. + /// No description provided for @settingsNewPassword. /// /// In fr, this message translates to: - /// **'Annonce'** - String get moduleAdvert; + /// **'Nouveau mot de passe'** + String get settingsNewPassword; - /// No description provided for @moduleAdvertDescription. + /// No description provided for @settingsNickname. /// /// In fr, this message translates to: - /// **'Gérer les annonces'** - String get moduleAdvertDescription; + /// **'Surnom'** + String get settingsNickname; - /// No description provided for @moduleAmap. + /// No description provided for @settingsNotifications. /// /// In fr, this message translates to: - /// **'AMAP'** - String get moduleAmap; + /// **'Notifications'** + String get settingsNotifications; - /// No description provided for @moduleAmapDescription. + /// No description provided for @settingsOldPassword. /// /// In fr, this message translates to: - /// **'Gérer les livraisons et les produits'** - String get moduleAmapDescription; + /// **'Ancien mot de passe'** + String get settingsOldPassword; - /// No description provided for @moduleBooking. + /// No description provided for @settingsPasswordChanged. /// /// In fr, this message translates to: - /// **'Réservation'** - String get moduleBooking; + /// **'Mot de passe changé'** + String get settingsPasswordChanged; - /// No description provided for @moduleBookingDescription. + /// No description provided for @settingsPasswordsNotMatch. /// /// In fr, this message translates to: - /// **'Gérer les réservations, les salles et les managers'** - String get moduleBookingDescription; + /// **'Les mots de passe ne correspondent pas'** + String get settingsPasswordsNotMatch; - /// No description provided for @moduleCalendar. + /// No description provided for @settingsPersonalData. /// /// In fr, this message translates to: - /// **'Calendrier'** - String get moduleCalendar; + /// **'Données personnelles'** + String get settingsPersonalData; - /// No description provided for @moduleCalendarDescription. + /// No description provided for @settingsPersonalisation. /// /// In fr, this message translates to: - /// **'Consulter les événements et les activités'** - String get moduleCalendarDescription; + /// **'Personnalisation'** + String get settingsPersonalisation; - /// No description provided for @moduleCentralisation. + /// No description provided for @settingsPhone. /// /// In fr, this message translates to: - /// **'Centralisation'** - String get moduleCentralisation; + /// **'Téléphone'** + String get settingsPhone; - /// No description provided for @moduleCentralisationDescription. + /// No description provided for @settingsProfilePicture. /// /// In fr, this message translates to: - /// **'Gérer la centralisation des données'** - String get moduleCentralisationDescription; + /// **'Photo de profil'** + String get settingsProfilePicture; - /// No description provided for @moduleCinema. + /// No description provided for @settingsPromo. /// /// In fr, this message translates to: - /// **'Cinéma'** - String get moduleCinema; + /// **'Promotion'** + String get settingsPromo; - /// No description provided for @moduleCinemaDescription. + /// No description provided for @settingsRepportBug. /// /// In fr, this message translates to: - /// **'Gérer les séances de cinéma'** - String get moduleCinemaDescription; + /// **'Signaler un bug'** + String get settingsRepportBug; - /// No description provided for @moduleEvent. + /// No description provided for @settingsSave. /// /// In fr, this message translates to: - /// **'Événement'** - String get moduleEvent; + /// **'Enregistrer'** + String get settingsSave; - /// No description provided for @moduleEventDescription. + /// No description provided for @settingsSecurity. /// /// In fr, this message translates to: - /// **'Gérer les événements et les participants'** - String get moduleEventDescription; + /// **'Sécurité'** + String get settingsSecurity; - /// No description provided for @moduleFlappyBird. + /// No description provided for @settingsSendedDemand. /// /// In fr, this message translates to: - /// **'Flappy Bird'** - String get moduleFlappyBird; + /// **'Demande envoyée'** + String get settingsSendedDemand; - /// No description provided for @moduleFlappyBirdDescription. + /// No description provided for @settingsSettings. /// /// In fr, this message translates to: - /// **'Jouer à Flappy Bird et consulter le classement'** - String get moduleFlappyBirdDescription; + /// **'Paramètres'** + String get settingsSettings; - /// No description provided for @moduleLoan. + /// No description provided for @settingsTooHeavyProfilePicture. /// /// In fr, this message translates to: - /// **'Prêt'** - String get moduleLoan; + /// **'L\'image est trop lourde (max 4Mo)'** + String get settingsTooHeavyProfilePicture; - /// No description provided for @moduleLoanDescription. + /// No description provided for @settingsUpdatedProfile. /// /// In fr, this message translates to: - /// **'Gérer les prêts et les articles'** - String get moduleLoanDescription; + /// **'Profil modifié'** + String get settingsUpdatedProfile; - /// No description provided for @modulePhonebook. + /// No description provided for @settingsUpdatedProfilePicture. /// /// In fr, this message translates to: - /// **'Annuaire'** - String get modulePhonebook; + /// **'Photo de profil modifiée'** + String get settingsUpdatedProfilePicture; - /// No description provided for @modulePhonebookDescription. + /// No description provided for @settingsUpdateNotification. /// /// In fr, this message translates to: - /// **'Gérer les associations, les membres et les administrateurs'** - String get modulePhonebookDescription; + /// **'Mettre à jour les notifications'** + String get settingsUpdateNotification; - /// No description provided for @modulePurchases. + /// No description provided for @settingsUpdatingError. /// /// In fr, this message translates to: - /// **'Achats'** - String get modulePurchases; + /// **'Erreur lors de la modification du profil'** + String get settingsUpdatingError; - /// No description provided for @modulePurchasesDescription. + /// No description provided for @settingsVersion. /// /// In fr, this message translates to: - /// **'Gérer les achats, les tickets et l\'historique'** - String get modulePurchasesDescription; + /// **'Version'** + String get settingsVersion; - /// No description provided for @moduleRaffle. + /// No description provided for @settingsPasswordStrength. /// /// In fr, this message translates to: - /// **'Tombola'** - String get moduleRaffle; + /// **'Force du mot de passe'** + String get settingsPasswordStrength; - /// No description provided for @moduleRaffleDescription. + /// No description provided for @settingsPasswordStrengthVeryWeak. /// /// In fr, this message translates to: - /// **'Gérer les tombolas, les prix et les tickets'** - String get moduleRaffleDescription; + /// **'Très faible'** + String get settingsPasswordStrengthVeryWeak; - /// No description provided for @moduleRecommendation. + /// No description provided for @settingsPasswordStrengthWeak. /// /// In fr, this message translates to: - /// **'Bons plans'** - String get moduleRecommendation; + /// **'Faible'** + String get settingsPasswordStrengthWeak; - /// No description provided for @moduleRecommendationDescription. + /// No description provided for @settingsPasswordStrengthMedium. /// /// In fr, this message translates to: - /// **'Gérer les recommandations, les informations et les administrateurs'** - String get moduleRecommendationDescription; + /// **'Moyen'** + String get settingsPasswordStrengthMedium; - /// No description provided for @moduleSeedLibrary. + /// No description provided for @settingsPasswordStrengthStrong. /// /// In fr, this message translates to: - /// **'Grainothèque'** - String get moduleSeedLibrary; + /// **'Fort'** + String get settingsPasswordStrengthStrong; - /// No description provided for @moduleSeedLibraryDescription. + /// No description provided for @settingsPasswordStrengthVeryStrong. /// /// In fr, this message translates to: - /// **'Gérer les graines, les espèces et les stocks'** - String get moduleSeedLibraryDescription; + /// **'Très fort'** + String get settingsPasswordStrengthVeryStrong; - /// No description provided for @moduleVote. + /// No description provided for @settingsPhoneNumber. /// /// In fr, this message translates to: - /// **'Vote'** - String get moduleVote; + /// **'Numéro de téléphone'** + String get settingsPhoneNumber; - /// No description provided for @moduleVoteDescription. + /// No description provided for @settingsValidate. /// /// In fr, this message translates to: - /// **'Gérer les votes, les sections et les candidats'** - String get moduleVoteDescription; + /// **'Valider'** + String get settingsValidate; - /// No description provided for @modulePh. + /// No description provided for @settingsEditedAccount. /// /// In fr, this message translates to: - /// **'PH'** - String get modulePh; + /// **'Compte modifié avec succès'** + String get settingsEditedAccount; - /// No description provided for @modulePhDescription. + /// No description provided for @settingsFailedToEditAccount. /// /// In fr, this message translates to: - /// **'Gérer les PH, les formulaires et les administrateurs'** - String get modulePhDescription; + /// **'Échec de la modification du compte'** + String get settingsFailedToEditAccount; - /// No description provided for @moduleSettings. + /// No description provided for @settingsChooseLanguage. /// /// In fr, this message translates to: - /// **'Paramètres'** - String get moduleSettings; + /// **'Choix de la langue'** + String get settingsChooseLanguage; - /// No description provided for @moduleSettingsDescription. + /// Affiche le nombre de notifications actives sur le total des notifications disponibles, avec gestion du pluriel /// /// In fr, this message translates to: - /// **'Gérer les paramètres de l\'application'** - String get moduleSettingsDescription; + /// **'{active}/{total} {active, plural, zero {activée} one {activée} other {activées}}'** + String settingsNotificationCounter(int active, int total); - /// No description provided for @moduleFeed. + /// No description provided for @settingsEvent. /// /// In fr, this message translates to: - /// **'Feed'** - String get moduleFeed; + /// **'Événement'** + String get settingsEvent; - /// No description provided for @moduleFeedDescription. + /// No description provided for @settingsIcal. /// /// In fr, this message translates to: - /// **'Consulter les actualités et mises à jour'** - String get moduleFeedDescription; + /// **'Lien Ical'** + String get settingsIcal; - /// No description provided for @moduleStyleGuide. + /// No description provided for @settingsSynncWithCalendar. /// /// In fr, this message translates to: - /// **'StyleGuide'** - String get moduleStyleGuide; + /// **'Synchroniser avec votre calendrier'** + String get settingsSynncWithCalendar; - /// No description provided for @moduleStyleGuideDescription. + /// No description provided for @settingsIcalLinkCopied. /// /// In fr, this message translates to: - /// **'Explore the UI components and styles used in Titan'** - String get moduleStyleGuideDescription; + /// **'Lien Ical copié dans le presse-papier'** + String get settingsIcalLinkCopied; - /// No description provided for @moduleAdmin. + /// No description provided for @settingsProfile. /// /// In fr, this message translates to: - /// **'Admin'** - String get moduleAdmin; + /// **'Profil'** + String get settingsProfile; - /// No description provided for @moduleAdminDescription. + /// No description provided for @settingsConnexion. /// /// In fr, this message translates to: - /// **'Gérer les utilisateurs, groupes et structures'** - String get moduleAdminDescription; + /// **'Connexion'** + String get settingsConnexion; - /// No description provided for @moduleOthers. + /// No description provided for @settingsDisconnect. /// /// In fr, this message translates to: - /// **'Autres'** - String get moduleOthers; + /// **'Se déconnecter'** + String get settingsDisconnect; - /// No description provided for @moduleOthersDescription. + /// No description provided for @settingsDisconnectDescription. /// /// In fr, this message translates to: - /// **'Afficher les autres modules'** - String get moduleOthersDescription; + /// **'Êtes-vous sûr de vouloir vous déconnecter ?'** + String get settingsDisconnectDescription; - /// No description provided for @modulePayment. + /// No description provided for @settingsDisconnectionSuccess. /// /// In fr, this message translates to: - /// **'Paiement'** - String get modulePayment; + /// **'Déconnexion réussie'** + String get settingsDisconnectionSuccess; - /// No description provided for @modulePaymentDescription. + /// No description provided for @settingsDeleteMyAccount. /// /// In fr, this message translates to: - /// **'Gérer les paiements, les statistiques et les appareils'** - String get modulePaymentDescription; + /// **'Supprimer mon compte'** + String get settingsDeleteMyAccount; - /// No description provided for @paiementTopUp. + /// No description provided for @settingsDeleteMyAccountDescription. /// /// In fr, this message translates to: - /// **'Recharge'** - String get paiementTopUp; + /// **'Cette action notifie l\'administrateur que vous souhaitez supprimer votre compte.'** + String get settingsDeleteMyAccountDescription; - /// No description provided for @paiementStoreManagement. + /// No description provided for @settingsDeletionAsked. /// /// In fr, this message translates to: - /// **'Gestion des associations'** - String get paiementStoreManagement; + /// **'Demande de suppression de compte envoyée'** + String get settingsDeletionAsked; - /// No description provided for @paiementDeleteStore. + /// No description provided for @settingsDeleteMyAccountError. /// /// In fr, this message translates to: - /// **'Supprimer l\'association'** - String get paiementDeleteStore; + /// **'Erreur lors de la demande de suppression de compte'** + String get settingsDeleteMyAccountError; - /// No description provided for @paiementDeleteStoreDescription. + /// No description provided for @voteAdd. /// /// In fr, this message translates to: - /// **'Voulez-vous vraiment supprimer cette association ?'** - String get paiementDeleteStoreDescription; + /// **'Ajouter'** + String get voteAdd; - /// No description provided for @paiementDeleteStoreError. + /// No description provided for @voteAddMember. /// /// In fr, this message translates to: - /// **'Impossible de supprimer l\'association'** - String get paiementDeleteStoreError; + /// **'Ajouter un membre'** + String get voteAddMember; - /// No description provided for @paiementStoreDeleted. + /// No description provided for @voteAddedPretendance. /// /// In fr, this message translates to: - /// **'Association supprimée'** - String get paiementStoreDeleted; + /// **'Liste ajoutée'** + String get voteAddedPretendance; - /// No description provided for @paiementAddThisDevice. + /// No description provided for @voteAddedSection. /// /// In fr, this message translates to: - /// **'Ajouter cet appareil'** - String get paiementAddThisDevice; + /// **'Section ajoutée'** + String get voteAddedSection; - /// No description provided for @paiementThisDevice. + /// No description provided for @voteAddingError. /// /// In fr, this message translates to: - /// **'(cet appareil)'** - String get paiementThisDevice; + /// **'Erreur lors de l\'ajout'** + String get voteAddingError; - /// No description provided for @paiementCancelled. + /// No description provided for @voteAddPretendance. /// /// In fr, this message translates to: - /// **'Annulé'** - String get paiementCancelled; + /// **'Ajouter une liste'** + String get voteAddPretendance; - /// No description provided for @paiementThe. + /// No description provided for @voteAddSection. /// /// In fr, this message translates to: - /// **'Le'** - String get paiementThe; + /// **'Ajouter une section'** + String get voteAddSection; - /// No description provided for @paiementOf. + /// No description provided for @voteAll. /// /// In fr, this message translates to: - /// **'de'** - String get paiementOf; + /// **'Tous'** + String get voteAll; - /// No description provided for @paiementRefundedThe. + /// No description provided for @voteAlreadyAddedMember. /// /// In fr, this message translates to: - /// **'Remboursé le'** - String get paiementRefundedThe; + /// **'Membre déjà ajouté'** + String get voteAlreadyAddedMember; - /// No description provided for @paiementAt. + /// No description provided for @voteAlreadyVoted. /// /// In fr, this message translates to: - /// **'à'** - String get paiementAt; + /// **'Vote enregistré'** + String get voteAlreadyVoted; - /// No description provided for @paiementPleaseAcceptTOS. + /// No description provided for @voteChooseList. /// /// In fr, this message translates to: - /// **'Veuillez accepter les Conditions Générales d\'Utilisation.'** - String get paiementPleaseAcceptTOS; + /// **'Choisir une liste'** + String get voteChooseList; - /// No description provided for @paiementAskDeviceActivation. + /// No description provided for @voteClear. /// /// In fr, this message translates to: - /// **'Demande d\'activation de l\'appareil'** - String get paiementAskDeviceActivation; + /// **'Réinitialiser'** + String get voteClear; - /// No description provided for @paiementDeviceActivationReceived. + /// No description provided for @voteClearVotes. /// /// In fr, this message translates to: - /// **'La demande d\'activation est prise en compte, veuilliez consulter votre boite mail pour finaliser la démarche'** - String get paiementDeviceActivationReceived; + /// **'Réinitialiser les votes'** + String get voteClearVotes; - /// No description provided for @paiementRevokeDevice. + /// No description provided for @voteClosedVote. /// /// In fr, this message translates to: - /// **'Révoquer l\'appareil ?'** - String get paiementRevokeDevice; + /// **'Votes clos'** + String get voteClosedVote; - /// No description provided for @paiementRevokeDeviceDescription. + /// No description provided for @voteCloseVote. /// /// In fr, this message translates to: - /// **'Vous ne pourrez plus utiliser cet appareil pour les paiements'** - String get paiementRevokeDeviceDescription; + /// **'Fermer les votes'** + String get voteCloseVote; - /// No description provided for @paiementDeviceRevoked. + /// No description provided for @voteConfirmVote. /// /// In fr, this message translates to: - /// **'Appareil révoqué'** - String get paiementDeviceRevoked; + /// **'Confirmer le vote'** + String get voteConfirmVote; - /// No description provided for @paiementDeviceRevokingError. + /// No description provided for @voteCountVote. /// /// In fr, this message translates to: - /// **'Erreur lors de la révocation de l\'appareil'** - String get paiementDeviceRevokingError; + /// **'Dépouiller les votes'** + String get voteCountVote; - /// No description provided for @paiementPleaseAcceptPopup. + /// No description provided for @voteDeletedAll. /// /// In fr, this message translates to: - /// **'Veuillez autoriser les popups'** - String get paiementPleaseAcceptPopup; + /// **'Tout supprimé'** + String get voteDeletedAll; - /// No description provided for @paiementProceedSuccessfully. + /// No description provided for @voteDeletedPipo. /// /// In fr, this message translates to: - /// **'Paiement effectué avec succès'** - String get paiementProceedSuccessfully; + /// **'Listes pipos supprimées'** + String get voteDeletedPipo; - /// No description provided for @paiementCancelledTransaction. + /// No description provided for @voteDeletedSection. /// /// In fr, this message translates to: - /// **'Paiement annulé'** - String get paiementCancelledTransaction; + /// **'Section supprimée'** + String get voteDeletedSection; - /// No description provided for @paiementPleaseEnterMinAmount. + /// No description provided for @voteDeleteAll. /// /// In fr, this message translates to: - /// **'Veuillez entrer un montant supérieur à 1'** - String get paiementPleaseEnterMinAmount; + /// **'Supprimer tout'** + String get voteDeleteAll; - /// No description provided for @paiementMaxAmount. + /// No description provided for @voteDeleteAllDescription. /// /// In fr, this message translates to: - /// **'Le montant maximum de votre portefeuille est de'** - String get paiementMaxAmount; + /// **'Voulez-vous vraiment supprimer tout ?'** + String get voteDeleteAllDescription; - /// No description provided for @paiementPayWithHA. + /// No description provided for @voteDeletePipo. /// /// In fr, this message translates to: - /// **'Payer avec HelloAsso'** - String get paiementPayWithHA; + /// **'Supprimer les listes pipos'** + String get voteDeletePipo; - /// No description provided for @paiementBalanceAfterTopUp. + /// No description provided for @voteDeletePipoDescription. /// /// In fr, this message translates to: - /// **'Solde après recharge :'** - String get paiementBalanceAfterTopUp; + /// **'Voulez-vous vraiment supprimer les listes pipos ?'** + String get voteDeletePipoDescription; - /// No description provided for @paiementPersonalBalance. + /// No description provided for @voteDeletePretendance. /// /// In fr, this message translates to: - /// **'Solde personnel'** - String get paiementPersonalBalance; + /// **'Supprimer la liste'** + String get voteDeletePretendance; - /// No description provided for @paiementDevices. + /// No description provided for @voteDeletePretendanceDesc. /// /// In fr, this message translates to: - /// **'Appareils'** - String get paiementDevices; + /// **'Voulez-vous vraiment supprimer cette liste ?'** + String get voteDeletePretendanceDesc; - /// No description provided for @paiementPay. + /// No description provided for @voteDeleteSection. /// /// In fr, this message translates to: - /// **'Payer'** - String get paiementPay; + /// **'Supprimer la section'** + String get voteDeleteSection; - /// No description provided for @paiementDeviceNotRegistered. + /// No description provided for @voteDeleteSectionDescription. /// /// In fr, this message translates to: - /// **'Appareil non enregistré'** - String get paiementDeviceNotRegistered; + /// **'Voulez-vous vraiment supprimer cette section ?'** + String get voteDeleteSectionDescription; - /// No description provided for @paiementDeviceNotRegisteredDescription. + /// No description provided for @voteDeletingError. /// /// In fr, this message translates to: - /// **'Votre appareil n\'est pas encore enregistré. \nPour l\'enregistrer, veuillez vous rendre sur la page des appareils.'** - String get paiementDeviceNotRegisteredDescription; + /// **'Erreur lors de la suppression'** + String get voteDeletingError; - /// No description provided for @paiementAccessPage. + /// No description provided for @voteDescription. /// /// In fr, this message translates to: - /// **'Accéder à la page'** - String get paiementAccessPage; + /// **'Description'** + String get voteDescription; - /// No description provided for @paiementDeviceNotActivated. + /// No description provided for @voteEdit. /// /// In fr, this message translates to: - /// **'Appareil non activé'** - String get paiementDeviceNotActivated; + /// **'Modifier'** + String get voteEdit; - /// No description provided for @paiementDeviceNotActivatedDescription. + /// No description provided for @voteEditedPretendance. /// /// In fr, this message translates to: - /// **'Votre appareil n\'est pas encore activé. \nPour l\'activer, veuillez vous rendre sur la page des appareils.'** - String get paiementDeviceNotActivatedDescription; + /// **'Liste modifiée'** + String get voteEditedPretendance; - /// No description provided for @paiementReactivateRevokedDeviceDescription. + /// No description provided for @voteEditedSection. /// /// In fr, this message translates to: - /// **'Votre appareil a été révoqué. \nPour le réactiver, veuillez vous rendre sur la page des appareils.'** - String get paiementReactivateRevokedDeviceDescription; + /// **'Section modifiée'** + String get voteEditedSection; - /// No description provided for @paiementDeviceRecoveryError. + /// No description provided for @voteEditingError. /// /// In fr, this message translates to: - /// **'Erreur lors de la récupération de l\'appareil'** - String get paiementDeviceRecoveryError; + /// **'Erreur lors de la modification'** + String get voteEditingError; - /// No description provided for @paiementStats. + /// No description provided for @voteErrorClosingVotes. /// /// In fr, this message translates to: - /// **'Stats'** - String get paiementStats; + /// **'Erreur lors de la fermeture des votes'** + String get voteErrorClosingVotes; - /// No description provided for @paimentTopUpAction. + /// No description provided for @voteErrorCountingVotes. /// /// In fr, this message translates to: - /// **'Recharger'** - String get paimentTopUpAction; + /// **'Erreur lors du dépouillement des votes'** + String get voteErrorCountingVotes; - /// No description provided for @paiementGetBalanceError. + /// No description provided for @voteErrorResetingVotes. /// /// In fr, this message translates to: - /// **'Erreur lors de la récupération du solde : '** - String get paiementGetBalanceError; + /// **'Erreur lors de la réinitialisation des votes'** + String get voteErrorResetingVotes; - /// No description provided for @paiementLastTransactions. + /// No description provided for @voteErrorOpeningVotes. /// /// In fr, this message translates to: - /// **'Dernières transactions'** - String get paiementLastTransactions; + /// **'Erreur lors de l\'ouverture des votes'** + String get voteErrorOpeningVotes; - /// No description provided for @paiementGetTransactionsError. + /// No description provided for @voteIncorrectOrMissingFields. /// /// In fr, this message translates to: - /// **'Erreur lors de la récupération des transactions : '** - String get paiementGetTransactionsError; + /// **'Champs incorrects ou manquants'** + String get voteIncorrectOrMissingFields; - /// No description provided for @paiementStoreBalance. + /// No description provided for @voteMembers. /// /// In fr, this message translates to: - /// **'Solde associatif'** - String get paiementStoreBalance; + /// **'Membres'** + String get voteMembers; - /// No description provided for @paiementScan. + /// No description provided for @voteName. /// /// In fr, this message translates to: - /// **'Scanner'** - String get paiementScan; + /// **'Nom'** + String get voteName; - /// No description provided for @paiementManagement. + /// No description provided for @voteNoPretendanceList. /// /// In fr, this message translates to: - /// **'Gestion'** - String get paiementManagement; + /// **'Aucune liste de prétendance'** + String get voteNoPretendanceList; - /// No description provided for @paiementHistory. + /// No description provided for @voteNoSection. /// /// In fr, this message translates to: - /// **'Historique'** - String get paiementHistory; + /// **'Aucune section'** + String get voteNoSection; - /// No description provided for @paiementHandOver. + /// No description provided for @voteCanNotVote. /// /// In fr, this message translates to: - /// **'Passation'** - String get paiementHandOver; + /// **'Vous ne pouvez pas voter'** + String get voteCanNotVote; - /// No description provided for @paiementStores. + /// No description provided for @voteNoSectionList. /// /// In fr, this message translates to: - /// **'Associations'** - String get paiementStores; + /// **'Aucune section'** + String get voteNoSectionList; - /// No description provided for @paiementAdmin. + /// No description provided for @voteNotOpenedVote. /// /// In fr, this message translates to: - /// **'Administrateur'** - String get paiementAdmin; + /// **'Vote non ouvert'** + String get voteNotOpenedVote; - /// No description provided for @paiementSuccededTransaction. + /// No description provided for @voteOnGoingCount. /// /// In fr, this message translates to: - /// **'Paiement réussi'** - String get paiementSuccededTransaction; + /// **'Dépouillement en cours'** + String get voteOnGoingCount; - /// No description provided for @paiementNewCGU. + /// No description provided for @voteOpenVote. /// /// In fr, this message translates to: - /// **'Nouvelles Conditions Générales d\'Utilisation'** - String get paiementNewCGU; + /// **'Ouvrir les votes'** + String get voteOpenVote; - /// No description provided for @paiementDecline. + /// No description provided for @votePipo. /// /// In fr, this message translates to: - /// **'Refuser'** - String get paiementDecline; + /// **'Pipo'** + String get votePipo; - /// No description provided for @paiementAccept. + /// No description provided for @votePretendance. /// /// In fr, this message translates to: - /// **'Accepter'** - String get paiementAccept; + /// **'Listes'** + String get votePretendance; - /// No description provided for @paiementAmount. + /// No description provided for @votePretendanceDeleted. /// /// In fr, this message translates to: - /// **'Montant'** - String get paiementAmount; + /// **'Prétendance supprimée'** + String get votePretendanceDeleted; - /// No description provided for @paiementValidUntil. + /// No description provided for @votePretendanceNotDeleted. /// /// In fr, this message translates to: - /// **'Valide jusqu\'à'** - String get paiementValidUntil; + /// **'Erreur lors de la suppression'** + String get votePretendanceNotDeleted; - /// No description provided for @paiementClose. + /// No description provided for @voteProgram. /// /// In fr, this message translates to: - /// **'Fermer'** - String get paiementClose; + /// **'Programme'** + String get voteProgram; - /// No description provided for @paiementPleaseEnterValidAmount. + /// No description provided for @votePublish. /// /// In fr, this message translates to: - /// **'Veuillez entrer un montant valide'** - String get paiementPleaseEnterValidAmount; + /// **'Publier'** + String get votePublish; - /// No description provided for @paiementPleaseAuthenticate. + /// No description provided for @votePublishVoteDescription. /// /// In fr, this message translates to: - /// **'Veuillez vous authentifier'** - String get paiementPleaseAuthenticate; + /// **'Voulez-vous vraiment publier les votes ?'** + String get votePublishVoteDescription; - /// No description provided for @paiementAthenticationRequired. + /// No description provided for @voteResetedVotes. /// /// In fr, this message translates to: - /// **'Authentification requise pour payer'** - String get paiementAthenticationRequired; + /// **'Votes réinitialisés'** + String get voteResetedVotes; - /// No description provided for @paiementNoThanks. + /// No description provided for @voteResetVote. /// /// In fr, this message translates to: - /// **'Non merci'** - String get paiementNoThanks; + /// **'Réinitialiser les votes'** + String get voteResetVote; - /// No description provided for @paiementAuthentificationFailed. + /// No description provided for @voteResetVoteDescription. /// /// In fr, this message translates to: - /// **'Échec de l\'authentification'** - String get paiementAuthentificationFailed; + /// **'Que voulez-vous faire ?'** + String get voteResetVoteDescription; - /// No description provided for @paiementPleaseAddDevice. + /// No description provided for @voteRole. /// /// In fr, this message translates to: - /// **'Veuillez ajouter cet appareil pour payer'** - String get paiementPleaseAddDevice; + /// **'Rôle'** + String get voteRole; - /// No description provided for @paiementPayment. + /// No description provided for @voteSectionDescription. /// /// In fr, this message translates to: - /// **'Paiement'** - String get paiementPayment; + /// **'Description de la section'** + String get voteSectionDescription; - /// No description provided for @paiementBalanceAfterTransaction. + /// No description provided for @voteSection. /// /// In fr, this message translates to: - /// **'Solde après paiement : '** - String get paiementBalanceAfterTransaction; + /// **'Section'** + String get voteSection; - /// No description provided for @paiementCancel. + /// No description provided for @voteSectionName. /// /// In fr, this message translates to: - /// **'Annuler'** - String get paiementCancel; + /// **'Nom de la section'** + String get voteSectionName; - /// No description provided for @paiementLimitedTo. + /// No description provided for @voteSeeMore. /// /// In fr, this message translates to: - /// **'Limité à'** - String get paiementLimitedTo; + /// **'Voir plus'** + String get voteSeeMore; - /// No description provided for @paiementScanCode. + /// No description provided for @voteSelected. /// /// In fr, this message translates to: - /// **'Scanner un code'** - String get paiementScanCode; + /// **'Sélectionné'** + String get voteSelected; - /// No description provided for @paiementNext. + /// No description provided for @voteShowVotes. /// /// In fr, this message translates to: - /// **'Suivant'** - String get paiementNext; + /// **'Voir les votes'** + String get voteShowVotes; - /// No description provided for @paiementCancelTransaction. + /// No description provided for @voteVote. /// /// In fr, this message translates to: - /// **'Annuler la transaction'** - String get paiementCancelTransaction; + /// **'Vote'** + String get voteVote; - /// No description provided for @paiementTransactionCancelled. + /// No description provided for @voteVoteError. /// /// In fr, this message translates to: - /// **'Transaction annulée'** - String get paiementTransactionCancelled; + /// **'Erreur lors de l\'enregistrement du vote'** + String get voteVoteError; - /// No description provided for @paiementTransactionCancelledDescription. + /// No description provided for @voteVoteFor. /// /// In fr, this message translates to: - /// **'Voulez-vous vraiment annuler la transaction de'** - String get paiementTransactionCancelledDescription; + /// **'Voter pour '** + String get voteVoteFor; - /// No description provided for @paiementTransactionCancelledError. + /// No description provided for @voteVoteNotStarted. /// /// In fr, this message translates to: - /// **'Erreur lors de l\'annulation de la transaction'** - String get paiementTransactionCancelledError; + /// **'Vote non ouvert'** + String get voteVoteNotStarted; - /// No description provided for @paiementNoMembership. + /// No description provided for @voteVoters. /// /// In fr, this message translates to: - /// **'Aucune adhésion'** - String get paiementNoMembership; + /// **'Groupes votants'** + String get voteVoters; - /// No description provided for @paiementNoMembershipDescription. + /// No description provided for @voteVoteSuccess. /// /// In fr, this message translates to: - /// **'Ce produit n\'est pas disponnible pour les non-adhérents. Confirmer l\'encaissement ?'** - String get paiementNoMembershipDescription; + /// **'Vote enregistré'** + String get voteVoteSuccess; - /// No description provided for @paiementQRCodeAlreadyUsed. + /// No description provided for @voteVotes. /// /// In fr, this message translates to: - /// **'QR Code déjà utilisé'** - String get paiementQRCodeAlreadyUsed; + /// **'Voix'** + String get voteVotes; - /// No description provided for @paiementCameraPermissionRequired. + /// No description provided for @voteVotesClosed. /// /// In fr, this message translates to: - /// **'Permission d\'accès à la caméra requise'** - String get paiementCameraPermissionRequired; + /// **'Votes clos'** + String get voteVotesClosed; - /// No description provided for @paiementCameraPerssionRequiredDescription. + /// No description provided for @voteVotesCounted. /// /// In fr, this message translates to: - /// **'Pour scanner un QR Code, vous devez autoriser l\'accès à la caméra.'** - String get paiementCameraPerssionRequiredDescription; + /// **'Votes dépouillés'** + String get voteVotesCounted; - /// No description provided for @paiementSettings. + /// No description provided for @voteVotesOpened. /// /// In fr, this message translates to: - /// **'Paramètres'** - String get paiementSettings; + /// **'Votes ouverts'** + String get voteVotesOpened; - /// No description provided for @paiementReceived. + /// No description provided for @voteWarning. /// /// In fr, this message translates to: - /// **'Reçu'** - String get paiementReceived; + /// **'Attention'** + String get voteWarning; - /// No description provided for @paiementSpent. + /// No description provided for @voteWarningMessage. /// /// In fr, this message translates to: - /// **'Déboursé'** - String get paiementSpent; + /// **'La sélection ne sera pas sauvegardée.\nVoulez-vous continuer ?'** + String get voteWarningMessage; - /// No description provided for @paiementNoTrasactionForThisMonth. + /// No description provided for @moduleAdvert. /// /// In fr, this message translates to: - /// **'Aucune transaction pour ce mois'** - String get paiementNoTrasactionForThisMonth; + /// **'Annonce'** + String get moduleAdvert; - /// No description provided for @paiementNoTransactinon. + /// No description provided for @moduleAdvertDescription. /// /// In fr, this message translates to: - /// **'Aucune transaction'** - String get paiementNoTransactinon; + /// **'Gérer les annonces'** + String get moduleAdvertDescription; - /// No description provided for @paiementSellerRigths. + /// No description provided for @moduleAmap. /// /// In fr, this message translates to: - /// **'Droits du vendeur'** - String get paiementSellerRigths; + /// **'AMAP'** + String get moduleAmap; - /// No description provided for @paiementCanBank. + /// No description provided for @moduleAmapDescription. /// /// In fr, this message translates to: - /// **'Peut encaisser'** - String get paiementCanBank; + /// **'Gérer les livraisons et les produits'** + String get moduleAmapDescription; - /// No description provided for @paiementCanSeeHistory. + /// No description provided for @moduleBooking. /// /// In fr, this message translates to: - /// **'Peut voir l\'historique'** - String get paiementCanSeeHistory; + /// **'Réservation'** + String get moduleBooking; - /// No description provided for @paiementCanCancelTransaction. + /// No description provided for @moduleBookingDescription. /// /// In fr, this message translates to: - /// **'Peut annuler des transactions'** - String get paiementCanCancelTransaction; + /// **'Gérer les réservations, les salles et les managers'** + String get moduleBookingDescription; - /// No description provided for @paiementCanManageSellers. + /// No description provided for @moduleCalendar. /// /// In fr, this message translates to: - /// **'Peut gérer les vendeurs'** - String get paiementCanManageSellers; + /// **'Calendrier'** + String get moduleCalendar; - /// No description provided for @paiementAddedSeller. + /// No description provided for @moduleCalendarDescription. /// /// In fr, this message translates to: - /// **'Vendeur ajouté'** - String get paiementAddedSeller; + /// **'Consulter les événements et les activités'** + String get moduleCalendarDescription; - /// No description provided for @paiementAddingSellerError. + /// No description provided for @moduleCentralisation. /// /// In fr, this message translates to: - /// **'Erreur lors de l\'ajout du vendeur'** - String get paiementAddingSellerError; + /// **'Centralisation'** + String get moduleCentralisation; - /// No description provided for @paiementBank. + /// No description provided for @moduleCentralisationDescription. /// /// In fr, this message translates to: - /// **'Encaisser'** - String get paiementBank; + /// **'Gérer la centralisation des données'** + String get moduleCentralisationDescription; - /// No description provided for @paiementSeeHistory. + /// No description provided for @moduleCinema. /// /// In fr, this message translates to: - /// **'Voir l\'historique'** - String get paiementSeeHistory; + /// **'Cinéma'** + String get moduleCinema; - /// No description provided for @paiementCancelTransactions. + /// No description provided for @moduleCinemaDescription. /// /// In fr, this message translates to: - /// **'Annuler les transactions'** - String get paiementCancelTransactions; + /// **'Gérer les séances de cinéma'** + String get moduleCinemaDescription; - /// No description provided for @paiementManageSellers. + /// No description provided for @moduleEvent. /// /// In fr, this message translates to: - /// **'Gérer les vendeurs'** - String get paiementManageSellers; + /// **'Événement'** + String get moduleEvent; - /// No description provided for @paiementStructureAdmin. + /// No description provided for @moduleEventDescription. /// /// In fr, this message translates to: - /// **'Administrateur de la structure'** - String get paiementStructureAdmin; + /// **'Gérer les événements et les participants'** + String get moduleEventDescription; - /// No description provided for @paiementRightsOf. + /// No description provided for @moduleFlappyBird. /// /// In fr, this message translates to: - /// **'Droits de'** - String get paiementRightsOf; + /// **'Flappy Bird'** + String get moduleFlappyBird; - /// No description provided for @paiementRightsUpdated. + /// No description provided for @moduleFlappyBirdDescription. /// /// In fr, this message translates to: - /// **'Droits mis à jour'** - String get paiementRightsUpdated; + /// **'Jouer à Flappy Bird et consulter le classement'** + String get moduleFlappyBirdDescription; - /// No description provided for @paiementRightsUpdateError. + /// No description provided for @moduleLoan. /// /// In fr, this message translates to: - /// **'Erreur lors de la mise à jour des droits'** - String get paiementRightsUpdateError; + /// **'Prêt'** + String get moduleLoan; - /// No description provided for @paiementDeleteSellerDescription. + /// No description provided for @moduleLoanDescription. /// /// In fr, this message translates to: - /// **'Voulez-vous vraiment supprimer ce vendeur ?'** - String get paiementDeleteSellerDescription; + /// **'Gérer les prêts et les articles'** + String get moduleLoanDescription; - /// No description provided for @paiementDeletedSeller. + /// No description provided for @modulePhonebook. /// /// In fr, this message translates to: - /// **'Vendeur supprimé'** - String get paiementDeletedSeller; + /// **'Annuaire'** + String get modulePhonebook; - /// No description provided for @paiementDeletingSellerError. + /// No description provided for @modulePhonebookDescription. /// /// In fr, this message translates to: - /// **'Erreur lors de la suppression du vendeur'** - String get paiementDeletingSellerError; + /// **'Gérer les associations, les membres et les administrateurs'** + String get modulePhonebookDescription; - /// No description provided for @paiementDeleteSeller. + /// No description provided for @modulePurchases. /// /// In fr, this message translates to: - /// **'Supprimer le vendeur'** - String get paiementDeleteSeller; + /// **'Achats'** + String get modulePurchases; - /// No description provided for @paiementAdd. + /// No description provided for @modulePurchasesDescription. /// /// In fr, this message translates to: - /// **'Ajouter'** - String get paiementAdd; + /// **'Gérer les achats, les tickets et l\'historique'** + String get modulePurchasesDescription; - /// No description provided for @paiementAddSeller. + /// No description provided for @moduleRaffle. /// /// In fr, this message translates to: - /// **'Ajouter un vendeur'** - String get paiementAddSeller; + /// **'Tombola'** + String get moduleRaffle; - /// No description provided for @paiementSellerError. + /// No description provided for @moduleRaffleDescription. /// /// In fr, this message translates to: - /// **'Vous n\'êtes pas vendeur de cette association'** - String get paiementSellerError; + /// **'Gérer les tombolas, les prix et les tickets'** + String get moduleRaffleDescription; - /// No description provided for @paiementSellersOf. + /// No description provided for @moduleRecommendation. /// /// In fr, this message translates to: - /// **'Les vendeurs de'** - String get paiementSellersOf; + /// **'Bons plans'** + String get moduleRecommendation; - /// No description provided for @paiementModify. + /// No description provided for @moduleRecommendationDescription. /// /// In fr, this message translates to: - /// **'Modifier'** - String get paiementModify; + /// **'Gérer les recommandations, les informations et les administrateurs'** + String get moduleRecommendationDescription; - /// No description provided for @paiementAStore. + /// No description provided for @moduleSeedLibrary. /// /// In fr, this message translates to: - /// **'une association'** - String get paiementAStore; + /// **'Grainothèque'** + String get moduleSeedLibrary; - /// No description provided for @paiementStoreName. + /// No description provided for @moduleSeedLibraryDescription. /// /// In fr, this message translates to: - /// **'Nom de l\'association'** - String get paiementStoreName; + /// **'Gérer les graines, les espèces et les stocks'** + String get moduleSeedLibraryDescription; - /// No description provided for @paiementSuccessfullyAddedStore. + /// No description provided for @moduleVote. /// /// In fr, this message translates to: - /// **'Association ajoutée avec succès'** - String get paiementSuccessfullyAddedStore; + /// **'Vote'** + String get moduleVote; - /// No description provided for @paiementSuccessfullyModifiedStore. + /// No description provided for @moduleVoteDescription. /// /// In fr, this message translates to: - /// **'Association modifiée avec succès'** - String get paiementSuccessfullyModifiedStore; + /// **'Gérer les votes, les sections et les candidats'** + String get moduleVoteDescription; - /// No description provided for @paiementAddingStoreError. + /// No description provided for @modulePh. /// /// In fr, this message translates to: - /// **'Erreur lors de l\'ajout de l\'association'** - String get paiementAddingStoreError; + /// **'PH'** + String get modulePh; - /// No description provided for @paiementModifyingStoreError. + /// No description provided for @modulePhDescription. /// /// In fr, this message translates to: - /// **'Erreur lors de la modification de l\'association'** - String get paiementModifyingStoreError; + /// **'Gérer les PH, les formulaires et les administrateurs'** + String get modulePhDescription; - /// No description provided for @paiementRefund. + /// No description provided for @moduleSettings. /// /// In fr, this message translates to: - /// **'Remboursement'** - String get paiementRefund; + /// **'Paramètres'** + String get moduleSettings; - /// No description provided for @paiementDoneTransaction. + /// No description provided for @moduleSettingsDescription. /// /// In fr, this message translates to: - /// **'Transaction effectuée'** - String get paiementDoneTransaction; + /// **'Gérer les paramètres de l\'application'** + String get moduleSettingsDescription; - /// No description provided for @paiementRefundAction. + /// No description provided for @moduleFeed. /// /// In fr, this message translates to: - /// **'Rembourser'** - String get paiementRefundAction; + /// **'Feed'** + String get moduleFeed; - /// No description provided for @paiementTotalDuringPeriod. + /// No description provided for @moduleFeedDescription. /// /// In fr, this message translates to: - /// **'Total sur la période'** - String get paiementTotalDuringPeriod; + /// **'Consulter les actualités et mises à jour'** + String get moduleFeedDescription; - /// No description provided for @paiementMean. + /// No description provided for @moduleStyleGuide. /// /// In fr, this message translates to: - /// **'Moyenne : '** - String get paiementMean; + /// **'StyleGuide'** + String get moduleStyleGuide; - /// No description provided for @paiementTransaction. + /// No description provided for @moduleStyleGuideDescription. /// /// In fr, this message translates to: - /// **'ransaction'** - String get paiementTransaction; + /// **'Explore the UI components and styles used in Titan'** + String get moduleStyleGuideDescription; - /// No description provided for @paiementTransferStructure. + /// No description provided for @moduleAdmin. /// /// In fr, this message translates to: - /// **'Transfert de structure'** - String get paiementTransferStructure; + /// **'Admin'** + String get moduleAdmin; - /// No description provided for @paiementYouAreTransferingStructureTo. + /// No description provided for @moduleAdminDescription. /// /// In fr, this message translates to: - /// **'Vous êtes sur le point de transférer la structure à '** - String get paiementYouAreTransferingStructureTo; + /// **'Gérer les utilisateurs, groupes et structures'** + String get moduleAdminDescription; - /// No description provided for @paiementTransferStructureDescription. + /// No description provided for @moduleOthers. /// /// In fr, this message translates to: - /// **'Le nouveau responsable aura accès à toutes les fonctionnalités de gestion de la structure. Vous allez recevoir un email pour valider ce transfert. Le lien ne sera actif que pendant 20 minutes. Cette action est irréversible. Êtes-vous sûr de vouloir continuer ?'** - String get paiementTransferStructureDescription; + /// **'Autres'** + String get moduleOthers; - /// No description provided for @paiementTransferStructureError. + /// No description provided for @moduleOthersDescription. /// /// In fr, this message translates to: - /// **'Erreur lors du transfert de la structure'** - String get paiementTransferStructureError; + /// **'Afficher les autres modules'** + String get moduleOthersDescription; - /// No description provided for @paiementTransferStructureSuccess. + /// No description provided for @modulePayment. /// /// In fr, this message translates to: - /// **'Transfert de structure demandé avec succès'** - String get paiementTransferStructureSuccess; + /// **'Paiement'** + String get modulePayment; - /// No description provided for @paiementNextAccountable. + /// No description provided for @modulePaymentDescription. /// /// In fr, this message translates to: - /// **'Prochain responsable'** - String get paiementNextAccountable; + /// **'Gérer les paiements, les statistiques et les appareils'** + String get modulePaymentDescription; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index b3d5028b09..d248d91863 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -146,9 +146,32 @@ class AppLocalizationsEn extends AppLocalizations { @override String get adminAssociationsMemberships => 'Memberships'; + @override + String adminBankAccountHolder(String bankAccountHolder) { + return 'Bank account holder: $bankAccountHolder'; + } + + @override + String get adminBankAccountHolderModified => 'Bank account holder modified'; + + @override + String get adminBankDetails => 'Bank details'; + + @override + String get adminBic => 'BIC'; + + @override + String get adminBicError => 'BIC must be 11 characters'; + + @override + String get adminCity => 'City'; + @override String get adminClearFilters => 'Clear filters'; + @override + String get adminCountry => 'Country'; + @override String get adminCreateAssociationMembership => 'Create membership'; @@ -161,6 +184,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get adminDateError => 'Start date must be before end date'; + @override + String get adminDefineAsBankAccountHolder => 'Define as bank account holder'; + @override String get adminDelete => 'Delete'; @@ -233,6 +259,12 @@ class AppLocalizationsEn extends AppLocalizations { @override String get adminGroups => 'Groups'; + @override + String get adminIban => 'IBAN'; + + @override + String get adminIbanError => 'IBAN must be 27 characters'; + @override String get adminLoaningGroup => 'Loaning group'; @@ -289,6 +321,24 @@ class AppLocalizationsEn extends AppLocalizations { @override String get adminSchools => 'Schools'; + @override + String get adminShortId => 'Short ID (3 letters)'; + + @override + String get adminShortIdError => 'Short ID must be 3 characters'; + + @override + String get adminSiegeAddress => 'Head office address'; + + @override + String get adminSiret => 'SIRET'; + + @override + String get adminSiretError => 'SIRET must be 14 digits'; + + @override + String get adminStreet => 'Street and number'; + @override String get adminStructures => 'Structures'; @@ -301,6 +351,10 @@ class AppLocalizationsEn extends AppLocalizations { @override String get adminStartDateMinimal => 'Minimum start date'; + @override + String get adminUndefinedBankAccountHolder => + 'Bank account holder not defined'; + @override String get adminUpdatedAssociationMembership => 'Membership updated'; @@ -322,6 +376,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get adminVisibilities => 'Visibilities'; + @override + String get adminZipcode => 'Zip code'; + @override String get adminGroupNotification => 'Group notifications'; @@ -2112,2178 +2169,2256 @@ class AppLocalizationsEn extends AppLocalizations { String get othersImageError => 'Error adding the image'; @override - String get phAddNewJournal => 'Add a new journal'; + String get paiementAccept => 'Accept'; @override - String get phNameField => 'Name: '; + String get paiementAccessPage => 'Access the page'; @override - String get phDateField => 'Date: '; + String get paiementAdd => 'Add'; @override - String get phDelete => 'Are you sure you want to delete this journal?'; + String get paiementAddedSeller => 'Seller added'; @override - String get phIrreversibleAction => 'This action is irreversible'; + String get paiementAddingSellerError => 'Error while adding seller'; @override - String get phToHeavyFile => 'File too large'; + String get paiementAddingStoreError => 'Error while adding the store'; @override - String get phAddPdfFile => 'Add a PDF file'; + String get paiementAddSeller => 'Add seller'; @override - String get phEditPdfFile => 'Edit PDF file'; + String get paiementAddStore => 'Add store'; @override - String get phPhName => 'PH name'; + String get paiementAddThisDevice => 'Add this device'; @override - String get phDate => 'Date'; + String get paiementAdmin => 'Administrator'; @override - String get phAdded => 'Added'; + String get paiementAmount => 'Amount'; @override - String get phEdited => 'Edited'; + String get paiementAskDeviceActivation => 'Device activation request'; @override - String get phAddingFileError => 'Add error'; + String get paiementAStore => 'a store'; @override - String get phMissingInformatonsOrPdf => 'Missing information or PDF file'; + String get paiementAt => 'at'; @override - String get phAdd => 'Add'; + String get paiementAuthenticationRequired => 'Authentication required to pay'; @override - String get phEdit => 'Edit'; + String get paiementAuthentificationFailed => 'Authentication failed'; @override - String get phSeePreviousJournal => 'See previous journals'; + String get paiementBalanceAfterTopUp => 'Balance after top-up:'; @override - String get phNoJournalInDatabase => 'No PH yet in database'; + String get paiementBalanceAfterTransaction => 'Balance after payment: '; @override - String get phSuccesDowloading => 'Successfully downloaded'; + String get paiementBank => 'Collect'; @override - String get phonebookAdd => 'Add'; + String get paiementBillingSpace => 'Billing page'; @override - String get phonebookAddAssociation => 'Add an association'; + String get paiementCameraPermissionRequired => 'Camera permission required'; @override - String get phonebookAddAssociationGroupement => - 'Add an association groupement'; + String get paiementCameraPerssionRequiredDescription => + 'To scan a QR Code, you must allow camera access.'; @override - String get phonebookAddedAssociation => 'Association added'; + String get paiementCanBank => 'Can collect payments'; @override - String get phonebookAddedMember => 'Member added'; + String get paiementCanCancelTransaction => 'Can cancel transactions'; @override - String get phonebookAddingError => 'Error adding'; + String get paiementCancel => 'Cancel'; @override - String get phonebookAddMember => 'Add a member'; + String get paiementCancelled => 'Cancelled'; @override - String get phonebookAddRole => 'Add a role'; + String get paiementCancelledTransaction => 'Payment cancelled'; @override - String get phonebookAdmin => 'Admin'; + String get paiementCancelTransaction => 'Cancel transaction'; @override - String get phonebookAll => 'All'; + String get paiementCancelTransactions => 'Cancel transactions'; @override - String get phonebookApparentName => 'Public role name:'; + String get paiementCanManageSellers => 'Can manage sellers'; @override - String get phonebookAssociation => 'Association'; + String get paiementCanSeeHistory => 'Can view history'; @override - String get phonebookAssociationDetail => 'Association details:'; + String get paiementClose => 'Close'; @override - String get phonebookAssociationGroupement => 'Association groupement'; + String get paiementCreate => 'Create'; @override - String get phonebookAssociationKind => 'Type of association:'; + String get paiementCreateInvoice => 'Create new invoice'; @override - String get phonebookAssociationName => 'Association name'; + String get paiementDecline => 'Decline'; @override - String get phonebookAssociations => 'Associations'; + String get paiementDeletedSeller => 'Seller deleted'; @override - String get phonebookCancel => 'Cancel'; + String get paiementDeleteInvoice => 'Delete invoice'; @override - String phonebookChangeTermYear(int year) { - return 'Switch to $year term'; - } + String get paiementDeleteSeller => 'Delete seller'; @override - String get phonebookChangeTermConfirm => - 'Are you sure you want to change the entire term?\nThis action is irreversible!'; + String get paiementDeleteSellerDescription => + 'Are you sure you want to delete this seller?'; @override - String get phonebookClose => 'Close'; + String get paiementDeleteSuccessfully => 'Successfully deleted'; @override - String get phonebookConfirm => 'Confirm'; + String get paiementDeleteStore => 'Delete store'; @override - String get phonebookCopied => 'Copied to clipboard'; + String get paiementDeleteStoreDescription => + 'Are you sure you want to delete this store?'; @override - String get phonebookDeactivateAssociation => 'Deactivate association'; + String get paiementDeleteStoreError => 'Unable to delete the store'; @override - String get phonebookDeactivatedAssociation => 'Association deactivated'; + String get paiementDeletingSellerError => 'Error while deleting seller'; @override - String get phonebookDeactivatedAssociationWarning => - 'Warning, this association is deactivated, you cannot modify it'; + String get paiementDeviceActivationReceived => + 'The activation request has been received, please check your email to finalize the process'; @override - String phonebookDeactivateSelectedAssociation(String association) { - return 'Désactiver l\'association $association ?'; - } + String get paiementDeviceNotActivated => 'Device not activated'; @override - String get phonebookDeactivatingError => 'Error during deactivation'; + String get paiementDeviceNotActivatedDescription => + 'Your device is not yet activated. \nTo activate it, please go to the devices page.'; @override - String get phonebookDetail => 'Details:'; + String get paiementDeviceNotRegistered => 'Device not registered'; @override - String get phonebookDelete => 'Delete'; + String get paiementDeviceNotRegisteredDescription => + 'Your device is not registered yet. \nTo register it, please go to the devices page.'; @override - String get phonebookDeleteAssociation => 'Delete association'; + String get paiementDeviceRecoveryError => 'Error while retrieving device'; @override - String phonebookDeleteSelectedAssociation(String association) { - return 'Delete the association $association?'; - } + String get paiementDeviceRevoked => 'Device revoked'; @override - String get phonebookDeleteAssociationDescription => - 'This will erase all association history'; + String get paiementDeviceRevokingError => 'Error while revoking device'; @override - String get phonebookDeletedAssociation => 'Association deleted'; + String get paiementDevices => 'Devices'; @override - String get phonebookDeletedMember => 'Member deleted'; + String get paiementDoneTransaction => 'Transaction completed'; @override - String get phonebookDeleteRole => 'Delete role'; + String get paiementDownload => 'Download'; @override - String phonebookDeleteUserRole(String name) { - return 'Delete the role of $name?'; + String paiementEditStore(String store) { + return 'Edit store $store'; } @override - String get phonebookDeactivating => 'Deactivate the association?'; + String get paiementErrorDeleting => 'Error while deleting'; @override - String get phonebookDeleting => 'Deleting'; + String get paiementErrorUpdatingStatus => 'Error while updating the status'; @override - String get phonebookDeletingError => 'Error deleting'; + String paiementFromTo(DateTime from, DateTime to) { + final intl.DateFormat fromDateFormat = intl.DateFormat.yMd(localeName); + final String fromString = fromDateFormat.format(from); + final intl.DateFormat toDateFormat = intl.DateFormat.yMd(localeName); + final String toString = toDateFormat.format(to); + + return 'From $fromString to $toString'; + } @override - String get phonebookDescription => 'Description'; + String get paiementGetBalanceError => 'Error while retrieving balance: '; @override - String get phonebookEdit => 'Edit'; + String get paiementGetTransactionsError => + 'Error while retrieving transactions: '; @override - String get phonebookEditAssociationGroupement => - 'Edit association groupement'; + String get paiementHandOver => 'Handover'; @override - String get phonebookEditAssociationGroups => 'Manage groups'; + String get paiementHistory => 'History'; @override - String get phonebookEditAssociationInfo => 'Edit'; + String get paiementInvoiceCreatedSuccessfully => + 'Invoice created successfully'; @override - String get phonebookEditAssociationMembers => 'Manage members'; + String get paiementInvoices => 'Invoices'; @override - String get phonebookEditRole => 'Edit role'; + String paiementInvoicesPerPage(int quantity) { + return '$quantity invoices/page'; + } @override - String get phonebookEditMembership => 'Edit role'; + String get paiementLastTransactions => 'Latest transactions'; @override - String get phonebookEmail => 'Email:'; + String get paiementLimitedTo => 'Limited to'; @override - String get phonebookEmailCopied => 'Email copied to clipboard'; + String get paiementManagement => 'Management'; @override - String get phonebookEmptyApparentName => 'Please enter a role name'; + String get paiementManageSellers => 'Manage sellers'; @override - String get phonebookEmptyFieldError => 'A field is not filled'; + String get paiementMarkPaid => 'Mark as paid'; @override - String get phonebookEmptyKindError => 'Please choose an association type'; + String get paiementMarkReceived => 'Mark as received'; @override - String get phonebookEmptyMember => 'No member selected'; + String get paiementMarkUnpaid => 'Mark as unpaid'; @override - String get phonebookErrorAssociationLoading => 'Error loading association'; + String get paiementMaxAmount => 'The maximum wallet amount is'; @override - String get phonebookErrorAssociationNameEmpty => - 'Please enter an association name'; + String get paiementMean => 'Average: '; @override - String get phonebookErrorAssociationPicture => - 'Error editing association picture'; + String get paiementModify => 'Edit'; @override - String get phonebookErrorKindsLoading => 'Error loading association types'; + String get paiementModifyingStoreError => 'Error while updating the store'; @override - String get phonebookErrorLoadAssociationList => - 'Error loading association list'; + String get paiementModifySuccessfully => 'Successfully modified'; @override - String get phonebookErrorLoadAssociationMember => - 'Error loading association members'; + String get paiementNewCGU => 'New Terms of Service'; @override - String get phonebookErrorLoadAssociationPicture => - 'Error loading association picture'; + String get paiementNext => 'Next'; @override - String get phonebookErrorLoadProfilePicture => 'Error'; + String get paiementNextAccountable => 'Next responsible'; @override - String get phonebookErrorRoleTagsLoading => 'Error loading role tags'; + String get paiementNoInvoiceToCreate => 'No invoice to create'; @override - String get phonebookExistingMembership => - 'This member is already in the current term'; + String get paiementNoMembership => 'No membership'; @override - String get phonebookFilter => 'Filter'; + String get paiementNoMembershipDescription => + 'This product is not available to non-members. Confirm the payment?'; @override - String get phonebookFilterDescription => - 'Filter the associations by their type'; + String get paiementNoThanks => 'No thanks'; @override - String get phonebookFirstname => 'First name:'; + String get paiementNoTransaction => 'No transaction'; @override - String get phonebookGroupementDeleted => 'Association groupement deleted'; + String get paiementNoTransactionForThisMonth => + 'No transactions for this month'; @override - String get phonebookGroupementDeleteError => - 'Error deleting association groupement'; + String get paiementOf => 'of'; @override - String get phonebookGroupementName => 'Groupement name'; + String get paiementPaid => 'Paid'; @override - String phonebookGroups(String association) { - return 'Manage $association groups'; - } + String get paiementPay => 'Pay'; @override - String phonebookTerm(int year) { - return '$year term'; - } + String get paiementPayment => 'Payment'; @override - String get phonebookTermChangingError => 'Error changing term'; + String get paiementPayWithHA => 'Pay with HelloAsso'; @override - String get phonebookMember => 'Member'; + String get paiementPending => 'Pending'; @override - String get phonebookMemberReordered => 'Member reordered'; + String get paiementPersonalBalance => 'Personal balance'; @override - String phonebookMembers(String association) { - return 'Manage $association members'; - } + String get paiementPleaseAcceptPopup => 'Please allow popups'; @override - String get phonebookMembershipAssociationError => - 'Please choose an association'; + String get paiementPleaseAcceptTOS => 'Please accept the Terms of Service.'; @override - String get phonebookMembershipRole => 'Role:'; + String get paiementPleaseAddDevice => 'Please add this device to pay'; @override - String get phonebookMembershipRoleError => 'Please choose a role'; + String get paiementPleaseAuthenticate => 'Please authenticate'; @override - String phonebookModifyMembership(String name) { - return 'Modify $name\'s role'; - } + String get paiementPleaseEnterMinAmount => + 'Please enter an amount greater than 1'; @override - String get phonebookName => 'Last name:'; + String get paiementPleaseEnterValidAmount => 'Please enter a valid amount'; @override - String get phonebookNameCopied => 'Name and first name copied to clipboard'; + String get paiementProceedSuccessfully => 'Payment completed successfully'; @override - String get phonebookNamePure => 'Last name'; + String get paiementQRCodeAlreadyUsed => 'QR Code already used'; @override - String get phonebookNewTerm => 'New term'; + String get paiementReactivateRevokedDeviceDescription => + 'Your device has been revoked. \nTo reactivate it, please go to the devices page.'; @override - String get phonebookNewTermConfirmed => 'Term changed'; + String get paiementReceived => 'Received'; @override - String get phonebookNickname => 'Nickname:'; + String get paiementRefund => 'Refund'; @override - String get phonebookNicknameCopied => 'Nickname copied to clipboard'; + String get paiementRefundAction => 'Refund'; @override - String get phonebookNoAssociationFound => 'No association found'; + String get paiementRefundedThe => 'Refunded on'; @override - String get phonebookNoMember => 'No member'; + String get paiementRevokeDevice => 'Revoke device?'; @override - String get phonebookNoMemberRole => 'No role found'; + String get paiementRevokeDeviceDescription => + 'You will no longer be able to use this device for payments'; @override - String get phonebookNoRoleTags => 'No role tags found'; + String get paiementRightsOf => 'Rights of'; @override - String get phonebookPhone => 'Phone:'; + String get paiementRightsUpdated => 'Rights updated'; @override - String get phonebookPhonebook => 'Phonebook'; + String get paiementRightsUpdateError => 'Error while updating rights'; @override - String get phonebookPhonebookSearch => 'Search'; + String get paiementScan => 'Scan'; @override - String get phonebookPhonebookSearchAssociation => 'Association'; + String get paiementScanCode => 'Scan a code'; @override - String get phonebookPhonebookSearchField => 'Search:'; + String get paiementSeeHistory => 'View history'; @override - String get phonebookPhonebookSearchName => 'Last name/First name/Nickname'; + String get paiementSelectStructure => 'Select a structure'; @override - String get phonebookPhonebookSearchRole => 'Position'; + String get paiementSellerError => 'You are not a seller of this store'; @override - String get phonebookPresidentRoleTag => 'Prez\''; + String get paiementSellerRigths => 'Seller rights'; @override - String get phonebookPromoNotGiven => 'Promotion not provided'; + String get paiementSellersOf => 'Sellers of'; @override - String phonebookPromotion(int year) { - return 'Promotion $year'; - } + String get paiementSettings => 'Settings'; @override - String get phonebookReorderingError => 'Error during reordering'; + String get paiementSpent => 'Spent'; @override - String get phonebookResearch => 'Search'; + String get paiementStats => 'Stats'; @override - String get phonebookRolePure => 'Role'; + String get paiementStoreBalance => 'Store balance'; @override - String get phonebookSearchUser => 'Search a user'; + String get paiementStoreDeleted => 'Store deleted'; @override - String get phonebookTooHeavyAssociationPicture => - 'Image is too large (max 4MB)'; + String paiementStructureManagement(String structure) { + return '$structure management'; + } @override - String get phonebookUpdateGroups => 'Update groups'; + String get paiementStoreName => 'Store name'; @override - String get phonebookUpdatedAssociation => 'Association updated'; + String get paiementStores => 'Stores'; @override - String get phonebookUpdatedAssociationPicture => - 'Association picture has been changed'; + String get paiementStructureAdmin => 'Structure administrator'; @override - String get phonebookUpdatedGroups => 'Groups updated'; + String get paiementSuccededTransaction => 'Successful payment'; @override - String get phonebookUpdatedMember => 'Member updated'; + String get paiementSuccessfullyAddedStore => 'Store successfully added'; @override - String get phonebookUpdatingError => 'Error during update'; + String get paiementSuccessfullyModifiedStore => 'Store successfully updated'; @override - String get phonebookValidation => 'Validate'; + String get paiementThe => 'The'; @override - String get purchasesPurchases => 'Purchases'; + String get paiementThisDevice => '(this device)'; @override - String get purchasesResearch => 'Search'; + String get paiementTopUp => 'Top-up'; @override - String get purchasesNoPurchasesFound => 'No purchases found'; + String get paiementTopUpAction => 'Top-up'; @override - String get purchasesNoTickets => 'No tickets'; + String get paiementTotalDuringPeriod => 'Total during the period'; @override - String get purchasesTicketsError => 'Error loading tickets'; + String get paiementTransaction => 'Transaction'; @override - String get purchasesPurchasesError => 'Error loading purchases'; + String get paiementTransactionCancelled => 'Transaction cancelled'; @override - String get purchasesNoPurchases => 'No purchase'; + String get paiementTransactionCancelledDescription => + 'Are you sure you want to cancel the transaction of'; @override - String get purchasesTimes => 'times'; + String get paiementTransactionCancelledError => + 'Error while cancelling the transaction'; @override - String get purchasesAlreadyUsed => 'Already used'; + String get paiementTransferStructure => 'Structure transfer'; @override - String get purchasesNotPaid => 'Not validated'; + String get paiementTransferStructureDescription => + 'The new manager will have access to all structure management features. You will receive an email to confirm this transfer. The link will only be active for 20 minutes. This action is irreversible. Are you sure you want to continue?'; @override - String get purchasesPleaseSelectProduct => 'Please select a product'; + String get paiementTransferStructureError => + 'Error while transferring structure'; @override - String get purchasesProducts => 'Products'; + String get paiementTransferStructureSuccess => + 'Structure transfer requested successfully'; @override - String get purchasesCancel => 'Cancel'; + String get paiementValidUntil => 'Valid until'; @override - String get purchasesValidate => 'Validate'; + String get paiementYouAreTransferingStructureTo => + 'You are about to transfer the structure to '; @override - String get purchasesLeftScan => 'Scans remaining'; + String get phAddNewJournal => 'Add a new journal'; @override - String get purchasesTag => 'Tag'; + String get phNameField => 'Name: '; @override - String get purchasesHistory => 'History'; + String get phDateField => 'Date: '; @override - String get purchasesPleaseSelectSeller => 'Please select a seller'; + String get phDelete => 'Are you sure you want to delete this journal?'; @override - String get purchasesNoTagGiven => 'Warning, no tag entered'; + String get phIrreversibleAction => 'This action is irreversible'; @override - String get purchasesTickets => 'Tickets'; + String get phToHeavyFile => 'File too large'; @override - String get purchasesNoScannableProducts => 'No scannable products'; + String get phAddPdfFile => 'Add a PDF file'; @override - String get purchasesLoading => 'Waiting for scan'; + String get phEditPdfFile => 'Edit PDF file'; @override - String get purchasesScan => 'Scan'; + String get phPhName => 'PH name'; @override - String get raffleRaffle => 'Raffle'; + String get phDate => 'Date'; @override - String get rafflePrize => 'Prize'; + String get phAdded => 'Added'; @override - String get rafflePrizes => 'Prizes'; + String get phEdited => 'Edited'; @override - String get raffleActualRaffles => 'Current raffles'; + String get phAddingFileError => 'Add error'; @override - String get rafflePastRaffles => 'Past raffles'; + String get phMissingInformatonsOrPdf => 'Missing information or PDF file'; @override - String get raffleYourTickets => 'All your tickets'; + String get phAdd => 'Add'; @override - String get raffleCreateMenu => 'Creation menu'; + String get phEdit => 'Edit'; @override - String get raffleNextRaffles => 'Upcoming raffles'; + String get phSeePreviousJournal => 'See previous journals'; @override - String get raffleNoTicket => 'You have no ticket'; + String get phNoJournalInDatabase => 'No PH yet in database'; @override - String get raffleSeeRaffleDetail => 'View prizes/tickets'; + String get phSuccesDowloading => 'Successfully downloaded'; @override - String get raffleActualPrize => 'Current prizes'; + String get phonebookAdd => 'Add'; @override - String get raffleMajorPrize => 'Major prizes'; + String get phonebookAddAssociation => 'Add an association'; @override - String get raffleTakeTickets => 'Take your tickets'; + String get phonebookAddAssociationGroupement => + 'Add an association groupement'; @override - String get raffleNoTicketBuyable => 'You cannot buy tickets right now'; + String get phonebookAddedAssociation => 'Association added'; @override - String get raffleNoCurrentPrize => 'There are no prizes currently'; + String get phonebookAddedMember => 'Member added'; @override - String get raffleModifTombola => - 'You can modify your raffles or create new ones, all decisions must then be approved by admins'; + String get phonebookAddingError => 'Error adding'; @override - String get raffleCreateYourRaffle => 'Your raffle creation menu'; + String get phonebookAddMember => 'Add a member'; @override - String get rafflePossiblePrice => 'Possible prize'; + String get phonebookAddRole => 'Add a role'; @override - String get raffleInformation => 'Information and statistics'; + String get phonebookAdmin => 'Admin'; @override - String get raffleAccounts => 'Accounts'; + String get phonebookAll => 'All'; @override - String get raffleAdd => 'Add'; + String get phonebookApparentName => 'Public role name:'; @override - String get raffleUpdatedAmount => 'Amount updated'; + String get phonebookAssociation => 'Association'; @override - String get raffleUpdatingError => 'Error during update'; + String get phonebookAssociationDetail => 'Association details:'; @override - String get raffleDeletedPrize => 'Prize deleted'; + String get phonebookAssociationGroupement => 'Association groupement'; @override - String get raffleDeletingError => 'Error during deletion'; + String get phonebookAssociationKind => 'Type of association:'; @override - String get raffleQuantity => 'Quantity'; + String get phonebookAssociationName => 'Association name'; @override - String get raffleClose => 'Close'; + String get phonebookAssociations => 'Associations'; @override - String get raffleOpen => 'Open'; + String get phonebookCancel => 'Cancel'; @override - String get raffleAddTypeTicketSimple => 'Add'; + String phonebookChangeTermYear(int year) { + return 'Switch to $year term'; + } @override - String get raffleAddingError => 'Error during addition'; + String get phonebookChangeTermConfirm => + 'Are you sure you want to change the entire term?\nThis action is irreversible!'; @override - String get raffleEditTypeTicketSimple => 'Edit'; + String get phonebookClose => 'Close'; @override - String get raffleFillField => 'Field cannot be empty'; + String get phonebookConfirm => 'Confirm'; @override - String get raffleWaiting => 'Loading'; + String get phonebookCopied => 'Copied to clipboard'; @override - String get raffleEditingError => 'Error during editing'; + String get phonebookDeactivateAssociation => 'Deactivate association'; @override - String get raffleAddedTicket => 'Ticket added'; + String get phonebookDeactivatedAssociation => 'Association deactivated'; @override - String get raffleEditedTicket => 'Ticket edited'; + String get phonebookDeactivatedAssociationWarning => + 'Warning, this association is deactivated, you cannot modify it'; @override - String get raffleAlreadyExistTicket => 'Ticket already exists'; + String phonebookDeactivateSelectedAssociation(String association) { + return 'Désactiver l\'association $association ?'; + } @override - String get raffleNumberExpected => 'An integer is expected'; + String get phonebookDeactivatingError => 'Error during deactivation'; @override - String get raffleDeletedTicket => 'Ticket deleted'; + String get phonebookDetail => 'Details:'; @override - String get raffleAddPrize => 'Add'; + String get phonebookDelete => 'Delete'; @override - String get raffleEditPrize => 'Edit'; + String get phonebookDeleteAssociation => 'Delete association'; @override - String get raffleOpenRaffle => 'Open raffle'; + String phonebookDeleteSelectedAssociation(String association) { + return 'Delete the association $association?'; + } @override - String get raffleCloseRaffle => 'Close raffle'; + String get phonebookDeleteAssociationDescription => + 'This will erase all association history'; @override - String get raffleOpenRaffleDescription => - 'You are going to open the raffle, users will be able to buy tickets. You will no longer be able to modify the raffle. Are you sure you want to continue?'; + String get phonebookDeletedAssociation => 'Association deleted'; @override - String get raffleCloseRaffleDescription => - 'You are going to close the raffle, users will no longer be able to buy tickets. Are you sure you want to continue?'; + String get phonebookDeletedMember => 'Member deleted'; @override - String get raffleNoCurrentRaffle => 'There is no ongoing raffle'; + String get phonebookDeleteRole => 'Delete role'; @override - String get raffleBoughtTicket => 'Ticket purchased'; + String phonebookDeleteUserRole(String name) { + return 'Delete the role of $name?'; + } @override - String get raffleDrawingError => 'Error during drawing'; + String get phonebookDeactivating => 'Deactivate the association?'; @override - String get raffleInvalidPrice => 'Price must be greater than 0'; + String get phonebookDeleting => 'Deleting'; @override - String get raffleMustBePositive => 'Number must be strictly positive'; + String get phonebookDeletingError => 'Error deleting'; @override - String get raffleDraw => 'Draw'; + String get phonebookDescription => 'Description'; @override - String get raffleDrawn => 'Drawn'; + String get phonebookEdit => 'Edit'; @override - String get raffleError => 'Error'; + String get phonebookEditAssociationGroupement => + 'Edit association groupement'; @override - String get raffleGathered => 'Collected'; + String get phonebookEditAssociationGroups => 'Manage groups'; @override - String get raffleTickets => 'Tickets'; + String get phonebookEditAssociationInfo => 'Edit'; @override - String get raffleTicket => 'ticket'; + String get phonebookEditAssociationMembers => 'Manage members'; @override - String get raffleWinner => 'Winner'; + String get phonebookEditRole => 'Edit role'; @override - String get raffleNoPrize => 'No prize'; + String get phonebookEditMembership => 'Edit role'; @override - String get raffleDeletePrize => 'Delete prize'; + String get phonebookEmail => 'Email:'; @override - String get raffleDeletePrizeDescription => - 'You are going to delete the prize, are you sure you want to continue?'; + String get phonebookEmailCopied => 'Email copied to clipboard'; @override - String get raffleDrawing => 'Drawing'; + String get phonebookEmptyApparentName => 'Please enter a role name'; @override - String get raffleDrawingDescription => 'Draw the prize winner?'; + String get phonebookEmptyFieldError => 'A field is not filled'; @override - String get raffleDeleteTicket => 'Delete ticket'; + String get phonebookEmptyKindError => 'Please choose an association type'; @override - String get raffleDeleteTicketDescription => - 'You are going to delete the ticket, are you sure you want to continue?'; + String get phonebookEmptyMember => 'No member selected'; @override - String get raffleWinningTickets => 'Winning tickets'; + String get phonebookErrorAssociationLoading => 'Error loading association'; @override - String get raffleNoWinningTicketYet => - 'Winning tickets will be displayed here'; + String get phonebookErrorAssociationNameEmpty => + 'Please enter an association name'; @override - String get raffleName => 'Name'; + String get phonebookErrorAssociationPicture => + 'Error editing association picture'; @override - String get raffleDescription => 'Description'; + String get phonebookErrorKindsLoading => 'Error loading association types'; @override - String get raffleBuyThisTicket => 'Buy this ticket'; + String get phonebookErrorLoadAssociationList => + 'Error loading association list'; @override - String get raffleLockedRaffle => 'Locked raffle'; + String get phonebookErrorLoadAssociationMember => + 'Error loading association members'; @override - String get raffleUnavailableRaffle => 'Unavailable raffle'; + String get phonebookErrorLoadAssociationPicture => + 'Error loading association picture'; @override - String get raffleNotEnoughMoney => 'You don\'t have enough money'; + String get phonebookErrorLoadProfilePicture => 'Error'; @override - String get raffleWinnable => 'winnable'; + String get phonebookErrorRoleTagsLoading => 'Error loading role tags'; @override - String get raffleNoDescription => 'No description'; + String get phonebookExistingMembership => + 'This member is already in the current term'; @override - String get raffleAmount => 'Balance'; + String get phonebookFilter => 'Filter'; @override - String get raffleLoading => 'Loading'; + String get phonebookFilterDescription => + 'Filter the associations by their type'; @override - String get raffleTicketNumber => 'Number of tickets'; + String get phonebookFirstname => 'First name:'; @override - String get rafflePrice => 'Price'; + String get phonebookGroupementDeleted => 'Association groupement deleted'; @override - String get raffleEditRaffle => 'Edit raffle'; + String get phonebookGroupementDeleteError => + 'Error deleting association groupement'; @override - String get raffleEdit => 'Edit'; + String get phonebookGroupementName => 'Groupement name'; @override - String get raffleAddPackTicket => 'Add ticket pack'; + String phonebookGroups(String association) { + return 'Manage $association groups'; + } @override - String get recommendationRecommendation => 'Recommendation'; + String phonebookTerm(int year) { + return '$year term'; + } @override - String get recommendationTitle => 'Title'; + String get phonebookTermChangingError => 'Error changing term'; @override - String get recommendationLogo => 'Logo'; + String get phonebookMember => 'Member'; @override - String get recommendationCode => 'Code'; + String get phonebookMemberReordered => 'Member reordered'; @override - String get recommendationSummary => 'Short summary'; + String phonebookMembers(String association) { + return 'Manage $association members'; + } @override - String get recommendationDescription => 'Description'; + String get phonebookMembershipAssociationError => + 'Please choose an association'; @override - String get recommendationAdd => 'Add'; + String get phonebookMembershipRole => 'Role:'; @override - String get recommendationEdit => 'Edit'; + String get phonebookMembershipRoleError => 'Please choose a role'; @override - String get recommendationDelete => 'Delete'; + String phonebookModifyMembership(String name) { + return 'Modify $name\'s role'; + } @override - String get recommendationAddImage => 'Please add an image'; + String get phonebookName => 'Last name:'; @override - String get recommendationAddedRecommendation => 'Deal added'; + String get phonebookNameCopied => 'Name and first name copied to clipboard'; @override - String get recommendationEditedRecommendation => 'Deal updated'; + String get phonebookNamePure => 'Last name'; @override - String get recommendationDeleteRecommendationConfirmation => - 'Are you sure you want to delete this deal?'; + String get phonebookNewTerm => 'New term'; @override - String get recommendationDeleteRecommendation => 'Delete'; + String get phonebookNewTermConfirmed => 'Term changed'; @override - String get recommendationDeletingRecommendationError => - 'Error during deletion'; + String get phonebookNickname => 'Nickname:'; @override - String get recommendationDeletedRecommendation => 'Deal deleted'; + String get phonebookNicknameCopied => 'Nickname copied to clipboard'; @override - String get recommendationIncorrectOrMissingFields => - 'Incorrect or missing fields'; + String get phonebookNoAssociationFound => 'No association found'; @override - String get recommendationEditingError => 'Edit failed'; + String get phonebookNoMember => 'No member'; @override - String get recommendationAddingError => 'Add failed'; + String get phonebookNoMemberRole => 'No role found'; @override - String get recommendationCopiedCode => 'Discount code copied'; + String get phonebookNoRoleTags => 'No role tags found'; @override - String get seedLibraryAdd => 'Add'; + String get phonebookPhone => 'Phone:'; @override - String get seedLibraryAddedPlant => 'Plant added'; + String get phonebookPhonebook => 'Phonebook'; @override - String get seedLibraryAddedSpecies => 'Species added'; + String get phonebookPhonebookSearch => 'Search'; @override - String get seedLibraryAddingError => 'Error during addition'; + String get phonebookPhonebookSearchAssociation => 'Association'; @override - String get seedLibraryAddPlant => 'Deposit a plant'; + String get phonebookPhonebookSearchField => 'Search:'; @override - String get seedLibraryAddSpecies => 'Add a species'; + String get phonebookPhonebookSearchName => 'Last name/First name/Nickname'; @override - String get seedLibraryAll => 'All'; + String get phonebookPhonebookSearchRole => 'Position'; @override - String get seedLibraryAncestor => 'Ancestor'; + String get phonebookPresidentRoleTag => 'Prez\''; @override - String get seedLibraryAround => 'around'; + String get phonebookPromoNotGiven => 'Promotion not provided'; @override - String get seedLibraryAutumn => 'Autumn'; + String phonebookPromotion(int year) { + return 'Promotion $year'; + } @override - String get seedLibraryBorrowedPlant => 'Borrowed plant'; + String get phonebookReorderingError => 'Error during reordering'; @override - String get seedLibraryBorrowingDate => 'Borrowing date:'; + String get phonebookResearch => 'Search'; @override - String get seedLibraryBorrowPlant => 'Borrow plant'; + String get phonebookRolePure => 'Role'; @override - String get seedLibraryCard => 'Card'; + String get phonebookSearchUser => 'Search a user'; @override - String get seedLibraryChoosingAncestor => 'Please choose an ancestor'; + String get phonebookTooHeavyAssociationPicture => + 'Image is too large (max 4MB)'; @override - String get seedLibraryChoosingSpecies => 'Please choose a species'; + String get phonebookUpdateGroups => 'Update groups'; @override - String get seedLibraryChoosingSpeciesOrAncestor => - 'Please choose a species or an ancestor'; + String get phonebookUpdatedAssociation => 'Association updated'; @override - String get seedLibraryContact => 'Contact:'; + String get phonebookUpdatedAssociationPicture => + 'Association picture has been changed'; @override - String get seedLibraryDays => 'days'; + String get phonebookUpdatedGroups => 'Groups updated'; @override - String get seedLibraryDeadMsg => 'Do you want to declare the plant dead?'; + String get phonebookUpdatedMember => 'Member updated'; @override - String get seedLibraryDeadPlant => 'Dead plant'; + String get phonebookUpdatingError => 'Error during update'; @override - String get seedLibraryDeathDate => 'Date of death'; + String get phonebookValidation => 'Validate'; @override - String get seedLibraryDeletedSpecies => 'Species deleted'; + String get purchasesPurchases => 'Purchases'; @override - String get seedLibraryDeleteSpecies => 'Delete species?'; + String get purchasesResearch => 'Search'; @override - String get seedLibraryDeleting => 'Deleting'; + String get purchasesNoPurchasesFound => 'No purchases found'; @override - String get seedLibraryDeletingError => 'Error during deletion'; + String get purchasesNoTickets => 'No tickets'; @override - String get seedLibraryDepositNotAvailable => - 'Plant deposit is not possible without borrowing a plant first'; + String get purchasesTicketsError => 'Error loading tickets'; @override - String get seedLibraryDescription => 'Description'; + String get purchasesPurchasesError => 'Error loading purchases'; @override - String get seedLibraryDifficulty => 'Difficulty:'; + String get purchasesNoPurchases => 'No purchase'; @override - String get seedLibraryEdit => 'Edit'; + String get purchasesTimes => 'times'; @override - String get seedLibraryEditedPlant => 'Plant updated'; + String get purchasesAlreadyUsed => 'Already used'; @override - String get seedLibraryEditInformation => 'Edit information'; + String get purchasesNotPaid => 'Not validated'; @override - String get seedLibraryEditingError => 'Error during editing'; + String get purchasesPleaseSelectProduct => 'Please select a product'; @override - String get seedLibraryEditSpecies => 'Edit species'; + String get purchasesProducts => 'Products'; @override - String get seedLibraryEmptyDifficultyError => 'Please choose a difficulty'; + String get purchasesCancel => 'Cancel'; @override - String get seedLibraryEmptyFieldError => 'Please fill all fields'; + String get purchasesValidate => 'Validate'; @override - String get seedLibraryEmptyTypeError => 'Please choose a plant type'; + String get purchasesLeftScan => 'Scans remaining'; @override - String get seedLibraryEndMonth => 'End month:'; + String get purchasesTag => 'Tag'; @override - String get seedLibraryFacebookUrl => 'Facebook link'; + String get purchasesHistory => 'History'; @override - String get seedLibraryFilters => 'Filters'; + String get purchasesPleaseSelectSeller => 'Please select a seller'; @override - String get seedLibraryForum => 'Oskour mom I killed my plant - Help forum'; + String get purchasesNoTagGiven => 'Warning, no tag entered'; @override - String get seedLibraryForumUrl => 'Forum link'; + String get purchasesTickets => 'Tickets'; @override - String get seedLibraryHelpSheets => 'Plant sheets'; + String get purchasesNoScannableProducts => 'No scannable products'; @override - String get seedLibraryInformation => 'Information:'; + String get purchasesLoading => 'Waiting for scan'; @override - String get seedLibraryMaturationTime => 'Maturation time'; + String get purchasesScan => 'Scan'; @override - String get seedLibraryMonthJan => 'January'; + String get raffleRaffle => 'Raffle'; @override - String get seedLibraryMonthFeb => 'February'; + String get rafflePrize => 'Prize'; @override - String get seedLibraryMonthMar => 'March'; + String get rafflePrizes => 'Prizes'; @override - String get seedLibraryMonthApr => 'April'; + String get raffleActualRaffles => 'Current raffles'; @override - String get seedLibraryMonthMay => 'May'; + String get rafflePastRaffles => 'Past raffles'; @override - String get seedLibraryMonthJun => 'June'; + String get raffleYourTickets => 'All your tickets'; @override - String get seedLibraryMonthJul => 'July'; + String get raffleCreateMenu => 'Creation menu'; @override - String get seedLibraryMonthAug => 'August'; + String get raffleNextRaffles => 'Upcoming raffles'; @override - String get seedLibraryMonthSep => 'September'; + String get raffleNoTicket => 'You have no ticket'; @override - String get seedLibraryMonthOct => 'October'; + String get raffleSeeRaffleDetail => 'View prizes/tickets'; @override - String get seedLibraryMonthNov => 'November'; + String get raffleActualPrize => 'Current prizes'; @override - String get seedLibraryMonthDec => 'December'; + String get raffleMajorPrize => 'Major prizes'; @override - String get seedLibraryMyPlants => 'My plants'; + String get raffleTakeTickets => 'Take your tickets'; @override - String get seedLibraryName => 'Name'; + String get raffleNoTicketBuyable => 'You cannot buy tickets right now'; @override - String get seedLibraryNbSeedsRecommended => 'Number of seeds recommended'; + String get raffleNoCurrentPrize => 'There are no prizes currently'; @override - String get seedLibraryNbSeedsRecommendedError => - 'Please enter a recommended seed number greater than 0'; + String get raffleModifTombola => + 'You can modify your raffles or create new ones, all decisions must then be approved by admins'; @override - String get seedLibraryNoDateError => 'Please enter a date'; + String get raffleCreateYourRaffle => 'Your raffle creation menu'; @override - String get seedLibraryNoFilteredPlants => - 'No plants match your search. Try other filters.'; + String get rafflePossiblePrice => 'Possible prize'; @override - String get seedLibraryNoMorePlant => 'No plants available'; + String get raffleInformation => 'Information and statistics'; @override - String get seedLibraryNoPersonalPlants => - 'You don\'t have any plants yet in your seed library. You can add some in the stocks.'; + String get raffleAccounts => 'Accounts'; @override - String get seedLibraryNoSpecies => 'No species found'; + String get raffleAdd => 'Add'; @override - String get seedLibraryNoStockPlants => 'No plants available in stock'; + String get raffleUpdatedAmount => 'Amount updated'; @override - String get seedLibraryNotes => 'Notes'; + String get raffleUpdatingError => 'Error during update'; @override - String get seedLibraryOk => 'OK'; + String get raffleDeletedPrize => 'Prize deleted'; @override - String get seedLibraryPlantationPeriod => 'Planting period:'; + String get raffleDeletingError => 'Error during deletion'; @override - String get seedLibraryPlantationType => 'Plantation type:'; + String get raffleQuantity => 'Quantity'; @override - String get seedLibraryPlantDetail => 'Plant details'; + String get raffleClose => 'Close'; @override - String get seedLibraryPlantingDate => 'Planting date'; + String get raffleOpen => 'Open'; @override - String get seedLibraryPlantingNow => 'I\'m planting it now'; + String get raffleAddTypeTicketSimple => 'Add'; @override - String get seedLibraryPrefix => 'Prefix'; + String get raffleAddingError => 'Error during addition'; @override - String get seedLibraryPrefixError => 'Prefix already used'; + String get raffleEditTypeTicketSimple => 'Edit'; @override - String get seedLibraryPrefixLengthError => 'The prefix must be 3 characters'; + String get raffleFillField => 'Field cannot be empty'; @override - String get seedLibraryPropagationMethod => 'Propagation method:'; + String get raffleWaiting => 'Loading'; @override - String get seedLibraryReference => 'Reference:'; + String get raffleEditingError => 'Error during editing'; @override - String get seedLibraryRemovedPlant => 'Plant removed'; + String get raffleAddedTicket => 'Ticket added'; @override - String get seedLibraryRemovingError => 'Error removing plant'; + String get raffleEditedTicket => 'Ticket edited'; @override - String get seedLibraryResearch => 'Search'; + String get raffleAlreadyExistTicket => 'Ticket already exists'; @override - String get seedLibrarySaveChanges => 'Save changes'; + String get raffleNumberExpected => 'An integer is expected'; + + @override + String get raffleDeletedTicket => 'Ticket deleted'; + + @override + String get raffleAddPrize => 'Add'; + + @override + String get raffleEditPrize => 'Edit'; + + @override + String get raffleOpenRaffle => 'Open raffle'; + + @override + String get raffleCloseRaffle => 'Close raffle'; + + @override + String get raffleOpenRaffleDescription => + 'You are going to open the raffle, users will be able to buy tickets. You will no longer be able to modify the raffle. Are you sure you want to continue?'; + + @override + String get raffleCloseRaffleDescription => + 'You are going to close the raffle, users will no longer be able to buy tickets. Are you sure you want to continue?'; + + @override + String get raffleNoCurrentRaffle => 'There is no ongoing raffle'; + + @override + String get raffleBoughtTicket => 'Ticket purchased'; + + @override + String get raffleDrawingError => 'Error during drawing'; + + @override + String get raffleInvalidPrice => 'Price must be greater than 0'; + + @override + String get raffleMustBePositive => 'Number must be strictly positive'; + + @override + String get raffleDraw => 'Draw'; + + @override + String get raffleDrawn => 'Drawn'; + + @override + String get raffleError => 'Error'; + + @override + String get raffleGathered => 'Collected'; + + @override + String get raffleTickets => 'Tickets'; + + @override + String get raffleTicket => 'ticket'; + + @override + String get raffleWinner => 'Winner'; + + @override + String get raffleNoPrize => 'No prize'; + + @override + String get raffleDeletePrize => 'Delete prize'; + + @override + String get raffleDeletePrizeDescription => + 'You are going to delete the prize, are you sure you want to continue?'; @override - String get seedLibrarySeason => 'Season:'; + String get raffleDrawing => 'Drawing'; @override - String get seedLibrarySeed => 'Seed'; + String get raffleDrawingDescription => 'Draw the prize winner?'; @override - String get seedLibrarySeeds => 'seeds'; + String get raffleDeleteTicket => 'Delete ticket'; @override - String get seedLibrarySeedDeposit => 'Plant deposit'; + String get raffleDeleteTicketDescription => + 'You are going to delete the ticket, are you sure you want to continue?'; @override - String get seedLibrarySeedLibrary => 'Seed library'; + String get raffleWinningTickets => 'Winning tickets'; @override - String get seedLibrarySeedQuantitySimple => 'Seed quantity'; + String get raffleNoWinningTicketYet => + 'Winning tickets will be displayed here'; @override - String get seedLibrarySeedQuantity => 'Seed quantity:'; + String get raffleName => 'Name'; @override - String get seedLibraryShowDeadPlants => 'Show dead plants'; + String get raffleDescription => 'Description'; @override - String get seedLibrarySpecies => 'Species:'; + String get raffleBuyThisTicket => 'Buy this ticket'; @override - String get seedLibrarySpeciesHelp => 'Help on species'; + String get raffleLockedRaffle => 'Locked raffle'; @override - String get seedLibrarySpeciesPlural => 'Species'; + String get raffleUnavailableRaffle => 'Unavailable raffle'; @override - String get seedLibrarySpeciesSimple => 'Species'; + String get raffleNotEnoughMoney => 'You don\'t have enough money'; @override - String get seedLibrarySpeciesType => 'Species type:'; + String get raffleWinnable => 'winnable'; @override - String get seedLibrarySpring => 'Spring'; + String get raffleNoDescription => 'No description'; @override - String get seedLibraryStartMonth => 'Start month:'; + String get raffleAmount => 'Balance'; @override - String get seedLibraryStock => 'Available stock'; + String get raffleLoading => 'Loading'; @override - String get seedLibrarySummer => 'Summer'; + String get raffleTicketNumber => 'Number of tickets'; @override - String get seedLibraryStocks => 'Stocks'; + String get rafflePrice => 'Price'; @override - String get seedLibraryTimeUntilMaturation => 'Time until maturation:'; + String get raffleEditRaffle => 'Edit raffle'; @override - String get seedLibraryType => 'Type:'; + String get raffleEdit => 'Edit'; @override - String get seedLibraryUnableToOpen => 'Unable to open link'; + String get raffleAddPackTicket => 'Add ticket pack'; @override - String get seedLibraryUpdate => 'Edit'; + String get recommendationRecommendation => 'Recommendation'; @override - String get seedLibraryUpdatedInformation => 'Information updated'; + String get recommendationTitle => 'Title'; @override - String get seedLibraryUpdatedSpecies => 'Species updated'; + String get recommendationLogo => 'Logo'; @override - String get seedLibraryUpdatedPlant => 'Plant updated'; + String get recommendationCode => 'Code'; @override - String get seedLibraryUpdatingError => 'Error updating'; + String get recommendationSummary => 'Short summary'; @override - String get seedLibraryWinter => 'Winter'; + String get recommendationDescription => 'Description'; @override - String get seedLibraryWriteReference => - 'Please write the following reference: '; + String get recommendationAdd => 'Add'; @override - String get settingsAccount => 'Account'; + String get recommendationEdit => 'Edit'; @override - String get settingsAddProfilePicture => 'Add a photo'; + String get recommendationDelete => 'Delete'; @override - String get settingsAdmin => 'Administrator'; + String get recommendationAddImage => 'Please add an image'; @override - String get settingsAskHelp => 'Ask for help'; + String get recommendationAddedRecommendation => 'Deal added'; @override - String get settingsAssociation => 'Association'; + String get recommendationEditedRecommendation => 'Deal updated'; @override - String get settingsBirthday => 'Birthday'; + String get recommendationDeleteRecommendationConfirmation => + 'Are you sure you want to delete this deal?'; @override - String get settingsBugs => 'Bugs'; + String get recommendationDeleteRecommendation => 'Delete'; @override - String get settingsChangePassword => 'Change password'; + String get recommendationDeletingRecommendationError => + 'Error during deletion'; @override - String get settingsChangingPassword => - 'Do you really want to change your password?'; + String get recommendationDeletedRecommendation => 'Deal deleted'; @override - String get settingsConfirmPassword => 'Confirm password'; + String get recommendationIncorrectOrMissingFields => + 'Incorrect or missing fields'; @override - String get settingsCopied => 'Copied!'; + String get recommendationEditingError => 'Edit failed'; @override - String get settingsDarkMode => 'Dark mode'; + String get recommendationAddingError => 'Add failed'; @override - String get settingsDarkModeOff => 'Off'; + String get recommendationCopiedCode => 'Discount code copied'; @override - String get settingsDeleteLogs => 'Delete logs?'; + String get seedLibraryAdd => 'Add'; @override - String get settingsDeleteNotificationLogs => 'Delete notification logs?'; + String get seedLibraryAddedPlant => 'Plant added'; @override - String get settingsDetelePersonalData => 'Delete my personal data'; + String get seedLibraryAddedSpecies => 'Species added'; @override - String get settingsDetelePersonalDataDesc => - 'This action notifies the administrator that you want to delete your personal data.'; + String get seedLibraryAddingError => 'Error during addition'; @override - String get settingsDeleting => 'Deleting'; + String get seedLibraryAddPlant => 'Deposit a plant'; @override - String get settingsEdit => 'Edit'; + String get seedLibraryAddSpecies => 'Add a species'; @override - String get settingsEditAccount => 'Edit account'; + String get seedLibraryAll => 'All'; @override - String get settingsEmail => 'Email'; + String get seedLibraryAncestor => 'Ancestor'; @override - String get settingsEmptyField => 'This field cannot be empty'; + String get seedLibraryAround => 'around'; @override - String get settingsErrorProfilePicture => 'Error editing profile picture'; + String get seedLibraryAutumn => 'Autumn'; @override - String get settingsErrorSendingDemand => 'Error sending request'; + String get seedLibraryBorrowedPlant => 'Borrowed plant'; @override - String get settingsEventsIcal => 'Ical link for events'; + String get seedLibraryBorrowingDate => 'Borrowing date:'; @override - String get settingsExpectingDate => 'Expected birth date'; + String get seedLibraryBorrowPlant => 'Borrow plant'; @override - String get settingsFirstname => 'First name'; + String get seedLibraryCard => 'Card'; @override - String get settingsFloor => 'Floor'; + String get seedLibraryChoosingAncestor => 'Please choose an ancestor'; @override - String get settingsHelp => 'Help'; + String get seedLibraryChoosingSpecies => 'Please choose a species'; @override - String get settingsIcalCopied => 'Ical link copied!'; + String get seedLibraryChoosingSpeciesOrAncestor => + 'Please choose a species or an ancestor'; @override - String get settingsLanguage => 'Language'; + String get seedLibraryContact => 'Contact:'; @override - String get settingsLanguageVar => 'English 🇬🇧'; + String get seedLibraryDays => 'days'; @override - String get settingsLogs => 'Logs'; + String get seedLibraryDeadMsg => 'Do you want to declare the plant dead?'; @override - String get settingsModules => 'Modules'; + String get seedLibraryDeadPlant => 'Dead plant'; @override - String get settingsMyIcs => 'My Ical link'; + String get seedLibraryDeathDate => 'Date of death'; @override - String get settingsName => 'Last name'; + String get seedLibraryDeletedSpecies => 'Species deleted'; @override - String get settingsNewPassword => 'New password'; + String get seedLibraryDeleteSpecies => 'Delete species?'; @override - String get settingsNickname => 'Nickname'; + String get seedLibraryDeleting => 'Deleting'; @override - String get settingsNotifications => 'Notifications'; + String get seedLibraryDeletingError => 'Error during deletion'; @override - String get settingsOldPassword => 'Old password'; + String get seedLibraryDepositNotAvailable => + 'Plant deposit is not possible without borrowing a plant first'; @override - String get settingsPasswordChanged => 'Password changed'; + String get seedLibraryDescription => 'Description'; @override - String get settingsPasswordsNotMatch => 'Passwords do not match'; + String get seedLibraryDifficulty => 'Difficulty:'; @override - String get settingsPersonalData => 'Personal data'; + String get seedLibraryEdit => 'Edit'; @override - String get settingsPersonalisation => 'Personalization'; + String get seedLibraryEditedPlant => 'Plant updated'; @override - String get settingsPhone => 'Phone'; + String get seedLibraryEditInformation => 'Edit information'; @override - String get settingsProfilePicture => 'Profile picture'; + String get seedLibraryEditingError => 'Error during editing'; @override - String get settingsPromo => 'Promotion'; + String get seedLibraryEditSpecies => 'Edit species'; @override - String get settingsRepportBug => 'Report a bug'; + String get seedLibraryEmptyDifficultyError => 'Please choose a difficulty'; @override - String get settingsSave => 'Save'; + String get seedLibraryEmptyFieldError => 'Please fill all fields'; @override - String get settingsSecurity => 'Security'; + String get seedLibraryEmptyTypeError => 'Please choose a plant type'; @override - String get settingsSendedDemand => 'Request sent'; + String get seedLibraryEndMonth => 'End month:'; @override - String get settingsSettings => 'Settings'; + String get seedLibraryFacebookUrl => 'Facebook link'; @override - String get settingsTooHeavyProfilePicture => 'Image is too large (max 4MB)'; + String get seedLibraryFilters => 'Filters'; @override - String get settingsUpdatedProfile => 'Profile updated'; + String get seedLibraryForum => 'Oskour mom I killed my plant - Help forum'; @override - String get settingsUpdatedProfilePicture => 'Profile picture updated'; + String get seedLibraryForumUrl => 'Forum link'; @override - String get settingsUpdateNotification => 'Update notifications'; + String get seedLibraryHelpSheets => 'Plant sheets'; @override - String get settingsUpdatingError => 'Error updating profile'; + String get seedLibraryInformation => 'Information:'; @override - String get settingsVersion => 'Version'; + String get seedLibraryMaturationTime => 'Maturation time'; @override - String get settingsPasswordStrength => 'Password strength'; + String get seedLibraryMonthJan => 'January'; @override - String get settingsPasswordStrengthVeryWeak => 'Very weak'; + String get seedLibraryMonthFeb => 'February'; @override - String get settingsPasswordStrengthWeak => 'Weak'; + String get seedLibraryMonthMar => 'March'; @override - String get settingsPasswordStrengthMedium => 'Medium'; + String get seedLibraryMonthApr => 'April'; @override - String get settingsPasswordStrengthStrong => 'Strong'; + String get seedLibraryMonthMay => 'May'; @override - String get settingsPasswordStrengthVeryStrong => 'Very strong'; + String get seedLibraryMonthJun => 'June'; @override - String get settingsPhoneNumber => 'Phone number'; + String get seedLibraryMonthJul => 'July'; @override - String get settingsValidate => 'Confirm'; + String get seedLibraryMonthAug => 'August'; @override - String get settingsEditedAccount => 'Account edited'; + String get seedLibraryMonthSep => 'September'; @override - String get settingsFailedToEditAccount => 'Failed to edit account'; + String get seedLibraryMonthOct => 'October'; @override - String get settingsChooseLanguage => 'Choose a language'; + String get seedLibraryMonthNov => 'November'; @override - String settingsNotificationCounter(int active, int total) { - String _temp0 = intl.Intl.pluralLogic( - active, - locale: localeName, - other: 'notifications', - one: 'notification', - zero: 'notification', - ); - return '$active/$total active $_temp0'; - } + String get seedLibraryMonthDec => 'December'; @override - String get settingsEvent => 'Event'; + String get seedLibraryMyPlants => 'My plants'; @override - String get settingsIcal => 'Ical link'; + String get seedLibraryName => 'Name'; @override - String get settingsSynncWithCalendar => 'Sync with calendar'; + String get seedLibraryNbSeedsRecommended => 'Number of seeds recommended'; @override - String get settingsIcalLinkCopied => 'Ical link copied'; + String get seedLibraryNbSeedsRecommendedError => + 'Please enter a recommended seed number greater than 0'; @override - String get settingsProfile => 'Profile'; + String get seedLibraryNoDateError => 'Please enter a date'; @override - String get settingsConnexion => 'Connection'; + String get seedLibraryNoFilteredPlants => + 'No plants match your search. Try other filters.'; @override - String get settingsDisconnect => 'Disconnect'; + String get seedLibraryNoMorePlant => 'No plants available'; @override - String get settingsDisconnectDescription => - 'Do you really want to disconnect?'; + String get seedLibraryNoPersonalPlants => + 'You don\'t have any plants yet in your seed library. You can add some in the stocks.'; @override - String get settingsDisconnectionSuccess => 'Disconnected successfully'; + String get seedLibraryNoSpecies => 'No species found'; @override - String get settingsDeleteMyAccount => 'Delete my account'; + String get seedLibraryNoStockPlants => 'No plants available in stock'; @override - String get settingsDeleteMyAccountDescription => - 'This action will send a request to the administrator to delete your account.'; + String get seedLibraryNotes => 'Notes'; @override - String get settingsDeletionAsked => - 'Your account deletion request has been sent to the administrator.'; + String get seedLibraryOk => 'OK'; @override - String get settingsDeleteMyAccountError => - 'Error sending account deletion request'; + String get seedLibraryPlantationPeriod => 'Planting period:'; @override - String get voteAdd => 'Add'; + String get seedLibraryPlantationType => 'Plantation type:'; @override - String get voteAddMember => 'Add a member'; + String get seedLibraryPlantDetail => 'Plant details'; @override - String get voteAddedPretendance => 'List added'; + String get seedLibraryPlantingDate => 'Planting date'; @override - String get voteAddedSection => 'Section added'; + String get seedLibraryPlantingNow => 'I\'m planting it now'; @override - String get voteAddingError => 'Error adding'; + String get seedLibraryPrefix => 'Prefix'; @override - String get voteAddPretendance => 'Add a list'; + String get seedLibraryPrefixError => 'Prefix already used'; @override - String get voteAddSection => 'Add a section'; + String get seedLibraryPrefixLengthError => 'The prefix must be 3 characters'; @override - String get voteAll => 'All'; + String get seedLibraryPropagationMethod => 'Propagation method:'; @override - String get voteAlreadyAddedMember => 'Member already added'; + String get seedLibraryReference => 'Reference:'; @override - String get voteAlreadyVoted => 'Vote recorded'; + String get seedLibraryRemovedPlant => 'Plant removed'; @override - String get voteChooseList => 'Choose a list'; + String get seedLibraryRemovingError => 'Error removing plant'; @override - String get voteClear => 'Reset'; + String get seedLibraryResearch => 'Search'; @override - String get voteClearVotes => 'Reset votes'; + String get seedLibrarySaveChanges => 'Save changes'; @override - String get voteClosedVote => 'Votes closed'; + String get seedLibrarySeason => 'Season:'; @override - String get voteCloseVote => 'Close votes'; + String get seedLibrarySeed => 'Seed'; @override - String get voteConfirmVote => 'Confirm vote'; + String get seedLibrarySeeds => 'seeds'; @override - String get voteCountVote => 'Count votes'; + String get seedLibrarySeedDeposit => 'Plant deposit'; @override - String get voteDeletedAll => 'All deleted'; + String get seedLibrarySeedLibrary => 'Seed library'; @override - String get voteDeletedPipo => 'Fake lists deleted'; + String get seedLibrarySeedQuantitySimple => 'Seed quantity'; @override - String get voteDeletedSection => 'Section deleted'; + String get seedLibrarySeedQuantity => 'Seed quantity:'; @override - String get voteDeleteAll => 'Delete all'; + String get seedLibraryShowDeadPlants => 'Show dead plants'; @override - String get voteDeleteAllDescription => - 'Do you really want to delete everything?'; + String get seedLibrarySpecies => 'Species:'; @override - String get voteDeletePipo => 'Delete fake lists'; + String get seedLibrarySpeciesHelp => 'Help on species'; @override - String get voteDeletePipoDescription => - 'Do you really want to delete the fake lists?'; + String get seedLibrarySpeciesPlural => 'Species'; @override - String get voteDeletePretendance => 'Delete the list'; + String get seedLibrarySpeciesSimple => 'Species'; @override - String get voteDeletePretendanceDesc => - 'Do you really want to delete this list?'; + String get seedLibrarySpeciesType => 'Species type:'; @override - String get voteDeleteSection => 'Delete the section'; + String get seedLibrarySpring => 'Spring'; @override - String get voteDeleteSectionDescription => - 'Do you really want to delete this section?'; + String get seedLibraryStartMonth => 'Start month:'; @override - String get voteDeletingError => 'Error deleting'; + String get seedLibraryStock => 'Available stock'; @override - String get voteDescription => 'Description'; + String get seedLibrarySummer => 'Summer'; @override - String get voteEdit => 'Edit'; + String get seedLibraryStocks => 'Stocks'; @override - String get voteEditedPretendance => 'List edited'; + String get seedLibraryTimeUntilMaturation => 'Time until maturation:'; @override - String get voteEditedSection => 'Section edited'; + String get seedLibraryType => 'Type:'; @override - String get voteEditingError => 'Error editing'; + String get seedLibraryUnableToOpen => 'Unable to open link'; @override - String get voteErrorClosingVotes => 'Error closing votes'; + String get seedLibraryUpdate => 'Edit'; @override - String get voteErrorCountingVotes => 'Error counting votes'; + String get seedLibraryUpdatedInformation => 'Information updated'; @override - String get voteErrorResetingVotes => 'Error resetting votes'; + String get seedLibraryUpdatedSpecies => 'Species updated'; @override - String get voteErrorOpeningVotes => 'Error opening votes'; + String get seedLibraryUpdatedPlant => 'Plant updated'; @override - String get voteIncorrectOrMissingFields => 'Incorrect or missing fields'; + String get seedLibraryUpdatingError => 'Error updating'; @override - String get voteMembers => 'Members'; + String get seedLibraryWinter => 'Winter'; @override - String get voteName => 'Name'; + String get seedLibraryWriteReference => + 'Please write the following reference: '; @override - String get voteNoPretendanceList => 'No list of candidates'; + String get settingsAccount => 'Account'; @override - String get voteNoSection => 'No section'; + String get settingsAddProfilePicture => 'Add a photo'; @override - String get voteCanNotVote => 'You cannot vote'; + String get settingsAdmin => 'Administrator'; @override - String get voteNoSectionList => 'No section'; + String get settingsAskHelp => 'Ask for help'; @override - String get voteNotOpenedVote => 'Vote not opened'; + String get settingsAssociation => 'Association'; @override - String get voteOnGoingCount => 'Counting in progress'; + String get settingsBirthday => 'Birthday'; @override - String get voteOpenVote => 'Open votes'; + String get settingsBugs => 'Bugs'; @override - String get votePipo => 'Fake'; + String get settingsChangePassword => 'Change password'; @override - String get votePretendance => 'Lists'; + String get settingsChangingPassword => + 'Do you really want to change your password?'; @override - String get votePretendanceDeleted => 'Candidate list deleted'; + String get settingsConfirmPassword => 'Confirm password'; @override - String get votePretendanceNotDeleted => 'Error deleting'; + String get settingsCopied => 'Copied!'; @override - String get voteProgram => 'Program'; + String get settingsDarkMode => 'Dark mode'; @override - String get votePublish => 'Publish'; + String get settingsDarkModeOff => 'Off'; @override - String get votePublishVoteDescription => - 'Do you really want to publish the votes?'; + String get settingsDeleteLogs => 'Delete logs?'; @override - String get voteResetedVotes => 'Votes reset'; + String get settingsDeleteNotificationLogs => 'Delete notification logs?'; @override - String get voteResetVote => 'Reset votes'; + String get settingsDetelePersonalData => 'Delete my personal data'; @override - String get voteResetVoteDescription => 'What do you want to do?'; + String get settingsDetelePersonalDataDesc => + 'This action notifies the administrator that you want to delete your personal data.'; @override - String get voteRole => 'Role'; + String get settingsDeleting => 'Deleting'; @override - String get voteSectionDescription => 'Section description'; + String get settingsEdit => 'Edit'; @override - String get voteSection => 'Section'; + String get settingsEditAccount => 'Edit account'; @override - String get voteSectionName => 'Section name'; + String get settingsEmail => 'Email'; @override - String get voteSeeMore => 'See more'; + String get settingsEmptyField => 'This field cannot be empty'; @override - String get voteSelected => 'Selected'; + String get settingsErrorProfilePicture => 'Error editing profile picture'; @override - String get voteShowVotes => 'Show votes'; + String get settingsErrorSendingDemand => 'Error sending request'; @override - String get voteVote => 'Vote'; + String get settingsEventsIcal => 'Ical link for events'; @override - String get voteVoteError => 'Error recording vote'; + String get settingsExpectingDate => 'Expected birth date'; @override - String get voteVoteFor => 'Vote for '; + String get settingsFirstname => 'First name'; @override - String get voteVoteNotStarted => 'Vote not opened'; + String get settingsFloor => 'Floor'; @override - String get voteVoters => 'Voting groups'; + String get settingsHelp => 'Help'; @override - String get voteVoteSuccess => 'Vote recorded'; + String get settingsIcalCopied => 'Ical link copied!'; @override - String get voteVotes => 'Votes'; + String get settingsLanguage => 'Language'; @override - String get voteVotesClosed => 'Votes closed'; + String get settingsLanguageVar => 'English 🇬🇧'; @override - String get voteVotesCounted => 'Votes counted'; + String get settingsLogs => 'Logs'; @override - String get voteVotesOpened => 'Votes opened'; + String get settingsModules => 'Modules'; @override - String get voteWarning => 'Warning'; + String get settingsMyIcs => 'My Ical link'; @override - String get voteWarningMessage => - 'Selection will not be saved.\nDo you want to continue?'; + String get settingsName => 'Last name'; @override - String get moduleAdvert => 'Advert'; + String get settingsNewPassword => 'New password'; @override - String get moduleAdvertDescription => 'View the latest adverts'; + String get settingsNickname => 'Nickname'; @override - String get moduleAmap => 'AMAP'; + String get settingsNotifications => 'Notifications'; @override - String get moduleAmapDescription => 'Order your AMAP basket'; + String get settingsOldPassword => 'Old password'; @override - String get moduleBooking => 'Booking'; + String get settingsPasswordChanged => 'Password changed'; @override - String get moduleBookingDescription => 'Book a room'; + String get settingsPasswordsNotMatch => 'Passwords do not match'; @override - String get moduleCalendar => 'Calendar'; + String get settingsPersonalData => 'Personal data'; @override - String get moduleCalendarDescription => 'View the calendar of events'; + String get settingsPersonalisation => 'Personalization'; @override - String get moduleCentralisation => 'Centralisation'; + String get settingsPhone => 'Phone'; @override - String get moduleCentralisationDescription => 'Viw all links'; + String get settingsProfilePicture => 'Profile picture'; @override - String get moduleCinema => 'Cinema'; + String get settingsPromo => 'Promotion'; @override - String get moduleCinemaDescription => 'View the cinema schedule'; + String get settingsRepportBug => 'Report a bug'; @override - String get moduleEvent => 'Event'; + String get settingsSave => 'Save'; @override - String get moduleEventDescription => 'View events'; + String get settingsSecurity => 'Security'; @override - String get moduleFlappyBird => 'Flappy Bird'; + String get settingsSendedDemand => 'Request sent'; @override - String get moduleFlappyBirdDescription => 'Play Flappy Bird'; + String get settingsSettings => 'Settings'; @override - String get moduleLoan => 'Loan'; + String get settingsTooHeavyProfilePicture => 'Image is too large (max 4MB)'; @override - String get moduleLoanDescription => 'See your loans'; + String get settingsUpdatedProfile => 'Profile updated'; @override - String get modulePhonebook => 'Phonebook'; + String get settingsUpdatedProfilePicture => 'Profile picture updated'; @override - String get modulePhonebookDescription => 'View the phonebook'; + String get settingsUpdateNotification => 'Update notifications'; @override - String get modulePurchases => 'Purchases'; + String get settingsUpdatingError => 'Error updating profile'; @override - String get modulePurchasesDescription => 'View your purchases'; + String get settingsVersion => 'Version'; @override - String get moduleRaffle => 'Raffle'; + String get settingsPasswordStrength => 'Password strength'; @override - String get moduleRaffleDescription => 'View the raffle'; + String get settingsPasswordStrengthVeryWeak => 'Very weak'; @override - String get moduleRecommendation => 'Recommendation'; + String get settingsPasswordStrengthWeak => 'Weak'; @override - String get moduleRecommendationDescription => 'View the recommendations'; + String get settingsPasswordStrengthMedium => 'Medium'; @override - String get moduleSeedLibrary => 'Seed Library'; + String get settingsPasswordStrengthStrong => 'Strong'; @override - String get moduleSeedLibraryDescription => 'View the seed library'; + String get settingsPasswordStrengthVeryStrong => 'Very strong'; @override - String get moduleVote => 'Vote'; + String get settingsPhoneNumber => 'Phone number'; @override - String get moduleVoteDescription => 'Vote for the campaigns'; + String get settingsValidate => 'Confirm'; @override - String get modulePh => 'PH'; + String get settingsEditedAccount => 'Account edited'; @override - String get modulePhDescription => 'View the PH'; + String get settingsFailedToEditAccount => 'Failed to edit account'; @override - String get moduleSettings => 'Settings'; + String get settingsChooseLanguage => 'Choose a language'; @override - String get moduleSettingsDescription => 'Manage your settings'; + String settingsNotificationCounter(int active, int total) { + String _temp0 = intl.Intl.pluralLogic( + active, + locale: localeName, + other: 'notifications', + one: 'notification', + zero: 'notification', + ); + return '$active/$total active $_temp0'; + } @override - String get moduleFeed => 'Feed'; + String get settingsEvent => 'Event'; @override - String get moduleFeedDescription => 'View the latest news'; + String get settingsIcal => 'Ical link'; @override - String get moduleStyleGuide => 'StyleGuide'; + String get settingsSynncWithCalendar => 'Sync with calendar'; @override - String get moduleStyleGuideDescription => 'Style guide for developers'; + String get settingsIcalLinkCopied => 'Ical link copied'; @override - String get moduleAdmin => 'Admin'; + String get settingsProfile => 'Profile'; @override - String get moduleAdminDescription => - 'Administration module for administrators'; + String get settingsConnexion => 'Connection'; @override - String get moduleOthers => 'Others'; + String get settingsDisconnect => 'Disconnect'; @override - String get moduleOthersDescription => 'Other modules'; + String get settingsDisconnectDescription => + 'Do you really want to disconnect?'; @override - String get modulePayment => 'Payment'; + String get settingsDisconnectionSuccess => 'Disconnected successfully'; @override - String get modulePaymentDescription => 'Pay and see your transactions'; + String get settingsDeleteMyAccount => 'Delete my account'; @override - String get paiementTopUp => 'Top-up'; + String get settingsDeleteMyAccountDescription => + 'This action will send a request to the administrator to delete your account.'; @override - String get paiementStoreManagement => 'Association management'; + String get settingsDeletionAsked => + 'Your account deletion request has been sent to the administrator.'; @override - String get paiementDeleteStore => 'Delete association'; + String get settingsDeleteMyAccountError => + 'Error sending account deletion request'; @override - String get paiementDeleteStoreDescription => - 'Are you sure you want to delete this association?'; + String get voteAdd => 'Add'; @override - String get paiementDeleteStoreError => 'Unable to delete the association'; + String get voteAddMember => 'Add a member'; @override - String get paiementStoreDeleted => 'Association deleted'; + String get voteAddedPretendance => 'List added'; @override - String get paiementAddThisDevice => 'Add this device'; + String get voteAddedSection => 'Section added'; @override - String get paiementThisDevice => '(this device)'; + String get voteAddingError => 'Error adding'; @override - String get paiementCancelled => 'Cancelled'; + String get voteAddPretendance => 'Add a list'; @override - String get paiementThe => 'The'; + String get voteAddSection => 'Add a section'; @override - String get paiementOf => 'of'; + String get voteAll => 'All'; @override - String get paiementRefundedThe => 'Refunded on'; + String get voteAlreadyAddedMember => 'Member already added'; @override - String get paiementAt => 'at'; + String get voteAlreadyVoted => 'Vote recorded'; @override - String get paiementPleaseAcceptTOS => 'Please accept the Terms of Service.'; + String get voteChooseList => 'Choose a list'; @override - String get paiementAskDeviceActivation => 'Device activation request'; + String get voteClear => 'Reset'; @override - String get paiementDeviceActivationReceived => - 'The activation request has been received, please check your email to finalize the process'; + String get voteClearVotes => 'Reset votes'; @override - String get paiementRevokeDevice => 'Revoke device?'; + String get voteClosedVote => 'Votes closed'; @override - String get paiementRevokeDeviceDescription => - 'You will no longer be able to use this device for payments'; + String get voteCloseVote => 'Close votes'; @override - String get paiementDeviceRevoked => 'Device revoked'; + String get voteConfirmVote => 'Confirm vote'; @override - String get paiementDeviceRevokingError => 'Error while revoking device'; + String get voteCountVote => 'Count votes'; @override - String get paiementPleaseAcceptPopup => 'Please allow popups'; + String get voteDeletedAll => 'All deleted'; @override - String get paiementProceedSuccessfully => 'Payment completed successfully'; + String get voteDeletedPipo => 'Fake lists deleted'; @override - String get paiementCancelledTransaction => 'Payment cancelled'; + String get voteDeletedSection => 'Section deleted'; @override - String get paiementPleaseEnterMinAmount => - 'Please enter an amount greater than 1'; + String get voteDeleteAll => 'Delete all'; @override - String get paiementMaxAmount => 'The maximum wallet amount is'; + String get voteDeleteAllDescription => + 'Do you really want to delete everything?'; @override - String get paiementPayWithHA => 'Pay with HelloAsso'; + String get voteDeletePipo => 'Delete fake lists'; @override - String get paiementBalanceAfterTopUp => 'Balance after top-up:'; + String get voteDeletePipoDescription => + 'Do you really want to delete the fake lists?'; @override - String get paiementPersonalBalance => 'Personal balance'; + String get voteDeletePretendance => 'Delete the list'; @override - String get paiementDevices => 'Devices'; + String get voteDeletePretendanceDesc => + 'Do you really want to delete this list?'; @override - String get paiementPay => 'Pay'; + String get voteDeleteSection => 'Delete the section'; @override - String get paiementDeviceNotRegistered => 'Device not registered'; + String get voteDeleteSectionDescription => + 'Do you really want to delete this section?'; @override - String get paiementDeviceNotRegisteredDescription => - 'Your device is not registered yet. \nTo register it, please go to the devices page.'; + String get voteDeletingError => 'Error deleting'; @override - String get paiementAccessPage => 'Access the page'; + String get voteDescription => 'Description'; @override - String get paiementDeviceNotActivated => 'Device not activated'; + String get voteEdit => 'Edit'; @override - String get paiementDeviceNotActivatedDescription => - 'Your device is not yet activated. \nTo activate it, please go to the devices page.'; + String get voteEditedPretendance => 'List edited'; @override - String get paiementReactivateRevokedDeviceDescription => - 'Your device has been revoked. \nTo reactivate it, please go to the devices page.'; + String get voteEditedSection => 'Section edited'; @override - String get paiementDeviceRecoveryError => 'Error while retrieving device'; + String get voteEditingError => 'Error editing'; @override - String get paiementStats => 'Stats'; + String get voteErrorClosingVotes => 'Error closing votes'; @override - String get paimentTopUpAction => 'Top-up'; + String get voteErrorCountingVotes => 'Error counting votes'; @override - String get paiementGetBalanceError => 'Error while retrieving balance: '; + String get voteErrorResetingVotes => 'Error resetting votes'; @override - String get paiementLastTransactions => 'Latest transactions'; + String get voteErrorOpeningVotes => 'Error opening votes'; @override - String get paiementGetTransactionsError => - 'Error while retrieving transactions: '; + String get voteIncorrectOrMissingFields => 'Incorrect or missing fields'; @override - String get paiementStoreBalance => 'Association balance'; + String get voteMembers => 'Members'; @override - String get paiementScan => 'Scan'; + String get voteName => 'Name'; @override - String get paiementManagement => 'Management'; + String get voteNoPretendanceList => 'No list of candidates'; @override - String get paiementHistory => 'History'; + String get voteNoSection => 'No section'; @override - String get paiementHandOver => 'Handover'; + String get voteCanNotVote => 'You cannot vote'; @override - String get paiementStores => 'Associations'; + String get voteNoSectionList => 'No section'; @override - String get paiementAdmin => 'Administrator'; + String get voteNotOpenedVote => 'Vote not opened'; @override - String get paiementSuccededTransaction => 'Successful payment'; + String get voteOnGoingCount => 'Counting in progress'; @override - String get paiementNewCGU => 'New Terms of Service'; + String get voteOpenVote => 'Open votes'; @override - String get paiementDecline => 'Decline'; + String get votePipo => 'Fake'; @override - String get paiementAccept => 'Accept'; + String get votePretendance => 'Lists'; @override - String get paiementAmount => 'Amount'; + String get votePretendanceDeleted => 'Candidate list deleted'; @override - String get paiementValidUntil => 'Valid until'; + String get votePretendanceNotDeleted => 'Error deleting'; @override - String get paiementClose => 'Close'; + String get voteProgram => 'Program'; @override - String get paiementPleaseEnterValidAmount => 'Please enter a valid amount'; + String get votePublish => 'Publish'; @override - String get paiementPleaseAuthenticate => 'Please authenticate'; + String get votePublishVoteDescription => + 'Do you really want to publish the votes?'; @override - String get paiementAthenticationRequired => 'Authentication required to pay'; + String get voteResetedVotes => 'Votes reset'; @override - String get paiementNoThanks => 'No thanks'; + String get voteResetVote => 'Reset votes'; @override - String get paiementAuthentificationFailed => 'Authentication failed'; + String get voteResetVoteDescription => 'What do you want to do?'; @override - String get paiementPleaseAddDevice => 'Please add this device to pay'; + String get voteRole => 'Role'; @override - String get paiementPayment => 'Payment'; + String get voteSectionDescription => 'Section description'; @override - String get paiementBalanceAfterTransaction => 'Balance after payment: '; + String get voteSection => 'Section'; @override - String get paiementCancel => 'Cancel'; + String get voteSectionName => 'Section name'; @override - String get paiementLimitedTo => 'Limited to'; + String get voteSeeMore => 'See more'; @override - String get paiementScanCode => 'Scan a code'; + String get voteSelected => 'Selected'; @override - String get paiementNext => 'Next'; + String get voteShowVotes => 'Show votes'; @override - String get paiementCancelTransaction => 'Cancel transaction'; + String get voteVote => 'Vote'; @override - String get paiementTransactionCancelled => 'Transaction cancelled'; + String get voteVoteError => 'Error recording vote'; @override - String get paiementTransactionCancelledDescription => - 'Are you sure you want to cancel the transaction of'; + String get voteVoteFor => 'Vote for '; @override - String get paiementTransactionCancelledError => - 'Error while cancelling the transaction'; + String get voteVoteNotStarted => 'Vote not opened'; @override - String get paiementNoMembership => 'No membership'; + String get voteVoters => 'Voting groups'; @override - String get paiementNoMembershipDescription => - 'This product is not available to non-members. Confirm the payment?'; + String get voteVoteSuccess => 'Vote recorded'; @override - String get paiementQRCodeAlreadyUsed => 'QR Code already used'; + String get voteVotes => 'Votes'; @override - String get paiementCameraPermissionRequired => 'Camera permission required'; + String get voteVotesClosed => 'Votes closed'; @override - String get paiementCameraPerssionRequiredDescription => - 'To scan a QR Code, you must allow camera access.'; + String get voteVotesCounted => 'Votes counted'; @override - String get paiementSettings => 'Settings'; + String get voteVotesOpened => 'Votes opened'; @override - String get paiementReceived => 'Received'; + String get voteWarning => 'Warning'; @override - String get paiementSpent => 'Spent'; + String get voteWarningMessage => + 'Selection will not be saved.\nDo you want to continue?'; @override - String get paiementNoTrasactionForThisMonth => - 'No transactions for this month'; + String get moduleAdvert => 'Advert'; @override - String get paiementNoTransactinon => 'No transaction'; + String get moduleAdvertDescription => 'View the latest adverts'; @override - String get paiementSellerRigths => 'Seller rights'; + String get moduleAmap => 'AMAP'; @override - String get paiementCanBank => 'Can collect payments'; + String get moduleAmapDescription => 'Order your AMAP basket'; @override - String get paiementCanSeeHistory => 'Can view history'; + String get moduleBooking => 'Booking'; @override - String get paiementCanCancelTransaction => 'Can cancel transactions'; + String get moduleBookingDescription => 'Book a room'; @override - String get paiementCanManageSellers => 'Can manage sellers'; + String get moduleCalendar => 'Calendar'; @override - String get paiementAddedSeller => 'Seller added'; + String get moduleCalendarDescription => 'View the calendar of events'; @override - String get paiementAddingSellerError => 'Error while adding seller'; + String get moduleCentralisation => 'Centralisation'; @override - String get paiementBank => 'Collect'; + String get moduleCentralisationDescription => 'Viw all links'; @override - String get paiementSeeHistory => 'View history'; + String get moduleCinema => 'Cinema'; @override - String get paiementCancelTransactions => 'Cancel transactions'; + String get moduleCinemaDescription => 'View the cinema schedule'; @override - String get paiementManageSellers => 'Manage sellers'; + String get moduleEvent => 'Event'; @override - String get paiementStructureAdmin => 'Structure administrator'; + String get moduleEventDescription => 'View events'; @override - String get paiementRightsOf => 'Rights of'; + String get moduleFlappyBird => 'Flappy Bird'; @override - String get paiementRightsUpdated => 'Rights updated'; + String get moduleFlappyBirdDescription => 'Play Flappy Bird'; @override - String get paiementRightsUpdateError => 'Error while updating rights'; + String get moduleLoan => 'Loan'; @override - String get paiementDeleteSellerDescription => - 'Are you sure you want to delete this seller?'; + String get moduleLoanDescription => 'See your loans'; @override - String get paiementDeletedSeller => 'Seller deleted'; + String get modulePhonebook => 'Phonebook'; @override - String get paiementDeletingSellerError => 'Error while deleting seller'; + String get modulePhonebookDescription => 'View the phonebook'; @override - String get paiementDeleteSeller => 'Delete seller'; + String get modulePurchases => 'Purchases'; @override - String get paiementAdd => 'Add'; + String get modulePurchasesDescription => 'View your purchases'; @override - String get paiementAddSeller => 'Add seller'; + String get moduleRaffle => 'Raffle'; @override - String get paiementSellerError => 'You are not a seller of this association'; + String get moduleRaffleDescription => 'View the raffle'; @override - String get paiementSellersOf => 'Sellers of'; + String get moduleRecommendation => 'Recommendation'; @override - String get paiementModify => 'Edit'; + String get moduleRecommendationDescription => 'View the recommendations'; @override - String get paiementAStore => 'an association'; + String get moduleSeedLibrary => 'Seed Library'; @override - String get paiementStoreName => 'Association name'; + String get moduleSeedLibraryDescription => 'View the seed library'; @override - String get paiementSuccessfullyAddedStore => 'Association successfully added'; + String get moduleVote => 'Vote'; @override - String get paiementSuccessfullyModifiedStore => - 'Association successfully updated'; + String get moduleVoteDescription => 'Vote for the campaigns'; @override - String get paiementAddingStoreError => 'Error while adding the association'; + String get modulePh => 'PH'; @override - String get paiementModifyingStoreError => - 'Error while updating the association'; + String get modulePhDescription => 'View the PH'; @override - String get paiementRefund => 'Refund'; + String get moduleSettings => 'Settings'; @override - String get paiementDoneTransaction => 'Transaction completed'; + String get moduleSettingsDescription => 'Manage your settings'; @override - String get paiementRefundAction => 'Refund'; + String get moduleFeed => 'Feed'; @override - String get paiementTotalDuringPeriod => 'Total during the period'; + String get moduleFeedDescription => 'View the latest news'; @override - String get paiementMean => 'Average: '; + String get moduleStyleGuide => 'StyleGuide'; @override - String get paiementTransaction => 'Transaction'; + String get moduleStyleGuideDescription => 'Style guide for developers'; @override - String get paiementTransferStructure => 'Structure transfer'; + String get moduleAdmin => 'Admin'; @override - String get paiementYouAreTransferingStructureTo => - 'You are about to transfer the structure to '; + String get moduleAdminDescription => + 'Administration module for administrators'; @override - String get paiementTransferStructureDescription => - 'The new manager will have access to all structure management features. You will receive an email to confirm this transfer. The link will only be active for 20 minutes. This action is irreversible. Are you sure you want to continue?'; + String get moduleOthers => 'Others'; @override - String get paiementTransferStructureError => - 'Error while transferring structure'; + String get moduleOthersDescription => 'Other modules'; @override - String get paiementTransferStructureSuccess => - 'Structure transfer requested successfully'; + String get modulePayment => 'Payment'; @override - String get paiementNextAccountable => 'Next responsible'; + String get modulePaymentDescription => 'Pay and see your transactions'; } diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index e9b7d0481a..40a32e9c67 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -146,9 +146,33 @@ class AppLocalizationsFr extends AppLocalizations { @override String get adminAssociationsMemberships => 'Adhésions'; + @override + String adminBankAccountHolder(String bankAccountHolder) { + return 'Titulaire du compte bancaire : $bankAccountHolder'; + } + + @override + String get adminBankAccountHolderModified => + 'Titulaire du compte bancaire modifié'; + + @override + String get adminBankDetails => 'Coordonnées bancaires'; + + @override + String get adminBic => 'BIC'; + + @override + String get adminBicError => 'Le BIC doit faire 11 caractères'; + + @override + String get adminCity => 'Ville'; + @override String get adminClearFilters => 'Effacer les filtres'; + @override + String get adminCountry => 'Pays'; + @override String get adminCreateAssociationMembership => 'Créer une adhésion'; @@ -162,6 +186,10 @@ class AppLocalizationsFr extends AppLocalizations { String get adminDateError => 'La date de début doit être avant la date de fin'; + @override + String get adminDefineAsBankAccountHolder => + 'Définir comme titulaire du compte bancaire'; + @override String get adminDelete => 'Supprimer'; @@ -234,6 +262,12 @@ class AppLocalizationsFr extends AppLocalizations { @override String get adminGroups => 'Groupes'; + @override + String get adminIban => 'IBAN'; + + @override + String get adminIbanError => 'L\'IBAN doit faire 27 caractères'; + @override String get adminLoaningGroup => 'Groupe de prêt'; @@ -290,6 +324,24 @@ class AppLocalizationsFr extends AppLocalizations { @override String get adminSchools => 'Écoles'; + @override + String get adminShortId => 'Short ID (3 lettres)'; + + @override + String get adminShortIdError => 'Le short ID doit faire 3 caractères'; + + @override + String get adminSiegeAddress => 'Adresse du siège'; + + @override + String get adminSiret => 'SIRET'; + + @override + String get adminSiretError => 'SIRET must be 14 digits'; + + @override + String get adminStreet => 'Numéro et rue'; + @override String get adminStructures => 'Structures'; @@ -302,6 +354,10 @@ class AppLocalizationsFr extends AppLocalizations { @override String get adminStartDateMinimal => 'Date de début minimale'; + @override + String get adminUndefinedBankAccountHolder => + 'Titulaire du compte bancaire non défini'; + @override String get adminUpdatedAssociationMembership => 'Adhésion modifiée'; @@ -323,6 +379,9 @@ class AppLocalizationsFr extends AppLocalizations { @override String get adminVisibilities => 'Visibilités'; + @override + String get adminZipcode => 'Code postal'; + @override String get adminGroupNotification => 'Notification de groupe'; @@ -2124,2228 +2183,2304 @@ class AppLocalizationsFr extends AppLocalizations { String get othersImageError => 'Erreur lors de l\'ajout de l\'image'; @override - String get phAddNewJournal => 'Ajouter un nouveau journal'; + String get paiementAccept => 'Accepter'; @override - String get phNameField => 'Nom : '; + String get paiementAccessPage => 'Accéder à la page'; @override - String get phDateField => 'Date : '; + String get paiementAdd => 'Ajouter'; @override - String get phDelete => 'Voulez-vous vraiment supprimer ce journal ?'; + String get paiementAddedSeller => 'Vendeur ajouté'; @override - String get phIrreversibleAction => 'Cette action est irréversible'; + String get paiementAddingSellerError => 'Erreur lors de l\'ajout du vendeur'; @override - String get phToHeavyFile => 'Fichier trop volumineux'; + String get paiementAddingStoreError => 'Erreur lors de l\'ajout du magasin'; @override - String get phAddPdfFile => 'Ajouter un fichier PDF'; + String get paiementAddSeller => 'Ajouter un vendeur'; @override - String get phEditPdfFile => 'Modifier le fichier PDF'; + String get paiementAddStore => 'Ajouter un magasin'; @override - String get phPhName => 'Nom du PH'; + String get paiementAddThisDevice => 'Ajouter cet appareil'; @override - String get phDate => 'Date'; + String get paiementAdmin => 'Administrateur'; @override - String get phAdded => 'Ajouté'; + String get paiementAmount => 'Montant'; @override - String get phEdited => 'Modifié'; + String get paiementAskDeviceActivation => + 'Demande d\'activation de l\'appareil'; @override - String get phAddingFileError => 'Erreur d\'ajout'; + String get paiementAStore => 'un magasin'; @override - String get phMissingInformatonsOrPdf => - 'Informations manquantes ou fichier PDF manquant'; + String get paiementAt => 'à'; @override - String get phAdd => 'Ajouter'; + String get paiementAuthenticationRequired => + 'Authentification requise pour payer'; @override - String get phEdit => 'Modifier'; + String get paiementAuthentificationFailed => 'Échec de l\'authentification'; @override - String get phSeePreviousJournal => 'Voir les anciens journaux'; + String get paiementBalanceAfterTopUp => 'Solde après recharge :'; @override - String get phNoJournalInDatabase => 'Pas encore de PH dans la base de donnée'; + String get paiementBalanceAfterTransaction => 'Solde après paiement : '; @override - String get phSuccesDowloading => 'Téléchargé avec succès'; + String get paiementBank => 'Encaisser'; @override - String get phonebookAdd => 'Ajouter'; + String get paiementBillingSpace => 'Espace facturation'; @override - String get phonebookAddAssociation => 'Ajouter une association'; + String get paiementCameraPermissionRequired => + 'Permission d\'accès à la caméra requise'; @override - String get phonebookAddAssociationGroupement => - 'Ajouter un groupement d\'association'; + String get paiementCameraPerssionRequiredDescription => + 'Pour scanner un QR Code, vous devez autoriser l\'accès à la caméra.'; @override - String get phonebookAddedAssociation => 'Association ajoutée'; + String get paiementCanBank => 'Peut encaisser'; @override - String get phonebookAddedMember => 'Membre ajouté'; + String get paiementCanCancelTransaction => 'Peut annuler des transactions'; @override - String get phonebookAddingError => 'Erreur lors de l\'ajout'; + String get paiementCancel => 'Annuler'; @override - String get phonebookAddMember => 'Ajouter un membre'; + String get paiementCancelled => 'Annulé'; @override - String get phonebookAddRole => 'Ajouter un rôle'; + String get paiementCancelledTransaction => 'Paiement annulé'; @override - String get phonebookAdmin => 'Admin'; + String get paiementCancelTransaction => 'Annuler la transaction'; @override - String get phonebookAll => 'Toutes'; + String get paiementCancelTransactions => 'Annuler les transactions'; @override - String get phonebookApparentName => 'Nom public du rôle :'; + String get paiementCanManageSellers => 'Peut gérer les vendeurs'; @override - String get phonebookAssociation => 'Association'; + String get paiementCanSeeHistory => 'Peut voir l\'historique'; @override - String get phonebookAssociationDetail => 'Détail de l\'association :'; + String get paiementClose => 'Fermer'; @override - String get phonebookAssociationGroupement => 'Groupement d\'association'; + String get paiementCreate => 'Créer'; @override - String get phonebookAssociationKind => 'Type d\'association :'; + String get paiementCreateInvoice => 'Créer une facture'; @override - String get phonebookAssociationName => 'Nom de l\'association'; + String get paiementDecline => 'Refuser'; @override - String get phonebookAssociations => 'Associations'; + String get paiementDeletedSeller => 'Vendeur supprimé'; @override - String get phonebookCancel => 'Annuler'; + String get paiementDeleteInvoice => 'Supprimer la facture'; @override - String phonebookChangeTermYear(int year) { - return 'Passer au mandat $year'; - } + String get paiementDeleteSeller => 'Supprimer le vendeur'; @override - String get phonebookChangeTermConfirm => - 'Êtes-vous sûr de vouloir changer tout le mandat ?\nCette action est irréversible !'; + String get paiementDeleteSellerDescription => + 'Voulez-vous vraiment supprimer ce vendeur ?'; @override - String get phonebookClose => 'Fermer'; + String get paiementDeleteSuccessfully => 'Supprimé avec succès'; @override - String get phonebookConfirm => 'Confirmer'; + String get paiementDeleteStore => 'Supprimer le magasin'; @override - String get phonebookCopied => 'Copié dans le presse-papier'; + String get paiementDeleteStoreDescription => + 'Voulez-vous vraiment supprimer ce magasin ?'; @override - String get phonebookDeactivateAssociation => 'Désactiver l\'association'; + String get paiementDeleteStoreError => 'Impossible de supprimer le magasin'; @override - String get phonebookDeactivatedAssociation => 'Association désactivée'; + String get paiementDeletingSellerError => + 'Erreur lors de la suppression du vendeur'; @override - String get phonebookDeactivatedAssociationWarning => - 'Attention, cette association est désactivée, vous ne pouvez pas la modifier'; + String get paiementDeviceActivationReceived => + 'La demande d\'activation est prise en compte, veuilliez consulter votre boite mail pour finaliser la démarche'; @override - String phonebookDeactivateSelectedAssociation(String association) { - return 'Désactiver l\'association $association ?'; - } + String get paiementDeviceNotActivated => 'Appareil non activé'; @override - String get phonebookDeactivatingError => 'Erreur lors de la désactivation'; + String get paiementDeviceNotActivatedDescription => + 'Votre appareil n\'est pas encore activé. \nPour l\'activer, veuillez vous rendre sur la page des appareils.'; @override - String get phonebookDetail => 'Détail :'; + String get paiementDeviceNotRegistered => 'Appareil non enregistré'; @override - String get phonebookDelete => 'Supprimer'; + String get paiementDeviceNotRegisteredDescription => + 'Votre appareil n\'est pas encore enregistré. \nPour l\'enregistrer, veuillez vous rendre sur la page des appareils.'; @override - String get phonebookDeleteAssociation => 'Supprimer l\'association'; + String get paiementDeviceRecoveryError => + 'Erreur lors de la récupération de l\'appareil'; @override - String phonebookDeleteSelectedAssociation(String association) { - return 'Supprimer l\'association $association ?'; - } + String get paiementDeviceRevoked => 'Appareil révoqué'; @override - String get phonebookDeleteAssociationDescription => - 'Ceci va supprimer l\'historique de l\'association'; + String get paiementDeviceRevokingError => + 'Erreur lors de la révocation de l\'appareil'; @override - String get phonebookDeletedAssociation => 'Association supprimée'; + String get paiementDevices => 'Appareils'; @override - String get phonebookDeletedMember => 'Membre supprimé'; + String get paiementDoneTransaction => 'Transaction effectuée'; @override - String get phonebookDeleteRole => 'Supprimer le rôle'; + String get paiementDownload => 'Télécharger'; @override - String phonebookDeleteUserRole(String name) { - return 'Supprimer le rôle de l\'utilisateur $name ?'; + String paiementEditStore(String store) { + return 'Modifier le magasin $store'; } @override - String get phonebookDeactivating => 'Désactiver l\'association ?'; + String get paiementErrorDeleting => 'Erreur lors de la suppression'; @override - String get phonebookDeleting => 'Suppression'; + String get paiementErrorUpdatingStatus => + 'Erreur lors de la mise à jour du statut'; @override - String get phonebookDeletingError => 'Erreur lors de la suppression'; + String paiementFromTo(DateTime from, DateTime to) { + final intl.DateFormat fromDateFormat = intl.DateFormat.yMd(localeName); + final String fromString = fromDateFormat.format(from); + final intl.DateFormat toDateFormat = intl.DateFormat.yMd(localeName); + final String toString = toDateFormat.format(to); + + return 'Du $fromString au $toString'; + } @override - String get phonebookDescription => 'Description'; + String get paiementGetBalanceError => + 'Erreur lors de la récupération du solde : '; @override - String get phonebookEdit => 'Modifier'; + String get paiementGetTransactionsError => + 'Erreur lors de la récupération des transactions : '; @override - String get phonebookEditAssociationGroupement => - 'Modifier le groupement d\'association'; + String get paiementHandOver => 'Passation'; @override - String get phonebookEditAssociationGroups => 'Gérer les groupes'; + String get paiementHistory => 'Historique'; @override - String get phonebookEditAssociationInfo => 'Modifier'; + String get paiementInvoiceCreatedSuccessfully => 'Facture créée avec succès'; @override - String get phonebookEditAssociationMembers => 'Gérer les membres'; + String get paiementInvoices => 'Factures'; @override - String get phonebookEditRole => 'Modifier le rôle'; + String paiementInvoicesPerPage(int quantity) { + return '$quantity factures/page'; + } @override - String get phonebookEditMembership => 'Modifier le rôle'; + String get paiementLastTransactions => 'Dernières transactions'; @override - String get phonebookEmail => 'Email :'; + String get paiementLimitedTo => 'Limité à'; @override - String get phonebookEmailCopied => 'Email copié dans le presse-papier'; + String get paiementManagement => 'Gestion'; @override - String get phonebookEmptyApparentName => 'Veuillez entrer un nom de role'; + String get paiementManageSellers => 'Gérer les vendeurs'; @override - String get phonebookEmptyFieldError => 'Un champ n\'est pas rempli'; + String get paiementMarkPaid => 'Marquer comme payé'; @override - String get phonebookEmptyKindError => - 'Veuillez choisir un type d\'association'; + String get paiementMarkReceived => 'Marquer comme reçu'; @override - String get phonebookEmptyMember => 'Aucun membre sélectionné'; + String get paiementMarkUnpaid => 'Marquer comme non payé'; @override - String get phonebookErrorAssociationLoading => - 'Erreur lors du chargement de l\'association'; + String get paiementMaxAmount => + 'Le montant maximum de votre portefeuille est de'; @override - String get phonebookErrorAssociationNameEmpty => - 'Veuillez entrer un nom d\'association'; + String get paiementMean => 'Moyenne : '; @override - String get phonebookErrorAssociationPicture => - 'Erreur lors de la modification de la photo d\'association'; + String get paiementModify => 'Modifier'; @override - String get phonebookErrorKindsLoading => - 'Erreur lors du chargement des types d\'association'; + String get paiementModifyingStoreError => + 'Erreur lors de la modification du magasin'; @override - String get phonebookErrorLoadAssociationList => - 'Erreur lors du chargement de la liste des associations'; + String get paiementModifySuccessfully => 'Modifié avec succès'; @override - String get phonebookErrorLoadAssociationMember => - 'Erreur lors du chargement des membres de l\'association'; + String get paiementNewCGU => 'Nouvelles Conditions Générales d\'Utilisation'; @override - String get phonebookErrorLoadAssociationPicture => - 'Erreur lors du chargement de la photo d\'association'; + String get paiementNext => 'Suivant'; @override - String get phonebookErrorLoadProfilePicture => 'Erreur'; + String get paiementNextAccountable => 'Prochain responsable'; @override - String get phonebookErrorRoleTagsLoading => - 'Erreur lors du chargement des tags de rôle'; + String get paiementNoInvoiceToCreate => 'Aucune facture à créer'; @override - String get phonebookExistingMembership => - 'Ce membre est déjà dans le mandat actuel'; + String get paiementNoMembership => 'Aucune adhésion'; @override - String get phonebookFilter => 'Filtrer'; + String get paiementNoMembershipDescription => + 'Ce produit n\'est pas disponnible pour les non-adhérents. Confirmer l\'encaissement ?'; @override - String get phonebookFilterDescription => 'Filtrer les associations par type'; + String get paiementNoThanks => 'Non merci'; @override - String get phonebookFirstname => 'Prénom :'; + String get paiementNoTransaction => 'Aucune transaction'; @override - String get phonebookGroupementDeleted => 'Groupement d\'association supprimé'; + String get paiementNoTransactionForThisMonth => + 'Aucune transaction pour ce mois'; @override - String get phonebookGroupementDeleteError => - 'Erreur lors de la suppression du groupement d\'association'; + String get paiementOf => 'de'; @override - String get phonebookGroupementName => 'Nom du groupement'; + String get paiementPaid => 'Payé'; @override - String phonebookGroups(String association) { - return 'Gérer les groupes de $association'; - } + String get paiementPay => 'Payer'; @override - String phonebookTerm(int year) { - return 'Mandat $year'; - } + String get paiementPayment => 'Paiement'; @override - String get phonebookTermChangingError => - 'Erreur lors du changement de mandat'; + String get paiementPayWithHA => 'Payer avec HelloAsso'; @override - String get phonebookMember => 'Membre'; + String get paiementPending => 'En attente'; @override - String get phonebookMemberReordered => 'Membre réordonné'; + String get paiementPersonalBalance => 'Solde personnel'; @override - String phonebookMembers(String association) { - return 'Gérer les membres de $association'; - } + String get paiementPleaseAcceptPopup => 'Veuillez autoriser les popups'; @override - String get phonebookMembershipAssociationError => - 'Veuillez choisir une association'; + String get paiementPleaseAcceptTOS => + 'Veuillez accepter les Conditions Générales d\'Utilisation.'; @override - String get phonebookMembershipRole => 'Rôle :'; + String get paiementPleaseAddDevice => + 'Veuillez ajouter cet appareil pour payer'; @override - String get phonebookMembershipRoleError => 'Veuillez choisir un rôle'; + String get paiementPleaseAuthenticate => 'Veuillez vous authentifier'; @override - String phonebookModifyMembership(String name) { - return 'Modifier le rôle de $name'; - } + String get paiementPleaseEnterMinAmount => + 'Veuillez entrer un montant supérieur à 1'; @override - String get phonebookName => 'Nom :'; + String get paiementPleaseEnterValidAmount => + 'Veuillez entrer un montant valide'; @override - String get phonebookNameCopied => 'Nom et prénom copié dans le presse-papier'; + String get paiementProceedSuccessfully => 'Paiement effectué avec succès'; @override - String get phonebookNamePure => 'Nom'; + String get paiementQRCodeAlreadyUsed => 'QR Code déjà utilisé'; @override - String get phonebookNewTerm => 'Nouveau mandat'; + String get paiementReactivateRevokedDeviceDescription => + 'Votre appareil a été révoqué. \nPour le réactiver, veuillez vous rendre sur la page des appareils.'; @override - String get phonebookNewTermConfirmed => 'Mandat changé'; + String get paiementReceived => 'Reçu'; @override - String get phonebookNickname => 'Surnom :'; + String get paiementRefund => 'Remboursement'; @override - String get phonebookNicknameCopied => 'Surnom copié dans le presse-papier'; + String get paiementRefundAction => 'Rembourser'; @override - String get phonebookNoAssociationFound => 'Aucune association trouvée'; + String get paiementRefundedThe => 'Remboursé le'; @override - String get phonebookNoMember => 'Aucun membre'; + String get paiementRevokeDevice => 'Révoquer l\'appareil ?'; @override - String get phonebookNoMemberRole => 'Aucun role trouvé'; + String get paiementRevokeDeviceDescription => + 'Vous ne pourrez plus utiliser cet appareil pour les paiements'; @override - String get phonebookNoRoleTags => 'Aucun tag de rôle trouvé'; + String get paiementRightsOf => 'Droits de'; @override - String get phonebookPhone => 'Téléphone :'; + String get paiementRightsUpdated => 'Droits mis à jour'; @override - String get phonebookPhonebook => 'Annuaire'; + String get paiementRightsUpdateError => + 'Erreur lors de la mise à jour des droits'; @override - String get phonebookPhonebookSearch => 'Rechercher'; + String get paiementScan => 'Scanner'; @override - String get phonebookPhonebookSearchAssociation => 'Association'; + String get paiementScanCode => 'Scanner un code'; @override - String get phonebookPhonebookSearchField => 'Rechercher :'; + String get paiementSeeHistory => 'Voir l\'historique'; @override - String get phonebookPhonebookSearchName => 'Nom/Prénom/Surnom'; + String get paiementSelectStructure => 'Choisir une structure'; @override - String get phonebookPhonebookSearchRole => 'Poste'; + String get paiementSellerError => 'Vous n\'êtes pas vendeur de ce magasin'; @override - String get phonebookPresidentRoleTag => 'Prez\''; + String get paiementSellerRigths => 'Droits du vendeur'; @override - String get phonebookPromoNotGiven => 'Promo non renseignée'; + String get paiementSellersOf => 'Les vendeurs de'; @override - String phonebookPromotion(int year) { - return 'Promotion $year'; - } + String get paiementSettings => 'Paramètres'; @override - String get phonebookReorderingError => 'Erreur lors du réordonnement'; + String get paiementSpent => 'Déboursé'; @override - String get phonebookResearch => 'Rechercher'; + String get paiementStats => 'Stats'; @override - String get phonebookRolePure => 'Rôle'; + String get paiementStoreBalance => 'Solde du magasin'; @override - String get phonebookSearchUser => 'Rechercher un utilisateur'; + String get paiementStoreDeleted => 'Magasin supprimée'; @override - String get phonebookTooHeavyAssociationPicture => - 'L\'image est trop lourde (max 4Mo)'; + String paiementStructureManagement(String structure) { + return 'Gestion de $structure'; + } @override - String get phonebookUpdateGroups => 'Mettre à jour les groupes'; + String get paiementStoreName => 'Nom du magasin'; @override - String get phonebookUpdatedAssociation => 'Association modifiée'; + String get paiementStores => 'Magasins'; @override - String get phonebookUpdatedAssociationPicture => - 'La photo d\'association a été changée'; + String get paiementStructureAdmin => 'Administrateur de la structure'; @override - String get phonebookUpdatedGroups => 'Groupes mis à jour'; + String get paiementSuccededTransaction => 'Paiement réussi'; @override - String get phonebookUpdatedMember => 'Membre modifié'; + String get paiementSuccessfullyAddedStore => 'Magasin ajoutée avec succès'; @override - String get phonebookUpdatingError => 'Erreur lors de la modification'; + String get paiementSuccessfullyModifiedStore => + 'Magasin modifiée avec succès'; @override - String get phonebookValidation => 'Valider'; + String get paiementThe => 'Le'; @override - String get purchasesPurchases => 'Achats'; + String get paiementThisDevice => '(cet appareil)'; @override - String get purchasesResearch => 'Rechercher'; + String get paiementTopUp => 'Recharge'; @override - String get purchasesNoPurchasesFound => 'Aucun achat trouvé'; + String get paiementTopUpAction => 'Recharger'; @override - String get purchasesNoTickets => 'Aucun ticket'; + String get paiementTotalDuringPeriod => 'Total sur la période'; @override - String get purchasesTicketsError => 'Erreur lors du chargement des tickets'; + String get paiementTransaction => 'ransaction'; @override - String get purchasesPurchasesError => 'Erreur lors du chargement des achats'; + String get paiementTransactionCancelled => 'Transaction annulée'; @override - String get purchasesNoPurchases => 'Aucun achat'; + String get paiementTransactionCancelledDescription => + 'Voulez-vous vraiment annuler la transaction de'; @override - String get purchasesTimes => 'fois'; + String get paiementTransactionCancelledError => + 'Erreur lors de l\'annulation de la transaction'; @override - String get purchasesAlreadyUsed => 'Déjà utilisé'; + String get paiementTransferStructure => 'Transfert de structure'; @override - String get purchasesNotPaid => 'Non validé'; + String get paiementTransferStructureDescription => + 'Le nouveau responsable aura accès à toutes les fonctionnalités de gestion de la structure. Vous allez recevoir un email pour valider ce transfert. Le lien ne sera actif que pendant 20 minutes. Cette action est irréversible. Êtes-vous sûr de vouloir continuer ?'; @override - String get purchasesPleaseSelectProduct => 'Veuillez sélectionner un produit'; + String get paiementTransferStructureError => + 'Erreur lors du transfert de la structure'; @override - String get purchasesProducts => 'Produits'; + String get paiementTransferStructureSuccess => + 'Transfert de structure demandé avec succès'; @override - String get purchasesCancel => 'Annuler'; + String get paiementValidUntil => 'Valide jusqu\'à'; @override - String get purchasesValidate => 'Valider'; + String get paiementYouAreTransferingStructureTo => + 'Vous êtes sur le point de transférer la structure à '; @override - String get purchasesLeftScan => 'Scans restants'; + String get phAddNewJournal => 'Ajouter un nouveau journal'; @override - String get purchasesTag => 'Tag'; + String get phNameField => 'Nom : '; @override - String get purchasesHistory => 'Historique'; + String get phDateField => 'Date : '; @override - String get purchasesPleaseSelectSeller => 'Veuillez sélectionner un vendeur'; + String get phDelete => 'Voulez-vous vraiment supprimer ce journal ?'; @override - String get purchasesNoTagGiven => 'Attention, aucun tag n\'a été entré'; + String get phIrreversibleAction => 'Cette action est irréversible'; @override - String get purchasesTickets => 'Tickets'; + String get phToHeavyFile => 'Fichier trop volumineux'; @override - String get purchasesNoScannableProducts => 'Aucun produit scannable'; + String get phAddPdfFile => 'Ajouter un fichier PDF'; @override - String get purchasesLoading => 'En attente de scan'; + String get phEditPdfFile => 'Modifier le fichier PDF'; @override - String get purchasesScan => 'Scanner'; + String get phPhName => 'Nom du PH'; @override - String get raffleRaffle => 'Tombola'; + String get phDate => 'Date'; @override - String get rafflePrize => 'Lot'; + String get phAdded => 'Ajouté'; @override - String get rafflePrizes => 'Lots'; + String get phEdited => 'Modifié'; @override - String get raffleActualRaffles => 'Tombola en cours'; + String get phAddingFileError => 'Erreur d\'ajout'; @override - String get rafflePastRaffles => 'Tombola passés'; + String get phMissingInformatonsOrPdf => + 'Informations manquantes ou fichier PDF manquant'; @override - String get raffleYourTickets => 'Tous vos tickets'; + String get phAdd => 'Ajouter'; @override - String get raffleCreateMenu => 'Menu de Création'; + String get phEdit => 'Modifier'; @override - String get raffleNextRaffles => 'Prochaines tombolas'; + String get phSeePreviousJournal => 'Voir les anciens journaux'; @override - String get raffleNoTicket => 'Vous n\'avez pas de ticket'; + String get phNoJournalInDatabase => 'Pas encore de PH dans la base de donnée'; @override - String get raffleSeeRaffleDetail => 'Voir lots/tickets'; + String get phSuccesDowloading => 'Téléchargé avec succès'; @override - String get raffleActualPrize => 'Lots actuels'; + String get phonebookAdd => 'Ajouter'; @override - String get raffleMajorPrize => 'Lot Majeurs'; + String get phonebookAddAssociation => 'Ajouter une association'; @override - String get raffleTakeTickets => 'Prendre vos tickets'; + String get phonebookAddAssociationGroupement => + 'Ajouter un groupement d\'association'; @override - String get raffleNoTicketBuyable => - 'Vous ne pouvez pas achetez de billets pour l\'instant'; + String get phonebookAddedAssociation => 'Association ajoutée'; @override - String get raffleNoCurrentPrize => 'Il n\'y a aucun lots actuellement'; + String get phonebookAddedMember => 'Membre ajouté'; @override - String get raffleModifTombola => - 'Vous pouvez modifiez vos tombolas ou en créer de nouvelles, toute décision doit ensuite être prise par les admins'; + String get phonebookAddingError => 'Erreur lors de l\'ajout'; @override - String get raffleCreateYourRaffle => 'Votre menu de création de tombolas'; + String get phonebookAddMember => 'Ajouter un membre'; @override - String get rafflePossiblePrice => 'Prix possible'; + String get phonebookAddRole => 'Ajouter un rôle'; @override - String get raffleInformation => 'Information et Statistiques'; + String get phonebookAdmin => 'Admin'; @override - String get raffleAccounts => 'Comptes'; + String get phonebookAll => 'Toutes'; @override - String get raffleAdd => 'Ajouter'; + String get phonebookApparentName => 'Nom public du rôle :'; @override - String get raffleUpdatedAmount => 'Montant mis à jour'; + String get phonebookAssociation => 'Association'; @override - String get raffleUpdatingError => 'Erreur lors de la mise à jour'; + String get phonebookAssociationDetail => 'Détail de l\'association :'; @override - String get raffleDeletedPrize => 'Lot supprimé'; + String get phonebookAssociationGroupement => 'Groupement d\'association'; @override - String get raffleDeletingError => 'Erreur lors de la suppression'; + String get phonebookAssociationKind => 'Type d\'association :'; @override - String get raffleQuantity => 'Quantité'; + String get phonebookAssociationName => 'Nom de l\'association'; @override - String get raffleClose => 'Fermer'; + String get phonebookAssociations => 'Associations'; @override - String get raffleOpen => 'Ouvrir'; + String get phonebookCancel => 'Annuler'; @override - String get raffleAddTypeTicketSimple => 'Ajouter'; + String phonebookChangeTermYear(int year) { + return 'Passer au mandat $year'; + } @override - String get raffleAddingError => 'Erreur lors de l\'ajout'; + String get phonebookChangeTermConfirm => + 'Êtes-vous sûr de vouloir changer tout le mandat ?\nCette action est irréversible !'; @override - String get raffleEditTypeTicketSimple => 'Modifier'; + String get phonebookClose => 'Fermer'; @override - String get raffleFillField => 'Le champ ne peut pas être vide'; + String get phonebookConfirm => 'Confirmer'; @override - String get raffleWaiting => 'Chargement'; + String get phonebookCopied => 'Copié dans le presse-papier'; @override - String get raffleEditingError => 'Erreur lors de la modification'; + String get phonebookDeactivateAssociation => 'Désactiver l\'association'; @override - String get raffleAddedTicket => 'Ticket ajouté'; + String get phonebookDeactivatedAssociation => 'Association désactivée'; @override - String get raffleEditedTicket => 'Ticket modifié'; + String get phonebookDeactivatedAssociationWarning => + 'Attention, cette association est désactivée, vous ne pouvez pas la modifier'; @override - String get raffleAlreadyExistTicket => 'Le ticket existe déjà'; + String phonebookDeactivateSelectedAssociation(String association) { + return 'Désactiver l\'association $association ?'; + } @override - String get raffleNumberExpected => 'Un entier est attendu'; + String get phonebookDeactivatingError => 'Erreur lors de la désactivation'; @override - String get raffleDeletedTicket => 'Ticket supprimé'; + String get phonebookDetail => 'Détail :'; @override - String get raffleAddPrize => 'Ajouter'; + String get phonebookDelete => 'Supprimer'; @override - String get raffleEditPrize => 'Modifier'; + String get phonebookDeleteAssociation => 'Supprimer l\'association'; @override - String get raffleOpenRaffle => 'Ouvrir la tombola'; + String phonebookDeleteSelectedAssociation(String association) { + return 'Supprimer l\'association $association ?'; + } @override - String get raffleCloseRaffle => 'Fermer la tombola'; + String get phonebookDeleteAssociationDescription => + 'Ceci va supprimer l\'historique de l\'association'; @override - String get raffleOpenRaffleDescription => - 'Vous allez ouvrir la tombola, les utilisateurs pourront acheter des tickets. Vous ne pourrez plus modifier la tombola. Êtes-vous sûr de vouloir continuer ?'; + String get phonebookDeletedAssociation => 'Association supprimée'; @override - String get raffleCloseRaffleDescription => - 'Vous allez fermer la tombola, les utilisateurs ne pourront plus acheter de tickets. Êtes-vous sûr de vouloir continuer ?'; + String get phonebookDeletedMember => 'Membre supprimé'; @override - String get raffleNoCurrentRaffle => 'Il n\'y a aucune tombola en cours'; + String get phonebookDeleteRole => 'Supprimer le rôle'; @override - String get raffleBoughtTicket => 'Ticket acheté'; + String phonebookDeleteUserRole(String name) { + return 'Supprimer le rôle de l\'utilisateur $name ?'; + } @override - String get raffleDrawingError => 'Erreur lors du tirage'; + String get phonebookDeactivating => 'Désactiver l\'association ?'; @override - String get raffleInvalidPrice => 'Le prix doit être supérieur à 0'; + String get phonebookDeleting => 'Suppression'; @override - String get raffleMustBePositive => 'Le nombre doit être strictement positif'; + String get phonebookDeletingError => 'Erreur lors de la suppression'; @override - String get raffleDraw => 'Tirer'; + String get phonebookDescription => 'Description'; @override - String get raffleDrawn => 'Tiré'; + String get phonebookEdit => 'Modifier'; @override - String get raffleError => 'Erreur'; + String get phonebookEditAssociationGroupement => + 'Modifier le groupement d\'association'; @override - String get raffleGathered => 'Récolté'; + String get phonebookEditAssociationGroups => 'Gérer les groupes'; @override - String get raffleTickets => 'Tickets'; + String get phonebookEditAssociationInfo => 'Modifier'; @override - String get raffleTicket => 'ticket'; + String get phonebookEditAssociationMembers => 'Gérer les membres'; @override - String get raffleWinner => 'Gagnant'; + String get phonebookEditRole => 'Modifier le rôle'; @override - String get raffleNoPrize => 'Aucun lot'; + String get phonebookEditMembership => 'Modifier le rôle'; @override - String get raffleDeletePrize => 'Supprimer le lot'; + String get phonebookEmail => 'Email :'; @override - String get raffleDeletePrizeDescription => - 'Vous allez supprimer le lot, êtes-vous sûr de vouloir continuer ?'; + String get phonebookEmailCopied => 'Email copié dans le presse-papier'; @override - String get raffleDrawing => 'Tirage'; + String get phonebookEmptyApparentName => 'Veuillez entrer un nom de role'; @override - String get raffleDrawingDescription => 'Tirer le gagnant du lot ?'; + String get phonebookEmptyFieldError => 'Un champ n\'est pas rempli'; @override - String get raffleDeleteTicket => 'Supprimer le ticket'; + String get phonebookEmptyKindError => + 'Veuillez choisir un type d\'association'; @override - String get raffleDeleteTicketDescription => - 'Vous allez supprimer le ticket, êtes-vous sûr de vouloir continuer ?'; + String get phonebookEmptyMember => 'Aucun membre sélectionné'; @override - String get raffleWinningTickets => 'Tickets gagnants'; + String get phonebookErrorAssociationLoading => + 'Erreur lors du chargement de l\'association'; @override - String get raffleNoWinningTicketYet => - 'Les tickets gagnants seront affichés ici'; + String get phonebookErrorAssociationNameEmpty => + 'Veuillez entrer un nom d\'association'; @override - String get raffleName => 'Nom'; + String get phonebookErrorAssociationPicture => + 'Erreur lors de la modification de la photo d\'association'; @override - String get raffleDescription => 'Description'; + String get phonebookErrorKindsLoading => + 'Erreur lors du chargement des types d\'association'; @override - String get raffleBuyThisTicket => 'Acheter ce ticket'; + String get phonebookErrorLoadAssociationList => + 'Erreur lors du chargement de la liste des associations'; @override - String get raffleLockedRaffle => 'Tombola verrouillée'; + String get phonebookErrorLoadAssociationMember => + 'Erreur lors du chargement des membres de l\'association'; @override - String get raffleUnavailableRaffle => 'Tombola indisponible'; + String get phonebookErrorLoadAssociationPicture => + 'Erreur lors du chargement de la photo d\'association'; @override - String get raffleNotEnoughMoney => 'Vous n\'avez pas assez d\'argent'; + String get phonebookErrorLoadProfilePicture => 'Erreur'; @override - String get raffleWinnable => 'gagnable'; + String get phonebookErrorRoleTagsLoading => + 'Erreur lors du chargement des tags de rôle'; @override - String get raffleNoDescription => 'Aucune description'; + String get phonebookExistingMembership => + 'Ce membre est déjà dans le mandat actuel'; @override - String get raffleAmount => 'Solde'; + String get phonebookFilter => 'Filtrer'; @override - String get raffleLoading => 'Chargement'; + String get phonebookFilterDescription => 'Filtrer les associations par type'; @override - String get raffleTicketNumber => 'Nombre de ticket'; + String get phonebookFirstname => 'Prénom :'; @override - String get rafflePrice => 'Prix'; + String get phonebookGroupementDeleted => 'Groupement d\'association supprimé'; @override - String get raffleEditRaffle => 'Modifier la tombola'; + String get phonebookGroupementDeleteError => + 'Erreur lors de la suppression du groupement d\'association'; @override - String get raffleEdit => 'Modifier'; + String get phonebookGroupementName => 'Nom du groupement'; @override - String get raffleAddPackTicket => 'Ajouter un pack de ticket'; + String phonebookGroups(String association) { + return 'Gérer les groupes de $association'; + } @override - String get recommendationRecommendation => 'Bons plans'; + String phonebookTerm(int year) { + return 'Mandat $year'; + } @override - String get recommendationTitle => 'Titre'; + String get phonebookTermChangingError => + 'Erreur lors du changement de mandat'; @override - String get recommendationLogo => 'Logo'; + String get phonebookMember => 'Membre'; @override - String get recommendationCode => 'Code'; + String get phonebookMemberReordered => 'Membre réordonné'; @override - String get recommendationSummary => 'Court résumé'; + String phonebookMembers(String association) { + return 'Gérer les membres de $association'; + } @override - String get recommendationDescription => 'Description'; + String get phonebookMembershipAssociationError => + 'Veuillez choisir une association'; @override - String get recommendationAdd => 'Ajouter'; + String get phonebookMembershipRole => 'Rôle :'; @override - String get recommendationEdit => 'Modifier'; + String get phonebookMembershipRoleError => 'Veuillez choisir un rôle'; @override - String get recommendationDelete => 'Supprimer'; + String phonebookModifyMembership(String name) { + return 'Modifier le rôle de $name'; + } @override - String get recommendationAddImage => 'Veuillez ajouter une image'; + String get phonebookName => 'Nom :'; @override - String get recommendationAddedRecommendation => 'Bon plan ajouté'; + String get phonebookNameCopied => 'Nom et prénom copié dans le presse-papier'; @override - String get recommendationEditedRecommendation => 'Bon plan modifié'; + String get phonebookNamePure => 'Nom'; @override - String get recommendationDeleteRecommendationConfirmation => - 'Êtes-vous sûr de vouloir supprimer ce bon plan ?'; + String get phonebookNewTerm => 'Nouveau mandat'; @override - String get recommendationDeleteRecommendation => 'Suppresion'; + String get phonebookNewTermConfirmed => 'Mandat changé'; @override - String get recommendationDeletingRecommendationError => - 'Erreur lors de la suppression'; + String get phonebookNickname => 'Surnom :'; @override - String get recommendationDeletedRecommendation => 'Bon plan supprimé'; + String get phonebookNicknameCopied => 'Surnom copié dans le presse-papier'; @override - String get recommendationIncorrectOrMissingFields => - 'Champs incorrects ou manquants'; + String get phonebookNoAssociationFound => 'Aucune association trouvée'; @override - String get recommendationEditingError => 'Échec de la modification'; + String get phonebookNoMember => 'Aucun membre'; @override - String get recommendationAddingError => 'Échec de l\'ajout'; + String get phonebookNoMemberRole => 'Aucun role trouvé'; @override - String get recommendationCopiedCode => 'Code de réduction copié'; + String get phonebookNoRoleTags => 'Aucun tag de rôle trouvé'; @override - String get seedLibraryAdd => 'Ajouter'; + String get phonebookPhone => 'Téléphone :'; @override - String get seedLibraryAddedPlant => 'Plante ajoutée'; + String get phonebookPhonebook => 'Annuaire'; @override - String get seedLibraryAddedSpecies => 'Espèce ajoutée'; + String get phonebookPhonebookSearch => 'Rechercher'; @override - String get seedLibraryAddingError => 'Erreur lors de l\'ajout'; + String get phonebookPhonebookSearchAssociation => 'Association'; @override - String get seedLibraryAddPlant => 'Déposer une plante'; + String get phonebookPhonebookSearchField => 'Rechercher :'; @override - String get seedLibraryAddSpecies => 'Ajouter une espèce'; + String get phonebookPhonebookSearchName => 'Nom/Prénom/Surnom'; @override - String get seedLibraryAll => 'Toutes'; + String get phonebookPhonebookSearchRole => 'Poste'; @override - String get seedLibraryAncestor => 'Ancêtre'; + String get phonebookPresidentRoleTag => 'Prez\''; @override - String get seedLibraryAround => 'environ'; + String get phonebookPromoNotGiven => 'Promo non renseignée'; @override - String get seedLibraryAutumn => 'Automne'; + String phonebookPromotion(int year) { + return 'Promotion $year'; + } @override - String get seedLibraryBorrowedPlant => 'Plante empruntée'; + String get phonebookReorderingError => 'Erreur lors du réordonnement'; @override - String get seedLibraryBorrowingDate => 'Date d\'emprunt :'; + String get phonebookResearch => 'Rechercher'; @override - String get seedLibraryBorrowPlant => 'Emprunter la plante'; + String get phonebookRolePure => 'Rôle'; @override - String get seedLibraryCard => 'Carte'; + String get phonebookSearchUser => 'Rechercher un utilisateur'; @override - String get seedLibraryChoosingAncestor => 'Veuillez choisir un ancêtre'; + String get phonebookTooHeavyAssociationPicture => + 'L\'image est trop lourde (max 4Mo)'; @override - String get seedLibraryChoosingSpecies => 'Veuillez choisir une espèce'; + String get phonebookUpdateGroups => 'Mettre à jour les groupes'; @override - String get seedLibraryChoosingSpeciesOrAncestor => - 'Veuillez choisir une espèce ou un ancêtre'; + String get phonebookUpdatedAssociation => 'Association modifiée'; @override - String get seedLibraryContact => 'Contact :'; + String get phonebookUpdatedAssociationPicture => + 'La photo d\'association a été changée'; @override - String get seedLibraryDays => 'jours'; + String get phonebookUpdatedGroups => 'Groupes mis à jour'; @override - String get seedLibraryDeadMsg => 'Voulez-vous déclarer la plante morte ?'; + String get phonebookUpdatedMember => 'Membre modifié'; @override - String get seedLibraryDeadPlant => 'Plante morte'; + String get phonebookUpdatingError => 'Erreur lors de la modification'; @override - String get seedLibraryDeathDate => 'Date de mort'; + String get phonebookValidation => 'Valider'; @override - String get seedLibraryDeletedSpecies => 'Espèce supprimée'; + String get purchasesPurchases => 'Achats'; @override - String get seedLibraryDeleteSpecies => 'Supprimer l\'espèce ?'; + String get purchasesResearch => 'Rechercher'; @override - String get seedLibraryDeleting => 'Suppression'; + String get purchasesNoPurchasesFound => 'Aucun achat trouvé'; @override - String get seedLibraryDeletingError => 'Erreur lors de la suppression'; + String get purchasesNoTickets => 'Aucun ticket'; @override - String get seedLibraryDepositNotAvailable => - 'Le dépôt de plantes n\'est pas possible sans emprunter une plante au préalable'; + String get purchasesTicketsError => 'Erreur lors du chargement des tickets'; @override - String get seedLibraryDescription => 'Description'; + String get purchasesPurchasesError => 'Erreur lors du chargement des achats'; @override - String get seedLibraryDifficulty => 'Difficulté :'; + String get purchasesNoPurchases => 'Aucun achat'; @override - String get seedLibraryEdit => 'Modifier'; + String get purchasesTimes => 'fois'; @override - String get seedLibraryEditedPlant => 'Plante modifiée'; + String get purchasesAlreadyUsed => 'Déjà utilisé'; @override - String get seedLibraryEditInformation => 'Modifier les informations'; + String get purchasesNotPaid => 'Non validé'; @override - String get seedLibraryEditingError => 'Erreur lors de la modification'; + String get purchasesPleaseSelectProduct => 'Veuillez sélectionner un produit'; @override - String get seedLibraryEditSpecies => 'Modifier l\'espèce'; + String get purchasesProducts => 'Produits'; @override - String get seedLibraryEmptyDifficultyError => - 'Veuillez choisir une difficulté'; + String get purchasesCancel => 'Annuler'; @override - String get seedLibraryEmptyFieldError => 'Veuillez remplir tous les champs'; + String get purchasesValidate => 'Valider'; @override - String get seedLibraryEmptyTypeError => 'Veuillez choisir un type de plante'; + String get purchasesLeftScan => 'Scans restants'; @override - String get seedLibraryEndMonth => 'Mois de fin :'; + String get purchasesTag => 'Tag'; @override - String get seedLibraryFacebookUrl => 'Lien Facebook'; + String get purchasesHistory => 'Historique'; @override - String get seedLibraryFilters => 'Filtres'; + String get purchasesPleaseSelectSeller => 'Veuillez sélectionner un vendeur'; @override - String get seedLibraryForum => - 'Oskour maman j\'ai tué ma plante - Forum d\'aide'; + String get purchasesNoTagGiven => 'Attention, aucun tag n\'a été entré'; @override - String get seedLibraryForumUrl => 'Lien Forum'; + String get purchasesTickets => 'Tickets'; @override - String get seedLibraryHelpSheets => 'Fiches sur les plantes'; + String get purchasesNoScannableProducts => 'Aucun produit scannable'; @override - String get seedLibraryInformation => 'Informations :'; + String get purchasesLoading => 'En attente de scan'; @override - String get seedLibraryMaturationTime => 'Temps de maturation'; + String get purchasesScan => 'Scanner'; @override - String get seedLibraryMonthJan => 'Janvier'; + String get raffleRaffle => 'Tombola'; @override - String get seedLibraryMonthFeb => 'Février'; + String get rafflePrize => 'Lot'; @override - String get seedLibraryMonthMar => 'Mars'; + String get rafflePrizes => 'Lots'; @override - String get seedLibraryMonthApr => 'Avril'; + String get raffleActualRaffles => 'Tombola en cours'; @override - String get seedLibraryMonthMay => 'Mai'; + String get rafflePastRaffles => 'Tombola passés'; @override - String get seedLibraryMonthJun => 'Juin'; + String get raffleYourTickets => 'Tous vos tickets'; @override - String get seedLibraryMonthJul => 'Juillet'; + String get raffleCreateMenu => 'Menu de Création'; @override - String get seedLibraryMonthAug => 'Août'; + String get raffleNextRaffles => 'Prochaines tombolas'; @override - String get seedLibraryMonthSep => 'Septembre'; + String get raffleNoTicket => 'Vous n\'avez pas de ticket'; @override - String get seedLibraryMonthOct => 'Octobre'; + String get raffleSeeRaffleDetail => 'Voir lots/tickets'; @override - String get seedLibraryMonthNov => 'Novembre'; + String get raffleActualPrize => 'Lots actuels'; @override - String get seedLibraryMonthDec => 'Décembre'; + String get raffleMajorPrize => 'Lot Majeurs'; @override - String get seedLibraryMyPlants => 'Mes plantes'; + String get raffleTakeTickets => 'Prendre vos tickets'; @override - String get seedLibraryName => 'Nom'; + String get raffleNoTicketBuyable => + 'Vous ne pouvez pas achetez de billets pour l\'instant'; @override - String get seedLibraryNbSeedsRecommended => 'Nombre de graines recommandées'; + String get raffleNoCurrentPrize => 'Il n\'y a aucun lots actuellement'; @override - String get seedLibraryNbSeedsRecommendedError => - 'Veuillez entrer un nombre de graines recommandé supérieur à 0'; + String get raffleModifTombola => + 'Vous pouvez modifiez vos tombolas ou en créer de nouvelles, toute décision doit ensuite être prise par les admins'; @override - String get seedLibraryNoDateError => 'Veuillez entrer une date'; + String get raffleCreateYourRaffle => 'Votre menu de création de tombolas'; @override - String get seedLibraryNoFilteredPlants => - 'Aucune plante ne correspond à votre recherche. Essayez d\'autres filtres.'; + String get rafflePossiblePrice => 'Prix possible'; @override - String get seedLibraryNoMorePlant => 'Aucune plante n\'est disponible'; + String get raffleInformation => 'Information et Statistiques'; @override - String get seedLibraryNoPersonalPlants => - 'Vous n\'avez pas encore de plantes dans votre grainothèque. Vous pouvez en ajouter en allant dans les stocks.'; + String get raffleAccounts => 'Comptes'; @override - String get seedLibraryNoSpecies => 'Aucune espèce trouvée'; + String get raffleAdd => 'Ajouter'; @override - String get seedLibraryNoStockPlants => - 'Aucune plante disponible dans le stock'; + String get raffleUpdatedAmount => 'Montant mis à jour'; @override - String get seedLibraryNotes => 'Notes'; + String get raffleUpdatingError => 'Erreur lors de la mise à jour'; @override - String get seedLibraryOk => 'OK'; + String get raffleDeletedPrize => 'Lot supprimé'; @override - String get seedLibraryPlantationPeriod => 'Période de plantation :'; + String get raffleDeletingError => 'Erreur lors de la suppression'; @override - String get seedLibraryPlantationType => 'Type de plantation :'; + String get raffleQuantity => 'Quantité'; @override - String get seedLibraryPlantDetail => 'Détail de la plante'; + String get raffleClose => 'Fermer'; @override - String get seedLibraryPlantingDate => 'Date de plantation'; + String get raffleOpen => 'Ouvrir'; @override - String get seedLibraryPlantingNow => 'Je la plante maintenant'; + String get raffleAddTypeTicketSimple => 'Ajouter'; @override - String get seedLibraryPrefix => 'Préfixe'; + String get raffleAddingError => 'Erreur lors de l\'ajout'; @override - String get seedLibraryPrefixError => 'Prefixe déjà utilisé'; + String get raffleEditTypeTicketSimple => 'Modifier'; @override - String get seedLibraryPrefixLengthError => - 'Le préfixe doit faire 3 caractères'; + String get raffleFillField => 'Le champ ne peut pas être vide'; @override - String get seedLibraryPropagationMethod => 'Méthode de propagation :'; + String get raffleWaiting => 'Chargement'; @override - String get seedLibraryReference => 'Référence :'; + String get raffleEditingError => 'Erreur lors de la modification'; @override - String get seedLibraryRemovedPlant => 'Plante supprimée'; + String get raffleAddedTicket => 'Ticket ajouté'; @override - String get seedLibraryRemovingError => 'Erreur lors de la suppression'; + String get raffleEditedTicket => 'Ticket modifié'; @override - String get seedLibraryResearch => 'Recherche'; + String get raffleAlreadyExistTicket => 'Le ticket existe déjà'; @override - String get seedLibrarySaveChanges => 'Sauvegarder les modifications'; + String get raffleNumberExpected => 'Un entier est attendu'; @override - String get seedLibrarySeason => 'Saison :'; + String get raffleDeletedTicket => 'Ticket supprimé'; @override - String get seedLibrarySeed => 'Graine'; + String get raffleAddPrize => 'Ajouter'; @override - String get seedLibrarySeeds => 'graines'; + String get raffleEditPrize => 'Modifier'; @override - String get seedLibrarySeedDeposit => 'Dépôt de plantes'; + String get raffleOpenRaffle => 'Ouvrir la tombola'; @override - String get seedLibrarySeedLibrary => 'Grainothèque'; + String get raffleCloseRaffle => 'Fermer la tombola'; @override - String get seedLibrarySeedQuantitySimple => 'Quantité de graines'; + String get raffleOpenRaffleDescription => + 'Vous allez ouvrir la tombola, les utilisateurs pourront acheter des tickets. Vous ne pourrez plus modifier la tombola. Êtes-vous sûr de vouloir continuer ?'; @override - String get seedLibrarySeedQuantity => 'Quantité de graines :'; + String get raffleCloseRaffleDescription => + 'Vous allez fermer la tombola, les utilisateurs ne pourront plus acheter de tickets. Êtes-vous sûr de vouloir continuer ?'; @override - String get seedLibraryShowDeadPlants => 'Afficher les plantes mortes'; + String get raffleNoCurrentRaffle => 'Il n\'y a aucune tombola en cours'; @override - String get seedLibrarySpecies => 'Espèce :'; + String get raffleBoughtTicket => 'Ticket acheté'; @override - String get seedLibrarySpeciesHelp => 'Aide sur l\'espèce'; + String get raffleDrawingError => 'Erreur lors du tirage'; @override - String get seedLibrarySpeciesPlural => 'Espèces'; + String get raffleInvalidPrice => 'Le prix doit être supérieur à 0'; @override - String get seedLibrarySpeciesSimple => 'Espèce'; + String get raffleMustBePositive => 'Le nombre doit être strictement positif'; @override - String get seedLibrarySpeciesType => 'Type d\'espèce :'; + String get raffleDraw => 'Tirer'; @override - String get seedLibrarySpring => 'Printemps'; + String get raffleDrawn => 'Tiré'; @override - String get seedLibraryStartMonth => 'Mois de début :'; + String get raffleError => 'Erreur'; @override - String get seedLibraryStock => 'Stock disponible'; + String get raffleGathered => 'Récolté'; @override - String get seedLibrarySummer => 'Été'; + String get raffleTickets => 'Tickets'; @override - String get seedLibraryStocks => 'Stocks'; + String get raffleTicket => 'ticket'; @override - String get seedLibraryTimeUntilMaturation => 'Temps avant maturation :'; + String get raffleWinner => 'Gagnant'; @override - String get seedLibraryType => 'Type :'; + String get raffleNoPrize => 'Aucun lot'; @override - String get seedLibraryUnableToOpen => 'Impossible d\'ouvrir le lien'; + String get raffleDeletePrize => 'Supprimer le lot'; @override - String get seedLibraryUpdate => 'Modifier'; + String get raffleDeletePrizeDescription => + 'Vous allez supprimer le lot, êtes-vous sûr de vouloir continuer ?'; @override - String get seedLibraryUpdatedInformation => 'Informations modifiées'; + String get raffleDrawing => 'Tirage'; @override - String get seedLibraryUpdatedSpecies => 'Espèce modifiée'; + String get raffleDrawingDescription => 'Tirer le gagnant du lot ?'; @override - String get seedLibraryUpdatedPlant => 'Plante modifiée'; + String get raffleDeleteTicket => 'Supprimer le ticket'; @override - String get seedLibraryUpdatingError => 'Erreur lors de la modification'; + String get raffleDeleteTicketDescription => + 'Vous allez supprimer le ticket, êtes-vous sûr de vouloir continuer ?'; @override - String get seedLibraryWinter => 'Hiver'; + String get raffleWinningTickets => 'Tickets gagnants'; @override - String get seedLibraryWriteReference => - 'Veuillez écrire la référence suivante : '; + String get raffleNoWinningTicketYet => + 'Les tickets gagnants seront affichés ici'; @override - String get settingsAccount => 'Compte'; + String get raffleName => 'Nom'; @override - String get settingsAddProfilePicture => 'Ajouter une photo'; + String get raffleDescription => 'Description'; @override - String get settingsAdmin => 'Administrateur'; + String get raffleBuyThisTicket => 'Acheter ce ticket'; @override - String get settingsAskHelp => 'Demander de l\'aide'; + String get raffleLockedRaffle => 'Tombola verrouillée'; @override - String get settingsAssociation => 'Association'; + String get raffleUnavailableRaffle => 'Tombola indisponible'; @override - String get settingsBirthday => 'Date de naissance'; + String get raffleNotEnoughMoney => 'Vous n\'avez pas assez d\'argent'; @override - String get settingsBugs => 'Bugs'; + String get raffleWinnable => 'gagnable'; @override - String get settingsChangePassword => 'Changer de mot de passe'; + String get raffleNoDescription => 'Aucune description'; @override - String get settingsChangingPassword => - 'Voulez-vous vraiment changer votre mot de passe ?'; + String get raffleAmount => 'Solde'; @override - String get settingsConfirmPassword => 'Confirmer le mot de passe'; + String get raffleLoading => 'Chargement'; @override - String get settingsCopied => 'Copié !'; + String get raffleTicketNumber => 'Nombre de ticket'; @override - String get settingsDarkMode => 'Mode sombre'; + String get rafflePrice => 'Prix'; @override - String get settingsDarkModeOff => 'Désactivé'; + String get raffleEditRaffle => 'Modifier la tombola'; @override - String get settingsDeleteLogs => 'Supprimer les logs ?'; + String get raffleEdit => 'Modifier'; @override - String get settingsDeleteNotificationLogs => - 'Supprimer les logs des notifications ?'; + String get raffleAddPackTicket => 'Ajouter un pack de ticket'; @override - String get settingsDetelePersonalData => 'Supprimer mes données personnelles'; + String get recommendationRecommendation => 'Bons plans'; @override - String get settingsDetelePersonalDataDesc => - 'Cette action notifie l\'administrateur que vous souhaitez supprimer vos données personnelles.'; + String get recommendationTitle => 'Titre'; @override - String get settingsDeleting => 'Suppresion'; + String get recommendationLogo => 'Logo'; @override - String get settingsEdit => 'Modifier'; + String get recommendationCode => 'Code'; @override - String get settingsEditAccount => 'Modifier mon profil'; + String get recommendationSummary => 'Court résumé'; @override - String get settingsEmail => 'Email'; + String get recommendationDescription => 'Description'; @override - String get settingsEmptyField => 'Ce champ ne peut pas être vide'; + String get recommendationAdd => 'Ajouter'; @override - String get settingsErrorProfilePicture => - 'Erreur lors de la modification de la photo de profil'; + String get recommendationEdit => 'Modifier'; @override - String get settingsErrorSendingDemand => - 'Erreur lors de l\'envoi de la demande'; + String get recommendationDelete => 'Supprimer'; @override - String get settingsEventsIcal => 'Lien Ical des événements'; + String get recommendationAddImage => 'Veuillez ajouter une image'; @override - String get settingsExpectingDate => 'Date de naissance attendue'; + String get recommendationAddedRecommendation => 'Bon plan ajouté'; @override - String get settingsFirstname => 'Prénom'; + String get recommendationEditedRecommendation => 'Bon plan modifié'; @override - String get settingsFloor => 'Étage'; + String get recommendationDeleteRecommendationConfirmation => + 'Êtes-vous sûr de vouloir supprimer ce bon plan ?'; @override - String get settingsHelp => 'Aide'; + String get recommendationDeleteRecommendation => 'Suppresion'; @override - String get settingsIcalCopied => 'Lien Ical copié !'; + String get recommendationDeletingRecommendationError => + 'Erreur lors de la suppression'; @override - String get settingsLanguage => 'Langue'; + String get recommendationDeletedRecommendation => 'Bon plan supprimé'; @override - String get settingsLanguageVar => 'Français 🇫🇷'; + String get recommendationIncorrectOrMissingFields => + 'Champs incorrects ou manquants'; @override - String get settingsLogs => 'Logs'; + String get recommendationEditingError => 'Échec de la modification'; @override - String get settingsModules => 'Modules'; + String get recommendationAddingError => 'Échec de l\'ajout'; @override - String get settingsMyIcs => 'Mon lien Ical'; + String get recommendationCopiedCode => 'Code de réduction copié'; @override - String get settingsName => 'Nom'; + String get seedLibraryAdd => 'Ajouter'; @override - String get settingsNewPassword => 'Nouveau mot de passe'; + String get seedLibraryAddedPlant => 'Plante ajoutée'; @override - String get settingsNickname => 'Surnom'; + String get seedLibraryAddedSpecies => 'Espèce ajoutée'; @override - String get settingsNotifications => 'Notifications'; + String get seedLibraryAddingError => 'Erreur lors de l\'ajout'; @override - String get settingsOldPassword => 'Ancien mot de passe'; + String get seedLibraryAddPlant => 'Déposer une plante'; @override - String get settingsPasswordChanged => 'Mot de passe changé'; + String get seedLibraryAddSpecies => 'Ajouter une espèce'; @override - String get settingsPasswordsNotMatch => - 'Les mots de passe ne correspondent pas'; + String get seedLibraryAll => 'Toutes'; @override - String get settingsPersonalData => 'Données personnelles'; + String get seedLibraryAncestor => 'Ancêtre'; @override - String get settingsPersonalisation => 'Personnalisation'; + String get seedLibraryAround => 'environ'; @override - String get settingsPhone => 'Téléphone'; + String get seedLibraryAutumn => 'Automne'; @override - String get settingsProfilePicture => 'Photo de profil'; + String get seedLibraryBorrowedPlant => 'Plante empruntée'; @override - String get settingsPromo => 'Promotion'; + String get seedLibraryBorrowingDate => 'Date d\'emprunt :'; @override - String get settingsRepportBug => 'Signaler un bug'; + String get seedLibraryBorrowPlant => 'Emprunter la plante'; @override - String get settingsSave => 'Enregistrer'; + String get seedLibraryCard => 'Carte'; @override - String get settingsSecurity => 'Sécurité'; + String get seedLibraryChoosingAncestor => 'Veuillez choisir un ancêtre'; @override - String get settingsSendedDemand => 'Demande envoyée'; + String get seedLibraryChoosingSpecies => 'Veuillez choisir une espèce'; @override - String get settingsSettings => 'Paramètres'; + String get seedLibraryChoosingSpeciesOrAncestor => + 'Veuillez choisir une espèce ou un ancêtre'; @override - String get settingsTooHeavyProfilePicture => - 'L\'image est trop lourde (max 4Mo)'; + String get seedLibraryContact => 'Contact :'; @override - String get settingsUpdatedProfile => 'Profil modifié'; + String get seedLibraryDays => 'jours'; @override - String get settingsUpdatedProfilePicture => 'Photo de profil modifiée'; + String get seedLibraryDeadMsg => 'Voulez-vous déclarer la plante morte ?'; @override - String get settingsUpdateNotification => 'Mettre à jour les notifications'; + String get seedLibraryDeadPlant => 'Plante morte'; @override - String get settingsUpdatingError => - 'Erreur lors de la modification du profil'; + String get seedLibraryDeathDate => 'Date de mort'; @override - String get settingsVersion => 'Version'; + String get seedLibraryDeletedSpecies => 'Espèce supprimée'; @override - String get settingsPasswordStrength => 'Force du mot de passe'; + String get seedLibraryDeleteSpecies => 'Supprimer l\'espèce ?'; @override - String get settingsPasswordStrengthVeryWeak => 'Très faible'; + String get seedLibraryDeleting => 'Suppression'; @override - String get settingsPasswordStrengthWeak => 'Faible'; + String get seedLibraryDeletingError => 'Erreur lors de la suppression'; @override - String get settingsPasswordStrengthMedium => 'Moyen'; + String get seedLibraryDepositNotAvailable => + 'Le dépôt de plantes n\'est pas possible sans emprunter une plante au préalable'; @override - String get settingsPasswordStrengthStrong => 'Fort'; + String get seedLibraryDescription => 'Description'; @override - String get settingsPasswordStrengthVeryStrong => 'Très fort'; + String get seedLibraryDifficulty => 'Difficulté :'; @override - String get settingsPhoneNumber => 'Numéro de téléphone'; + String get seedLibraryEdit => 'Modifier'; @override - String get settingsValidate => 'Valider'; + String get seedLibraryEditedPlant => 'Plante modifiée'; @override - String get settingsEditedAccount => 'Compte modifié avec succès'; + String get seedLibraryEditInformation => 'Modifier les informations'; @override - String get settingsFailedToEditAccount => - 'Échec de la modification du compte'; + String get seedLibraryEditingError => 'Erreur lors de la modification'; @override - String get settingsChooseLanguage => 'Choix de la langue'; + String get seedLibraryEditSpecies => 'Modifier l\'espèce'; @override - String settingsNotificationCounter(int active, int total) { - String _temp0 = intl.Intl.pluralLogic( - active, - locale: localeName, - other: 'activées', - one: 'activée', - zero: 'activée', - ); - return '$active/$total $_temp0'; - } + String get seedLibraryEmptyDifficultyError => + 'Veuillez choisir une difficulté'; @override - String get settingsEvent => 'Événement'; + String get seedLibraryEmptyFieldError => 'Veuillez remplir tous les champs'; @override - String get settingsIcal => 'Lien Ical'; + String get seedLibraryEmptyTypeError => 'Veuillez choisir un type de plante'; @override - String get settingsSynncWithCalendar => 'Synchroniser avec votre calendrier'; + String get seedLibraryEndMonth => 'Mois de fin :'; @override - String get settingsIcalLinkCopied => 'Lien Ical copié dans le presse-papier'; + String get seedLibraryFacebookUrl => 'Lien Facebook'; @override - String get settingsProfile => 'Profil'; + String get seedLibraryFilters => 'Filtres'; @override - String get settingsConnexion => 'Connexion'; + String get seedLibraryForum => + 'Oskour maman j\'ai tué ma plante - Forum d\'aide'; @override - String get settingsDisconnect => 'Se déconnecter'; + String get seedLibraryForumUrl => 'Lien Forum'; @override - String get settingsDisconnectDescription => - 'Êtes-vous sûr de vouloir vous déconnecter ?'; + String get seedLibraryHelpSheets => 'Fiches sur les plantes'; @override - String get settingsDisconnectionSuccess => 'Déconnexion réussie'; + String get seedLibraryInformation => 'Informations :'; @override - String get settingsDeleteMyAccount => 'Supprimer mon compte'; + String get seedLibraryMaturationTime => 'Temps de maturation'; @override - String get settingsDeleteMyAccountDescription => - 'Cette action notifie l\'administrateur que vous souhaitez supprimer votre compte.'; + String get seedLibraryMonthJan => 'Janvier'; @override - String get settingsDeletionAsked => - 'Demande de suppression de compte envoyée'; + String get seedLibraryMonthFeb => 'Février'; @override - String get settingsDeleteMyAccountError => - 'Erreur lors de la demande de suppression de compte'; + String get seedLibraryMonthMar => 'Mars'; @override - String get voteAdd => 'Ajouter'; + String get seedLibraryMonthApr => 'Avril'; @override - String get voteAddMember => 'Ajouter un membre'; + String get seedLibraryMonthMay => 'Mai'; @override - String get voteAddedPretendance => 'Liste ajoutée'; + String get seedLibraryMonthJun => 'Juin'; @override - String get voteAddedSection => 'Section ajoutée'; + String get seedLibraryMonthJul => 'Juillet'; @override - String get voteAddingError => 'Erreur lors de l\'ajout'; + String get seedLibraryMonthAug => 'Août'; @override - String get voteAddPretendance => 'Ajouter une liste'; + String get seedLibraryMonthSep => 'Septembre'; @override - String get voteAddSection => 'Ajouter une section'; + String get seedLibraryMonthOct => 'Octobre'; @override - String get voteAll => 'Tous'; + String get seedLibraryMonthNov => 'Novembre'; @override - String get voteAlreadyAddedMember => 'Membre déjà ajouté'; + String get seedLibraryMonthDec => 'Décembre'; @override - String get voteAlreadyVoted => 'Vote enregistré'; + String get seedLibraryMyPlants => 'Mes plantes'; @override - String get voteChooseList => 'Choisir une liste'; + String get seedLibraryName => 'Nom'; @override - String get voteClear => 'Réinitialiser'; + String get seedLibraryNbSeedsRecommended => 'Nombre de graines recommandées'; @override - String get voteClearVotes => 'Réinitialiser les votes'; + String get seedLibraryNbSeedsRecommendedError => + 'Veuillez entrer un nombre de graines recommandé supérieur à 0'; @override - String get voteClosedVote => 'Votes clos'; + String get seedLibraryNoDateError => 'Veuillez entrer une date'; @override - String get voteCloseVote => 'Fermer les votes'; + String get seedLibraryNoFilteredPlants => + 'Aucune plante ne correspond à votre recherche. Essayez d\'autres filtres.'; @override - String get voteConfirmVote => 'Confirmer le vote'; + String get seedLibraryNoMorePlant => 'Aucune plante n\'est disponible'; @override - String get voteCountVote => 'Dépouiller les votes'; + String get seedLibraryNoPersonalPlants => + 'Vous n\'avez pas encore de plantes dans votre grainothèque. Vous pouvez en ajouter en allant dans les stocks.'; @override - String get voteDeletedAll => 'Tout supprimé'; + String get seedLibraryNoSpecies => 'Aucune espèce trouvée'; @override - String get voteDeletedPipo => 'Listes pipos supprimées'; + String get seedLibraryNoStockPlants => + 'Aucune plante disponible dans le stock'; @override - String get voteDeletedSection => 'Section supprimée'; + String get seedLibraryNotes => 'Notes'; @override - String get voteDeleteAll => 'Supprimer tout'; + String get seedLibraryOk => 'OK'; @override - String get voteDeleteAllDescription => - 'Voulez-vous vraiment supprimer tout ?'; + String get seedLibraryPlantationPeriod => 'Période de plantation :'; @override - String get voteDeletePipo => 'Supprimer les listes pipos'; + String get seedLibraryPlantationType => 'Type de plantation :'; @override - String get voteDeletePipoDescription => - 'Voulez-vous vraiment supprimer les listes pipos ?'; + String get seedLibraryPlantDetail => 'Détail de la plante'; @override - String get voteDeletePretendance => 'Supprimer la liste'; + String get seedLibraryPlantingDate => 'Date de plantation'; @override - String get voteDeletePretendanceDesc => - 'Voulez-vous vraiment supprimer cette liste ?'; + String get seedLibraryPlantingNow => 'Je la plante maintenant'; @override - String get voteDeleteSection => 'Supprimer la section'; + String get seedLibraryPrefix => 'Préfixe'; @override - String get voteDeleteSectionDescription => - 'Voulez-vous vraiment supprimer cette section ?'; + String get seedLibraryPrefixError => 'Prefixe déjà utilisé'; @override - String get voteDeletingError => 'Erreur lors de la suppression'; + String get seedLibraryPrefixLengthError => + 'Le préfixe doit faire 3 caractères'; @override - String get voteDescription => 'Description'; + String get seedLibraryPropagationMethod => 'Méthode de propagation :'; @override - String get voteEdit => 'Modifier'; + String get seedLibraryReference => 'Référence :'; @override - String get voteEditedPretendance => 'Liste modifiée'; + String get seedLibraryRemovedPlant => 'Plante supprimée'; @override - String get voteEditedSection => 'Section modifiée'; + String get seedLibraryRemovingError => 'Erreur lors de la suppression'; @override - String get voteEditingError => 'Erreur lors de la modification'; + String get seedLibraryResearch => 'Recherche'; @override - String get voteErrorClosingVotes => 'Erreur lors de la fermeture des votes'; + String get seedLibrarySaveChanges => 'Sauvegarder les modifications'; @override - String get voteErrorCountingVotes => 'Erreur lors du dépouillement des votes'; + String get seedLibrarySeason => 'Saison :'; @override - String get voteErrorResetingVotes => - 'Erreur lors de la réinitialisation des votes'; + String get seedLibrarySeed => 'Graine'; @override - String get voteErrorOpeningVotes => 'Erreur lors de l\'ouverture des votes'; + String get seedLibrarySeeds => 'graines'; @override - String get voteIncorrectOrMissingFields => 'Champs incorrects ou manquants'; + String get seedLibrarySeedDeposit => 'Dépôt de plantes'; @override - String get voteMembers => 'Membres'; + String get seedLibrarySeedLibrary => 'Grainothèque'; @override - String get voteName => 'Nom'; + String get seedLibrarySeedQuantitySimple => 'Quantité de graines'; @override - String get voteNoPretendanceList => 'Aucune liste de prétendance'; + String get seedLibrarySeedQuantity => 'Quantité de graines :'; @override - String get voteNoSection => 'Aucune section'; + String get seedLibraryShowDeadPlants => 'Afficher les plantes mortes'; @override - String get voteCanNotVote => 'Vous ne pouvez pas voter'; + String get seedLibrarySpecies => 'Espèce :'; @override - String get voteNoSectionList => 'Aucune section'; + String get seedLibrarySpeciesHelp => 'Aide sur l\'espèce'; @override - String get voteNotOpenedVote => 'Vote non ouvert'; + String get seedLibrarySpeciesPlural => 'Espèces'; @override - String get voteOnGoingCount => 'Dépouillement en cours'; + String get seedLibrarySpeciesSimple => 'Espèce'; @override - String get voteOpenVote => 'Ouvrir les votes'; + String get seedLibrarySpeciesType => 'Type d\'espèce :'; @override - String get votePipo => 'Pipo'; + String get seedLibrarySpring => 'Printemps'; @override - String get votePretendance => 'Listes'; + String get seedLibraryStartMonth => 'Mois de début :'; @override - String get votePretendanceDeleted => 'Prétendance supprimée'; + String get seedLibraryStock => 'Stock disponible'; @override - String get votePretendanceNotDeleted => 'Erreur lors de la suppression'; + String get seedLibrarySummer => 'Été'; @override - String get voteProgram => 'Programme'; + String get seedLibraryStocks => 'Stocks'; @override - String get votePublish => 'Publier'; + String get seedLibraryTimeUntilMaturation => 'Temps avant maturation :'; @override - String get votePublishVoteDescription => - 'Voulez-vous vraiment publier les votes ?'; + String get seedLibraryType => 'Type :'; @override - String get voteResetedVotes => 'Votes réinitialisés'; + String get seedLibraryUnableToOpen => 'Impossible d\'ouvrir le lien'; @override - String get voteResetVote => 'Réinitialiser les votes'; + String get seedLibraryUpdate => 'Modifier'; @override - String get voteResetVoteDescription => 'Que voulez-vous faire ?'; + String get seedLibraryUpdatedInformation => 'Informations modifiées'; @override - String get voteRole => 'Rôle'; + String get seedLibraryUpdatedSpecies => 'Espèce modifiée'; @override - String get voteSectionDescription => 'Description de la section'; + String get seedLibraryUpdatedPlant => 'Plante modifiée'; @override - String get voteSection => 'Section'; + String get seedLibraryUpdatingError => 'Erreur lors de la modification'; @override - String get voteSectionName => 'Nom de la section'; + String get seedLibraryWinter => 'Hiver'; @override - String get voteSeeMore => 'Voir plus'; + String get seedLibraryWriteReference => + 'Veuillez écrire la référence suivante : '; @override - String get voteSelected => 'Sélectionné'; + String get settingsAccount => 'Compte'; @override - String get voteShowVotes => 'Voir les votes'; + String get settingsAddProfilePicture => 'Ajouter une photo'; @override - String get voteVote => 'Vote'; + String get settingsAdmin => 'Administrateur'; @override - String get voteVoteError => 'Erreur lors de l\'enregistrement du vote'; + String get settingsAskHelp => 'Demander de l\'aide'; @override - String get voteVoteFor => 'Voter pour '; + String get settingsAssociation => 'Association'; @override - String get voteVoteNotStarted => 'Vote non ouvert'; + String get settingsBirthday => 'Date de naissance'; @override - String get voteVoters => 'Groupes votants'; + String get settingsBugs => 'Bugs'; @override - String get voteVoteSuccess => 'Vote enregistré'; + String get settingsChangePassword => 'Changer de mot de passe'; @override - String get voteVotes => 'Voix'; + String get settingsChangingPassword => + 'Voulez-vous vraiment changer votre mot de passe ?'; @override - String get voteVotesClosed => 'Votes clos'; + String get settingsConfirmPassword => 'Confirmer le mot de passe'; @override - String get voteVotesCounted => 'Votes dépouillés'; + String get settingsCopied => 'Copié !'; @override - String get voteVotesOpened => 'Votes ouverts'; + String get settingsDarkMode => 'Mode sombre'; @override - String get voteWarning => 'Attention'; + String get settingsDarkModeOff => 'Désactivé'; @override - String get voteWarningMessage => - 'La sélection ne sera pas sauvegardée.\nVoulez-vous continuer ?'; + String get settingsDeleteLogs => 'Supprimer les logs ?'; @override - String get moduleAdvert => 'Annonce'; + String get settingsDeleteNotificationLogs => + 'Supprimer les logs des notifications ?'; @override - String get moduleAdvertDescription => 'Gérer les annonces'; + String get settingsDetelePersonalData => 'Supprimer mes données personnelles'; @override - String get moduleAmap => 'AMAP'; + String get settingsDetelePersonalDataDesc => + 'Cette action notifie l\'administrateur que vous souhaitez supprimer vos données personnelles.'; @override - String get moduleAmapDescription => 'Gérer les livraisons et les produits'; + String get settingsDeleting => 'Suppresion'; @override - String get moduleBooking => 'Réservation'; + String get settingsEdit => 'Modifier'; @override - String get moduleBookingDescription => - 'Gérer les réservations, les salles et les managers'; + String get settingsEditAccount => 'Modifier mon profil'; @override - String get moduleCalendar => 'Calendrier'; + String get settingsEmail => 'Email'; @override - String get moduleCalendarDescription => - 'Consulter les événements et les activités'; + String get settingsEmptyField => 'Ce champ ne peut pas être vide'; @override - String get moduleCentralisation => 'Centralisation'; + String get settingsErrorProfilePicture => + 'Erreur lors de la modification de la photo de profil'; @override - String get moduleCentralisationDescription => - 'Gérer la centralisation des données'; + String get settingsErrorSendingDemand => + 'Erreur lors de l\'envoi de la demande'; @override - String get moduleCinema => 'Cinéma'; + String get settingsEventsIcal => 'Lien Ical des événements'; @override - String get moduleCinemaDescription => 'Gérer les séances de cinéma'; + String get settingsExpectingDate => 'Date de naissance attendue'; @override - String get moduleEvent => 'Événement'; + String get settingsFirstname => 'Prénom'; @override - String get moduleEventDescription => - 'Gérer les événements et les participants'; + String get settingsFloor => 'Étage'; @override - String get moduleFlappyBird => 'Flappy Bird'; + String get settingsHelp => 'Aide'; @override - String get moduleFlappyBirdDescription => - 'Jouer à Flappy Bird et consulter le classement'; + String get settingsIcalCopied => 'Lien Ical copié !'; @override - String get moduleLoan => 'Prêt'; + String get settingsLanguage => 'Langue'; @override - String get moduleLoanDescription => 'Gérer les prêts et les articles'; + String get settingsLanguageVar => 'Français 🇫🇷'; @override - String get modulePhonebook => 'Annuaire'; + String get settingsLogs => 'Logs'; @override - String get modulePhonebookDescription => - 'Gérer les associations, les membres et les administrateurs'; + String get settingsModules => 'Modules'; @override - String get modulePurchases => 'Achats'; + String get settingsMyIcs => 'Mon lien Ical'; @override - String get modulePurchasesDescription => - 'Gérer les achats, les tickets et l\'historique'; + String get settingsName => 'Nom'; @override - String get moduleRaffle => 'Tombola'; + String get settingsNewPassword => 'Nouveau mot de passe'; @override - String get moduleRaffleDescription => - 'Gérer les tombolas, les prix et les tickets'; + String get settingsNickname => 'Surnom'; @override - String get moduleRecommendation => 'Bons plans'; + String get settingsNotifications => 'Notifications'; @override - String get moduleRecommendationDescription => - 'Gérer les recommandations, les informations et les administrateurs'; + String get settingsOldPassword => 'Ancien mot de passe'; @override - String get moduleSeedLibrary => 'Grainothèque'; + String get settingsPasswordChanged => 'Mot de passe changé'; @override - String get moduleSeedLibraryDescription => - 'Gérer les graines, les espèces et les stocks'; + String get settingsPasswordsNotMatch => + 'Les mots de passe ne correspondent pas'; @override - String get moduleVote => 'Vote'; + String get settingsPersonalData => 'Données personnelles'; @override - String get moduleVoteDescription => - 'Gérer les votes, les sections et les candidats'; + String get settingsPersonalisation => 'Personnalisation'; @override - String get modulePh => 'PH'; + String get settingsPhone => 'Téléphone'; @override - String get modulePhDescription => - 'Gérer les PH, les formulaires et les administrateurs'; + String get settingsProfilePicture => 'Photo de profil'; @override - String get moduleSettings => 'Paramètres'; + String get settingsPromo => 'Promotion'; @override - String get moduleSettingsDescription => - 'Gérer les paramètres de l\'application'; + String get settingsRepportBug => 'Signaler un bug'; @override - String get moduleFeed => 'Feed'; + String get settingsSave => 'Enregistrer'; @override - String get moduleFeedDescription => - 'Consulter les actualités et mises à jour'; + String get settingsSecurity => 'Sécurité'; @override - String get moduleStyleGuide => 'StyleGuide'; + String get settingsSendedDemand => 'Demande envoyée'; @override - String get moduleStyleGuideDescription => - 'Explore the UI components and styles used in Titan'; + String get settingsSettings => 'Paramètres'; @override - String get moduleAdmin => 'Admin'; + String get settingsTooHeavyProfilePicture => + 'L\'image est trop lourde (max 4Mo)'; @override - String get moduleAdminDescription => - 'Gérer les utilisateurs, groupes et structures'; + String get settingsUpdatedProfile => 'Profil modifié'; @override - String get moduleOthers => 'Autres'; + String get settingsUpdatedProfilePicture => 'Photo de profil modifiée'; @override - String get moduleOthersDescription => 'Afficher les autres modules'; + String get settingsUpdateNotification => 'Mettre à jour les notifications'; @override - String get modulePayment => 'Paiement'; + String get settingsUpdatingError => + 'Erreur lors de la modification du profil'; @override - String get modulePaymentDescription => - 'Gérer les paiements, les statistiques et les appareils'; + String get settingsVersion => 'Version'; @override - String get paiementTopUp => 'Recharge'; + String get settingsPasswordStrength => 'Force du mot de passe'; @override - String get paiementStoreManagement => 'Gestion des associations'; + String get settingsPasswordStrengthVeryWeak => 'Très faible'; @override - String get paiementDeleteStore => 'Supprimer l\'association'; + String get settingsPasswordStrengthWeak => 'Faible'; @override - String get paiementDeleteStoreDescription => - 'Voulez-vous vraiment supprimer cette association ?'; + String get settingsPasswordStrengthMedium => 'Moyen'; @override - String get paiementDeleteStoreError => - 'Impossible de supprimer l\'association'; + String get settingsPasswordStrengthStrong => 'Fort'; @override - String get paiementStoreDeleted => 'Association supprimée'; + String get settingsPasswordStrengthVeryStrong => 'Très fort'; @override - String get paiementAddThisDevice => 'Ajouter cet appareil'; + String get settingsPhoneNumber => 'Numéro de téléphone'; @override - String get paiementThisDevice => '(cet appareil)'; + String get settingsValidate => 'Valider'; @override - String get paiementCancelled => 'Annulé'; + String get settingsEditedAccount => 'Compte modifié avec succès'; @override - String get paiementThe => 'Le'; + String get settingsFailedToEditAccount => + 'Échec de la modification du compte'; @override - String get paiementOf => 'de'; + String get settingsChooseLanguage => 'Choix de la langue'; @override - String get paiementRefundedThe => 'Remboursé le'; + String settingsNotificationCounter(int active, int total) { + String _temp0 = intl.Intl.pluralLogic( + active, + locale: localeName, + other: 'activées', + one: 'activée', + zero: 'activée', + ); + return '$active/$total $_temp0'; + } @override - String get paiementAt => 'à'; + String get settingsEvent => 'Événement'; @override - String get paiementPleaseAcceptTOS => - 'Veuillez accepter les Conditions Générales d\'Utilisation.'; + String get settingsIcal => 'Lien Ical'; @override - String get paiementAskDeviceActivation => - 'Demande d\'activation de l\'appareil'; + String get settingsSynncWithCalendar => 'Synchroniser avec votre calendrier'; @override - String get paiementDeviceActivationReceived => - 'La demande d\'activation est prise en compte, veuilliez consulter votre boite mail pour finaliser la démarche'; + String get settingsIcalLinkCopied => 'Lien Ical copié dans le presse-papier'; @override - String get paiementRevokeDevice => 'Révoquer l\'appareil ?'; + String get settingsProfile => 'Profil'; @override - String get paiementRevokeDeviceDescription => - 'Vous ne pourrez plus utiliser cet appareil pour les paiements'; + String get settingsConnexion => 'Connexion'; @override - String get paiementDeviceRevoked => 'Appareil révoqué'; + String get settingsDisconnect => 'Se déconnecter'; @override - String get paiementDeviceRevokingError => - 'Erreur lors de la révocation de l\'appareil'; + String get settingsDisconnectDescription => + 'Êtes-vous sûr de vouloir vous déconnecter ?'; @override - String get paiementPleaseAcceptPopup => 'Veuillez autoriser les popups'; + String get settingsDisconnectionSuccess => 'Déconnexion réussie'; @override - String get paiementProceedSuccessfully => 'Paiement effectué avec succès'; + String get settingsDeleteMyAccount => 'Supprimer mon compte'; @override - String get paiementCancelledTransaction => 'Paiement annulé'; + String get settingsDeleteMyAccountDescription => + 'Cette action notifie l\'administrateur que vous souhaitez supprimer votre compte.'; @override - String get paiementPleaseEnterMinAmount => - 'Veuillez entrer un montant supérieur à 1'; + String get settingsDeletionAsked => + 'Demande de suppression de compte envoyée'; @override - String get paiementMaxAmount => - 'Le montant maximum de votre portefeuille est de'; + String get settingsDeleteMyAccountError => + 'Erreur lors de la demande de suppression de compte'; @override - String get paiementPayWithHA => 'Payer avec HelloAsso'; + String get voteAdd => 'Ajouter'; @override - String get paiementBalanceAfterTopUp => 'Solde après recharge :'; + String get voteAddMember => 'Ajouter un membre'; @override - String get paiementPersonalBalance => 'Solde personnel'; + String get voteAddedPretendance => 'Liste ajoutée'; @override - String get paiementDevices => 'Appareils'; + String get voteAddedSection => 'Section ajoutée'; @override - String get paiementPay => 'Payer'; + String get voteAddingError => 'Erreur lors de l\'ajout'; @override - String get paiementDeviceNotRegistered => 'Appareil non enregistré'; + String get voteAddPretendance => 'Ajouter une liste'; @override - String get paiementDeviceNotRegisteredDescription => - 'Votre appareil n\'est pas encore enregistré. \nPour l\'enregistrer, veuillez vous rendre sur la page des appareils.'; + String get voteAddSection => 'Ajouter une section'; @override - String get paiementAccessPage => 'Accéder à la page'; + String get voteAll => 'Tous'; @override - String get paiementDeviceNotActivated => 'Appareil non activé'; + String get voteAlreadyAddedMember => 'Membre déjà ajouté'; @override - String get paiementDeviceNotActivatedDescription => - 'Votre appareil n\'est pas encore activé. \nPour l\'activer, veuillez vous rendre sur la page des appareils.'; + String get voteAlreadyVoted => 'Vote enregistré'; @override - String get paiementReactivateRevokedDeviceDescription => - 'Votre appareil a été révoqué. \nPour le réactiver, veuillez vous rendre sur la page des appareils.'; + String get voteChooseList => 'Choisir une liste'; @override - String get paiementDeviceRecoveryError => - 'Erreur lors de la récupération de l\'appareil'; + String get voteClear => 'Réinitialiser'; @override - String get paiementStats => 'Stats'; + String get voteClearVotes => 'Réinitialiser les votes'; @override - String get paimentTopUpAction => 'Recharger'; + String get voteClosedVote => 'Votes clos'; @override - String get paiementGetBalanceError => - 'Erreur lors de la récupération du solde : '; + String get voteCloseVote => 'Fermer les votes'; @override - String get paiementLastTransactions => 'Dernières transactions'; + String get voteConfirmVote => 'Confirmer le vote'; @override - String get paiementGetTransactionsError => - 'Erreur lors de la récupération des transactions : '; + String get voteCountVote => 'Dépouiller les votes'; @override - String get paiementStoreBalance => 'Solde associatif'; + String get voteDeletedAll => 'Tout supprimé'; @override - String get paiementScan => 'Scanner'; + String get voteDeletedPipo => 'Listes pipos supprimées'; @override - String get paiementManagement => 'Gestion'; + String get voteDeletedSection => 'Section supprimée'; @override - String get paiementHistory => 'Historique'; + String get voteDeleteAll => 'Supprimer tout'; @override - String get paiementHandOver => 'Passation'; + String get voteDeleteAllDescription => + 'Voulez-vous vraiment supprimer tout ?'; @override - String get paiementStores => 'Associations'; + String get voteDeletePipo => 'Supprimer les listes pipos'; @override - String get paiementAdmin => 'Administrateur'; + String get voteDeletePipoDescription => + 'Voulez-vous vraiment supprimer les listes pipos ?'; @override - String get paiementSuccededTransaction => 'Paiement réussi'; + String get voteDeletePretendance => 'Supprimer la liste'; @override - String get paiementNewCGU => 'Nouvelles Conditions Générales d\'Utilisation'; + String get voteDeletePretendanceDesc => + 'Voulez-vous vraiment supprimer cette liste ?'; @override - String get paiementDecline => 'Refuser'; + String get voteDeleteSection => 'Supprimer la section'; @override - String get paiementAccept => 'Accepter'; + String get voteDeleteSectionDescription => + 'Voulez-vous vraiment supprimer cette section ?'; @override - String get paiementAmount => 'Montant'; + String get voteDeletingError => 'Erreur lors de la suppression'; @override - String get paiementValidUntil => 'Valide jusqu\'à'; + String get voteDescription => 'Description'; @override - String get paiementClose => 'Fermer'; + String get voteEdit => 'Modifier'; @override - String get paiementPleaseEnterValidAmount => - 'Veuillez entrer un montant valide'; + String get voteEditedPretendance => 'Liste modifiée'; @override - String get paiementPleaseAuthenticate => 'Veuillez vous authentifier'; + String get voteEditedSection => 'Section modifiée'; @override - String get paiementAthenticationRequired => - 'Authentification requise pour payer'; + String get voteEditingError => 'Erreur lors de la modification'; @override - String get paiementNoThanks => 'Non merci'; + String get voteErrorClosingVotes => 'Erreur lors de la fermeture des votes'; @override - String get paiementAuthentificationFailed => 'Échec de l\'authentification'; + String get voteErrorCountingVotes => 'Erreur lors du dépouillement des votes'; @override - String get paiementPleaseAddDevice => - 'Veuillez ajouter cet appareil pour payer'; + String get voteErrorResetingVotes => + 'Erreur lors de la réinitialisation des votes'; @override - String get paiementPayment => 'Paiement'; + String get voteErrorOpeningVotes => 'Erreur lors de l\'ouverture des votes'; @override - String get paiementBalanceAfterTransaction => 'Solde après paiement : '; + String get voteIncorrectOrMissingFields => 'Champs incorrects ou manquants'; @override - String get paiementCancel => 'Annuler'; + String get voteMembers => 'Membres'; @override - String get paiementLimitedTo => 'Limité à'; + String get voteName => 'Nom'; @override - String get paiementScanCode => 'Scanner un code'; + String get voteNoPretendanceList => 'Aucune liste de prétendance'; @override - String get paiementNext => 'Suivant'; + String get voteNoSection => 'Aucune section'; @override - String get paiementCancelTransaction => 'Annuler la transaction'; + String get voteCanNotVote => 'Vous ne pouvez pas voter'; @override - String get paiementTransactionCancelled => 'Transaction annulée'; + String get voteNoSectionList => 'Aucune section'; @override - String get paiementTransactionCancelledDescription => - 'Voulez-vous vraiment annuler la transaction de'; + String get voteNotOpenedVote => 'Vote non ouvert'; @override - String get paiementTransactionCancelledError => - 'Erreur lors de l\'annulation de la transaction'; + String get voteOnGoingCount => 'Dépouillement en cours'; @override - String get paiementNoMembership => 'Aucune adhésion'; + String get voteOpenVote => 'Ouvrir les votes'; @override - String get paiementNoMembershipDescription => - 'Ce produit n\'est pas disponnible pour les non-adhérents. Confirmer l\'encaissement ?'; + String get votePipo => 'Pipo'; @override - String get paiementQRCodeAlreadyUsed => 'QR Code déjà utilisé'; + String get votePretendance => 'Listes'; @override - String get paiementCameraPermissionRequired => - 'Permission d\'accès à la caméra requise'; + String get votePretendanceDeleted => 'Prétendance supprimée'; @override - String get paiementCameraPerssionRequiredDescription => - 'Pour scanner un QR Code, vous devez autoriser l\'accès à la caméra.'; + String get votePretendanceNotDeleted => 'Erreur lors de la suppression'; @override - String get paiementSettings => 'Paramètres'; + String get voteProgram => 'Programme'; @override - String get paiementReceived => 'Reçu'; + String get votePublish => 'Publier'; @override - String get paiementSpent => 'Déboursé'; + String get votePublishVoteDescription => + 'Voulez-vous vraiment publier les votes ?'; @override - String get paiementNoTrasactionForThisMonth => - 'Aucune transaction pour ce mois'; + String get voteResetedVotes => 'Votes réinitialisés'; @override - String get paiementNoTransactinon => 'Aucune transaction'; + String get voteResetVote => 'Réinitialiser les votes'; @override - String get paiementSellerRigths => 'Droits du vendeur'; + String get voteResetVoteDescription => 'Que voulez-vous faire ?'; @override - String get paiementCanBank => 'Peut encaisser'; + String get voteRole => 'Rôle'; @override - String get paiementCanSeeHistory => 'Peut voir l\'historique'; + String get voteSectionDescription => 'Description de la section'; @override - String get paiementCanCancelTransaction => 'Peut annuler des transactions'; + String get voteSection => 'Section'; @override - String get paiementCanManageSellers => 'Peut gérer les vendeurs'; + String get voteSectionName => 'Nom de la section'; @override - String get paiementAddedSeller => 'Vendeur ajouté'; + String get voteSeeMore => 'Voir plus'; @override - String get paiementAddingSellerError => 'Erreur lors de l\'ajout du vendeur'; + String get voteSelected => 'Sélectionné'; @override - String get paiementBank => 'Encaisser'; + String get voteShowVotes => 'Voir les votes'; @override - String get paiementSeeHistory => 'Voir l\'historique'; + String get voteVote => 'Vote'; @override - String get paiementCancelTransactions => 'Annuler les transactions'; + String get voteVoteError => 'Erreur lors de l\'enregistrement du vote'; @override - String get paiementManageSellers => 'Gérer les vendeurs'; + String get voteVoteFor => 'Voter pour '; @override - String get paiementStructureAdmin => 'Administrateur de la structure'; + String get voteVoteNotStarted => 'Vote non ouvert'; @override - String get paiementRightsOf => 'Droits de'; + String get voteVoters => 'Groupes votants'; @override - String get paiementRightsUpdated => 'Droits mis à jour'; + String get voteVoteSuccess => 'Vote enregistré'; @override - String get paiementRightsUpdateError => - 'Erreur lors de la mise à jour des droits'; + String get voteVotes => 'Voix'; @override - String get paiementDeleteSellerDescription => - 'Voulez-vous vraiment supprimer ce vendeur ?'; + String get voteVotesClosed => 'Votes clos'; @override - String get paiementDeletedSeller => 'Vendeur supprimé'; + String get voteVotesCounted => 'Votes dépouillés'; @override - String get paiementDeletingSellerError => - 'Erreur lors de la suppression du vendeur'; + String get voteVotesOpened => 'Votes ouverts'; @override - String get paiementDeleteSeller => 'Supprimer le vendeur'; + String get voteWarning => 'Attention'; @override - String get paiementAdd => 'Ajouter'; + String get voteWarningMessage => + 'La sélection ne sera pas sauvegardée.\nVoulez-vous continuer ?'; @override - String get paiementAddSeller => 'Ajouter un vendeur'; + String get moduleAdvert => 'Annonce'; @override - String get paiementSellerError => - 'Vous n\'êtes pas vendeur de cette association'; + String get moduleAdvertDescription => 'Gérer les annonces'; @override - String get paiementSellersOf => 'Les vendeurs de'; + String get moduleAmap => 'AMAP'; @override - String get paiementModify => 'Modifier'; + String get moduleAmapDescription => 'Gérer les livraisons et les produits'; @override - String get paiementAStore => 'une association'; + String get moduleBooking => 'Réservation'; @override - String get paiementStoreName => 'Nom de l\'association'; + String get moduleBookingDescription => + 'Gérer les réservations, les salles et les managers'; @override - String get paiementSuccessfullyAddedStore => - 'Association ajoutée avec succès'; + String get moduleCalendar => 'Calendrier'; @override - String get paiementSuccessfullyModifiedStore => - 'Association modifiée avec succès'; + String get moduleCalendarDescription => + 'Consulter les événements et les activités'; @override - String get paiementAddingStoreError => - 'Erreur lors de l\'ajout de l\'association'; + String get moduleCentralisation => 'Centralisation'; @override - String get paiementModifyingStoreError => - 'Erreur lors de la modification de l\'association'; + String get moduleCentralisationDescription => + 'Gérer la centralisation des données'; @override - String get paiementRefund => 'Remboursement'; + String get moduleCinema => 'Cinéma'; @override - String get paiementDoneTransaction => 'Transaction effectuée'; + String get moduleCinemaDescription => 'Gérer les séances de cinéma'; @override - String get paiementRefundAction => 'Rembourser'; + String get moduleEvent => 'Événement'; @override - String get paiementTotalDuringPeriod => 'Total sur la période'; + String get moduleEventDescription => + 'Gérer les événements et les participants'; @override - String get paiementMean => 'Moyenne : '; + String get moduleFlappyBird => 'Flappy Bird'; @override - String get paiementTransaction => 'ransaction'; + String get moduleFlappyBirdDescription => + 'Jouer à Flappy Bird et consulter le classement'; @override - String get paiementTransferStructure => 'Transfert de structure'; + String get moduleLoan => 'Prêt'; @override - String get paiementYouAreTransferingStructureTo => - 'Vous êtes sur le point de transférer la structure à '; + String get moduleLoanDescription => 'Gérer les prêts et les articles'; @override - String get paiementTransferStructureDescription => - 'Le nouveau responsable aura accès à toutes les fonctionnalités de gestion de la structure. Vous allez recevoir un email pour valider ce transfert. Le lien ne sera actif que pendant 20 minutes. Cette action est irréversible. Êtes-vous sûr de vouloir continuer ?'; + String get modulePhonebook => 'Annuaire'; @override - String get paiementTransferStructureError => - 'Erreur lors du transfert de la structure'; + String get modulePhonebookDescription => + 'Gérer les associations, les membres et les administrateurs'; @override - String get paiementTransferStructureSuccess => - 'Transfert de structure demandé avec succès'; + String get modulePurchases => 'Achats'; @override - String get paiementNextAccountable => 'Prochain responsable'; + String get modulePurchasesDescription => + 'Gérer les achats, les tickets et l\'historique'; + + @override + String get moduleRaffle => 'Tombola'; + + @override + String get moduleRaffleDescription => + 'Gérer les tombolas, les prix et les tickets'; + + @override + String get moduleRecommendation => 'Bons plans'; + + @override + String get moduleRecommendationDescription => + 'Gérer les recommandations, les informations et les administrateurs'; + + @override + String get moduleSeedLibrary => 'Grainothèque'; + + @override + String get moduleSeedLibraryDescription => + 'Gérer les graines, les espèces et les stocks'; + + @override + String get moduleVote => 'Vote'; + + @override + String get moduleVoteDescription => + 'Gérer les votes, les sections et les candidats'; + + @override + String get modulePh => 'PH'; + + @override + String get modulePhDescription => + 'Gérer les PH, les formulaires et les administrateurs'; + + @override + String get moduleSettings => 'Paramètres'; + + @override + String get moduleSettingsDescription => + 'Gérer les paramètres de l\'application'; + + @override + String get moduleFeed => 'Feed'; + + @override + String get moduleFeedDescription => + 'Consulter les actualités et mises à jour'; + + @override + String get moduleStyleGuide => 'StyleGuide'; + + @override + String get moduleStyleGuideDescription => + 'Explore the UI components and styles used in Titan'; + + @override + String get moduleAdmin => 'Admin'; + + @override + String get moduleAdminDescription => + 'Gérer les utilisateurs, groupes et structures'; + + @override + String get moduleOthers => 'Autres'; + + @override + String get moduleOthersDescription => 'Afficher les autres modules'; + + @override + String get modulePayment => 'Paiement'; + + @override + String get modulePaymentDescription => + 'Gérer les paiements, les statistiques et les appareils'; } diff --git a/lib/paiement/class/bank_account_holder.dart b/lib/paiement/class/bank_account_holder.dart new file mode 100644 index 0000000000..ebcebbac3a --- /dev/null +++ b/lib/paiement/class/bank_account_holder.dart @@ -0,0 +1,32 @@ +import 'package:titan/paiement/class/structure.dart'; + +class BankAccountHolder { + String holderStructureId; + Structure holderStructure; + + BankAccountHolder({ + required this.holderStructureId, + required this.holderStructure, + }); + + factory BankAccountHolder.fromJson(Map json) { + return BankAccountHolder( + holderStructureId: json['holder_structure_id'], + holderStructure: Structure.fromJson(json['holder_structure']), + ); + } + + Map toJson() { + return { + 'holder_structure_id': holderStructureId, + 'holder_structure': holderStructure.toJson(), + }; + } + + static BankAccountHolder empty() { + return BankAccountHolder( + holderStructureId: '', + holderStructure: Structure.empty(), + ); + } +} diff --git a/lib/paiement/class/invoice.dart b/lib/paiement/class/invoice.dart new file mode 100644 index 0000000000..14806ff274 --- /dev/null +++ b/lib/paiement/class/invoice.dart @@ -0,0 +1,133 @@ +import 'package:titan/paiement/class/store.dart'; +import 'package:titan/paiement/class/structure.dart'; +import 'package:titan/tools/functions.dart'; + +class InvoiceDetail { + final int total; + final StoreSimple store; + + InvoiceDetail({required this.total, required this.store}); + + factory InvoiceDetail.fromJson(Map json) { + return InvoiceDetail( + total: json['total'], + store: StoreSimple.fromJson(json['store']), + ); + } + + Map toJson() { + return {'total': total, 'store': store.toJson()}; + } + + @override + String toString() { + return 'InvoiceDetail {total: $total, store: $store}'; + } +} + +class Invoice { + final String id; + final String reference; + final Structure structure; + final DateTime creation; + final DateTime startDate; + final DateTime endDate; + final int total; + final List details; + final bool paid; + final bool received; + + Invoice({ + required this.id, + required this.reference, + required this.structure, + required this.creation, + required this.startDate, + required this.endDate, + required this.total, + required this.details, + required this.paid, + required this.received, + }); + + Invoice.fromJson(Map json) + : id = json['id'], + reference = json['reference'], + structure = Structure.fromJson(json['structure']), + creation = processDateFromAPI(json['creation']), + startDate = processDateFromAPI(json['start_date']), + endDate = processDateFromAPI(json['end_date']), + total = json['total'], + details = List.from( + json['details'].map((item) => InvoiceDetail.fromJson(item)), + ), + paid = json['paid'], + received = json['received']; + + Map toJson() { + return { + 'id': id, + 'reference': reference, + 'structure': structure.toJson(), + 'creation': processDateToAPI(creation), + 'start_date': processDateToAPI(startDate), + 'end_date': processDateToAPI(endDate), + 'total': total, + 'detail': details.map((item) => item.toJson()).toList(), + 'paid': paid, + 'received': received, + }; + } + + Invoice.empty() + : id = '', + reference = '', + structure = Structure.empty(), + creation = DateTime.now(), + startDate = DateTime.now(), + endDate = DateTime.now(), + total = 0, + details = [], + paid = false, + received = false; + + Invoice copyWith({ + String? id, + String? reference, + Structure? structure, + DateTime? creation, + DateTime? startDate, + DateTime? endDate, + int? total, + List? details, + bool? paid, + bool? received, + }) { + return Invoice( + id: id ?? this.id, + reference: reference ?? this.reference, + structure: structure ?? this.structure, + creation: creation ?? this.creation, + startDate: startDate ?? this.startDate, + endDate: endDate ?? this.endDate, + total: total ?? this.total, + details: details ?? this.details, + paid: paid ?? this.paid, + received: received ?? this.received, + ); + } + + @override + String toString() { + return 'Invoice {id: $id,\n' + 'reference: $reference,\n' + 'structure: $structure,\n' + 'creation: $creation,\n' + 'startDate: $startDate,\n' + 'endDate: $endDate,\n' + 'total: $total,\n' + 'detail: $details,\n' + 'paid: $paid,\n' + 'received: $received}'; + } +} diff --git a/lib/paiement/class/store.dart b/lib/paiement/class/store.dart index 94823fa936..f67e86768c 100644 --- a/lib/paiement/class/store.dart +++ b/lib/paiement/class/store.dart @@ -1,15 +1,37 @@ import 'package:titan/paiement/class/structure.dart'; -class Store { +class StoreSimple { final String id; final String name; final String walletId; + + StoreSimple({required this.id, required this.name, required this.walletId}); + + factory StoreSimple.fromJson(Map json) { + return StoreSimple( + id: json['id'], + name: json['name'], + walletId: json['wallet_id'], + ); + } + + Map toJson() { + return {'id': id, 'name': name, 'wallet_id': walletId}; + } + + @override + String toString() { + return 'StoreSimple(id: $id, name: $name, walletId: $walletId)'; + } +} + +class Store extends StoreSimple { final Structure structure; Store({ - required this.id, - required this.name, - required this.walletId, + required super.id, + required super.name, + required super.walletId, required this.structure, }); @@ -22,6 +44,7 @@ class Store { ); } + @override Map toJson() { return { 'id': id, diff --git a/lib/paiement/class/structure.dart b/lib/paiement/class/structure.dart index 9c1c3eefa2..d73050afe7 100644 --- a/lib/paiement/class/structure.dart +++ b/lib/paiement/class/structure.dart @@ -2,34 +2,66 @@ import 'package:titan/admin/class/association_membership_simple.dart'; import 'package:titan/user/class/simple_users.dart'; class Structure { + final String id; final String name; final AssociationMembership associationMembership; - final String id; final SimpleUser managerUser; + final String shortId; + final String siegeAddressStreet; + final String siegeAddressCity; + final String siegeAddressZipcode; + final String siegeAddressCountry; + final String? siret; + final String iban; + final String bic; Structure({ + required this.id, required this.name, required this.associationMembership, - required this.id, required this.managerUser, + required this.shortId, + required this.siegeAddressStreet, + required this.siegeAddressCity, + required this.siegeAddressZipcode, + required this.siegeAddressCountry, + this.siret, + required this.iban, + required this.bic, }); factory Structure.fromJson(Map json) { return Structure( + id: json['id'], + shortId: json['short_id'], name: json['name'], + siegeAddressStreet: json['siege_address_street'], + siegeAddressCity: json['siege_address_city'], + siegeAddressZipcode: json['siege_address_zipcode'], + siegeAddressCountry: json['siege_address_country'], + siret: json['siret'], + iban: json['iban'], + bic: json['bic'], associationMembership: json['association_membership'] != null ? AssociationMembership.fromJson(json['association_membership']) : AssociationMembership.empty(), - id: json['id'], managerUser: SimpleUser.fromJson(json['manager_user']), ); } Map toJson() { return { - 'name': name, 'id': id, + 'name': name, + 'short_id': shortId, 'manager_user_id': managerUser.id, + 'siege_address_street': siegeAddressStreet, + 'siege_address_city': siegeAddressCity, + 'siege_address_zipcode': siegeAddressZipcode, + 'siege_address_country': siegeAddressCountry, + 'siret': siret, + 'iban': iban, + 'bic': bic, 'association_membership_id': associationMembership.id != '' ? associationMembership.id : null, @@ -38,29 +70,64 @@ class Structure { @override String toString() { - return 'Structure{name: $name, associationMembership: $associationMembership, id: $id, managerUserId: $managerUser}'; + return 'Structure{id: $id\n' + 'name: $name\n' + 'shortId: $shortId\n' + 'siegeAddressStreet: $siegeAddressStreet\n' + 'siegeAddressCity: $siegeAddressCity\n' + 'siegeAddressZipcode: $siegeAddressZipcode\n' + 'siegeAddressCountry: $siegeAddressCountry\n' + 'siret: $siret\n' + 'iban: $iban\n' + 'bic: $bic\n' + 'associationMembership: $associationMembership\n' + 'managerUser: $managerUser}'; } Structure copyWith({ + String? id, + String? shortId, String? name, AssociationMembership? associationMembership, - String? id, SimpleUser? managerUser, + String? siegeAddressStreet, + String? siegeAddressCity, + String? siegeAddressZipcode, + String? siegeAddressCountry, + String? siret, + String? iban, + String? bic, }) { return Structure( + id: id ?? this.id, + shortId: shortId ?? this.shortId, name: name ?? this.name, + siegeAddressStreet: siegeAddressStreet ?? this.siegeAddressStreet, + siegeAddressCity: siegeAddressCity ?? this.siegeAddressCity, + siegeAddressZipcode: siegeAddressZipcode ?? this.siegeAddressZipcode, + siegeAddressCountry: siegeAddressCountry ?? this.siegeAddressCountry, + siret: siret ?? this.siret, + iban: iban ?? this.iban, + bic: bic ?? this.bic, associationMembership: associationMembership ?? this.associationMembership, - id: id ?? this.id, managerUser: managerUser ?? this.managerUser, ); } Structure.empty() : this( + id: '', + shortId: '', name: '', + siegeAddressStreet: '', + siegeAddressCity: '', + siegeAddressZipcode: '', + siegeAddressCountry: '', + siret: null, + iban: '', + bic: '', associationMembership: AssociationMembership.empty(), - id: '', managerUser: SimpleUser.empty(), ); } diff --git a/lib/paiement/providers/bank_account_holder_provider.dart b/lib/paiement/providers/bank_account_holder_provider.dart new file mode 100644 index 0000000000..53b4f4cc80 --- /dev/null +++ b/lib/paiement/providers/bank_account_holder_provider.dart @@ -0,0 +1,34 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/paiement/class/bank_account_holder.dart'; +import 'package:titan/paiement/repositories/bank_account_holder_repository.dart'; +import 'package:titan/tools/providers/single_notifier.dart'; + +class BankAccountHolderNotifier extends SingleNotifier { + final BankAccountHolderRepository bankAccountHolderRepository; + BankAccountHolderNotifier({required this.bankAccountHolderRepository}) + : super(const AsyncValue.loading()); + + Future> getBankAccountHolder() async { + return await load(bankAccountHolderRepository.getBankAccountHolder); + } + + Future updateBankAccountHolder(String structureId) async { + return await add( + (_) => bankAccountHolderRepository.updateBankAccountHolder(structureId), + BankAccountHolder.empty(), + ); + } +} + +final bankAccountHolderProvider = + StateNotifierProvider< + BankAccountHolderNotifier, + AsyncValue + >((ref) { + final bankAccountHolderRepository = ref.watch( + bankAccountHolderRepositoryProvider, + ); + return BankAccountHolderNotifier( + bankAccountHolderRepository: bankAccountHolderRepository, + )..getBankAccountHolder(); + }); diff --git a/lib/paiement/providers/device_provider.dart b/lib/paiement/providers/device_provider.dart index 718bb3ce83..c18a8bd02c 100644 --- a/lib/paiement/providers/device_provider.dart +++ b/lib/paiement/providers/device_provider.dart @@ -4,9 +4,9 @@ import 'package:titan/paiement/class/wallet_device.dart'; import 'package:titan/paiement/repositories/devices_repository.dart'; import 'package:titan/tools/providers/single_notifier.dart'; -class DeviceListNotifier extends SingleNotifier { +class DeviceNotifier extends SingleNotifier { final DevicesRepository devicesRepository; - DeviceListNotifier({required this.devicesRepository}) + DeviceNotifier({required this.devicesRepository}) : super(const AsyncValue.loading()); Future> getDevice(String deviceId) async { @@ -26,7 +26,7 @@ class DeviceListNotifier extends SingleNotifier { } final deviceProvider = - StateNotifierProvider>((ref) { + StateNotifierProvider>((ref) { final deviceListRepository = ref.watch(devicesRepositoryProvider); - return DeviceListNotifier(devicesRepository: deviceListRepository); + return DeviceNotifier(devicesRepository: deviceListRepository); }); diff --git a/lib/paiement/providers/invoice_list_provider.dart b/lib/paiement/providers/invoice_list_provider.dart new file mode 100644 index 0000000000..391b2f1a72 --- /dev/null +++ b/lib/paiement/providers/invoice_list_provider.dart @@ -0,0 +1,90 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/paiement/class/invoice.dart'; +import 'package:titan/paiement/class/structure.dart'; +import 'package:titan/paiement/repositories/invoices_repository.dart'; +import 'package:titan/tools/providers/list_notifier.dart'; + +class InvoiceListNotifier extends ListNotifier { + final InvoiceRepository invoicesRepository; + InvoiceListNotifier({required this.invoicesRepository}) + : super(const AsyncValue.loading()); + + Future>> getInvoices({ + int page = 1, + int pageLimit = 20, + List? structuresIds, + DateTime? startDate, + DateTime? endDate, + }) async { + return await loadList( + () async => invoicesRepository.getInvoices( + page, + pageLimit, + structuresIds, + startDate, + endDate, + ), + ); + } + + Future>> getStructureInvoices( + String structuresId, { + int page = 1, + int pageLimit = 20, + DateTime? startDate, + DateTime? endDate, + }) async { + return await loadList( + () async => invoicesRepository.getStructureInvoices( + structuresId, + page, + pageLimit, + startDate, + endDate, + ), + ); + } + + Future createInvoice(Structure structure) async { + return await add( + (_) => invoicesRepository.createInvoice(structure.id), + Invoice.empty(), + ); + } + + Future updateInvoicePaidStatus(Invoice invoice, bool paid) async { + return await update( + (_) => invoicesRepository.updateInvoicePaidStatus(invoice.id, paid), + (invoices, invoice) => + invoices..[invoices.indexWhere((s) => s.id == invoice.id)] = invoice, + invoice.copyWith(paid: paid), + ); + } + + Future updateInvoiceReceivedStatus(Invoice invoice, bool paid) async { + return await update( + (_) => invoicesRepository.updateInvoiceReceivedStatus(invoice.id), + (invoices, invoice) => + invoices..[invoices.indexWhere((s) => s.id == invoice.id)] = invoice, + invoice.copyWith(received: true), + ); + } + + Future deleteInvoice(Invoice invoice) async { + return await delete( + invoicesRepository.deleteInvoice, + (invoices, invoice) => invoices..remove(invoice), + invoice.id, + invoice, + ); + } +} + +final invoiceListProvider = + StateNotifierProvider>>(( + ref, + ) { + final invoicesRepository = ref.watch(invoiceRepositoryProvider); + return InvoiceListNotifier(invoicesRepository: invoicesRepository) + ..getInvoices(); + }); diff --git a/lib/paiement/providers/invoice_pdf_provider.dart b/lib/paiement/providers/invoice_pdf_provider.dart new file mode 100644 index 0000000000..09af8adc58 --- /dev/null +++ b/lib/paiement/providers/invoice_pdf_provider.dart @@ -0,0 +1,19 @@ +import 'dart:typed_data'; + +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/paiement/repositories/invoice_pdf_repository.dart'; + +class InvoicePdfNotifier extends FamilyAsyncNotifier { + @override + Future build(String arg) async { + final InvoicePdfRepository invoicePdfRepository = ref.watch( + invoicePdfRepositoryProvider, + ); + return await invoicePdfRepository.getInvoicePdf(arg); + } +} + +final invoicePdfProvider = + AsyncNotifierProvider.family( + InvoicePdfNotifier.new, + ); diff --git a/lib/paiement/providers/invoice_provider.dart b/lib/paiement/providers/invoice_provider.dart new file mode 100644 index 0000000000..33921ba96e --- /dev/null +++ b/lib/paiement/providers/invoice_provider.dart @@ -0,0 +1,14 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/paiement/class/invoice.dart'; + +class InvoiceNotifier extends StateNotifier { + InvoiceNotifier() : super(Invoice.empty()); + + void setInvoice(Invoice invoice) { + state = invoice; + } + + void clearInvoice() { + state = Invoice.empty(); + } +} diff --git a/lib/paiement/providers/is_payment_admin.dart b/lib/paiement/providers/is_payment_admin.dart index 4fdca3f91d..b85847fba5 100644 --- a/lib/paiement/providers/is_payment_admin.dart +++ b/lib/paiement/providers/is_payment_admin.dart @@ -1,7 +1,21 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/paiement/providers/bank_account_holder_provider.dart'; import 'package:titan/paiement/providers/my_structures_provider.dart'; -final isPaymentAdminProvider = StateProvider((ref) { +final isStructureAdminProvider = StateProvider((ref) { final myStructures = ref.watch(myStructuresProvider); return myStructures.isNotEmpty; }); + +final isBankAccountHolderProvider = Provider((ref) { + final bankAccountHolder = ref.watch(bankAccountHolderProvider); + final myStructures = ref.watch(myStructuresProvider); + return bankAccountHolder.maybeWhen( + data: (bankAccountHolder) { + return myStructures.any( + (structure) => structure.id == bankAccountHolder.holderStructureId, + ); + }, + orElse: () => false, + ); +}); diff --git a/lib/paiement/providers/my_structures_provider.dart b/lib/paiement/providers/my_structures_provider.dart index d3b7b4c49d..4d6a478061 100644 --- a/lib/paiement/providers/my_structures_provider.dart +++ b/lib/paiement/providers/my_structures_provider.dart @@ -1,4 +1,5 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/paiement/class/structure.dart'; import 'package:titan/paiement/providers/structure_list_provider.dart'; import 'package:titan/user/providers/user_provider.dart'; @@ -9,7 +10,7 @@ final myStructuresProvider = StateProvider((ref) { data: (structures) => structures .where((structure) => structure.managerUser.id == user.id) .toList(), - loading: () => [], - error: (error, stack) => [], + loading: () => List.empty(), + error: (error, stack) => List.empty(), ); }); diff --git a/lib/paiement/repositories/bank_account_holder_repository.dart b/lib/paiement/repositories/bank_account_holder_repository.dart new file mode 100644 index 0000000000..af2952790e --- /dev/null +++ b/lib/paiement/repositories/bank_account_holder_repository.dart @@ -0,0 +1,34 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/auth/providers/openid_provider.dart'; +import 'package:titan/paiement/class/bank_account_holder.dart'; +import 'package:titan/tools/exception.dart'; +import 'package:titan/tools/repository/repository.dart'; + +class BankAccountHolderRepository extends Repository { + @override + // ignore: overridden_fields + final ext = 'mypayment/bank-account-holder'; + + Future getBankAccountHolder() async { + try { + return BankAccountHolder.fromJson(await getOne("")); + } on AppException catch (e) { + if (e.type == ErrorType.tokenExpire) rethrow; + return BankAccountHolder.empty(); + } catch (e) { + return BankAccountHolder.empty(); + } + } + + Future updateBankAccountHolder(String structureId) async { + return BankAccountHolder.fromJson( + await create({'holder_structure_id': structureId}), + ); + } +} + +final bankAccountHolderRepositoryProvider = + Provider((ref) { + final token = ref.watch(tokenProvider); + return BankAccountHolderRepository()..setToken(token); + }); diff --git a/lib/paiement/repositories/invoice_pdf_repository.dart b/lib/paiement/repositories/invoice_pdf_repository.dart new file mode 100644 index 0000000000..afb36f7e7a --- /dev/null +++ b/lib/paiement/repositories/invoice_pdf_repository.dart @@ -0,0 +1,20 @@ +import 'dart:typed_data'; + +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/auth/providers/openid_provider.dart'; +import 'package:titan/tools/repository/pdf_repository.dart'; + +class InvoicePdfRepository extends PdfRepository { + @override + // ignore: overridden_fields + final String ext = "mypayment/invoices/"; + + Future getInvoicePdf(String invoiceId) async { + return await getPdf(invoiceId); + } +} + +final invoicePdfRepositoryProvider = Provider((ref) { + final token = ref.watch(tokenProvider); + return InvoicePdfRepository()..setToken(token); +}); diff --git a/lib/paiement/repositories/invoices_repository.dart b/lib/paiement/repositories/invoices_repository.dart new file mode 100644 index 0000000000..4197cbed2a --- /dev/null +++ b/lib/paiement/repositories/invoices_repository.dart @@ -0,0 +1,87 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/auth/providers/openid_provider.dart'; +import 'package:titan/paiement/class/invoice.dart'; +import 'package:titan/tools/functions.dart'; +import 'package:titan/tools/repository/repository.dart'; + +String formatQuery( + int? page, + int? pageSize, + List? structuresIds, + DateTime? startDate, + DateTime? endDate, +) { + final queryParams = []; + if (page != null) queryParams.add('page=$page'); + if (pageSize != null) queryParams.add('page_size=$pageSize'); + if (structuresIds != null) { + for (final id in structuresIds) { + queryParams.add('structures_ids=$id'); + } + } + if (startDate != null) { + queryParams.add('start_date=${processDateToAPI(startDate)}'); + } + if (endDate != null) { + queryParams.add('end_date=${processDateToAPI(endDate)}'); + } + return queryParams.isNotEmpty ? '?${queryParams.join('&')}' : ''; +} + +class InvoiceRepository extends Repository { + @override + // ignore: overridden_fields + final ext = 'mypayment/invoices'; + + Future> getInvoices( + int page, + int pageSize, + List? structuresIds, + DateTime? startDate, + DateTime? endDate, + ) async { + return List.from( + (await getList( + suffix: formatQuery(page, pageSize, structuresIds, startDate, endDate), + )).map((e) => Invoice.fromJson(e)), + ); + } + + Future> getStructureInvoices( + String structureId, + int page, + int pageSize, + DateTime? startDate, + DateTime? endDate, + ) async { + return List.from( + (await getList( + suffix: + "/structures/$structureId${formatQuery(page, pageSize, null, startDate, endDate)}", + )).map((e) => Invoice.fromJson(e)), + ); + } + + Future createInvoice(String structureId) async { + return Invoice.fromJson( + await create(null, suffix: "/structures/$structureId"), + ); + } + + Future updateInvoicePaidStatus(String invoiceId, bool paid) async { + return await update(null, "/$invoiceId/paid?paid=$paid"); + } + + Future updateInvoiceReceivedStatus(String invoiceId) async { + return await update(null, "/$invoiceId/received"); + } + + Future deleteInvoice(String invoiceId) async { + return await delete("/$invoiceId"); + } +} + +final invoiceRepositoryProvider = Provider((ref) { + final token = ref.watch(tokenProvider); + return InvoiceRepository()..setToken(token); +}); diff --git a/lib/paiement/router.dart b/lib/paiement/router.dart index fd36380079..4895966a5a 100644 --- a/lib/paiement/router.dart +++ b/lib/paiement/router.dart @@ -3,10 +3,14 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/navigation/class/module.dart'; import 'package:titan/paiement/providers/is_payment_admin.dart'; -import 'package:titan/paiement/ui/pages/admin_page/admin_page.dart' - deferred as admin_page; +import 'package:titan/paiement/ui/pages/structure_admin_page/structure_admin_page.dart' + deferred as structure_stores_page; import 'package:titan/paiement/ui/pages/fund_page/web_view_modal.dart' deferred as fund_page; +import 'package:titan/paiement/ui/pages/invoices_admin_page/invoices_admin_page.dart' + deferred as invoices_admin_page; +import 'package:titan/paiement/ui/pages/invoices_structure_page/invoices_structure_page.dart' + deferred as structure_invoices_page; import 'package:titan/paiement/ui/pages/store_pages/add_edit_store.dart' deferred as add_edit_page; import 'package:titan/paiement/ui/pages/store_admin_page/store_admin_page.dart' @@ -32,7 +36,9 @@ class PaymentRouter { static const String root = '/payment'; static const String stats = '/stats'; static const String devices = '/devices'; - static const String admin = '/admin'; + static const String structureStores = '/structureStores'; + static const String invoicesAdmin = '/invoicesAdmin'; + static const String invoicesStructure = '/invoicesStructure'; static const String fund = '/fund'; static const String addEditStore = '/addEditStore'; static const String transferStructure = '/transferStructure'; @@ -75,11 +81,27 @@ class PaymentRouter { middleware: [DeferredLoadingMiddleware(store_admin_page.loadLibrary)], ), QRoute( - path: PaymentRouter.admin, - builder: () => admin_page.AdminPage(), + path: PaymentRouter.invoicesAdmin, + builder: () => invoices_admin_page.InvoicesAdminPage(), middleware: [ - DeferredLoadingMiddleware(admin_page.loadLibrary), - AdminMiddleware(ref, isPaymentAdminProvider), + DeferredLoadingMiddleware(invoices_admin_page.loadLibrary), + AdminMiddleware(ref, isBankAccountHolderProvider), + ], + ), + QRoute( + path: PaymentRouter.invoicesStructure, + builder: () => structure_invoices_page.StructureInvoicesPage(), + middleware: [ + DeferredLoadingMiddleware(structure_invoices_page.loadLibrary), + AdminMiddleware(ref, isStructureAdminProvider), + ], + ), + QRoute( + path: PaymentRouter.structureStores, + builder: () => structure_stores_page.StructureStoresPage(), + middleware: [ + DeferredLoadingMiddleware(structure_stores_page.loadLibrary), + AdminMiddleware(ref, isStructureAdminProvider), ], children: [ QRoute( diff --git a/lib/paiement/tools/constants.dart b/lib/paiement/tools/constants.dart deleted file mode 100644 index 8fa14620d3..0000000000 --- a/lib/paiement/tools/constants.dart +++ /dev/null @@ -1,3 +0,0 @@ -class PaiementTextConstants { - static const String paiement = "Paiement"; -} diff --git a/lib/paiement/tools/functions.dart b/lib/paiement/tools/functions.dart index 652db1fb66..d5dc5925bf 100644 --- a/lib/paiement/tools/functions.dart +++ b/lib/paiement/tools/functions.dart @@ -10,24 +10,6 @@ import 'package:titan/paiement/tools/key_service.dart'; enum TransferType { helloAsso, check, cash, bankTransfer } -String getMonth(int m) { - final months = [ - "Décembre", - "Janvier", - "Février", - "Mars", - "Avril", - "Mai", - "Juin", - "Juillet", - "Août", - "Septembre", - "Octobre", - "Novembre", - ]; - return months[m]; -} - Widget getStatusTag(WalletDeviceStatus status) { switch (status) { case WalletDeviceStatus.active: diff --git a/lib/paiement/ui/pages/devices_page/devices_page.dart b/lib/paiement/ui/pages/devices_page/devices_page.dart index c430d8f82f..537909fee6 100644 --- a/lib/paiement/ui/pages/devices_page/devices_page.dart +++ b/lib/paiement/ui/pages/devices_page/devices_page.dart @@ -208,6 +208,7 @@ class DevicesPage extends HookConsumerWidget { }, ); }), + SizedBox(height: 80), ], ); }, diff --git a/lib/paiement/ui/pages/fund_page/fund_page.dart b/lib/paiement/ui/pages/fund_page/fund_page.dart index 3cec59a8f2..c1b7e15c29 100644 --- a/lib/paiement/ui/pages/fund_page/fund_page.dart +++ b/lib/paiement/ui/pages/fund_page/fund_page.dart @@ -45,6 +45,7 @@ class FundPage extends ConsumerWidget { end: Alignment.bottomRight, ), ), + height: MediaQuery.of(context).size.height * 0.8, child: Column( children: [ const SizedBox(height: 20), @@ -124,6 +125,7 @@ class FundPage extends ConsumerWidget { }, ), const Expanded(child: Center(child: ConfirmFundButton())), + const SizedBox(height: 10), ], ), ), diff --git a/lib/paiement/ui/pages/invoices_admin_page/invoice_card.dart b/lib/paiement/ui/pages/invoices_admin_page/invoice_card.dart new file mode 100644 index 0000000000..cc7bc7e989 --- /dev/null +++ b/lib/paiement/ui/pages/invoices_admin_page/invoice_card.dart @@ -0,0 +1,224 @@ +import 'package:auto_size_text/auto_size_text.dart'; +import 'package:file_saver/file_saver.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:heroicons/heroicons.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/l10n/app_localizations.dart'; +import 'package:titan/paiement/class/invoice.dart'; +import 'package:titan/paiement/providers/invoice_list_provider.dart'; +import 'package:titan/paiement/providers/invoice_pdf_provider.dart'; +import 'package:titan/tools/constants.dart'; +import 'package:titan/tools/functions.dart'; +import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; +import 'package:titan/tools/ui/styleguide/button.dart'; +import 'package:titan/tools/ui/styleguide/confirm_modal.dart'; +import 'package:titan/tools/ui/styleguide/list_item_template.dart'; + +class InvoiceCard extends HookConsumerWidget { + final Invoice invoice; + final bool isAdmin; + + const InvoiceCard({super.key, required this.invoice, required this.isAdmin}) + : super(); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final localizeWithContext = AppLocalizations.of(context)!; + final invoicesNotifier = ref.read(invoiceListProvider.notifier); + final invoicePdf = ref.watch(invoicePdfProvider(invoice.id).future); + + void displayToastWithContext(TypeMsg type, String msg) { + displayToast(context, type, msg); + } + + return Padding( + padding: const EdgeInsets.symmetric(vertical: 5), + child: ListItemTemplate( + title: invoice.reference, + subtitle: invoice.structure.name, + onTap: () => showCustomBottomModal( + context: context, + modal: BottomModalTemplate( + title: invoice.reference, + child: Column( + children: [ + Button( + text: localizeWithContext.paiementDownload, + onPressed: () async { + late final Uint8List pdfBytes; + try { + pdfBytes = await invoicePdf; + } catch (e) { + displayToastWithContext(TypeMsg.error, e.toString()); + return; + } + final path = kIsWeb + ? await FileSaver.instance.saveFile( + name: invoice.reference, + bytes: pdfBytes, + ext: "pdf", + mimeType: MimeType.pdf, + ) + : await FileSaver.instance.saveAs( + name: invoice.reference, + bytes: pdfBytes, + ext: "pdf", + mimeType: MimeType.pdf, + ); + if (path != null) { + displayToastWithContext( + TypeMsg.msg, + localizeWithContext.phSuccesDowloading, + ); + } + }, + ), + if (!invoice.received && isAdmin) ...[ + const SizedBox(height: 10), + Button( + text: invoice.paid + ? localizeWithContext.paiementMarkUnpaid + : localizeWithContext.paiementMarkPaid, + onPressed: () async { + final value = await invoicesNotifier + .updateInvoicePaidStatus(invoice, !invoice.paid); + if (value) { + displayToastWithContext( + TypeMsg.msg, + localizeWithContext.paiementModifySuccessfully, + ); + } else { + displayToastWithContext( + TypeMsg.error, + localizeWithContext.paiementErrorUpdatingStatus, + ); + } + }, + ), + ], + if (!isAdmin && invoice.paid && !invoice.received) ...[ + const SizedBox(height: 10), + Button( + text: localizeWithContext.paiementMarkReceived, + onPressed: () async { + Navigator.of(context).pop(); + showCustomBottomModal( + context: context, + ref: ref, + modal: ConfirmModal.danger( + title: localizeWithContext.paiementDeleteInvoice, + description: + localizeWithContext.globalIrreversibleAction, + onYes: () async { + final value = await invoicesNotifier + .updateInvoiceReceivedStatus(invoice, true); + if (value) { + displayToastWithContext( + TypeMsg.msg, + localizeWithContext.paiementModifySuccessfully, + ); + } else { + displayToastWithContext( + TypeMsg.error, + localizeWithContext.paiementErrorUpdatingStatus, + ); + } + }, + ), + ); + }, + ), + ], + if (!invoice.paid && isAdmin) ...[ + const SizedBox(height: 10), + Button( + text: localizeWithContext.paiementDeleteInvoice, + onPressed: () async { + Navigator.of(context).pop(); + showCustomBottomModal( + context: context, + ref: ref, + modal: ConfirmModal.danger( + title: localizeWithContext.paiementDeleteInvoice, + description: + localizeWithContext.globalIrreversibleAction, + onYes: () async { + final value = await invoicesNotifier.deleteInvoice( + invoice, + ); + if (value) { + displayToastWithContext( + TypeMsg.msg, + localizeWithContext.paiementDeleteSuccessfully, + ); + } else { + displayToastWithContext( + TypeMsg.error, + localizeWithContext.paiementErrorDeleting, + ); + } + }, + ), + ); + }, + ), + ], + ], + ), + ), + ref: ref, + ), + trailing: Expanded( + flex: kIsWeb ? 2 : 1, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if (kIsWeb) + Column( + children: [ + Text( + '${(invoice.total / 100).toStringAsFixed(2)} €', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: ColorConstants.tertiary, + ), + ), + AutoSizeText( + localizeWithContext.paiementFromTo( + invoice.startDate, + invoice.endDate, + ), + maxLines: 2, + style: TextStyle(fontSize: 12), + ), + ], + ), + Text( + invoice.received + ? localizeWithContext.paiementReceived + : invoice.paid + ? localizeWithContext.paiementPaid + : localizeWithContext.paiementPending, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: invoice.received + ? Colors.green + : invoice.paid + ? Colors.blue + : Colors.orange, + ), + ), + const HeroIcon( + HeroIcons.chevronRight, + color: ColorConstants.tertiary, + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/paiement/ui/pages/invoices_admin_page/invoices_admin_page.dart b/lib/paiement/ui/pages/invoices_admin_page/invoices_admin_page.dart new file mode 100644 index 0000000000..45e2fa5e18 --- /dev/null +++ b/lib/paiement/ui/pages/invoices_admin_page/invoices_admin_page.dart @@ -0,0 +1,210 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:heroicons/heroicons.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/admin/providers/structure_provider.dart'; +import 'package:titan/l10n/app_localizations.dart'; +import 'package:titan/paiement/providers/invoice_list_provider.dart'; +import 'package:titan/paiement/providers/structure_list_provider.dart'; +import 'package:titan/paiement/ui/pages/invoices_admin_page/invoice_card.dart'; +import 'package:titan/paiement/ui/paiement.dart'; +import 'package:titan/tools/constants.dart'; +import 'package:titan/tools/functions.dart'; +import 'package:titan/tools/token_expire_wrapper.dart'; +import 'package:titan/tools/ui/builders/async_child.dart'; +import 'package:titan/tools/ui/layouts/refresher.dart'; +import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; +import 'package:titan/tools/ui/styleguide/button.dart'; +import 'package:titan/tools/ui/styleguide/item_chip.dart'; +import 'package:titan/tools/ui/styleguide/list_item_template.dart'; +import 'package:tuple/tuple.dart'; + +class InvoicesAdminPage extends HookConsumerWidget { + const InvoicesAdminPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final controller = useScrollController(); + final page = useState(1); + final pageSize = useState(20); + final invoices = ref.watch(invoiceListProvider); + final structures = ref.watch(structureListProvider); + final structureNotifier = ref.watch(structureProvider.notifier); + final invoicesNotifier = ref.read(invoiceListProvider.notifier); + + final localizeWithContext = AppLocalizations.of(context)!; + + void refreshInvoices() { + tokenExpireWrapper( + ref, + () => invoicesNotifier.getInvoices( + page: page.value, + pageLimit: pageSize.value, + ), + ); + } + + void displayToastWithContext(TypeMsg type, String msg) { + displayToast(context, type, msg); + } + + return PaymentTemplate( + child: Refresher( + onRefresh: () async { + refreshInvoices(); + }, + controller: controller, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Async2Children( + values: Tuple2(invoices, structures), + builder: (context, invoices, structures) { + return Column( + children: [ + Row( + children: [ + IconButton( + icon: HeroIcon(HeroIcons.arrowLeft), + onPressed: page.value <= 1 + ? null + : () { + page.value--; + refreshInvoices(); + }, + color: ColorConstants.onTertiary, + disabledColor: ColorConstants.background, + ), + DropdownButton( + items: [10, 20, 50, 100] + .map( + (size) => DropdownMenuItem( + value: size, + child: Text( + localizeWithContext.paiementInvoicesPerPage( + size, + ), + ), + ), + ) + .toList(), + onChanged: (value) { + if (value != null) { + pageSize.value = value; + refreshInvoices(); + } + }, + value: pageSize.value, + ), + IconButton( + icon: HeroIcon(HeroIcons.arrowRight), + onPressed: invoices.length < pageSize.value + ? null + : () { + page.value++; + refreshInvoices(); + }, + color: ColorConstants.onTertiary, + disabledColor: ColorConstants.background, + ), + ], + ), + const SizedBox(height: 10), + ListItemTemplate( + title: localizeWithContext.paiementCreateInvoice, + onTap: () => showCustomBottomModal( + context: context, + modal: Consumer( + builder: (context, ref, _) { + final structure = ref.watch(structureProvider); + return BottomModalTemplate( + title: localizeWithContext.paiementCreateInvoice, + child: Column( + children: [ + Text( + localizeWithContext.paiementSelectStructure, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + SizedBox( + height: min( + structures.length * 50, + MediaQuery.of(context).size.height * 0.8, + ), + child: SingleChildScrollView( + child: Column( + children: structures + .map( + (e) => ItemChip( + scrollDirection: Axis.vertical, + onTap: () => structureNotifier + .setStructure(e), + selected: structure.id == e.id, + child: Text( + e.name, + style: TextStyle( + fontSize: 16, + color: structure.id == e.id + ? Colors.white + : Colors.black, + ), + ), + ), + ) + .toList(), + ), + ), + ), + Button( + text: localizeWithContext.paiementCreate, + onPressed: () async { + if (structure.id == "") return; + Navigator.pop(context); + await tokenExpireWrapper(ref, () async { + final value = await invoicesNotifier + .createInvoice(structure); + if (value) { + displayToastWithContext( + TypeMsg.msg, + localizeWithContext + .paiementInvoiceCreatedSuccessfully, + ); + refreshInvoices(); + } else { + displayToastWithContext( + TypeMsg.error, + localizeWithContext + .paiementNoInvoiceToCreate, + ); + } + }); + }, + ), + ], + ), + ); + }, + ), + ref: ref, + ), + trailing: HeroIcon( + HeroIcons.plus, + color: ColorConstants.onTertiary, + ), + ), + const SizedBox(height: 10), + ...invoices.map( + (invoice) => InvoiceCard(invoice: invoice, isAdmin: true), + ), + ], + ); + }, + ), + ), + ), + ); + } +} diff --git a/lib/paiement/ui/pages/invoices_structure_page/invoices_structure_page.dart b/lib/paiement/ui/pages/invoices_structure_page/invoices_structure_page.dart new file mode 100644 index 0000000000..1edec34da4 --- /dev/null +++ b/lib/paiement/ui/pages/invoices_structure_page/invoices_structure_page.dart @@ -0,0 +1,106 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:heroicons/heroicons.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/paiement/providers/invoice_list_provider.dart'; +import 'package:titan/paiement/providers/selected_structure_provider.dart'; +import 'package:titan/paiement/ui/pages/invoices_admin_page/invoice_card.dart'; +import 'package:titan/paiement/ui/paiement.dart'; +import 'package:titan/tools/constants.dart'; +import 'package:titan/tools/token_expire_wrapper.dart'; +import 'package:titan/tools/ui/builders/async_child.dart'; +import 'package:titan/tools/ui/layouts/refresher.dart'; + +class StructureInvoicesPage extends HookConsumerWidget { + const StructureInvoicesPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final controller = useScrollController(); + final page = useState(1); + final pageSize = useState(20); + + final selectedStructure = ref.watch(selectedStructureProvider); + final invoices = ref.watch(invoiceListProvider); + final invoicesNotifier = ref.watch(invoiceListProvider.notifier); + + void refreshInvoices() { + tokenExpireWrapper( + ref, + () => invoicesNotifier.getStructureInvoices( + selectedStructure.id, + page: page.value, + pageLimit: pageSize.value, + ), + ); + } + + return PaymentTemplate( + child: Refresher( + controller: controller, + onRefresh: () async { + refreshInvoices(); + }, + child: AsyncChild( + value: invoices, + builder: (context, invoices) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Column( + children: [ + Row( + children: [ + IconButton( + icon: HeroIcon(HeroIcons.arrowLeft), + onPressed: page.value <= 1 + ? null + : () { + page.value--; + refreshInvoices(); + }, + color: ColorConstants.onTertiary, + disabledColor: ColorConstants.background, + ), + DropdownButton( + items: [10, 20, 50, 100] + .map( + (size) => DropdownMenuItem( + value: size, + child: Text(size.toString()), + ), + ) + .toList(), + onChanged: (value) { + if (value != null) { + pageSize.value = value; + refreshInvoices(); + } + }, + value: pageSize.value, + ), + IconButton( + icon: HeroIcon(HeroIcons.arrowRight), + onPressed: invoices.length < pageSize.value + ? null + : () { + page.value++; + refreshInvoices(); + }, + color: ColorConstants.onTertiary, + disabledColor: ColorConstants.background, + ), + ], + ), + const SizedBox(height: 10), + ...invoices.map( + (invoice) => InvoiceCard(invoice: invoice, isAdmin: false), + ), + ], + ), + ); + }, + ), + ), + ); + } +} diff --git a/lib/paiement/ui/pages/main_page/account_card/account_card.dart b/lib/paiement/ui/pages/main_page/account_card/account_card.dart index 124842673a..6c8512dba3 100644 --- a/lib/paiement/ui/pages/main_page/account_card/account_card.dart +++ b/lib/paiement/ui/pages/main_page/account_card/account_card.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:intl/intl.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/paiement/class/wallet_device.dart'; import 'package:titan/paiement/providers/device_list_provider.dart'; import 'package:titan/paiement/providers/device_provider.dart'; @@ -21,6 +22,7 @@ import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; class AccountCard extends HookConsumerWidget { final Function? toggle; @@ -44,34 +46,38 @@ class AccountCard extends HookConsumerWidget { const Color.fromARGB(255, 4, 84, 84), ]; final formatter = NumberFormat("#,##0.00", "fr_FR"); + final localizeWithContext = AppLocalizations.of(context)!; void displayToastWithContext(TypeMsg type, String message) { displayToast(context, type, message); } void showPayModal() { - showModalBottomSheet( + showCustomBottomModal( context: context, - backgroundColor: Colors.transparent, - scrollControlDisabledMaxHeightRatio: - (1 - 80 / MediaQuery.of(context).size.height), - builder: (context) => const PayPage(), - ).then((_) { - payAmountNotifier.setPayAmount(""); - }); + // backgroundColor: Colors.transparent, + // scrollControlDisabledMaxHeightRatio: + // (1 - 80 / MediaQuery.of(context).size.height), + // builder: (context) => const PayPage(), + modal: PayPage(), + ref: ref, + onCloseCallback: () => payAmountNotifier.setPayAmount(""), + ); } void showFundModal() async { resetHandledKeys(); - await showModalBottomSheet( + await showCustomBottomModal( context: context, - backgroundColor: Colors.transparent, - scrollControlDisabledMaxHeightRatio: - (1 - 80 / MediaQuery.of(context).size.height), - builder: (context) => const FundPage(), - ).then((code) { - fundAmountNotifier.setFundAmount(""); - }); + modal: FundPage(), + ref: ref, + onCloseCallback: () => fundAmountNotifier.setFundAmount(""), + + // backgroundColor: Colors.transparent, + // scrollControlDisabledMaxHeightRatio: + // (1 - 80 / MediaQuery.of(context).size.height), + // builder: (context) => const FundPage(), + ); } void showNotRegisteredDeviceDialog() async { @@ -79,10 +85,10 @@ class AccountCard extends HookConsumerWidget { context: context, builder: (context) { return DeviceDialogBox( - title: 'Appareil non enregistré', + title: localizeWithContext.paiementDeviceNotRegistered, descriptions: - 'Votre appareil n\'est pas encore enregistré. \nPour l\'enregistrer, veuillez vous rendre sur la page des appareils.', - buttonText: 'Accéder à la page', + localizeWithContext.paiementDeviceNotRegisteredDescription, + buttonText: localizeWithContext.paiementAccessPage, onClick: () { QR.to(PaymentRouter.root + PaymentRouter.devices); }, @@ -97,13 +103,13 @@ class AccountCard extends HookConsumerWidget { Color(0xff017f80), Color.fromARGB(255, 4, 84, 84), ], - title: 'Solde personnel', + title: localizeWithContext.paiementPersonalBalance, toggle: toggle, actionButtons: [ MainCardButton( colors: buttonGradient, icon: HeroIcons.devicePhoneMobile, - title: "Appareils", + title: localizeWithContext.paiementDevices, onPressed: () async { ref.invalidate(deviceListProvider); QR.to(PaymentRouter.root + PaymentRouter.devices); @@ -113,13 +119,13 @@ class AccountCard extends HookConsumerWidget { MainCardButton( colors: buttonGradient, icon: HeroIcons.qrCode, - title: "Payer", + title: localizeWithContext.paiementPay, onPressed: () async { await tokenExpireWrapper(ref, () async { if (!hasAcceptedToS) { displayToastWithContext( TypeMsg.error, - "Veuillez accepter les Conditions Générales d'Utilisation.", + localizeWithContext.paiementPleaseAcceptTOS, ); return; } @@ -138,10 +144,11 @@ class AccountCard extends HookConsumerWidget { context: context, builder: (context) { return DeviceDialogBox( - title: 'Appareil non activé', - descriptions: - 'Votre appareil n\'est pas encore activé. \nPour l\'activer, veuillez vous rendre sur la page des appareils.', - buttonText: 'Accéder à la page', + title: + localizeWithContext.paiementDeviceNotActivated, + descriptions: localizeWithContext + .paiementDeviceNotActivatedDescription, + buttonText: localizeWithContext.paiementAccessPage, onClick: () { QR.to(PaymentRouter.root + PaymentRouter.devices); }, @@ -153,10 +160,10 @@ class AccountCard extends HookConsumerWidget { context: context, builder: (context) { return DeviceDialogBox( - title: 'Appareil révoqué', - descriptions: - 'Votre appareil a été révoqué. \nPour le réactiver, veuillez vous rendre sur la page des appareils.', - buttonText: 'Accéder à la page', + title: localizeWithContext.paiementDeviceRevoked, + descriptions: localizeWithContext + .paiementReactivateRevokedDeviceDescription, + buttonText: localizeWithContext.paiementAccessPage, onClick: () { QR.to(PaymentRouter.root + PaymentRouter.devices); }, @@ -168,7 +175,7 @@ class AccountCard extends HookConsumerWidget { error: (e, s) { displayToastWithContext( TypeMsg.error, - "Erreur lors de la récupération de l'appareil", + localizeWithContext.paiementDeviceRecoveryError, ); }, loading: () {}, @@ -179,7 +186,7 @@ class AccountCard extends HookConsumerWidget { MainCardButton( colors: buttonGradient, icon: HeroIcons.chartPie, - title: "Stats", + title: localizeWithContext.paiementStats, onPressed: () async { QR.to(PaymentRouter.root + PaymentRouter.stats); }, @@ -187,12 +194,12 @@ class AccountCard extends HookConsumerWidget { MainCardButton( colors: buttonGradient, icon: HeroIcons.creditCard, - title: "Recharger", + title: localizeWithContext.paiementTopUpAction, onPressed: () async { if (!hasAcceptedToS) { displayToastWithContext( TypeMsg.error, - "Veuillez accepter les Conditions Générales d'Utilisation.", + localizeWithContext.paiementPleaseAcceptTOS, ); return; } @@ -207,7 +214,7 @@ class AccountCard extends HookConsumerWidget { style: const TextStyle(color: Colors.white, fontSize: 50), ), errorBuilder: (error, stackTrace) => Text( - 'Erreur lors de la récupération du solde : $error', + localizeWithContext.paiementGetBalanceError, style: const TextStyle(color: Colors.white, fontSize: 50), ), ), diff --git a/lib/paiement/ui/pages/main_page/main_page.dart b/lib/paiement/ui/pages/main_page/main_page.dart index 623c722b20..f1714ea4ac 100644 --- a/lib/paiement/ui/pages/main_page/main_page.dart +++ b/lib/paiement/ui/pages/main_page/main_page.dart @@ -45,7 +45,7 @@ class PaymentMainPage extends HookConsumerWidget { final mySellersNotifier = ref.read(myStoresProvider.notifier); final myHistoryNotifier = ref.read(myHistoryProvider.notifier); final myWalletNotifier = ref.read(myWalletProvider.notifier); - final isAdmin = ref.watch(isPaymentAdminProvider); + final isAdmin = ref.watch(isStructureAdminProvider); final flipped = useState(true); ref.listen(pathForwardingProvider, (previous, next) async { diff --git a/lib/paiement/ui/pages/main_page/seller_card/admin_invoice_card.dart b/lib/paiement/ui/pages/main_page/seller_card/admin_invoice_card.dart new file mode 100644 index 0000000000..a6bde2e160 --- /dev/null +++ b/lib/paiement/ui/pages/main_page/seller_card/admin_invoice_card.dart @@ -0,0 +1,60 @@ +import 'package:auto_size_text/auto_size_text.dart'; +import 'package:flutter/material.dart'; +import 'package:heroicons/heroicons.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/l10n/app_localizations.dart'; +import 'package:titan/paiement/providers/invoice_list_provider.dart'; +import 'package:titan/paiement/router.dart'; +import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/tools/constants.dart'; +import 'package:titan/tools/token_expire_wrapper.dart'; + +class InvoiceAdminCard extends ConsumerWidget { + const InvoiceAdminCard({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final invoicesNotifier = ref.watch(invoiceListProvider.notifier); + return GestureDetector( + behavior: HitTestBehavior.opaque, + child: Container( + height: 70, + padding: const EdgeInsets.symmetric(horizontal: 20), + width: MediaQuery.of(context).size.width, + child: Row( + children: [ + CircleAvatar( + radius: 27, + backgroundColor: Color.fromARGB(255, 6, 75, 75), + child: HeroIcon( + HeroIcons.documentCurrencyEuro, + color: ColorConstants.background, + ), + ), + SizedBox(width: 15), + Expanded( + child: AutoSizeText( + AppLocalizations.of(context)!.paiementBillingSpace, + maxLines: 1, + style: TextStyle( + color: Color.fromARGB(255, 0, 29, 29), + fontSize: 14, + ), + ), + ), + SizedBox(width: 10), + HeroIcon( + HeroIcons.arrowRight, + color: Color.fromARGB(255, 0, 29, 29), + size: 25, + ), + ], + ), + ), + onTap: () { + tokenExpireWrapper(ref, () => invoicesNotifier.getInvoices()); + QR.to(PaymentRouter.root + PaymentRouter.invoicesAdmin); + }, + ); + } +} diff --git a/lib/paiement/ui/pages/main_page/seller_card/store_admin_card.dart b/lib/paiement/ui/pages/main_page/seller_card/store_admin_card.dart deleted file mode 100644 index ca467b3dca..0000000000 --- a/lib/paiement/ui/pages/main_page/seller_card/store_admin_card.dart +++ /dev/null @@ -1,62 +0,0 @@ -import 'package:auto_size_text/auto_size_text.dart'; -import 'package:flutter/material.dart'; -import 'package:heroicons/heroicons.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/l10n/app_localizations.dart'; -import 'package:titan/paiement/providers/my_structures_provider.dart'; -import 'package:titan/paiement/providers/selected_structure_provider.dart'; -import 'package:titan/paiement/router.dart'; -import 'package:qlevar_router/qlevar_router.dart'; - -class StoreAdminCard extends ConsumerWidget { - const StoreAdminCard({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final myStructures = ref.watch(myStructuresProvider); - final selectedStructureNotifier = ref.read( - selectedStructureProvider.notifier, - ); - return Column( - children: myStructures.map((structure) { - return GestureDetector( - behavior: HitTestBehavior.opaque, - child: Container( - height: 70, - padding: const EdgeInsets.symmetric(horizontal: 20), - width: MediaQuery.of(context).size.width, - child: Row( - children: [ - CircleAvatar( - radius: 27, - backgroundColor: Color.fromARGB(255, 6, 75, 75), - ), - SizedBox(width: 15), - Expanded( - child: AutoSizeText( - "${AppLocalizations.of(context)!.paiementStoreManagement} ${structure.name}", - maxLines: 2, - style: TextStyle( - color: Color.fromARGB(255, 0, 29, 29), - fontSize: 14, - ), - ), - ), - SizedBox(width: 10), - HeroIcon( - HeroIcons.arrowRight, - color: Color.fromARGB(255, 0, 29, 29), - size: 25, - ), - ], - ), - ), - onTap: () { - selectedStructureNotifier.setStructure(structure); - QR.to(PaymentRouter.root + PaymentRouter.admin); - }, - ); - }).toList(), - ); - } -} diff --git a/lib/paiement/ui/pages/main_page/seller_card/store_card.dart b/lib/paiement/ui/pages/main_page/seller_card/store_card.dart index d3f93d3b63..ee7c0faf09 100644 --- a/lib/paiement/ui/pages/main_page/seller_card/store_card.dart +++ b/lib/paiement/ui/pages/main_page/seller_card/store_card.dart @@ -10,6 +10,7 @@ import 'package:titan/paiement/router.dart'; import 'package:titan/paiement/ui/pages/main_page/main_card_button.dart'; import 'package:titan/paiement/ui/pages/main_page/main_card_template.dart'; import 'package:titan/paiement/ui/pages/scan_page/scan_page.dart'; +import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; import 'package:titan/user/providers/user_provider.dart'; import 'package:qlevar_router/qlevar_router.dart'; @@ -45,17 +46,20 @@ class StoreCard extends HookConsumerWidget { icon: HeroIcons.viewfinderCircle, title: AppLocalizations.of(context)!.paiementScan, onPressed: () async { - showModalBottomSheet( + showCustomBottomModal( context: context, - enableDrag: false, - backgroundColor: Colors.transparent, - scrollControlDisabledMaxHeightRatio: - (1 - 80 / MediaQuery.of(context).size.height), - builder: (context) => ScanPage(), - ).then((_) { - ongoingTransactionNotifier.clearOngoingTransaction(); - barcodeNotifier.clearBarcode(); - }); + modal: ScanPage(), + ref: ref, + onCloseCallback: () { + ongoingTransactionNotifier.clearOngoingTransaction(); + barcodeNotifier.clearBarcode(); + }, + // enableDrag: false, + // backgroundColor: Colors.transparent, + // scrollControlDisabledMaxHeightRatio: + // (1 - 80 / MediaQuery.of(context).size.height), + // builder: (context) => ScanPage(), + ); }, ), if (store.canManageSellers) diff --git a/lib/paiement/ui/pages/main_page/seller_card/store_list.dart b/lib/paiement/ui/pages/main_page/seller_card/store_list.dart index f3dbb2ea4a..7208083b63 100644 --- a/lib/paiement/ui/pages/main_page/seller_card/store_list.dart +++ b/lib/paiement/ui/pages/main_page/seller_card/store_list.dart @@ -4,7 +4,8 @@ import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/paiement/class/user_store.dart'; import 'package:titan/paiement/providers/is_payment_admin.dart'; import 'package:titan/paiement/providers/my_stores_provider.dart'; -import 'package:titan/paiement/ui/pages/main_page/seller_card/store_admin_card.dart'; +import 'package:titan/paiement/ui/pages/main_page/seller_card/admin_invoice_card.dart'; +import 'package:titan/paiement/ui/pages/main_page/seller_card/structure_admin_card.dart'; import 'package:titan/paiement/ui/pages/main_page/seller_card/store_divider.dart'; import 'package:titan/paiement/ui/pages/main_page/seller_card/store_seller_card.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; @@ -16,7 +17,8 @@ class StoreList extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final stores = ref.watch(myStoresProvider); - final isAdmin = ref.watch(isPaymentAdminProvider); + final isStructureAdmin = ref.watch(isStructureAdminProvider); + final isBankAccountHolder = ref.watch(isBankAccountHolderProvider); return SizedBox( height: maxHeight, child: SingleChildScrollView( @@ -49,11 +51,12 @@ class StoreList extends ConsumerWidget { } return Column( children: [ - if (isAdmin) ...[ + if (isStructureAdmin) ...[ StoreDivider( name: AppLocalizations.of(context)!.paiementAdmin, ), - const StoreAdminCard(), + if (isBankAccountHolder) const InvoiceAdminCard(), + const StructureAdminCard(), ], ...sortedByMembership.map((membership, stores) { final List alphabeticallyOrderedStores = stores @@ -73,7 +76,7 @@ class StoreList extends ConsumerWidget { ); }, ), - const SizedBox(height: 15), + const SizedBox(height: 80), ], ), ), diff --git a/lib/paiement/ui/pages/main_page/seller_card/structure_admin_card.dart b/lib/paiement/ui/pages/main_page/seller_card/structure_admin_card.dart new file mode 100644 index 0000000000..c89fcb8964 --- /dev/null +++ b/lib/paiement/ui/pages/main_page/seller_card/structure_admin_card.dart @@ -0,0 +1,107 @@ +import 'package:auto_size_text/auto_size_text.dart'; +import 'package:flutter/material.dart'; +import 'package:heroicons/heroicons.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/l10n/app_localizations.dart'; +import 'package:titan/paiement/providers/invoice_list_provider.dart'; +import 'package:titan/paiement/providers/my_structures_provider.dart'; +import 'package:titan/paiement/providers/selected_structure_provider.dart'; +import 'package:titan/paiement/router.dart'; +import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/tools/token_expire_wrapper.dart'; +import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; +import 'package:titan/tools/ui/styleguide/button.dart'; + +class StructureAdminCard extends ConsumerWidget { + const StructureAdminCard({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final myStructures = ref.watch(myStructuresProvider); + final selectedStructureNotifier = ref.read( + selectedStructureProvider.notifier, + ); + final invoicesNotifier = ref.watch(invoiceListProvider.notifier); + + final localizeWithContext = AppLocalizations.of(context)!; + + return Column( + children: myStructures.map((structure) { + return GestureDetector( + behavior: HitTestBehavior.opaque, + child: Container( + height: 70, + padding: const EdgeInsets.symmetric(horizontal: 20), + width: MediaQuery.of(context).size.width, + child: Row( + children: [ + CircleAvatar( + radius: 27, + backgroundColor: Color.fromARGB(255, 6, 75, 75), + ), + SizedBox(width: 15), + Expanded( + child: AutoSizeText( + localizeWithContext.paiementStructureManagement( + structure.name, + ), + maxLines: 2, + style: TextStyle( + color: Color.fromARGB(255, 0, 29, 29), + fontSize: 14, + ), + ), + ), + SizedBox(width: 10), + HeroIcon( + HeroIcons.arrowRight, + color: Color.fromARGB(255, 0, 29, 29), + size: 25, + ), + ], + ), + ), + onTap: () { + showCustomBottomModal( + context: context, + modal: BottomModalTemplate( + title: structure.name, + child: Column( + children: [ + Button( + text: localizeWithContext.paiementStores, + onPressed: () { + Navigator.of(context).pop(); + selectedStructureNotifier.setStructure(structure); + QR.to( + PaymentRouter.root + PaymentRouter.structureStores, + ); + }, + ), + const SizedBox(height: 10), + Button( + text: localizeWithContext.paiementInvoices, + onPressed: () { + Navigator.of(context).pop(); + tokenExpireWrapper( + ref, + () => invoicesNotifier.getStructureInvoices( + structure.id, + ), + ); + QR.to( + PaymentRouter.root + PaymentRouter.invoicesStructure, + ); + }, + ), + ], + ), + ), + ref: ref, + ); + }, + ); + }).toList(), + ); + } +} diff --git a/lib/paiement/ui/pages/main_page/tos_dialog.dart b/lib/paiement/ui/pages/main_page/tos_dialog.dart index 706d94697c..bd8a5d5fbf 100644 --- a/lib/paiement/ui/pages/main_page/tos_dialog.dart +++ b/lib/paiement/ui/pages/main_page/tos_dialog.dart @@ -112,6 +112,7 @@ class TOSDialogBox extends StatelessWidget { ], ), ), + SizedBox(height: 40), ], ), ), diff --git a/lib/paiement/ui/pages/pay_page/confirm_button.dart b/lib/paiement/ui/pages/pay_page/confirm_button.dart index 814e19c3ec..d3e2223b7a 100644 --- a/lib/paiement/ui/pages/pay_page/confirm_button.dart +++ b/lib/paiement/ui/pages/pay_page/confirm_button.dart @@ -139,7 +139,7 @@ class ConfirmButton extends ConsumerWidget { AndroidAuthMessages( signInTitle: AppLocalizations.of( context, - )!.paiementAthenticationRequired, + )!.paiementAuthenticationRequired, cancelButton: AppLocalizations.of(context)!.paiementNoThanks, ), IOSAuthMessages( diff --git a/lib/paiement/ui/pages/pay_page/pay_page.dart b/lib/paiement/ui/pages/pay_page/pay_page.dart index 4cae0b60cc..cabd3a5368 100644 --- a/lib/paiement/ui/pages/pay_page/pay_page.dart +++ b/lib/paiement/ui/pages/pay_page/pay_page.dart @@ -38,6 +38,7 @@ class PayPage extends ConsumerWidget { end: Alignment.bottomRight, ), ), + height: MediaQuery.of(context).size.height * 0.8, child: Column( children: [ const SizedBox(height: 20), diff --git a/lib/paiement/ui/pages/scan_page/scan_page.dart b/lib/paiement/ui/pages/scan_page/scan_page.dart index 1efdc3f270..e74e4ad6fe 100644 --- a/lib/paiement/ui/pages/scan_page/scan_page.dart +++ b/lib/paiement/ui/pages/scan_page/scan_page.dart @@ -43,349 +43,354 @@ class ScanPage extends HookConsumerWidget { final opacity = useAnimationController(duration: const Duration(seconds: 1)) ..repeat(reverse: true); - return Stack( - children: [ - Scanner(key: scannerKey), - store.structure.associationMembership.id != '' - ? Positioned( - top: 10, - left: 20, - child: SizedBox( - width: MediaQuery.of(context).size.width - 40, - child: Row( - children: [ - GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () { - bypassNotifier.setBypass(!bypass); - }, - child: Row( - children: [ - Checkbox( - value: !bypass, - checkColor: Colors.black, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(5), - ), - side: const BorderSide( - color: Colors.white, - width: 1.5, + return SizedBox( + height: MediaQuery.of(context).size.height * 0.9, + child: Stack( + children: [ + Scanner(key: scannerKey), + store.structure.associationMembership.id != '' + ? Positioned( + top: 10, + left: 20, + child: SizedBox( + width: MediaQuery.of(context).size.width - 40, + child: Row( + children: [ + GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + bypassNotifier.setBypass(!bypass); + }, + child: Row( + children: [ + Checkbox( + value: !bypass, + checkColor: Colors.black, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + side: const BorderSide( + color: Colors.white, + width: 1.5, + ), + activeColor: Colors.white, + onChanged: (value) { + bypassNotifier.setBypass(!bypass); + }, ), - activeColor: Colors.white, - onChanged: (value) { - bypassNotifier.setBypass(!bypass); - }, - ), - const SizedBox(width: 5), - Text( - "${AppLocalizations.of(context)!.paiementLimitedTo} ${store.structure.associationMembership.name}", - style: TextStyle( - color: bypass - ? Colors.white.withValues(alpha: 0.5) - : Colors.white, - fontSize: 15, + const SizedBox(width: 5), + Text( + "${AppLocalizations.of(context)!.paiementLimitedTo} ${store.structure.associationMembership.name}", + style: TextStyle( + color: bypass + ? Colors.white.withValues(alpha: 0.5) + : Colors.white, + fontSize: 15, + ), ), - ), - ], + ], + ), ), - ), - Spacer(), - GestureDetector( - onTap: () { - Navigator.pop(context); - }, - child: const HeroIcon( - HeroIcons.xMark, - size: 20, - color: Colors.white, + Spacer(), + GestureDetector( + onTap: () { + Navigator.pop(context); + }, + child: const HeroIcon( + HeroIcons.xMark, + size: 20, + color: Colors.white, + ), ), - ), - ], + ], + ), ), - ), - ) - : Positioned( - top: 20, - right: 20, - child: GestureDetector( - onTap: () { - Navigator.pop(context); - }, - child: const HeroIcon( - HeroIcons.xMark, - size: 20, - color: Colors.white, + ) + : Positioned( + top: 20, + right: 20, + child: GestureDetector( + onTap: () { + Navigator.pop(context); + }, + child: const HeroIcon( + HeroIcons.xMark, + size: 20, + color: Colors.white, + ), ), ), - ), - Column( - children: [ - Expanded( - child: Column( - children: [ - const SizedBox(height: 60), - barcode != null - ? Row( - children: [ - const Spacer(), - AsyncChild( - value: ongoingTransaction, - builder: (context, transaction) { - return Container( - padding: const EdgeInsets.symmetric( - vertical: 20, - horizontal: 50, - ), - decoration: BoxDecoration( - gradient: const RadialGradient( - colors: [ - Color(0xff79a400), - Color(0xff387200), - ], - center: Alignment.topLeft, - radius: 2, + Column( + children: [ + Expanded( + child: Column( + children: [ + const SizedBox(height: 60), + barcode != null + ? Row( + children: [ + const Spacer(), + AsyncChild( + value: ongoingTransaction, + builder: (context, transaction) { + return Container( + padding: const EdgeInsets.symmetric( + vertical: 20, + horizontal: 50, ), - borderRadius: BorderRadius.circular(20), - ), - child: Column( - children: [ - Text( - AppLocalizations.of( - context, - )!.paiementAmount, - style: TextStyle( - fontSize: 13, - color: Colors.white, - ), + decoration: BoxDecoration( + gradient: const RadialGradient( + colors: [ + Color(0xff79a400), + Color(0xff387200), + ], + center: Alignment.topLeft, + radius: 2, ), - Text( - '${formatter.format(barcode.tot / 100)} €', - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 25, - color: Colors.white, + borderRadius: BorderRadius.circular(20), + ), + child: Column( + children: [ + Text( + AppLocalizations.of( + context, + )!.paiementAmount, + style: TextStyle( + fontSize: 13, + color: Colors.white, + ), + ), + Text( + '${formatter.format(barcode.tot / 100)} €', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 25, + color: Colors.white, + ), ), + ], + ), + ); + }, + errorBuilder: (error, stack) { + return Container( + padding: const EdgeInsets.symmetric( + vertical: 20, + horizontal: 50, + ), + decoration: BoxDecoration( + gradient: const RadialGradient( + colors: [ + Color(0xffa40000), + Color(0xff720000), + ], + center: Alignment.topLeft, + radius: 2, ), - ], - ), - ); - }, - errorBuilder: (error, stack) { - return Container( + borderRadius: BorderRadius.circular(20), + ), + child: Text( + (error as AppException).message, + style: TextStyle( + fontSize: 15, + color: Colors.white, + ), + ), + ); + }, + loadingBuilder: (context) => Container( padding: const EdgeInsets.symmetric( vertical: 20, horizontal: 50, ), decoration: BoxDecoration( - gradient: const RadialGradient( + gradient: RadialGradient( colors: [ - Color(0xffa40000), - Color(0xff720000), + Colors.grey.shade200, + Colors.grey.shade300, ], center: Alignment.topLeft, radius: 2, ), borderRadius: BorderRadius.circular(20), ), - child: Text( - (error as AppException).message, - style: TextStyle( - fontSize: 15, - color: Colors.white, - ), - ), - ); - }, - loadingBuilder: (context) => Container( - padding: const EdgeInsets.symmetric( - vertical: 20, - horizontal: 50, - ), - decoration: BoxDecoration( - gradient: RadialGradient( - colors: [ - Colors.grey.shade200, - Colors.grey.shade300, - ], - center: Alignment.topLeft, - radius: 2, - ), - borderRadius: BorderRadius.circular(20), + child: Loader(), ), - child: Loader(), ), - ), - const Spacer(), - ], - ) - : SizedBox( - height: 100, - child: Center( - child: AnimatedBuilder( - animation: opacity, - builder: (context, child) { - return Opacity( - opacity: opacity.value, - child: Text( - AppLocalizations.of( - context, - )!.paiementScanCode, - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - color: Colors.white, + const Spacer(), + ], + ) + : SizedBox( + height: 100, + child: Center( + child: AnimatedBuilder( + animation: opacity, + builder: (context, child) { + return Opacity( + opacity: opacity.value, + child: Text( + AppLocalizations.of( + context, + )!.paiementScanCode, + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Colors.white, + ), ), - ), - ); - }, + ); + }, + ), ), ), - ), - ], + ], + ), ), - ), - // Qr code scanning zone - SizedBox(height: MediaQuery.of(context).size.width * 0.8), - Expanded( - child: Column( - children: [ - const Spacer(), - AsyncChild( - value: ongoingTransaction, - errorBuilder: (errorContext, child) => Padding( - padding: const EdgeInsets.symmetric(horizontal: 30), - child: GestureDetector( - child: Container( - width: double.infinity, - height: 50, - alignment: Alignment.center, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - color: Colors.white.withValues(alpha: 0.8), - ), - child: Text( - AppLocalizations.of(context)!.paiementNext, - style: TextStyle( - color: Colors.black, - fontWeight: FontWeight.bold, - fontSize: 20, + // Qr code scanning zone + SizedBox(height: MediaQuery.of(context).size.width * 0.8), + Expanded( + child: Column( + children: [ + const Spacer(), + AsyncChild( + value: ongoingTransaction, + errorBuilder: (errorContext, child) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 30), + child: GestureDetector( + child: Container( + width: double.infinity, + height: 50, + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Colors.white.withValues(alpha: 0.8), + ), + child: Text( + AppLocalizations.of(context)!.paiementNext, + style: TextStyle( + color: Colors.black, + fontWeight: FontWeight.bold, + fontSize: 20, + ), ), ), + onTap: () { + scannerKey.currentState?.resetScanner(); + barcodeNotifier.clearBarcode(); + ongoingTransactionNotifier + .clearOngoingTransaction(); + }, ), - onTap: () { - scannerKey.currentState?.resetScanner(); - barcodeNotifier.clearBarcode(); - ongoingTransactionNotifier.clearOngoingTransaction(); - }, ), - ), - builder: (context, transaction) => Padding( - padding: const EdgeInsets.symmetric(horizontal: 30), - child: Row( - children: [ - CancelButton( - onCancel: (bool isInTime) async { - if (isInTime) { - await showDialog( - context: context, - builder: (context) { - return CustomDialogBox( - title: AppLocalizations.of( - context, - )!.paiementCancelTransaction, - descriptions: - "${AppLocalizations.of(context)!.paiementTransactionCancelledDescription} ${formatter.format(transaction.total / 100)} € ?", - onYes: () async { - tokenExpireWrapper(ref, () async { - final value = - await transactionNotifier - .cancelTransaction( - transaction.id, + builder: (context, transaction) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 30), + child: Row( + children: [ + CancelButton( + onCancel: (bool isInTime) async { + if (isInTime) { + await showDialog( + context: context, + builder: (context) { + return CustomDialogBox( + title: AppLocalizations.of( + context, + )!.paiementCancelTransaction, + descriptions: + "${AppLocalizations.of(context)!.paiementTransactionCancelledDescription} ${formatter.format(transaction.total / 100)} € ?", + onYes: () async { + tokenExpireWrapper(ref, () async { + final value = + await transactionNotifier + .cancelTransaction( + transaction.id, + ); + value.when( + data: (value) { + if (value) { + displayToastWithContext( + TypeMsg.msg, + AppLocalizations.of( + context, + )!.paiementTransactionCancelled, ); - value.when( - data: (value) { - if (value) { - displayToastWithContext( - TypeMsg.msg, - AppLocalizations.of( - context, - )!.paiementTransactionCancelled, - ); - ref - .read( - ongoingTransactionProvider - .notifier, - ) + ref + .read( + ongoingTransactionProvider + .notifier, + ) + .clearOngoingTransaction(); + } else { + displayToastWithContext( + TypeMsg.error, + AppLocalizations.of( + context, + )!.paiementTransactionCancelledError, + ); + } + ongoingTransactionNotifier .clearOngoingTransaction(); - } else { + barcodeNotifier.clearBarcode(); + }, + error: (error, stack) { displayToastWithContext( TypeMsg.error, - AppLocalizations.of( - context, - )!.paiementTransactionCancelledError, + error.toString(), ); - } - ongoingTransactionNotifier - .clearOngoingTransaction(); - barcodeNotifier.clearBarcode(); - }, - error: (error, stack) { - displayToastWithContext( - TypeMsg.error, - error.toString(), - ); - }, - loading: () {}, - ); - }); - scannerKey.currentState?.resetScanner(); - }, - ); - }, - ); - } - }, - ), - const SizedBox(width: 20), - Expanded( - child: GestureDetector( - child: Container( - width: double.infinity, - height: 50, - alignment: Alignment.center, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - color: Colors.white.withValues(alpha: 0.8), - ), - child: Text( - AppLocalizations.of(context)!.paiementNext, - style: TextStyle( - color: Colors.black, - fontWeight: FontWeight.bold, - fontSize: 20, + }, + loading: () {}, + ); + }); + scannerKey.currentState + ?.resetScanner(); + }, + ); + }, + ); + } + }, + ), + const SizedBox(width: 20), + Expanded( + child: GestureDetector( + child: Container( + width: double.infinity, + height: 50, + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Colors.white.withValues(alpha: 0.8), + ), + child: Text( + AppLocalizations.of(context)!.paiementNext, + style: TextStyle( + color: Colors.black, + fontWeight: FontWeight.bold, + fontSize: 20, + ), ), ), + onTap: () { + scannerKey.currentState?.resetScanner(); + barcodeNotifier.clearBarcode(); + ongoingTransactionNotifier + .clearOngoingTransaction(); + }, ), - onTap: () { - scannerKey.currentState?.resetScanner(); - barcodeNotifier.clearBarcode(); - ongoingTransactionNotifier - .clearOngoingTransaction(); - }, ), - ), - ], + ], + ), ), + loadingBuilder: (context) => const SizedBox(), ), - loadingBuilder: (context) => const SizedBox(), - ), - const Spacer(), - ], + const Spacer(), + ], + ), ), - ), - ], - ), - ], + ], + ), + ], + ), ); } } diff --git a/lib/paiement/ui/pages/stats_page/sum_up_chart.dart b/lib/paiement/ui/pages/stats_page/sum_up_chart.dart index eb4a797f65..a02ea60e06 100644 --- a/lib/paiement/ui/pages/stats_page/sum_up_chart.dart +++ b/lib/paiement/ui/pages/stats_page/sum_up_chart.dart @@ -182,7 +182,7 @@ class SumUpChart extends HookConsumerWidget { child: Text( AppLocalizations.of( context, - )!.paiementNoTrasactionForThisMonth, + )!.paiementNoTransactionForThisMonth, style: TextStyle(fontSize: 18, color: Colors.grey), ), ); diff --git a/lib/paiement/ui/pages/stats_page/transaction_chart.dart b/lib/paiement/ui/pages/stats_page/transaction_chart.dart index 1a7dda50ae..9afb850429 100644 --- a/lib/paiement/ui/pages/stats_page/transaction_chart.dart +++ b/lib/paiement/ui/pages/stats_page/transaction_chart.dart @@ -65,7 +65,7 @@ class TransactionChart extends HookConsumerWidget { return chartPart.isEmpty ? Center( child: Text( - AppLocalizations.of(context)!.paiementNoTransactinon, + AppLocalizations.of(context)!.paiementNoTransaction, style: TextStyle(fontSize: 20, color: Colors.black54), ), ) diff --git a/lib/paiement/ui/pages/store_pages/add_edit_store.dart b/lib/paiement/ui/pages/store_pages/add_edit_store.dart index 2cbde0a984..5efe7a9783 100644 --- a/lib/paiement/ui/pages/store_pages/add_edit_store.dart +++ b/lib/paiement/ui/pages/store_pages/add_edit_store.dart @@ -41,7 +41,11 @@ class AddEditStorePage extends HookConsumerWidget { children: [ const SizedBox(height: 10), AlignLeftText( - "${isEdit ? AppLocalizations.of(context)!.paiementModify : AppLocalizations.of(context)!.paiementAdd}} ${AppLocalizations.of(context)!.paiementAStore} ${structure.name}", + isEdit + ? AppLocalizations.of( + context, + )!.paiementEditStore(store.name) + : AppLocalizations.of(context)!.paiementAddStore, padding: const EdgeInsets.symmetric(horizontal: 30), color: Colors.grey, ), diff --git a/lib/paiement/ui/pages/admin_page/add_store_card.dart b/lib/paiement/ui/pages/structure_admin_page/add_store_card.dart similarity index 92% rename from lib/paiement/ui/pages/admin_page/add_store_card.dart rename to lib/paiement/ui/pages/structure_admin_page/add_store_card.dart index 80fda502a2..c43c414c4b 100644 --- a/lib/paiement/ui/pages/admin_page/add_store_card.dart +++ b/lib/paiement/ui/pages/structure_admin_page/add_store_card.dart @@ -16,7 +16,9 @@ class AddStoreCard extends ConsumerWidget { onTap: () { storeNotifier.updateStore(Store.empty()); QR.to( - PaymentRouter.root + PaymentRouter.admin + PaymentRouter.addEditStore, + PaymentRouter.root + + PaymentRouter.structureStores + + PaymentRouter.addEditStore, ); }, child: Container( diff --git a/lib/paiement/ui/pages/admin_page/admin_store_card.dart b/lib/paiement/ui/pages/structure_admin_page/admin_store_card.dart similarity index 98% rename from lib/paiement/ui/pages/admin_page/admin_store_card.dart rename to lib/paiement/ui/pages/structure_admin_page/admin_store_card.dart index b0bbe20e4f..1f81751a1b 100644 --- a/lib/paiement/ui/pages/admin_page/admin_store_card.dart +++ b/lib/paiement/ui/pages/structure_admin_page/admin_store_card.dart @@ -60,7 +60,7 @@ class AdminStoreCard extends ConsumerWidget { storeNotifier.updateStore(store); QR.to( PaymentRouter.root + - PaymentRouter.admin + + PaymentRouter.structureStores + PaymentRouter.addEditStore, ); }, diff --git a/lib/paiement/ui/pages/admin_page/admin_page.dart b/lib/paiement/ui/pages/structure_admin_page/structure_admin_page.dart similarity index 82% rename from lib/paiement/ui/pages/admin_page/admin_page.dart rename to lib/paiement/ui/pages/structure_admin_page/structure_admin_page.dart index a020394d20..420614ac27 100644 --- a/lib/paiement/ui/pages/admin_page/admin_page.dart +++ b/lib/paiement/ui/pages/structure_admin_page/structure_admin_page.dart @@ -3,15 +3,15 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/paiement/providers/selected_structure_provider.dart'; import 'package:titan/paiement/providers/stores_list_provider.dart'; -import 'package:titan/paiement/ui/pages/admin_page/add_store_card.dart'; -import 'package:titan/paiement/ui/pages/admin_page/admin_store_card.dart'; +import 'package:titan/paiement/ui/pages/structure_admin_page/add_store_card.dart'; +import 'package:titan/paiement/ui/pages/structure_admin_page/admin_store_card.dart'; import 'package:titan/paiement/ui/paiement.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; import 'package:titan/tools/ui/widgets/align_left_text.dart'; -class AdminPage extends ConsumerWidget { - const AdminPage({super.key}); +class StructureStoresPage extends ConsumerWidget { + const StructureStoresPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -28,7 +28,9 @@ class AdminPage extends ConsumerWidget { children: [ const SizedBox(height: 10), AlignLeftText( - "${AppLocalizations.of(context)!} ${structure.name}", + AppLocalizations.of( + context, + )!.paiementStructureManagement(structure.name), color: Colors.grey, fontSize: 20, fontWeight: FontWeight.bold, diff --git a/lib/paiement/ui/paiement.dart b/lib/paiement/ui/paiement.dart index 84d6803542..c1f2773542 100644 --- a/lib/paiement/ui/paiement.dart +++ b/lib/paiement/ui/paiement.dart @@ -1,14 +1,15 @@ import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/paiement/router.dart'; import 'package:titan/tools/ui/widgets/top_bar.dart'; import 'package:titan/tools/constants.dart'; -class PaymentTemplate extends StatelessWidget { +class PaymentTemplate extends HookConsumerWidget { final Widget child; const PaymentTemplate({super.key, required this.child}); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { return Scaffold( body: Container( decoration: const BoxDecoration(color: ColorConstants.background), @@ -16,7 +17,7 @@ class PaymentTemplate extends StatelessWidget { child: Column( mainAxisAlignment: MainAxisAlignment.start, children: [ - const TopBar(root: PaymentRouter.root), + TopBar(root: PaymentRouter.root), Expanded(child: child), ], ), diff --git a/lib/phonebook/ui/pages/admin_page/association_admin_edition_modal.dart b/lib/phonebook/ui/pages/admin_page/association_admin_edition_modal.dart index c0c4e48710..3b26a2f8a1 100644 --- a/lib/phonebook/ui/pages/admin_page/association_admin_edition_modal.dart +++ b/lib/phonebook/ui/pages/admin_page/association_admin_edition_modal.dart @@ -12,7 +12,7 @@ import 'package:titan/phonebook/router.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; import 'package:titan/tools/ui/styleguide/button.dart'; -import 'package:titan/tools/ui/styleguide/custom_dialog_box.dart'; +import 'package:titan/tools/ui/styleguide/confirm_modal.dart'; class AssociationAdminEditionModal extends HookConsumerWidget { final Association association; diff --git a/lib/phonebook/ui/pages/association_members_page/member_edition_modal.dart b/lib/phonebook/ui/pages/association_members_page/member_edition_modal.dart index e25d11ab0d..a532040ecd 100644 --- a/lib/phonebook/ui/pages/association_members_page/member_edition_modal.dart +++ b/lib/phonebook/ui/pages/association_members_page/member_edition_modal.dart @@ -13,7 +13,7 @@ import 'package:titan/phonebook/tools/function.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; import 'package:titan/tools/ui/styleguide/button.dart'; -import 'package:titan/tools/ui/styleguide/custom_dialog_box.dart'; +import 'package:titan/tools/ui/styleguide/confirm_modal.dart'; class MemberEditionModal extends HookConsumerWidget { final CompleteMember member; diff --git a/lib/tools/ui/styleguide/button.dart b/lib/tools/ui/styleguide/button.dart index d9fd142337..cdd4c08d9b 100644 --- a/lib/tools/ui/styleguide/button.dart +++ b/lib/tools/ui/styleguide/button.dart @@ -102,6 +102,7 @@ class Button extends StatelessWidget { child: Center( child: Text( text, + textAlign: TextAlign.center, style: TextStyle( color: textColor, fontSize: fontSize ?? 18, diff --git a/lib/tools/ui/styleguide/custom_dialog_box.dart b/lib/tools/ui/styleguide/confirm_modal.dart similarity index 100% rename from lib/tools/ui/styleguide/custom_dialog_box.dart rename to lib/tools/ui/styleguide/confirm_modal.dart From 6d7ddc3c00696940edac662feb64a11633c1f444 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 24 Aug 2025 00:17:43 +0200 Subject: [PATCH 329/473] feat: all module page spacing --- lib/navigation/ui/all_module_page.dart | 105 ++++++++++++++----------- 1 file changed, 57 insertions(+), 48 deletions(-) diff --git a/lib/navigation/ui/all_module_page.dart b/lib/navigation/ui/all_module_page.dart index 9d8d78b706..865ee92cd8 100644 --- a/lib/navigation/ui/all_module_page.dart +++ b/lib/navigation/ui/all_module_page.dart @@ -45,60 +45,69 @@ class AllModulePage extends HookConsumerWidget { physics: const BouncingScrollPhysics(), controller: scrollController, child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 20.0), + padding: const EdgeInsets.all(20.0), child: Column( children: [ ...modules.map( - (module) => Row( - children: [ - GestureDetector( - onTap: () { - if (preferedModuleRootList.contains( - module.root, - )) { - preferedModuleRootListNotifier - .removePreferedModulesRoot(module.root); - } else if (preferedModuleRootList.length < - 2) { - preferedModuleRootListNotifier - .addPreferedModulesRoot(module.root); - } - }, - child: HeroIcon( - HeroIcons.bookmark, - style: - preferedModuleRootList.contains( - module.root, - ) - ? HeroIconStyle.solid - : HeroIconStyle.outline, - size: 20, - color: - preferedModuleRootList.contains( - module.root, - ) - ? ColorConstants.main - : ColorConstants.secondary, - ), - ), - SizedBox(width: 10), - Expanded( - child: ListItem( - title: module.getName(context), - subtitle: module.getDescription(context), + (module) => Padding( + padding: const EdgeInsets.symmetric(vertical: 5.0), + child: Row( + children: [ + GestureDetector( onTap: () { - navbarListModuleNotifier.pushModule(module); - final pathForwardingNotifier = ref.watch( - pathForwardingProvider.notifier, - ); - pathForwardingNotifier.forward(module.root); - - QR.to(module.root); - navbarVisibilityNotifier.show(); + if (preferedModuleRootList.contains( + module.root, + )) { + preferedModuleRootListNotifier + .removePreferedModulesRoot( + module.root, + ); + } else if (preferedModuleRootList.length < + 2) { + preferedModuleRootListNotifier + .addPreferedModulesRoot(module.root); + } }, + child: HeroIcon( + HeroIcons.bookmark, + style: + preferedModuleRootList.contains( + module.root, + ) + ? HeroIconStyle.solid + : HeroIconStyle.outline, + size: 20, + color: + preferedModuleRootList.contains( + module.root, + ) + ? ColorConstants.main + : ColorConstants.tertiary, + ), + ), + SizedBox(width: 20), + Expanded( + child: ListItem( + title: module.getName(context), + subtitle: module.getDescription(context), + onTap: () { + navbarListModuleNotifier.pushModule( + module, + ); + final pathForwardingNotifier = ref.watch( + pathForwardingProvider.notifier, + ); + pathForwardingNotifier.forward( + module.root, + ); + + QR.to(module.root); + navbarVisibilityNotifier.show(); + }, + ), ), - ), - ], + ], + ), ), ), ], From be18c0f654a853eee07a4af1b77088f86dc7ecba Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 24 Aug 2025 00:17:43 +0200 Subject: [PATCH 330/473] feat: admin home page spacing --- lib/admin/ui/pages/main_page/main_page.dart | 155 +++++++++++--------- 1 file changed, 87 insertions(+), 68 deletions(-) diff --git a/lib/admin/ui/pages/main_page/main_page.dart b/lib/admin/ui/pages/main_page/main_page.dart index e418b2034c..c039670041 100644 --- a/lib/admin/ui/pages/main_page/main_page.dart +++ b/lib/admin/ui/pages/main_page/main_page.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/navigation/ui/scroll_to_hide_navbar.dart'; import 'package:titan/tools/functions.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/admin/admin.dart'; @@ -22,76 +23,94 @@ class AdminMainPage extends HookConsumerWidget { return AdminTemplate( child: Padding( - padding: const EdgeInsets.all(40), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - localizeWithContext.adminAdministration, - style: Theme.of(context).textTheme.headlineMedium, - ), - const SizedBox(height: 20), - Text( - localizeWithContext.adminUsersAndGroups, - style: Theme.of(context).textTheme.titleLarge, - ), - ListItem( - title: localizeWithContext.adminUsersManagement, - subtitle: localizeWithContext.adminUsersManagementDescription, - onTap: () async { - await showCustomBottomModal( - context: context, - ref: ref, - modal: BottomModalTemplate( - title: localizeWithContext.adminUsersManagement, - child: UsersManagementPage(), + padding: const EdgeInsets.all(20), + child: ScrollToHideNavbar( + controller: ScrollController(), + child: SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + localizeWithContext.adminAdministration, + style: Theme.of(context).textTheme.headlineMedium, + ), + const SizedBox(height: 20), + Text( + localizeWithContext.adminUsersAndGroups, + style: Theme.of(context).textTheme.titleLarge, + ), + const SizedBox(height: 10), + ListItem( + title: localizeWithContext.adminUsersManagement, + subtitle: localizeWithContext.adminUsersManagementDescription, + onTap: () async { + await showCustomBottomModal( + context: context, + ref: ref, + modal: BottomModalTemplate( + title: localizeWithContext.adminUsersManagement, + child: UsersManagementPage(), + ), + ); + }, + ), + const SizedBox(height: 10), + ListItem( + title: localizeWithContext.adminGroupsManagement, + subtitle: localizeWithContext.adminManageUserGroups, + onTap: () => + QR.to(AdminRouter.root + AdminRouter.usersGroups), + ), + const SizedBox(height: 10), + ListItem( + title: localizeWithContext.adminGroupNotification, + subtitle: localizeWithContext.adminSendNotificationToGroup, + onTap: () => + QR.to(AdminRouter.root + AdminRouter.groupNotification), + ), + const SizedBox(height: 20), + Text( + localizeWithContext.adminPaiementModule, + style: Theme.of(context).textTheme.titleLarge, + ), + const SizedBox(height: 10), + ListItem( + title: getPaymentName(), + subtitle: localizeWithContext.adminManagePaiementStructures, + onTap: () => QR.to(AdminRouter.root + AdminRouter.structures), + ), + const SizedBox(height: 20), + Text( + localizeWithContext.adminAssociationMembership, + style: Theme.of(context).textTheme.titleLarge, + ), + const SizedBox(height: 10), + ListItem( + title: localizeWithContext.adminAssociationMembership, + subtitle: localizeWithContext + .adminManageUsersAssociationMemberships, + onTap: () => QR.to( + AdminRouter.root + AdminRouter.associationMemberships, ), - ); - }, - ), - ListItem( - title: localizeWithContext.adminGroupsManagement, - subtitle: localizeWithContext.adminManageUserGroups, - onTap: () => QR.to(AdminRouter.root + AdminRouter.usersGroups), - ), - ListItem( - title: localizeWithContext.adminGroupNotification, - subtitle: localizeWithContext.adminSendNotificationToGroup, - onTap: () => - QR.to(AdminRouter.root + AdminRouter.groupNotification), - ), - Text( - localizeWithContext.adminPaiementModule, - style: Theme.of(context).textTheme.titleLarge, - ), - ListItem( - title: getPaymentName(), - subtitle: localizeWithContext.adminManagePaiementStructures, - onTap: () => QR.to(AdminRouter.root + AdminRouter.structures), - ), - Text( - localizeWithContext.adminAssociationMembership, - style: Theme.of(context).textTheme.titleLarge, - ), - ListItem( - title: localizeWithContext.adminAssociationMembership, - subtitle: - localizeWithContext.adminManageUsersAssociationMemberships, - onTap: () => - QR.to(AdminRouter.root + AdminRouter.associationMemberships), - ), - Text( - localizeWithContext.adminAssociations, - style: Theme.of(context).textTheme.titleLarge, - ), - ListItem( - title: localizeWithContext.adminAssociations, - subtitle: localizeWithContext.adminManageAssociations, - onTap: () { - QR.to(AdminRouter.root + AdminRouter.association); - }, + ), + const SizedBox(height: 20), + Text( + localizeWithContext.adminAssociations, + style: Theme.of(context).textTheme.titleLarge, + ), + const SizedBox(height: 10), + ListItem( + title: localizeWithContext.adminAssociations, + subtitle: localizeWithContext.adminManageAssociations, + onTap: () { + QR.to(AdminRouter.root + AdminRouter.association); + }, + ), + const SizedBox(height: 20), + ], ), - ], + ), ), ), ); From 510979b73a5fbe8199d689d16c853984a46e6e8d Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 24 Aug 2025 00:17:44 +0200 Subject: [PATCH 331/473] feat: group page spacing --- .../pages/groups/groups_page/groups_page.dart | 266 +++++++++--------- 1 file changed, 134 insertions(+), 132 deletions(-) diff --git a/lib/admin/ui/pages/groups/groups_page/groups_page.dart b/lib/admin/ui/pages/groups/groups_page/groups_page.dart index 4a9f90eb4c..d61438321a 100644 --- a/lib/admin/ui/pages/groups/groups_page/groups_page.dart +++ b/lib/admin/ui/pages/groups/groups_page/groups_page.dart @@ -52,7 +52,7 @@ class GroupsPage extends HookConsumerWidget { await groupsNotifier.loadGroups(); }, child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 30.0), + padding: const EdgeInsets.symmetric(horizontal: 20.0), child: Column( children: [ const SizedBox(height: 20), @@ -134,7 +134,7 @@ class GroupsPage extends HookConsumerWidget { ), ], ), - const SizedBox(height: 30), + const SizedBox(height: 10), AsyncChild( value: groups, builder: (context, g) { @@ -145,125 +145,127 @@ class GroupsPage extends HookConsumerWidget { return Column( children: [ ...g.map( - (group) => ListItem( - title: group.name, - subtitle: group.description, - onTap: () async { - await showCustomBottomModal( - context: context, - ref: ref, - modal: BottomModalTemplate( - title: group.name, - child: Column( - children: [ - Button( - text: localizeWithContext.adminEdit, - onPressed: () async { - nameController.text = group.name; - descController.text = - group.description; - Navigator.pop(context); - await showCustomBottomModal( - context: context, - ref: ref, - modal: BottomModalTemplate( - title: localizeWithContext - .adminEditGroup, - child: Column( - children: [ - TextEntry( - label: localizeWithContext - .adminName, - controller: nameController, - ), - const SizedBox(height: 20), - TextEntry( - label: localizeWithContext - .adminDescription, - controller: descController, - ), - const SizedBox(height: 20), - Button( - text: localizeWithContext - .adminEdit, - onPressed: () async { - final addedGroupMsg = - AppLocalizations.of( - context, - )!.adminAddedGroup; - final addingErrorMsg = - AppLocalizations.of( - context, - )!.adminAddingError; - await tokenExpireWrapper( - ref, - () async { - final value = await groupListNotifier - .updateGroup( - SimpleGroup( - name: - nameController - .text, - description: - descController - .text, - id: group.id, - ), + (group) => Padding( + padding: const EdgeInsets.symmetric(vertical: 5.0), + child: ListItem( + title: group.name, + subtitle: group.description, + onTap: () async { + await showCustomBottomModal( + context: context, + ref: ref, + modal: BottomModalTemplate( + title: group.name, + child: Column( + children: [ + Button( + text: localizeWithContext.adminEdit, + onPressed: () async { + nameController.text = group.name; + descController.text = + group.description; + Navigator.pop(context); + await showCustomBottomModal( + context: context, + ref: ref, + modal: BottomModalTemplate( + title: localizeWithContext + .adminEditGroup, + child: Column( + children: [ + TextEntry( + label: localizeWithContext + .adminName, + controller: + nameController, + ), + const SizedBox(height: 20), + TextEntry( + label: localizeWithContext + .adminDescription, + controller: + descController, + ), + const SizedBox(height: 20), + Button( + text: localizeWithContext + .adminEdit, + onPressed: () async { + final addedGroupMsg = + AppLocalizations.of( + context, + )!.adminAddedGroup; + final addingErrorMsg = + AppLocalizations.of( + context, + )!.adminAddingError; + await tokenExpireWrapper( + ref, + () async { + final value = await groupListNotifier + .updateGroup( + SimpleGroup( + name: nameController + .text, + description: + descController + .text, + id: group + .id, + ), + ); + if (value) { + QR.back(); + displayToastWithContext( + TypeMsg.msg, + addedGroupMsg, + ); + } else { + displayToastWithContext( + TypeMsg.error, + addingErrorMsg, ); - if (value) { - QR.back(); - displayToastWithContext( - TypeMsg.msg, - addedGroupMsg, - ); - } else { - displayToastWithContext( - TypeMsg.error, - addingErrorMsg, - ); - } - }, - ); - }, - ), - ], + } + }, + ); + }, + ), + ], + ), ), - ), - ); - }, - ), - const SizedBox(height: 20), - Button( - text: localizeWithContext - .adminManageMembers, - onPressed: () { - Navigator.pop(context); - groupIdNotifier.setId(group.id); - QR.to( - AdminRouter.root + - AdminRouter.usersGroups + - AdminRouter.editGroup, - ); - }, - ), - const SizedBox(height: 20), - Button( - text: localizeWithContext - .adminDeleteGroup, - type: ButtonType.danger, - onPressed: () async { - await showDialog( - context: context, - builder: (context) { - return CustomDialogBox( - title: localizeWithContext - .adminDelete, - descriptions: localizeWithContext - .adminDeleteGroupConfirmation, - onYes: () async { - tokenExpireWrapper( - ref, - () async { + ); + }, + ), + const SizedBox(height: 20), + Button( + text: localizeWithContext + .adminManageMembers, + onPressed: () { + Navigator.pop(context); + groupIdNotifier.setId(group.id); + QR.to( + AdminRouter.root + + AdminRouter.usersGroups + + AdminRouter.editGroup, + ); + }, + ), + const SizedBox(height: 20), + Button( + text: localizeWithContext + .adminDeleteGroup, + type: ButtonType.danger, + onPressed: () async { + await showDialog( + context: context, + builder: (context) { + return CustomDialogBox( + title: localizeWithContext + .adminDelete, + descriptions: localizeWithContext + .adminDeleteGroupConfirmation, + onYes: () async { + tokenExpireWrapper(ref, () async { final value = await groupsNotifier .deleteGroup( @@ -282,20 +284,20 @@ class GroupsPage extends HookConsumerWidget { .adminFailedToDeleteGroup, ); } - }, - ); - }, - ); - }, - ); - navigatorWithContext.pop(); - }, - ), - ], + }); + }, + ); + }, + ); + navigatorWithContext.pop(); + }, + ), + ], + ), ), - ), - ); - }, + ); + }, + ), ), ), const SizedBox(height: 20), From 7e858c6b6cb5f4a18810c272a41ff343f0f6d6de Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 24 Aug 2025 00:17:44 +0200 Subject: [PATCH 332/473] feat: administration title style --- lib/admin/ui/pages/main_page/main_page.dart | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/admin/ui/pages/main_page/main_page.dart b/lib/admin/ui/pages/main_page/main_page.dart index c039670041..e59220c545 100644 --- a/lib/admin/ui/pages/main_page/main_page.dart +++ b/lib/admin/ui/pages/main_page/main_page.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/navigation/ui/scroll_to_hide_navbar.dart'; +import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/admin/admin.dart'; @@ -33,7 +34,11 @@ class AdminMainPage extends HookConsumerWidget { children: [ Text( localizeWithContext.adminAdministration, - style: Theme.of(context).textTheme.headlineMedium, + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: ColorConstants.title, + ), ), const SizedBox(height: 20), Text( From 27b78bf2e3c5093ba0609d1ad2ca70d9da7ef13a Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 24 Aug 2025 00:17:44 +0200 Subject: [PATCH 333/473] feat: better searchbar ui --- lib/tools/ui/styleguide/searchbar.dart | 40 +++++++++++++++++++------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/lib/tools/ui/styleguide/searchbar.dart b/lib/tools/ui/styleguide/searchbar.dart index 1f8ed456dc..63aa3e0bb2 100644 --- a/lib/tools/ui/styleguide/searchbar.dart +++ b/lib/tools/ui/styleguide/searchbar.dart @@ -8,32 +8,49 @@ class CustomSearchBar extends HookWidget { final Function()? onFilter; final Function(String) onSearch; final bool autofocus; - final FocusNode? focusNode; const CustomSearchBar({ super.key, this.hintText = 'Rechercher', this.onFilter, required this.onSearch, this.autofocus = false, - this.focusNode, }); @override Widget build(BuildContext context) { final textController = useTextEditingController(); + final focusNode = useFocusNode(); - return Material( - elevation: 4, - borderRadius: BorderRadius.circular(50), - color: ColorConstants.background, + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(50), + color: ColorConstants.background, + boxShadow: [ + BoxShadow( + color: ColorConstants.onTertiary.withAlpha(30), + blurRadius: 6, + spreadRadius: 1, + offset: const Offset(0, 2), + ), + ], + ), child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 4.0), + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 3.0), child: Row( children: [ - HeroIcon( - HeroIcons.magnifyingGlass, - color: ColorConstants.tertiary, - size: 24, + GestureDetector( + onTap: () { + if (textController.text.isEmpty) { + focusNode.requestFocus(); + } else { + onSearch(textController.text); + } + }, + child: HeroIcon( + HeroIcons.magnifyingGlass, + color: ColorConstants.tertiary, + size: 24, + ), ), const SizedBox(width: 8), Expanded( @@ -45,6 +62,7 @@ class CustomSearchBar extends HookWidget { onSearch(value); }, style: TextStyle(color: ColorConstants.tertiary, fontSize: 16), + cursorColor: ColorConstants.tertiary, decoration: InputDecoration( hintText: hintText, hintStyle: TextStyle( From 40ee4598e62565d07e5ba8dbe874eed522fb95ec Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 24 Aug 2025 00:17:45 +0200 Subject: [PATCH 334/473] feat: space before searchbar --- lib/admin/ui/pages/groups/edit_group_page/search_user.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/admin/ui/pages/groups/edit_group_page/search_user.dart b/lib/admin/ui/pages/groups/edit_group_page/search_user.dart index 7ac8bfb2ea..24009e5797 100644 --- a/lib/admin/ui/pages/groups/edit_group_page/search_user.dart +++ b/lib/admin/ui/pages/groups/edit_group_page/search_user.dart @@ -39,6 +39,7 @@ class SearchUser extends HookConsumerWidget { builder: (context, g) { return Column( children: [ + const SizedBox(height: 20), Row( children: [ Expanded( From f71453ebc5abcd538b1f02672413946aeb79ab6b Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 24 Aug 2025 00:17:45 +0200 Subject: [PATCH 335/473] feat: padding in edit group --- lib/admin/ui/pages/groups/edit_group_page/edit_group_page.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/admin/ui/pages/groups/edit_group_page/edit_group_page.dart b/lib/admin/ui/pages/groups/edit_group_page/edit_group_page.dart index 8261493875..76b16c7fc9 100644 --- a/lib/admin/ui/pages/groups/edit_group_page/edit_group_page.dart +++ b/lib/admin/ui/pages/groups/edit_group_page/edit_group_page.dart @@ -30,7 +30,7 @@ class EditGroupPage extends ConsumerWidget { child: SingleChildScrollView( physics: const BouncingScrollPhysics(), child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 30.0), + padding: const EdgeInsets.symmetric(horizontal: 20.0), child: SingleAutoLoaderChild( item: group, notifier: groupFromSimpleGroupNotifier, From 5e77bce5f1b161769213061d425c4a4ab327a046 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 24 Aug 2025 00:17:46 +0200 Subject: [PATCH 336/473] feat: search user modal --- lib/admin/ui/pages/groups/edit_group_page/search_user.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/admin/ui/pages/groups/edit_group_page/search_user.dart b/lib/admin/ui/pages/groups/edit_group_page/search_user.dart index 24009e5797..8fcc0629a0 100644 --- a/lib/admin/ui/pages/groups/edit_group_page/search_user.dart +++ b/lib/admin/ui/pages/groups/edit_group_page/search_user.dart @@ -66,7 +66,7 @@ class SearchUser extends HookConsumerWidget { modal: BottomModalTemplate( title: localizeWithContext.adminAddMember, child: ConstrainedBox( - constraints: const BoxConstraints(maxHeight: 300), + constraints: const BoxConstraints(maxHeight: 350), child: Column( children: [ CustomSearchBar( @@ -81,8 +81,10 @@ class SearchUser extends HookConsumerWidget { } }, ), + const SizedBox(height: 10), Expanded( child: SingleChildScrollView( + physics: const BouncingScrollPhysics(), child: const MemberResults(), ), ), From 545d53e592a9824c76031aa2532a79f1e7f1eb30 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 24 Aug 2025 00:17:46 +0200 Subject: [PATCH 337/473] feat: group notification spacing --- .../group_notification_page.dart | 125 +++++++++--------- 1 file changed, 65 insertions(+), 60 deletions(-) diff --git a/lib/admin/ui/pages/group_notifification_page/group_notification_page.dart b/lib/admin/ui/pages/group_notifification_page/group_notification_page.dart index cfb4a63cad..159869c7b8 100644 --- a/lib/admin/ui/pages/group_notifification_page/group_notification_page.dart +++ b/lib/admin/ui/pages/group_notifification_page/group_notification_page.dart @@ -36,7 +36,7 @@ class GroupNotificationPage extends HookConsumerWidget { await groupsNotifier.loadGroups(); }, child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 30.0), + padding: const EdgeInsets.symmetric(horizontal: 20.0), child: Column( children: [ const SizedBox(height: 20), @@ -51,7 +51,7 @@ class GroupNotificationPage extends HookConsumerWidget { ), ), ), - const SizedBox(height: 30), + const SizedBox(height: 10), AsyncChild( value: groups, builder: (context, g) { @@ -64,68 +64,73 @@ class GroupNotificationPage extends HookConsumerWidget { Column( children: [ ...g.map( - (group) => ListItem( - title: group.name, - subtitle: group.description, - onTap: () async { - await showCustomBottomModal( - context: context, - ref: ref, - modal: BottomModalTemplate( - title: localizeWithContext.adminNotifyGroup( - group.name, - ), - child: Column( - children: [ - TextEntry( - label: localizeWithContext.adminTitle, - controller: titleController, - ), - const SizedBox(height: 20), - TextEntry( - label: - localizeWithContext.adminContent, - controller: contentController, - maxLines: 5, - ), - const SizedBox(height: 20), - Button( - text: localizeWithContext.adminSend, - onPressed: () { - notificationRepository - .sendNotification( - group.id, - titleController.text, - contentController.text, - ) - .then((value) { - if (value) { - displayToastWithContext( - TypeMsg.msg, - localizeWithContext - .adminNotificationSent, - ); - } else { + (group) => Padding( + padding: const EdgeInsets.symmetric( + vertical: 5.0, + ), + child: ListItem( + title: group.name, + subtitle: group.description, + onTap: () async { + await showCustomBottomModal( + context: context, + ref: ref, + modal: BottomModalTemplate( + title: localizeWithContext + .adminNotifyGroup(group.name), + child: Column( + children: [ + TextEntry( + label: + localizeWithContext.adminTitle, + controller: titleController, + ), + const SizedBox(height: 20), + TextEntry( + label: localizeWithContext + .adminContent, + controller: contentController, + maxLines: 5, + ), + const SizedBox(height: 20), + Button( + text: localizeWithContext.adminSend, + onPressed: () { + notificationRepository + .sendNotification( + group.id, + titleController.text, + contentController.text, + ) + .then((value) { + if (value) { + displayToastWithContext( + TypeMsg.msg, + localizeWithContext + .adminNotificationSent, + ); + } else { + displayToastWithContext( + TypeMsg.error, + localizeWithContext + .adminFailedToSendNotification, + ); + } + }) + .catchError((error) { displayToastWithContext( TypeMsg.error, - localizeWithContext - .adminFailedToSendNotification, + error.toString(), ); - } - }) - .catchError((error) { - displayToastWithContext( - TypeMsg.error, - error.toString(), - ); - }); - }, - ), - ], + }); + }, + ), + ], + ), ), - ), - ); - }, + ); + }, + ), ), ), const SizedBox(height: 20), From 8202c000b2c0d0ba5a6f8a567864dd8143a19719 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 24 Aug 2025 00:17:46 +0200 Subject: [PATCH 338/473] feat: better search user --- .../search_result.dart | 39 +++++++++++++------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/lib/admin/ui/pages/structure_page/add_edit_structure_page/search_result.dart b/lib/admin/ui/pages/structure_page/add_edit_structure_page/search_result.dart index db5142ba49..9d30ca97f6 100644 --- a/lib/admin/ui/pages/structure_page/add_edit_structure_page/search_result.dart +++ b/lib/admin/ui/pages/structure_page/add_edit_structure_page/search_result.dart @@ -1,7 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/providers/structure_manager_provider.dart'; -import 'package:titan/tools/ui/styleguide/list_item_template.dart'; +import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/user/providers/user_list_provider.dart'; class SearchResult extends HookConsumerWidget { @@ -16,25 +17,39 @@ class SearchResult extends HookConsumerWidget { structureManagerProvider.notifier, ); - return users.when( - data: (usersData) { + return AsyncChild( + value: users, + builder: (context, usersData) { return Column( children: usersData .map( - (user) => ListItemTemplate( - title: user.getName(), - onTap: () { - structureManagerNotifier.setUser(user); - usersNotifier.clear(); - Navigator.of(context).pop(); - }, + (user) => Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + user.getName(), + style: const TextStyle(fontSize: 15), + overflow: TextOverflow.ellipsis, + ), + ), + GestureDetector( + onTap: () { + structureManagerNotifier.setUser(user); + usersNotifier.clear(); + Navigator.of(context).pop(); + }, + child: const HeroIcon(HeroIcons.plus), + ), + ], + ), ), ) .toList(), ); }, - loading: () => const Center(child: CircularProgressIndicator()), - error: (e, s) => Text(e.toString()), ); } } From 39663b7ce8e0ce1083a1a14250750e8a6dd64f04 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 24 Aug 2025 00:18:48 +0200 Subject: [PATCH 339/473] feat: displaying current selected manager --- .../add_edit_structure_page/add_edit_structure_page.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/admin/ui/pages/structure_page/add_edit_structure_page/add_edit_structure_page.dart b/lib/admin/ui/pages/structure_page/add_edit_structure_page/add_edit_structure_page.dart index 5ffa4dc2e1..a423c51956 100644 --- a/lib/admin/ui/pages/structure_page/add_edit_structure_page/add_edit_structure_page.dart +++ b/lib/admin/ui/pages/structure_page/add_edit_structure_page/add_edit_structure_page.dart @@ -9,6 +9,7 @@ import 'package:titan/admin/providers/structure_manager_provider.dart'; import 'package:titan/admin/providers/structure_provider.dart'; import 'package:titan/paiement/class/structure.dart'; import 'package:titan/paiement/providers/structure_list_provider.dart'; +import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; @@ -70,7 +71,7 @@ class AddEditStructurePage extends HookConsumerWidget { return AdminTemplate( child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 30.0), + padding: const EdgeInsets.symmetric(horizontal: 20.0), child: SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics( parent: BouncingScrollPhysics(), @@ -231,7 +232,9 @@ class AddEditStructurePage extends HookConsumerWidget { ], ) : ListItem( - title: localizeWithContext.adminSelectManager, + title: structureManager.id.isNotEmpty + ? structureManager.getName() + : localizeWithContext.adminSelectManager, subtitle: structureManager.getName(), onTap: () async { await showCustomBottomModal( From 844365e339edf4b3cc8599ffd9b32ef85bb5e2dc Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 24 Aug 2025 00:20:17 +0200 Subject: [PATCH 340/473] feat: payment page spacing --- .../ui/pages/structure_page/structure_page.dart | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/admin/ui/pages/structure_page/structure_page.dart b/lib/admin/ui/pages/structure_page/structure_page.dart index e0e4c13bfd..9afde99fc0 100644 --- a/lib/admin/ui/pages/structure_page/structure_page.dart +++ b/lib/admin/ui/pages/structure_page/structure_page.dart @@ -8,6 +8,7 @@ import 'package:titan/admin/providers/structure_provider.dart'; import 'package:titan/paiement/class/structure.dart'; import 'package:titan/paiement/providers/bank_account_holder_provider.dart'; import 'package:titan/paiement/providers/structure_list_provider.dart'; +import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; import 'package:titan/tools/ui/styleguide/button.dart'; @@ -53,7 +54,7 @@ class StructurePage extends HookConsumerWidget { await structuresNotifier.getStructures(); }, child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 30.0), + padding: const EdgeInsets.symmetric(horizontal: 20.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -62,7 +63,11 @@ class StructurePage extends HookConsumerWidget { children: [ Text( localizeWithContext.adminStructures, - style: Theme.of(context).textTheme.headlineMedium, + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: ColorConstants.title, + ), ), const Spacer(), CustomIconButton( @@ -83,7 +88,7 @@ class StructurePage extends HookConsumerWidget { ), ], ), - const SizedBox(height: 30), + const SizedBox(height: 10), Async2Children( values: Tuple2(structures, bankAccountHolder), builder: (context, structures, bankAccountHolder) { @@ -110,7 +115,9 @@ class StructurePage extends HookConsumerWidget { const SizedBox(height: 10), ...structures.map( (structure) => Padding( - padding: const EdgeInsets.symmetric(vertical: 2), + padding: const EdgeInsets.symmetric( + vertical: 5.0, + ), child: ListItem( title: structure.name, subtitle: structure.managerUser.getName(), From 4c4ee872c69548ad4af3d7252c0cd66a8517a825 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 24 Aug 2025 00:20:18 +0200 Subject: [PATCH 341/473] feat: reordering search modal --- .../add_edit_structure_page/user_search_modal.dart | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/admin/ui/pages/structure_page/add_edit_structure_page/user_search_modal.dart b/lib/admin/ui/pages/structure_page/add_edit_structure_page/user_search_modal.dart index 9b10f49bc5..b6c35f61c5 100644 --- a/lib/admin/ui/pages/structure_page/add_edit_structure_page/user_search_modal.dart +++ b/lib/admin/ui/pages/structure_page/add_edit_structure_page/user_search_modal.dart @@ -23,12 +23,6 @@ class UserSearchModal extends HookConsumerWidget { type: BottomModalType.main, child: Column( children: [ - ConstrainedBox( - constraints: const BoxConstraints(maxHeight: 280), - child: SingleChildScrollView( - child: SearchResult(queryController: textController), - ), - ), CustomSearchBar( autofocus: true, onSearch: (value) => tokenExpireWrapper(ref, () async { @@ -41,6 +35,14 @@ class UserSearchModal extends HookConsumerWidget { } }), ), + const SizedBox(height: 10), + ConstrainedBox( + constraints: const BoxConstraints(maxHeight: 280), + child: SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: SearchResult(queryController: textController), + ), + ), ], ), ); From 249328310153b311efd51e5694ea0cd4fb71d534 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 24 Aug 2025 00:20:19 +0200 Subject: [PATCH 342/473] fix: removing useless row --- .../pages/groups/edit_group_page/results.dart | 80 +++++++++---------- 1 file changed, 37 insertions(+), 43 deletions(-) diff --git a/lib/admin/ui/pages/groups/edit_group_page/results.dart b/lib/admin/ui/pages/groups/edit_group_page/results.dart index 4a6351928d..fc43f53c2e 100644 --- a/lib/admin/ui/pages/groups/edit_group_page/results.dart +++ b/lib/admin/ui/pages/groups/edit_group_page/results.dart @@ -45,49 +45,43 @@ class MemberResults extends HookConsumerWidget { overflow: TextOverflow.ellipsis, ), ), - Row( - children: [ - WaitingButton( - onTap: () async { - if (!group.value!.members.contains(e)) { - Group newGroup = group.value!.copyWith( - members: group.value!.members + [e], - ); - final addedMemberMsg = AppLocalizations.of( - context, - )!.adminAddedMember; - final addingErrorMsg = AppLocalizations.of( - context, - )!.adminAddingError; - await tokenExpireWrapper(ref, () async { - groupNotifier.addMember(newGroup, e).then(( - result, - ) { - if (result) { - simpleGroupGroupsNotifier.setTData( - newGroup.id, - AsyncData([newGroup]), - ); - value.remove(e); - displayToastWithContext( - TypeMsg.msg, - addedMemberMsg, - ); - } else { - displayToastWithContext( - TypeMsg.error, - addingErrorMsg, - ); - } - }); - }); - } - }, - waitingColor: ColorConstants.main, - builder: (child) => child, - child: const HeroIcon(HeroIcons.plus), - ), - ], + WaitingButton( + onTap: () async { + if (!group.value!.members.contains(e)) { + Group newGroup = group.value!.copyWith( + members: group.value!.members + [e], + ); + final addedMemberMsg = AppLocalizations.of( + context, + )!.adminAddedMember; + final addingErrorMsg = AppLocalizations.of( + context, + )!.adminAddingError; + await tokenExpireWrapper(ref, () async { + groupNotifier.addMember(newGroup, e).then((result) { + if (result) { + simpleGroupGroupsNotifier.setTData( + newGroup.id, + AsyncData([newGroup]), + ); + value.remove(e); + displayToastWithContext( + TypeMsg.msg, + addedMemberMsg, + ); + } else { + displayToastWithContext( + TypeMsg.error, + addingErrorMsg, + ); + } + }); + }); + } + }, + waitingColor: ColorConstants.main, + builder: (child) => child, + child: const HeroIcon(HeroIcons.plus), ), ], ), From 066e5c1ce012a41764ce2c2d183376a0874874b8 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 24 Aug 2025 00:20:19 +0200 Subject: [PATCH 343/473] feat: better add membership modal --- .../add_membership_modal.dart | 81 ++++++++++++------- 1 file changed, 53 insertions(+), 28 deletions(-) diff --git a/lib/admin/ui/pages/membership/association_membership_page/add_membership_modal.dart b/lib/admin/ui/pages/membership/association_membership_page/add_membership_modal.dart index 749900f1b3..b126b4ea80 100644 --- a/lib/admin/ui/pages/membership/association_membership_page/add_membership_modal.dart +++ b/lib/admin/ui/pages/membership/association_membership_page/add_membership_modal.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:heroicons/heroicons.dart'; import 'package:titan/admin/class/simple_group.dart'; import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; @@ -41,38 +42,62 @@ class AddMembershipModal extends HookWidget { ), Padding( padding: const EdgeInsets.all(8.0), - child: chosenGroup.value == null - ? ListItem( - title: localizeWithContext.adminChooseGroupManager, - onTap: () async { - FocusScope.of(context).unfocus(); - final ctx = context; - await Future.delayed(Duration(milliseconds: 150)); - if (!ctx.mounted) return; + child: ListItem( + title: chosenGroup.value == null + ? localizeWithContext.adminChooseGroupManager + : chosenGroup.value!.name, + onTap: () async { + FocusScope.of(context).unfocus(); + final ctx = context; + await Future.delayed(Duration(milliseconds: 150)); + if (!ctx.mounted) return; - await showCustomBottomModal( - context: ctx, - ref: ref, - modal: BottomModalTemplate( - title: localizeWithContext.adminChooseGroupManager, - child: Column( - children: [ - ...groups.map( - (e) => ListItem( - title: e.name, - onTap: () { - chosenGroup.value = e; - Navigator.of(ctx).pop(); - }, + await showCustomBottomModal( + context: ctx, + ref: ref, + modal: BottomModalTemplate( + title: localizeWithContext.adminChooseGroupManager, + child: ConstrainedBox( + constraints: const BoxConstraints(maxHeight: 280), + child: SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: Column( + children: [ + ...groups.map( + (e) => Padding( + padding: const EdgeInsets.symmetric( + vertical: 8.0, + ), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + e.name, + style: const TextStyle(fontSize: 15), + overflow: TextOverflow.ellipsis, + ), + ), + GestureDetector( + onTap: () { + chosenGroup.value = e; + Navigator.of(ctx).pop(); + }, + child: const HeroIcon(HeroIcons.plus), + ), + ], ), ), - ], - ), + ), + ], ), - ); - }, - ) - : Text(chosenGroup.value!.name), + ), + ), + ), + ); + }, + ), ), const SizedBox(height: 10), Button( From ea87bdc596ea891495229e76c9774aa8bc71a0e1 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 24 Aug 2025 00:20:19 +0200 Subject: [PATCH 344/473] feat: better membership page --- .../association_membership_page.dart | 181 ++++++++++-------- 1 file changed, 96 insertions(+), 85 deletions(-) diff --git a/lib/admin/ui/pages/membership/association_membership_page/association_membership_page.dart b/lib/admin/ui/pages/membership/association_membership_page/association_membership_page.dart index 5941407b4f..79ab71b5f9 100644 --- a/lib/admin/ui/pages/membership/association_membership_page/association_membership_page.dart +++ b/lib/admin/ui/pages/membership/association_membership_page/association_membership_page.dart @@ -58,7 +58,7 @@ class AssociationMembershipsPage extends HookConsumerWidget { await associationMembershipsNotifier.loadAssociationMemberships(); }, child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 30.0), + padding: const EdgeInsets.symmetric(horizontal: 20.0), child: Column( children: [ const SizedBox(height: 20), @@ -66,7 +66,11 @@ class AssociationMembershipsPage extends HookConsumerWidget { children: [ Text( AppLocalizations.of(context)!.adminAssociationMembership, - style: Theme.of(context).textTheme.headlineMedium, + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: ColorConstants.title, + ), ), const Spacer(), CustomIconButton( @@ -112,7 +116,7 @@ class AssociationMembershipsPage extends HookConsumerWidget { ), ], ), - SizedBox(height: 30), + SizedBox(height: 10), AsyncChild( value: associationsMemberships, builder: (context, g) { @@ -125,89 +129,96 @@ class AssociationMembershipsPage extends HookConsumerWidget { Column( children: [ ...g.map( - (associationMembership) => ListItem( - title: associationMembership.name, - onTap: () async { - await showCustomBottomModal( - context: context, - ref: ref, - modal: BottomModalTemplate( - title: associationMembership.name, - child: Column( - children: [ - Button( - text: localizeWithContext.adminEdit, - onPressed: () { - associationMembershipMembersNotifier - .loadAssociationMembershipMembers( - associationMembership.id, - ); - associationMembershipNotifier - .setAssociationMembership( - associationMembership, - ); - QR.to( - AdminRouter.root + - AdminRouter - .associationMemberships + - AdminRouter - .detailAssociationMembership, - ); - }, - ), - const SizedBox(height: 20), - Button( - text: localizeWithContext.adminDelete, - type: ButtonType.danger, - onPressed: () async { - await showDialog( - context: context, - builder: (context) { - return CustomDialogBox( - title: AppLocalizations.of( - context, - )!.adminDeleting, - descriptions: AppLocalizations.of( - context, - )!.adminDeleteAssociationMembership, - onYes: () async { - tokenExpireWrapper(ref, () async { - final deletedAssociationMembershipMsg = - AppLocalizations.of( - context, - )!.adminDeletedAssociationMembership; - final deletingErrorMsg = - AppLocalizations.of( - context, - )!.adminDeletingError; - final value = - await associationMembershipsNotifier - .deleteAssociationMembership( - associationMembership, - ); - if (value) { - displayToastWithContext( - TypeMsg.msg, - deletedAssociationMembershipMsg, - ); - } else { - displayToastWithContext( - TypeMsg.error, - deletingErrorMsg, - ); - } - }); - }, - ); - }, - ); - }, - ), - ], + (associationMembership) => Padding( + padding: const EdgeInsets.symmetric( + vertical: 5.0, + ), + child: ListItem( + title: associationMembership.name, + onTap: () async { + await showCustomBottomModal( + context: context, + ref: ref, + modal: BottomModalTemplate( + title: associationMembership.name, + child: Column( + children: [ + Button( + text: localizeWithContext.adminEdit, + onPressed: () { + associationMembershipMembersNotifier + .loadAssociationMembershipMembers( + associationMembership.id, + ); + associationMembershipNotifier + .setAssociationMembership( + associationMembership, + ); + QR.to( + AdminRouter.root + + AdminRouter + .associationMemberships + + AdminRouter + .detailAssociationMembership, + ); + }, + ), + const SizedBox(height: 20), + Button( + text: + localizeWithContext.adminDelete, + type: ButtonType.danger, + onPressed: () async { + await showDialog( + context: context, + builder: (context) { + return CustomDialogBox( + title: AppLocalizations.of( + context, + )!.adminDeleting, + descriptions: + AppLocalizations.of( + context, + )!.adminDeleteAssociationMembership, + onYes: () async { + tokenExpireWrapper(ref, () async { + final deletedAssociationMembershipMsg = + AppLocalizations.of( + context, + )!.adminDeletedAssociationMembership; + final deletingErrorMsg = + AppLocalizations.of( + context, + )!.adminDeletingError; + final value = + await associationMembershipsNotifier + .deleteAssociationMembership( + associationMembership, + ); + if (value) { + displayToastWithContext( + TypeMsg.msg, + deletedAssociationMembershipMsg, + ); + } else { + displayToastWithContext( + TypeMsg.error, + deletingErrorMsg, + ); + } + }); + }, + ); + }, + ); + }, + ), + ], + ), ), - ), - ); - }, + ); + }, + ), ), ), const SizedBox(height: 20), From 253e2dd70e5b27bf38c2f7c378427c6b0b2b82e1 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 24 Aug 2025 00:20:20 +0200 Subject: [PATCH 345/473] feat: membership pages ui --- .../add_edit_user_membership_page.dart | 56 ++-- .../search_result.dart | 69 ++--- .../user_search_modal.dart | 50 ++++ .../association_membership_detail_page.dart | 79 ++--- ...ciation_membership_information_editor.dart | 273 ++++++++++-------- ...ation_membership_member_editable_card.dart | 95 +++--- .../delete_button.dart | 52 ---- .../edition_button.dart | 38 --- .../research_bar.dart | 41 --- .../search_filters.dart | 261 ++++++++--------- 10 files changed, 487 insertions(+), 527 deletions(-) create mode 100644 lib/admin/ui/pages/membership/add_edit_user_membership_page/user_search_modal.dart delete mode 100644 lib/admin/ui/pages/membership/association_membership_detail_page/delete_button.dart delete mode 100644 lib/admin/ui/pages/membership/association_membership_detail_page/edition_button.dart delete mode 100644 lib/admin/ui/pages/membership/association_membership_detail_page/research_bar.dart diff --git a/lib/admin/ui/pages/membership/add_edit_user_membership_page/add_edit_user_membership_page.dart b/lib/admin/ui/pages/membership/add_edit_user_membership_page/add_edit_user_membership_page.dart index 788f23e198..89a6d5df51 100644 --- a/lib/admin/ui/pages/membership/add_edit_user_membership_page/add_edit_user_membership_page.dart +++ b/lib/admin/ui/pages/membership/add_edit_user_membership_page/add_edit_user_membership_page.dart @@ -6,16 +6,15 @@ import 'package:titan/admin/class/user_association_membership.dart'; import 'package:titan/admin/class/user_association_membership_base.dart'; import 'package:titan/admin/providers/association_membership_members_list_provider.dart'; import 'package:titan/admin/providers/user_association_membership_provider.dart'; -import 'package:titan/admin/ui/pages/membership/add_edit_user_membership_page/search_result.dart'; +import 'package:titan/admin/ui/pages/membership/add_edit_user_membership_page/user_search_modal.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; -import 'package:titan/tools/ui/layouts/add_edit_button_layout.dart'; +import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; +import 'package:titan/tools/ui/styleguide/list_item.dart'; import 'package:titan/tools/ui/widgets/align_left_text.dart'; import 'package:titan/tools/ui/widgets/date_entry.dart'; -import 'package:titan/tools/ui/widgets/styled_search_bar.dart'; -import 'package:titan/user/providers/user_list_provider.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/l10n/app_localizations.dart'; @@ -27,8 +26,6 @@ class AddEditUserMembershipPage extends HookConsumerWidget { final associationMembershipMembersNotifier = ref.watch( associationMembershipMembersProvider.notifier, ); - final queryController = useTextEditingController(text: ''); - final usersNotifier = ref.watch(userList.notifier); final membership = ref.watch(userAssociationMembershipProvider); final isEdit = membership.id != UserAssociationMembership.empty().id; final start = useTextEditingController( @@ -44,7 +41,7 @@ class AddEditUserMembershipPage extends HookConsumerWidget { return AdminTemplate( child: Padding( - padding: const EdgeInsets.all(30.0), + padding: const EdgeInsets.all(20.0), child: SingleChildScrollView( child: Column( children: [ @@ -52,24 +49,24 @@ class AddEditUserMembershipPage extends HookConsumerWidget { isEdit ? AppLocalizations.of(context)!.adminEditMembership : AppLocalizations.of(context)!.adminAddMember, + fontWeight: FontWeight.w900, + color: ColorConstants.title, + fontSize: 24, ), const SizedBox(height: 20), if (!isEdit) ...[ - StyledSearchBar( - padding: EdgeInsets.zero, - label: AppLocalizations.of(context)!.adminUser, - editingController: queryController, - onChanged: (value) async { - tokenExpireWrapper(ref, () async { - if (value.isNotEmpty) { - await usersNotifier.filterUsers(value); - } else { - usersNotifier.clear(); - } - }); + ListItem( + title: membership.user.id.isNotEmpty + ? membership.user.getName() + : AppLocalizations.of(context)!.adminUser, + onTap: () async { + await showCustomBottomModal( + context: context, + ref: ref, + modal: UserSearchModal(), + ); }, ), - SearchResult(queryController: queryController), ] else Text( membership.user.getName(), @@ -89,7 +86,7 @@ class AddEditUserMembershipPage extends HookConsumerWidget { lastDate: DateTime(2100), ), ), - const SizedBox(height: 50), + const SizedBox(height: 10), DateEntry( label: AppLocalizations.of(context)!.adminEndDate, controller: end, @@ -100,11 +97,20 @@ class AddEditUserMembershipPage extends HookConsumerWidget { lastDate: DateTime(2100), ), ), - const SizedBox(height: 50), + const SizedBox(height: 20), WaitingButton( - builder: (child) => AddEditButtonLayout( - colors: const [ColorConstants.main, ColorConstants.onMain], - child: child, + builder: (child) => Container( + width: double.infinity, + padding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 10, + ), + decoration: BoxDecoration( + color: ColorConstants.tertiary, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: ColorConstants.onTertiary), + ), + child: Center(child: child), ), child: Text( !isEdit diff --git a/lib/admin/ui/pages/membership/add_edit_user_membership_page/search_result.dart b/lib/admin/ui/pages/membership/add_edit_user_membership_page/search_result.dart index e9d0994a6b..281d4e5743 100644 --- a/lib/admin/ui/pages/membership/add_edit_user_membership_page/search_result.dart +++ b/lib/admin/ui/pages/membership/add_edit_user_membership_page/search_result.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/providers/user_association_membership_provider.dart'; +import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/user/providers/user_list_provider.dart'; class SearchResult extends HookConsumerWidget { @@ -16,60 +18,41 @@ class SearchResult extends HookConsumerWidget { ); final membership = ref.watch(userAssociationMembershipProvider); - return users.when( - data: (usersData) { + return AsyncChild( + value: users, + builder: (context, usersData) { return Column( children: usersData .map( - (user) => GestureDetector( - behavior: HitTestBehavior.opaque, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 4.0), - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - color: Colors.white, - boxShadow: [ - BoxShadow( - color: Colors.black.withValues(alpha: 0.1), - offset: const Offset(0, 1), - blurRadius: 4, - spreadRadius: 2, - ), - ], - ), - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 14), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container(width: 20), - Expanded( - child: Text( - user.getName(), - style: const TextStyle(fontSize: 13), - overflow: TextOverflow.ellipsis, - ), - ), - ], + (user) => Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + user.getName(), + style: const TextStyle(fontSize: 15), + overflow: TextOverflow.ellipsis, ), ), - ), + GestureDetector( + onTap: () { + membershipNotifier.setUserAssociationMembership( + membership.copyWith(user: user, userId: user.id), + ); + usersNotifier.clear(); + Navigator.of(context).pop(); + }, + child: const HeroIcon(HeroIcons.plus), + ), + ], ), - onTap: () { - membershipNotifier.setUserAssociationMembership( - membership.copyWith(user: user, userId: user.id), - ); - queryController.text = user.getName(); - usersNotifier.clear(); - }, ), ) .toList(), ); }, - loading: () => const Center(child: CircularProgressIndicator()), - error: (e, s) => Text(e.toString()), ); } } diff --git a/lib/admin/ui/pages/membership/add_edit_user_membership_page/user_search_modal.dart b/lib/admin/ui/pages/membership/add_edit_user_membership_page/user_search_modal.dart new file mode 100644 index 0000000000..20b3195f97 --- /dev/null +++ b/lib/admin/ui/pages/membership/add_edit_user_membership_page/user_search_modal.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/admin/ui/pages/membership/add_edit_user_membership_page/search_result.dart'; +import 'package:titan/l10n/app_localizations.dart'; +import 'package:titan/tools/token_expire_wrapper.dart'; +import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; +import 'package:titan/tools/ui/styleguide/searchbar.dart'; +import 'package:titan/user/providers/user_list_provider.dart'; + +class UserSearchModal extends HookConsumerWidget { + const UserSearchModal({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final usersNotifier = ref.watch(userList.notifier); + final textController = useTextEditingController(); + + final localizeWithContext = AppLocalizations.of(context)!; + + return BottomModalTemplate( + title: localizeWithContext.adminSelectManager, + type: BottomModalType.main, + child: Column( + children: [ + CustomSearchBar( + autofocus: true, + onSearch: (value) => tokenExpireWrapper(ref, () async { + if (value.isNotEmpty) { + await usersNotifier.filterUsers(value); + textController.text = value; + } else { + usersNotifier.clear(); + textController.clear(); + } + }), + ), + const SizedBox(height: 10), + ConstrainedBox( + constraints: const BoxConstraints(maxHeight: 280), + child: SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: SearchResult(queryController: textController), + ), + ), + ], + ), + ); + } +} diff --git a/lib/admin/ui/pages/membership/association_membership_detail_page/association_membership_detail_page.dart b/lib/admin/ui/pages/membership/association_membership_detail_page/association_membership_detail_page.dart index 4197ea29b1..53349aa6b6 100644 --- a/lib/admin/ui/pages/membership/association_membership_detail_page/association_membership_detail_page.dart +++ b/lib/admin/ui/pages/membership/association_membership_detail_page/association_membership_detail_page.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/admin.dart'; +import 'package:titan/admin/providers/research_filter_provider.dart'; import 'package:titan/admin/router.dart'; import 'package:titan/admin/class/user_association_membership.dart'; import 'package:titan/admin/providers/association_membership_filtered_members_provider.dart'; @@ -10,13 +11,15 @@ import 'package:titan/admin/providers/association_membership_provider.dart'; import 'package:titan/admin/providers/user_association_membership_provider.dart'; import 'package:titan/admin/ui/pages/membership/association_membership_detail_page/association_membership_information_editor.dart'; import 'package:titan/admin/ui/pages/membership/association_membership_detail_page/association_membership_member_editable_card.dart'; -import 'package:titan/admin/ui/pages/membership/association_membership_detail_page/research_bar.dart'; import 'package:titan/admin/ui/pages/membership/association_membership_detail_page/search_filters.dart'; import 'package:titan/tools/constants.dart'; -import 'package:titan/tools/ui/builders/waiting_button.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/l10n/app_localizations.dart'; +import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; +import 'package:titan/tools/ui/styleguide/icon_button.dart'; +import 'package:titan/tools/ui/styleguide/list_item.dart'; +import 'package:titan/tools/ui/styleguide/searchbar.dart'; class AssociationMembershipEditorPage extends HookConsumerWidget { final scrollKey = GlobalKey(); @@ -24,6 +27,7 @@ class AssociationMembershipEditorPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final filterNotifier = ref.watch(filterProvider.notifier); final associationMembership = ref.watch(associationMembershipProvider); final associationMembershipMemberListNotifier = ref.watch( associationMembershipMembersProvider.notifier, @@ -43,7 +47,7 @@ class AssociationMembershipEditorPage extends HookConsumerWidget { .loadAssociationMembershipMembers(associationMembership.id); }, child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 30), + padding: const EdgeInsets.symmetric(horizontal: 20), child: Column( children: [ const SizedBox(height: 20), @@ -52,9 +56,9 @@ class AssociationMembershipEditorPage extends HookConsumerWidget { child: Text( "${AppLocalizations.of(context)!.adminAssociationMembership} ${associationMembership.name}", style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.w700, - color: ColorConstants.main, + fontSize: 24, + fontWeight: FontWeight.bold, + color: ColorConstants.title, ), ), ), @@ -63,34 +67,16 @@ class AssociationMembershipEditorPage extends HookConsumerWidget { Row( children: [ Text( - AppLocalizations.of(context)!.adminMembers, - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.w700, - color: ColorConstants.main, - ), - ), - const SizedBox(width: 10), - Text( - "(${associationMembershipFilteredList.length} ${AppLocalizations.of(context)!.adminMembers})", - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.w700, - color: ColorConstants.main, + "${AppLocalizations.of(context)!.adminMembers} (${associationMembershipFilteredList.length})", + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: ColorConstants.title, ), ), const Spacer(), - WaitingButton( - builder: (child) => Container( - width: 40, - height: 40, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - color: ColorConstants.main, - ), - child: child, - ), - onTap: () async { + CustomIconButton( + onPressed: () async { userAssociationMembershipNotifier .setUserAssociationMembership( UserAssociationMembership.empty().copyWith( @@ -104,7 +90,7 @@ class AssociationMembershipEditorPage extends HookConsumerWidget { AdminRouter.addEditMember, ); }, - child: const HeroIcon( + icon: const HeroIcon( HeroIcons.plus, size: 30, color: Colors.white, @@ -113,12 +99,33 @@ class AssociationMembershipEditorPage extends HookConsumerWidget { ], ), const SizedBox(height: 10), - ExpansionTile( - title: Text(AppLocalizations.of(context)!.adminFilters), - children: const [SearchFilters()], + ListItem( + title: AppLocalizations.of(context)!.adminFilters, + onTap: () async { + FocusScope.of(context).unfocus(); + final ctx = context; + await Future.delayed(Duration(milliseconds: 150)); + if (!ctx.mounted) return; + + await showCustomBottomModal( + context: ctx, + ref: ref, + modal: BottomModalTemplate( + title: AppLocalizations.of(context)!.adminFilters, + child: SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: SearchFilters(), + ), + ), + ); + }, ), const SizedBox(height: 20), - ResearchBar(), + CustomSearchBar( + onSearch: (query) { + filterNotifier.setFilter(query); + }, + ), const SizedBox(height: 10), associationMembershipFilteredList.isEmpty ? Text(AppLocalizations.of(context)!.adminNoMember) diff --git a/lib/admin/ui/pages/membership/association_membership_detail_page/association_membership_information_editor.dart b/lib/admin/ui/pages/membership/association_membership_detail_page/association_membership_information_editor.dart index fe4af2d263..eef898f45f 100644 --- a/lib/admin/ui/pages/membership/association_membership_detail_page/association_membership_information_editor.dart +++ b/lib/admin/ui/pages/membership/association_membership_detail_page/association_membership_information_editor.dart @@ -9,8 +9,9 @@ import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; -import 'package:titan/tools/ui/layouts/add_edit_button_layout.dart'; import 'package:titan/l10n/app_localizations.dart'; +import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; +import 'package:titan/tools/ui/styleguide/list_item.dart'; class AssociationMembershipInformationEditor extends HookConsumerWidget { final scrollKey = GlobalKey(); @@ -35,132 +36,174 @@ class AssociationMembershipInformationEditor extends HookConsumerWidget { allAssociationMembershipListProvider.notifier, ); final key = GlobalKey(); + final localizeWithContext = AppLocalizations.of(context)!; groups.sort((a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase())); - return Column( - children: [ - Form( - key: key, - child: Column( - children: [ - Container( - margin: const EdgeInsets.symmetric(vertical: 10), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - child: TextFormField( - controller: name, - cursorColor: ColorConstants.main, - decoration: InputDecoration( - labelText: AppLocalizations.of(context)!.adminName, - labelStyle: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - ), - suffixIcon: Container( - padding: const EdgeInsets.all(10), - child: const HeroIcon(HeroIcons.pencil), - ), - enabledBorder: const UnderlineInputBorder( - borderSide: BorderSide(color: Colors.transparent), - ), - focusedBorder: const UnderlineInputBorder( - borderSide: BorderSide(color: ColorConstants.main), - ), - ), - validator: (value) { - if (value == null || value.isEmpty) { - return AppLocalizations.of( - context, - )!.adminEmptyFieldError; - } - return null; - }, + return Form( + key: key, + child: Column( + children: [ + Container( + margin: const EdgeInsets.symmetric(vertical: 10), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + child: TextFormField( + controller: name, + cursorColor: ColorConstants.tertiary, + decoration: InputDecoration( + labelText: AppLocalizations.of(context)!.adminName, + labelStyle: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + suffixIcon: Container( + margin: const EdgeInsets.only(left: 20), + child: const HeroIcon(HeroIcons.pencil), + ), + enabledBorder: const UnderlineInputBorder( + borderSide: BorderSide(color: Colors.transparent), + ), + focusedBorder: const UnderlineInputBorder( + borderSide: BorderSide(color: ColorConstants.tertiary), ), ), - ], - ), - ), - Align( - alignment: Alignment.centerLeft, - child: Text( - AppLocalizations.of(context)!.adminGroup, - style: const TextStyle(fontWeight: FontWeight.bold), + validator: (value) { + if (value == null || value.isEmpty) { + return AppLocalizations.of( + context, + )!.adminEmptyFieldError; + } + return null; + }, + ), ), - ), - DropdownButtonFormField( - value: groupIdController.text, - onChanged: (String? newValue) { - groupIdController.text = newValue!; - }, - items: groups - .map( - (group) => DropdownMenuItem( - value: group.id, - child: Text(group.name), + ], + ), + ), + Align( + alignment: Alignment.centerLeft, + child: Text( + AppLocalizations.of(context)!.adminGroup, + style: const TextStyle(fontWeight: FontWeight.bold), + ), + ), + ListItem( + title: groups + .firstWhere((group) => group.id == groupIdController.text) + .name, + onTap: () async { + FocusScope.of(context).unfocus(); + final ctx = context; + await Future.delayed(Duration(milliseconds: 150)); + if (!ctx.mounted) return; + + await showCustomBottomModal( + context: ctx, + ref: ref, + modal: BottomModalTemplate( + title: localizeWithContext.adminChooseGroupManager, + child: ConstrainedBox( + constraints: const BoxConstraints(maxHeight: 280), + child: SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: Column( + children: [ + ...groups.map( + (e) => Padding( + padding: const EdgeInsets.symmetric( + vertical: 8.0, + ), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + e.name, + style: const TextStyle(fontSize: 15), + overflow: TextOverflow.ellipsis, + ), + ), + GestureDetector( + onTap: () { + groupIdController.text = e.id; + Navigator.of(ctx).pop(); + }, + child: const HeroIcon(HeroIcons.plus), + ), + ], + ), + ), + ), + ], ), - ) - .toList(), - decoration: InputDecoration( - hintText: AppLocalizations.of(context)!.adminGroup, + ), + ), ), + ); + }, + ), + const SizedBox(height: 20), + WaitingButton( + builder: (child) => Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + decoration: BoxDecoration( + color: ColorConstants.tertiary, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: ColorConstants.onTertiary), ), - const SizedBox(height: 20), - WaitingButton( - builder: (child) => AddEditButtonLayout( - colors: const [ColorConstants.main, ColorConstants.onMain], - child: child, - ), - onTap: () async { - if (!key.currentState!.validate()) { - return; - } + child: Center(child: child), + ), + onTap: () async { + if (!key.currentState!.validate()) { + return; + } - await tokenExpireWrapper(ref, () async { - final updatedAssociationMembershipMsg = AppLocalizations.of( - context, - )!.adminUpdatedAssociationMembership; - final updatingAssociationMembershipErrorMsg = - AppLocalizations.of(context)!.adminUpdatingError; - final value = await associationMembershipListNotifier - .updateAssociationMembership( - associationMembership.copyWith(name: name.text), - ); - if (value) { - associationMembershipNotifier.setAssociationMembership( - associationMembership.copyWith( - name: name.text, - managerGroupId: groupIdController.text, - ), - ); - displayToastWithContext( - TypeMsg.msg, - updatedAssociationMembershipMsg, - ); - } else { - displayToastWithContext( - TypeMsg.msg, - updatingAssociationMembershipErrorMsg, - ); - } - }); - }, - child: Text( - AppLocalizations.of(context)!.adminEdit, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - color: Color.fromARGB(255, 255, 255, 255), - ), - ), + await tokenExpireWrapper(ref, () async { + final updatedAssociationMembershipMsg = AppLocalizations.of( + context, + )!.adminUpdatedAssociationMembership; + final updatingAssociationMembershipErrorMsg = + AppLocalizations.of(context)!.adminUpdatingError; + final value = await associationMembershipListNotifier + .updateAssociationMembership( + associationMembership.copyWith(name: name.text), + ); + if (value) { + associationMembershipNotifier.setAssociationMembership( + associationMembership.copyWith( + name: name.text, + managerGroupId: groupIdController.text, + ), + ); + displayToastWithContext( + TypeMsg.msg, + updatedAssociationMembershipMsg, + ); + } else { + displayToastWithContext( + TypeMsg.msg, + updatingAssociationMembershipErrorMsg, + ); + } + }); + }, + child: Text( + AppLocalizations.of(context)!.adminEdit, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + color: Color.fromARGB(255, 255, 255, 255), ), - ], + ), ), - ), - ], + ], + ), ); } } diff --git a/lib/admin/ui/pages/membership/association_membership_detail_page/association_membership_member_editable_card.dart b/lib/admin/ui/pages/membership/association_membership_detail_page/association_membership_member_editable_card.dart index 317773b5e6..5f43d7a73a 100644 --- a/lib/admin/ui/pages/membership/association_membership_detail_page/association_membership_member_editable_card.dart +++ b/lib/admin/ui/pages/membership/association_membership_detail_page/association_membership_member_editable_card.dart @@ -1,16 +1,19 @@ import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter/material.dart'; +import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:intl/intl.dart'; import 'package:titan/admin/class/user_association_membership.dart'; import 'package:titan/admin/providers/association_membership_members_list_provider.dart'; import 'package:titan/admin/providers/user_association_membership_provider.dart'; import 'package:titan/admin/router.dart'; -import 'package:titan/admin/ui/pages/membership/association_membership_detail_page/delete_button.dart'; -import 'package:titan/admin/ui/pages/membership/association_membership_detail_page/edition_button.dart'; +import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/l10n/app_localizations.dart'; +import 'package:titan/tools/ui/styleguide/icon_button.dart'; +import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; class MemberEditableCard extends HookConsumerWidget { const MemberEditableCard({super.key, required this.associationMembership}); @@ -31,12 +34,7 @@ class MemberEditableCard extends HookConsumerWidget { } return Container( - padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 10), - margin: const EdgeInsets.symmetric(vertical: 5), - decoration: BoxDecoration( - border: Border.all(), - borderRadius: const BorderRadius.all(Radius.circular(20)), - ), + padding: const EdgeInsets.symmetric(vertical: 5), child: Row( children: [ Expanded( @@ -49,13 +47,14 @@ class MemberEditableCard extends HookConsumerWidget { style: const TextStyle(fontWeight: FontWeight.bold), minFontSize: 10, maxFontSize: 15, + overflow: TextOverflow.ellipsis, ), - const SizedBox(height: 3), associationMembership.user.nickname != null ? AutoSizeText( "${associationMembership.user.firstname} ${associationMembership.user.name}", minFontSize: 10, maxFontSize: 15, + overflow: TextOverflow.ellipsis, ) : const SizedBox(), ], @@ -64,14 +63,27 @@ class MemberEditableCard extends HookConsumerWidget { Expanded( child: Column( children: [ - Text(associationMembership.startDate.toString().split(" ")[0]), - Text(associationMembership.endDate.toString().split(" ")[0]), + Text( + DateFormat( + "dd/MM/yyyy", + ).format(associationMembership.startDate), + style: const TextStyle(fontSize: 12), + ), + Text( + DateFormat( + "dd/MM/yyyy", + ).format(associationMembership.endDate), + style: const TextStyle(fontSize: 12), + ), ], ), ), - EditionButton( - deactivated: false, - onEdition: () async { + CustomIconButton.secondary( + icon: const HeroIcon( + HeroIcons.pencil, + color: ColorConstants.tertiary, + ), + onPressed: () async { userAssociationMembershipNotifier.setUserAssociationMembership( associationMembership, ); @@ -84,25 +96,42 @@ class MemberEditableCard extends HookConsumerWidget { }, ), const SizedBox(width: 10), - DeleteButton( - deactivated: false, - deletion: true, - onDelete: () async { - final deletedMemberMsg = AppLocalizations.of( - context, - )!.phonebookDeletedMember; - final deleteMemberErrorMsg = AppLocalizations.of( - context, - )!.phonebookDeletingError; - await tokenExpireWrapper(ref, () async { - final result = await associationMembershipMemberListNotifier - .deleteMember(associationMembership); - if (result) { - displayToastWithContext(TypeMsg.msg, deletedMemberMsg); - } else { - displayToastWithContext(TypeMsg.error, deleteMemberErrorMsg); - } - }); + CustomIconButton.danger( + icon: HeroIcon(HeroIcons.trash, color: Colors.white), + onPressed: () async { + await showDialog( + context: context, + builder: (context) { + return CustomDialogBox( + title: "Supprimer le membre", + descriptions: "Êtes-vous sûr de vouloir supprimer ce membre ?", + onYes: () async { + final deletedMemberMsg = AppLocalizations.of( + context, + )!.phonebookDeletedMember; + final deleteMemberErrorMsg = AppLocalizations.of( + context, + )!.phonebookDeletingError; + await tokenExpireWrapper(ref, () async { + final result = + await associationMembershipMemberListNotifier + .deleteMember(associationMembership); + if (result) { + displayToastWithContext( + TypeMsg.msg, + deletedMemberMsg, + ); + } else { + displayToastWithContext( + TypeMsg.error, + deleteMemberErrorMsg, + ); + } + }); + }, + ); + }, + ); }, ), ], diff --git a/lib/admin/ui/pages/membership/association_membership_detail_page/delete_button.dart b/lib/admin/ui/pages/membership/association_membership_detail_page/delete_button.dart deleted file mode 100644 index 93b7c44ca3..0000000000 --- a/lib/admin/ui/pages/membership/association_membership_detail_page/delete_button.dart +++ /dev/null @@ -1,52 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:heroicons/heroicons.dart'; -import 'package:titan/tools/constants.dart'; -import 'package:titan/tools/ui/builders/waiting_button.dart'; - -class DeleteButton extends StatelessWidget { - final Future Function() onDelete; - final bool deactivated; - final bool deletion; - - const DeleteButton({ - super.key, - required this.onDelete, - required this.deactivated, - required this.deletion, - }); - - @override - Widget build(BuildContext context) { - return WaitingButton( - builder: (child) => Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - gradient: LinearGradient( - colors: !deactivated - ? [ColorConstants.gradient1, ColorConstants.gradient2] - : [ColorConstants.deactivated1, ColorConstants.deactivated2], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ), - boxShadow: [ - BoxShadow( - color: !deactivated - ? ColorConstants.gradient2.withValues(alpha: 0.2) - : ColorConstants.deactivated2.withValues(alpha: 0.2), - blurRadius: 10, - offset: const Offset(2, 3), - ), - ], - borderRadius: BorderRadius.circular(10), - ), - child: child, - ), - onTap: !deactivated ? onDelete : () async {}, - child: HeroIcon( - deletion ? HeroIcons.trash : HeroIcons.noSymbol, - size: 30, - color: Colors.white, - ), - ); - } -} diff --git a/lib/admin/ui/pages/membership/association_membership_detail_page/edition_button.dart b/lib/admin/ui/pages/membership/association_membership_detail_page/edition_button.dart deleted file mode 100644 index 0610372c55..0000000000 --- a/lib/admin/ui/pages/membership/association_membership_detail_page/edition_button.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:heroicons/heroicons.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/tools/constants.dart'; - -class EditionButton extends HookConsumerWidget { - const EditionButton({ - super.key, - required this.onEdition, - required this.deactivated, - }); - final void Function() onEdition; - final bool deactivated; - - @override - Widget build(BuildContext context, WidgetRef ref) { - return GestureDetector( - onTap: !deactivated ? onEdition : null, - child: Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: !deactivated - ? Colors.grey.shade200 - : ColorConstants.deactivated1, - borderRadius: BorderRadius.circular(10), - boxShadow: [ - BoxShadow( - color: Colors.grey.withValues(alpha: 0.2), - blurRadius: 10, - offset: const Offset(2, 3), - ), - ], - ), - child: const HeroIcon(HeroIcons.pencil, color: Colors.black), - ), - ); - } -} diff --git a/lib/admin/ui/pages/membership/association_membership_detail_page/research_bar.dart b/lib/admin/ui/pages/membership/association_membership_detail_page/research_bar.dart deleted file mode 100644 index 84b1f5accf..0000000000 --- a/lib/admin/ui/pages/membership/association_membership_detail_page/research_bar.dart +++ /dev/null @@ -1,41 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/providers/research_filter_provider.dart'; -import 'package:titan/tools/constants.dart'; -import 'package:titan/l10n/app_localizations.dart'; - -class ResearchBar extends HookConsumerWidget { - const ResearchBar({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final focusNode = useFocusNode(); - final editingController = useTextEditingController(); - final filterNotifier = ref.watch(filterProvider.notifier); - - return TextField( - onChanged: (value) { - filterNotifier.setFilter(value); - }, - focusNode: focusNode, - controller: editingController, - cursorColor: Color(0xFF1D1D1D), - decoration: InputDecoration( - isDense: true, - suffixIcon: const Icon( - Icons.search, - color: Color(0xFF1D1D1D), - size: 30, - ), - label: Text( - AppLocalizations.of(context)!.adminResearch, - style: const TextStyle(color: Color(0xFF1D1D1D)), - ), - focusedBorder: const UnderlineInputBorder( - borderSide: BorderSide(color: ColorConstants.gradient1), - ), - ), - ); - } -} diff --git a/lib/admin/ui/pages/membership/association_membership_detail_page/search_filters.dart b/lib/admin/ui/pages/membership/association_membership_detail_page/search_filters.dart index 0fa5ee2bbe..6fb8d74e65 100644 --- a/lib/admin/ui/pages/membership/association_membership_detail_page/search_filters.dart +++ b/lib/admin/ui/pages/membership/association_membership_detail_page/search_filters.dart @@ -30,155 +30,128 @@ class SearchFilters extends HookConsumerWidget { final endMaximal = useTextEditingController(text: ""); return Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - children: [ - SizedBox( - width: MediaQuery.of(context).size.width * 0.4, - child: Column( - children: [ - Text( - AppLocalizations.of(context)!.adminStartDate, - style: const TextStyle(fontSize: 18), - ), - DateEntry( - label: AppLocalizations.of(context)!.adminStartDateMinimal, - controller: startMinimal, - onTap: () => getOnlyDayDate( - context, - startMinimal, - firstDate: DateTime(2019), - lastDate: DateTime(DateTime.now().year + 7), - ), - ), - const SizedBox(height: 20), - DateEntry( - label: AppLocalizations.of(context)!.adminStartDateMaximal, - controller: startMaximal, - onTap: () => getOnlyDayDate( - context, - startMaximal, - firstDate: DateTime(2019), - lastDate: DateTime(DateTime.now().year + 7), - ), - ), - ], - ), - ), - const Spacer(), - SizedBox( - width: MediaQuery.of(context).size.width * 0.4, - child: Column( - children: [ - Text( - AppLocalizations.of(context)!.adminEndDate, - style: const TextStyle(fontSize: 18), - ), - DateEntry( - label: AppLocalizations.of(context)!.adminEndDateMinimal, - controller: endMinimal, - onTap: () => getOnlyDayDate( - context, - endMinimal, - firstDate: DateTime(2019), - lastDate: DateTime(DateTime.now().year + 7), - ), - ), - const SizedBox(height: 20), - DateEntry( - label: AppLocalizations.of(context)!.adminEndDateMaximal, - controller: endMaximal, - onTap: () => getOnlyDayDate( - context, - endMaximal, - firstDate: DateTime(2019), - lastDate: DateTime(DateTime.now().year + 7), - ), - ), - ], - ), - ), - ], + Text( + AppLocalizations.of(context)!.adminStartDate, + style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w900), + ), + const SizedBox(height: 10), + DateEntry( + label: AppLocalizations.of(context)!.adminStartDateMinimal, + controller: startMinimal, + onTap: () => getOnlyDayDate( + context, + startMinimal, + firstDate: DateTime(2019), + lastDate: DateTime(DateTime.now().year + 7), + ), + ), + const SizedBox(height: 10), + DateEntry( + label: AppLocalizations.of(context)!.adminStartDateMaximal, + controller: startMaximal, + onTap: () => getOnlyDayDate( + context, + startMaximal, + firstDate: DateTime(2019), + lastDate: DateTime(DateTime.now().year + 7), + ), ), const SizedBox(height: 20), - Row( - children: [ - const Spacer(flex: 2), - SizedBox( - width: MediaQuery.of(context).size.width * 0.3, - child: WaitingButton( - onTap: () async { - await tokenExpireWrapper(ref, () async { - await associationMembershipMemberListNotifier - .loadAssociationMembershipMembers( - associationMembership.id, - minimalStartDate: startMinimal.text.isNotEmpty - ? DateTime.parse( - processDateBack(startMinimal.text), - ) - : null, - minimalEndDate: endMinimal.text.isNotEmpty - ? DateTime.parse(processDateBack(endMinimal.text)) - : null, - maximalStartDate: startMaximal.text.isNotEmpty - ? DateTime.parse( - processDateBack(startMaximal.text), - ) - : null, - maximalEndDate: endMaximal.text.isNotEmpty - ? DateTime.parse(processDateBack(endMaximal.text)) - : null, - ); - }); - }, - builder: (child) => AddEditButtonLayout( - colors: const [ColorConstants.main, ColorConstants.onMain], - child: child, - ), - child: Text( - AppLocalizations.of(context)!.adminValidateFilters, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - color: Color.fromARGB(255, 255, 255, 255), - ), - textAlign: TextAlign.center, - ), - ), + Text( + AppLocalizations.of(context)!.adminEndDate, + style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w900), + ), + const SizedBox(height: 10), + DateEntry( + label: AppLocalizations.of(context)!.adminEndDateMinimal, + controller: endMinimal, + onTap: () => getOnlyDayDate( + context, + endMinimal, + firstDate: DateTime(2019), + lastDate: DateTime(DateTime.now().year + 7), + ), + ), + const SizedBox(height: 10), + DateEntry( + label: AppLocalizations.of(context)!.adminEndDateMaximal, + controller: endMaximal, + onTap: () => getOnlyDayDate( + context, + endMaximal, + firstDate: DateTime(2019), + lastDate: DateTime(DateTime.now().year + 7), + ), + ), + const SizedBox(height: 30), + WaitingButton( + onTap: () async { + await tokenExpireWrapper(ref, () async { + await associationMembershipMemberListNotifier + .loadAssociationMembershipMembers( + associationMembership.id, + minimalStartDate: startMinimal.text.isNotEmpty + ? DateTime.parse(processDateBack(startMinimal.text)) + : null, + minimalEndDate: endMinimal.text.isNotEmpty + ? DateTime.parse(processDateBack(endMinimal.text)) + : null, + maximalStartDate: startMaximal.text.isNotEmpty + ? DateTime.parse(processDateBack(startMaximal.text)) + : null, + maximalEndDate: endMaximal.text.isNotEmpty + ? DateTime.parse(processDateBack(endMaximal.text)) + : null, + ); + }); + }, + builder: (child) => Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + decoration: BoxDecoration( + color: ColorConstants.tertiary, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: ColorConstants.onTertiary), ), - const Spacer(), - SizedBox( - width: MediaQuery.of(context).size.width * 0.3, - child: WaitingButton( - onTap: () async { - startMaximal.clear(); - startMinimal.clear(); - endMaximal.clear(); - endMinimal.clear(); - await tokenExpireWrapper(ref, () async { - await associationMembershipMemberListNotifier - .loadAssociationMembershipMembers( - associationMembership.id, - ); - }); - }, - builder: (child) => AddEditButtonLayout( - colors: const [ColorConstants.main, ColorConstants.onMain], - child: child, - ), - child: Text( - AppLocalizations.of(context)!.adminClearFilters, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - color: Color.fromARGB(255, 255, 255, 255), - ), - textAlign: TextAlign.center, - ), - ), + child: Center(child: child), + ), + child: Text( + AppLocalizations.of(context)!.adminValidateFilters, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + color: Color.fromARGB(255, 255, 255, 255), + ), + textAlign: TextAlign.center, + ), + ), + SizedBox(height: 20), + WaitingButton( + onTap: () async { + startMaximal.clear(); + startMinimal.clear(); + endMaximal.clear(); + endMinimal.clear(); + await tokenExpireWrapper(ref, () async { + await associationMembershipMemberListNotifier + .loadAssociationMembershipMembers(associationMembership.id); + }); + }, + builder: (child) => AddEditButtonLayout( + colors: const [ColorConstants.main, ColorConstants.onMain], + child: child, + ), + child: Text( + AppLocalizations.of(context)!.adminClearFilters, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + color: Color.fromARGB(255, 255, 255, 255), ), - const Spacer(flex: 2), - ], + textAlign: TextAlign.center, + ), ), SizedBox(height: 10), ], From 1bfbdcc5f77f7b47788487c5cddd4fb8347e2df1 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 24 Aug 2025 00:20:20 +0200 Subject: [PATCH 346/473] feat: association pages ui --- .../add_association_modal.dart | 8 +- .../association_page/association_page.dart | 152 ++++----- .../association_page/edit_association.dart | 299 +++++++++--------- 3 files changed, 234 insertions(+), 225 deletions(-) diff --git a/lib/admin/ui/pages/association_page/add_association_modal.dart b/lib/admin/ui/pages/association_page/add_association_modal.dart index 0f9a70fd88..770c924082 100644 --- a/lib/admin/ui/pages/association_page/add_association_modal.dart +++ b/lib/admin/ui/pages/association_page/add_association_modal.dart @@ -1,11 +1,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:heroicons/heroicons.dart'; import 'package:titan/admin/class/simple_group.dart'; import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; import 'package:titan/tools/ui/styleguide/button.dart'; import 'package:titan/tools/ui/styleguide/list_item.dart'; +import 'package:titan/tools/ui/styleguide/list_item_template.dart'; import 'package:titan/tools/ui/widgets/text_entry.dart'; class AddAssociationModal extends HookWidget { @@ -59,8 +61,9 @@ class AddAssociationModal extends HookWidget { child: Column( children: [ ...groups.map( - (e) => ListItem( + (e) => ListItemTemplate( title: e.name, + trailing: const HeroIcon(HeroIcons.plus), onTap: () { chosenGroup.value = e; Navigator.of(ctx).pop(); @@ -91,8 +94,9 @@ class AddAssociationModal extends HookWidget { child: Column( children: [ ...groups.map( - (e) => ListItem( + (e) => ListItemTemplate( title: e.name, + trailing: const HeroIcon(HeroIcons.plus), onTap: () { chosenGroup.value = e; Navigator.of(ctx).pop(); diff --git a/lib/admin/ui/pages/association_page/association_page.dart b/lib/admin/ui/pages/association_page/association_page.dart index d4e11fa34d..01b83c84a7 100644 --- a/lib/admin/ui/pages/association_page/association_page.dart +++ b/lib/admin/ui/pages/association_page/association_page.dart @@ -8,6 +8,7 @@ import 'package:titan/admin/providers/assocation_list_provider.dart'; import 'package:titan/admin/ui/pages/association_page/add_association_modal.dart'; import 'package:titan/admin/ui/pages/association_page/association_item.dart'; import 'package:titan/l10n/app_localizations.dart'; +import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; @@ -34,84 +35,87 @@ class AssociationPage extends ConsumerWidget { final localizeWithContext = AppLocalizations.of(context)!; return AdminTemplate( - child: AsyncChild( - value: associationList, - builder: (BuildContext context, associationList) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 30), - child: Column( - children: [ - Row( - children: [ - Text( - localizeWithContext.adminAssociations, - style: Theme.of(context).textTheme.headlineMedium, + child: Refresher( + controller: ScrollController(), + onRefresh: () async { + await associationNotifier.loadAssociations(); + }, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Column( + children: [ + const SizedBox(height: 20), + Row( + children: [ + Text( + localizeWithContext.adminAssociations, + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: ColorConstants.title, ), - const Spacer(), - CustomIconButton( - icon: HeroIcon( - HeroIcons.plus, - color: Colors.white, - size: 30, - ), - onPressed: () async { - await showCustomBottomModal( - context: context, - ref: ref, - modal: AddAssociationModal( - groups: groups, - onSubmit: (group, name) { - tokenExpireWrapper(ref, () async { - final value = await associationNotifier - .createAssociation( - Association.empty().copyWith( - groupId: group.id, - name: name, - ), - ); - if (value) { - displayToastWithContext( - TypeMsg.msg, - localizeWithContext.adminAssociationCreated, - ); - } else { - displayToastWithContext( - TypeMsg.error, - localizeWithContext - .adminAssociationCreationError, - ); - } - popWithContext(); - }); - }, - ref: ref, - ), - ); - }, + ), + const Spacer(), + CustomIconButton( + icon: HeroIcon( + HeroIcons.plus, + color: Colors.white, + size: 30, ), - ], - ), - SizedBox(height: 20), - Expanded( - child: Refresher( - controller: ScrollController(), - onRefresh: () async { - await associationNotifier.loadAssociations(); - }, - child: Column( - children: [ - ...associationList.map( - (association) => - AssociationItem(association: association), + onPressed: () async { + await showCustomBottomModal( + context: context, + ref: ref, + modal: AddAssociationModal( + groups: groups, + onSubmit: (group, name) { + tokenExpireWrapper(ref, () async { + final value = await associationNotifier + .createAssociation( + Association.empty().copyWith( + groupId: group.id, + name: name, + ), + ); + if (value) { + displayToastWithContext( + TypeMsg.msg, + localizeWithContext.adminAssociationCreated, + ); + } else { + displayToastWithContext( + TypeMsg.error, + localizeWithContext + .adminAssociationCreationError, + ); + } + popWithContext(); + }); + }, + ref: ref, ), - ], - ), + ); + }, ), - ), - ], - ), - ); - }, + ], + ), + SizedBox(height: 10), + AsyncChild( + value: associationList, + builder: (BuildContext context, associationList) { + return Column( + children: [ + ...associationList.map( + (association) => + AssociationItem(association: association), + ), + ], + ); + }, + ), + ], + ), + ), ), ); } diff --git a/lib/admin/ui/pages/association_page/edit_association.dart b/lib/admin/ui/pages/association_page/edit_association.dart index 2d34818584..82928e5988 100644 --- a/lib/admin/ui/pages/association_page/edit_association.dart +++ b/lib/admin/ui/pages/association_page/edit_association.dart @@ -45,175 +45,176 @@ class EditAssociation extends HookConsumerWidget { final localizeWithContext = AppLocalizations.of(context)!; final navigatorWithContext = Navigator.of(context); - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 40), - child: SingleChildScrollView( - child: Column( - children: [ - if (View.of(context).viewInsets.bottom == 0) - AsyncChild( - value: associationLogo, - builder: (context, associationLogo) { - return Center( - child: Stack( - clipBehavior: Clip.none, - children: [ - Container( - decoration: BoxDecoration( - shape: BoxShape.circle, - boxShadow: [ - BoxShadow( - color: Colors.black.withValues(alpha: 0.1), - spreadRadius: 5, - blurRadius: 10, - offset: const Offset(2, 3), - ), - ], - ), - child: CircleAvatar( - radius: 80, - backgroundImage: Image( - image: associationLogo.image, - ).image, - ), + return SingleChildScrollView( + child: Column( + children: [ + if (View.of(context).viewInsets.bottom == 0) + AsyncChild( + value: associationLogo, + builder: (context, associationLogo) { + return Center( + child: Stack( + clipBehavior: Clip.none, + children: [ + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.1), + spreadRadius: 5, + blurRadius: 10, + offset: const Offset(2, 3), + ), + ], ), - Positioned( - bottom: 0, - left: 0, - child: GestureDetector( - onTap: () async { - final value = await associationLogoNotifier - .setLogo(ImageSource.gallery, association.id); - if (value != null) { - if (value) { - displayToastWithContext( - TypeMsg.msg, - localizeWithContext - .adminUpdatedAssociationLogo, - ); - } else { - displayToastWithContext( - TypeMsg.error, - localizeWithContext.adminTooHeavyLogo, - ); - } + child: CircleAvatar( + radius: 80, + backgroundImage: Image( + image: associationLogo.image, + ).image, + ), + ), + Positioned( + bottom: 0, + left: 0, + child: GestureDetector( + onTap: () async { + final value = await associationLogoNotifier.setLogo( + ImageSource.gallery, + association.id, + ); + if (value != null) { + if (value) { + displayToastWithContext( + TypeMsg.msg, + localizeWithContext + .adminUpdatedAssociationLogo, + ); } else { displayToastWithContext( TypeMsg.error, - localizeWithContext - .adminFailedToUpdateAssociationLogo, + localizeWithContext.adminTooHeavyLogo, ); } - }, - child: const PictureButton(icon: HeroIcons.photo), - ), + } else { + displayToastWithContext( + TypeMsg.error, + localizeWithContext + .adminFailedToUpdateAssociationLogo, + ); + } + }, + child: const PictureButton(icon: HeroIcons.photo), ), - Positioned( - bottom: 0, - right: 0, - child: GestureDetector( - onTap: () async { - final value = await associationLogoNotifier - .setLogo(ImageSource.camera, association.id); - if (value != null) { - if (value) { - displayToastWithContext( - TypeMsg.msg, - localizeWithContext - .adminUpdatedAssociationLogo, - ); - } else { - displayToastWithContext( - TypeMsg.error, - localizeWithContext.adminTooHeavyLogo, - ); - } + ), + Positioned( + bottom: 0, + right: 0, + child: GestureDetector( + onTap: () async { + final value = await associationLogoNotifier.setLogo( + ImageSource.camera, + association.id, + ); + if (value != null) { + if (value) { + displayToastWithContext( + TypeMsg.msg, + localizeWithContext + .adminUpdatedAssociationLogo, + ); } else { displayToastWithContext( TypeMsg.error, - localizeWithContext - .adminFailedToUpdateAssociationLogo, + localizeWithContext.adminTooHeavyLogo, ); } - }, - child: const PictureButton(icon: HeroIcons.camera), - ), + } else { + displayToastWithContext( + TypeMsg.error, + localizeWithContext + .adminFailedToUpdateAssociationLogo, + ); + } + }, + child: const PictureButton(icon: HeroIcons.camera), ), - ], - ), - ); - }, - ), - SizedBox(height: View.of(context).viewInsets.bottom == 0 ? 30 : 10), - TextEntry( - label: localizeWithContext.adminAssociationName, - controller: nameController, + ), + ], + ), + ); + }, ), - SizedBox(height: 20), - ListItem( - title: localizeWithContext.adminManagerGroup( - chosenGroup.value!.name, - ), - onTap: () async { - FocusScope.of(context).unfocus(); - final ctx = context; - await Future.delayed(Duration(milliseconds: 150)); - if (!ctx.mounted) return; + SizedBox(height: View.of(context).viewInsets.bottom == 0 ? 30 : 10), + TextEntry( + label: localizeWithContext.adminAssociationName, + controller: nameController, + ), + SizedBox(height: 20), + ListItem( + title: localizeWithContext.adminManagerGroup( + chosenGroup.value!.name, + ), + onTap: () async { + FocusScope.of(context).unfocus(); + final ctx = context; + await Future.delayed(Duration(milliseconds: 150)); + if (!ctx.mounted) return; - await showCustomBottomModal( - context: ctx, - ref: ref, - modal: BottomModalTemplate( - title: localizeWithContext.adminChooseGroup, - child: Column( - children: [ - ...groups.map( - (e) => ListItem( - title: e.name, - onTap: () { - chosenGroup.value = e; - Navigator.of(ctx).pop(); - }, - ), + await showCustomBottomModal( + context: ctx, + ref: ref, + modal: BottomModalTemplate( + title: localizeWithContext.adminChooseGroup, + child: Column( + children: [ + ...groups.map( + (e) => ListItem( + title: e.name, + onTap: () { + chosenGroup.value = e; + Navigator.of(ctx).pop(); + }, ), - ], - ), + ), + ], ), + ), + ); + }, + ), + SizedBox(height: 20), + Button( + text: localizeWithContext.globalConfirm, + disabled: + !(nameController.value.text != association.name || + chosenGroup.value!.id != association.groupId), + onPressed: () async { + await tokenExpireWrapper(ref, () async { + final newAssociation = association.copyWith( + name: nameController.value.text, + groupId: chosenGroup.value!.id, ); - }, - ), - SizedBox(height: 20), - Button( - text: localizeWithContext.globalConfirm, - disabled: - !(nameController.value.text != association.name || - chosenGroup.value!.id != association.groupId), - onPressed: () async { - await tokenExpireWrapper(ref, () async { - final newAssociation = association.copyWith( - name: nameController.value.text, - groupId: chosenGroup.value!.id, + final value = await associationListNotifier.updateAssociation( + newAssociation, + ); + if (value) { + displayToastWithContext( + TypeMsg.msg, + localizeWithContext.adminAssociationUpdated, ); - final value = await associationListNotifier.updateAssociation( - newAssociation, + navigatorWithContext.pop(); + } else { + displayToastWithContext( + TypeMsg.error, + localizeWithContext.adminAssociationUpdateError, ); - if (value) { - displayToastWithContext( - TypeMsg.msg, - localizeWithContext.adminAssociationUpdated, - ); - navigatorWithContext.pop(); - } else { - displayToastWithContext( - TypeMsg.error, - localizeWithContext.adminAssociationUpdateError, - ); - } - }); - }, - ), - ], - ), + } + }); + }, + ), + ], ), ); } From 8ac317a9a0f000dd96571985cceadc5fe491b6a0 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 24 Aug 2025 00:20:21 +0200 Subject: [PATCH 347/473] feat: phonebook ui fixups --- .../ui/components/groupement_bar.dart | 5 +- lib/phonebook/ui/components/member_card.dart | 2 +- .../groupement_add_edit_page.dart | 8 +- .../ui/pages/admin_page/admin_page.dart | 66 +++--- .../association_add_edit_page.dart | 224 +++++++++--------- .../association_groups_page.dart | 10 +- .../association_members_page.dart | 16 +- .../member_edition_modal.dart | 2 +- .../ui/pages/main_page/main_page.dart | 5 +- .../member_detail_page.dart | 5 +- .../membership_editor_page.dart | 39 +-- .../membership_editor_page/search_result.dart | 31 ++- .../user_search_modal.dart | 9 +- 13 files changed, 239 insertions(+), 183 deletions(-) diff --git a/lib/phonebook/ui/components/groupement_bar.dart b/lib/phonebook/ui/components/groupement_bar.dart index c44b4899cd..de74b937b3 100644 --- a/lib/phonebook/ui/components/groupement_bar.dart +++ b/lib/phonebook/ui/components/groupement_bar.dart @@ -67,7 +67,7 @@ class AssociationGroupementBar extends HookConsumerWidget { ); }, ), - SizedBox(height: 30), + SizedBox(height: 20), Button.danger( text: localizeWithContext.phonebookDelete, onPressed: () async { @@ -109,8 +109,9 @@ class AssociationGroupementBar extends HookConsumerWidget { width: MediaQuery.of(context).size.width, height: scrollDirection == Axis.horizontal ? 40 - : min(associationGroupements.length * 50, 220), + : min(associationGroupements.length * 52, 220), child: ListView.builder( + physics: const BouncingScrollPhysics(), scrollDirection: scrollDirection, itemCount: editable ? associationGroupements.length + 1 diff --git a/lib/phonebook/ui/components/member_card.dart b/lib/phonebook/ui/components/member_card.dart index d2e6e61102..40364e5981 100644 --- a/lib/phonebook/ui/components/member_card.dart +++ b/lib/phonebook/ui/components/member_card.dart @@ -83,7 +83,7 @@ class MemberCard extends HookConsumerWidget { memberNotifier.setCompleteMember(member); QR.to(PhonebookRouter.root + PhonebookRouter.memberDetail); }, - trailing: !editable + trailing: editable ? const HeroIcon( HeroIcons.chevronRight, color: ColorConstants.tertiary, diff --git a/lib/phonebook/ui/pages/add_edit_groupement_page/groupement_add_edit_page.dart b/lib/phonebook/ui/pages/add_edit_groupement_page/groupement_add_edit_page.dart index 0d2b960edc..14d13b32a6 100644 --- a/lib/phonebook/ui/pages/add_edit_groupement_page/groupement_add_edit_page.dart +++ b/lib/phonebook/ui/pages/add_edit_groupement_page/groupement_add_edit_page.dart @@ -34,10 +34,10 @@ class AssociationGroupementAddEditPage extends HookConsumerWidget { return PhonebookTemplate( child: Padding( - padding: EdgeInsets.symmetric(horizontal: 30.0), + padding: EdgeInsets.symmetric(horizontal: 20.0), child: Column( children: [ - const SizedBox(height: 30), + const SizedBox(height: 20), Align( alignment: Alignment.centerLeft, child: Text( @@ -51,13 +51,13 @@ class AssociationGroupementAddEditPage extends HookConsumerWidget { ), ), ), - const SizedBox(height: 30), + const SizedBox(height: 20), TextEntry( controller: name, label: localizeWithContext.phonebookGroupementName, canBeEmpty: false, ), - const SizedBox(height: 50), + const SizedBox(height: 20), Button( text: associationGroupement.id != "" ? localizeWithContext.phonebookEdit diff --git a/lib/phonebook/ui/pages/admin_page/admin_page.dart b/lib/phonebook/ui/pages/admin_page/admin_page.dart index e52ecaefb4..0e8a0b6a49 100644 --- a/lib/phonebook/ui/pages/admin_page/admin_page.dart +++ b/lib/phonebook/ui/pages/admin_page/admin_page.dart @@ -13,10 +13,11 @@ import 'package:titan/phonebook/router.dart'; import 'package:titan/phonebook/ui/components/association_research_bar.dart'; import 'package:titan/phonebook/ui/phonebook.dart'; import 'package:titan/phonebook/ui/pages/admin_page/editable_association_card.dart'; +import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; import 'package:qlevar_router/qlevar_router.dart'; -import 'package:titan/tools/ui/styleguide/list_item_template.dart'; +import 'package:titan/tools/ui/styleguide/icon_button.dart'; import 'package:tuple/tuple.dart'; import 'package:titan/l10n/app_localizations.dart'; @@ -49,44 +50,51 @@ class AdminPage extends HookConsumerWidget { await roleNotifier.loadRolesTags(); }, child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + padding: const EdgeInsets.symmetric(horizontal: 20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - AssociationResearchBar(), - const SizedBox(height: 10), - Text( - localizeWithContext.phonebookAdmin, - style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + const SizedBox(height: 20), + Row( + children: [ + Text( + "Associations", + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: ColorConstants.title, + ), + ), + const Spacer(), + CustomIconButton( + icon: HeroIcon( + HeroIcons.plus, + color: Colors.white, + size: 30, + ), + onPressed: isPhonebookAdmin + ? () { + associationNotifier.resetAssociation(); + associationGroupementNotifier + .resetAssociationGroupement(); + QR.to( + PhonebookRouter.root + + PhonebookRouter.admin + + PhonebookRouter.addEditAssociation, + ); + } + : () {}, + ), + ], ), + const SizedBox(height: 20), + AssociationResearchBar(), const SizedBox(height: 10), Async2Children( values: Tuple2(associationList, associationGroupementList), builder: (context, associations, associationGroupements) { return Column( children: [ - ListItemTemplate( - title: localizeWithContext.phonebookAddAssociation, - icon: HeroIcon( - HeroIcons.plus, - size: 40, - color: Colors.grey.shade500, - ), - trailing: SizedBox.shrink(), - onTap: isPhonebookAdmin - ? () { - associationNotifier.resetAssociation(); - associationGroupementNotifier - .resetAssociationGroupement(); - QR.to( - PhonebookRouter.root + - PhonebookRouter.admin + - PhonebookRouter.addEditAssociation, - ); - } - : null, - ), - SizedBox(height: 5), if (associations.isEmpty) Center( child: Text( diff --git a/lib/phonebook/ui/pages/association_add_edit_page/association_add_edit_page.dart b/lib/phonebook/ui/pages/association_add_edit_page/association_add_edit_page.dart index 6717342f53..55c1d94ade 100644 --- a/lib/phonebook/ui/pages/association_add_edit_page/association_add_edit_page.dart +++ b/lib/phonebook/ui/pages/association_add_edit_page/association_add_edit_page.dart @@ -55,13 +55,13 @@ class AssociationAddEditPage extends HookConsumerWidget { ), child: Form( key: key, - child: Padding( - padding: EdgeInsets.symmetric(horizontal: 30.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 30), - Align( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 20), + Padding( + padding: EdgeInsets.symmetric(horizontal: 20.0), + child: Align( alignment: Alignment.centerLeft, child: Text( association.id == "" @@ -74,128 +74,138 @@ class AssociationAddEditPage extends HookConsumerWidget { ), ), ), - if (association.id != "") ...[ - const SizedBox(height: 30), - AsyncChild( - value: associationPicture, - builder: (context, image) { - return Center( - child: Stack( - clipBehavior: Clip.none, - children: [ - Container( - decoration: BoxDecoration( - shape: BoxShape.circle, - boxShadow: [ - BoxShadow( - color: Colors.black.withValues(alpha: 0.1), - spreadRadius: 5, - blurRadius: 10, - offset: const Offset(2, 3), - ), - ], - ), - child: CircleAvatar( - radius: 80, - backgroundColor: Colors.white, - backgroundImage: image.image, - ), + ), + if (association.id != "") ...[ + const SizedBox(height: 30), + AsyncChild( + value: associationPicture, + builder: (context, image) { + return Center( + child: Stack( + clipBehavior: Clip.none, + children: [ + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.1), + spreadRadius: 5, + blurRadius: 10, + offset: const Offset(2, 3), + ), + ], + ), + child: CircleAvatar( + radius: 80, + backgroundColor: Colors.white, + backgroundImage: image.image, ), - Positioned( - bottom: 0, - left: 0, - child: GestureDetector( - onTap: () async { - final value = await associationPictureNotifier - .setProfilePicture( - ImageSource.gallery, - association.id, - ); - if (value != null) { - if (value) { - showSnackBarWithContext( - localizeWithContext - .settingsUpdatedProfilePicture, - ); - } else { - showSnackBarWithContext( - localizeWithContext - .settingsTooHeavyProfilePicture, - ); - } + ), + Positioned( + bottom: 0, + left: 0, + child: GestureDetector( + onTap: () async { + final value = await associationPictureNotifier + .setProfilePicture( + ImageSource.gallery, + association.id, + ); + if (value != null) { + if (value) { + showSnackBarWithContext( + localizeWithContext + .settingsUpdatedProfilePicture, + ); } else { showSnackBarWithContext( localizeWithContext - .settingsErrorProfilePicture, + .settingsTooHeavyProfilePicture, ); } - }, - child: const PictureButton( - icon: HeroIcons.photo, - ), - ), + } else { + showSnackBarWithContext( + localizeWithContext + .settingsErrorProfilePicture, + ); + } + }, + child: const PictureButton(icon: HeroIcons.photo), ), - Positioned( - bottom: 0, - right: 0, - child: GestureDetector( - onTap: () async { - final value = await associationPictureNotifier - .setProfilePicture( - ImageSource.camera, - association.id, - ); - if (value != null) { - if (value) { - showSnackBarWithContext( - localizeWithContext - .settingsUpdatedProfilePicture, - ); - } else { - showSnackBarWithContext( - localizeWithContext - .settingsTooHeavyProfilePicture, - ); - } + ), + Positioned( + bottom: 0, + right: 0, + child: GestureDetector( + onTap: () async { + final value = await associationPictureNotifier + .setProfilePicture( + ImageSource.camera, + association.id, + ); + if (value != null) { + if (value) { + showSnackBarWithContext( + localizeWithContext + .settingsUpdatedProfilePicture, + ); } else { showSnackBarWithContext( localizeWithContext - .settingsErrorProfilePicture, + .settingsTooHeavyProfilePicture, ); } - }, - child: const PictureButton( - icon: HeroIcons.camera, - ), + } else { + showSnackBarWithContext( + localizeWithContext + .settingsErrorProfilePicture, + ); + } + }, + child: const PictureButton( + icon: HeroIcons.camera, ), ), - ], - ), - ); - }, - ), - ], - const SizedBox(height: 20), - Text( + ), + ], + ), + ); + }, + ), + ], + const SizedBox(height: 20), + Padding( + padding: EdgeInsets.symmetric(horizontal: 20.0), + child: Text( localizeWithContext.phonebookAssociationGroupement, style: TextStyle(fontSize: 16, fontWeight: FontWeight.normal), ), - const SizedBox(height: 5), - AssociationGroupementBar(editable: true), - Container(margin: const EdgeInsets.symmetric(vertical: 10)), - TextEntry( + ), + const SizedBox(height: 10), + AssociationGroupementBar(editable: true), + Container(margin: const EdgeInsets.symmetric(vertical: 10)), + Padding( + padding: EdgeInsets.symmetric(horizontal: 20.0), + child: TextEntry( controller: name, label: localizeWithContext.phonebookAssociationName, canBeEmpty: false, ), - const SizedBox(height: 10), - TextEntry( + ), + const SizedBox(height: 10), + Padding( + padding: EdgeInsets.symmetric(horizontal: 20.0), + child: TextEntry( controller: description, label: localizeWithContext.phonebookDescription, canBeEmpty: true, ), - const SizedBox(height: 50), - Button( + ), + const SizedBox(height: 30), + Padding( + padding: EdgeInsets.symmetric(horizontal: 20.0), + child: Button( onPressed: () async { if (!key.currentState!.validate()) { showSnackBarWithContext( @@ -279,9 +289,9 @@ class AssociationAddEditPage extends HookConsumerWidget { ? localizeWithContext.phonebookEdit : localizeWithContext.phonebookAdd, ), - SizedBox(height: 80), - ], - ), + ), + SizedBox(height: 80), + ], ), ), ), diff --git a/lib/phonebook/ui/pages/association_groups_page/association_groups_page.dart b/lib/phonebook/ui/pages/association_groups_page/association_groups_page.dart index 43942ea55e..8c086bdf84 100644 --- a/lib/phonebook/ui/pages/association_groups_page/association_groups_page.dart +++ b/lib/phonebook/ui/pages/association_groups_page/association_groups_page.dart @@ -8,6 +8,7 @@ import 'package:titan/phonebook/providers/association_groupement_provider.dart'; import 'package:titan/phonebook/providers/association_list_provider.dart'; import 'package:titan/phonebook/providers/association_provider.dart'; import 'package:titan/phonebook/ui/phonebook.dart'; +import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; @@ -60,13 +61,18 @@ class AssociationGroupsPage extends HookConsumerWidget { }); }, child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 30), + padding: const EdgeInsets.symmetric(horizontal: 20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + const SizedBox(height: 20), Text( localizeWithContext.phonebookGroups(association.name), - style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: ColorConstants.title, + ), ), const SizedBox(height: 20), AsyncChild( diff --git a/lib/phonebook/ui/pages/association_members_page/association_members_page.dart b/lib/phonebook/ui/pages/association_members_page/association_members_page.dart index 72410765cb..f6a49db1c9 100644 --- a/lib/phonebook/ui/pages/association_members_page/association_members_page.dart +++ b/lib/phonebook/ui/pages/association_members_page/association_members_page.dart @@ -13,6 +13,7 @@ import 'package:titan/phonebook/providers/membership_provider.dart'; import 'package:titan/phonebook/router.dart'; import 'package:titan/phonebook/ui/components/member_card.dart'; import 'package:titan/phonebook/ui/phonebook.dart'; +import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; @@ -57,9 +58,14 @@ class AssociationMembersPage extends HookConsumerWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + const SizedBox(height: 20), Text( localizeWithContext.phonebookMembers(association.name), - style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: ColorConstants.title, + ), ), if (!association.deactivated) ...[ SizedBox(height: 20), @@ -105,8 +111,12 @@ class AssociationMembersPage extends HookConsumerWidget { ? Text(localizeWithContext.phonebookNoMember) : !association.deactivated ? SizedBox( - height: MediaQuery.of(context).size.height * 0.7, + height: MediaQuery.of(context).size.height - 120, child: ReorderableListView( + physics: const BouncingScrollPhysics(), + proxyDecorator: (child, index, animation) { + return Transform.scale(scale: 1.05, child: child); + }, onReorder: (int oldIndex, int newIndex) async { await tokenExpireWrapper(ref, () async { final result = await associationMemberListNotifier @@ -164,7 +174,7 @@ class AssociationMembersPage extends HookConsumerWidget { .toList(), ), ), - SizedBox(height: 80), + SizedBox(height: 20), ], ), ), diff --git a/lib/phonebook/ui/pages/association_members_page/member_edition_modal.dart b/lib/phonebook/ui/pages/association_members_page/member_edition_modal.dart index a532040ecd..88b0141c9a 100644 --- a/lib/phonebook/ui/pages/association_members_page/member_edition_modal.dart +++ b/lib/phonebook/ui/pages/association_members_page/member_edition_modal.dart @@ -69,7 +69,7 @@ class MemberEditionModal extends HookConsumerWidget { } }, ), - SizedBox(height: 5), + SizedBox(height: 20), Button.danger( text: localizeWithContext.phonebookDeleteRole, onPressed: () { diff --git a/lib/phonebook/ui/pages/main_page/main_page.dart b/lib/phonebook/ui/pages/main_page/main_page.dart index 2006a98437..b314521419 100644 --- a/lib/phonebook/ui/pages/main_page/main_page.dart +++ b/lib/phonebook/ui/pages/main_page/main_page.dart @@ -48,9 +48,10 @@ class PhonebookMainPage extends HookConsumerWidget { await associationListNotifier.loadAssociations(); }, child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + padding: const EdgeInsets.symmetric(horizontal: 20), child: Column( children: [ + const SizedBox(height: 10), Row( children: [ Expanded(child: AssociationResearchBar()), @@ -96,7 +97,7 @@ class PhonebookMainPage extends HookConsumerWidget { ); }, ), - SizedBox(height: 80), + const SizedBox(height: 20), ], ), ), diff --git a/lib/phonebook/ui/pages/member_detail_page/member_detail_page.dart b/lib/phonebook/ui/pages/member_detail_page/member_detail_page.dart index ea76ce576f..71dc5cc3bc 100644 --- a/lib/phonebook/ui/pages/member_detail_page/member_detail_page.dart +++ b/lib/phonebook/ui/pages/member_detail_page/member_detail_page.dart @@ -30,11 +30,12 @@ class MemberDetailPage extends HookConsumerWidget { return PhonebookTemplate( child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 30), + padding: const EdgeInsets.symmetric(horizontal: 20), child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + const SizedBox(height: 20), Center( child: Column( children: [ @@ -129,7 +130,7 @@ class MemberDetailPage extends HookConsumerWidget { ], ), ), - const SizedBox(height: 80), + const SizedBox(height: 20), ], ), ), diff --git a/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart b/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart index 7e61409a35..3af60495d0 100644 --- a/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart +++ b/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/phonebook/class/membership.dart'; import 'package:titan/phonebook/providers/association_member_list_provider.dart'; @@ -10,11 +9,12 @@ import 'package:titan/phonebook/providers/phonebook_admin_provider.dart'; import 'package:titan/phonebook/providers/roles_tags_provider.dart'; import 'package:titan/phonebook/ui/pages/membership_editor_page/user_search_modal.dart'; import 'package:titan/phonebook/ui/phonebook.dart'; +import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; import 'package:titan/tools/ui/styleguide/button.dart'; -import 'package:titan/tools/ui/styleguide/list_item_template.dart'; +import 'package:titan/tools/ui/styleguide/list_item.dart'; import 'package:titan/tools/ui/styleguide/list_item_toggle.dart'; import 'package:titan/tools/ui/widgets/text_entry.dart'; import 'package:qlevar_router/qlevar_router.dart'; @@ -135,36 +135,43 @@ class MembershipEditorPage extends HookConsumerWidget { return PhonebookTemplate( child: Padding( - padding: const EdgeInsets.all(30.0), + padding: const EdgeInsets.symmetric(horizontal: 20.0), child: SingleChildScrollView( child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ + const SizedBox(height: 20), if (!isEdit) ...[ Text( localizeWithContext.phonebookAddMember, - style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: ColorConstants.title, + ), ), - ListItemTemplate( - icon: const HeroIcon(HeroIcons.magnifyingGlass), + const SizedBox(height: 20), + ListItem( title: member.member.id == "" ? localizeWithContext.phonebookSearchUser : member.member.getName(), - subtitle: member.member.id == "" - ? null - : localizeWithContext.phonebookSearchUser, - trailing: const HeroIcon(HeroIcons.plus), - onTap: () => showCustomBottomModal( + onTap: () async {showCustomBottomModal( context: context, modal: UserSearchModal(), ref: ref, - ), + ); + }, ), ] else Text( localizeWithContext.phonebookModifyMembership( member.member.nickname ?? member.getName(), ), - style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: ColorConstants.title, + ), ), const SizedBox(height: 10), rolesTagList.maybeWhen( @@ -199,12 +206,12 @@ class MembershipEditorPage extends HookConsumerWidget { ); }, ), - const SizedBox(height: 30), + const SizedBox(height: 20), TextEntry( controller: apparentNameController, label: localizeWithContext.phonebookApparentName, ), - const SizedBox(height: 50), + const SizedBox(height: 30), Button( text: isEdit ? localizeWithContext.phonebookEdit @@ -234,7 +241,7 @@ class MembershipEditorPage extends HookConsumerWidget { }); }, ), - const SizedBox(height: 80), + const SizedBox(height: 20), ], ), ), diff --git a/lib/phonebook/ui/pages/membership_editor_page/search_result.dart b/lib/phonebook/ui/pages/membership_editor_page/search_result.dart index 4fd44fbe3d..166599fbd5 100644 --- a/lib/phonebook/ui/pages/membership_editor_page/search_result.dart +++ b/lib/phonebook/ui/pages/membership_editor_page/search_result.dart @@ -1,7 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/phonebook/class/member.dart'; import 'package:titan/phonebook/providers/complete_member_provider.dart'; +import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/styleguide/list_item_template.dart'; import 'package:titan/user/providers/user_list_provider.dart'; @@ -15,27 +17,30 @@ class SearchResult extends HookConsumerWidget { final usersNotifier = ref.watch(userList.notifier); final memberNotifier = ref.watch(completeMemberProvider.notifier); - return users.when( - data: (usersData) { + return AsyncChild( + value: users, + builder: (context, usersData) { return Column( children: usersData .map( - (user) => ListItemTemplate( - title: user.getName(), - onTap: () { - memberNotifier.setMember(Member.fromUser(user)); - queryController.text = user.getName(); - usersNotifier.clear(); - memberNotifier.loadMemberComplete(); - Navigator.of(context).pop(); - }, + (user) => Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: ListItemTemplate( + title: user.getName(), + trailing: const HeroIcon(HeroIcons.plus), + onTap: () { + memberNotifier.setMember(Member.fromUser(user)); + queryController.text = user.getName(); + usersNotifier.clear(); + memberNotifier.loadMemberComplete(); + Navigator.of(context).pop(); + }, + ), ), ) .toList(), ); }, - loading: () => const Center(child: CircularProgressIndicator()), - error: (e, s) => Text(e.toString()), ); } } diff --git a/lib/phonebook/ui/pages/membership_editor_page/user_search_modal.dart b/lib/phonebook/ui/pages/membership_editor_page/user_search_modal.dart index d5c8a69092..2148f64414 100644 --- a/lib/phonebook/ui/pages/membership_editor_page/user_search_modal.dart +++ b/lib/phonebook/ui/pages/membership_editor_page/user_search_modal.dart @@ -36,7 +36,14 @@ class UserSearchModal extends HookConsumerWidget { } }), ), - SearchResult(queryController: textController), + SizedBox(height: 10), + ConstrainedBox( + constraints: const BoxConstraints(maxHeight: 280), + child: SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: SearchResult(queryController: textController), + ), + ), ], ), ), From adfd14c9442f57293d0d82c9cfb365bf4718a948 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 24 Aug 2025 00:20:21 +0200 Subject: [PATCH 348/473] feat: settings ui fixups --- .../ui/pages/main_page/edit_profile.dart | 361 +++++++++--------- .../ui/pages/main_page/main_page.dart | 69 ++-- 2 files changed, 216 insertions(+), 214 deletions(-) diff --git a/lib/settings/ui/pages/main_page/edit_profile.dart b/lib/settings/ui/pages/main_page/edit_profile.dart index 18dfdd435f..11c6686b28 100644 --- a/lib/settings/ui/pages/main_page/edit_profile.dart +++ b/lib/settings/ui/pages/main_page/edit_profile.dart @@ -9,8 +9,8 @@ import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/styleguide/button.dart'; -import 'package:titan/tools/ui/styleguide/icon_button.dart'; import 'package:titan/tools/ui/styleguide/text_entry.dart'; +import 'package:titan/tools/ui/widgets/date_entry.dart'; import 'package:titan/user/providers/profile_picture_provider.dart'; import 'package:titan/user/providers/user_provider.dart'; @@ -40,214 +40,197 @@ class EditProfile extends HookConsumerWidget { final localizeWithContext = AppLocalizations.of(context)!; final navigatorWithContext = Navigator.of(context); - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 40), - child: SingleChildScrollView( - child: Column( - children: [ - if (View.of(context).viewInsets.bottom == 0) - AsyncChild( - value: profilePicture, - builder: (context, profile) { - return Center( - child: Stack( - clipBehavior: Clip.none, - children: [ - Container( - decoration: BoxDecoration( - shape: BoxShape.circle, - boxShadow: [ - BoxShadow( - color: Colors.black.withValues(alpha: 0.1), - spreadRadius: 5, - blurRadius: 10, - offset: const Offset(2, 3), - ), - ], - ), - child: CircleAvatar( - radius: 80, - backgroundImage: profile.isEmpty - ? const AssetImage('assets/images/profile.png') - : Image.memory(profile).image, - ), + return SingleChildScrollView( + child: Column( + children: [ + if (View.of(context).viewInsets.bottom == 0) + AsyncChild( + value: profilePicture, + builder: (context, profile) { + return Center( + child: Stack( + clipBehavior: Clip.none, + children: [ + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.1), + spreadRadius: 5, + blurRadius: 10, + offset: const Offset(2, 3), + ), + ], + ), + child: CircleAvatar( + radius: 80, + backgroundImage: profile.isEmpty + ? const AssetImage('assets/images/profile.png') + : Image.memory(profile).image, ), - Positioned( - bottom: 0, - left: 0, - child: GestureDetector( - onTap: () async { - final value = await profilePictureNotifier - .setProfilePicture(ImageSource.gallery); - if (value != null) { - if (value) { - displayToastWithContext( - TypeMsg.msg, - localizeWithContext - .settingsUpdatedProfilePicture, - ); - } else { - displayToastWithContext( - TypeMsg.error, - localizeWithContext - .settingsTooHeavyProfilePicture, - ); - } + ), + Positioned( + bottom: 0, + left: 0, + child: GestureDetector( + onTap: () async { + final value = await profilePictureNotifier + .setProfilePicture(ImageSource.gallery); + if (value != null) { + if (value) { + displayToastWithContext( + TypeMsg.msg, + localizeWithContext + .settingsUpdatedProfilePicture, + ); } else { displayToastWithContext( TypeMsg.error, localizeWithContext - .settingsErrorProfilePicture, + .settingsTooHeavyProfilePicture, ); } - }, - child: const PictureButton(icon: HeroIcons.photo), - ), + } else { + displayToastWithContext( + TypeMsg.error, + localizeWithContext.settingsErrorProfilePicture, + ); + } + }, + child: const PictureButton(icon: HeroIcons.photo), ), - Positioned( - bottom: 0, - right: 0, - child: GestureDetector( - onTap: () async { - final value = await profilePictureNotifier - .setProfilePicture(ImageSource.camera); - if (value != null) { - if (value) { - displayToastWithContext( - TypeMsg.msg, - localizeWithContext - .settingsUpdatedProfilePicture, - ); - } else { - displayToastWithContext( - TypeMsg.error, - localizeWithContext - .settingsTooHeavyProfilePicture, - ); - } + ), + Positioned( + bottom: 0, + right: 0, + child: GestureDetector( + onTap: () async { + final value = await profilePictureNotifier + .setProfilePicture(ImageSource.camera); + if (value != null) { + if (value) { + displayToastWithContext( + TypeMsg.msg, + localizeWithContext + .settingsUpdatedProfilePicture, + ); } else { displayToastWithContext( TypeMsg.error, localizeWithContext - .settingsErrorProfilePicture, + .settingsTooHeavyProfilePicture, ); } - }, - child: const PictureButton(icon: HeroIcons.camera), - ), + } else { + displayToastWithContext( + TypeMsg.error, + localizeWithContext.settingsErrorProfilePicture, + ); + } + }, + child: const PictureButton(icon: HeroIcons.camera), ), - Positioned( - bottom: -20, - right: 60, - child: GestureDetector( - onTap: () async { - final value = await profilePictureNotifier - .cropImage(); - if (value != null) { - if (value) { - displayToastWithContext( - TypeMsg.msg, - localizeWithContext - .settingsUpdatedProfilePicture, - ); - } else { - displayToastWithContext( - TypeMsg.error, - localizeWithContext - .settingsErrorProfilePicture, - ); - } + ), + Positioned( + bottom: -20, + right: 60, + child: GestureDetector( + onTap: () async { + final value = await profilePictureNotifier + .cropImage(); + if (value != null) { + if (value) { + displayToastWithContext( + TypeMsg.msg, + localizeWithContext + .settingsUpdatedProfilePicture, + ); + } else { + displayToastWithContext( + TypeMsg.error, + localizeWithContext + .settingsErrorProfilePicture, + ); } - }, - child: const PictureButton( - icon: HeroIcons.sparkles, - ), - ), + } + }, + child: const PictureButton(icon: HeroIcons.sparkles), ), - ], - ), - ); - }, - ), - SizedBox(height: View.of(context).viewInsets.bottom == 0 ? 30 : 10), - TextEntry( - label: localizeWithContext.settingsEmail, - controller: emailController, - enabled: false, - ), - SizedBox(height: 20), - TextEntry( - label: localizeWithContext.settingsPhoneNumber, - controller: phoneController, - textInputAction: TextInputAction.done, - ), - SizedBox(height: 20), - Row( - children: [ - Expanded( - child: TextEntry( - label: localizeWithContext.settingsBirthday, - controller: birthdayController, - enabled: false, + ), + ], ), - ), - CustomIconButton( - icon: const Icon(Icons.calendar_today, color: Colors.white), - onPressed: () async { - final date = await showDatePicker( - context: context, - initialDate: DateTime(2004), - firstDate: DateTime(1900), - lastDate: DateTime.now(), - ); - if (date != null) { - birthdayController.text = - "${date.day.toString().padLeft(2, '0')}/${date.month.toString().padLeft(2, '0')}/${date.year}"; - } - }, - ), - ], - ), - SizedBox(height: 30), - Button( - text: localizeWithContext.settingsValidate, - disabled: - !(phoneController.value.text != me.phone || - birthdayController.value.text != - "${me.birthday!.day.toString().padLeft(2, '0')}/${me.birthday!.month.toString().padLeft(2, '0')}/${me.birthday!.year}"), - onPressed: () async { - if (phoneController.value.text != me.phone || - birthdayController.value.text.isNotEmpty) { - await tokenExpireWrapper(ref, () async { - final newMe = me.copyWith( - birthday: birthdayController.value.text.isNotEmpty - ? DateTime.parse( - processDateBack(birthdayController.value.text), - ) - : null, - phone: phoneController.value.text.isEmpty - ? null - : phoneController.value.text, - ); - final value = await asyncUserNotifier.updateMe(newMe); - if (value) { - displayToastWithContext( - TypeMsg.msg, - localizeWithContext.settingsEditedAccount, - ); - navigatorWithContext.pop(); - } else { - displayToastWithContext( - TypeMsg.error, - localizeWithContext.settingsFailedToEditAccount, - ); - } - }); - } + ); }, ), - ], - ), + SizedBox(height: View.of(context).viewInsets.bottom == 0 ? 30 : 10), + TextEntry( + label: localizeWithContext.settingsEmail, + controller: emailController, + enabled: false, + ), + SizedBox(height: 10), + TextEntry( + label: localizeWithContext.settingsPhoneNumber, + controller: phoneController, + textInputAction: TextInputAction.done, + ), + SizedBox(height: 10), + DateEntry( + label: localizeWithContext.settingsBirthday, + controller: birthdayController, + onTap: () async { + final date = await showDatePicker( + context: context, + initialDate: DateTime(2004), + firstDate: DateTime(1900), + lastDate: DateTime.now(), + ); + if (date != null) { + birthdayController.text = + "${date.day.toString().padLeft(2, '0')}/${date.month.toString().padLeft(2, '0')}/${date.year}"; + } + }, + ), + SizedBox(height: 30), + Button( + text: localizeWithContext.settingsValidate, + disabled: + !(phoneController.value.text != me.phone || + birthdayController.value.text != + "${me.birthday!.day.toString().padLeft(2, '0')}/${me.birthday!.month.toString().padLeft(2, '0')}/${me.birthday!.year}"), + onPressed: () async { + if (phoneController.value.text != me.phone || + birthdayController.value.text.isNotEmpty) { + await tokenExpireWrapper(ref, () async { + final newMe = me.copyWith( + birthday: birthdayController.value.text.isNotEmpty + ? DateTime.parse( + processDateBack(birthdayController.value.text), + ) + : null, + phone: phoneController.value.text.isEmpty + ? null + : phoneController.value.text, + ); + final value = await asyncUserNotifier.updateMe(newMe); + if (value) { + displayToastWithContext( + TypeMsg.msg, + localizeWithContext.settingsEditedAccount, + ); + navigatorWithContext.pop(); + } else { + displayToastWithContext( + TypeMsg.error, + localizeWithContext.settingsFailedToEditAccount, + ); + } + }); + } + }, + ), + ], ), ); } diff --git a/lib/settings/ui/pages/main_page/main_page.dart b/lib/settings/ui/pages/main_page/main_page.dart index 1608e2aede..6975fd2713 100644 --- a/lib/settings/ui/pages/main_page/main_page.dart +++ b/lib/settings/ui/pages/main_page/main_page.dart @@ -159,6 +159,7 @@ class SettingsMainPage extends HookConsumerWidget { ) : Container(), ), + const SizedBox(height: 10), ListItemTemplate( title: "🇬🇧 English", onTap: () async { @@ -220,11 +221,16 @@ class SettingsMainPage extends HookConsumerWidget { child: Column( children: [ ...uniqueTopics.map( - (notificationTopic) => ListItemTemplate( - title: notificationTopic.name, - trailing: LoadSwitchTopic( - notificationTopic: - notificationTopic, + (notificationTopic) => Padding( + padding: const EdgeInsets.symmetric( + vertical: 8.0, + ), + child: ListItemTemplate( + title: notificationTopic.name, + trailing: LoadSwitchTopic( + notificationTopic: + notificationTopic, + ), ), ), ), @@ -239,33 +245,46 @@ class SettingsMainPage extends HookConsumerWidget { CrossAxisAlignment.start, children: [ const SizedBox(height: 20), - ListItemTemplate( - title: moduleRoot, - trailing: HeroIcon( - expanded - ? HeroIcons.chevronDown - : HeroIcons - .chevronRight, - color: - ColorConstants.tertiary, + Padding( + padding: + const EdgeInsets.symmetric( + vertical: 8.0, + ), + child: ListItemTemplate( + title: moduleRoot, + trailing: HeroIcon( + expanded + ? HeroIcons + .chevronDown + : HeroIcons + .chevronRight, + color: ColorConstants + .tertiary, + ), + onTap: () { + setState(() { + expanded = !expanded; + }); + }, ), - onTap: () { - setState(() { - expanded = !expanded; - }); - }, ), const SizedBox(height: 10), if (expanded) ...topics.map( ( notificationTopic, - ) => ListItemTemplate( - title: notificationTopic - .name, - trailing: LoadSwitchTopic( - notificationTopic: - notificationTopic, + ) => Padding( + padding: + const EdgeInsets.symmetric( + vertical: 8.0, + ), + child: ListItemTemplate( + title: notificationTopic + .name, + trailing: LoadSwitchTopic( + notificationTopic: + notificationTopic, + ), ), ), ), From 6ec7dfe16e0a6bd656a9c3a4ae9592288ea11659 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 24 Aug 2025 00:20:47 +0200 Subject: [PATCH 349/473] feat: payment fixups --- lib/paiement/class/history_interval.dart | 3 + .../ui/pages/main_page/main_page.dart | 59 ++++---- .../ui/pages/main_page/tos_dialog.dart | 135 +++++++----------- 3 files changed, 91 insertions(+), 106 deletions(-) diff --git a/lib/paiement/class/history_interval.dart b/lib/paiement/class/history_interval.dart index 067ac920b4..eb8666aa5e 100644 --- a/lib/paiement/class/history_interval.dart +++ b/lib/paiement/class/history_interval.dart @@ -23,5 +23,8 @@ class HistoryInterval { DateTime.now().year, DateTime.now().month, DateTime.now().day, + 23, + 59, + 59, ); } diff --git a/lib/paiement/ui/pages/main_page/main_page.dart b/lib/paiement/ui/pages/main_page/main_page.dart index f1714ea4ac..2738ca2131 100644 --- a/lib/paiement/ui/pages/main_page/main_page.dart +++ b/lib/paiement/ui/pages/main_page/main_page.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/l10n/app_localizations.dart'; +import 'package:titan/navigation/ui/scroll_to_hide_navbar.dart'; import 'package:titan/paiement/providers/has_accepted_tos_provider.dart'; import 'package:titan/paiement/providers/my_wallet_provider.dart'; import 'package:titan/paiement/providers/tos_provider.dart'; @@ -100,33 +101,39 @@ class PaymentMainPage extends HookConsumerWidget { return PaymentTemplate( child: shouldDisplayTosDialog - ? SingleChildScrollView( - child: TOSDialogBox( - descriptions: tos.maybeWhen( - orElse: () => '', - data: (tos) => tos.tosContent, + ? ScrollToHideNavbar( + controller: ScrollController(), + child: SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: TOSDialogBox( + descriptions: tos.maybeWhen( + orElse: () => '', + data: (tos) => tos.tosContent, + ), + title: AppLocalizations.of(context)!.paiementNewCGU, + onYes: () { + tos.maybeWhen( + orElse: () {}, + data: (tos) async { + final value = await tosNotifier.signTOS( + tos.copyWith( + acceptedTosVersion: tos.latestTosVersion, + ), + ); + if (value) { + await mySellersNotifier.getMyStores(); + await myHistoryNotifier.getHistory(); + await myWalletNotifier.getMyWallet(); + shouldDisplayTosDialogNotifier.update(false); + hasAcceptedToSNotifier.update(true); + } + }, + ); + }, + onNo: () { + shouldDisplayTosDialogNotifier.update(false); + }, ), - title: AppLocalizations.of(context)!.paiementNewCGU, - onYes: () { - tos.maybeWhen( - orElse: () {}, - data: (tos) async { - final value = await tosNotifier.signTOS( - tos.copyWith(acceptedTosVersion: tos.latestTosVersion), - ); - if (value) { - await mySellersNotifier.getMyStores(); - await myHistoryNotifier.getHistory(); - await myWalletNotifier.getMyWallet(); - shouldDisplayTosDialogNotifier.update(false); - hasAcceptedToSNotifier.update(true); - } - }, - ); - }, - onNo: () { - shouldDisplayTosDialogNotifier.update(false); - }, ), ) : LayoutBuilder( diff --git a/lib/paiement/ui/pages/main_page/tos_dialog.dart b/lib/paiement/ui/pages/main_page/tos_dialog.dart index bd8a5d5fbf..12ffb1afe7 100644 --- a/lib/paiement/ui/pages/main_page/tos_dialog.dart +++ b/lib/paiement/ui/pages/main_page/tos_dialog.dart @@ -6,7 +6,7 @@ import 'package:titan/tools/ui/builders/waiting_button.dart'; class TOSDialogBox extends StatelessWidget { final String title, descriptions; - static const Color titleColor = Color.fromARGB(255, 4, 84, 84); + static const Color titleColor = ColorConstants.onMain; static const Color descriptionColor = Colors.black; static const Color yesColor = Color.fromARGB(255, 9, 103, 103); static const Color noColor = ColorConstants.background2; @@ -15,7 +15,6 @@ class TOSDialogBox extends StatelessWidget { final Function()? onNo; static const double _padding = 20; - static const double _avatarRadius = 45; static const Color background = Color(0xfffafafa); const TOSDialogBox({ @@ -28,94 +27,70 @@ class TOSDialogBox extends StatelessWidget { @override Widget build(BuildContext context) { - return Dialog( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(TOSDialogBox._padding), - ), - elevation: 0, - backgroundColor: Colors.transparent, - child: Stack( + return Container( + padding: const EdgeInsets.all(TOSDialogBox._padding), + color: ColorConstants.background, + child: Column( + mainAxisSize: MainAxisSize.min, children: [ - Container( - padding: const EdgeInsets.all(TOSDialogBox._padding), - margin: const EdgeInsets.only(top: TOSDialogBox._avatarRadius), - decoration: BoxDecoration( - shape: BoxShape.rectangle, - color: TOSDialogBox.background, - borderRadius: BorderRadius.circular(TOSDialogBox._padding), - boxShadow: [ - BoxShadow( - color: Colors.grey.shade400, - offset: const Offset(0, 5), - blurRadius: 5, - ), - ], + Text( + title, + style: const TextStyle( + fontSize: 25, + fontWeight: FontWeight.w800, + color: titleColor, ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - title, - style: const TextStyle( - fontSize: 25, - fontWeight: FontWeight.w800, - color: titleColor, - ), - ), - const SizedBox(height: 15), - MarkdownBody( - data: descriptions, - selectable: true, - styleSheet: MarkdownStyleSheet( - h2Padding: const EdgeInsets.only(top: 20.0), - textAlign: WrapAlignment.spaceAround, + ), + const SizedBox(height: 15), + MarkdownBody( + data: descriptions, + selectable: true, + styleSheet: MarkdownStyleSheet( + h2Padding: const EdgeInsets.only(top: 20.0), + textAlign: WrapAlignment.spaceAround, + ), + ), + const SizedBox(height: 22), + Align( + alignment: Alignment.bottomCenter, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + TextButton( + onPressed: () { + onNo == null ? Navigator.of(context).pop() : onNo?.call(); + }, + child: Text( + AppLocalizations.of(context)!.paiementDecline, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + color: noColor, + ), ), ), - const SizedBox(height: 22), - Align( - alignment: Alignment.bottomCenter, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - TextButton( - onPressed: () { - onNo == null - ? Navigator.of(context).pop() - : onNo?.call(); - }, - child: Text( - AppLocalizations.of(context)!.paiementDecline, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - color: noColor, - ), - ), - ), - WaitingButton( - onTap: () async { - if (onNo == null) { - Navigator.of(context).pop(); - } - await onYes(); - }, - builder: (child) => child, - child: Text( - AppLocalizations.of(context)!.paiementAccept, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - color: yesColor, - ), - ), - ), - ], + WaitingButton( + onTap: () async { + if (onNo == null) { + Navigator.of(context).pop(); + } + await onYes(); + }, + builder: (child) => child, + child: Text( + AppLocalizations.of(context)!.paiementAccept, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + color: yesColor, + ), ), ), SizedBox(height: 40), ], ), ), + const SizedBox(height: 20), ], ), ); From d4b30781df70d2f159bd91cfea8564d7de14f92a Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 24 Aug 2025 00:20:48 +0200 Subject: [PATCH 350/473] feat: vote ui adaptation --- lib/tools/ui/styleguide/list_item.dart | 7 +- lib/vote/ui/components/member_card.dart | 121 +-- lib/vote/ui/pages/admin_page/admin_page.dart | 804 +++++++----------- .../ui/pages/admin_page/contender_card.dart | 153 +--- .../ui/pages/admin_page/opening_vote.dart | 215 +++++ lib/vote/ui/pages/admin_page/section_bar.dart | 4 +- .../ui/pages/admin_page/section_chip.dart | 8 +- .../admin_page/section_contender_items.dart | 155 ++-- lib/vote/ui/pages/admin_page/voter_chip.dart | 2 +- lib/vote/ui/pages/admin_page/voters_bar.dart | 2 +- .../contender_pages/add_edit_contender.dart | 623 ++++++-------- .../contender_pages/contender_member.dart | 167 ++++ .../ui/pages/detail_page/detail_page.dart | 16 +- lib/vote/ui/pages/main_page/main_page.dart | 201 ++--- 14 files changed, 1209 insertions(+), 1269 deletions(-) create mode 100644 lib/vote/ui/pages/admin_page/opening_vote.dart create mode 100644 lib/vote/ui/pages/contender_pages/contender_member.dart diff --git a/lib/tools/ui/styleguide/list_item.dart b/lib/tools/ui/styleguide/list_item.dart index a6dfbf0ec7..7c8b243f16 100644 --- a/lib/tools/ui/styleguide/list_item.dart +++ b/lib/tools/ui/styleguide/list_item.dart @@ -24,10 +24,9 @@ class ListItem extends StatelessWidget { title: title, subtitle: subtitle, icon: icon, - trailing: const HeroIcon( - HeroIcons.chevronRight, - color: ColorConstants.tertiary, - ), + trailing: onTap != null + ? HeroIcon(HeroIcons.chevronRight, color: ColorConstants.tertiary) + : SizedBox.shrink(), ); } } diff --git a/lib/vote/ui/components/member_card.dart b/lib/vote/ui/components/member_card.dart index 0a9f7924fc..39f21b4af2 100644 --- a/lib/vote/ui/components/member_card.dart +++ b/lib/vote/ui/components/member_card.dart @@ -1,99 +1,56 @@ -import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter/material.dart'; -import 'package:heroicons/heroicons.dart'; -import 'package:titan/tools/ui/layouts/card_button.dart'; -import 'package:titan/tools/ui/layouts/card_layout.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/l10n/app_localizations.dart'; +import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; +import 'package:titan/tools/ui/styleguide/button.dart'; +import 'package:titan/tools/ui/styleguide/list_item.dart'; import 'package:titan/vote/class/members.dart'; -class MemberCard extends StatelessWidget { +class MemberCard extends ConsumerWidget { final Member member; - final Function()? onEdit, onDelete; + final Function() onEdit, onDelete; final bool isAdmin; const MemberCard({ super.key, required this.member, - this.onEdit, - this.onDelete, + required this.onEdit, + required this.onDelete, this.isAdmin = false, }); @override - Widget build(BuildContext context) { - return CardLayout( - id: member.id, - width: 150, - height: isAdmin ? 145 : 110, - margin: const EdgeInsets.all(10), - padding: const EdgeInsets.symmetric(horizontal: 17.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 10), - AutoSizeText( - member.nickname ?? member.firstname, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Colors.black, - ), - ), - const SizedBox(height: 2), - AutoSizeText( - member.nickname != null - ? '${member.firstname} ${member.name}' - : member.name, - maxLines: 2, - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: 13, - fontWeight: FontWeight.bold, - color: Colors.grey.shade400, - ), - ), - const SizedBox(height: 2), - if (!isAdmin) const Spacer(), - AutoSizeText( - member.role, - maxLines: 1, - minFontSize: 10, - overflow: TextOverflow.ellipsis, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Colors.black, - ), - ), - if (isAdmin) const Spacer(), - if (isAdmin) - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - GestureDetector( - onTap: onEdit, - child: CardButton( - color: Colors.grey.shade200, - shadowColor: Colors.grey.withValues(alpha: 0.2), - child: const HeroIcon( - HeroIcons.pencil, - color: Colors.black, - ), - ), - ), - GestureDetector( - onTap: onDelete, - child: const CardButton( - color: Colors.black, - child: HeroIcon(HeroIcons.trash, color: Colors.white), + Widget build(BuildContext context, WidgetRef ref) { + return ListItem( + title: member.getName(), + subtitle: member.role, + onTap: isAdmin + ? () async { + FocusScope.of(context).unfocus(); + final ctx = context; + await Future.delayed(Duration(milliseconds: 150)); + if (!ctx.mounted) return; + + await showCustomBottomModal( + context: ctx, + ref: ref, + modal: BottomModalTemplate( + title: member.getName(), + description: member.role, + child: Column( + children: [ + const SizedBox(height: 20), + Button( + text: AppLocalizations.of(context)!.voteEdit, + onPressed: onEdit, + ), + const SizedBox(height: 20), + Button.danger(text: "Supprimer", onPressed: onDelete), + ], ), ), - ], - ), - SizedBox(height: isAdmin ? 10 : 15), - ], - ), + ); + } + : null, ); } } diff --git a/lib/vote/ui/pages/admin_page/admin_page.dart b/lib/vote/ui/pages/admin_page/admin_page.dart index d66cd3862b..f2de57490a 100644 --- a/lib/vote/ui/pages/admin_page/admin_page.dart +++ b/lib/vote/ui/pages/admin_page/admin_page.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/amap/tools/constants.dart'; +import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/tools/constants.dart'; -import 'package:titan/tools/ui/layouts/card_layout.dart'; +import 'package:titan/tools/ui/styleguide/icon_button.dart'; import 'package:titan/tools/ui/widgets/align_left_text.dart'; import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; import 'package:titan/tools/functions.dart'; @@ -13,13 +13,17 @@ import 'package:titan/tools/ui/builders/waiting_button.dart'; import 'package:titan/user/providers/user_list_provider.dart'; import 'package:titan/vote/class/contender.dart'; import 'package:titan/vote/providers/contender_list_provider.dart'; +import 'package:titan/vote/providers/contender_members.dart'; +import 'package:titan/vote/providers/contender_provider.dart'; import 'package:titan/vote/providers/result_provider.dart'; import 'package:titan/vote/providers/sections_contender_provider.dart'; import 'package:titan/vote/providers/sections_provider.dart'; import 'package:titan/vote/providers/show_graph_provider.dart'; import 'package:titan/vote/providers/status_provider.dart'; import 'package:titan/vote/repositories/status_repository.dart'; +import 'package:titan/vote/router.dart'; import 'package:titan/vote/ui/pages/admin_page/admin_button.dart'; +import 'package:titan/vote/ui/pages/admin_page/opening_vote.dart'; import 'package:titan/vote/ui/pages/admin_page/section_bar.dart'; import 'package:titan/vote/ui/pages/admin_page/section_contender_items.dart'; import 'package:titan/vote/ui/pages/admin_page/vote_bars.dart'; @@ -36,6 +40,8 @@ class AdminPage extends HookConsumerWidget { final sectionContenderListNotifier = ref.watch( sectionContenderProvider.notifier, ); + final membersNotifier = ref.read(contenderMembersProvider.notifier); + final contenderNotifier = ref.read(contenderProvider.notifier); final sectionsNotifier = ref.watch(sectionsProvider.notifier); final contenderList = ref.watch(contenderListProvider); final asyncStatus = ref.watch(statusProvider); @@ -79,542 +85,342 @@ class AdminPage extends HookConsumerWidget { } }); }, - child: Padding( - padding: const EdgeInsets.only(top: 10.0), - child: Column( - children: [ - const SizedBox(height: 20), - const SectionBar(), - const SizedBox(height: 30), - AlignLeftText( - AppLocalizations.of(context)!.voteVoters, - padding: const EdgeInsets.symmetric(horizontal: 30.0), - color: const Color.fromARGB(255, 149, 149, 149), - ), - const SizedBox(height: 30), - const VotersBar(), - const SizedBox(height: 30), - AlignLeftText( - AppLocalizations.of(context)!.votePretendance, - padding: const EdgeInsets.symmetric(horizontal: 30.0), - color: Colors.grey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 20), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + child: Text( + "Administration", + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: ColorConstants.title, + ), ), - const SizedBox(height: 10), - const SectionContenderItems(), - const SizedBox(height: 20), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 30.0), - child: Align( - alignment: Alignment.centerLeft, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - AppLocalizations.of(context)!.voteVote, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Colors.grey, - ), - ), - if (showVotes && status == Status.counting) - Expanded( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - GestureDetector( - onTap: () { - showVotesNotifier.toggle(false); - }, - child: const HeroIcon( - HeroIcons.eyeSlash, - size: 25.0, - color: Colors.black, - ), - ), - WaitingButton( - builder: (child) => AdminButton(child: child), - onTap: () async { - await showDialog( - context: context, - builder: (context) => CustomDialogBox( - title: AppLocalizations.of( - context, - )!.votePublish, - descriptions: AppLocalizations.of( - context, - )!.votePublishVoteDescription, - onYes: () { - statusNotifier.publishVote(); - ref - .watch(resultProvider.notifier) - .loadResult(); - }, - ), - ); - }, - child: Text( - AppLocalizations.of(context)!.votePublish, - style: const TextStyle( - fontSize: 13, - fontWeight: FontWeight.bold, - color: Colors.white, - ), - ), - ), - ], - ), - ), - if (status == Status.counting || - status == Status.published) - WaitingButton( - builder: (child) => AdminButton(child: child), - onTap: () async { - await showDialog( - context: context, - builder: (context) { - return CustomDialogBox( - title: AppLocalizations.of( - context, - )!.voteResetVote, - descriptions: AppLocalizations.of( - context, - )!.voteResetVoteDescription, - onYes: () async { - await tokenExpireWrapper(ref, () async { - final resetedVotesMsg = - AppLocalizations.of( - context, - )!.voteResetedVotes; - final resetedVotesErrorMsg = - AppLocalizations.of( - context, - )!.voteErrorResetingVotes; - final value = await statusNotifier - .resetVote(); - ref - .watch(contenderListProvider.notifier) - .loadContenderList(); - if (value) { - showVotesNotifier.toggle(false); - displayVoteToastWithContext( - TypeMsg.msg, - resetedVotesMsg, - ); - } else { - displayVoteToastWithContext( - TypeMsg.error, - resetedVotesErrorMsg, - ); - } - }); - }, - ); - }, - ); - }, - child: Text( - AppLocalizations.of(context)!.voteClear, - style: const TextStyle( - fontSize: 13, - fontWeight: FontWeight.bold, - color: Colors.white, - ), - ), - ), - ], + ), + const SizedBox(height: 20), + AlignLeftText( + "Association", + padding: const EdgeInsets.symmetric(horizontal: 20.0), + color: ColorConstants.tertiary, + ), + const SizedBox(height: 10), + const SectionBar(), + const SizedBox(height: 20), + AlignLeftText( + AppLocalizations.of(context)!.voteVoters, + padding: const EdgeInsets.symmetric(horizontal: 20.0), + color: ColorConstants.tertiary, + ), + const SizedBox(height: 10), + const VotersBar(), + const SizedBox(height: 20), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + AppLocalizations.of(context)!.votePretendance, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: ColorConstants.tertiary, + ), ), - ), + const Spacer(), + if (status == Status.waiting) + CustomIconButton( + icon: HeroIcon( + HeroIcons.plus, + color: ColorConstants.background, + ), + onPressed: () { + contenderNotifier.setId(Contender.empty()); + membersNotifier.setMembers([]); + QR.to( + VoteRouter.root + + VoteRouter.admin + + VoteRouter.addEditContender, + ); + }, + ), + ], ), - const SizedBox(height: 20), - SizedBox( - height: - MediaQuery.of(context).size.height - - 500 + - (status == Status.waiting ? 0 : 50), - child: Column( + ), + const SizedBox(height: 10), + const SectionContenderItems(), + const SizedBox(height: 20), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + child: Align( + alignment: Alignment.centerLeft, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - if (status == Status.counting) - showVotes - ? const VoteBars() - : GestureDetector( + Text( + AppLocalizations.of(context)!.voteVote, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: ColorConstants.tertiary, + ), + ), + if (showVotes && status == Status.counting) + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + GestureDetector( onTap: () { - showVotesNotifier.toggle(true); + showVotesNotifier.toggle(false); }, - behavior: HitTestBehavior.opaque, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const SizedBox(height: 40), - const HeroIcon( - HeroIcons.eye, - size: 80.0, - color: Colors.black, - ), - const SizedBox(height: 40), - Text( - AppLocalizations.of(context)!.voteShowVotes, - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - color: Colors.black, - ), - ), - ], + child: const HeroIcon( + HeroIcons.eyeSlash, + size: 25.0, + color: ColorConstants.tertiary, ), ), - if (status == Status.published) const VoteBars(), - if (status == Status.closed) - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 30.0, - vertical: 50, - ), - child: WaitingButton( - builder: (child) => CardLayout( - padding: const EdgeInsets.only(top: 10, bottom: 12), - width: double.infinity, - colors: [Colors.grey.shade900, Colors.black], - child: child, - ), - onTap: () async { - await tokenExpireWrapper(ref, () async { - final votesCountedMsg = AppLocalizations.of( - context, - )!.voteVotesCounted; - final errorCountingVotesMsg = AppLocalizations.of( - context, - )!.voteErrorCountingVotes; - final value = await statusNotifier.countVote(); - if (value) { - displayVoteToastWithContext( - TypeMsg.msg, - votesCountedMsg, - ); - } else { - displayVoteToastWithContext( - TypeMsg.error, - errorCountingVotesMsg, - ); - } - }); - }, - child: Center( - child: Text( - AppLocalizations.of(context)!.voteCountVote, - style: const TextStyle( - color: Colors.white, - fontSize: 20, - fontWeight: FontWeight.w700, - ), - ), - ), - ), - ), - if (status == Status.open) - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 30.0, - vertical: 50, - ), - child: WaitingButton( - builder: (child) => CardLayout( - padding: const EdgeInsets.only(top: 10, bottom: 12), - width: double.infinity, - colors: const [ - ColorConstants.gradient1, - ColorConstants.gradient2, - ], - child: child, - ), - onTap: () async { - await tokenExpireWrapper(ref, () async { - final closeVotesMsg = AppLocalizations.of( - context, - )!.voteVotesClosed; - final errorClosingVotesMsg = AppLocalizations.of( - context, - )!.voteErrorClosingVotes; - final value = await statusNotifier.closeVote(); - if (value) { - displayVoteToastWithContext( - TypeMsg.msg, - closeVotesMsg, - ); - } else { - displayVoteToastWithContext( - TypeMsg.error, - errorClosingVotesMsg, + WaitingButton( + builder: (child) => AdminButton(child: child), + onTap: () async { + await showDialog( + context: context, + builder: (context) => CustomDialogBox( + title: AppLocalizations.of( + context, + )!.votePublish, + descriptions: AppLocalizations.of( + context, + )!.votePublishVoteDescription, + onYes: () { + statusNotifier.publishVote(); + ref + .watch(resultProvider.notifier) + .loadResult(); + }, + ), ); - } - }); - }, - child: Center( - child: Text( - AppLocalizations.of(context)!.voteCloseVote, - style: const TextStyle( - color: Colors.white, - fontSize: 20, - fontWeight: FontWeight.w700, + }, + child: Text( + AppLocalizations.of(context)!.votePublish, + style: const TextStyle( + fontSize: 13, + fontWeight: FontWeight.bold, + color: ColorConstants.background, + ), ), ), - ), + ], ), ), - if (status == Status.waiting) - Expanded( - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 30.0, - vertical: 50, - ), - child: Column( - children: [ - WaitingButton( - waitingColor: Colors.black, - builder: (child) => CardLayout( - padding: const EdgeInsets.only( - top: 10, - bottom: 12, - ), - margin: const EdgeInsets.all(0), - color: Colors.white, - borderColor: Colors.black, - child: child, - ), - onTap: () async { + if (status == Status.counting || status == Status.published) + WaitingButton( + builder: (child) => AdminButton(child: child), + onTap: () async { + await showDialog( + context: context, + builder: (context) { + return CustomDialogBox( + title: AppLocalizations.of( + context, + )!.voteResetVote, + descriptions: AppLocalizations.of( + context, + )!.voteResetVoteDescription, + onYes: () async { await tokenExpireWrapper(ref, () async { - final openVotesMsg = AppLocalizations.of( + final resetedVotesMsg = AppLocalizations.of( context, - )!.voteVotesOpened; - final errorOpeningVotesMsg = + )!.voteResetedVotes; + final resetedVotesErrorMsg = AppLocalizations.of( context, - )!.voteErrorOpeningVotes; + )!.voteErrorResetingVotes; final value = await statusNotifier - .openVote(); + .resetVote(); ref .watch(contenderListProvider.notifier) .loadContenderList(); if (value) { + showVotesNotifier.toggle(false); displayVoteToastWithContext( TypeMsg.msg, - openVotesMsg, + resetedVotesMsg, ); } else { displayVoteToastWithContext( TypeMsg.error, - errorOpeningVotesMsg, + resetedVotesErrorMsg, ); } }); }, - child: Center( - child: Text( - AppLocalizations.of(context)!.voteOpenVote, - style: const TextStyle( - color: Colors.black, - fontSize: 20, - fontWeight: FontWeight.w700, - ), - ), - ), + ); + }, + ); + }, + child: Text( + AppLocalizations.of(context)!.voteClear, + style: const TextStyle( + fontSize: 13, + fontWeight: FontWeight.bold, + color: ColorConstants.background, + ), + ), + ), + ], + ), + ), + ), + const SizedBox(height: 20), + Column( + children: [ + if (status == Status.counting) + showVotes + ? const VoteBars() + : GestureDetector( + onTap: () { + showVotesNotifier.toggle(true); + }, + behavior: HitTestBehavior.opaque, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const SizedBox(height: 40), + const HeroIcon( + HeroIcons.eye, + size: 80.0, + color: ColorConstants.tertiary, ), - const SizedBox(height: 50), - SizedBox( - width: double.infinity, - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: WaitingButton( - builder: (child) => CardLayout( - padding: const EdgeInsets.only( - top: 10, - bottom: 12, - ), - margin: const EdgeInsets.all(0), - colors: const [ - AMAPColorConstants.redGradient1, - AMAPColorConstants.redGradient2, - ], - borderColor: Colors.white, - child: child, - ), - onTap: () async { - await showDialog( - context: context, - builder: (context) => CustomDialogBox( - title: AppLocalizations.of( - context, - )!.voteDeleteAll, - descriptions: AppLocalizations.of( - context, - )!.voteDeleteAllDescription, - onYes: () async { - final deleteAllVotesMsg = - AppLocalizations.of( - context, - )!.voteDeletedAll; - final errorDeletingVotesMsg = - AppLocalizations.of( - context, - )!.voteDeletingError; - await tokenExpireWrapper( - ref, - () async { - final value = await ref - .watch( - contenderListProvider - .notifier, - ) - .deleteContenders(); - if (value) { - displayVoteToastWithContext( - TypeMsg.msg, - deleteAllVotesMsg, - ); - } else { - displayVoteToastWithContext( - TypeMsg.error, - errorDeletingVotesMsg, - ); - } - }, - ); - }, - ), - ); - }, - child: Center( - child: Row( - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - Text( - AppLocalizations.of( - context, - )!.voteAll, - style: const TextStyle( - color: Colors.white, - fontSize: 20, - fontWeight: FontWeight.w700, - ), - ), - const SizedBox(width: 10), - const HeroIcon( - HeroIcons.trash, - color: Colors.white, - size: 20, - ), - ], - ), - ), - ), - ), - const SizedBox(width: 20), - Expanded( - child: WaitingButton( - builder: (child) => CardLayout( - padding: const EdgeInsets.only( - top: 10, - bottom: 12, - ), - margin: const EdgeInsets.all(0), - colors: const [ - AMAPColorConstants.redGradient1, - AMAPColorConstants.redGradient2, - ], - borderColor: Colors.white, - child: child, - ), - onTap: () async { - await showDialog( - context: context, - builder: (context) => CustomDialogBox( - title: AppLocalizations.of( - context, - )!.voteDeletePipo, - descriptions: AppLocalizations.of( - context, - )!.voteDeletePipoDescription, - onYes: () async { - final deletePipoVotesMsg = - AppLocalizations.of( - context, - )!.voteDeletedPipo; - final errorDeletingPipoVotesMsg = - AppLocalizations.of( - context, - )!.voteDeletingError; - await tokenExpireWrapper( - ref, - () async { - final value = await ref - .watch( - contenderListProvider - .notifier, - ) - .deleteContenders( - type: ListType.fake, - ); - if (value) { - displayVoteToastWithContext( - TypeMsg.msg, - deletePipoVotesMsg, - ); - } else { - displayVoteToastWithContext( - TypeMsg.error, - errorDeletingPipoVotesMsg, - ); - } - }, - ); - }, - ), - ); - }, - child: Center( - child: Row( - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - Text( - AppLocalizations.of( - context, - )!.votePipo, - style: const TextStyle( - color: Colors.white, - fontSize: 20, - fontWeight: FontWeight.w700, - ), - ), - const SizedBox(width: 10), - const HeroIcon( - HeroIcons.trash, - color: Colors.white, - size: 20, - ), - ], - ), - ), - ), - ), - ], + const SizedBox(height: 40), + Text( + AppLocalizations.of(context)!.voteShowVotes, + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: ColorConstants.tertiary, ), ), ], ), ), + if (status == Status.published) const VoteBars(), + if (status == Status.closed) + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 30.0, + vertical: 50, + ), + child: WaitingButton( + builder: (child) => Container( + width: double.infinity, + padding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 10, + ), + decoration: BoxDecoration( + color: ColorConstants.tertiary, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: ColorConstants.onTertiary), + ), + child: child, ), - if (status == Status.open) const VoteCount(), - ], - ), - ), - ], - ), + onTap: () async { + await tokenExpireWrapper(ref, () async { + final votesCountedMsg = AppLocalizations.of( + context, + )!.voteVotesCounted; + final errorCountingVotesMsg = AppLocalizations.of( + context, + )!.voteErrorCountingVotes; + final value = await statusNotifier.countVote(); + if (value) { + displayVoteToastWithContext( + TypeMsg.msg, + votesCountedMsg, + ); + } else { + displayVoteToastWithContext( + TypeMsg.error, + errorCountingVotesMsg, + ); + } + }); + }, + child: Center( + child: Text( + AppLocalizations.of(context)!.voteCountVote, + style: const TextStyle( + color: ColorConstants.background, + fontSize: 20, + fontWeight: FontWeight.w700, + ), + ), + ), + ), + ), + if (status == Status.open) + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 30.0, + vertical: 50, + ), + child: WaitingButton( + builder: (child) => Container( + width: double.infinity, + padding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 10, + ), + decoration: BoxDecoration( + color: ColorConstants.main, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: ColorConstants.mainBorder), + ), + child: child, + ), + onTap: () async { + await tokenExpireWrapper(ref, () async { + final closeVotesMsg = AppLocalizations.of( + context, + )!.voteVotesClosed; + final errorClosingVotesMsg = AppLocalizations.of( + context, + )!.voteErrorClosingVotes; + final value = await statusNotifier.closeVote(); + if (value) { + displayVoteToastWithContext( + TypeMsg.msg, + closeVotesMsg, + ); + } else { + displayVoteToastWithContext( + TypeMsg.error, + errorClosingVotesMsg, + ); + } + }); + }, + child: Center( + child: Text( + AppLocalizations.of(context)!.voteCloseVote, + style: const TextStyle( + color: Colors.white, + fontSize: 20, + fontWeight: FontWeight.w700, + ), + ), + ), + ), + ), + if (status == Status.waiting) const OpeningVote(), + if (status == Status.open) const VoteCount(), + ], + ), + ], ), ), ); diff --git a/lib/vote/ui/pages/admin_page/contender_card.dart b/lib/vote/ui/pages/admin_page/contender_card.dart index 4cf55d1e8e..79e267a6e6 100644 --- a/lib/vote/ui/pages/admin_page/contender_card.dart +++ b/lib/vote/ui/pages/admin_page/contender_card.dart @@ -1,139 +1,60 @@ -import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter/material.dart'; -import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/tools/functions.dart'; -import 'package:titan/tools/ui/layouts/card_button.dart'; -import 'package:titan/tools/ui/layouts/card_layout.dart'; -import 'package:titan/tools/ui/builders/waiting_button.dart'; +import 'package:titan/l10n/app_localizations.dart'; +import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; +import 'package:titan/tools/ui/styleguide/button.dart'; +import 'package:titan/tools/ui/styleguide/list_item.dart'; import 'package:titan/vote/class/contender.dart'; -import 'package:titan/vote/providers/contender_provider.dart'; -import 'package:titan/vote/providers/status_provider.dart'; -import 'package:titan/vote/repositories/status_repository.dart'; -import 'package:titan/vote/router.dart'; import 'package:titan/vote/ui/components/contender_logo.dart'; -import 'package:qlevar_router/qlevar_router.dart'; class ContenderCard extends HookConsumerWidget { final Contender contender; final bool isAdmin, isDetail; - final Function()? onEdit; - final Future Function()? onDelete; + final Function() onEdit; + final Future Function() onDelete; const ContenderCard({ super.key, required this.contender, - this.onEdit, - this.onDelete, + required this.onEdit, + required this.onDelete, this.isAdmin = false, this.isDetail = false, }); @override Widget build(BuildContext context, WidgetRef ref) { - final contenderNotifier = ref.watch(contenderProvider.notifier); - final status = ref - .watch(statusProvider) - .maybeWhen(data: (status) => status, orElse: () => Status.waiting); - return CardLayout( - id: contender.id, - width: 250, - height: - (contender.listType != ListType.blank && - status == Status.waiting && - isAdmin) - ? 180 - : 130, - padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 15), - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - ContenderLogo(contender), - const SizedBox(width: 10), - Expanded( - child: Column( - children: [ - AutoSizeText( - contender.name, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - color: Colors.black, - ), - ), - Text( - capitalize(contender.listType.toString().split('.').last), - style: const TextStyle( - fontSize: 13, - fontWeight: FontWeight.bold, - color: Colors.black, - ), - ), - const SizedBox(height: 3), - ], - ), - ), - const SizedBox(width: 5), - isDetail || contender.listType == ListType.blank - ? Container(width: 30) - : GestureDetector( - onTap: () { - contenderNotifier.setId(contender); - QR.to(VoteRouter.root + VoteRouter.detail); - }, - child: const HeroIcon( - HeroIcons.informationCircle, - color: Colors.black, - size: 25, + return ListItem( + title: contender.name, + subtitle: contender.listType.name, + icon: ContenderLogo(contender), + onTap: isAdmin + ? () async { + FocusScope.of(context).unfocus(); + final ctx = context; + await Future.delayed(Duration(milliseconds: 150)); + if (!ctx.mounted) return; + + await showCustomBottomModal( + context: ctx, + ref: ref, + modal: BottomModalTemplate( + title: contender.name, + description: contender.program, + child: Column( + children: [ + const SizedBox(height: 20), + Button( + text: AppLocalizations.of(context)!.voteEdit, + onPressed: onEdit, ), - ), - ], - ), - Center( - child: Text( - contender.description, - maxLines: 2, - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.bold, - color: Colors.grey.shade400, - ), - ), - ), - const Spacer(), - if (contender.listType != ListType.blank && - status == Status.waiting && - isAdmin) - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - GestureDetector( - onTap: onEdit, - child: CardButton( - color: Colors.grey.shade200, - shadowColor: Colors.grey.withValues(alpha: 0.2), - child: const HeroIcon( - HeroIcons.pencil, - color: Colors.black, - ), + const SizedBox(height: 20), + Button.danger(text: "Supprimer", onPressed: onDelete), + ], ), ), - WaitingButton( - builder: (child) => - CardButton(color: Colors.black, child: child), - onTap: onDelete, - child: const HeroIcon(HeroIcons.trash, color: Colors.white), - ), - ], - ), - ], - ), + ); + } + : null, ); } } diff --git a/lib/vote/ui/pages/admin_page/opening_vote.dart b/lib/vote/ui/pages/admin_page/opening_vote.dart new file mode 100644 index 0000000000..95c7ed0e2c --- /dev/null +++ b/lib/vote/ui/pages/admin_page/opening_vote.dart @@ -0,0 +1,215 @@ +import 'package:flutter/material.dart'; +import 'package:heroicons/heroicons.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/l10n/app_localizations.dart'; +import 'package:titan/tools/constants.dart'; +import 'package:titan/tools/functions.dart'; +import 'package:titan/tools/token_expire_wrapper.dart'; +import 'package:titan/tools/ui/builders/waiting_button.dart'; +import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; +import 'package:titan/vote/class/contender.dart'; +import 'package:titan/vote/providers/contender_list_provider.dart'; +import 'package:titan/vote/providers/status_provider.dart'; + +class OpeningVote extends ConsumerWidget { + const OpeningVote({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final statusNotifier = ref.watch(statusProvider.notifier); + + void displayVoteToastWithContext(TypeMsg type, String msg) { + displayToast(context, type, msg); + } + + return Container( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + child: Column( + children: [ + WaitingButton( + waitingColor: ColorConstants.background, + builder: (child) => Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + decoration: BoxDecoration( + color: ColorConstants.tertiary, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: ColorConstants.onTertiary), + ), + child: child, + ), + onTap: () async { + await tokenExpireWrapper(ref, () async { + final openVotesMsg = AppLocalizations.of( + context, + )!.voteVotesOpened; + final errorOpeningVotesMsg = AppLocalizations.of( + context, + )!.voteErrorOpeningVotes; + final value = await statusNotifier.openVote(); + ref.watch(contenderListProvider.notifier).loadContenderList(); + if (value) { + displayVoteToastWithContext(TypeMsg.msg, openVotesMsg); + } else { + displayVoteToastWithContext( + TypeMsg.error, + errorOpeningVotesMsg, + ); + } + }); + }, + child: Center( + child: Text( + AppLocalizations.of(context)!.voteOpenVote, + style: const TextStyle( + color: ColorConstants.background, + fontSize: 20, + fontWeight: FontWeight.w700, + ), + ), + ), + ), + const SizedBox(height: 20), + WaitingButton( + builder: (child) => Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + decoration: BoxDecoration( + color: ColorConstants.main, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: ColorConstants.mainBorder), + ), + child: child, + ), + onTap: () async { + await showDialog( + context: context, + builder: (context) => CustomDialogBox( + title: AppLocalizations.of(context)!.voteDeleteAll, + descriptions: AppLocalizations.of( + context, + )!.voteDeleteAllDescription, + onYes: () async { + final deleteAllVotesMsg = AppLocalizations.of( + context, + )!.voteDeletedAll; + final errorDeletingVotesMsg = AppLocalizations.of( + context, + )!.voteDeletingError; + await tokenExpireWrapper(ref, () async { + final value = await ref + .watch(contenderListProvider.notifier) + .deleteContenders(); + if (value) { + displayVoteToastWithContext( + TypeMsg.msg, + deleteAllVotesMsg, + ); + } else { + displayVoteToastWithContext( + TypeMsg.error, + errorDeletingVotesMsg, + ); + } + }); + }, + ), + ); + }, + child: Center( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + AppLocalizations.of(context)!.voteAll, + style: const TextStyle( + color: ColorConstants.background, + fontSize: 20, + fontWeight: FontWeight.w700, + ), + ), + const SizedBox(width: 10), + const HeroIcon( + HeroIcons.trash, + color: ColorConstants.background, + size: 20, + ), + ], + ), + ), + ), + const SizedBox(height: 20), + WaitingButton( + builder: (child) => Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + decoration: BoxDecoration( + color: ColorConstants.main, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: ColorConstants.mainBorder), + ), + child: child, + ), + onTap: () async { + await showDialog( + context: context, + builder: (context) => CustomDialogBox( + title: AppLocalizations.of(context)!.voteDeletePipo, + descriptions: AppLocalizations.of( + context, + )!.voteDeletePipoDescription, + onYes: () async { + final deletePipoVotesMsg = AppLocalizations.of( + context, + )!.voteDeletedPipo; + final errorDeletingPipoVotesMsg = AppLocalizations.of( + context, + )!.voteDeletingError; + await tokenExpireWrapper(ref, () async { + final value = await ref + .watch(contenderListProvider.notifier) + .deleteContenders(type: ListType.fake); + if (value) { + displayVoteToastWithContext( + TypeMsg.msg, + deletePipoVotesMsg, + ); + } else { + displayVoteToastWithContext( + TypeMsg.error, + errorDeletingPipoVotesMsg, + ); + } + }); + }, + ), + ); + }, + child: Center( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + AppLocalizations.of(context)!.votePipo, + style: const TextStyle( + color: ColorConstants.background, + fontSize: 20, + fontWeight: FontWeight.w700, + ), + ), + const SizedBox(width: 10), + const HeroIcon( + HeroIcons.trash, + color: ColorConstants.background, + size: 20, + ), + ], + ), + ), + ), + const SizedBox(height: 20), + ], + ), + ); + } +} diff --git a/lib/vote/ui/pages/admin_page/section_bar.dart b/lib/vote/ui/pages/admin_page/section_bar.dart index 11e1514f86..a316a7e7b2 100644 --- a/lib/vote/ui/pages/admin_page/section_bar.dart +++ b/lib/vote/ui/pages/admin_page/section_bar.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/tools/ui/styleguide/item_chip.dart'; import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/layouts/horizontal_list_view.dart'; -import 'package:titan/tools/ui/layouts/item_chip.dart'; import 'package:titan/vote/providers/section_id_provider.dart'; import 'package:titan/vote/providers/sections_contender_provider.dart'; import 'package:titan/vote/providers/sections_provider.dart'; @@ -36,7 +36,7 @@ class SectionBar extends HookConsumerWidget { } return HorizontalListView.builder( - height: 40, + height: 50, items: sectionContender.keys.toList(), firstChild: (status == Status.waiting) ? ItemChip( diff --git a/lib/vote/ui/pages/admin_page/section_chip.dart b/lib/vote/ui/pages/admin_page/section_chip.dart index bb2798e522..6464e2e95b 100644 --- a/lib/vote/ui/pages/admin_page/section_chip.dart +++ b/lib/vote/ui/pages/admin_page/section_chip.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:titan/tools/constants.dart'; class SectionChip extends StatelessWidget { final bool selected, isAdmin; @@ -18,11 +19,14 @@ class SectionChip extends StatelessWidget { return GestureDetector( onTap: onTap, child: Container( - margin: const EdgeInsets.symmetric(horizontal: 10.0), + margin: EdgeInsets.symmetric(horizontal: 5.0), padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 20.0), decoration: BoxDecoration( borderRadius: BorderRadius.circular(30.0), - color: selected ? Colors.black : Colors.grey.shade200, + border: Border.all(color: ColorConstants.onTertiary), + color: selected + ? ColorConstants.onTertiary + : ColorConstants.background, ), child: Row( children: [ diff --git a/lib/vote/ui/pages/admin_page/section_contender_items.dart b/lib/vote/ui/pages/admin_page/section_contender_items.dart index 46961b0eaa..4dcd05ea8a 100644 --- a/lib/vote/ui/pages/admin_page/section_contender_items.dart +++ b/lib/vote/ui/pages/admin_page/section_contender_items.dart @@ -1,24 +1,18 @@ import 'package:flutter/material.dart'; -import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; -import 'package:titan/tools/ui/layouts/card_layout.dart'; import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; -import 'package:titan/tools/ui/layouts/horizontal_list_view.dart'; -import 'package:titan/vote/class/contender.dart'; import 'package:titan/vote/providers/contender_list_provider.dart'; import 'package:titan/vote/providers/contender_members.dart'; import 'package:titan/vote/providers/contender_provider.dart'; import 'package:titan/vote/providers/sections_contender_provider.dart'; import 'package:titan/vote/providers/sections_provider.dart'; -import 'package:titan/vote/providers/status_provider.dart'; -import 'package:titan/vote/repositories/status_repository.dart'; import 'package:titan/vote/router.dart'; -import 'package:titan/vote/ui/pages/admin_page/contender_card.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/l10n/app_localizations.dart'; +import 'package:titan/vote/ui/pages/admin_page/contender_card.dart'; class SectionContenderItems extends HookConsumerWidget { const SectionContenderItems({super.key}); @@ -34,98 +28,83 @@ class SectionContenderItems extends HookConsumerWidget { ); final contenderNotifier = ref.read(contenderProvider.notifier); - final asyncStatus = ref.watch(statusProvider); - Status status = Status.open; - asyncStatus.whenData((value) => status = value); - void displayVoteToastWithContext(TypeMsg type, String msg) { displayToast(context, type, msg); } return AsyncChild( value: sectionContender[section]!, - builder: (context, data) => HorizontalListView.builder( - height: 190, - firstChild: (status == Status.waiting) - ? GestureDetector( - onTap: () { - contenderNotifier.setId(Contender.empty()); - membersNotifier.setMembers([]); - QR.to( - VoteRouter.root + - VoteRouter.admin + - VoteRouter.addEditContender, - ); - }, - child: const CardLayout( - width: 120, - height: 180, - child: Center( - child: HeroIcon( - HeroIcons.plus, - size: 40.0, - color: Colors.black, - ), - ), + builder: (context, data) => Column( + children: data + .map( + (e) => Padding( + padding: const EdgeInsets.symmetric( + vertical: 5.0, + horizontal: 20.0, ), - ) - : null, - items: data, - itemBuilder: (context, e, i) => ContenderCard( - contender: e, - isAdmin: true, - onEdit: () { - tokenExpireWrapper(ref, () async { - contenderNotifier.setId(e); - membersNotifier.setMembers(e.members); - QR.to( - VoteRouter.root + - VoteRouter.admin + - VoteRouter.addEditContender, - ); - }); - }, - onDelete: () async { - await showDialog( - context: context, - builder: (context) { - return CustomDialogBox( - title: AppLocalizations.of(context)!.voteDeletePretendance, - descriptions: AppLocalizations.of( - context, - )!.voteDeletePretendanceDesc, - onYes: () { - final pretendanceDeletedMsg = AppLocalizations.of( - context, - )!.votePretendanceDeleted; - final pretendanceNotDeletedMsg = AppLocalizations.of( - context, - )!.votePretendanceNotDeleted; + child: ContenderCard( + contender: e, + isAdmin: true, + onEdit: () { tokenExpireWrapper(ref, () async { - final value = await contenderListNotifier.deleteContender( - e, + contenderNotifier.setId(e); + membersNotifier.setMembers(e.members); + QR.to( + VoteRouter.root + + VoteRouter.admin + + VoteRouter.addEditContender, ); - if (value) { - displayVoteToastWithContext( - TypeMsg.msg, - pretendanceDeletedMsg, - ); - contenderListNotifier.copy().then((value) { - sectionContenderListNotifier.setTData(section, value); - }); - } else { - displayVoteToastWithContext( - TypeMsg.error, - pretendanceNotDeletedMsg, - ); - } }); }, - ); - }, - ); - }, - ), + onDelete: () async { + await showDialog( + context: context, + builder: (context) { + return CustomDialogBox( + title: AppLocalizations.of( + context, + )!.voteDeletePretendance, + descriptions: AppLocalizations.of( + context, + )!.voteDeletePretendanceDesc, + onYes: () { + final pretendanceDeletedMsg = AppLocalizations.of( + context, + )!.votePretendanceDeleted; + final pretendanceNotDeletedMsg = + AppLocalizations.of( + context, + )!.votePretendanceNotDeleted; + tokenExpireWrapper(ref, () async { + final value = await contenderListNotifier + .deleteContender(e); + if (value) { + displayVoteToastWithContext( + TypeMsg.msg, + pretendanceDeletedMsg, + ); + contenderListNotifier.copy().then((value) { + sectionContenderListNotifier.setTData( + section, + value, + ); + }); + } else { + displayVoteToastWithContext( + TypeMsg.error, + pretendanceNotDeletedMsg, + ); + } + }); + }, + ); + }, + ); + }, + ), + ), + ) + .toList(), ), ); } diff --git a/lib/vote/ui/pages/admin_page/voter_chip.dart b/lib/vote/ui/pages/admin_page/voter_chip.dart index cb1d155c02..32d2b83cb2 100644 --- a/lib/vote/ui/pages/admin_page/voter_chip.dart +++ b/lib/vote/ui/pages/admin_page/voter_chip.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:titan/tools/ui/layouts/item_chip.dart'; +import 'package:titan/tools/ui/styleguide/item_chip.dart'; class VoterChip extends StatelessWidget { final bool selected; diff --git a/lib/vote/ui/pages/admin_page/voters_bar.dart b/lib/vote/ui/pages/admin_page/voters_bar.dart index 81dac4a033..f43fe98e15 100644 --- a/lib/vote/ui/pages/admin_page/voters_bar.dart +++ b/lib/vote/ui/pages/admin_page/voters_bar.dart @@ -22,7 +22,7 @@ class VotersBar extends HookConsumerWidget { Status status = Status.open; asyncStatus.whenData((value) => status = value); return SizedBox( - height: 40, + height: 50, child: groups.when( data: (data) => SingleChildScrollView( scrollDirection: Axis.horizontal, diff --git a/lib/vote/ui/pages/contender_pages/add_edit_contender.dart b/lib/vote/ui/pages/contender_pages/add_edit_contender.dart index ce267a2ee3..47c9beeb5b 100644 --- a/lib/vote/ui/pages/contender_pages/add_edit_contender.dart +++ b/lib/vote/ui/pages/contender_pages/add_edit_contender.dart @@ -4,20 +4,17 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:image_picker/image_picker.dart'; +import 'package:titan/navigation/ui/scroll_to_hide_navbar.dart'; +import 'package:titan/settings/ui/pages/main_page/picture_button.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/widgets/align_left_text.dart'; -import 'package:titan/tools/ui/layouts/card_button.dart'; import 'package:titan/tools/ui/layouts/horizontal_list_view.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; import 'package:titan/tools/ui/widgets/image_picker_on_tap.dart'; import 'package:titan/tools/ui/widgets/text_entry.dart'; -import 'package:titan/user/class/simple_users.dart'; -import 'package:titan/user/providers/user_list_provider.dart'; -import 'package:titan/vote/class/members.dart'; import 'package:titan/vote/class/contender.dart'; -import 'package:titan/vote/providers/display_results.dart'; import 'package:titan/vote/providers/contender_logo_provider.dart'; import 'package:titan/vote/providers/contender_logos_provider.dart'; import 'package:titan/vote/providers/contender_members.dart'; @@ -26,7 +23,7 @@ import 'package:titan/vote/providers/contender_provider.dart'; import 'package:titan/vote/providers/sections_contender_provider.dart'; import 'package:titan/vote/providers/sections_provider.dart'; import 'package:titan/vote/ui/components/member_card.dart'; -import 'package:titan/vote/ui/pages/contender_pages/search_result.dart'; +import 'package:titan/vote/ui/pages/contender_pages/contender_member.dart'; import 'package:titan/vote/ui/pages/admin_page/section_chip.dart'; import 'package:titan/vote/ui/vote.dart'; import 'package:qlevar_router/qlevar_router.dart'; @@ -38,7 +35,6 @@ class AddEditContenderPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final key = GlobalKey(); - final addMemberKey = GlobalKey(); final section = useState(ref.watch(sectionProvider)); final contenderListNotifier = ref.read(contenderListProvider.notifier); final sectionsNotifier = ref.read(sectionContenderProvider.notifier); @@ -47,18 +43,13 @@ class AddEditContenderPage extends HookConsumerWidget { final name = useTextEditingController(text: contender.name); final description = useTextEditingController(text: contender.description); final listType = useState(contender.listType); - final usersNotifier = ref.read(userList.notifier); - final queryController = useTextEditingController(); - final role = useTextEditingController(); final program = useTextEditingController(text: contender.program); - final member = useState(SimpleUser.empty()); final members = ref.watch(contenderMembersProvider); final membersNotifier = ref.read(contenderMembersProvider.notifier); final contenderLogosNotifier = ref.read(contenderLogosProvider.notifier); final logoNotifier = ref.read(contenderLogoProvider.notifier); final logo = useState(null); final logoFile = useState(null); - final showNotifier = ref.read(displayResult.notifier); final contenderLogos = ref.watch(contenderLogosProvider); if (contenderLogos[contender.id] != null) { @@ -76,394 +67,274 @@ class AddEditContenderPage extends HookConsumerWidget { } return VoteTemplate( - child: SingleChildScrollView( - physics: const BouncingScrollPhysics(), - child: Form( - key: key, - child: Column( - children: [ - AlignLeftText( - AppLocalizations.of(context)!.voteAddPretendance, - padding: const EdgeInsets.only( - top: 40, - left: 30, - right: 30, - bottom: 50, + child: ScrollToHideNavbar( + controller: useScrollController(), + child: SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: Form( + key: key, + child: Column( + children: [ + const SizedBox(height: 20), + AlignLeftText( + AppLocalizations.of(context)!.voteAddPretendance, + padding: EdgeInsets.symmetric(horizontal: 20.0), + color: ColorConstants.title, + fontSize: 24, + fontWeight: FontWeight.bold, ), - color: Colors.grey, - ), - Center( - child: Stack( - clipBehavior: Clip.none, - children: [ - Container( - decoration: BoxDecoration( - color: Colors.white, - shape: BoxShape.circle, - boxShadow: [ - BoxShadow( - color: Colors.black.withValues(alpha: 0.1), - spreadRadius: 5, - blurRadius: 10, - offset: const Offset(2, 3), - ), - ], - ), - child: logoFile.value != null - ? Container( - width: 160, - height: 160, - decoration: BoxDecoration( - shape: BoxShape.circle, - image: DecorationImage( - image: logo.value != null - ? Image.memory( - logo.value!, - fit: BoxFit.cover, - ).image - : logoFile.value!.image, - fit: BoxFit.cover, - ), - ), - ) - : const HeroIcon( - HeroIcons.userCircle, - size: 160, - color: Colors.grey, + SizedBox(height: 20), + Center( + child: Stack( + clipBehavior: Clip.none, + children: [ + Container( + decoration: BoxDecoration( + color: Colors.white, + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.1), + spreadRadius: 5, + blurRadius: 10, + offset: const Offset(2, 3), ), - ), - Positioned( - bottom: 0, - left: 0, - child: ImagePickerOnTap( - picker: picker, - imageBytesNotifier: logo, - imageNotifier: logoFile, - displayToastWithContext: displayVoteToastWithContext, - child: const CardButton( - colors: [ - ColorConstants.gradient1, - ColorConstants.gradient2, ], - child: HeroIcon(HeroIcons.photo, color: Colors.white), ), + child: logoFile.value != null + ? Container( + width: 160, + height: 160, + decoration: BoxDecoration( + shape: BoxShape.circle, + image: DecorationImage( + image: logo.value != null + ? Image.memory( + logo.value!, + fit: BoxFit.cover, + ).image + : logoFile.value!.image, + fit: BoxFit.cover, + ), + ), + ) + : const HeroIcon( + HeroIcons.userCircle, + size: 160, + color: Colors.grey, + ), ), - ), - ], + Positioned( + bottom: 0, + left: 0, + child: ImagePickerOnTap( + picker: picker, + imageBytesNotifier: logo, + imageNotifier: logoFile, + displayToastWithContext: displayVoteToastWithContext, + child: const PictureButton(icon: HeroIcons.photo), + ), + ), + ], + ), ), - ), - const SizedBox(height: 30), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 30.0), - child: TextEntry( - label: AppLocalizations.of(context)!.voteName, - controller: name, + const SizedBox(height: 10), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + child: TextEntry( + label: AppLocalizations.of(context)!.voteName, + controller: name, + ), ), - ), - const SizedBox(height: 50), - HorizontalListView.builder( - height: 40, - items: ListType.values - .where((e) => e != ListType.blank) - .toList(), - itemBuilder: (context, e, i) => SectionChip( - label: capitalize(e.toString().split('.').last), - selected: listType.value == e, - onTap: () async { - listType.value = e; - }, + const SizedBox(height: 20), + HorizontalListView.builder( + height: 50, + items: ListType.values + .where((e) => e != ListType.blank) + .toList(), + itemBuilder: (context, e, i) => SectionChip( + label: capitalize(e.toString().split('.').last), + selected: listType.value == e, + onTap: () async { + listType.value = e; + }, + ), ), - ), - const SizedBox(height: 30), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 30.0), - child: TextEntry( - keyboardType: TextInputType.multiline, - controller: description, - label: AppLocalizations.of(context)!.voteDescription, + const SizedBox(height: 20), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + child: TextEntry( + keyboardType: TextInputType.multiline, + controller: description, + label: AppLocalizations.of(context)!.voteDescription, + ), ), - ), - const SizedBox(height: 30), - HorizontalListView.builder( - height: 155, - items: members, - itemBuilder: (context, e, i) => MemberCard( - member: e, - isAdmin: true, - onDelete: () async { - membersNotifier.removeMember(e); - }, + const SizedBox(height: 20), + ContenderMember(), + const SizedBox(height: 10), + members.isEmpty + ? const Center(child: Text('No members added yet.')) + : Padding( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + child: Column( + children: members + .map( + (e) => MemberCard( + member: e, + isAdmin: true, + onEdit: () {}, // TODO: maybe hide + onDelete: () async { + membersNotifier.removeMember(e); + }, + ), + ) + .toList(), + ), + ), + const SizedBox(height: 10), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + child: TextEntry( + keyboardType: TextInputType.multiline, + label: AppLocalizations.of(context)!.voteProgram, + controller: program, + ), ), - ), - const SizedBox(height: 30), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 30.0), - child: InputDecorator( - decoration: InputDecoration( - floatingLabelStyle: const TextStyle( - color: Colors.black, - fontWeight: FontWeight.bold, - ), - focusedBorder: const UnderlineInputBorder( - borderSide: BorderSide(color: Colors.black, width: 2.0), - ), - labelText: AppLocalizations.of(context)!.voteAddMember, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(10.0), + const SizedBox(height: 30), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + child: WaitingButton( + builder: (child) => Container( + width: double.infinity, + padding: const EdgeInsets.only(top: 8, bottom: 12), + alignment: Alignment.center, + decoration: BoxDecoration( + color: Colors.black, + borderRadius: BorderRadius.circular(10), + boxShadow: [ + BoxShadow( + color: Colors.grey.withValues(alpha: 0.5), + spreadRadius: 5, + blurRadius: 10, + offset: const Offset(3, 3), + ), + ], + ), + child: child, ), - ), - child: Form( - key: addMemberKey, - child: Column( - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 20.0), - child: Column( - children: [ - TextEntry( - label: AppLocalizations.of( + onTap: () async { + if (key.currentState == null) { + return; + } + if (key.currentState!.validate()) { + await tokenExpireWrapper(ref, () async { + final contenderList = ref.watch( + contenderListProvider, + ); + Contender newContender = Contender( + name: name.text, + id: isEdit ? contender.id : '', + description: description.text, + listType: listType.value, + members: members, + section: section.value, + program: program.text, + ); + final editedPretendanceMsg = isEdit + ? AppLocalizations.of( context, - )!.voteMembers, - onChanged: (newQuery) { - showNotifier.setId(true); - tokenExpireWrapper(ref, () async { - if (queryController.text.isNotEmpty) { - await usersNotifier.filterUsers( - queryController.text, - ); - } else { - usersNotifier.clear(); - } - }); - }, - color: Colors.black, - controller: queryController, - ), - const SizedBox(height: 10), - SearchResult( - borrower: member, - queryController: queryController, - ), - TextEntry( - label: AppLocalizations.of(context)!.voteRole, - controller: role, - ), - const SizedBox(height: 30), - GestureDetector( - onTap: () async { - if (addMemberKey.currentState == null) { - return; - } - if (member.value.id == '' || - role.text == '') { - return; - } - final alreadyAddedMemberMsg = - AppLocalizations.of( - context, - )!.voteAlreadyAddedMember; - if (addMemberKey.currentState!.validate()) { - final value = await membersNotifier - .addMember( - Member.fromSimpleUser( - member.value, - role.text, - ), - ); - if (value) { - role.text = ''; - member.value = SimpleUser.empty(); - queryController.text = ''; - } else { - displayVoteToastWithContext( - TypeMsg.error, - alreadyAddedMemberMsg, - ); - } + )!.voteEditedPretendance + : AppLocalizations.of( + context, + )!.voteAddedPretendance; + final editingPretendanceErrorMsg = + AppLocalizations.of(context)!.voteEditingError; + final value = isEdit + ? await contenderListNotifier.updateContender( + newContender, + ) + : await contenderListNotifier.addContender( + newContender, + ); + if (value) { + QR.back(); + displayVoteToastWithContext( + TypeMsg.msg, + editedPretendanceMsg, + ); + if (isEdit) { + contenderList.maybeWhen( + data: (list) { + final logoBytes = logo.value; + if (logoBytes != null) { + contenderLogosNotifier.autoLoad( + ref, + contender.id, + (contenderId) => logoNotifier.updateLogo( + contenderId, + logoBytes, + ), + ); } }, - child: Container( - width: double.infinity, - padding: const EdgeInsets.only( - top: 8, - bottom: 12, - ), - alignment: Alignment.center, - decoration: BoxDecoration( - color: Colors.black, - borderRadius: BorderRadius.circular(10), - boxShadow: [ - BoxShadow( - color: Colors.grey.withValues( - alpha: 0.5, - ), - spreadRadius: 5, - blurRadius: 10, - offset: const Offset( - 3, - 3, - ), // changes position of shadow + orElse: () {}, + ); + } else { + contenderList.maybeWhen( + data: (list) { + final newContender = list.last; + final logoBytes = logo.value; + if (logoBytes != null) { + contenderLogosNotifier.autoLoad( + ref, + newContender.id, + (contenderId) => logoNotifier.updateLogo( + contenderId, + logoBytes, ), - ], - ), - child: Text( - AppLocalizations.of(context)!.voteAdd, - style: const TextStyle( - color: Colors.white, - fontSize: 25, - fontWeight: FontWeight.bold, - ), - ), - ), - ), - ], - ), - ), - ], - ), - ), - ), - ), - const SizedBox(height: 50), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 30.0), - child: TextEntry( - keyboardType: TextInputType.multiline, - label: AppLocalizations.of(context)!.voteProgram, - controller: program, - ), - ), - const SizedBox(height: 50), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 30.0), - child: WaitingButton( - builder: (child) => Container( - width: double.infinity, - padding: const EdgeInsets.only(top: 8, bottom: 12), - alignment: Alignment.center, - decoration: BoxDecoration( - color: Colors.black, - borderRadius: BorderRadius.circular(10), - boxShadow: [ - BoxShadow( - color: Colors.grey.withValues(alpha: 0.5), - spreadRadius: 5, - blurRadius: 10, - offset: const Offset(3, 3), - ), - ], - ), - child: child, - ), - onTap: () async { - if (key.currentState == null) { - return; - } - if (key.currentState!.validate()) { - await tokenExpireWrapper(ref, () async { - final contenderList = ref.watch(contenderListProvider); - Contender newContender = Contender( - name: name.text, - id: isEdit ? contender.id : '', - description: description.text, - listType: listType.value, - members: members, - section: section.value, - program: program.text, - ); - final editedPretendanceMsg = isEdit - ? AppLocalizations.of( - context, - )!.voteEditedPretendance - : AppLocalizations.of( - context, - )!.voteAddedPretendance; - final editingPretendanceErrorMsg = AppLocalizations.of( - context, - )!.voteEditingError; - final value = isEdit - ? await contenderListNotifier.updateContender( - newContender, - ) - : await contenderListNotifier.addContender( - newContender, + ); + } + }, + orElse: () {}, ); - if (value) { - QR.back(); - displayVoteToastWithContext( - TypeMsg.msg, - editedPretendanceMsg, - ); - if (isEdit) { - contenderList.maybeWhen( - data: (list) { - final logoBytes = logo.value; - if (logoBytes != null) { - contenderLogosNotifier.autoLoad( - ref, - contender.id, - (contenderId) => logoNotifier.updateLogo( - contenderId, - logoBytes, - ), - ); - } - }, - orElse: () {}, + } + membersNotifier.clearMembers(); + sectionsNotifier.setTData( + section.value, + await contenderListNotifier.copy(), ); } else { - contenderList.maybeWhen( - data: (list) { - final newContender = list.last; - final logoBytes = logo.value; - if (logoBytes != null) { - contenderLogosNotifier.autoLoad( - ref, - newContender.id, - (contenderId) => logoNotifier.updateLogo( - contenderId, - logoBytes, - ), - ); - } - }, - orElse: () {}, + displayVoteToastWithContext( + TypeMsg.error, + editingPretendanceErrorMsg, ); } - membersNotifier.clearMembers(); - sectionsNotifier.setTData( - section.value, - await contenderListNotifier.copy(), - ); - } else { - displayVoteToastWithContext( - TypeMsg.error, - editingPretendanceErrorMsg, - ); - } - }); - } else { - displayToast( - context, - TypeMsg.error, - AppLocalizations.of( + }); + } else { + displayToast( context, - )!.voteIncorrectOrMissingFields, - ); - } - }, - child: Text( - AppLocalizations.of(context)!.voteEdit, - style: const TextStyle( - color: Colors.white, - fontSize: 25, - fontWeight: FontWeight.bold, + TypeMsg.error, + AppLocalizations.of( + context, + )!.voteIncorrectOrMissingFields, + ); + } + }, + child: Text( + isEdit + ? AppLocalizations.of(context)!.voteEdit + : AppLocalizations.of(context)!.voteAdd, + style: const TextStyle( + color: Colors.white, + fontSize: 25, + fontWeight: FontWeight.bold, + ), ), ), ), - ), - const SizedBox(height: 30), - ], + const SizedBox(height: 30), + ], + ), ), ), ), diff --git a/lib/vote/ui/pages/contender_pages/contender_member.dart b/lib/vote/ui/pages/contender_pages/contender_member.dart new file mode 100644 index 0000000000..b1873c01d2 --- /dev/null +++ b/lib/vote/ui/pages/contender_pages/contender_member.dart @@ -0,0 +1,167 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:heroicons/heroicons.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/l10n/app_localizations.dart'; +import 'package:titan/tools/constants.dart'; +import 'package:titan/tools/functions.dart'; +import 'package:titan/tools/token_expire_wrapper.dart'; +import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; +import 'package:titan/tools/ui/styleguide/icon_button.dart'; +import 'package:titan/tools/ui/styleguide/text_entry.dart'; +import 'package:titan/user/class/simple_users.dart'; +import 'package:titan/user/providers/user_list_provider.dart'; +import 'package:titan/vote/class/members.dart'; +import 'package:titan/vote/providers/contender_members.dart'; +import 'package:titan/vote/providers/display_results.dart'; +import 'package:titan/vote/ui/pages/contender_pages/search_result.dart'; + +class ContenderMember extends HookConsumerWidget { + const ContenderMember({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final addMemberKey = GlobalKey(); + final usersNotifier = ref.read(userList.notifier); + final queryController = useTextEditingController(); + final role = useTextEditingController(); + final membersNotifier = ref.read(contenderMembersProvider.notifier); + final member = useState(SimpleUser.empty()); + + void displayVoteToastWithContext(TypeMsg type, String msg) { + displayToast(context, type, msg); + } + + final showNotifier = ref.read(displayResult.notifier); + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + AppLocalizations.of(context)!.voteMembers, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: ColorConstants.tertiary, + ), + ), + const Spacer(), + CustomIconButton( + icon: HeroIcon(HeroIcons.plus, color: ColorConstants.background), + onPressed: () async { + await showCustomBottomModal( + context: context, + ref: ref, + modal: BottomModalTemplate( + title: AppLocalizations.of(context)!.voteAddMember, + child: Form( + key: addMemberKey, + child: Column( + children: [ + Column( + children: [ + TextEntry( + label: AppLocalizations.of(context)!.voteMembers, + onChanged: (newQuery) { + showNotifier.setId(true); + tokenExpireWrapper(ref, () async { + if (queryController.text.isNotEmpty) { + await usersNotifier.filterUsers( + queryController.text, + ); + } else { + usersNotifier.clear(); + } + }); + }, + color: Colors.black, + controller: queryController, + ), + const SizedBox(height: 10), + SearchResult( + borrower: member, + queryController: queryController, + ), + TextEntry( + label: AppLocalizations.of(context)!.voteRole, + controller: role, + ), + const SizedBox(height: 30), + GestureDetector( + onTap: () async { + if (addMemberKey.currentState == null) { + return; + } + if (member.value.id == '' || role.text == '') { + return; + } + final alreadyAddedMemberMsg = + AppLocalizations.of( + context, + )!.voteAlreadyAddedMember; + if (addMemberKey.currentState!.validate()) { + final value = await membersNotifier.addMember( + Member.fromSimpleUser( + member.value, + role.text, + ), + ); + if (value) { + role.text = ''; + member.value = SimpleUser.empty(); + queryController.text = ''; + QR.back(); + } else { + displayVoteToastWithContext( + TypeMsg.error, + alreadyAddedMemberMsg, + ); + } + } + }, + child: Container( + width: double.infinity, + padding: const EdgeInsets.only( + top: 8, + bottom: 12, + ), + alignment: Alignment.center, + decoration: BoxDecoration( + color: Colors.black, + borderRadius: BorderRadius.circular(10), + boxShadow: [ + BoxShadow( + color: Colors.grey.withValues(alpha: 0.5), + spreadRadius: 5, + blurRadius: 10, + offset: const Offset(3, 3), + ), + ], + ), + child: Text( + AppLocalizations.of(context)!.voteAdd, + style: const TextStyle( + color: Colors.white, + fontSize: 25, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ], + ), + ], + ), + ), + ), + ); + }, + ), + ], + ), + ); + } +} diff --git a/lib/vote/ui/pages/detail_page/detail_page.dart b/lib/vote/ui/pages/detail_page/detail_page.dart index 745715299b..728d185312 100644 --- a/lib/vote/ui/pages/detail_page/detail_page.dart +++ b/lib/vote/ui/pages/detail_page/detail_page.dart @@ -129,7 +129,13 @@ class DetailPage extends HookConsumerWidget { physics: const BouncingScrollPhysics(), child: Wrap( children: contender.members - .map((e) => MemberCard(member: e)) + .map( + (e) => MemberCard( + member: e, + onEdit: () {}, + onDelete: () {}, + ), + ) .toList(), ), ) @@ -152,7 +158,13 @@ class DetailPage extends HookConsumerWidget { ), Padding( padding: const EdgeInsets.all(20.0), - child: Center(child: ContenderCard(contender: contender)), + child: Center( + child: ContenderCard( + contender: contender, + onDelete: () async {}, + onEdit: () {}, + ), + ), ), ], ), diff --git a/lib/vote/ui/pages/main_page/main_page.dart b/lib/vote/ui/pages/main_page/main_page.dart index 8699731ee2..63aaf8b384 100644 --- a/lib/vote/ui/pages/main_page/main_page.dart +++ b/lib/vote/ui/pages/main_page/main_page.dart @@ -1,6 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/tools/constants.dart'; +import 'package:titan/tools/ui/styleguide/icon_button.dart'; import 'package:titan/tools/ui/widgets/admin_button.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; @@ -56,36 +59,43 @@ class VoteMainPage extends HookConsumerWidget { if (!canVote) { return VoteTemplate( - child: SizedBox( - height: MediaQuery.of(context).size.height - 100, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 30.0), - child: Column( - children: [ - if (isAdmin) - Row( - children: [ - const Spacer(), - Container( - margin: const EdgeInsets.only(right: 20), - child: AdminButton( - onTap: () { - QR.to(VoteRouter.root + VoteRouter.admin); - }, - ), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + child: Column( + children: [ + const SizedBox(height: 20), + Row( + children: [ + if (isAdmin) + Text( + "Vote", + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: ColorConstants.title, ), - ], - ), - Expanded( - child: Center( - child: Text( - AppLocalizations.of(context)!.voteCanNotVote, - style: const TextStyle(fontSize: 20), ), + const Spacer(), + CustomIconButton( + icon: HeroIcon( + HeroIcons.userGroup, + color: ColorConstants.background, + ), + onPressed: () { + QR.to(VoteRouter.root + VoteRouter.admin); + }, + ), + ], + ), + Expanded( + child: Center( + child: Text( + AppLocalizations.of(context)!.voteCanNotVote, + style: const TextStyle(fontSize: 20), ), ), - ], - ), + ), + ], ), ), ); @@ -129,82 +139,81 @@ class VoteMainPage extends HookConsumerWidget { } }); }, - child: SizedBox( - height: MediaQuery.of(context).size.height - 100, - child: Padding( - padding: const EdgeInsets.only(left: 30.0), - child: Column( - children: [ - SizedBox(height: isAdmin ? 10 : 15), - AsyncChild( - value: sections, - builder: (context, sectionList) => Column( - children: [ - SizedBox( - height: - MediaQuery.of(context).size.height - - (s == Status.open - ? isAdmin - ? 215 - : 220 - : isAdmin - ? 150 - : 155), - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - ListSideItem( - sectionList: sectionList, - animation: animation, - ), - Expanded( - child: SizedBox( - width: double.infinity, - child: Column( - children: [ - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - SectionTitle(sectionList: sectionList), - if (isAdmin) - Container( - margin: const EdgeInsets.only( - right: 20, - ), - child: AdminButton( - onTap: () { - QR.to( - VoteRouter.root + - VoteRouter.admin, - ); - }, - ), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + child: Column( + children: [ + const SizedBox(height: 20), + Text( + "Vote", + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: ColorConstants.title, + ), + ), + AsyncChild( + value: sections, + builder: (context, sectionList) => Column( + children: [ + SizedBox( + height: + MediaQuery.of(context).size.height - + (s == Status.open ? 220 : 155), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + ListSideItem( + sectionList: sectionList, + animation: animation, + ), + Expanded( + child: SizedBox( + width: double.infinity, + child: Column( + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + SectionTitle(sectionList: sectionList), + if (isAdmin) + Container( + margin: const EdgeInsets.only( + right: 20, ), - ], - ), - const SizedBox(height: 15), - Expanded( - child: ListContenderCard( - animation: animation, - ), + child: AdminButton( + onTap: () { + QR.to( + VoteRouter.root + + VoteRouter.admin, + ); + }, + ), + ), + ], + ), + const SizedBox(height: 15), + Expanded( + child: ListContenderCard( + animation: animation, ), - ], - ), + ), + ], ), ), - ], - ), + ), + ], ), - const SizedBox(height: 20), - if (sectionList.isNotEmpty && s == Status.open) - const VoteButton(), - const SizedBox(height: 20), - ], - ), + ), + const SizedBox(height: 20), + if (sectionList.isNotEmpty && s == Status.open) + const VoteButton(), + const SizedBox(height: 20), + ], ), - ], - ), + ), + ], ), ), ), From b476a53281e1fbfbc9a32d9f9d2a825b0d4e1fee Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 24 Aug 2025 09:37:25 +0200 Subject: [PATCH 351/473] fix: missing part of rebase --- lib/l10n/app_localizations.dart | 6 ++++++ lib/l10n/app_localizations_en.dart | 3 +++ lib/l10n/app_localizations_fr.dart | 3 +++ lib/settings/ui/pages/main_page/main_page.dart | 2 +- 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 3d98877afa..f6873adc94 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -1106,6 +1106,12 @@ abstract class AppLocalizations { /// **'Choisir un groupe gestionnaire pour l\'association'** String get adminChooseAssociationManagerGroup; + /// No description provided for @adminConfirm. + /// + /// In fr, this message translates to: + /// **'Valider'** + String get adminConfirm; + /// No description provided for @advertAdd. /// /// In fr, this message translates to: diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index d248d91863..f8149b07e5 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -532,6 +532,9 @@ class AppLocalizationsEn extends AppLocalizations { String get adminChooseAssociationManagerGroup => 'Choose a group to manage this association'; + @override + String get adminConfirm => 'Confirm'; + @override String get advertAdd => 'Add'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 40a32e9c67..29471f2300 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -539,6 +539,9 @@ class AppLocalizationsFr extends AppLocalizations { String get adminChooseAssociationManagerGroup => 'Choisir un groupe gestionnaire pour l\'association'; + @override + String get adminConfirm => 'Valider'; + @override String get advertAdd => 'Ajouter'; diff --git a/lib/settings/ui/pages/main_page/main_page.dart b/lib/settings/ui/pages/main_page/main_page.dart index 6975fd2713..b01ae7d99f 100644 --- a/lib/settings/ui/pages/main_page/main_page.dart +++ b/lib/settings/ui/pages/main_page/main_page.dart @@ -6,7 +6,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/auth/providers/openid_provider.dart'; import 'package:titan/service/providers/firebase_token_expiration_provider.dart'; import 'package:titan/service/providers/messages_provider.dart'; -import 'package:titan/tools/ui/styleguide/custom_dialog_box.dart'; +import 'package:titan/tools/ui/styleguide/confirm_modal.dart'; import 'package:titan/tools/ui/widgets/vertical_clip_scroll.dart'; import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/settings/providers/notification_topic_provider.dart'; From 9ba6793275b298b353f43a589cb5451a63865e0c Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 24 Aug 2025 10:01:47 +0200 Subject: [PATCH 352/473] fix: linter --- .../association_membership_member_editable_card.dart | 3 ++- .../add_edit_structure_page.dart | 1 - .../membership_editor_page.dart | 11 ++++++----- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/admin/ui/pages/membership/association_membership_detail_page/association_membership_member_editable_card.dart b/lib/admin/ui/pages/membership/association_membership_detail_page/association_membership_member_editable_card.dart index 5f43d7a73a..95908887dc 100644 --- a/lib/admin/ui/pages/membership/association_membership_detail_page/association_membership_member_editable_card.dart +++ b/lib/admin/ui/pages/membership/association_membership_detail_page/association_membership_member_editable_card.dart @@ -104,7 +104,8 @@ class MemberEditableCard extends HookConsumerWidget { builder: (context) { return CustomDialogBox( title: "Supprimer le membre", - descriptions: "Êtes-vous sûr de vouloir supprimer ce membre ?", + descriptions: + "Êtes-vous sûr de vouloir supprimer ce membre ?", onYes: () async { final deletedMemberMsg = AppLocalizations.of( context, diff --git a/lib/admin/ui/pages/structure_page/add_edit_structure_page/add_edit_structure_page.dart b/lib/admin/ui/pages/structure_page/add_edit_structure_page/add_edit_structure_page.dart index a423c51956..5ec47ef0e1 100644 --- a/lib/admin/ui/pages/structure_page/add_edit_structure_page/add_edit_structure_page.dart +++ b/lib/admin/ui/pages/structure_page/add_edit_structure_page/add_edit_structure_page.dart @@ -9,7 +9,6 @@ import 'package:titan/admin/providers/structure_manager_provider.dart'; import 'package:titan/admin/providers/structure_provider.dart'; import 'package:titan/paiement/class/structure.dart'; import 'package:titan/paiement/providers/structure_list_provider.dart'; -import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; diff --git a/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart b/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart index 3af60495d0..2471223364 100644 --- a/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart +++ b/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart @@ -155,11 +155,12 @@ class MembershipEditorPage extends HookConsumerWidget { title: member.member.id == "" ? localizeWithContext.phonebookSearchUser : member.member.getName(), - onTap: () async {showCustomBottomModal( - context: context, - modal: UserSearchModal(), - ref: ref, - ); + onTap: () async { + showCustomBottomModal( + context: context, + modal: UserSearchModal(), + ref: ref, + ); }, ), ] else From b5720df593666993d7f8e9149cea8589b5892272 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Thu, 21 Aug 2025 23:43:05 +0200 Subject: [PATCH 353/473] fix: limit app name to half the screen width --- lib/login/ui/app_sign_in.dart | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/lib/login/ui/app_sign_in.dart b/lib/login/ui/app_sign_in.dart index c341251d49..2fe6a97f7a 100644 --- a/lib/login/ui/app_sign_in.dart +++ b/lib/login/ui/app_sign_in.dart @@ -1,3 +1,4 @@ +import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:heroicons/heroicons.dart'; @@ -32,18 +33,25 @@ class AppSignIn extends HookConsumerWidget { children: [ Expanded( flex: 3, - child: Align( - alignment: Alignment.centerLeft, - child: Text( - AppLocalizations.of(context)!.loginAppName, - style: GoogleFonts.elMessiri( - textStyle: const TextStyle( - fontSize: 40, - fontWeight: FontWeight.bold, - color: Colors.white, + child: Row( + children: [ + Expanded( + child: Align( + alignment: Alignment.centerLeft, + child: AutoSizeText( + AppLocalizations.of(context)!.loginAppName, + style: GoogleFonts.elMessiri( + textStyle: const TextStyle( + fontSize: 40, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + ), ), ), - ), + const Spacer(), + ], ), ), Expanded( From 81c7facc087448d0ab12c9557fb40aa7235f700b Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Fri, 22 Aug 2025 17:41:18 +0200 Subject: [PATCH 354/473] fix: space for title --- lib/login/ui/app_sign_in.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/login/ui/app_sign_in.dart b/lib/login/ui/app_sign_in.dart index 2fe6a97f7a..910d2e2ee1 100644 --- a/lib/login/ui/app_sign_in.dart +++ b/lib/login/ui/app_sign_in.dart @@ -36,6 +36,7 @@ class AppSignIn extends HookConsumerWidget { child: Row( children: [ Expanded( + flex: 2, child: Align( alignment: Alignment.centerLeft, child: AutoSizeText( @@ -50,7 +51,7 @@ class AppSignIn extends HookConsumerWidget { ), ), ), - const Spacer(), + const Spacer(flex: 3), ], ), ), From 9dc0aeb5445074cfca977456edc6c7275993fddb Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sun, 24 Aug 2025 10:43:34 +0200 Subject: [PATCH 355/473] move refresh --- lib/feed/ui/pages/event_handling_page/event_handling_page.dart | 1 - lib/feed/ui/pages/main_page/main_page.dart | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/feed/ui/pages/event_handling_page/event_handling_page.dart b/lib/feed/ui/pages/event_handling_page/event_handling_page.dart index 1e2aacf002..08b1fd3a94 100644 --- a/lib/feed/ui/pages/event_handling_page/event_handling_page.dart +++ b/lib/feed/ui/pages/event_handling_page/event_handling_page.dart @@ -22,7 +22,6 @@ class EventHandlingPage extends HookConsumerWidget { final newsListAsync = ref.watch(adminNewsListProvider); final newsListNotifier = ref.watch(adminNewsListProvider.notifier); final selectedFilter = useState(NewsFilterType.pending); - newsListNotifier.loadNewsList(); return FeedTemplate( child: Padding( diff --git a/lib/feed/ui/pages/main_page/main_page.dart b/lib/feed/ui/pages/main_page/main_page.dart index e2b87fdc83..beb5a68dae 100644 --- a/lib/feed/ui/pages/main_page/main_page.dart +++ b/lib/feed/ui/pages/main_page/main_page.dart @@ -161,6 +161,7 @@ class FeedMainPage extends HookConsumerWidget { text: 'Demandes de publication', onPressed: () { Navigator.of(context).pop(); + newsNotifier.loadNewsList(); QR.to( FeedRouter.root + FeedRouter.eventHandling, From 74ca42d1bed98e61cfb334bccfa17e4bd5e8b1e3 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sun, 24 Aug 2025 10:43:35 +0200 Subject: [PATCH 356/473] part 1 # Conflicts: # lib/feed/ui/pages/add_event_page/add_event_page.dart # lib/feed/ui/pages/main_page/event_card.dart # Conflicts: # lib/feed/ui/pages/add_event_page/add_event_page.dart --- .../pages/add_event_page/add_event_page.dart | 45 ++++++++++------- .../event_handling_page/admin_event_card.dart | 7 ++- .../event_handling_page.dart | 25 +++++----- .../pages/main_page/event_action_admin.dart | 4 +- lib/feed/ui/pages/main_page/event_card.dart | 4 +- lib/l10n/app_fr.arb | 8 ++++ lib/l10n/app_localizations.dart | 48 +++++++++++++++++++ lib/l10n/app_localizations_en.dart | 25 ++++++++++ lib/l10n/app_localizations_fr.dart | 25 ++++++++++ 9 files changed, 157 insertions(+), 34 deletions(-) diff --git a/lib/feed/ui/pages/add_event_page/add_event_page.dart b/lib/feed/ui/pages/add_event_page/add_event_page.dart index a47ff79563..e1c5729c88 100644 --- a/lib/feed/ui/pages/add_event_page/add_event_page.dart +++ b/lib/feed/ui/pages/add_event_page/add_event_page.dart @@ -6,6 +6,7 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:image_picker/image_picker.dart'; +import 'package:qlevar_router/qlevar_router.dart'; // import 'package:syncfusion_flutter_calendar/calendar.dart'; import 'package:titan/admin/class/assocation.dart'; import 'package:titan/admin/providers/my_association_list_provider.dart'; @@ -65,6 +66,8 @@ class AddEventPage extends HookConsumerWidget { displayToast(context, type, msg); } + final localizeWithContext = AppLocalizations.of(context)!; + return FeedTemplate( child: Expanded( child: Form( @@ -102,10 +105,13 @@ class AddEventPage extends HookConsumerWidget { ), ), const SizedBox(height: 10), - TextEntry(label: "Titre", controller: titleController), + TextEntry( + label: localizeWithContext.feedTitle, + controller: titleController, + ), const SizedBox(height: 10), CheckBoxEntry( - title: AppLocalizations.of(context)!.eventAllDay, + title: localizeWithContext.eventAllDay, valueNotifier: allDay, onChanged: () { allDay.value = !allDay.value; @@ -117,7 +123,7 @@ class AddEventPage extends HookConsumerWidget { // const SizedBox(height: 10), // CheckBoxEntry( - // title: AppLocalizations.of(context)!.eventRecurrence, + // title: localizeWithContext.eventRecurrence, // valueNotifier: recurrentController, // onChanged: () { // startDateController.text = ""; @@ -180,7 +186,7 @@ class AddEventPage extends HookConsumerWidget { // ), // const SizedBox(height: 20), // Text( - // AppLocalizations.of(context)!.eventInterval, + // localizeWithContext.eventInterval, // style: const TextStyle(color: Colors.black), // ), // const SizedBox(height: 10), @@ -246,7 +252,7 @@ class AddEventPage extends HookConsumerWidget { ? getOnlyDayDate(context, startDateController) : getFullDate(context, startDateController), controller: startDateController, - label: AppLocalizations.of(context)!.eventStartDate, + label:localizeWithContext.eventStartDate, ), const SizedBox(height: 10), DateEntry( @@ -254,20 +260,23 @@ class AddEventPage extends HookConsumerWidget { ? getOnlyDayDate(context, endDateController) : getFullDate(context, endDateController), controller: endDateController, - label: AppLocalizations.of(context)!.eventEndDate, + label: localizeWithContext.eventEndDate, ), SizedBox(height: 10), - TextEntry(label: "Lieu", controller: locationController), + TextEntry( + label: localizeWithContext.feedLocation, + controller: locationController, + ), SizedBox(height: 10), DateEntry( onTap: () => getFullDate(context, shotgunDateController), controller: shotgunDateController, - label: "Date du SG ", + label: localizeWithContext.feedSGDate, canBeEmpty: true, ), SizedBox(height: 10), TextEntry( - label: "Lien externe pour le SG", + label: localizeWithContext.feedSGExternalLink, controller: externalLinkController, canBeEmpty: true, ), @@ -365,7 +374,7 @@ class AddEventPage extends HookConsumerWidget { ), const SizedBox(height: 40), Button( - text: "Créer l'événement", + text: localizeWithContext.feedCreateEvent, onPressed: () async { if (key.currentState == null) { return; @@ -373,7 +382,7 @@ class AddEventPage extends HookConsumerWidget { if (selectedAssociation.value == null) { displayToastWithContext( TypeMsg.error, - "Veuillez sélectionner une association", + localizeWithContext.feedPleaseSelectAnAssociation, ); return; } @@ -414,14 +423,14 @@ class AddEventPage extends HookConsumerWidget { displayToast( context, TypeMsg.error, - AppLocalizations.of(context)!.eventInvalidDates, + localizeWithContext.eventInvalidDates, ); // } else if (recurrentController.value && // selectedDays.where((element) => element).isEmpty) { // displayToast( // context, // TypeMsg.error, - // AppLocalizations.of(context)!.eventNoDaySelected, + // localizeWithContext.eventNoDaySelected, // ); } else { await tokenExpireWrapper(ref, () async { @@ -491,10 +500,10 @@ class AddEventPage extends HookConsumerWidget { final eventCreated = await eventCreationNotifier .addEvent(newEvent); if (poster.value == null) { - Navigator.of(context).pop(); + QR.back(); displayToastWithContext( TypeMsg.msg, - addedEventMsg, + localizeWithContext.eventAddedEvent, ); newsListNotifier.loadNewsList(); return; @@ -505,16 +514,16 @@ class AddEventPage extends HookConsumerWidget { poster.value!, ); if (value) { - Navigator.of(context).pop(); + QR.back(); displayToastWithContext( TypeMsg.msg, - addedEventMsg, + localizeWithContext.eventAddedEvent, ); newsListNotifier.loadNewsList(); } else { displayToastWithContext( TypeMsg.error, - addingErrorMsg, + localizeWithContext.eventAddingError, ); } }); diff --git a/lib/feed/ui/pages/event_handling_page/admin_event_card.dart b/lib/feed/ui/pages/event_handling_page/admin_event_card.dart index c88054e05c..9f8e39c935 100644 --- a/lib/feed/ui/pages/event_handling_page/admin_event_card.dart +++ b/lib/feed/ui/pages/event_handling_page/admin_event_card.dart @@ -6,6 +6,7 @@ import 'package:titan/feed/providers/admin_news_list_provider.dart'; import 'package:titan/feed/providers/news_list_provider.dart'; import 'package:titan/feed/tools/function.dart'; import 'package:titan/feed/tools/news_helper.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; @@ -19,6 +20,8 @@ class AdminEventCard extends ConsumerWidget { final newsAdminNotifier = ref.watch(adminNewsListProvider.notifier); final newsNotifier = ref.watch(newsListProvider.notifier); + final localizeWithContext = AppLocalizations.of(context)!; + return Container( decoration: BoxDecoration( color: ColorConstants.background, @@ -144,7 +147,7 @@ class AdminEventCard extends ConsumerWidget { waitingColor: ColorConstants.background, child: Center( child: Text( - "Rejeter", + localizeWithContext.feedReject, style: TextStyle( fontSize: 12, fontWeight: FontWeight.bold, @@ -183,7 +186,7 @@ class AdminEventCard extends ConsumerWidget { waitingColor: ColorConstants.background, child: Center( child: Text( - "Approuver", + localizeWithContext.feedApprove, style: TextStyle( fontSize: 12, fontWeight: FontWeight.bold, diff --git a/lib/feed/ui/pages/event_handling_page/event_handling_page.dart b/lib/feed/ui/pages/event_handling_page/event_handling_page.dart index 08b1fd3a94..2a67a6101b 100644 --- a/lib/feed/ui/pages/event_handling_page/event_handling_page.dart +++ b/lib/feed/ui/pages/event_handling_page/event_handling_page.dart @@ -23,6 +23,8 @@ class EventHandlingPage extends HookConsumerWidget { final newsListNotifier = ref.watch(adminNewsListProvider.notifier); final selectedFilter = useState(NewsFilterType.pending); + final localizeWithContext = AppLocalizations.of(context)!; + return FeedTemplate( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 20.0), @@ -37,8 +39,7 @@ class EventHandlingPage extends HookConsumerWidget { const SizedBox(height: 16), Text( - AppLocalizations.of(context)?.feedEventManagement ?? - 'Event Management', + localizeWithContext.feedEventManagement, style: const TextStyle( fontSize: 24, fontWeight: FontWeight.bold, @@ -111,16 +112,16 @@ class EventHandlingPage extends HookConsumerWidget { } String _getFilterName(BuildContext context, NewsFilterType filter) { - final localizations = AppLocalizations.of(context); + final localizeWithContext = AppLocalizations.of(context)!; switch (filter) { case NewsFilterType.all: - return localizations?.feedFilterAll ?? 'All'; + return localizeWithContext.feedFilterAll; case NewsFilterType.pending: - return localizations?.feedFilterPending ?? 'Pending'; + return localizeWithContext.feedFilterPending; case NewsFilterType.approved: - return localizations?.feedFilterApproved ?? 'Approved'; + return localizeWithContext.feedFilterApproved; case NewsFilterType.rejected: - return localizations?.feedFilterRejected ?? 'Rejected'; + return localizeWithContext.feedFilterRejected; } } @@ -144,16 +145,16 @@ class EventHandlingPage extends HookConsumerWidget { } String _getEmptyMessage(BuildContext context, NewsFilterType filter) { - final localizations = AppLocalizations.of(context); + final localizeWithContext = AppLocalizations.of(context)!; switch (filter) { case NewsFilterType.all: - return localizations?.feedEmptyAll ?? 'No events available'; + return localizeWithContext.feedEmptyAll; case NewsFilterType.pending: - return localizations?.feedEmptyPending ?? 'No events pending approval'; + return localizeWithContext.feedEmptyPending; case NewsFilterType.approved: - return localizations?.feedEmptyApproved ?? 'No approved events'; + return localizeWithContext.feedEmptyApproved; case NewsFilterType.rejected: - return localizations?.feedEmptyRejected ?? 'No rejected events'; + return localizeWithContext.feedEmptyRejected; } } } diff --git a/lib/feed/ui/pages/main_page/event_action_admin.dart b/lib/feed/ui/pages/main_page/event_action_admin.dart index 57f8e26493..ddfb4b20d0 100644 --- a/lib/feed/ui/pages/main_page/event_action_admin.dart +++ b/lib/feed/ui/pages/main_page/event_action_admin.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/feed/class/news.dart'; import 'package:titan/feed/providers/admin_news_list_provider.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; @@ -11,6 +12,7 @@ class EventActionAdmin extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final localizeWithContext = AppLocalizations.of(context)!; final newsAdminNotifier = ref.watch(adminNewsListProvider.notifier); return Align( alignment: Alignment.centerRight, @@ -29,7 +31,7 @@ class EventActionAdmin extends ConsumerWidget { waitingColor: ColorConstants.background, child: Center( child: Text( - "Supprimer", + localizeWithContext.eventDelete, style: TextStyle( fontSize: 12, fontWeight: FontWeight.bold, diff --git a/lib/feed/ui/pages/main_page/event_card.dart b/lib/feed/ui/pages/main_page/event_card.dart index a2db3a0679..0e38c36799 100644 --- a/lib/feed/ui/pages/main_page/event_card.dart +++ b/lib/feed/ui/pages/main_page/event_card.dart @@ -6,6 +6,7 @@ import 'package:titan/feed/class/news.dart'; import 'package:titan/feed/providers/news_image_provider.dart'; import 'package:titan/feed/providers/news_images_provider.dart'; import 'package:titan/feed/tools/news_helper.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/auto_loader_child.dart'; @@ -21,6 +22,7 @@ class EventCard extends ConsumerWidget { ); final newsImagesNotifier = ref.watch(newsImagesProvider.notifier); final imageNotifier = ref.watch(newsImageProvider.notifier); + final localizeWithContext = AppLocalizations.of(context)! return GestureDetector( onTap: () { if (item.module == "advert") { @@ -118,7 +120,7 @@ class EventCard extends ConsumerWidget { borderRadius: BorderRadius.all(Radius.circular(5)), ), child: const Text( - 'Terminé', + localizeWithContext.eventEnded, style: TextStyle( color: ColorConstants.background, fontSize: 10, diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 2591ab2bf6..e03d7a4231 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -19,6 +19,14 @@ "feedEmptyApproved": "Aucun événement approuvé", "feedEmptyRejected": "Aucun événement rejeté", "feedEventManagement": "Gestion des événements", + "feedTitle": "Titre", + "feedLocation": "Lieu", + "feedSGDate": "Date du SG", + "feedSGExternalLink": "Lien externe du SG", + "feedCreateEvent": "Créer l'événement", + "feedPleaseSelectAnAssociation": "Veuillez sélectionner une association", + "feedReject" : "Rejeter", + "feedApprove": "Approuver", "eventActionCampaign": "Tu peux voter", "eventActionEvent": "Tu es invité", "eventActionCampaignSubtitle": "Votez maintenant", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index f6873adc94..458196f02d 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -212,6 +212,54 @@ abstract class AppLocalizations { /// **'Gestion des événements'** String get feedEventManagement; + /// No description provided for @feedTitle. + /// + /// In fr, this message translates to: + /// **'Titre'** + String get feedTitle; + + /// No description provided for @feedLocation. + /// + /// In fr, this message translates to: + /// **'Lieu'** + String get feedLocation; + + /// No description provided for @feedSGDate. + /// + /// In fr, this message translates to: + /// **'Date du SG'** + String get feedSGDate; + + /// No description provided for @feedSGExternalLink. + /// + /// In fr, this message translates to: + /// **'Lien externe du SG'** + String get feedSGExternalLink; + + /// No description provided for @feedCreateEvent. + /// + /// In fr, this message translates to: + /// **'Créer l\'événement'** + String get feedCreateEvent; + + /// No description provided for @feedPleaseSelectAnAssociation. + /// + /// In fr, this message translates to: + /// **'Veuillez sélectionner une association'** + String get feedPleaseSelectAnAssociation; + + /// No description provided for @feedReject. + /// + /// In fr, this message translates to: + /// **'Rejeter'** + String get feedReject; + + /// No description provided for @feedApprove. + /// + /// In fr, this message translates to: + /// **'Approuver'** + String get feedApprove; + /// No description provided for @eventActionCampaign. /// /// In fr, this message translates to: diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index f8149b07e5..debe279614 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -65,6 +65,31 @@ class AppLocalizationsEn extends AppLocalizations { @override String get feedEventManagement => 'Event Management'; + @override + String get feedTitle => 'Titre'; + + @override + String get feedLocation => 'Lieu'; + + @override + String get feedSGDate => 'Date du SG'; + + @override + String get feedSGExternalLink => 'Lien externe du SG'; + + @override + String get feedCreateEvent => 'Créer l\'événement'; + + @override + String get feedPleaseSelectAnAssociation => + 'Veuillez sélectionner une association'; + + @override + String get feedReject => 'Rejeter'; + + @override + String get feedApprove => 'Approuver'; + @override String get eventActionCampaign => 'You can vote'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 29471f2300..61cb747351 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -65,6 +65,31 @@ class AppLocalizationsFr extends AppLocalizations { @override String get feedEventManagement => 'Gestion des événements'; + @override + String get feedTitle => 'Titre'; + + @override + String get feedLocation => 'Lieu'; + + @override + String get feedSGDate => 'Date du SG'; + + @override + String get feedSGExternalLink => 'Lien externe du SG'; + + @override + String get feedCreateEvent => 'Créer l\'événement'; + + @override + String get feedPleaseSelectAnAssociation => + 'Veuillez sélectionner une association'; + + @override + String get feedReject => 'Rejeter'; + + @override + String get feedApprove => 'Approuver'; + @override String get eventActionCampaign => 'Tu peux voter'; From 56cca58a353eb43f5baf2da9f21a18772ce266cb Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sun, 24 Aug 2025 10:43:36 +0200 Subject: [PATCH 357/473] part 2 --- lib/feed/ui/pages/main_page/event_card.dart | 2 +- lib/feed/ui/pages/main_page/filter_news.dart | 10 +-- lib/feed/ui/pages/main_page/main_page.dart | 17 +++-- .../main_page/scroll_with_refresh_button.dart | 5 +- lib/l10n/app_fr.arb | 12 ++++ lib/l10n/app_localizations.dart | 72 +++++++++++++++++++ lib/l10n/app_localizations_en.dart | 36 ++++++++++ lib/l10n/app_localizations_fr.dart | 36 ++++++++++ 8 files changed, 178 insertions(+), 12 deletions(-) diff --git a/lib/feed/ui/pages/main_page/event_card.dart b/lib/feed/ui/pages/main_page/event_card.dart index 0e38c36799..e59ff8f518 100644 --- a/lib/feed/ui/pages/main_page/event_card.dart +++ b/lib/feed/ui/pages/main_page/event_card.dart @@ -139,7 +139,7 @@ class EventCard extends ConsumerWidget { borderRadius: BorderRadius.all(Radius.circular(5)), ), child: const Text( - 'En cours', + localizeWithContext.feedOngoing, style: TextStyle(color: ColorConstants.main, fontSize: 10), ), ), diff --git a/lib/feed/ui/pages/main_page/filter_news.dart b/lib/feed/ui/pages/main_page/filter_news.dart index 5df555cbb3..38ddaa35f5 100644 --- a/lib/feed/ui/pages/main_page/filter_news.dart +++ b/lib/feed/ui/pages/main_page/filter_news.dart @@ -3,6 +3,7 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/feed/providers/filter_state_provider.dart'; import 'package:titan/feed/providers/news_list_provider.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/layouts/horizontal_list_view.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; @@ -19,17 +20,18 @@ class FilterNewsModal extends HookWidget { @override Widget build(BuildContext context) { + final localizeWithContext = AppLocalizations.of(context)!; return HookConsumer( builder: (context, ref, child) { final newsListNotifier = ref.watch(newsListProvider.notifier); final filterState = ref.watch(filterStateProvider); final filterStateNotifier = ref.watch(filterStateProvider.notifier); return BottomModalTemplate( - title: 'Filtrer', + title: localizeWithContext.feedFilter, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('Association'), + Text(localizeWithContext.feedAssociation), SizedBox(height: 10), HorizontalListView( height: 50, @@ -80,7 +82,7 @@ class FilterNewsModal extends HookWidget { .toList(), ), SizedBox(height: 30), - Text('Type d\'annonce'), + Text(localizeWithContext.feedNewsType), SizedBox(height: 10), HorizontalListView( height: 50, @@ -128,7 +130,7 @@ class FilterNewsModal extends HookWidget { ), SizedBox(height: 40), Button( - text: 'Appliquer', + text: localizeWithContext.feedApply, onPressed: () { Navigator.of(context).pop(); }, diff --git a/lib/feed/ui/pages/main_page/main_page.dart b/lib/feed/ui/pages/main_page/main_page.dart index beb5a68dae..5e13ca15c2 100644 --- a/lib/feed/ui/pages/main_page/main_page.dart +++ b/lib/feed/ui/pages/main_page/main_page.dart @@ -12,6 +12,7 @@ import 'package:titan/feed/ui/feed.dart'; import 'package:titan/feed/ui/pages/main_page/feed_timeline.dart'; import 'package:titan/feed/ui/pages/main_page/filter_news.dart'; import 'package:titan/feed/ui/pages/main_page/scroll_with_refresh_button.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; @@ -31,6 +32,8 @@ class FeedMainPage extends HookConsumerWidget { final isFeedAdmin = ref.watch(isFeedAdminProvider); final scrollController = useScrollController(); + final localizeWithContext = AppLocalizations.of(context)!; + Future onRefresh() async { await newsListNotifier.loadNewsList(); } @@ -89,8 +92,8 @@ class FeedMainPage extends HookConsumerWidget { Row( children: [ - const Text( - "Actualité", + Text( + localizeWithContext.feedNews, style: TextStyle( fontSize: 24, fontWeight: FontWeight.bold, @@ -143,12 +146,13 @@ class FeedMainPage extends HookConsumerWidget { } else { showCustomBottomModal( modal: BottomModalTemplate( - title: 'Administration', + title: localizeWithContext.feedAdmin, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Button( - text: 'Créer un événement', + text: + localizeWithContext.feedCreateAnEvent, onPressed: () { Navigator.of(context).pop(); QR.to( @@ -158,7 +162,8 @@ class FeedMainPage extends HookConsumerWidget { ), const SizedBox(height: 20), Button( - text: 'Demandes de publication', + text: localizeWithContext + .feedManageRequests, onPressed: () { Navigator.of(context).pop(); newsNotifier.loadNewsList(); @@ -191,7 +196,7 @@ class FeedMainPage extends HookConsumerWidget { builder: (context, news) => news.isEmpty ? const Center( child: Text( - 'Aucune actualité disponible', + localizeWithContext.feedNoNewsAvailable, style: TextStyle( fontSize: 16, color: ColorConstants.tertiary, diff --git a/lib/feed/ui/pages/main_page/scroll_with_refresh_button.dart b/lib/feed/ui/pages/main_page/scroll_with_refresh_button.dart index 569ce2ecfd..5086312d92 100644 --- a/lib/feed/ui/pages/main_page/scroll_with_refresh_button.dart +++ b/lib/feed/ui/pages/main_page/scroll_with_refresh_button.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/navigation/providers/navbar_visibility_provider.dart'; import 'package:titan/tools/constants.dart'; @@ -24,6 +25,8 @@ class ScrollWithRefreshButton extends HookConsumerWidget { final lastUserScrollTime = useState(DateTime.now()); final consecutiveUpwardScrolls = useState(0); + final localizeWithContext = AppLocalizations.of(context)!; + useEffect(() { void scrollListener() { if (!controller.hasClients) return; @@ -130,7 +133,7 @@ class ScrollWithRefreshButton extends HookConsumerWidget { ), const SizedBox(width: 8), Text( - 'Actualiser', + localizeWithContext.feedRefresh, style: TextStyle( color: ColorConstants.background, fontSize: 14, diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index e03d7a4231..ebfc307cea 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -27,6 +27,18 @@ "feedPleaseSelectAnAssociation": "Veuillez sélectionner une association", "feedReject" : "Rejeter", "feedApprove": "Approuver", + "feedEnded": "Terminé", + "feedOngoing": "En cours", + "feedFilter": "Filtrer", + "feedAssociation": "Association", + "feedNewsType": "Type d'actualité", + "feedApply" : "Appliquer", + "feedNews": "Actualités", + "feedAdmin": "Administration", + "feedCreateAnEvent" : "Créer un événement", + "feedManageRequests": "Demandes de publication", + "feedNoNewsAvailable": "Aucune actualité disponible", + "feedRefresh": "Actualiser", "eventActionCampaign": "Tu peux voter", "eventActionEvent": "Tu es invité", "eventActionCampaignSubtitle": "Votez maintenant", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 458196f02d..4efe73d5b6 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -260,6 +260,78 @@ abstract class AppLocalizations { /// **'Approuver'** String get feedApprove; + /// No description provided for @feedEnded. + /// + /// In fr, this message translates to: + /// **'Terminé'** + String get feedEnded; + + /// No description provided for @feedOngoing. + /// + /// In fr, this message translates to: + /// **'En cours'** + String get feedOngoing; + + /// No description provided for @feedFilter. + /// + /// In fr, this message translates to: + /// **'Filtrer'** + String get feedFilter; + + /// No description provided for @feedAssociation. + /// + /// In fr, this message translates to: + /// **'Association'** + String get feedAssociation; + + /// No description provided for @feedNewsType. + /// + /// In fr, this message translates to: + /// **'Type d\'actualité'** + String get feedNewsType; + + /// No description provided for @feedApply. + /// + /// In fr, this message translates to: + /// **'Appliquer'** + String get feedApply; + + /// No description provided for @feedNews. + /// + /// In fr, this message translates to: + /// **'Actualités'** + String get feedNews; + + /// No description provided for @feedAdmin. + /// + /// In fr, this message translates to: + /// **'Administration'** + String get feedAdmin; + + /// No description provided for @feedCreateAnEvent. + /// + /// In fr, this message translates to: + /// **'Créer un événement'** + String get feedCreateAnEvent; + + /// No description provided for @feedManageRequests. + /// + /// In fr, this message translates to: + /// **'Demandes de publication'** + String get feedManageRequests; + + /// No description provided for @feedNoNewsAvailable. + /// + /// In fr, this message translates to: + /// **'Aucune actualité disponible'** + String get feedNoNewsAvailable; + + /// No description provided for @feedRefresh. + /// + /// In fr, this message translates to: + /// **'Actualiser'** + String get feedRefresh; + /// No description provided for @eventActionCampaign. /// /// In fr, this message translates to: diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index debe279614..8fe2e142bf 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -90,6 +90,42 @@ class AppLocalizationsEn extends AppLocalizations { @override String get feedApprove => 'Approuver'; + @override + String get feedEnded => 'Terminé'; + + @override + String get feedOngoing => 'En cours'; + + @override + String get feedFilter => 'Filtrer'; + + @override + String get feedAssociation => 'Association'; + + @override + String get feedNewsType => 'Type d\'actualité'; + + @override + String get feedApply => 'Appliquer'; + + @override + String get feedNews => 'Actualités'; + + @override + String get feedAdmin => 'Administration'; + + @override + String get feedCreateAnEvent => 'Créer un événement'; + + @override + String get feedManageRequests => 'Demandes de publication'; + + @override + String get feedNoNewsAvailable => 'Aucune actualité disponible'; + + @override + String get feedRefresh => 'Actualiser'; + @override String get eventActionCampaign => 'You can vote'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 61cb747351..9b9ff40a64 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -90,6 +90,42 @@ class AppLocalizationsFr extends AppLocalizations { @override String get feedApprove => 'Approuver'; + @override + String get feedEnded => 'Terminé'; + + @override + String get feedOngoing => 'En cours'; + + @override + String get feedFilter => 'Filtrer'; + + @override + String get feedAssociation => 'Association'; + + @override + String get feedNewsType => 'Type d\'actualité'; + + @override + String get feedApply => 'Appliquer'; + + @override + String get feedNews => 'Actualités'; + + @override + String get feedAdmin => 'Administration'; + + @override + String get feedCreateAnEvent => 'Créer un événement'; + + @override + String get feedManageRequests => 'Demandes de publication'; + + @override + String get feedNoNewsAvailable => 'Aucune actualité disponible'; + + @override + String get feedRefresh => 'Actualiser'; + @override String get eventActionCampaign => 'Tu peux voter'; From 281e45ef10124a766bf28b475f894bdeb1ac1616 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sun, 24 Aug 2025 10:43:37 +0200 Subject: [PATCH 358/473] english translations --- lib/l10n/app_en.arb | 20 ++++++++++++++++ lib/l10n/app_localizations_en.dart | 37 +++++++++++++++--------------- 2 files changed, 38 insertions(+), 19 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index e32df92b73..3322f8408a 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -19,6 +19,26 @@ "feedEmptyApproved": "No approved events", "feedEmptyRejected": "No rejected events", "feedEventManagement": "Event Management", + "feedTitle": "Title", + "feedLocation" : "Location", + "feedSGDate": "Date", + "feedSGExternalLink": "SG External link", + "feedCreateEvent": "Create an event", + "feedPleaseSelectAnAssociation": "Please select an association", + "feedReject": "Reject", + "feedApprove": "Approve", + "feedEnded": "Ended", + "feedOngoing": "Ongoing", + "feedFilter": "Filter", + "feedAssociation": "Association", + "feedNewsType": "News type", + "feedApply": "Apply", + "feedNews": "News", + "feedAdmin": "Administration", + "feedCreateAnEvent": "Create an event", + "feedManageRequests": "Manage requests", + "feedNoNewsAvailable": "No news available", + "feedRefresh": "Refresh", "eventActionCampaign": "You can vote", "eventActionEvent": "You are invited", "eventActionCampaignSubtitle": "Vote now", diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 8fe2e142bf..d664ece0f4 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -66,65 +66,64 @@ class AppLocalizationsEn extends AppLocalizations { String get feedEventManagement => 'Event Management'; @override - String get feedTitle => 'Titre'; + String get feedTitle => 'Title'; @override - String get feedLocation => 'Lieu'; + String get feedLocation => 'Location'; @override - String get feedSGDate => 'Date du SG'; + String get feedSGDate => 'Date'; @override - String get feedSGExternalLink => 'Lien externe du SG'; + String get feedSGExternalLink => 'SG External link'; @override - String get feedCreateEvent => 'Créer l\'événement'; + String get feedCreateEvent => 'Create an event'; @override - String get feedPleaseSelectAnAssociation => - 'Veuillez sélectionner une association'; + String get feedPleaseSelectAnAssociation => 'Please select an association'; @override - String get feedReject => 'Rejeter'; + String get feedReject => 'Reject'; @override - String get feedApprove => 'Approuver'; + String get feedApprove => 'Approve'; @override - String get feedEnded => 'Terminé'; + String get feedEnded => 'Ended'; @override - String get feedOngoing => 'En cours'; + String get feedOngoing => 'Ongoing'; @override - String get feedFilter => 'Filtrer'; + String get feedFilter => 'Filter'; @override String get feedAssociation => 'Association'; @override - String get feedNewsType => 'Type d\'actualité'; + String get feedNewsType => 'News type'; @override - String get feedApply => 'Appliquer'; + String get feedApply => 'Apply'; @override - String get feedNews => 'Actualités'; + String get feedNews => 'News'; @override String get feedAdmin => 'Administration'; @override - String get feedCreateAnEvent => 'Créer un événement'; + String get feedCreateAnEvent => 'Create an event'; @override - String get feedManageRequests => 'Demandes de publication'; + String get feedManageRequests => 'Manage requests'; @override - String get feedNoNewsAvailable => 'Aucune actualité disponible'; + String get feedNoNewsAvailable => 'No news available'; @override - String get feedRefresh => 'Actualiser'; + String get feedRefresh => 'Refresh'; @override String get eventActionCampaign => 'You can vote'; From 10f3d0b4de3793efd3a070d3116736855d1fb1e4 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sun, 24 Aug 2025 10:43:37 +0200 Subject: [PATCH 359/473] fix --- lib/l10n/app_en.arb | 2 +- lib/l10n/app_localizations_en.dart | 2 +- lib/tools/ui/widgets/text_entry.dart | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 3322f8408a..851786bdad 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -21,7 +21,7 @@ "feedEventManagement": "Event Management", "feedTitle": "Title", "feedLocation" : "Location", - "feedSGDate": "Date", + "feedSGDate": "SG Date", "feedSGExternalLink": "SG External link", "feedCreateEvent": "Create an event", "feedPleaseSelectAnAssociation": "Please select an association", diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index d664ece0f4..377c2bc08c 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -72,7 +72,7 @@ class AppLocalizationsEn extends AppLocalizations { String get feedLocation => 'Location'; @override - String get feedSGDate => 'Date'; + String get feedSGDate => 'SG Date'; @override String get feedSGExternalLink => 'SG External link'; diff --git a/lib/tools/ui/widgets/text_entry.dart b/lib/tools/ui/widgets/text_entry.dart index 6d426d45bc..4a1f47e3d3 100644 --- a/lib/tools/ui/widgets/text_entry.dart +++ b/lib/tools/ui/widgets/text_entry.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/tools/constants.dart'; class TextEntry extends StatelessWidget { @@ -41,6 +42,7 @@ class TextEntry extends StatelessWidget { @override Widget build(BuildContext context) { + final localizeWithContext = AppLocalizations.of(context)!; return TextFormField( minLines: minLines, maxLines: maxLines, @@ -54,7 +56,7 @@ class TextEntry extends StatelessWidget { enabled: enabled, decoration: InputDecoration( label: Text( - canBeEmpty ? '$label (optionnel)' : label, + canBeEmpty ? localizeWithContext.globalOptionnal(label) : label, style: TextStyle(color: color, height: 0.5), ), suffix: suffixIcon == null && suffix.isEmpty From 1975e75148beeff455e27ee20dd4f34a65b1ea54 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sun, 24 Aug 2025 10:43:37 +0200 Subject: [PATCH 360/473] rebase --- lib/feed/ui/pages/main_page/event_card.dart | 8 ++++---- lib/feed/ui/pages/main_page/main_page.dart | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/feed/ui/pages/main_page/event_card.dart b/lib/feed/ui/pages/main_page/event_card.dart index e59ff8f518..015da5ed94 100644 --- a/lib/feed/ui/pages/main_page/event_card.dart +++ b/lib/feed/ui/pages/main_page/event_card.dart @@ -22,7 +22,7 @@ class EventCard extends ConsumerWidget { ); final newsImagesNotifier = ref.watch(newsImagesProvider.notifier); final imageNotifier = ref.watch(newsImageProvider.notifier); - final localizeWithContext = AppLocalizations.of(context)! + final localizeWithContext = AppLocalizations.of(context)!; return GestureDetector( onTap: () { if (item.module == "advert") { @@ -119,8 +119,8 @@ class EventCard extends ConsumerWidget { color: ColorConstants.main, borderRadius: BorderRadius.all(Radius.circular(5)), ), - child: const Text( - localizeWithContext.eventEnded, + child: Text( + localizeWithContext.feedEnded, style: TextStyle( color: ColorConstants.background, fontSize: 10, @@ -138,7 +138,7 @@ class EventCard extends ConsumerWidget { color: ColorConstants.background, borderRadius: BorderRadius.all(Radius.circular(5)), ), - child: const Text( + child: Text( localizeWithContext.feedOngoing, style: TextStyle(color: ColorConstants.main, fontSize: 10), ), diff --git a/lib/feed/ui/pages/main_page/main_page.dart b/lib/feed/ui/pages/main_page/main_page.dart index 5e13ca15c2..bb74f392b5 100644 --- a/lib/feed/ui/pages/main_page/main_page.dart +++ b/lib/feed/ui/pages/main_page/main_page.dart @@ -166,7 +166,7 @@ class FeedMainPage extends HookConsumerWidget { .feedManageRequests, onPressed: () { Navigator.of(context).pop(); - newsNotifier.loadNewsList(); + newsListNotifier.loadNewsList(); QR.to( FeedRouter.root + FeedRouter.eventHandling, @@ -194,7 +194,7 @@ class FeedMainPage extends HookConsumerWidget { child: AsyncChild( value: news, builder: (context, news) => news.isEmpty - ? const Center( + ? Center( child: Text( localizeWithContext.feedNoNewsAvailable, style: TextStyle( From 5d5ae0ec7b1ac26e9061ff8991be22afbc4689f3 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sun, 24 Aug 2025 10:43:38 +0200 Subject: [PATCH 361/473] sg translations --- lib/feed/tools/news_helper.dart | 62 +++++++++---------- .../pages/add_event_page/add_event_page.dart | 13 ++-- lib/feed/ui/pages/main_page/event_action.dart | 3 +- lib/l10n/app_en.arb | 18 ++++++ lib/l10n/app_fr.arb | 21 +++++++ lib/l10n/app_localizations.dart | 30 +++++++++ lib/l10n/app_localizations_en.dart | 20 ++++++ lib/l10n/app_localizations_fr.dart | 20 ++++++ 8 files changed, 145 insertions(+), 42 deletions(-) diff --git a/lib/feed/tools/news_helper.dart b/lib/feed/tools/news_helper.dart index b934b640e7..7ad682e21d 100644 --- a/lib/feed/tools/news_helper.dart +++ b/lib/feed/tools/news_helper.dart @@ -44,22 +44,22 @@ String formatUserFriendlyDate( final timeFormat = DateFormat('HH:mm'); final time = timeFormat.format(date); - final connector = AppLocalizations.of(context)?.dateAt ?? 'à'; + final connector = AppLocalizations.of(context)!.dateAt; final difference = dateDay.difference(today).inDays; if (difference == 0) { - return "${_capitalize(AppLocalizations.of(context)?.dateToday ?? 'Aujourd\'hui')} $connector $time"; + return "${_capitalize(AppLocalizations.of(context)!.dateToday)} $connector $time"; } else if (difference == -1) { - return "${_capitalize(AppLocalizations.of(context)?.dateYesterday ?? 'Hier')} $connector $time"; + return "${_capitalize(AppLocalizations.of(context)!.dateYesterday)} $connector $time"; } else if (difference == 1) { - return "${_capitalize(AppLocalizations.of(context)?.dateTomorrow ?? 'Demain')} $connector $time"; + return "${_capitalize(AppLocalizations.of(context)!.dateTomorrow)} $connector $time"; } else if (difference > 1 && difference < 7) { final dayName = _capitalize(DateFormat('EEEE', locale).format(date)); return "$dayName $connector $time"; } else if (difference < 0 && difference > -7) { final dayName = _capitalize(DateFormat('EEEE', locale).format(date)); - final prefix = AppLocalizations.of(context)?.dateLast ?? ''; + final prefix = AppLocalizations.of(context)!.dateLast; final prefixWithSpace = prefix.isEmpty ? '' : _capitalize('$prefix '); return "$prefixWithSpace$dayName $connector $time"; @@ -87,10 +87,7 @@ String getNewsSubtitle( final startDate = news.start.toLocal(); if (isNewsOngoing(news) && news.end != null) { - final untilText = _capitalize( - AppLocalizations.of(context)?.dateUntil ?? - (locale == 'fr' ? "Jusqu'au" : "Until"), - ); + final untilText = _capitalize(AppLocalizations.of(context)!.dateUntil); subtitle = "$untilText ${formatUserFriendlyDate(news.end!.toLocal(), locale: locale, context: context)}"; } else if (news.end == null) { @@ -107,7 +104,7 @@ String getNewsSubtitle( startDate.day == endDate.day; if (sameDay) { - final connector = AppLocalizations.of(context)?.dateAt ?? 'à'; + final connector = AppLocalizations.of(context)!.dateAt; String dateStr = formatUserFriendlyDate( startDate, @@ -118,14 +115,12 @@ String getNewsSubtitle( final startTime = DateFormat('HH:mm').format(startDate); final endTime = DateFormat('HH:mm').format(endDate); - final fromWord = AppLocalizations.of(context)?.dateFrom ?? 'de'; - final toWord = AppLocalizations.of(context)?.dateTo ?? 'à'; + final fromWord = AppLocalizations.of(context)!.dateFrom; + final toWord = AppLocalizations.of(context)!.dateTo; subtitle = '$dateStr $fromWord $startTime $toWord $endTime'; } else { - final fromWord = _capitalize( - AppLocalizations.of(context)?.dateFrom ?? 'de', - ); + final fromWord = _capitalize(AppLocalizations.of(context)!.dateFrom); final now = DateTime.now(); final today = DateTime(now.year, now.month, now.day); @@ -133,8 +128,8 @@ String getNewsSubtitle( final difference = endDateTime.difference(today).inDays; final toWord = (difference >= -1 && difference <= 1) - ? (AppLocalizations.of(context)?.dateTo ?? 'à') - : (AppLocalizations.of(context)?.dateBetweenDays ?? 'au'); + ? (AppLocalizations.of(context)!.dateTo) + : (AppLocalizations.of(context)!.dateBetweenDays); subtitle = '$fromWord ${formatUserFriendlyDate(startDate, locale: locale, context: context)} $toWord ${formatUserFriendlyDate(endDate, locale: locale, context: context)}'; @@ -152,9 +147,9 @@ String getActionTitle(News news, BuildContext context) { final module = news.module; if (module == "campagne") { - return AppLocalizations.of(context)?.eventActionCampaign ?? 'Tu peux voter'; + return AppLocalizations.of(context)!.eventActionCampaign; } else if (module == "event") { - return AppLocalizations.of(context)?.eventActionEvent ?? 'Tu est invité'; + return AppLocalizations.of(context)!.eventActionEvent; } return ''; } @@ -165,11 +160,12 @@ String getWaitingTitle( required String timeToGo, }) { final module = news.module; + final localizeWithContext = AppLocalizations.of(context)!; if (module == "campagne") { - return 'Vote $timeToGo'; + return localizeWithContext.feedVoteIn(timeToGo); } else if (module == "event") { - return 'Shotgun $timeToGo'; + return localizeWithContext.feedShotgunIn(timeToGo); } return ''; } @@ -178,11 +174,9 @@ String getActionSubtitle(News news, BuildContext context) { final module = news.module; if (module == "campagne") { - return AppLocalizations.of(context)?.eventActionCampaignSubtitle ?? - 'Votes maintenant'; + return AppLocalizations.of(context)!.eventActionCampaignSubtitle; } else if (module == "event") { - return AppLocalizations.of(context)?.eventActionEventSubtitle ?? - 'Réponds à l\'invitation'; + return AppLocalizations.of(context)!.eventActionEventSubtitle; } return ''; } @@ -191,22 +185,21 @@ String getActionEnableButtonText(News news, BuildContext context) { final module = news.module; if (module == "campagne") { - return AppLocalizations.of(context)?.eventActionCampaignButton ?? 'Voter'; + return AppLocalizations.of(context)!.eventActionCampaignButton; } else if (module == "event") { - return AppLocalizations.of(context)?.eventActionEventButton ?? 'Participer'; + return AppLocalizations.of(context)!.eventActionEventButton; } return ''; } String getActionValidatedButtonText(News news, BuildContext context) { final module = news.module; + final localizeWithContext = AppLocalizations.of(context)!; if (module == "campagne") { - return AppLocalizations.of(context)?.eventActionCampaignValidated ?? - 'J\'ai voté !'; + return localizeWithContext.eventActionCampaignValidated; } else if (module == "event") { - return AppLocalizations.of(context)?.eventActionEventValidated ?? - 'Je viens !'; + return localizeWithContext.eventActionEventValidated; } return ''; } @@ -217,6 +210,7 @@ void getActionButtonAction( WidgetRef ref, ) async { final module = news.module; + final localizeWithContext = AppLocalizations.of(context)!; if (module == "campagne") { QR.to(VoteRouter.root); @@ -231,7 +225,11 @@ void getActionButtonAction( mode: LaunchMode.externalApplication, ); } else { - displayToast(context, TypeMsg.error, 'Impossible d\'ouvrir le lien'); + displayToast( + context, + TypeMsg.error, + localizeWithContext.feedCantOpenLink, + ); } }, error: (e, stackTrace) { diff --git a/lib/feed/ui/pages/add_event_page/add_event_page.dart b/lib/feed/ui/pages/add_event_page/add_event_page.dart index e1c5729c88..b1e22666f5 100644 --- a/lib/feed/ui/pages/add_event_page/add_event_page.dart +++ b/lib/feed/ui/pages/add_event_page/add_event_page.dart @@ -252,7 +252,7 @@ class AddEventPage extends HookConsumerWidget { ? getOnlyDayDate(context, startDateController) : getFullDate(context, startDateController), controller: startDateController, - label:localizeWithContext.eventStartDate, + label: localizeWithContext.eventStartDate, ), const SizedBox(height: 10), DateEntry( @@ -390,7 +390,8 @@ class AddEventPage extends HookConsumerWidget { shotgunDateController.text.isNotEmpty) { displayToastWithContext( TypeMsg.error, - "Veuillez fournir un lien externe", + localizeWithContext + .feedPleaseProvideASGExternalLink, ); return; } @@ -398,16 +399,10 @@ class AddEventPage extends HookConsumerWidget { shotgunDateController.text.isEmpty) { displayToastWithContext( TypeMsg.error, - "Veuillez fournir une date", + localizeWithContext.feedPleaseProvideASGDate, ); return; } - final addedEventMsg = AppLocalizations.of( - context, - )!.eventAddedEvent; - final addingErrorMsg = AppLocalizations.of( - context, - )!.eventAddingError; if (key.currentState!.validate()) { // if (allDay.value) { // startDateController.text = diff --git a/lib/feed/ui/pages/main_page/event_action.dart b/lib/feed/ui/pages/main_page/event_action.dart index 5a9eb8c1a2..d707d1e4dd 100644 --- a/lib/feed/ui/pages/main_page/event_action.dart +++ b/lib/feed/ui/pages/main_page/event_action.dart @@ -31,6 +31,7 @@ class EventAction extends HookWidget { @override Widget build(BuildContext context) { final now = useState(DateTime.now()); + final locale = Localizations.localeOf(context); useEffect(() { final timer = Timer.periodic(const Duration(seconds: 1), (_) { @@ -68,7 +69,7 @@ class EventAction extends HookWidget { timeOpening!.isAfter(now.value) ? Timeago( date: timeOpening!, - locale: 'fr_short', + locale: '${locale.languageCode}_short', allowFromNow: true, refreshRate: const Duration(seconds: 1), builder: (context, str) => Text( diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 851786bdad..64747b9ccd 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -39,6 +39,24 @@ "feedManageRequests": "Manage requests", "feedNoNewsAvailable": "No news available", "feedRefresh": "Refresh", + "feedShotgunIn": "Shotgun in {time}", + "@feedShotgunIn": { + "description": "Shotgun in time", + "placeholders": { + "time": { + "type": "String" + } + } + }, + "feedVoteIn": "Vote in {time}", + "@feedVoteIn": { + "description": "Vote in time", + "placeholders": { + "time": { + "type": "String" + } + } + }, "eventActionCampaign": "You can vote", "eventActionEvent": "You are invited", "eventActionCampaignSubtitle": "Vote now", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index ebfc307cea..73b68c0c85 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -39,6 +39,27 @@ "feedManageRequests": "Demandes de publication", "feedNoNewsAvailable": "Aucune actualité disponible", "feedRefresh": "Actualiser", + "feedPleaseProvideASGExternalLink": "Veuillez entrer un lien externe pour le SG", + "feedPleaseProvideASGDate": "Veuillez entrer une date de SG", + "feedShotgunIn" : "Shotgun {time}", + "@feedShotgunIn": { + "description": "Placeholder pour le temps restant avant le shotgun", + "placeholders": { + "time": { + "type": "String" + } + } + }, + "feedVoteIn": "Vote {time}", + "@feedVoteIn": { + "description": "Temps restant avant le vote", + "placeholders": { + "time": { + "type": "String" + } + } + }, + "feedCantOpenLink": "Impossible d'ouvrir le lien", "eventActionCampaign": "Tu peux voter", "eventActionEvent": "Tu es invité", "eventActionCampaignSubtitle": "Votez maintenant", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 4efe73d5b6..e88bb115a5 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -332,6 +332,36 @@ abstract class AppLocalizations { /// **'Actualiser'** String get feedRefresh; + /// No description provided for @feedPleaseProvideASGExternalLink. + /// + /// In fr, this message translates to: + /// **'Veuillez entrer un lien externe pour le SG'** + String get feedPleaseProvideASGExternalLink; + + /// No description provided for @feedPleaseProvideASGDate. + /// + /// In fr, this message translates to: + /// **'Veuillez entrer une date de SG'** + String get feedPleaseProvideASGDate; + + /// Placeholder pour le temps restant avant le shotgun + /// + /// In fr, this message translates to: + /// **'Shotgun {time}'** + String feedShotgunIn(String time); + + /// Temps restant avant le vote + /// + /// In fr, this message translates to: + /// **'Vote {time}'** + String feedVoteIn(String time); + + /// No description provided for @feedCantOpenLink. + /// + /// In fr, this message translates to: + /// **'Impossible d\'ouvrir le lien'** + String get feedCantOpenLink; + /// No description provided for @eventActionCampaign. /// /// In fr, this message translates to: diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 377c2bc08c..43e77d66f8 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -125,6 +125,26 @@ class AppLocalizationsEn extends AppLocalizations { @override String get feedRefresh => 'Refresh'; + @override + String get feedPleaseProvideASGExternalLink => + 'Veuillez entrer un lien externe pour le SG'; + + @override + String get feedPleaseProvideASGDate => 'Veuillez entrer une date de SG'; + + @override + String feedShotgunIn(String time) { + return 'Shotgun in $time'; + } + + @override + String feedVoteIn(String time) { + return 'Vote in $time'; + } + + @override + String get feedCantOpenLink => 'Impossible d\'ouvrir le lien'; + @override String get eventActionCampaign => 'You can vote'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 9b9ff40a64..f9f7d62fc3 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -126,6 +126,26 @@ class AppLocalizationsFr extends AppLocalizations { @override String get feedRefresh => 'Actualiser'; + @override + String get feedPleaseProvideASGExternalLink => + 'Veuillez entrer un lien externe pour le SG'; + + @override + String get feedPleaseProvideASGDate => 'Veuillez entrer une date de SG'; + + @override + String feedShotgunIn(String time) { + return 'Shotgun $time'; + } + + @override + String feedVoteIn(String time) { + return 'Vote $time'; + } + + @override + String get feedCantOpenLink => 'Impossible d\'ouvrir le lien'; + @override String get eventActionCampaign => 'Tu peux voter'; From 7eb019d6eaa4c2be259f67cbd63b3fa13380364e Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sun, 24 Aug 2025 10:43:39 +0200 Subject: [PATCH 362/473] missing transliations --- lib/feed/ui/pages/main_page/event_action.dart | 4 +++- lib/l10n/app_en.arb | 3 ++- lib/l10n/app_fr.arb | 1 + lib/l10n/app_localizations.dart | 6 ++++++ lib/l10n/app_localizations_en.dart | 5 ++++- lib/l10n/app_localizations_fr.dart | 3 +++ 6 files changed, 19 insertions(+), 3 deletions(-) diff --git a/lib/feed/ui/pages/main_page/event_action.dart b/lib/feed/ui/pages/main_page/event_action.dart index d707d1e4dd..20492640ba 100644 --- a/lib/feed/ui/pages/main_page/event_action.dart +++ b/lib/feed/ui/pages/main_page/event_action.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:timeago_flutter/timeago_flutter.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/tools/constants.dart'; class EventAction extends HookWidget { @@ -32,6 +33,7 @@ class EventAction extends HookWidget { Widget build(BuildContext context) { final now = useState(DateTime.now()); final locale = Localizations.localeOf(context); + final localizeWithContext = AppLocalizations.of(context)!; useEffect(() { final timer = Timer.periodic(const Duration(seconds: 1), (_) { @@ -55,7 +57,7 @@ class EventAction extends HookWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - isWaiting ? 'Prépares-toi' : title, + isWaiting ? AppLocalizations.of(context)!.feedGetReady : title, style: const TextStyle( fontSize: 13, fontWeight: FontWeight.bold, diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 64747b9ccd..e19038dcb7 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -57,10 +57,11 @@ } } }, + "feedGetReady": "Get ready!", "eventActionCampaign": "You can vote", "eventActionEvent": "You are invited", "eventActionCampaignSubtitle": "Vote now", - "eventActionEventSubtitle": "Respond to the invitation", + "eventActionEventSubtitle": "Answer the invitation", "eventActionCampaignButton": "Vote", "eventActionEventButton": "Participate", "eventActionCampaignValidated": "I voted!", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 73b68c0c85..57b2b2560e 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -60,6 +60,7 @@ } }, "feedCantOpenLink": "Impossible d'ouvrir le lien", + "feedGetReady": "Prépare-toi !", "eventActionCampaign": "Tu peux voter", "eventActionEvent": "Tu es invité", "eventActionCampaignSubtitle": "Votez maintenant", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index e88bb115a5..97bc7f9fe3 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -362,6 +362,12 @@ abstract class AppLocalizations { /// **'Impossible d\'ouvrir le lien'** String get feedCantOpenLink; + /// No description provided for @feedGetReady. + /// + /// In fr, this message translates to: + /// **'Prépare-toi !'** + String get feedGetReady; + /// No description provided for @eventActionCampaign. /// /// In fr, this message translates to: diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 43e77d66f8..064def6d72 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -145,6 +145,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get feedCantOpenLink => 'Impossible d\'ouvrir le lien'; + @override + String get feedGetReady => 'Get ready!'; + @override String get eventActionCampaign => 'You can vote'; @@ -155,7 +158,7 @@ class AppLocalizationsEn extends AppLocalizations { String get eventActionCampaignSubtitle => 'Vote now'; @override - String get eventActionEventSubtitle => 'Respond to the invitation'; + String get eventActionEventSubtitle => 'Answer the invitation'; @override String get eventActionCampaignButton => 'Vote'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index f9f7d62fc3..612cff22d9 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -146,6 +146,9 @@ class AppLocalizationsFr extends AppLocalizations { @override String get feedCantOpenLink => 'Impossible d\'ouvrir le lien'; + @override + String get feedGetReady => 'Prépare-toi !'; + @override String get eventActionCampaign => 'Tu peux voter'; From 1f5e57a02c721807e9943a08a6f1182be92d6428 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sun, 24 Aug 2025 10:43:39 +0200 Subject: [PATCH 363/473] useless variable --- lib/feed/ui/pages/main_page/event_action.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/feed/ui/pages/main_page/event_action.dart b/lib/feed/ui/pages/main_page/event_action.dart index 20492640ba..2b128a55e5 100644 --- a/lib/feed/ui/pages/main_page/event_action.dart +++ b/lib/feed/ui/pages/main_page/event_action.dart @@ -33,7 +33,6 @@ class EventAction extends HookWidget { Widget build(BuildContext context) { final now = useState(DateTime.now()); final locale = Localizations.localeOf(context); - final localizeWithContext = AppLocalizations.of(context)!; useEffect(() { final timer = Timer.periodic(const Duration(seconds: 1), (_) { From 821c48fde984e579c9395c296d56744cf30d1ed6 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 24 Aug 2025 10:48:01 +0200 Subject: [PATCH 364/473] fix: removing default size --- lib/login/ui/app_sign_in.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/login/ui/app_sign_in.dart b/lib/login/ui/app_sign_in.dart index 910d2e2ee1..36cac611ee 100644 --- a/lib/login/ui/app_sign_in.dart +++ b/lib/login/ui/app_sign_in.dart @@ -43,7 +43,6 @@ class AppSignIn extends HookConsumerWidget { AppLocalizations.of(context)!.loginAppName, style: GoogleFonts.elMessiri( textStyle: const TextStyle( - fontSize: 40, fontWeight: FontWeight.bold, color: Colors.white, ), From 9340caf0821155294971d2e7c5d5d5006db07afc Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 24 Aug 2025 11:04:52 +0200 Subject: [PATCH 365/473] fix: missing maxlines --- lib/login/ui/app_sign_in.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/login/ui/app_sign_in.dart b/lib/login/ui/app_sign_in.dart index 36cac611ee..9cb8260c44 100644 --- a/lib/login/ui/app_sign_in.dart +++ b/lib/login/ui/app_sign_in.dart @@ -41,8 +41,10 @@ class AppSignIn extends HookConsumerWidget { alignment: Alignment.centerLeft, child: AutoSizeText( AppLocalizations.of(context)!.loginAppName, + maxLines: 1, style: GoogleFonts.elMessiri( textStyle: const TextStyle( + fontSize: 40, fontWeight: FontWeight.bold, color: Colors.white, ), From fabe3b5b3c9413a759729c47a5c19e574937b19f Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 24 Aug 2025 11:59:37 +0200 Subject: [PATCH 366/473] feat: missing trads --- ...ation_membership_member_editable_card.dart | 16 ++--- .../pages/form_page/add_edit_advert_page.dart | 2 +- lib/l10n/app_en.arb | 18 ++++- lib/l10n/app_fr.arb | 21 ++++-- lib/l10n/app_localizations.dart | 66 +++++++++++++++++++ lib/l10n/app_localizations_en.dart | 41 +++++++++++- lib/l10n/app_localizations_fr.dart | 35 ++++++++++ .../ui/components/transaction_card.dart | 2 +- .../ui/pages/devices_page/devices_page.dart | 2 +- .../ui/pages/fund_page/confirm_button.dart | 2 +- lib/paiement/ui/pages/scan_page/scanner.dart | 18 +++-- .../ui/pages/admin_page/admin_page.dart | 2 +- lib/tools/ui/styleguide/searchbar.dart | 7 +- lib/tools/ui/styleguide/text_entry.dart | 4 +- lib/tools/ui/widgets/custom_dialog_box.dart | 5 +- lib/tools/ui/widgets/date_entry.dart | 3 +- lib/tools/ui/widgets/image_picker_on_tap.dart | 3 +- lib/tools/ui/widgets/text_entry.dart | 4 +- lib/vote/ui/components/member_card.dart | 2 +- lib/vote/ui/pages/admin_page/admin_page.dart | 4 +- .../ui/pages/admin_page/contender_card.dart | 2 +- lib/vote/ui/pages/admin_page/vote_count.dart | 3 +- .../contender_pages/add_edit_contender.dart | 6 +- lib/vote/ui/pages/main_page/main_page.dart | 4 +- 24 files changed, 223 insertions(+), 49 deletions(-) diff --git a/lib/admin/ui/pages/membership/association_membership_detail_page/association_membership_member_editable_card.dart b/lib/admin/ui/pages/membership/association_membership_detail_page/association_membership_member_editable_card.dart index 95908887dc..ca5540fc47 100644 --- a/lib/admin/ui/pages/membership/association_membership_detail_page/association_membership_member_editable_card.dart +++ b/lib/admin/ui/pages/membership/association_membership_detail_page/association_membership_member_editable_card.dart @@ -29,6 +29,10 @@ class MemberEditableCard extends HookConsumerWidget { userAssociationMembershipProvider.notifier, ); + final localization = AppLocalizations.of( + context, + )!; + void displayToastWithContext(TypeMsg type, String msg) { displayToast(context, type, msg); } @@ -103,16 +107,12 @@ class MemberEditableCard extends HookConsumerWidget { context: context, builder: (context) { return CustomDialogBox( - title: "Supprimer le membre", + title: localization.adminDeleteAssociationMember, descriptions: - "Êtes-vous sûr de vouloir supprimer ce membre ?", + localization.adminDeleteAssociationMemberConfirmation, onYes: () async { - final deletedMemberMsg = AppLocalizations.of( - context, - )!.phonebookDeletedMember; - final deleteMemberErrorMsg = AppLocalizations.of( - context, - )!.phonebookDeletingError; + final deletedMemberMsg = localization.phonebookDeletedMember; + final deleteMemberErrorMsg = localization.phonebookDeletingError; await tokenExpireWrapper(ref, () async { final result = await associationMembershipMemberListNotifier diff --git a/lib/advert/ui/pages/form_page/add_edit_advert_page.dart b/lib/advert/ui/pages/form_page/add_edit_advert_page.dart index b05299cd51..2265a825e8 100644 --- a/lib/advert/ui/pages/form_page/add_edit_advert_page.dart +++ b/lib/advert/ui/pages/form_page/add_edit_advert_page.dart @@ -228,7 +228,7 @@ class AdvertAddEditAdvertPage extends HookConsumerWidget { Padding( padding: const EdgeInsets.symmetric(horizontal: 30.0), child: CheckBoxEntry( - title: "Poster dans le feed ?", + title: AppLocalizations.of(context)!.advertPublishToFeed, valueNotifier: postToFeed, onChanged: () { postToFeed.value = !postToFeed.value; diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index e19038dcb7..ee8d19ace5 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -20,7 +20,7 @@ "feedEmptyRejected": "No rejected events", "feedEventManagement": "Event Management", "feedTitle": "Title", - "feedLocation" : "Location", + "feedLocation": "Location", "feedSGDate": "SG Date", "feedSGExternalLink": "SG External link", "feedCreateEvent": "Create an event", @@ -39,6 +39,8 @@ "feedManageRequests": "Manage requests", "feedNoNewsAvailable": "No news available", "feedRefresh": "Refresh", + "feedPleaseProvideASGExternalLink": "Please provide a SG external link", + "feedPleaseProvideASGDate": "Please provide a SG date", "feedShotgunIn": "Shotgun in {time}", "@feedShotgunIn": { "description": "Shotgun in time", @@ -57,6 +59,7 @@ } } }, + "feedCantOpenLink": "Can't open link", "feedGetReady": "Get ready!", "eventActionCampaign": "You can vote", "eventActionEvent": "You are invited", @@ -107,6 +110,8 @@ "adminDateError": "Start date must be before end date", "adminDefineAsBankAccountHolder": "Define as bank account holder", "adminDelete": "Delete", + "adminDeleteAssociationMember": "Delete member?", + "adminDeleteAssociationMemberConfirmation": "Are you sure you want to delete this member?", "adminDeleteAssociationMembership": "Delete membership?", "adminDeletedAssociationMembership": "Membership deleted", "adminDeleteGroup": "Delete group?", @@ -255,6 +260,7 @@ "advertNoMoreAnnouncer": "No more announcers available", "advertNoValue": "Please enter a value", "advertPositiveNumber": "Please enter a positive number", + "advertPublishToFeed": "Publish to feed?", "advertRemovedAnnouncer": "Announcer removed", "advertRemovingError": "Error during removal", "advertTags": "Tags", @@ -808,6 +814,7 @@ "paiementCancelTransactions": "Cancel transactions", "paiementCanManageSellers": "Can manage sellers", "paiementCanSeeHistory": "Can view history", + "paiementCantLaunchURL": "Can't open link", "paiementClose": "Close", "paiementCreate": "Create", "paiementCreateInvoice": "Create new invoice", @@ -919,7 +926,10 @@ "paiementRightsUpdated": "Rights updated", "paiementRightsUpdateError": "Error while updating rights", "paiementScan": "Scan", + "paiementScanAlreadyUsedQRCode": "QR Code already used", "paiementScanCode": "Scan a code", + "paiementScanNoMembership": "No membership", + "paiementScanNoMembershipConfirmation": "This product is not available to non-members. Confirm the payment?", "paiementSeeHistory": "View history", "paiementSelectStructure": "Select a structure", "paiementSellerError": "You are not a seller of this store", @@ -958,6 +968,7 @@ "paiementTransferStructureDescription": "The new manager will have access to all structure management features. You will receive an email to confirm this transfer. The link will only be active for 20 minutes. This action is irreversible. Are you sure you want to continue?", "paiementTransferStructureError": "Error while transferring structure", "paiementTransferStructureSuccess": "Structure transfer requested successfully", + "paiementUnknownDevice": "Unknown device", "paiementValidUntil": "Valid until", "paiementYouAreTransferingStructureTo": "You are about to transfer the structure to ", "phAddNewJournal": "Add a new journal", @@ -1511,6 +1522,7 @@ "voteCloseVote": "Close votes", "voteConfirmVote": "Confirm vote", "voteCountVote": "Count votes", + "voteDelete": "Delete", "voteDeletedAll": "All deleted", "voteDeletedPipo": "Fake lists deleted", "voteDeletedSection": "Section deleted", @@ -1614,5 +1626,7 @@ "moduleStyleGuideDescription": "Style guide for developers", "moduleAdminDescription": "Administration module for administrators", "moduleOthersDescription": "Other modules", - "modulePaymentDescription": "Pay and see your transactions" + "modulePaymentDescription": "Pay and see your transactions", + "toolInvalidNumber": "Invalid number", + "toolDateRequired": "Date required" } diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 57b2b2560e..b9428def06 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -25,23 +25,23 @@ "feedSGExternalLink": "Lien externe du SG", "feedCreateEvent": "Créer l'événement", "feedPleaseSelectAnAssociation": "Veuillez sélectionner une association", - "feedReject" : "Rejeter", + "feedReject": "Rejeter", "feedApprove": "Approuver", "feedEnded": "Terminé", "feedOngoing": "En cours", "feedFilter": "Filtrer", "feedAssociation": "Association", "feedNewsType": "Type d'actualité", - "feedApply" : "Appliquer", + "feedApply": "Appliquer", "feedNews": "Actualités", "feedAdmin": "Administration", - "feedCreateAnEvent" : "Créer un événement", + "feedCreateAnEvent": "Créer un événement", "feedManageRequests": "Demandes de publication", "feedNoNewsAvailable": "Aucune actualité disponible", "feedRefresh": "Actualiser", "feedPleaseProvideASGExternalLink": "Veuillez entrer un lien externe pour le SG", "feedPleaseProvideASGDate": "Veuillez entrer une date de SG", - "feedShotgunIn" : "Shotgun {time}", + "feedShotgunIn": "Shotgun {time}", "@feedShotgunIn": { "description": "Placeholder pour le temps restant avant le shotgun", "placeholders": { @@ -110,6 +110,8 @@ "adminDateError": "La date de début doit être avant la date de fin", "adminDefineAsBankAccountHolder": "Définir comme titulaire du compte bancaire", "adminDelete": "Supprimer", + "adminDeleteAssociationMember": "Supprimer le membre ?", + "adminDeleteAssociationMemberConfirmation": "Êtes-vous sûr de vouloir supprimer ce membre ?", "adminDeleteAssociationMembership": "Supprimer l'adhésion ?", "adminDeletedAssociationMembership": "Adhésion supprimée", "adminDeleteGroup": "Supprimer le groupe", @@ -266,6 +268,7 @@ "advertNoMoreAnnouncer": "Aucun annonceur n'est disponible", "advertNoValue": "Veuillez entrer une valeur", "advertPositiveNumber": "Veuillez entrer un nombre positif", + "advertPublishToFeed": "Publier dans le feed", "advertRemovedAnnouncer": "Annonceur supprimé", "advertRemovingError": "Erreur lors de la suppression", "advertTags": "Tags", @@ -819,6 +822,7 @@ "paiementCancelTransactions": "Annuler les transactions", "paiementCanManageSellers": "Peut gérer les vendeurs", "paiementCanSeeHistory": "Peut voir l'historique", + "paiementCantLaunchURL": "Impossible d'ouvrir le lien", "paiementClose": "Fermer", "paiementCreate": "Créer", "paiementCreateInvoice": "Créer une facture", @@ -930,7 +934,10 @@ "paiementRightsUpdated": "Droits mis à jour", "paiementRightsUpdateError": "Erreur lors de la mise à jour des droits", "paiementScan": "Scanner", + "paiementScanAlreadyUsedQRCode": "QR Code déjà utilisé", "paiementScanCode": "Scanner un code", + "paiementScanNoMembership": "Pas d'adhésion", + "paiementScanNoMembershipConfirmation": "Ce produit n'est pas disponnible pour les non-adhérents. Confirmer l'encaissement ?", "paiementSeeHistory": "Voir l'historique", "paiementSelectStructure": "Choisir une structure", "paiementSellerError": "Vous n'êtes pas vendeur de ce magasin", @@ -969,6 +976,7 @@ "paiementTransferStructureDescription": "Le nouveau responsable aura accès à toutes les fonctionnalités de gestion de la structure. Vous allez recevoir un email pour valider ce transfert. Le lien ne sera actif que pendant 20 minutes. Cette action est irréversible. Êtes-vous sûr de vouloir continuer ?", "paiementTransferStructureError": "Erreur lors du transfert de la structure", "paiementTransferStructureSuccess": "Transfert de structure demandé avec succès", + "paiementUnknownDevice": "Appareil inconnu", "paiementValidUntil": "Valide jusqu'à", "paiementYouAreTransferingStructureTo": "Vous êtes sur le point de transférer la structure à ", "phAddNewJournal": "Ajouter un nouveau journal", @@ -1521,6 +1529,7 @@ "voteCloseVote": "Fermer les votes", "voteConfirmVote": "Confirmer le vote", "voteCountVote": "Dépouiller les votes", + "voteDelete": "Supprimer", "voteDeletedAll": "Tout supprimé", "voteDeletedPipo": "Listes pipos supprimées", "voteDeletedSection": "Section supprimée", @@ -1624,5 +1633,7 @@ "moduleOthers": "Autres", "moduleOthersDescription": "Afficher les autres modules", "modulePayment": "Paiement", - "modulePaymentDescription": "Gérer les paiements, les statistiques et les appareils" + "modulePaymentDescription": "Gérer les paiements, les statistiques et les appareils", + "toolInvalidNumber": "Chiffre invalide", + "toolDateRequired": "Date requise" } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 97bc7f9fe3..4b5db31554 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -614,6 +614,18 @@ abstract class AppLocalizations { /// **'Supprimer'** String get adminDelete; + /// No description provided for @adminDeleteAssociationMember. + /// + /// In fr, this message translates to: + /// **'Supprimer le membre ?'** + String get adminDeleteAssociationMember; + + /// No description provided for @adminDeleteAssociationMemberConfirmation. + /// + /// In fr, this message translates to: + /// **'Êtes-vous sûr de vouloir supprimer ce membre ?'** + String get adminDeleteAssociationMemberConfirmation; + /// No description provided for @adminDeleteAssociationMembership. /// /// In fr, this message translates to: @@ -1406,6 +1418,12 @@ abstract class AppLocalizations { /// **'Veuillez entrer un nombre positif'** String get advertPositiveNumber; + /// No description provided for @advertPublishToFeed. + /// + /// In fr, this message translates to: + /// **'Publier dans le feed'** + String get advertPublishToFeed; + /// No description provided for @advertRemovedAnnouncer. /// /// In fr, this message translates to: @@ -4676,6 +4694,12 @@ abstract class AppLocalizations { /// **'Peut voir l\'historique'** String get paiementCanSeeHistory; + /// No description provided for @paiementCantLaunchURL. + /// + /// In fr, this message translates to: + /// **'Impossible d\'ouvrir le lien'** + String get paiementCantLaunchURL; + /// No description provided for @paiementClose. /// /// In fr, this message translates to: @@ -5168,12 +5192,30 @@ abstract class AppLocalizations { /// **'Scanner'** String get paiementScan; + /// No description provided for @paiementScanAlreadyUsedQRCode. + /// + /// In fr, this message translates to: + /// **'QR Code déjà utilisé'** + String get paiementScanAlreadyUsedQRCode; + /// No description provided for @paiementScanCode. /// /// In fr, this message translates to: /// **'Scanner un code'** String get paiementScanCode; + /// No description provided for @paiementScanNoMembership. + /// + /// In fr, this message translates to: + /// **'Pas d\'adhésion'** + String get paiementScanNoMembership; + + /// No description provided for @paiementScanNoMembershipConfirmation. + /// + /// In fr, this message translates to: + /// **'Ce produit n\'est pas disponnible pour les non-adhérents. Confirmer l\'encaissement ?'** + String get paiementScanNoMembershipConfirmation; + /// No description provided for @paiementSeeHistory. /// /// In fr, this message translates to: @@ -5354,6 +5396,12 @@ abstract class AppLocalizations { /// **'Transfert de structure demandé avec succès'** String get paiementTransferStructureSuccess; + /// No description provided for @paiementUnknownDevice. + /// + /// In fr, this message translates to: + /// **'Appareil inconnu'** + String get paiementUnknownDevice; + /// No description provided for @paiementValidUntil. /// /// In fr, this message translates to: @@ -8168,6 +8216,12 @@ abstract class AppLocalizations { /// **'Dépouiller les votes'** String get voteCountVote; + /// No description provided for @voteDelete. + /// + /// In fr, this message translates to: + /// **'Supprimer'** + String get voteDelete; + /// No description provided for @voteDeletedAll. /// /// In fr, this message translates to: @@ -8791,6 +8845,18 @@ abstract class AppLocalizations { /// In fr, this message translates to: /// **'Gérer les paiements, les statistiques et les appareils'** String get modulePaymentDescription; + + /// No description provided for @toolInvalidNumber. + /// + /// In fr, this message translates to: + /// **'Chiffre invalide'** + String get toolInvalidNumber; + + /// No description provided for @toolDateRequired. + /// + /// In fr, this message translates to: + /// **'Date requise'** + String get toolDateRequired; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 064def6d72..816bb904eb 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -127,10 +127,10 @@ class AppLocalizationsEn extends AppLocalizations { @override String get feedPleaseProvideASGExternalLink => - 'Veuillez entrer un lien externe pour le SG'; + 'Please provide a SG external link'; @override - String get feedPleaseProvideASGDate => 'Veuillez entrer une date de SG'; + String get feedPleaseProvideASGDate => 'Please provide a SG date'; @override String feedShotgunIn(String time) { @@ -143,7 +143,7 @@ class AppLocalizationsEn extends AppLocalizations { } @override - String get feedCantOpenLink => 'Impossible d\'ouvrir le lien'; + String get feedCantOpenLink => 'Can\'t open link'; @override String get feedGetReady => 'Get ready!'; @@ -273,6 +273,13 @@ class AppLocalizationsEn extends AppLocalizations { @override String get adminDelete => 'Delete'; + @override + String get adminDeleteAssociationMember => 'Delete member?'; + + @override + String get adminDeleteAssociationMemberConfirmation => + 'Are you sure you want to delete this member?'; + @override String get adminDeleteAssociationMembership => 'Delete membership?'; @@ -687,6 +694,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get advertPositiveNumber => 'Please enter a positive number'; + @override + String get advertPublishToFeed => 'Publish to feed?'; + @override String get advertRemovedAnnouncer => 'Announcer removed'; @@ -2348,6 +2358,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get paiementCanSeeHistory => 'Can view history'; + @override + String get paiementCantLaunchURL => 'Can\'t open link'; + @override String get paiementClose => 'Close'; @@ -2617,9 +2630,19 @@ class AppLocalizationsEn extends AppLocalizations { @override String get paiementScan => 'Scan'; + @override + String get paiementScanAlreadyUsedQRCode => 'QR Code already used'; + @override String get paiementScanCode => 'Scan a code'; + @override + String get paiementScanNoMembership => 'No membership'; + + @override + String get paiementScanNoMembershipConfirmation => + 'This product is not available to non-members. Confirm the payment?'; + @override String get paiementSeeHistory => 'View history'; @@ -2717,6 +2740,9 @@ class AppLocalizationsEn extends AppLocalizations { String get paiementTransferStructureSuccess => 'Structure transfer requested successfully'; + @override + String get paiementUnknownDevice => 'Unknown device'; + @override String get paiementValidUntil => 'Valid until'; @@ -4189,6 +4215,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get voteCountVote => 'Count votes'; + @override + String get voteDelete => 'Delete'; + @override String get voteDeletedAll => 'All deleted'; @@ -4507,4 +4536,10 @@ class AppLocalizationsEn extends AppLocalizations { @override String get modulePaymentDescription => 'Pay and see your transactions'; + + @override + String get toolInvalidNumber => 'Invalid number'; + + @override + String get toolDateRequired => 'Date required'; } diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 612cff22d9..f226aeecf8 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -277,6 +277,13 @@ class AppLocalizationsFr extends AppLocalizations { @override String get adminDelete => 'Supprimer'; + @override + String get adminDeleteAssociationMember => 'Supprimer le membre ?'; + + @override + String get adminDeleteAssociationMemberConfirmation => + 'Êtes-vous sûr de vouloir supprimer ce membre ?'; + @override String get adminDeleteAssociationMembership => 'Supprimer l\'adhésion ?'; @@ -695,6 +702,9 @@ class AppLocalizationsFr extends AppLocalizations { @override String get advertPositiveNumber => 'Veuillez entrer un nombre positif'; + @override + String get advertPublishToFeed => 'Publier dans le feed'; + @override String get advertRemovedAnnouncer => 'Annonceur supprimé'; @@ -2366,6 +2376,9 @@ class AppLocalizationsFr extends AppLocalizations { @override String get paiementCanSeeHistory => 'Peut voir l\'historique'; + @override + String get paiementCantLaunchURL => 'Impossible d\'ouvrir le lien'; + @override String get paiementClose => 'Fermer'; @@ -2645,9 +2658,19 @@ class AppLocalizationsFr extends AppLocalizations { @override String get paiementScan => 'Scanner'; + @override + String get paiementScanAlreadyUsedQRCode => 'QR Code déjà utilisé'; + @override String get paiementScanCode => 'Scanner un code'; + @override + String get paiementScanNoMembership => 'Pas d\'adhésion'; + + @override + String get paiementScanNoMembershipConfirmation => + 'Ce produit n\'est pas disponnible pour les non-adhérents. Confirmer l\'encaissement ?'; + @override String get paiementSeeHistory => 'Voir l\'historique'; @@ -2746,6 +2769,9 @@ class AppLocalizationsFr extends AppLocalizations { String get paiementTransferStructureSuccess => 'Transfert de structure demandé avec succès'; + @override + String get paiementUnknownDevice => 'Appareil inconnu'; + @override String get paiementValidUntil => 'Valide jusqu\'à'; @@ -4235,6 +4261,9 @@ class AppLocalizationsFr extends AppLocalizations { @override String get voteCountVote => 'Dépouiller les votes'; + @override + String get voteDelete => 'Supprimer'; + @override String get voteDeletedAll => 'Tout supprimé'; @@ -4570,4 +4599,10 @@ class AppLocalizationsFr extends AppLocalizations { @override String get modulePaymentDescription => 'Gérer les paiements, les statistiques et les appareils'; + + @override + String get toolInvalidNumber => 'Chiffre invalide'; + + @override + String get toolDateRequired => 'Date requise'; } diff --git a/lib/paiement/ui/components/transaction_card.dart b/lib/paiement/ui/components/transaction_card.dart index 6ff9b4db93..063620e632 100644 --- a/lib/paiement/ui/components/transaction_card.dart +++ b/lib/paiement/ui/components/transaction_card.dart @@ -80,7 +80,7 @@ class TransactionCard extends StatelessWidget { child: AutoSizeText( storeView ? transactionName - : "${transaction.type == HistoryType.refundCredited || transaction.type == HistoryType.refundDebited ? "Remboursement - " : ""}$transactionName", + : "${transaction.type == HistoryType.refundCredited || transaction.type == HistoryType.refundDebited ? "${AppLocalizations.of(context)!.paiementRefund} - " : ""}$transactionName", maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( diff --git a/lib/paiement/ui/pages/devices_page/devices_page.dart b/lib/paiement/ui/pages/devices_page/devices_page.dart index 537909fee6..5e7856c674 100644 --- a/lib/paiement/ui/pages/devices_page/devices_page.dart +++ b/lib/paiement/ui/pages/devices_page/devices_page.dart @@ -47,7 +47,7 @@ class DevicesPage extends HookConsumerWidget { } else if (Theme.of(context).platform == TargetPlatform.iOS) { return deviceInfo.iosInfo.then((info) => info.utsname.machine); } else { - return Future.value("Unknown Device"); + return Future.value(AppLocalizations.of(context)!.paiementUnknownDevice); } } diff --git a/lib/paiement/ui/pages/fund_page/confirm_button.dart b/lib/paiement/ui/pages/fund_page/confirm_button.dart index 48f95fdfd9..d64abe1941 100644 --- a/lib/paiement/ui/pages/fund_page/confirm_button.dart +++ b/lib/paiement/ui/pages/fund_page/confirm_button.dart @@ -56,7 +56,7 @@ class ConfirmFundButton extends ConsumerWidget { Uri.parse(url), mode: LaunchMode.externalApplication, )) { - throw Exception('Could not launch google'); + throw Exception(AppLocalizations.of(context)!.paiementCantLaunchURL); } } diff --git a/lib/paiement/ui/pages/scan_page/scanner.dart b/lib/paiement/ui/pages/scan_page/scanner.dart index a9711fdbac..979204d7f1 100644 --- a/lib/paiement/ui/pages/scan_page/scanner.dart +++ b/lib/paiement/ui/pages/scan_page/scanner.dart @@ -5,6 +5,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:mobile_scanner/mobile_scanner.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/paiement/providers/barcode_provider.dart'; import 'package:titan/paiement/providers/bypass_provider.dart'; import 'package:titan/paiement/providers/last_time_scanned.dart'; @@ -43,9 +44,9 @@ class ScannerState extends ConsumerState with WidgetsBindingObserver { context: context, builder: (context) { return CustomDialogBox( - title: "Pas d'adhésion", + title: AppLocalizations.of(context)!.paiementScanNoMembership, descriptions: - "Ce produit n'est pas disponnible pour les non-adhérents. Confirmer l'encaissement ?", + AppLocalizations.of(context)!.paiementScanNoMembershipConfirmation, onYes: () async { tokenExpireWrapper(ref, () async { onYes.call(); @@ -87,7 +88,8 @@ class ScannerState extends ConsumerState with WidgetsBindingObserver { showWithoutMembershipDialog(() async { final value = await scanNotifier.scan(store.id, data, bypass: true); if (value == null) { - displayToastWithContext(TypeMsg.error, "QR Code déjà utilisé"); + displayToastWithContext(TypeMsg.error, AppLocalizations.of(context)!.paiementScanAlreadyUsedQRCode, + ); barcodeNotifier.clearBarcode(); ongoingTransactionNotifier.clearOngoingTransaction(); return; @@ -99,7 +101,9 @@ class ScannerState extends ConsumerState with WidgetsBindingObserver { } final value = await scanNotifier.scan(store.id, data); if (value == null) { - displayToastWithContext(TypeMsg.error, "QR Code déjà utilisé"); + displayToastWithContext(TypeMsg.error, + AppLocalizations.of(context)!.paiementScanAlreadyUsedQRCode, + ); barcodeNotifier.clearBarcode(); ongoingTransactionNotifier.clearOngoingTransaction(); return; @@ -116,14 +120,14 @@ class ScannerState extends ConsumerState with WidgetsBindingObserver { await showDialog( context: context, builder: (context) => CustomDialogBox( - title: 'Permission caméra requise', + title: AppLocalizations.of(context)!.paiementCameraPermissionRequired, descriptions: - 'Pour scanner des QR codes, l\'application a besoin d\'accéder à votre caméra. Veuillez accorder cette permission dans les paramètres de votre appareil.', + AppLocalizations.of(context)!.paiementCameraPerssionRequiredDescription, onYes: () async { Navigator.of(context).pop(); await openAppSettings(); }, - yesText: 'Paramètres', + yesText: AppLocalizations.of(context)!.paiementSettings, ), ); } diff --git a/lib/phonebook/ui/pages/admin_page/admin_page.dart b/lib/phonebook/ui/pages/admin_page/admin_page.dart index 0e8a0b6a49..dc237941da 100644 --- a/lib/phonebook/ui/pages/admin_page/admin_page.dart +++ b/lib/phonebook/ui/pages/admin_page/admin_page.dart @@ -58,7 +58,7 @@ class AdminPage extends HookConsumerWidget { Row( children: [ Text( - "Associations", + AppLocalizations.of(context)!.phonebookAssociations, style: TextStyle( fontSize: 24, fontWeight: FontWeight.bold, diff --git a/lib/tools/ui/styleguide/searchbar.dart b/lib/tools/ui/styleguide/searchbar.dart index 63aa3e0bb2..bf77304477 100644 --- a/lib/tools/ui/styleguide/searchbar.dart +++ b/lib/tools/ui/styleguide/searchbar.dart @@ -1,16 +1,17 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:heroicons/heroicons.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/tools/constants.dart'; class CustomSearchBar extends HookWidget { - final String hintText; + final String? hintText; final Function()? onFilter; final Function(String) onSearch; final bool autofocus; const CustomSearchBar({ super.key, - this.hintText = 'Rechercher', + this.hintText, this.onFilter, required this.onSearch, this.autofocus = false, @@ -64,7 +65,7 @@ class CustomSearchBar extends HookWidget { style: TextStyle(color: ColorConstants.tertiary, fontSize: 16), cursorColor: ColorConstants.tertiary, decoration: InputDecoration( - hintText: hintText, + hintText: hintText ?? AppLocalizations.of(context)!.phonebookPhonebookSearch, hintStyle: TextStyle( color: ColorConstants.secondary, fontSize: 16, diff --git a/lib/tools/ui/styleguide/text_entry.dart b/lib/tools/ui/styleguide/text_entry.dart index ecf3ac216f..fc52b7e906 100644 --- a/lib/tools/ui/styleguide/text_entry.dart +++ b/lib/tools/ui/styleguide/text_entry.dart @@ -103,14 +103,14 @@ class TextEntry extends StatelessWidget { if (isInt) { final intValue = int.tryParse(value); if (intValue == null || (intValue < 0 && !isNegative)) { - return "Invalid number"; + return localizeWithContext.toolInvalidNumber; } } if (isDouble) { final doubleValue = double.tryParse(value.replaceAll(',', '.')); if (doubleValue == null || (doubleValue < 0 && !isNegative)) { - return "Invalid number"; + return localizeWithContext.toolInvalidNumber; } } diff --git a/lib/tools/ui/widgets/custom_dialog_box.dart b/lib/tools/ui/widgets/custom_dialog_box.dart index 3c301a6c85..36e8166914 100644 --- a/lib/tools/ui/widgets/custom_dialog_box.dart +++ b/lib/tools/ui/widgets/custom_dialog_box.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; class Consts { @@ -113,7 +114,7 @@ class CustomDialogBox extends StatelessWidget { ), child: Center( child: Text( - noText ?? "Annuler", + noText ?? AppLocalizations.of(context)!.globalCancel, style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, @@ -148,7 +149,7 @@ class CustomDialogBox extends StatelessWidget { ), child: Center( child: Text( - yesText ?? "Confirmer", + yesText ?? AppLocalizations.of(context)!.globalConfirm, style: TextStyle(fontWeight: FontWeight.bold), ), ), diff --git a/lib/tools/ui/widgets/date_entry.dart b/lib/tools/ui/widgets/date_entry.dart index 2eb4eb6fd9..0c2c0b5881 100644 --- a/lib/tools/ui/widgets/date_entry.dart +++ b/lib/tools/ui/widgets/date_entry.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/tools/ui/widgets/text_entry.dart'; class DateEntry extends StatelessWidget { @@ -29,7 +30,7 @@ class DateEntry extends StatelessWidget { child: TextEntry( label: label, controller: controller, - noValueError: "Date is required", + noValueError: AppLocalizations.of(context)!.toolDateRequired, enabled: enabled, color: color, enabledColor: enabledColor, diff --git a/lib/tools/ui/widgets/image_picker_on_tap.dart b/lib/tools/ui/widgets/image_picker_on_tap.dart index 23d65ad679..0e95adaa7a 100644 --- a/lib/tools/ui/widgets/image_picker_on_tap.dart +++ b/lib/tools/ui/widgets/image_picker_on_tap.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:image_picker/image_picker.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; @@ -37,7 +38,7 @@ class ImagePickerOnTap extends StatelessWidget { if (size > maxHyperionFileSize) { displayToastWithContext( TypeMsg.error, - "Image size exceeds the maximum limit of 4Mio. Please select a smaller image.", + AppLocalizations.of(context)!.othersImageSizeTooBig, ); } else { if (kIsWeb) { diff --git a/lib/tools/ui/widgets/text_entry.dart b/lib/tools/ui/widgets/text_entry.dart index 4a1f47e3d3..790a72bc33 100644 --- a/lib/tools/ui/widgets/text_entry.dart +++ b/lib/tools/ui/widgets/text_entry.dart @@ -97,14 +97,14 @@ class TextEntry extends StatelessWidget { if (isInt) { final intValue = int.tryParse(value); if (intValue == null || (intValue < 0 && !isNegative)) { - return "Invalid number"; + return AppLocalizations.of(context)!.toolInvalidNumber; } } if (isDouble) { final doubleValue = double.tryParse(value.replaceAll(',', '.')); if (doubleValue == null || (doubleValue < 0 && !isNegative)) { - return "Invalid number"; + return AppLocalizations.of(context)!.toolInvalidNumber; } } diff --git a/lib/vote/ui/components/member_card.dart b/lib/vote/ui/components/member_card.dart index 39f21b4af2..e52851e272 100644 --- a/lib/vote/ui/components/member_card.dart +++ b/lib/vote/ui/components/member_card.dart @@ -44,7 +44,7 @@ class MemberCard extends ConsumerWidget { onPressed: onEdit, ), const SizedBox(height: 20), - Button.danger(text: "Supprimer", onPressed: onDelete), + Button.danger(text: AppLocalizations.of(context)!.voteDelete, onPressed: onDelete), ], ), ), diff --git a/lib/vote/ui/pages/admin_page/admin_page.dart b/lib/vote/ui/pages/admin_page/admin_page.dart index f2de57490a..8bce2d5529 100644 --- a/lib/vote/ui/pages/admin_page/admin_page.dart +++ b/lib/vote/ui/pages/admin_page/admin_page.dart @@ -92,7 +92,7 @@ class AdminPage extends HookConsumerWidget { Padding( padding: const EdgeInsets.symmetric(horizontal: 20.0), child: Text( - "Administration", + AppLocalizations.of(context)!.adminAdministration, style: TextStyle( fontSize: 24, fontWeight: FontWeight.bold, @@ -102,7 +102,7 @@ class AdminPage extends HookConsumerWidget { ), const SizedBox(height: 20), AlignLeftText( - "Association", + AppLocalizations.of(context)!.feedAssociation, padding: const EdgeInsets.symmetric(horizontal: 20.0), color: ColorConstants.tertiary, ), diff --git a/lib/vote/ui/pages/admin_page/contender_card.dart b/lib/vote/ui/pages/admin_page/contender_card.dart index 79e267a6e6..f6cc1d79cc 100644 --- a/lib/vote/ui/pages/admin_page/contender_card.dart +++ b/lib/vote/ui/pages/admin_page/contender_card.dart @@ -48,7 +48,7 @@ class ContenderCard extends HookConsumerWidget { onPressed: onEdit, ), const SizedBox(height: 20), - Button.danger(text: "Supprimer", onPressed: onDelete), + Button.danger(text: AppLocalizations.of(context)!.voteDelete, onPressed: onDelete), ], ), ), diff --git a/lib/vote/ui/pages/admin_page/vote_count.dart b/lib/vote/ui/pages/admin_page/vote_count.dart index e089f1a43e..c7ba2bb469 100644 --- a/lib/vote/ui/pages/admin_page/vote_count.dart +++ b/lib/vote/ui/pages/admin_page/vote_count.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/tools/ui/builders/auto_loader_child.dart'; import 'package:titan/vote/providers/section_vote_count_provide.dart'; import 'package:titan/vote/providers/sections_provider.dart'; @@ -28,7 +29,7 @@ class VoteCount extends HookConsumerWidget { padding: const EdgeInsets.symmetric(horizontal: 30.0, vertical: 50), child: Center( child: Text( - '$data votes', + '$data ${AppLocalizations.of(context)!.voteVotes}', style: const TextStyle( color: Colors.white, fontSize: 20, diff --git a/lib/vote/ui/pages/contender_pages/add_edit_contender.dart b/lib/vote/ui/pages/contender_pages/add_edit_contender.dart index 47c9beeb5b..ac47de26a1 100644 --- a/lib/vote/ui/pages/contender_pages/add_edit_contender.dart +++ b/lib/vote/ui/pages/contender_pages/add_edit_contender.dart @@ -173,7 +173,11 @@ class AddEditContenderPage extends HookConsumerWidget { ContenderMember(), const SizedBox(height: 10), members.isEmpty - ? const Center(child: Text('No members added yet.')) + ? Center( + child: Text( + AppLocalizations.of(context)!.adminNoMember, + ), + ) : Padding( padding: const EdgeInsets.symmetric(horizontal: 20.0), child: Column( diff --git a/lib/vote/ui/pages/main_page/main_page.dart b/lib/vote/ui/pages/main_page/main_page.dart index 63aaf8b384..8ac787b965 100644 --- a/lib/vote/ui/pages/main_page/main_page.dart +++ b/lib/vote/ui/pages/main_page/main_page.dart @@ -68,7 +68,7 @@ class VoteMainPage extends HookConsumerWidget { children: [ if (isAdmin) Text( - "Vote", + AppLocalizations.of(context)!.voteVote, style: TextStyle( fontSize: 24, fontWeight: FontWeight.bold, @@ -145,7 +145,7 @@ class VoteMainPage extends HookConsumerWidget { children: [ const SizedBox(height: 20), Text( - "Vote", + AppLocalizations.of(context)!.voteVote, style: TextStyle( fontSize: 24, fontWeight: FontWeight.bold, From 717cecf6b355053f5898a9c89d16926e56788fd0 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 24 Aug 2025 12:18:07 +0200 Subject: [PATCH 367/473] fix: linter --- ...ciation_membership_member_editable_card.dart | 10 +++++----- .../ui/pages/devices_page/devices_page.dart | 4 +++- lib/paiement/ui/pages/scan_page/scanner.dart | 17 +++++++++++------ lib/tools/ui/styleguide/searchbar.dart | 4 +++- lib/tools/ui/widgets/custom_dialog_box.dart | 8 ++++++-- lib/vote/ui/components/member_card.dart | 5 ++++- .../ui/pages/admin_page/contender_card.dart | 5 ++++- 7 files changed, 36 insertions(+), 17 deletions(-) diff --git a/lib/admin/ui/pages/membership/association_membership_detail_page/association_membership_member_editable_card.dart b/lib/admin/ui/pages/membership/association_membership_detail_page/association_membership_member_editable_card.dart index ca5540fc47..00e2855864 100644 --- a/lib/admin/ui/pages/membership/association_membership_detail_page/association_membership_member_editable_card.dart +++ b/lib/admin/ui/pages/membership/association_membership_detail_page/association_membership_member_editable_card.dart @@ -29,9 +29,7 @@ class MemberEditableCard extends HookConsumerWidget { userAssociationMembershipProvider.notifier, ); - final localization = AppLocalizations.of( - context, - )!; + final localization = AppLocalizations.of(context)!; void displayToastWithContext(TypeMsg type, String msg) { displayToast(context, type, msg); @@ -111,8 +109,10 @@ class MemberEditableCard extends HookConsumerWidget { descriptions: localization.adminDeleteAssociationMemberConfirmation, onYes: () async { - final deletedMemberMsg = localization.phonebookDeletedMember; - final deleteMemberErrorMsg = localization.phonebookDeletingError; + final deletedMemberMsg = + localization.phonebookDeletedMember; + final deleteMemberErrorMsg = + localization.phonebookDeletingError; await tokenExpireWrapper(ref, () async { final result = await associationMembershipMemberListNotifier diff --git a/lib/paiement/ui/pages/devices_page/devices_page.dart b/lib/paiement/ui/pages/devices_page/devices_page.dart index 5e7856c674..c34c6e1b07 100644 --- a/lib/paiement/ui/pages/devices_page/devices_page.dart +++ b/lib/paiement/ui/pages/devices_page/devices_page.dart @@ -47,7 +47,9 @@ class DevicesPage extends HookConsumerWidget { } else if (Theme.of(context).platform == TargetPlatform.iOS) { return deviceInfo.iosInfo.then((info) => info.utsname.machine); } else { - return Future.value(AppLocalizations.of(context)!.paiementUnknownDevice); + return Future.value( + AppLocalizations.of(context)!.paiementUnknownDevice, + ); } } diff --git a/lib/paiement/ui/pages/scan_page/scanner.dart b/lib/paiement/ui/pages/scan_page/scanner.dart index 979204d7f1..81cb1fef31 100644 --- a/lib/paiement/ui/pages/scan_page/scanner.dart +++ b/lib/paiement/ui/pages/scan_page/scanner.dart @@ -45,8 +45,9 @@ class ScannerState extends ConsumerState with WidgetsBindingObserver { builder: (context) { return CustomDialogBox( title: AppLocalizations.of(context)!.paiementScanNoMembership, - descriptions: - AppLocalizations.of(context)!.paiementScanNoMembershipConfirmation, + descriptions: AppLocalizations.of( + context, + )!.paiementScanNoMembershipConfirmation, onYes: () async { tokenExpireWrapper(ref, () async { onYes.call(); @@ -88,7 +89,9 @@ class ScannerState extends ConsumerState with WidgetsBindingObserver { showWithoutMembershipDialog(() async { final value = await scanNotifier.scan(store.id, data, bypass: true); if (value == null) { - displayToastWithContext(TypeMsg.error, AppLocalizations.of(context)!.paiementScanAlreadyUsedQRCode, + displayToastWithContext( + TypeMsg.error, + AppLocalizations.of(context)!.paiementScanAlreadyUsedQRCode, ); barcodeNotifier.clearBarcode(); ongoingTransactionNotifier.clearOngoingTransaction(); @@ -101,7 +104,8 @@ class ScannerState extends ConsumerState with WidgetsBindingObserver { } final value = await scanNotifier.scan(store.id, data); if (value == null) { - displayToastWithContext(TypeMsg.error, + displayToastWithContext( + TypeMsg.error, AppLocalizations.of(context)!.paiementScanAlreadyUsedQRCode, ); barcodeNotifier.clearBarcode(); @@ -121,8 +125,9 @@ class ScannerState extends ConsumerState with WidgetsBindingObserver { context: context, builder: (context) => CustomDialogBox( title: AppLocalizations.of(context)!.paiementCameraPermissionRequired, - descriptions: - AppLocalizations.of(context)!.paiementCameraPerssionRequiredDescription, + descriptions: AppLocalizations.of( + context, + )!.paiementCameraPerssionRequiredDescription, onYes: () async { Navigator.of(context).pop(); await openAppSettings(); diff --git a/lib/tools/ui/styleguide/searchbar.dart b/lib/tools/ui/styleguide/searchbar.dart index bf77304477..9e6c56eb7f 100644 --- a/lib/tools/ui/styleguide/searchbar.dart +++ b/lib/tools/ui/styleguide/searchbar.dart @@ -65,7 +65,9 @@ class CustomSearchBar extends HookWidget { style: TextStyle(color: ColorConstants.tertiary, fontSize: 16), cursorColor: ColorConstants.tertiary, decoration: InputDecoration( - hintText: hintText ?? AppLocalizations.of(context)!.phonebookPhonebookSearch, + hintText: + hintText ?? + AppLocalizations.of(context)!.phonebookPhonebookSearch, hintStyle: TextStyle( color: ColorConstants.secondary, fontSize: 16, diff --git a/lib/tools/ui/widgets/custom_dialog_box.dart b/lib/tools/ui/widgets/custom_dialog_box.dart index 36e8166914..aa7cd17a7e 100644 --- a/lib/tools/ui/widgets/custom_dialog_box.dart +++ b/lib/tools/ui/widgets/custom_dialog_box.dart @@ -114,7 +114,10 @@ class CustomDialogBox extends StatelessWidget { ), child: Center( child: Text( - noText ?? AppLocalizations.of(context)!.globalCancel, + noText ?? + AppLocalizations.of( + context, + )!.globalCancel, style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, @@ -149,7 +152,8 @@ class CustomDialogBox extends StatelessWidget { ), child: Center( child: Text( - yesText ?? AppLocalizations.of(context)!.globalConfirm, + yesText ?? + AppLocalizations.of(context)!.globalConfirm, style: TextStyle(fontWeight: FontWeight.bold), ), ), diff --git a/lib/vote/ui/components/member_card.dart b/lib/vote/ui/components/member_card.dart index e52851e272..de7ae76e56 100644 --- a/lib/vote/ui/components/member_card.dart +++ b/lib/vote/ui/components/member_card.dart @@ -44,7 +44,10 @@ class MemberCard extends ConsumerWidget { onPressed: onEdit, ), const SizedBox(height: 20), - Button.danger(text: AppLocalizations.of(context)!.voteDelete, onPressed: onDelete), + Button.danger( + text: AppLocalizations.of(context)!.voteDelete, + onPressed: onDelete, + ), ], ), ), diff --git a/lib/vote/ui/pages/admin_page/contender_card.dart b/lib/vote/ui/pages/admin_page/contender_card.dart index f6cc1d79cc..aabd22250f 100644 --- a/lib/vote/ui/pages/admin_page/contender_card.dart +++ b/lib/vote/ui/pages/admin_page/contender_card.dart @@ -48,7 +48,10 @@ class ContenderCard extends HookConsumerWidget { onPressed: onEdit, ), const SizedBox(height: 20), - Button.danger(text: AppLocalizations.of(context)!.voteDelete, onPressed: onDelete), + Button.danger( + text: AppLocalizations.of(context)!.voteDelete, + onPressed: onDelete, + ), ], ), ), From 7db2edb12412d237665d54f4f672bef1056ca1d9 Mon Sep 17 00:00:00 2001 From: Maxime Roucher Date: Sun, 24 Aug 2025 12:57:08 +0200 Subject: [PATCH 368/473] feat: new toast (#56) * feat: new toast * fix: clearing faulty test * fix: linter * fix: linter --- .../ui/components/groupement_bar.dart | 13 +- .../groupement_add_edit_page.dart | 23 +- .../association_add_edit_page.dart | 46 ++- lib/tools/functions.dart | 108 +++--- pubspec.lock | 32 +- pubspec.yaml | 2 +- test/tools/tools_test.dart | 358 +++++++++--------- 7 files changed, 299 insertions(+), 283 deletions(-) diff --git a/lib/phonebook/ui/components/groupement_bar.dart b/lib/phonebook/ui/components/groupement_bar.dart index de74b937b3..a1f54756d2 100644 --- a/lib/phonebook/ui/components/groupement_bar.dart +++ b/lib/phonebook/ui/components/groupement_bar.dart @@ -9,6 +9,7 @@ import 'package:titan/phonebook/class/association_groupement.dart'; import 'package:titan/phonebook/providers/association_groupement_provider.dart'; import 'package:titan/phonebook/providers/association_groupement_list_provider.dart'; import 'package:titan/phonebook/router.dart'; +import 'package:titan/tools/functions.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; import 'package:titan/tools/ui/styleguide/button.dart'; @@ -37,10 +38,8 @@ class AssociationGroupementBar extends HookConsumerWidget { associationGroupementListProvider.notifier, ); - void showSnackBarWithContext(String message) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text(message))); + void displayToastWithContext(TypeMsg type, String msg) { + displayToast(context, type, msg); } void popWithContext() { @@ -75,12 +74,14 @@ class AssociationGroupementBar extends HookConsumerWidget { .deleteAssociationGroupement(item); if (result && context.mounted) { popWithContext(); - showSnackBarWithContext( + displayToastWithContext( + TypeMsg.msg, localizeWithContext.phonebookGroupementDeleted, ); } if (!result && context.mounted) { - showSnackBarWithContext( + displayToastWithContext( + TypeMsg.error, localizeWithContext.phonebookGroupementDeleteError, ); } diff --git a/lib/phonebook/ui/pages/add_edit_groupement_page/groupement_add_edit_page.dart b/lib/phonebook/ui/pages/add_edit_groupement_page/groupement_add_edit_page.dart index 14d13b32a6..fadafbc7b8 100644 --- a/lib/phonebook/ui/pages/add_edit_groupement_page/groupement_add_edit_page.dart +++ b/lib/phonebook/ui/pages/add_edit_groupement_page/groupement_add_edit_page.dart @@ -9,6 +9,7 @@ import 'package:titan/phonebook/providers/association_groupement_list_provider.d import 'package:titan/phonebook/providers/association_groupement_provider.dart'; import 'package:titan/phonebook/ui/phonebook.dart'; import 'package:titan/tools/constants.dart'; +import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/tools/ui/styleguide/button.dart'; @@ -24,10 +25,9 @@ class AssociationGroupementAddEditPage extends HookConsumerWidget { associationGroupementListProvider.notifier, ); final name = useTextEditingController(text: associationGroupement.name); - void showSnackBarWithContext(String message) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text(message))); + + void displayToastWithContext(TypeMsg type, String msg) { + displayToast(context, type, msg); } AppLocalizations localizeWithContext = AppLocalizations.of(context)!; @@ -64,7 +64,8 @@ class AssociationGroupementAddEditPage extends HookConsumerWidget { : localizeWithContext.phonebookAdd, onPressed: () async { if (name.text.isEmpty) { - showSnackBarWithContext( + displayToastWithContext( + TypeMsg.error, localizeWithContext.phonebookEmptyFieldError, ); return; @@ -79,12 +80,14 @@ class AssociationGroupementAddEditPage extends HookConsumerWidget { ), ); if (value) { - showSnackBarWithContext( + displayToastWithContext( + TypeMsg.msg, localizeWithContext.phonebookAddedAssociation, ); QR.back(); } else { - showSnackBarWithContext( + displayToastWithContext( + TypeMsg.error, localizeWithContext.phonebookUpdatingError, ); } @@ -95,12 +98,14 @@ class AssociationGroupementAddEditPage extends HookConsumerWidget { AssociationGroupement(id: "", name: name.text), ); if (value) { - showSnackBarWithContext( + displayToastWithContext( + TypeMsg.msg, localizeWithContext.phonebookAddedAssociation, ); QR.back(); } else { - showSnackBarWithContext( + displayToastWithContext( + TypeMsg.error, localizeWithContext.phonebookAddingError, ); } diff --git a/lib/phonebook/ui/pages/association_add_edit_page/association_add_edit_page.dart b/lib/phonebook/ui/pages/association_add_edit_page/association_add_edit_page.dart index 55c1d94ade..b14f4b99f7 100644 --- a/lib/phonebook/ui/pages/association_add_edit_page/association_add_edit_page.dart +++ b/lib/phonebook/ui/pages/association_add_edit_page/association_add_edit_page.dart @@ -13,6 +13,7 @@ import 'package:titan/phonebook/ui/components/groupement_bar.dart'; import 'package:titan/phonebook/ui/phonebook.dart'; import 'package:titan/settings/ui/pages/main_page/picture_button.dart'; import 'package:titan/tools/constants.dart'; +import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; @@ -40,10 +41,8 @@ class AssociationAddEditPage extends HookConsumerWidget { final name = useTextEditingController(text: association.name); final description = useTextEditingController(text: association.description); - void showSnackBarWithContext(String message) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text(message))); + void displayToastWithContext(TypeMsg type, String msg) { + displayToast(context, type, msg); } final localizeWithContext = AppLocalizations.of(context)!; @@ -114,18 +113,21 @@ class AssociationAddEditPage extends HookConsumerWidget { ); if (value != null) { if (value) { - showSnackBarWithContext( + displayToastWithContext( + TypeMsg.msg, localizeWithContext .settingsUpdatedProfilePicture, ); } else { - showSnackBarWithContext( + displayToastWithContext( + TypeMsg.error, localizeWithContext .settingsTooHeavyProfilePicture, ); } } else { - showSnackBarWithContext( + displayToastWithContext( + TypeMsg.error, localizeWithContext .settingsErrorProfilePicture, ); @@ -146,18 +148,21 @@ class AssociationAddEditPage extends HookConsumerWidget { ); if (value != null) { if (value) { - showSnackBarWithContext( + displayToastWithContext( + TypeMsg.msg, localizeWithContext .settingsUpdatedProfilePicture, ); } else { - showSnackBarWithContext( + displayToastWithContext( + TypeMsg.error, localizeWithContext .settingsTooHeavyProfilePicture, ); } } else { - showSnackBarWithContext( + displayToastWithContext( + TypeMsg.error, localizeWithContext .settingsErrorProfilePicture, ); @@ -208,13 +213,15 @@ class AssociationAddEditPage extends HookConsumerWidget { child: Button( onPressed: () async { if (!key.currentState!.validate()) { - showSnackBarWithContext( + displayToastWithContext( + TypeMsg.error, localizeWithContext.phonebookEmptyFieldError, ); return; } if (associationGroupement.id == '') { - showSnackBarWithContext( + displayToastWithContext( + TypeMsg.error, localizeWithContext.phonebookEmptyKindError, ); return; @@ -231,7 +238,8 @@ class AssociationAddEditPage extends HookConsumerWidget { ), ); if (value) { - showSnackBarWithContext( + displayToastWithContext( + TypeMsg.msg, localizeWithContext.phonebookAddedAssociation, ); associations.when( @@ -243,14 +251,16 @@ class AssociationAddEditPage extends HookConsumerWidget { PhonebookRouter.addEditAssociation, ); }, - error: (e, s) => showSnackBarWithContext( + error: (e, s) => displayToastWithContext( + TypeMsg.error, localizeWithContext .phonebookErrorAssociationLoading, ), loading: () {}, ); } else { - showSnackBarWithContext( + displayToastWithContext( + TypeMsg.error, localizeWithContext.phonebookAddingError, ); } @@ -264,7 +274,8 @@ class AssociationAddEditPage extends HookConsumerWidget { ), ); if (value) { - showSnackBarWithContext( + displayToastWithContext( + TypeMsg.msg, localizeWithContext.phonebookUpdatedAssociation, ); associationNotifier.setAssociation( @@ -278,7 +289,8 @@ class AssociationAddEditPage extends HookConsumerWidget { .resetAssociationGroupement(); QR.back(); } else { - showSnackBarWithContext( + displayToastWithContext( + TypeMsg.error, localizeWithContext.phonebookUpdatingError, ); } diff --git a/lib/tools/functions.dart b/lib/tools/functions.dart index fa1280a3fe..663adb92f6 100644 --- a/lib/tools/functions.dart +++ b/lib/tools/functions.dart @@ -1,14 +1,12 @@ import 'package:datetime_picker_formfield/datetime_picker_formfield.dart'; -import 'package:flash/flash.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; -import 'package:heroicons/heroicons.dart'; import 'package:intl/intl.dart'; import 'package:titan/tools/constants.dart'; -import 'package:auto_size_text/auto_size_text.dart'; import 'package:titan/tools/plausible/plausible.dart'; import 'package:syncfusion_flutter_calendar/calendar.dart'; +import 'package:toastification/toastification.dart'; enum TypeMsg { msg, error } @@ -33,75 +31,59 @@ void displayToast( String text, { int? duration, }) { - LinearGradient linearGradient; - HeroIcons icon; + String title; + Color primaryColor, textColor; + ToastificationType toastType; switch (type) { case TypeMsg.msg: - linearGradient = const LinearGradient( - colors: [ColorConstants.gradient1, ColorConstants.gradient2], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ); - icon = HeroIcons.check; - duration = duration ?? 1500; + title = "Succès"; + primaryColor = ColorConstants.background; + textColor = ColorConstants.tertiary; + toastType = ToastificationType.success; break; case TypeMsg.error: - linearGradient = const LinearGradient( - colors: [ColorConstants.background2, Colors.black], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ); - icon = HeroIcons.exclamationTriangle; - duration = duration ?? 3000; + title = "Erreur"; + primaryColor = ColorConstants.onMain; + textColor = ColorConstants.background; + toastType = ToastificationType.error; break; } - showFlash( + toastification.show( context: context, - duration: Duration(milliseconds: duration), - builder: (context, controller) { - return FlashBar( - position: FlashPosition.top, - controller: controller, - surfaceTintColor: Colors.transparent, - backgroundColor: Colors.transparent, - shadowColor: Colors.transparent, - margin: const EdgeInsets.only(top: 30, left: 20, right: 20), - content: Container( - alignment: Alignment.topCenter, - decoration: BoxDecoration( - borderRadius: const BorderRadius.all(Radius.circular(15)), - gradient: linearGradient, - ), - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), - height: 50 + text.length / 2, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - width: 40, - alignment: Alignment.center, - child: HeroIcon(icon, color: Colors.white), - ), - const SizedBox(width: 10), - Expanded( - child: Center( - child: AutoSizeText( - text, - overflow: TextOverflow.ellipsis, - style: const TextStyle( - color: Colors.white, - fontSize: 20, - fontWeight: FontWeight.bold, - ), - maxLines: 8, - ), - ), - ), - ], - ), - ), + type: toastType, + style: ToastificationStyle.fillColored, + alignment: Alignment.topCenter, + title: Text( + title, + style: TextStyle( + color: textColor, + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + description: Text( + text, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w400, + color: textColor, + ), + ), + showIcon: false, + primaryColor: primaryColor, + showProgressBar: false, + closeButton: ToastCloseButton(showType: CloseButtonShowType.none), + autoCloseDuration: const Duration(milliseconds: 2500), + animationDuration: const Duration(milliseconds: 400), + animationBuilder: (context, animation, alignment, child) { + return SlideTransition( + position: Tween( + begin: const Offset(0, -1), + end: Offset.zero, + ).animate(animation), + child: Opacity(opacity: animation.value, child: child), ); }, ); diff --git a/pubspec.lock b/pubspec.lock index 062fe329e5..900e83a8f8 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -433,14 +433,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" - flash: - dependency: "direct main" - description: - name: flash - sha256: f8b6ed7410df7581b35824967427b79b4e66d59c8f24012ed10f26667a4d44b1 - url: "https://pub.dev" - source: hosted - version: "3.1.1" flutter: dependency: "direct main" description: flutter @@ -693,6 +685,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.2" + iconsax_flutter: + dependency: transitive + description: + name: iconsax_flutter + sha256: d14b4cec8586025ac15276bdd40f6eea308cb85748135965bb6255f14beb2564 + url: "https://pub.dev" + source: hosted + version: "1.0.1" image: dependency: "direct main" description: @@ -1077,6 +1077,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.0" + pausable_timer: + dependency: transitive + description: + name: pausable_timer + sha256: "6ef1a95441ec3439de6fb63f39a011b67e693198e7dae14e20675c3c00e86074" + url: "https://pub.dev" + source: hosted + version: "3.1.0+3" pdfx: dependency: "direct main" description: @@ -1426,6 +1434,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.10.0" + toastification: + dependency: "direct main" + description: + name: toastification + sha256: "69db2bff425b484007409650d8bcd5ed1ce2e9666293ece74dcd917dacf23112" + url: "https://pub.dev" + source: hosted + version: "3.0.3" tuple: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 66a53bb1b7..d6650bc407 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -24,7 +24,6 @@ dependencies: firebase_core: ^3.9.0 firebase_messaging: ^15.2.2 fl_chart: ^1.0.0 - flash: ^3.1.1 flutter: sdk: flutter flutter_appauth: ^9.0.0 @@ -68,6 +67,7 @@ dependencies: timeago: ^3.7.0 timeago_flutter: ^3.7.0 timezone: ^0.10.0 + toastification: ^3.0.3 tuple: ^2.0.0 universal_html: ^2.0.8 url_launcher: ^6.2.5 diff --git a/test/tools/tools_test.dart b/test/tools/tools_test.dart index fba5f5d815..0804ac087c 100644 --- a/test/tools/tools_test.dart +++ b/test/tools/tools_test.dart @@ -1,4 +1,4 @@ -import 'package:flutter/material.dart'; +// import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:titan/tools/exception.dart'; import 'package:titan/tools/functions.dart'; @@ -142,182 +142,182 @@ void main() { }); }); - group('displayToast', () { - testWidgets( - 'displays a toast message with the correct duration when the type is "msg"', - (WidgetTester tester) async { - // Arrange - const type = TypeMsg.msg; - const text = 'Success!'; - final scaffoldKey = GlobalKey(); - - // Act - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - key: scaffoldKey, - body: Builder( - builder: (context) => ElevatedButton( - onPressed: () => displayToast(context, type, text), - child: const Text('Show Toast'), - ), - ), - ), - ), - ); - await tester.tap(find.byType(ElevatedButton)); - await tester.pump(const Duration(milliseconds: 500)); - - // Assert - expect( - find.text(text), - findsOneWidget, - ); // Check that the toast message is still visible - await tester.pump(const Duration(milliseconds: 2000)); - expect( - find.text(text), - findsNothing, - ); // Check that the toast message has disappeared - }, - ); - - testWidgets( - 'displays a toast message with the correct duration when the type is "error"', - (WidgetTester tester) async { - // Arrange - const type = TypeMsg.error; - const text = 'Error!'; - final scaffoldKey = GlobalKey(); - - // Act - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - key: scaffoldKey, - body: Builder( - builder: (context) => ElevatedButton( - onPressed: () => displayToast(context, type, text), - child: const Text('Show Toast'), - ), - ), - ), - ), - ); - await tester.tap(find.byType(ElevatedButton)); - await tester.pump(const Duration(milliseconds: 500)); - - // Assert - expect( - find.text(text), - findsOneWidget, - ); // Check that the toast message is still visible - await tester.pump(const Duration(milliseconds: 3000)); - expect( - find.text(text), - findsNothing, - ); // Check that the toast message has disappeared - }, - ); - - // testWidgets( - // 'displays a toast message with the correct background color when the type is "msg"', - // (WidgetTester tester) async { - // // Arrange - // const type = TypeMsg.msg; - // const text = 'Success!'; - // final scaffoldKey = GlobalKey(); - - // // Act - // await tester.pumpWidget(MaterialApp( - // home: Scaffold( - // key: scaffoldKey, - // body: Builder( - // builder: (context) => ElevatedButton( - // onPressed: () => displayToast(context, type, text), - // child: const Text('Show Toast'), - // ), - // ), - // ), - // )); - // await tester.tap(find.byType(ElevatedButton)); - // await tester.pump(const Duration(milliseconds: 500)); - - // // Assert - // final container = - // find.byType(Ink).evaluate().first.widget as Ink; - // final decoration = container.decoration as BoxDecoration; - // expect( - // decoration.gradient!.colors, - // [ - // ColorConstants.gradient1, - // ColorConstants.gradient2 - // ]); // Check that the toast message has the correct background color - // await tester.pump(const Duration(milliseconds: 3000)); - // }); - - // testWidgets( - // 'displays a toast message with the correct background color when the type is "error"', - // (WidgetTester tester) async { - // // Arrange - // const type = TypeMsg.error; - // const text = 'Error!'; - // final scaffoldKey = GlobalKey(); - - // // Act - // await tester.pumpWidget(MaterialApp( - // home: Scaffold( - // key: scaffoldKey, - // body: Builder( - // builder: (context) => ElevatedButton( - // onPressed: () => displayToast(context, type, text), - // child: const Text('Show Toast'), - // ), - // ), - // ), - // )); - // await tester.tap(find.byType(ElevatedButton)); - // await tester.pump(const Duration(milliseconds: 500)); - - // // Assert - // final container = - // find.byType(Ink).evaluate().first.widget as Ink; - // final decoration = container.decoration as BoxDecoration; - // expect( - // decoration.gradient!.colors, - // [ - // ColorConstants.background2, - // Colors.black - // ]); // Check that the toast message has the correct background color - // await tester.pump(const Duration(milliseconds: 3000)); - // }); - - // testWidgets('displays a toast message with the correct font size', - // (WidgetTester tester) async { - // // Arrange - // const type = TypeMsg.msg; - // const text = 'Success!'; - // final scaffoldKey = GlobalKey(); - - // // Act - // await tester.pumpWidget(MaterialApp( - // home: Scaffold( - // key: scaffoldKey, - // body: Builder( - // builder: (context) => ElevatedButton( - // onPressed: () => displayToast(context, type, text), - // child: const Text('Show Toast'), - // ), - // ), - // ), - // )); - // await tester.tap(find.byType(ElevatedButton)); - // await tester.pump(const Duration(milliseconds: 500)); - - // // Assert - // final textWidget = find.text(text).evaluate().first.widget as Text; - // expect(textWidget.style!.fontSize, - // 20.0); // Check that the toast message has the correct font size - // await tester.pump(const Duration(milliseconds: 3000)); - // }); - }); + // group('displayToast', () { + // testWidgets( + // 'displays a toast message with the correct duration when the type is "msg"', + // (WidgetTester tester) async { + // // Arrange + // const type = TypeMsg.msg; + // const text = 'Success!'; + // final scaffoldKey = GlobalKey(); + + // // Act + // await tester.pumpWidget( + // MaterialApp( + // home: Scaffold( + // key: scaffoldKey, + // body: Builder( + // builder: (context) => ElevatedButton( + // onPressed: () => displayToast(context, type, text), + // child: const Text('Show Toast'), + // ), + // ), + // ), + // ), + // ); + // await tester.tap(find.byType(ElevatedButton)); + // await tester.pump(const Duration(milliseconds: 500)); + + // // Assert + // expect( + // find.text(text), + // findsOneWidget, + // ); // Check that the toast message is still visible + // await tester.pump(const Duration(milliseconds: 2000)); + // expect( + // find.text(text), + // findsNothing, + // ); // Check that the toast message has disappeared + // }, + // ); + + // testWidgets( + // 'displays a toast message with the correct duration when the type is "error"', + // (WidgetTester tester) async { + // // Arrange + // const type = TypeMsg.error; + // const text = 'Error!'; + // final scaffoldKey = GlobalKey(); + + // // Act + // await tester.pumpWidget( + // MaterialApp( + // home: Scaffold( + // key: scaffoldKey, + // body: Builder( + // builder: (context) => ElevatedButton( + // onPressed: () => displayToast(context, type, text), + // child: const Text('Show Toast'), + // ), + // ), + // ), + // ), + // ); + // await tester.tap(find.byType(ElevatedButton)); + // await tester.pump(const Duration(milliseconds: 500)); + + // // Assert + // expect( + // find.text(text), + // findsOneWidget, + // ); // Check that the toast message is still visible + // await tester.pump(const Duration(milliseconds: 3000)); + // expect( + // find.text(text), + // findsNothing, + // ); // Check that the toast message has disappeared + // }, + // ); + + // testWidgets( + // 'displays a toast message with the correct background color when the type is "msg"', + // (WidgetTester tester) async { + // // Arrange + // const type = TypeMsg.msg; + // const text = 'Success!'; + // final scaffoldKey = GlobalKey(); + + // // Act + // await tester.pumpWidget(MaterialApp( + // home: Scaffold( + // key: scaffoldKey, + // body: Builder( + // builder: (context) => ElevatedButton( + // onPressed: () => displayToast(context, type, text), + // child: const Text('Show Toast'), + // ), + // ), + // ), + // )); + // await tester.tap(find.byType(ElevatedButton)); + // await tester.pump(const Duration(milliseconds: 500)); + + // // Assert + // final container = + // find.byType(Ink).evaluate().first.widget as Ink; + // final decoration = container.decoration as BoxDecoration; + // expect( + // decoration.gradient!.colors, + // [ + // ColorConstants.gradient1, + // ColorConstants.gradient2 + // ]); // Check that the toast message has the correct background color + // await tester.pump(const Duration(milliseconds: 3000)); + // }); + + // testWidgets( + // 'displays a toast message with the correct background color when the type is "error"', + // (WidgetTester tester) async { + // // Arrange + // const type = TypeMsg.error; + // const text = 'Error!'; + // final scaffoldKey = GlobalKey(); + + // // Act + // await tester.pumpWidget(MaterialApp( + // home: Scaffold( + // key: scaffoldKey, + // body: Builder( + // builder: (context) => ElevatedButton( + // onPressed: () => displayToast(context, type, text), + // child: const Text('Show Toast'), + // ), + // ), + // ), + // )); + // await tester.tap(find.byType(ElevatedButton)); + // await tester.pump(const Duration(milliseconds: 500)); + + // // Assert + // final container = + // find.byType(Ink).evaluate().first.widget as Ink; + // final decoration = container.decoration as BoxDecoration; + // expect( + // decoration.gradient!.colors, + // [ + // ColorConstants.background2, + // Colors.black + // ]); // Check that the toast message has the correct background color + // await tester.pump(const Duration(milliseconds: 3000)); + // }); + + // testWidgets('displays a toast message with the correct font size', + // (WidgetTester tester) async { + // // Arrange + // const type = TypeMsg.msg; + // const text = 'Success!'; + // final scaffoldKey = GlobalKey(); + + // // Act + // await tester.pumpWidget(MaterialApp( + // home: Scaffold( + // key: scaffoldKey, + // body: Builder( + // builder: (context) => ElevatedButton( + // onPressed: () => displayToast(context, type, text), + // child: const Text('Show Toast'), + // ), + // ), + // ), + // )); + // await tester.tap(find.byType(ElevatedButton)); + // await tester.pump(const Duration(milliseconds: 500)); + + // // Assert + // final textWidget = find.text(text).evaluate().first.widget as Text; + // expect(textWidget.style!.fontSize, + // 20.0); // Check that the toast message has the correct font size + // await tester.pump(const Duration(milliseconds: 3000)); + // }); + // }); } From cb3c991fb58b19d5291910838823dc2da0d8ad6e Mon Sep 17 00:00:00 2001 From: Thonyk Date: Sun, 24 Aug 2025 19:52:14 +0200 Subject: [PATCH 369/473] feat: add emlyon login assets --- assets/emlyon/back.webp | Bin 0 -> 674912 bytes assets/emlyon/login.webp | Bin 0 -> 28806 bytes assets/images/login.svg | 1 - assets/images/login.webp | Bin 0 -> 28926 bytes assets/images/proximapp.png | Bin 0 -> 396425 bytes lib/l10n/app_en.arb | 2 +- lib/l10n/app_fr.arb | 2 +- lib/l10n/app_localizations.dart | 12 ++++++------ lib/l10n/app_localizations_en.dart | 7 +++---- lib/l10n/app_localizations_fr.dart | 7 +++---- lib/login/ui/web/left_panel.dart | 7 +------ lib/login/ui/web/right_panel.dart | 11 ++++++++--- pubspec.yaml | 4 ++-- 13 files changed, 25 insertions(+), 28 deletions(-) create mode 100644 assets/emlyon/back.webp create mode 100644 assets/emlyon/login.webp delete mode 100644 assets/images/login.svg create mode 100644 assets/images/login.webp create mode 100644 assets/images/proximapp.png diff --git a/assets/emlyon/back.webp b/assets/emlyon/back.webp new file mode 100644 index 0000000000000000000000000000000000000000..f5f2c9f43cf5bcdd40d90e1b1621c1fdc84c91d5 GIT binary patch literal 674912 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

Kq~B)`Ez9uu&-I?zQ_%Dm9@T|LXlQMDb?={~Z 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ðut3N{c4T_eQZg_}ga2MWzn3uWf<=5MldZ~PPd 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

VU9>y}F61!#Ygn2iCAl(~i6h)3AL~1c5POSahBz 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 z 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


cs3wnfGUYw6@7V;x4`afHGT zzk;tG-of=c3UFX*Fg5N2XOoWaOlzgK-IXxGOOf4+bIxtgL`&K62Q&m+fugb)HOfEHDMEinr}d#NoPy=#F- z!3l=(whKR_raHCcA3B>^fsoA*M5D$5iSo7safyfZ%nTQsfNV?aerTjH?4)dX$2``5 zDVjk7s=%0z`lD%5(nmHELQ-=x46w;n6ZuGXv%2r7o7Gu7weP=@)@Y6Y`|n?ymk(F` zAmkE3YqSb#Trj?h)DbSZvdJh?@AfkpJiDGrpz6_PH}Djsx3{;q7_pUUhXM5Dps#wgBjTY)7cU#6q@nT< zMmT`UBeKT09}`iQbv+rWSAKd*Prjd8+##eKgpgphvBfJJM-)jL+g0zf%VV*jj)SJj z0dWNqqUWg#h+0Ap>px>q=zw1;7ls_Tr`6Ha>pZI!MJB_+jpoqz6yMatcgWu`?*Z2R zxz%cY-haDaIG?_#Mm!98C^P{FMU^#<3-&&n^>X22)bmw63Hs>ra)LQMrL4p>57XiU zjK|}u-j&)={fCC5e}0zIby=Od~&s*&yar0zL|~a1j%Shqop7V?X<>~jvx1Eqsq}f z>|Q!&W#p2{WYX{m8wP71dr09ThN6-4`kL)P{SGItI9y9^B=^PCTAFJ ze(>1SkZP*=>xX~+{Y~v)Jp_$z+Zqx=M{b!A1ZaU|ImjJH8$r+Sg1O%Z06M%l#e6OY zbj3Oc8giK4OOS=RxZhLL>2$I~Yu~pyT;Z^ZV}et_c}1a_y*6=*s004A`XwOL_%XaK z_QEe4f5LwS(WmxEL*Y7DsJHxT=GV*VI`U=9-~ za2&4nHeW>HF!vt@UhWYxq0|J4vMe{Rm!rmcB<^?7EI5ew)F=Ahh{Fyhr}dUyuRTxi zzv=ySKiTB_j#8YD=I>01~dEItK%WUS8cet%q#M+VPYC>tGwzs}?F zD;#K;^g@u0nC2>Avx+MJ_s@T`TGGg0|M{P%Kfhku*rJSvLEzbK^M)}{uE%H#r<