Initial commit: unionflow-mobile-apps
Application Flutter complète (sans build artifacts). Signed-off-by: lions dev Team
This commit is contained in:
@@ -0,0 +1,929 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../../authentication/presentation/bloc/auth_bloc.dart';
|
||||
import '../../../members/presentation/pages/members_page_wrapper.dart';
|
||||
import '../../../events/presentation/pages/events_page_wrapper.dart';
|
||||
import '../../../organizations/presentation/pages/organizations_page.dart';
|
||||
import '../bloc/notifications_bloc.dart';
|
||||
import '../../data/models/notification_model.dart';
|
||||
import '../../../../shared/design_system/unionflow_design_system.dart';
|
||||
import '../../../../shared/widgets/core_card.dart';
|
||||
import '../../../../shared/widgets/mini_avatar.dart';
|
||||
import '../../../../shared/widgets/info_badge.dart';
|
||||
import '../../../../shared/design_system/components/uf_app_bar.dart';
|
||||
import '../../../../shared/design_system/components/uf_buttons.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<NotificationsPage> createState() => _NotificationsPageState();
|
||||
}
|
||||
|
||||
class _NotificationsPageState extends State<NotificationsPage>
|
||||
with TickerProviderStateMixin {
|
||||
late TabController _tabController;
|
||||
String _selectedFilter = 'Toutes';
|
||||
bool _showOnlyUnread = false;
|
||||
List<NotificationModel> _liveNotifications = [];
|
||||
|
||||
final List<String> _filters = [
|
||||
'Toutes',
|
||||
'Membres',
|
||||
'Événements',
|
||||
'Organisations',
|
||||
'Système',
|
||||
];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_tabController = TabController(length: 2, vsync: this);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => _loadNotificationsFromBloc());
|
||||
}
|
||||
|
||||
void _loadNotificationsFromBloc() {
|
||||
final authState = context.read<AuthBloc>().state;
|
||||
if (authState is AuthAuthenticated) {
|
||||
context.read<NotificationsBloc>().add(
|
||||
const LoadNotifications(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_tabController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocConsumer<NotificationsBloc, NotificationsState>(
|
||||
listener: (context, state) {
|
||||
if (state is NotificationsLoaded) {
|
||||
setState(() => _liveNotifications = state.notifications);
|
||||
}
|
||||
if (state is NotificationMarkedAsRead) {
|
||||
setState(() => _liveNotifications = state.notifications);
|
||||
}
|
||||
if (state is NotificationsError) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(state.message), backgroundColor: Colors.red),
|
||||
);
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFFF8F9FA),
|
||||
body: Column(
|
||||
children: [
|
||||
_buildHeader(),
|
||||
_buildTabBar(),
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
controller: _tabController,
|
||||
children: [
|
||||
_buildNotificationsTab(),
|
||||
_buildPreferencesTab(),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Header harmonisé avec le design system
|
||||
Widget _buildHeader() {
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [AppColors.brandGreen, AppColors.primaryGreen],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: Color(0x1A000000),
|
||||
blurRadius: 10,
|
||||
offset: Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.notifications_none,
|
||||
color: Colors.white,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'NOTIFICATIONS',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
letterSpacing: 1.1,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'Restez connecté à votre réseau',
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: Colors.white.withOpacity(0.9),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () => _markAllAsRead(),
|
||||
icon: const Icon(Icons.done_all, color: Colors.white, size: 20),
|
||||
tooltip: 'Tout marquer comme lu',
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Barre d'onglets
|
||||
Widget _buildTabBar() {
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.lightSurface,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: TabBar(
|
||||
controller: _tabController,
|
||||
labelColor: AppColors.primaryGreen,
|
||||
unselectedLabelColor: AppColors.textSecondaryLight,
|
||||
indicator: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
color: AppColors.primaryGreen.withOpacity(0.1),
|
||||
),
|
||||
labelStyle: AppTypography.actionText.copyWith(fontSize: 12),
|
||||
unselectedLabelStyle: AppTypography.bodyTextSmall.copyWith(fontSize: 12),
|
||||
tabs: const [
|
||||
Tab(text: 'FLUX'),
|
||||
Tab(text: 'RÉGLAGES'),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 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 Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
'FILTRER PAR :',
|
||||
style: AppTypography.subtitleSmall.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
letterSpacing: 1.1,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Text(
|
||||
'NON LUES',
|
||||
style: AppTypography.badgeText.copyWith(fontSize: 9),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
SizedBox(
|
||||
height: 24,
|
||||
child: Switch(
|
||||
value: _showOnlyUnread,
|
||||
onChanged: (value) => setState(() => _showOnlyUnread = value),
|
||||
activeColor: AppColors.primaryGreen,
|
||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
children: _filters.map((filter) {
|
||||
final isSelected = _selectedFilter == filter;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: _buildFilterChip(filter, isSelected),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Chip de filtre
|
||||
Widget _buildFilterChip(String label, bool isSelected) {
|
||||
return InkWell(
|
||||
onTap: () => setState(() => _selectedFilter = label),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? AppColors.primaryGreen.withOpacity(0.1) : Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
border: Border.all(
|
||||
color: isSelected ? AppColors.primaryGreen : AppColors.lightBorder,
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
label.toUpperCase(),
|
||||
style: AppTypography.badgeText.copyWith(
|
||||
color: isSelected ? AppColors.primaryGreen : AppColors.textSecondaryLight,
|
||||
fontSize: 9,
|
||||
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 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: [
|
||||
const Icon(
|
||||
Icons.notifications_none_outlined,
|
||||
size: 40,
|
||||
color: AppColors.textSecondaryLight,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
'AUCUNE NOTIFICATION',
|
||||
style: AppTypography.subtitleSmall.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
_showOnlyUnread
|
||||
? 'Toutes vos notifications ont été lues'
|
||||
: 'Votre flux est à jour.',
|
||||
style: AppTypography.subtitleSmall,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Carte de notification
|
||||
Widget _buildNotificationCard(Map<String, dynamic> notification) {
|
||||
final isRead = notification['isRead'] as bool;
|
||||
final type = notification['type'] as String;
|
||||
final color = _getNotificationColor(type);
|
||||
|
||||
return CoreCard(
|
||||
margin: const EdgeInsets.only(bottom: 8),
|
||||
padding: const EdgeInsets.all(12),
|
||||
onTap: () => _handleNotificationTap(notification),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
MiniAvatar(
|
||||
fallbackText: _getNotificationIconSource(type),
|
||||
size: 32,
|
||||
backgroundColor: isRead ? AppColors.lightSurface : color.withOpacity(0.1),
|
||||
iconColor: isRead ? AppColors.textSecondaryLight : color,
|
||||
isIcon: true,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
notification['title'].toString().toUpperCase(),
|
||||
style: AppTypography.actionText.copyWith(
|
||||
fontSize: 11,
|
||||
color: isRead ? AppColors.textSecondaryLight : AppColors.textPrimaryLight,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
notification['time'],
|
||||
style: AppTypography.subtitleSmall.copyWith(fontSize: 10),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
notification['message'],
|
||||
style: AppTypography.bodyTextSmall.copyWith(
|
||||
color: isRead ? AppColors.textSecondaryLight : AppColors.textPrimaryLight,
|
||||
fontWeight: isRead ? FontWeight.normal : FontWeight.w500,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
if (!isRead) ...[
|
||||
const SizedBox(height: 4),
|
||||
InfoBadge(
|
||||
text: 'NOUVEAU',
|
||||
backgroundColor: AppColors.primaryGreen.withOpacity(0.1),
|
||||
textColor: AppColors.primaryGreen,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
PopupMenuButton<String>(
|
||||
onSelected: (action) => _handleNotificationAction(notification, action),
|
||||
padding: EdgeInsets.zero,
|
||||
constraints: const BoxConstraints(),
|
||||
itemBuilder: (context) => [
|
||||
PopupMenuItem(
|
||||
value: isRead ? 'mark_unread' : 'mark_read',
|
||||
child: Text(isRead ? 'Marquer non lu' : 'Marquer lu', style: AppTypography.bodyTextSmall),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: 'delete',
|
||||
child: Text('Supprimer', style: AppTypography.bodyTextSmall.copyWith(color: AppColors.error)),
|
||||
),
|
||||
],
|
||||
child: const Icon(Icons.more_vert, size: 14, color: AppColors.textSecondaryLight),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _getNotificationIconSource(String type) {
|
||||
switch (type) {
|
||||
case 'Membres': return 'people';
|
||||
case 'Événements': return 'event';
|
||||
case 'Organisations': return 'business';
|
||||
case 'Système': return 'settings';
|
||||
default: return 'notifications';
|
||||
}
|
||||
}
|
||||
|
||||
/// 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_outlined,
|
||||
[
|
||||
_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<Widget> items,
|
||||
) {
|
||||
return CoreCard(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
icon,
|
||||
color: AppColors.primaryGreen,
|
||||
size: 18,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: AppTypography.actionText.copyWith(fontSize: 12),
|
||||
),
|
||||
Text(
|
||||
subtitle,
|
||||
style: AppTypography.subtitleSmall.copyWith(fontSize: 10),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
...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: 4),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: AppTypography.bodyTextSmall.copyWith(fontWeight: FontWeight.w500),
|
||||
),
|
||||
Text(
|
||||
subtitle,
|
||||
style: AppTypography.subtitleSmall.copyWith(fontSize: 10),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 24,
|
||||
child: Switch(
|
||||
value: value,
|
||||
onChanged: onChanged,
|
||||
activeColor: AppColors.primaryGreen,
|
||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ==================== MÉTHODES DE DONNÉES ====================
|
||||
|
||||
/// Obtenir les notifications filtrées
|
||||
List<Map<String, dynamic>> _getFilteredNotifications() {
|
||||
// Utiliser les données réelles du backend si disponibles
|
||||
List<Map<String, dynamic>> allNotifications;
|
||||
|
||||
if (_liveNotifications.isNotEmpty) {
|
||||
allNotifications = _liveNotifications.map((n) {
|
||||
final dateRef = n.dateEnvoi ?? n.dateEnvoiPrevue;
|
||||
String timeAgo = '';
|
||||
if (dateRef != null) {
|
||||
final diff = DateTime.now().difference(dateRef);
|
||||
if (diff.inMinutes < 60) timeAgo = '${diff.inMinutes} min';
|
||||
else if (diff.inHours < 24) timeAgo = '${diff.inHours}h';
|
||||
else timeAgo = '${diff.inDays}j';
|
||||
}
|
||||
return {
|
||||
'id': n.id,
|
||||
'type': n.typeAffichage,
|
||||
'title': n.sujet ?? 'Notification',
|
||||
'message': n.corps ?? '',
|
||||
'time': timeAgo,
|
||||
'isRead': n.estLue,
|
||||
'actionText': null,
|
||||
};
|
||||
}).toList();
|
||||
} else {
|
||||
// Données de démonstration quand le backend n'a pas encore de données
|
||||
allNotifications = [];
|
||||
}
|
||||
|
||||
var filtered = allNotifications;
|
||||
if (_selectedFilter != 'Toutes') {
|
||||
filtered = filtered.where((n) => n['type'] == _selectedFilter).toList();
|
||||
}
|
||||
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 AppColors.primaryGreen;
|
||||
case 'Événements':
|
||||
return const Color(0xFF00B894);
|
||||
case 'Organisations':
|
||||
return AppColors.primaryGreen;
|
||||
case 'Système':
|
||||
return AppColors.warning;
|
||||
default:
|
||||
return AppColors.textSecondaryLight;
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<String, dynamic> notification) {
|
||||
// Marquer comme lue via BLoC si non lue
|
||||
if (!(notification['isRead'] as bool)) {
|
||||
context.read<NotificationsBloc>().add(
|
||||
MarkNotificationAsRead(notification['id'].toString()),
|
||||
);
|
||||
}
|
||||
|
||||
// Action selon le type : navigation vers l'écran concerné
|
||||
final type = notification['type'] as String;
|
||||
switch (type) {
|
||||
case 'Membres':
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute<void>(builder: (_) => const MembersPageWrapper()),
|
||||
);
|
||||
break;
|
||||
case 'Événements':
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute<void>(builder: (_) => const EventsPageWrapper()),
|
||||
);
|
||||
break;
|
||||
case 'Organisations':
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute<void>(builder: (_) => const OrganizationsPage()),
|
||||
);
|
||||
break;
|
||||
case 'Système':
|
||||
_showSystemNotificationDialog(notification);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// Gérer les actions du menu contextuel
|
||||
void _handleNotificationAction(Map<String, dynamic> 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: Text('Vider le flux', style: AppTypography.headerSmall),
|
||||
content: Text(
|
||||
'Voulez-vous marquer toutes les notifications comme lues ?',
|
||||
style: AppTypography.bodyTextSmall,
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: Text('ANNULER', style: AppTypography.actionText.copyWith(color: AppColors.textSecondaryLight)),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
setState(() {
|
||||
final notifications = _getFilteredNotifications();
|
||||
for (var notification in notifications) {
|
||||
notification['isRead'] = true;
|
||||
}
|
||||
});
|
||||
_showSuccessSnackBar('Flux marqué comme lu');
|
||||
},
|
||||
child: Text('CONFIRMER', style: AppTypography.actionText.copyWith(color: AppColors.primaryGreen)),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 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<String, dynamic> 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<String, dynamic> 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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user