import 'package:flutter/material.dart'; /// Page Notifications - UnionFlow Mobile /// /// Page complète de gestion des notifications avec historique, /// préférences, filtres et actions sur les notifications. class NotificationsPage extends StatefulWidget { const NotificationsPage({super.key}); @override State createState() => _NotificationsPageState(); } class _NotificationsPageState extends State with TickerProviderStateMixin { late TabController _tabController; String _selectedFilter = 'Toutes'; bool _showOnlyUnread = false; final List _filters = [ 'Toutes', 'Membres', 'Événements', 'Organisations', 'Système', ]; @override void initState() { super.initState(); _tabController = TabController(length: 2, vsync: this); } @override void dispose() { _tabController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xFFF8F9FA), body: Column( children: [ // Header harmonisé _buildHeader(), // Onglets _buildTabBar(), // Contenu des onglets Expanded( child: TabBarView( controller: _tabController, children: [ _buildNotificationsTab(), _buildPreferencesTab(), ], ), ), ], ), ); } /// Header harmonisé avec le design system Widget _buildHeader() { return Container( margin: const EdgeInsets.all(12), padding: const EdgeInsets.all(16), decoration: BoxDecoration( gradient: const LinearGradient( colors: [Color(0xFF6C5CE7), Color(0xFF5A4FCF)], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: const Color(0xFF6C5CE7).withOpacity(0.3), blurRadius: 20, offset: const Offset(0, 8), ), ], ), child: Row( children: [ Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.white.withOpacity(0.2), borderRadius: BorderRadius.circular(12), ), child: const Icon( Icons.notifications, color: Colors.white, size: 24, ), ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Notifications', style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: Colors.white, ), ), Text( 'Gérer vos notifications et préférences', style: TextStyle( fontSize: 14, color: Colors.white.withOpacity(0.8), ), ), ], ), ), Row( children: [ Container( decoration: BoxDecoration( color: Colors.white.withOpacity(0.2), borderRadius: BorderRadius.circular(8), ), child: IconButton( onPressed: () => _markAllAsRead(), icon: const Icon( Icons.done_all, color: Colors.white, ), tooltip: 'Tout marquer comme lu', ), ), const SizedBox(width: 8), Container( decoration: BoxDecoration( color: Colors.white.withOpacity(0.2), borderRadius: BorderRadius.circular(8), ), child: IconButton( onPressed: () => _showNotificationSettings(), icon: const Icon( Icons.settings, color: Colors.white, ), tooltip: 'Paramètres', ), ), ], ), ], ), ); } /// Barre d'onglets Widget _buildTabBar() { return Container( margin: const EdgeInsets.symmetric(horizontal: 12), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 2), ), ], ), child: TabBar( controller: _tabController, labelColor: const Color(0xFF6C5CE7), unselectedLabelColor: Colors.grey[600], indicatorColor: const Color(0xFF6C5CE7), indicatorWeight: 3, indicatorSize: TabBarIndicatorSize.tab, labelStyle: const TextStyle( fontWeight: FontWeight.w600, fontSize: 14, ), unselectedLabelStyle: const TextStyle( fontWeight: FontWeight.normal, fontSize: 14, ), tabs: const [ Tab( icon: Icon(Icons.inbox), text: 'Notifications', ), Tab( icon: Icon(Icons.tune), text: 'Préférences', ), ], ), ); } /// Onglet des notifications Widget _buildNotificationsTab() { return Column( children: [ const SizedBox(height: 16), // Filtres et options _buildFiltersSection(), // Liste des notifications Expanded( child: _buildNotificationsList(), ), ], ); } /// Section filtres Widget _buildFiltersSection() { return Container( margin: const EdgeInsets.symmetric(horizontal: 12), padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 2), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon( Icons.filter_list, color: Colors.grey[600], size: 20, ), const SizedBox(width: 8), Text( 'Filtres', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: Colors.grey[800], ), ), const Spacer(), Switch( value: _showOnlyUnread, onChanged: (value) { setState(() { _showOnlyUnread = value; }); }, activeColor: const Color(0xFF6C5CE7), ), const SizedBox(width: 8), Text( 'Non lues uniquement', style: TextStyle( fontSize: 12, color: Colors.grey[600], ), ), ], ), const SizedBox(height: 12), Wrap( spacing: 8, runSpacing: 8, children: _filters.map((filter) { final isSelected = _selectedFilter == filter; return _buildFilterChip(filter, isSelected); }).toList(), ), ], ), ); } /// Chip de filtre Widget _buildFilterChip(String label, bool isSelected) { return InkWell( onTap: () { setState(() { _selectedFilter = label; }); }, borderRadius: BorderRadius.circular(20), child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration( color: isSelected ? const Color(0xFF6C5CE7) : Colors.grey[50], borderRadius: BorderRadius.circular(20), border: Border.all( color: isSelected ? const Color(0xFF6C5CE7) : Colors.grey[300]!, width: 1, ), ), child: Text( label, style: TextStyle( color: isSelected ? Colors.white : Colors.grey[700], fontWeight: FontWeight.w600, fontSize: 13, ), ), ), ); } /// Liste des notifications Widget _buildNotificationsList() { final notifications = _getFilteredNotifications(); if (notifications.isEmpty) { return _buildEmptyState(); } return ListView.builder( padding: const EdgeInsets.all(12), itemCount: notifications.length, itemBuilder: (context, index) { final notification = notifications[index]; return _buildNotificationCard(notification); }, ); } /// État vide Widget _buildEmptyState() { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( padding: const EdgeInsets.all(24), decoration: BoxDecoration( color: const Color(0xFF6C5CE7).withOpacity(0.1), borderRadius: BorderRadius.circular(50), ), child: const Icon( Icons.notifications_none, size: 48, color: Color(0xFF6C5CE7), ), ), const SizedBox(height: 16), const Text( 'Aucune notification', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, color: Color(0xFF1F2937), ), ), const SizedBox(height: 8), Text( _showOnlyUnread ? 'Toutes vos notifications ont été lues' : 'Vous n\'avez aucune notification pour le moment', style: TextStyle( fontSize: 14, color: Colors.grey[600], ), textAlign: TextAlign.center, ), ], ), ); } /// Carte de notification Widget _buildNotificationCard(Map notification) { final isRead = notification['isRead'] as bool; final type = notification['type'] as String; final color = _getNotificationColor(type); return Container( margin: const EdgeInsets.only(bottom: 12), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), border: isRead ? null : Border.all( color: const Color(0xFF6C5CE7).withOpacity(0.3), width: 2, ), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 2), ), ], ), child: InkWell( onTap: () => _handleNotificationTap(notification), borderRadius: BorderRadius.circular(16), child: Padding( padding: const EdgeInsets.all(16), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Icône et indicateur Stack( children: [ Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: color.withOpacity(0.1), borderRadius: BorderRadius.circular(12), ), child: Icon( _getNotificationIcon(type), color: color, size: 20, ), ), if (!isRead) Positioned( top: 0, right: 0, child: Container( width: 8, height: 8, decoration: const BoxDecoration( color: Color(0xFF6C5CE7), shape: BoxShape.circle, ), ), ), ], ), const SizedBox(width: 12), // Contenu Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Text( notification['title'], style: TextStyle( fontSize: 14, fontWeight: isRead ? FontWeight.w500 : FontWeight.w600, color: isRead ? Colors.grey[700] : const Color(0xFF1F2937), ), ), ), Text( notification['time'], style: TextStyle( fontSize: 12, color: Colors.grey[500], ), ), ], ), const SizedBox(height: 4), Text( notification['message'], style: TextStyle( fontSize: 13, color: Colors.grey[600], height: 1.3, ), maxLines: 2, overflow: TextOverflow.ellipsis, ), if (notification['actionText'] != null) ...[ const SizedBox(height: 8), Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: color.withOpacity(0.1), borderRadius: BorderRadius.circular(20), ), child: Text( notification['actionText'], style: TextStyle( fontSize: 12, color: color, fontWeight: FontWeight.w600, ), ), ), ], ], ), ), // Menu actions PopupMenuButton( onSelected: (action) => _handleNotificationAction(notification, action), itemBuilder: (context) => [ PopupMenuItem( value: isRead ? 'mark_unread' : 'mark_read', child: Row( children: [ Icon( isRead ? Icons.mark_email_unread : Icons.mark_email_read, size: 18, color: Colors.grey[600], ), const SizedBox(width: 8), Text(isRead ? 'Marquer non lu' : 'Marquer comme lu'), ], ), ), const PopupMenuItem( value: 'delete', child: Row( children: [ Icon( Icons.delete, size: 18, color: Colors.red, ), SizedBox(width: 8), Text('Supprimer', style: TextStyle(color: Colors.red)), ], ), ), ], child: Icon( Icons.more_vert, color: Colors.grey[400], size: 20, ), ), ], ), ), ), ); } /// Onglet préférences Widget _buildPreferencesTab() { return SingleChildScrollView( padding: const EdgeInsets.all(12), child: Column( children: [ const SizedBox(height: 16), // Notifications push _buildPreferenceSection( 'Notifications push', 'Recevoir des notifications sur votre appareil', Icons.notifications_active, [ _buildPreferenceItem( 'Activer les notifications', 'Recevoir toutes les notifications', true, (value) => _updatePreference('push_enabled', value), ), _buildPreferenceItem( 'Sons et vibrations', 'Alertes sonores et vibrations', true, (value) => _updatePreference('sound_enabled', value), ), ], ), const SizedBox(height: 16), // Types de notifications _buildPreferenceSection( 'Types de notifications', 'Choisir les notifications à recevoir', Icons.category, [ _buildPreferenceItem( 'Nouveaux membres', 'Adhésions et modifications de profil', true, (value) => _updatePreference('members_notifications', value), ), _buildPreferenceItem( 'Événements', 'Créations, modifications et rappels', true, (value) => _updatePreference('events_notifications', value), ), _buildPreferenceItem( 'Organisations', 'Changements dans les organisations', false, (value) => _updatePreference('organizations_notifications', value), ), _buildPreferenceItem( 'Système', 'Mises à jour et maintenance', true, (value) => _updatePreference('system_notifications', value), ), ], ), const SizedBox(height: 16), // Email _buildPreferenceSection( 'Notifications email', 'Recevoir des notifications par email', Icons.email, [ _buildPreferenceItem( 'Résumé quotidien', 'Récapitulatif des activités du jour', false, (value) => _updatePreference('daily_summary', value), ), _buildPreferenceItem( 'Résumé hebdomadaire', 'Rapport hebdomadaire des activités', true, (value) => _updatePreference('weekly_summary', value), ), _buildPreferenceItem( 'Notifications importantes', 'Alertes critiques uniquement', true, (value) => _updatePreference('important_emails', value), ), ], ), const SizedBox(height: 80), ], ), ); } /// Section de préférence Widget _buildPreferenceSection( String title, String subtitle, IconData icon, List items, ) { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 2), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon( icon, color: Colors.grey[600], size: 20, ), const SizedBox(width: 8), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: Colors.grey[800], ), ), Text( subtitle, style: TextStyle( fontSize: 12, color: Colors.grey[600], ), ), ], ), ), ], ), const SizedBox(height: 16), ...items, ], ), ); } /// Élément de préférence Widget _buildPreferenceItem( String title, String subtitle, bool value, Function(bool) onChanged, ) { return Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: Color(0xFF1F2937), ), ), Text( subtitle, style: TextStyle( fontSize: 12, color: Colors.grey[600], ), ), ], ), ), Switch( value: value, onChanged: onChanged, activeColor: const Color(0xFF6C5CE7), ), ], ), ); } // ==================== MÉTHODES DE DONNÉES ==================== /// Obtenir les notifications filtrées List> _getFilteredNotifications() { final allNotifications = [ { 'id': '1', 'type': 'Membres', 'title': 'Nouveau membre inscrit', 'message': 'Marie Dubois a rejoint l\'organisation Syndicat CGT Métallurgie', 'time': '2 min', 'isRead': false, 'actionText': 'Voir le profil', }, { 'id': '2', 'type': 'Événements', 'title': 'Rappel d\'événement', 'message': 'L\'assemblée générale commence dans 1 heure (14h00)', 'time': '1h', 'isRead': false, 'actionText': 'Voir l\'événement', }, { 'id': '3', 'type': 'Organisations', 'title': 'Modification d\'organisation', 'message': 'Les informations de contact de Fédération CGT ont été mises à jour', 'time': '3h', 'isRead': true, 'actionText': null, }, { 'id': '4', 'type': 'Système', 'title': 'Mise à jour disponible', 'message': 'Une nouvelle version de UnionFlow est disponible (v2.1.0)', 'time': '1j', 'isRead': true, 'actionText': 'Mettre à jour', }, { 'id': '5', 'type': 'Membres', 'title': 'Cotisation en retard', 'message': '5 membres ont des cotisations en retard ce mois-ci', 'time': '2j', 'isRead': false, 'actionText': 'Voir la liste', }, { 'id': '6', 'type': 'Événements', 'title': 'Événement annulé', 'message': 'La formation "Négociation collective" du 15/12 a été annulée', 'time': '3j', 'isRead': true, 'actionText': null, }, ]; var filtered = allNotifications; // Filtrer par type if (_selectedFilter != 'Toutes') { filtered = filtered.where((n) => n['type'] == _selectedFilter).toList(); } // Filtrer par statut de lecture if (_showOnlyUnread) { filtered = filtered.where((n) => !(n['isRead'] as bool)).toList(); } return filtered; } /// Obtenir la couleur selon le type de notification Color _getNotificationColor(String type) { switch (type) { case 'Membres': return const Color(0xFF6C5CE7); case 'Événements': return const Color(0xFF00B894); case 'Organisations': return const Color(0xFF0984E3); case 'Système': return const Color(0xFFE17055); default: return Colors.grey; } } /// Obtenir l'icône selon le type de notification IconData _getNotificationIcon(String type) { switch (type) { case 'Membres': return Icons.person_add; case 'Événements': return Icons.event; case 'Organisations': return Icons.business; case 'Système': return Icons.system_update; default: return Icons.notifications; } } // ==================== MÉTHODES D'ACTION ==================== /// Gérer le tap sur une notification void _handleNotificationTap(Map notification) { // Marquer comme lue si non lue if (!(notification['isRead'] as bool)) { setState(() { notification['isRead'] = true; }); } // Action selon le type final type = notification['type'] as String; switch (type) { case 'Membres': _showSuccessSnackBar('Navigation vers la gestion des membres'); break; case 'Événements': _showSuccessSnackBar('Navigation vers les événements'); break; case 'Organisations': _showSuccessSnackBar('Navigation vers les organisations'); break; case 'Système': _showSystemNotificationDialog(notification); break; } } /// Gérer les actions du menu contextuel void _handleNotificationAction(Map notification, String action) { switch (action) { case 'mark_read': setState(() { notification['isRead'] = true; }); _showSuccessSnackBar('Notification marquée comme lue'); break; case 'mark_unread': setState(() { notification['isRead'] = false; }); _showSuccessSnackBar('Notification marquée comme non lue'); break; case 'delete': _showDeleteConfirmationDialog(notification); break; } } /// Marquer toutes les notifications comme lues void _markAllAsRead() { showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Marquer tout comme lu'), content: const Text( 'Êtes-vous sûr de vouloir marquer toutes les notifications comme lues ?', ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('Annuler'), ), ElevatedButton( onPressed: () { Navigator.of(context).pop(); setState(() { // Marquer toutes les notifications comme lues final notifications = _getFilteredNotifications(); for (var notification in notifications) { notification['isRead'] = true; } }); _showSuccessSnackBar('Toutes les notifications ont été marquées comme lues'); }, style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF6C5CE7), foregroundColor: Colors.white, ), child: const Text('Confirmer'), ), ], ), ); } /// Afficher les paramètres de notification void _showNotificationSettings() { showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Paramètres de notification'), content: const Text( 'Utilisez l\'onglet "Préférences" pour configurer vos notifications ' 'ou accédez aux paramètres système de votre appareil.', ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('Fermer'), ), ElevatedButton( onPressed: () { Navigator.of(context).pop(); _tabController.animateTo(1); // Aller à l'onglet Préférences }, style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF6C5CE7), foregroundColor: Colors.white, ), child: const Text('Voir les préférences'), ), ], ), ); } /// Dialogue de confirmation de suppression void _showDeleteConfirmationDialog(Map notification) { showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Supprimer la notification'), content: const Text( 'Êtes-vous sûr de vouloir supprimer cette notification ? ' 'Cette action est irréversible.', ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('Annuler'), ), ElevatedButton( onPressed: () { Navigator.of(context).pop(); setState(() { // Simuler la suppression (dans une vraie app, on supprimerait de la base de données) }); _showSuccessSnackBar('Notification supprimée'); }, style: ElevatedButton.styleFrom( backgroundColor: Colors.red, foregroundColor: Colors.white, ), child: const Text('Supprimer'), ), ], ), ); } /// Dialogue pour les notifications système void _showSystemNotificationDialog(Map notification) { showDialog( context: context, builder: (context) => AlertDialog( title: Text(notification['title']), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(notification['message']), const SizedBox(height: 16), if (notification['actionText'] != null) Container( width: double.infinity, padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: const Color(0xFFE17055).withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), child: Text( 'Action disponible : ${notification['actionText']}', style: const TextStyle( color: Color(0xFFE17055), fontWeight: FontWeight.w600, ), ), ), ], ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('Fermer'), ), if (notification['actionText'] != null) ElevatedButton( onPressed: () { Navigator.of(context).pop(); _showSuccessSnackBar('Action "${notification['actionText']}" exécutée'); }, style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFFE17055), foregroundColor: Colors.white, ), child: Text(notification['actionText']), ), ], ), ); } /// Mettre à jour une préférence void _updatePreference(String key, bool value) { // Ici on sauvegarderait dans les préférences locales ou sur le serveur _showSuccessSnackBar( value ? 'Préférence activée' : 'Préférence désactivée' ); } /// Afficher un message de succès void _showSuccessSnackBar(String message) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(message), backgroundColor: const Color(0xFF00B894), behavior: SnackBarBehavior.floating, ), ); } /// Afficher un message d'erreur void _showErrorSnackBar(String message) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(message), backgroundColor: const Color(0xFFE74C3C), behavior: SnackBarBehavior.floating, ), ); } }