import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../../../core/widgets/unified_page_layout.dart'; import '../../../../core/widgets/unified_card.dart'; import '../../../../core/theme/app_colors.dart'; import '../../../../core/theme/app_text_styles.dart'; import '../../domain/entities/notification.dart'; import '../../domain/entities/preferences_notification.dart'; import '../bloc/notification_preferences_bloc.dart'; import '../widgets/preference_section_widget.dart'; import '../widgets/silent_mode_config_widget.dart'; /// Page de configuration des préférences de notifications class NotificationPreferencesPage extends StatefulWidget { const NotificationPreferencesPage({super.key}); @override State createState() => _NotificationPreferencesPageState(); } class _NotificationPreferencesPageState extends State with TickerProviderStateMixin { late TabController _tabController; @override void initState() { super.initState(); _tabController = TabController(length: 3, vsync: this); // Chargement des préférences context.read().add( const LoadPreferencesEvent(), ); } @override void dispose() { _tabController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return UnifiedPageLayout( title: 'Préférences de notifications', showBackButton: true, actions: [ PopupMenuButton( icon: const Icon(Icons.more_vert), onSelected: (value) { switch (value) { case 'reset': _showResetDialog(); break; case 'test': _sendTestNotification(); break; case 'export': _exportPreferences(); break; } }, itemBuilder: (context) => [ const PopupMenuItem( value: 'test', child: ListTile( leading: Icon(Icons.send), title: Text('Envoyer un test'), contentPadding: EdgeInsets.zero, ), ), const PopupMenuItem( value: 'export', child: ListTile( leading: Icon(Icons.download), title: Text('Exporter'), contentPadding: EdgeInsets.zero, ), ), const PopupMenuDivider(), const PopupMenuItem( value: 'reset', child: ListTile( leading: Icon(Icons.restore, color: Colors.red), title: Text('Réinitialiser', style: TextStyle(color: Colors.red)), contentPadding: EdgeInsets.zero, ), ), ], ), ], body: BlocBuilder( builder: (context, state) { if (state is PreferencesLoading) { return const Center( child: CircularProgressIndicator(), ); } if (state is PreferencesError) { return Center( child: UnifiedCard( variant: UnifiedCardVariant.outlined, child: Padding( padding: const EdgeInsets.all(24.0), child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon( Icons.error_outline, size: 48, color: AppColors.error, ), const SizedBox(height: 16), Text( 'Erreur de chargement', style: AppTextStyles.titleMedium, ), const SizedBox(height: 8), Text( state.message, style: AppTextStyles.bodyMedium.copyWith( color: AppColors.onSurface.withOpacity(0.7), ), textAlign: TextAlign.center, ), const SizedBox(height: 16), ElevatedButton.icon( onPressed: () { context.read().add( const LoadPreferencesEvent(), ); }, icon: const Icon(Icons.refresh), label: const Text('Réessayer'), ), ], ), ), ), ); } if (state is! PreferencesLoaded) { return const SizedBox.shrink(); } return Column( children: [ // Onglets de navigation Container( margin: const EdgeInsets.all(16.0), decoration: BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.circular(12), border: Border.all(color: AppColors.outline.withOpacity(0.2)), ), child: TabBar( controller: _tabController, indicator: BoxDecoration( color: AppColors.primary, borderRadius: BorderRadius.circular(8), ), indicatorSize: TabBarIndicatorSize.tab, indicatorPadding: const EdgeInsets.all(4), labelColor: AppColors.onPrimary, unselectedLabelColor: AppColors.onSurface, labelStyle: AppTextStyles.bodyMedium.copyWith( fontWeight: FontWeight.w600, ), unselectedLabelStyle: AppTextStyles.bodyMedium, tabs: const [ Tab(text: 'Général'), Tab(text: 'Types'), Tab(text: 'Avancé'), ], ), ), // Contenu des onglets Expanded( child: TabBarView( controller: _tabController, children: [ _buildGeneralTab(state.preferences), _buildTypesTab(state.preferences), _buildAdvancedTab(state.preferences), ], ), ), ], ); }, ), ); } Widget _buildGeneralTab(PreferencesNotificationEntity preferences) { return SingleChildScrollView( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Activation générale PreferenceSectionWidget( title: 'Notifications', subtitle: 'Paramètres généraux des notifications', icon: Icons.notifications, children: [ SwitchListTile( title: const Text('Activer les notifications'), subtitle: const Text('Recevoir toutes les notifications'), value: preferences.notificationsActivees, onChanged: (value) => _updatePreference( preferences.copyWith(notificationsActivees: value), ), ), if (preferences.notificationsActivees) ...[ SwitchListTile( title: const Text('Notifications push'), subtitle: const Text('Recevoir les notifications sur l\'appareil'), value: preferences.pushActivees, onChanged: (value) => _updatePreference( preferences.copyWith(pushActivees: value), ), ), SwitchListTile( title: const Text('Notifications par email'), subtitle: const Text('Recevoir les notifications par email'), value: preferences.emailActivees, onChanged: (value) => _updatePreference( preferences.copyWith(emailActivees: value), ), ), SwitchListTile( title: const Text('Notifications SMS'), subtitle: const Text('Recevoir les notifications par SMS'), value: preferences.smsActivees, onChanged: (value) => _updatePreference( preferences.copyWith(smsActivees: value), ), ), ], ], ), const SizedBox(height: 24), // Mode silencieux PreferenceSectionWidget( title: 'Mode silencieux', subtitle: 'Configurer les périodes de silence', icon: Icons.do_not_disturb, children: [ SilentModeConfigWidget( preferences: preferences, onPreferencesChanged: _updatePreference, ), ], ), const SizedBox(height: 24), // Paramètres visuels et sonores PreferenceSectionWidget( title: 'Apparence et sons', subtitle: 'Personnaliser l\'affichage des notifications', icon: Icons.palette, children: [ SwitchListTile( title: const Text('Vibration'), subtitle: const Text('Faire vibrer l\'appareil'), value: preferences.vibrationActivee, onChanged: (value) => _updatePreference( preferences.copyWith(vibrationActivee: value), ), ), SwitchListTile( title: const Text('Son'), subtitle: const Text('Jouer un son'), value: preferences.sonActive, onChanged: (value) => _updatePreference( preferences.copyWith(sonActive: value), ), ), SwitchListTile( title: const Text('LED'), subtitle: const Text('Allumer la LED de notification'), value: preferences.ledActivee, onChanged: (value) => _updatePreference( preferences.copyWith(ledActivee: value), ), ), SwitchListTile( title: const Text('Aperçu sur écran verrouillé'), subtitle: const Text('Afficher le contenu sur l\'écran verrouillé'), value: preferences.apercuEcranVerrouillage, onChanged: (value) => _updatePreference( preferences.copyWith(apercuEcranVerrouillage: value), ), ), ], ), ], ), ); } Widget _buildTypesTab(PreferencesNotificationEntity preferences) { return SingleChildScrollView( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Choisissez les types de notifications que vous souhaitez recevoir', style: AppTextStyles.bodyMedium.copyWith( color: AppColors.onSurface.withOpacity(0.7), ), ), const SizedBox(height: 16), // Groupement par catégorie ..._buildTypesByCategory(preferences), ], ), ); } Widget _buildAdvancedTab(PreferencesNotificationEntity preferences) { return SingleChildScrollView( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Gestion automatique PreferenceSectionWidget( title: 'Gestion automatique', subtitle: 'Paramètres de gestion automatique des notifications', icon: Icons.auto_mode, children: [ SwitchListTile( title: const Text('Marquage automatique comme lu'), subtitle: const Text('Marquer automatiquement les notifications comme lues'), value: preferences.marquageLectureAutomatique, onChanged: (value) => _updatePreference( preferences.copyWith(marquageLectureAutomatique: value), ), ), if (preferences.marquageLectureAutomatique) ListTile( title: const Text('Délai de marquage'), subtitle: Text('${preferences.delaiMarquageLectureSecondes ?? 5} secondes'), trailing: const Icon(Icons.chevron_right), onTap: () => _showDelayPicker( 'Délai de marquage automatique', preferences.delaiMarquageLectureSecondes ?? 5, (value) => _updatePreference( preferences.copyWith(delaiMarquageLectureSecondes: value), ), ), ), SwitchListTile( title: const Text('Archivage automatique'), subtitle: const Text('Archiver automatiquement les notifications lues'), value: preferences.archivageAutomatique, onChanged: (value) => _updatePreference( preferences.copyWith(archivageAutomatique: value), ), ), if (preferences.archivageAutomatique) ListTile( title: const Text('Délai d\'archivage'), subtitle: Text('${preferences.delaiArchivageHeures} heures'), trailing: const Icon(Icons.chevron_right), onTap: () => _showDelayPicker( 'Délai d\'archivage automatique', preferences.delaiArchivageHeures, (value) => _updatePreference( preferences.copyWith(delaiArchivageHeures: value), ), ), ), ], ), const SizedBox(height: 24), // Limites et regroupement PreferenceSectionWidget( title: 'Limites et regroupement', subtitle: 'Contrôler le nombre et le regroupement des notifications', icon: Icons.group_work, children: [ ListTile( title: const Text('Notifications simultanées maximum'), subtitle: Text('${preferences.maxNotificationsSimultanees} notifications'), trailing: const Icon(Icons.chevron_right), onTap: () => _showNumberPicker( 'Nombre maximum de notifications simultanées', preferences.maxNotificationsSimultanees, 1, 50, (value) => _updatePreference( preferences.copyWith(maxNotificationsSimultanees: value), ), ), ), ListTile( title: const Text('Fréquence de regroupement'), subtitle: Text('${preferences.frequenceRegroupementMinutes} minutes'), trailing: const Icon(Icons.chevron_right), onTap: () => _showNumberPicker( 'Fréquence de regroupement des notifications', preferences.frequenceRegroupementMinutes, 1, 60, (value) => _updatePreference( preferences.copyWith(frequenceRegroupementMinutes: value), ), ), ), ListTile( title: const Text('Durée d\'affichage'), subtitle: Text('${preferences.dureeAffichageSecondes} secondes'), trailing: const Icon(Icons.chevron_right), onTap: () => _showNumberPicker( 'Durée d\'affichage des notifications', preferences.dureeAffichageSecondes, 3, 30, (value) => _updatePreference( preferences.copyWith(dureeAffichageSecondes: value), ), ), ), ], ), const SizedBox(height: 24), // Conservation des données PreferenceSectionWidget( title: 'Conservation des données', subtitle: 'Durée de conservation des notifications', icon: Icons.storage, children: [ ListTile( title: const Text('Durée de conservation'), subtitle: Text('${preferences.dureeConservationJours} jours'), trailing: const Icon(Icons.chevron_right), onTap: () => _showNumberPicker( 'Durée de conservation des notifications', preferences.dureeConservationJours, 7, 365, (value) => _updatePreference( preferences.copyWith(dureeConservationJours: value), ), ), ), SwitchListTile( title: const Text('Affichage de l\'historique'), subtitle: const Text('Conserver l\'historique des notifications'), value: preferences.affichageHistorique, onChanged: (value) => _updatePreference( preferences.copyWith(affichageHistorique: value), ), ), ], ), ], ), ); } List _buildTypesByCategory(PreferencesNotificationEntity preferences) { final typesByCategory = >{}; for (final type in TypeNotification.values) { typesByCategory.putIfAbsent(type.categorie, () => []).add(type); } return typesByCategory.entries.map((entry) { return PreferenceSectionWidget( title: _getCategoryTitle(entry.key), subtitle: _getCategorySubtitle(entry.key), icon: _getCategoryIcon(entry.key), children: entry.value.map((type) { return SwitchListTile( title: Text(type.libelle), subtitle: Text(_getTypeDescription(type)), value: preferences.isTypeActive(type), onChanged: (value) => _toggleNotificationType(type, value), secondary: Icon( _getTypeIconData(type), color: _getTypeColor(type), ), ); }).toList(), ); }).toList(); } String _getCategoryTitle(String category) { switch (category) { case 'evenements': return 'Événements'; case 'cotisations': return 'Cotisations'; case 'solidarite': return 'Solidarité'; case 'membres': return 'Membres'; case 'organisation': return 'Organisation'; case 'messages': return 'Messages'; case 'systeme': return 'Système'; default: return category; } } String _getCategorySubtitle(String category) { switch (category) { case 'evenements': return 'Notifications liées aux événements'; case 'cotisations': return 'Notifications de paiement et cotisations'; case 'solidarite': return 'Demandes d\'aide et solidarité'; case 'membres': return 'Nouveaux membres et anniversaires'; case 'organisation': return 'Annonces et réunions'; case 'messages': return 'Messages privés et mentions'; case 'systeme': return 'Mises à jour et maintenance'; default: return ''; } } IconData _getCategoryIcon(String category) { switch (category) { case 'evenements': return Icons.event; case 'cotisations': return Icons.payment; case 'solidarite': return Icons.volunteer_activism; case 'membres': return Icons.people; case 'organisation': return Icons.business; case 'messages': return Icons.message; case 'systeme': return Icons.settings; default: return Icons.notifications; } } String _getTypeDescription(TypeNotification type) { // Descriptions courtes pour chaque type switch (type) { case TypeNotification.nouvelEvenement: return 'Nouveaux événements créés'; case TypeNotification.rappelEvenement: return 'Rappels avant les événements'; case TypeNotification.cotisationDue: return 'Échéances de cotisations'; case TypeNotification.cotisationPayee: return 'Confirmations de paiement'; case TypeNotification.nouvelleDemandeAide: return 'Nouvelles demandes d\'aide'; case TypeNotification.nouveauMembre: return 'Nouveaux membres rejoignant'; case TypeNotification.anniversaireMembre: return 'Anniversaires des membres'; case TypeNotification.annonceGenerale: return 'Annonces importantes'; case TypeNotification.messagePrive: return 'Messages privés reçus'; default: return type.libelle; } } IconData _getTypeIconData(TypeNotification type) { switch (type.icone) { case 'event': return Icons.event; case 'payment': return Icons.payment; case 'help': return Icons.help; case 'person_add': return Icons.person_add; case 'cake': return Icons.cake; case 'campaign': return Icons.campaign; case 'mail': return Icons.mail; default: return Icons.notifications; } } Color _getTypeColor(TypeNotification type) { try { return Color(int.parse(type.couleur.replaceFirst('#', '0xFF'))); } catch (e) { return AppColors.primary; } } void _updatePreference(PreferencesNotificationEntity preferences) { context.read().add( UpdatePreferencesEvent(preferences: preferences), ); } void _toggleNotificationType(TypeNotification type, bool active) { context.read().add( ToggleNotificationTypeEvent(type: type, active: active), ); } void _showResetDialog() { showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Réinitialiser les préférences'), content: const Text( 'Êtes-vous sûr de vouloir réinitialiser toutes vos préférences ' 'de notifications aux valeurs par défaut ?', ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('Annuler'), ), TextButton( onPressed: () { Navigator.pop(context); context.read().add( const ResetPreferencesEvent(), ); }, style: TextButton.styleFrom( foregroundColor: AppColors.error, ), child: const Text('Réinitialiser'), ), ], ), ); } void _sendTestNotification() { context.read().add( const SendTestNotificationEvent(type: TypeNotification.annonceGenerale), ); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Notification de test envoyée'), duration: Duration(seconds: 2), ), ); } void _exportPreferences() { context.read().add( const ExportPreferencesEvent(), ); } void _showDelayPicker(String title, int currentValue, Function(int) onChanged) { showDialog( context: context, builder: (context) => AlertDialog( title: Text(title), content: Column( mainAxisSize: MainAxisSize.min, children: [ Text('Valeur actuelle: $currentValue secondes'), const SizedBox(height: 16), // Ici vous pourriez ajouter un slider ou un picker // Pour simplifier, on utilise des boutons prédéfinis Wrap( spacing: 8, children: [5, 10, 15, 30, 60].map((value) { return ChoiceChip( label: Text('${value}s'), selected: currentValue == value, onSelected: (selected) { if (selected) { onChanged(value); Navigator.pop(context); } }, ); }).toList(), ), ], ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('Annuler'), ), ], ), ); } void _showNumberPicker( String title, int currentValue, int min, int max, Function(int) onChanged, ) { showDialog( context: context, builder: (context) => AlertDialog( title: Text(title), content: Column( mainAxisSize: MainAxisSize.min, children: [ Text('Valeur actuelle: $currentValue'), const SizedBox(height: 16), // Slider pour choisir la valeur Slider( value: currentValue.toDouble(), min: min.toDouble(), max: max.toDouble(), divisions: max - min, label: currentValue.toString(), onChanged: (value) { // Mise à jour en temps réel }, onChangeEnd: (value) { onChanged(value.round()); Navigator.pop(context); }, ), ], ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('Annuler'), ), ], ), ); } }